From a7421da609d3e4349e81080d182450b04e7bbcc0 Mon Sep 17 00:00:00 2001 From: Amir Date: Mon, 29 Jul 2024 12:26:46 -0700 Subject: [PATCH 1/9] Remove class functio that wraps verifyRangeProof --- packages/trie/src/trie.ts | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/packages/trie/src/trie.ts b/packages/trie/src/trie.ts index 53f8cea120..b5803b7d52 100644 --- a/packages/trie/src/trie.ts +++ b/packages/trie/src/trie.ts @@ -134,38 +134,6 @@ export class Trie { || ----------------`) } - /** - * A range proof is a proof that includes the encoded trie nodes from the root node to leaf node for one or more branches of a trie, - * allowing an entire range of leaf nodes to be validated. This is useful in applications such as snap sync where contiguous ranges - * of state trie data is received and validated for constructing world state, locally. Also see {@link verifyRangeProof}. A static - * version of this function also exists. - * @param rootHash - root hash of state trie this proof is being verified against. - * @param firstKey - first key of range being proven. - * @param lastKey - last key of range being proven. - * @param keys - key list of leaf data being proven. - * @param values - value list of leaf data being proven, one-to-one correspondence with keys. - * @param proof - proof node list, if all-elements-proof where no proof is needed, proof should be null, and both `firstKey` and `lastKey` must be null as well - * @returns a flag to indicate whether there exists more trie node in the trie - */ - verifyRangeProof( - rootHash: Uint8Array, - firstKey: Uint8Array | null, - lastKey: Uint8Array | null, - keys: Uint8Array[], - values: Uint8Array[], - proof: Uint8Array[] | null, - ): Promise { - return verifyRangeProof( - rootHash, - firstKey && bytesToNibbles(this.appliedKey(firstKey)), - lastKey && bytesToNibbles(this.appliedKey(lastKey)), - keys.map((k) => this.appliedKey(k)).map(bytesToNibbles), - values, - proof, - this._opts.useKeyHashingFunction, - ) - } - /** * Creates a proof from a trie and key that can be verified using {@link verifyTrieProof}. An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof contains * the encoded trie nodes from the root node to the leaf node storing state data. The returned proof will be in the format of an array that contains Uint8Arrays of From 85703587a2f61914ea1d734fd1758e850bbbb69e Mon Sep 17 00:00:00 2001 From: Amir Date: Mon, 29 Jul 2024 13:59:00 -0700 Subject: [PATCH 2/9] Move proof functions to be standalone functions --- packages/statemanager/src/stateManager.ts | 20 ++-- packages/trie/src/constructors.ts | 4 +- packages/trie/src/proof/index.ts | 123 +++++++++++++++++++++- packages/trie/src/trie.ts | 123 +--------------------- packages/trie/test/proof.spec.ts | 52 +++++---- packages/trie/test/proof/range.spec.ts | 10 +- packages/trie/test/trie/secure.spec.ts | 4 +- packages/trie/test/util/asyncWalk.spec.ts | 10 +- packages/trie/test/util/log.spec.ts | 7 +- 9 files changed, 187 insertions(+), 166 deletions(-) diff --git a/packages/statemanager/src/stateManager.ts b/packages/statemanager/src/stateManager.ts index 79c75a34f7..3f82d46d90 100644 --- a/packages/statemanager/src/stateManager.ts +++ b/packages/statemanager/src/stateManager.ts @@ -1,6 +1,12 @@ import { Chain, Common } from '@ethereumjs/common' import { RLP } from '@ethereumjs/rlp' -import { Trie, createTrieFromProof, verifyTrieProof } from '@ethereumjs/trie' +import { + Trie, + createProof, + createTrieFromProof, + updateFromProof, + verifyTrieProof, +} from '@ethereumjs/trie' import { Account, KECCAK256_NULL, @@ -629,19 +635,19 @@ export class DefaultStateManager implements StateManagerInterface { codeHash: KECCAK256_NULL_S, nonce: '0x0', storageHash: KECCAK256_RLP_S, - accountProof: (await this._trie.createProof(address.bytes)).map((p) => bytesToHex(p)), + accountProof: (await createProof(this._trie, address.bytes)).map((p) => bytesToHex(p)), storageProof: [], } return returnValue } - const accountProof: PrefixedHexString[] = (await this._trie.createProof(address.bytes)).map( + const accountProof: PrefixedHexString[] = (await createProof(this._trie, address.bytes)).map( (p) => bytesToHex(p), ) const storageProof: StorageProof[] = [] const storageTrie = this._getStorageTrie(address, account) for (const storageKey of storageSlots) { - const proof = (await storageTrie.createProof(storageKey)).map((p) => bytesToHex(p)) + const proof = (await createProof(storageTrie, storageKey)).map((p) => bytesToHex(p)) const value = bytesToHex(await this.getStorage(address, storageKey)) const proofItem: StorageProof = { key: bytesToHex(storageKey), @@ -717,7 +723,8 @@ export class DefaultStateManager implements StateManagerInterface { const trie = this._getStorageTrie(address) trie.root(hexToBytes(storageHash)) for (let i = 0; i < storageProof.length; i++) { - await trie.updateFromProof( + await updateFromProof( + trie, storageProof[i].proof.map((e) => hexToBytes(e)), safe, ) @@ -733,7 +740,8 @@ export class DefaultStateManager implements StateManagerInterface { async addProofData(proof: Proof | Proof[], safe: boolean = false) { if (Array.isArray(proof)) { for (let i = 0; i < proof.length; i++) { - await this._trie.updateFromProof( + await updateFromProof( + this._trie, proof[i].accountProof.map((e) => hexToBytes(e)), safe, ) diff --git a/packages/trie/src/constructors.ts b/packages/trie/src/constructors.ts index 42676729ad..0d5844bbd1 100644 --- a/packages/trie/src/constructors.ts +++ b/packages/trie/src/constructors.ts @@ -7,7 +7,7 @@ import { import { keccak256 } from 'ethereum-cryptography/keccak' import { concatBytes } from 'ethereum-cryptography/utils' -import { ROOT_DB_KEY, Trie } from './index.js' +import { ROOT_DB_KEY, Trie, updateFromProof } from './index.js' import type { Proof, TrieOpts } from './index.js' @@ -63,7 +63,7 @@ export async function createTrie(opts?: TrieOpts) { export async function createTrieFromProof(proof: Proof, trieOpts?: TrieOpts) { const shouldVerifyRoot = trieOpts?.root !== undefined const trie = new Trie(trieOpts) - const root = await trie.updateFromProof(proof, shouldVerifyRoot) + const root = await updateFromProof(trie, proof, shouldVerifyRoot) trie.root(root) await trie.persistRoot() return trie diff --git a/packages/trie/src/proof/index.ts b/packages/trie/src/proof/index.ts index 022acdaf27..203f41eb15 100644 --- a/packages/trie/src/proof/index.ts +++ b/packages/trie/src/proof/index.ts @@ -4,7 +4,8 @@ import { createTrieFromProof } from '../constructors.js' import { verifyRangeProof } from '../index.js' import { bytesToNibbles } from '../util/nibbles.js' -import type { Proof, TrieOpts } from '../index.js' +import { Proof, Trie, TrieOpts } from '../index.js' +import { PutBatch, bytesToHex, concatBytes, equalsBytes } from '@ethereumjs/util' /** * Static version of verifyProof function with the same behavior. An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof contains the encoded trie nodes @@ -64,4 +65,124 @@ export function verifyTrieRangeProof( ) } +/** + * Creates a proof from a trie and key that can be verified using {@link verifyTrieProof}. An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof contains + * the encoded trie nodes from the root node to the leaf node storing state data. The returned proof will be in the format of an array that contains Uint8Arrays of + * serialized branch, extension, and/or leaf nodes. + * @param key key to create a proof for + */ +export async function createProof(trie: Trie, key: Uint8Array): Promise { + trie['DEBUG'] && trie['debug'](`Creating Proof for Key: ${bytesToHex(key)}`, ['CREATE_PROOF']) + const { stack } = await trie.findPath(trie['appliedKey'](key)) + const p = stack.map((stackElem) => { + return stackElem.serialize() + }) + trie['DEBUG'] && trie['debug'](`Proof created with (${stack.length}) nodes`, ['CREATE_PROOF']) + return p +} + +/** + * Updates a trie from a proof by putting all the nodes in the proof into the trie. If a trie is being updated with multiple proofs, {@param shouldVerifyRoot} can + * be passed as false in order to not immediately throw on an unexpected root, so that root verification can happen after all proofs and their nodes have been added. + * An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof contains the encoded trie nodes from the root node to the leaf node storing state data. + * @param proof An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof to update the trie from. + * @param shouldVerifyRoot If `true`, verifies that the root key of the proof matches the trie root. Throws if this is not the case. + * @returns The root of the proof + */ +export async function updateFromProof(trie: Trie, proof: Proof, shouldVerifyRoot: boolean = false) { + trie['DEBUG'] && trie['debug'](`Saving (${proof.length}) proof nodes in DB`, ['FROM_PROOF']) + const opStack = proof.map((nodeValue) => { + let key = Uint8Array.from(trie['hash'](nodeValue)) + key = trie['_opts'].keyPrefix ? concatBytes(trie['_opts'].keyPrefix, key) : key + return { + type: 'put', + key, + value: nodeValue, + } as PutBatch + }) + + if (shouldVerifyRoot) { + if (opStack[0] !== undefined && opStack[0] !== null) { + if (!equalsBytes(trie.root(), opStack[0].key)) { + throw new Error('The provided proof does not have the expected trie root') + } + } + } + + await trie['_db'].batch(opStack) + if (opStack[0] !== undefined) { + return opStack[0].key + } +} + +/** + * Verifies a proof by putting all of its nodes into a trie and attempting to get the proven key. An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof + * contains the encoded trie nodes from the root node to the leaf node storing state data. A static version of this function exists with the same name. + * @param rootHash Root hash of the trie that this proof was created from and is being verified for + * @param key Key that is being verified and that the proof is created for + * @param proof an EIP-1186 proof to verify the key against + * @throws If proof is found to be invalid. + * @returns The value from the key, or null if valid proof of non-existence. + */ +export async function verifyProof( + trie: Trie, + rootHash: Uint8Array, + key: Uint8Array, + proof: Proof, +): Promise { + trie['DEBUG'] && + trie['debug']( + `Verifying Proof:\n|| Key: ${bytesToHex(key)}\n|| Root: ${bytesToHex( + rootHash, + )}\n|| Proof: (${proof.length}) nodes + `, + ['VERIFY_PROOF'], + ) + const proofTrie = new Trie({ + root: rootHash, + useKeyHashingFunction: trie['_opts'].useKeyHashingFunction, + common: trie['_opts'].common, + }) + try { + await updateFromProof(proofTrie, proof, true) + } catch (e: any) { + throw new Error('Invalid proof nodes given') + } + try { + trie['DEBUG'] && + trie['debug'](`Verifying proof by retrieving key: ${bytesToHex(key)} from proof trie`, [ + 'VERIFY_PROOF', + ]) + const value = await proofTrie.get(trie['appliedKey'](key), true) + trie['DEBUG'] && trie['debug'](`PROOF VERIFIED`, ['VERIFY_PROOF']) + return value + } catch (err: any) { + if (err.message === 'Missing node in DB') { + throw new Error('Invalid proof provided') + } else { + throw err + } + } +} + +/** + * Create a trie from a given (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof. An EIP-1186 proof contains the encoded trie nodes from the root + * node to the leaf node storing state data. This function does not check if the proof has the same expected root. A static version of this function exists + * with the same name. + * @param proof an EIP-1186 proof to update the trie from + * @deprecated Use `updateFromProof` + */ +export async function fromProof(trie: Trie, proof: Proof): Promise { + await updateFromProof(trie, proof, false) + + if (equalsBytes(trie.root(), trie.EMPTY_TRIE_ROOT) && proof[0] !== undefined) { + let rootKey = Uint8Array.from(trie['hash'](proof[0])) + // TODO: what if we have keyPrefix and we set root? This should not work, right? (all trie nodes are non-reachable) + rootKey = trie['_opts'].keyPrefix ? concatBytes(trie['_opts'].keyPrefix, rootKey) : rootKey + trie.root(rootKey) + await trie.persistRoot() + } + return +} + export * from './range.js' diff --git a/packages/trie/src/trie.ts b/packages/trie/src/trie.ts index b5803b7d52..6f32b8ec62 100644 --- a/packages/trie/src/trie.ts +++ b/packages/trie/src/trie.ts @@ -28,7 +28,6 @@ import { decodeRawNode, isRawNode, } from './node/index.js' -import { verifyRangeProof } from './proof/range.js' import { ROOT_DB_KEY } from './types.js' import { _walkTrie } from './util/asyncWalk.js' import { bytesToNibbles, matchingNibbleLength, nibblesTypeToPackedBytes } from './util/nibbles.js' @@ -39,14 +38,13 @@ import type { FoundNodeFunction, Nibbles, Path, - Proof, TrieNode, TrieOpts, TrieOptsWithDefaults, TrieShallowCopyOpts, } from './types.js' import type { OnFound } from './util/asyncWalk.js' -import type { BatchDBOp, DB, PutBatch } from '@ethereumjs/util' +import type { BatchDBOp, DB } from '@ethereumjs/util' import type { Debugger } from 'debug' /** @@ -134,125 +132,6 @@ export class Trie { || ----------------`) } - /** - * Creates a proof from a trie and key that can be verified using {@link verifyTrieProof}. An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof contains - * the encoded trie nodes from the root node to the leaf node storing state data. The returned proof will be in the format of an array that contains Uint8Arrays of - * serialized branch, extension, and/or leaf nodes. - * @param key key to create a proof for - */ - async createProof(key: Uint8Array): Promise { - this.DEBUG && this.debug(`Creating Proof for Key: ${bytesToHex(key)}`, ['CREATE_PROOF']) - const { stack } = await this.findPath(this.appliedKey(key)) - const p = stack.map((stackElem) => { - return stackElem.serialize() - }) - this.DEBUG && this.debug(`Proof created with (${stack.length}) nodes`, ['CREATE_PROOF']) - return p - } - - /** - * Updates a trie from a proof by putting all the nodes in the proof into the trie. If a trie is being updated with multiple proofs, {@param shouldVerifyRoot} can - * be passed as false in order to not immediately throw on an unexpected root, so that root verification can happen after all proofs and their nodes have been added. - * An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof contains the encoded trie nodes from the root node to the leaf node storing state data. - * @param proof An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof to update the trie from. - * @param shouldVerifyRoot If `true`, verifies that the root key of the proof matches the trie root. Throws if this is not the case. - * @returns The root of the proof - */ - async updateFromProof(proof: Proof, shouldVerifyRoot: boolean = false) { - this.DEBUG && this.debug(`Saving (${proof.length}) proof nodes in DB`, ['FROM_PROOF']) - const opStack = proof.map((nodeValue) => { - let key = Uint8Array.from(this.hash(nodeValue)) - key = this._opts.keyPrefix ? concatBytes(this._opts.keyPrefix, key) : key - return { - type: 'put', - key, - value: nodeValue, - } as PutBatch - }) - - if (shouldVerifyRoot) { - if (opStack[0] !== undefined && opStack[0] !== null) { - if (!equalsBytes(this.root(), opStack[0].key)) { - throw new Error('The provided proof does not have the expected trie root') - } - } - } - - await this._db.batch(opStack) - if (opStack[0] !== undefined) { - return opStack[0].key - } - } - - /** - * Verifies a proof by putting all of its nodes into a trie and attempting to get the proven key. An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof - * contains the encoded trie nodes from the root node to the leaf node storing state data. A static version of this function exists with the same name. - * @param rootHash Root hash of the trie that this proof was created from and is being verified for - * @param key Key that is being verified and that the proof is created for - * @param proof an EIP-1186 proof to verify the key against - * @throws If proof is found to be invalid. - * @returns The value from the key, or null if valid proof of non-existence. - */ - async verifyProof( - rootHash: Uint8Array, - key: Uint8Array, - proof: Proof, - ): Promise { - this.DEBUG && - this.debug( - `Verifying Proof:\n|| Key: ${bytesToHex(key)}\n|| Root: ${bytesToHex( - rootHash, - )}\n|| Proof: (${proof.length}) nodes - `, - ['VERIFY_PROOF'], - ) - const proofTrie = new Trie({ - root: rootHash, - useKeyHashingFunction: this._opts.useKeyHashingFunction, - common: this._opts.common, - }) - try { - await proofTrie.updateFromProof(proof, true) - } catch (e: any) { - throw new Error('Invalid proof nodes given') - } - try { - this.DEBUG && - this.debug(`Verifying proof by retrieving key: ${bytesToHex(key)} from proof trie`, [ - 'VERIFY_PROOF', - ]) - const value = await proofTrie.get(this.appliedKey(key), true) - this.DEBUG && this.debug(`PROOF VERIFIED`, ['VERIFY_PROOF']) - return value - } catch (err: any) { - if (err.message === 'Missing node in DB') { - throw new Error('Invalid proof provided') - } else { - throw err - } - } - } - - /** - * Create a trie from a given (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof. An EIP-1186 proof contains the encoded trie nodes from the root - * node to the leaf node storing state data. This function does not check if the proof has the same expected root. A static version of this function exists - * with the same name. - * @param proof an EIP-1186 proof to update the trie from - * @deprecated Use `updateFromProof` - */ - async fromProof(proof: Proof): Promise { - await this.updateFromProof(proof, false) - - if (equalsBytes(this.root(), this.EMPTY_TRIE_ROOT) && proof[0] !== undefined) { - let rootKey = Uint8Array.from(this.hash(proof[0])) - // TODO: what if we have keyPrefix and we set root? This should not work, right? (all trie nodes are non-reachable) - rootKey = this._opts.keyPrefix ? concatBytes(this._opts.keyPrefix, rootKey) : rootKey - this.root(rootKey) - await this.persistRoot() - } - return - } - database(db?: DB, valueEncoding?: ValueEncoding) { if (db !== undefined) { if (db instanceof CheckpointDB) { diff --git a/packages/trie/test/proof.spec.ts b/packages/trie/test/proof.spec.ts index 2a5b751830..56d22c195e 100644 --- a/packages/trie/test/proof.spec.ts +++ b/packages/trie/test/proof.spec.ts @@ -2,7 +2,13 @@ import { RLP } from '@ethereumjs/rlp' import { bytesToUtf8, equalsBytes, setLengthLeft, utf8ToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { Trie, createTrieFromProof, verifyTrieProof } from '../src/index.js' +import { + Trie, + createProof, + createTrieFromProof, + updateFromProof, + verifyTrieProof, +} from '../src/index.js' describe('simple merkle proofs generation and verification', () => { it('create a merkle proof and verify it', async () => { @@ -12,27 +18,27 @@ describe('simple merkle proofs generation and verification', () => { await trie.put(utf8ToBytes('key2bb'), utf8ToBytes('aval2')) await trie.put(utf8ToBytes('key3cc'), utf8ToBytes('aval3')) - let proof = await trie.createProof(utf8ToBytes('key2bb')) + let proof = await createProof(trie, utf8ToBytes('key2bb')) let val = await verifyTrieProof(utf8ToBytes('key2bb'), proof) assert.equal(bytesToUtf8(val!), 'aval2') - proof = await trie.createProof(utf8ToBytes('key1aa')) + proof = await createProof(trie, utf8ToBytes('key1aa')) val = await verifyTrieProof(utf8ToBytes('key1aa'), proof) assert.equal(bytesToUtf8(val!), '0123456789012345678901234567890123456789xx') - proof = await trie.createProof(utf8ToBytes('key2bb')) + proof = await createProof(trie, utf8ToBytes('key2bb')) val = await verifyTrieProof(utf8ToBytes('key2'), proof) // In this case, the proof _happens_ to contain enough nodes to prove `key2` because // traversing into `key22` would touch all the same nodes as traversing into `key2` assert.equal(val, null, 'Expected value at a random key to be null') let myKey = utf8ToBytes('anyrandomkey') - proof = await trie.createProof(myKey) + proof = await createProof(trie, myKey) val = await verifyTrieProof(myKey, proof) assert.equal(val, null, 'Expected value to be null') myKey = utf8ToBytes('anothergarbagekey') // should generate a valid proof of null - proof = await trie.createProof(myKey) + proof = await createProof(trie, myKey) proof.push(utf8ToBytes('123456')) // extra nodes are just ignored val = await verifyTrieProof(myKey, proof) assert.equal(val, null, 'Expected value to be null') @@ -40,7 +46,7 @@ describe('simple merkle proofs generation and verification', () => { await trie.put(utf8ToBytes('another'), utf8ToBytes('3498h4riuhgwe')) // to fail our proof we can request a proof for one key - proof = await trie.createProof(utf8ToBytes('another')) + proof = await createProof(trie, utf8ToBytes('another')) // and try to use that proof on another key try { await verifyTrieProof(utf8ToBytes('key1aa'), proof) @@ -50,7 +56,7 @@ describe('simple merkle proofs generation and verification', () => { } // we can also corrupt a valid proof - proof = await trie.createProof(utf8ToBytes('key2bb')) + proof = await createProof(trie, utf8ToBytes('key2bb')) proof[0].reverse() try { await verifyTrieProof(utf8ToBytes('key2bb'), proof) @@ -62,7 +68,7 @@ describe('simple merkle proofs generation and verification', () => { // test an invalid exclusion proof by creating // a valid exclusion proof then making it non-null myKey = utf8ToBytes('anyrandomkey') - proof = await trie.createProof(myKey) + proof = await createProof(trie, myKey) val = await verifyTrieProof(myKey, proof) assert.equal(val, null, 'Expected value to be null') // now make the key non-null so the exclusion proof becomes invalid @@ -80,7 +86,7 @@ describe('simple merkle proofs generation and verification', () => { await trie.put(utf8ToBytes('key1aa'), utf8ToBytes('0123456789012345678901234567890123456789xx')) - const proof = await trie.createProof(utf8ToBytes('key1aa')) + const proof = await createProof(trie, utf8ToBytes('key1aa')) const val = await verifyTrieProof(utf8ToBytes('key1aa'), proof) assert.equal(bytesToUtf8(val!), '0123456789012345678901234567890123456789xx') }) @@ -90,7 +96,7 @@ describe('simple merkle proofs generation and verification', () => { await trie.put(utf8ToBytes('key1aa'), utf8ToBytes('01234')) - const proof = await trie.createProof(utf8ToBytes('key1aa')) + const proof = await createProof(trie, utf8ToBytes('key1aa')) const val = await verifyTrieProof(utf8ToBytes('key1aa'), proof) assert.equal(bytesToUtf8(val!), '01234') }) @@ -111,15 +117,15 @@ describe('simple merkle proofs generation and verification', () => { await trie.put(utf8ToBytes('key3cc'), utf8ToBytes('aval3')) await trie.put(utf8ToBytes('key3'), utf8ToBytes('1234567890123456789012345678901')) - let proof = await trie.createProof(utf8ToBytes('key1')) + let proof = await createProof(trie, utf8ToBytes('key1')) let val = await verifyTrieProof(utf8ToBytes('key1'), proof) assert.equal(bytesToUtf8(val!), '0123456789012345678901234567890123456789Very_Long') - proof = await trie.createProof(utf8ToBytes('key2')) + proof = await createProof(trie, utf8ToBytes('key2')) val = await verifyTrieProof(utf8ToBytes('key2'), proof) assert.equal(bytesToUtf8(val!), 'short') - proof = await trie.createProof(utf8ToBytes('key3')) + proof = await createProof(trie, utf8ToBytes('key3')) val = await verifyTrieProof(utf8ToBytes('key3'), proof) assert.equal(bytesToUtf8(val!), '1234567890123456789012345678901') }) @@ -131,15 +137,15 @@ describe('simple merkle proofs generation and verification', () => { await trie.put(utf8ToBytes('b'), utf8ToBytes('b')) await trie.put(utf8ToBytes('c'), utf8ToBytes('c')) - let proof = await trie.createProof(utf8ToBytes('a')) + let proof = await createProof(trie, utf8ToBytes('a')) let val = await verifyTrieProof(utf8ToBytes('a'), proof) assert.equal(bytesToUtf8(val!), 'a') - proof = await trie.createProof(utf8ToBytes('b')) + proof = await createProof(trie, utf8ToBytes('b')) val = await verifyTrieProof(utf8ToBytes('b'), proof) assert.equal(bytesToUtf8(val!), 'b') - proof = await trie.createProof(utf8ToBytes('c')) + proof = await createProof(trie, utf8ToBytes('c')) val = await verifyTrieProof(utf8ToBytes('c'), proof) assert.equal(bytesToUtf8(val!), 'c') }) @@ -159,7 +165,7 @@ describe('simple merkle proofs generation and verification', () => { await trie.put(key, encodedValue) await trie.put(key2, encodedValue2) await trie.put(key3, encodedValue3) - const proof = await trie.createProof(key) + const proof = await createProof(trie, key) const newTrie = await createTrieFromProof(proof, { useKeyHashing: true }) const trieValue = await newTrie.get(key) @@ -167,8 +173,8 @@ describe('simple merkle proofs generation and verification', () => { assert.ok(equalsBytes(trieValue!, encodedValue), 'trie value sucessfully copied') assert.ok(equalsBytes(trie.root(), newTrie.root()), 'root set correctly') - const proof2 = await trie.createProof(key2) - await newTrie.updateFromProof(proof2) + const proof2 = await createProof(trie, key2) + await updateFromProof(newTrie, proof2) const trieValue2 = await newTrie.get(key2) assert.ok(equalsBytes(trieValue2!, encodedValue2), 'trie value succesfully updated') @@ -182,16 +188,16 @@ describe('simple merkle proofs generation and verification', () => { const safeValue = RLP.encode(new Uint8Array([1337])) await safeTrie.put(safeKey, safeValue) - const safeProof = await safeTrie.createProof(safeKey) + const safeProof = await createProof(safeTrie, safeKey) try { - await newTrie.updateFromProof(safeProof, true) + await updateFromProof(newTrie, safeProof, true) assert.fail('cannot reach this') } catch (e) { assert.ok(true, 'throws on unmatching proof') } - await newTrie.updateFromProof(safeProof) + await updateFromProof(newTrie, safeProof) assert.ok(equalsBytes(trie.root(), newTrie.root()), 'root set correctly') const newSafeValue = await newTrie.get(safeKey) diff --git a/packages/trie/test/proof/range.spec.ts b/packages/trie/test/proof/range.spec.ts index 3fb45cf464..2ca66baefa 100644 --- a/packages/trie/test/proof/range.spec.ts +++ b/packages/trie/test/proof/range.spec.ts @@ -9,7 +9,7 @@ import { } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { Trie } from '../../src/index.js' +import { Trie, createProof, verifyTrieRangeProof } from '../../src/index.js' import type { DB } from '@ethereumjs/util' @@ -88,13 +88,13 @@ async function verify( startKey = startKey ?? entries[start][0] endKey = endKey ?? entries[end][0] const targetRange = entries.slice(start, end + 1) - return trie.verifyRangeProof( + return verifyTrieRangeProof( trie.root(), startKey, endKey, keys ?? targetRange.map(([key]) => key), vals ?? targetRange.map(([, val]) => val), - [...(await trie.createProof(startKey)), ...(await trie.createProof(endKey))], + [...(await createProof(trie, startKey)), ...(await createProof(trie, endKey))], ) } @@ -211,7 +211,7 @@ describe('simple merkle range proofs generation and verification', () => { const { trie, entries } = await randomTrie(new MapDB()) assert.equal( - await trie.verifyRangeProof( + await verifyTrieRangeProof( trie.root(), null, null, @@ -474,7 +474,7 @@ describe('simple merkle range proofs generation and verification', () => { let bloatedProof: Uint8Array[] = [] for (let i = 0; i < TRIE_SIZE; i++) { - bloatedProof = bloatedProof.concat(await trie.createProof(entries[i][0])) + bloatedProof = bloatedProof.concat(await createProof(trie, entries[i][0])) } assert.equal(await verify(trie, entries, 0, entries.length - 1), false) diff --git a/packages/trie/test/trie/secure.spec.ts b/packages/trie/test/trie/secure.spec.ts index 2a866250af..a13171adb4 100644 --- a/packages/trie/test/trie/secure.spec.ts +++ b/packages/trie/test/trie/secure.spec.ts @@ -10,7 +10,7 @@ import { keccak256 } from 'ethereum-cryptography/keccak.js' import { sha256 } from 'ethereum-cryptography/sha256.js' import { assert, describe, it } from 'vitest' -import { ROOT_DB_KEY, Trie, verifyTrieProof } from '../../src/index.js' +import { ROOT_DB_KEY, Trie, createProof, verifyTrieProof } from '../../src/index.js' import secureTrieTests from '../fixtures/trietest_secureTrie.json' describe('SecureTrie', () => { @@ -52,7 +52,7 @@ describe('SecureTrie proof', () => { const trie = new Trie({ useKeyHashing: true, db: new MapDB() }) await trie.put(utf8ToBytes('key1aa'), utf8ToBytes('01234')) - const proof = await trie.createProof(utf8ToBytes('key1aa')) + const proof = await createProof(trie, utf8ToBytes('key1aa')) const val = await verifyTrieProof(utf8ToBytes('key1aa'), proof, { useKeyHashing: true, }) diff --git a/packages/trie/test/util/asyncWalk.spec.ts b/packages/trie/test/util/asyncWalk.spec.ts index 51fefddc9f..0adf4e2d09 100644 --- a/packages/trie/test/util/asyncWalk.spec.ts +++ b/packages/trie/test/util/asyncWalk.spec.ts @@ -1,7 +1,13 @@ import { bytesToHex, equalsBytes, hexToBytes, utf8ToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { LeafNode, Trie, createTrieFromProof, verifyTrieProof } from '../../src/index.js' +import { + LeafNode, + Trie, + createProof, + createTrieFromProof, + verifyTrieProof, +} from '../../src/index.js' import { _walkTrie } from '../../src/util/asyncWalk.js' import { bytesToNibbles } from '../../src/util/nibbles.js' import trieTests from '../fixtures/trietest.json' @@ -78,7 +84,7 @@ describe('walk a sparse trie', async () => { }) // Generate a proof for inputs[0] const proofKey = inputs[0][0] - const proof = await trie.createProof(proofKey) + const proof = await createProof(trie, proofKey) assert.ok(await verifyTrieProof(proofKey, proof)) // Build a sparse trie from the proof diff --git a/packages/trie/test/util/log.spec.ts b/packages/trie/test/util/log.spec.ts index bb58340d2b..7141b4321f 100644 --- a/packages/trie/test/util/log.spec.ts +++ b/packages/trie/test/util/log.spec.ts @@ -2,6 +2,7 @@ import { utf8ToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' import { Trie } from '../../src/trie.js' +import { createProof, fromProof, verifyProof } from '../../src/index.js' describe('Run Trie script with DEBUG enabled', async () => { const trie_entries: [string, string | null][] = [ @@ -19,8 +20,8 @@ describe('Run Trie script with DEBUG enabled', async () => { await trie.put(utf8ToBytes(key), value === null ? Uint8Array.from([]) : utf8ToBytes(value)) } - const proof = await trie.createProof(utf8ToBytes('doge')) - const valid = await trie.verifyProof(trie.root(), utf8ToBytes('doge'), proof) + const proof = await createProof(trie, utf8ToBytes('doge')) + const valid = await verifyProof(trie, trie.root(), utf8ToBytes('doge'), proof) it('should be valid', async () => { assert.deepEqual(valid, utf8ToBytes('coin')) @@ -34,7 +35,7 @@ describe('Run Trie script with DEBUG enabled', async () => { process.env.DEBUG = '' const trie2 = new Trie({}) trie2['DEBUG'] = true - await trie2.fromProof(proof) + await fromProof(trie2, proof) it('tries should share root', async () => { assert.deepEqual(trie.root(), trie2.root()) }) From 29b64d5c583929fe691dbb7fba4e0a36fa07887a Mon Sep 17 00:00:00 2001 From: Amir Date: Mon, 29 Jul 2024 14:01:32 -0700 Subject: [PATCH 3/9] Fix lint issues --- packages/trie/examples/proofs.ts | 14 +++++++------- packages/trie/src/proof/index.ts | 7 ++++--- packages/trie/test/util/log.spec.ts | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/trie/examples/proofs.ts b/packages/trie/examples/proofs.ts index 4f618a688b..c2a14f38b1 100644 --- a/packages/trie/examples/proofs.ts +++ b/packages/trie/examples/proofs.ts @@ -1,4 +1,4 @@ -import { Trie } from '@ethereumjs/trie' +import { Trie, createProof, verifyProof } from '@ethereumjs/trie' import { bytesToUtf8, utf8ToBytes } from '@ethereumjs/util' const trie = new Trie() @@ -11,24 +11,24 @@ async function main() { // proof-of-inclusion await trie.put(k1, v1) - let proof = await trie.createProof(k1) - let value = await trie.verifyProof(trie.root(), k1, proof) + let proof = await createProof(trie, k1) + let value = await verifyProof(trie, trie.root(), k1, proof) console.log(value ? bytesToUtf8(value) : 'not found') // 'one' // proof-of-exclusion await trie.put(k1, v1) await trie.put(k2, v2) - proof = await trie.createProof(utf8ToBytes('key3')) - value = await trie.verifyProof(trie.root(), utf8ToBytes('key3'), proof) + proof = await createProof(trie, utf8ToBytes('key3')) + value = await verifyProof(trie, trie.root(), utf8ToBytes('key3'), proof) console.log(value ? bytesToUtf8(value) : 'null') // null // invalid proof await trie.put(k1, v1) await trie.put(k2, v2) - proof = await trie.createProof(k2) + proof = await createProof(trie, k2) proof[0].reverse() try { - const _value = await trie.verifyProof(trie.root(), k2, proof) // results in error + const _value = await verifyProof(trie, trie.root(), k2, proof) // results in error } catch (err) { console.log(err) } diff --git a/packages/trie/src/proof/index.ts b/packages/trie/src/proof/index.ts index 203f41eb15..f92d17e356 100644 --- a/packages/trie/src/proof/index.ts +++ b/packages/trie/src/proof/index.ts @@ -1,11 +1,12 @@ +import { bytesToHex, concatBytes, equalsBytes } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak' import { createTrieFromProof } from '../constructors.js' -import { verifyRangeProof } from '../index.js' +import { Trie, verifyRangeProof } from '../index.js' import { bytesToNibbles } from '../util/nibbles.js' -import { Proof, Trie, TrieOpts } from '../index.js' -import { PutBatch, bytesToHex, concatBytes, equalsBytes } from '@ethereumjs/util' +import type { Proof, TrieOpts } from '../index.js' +import type { PutBatch } from '@ethereumjs/util' /** * Static version of verifyProof function with the same behavior. An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof contains the encoded trie nodes diff --git a/packages/trie/test/util/log.spec.ts b/packages/trie/test/util/log.spec.ts index 7141b4321f..5239eb8449 100644 --- a/packages/trie/test/util/log.spec.ts +++ b/packages/trie/test/util/log.spec.ts @@ -1,8 +1,8 @@ import { utf8ToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { Trie } from '../../src/trie.js' import { createProof, fromProof, verifyProof } from '../../src/index.js' +import { Trie } from '../../src/trie.js' describe('Run Trie script with DEBUG enabled', async () => { const trie_entries: [string, string | null][] = [ From 34d8ff44ad0a20a4b6a7fe689243e75f0d78b901 Mon Sep 17 00:00:00 2001 From: Amir Date: Tue, 30 Jul 2024 10:42:51 -0700 Subject: [PATCH 4/9] Fix examples --- packages/trie/examples/createFromProof.ts | 8 ++++---- packages/trie/examples/logDemo.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/trie/examples/createFromProof.ts b/packages/trie/examples/createFromProof.ts index f3db4f5867..e1f08dbfa4 100644 --- a/packages/trie/examples/createFromProof.ts +++ b/packages/trie/examples/createFromProof.ts @@ -1,4 +1,4 @@ -import { Trie, createTrieFromProof } from '@ethereumjs/trie' +import { Trie, createProof, createTrieFromProof, updateFromProof } from '@ethereumjs/trie' import { bytesToUtf8, utf8ToBytes } from '@ethereumjs/util' async function main() { @@ -9,12 +9,12 @@ async function main() { await someOtherTrie.put(k1, utf8ToBytes('valueOne')) await someOtherTrie.put(k2, utf8ToBytes('valueTwo')) - const proof = await someOtherTrie.createProof(k1) + const proof = await createProof(someOtherTrie, k1) const trie = await createTrieFromProof(proof, { useKeyHashing: true }) - const otherProof = await someOtherTrie.createProof(k2) + const otherProof = await createProof(someOtherTrie, k2) // To add more proofs to the trie, use `updateFromProof` - await trie.updateFromProof(otherProof) + await updateFromProof(trie, otherProof) const value = await trie.get(k1) console.log(bytesToUtf8(value!)) // valueOne diff --git a/packages/trie/examples/logDemo.ts b/packages/trie/examples/logDemo.ts index 536ec7e2a0..0875a22c3a 100644 --- a/packages/trie/examples/logDemo.ts +++ b/packages/trie/examples/logDemo.ts @@ -1,7 +1,7 @@ /** * Run with DEBUG=ethjs,trie:* to see debug log ouput */ -import { Trie } from '@ethereumjs/trie' +import { Trie, createProof, verifyProof } from '@ethereumjs/trie' import { utf8ToBytes } from '@ethereumjs/util' const trie_entries: [string, string | null][] = [ @@ -22,8 +22,8 @@ const main = async () => { for (const [key, value] of trie_entries) { await trie.put(utf8ToBytes(key), value === null ? Uint8Array.from([]) : utf8ToBytes(value)) } - const proof = await trie.createProof(utf8ToBytes('doge')) - const valid = await trie.verifyProof(trie.root(), utf8ToBytes('doge'), proof) + const proof = await createProof(trie, utf8ToBytes('doge')) + const valid = await verifyProof(trie, trie.root(), utf8ToBytes('doge'), proof) console.log('valid', valid) } From d12a1ed83a0a61b615a7b2ab959cf7739ad9b4eb Mon Sep 17 00:00:00 2001 From: Scorbajio Date: Tue, 30 Jul 2024 11:53:13 -0700 Subject: [PATCH 5/9] Update packages/trie/src/proof/index.ts Co-authored-by: acolytec3 <17355484+acolytec3@users.noreply.github.com> --- packages/trie/src/proof/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/trie/src/proof/index.ts b/packages/trie/src/proof/index.ts index f92d17e356..5bb4ec1708 100644 --- a/packages/trie/src/proof/index.ts +++ b/packages/trie/src/proof/index.ts @@ -9,7 +9,7 @@ import type { Proof, TrieOpts } from '../index.js' import type { PutBatch } from '@ethereumjs/util' /** - * Static version of verifyProof function with the same behavior. An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof contains the encoded trie nodes + * An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof contains the encoded trie nodes * from the root node to the leaf node storing state data. * @param rootHash Root hash of the trie that this proof was created from and is being verified for * @param key Key that is being verified and that the proof is created for From ab24833652b54a885605619cb38be5e12e660f7a Mon Sep 17 00:00:00 2001 From: Amir Date: Tue, 30 Jul 2024 11:56:39 -0700 Subject: [PATCH 6/9] Update docstrings --- packages/trie/src/proof/index.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/trie/src/proof/index.ts b/packages/trie/src/proof/index.ts index 5bb4ec1708..2fc2211593 100644 --- a/packages/trie/src/proof/index.ts +++ b/packages/trie/src/proof/index.ts @@ -35,8 +35,7 @@ export async function verifyTrieProof( // /** // * A range proof is a proof that includes the encoded trie nodes from the root node to leaf node for one or more branches of a trie, // * allowing an entire range of leaf nodes to be validated. This is useful in applications such as snap sync where contiguous ranges -// * of state trie data is received and validated for constructing world state, locally. Also see {@link verifyRangeProof}. A static -// * version of this function also exists. +// * of state trie data is received and validated for constructing world state, locally. Also see {@link verifyRangeProof}. // * @param rootHash - root hash of state trie this proof is being verified against. // * @param firstKey - first key of range being proven. // * @param lastKey - last key of range being proven. @@ -118,7 +117,7 @@ export async function updateFromProof(trie: Trie, proof: Proof, shouldVerifyRoot /** * Verifies a proof by putting all of its nodes into a trie and attempting to get the proven key. An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof - * contains the encoded trie nodes from the root node to the leaf node storing state data. A static version of this function exists with the same name. + * contains the encoded trie nodes from the root node to the leaf node storing state data. * @param rootHash Root hash of the trie that this proof was created from and is being verified for * @param key Key that is being verified and that the proof is created for * @param proof an EIP-1186 proof to verify the key against @@ -168,7 +167,7 @@ export async function verifyProof( /** * Create a trie from a given (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof. An EIP-1186 proof contains the encoded trie nodes from the root - * node to the leaf node storing state data. This function does not check if the proof has the same expected root. A static version of this function exists + * node to the leaf node storing state data. This function does not check if the proof has the same expected root. * with the same name. * @param proof an EIP-1186 proof to update the trie from * @deprecated Use `updateFromProof` From 6eb240b6f386b9ce4f633e58bcc0b8102aece517 Mon Sep 17 00:00:00 2001 From: Amir Date: Tue, 30 Jul 2024 12:15:28 -0700 Subject: [PATCH 7/9] Remove fromProof in favor of createTrieFromProof --- packages/trie/src/proof/index.ts | 20 -------------------- packages/trie/test/util/log.spec.ts | 5 ++--- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/packages/trie/src/proof/index.ts b/packages/trie/src/proof/index.ts index 2fc2211593..d493c205e5 100644 --- a/packages/trie/src/proof/index.ts +++ b/packages/trie/src/proof/index.ts @@ -165,24 +165,4 @@ export async function verifyProof( } } -/** - * Create a trie from a given (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof. An EIP-1186 proof contains the encoded trie nodes from the root - * node to the leaf node storing state data. This function does not check if the proof has the same expected root. - * with the same name. - * @param proof an EIP-1186 proof to update the trie from - * @deprecated Use `updateFromProof` - */ -export async function fromProof(trie: Trie, proof: Proof): Promise { - await updateFromProof(trie, proof, false) - - if (equalsBytes(trie.root(), trie.EMPTY_TRIE_ROOT) && proof[0] !== undefined) { - let rootKey = Uint8Array.from(trie['hash'](proof[0])) - // TODO: what if we have keyPrefix and we set root? This should not work, right? (all trie nodes are non-reachable) - rootKey = trie['_opts'].keyPrefix ? concatBytes(trie['_opts'].keyPrefix, rootKey) : rootKey - trie.root(rootKey) - await trie.persistRoot() - } - return -} - export * from './range.js' diff --git a/packages/trie/test/util/log.spec.ts b/packages/trie/test/util/log.spec.ts index 5239eb8449..bc9eac45a7 100644 --- a/packages/trie/test/util/log.spec.ts +++ b/packages/trie/test/util/log.spec.ts @@ -1,7 +1,7 @@ import { utf8ToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { createProof, fromProof, verifyProof } from '../../src/index.js' +import { createProof, createTrieFromProof, fromProof, verifyProof } from '../../src/index.js' import { Trie } from '../../src/trie.js' describe('Run Trie script with DEBUG enabled', async () => { @@ -33,9 +33,8 @@ describe('Run Trie script with DEBUG enabled', async () => { trie.checkpoint() await trie.revert() process.env.DEBUG = '' - const trie2 = new Trie({}) + const trie2 = await createTrieFromProof(proof) trie2['DEBUG'] = true - await fromProof(trie2, proof) it('tries should share root', async () => { assert.deepEqual(trie.root(), trie2.root()) }) From 6d8dd97c4947cc88642d1dd0071d5b99fc6e9f03 Mon Sep 17 00:00:00 2001 From: Amir Date: Tue, 30 Jul 2024 12:19:19 -0700 Subject: [PATCH 8/9] Fix lint issues --- packages/trie/test/util/log.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/trie/test/util/log.spec.ts b/packages/trie/test/util/log.spec.ts index bc9eac45a7..b4f79826d1 100644 --- a/packages/trie/test/util/log.spec.ts +++ b/packages/trie/test/util/log.spec.ts @@ -1,7 +1,7 @@ import { utf8ToBytes } from '@ethereumjs/util' import { assert, describe, it } from 'vitest' -import { createProof, createTrieFromProof, fromProof, verifyProof } from '../../src/index.js' +import { createProof, createTrieFromProof, verifyProof } from '../../src/index.js' import { Trie } from '../../src/trie.js' describe('Run Trie script with DEBUG enabled', async () => { From 8f6d816a32fcae2d80bffc2ec7addcd572c56592 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 30 Jul 2024 20:56:10 -0400 Subject: [PATCH 9/9] address feedback [no ci] --- packages/trie/src/proof/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/trie/src/proof/index.ts b/packages/trie/src/proof/index.ts index d493c205e5..c7ec4ac5cc 100644 --- a/packages/trie/src/proof/index.ts +++ b/packages/trie/src/proof/index.ts @@ -82,11 +82,11 @@ export async function createProof(trie: Trie, key: Uint8Array): Promise { } /** - * Updates a trie from a proof by putting all the nodes in the proof into the trie. If a trie is being updated with multiple proofs, {@param shouldVerifyRoot} can - * be passed as false in order to not immediately throw on an unexpected root, so that root verification can happen after all proofs and their nodes have been added. + * Updates a trie from a proof by putting all the nodes in the proof into the trie. Pass {@param shouldVerifyRoot} as true to check + * that root key of proof matches root of trie and throw if not. * An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof contains the encoded trie nodes from the root node to the leaf node storing state data. * @param proof An (EIP-1186)[https://eips.ethereum.org/EIPS/eip-1186] proof to update the trie from. - * @param shouldVerifyRoot If `true`, verifies that the root key of the proof matches the trie root. Throws if this is not the case. + * @param shouldVerifyRoot - defaults to false. If `true`, verifies that the root key of the proof matches the trie root and throws if not (i.e invalid proof). * @returns The root of the proof */ export async function updateFromProof(trie: Trie, proof: Proof, shouldVerifyRoot: boolean = false) {