Skip to content

Commit

Permalink
Added custom circuit config and optional data args (#29)
Browse files Browse the repository at this point in the history
* added custom circuit config and optional data args

* added docs
  • Loading branch information
erhant authored Aug 30, 2023
1 parent 93b9408 commit b11689a
Show file tree
Hide file tree
Showing 13 changed files with 580 additions and 395 deletions.
30 changes: 0 additions & 30 deletions .github/workflows/build.yml

This file was deleted.

9 changes: 9 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,14 @@ jobs:
- name: Install dependencies
run: yarn

- name: Format code
run: yarn format

- name: Lint code
run: yarn lint

- name: Build everything
run: yarn build

- name: Run tests
run: yarn test
26 changes: 10 additions & 16 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,31 @@
# Change Log
# Changelog

All notable changes to this project will be documented in this file.
## [0.0.17] - TBD

The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
- Merged `build` and `test` workflows.
- Added optional Circuit Config parameter to `compile` method, allowing one to compile without having the circuit config at `circuits.json`.
- Added tests for the opitonal argument.
- Added optional input data arguments to `prove` and `witness`, allowing one to provide data without having it at `inputs` folder.
- Tidy up the changelog.
- Updated `gts` for development

## [0.0.16] - 2023-18-17

- Quickfix: `compile` failed when there is no `build` folder.

## [0.0.15] - 2023-18-17

### Added

- `compile` now calls Circom subprocess instead of using WASM tester. This provides better control over cmdline args, e.g. we can call `--inspect` and `--O2` and such. [#20](https://github.com/erhant/circomkit/issues/20)
- Added `inspect` option to config, defaulting to `true`.
- Added `--O2` and `--O2round` optimization levels by allowing `optimization` to be any number. If optimization is greater than 2, it corresponds to `--O2round` with the max round number as optimization level.

## [0.0.14] - 2023-18-15

### Added

- Teardown script to terminate SnarkJS which makes tests hang indefinitely otherwise.
- `readWitness` added to `WitnessTester`.
- `readWitnessSignals` added to `WitnessTester`.
- `compute` function parameter typing fixed. It now allows any symbol.

### Changed

- Default optimization level is now 1.

### Fixed

- `compute` function parameter typing fixed, it now allows any symbol.
- Default optimization level is now changed to 1 from 0.
- Fixed missing `WitnessTester` import in outputs of `npx circomkit init`.

## [0.0.13] - 2023-18-15
Expand Down
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
<a href="https://www.npmjs.com/package/circomkit" target="_blank">
<img alt="NPM" src="https://img.shields.io/npm/v/circomkit?logo=npm&color=CB3837">
</a>
<a href="./.github/workflows/build.yml" target="_blank">
<img alt="Workflow: Build" src="https://github.com/erhant/circomkit/actions/workflows/build.yml/badge.svg?branch=main">
</a>
<a href="./.github/workflows/tests.yml" target="_blank">
<img alt="Workflow: Tests" src="https://github.com/erhant/circomkit/actions/workflows/tests.yml/badge.svg?branch=main">
</a>
Expand Down Expand Up @@ -121,22 +118,28 @@ You can omit `pubs` and `params` options, they default to `[]`.

### Using Circomkit in Code

All CLI commands other than `init` can be used with the same name and arguments within Circomkit.
All CLI commands other than `init` can be used with the same name and arguments within Circomkit. Furthermore, you can provide configuration & inputs directly, instead of letting Circomkit read from `circuits.json` or from within the `inputs` folder.

```ts
import {Circomkit} from 'circomkit';

const circomkit = new Circomkit({
/* custom configurations */
// custom configurations
protocol: 'plonk',
});

circomkit.instantiate('multiplier_3', {
// artifacts output at `build/multiplier_3` directory
await circomkit.compile('multiplier_3', {
file: 'multiplier',
template: 'Multiplier',
params: [3],
});
await circomkit.compile('multiplier_3');

// proof & public signals at `build/multiplier_3/my_input` directory
await circomkit.prove('multiplier_3', 'my_input', [3, 5, 7]);

// verify with proof & public signals at `build/multiplier_3/my_input`
await circomkit.verify('multiplier_3', 'my_input');
```

## Writing Tests
Expand Down
17 changes: 17 additions & 0 deletions circuits/fibonacci/recursive.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
pragma circom 2.0.0;

// Fibonacci with custom starting numbers, recursive & inefficient
template Fibonacci(n) {
signal input in[2];
signal output out;
component f1, f2;
if (n <= 1) {
out <== in[n];
} else {
f1 = Fibonacci(n-1);
f1.in <== in;
f2 = Fibonacci(n-2);
f2.in <== in;
out <== f1.out + f2.out;
}
}
17 changes: 17 additions & 0 deletions circuits/fibonacci/vanilla.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
pragma circom 2.0.0;

// Fibonacci with custom starting numbers
template Fibonacci(n) {
assert(n >= 2);
signal input in[2];
signal output out;

signal fib[n+1];
fib[0] <== in[0];
fib[1] <== in[1];
for (var i = 2; i <= n; i++) {
fib[i] <== fib[i-2] + fib[i-1];
}

out <== fib[n];
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "circomkit",
"version": "0.0.16",
"version": "0.0.17",
"description": "A Circom development environment",
"author": "erhant",
"license": "MIT",
Expand Down Expand Up @@ -40,7 +40,7 @@
"@types/mocha": "^10.0.1",
"@types/mocha-each": "^2.0.0",
"@types/node": "^18.11.18",
"gts": "^3.1.1",
"gts": "^5.0.1",
"mocha": "^10.2.0",
"mocha-each": "^2.0.1",
"rimraf": "^5.0.1",
Expand Down
31 changes: 17 additions & 14 deletions src/circomkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,15 @@ export class Circomkit {
}

/** Compile the circuit.
*
* A circuit configuration can be passed optionally; if not, the
* config will be read from `circuits.json` at the working directory.
*
* @returns path of the build directory
*/
async compile(circuit: string) {
// instantiate main component
const targetPath = this.path(circuit, 'target');
const path = this.instantiate(circuit);
this.log('Main component created at: ' + path, 'debug');
async compile(circuit: string, config?: CircuitConfig) {
const targetPath = this.instantiate(circuit, config);
this.log('Main component created at: ' + targetPath, 'debug');

const outDir = this.path(circuit, 'dir');
mkdirSync(outDir, {recursive: true});
Expand Down Expand Up @@ -302,9 +304,12 @@ export class Circomkit {
}

/** Generate a proof.
*
* If `data` is not passed, the input data will be read from `inputs/<circuit>/<input>.json`.
*
* @returns path of the directory where public signals and proof are created
*/
async prove(circuit: string, input: string): Promise<string> {
async prove(circuit: string, input: string, data?: CircuitSignals): Promise<string> {
// create WASM if needed
const wasmPath = this.path(circuit, 'wasm');
if (!existsSync(wasmPath)) {
Expand All @@ -319,12 +324,7 @@ export class Circomkit {
await this.setup(circuit);
}

// check input path
const inputPath = this.pathWithInput(circuit, input, 'in');
if (!existsSync(inputPath)) {
throw new Error('Input does not exist at: ' + inputPath);
}
const jsonInput = JSON.parse(readFileSync(inputPath, 'utf-8'));
const jsonInput = data || JSON.parse(readFileSync(this.pathWithInput(circuit, input, 'in'), 'utf-8'));

const {proof, publicSignals} = await snarkjs[this.config.protocol].fullProve(
jsonInput,
Expand Down Expand Up @@ -432,13 +432,16 @@ export class Circomkit {
}

/** Calculates the witness for the given circuit and input.
*
* If `data` is not passed, the input data will be read from `inputs/<circuit>/<input>.json`.
*
* @returns path of the created witness
*/
async witness(circuit: string, input: string): Promise<string> {
async witness(circuit: string, input: string, data?: CircuitSignals): Promise<string> {
const wasmPath = this.path(circuit, 'wasm');
const wtnsPath = this.pathWithInput(circuit, input, 'wtns');
const outDir = this.pathWithInput(circuit, input, 'dir');
const jsonInput = JSON.parse(readFileSync(this.pathWithInput(circuit, input, 'in'), 'utf-8'));
const jsonInput = data || JSON.parse(readFileSync(this.pathWithInput(circuit, input, 'in'), 'utf-8'));

mkdirSync(outDir, {recursive: true});
await snarkjs.wtns.calculate(jsonInput, wasmPath, wtnsPath, this._logger);
Expand Down
6 changes: 5 additions & 1 deletion src/testers/proofTester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ export default class ProofTester<IN extends string[] = []> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public readonly verificationKey: any;

constructor(readonly wasmPath: string, readonly pkeyPath: string, readonly vkeyPath: string) {
constructor(
readonly wasmPath: string,
readonly pkeyPath: string,
readonly vkeyPath: string
) {
this.verificationKey = JSON.parse(readFileSync(vkeyPath).toString()) as typeof this.verificationKey;
this.protocol = this.verificationKey.protocol;
}
Expand Down
42 changes: 40 additions & 2 deletions tests/circomkit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import forEach from 'mocha-each';
import {PROTOCOLS} from '../src/utils/config';
import {Circomkit} from '../src';
import {expect} from 'chai';
import {existsSync} from 'fs';
import {CIRCUIT_CONFIG, CIRCUIT_NAME, INPUT, INPUT_NAME, PTAU_PATH} from './common';
import {existsSync, rmSync} from 'fs';
import {CIRCUIT_CONFIG, CIRCUIT_NAME, FIBONACCI_CASES, INPUT, INPUT_NAME, PTAU_PATH} from './common';

// we are not testing all curves because PTAU is only available for bn128
forEach(PROTOCOLS).describe('protocol: %s', (protocol: (typeof PROTOCOLS)[number]) => {
Expand All @@ -20,6 +20,7 @@ forEach(PROTOCOLS).describe('protocol: %s', (protocol: (typeof PROTOCOLS)[number
it('should instantiate circuit', () => {
const path = circomkit.instantiate(CIRCUIT_NAME, CIRCUIT_CONFIG);
expect(existsSync(path)).to.be.true;
rmSync(path); // remove it to see if compile command creates it too
});

it('should compile circuit', async () => {
Expand Down Expand Up @@ -90,3 +91,40 @@ forEach(PROTOCOLS).describe('protocol: %s', (protocol: (typeof PROTOCOLS)[number
}
});
});

forEach(FIBONACCI_CASES).describe(
'circomkit with explicit config & input',
(customCase: (typeof FIBONACCI_CASES)[0]) => {
let circomkit: Circomkit;

before(() => {
circomkit = new Circomkit({
protocol: 'groth16',
verbose: false,
logLevel: 'silent',
});
});

it('should compile with custom config', async () => {
await circomkit.compile(customCase.circuit, {
file: customCase.file,
template: 'Fibonacci',
params: [7],
});

await circomkit.info(customCase.circuit);
});

it('should prove with custom input data', async () => {
const path = await circomkit.prove(customCase.circuit, customCase.input, {
in: [1, 1],
});
expect(existsSync(path)).to.be.true;
});

it('should verify the proof', async () => {
const isVerified = await circomkit.verify(customCase.circuit, customCase.input);
expect(isVerified).to.be.true;
});
}
);
13 changes: 13 additions & 0 deletions tests/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,16 @@ export const BAD_INPUT = {
export const OUTPUT = {
out: product,
};

export const FIBONACCI_CASES = [
{
file: 'fibonacci/vanilla',
circuit: 'fibo_vanilla',
input: 'vanilla',
},
{
file: 'fibonacci/recursive',
circuit: 'fibo_recursive',
input: 'recursive',
},
];
2 changes: 1 addition & 1 deletion tests/configs.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {expect} from 'chai';
import {Circomkit} from '../src';

describe('config overrides', () => {
describe('circomkit config overrides', () => {
it('should override default configs', () => {
const circomkit = new Circomkit({
prime: 'goldilocks',
Expand Down
Loading

0 comments on commit b11689a

Please sign in to comment.