// This is free and unencumbered software released into the public domain. // // Anyone is free to copy, modify, publish, use, compile, sell, or // distribute this software, either in source code form or as a compiled // binary, for any purpose, commercial or non-commercial, and by any // means. // // In jurisdictions that recognize copyright laws, the author or authors // of this software dedicate any and all copyright interest in the // software to the public domain. We make this dedication for the benefit // of the public at large and to the detriment of our heirs and // successors. We intend this dedication to be an overt act of // relinquishment in perpetuity of all present and future rights to this // software under copyright law. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // For more information, please refer to package verkle import ( "bytes" "encoding/hex" "encoding/json" "errors" "fmt" "runtime" "sync" "github.com/crate-crypto/go-ipa/banderwagon" ) type ( NodeFlushFn func([]byte, VerkleNode) NodeResolverFn func([]byte) ([]byte, error) ) type keylist [][]byte func (kl keylist) Len() int { return len(kl) } func (kl keylist) Less(i, j int) bool { return bytes.Compare(kl[i], kl[j]) == -1 } func (kl keylist) Swap(i, j int) { kl[i], kl[j] = kl[j], kl[i] } type Stem []byte func KeyToStem(key []byte) Stem { if len(key) < StemSize { panic(fmt.Errorf("key length (%d) is shorter than the expected stem size (%d)", len(key), StemSize)) } return Stem(key[:StemSize]) } type VerkleNode interface { // Insert or Update value into the tree Insert([]byte, []byte, NodeResolverFn) error // Delete a leaf with the given key Delete([]byte, NodeResolverFn) (bool, error) // Get value at a given key Get([]byte, NodeResolverFn) ([]byte, error) // Commit computes the commitment of the node. The // result (the curve point) is cached. Commit() *Point // Commitment is a getter for the cached commitment // to this node. Commitment() *Point // Hash returns the field representation of the commitment. Hash() *Fr // GetProofItems collects the various proof elements, and // 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) // Serialize encodes the node to RLP. Serialize() ([]byte, error) // Copy a node and its children Copy() VerkleNode // toDot returns a string representing this subtree in DOT language toDot(string, string) string setDepth(depth byte) } // ProofElements gathers the elements needed to build a proof. type ProofElements struct { Cis []*Point Zis []byte Yis []*Fr Fis [][]Fr ByPath map[string]*Point // Gather commitments by path Vals [][]byte // list of values read from the tree // dedups flags the presence of each (Ci,zi) tuple dedups map[*Point]map[byte]struct{} } // Merge merges the elements of two proofs and removes duplicates. func (pe *ProofElements) Merge(other *ProofElements) { // Build the local map if it's missing if pe.dedups == nil { pe.dedups = make(map[*Point]map[byte]struct{}) for i, ci := range pe.Cis { if _, ok := pe.dedups[ci]; !ok { pe.dedups[ci] = make(map[byte]struct{}) } pe.dedups[ci][pe.Zis[i]] = struct{}{} } } for i, ci := range other.Cis { if _, ok := pe.dedups[ci]; !ok { // First time this commitment has been seen, create // the map and flatten the zi. pe.dedups[ci] = make(map[byte]struct{}) } if _, ok := pe.dedups[ci][other.Zis[i]]; ok { // duplicate, skip continue } pe.dedups[ci][other.Zis[i]] = struct{}{} pe.Cis = append(pe.Cis, ci) pe.Zis = append(pe.Zis, other.Zis[i]) pe.Yis = append(pe.Yis, other.Yis[i]) if pe.Fis != nil { pe.Fis = append(pe.Fis, other.Fis[i]) } } for path, C := range other.ByPath { if _, ok := pe.ByPath[path]; !ok { pe.ByPath[path] = C } } pe.Vals = append(pe.Vals, other.Vals...) } const ( // These types will distinguish internal // and leaf nodes when decoding from RLP. internalType byte = 1 leafType byte = 2 eoAccountType byte = 3 singleSlotType byte = 4 ) type ( // Represents an internal node at any level InternalNode struct { // List of child nodes of this internal node. children []VerkleNode // node depth in the tree, in bits depth byte // Cache the commitment value commitment *Point cow map[byte]*Point } LeafNode struct { stem Stem values [][]byte commitment *Point c1, c2 *Point depth byte // IsPOAStub indicates if this LeafNode is a proof of absence // for a steam that isn't present in the tree. This flag is only // true in the context of a stateless tree. isPOAStub bool } ) func (n *InternalNode) toExportable() *ExportableInternalNode { comm := n.commitment.Bytes() exportable := &ExportableInternalNode{ Children: make([]interface{}, NodeWidth), Commitment: comm[:], } for i := range exportable.Children { switch child := n.children[i].(type) { case Empty: exportable.Children[i] = nil case HashedNode: exportable.Children[i] = n.commitment.Bytes() case *InternalNode: exportable.Children[i] = child.toExportable() case *LeafNode: exportable.Children[i] = &ExportableLeafNode{ Stem: child.stem, Values: child.values, C: child.commitment.Bytes(), C1: child.c1.Bytes(), } default: panic("unexportable type") } } return exportable } // Turn an internal node into a JSON string func (n *InternalNode) ToJSON() ([]byte, error) { return json.Marshal(n.toExportable()) } func newInternalNode(depth byte) VerkleNode { node := new(InternalNode) node.children = make([]VerkleNode, NodeWidth) for idx := range node.children { node.children[idx] = Empty(struct{}{}) } node.depth = depth node.commitment = new(Point).SetIdentity() return node } // New creates a new tree root func New() VerkleNode { return newInternalNode(0) } func NewStatelessInternal(depth byte, comm *Point) VerkleNode { node := &InternalNode{ children: make([]VerkleNode, NodeWidth), depth: depth, commitment: comm, } for idx := range node.children { node.children[idx] = UnknownNode(struct{}{}) } return node } // New creates a new leaf node func NewLeafNode(stem Stem, values [][]byte) (*LeafNode, error) { cfg := GetConfig() // C1. var c1poly [NodeWidth]Fr var c1 *Point count, err := fillSuffixTreePoly(c1poly[:], values[:NodeWidth/2]) if err != nil { return nil, err } containsEmptyCodeHash := c1poly[EmptyCodeHashFirstHalfIdx].Equal(&EmptyCodeHashFirstHalfValue) && c1poly[EmptyCodeHashSecondHalfIdx].Equal(&EmptyCodeHashSecondHalfValue) if containsEmptyCodeHash { // Clear out values of the cached point. c1poly[EmptyCodeHashFirstHalfIdx] = FrZero c1poly[EmptyCodeHashSecondHalfIdx] = FrZero // Calculate the remaining part of c1 and add to the base value. partialc1 := cfg.CommitToPoly(c1poly[:], NodeWidth-count-2) c1 = new(Point) c1.Add(&EmptyCodeHashPoint, partialc1) } else { c1 = cfg.CommitToPoly(c1poly[:], NodeWidth-count) } // C2. var c2poly [NodeWidth]Fr count, err = fillSuffixTreePoly(c2poly[:], values[NodeWidth/2:]) if err != nil { return nil, err } c2 := cfg.CommitToPoly(c2poly[:], NodeWidth-count) // Root commitment preparation for calculation. stem = stem[:StemSize] // enforce a 31-byte length var poly [NodeWidth]Fr poly[0].SetUint64(1) if err := StemFromLEBytes(&poly[1], stem); err != nil { return nil, err } if err := banderwagon.BatchMapToScalarField([]*Fr{&poly[2], &poly[3]}, []*Point{c1, c2}); err != nil { return nil, fmt.Errorf("batch mapping to scalar fields: %s", err) } return &LeafNode{ // depth will be 0, but the commitment calculation // does not need it, and so it won't be free. values: values, stem: stem, commitment: cfg.CommitToPoly(poly[:], NodeWidth-4), c1: c1, c2: c2, }, nil } // NewLeafNodeWithNoComms create a leaf node but does not compute its // commitments. The created node's commitments are intended to be // initialized with `SetTrustedBytes` in a deserialization context. func NewLeafNodeWithNoComms(stem Stem, values [][]byte) *LeafNode { return &LeafNode{ // depth will be 0, but the commitment calculation // does not need it, and so it won't be free. values: values, stem: stem, } } // Children return the children of the node. The returned slice is // internal to the tree, so callers *must* consider it readonly. func (n *InternalNode) Children() []VerkleNode { return n.children } // SetChild *replaces* the child at the given index with the given node. func (n *InternalNode) SetChild(i int, c VerkleNode) error { if i >= NodeWidth { return errors.New("child index higher than node width") } n.children[i] = c return nil } func (n *InternalNode) cowChild(index byte) { if n.cow == nil { n.cow = make(map[byte]*Point) } if n.cow[index] == nil { n.cow[index] = new(Point) n.cow[index].Set(n.children[index].Commitment()) } } func (n *InternalNode) Insert(key []byte, value []byte, resolver NodeResolverFn) error { values := make([][]byte, NodeWidth) values[key[StemSize]] = value return n.InsertValuesAtStem(KeyToStem(key), values, resolver) } func (n *InternalNode) InsertValuesAtStem(stem Stem, values [][]byte, 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) { case UnknownNode: return errMissingNodeInStateless case Empty: n.cowChild(nChild) var err error n.children[nChild], err = NewLeafNode(stem, values) if err != nil { return err } n.children[nChild].setDepth(n.depth + 1) case HashedNode: if resolver == nil { return errInsertIntoHash } serialized, err := resolver(stem[:n.depth+1]) if err != nil { return fmt.Errorf("verkle tree: error resolving node %x at depth %d: %w", stem, n.depth, err) } resolved, err := ParseNode(serialized, n.depth+1) if err != nil { return fmt.Errorf("verkle tree: error parsing resolved node %x: %w", stem, err) } n.children[nChild] = resolved n.cowChild(nChild) // recurse to handle the case of a LeafNode child that // splits. return n.InsertValuesAtStem(stem, values, resolver) case *LeafNode: if equalPaths(child.stem, stem) { // We can't insert any values into a POA leaf node. if child.isPOAStub { return errIsPOAStub } n.cowChild(nChild) return child.insertMultiple(stem, values) } n.cowChild(nChild) // A new branch node has to be inserted. Depending // on the next word in both keys, a recursion into // the moved leaf node can occur. 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, resolver) } // Next word differs, so this was the last level. // Insert it directly into its final slot. leaf, err := NewLeafNode(stem, values) if err != nil { return err } leaf.setDepth(n.depth + 2) newBranch.cowChild(nextWordInInsertedKey) newBranch.children[nextWordInInsertedKey] = leaf case *InternalNode: n.cowChild(nChild) return child.InsertValuesAtStem(stem, values, resolver) default: // It should be an UknownNode. return errUnknownNodeType } 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) { // skipcq: GO-R1005 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: // Set child to Empty so that, in a stateless context, // a node known to be absent is differentiated from an // unknown node. n.children[path[0]] = Empty{} case extStatusAbsentOther: 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)) } // insert poa stem newchild := &LeafNode{ commitment: comms[0], stem: stemInfo.stem, values: nil, depth: n.depth + 1, isPOAStub: true, } n.children[path[0]] = newchild comms = comms[1:] case extStatusPresent: 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)) } // 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 { if len(comms) == 0 { return comms, fmt.Errorf("missing commitment for stem %x", stemInfo.stem) } newchild.c1 = comms[0] comms = comms[1:] } else { newchild.c1 = new(Point) } if stemInfo.has_c2 { if len(comms) == 0 { return comms, fmt.Errorf("missing commitment for stem %x", stemInfo.stem) } newchild.c2 = comms[0] comms = comms[1:] } else { newchild.c2 = new(Point) } for b, value := range stemInfo.values { newchild.values[b] = value } default: return comms, fmt.Errorf("invalid stem type %d", stemInfo.stemType) } return comms, nil } switch child := n.children[path[0]].(type) { case UnknownNode: // create the child node if missing n.children[path[0]] = NewStatelessInternal(n.depth+1, 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) } // GetValuesAtStem returns the all NodeWidth values of the stem. // The returned slice is internal to the tree, so it *must* be considered readonly // for callers. func (n *InternalNode) GetValuesAtStem(stem Stem, 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 UnknownNode: return nil, errMissingNodeInStateless case Empty: return nil, nil case HashedNode: if resolver == nil { return nil, fmt.Errorf("hashed node %x at depth %d along stem %x could not be resolved: %w", child.Commitment().Bytes(), n.depth, stem, errReadFromInvalid) } serialized, err := resolver(stem[:n.depth+1]) if err != nil { return nil, fmt.Errorf("resolving node %x at depth %d: %w", stem, n.depth, err) } resolved, err := ParseNode(serialized, n.depth+1) if err != nil { return nil, fmt.Errorf("verkle tree: error parsing resolved node %x: %w", stem, err) } n.children[nchild] = resolved // recurse to handle the case of a LeafNode child that // splits. return n.GetValuesAtStem(stem, resolver) case *LeafNode: if equalPaths(child.stem, stem) { // We can't return the values since it's a POA leaf node, so we know nothing // about its values. if child.isPOAStub { return nil, errIsPOAStub } return child.values, nil } return nil, nil case *InternalNode: return child.GetValuesAtStem(stem, resolver) default: return nil, errUnknownNodeType } } func (n *InternalNode) Delete(key []byte, resolver NodeResolverFn) (bool, error) { nChild := offset2key(key, n.depth) switch child := n.children[nChild].(type) { case Empty: return false, nil case HashedNode: if resolver == nil { return false, errDeleteHash } payload, err := resolver(key[:n.depth+1]) if err != nil { return false, err } // deserialize the payload and set it as the child c, err := ParseNode(payload, n.depth+1) if err != nil { return false, err } n.children[nChild] = c return n.Delete(key, resolver) default: n.cowChild(nChild) del, err := child.Delete(key, resolver) if err != nil { return false, err } // delete the entire child if instructed to by // the recursive algorigthm. if del { n.children[nChild] = Empty{} // Check if all children are gone, if so // signal that this node should be deleted // as well. for _, c := range n.children { if _, ok := c.(Empty); !ok { return false, nil } } return true, nil } return false, nil } } // 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. func (n *InternalNode) DeleteAtStem(key []byte, resolver NodeResolverFn) (bool, error) { nChild := offset2key(key, n.depth) switch child := n.children[nChild].(type) { case Empty: return false, errDeleteMissing case HashedNode: if resolver == nil { return false, errDeleteHash } payload, err := resolver(key[:n.depth+1]) if err != nil { return false, err } // deserialize the payload and set it as the child c, err := ParseNode(payload, n.depth+1) if err != nil { return false, err } n.children[nChild] = c return n.DeleteAtStem(key, resolver) case *LeafNode: if !bytes.Equal(child.stem, key[:31]) { return false, errDeleteMissing } n.cowChild(nChild) n.children[nChild] = Empty{} // Check if all children are gone, if so // signal that this node should be deleted // as well. for _, c := range n.children { if _, ok := c.(Empty); !ok { return false, nil } } return true, nil case *InternalNode: n.cowChild(nChild) del, err := child.DeleteAtStem(key, resolver) if err != nil { return false, err } // delete the entire child if instructed to by // the recursive algorigthm. if del { n.children[nChild] = Empty{} // Check if all children are gone, if so // signal that this node should be deleted // as well. for _, c := range n.children { if _, ok := c.(Empty); !ok { return false, nil } } return true, nil } return false, nil default: // only unknown nodes are left return false, errDeleteUnknown } } // Flush hashes the children of an internal node and replaces them // with HashedNode. It also sends the current node on the flush channel. func (n *InternalNode) Flush(flush NodeFlushFn) { // var ( path []byte flushAndCapturePath = func(p []byte, vn VerkleNode) { // wrap the flush function into a function that will // capture the path of the first leaf being flushed, // so that it can be used as the path to call `flush`. if len(path) == 0 { path = p[:n.depth] } flush(p, vn) } ) n.Commit() for i, child := range n.children { if c, ok := child.(*InternalNode); ok { c.Commit() c.Flush(flushAndCapturePath) n.children[i] = HashedNode{} } else if c, ok := child.(*LeafNode); ok { c.Commit() flushAndCapturePath(c.stem[:n.depth+1], n.children[i]) n.children[i] = HashedNode{} } } flush(path, n) } // FlushAtDepth goes over all internal nodes of a given depth, and // flushes them to disk. Its purpose it to free up space if memory // is running scarce. func (n *InternalNode) FlushAtDepth(depth uint8, flush NodeFlushFn) { for i, child := range n.children { // Skip non-internal nodes c, ok := child.(*InternalNode) if !ok { if c, ok := child.(*LeafNode); ok { c.Commit() flush(c.stem[:c.depth], c) n.children[i] = HashedNode{} } continue } // Not deep enough, recurse if n.depth < depth { c.FlushAtDepth(depth, flush) continue } child.Commit() c.Flush(flush) n.children[i] = HashedNode{} } } func (n *InternalNode) Get(key []byte, resolver NodeResolverFn) ([]byte, error) { if len(key) != StemSize+1 { return nil, fmt.Errorf("invalid key length, expected %d, got %d", StemSize+1, len(key)) } stemValues, err := n.GetValuesAtStem(KeyToStem(key), resolver) if err != nil { return nil, err } // If the stem results in an empty node, return nil. if stemValues == nil { return nil, nil } // Return nil as a signal that the value isn't // present in the tree. This matches the behavior // of SecureTrie in Geth. return stemValues[key[StemSize]], nil } func (n *InternalNode) Hash() *Fr { var hash Fr n.Commitment().MapToScalarField(&hash) return &hash } func (n *InternalNode) Commitment() *Point { if n.commitment == nil { panic("nil commitment") } return n.commitment } func (n *InternalNode) fillLevels(levels [][]*InternalNode) { levels[int(n.depth)] = append(levels[int(n.depth)], n) for idx := range n.cow { child := n.children[idx] if childInternalNode, ok := child.(*InternalNode); ok && len(childInternalNode.cow) > 0 { childInternalNode.fillLevels(levels) } } } func (n *InternalNode) Commit() *Point { if len(n.cow) == 0 { return n.commitment } internalNodeLevels := make([][]*InternalNode, StemSize) n.fillLevels(internalNodeLevels) for level := len(internalNodeLevels) - 1; level >= 0; level-- { nodes := internalNodeLevels[level] if len(nodes) == 0 { continue } minBatchSize := 4 if len(nodes) <= minBatchSize { if err := commitNodesAtLevel(nodes); err != nil { // TODO: make Commit() return an error panic(err) } } else { var wg sync.WaitGroup numBatches := runtime.NumCPU() batchSize := (len(nodes) + numBatches - 1) / numBatches if batchSize < minBatchSize { batchSize = minBatchSize } for i := 0; i < len(nodes); i += batchSize { start := i end := i + batchSize if end > len(nodes) { end = len(nodes) } wg.Add(1) go func() { defer wg.Done() if err := commitNodesAtLevel(nodes[start:end]); err != nil { // TODO: make Commit() return an error panic(err) } }() } wg.Wait() } } return n.commitment } func commitNodesAtLevel(nodes []*InternalNode) error { points := make([]*Point, 0, 1024) cowIndexes := make([]int, 0, 1024) // For each internal node, we collect in `points` all the ones we need to map to a field element. // That is, for each touched children in a node, we collect the old and new commitment to do the diff updating // later. for _, node := range nodes { for idx, nodeChildComm := range node.cow { points = append(points, nodeChildComm) points = append(points, node.children[idx].Commitment()) cowIndexes = append(cowIndexes, int(idx)) } } // We generate `frs` which will contain the result for each element in `points`. frs := make([]*Fr, len(points)) for i := range frs { frs[i] = &Fr{} } // Do a single batch calculation for all the points in this level. if err := banderwagon.BatchMapToScalarField(frs, points); err != nil { return fmt.Errorf("batch mapping to scalar fields: %s", err) } // We calculate the difference between each (new commitment - old commitment) pair, and store it // in the same slice to avoid allocations. for i := 0; i < len(frs); i += 2 { frs[i/2].Sub(frs[i+1], frs[i]) } // Now `frs` have half of the elements, and these are the Frs differences to update commitments. frs = frs[:len(frs)/2] // Now we iterate on the nodes, and use this calculated differences to update their commitment. var frsIdx int var cowIndex int for _, node := range nodes { poly := make([]Fr, NodeWidth) for i := 0; i < len(node.cow); i++ { poly[cowIndexes[cowIndex]] = *frs[frsIdx] frsIdx++ cowIndex++ } node.cow = nil node.commitment.Add(node.commitment, cfg.CommitToPoly(poly, 0)) } return nil } // groupKeys groups a set of keys based on their byte at a given depth. func groupKeys(keys keylist, depth byte) []keylist { // special case: no key if len(keys) == 0 { return []keylist{} } // special case: only one key left if len(keys) == 1 { return []keylist{keys} } // there are at least two keys left in the list at this depth groups := make([]keylist, 0, len(keys)) firstkey, lastkey := 0, 1 for ; lastkey < len(keys); lastkey++ { key := keys[lastkey] keyidx := offset2key(key, depth) previdx := offset2key(keys[lastkey-1], depth) if keyidx != previdx { groups = append(groups, keys[firstkey:lastkey]) firstkey = lastkey } } groups = append(groups, keys[firstkey:lastkey]) return groups } func (n *InternalNode) GetProofItems(keys keylist, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, error) { var ( groups = groupKeys(keys, n.depth) pe = &ProofElements{ Cis: []*Point{}, Zis: []byte{}, Yis: []*Fr{}, // Should be 0 Fis: [][]Fr{}, ByPath: map[string]*Point{}, } esses []byte = nil // list of extension statuses poass []Stem // list of proof-of-absence stems ) // fill in the polynomial for this node var fi [NodeWidth]Fr var fiPtrs [NodeWidth]*Fr var points [NodeWidth]*Point for i, child := range n.children { fiPtrs[i] = &fi[i] if child != nil { var c VerkleNode if _, ok := child.(HashedNode); ok { childpath := make([]byte, n.depth+1) 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) } serialized, err := resolver(childpath) if err != nil { return 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 } n.children[i] = c } else { c = child } points[i] = c.Commitment() } else { // TODO: add a test case to cover this scenario. points[i] = new(Point) } } if err := banderwagon.BatchMapToScalarField(fiPtrs[:], points[:]); err != nil { return nil, nil, nil, fmt.Errorf("batch mapping to scalar fields: %s", err) } for _, group := range groups { childIdx := offset2key(group[0], n.depth) // Build the list of elements for this level var yi Fr yi.Set(&fi[childIdx]) pe.Cis = append(pe.Cis, n.commitment) pe.Zis = append(pe.Zis, childIdx) pe.Yis = append(pe.Yis, &yi) pe.Fis = append(pe.Fis, fi[:]) pe.ByPath[string(group[0][:n.depth])] = n.commitment } // Loop over again, collecting the children's proof elements // This is because the order is breadth-first. for _, group := range groups { childIdx := offset2key(group[0], n.depth) if _, isunknown := n.children[childIdx].(UnknownNode); isunknown { // 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 { addedStems := map[string]struct{}{} for i := 0; i < len(group); i++ { stemStr := string(KeyToStem(group[i])) if _, ok := addedStems[stemStr]; !ok { // A question arises here: what if this proof of absence // corresponds to several stems? Should the ext status be // repeated as many times? It's wasteful, so consider if the // decoding code can be aware of this corner case. esses = append(esses, extStatusAbsentEmpty|((n.depth+1)<<3)) addedStems[stemStr] = struct{}{} } // Append one nil value per key in this missing stem pe.Vals = append(pe.Vals, nil) } continue } pec, es, other, err := n.children[childIdx].GetProofItems(group, resolver) if err != nil { // TODO: add a test case to cover this scenario. return nil, nil, nil, err } pe.Merge(pec) poass = append(poass, other...) esses = append(esses, es...) } return pe, esses, poass, nil } // Serialize returns the serialized form of the internal node. // The format is: func (n *InternalNode) Serialize() ([]byte, error) { ret := make([]byte, nodeTypeSize+bitlistSize+banderwagon.UncompressedSize) // Write the . bitlist := ret[internalBitlistOffset:internalCommitmentOffset] for i, c := range n.children { if _, ok := c.(Empty); !ok { setBit(bitlist, i) } } // Write the ret[nodeTypeOffset] = internalType // Write the comm := n.commitment.BytesUncompressedTrusted() copy(ret[internalCommitmentOffset:], comm[:]) return ret, nil } func (n *InternalNode) Copy() VerkleNode { ret := &InternalNode{ children: make([]VerkleNode, len(n.children)), commitment: new(Point), depth: n.depth, } for i, child := range n.children { ret.children[i] = child.Copy() } if n.commitment != nil { ret.commitment.Set(n.commitment) } if n.cow != nil { ret.cow = make(map[byte]*Point) for k, v := range n.cow { ret.cow[k] = new(Point) ret.cow[k].Set(v) } } return ret } func (n *InternalNode) toDot(parent, path string) string { me := fmt.Sprintf("internal%s", path) var hash Fr n.commitment.MapToScalarField(&hash) ret := fmt.Sprintf("%s [label=\"I: %x\"]\n", me, hash.BytesLE()) if len(parent) > 0 { ret = fmt.Sprintf("%s %s -> %s\n", ret, parent, me) } 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))) } return ret } func (n *InternalNode) setDepth(d byte) { n.depth = d } // MergeTrees takes a series of subtrees that got filled following // a command-and-conquer method, and merges them into a single tree. // This method is deprecated, use with caution. func MergeTrees(subroots []*InternalNode) VerkleNode { root := New().(*InternalNode) for _, subroot := range subroots { for i := 0; i < NodeWidth; i++ { if _, ok := subroot.children[i].(Empty); ok { continue } root.touchCoW(byte(i)) root.children[i] = subroot.children[i] } } return root } // TouchCoW is a helper function that will mark a child as // "inserted into". It is used by the conversion code to // mark reconstructed subtrees as 'written to', so that its // root commitment can be computed. func (n *InternalNode) touchCoW(index byte) { n.cowChild(index) } func (n *LeafNode) Insert(key []byte, value []byte, _ NodeResolverFn) error { if n.isPOAStub { return errIsPOAStub } if len(key) != StemSize+1 { return fmt.Errorf("invalid key size: %d", len(key)) } stem := KeyToStem(key) if !bytes.Equal(stem, n.stem) { return fmt.Errorf("stems don't match: %x != %x", stem, n.stem) } values := make([][]byte, NodeWidth) values[key[StemSize]] = value return n.insertMultiple(stem, values) } func (n *LeafNode) insertMultiple(stem Stem, values [][]byte) error { // Sanity check: ensure the stems are the same. if !equalPaths(stem, n.stem) { return errInsertIntoOtherStem } return n.updateMultipleLeaves(values) } func (n *LeafNode) updateC(cxIndex int, newC Fr, oldC Fr) { // Calculate the Fr-delta. var deltaC Fr deltaC.Sub(&newC, &oldC) // Calculate the Point-delta. var poly [NodeWidth]Fr poly[cxIndex] = deltaC // Add delta to the current commitment. n.commitment.Add(n.commitment, cfg.CommitToPoly(poly[:], 0)) } func (n *LeafNode) updateCn(index byte, value []byte, c *Point) error { var ( old, newH [2]Fr diff Point poly [NodeWidth]Fr ) // Optimization idea: // If the value is created (i.e. not overwritten), the leaf marker // is already present in the commitment. In order to save computations, // do not include it. The result should be the same, // but the computation time should be faster as one doesn't need to // compute 1 - 1 mod N. err := leafToComms(old[:], n.values[index]) if err != nil { return err } if err = leafToComms(newH[:], value); err != nil { return err } newH[0].Sub(&newH[0], &old[0]) poly[2*(index%128)] = newH[0] diff = cfg.conf.Commit(poly[:]) poly[2*(index%128)].SetZero() c.Add(c, &diff) newH[1].Sub(&newH[1], &old[1]) poly[2*(index%128)+1] = newH[1] diff = cfg.conf.Commit(poly[:]) c.Add(c, &diff) return nil } func (n *LeafNode) updateLeaf(index byte, value []byte) error { // Update the corresponding C1 or C2 commitment. var c *Point var oldC Point if index < NodeWidth/2 { c = n.c1 oldC = *n.c1 } else { c = n.c2 oldC = *n.c2 } if err := n.updateCn(index, value, c); err != nil { return err } // Batch the Fr transformation of the new and old CX. var frs [2]Fr if err := banderwagon.BatchMapToScalarField([]*Fr{&frs[0], &frs[1]}, []*Point{c, &oldC}); err != nil { return fmt.Errorf("batch mapping to scalar fields: %s", err) } // If index is in the first NodeWidth/2 elements, we need to update C1. Otherwise, C2. cxIndex := 2 + int(index)/(NodeWidth/2) // [1, stem, -> C1, C2 <-] n.updateC(cxIndex, frs[0], frs[1]) n.values[index] = value return nil } func (n *LeafNode) updateMultipleLeaves(values [][]byte) error { // skipcq: GO-R1005 var oldC1, oldC2 *Point // We iterate the values, and we update the C1 and/or C2 commitments depending on the index. // If any of them is touched, we save the original point so we can update the LeafNode root // commitment. We copy the original point in oldC1 and oldC2, so we can batch their Fr transformation // after this loop. for i, v := range values { if len(v) != 0 && !bytes.Equal(v, n.values[i]) { if i < NodeWidth/2 { // First time we touch C1? Save the original point for later. if oldC1 == nil { oldC1 = &Point{} oldC1.Set(n.c1) } // We update C1 directly in `n`. We have our original copy in oldC1. if err := n.updateCn(byte(i), v, n.c1); err != nil { return err } } else { // First time we touch C2? Save the original point for later. if oldC2 == nil { oldC2 = &Point{} oldC2.Set(n.c2) } // We update C2 directly in `n`. We have our original copy in oldC2. if err := n.updateCn(byte(i), v, n.c2); err != nil { return err } } n.values[i] = v } } // We have three potential cases here: // 1. We have touched C1 and C2: we Fr-batch old1, old2 and newC1, newC2. (4x gain ratio) // 2. We have touched only one CX: we Fr-batch oldX and newCX. (2x gain ratio) // 3. No C1 or C2 was touched, this is a noop. var frs [4]Fr const c1Idx = 2 // [1, stem, ->C1<-, C2] const c2Idx = 3 // [1, stem, C1, ->C2<-] if oldC1 != nil && oldC2 != nil { // Case 1. if err := banderwagon.BatchMapToScalarField([]*Fr{&frs[0], &frs[1], &frs[2], &frs[3]}, []*Point{n.c1, oldC1, n.c2, oldC2}); err != nil { return fmt.Errorf("batch mapping to scalar fields: %s", err) } n.updateC(c1Idx, frs[0], frs[1]) n.updateC(c2Idx, frs[2], frs[3]) } else if oldC1 != nil { // Case 2. (C1 touched) if err := banderwagon.BatchMapToScalarField([]*Fr{&frs[0], &frs[1]}, []*Point{n.c1, oldC1}); err != nil { return fmt.Errorf("batch mapping to scalar fields: %s", err) } n.updateC(c1Idx, frs[0], frs[1]) } else if oldC2 != nil { // Case 2. (C2 touched) if err := banderwagon.BatchMapToScalarField([]*Fr{&frs[0], &frs[1]}, []*Point{n.c2, oldC2}); err != nil { return fmt.Errorf("batch mapping to scalar fields: %s", err) } n.updateC(c2Idx, frs[0], frs[1]) } return nil } // Delete deletes a value from the leaf, return `true` as a second // return value, if the parent should entirely delete the child. func (n *LeafNode) Delete(k []byte, _ NodeResolverFn) (bool, error) { // Sanity check: ensure the key header is the same: if !equalPaths(k, n.stem) { return false, nil } // Erase the value it used to contain original := n.values[k[StemSize]] // save original value n.values[k[StemSize]] = nil // Check if a Cn subtree is entirely empty, or if // the entire subtree is empty. var ( isCnempty = true isCempty = true ) for i := 0; i < NodeWidth; i++ { if len(n.values[i]) > 0 { // if i and k[StemSize] are in the same subtree, // set both values and return. if byte(i/128) == k[StemSize]/128 { isCnempty = false isCempty = false break } // i and k[StemSize] were in a different subtree, // so all we can say at this stage, is that // the whole tree isn't empty. // TODO if i < 128, then k[StemSize] >= 128 and // we could skip to 128, but that's an // optimization for later. isCempty = false } } // if the whole subtree is empty, then the // entire node should be deleted. if isCempty { return true, nil } // if a Cn branch becomes empty as a result // of removing the last value, update C by // adding -Cn to it and exit. if isCnempty { var ( cn *Point subtreeindex = 2 + k[StemSize]/128 ) if k[StemSize] < 128 { cn = n.c1 } else { cn = n.c2 } // Update C by subtracting the old value for Cn // Note: this isn't done in one swoop, which would make sense // since presumably a lot of values would be deleted at the same // time when reorging. Nonetheless, a reorg is an already complex // operation which is slow no matter what, so ensuring correctness // is more important than var poly [4]Fr cn.MapToScalarField(&poly[subtreeindex]) n.commitment.Sub(n.commitment, cfg.CommitToPoly(poly[:], 0)) // Clear the corresponding commitment if k[StemSize] < 128 { n.c1 = nil } else { n.c2 = nil } return false, nil } // Recompute the updated C & Cn // // This is done by setting the leaf value // to `nil` at this point, and all the // diff computation will be performed by // updateLeaf since leafToComms supports // nil values. // Note that the value is set to nil by // the method, as it needs the original // value to compute the commitment diffs. n.values[k[StemSize]] = original return false, n.updateLeaf(k[StemSize], nil) } func (n *LeafNode) Get(k []byte, _ NodeResolverFn) ([]byte, error) { if n.isPOAStub { return nil, errIsPOAStub } if !equalPaths(k, n.stem) { // If keys differ, return nil in order to // signal that the key isn't present in the // tree. Do not return an error, thus matching // the behavior of Geth's SecureTrie. return nil, nil } // value can be nil, as expected by geth return n.values[k[StemSize]], nil } func (n *LeafNode) Hash() *Fr { // TODO cache this in a subsequent PR, not done here // to reduce complexity. // TODO use n.commitment once all Insert* are diff-inserts var hash Fr n.Commitment().MapToScalarField(&hash) return &hash } func (n *LeafNode) Commitment() *Point { if n.commitment == nil { panic("nil commitment") } return n.commitment } func (n *LeafNode) Commit() *Point { return n.commitment } // fillSuffixTreePoly takes one of the two suffix tree and // builds the associated polynomial, to be used to compute // the corresponding C{1,2} commitment. func fillSuffixTreePoly(poly []Fr, values [][]byte) (int, error) { count := 0 for idx, val := range values { if val == nil { continue } count++ if err := leafToComms(poly[(idx<<1)&0xFF:], val); err != nil { return 0, err } } return count, nil } // leafToComms turns a leaf into two commitments of the suffix // and extension tree. func leafToComms(poly []Fr, val []byte) error { if len(val) == 0 { return nil } if len(val) > LeafValueSize { return fmt.Errorf("invalid leaf length %d, %v", len(val), val) } var ( valLoWithMarker [17]byte loEnd = 16 err error ) if len(val) < loEnd { loEnd = len(val) } copy(valLoWithMarker[:loEnd], val[:loEnd]) valLoWithMarker[16] = 1 // 2**128 if err = FromLEBytes(&poly[0], valLoWithMarker[:]); err != nil { return err } if len(val) >= 16 { if err = FromLEBytes(&poly[1], val[16:]); err != nil { return err } } return nil } func (n *LeafNode) GetProofItems(keys keylist, _ NodeResolverFn) (*ProofElements, []byte, []Stem, error) { // skipcq: GO-R1005 var ( poly [NodeWidth]Fr // top-level polynomial pe = &ProofElements{ Cis: []*Point{n.commitment, n.commitment}, Zis: []byte{0, 1}, Yis: []*Fr{&poly[0], &poly[1]}, // Should be 0 Fis: [][]Fr{poly[:], poly[:]}, Vals: make([][]byte, 0, len(keys)), ByPath: map[string]*Point{}, } esses []byte = nil // list of extension statuses poass []Stem // list of proof-of-absence stems ) // Initialize the top-level polynomial with 1 + stem + C1 + C2 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) } // First pass: add top-level elements first var hasC1, hasC2 bool for _, key := range keys { // Note that keys might contain keys that don't correspond to this leaf node. // We should only analize the inclusion of C1/C2 for keys corresponding to this // leaf node stem. if equalPaths(n.stem, key) { hasC1 = hasC1 || (key[StemSize] < 128) hasC2 = hasC2 || (key[StemSize] >= 128) if hasC2 { break } } } // If this tree is a full tree (i.e: not a stateless tree), we know we have c1 and c2 values. // Also, we _need_ them independently of hasC1 or hasC2 since the prover needs `Fis`. if !n.isPOAStub { if err := banderwagon.BatchMapToScalarField([]*Fr{&poly[2], &poly[3]}, []*Point{n.c1, n.c2}); err != nil { return nil, nil, nil, fmt.Errorf("batch mapping to scalar fields: %s", err) } } else if hasC1 || 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") } if hasC1 { pe.Cis = append(pe.Cis, n.commitment) pe.Zis = append(pe.Zis, 2) pe.Yis = append(pe.Yis, &poly[2]) pe.Fis = append(pe.Fis, poly[:]) } if hasC2 { pe.Cis = append(pe.Cis, n.commitment) pe.Zis = append(pe.Zis, 3) pe.Yis = append(pe.Yis, &poly[3]) 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 // Proof of absence: case of a differing stem. if !equalPaths(n.stem, key) { // If this is the first extension status added for this path, // add the proof of absence stem (only once). If later we detect a proof of // presence, we'll clear the list since that proof of presence // will be enough to provide the stem. if len(esses) == 0 { poass = append(poass, n.stem) } // 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{}{} } pe.Vals = append(pe.Vals, nil) continue } // As mentioned above, if a proof-of-absence stem was found, and // it now turns out the same stem is used as a proof of presence, // clear the proof-of-absence list to avoid redundancy. Note that // we don't delete the extension statuses since that is needed to // figure out which is the correct stem for this path. if len(poass) > 0 { poass = nil } var ( suffix = key[StemSize] suffPoly [NodeWidth]Fr // suffix-level polynomial err error scomm *Point ) if suffix >= 128 { if _, err = fillSuffixTreePoly(suffPoly[:], n.values[128:]); err != nil { return 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) } scomm = n.c1 } var leaves [2]Fr if n.values[suffix] == nil { // Proof of absence: case of a missing value. // // Suffix tree is present as a child of the extension, // but does not contain the requested suffix. This can // only happen when the leaf has never been written to // since after deletion the value would be set to zero // but still contain the leaf marker 2^128. leaves[0], leaves[1] = FrZero, FrZero } else { // suffix tree is present and contains the key leaves[0], leaves[1] = suffPoly[2*suffix], suffPoly[2*suffix+1] } pe.Cis = append(pe.Cis, scomm, scomm) pe.Zis = append(pe.Zis, 2*suffix, 2*suffix+1) pe.Yis = append(pe.Yis, &leaves[0], &leaves[1]) 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{}{} } slotPath := string(key[:n.depth]) + string([]byte{2 + suffix/128}) pe.ByPath[slotPath] = scomm } return pe, esses, poass, nil } // Serialize serializes a LeafNode. // The format is: func (n *LeafNode) Serialize() ([]byte, error) { cBytes := banderwagon.BatchToBytesUncompressed(n.commitment, n.c1, n.c2) return n.serializeLeafWithUncompressedCommitments(cBytes[0], cBytes[1], cBytes[2]), nil } func (n *LeafNode) Copy() VerkleNode { l := &LeafNode{} l.stem = make([]byte, len(n.stem)) l.values = make([][]byte, len(n.values)) l.depth = n.depth copy(l.stem, n.stem) for i, v := range n.values { l.values[i] = make([]byte, len(v)) copy(l.values[i], v) } if n.commitment != nil { l.commitment = new(Point) l.commitment.Set(n.commitment) } if n.c1 != nil { l.c1 = new(Point) l.c1.Set(n.c1) } if n.c2 != nil { l.c2 = new(Point) l.c2.Set(n.c2) } l.isPOAStub = n.isPOAStub return l } func (n *LeafNode) Key(i int) []byte { var ret [KeySize]byte copy(ret[:], n.stem) ret[StemSize] = byte(i) return ret[:] } func (n *LeafNode) Value(i int) []byte { if i >= NodeWidth { panic("leaf node index out of range") } return n.values[byte(i)] } func (n *LeafNode) toDot(parent, path string) string { var hash Fr n.Commitment().MapToScalarField(&hash) ret := fmt.Sprintf("leaf%s [label=\"L: %x\nC: %x\nStem: %x\nC₁: %x\nC₂:%x\"]\n%s -> leaf%s\n", path, hash.Bytes(), n.commitment.Bytes(), n.stem, n.c1.Bytes(), n.c2.Bytes(), parent, path) for i, v := range n.values { if len(v) != 0 { ret = fmt.Sprintf("%sval%s%02x [label=\"%x\"]\nleaf%s -> val%s%02x\n", ret, path, i, v, path, path, i) } } return ret } func (n *LeafNode) setDepth(d byte) { n.depth = d } func (n *LeafNode) Values() [][]byte { return n.values } func setBit(bitlist []byte, index int) { bitlist[index/8] |= mask[index%8] } func ToDot(root VerkleNode) string { root.Commit() return fmt.Sprintf("digraph D {\n%s}", root.toDot("", "")) } // SerializedNode contains a serialization of a tree node. // It provides everything that the client needs to save the node to the database. // For example, CommitmentBytes is usually use as key and SerializedBytes as value. // Providing both allows this library to do more optimizations. type SerializedNode struct { Node VerkleNode CommitmentBytes [banderwagon.UncompressedSize]byte Path []byte SerializedBytes []byte } // BatchSerialize is an optimized serialization API when multiple VerkleNodes serializations are required, and all are // available in memory. func (n *InternalNode) BatchSerialize() ([]SerializedNode, error) { // Commit to the node to update all the nodes commitments. n.Commit() // Collect all nodes that we need to serialize. nodes := make([]VerkleNode, 0, 1024) paths := make([][]byte, 0, 1024) nodes, paths = n.collectNonHashedNodes(nodes, paths, nil) // We collect all the *Point, so we can batch all projective->affine transformations. pointsToCompress := make([]*Point, 0, 3*len(nodes)) // Contains a map between VerkleNode and the index in the serializedPoints containing the commitment below. serializedPointsIdxs := make(map[VerkleNode]int, len(nodes)) for i := range nodes { switch n := nodes[i].(type) { case *InternalNode: pointsToCompress = append(pointsToCompress, n.commitment) serializedPointsIdxs[n] = len(pointsToCompress) - 1 case *LeafNode: pointsToCompress = append(pointsToCompress, n.commitment, n.c1, n.c2) } } // Now we do the all transformations in a single-shot. serializedPoints := banderwagon.BatchToBytesUncompressed(pointsToCompress...) // Now we that we did the heavy CPU work, we have to do the rest of `nodes` serialization // taking the compressed points from this single list. ret := make([]SerializedNode, 0, len(nodes)) idx := 0 for i := range nodes { switch n := nodes[i].(type) { case *InternalNode: serialized, err := n.serializeInternalWithUncompressedCommitment(serializedPointsIdxs, serializedPoints) if err != nil { return nil, err } sn := SerializedNode{ Node: n, Path: paths[i], CommitmentBytes: serializedPoints[idx], SerializedBytes: serialized, } ret = append(ret, sn) idx++ case *LeafNode: cBytes := serializedPoints[idx] c1Bytes := serializedPoints[idx+1] c2Bytes := serializedPoints[idx+2] sn := SerializedNode{ Node: n, Path: paths[i], CommitmentBytes: serializedPoints[idx], SerializedBytes: n.serializeLeafWithUncompressedCommitments(cBytes, c1Bytes, c2Bytes), } ret = append(ret, sn) idx += 3 } } return ret, nil } func (n *InternalNode) collectNonHashedNodes(list []VerkleNode, paths [][]byte, path []byte) ([]VerkleNode, [][]byte) { list = append(list, n) paths = append(paths, path) for i, child := range n.children { switch childNode := child.(type) { case *LeafNode: list = append(list, childNode) paths = append(paths, childNode.stem[:len(path)+1]) case *InternalNode: childpath := make([]byte, len(path)+1) copy(childpath, path) childpath[len(path)] = byte(i) list, paths = childNode.collectNonHashedNodes(list, paths, childpath) } } return list, paths } // unpack one compressed commitment from the list of batch-compressed commitments func (n *InternalNode) serializeInternalWithUncompressedCommitment(pointsIdx map[VerkleNode]int, serializedPoints [][banderwagon.UncompressedSize]byte) ([]byte, error) { serialized := make([]byte, nodeTypeSize+bitlistSize+banderwagon.UncompressedSize) bitlist := serialized[internalBitlistOffset:internalCommitmentOffset] for i, c := range n.children { if _, ok := c.(Empty); !ok { setBit(bitlist, i) } } serialized[nodeTypeOffset] = internalType pointidx, ok := pointsIdx[n] if !ok { return nil, fmt.Errorf("child node not found in cache") } copy(serialized[internalCommitmentOffset:], serializedPoints[pointidx][:]) return serialized, nil } var ( zero32 [32]byte EmptyCodeHash, _ = hex.DecodeString("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") ) func (n *LeafNode) serializeLeafWithUncompressedCommitments(cBytes, c1Bytes, c2Bytes [banderwagon.UncompressedSize]byte) []byte { // Empty value in LeafNode used for padding. var emptyValue [LeafValueSize]byte // Create bitlist and store in children LeafValueSize (padded) values. children := make([]byte, 0, NodeWidth*LeafValueSize) var ( bitlist [bitlistSize]byte isEoA = true count, lastIdx int ) for i, v := range n.values { if v != nil { count++ lastIdx = i setBit(bitlist[:], i) children = append(children, v...) if padding := emptyValue[:LeafValueSize-len(v)]; len(padding) != 0 { children = append(children, padding...) } } if isEoA { switch i { case 0: // Version should be 0 isEoA = v != nil case 1: // Code hash should be the empty code hash isEoA = v != nil && bytes.Equal(v, EmptyCodeHash[:]) default: // All other values must be nil isEoA = v == nil } } } // Create the serialization. var result []byte switch { case count == 1: var buf [singleSlotLeafSize]byte result = buf[:] result[0] = singleSlotType copy(result[leafStemOffset:], n.stem[:StemSize]) if lastIdx < 128 { copy(result[leafStemOffset+StemSize:], c1Bytes[:]) } else { copy(result[leafStemOffset+StemSize:], c2Bytes[:]) } copy(result[leafStemOffset+StemSize+banderwagon.UncompressedSize:], cBytes[:]) result[leafStemOffset+StemSize+2*banderwagon.UncompressedSize] = byte(lastIdx) copy(result[leafStemOffset+StemSize+2*banderwagon.UncompressedSize+leafValueIndexSize:], n.values[lastIdx][:]) case isEoA: var buf [eoaLeafSize]byte result = buf[:] result[0] = eoAccountType copy(result[leafStemOffset:], n.stem[:StemSize]) copy(result[leafStemOffset+StemSize:], c1Bytes[:]) copy(result[leafStemOffset+StemSize+banderwagon.UncompressedSize:], cBytes[:]) copy(result[leafStemOffset+StemSize+2*banderwagon.UncompressedSize:], n.values[0]) // copy basic data default: result = make([]byte, nodeTypeSize+StemSize+bitlistSize+3*banderwagon.UncompressedSize+len(children)) result[0] = leafType copy(result[leafStemOffset:], n.stem[:StemSize]) copy(result[leafBitlistOffset:], bitlist[:]) copy(result[leafCommitmentOffset:], cBytes[:]) copy(result[leafC1CommitmentOffset:], c1Bytes[:]) copy(result[leafC2CommitmentOffset:], c2Bytes[:]) copy(result[leafChildrenOffset:], children) } return result }