Skip to content

Commit

Permalink
chore: Noir.js interface accepts a stuct of public inputs and proof (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
kevaundray authored Oct 5, 2023
1 parent a29b568 commit 6796bec
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import toml from 'toml';
import { compile, init_log_level as compilerLogLevel } from '@noir-lang/noir_wasm';
import { Noir } from '@noir-lang/noir_js';
import { BarretenbergBackend } from '@noir-lang/backend_barretenberg';
import { separatePublicInputsFromProof } from '../shared/proof';

compilerLogLevel('INFO');

Expand Down Expand Up @@ -44,19 +43,18 @@ test_cases.forEach((testInfo) => {
const prover_toml = readFileSync(resolve(`${base_relative_path}/${test_case}/Prover.toml`)).toString();
const inputs = toml.parse(prover_toml);

const proofWithPublicInputs = await program.generateFinalProof(inputs);
const proofData = await program.generateFinalProof(inputs);

// JS verification

const verified = await program.verifyFinalProof(proofWithPublicInputs);
const verified = await program.verifyFinalProof(proofData);
expect(verified, 'Proof fails verification in JS').to.be.true;

// Smart contract verification

const contract = await ethers.deployContract(testInfo.compiled, [], {});

const { proof, publicInputs } = separatePublicInputsFromProof(proofWithPublicInputs, testInfo.numPublicInputs);
const result = await contract.verify(proof, publicInputs);
const result = await contract.verify(proofData.proof, proofData.publicInputs);

expect(result).to.be.true;
});
Expand Down
17 changes: 0 additions & 17 deletions compiler/integration-tests/test/shared/proof.ts

This file was deleted.

8 changes: 4 additions & 4 deletions tooling/noir_js/src/program.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Backend, CompiledCircuit } from '@noir-lang/types';
import { Backend, CompiledCircuit, ProofData } from '@noir-lang/types';
import { generateWitness } from './witness_generation.js';
import initAbi from '@noir-lang/noirc_abi';
import initACVM from '@noir-lang/acvm_js';
Expand All @@ -20,13 +20,13 @@ export class Noir {
}

// Initial inputs to your program
async generateFinalProof(inputs: any): Promise<Uint8Array> {
async generateFinalProof(inputs: any): Promise<ProofData> {
await this.init();
const serializedWitness = await generateWitness(this.circuit, inputs);
return this.backend.generateFinalProof(serializedWitness);
}

async verifyFinalProof(proof: Uint8Array): Promise<boolean> {
return this.backend.verifyFinalProof(proof);
async verifyFinalProof(proofData: ProofData): Promise<boolean> {
return this.backend.verifyFinalProof(proofData);
}
}
62 changes: 53 additions & 9 deletions tooling/noir_js_backend_barretenberg/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { acirToUint8Array } from './serialize.js';
import { Backend, CompiledCircuit } from '@noir-lang/types';
import { Backend, CompiledCircuit, ProofData } from '@noir-lang/types';

// This is the number of bytes in a UltraPlonk proof
// minus the public inputs.
const numBytesInProofWithoutPublicInputs: number = 2144;

export class BarretenbergBackend implements Backend {
// These type assertions are used so that we don't
Expand Down Expand Up @@ -40,7 +44,7 @@ export class BarretenbergBackend implements Backend {
//
// The settings for this proof are the same as the settings for a "normal" proof
// ie one that is not in the recursive setting.
async generateFinalProof(decompressedWitness: Uint8Array): Promise<Uint8Array> {
async generateFinalProof(decompressedWitness: Uint8Array): Promise<ProofData> {
const makeEasyToVerifyInCircuit = false;
return this.generateProof(decompressedWitness, makeEasyToVerifyInCircuit);
}
Expand All @@ -56,21 +60,35 @@ export class BarretenbergBackend implements Backend {
// We set `makeEasyToVerifyInCircuit` to true, which will tell the backend to
// generate the proof using components that will make the proof
// easier to verify in a circuit.
async generateIntermediateProof(witness: Uint8Array): Promise<Uint8Array> {
async generateIntermediateProof(witness: Uint8Array): Promise<ProofData> {
const makeEasyToVerifyInCircuit = true;
return this.generateProof(witness, makeEasyToVerifyInCircuit);
}

async generateProof(decompressedWitness: Uint8Array, makeEasyToVerifyInCircuit: boolean): Promise<Uint8Array> {
async generateProof(decompressedWitness: Uint8Array, makeEasyToVerifyInCircuit: boolean): Promise<ProofData> {
await this.instantiate();
const proof = await this.api.acirCreateProof(
const proofWithPublicInputs = await this.api.acirCreateProof(
this.acirComposer,
this.acirUncompressedBytecode,
decompressedWitness,
makeEasyToVerifyInCircuit,
);

return proof;
const splitIndex = proofWithPublicInputs.length - numBytesInProofWithoutPublicInputs;

const publicInputsConcatenated = proofWithPublicInputs.slice(0, splitIndex);

const publicInputSize = 32;
const publicInputs: Uint8Array[] = [];

for (let i = 0; i < publicInputsConcatenated.length; i += publicInputSize) {
const publicInput = publicInputsConcatenated.slice(i, i + publicInputSize);
publicInputs.push(publicInput);
}

const proof = proofWithPublicInputs.slice(splitIndex);

return { proof, publicInputs };
}

// Generates artifacts that will be passed to a circuit that will verify this proof.
Expand All @@ -83,14 +101,15 @@ export class BarretenbergBackend implements Backend {
//
// The number of public inputs denotes how many public inputs are in the inner proof.
async generateIntermediateProofArtifacts(
proof: Uint8Array,
proofData: ProofData,
numOfPublicInputs = 0,
): Promise<{
proofAsFields: string[];
vkAsFields: string[];
vkHash: string;
}> {
await this.instantiate();
const proof = reconstructProofWithPublicInputs(proofData);
const proofAsFields = await this.api.acirSerializeProofIntoFields(this.acirComposer, proof, numOfPublicInputs);

// TODO: perhaps we should put this in the init function. Need to benchmark
Expand All @@ -107,13 +126,15 @@ export class BarretenbergBackend implements Backend {
};
}

async verifyFinalProof(proof: Uint8Array): Promise<boolean> {
async verifyFinalProof(proofData: ProofData): Promise<boolean> {
const proof = reconstructProofWithPublicInputs(proofData);
const makeEasyToVerifyInCircuit = false;
const verified = await this.verifyProof(proof, makeEasyToVerifyInCircuit);
return verified;
}

async verifyIntermediateProof(proof: Uint8Array): Promise<boolean> {
async verifyIntermediateProof(proofData: ProofData): Promise<boolean> {
const proof = reconstructProofWithPublicInputs(proofData);
const makeEasyToVerifyInCircuit = true;
return this.verifyProof(proof, makeEasyToVerifyInCircuit);
}
Expand All @@ -131,3 +152,26 @@ export class BarretenbergBackend implements Backend {
await this.api.destroy();
}
}

function reconstructProofWithPublicInputs(proofData: ProofData): Uint8Array {
// Flatten publicInputs
const publicInputsConcatenated = flattenUint8Arrays(proofData.publicInputs);

// Concatenate publicInputs and proof
const proofWithPublicInputs = Uint8Array.from([...publicInputsConcatenated, ...proofData.proof]);

return proofWithPublicInputs;
}

function flattenUint8Arrays(arrays: Uint8Array[]): Uint8Array {
const totalLength = arrays.reduce((acc, val) => acc + val.length, 0);
const result = new Uint8Array(totalLength);

let offset = 0;
for (const arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}

return result;
}
13 changes: 9 additions & 4 deletions tooling/noir_js_types/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
export interface Backend {
// Generate an outer proof. This is the proof for the circuit which will verify
// inner proofs and or can be seen as the proof created for regular circuits.
generateFinalProof(decompressedWitness: Uint8Array): Promise<Uint8Array>;
generateFinalProof(decompressedWitness: Uint8Array): Promise<ProofData>;

// Generates an inner proof. This is the proof that will be verified
// in another circuit.
generateIntermediateProof(decompressedWitness: Uint8Array): Promise<Uint8Array>;
generateIntermediateProof(decompressedWitness: Uint8Array): Promise<ProofData>;

verifyFinalProof(proof: Uint8Array): Promise<boolean>;
verifyFinalProof(proofData: ProofData): Promise<boolean>;

verifyIntermediateProof(proof: Uint8Array): Promise<boolean>;
verifyIntermediateProof(proofData: ProofData): Promise<boolean>;
}

export type ProofData = {
publicInputs: Uint8Array[];
proof: Uint8Array;
};

export type CompiledCircuit = {
bytecode: string;
abi: object;
Expand Down

0 comments on commit 6796bec

Please sign in to comment.