Skip to content

Commit

Permalink
chore!: Add tests for createProof and verifyProof alongside an easier…
Browse files Browse the repository at this point in the history
… to use abstraction (#58)

* add ProverInput and VerifierInput abstraction

* add tests

* Fix export typing

---------

Co-authored-by: acolytec3 <[email protected]>
  • Loading branch information
kevaundray and acolytec3 authored Sep 10, 2024
1 parent 47410f3 commit 43a3b76
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 17 deletions.
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
}
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)
}

0 comments on commit 43a3b76

Please sign in to comment.