Skip to content

Commit

Permalink
Merge pull request #18 from CodeDragonVN/update_recursive_example
Browse files Browse the repository at this point in the history
chore: update recursion example for noir v0.24.0
  • Loading branch information
signorecello authored Mar 13, 2024
2 parents 199a0a7 + c574115 commit 10bb8c8
Show file tree
Hide file tree
Showing 18 changed files with 644 additions and 297 deletions.
10 changes: 5 additions & 5 deletions recursion/.prettierrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"arrowParens": "avoid",
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"proseWrap": "always"
"arrowParens": "avoid",
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"proseWrap": "always"
}
Binary file modified recursion/.yarn/install-state.gz
Binary file not shown.
12 changes: 8 additions & 4 deletions recursion/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
# Recursive proofs with Noir

Recursive proofs mean you prove that another proof is correct. A bit of a proofinception but bear with me:
Recursive proofs mean you prove that another proof is correct. A bit of a proofinception but bear
with me:

- You prove that `x != y`
- You pick that proof and send it to another circuit (the "outer" proof)
- You generate the outer proof and verify it

Why is this useful? In this example, it doesn't do much. But you could verify two proofs within a proof, which can be incredibly useful.
Why is this useful? In this example, it doesn't do much. But you could verify two proofs within a
proof, which can be incredibly useful.

You could also avoid verifying stuff on-chain for turn-based games, for example. Check out the [Noir Docs](https://noir-lang.org/docs/explainers/explainer-recursion) for a high-level explanation.
You could also avoid verifying stuff on-chain for turn-based games, for example. Check out the
[Noir Docs](https://noir-lang.org/docs/explainers/explainer-recursion) for a high-level explanation.

## Getting Started

1. Install dependencies by running `yarn`
2. For on-chain verification, open another terminal, and run `cd packages/hardhat && npx hardhat node`
2. For on-chain verification, open another terminal, and run
`cd packages/hardhat && npx hardhat node`
3. Run `yarn dev`

## Testing
Expand Down
42 changes: 21 additions & 21 deletions recursion/package.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
{
"name": "recursion",
"private": true,
"packageManager": "[email protected]",
"workspaces": [
"packages/hardhat",
"packages/noir",
"packages/next"
],
"engines": {
"node": "^20.10.0"
},
"scripts": {
"dev": "yarn workspace hardhat run deploy && yarn workspace next run dev",
"test": "yarn workspace hardhat run test",
"build": "yarn workspace next run build"
},
"dependencies": {
"@noir-lang/backend_barretenberg": "^0.23.0",
"@noir-lang/noir_js": "^0.23.0",
"@noir-lang/noir_wasm": "^0.23.0"
}
"name": "recursion",
"private": true,
"packageManager": "[email protected]",
"workspaces": [
"packages/hardhat",
"packages/noir",
"packages/next"
],
"engines": {
"node": "^20.10.0"
},
"scripts": {
"dev": "yarn workspace hardhat run deploy && yarn workspace next run dev",
"test": "yarn workspace hardhat run test",
"build": "yarn workspace next run build"
},
"dependencies": {
"@noir-lang/backend_barretenberg": "^0.25.0",
"@noir-lang/noir_js": "^0.25.0",
"@noir-lang/noir_wasm": "^0.25.0"
}
}
2 changes: 1 addition & 1 deletion recursion/packages/addresses.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"chainId":31337,"verifier":"0xdc64a140aa3e981100a9beca4e685f962f0cf6c9"}
{"chainId":31337,"verifier":"0xe7f1725e7734ce288f8367e1bb143e90bb3f0512"}
10 changes: 5 additions & 5 deletions recursion/packages/hardhat/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ describe('It compiles noir program code, receiving circuit bytes and abi object.
describe.only('Proof generation', async () => {
it('Should generate an intermediate proof', async () => {
const { witness } = await noirs.main.execute(mainInput);
intermediateProof = await backends.main.generateIntermediateProof(witness);
intermediateProof = await backends.main.generateProof(witness);

const { proof, publicInputs } = intermediateProof;
expect(proof instanceof Uint8Array).to.be.true;

const verified = await backends.main.verifyIntermediateProof({ proof, publicInputs });
const verified = await backends.main.verifyProof({ proof, publicInputs });
expect(verified).to.be.true;

const numPublicInputs = 1;
const { proofAsFields, vkAsFields, vkHash } =
await backends.main.generateIntermediateProofArtifacts(
await backends.main.generateRecursiveProofArtifacts(
{ publicInputs, proof },
numPublicInputs,
);
Expand All @@ -81,7 +81,7 @@ describe('It compiles noir program code, receiving circuit bytes and abi object.
});

it('Should generate a final proof with a recursive input', async () => {
finalProof = await noirs.recursive.generateFinalProof(recursiveInputs);
finalProof = await noirs.recursive.generateProof(recursiveInputs);
expect(finalProof.proof instanceof Uint8Array).to.be.true;
});
});
Expand All @@ -94,7 +94,7 @@ describe('It compiles noir program code, receiving circuit bytes and abi object.
});

it('Should verify off-chain', async () => {
const verified = await noirs.recursive.verifyFinalProof(finalProof);
const verified = await noirs.recursive.verifyProof(finalProof);
expect(verified).to.be.true;
});

Expand Down
21 changes: 10 additions & 11 deletions recursion/packages/next/hooks/useMainProofGeneration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,29 @@ export function useMainProofGeneration(inputs?: { x: string; y: string }) {
const [mainProofArtifacts, setMainProofArtifacts] = useState<ProofArtifacts>();

const proofGeneration = async () => {
if (!inputs) return;
if (!inputs) {
return;
}

const circuit = await getCircuit('main');
const backend = new BarretenbergBackend(circuit, { threads: navigator.hardwareConcurrency });
const noir = new Noir(circuit, backend);

const { witness } = await noir.execute(inputs);

const { publicInputs, proof } = await toast.promise(
backend.generateIntermediateProof(witness),
{
pending: 'Generating proof',
success: 'Proof generated',
error: 'Error generating proof',
},
);
const { publicInputs, proof } = await toast.promise(backend.generateProof(witness), {
pending: 'Generating proof',
success: 'Proof generated',
error: 'Error generating proof',
});

toast.promise(backend.verifyIntermediateProof({ proof, publicInputs }), {
toast.promise(backend.verifyProof({ proof, publicInputs }), {
pending: 'Verifying intermediate proof',
success: 'Intermediate proof verified',
error: 'Error verifying intermediate proof',
});

const mainProofArtifacts = await backend.generateIntermediateProofArtifacts(
const mainProofArtifacts = await backend.generateRecursiveProofArtifacts(
{ publicInputs, proof },
1, // 1 public input
);
Expand Down
13 changes: 11 additions & 2 deletions recursion/packages/next/hooks/useOffChainVerification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,26 @@ import { useEffect } from 'react';
import { toast } from 'react-toastify';
import { BarretenbergBackend } from '@noir-lang/backend_barretenberg';

export function useOffChainVerification(backend?: BarretenbergBackend, proofData?: ProofData) {
export function useOffChainVerification(
backend?: BarretenbergBackend,
proofData?: ProofData,
stopLoading?: () => void,
) {
useEffect(() => {
if (!proofData || !backend) return;
const { proof, publicInputs } = proofData;

toast.promise(backend.verifyFinalProof({ proof, publicInputs }), {
const verificationPromise = backend.verifyProof({ proof, publicInputs });
toast.promise(verificationPromise, {
pending: 'Verifying recursive proof off-chain',
success: 'Recursive proof verified off-chain',
error: 'Error verifying recursive proof off-chain',
});

verificationPromise.then(() => {
stopLoading?.();
});

// return () => {
// backend.destroy();
// };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function useRecursiveProofGeneration(
};

const { witness } = await noir.execute(recInput);
const proofData = await backend.generateFinalProof(witness);
const proofData = await backend.generateProof(witness);

setRecursiveBackend(backend);
setProofData(proofData);
Expand Down
2 changes: 1 addition & 1 deletion recursion/packages/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"typescript": "5.0.4"
},
"dependencies": {
"@noir-lang/types": "^0.23.0",
"@noir-lang/types": "^0.25.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-toastify": "^10.0.4",
Expand Down
70 changes: 70 additions & 0 deletions recursion/packages/next/pages/Page.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* Page.module.css */
.container {
max-width: 600px;
margin: 2rem auto;
padding: 2rem;
background: #f9f9f9;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.title {
color: #333;
text-align: center;
}

.proofForm {
display: flex;
flex-direction: column;
gap: 1rem;
}

.inputGroup {
display: flex;
flex-direction: column;
gap: 0.5rem;
}

.inputLabel {
font-size: 1rem;
color: #666;
}

.inputField {
padding: 0.5rem;
font-size: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
}

.submitButton {
padding: 0.5rem 1rem;
font-size: 1rem;
color: white;
background-color: #5c6bc0;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
}

.submitButton:hover {
background-color: #3f51b5;
}

.submitButton:disabled {
background-color: #9fa8da;
cursor: not-allowed;
}

.errorMessage {
color: #b71c1c;
text-align: center;
margin-top: 1rem;
}

.successMessage {
color: #2e7d32;
text-align: center;
margin-top: 1rem;
}
62 changes: 51 additions & 11 deletions recursion/packages/next/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
'use client';

import React, { useState } from 'react';
import styles from './Page.module.css';

import React, { useState, useCallback } from 'react';
import { useOffChainVerification } from '../hooks/useOffChainVerification';
import { useOnChainVerification } from '../hooks/useOnChainVerification';
import { useMainProofGeneration } from '../hooks/useMainProofGeneration';
import { useRecursiveProofGeneration } from '../hooks/useRecursiveProofGeneration';

export default function Page() {
const [xValue, setXValue] = useState('');
const [yValue, setYValue] = useState('');
const [loading, setLoading] = useState(false);
const stopLoading = useCallback(() => {
setLoading(false);
}, []);

const [input, setInput] = useState<{ x: string; y: string } | undefined>();
const mainProofArtifacts = useMainProofGeneration(input);
const { recursiveBackend, proofData } = useRecursiveProofGeneration(mainProofArtifacts, input);

useOffChainVerification(recursiveBackend, proofData);
useOffChainVerification(recursiveBackend, proofData, stopLoading);
useOnChainVerification(proofData);

const submit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
const elements = e.currentTarget.elements;
if (!elements) return;

Expand All @@ -25,18 +35,48 @@ export default function Page() {
setInput({ x: x.value, y: y.value });
};

const inputsFilled = xValue.trim() !== '' && yValue.trim() !== '';
const isDisabled = loading || !inputsFilled;

return (
<form className="gameContainer" onSubmit={submit}>
<h1>Recursive!</h1>
<p>This circuit proves that x and y are different (main proof)</p>
<div className={styles.container}>
<h1 className={styles.title}>Recursive!</h1>
<p>This circuit proves that x and y are different (main proof).</p>
<p>
Then it feeds that proof into another circuit, which then proves it verifies (recursive
proof)
proof).
</p>
<h2>Try it!</h2>
<input name="x" type={'text'} />
<input name="y" type={'text'} />
<button type="submit">Calculate proof</button>
</form>
<form className={styles.proofForm} onSubmit={submit}>
<div className={styles.inputGroup}>
<label htmlFor="x-input" className={styles.inputLabel}>
Value of x:
</label>
<input
id="x-input"
name="x"
type="text"
className={styles.inputField}
value={xValue}
onChange={e => setXValue(e.target.value)}
/>
</div>
<div className={styles.inputGroup}>
<label htmlFor="y-input" className={styles.inputLabel}>
Value of y:
</label>
<input
id="y-input"
name="y"
type="text"
className={styles.inputField}
value={yValue}
onChange={e => setYValue(e.target.value)}
/>
</div>
<button type="submit" className={styles.submitButton} disabled={isDisabled}>
Calculate proof
</button>
</form>
</div>
);
}
Loading

0 comments on commit 10bb8c8

Please sign in to comment.