Skip to content

Commit 15f8ff2

Browse files
Various Verkle Fixes (#3650)
* scaffolding * broken WIP * partial implementations * fix getAccount * add todo * make trie.get accept suffixes * clean up reference * Add reserved bytes to encodeBasicData function * FIx encoding again * spelling [no ci] * change param to account [no ci] * Add support for basic account delete * implement chunkify code * Add putCode * Move code to helpers * getCode and getCodeSize * Start work on tests * Update magic numbers to constants * Make get/putCode work * Fix various get/putCode bugs * add get/putstorage * export SFVKSM [no ci] * add commit/flush/revert * Tests for caching * make cspell happy * lint * add back missing method from interface * Update packages/util/test/verkle.spec.ts * Apply suggestions from code review * address some feedback * Update types and add test * FIx commitment format * Update verkle crypto and add proof test * add max chunks constant * delete account in put if no account * spelling * fix basic data encoding offsets * remove console log * Fix suffix logic * wasm update * update interface * update package lock * update verkle crypto wasm again * console logs * maybe another fix * various fixes * update tests * Remove obsolete proof helpers * add second test * add explanatory comment for leaf marker * Add safeguards to leafnode.create * use correct randomBytes --------- Co-authored-by: Gabriel Rocheleau <[email protected]>
1 parent 3a7e07f commit 15f8ff2

File tree

6 files changed

+158
-49
lines changed

6 files changed

+158
-49
lines changed

packages/util/src/verkle.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface VerkleCrypto {
3636
serializeCommitment: (commitment: Uint8Array) => Uint8Array
3737
createProof: (bytes: ProverInput[]) => Uint8Array
3838
verifyProof: (proof: Uint8Array, verifierInput: VerifierInput[]) => boolean
39+
commitToScalars: (vector: Uint8Array[]) => Uint8Array
3940
}
4041

4142
export interface ProverInput {
@@ -352,8 +353,8 @@ export function encodeVerkleLeafBasicData(account: Account): Uint8Array {
352353
*/
353354
export const generateChunkSuffixes = (numChunks: number) => {
354355
if (numChunks === 0) return []
355-
const chunkSuffixes = new Array<number>(numChunks)
356-
const firstChunksSet = Math.min(numChunks, VERKLE_CODE_OFFSET)
356+
const chunkSuffixes: number[] = new Array<number>(numChunks)
357+
const firstChunksSet = numChunks > VERKLE_CODE_OFFSET ? VERKLE_CODE_OFFSET : numChunks
357358
for (let x = 0; x < firstChunksSet; x++) {
358359
// Fill up to first 128 suffixes
359360
chunkSuffixes[x] = x + VERKLE_CODE_OFFSET

packages/verkle/src/node/leafNode.ts

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { equalsBytes, intToBytes, setLengthLeft, setLengthRight } from '@ethereumjs/util'
1+
import { equalsBytes, intToBytes, setLengthRight } from '@ethereumjs/util'
22

33
import { BaseVerkleNode } from './baseVerkleNode.js'
44
import { NODE_WIDTH, VerkleLeafNodeValue, VerkleNodeType } from './types.js'
@@ -41,7 +41,15 @@ export class LeafNode extends BaseVerkleNode<VerkleNodeType.Leaf> {
4141
values?: (Uint8Array | VerkleLeafNodeValue)[],
4242
): Promise<LeafNode> {
4343
// Generate the value arrays for c1 and c2
44-
values = values !== undefined ? values : createDefaultLeafValues()
44+
if (values !== undefined) {
45+
values = values.map((el) => {
46+
// Checks for instances of zeros and replaces with the "deleted" leaf node value
47+
if (el instanceof Uint8Array && equalsBytes(el, new Uint8Array(32)))
48+
return VerkleLeafNodeValue.Deleted
49+
return el
50+
})
51+
} else values = createDefaultLeafValues()
52+
4553
const c1Values = createCValues(values.slice(0, 128))
4654
const c2Values = createCValues(values.slice(128))
4755
let c1 = verkleCrypto.zeroCommitment
@@ -66,7 +74,7 @@ export class LeafNode extends BaseVerkleNode<VerkleNodeType.Leaf> {
6674
verkleCrypto.zeroCommitment,
6775
0,
6876
new Uint8Array(32),
69-
setLengthLeft(intToBytes(1), 32),
77+
setLengthRight(intToBytes(1), 32),
7078
)
7179
commitment = verkleCrypto.updateCommitment(
7280
commitment,
@@ -148,33 +156,18 @@ export class LeafNode extends BaseVerkleNode<VerkleNodeType.Leaf> {
148156
value === VerkleLeafNodeValue.Untouched
149157
? createUntouchedLeafValue()
150158
: createDeletedLeafValue()
159+
160+
// Set the new values in the values array
161+
this.values[index] = val
162+
151163
// First we update c1 or c2 (depending on whether the index is < 128 or not)
152164
// Generate the 16 byte values representing the 32 byte values in the half of the values array that
153165
// contain the old value for the leaf node
154166
const cValues =
155167
index < 128 ? createCValues(this.values.slice(0, 128)) : createCValues(this.values.slice(128))
156-
// The commitment index is 2 * the suffix (i.e. the position of the value in the values array)
157-
// here because each 32 byte value in the leaf node is represented as two 16 byte values in the
158-
// cValues array.
159-
const commitmentIndex = index < 128 ? index * 2 : (index - 128) * 2
160-
let cCommitment = index < 128 ? this.c1 : this.c2
161-
// Update the commitment for the first 16 bytes of the value
162-
cCommitment = this.verkleCrypto.updateCommitment(
163-
cCommitment!,
164-
commitmentIndex,
165-
cValues[commitmentIndex],
166-
// Right pad the value with zeroes since commitments require 32 byte scalars
167-
setLengthRight(val.slice(0, 16), 32),
168-
)
169-
// Update the commitment for the second 16 bytes of the value
170-
cCommitment = this.verkleCrypto.updateCommitment(
171-
cCommitment!,
172-
commitmentIndex + 1,
173-
cValues[commitmentIndex + 1],
174-
// Right pad the value with zeroes since commitments require 32 byte scalars
175-
setLengthRight(val.slice(16), 32),
176-
)
177-
// Update the cCommitment corresponding to the index
168+
// Create a commitment to the cValues returned and then use this to replace the c1/c2 commitment value
169+
const cCommitment = this.verkleCrypto.commitToScalars(cValues)
170+
178171
let oldCCommitment: Uint8Array | undefined
179172
if (index < 128) {
180173
oldCCommitment = this.c1
@@ -184,8 +177,6 @@ export class LeafNode extends BaseVerkleNode<VerkleNodeType.Leaf> {
184177
this.c2 = cCommitment
185178
}
186179

187-
// Set the new values in the values array
188-
this.values[index] = value
189180
// Update leaf node commitment -- c1 (2) if index is < 128 or c2 (3) otherwise
190181
const cIndex = index < 128 ? 2 : 3
191182
this.commitment = this.verkleCrypto.updateCommitment(

packages/verkle/src/node/util.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,15 @@ export function isInternalNode(node: VerkleNode): node is InternalNode {
4242
export const createUntouchedLeafValue = () => new Uint8Array(32)
4343

4444
/**
45-
* Generates a 32 byte array of zeroes and sets the 129th bit to 1, which the EIP
46-
* refers to as the leaf marker to indicate a leaf value that has been touched previously
45+
* Generates a 32 byte array of zeroes and sets the 129th bit to 1 (if `setLeafMarker` is set),
46+
* which the EIP refers to as the leaf marker to indicate a leaf value that has been touched previously
4747
* and contains only zeroes
48-
*
49-
* Note: this value should only used in the commitment update process
50-
*
51-
* @returns a 32 byte array of zeroes with the 129th bit set to 1
48+
* @returns a 32 byte array of zeroes (optionally with 129th bit set to 1)
5249
*/
53-
export const createDeletedLeafValue = () => {
50+
export const createDeletedLeafValue = (setLeafMarker = false) => {
5451
const bytes = new Uint8Array(32)
55-
// Set the 129th bit to 1 directly by setting the 17th byte (index 16) to 0x80
56-
bytes[16] = 0x80
52+
// Set the 129th bit to 1 directly by setting the 17th byte (index 16) to 1 (since these bytes are little endian)
53+
if (setLeafMarker) bytes[16] = 1
5754

5855
return bytes
5956
}
@@ -81,16 +78,22 @@ export const createCValues = (values: (Uint8Array | VerkleLeafNodeValue)[]) => {
8178
val = createUntouchedLeafValue()
8279
break
8380
case VerkleLeafNodeValue.Deleted: // Leaf value that has been written with zeros (either zeroes or a deleted value)
84-
val = createDeletedLeafValue()
81+
val = createDeletedLeafValue(true)
8582
break
8683
default:
8784
val = retrievedValue
8885
break
8986
}
90-
// We add 16 trailing zeros to each value since all commitments are padded to an array of 32 byte values
87+
// We add 16 trailing zeros to each value since all commitments are little endian and padded to 32 bytes
9188
expandedValues[x * 2] = setLengthRight(val.slice(0, 16), 32)
92-
// Apply leaf marker to all touched values (i.e. flip 129th bit)
93-
if (retrievedValue !== VerkleLeafNodeValue.Untouched) expandedValues[x * 2][16] = 0x80
89+
// Apply leaf marker to all touched values (i.e. flip 129th bit) of the lower value (the 16 lower bytes
90+
// of the original 32 byte value array)
91+
// This is counterintuitive since the 129th bit is little endian byte encoding so 10000000 in bits but
92+
// each byte in a Javascript Uint8Array is still "big endian" so the 16th byte (which contains the 129-137th bits)
93+
// should be 1 and not 256. In other words, the little endian value 10000000 is represented as an integer 1 in the byte
94+
// at index 16 of the Uint8Array since each byte is big endian at the system level so we have to invert that
95+
// value to get the correct representation
96+
if (retrievedValue !== VerkleLeafNodeValue.Untouched) expandedValues[x * 2][16] = 1
9497
expandedValues[x * 2 + 1] = setLengthRight(val.slice(16), 32)
9598
}
9699
return expandedValues
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { MapDB, bytesToHex } from '@ethereumjs/util'
2+
import { loadVerkleCrypto } from 'verkle-cryptography-wasm'
3+
import { assert, beforeAll, describe, it } from 'vitest'
4+
5+
import { createVerkleTree } from '../src/constructors.js'
6+
7+
describe('rust-verkle test vectors', () => {
8+
let verkleCrypto: Awaited<ReturnType<typeof loadVerkleCrypto>>
9+
beforeAll(async () => {
10+
verkleCrypto = await loadVerkleCrypto()
11+
})
12+
it('should produce the correct commitment', async () => {
13+
// Test from python implementation
14+
//https://github.com/crate-crypto/verkle-trie-ref/blob/483f40c737f27bc8f059870f862cf6c244159cd4/verkle/verkle_test.py#L63
15+
// It inserts a single value and then verifies that the hash of the root node matches (not the `trie.root` which is a serialized commitment and not the hash)
16+
const key = Uint8Array.from([
17+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
18+
27, 28, 29, 30, 31, 32,
19+
])
20+
const trie = await createVerkleTree({ verkleCrypto, db: new MapDB() })
21+
await trie.put(key.slice(0, 31), [key[31]], [key])
22+
23+
const path = await trie.findPath(key.slice(0, 31))
24+
25+
assert.equal(
26+
bytesToHex(path.stack[0][0].hash()),
27+
'0x029b6c4c8af9001f0ac76472766c6579f41eec84a73898da06eb97ebdab80a09',
28+
)
29+
assert.equal(
30+
bytesToHex(trie.root()),
31+
'0x6f5e7cfc3a158a64e5718b0d2f18f564171342380f5808f3d2a82f7e7f3c2778',
32+
)
33+
})
34+
it('should produce correct commitments after value updates', async () => {
35+
// Variant of previous test that puts 0s at a specific key and then updates that value
36+
// https://github.com/crate-crypto/verkle-trie-ref/blob/483f40c737f27bc8f059870f862cf6c244159cd4/verkle/verkle_test.py#L96
37+
const key = Uint8Array.from([
38+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
39+
27, 28, 29, 30, 31, 32,
40+
])
41+
const stem = key.slice(0, 31)
42+
const trie = await createVerkleTree({ verkleCrypto, db: new MapDB() })
43+
await trie.put(stem, [key[31]], [new Uint8Array(32)])
44+
let path = await trie.findPath(stem)
45+
assert.equal(
46+
bytesToHex(path.stack[0][0].hash()),
47+
'0x77a0747bd526d9d9af60bd5665d24d6cb421f5c8e726b1de62f914f3ff9a361c',
48+
)
49+
await trie.put(stem, [key[31]], [key])
50+
path = await trie.findPath(key.slice(0, 31))
51+
52+
assert.equal(
53+
bytesToHex(path.stack[0][0].hash()),
54+
'0x029b6c4c8af9001f0ac76472766c6579f41eec84a73898da06eb97ebdab80a09',
55+
)
56+
assert.equal(
57+
bytesToHex(trie.root()),
58+
'0x6f5e7cfc3a158a64e5718b0d2f18f564171342380f5808f3d2a82f7e7f3c2778',
59+
)
60+
})
61+
})

packages/verkle/test/leafNode.spec.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type VerkleCrypto, equalsBytes, randomBytes, setLengthLeft } from '@ethereumjs/util'
1+
import { type VerkleCrypto, equalsBytes, randomBytes, setLengthRight } from '@ethereumjs/util'
22
import { loadVerkleCrypto } from 'verkle-cryptography-wasm'
33
import { assert, beforeAll, describe, it } from 'vitest'
44

@@ -61,8 +61,8 @@ describe('verkle node - leaf', () => {
6161
const node = await LeafNode.create(key.slice(0, 31), verkleCrypto)
6262
assert.ok(node instanceof LeafNode)
6363
assert.equal(node.getValue(0), undefined)
64-
node.setValue(0, setLengthLeft(Uint8Array.from([5]), 32))
65-
assert.deepEqual(node.getValue(0), setLengthLeft(Uint8Array.from([5]), 32))
64+
node.setValue(0, setLengthRight(Uint8Array.from([5]), 32))
65+
assert.deepEqual(node.getValue(0), setLengthRight(Uint8Array.from([5]), 32))
6666
node.setValue(0, VerkleLeafNodeValue.Deleted)
6767
assert.deepEqual(node.getValue(0), new Uint8Array(32))
6868
})
@@ -72,7 +72,7 @@ describe('verkle node - leaf', () => {
7272
const node = await LeafNode.create(key.slice(0, 31), verkleCrypto)
7373
node.setValue(0, VerkleLeafNodeValue.Deleted)
7474
const c1Values = createCValues(node.values.slice(0, 128))
75-
assert.equal(c1Values[0][16], 0x80)
75+
assert.equal(c1Values[0][16], 1)
7676
})
7777

7878
it('should update a commitment when setting a value', async () => {
@@ -91,9 +91,11 @@ describe('verkle node - leaf', () => {
9191
const node = await LeafNode.create(stem, verkleCrypto, values)
9292
const serialized = node.serialize()
9393
const decodedNode = decodeNode(serialized, verkleCrypto)
94+
9495
assert.deepEqual(node, decodedNode)
9596

9697
const defaultNode = await LeafNode.create(randomBytes(31), verkleCrypto)
98+
9799
assert.deepEqual(defaultNode, decodeNode(defaultNode.serialize(), verkleCrypto))
98100
})
99101
})

packages/verkle/test/proof.spec.ts

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { MapDB, hexToBytes } from '@ethereumjs/util'
1+
import { MapDB, bigIntToBytes, hexToBytes, randomBytes, setLengthRight } from '@ethereumjs/util'
22
import { loadVerkleCrypto } from 'verkle-cryptography-wasm'
33
import { assert, beforeAll, describe, it } from 'vitest'
44

55
import { createVerkleTree } from '../src/constructors.js'
6+
import { LeafNode } from '../src/index.js'
67

7-
import type { LeafNode } from '../src/index.js'
8-
import type { PrefixedHexString, ProverInput, VerifierInput, VerkleCrypto } from '@ethereumjs/util'
8+
import type { PrefixedHexString, VerkleCrypto } from '@ethereumjs/util'
9+
import type { ProverInput, VerifierInput } from 'verkle-cryptography-wasm'
910

1011
describe('lets make proofs', () => {
1112
let verkleCrypto: VerkleCrypto
@@ -69,4 +70,54 @@ describe('lets make proofs', () => {
6970
assert.fail(`Failed to verify proof: ${err}`)
7071
}
7172
})
73+
it('should pass for empty trie', async () => {
74+
const trie = await createVerkleTree({ verkleCrypto, db: new MapDB() })
75+
76+
await trie['_createRootNode']()
77+
const proof = verkleCrypto.createProof([
78+
{
79+
// Get commitment from root node
80+
serializedCommitment: verkleCrypto.serializeCommitment(
81+
(await trie.findPath(new Uint8Array(31))).stack![0][0].commitment,
82+
),
83+
vector: new Array(256).fill(new Uint8Array(32).fill(0)),
84+
indices: [0],
85+
},
86+
])
87+
const res = verkleCrypto.verifyProof(proof, [
88+
{
89+
serializedCommitment: verkleCrypto.serializeCommitment(
90+
(await trie.findPath(new Uint8Array(31))).stack![0][0].commitment,
91+
),
92+
indexValuePairs: [{ index: 0, value: new Uint8Array(32) }],
93+
},
94+
])
95+
assert.ok(res)
96+
})
97+
it.skip('should verify proof for single leaf node', async () => {
98+
const node = await LeafNode.create(randomBytes(31), verkleCrypto)
99+
node.setValue(0, setLengthRight(bigIntToBytes(1n), 32))
100+
const valuesArray = new Array<Uint8Array>(256)
101+
for (let x = 0; x < 256; x++) {
102+
let value = node.getValue(x)
103+
if (value === undefined) value = new Uint8Array(32)
104+
valuesArray[x] = value
105+
}
106+
107+
const proof = verkleCrypto.createProof([
108+
{
109+
serializedCommitment: verkleCrypto.serializeCommitment(node.commitment),
110+
vector: valuesArray,
111+
indices: [0],
112+
},
113+
])
114+
115+
const res = verkleCrypto.verifyProof(proof, [
116+
{
117+
serializedCommitment: verkleCrypto.serializeCommitment(node.commitment),
118+
indexValuePairs: [{ index: 0, value: node.getValue(0)! }],
119+
},
120+
])
121+
assert.ok(res)
122+
})
72123
})

0 commit comments

Comments
 (0)