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.

+ + + NPM Workflow: Styles - - Test Suite: Mocha - - - Linter: ESLint - - - Formatter: Prettier + + Workflow: Build GitHub: SnarkJS @@ -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"