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 17 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
4 changes: 4 additions & 0 deletions examples/recursive-proofs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
build
codegen
circuits/*/export
23 changes: 23 additions & 0 deletions examples/recursive-proofs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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`
- Compile all the things: `yarn compile:all`
- Run main typescript: `yarn start`
8 changes: 8 additions & 0 deletions examples/recursive-proofs/circuits/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/recursive-proofs/circuits/recurseLeaf/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use dep::std;
use dep::sumLib::secretSum;

#[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
);
secretSum((public_inputs[2] as u64), num)
}
8 changes: 8 additions & 0 deletions examples/recursive-proofs/circuits/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/recursive-proofs/circuits/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; 5],
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[4] as u64
}
8 changes: 8 additions & 0 deletions examples/recursive-proofs/circuits/sum/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "sum"
type = "bin"
authors = [""]
compiler_version = ">=0.26.0"

[dependencies]
sumLib = { path = "../sumLib" }
11 changes: 11 additions & 0 deletions examples/recursive-proofs/circuits/sum/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use dep::sumLib::secretSum;

#[recursive]
fn main(a: pub u64, b: pub u64) -> pub u64 {
secretSum(a, b)
}

#[test]
fn test_not_equal() {
assert(main(1, 3) == 4);
}
7 changes: 7 additions & 0 deletions examples/recursive-proofs/circuits/sumLib/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "sumLib"
type = "lib"
authors = [""]
compiler_version = ">=0.26.0"

[dependencies]
10 changes: 10 additions & 0 deletions examples/recursive-proofs/circuits/sumLib/src/lib.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pub fn secretSum(a: u64, b: u64) -> u64 {
a + b
}

#[test]
fn test_sum() {
assert(secretSum(1, 2) == 3);
// Uncomment to make test fail
// assert(secretSum(1, 1) == 3);
}
124 changes: 124 additions & 0 deletions examples/recursive-proofs/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { CompiledCircuit } from '@noir-lang/types';
// import { main } from './codegen';
import { setTimeout } from "timers/promises";

import { Noir } from '@noir-lang/noir_js';
import { BarretenbergBackend } from '@noir-lang/backend_barretenberg';
import { join, resolve } from 'path';
import { compile, createFileManager } from '@noir-lang/noir_wasm';

import { ProofData } from '@noir-lang/types';

// Helper function to get compiled Noir program
async function getCircuit(name: string) {
const basePath = resolve(join('./circuits', 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, backend);
return { circuit, backend, noir };
}

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

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


async function start() {
// Create Noir objects for each circuit
const leaf: FullNoir = await fullNoirFromCircuit('sum'); // a + b = c
const recurseLeaf: FullNoir = await fullNoirFromCircuit('recurseLeaf'); // verify l1 + l2 = n1, then sum n1 + n2
const recurseNode: FullNoir = await fullNoirFromCircuit('recurseNode'); // verify n1 + n2 = root1

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

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

let { witness, returnValue } = await leaf.noir.execute(leafParams);
console.log("leaf: %d + %d = ", ...Object.values(leafParams), Number(returnValue).toString());
const innerProof1: ProofData = await leaf.backend.generateProof(witness);
console.log("Generating intermediate proof artifacts for leaf calculation...");
const artifacts1 = await leaf.backend.generateRecursiveProofArtifacts(
innerProof1,
numPubInputs + 1 // +1 for public return
);

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

const a = 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;

({ witness, returnValue } = await recurseLeaf.noir.execute(nodeParams));
console.log("recurseLeaf: %d + %d = ", a, b, Number(returnValue).toString());
const innerProof2: ProofData = await recurseLeaf.backend.generateProof(witness);
console.log("Generating intermediate proof artifacts recurseLeaf...");
const artifacts2 = await recurseLeaf.backend.generateRecursiveProofArtifacts(
innerProof2,
numPubInputs + 1 + 16 // +1 for public return +16 for hidden aggregation object
);
console.log("artifacts2.proof length = ", artifacts2.proofAsFields.length);

pub_inputs.push(returnValue.toString()); // leaf returns sum
pub_inputs.push(returnValue.toString()); // node also coded to return same value

// Generate outer proof artifacts (S3: verify 4+5=9)
const outerParams = {
verification_key: artifacts2.vkAsFields,
public_inputs: pub_inputs,
key_hash: artifacts2.vkHash,
proof: artifacts2.proofAsFields // the proof size of a function that verifies another proof was expected to be 109 bytes, but was still 93
};

console.log("Executing...");
({ witness, returnValue } = await recurseNode.noir.execute(outerParams));
console.log("Generating outer proof...");
const outerProof: ProofData = await recurseNode.backend.generateProof(witness);
console.log("Verifying outer proof...");
const resNode: boolean = await recurseNode.backend.verifyProof(outerProof);
console.log("Verification", resNode ? "PASSED" : "failed");

// Cleanup
recurseNode.backend.destroy();
recurseLeaf.backend.destroy();
leaf.backend.destroy();
}

start();
23 changes: 23 additions & 0 deletions examples/recursive-proofs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
jzaki marked this conversation as resolved.
Show resolved Hide resolved
"license": "UNLICENSED",
jzaki marked this conversation as resolved.
Show resolved Hide resolved
"scripts": {
jzaki marked this conversation as resolved.
Show resolved Hide resolved
"clean": "rm -rf build",
"compile": "yarn clean && tsc",
"start": "node build/main.js",
"clean:codegen": "rm -rf codegen",
"export:leaf": "nargo export --program-dir=./circuits/sum",
"export:recurseLeaf": "nargo export --program-dir=./circuits/recurseLeaf",
"export:recurseNode": "nargo export --program-dir=./circuits/recurseNode",
"export:all": "yarn clean:codegen && yarn export:leaf && yarn export:recurseLeaf && yarn export:recurseNode",
jzaki marked this conversation as resolved.
Show resolved Hide resolved
"codegen": "echo 'skipping' || yarn noir-codegen ./circuits/**/export/*.json",
"compile:all": "yarn export:all && yarn codegen && yarn compile"
},
"devDependencies": {
"@noir-lang/backend_barretenberg": "^0.26.0",
"@noir-lang/noir_codegen": "^0.26.0",
"@noir-lang/noir_js": "^0.26.0",
"@noir-lang/noir_wasm": "^0.26.0",
jzaki marked this conversation as resolved.
Show resolved Hide resolved
"@types/node": "^20.12.2",
"typescript": "^5.4.3"
}
}
Loading
Loading