micro-zk-proofs
Version:
Create & verify zero-knowledge SNARK proofs in parallel, using noble cryptography
264 lines (219 loc) • 9.39 kB
Markdown
# 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.