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: Refine Noir.js API #2732

Merged
merged 42 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
ea66d2f
add base64 decode method
kevaundray Sep 16, 2023
723028f
add wip version of input validation
kevaundray Sep 16, 2023
56dc4c1
add initial witness generation method - does not handle return values
kevaundray Sep 16, 2023
581758f
add serialize method to abstract away the discrepancies with serializ…
kevaundray Sep 16, 2023
752535d
export new methods
kevaundray Sep 16, 2023
e94f00b
add mocha, prettier and add `type: module` for node
kevaundray Sep 16, 2023
c45fc8f
add compiled_examples folder for noir tests
kevaundray Sep 16, 2023
bcbc0ce
do not ignore the target folders
kevaundray Sep 16, 2023
6664b72
commit .json files for noir_compiled examples
kevaundray Sep 16, 2023
8bd188c
add bb test backend code
kevaundray Sep 16, 2023
f431c72
add comment to explain why we are doing type assertions
kevaundray Sep 16, 2023
ec3cc28
add some smoke tests
kevaundray Sep 16, 2023
153a72f
add bb.js test to show how it may interact with noir.js
kevaundray Sep 16, 2023
98bfa9f
increase timeout
kevaundray Sep 16, 2023
6386400
add another instance of null function
kevaundray Sep 16, 2023
e479c9c
update yarn.lock
kevaundray Sep 16, 2023
c7d48fd
Merge remote-tracking branch 'origin/master' into kw/more-noir-js
kevaundray Sep 16, 2023
4cdeeae
add `yarn test`
kevaundray Sep 16, 2023
dd6b837
remove .only
kevaundray Sep 16, 2023
de1f7ef
no test is using 1_mul so we can remove it
kevaundray Sep 16, 2023
1fb56d0
add separate inner proof -- this quickly verifies that inner proof cr…
kevaundray Sep 16, 2023
3666b5a
add bug-doc test that creates and verifies an inner and outer proof u…
kevaundray Sep 16, 2023
23f9bfa
Merge remote-tracking branch 'origin/master' into kw/more-noir-js
kevaundray Sep 17, 2023
f406033
update yarn.lock
kevaundray Sep 17, 2023
c7146dd
add correct eslint and prettier
kevaundray Sep 18, 2023
97ad1fc
change file to .cjs since we have instructed typescript to treat node…
kevaundray Sep 18, 2023
b01a215
update yarn.lock
kevaundray Sep 18, 2023
f2fd224
commit recursive
kevaundray Sep 18, 2023
13304e7
remove recursive
kevaundray Sep 18, 2023
5562e21
add array case to type_to_string
kevaundray Sep 18, 2023
5b3f24d
lint
kevaundray Sep 18, 2023
edcb749
explicit any
kevaundray Sep 18, 2023
ed6369c
hack around linting -> I think any should be explicit
kevaundray Sep 18, 2023
ae9a6fa
cannot remove ts-ignore unfortunately, or it will not compile
kevaundray Sep 18, 2023
be7e09b
update dependencies
kevaundray Sep 18, 2023
403d9b5
linter
kevaundray Sep 18, 2023
460a8a9
Merge branch 'master' into kw/more-noir-js
TomAFrench Sep 19, 2023
75c226d
chore: update integration test glob
TomAFrench Sep 19, 2023
7af1dc6
Merge branch 'master' into kw/more-noir-js
kevaundray Sep 20, 2023
f8ad376
fix package.json
kevaundray Sep 21, 2023
0a52241
Merge remote-tracking branch 'origin/master' into kw/more-noir-js
kevaundray Sep 21, 2023
6d5d960
cargo fmt
kevaundray Sep 21, 2023
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
2 changes: 1 addition & 1 deletion .github/workflows/test-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
pull_request:
paths:
- ./compiler/integration-tests
- ./compiler/integration-tests/**
schedule:
- cron: "0 2 * * *" # Run nightly at 2 AM UTC

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ test_cases.forEach((testInfo) => {

const noir_source_url = new URL(
`${base_relative_path}/${test_case}/src/main.nr`,
import.meta.url
import.meta.url,
);
const prover_toml_url = new URL(
`${base_relative_path}/${test_case}/Prover.toml`,
import.meta.url
import.meta.url,
);

const noir_source = await getFile(noir_source_url);
Expand Down Expand Up @@ -101,15 +101,15 @@ test_cases.forEach((testInfo) => {
try {
compressedByteCode = Uint8Array.from(
atob(compile_output.circuit),
(c) => c.charCodeAt(0)
(c) => c.charCodeAt(0),
);

solvedWitness = await executeCircuit(
compressedByteCode,
witnessMap,
() => {
throw Error("unexpected oracle");
}
},
);
} catch (e) {
expect(e, "Abi Encoding Step").to.not.be.an("error");
Expand All @@ -130,7 +130,7 @@ test_cases.forEach((testInfo) => {
await api.srsInitSrs(
new RawBuffer(crs.getG1Data()),
crs.numPoints,
new RawBuffer(crs.getG2Data())
new RawBuffer(crs.getG2Data()),
);

const acirComposer = await api.acirNewAcirComposer(CIRCUIT_SIZE);
Expand All @@ -140,22 +140,22 @@ test_cases.forEach((testInfo) => {
acirComposer,
acirUint8Array,
witnessUint8Array,
isRecursive
isRecursive,
);

// And this took ~5 minutes!
const verified = await api.acirVerifyProof(
acirComposer,
proof,
isRecursive
isRecursive,
);

expect(verified).to.be.true;
} catch (e) {
expect(e, "Proving and Verifying").to.not.be.an("error");
throw e;
}
}
},
);

suite.addTest(mochaTest);
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/hir/resolution/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use crate::hir_def::expr::{
HirMemberAccess, HirMethodCallExpression, HirPrefixExpression,
};

use crate::token::FunctionAttribute;
use crate::hir_def::traits::{Trait, TraitConstraint};
use crate::token::FunctionAttribute;
use regex::Regex;
use std::collections::{BTreeMap, HashSet};
use std::rc::Rc;
Expand Down
10 changes: 5 additions & 5 deletions compiler/source-resolver/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,30 @@ export const read_file = function (source_id: string): string {
return result;
} else {
throw new Error(
"Noir source resolver function MUST return String synchronously. Are you trying to return anything else, eg. `Promise`?"
"Noir source resolver function MUST return String synchronously. Are you trying to return anything else, eg. `Promise`?",
);
}
} else {
throw new Error(
"Not yet initialized. Use initializeResolver(() => string)"
"Not yet initialized. Use initializeResolver(() => string)",
);
}
};

function initialize(
noir_resolver: (source_id: string) => string
noir_resolver: (source_id: string) => string,
): (source_id: string) => string {
if (typeof noir_resolver === "function") {
return noir_resolver;
} else {
throw new Error(
"Provided Noir Resolver is not a function, hint: use function(module_id) => NoirSource as second parameter"
"Provided Noir Resolver is not a function, hint: use function(module_id) => NoirSource as second parameter",
);
}
}

export function initializeResolver(
resolver: (source_id: string) => string
resolver: (source_id: string) => string,
): void {
resolveFunction = initialize(resolver);
}
2 changes: 1 addition & 1 deletion compiler/source-resolver/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export declare const read_file: (source_id: string) => string;
export declare function initializeResolver(
resolver: (source_id: string) => string
resolver: (source_id: string) => string,
): void;
2 changes: 1 addition & 1 deletion compiler/wasm/test/node/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("noir wasm compilation", () => {

console.log(
"Compilation is a match? ",
wasmCircuitBase64 === cliCircuitBase64
wasmCircuitBase64 === cliCircuitBase64,
);

expect(wasmCircuitBase64).to.equal(cliCircuitBase64);
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
"@typescript-eslint/parser": "^5.59.5",
"chai": "^4.3.7",
"eslint": "^8.40.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-prettier": "^5.0.0",
"mocha": "^10.2.0",
"prettier": "^2.8.8",
"prettier": "3.0.3",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
},
Expand Down
1 change: 1 addition & 0 deletions tooling/noir_js/.eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
test/backend/barretenberg.ts
File renamed without changes.
1 change: 1 addition & 0 deletions tooling/noir_js/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!test/noir_compiled_examples/*/target
8 changes: 8 additions & 0 deletions tooling/noir_js/.mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"require": "ts-node/register",
"loader": "ts-node/esm",
"extensions": ["ts"],
"spec": [
"test/node/**/*.test.ts*"
]
}
6 changes: 6 additions & 0 deletions tooling/noir_js/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"parser": "typescript",
"printWidth": 120,
"singleQuote": true,
"trailingComma": "all"
}
20 changes: 19 additions & 1 deletion tooling/noir_js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
"version": "0.12.0",
"packageManager": "[email protected]",
"license": "(MIT OR Apache-2.0)",
"type": "module",
"dependencies": {
"@noir-lang/acvm_js": "0.27.0",
"@noir-lang/noirc_abi": "workspace:*"
"@noir-lang/noirc_abi": "workspace:*",
"fflate": "^0.8.0"
},
"files": [
"lib",
Expand All @@ -17,10 +19,26 @@
"main": "lib/index.js",
"types": "lib/index.d.ts",
"scripts": {
"dev": "tsc --watch",
"build": "tsc",
"test": "yarn test:node",
"test:node": "mocha --timeout 25000",
"prettier": "prettier 'src/**/*.ts'",
"prettier:fix": "prettier --write 'src/**/*.ts' 'test/**/*.ts'",
"lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0"
},
"devDependencies": {
"@aztec/bb.js": "0.7.2",
"@types/chai": "^4",
"@types/mocha": "^10.0.1",
"@types/node": "^20.6.2",
"@types/prettier": "^3",
"chai": "^4.3.8",
"eslint": "^8.40.0",
"eslint-plugin-prettier": "^5.0.0",
"mocha": "^10.2.0",
"prettier": "3.0.3",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
}
}
13 changes: 13 additions & 0 deletions tooling/noir_js/src/base64_decode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +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') {
// Node.js environment
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.');
}
}
11 changes: 8 additions & 3 deletions tooling/noir_js/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import * as acvm from "@noir-lang/acvm_js";
import * as noirc from "@noir-lang/noirc_abi";
export { acvm, noirc };
import * as acvm from '@noir-lang/acvm_js';
import * as abi from '@noir-lang/noirc_abi';

export { acvm, abi };

import { generateWitness } from './witness_generation.js';
import { acirToUint8Array, witnessMapToUint8Array } from './serialize.js';
export { acirToUint8Array, witnessMapToUint8Array, generateWitness };
55 changes: 55 additions & 0 deletions tooling/noir_js/src/input_validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Check if all of the input values are correct according to the ABI
export function validateInputs(inputs, abi) {
for (const param of abi.parameters) {
const inputValue = inputs[param.name];
if (inputValue === undefined) {
// This is checked by noirc_abi, so we could likely remove this check
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
return { isValid: false, error: `Input for ${param.name} is missing` };
}
if (!checkType(inputValue, param.type)) {
return {
isValid: false,
error: `Input for ${param.name} is the wrong type, expected ${type_to_string(param.type)}, got "${inputValue}"`,
};
}
}
return { isValid: true, error: null };
}

// Checks that value is of type "type"
// Where type is taken from the abi
function checkType(value, type) {
switch (type.kind) {
case 'integer':
if (type.sign === 'unsigned') {
return isUnsignedInteger(value, type.width);
}
// Other integer sign checks can be added here
break;
// Other type.kind checks can be added here
}
return false;
}

function type_to_string(type): string {
switch (type.kind) {
case 'integer':
if (type.sign === 'unsigned') {
return `uint${type.width}`;
}
break;
case 'array':
return `${type_to_string(type.element)}[${type.length}]`;
}
return 'unknown type';
}

// Returns true if `value` is an unsigned integer that is less than 2^{width}
function isUnsignedInteger(value: bigint, width: bigint) {
try {
const bigIntValue = BigInt(value);
return bigIntValue >= 0 && bigIntValue <= BigInt(2) ** BigInt(width) - 1n;
} catch (e) {
return false; // Not a valid integer
}
}
17 changes: 17 additions & 0 deletions tooling/noir_js/src/serialize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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 {
// TODO: We just want to serialize, but this will zip up the witness
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
// TODO so its not ideal
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
const compressedWitness = compressWitness(solvedWitness);
return gunzip(compressedWitness);
}

// Converts an bytecode to a Uint8Array
export function acirToUint8Array(base64EncodedBytecode): Uint8Array {
const compressedByteCode = base64Decode(base64EncodedBytecode);
return gunzip(compressedByteCode);
}
24 changes: 24 additions & 0 deletions tooling/noir_js/src/witness_generation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { abiEncode } from '@noir-lang/noirc_abi';
import { validateInputs } from './input_validation.js';
import { base64Decode } from './base64_decode.js';
import { WitnessMap, executeCircuit } from '@noir-lang/acvm_js';

// Generates the witnesses needed to feed into the chosen proving system
export async function generateWitness(compiledProgram, inputs): Promise<WitnessMap> {
// Validate inputs
const { isValid, error } = validateInputs(inputs, compiledProgram.abi);
if (!isValid) {
throw new Error(error?.toString());
}
const witnessMap = abiEncode(compiledProgram.abi, inputs, null);

// Execute the circuit to generate the rest of the witnesses
try {
const solvedWitness = await executeCircuit(base64Decode(compiledProgram.bytecode), witnessMap, () => {
throw Error('unexpected oracle during execution');
});
return solvedWitness;
} catch (err) {
throw new Error(`Circuit execution failed: ${err}`);
}
}
Loading