mirror of
https://github.com/erhant/circomkit.git
synced 2026-05-05 03:00:37 -04:00
refactors and generic testing
This commit is contained in:
23
README.md
23
README.md
@@ -2,12 +2,10 @@
|
||||
|
||||
> An opinionated Circom circuit development environment.
|
||||
|
||||
You can develop & test Circom circuits with ease using this repository. We have several example circuits to help guide you:
|
||||
You can develop & test Circom circuits with ease using this repository.
|
||||
|
||||
- **Multiplier**: Proves that you know the factors of a number.
|
||||
- **Floating Point Addition**: A floating-point addition circuit, as written in [Berkeley ZKP MOOC 2023- Lab 1](https://github.com/rdi-berkeley/zkp-mooc-lab).
|
||||
- **Fibonacci**: Calculate N'th Fibonacci number, has both recursive & iterative implementations.
|
||||
- **Sudoku**: Prove that you know the solution to a sudoku puzzle where the board size is a perfect square.
|
||||
- [x] CLI wrapper for Cirom and SnarkJS
|
||||
- [x] Generic & type-safe circuit tester
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -66,11 +64,11 @@ Use the [CLI](./scripts/cli.sh), or its wrapper scripts in [package.json](./pack
|
||||
|
||||
```bash
|
||||
# first argument is ALWAYS the circuit name
|
||||
yarn compile circuit-name
|
||||
yarn clean circuit-name
|
||||
yarn ptau circuit-name -n num-contribs -p phase1-ptau-path
|
||||
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
|
||||
```
|
||||
@@ -93,6 +91,15 @@ Within each test, there are two sub-tests:
|
||||
- **Witness Computation** will test whether witness computations are matching the expectations & the constraints hold.
|
||||
- **Proof Validation** will test whether proof generation & verification works correctly. This requires the **WASM file**, **prover key**, and **verification key** to be calculated beforehand.
|
||||
|
||||
## Examples
|
||||
|
||||
We have several example circuits to help guide you:
|
||||
|
||||
- **Multiplier**: Prove that you know the factors of a number.
|
||||
- **Floating Point Addition**: A floating-point addition circuit, as written in [Berkeley ZKP MOOC 2023 - Lab 1](https://github.com/rdi-berkeley/zkp-mooc-lab).
|
||||
- **Fibonacci**: Calculate N'th Fibonacci number, has both recursive & iterative implementations.
|
||||
- **Sudoku**: Prove that you know the solution to a sudoku puzzle where the board size is a perfect square.
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {Config} from './types/circuit';
|
||||
/**
|
||||
* @type {import("./types/circuit").Config}
|
||||
* A configuration object for circuit `main` components.
|
||||
*/
|
||||
const config = {
|
||||
const config: Config = {
|
||||
// multiplication of 3 numbers
|
||||
multiplier_3: {
|
||||
file: 'multiplier',
|
||||
@@ -53,4 +54,4 @@ const config = {
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
export default config as Readonly<typeof config>;
|
||||
@@ -6,7 +6,6 @@ template Fibonacci(n) {
|
||||
signal input in[2];
|
||||
signal output out;
|
||||
|
||||
// compute the sequence
|
||||
signal fib[n+1];
|
||||
fib[0] <== in[0];
|
||||
fib[1] <== in[1];
|
||||
|
||||
6
circuits/main/multiplier_3.circom
Normal file
6
circuits/main/multiplier_3.circom
Normal file
@@ -0,0 +1,6 @@
|
||||
// auto-generated by instantiate.js
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "../multiplier.circom";
|
||||
|
||||
component main = Multiplier(3);
|
||||
6
circuits/test/multiplier_3.circom
Normal file
6
circuits/test/multiplier_3.circom
Normal file
@@ -0,0 +1,6 @@
|
||||
// auto-generated by instantiate.js
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "../multiplier.circom";
|
||||
|
||||
component main = Multiplier(3);
|
||||
@@ -1,8 +1,3 @@
|
||||
/**
|
||||
* A label to be used in `console.time`
|
||||
*/
|
||||
export const WITNESS_COMP_LABEL = 'Witness computation';
|
||||
|
||||
/**
|
||||
* Order of the finite field used in Ethereum (BN_254)
|
||||
* If you have a number larger than this, you should take the modulus.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"test:all": "npx mocha",
|
||||
"test": "npx mocha --grep",
|
||||
"type": "./scripts/cli.sh -f type -c",
|
||||
"instantiate": "./scripts/cli.sh -f instantiate -c",
|
||||
"compile": "./scripts/cli.sh -f compile -c",
|
||||
"clean": "./scripts/cli.sh -f clean -c",
|
||||
"ptau": "./scripts/cli.sh -f ptau -c",
|
||||
@@ -17,6 +18,7 @@
|
||||
"author": "erhant",
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.4",
|
||||
"@types/ejs": "^3.1.2",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/node": "^18.11.18",
|
||||
"chai": "^4.3.7",
|
||||
|
||||
@@ -14,6 +14,7 @@ source ./scripts/functions/prove.sh
|
||||
source ./scripts/functions/verify.sh
|
||||
source ./scripts/functions/witness.sh
|
||||
source ./scripts/functions/compile.sh
|
||||
source ./scripts/functions/instantiate.sh
|
||||
|
||||
# get arguments
|
||||
NUM_CONTRIBS=1 # default value
|
||||
@@ -68,6 +69,9 @@ case $FUNC in
|
||||
compile)
|
||||
compile $CIRCUIT $COMPILE_DIR
|
||||
;;
|
||||
instantiate)
|
||||
instantiate $CIRCUIT $COMPILE_DIR
|
||||
;;
|
||||
type)
|
||||
type $CIRCUIT
|
||||
;;
|
||||
@@ -86,13 +90,14 @@ case $FUNC in
|
||||
*)
|
||||
echo "Usage:"
|
||||
echo " -f <function>"
|
||||
echo " clean Cleans the build artifacts"
|
||||
echo " compile Compile the circuit"
|
||||
echo " type Generate types for TypeScript"
|
||||
echo " ptau 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 " clean Cleans the build artifacts"
|
||||
echo " compile Compile the circuit"
|
||||
echo " instantiate Instantiate the main component"
|
||||
echo " type Generate types for TypeScript"
|
||||
echo " ptau 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 " -c <circuit-name>"
|
||||
echo " -d <directory-name>"
|
||||
echo " -n <num-contributions> (default: 1)"
|
||||
|
||||
@@ -5,10 +5,6 @@ compile() {
|
||||
local DIR=$2
|
||||
local CIRCOM_IN=./circuits/$DIR/$CIRCUIT.circom
|
||||
local CIRCOM_OUT=./build/$CIRCUIT
|
||||
|
||||
# generate the circuit main component
|
||||
mkdir -p ./circuits/$DIR
|
||||
node ./scripts/instantiate.js $CIRCUIT $DIR
|
||||
|
||||
# create build dir if not exists already
|
||||
mkdir -p $CIRCOM_OUT
|
||||
|
||||
12
scripts/functions/instantiate.sh
Executable file
12
scripts/functions/instantiate.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
## Instantiate the main component
|
||||
instantiate() {
|
||||
echo -e "\n${CLIENV_COLOR_TITLE}=== Creating main component ===${CLIENV_COLOR_RESET}"
|
||||
local CIRCUIT=$1
|
||||
local DIR=$2
|
||||
|
||||
# generate the circuit main component
|
||||
mkdir -p ./circuits/$DIR
|
||||
npx ts-node ./utils/instantiate.ts $CIRCUIT $DIR
|
||||
|
||||
echo -e "${CLIENV_COLOR_LOG}Done!${CLIENV_COLOR_RESET}"
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
const ejs = require('ejs');
|
||||
const {writeFileSync, readFileSync} = require('fs');
|
||||
const config = require('../circuit.config.cjs');
|
||||
|
||||
// read circuit from config
|
||||
const target = process.argv[2];
|
||||
const dir = process.argv[3];
|
||||
if (!(target in config)) {
|
||||
throw new Error(`Target ${target} not found in config.`);
|
||||
}
|
||||
|
||||
// generate the main component code
|
||||
const ejsPath = './circuits/ejs/template.circom';
|
||||
let circuit = ejs.render(readFileSync(ejsPath).toString(), config[target]);
|
||||
|
||||
// output to file
|
||||
const targetPath = `./circuits/${dir}/${target}.circom`;
|
||||
writeFileSync(targetPath, circuit);
|
||||
console.log(`Main component created at: ${targetPath}\n`);
|
||||
@@ -1,24 +1,22 @@
|
||||
import {createWasmTester} from '../utils/wasmTester';
|
||||
import type {CircuitSignals} from '../types/circuit';
|
||||
|
||||
describe('fibonacci_11', () => {
|
||||
const INPUT: CircuitSignals = {
|
||||
in: [1, 1],
|
||||
};
|
||||
|
||||
let circuit: Awaited<ReturnType<typeof createWasmTester>>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await createWasmTester('fibonacci_11');
|
||||
await circuit.printConstraintCount();
|
||||
});
|
||||
|
||||
it('should compute correctly', async () => {
|
||||
const witness = await circuit.calculateWitness(INPUT, true);
|
||||
await circuit.checkConstraints(witness);
|
||||
const output = {
|
||||
out: fibonacci(INPUT.in, 11),
|
||||
};
|
||||
await circuit.assertOut(witness, output);
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
in: [1, 1],
|
||||
},
|
||||
{
|
||||
out: fibonacci([1, 1], 11),
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,147 +1,100 @@
|
||||
import {createWasmTester, printConstraintCount} from '../utils/wasmTester';
|
||||
import {assert, expect} from 'chai';
|
||||
import {createWasmTester} from '../utils/wasmTester';
|
||||
|
||||
// tests adapted from https://github.com/rdi-berkeley/zkp-mooc-lab
|
||||
|
||||
describe('fp32', () => {
|
||||
let circuit: Awaited<ReturnType<typeof createWasmTester>>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await createWasmTester('fp32');
|
||||
await circuit.loadConstraints();
|
||||
await printConstraintCount(circuit, 401);
|
||||
await circuit.printConstraintCount(401);
|
||||
});
|
||||
|
||||
it('case I test', async () => {
|
||||
const witness = await circuit.calculateWitness(
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
e: ['43', '5'],
|
||||
m: ['11672136', '10566265'],
|
||||
},
|
||||
true
|
||||
{e_out: '43', m_out: '11672136'}
|
||||
);
|
||||
await circuit.checkConstraints(witness);
|
||||
await circuit.assertOut(witness, {e_out: '43', m_out: '11672136'});
|
||||
});
|
||||
|
||||
it('case II test 1', async () => {
|
||||
const witness = await circuit.calculateWitness(
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
e: ['104', '106'],
|
||||
m: ['12444445', '14159003'],
|
||||
},
|
||||
true
|
||||
{e_out: '107', m_out: '8635057'}
|
||||
);
|
||||
await circuit.checkConstraints(witness);
|
||||
await circuit.assertOut(witness, {e_out: '107', m_out: '8635057'});
|
||||
});
|
||||
|
||||
it('case II test 2', async () => {
|
||||
const witness = await circuit.calculateWitness(
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
e: ['176', '152'],
|
||||
m: ['16777215', '16777215'],
|
||||
},
|
||||
true
|
||||
{e_out: '177', m_out: '8388608'}
|
||||
);
|
||||
await circuit.checkConstraints(witness);
|
||||
await circuit.assertOut(witness, {e_out: '177', m_out: '8388608'});
|
||||
});
|
||||
|
||||
it('case II test 3', async () => {
|
||||
const witness = await circuit.calculateWitness(
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
e: ['142', '142'],
|
||||
m: ['13291872', '13291872'],
|
||||
},
|
||||
true
|
||||
{e_out: '143', m_out: '13291872'}
|
||||
);
|
||||
await circuit.checkConstraints(witness);
|
||||
await circuit.assertOut(witness, {e_out: '143', m_out: '13291872'});
|
||||
});
|
||||
|
||||
it('one input zero test', async () => {
|
||||
const witness = await circuit.calculateWitness(
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
e: ['0', '43'],
|
||||
m: ['0', '10566265'],
|
||||
},
|
||||
true
|
||||
{e_out: '43', m_out: '10566265'}
|
||||
);
|
||||
await circuit.checkConstraints(witness);
|
||||
await circuit.assertOut(witness, {e_out: '43', m_out: '10566265'});
|
||||
});
|
||||
|
||||
it('both inputs zero test', async () => {
|
||||
const witness = await circuit.calculateWitness(
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
e: ['0', '0'],
|
||||
m: ['0', '0'],
|
||||
},
|
||||
true
|
||||
{e_out: '0', m_out: '0'}
|
||||
);
|
||||
await circuit.checkConstraints(witness);
|
||||
await circuit.assertOut(witness, {e_out: '0', m_out: '0'});
|
||||
});
|
||||
|
||||
it('should fail - exponent zero but mantissa non-zero', async () => {
|
||||
await circuit
|
||||
.calculateWitness(
|
||||
{
|
||||
e: ['0', '0'],
|
||||
m: ['0', '10566265'],
|
||||
},
|
||||
true
|
||||
)
|
||||
.then(
|
||||
() => assert.fail(),
|
||||
err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.')
|
||||
);
|
||||
await circuit.expectFailedAssert({
|
||||
e: ['0', '0'],
|
||||
m: ['0', '10566265'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail - mantissa >= 2^{p+1}', async () => {
|
||||
await circuit
|
||||
.calculateWitness(
|
||||
{
|
||||
e: ['0', '43'],
|
||||
m: ['0', '16777216'],
|
||||
},
|
||||
true
|
||||
)
|
||||
.then(
|
||||
() => assert.fail(),
|
||||
err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.')
|
||||
);
|
||||
await circuit.expectFailedAssert({
|
||||
e: ['0', '43'],
|
||||
m: ['0', '16777216'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail - mantissa < 2^{p}', async () => {
|
||||
await circuit
|
||||
.calculateWitness(
|
||||
{
|
||||
e: ['0', '43'],
|
||||
m: ['0', '6777216'],
|
||||
},
|
||||
true
|
||||
)
|
||||
.then(
|
||||
() => assert.fail(),
|
||||
err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.')
|
||||
);
|
||||
await circuit.expectFailedAssert({
|
||||
e: ['0', '43'],
|
||||
m: ['0', '6777216'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail - exponent >= 2^k', async () => {
|
||||
await circuit
|
||||
.calculateWitness(
|
||||
{
|
||||
e: ['0', '256'],
|
||||
m: ['0', '10566265'],
|
||||
},
|
||||
true
|
||||
)
|
||||
.then(
|
||||
() => assert.fail(),
|
||||
err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.')
|
||||
);
|
||||
await circuit.expectFailedAssert({
|
||||
e: ['0', '256'],
|
||||
m: ['0', '10566265'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -150,109 +103,80 @@ describe('fp64', () => {
|
||||
|
||||
before(async () => {
|
||||
circuit = await createWasmTester('fp64');
|
||||
await circuit.loadConstraints();
|
||||
await printConstraintCount(circuit, 819);
|
||||
await circuit.printConstraintCount(819);
|
||||
});
|
||||
|
||||
it('case I test', async () => {
|
||||
const witness = await circuit.calculateWitness(
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
e: ['1122', '1024'],
|
||||
m: ['7807742059002284', '7045130465601185'],
|
||||
},
|
||||
true
|
||||
{e_out: '1122', m_out: '7807742059002284'}
|
||||
);
|
||||
await circuit.checkConstraints(witness);
|
||||
await circuit.assertOut(witness, {e_out: '1122', m_out: '7807742059002284'});
|
||||
});
|
||||
|
||||
it('case II test 1', async () => {
|
||||
const witness = await circuit.calculateWitness(
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
e: ['1056', '1053'],
|
||||
m: ['8879495032259305', '5030141535601637'],
|
||||
},
|
||||
true
|
||||
{e_out: '1057', m_out: '4754131362104755'}
|
||||
);
|
||||
await circuit.checkConstraints(witness);
|
||||
await circuit.assertOut(witness, {e_out: '1057', m_out: '4754131362104755'});
|
||||
});
|
||||
|
||||
it('case II test 2', async () => {
|
||||
const witness = await circuit.calculateWitness(
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
e: ['1035', '982'],
|
||||
m: ['4804509148660890', '8505192799372177'],
|
||||
},
|
||||
true
|
||||
{e_out: '1035', m_out: '4804509148660891'}
|
||||
);
|
||||
await circuit.checkConstraints(witness);
|
||||
await circuit.assertOut(witness, {e_out: '1035', m_out: '4804509148660891'});
|
||||
});
|
||||
|
||||
it('case II test 3', async () => {
|
||||
const witness = await circuit.calculateWitness(
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
e: ['982', '982'],
|
||||
m: ['8505192799372177', '8505192799372177'],
|
||||
},
|
||||
true
|
||||
{e_out: '983', m_out: '8505192799372177'}
|
||||
);
|
||||
await circuit.checkConstraints(witness);
|
||||
await circuit.assertOut(witness, {e_out: '983', m_out: '8505192799372177'});
|
||||
});
|
||||
|
||||
it('one input zero test', async () => {
|
||||
const witness = await circuit.calculateWitness(
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
e: ['0', '982'],
|
||||
m: ['0', '8505192799372177'],
|
||||
},
|
||||
true
|
||||
{e_out: '982', m_out: '8505192799372177'}
|
||||
);
|
||||
await circuit.checkConstraints(witness);
|
||||
await circuit.assertOut(witness, {e_out: '982', m_out: '8505192799372177'});
|
||||
});
|
||||
|
||||
it('both inputs zero test', async () => {
|
||||
const witness = await circuit.calculateWitness(
|
||||
await circuit.expectCorrectAssert(
|
||||
{
|
||||
e: ['0', '0'],
|
||||
m: ['0', '0'],
|
||||
},
|
||||
true
|
||||
{e_out: '0', m_out: '0'}
|
||||
);
|
||||
await circuit.checkConstraints(witness);
|
||||
await circuit.assertOut(witness, {e_out: '0', m_out: '0'});
|
||||
});
|
||||
|
||||
it('should fail - exponent zero but mantissa non-zero', async () => {
|
||||
await circuit
|
||||
.calculateWitness(
|
||||
{
|
||||
e: ['0', '0'],
|
||||
m: ['0', '8505192799372177'],
|
||||
},
|
||||
true
|
||||
)
|
||||
.then(
|
||||
() => assert.fail(),
|
||||
err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.')
|
||||
);
|
||||
await circuit.expectFailedAssert({
|
||||
e: ['0', '0'],
|
||||
m: ['0', '8505192799372177'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail - mantissa < 2^{p}', async () => {
|
||||
await circuit
|
||||
.calculateWitness(
|
||||
{
|
||||
e: ['0', '43'],
|
||||
m: ['0', '16777216'],
|
||||
},
|
||||
true
|
||||
)
|
||||
.then(
|
||||
() => assert.fail(),
|
||||
err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.')
|
||||
);
|
||||
await circuit.expectFailedAssert({
|
||||
e: ['0', '43'],
|
||||
m: ['0', '16777216'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,31 +2,34 @@ import {createWasmTester} from '../utils/wasmTester';
|
||||
import {ProofTester} from '../utils/proofTester';
|
||||
import type {CircuitSignals, FullProof} from '../types/circuit';
|
||||
import {assert, expect} from 'chai';
|
||||
import {instantiate, clearInstance} from '../utils/instantiate';
|
||||
// read inputs from file
|
||||
import input80 from '../inputs/multiplier3/80.json';
|
||||
import input80 from '../inputs/multiplier_3/80.json';
|
||||
|
||||
const CIRCUIT_NAME = 'multiplier3';
|
||||
describe(CIRCUIT_NAME, () => {
|
||||
describe('multiplier_3', () => {
|
||||
const INPUT: CircuitSignals = input80;
|
||||
|
||||
let circuit: Awaited<ReturnType<typeof createWasmTester>>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await createWasmTester(CIRCUIT_NAME);
|
||||
instantiate('multiplier_3', 'test', {
|
||||
file: 'multiplier',
|
||||
template: 'Multiplier',
|
||||
publicInputs: [],
|
||||
templateParams: [3],
|
||||
});
|
||||
circuit = await createWasmTester('multiplier_3', 'test');
|
||||
await circuit.printConstraintCount(2); // N - 1
|
||||
});
|
||||
|
||||
after(() => {
|
||||
clearInstance('multiplier_3', 'test');
|
||||
});
|
||||
|
||||
it('should compute correctly', async () => {
|
||||
// compute witness
|
||||
const witness = await circuit.calculateWitness(INPUT, true);
|
||||
|
||||
// witness should have valid constraints
|
||||
await circuit.checkConstraints(witness);
|
||||
|
||||
// witness should have correct output
|
||||
const output = {
|
||||
await circuit.expectCorrectAssert(INPUT, {
|
||||
out: BigInt(INPUT.in.reduce((prev: bigint, acc: bigint) => acc * prev)),
|
||||
};
|
||||
await circuit.assertOut(witness, output);
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT compute with wrong number of inputs', async () => {
|
||||
@@ -45,13 +48,14 @@ describe(CIRCUIT_NAME, () => {
|
||||
});
|
||||
|
||||
// you can also test prover & verifier functions using the actual build files!
|
||||
describe('multiplier3 (proofs)', () => {
|
||||
describe.skip('multiplier_3 (proofs)', () => {
|
||||
const INPUT: CircuitSignals = input80;
|
||||
|
||||
let fullProof: FullProof;
|
||||
const circuit = new ProofTester('multiplier3');
|
||||
let circuit: ProofTester;
|
||||
|
||||
before(async () => {
|
||||
circuit = new ProofTester('multiplier_3');
|
||||
fullProof = await circuit.prove(INPUT);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {createWasmTester} from '../utils/wasmTester';
|
||||
import {assert, expect} from 'chai';
|
||||
|
||||
const INPUTS = {
|
||||
sudoku_9x9: {
|
||||
@@ -46,7 +45,6 @@ const INPUTS = {
|
||||
describe(circuitName, () => {
|
||||
// @ts-ignore
|
||||
const INPUT = INPUTS[circuitName];
|
||||
|
||||
let circuit: Awaited<ReturnType<typeof createWasmTester>>;
|
||||
|
||||
before(async () => {
|
||||
@@ -54,58 +52,37 @@ const INPUTS = {
|
||||
});
|
||||
|
||||
it('should compute correctly', async () => {
|
||||
const witness = await circuit.calculateWitness(INPUT, true);
|
||||
await circuit.checkConstraints(witness);
|
||||
await circuit.expectCorrectAssert(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.calculateWitness(badInput, true).then(
|
||||
() => assert.fail(),
|
||||
err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.')
|
||||
);
|
||||
await circuit.expectFailedAssert(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.calculateWitness(badInput, true).then(
|
||||
() => assert.fail(),
|
||||
err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.')
|
||||
);
|
||||
await circuit.expectFailedAssert(badInput);
|
||||
});
|
||||
|
||||
it('should NOT accept non-distinct square', async () => {
|
||||
const badInput: typeof INPUT = JSON.parse(JSON.stringify(INPUT));
|
||||
|
||||
const badInput = JSON.parse(JSON.stringify(INPUT));
|
||||
badInput.solution[0][0] = badInput.solution[1][1];
|
||||
await circuit.calculateWitness(badInput, true).then(
|
||||
() => assert.fail(),
|
||||
err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.')
|
||||
);
|
||||
await circuit.expectFailedAssert(badInput);
|
||||
});
|
||||
|
||||
it('should NOT accept empty value in solution', async () => {
|
||||
const badInput = JSON.parse(JSON.stringify(INPUT));
|
||||
|
||||
badInput.solution[0][0] = 0;
|
||||
await circuit.calculateWitness(badInput, true).then(
|
||||
() => assert.fail(),
|
||||
err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.')
|
||||
);
|
||||
await circuit.expectFailedAssert(badInput);
|
||||
});
|
||||
|
||||
it('should NOT accept out-of-range values', async () => {
|
||||
const badInput = JSON.parse(JSON.stringify(INPUT));
|
||||
|
||||
badInput.solution[0][0] = 99999;
|
||||
await circuit.calculateWitness(badInput, true).then(
|
||||
() => assert.fail(),
|
||||
err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.')
|
||||
);
|
||||
await circuit.expectFailedAssert(badInput);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
|
||||
@@ -19,35 +19,40 @@ export type FullProof = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Configuration file for your circuits.
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* An array of public input signal names
|
||||
*/
|
||||
publicInputs: string[];
|
||||
|
||||
/**
|
||||
* An array of template parameters
|
||||
*/
|
||||
templateParams: (number | bigint)[];
|
||||
|
||||
/**
|
||||
* Directory to output under `circuits`, defaults to `main`
|
||||
* @depracated work in progress, use `main` for now (leave empty)
|
||||
*/
|
||||
dir?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Configurations for your circuits.
|
||||
* @see `circuit.config.cjs` in the project root.
|
||||
*/
|
||||
export type Config = {
|
||||
[circuitName: string]: {
|
||||
/**
|
||||
* File to read the template from
|
||||
*/
|
||||
file: string;
|
||||
|
||||
/**
|
||||
* The template name to instantiate
|
||||
*/
|
||||
template: string;
|
||||
|
||||
/**
|
||||
* An array of public input signal names
|
||||
*/
|
||||
publicInputs: string[];
|
||||
|
||||
/**
|
||||
* An array of template parameters
|
||||
*/
|
||||
templateParams: (number | bigint)[];
|
||||
|
||||
/**
|
||||
* Directory to output under `circuits`, defaults to `main`
|
||||
* @depracated work in progress, use `main` for now (leave empty)
|
||||
*/
|
||||
dir?: string;
|
||||
};
|
||||
[circuitName: string]: CircuitConfig;
|
||||
};
|
||||
|
||||
18
types/wasmTester.ts
Normal file
18
types/wasmTester.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {WitnessType, CircuitSignals} from './circuit';
|
||||
|
||||
/**
|
||||
* A simple type-wrapper for `circom_tester` WASM tester class.
|
||||
* Not all functions may exist here, some are omitted.
|
||||
* @see https://github.com/iden3/circom_tester/blob/main/wasm/tester.js
|
||||
*/
|
||||
export type CircomWasmTester = {
|
||||
checkConstraints: (witness: WitnessType) => Promise<void>;
|
||||
release: () => Promise<void>;
|
||||
assertOut: (actualOut: CircuitSignals, expectedOut: CircuitSignals) => Promise<void>;
|
||||
calculateWitness: (input: CircuitSignals, sanityCheck: boolean) => Promise<WitnessType>;
|
||||
loadConstraints: () => Promise<void>;
|
||||
constraints: any[] | undefined;
|
||||
loadSymbols: () => Promise<void>;
|
||||
symbols: object | undefined;
|
||||
getDecoratedOutput: (witness: WitnessType) => Promise<string>;
|
||||
};
|
||||
36
utils/instantiate.ts
Normal file
36
utils/instantiate.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import ejs from 'ejs';
|
||||
import {writeFileSync, readFileSync} from 'fs';
|
||||
import config from '../circuit.config';
|
||||
import {CircuitConfig} from '../types/circuit';
|
||||
|
||||
/**
|
||||
* Programmatically generate the `main` component
|
||||
*/
|
||||
export function instantiate(name: string, directory: string, circuitConfig?: CircuitConfig) {
|
||||
if (circuitConfig == undefined) {
|
||||
if (!(name in config)) {
|
||||
throw new Error(`Target ${name} not found in circuit.config.cjs`);
|
||||
}
|
||||
circuitConfig = config[name];
|
||||
}
|
||||
|
||||
// generate the main component code
|
||||
const ejsPath = './circuits/ejs/template.circom';
|
||||
let circuit = ejs.render(readFileSync(ejsPath).toString(), circuitConfig);
|
||||
|
||||
// output to file
|
||||
const targetDir = directory || 'main';
|
||||
const targetPath = `./circuits/${targetDir}/${name}.circom`;
|
||||
writeFileSync(targetPath, circuit);
|
||||
console.log(`Main component created at: ${targetPath}\n`);
|
||||
}
|
||||
|
||||
export function clearInstance(name: string, directory: string) {
|
||||
// TODO: remove the file
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
const name = process.argv[2];
|
||||
const directory = process.argv[3];
|
||||
instantiate(name, directory);
|
||||
}
|
||||
@@ -1,46 +1,65 @@
|
||||
const wasm_tester = require('circom_tester').wasm;
|
||||
import {WitnessType, CircuitSignals} from '../types/circuit';
|
||||
import {CircomWasmTester} from '../types/wasmTester';
|
||||
import {assert, expect} from 'chai';
|
||||
|
||||
/**
|
||||
* Custom types added with respect to `circomlibjs.wasm`. Not all functions exist here, some are omitted.
|
||||
* @see https://github.com/iden3/circom_tester/blob/main/wasm/tester.js
|
||||
A utility class to test your circuits.
|
||||
- Use `expectFailedAssert` and `expectCorrectAssert` to test out evaluations
|
||||
*/
|
||||
type WasmTester = {
|
||||
class WasmTester {
|
||||
/**
|
||||
* The underlying `circom_tester` object
|
||||
*/
|
||||
readonly circomWasmTester: CircomWasmTester;
|
||||
|
||||
/**
|
||||
* A dictionary of symbols
|
||||
*/
|
||||
symbols: object | undefined;
|
||||
|
||||
/**
|
||||
* List of constraints, must call `loadConstraints` before accessing this key
|
||||
*/
|
||||
constraints: any[] | undefined;
|
||||
|
||||
constructor(circomWasmTester: CircomWasmTester) {
|
||||
this.circomWasmTester = circomWasmTester;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that constraints are valid.
|
||||
* @param witness witness
|
||||
*/
|
||||
checkConstraints: (witness: WitnessType) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Cleanup directory, should probably be called upon test completion
|
||||
* @deprecated this is buggy right now
|
||||
*/
|
||||
release(): Promise<void>;
|
||||
checkConstraints(witness: WitnessType): Promise<void> {
|
||||
return this.circomWasmTester.checkConstraints(witness);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the output of a given witness.
|
||||
* @param actualOut expected output signals
|
||||
* @param expectedOut computed output signals
|
||||
*/
|
||||
assertOut: (actualOut: CircuitSignals, expectedOut: CircuitSignals) => Promise<void>;
|
||||
assertOut(actualOut: CircuitSignals, expectedOut: CircuitSignals): Promise<void> {
|
||||
return this.circomWasmTester.assertOut(actualOut, expectedOut);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute witness given the input signals.
|
||||
* @param input all signals, private and public.
|
||||
* @param sanityCheck check if input signals are sanitized
|
||||
*/
|
||||
calculateWitness: (input: CircuitSignals, sanityCheck: boolean) => Promise<WitnessType>;
|
||||
calculateWitness(input: CircuitSignals, sanityCheck: boolean): Promise<WitnessType> {
|
||||
return this.circomWasmTester.calculateWitness(input, sanityCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of R1CS constraints to `this.constraints`
|
||||
*/
|
||||
loadConstraints(): Promise<void>;
|
||||
|
||||
/**
|
||||
* List of constraints, must call `loadConstraints` before accessing this key
|
||||
*/
|
||||
constraints: any[] | undefined;
|
||||
async loadConstraints(): Promise<void> {
|
||||
await this.circomWasmTester.loadConstraints();
|
||||
this.constraints = this.circomWasmTester.constraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the symbols in a dictionary at `this.symbols`
|
||||
@@ -50,19 +69,80 @@ type WasmTester = {
|
||||
* 1: variable index
|
||||
* 2: component index
|
||||
*/
|
||||
loadSymbols(): Promise<void>;
|
||||
|
||||
/**
|
||||
* A dictionary of symbols
|
||||
*/
|
||||
symbols: object;
|
||||
async loadSymbols(): Promise<void> {
|
||||
await this.circomWasmTester.loadSymbols();
|
||||
this.symbols = this.circomWasmTester.symbols;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated this is buggy right now
|
||||
* @param witness witness
|
||||
*/
|
||||
getDecoratedOutput(witness: WitnessType): Promise<string>;
|
||||
};
|
||||
getDecoratedOutput(witness: WitnessType): Promise<string> {
|
||||
return this.circomWasmTester.getDecoratedOutput(witness);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup directory, should probably be called upon test completion
|
||||
* @deprecated this is buggy right now
|
||||
*/
|
||||
release(): Promise<void> {
|
||||
return this.circomWasmTester.release();
|
||||
}
|
||||
|
||||
//////// CUSTOM ADDITIONS /////////
|
||||
|
||||
/**
|
||||
* Prints the number of constraints of the circuit.
|
||||
* If expected count is provided, will also include that in the log.
|
||||
* @param circuit WasmTester circuit
|
||||
* @param expected expected number of constraints
|
||||
*/
|
||||
async printConstraintCount(expected?: number) {
|
||||
// load constraints
|
||||
if (this.constraints == undefined) {
|
||||
await this.loadConstraints();
|
||||
}
|
||||
const numConstraints = this.constraints!.length;
|
||||
let expectionMessage = '';
|
||||
if (expected !== undefined) {
|
||||
let alertType = '';
|
||||
if (numConstraints < expected) {
|
||||
alertType = '🔴';
|
||||
} else if (numConstraints > expected) {
|
||||
alertType = '🟡';
|
||||
} else {
|
||||
alertType = '🟢';
|
||||
}
|
||||
expectionMessage = ` (${alertType} expected ${expected})`;
|
||||
}
|
||||
console.log(`#constraints: ${numConstraints}` + expectionMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expect an input to fail an assertion in the circuit.
|
||||
* @param input bad input
|
||||
*/
|
||||
async expectFailedAssert(input: CircuitSignals) {
|
||||
await this.calculateWitness(input, true).then(
|
||||
() => assert.fail(),
|
||||
err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expect an input to pass assertions and match the output.
|
||||
* @param input correct input
|
||||
* @param output expected output, if `undefined` it will only check constraints
|
||||
*/
|
||||
async expectCorrectAssert(input: CircuitSignals, output?: CircuitSignals) {
|
||||
const witness = await this.calculateWitness(input, true);
|
||||
await this.checkConstraints(witness);
|
||||
if (output) {
|
||||
await this.assertOut(witness, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles and reutrns a circuit via `circom_tester`'s `wasm_tester`.
|
||||
@@ -72,34 +152,8 @@ type WasmTester = {
|
||||
* @returns a `wasm_tester` object
|
||||
*/
|
||||
export async function createWasmTester(circuitName: string, dir: string = 'main'): Promise<WasmTester> {
|
||||
return wasm_tester(`./circuits/${dir}/${circuitName}.circom`, {
|
||||
const circomWasmTester: CircomWasmTester = await wasm_tester(`./circuits/${dir}/${circuitName}.circom`, {
|
||||
include: 'node_modules', // will link circomlib circuits
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the number of constraints of the circuit.
|
||||
* If expected count is provided, will also include that in the log.
|
||||
* @param circuit WasmTester circuit
|
||||
* @param expected expected number of constraints
|
||||
*/
|
||||
export async function printConstraintCount(circuit: WasmTester, expected?: number) {
|
||||
// load constraints
|
||||
if (!circuit.constraints) {
|
||||
await circuit.loadConstraints();
|
||||
}
|
||||
const numConstraints = circuit.constraints!.length;
|
||||
let expectionMessage = '';
|
||||
if (expected !== undefined) {
|
||||
let alertType = '';
|
||||
if (numConstraints < expected) {
|
||||
alertType = '🔴';
|
||||
} else if (numConstraints > expected) {
|
||||
alertType = '🟡';
|
||||
} else {
|
||||
alertType = '🟢';
|
||||
}
|
||||
expectionMessage = ` (${alertType} expected ${expected})`;
|
||||
}
|
||||
console.log(`#constraints: ${numConstraints}` + expectionMessage);
|
||||
return new WasmTester(circomWasmTester);
|
||||
}
|
||||
|
||||
@@ -485,6 +485,11 @@
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user