Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

verkle: implement verkle proof verification #3423

Merged
merged 20 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
249dc9b
statemanager: proper type for verkleproof
gabrocheleau May 14, 2024
68c9b2e
verkle: add verifyProof wrapper in verkle crypto
gabrocheleau May 14, 2024
15ab35f
verkle: remove unused import
gabrocheleau May 14, 2024
1f9fdb0
chore: update package lock
gabrocheleau May 14, 2024
747669d
statemanageR: add verifyProof implementation to stateless verkle stat…
gabrocheleau May 14, 2024
f352cca
verkle: add jsdoc for verkle verifyProof method
gabrocheleau May 14, 2024
04a0cb5
verkle: verkle proof test
gabrocheleau May 14, 2024
c541b55
chore: fix merge conflicts
gabrocheleau May 14, 2024
601ec88
Merge branch 'master' into verkle/verify-proof
gabrocheleau May 14, 2024
5851892
verkle: add failing test case
gabrocheleau May 14, 2024
15eed33
Merge branch 'verkle/verify-proof' of https://github.com/ethereumjs/e…
gabrocheleau May 14, 2024
fa8470a
client: add the ability to provide and use the parentStateRoot
gabrocheleau May 15, 2024
81fc963
Update verkle crypto dep
acolytec3 May 15, 2024
6091bc5
Activate invalid proof test
acolytec3 May 15, 2024
8170563
src: cleanup
gabrocheleau May 15, 2024
f85600b
Merge branch 'verkle/verify-proof' of https://github.com/ethereumjs/e…
gabrocheleau May 15, 2024
a933deb
Update packages/client/src/config.ts
gabrocheleau May 16, 2024
2793134
Merge branch 'master' into verkle/verify-proof
gabrocheleau May 16, 2024
a3c7bd4
vm: move up error check
gabrocheleau May 17, 2024
c592469
Merge branch 'verkle/verify-proof' of https://github.com/ethereumjs/e…
gabrocheleau May 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions packages/client/bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,12 @@ const args: ClientOpts = yargs
boolean: true,
hidden: true,
})
.option('initialVerkleStateRoot', {
describe:
'Provides an initial stateRoot to start the StatelessVerkleStateManager. This is required to bootstrap verkle witness proof verification, since they depend on the stateRoot of the parent block',
string: true,
coerce: (initialVerkleStateRoot: PrefixedHexString) => hexToBytes(initialVerkleStateRoot),
})
.option('useJsCrypto', {
describe: 'Use pure Javascript cryptography functions',
boolean: true,
Expand Down
3 changes: 3 additions & 0 deletions packages/client/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ export interface ConfigOptions {
statelessVerkle?: boolean
startExecution?: boolean
ignoreStatelessInvalidExecs?: boolean
initialVerkleStateRoot?: Uint8Array

/**
* Enables Prometheus Metrics that can be collected for monitoring client health
Expand Down Expand Up @@ -451,6 +452,7 @@ export class Config {
public readonly statelessVerkle: boolean
public readonly startExecution: boolean
public readonly ignoreStatelessInvalidExecs: boolean
public readonly initialVerkleStateRoot: Uint8Array

public synchronized: boolean
public lastsyncronized?: boolean
Expand Down Expand Up @@ -544,6 +546,7 @@ export class Config {
this.ignoreStatelessInvalidExecs = options.ignoreStatelessInvalidExecs ?? false

this.metrics = options.prometheusMetrics
this.initialVerkleStateRoot = options.initialVerkleStateRoot ?? new Uint8Array()

// Start it off as synchronized if this is configured to mine or as single node
this.synchronized = this.isSingleNode ?? this.mine
Expand Down
6 changes: 5 additions & 1 deletion packages/client/src/execution/vmexecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,9 @@
return
}
this.config.logger.info(`Setting up verkleVM`)
const stateManager = await StatelessVerkleStateManager.create()
const stateManager = await StatelessVerkleStateManager.create({
initialStateRoot: this.config.initialVerkleStateRoot,
})
this.verkleVM = await VM.create({
common: this.config.execCommon,
blockchain: this.chain.blockchain,
Expand Down Expand Up @@ -439,6 +441,7 @@
const result = await vm.runBlock({
clearCache,
...opts,
parentStateRoot: prevVMStateRoot,
skipHeaderValidation,
reportPreimages,
})
Expand Down Expand Up @@ -733,6 +736,7 @@
skipBlockValidation,
skipHeaderValidation: true,
reportPreimages: this.config.savePreimages,
parentStateRoot: parentState,

Check warning on line 739 in packages/client/src/execution/vmexecution.ts

View check run for this annotation

Codecov / codecov/patch

packages/client/src/execution/vmexecution.ts#L739

Added line #L739 was not covered by tests
})
const afterTS = Date.now()
const diffSec = Math.round((afterTS - beforeTS) / 1000)
Expand Down
2 changes: 1 addition & 1 deletion packages/statemanager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"ethereum-cryptography": "^2.1.3",
"js-sdsl": "^4.1.4",
"lru-cache": "10.1.0",
"verkle-cryptography-wasm": "^0.4.1"
"verkle-cryptography-wasm": "^0.4.2"
},
"devDependencies": {
"@ethereumjs/block": "^5.2.0",
Expand Down
70 changes: 34 additions & 36 deletions packages/statemanager/src/statelessVerkleStateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
getStem,
getTreeKeyForCodeChunk,
getTreeKeyForStorageSlot,
verifyProof,
} from '@ethereumjs/verkle'
import debugDefault from 'debug'
import { keccak256 } from 'ethereum-cryptography/keccak.js'
Expand All @@ -30,7 +31,7 @@ import { OriginalStorageCache } from './cache/originalStorageCache.js'

import type { AccessedStateWithAddress } from './accessWitness.js'
import type { DefaultStateManager } from './stateManager.js'
import type { VerkleExecutionWitness } from '@ethereumjs/block'
import type { VerkleExecutionWitness, VerkleProof } from '@ethereumjs/block'
import type {
AccountFields,
Common,
Expand Down Expand Up @@ -110,6 +111,7 @@ export interface StatelessVerkleStateManagerOpts {
codeCacheOpts?: CacheOptions
accesses?: AccessWitness
verkleCrypto?: VerkleCrypto
initialStateRoot?: Uint8Array
}

const PUSH_OFFSET = 95
Expand All @@ -135,6 +137,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface {
_accountCache?: AccountCache
_storageCache?: StorageCache
_codeCache?: CodeCache
_cachedStateRoot?: Uint8Array

originalStorageCache: OriginalStorageCache

Expand All @@ -156,7 +159,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface {
private _blockNum = BigInt(0)
private _executionWitness?: VerkleExecutionWitness

private _proof: Uint8Array | undefined
private _proof: VerkleProof | undefined

// State along execution (should update)
private _state: VerkleState = {}
Expand Down Expand Up @@ -229,6 +232,8 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface {
})
}

this._cachedStateRoot = opts.initialStateRoot

this.keccakFunction = opts.common?.customCrypto.keccak256 ?? keccak256

if (opts.verkleCrypto === undefined) {
Expand Down Expand Up @@ -261,7 +266,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface {
this._executionWitness = executionWitness
this.accessWitness = accessWitness ?? new AccessWitness({ verkleCrypto: this.verkleCrypto })

this._proof = executionWitness.verkleProof as unknown as Uint8Array
this._proof = executionWitness.verkleProof

// Populate the pre-state and post-state from the executionWitness
const preStateRaw = executionWitness.stateDiff.flatMap(({ stem, suffixDiffs }) => {
Expand Down Expand Up @@ -493,7 +498,7 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface {
}
}

// Note from Gabriel: This is actually not possible in Verkle.
// Note from Gabriel: Clearing storage is not actually not possible in Verkle.
// This is because the storage keys are scattered throughout the verkle tree.
/**
* Clears all storage entries for the account corresponding to `address`.
Expand Down Expand Up @@ -657,30 +662,18 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface {
getProof(_: Address, __: Uint8Array[] = []): Promise<Proof> {
throw new Error('Not implemented yet')
}
/**
* Verifies whether the execution witness matches the stateRoot
* @param {Uint8Array} stateRoot - The stateRoot to verify the executionWitness against
* @returns {boolean} - Returns true if the executionWitness matches the provided stateRoot, otherwise false
*/
verifyProof(stateRoot: Uint8Array): boolean {
if (this._executionWitness === undefined) {
debug('Missing executionWitness')
return false
}

// TODO: Re-implement this method once we have working verifyUpdate and the testnets have been updated to provide ingestible data
async verifyProof(_: Uint8Array): Promise<boolean> {
// 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
// keys_values is a map from the key of the accessed value to a tuple
// the tuple contains the old value and the updated value
//
// 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
// )

// 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()

// Verify that updatedStateRoot matches the state root of the block
// return equalsBytes(updatedStateRoot, verkleRoot)

return true
return verifyProof(this.verkleCrypto, stateRoot, this._executionWitness)
}

// Verifies that the witness post-state matches the computed post-state
Expand Down Expand Up @@ -903,21 +896,26 @@ export class StatelessVerkleStateManager implements EVMStateManagerInterface {
async flush(): Promise<void> {}

/**
* Gets the verkle root.
* NOTE: this needs some examination in the code where this is needed
* and if we have the verkle root present
* @returns {Promise<Uint8Array>} - Returns the verkle root of the `StateManager`
* Gets the cache state root.
* This is used to persist the stateRoot between blocks, so that blocks can retrieve the stateRoot of the parent block.
* This is required to verify and prove verkle execution witnesses.
* @returns {Promise<Uint8Array>} - Returns the cached state root
*/
async getStateRoot(): Promise<Uint8Array> {
return new Uint8Array(0)
if (this._cachedStateRoot === undefined) {
throw new Error('Cache state root missing')
}
return this._cachedStateRoot
}

/**
* TODO: needed?
* Maybe in this context: reset to original pre state suffice
* @param stateRoot - The verkle root to reset the instance to
* Sets the cache state root.
* This is used to persist the stateRoot between blocks, so that blocks can retrieve the stateRoot of the parent block.
* @param stateRoot - The stateRoot to set
*/
async setStateRoot(_: Uint8Array): Promise<void> {}
async setStateRoot(stateRoot: Uint8Array): Promise<void> {
this._cachedStateRoot = stateRoot
}

/**
* Dumps the RLP-encoded storage values for an `account` specified by `address`.
Expand Down
3 changes: 2 additions & 1 deletion packages/verkle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
},
"dependencies": {
"lru-cache": "10.1.0",
"verkle-cryptography-wasm": "^0.4.1",
"verkle-cryptography-wasm": "^0.4.2",
"@ethereumjs/block": "^5.2.0",
"@ethereumjs/rlp": "^5.0.2",
"@ethereumjs/util": "^9.0.3"
},
Expand Down
20 changes: 20 additions & 0 deletions packages/verkle/src/util/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import {
type Address,
bigIntToBytes,
bytesToHex,
int32ToBytes,
setLengthLeft,
setLengthRight,
} from '@ethereumjs/util'

import type { VerkleExecutionWitness } from '@ethereumjs/block'
import type { VerkleCrypto } from 'verkle-cryptography-wasm'

/**
Expand Down Expand Up @@ -35,4 +37,22 @@ export function getStem(
return treeStem
}

/**
* Verifies that the executionWitness is valid for the given prestateRoot.
* @param ffi The verkle ffi object from verkle-crypotography-wasm.
* @param prestateRoot The prestateRoot matching the executionWitness.
* @param executionWitness The verkle execution witness.
* @returns {boolean} Whether or not the executionWitness belongs to the prestateRoot.
*/
export function verifyProof(
ffi: VerkleCrypto,
prestateRoot: Uint8Array,
executionWitness: VerkleExecutionWitness
): boolean {
return ffi.verifyExecutionWitnessPreState(
bytesToHex(prestateRoot),
JSON.stringify(executionWitness)
)
}

export const POINT_IDENTITY = new Uint8Array(0)
23 changes: 21 additions & 2 deletions packages/verkle/test/crypto.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Address, bytesToHex } from '@ethereumjs/util'
import { Address, bytesToHex, hexToBytes, randomBytes } from '@ethereumjs/util'
import { loadVerkleCrypto } from 'verkle-cryptography-wasm'
import { assert, beforeAll, describe, it } from 'vitest'

import { getStem } from '../src/index.js'
import * as verkleBlockJSON from '../../statemanager/test/testdata/verkleKaustinen6Block72.json'
import { getStem, verifyProof } from '../src/index.js'

import type { VerkleCrypto } from '../src/index.js'
import type { VerkleExecutionWitness } from '@ethereumjs/block'

describe('Verkle cryptographic helpers', () => {
let verkle: VerkleCrypto
Expand All @@ -25,4 +27,21 @@ describe('Verkle cryptographic helpers', () => {
'0x1540dfad7755b40be0768c6aa0a5096fbf0215e0e8cf354dd928a178346466'
)
})

it('verifyProof(): should verify verkle proofs', () => {
// Src: Kaustinen6 testnet, block 71 state root (parent of block 72)
const prestateRoot = hexToBytes(
'0x64e1a647f42e5c2e3c434531ccf529e1b3e93363a40db9fc8eec81f492123510'
)
const executionWitness = verkleBlockJSON.executionWitness as VerkleExecutionWitness
assert.isTrue(verifyProof(verkle, prestateRoot, executionWitness))
})

it('verifyProof(): should return false for invalid verkle proofs', () => {
// Random preStateRoot
const prestateRoot = randomBytes(32)
const executionWitness = verkleBlockJSON.executionWitness as VerkleExecutionWitness
// Modify the proof to make it invalid
assert.isFalse(verifyProof(verkle, prestateRoot, executionWitness))
})
})
Loading
Loading