mirror of
https://github.com/erhant/circomkit.git
synced 2026-05-05 03:00:37 -04:00
better docs
This commit is contained in:
12
.cli.env
12
.cli.env
@@ -1,7 +1,9 @@
|
||||
# compiler args
|
||||
CLIENV_COMPILER_ARGS="-l ./node_modules --r1cs --wasm --sym --inspect"
|
||||
# can add --inspect and -c for example
|
||||
CIRCOMKIT_COMPILER_ARGS="-l ./node_modules --r1cs --wasm --sym"
|
||||
|
||||
# colors for swag terminal outputs
|
||||
CIRCOMKIT_COLOR_TITLE='\033[0;34m' # blue
|
||||
CIRCOMKIT_COLOR_LOG='\033[2;37m' # gray
|
||||
CIRCOMKIT_COLOR_RESET='\033[0m' # reset color
|
||||
|
||||
# colors for swag
|
||||
CLIENV_COLOR_TITLE='\033[0;34m' # blue
|
||||
CLIENV_COLOR_LOG='\033[2;37m' # gray
|
||||
CLIENV_COLOR_RESET='\033[0m' # reset color
|
||||
|
||||
269
README.md
269
README.md
@@ -2,7 +2,7 @@
|
||||
<h1 align="center">
|
||||
Circomkit
|
||||
</h1>
|
||||
<p align="center">An opinionated Circom circuit development & testing environment..</p>
|
||||
<p align="center">An opinionated Circom circuit development & testing environment.</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -21,16 +21,213 @@
|
||||
<a href="https://prettier.io/" target="_blank">
|
||||
<img alt="Formatter: Prettier" src="https://img.shields.io/badge/formatter-prettier-f8bc45?logo=prettier">
|
||||
</a>
|
||||
<a href="https://github.com/google/gts" target="_blank">
|
||||
<img alt="GTS" src="https://img.shields.io/badge/code%20style-google-4285F4?logo=google">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Usage
|
||||
|
||||
Clone the repository or create a new one with this as the template! You need [Circom](https://docs.circom.io/getting-started/installation/) to compile circuits. Other than that, just `yarn` or `npm install` to get started. It will also install [Circomlib](https://github.com/iden3/circomlib/tree/master/circuits) which has many utility circuits.
|
||||
|
||||
The repository follows an _opinionated file structure_ shown below, abstracting away the pathing and orientation behind the scenes. Shell scripts handle most of the work, and they are exposed through a [CLI](./scripts/main.sh).
|
||||
The repository follows an _opinionated file structure_ shown below, abstracting away the pathing and orientation behind the scenes. Shell scripts handle most of the work, and they are exposed through a CLI.
|
||||
|
||||
Write your circuits under the `circuits` folder; the circuit code itself should be templates only. The main component itself is created automatically via a [script](./utils/instantiate.ts) which uses a simple EJS [template](./circuits/ejs/template.circom) to create the main component. The target circuits are defined under the [circuit configs](./circuit.config.ts) file, such as:
|
||||
|
||||
```js
|
||||
// circuit name is the key
|
||||
multiplier_3: {
|
||||
// template to instantiate the main component
|
||||
template: 'Multiplier',
|
||||
// file to include for the template
|
||||
file: 'multiplier',
|
||||
// array of public inputs
|
||||
publicInputs: [],
|
||||
// template parameters, order is important
|
||||
templateParams: [3],
|
||||
}
|
||||
```
|
||||
|
||||
Use the [CLI](./scripts/cli.sh), or its wrapper scripts in [package.json](./package.json) to do stuff with your circuits.
|
||||
|
||||
```bash
|
||||
# Compile the circuit
|
||||
yarn compile circuit-name [-d directory-name (default: main)]
|
||||
|
||||
# Phase-2 Circuit-specific setup
|
||||
yarn ptau circuit-name -p phase1-ptau-path [-n num-contribs (default: 1)]
|
||||
|
||||
# Shorthand for `compile` and then `ptau`
|
||||
yarn keygen circuit-name -p phase1-ptau-path [-n num-contribs (default: 1)]
|
||||
|
||||
# Generate a proof for a JSON input
|
||||
yarn prove circuit-name -i input-name
|
||||
|
||||
# Verify a proof for some JSON input
|
||||
yarn verify circuit-name -i input-name
|
||||
|
||||
# Clean circuit artifacts
|
||||
yarn clean circuit-name
|
||||
|
||||
# Run the test for a circuit
|
||||
yarn test circuit-name
|
||||
|
||||
# Run all tests
|
||||
yarn test:all
|
||||
```
|
||||
|
||||
There are some environment variables that the CLI can make use of, they are written under [.cli.env](./.cli.env) file.
|
||||
|
||||
### Examples
|
||||
|
||||
We have several example circuits to help guide you:
|
||||
|
||||
- **Multiplier**: A circuit to prove that you know the factors of a number.
|
||||
- **Floating Point Addition**: A circuit to compute the sum of two floating-point numbers, adapted from [Berkeley ZKP MOOC 2023 - Lab 1](https://github.com/rdi-berkeley/zkp-mooc-lab).
|
||||
- **Fibonacci**: A circuit to compute Fibonacci numbers.
|
||||
- **Sudoku**: A circuit to prove that you know the solution to a Sudoku puzzle.
|
||||
|
||||
## Testing
|
||||
|
||||
To run tests do the following:
|
||||
|
||||
```bash
|
||||
# test all circuits
|
||||
yarn test:all
|
||||
# test a specific circuit
|
||||
yarn test "circuit name"
|
||||
```
|
||||
|
||||
You can test both witness calculations and proof generation & verification. We describe both in their respective sections, going over an example of "Multiplication" circuit.
|
||||
|
||||
### Witness Calculation
|
||||
|
||||
Witness calculation tests check whether your circuit computes the correct result based on your inputs, and makes sure that assertions are correct. We provide very useful utility functions to help write these tests.
|
||||
|
||||
To run a circuit, you need to create a `main` component in Circom, where your main template is assigned to this component. You could do this manually, but in Circomkit we prefer to do this programmatically, using the `instantiate` function. Let us go over an example test for the multiplication circuit.
|
||||
|
||||
```ts
|
||||
import {instantiate} from '../utils/instantiate';
|
||||
import {createWasmTester} from '../utils/wasmTester';
|
||||
|
||||
describe('multiplier', () => {
|
||||
const N = 3;
|
||||
let circuit: Awaited<ReturnType<typeof createWasmTester>>;
|
||||
|
||||
before(async () => {
|
||||
const circuitName = 'multiplier_' + N;
|
||||
// (1) creates the main component at ./circuits/test/<circuitName>.circom
|
||||
instantiate(circuitName, 'test', {
|
||||
file: 'multiplier', // our file is at ./circuits/multiplier.circom
|
||||
template: 'Multiplier', // our file has the template "Template"
|
||||
publicInputs: [], // list of public signal input names
|
||||
templateParams: [N], // list of template parameters in order
|
||||
});
|
||||
|
||||
// (2) reads the main component at ./circuits/test/<circuitName>.circom
|
||||
circuit = await createWasmTester(circuitName, 'test');
|
||||
|
||||
// (3) optionally checks if the constraint count meets your expectations
|
||||
await circuit.printConstraintCount(N - 1);
|
||||
});
|
||||
|
||||
it('should compute correctly', async () => {
|
||||
// N random numbers
|
||||
const input = {
|
||||
in: Array<number>(N)
|
||||
.fill(0)
|
||||
.map(() => Math.floor(Math.random() * 100 * N)),
|
||||
};
|
||||
|
||||
// make sure the output is correct
|
||||
await circuit.expectCorrectAssert(input, {
|
||||
out: input.in.reduce((prev, acc) => acc * prev),
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Before tests begin, we must create a circuit tester object, which is what happens in the `before` hook.
|
||||
|
||||
1. A `main` component is created with the given configuration.
|
||||
2. A circuit tester is created from that main component.
|
||||
3. Constraint count is checked (optional).
|
||||
|
||||
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
|
||||
|
||||
#### Multiple templates
|
||||
|
||||
You will often have multiple templates in your circuit code, and you might want to test them in the same test file of your main circuit too. Well, you can!
|
||||
|
||||
```ts
|
||||
describe('multiplier utilities', () => {
|
||||
describe('multiplication gate', () => {
|
||||
let circuit: Awaited<ReturnType<typeof createWasmTester>>;
|
||||
|
||||
before(async () => {
|
||||
const circuitName = 'mulgate';
|
||||
// we can provide sub-folders as the target, such as test/multiplier in this case!
|
||||
instantiate(circuitName, 'test/multiplier', {
|
||||
file: 'multiplier',
|
||||
template: 'MultiplicationGate',
|
||||
publicInputs: [],
|
||||
templateParams: [],
|
||||
});
|
||||
circuit = await createWasmTester(circuitName, 'test/multiplier');
|
||||
});
|
||||
|
||||
it('should pass for in range', async () => {
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
in: [7, 5],
|
||||
},
|
||||
{out: 7 * 5}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Proof Verification
|
||||
|
||||
If you have created the prover key, verification key & the circuit WASM file, you can also test proving & verification keys.
|
||||
|
||||
```ts
|
||||
describe('multiplier (proofs)', () => {
|
||||
const N = 3;
|
||||
|
||||
let fullProof: FullProof;
|
||||
let circuit: ProofTester;
|
||||
before(async () => {
|
||||
const circuitName = 'multiplier_' + N;
|
||||
circuit = new ProofTester(circuitName);
|
||||
fullProof = await circuit.prove({
|
||||
in: Array<number>(N)
|
||||
.fill(0)
|
||||
.map(() => Math.floor(Math.random() * 100 * N)),
|
||||
});
|
||||
});
|
||||
|
||||
it('should verify', async () => {
|
||||
await circuit.expectVerificationPass(fullProof.proof, fullProof.publicSignals);
|
||||
});
|
||||
|
||||
it('should NOT verify a wrong multiplication', async () => {
|
||||
// just give a prime number as the public signal, assuming none of the inputs are 1
|
||||
await circuit.expectVerificationFail(fullProof.proof, ['13']);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Notice the two utility functions provided here:
|
||||
|
||||
- `circuit.expectVerificationPass(proof, publicSignals)` makes sure that the given proof is **accepted** by the verifier for the given public signals.
|
||||
- `circuit.expectVerificationFail(proof, publicSignals)` makes sure that the given proof is **rejected** by the verifier for the given public signals.
|
||||
|
||||
## File Structure
|
||||
|
||||
The underlying file structure is explained below.
|
||||
|
||||
```sh
|
||||
circomkit
|
||||
@@ -67,65 +264,3 @@ circomkit
|
||||
│ └── verification_key.json
|
||||
└── ...
|
||||
```
|
||||
|
||||
Write your circuits under the `circuits` folder; the circuit code itself should be templates only. The main component itself is created automatically via a [script](./scripts/instantiate.js) which uses a simple EJS [template](./circuits/ejs/_template.circom) to create the main component. The target circuits are defined under the [circuit configs](./circuit.config.cjs) file, such as:
|
||||
|
||||
```js
|
||||
multiplier_3: {
|
||||
// template to instantiate the main component
|
||||
template: 'Multiplier',
|
||||
// file to include for the template
|
||||
file: 'multiplier',
|
||||
// array of public inputs
|
||||
publicInputs: [],
|
||||
// template parameters, order is important
|
||||
templateParams: [3],
|
||||
}
|
||||
```
|
||||
|
||||
Use the [CLI](./scripts/cli.sh), or its wrapper scripts in [package.json](./package.json) to do stuff with your circuits.
|
||||
|
||||
```bash
|
||||
# first argument is ALWAYS the circuit name
|
||||
yarn compile circuit-name [-d directory-name (default: main)]
|
||||
yarn ptau circuit-name -p phase1-ptau-path [-n num-contribs (default: 1)]
|
||||
yarn prove circuit-name -i input-name
|
||||
yarn verify circuit-name -i input-name
|
||||
yarn clean circuit-name
|
||||
yarn test circuit-name
|
||||
yarn test:all
|
||||
```
|
||||
|
||||
There are some environment variables that the CLI can make use of, they are written under [.cli.env](./.cli.env) file.
|
||||
|
||||
## Testing
|
||||
|
||||
To run Mocha tests do the following:
|
||||
|
||||
```bash
|
||||
# run all tests
|
||||
yarn test:all
|
||||
# run a specific test
|
||||
yarn test "circuit name"
|
||||
```
|
||||
|
||||
### Witness Computation
|
||||
|
||||
TODO
|
||||
|
||||
### Proof Verification
|
||||
|
||||
TODO
|
||||
|
||||
## Examples
|
||||
|
||||
We have several example circuits to help guide you:
|
||||
|
||||
- **Multiplier**: A circuit to prove that you know the factors of a number.
|
||||
- **Floating Point Addition**: A circuit to compute the sum of two floating-point numbers, as written in [Berkeley ZKP MOOC 2023 - Lab 1](https://github.com/rdi-berkeley/zkp-mooc-lab).
|
||||
- **Fibonacci**: A circuit to compute Fibonacci numbers.
|
||||
- **Sudoku**: A circuit to prove that you know the solution to a Sudoku puzzle.
|
||||
|
||||
## Styling
|
||||
|
||||
The code uses Google TypeScript Style guide. It also has some folder & file icon overrides for several Material UI icons to make things look better in VSCode.
|
||||
|
||||
@@ -5,7 +5,7 @@ import {Config} from './types/circuit';
|
||||
*/
|
||||
const config: Config = {
|
||||
// multiplication of 3 numbers
|
||||
mert: {
|
||||
multiplier_3: {
|
||||
file: 'multiplier',
|
||||
template: 'Multiplier',
|
||||
publicInputs: [],
|
||||
|
||||
@@ -8,6 +8,7 @@ template MultiplicationGate() {
|
||||
|
||||
// Multiplication of N numbers
|
||||
template Multiplier(N) {
|
||||
assert(N > 1);
|
||||
signal input in[N];
|
||||
signal output out;
|
||||
component comp[N-1];
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"compile": "./scripts/cli.sh -f compile -c",
|
||||
"clean": "./scripts/cli.sh -f clean -c",
|
||||
"ptau": "./scripts/cli.sh -f ptau -c",
|
||||
"keygen": "./scripts/cli.sh -f keygen -c",
|
||||
"prove": "./scripts/cli.sh -f prove -c",
|
||||
"verify": "./scripts/cli.sh -f verify -c",
|
||||
"_help": "./scripts/cli.sh -f help",
|
||||
|
||||
@@ -78,6 +78,9 @@ case $FUNC in
|
||||
ptau)
|
||||
ptau $CIRCUIT $NUM_CONTRIBS $P1_PTAU
|
||||
;;
|
||||
keygen)
|
||||
compile $CIRCUIT $COMPILE_DIR && ptau $CIRCUIT $NUM_CONTRIBS $P1_PTAU
|
||||
;;
|
||||
prove)
|
||||
witness $CIRCUIT $INPUT && prove $CIRCUIT $INPUT
|
||||
;;
|
||||
@@ -98,6 +101,7 @@ case $FUNC in
|
||||
echo " witness Generate witness from an input"
|
||||
echo " prove Prove an input"
|
||||
echo " verify Verify a proof & public signals"
|
||||
echo " keygen Shorthand for compile & ptau"
|
||||
echo " -c <circuit-name>"
|
||||
echo " -d <directory-name>"
|
||||
echo " -n <num-contributions> (default: 1)"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Clean build files
|
||||
clean() {
|
||||
echo -e "\n${CLIENV_COLOR_TITLE}=== Cleaning artifacts ===${CLIENV_COLOR_RESET}"
|
||||
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
|
||||
@@ -8,5 +8,5 @@ clean() {
|
||||
rm -rf $CIRCUIT_DIR
|
||||
rm -f $TARGET
|
||||
|
||||
echo -e "${CLIENV_COLOR_LOG}Deleted $CIRCUIT_DIR and $TARGET${CLIENV_COLOR_RESET}"
|
||||
echo -e "${CIRCOMKIT_COLOR_LOG}Deleted $CIRCUIT_DIR and $TARGET${CIRCOMKIT_COLOR_RESET}"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Compile the circuit, outputting R1CS and JS files
|
||||
compile() {
|
||||
echo -e "\n${CLIENV_COLOR_TITLE}=== Compiling the circuit ===${CLIENV_COLOR_RESET}"
|
||||
echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Compiling the circuit ===${CIRCOMKIT_COLOR_RESET}"
|
||||
local CIRCUIT=$1
|
||||
local DIR=$2
|
||||
local CIRCOM_IN=./circuits/$DIR/$CIRCUIT.circom
|
||||
@@ -10,8 +10,8 @@ compile() {
|
||||
mkdir -p $CIRCOM_OUT
|
||||
|
||||
# compile with circom
|
||||
echo "circom $CIRCOM_IN -o $CIRCOM_OUT $CLIENV_COMPILER_ARGS"
|
||||
circom $CIRCOM_IN -o $CIRCOM_OUT $CLIENV_COMPILER_ARGS
|
||||
echo "circom $CIRCOM_IN -o $CIRCOM_OUT $CIRCOMKIT_COMPILER_ARGS"
|
||||
circom $CIRCOM_IN -o $CIRCOM_OUT $CIRCOMKIT_COMPILER_ARGS
|
||||
|
||||
echo -e "${CLIENV_COLOR_LOG}Built artifacts under $CIRCOM_OUT${CLIENV_COLOR_RESET}"
|
||||
echo -e "${CIRCOMKIT_COLOR_LOG}Built artifacts under $CIRCOM_OUT${CIRCOMKIT_COLOR_RESET}"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Instantiate the main component
|
||||
instantiate() {
|
||||
echo -e "\n${CLIENV_COLOR_TITLE}=== Creating main component ===${CLIENV_COLOR_RESET}"
|
||||
echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Creating main component ===${CIRCOMKIT_COLOR_RESET}"
|
||||
local CIRCUIT=$1
|
||||
local DIR=$2
|
||||
|
||||
@@ -8,5 +8,5 @@ instantiate() {
|
||||
mkdir -p ./circuits/$DIR
|
||||
npx ts-node ./utils/instantiate.ts $CIRCUIT $DIR
|
||||
|
||||
echo -e "${CLIENV_COLOR_LOG}Done!${CLIENV_COLOR_RESET}"
|
||||
echo -e "${CIRCOMKIT_COLOR_LOG}Done!${CIRCOMKIT_COLOR_RESET}"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Generate a proof
|
||||
prove() {
|
||||
echo -e "\n${CLIENV_COLOR_TITLE}=== Generating proof ===${CLIENV_COLOR_RESET}"
|
||||
echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Generating proof ===${CIRCOMKIT_COLOR_RESET}"
|
||||
local CIRCUIT=$1
|
||||
local INPUT=$2
|
||||
local CIRCUIT_DIR=./build/$CIRCUIT
|
||||
@@ -12,5 +12,5 @@ prove() {
|
||||
$OUTPUT_DIR/proof.json \
|
||||
$OUTPUT_DIR/public.json
|
||||
|
||||
echo -e "${CLIENV_COLOR_LOG}Generated under $OUTPUT_DIR${CLIENV_COLOR_RESET}"
|
||||
echo -e "${CIRCOMKIT_COLOR_LOG}Generated under $OUTPUT_DIR${CIRCOMKIT_COLOR_RESET}"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Commence a circuit-specific phase-2 powers-of-tau ceremony
|
||||
ptau() {
|
||||
echo -e "\n${CLIENV_COLOR_TITLE}=== Phase-2 Powers of Tau ===${CLIENV_COLOR_RESET}"
|
||||
echo -e "${CLIENV_COLOR_LOG}this may take a while...${CLIENV_COLOR_RESET}"
|
||||
echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Phase-2 Powers of Tau ===${CIRCOMKIT_COLOR_RESET}"
|
||||
echo -e "${CIRCOMKIT_COLOR_LOG}this may take a while...${CIRCOMKIT_COLOR_RESET}"
|
||||
local CIRCUIT=$1 # circuit name
|
||||
local NUM_CONTRIBS=$2 # number of contributions
|
||||
local P1_PTAU=$3 # path to phase-1 ptau
|
||||
@@ -11,6 +11,12 @@ ptau() {
|
||||
local PROVER_KEY=$CIRCUIT_DIR/prover_key.zkey
|
||||
local VERIFICATION_KEY=$CIRCUIT_DIR/verification_key.json
|
||||
|
||||
# check if groth16 is used, as there is no need for phase-2 for plonk
|
||||
# TODO
|
||||
|
||||
# check if P1_PTAU exists
|
||||
# TODO
|
||||
|
||||
# start phase-2 ceremony (circuit specific)
|
||||
snarkjs powersoftau prepare phase2 $P1_PTAU $P2_PTAU -v
|
||||
|
||||
@@ -43,5 +49,5 @@ ptau() {
|
||||
# export
|
||||
snarkjs zkey export verificationkey $PROVER_KEY $VERIFICATION_KEY
|
||||
|
||||
echo -e "${CLIENV_COLOR_LOG}Generated keys\n\tProver key: $PROVER_KEY\n\tVerification key: $VERIFICATION_KEY${CLIENV_COLOR_RESET}"
|
||||
echo -e "${CIRCOMKIT_COLOR_LOG}Generated keys\n\tProver key: $PROVER_KEY\n\tVerification key: $VERIFICATION_KEY${CIRCOMKIT_COLOR_RESET}"
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
type() {
|
||||
set -e
|
||||
|
||||
echo -e "\n${CLIENV_COLOR_TITLE}=== Generating types ===${CLIENV_COLOR_RESET}"
|
||||
echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Generating types ===${CIRCOMKIT_COLOR_RESET}"
|
||||
local CIRCUIT=$1
|
||||
local SYM=./build/$CIRCUIT/$CIRCUIT.sym
|
||||
local TMP=./scripts/utils.tmp.txt
|
||||
|
||||
# choose lines with 1 dot only (these are the signals of the main component), extract their names
|
||||
cat $SYM | awk -F '.' '{print $2}'
|
||||
cat $SYM | awk -F '.' 'NF==2{print $2}'
|
||||
|
||||
echo -e "\n${CLIENV_COLOR_LOG}Types generated!${CLIENV_COLOR_RESET}"
|
||||
echo -e "\n${CIRCOMKIT_COLOR_LOG}Types generated!${CIRCOMKIT_COLOR_RESET}"
|
||||
}
|
||||
|
||||
# cat ./build/multiplier3/multiplier3.sym | awk -F '.' 'NF==2{print $2}'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Verify a witness & proof
|
||||
verify() {
|
||||
echo -e "\n${CLIENV_COLOR_TITLE}=== Verifying proof ===${CLIENV_COLOR_RESET}"
|
||||
echo -e "\n${CIRCOMKIT_COLOR_TITLE}=== Verifying proof ===${CIRCOMKIT_COLOR_RESET}"
|
||||
local CIRCUIT=$1
|
||||
local INPUT=$2
|
||||
local CIRCUIT_DIR=./build/${CIRCUIT}
|
||||
@@ -10,5 +10,5 @@ verify() {
|
||||
$CIRCUIT_DIR/$INPUT/public.json \
|
||||
$CIRCUIT_DIR/$INPUT/proof.json
|
||||
|
||||
echo -e "${CLIENV_COLOR_LOG}Verification complete.${CLIENV_COLOR_RESET}"
|
||||
echo -e "${CIRCOMKIT_COLOR_LOG}Verification complete.${CIRCOMKIT_COLOR_RESET}"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Computes the witness for the given circuit and input
|
||||
## Calculates the witness for the given circuit and input
|
||||
witness() {
|
||||
echo -e "\n${CLIENV_COLOR_TITLE}=== Computing witness ===${CLIENV_COLOR_RESET}"
|
||||
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
|
||||
@@ -15,5 +15,5 @@ witness() {
|
||||
$INPUT_DIR/$INPUT.json \
|
||||
$WITNESS
|
||||
|
||||
echo -e "${CLIENV_COLOR_LOG}Generated\n\tWitness: $WITNESS${CLIENV_COLOR_RESET}"
|
||||
echo -e "${CIRCOMKIT_COLOR_LOG}Generated\n\tWitness: $WITNESS${CIRCOMKIT_COLOR_RESET}"
|
||||
}
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
import {createWasmTester} from '../utils/wasmTester';
|
||||
import {ProofTester} from '../utils/proofTester';
|
||||
import type {CircuitSignals, FullProof} from '../types/circuit';
|
||||
import {assert, expect} from 'chai';
|
||||
import type {FullProof} from '../types/circuit';
|
||||
import {instantiate} from '../utils/instantiate';
|
||||
// read inputs from file
|
||||
import input80 from '../inputs/multiplier_3/80.json';
|
||||
|
||||
const N = 3;
|
||||
|
||||
describe('multiplier', () => {
|
||||
const INPUT: CircuitSignals = {
|
||||
in: [1, 2, 3], // TODO: N random ints
|
||||
};
|
||||
|
||||
const N = 3;
|
||||
let circuit: Awaited<ReturnType<typeof createWasmTester>>;
|
||||
|
||||
before(async () => {
|
||||
@@ -24,52 +16,68 @@ describe('multiplier', () => {
|
||||
templateParams: [N],
|
||||
});
|
||||
circuit = await createWasmTester(circuitName, 'test');
|
||||
await circuit.printConstraintCount(N); // N - 1
|
||||
await circuit.printConstraintCount(N - 1);
|
||||
});
|
||||
|
||||
// after(() => {
|
||||
// clearInstance(circuitName, 'test');
|
||||
// });
|
||||
|
||||
it('should compute correctly', async () => {
|
||||
await circuit.expectCorrectAssert(INPUT, {
|
||||
out: BigInt(INPUT.in.reduce((prev: bigint, acc: bigint) => acc * prev)),
|
||||
const input = {
|
||||
in: Array<number>(N)
|
||||
.fill(0)
|
||||
.map(() => Math.floor(Math.random() * 100 * N)),
|
||||
};
|
||||
await circuit.expectCorrectAssert(input, {
|
||||
out: input.in.reduce((prev, acc) => acc * prev),
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT compute with wrong number of inputs', async () => {
|
||||
const fewInputs = INPUT.in.slice(1);
|
||||
await circuit.calculateWitness({in: fewInputs}, true).then(
|
||||
() => assert.fail(),
|
||||
err => expect(err.message).to.eq('Not enough values for input signal in\n')
|
||||
);
|
||||
|
||||
const manyInputs = [2n, ...INPUT.in];
|
||||
await circuit.calculateWitness({in: manyInputs}, true).then(
|
||||
() => assert.fail(),
|
||||
err => expect(err.message).to.eq('Too many values for input signal in\n')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// you can also test prover & verifier functions using the actual build files!
|
||||
describe.skip('multiplier (proofs)', () => {
|
||||
const INPUT: CircuitSignals = input80;
|
||||
describe('multiplier utilities', () => {
|
||||
describe('multiplication gate', () => {
|
||||
let circuit: Awaited<ReturnType<typeof createWasmTester>>;
|
||||
|
||||
before(async () => {
|
||||
const circuitName = 'mulgate';
|
||||
instantiate(circuitName, 'test/multiplier', {
|
||||
file: 'multiplier',
|
||||
template: 'MultiplicationGate',
|
||||
publicInputs: [],
|
||||
templateParams: [],
|
||||
});
|
||||
circuit = await createWasmTester(circuitName, 'test/multiplier');
|
||||
});
|
||||
|
||||
it('should pass for in range', async () => {
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
in: [7, 5],
|
||||
},
|
||||
{out: 7 * 5}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiplier proofs', () => {
|
||||
const N = 3;
|
||||
|
||||
let fullProof: FullProof;
|
||||
let circuit: ProofTester;
|
||||
|
||||
before(async () => {
|
||||
circuit = new ProofTester('multiplier_3');
|
||||
fullProof = await circuit.prove(INPUT);
|
||||
const circuitName = 'multiplier_' + N;
|
||||
circuit = new ProofTester(circuitName);
|
||||
fullProof = await circuit.prove({
|
||||
in: Array<number>(N)
|
||||
.fill(0)
|
||||
.map(() => Math.floor(Math.random() * 100 * N)),
|
||||
});
|
||||
});
|
||||
|
||||
it('should verify', async () => {
|
||||
expect(await circuit.verify(fullProof.proof, fullProof.publicSignals)).to.be.true;
|
||||
await circuit.expectVerificationPass(fullProof.proof, fullProof.publicSignals);
|
||||
});
|
||||
|
||||
it('should NOT verify a wrong multiplication', async () => {
|
||||
// just give a prime number, assuming there are no factors of 1
|
||||
expect(await circuit.verify(fullProof.proof, ['13'])).to.be.false;
|
||||
// just give a prime number as the output, assuming none of the inputs are 1
|
||||
await circuit.expectVerificationFail(fullProof.proof, ['13']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -181,7 +181,7 @@ describe('sudoku utilities', () => {
|
||||
circuit = await createWasmTester(circuitName, 'test/sudoku');
|
||||
});
|
||||
|
||||
it('in range', async () => {
|
||||
it('should pass for in range', async () => {
|
||||
await circuit.expectCorrectAssert({
|
||||
in: MAX,
|
||||
});
|
||||
@@ -193,13 +193,13 @@ describe('sudoku utilities', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('out of range (upper bound)', async () => {
|
||||
it('should FAIL for out of range (upper bound)', async () => {
|
||||
await circuit.expectFailedAssert({
|
||||
in: MAX + 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('out of range (lower bound)', async () => {
|
||||
it('should FAIL for out of range (lower bound)', async () => {
|
||||
if (MIN > 0) {
|
||||
await circuit.expectFailedAssert({
|
||||
in: MIN - 1,
|
||||
|
||||
@@ -5,8 +5,12 @@ import {CircuitConfig} from '../types/circuit';
|
||||
|
||||
/**
|
||||
* Programmatically generate the `main` component
|
||||
* @param name name of the circuit to be generated
|
||||
* @param directory name of the directory under circuits to be created. Can be given sub-folders like `test/myCircuit/foobar`.
|
||||
* @param circuitConfig circuit configurations, if `undefined` then `circuit.config.ts` will be used.
|
||||
*/
|
||||
export function instantiate(name: string, directory: string, circuitConfig?: CircuitConfig) {
|
||||
// get config from circuit.config.ts if none are given
|
||||
if (circuitConfig === undefined) {
|
||||
if (!(name in config)) {
|
||||
throw new Error(`Target ${name} not found in circuit.config.cjs`);
|
||||
@@ -38,10 +42,6 @@ export function instantiate(name: string, directory: string, circuitConfig?: Cir
|
||||
// console.log(`Main component created at: ${targetPath}\n`);
|
||||
}
|
||||
|
||||
// export function clearTestInstance(name: string, directory: string) {
|
||||
// // TODO: remove the file
|
||||
// }
|
||||
|
||||
if (require.main === module) {
|
||||
const name = process.argv[2];
|
||||
const directory = process.argv[3];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from 'fs';
|
||||
const snarkjs = require('snarkjs');
|
||||
import {CircuitSignals, FullProof} from '../types/circuit';
|
||||
import {expect} from 'chai';
|
||||
import type {CircuitSignals, FullProof} from '../types/circuit';
|
||||
|
||||
/**
|
||||
* A more extensive Circuit class, able to generate proofs & verify them.
|
||||
@@ -49,4 +50,22 @@ export class ProofTester {
|
||||
async verify(proof: object, publicSignals: string[]): Promise<boolean> {
|
||||
return await snarkjs.groth16.verify(this.verificationKey, publicSignals, proof);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verification should pass for this proof and public signals.
|
||||
* @param proof proof object, given from `prove`
|
||||
* @param publicSignals public signals for the circuit
|
||||
*/
|
||||
async expectVerificationPass(proof: object, publicSignals: string[]): Promise<void> {
|
||||
expect(await this.verify(proof, publicSignals)).to.be.true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verification should fail for this proof and public signals.
|
||||
* @param proof proof object, given from `prove`
|
||||
* @param publicSignals public signals for the circuit
|
||||
*/
|
||||
async expectVerificationFail(proof: object, publicSignals: string[]): Promise<void> {
|
||||
expect(await this.verify(proof, publicSignals)).to.be.false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user