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

chore: add recursive example #4969

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
5763e3d
Add 'how-to/recursive-proofs/' from commit '6ede541281290dcff86ca37d4…
jzaki Apr 5, 2024
7c2c4cb
Ignore circuit exports
jzaki Apr 5, 2024
5300045
add usage instructions
jzaki Apr 5, 2024
20da2c1
User intermediate proof in circuit
jzaki Apr 5, 2024
61e732b
Test altered proof
jzaki Apr 5, 2024
29a714f
verify outer proof
jzaki Apr 5, 2024
5e036fc
Verify two inner proofs (exception)
jzaki Apr 5, 2024
dfbdbea
minor updates to recursive howto script (exception)
jzaki Apr 5, 2024
39d20c0
Single recursive function (WIP)
jzaki Apr 11, 2024
f02851a
Revert "Single recursive function (WIP)"
jzaki Apr 11, 2024
112f83d
Modify for single nested recursion
jzaki Apr 12, 2024
fad7e64
Add verification before failing recursive generation
jzaki Apr 12, 2024
2ce50c5
fix public input length with hidden size
jzaki Apr 12, 2024
d33f7ff
WIP outer proof
jzaki Apr 12, 2024
9c3d5af
Fix execution errors with outer recursive proof
jzaki Apr 12, 2024
13a9ecf
Document recursive verification example
jzaki Apr 17, 2024
ba904f4
Add 'examples/' from commit '13a9ecf375be0b85bdd6cbfc0e93a530b839f5d5'
jzaki May 3, 2024
23e43eb
Update examples/recursive-proofs/package.json
jzaki May 3, 2024
96b8a22
Update examples/recursive-proofs/package.json
jzaki May 3, 2024
b8b2a50
Merge branch 'master' into jz/add-recursive-example
jzaki May 13, 2024
0ee3085
Remove use of codegen
jzaki May 13, 2024
88cb5a9
Update example to match tooling packages (WIP)
jzaki May 14, 2024
fbd850f
minor clarifications
jzaki May 14, 2024
b5805af
Fix indices from removed public param
jzaki May 16, 2024
f7b2e74
Merge branch 'master' into jz/add-recursive-example
jzaki May 21, 2024
e1a6b94
Update dir to match bb script example (in AP)
jzaki May 21, 2024
406616c
Merge branch 'master' into jz/add-recursive-example
jzaki Jun 12, 2024
0b793fc
Update example recursion js against script (wip)
jzaki Jun 18, 2024
66fb7b5
Merge branch 'master' into jz/add-recursive-example
jzaki Jun 18, 2024
ab76f35
Pass through artifacts for passing nested recursion test
jzaki Jun 20, 2024
cb4283f
Merge branch 'master' into jz/add-recursive-example
TomAFrench Jul 2, 2024
e10cc23
chore nits
TomAFrench Jul 2, 2024
c87212d
chore: add to CI
TomAFrench Jul 2, 2024
7093876
.
TomAFrench Jul 2, 2024
9a2202d
chore: remove unnecessary `before` block
TomAFrench Jul 2, 2024
1ee2315
.
TomAFrench Jul 2, 2024
05a2da2
Merge branch 'master' into jz/add-recursive-example
jzaki Jul 16, 2024
1a427c4
Consolidate test command
jzaki Jul 16, 2024
6d7d53a
Add noir_js and types to test-js workflow
jzaki Jul 16, 2024
3c50d5f
Add build of packages to CI js tests
jzaki Jul 16, 2024
636b139
Update .github/workflows/test-js-packages.yml
jzaki Jul 16, 2024
65ce6f8
Skip recursive test due like other recursive test
jzaki Jul 16, 2024
ccf5e3a
Adding tests around gate boundry benchmarks
jzaki Jul 17, 2024
b7dd587
Enable expected failing test at circuit limit
jzaki Jul 17, 2024
20ea3e6
Increase recursion test timeout
jzaki Jul 17, 2024
55002cd
Merge branch 'master' into jz/add-recursive-example
jzaki Jul 17, 2024
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
48 changes: 48 additions & 0 deletions .github/workflows/test-js-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,51 @@ jobs:
working-directory: ./examples/codegen_verifier
run: ./test.sh

- name: Run `recursion`
working-directory: ./examples/recursion
run: ./test.sh

test-js-examples:
name: Example JS scripts
runs-on: ubuntu-latest
needs: [build-acvm-js, build-noir-wasm, build-noirc-abi]
timeout-minutes: 30

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Download acvm_js package artifact
uses: actions/download-artifact@v4
with:
name: acvm-js
path: ./acvm-repo/acvm_js

- name: Download noir_wasm package artifact
uses: actions/download-artifact@v4
with:
name: noir_wasm
path: ./compiler/wasm

- name: Download noirc_abi package artifact
uses: actions/download-artifact@v4
with:
name: noirc_abi_wasm
path: ./tooling/noirc_abi_wasm

- name: Install Yarn dependencies
uses: ./.github/actions/setup

- name: Setup `js-tests`
run: |
# Note the lack of spaces between package names.
PACKAGES_TO_BUILD="@noir-lang/types,@noir-lang/backend_barretenberg,@noir-lang/noir_js"
yarn workspaces foreach -vtp --from "{$PACKAGES_TO_BUILD}" run build

- name: Run `recursion_js`
working-directory: ./examples/recursion_js
run: yarn test

external-repo-checks:
needs: [build-nargo]
runs-on: ubuntu-22.04
Expand All @@ -532,6 +577,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4

with:
repository: ${{ matrix.project.repo }}
path: test-repo
Expand Down Expand Up @@ -579,6 +625,8 @@ jobs:
- test-integration-node
- test-integration-browser
- test-examples
- test-js-examples


steps:
- name: Report overall success
Expand Down
2 changes: 2 additions & 0 deletions examples/recursion_js/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
lib
3 changes: 3 additions & 0 deletions examples/recursion_js/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ['../../.eslintrc.js'],
};
4 changes: 4 additions & 0 deletions examples/recursion_js/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build
test/*/export

test/recursive
11 changes: 11 additions & 0 deletions examples/recursion_js/.mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"require": "ts-node/register",
"loader": "ts-node/esm",
"extensions": [
"ts",
"cjs"
],
"spec": [
"test/**/*.test.ts*"
]
}
25 changes: 25 additions & 0 deletions examples/recursion_js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# A minimal example of nested recursion

## About

The code in this project shows recursive verification of Noir functions.

The primary function is simply addition, and verifies the re-calculation of one path up a binary tree from two leaf nodes.
A more useful application of this design would be proving the hash of data in a merkle tree (leaf nodes), up to the merkle root. Amortizing the cost of proving each hash per nested call.

## The circuits
The function doing the calculation, in this case addition, is in the sumLib. It is used in both recursive circuits: `sum` and `recursiveLeaf`.

## Verification
Results of a call to `sum` are verified in `recursiveLeaf`, which itself also calls `sum` again. The results of the `recursiveLeaf` call are then verified in `recursiveNode`.

That is:
- `recursiveNode` verifies `recursiveLeaf` artifacts
- `recursiveLeaf` verifies `sum` artifacts

## Using this project
- Install dependencies: `yarn`
- Check build succeeds: `yarn build`
- Run tests: `yarn test`

Note: the test functions take some time (can be 2mins or so for nested/recursive proofs).
30 changes: 30 additions & 0 deletions examples/recursion_js/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"type": "module",
"private": true,
"license": "(MIT OR Apache-2.0)",
"scripts": {
"dev": "tsc-multi --watch",
"build": "tsc",
"test": "mocha --timeout 300000 --exit --config ./.mocharc.json",
"lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0",
"publish": "echo (nothing to publish `$npm_package_name`)",
"clean": "rm -rf ./build"
},
"devDependencies": {
"@noir-lang/backend_barretenberg": "workspace:*",
"@noir-lang/noir_js": "workspace:*",
"@noir-lang/noir_wasm": "workspace:*",
"@types/chai": "^4",
"@types/mocha": "^10.0.1",
"@types/node": "^20.6.2",
"@types/prettier": "^3",
"chai": "^4.4.1",
"eslint": "^8.57.0",
"eslint-plugin-prettier": "^5.1.3",
"mocha": "^10.2.0",
"prettier": "3.2.5",
"ts-node": "^10.9.1",
"tsx": "^4.6.2",
"typescript": "^5.4.2"
}
}
7 changes: 7 additions & 0 deletions examples/recursion_js/test/2^17/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "2^17"
type = "bin"
authors = [""]
compiler_version = ">=0.19.4"

[dependencies]
1 change: 1 addition & 0 deletions examples/recursion_js/test/2^17/Prover.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x = "10"
7 changes: 7 additions & 0 deletions examples/recursion_js/test/2^17/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
global N = 32768;

fn main(x: Field) {
for i in 0 .. N {
assert(x != x * i as Field * 2);
};
}
7 changes: 7 additions & 0 deletions examples/recursion_js/test/2^18/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "2^18"
type = "bin"
authors = [""]
compiler_version = ">=0.19.4"

[dependencies]
1 change: 1 addition & 0 deletions examples/recursion_js/test/2^18/Prover.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x = "10"
7 changes: 7 additions & 0 deletions examples/recursion_js/test/2^18/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
global N = 65536;

fn main(x: Field) {
for i in 0 .. N {
assert(x != x * i as Field * 2);
};
}
8 changes: 8 additions & 0 deletions examples/recursion_js/test/recurseLeaf/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "recurse"
type = "bin"
authors = [""]
compiler_version = ">=0.26.0"

[dependencies]
sumLib = { path = "../sumLib" }
20 changes: 20 additions & 0 deletions examples/recursion_js/test/recurseLeaf/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use dep::std;

#[recursive]
fn main(
verification_key: [Field; 114],
public_inputs: pub [Field; 3],
key_hash: Field,
proof: [Field; 93],
num: u64
) -> pub u64 {
// verify sum so far was computed correctly
std::verify_proof(
verification_key.as_slice(),
proof.as_slice(),
public_inputs.as_slice(),
key_hash
);
// Take output of previous proof and add another number to it.
public_inputs[2] as u64 + num
}
8 changes: 8 additions & 0 deletions examples/recursion_js/test/recurseNode/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "recurse"
type = "bin"
authors = [""]
compiler_version = ">=0.26.0"

[dependencies]
sumLib = { path = "../sumLib" }
17 changes: 17 additions & 0 deletions examples/recursion_js/test/recurseNode/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use dep::std;

fn main(
verification_key: [Field; 114],
public_inputs: pub [Field; 20],
key_hash: Field,
proof: [Field; 93] // 109 bytes were supposed to be required to verify recursive proofs (WIP)
) -> pub u64 {
// verify sum was computed correctly
std::verify_proof(
verification_key.as_slice(),
proof.as_slice(),
public_inputs.as_slice(),
key_hash
);
public_inputs[3] as u64
}
145 changes: 145 additions & 0 deletions examples/recursion_js/test/recursive.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { expect } from 'chai';
import { join, resolve } from 'path';

import { CompiledCircuit, ProofData } from '@noir-lang/types';
import { Noir } from '@noir-lang/noir_js';
import { BarretenbergBackend } from '@noir-lang/backend_barretenberg';
import { compile, createFileManager } from '@noir-lang/noir_wasm';

// Helper function to get compiled Noir program
async function getCircuit(name: string) {
const basePath = resolve(join('./test', name));
const fm = createFileManager(basePath);
const compiled = await compile(fm, basePath);
if (!('program' in compiled)) {
throw new Error('Compilation failed');
}
return compiled.program;
}

const cores = 12;

// Helper function to create Noir objects
async function fullNoirFromCircuit(circuitName: string): Promise<FullNoir> {
const circuit: CompiledCircuit = await getCircuit(circuitName);
const backend: BarretenbergBackend = new BarretenbergBackend(circuit, { threads: cores });
const noir: Noir = new Noir(circuit);
return { circuit, backend, noir };
}

// Type to associate related Noir objects
type FullNoir = {
circuit: CompiledCircuit;
backend: BarretenbergBackend;
noir: Noir;
};

async function testBenchmark(name: string) {
console.log('Testing ', name);
const p2_n = await fullNoirFromCircuit(name);
console.log('Execute');
const { witness } = await p2_n.noir.execute({ x: 1 });
console.log('genProof from witness');
const proof = await p2_n.backend.generateProof(witness);
console.log('genRecProof');
/*const p2_n_artifacts =*/ await p2_n.backend.generateRecursiveProofArtifacts(proof, 0);
console.log('Done generating recursive proof artifacts for ', name);
p2_n.backend.destroy();
}

// Calculate example sum of two leaf nodes up left branch
// S3
// S2 9
// / \
// / \
// S1 4 5
// / \ / \
// 1 3 # #

describe('can verify recursive proofs', () => {
it('does generateProof for circuit with 2^17 gates', async () => {
await testBenchmark('2^17');
});

it('[BUG] -- does NOT generateProof for circuit with 2^18 gates', async () => {
try {
await testBenchmark('2^18');
} catch (error) {
expect(error).to.be.an('Error');
}
});

// TODO(https://github.com/AztecProtocol/aztec-packages/issues/6672): Reinstate this test.
it.skip('does recursive proof', async () => {
jzaki marked this conversation as resolved.
Show resolved Hide resolved
const leaf = await fullNoirFromCircuit('sum'); // a + b = c

// Generate leaf proof artifacts (S1, addition of 1 and 3)

// Leaf params of left branch
const leafParams = { a: 1, b: 3 };
let numPubInputs = 2;

const leafExecution = await leaf.noir.execute(leafParams);
console.log('leaf: %d + %d = ', ...Object.values(leafParams), Number(leafExecution.returnValue).toString());
const innerProof1: ProofData = await leaf.backend.generateProof(leafExecution.witness);
console.log('\n\ninnerProof1.publicInputs\n', innerProof1.publicInputs);
console.log('Generating intermediate proof artifacts for leaf calculation...');
const artifacts1 = await leaf.backend.generateRecursiveProofArtifacts(
innerProof1,
numPubInputs + 1, // +1 for public return
);
leaf.backend.destroy();

const pub_inputs: string[] = [
...Object.values(leafParams).map((n) => Number(n).toString()),
Number(leafExecution.returnValue).toString(),
];

const a = leafExecution.returnValue;
const b = 5; // Sum of leaf branches beneath right node

// Generate node proof artifacts (S2: verify 1+3=4 proof, add 5)
const nodeParams = {
verification_key: artifacts1.vkAsFields,
public_inputs: pub_inputs, // public, each counted individually
key_hash: artifacts1.vkHash,
proof: artifacts1.proofAsFields,
num: 5,
};
numPubInputs = pub_inputs.length;

const recurseLeaf = await fullNoirFromCircuit('recurseLeaf'); // verify l1 + l2 = n1, then sum n1 + n2
const nodeExecution = await recurseLeaf.noir.execute(nodeParams);
console.log('recurseLeaf: %d + %d = ', a, b, Number(nodeExecution.returnValue).toString());
const innerProof2: ProofData = await recurseLeaf.backend.generateProof(nodeExecution.witness);
jzaki marked this conversation as resolved.
Show resolved Hide resolved
console.log('Generating intermediate proof artifacts recurseLeaf...');
//TODO: maybe not do magic +16
const artifacts2 = await recurseLeaf.backend.generateRecursiveProofArtifacts(
innerProof2,
numPubInputs + 1 + 16, // +1 for public return +16 for hidden aggregation object
);
console.log('innerProof2.publicInputs length = ', innerProof2.publicInputs.length);
console.log('artifacts2.proof length = ', artifacts2.proofAsFields.length);
recurseLeaf.backend.destroy();

// Generate outer proof artifacts (S3: verify 4+5=9)
const outerParams = {
verification_key: artifacts2.vkAsFields,
public_inputs: innerProof2.publicInputs, // 20 = 4 public inputs + 16 aggregation bytes,
key_hash: artifacts2.vkHash,
proof: artifacts2.proofAsFields, // 93 = 109 - 16 aggregation bytes in public inputs
};

const recurseNode = await fullNoirFromCircuit('recurseNode'); // verify n1 + n2 = root1
console.log('Executing...');
const rootExecution = await recurseNode.noir.execute(outerParams);
console.log('Generating outer proof...');
const outerProof: ProofData = await recurseNode.backend.generateProof(rootExecution.witness);
console.log('Verifying outer proof...');
const resNode: boolean = await recurseNode.backend.verifyProof(outerProof);
console.log('Verification', resNode ? 'PASSED' : 'failed');
expect(resNode).to.be.true;

recurseNode.backend.destroy();
});
});
Loading
Loading