diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..024efd4
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,24 @@
+name: build
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ style:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: 18.x
+
+ - name: Install dependencies
+ run: yarn
+
+ - name: Build everything
+ run: yarn build
diff --git a/.gitignore b/.gitignore
index 5830290..6b58145 100644
--- a/.gitignore
+++ b/.gitignore
@@ -119,3 +119,4 @@ tmp.ptau
# ignore auto generated test circuits
circuits/test
+ptau
diff --git a/.mocharc.json b/.mocharc.json
index 7cd0206..6382cd6 100644
--- a/.mocharc.json
+++ b/.mocharc.json
@@ -1,7 +1,7 @@
{
"extension": ["ts"],
"require": "ts-node/register",
- "spec": "tests/**/*.ts",
+ "spec": "tests/**/*.test.ts",
"timeout": 100000,
"exit": true
}
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..a66bec2
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,5 @@
+*
+!dist
+!LICENSE
+!package.json
+!README.md
diff --git a/README.md b/README.md
index 506de0c..2092f32 100644
--- a/README.md
+++ b/README.md
@@ -2,24 +2,21 @@
Circomkit
- A simple-to-use Circom & SnarkJS circuit development & testing environment.
+ A simple-to-use & opinionated circuit development & testing toolkit.
+
+
+
-
-
-
-
-
-
-
-
+
+
@@ -65,22 +62,19 @@ You can omit `pubs` and `params` options, they default to `[]`. Afterwards, you
```bash
# Compile the circuit (generates the main component & compiles it)
-yarn compile circuit-name
+npx circomkit compile circuit-name
# Circuit setup
-yarn setup circuit-name -p phase1-ptau-path [-n num-contribs (default: 1)]
-
-# Shorthand for `compile` and then `setup`
-yarn keygen circuit-name -p phase1-ptau-path [-n num-contribs (default: 1)]
+npx circomkit setup circuit-name -p phase1-ptau-path
# Create a Solidity verifier contract
-yarn contract circuit-name
+npx circomkit contract circuit-name
# Clean circuit artifacts
-yarn clean circuit-name
+npx circomkit clean circuit-name
# Generate the `main` component without compiling it afterwards
-yarn instantiate circuit-name
+npx circomkit instantiate circuit-name
```
You can change some general settings such as the configured proof system or the prime field under [circomkit.env](./circomkit.env).
@@ -91,35 +85,21 @@ Some actions such as generating a witness, generating a proof and verifying a pr
```bash
# Generate a witness for some input
-yarn witness circuit-name [-i input-name (default: "default")]
+npx circomkit witness circuit-name [-i input-name (default: "default")]
# Generate a proof for some input
-yarn prove circuit-name [-i input-name (default: "default")]
+npx circomkit prove circuit-name [-i input-name (default: "default")]
# Verify a proof for some input (public signals only)
-yarn verify circuit-name [-i input-name (default: "default")]
+npx circomkit verify circuit-name [-i input-name (default: "default")]
# Debug a witness of some input
-yarn debug circuit-name [-i input-name (default: "default")]
+npx circomkit debug circuit-name input-name
# Export calldata to call your Solidity verifier contract
-yarn calldata circuit-name [-i input-name (default: "default")]
+npx circomkit calldata circuit-name [-i input-name (default: "default")]
```
-## Testing
-
-To run tests do the following:
-
-```bash
-# test a specific circuit
-yarn test
-
-# 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 that you can check out. With them, you can prove the following statements:
@@ -155,16 +135,16 @@ describe('multiplier', () => {
it('should compute correctly', async () => {
const randomNumbers = Array.from({length: N}, () => Math.floor(Math.random() * 100 * N));
- await circuit.expectCorrectAssert({in: randomNumbers}, {out: randomNumbers.reduce((prev, acc) => acc * prev)});
+ await circuit.expectPass({in: randomNumbers}, {out: randomNumbers.reduce((prev, acc) => acc * prev)});
});
});
```
With the circuit object, we can do the following:
-- `circuit.expectCorrectAssert(input, output)` to test whether we get the expected output for some given input.
-- `circuit.expectCorrectAssert(input)` to test whether the circuit assertions pass for some given input
-- `circuit.expectFailedAssert(input)` to test whether the circuit assertions pass for some given input
+- `circuit.expectPass(input, output)` to test whether we get the expected output for some given input.
+- `circuit.expectPass(input)` to test whether the circuit assertions pass for some given input
+- `circuit.expectFail(input)` to test whether the circuit assertions pass for some given input
#### Witness
@@ -209,7 +189,7 @@ describe('multiplier utilities', () => {
});
it('should pass for in range', async () => {
- await circuit.expectCorrectAssert({in: [7, 5]}, {out: 7 * 5});
+ await circuit.expectPass({in: [7, 5]}, {out: 7 * 5});
});
});
});
@@ -232,19 +212,19 @@ describe('multiplier proofs', () => {
});
it('should verify', async () => {
- await circuit.expectVerificationPass(fullProof.proof, fullProof.publicSignals);
+ await circuit.expectPass(fullProof.proof, fullProof.publicSignals);
});
it('should NOT verify', async () => {
- await circuit.expectVerificationFail(fullProof.proof, ['13']);
+ await circuit.expectFail(fullProof.proof, ['13']);
});
});
```
The two utility functions provided here are:
-- `circuit.expectVerificationPass(proof, publicSignals)` that makes sure that the given proof is **accepted** by the verifier for the given public signals.
-- `circuit.expectVerificationFail(proof, publicSignals)` that makes sure that the given proof is **rejected** by the verifier for the given public signals.
+- `circuit.expectPass(proof, publicSignals)` that makes sure that the given proof is **accepted** by the verifier for the given public signals.
+- `circuit.expectFail(proof, publicSignals)` that makes sure that the given proof is **rejected** by the verifier for the given public signals.
## File Structure
@@ -288,6 +268,20 @@ circomkit
└── ...
```
+## Testing
+
+To run tests do the following:
+
+```bash
+# test a specific circuit
+yarn test
+
+# 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.
+
## Styling
We use [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html) for the TypeScript codes.
diff --git a/circomkit.env b/circomkit.env
deleted file mode 100644
index 85fe643..0000000
--- a/circomkit.env
+++ /dev/null
@@ -1,20 +0,0 @@
-# proof system to be used, can be: groth16 | plonk | fflonk
-CIRCOMKIT_PROOF_SYSTEM="groth16"
-
-# name of the curve to be used: bn128 | bls12381 | goldilocks
-CIRCOMKIT_ELLIPTIC_CURVE="bn128"
-
-# solidity contract export path
-CIRCOMKIT_SOLIDITY_PATH="./contracts"
-
-# compiler args, can add --inspect and -c for example
-CIRCOMKIT_COMPILER_ARGS="--r1cs --wasm --sym -l ./node_modules -p $CIRCOMKIT_ELLIPTIC_CURVE --inspect"
-
-# solidity contract export path
-CIRCOMKIT_VERSION="2.1.0"
-
-# colors for swag terminal outputs
-CIRCOMKIT_COLOR_TITLE='\033[0;34m' # blue
-CIRCOMKIT_COLOR_LOG='\033[2;37m' # gray
-CIRCOMKIT_COLOR_ERR='\033[0;31m' # red
-CIRCOMKIT_COLOR_RESET='\033[0m' # reset color
diff --git a/circomkit.json b/circomkit.json
new file mode 100644
index 0000000..0d9c90c
--- /dev/null
+++ b/circomkit.json
@@ -0,0 +1,5 @@
+{
+ "colors": {
+ "title": "\u001b[0;33m"
+ }
+}
diff --git a/circuits/main/multiplier_3.circom b/circuits/main/multiplier_3.circom
index 8b5618b..87c6dc4 100644
--- a/circuits/main/multiplier_3.circom
+++ b/circuits/main/multiplier_3.circom
@@ -1,4 +1,4 @@
-// auto-generated by instantiate.js
+// auto-generated by circomkit
pragma circom 2.1.0;
include "../multiplier.circom";
diff --git a/ejs/template.circom b/ejs/template.circom
deleted file mode 100644
index fb58573..0000000
--- a/ejs/template.circom
+++ /dev/null
@@ -1,13 +0,0 @@
-<%#'this file is an EJS template to generate main component for circuits'-%>
-<%#'configuration is read from config.js in the project root directory'-%>
-<%#'do not edit this file!'-%>
-// auto-generated by instantiate.js
-pragma circom <%= typeof version == "undefined" ? "2.0.0" : version %>;
-
-include "../<%= file %>.circom";
-
-component main<%=
- (typeof pubs == "undefined" || pubs.length == 0) ?
- "" :
- " {public[" + pubs.join(", ") + "]}"
- %> = <%= template %>(<%= typeof params == "undefined" ? "" : params.join(", ") %>);
diff --git a/package.json b/package.json
index d8e3b63..9281dda 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,19 @@
{
"name": "circomkit",
- "version": "0.0.1",
+ "version": "0.0.4",
"description": "A Circom development environment",
"author": "erhant",
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
+ "files": [
+ "dist"
+ ],
+ "bin": "dist/bin/index.js",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "homepage": "https://github.com/erhant/circomkit#readme",
"repository": {
"type": "git",
"url": "https://github.com/erhant/circomkit.git"
@@ -17,6 +24,8 @@
"scripts": {
"build": "yarn build:clean && npx tsc -p tsconfig.build.json",
"build:clean": "rimraf dist/",
+ "prepublish": "yarn build",
+ "cli": "yarn build && node ./dist/bin",
"test:all": "npx mocha",
"test": "npx mocha --grep",
"lint": "npx gts lint",
@@ -38,7 +47,6 @@
},
"devDependencies": {
"@types/chai": "^4.3.4",
- "@types/ejs": "^3.1.2",
"@types/mocha": "^10.0.1",
"@types/node": "^18.11.18",
"gts": "^3.1.1",
@@ -53,9 +61,17 @@
"dependencies": {
"chai": "^4.3.7",
"circom_tester": "^0.0.19",
- "ejs": "^3.1.9"
- },
- "peerDependencies": {
"snarkjs": "^0.6.0"
- }
+ },
+ "keywords": [
+ "circom",
+ "zk",
+ "zero knowledge",
+ "zero-knowledge",
+ "snarkjs",
+ "typescript",
+ "cli",
+ "tooling",
+ "blockchain"
+ ]
}
diff --git a/ptau/README.md b/ptau/README.md
deleted file mode 100644
index 229de04..0000000
--- a/ptau/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-$$
-\left(g^\tau, g^{\tau^2}, g^{\tau^3}, g^{\tau^4}, \ldots, g^{\tau^d}\right)
-$$
-
-For circuit setups, you need to have `ptau` files from the universal setup phase. See [SnarkJS docs](https://github.com/iden3/snarkjs#7-prepare-phase-2) for more information.
diff --git a/ptau/powersOfTau28_hez_final_12.ptau b/ptau/powersOfTau28_hez_final_12.ptau
deleted file mode 100644
index 144198e..0000000
Binary files a/ptau/powersOfTau28_hez_final_12.ptau and /dev/null differ
diff --git a/scripts/cli.sh b/scripts/cli.sh
deleted file mode 100755
index c8ff341..0000000
--- a/scripts/cli.sh
+++ /dev/null
@@ -1,137 +0,0 @@
-#!/bin/bash
-
-cd "${0%/*}"/.. # get to project root
-set -e # abort on error
-
-# load CLI environment variables
-source ./circomkit.env
-
-# check validness of variables
-valid_proof_systems=("groth16" "plonk" "fflonk")
-if [[ ! " ${valid_proof_systems[@]} " =~ " ${CIRCOMKIT_PROOF_SYSTEM} " ]]; then
- echo -e "${CIRCOMKIT_COLOR_ERR}Invalid proof system: $CIRCOMKIT_PROOF_SYSTEM${CIRCOMKIT_COLOR_RESET}"
- exit 1
-fi
-valid_elliptic_curves=("bn128" "bls12381" "goldilocks")
-if [[ ! " ${valid_elliptic_curves[@]} " =~ " ${CIRCOMKIT_ELLIPTIC_CURVE} " ]]; then
- echo -e "${CIRCOMKIT_COLOR_ERR}Invalid elliptic curve: $CIRCOMKIT_ELLIPTIC_CURVE${CIRCOMKIT_COLOR_RESET}"
- exit 1
-fi
-
-# import functions
-source ./scripts/functions/type.sh
-source ./scripts/functions/setup.sh
-source ./scripts/functions/compile.sh
-source ./scripts/functions/clean.sh
-source ./scripts/functions/contract.sh
-source ./scripts/functions/calldata.sh
-source ./scripts/functions/debug.sh
-source ./scripts/functions/instantiate.sh
-source ./scripts/functions/prove.sh
-source ./scripts/functions/verify.sh
-source ./scripts/functions/witness.sh
-
-# default values
-NUM_CONTRIBS=1
-INPUT="default"
-P1_PTAU="./ptau/powersOfTau28_hez_final_12.ptau"
-
-# get arguments
-while getopts "f:c:n:i:p:d:" opt; do
- case $opt in
- # function to call
- f)
- FUNC="$OPTARG"
- ;;
- # circuit name
- c)
- CIRCUIT="$OPTARG"
- ;;
- # number of contributions
- n)
- NUM_CONTRIBS="$OPTARG"
- ;;
- # input name
- i)
- INPUT="$OPTARG"
- ;;
- # phase-1 ptau path
- p)
- P1_PTAU="$OPTARG"
- ;;
- # invalid option
- \?)
- echo "Invalid option -$OPTARG" >&2
- exit 1
- ;;
- esac
-
- case $OPTARG in
- -*) echo "Option $opt needs a valid argument"
- exit 1
- ;;
- esac
-done
-
-# parse circuit & input paths via basename
-# TODO, maybe not needed
-CIRCUIT=$(basename $CIRCUIT .circom)
-INPUT=$(basename $INPUT .json)
-
-case $FUNC in
- clean)
- clean $CIRCUIT
- ;;
- contract)
- contract $CIRCUIT
- ;;
- calldata)
- calldata $CIRCUIT $INPUT
- ;;
- compile)
- instantiate $CIRCUIT && compile $CIRCUIT
- ;;
- debug)
- debug $CIRCUIT $INPUT
- ;;
- instantiate)
- instantiate $CIRCUIT
- ;;
- type)
- type $CIRCUIT
- ;;
- setup)
- setup $CIRCUIT $P1_PTAU $NUM_CONTRIBS
- ;;
- keygen)
- compile $CIRCUIT && setup $CIRCUIT $P1_PTAU $NUM_CONTRIBS
- ;;
- prove)
- witness $CIRCUIT $INPUT && prove $CIRCUIT $INPUT
- ;;
- witness)
- witness $CIRCUIT $INPUT
- ;;
- verify)
- verify $CIRCUIT $INPUT
- ;;
- *)
- echo "Usage:"
- echo " -f "
- 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 " 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 & setup"
- echo " -c "
- echo " -n (default: $NUM_CONTRIBS)"
- echo " -i "
- echo " -p "
- ;;
-esac
diff --git a/scripts/functions/calldata.sh b/scripts/functions/calldata.sh
deleted file mode 100755
index 530cc4f..0000000
--- a/scripts/functions/calldata.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-## 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}"
-}
diff --git a/scripts/functions/clean.sh b/scripts/functions/clean.sh
deleted file mode 100755
index bf4afcf..0000000
--- a/scripts/functions/clean.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-## Clean build files
-clean() {
- echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Cleaning artifacts ===${CIRCOMKIT_COLOR_RESET}"
- local CIRCUIT=$1
- local CIRCUIT_DIR=./build/$CIRCUIT
- local TARGET=./circuits/main/$CIRCUIT.circom
-
- rm -rf $CIRCUIT_DIR
- rm -f $TARGET
-
- echo -e "${CIRCOMKIT_COLOR_LOG}Deleted $CIRCUIT_DIR and $TARGET${CIRCOMKIT_COLOR_RESET}"
-}
diff --git a/scripts/functions/compile.sh b/scripts/functions/compile.sh
deleted file mode 100755
index 6d6163e..0000000
--- a/scripts/functions/compile.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-## Compile the circuit, outputting R1CS and JS files
-compile() {
- echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Compiling the circuit ===${CIRCOMKIT_COLOR_RESET}"
- local CIRCUIT=$1
- local CIRCOM_IN=./circuits/main/$CIRCUIT.circom
- local CIRCOM_OUT=./build/$CIRCUIT
-
- # create build dir if not exists already
- mkdir -p $CIRCOM_OUT
-
- # compile with circom
- echo "circom $CIRCOM_IN -o $CIRCOM_OUT $CIRCOMKIT_COMPILER_ARGS"
- circom $CIRCOM_IN -o $CIRCOM_OUT $CIRCOMKIT_COMPILER_ARGS
-
- echo -e "${CIRCOMKIT_COLOR_LOG}Built artifacts under $CIRCOM_OUT${CIRCOMKIT_COLOR_RESET}"
-}
diff --git a/scripts/functions/contract.sh b/scripts/functions/contract.sh
deleted file mode 100755
index 8d94161..0000000
--- a/scripts/functions/contract.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-## 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}"
-}
diff --git a/scripts/functions/debug.sh b/scripts/functions/debug.sh
deleted file mode 100644
index a525411..0000000
--- a/scripts/functions/debug.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-## Debug a witness
-debug() {
- echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Debugging witness ===${CIRCOMKIT_COLOR_RESET}"
- local CIRCUIT=$1
- local INPUT=$2
- local CIRCUIT_DIR=./build/$CIRCUIT
- local OUTPUT_DIR=./build/$CIRCUIT/$INPUT # directory for proof & public signals
- local INPUT_DIR=./inputs/$CIRCUIT # directory for inputs
-
- snarkjs wtns debug \
- $CIRCUIT_DIR/${CIRCUIT}_js/$CIRCUIT.wasm \
- $INPUT_DIR/$INPUT.json \
- $OUTPUT_DIR/witness.wtns \
- $CIRCUIT_DIR/$CIRCUIT.sym
-
- echo -e "${CIRCOMKIT_COLOR_LOG}Done!${CIRCOMKIT_COLOR_RESET}"
-}
diff --git a/scripts/functions/instantiate.sh b/scripts/functions/instantiate.sh
deleted file mode 100755
index d848a8f..0000000
--- a/scripts/functions/instantiate.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-## Instantiate the main component
-instantiate() {
- echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Creating main component ===${CIRCOMKIT_COLOR_RESET}"
- local CIRCUIT=$1
-
- # parse json for the circuit, trim first and last lines, and then remove all whitespace
- local MATCH=$(sed -n "/ *\"${CIRCUIT}\": *{/, /^ *}[, ]$/p" ./circuits.json | sed '1d;$d' | tr -d "[:space:]")
- if [ -z "$MATCH" ]
- then
- echo -e "${CIRCOMKIT_COLOR_ERR}No such circuit found!${CIRCOMKIT_COLOR_RESET}"
- exit
- fi
-
- # create JSON object
- local JSON_IN="{\"version\":\"${CIRCOMKIT_VERSION}\",$MATCH}"
-
- # generate the circuit main component
- local OUTDIR="./circuits/main/$CIRCUIT.circom"
- npx ejs ./ejs/template.circom -i $JSON_IN -o $OUTDIR
-
- echo -e "${CIRCOMKIT_COLOR_LOG}Done!${CIRCOMKIT_COLOR_RESET}"
-}
diff --git a/scripts/functions/prove.sh b/scripts/functions/prove.sh
deleted file mode 100755
index 386651b..0000000
--- a/scripts/functions/prove.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-## Generate a proof
-prove() {
- echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Generating proof ===${CIRCOMKIT_COLOR_RESET}"
- local CIRCUIT=$1
- local INPUT=$2
- local CIRCUIT_DIR=./build/$CIRCUIT
- local OUTPUT_DIR=$CIRCUIT_DIR/$INPUT
-
- snarkjs $CIRCOMKIT_PROOF_SYSTEM prove \
- $CIRCUIT_DIR/prover_key.zkey \
- $OUTPUT_DIR/witness.wtns \
- $OUTPUT_DIR/proof.json \
- $OUTPUT_DIR/public.json
-
- echo -e "${CIRCOMKIT_COLOR_LOG}Generated under $OUTPUT_DIR${CIRCOMKIT_COLOR_RESET}"
-}
diff --git a/scripts/functions/setup.sh b/scripts/functions/setup.sh
deleted file mode 100755
index ebfde16..0000000
--- a/scripts/functions/setup.sh
+++ /dev/null
@@ -1,62 +0,0 @@
-## Commence a circuit-specific setup
-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 "PTAU file ${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}"
-}
diff --git a/scripts/functions/type.sh b/scripts/functions/type.sh
deleted file mode 100755
index c1d6af1..0000000
--- a/scripts/functions/type.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-## Parse the template circuit that you are using for your main component
-## and generate TypeScript interfaces for it
-type() {
- set -e
-
- echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Generating types ===${CIRCOMKIT_COLOR_RESET}"
- local CIRCUIT=$1
- local SYM=./build/$CIRCUIT/$CIRCUIT.sym
-
- # choose lines with 1 dot only (these are the signals of the main component), extract their names
- local MAIN_SIGNALS=$(cat $SYM | awk -F '.' 'NF==2 {print $2}')
-
- # get the unique signal names
- local MAIN_SIGNAL_NAMES=$(echo "$MAIN_SIGNALS" | awk -F '[' '{print $1}' | uniq)
-
- # get the last signal for each signal name
- local SIGNALS=""
- for SIGNAL in $MAIN_SIGNAL_NAMES; do
- SIGNALS+="$(echo "$MAIN_SIGNALS" | grep $SIGNAL | tail -n 1) "
- done
- echo "$SIGNALS"
-
- echo -e "\n${CIRCOMKIT_COLOR_LOG}Types generated!${CIRCOMKIT_COLOR_RESET}"
-}
diff --git a/scripts/functions/verify.sh b/scripts/functions/verify.sh
deleted file mode 100755
index 78001a6..0000000
--- a/scripts/functions/verify.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-## Verify a witness & proof
-verify() {
- echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Verifying proof ===${CIRCOMKIT_COLOR_RESET}"
- local CIRCUIT=$1
- local INPUT=$2
- local CIRCUIT_DIR=./build/$CIRCUIT
-
- snarkjs $CIRCOMKIT_PROOF_SYSTEM verify \
- $CIRCUIT_DIR/verification_key.json \
- $CIRCUIT_DIR/$INPUT/public.json \
- $CIRCUIT_DIR/$INPUT/proof.json
-
- echo -e "${CIRCOMKIT_COLOR_LOG}Verification complete.${CIRCOMKIT_COLOR_RESET}"
-}
diff --git a/scripts/functions/witness.sh b/scripts/functions/witness.sh
deleted file mode 100755
index 10af6a1..0000000
--- a/scripts/functions/witness.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-## Calculates the witness for the given circuit and input
-witness() {
- echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Computing witness ===${CIRCOMKIT_COLOR_RESET}"
- local CIRCUIT=$1
- local INPUT=$2
- local JS_DIR=./build/$CIRCUIT/${CIRCUIT}_js # JS files for the circuit
- local OUTPUT_DIR=./build/$CIRCUIT/$INPUT # directory for proof & public signals
- local INPUT_DIR=./inputs/$CIRCUIT # directory for inputs
- local WITNESS=$OUTPUT_DIR/witness.wtns # witness output
-
- mkdir -p $OUTPUT_DIR
-
- node $JS_DIR/generate_witness.js \
- $JS_DIR/$CIRCUIT.wasm \
- $INPUT_DIR/$INPUT.json \
- $WITNESS
-
- echo -e "${CIRCOMKIT_COLOR_LOG}Generated\n\tWitness: $WITNESS${CIRCOMKIT_COLOR_RESET}"
-}
diff --git a/src/bin/index.ts b/src/bin/index.ts
new file mode 100644
index 0000000..d733715
--- /dev/null
+++ b/src/bin/index.ts
@@ -0,0 +1,187 @@
+#!/usr/bin/env node
+import {existsSync, mkdirSync, readFileSync, writeFileSync} from 'fs';
+import {Circomkit} from '../circomkit';
+import {initFiles} from '../utils/initFiles';
+
+const CONFIG_PATH = './circomkit.json';
+const DEFAULT_INPUT = 'default';
+const USAGE = `Usage:
+
+ Compile the circuit.
+ compile circuit
+
+ Create main component.
+ instantiate circuit
+
+ Clean build artifacts & main component.
+ clean circuit
+
+ Export Solidity verifier.
+ contract circuit
+
+ Commence circuit-specific setup.
+ setup circuit
+
+ Download the PTAU file needed for the circuit.
+ ptau circuit
+
+ Export calldata for a verifier contract.
+ contract circuit input
+
+ Generate a proof.
+ prove circuit input
+
+ Verify a proof.
+ verify circuit input
+
+ Generate a witness.
+ witness circuit input
+
+ Initialize a Circomkit project.
+ init [name]
+`;
+
+async function cli(): Promise {
+ // read user configs
+ let config = {};
+ if (existsSync(CONFIG_PATH)) {
+ config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
+ }
+ const circomkit = new Circomkit(config);
+
+ // execute command
+ switch (process.argv[2] as unknown as keyof Circomkit | 'init') {
+ case 'compile': {
+ circomkit.log('\n=== Compiling the circuit ===', 'title');
+ const path = await circomkit.compile(process.argv[3]);
+ circomkit.log('Built at: ' + path);
+ break;
+ }
+
+ case 'instantiate': {
+ circomkit.log('\n=== Creating main component ===', 'title');
+ const path = circomkit.instantiate(process.argv[3]);
+ circomkit.log('Created at: ' + path);
+ break;
+ }
+
+ case 'clean': {
+ circomkit.log('\n=== Cleaning artifacts ===', 'title');
+ await circomkit.clean(process.argv[3]);
+ circomkit.log('Cleaned.');
+ break;
+ }
+
+ case 'info': {
+ circomkit.log('\n=== Circuit information ===', 'title');
+ const info = await circomkit.info(process.argv[3]);
+ circomkit.log(`Number of of Wires: ${info.variables}`);
+ circomkit.log(`Number of Constraints: ${info.constraints}`);
+ circomkit.log(`Number of Private Inputs: ${info.privateInputs}`);
+ circomkit.log(`Number of Public Inputs: ${info.publicInputs}`);
+ circomkit.log(`Number of Labels: ${info.labels}`);
+ circomkit.log(`Number of Outputs: ${info.outputs}`);
+ break;
+ }
+
+ case 'contract': {
+ circomkit.log('\n=== Exporting contract ===', 'title');
+ const path = await circomkit.contract(process.argv[3]);
+ circomkit.log('Created at: ' + path);
+ break;
+ }
+
+ case 'calldata': {
+ circomkit.log('\n=== Exporting contract ===', 'title');
+ const calldata = await circomkit.calldata(process.argv[3], process.argv[4] || DEFAULT_INPUT);
+ circomkit.log('Calldata printed:', 'success');
+ circomkit.log(calldata);
+ break;
+ }
+
+ case 'prove': {
+ circomkit.log('\n=== Generating proof ===', 'title');
+ const path = await circomkit.prove(process.argv[3], process.argv[4] || DEFAULT_INPUT);
+ circomkit.log('Generated at: ' + path);
+ break;
+ }
+
+ case 'ptau': {
+ circomkit.log('\n=== Retrieving PTAU ===', 'title');
+ const path = await circomkit.ptau(process.argv[3]);
+ circomkit.log('PTAU ready at: ' + path);
+ break;
+ }
+
+ case 'verify': {
+ circomkit.log('\n=== Verifying proof ===', 'title');
+ const result = await circomkit.verify(process.argv[3], process.argv[4] || DEFAULT_INPUT);
+ if (result) {
+ circomkit.log('Verification successful.', 'success');
+ } else {
+ circomkit.log('Verification failed!', 'error');
+ }
+ break;
+ }
+
+ case 'witness': {
+ circomkit.log('\n=== Calculating witness ===', 'title');
+ const path = await circomkit.witness(process.argv[3], process.argv[4] || DEFAULT_INPUT);
+ circomkit.log('Witness created: ' + path);
+ break;
+ }
+
+ case 'setup': {
+ circomkit.log('\n=== Circuit-specific setup ===', 'title');
+ const path = await circomkit.setup(process.argv[3]);
+ circomkit.log('Prover key created: ' + path);
+ break;
+ }
+
+ case 'init': {
+ circomkit.log('\n=== Initializing project ===', 'title');
+ const baseDir = process.argv[3] || '.';
+ await Promise.all(
+ Object.values(initFiles).map(item => {
+ const path = `${baseDir}/${item.dir}/${item.name}`;
+ if (!existsSync(path)) {
+ mkdirSync(`${baseDir}/${item.dir}`, {recursive: true});
+ writeFileSync(path, item.content);
+ circomkit.log('Created: ' + path);
+ } else {
+ circomkit.log(path + ' exists, skipping.', 'error');
+ }
+ })
+ );
+
+ circomkit.log('Circomkit project created!', 'success');
+ break;
+ }
+
+ default:
+ console.log(USAGE);
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * We have to exit forcefully, as SnarkJS CLI does too.
+ * In their code, each function returns a code, with the
+ * succesfull ones returning 0. If an error is thrown,
+ * that error is logged and process is exited with error code 1.
+ *
+ * See line 312 in snarkjs/circomkit.js
+ */
+function exit(code: number) {
+ // eslint-disable-next-line no-process-exit
+ process.exit(code);
+}
+
+cli()
+ .then(exit)
+ .catch(err => {
+ console.error(err);
+ exit(1);
+ });
diff --git a/src/circomkit.ts b/src/circomkit.ts
new file mode 100644
index 0000000..a4bdb18
--- /dev/null
+++ b/src/circomkit.ts
@@ -0,0 +1,317 @@
+const snarkjs = require('snarkjs');
+const wasm_tester = require('circom_tester').wasm;
+import {writeFileSync, readFileSync, existsSync, mkdirSync} from 'fs';
+import {readFile, rm, writeFile} from 'fs/promises';
+import {instantiate} from './utils/instantiate';
+import {downloadPtau, getPtauName} from './utils/ptau';
+import type {CircuitConfig, R1CSInfoType} from './types/circuit';
+import type {CircomkitConfig, CircuitInputPathBuilders, CircuitPathBuilders} from './types/circomkit';
+
+/** Default configurations */
+const defaultConfig: Readonly = {
+ proofSystem: 'plonk',
+ curve: 'bn128',
+ version: '2.1.0',
+ silent: false,
+ dirs: {
+ ptau: './ptau',
+ circuits: './circuits',
+ inputs: './inputs',
+ main: 'main',
+ build: './build',
+ },
+ compiler: {
+ optimization: 0,
+ verbose: false,
+ json: false,
+ include: ['./node_modules'],
+ },
+ colors: {
+ title: '\u001b[0;34m', // blue
+ log: '\u001b[2;37m', // gray
+ error: '\u001b[0;31m', // red
+ success: '\u001b[0;32m', // green
+ },
+};
+
+/**
+ * Circomkit is an opinionated wrapper around a few
+ * [Snarkjs](../../node_modules/snarkjs/main.js) functions.
+ *
+ * It abstracts away all the path and commands by providing a simple interface,
+ * built around just providing the circuit name and the input name.
+ *
+ */
+export class Circomkit {
+ readonly config: CircomkitConfig;
+
+ constructor(overrides: Partial = {}) {
+ // override default options with the user-provided ones
+ const config: CircomkitConfig = Object.assign({}, overrides, defaultConfig);
+
+ // sanitize
+ this.config = config;
+ }
+
+ /** Colorful logging based on config, used by CLI. */
+ log(message: string, type: keyof CircomkitConfig['colors'] = 'log') {
+ if (!this.config.silent) {
+ console.log(`${this.config.colors[type]}${message}\x1b[0m`);
+ }
+ }
+
+ /** Pretty-print for JSON stringify. */
+ private prettyStringify(obj: unknown) {
+ return JSON.stringify(obj, undefined, 2);
+ }
+
+ /** Parse circuit config from `circuits.json` */
+ private readCircuitConfig(circuit: string): CircuitConfig {
+ const circuits = JSON.parse(readFileSync('./circuits.json', 'utf-8'));
+ if (!(circuit in circuits)) {
+ throw new Error('No such circuit in circuits.json');
+ }
+ return circuits[circuit] as CircuitConfig;
+ }
+
+ /**
+ * Computes a path that requires a circuit name.
+ * @param circuit circuit name
+ * @param type path type
+ * @returns path
+ */
+ private path(circuit: string, type: CircuitPathBuilders): string {
+ const dir = `${this.config.dirs.build}/${circuit}`;
+ switch (type) {
+ case 'dir':
+ return dir;
+ case 'target':
+ return `${this.config.dirs.circuits}/${this.config.dirs.main}/${circuit}.circom`;
+ case 'pkey':
+ return `${dir}/prover_key.zkey`;
+ case 'vkey':
+ return `${dir}/verifier_key.json`;
+ case 'r1cs':
+ return `${dir}/${circuit}.r1cs`;
+ case 'sym':
+ return `${dir}/${circuit}.sym`;
+ case 'sol':
+ return `${dir}/Verifier_${this.config.proofSystem}.sol`;
+ case 'wasm':
+ return `${dir}/${circuit}_js/${circuit}.wasm`;
+ default:
+ throw new Error('Invalid type: ' + type);
+ }
+ }
+
+ /**
+ * Computes a path that requires a circuit and an input name.
+ * @param circuit circuit name
+ * @param input input name
+ * @param type path type
+ * @returns path
+ */
+ private path2(circuit: string, input: string, type: CircuitInputPathBuilders): string {
+ const dir = `${this.config.dirs.build}/${circuit}/${input}`;
+ switch (type) {
+ case 'dir':
+ return dir;
+ case 'pubs':
+ return `${dir}/public.json`;
+ case 'proof':
+ return `${dir}/proof.json`;
+ case 'wtns':
+ return `${dir}/witness.wtns`;
+ case 'in':
+ return `${this.config.dirs.inputs}/${circuit}/${input}.json`;
+ default:
+ throw new Error('Invalid type: ' + type);
+ }
+ }
+
+ /** Clean build files and the main component. */
+ async clean(circuit: string) {
+ await Promise.all([
+ rm(this.path(circuit, 'dir'), {recursive: true, force: true}),
+ rm(this.path(circuit, 'target'), {force: true}),
+ ]);
+ }
+
+ /** Information about circuit. */
+ async info(circuit: string): Promise {
+ const r1csinfo = await snarkjs.r1cs.info(this.path(circuit, 'r1cs'));
+ return {
+ variables: r1csinfo.nVars,
+ constraints: r1csinfo.nConstraints,
+ privateInputs: r1csinfo.nPrvInputs,
+ publicInputs: r1csinfo.nPubInputs,
+ labels: r1csinfo.nLabels,
+ outputs: r1csinfo.nOutputs,
+ };
+ }
+
+ /** Downloads the ptau file for a circuit based on it's number of constraints. */
+ async ptau(circuit: string) {
+ const {constraints} = await this.info(circuit);
+ const ptauName = getPtauName(constraints);
+
+ // return if ptau exists already
+ const ptauPath = `${this.config.dirs.ptau}/${ptauName}`;
+ if (existsSync(ptauPath)) {
+ return ptauPath;
+ } else {
+ this.log('Downloading ' + ptauName + '...');
+ return await downloadPtau(ptauName, this.config.dirs.ptau);
+ }
+ }
+
+ /** Compile the circuit.
+ * This function uses [wasm tester](../../node_modules/circom_tester/wasm/tester.js)
+ * in the background.
+ *
+ * @returns path to build files
+ */
+ async compile(circuit: string) {
+ const outDir = this.path(circuit, 'dir');
+ const targetPath = this.path(circuit, 'target');
+
+ if (!existsSync(targetPath)) {
+ this.log('Main component does not exist, creating it now.');
+ const path = this.instantiate(circuit);
+ this.log('Main component created at: ' + path);
+ }
+
+ await wasm_tester(targetPath, {
+ output: outDir,
+ prime: this.config.curve,
+ verbose: this.config.compiler.verbose,
+ O: this.config.compiler.optimization,
+ json: this.config.compiler.json,
+ include: this.config.compiler.include,
+ wasm: true,
+ sym: true,
+ recompile: true,
+ });
+
+ return outDir;
+ }
+
+ /** Exports a solidity contract for the verifier.
+ * @returns path to exported Solidity contract
+ */
+ async contract(circuit: string) {
+ const pkey = this.path(circuit, 'pkey');
+ const template = readFileSync(
+ `./node_modules/snarkjs/templates/verifier_${this.config.proofSystem}.sol.ejs`,
+ 'utf-8'
+ );
+ const contractCode = await snarkjs.zKey.exportSolidityVerifier(pkey, {
+ [this.config.proofSystem]: template,
+ });
+
+ const contractPath = this.path(circuit, 'sol');
+ writeFileSync(contractPath, contractCode);
+ return contractPath;
+ }
+
+ /** Export calldata to console.
+ * @returns calldata as a string
+ */
+ async calldata(circuit: string, input: string) {
+ const [pubs, proof] = (
+ await Promise.all(
+ [this.path2(circuit, input, 'pubs'), this.path2(circuit, input, 'proof')].map(path => readFile(path, 'utf-8'))
+ )
+ ).map(content => JSON.parse(content));
+ return await snarkjs[this.config.proofSystem].exportSolidityCallData(proof, pubs);
+ }
+
+ /** Instantiate the `main` component.
+ * @returns path to created main component
+ */
+ instantiate(circuit: string) {
+ const circuitConfig = this.readCircuitConfig(circuit);
+ const target = instantiate(circuit, {
+ ...circuitConfig,
+ dir: this.config.dirs.main,
+ version: this.config.version,
+ });
+ return target;
+ }
+
+ /** Generate a proof.
+ * @returns path to directory where public signals and proof are created
+ */
+ async prove(circuit: string, input: string) {
+ const jsonInput = JSON.parse(readFileSync(this.path2(circuit, input, 'in'), 'utf-8'));
+ const fullProof = await snarkjs[this.config.proofSystem].fullProve(
+ jsonInput,
+ this.path(circuit, 'wasm'),
+ this.path(circuit, 'pkey')
+ );
+
+ const dir = this.path2(circuit, input, 'dir');
+ mkdirSync(dir, {recursive: true});
+ await Promise.all([
+ writeFile(this.path2(circuit, input, 'pubs'), this.prettyStringify(fullProof.publicSignals)),
+ writeFile(this.path2(circuit, input, 'proof'), this.prettyStringify(fullProof.proof)),
+ ]);
+ return dir;
+ }
+
+ /** Commence a circuit-specific setup.
+ * @returns path to prover key
+ */
+ async setup(circuit: string) {
+ const r1csPath = this.path(circuit, 'r1cs');
+ const pkeyPath = this.path(circuit, 'pkey');
+ const vkeyPath = this.path(circuit, 'vkey');
+
+ // create R1CS if needed
+ if (!existsSync(r1csPath)) {
+ this.log('R1CS does not exist, creating it now...');
+ await this.compile(circuit);
+ }
+
+ // get ptau path
+ this.log('Checking for PTAU file...');
+ const ptau = await this.ptau(circuit);
+
+ // circuit specific setup
+ if (this.config.proofSystem === 'plonk') {
+ await snarkjs.plonk.setup(this.path(circuit, 'r1cs'), ptau, pkeyPath);
+ } else {
+ throw new Error('Not implemented.');
+ }
+
+ // export verification key
+ const vkey = await snarkjs.zKey.exportVerificationKey(pkeyPath);
+ writeFileSync(vkeyPath, this.prettyStringify(vkey));
+ return vkeyPath;
+ }
+
+ /** Verify a proof for some public signals. */
+ async verify(circuit: string, input: string) {
+ const [vkey, pubs, proof] = (
+ await Promise.all(
+ [this.path(circuit, 'vkey'), this.path2(circuit, input, 'pubs'), this.path2(circuit, input, 'proof')].map(
+ path => readFile(path, 'utf-8')
+ )
+ )
+ ).map(content => JSON.parse(content));
+
+ return await snarkjs[this.config.proofSystem].verify(vkey, pubs, proof);
+ }
+
+ /** Calculates the witness for the given circuit and input. */
+ async witness(circuit: string, input: string) {
+ const wasmPath = this.path(circuit, 'wasm');
+ const wtnsPath = this.path2(circuit, input, 'wtns');
+ const outDir = this.path2(circuit, input, 'dir');
+ const jsonInput = JSON.parse(readFileSync(this.path2(circuit, input, 'in'), 'utf-8'));
+
+ mkdirSync(outDir, {recursive: true});
+ await snarkjs.wtns.calculate(jsonInput, wasmPath, wtnsPath);
+ return wtnsPath;
+ }
+}
diff --git a/src/index.ts b/src/index.ts
index 052389c..d0e2b32 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,5 +1,7 @@
-import ProofTester from './proofTester';
-import WasmTester from './wasmTester';
-import instantiate from './instantiate';
+import ProofTester from './testers/proofTester';
+import WasmTester from './testers/wasmTester';
+import {instantiate} from './utils/instantiate';
+import {Circomkit} from './circomkit';
+export * from './types/circuit';
-export {ProofTester, WasmTester, instantiate};
+export {ProofTester, WasmTester, instantiate, Circomkit};
diff --git a/src/instantiate.ts b/src/instantiate.ts
deleted file mode 100644
index ee00b9d..0000000
--- a/src/instantiate.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import type {CircuitConfig} from './types/circuit';
-import ejs from 'ejs';
-import {writeFileSync, existsSync, mkdirSync} from 'fs';
-
-const EJS_TEMPLATE = `
-// auto-generated by circomkit
-pragma circom <%= typeof version == "undefined" ? "2.0.0" : version %>;
-
-include "../<%= file %>.circom";
-
-component main<%=
- (typeof pubs == "undefined" || pubs.length == 0) ?
- "" :
- " {public[" + pubs.join(", ") + "]}"
- %> = <%= template %>(<%= typeof params == "undefined" ? "" : params.join(", ") %>);
-`;
-/**
- * Programmatically generate the `main` component of a circuit
- * @param name name of the circuit to be generated
- * @param circuitConfig circuit configuration
- */
-export default function instantiate(name: string, circuitConfig: CircuitConfig) {
- // directory to output the file
- const directory = circuitConfig.dir || 'test';
-
- // add "../" to the filename in include, one for each "/" in directory name
- // if none, the prefix becomes empty string
- const filePrefixMatches = directory.match(/\//g);
- if (filePrefixMatches !== null) {
- circuitConfig.file = '../'.repeat(filePrefixMatches.length) + circuitConfig.file;
- }
-
- // create circuit code
- const circuit = ejs.render(EJS_TEMPLATE, circuitConfig);
-
- // output to file
- const targetDir = `./circuits/${directory}`;
- if (!existsSync(targetDir)) {
- mkdirSync(targetDir, {
- recursive: true,
- });
- }
- const targetPath = `${targetDir}/${name}.circom`;
- writeFileSync(targetPath, circuit);
-}
diff --git a/src/proofTester.ts b/src/testers/proofTester.ts
similarity index 79%
rename from src/proofTester.ts
rename to src/testers/proofTester.ts
index 5aa8d67..0290609 100644
--- a/src/proofTester.ts
+++ b/src/testers/proofTester.ts
@@ -1,23 +1,22 @@
-import fs from 'fs';
+import {readFileSync, existsSync} from 'fs';
const snarkjs = require('snarkjs');
import {expect} from 'chai';
-import type {CircuitSignals, FullProof, ProofSystem} from './types/circuit';
+import type {CircuitSignals, FullProof} from '../types/circuit';
-/**
- * Allowed proof systems as defined in SnarkJS.
- */
-const PROOF_SYSTEMS = ['groth16', 'plonk', 'fflonk'] as const;
+/** Allowed proof systems as defined in SnarkJS. */
+const PROOF_SYSTEMS = ['groth16', 'plonk'] as const;
/**
* A more extensive Circuit class, able to generate proofs & verify them.
* Assumes that prover key and verifier key have been computed.
*/
export default class ProofTester {
- public readonly protocol: ProofSystem;
+ public readonly protocol: 'groth16' | 'plonk';
private readonly wasmPath: string;
private readonly proverKeyPath: string;
private readonly verificationKeyPath: string;
- private readonly verificationKey: unknown & {protocol: ProofSystem};
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ private readonly verificationKey: any;
/**
* Sets the paths & loads the verification key. The underlying proof system is checked by looking
@@ -31,15 +30,13 @@ export default class ProofTester {
this.verificationKeyPath = `./build/${circuit}/verification_key.json`;
// ensure that paths exist
- const missing = [this.wasmPath, this.proverKeyPath, this.verificationKeyPath].filter(p => !fs.existsSync(p));
+ const missing = [this.wasmPath, this.proverKeyPath, this.verificationKeyPath].filter(p => !existsSync(p));
if (missing.length !== 0) {
throw new Error('Missing files for' + circuit + ':\n' + missing.join('\n'));
}
// load verification key
- this.verificationKey = JSON.parse(
- fs.readFileSync(this.verificationKeyPath).toString()
- ) as typeof this.verificationKey;
+ this.verificationKey = JSON.parse(readFileSync(this.verificationKeyPath).toString()) as typeof this.verificationKey;
// check proof system
const protocol = this.verificationKey.protocol;
@@ -74,7 +71,7 @@ export default class ProofTester {
* @param proof proof object, given from `prove`
* @param publicSignals public signals for the circuit
*/
- async expectVerificationPass(proof: object, publicSignals: string[]): Promise {
+ async expectPass(proof: object, publicSignals: string[]): Promise {
expect(await this.verify(proof, publicSignals)).to.be.true;
}
@@ -83,7 +80,7 @@ export default class ProofTester {
* @param proof proof object, given from `prove`
* @param publicSignals public signals for the circuit
*/
- async expectVerificationFail(proof: object, publicSignals: string[]): Promise {
+ async expectFail(proof: object, publicSignals: string[]): Promise {
expect(await this.verify(proof, publicSignals)).to.be.false;
}
}
diff --git a/src/wasmTester.ts b/src/testers/wasmTester.ts
similarity index 92%
rename from src/wasmTester.ts
rename to src/testers/wasmTester.ts
index 64716f3..20d935f 100644
--- a/src/wasmTester.ts
+++ b/src/testers/wasmTester.ts
@@ -1,27 +1,34 @@
const wasm_tester = require('circom_tester').wasm;
-import type {WitnessType, CircuitSignals, SymbolsType, SignalValueType, CircuitConfig} from './types/circuit';
-import type {CircomWasmTester} from './types/wasmTester';
+import type {WitnessType, CircuitSignals, SymbolsType, SignalValueType, CircuitConfig} from '../types/circuit';
+import type {CircomWasmTester} from '../types/wasmTester';
import {assert, expect} from 'chai';
-import instantiate from './instantiate';
+import {instantiate} from '../utils/instantiate';
-/**
- A utility class to test your circuits. Use `expectFailedAssert` and `expectCorrectAssert` to test out evaluations
- */
+/** A utility class to test your circuits. Use `expectFail` and `expectPass` to test out evaluations. */
export default class WasmTester {
- /**
- * The underlying `circom_tester` object
- */
+ /** The underlying `circom_tester` object */
readonly circomWasmTester: CircomWasmTester;
-
- /**
- * A dictionary of symbols
- */
+ /** A dictionary of symbols, see {@link loadSymbols} */
symbols: SymbolsType | undefined;
-
- /**
- * List of constraints, must call `loadConstraints` before accessing this key
- */
+ /** List of constraints, see {@link loadConstraints} */
constraints: unknown[] | undefined;
+ /**
+ * Compiles and reutrns a circuit tester class instance.
+ * @param circuit name of circuit
+ * @param dir directory to read the circuit from, defaults to `test`
+ * @returns a `WasmTester` instance
+ */
+ static async new(
+ circuitName: string,
+ circuitConfig: CircuitConfig
+ ): Promise> {
+ const dir = circuitConfig.dir || 'test';
+ instantiate(circuitName, circuitConfig);
+ const circomWasmTester: CircomWasmTester = await wasm_tester(`./circuits/${dir}/${circuitName}.circom`, {
+ include: 'node_modules', // will link circomlib circuits
+ });
+ return new WasmTester(circomWasmTester);
+ }
constructor(circomWasmTester: CircomWasmTester) {
this.circomWasmTester = circomWasmTester;
@@ -124,7 +131,7 @@ export default class WasmTester) {
+ async expectFail(input: CircuitSignals) {
await this.calculateWitness(input).then(
() => assert.fail(),
err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.')
@@ -136,7 +143,7 @@ export default class WasmTester, output?: CircuitSignals) {
+ async expectPass(input: CircuitSignals, output?: CircuitSignals) {
const witness = await this.calculateWitness(input);
await this.checkConstraints(witness);
if (output) {
@@ -193,10 +200,8 @@ export default class WasmTester(
- circuitName: string,
- circuitConfig: CircuitConfig
- ): Promise> {
- const dir = circuitConfig.dir || 'test';
- instantiate(circuitName, circuitConfig);
- const circomWasmTester: CircomWasmTester = await wasm_tester(`./circuits/${dir}/${circuitName}.circom`, {
- include: 'node_modules', // will link circomlib circuits
- });
- return new WasmTester(circomWasmTester);
- }
}
diff --git a/src/types/circomkit.ts b/src/types/circomkit.ts
new file mode 100644
index 0000000..05a7b32
--- /dev/null
+++ b/src/types/circomkit.ts
@@ -0,0 +1,54 @@
+type ColorType = `\x1b[${number}m` | `\x1b[${number};${number}m`;
+type VersionType = `${number}.${number}.${number}`;
+
+export type CircomkitConfig = {
+ /** Proof system to be used. */
+ proofSystem: 'groth16' | 'plonk';
+ /** Curve to be used, which defines the underlying prime field. */
+ curve: 'bn128' | 'bls12381' | 'goldilocks';
+ /** Directory overrides, it is best you leave this as is. */
+ dirs: {
+ /** Directory to read circuits from. */
+ circuits: string;
+ /** Folder name to output the main component under `circuits` directory. */
+ main: string;
+ /** Directory to read inputs from. */
+ inputs: string;
+ /** Directory to download PTAU files. */
+ ptau: string;
+ /** Directory to output circuit build files. */
+ build: string;
+ };
+ /** Version number for main components. */
+ version: VersionType;
+ /** Hide Circomkit logs */
+ silent: boolean;
+ /** Compiler options for Circom. */
+ compiler: {
+ /** Output constraints in JSON format. */
+ json: boolean;
+ /** Show Circom logs during compilation. */
+ verbose: boolean;
+ /**
+ * Optimization level.
+ * - `0`: No simplification is applied.
+ * - `1`: Only applies `var` to `var` and `var` to `constant` simplification.
+ */
+ optimization: 0 | 1;
+ /** Include paths as libraries during compilation. */
+ include: string[];
+ };
+ /** Colors for the logs */
+ colors: {
+ title: ColorType;
+ success: ColorType;
+ log: ColorType;
+ error: ColorType;
+ };
+};
+
+/** Shorthand notations for which path to build in Circomkit. These paths require a circuit name. */
+export type CircuitPathBuilders = 'target' | 'sym' | 'pkey' | 'vkey' | 'wasm' | 'sol' | 'dir' | 'r1cs';
+
+/** Shorthand notations for which path to build in Circomkit. These paths require a circuit name and input name. */
+export type CircuitInputPathBuilders = 'pubs' | 'proof' | 'wtns' | 'in' | 'dir';
diff --git a/src/types/circuit.ts b/src/types/circuit.ts
index 46afff0..5d85e78 100644
--- a/src/types/circuit.ts
+++ b/src/types/circuit.ts
@@ -1,11 +1,7 @@
-/**
- * An integer value is a numerical string, a number, or a bigint.
- */
+/** An integer value is a numerical string, a number, or a bigint. */
type IntegerValueType = `${number}` | number | bigint;
-/**
- * A signal value is a number, or an array of numbers (recursively).
- */
+/** A signal value is a number, or an array of numbers (recursively). */
export type SignalValueType = IntegerValueType | SignalValueType[];
/**
@@ -19,10 +15,7 @@ export type CircuitSignals = T extends []
? {[signal: string]: SignalValueType}
: {[signal in T[number]]: SignalValueType};
-/**
- * A witness is an array of bigints, corresponding to the values of each wire in
- * the evaluation of the circuit.
- */
+/** A witness is an array of `bigint`s, corresponding to the values of each wire in the evaluation of the circuit. */
export type WitnessType = bigint[];
/**
@@ -37,31 +30,36 @@ export type SymbolsType = {
};
};
-/**
- * A FullProof, as returned from SnarkJS `fullProve` function.
- */
+/** A FullProof, as returned from SnarkJS `fullProve` function. */
export type FullProof = {
proof: object;
publicSignals: string[];
};
-/**
- * Proof system to be used by SnarkJS.
- */
-export type ProofSystem = 'groth16' | 'plonk' | 'fflonk';
-
-/**
- * A configuration object for circuit main components.
- */
+/** A configuration object for circuit main components. */
export type CircuitConfig = {
/** File to read the template from */
file: string;
/** The template name to instantiate */
template: string;
- /** Directory to read the file, defaults to `test` */
+ /** Directory to instantiate at */
dir?: string;
+ /** Target version */
+ version?: `${number}.${number}.${number}`;
/** An array of public input signal names, defaults to `[]` */
pubs?: string[];
/** An array of template parameters, defaults to `[]` */
params?: (number | bigint)[];
};
+
+/** R1CS information type, as returned by SnarkJS.
+ * Some fields may be omitted.
+ */
+export type R1CSInfoType = {
+ variables: number;
+ constraints: number;
+ privateInputs: number;
+ publicInputs: number;
+ labels: number;
+ outputs: number;
+};
diff --git a/src/utils/initFiles.ts b/src/utils/initFiles.ts
new file mode 100644
index 0000000..6b7dc7b
--- /dev/null
+++ b/src/utils/initFiles.ts
@@ -0,0 +1,96 @@
+/**
+ * Initial files for Cirocmkit development environment.
+ * This is most likely to be used by the CLI via `npx circomkit init`.
+ *
+ * It has the following:
+ *
+ * - An example circuit.
+ * - A circuit config for that circuit.
+ * - An input for that circuit.
+ * - A test code for that circuit.
+ * - A `.mocharc.json` for the tests.
+ */
+export const initFiles = {
+ circuit: {
+ dir: 'circuits',
+ name: 'multiplier.circom',
+ content: `pragma circom 2.0.0;
+
+template Multiplier(N) {
+ assert(N > 1);
+ signal input in[N];
+ signal output out;
+
+ signal inner[N-1];
+
+ inner[0] <== in[0] * in[1];
+ for(var i = 2; i < N; i++) {
+ inner[i-1] <== inner[i-2] * in[i];
+ }
+
+ out <== inner[N-2];
+}`,
+ },
+ input: {
+ dir: 'inputs/multiplier_3',
+ name: '80.json',
+ content: `{
+ "in": [2, 4, 10]
+}
+`,
+ },
+ circuits: {
+ dir: '.',
+ name: 'circuits.json',
+ content: `{
+ "multiplier_3": {
+ "file": "multiplier",
+ "template": "Multiplier",
+ "params": [3]
+ }
+}
+`,
+ },
+ tests: {
+ dir: 'tests',
+ name: 'multiplier.test.ts',
+ content: `import { WasmTester } from "circomkit";
+
+// exercise: make this test work for all numbers, not just 3
+describe("multiplier", () => {
+ let circuit: WasmTester<["in"], ["out"]>;
+
+ before(async () => {
+ circuit = await WasmTester.new('multiplier_3', {
+ file: "multiplier",
+ template: "Multiplier",
+ params: [3],
+ });
+ await circuit.checkConstraintCount(2);
+ });
+
+ it("should multiply correctly", async () => {
+ await circuit.expectPass({ in: [2, 4, 10] }, { out: 80 });
+ });
+});
+`,
+ },
+ mochaConfig: {
+ dir: '.',
+ name: '.mocharc.json',
+ content: `{
+ "extension": ["ts"],
+ "require": "ts-node/register",
+ "spec": "tests/**/*.ts",
+ "timeout": 100000,
+ "exit": true
+}
+`,
+ },
+} satisfies {
+ [key: string]: {
+ dir: string;
+ name: string;
+ content: string;
+ };
+};
diff --git a/src/utils/instantiate.ts b/src/utils/instantiate.ts
new file mode 100644
index 0000000..6c6b485
--- /dev/null
+++ b/src/utils/instantiate.ts
@@ -0,0 +1,52 @@
+import type {CircuitConfig} from '../types/circuit';
+import {writeFileSync, existsSync, mkdirSync} from 'fs';
+
+/** Circuit builder, kinda like `ejs.render`. **Be very careful when editing this file.** */
+const makeCircuit = (config: Required) => `// auto-generated by circomkit
+pragma circom ${config.version};
+
+include "../${config.file}.circom";
+
+component main${config.pubs.length === 0 ? '' : ' {public[' + config.pubs.join(', ') + ']}'} = ${
+ config.template
+}(${config.params.join(', ')});
+`;
+
+/**
+ * Programmatically generate the `main` component of a circuit
+ * @param name name of the circuit to be generated
+ * @param circuitConfig circuit configuration
+ * @return path to the created file
+ */
+export function instantiate(name: string, circuitConfig: CircuitConfig): string {
+ // directory to output the file
+ const directory = circuitConfig.dir || 'test';
+
+ // add "../" to the filename in include, one for each "/" in directory name
+ // if none, the prefix becomes empty string
+ const filePrefixMatches = directory.match(/\//g);
+ let file = circuitConfig.file;
+ if (filePrefixMatches !== null) {
+ file = '../'.repeat(filePrefixMatches.length) + file;
+ }
+
+ const circuitCode = makeCircuit({
+ file: file,
+ template: circuitConfig.template,
+ version: circuitConfig.version || '2.0.0',
+ dir: directory,
+ pubs: circuitConfig.pubs || [],
+ params: circuitConfig.params || [],
+ });
+
+ const targetDir = `./circuits/${directory}`;
+ if (!existsSync(targetDir)) {
+ mkdirSync(targetDir, {
+ recursive: true,
+ });
+ }
+ const targetPath = `${targetDir}/${name}.circom`;
+ writeFileSync(targetPath, circuitCode);
+
+ return targetPath;
+}
diff --git a/src/utils/ptau.ts b/src/utils/ptau.ts
new file mode 100644
index 0000000..9839bd2
--- /dev/null
+++ b/src/utils/ptau.ts
@@ -0,0 +1,50 @@
+import {createWriteStream} from 'fs';
+import {get} from 'https';
+
+/** Base PTAU URL as seen in [SnarkJS docs](https://github.com/iden3/snarkjs#7-prepare-phase-2). */
+const PTAU_URL_BASE = 'https://hermez.s3-eu-west-1.amazonaws.com';
+
+/**
+ * Returns the name of PTAU file for a given number of constraints.
+ * @see {@link https://github.com/iden3/snarkjs#7-prepare-phase-2}
+ * @param n number of constraints
+ * @returns name of the PTAU file
+ */
+export function getPtauName(n: number): string {
+ // smallest p such that 2^p >= n
+ const p = Math.ceil(Math.log2(n));
+
+ let id = ''; // default for large values
+ if (p < 8) {
+ id = '_08';
+ } else if (p < 10) {
+ id = `_0${p}`;
+ } else if (p < 28) {
+ id = `_${p}`;
+ } else if (p === 28) {
+ id = '';
+ } else {
+ throw new Error('No PTAU for that many constraints!');
+ }
+ return `powersOfTau28_hez_final${id}.ptau`;
+}
+
+/**
+ * Downloads phase-1 powers of tau from Polygon Hermez.
+ * @param ptauName name of PTAU file
+ * @param ptauDir directory to download to
+ * @returns path to downloaded PTAU file
+ */
+export function downloadPtau(ptauName: string, ptauDir: string): Promise {
+ const ptauPath = `${ptauDir}/${ptauName}`;
+ const file = createWriteStream(ptauPath);
+ return new Promise(resolve => {
+ get(`${PTAU_URL_BASE}/${ptauName}`, response => {
+ response.pipe(file);
+ file.on('finish', () => {
+ file.close();
+ resolve(ptauPath);
+ });
+ });
+ });
+}
diff --git a/tests/fibonacci.test.ts b/tests/fibonacci.test.ts
index 8540176..0f1852c 100644
--- a/tests/fibonacci.test.ts
+++ b/tests/fibonacci.test.ts
@@ -1,8 +1,7 @@
-import WasmTester from '../src/wasmTester';
+import {WasmTester} from '../src';
describe('fibonacci', () => {
let circuit: WasmTester<['in'], ['out']>;
-
const N = 19;
before(async () => {
@@ -15,14 +14,7 @@ describe('fibonacci', () => {
});
it('should compute correctly', async () => {
- await circuit.expectCorrectAssert(
- {
- in: [1, 1],
- },
- {
- out: fibonacci([1, 1], N),
- }
- );
+ await circuit.expectPass({in: [1, 1]}, {out: fibonacci([1, 1], N)});
});
});
@@ -42,14 +34,7 @@ describe.skip('fibonacci recursive', () => {
});
it('should compute correctly', async () => {
- await circuit.expectCorrectAssert(
- {
- in: [1, 1],
- },
- {
- out: fibonacci([1, 1], N),
- }
- );
+ await circuit.expectPass({in: [1, 1]}, {out: fibonacci([1, 1], N)});
});
});
diff --git a/tests/float_add.test.ts b/tests/float_add.test.ts
index 64f8e2e..7779a24 100644
--- a/tests/float_add.test.ts
+++ b/tests/float_add.test.ts
@@ -1,6 +1,4 @@
-import WasmTester from '../src/wasmTester';
-
-// tests adapted from https://github.com/rdi-berkeley/zkp-mooc-lab
+import {WasmTester} from '../src';
const expectedConstraints = {
fp32: 401,
@@ -12,6 +10,7 @@ const expectedConstraints = {
msnzb: (bits: number) => 3 * bits + 1,
};
+// tests adapted from https://github.com/rdi-berkeley/zkp-mooc-lab
describe('float_add 32-bit', () => {
let circuit: WasmTester<['e', 'm'], ['e_out', 'm_out']>;
@@ -28,7 +27,7 @@ describe('float_add 32-bit', () => {
});
it('case I test', async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
e: ['43', '5'],
m: ['11672136', '10566265'],
@@ -38,7 +37,7 @@ describe('float_add 32-bit', () => {
});
it('case II test 1', async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
e: ['104', '106'],
m: ['12444445', '14159003'],
@@ -48,7 +47,7 @@ describe('float_add 32-bit', () => {
});
it('case II test 2', async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
e: ['176', '152'],
m: ['16777215', '16777215'],
@@ -58,7 +57,7 @@ describe('float_add 32-bit', () => {
});
it('case II test 3', async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
e: ['142', '142'],
m: ['13291872', '13291872'],
@@ -68,7 +67,7 @@ describe('float_add 32-bit', () => {
});
it('one input zero test', async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
e: ['0', '43'],
m: ['0', '10566265'],
@@ -78,7 +77,7 @@ describe('float_add 32-bit', () => {
});
it('both inputs zero test', async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
e: ['0', '0'],
m: ['0', '0'],
@@ -88,28 +87,28 @@ describe('float_add 32-bit', () => {
});
it('should fail - exponent zero but mantissa non-zero', async () => {
- await circuit.expectFailedAssert({
+ await circuit.expectFail({
e: ['0', '0'],
m: ['0', '10566265'],
});
});
it('should fail - mantissa ≥ 2^(p+1)', async () => {
- await circuit.expectFailedAssert({
+ await circuit.expectFail({
e: ['0', '43'],
m: ['0', '16777216'],
});
});
it('should fail - mantissa < 2^p', async () => {
- await circuit.expectFailedAssert({
+ await circuit.expectFail({
e: ['0', '43'],
m: ['0', '6777216'],
});
});
it('should fail - exponent ≥ 2^k', async () => {
- await circuit.expectFailedAssert({
+ await circuit.expectFail({
e: ['0', '256'],
m: ['0', '10566265'],
});
@@ -131,7 +130,7 @@ describe('float_add 64-bit', () => {
});
it('case I test', async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
e: ['1122', '1024'],
m: ['7807742059002284', '7045130465601185'],
@@ -141,7 +140,7 @@ describe('float_add 64-bit', () => {
});
it('case II test 1', async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
e: ['1056', '1053'],
m: ['8879495032259305', '5030141535601637'],
@@ -151,7 +150,7 @@ describe('float_add 64-bit', () => {
});
it('case II test 2', async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
e: ['1035', '982'],
m: ['4804509148660890', '8505192799372177'],
@@ -161,7 +160,7 @@ describe('float_add 64-bit', () => {
});
it('case II test 3', async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
e: ['982', '982'],
m: ['8505192799372177', '8505192799372177'],
@@ -171,7 +170,7 @@ describe('float_add 64-bit', () => {
});
it('one input zero test', async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
e: ['0', '982'],
m: ['0', '8505192799372177'],
@@ -181,7 +180,7 @@ describe('float_add 64-bit', () => {
});
it('both inputs zero test', async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
e: ['0', '0'],
m: ['0', '0'],
@@ -191,14 +190,14 @@ describe('float_add 64-bit', () => {
});
it('should fail - exponent zero but mantissa non-zero', async () => {
- await circuit.expectFailedAssert({
+ await circuit.expectFail({
e: ['0', '0'],
m: ['0', '8505192799372177'],
});
});
it('should fail - mantissa < 2^p', async () => {
- await circuit.expectFailedAssert({
+ await circuit.expectFail({
e: ['0', '43'],
m: ['0', '16777216'],
});
@@ -221,21 +220,11 @@ describe('float_add utilities', () => {
});
it('should give 1 for in ≤ b', async () => {
- await circuit.expectCorrectAssert(
- {
- in: '4903265',
- },
- {out: '1'}
- );
+ await circuit.expectPass({in: '4903265'}, {out: '1'});
});
it('should give 0 for in > b', async () => {
- await circuit.expectCorrectAssert(
- {
- in: '13291873',
- },
- {out: '0'}
- );
+ await circuit.expectPass({in: '13291873'}, {out: '0'});
});
});
@@ -254,7 +243,7 @@ describe('float_add utilities', () => {
});
it("should pass test 1 - don't skip checks", async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
x: '65',
shift: '24',
@@ -265,7 +254,7 @@ describe('float_add utilities', () => {
});
it("should pass test 2 - don't skip checks", async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
x: '65',
shift: '0',
@@ -276,7 +265,7 @@ describe('float_add utilities', () => {
});
it("should fail - don't skip checks", async () => {
- await circuit.expectFailedAssert({
+ await circuit.expectFail({
x: '65',
shift: '25',
skip_checks: '0',
@@ -284,7 +273,7 @@ describe('float_add utilities', () => {
});
it('should pass when skip_checks = 1 and shift is ≥ shift_bound', async () => {
- await circuit.expectCorrectAssert({
+ await circuit.expectPass({
x: '65',
shift: '25',
skip_checks: '1',
@@ -308,18 +297,11 @@ describe('float_add utilities', () => {
});
it('should pass - small bitwidth', async () => {
- await circuit.expectCorrectAssert(
- {
- x: '82263136010365',
- },
- {y: '4903265'}
- );
+ await circuit.expectPass({x: '82263136010365'}, {y: '4903265'});
});
it('should fail - large bitwidth', async () => {
- await circuit.expectFailedAssert({
- x: '15087340228765024367',
- });
+ await circuit.expectFail({x: '15087340228765024367'});
});
});
@@ -340,7 +322,7 @@ describe('float_add utilities', () => {
});
it("should pass - don't skip checks", async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
e: '100',
m: '20565784002591',
@@ -351,7 +333,7 @@ describe('float_add utilities', () => {
});
it("should pass - already normalized and don't skip checks", async () => {
- await circuit.expectCorrectAssert(
+ await circuit.expectPass(
{
e: '100',
m: '164526272020728',
@@ -362,7 +344,7 @@ describe('float_add utilities', () => {
});
it("should fail when m = 0 - don't skip checks", async () => {
- await circuit.expectFailedAssert({
+ await circuit.expectFail({
e: '100',
m: '0',
skip_checks: '0',
@@ -370,7 +352,7 @@ describe('float_add utilities', () => {
});
it('should pass when skip_checks = 1 and m is 0', async () => {
- await circuit.expectCorrectAssert({
+ await circuit.expectPass({
e: '100',
m: '0',
skip_checks: '1',
@@ -393,11 +375,8 @@ describe('float_add utilities', () => {
});
it("should pass test 1 - don't skip checks", async () => {
- await circuit.expectCorrectAssert(
- {
- in: '1',
- skip_checks: '0',
- },
+ await circuit.expectPass(
+ {in: '1', skip_checks: '0'},
{
// prettier-ignore
one_hot: ['1', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'],
@@ -406,11 +385,8 @@ describe('float_add utilities', () => {
});
it("should pass test 2 - don't skip checks", async () => {
- await circuit.expectCorrectAssert(
- {
- in: '281474976710655',
- skip_checks: '0',
- },
+ await circuit.expectPass(
+ {in: '281474976710655', skip_checks: '0'},
{
// prettier-ignore
one_hot: ['0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1'],
@@ -419,17 +395,11 @@ describe('float_add utilities', () => {
});
it("should fail when in = 0 - don't skip checks", async () => {
- await circuit.expectFailedAssert({
- in: '0',
- skip_checks: '0',
- });
+ await circuit.expectFail({in: '0', skip_checks: '0'});
});
it('should pass when skip_checks = 1 and in is 0', async () => {
- await circuit.expectCorrectAssert({
- in: '0',
- skip_checks: '1',
- });
+ await circuit.expectPass({in: '0', skip_checks: '1'});
});
});
});
diff --git a/tests/multiplier.test.ts b/tests/multiplier.test.ts
index 0816040..e373771 100644
--- a/tests/multiplier.test.ts
+++ b/tests/multiplier.test.ts
@@ -1,12 +1,10 @@
-import WasmTester from '../src/wasmTester';
-import ProofTester from '../src/proofTester';
-import type {FullProof} from '../src/types/circuit';
+import {FullProof, ProofTester, WasmTester} from '../src';
describe('multiplier', () => {
- let circuit: WasmTester<['in'], ['out']>;
-
const N = 3;
+ let circuit: WasmTester<['in'], ['out']>;
+
before(async () => {
circuit = await WasmTester.new(`multiplier_${N}`, {
file: 'multiplier',
@@ -20,14 +18,7 @@ describe('multiplier', () => {
it('should multiply correctly', async () => {
const randomNumbers = Array.from({length: N}, () => Math.floor(Math.random() * 100 * N));
- await circuit.expectCorrectAssert(
- {
- in: randomNumbers,
- },
- {
- out: randomNumbers.reduce((prev, acc) => acc * prev),
- }
- );
+ await circuit.expectPass({in: randomNumbers}, {out: randomNumbers.reduce((prev, acc) => acc * prev)});
});
});
@@ -44,12 +35,7 @@ describe('multiplier utilities', () => {
});
it('should multiply correctly', async () => {
- await circuit.expectCorrectAssert(
- {
- in: [7, 5],
- },
- {out: 7 * 5}
- );
+ await circuit.expectPass({in: [7, 5]}, {out: 7 * 5});
});
});
});
@@ -67,11 +53,11 @@ describe.skip('multiplier proofs', () => {
});
it('should verify', async () => {
- await circuit.expectVerificationPass(fullProof.proof, fullProof.publicSignals);
+ await circuit.expectPass(fullProof.proof, fullProof.publicSignals);
});
it('should NOT verify', async () => {
// just give a prime number as the output, assuming none of the inputs are 1
- await circuit.expectVerificationFail(fullProof.proof, ['13']);
+ await circuit.expectFail(fullProof.proof, ['13']);
});
});
diff --git a/tests/sha256.test.ts b/tests/sha256.test.ts
index e1df467..280eb03 100644
--- a/tests/sha256.test.ts
+++ b/tests/sha256.test.ts
@@ -1,7 +1,6 @@
import {expect} from 'chai';
-import WasmTester from '../src/wasmTester';
-import {randomBytes} from 'crypto';
-import {createHash} from 'crypto';
+import {WasmTester} from '../src';
+import {randomBytes, createHash} from 'crypto';
describe('sha256', () => {
let circuit: WasmTester<['in'], ['out']>;
diff --git a/tests/sudoku.test.ts b/tests/sudoku.test.ts
index 31aa80f..dfe306f 100644
--- a/tests/sudoku.test.ts
+++ b/tests/sudoku.test.ts
@@ -1,5 +1,4 @@
-import {CircuitSignals} from '../src/types/circuit';
-import WasmTester from '../src/wasmTester';
+import {WasmTester, CircuitSignals} from '../src';
type BoardSizes = 4 | 9;
@@ -60,37 +59,37 @@ const INPUTS: {[N in BoardSizes]: CircuitSignals<['solution', 'puzzle']>} = {
});
it('should compute correctly', async () => {
- await circuit.expectCorrectAssert(INPUT);
+ await circuit.expectPass(INPUT);
});
it('should NOT accept non-distinct rows', async () => {
const badInput = JSON.parse(JSON.stringify(INPUT));
badInput.solution[0][0] = badInput.solution[0][1];
- await circuit.expectFailedAssert(badInput);
+ await circuit.expectFail(badInput);
});
it('should NOT accept non-distinct columns', async () => {
const badInput = JSON.parse(JSON.stringify(INPUT));
badInput.solution[0][0] = badInput.solution[1][0];
- await circuit.expectFailedAssert(badInput);
+ await circuit.expectFail(badInput);
});
it('should NOT accept non-distinct square', async () => {
const badInput = JSON.parse(JSON.stringify(INPUT));
badInput.solution[0][0] = badInput.solution[1][1];
- await circuit.expectFailedAssert(badInput);
+ await circuit.expectFail(badInput);
});
it('should NOT accept empty value in solution', async () => {
const badInput = JSON.parse(JSON.stringify(INPUT));
badInput.solution[0][0] = 0;
- await circuit.expectFailedAssert(badInput);
+ await circuit.expectFail(badInput);
});
it('should NOT accept out-of-range values', async () => {
const badInput = JSON.parse(JSON.stringify(INPUT));
badInput.solution[0][0] = 99999;
- await circuit.expectFailedAssert(badInput);
+ await circuit.expectFail(badInput);
});
})
);
@@ -111,16 +110,16 @@ describe('sudoku utilities', () => {
});
it('should pass for input < 2^b', async () => {
- await circuit.expectCorrectAssert({
+ await circuit.expectPass({
in: 2 ** b - 1,
});
});
it('should fail for input ≥ 2^b ', async () => {
- await circuit.expectFailedAssert({
+ await circuit.expectFail({
in: 2 ** b,
});
- await circuit.expectFailedAssert({
+ await circuit.expectFail({
in: 2 ** b + 1,
});
});
@@ -141,7 +140,7 @@ describe('sudoku utilities', () => {
});
it('should pass if all inputs are unique', async () => {
- await circuit.expectCorrectAssert({
+ await circuit.expectPass({
in: Array(n)
.fill(0)
.map((v, i) => v + i),
@@ -154,7 +153,7 @@ describe('sudoku utilities', () => {
.map((v, i) => v + i);
// make a duplicate
arr[0] = arr[arr.length - 1];
- await circuit.expectFailedAssert({
+ await circuit.expectFail({
in: arr,
});
});
@@ -175,26 +174,26 @@ describe('sudoku utilities', () => {
});
it('should pass for in range', async () => {
- await circuit.expectCorrectAssert({
+ await circuit.expectPass({
in: MAX,
});
- await circuit.expectCorrectAssert({
+ await circuit.expectPass({
in: MIN,
});
- await circuit.expectCorrectAssert({
+ await circuit.expectPass({
in: Math.floor((MIN + MAX) / 2),
});
});
it('should FAIL for out of range (upper bound)', async () => {
- await circuit.expectFailedAssert({
+ await circuit.expectFail({
in: MAX + 1,
});
});
it('should FAIL for out of range (lower bound)', async () => {
if (MIN > 0) {
- await circuit.expectFailedAssert({
+ await circuit.expectFail({
in: MIN - 1,
});
}
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 65920cf..1ea7f64 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -6,8 +6,6 @@
"strict": true,
"resolveJsonModule": true,
"esModuleInterop": true,
- "allowJs": true,
- "checkJs": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
diff --git a/tsconfig.type.json b/tsconfig.type.json
deleted file mode 100644
index 8ba7e22..0000000
--- a/tsconfig.type.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "extends": "./tsconfig.base.json",
- "include": ["src/**/*.ts"],
- "compilerOptions": {
- "module": "esnext",
- "target": "es2019",
- "removeComments": false,
- "declaration": true,
- "declarationMap": true,
- "declarationDir": "./dist/types",
- "outDir": "./dist/types",
- "emitDeclarationOnly": true
- }
-}
diff --git a/yarn.lock b/yarn.lock
index ed73daa..8fcfb40 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -160,11 +160,6 @@
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.4.tgz#e913e8175db8307d78b4e8fa690408ba6b65dee4"
integrity sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==
-"@types/ejs@^3.1.2":
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/@types/ejs/-/ejs-3.1.2.tgz#75d277b030bc11b3be38c807e10071f45ebc78d9"
- integrity sha512-ZmiaE3wglXVWBM9fyVC17aGPkLo/UgaOjEiI2FXQfyczrCefORPxIe+2dVmnmk3zkVIbizjrlQzmPGhSYGXG5g==
-
"@types/json-schema@^7.0.7":
version "7.0.11"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
@@ -570,6 +565,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"
@@ -719,7 +721,7 @@ eastasianwidth@^0.2.0:
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
-ejs@^3.1.6, ejs@^3.1.9:
+ejs@^3.1.6:
version "3.1.9"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361"
integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==
@@ -994,6 +996,15 @@ ffjavascript@0.2.56:
wasmcurves "0.2.0"
web-worker "^1.2.0"
+ffjavascript@0.2.57:
+ version "0.2.57"
+ resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.2.57.tgz#ba1be96015b2688192e49f2f4de2cc5150fd8594"
+ integrity sha512-V+vxZ/zPNcthrWmqfe/1YGgqdkTamJeXiED0tsk7B84g40DKlrTdx47IqZuiygqAVG6zMw4qYuvXftIJWsmfKQ==
+ dependencies:
+ wasmbuilder "0.0.16"
+ wasmcurves "0.2.0"
+ web-worker "^1.2.0"
+
ffjavascript@^0.2.48, ffjavascript@^0.2.56:
version "0.2.59"
resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.2.59.tgz#b2f836082587fab333dfb181b909a188f80036f3"
@@ -1970,6 +1981,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"
@@ -2172,6 +2193,22 @@ snarkjs@0.5.0:
logplease "^1.2.15"
r1csfile "0.0.41"
+snarkjs@^0.6.0:
+ version "0.6.11"
+ resolved "https://registry.yarnpkg.com/snarkjs/-/snarkjs-0.6.11.tgz#577b0250c0d9bb93c0ee1d4a3d3075956d11775c"
+ integrity sha512-pYDcrSKmUMMJtYxSQTMNG/MtuGvUWPSk9gZzADeIiHp8vJmFdCscMFa/YtemPAkne4v6Awm5HAhqbfNPyoqjug==
+ 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"