# 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.
