Skip to content

Commit

Permalink
Merge pull request #10 from zkemail/feat/prover-api
Browse files Browse the repository at this point in the history
add prover import
  • Loading branch information
jp4g authored Oct 3, 2024
2 parents 442e9ea + 1e91edd commit ba34e7a
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 71 deletions.
4 changes: 3 additions & 1 deletion js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@mach-34/noir-bignum-paramgen": "^1.0.2",
"@noir-lang/backend_barretenberg": "=0.34.0",
"@noir-lang/noir_js": "=0.34.0",
"@noir-lang/noirc_abi": "^0.35.0",
"@zk-email/helpers": "=6.1.5"
},
"devDependencies": {
Expand Down Expand Up @@ -51,7 +52,8 @@
"testPathIgnorePatterns": [
"/dist/",
"/node_modules/"
]
],
"testTimeout": 100000
},
"babel": {
"presets": [
Expand Down
4 changes: 2 additions & 2 deletions js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { u8ToU32 } from "./utils";
// with a few modifications for noir input generation
// also removes some of the unused functionality like masking

type CircuitInput = {
export type CircuitInput = {
header: string[];
header_length: string;
pubkey: string[];
Expand All @@ -30,7 +30,7 @@ type CircuitInput = {
body_hash_index?: string;
};

type InputGenerationArgs = {
export type InputGenerationArgs = {
ignoreBodyHashCheck?: boolean;
shaPrecomputeSelector?: string;
maxHeadersLength?: number; // Max length of the email header including padding
Expand Down
145 changes: 145 additions & 0 deletions js/src/prover.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import {
BarretenbergBackend,
CompiledCircuit,
ProofData,
UltraHonkBackend,
} from "@noir-lang/backend_barretenberg";
import { Noir } from "@noir-lang/noir_js";
import { InputValue } from "@noir-lang/noirc_abi";
import { CircuitInput } from "./index";

type ProvingBackend = "honk" | "plonk" | "all";

export class ZKEmailProver {
private plonk?: BarretenbergBackend;
private honk?: UltraHonkBackend;
private noir: Noir;

constructor(
/* The ACIR of the Noir circuit to prove */
circuit: CompiledCircuit,
/* Define the prover backend to use */
private provingBackend: ProvingBackend = "plonk"
) {
// initialize the backends
if (provingBackend === "plonk" || provingBackend === "all") {
this.plonk = new BarretenbergBackend(circuit);
}
if (provingBackend === "honk" || provingBackend === "all") {
this.honk = new UltraHonkBackend(circuit);
}
// initialize the Noir instance
this.noir = new Noir(circuit);
}

/**
* Compute the witness for a given input to the circuit without generating a proof
*
* @param input - the input that should produce a satisfying witness for the circuit
* @returns - the witness for the input and the output of the circuit if satisfiable
*/
async simulateWitness(
input: CircuitInput
): Promise<{ witness: Uint8Array; returnValue: InputValue }> {
return await this.noir.execute(input);
}

/**
* Generate a proof of a satisfying input to the circuit using a provided witness
*
* @param input - a satisfying witness for the circuit
* @param provingBackend - optionally provided if the class was initialized with both proving schemes
* @returns proof of valid execution of the circuit
*/
async prove(
witness: Uint8Array,
provingBackend?: ProvingBackend
): Promise<ProofData> {
// determine proving backend to use
let backend: BarretenbergBackend | UltraHonkBackend;
if (provingBackend) {
// check that the asserted backend is initialized
if (provingBackend === "plonk" && this.plonk) {
backend = this.plonk;
} else if (provingBackend === "honk" && this.honk) {
backend = this.honk;
} else {
throw new Error(`Proving scheme ${provingBackend} not initialized`);
}
} else {
// default to the backend used to initialize the class
if (this.provingBackend === "honk" && this.honk) {
backend = this.honk;
} else if (this.provingBackend === "plonk" && this.plonk) {
backend = this.plonk;
} else {
throw new Error(`Proving scheme ${this.provingBackend} not initialized`);
}
}
// generate the proof
return await backend.generateProof(witness);
}

/**
* Simulate the witness for a given input and generate a proof
*
* @param input - the input that should produce a satisfying witness for the circuit
* @param provingBackend - optionally provided if the class was initialized with both proving schemes
* @returns proof of valid execution of the circuit
*/
async fullProve(
input: CircuitInput,
provingBackend?: ProvingBackend
): Promise<ProofData> {
const { witness } = await this.simulateWitness(input);
return await this.prove(witness, provingBackend);
}

/**
* Verify a proof of a satisfying input to the circuit for a given proving scheme
*
* @param proof - the proof to verify
* @param provingBackend - optionally provided if the class was initialized with both proving schemes
* @returns true if the proof is valid, false otherwise
*/
async verify(
proof: ProofData,
provingBackend?: ProvingBackend
): Promise<boolean> {
// determine proving backend to use
let backend: BarretenbergBackend | UltraHonkBackend;
if (provingBackend) {
// check that the asserted backend is initialized
if (provingBackend === "plonk" && this.plonk) {
backend = this.plonk;
} else if (provingBackend === "honk" && this.honk) {
backend = this.honk;
} else {
throw new Error(`Proving scheme ${provingBackend} not initialized`);
}
} else {
// default to the backend used to initialize the class
if (this.provingBackend === "honk" && this.honk) {
backend = this.honk;
} else if (this.provingBackend === "plonk" && this.plonk) {
backend = this.plonk;
} else {
throw new Error(`Proving scheme ${this.provingBackend} not initialized`);
}
}
// verify the proof
return await backend.verifyProof(proof);
}

/**
* End the prover wasm instance(s) and clean up resources
*/
async destroy() {
if (this.plonk) {
await this.plonk.destroy();
}
if (this.honk) {
await this.honk.destroy();
}
}
}
106 changes: 40 additions & 66 deletions js/tests/circuits.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import fs from "fs";
import path from "path";
import {
BarretenbergBackend,
CompiledCircuit,
UltraHonkBackend,
} from "@noir-lang/backend_barretenberg";
import { Noir, acvm } from "@noir-lang/noir_js";
import { generateEmailVerifierInputs } from "../src";
import circuit1024 from "../../examples/verify_email_1024_bit_dkim/target/verify_email_1024_bit_dkim.json";
import { ZKEmailProver } from "../src/prover";
import { generateEmailVerifierInputs } from "../src/index";
// import circuit1024 from "../../examples/verify_email_1024_bit_dkim/target/verify_email_1024_bit_dkim.json";
import circuit2048 from "../../examples/verify_email_2048_bit_dkim/target/verify_email_2048_bit_dkim.json";
import partialHash from "../../examples/partial_hash/target/partial_hash.json";
const emails = {
Expand All @@ -17,118 +12,97 @@ const emails = {
),
};

type Prover = {
noir: Noir;
barretenberg: BarretenbergBackend;
ultraHonk: UltraHonkBackend;
// default header/ body lengths to use for input gen
const inputParams = {
maxHeadersLength: 512,
maxBodyLength: 1024,
};

function makeProver(circuit: CompiledCircuit): Prover {
return {
noir: new Noir(circuit),
barretenberg: new BarretenbergBackend(circuit),
ultraHonk: new UltraHonkBackend(circuit),
};
}

async function teardownProver(prover: Prover) {
await prover.barretenberg.destroy();
await prover.ultraHonk.destroy();
}

describe("Fixed Size Circuit Input", () => {
let prover1024: Prover;
let prover2048: Prover;
let proverPartialHash: Prover;
const inputParams = {
maxHeadersLength: 512,
maxBodyLength: 1024,
};
jest.setTimeout(100000);
beforeAll(async () => {
// todo: get a github email from a throwaway account to verify
// let prover1024: ZKEmailProver;
let prover2048: ZKEmailProver;
let proverPartialHash: ZKEmailProver;

beforeAll(() => {
//@ts-ignore
prover1024 = makeProver(circuit1024);
// prover1024 = new ZKEmailProver(circuit1024, "all");
//@ts-ignore
prover2048 = makeProver(circuit2048);
prover2048 = new ZKEmailProver(circuit2048, "all");
//@ts-ignore
proverPartialHash = makeProver(partialHash);
proverPartialHash = new ZKEmailProver(partialHash, "all");
});

afterAll(async () => {
teardownProver(prover1024);
teardownProver(prover2048);
teardownProver(proverPartialHash);
// await prover1024.destroy();
await prover2048.destroy();
await proverPartialHash.destroy();
});
describe("UltraHonk", () => {
it("UltraHonk::SmallEmail", async () => {

describe("UltraPlonk", () => {
it("UltraPlonk::SmallEmail", async () => {
const inputs = await generateEmailVerifierInputs(
emails.small,
inputParams
);
const { witness } = await prover2048.noir.execute(inputs);
const proof = await prover2048.ultraHonk.generateProof(witness);
const result = await prover2048.ultraHonk.verifyProof(proof);
const proof = await prover2048.fullProve(inputs, "plonk");
const result = await prover2048.verify(proof, "plonk");
expect(result).toBeTruthy();
});

it("UltraHonk::LargeEmail", async () => {
it("UltraPlonk::LargeEmail", async () => {
const inputs = await generateEmailVerifierInputs(
emails.large,
inputParams
);
const { witness } = await prover2048.noir.execute(inputs);
const proof = await prover2048.ultraHonk.generateProof(witness);
const result = await prover2048.ultraHonk.verifyProof(proof);
const proof = await prover2048.fullProve(inputs, "plonk");
const result = await prover2048.verify(proof, "plonk");
expect(result).toBeTruthy();
});
});

describe("UltraPlonk", () => {
it("UltraPlonk::SmallEmail", async () => {
describe("UltraHonk", () => {
it("UltraHonk::SmallEmail", async () => {
const inputs = await generateEmailVerifierInputs(
emails.small,
inputParams
);
const { witness } = await prover2048.noir.execute(inputs);
const proof = await prover2048.barretenberg.generateProof(witness);
const result = await prover2048.barretenberg.verifyProof(proof);
const proof = await prover2048.fullProve(inputs, "honk");
const result = await prover2048.verify(proof, "honk");
expect(result).toBeTruthy();
});

it("UltraPlonk::LargeEmail", async () => {
it("UltraHonk::LargeEmail", async () => {
const inputs = await generateEmailVerifierInputs(
emails.large,
inputParams
);
const { witness } = await prover2048.noir.execute(inputs);
const proof = await prover2048.barretenberg.generateProof(witness);
const result = await prover2048.barretenberg.verifyProof(proof);
const proof = await prover2048.fullProve(inputs, "honk");
const result = await prover2048.verify(proof, "honk");
expect(result).toBeTruthy();
});
});

describe("Partial Hash", () => {
const selectorText = "All nodes in the Bitcoin network can consult it";
it("UltraPlonk::PartialHash", async () => {
const selectorText = "All nodes in the Bitcoin network can consult it";
const inputs = await generateEmailVerifierInputs(emails.large, {
shaPrecomputeSelector: selectorText,
maxHeadersLength: 512,
maxBodyLength: 192,
});
const { witness } = await proverPartialHash.noir.execute(inputs);
const proof = await proverPartialHash.barretenberg.generateProof(witness);
const result = await proverPartialHash.barretenberg.verifyProof(proof);
const proof = await proverPartialHash.fullProve(inputs, "plonk");
const result = await proverPartialHash.verify(proof, "plonk");
expect(result).toBeTruthy();
});
it("UltraHonk::PartialHash", async () => {
const selectorText = "All nodes in the Bitcoin network can consult it";
const inputs = await generateEmailVerifierInputs(emails.large, {
shaPrecomputeSelector: selectorText,
maxHeadersLength: 512,
maxBodyLength: 192,
});
const { witness } = await proverPartialHash.noir.execute(inputs);
const proof = await proverPartialHash.ultraHonk.generateProof(witness);
const result = await proverPartialHash.ultraHonk.verifyProof(proof);
const proof = await proverPartialHash.fullProve(inputs, "honk");
const result = await proverPartialHash.verify(proof, "honk");
expect(result).toBeTruthy();
});
});
Expand Down
16 changes: 14 additions & 2 deletions js/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1694,11 +1694,23 @@
dependencies:
"@noir-lang/types" "0.34.0"

"@noir-lang/noirc_abi@^0.35.0":
version "0.35.0"
resolved "https://registry.yarnpkg.com/@noir-lang/noirc_abi/-/noirc_abi-0.35.0.tgz#6b56f356a056ef4f935503d70460afa2aae76018"
integrity sha512-iTlHWHdnxgwQveGNCaeQKj1UenFqtnWvh62mfY7m/GD9cs9LB5PFs57rmYMEGaQysEix3sM3BmSX1h9C93pHDg==
dependencies:
"@noir-lang/types" "0.35.0"

"@noir-lang/[email protected]":
version "0.34.0"
resolved "https://registry.yarnpkg.com/@noir-lang/types/-/types-0.34.0.tgz#909d4a170177c7ab5adfeed5dc8660d69825ced2"
integrity sha512-04A14JMPt5G6/cCD4n59W0DUtAbcN8YByvSJ6gj0VQramd11tWK3ZqhKadnQrzhA0GwEvROd6jvn/blKdHlSXQ==

"@noir-lang/[email protected]":
version "0.35.0"
resolved "https://registry.yarnpkg.com/@noir-lang/types/-/types-0.35.0.tgz#47e73db1c545b0a68a7b1c4d551843ed71e47291"
integrity sha512-m4zzTuWVeYI/lgAKU4XwS61lfU/vr8laal8mLm2YEym8GTyVfezjjVrzQd0/mOZtpZ6hO7r3rbd3W2Vs9IkMrg==

"@rtsao/scc@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
Expand Down Expand Up @@ -4783,9 +4795,9 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==

"snarkjs@git+https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e":
"snarkjs@https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e":
version "0.5.0"
resolved "git+https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e"
resolved "https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e"
dependencies:
"@iden3/binfileutils" "0.0.11"
bfj "^7.0.2"
Expand Down

0 comments on commit ba34e7a

Please sign in to comment.