├── apps ├── web │ ├── README.md │ ├── .eslintrc.js │ ├── postcss.config.js │ ├── tailwind.config.js │ ├── public │ │ ├── favicon.ico │ │ ├── agg_ecdsa.r1cs │ │ ├── agg_ecdsa.wasm │ │ ├── github-mark.png │ │ ├── agg_ecdsa_mobile.r1cs │ │ └── agg_ecdsa_mobile.wasm │ ├── workers │ │ ├── shared.ts │ │ ├── generate-params.worker.ts │ │ ├── verify.worker.ts │ │ └── generate-proof.worker.ts │ ├── app │ │ ├── fonts.ts │ │ ├── layout.tsx │ │ └── page.tsx │ ├── next-env.d.ts │ ├── tsconfig.json │ ├── types │ │ └── types.ts │ ├── .gitignore │ ├── next.config.js │ ├── package.json │ └── hooks │ │ ├── useGenerateParams.ts │ │ ├── useFolding.ts │ │ ├── useProve.ts │ │ └── useVerify.ts └── docs │ ├── README.md │ ├── .eslintrc.js │ ├── next.config.js │ ├── app │ ├── page.tsx │ └── layout.tsx │ ├── next-env.d.ts │ ├── tsconfig.json │ ├── .gitignore │ └── package.json ├── .npmrc ├── nova-ecdsa-browser ├── rust-toolchain ├── src │ ├── lib.rs │ └── wasm.rs ├── notes.md ├── .cargo │ └── config.toml ├── Cargo.toml └── Cargo.lock ├── .gitattributes ├── pnpm-workspace.yaml ├── packages ├── ui │ ├── postcss.config.js │ ├── tailwind.config.js │ ├── styles.css │ ├── tsconfig.json │ ├── index.tsx │ ├── Header.tsx │ ├── turbo │ │ └── generators │ │ │ ├── templates │ │ │ └── component.hbs │ │ │ └── config.ts │ ├── Button.tsx │ ├── package.json │ ├── Containers.tsx │ ├── Description.tsx │ └── Nova.tsx ├── configs │ └── tailwind │ │ ├── package.json │ │ ├── postcss.config.js │ │ └── tailwind.config.js ├── nova-ecdsa-browser-pkg │ ├── nova_ecdsa_browser_bg.wasm │ ├── snippets │ │ ├── wasm-bindgen-futures-264cf9c4a001c783 │ │ │ └── src │ │ │ │ └── task │ │ │ │ └── worker.js │ │ ├── wasm-bindgen-rayon-7afa899f36665473 │ │ │ └── src │ │ │ │ └── workerHelpers.js │ │ └── nova-scotia-30308307bbbbb0ad │ │ │ └── src │ │ │ └── circom │ │ │ └── wasm_deps │ │ │ └── generate_witness_browser.js │ ├── package.json │ ├── nova_ecdsa_browser_bg.wasm.d.ts │ ├── nova_ecdsa_browser.d.ts │ └── nova_ecdsa_browser.js ├── tsconfig │ ├── package.json │ ├── react-library.json │ ├── base.json │ └── nextjs.json ├── eslint-config-custom │ ├── index.js │ └── package.json └── scripts │ ├── package.json │ └── generateSampleSignature.ts ├── Cargo.toml ├── nova-ecdsa ├── src │ ├── data │ │ ├── agg_ecdsa.r1cs │ │ ├── agg_ecdsa.wasm │ │ ├── agg_ecdsa_mobile.r1cs │ │ ├── agg_ecdsa_mobile.wasm │ │ └── circuits │ │ │ ├── compile.sh │ │ │ ├── efficient_ecdsa_pubkey.circom │ │ │ ├── main.circom │ │ │ └── batch_efficient_ecdsa_pubkey.circom │ └── main.rs ├── Cargo.toml └── Cargo.lock ├── .eslintrc.js ├── turbo.json ├── package.json ├── .gitignore ├── README.md └── Cargo.lock /apps/web/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/docs/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers = true 2 | -------------------------------------------------------------------------------- /nova-ecdsa-browser/rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2022-12-12 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | packages/nova-ecdsa-browser-pkg/* linguist-vendored -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "apps/*" 3 | - "packages/*" 4 | -------------------------------------------------------------------------------- /nova-ecdsa-browser/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_family = "wasm")] 2 | pub mod wasm; -------------------------------------------------------------------------------- /packages/ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("../configs/tailwind/postcss.config"); -------------------------------------------------------------------------------- /apps/docs/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["custom"], 4 | }; 5 | -------------------------------------------------------------------------------- /apps/web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["custom"], 4 | }; 5 | -------------------------------------------------------------------------------- /packages/ui/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("../configs/tailwind/tailwind.config"); -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "nova-ecdsa", 5 | "nova-ecdsa-browser", 6 | ] -------------------------------------------------------------------------------- /apps/web/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("../../packages/configs/tailwind/postcss.config"); -------------------------------------------------------------------------------- /apps/web/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("../../packages/configs/tailwind/tailwind.config"); -------------------------------------------------------------------------------- /apps/docs/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | transpilePackages: ["ui"], 4 | }; 5 | -------------------------------------------------------------------------------- /apps/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmpierre/nova-browser-ecdsa/HEAD/apps/web/public/favicon.ico -------------------------------------------------------------------------------- /apps/web/public/agg_ecdsa.r1cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmpierre/nova-browser-ecdsa/HEAD/apps/web/public/agg_ecdsa.r1cs -------------------------------------------------------------------------------- /apps/web/public/agg_ecdsa.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmpierre/nova-browser-ecdsa/HEAD/apps/web/public/agg_ecdsa.wasm -------------------------------------------------------------------------------- /apps/web/public/github-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmpierre/nova-browser-ecdsa/HEAD/apps/web/public/github-mark.png -------------------------------------------------------------------------------- /packages/configs/tailwind/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "configs", 3 | "version": "0.0.0", 4 | "private": true 5 | } -------------------------------------------------------------------------------- /nova-ecdsa/src/data/agg_ecdsa.r1cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmpierre/nova-browser-ecdsa/HEAD/nova-ecdsa/src/data/agg_ecdsa.r1cs -------------------------------------------------------------------------------- /nova-ecdsa/src/data/agg_ecdsa.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmpierre/nova-browser-ecdsa/HEAD/nova-ecdsa/src/data/agg_ecdsa.wasm -------------------------------------------------------------------------------- /apps/web/public/agg_ecdsa_mobile.r1cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmpierre/nova-browser-ecdsa/HEAD/apps/web/public/agg_ecdsa_mobile.r1cs -------------------------------------------------------------------------------- /apps/web/public/agg_ecdsa_mobile.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmpierre/nova-browser-ecdsa/HEAD/apps/web/public/agg_ecdsa_mobile.wasm -------------------------------------------------------------------------------- /nova-ecdsa/src/data/agg_ecdsa_mobile.r1cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmpierre/nova-browser-ecdsa/HEAD/nova-ecdsa/src/data/agg_ecdsa_mobile.r1cs -------------------------------------------------------------------------------- /nova-ecdsa/src/data/agg_ecdsa_mobile.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmpierre/nova-browser-ecdsa/HEAD/nova-ecdsa/src/data/agg_ecdsa_mobile.wasm -------------------------------------------------------------------------------- /packages/ui/styles.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | h1 { 6 | font-family: var(--font-roboto-500) 7 | } -------------------------------------------------------------------------------- /packages/configs/tailwind/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/nova-ecdsa-browser-pkg/nova_ecdsa_browser_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmpierre/nova-browser-ecdsa/HEAD/packages/nova-ecdsa-browser-pkg/nova_ecdsa_browser_bg.wasm -------------------------------------------------------------------------------- /nova-ecdsa-browser/notes.md: -------------------------------------------------------------------------------- 1 | - Environment variable should correctly point to your llvm installation 2 | - `.vscode` folder is used for environment setup. Might need to change paths provided for it to work properly -------------------------------------------------------------------------------- /packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsconfig", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "publishConfig": { 7 | "access": "public" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/docs/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Header } from "ui"; 2 | 3 | export default function Page() { 4 | return ( 5 | <> 6 |
7 | ; 12 | }; 13 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "globalDependencies": ["**/.env.*local"], 4 | "pipeline": { 5 | "build": { 6 | "dependsOn": ["^build"], 7 | "outputs": [".next/**", "!.next/cache/**"] 8 | }, 9 | "lint": {}, 10 | "dev": { 11 | "cache": false, 12 | "persistent": true 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/nova-ecdsa-browser-pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nova-ecdsa-browser", 3 | "version": "0.1.0", 4 | "files": [ 5 | "nova_ecdsa_browser_bg.wasm", 6 | "nova_ecdsa_browser.js", 7 | "nova_ecdsa_browser.d.ts" 8 | ], 9 | "module": "nova_ecdsa_browser.js", 10 | "types": "nova_ecdsa_browser.d.ts", 11 | "sideEffects": [ 12 | "./snippets/*" 13 | ] 14 | } -------------------------------------------------------------------------------- /packages/eslint-config-custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-custom", 3 | "version": "0.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "eslint-config-next": "^13.4.1", 8 | "eslint-config-prettier": "^8.3.0", 9 | "eslint-plugin-react": "7.28.0", 10 | "eslint-config-turbo": "^1.9.3" 11 | }, 12 | "publishConfig": { 13 | "access": "public" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scripts", 3 | "version": "1.0.0", 4 | "description": "Utility scripts for the project", 5 | "keywords": [], 6 | "author": "", 7 | "license": "ISC", 8 | "dependencies": { 9 | "@ethereumjs/util": "^8.0.5", 10 | "@personaelabs/spartan-ecdsa": "^2.1.4", 11 | "elliptic": "^6.5.4" 12 | }, 13 | "devDependencies": { 14 | "@types/elliptic": "^6.4.14", 15 | "ts-node": "^10.9.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "turbo run build", 5 | "dev": "turbo run dev", 6 | "lint": "turbo run lint", 7 | "format": "prettier --write \"**/*.{ts,tsx,md}\"" 8 | }, 9 | "devDependencies": { 10 | "@turbo/gen": "^1.9.7", 11 | "eslint": "^7.32.0", 12 | "eslint-config-custom": "workspace:*", 13 | "prettier": "^2.5.1", 14 | "turbo": "latest" 15 | }, 16 | "packageManager": "pnpm@8.6.10", 17 | "name": "nova-ecdsa" 18 | } 19 | -------------------------------------------------------------------------------- /nova-ecdsa/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nova-ecdsa" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | nova-scotia = { git = "https://github.com/dmpierre/Nova-Scotia.git", branch = "secp_secq" } 10 | ff = { version = "0.13", features = ["derive"]} 11 | nova-snark = { git = "https://github.com/dmpierre/Nova.git", default-features = false, branch = "dev-secpsecq-031738d" } 12 | serde = "1.0.185" 13 | serde_json = "1.0.105" -------------------------------------------------------------------------------- /apps/docs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /apps/web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /packages/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui", 3 | "version": "0.0.0", 4 | "main": "./index.tsx", 5 | "types": "./index.tsx", 6 | "license": "MIT", 7 | "scripts": { 8 | "lint": "eslint \"**/*.ts*\"", 9 | "generate:component": "turbo gen react-component" 10 | }, 11 | "devDependencies": { 12 | "@types/react": "^18.2.0", 13 | "@types/react-dom": "^18.2.0", 14 | "eslint": "^7.32.0", 15 | "eslint-config-custom": "workspace:*", 16 | "react": "^18.2.0", 17 | "tsconfig": "workspace:*", 18 | "typescript": "^4.5.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /nova-ecdsa/src/data/circuits/efficient_ecdsa_pubkey.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.1.2; 2 | 3 | template EfficientECDSAPubKey() { 4 | 5 | signal input s; 6 | signal input Tx; 7 | signal input Ty; 8 | signal input Ux; 9 | signal input Uy; 10 | signal input pubX; 11 | signal input pubY; 12 | 13 | component eff_ecdsa = EfficientECDSA(); 14 | eff_ecdsa.s <== s; 15 | eff_ecdsa.Tx <== Tx; 16 | eff_ecdsa.Ty <== Ty; 17 | eff_ecdsa.Ux <== Ux; 18 | eff_ecdsa.Uy <== Uy; 19 | 20 | eff_ecdsa.pubKeyX === pubX; 21 | eff_ecdsa.pubKeyY === pubY; 22 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | build 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | 20 | # debug 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # local env files 26 | .env 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # turbo 33 | .turbo 34 | 35 | # vercel 36 | .vercel 37 | 38 | 39 | # Added by cargo 40 | target 41 | 42 | .vscode -------------------------------------------------------------------------------- /apps/web/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | transpilePackages: ["ui"], 4 | async headers() { 5 | return [ 6 | { 7 | source: '/(.*?)', 8 | headers: [ 9 | { 10 | key: 'Cross-Origin-Embedder-Policy', 11 | value: 'require-corp', 12 | }, 13 | { 14 | key: 'Cross-Origin-Opener-Policy', 15 | value: 'same-origin', 16 | }, 17 | { 18 | key: 'Access-Control-Allow-Origin', 19 | value: '*', 20 | } 21 | ], 22 | }, 23 | ] 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /packages/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "composite": false, 6 | "declaration": true, 7 | "declarationMap": true, 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "inlineSources": false, 11 | "isolatedModules": true, 12 | "moduleResolution": "node", 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "preserveWatchOutput": true, 16 | "skipLibCheck": true, 17 | "strict": true 18 | }, 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/tsconfig/nextjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "plugins": [{ "name": "next" }], 7 | "allowJs": true, 8 | "declaration": false, 9 | "declarationMap": false, 10 | "incremental": true, 11 | "jsx": "preserve", 12 | "lib": ["dom", "dom.iterable", "esnext"], 13 | "module": "esnext", 14 | "noEmit": true, 15 | "resolveJsonModule": true, 16 | "strict": false, 17 | "target": "es5" 18 | }, 19 | "include": ["src", "next-env.d.ts"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /apps/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --port 3001", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "^13.4.1", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "ui": "workspace:*" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^17.0.12", 19 | "@types/react": "^18.0.22", 20 | "@types/react-dom": "^18.0.7", 21 | "eslint-config-custom": "workspace:*", 22 | "tsconfig": "workspace:*", 23 | "typescript": "^4.5.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/web/workers/generate-params.worker.ts: -------------------------------------------------------------------------------- 1 | import { ctx, WEBSITE_ROOT } from "./shared"; 2 | 3 | async function generateParams(filename: string) { 4 | const multiThread = await import("nova-ecdsa-browser"); 5 | await multiThread.default(); 6 | await multiThread.initThreadPool(navigator.hardwareConcurrency); 7 | const start = performance.now(); 8 | const data = await multiThread.generate_params(WEBSITE_ROOT, filename); 9 | const end = performance.now(); 10 | ctx.postMessage({ 11 | data: data, 12 | time: end - start 13 | }); 14 | } 15 | 16 | ctx.addEventListener("message", async (event) => { 17 | generateParams(event.data.filename); 18 | }); -------------------------------------------------------------------------------- /apps/web/workers/verify.worker.ts: -------------------------------------------------------------------------------- 1 | import { ctx } from "./shared"; 2 | 3 | async function verify(iteration_count: number, pp: string, proof: string, sigs: string) { 4 | const multiThread = await import("nova-ecdsa-browser"); 5 | await multiThread.default(); 6 | await multiThread.initThreadPool(navigator.hardwareConcurrency); 7 | const start = performance.now(); 8 | const data = await multiThread.verify_compressed_proof(iteration_count, pp, proof, sigs); 9 | const end = performance.now(); 10 | ctx.postMessage({ 11 | data: data, 12 | time: end - start 13 | }); 14 | } 15 | 16 | ctx.addEventListener("message", async (event) => { 17 | verify(event.data.iteration_count, event.data.pp, event.data.proof, event.data.sigs); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/ui/Containers.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const MainContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => { 4 | return ( 5 |
6 |
7 | {children} 8 |
9 |
10 | ) 11 | } 12 | 13 | export const MainNovaContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => { 14 | return ( 15 |
16 | {children} 17 |
18 | ) 19 | } 20 | 21 | export const NovaContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => { 22 | return ( 23 |
24 | {children} 25 |
26 | ) 27 | } -------------------------------------------------------------------------------- /nova-ecdsa/src/data/circuits/main.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.1.2; 2 | 3 | // circomlib: https://github.com/iden3/circomlib/tree/master 4 | include "comparators.circom"; 5 | include "gates.circom"; 6 | include "mux1.circom"; 7 | include "bitify.circom"; 8 | include "comparators.circom"; 9 | include "gates.circom"; 10 | include "bitify.circom"; 11 | 12 | // spartan-ecdsa-monorepo: https://github.com/personaelabs/spartan-ecdsa 13 | // eff_ecdsa: https://github.com/personaelabs/spartan-ecdsa/tree/main/packages/circuits/eff_ecdsa_membership 14 | // you will need to remove 'include' statements there to avoid duplicate symbol errors with circomlib 15 | include "eff_ecdsa.circom"; 16 | 17 | include "batch_efficient_ecdsa_pubkey.circom"; 18 | 19 | // 10 is the batch size here. Change it to whatever you want. 20 | component main { public [ step_in ] } = BatchEfficientECDSAPubKey(10); 21 | -------------------------------------------------------------------------------- /apps/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "wasm:build": "cd ../../nova-ecdsa-browser && wasm-pack build --target web --out-dir ../packages/nova-ecdsa-browser-pkg" 11 | }, 12 | "dependencies": { 13 | "comlink": "^4.4.1", 14 | "next": "^13.4.1", 15 | "nova-ecdsa-browser": "workspace:*", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0", 18 | "ui": "workspace:*" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^17.0.12", 22 | "@types/react": "^18.0.22", 23 | "@types/react-dom": "^18.0.7", 24 | "autoprefixer": "^10.4.15", 25 | "eslint-config-custom": "workspace:*", 26 | "tailwindcss": "^3.3.3", 27 | "tsconfig": "workspace:*", 28 | "typescript": "^4.5.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /apps/web/workers/generate-proof.worker.ts: -------------------------------------------------------------------------------- 1 | import { ctx, WEBSITE_ROOT } from "./shared"; 2 | 3 | async function generateProof(filename: string, iteration_count: number, per_iteration_count: number, pp: string, sigs: string) { 4 | const multiThread = await import("nova-ecdsa-browser"); 5 | await multiThread.default(); 6 | await multiThread.initThreadPool(navigator.hardwareConcurrency); 7 | const start = performance.now(); 8 | const data = await multiThread.generate_proof( 9 | WEBSITE_ROOT, filename, 10 | iteration_count, per_iteration_count, 11 | pp, sigs 12 | ); 13 | const end = performance.now(); 14 | ctx.postMessage({ 15 | data: data, 16 | time: end - start 17 | }); 18 | } 19 | 20 | ctx.addEventListener("message", async (event) => { 21 | generateProof( 22 | event.data.filename, 23 | event.data.iteration_count, event.data.per_iteration_count, 24 | event.data.pp, event.data.sigs 25 | ); 26 | }); -------------------------------------------------------------------------------- /packages/ui/turbo/generators/config.ts: -------------------------------------------------------------------------------- 1 | import { PlopTypes } from "@turbo/gen"; 2 | 3 | // Learn more about Turborepo Generators at https://turbo.build/repo/docs/core-concepts/monorepos/code-generation 4 | 5 | export default function generator(plop: PlopTypes.NodePlopAPI): void { 6 | // A simple generator to add a new React component to the internal UI library 7 | plop.setGenerator("react-component", { 8 | description: "Adds a new react component", 9 | prompts: [ 10 | { 11 | type: "input", 12 | name: "name", 13 | message: "What is the name of the component?", 14 | }, 15 | ], 16 | actions: [ 17 | { 18 | type: "add", 19 | path: "{{pascalCase name}}.tsx", 20 | templateFile: "templates/component.hbs", 21 | }, 22 | { 23 | type: "append", 24 | path: "index.tsx", 25 | pattern: /(\/\/ component exports)/g, 26 | template: 'export * from "./{{pascalCase name}}";', 27 | }, 28 | ], 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /packages/ui/Description.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | export const Description: React.FC<{ total: number, type: string, iteration_count: number, per_iteration_count: number }> = ({ total, type, iteration_count, per_iteration_count }) => { 4 | return ( 5 |
6 |

7 | This is an app aggregating {total} ECDSA signatures - {iteration_count} folding steps, {per_iteration_count} signatures per step - over secp/secq using Nova. You can test it to generate and subsequently verify a compressed Nova SNARK. 8 |

9 | Read here on how to leverage Nova within your zk app. 10 | And here on how we did this along with some benchmarks. 11 |

12 |
13 | ) 14 | }; -------------------------------------------------------------------------------- /nova-ecdsa-browser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nova-ecdsa-browser" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | nova-scotia = { git = "https://github.com/dmpierre/Nova-Scotia.git", branch = "secp_secq" } 10 | ff = { version = "0.13", features = ["derive"]} 11 | nova-snark = { git = "https://github.com/dmpierre/Nova.git", default-features = false, branch = "dev-secpsecq-031738d" } 12 | serde = "1.0.185" 13 | serde_json = "1.0.105" 14 | num-bigint = { version = "0.4", features = ["serde", "rand"] } 15 | num-traits = "0.2.15" 16 | 17 | [target.'cfg(target_family = "wasm")'.dependencies] 18 | getrandom = { version = "0.2", features = ["js"]} 19 | wasm-bindgen = { version = "0.2.81", features = ["serde-serialize"]} 20 | console_error_panic_hook = "0.1.7" 21 | rayon = "1.5" 22 | wasm-bindgen-rayon = { version = "1.0"} 23 | web-sys = { version = "0.3", features = ["Request", "Window", "Response"] } 24 | wasm-bindgen-futures = "0.4" 25 | js-sys = "0.3" 26 | 27 | [lib] 28 | crate-type = ["cdylib", "rlib"] -------------------------------------------------------------------------------- /apps/web/hooks/useGenerateParams.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect, useCallback, useState } from "react"; 2 | 3 | export const useGenerateParams = (filename: string) => { 4 | const [pp, setpp] = useState({ data: "" }); 5 | const [time, settime] = useState(); 6 | const [isGenerating, setisGenerating] = useState(false); 7 | 8 | const worker = useRef(); 9 | 10 | useEffect(() => { 11 | worker.current = new Worker(new URL("../workers/generate-params.worker.ts", import.meta.url)); 12 | worker.current.onmessage = (e) => { 13 | console.log("Public params generated!"); 14 | setisGenerating(false); 15 | setpp(e.data); 16 | settime(e.data.time); 17 | }; 18 | return () => { 19 | worker.current?.terminate(); 20 | }; 21 | }, []); 22 | 23 | const generateParams = useCallback(async () => { 24 | console.log("Starting public params generation..."); 25 | setisGenerating(true); 26 | worker.current?.postMessage({ 27 | filename: filename 28 | }); 29 | }, []); 30 | 31 | return { pp, generateParams, isGenerating, time }; 32 | }; -------------------------------------------------------------------------------- /apps/web/hooks/useFolding.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | export const useFoldingParams = () => { 4 | // our folding will be different if on mobile vs on desktop 5 | // hook to detect whether running on mobile phones 6 | const initialWidth = typeof window !== "undefined" ? window.innerWidth : 0; 7 | const [width, setWidth] = useState(initialWidth); 8 | 9 | function handleWindowSizeChange() { 10 | setWidth(window.innerWidth); 11 | } 12 | 13 | useEffect(() => { 14 | window.addEventListener('resize', handleWindowSizeChange); 15 | return () => { 16 | window.removeEventListener('resize', handleWindowSizeChange); 17 | } 18 | }, []); 19 | 20 | const isMobile = width <= 730; 21 | const folding = isMobile ? 22 | { 23 | filename: "agg_ecdsa", 24 | iteration_count: 30, 25 | per_iteration_count: 10, 26 | total: 300, 27 | type: "mobile" 28 | } : 29 | { 30 | filename: "agg_ecdsa", 31 | iteration_count: 30, 32 | per_iteration_count: 10, 33 | total: 300, 34 | type: "desktop" 35 | } 36 | ; 37 | 38 | return folding; 39 | } -------------------------------------------------------------------------------- /nova-ecdsa/src/data/circuits/batch_efficient_ecdsa_pubkey.circom: -------------------------------------------------------------------------------- 1 | include "./efficient_ecdsa_pubkey.circom"; 2 | 3 | template BatchEfficientECDSAPubKey(N_SIGS) { 4 | 5 | signal input step_in[7]; 6 | signal input signatures[N_SIGS][7]; 7 | signal output step_out[7]; 8 | 9 | component sigsChecker[N_SIGS]; 10 | 11 | sigsChecker[0] = EfficientECDSAPubKey(); 12 | sigsChecker[0].s <== step_in[0]; 13 | sigsChecker[0].Tx <== step_in[1]; 14 | sigsChecker[0].Ty <== step_in[2]; 15 | sigsChecker[0].Ux <== step_in[3]; 16 | sigsChecker[0].Uy <== step_in[4]; 17 | sigsChecker[0].pubX <== step_in[5]; 18 | sigsChecker[0].pubY <== step_in[6]; 19 | 20 | 21 | for (var i = 1; i < N_SIGS; i++) { 22 | 23 | sigsChecker[i] = EfficientECDSAPubKey(); 24 | sigsChecker[i].s <== signatures[i - 1][0]; 25 | sigsChecker[i].Tx <== signatures[i - 1][1]; 26 | sigsChecker[i].Ty <== signatures[i - 1][2]; 27 | sigsChecker[i].Ux <== signatures[i - 1][3]; 28 | sigsChecker[i].Uy <== signatures[i - 1][4]; 29 | sigsChecker[i].pubX <== signatures[i - 1][5]; 30 | sigsChecker[i].pubY <== signatures[i - 1][6]; 31 | 32 | } 33 | 34 | for (var i = 0; i < 7; i++) { 35 | step_out[i] <== signatures[N_SIGS - 1][i]; 36 | } 37 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://img.shields.io/badge/turbomonorepo-blue) ![](https://img.shields.io/badge/pnpm-green) 2 | 3 | # In-browser signature aggregation with Nova 4 | 5 | There are two accompanying write-ups to this project. The [first one](https://hackmd.io/KxG-BH1nQPGpdRxz6M50hw) targets developers interested in developing an app using nova and nova-scotia. The [second one](https://hackmd.io/mArMuUx5TC2LEcYecc741Q?view) is a tad more technical, it features how we implement the secp/secq cycle in nova along with some benchmarks. 6 | 7 | This is a monorepo demonstrating a working secp256k1 signature aggregation circuit using Nova. This work aims to show: 8 | 9 | 1. How to leverage nova for zk/snark app developers. Since it features a rust codebase, we can easily port folding circuits to `wasm` and get in-browser functionality. 10 | 11 | 2. Demonstrate the relevancy of Nova in terms of performances. We compiled some benchmarks [here](https://hackmd.io/mArMuUx5TC2LEcYecc741Q#Benchmark). According to our estimates, this demo web app proving time for 300 secp signatures should oscillate from 2 to 6 signatures per second in the browser. 12 | 13 | # How to run 14 | 15 | You can run this app locally: 16 | 17 | ```sh 18 | $ git clone git@github.com:dmpierre/nova-browser-ecdsa.git 19 | $ cd nova-browser-ecdsa/ 20 | $ pnpm i && pnpm dev 21 | ``` 22 | 23 | Navigate to [localhost](http://localhost:3000/) and you should see the app running. -------------------------------------------------------------------------------- /apps/web/hooks/useProve.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect, useCallback, useState } from "react"; 2 | import sigsJSON from "../public/batch.json"; 3 | import { FoldingParams } from "../types/types"; 4 | 5 | export const useProve = (foldingParams: FoldingParams, pp: string) => { 6 | const [proof, setproof] = useState({ data: "" }); 7 | const worker = useRef(); 8 | const [time, settime] = useState(0); 9 | const [isGenerating, setisGenerating] = useState(false); 10 | 11 | useEffect(() => { 12 | worker.current = new Worker(new URL("../workers/generate-proof.worker.ts", import.meta.url)); 13 | worker.current.onmessage = (e) => { 14 | console.log("CompressedSNARK generated!"); 15 | setisGenerating(false); 16 | setproof(e.data); 17 | settime(e.data.time); 18 | }; 19 | return () => { 20 | worker.current?.terminate(); 21 | }; 22 | }, []); 23 | 24 | const generateProof = useCallback(async () => { 25 | setisGenerating(true); 26 | worker.current?.postMessage({ 27 | pp: pp, 28 | sigs: JSON.stringify(sigsJSON), 29 | filename: foldingParams.filename, 30 | iteration_count: foldingParams.iteration_count, 31 | per_iteration_count: foldingParams.per_iteration_count 32 | }); 33 | }, [pp]); 34 | 35 | return { proof, generateProof, isGenerating, time }; 36 | }; -------------------------------------------------------------------------------- /apps/web/hooks/useVerify.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect, useCallback, useState } from "react"; 2 | import sigsJSON from "../public/batch.json"; 3 | import { FoldingParams } from "../types/types"; 4 | 5 | export const useVerify = (foldingParams: FoldingParams, pp: string, proof: string) => { 6 | const worker = useRef(); 7 | const [verify, setverify] = useState<{ data: boolean | undefined }>({ data: undefined }); 8 | const [time, settime] = useState(); 9 | const [isGenerating, setisGenerating] = useState(false); 10 | 11 | useEffect(() => { 12 | worker.current = new Worker(new URL("../workers/verify.worker.ts", import.meta.url)); 13 | worker.current.onmessage = (e) => { 14 | console.log("CompressedSNARK verified!"); 15 | setisGenerating(false); 16 | setverify(e.data); 17 | settime(e.data.time); 18 | }; 19 | return () => { 20 | worker.current?.terminate(); 21 | }; 22 | }, []); 23 | 24 | const generateVerify = useCallback(async () => { 25 | console.log("Starting compressedSNARK verification..."); 26 | setisGenerating(true); 27 | worker.current?.postMessage({ 28 | pp: pp, 29 | sigs: JSON.stringify(sigsJSON), 30 | proof: proof, 31 | iteration_count: foldingParams.iteration_count, 32 | }); 33 | }, [pp, proof]); 34 | 35 | return { verify, generateVerify, isGenerating, time }; 36 | }; -------------------------------------------------------------------------------- /packages/nova-ecdsa-browser-pkg/nova_ecdsa_browser_bg.wasm.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | export function __wbg_effsig_free(a: number): void; 4 | export function generate_params(a: number, b: number, c: number, d: number): number; 5 | export function generate_proof(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number): number; 6 | export function verify_compressed_proof(a: number, b: number, c: number, d: number, e: number, f: number, g: number): number; 7 | export function init_panic_hook(): void; 8 | export function __wbg_wbg_rayon_poolbuilder_free(a: number): void; 9 | export function wbg_rayon_poolbuilder_numThreads(a: number): number; 10 | export function wbg_rayon_poolbuilder_receiver(a: number): number; 11 | export function wbg_rayon_poolbuilder_build(a: number): void; 12 | export function wbg_rayon_start_worker(a: number): void; 13 | export function initThreadPool(a: number): number; 14 | export function read_file(a: number, b: number): number; 15 | export function generate_witness_browser(a: number, b: number, c: number, d: number): number; 16 | export const memory: WebAssembly.Memory; 17 | export const __wbindgen_export_1: WebAssembly.Table; 18 | export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h6ae7b9308d5b8965(a: number, b: number, c: number): void; 19 | export function __wbindgen_malloc(a: number, b: number): number; 20 | export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number; 21 | export function __wbindgen_exn_store(a: number): void; 22 | export function wasm_bindgen__convert__closures__invoke2_mut__h79d8b12638ec1af1(a: number, b: number, c: number, d: number): void; 23 | export function __wbindgen_free(a: number, b: number, c: number): void; 24 | export function __wbindgen_thread_destroy(a: number, b: number): void; 25 | export function __wbindgen_start(): void; 26 | -------------------------------------------------------------------------------- /packages/scripts/generateSampleSignature.ts: -------------------------------------------------------------------------------- 1 | import { computeEffEcdsaPubInput } from "@personaelabs/spartan-ecdsa" 2 | import { ecsign } from "@ethereumjs/util"; 3 | import { ec } from "elliptic"; 4 | import crypto from "crypto"; 5 | import fs from "fs"; 6 | 7 | (BigInt.prototype as any).toJSON = function () { 8 | return this.toString(); 9 | }; 10 | 11 | const main = () => { 12 | /* 13 | * This is a script for generating sample signatures for the sig_ecdsa circuits. 14 | * Useful for generating batches of random signatures when needed. 15 | */ 16 | const numSignatures = parseInt(process.argv[2]); 17 | if (isNaN(numSignatures)) { 18 | throw new Error("Number of signatures must be a number"); 19 | } 20 | if (numSignatures <= 1) { 21 | throw new Error("Number of signatures must be greater than 1"); 22 | } 23 | const curve = new ec("secp256k1"); 24 | const privKey = Buffer.from( 25 | "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", 26 | "hex" 27 | ); 28 | const keypair = curve.keyFromPrivate(privKey) 29 | const point = keypair.getPublic(); 30 | const inputs: any[] = []; 31 | 32 | for (let i = 0; i < numSignatures; i++) { 33 | const msg = crypto.randomBytes(32); 34 | const { v, r: _r, s } = ecsign(msg, privKey); 35 | const r = BigInt("0x" + _r.toString("hex")); 36 | const circuitPubInput = computeEffEcdsaPubInput(r, v, msg); 37 | const input = [ 38 | BigInt("0x" + s.toString("hex")), 39 | circuitPubInput.Tx, 40 | circuitPubInput.Ty, 41 | circuitPubInput.Ux, 42 | circuitPubInput.Uy, 43 | point.getX().toString(), 44 | point.getY().toString(), 45 | ] 46 | inputs.push(input); 47 | } 48 | 49 | const fileOutput = { 50 | "start_pub_input": inputs[0], 51 | "signatures": inputs.slice(1, inputs.length) 52 | }; 53 | 54 | fs.writeFileSync( 55 | "out/sig_ecdsa_batch_sample.json", 56 | JSON.stringify(fileOutput) 57 | ); 58 | }; 59 | 60 | main(); -------------------------------------------------------------------------------- /apps/web/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Header, NovaGenerateParams, NovaGenerateProof, NovaVerify, MainContainer } from "ui"; 4 | import { useGenerateParams } from "../hooks/useGenerateParams"; 5 | import { useProve } from "../hooks/useProve"; 6 | import { useVerify } from "../hooks/useVerify"; 7 | import { useFoldingParams } from "../hooks/useFolding"; 8 | import Image from "next/image"; 9 | import "ui/styles.css"; 10 | import dynamic from "next/dynamic"; 11 | 12 | const Description = dynamic(() => import('ui/Description').then((mod) => mod.Description), { ssr: false }) 13 | const MainNovaContainer = dynamic(() => import('ui/Containers').then((mod) => mod.MainNovaContainer), { ssr: false }) 14 | 15 | export default function Page() { 16 | 17 | const foldingParams = useFoldingParams(); 18 | const { pp, generateParams, isGenerating: isGeneratingParams, time: paramsTime } = useGenerateParams(foldingParams.filename); 19 | const { proof, generateProof, isGenerating: isGeneratingProof, time: provingTime } = useProve(foldingParams, pp.data); 20 | const { verify, generateVerify, isGenerating: isGeneratingVerify, time: verifyTime } = useVerify(foldingParams, pp.data, proof.data); 21 | 22 | return ( 23 | 24 |
25 |
26 | 27 | github 28 | 29 |
30 | 36 | 37 | { 38 | foldingParams.type === "mobile" ? 39 |

Currently desktop only!

40 | : 41 | <> 42 | 43 | { 44 | pp.data ? 45 | 46 | : 47 | <> 48 | } 49 | { 50 | proof.data ? 51 | 52 | : 53 | <> 54 | } 55 | 56 | } 57 |
58 | 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /packages/ui/Nova.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Button } from './Button'; 3 | import { NovaContainer } from './Containers'; 4 | 5 | const formatForDisplay = (data: string) => { 6 | return `${data.slice(1, 20)} [...] ${data.slice(data.length - 20, data.length - 1)}}`; 7 | }; 8 | 9 | const formatTime = (time: number) => { 10 | return (time / 1000).toFixed(2); 11 | } 12 | 13 | interface NovaProps { 14 | data: string | boolean | undefined; 15 | time: number; 16 | isGenerating: boolean; 17 | } 18 | 19 | interface NovaGenerateParams extends NovaProps { 20 | generateParams: () => Promise; 21 | } 22 | 23 | interface NovaGenerateProofProps extends NovaProps { 24 | generateProof: () => Promise; 25 | } 26 | 27 | interface NovaVerifyProps extends NovaProps { 28 | generateVerify: () => Promise; 29 | } 30 | 31 | export const NovaGenerateParams: React.FC = ({ data, isGenerating, time, generateParams }) => { 32 | return ( 33 | 34 |

Generate Public Parameters:

35 | { 36 | isGenerating ? 37 |

Generating...

38 | : 39 | data ? 40 |
41 |

{formatForDisplay(`${data}`)}

42 |

Generation time: {formatTime(time)}s

43 |
44 | : 45 |