From dccaca3af4b7a6b7f9b6f05c0118032994a5acd9 Mon Sep 17 00:00:00 2001 From: harkamal Date: Mon, 15 Apr 2024 15:22:46 +0530 Subject: [PATCH] cleanly apply stem changes from gabriels commit --- package-lock.json | 23 +++-- packages/client/src/execution/vmexecution.ts | 3 +- .../client/test/rpc/engine/kaustinen6.spec.ts | 2 +- packages/statemanager/package.json | 3 +- packages/statemanager/src/accessWitness.ts | 20 +++-- .../src/statelessVerkleStateManager.ts | 85 ++++++++++--------- .../test/statelessVerkleStateManager.spec.ts | 42 +++++---- packages/verkle/package.json | 6 +- packages/verkle/src/node/baseVerkleNode.ts | 6 +- packages/verkle/src/node/internalNode.ts | 7 +- packages/verkle/src/node/leafNode.ts | 6 +- packages/verkle/src/node/types.ts | 6 +- packages/verkle/src/types.ts | 7 ++ packages/verkle/src/util/crypto.ts | 47 ++-------- packages/verkle/src/verkleTree.ts | 17 ++++ packages/verkle/test/crypto.spec.ts | 20 +++-- 16 files changed, 170 insertions(+), 130 deletions(-) diff --git a/package-lock.json b/package-lock.json index c1db22490a6..60cc0f5c00f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12099,10 +12099,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rust-verkle-wasm": { - "version": "0.0.1", - "license": "(MIT OR Apache-2.0)" - }, "node_modules/rustbn-wasm": { "version": "0.4.0", "license": "(MIT OR Apache-2.0)", @@ -13654,6 +13650,18 @@ "node": ">= 0.8" } }, + "node_modules/verkle-cryptography-wasm": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/verkle-cryptography-wasm/-/verkle-cryptography-wasm-0.4.0.tgz", + "integrity": "sha512-g0gvXqUvdOxqKhyE6qiMLIWqmyP9rnQIb9VDc+GS8SOIPFGx12WhVB7IMkPcfBXUTZmcMT2JXiNMrK7iR2ofBA==", + "dependencies": { + "@scure/base": "^1.1.5" + }, + "engines": { + "node": ">=18", + "npm": ">=7" + } + }, "node_modules/vite": { "version": "5.2.8", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", @@ -14993,7 +15001,8 @@ "debug": "^4.3.3", "ethereum-cryptography": "^2.1.3", "js-sdsl": "^4.1.4", - "lru-cache": "10.1.0" + "lru-cache": "10.1.0", + "verkle-cryptography-wasm": "^0.4.0" }, "devDependencies": { "@ethereumjs/block": "^5.2.0", @@ -15074,8 +15083,8 @@ "dependencies": { "@ethereumjs/rlp": "^5.0.2", "@ethereumjs/util": "^9.0.3", - "lru-cache": "^10.0.0", - "rust-verkle-wasm": "^0.0.1" + "lru-cache": "10.1.0", + "verkle-cryptography-wasm": "^0.4.0" }, "engines": { "node": ">=18" diff --git a/packages/client/src/execution/vmexecution.ts b/packages/client/src/execution/vmexecution.ts index ce6062aa980..318f31ab1f2 100644 --- a/packages/client/src/execution/vmexecution.ts +++ b/packages/client/src/execution/vmexecution.ts @@ -188,9 +188,8 @@ export class VMExecution extends Execution { if (this.verkleVM !== undefined) { return } - this.config.logger.info(`Setting up verkleVM`) - const stateManager = new StatelessVerkleStateManager() + const stateManager = await StatelessVerkleStateManager.create() this.verkleVM = await VM.create({ common: this.config.execCommon, blockchain: this.chain.blockchain, diff --git a/packages/client/test/rpc/engine/kaustinen6.spec.ts b/packages/client/test/rpc/engine/kaustinen6.spec.ts index fa088bb2637..367485ac097 100644 --- a/packages/client/test/rpc/engine/kaustinen6.spec.ts +++ b/packages/client/test/rpc/engine/kaustinen6.spec.ts @@ -13,7 +13,7 @@ import type { BeaconPayloadJson } from '@ethereumjs/block' import type { Common } from '@ethereumjs/common' import type { HttpClient } from 'jayson/promise' const genesisVerkleStateRoot = '0x382960711d9ccf58b9db20122e2253eb9bfa99d513f8c9d4e85b55971721f4de' -const genesisVerkleBlockHash = '0x086326f2922364dba375e7c9bed375d622845615c0974ffd1d3be0e34edbfbc3' +const genesisVerkleBlockHash = '0x2d4f1927f519051a71d6d9b1bf7066ded483b13c0f4a7bb9e5b388cdf0d496fd' /** * One can run this test in this format: diff --git a/packages/statemanager/package.json b/packages/statemanager/package.json index 64f437a5b1d..f794eb2e666 100644 --- a/packages/statemanager/package.json +++ b/packages/statemanager/package.json @@ -57,7 +57,8 @@ "debug": "^4.3.3", "ethereum-cryptography": "^2.1.3", "js-sdsl": "^4.1.4", - "lru-cache": "10.1.0" + "lru-cache": "10.1.0", + "verkle-cryptography-wasm": "^0.4.0" }, "devDependencies": { "@ethereumjs/block": "^5.2.0", diff --git a/packages/statemanager/src/accessWitness.ts b/packages/statemanager/src/accessWitness.ts index 84ee09be9c6..fe77207b954 100644 --- a/packages/statemanager/src/accessWitness.ts +++ b/packages/statemanager/src/accessWitness.ts @@ -3,6 +3,7 @@ import { getKey, getStem } from '@ethereumjs/verkle' import debugDefault from 'debug' import type { Address, PrefixedHexString } from '@ethereumjs/util' +import type { VerkleCrypto } from '@ethereumjs/verkle' const { debug: createDebugLogger } = debugDefault const debug = createDebugLogger('statemanager:verkle:aw') @@ -72,13 +73,18 @@ export type AccessedStateWithAddress = AccessedState & { export class AccessWitness { stems: Map chunks: Map - + verkleCrypto: VerkleCrypto constructor( opts: { + verkleCrypto?: VerkleCrypto stems?: Map chunks?: Map } = {} ) { + if (opts.verkleCrypto === undefined) { + throw new Error('verkle crypto required') + } + this.verkleCrypto = opts.verkleCrypto this.stems = opts.stems ?? new Map() this.chunks = opts.chunks ?? new Map() } @@ -164,7 +170,7 @@ export class AccessWitness { return gas } - touchCodeChunksRangeOnReadAndChargeGas(contact: Address, startPc: number, endPc: number) { + touchCodeChunksRangeOnReadAndChargeGas(contact: Address, startPc: number, endPc: number): bigint { let gas = BIGINT_0 for (let chunkNum = Math.floor(startPc / 31); chunkNum <= Math.floor(endPc / 31); chunkNum++) { const { treeIndex, subIndex } = getTreeIndicesForCodeChunk(chunkNum) @@ -173,7 +179,11 @@ export class AccessWitness { return gas } - touchCodeChunksRangeOnWriteAndChargeGas(contact: Address, startPc: number, endPc: number) { + touchCodeChunksRangeOnWriteAndChargeGas( + contact: Address, + startPc: number, + endPc: number + ): bigint { let gas = BIGINT_0 for (let chunkNum = Math.floor(startPc / 31); chunkNum <= Math.floor(endPc / 31); chunkNum++) { const { treeIndex, subIndex } = getTreeIndicesForCodeChunk(chunkNum) @@ -251,7 +261,7 @@ export class AccessWitness { // i.e. no fill cost is charged right now const chunkFill = false - const accessedStemKey = getStem(address, treeIndex) + const accessedStemKey = getStem(this.verkleCrypto, address, treeIndex) const accessedStemHex = bytesToHex(accessedStemKey) let accessedStem = this.stems.get(accessedStemHex) if (accessedStem === undefined) { @@ -291,7 +301,7 @@ export class AccessWitness { /**Create a shallow copy, could clone some caches in future for optimizations */ shallowCopy(): AccessWitness { - return new AccessWitness() + return new AccessWitness({ verkleCrypto: this.verkleCrypto }) } merge(accessWitness: AccessWitness): void { diff --git a/packages/statemanager/src/statelessVerkleStateManager.ts b/packages/statemanager/src/statelessVerkleStateManager.ts index d8d8aa29d18..1e2140d32ce 100644 --- a/packages/statemanager/src/statelessVerkleStateManager.ts +++ b/packages/statemanager/src/statelessVerkleStateManager.ts @@ -13,10 +13,10 @@ import { short, toBytes, } from '@ethereumjs/util' -import { getKey, getStem, verifyUpdate } from '@ethereumjs/verkle' +import { getKey, getStem } from '@ethereumjs/verkle' import debugDefault from 'debug' import { keccak256 } from 'ethereum-cryptography/keccak.js' -import { equalsBytes } from 'ethereum-cryptography/utils' +import { loadVerkleCrypto } from 'verkle-cryptography-wasm' import { AccessWitness, @@ -45,6 +45,7 @@ import type { StorageRange, } from '@ethereumjs/common' import type { Address, PrefixedHexString } from '@ethereumjs/util' +import type { VerkleCrypto } from '@ethereumjs/verkle' const { debug: createDebugLogger } = debugDefault @@ -113,6 +114,7 @@ export interface StatelessVerkleStateManagerOpts { storageCacheOpts?: CacheOptions codeCacheOpts?: CacheOptions accesses?: AccessWitness + verkleCrypto?: VerkleCrypto } const PUSH_OFFSET = 95 @@ -141,6 +143,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { originalStorageCache: OriginalStorageCache + verkleCrypto: VerkleCrypto protected readonly _accountCacheSettings: CacheSettings protected readonly _storageCacheSettings: CacheSettings protected readonly _codeCacheSettings: CacheSettings @@ -173,6 +176,17 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { private keccakFunction: Function + /** + * Async static constructor for StatelessVerkleStateManager + * @param opts `StatelessVerkleStateManagerOpts` + * @returns a StatelessVerkleStateManager with initialized Verkle Crypto + */ + static create = async (opts: StatelessVerkleStateManagerOpts = {}) => { + if (opts.verkleCrypto === undefined) { + opts.verkleCrypto = await loadVerkleCrypto() + } + return new StatelessVerkleStateManager(opts) + } /** * Instantiate the StateManager interface. */ @@ -221,23 +235,15 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { this.keccakFunction = opts.common?.customCrypto.keccak256 ?? keccak256 + if (opts.verkleCrypto === undefined) { + throw new Error('verkle crypto required') + } + this.verkleCrypto = opts.verkleCrypto + // Skip DEBUG calls unless 'ethjs' included in environmental DEBUG variables // Additional window check is to prevent vite browser bundling (and potentially other) to break this.DEBUG = typeof window === 'undefined' ? process?.env?.DEBUG?.includes('ethjs') ?? false : false - - /* - * For a custom StateManager implementation adopt these - * callbacks passed to the `Cache` instantiated to perform - * the `get`, `put` and `delete` operations with the - * desired backend. - */ - // const getCb: get = async (address) => { - // return undefined - // } - // const putCb: put = async (keyBuf, accountRlp) => {} - // const deleteCb = async (keyBuf: Uint8Array) => {} - // this._cache = new Cache({ get, putCb, deleteCb }) } async getTransitionStateRoot(_: DefaultStateManager, __: Uint8Array): Promise { @@ -257,7 +263,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { } this._executionWitness = executionWitness - this.accessWitness = accessWitness ?? new AccessWitness() + this.accessWitness = accessWitness ?? new AccessWitness({ verkleCrypto: this.verkleCrypto }) this._proof = executionWitness.verkleProof as unknown as Uint8Array @@ -325,9 +331,9 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { return getKey(stem, CODE_SIZE_LEAF_KEY) } - getTreeKeyForCodeChunk(address: Address, chunkId: number) { + async getTreeKeyForCodeChunk(address: Address, chunkId: number) { const { treeIndex, subIndex } = getTreeIndicesForCodeChunk(chunkId) - return getKey(getStem(address, treeIndex), toBytes(subIndex)) + return getKey(getStem(this.verkleCrypto, address, treeIndex), toBytes(subIndex)) } chunkifyCode(code: Uint8Array) { @@ -340,15 +346,15 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { throw new Error('Not implemented') } - getTreeKeyForStorageSlot(address: Address, storageKey: bigint) { + async getTreeKeyForStorageSlot(address: Address, storageKey: bigint) { const { treeIndex, subIndex } = getTreeIndexesForStorageSlot(storageKey) - return getKey(getStem(address, treeIndex), toBytes(subIndex)) + return getKey(getStem(this.verkleCrypto, address, treeIndex), toBytes(subIndex)) } - checkChunkWitnessPresent(address: Address, codeOffset: number) { - const chunkId = Math.floor(codeOffset / 31) - const chunkKey = bytesToHex(this.getTreeKeyForCodeChunk(address, chunkId)) + async checkChunkWitnessPresent(address: Address, codeOffset: number) { + const chunkId = codeOffset / 31 + const chunkKey = bytesToHex(await this.getTreeKeyForCodeChunk(address, chunkId)) return this._state[chunkKey] !== undefined } @@ -358,7 +364,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { * checkpoints were reverted. */ shallowCopy(): EVMStateManagerInterface { - const stateManager = new StatelessVerkleStateManager() + const stateManager = new StatelessVerkleStateManager({ verkleCrypto: this.verkleCrypto }) stateManager.initVerkleExecutionWitness(this._blockNum, this._executionWitness!) return stateManager } @@ -419,7 +425,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { const chunks = Math.floor(codeSize / 31) + 1 for (let chunkId = 0; chunkId < chunks; chunkId++) { - const chunkKey = bytesToHex(this.getTreeKeyForCodeChunk(address, chunkId)) + const chunkKey = bytesToHex(await this.getTreeKeyForCodeChunk(address, chunkId)) const codeChunk = this._state[chunkKey] if (codeChunk === null) { const errorMsg = `Invalid access to a non existent code chunk with chunkKey=${chunkKey}` @@ -486,7 +492,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { } } - const storageKey = this.getTreeKeyForStorageSlot(address, BigInt(bytesToHex(key))) + const storageKey = await this.getTreeKeyForStorageSlot(address, BigInt(bytesToHex(key))) const storageValue = toBytes(this._state[bytesToHex(storageKey)]) if (!this._storageCacheSettings.deactivate) { @@ -508,7 +514,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { this._storageCache!.put(address, key, value) } else { // TODO: Consider refactoring this in a writeContractStorage function? Like in stateManager.ts - const storageKey = this.getTreeKeyForStorageSlot(address, BigInt(bytesToHex(key))) + const storageKey = await this.getTreeKeyForStorageSlot(address, BigInt(bytesToHex(key))) this._state[bytesToHex(storageKey)] = bytesToHex(setLengthRight(value, 32)) } } @@ -518,7 +524,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { * @param address - Address to clear the storage of */ async clearContractStorage(address: Address): Promise { - const stem = getStem(address, 0) + const stem = getStem(this.verkleCrypto, address, 0) const codeHashKey = this.getTreeKeyForCodeHash(stem) this._storageCache?.clearContractStorage(address) // Update codeHash to `c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470` @@ -537,7 +543,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { } } - const stem = getStem(address, 0) + const stem = getStem(this.verkleCrypto, address, 0) const versionKey = this.getTreeKeyForVersion(stem) const balanceKey = this.getTreeKeyForBalance(stem) const nonceKey = this.getTreeKeyForNonce(stem) @@ -624,7 +630,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { } if (this._accountCacheSettings.deactivate) { - const stem = getStem(address, 0) + const stem = getStem(this.verkleCrypto, address, 0) const balanceKey = this.getTreeKeyForBalance(stem) const nonceKey = this.getTreeKeyForNonce(stem) const codeHashKey = this.getTreeKeyForCodeHash(stem) @@ -678,7 +684,8 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { throw new Error('Not implemented yet') } - async verifyProof(parentVerkleRoot: Uint8Array): Promise { + // TODO: Re-implement this method once we have working verifyUpdate and the testnets have been updated to provide ingestible data + async verifyProof(_: Uint8Array): Promise { // Implementation: https://github.com/crate-crypto/rust-verkle-wasm/blob/master/src/lib.rs#L45 // The root is the root of the current (un-updated) trie // The proof is proof of membership of all of the accessed values @@ -687,17 +694,19 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface { // // This function returns the new root when all of the updated values are applied - const updatedStateRoot: Uint8Array = verifyUpdate( - parentVerkleRoot, - this._proof!, // TODO: Convert this into a Uint8Array ingestible by the method - new Map() // TODO: Generate the keys_values map from the old to the updated value - ) + // const updatedStateRoot: Uint8Array = verifyUpdate( + // parentVerkleRoot, + // this._proof!, // TODO: Convert this into a Uint8Array ingestible by the method + // new Map() // TODO: Generate the keys_values map from the old to the updated value + // ) // TODO: Not sure if this should return the updated state Root (current block) or the un-updated one (parent block) - const verkleRoot = await this.getStateRoot() + // const verkleRoot = await this.getStateRoot() // Verify that updatedStateRoot matches the state root of the block - return equalsBytes(updatedStateRoot, verkleRoot) + // return equalsBytes(updatedStateRoot, verkleRoot) + + return true } // Verifies that the witness post-state matches the computed post-state diff --git a/packages/statemanager/test/statelessVerkleStateManager.spec.ts b/packages/statemanager/test/statelessVerkleStateManager.spec.ts index 75450d9c51a..8731e098f4f 100644 --- a/packages/statemanager/test/statelessVerkleStateManager.spec.ts +++ b/packages/statemanager/test/statelessVerkleStateManager.spec.ts @@ -1,5 +1,6 @@ import { Block } from '@ethereumjs/block' import { Common } from '@ethereumjs/common' +import { TransactionFactory } from '@ethereumjs/tx' import { Account, Address, @@ -9,37 +10,47 @@ import { randomBytes, } from '@ethereumjs/util' import { getStem } from '@ethereumjs/verkle' -import { assert, describe, it, test } from 'vitest' +import { loadVerkleCrypto } from 'verkle-cryptography-wasm' +import { assert, beforeAll, describe, it, test } from 'vitest' import { CacheType, StatelessVerkleStateManager } from '../src/index.js' import * as testnetVerkleKaustinen from './testdata/testnetVerkleKaustinen.json' import * as verkleBlockJSON from './testdata/verkleKaustinenBlock.json' +import type { VerkleCrypto } from '@ethereumjs/verkle' + describe('StatelessVerkleStateManager: Kaustinen Verkle Block', () => { + let verkleCrypto: VerkleCrypto + beforeAll(async () => { + verkleCrypto = await loadVerkleCrypto() + }) const common = Common.fromGethGenesis(testnetVerkleKaustinen, { chain: 'customChain', - eips: [6800], + eips: [4895, 6800], }) - const block = Block.fromBlockData(verkleBlockJSON, { common }) + const decodedTxs = verkleBlockJSON.transactions.map((tx) => + TransactionFactory.fromSerializedData(hexToBytes(tx)) + ) + const block = Block.fromBlockData({ ...verkleBlockJSON, transactions: decodedTxs }, { common }) it('initPreState()', async () => { - const stateManager = new StatelessVerkleStateManager() + const stateManager = await StatelessVerkleStateManager.create({ verkleCrypto }) stateManager.initVerkleExecutionWitness(block.header.number, block.executionWitness) assert.ok(Object.keys(stateManager['_state']).length !== 0, 'should initialize with state') }) it('getAccount()', async () => { - const stateManager = new StatelessVerkleStateManager({ common }) + const stateManager = await StatelessVerkleStateManager.create({ common, verkleCrypto }) stateManager.initVerkleExecutionWitness(block.header.number, block.executionWitness) const account = await stateManager.getAccount( - Address.fromString('0x9791ded6e5d3d5dafca71bb7bb2a14187d17e32e') + Address.fromString('0x6177843db3138ae69679a54b95cf345ed759450d') ) - assert.equal(account!.balance, 99765345920194942688594n, 'should have correct balance') - assert.equal(account!.nonce, 3963257n, 'should have correct nonce') + assert.equal(account!.balance, 241791330767054707n, 'should have correct balance') + assert.equal(account!.nonce, 200n, 'should have correct nonce') assert.equal(account!._storageRoot, null, 'stateroot should have not been set') assert.equal( bytesToHex(account!.codeHash), @@ -49,7 +60,7 @@ describe('StatelessVerkleStateManager: Kaustinen Verkle Block', () => { }) it('put/delete/modify account', async () => { - const stateManager = new StatelessVerkleStateManager({ common }) + const stateManager = await StatelessVerkleStateManager.create({ common, verkleCrypto }) stateManager.initVerkleExecutionWitness(block.header.number, block.executionWitness) const address = new Address(randomBytes(20)) @@ -95,11 +106,11 @@ describe('StatelessVerkleStateManager: Kaustinen Verkle Block', () => { }) it('getTreeKeyFor* functions', async () => { - const stateManager = new StatelessVerkleStateManager({ common }) + const stateManager = await StatelessVerkleStateManager.create({ common, verkleCrypto }) stateManager.initVerkleExecutionWitness(block.header.number, block.executionWitness) - const address = Address.fromString('0x9791ded6e5d3d5dafca71bb7bb2a14187d17e32e') - const stem = getStem(address, 0) + const address = Address.fromString('0x6177843db3138ae69679a54b95cf345ed759450d') + const stem = getStem(stateManager.verkleCrypto, address, 0n) const balanceKey = stateManager.getTreeKeyForBalance(stem) const nonceKey = stateManager.getTreeKeyForNonce(stem) @@ -133,10 +144,11 @@ describe('StatelessVerkleStateManager: Kaustinen Verkle Block', () => { type: CacheType.ORDERED_MAP, }, common, + verkleCrypto, }) stateManager.initVerkleExecutionWitness(block.header.number, block.executionWitness) - const stateManagerCopy = stateManager.shallowCopy() + const stateManagerCopy = stateManager.shallowCopy() as StatelessVerkleStateManager assert.equal( (stateManagerCopy as any)['_accountCacheSettings'].type, @@ -152,8 +164,8 @@ describe('StatelessVerkleStateManager: Kaustinen Verkle Block', () => { // TODO contract storage functions not yet completely implemented test.skip('get/put/clear contract storage', async () => { - const stateManager = new StatelessVerkleStateManager({ common }) - stateManager.initVerkleExecutionWitness(block.executionWitness) + const stateManager = await StatelessVerkleStateManager.create({ common, verkleCrypto }) + stateManager.initVerkleExecutionWitness(block.header.number, block.executionWitness) const contractAddress = Address.fromString('0x4242424242424242424242424242424242424242') const storageKey = '0x0000000000000000000000000000000000000000000000000000000000000022' diff --git a/packages/verkle/package.json b/packages/verkle/package.json index 2a7e028bc4e..561ae150db2 100644 --- a/packages/verkle/package.json +++ b/packages/verkle/package.json @@ -51,10 +51,10 @@ "tsc": "../../config/cli/ts-compile.sh" }, "dependencies": { + "lru-cache": "10.1.0", + "verkle-cryptography-wasm": "^0.4.0", "@ethereumjs/rlp": "^5.0.2", - "@ethereumjs/util": "^9.0.3", - "lru-cache": "^10.0.0", - "rust-verkle-wasm": "^0.0.1" + "@ethereumjs/util": "^9.0.3" }, "engines": { "node": ">=18" diff --git a/packages/verkle/src/node/baseVerkleNode.ts b/packages/verkle/src/node/baseVerkleNode.ts index 01aee3ebfe8..43edb150079 100644 --- a/packages/verkle/src/node/baseVerkleNode.ts +++ b/packages/verkle/src/node/baseVerkleNode.ts @@ -2,10 +2,8 @@ import { RLP } from '@ethereumjs/rlp' import { type VerkleNodeInterface, type VerkleNodeOptions, type VerkleNodeType } from './types.js' -import type { Point } from '../types.js' - export abstract class BaseVerkleNode implements VerkleNodeInterface { - public commitment: Point + public commitment: Uint8Array public depth: number constructor(options: VerkleNodeOptions[T]) { @@ -13,7 +11,7 @@ export abstract class BaseVerkleNode implements Verkle this.depth = options.depth } - abstract commit(): Point + abstract commit(): Uint8Array // Hash returns the field representation of the commitment. hash(): Uint8Array { diff --git a/packages/verkle/src/node/internalNode.ts b/packages/verkle/src/node/internalNode.ts index 9371744124d..06869e11723 100644 --- a/packages/verkle/src/node/internalNode.ts +++ b/packages/verkle/src/node/internalNode.ts @@ -6,13 +6,12 @@ import { BaseVerkleNode } from './baseVerkleNode.js' import { LeafNode } from './leafNode.js' import { NODE_WIDTH, VerkleNodeType } from './types.js' -import type { Point } from '../types.js' import type { VerkleNode, VerkleNodeOptions } from './types.js' export class InternalNode extends BaseVerkleNode { // Array of references to children nodes public children: Array - public copyOnWrite: Record + public copyOnWrite: Record public type = VerkleNodeType.Internal /* TODO: options.children is not actually used here */ @@ -22,7 +21,7 @@ export class InternalNode extends BaseVerkleNode { this.copyOnWrite = options.copyOnWrite ?? {} } - commit(): Point { + commit(): Uint8Array { throw new Error('Not implemented') } @@ -46,7 +45,7 @@ export class InternalNode extends BaseVerkleNode { } // TODO: Generate Point from rawNode value - const commitment = rawNode[rawNode.length - 1] as unknown as Point + const commitment = rawNode[rawNode.length - 1] return new InternalNode({ commitment, depth }) } diff --git a/packages/verkle/src/node/leafNode.ts b/packages/verkle/src/node/leafNode.ts index dd11308d82c..330fbf17c67 100644 --- a/packages/verkle/src/node/leafNode.ts +++ b/packages/verkle/src/node/leafNode.ts @@ -38,14 +38,14 @@ export class LeafNode extends BaseVerkleNode { const stem = rawNode[1] // TODO: Convert the rawNode commitments to points - const commitment = rawNode[2] as unknown as Point + const commitment = rawNode[2] const c1 = rawNode[3] as unknown as Point const c2 = rawNode[4] as unknown as Point const values = rawNode.slice(5, rawNode.length) return new LeafNode({ depth, stem, values, c1, c2, commitment }) } - commit(): Point { + commit(): Uint8Array { throw new Error('Not implemented') } @@ -72,7 +72,7 @@ export class LeafNode extends BaseVerkleNode { return [ new Uint8Array([VerkleNodeType.Leaf]), this.stem, - this.commitment.bytes(), + this.commitment, this.c1.bytes(), this.c2.bytes(), ...this.values, diff --git a/packages/verkle/src/node/types.ts b/packages/verkle/src/node/types.ts index ab956271f7f..4070d1e7ade 100644 --- a/packages/verkle/src/node/types.ts +++ b/packages/verkle/src/node/types.ts @@ -15,14 +15,14 @@ export interface TypedVerkleNode { export type VerkleNode = TypedVerkleNode[VerkleNodeType] export interface VerkleNodeInterface { - commit(): Point + commit(): Uint8Array hash(): any serialize(): Uint8Array } interface BaseVerkleNodeOptions { // Value of the commitment - commitment: Point + commitment: Uint8Array depth: number } @@ -32,7 +32,7 @@ interface VerkleInternalNodeOptions extends BaseVerkleNodeOptions { // Values of the child commitments before the tree is modified by inserts. // This is useful because the delta of the child commitments can be used to efficiently update the node's commitment - copyOnWrite?: Record + copyOnWrite?: Record } interface VerkleLeafNodeOptions extends BaseVerkleNodeOptions { stem: Uint8Array diff --git a/packages/verkle/src/types.ts b/packages/verkle/src/types.ts index 596b5ad0ec7..63e7f18f86d 100644 --- a/packages/verkle/src/types.ts +++ b/packages/verkle/src/types.ts @@ -3,6 +3,7 @@ import { utf8ToBytes } from '@ethereumjs/util' import type { VerkleNode } from './node/index.js' import type { WalkController } from './util/walkController.js' import type { DB } from '@ethereumjs/util' +import type { VerkleCrypto as VerkleFFI } from 'verkle-cryptography-wasm' // Field representation of a commitment export interface Fr {} @@ -61,6 +62,10 @@ export interface Point { export type Proof = Uint8Array[] export interface VerkleTreeOpts { + /** + * An instantiated Verkle Cryptography interface + */ + verkleCrypto: any /** * A database instance. */ @@ -116,3 +121,5 @@ export type FoundNodeFunction = ( ) => void export const ROOT_DB_KEY = utf8ToBytes('__root__') + +export type VerkleCrypto = VerkleFFI diff --git a/packages/verkle/src/util/crypto.ts b/packages/verkle/src/util/crypto.ts index ca5aa03b634..b64bfec9c62 100644 --- a/packages/verkle/src/util/crypto.ts +++ b/packages/verkle/src/util/crypto.ts @@ -1,46 +1,27 @@ import { type Address, bigIntToBytes, - bytesToHex, concatBytes, int32ToBytes, setLengthLeft, setLengthRight, } from '@ethereumjs/util' -import { pedersen_hash, verify_update } from 'rust-verkle-wasm' -import type { Point } from '../types.js' - -export function pedersenHash(input: Uint8Array): Uint8Array { - const pedersenHash = pedersen_hash(input) - - if (pedersenHash === null) { - throw new Error( - `pedersenHash: Wrong pedersenHash input: ${bytesToHex( - input - )}. This might happen if length is not correct.` - ) - } - - return pedersenHash -} - -export function verifyUpdate( - root: Uint8Array, - proof: Uint8Array, - keyValues: Map -): Uint8Array { - return verify_update(root, proof, keyValues) -} +import type { VerkleCrypto } from 'verkle-cryptography-wasm' /** * @dev Returns the 31-bytes verkle tree stem for a given address and tree index. * @dev Assumes that the verkle node width = 256 + * @param ffi The verkle ffi object from verkle-crypotography-wasm. * @param address The address to generate the tree key for. * @param treeIndex The index of the tree to generate the key for. Defaults to 0. * @return The 31-bytes verkle tree stem as a Uint8Array. */ -export function getStem(address: Address, treeIndex: number | bigint = 0): Uint8Array { +export function getStem( + ffi: VerkleCrypto, + address: Address, + treeIndex: number | bigint = 0 +): Uint8Array { const address32 = setLengthLeft(address.toBytes(), 32) let treeIndexBytes: Uint8Array @@ -50,8 +31,7 @@ export function getStem(address: Address, treeIndex: number | bigint = 0): Uint8 treeIndexBytes = setLengthRight(bigIntToBytes(BigInt(treeIndex), true).slice(0, 32), 32) } - const input = concatBytes(address32, treeIndexBytes) - const treeStem = pedersenHash(input).slice(0, 31) + const treeStem = ffi.getTreeKey(address32, treeIndexBytes, 0).slice(0, 31) return treeStem } @@ -69,13 +49,4 @@ export function getKey(stem: Uint8Array, subIndex: Uint8Array): Uint8Array { return treeKey } -export function verifyProof( - root: Uint8Array, - proof: Uint8Array, - keyValues: Map -): Uint8Array { - return verify_update(root, proof, keyValues) -} - -// TODO: Replace this by the actual value of Point().Identity() from the Go code. -export const POINT_IDENTITY = new Uint8Array(32).fill(0) as unknown as Point +export const POINT_IDENTITY = new Uint8Array(0) diff --git a/packages/verkle/src/verkleTree.ts b/packages/verkle/src/verkleTree.ts index b756262f6a6..261865903df 100644 --- a/packages/verkle/src/verkleTree.ts +++ b/packages/verkle/src/verkleTree.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { KeyEncoding, Lock, ValueEncoding, equalsBytes, zeros } from '@ethereumjs/util' +import { loadVerkleCrypto } from 'verkle-cryptography-wasm' import { CheckpointDB } from './db/checkpoint.js' import { InternalNode } from './node/internalNode.js' @@ -30,6 +31,7 @@ export class VerkleTree { protected readonly _opts: VerkleTreeOptsWithDefaults = { useRootPersistence: false, cacheSize: 0, + verkleCrypto: undefined, } /** The root for an empty tree */ @@ -41,6 +43,7 @@ export class VerkleTree { protected _lock = new Lock() protected _root: Uint8Array + protected verkleCrypto: any /** * Creates a new verkle tree. * @param opts Options for instantiating the verkle tree @@ -61,6 +64,12 @@ export class VerkleTree { if (opts?.root) { this.root(opts.root) } + + if (opts === undefined || opts?.verkleCrypto === undefined) { + throw new Error('instantiated verkle cryptography option required for verkle tries') + } + + this.verkleCrypto = opts?.verkleCrypto } static async create(opts?: VerkleTreeOpts) { @@ -80,6 +89,13 @@ export class VerkleTree { } } + if (opts === undefined) { + opts = { + verkleCrypto: loadVerkleCrypto, + } + } + opts.verkleCrypto = await loadVerkleCrypto() + return new VerkleTree(opts) } @@ -425,6 +441,7 @@ export class VerkleTree { db: this._db.db.shallowCopy(), root: this.root(), cacheSize: 0, + verkleCrypto: this.verkleCrypto, }) if (includeCheckpoints && this.hasCheckpoints()) { tree._db.setCheckpoints(this._db.checkpoints) diff --git a/packages/verkle/test/crypto.spec.ts b/packages/verkle/test/crypto.spec.ts index b1448edac26..604fd82d22a 100644 --- a/packages/verkle/test/crypto.spec.ts +++ b/packages/verkle/test/crypto.spec.ts @@ -1,20 +1,28 @@ import { Address, bytesToHex } from '@ethereumjs/util' -import { assert, describe, it } from 'vitest' +import { loadVerkleCrypto } from 'verkle-cryptography-wasm' +import { assert, beforeAll, describe, it } from 'vitest' import { getStem } from '../src/index.js' +import type { VerkleCrypto } from '../src/index.js' + describe('Verkle cryptographic helpers', () => { - it('getStem(): returns the expected stems', async () => { + let verkle: VerkleCrypto + beforeAll(async () => { + verkle = await loadVerkleCrypto() + }) + + it('getStem(): returns the expected stems', () => { // Empty address assert.equal( - bytesToHex(getStem(Address.fromString('0x0000000000000000000000000000000000000000'))), - '0xbf101a6e1c8e83c11bd203a582c7981b91097ec55cbd344ce09005c1f26d19' + bytesToHex(getStem(verkle, Address.fromString('0x0000000000000000000000000000000000000000'))), + '0x1a100684fd68185060405f3f160e4bb6e034194336b547bdae323f888d5332' ) // Non-empty address assert.equal( - bytesToHex(getStem(Address.fromString('0x71562b71999873DB5b286dF957af199Ec94617f7'))), - '0x274cde18dd9dbb04caf16ad5ee969c19fe6ca764d5688b5e1d419f4ac6cd16' + bytesToHex(getStem(verkle, Address.fromString('0x71562b71999873DB5b286dF957af199Ec94617f7'))), + '0x1540dfad7755b40be0768c6aa0a5096fbf0215e0e8cf354dd928a178346466' ) }) })