Merge pull request #10 from erhant/erhant/more-packaging

Circomkit is now a package, not a template repo!
This commit is contained in:
erhant.eth
2023-06-03 20:58:15 +03:00
committed by GitHub
43 changed files with 1029 additions and 731 deletions

24
.github/workflows/build.yml vendored Normal file
View File

@@ -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

1
.gitignore vendored
View File

@@ -119,3 +119,4 @@ tmp.ptau
# ignore auto generated test circuits
circuits/test
ptau

View File

@@ -1,7 +1,7 @@
{
"extension": ["ts"],
"require": "ts-node/register",
"spec": "tests/**/*.ts",
"spec": "tests/**/*.test.ts",
"timeout": 100000,
"exit": true
}

5
.npmignore Normal file
View File

@@ -0,0 +1,5 @@
*
!dist
!LICENSE
!package.json
!README.md

View File

@@ -2,24 +2,21 @@
<h1 align="center">
Circomkit
</h1>
<p align="center"><i>A simple-to-use Circom & SnarkJS circuit development & testing environment.</i></p>
<p align="center"><i>A simple-to-use & opinionated circuit development & testing toolkit.</i></p>
</p>
<p align="center">
<a href="https://opensource.org/licenses/MIT" target="_blank">
<img src="https://img.shields.io/badge/license-MIT-yellow.svg">
</a>
<a href="https://www.npmjs.com/package/circomkit" target="_blank">
<img alt="NPM" src="https://img.shields.io/npm/v/circomkit?logo=npm&color=CB3837">
</a>
<a href="./.github/workflows/styles.yml" target="_blank">
<img alt="Workflow: Styles" src="https://github.com/erhant/circomkit/actions/workflows/styles.yml/badge.svg?branch=main">
</a>
<a href="https://mochajs.org/" target="_blank">
<img alt="Test Suite: Mocha" src="https://img.shields.io/badge/tester-mocha-8D6748?logo=Mocha">
</a>
<a href="https://eslint.org/" target="_blank">
<img alt="Linter: ESLint" src="https://img.shields.io/badge/linter-eslint-8080f2?logo=eslint">
</a>
<a href="https://prettier.io/" target="_blank">
<img alt="Formatter: Prettier" src="https://img.shields.io/badge/formatter-prettier-f8bc45?logo=prettier">
<a href="./.github/workflows/build.yml" target="_blank">
<img alt="Workflow: Build" src="https://github.com/erhant/circomkit/actions/workflows/build.yml/badge.svg?branch=main">
</a>
<a href="https://github.com/iden3/snarkjs" target="_blank">
<img alt="GitHub: SnarkJS" src="https://img.shields.io/badge/github-snarkjs-lightgray?logo=github">
@@ -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 <circuit-name>
# test all circuits
yarn test:all
```
You can test both witness calculations and proof generation & verification. We describe both in their respective sections, going over an example of "Multiplication" circuit.
### Example Circuits
We have several example circuits 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 <circuit-name>
# test all circuits
yarn test:all
```
You can test both witness calculations and proof generation & verification. We describe both in their respective sections, going over an example of "Multiplication" circuit.
## Styling
We use [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html) for the TypeScript codes.

View File

@@ -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

5
circomkit.json Normal file
View File

@@ -0,0 +1,5 @@
{
"colors": {
"title": "\u001b[0;33m"
}
}

View File

@@ -1,4 +1,4 @@
// auto-generated by instantiate.js
// auto-generated by circomkit
pragma circom 2.1.0;
include "../multiplier.circom";

View File

@@ -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(", ") %>);

View File

@@ -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"
]
}

View File

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

Binary file not shown.

View File

@@ -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 <function>"
echo " clean Cleans the build artifacts"
echo " contract Export Solidity verifier"
echo " calldata Export Solidity calldata for verification"
echo " compile Compile the circuit"
echo " instantiate Instantiate the main component"
echo " type Generate types for TypeScript"
echo " 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 <circuit-name>"
echo " -n <num-contributions> (default: $NUM_CONTRIBS)"
echo " -i <input-name>"
echo " -p <phase1-ptau-path>"
;;
esac

View File

@@ -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}"
}

View File

@@ -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}"
}

View File

@@ -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}"
}

View File

@@ -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}"
}

View File

@@ -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}"
}

View File

@@ -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}"
}

View File

@@ -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}"
}

View File

@@ -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}"
}

View File

@@ -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}"
}

View File

@@ -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}"
}

View File

@@ -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}"
}

187
src/bin/index.ts Normal file
View File

@@ -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<number> {
// 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);
});

317
src/circomkit.ts Normal file
View File

@@ -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<CircomkitConfig> = {
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<CircomkitConfig> = {}) {
// 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<R1CSInfoType> {
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;
}
}

View File

@@ -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};

View File

@@ -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);
}

View File

@@ -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<IN extends string[] = []> {
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<IN extends string[] = []> {
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<IN extends string[] = []> {
* @param proof proof object, given from `prove`
* @param publicSignals public signals for the circuit
*/
async expectVerificationPass(proof: object, publicSignals: string[]): Promise<void> {
async expectPass(proof: object, publicSignals: string[]): Promise<void> {
expect(await this.verify(proof, publicSignals)).to.be.true;
}
@@ -83,7 +80,7 @@ export default class ProofTester<IN extends string[] = []> {
* @param proof proof object, given from `prove`
* @param publicSignals public signals for the circuit
*/
async expectVerificationFail(proof: object, publicSignals: string[]): Promise<void> {
async expectFail(proof: object, publicSignals: string[]): Promise<void> {
expect(await this.verify(proof, publicSignals)).to.be.false;
}
}

View File

@@ -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<IN extends readonly string[] = [], OUT extends readonly string[] = []> {
/**
* 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<IN extends string[] = [], OUT extends string[] = []>(
circuitName: string,
circuitConfig: CircuitConfig
): Promise<WasmTester<IN, OUT>> {
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<IN, OUT>(circomWasmTester);
}
constructor(circomWasmTester: CircomWasmTester) {
this.circomWasmTester = circomWasmTester;
@@ -124,7 +131,7 @@ export default class WasmTester<IN extends readonly string[] = [], OUT extends r
* Expect an input to fail an assertion in the circuit.
* @param input bad input
*/
async expectFailedAssert(input: CircuitSignals<IN>) {
async expectFail(input: CircuitSignals<IN>) {
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<IN extends readonly string[] = [], OUT extends r
* @param input correct input
* @param output expected output, if `undefined` it will only check constraints
*/
async expectCorrectAssert(input: CircuitSignals<IN>, output?: CircuitSignals<OUT>) {
async expectPass(input: CircuitSignals<IN>, output?: CircuitSignals<OUT>) {
const witness = await this.calculateWitness(input);
await this.checkConstraints(witness);
if (output) {
@@ -193,10 +200,8 @@ export default class WasmTester<IN extends readonly string[] = [], OUT extends r
// easy case, just return the witness of this symbol
entries.push([outSignal, witness[idx]]);
} else {
/*
at this point, we have an array of signals like `main.signal[0..dim1][0..dim2]..[0..dimN]` and we must construct
the necessary multi-dimensional array out of it.
*/
// at this point, we have an array of signals like `main.signal[0..dim1][0..dim2]..[0..dimN]`
// and we must construct the necessary multi-dimensional array out of it.
// eslint-disable-next-line no-inner-declarations
function processDepth(d: number): SignalValueType {
const acc: SignalValueType = [];
@@ -260,22 +265,4 @@ export default class WasmTester<IN extends readonly string[] = [], OUT extends r
return fakeWitness;
}
/**
* 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<IN extends string[] = [], OUT extends string[] = []>(
circuitName: string,
circuitConfig: CircuitConfig
): Promise<WasmTester<IN, OUT>> {
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<IN, OUT>(circomWasmTester);
}
}

54
src/types/circomkit.ts Normal file
View File

@@ -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';

View File

@@ -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 readonly string[] = []> = 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;
};

96
src/utils/initFiles.ts Normal file
View File

@@ -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;
};
};

52
src/utils/instantiate.ts Normal file
View File

@@ -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<CircuitConfig>) => `// 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;
}

50
src/utils/ptau.ts Normal file
View File

@@ -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<string> {
const ptauPath = `${ptauDir}/${ptauName}`;
const file = createWriteStream(ptauPath);
return new Promise<string>(resolve => {
get(`${PTAU_URL_BASE}/${ptauName}`, response => {
response.pipe(file);
file.on('finish', () => {
file.close();
resolve(ptauPath);
});
});
});
}

View File

@@ -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)});
});
});

View File

@@ -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'});
});
});
});

View File

@@ -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']);
});
});

View File

@@ -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']>;

View File

@@ -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,
});
}

View File

@@ -6,8 +6,6 @@
"strict": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"allowJs": true,
"checkJs": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",

View File

@@ -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
}
}

View File

@@ -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"