mirror of
https://github.com/erhant/circomkit.git
synced 2026-05-05 03:00:37 -04:00
Merge pull request #10 from erhant/erhant/more-packaging
Circomkit is now a package, not a template repo!
This commit is contained in:
24
.github/workflows/build.yml
vendored
Normal file
24
.github/workflows/build.yml
vendored
Normal 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
1
.gitignore
vendored
@@ -119,3 +119,4 @@ tmp.ptau
|
||||
|
||||
# ignore auto generated test circuits
|
||||
circuits/test
|
||||
ptau
|
||||
|
||||
@@ -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
5
.npmignore
Normal file
@@ -0,0 +1,5 @@
|
||||
*
|
||||
!dist
|
||||
!LICENSE
|
||||
!package.json
|
||||
!README.md
|
||||
84
README.md
84
README.md
@@ -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.
|
||||
|
||||
@@ -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
5
circomkit.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"colors": {
|
||||
"title": "\u001b[0;33m"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// auto-generated by instantiate.js
|
||||
// auto-generated by circomkit
|
||||
pragma circom 2.1.0;
|
||||
|
||||
include "../multiplier.circom";
|
||||
|
||||
@@ -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(", ") %>);
|
||||
28
package.json
28
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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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.
137
scripts/cli.sh
137
scripts/cli.sh
@@ -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
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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}"
|
||||
}
|
||||
@@ -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
187
src/bin/index.ts
Normal 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
317
src/circomkit.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
10
src/index.ts
10
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};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
54
src/types/circomkit.ts
Normal 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';
|
||||
@@ -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
96
src/utils/initFiles.ts
Normal 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
52
src/utils/instantiate.ts
Normal 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
50
src/utils/ptau.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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)});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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'});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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']>;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
49
yarn.lock
49
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"
|
||||
|
||||
Reference in New Issue
Block a user