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

feat: add JS types for ABI and input maps #3023

Merged
merged 9 commits into from
Oct 9, 2023
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
7 changes: 4 additions & 3 deletions compiler/integration-tests/test/browser/recursion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { acvm, abi, generateWitness } from '@noir-lang/noir_js';
import * as TOML from 'smol-toml';
import { BarretenbergBackend } from '@noir-lang/backend_barretenberg';
import { getFile } from './utils.js';
import { Field, InputMap } from '@noir-lang/noirc_abi';

const logger = new Logger({ name: 'test', minLevel: TEST_LOG_LEVEL });

Expand Down Expand Up @@ -50,7 +51,7 @@ describe('It compiles noir program code, receiving circuit bytes and abi object.

it('Should generate valid inner proof for correct input, then verify proof within a proof', async () => {
const main_program = await getCircuit(circuit_main_source);
const main_inputs = TOML.parse(circuit_main_toml);
const main_inputs: InputMap = TOML.parse(circuit_main_toml) as InputMap;

const main_backend = new BarretenbergBackend(main_program);

Expand All @@ -69,10 +70,10 @@ describe('It compiles noir program code, receiving circuit bytes and abi object.
numPublicInputs,
);

const recursion_inputs = {
const recursion_inputs: InputMap = {
verification_key: vkAsFields,
proof: proofAsFields,
public_inputs: [main_inputs.y],
public_inputs: [main_inputs.y as Field],
key_hash: vkHash,
input_aggregation_object: ['0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'],
};
Expand Down
6 changes: 3 additions & 3 deletions tooling/noir_js/src/witness_generation.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { abiEncode } from '@noir-lang/noirc_abi';
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 { CompiledCircuit } from '@noir-lang/types';

// Generates the witnesses needed to feed into the chosen proving system
export async function generateWitness(compiledProgram: CompiledCircuit, inputs: unknown): Promise<Uint8Array> {
export async function generateWitness(compiledProgram: CompiledCircuit, inputs: InputMap): Promise<Uint8Array> {
// Throws on ABI encoding error
const witnessMap = abiEncode(compiledProgram.abi, inputs, null);
const witnessMap = abiEncode(compiledProgram.abi, inputs);

// Execute the circuit to generate the rest of the witnesses and serialize
// them into a Uint8Array.
Expand Down
25 changes: 14 additions & 11 deletions tooling/noir_js/test/node/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ import assert_lt_json from '../noir_compiled_examples/assert_lt/target/assert_lt
import { generateWitness } from '../../src/index.js';
import { Noir } from '../../src/program.js';
import { BarretenbergBackend as Backend } from '@noir-lang/backend_barretenberg';
import { CompiledCircuit } from '@noir-lang/types';

const assert_lt_program = assert_lt_json as CompiledCircuit;

it('end-to-end proof creation and verification (outer)', async () => {
// Noir.Js part
const inputs = {
x: '2',
y: '3',
};
const serializedWitness = await generateWitness(assert_lt_json, inputs);
const serializedWitness = await generateWitness(assert_lt_program, inputs);

// bb.js part
//
// Proof creation
const prover = new Backend(assert_lt_json);
const prover = new Backend(assert_lt_program);
const proof = await prover.generateFinalProof(serializedWitness);

// Proof verification
Expand All @@ -31,9 +34,9 @@ it('end-to-end proof creation and verification (outer) -- Program API', async ()
};

// Initialize backend
const backend = new Backend(assert_lt_json);
const backend = new Backend(assert_lt_program);
// Initialize program
const program = new Noir(assert_lt_json, backend);
const program = new Noir(assert_lt_program, backend);
// Generate proof
const proof = await program.generateFinalProof(inputs);

Expand All @@ -48,12 +51,12 @@ it('end-to-end proof creation and verification (inner)', async () => {
x: '2',
y: '3',
};
const serializedWitness = await generateWitness(assert_lt_json, inputs);
const serializedWitness = await generateWitness(assert_lt_program, inputs);

// bb.js part
//
// Proof creation
const prover = new Backend(assert_lt_json);
const prover = new Backend(assert_lt_program);
const proof = await prover.generateIntermediateProof(serializedWitness);

// Proof verification
Expand All @@ -79,15 +82,15 @@ it('[BUG] -- bb.js null function or function signature mismatch (different insta
x: '2',
y: '3',
};
const serializedWitness = await generateWitness(assert_lt_json, inputs);
const serializedWitness = await generateWitness(assert_lt_program, inputs);

// bb.js part
const prover = new Backend(assert_lt_json);
const prover = new Backend(assert_lt_program);

const proof = await prover.generateFinalProof(serializedWitness);

try {
const verifier = new Backend(assert_lt_json);
const verifier = new Backend(assert_lt_program);
await verifier.verifyFinalProof(proof);
expect.fail(
'bb.js currently returns a bug when we try to verify a proof with a different Barretenberg instance that created it.',
Expand All @@ -111,13 +114,13 @@ it('[BUG] -- bb.js null function or function signature mismatch (outer-inner) ',
x: '2',
y: '3',
};
const serializedWitness = await generateWitness(assert_lt_json, inputs);
const serializedWitness = await generateWitness(assert_lt_program, inputs);

// bb.js part
//
// Proof creation
//
const prover = new Backend(assert_lt_json);
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);
Expand Down
17 changes: 10 additions & 7 deletions tooling/noir_js/test/node/smoke.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { expect } from 'chai';
import assert_lt_json from '../noir_compiled_examples/assert_lt/target/assert_lt.json' assert { type: 'json' };
import { generateWitness } from '../../src/index.js';
import { CompiledCircuit } from '@noir-lang/types';

const assert_lt_program = assert_lt_json as CompiledCircuit;

it('generates witnesses successfully', async () => {
const inputs = {
x: '2',
y: '3',
};
expect(() => generateWitness(assert_lt_json, inputs)).to.not.throw;
expect(() => generateWitness(assert_lt_program, inputs)).to.not.throw;
});

it('string input and number input are the same', async () => {
Expand All @@ -19,8 +22,8 @@ it('string input and number input are the same', async () => {
x: 2,
y: 3,
};
const solvedWitnessString = await generateWitness(assert_lt_json, inputsString);
const solvedWitnessNumber = await generateWitness(assert_lt_json, inputsNumber);
const solvedWitnessString = await generateWitness(assert_lt_program, inputsString);
const solvedWitnessNumber = await generateWitness(assert_lt_program, inputsNumber);
expect(solvedWitnessString).to.deep.equal(solvedWitnessNumber);
});

Expand All @@ -34,8 +37,8 @@ it('string input and number input are the same', async () => {
y: 3,
};

const solvedWitnessString = await generateWitness(assert_lt_json, inputsString);
const solvedWitnessNumber = await generateWitness(assert_lt_json, inputsNumber);
const solvedWitnessString = await generateWitness(assert_lt_program, inputsString);
const solvedWitnessNumber = await generateWitness(assert_lt_program, inputsNumber);
expect(solvedWitnessString).to.deep.equal(solvedWitnessNumber);
});

Expand All @@ -46,7 +49,7 @@ it('0x prefixed string input for inputs will throw', async () => {
};

try {
await generateWitness(assert_lt_json, inputsHexPrefix);
await generateWitness(assert_lt_program, 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
Expand All @@ -62,7 +65,7 @@ describe('input validation', () => {
};

try {
await generateWitness(assert_lt_json, inputs);
await generateWitness(assert_lt_program, inputs);
expect.fail('Expected generatedWitness to throw, due to x not being convertible to a uint64');
} catch (error) {
const knownError = error as Error;
Expand Down
3 changes: 3 additions & 0 deletions tooling/noir_js_types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
"types": "./lib/esm/types.d.ts"
}
},
"dependencies": {
"@noir-lang/noirc_abi": "workspace:*"
},
"devDependencies": {
"@types/prettier": "^3",
"eslint": "^8.50.0",
Expand Down
4 changes: 3 additions & 1 deletion tooling/noir_js_types/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Abi } from '@noir-lang/noirc_abi';

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.
Expand All @@ -19,5 +21,5 @@ export type ProofData = {

export type CompiledCircuit = {
bytecode: string;
abi: object;
abi: Abi;
};
94 changes: 75 additions & 19 deletions tooling/noirc_abi_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,83 @@ use errors::JsAbiError;
use js_witness_map::JsWitnessMap;
use temp::{input_value_from_json_type, JsonTypes};

#[wasm_bindgen(typescript_custom_section)]
const INPUT_MAP: &'static str = r#"
export type Field = string | number | boolean;
export type InputValue = Field | Field[] | InputMap;
export type InputMap = { [key: string]: InputValue };
"#;
kevaundray marked this conversation as resolved.
Show resolved Hide resolved

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = js_sys::Object, js_name = "InputMap", typescript_type = "InputMap")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub type JsInputMap;
}

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = js_sys::Object, js_name = "InputValue", typescript_type = "InputValue")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub type JsInputValue;
}

#[wasm_bindgen(typescript_custom_section)]
const ABI: &'static str = r#"
export type Visibility = "public" | "private";
export type Sign = "unsigned" | "signed";
export type AbiType =
{ kind: "field" } |
{ kind: "boolean" } |
{ kind: "string", length: number } |
{ kind: "integer", sign: Sign, width: number } |
{ kind: "array", length: number, type: AbiType } |
{ kind: "tuple", fields: AbiType[] } |
{ kind: "struct", path: string, fields: [string, AbiType][] };
kevaundray marked this conversation as resolved.
Show resolved Hide resolved

export type AbiParameter = {
name: string,
type: AbiType,
visibility: Visibility,
};

export type Abi = {
parameters: AbiParameter[],
param_witnesses: Record<string, number[]>,
return_type: AbiType | null,
return_witnesses: number[],
}
"#;

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = js_sys::Object, js_name = "Abi", typescript_type = "Abi")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub type JsAbi;
}

#[wasm_bindgen(js_name = abiEncode)]
pub fn abi_encode(
abi: JsValue,
inputs: JsValue,
return_value: JsValue,
abi: JsAbi,
inputs: JsInputMap,
return_value: Option<JsInputValue>,
) -> Result<JsWitnessMap, JsAbiError> {
console_error_panic_hook::set_once();
let abi: Abi = JsValueSerdeExt::into_serde(&abi).map_err(|err| err.to_string())?;
let abi: Abi =
JsValueSerdeExt::into_serde(&JsValue::from(abi)).map_err(|err| err.to_string())?;
let inputs: BTreeMap<String, JsonTypes> =
JsValueSerdeExt::into_serde(&inputs).map_err(|err| err.to_string())?;
let return_value: Option<InputValue> = if return_value.is_undefined() || return_value.is_null()
{
None
} else {
let toml_return_value =
JsValueSerdeExt::into_serde(&return_value).expect("could not decode return value");
Some(input_value_from_json_type(
toml_return_value,
abi.return_type.as_ref().unwrap(),
MAIN_RETURN_NAME,
)?)
};
JsValueSerdeExt::into_serde(&JsValue::from(inputs)).map_err(|err| err.to_string())?;
let return_value: Option<InputValue> = return_value
.map(|return_value| {
let toml_return_value = JsValueSerdeExt::into_serde(&JsValue::from(return_value))
.expect("could not decode return value");
input_value_from_json_type(
toml_return_value,
abi.return_type.as_ref().unwrap(),
MAIN_RETURN_NAME,
)
})
.transpose()?;

let abi_map = abi.to_btree_map();
let parsed_inputs: BTreeMap<String, InputValue> =
Expand All @@ -62,9 +117,10 @@ pub fn abi_encode(
}

#[wasm_bindgen(js_name = abiDecode)]
pub fn abi_decode(abi: JsValue, witness_map: JsWitnessMap) -> Result<JsValue, JsAbiError> {
pub fn abi_decode(abi: JsAbi, witness_map: JsWitnessMap) -> Result<JsValue, JsAbiError> {
console_error_panic_hook::set_once();
let abi: Abi = JsValueSerdeExt::into_serde(&abi).map_err(|err| err.to_string())?;
let abi: Abi =
JsValueSerdeExt::into_serde(&JsValue::from(abi)).map_err(|err| err.to_string())?;

let witness_map = WitnessMap::from(witness_map);

Expand Down
12 changes: 7 additions & 5 deletions tooling/noirc_abi_wasm/test/browser/abi_encode.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from '@esm-bundle/chai';
import initNoirAbi, { abiEncode, abiDecode, WitnessMap } from '@noir-lang/noirc_abi';
import initNoirAbi, { abiEncode, abiDecode, WitnessMap, Field } from '@noir-lang/noirc_abi';
import { DecodedInputs } from '../types';

beforeEach(async () => {
Expand All @@ -9,11 +9,13 @@ beforeEach(async () => {
it('recovers original inputs when abi encoding and decoding', async () => {
const { abi, inputs } = await import('../shared/abi_encode');

const initial_witness: WitnessMap = abiEncode(abi, inputs, null);
const initial_witness: WitnessMap = abiEncode(abi, inputs);
const decoded_inputs: DecodedInputs = abiDecode(abi, initial_witness);

expect(BigInt(decoded_inputs.inputs.foo)).to.be.equal(BigInt(inputs.foo));
expect(BigInt(decoded_inputs.inputs.bar[0])).to.be.equal(BigInt(inputs.bar[0]));
expect(BigInt(decoded_inputs.inputs.bar[1])).to.be.equal(BigInt(inputs.bar[1]));
const foo: Field = inputs.foo as Field;
const bar: Field[] = inputs.bar as Field[];
expect(BigInt(decoded_inputs.inputs.foo)).to.be.equal(BigInt(foo));
expect(BigInt(decoded_inputs.inputs.bar[0])).to.be.equal(BigInt(bar[0]));
expect(BigInt(decoded_inputs.inputs.bar[1])).to.be.equal(BigInt(bar[1]));
expect(decoded_inputs.return_value).to.be.null;
});
6 changes: 3 additions & 3 deletions tooling/noirc_abi_wasm/test/browser/errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ beforeEach(async () => {
it('errors when an integer input overflows', async () => {
const { abi, inputs } = await import('../shared/uint_overflow');

expect(() => abiEncode(abi, inputs, null)).to.throw(
expect(() => abiEncode(abi, inputs)).to.throw(
'The parameter foo is expected to be a Integer { sign: Unsigned, width: 32 } but found incompatible value Field(2³⁸)',
);
});

it('errors when passing a field in place of an array', async () => {
const { abi, inputs } = await import('../shared/field_as_array');

expect(() => abiEncode(abi, inputs, null)).to.throw('cannot parse value into Array { length: 2, typ: Field }');
expect(() => abiEncode(abi, inputs)).to.throw('cannot parse value into Array { length: 2, typ: Field }');
});

it('errors when passing an array in place of a field', async () => {
const { abi, inputs } = await import('../shared/array_as_field');

expect(() => abiEncode(abi, inputs, null)).to.throw('cannot parse value into Field');
expect(() => abiEncode(abi, inputs)).to.throw('cannot parse value into Field');
});
12 changes: 7 additions & 5 deletions tooling/noirc_abi_wasm/test/node/abi_encode.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { expect } from 'chai';
import { abiEncode, abiDecode, WitnessMap } from '@noir-lang/noirc_abi';
import { abiEncode, abiDecode, WitnessMap, Field } from '@noir-lang/noirc_abi';
import { DecodedInputs } from '../types';

it('recovers original inputs when abi encoding and decoding', async () => {
const { abi, inputs } = await import('../shared/abi_encode');

const initial_witness: WitnessMap = abiEncode(abi, inputs, null);
const initial_witness: WitnessMap = abiEncode(abi, inputs);
const decoded_inputs: DecodedInputs = abiDecode(abi, initial_witness);

expect(BigInt(decoded_inputs.inputs.foo)).to.be.equal(BigInt(inputs.foo));
expect(BigInt(decoded_inputs.inputs.bar[0])).to.be.equal(BigInt(inputs.bar[0]));
expect(BigInt(decoded_inputs.inputs.bar[1])).to.be.equal(BigInt(inputs.bar[1]));
const foo: Field = inputs.foo as Field;
const bar: Field[] = inputs.bar as Field[];
expect(BigInt(decoded_inputs.inputs.foo)).to.be.equal(BigInt(foo));
expect(BigInt(decoded_inputs.inputs.bar[0])).to.be.equal(BigInt(bar[0]));
expect(BigInt(decoded_inputs.inputs.bar[1])).to.be.equal(BigInt(bar[1]));
expect(decoded_inputs.return_value).to.be.null;
});
Loading