UNPKG

micro-zk-proofs

Version:

Create & verify zero-knowledge SNARK proofs in parallel, using noble cryptography

264 lines (219 loc) 9.39 kB
# micro-zk-proofs Create & verify zero-knowledge SNARK proofs in parallel, using [noble cryptography](https://paulmillr.com/noble/). - Supports Groth16. PLONK and others are planned - Optional, fast proof generation using web workers - Supports modern wasm and legacy js circom programs - Parse R1CS, WTNS ## Usage `npm install micro-zk-proofs` `deno add jsr:@paulmillr/micro-zk-proofs` ```js import * as zkp from 'micro-zk-proofs'; const proof = await zkp.bn254.groth.createProof(provingKey, witness); const isValid = zkp.bn254.groth.verifyProof(verificationKey, proof); ``` There are 4 steps: 1. Compile circuit (outside of scope) 2. Setup circuit (outside of scope) 3. Generate witness (outside of scope) 4. Create / verify proof Check out [examples](./examples/) directory. It contains wasm-v2, wasm-v1 and js circuits. ### Compile, setup, generate witness We need a circuit, and a compiler. Circuit compilation is **outside of scope of our library** and depends on a circuit language. Groth16 proofs don't care about language. We use circom in examples below, but you can use anything. There is [no common serialization format](https://github.com/orgs/arkworks-rs/discussions/8) for circom, but this is not a big deal. There are three circom compilers: - WASM circom v2 v2.2.2 [(github)](https://github.com/iden3/circom/releases/tag/v2.2.2) - modern version - WASM circom v1 v0.5.46 [(github)](https://github.com/iden3/circom_old/releases/tag/v0.5.46) - legacy rewrite of v0.0.35 - JS circom v1 v0.0.35 [(github)](https://github.com/iden3/circom_old/releases/tag/v0.0.35) - original JS version We support all versions for backwards-compatibility reasons: v2 programs are different from circom v1, old circuits won't always compile with new compiler, and their output may differ between each other. - First, we need to write circuit in circom language. - Result of compilation: - constraints list/info: - json or r1cs format for circom2 - embedded in circuit.json for old compiler - witness calculation program: - wasm/js for circom2 - embedded in circuit.json for old compiler Witness generation: - This step depends on language, but we just need array of bigints. - For wasm (circom2) there is nice zero-deps calculator generated by compiler itself - there also 'circom_tester' package to run these wasm witness calculation programs > [!NOTE] > When using with existing project, proving/verify keys, witness calculation program and > circuit info should be provided by authors. Compiling same circuit with slightly > different version of compiler will result in incompatible circuit which will generate > invalid proofs. > [!WARNING] > `.setup` method is for tests only, in real production setup you need to do multi-party ceremony to avoid leaking of toxic scalars. ### Create / verify proof Check out [examples](./examples/) directory. It contains wasm-v2, wasm-v1 and js circuits. We will use a test circuit. - This is basic circuit that takes 3 variables: 'a, b, sum' (where a is private) and verifies that 'a + b = sum'. All variables are 32 bit. This allows us to prove that we know such 'a' that produces specific 'sum' with publicly known 'b' without disclosing which a we know. - This is a toy circuit and it is not hard to identify which 'a' was used, in real example there would be some hash. - Last version of sum.json: [sum_last.json from snarkjs v0.2.0](https://raw.githubusercontent.com/iden3/snarkjs/refs/tags/v0.2.0/test/circuit/sum.json) - this specific circuit compiles both with new compiler and old one, other circuits may not. #### WASM v2 ```sh dir='circom-wasm' if [ -d "$dir" ]; then exit 0; fi git clone https://github.com/iden3/circom $dir cd $dir git checkout v2.2.2 cargo build --release ``` ```sh ./circom-wasm/target/release/circom -o output --r1cs --sym --wasm --json --wat circuit-v2/sum_test.circom cd output/sum_test_js mv witness_calculator.js witness_calculator.cjs ``` ```js import { bn254 } from '@noble/curves/bn254'; import * as zkp from 'micro-zk-proofs'; import * as zkpWitness from 'micro-zk-proofs/witness.js'; import { deepStrictEqual } from 'node:assert'; import { default as calc } from './output/sum_test_js/witness_calculator.cjs'; import { readFileSync } from 'node:fs'; import { dirname, join as pjoin } from 'node:path'; import { fileURLToPath } from 'node:url'; const _dirname = dirname(fileURLToPath(import.meta.url)); const read = (...paths) => readFileSync(pjoin(_dirname, ...paths)); console.log('# wasm circom v2'); (async () => { const input = { a: '33', b: '34' }; // 2. setup const coders = zkpWitness.getCoders(bn254.fields.Fr); const setupWasm = zkp.bn254.groth.setup( coders.getCircuitInfo(read('output', 'sum_test.r1cs')) ); // 3. generate witness // NOTE: circom generates zero-deps witness calculator from wasm. // In theory we can do small wasm runtime for it, but it depends on compiler version and can change! const c = await calc(read('output', 'sum_test_js', 'sum_test.wasm')); const binWitness = await c.calculateBinWitness(input, true); const wtns = await c.calculateWTNSBin(input, true); const witness0 = coders.binWitness.decode(binWitness); const witness1 = coders.WTNS.decode(wtns).sections[1].data; // Or using WTNS circom format deepStrictEqual(witness0, witness1); // 4. create proof console.log('creating proof'); const proofWasm = await zkp.bn254.groth.createProof(setupWasm.pkey, witness0); console.log('created proof', proofWasm); // 4. verify proof console.log('verifying proof'); deepStrictEqual( zkp.bn254.groth.verifyProof(setupWasm.vkey, proofWasm), true ); })(); ``` #### WASM v1 ```sh dir='wasmsnark' if [ -d "$dir" ]; then exit 0; fi git clone https://github.com/iden3/wasmsnark.git $dir cd $dir git checkout v0.0.12 ``` ```js import * as zkp from 'micro-zk-proofs'; import { deepStrictEqual } from 'node:assert'; import { readFileSync } from 'node:fs'; import { dirname, join as pjoin } from 'node:path'; import { fileURLToPath } from 'node:url'; const _dirname = dirname(fileURLToPath(import.meta.url)); const read = (...paths) => readFileSync(pjoin(_dirname, ...paths)); console.log('# wasm circom v1'); (async () => { const bigjson = (path) => zkp.stringBigints.decode( JSON.parse(read('wasmsnark', 'example', 'bn128', path)) ); const pkey = bigjson('proving_key.json'); const vkey = bigjson('verification_key.json'); const witness = bigjson('witness.json'); const oldProof = bigjson('proof.json'); const oldProofGood = bigjson('proof_good.json'); const oldProofGood0 = bigjson('proof_good0.json'); const oldPublic = bigjson('public.json'); // Generate proofs console.log('creating proof'); const proofNew = await zkp.bn254.groth.createProof(pkey, witness); console.log('created proof', proofNew); console.log('verifying proof'); deepStrictEqual( zkp.bn254.groth.verifyProof(vkey, proofNew), true ); const { publicSignals } = proofNew; // Verify proofs console.log('verifying proof 2'); deepStrictEqual(zkp.bn254.groth.verifyProof(vkey, { proof: oldProof, publicSignals }), true); console.log('verifying proof 3'); deepStrictEqual(zkp.bn254.groth.verifyProof(vkey, { proof: oldProofGood, publicSignals }), true); console.log('verifying proof 4'); deepStrictEqual(zkp.bn254.groth.verifyProof(vkey, { proof: oldProofGood0, publicSignals }), true); console.log('all proofs were correct') })(); ``` #### JS v1 ```sh dir='circom-js' if [ -d "$dir" ]; then exit 0; fi git clone https://github.com/iden3/circom_old $dir cd $dir git checkout v0.0.35 npm install ``` ```js import { bn254 } from '@noble/curves/bn254'; import * as zkp from 'micro-zk-proofs'; import * as zkpMsm from 'micro-zk-proofs/msm.js'; import * as zkpWitness from 'micro-zk-proofs/witness.js'; import { deepStrictEqual } from 'node:assert'; import sumCircuit from './sum-circuit.json' with { "type": "json" }; const groth = zkp.bn254.groth; const input = { a: '33', b: '34' }; const setupJs = groth.setup(sumCircuit); (async () => { // 2. setup // Generate using circom_old circuit // NOTE: we have this small util to remove dependencies on snarkjs for witness generation // 3. generate witness const witnessJs = zkpWitness.generateWitness(sumCircuit)(input); //deepStrictEqual(witness0, witnessJs); // -> will fail, because we have different constrains! // 4. create proof const proofJs = await groth.createProof(setupJs.pkey, witnessJs); console.log('proof created, signals:', proofJs.publicSignals) // 4. verify proof deepStrictEqual( groth.verifyProof(setupJs.vkey, proofJs), true ); console.log('proof is valid'); })(); // Fast, parallel proofs (async () => { console.log('testing fast parallel proofs, using web workers'); const msm = zkpMsm.initMSM(); const grothp = zkp.buildSnark(bn254, { G1msm: msm.methods.bn254_msmG1, G2msm: msm.methods.bn254_msmG2, }).groth; // 4. generate proof const proofJs2 = await grothp.createProof(setupJs.pkey, witnessJs); console.log('proof created, signals:', proofJs2.publicSignals) // 4. verify proof deepStrictEqual( grothp.verifyProof(setupJs.vkey, proofJs2), true ); console.log('proof is valid'); msm.terminate(); })(); ``` ## License MIT (c) Paul Miller [(https://paulmillr.com)](https://paulmillr.com), see LICENSE file.