multiple proof systems, solidity exports

This commit is contained in:
erhant
2023-04-21 17:24:01 +03:00
parent 5e1e2154b3
commit 9abf3d4dbb
15 changed files with 196 additions and 108 deletions

View File

@@ -1,7 +1,12 @@
# compiler args
# can add --inspect and -c for example
# compiler args, can add --inspect and -c for example
CIRCOMKIT_COMPILER_ARGS="-l ./node_modules --r1cs --wasm --sym"
# proof system to be used, can be: groth16 | plonk | fflonk
CIRCOMKIT_PROOF_SYSTEM="plonk"
# solidity contract export path
CIRCOMKIT_SOLIDITY_PATH="./contracts"
# colors for swag terminal outputs
CIRCOMKIT_COLOR_TITLE='\033[0;34m' # blue
CIRCOMKIT_COLOR_LOG='\033[2;37m' # gray

View File

@@ -33,9 +33,10 @@
- [x] **Witness Testing**: You can test computations & assertions for every template in a circuit, with minimal code-repetition.
- [x] **Proof Testing**: With prover & verification keys and the WASM circuit, you can test proof generation & verification.
- [x] **Simple CLI**: A very easy to use CLI is provided as a wrapper around SnarkJS commands, and they are all provided as `package.json` scripts!
- [ ] **Multiple Proof-Systems**: Soon, only Groth16 for now!
- [ ] **Type Generation**: Generate input & output types for a given circuit, perhaps in future.
- [ ] **Multiple Back-ends**: We only use WASM right now, but we would like to compile prover libraries for other backends.
- [x] **Multiple Proof-Systems**: Just change the configured proof-system at [`.cli.env`](./.cli.env) and you are good to go.
- [x] **Solidity Exports**: Export a verifier contract in Solidity, or export a calldata for your proofs & public signals.
- [ ] **Multiple Backends**: We only use WASM & SnarkJS right now, but we would like to export prover libraries for other backends such as mobile.
- [ ] **Type Generation**: Generate input & output signal type declarations for a given circuit, [work in progress](./scripts/functions/type.sh).
## Usage
@@ -65,25 +66,19 @@ Use the [CLI](./scripts/cli.sh), or its wrapper scripts in [package.json](./pack
# Compile the circuit (generates the main component too)
yarn compile circuit-name [-d directory-name (default: main)]
# Phase-2 Circuit-specific setup
yarn ptau circuit-name -p phase1-ptau-path [-n num-contribs (default: 1)]
# Circuit setup
yarn setup circuit-name -p phase1-ptau-path [-n num-contribs (default: 1)]
# Shorthand for `compile` and then `ptau`
# Shorthand for `compile` and then `setup`
yarn keygen circuit-name -p phase1-ptau-path [-n num-contribs (default: 1)]
# Create a Solidity verifier contract
yarn contract circuit-name
# Clean circuit artifacts
yarn clean circuit-name
# Run the test for a circuit
yarn test circuit-name
# Run all tests
yarn test:all
```
If you just want to generate the `main` component but not compile it, you can also call:
```bash
# Generate the `main` component without compiling it afterwards
yarn instantiate circuit-name [-d directory-name (default: main)]
```
@@ -100,8 +95,25 @@ yarn prove circuit-name -i input-name
# Verify a proof for some input (public signals only)
yarn verify circuit-name -i input-name
# Export calldata to call your Solidity verifier contract
yarn contract circuit-name -i input-name
```
## Testing
To run tests do the following:
```bash
# test a specific circuit
yarn test "circuit name"
# test all circuits
yarn test:all
```
You can test both witness calculations and proof generation & verification. We describe both in their respective sections, going over an example of "Multiplication" circuit.
### Example Circuits
We have several example circuits to help guide you:
@@ -111,19 +123,6 @@ We have several example circuits to help guide you:
- **Sudoku**: A circuit to prove that you know the solution to a Sudoku puzzle.
- **Floating-Point Addition**: A circuit to compute the sum of two floating-point numbers, adapted from [Berkeley ZKP MOOC 2023 - Lab 1](https://github.com/rdi-berkeley/zkp-mooc-lab).
## Testing
To run tests do the following:
```bash
# test all circuits
yarn test:all
# test a specific circuit
yarn test "circuit name"
```
You can test both witness calculations and proof generation & verification. We describe both in their respective sections, going over an example of "Multiplication" circuit.
### Witness Calculation
Witness calculation tests check whether your circuit computes the correct result based on your inputs, and makes sure that assertions are correct. We provide very useful utility functions to help write these tests.

View File

@@ -1,6 +0,0 @@
// auto-generated by instantiate.js
pragma circom 2.0.0;
include "../multiplier.circom";
component main = Multiplier(3);

View File

@@ -13,6 +13,8 @@
"instantiate": "./scripts/cli.sh -f instantiate -c",
"compile": "./scripts/cli.sh -f compile -c",
"clean": "./scripts/cli.sh -f clean -c",
"contract": "./scripts/cli.sh -f contract -c",
"calldata": "./scripts/cli.sh -f calldata -c",
"ptau": "./scripts/cli.sh -f ptau -c",
"keygen": "./scripts/cli.sh -f keygen -c",
"witness": "./scripts/cli.sh -f witness -c",
@@ -35,7 +37,7 @@
"ejs": "^3.1.9",
"gts": "^3.1.1",
"mocha": "^10.2.0",
"snarkjs": "^0.5.0",
"snarkjs": "^0.6.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.5"
}

View File

@@ -8,8 +8,10 @@ source ./.cli.env
# import functions
source ./scripts/functions/type.sh
source ./scripts/functions/ptau.sh
source ./scripts/functions/setup.sh
source ./scripts/functions/clean.sh
source ./scripts/functions/contract.sh
source ./scripts/functions/calldata.sh
source ./scripts/functions/prove.sh
source ./scripts/functions/verify.sh
source ./scripts/functions/witness.sh
@@ -66,6 +68,12 @@ case $FUNC in
clean)
clean $CIRCUIT
;;
contract)
contract $CIRCUIT
;;
calldata)
calldata $CIRCUIT $INPUT
;;
compile)
instantiate $CIRCUIT $COMPILE_DIR && compile $CIRCUIT $COMPILE_DIR
;;
@@ -75,11 +83,11 @@ case $FUNC in
type)
type $CIRCUIT
;;
ptau)
ptau $CIRCUIT $NUM_CONTRIBS $P1_PTAU
setup)
setup $CIRCUIT $P1_PTAU $NUM_CONTRIBS
;;
keygen)
compile $CIRCUIT $COMPILE_DIR && ptau $CIRCUIT $NUM_CONTRIBS $P1_PTAU
compile $CIRCUIT $COMPILE_DIR && setup $CIRCUIT $P1_PTAU $NUM_CONTRIBS
;;
prove)
witness $CIRCUIT $INPUT && prove $CIRCUIT $INPUT
@@ -94,14 +102,16 @@ case $FUNC in
echo "Usage:"
echo " -f <function>"
echo " clean Cleans the build artifacts"
echo " contract Export Solidity verifier"
echo " calldata Export Solidity calldata for verification"
echo " compile Compile the circuit"
echo " instantiate Instantiate the main component"
echo " type Generate types for TypeScript"
echo " ptau Phase-2 setup for the circuit"
echo " setup Phase-2 setup for the circuit"
echo " witness Generate witness from an input"
echo " prove Prove an input"
echo " verify Verify a proof & public signals"
echo " keygen Shorthand for compile & ptau"
echo " keygen Shorthand for compile & setup"
echo " -c <circuit-name>"
echo " -d <directory-name> (default: $COMPILE_DIR)"
echo " -n <num-contributions> (default: $NUM_CONTRIBS)"

View File

@@ -0,0 +1,13 @@
## Exports a solidity contract for the verifier
calldata() {
echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Exporting calldata ===${CIRCOMKIT_COLOR_RESET}"
local CIRCUIT=$1
local INPUT=$2
local CIRCUIT_DIR=./build/$CIRCUIT
snarkjs zkey export soliditycalldata \
$CIRCUIT_DIR/$INPUT/public.json \
$CIRCUIT_DIR/$INPUT/proof.json
echo -e "${CIRCOMKIT_COLOR_LOG}Done!${CIRCOMKIT_COLOR_RESET}"
}

12
scripts/functions/contract.sh Executable file
View File

@@ -0,0 +1,12 @@
## Exports a solidity contract for the verifier
contract() {
echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Generating Solidity verifier ===${CIRCOMKIT_COLOR_RESET}"
local CIRCUIT=$1
local CIRCUIT_DIR=./build/$CIRCUIT
snarkjs zkey export solidityverifier \
$CIRCUIT_DIR/prover_key.zkey \
$CIRCUIT_DIR/verifier.sol
echo -e "${CIRCOMKIT_COLOR_LOG}Contract created at $CIRCUIT_DIR/verifier.sol!${CIRCOMKIT_COLOR_RESET}"
}

View File

@@ -6,7 +6,7 @@ prove() {
local CIRCUIT_DIR=./build/$CIRCUIT
local OUTPUT_DIR=$CIRCUIT_DIR/$INPUT
snarkjs groth16 prove \
snarkjs $CIRCOMKIT_PROOF_SYSTEM prove \
$CIRCUIT_DIR/prover_key.zkey \
$OUTPUT_DIR/witness.wtns \
$OUTPUT_DIR/proof.json \

View File

@@ -1,53 +0,0 @@
## Commence a circuit-specific phase-2 powers-of-tau ceremony
ptau() {
echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Phase-2 Powers of Tau ===${CIRCOMKIT_COLOR_RESET}"
local CIRCUIT=$1 # circuit name
local NUM_CONTRIBS=$2 # number of contributions
local P1_PTAU=$3 # path to phase-1 ptau
local CIRCUIT_DIR=./build/$CIRCUIT # circuit directory
local P2_PTAU=$CIRCUIT_DIR/phase2_final.ptau # phase-2 ptau
local CUR=000 # zkey id, initially 0
local PROVER_KEY=$CIRCUIT_DIR/prover_key.zkey
local VERIFICATION_KEY=$CIRCUIT_DIR/verification_key.json
# check if P1_PTAU exists
if [ ! -f "$P1_PTAU" ]; then
echo -e "${CIRCOMKIT_COLOR_ERR}${P1_PTAU} does not exist.${CIRCOMKIT_COLOR_RESET}"
exit 1
fi
# start phase-2 ceremony (circuit specific)
echo -e "${CIRCOMKIT_COLOR_LOG}this may take a while...${CIRCOMKIT_COLOR_RESET}"
snarkjs powersoftau prepare phase2 $P1_PTAU $P2_PTAU -v
# generate a zkey that contains proving and verification keys, along with phase-2 contributions
snarkjs groth16 setup \
$CIRCUIT_DIR/$CIRCUIT.r1cs \
$P2_PTAU \
$CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey
# get rid of phase-2 ptau
rm $P2_PTAU
# make contributions (001, 002, ...)
for NEXT in $(seq -f "%03g" 1 ${NUM_CONTRIBS})
do
echo "Making Phase-2 Contribution: ${NEXT}"
snarkjs zkey contribute \
$CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey \
$CIRCUIT_DIR/${CIRCUIT}_${NEXT}.zkey -v
rm $CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey
CUR=$NEXT
done
echo "Phase 2 Complete."
# rename key
mv $CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey $CIRCUIT_DIR/prover_key.zkey
# export
snarkjs zkey export verificationkey $PROVER_KEY $VERIFICATION_KEY
echo -e "${CIRCOMKIT_COLOR_LOG}Generated keys\n\tProver key: $PROVER_KEY\n\tVerification key: $VERIFICATION_KEY${CIRCOMKIT_COLOR_RESET}"
}

62
scripts/functions/setup.sh Executable file
View File

@@ -0,0 +1,62 @@
## Commence a circuit-specific phase-2 powers-of-tau ceremony
setup() {
echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Circuit Setup ($CIRCOMKIT_PROOF_SYSTEM) ===${CIRCOMKIT_COLOR_RESET}"
local CIRCUIT=$1 # circuit name
local P1_PTAU=$2 # path to phase-1 ptau
local NUM_CONTRIBS=$3 # number of contributions (for groth16)
local CIRCUIT_DIR=./build/$CIRCUIT # circuit directory
local PROVER_KEY=$CIRCUIT_DIR/prover_key.zkey
local VERIFICATION_KEY=$CIRCUIT_DIR/verification_key.json
# check if P1_PTAU exists
if [ ! -f "$P1_PTAU" ]; then
echo -e "${CIRCOMKIT_COLOR_ERR}${P1_PTAU} does not exist.${CIRCOMKIT_COLOR_RESET}"
exit 1
fi
if [[ "$CIRCOMKIT_PROOF_SYSTEM" == "groth16" ]]; then
local P2_PTAU=$CIRCUIT_DIR/phase2_final.ptau # phase-2 ptau
local CUR=000 # zkey id, initially 0
# if Groth16, circuit specific ceremony is needed
# start phase-2 ceremony (circuit specific)
echo -e "${CIRCOMKIT_COLOR_LOG}this may take a while...${CIRCOMKIT_COLOR_RESET}"
snarkjs powersoftau prepare phase2 $P1_PTAU $P2_PTAU -v
# generate a zkey that contains proving and verification keys, along with phase-2 contributions
snarkjs groth16 setup \
$CIRCUIT_DIR/$CIRCUIT.r1cs \
$P2_PTAU \
$CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey
# get rid of phase-2 ptau
rm $P2_PTAU
# make contributions (001, 002, ...)
for NEXT in $(seq -f "%03g" 1 ${NUM_CONTRIBS})
do
echo "Making Phase-2 Contribution: ${NEXT}"
snarkjs zkey contribute \
$CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey \
$CIRCUIT_DIR/${CIRCUIT}_${NEXT}.zkey -v
rm $CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey
CUR=$NEXT
done
echo "Phase 2 Complete."
# rename key to the prover key
mv $CIRCUIT_DIR/${CIRCUIT}_${CUR}.zkey $PROVER_KEY
else
# otherwise, we can use that phase-1 ptau alone
snarkjs $CIRCOMKIT_PROOF_SYSTEM setup $CIRCUIT_DIR/$CIRCUIT.r1cs \
$P1_PTAU \
$PROVER_KEY
fi
# export
snarkjs zkey export verificationkey $PROVER_KEY $VERIFICATION_KEY
echo -e "${CIRCOMKIT_COLOR_LOG}Generated keys\n\tProver key: $PROVER_KEY\n\tVerification key: $VERIFICATION_KEY${CIRCOMKIT_COLOR_RESET}"
}

View File

@@ -3,9 +3,9 @@ verify() {
echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Verifying proof ===${CIRCOMKIT_COLOR_RESET}"
local CIRCUIT=$1
local INPUT=$2
local CIRCUIT_DIR=./build/${CIRCUIT}
local CIRCUIT_DIR=./build/$CIRCUIT
snarkjs groth16 verify \
snarkjs $CIRCOMKIT_PROOF_SYSTEM verify \
$CIRCUIT_DIR/verification_key.json \
$CIRCUIT_DIR/$INPUT/public.json \
$CIRCUIT_DIR/$INPUT/proof.json

View File

@@ -44,9 +44,9 @@ const INPUTS: {[N in BoardSizes]: any} = {
},
};
[9, 4].map(N =>
([9, 4] as BoardSizes[]).map(N =>
describe(`sudoku (${N} by ${N})`, () => {
const INPUT = INPUTS[N as BoardSizes];
const INPUT = INPUTS[N];
let circuit: Awaited<ReturnType<typeof createWasmTester>>;
before(async () => {

View File

@@ -50,3 +50,8 @@ export type CircuitConfig = {
export type Config = {
[circuitName: string]: CircuitConfig;
};
/**
* Proof system to be used
*/
export type ProofSystem = 'groth16' | 'plonk' | 'fflonk';

View File

@@ -1,13 +1,14 @@
import fs from 'fs';
const snarkjs = require('snarkjs');
import {expect} from 'chai';
import type {CircuitSignals, FullProof} from '../types/circuit';
import type {CircuitSignals, FullProof, ProofSystem} from '../types/circuit';
/**
* A more extensive Circuit class, able to generate proofs & verify them.
* Assumes that prover key and verifier key have been computed.
*/
export class ProofTester {
public readonly proofSystem: ProofSystem;
private readonly wasmPath: string;
private readonly proverKeyPath: string;
private readonly verificationKey: object;
@@ -15,8 +16,9 @@ export class ProofTester {
/**
* Sets the paths & loads the verification key
* @param circuit a proof tester
* @param proofSystem proof system to use, defaults to `groth16`
*/
constructor(circuit: string) {
constructor(circuit: string, proofSystem: ProofSystem = 'groth16') {
// find paths (computed w.r.t circuit name)
this.wasmPath = `./build/${circuit}/${circuit}_js/${circuit}.wasm`;
this.proverKeyPath = `./build/${circuit}/prover_key.zkey`;
@@ -30,15 +32,19 @@ export class ProofTester {
// load verification key
this.verificationKey = JSON.parse(fs.readFileSync(verificationKeyPath).toString());
// set proof system
this.proofSystem = proofSystem;
}
/**
* Generate a proof for the witness computed from the given input signals.
* Calls `fullProve` behind the scenes.
* @param input input signals for the circuit
* @returns a proof and public signals
*/
async prove(input: CircuitSignals): Promise<FullProof> {
return await snarkjs.groth16.fullProve(input, this.wasmPath, this.proverKeyPath);
return await snarkjs[this.proofSystem].fullProve(input, this.wasmPath, this.proverKeyPath);
}
/**
@@ -48,7 +54,7 @@ export class ProofTester {
* @returns `true` if proof verifies, `false` otherwise
*/
async verify(proof: object, publicSignals: string[]): Promise<boolean> {
return await snarkjs.groth16.verify(this.verificationKey, publicSignals, proof);
return await snarkjs[this.proofSystem].verify(this.verificationKey, publicSignals, proof);
}
/**

View File

@@ -927,6 +927,13 @@ circom_runtime@0.1.21:
dependencies:
ffjavascript "0.2.56"
circom_runtime@0.1.22:
version "0.1.22"
resolved "https://registry.yarnpkg.com/circom_runtime/-/circom_runtime-0.1.22.tgz#f957c47662cdd03cd3fb76979c434c719a366373"
integrity sha512-V/XYZWBhbZY8SotkaGH4FbiDYAZ8a1Md++MBiKPDOuWS/NIJB+Q+XIiTC8zKMgoDaa9cd2OiTvsC9J6te7twNg==
dependencies:
ffjavascript "0.2.57"
circom_tester@^0.0.19:
version "0.0.19"
resolved "https://registry.yarnpkg.com/circom_tester/-/circom_tester-0.0.19.tgz#e8bed494d080f8186bd0ac6571755d00ccec83bd"
@@ -1400,7 +1407,7 @@ ffjavascript@0.2.56:
wasmcurves "0.2.0"
web-worker "^1.2.0"
ffjavascript@^0.2.45, ffjavascript@^0.2.48, ffjavascript@^0.2.56:
ffjavascript@0.2.57, ffjavascript@^0.2.45, ffjavascript@^0.2.48, ffjavascript@^0.2.56:
version "0.2.57"
resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.2.57.tgz#ba1be96015b2688192e49f2f4de2cc5150fd8594"
integrity sha512-V+vxZ/zPNcthrWmqfe/1YGgqdkTamJeXiED0tsk7B84g40DKlrTdx47IqZuiygqAVG6zMw4qYuvXftIJWsmfKQ==
@@ -2354,6 +2361,16 @@ r1csfile@0.0.41, r1csfile@^0.0.41:
fastfile "0.0.20"
ffjavascript "0.2.56"
r1csfile@0.0.45:
version "0.0.45"
resolved "https://registry.yarnpkg.com/r1csfile/-/r1csfile-0.0.45.tgz#59d59a33f8b5280017fc00ee691d003a3d705fe0"
integrity sha512-YKIp4D441aZ6OoI9y+bfAyb2j4Cl+OFq/iiX6pPWDrL4ZO968h0dq0w07i65edvrTt7/G43mTnl0qEuLXyp/Yw==
dependencies:
"@iden3/bigarray" "0.0.2"
"@iden3/binfileutils" "0.0.11"
fastfile "0.0.20"
ffjavascript "0.2.57"
randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -2542,7 +2559,7 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
snarkjs@0.5.0, snarkjs@^0.5.0:
snarkjs@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/snarkjs/-/snarkjs-0.5.0.tgz#cf26bf1d3835eb16b4b330a438bad9824837d6b0"
integrity sha512-KWz8mZ2Y+6wvn6GGkQo6/ZlKwETdAGohd40Lzpwp5TUZCn6N6O4Az1SuX1rw/qREGL6Im+ycb19suCFE8/xaKA==
@@ -2558,6 +2575,22 @@ snarkjs@0.5.0, snarkjs@^0.5.0:
logplease "^1.2.15"
r1csfile "0.0.41"
snarkjs@^0.6.0:
version "0.6.10"
resolved "https://registry.yarnpkg.com/snarkjs/-/snarkjs-0.6.10.tgz#d751645a45d2390abcb007c409b31d813bedd108"
integrity sha512-Ls9XPTIuW2Ivk2sACM0Pn/FnKZP8UYYqRbzDl3SnglY5Gw28BIp1sfArOxdJJ2QrlbK7e6FFjMHQPvTp0Ttn0w==
dependencies:
"@iden3/binfileutils" "0.0.11"
bfj "^7.0.2"
blake2b-wasm "^2.4.0"
circom_runtime "0.1.22"
ejs "^3.1.6"
fastfile "0.0.20"
ffjavascript "0.2.57"
js-sha3 "^0.8.0"
logplease "^1.2.15"
r1csfile "0.0.45"
spdx-correct@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"