diff --git a/empty.go b/empty.go index f8db089..4529bc7 100644 --- a/empty.go +++ b/empty.go @@ -43,7 +43,7 @@ func (Empty) Get([]byte, StatePeriod, NodeResolverFn) ([]byte, error) { return nil, nil } -func (Empty) Revive(Stem, [][]byte, StatePeriod, StatePeriod, NodeResolverFn) error { +func (Empty) Revive(Stem, [][]byte, StatePeriod, StatePeriod, bool, NodeResolverFn) error { return errors.New("cannot revive an empty node") } @@ -57,8 +57,8 @@ func (Empty) Commitment() *Point { return &id } -func (Empty) GetProofItems(keylist, NodeResolverFn) (*ProofElements, []byte, []Stem, error) { - return nil, nil, nil, errors.New("trying to produce a commitment for an empty subtree") +func (Empty) GetProofItems(keylist, NodeResolverFn, StatePeriod) (*ProofElements, []byte, []Stem, []StatePeriod, error) { + return nil, nil, nil, nil, errors.New("trying to produce a commitment for an empty subtree") } func (Empty) Serialize() ([]byte, error) { diff --git a/empty_test.go b/empty_test.go index 318e110..8f2177b 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, nil, period0); err == nil { t.Fatal("get proof items should error") } diff --git a/expired_leaf.go b/expired_leaf.go index 5728599..14e1afb 100644 --- a/expired_leaf.go +++ b/expired_leaf.go @@ -54,7 +54,7 @@ func (n *ExpiredLeafNode) Get([]byte, StatePeriod, NodeResolverFn) ([]byte, erro return nil, errExpired } -func (n *ExpiredLeafNode) Revive(Stem, [][]byte, StatePeriod, StatePeriod, NodeResolverFn) error { +func (n *ExpiredLeafNode) Revive(Stem, [][]byte, StatePeriod, StatePeriod, bool, NodeResolverFn) error { return errors.New("cannot revive an expired leaf node directly") } @@ -69,7 +69,7 @@ func (n *ExpiredLeafNode) Commitment() *Point { return n.commitment } -func (n *ExpiredLeafNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, error) { +func (n *ExpiredLeafNode) GetProofItems(keys keylist, resolver NodeResolverFn, _ StatePeriod) (*ProofElements, []byte, []Stem, []StatePeriod, error) { var ( pe = &ProofElements{ Vals: make([][]byte, len(keys)), @@ -87,7 +87,7 @@ func (n *ExpiredLeafNode) GetProofItems(keys keylist, resolver NodeResolverFn) ( poass = append(poass, n.stem) } - return pe, esses, poass, nil + return pe, esses, poass, []StatePeriod{n.lastPeriod}, nil } func (n *ExpiredLeafNode) Serialize() ([]byte, error) { diff --git a/expired_tree_test.go b/expired_tree_test.go index 8a490a8..9bfb7a5 100644 --- a/expired_tree_test.go +++ b/expired_tree_test.go @@ -10,11 +10,11 @@ func TestInsertSameLeafNoExpired(t *testing.T) { t.Parallel() root := New() - if err := root.Insert(zeroKeyTest, testValue, 0, nil); err != nil { + if err := root.Insert(zeroKeyTest, testValue, period0, nil); err != nil { t.Fatalf("error inserting: %v", err) } - if err := root.Insert(oneKeyTest, testValue, 1, nil); err != nil { + if err := root.Insert(oneKeyTest, testValue, period1, nil); err != nil { t.Fatalf("error inserting: %v", err) } @@ -31,7 +31,7 @@ func TestInsertSameLeafNoExpired(t *testing.T) { t.Fatalf("expected value %x, got %x", testValue, leaf.values[oneKeyTest[StemSize]]) } - if leaf.lastPeriod != 1 { + if leaf.lastPeriod != period1 { t.Fatalf("expected last accessed to be 1, got %d", leaf.lastPeriod) } } @@ -40,11 +40,11 @@ func TestInsertSameLeafExpired(t *testing.T) { t.Parallel() root := New() - if err := root.Insert(zeroKeyTest, testValue, 0, nil); err != nil { + if err := root.Insert(zeroKeyTest, testValue, period0, nil); err != nil { t.Fatalf("error inserting: %v", err) } - err := root.Insert(oneKeyTest, testValue, 2, nil) + err := root.Insert(oneKeyTest, testValue, period2, nil) if !errors.Is(err, errExpired) { t.Fatalf("expected period expired error when inserting, got %v", err) } @@ -58,7 +58,7 @@ func TestInsertSameLeafExpired(t *testing.T) { t.Fatalf("expected value %x, got %x", testValue, leaf.values[zeroKeyTest[StemSize]]) } - if leaf.lastPeriod != 0 { + if leaf.lastPeriod != period0 { t.Fatalf("expected last accessed to be 0, got %d", leaf.lastPeriod) } } @@ -67,11 +67,11 @@ func TestInsertDiffLeaf(t *testing.T) { t.Parallel() root := New() - if err := root.Insert(zeroKeyTest, testValue, 0, nil); err != nil { + if err := root.Insert(zeroKeyTest, testValue, period0, nil); err != nil { t.Fatalf("error inserting: %v", err) } - if err := root.Insert(ffx32KeyTest, testValue, 2, nil); err != nil { + if err := root.Insert(ffx32KeyTest, testValue, period2, nil); err != nil { t.Fatalf("error inserting: %v", err) } @@ -93,24 +93,63 @@ func TestInsertDiffLeaf(t *testing.T) { t.Fatalf("expected value %x, got %x", testValue, leaff.values[ffx32KeyTest[StemSize]]) } - if leaf0.lastPeriod != 0 { + if leaf0.lastPeriod != period0 { t.Fatalf("expected last accessed to be 0, got %d", leaf0.lastPeriod) } - if leaff.lastPeriod != 2 { + if leaff.lastPeriod != period2 { t.Fatalf("expected last accessed to be 2, got %d", leaff.lastPeriod) } } +func TestInsertExpiredLeafSibling(t *testing.T) { + t.Parallel() + + root := New() + if err := root.Insert(zeroKeyTest, testValue, period0, nil); err != nil { + t.Fatalf("error inserting: %v", err) + } + + leaf, ok := root.(*InternalNode).children[0].(*LeafNode) + if !ok { + t.Fatalf("expected leaf node, got %T", root.(*InternalNode).children[0]) + } + + root.(*InternalNode).children[0] = NewExpiredLeafNode(leaf.stem, leaf.lastPeriod, leaf.commitment) + + if err := root.Insert(forkOneKeyTest, testValue, period2, nil); err != nil { + t.Fatalf("error inserting: %v", err) + } + + c1 := root.Commit() + + // Reconstruct a new tree with the same key-values but without the expired leaf node + root2 := New() + if err := root2.Insert(zeroKeyTest, testValue, period0, nil); err != nil { + t.Fatalf("error inserting: %v", err) + } + + if err := root2.Insert(forkOneKeyTest, testValue, period2, nil); err != nil { + t.Fatalf("error inserting: %v", err) + } + + c2 := root2.Commit() + + // The two trees should have the same commitment + if !c1.Equal(c2) { + t.Fatalf("expected commitment to be %x, got %x", c1, c2) + } +} + func TestGetNoExpired(t *testing.T) { t.Parallel() root := New() - if err := root.Insert(zeroKeyTest, testValue, 0, nil); err != nil { + if err := root.Insert(zeroKeyTest, testValue, period0, nil); err != nil { t.Fatalf("error inserting: %v", err) } - val, err := root.Get(zeroKeyTest, 1, nil) + val, err := root.Get(zeroKeyTest, period1, nil) if err != nil { t.Fatalf("error getting: %v", err) } @@ -124,7 +163,7 @@ func TestGetNoExpired(t *testing.T) { t.Fatalf("expected value %x, got %x", testValue, val) } - if leaf.lastPeriod != 0 { + if leaf.lastPeriod != period0 { t.Fatalf("expected last accessed to be 0, got %d", leaf.lastPeriod) } } @@ -133,11 +172,11 @@ func TestGetExpired(t *testing.T) { t.Parallel() root := New() - if err := root.Insert(zeroKeyTest, testValue, 0, nil); err != nil { + if err := root.Insert(zeroKeyTest, testValue, period0, nil); err != nil { t.Fatalf("error inserting: %v", err) } - val, err := root.Get(zeroKeyTest, 2, nil) + val, err := root.Get(zeroKeyTest, period2, nil) if !errors.Is(err, errExpired) { t.Fatalf("expected period expired error when getting, got %v", err) } @@ -151,7 +190,7 @@ func TestGetExpired(t *testing.T) { t.Fatalf("expected leaf node, got %T", root.(*InternalNode).children[0]) } - if leaf.lastPeriod != 0 { + if leaf.lastPeriod != period0 { t.Fatalf("expected last accessed to be 0, got %d", leaf.lastPeriod) } } @@ -160,11 +199,11 @@ func TestDelLeafNoExpired(t *testing.T) { t.Parallel() root := New() - if err := root.Insert(zeroKeyTest, testValue, 0, nil); err != nil { + if err := root.Insert(zeroKeyTest, testValue, period0, nil); err != nil { t.Fatalf("error inserting: %v", err) } - if _, err := root.Delete(zeroKeyTest, 1, nil); err != nil { + if _, err := root.Delete(zeroKeyTest, period1, nil); err != nil { t.Fatalf("error deleting: %v", err) } @@ -178,11 +217,11 @@ func TestDelLeafExpired(t *testing.T) { t.Parallel() root := New() - if err := root.Insert(zeroKeyTest, testValue, 0, nil); err != nil { + if err := root.Insert(zeroKeyTest, testValue, period0, nil); err != nil { t.Fatalf("error inserting: %v", err) } - _, err := root.Delete(zeroKeyTest, 2, nil) + _, err := root.Delete(zeroKeyTest, period2, nil) if !errors.Is(err, errExpired) { t.Fatalf("expected period expired error when deleting, got %v", err) } @@ -192,16 +231,80 @@ func TestDelLeafExpired(t *testing.T) { t.Fatalf("expected empty node, got %T", root.(*InternalNode).children[0]) } - if leaf.lastPeriod != 0 { + if leaf.lastPeriod != period0 { t.Fatalf("expected last accessed to be 0, got %d", leaf.lastPeriod) } } +func TestReviveExpired(t *testing.T) { + t.Parallel() + + root := New() + if err := root.Insert(zeroKeyTest, testValue, period0, nil); err != nil { + t.Fatalf("error inserting: %v", err) + } + + leaf, ok := root.(*InternalNode).children[0].(*LeafNode) + if !ok { + t.Fatalf("expected leaf node, got %T", root.(*InternalNode).children[0]) + } + + expiredLeaf := NewExpiredLeafNode(leaf.stem, leaf.lastPeriod, leaf.commitment) + root.(*InternalNode).children[0] = expiredLeaf + + if err := root.Revive(leaf.stem, leaf.values, leaf.lastPeriod, period2, false, nil); err != nil { + t.Fatalf("error reviving: %v", err) + } + + rLeaf, ok := root.(*InternalNode).children[0].(*LeafNode) + if !ok { + t.Fatalf("expected leaf node, got %T", root.(*InternalNode).children[0]) + } + + if rLeaf.lastPeriod != period2 { + t.Fatalf("expected last accessed to be 2, got %d", rLeaf.lastPeriod) + } +} + +func TestReviveNoExpired(t *testing.T) { + t.Parallel() + + root := New() + if err := root.Insert(zeroKeyTest, testValue, period0, nil); err != nil { + t.Fatalf("error inserting: %v", err) + } + + leaf, ok := root.(*InternalNode).children[0].(*LeafNode) + if !ok { + t.Fatalf("expected leaf node, got %T", root.(*InternalNode).children[0]) + } + + comm := root.Commit() + + if err := root.Revive(leaf.stem, leaf.values, leaf.lastPeriod, period0, false, nil); err != nil { + t.Fatalf("error reviving: %v", err) + } + + rLeaf, ok := root.(*InternalNode).children[0].(*LeafNode) + if !ok { + t.Fatalf("expected leaf node, got %T", root.(*InternalNode).children[0]) + } + + if rLeaf.lastPeriod != period0 { + t.Fatalf("expected last accessed to be 0, got %d", rLeaf.lastPeriod) + } + + rComm := root.Commit() + if !rComm.Equal(comm) { + t.Fatalf("expected commitment to be %x, got %x", comm, rComm) + } +} + func TestRootCommitExpired(t *testing.T) { t.Parallel() root := New() - if err := root.Insert(zeroKeyTest, testValue, 0, nil); err != nil { + if err := root.Insert(zeroKeyTest, testValue, period0, nil); err != nil { t.Fatalf("error inserting: %v", err) } @@ -226,13 +329,13 @@ func TestRootCommitDiffEpoch(t *testing.T) { t.Parallel() root1 := New() - if err := root1.Insert(zeroKeyTest, testValue, 0, nil); err != nil { + if err := root1.Insert(zeroKeyTest, testValue, period0, nil); err != nil { t.Fatalf("error inserting: %v", err) } comm1 := root1.Commit() root2 := New() - if err := root2.Insert(zeroKeyTest, testValue, 2, nil); err != nil { + if err := root2.Insert(zeroKeyTest, testValue, period2, nil); err != nil { t.Fatalf("error inserting: %v", err) } comm2 := root2.Commit() diff --git a/hashednode.go b/hashednode.go index 89c7204..c64a012 100644 --- a/hashednode.go +++ b/hashednode.go @@ -44,7 +44,7 @@ func (HashedNode) Get([]byte, StatePeriod, NodeResolverFn) ([]byte, error) { return nil, errors.New("can not read from a hash node") } -func (HashedNode) Revive(Stem, [][]byte, StatePeriod, StatePeriod, NodeResolverFn) error { +func (HashedNode) Revive(Stem, [][]byte, StatePeriod, StatePeriod, bool, NodeResolverFn) error { return errors.New("cannot revive a hashed node") } @@ -62,8 +62,8 @@ func (HashedNode) Commitment() *Point { panic("can not get commitment of a hash node") } -func (HashedNode) GetProofItems(keylist, NodeResolverFn) (*ProofElements, []byte, []Stem, error) { - return nil, nil, nil, errors.New("can not get the full path, and there is no proof of absence") +func (HashedNode) GetProofItems(keylist, NodeResolverFn, StatePeriod) (*ProofElements, []byte, []Stem, []StatePeriod, error) { + return nil, nil, nil, nil, errors.New("can not get the full path, and there is no proof of absence") } func (HashedNode) Serialize() ([]byte, error) { diff --git a/hashednode_test.go b/hashednode_test.go index 1c48d9d..ad79f9b 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, nil, period0); err == nil { t.Fatal("got nil error when getting proof items from a hashed node") } if _, err := e.Serialize(); err != errSerializeHashedNode { diff --git a/period.go b/period.go index efbfb03..35edec5 100644 --- a/period.go +++ b/period.go @@ -8,7 +8,9 @@ type StatePeriod uint16 const ( NumActiveEpochs = 2 - period0 = 0 + period0 = StatePeriod(0) + period1 = StatePeriod(1) + period2 = StatePeriod(2) ) func IsExpired(prev, cur StatePeriod) bool { diff --git a/proof_ipa.go b/proof_ipa.go index e6eaf0e..3d3dfb7 100644 --- a/proof_ipa.go +++ b/proof_ipa.go @@ -105,23 +105,17 @@ func (vp *VerkleProof) Equal(other *VerkleProof) error { return nil } -// TODO(weiihann): add PoeStems for proof of expiry? It should be a list of (stem, lastPeriod) type Proof struct { Multipoint *ipa.MultiProof // multipoint argument ExtStatus []byte // the extension status of each stem Cs []*Point // commitments, sorted by their path in the tree PoaStems []Stem // stems proving another stem is absent - // PoeInfos []PoeInfo // stems proving another stem is expired + ExpiryPeriods []StatePeriod // periods of expired stems Keys [][]byte PreValues [][]byte PostValues [][]byte } -type PoeInfo struct { - Stem Stem - Period StatePeriod -} - type SuffixStateDiff struct { Suffix byte `json:"suffix"` CurrentValue *[32]byte `json:"currentValue"` @@ -133,7 +127,7 @@ type SuffixStateDiffs []SuffixStateDiff type StemStateDiff struct { Stem [StemSize]byte `json:"stem"` SuffixDiffs SuffixStateDiffs `json:"suffixDiffs"` - Resurrected bool `json:"resurrected"` + PeriodExpired *StatePeriod `json:"periodExpired,omitempty"` } type StateDiff []StemStateDiff @@ -143,7 +137,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 + ret[i].PeriodExpired = sd[i].PeriodExpired for j := range sd[i].SuffixDiffs { ret[i].SuffixDiffs[j].Suffix = sd[i].SuffixDiffs[j].Suffix if sd[i].SuffixDiffs[j].CurrentValue != nil { @@ -193,24 +187,24 @@ func (sd StateDiff) Equal(other StateDiff) error { return nil } -func GetCommitmentsForMultiproof(root VerkleNode, keys [][]byte, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, error) { +func GetCommitmentsForMultiproof(root VerkleNode, keys [][]byte, resolver NodeResolverFn, period StatePeriod) (*ProofElements, []byte, []Stem, []StatePeriod, error) { sort.Sort(keylist(keys)) - return root.GetProofItems(keylist(keys), resolver) + return root.GetProofItems(keylist(keys), resolver, period) } // 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, postEpoch StatePeriod, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, [][]byte, error) { +func getProofElementsFromTree(preroot, postroot VerkleNode, keys [][]byte, prePeriod, postEpoch StatePeriod, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, [][]byte, []StatePeriod, 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") + return nil, nil, nil, nil, nil, errors.New("no key provided for proof") } - pe, es, poas, err := GetCommitmentsForMultiproof(preroot, keys, resolver) + pe, es, poas, eps, err := GetCommitmentsForMultiproof(preroot, keys, resolver, prePeriod) if err != nil { - return nil, nil, nil, nil, fmt.Errorf("error getting pre-state proof data: %w", err) + return nil, nil, nil, nil, nil, fmt.Errorf("error getting pre-state proof data: %w", err) } // if a post-state tree is present, merge its proof elements with @@ -222,7 +216,7 @@ func getProofElementsFromTree(preroot, postroot VerkleNode, keys [][]byte, postE for i := range keys { val, err := postroot.Get(keys[i], postEpoch, resolver) if err != nil { - return nil, nil, nil, nil, fmt.Errorf("error getting post-state value for key %x: %w", keys[i], err) + return nil, nil, nil, nil, nil, fmt.Errorf("error getting post-state value for key %x: %w", keys[i], err) } if !bytes.Equal(pe.Vals[i], val) { postvals[i] = val @@ -232,11 +226,12 @@ func getProofElementsFromTree(preroot, postroot VerkleNode, keys [][]byte, postE // [0:3]: proof elements of the pre-state trie for serialization, // 3: values to be inserted in the post-state trie for serialization - return pe, es, poas, postvals, nil + // 4: expiry stems, also included in proof elements for serialization + return pe, es, poas, postvals, eps, nil } -func MakeVerkleMultiProof(preroot, postroot VerkleNode, keys [][]byte, postEpoch StatePeriod, resolver NodeResolverFn) (*Proof, []*Point, []byte, []*Fr, error) { - pe, es, poas, postvals, err := getProofElementsFromTree(preroot, postroot, keys, postEpoch, resolver) +func MakeVerkleMultiProof(preroot, postroot VerkleNode, keys [][]byte, prePeriod, postEpoch StatePeriod, resolver NodeResolverFn) (*Proof, []*Point, []byte, []*Fr, error) { + pe, es, poas, postvals, eps, err := getProofElementsFromTree(preroot, postroot, keys, prePeriod, postEpoch, resolver) if err != nil { return nil, nil, nil, nil, fmt.Errorf("get commitments for multiproof: %s", err) } @@ -270,6 +265,7 @@ func MakeVerkleMultiProof(preroot, postroot VerkleNode, keys [][]byte, postEpoch Cs: cis, ExtStatus: es, PoaStems: poas, + ExpiryPeriods: eps, Keys: keys, PreValues: pe.Vals, PostValues: postvals, @@ -278,8 +274,8 @@ func MakeVerkleMultiProof(preroot, postroot VerkleNode, keys [][]byte, postEpoch } // verifyVerkleProofWithPreState takes a proof and a trusted tree root and verifies that the proof is valid. -func verifyVerkleProofWithPreState(proof *Proof, preroot VerkleNode) error { - pe, _, _, _, err := getProofElementsFromTree(preroot, nil, proof.Keys, 0, nil) +func verifyVerkleProofWithPreState(proof *Proof, preroot VerkleNode, prePeriod StatePeriod) error { + pe, _, _, _, _, err := getProofElementsFromTree(preroot, nil, proof.Keys, prePeriod, period0,nil) if err != nil { return fmt.Errorf("error getting proof elements: %w", err) } @@ -325,19 +321,20 @@ func SerializeProof(proof *Proof) (*VerkleProof, StateDiff, error) { var stemdiff *StemStateDiff var statediff StateDiff - var checkResurrect bool var curExtStatus byte + var checkStemExpiry bool - curEsInd := -1 // index of the current extension status + curExtInd := -1 // index of the current extension status + curExpInd := 0 // index of the current expiry period 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] + checkStemExpiry = true + curExtInd += 1 + curExtStatus = proof.ExtStatus[curExtInd] } stemdiff.SuffixDiffs = append(stemdiff.SuffixDiffs, SuffixStateDiff{Suffix: key[StemSize]}) @@ -368,11 +365,11 @@ func SerializeProof(proof *Proof) (*VerkleProof, StateDiff, error) { 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 + if checkStemExpiry && curExtStatus&3 == extStatusExpired { + checkStemExpiry = false + period := proof.ExpiryPeriods[curExpInd] + stemdiff.PeriodExpired = &period + curExpInd += 1 } } @@ -399,6 +396,7 @@ func DeserializeProof(vp *VerkleProof, statediff StateDiff) (*Proof, error) { extStatus []byte commitments []*Point multipoint ipa.MultiProof + expiryPeriods []StatePeriod ) poaStems = make([]Stem, len(vp.OtherStems)) @@ -454,6 +452,10 @@ func DeserializeProof(vp *VerkleProof, statediff StateDiff) (*Proof, error) { postvalues = append(postvalues, nil) } } + + if stemdiff.PeriodExpired != nil { + expiryPeriods = append(expiryPeriods, *stemdiff.PeriodExpired) + } } proof := Proof{ @@ -461,6 +463,7 @@ func DeserializeProof(vp *VerkleProof, statediff StateDiff) (*Proof, error) { extStatus, commitments, poaStems, + expiryPeriods, keys, prevalues, postvalues, @@ -474,6 +477,7 @@ type stemInfo struct { has_c1, has_c2 bool values map[byte][]byte stem []byte + period StatePeriod } // PreStateTreeFromProof builds a stateless prestate tree from the proof. @@ -499,6 +503,7 @@ func PreStateTreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { // paths [][]byte err error poas = proof.PoaStems + eps = proof.ExpiryPeriods ) // The proof of absence stems must be sorted. If that isn't the case, the proof is invalid. @@ -577,7 +582,9 @@ func PreStateTreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { // } } si.stem = poas[0] + si.period = eps[0] poas = poas[1:] + eps = eps[1:] default: return nil, fmt.Errorf("invalid extension status: %d", si.stemType) } @@ -600,7 +607,7 @@ func PreStateTreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { // for i, k := range proof.Keys { if len(proof.PreValues[i]) == 0 { // Skip the nil keys, they are here to prove - // an absence. + // an absence or expiry. continue } @@ -640,10 +647,8 @@ func PostStateTreeFromStateDiff(preroot VerkleNode, statediff StateDiff, postEpo var stem [StemSize]byte copy(stem[:StemSize], stemstatediff.Stem[:]) - if stemstatediff.Resurrected { - // We skip verifying the revive of reconstructed leaf node because the post values may already be - // modified after reviving - if err := postroot.Revive(stem[:], values, period0, postEpoch, nil); err != nil { + if stemstatediff.PeriodExpired != nil { + if err := postroot.Revive(stem[:], values, *stemstatediff.PeriodExpired, postEpoch, true, nil); err != nil { return nil, fmt.Errorf("error reviving in post state: %w", err) } } else if overwrites { @@ -664,7 +669,7 @@ func (x bytesSlice) Less(i, j int) bool { return bytes.Compare(x[i], x[j]) < 0 } func (x bytesSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] } // Verify is the API function that verifies a verkle proofs as found in a block/execution payload. -func Verify(vp *VerkleProof, preStateRoot []byte, postStateRoot []byte, statediff StateDiff, postPeriod StatePeriod) error { +func Verify(vp *VerkleProof, preStateRoot []byte, postStateRoot []byte, statediff StateDiff, prePeriod, postPeriod StatePeriod) error { proof, err := DeserializeProof(vp, statediff) if err != nil { return fmt.Errorf("verkle proof deserialization error: %w", err) @@ -715,5 +720,5 @@ func Verify(vp *VerkleProof, preStateRoot []byte, postStateRoot []byte, statedif return fmt.Errorf("post tree root mismatch: %x != %x", regeneratedPostTreeRoot, postStateRoot) } - return verifyVerkleProofWithPreState(proof, pretree) + return verifyVerkleProofWithPreState(proof, pretree, prePeriod) } diff --git a/proof_json.go b/proof_json.go index 5c4e3d2..9b2be63 100644 --- a/proof_json.go +++ b/proof_json.go @@ -186,14 +186,14 @@ func (vp *VerkleProof) UnmarshalJSON(data []byte) error { type stemStateDiffMarshaller struct { Stem string `json:"stem"` SuffixDiffs SuffixStateDiffs `json:"suffixDiffs"` - Resurrected bool `json:"resurrected"` + PeriodExpired *StatePeriod `json:"periodExpired,omitempty"` } func (ssd StemStateDiff) MarshalJSON() ([]byte, error) { return json.Marshal(&stemStateDiffMarshaller{ Stem: HexToPrefixedString(ssd.Stem[:]), SuffixDiffs: ssd.SuffixDiffs, - Resurrected: ssd.Resurrected, + PeriodExpired: ssd.PeriodExpired, }) } @@ -209,7 +209,7 @@ func (ssd *StemStateDiff) UnmarshalJSON(data []byte) error { } *ssd = StemStateDiff{ SuffixDiffs: aux.SuffixDiffs, - Resurrected: aux.Resurrected, + PeriodExpired: aux.PeriodExpired, } copy(ssd.Stem[:], stem) return nil diff --git a/proof_test.go b/proof_test.go index 6a6bbfd..6546d36 100644 --- a/proof_test.go +++ b/proof_test.go @@ -37,17 +37,13 @@ import ( "github.com/crate-crypto/go-ipa/common" ) -const ( - period2 = 2 -) - func TestProofEmptyTree(t *testing.T) { t.Parallel() root := New() root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ffx32KeyTest}, period0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ffx32KeyTest}, period0, period0, 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)) @@ -69,7 +65,7 @@ func TestProofVerifyTwoLeaves(t *testing.T) { } root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ffx32KeyTest}, period0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ffx32KeyTest}, period0, period0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { @@ -96,7 +92,7 @@ func TestProofVerifyMultipleLeaves(t *testing.T) { } root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{keys[0]}, period0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{keys[0]}, period0, period0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { @@ -123,9 +119,9 @@ func TestMultiProofVerifyMultipleLeaves(t *testing.T) { } root.Commit() - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keys[0:2], period0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keys[0:2], period0, period0, nil) - pe, _, _, err := GetCommitmentsForMultiproof(root, keys[0:2], nil) + pe, _, _, _, err := GetCommitmentsForMultiproof(root, keys[0:2], nil, period0) if err != nil { t.Fatal(err) } @@ -163,9 +159,9 @@ func TestMultiProofVerifyMultipleLeavesWithAbsentStem(t *testing.T) { absent[3] = 1 // and the stem differs keys = append(keys, absent) - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keys, period0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keys, period0, period0, nil) - pe, _, isabsent, err := GetCommitmentsForMultiproof(root, keys, nil) + pe, _, isabsent, _, err := GetCommitmentsForMultiproof(root, keys, nil, period0) if err != nil { t.Fatal(err) } @@ -197,9 +193,9 @@ func TestMultiProofVerifyMultipleLeavesCommitmentRedundancy(t *testing.T) { } root.Commit() - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keys, period0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keys, period0, period0, nil) - pe, _, _, err := GetCommitmentsForMultiproof(root, keys, nil) + pe, _, _, _, err := GetCommitmentsForMultiproof(root, keys, nil, period0) if err != nil { t.Fatal(err) } @@ -221,7 +217,7 @@ func TestProofOfAbsenceInternalVerify(t *testing.T) { } root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ffx32KeyTest}, period0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ffx32KeyTest}, period0, period0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { @@ -241,7 +237,7 @@ func TestProofOfAbsenceLeafVerify(t *testing.T) { } root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{oneKeyTest}, period0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{oneKeyTest}, period0, period0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { @@ -266,7 +262,7 @@ func TestProofOfAbsenceLeafVerifyOtherSuffix(t *testing.T) { return ret }() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{key}, period0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{key}, period0, period0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { @@ -288,7 +284,7 @@ func TestProofOfAbsenceStemVerify(t *testing.T) { }() root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{key}, period0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{key}, period0, period0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cis, zis, yis, cfg); !ok || err != nil { @@ -314,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]}, period0, nil); err != nil { + if _, _, _, _, err := MakeVerkleMultiProof(root, nil, [][]byte{keys[len(keys)/2]}, period0, period0, nil); err != nil { b.Fatal(err) } } @@ -335,7 +331,7 @@ func BenchmarkProofVerification(b *testing.B) { } root.Commit() - proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{keys[len(keys)/2]}, period0, nil) + proof, cis, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{keys[len(keys)/2]}, period0, period0, nil) b.ResetTimer() b.ReportAllocs() @@ -366,7 +362,7 @@ func TestProofSerializationNoAbsentStem(t *testing.T) { } } - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, [][]byte{keys[0]}, period0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, [][]byte{keys[0]}, period0, period0, nil) vp, statediff, err := SerializeProof(proof) if err != nil { @@ -405,7 +401,7 @@ func TestProofSerializationWithAbsentStem(t *testing.T) { absentkey[2] = 2 absentkey[3] = 1 - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, [][]byte{absentkey[:]}, period0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, [][]byte{absentkey[:]}, period0, period0, nil) vp, statediff, err := SerializeProof(proof) if err != nil { @@ -446,7 +442,7 @@ func TestProofDeserialize(t *testing.T) { absentkey[2] = 2 absentkey[3] = 1 - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, [][]byte{absentkey[:]}, period0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, [][]byte{absentkey[:]}, period0, period0, nil) vp, statediff, err := SerializeProof(proof) if err != nil { @@ -459,7 +455,7 @@ func TestProofDeserialize(t *testing.T) { } _ = deserialized - pe, _, _, err := root.GetProofItems(keylist{absentkey[:]}, nil) + pe, _, _, _, err := root.GetProofItems(keylist{absentkey[:]}, nil, period0) if err != nil { t.Fatal(err) } @@ -476,7 +472,7 @@ func TestProofOfAbsenceEdgeCase(t *testing.T) { root.Commit() ret, _ := hex.DecodeString("0303030303030303030303030303030303030303030303030303030303030303") - proof, cs, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ret}, period0, nil) + proof, cs, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ret}, period0, period0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cs, zis, yis, cfg); !ok || err != nil { t.Fatal("could not verify proof") @@ -497,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}, period0, nil) + proof, cs, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ret1, ret2}, period0, period0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cs, zis, yis, cfg); !ok || err != nil { t.Fatal("could not verify proof") @@ -559,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}, period0, nil) + proof, cs, zis, yis, _ := MakeVerkleMultiProof(root, nil, [][]byte{ret1, ret2}, period0, period0, nil) cfg := GetConfig() if ok, err := verifyVerkleProof(proof, cs, zis, yis, cfg); !ok || err != nil { t.Fatal("could not verify proof") @@ -630,10 +626,10 @@ func TestStemStateDiffJSONMarshalUn(t *testing.T) { 0xDD, 0xEE, 0xFF, 0x00, }, }}, - Resurrected: true, + PeriodExpired: func() *StatePeriod { p := StatePeriod(100); return &p }(), } - expectedJSON := `{"stem":"0x0a000000000000000000000000000000000000000000000000000000000000","suffixDiffs":[{"suffix":65,"currentValue":"0x102030405060708090a0b0c0d0e0f000112233445566778899aabbccddeeff00","newValue":null}],"resurrected":true}` + expectedJSON := `{"stem":"0x0a000000000000000000000000000000000000000000000000000000000000","suffixDiffs":[{"suffix":65,"currentValue":"0x102030405060708090a0b0c0d0e0f000112233445566778899aabbccddeeff00","newValue":null}],"periodExpired":100}` actualJSON, err := json.Marshal(ssd) if err != nil { t.Errorf("error marshalling SuffixStateDiff to JSON: %v", err) @@ -866,7 +862,7 @@ func TestProofOfExpiryOneLeaf(t *testing.T) { t.Fatalf("expected commitment to be %x, got %x", init, comm) } - proof, cis, zis, yis, err := MakeVerkleMultiProof(root, nil, [][]byte{zeroKeyTest}, period0, nil) + proof, cis, zis, yis, err := MakeVerkleMultiProof(root, nil, [][]byte{zeroKeyTest}, period0, period0, nil) if err != nil { t.Fatalf("could not make verkle proof: %v", err) } @@ -918,7 +914,7 @@ func TestProofOfExpiryMultipleLeaves(t *testing.T) { t.Fatalf("expected commitment to be %x, got %x", init, comm) } - proof, cis, zis, yis, err := MakeVerkleMultiProof(root, nil, [][]byte{zeroKeyTest, ffx32KeyTest}, period0, nil) + proof, cis, zis, yis, err := MakeVerkleMultiProof(root, nil, [][]byte{zeroKeyTest, ffx32KeyTest}, period0, period0, nil) if err != nil { t.Fatalf("could not make verkle proof: %v", err) } @@ -974,7 +970,7 @@ func testSerializeDeserializeProof(t *testing.T, insertKVs map[string][]byte, pr proveKVs[string(key)] = value } - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, proveKeys, period0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, proveKeys, period0, period0, nil) serialized, statediff, err := SerializeProof(proof) if err != nil { @@ -1123,7 +1119,7 @@ func TestProofVerificationWithPostState(t *testing.T) { // skipcq: GO-R1005 } postroot.Commit() - proof, _, _, _, _ := MakeVerkleMultiProof(root, postroot, data.keystoprove, period0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, postroot, data.keystoprove, period0, period0, nil) keys: for i := range proof.Keys { @@ -1162,7 +1158,7 @@ func TestProofVerificationWithPostState(t *testing.T) { // skipcq: GO-R1005 t.Fatalf("error deserializing proof: %v", err) } - if err = verifyVerkleProofWithPreState(dproof, root); err != nil { + if err = verifyVerkleProofWithPreState(dproof, root, period0); err != nil { t.Fatalf("could not verify verkle proof: %v, original: %s reconstructed: %s", err, ToDot(root), ToDot(postroot)) } @@ -1180,7 +1176,7 @@ func TestProofVerificationWithPostState(t *testing.T) { // skipcq: GO-R1005 t.Fatalf("differing root commitments %x != %x", dpostroot.Commitment().Bytes(), postroot.Commitment().Bytes()) } - if err = verifyVerkleProofWithPreState(dproof, dpreroot); err != nil { + if err = verifyVerkleProofWithPreState(dproof, dpreroot, period0); err != nil { t.Fatalf("could not verify verkle proof: %v, original: %s reconstructed: %s", err, ToDot(dpreroot), ToDot(dpostroot)) } }) @@ -1191,11 +1187,11 @@ func TestProofVerificationWithPostState(t *testing.T) { // skipcq: GO-R1005 func TestProofVerificationPreStateExpiredPostStateResurrected(t *testing.T) { t.Parallel() - preEpoch := StatePeriod(0) - postEpoch := StatePeriod(2) + prePeriod := period0 + postPeriod := period2 preRoot := New() - if err := preRoot.Insert(zeroKeyTest, zeroKeyTest, preEpoch, nil); err != nil { + if err := preRoot.Insert(zeroKeyTest, zeroKeyTest, prePeriod, nil); err != nil { t.Fatalf("could not insert key: %v", err) } rootC := preRoot.Commit() @@ -1206,11 +1202,11 @@ func TestProofVerificationPreStateExpiredPostStateResurrected(t *testing.T) { preRoot.(*InternalNode).children[0] = expiredLeaf postRoot := New() - if err := postRoot.Insert(zeroKeyTest, fourtyKeyTest, postEpoch, nil); err != nil { + if err := postRoot.Insert(zeroKeyTest, fourtyKeyTest, postPeriod, nil); err != nil { t.Fatalf("could not insert key: %v", err) } - proof, _, _, _, _ := MakeVerkleMultiProof(preRoot, postRoot, keylist{zeroKeyTest}, postEpoch, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(preRoot, postRoot, keylist{zeroKeyTest}, prePeriod, postPeriod, nil) p, diff, err := SerializeProof(proof) if err != nil { @@ -1222,7 +1218,7 @@ func TestProofVerificationPreStateExpiredPostStateResurrected(t *testing.T) { t.Fatalf("error deserializing proof: %v", err) } - if err = verifyVerkleProofWithPreState(dproof, preRoot); err != nil { + if err = verifyVerkleProofWithPreState(dproof, preRoot, prePeriod); err != nil { t.Fatalf("could not verify verkle proof: %v", err) } @@ -1231,16 +1227,16 @@ func TestProofVerificationPreStateExpiredPostStateResurrected(t *testing.T) { t.Fatalf("error recreating pre tree: %v", err) } - dpostroot, err := PostStateTreeFromStateDiff(dpreroot, diff, postEpoch) + dpostroot, err := PostStateTreeFromStateDiff(dpreroot, diff, postPeriod) if err != nil { t.Fatalf("error recreating post tree: %v", err) } - if err = verifyVerkleProofWithPreState(dproof, dpreroot); err != nil { + if err = verifyVerkleProofWithPreState(dproof, dpreroot, prePeriod); err != nil { t.Fatalf("could not verify verkle proof: %v, original: %s reconstructed: %s", err, ToDot(dpreroot), ToDot(dpostroot)) } - got, err := dpostroot.Get(zeroKeyTest, postEpoch, nil) + got, err := dpostroot.Get(zeroKeyTest, postPeriod, nil) if err != nil { t.Fatalf("error getting key: %v", err) } @@ -1263,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}, period0, nil) + proof, cis, zis, yis, err := MakeVerkleMultiProof(root, nil, keylist{absentKey}, period0, period0, nil) if err != nil { t.Fatal(err) } @@ -1288,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}, nil, period0) if err != nil { t.Fatal(err) } @@ -1343,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}, period0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keylist{key2, key3}, period0, period0, nil) serialized, statediff, err := SerializeProof(proof) if err != nil { @@ -1392,7 +1388,7 @@ func TestProveAbsenceInEmptyHalf(t *testing.T) { key2, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000100") key3, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000") - proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keylist{key2, key3}, period0, nil) + proof, _, _, _, _ := MakeVerkleMultiProof(root, nil, keylist{key2, key3}, period0, period0, nil) serialized, statediff, err := SerializeProof(proof) if err != nil { diff --git a/tree.go b/tree.go index ce2dc0c..b17c6c5 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, NodeResolverFn, StatePeriod) (*ProofElements, []byte, []Stem, []StatePeriod, error) // Serialize encodes the node to RLP. Serialize() ([]byte, error) @@ -99,7 +99,7 @@ type VerkleNode interface { // Copy a node and its children Copy() VerkleNode - Revive(Stem, [][]byte, StatePeriod, StatePeriod, NodeResolverFn) error + Revive(Stem, [][]byte, StatePeriod, StatePeriod, bool, NodeResolverFn) error // toDot returns a string representing this subtree in DOT language toDot(string, string) string @@ -413,7 +413,30 @@ func (n *InternalNode) InsertValuesAtStem(stem Stem, values [][]byte, curPeriod // splits. return n.InsertValuesAtStem(stem, values, curPeriod, resolver) case *ExpiredLeafNode: - return errExpired + // If the stem is the same as the expired leaf node, we can't insert + if equalPaths(child.stem, stem) { + return errExpired + } + // Otherwise, insert a branch node + nextWordInExistingKey := offset2key(child.stem, n.depth+1) + newBranch := newInternalNode(n.depth + 1).(*InternalNode) + newBranch.cowChild(nextWordInExistingKey) + n.children[nChild] = newBranch + newBranch.children[nextWordInExistingKey] = child + child.depth += 1 + + nextWordInInsertedKey := offset2key(stem, n.depth+1) + if nextWordInInsertedKey == nextWordInExistingKey { + return newBranch.InsertValuesAtStem(stem, values, curPeriod, resolver) + } + + leaf, err := NewLeafNode(stem, values, curPeriod) + if err != nil { + return err + } + leaf.setDepth(n.depth + 2) + newBranch.cowChild(nextWordInInsertedKey) + newBranch.children[nextWordInInsertedKey] = leaf case *LeafNode: if equalPaths(child.stem, stem) { // We can't insert any values into a POA leaf node. @@ -459,7 +482,7 @@ func (n *InternalNode) InsertValuesAtStem(stem Stem, values [][]byte, curPeriod return nil } -func (n *InternalNode) Revive(stem Stem, values [][]byte, oldPeriod, curPeriod StatePeriod, resolver NodeResolverFn) error { +func (n *InternalNode) Revive(stem Stem, values [][]byte, oldPeriod, curPeriod StatePeriod, skipReviveVerify bool,resolver NodeResolverFn) error { nChild := offset2key(stem, n.depth) switch child := n.children[nChild].(type) { @@ -482,8 +505,18 @@ func (n *InternalNode) Revive(stem Stem, values [][]byte, oldPeriod, curPeriod S } n.children[nChild] = resolved n.cowChild(nChild) - return n.Revive(stem, values, oldPeriod, curPeriod, resolver) + return n.Revive(stem, values, oldPeriod, curPeriod, skipReviveVerify, resolver) case *ExpiredLeafNode: + if skipReviveVerify { + leaf, err := NewLeafNode(stem, values, curPeriod) + if err != nil { + return err + } + leaf.setDepth(n.depth + 1) + n.children[nChild] = leaf + return nil + } + leaf, err := NewLeafNode(stem, values, oldPeriod) if err != nil { return err @@ -501,7 +534,7 @@ func (n *InternalNode) Revive(stem Stem, values [][]byte, oldPeriod, curPeriod S return nil case *LeafNode: - return child.Revive(stem, values, oldPeriod, curPeriod, resolver) + return child.Revive(stem, values, oldPeriod, curPeriod, skipReviveVerify, resolver) } return nil @@ -586,10 +619,10 @@ func (n *InternalNode) CreatePath(path []byte, stemInfo stemInfo, comms []*Point if len(stemInfo.stem) != StemSize { return comms, fmt.Errorf("invalid stem size %d", len(stemInfo.stem)) } - // TODO(weiihann): add last period newchild := &ExpiredLeafNode{ commitment: comms[0], stem: stemInfo.stem, + lastPeriod: stemInfo.period, depth: n.depth + 1, } n.children[path[0]] = newchild @@ -724,7 +757,6 @@ func (n *InternalNode) Delete(key []byte, curPeriod StatePeriod, resolver NodeRe // DeleteAtStem delete a full stem. Unlike Delete, it will error out if the stem that is to // be deleted does not exist in the tree, because it's meant to be used by rollback code, // that should only delete things that exist. -// TODO(weiihann): check if need to compare access periods func (n *InternalNode) DeleteAtStem(key []byte, curPeriod StatePeriod, resolver NodeResolverFn) (bool, error) { nChild := offset2key(key, n.depth) switch child := n.children[nChild].(type) { @@ -1034,7 +1066,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, resolver NodeResolverFn, curPeriod StatePeriod) (*ProofElements, []byte, []Stem, []StatePeriod, error) { var ( groups = groupKeys(keys, n.depth) pe = &ProofElements{ @@ -1047,6 +1079,7 @@ func (n *InternalNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*Pr esses []byte = nil // list of extension statuses poass []Stem // list of proof-of-absence stems + expiryPeriods []StatePeriod ) // fill in the polynomial for this node @@ -1062,15 +1095,15 @@ func (n *InternalNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*Pr copy(childpath[:n.depth+1], keys[0][:n.depth]) childpath[n.depth] = byte(i) if resolver == nil { - return nil, nil, nil, fmt.Errorf("no resolver for path %x", childpath) + return nil, nil, nil, nil, fmt.Errorf("no resolver for path %x", childpath) } serialized, err := resolver(childpath) if err != nil { - return nil, nil, nil, fmt.Errorf("error resolving for path %x: %w", childpath, err) + return nil, nil, nil, nil, fmt.Errorf("error resolving for path %x: %w", childpath, err) } c, err = ParseNode(serialized, n.depth+1) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } n.children[i] = c } else { @@ -1083,7 +1116,7 @@ func (n *InternalNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*Pr } } if err := banderwagon.BatchMapToScalarField(fiPtrs[:], points[:]); err != nil { - return nil, nil, nil, fmt.Errorf("batch mapping to scalar fields: %s", err) + return nil, nil, nil, nil, fmt.Errorf("batch mapping to scalar fields: %s", err) } for _, group := range groups { @@ -1108,7 +1141,7 @@ func (n *InternalNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*Pr switch n.children[childIdx].(type) { case UnknownNode: // TODO: add a test case to cover this scenario. - return nil, nil, nil, errMissingNodeInStateless + return nil, nil, nil, nil,errMissingNodeInStateless case Empty: // Special case of a proof of absence: no children // commitment, as the value is 0. @@ -1129,17 +1162,18 @@ func (n *InternalNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*Pr continue } - pec, es, other, err := n.children[childIdx].GetProofItems(group, resolver) + pec, es, other, otherEs, err := n.children[childIdx].GetProofItems(group, resolver, curPeriod) if err != nil { // TODO: add a test case to cover this scenario. - return nil, nil, nil, err + return nil, nil, nil, nil, err } pe.Merge(pec) poass = append(poass, other...) esses = append(esses, es...) + expiryPeriods = append(expiryPeriods, otherEs...) } - return pe, esses, poass, nil + return pe, esses, poass, expiryPeriods, nil } // Serialize returns the serialized form of the internal node. @@ -1552,7 +1586,7 @@ func (n *LeafNode) Get(k []byte, curPeriod StatePeriod, _ NodeResolverFn) ([]byt return n.values[k[StemSize]], nil } -func (n *LeafNode) Revive(stem Stem, values [][]byte, oldPeriod, curPeriod StatePeriod, resolver NodeResolverFn) error { +func (n *LeafNode) Revive(stem Stem, values [][]byte, oldPeriod, curPeriod StatePeriod, _ bool, resolver NodeResolverFn) error { // No-op if already in current period if n.lastPeriod == curPeriod { return nil @@ -1663,7 +1697,7 @@ func leafToComms(poly []Fr, val []byte) error { return nil } -func (n *LeafNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, error) { // skipcq: GO-R1005 +func (n *LeafNode) GetProofItems(keys keylist, resolver NodeResolverFn, curPeriod StatePeriod) (*ProofElements, []byte, []Stem, []StatePeriod, error) { // skipcq: GO-R1005 var ( poly [NodeWidth]Fr // top-level polynomial pe = &ProofElements{ @@ -1676,13 +1710,20 @@ func (n *LeafNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*ProofE } esses []byte = nil // list of extension statuses - poass []Stem // list of proof-of-absence and proof-of-expiry stems + poass []Stem // list of proof-of-absence ) + // Leaf node is expired but not yet garbage collected, create a temporary expired leaf node + // and return its proof items. + if n.isExpired(curPeriod) { + expireLeaf := NewExpiredLeafNode(n.stem, n.lastPeriod, n.commitment) + return expireLeaf.GetProofItems(keys, resolver, curPeriod) + } + // Initialize the top-level polynomial with 1 + stem + C1 + C2 + lastPeriod 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) + return nil, nil, nil, nil, fmt.Errorf("error serializing stem '%x': %w", n.stem, err) } // First pass: add top-level elements first @@ -1704,14 +1745,14 @@ func (n *LeafNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*ProofE // 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) + return nil, nil, nil, nil, fmt.Errorf("batch mapping to scalar fields: %s", err) } } else if hasC1 || hasC2 || n.c1 != nil || n.c2 != nil { // This LeafNode is a proof of absence stub. It must be true that // both c1 and c2 are nil, and that hasC1 and hasC2 are false. // Let's just check that to be sure, since the code below can't use // poly[2] or poly[3]. - return nil, nil, nil, fmt.Errorf("invalid proof of absence stub") + return nil, nil, nil, nil,fmt.Errorf("invalid proof of absence stub") } if hasC1 { @@ -1727,8 +1768,6 @@ func (n *LeafNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*ProofE pe.Fis = append(pe.Fis, poly[:]) } - // We do not check for expiry, as we assume that the expiry detection is done before - // proof creation. poly[4].SetUint64(uint64(n.lastPeriod)) pe.Cis = append(pe.Cis, n.commitment) @@ -1781,12 +1820,12 @@ func (n *LeafNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*ProofE ) if suffix >= 128 { if _, err = fillSuffixTreePoly(suffPoly[:], n.values[128:]); err != nil { - return nil, nil, nil, fmt.Errorf("filling suffix tree poly: %w", err) + return nil, nil, nil, nil,fmt.Errorf("filling suffix tree poly: %w", err) } scomm = n.c2 } else { if _, err = fillSuffixTreePoly(suffPoly[:], n.values[:128]); err != nil { - return nil, nil, nil, fmt.Errorf("filling suffix tree poly: %w", err) + return nil, nil, nil, nil,fmt.Errorf("filling suffix tree poly: %w", err) } scomm = n.c1 } @@ -1820,7 +1859,7 @@ func (n *LeafNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*ProofE pe.ByPath[slotPath] = scomm } - return pe, esses, poass, nil + return pe, esses, poass, nil, nil } // Serialize serializes a LeafNode. diff --git a/tree_test.go b/tree_test.go index 72b80cb..840d67d 100644 --- a/tree_test.go +++ b/tree_test.go @@ -1047,7 +1047,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}, nil, period0) if err != nil { t.Fatal(err) } @@ -1114,7 +1114,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}, nil, period0) if err != nil { t.Fatal(err) } @@ -1369,7 +1369,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, period0, period0, nil) vp, statediff, err := SerializeProof(proof) if err != nil { t.Fatal(err) @@ -1390,7 +1390,7 @@ func TestRustBanderwagonBlock48(t *testing.T) { if err != nil { t.Fatal(err) } - pe, _, _, err := droot.GetProofItems(keylist(keys), nil) + pe, _, _, _, err := droot.GetProofItems(keylist(keys), nil, period0) if err != nil { t.Fatal(err) } @@ -1841,7 +1841,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, period0, period0, 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 b7f4f15..093b673 100644 --- a/unknown.go +++ b/unknown.go @@ -41,7 +41,7 @@ func (UnknownNode) Get([]byte, StatePeriod, NodeResolverFn) ([]byte, error) { return nil, nil } -func (UnknownNode) Revive(Stem, [][]byte, StatePeriod, StatePeriod, NodeResolverFn) error { +func (UnknownNode) Revive(Stem, [][]byte, StatePeriod, StatePeriod, bool, NodeResolverFn) error { return errors.New("cannot revive an unknown node") } @@ -55,8 +55,8 @@ func (UnknownNode) Commitment() *Point { return &id } -func (UnknownNode) GetProofItems(keylist, NodeResolverFn) (*ProofElements, []byte, []Stem, error) { - return nil, nil, nil, errors.New("can't generate proof items for unknown node") +func (UnknownNode) GetProofItems(keylist, NodeResolverFn, StatePeriod) (*ProofElements, []byte, []Stem, []StatePeriod, error) { + return nil, nil, nil, nil, errors.New("can't generate proof items for unknown node") } func (UnknownNode) Serialize() ([]byte, error) { diff --git a/unknown_test.go b/unknown_test.go index 8fb1c40..d78688b 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, nil, period0); err == nil { t.Errorf("got nil error when getting proof items from a hashed node") } if _, err := un.Serialize(); err == nil {