From a27fc03de83a7e8ad0c22a02e51d2219ddffadba Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 19 Apr 2023 21:30:02 +0200 Subject: [PATCH] use nil in InternalNode to turn it into a stateless node --- doc.go | 1 + proof_ipa.go | 26 ++++++------- stateless_test.go | 16 ++++---- tree.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 120 insertions(+), 22 deletions(-) diff --git a/doc.go b/doc.go index b02bb265..aa55d323 100644 --- a/doc.go +++ b/doc.go @@ -36,6 +36,7 @@ var ( errNotSupportedInStateless = errors.New("not implemented in stateless") errInsertIntoOtherStem = errors.New("insert splits a stem where it should not happen") errStatelessAndStatefulMix = errors.New("a stateless node should not be found in a stateful tree") + errMissingNodeInStateless = errors.New("trying to access a node that is missing from the stateless view") ) const ( diff --git a/proof_ipa.go b/proof_ipa.go index 8877aa07..41808815 100644 --- a/proof_ipa.go +++ b/proof_ipa.go @@ -335,23 +335,23 @@ func TreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { } } - root := NewStatelessWithCommitment(rootC) + root := NewStatelessInternal().(*InternalNode) + root.commitment = rootC comms := proof.Cs for _, p := range paths { - comms, err = root.insertStem(p, info[string(p)], comms) - if err != nil { - return nil, err - } - } + values := make([][]byte, NodeWidth) + for i, k := range proof.Keys { + if len(proof.Values[i]) == 0 { + // Skip the nil keys, they are here to prove + // an absence. + continue + } - for i, k := range proof.Keys { - if len(proof.Values[i]) == 0 { - // Skip the nil keys, they are here to prove - // an absence. - continue + if bytes.Equal(k, info[string(p)].stem) { + values[k[31]] = proof.Values[i] + } } - - err = root.insertValue(k, proof.Values[i]) + comms, err = root.CreatePath(p, info[string(p)], comms, values) if err != nil { return nil, err } diff --git a/stateless_test.go b/stateless_test.go index 772d6d00..b11bd09a 100644 --- a/stateless_test.go +++ b/stateless_test.go @@ -250,6 +250,7 @@ func TestStatelessToDot(t *testing.T) { } } +// TODO move this to proof_test func TestStatelessDeserialize(t *testing.T) { root := New() for _, k := range [][]byte{zeroKeyTest, oneKeyTest, fourtyKeyTest, ffx32KeyTest} { @@ -278,11 +279,11 @@ func TestStatelessDeserialize(t *testing.T) { t.Fatalf("differing root commitments %x != %x", droot.Commitment().Bytes(), root.Commitment().Bytes()) } - if !Equal(droot.(*StatelessNode).children[0].(*StatelessNode).commitment, root.(*InternalNode).children[0].Commit()) { + if !Equal(droot.(*InternalNode).children[0].(*LeafNode).commitment, root.(*InternalNode).children[0].Commit()) { t.Fatal("differing commitment for child #0") } - if !Equal(droot.(*StatelessNode).children[64].Commit(), root.(*InternalNode).children[64].Commit()) { + if !Equal(droot.(*InternalNode).children[64].Commit(), root.(*InternalNode).children[64].Commit()) { t.Fatal("differing commitment for child #64") } } @@ -313,13 +314,14 @@ func TestStatelessDeserializeMissginChildNode(t *testing.T) { if !Equal(droot.Commit(), root.Commit()) { t.Fatal("differing root commitments") } - - if !Equal(droot.(*StatelessNode).children[0].Commit(), root.(*InternalNode).children[0].Commit()) { + t.Log(ToDot(root)) + t.Log(ToDot(droot)) + if !Equal(droot.(*InternalNode).children[0].Commit(), root.(*InternalNode).children[0].Commit()) { t.Fatal("differing commitment for child #0") } - if droot.(*StatelessNode).children[64] != nil { - t.Fatal("non-nil child #64") + if droot.(*InternalNode).children[64] != nil { + t.Fatalf("non-nil child #64: %v", droot.(*InternalNode).children[64]) } } @@ -351,7 +353,7 @@ func TestStatelessDeserializeDepth2(t *testing.T) { t.Fatal("differing root commitments") } - if !Equal(droot.(*StatelessNode).children[0].Commit(), root.(*InternalNode).children[0].Commit()) { + if !Equal(droot.(*InternalNode).children[0].Commit(), root.(*InternalNode).children[0].Commit()) { t.Fatal("differing commitment for child #0") } } diff --git a/tree.go b/tree.go index d505d65b..b87d0cdc 100644 --- a/tree.go +++ b/tree.go @@ -196,6 +196,12 @@ func New() VerkleNode { return newInternalNode(0) } +func NewStatelessInternal() VerkleNode { + node := new(InternalNode) + node.children = make([]VerkleNode, NodeWidth) + return node +} + // New creates a new leaf node func NewLeafNode(stem []byte, values [][]byte) *LeafNode { cfg := GetConfig() @@ -296,6 +302,11 @@ func (n *InternalNode) InsertStem(stem []byte, values [][]byte, resolver NodeRes n.cowChild(nChild) switch child := n.children[nChild].(type) { + case nil: + // Finding nil in a tree means that this is a stateless tree, so + // some information is missing. Typically, this means that the proof + // is incorrect, or that there is a bug in tree reconstruction. + return errMissingNodeInStateless case Empty: n.children[nChild] = NewLeafNode(stem, values) n.children[nChild].setDepth(n.depth + 1) @@ -351,9 +362,86 @@ func (n *InternalNode) InsertStem(stem []byte, values [][]byte, resolver NodeRes return nil } +// CreatePath inserts a given stem in the tree, placing it as +// described by stemInfo. Its third parameters is the list of +// commitments that have not been assigned a node. It returns +// the same list, save the commitments that were consumed +// during this call. +func (n *InternalNode) CreatePath(path []byte, stemInfo stemInfo, comms []*Point, values [][]byte) ([]*Point, error) { + if len(path) == 0 { + return comms, errors.New("invalid path") + } + + // path is 1 byte long, the leaf node must be created + if len(path) == 1 { + switch stemInfo.stemType & 3 { + case extStatusAbsentEmpty: + // nothing to do + case extStatusAbsentOther: + // insert poa stem + case extStatusPresent: + // insert stem + newchild := &LeafNode{ + commitment: comms[0], + stem: stemInfo.stem, + values: values, + depth: n.depth + 1, + } + n.children[path[0]] = newchild + comms = comms[1:] + if stemInfo.has_c1 { + newchild.c1 = comms[0] + comms = comms[1:] + } else { + newchild.c1 = new(Point) + } + if stemInfo.has_c2 { + newchild.c2 = comms[0] + comms = comms[1:] + } else { + newchild.c2 = new(Point) + } + for b, value := range stemInfo.values { + newchild.values[b] = value + } + } + return comms, nil + } + + switch child := n.children[path[0]].(type) { + case nil: + // create the child node if missing + n.children[path[0]] = &InternalNode{ + children: make([]VerkleNode, NodeWidth), + depth: n.depth + 1, + commitment: comms[0], + } + comms = comms[1:] + case *InternalNode: + // nothing else to do + case *LeafNode: + return comms, fmt.Errorf("error rebuilding the tree from a proof: stem %x leads to an already-existing leaf node at depth %x", stemInfo.stem, n.depth) + default: + return comms, fmt.Errorf("error rebuilding the tree from a proof: stem %x leads to an unsupported node type %v", stemInfo.stem, child) + } + + // This should only be used in the context of + // stateless nodes, so panic if another node + // type is found. + child := n.children[path[0]].(*InternalNode) + + // recurse + return child.CreatePath(path[1:], stemInfo, comms, values) +} + func (n *InternalNode) GetStem(stem []byte, resolver NodeResolverFn) ([][]byte, error) { nchild := offset2key(stem, n.depth) // index of the child pointed by the next byte in the key switch child := n.children[nchild].(type) { + case nil: + // Finding nil in a tree means that this is a stateless tree, so + // some information is missing. Typically, this means that the proof + // is incorrect, or that there is a bug in tree reconstruction. + return nil, errMissingNodeInStateless case Empty: return nil, nil case *HashedNode: @@ -641,7 +729,11 @@ func (n *InternalNode) GetProofItems(keys keylist) (*ProofElements, []byte, [][] var points [NodeWidth]*Point for i, child := range n.children { fiPtrs[i] = &fi[i] - points[i] = child.Commitment() + if child != nil { + points[i] = child.Commitment() + } else { + points[i] = new(Point) + } } toFrMultiple(fiPtrs[:], points[:]) @@ -665,7 +757,7 @@ func (n *InternalNode) GetProofItems(keys keylist) (*ProofElements, []byte, [][] // Special case of a proof of absence: no children // commitment, as the value is 0. - if _, ok := n.children[childIdx].(Empty); ok { + if _, ok := n.children[childIdx].(Empty); ok || n.children[childIdx] == nil { // A question arises here: what if this proof of absence // corresponds to several stems? Should the ext status be // repeated as many times? It would be wasteful, so the @@ -768,6 +860,9 @@ func (n *InternalNode) toDot(parent, path string) string { } for i, child := range n.children { + if child == nil { + continue + } ret = fmt.Sprintf("%s%s", ret, child.toDot(me, fmt.Sprintf("%s%02x", path, i))) }