|
1 |
| -import { equalsBytes } from '@ethereumjs/util' |
2 |
| - |
3 |
| -import { POINT_IDENTITY } from '../util/crypto.js' |
| 1 | +import { type VerkleCrypto } from '@ethereumjs/util' |
4 | 2 |
|
5 | 3 | import { BaseVerkleNode } from './baseVerkleNode.js'
|
6 |
| -import { LeafNode } from './leafNode.js' |
7 | 4 | import { NODE_WIDTH, VerkleNodeType } from './types.js'
|
8 | 5 |
|
9 |
| -import type { VerkleNode, VerkleNodeOptions } from './types.js' |
| 6 | +import type { ChildNode, VerkleNodeOptions } from './types.js' |
10 | 7 |
|
11 | 8 | export class InternalNode extends BaseVerkleNode<VerkleNodeType.Internal> {
|
12 |
| - // Array of references to children nodes |
13 |
| - public children: Array<VerkleNode | null> |
14 |
| - public copyOnWrite: Record<string, Uint8Array> |
| 9 | + // Array of tuples of uncompressed commitments (i.e. 64 byte Uint8Arrays) to child nodes along with the path to that child (i.e. the partial stem) |
| 10 | + public children: Array<ChildNode> |
15 | 11 | public type = VerkleNodeType.Internal
|
16 | 12 |
|
17 |
| - /* TODO: options.children is not actually used here */ |
18 | 13 | constructor(options: VerkleNodeOptions[VerkleNodeType.Internal]) {
|
19 | 14 | super(options)
|
20 |
| - this.children = options.children ?? new Array(NODE_WIDTH).fill(null) |
21 |
| - this.copyOnWrite = options.copyOnWrite ?? {} |
22 |
| - } |
23 |
| - |
24 |
| - commit(): Uint8Array { |
25 |
| - throw new Error('Not implemented') |
26 |
| - } |
27 |
| - |
28 |
| - cowChild(_: number): void { |
29 |
| - // Not implemented yet |
| 15 | + this.children = |
| 16 | + options.children ?? |
| 17 | + new Array(256).fill({ |
| 18 | + commitment: options.verkleCrypto.zeroCommitment, |
| 19 | + path: new Uint8Array(), |
| 20 | + }) |
30 | 21 | }
|
31 | 22 |
|
32 |
| - setChild(index: number, child: VerkleNode) { |
33 |
| - this.children[index] = child |
| 23 | + // Updates the commitment value for a child node at the corresponding index |
| 24 | + setChild(childIndex: number, child: ChildNode) { |
| 25 | + // Get previous child commitment at `index` |
| 26 | + const oldChildReference = this.children[childIndex] |
| 27 | + // Updates the commitment to the child node at `index` |
| 28 | + this.children[childIndex] = { ...child } |
| 29 | + // Updates the overall node commitment based on the update to this child |
| 30 | + this.commitment = this.verkleCrypto.updateCommitment( |
| 31 | + this.commitment, |
| 32 | + childIndex, |
| 33 | + // The hashed child commitments are used when updating the internal node commitment |
| 34 | + this.verkleCrypto.hashCommitment(oldChildReference.commitment), |
| 35 | + this.verkleCrypto.hashCommitment(child.commitment) |
| 36 | + ) |
34 | 37 | }
|
35 | 38 |
|
36 |
| - static fromRawNode(rawNode: Uint8Array[], depth: number): InternalNode { |
| 39 | + static fromRawNode(rawNode: Uint8Array[], verkleCrypto: VerkleCrypto): InternalNode { |
37 | 40 | const nodeType = rawNode[0][0]
|
38 | 41 | if (nodeType !== VerkleNodeType.Internal) {
|
39 | 42 | throw new Error('Invalid node type')
|
40 | 43 | }
|
41 | 44 |
|
42 |
| - // The length of the rawNode should be the # of children, + 2 for the node type and the commitment |
43 |
| - if (rawNode.length !== NODE_WIDTH + 2) { |
| 45 | + // The length of the rawNode should be the # of children * 2 (for commitments and paths) + 2 for the node type and the commitment |
| 46 | + if (rawNode.length !== NODE_WIDTH * 2 + 2) { |
44 | 47 | throw new Error('Invalid node length')
|
45 | 48 | }
|
46 | 49 |
|
47 |
| - // TODO: Generate Point from rawNode value |
48 | 50 | const commitment = rawNode[rawNode.length - 1]
|
| 51 | + const childrenCommitments = rawNode.slice(1, NODE_WIDTH + 1) |
| 52 | + const childrenPaths = rawNode.slice(NODE_WIDTH + 1, NODE_WIDTH * 2 + 1) |
49 | 53 |
|
50 |
| - return new InternalNode({ commitment, depth }) |
| 54 | + const children = childrenCommitments.map((commitment, idx) => { |
| 55 | + return { commitment, path: childrenPaths[idx] } |
| 56 | + }) |
| 57 | + return new InternalNode({ commitment, verkleCrypto, children }) |
51 | 58 | }
|
52 | 59 |
|
53 |
| - static create(depth: number): InternalNode { |
| 60 | + /** |
| 61 | + * Generates a new Internal node with default commitment |
| 62 | + */ |
| 63 | + static create(verkleCrypto: VerkleCrypto): InternalNode { |
54 | 64 | const node = new InternalNode({
|
55 |
| - commitment: POINT_IDENTITY, |
56 |
| - depth, |
| 65 | + commitment: verkleCrypto.zeroCommitment, |
| 66 | + verkleCrypto, |
57 | 67 | })
|
58 | 68 |
|
59 | 69 | return node
|
60 | 70 | }
|
61 | 71 |
|
62 |
| - getChildren(index: number): VerkleNode | null { |
63 |
| - return this.children?.[index] ?? null |
64 |
| - } |
65 |
| - |
66 |
| - insert(key: Uint8Array, value: Uint8Array, resolver: () => void): void { |
67 |
| - const values = new Array<Uint8Array>(NODE_WIDTH) |
68 |
| - values[key[31]] = value |
69 |
| - this.insertStem(key.slice(0, 31), values, resolver) |
70 |
| - } |
71 |
| - |
72 |
| - insertStem(stem: Uint8Array, values: Uint8Array[], resolver: () => void): void { |
73 |
| - // Index of the child pointed by the next byte in the key |
74 |
| - const childIndex = stem[this.depth] |
75 |
| - |
76 |
| - const child = this.children[childIndex] |
77 |
| - |
78 |
| - if (child instanceof LeafNode) { |
79 |
| - this.cowChild(childIndex) |
80 |
| - if (equalsBytes(child.stem, stem)) { |
81 |
| - return child.insertMultiple(stem, values) |
82 |
| - } |
83 |
| - |
84 |
| - // A new branch node has to be inserted. Depending |
85 |
| - // on the next byte in both keys, a recursion into |
86 |
| - // the moved leaf node can occur. |
87 |
| - const nextByteInExistingKey = child.stem[this.depth + 1] |
88 |
| - const newBranch = InternalNode.create(this.depth + 1) |
89 |
| - newBranch.cowChild(nextByteInExistingKey) |
90 |
| - this.children[childIndex] = newBranch |
91 |
| - newBranch.children[nextByteInExistingKey] = child |
92 |
| - child.depth += 1 |
93 |
| - |
94 |
| - const nextByteInInsertedKey = stem[this.depth + 1] |
95 |
| - if (nextByteInInsertedKey === nextByteInExistingKey) { |
96 |
| - return newBranch.insertStem(stem, values, resolver) |
97 |
| - } |
98 |
| - |
99 |
| - // Next word differs, so this was the last level. |
100 |
| - // Insert it directly into its final slot. |
101 |
| - const leafNode = LeafNode.create(stem, values) |
102 |
| - |
103 |
| - leafNode.setDepth(this.depth + 2) |
104 |
| - newBranch.cowChild(nextByteInInsertedKey) |
105 |
| - newBranch.children[nextByteInInsertedKey] = leafNode |
106 |
| - } else if (child instanceof InternalNode) { |
107 |
| - this.cowChild(childIndex) |
108 |
| - return child.insertStem(stem, values, resolver) |
109 |
| - } else { |
110 |
| - throw new Error('Invalid node type') |
111 |
| - } |
| 72 | + /** |
| 73 | + * |
| 74 | + * @param index The index in the children array to retrieve the child node commitment from |
| 75 | + * @returns the uncompressed 64byte commitment for the child node at the `index` position in the children array |
| 76 | + */ |
| 77 | + getChildren(index: number): ChildNode | null { |
| 78 | + return this.children[index] |
112 | 79 | }
|
113 | 80 |
|
114 |
| - // TODO: go-verkle also adds the bitlist to the raw format. |
115 | 81 | raw(): Uint8Array[] {
|
116 |
| - throw new Error('not implemented yet') |
117 |
| - // return [new Uint8Array([VerkleNodeType.Internal]), ...this.children, this.commitment] |
| 82 | + return [ |
| 83 | + new Uint8Array([VerkleNodeType.Internal]), |
| 84 | + ...this.children.map((child) => child.commitment), |
| 85 | + ...this.children.map((child) => child.path), |
| 86 | + this.commitment, |
| 87 | + ] |
118 | 88 | }
|
119 | 89 | }
|
0 commit comments