diff --git a/conversion.go b/conversion.go index 547199e..e9b74cc 100644 --- a/conversion.go +++ b/conversion.go @@ -104,7 +104,7 @@ func (n *InternalNode) InsertMigratedLeaves(leaves []LeafNode, curTs AccessTimes // We first mark all children of the subtreess that we'll update in parallel, // so the subtree updating doesn't produce a concurrent access to n.cowChild(...). - lastChildrenIdx := -1 + var lastChildrenIdx = -1 for i := range leaves { if int(leaves[i].stem[0]) != lastChildrenIdx { lastChildrenIdx = int(leaves[i].stem[0]) diff --git a/doc.go b/doc.go index 37c44d7..2377132 100644 --- a/doc.go +++ b/doc.go @@ -38,7 +38,7 @@ var ( 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") - errEpochExpired = errors.New("trying to access an expired leaf node") + errExpired = errors.New("trying to access an expired leaf node") ) const ( @@ -46,4 +46,5 @@ const ( extStatusAbsentEmpty = byte(iota) // missing child node along the path extStatusAbsentOther // path led to a node with a different stem extStatusPresent // stem was present + extStatusExpired // stem was present but expired ) diff --git a/empty.go b/empty.go index 2e79c48..bf2f613 100644 --- a/empty.go +++ b/empty.go @@ -53,7 +53,7 @@ func (Empty) Commitment() *Point { return &id } -func (Empty) GetProofItems(keylist, NodeResolverFn) (*ProofElements, []byte, []Stem, error) { +func (Empty) GetProofItems(keylist, AccessTimestamp, NodeResolverFn) (*ProofElements, []byte, []Stem, error) { return nil, nil, nil, errors.New("trying to produce a commitment for an empty subtree") } diff --git a/empty_test.go b/empty_test.go index 318e110..e237f44 100644 --- a/empty_test.go +++ b/empty_test.go @@ -51,7 +51,7 @@ func TestEmptyFuncs(t *testing.T) { t.Fatal("commitment and commit mismatch") } - if _, _, _, err := e.GetProofItems(nil, nil); err == nil { + if _, _, _, err := e.GetProofItems(nil, 0, nil); err == nil { t.Fatal("get proof items should error") } diff --git a/encoding.go b/encoding.go index bea5a55..4864b9d 100644 --- a/encoding.go +++ b/encoding.go @@ -98,7 +98,7 @@ func ParseNode(serializedNode []byte, depth byte) (VerkleNode, error) { case singleSlotType: return parseSingleSlotNode(serializedNode, depth) case expiredLeafType: - return parseExpiredLeafNode(serializedNode) + return parseExpiredLeafNode(serializedNode, depth) default: return nil, ErrInvalidNodeEncoding } @@ -203,9 +203,10 @@ func parseSingleSlotNode(serialized []byte, depth byte) (VerkleNode, error) { return ln, nil } -func parseExpiredLeafNode(serialized []byte) (VerkleNode, error) { +func parseExpiredLeafNode(serialized []byte, depth byte) (VerkleNode, error) { l := &ExpiredLeafNode{} l.stem = serialized[leafStemOffset : leafStemOffset+StemSize] + l.setDepth(depth) l.commitment = new(Point) if err := l.commitment.SetBytesUncompressed(serialized[leafStemOffset+StemSize:], true); err != nil { return nil, fmt.Errorf("setting commitment: %w", err) diff --git a/expired_leaf.go b/expired_leaf.go index 58e2a2c..68cd52f 100644 --- a/expired_leaf.go +++ b/expired_leaf.go @@ -28,41 +28,58 @@ package verkle type ExpiredLeafNode struct { stem Stem commitment *Point + depth byte // used for proof only, not commitment calculation } func NewExpiredLeafNode(stem Stem, commitment *Point) *ExpiredLeafNode { return &ExpiredLeafNode{stem: stem, commitment: commitment} } -func (ExpiredLeafNode) Insert([]byte, []byte, AccessTimestamp, NodeResolverFn) error { - return errEpochExpired +func (n *ExpiredLeafNode) Insert([]byte, []byte, AccessTimestamp, NodeResolverFn) error { + return errExpired } -func (ExpiredLeafNode) Delete([]byte, AccessTimestamp, NodeResolverFn) (bool, error) { - return false, errEpochExpired +func (n *ExpiredLeafNode) Delete([]byte, AccessTimestamp, NodeResolverFn) (bool, error) { + return false, errExpired } -func (ExpiredLeafNode) Get([]byte, AccessTimestamp, NodeResolverFn) ([]byte, error) { - return nil, errEpochExpired +func (n *ExpiredLeafNode) Get([]byte, AccessTimestamp, NodeResolverFn) ([]byte, error) { + return nil, errExpired } -func (n ExpiredLeafNode) Commit() *Point { +func (n *ExpiredLeafNode) Commit() *Point { if n.commitment == nil { panic("nil commitment") } return n.commitment } -func (n ExpiredLeafNode) Commitment() *Point { +func (n *ExpiredLeafNode) Commitment() *Point { return n.commitment } -// TODO(weiihann): prove that something was expired, for the block to be able to execute statelessly. -func (n ExpiredLeafNode) GetProofItems(keylist, NodeResolverFn) (*ProofElements, []byte, []Stem, error) { - return nil, nil, nil, errEpochExpired +func (n *ExpiredLeafNode) GetProofItems(keys keylist, curTs AccessTimestamp, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, error) { + var ( + pe = &ProofElements{ + Vals: make([][]byte, len(keys)), + ByPath: map[string]*Point{}, + } + esses []byte = nil + poass []Stem + ) + + for i := range keys { + pe.ByPath[string(keys[i][:n.depth])] = n.commitment + pe.Vals[i] = nil + + esses = append(esses, extStatusExpired|(n.depth<<3)) + poass = append(poass, n.stem) + } + + return pe, esses, poass, nil } -func (n ExpiredLeafNode) Serialize() ([]byte, error) { +func (n *ExpiredLeafNode) Serialize() ([]byte, error) { cBytes := n.commitment.BytesUncompressedTrusted() var buf [expiredLeafSize]byte @@ -74,10 +91,11 @@ func (n ExpiredLeafNode) Serialize() ([]byte, error) { return result, nil } -func (n ExpiredLeafNode) Copy() VerkleNode { +func (n *ExpiredLeafNode) Copy() VerkleNode { l := &ExpiredLeafNode{} l.stem = make(Stem, len(n.stem)) - + l.depth = n.depth + copy(l.stem, n.stem) if n.commitment != nil { l.commitment = new(Point) l.commitment.Set(n.commitment) @@ -86,15 +104,15 @@ func (n ExpiredLeafNode) Copy() VerkleNode { return l } -func (n ExpiredLeafNode) toDot(string, string) string { +func (n *ExpiredLeafNode) toDot(string, string) string { return "" } -func (n ExpiredLeafNode) setDepth(_ byte) { - panic("should not be try to set the depth of an ExpiredLeafNode node") +func (n *ExpiredLeafNode) setDepth(d byte) { + n.depth = d } -func (n ExpiredLeafNode) Hash() *Fr { +func (n *ExpiredLeafNode) Hash() *Fr { var hash Fr n.commitment.MapToScalarField(&hash) return &hash diff --git a/expired_leaf_test.go b/expired_leaf_test.go index 086b57d..f933b5f 100644 --- a/expired_leaf_test.go +++ b/expired_leaf_test.go @@ -14,17 +14,17 @@ func TestExpiredLeafBasic(t *testing.T) { leaf := NewExpiredLeafNode(zeroKeyTest[:StemSize], &comm) err := leaf.Insert(zeroKeyTest, zeroKeyTest, 0, nil) - if !errors.Is(err, errEpochExpired) { + if !errors.Is(err, errExpired) { t.Fatalf("expected epoch expired error when inserting, got %v", err) } _, err = leaf.Delete(zeroKeyTest, 0, nil) - if !errors.Is(err, errEpochExpired) { + if !errors.Is(err, errExpired) { t.Fatalf("expected epoch expired error when deleting, got %v", err) } v, err := leaf.Get(zeroKeyTest, 0, nil) - if !errors.Is(err, errEpochExpired) { + if !errors.Is(err, errExpired) { t.Fatalf("expected epoch expired error when getting, got %v", err) } if v != nil { diff --git a/expired_tree_test.go b/expired_tree_test.go index 59e4b06..fabb45b 100644 --- a/expired_tree_test.go +++ b/expired_tree_test.go @@ -45,7 +45,7 @@ func TestInsertSameLeafExpired(t *testing.T) { } err := root.Insert(oneKeyTest, testValue, 2, nil) - if !errors.Is(err, errEpochExpired) { + if !errors.Is(err, errExpired) { t.Fatalf("expected epoch expired error when inserting, got %v", err) } @@ -138,7 +138,7 @@ func TestGetExpired(t *testing.T) { } val, err := root.Get(zeroKeyTest, 2, nil) - if !errors.Is(err, errEpochExpired) { + if !errors.Is(err, errExpired) { t.Fatalf("expected epoch expired error when getting, got %v", err) } @@ -156,7 +156,7 @@ func TestGetExpired(t *testing.T) { } } -func TestDelLeafNoExpired(t *testing.T) { // skipcq: GO-R1005 +func TestDelLeafNoExpired(t *testing.T) { t.Parallel() root := New() @@ -174,7 +174,7 @@ func TestDelLeafNoExpired(t *testing.T) { // skipcq: GO-R1005 } } -func TestDelLeafExpired(t *testing.T) { // skipcq: GO-R1005 +func TestDelLeafExpired(t *testing.T) { t.Parallel() root := New() @@ -183,7 +183,7 @@ func TestDelLeafExpired(t *testing.T) { // skipcq: GO-R1005 } _, err := root.Delete(zeroKeyTest, 2, nil) - if !errors.Is(err, errEpochExpired) { + if !errors.Is(err, errExpired) { t.Fatalf("expected epoch expired error when deleting, got %v", err) } @@ -221,3 +221,23 @@ func TestRootCommitExpired(t *testing.T) { t.Fatalf("expected commitment to be %x, got %x", &init, comm) } } + +func TestRootCommitDiffTimestamp(t *testing.T) { + t.Parallel() + + root1 := New() + if err := root1.Insert(zeroKeyTest, testValue, 0, nil); err != nil { + t.Fatalf("error inserting: %v", err) + } + comm1 := root1.Commit() + + root2 := New() + if err := root2.Insert(zeroKeyTest, testValue, 2, nil); err != nil { + t.Fatalf("error inserting: %v", err) + } + comm2 := root2.Commit() + + if comm1.Equal(comm2) { + t.Fatalf("expected different commitments, got %x", comm1) + } +} diff --git a/hashednode.go b/hashednode.go index 5566fb9..e2e4e24 100644 --- a/hashednode.go +++ b/hashednode.go @@ -58,7 +58,7 @@ func (HashedNode) Commitment() *Point { panic("can not get commitment of a hash node") } -func (HashedNode) GetProofItems(keylist, NodeResolverFn) (*ProofElements, []byte, []Stem, error) { +func (HashedNode) GetProofItems(keylist, AccessTimestamp, NodeResolverFn) (*ProofElements, []byte, []Stem, error) { return nil, nil, nil, errors.New("can not get the full path, and there is no proof of absence") } diff --git a/hashednode_test.go b/hashednode_test.go index 1c48d9d..fb53307 100644 --- a/hashednode_test.go +++ b/hashednode_test.go @@ -46,7 +46,7 @@ func TestHashedNodeFuncs(t *testing.T) { if v != nil { t.Fatal("non-nil get from a hashed node") } - if _, _, _, err := e.GetProofItems(nil, nil); err == nil { + if _, _, _, err := e.GetProofItems(nil, 0, nil); err == nil { t.Fatal("got nil error when getting proof items from a hashed node") } if _, err := e.Serialize(); err != errSerializeHashedNode { diff --git a/proof_ipa.go b/proof_ipa.go index 2c84a20..e0a7018 100644 --- a/proof_ipa.go +++ b/proof_ipa.go @@ -94,6 +94,7 @@ type SuffixStateDiffs []SuffixStateDiff type StemStateDiff struct { Stem [StemSize]byte `json:"stem"` SuffixDiffs SuffixStateDiffs `json:"suffixDiffs"` + Resurrected bool `json:"resurrected"` } type StateDiff []StemStateDiff @@ -103,6 +104,7 @@ func (sd StateDiff) Copy() StateDiff { for i := range sd { copy(ret[i].Stem[:], sd[i].Stem[:]) ret[i].SuffixDiffs = make([]SuffixStateDiff, len(sd[i].SuffixDiffs)) + ret[i].Resurrected = sd[i].Resurrected for j := range sd[i].SuffixDiffs { ret[i].SuffixDiffs[j].Suffix = sd[i].SuffixDiffs[j].Suffix if sd[i].SuffixDiffs[j].CurrentValue != nil { @@ -118,22 +120,22 @@ func (sd StateDiff) Copy() StateDiff { return ret } -func GetCommitmentsForMultiproof(root VerkleNode, keys [][]byte, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, error) { +func GetCommitmentsForMultiproof(root VerkleNode, keys [][]byte, curTs AccessTimestamp, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, error) { sort.Sort(keylist(keys)) - return root.GetProofItems(keylist(keys), resolver) + return root.GetProofItems(keylist(keys), curTs, resolver) } // getProofElementsFromTree factors the logic that is used both in the proving and verification methods. It takes a pre-state // tree and an optional post-state tree, extracts the proof data from them and returns all the items required to build/verify // a proof. -func getProofElementsFromTree(preroot, postroot VerkleNode, keys [][]byte, curTs AccessTimestamp, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, [][]byte, error) { +func getProofElementsFromTree(preroot, postroot VerkleNode, keys [][]byte, preTs, postTs AccessTimestamp, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, [][]byte, error) { // go-ipa won't accept no key as an input, catch this corner case // and return an empty result. if len(keys) == 0 { return nil, nil, nil, nil, errors.New("no key provided for proof") } - pe, es, poas, err := GetCommitmentsForMultiproof(preroot, keys, resolver) + pe, es, poas, err := GetCommitmentsForMultiproof(preroot, keys, preTs, resolver) if err != nil { return nil, nil, nil, nil, fmt.Errorf("error getting pre-state proof data: %w", err) } @@ -145,7 +147,7 @@ func getProofElementsFromTree(preroot, postroot VerkleNode, keys [][]byte, curTs // keys were sorted already in the above GetcommitmentsForMultiproof. // Set the post values, if they are untouched, leave them `nil` for i := range keys { - val, err := postroot.Get(keys[i], curTs, resolver) + val, err := postroot.Get(keys[i], postTs, resolver) if err != nil { return nil, nil, nil, nil, fmt.Errorf("error getting post-state value for key %x: %w", keys[i], err) } @@ -160,8 +162,8 @@ func getProofElementsFromTree(preroot, postroot VerkleNode, keys [][]byte, curTs return pe, es, poas, postvals, nil } -func MakeVerkleMultiProof(preroot, postroot VerkleNode, keys [][]byte, curTs AccessTimestamp, resolver NodeResolverFn) (*Proof, []*Point, []byte, []*Fr, error) { - pe, es, poas, postvals, err := getProofElementsFromTree(preroot, postroot, keys, curTs, resolver) +func MakeVerkleMultiProof(preroot, postroot VerkleNode, keys [][]byte, preTs, postTs AccessTimestamp, resolver NodeResolverFn) (*Proof, []*Point, []byte, []*Fr, error) { + pe, es, poas, postvals, err := getProofElementsFromTree(preroot, postroot, keys, preTs, postTs, resolver) if err != nil { return nil, nil, nil, nil, fmt.Errorf("get commitments for multiproof: %s", err) } @@ -203,8 +205,8 @@ func MakeVerkleMultiProof(preroot, postroot VerkleNode, keys [][]byte, curTs Acc } // verifyVerkleProofWithPreState takes a proof and a trusted tree root and verifies that the proof is valid. -func verifyVerkleProofWithPreState(proof *Proof, preroot VerkleNode, curTs AccessTimestamp) error { - pe, _, _, _, err := getProofElementsFromTree(preroot, nil, proof.Keys, curTs, nil) +func verifyVerkleProofWithPreState(proof *Proof, preroot VerkleNode, preTs AccessTimestamp) error { + pe, _, _, _, err := getProofElementsFromTree(preroot, nil, proof.Keys, preTs, 0, nil) if err != nil { return fmt.Errorf("error getting proof elements: %w", err) } @@ -250,13 +252,21 @@ func SerializeProof(proof *Proof) (*VerkleProof, StateDiff, error) { var stemdiff *StemStateDiff var statediff StateDiff + var checkResurrect bool + var curExtStatus byte + + curEsInd := -1 // index of the current extension status for i, key := range proof.Keys { stem := KeyToStem(key) if stemdiff == nil || !bytes.Equal(stemdiff.Stem[:], stem) { statediff = append(statediff, StemStateDiff{}) stemdiff = &statediff[len(statediff)-1] copy(stemdiff.Stem[:], stem) + checkResurrect = true + curEsInd += 1 + curExtStatus = proof.ExtStatus[curEsInd] } + stemdiff.SuffixDiffs = append(stemdiff.SuffixDiffs, SuffixStateDiff{Suffix: key[StemSize]}) newsd := &stemdiff.SuffixDiffs[len(stemdiff.SuffixDiffs)-1] @@ -284,6 +294,13 @@ func SerializeProof(proof *Proof) (*VerkleProof, StateDiff, error) { copy(aligned[:valueLen], proof.PostValues[i]) newsd.NewValue = (*[32]byte)(unsafe.Pointer(&aligned[0])) } + + // if the current extension status is expired and the post value is not nil, + // then it means that the leaf has been resurrected. + if checkResurrect && (curExtStatus&3 == extStatusExpired) && (proof.PostValues[i] != nil) { + stemdiff.Resurrected = true + checkResurrect = false + } } return &VerkleProof{ @@ -478,6 +495,16 @@ func PreStateTreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { // si.has_c2 = si.has_c2 || (k[StemSize] >= 128) } } + case extStatusExpired: + // All keys that are part of a proof of expiry, must contain empty + // prestate values. If that isn't the case, the proof is invalid. + for j := range proof.Keys { // TODO: DoS risk, use map or binary search. + if bytes.HasPrefix(proof.Keys[j], stems[i]) && proof.PreValues[j] != nil { + return nil, fmt.Errorf("proof of absence (empty) stem %x has a value", si.stem) + } + } + si.stem = poas[0] + poas = poas[1:] default: return nil, fmt.Errorf("invalid extension status: %d", si.stemType) } @@ -519,7 +546,7 @@ func PreStateTreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { // // PostStateTreeFromProof uses the pre-state trie and the list of updated values // to produce the stateless post-state trie. -func PostStateTreeFromStateDiff(preroot VerkleNode, statediff StateDiff) (VerkleNode, error) { +func PostStateTreeFromStateDiff(preroot VerkleNode, statediff StateDiff, postTs AccessTimestamp) (VerkleNode, error) { postroot := preroot.Copy() for _, stemstatediff := range statediff { @@ -540,8 +567,7 @@ func PostStateTreeFromStateDiff(preroot VerkleNode, statediff StateDiff) (Verkle if overwrites { var stem [StemSize]byte copy(stem[:StemSize], stemstatediff.Stem[:]) - // TODO(weiihann): double check the epoch - if err := postroot.(*InternalNode).InsertValuesAtStem(stem[:], values, 0, nil); err != nil { + if err := postroot.(*InternalNode).InsertValuesAtStem(stem[:], values, postTs, stemstatediff.Resurrected, nil); err != nil { return nil, fmt.Errorf("error overwriting value in post state: %w", err) } } @@ -600,7 +626,7 @@ func Verify(vp *VerkleProof, preStateRoot []byte, postStateRoot []byte, statedif // But all this can be avoided with a even faster way. The EVM block execution can // keep track of the written keys, and compare that list with this post-values list. // This can avoid regenerating the post-tree which is somewhat expensive. - posttree, err := PostStateTreeFromStateDiff(pretree, statediff) + posttree, err := PostStateTreeFromStateDiff(pretree, statediff, curTs) if err != nil { return fmt.Errorf("error rebuilding the post-tree from proof: %w", err) } diff --git a/proof_json.go b/proof_json.go index 7828697..5c4e3d2 100644 --- a/proof_json.go +++ b/proof_json.go @@ -186,12 +186,14 @@ func (vp *VerkleProof) UnmarshalJSON(data []byte) error { type stemStateDiffMarshaller struct { Stem string `json:"stem"` SuffixDiffs SuffixStateDiffs `json:"suffixDiffs"` + Resurrected bool `json:"resurrected"` } func (ssd StemStateDiff) MarshalJSON() ([]byte, error) { return json.Marshal(&stemStateDiffMarshaller{ Stem: HexToPrefixedString(ssd.Stem[:]), SuffixDiffs: ssd.SuffixDiffs, + Resurrected: ssd.Resurrected, }) } @@ -207,6 +209,7 @@ func (ssd *StemStateDiff) UnmarshalJSON(data []byte) error { } *ssd = StemStateDiff{ SuffixDiffs: aux.SuffixDiffs, + Resurrected: aux.Resurrected, } copy(ssd.Stem[:], stem) return nil diff --git a/proof_test.go b/proof_test.go index 193a8d4..6f3d98a 100644 --- a/proof_test.go +++ b/proof_test.go @@ -29,6 +29,7 @@ import ( "crypto/rand" "encoding/hex" "encoding/json" + "errors" "fmt" "reflect" "testing" @@ -42,7 +43,7 @@ func TestProofEmptyTree(t *testing.T) { root := New() root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ffx32KeyTest}, 0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ffx32KeyTest}, 0, 0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { t.Fatalf("could not verify verkle proof: %s", ToDot(root)) @@ -64,7 +65,7 @@ func TestProofVerifyTwoLeaves(t *testing.T) { } root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ffx32KeyTest}, 0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ffx32KeyTest}, 0, 0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { @@ -91,7 +92,7 @@ func TestProofVerifyMultipleLeaves(t *testing.T) { } root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{keys[0]}, 0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{keys[0]}, 0, 0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { @@ -118,9 +119,9 @@ func TestMultiProofVerifyMultipleLeaves(t *testing.T) { } root.Commit() - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keys[0:2], 0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keys[0:2], 0, 0, nil) - pe, _, _, err := GetCommitmentsForMultiproof(root, keys[0:2], nil) + pe, _, _, err := GetCommitmentsForMultiproof(root, keys[0:2], 0, nil) if err != nil { t.Fatal(err) } @@ -158,9 +159,9 @@ func TestMultiProofVerifyMultipleLeavesWithAbsentStem(t *testing.T) { absent[3] = 1 // and the stem differs keys = append(keys, absent) - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keys, 0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keys, 0, 0, nil) - pe, _, isabsent, err := GetCommitmentsForMultiproof(root, keys, nil) + pe, _, isabsent, err := GetCommitmentsForMultiproof(root, keys, 0, nil) if err != nil { t.Fatal(err) } @@ -192,9 +193,9 @@ func TestMultiProofVerifyMultipleLeavesCommitmentRedundancy(t *testing.T) { } root.Commit() - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keys, 0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keys, 0, 0, nil) - pe, _, _, err := GetCommitmentsForMultiproof(root, keys, nil) + pe, _, _, err := GetCommitmentsForMultiproof(root, keys, 0, nil) if err != nil { t.Fatal(err) } @@ -216,7 +217,7 @@ func TestProofOfAbsenceInternalVerify(t *testing.T) { } root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ffx32KeyTest}, 0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ffx32KeyTest}, 0, 0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { @@ -236,7 +237,7 @@ func TestProofOfAbsenceLeafVerify(t *testing.T) { } root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{oneKeyTest}, 0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{oneKeyTest}, 0, 0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { @@ -261,7 +262,7 @@ func TestProofOfAbsenceLeafVerifyOtherSuffix(t *testing.T) { return ret }() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{key}, 0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{key}, 0, 0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { @@ -283,7 +284,7 @@ func TestProofOfAbsenceStemVerify(t *testing.T) { }() root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{key}, 0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{key}, 0, 0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { @@ -309,7 +310,7 @@ func BenchmarkProofCalculation(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - if _, _, _, _, err := MakeVerkleMultiProof(root, nil, [][]byte{keys[len(keys)/2]}, 0, nil); err != nil { + if _, _, _, _, err := MakeVerkleMultiProof(root, nil, [][]byte{keys[len(keys)/2]}, 0, 0, nil); err != nil { b.Fatal(err) } } @@ -330,7 +331,7 @@ func BenchmarkProofVerification(b *testing.B) { } root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{keys[len(keys)/2]}, 0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{keys[len(keys)/2]}, 0, 0, nil) b.ResetTimer() b.ReportAllocs() @@ -361,7 +362,7 @@ func TestProofSerializationNoAbsentStem(t *testing.T) { } } - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, [][]byte{keys[0]}, 0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, [][]byte{keys[0]}, 0, 0, nil) vp, statediff, err := SerializeProof(proof) if err != nil { @@ -400,7 +401,7 @@ func TestProofSerializationWithAbsentStem(t *testing.T) { absentkey[2] = 2 absentkey[3] = 1 - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, [][]byte{absentkey[:]}, 0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, [][]byte{absentkey[:]}, 0, 0, nil) vp, statediff, err := SerializeProof(proof) if err != nil { @@ -441,7 +442,7 @@ func TestProofDeserialize(t *testing.T) { absentkey[2] = 2 absentkey[3] = 1 - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, [][]byte{absentkey[:]}, 0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, [][]byte{absentkey[:]}, 0, 0, nil) vp, statediff, err := SerializeProof(proof) if err != nil { @@ -454,7 +455,7 @@ func TestProofDeserialize(t *testing.T) { } _ = deserialized - pe, _, _, err := root.GetProofItems(keylist{absentkey[:]}, nil) + pe, _, _, err := root.GetProofItems(keylist{absentkey[:]}, 0, nil) if err != nil { t.Fatal(err) } @@ -471,7 +472,7 @@ func TestProofOfAbsenceEdgeCase(t *testing.T) { root.Commit() ret, _ := hex.DecodeString("0303030303030303030303030303030303030303030303030303030303030303") - proof, cs, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ret}, 0, nil) + proof, cs, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ret}, 0, 0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cs, zis, yis, cfg); !ok || err != nil { t.Fatal("could not verify proof") @@ -492,7 +493,7 @@ func TestProofOfAbsenceOtherMultipleLeaves(t *testing.T) { ret1, _ := hex.DecodeString("0303030303030303030303030303030303030303030303030303030303030300") ret2, _ := hex.DecodeString("0303030303030303030303030303030303030303030303030303030303030301") - proof, cs, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ret1, ret2}, 0, nil) + proof, cs, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ret1, ret2}, 0, 0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cs, zis, yis, cfg); !ok || err != nil { t.Fatal("could not verify proof") @@ -554,7 +555,7 @@ func TestProofOfAbsenceNoneMultipleStems(t *testing.T) { ret1, _ := hex.DecodeString("0303030303030303030303030303030303030303030303030303030303030300") ret2, _ := hex.DecodeString("0303030303030303030303030303030303030303030303030303030303030200") - proof, cs, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ret1, ret2}, 0, nil) + proof, cs, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ret1, ret2}, 0, 0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cs, zis, yis, cfg); !ok || err != nil { t.Fatal("could not verify proof") @@ -625,9 +626,10 @@ func TestStemStateDiffJSONMarshalUn(t *testing.T) { 0xDD, 0xEE, 0xFF, 0x00, }, }}, + Resurrected: true, } - expectedJSON := `{"stem":"0x0a000000000000000000000000000000000000000000000000000000000000","suffixDiffs":[{"suffix":65,"currentValue":"0x102030405060708090a0b0c0d0e0f000112233445566778899aabbccddeeff00","newValue":null}]}` + expectedJSON := `{"stem":"0x0a000000000000000000000000000000000000000000000000000000000000","suffixDiffs":[{"suffix":65,"currentValue":"0x102030405060708090a0b0c0d0e0f000112233445566778899aabbccddeeff00","newValue":null}],"resurrected":true}` actualJSON, err := json.Marshal(ssd) if err != nil { t.Errorf("error marshalling SuffixStateDiff to JSON: %v", err) @@ -837,6 +839,111 @@ func TestProofOfAbsenceBorderCaseReversed(t *testing.T) { testSerializeDeserializeProof(t, insertKVs, proveKeys) } +func TestProofOfExpiryOneLeaf(t *testing.T) { + t.Parallel() + + root := New() + if err := root.Insert(zeroKeyTest, zeroKeyTest, 2, nil); err != nil { + t.Fatalf("could not insert key: %v", err) + } + if err := root.Insert(oneKeyTest, zeroKeyTest, 2, nil); err != nil { + t.Fatalf("could not insert key: %v", err) + } + init := root.Commit() + + leaf := root.(*InternalNode).children[0].(*LeafNode) + + expiredLeaf := NewExpiredLeafNode(leaf.stem, leaf.commitment) + expiredLeaf.setDepth(1) + root.(*InternalNode).children[0] = expiredLeaf + + comm := root.Commit() + if !comm.Equal(init) { + t.Fatalf("expected commitment to be %x, got %x", init, comm) + } + + proof, cis, zis, yis, err := MakeVerkleMultiProof(root, nil, [][]byte{zeroKeyTest}, 0, 0, nil) + if err != nil { + t.Fatalf("could not make verkle proof: %v", err) + } + + cfg := GetConfig() + if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { + t.Fatalf("could not verify verkle proof: %s", ToDot(root)) + } + + deserialized, err := PreStateTreeFromProof(proof, init) + if err != nil { + t.Fatalf("error deserializing %v", err) + } + + _, err = deserialized.Get(zeroKeyTest, 0, nil) + if !errors.Is(err, errExpired) { + t.Fatalf("expected error getting key %x, got %v", zeroKeyTest, err) + } + + if !deserialized.Commit().Equal(init) { + t.Fatalf("expected commitment to be different from %x, got %x", init, deserialized.Commit()) + } +} + +func TestProofOfExpiryMultipleLeaves(t *testing.T) { + t.Parallel() + + root := New() + if err := root.Insert(zeroKeyTest, zeroKeyTest, 2, nil); err != nil { + t.Fatalf("could not insert key: %v", err) + } + if err := root.Insert(ffx32KeyTest, zeroKeyTest, 2, nil); err != nil { + t.Fatalf("could not insert key: %v", err) + } + init := root.Commit() + + leaf0 := root.(*InternalNode).children[0].(*LeafNode) + expiredLeaf0 := NewExpiredLeafNode(leaf0.stem, leaf0.commitment) + expiredLeaf0.setDepth(1) + root.(*InternalNode).children[0] = expiredLeaf0 + + leaff := root.(*InternalNode).children[255].(*LeafNode) + expiredLeaff := NewExpiredLeafNode(leaff.stem, leaff.commitment) + expiredLeaff.setDepth(1) + root.(*InternalNode).children[255] = expiredLeaff + + comm := root.Commit() + if !comm.Equal(init) { + t.Fatalf("expected commitment to be %x, got %x", init, comm) + } + + proof, cis, zis, yis, err := MakeVerkleMultiProof(root, nil, [][]byte{zeroKeyTest, ffx32KeyTest}, 0, 0, nil) + if err != nil { + t.Fatalf("could not make verkle proof: %v", err) + } + + cfg := GetConfig() + if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { + t.Fatalf("could not verify verkle proof: %s", ToDot(root)) + } + + deserialized, err := PreStateTreeFromProof(proof, init) + if err != nil { + t.Fatalf("error deserializing %v", err) + } + + _, err = deserialized.Get(zeroKeyTest, 0, nil) + if !errors.Is(err, errExpired) { + t.Fatalf("expected error getting key %x, got %v", zeroKeyTest, err) + } + + _, err = deserialized.Get(ffx32KeyTest, 0, nil) + if !errors.Is(err, errExpired) { + t.Fatalf("expected error getting key %x, got %v", ffx32KeyTest, err) + } + + if !deserialized.Commit().Equal(init) { + t.Fatalf("expected commitment to be different from %x, got %x", init, deserialized.Commit()) + } +} + func testSerializeDeserializeProof(t *testing.T, insertKVs map[string][]byte, proveKeys keylist) { t.Helper() @@ -863,7 +970,7 @@ func testSerializeDeserializeProof(t *testing.T, insertKVs map[string][]byte, pr proveKVs[string(key)] = value } - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, proveKeys, 0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, proveKeys, 0, 0, nil) serialized, statediff, err := SerializeProof(proof) if err != nil { @@ -1012,7 +1119,7 @@ func TestProofVerificationWithPostState(t *testing.T) { // skipcq: GO-R1005 } postroot.Commit() - proof, _, _, _, _ := MakeVerkleMultiProof(root, postroot, data.keystoprove, 0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, postroot, data.keystoprove, 0, 0, nil) keys: for i := range proof.Keys { @@ -1060,7 +1167,7 @@ func TestProofVerificationWithPostState(t *testing.T) { // skipcq: GO-R1005 t.Fatalf("error recreating pre tree: %v", err) } - dpostroot, err := PostStateTreeFromStateDiff(dpreroot, diff) + dpostroot, err := PostStateTreeFromStateDiff(dpreroot, diff, 0) if err != nil { t.Fatalf("error recreating post tree: %v", err) } @@ -1076,6 +1183,69 @@ func TestProofVerificationWithPostState(t *testing.T) { // skipcq: GO-R1005 } } +// TODO(weiihann): add more test cases similar to TestProofVerificationWithPostState +func TestProofVerificationPreStateExpiredPostStateResurrected(t *testing.T) { + t.Parallel() + + preTs := AccessTimestamp(0) + postTs := AccessTimestamp(2) + + preRoot := New() + if err := preRoot.Insert(zeroKeyTest, zeroKeyTest, preTs, nil); err != nil { + t.Fatalf("could not insert key: %v", err) + } + rootC := preRoot.Commit() + + leaf := preRoot.(*InternalNode).children[0].(*LeafNode) + expiredLeaf := NewExpiredLeafNode(leaf.stem, leaf.commitment) + expiredLeaf.setDepth(1) + preRoot.(*InternalNode).children[0] = expiredLeaf + + postRoot := New() + if err := postRoot.Insert(zeroKeyTest, fourtyKeyTest, postTs, nil); err != nil { + t.Fatalf("could not insert key: %v", err) + } + + proof, _, _, _, _ := MakeVerkleMultiProof(preRoot, postRoot, keylist{zeroKeyTest}, preTs, postTs, nil) + + p, diff, err := SerializeProof(proof) + if err != nil { + t.Fatalf("error serializing proof: %v", err) + } + + dproof, err := DeserializeProof(p, diff) + if err != nil { + t.Fatalf("error deserializing proof: %v", err) + } + + if err = verifyVerkleProofWithPreState(dproof, preRoot, preTs); err != nil { + t.Fatalf("could not verify verkle proof: %v", err) + } + + dpreroot, err := PreStateTreeFromProof(dproof, rootC) + if err != nil { + t.Fatalf("error recreating pre tree: %v", err) + } + + dpostroot, err := PostStateTreeFromStateDiff(dpreroot, diff, postTs) + if err != nil { + t.Fatalf("error recreating post tree: %v", err) + } + + if err = verifyVerkleProofWithPreState(dproof, dpreroot, preTs); err != nil { + t.Fatalf("could not verify verkle proof: %v, original: %s reconstructed: %s", err, ToDot(dpreroot), ToDot(dpostroot)) + } + + got, err := dpostroot.Get(zeroKeyTest, postTs, nil) + if err != nil { + t.Fatalf("error getting key: %v", err) + } + + if !bytes.Equal(got, fourtyKeyTest) { + t.Fatalf("value mismatch for key %x: %x != %x", zeroKeyTest, got, fourtyKeyTest) + } +} + func TestGenerateProofWithOnlyAbsentKeys(t *testing.T) { t.Parallel() @@ -1089,7 +1259,7 @@ func TestGenerateProofWithOnlyAbsentKeys(t *testing.T) { // Create a proof with a key with the same first byte, but different second byte (i.e: absent). absentKey, _ := hex.DecodeString("4010000000000000000000000000000000000000000000000000000000000000") - proof, cis, zis, yis, err := MakeVerkleMultiProof(root, nil, keylist{absentKey}, 0, nil) + proof, cis, zis, yis, err := MakeVerkleMultiProof(root, nil, keylist{absentKey}, 0, 0, nil) if err != nil { t.Fatal(err) } @@ -1114,7 +1284,7 @@ func TestGenerateProofWithOnlyAbsentKeys(t *testing.T) { } // From the rebuilt tree, validate the proof. - pe, _, _, err := GetCommitmentsForMultiproof(droot, keylist{absentKey}, nil) + pe, _, _, err := GetCommitmentsForMultiproof(droot, keylist{absentKey}, 0, nil) if err != nil { t.Fatal(err) } @@ -1169,7 +1339,7 @@ func TestDoubleProofOfAbsence(t *testing.T) { // in that leaf node. i.e: two proof of absence in the same leaf node with no proof of presence. key2, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000100") key3, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000200") - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keylist{key2, key3}, 0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keylist{key2, key3}, 0, 0, nil) serialized, statediff, err := SerializeProof(proof) if err != nil { @@ -1218,7 +1388,7 @@ func TestProveAbsenceInEmptyHalf(t *testing.T) { key2, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000100") key3, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000") - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keylist{key2, key3}, 0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keylist{key2, key3}, 0, 0, nil) serialized, statediff, err := SerializeProof(proof) if err != nil { diff --git a/tree.go b/tree.go index d2881a9..97a3806 100644 --- a/tree.go +++ b/tree.go @@ -91,7 +91,7 @@ type VerkleNode interface { // returns them breadth-first. On top of that, it returns // one "extension status" per stem, and an alternate stem // if the key is missing but another stem has been found. - GetProofItems(keylist, NodeResolverFn) (*ProofElements, []byte, []Stem, error) + GetProofItems(keylist, AccessTimestamp, NodeResolverFn) (*ProofElements, []byte, []Stem, error) // Serialize encodes the node to RLP. Serialize() ([]byte, error) @@ -374,10 +374,10 @@ func (n *InternalNode) cowChild(index byte) { func (n *InternalNode) Insert(key []byte, value []byte, curTs AccessTimestamp, resolver NodeResolverFn) error { values := make([][]byte, NodeWidth) values[key[StemSize]] = value - return n.InsertValuesAtStem(KeyToStem(key), values, curTs, resolver) + return n.InsertValuesAtStem(KeyToStem(key), values, curTs, false, resolver) } -func (n *InternalNode) InsertValuesAtStem(stem Stem, values [][]byte, curTs AccessTimestamp, resolver NodeResolverFn) error { +func (n *InternalNode) InsertValuesAtStem(stem Stem, values [][]byte, curTs AccessTimestamp, isResurrect bool, resolver NodeResolverFn) error { nChild := offset2key(stem, n.depth) // index of the child pointed by the next byte in the key switch child := n.children[nChild].(type) { @@ -407,9 +407,21 @@ func (n *InternalNode) InsertValuesAtStem(stem Stem, values [][]byte, curTs Acce n.cowChild(nChild) // recurse to handle the case of a LeafNode child that // splits. - return n.InsertValuesAtStem(stem, values, curTs, resolver) + return n.InsertValuesAtStem(stem, values, curTs, isResurrect, resolver) case *ExpiredLeafNode: - return errEpochExpired + if !isResurrect { + return errExpired + } + + // create a new leaf node with the given values + leaf, err := NewLeafNode(stem, values, curTs) + if err != nil { + return err + } + leaf.setDepth(n.depth + 1) + n.children[nChild] = leaf + + return nil case *LeafNode: if equalPaths(child.stem, stem) { // We can't insert any values into a POA leaf node. @@ -433,7 +445,7 @@ func (n *InternalNode) InsertValuesAtStem(stem Stem, values [][]byte, curTs Acce nextWordInInsertedKey := offset2key(stem, n.depth+1) if nextWordInInsertedKey == nextWordInExistingKey { - return newBranch.InsertValuesAtStem(stem, values, curTs, resolver) + return newBranch.InsertValuesAtStem(stem, values, curTs, isResurrect, resolver) } // Next word differs, so this was the last level. @@ -447,7 +459,7 @@ func (n *InternalNode) InsertValuesAtStem(stem Stem, values [][]byte, curTs Acce newBranch.children[nextWordInInsertedKey] = leaf case *InternalNode: n.cowChild(nChild) - return child.InsertValuesAtStem(stem, values, curTs, resolver) + return child.InsertValuesAtStem(stem, values, curTs, isResurrect, resolver) default: // It should be an UknownNode. return errUnknownNodeType } @@ -527,6 +539,20 @@ func (n *InternalNode) CreatePath(path []byte, stemInfo stemInfo, comms []*Point for b, value := range stemInfo.values { newchild.values[b] = value } + case extStatusExpired: + if len(comms) == 0 { + return comms, fmt.Errorf("missing commitment for stem %x", stemInfo.stem) + } + if len(stemInfo.stem) != StemSize { + return comms, fmt.Errorf("invalid stem size %d", len(stemInfo.stem)) + } + newchild := &ExpiredLeafNode{ + commitment: comms[0], + stem: stemInfo.stem, + depth: n.depth + 1, + } + n.children[path[0]] = newchild + comms = comms[1:] default: return comms, fmt.Errorf("invalid stem type %d", stemInfo.stemType) } @@ -582,10 +608,10 @@ func (n *InternalNode) GetValuesAtStem(stem Stem, curTs AccessTimestamp, resolve // splits. return n.GetValuesAtStem(stem, curTs, resolver) case *ExpiredLeafNode: - return nil, errEpochExpired + return nil, errExpired case *LeafNode: if IsExpired(child.lastTs, curTs) { - return nil, errEpochExpired + return nil, errExpired } if equalPaths(child.stem, stem) { @@ -625,7 +651,7 @@ func (n *InternalNode) Delete(key []byte, curTs AccessTimestamp, resolver NodeRe n.children[nChild] = c return n.Delete(key, curTs, resolver) case *ExpiredLeafNode: - return false, errEpochExpired + return false, errExpired default: n.cowChild(nChild) del, err := child.Delete(key, curTs, resolver) @@ -679,7 +705,7 @@ func (n *InternalNode) DeleteAtStem(key []byte, resolver NodeResolverFn) (bool, n.children[nChild] = c return n.DeleteAtStem(key, resolver) case *ExpiredLeafNode: - return false, errEpochExpired + return false, errExpired case *LeafNode: if !bytes.Equal(child.stem, key[:31]) { return false, errDeleteMissing @@ -963,7 +989,7 @@ func groupKeys(keys keylist, depth byte) []keylist { return groups } -func (n *InternalNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, error) { +func (n *InternalNode) GetProofItems(keys keylist, curTs AccessTimestamp, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, error) { var ( groups = groupKeys(keys, n.depth) pe = &ProofElements{ @@ -1034,15 +1060,13 @@ func (n *InternalNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*Pr for _, group := range groups { childIdx := offset2key(group[0], n.depth) - if _, isunknown := n.children[childIdx].(UnknownNode); isunknown { + switch n.children[childIdx].(type) { + case UnknownNode: // TODO: add a test case to cover this scenario. return nil, nil, nil, errMissingNodeInStateless - } - - // Special case of a proof of absence: no children - // commitment, as the value is 0. - _, isempty := n.children[childIdx].(Empty) - if isempty { + case Empty: + // Special case of a proof of absence: no children + // commitment, as the value is 0. addedStems := map[string]struct{}{} for i := 0; i < len(group); i++ { stemStr := string(KeyToStem(group[i])) @@ -1060,7 +1084,7 @@ func (n *InternalNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*Pr continue } - pec, es, other, err := n.children[childIdx].GetProofItems(group, resolver) + pec, es, other, err := n.children[childIdx].GetProofItems(group, curTs, resolver) if err != nil { // TODO: add a test case to cover this scenario. return nil, nil, nil, err @@ -1181,7 +1205,7 @@ func (n *LeafNode) Insert(key []byte, value []byte, curTs AccessTimestamp, _ Nod } if IsExpired(n.lastTs, curTs) { - return errEpochExpired + return errExpired } stem := KeyToStem(key) @@ -1200,7 +1224,7 @@ func (n *LeafNode) insertMultiple(stem Stem, values [][]byte, curTs AccessTimest } if IsExpired(n.lastTs, curTs) { - return errEpochExpired + return errExpired } if err := n.updateMultipleLeaves(values, curTs); err != nil { @@ -1367,7 +1391,7 @@ func (n *LeafNode) Delete(k []byte, curTs AccessTimestamp, _ NodeResolverFn) (bo } if IsExpired(n.lastTs, curTs) { - return false, errEpochExpired + return false, errExpired } // Erase the value it used to contain @@ -1552,7 +1576,7 @@ func leafToComms(poly []Fr, val []byte) error { return nil } -func (n *LeafNode) GetProofItems(keys keylist, _ NodeResolverFn) (*ProofElements, []byte, []Stem, error) { // skipcq: GO-R1005 +func (n *LeafNode) GetProofItems(keys keylist, curTs AccessTimestamp, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, error) { // skipcq: GO-R1005 var ( poly [NodeWidth]Fr // top-level polynomial pe = &ProofElements{ @@ -1565,10 +1589,23 @@ func (n *LeafNode) GetProofItems(keys keylist, _ NodeResolverFn) (*ProofElements } esses []byte = nil // list of extension statuses - poass []Stem // list of proof-of-absence stems + poass []Stem // list of proof-of-absence and proof-of-expiry stems ) - // Initialize the top-level polynomial with 1 + stem + C1 + C2 + // If the leaf node is expired, generate the proof of expiry. + if IsExpired(n.lastTs, curTs) { + for i := range keys { + pe.ByPath[string(keys[i][:n.depth])] = n.commitment + pe.Vals[i] = nil + + esses = append(esses, extStatusExpired|(n.depth<<3)) + poass = append(poass, n.stem) + } + + return &ProofElements{}, esses, poass, nil + } + + // Initialize the top-level polynomial with 1 + stem + C1 + C2 + lastTs poly[0].SetUint64(1) if err := StemFromLEBytes(&poly[1], n.stem); err != nil { return nil, nil, nil, fmt.Errorf("error serializing stem '%x': %w", n.stem, err) @@ -1616,12 +1653,21 @@ func (n *LeafNode) GetProofItems(keys keylist, _ NodeResolverFn) (*ProofElements pe.Fis = append(pe.Fis, poly[:]) } + // add last accessed timestamp + poly[4].SetUint64(uint64(n.lastTs)) + pe.Cis = append(pe.Cis, n.commitment) + pe.Zis = append(pe.Zis, 4) + pe.Yis = append(pe.Yis, &poly[4]) + pe.Fis = append(pe.Fis, poly[:]) + addedStems := map[string]struct{}{} // Second pass: add the cn-level elements for _, key := range keys { pe.ByPath[string(key[:n.depth])] = n.commitment + stemStr := string(KeyToStem(key)) + // Proof of absence: case of a differing stem. if !equalPaths(n.stem, key) { // If this is the first extension status added for this path, @@ -1634,7 +1680,6 @@ func (n *LeafNode) GetProofItems(keys keylist, _ NodeResolverFn) (*ProofElements // Add an extension status absent other for this stem. // Note we keep a cache to avoid adding the same stem twice (or more) if // there're multiple keys with the same stem. - stemStr := string(KeyToStem(key)) if _, ok := addedStems[stemStr]; !ok { esses = append(esses, extStatusAbsentOther|(n.depth<<3)) addedStems[stemStr] = struct{}{} @@ -1690,7 +1735,6 @@ func (n *LeafNode) GetProofItems(keys keylist, _ NodeResolverFn) (*ProofElements pe.Fis = append(pe.Fis, suffPoly[:], suffPoly[:]) pe.Vals = append(pe.Vals, n.values[key[StemSize]]) - stemStr := string(KeyToStem(key)) if _, ok := addedStems[stemStr]; !ok { esses = append(esses, extStatusPresent|(n.depth<<3)) addedStems[stemStr] = struct{}{} diff --git a/tree_test.go b/tree_test.go index a119ca3..b5f4989 100644 --- a/tree_test.go +++ b/tree_test.go @@ -500,7 +500,7 @@ func TestDeletePrune(t *testing.T) { // skipcq: GO-R1005 // their hashed values. It then tries to delete the hashed values, which should // fail. func TestDeleteHash(t *testing.T) { - // TODO: fix this test when we take a final decision about FlushAtDepth API. + //TODO: fix this test when we take a final decision about FlushAtDepth API. t.SkipNow() key1, _ := hex.DecodeString("0105000000000000000000000000000000000000000000000000000000000000") @@ -544,7 +544,7 @@ func TestDeleteUnequalPath(t *testing.T) { } func TestDeleteResolve(t *testing.T) { - // TODO: fix this test when we take a final decision about FlushAtDepth API. + //TODO: fix this test when we take a final decision about FlushAtDepth API. t.SkipNow() key1, _ := hex.DecodeString("0105000000000000000000000000000000000000000000000000000000000000") @@ -851,7 +851,7 @@ func isLeafEqual(a, b *LeafNode) bool { } func TestGetResolveFromHash(t *testing.T) { - // TODO: fix this test when we take a final decision about FlushAtDepth API. + //TODO: fix this test when we take a final decision about FlushAtDepth API. t.SkipNow() var count uint @@ -926,7 +926,7 @@ func TestGetKey(t *testing.T) { } func TestInsertIntoHashedNode(t *testing.T) { - // TODO: fix this test when we take a final decision about FlushAtDepth API. + //TODO: fix this test when we take a final decision about FlushAtDepth API. t.SkipNow() root := New() @@ -1025,7 +1025,7 @@ func TestEmptyCommitment(t *testing.T) { t.Fatalf("inserting into the original failed: %v", err) } root.Commit() - pe, _, _, err := root.GetProofItems(keylist{ffx32KeyTest}, nil) + pe, _, _, err := root.GetProofItems(keylist{ffx32KeyTest}, 0, nil) if err != nil { t.Fatal(err) } @@ -1092,7 +1092,7 @@ func TestGetProofItemsNoPoaIfStemPresent(t *testing.T) { key1, _ := hex.DecodeString("ffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") key2, _ := hex.DecodeString("ffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - _, esses, poas, err := root.GetProofItems(keylist{key1, key2, ffx32KeyTest}, nil) + _, esses, poas, err := root.GetProofItems(keylist{key1, key2, ffx32KeyTest}, 0, nil) if err != nil { t.Fatal(err) } @@ -1150,7 +1150,7 @@ func TestInsertStem(t *testing.T) { values[5] = zeroKeyTest values[192] = fourtyKeyTest - if err := root1.(*InternalNode).InsertValuesAtStem(KeyToStem(fourtyKeyTest), values, 0, nil); err != nil { + if err := root1.(*InternalNode).InsertValuesAtStem(KeyToStem(fourtyKeyTest), values, 0, false, nil); err != nil { t.Fatalf("error inserting: %s", err) } r1c := root1.Commit() @@ -1198,7 +1198,7 @@ func TestInsertStemTouchingBothHalves(t *testing.T) { newValues := make([][]byte, NodeWidth) newValues[1] = testValue newValues[NodeWidth-2] = testValue - if err := root.(*InternalNode).InsertValuesAtStem(KeyToStem(zeroKeyTest), newValues, 0, nil); err != nil { + if err := root.(*InternalNode).InsertValuesAtStem(KeyToStem(zeroKeyTest), newValues, 0, false, nil); err != nil { t.Fatalf("error inserting stem: %v", err) } root.Commit() @@ -1347,7 +1347,7 @@ func TestRustBanderwagonBlock48(t *testing.T) { r := tree.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(tree, nil, keys, 0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(tree, nil, keys, 0, 0, nil) vp, statediff, err := SerializeProof(proof) if err != nil { t.Fatal(err) @@ -1368,7 +1368,7 @@ func TestRustBanderwagonBlock48(t *testing.T) { if err != nil { t.Fatal(err) } - pe, _, _, err := droot.GetProofItems(keys, nil) + pe, _, _, err := droot.GetProofItems(keylist(keys), 0, nil) if err != nil { t.Fatal(err) } @@ -1723,7 +1723,7 @@ const ( // Generate implements the quick.Generator interface from testing/quick // to generate random test cases. func (randTest) Generate(r *mRandV1.Rand, size int) reflect.Value { - finishedFn := func() bool { + var finishedFn = func() bool { if size == 0 { return true } @@ -1735,7 +1735,7 @@ func (randTest) Generate(r *mRandV1.Rand, size int) reflect.Value { func generateSteps(finished func() bool, r io.Reader) randTest { var allKeys [][]byte - tmp := []byte{0} + var tmp = []byte{0} genKey := func() []byte { _, err := r.Read(tmp) if err != nil { @@ -1819,7 +1819,7 @@ func runRandTest(rt randTest) error { continue } root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, keys, 0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, keys, 0, 0, nil) if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { rt[i].err = fmt.Errorf("could not verify verkle proof: %s, err %v", ToDot(root), err) } diff --git a/unknown.go b/unknown.go index 2532069..11ed7ee 100644 --- a/unknown.go +++ b/unknown.go @@ -51,7 +51,7 @@ func (UnknownNode) Commitment() *Point { return &id } -func (UnknownNode) GetProofItems(keylist, NodeResolverFn) (*ProofElements, []byte, []Stem, error) { +func (UnknownNode) GetProofItems(keylist, AccessTimestamp, NodeResolverFn) (*ProofElements, []byte, []Stem, error) { return nil, nil, nil, errors.New("can't generate proof items for unknown node") } diff --git a/unknown_test.go b/unknown_test.go index 8fb1c40..a0fca43 100644 --- a/unknown_test.go +++ b/unknown_test.go @@ -24,7 +24,7 @@ func TestUnknownFuncs(t *testing.T) { if comm := un.Commitment(); !comm.Equal(&identity) { t.Errorf("got %v, want identity", comm) } - if _, _, _, err := un.GetProofItems(nil, nil); err == nil { + if _, _, _, err := un.GetProofItems(nil, 0, nil); err == nil { t.Errorf("got nil error when getting proof items from a hashed node") } if _, err := un.Serialize(); err == nil {