diff --git a/doc.go b/doc.go index 0d1b6b8e..8e8d1f04 100644 --- a/doc.go +++ b/doc.go @@ -35,6 +35,7 @@ var ( errInsertIntoOtherStem = errors.New("insert splits a stem where it should not happen") errUnknownNodeType = errors.New("unknown node type detected") errMissingNodeInStateless = errors.New("trying to access a node that is missing from the stateless view") + errIsPOAStub = errors.New("trying to read/write a proof of absence leaf node") ) const ( diff --git a/proof_test.go b/proof_test.go index 8860b620..c9f498e1 100644 --- a/proof_test.go +++ b/proof_test.go @@ -32,6 +32,8 @@ import ( "fmt" "reflect" "testing" + + "github.com/crate-crypto/go-ipa/common" ) func TestProofVerifyTwoLeaves(t *testing.T) { @@ -472,7 +474,7 @@ func TestProofOfAbsenceOtherMultipleLeaves(t *testing.T) { if err := root.Insert(key, testValue, nil); err != nil { t.Fatalf("could not insert key: %v", err) } - root.Commit() + rootC := root.Commit() ret1, _ := hex.DecodeString("0303030303030303030303030303030303030303030303030303030303030300") ret2, _ := hex.DecodeString("0303030303030303030303030303030303030303030303030303030303030301") @@ -485,6 +487,45 @@ func TestProofOfAbsenceOtherMultipleLeaves(t *testing.T) { if len(proof.PoaStems) > 1 { t.Fatalf("invalid number of proof-of-absence stems: %d", len(proof.PoaStems)) } + + deserialized, err := PreStateTreeFromProof(proof, rootC) + if err != nil { + t.Fatalf("error deserializing %v", err) + } + + got, err := deserialized.Get(ret1, nil) + if err != nil { + t.Fatalf("error while trying to read missing value: %v", err) + } + if got != nil { + t.Fatalf("should have returned nil, got: %v", got) + } + + // simulate the execution of a tx that creates a leaf at an address that isn't the one that is + // proven for absence, but needs to be inserted in the proof-of-absence stem. + // It differs from the poa stem here: 🠃 + ret3, _ := hex.DecodeString("0303030304030303030303030303030303030303030303030303030303030300") + err = deserialized.Insert(ret3, testValue, nil) + if err != nil { + t.Fatalf("error inserting value in proof-of-asbsence stem: %v", err) + } + + // check that there are splits up to depth 4 + node := deserialized.(*InternalNode) + for node.depth < 4 { + child, ok := node.children[ret3[node.depth]].(*InternalNode) + if !ok { + t.Fatalf("expected Internal node at depth %d, trie = %s", node.depth, ToDot(deserialized)) + } + node = child + } + + if _, ok := node.children[ret3[4]].(*LeafNode); !ok { + t.Fatalf("expected leaf node at depth 5, got %v", node.children[ret3[4]]) + } + if ln, ok := node.children[key[4]].(*LeafNode); !ok || !ln.isPOAStub { + t.Fatalf("expected unknown node at depth 5, got %v", node.children[key[4]]) + } } func TestProofOfAbsenceNoneMultipleStems(t *testing.T) { @@ -990,4 +1031,28 @@ func TestGenerateProofWithOnlyAbsentKeys(t *testing.T) { if ok, err := VerifyVerkleProof(dproof, pe.Cis, pe.Zis, pe.Yis, cfg); !ok || err != nil { t.Fatalf("reconstructed proof didn't verify: %v", err) } + + // Double-check that if we try to access any key in 40000000000000000000000000000000000000000000000000000000000000{XX} + // in the reconstructed tree, we get an error. This LeafNode is only supposed to prove + // the absence of 40100000000000000000000000000000000000000000000000000000000000{YY}, so + // we don't know anything about any value for slots XX. + for i := 0; i < common.VectorLength; i++ { + var key [32]byte + copy(key[:], presentKey) + key[31] = byte(i) + if _, err := droot.Get(key[:], nil); err != errIsPOAStub { + t.Fatalf("expected ErrPOALeafValue, got %v", err) + } + } + + // The same applies to trying to insert values in this LeafNode, this shouldn't be allowed since we don't know + // anything about C1 or C2 to do a proper updating. + for i := 0; i < common.VectorLength; i++ { + var key [32]byte + copy(key[:], presentKey) + key[31] = byte(i) + if err := droot.Insert(key[:], zeroKeyTest, nil); err != errIsPOAStub { + t.Fatalf("expected ErrPOALeafValue, got %v", err) + } + } } diff --git a/tree.go b/tree.go index c1a4ae8c..6a6a6175 100644 --- a/tree.go +++ b/tree.go @@ -183,6 +183,11 @@ type ( c1, c2 *Point depth byte + + // IsPOAStub indicates if this LeafNode is a proof of absence + // for a steam that isn't present in the tree. This flag is only + // true in the context of a stateless tree. + isPOAStub bool } ) @@ -380,6 +385,10 @@ func (n *InternalNode) InsertStem(stem []byte, values [][]byte, resolver NodeRes // splits. return n.InsertStem(stem, values, resolver) case *LeafNode: + if child.isPOAStub { + return errIsPOAStub + } + n.cowChild(nChild) if equalPaths(child.stem, stem) { return child.insertMultiple(stem, values) @@ -444,11 +453,10 @@ func (n *InternalNode) CreatePath(path []byte, stemInfo stemInfo, comms []*Point stem: stemInfo.stem, values: nil, depth: n.depth + 1, + isPOAStub: true, } n.children[path[0]] = newchild comms = comms[1:] - newchild.c1 = new(Point) - newchild.c2 = new(Point) case extStatusPresent: // insert stem newchild := &LeafNode{ @@ -527,6 +535,9 @@ func (n *InternalNode) GetStem(stem []byte, resolver NodeResolverFn) ([][]byte, // splits. return n.GetStem(stem, resolver) case *LeafNode: + if child.isPOAStub { + return nil, errIsPOAStub + } if equalPaths(child.stem, stem) { return child.values, nil } @@ -1024,6 +1035,10 @@ func (n *InternalNode) touchCoW(index byte) { } func (n *LeafNode) Insert(key []byte, value []byte, _ NodeResolverFn) error { + if n.isPOAStub { + return errIsPOAStub + } + if len(key) != StemSize+1 { return fmt.Errorf("invalid key size: %d", len(key)) } @@ -1278,6 +1293,10 @@ func (n *LeafNode) Delete(k []byte, _ NodeResolverFn) (bool, error) { } func (n *LeafNode) Get(k []byte, _ NodeResolverFn) ([]byte, error) { + if n.isPOAStub { + return nil, errIsPOAStub + } + if !equalPaths(k, n.stem) { // If keys differ, return nil in order to // signal that the key isn't present in the @@ -1378,9 +1397,6 @@ func (n *LeafNode) GetProofItems(keys keylist, _ NodeResolverFn) (*ProofElements if err := StemFromBytes(&poly[1], n.stem); err != nil { return nil, nil, nil, fmt.Errorf("error serializing stem '%x': %w", n.stem, err) } - if err := banderwagon.BatchMapToScalarField([]*Fr{&poly[2], &poly[3]}, []*Point{n.c1, n.c2}); err != nil { - return nil, nil, nil, fmt.Errorf("batch mapping to scalar fields: %s", err) - } // First pass: add top-level elements first var hasC1, hasC2 bool @@ -1396,6 +1412,22 @@ func (n *LeafNode) GetProofItems(keys keylist, _ NodeResolverFn) (*ProofElements } } } + + // If this tree is a full tree (i.e: not a stateless tree), we know we have c1 and c2 values. + // Also, we _need_ them independently of hasC1 or hasC2 since the prover needs `Fis`. + if !n.isPOAStub { + if err := banderwagon.BatchMapToScalarField([]*Fr{&poly[2], &poly[3]}, []*Point{n.c1, n.c2}); err != nil { + return nil, nil, nil, fmt.Errorf("batch mapping to scalar fields: %s", err) + } + } else { + if hasC1 { + n.c1.MapToScalarField(&poly[2]) + } + if hasC2 { + n.c2.MapToScalarField(&poly[3]) + } + } + if hasC1 { pe.Cis = append(pe.Cis, n.commitment) pe.Zis = append(pe.Zis, 2)