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 noir types package #2893

Merged
merged 13 commits into from
Sep 29, 2023
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
"workspaces": [
"compiler/wasm",
"compiler/source-resolver",
"tooling/noirc_abi_wasm",
"compiler/integration-tests",
"tooling/noir_js_types",
"tooling/noirc_abi_wasm",
"tooling/noir_js",
"acvm-repo/acvm_js",
"release-tests"
Expand Down
3 changes: 2 additions & 1 deletion tooling/noir_js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"dependencies": {
"@noir-lang/acvm_js": "workspace:*",
"@noir-lang/noirc_abi": "workspace:*",
"@noir-lang/types": "workspace:*",
"fflate": "^0.8.0"
},
"files": [
Expand Down Expand Up @@ -51,4 +52,4 @@
"tsc-multi": "^1.1.0",
"typescript": "^5.2.2"
}
}
}
8 changes: 4 additions & 4 deletions tooling/noir_js/src/base64_decode.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Since this is a simple function, we can use feature detection to
// see if we are in the nodeJs environment or the browser environment.
export function base64Decode(input: string): Uint8Array {
if (typeof Buffer !== 'undefined') {
if (typeof Buffer !== "undefined") {
// Node.js environment
return Buffer.from(input, 'base64');
} else if (typeof atob === 'function') {
return Buffer.from(input, "base64");
} else if (typeof atob === "function") {
// Browser environment
return Uint8Array.from(atob(input), (c) => c.charCodeAt(0));
} else {
throw new Error('No implementation found for base64 decoding.');
throw new Error("No implementation found for base64 decoding.");
}
}
10 changes: 6 additions & 4 deletions tooling/noir_js/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as acvm from '@noir-lang/acvm_js';
import * as abi from '@noir-lang/noirc_abi';
import * as acvm from "@noir-lang/acvm_js";
import * as abi from "@noir-lang/noirc_abi";

export { acvm, abi };

export { generateWitness } from './witness_generation.js';
export { acirToUint8Array, witnessMapToUint8Array } from './serialize.js';
export { generateWitness } from "./witness_generation.js";
export { acirToUint8Array, witnessMapToUint8Array } from "./serialize.js";

export { Noir } from "./program.js";
6 changes: 3 additions & 3 deletions tooling/noir_js/src/program.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Backend } from './backend/backend_interface.js';
import { generateWitness } from './witness_generation.js';
import { Backend, CompiledCircuit } from "@noir-lang/types";
import { generateWitness } from "./witness_generation.js";

export class Noir {
constructor(
private circuit: { bytecode: string; abi: any },
private circuit: CompiledCircuit,
private backend: Backend,
) {}

Expand Down
6 changes: 3 additions & 3 deletions tooling/noir_js/src/serialize.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { WitnessMap, compressWitness } from '@noir-lang/acvm_js';
import { decompressSync as gunzip } from 'fflate';
import { base64Decode } from './base64_decode.js';
import { WitnessMap, compressWitness } from "@noir-lang/acvm_js";
import { decompressSync as gunzip } from "fflate";
import { base64Decode } from "./base64_decode.js";

// After solving the witness, to pass it a backend, we need to serialize it to a Uint8Array
export function witnessMapToUint8Array(solvedWitness: WitnessMap): Uint8Array {
Expand Down
21 changes: 13 additions & 8 deletions tooling/noir_js/src/witness_generation.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { abiEncode } from '@noir-lang/noirc_abi';
import { base64Decode } from './base64_decode.js';
import { executeCircuit } from '@noir-lang/acvm_js';
import { witnessMapToUint8Array } from './serialize.js';
import { abiEncode } 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: { bytecode: string; abi: unknown },
compiledProgram: CompiledCircuit,
inputs: unknown,
): Promise<Uint8Array> {
// Throws on ABI encoding error
Expand All @@ -14,9 +15,13 @@ export async function generateWitness(
// Execute the circuit to generate the rest of the witnesses and serialize
// them into a Uint8Array.
try {
const solvedWitness = await executeCircuit(base64Decode(compiledProgram.bytecode), witnessMap, () => {
throw Error('unexpected oracle during execution');
});
const solvedWitness = await executeCircuit(
base64Decode(compiledProgram.bytecode),
witnessMap,
() => {
throw Error("unexpected oracle during execution");
},
);
return witnessMapToUint8Array(solvedWitness);
} catch (err) {
throw new Error(`Circuit execution failed: ${err}`);
Expand Down
2 changes: 1 addition & 1 deletion tooling/noir_js/test/backend/barretenberg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// @ts-ignore
import { Barretenberg, Crs, RawBuffer } from '@aztec/bb.js';
import { acirToUint8Array } from '../../src/index.js';
import { Backend } from '../../src/backend/backend_interface.js';
import { Backend } from '@noir-lang/types';

export class BarretenbergBackend implements Backend {
// These type assertions are used so that we don't
Expand Down
54 changes: 30 additions & 24 deletions tooling/noir_js/test/node/e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
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 { Noir } from '../../src/program.js';
import { BarretenbergBackend as Backend } from '../backend/barretenberg.js';
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 { Noir } from "../../src/program.js";
import { BarretenbergBackend as Backend } from "../backend/barretenberg.js";

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

Expand All @@ -23,11 +23,11 @@ it('end-to-end proof creation and verification (outer)', async () => {
expect(isValid).to.be.true;
});

it('end-to-end proof creation and verification (outer) -- Program API', async () => {
it("end-to-end proof creation and verification (outer) -- Program API", async () => {
// Noir.Js part
const inputs = {
x: '2',
y: '3',
x: "2",
y: "3",
};

// Initialize backend
Expand All @@ -42,11 +42,11 @@ it('end-to-end proof creation and verification (outer) -- Program API', async ()
expect(isValid).to.be.true;
});

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

Expand All @@ -73,11 +73,11 @@ it('end-to-end proof creation and verification (inner)', async () => {
// a prover and verifier class to more accurately reflect what happens in production.
//
// If its not fixable, we can leave it in as documentation of this behavior.
it('[BUG] -- bb.js null function or function signature mismatch (different instance) ', async () => {
it("[BUG] -- bb.js null function or function signature mismatch (different instance) ", async () => {
// Noir.Js part
const inputs = {
x: '2',
y: '3',
x: "2",
y: "3",
};
const serializedWitness = await generateWitness(assert_lt_json, inputs);

Expand All @@ -90,11 +90,13 @@ it('[BUG] -- bb.js null function or function signature mismatch (different insta
const verifier = await Backend.initialize(assert_lt_json);
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.',
"bb.js currently returns a bug when we try to verify a proof with a different Barretenberg instance that created it.",
);
} catch (error) {
const knownError = error as Error;
expect(knownError.message).to.contain('null function or function signature mismatch');
expect(knownError.message).to.contain(
"null function or function signature mismatch",
);
}
});

Expand All @@ -105,11 +107,11 @@ it('[BUG] -- bb.js null function or function signature mismatch (different insta
// If we only create one type of proof, then this works as expected.
//
// If we do not create an inner proof, then this will work as expected.
it('[BUG] -- bb.js null function or function signature mismatch (outer-inner) ', async () => {
it("[BUG] -- bb.js null function or function signature mismatch (outer-inner) ", async () => {
// Noir.Js part
const inputs = {
x: '2',
y: '3',
x: "2",
y: "3",
};
const serializedWitness = await generateWitness(assert_lt_json, inputs);

Expand All @@ -131,9 +133,13 @@ it('[BUG] -- bb.js null function or function signature mismatch (outer-inner) ',
// We can also try verifying an inner proof and it will fail.
// const isValidInner = await prover.verifyInnerProof(_proofInner);
// expect(isValidInner).to.be.true;
expect.fail('bb.js currently returns a bug when we try to verify an inner and outer proof with the same backend');
expect.fail(
"bb.js currently returns a bug when we try to verify an inner and outer proof with the same backend",
);
} catch (error) {
const knownError = error as Error;
expect(knownError.message).to.contain('null function or function signature mismatch');
expect(knownError.message).to.contain(
"null function or function signature mismatch",
);
}
});
68 changes: 42 additions & 26 deletions tooling/noir_js/test/node/smoke.test.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,89 @@
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 { 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";

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

it('string input and number input are the same', async () => {
it("string input and number input are the same", async () => {
const inputsString = {
x: '2',
y: '3',
x: "2",
y: "3",
};
const inputsNumber = {
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_json,
inputsString,
);
const solvedWitnessNumber = await generateWitness(
assert_lt_json,
inputsNumber,
);
expect(solvedWitnessString).to.deep.equal(solvedWitnessNumber);
});

it('string input and number input are the same', async () => {
it("string input and number input are the same", async () => {
const inputsString = {
x: '2',
y: '3',
x: "2",
y: "3",
};
const inputsNumber = {
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_json,
inputsString,
);
const solvedWitnessNumber = await generateWitness(
assert_lt_json,
inputsNumber,
);
expect(solvedWitnessString).to.deep.equal(solvedWitnessNumber);
});

it('0x prefixed string input for inputs will throw', async () => {
it("0x prefixed string input for inputs will throw", async () => {
const inputsHexPrefix = {
x: '0x2',
y: '0x3',
x: "0x2",
y: "0x3",
};

try {
await generateWitness(assert_lt_json, inputsHexPrefix);
expect.fail('Expected generatedWitness to throw, due to inputs being prefixed with 0x. Currently not supported');
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
// to support 0x prefixed inputs.
}
});

describe('input validation', () => {
it('x should be a uint64 not a string', async () => {
describe("input validation", () => {
it("x should be a uint64 not a string", async () => {
const inputs = {
x: 'foo',
y: '3',
x: "foo",
y: "3",
};

try {
await generateWitness(assert_lt_json, inputs);
expect.fail('Expected generatedWitness to throw, due to x not being convertible to a uint64');
expect.fail(
"Expected generatedWitness to throw, due to x not being convertible to a uint64",
);
} catch (error) {
const knownError = error as Error;
expect(knownError.message).to.equal(
'Expected witness values to be integers, provided value causes `invalid digit found in string` error',
"Expected witness values to be integers, provided value causes `invalid digit found in string` error",
);
}
});
Expand Down
1 change: 1 addition & 0 deletions tooling/noir_js_types/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
3 changes: 3 additions & 0 deletions tooling/noir_js_types/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ['../../.eslintrc.js'],
};
Empty file.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ export interface Backend {

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

export type CompiledCircuit = {
bytecode: string;
abi: object;
};
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
14 changes: 14 additions & 0 deletions tooling/noir_js_types/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "@noir-lang/types",
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
"collaborators": [
"The Noir Team <team@noir-lang.org>"
],
"version": "0.14.1",
"packageManager": "yarn@3.5.1",
"license": "(MIT OR Apache-2.0)",
"files": [
"lib",
"package.json"
],
"types": "lib/types.ts"
}
Loading