From d70aee59edf47b3cd029a74e08bbd291e75f15ba Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Mon, 9 Sep 2024 23:33:55 +0100 Subject: [PATCH 1/3] add ProverInput and VerifierInput abstraction --- src.ts/index.ts | 23 ++++++--- src.ts/verkleFFIBindings/index.ts | 20 ++++++-- src.ts/verkleFFIBindings/verkleFFI.ts | 67 +++++++++++++++++++++++++-- 3 files changed, 96 insertions(+), 14 deletions(-) diff --git a/src.ts/index.ts b/src.ts/index.ts index bb8ced7..41083be 100644 --- a/src.ts/index.ts +++ b/src.ts/index.ts @@ -7,6 +7,8 @@ import { verifyExecutionWitnessPreState as verifyExecutionWitnessPreStateBase, createProof as createProofBase, verifyProof as verifyProofBase, + ProverInput as ProverInputBase, + VerifierInput as VerifierInputBase, } from './verkleFFIBindings/index.js' import { Context as VerkleFFI } from './wasm/rust_verkle_wasm.js' @@ -29,15 +31,18 @@ export const loadVerkleCrypto = async () => { ): Commitment => updateCommitmentBase(verkleFFI, commitment, commitmentIndex, oldScalarValue, newScalarValue) - const verifyExecutionWitnessPreState = (prestateRoot: string, execution_witness_json: string): boolean => - verifyExecutionWitnessPreStateBase(prestateRoot, execution_witness_json) + const verifyExecutionWitnessPreState = ( + prestateRoot: string, + execution_witness_json: string, + ): boolean => verifyExecutionWitnessPreStateBase(prestateRoot, execution_witness_json) const zeroCommitment = zeroCommitmentBase() const hashCommitment = (commitment: Uint8Array) => verkleFFI.hashCommitment(commitment) const serializeCommitment = (commitment: Uint8Array) => verkleFFI.serializeCommitment(commitment) - const createProof = (input: Uint8Array) => verkleFFI.createProof(input) - const verifyProof = (proofInput: Uint8Array) => verkleFFI.verifyProof(proofInput) + const createProof = (proverInputs: ProverInput[]) => createProofBase(verkleFFI, proverInputs) + const verifyProof = (proof: Uint8Array, verifierInputs: VerifierInput[]) => + verifyProofBase(verkleFFI, proof, verifierInputs) return { getTreeKey, getTreeKeyHash, @@ -47,13 +52,19 @@ export const loadVerkleCrypto = async () => { hashCommitment, serializeCommitment, createProof, - verifyProof + verifyProof, } } +// Input used to create proofs over vectors +export type ProverInput = ProverInputBase +// Input needed to verify proofs over vectors +// alongside the proof. +export type VerifierInput = VerifierInputBase + // This is a 32 byte serialized field element export type Scalar = Uint8Array // This is a 64 byte serialized point. // It is 64 bytes because the point is serialized in uncompressed format. -export type Commitment = Uint8Array \ No newline at end of file +export type Commitment = Uint8Array diff --git a/src.ts/verkleFFIBindings/index.ts b/src.ts/verkleFFIBindings/index.ts index 256f9e1..fa61d8b 100644 --- a/src.ts/verkleFFIBindings/index.ts +++ b/src.ts/verkleFFIBindings/index.ts @@ -1,6 +1,18 @@ -import { initVerkleWasm, zeroCommitment, verifyExecutionWitnessPreState } from '../wasm/rust_verkle_wasm.js' +import { + initVerkleWasm, + zeroCommitment, + verifyExecutionWitnessPreState, +} from '../wasm/rust_verkle_wasm.js' -import { getTreeKey, getTreeKeyHash, updateCommitment, createProof, verifyProof } from './verkleFFI.js' +import { + getTreeKey, + getTreeKeyHash, + updateCommitment, + createProof, + verifyProof, + ProverInput, + VerifierInput, +} from './verkleFFI.js' export { initVerkleWasm, @@ -10,5 +22,7 @@ export { zeroCommitment, verifyExecutionWitnessPreState, createProof, - verifyProof + verifyProof, + ProverInput, + VerifierInput, } diff --git a/src.ts/verkleFFIBindings/verkleFFI.ts b/src.ts/verkleFFIBindings/verkleFFI.ts index d855c62..41048b3 100644 --- a/src.ts/verkleFFIBindings/verkleFFI.ts +++ b/src.ts/verkleFFIBindings/verkleFFI.ts @@ -1,3 +1,5 @@ +import { concatBytes } from '@ethereumjs/util' + import { Context as VerkleFFI } from '../wasm/rust_verkle_wasm.js' // This is equal to 2n + 256n * 64n. @@ -114,10 +116,65 @@ export function updateCommitment( return verkleFFI.updateCommitment(commitment, commitmentIndex, oldScalarValue, newScalarValue) } -export function createProof(verkleFFI: VerkleFFI, bytes: Uint8Array): Uint8Array { - return verkleFFI.createProof(bytes) +export function createProof(verkleFFI: VerkleFFI, proverInputs: ProverInput[]): Uint8Array { + const serializedProofInputs = serializedProverInputs(proverInputs) + return verkleFFI.createProof(serializedProofInputs) +} + +export function verifyProof( + verkleFFI: VerkleFFI, + proof: Uint8Array, + verifierInputs: VerifierInput[], +): boolean { + const serializedVerifierInput = serializeVerifierInputs(proof, verifierInputs) + return verkleFFI.verifyProof(serializedVerifierInput) +} + +export interface ProverInput { + // Commitment to the vector we want to create a proof for + serializedCommitment: Uint8Array + // The vector that we want to make proofs over + vector: Uint8Array[] + // The indices that we want to prove exist in the vector + indices: number[] } -export function verifyProof(verkleFFI: VerkleFFI, proof: Uint8Array): boolean { - return verkleFFI.verifyProof(proof) -} \ No newline at end of file +function serializedProverInputs(proofInputs: ProverInput[]): Uint8Array { + const serializedProverInputs = proofInputs.flatMap(({ serializedCommitment, vector, indices }) => + indices.flatMap((index) => [ + serializedCommitment, + ...vector, + new Uint8Array([index]), + vector[index], + ]), + ) + + return concatBytes(...serializedProverInputs) +} + +export interface VerifierInput { + // A commitment to the vector that we want to verify + // proofs over. + serializedCommitment: Uint8Array + // A tuple of index and values that we want to verify about the + // committed vector. + // + // ie (index_i, value_i) will verify that the value of the committed + // vector at index `index_i` was `value_i` + indexValuePairs: Array<{ index: number; value: Uint8Array }> +} + +function serializeVerifierInputs(proof: Uint8Array, verifierInputs: VerifierInput[]): Uint8Array { + const serializedVerifierInputs = [ + proof, + ...verifierInputs.flatMap(({ serializedCommitment, indexValuePairs }) => + indexValuePairs.flatMap(({ index, value }) => [ + serializedCommitment, + new Uint8Array([index]), + value, + ]), + ), + ] + + return concatBytes(...serializedVerifierInputs) +} From 502d77cf1d2d47a543c7971e1c3ee6e6d5f26072 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Mon, 9 Sep 2024 23:35:06 +0100 Subject: [PATCH 2/3] add tests --- src.ts/tests/ffi.spec.ts | 104 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/src.ts/tests/ffi.spec.ts b/src.ts/tests/ffi.spec.ts index 8e08e14..e38dd90 100644 --- a/src.ts/tests/ffi.spec.ts +++ b/src.ts/tests/ffi.spec.ts @@ -1,8 +1,9 @@ import { bytesToHex, randomBytes } from '@ethereumjs/util' import { beforeAll, describe, expect, test, assert } from 'vitest' -import { VerkleCrypto, loadVerkleCrypto } from '../index.js' +import { ProverInput, VerifierInput, VerkleCrypto, loadVerkleCrypto } from '../index.js' import { verifyExecutionWitnessPreState, Context as VerkleFFI } from '../wasm/rust_verkle_wasm.js' + import kaustinenBlock72 from './data/kaustinen6Block72.json' import kaustinenBlock73 from './data/kaustinen6Block73.json' @@ -138,6 +139,99 @@ describe('bindings', () => { } }) + test('createVerifyProof', () => { + // Preparation stage + // + // First we will emulate having a node with children. + // + // In verkle, the maximum number of children an internal node can have is 256. + const NUMBER_OF_CHILDREN = 256 + + // Populate the vector with 256 values. + const children = [] + for (let i = 0; i < NUMBER_OF_CHILDREN; i++) { + children.push(createScalarFromIndex(i)) + } + + // Commit to that vector/children + const commitment = ffi.commitToScalars(children) + + // Serialize the commitment + // + // This is the format that they should arrive in, over the wire + const serializedCommitment = ffi.serializeCommitment(commitment) + + // Create proof + // + const proofInputs: ProverInput[] = [ + { serializedCommitment: serializedCommitment, vector: children, indices: [1, 2] }, + ] + + const proof = verkleCrypto.createProof(proofInputs) + + // Verify proof + // + const verifierInputs: VerifierInput[] = [ + { + serializedCommitment: serializedCommitment, + indexValuePairs: [ + { index: 1, value: children[1] }, + { index: 2, value: children[2] }, + ], + }, + ] + + const valid = verkleCrypto.verifyProof(proof, verifierInputs) + + expect(valid).toBe(true) + }) + + test('createVerifyProofMultipleCommitments', () => { + const NUMBER_OF_CHILDREN = 256 + + // Create two sets of vectors + const children1 = Array.from({ length: NUMBER_OF_CHILDREN }, (_, i) => createScalarFromIndex(i)) + const children2 = Array.from({ length: NUMBER_OF_CHILDREN }, (_, i) => + createScalarFromIndex(i + NUMBER_OF_CHILDREN), + ) + + // Create commitments for both sets + const commitment1 = ffi.commitToScalars(children1) + const commitment2 = ffi.commitToScalars(children2) + + // Serialize the commitments + const serializedCommitment1 = verkleCrypto.serializeCommitment(commitment1) + const serializedCommitment2 = verkleCrypto.serializeCommitment(commitment2) + + // Create proof + const proofInputs: ProverInput[] = [ + { serializedCommitment: serializedCommitment1, vector: children1, indices: [1, 2] }, + { serializedCommitment: serializedCommitment2, vector: children2, indices: [3, 4] }, + ] + const proof = verkleCrypto.createProof(proofInputs) + + // Verify proof + const verifierInputs: VerifierInput[] = [ + { + serializedCommitment: serializedCommitment1, + indexValuePairs: [ + { index: 1, value: children1[1] }, + { index: 2, value: children1[2] }, + ], + }, + { + serializedCommitment: serializedCommitment2, + indexValuePairs: [ + { index: 3, value: children2[3] }, + { index: 4, value: children2[4] }, + ], + }, + ] + const valid = verkleCrypto.verifyProof(proof, verifierInputs) + + expect(valid).toBe(true) + }) + test('serializeCommitment', () => { // Create a commitment that we can hash const scalar = new Uint8Array([ @@ -229,3 +323,11 @@ describe('bindings', () => { }).toThrow('Expected 32 bytes, got 1') }) }) + +function createScalarFromIndex(index: number): Uint8Array { + const BYTES_PER_SCALAR = 32 + + const scalar = new Uint8Array(BYTES_PER_SCALAR) + scalar[0] = index // Set first byte to index (little-endian) + return scalar +} From a7c313875030c3af213d6bf71f2e66fdd4455354 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:03:39 -0400 Subject: [PATCH 3/3] Fix export typing --- src.rs/src/verkle_ffi_api.rs | 2 +- src.ts/index.ts | 4 ++-- src.ts/tests/ffi.spec.ts | 4 ++-- src.ts/verkleFFIBindings/index.ts | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src.rs/src/verkle_ffi_api.rs b/src.rs/src/verkle_ffi_api.rs index b9df7f8..590c8e0 100644 --- a/src.rs/src/verkle_ffi_api.rs +++ b/src.rs/src/verkle_ffi_api.rs @@ -219,7 +219,7 @@ impl Context { { let input_bytes = serde_wasm_bindgen::from_value(input.into()).unwrap(); let result = ffi_interface::verify_proof(&self.inner, input_bytes).map(|_op |Boolean::from(true)) - .map_err(|err| JsError::new(&format!("proof verification failed]: {:?}", err))); + .map_err(|err| JsError::new(&format!("proof verification failed: {:?}", err))); return result } } diff --git a/src.ts/index.ts b/src.ts/index.ts index 41083be..55f17fd 100644 --- a/src.ts/index.ts +++ b/src.ts/index.ts @@ -7,8 +7,8 @@ import { verifyExecutionWitnessPreState as verifyExecutionWitnessPreStateBase, createProof as createProofBase, verifyProof as verifyProofBase, - ProverInput as ProverInputBase, - VerifierInput as VerifierInputBase, + type ProverInput as ProverInputBase, + type VerifierInput as VerifierInputBase, } from './verkleFFIBindings/index.js' import { Context as VerkleFFI } from './wasm/rust_verkle_wasm.js' diff --git a/src.ts/tests/ffi.spec.ts b/src.ts/tests/ffi.spec.ts index e38dd90..abbebd5 100644 --- a/src.ts/tests/ffi.spec.ts +++ b/src.ts/tests/ffi.spec.ts @@ -1,7 +1,7 @@ import { bytesToHex, randomBytes } from '@ethereumjs/util' import { beforeAll, describe, expect, test, assert } from 'vitest' -import { ProverInput, VerifierInput, VerkleCrypto, loadVerkleCrypto } from '../index.js' +import { ProverInput, VerifierInput, loadVerkleCrypto } from '../index.js' import { verifyExecutionWitnessPreState, Context as VerkleFFI } from '../wasm/rust_verkle_wasm.js' import kaustinenBlock72 from './data/kaustinen6Block72.json' @@ -9,7 +9,7 @@ import kaustinenBlock73 from './data/kaustinen6Block73.json' describe('bindings', () => { let ffi: VerkleFFI - let verkleCrypto: VerkleCrypto + let verkleCrypto: Awaited> beforeAll(async () => { verkleCrypto = await loadVerkleCrypto() ffi = new VerkleFFI() diff --git a/src.ts/verkleFFIBindings/index.ts b/src.ts/verkleFFIBindings/index.ts index fa61d8b..549b18a 100644 --- a/src.ts/verkleFFIBindings/index.ts +++ b/src.ts/verkleFFIBindings/index.ts @@ -10,8 +10,8 @@ import { updateCommitment, createProof, verifyProof, - ProverInput, - VerifierInput, + type ProverInput, + type VerifierInput, } from './verkleFFI.js' export { @@ -23,6 +23,6 @@ export { verifyExecutionWitnessPreState, createProof, verifyProof, - ProverInput, - VerifierInput, + type ProverInput, + type VerifierInput, }