4 |
5 |
6 | Vite + React + TS
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-noir",
3 | "version": "0.1.3",
4 | "type": "module",
5 | "description": "This is a reference repo to help you get started with writing zero-knowledge circuits with [Noir](https://noir-lang.org/).",
6 | "bin": "npx.js",
7 | "author": "",
8 | "license": "Apache-2.0",
9 | "dependencies": {
10 | "@inquirer/input": "^1.2.16",
11 | "@inquirer/select": "^1.3.3",
12 | "commander": "^11.1.0",
13 | "tiged": "^2.12.6"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.github/actions/setup-nargo/action.yml:
--------------------------------------------------------------------------------
1 | name: Install Nargo
2 | description: Installs the workspace's nargo
3 | inputs:
4 | version:
5 | description: The version of the project to install dependencies for
6 | required: true
7 |
8 | runs:
9 | using: composite
10 | steps:
11 | - name: Install Nargo
12 | uses: noir-lang/noirup@v0.1.2
13 | with:
14 | toolchain: ${{ inputs.version }}
15 |
16 | - name: Use Nargo
17 | run: nargo --version
18 | shell: bash
19 |
--------------------------------------------------------------------------------
/with-foundry/foundry.toml:
--------------------------------------------------------------------------------
1 | [profile.default]
2 | src = "src"
3 | out = "out"
4 | libs = ["lib"]
5 | fs_permissions = [{ access = "read-write", path = "./"},{ access = "read-write", path = "/tmp/"}]
6 | ffi = true
7 |
8 | [profile.remappings]
9 | # ds-test = "lib/forge-std/lib/ds-test/src/"
10 | forge-std = "lib/foundry-noir-helper/lib/forge-std/src/"
11 | # foundry-noir-helper = "lib/foundry-noir-helper/src/"
12 |
13 | # See more config options https://github.com/foundry-rs/foundry/tree/master/crates/config
14 |
--------------------------------------------------------------------------------
/with-foundry/contract/Starter.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.8.17;
2 |
3 | import "../circuits/target/contract.sol";
4 |
5 | contract Starter {
6 | UltraVerifier public verifier;
7 |
8 | constructor(UltraVerifier _verifier) {
9 | verifier = _verifier;
10 | }
11 |
12 | function verifyEqual(bytes calldata proof, bytes32[] calldata y) public view returns (bool) {
13 | bool proofResult = verifier.verify(proof, y);
14 | require(proofResult, "Proof is not valid");
15 | return proofResult;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vite-hardhat/packages/vite/wagmi.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from '@wagmi/cli';
2 | import { react, hardhat } from '@wagmi/cli/plugins';
3 | import deployment from '../../deployment.json';
4 |
5 | export default defineConfig({
6 | out: 'artifacts/generated.ts',
7 | plugins: [
8 | react(),
9 | hardhat({
10 | project: '.',
11 | artifacts: '../artifacts',
12 | deployments: {
13 | UltraVerifier: {
14 | [deployment.networkConfig.id]: deployment.address as `0x${string}`,
15 | },
16 | },
17 | }),
18 | ],
19 | });
20 |
--------------------------------------------------------------------------------
/with-foundry/script/Starter.s.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.8.17;
2 |
3 | import "forge-std/Script.sol";
4 | import "../circuits/target/contract.sol";
5 | import "../contract/Starter.sol";
6 |
7 | contract StarterScript is Script {
8 | Starter public starter;
9 | UltraVerifier public verifier;
10 |
11 | function setUp() public {}
12 |
13 | function run() public {
14 | uint256 deployerPrivateKey = vm.envUint("LOCALHOST_PRIVATE_KEY");
15 | vm.startBroadcast(deployerPrivateKey);
16 |
17 | verifier = new UltraVerifier();
18 | starter = new Starter(verifier);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License 2.0
2 |
3 | Copyright 2025 Spilsbury Holdings Ltd
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2 | // README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
3 | {
4 | "name": "Existing Dockerfile",
5 | "build": {
6 | "context": ".",
7 | "dockerfile": "Dockerfile"
8 | },
9 | "customizations": {
10 | // Configure properties specific to VS Code.
11 | "vscode": {
12 | // Set *default* container specific settings.json values on container create.
13 | "settings": {},
14 | "extensions": ["noir-lang.vscode-noir"]
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/vite-hardhat/packages/noir/compile.ts:
--------------------------------------------------------------------------------
1 | import { compile, createFileManager } from '@noir-lang/noir_wasm';
2 | import { CompiledCircuit } from '@noir-lang/types';
3 |
4 | export async function getCircuit() {
5 | const fm = createFileManager('/');
6 | const main = (await fetch(new URL(`./src/main.nr`, import.meta.url)))
7 | .body as ReadableStream;
8 | const nargoToml = (await fetch(new URL(`./Nargo.toml`, import.meta.url)))
9 | .body as ReadableStream;
10 |
11 | fm.writeFile('./src/main.nr', main);
12 | fm.writeFile('./Nargo.toml', nargoToml);
13 | const result = await compile(fm);
14 | if (!('program' in result)) {
15 | throw new Error('Compilation failed');
16 | }
17 | return result.program as CompiledCircuit;
18 | }
19 |
--------------------------------------------------------------------------------
/vite-hardhat/packages/vite/hooks/useOffChainVerification.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { ProofData } from '@noir-lang/types';
4 | import { useEffect } from 'react';
5 | import { toast } from 'react-toastify';
6 | import { UltraPlonkBackend } from '@aztec/bb.js';
7 | import { Noir } from '@noir-lang/noir_js';
8 |
9 | export function useOffChainVerification(
10 | backend: UltraPlonkBackend,
11 | noir?: Noir,
12 | proofData?: ProofData,
13 | ) {
14 | useEffect(() => {
15 | if (!proofData || !noir) return;
16 |
17 | toast.promise(backend.verifyProof(proofData), {
18 | pending: 'Verifying proof off-chain',
19 | success: 'Proof verified off-chain',
20 | error: 'Error verifying proof off-chain',
21 | });
22 | }, [proofData]);
23 | }
24 |
--------------------------------------------------------------------------------
/vite-hardhat/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | .env
4 | .tmp
5 |
6 | # dependencies
7 | /node_modules
8 | /.pnp
9 | .pnp.js
10 |
11 | # testing
12 | /coverage
13 |
14 | # next.js
15 | /.next/
16 | /out/
17 |
18 | # production
19 | /build
20 |
21 | # misc
22 | .DS_Store
23 | *.pem
24 |
25 | # debug
26 | npm-debug.log*
27 | yarn-debug.log*
28 | yarn-error.log*
29 | .pnpm-debug.log*
30 |
31 | # local env files
32 | .env*.local
33 |
34 | # vercel
35 | .vercel
36 |
37 | # typescript
38 | *.tsbuildinfo
39 | next-env.d.ts
40 |
41 | # hardhat
42 | cache
43 |
44 | # artifacts
45 | typechain-types
46 | proofs
47 |
48 | # noir
49 | crs
50 |
51 | # other
52 | .vscode
53 | .DS_Store
54 | artifacts
55 | .yarn/
56 |
57 | noir/target/
58 | contracts
59 | dist
60 | deployment.json
61 | bun.lockb
62 |
--------------------------------------------------------------------------------
/vite-hardhat/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2022",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": true,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "NodeNext",
13 | "moduleResolution": "NodeNext",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "preserve",
18 | "noImplicitAny": true,
19 | "strictNullChecks": true,
20 | "incremental": true
21 | },
22 | "ts-node": {
23 | "experimentalResolver": true,
24 | "files": true
25 | },
26 | "exclude": ["dist", "node_modules"],
27 | "include": ["packages", "./tests"],
28 | "files": ["hardhat.config.cts"]
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/vite_hardhat.yaml:
--------------------------------------------------------------------------------
1 | name: PR - vite-hardhat
2 | on:
3 | pull_request:
4 | paths:
5 | - 'vite-hardhat/**'
6 |
7 | jobs:
8 | test-vite-hardhat:
9 | runs-on: ubuntu-latest
10 | defaults:
11 | run:
12 | working-directory: vite-hardhat
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 |
17 | - name: Set up bun
18 | uses: oven-sh/setup-bun@v1
19 |
20 | - name: Get latest version
21 | id: versions_step
22 | run: |
23 | output=$(node ../.github/scripts/latest.js)
24 | echo "Output from Node.js script: $output"
25 |
26 | STABLE=$(echo $output | jq -r '.stable')
27 | echo "::set-output name=stable::$STABLE"
28 |
29 | - name: Install dependencies
30 | run: bun install
31 |
32 | - name: Compile
33 | run: bunx hardhat compile
34 |
35 | - name: Run tests
36 | run: bun run test
37 |
--------------------------------------------------------------------------------
/npx.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { Command } from 'commander';
3 | import select from '@inquirer/select';
4 | import input from '@inquirer/input';
5 | const program = new Command();
6 | import tiged from 'tiged';
7 |
8 | program.action(async () => {
9 | const appType = await select({
10 | message: 'Please choose an option:',
11 | choices: [
12 | { value: 'vite-hardhat', name: 'Browser App using Vite' },
13 | { value: 'with-foundry', name: 'Solidity App using Foundry' },
14 | ],
15 | });
16 |
17 | console.log(`You chose: ${appType}`);
18 |
19 | const appName = await input({
20 | message: 'Your app name:',
21 | default: 'my-noir-app',
22 | });
23 |
24 | const emitter = tiged(`noir-lang/noir-starter/${appType}`, {
25 | disableCache: true,
26 | force: true,
27 | verbose: true,
28 | });
29 |
30 | emitter.on('info', info => {
31 | console.log(info.message);
32 | });
33 |
34 | emitter.clone(`./${appName}`).then(() => {
35 | console.log('done');
36 | });
37 | });
38 |
39 | program.parse();
40 |
--------------------------------------------------------------------------------
/with-foundry/script/Verify.s.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.8.17;
2 |
3 | import "forge-std/Script.sol";
4 | import "../circuits/target/contract.sol";
5 | import "../contract/Starter.sol";
6 |
7 | contract VerifyScript is Script {
8 | Starter public starter;
9 | UltraVerifier public verifier;
10 |
11 | function setUp() public {}
12 |
13 | function run() public returns (bool) {
14 | uint256 deployerPrivateKey = vm.envUint("LOCALHOST_PRIVATE_KEY");
15 | vm.startBroadcast(deployerPrivateKey);
16 |
17 | verifier = new UltraVerifier();
18 | starter = new Starter(verifier);
19 |
20 | string memory proof = vm.readLine("./circuits/proofs/with_foundry.proof");
21 | bytes memory proofBytes = vm.parseBytes(proof);
22 |
23 | bytes32[] memory correct = new bytes32[](2);
24 | correct[0] = bytes32(0x0000000000000000000000000000000000000000000000000000000000000003);
25 | correct[1] = correct[0];
26 |
27 | bool equal = starter.verifyEqual(proofBytes, correct);
28 | return equal;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Noir Starters
2 |
3 | This is a reference repo to help you get started with writing zero-knowledge circuits with [Noir](https://noir-lang.org/).
4 |
5 | Each project is an example you can use as template. Feel free to mix them in order to find the best combination of technology that suits your needs.
6 |
7 | ## Getting started
8 |
9 | If you have [node](https://nodejs.org/en/download) installed, just open a terminal and run:
10 |
11 | ```bash
12 | npx create-noir
13 | ```
14 |
15 | ### Templates
16 |
17 | - Foundry: [`./with-foundry`](./with-foundry)
18 | - Vite + Hardhat: [`./vite-hardhat`](./vite-hardhat)
19 |
20 | ## Example Projects
21 |
22 | You can view more complete example projects written in Noir in the [Noir Examples](https://github.com/noir-lang/noir-examples) repo.
23 |
24 | ## Support
25 |
26 | Need help? Join the [Noir Discord](https://discord.gg/JtqzkdeQ6G) or reach out on [Twitter](https://twitter.com/NoirLang).
27 |
28 | ## Contributing
29 |
30 | We welcome contributions! Check out the [contributing guidelines](./CONTRIBUTING.md) for more info.
31 |
--------------------------------------------------------------------------------
/vite-hardhat/packages/vite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite",
3 | "type": "module",
4 | "description": "A template repository to get started with writing zero knowledge programs with Noir.",
5 | "scripts": {
6 | "dev": "bun wagmi && vite dev",
7 | "wagmi": "wagmi generate"
8 | },
9 | "dependencies": {
10 | "react": "^18.2.0",
11 | "react-dom": "^18.2.0",
12 | "react-toastify": "^9.1.1",
13 | "viem": "2.x",
14 | "wagmi": "2.10.0"
15 | },
16 | "devDependencies": {
17 | "@nomicfoundation/hardhat-chai-matchers": "^2.0.7",
18 | "@tanstack/react-query": "5.44.0",
19 | "@tanstack/react-query-persist-client": "5.0.5",
20 | "@types/react-dom": "^18.3.1",
21 | "@tanstack/react-query-devtools": "5.0.5",
22 | "@types/bun": "^1.1.12",
23 | "@types/node": "^18.15.5",
24 | "@types/react": "^18.0.26",
25 | "@vitejs/plugin-react-swc": "^3.5.0",
26 | "@wagmi/cli": "^2.1.10",
27 | "chai": "^4.2.0",
28 | "ts-node": "^10.9.1",
29 | "typechain": "^8.1.0",
30 | "typescript": "^4.9.3",
31 | "vite": "^5.0.6"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via an issue.
4 |
5 | ## Contributing to an existing example project
6 |
7 | If you would like to contribute to an existing example project, please open an issue and tag one of the original
8 | authors or maintainers ([@critesjosh](https://github.com/critesjosh), [@signorecello](https://github.com/signorecello)) with your suggestion.
9 |
10 | ## Adding a new example project
11 |
12 | We are currently not accepting new contributions with starters. However, the Noir team encourages everyone to contribute to [awesome-noir](https://github.com/noir-lang/awesome-noir).
13 |
14 | If you want to contribute with unique ideas, check out Awesome Noir to see what's been done so far, or what's the status of each of the projects. Some unique ideas for boilerplates or examples, if you don't know where to start, would be:
15 |
16 | - Something integrating Angular
17 | - A React Native starter
18 | - A Svelte example
19 | - A server-side-rendered Go or Python app
20 |
21 | Basically anything that combines different technologies would be adding a lot to the ecosystem. If you'd like to discuss further, please message [@signorecello](https://github.com/signorecello) or [@critesjosh](https://github.com/critesjosh)!
22 |
--------------------------------------------------------------------------------
/vite-hardhat/packages/vite/components/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import React from 'react';
3 |
4 | import { useOnChainVerification } from '../hooks/useOnChainVerification.js';
5 | import { useProofGeneration } from '../hooks/useProofGeneration.js';
6 | import { useOffChainVerification } from '../hooks/useOffChainVerification.js';
7 |
8 | function Component() {
9 | const [input, setInput] = useState<{ x: string; y: string } | undefined>();
10 | const { noir, proofData, backend } = useProofGeneration(input);
11 | useOffChainVerification(backend!, noir, proofData);
12 | const verifyButton = useOnChainVerification(proofData);
13 |
14 | const submit = (e: React.FormEvent) => {
15 | e.preventDefault();
16 | const elements = e.currentTarget.elements;
17 | if (!elements) return;
18 |
19 | const x = elements.namedItem('x') as HTMLInputElement;
20 | const y = elements.namedItem('y') as HTMLInputElement;
21 |
22 | setInput({ x: x.value, y: y.value });
23 | };
24 |
25 | return (
26 | <>
27 |
35 | {verifyButton}
36 | >
37 | );
38 | }
39 |
40 | export default Component;
41 |
--------------------------------------------------------------------------------
/vite-hardhat/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-hardhat",
3 | "description": "A template repository to get started with writing zero knowledge programs with Noir.",
4 | "private": true,
5 | "workspaces": [
6 | "packages/*"
7 | ],
8 | "scripts": {
9 | "deploy": "bunx hardhat deploy",
10 | "dev": "bun --filter vite dev",
11 | "test:up": "bun test ./tests/up.test.ts",
12 | "test:uh": "bun test ./tests/uh.test.ts",
13 | "test": "bun test:up && bun test:uh",
14 | "node": "bunx hardhat node"
15 | },
16 | "type": "module",
17 | "devDependencies": {
18 | "@types/bun": "^1.1.12",
19 | "hardhat": "^2.19.2"
20 | },
21 | "dependencies": {
22 | "@noir-lang/noir_js": "1.0.0-beta.0",
23 | "@noir-lang/noir_wasm": "1.0.0-beta.0",
24 | "@noir-lang/types": "1.0.0-beta.0",
25 | "@aztec/bb.js": "0.63.1",
26 | "@nomicfoundation/hardhat-ignition": "^0.15.5",
27 | "@nomicfoundation/hardhat-ignition-viem": "^0.15.5",
28 | "commander": "^12.1.0",
29 | "dotenv": "^16.0.3",
30 | "shelljs": "^0.8.5",
31 | "@nomicfoundation/hardhat-ethers": "^3.0.6",
32 | "@nomicfoundation/hardhat-network-helpers": "^1.0.11",
33 | "@nomicfoundation/hardhat-toolbox-viem": "3.0.0",
34 | "@nomicfoundation/hardhat-verify": "^2.0.8",
35 | "@nomicfoundation/hardhat-viem": "2.0.2",
36 | "@types/mocha": "^10.0.1",
37 | "@types/shelljs": "^0.8.7",
38 | "hardhat-gas-reporter": "^1.0.9",
39 | "solidity-coverage": "^0.8.5",
40 | "hardhat-noirenberg": "0.2.0",
41 | "typescript": "^5.6.3"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/with-foundry/test/Starter.t.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.8.17;
2 |
3 | import "../contract/Starter.sol";
4 | import "../circuits/target/contract.sol";
5 | import "forge-std/console.sol";
6 |
7 | import "forge-std/Test.sol";
8 | import {NoirHelper} from "foundry-noir-helper/NoirHelper.sol";
9 |
10 |
11 | contract StarterTest is Test {
12 | Starter public starter;
13 | UltraVerifier public verifier;
14 | NoirHelper public noirHelper;
15 |
16 | function setUp() public {
17 | noirHelper = new NoirHelper();
18 | verifier = new UltraVerifier();
19 | starter = new Starter(verifier);
20 | }
21 |
22 | function test_verifyProof() public {
23 | noirHelper.withInput("x", 1).withInput("y", 1).withInput("return", 1);
24 | (bytes32[] memory publicInputs, bytes memory proof) = noirHelper.generateProof("test_verifyProof", 2);
25 | starter.verifyEqual(proof, publicInputs);
26 | }
27 |
28 | function test_wrongProof() public {
29 | noirHelper.clean();
30 | noirHelper.withInput("x", 1).withInput("y", 5).withInput("return", 5);
31 | (bytes32[] memory publicInputs, bytes memory proof) = noirHelper.generateProof("test_wrongProof", 2);
32 | vm.expectRevert();
33 | starter.verifyEqual(proof, publicInputs);
34 | }
35 |
36 | // function test_all() public {
37 | // // forge runs tests in parallel which messes with the read/writes to the proof file
38 | // // Run tests in wrapper to force them run sequentially
39 | // verifyProof();
40 | // wrongProof();
41 | // }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/vite-hardhat/packages/vite/hooks/useProofGeneration.tsx:
--------------------------------------------------------------------------------
1 | import { toast } from 'react-toastify';
2 | import { useEffect, useState } from 'react';
3 | import { getCircuit } from '../../noir/compile.js';
4 | import { UltraPlonkBackend } from '@aztec/bb.js';
5 | import { Noir } from '@noir-lang/noir_js';
6 | import { ProofData } from '@noir-lang/types';
7 |
8 | export function useProofGeneration(inputs?: { [key: string]: string }) {
9 | const [proofData, setProofData] = useState();
10 | const [backend, setBackend] = useState();
11 | const [noir, setNoir] = useState();
12 |
13 | const proofGeneration = async () => {
14 | if (!inputs) return;
15 | const circuit = await getCircuit();
16 | const backend = new UltraPlonkBackend(circuit.bytecode, {
17 | threads: navigator.hardwareConcurrency,
18 | });
19 | const noir = new Noir(circuit);
20 |
21 | await toast.promise(noir.init, {
22 | pending: 'Initializing Noir...',
23 | success: 'Noir initialized!',
24 | error: 'Error initializing Noir',
25 | });
26 |
27 | const { witness } = await toast.promise(noir.execute(inputs), {
28 | pending: 'Generating witness...',
29 | success: 'Witness generated!',
30 | error: 'Error generating witness',
31 | });
32 |
33 | const data = await toast.promise(backend.generateProof(witness), {
34 | pending: 'Generating proof',
35 | success: 'Proof generated',
36 | error: 'Error generating proof',
37 | });
38 |
39 | setProofData(data);
40 | setNoir(noir);
41 | setBackend(backend);
42 | };
43 |
44 | useEffect(() => {
45 | if (!inputs) return;
46 | proofGeneration();
47 | }, [inputs]);
48 |
49 | return { noir, proofData, backend };
50 | }
51 |
--------------------------------------------------------------------------------
/vite-hardhat/tests/uh.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, beforeAll, describe, test } from 'bun:test';
2 | import { Noir } from '@noir-lang/noir_js';
3 | import { ProofData } from '@noir-lang/types';
4 | import { UltraHonkBackend } from '@aztec/bb.js';
5 | import fs from 'fs';
6 | import { resolve } from 'path';
7 |
8 | describe('UltraHonk', () => {
9 | let correctProof: ProofData;
10 | let noir: Noir;
11 | let backend: UltraHonkBackend;
12 |
13 | beforeAll(async () => {
14 | const contractsDir = resolve('packages', 'contracts');
15 | if (fs.existsSync(contractsDir)) fs.rmdirSync(contractsDir, { recursive: true });
16 |
17 | const hre = require('hardhat');
18 |
19 | hre.run('node');
20 | hre.config.noirenberg = { provingSystem: 'UltraHonk' };
21 |
22 | console.log(hre);
23 | ({ noir, backend } = await hre.noirenberg.compile());
24 | await hre.noirenberg.getSolidityVerifier();
25 | });
26 |
27 | test('Should generate valid proof for correct input', async () => {
28 | const input = { x: 1, y: 2 };
29 | const { witness } = await noir.execute(input);
30 | correctProof = await backend.generateProof(witness);
31 | expect(correctProof.proof instanceof Uint8Array).toBeTrue;
32 | });
33 |
34 | test('Should verify valid proof for correct input', async () => {
35 | const verification = await backend.verifyProof(correctProof);
36 | expect(verification).toBeTrue;
37 | });
38 |
39 | test('Should fail to generate valid proof for incorrect input', async () => {
40 | try {
41 | const input = { x: 1, y: 1 };
42 | const { witness } = await noir.execute(input);
43 | const incorrectProof = await backend.generateProof(witness);
44 | } catch (err) {
45 | expect(err instanceof Error).toBeTrue;
46 | const error = err as Error;
47 | expect(error.message).toContain('Cannot satisfy constraint');
48 | }
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/vite-hardhat/packages/vite/index.tsx:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import acvm from '@noir-lang/acvm_js/web/acvm_js_bg.wasm?url';
3 | // @ts-ignore
4 | import noirc from '@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm?url';
5 | import initNoirC from '@noir-lang/noirc_abi';
6 | import initACVM from '@noir-lang/acvm_js';
7 | // @ts-ignore
8 | await Promise.all([initACVM(fetch(acvm)), initNoirC(fetch(noirc))]);
9 |
10 | import React, { ReactNode, useEffect } from 'react';
11 | import ReactDOM from 'react-dom/client';
12 | import './App.css';
13 | import 'react-toastify/dist/ReactToastify.css';
14 | import { ToastContainer } from 'react-toastify';
15 | import Component from './components/index.jsx';
16 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
17 | import { WagmiProvider, createConfig, http } from 'wagmi';
18 | import { defineChain, createClient } from 'viem';
19 | import { injected } from 'wagmi/connectors';
20 | import { networkConfig } from '../../deployment.json';
21 |
22 | const queryClient = new QueryClient();
23 |
24 | const { id, name, nativeCurrency, rpcUrls } = networkConfig;
25 | const chain = defineChain({
26 | id,
27 | name,
28 | nativeCurrency,
29 | rpcUrls,
30 | });
31 |
32 | const config = createConfig({
33 | connectors: [injected()],
34 | chains: [chain],
35 | client({ chain }) {
36 | return createClient({ chain, transport: http() });
37 | },
38 | });
39 |
40 | export function Providers({ children }: { children: React.ReactNode }) {
41 | const [mounted, setMounted] = React.useState(false);
42 | React.useEffect(() => setMounted(true), []);
43 | return (
44 |
45 | {mounted && children}
46 |
47 | );
48 | }
49 |
50 | ReactDOM.createRoot(document.getElementById('root')!).render(
51 |
52 |
53 |
54 | ,
55 | );
56 |
--------------------------------------------------------------------------------
/.github/workflows/with_foundry.yaml:
--------------------------------------------------------------------------------
1 | name: PR - with-foundry
2 | on:
3 | pull_request:
4 | paths:
5 | - 'with-foundry/**'
6 |
7 | jobs:
8 | test-with-foundry:
9 | defaults:
10 | run:
11 | working-directory: with-foundry
12 |
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v2
16 | with:
17 | submodules: recursive
18 |
19 | - name: Install libc++
20 | run: |
21 | sudo apt-get update
22 | sudo apt-get install -y libc++-dev libc++abi-dev
23 |
24 | - name: Get latest version
25 | id: versions_step
26 | run: |
27 | output=$(node ../.github/scripts/latest.js)
28 | echo "Output from Node.js script: $output"
29 |
30 | STABLE=$(echo $output | jq -r '.stable')
31 | echo "::set-output name=stable::$STABLE"
32 |
33 | - name: Set up nargo
34 | uses: ./.github/actions/setup-nargo
35 | with:
36 | version: ${{ steps.versions_step.outputs.stable }}
37 |
38 | - name: Set up foundry
39 | uses: ./.github/actions/setup-foundry
40 |
41 | - name: Install bb
42 | run: |
43 | curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash
44 | echo "PATH=$PATH:/home/runner/.bb" >> $GITHUB_ENV
45 | shell: bash
46 |
47 | - name: Use bbup
48 | run: |
49 | bbup -v 0.63.1
50 | shell: bash
51 |
52 | - name: Generate verifier contract
53 | run: |
54 | nargo compile && bb write_vk -b ./target/with_foundry.json && bb contract
55 | working-directory: with-foundry/circuits
56 |
57 | - name: Generate proof
58 | run: |
59 | nargo execute witness
60 | bb prove -b ./target/with_foundry.json -w ./target/witness.gz -o ./target/with_foundry.proof
61 | working-directory: with-foundry/circuits
62 |
63 | - name: Test with Foundry
64 | run: |
65 | forge test --optimize --optimizer-runs 5000 --evm-version cancun
66 |
--------------------------------------------------------------------------------
/.github/scripts/latest.js:
--------------------------------------------------------------------------------
1 | const GITHUB_PAGES = 3;
2 |
3 | async function main() {
4 | const fetchOpts = {
5 | params: { per_page: 100 },
6 | headers: {},
7 | };
8 |
9 | if (process.env.GITHUB_TOKEN)
10 | fetchOpts.headers = { Authorization: `token ${process.env.GITHUB_TOKEN}` };
11 |
12 | const versions = [];
13 | for (let i = 0; i < GITHUB_PAGES; i++) {
14 | const res = await fetch(
15 | `https://api.github.com/repos/noir-lang/noir/releases?page=${i + 1}`,
16 | fetchOpts,
17 | );
18 |
19 | const data = await res.json();
20 |
21 | const filtered = data.filter(
22 | release => !release.tag_name.includes('aztec') && !release.tag_name.includes('nightly'),
23 | );
24 | versions.push(...filtered);
25 | }
26 |
27 | const latestStable = versions.find(release => !release.prerelease).tag_name.substring(1);
28 |
29 | /**
30 | * TODO: test the prerelease!
31 | *
32 | * The problem with the prerelease is that if the test runs for both the stable and the prerelease,
33 | * and the prerelease has a breaking change, then the stable will fail.
34 | *
35 | * If we update the starter to match the prerelease, then the stable will fail.
36 | *
37 | * This means that if there is a breaking change in a prerelease, we will ALWAYS get a warning 😄, which defeats the purpose.
38 | *
39 | * A solution would be to have a separate "prerelease" branch that is updated with the prerelease. And the CI runs on that branch.
40 | * However, Noir hasn't yet reached a state where, for example, there is ALWAYS a prerelease newer than the stable.
41 | * Sometimes the stable is the last one, and there is a prerelease buried somewhere that never got the honor of being promoted to stable.
42 | *
43 | * So for now, we will just ignore the prerelease.
44 | */
45 |
46 | // const latestPreRelease = versions.find(release => release.prerelease).tag_name.substring(1);
47 |
48 | const workflowOutput = JSON.stringify({
49 | stable: latestStable,
50 | // prerelease: latestPreRelease,
51 | });
52 | console.log(workflowOutput); // DON'T REMOVE, GITHUB WILL CAPTURE THIS OUTPUT
53 | }
54 |
55 | main();
56 |
--------------------------------------------------------------------------------
/vite-hardhat/tests/up.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, beforeAll, describe, test } from 'bun:test';
2 | import { Noir } from '@noir-lang/noir_js';
3 | import { ProofData } from '@noir-lang/types';
4 | import { UltraPlonkBackend } from '@aztec/bb.js';
5 | import fs from 'fs';
6 | import { resolve } from 'path';
7 | import { bytesToHex } from 'viem';
8 |
9 | describe('UltraPlonk', () => {
10 | let correctProof: ProofData;
11 | let noir: Noir;
12 | let backend: UltraPlonkBackend;
13 |
14 | beforeAll(async () => {
15 | const contractsDir = resolve('packages', 'contracts');
16 | if (fs.existsSync(contractsDir)) fs.rmdirSync(contractsDir, { recursive: true });
17 |
18 | const hre = require('hardhat');
19 | hre.run('node');
20 | hre.config.noirenberg = { provingSystem: 'UltraPlonk' };
21 |
22 | ({ noir, backend } = await hre.noirenberg.compile());
23 | await hre.noirenberg.getSolidityVerifier();
24 | });
25 |
26 | test('Should generate valid proof for correct input', async () => {
27 | const input = { x: 1, y: 2 };
28 | const { witness } = await noir.execute(input);
29 | correctProof = await backend.generateProof(witness);
30 | expect(correctProof.proof instanceof Uint8Array).toBeTrue;
31 | });
32 |
33 | test('Should verify valid proof for correct input', async () => {
34 | const verification = await backend.verifyProof(correctProof);
35 | expect(verification).toBeTrue;
36 | });
37 |
38 | test('Should verify valid proof for correct input on a smart contract', async () => {
39 | const hre = require('hardhat');
40 |
41 | const verifier = await hre.viem.deployContract('UltraVerifier');
42 | const res = await verifier.read.verify([
43 | bytesToHex(correctProof.proof),
44 | correctProof.publicInputs as `0x${string}`[],
45 | ]);
46 | expect(res).toBeTrue;
47 | });
48 |
49 | test('Should fail to generate valid proof for incorrect input', async () => {
50 | try {
51 | const input = { x: 1, y: 1 };
52 | const { witness } = await noir.execute(input);
53 | const incorrectProof = await backend.generateProof(witness);
54 | } catch (err) {
55 | expect(err instanceof Error).toBeTrue;
56 | const error = err as Error;
57 | expect(error.message).toContain('Cannot satisfy constraint');
58 | }
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/vite-hardhat/packages/vite/hooks/useOnChainVerification.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ProofData } from '@noir-lang/types';
3 | import { useAccount, useConnect, useDisconnect, useSwitchChain } from 'wagmi';
4 | import { bytesToHex } from 'viem';
5 | import { useEffect, useState } from 'react';
6 | import { Id, toast } from 'react-toastify';
7 | import { useReadUltraVerifierVerify } from '../artifacts/generated.js';
8 | import deployment from '../../../deployment.json';
9 |
10 | export function useOnChainVerification(proofData?: ProofData) {
11 | const { connect, connectors } = useConnect();
12 | const { disconnect } = useDisconnect();
13 | const { isConnected } = useAccount();
14 | const [args, setArgs] = useState<[`0x${string}`, `0x${string}`[]] | undefined>();
15 |
16 | const { chains, switchChain } = useSwitchChain();
17 | const { data, error } = useReadUltraVerifierVerify({
18 | args,
19 | query: {
20 | enabled: !!args,
21 | },
22 | });
23 |
24 | const [onChainToast, setOnChainToast] = useState(0);
25 |
26 | useEffect(() => {
27 | switchChain({ chainId: chains[0].id });
28 | if (!proofData || !isConnected) {
29 | return;
30 | }
31 |
32 | setArgs([bytesToHex(proofData.proof), proofData.publicInputs as `0x${string}`[]]);
33 |
34 | if (!onChainToast)
35 | setOnChainToast(toast.loading('Verifying proof on-chain', { autoClose: 10000 }));
36 | }, [isConnected, proofData]);
37 |
38 | useEffect(() => {
39 | if (data) {
40 | toast.update(onChainToast, {
41 | type: 'success',
42 | render: 'Proof verified on-chain!',
43 | isLoading: false,
44 | });
45 | } else if (error) {
46 | toast.update(onChainToast, {
47 | type: 'error',
48 | render: 'Error verifying proof on-chain!',
49 | isLoading: false,
50 | });
51 | console.error(error.message);
52 | }
53 | }, [data, error]);
54 |
55 | if (!isConnected) {
56 | return (
57 |
58 |
66 |
67 | );
68 | } else {
69 | return (
70 |
71 |
72 |
73 | );
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/vite-hardhat/hardhat.config.cts:
--------------------------------------------------------------------------------
1 | import '@nomicfoundation/hardhat-toolbox-viem';
2 | import '@nomicfoundation/hardhat-viem';
3 | import '@nomicfoundation/hardhat-chai-matchers';
4 | import 'hardhat-noirenberg';
5 |
6 | import { writeFileSync } from 'fs';
7 | import { Chain } from 'viem';
8 | import { task, vars } from 'hardhat/config';
9 | import { HardhatUserConfig } from 'hardhat/types/config';
10 | import fs from 'fs';
11 | import { resolve } from 'path';
12 |
13 | const config: HardhatUserConfig = {
14 | solidity: {
15 | version: '0.8.28',
16 | settings: {
17 | optimizer: { enabled: true, runs: 5000 },
18 | },
19 | },
20 | defaultNetwork: 'localhost',
21 | networks: {
22 | localhost: {
23 | url: 'http://127.0.0.1:8545',
24 | chainId: 31337,
25 | accounts: vars.has('localhost')
26 | ? [vars.get('localhost')]
27 | : ['0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'],
28 | },
29 | scrollSepolia: {
30 | url: 'https://sepolia-rpc.scroll.io',
31 | accounts: vars.has('scrollSepolia') ? [vars.get('scrollSepolia')] : [],
32 | },
33 | holesky: {
34 | url: 'https://holesky.drpc.org',
35 | accounts: vars.has('holesky') ? [vars.get('holesky')] : [],
36 | },
37 | },
38 | paths: {
39 | root: 'packages',
40 | tests: 'tests',
41 | },
42 | };
43 |
44 | task('deploy', 'Deploys a verifier contract')
45 | .addOptionalPositionalParam('provingSystem')
46 | .setAction(async (taskArgs, hre) => {
47 | const contractsDir = resolve('packages', 'contracts');
48 | if (fs.existsSync(contractsDir)) fs.rmdirSync(contractsDir, { recursive: true });
49 |
50 | hre.config.noirenberg = { provingSystem: taskArgs.provingSystem || 'UltraPlonk' };
51 | await hre.run('compile');
52 |
53 | let verifier;
54 | if (taskArgs.provingSystem === 'UltraHonk') {
55 | verifier = await hre.viem.deployContract('HonkVerifier');
56 | } else {
57 | verifier = await hre.viem.deployContract('UltraVerifier');
58 | }
59 |
60 | const networkConfig = (await import(`viem/chains`))[hre.network.name] as Chain;
61 | const config = {
62 | name: hre.network.name,
63 | address: verifier.address,
64 | networkConfig: {
65 | ...networkConfig,
66 | id: hre.network.config.chainId,
67 | },
68 | };
69 |
70 | console.log(
71 | `Attached to address ${verifier.address} at network ${hre.network.name} with chainId ${networkConfig.id}...`,
72 | );
73 | writeFileSync('deployment.json', JSON.stringify(config), { flag: 'w' });
74 | });
75 |
76 | export default config;
77 |
--------------------------------------------------------------------------------
/vite-hardhat/README.md:
--------------------------------------------------------------------------------
1 | # Noir with Vite and Hardhat
2 |
3 | [](https://app.netlify.com/sites/noir-vite-hardhat/deploys)
4 |
5 | This example uses [Vite](https://vite.dev/) as the frontend framework, and
6 | [Hardhat](https://hardhat.org/) to deploy and test.
7 |
8 | ## Getting Started
9 |
10 | Want to get started in a pinch? Start your project in a free Github Codespace!
11 |
12 | [](https://codespaces.new/noir-lang/noir-starter/tree/main)
13 |
14 | ## Locally
15 |
16 | 1. Install your favorite package manager. We'll use [bun](https://bun.sh/docs/installation) but feel
17 | free to use `yarn` or others:
18 |
19 | ```bash
20 | curl -fsSL https://bun.sh/install | bash
21 | ```
22 |
23 | 2. Install dependencies:
24 |
25 | ```bash
26 | bun i
27 | ```
28 |
29 | 3. Run a node
30 |
31 | ```bash
32 | bunx hardhat node
33 | ```
34 |
35 | 4. Deploy the verifier contract (UltraPlonk)
36 |
37 | ```bash
38 | bun run deploy
39 | ```
40 |
41 | 5. Run the dev environment
42 |
43 | ```bash
44 | bun dev
45 | ```
46 |
47 | ### Testing
48 |
49 | You can run the [example test file](./test/index.test.ts) with
50 |
51 | ```bash
52 | bun run test
53 | ```
54 |
55 | This test shows the basic usage of Noir in a TypeScript Node.js environment. It also starts its own network and deploys the verifier contract.
56 |
57 | If you want to test only `UltraHonk`, you can run:
58 |
59 | ```bash
60 | bun run test:uh
61 | ```
62 |
63 | ### Deploying on other networks
64 |
65 | The default scripting targets a local environment. For convenience, we added some configurations for
66 | deployment on various other networks. You can see the existing list by running:
67 |
68 | ```bash
69 | bunx hardhat vars setup
70 | ```
71 |
72 | If you want to deploy on any of them, just pass in a private key, for example for the holesky
73 | network:
74 |
75 | ```bash
76 | bunx hardhat vars set holesky
77 | ```
78 |
79 | You can then deploy on that network by passing the `--network` flag. For example:
80 |
81 | ```bash
82 | bunx hardhat deploy --network holesky # deploys on holesky
83 | ```
84 |
85 | Feel free to add more networks, as long as they're supported by `wagmi`
86 | ([list here](https://wagmi.sh/react/api/chains#available-chains)). Just make sure you:
87 |
88 | - Have funds in these accounts
89 | - Add their configuration in the `networks` property in `hardhat.config.cts`
90 | - Use the name that wagmi expects (for example `ethereum` won't work, as `wagmi` calls it `mainnet`)
91 |
--------------------------------------------------------------------------------
/with-foundry/README.md:
--------------------------------------------------------------------------------
1 | # Noir with Foundry
2 |
3 | This example uses Foundry to deploy and test a verifier.
4 |
5 | ## Getting Started
6 |
7 | Want to get started in a pinch? Start your project in a free Github Codespace!
8 |
9 | [](https://codespaces.new/noir-lang/noir-starter)
10 |
11 | In the meantime, follow these simple steps to work on your own machine:
12 |
13 | Install [noirup](https://noir-lang.org/docs/getting_started/noir_installation) with
14 |
15 | 1. Install [noirup](https://noir-lang.org/docs/getting_started/noir_installation):
16 |
17 | ```bash
18 | curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash
19 | ```
20 |
21 | 2. Install Nargo:
22 |
23 | ```bash
24 | noirup
25 | ```
26 |
27 | 3. Install foundryup and follow the instructions on screen. You should then have all the foundry
28 | tools like `forge`, `cast`, `anvil` and `chisel`.
29 |
30 | ```bash
31 | curl -L https://foundry.paradigm.xyz | bash
32 | ```
33 |
34 | 4. Install foundry dependencies by running `forge install 0xnonso/foundry-noir-helper --no-commit`.
35 |
36 | 5. Install `bbup`, the tool for managing Barretenberg versions, by following the instructions
37 | [here](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/bbup/README.md#installation).
38 |
39 | 6. Then run `bbup`.
40 |
41 | ## Generate verifier contract and proof
42 |
43 | ### Contract
44 |
45 | The deployment assumes a verifier contract has been generated by nargo. In order to do this, run:
46 |
47 | ```bash
48 | cd circuits
49 | nargo compile
50 | bb write_vk -b ./target/with_foundry.json
51 | bb contract
52 | ```
53 |
54 | A file named `contract.sol` should appear in the `circuits/target` folder.
55 |
56 | ### Test with Foundry
57 |
58 | We're ready to test with Foundry. There's a basic test inside the `test` folder that deploys the
59 | verifier contract, the `Starter` contract and two bytes32 arrays correspondent to good and bad
60 | solutions to your circuit.
61 |
62 | By running the following command, forge will compile the contract with 5000 rounds of optimization
63 | and the London EVM version. **You need to use these optimizer settings to suppress the "stack too
64 | deep" error on the solc compiler**. Then it will run the test, expecting it to pass with correct
65 | inputs, and fail with wrong inputs:
66 |
67 | ```bash
68 | forge test --optimize --optimizer-runs 5000 --evm-version cancun
69 | ```
70 |
71 | #### Testing On-chain
72 |
73 | You can test that the Noir Solidity verifier contract works on a given chain by running the
74 | `Verify.s.sol` script against the appropriate RPC endpoint.
75 |
76 | ```bash
77 | forge script script/Verify.s.sol --rpc-url $RPC_ENDPOINT --broadcast
78 | ```
79 |
80 | If that doesn't work, you can add the network to Metamask and deploy and test via
81 | [Remix](https://remix.ethereum.org/).
82 |
83 | Note that some EVM network infrastructure may behave differently and this script may fail for
84 | reasons unrelated to the compatibility of the verifier contract.
85 |
86 | ### Deploy with Foundry
87 |
88 | This template also has a script to help you deploy on your own network. But for that you need to run
89 | your own node or, alternatively, deploy on a testnet.
90 |
91 | #### (Option 1) Run a local node
92 |
93 | If you want to deploy locally, run a node by opening a terminal and running
94 |
95 | ```bash
96 | anvil
97 | ```
98 |
99 | This should start a local node listening on `http://localhost:8545`. It will also give you many
100 | private keys.
101 |
102 | Edit your `.env` file to look like:
103 |
104 | ```
105 | ANVIL_RPC=http://localhost:8545
106 | LOCALHOST_PRIVATE_KEY=
107 | ```
108 |
109 | #### (Option 2) Prepare for testnet
110 |
111 | Pick a testnet like Sepolia or Goerli. Generate a private key and use a faucet (like
112 | [this one for Sepolia](https://sepoliafaucet.com/)) to get some coins in there.
113 |
114 | Edit your `.env` file to look like:
115 |
116 | ```env
117 | SEPOLIA_RPC=https://rpc2.sepolia.org
118 | LOCALHOST_PRIVATE_KEY=
119 | ```
120 |
121 | #### Run the deploy script
122 |
123 | You need to source your `.env` file before deploying. Do that with:
124 |
125 | ```bash
126 | source .env
127 | ```
128 |
129 | Then run the deployment with:
130 |
131 | ```bash
132 | forge script script/Starter.s.sol --rpc-url $ANVIL_RPC --broadcast --verify
133 | ```
134 |
135 | Replace `$ANVIL_RPC` with the testnet RPC, if you're deploying on a testnet.
136 |
137 | ## Developing on this template
138 |
139 | This template doesn't include settings you may need to deal with syntax highlighting and
140 | IDE-specific settings (i.e. VScode). Please follow the instructions on the
141 | [Foundry book](https://book.getfoundry.sh/config/vscode) to set that up.
142 |
143 | It's **highly recommended** you get familiar with [Foundry](https://book.getfoundry.sh) before
144 | developing on this template.
145 |
--------------------------------------------------------------------------------