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

chore!: Add tests for createProof and verifyProof alongside an easier to use abstraction #58

Merged
merged 3 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src.rs/src/verkle_ffi_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
23 changes: 17 additions & 6 deletions src.ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
verifyExecutionWitnessPreState as verifyExecutionWitnessPreStateBase,
createProof as createProofBase,
verifyProof as verifyProofBase,
type ProverInput as ProverInputBase,
type VerifierInput as VerifierInputBase,
} from './verkleFFIBindings/index.js'
import { Context as VerkleFFI } from './wasm/rust_verkle_wasm.js'

Expand All @@ -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,
Expand All @@ -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
export type Commitment = Uint8Array
106 changes: 104 additions & 2 deletions src.ts/tests/ffi.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { bytesToHex, randomBytes } from '@ethereumjs/util'
import { beforeAll, describe, expect, test, assert } from 'vitest'

import { 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'
import kaustinenBlock73 from './data/kaustinen6Block73.json'

describe('bindings', () => {
let ffi: VerkleFFI
let verkleCrypto: VerkleCrypto
let verkleCrypto: Awaited<ReturnType<typeof loadVerkleCrypto>>
beforeAll(async () => {
verkleCrypto = await loadVerkleCrypto()
ffi = new VerkleFFI()
Expand Down Expand Up @@ -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([
Expand Down Expand Up @@ -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
}
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
20 changes: 17 additions & 3 deletions src.ts/verkleFFIBindings/index.ts
Original file line number Diff line number Diff line change
@@ -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,
type ProverInput,
type VerifierInput,
} from './verkleFFI.js'

export {
initVerkleWasm,
Expand All @@ -10,5 +22,7 @@ export {
zeroCommitment,
verifyExecutionWitnessPreState,
createProof,
verifyProof
verifyProof,
type ProverInput,
type VerifierInput,
}
67 changes: 62 additions & 5 deletions src.ts/verkleFFIBindings/verkleFFI.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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)
}
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)
}