refactors and generic testing

This commit is contained in:
Erhan Tezcan
2023-04-04 00:13:55 +03:00
parent 7c8e4af15b
commit 3f1537f20f
22 changed files with 345 additions and 313 deletions

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
// auto-generated by instantiate.js
pragma circom 2.0.0;
include "../multiplier.circom";
component main = Multiplier(3);

View File

@@ -0,0 +1,6 @@
// auto-generated by instantiate.js
pragma circom 2.0.0;
include "../multiplier.circom";
component main = Multiplier(3);

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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