From 17bdd7e3909f0ddd195e5cb7095cd0d30758ed43 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Tue, 10 Oct 2023 18:26:42 +0100 Subject: [PATCH] feat: add `execute` method to `Noir` class (#3081) --- .../test/browser/compile_prove_verify.test.ts | 3 +- .../test/browser/recursion.test.ts | 6 ++-- tooling/noir_js/src/index.ts | 1 - tooling/noir_js/src/program.ts | 25 +++++++++++---- tooling/noir_js/src/witness_generation.ts | 7 ++-- tooling/noir_js/test/node/cjs.test.cjs | 16 +++++----- tooling/noir_js/test/node/e2e.test.ts | 32 +++++++++++++------ tooling/noir_js/test/node/execute.test.ts | 16 ++++++++++ tooling/noir_js/test/node/smoke.test.ts | 16 +++++----- .../assert_lt/src/main.nr | 5 +-- .../assert_lt/target/assert_lt.json | 2 +- 11 files changed, 85 insertions(+), 44 deletions(-) create mode 100644 tooling/noir_js/test/node/execute.test.ts diff --git a/compiler/integration-tests/test/browser/compile_prove_verify.test.ts b/compiler/integration-tests/test/browser/compile_prove_verify.test.ts index a2d6c7e94ed..f2063c5e4b0 100644 --- a/compiler/integration-tests/test/browser/compile_prove_verify.test.ts +++ b/compiler/integration-tests/test/browser/compile_prove_verify.test.ts @@ -5,6 +5,7 @@ import * as TOML from 'smol-toml'; import { initializeResolver } from '@noir-lang/source-resolver'; import newCompiler, { compile, init_log_level as compilerLogLevel } from '@noir-lang/noir_wasm'; import { Noir } from '@noir-lang/noir_js'; +import { InputMap } from '@noir-lang/noirc_abi'; import { BarretenbergBackend } from '@noir-lang/backend_barretenberg'; import { getFile } from './utils.js'; @@ -64,7 +65,7 @@ test_cases.forEach((testInfo) => { const program = new Noir(noir_program, backend); const prover_toml = await getFile(`${base_relative_path}/${test_case}/Prover.toml`); - const inputs = TOML.parse(prover_toml); + const inputs: InputMap = TOML.parse(prover_toml) as InputMap; // JS Proving diff --git a/compiler/integration-tests/test/browser/recursion.test.ts b/compiler/integration-tests/test/browser/recursion.test.ts index bdc44d8db5a..6a5592bca67 100644 --- a/compiler/integration-tests/test/browser/recursion.test.ts +++ b/compiler/integration-tests/test/browser/recursion.test.ts @@ -4,7 +4,7 @@ import { TEST_LOG_LEVEL } from '../environment.js'; import { Logger } from 'tslog'; import { initializeResolver } from '@noir-lang/source-resolver'; import newCompiler, { compile, init_log_level as compilerLogLevel } from '@noir-lang/noir_wasm'; -import { acvm, abi, generateWitness } from '@noir-lang/noir_js'; +import { acvm, abi, Noir } from '@noir-lang/noir_js'; import * as TOML from 'smol-toml'; import { BarretenbergBackend } from '@noir-lang/backend_barretenberg'; @@ -55,7 +55,7 @@ describe('It compiles noir program code, receiving circuit bytes and abi object. const main_backend = new BarretenbergBackend(main_program); - const main_witnessUint8Array = await generateWitness(main_program, main_inputs); + const { witness: main_witnessUint8Array } = await new Noir(main_program).execute(main_inputs); const main_proof = await main_backend.generateIntermediateProof(main_witnessUint8Array); const main_verification = await main_backend.verifyIntermediateProof(main_proof); @@ -84,7 +84,7 @@ describe('It compiles noir program code, receiving circuit bytes and abi object. const recursion_backend = new BarretenbergBackend(recursion_program); - const recursion_witnessUint8Array = await generateWitness(recursion_program, recursion_inputs); + const { witness: recursion_witnessUint8Array } = await new Noir(recursion_program).execute(recursion_inputs); const recursion_proof = await recursion_backend.generateFinalProof(recursion_witnessUint8Array); diff --git a/tooling/noir_js/src/index.ts b/tooling/noir_js/src/index.ts index 71914d59dbf..7c8d1bc7544 100644 --- a/tooling/noir_js/src/index.ts +++ b/tooling/noir_js/src/index.ts @@ -3,7 +3,6 @@ import * as abi from '@noir-lang/noirc_abi'; export { acvm, abi }; -export { generateWitness } from './witness_generation.js'; export { acirToUint8Array, witnessMapToUint8Array } from './serialize.js'; export { Noir } from './program.js'; diff --git a/tooling/noir_js/src/program.ts b/tooling/noir_js/src/program.ts index 1fd32862010..7d11166c0a7 100644 --- a/tooling/noir_js/src/program.ts +++ b/tooling/noir_js/src/program.ts @@ -1,13 +1,14 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Backend, CompiledCircuit, ProofData } from '@noir-lang/types'; import { generateWitness } from './witness_generation.js'; -import initAbi from '@noir-lang/noirc_abi'; +import initAbi, { abiDecode, InputMap, InputValue } from '@noir-lang/noirc_abi'; import initACVM from '@noir-lang/acvm_js'; +import { witnessMapToUint8Array } from './serialize.js'; export class Noir { constructor( private circuit: CompiledCircuit, - private backend: Backend, + private backend?: Backend, ) {} async init(): Promise { @@ -19,14 +20,26 @@ export class Noir { } } + private getBackend(): Backend { + if (this.backend === undefined) throw new Error('Operation requires a backend but none was provided'); + return this.backend; + } + // Initial inputs to your program - async generateFinalProof(inputs: any): Promise { + async execute(inputs: InputMap): Promise<{ witness: Uint8Array; returnValue: InputValue }> { await this.init(); - const serializedWitness = await generateWitness(this.circuit, inputs); - return this.backend.generateFinalProof(serializedWitness); + const witness = await generateWitness(this.circuit, inputs); + const { return_value: returnValue } = abiDecode(this.circuit.abi, witness); + return { witness: witnessMapToUint8Array(witness), returnValue }; + } + + // Initial inputs to your program + async generateFinalProof(inputs: InputMap): Promise { + const { witness } = await this.execute(inputs); + return this.getBackend().generateFinalProof(witness); } async verifyFinalProof(proofData: ProofData): Promise { - return this.backend.verifyFinalProof(proofData); + return this.getBackend().verifyFinalProof(proofData); } } diff --git a/tooling/noir_js/src/witness_generation.ts b/tooling/noir_js/src/witness_generation.ts index f3307837736..f96cddb0eca 100644 --- a/tooling/noir_js/src/witness_generation.ts +++ b/tooling/noir_js/src/witness_generation.ts @@ -1,11 +1,10 @@ import { abiEncode, InputMap } from '@noir-lang/noirc_abi'; import { base64Decode } from './base64_decode.js'; -import { executeCircuit } from '@noir-lang/acvm_js'; -import { witnessMapToUint8Array } from './serialize.js'; +import { executeCircuit, WitnessMap } from '@noir-lang/acvm_js'; import { CompiledCircuit } from '@noir-lang/types'; // Generates the witnesses needed to feed into the chosen proving system -export async function generateWitness(compiledProgram: CompiledCircuit, inputs: InputMap): Promise { +export async function generateWitness(compiledProgram: CompiledCircuit, inputs: InputMap): Promise { // Throws on ABI encoding error const witnessMap = abiEncode(compiledProgram.abi, inputs); @@ -15,7 +14,7 @@ export async function generateWitness(compiledProgram: CompiledCircuit, inputs: const solvedWitness = await executeCircuit(base64Decode(compiledProgram.bytecode), witnessMap, () => { throw Error('unexpected oracle during execution'); }); - return witnessMapToUint8Array(solvedWitness); + return solvedWitness; } catch (err) { throw new Error(`Circuit execution failed: ${err}`); } diff --git a/tooling/noir_js/test/node/cjs.test.cjs b/tooling/noir_js/test/node/cjs.test.cjs index b7b30d7dcdb..8698f9dfe2b 100644 --- a/tooling/noir_js/test/node/cjs.test.cjs +++ b/tooling/noir_js/test/node/cjs.test.cjs @@ -2,14 +2,14 @@ /* eslint-disable @typescript-eslint/no-var-requires */ const chai = require('chai'); const assert_lt_json = require('../noir_compiled_examples/assert_lt/target/assert_lt.json'); -const noirjs = require('@noir-lang/noir_js'); +const { Noir } = require('@noir-lang/noir_js'); it('generates witnesses successfully', async () => { const inputs = { x: '2', y: '3', }; - const _solvedWitness = await noirjs.generateWitness(assert_lt_json, inputs); + const _solvedWitness = await new Noir(assert_lt_json).execute(inputs); }); it('string input and number input are the same', async () => { @@ -21,8 +21,8 @@ it('string input and number input are the same', async () => { x: 2, y: 3, }; - const solvedWitnessString = await noirjs.generateWitness(assert_lt_json, inputsString); - const solvedWitnessNumber = await noirjs.generateWitness(assert_lt_json, inputsNumber); + const solvedWitnessString = await new Noir(assert_lt_json).execute(inputsString); + const solvedWitnessNumber = await new Noir(assert_lt_json).execute(inputsNumber); chai.expect(solvedWitnessString).to.deep.equal(solvedWitnessNumber); }); @@ -36,8 +36,8 @@ it('string input and number input are the same', async () => { y: 3, }; - const solvedWitnessString = await noirjs.generateWitness(assert_lt_json, inputsString); - const solvedWitnessNumber = await noirjs.generateWitness(assert_lt_json, inputsNumber); + const solvedWitnessString = await new Noir(assert_lt_json).execute(inputsString); + const solvedWitnessNumber = await new Noir(assert_lt_json).execute(inputsNumber); chai.expect(solvedWitnessString).to.deep.equal(solvedWitnessNumber); }); @@ -48,7 +48,7 @@ it('0x prefixed string input for inputs will throw', async () => { }; try { - await noirjs.generateWitness(assert_lt_json, inputsHexPrefix); + await new Noir(assert_lt_json).execute(inputsHexPrefix); chai.expect.fail( 'Expected generatedWitness to throw, due to inputs being prefixed with 0x. Currently not supported', ); @@ -66,7 +66,7 @@ describe('input validation', () => { }; try { - await noirjs.generateWitness(assert_lt_json, inputs); + await new Noir(assert_lt_json).execute(inputs); chai.expect.fail('Expected generatedWitness to throw, due to x not being convertible to a uint64'); } catch (error) { const knownError = error; diff --git a/tooling/noir_js/test/node/e2e.test.ts b/tooling/noir_js/test/node/e2e.test.ts index 3182f95ca10..773d6c5fc6c 100644 --- a/tooling/noir_js/test/node/e2e.test.ts +++ b/tooling/noir_js/test/node/e2e.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import assert_lt_json from '../noir_compiled_examples/assert_lt/target/assert_lt.json' assert { type: 'json' }; -import { Noir, generateWitness } from '@noir-lang/noir_js'; +import { Noir } from '@noir-lang/noir_js'; import { BarretenbergBackend as Backend } from '@noir-lang/backend_barretenberg'; import { CompiledCircuit } from '@noir-lang/types'; @@ -12,13 +12,16 @@ it('end-to-end proof creation and verification (outer)', async () => { x: '2', y: '3', }; - const serializedWitness = await generateWitness(assert_lt_program, inputs); + + const program = new Noir(assert_lt_program); + + const { witness } = await program.execute(inputs); // bb.js part // // Proof creation const prover = new Backend(assert_lt_program); - const proof = await prover.generateFinalProof(serializedWitness); + const proof = await prover.generateFinalProof(witness); // Proof verification const isValid = await prover.verifyFinalProof(proof); @@ -50,13 +53,16 @@ it('end-to-end proof creation and verification (inner)', async () => { x: '2', y: '3', }; - const serializedWitness = await generateWitness(assert_lt_program, inputs); + + const program = new Noir(assert_lt_program); + + const { witness } = await program.execute(inputs); // bb.js part // // Proof creation const prover = new Backend(assert_lt_program); - const proof = await prover.generateIntermediateProof(serializedWitness); + const proof = await prover.generateIntermediateProof(witness); // Proof verification const isValid = await prover.verifyIntermediateProof(proof); @@ -81,12 +87,15 @@ it('[BUG] -- bb.js null function or function signature mismatch (different insta x: '2', y: '3', }; - const serializedWitness = await generateWitness(assert_lt_program, inputs); + + const program = new Noir(assert_lt_program); + + const { witness } = await program.execute(inputs); // bb.js part const prover = new Backend(assert_lt_program); - const proof = await prover.generateFinalProof(serializedWitness); + const proof = await prover.generateFinalProof(witness); try { const verifier = new Backend(assert_lt_program); @@ -113,7 +122,10 @@ it('[BUG] -- bb.js null function or function signature mismatch (outer-inner) ', x: '2', y: '3', }; - const serializedWitness = await generateWitness(assert_lt_program, inputs); + + const program = new Noir(assert_lt_program); + + const { witness } = await program.execute(inputs); // bb.js part // @@ -122,8 +134,8 @@ it('[BUG] -- bb.js null function or function signature mismatch (outer-inner) ', const prover = new Backend(assert_lt_program); // Create a proof using both proving systems, the majority of the time // one would only use outer proofs. - const proofOuter = await prover.generateFinalProof(serializedWitness); - const _proofInner = await prover.generateIntermediateProof(serializedWitness); + const proofOuter = await prover.generateFinalProof(witness); + const _proofInner = await prover.generateIntermediateProof(witness); // Proof verification // diff --git a/tooling/noir_js/test/node/execute.test.ts b/tooling/noir_js/test/node/execute.test.ts new file mode 100644 index 00000000000..bfaf80882ab --- /dev/null +++ b/tooling/noir_js/test/node/execute.test.ts @@ -0,0 +1,16 @@ +import assert_lt_json from '../noir_compiled_examples/assert_lt/target/assert_lt.json' assert { type: 'json' }; +import { Noir } from '@noir-lang/noir_js'; +import { CompiledCircuit } from '@noir-lang/types'; +import { expect } from 'chai'; + +const assert_lt_program = assert_lt_json as CompiledCircuit; + +it('returns the return value of the circuit', async () => { + const inputs = { + x: '2', + y: '3', + }; + const { returnValue } = await new Noir(assert_lt_program).execute(inputs); + + expect(returnValue).to.be.eq('0x05'); +}); diff --git a/tooling/noir_js/test/node/smoke.test.ts b/tooling/noir_js/test/node/smoke.test.ts index 739dcbfcb62..6993a44f66e 100644 --- a/tooling/noir_js/test/node/smoke.test.ts +++ b/tooling/noir_js/test/node/smoke.test.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import assert_lt_json from '../noir_compiled_examples/assert_lt/target/assert_lt.json' assert { type: 'json' }; -import { generateWitness } from '@noir-lang/noir_js'; import { CompiledCircuit } from '@noir-lang/types'; +import { Noir } from '@noir-lang/noir_js'; const assert_lt_program = assert_lt_json as CompiledCircuit; @@ -10,7 +10,7 @@ it('generates witnesses successfully', async () => { x: '2', y: '3', }; - expect(() => generateWitness(assert_lt_program, inputs)).to.not.throw; + expect(() => new Noir(assert_lt_program).execute(inputs)).to.not.throw; }); it('string input and number input are the same', async () => { @@ -22,8 +22,8 @@ it('string input and number input are the same', async () => { x: 2, y: 3, }; - const solvedWitnessString = await generateWitness(assert_lt_program, inputsString); - const solvedWitnessNumber = await generateWitness(assert_lt_program, inputsNumber); + const solvedWitnessString = await new Noir(assert_lt_program).execute(inputsString); + const solvedWitnessNumber = await new Noir(assert_lt_program).execute(inputsNumber); expect(solvedWitnessString).to.deep.equal(solvedWitnessNumber); }); @@ -37,8 +37,8 @@ it('string input and number input are the same', async () => { y: 3, }; - const solvedWitnessString = await generateWitness(assert_lt_program, inputsString); - const solvedWitnessNumber = await generateWitness(assert_lt_program, inputsNumber); + const solvedWitnessString = await new Noir(assert_lt_program).execute(inputsString); + const solvedWitnessNumber = await new Noir(assert_lt_program).execute(inputsNumber); expect(solvedWitnessString).to.deep.equal(solvedWitnessNumber); }); @@ -49,7 +49,7 @@ it('0x prefixed string input for inputs will throw', async () => { }; try { - await generateWitness(assert_lt_program, inputsHexPrefix); + await new Noir(assert_lt_program).execute(inputsHexPrefix); expect.fail('Expected generatedWitness to throw, due to inputs being prefixed with 0x. Currently not supported'); } catch (error) { // Successfully errored due to 0x not being supported. Update this test once/if we choose @@ -65,7 +65,7 @@ describe('input validation', () => { }; try { - await generateWitness(assert_lt_program, inputs); + await new Noir(assert_lt_program).execute(inputs); expect.fail('Expected generatedWitness to throw, due to x not being convertible to a uint64'); } catch (error) { const knownError = error as Error; diff --git a/tooling/noir_js/test/noir_compiled_examples/assert_lt/src/main.nr b/tooling/noir_js/test/noir_compiled_examples/assert_lt/src/main.nr index 7f3767f4a48..8deda68c051 100644 --- a/tooling/noir_js/test/noir_compiled_examples/assert_lt/src/main.nr +++ b/tooling/noir_js/test/noir_compiled_examples/assert_lt/src/main.nr @@ -1,3 +1,4 @@ -fn main(x : u64, y : pub u64) { +fn main(x : u64, y : pub u64) -> pub u64 { assert(x < y); -} \ No newline at end of file + x + y +} diff --git a/tooling/noir_js/test/noir_compiled_examples/assert_lt/target/assert_lt.json b/tooling/noir_js/test/noir_compiled_examples/assert_lt/target/assert_lt.json index fdf25a4743d..23e660377e7 100644 --- a/tooling/noir_js/test/noir_compiled_examples/assert_lt/target/assert_lt.json +++ b/tooling/noir_js/test/noir_compiled_examples/assert_lt/target/assert_lt.json @@ -1 +1 @@ -{"backend":"acvm-backend-barretenberg","abi":{"parameters":[{"name":"x","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"private"},{"name":"y","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"public"}],"param_witnesses":{"x":[1],"y":[2]},"return_type":null,"return_witnesses":[]},"bytecode":"H4sIAAAAAAAA/81WXW7DIAw20BJl0noWE6CBt6k3WbT0/kfYooJm0bQPxZZqKXKw4PPPB5Y/AOATbqL+Pl30F1nrsqaiiq52j+cQ1nlanXffOOUlRQxxOSeXXEzxZ0rerymkOS95xuyCX901Zn/Fm5jXsbDBcobE9yxm7BNn+LCQxnvY+dfEZoq2AjlB46et42nHxupcgqSDAO4R+C6/VN5Hfo6QQIrWFDvFwP9DoaKZY1aM90C/HpdrDTupvyP2nS/a6GzRA7GNhEfd7Nu4qJxvb/5CzimiFcG4kDN7e9QDnJHY6vkTiQX4aoICjR5FG3mdHCwp5rYe4H6SMAK+Kxbng+zFGkGmCbf1652eLGPOAyOvHfV72sC4J1nLyPOjptXKL8ADXjPGCwAA"} \ No newline at end of file +{"hash":7965745029883743051,"backend":"acvm-backend-barretenberg","abi":{"parameters":[{"name":"x","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"private"},{"name":"y","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"public"}],"param_witnesses":{"x":[1],"y":[2]},"return_type":{"kind":"integer","sign":"unsigned","width":64},"return_witnesses":[12]},"bytecode":"H4sIAAAAAAAA/+1WXW6DMAw2oVAGqNqedg2HQAlv1W4yNHr/I2yoieRmlIfGZpNaS8jBCl/8F/O9AcA7XCT5eZTTJ/Ku3DuVxGlvN3hs26lvJm30JzbDaDtsu/FotdWd7b4aa8xkW9sP49DjoFsz6XM3mDNeJL0fCwMsnRL/1nzGONEpHxZSf3cLa0VsqdO5QEwQnBPm8bBgYz1cokg7AdwM+JpfKu6Mv0ZIIEVzipEyx08vjRfF7HPC2Afqfr90aFgInQ2b5jV3ek9sGekPv3e+gx/wu3cSslZuj1rZk9zAeSE2/73osPLB5cRB5RJxCs5MBc72WJxNF4t1AJlBE+YvliHkjDHvGesakb/VAcDN1nLY5ocS62fB2DNbscqCD+uKVdIB+WSVkZiFSyg3bgn/m1XOcZf8NRJllSWjn3/BKjFOtAKZOkX2wCqrrJyuie2hWGUF16yyhm1ZJcaJ5mSVryDTwNyssmKMuWasBWf+bl0wv6YX9htJ8OlsXBQAAA=="} \ No newline at end of file