diff --git a/README.md b/README.md index 2ae3cf4..fe2db55 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,23 @@ The repository follows an _opinionated file structure_ shown below, abstracting ```sh circom-ts-starter -├── circuits # where you write templates -│ ├── main # where you instantiate components +├── circuit.config.cjs # configs for circuit main components +├── .cli.env # environment variables for cli +├── circuits # where you write templates +│ ├── main # where you instantiate components │ │ │── foo-main.circom │ │ └── ... │ │── foo.circom │ └── ... -├── inputs # where you write JSON inputs per circuit +├── inputs # where you write JSON inputs per circuit │ ├── foo │ │ ├── input-name.json │ │ └── ... │ └── ... -├── ptau # universal phase-1 setups +├── ptau # universal phase-1 setups │ ├── powersOfTau28_hez_final_12.ptau │ └── ... -└── build # artifacts, .gitignore'd +└── build # artifacts, .gitignore'd │── foo-main │ │── foo-main_js # artifacts of compilation │ │ │── generate_witness.js @@ -42,14 +44,22 @@ circom-ts-starter └── ... ``` -Write your circuits under `circuits` folder. The circuit code itself should be templates only. You should only create the main component under `circuits/main` folder. +Write your circuits under `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/main/_template.circom) to create the main component. The target circuits are defined under the [circuit configs](./circuit.config.cjs) file, such as: + +```js +multiplier3: { + file: 'multiplier', + template: 'Multiplier', + publicInputs: [], + templateInputs: [3], +} +``` Use the [CLI](./scripts/cli.sh), or its wrapper scripts in [package.json](./package.json) to do stuff with your circuits. ```bash yarn compile -c circuit-name yarn clean -c circuit-name -yarn type -c circuit-name yarn ptau -c circuit-name -n num-contribs -p phase1-ptau-path yarn prove -c circuit-name -i input-name yarn verify -c circuit-name -i input-name diff --git a/circuit.config.cjs b/circuit.config.cjs new file mode 100644 index 0000000..1a10a4a --- /dev/null +++ b/circuit.config.cjs @@ -0,0 +1,30 @@ +module.exports = { + // multiplication of 3 numbers + multiplier3: { + file: 'multiplier', + template: 'Multiplier', + publicInputs: [], + templateInputs: [3], + }, + // A 9x9 sudoku board + sudoku9: { + file: 'sudoku', + template: 'Sudoku', + publicInputs: ['puzzle'], + templateInputs: [3], + }, + // 64-bit floating point, 11-bit exponent and 52-bit mantissa + fp64: { + file: 'float_add', + template: 'FloatAdd', + publicInputs: [], + templateInputs: [11, 52], + }, + // 32-bit floating point, 8-bit exponent and 23-bit mantissa + fp32: { + file: 'float_add', + template: 'FloatAdd', + publicInputs: [], + templateInputs: [8, 23], + }, +}; diff --git a/circuits/fibonacci.circom b/circuits/fibonacci.circom new file mode 100644 index 0000000..c89cac8 --- /dev/null +++ b/circuits/fibonacci.circom @@ -0,0 +1,18 @@ +pragma circom 2.0.0; + +// Fibonacci with custom starting numbers +template Fibonacci(n) { + assert(n >= 2); + signal input in[2]; + signal output out; + + // compute the sequence + signal f[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]; +} diff --git a/circuits/main/_template.circom b/circuits/main/_template.circom new file mode 100644 index 0000000..29b4897 --- /dev/null +++ b/circuits/main/_template.circom @@ -0,0 +1,12 @@ +<%#'this file is an EJS template to generate main component for circuits'-%> +<%#'configuration is read from config.js in the project root directory'-%> +// auto-generated by instantiate.js +pragma circom 2.0.0; + +include "../../circuits/<%= file %>.circom"; + +component main<%= + publicInputs.length == 0 ? + '' : + ' {public[' + publicInputs.join(", ") + ']}' + %> = <%= template %>(<%= templateInputs.join(", ") %>); diff --git a/circuits/main/fp32.circom b/circuits/main/fp32.circom new file mode 100644 index 0000000..bd56a09 --- /dev/null +++ b/circuits/main/fp32.circom @@ -0,0 +1,6 @@ +// auto-generated by instantiate.js +pragma circom 2.0.0; + +include "../../circuits/float_add.circom"; + +component main = FloatAdd(8, 23); diff --git a/circuits/main/fp64.circom b/circuits/main/fp64.circom new file mode 100644 index 0000000..b256872 --- /dev/null +++ b/circuits/main/fp64.circom @@ -0,0 +1,6 @@ +// auto-generated by instantiate.js +pragma circom 2.0.0; + +include "../../circuits/float_add.circom"; + +component main = FloatAdd(11, 52); diff --git a/circuits/main/multiplier3.circom b/circuits/main/multiplier3.circom index 1c03a8b..ba67f1c 100644 --- a/circuits/main/multiplier3.circom +++ b/circuits/main/multiplier3.circom @@ -1,6 +1,6 @@ +// auto-generated by instantiate.js pragma circom 2.0.0; include "../../circuits/multiplier.circom"; -// Multiply 3 numbers component main = Multiplier(3); diff --git a/circuits/main/sudoku9.circom b/circuits/main/sudoku9.circom index 3c57bd6..8ea72f1 100644 --- a/circuits/main/sudoku9.circom +++ b/circuits/main/sudoku9.circom @@ -2,5 +2,5 @@ pragma circom 2.0.0; include "../../circuits/sudoku.circom"; -// Circuit for 3^2 * 3^2 sudoku +// Circuit for 3^2 x 3^2 sudoku component main {public[puzzle]} = Sudoku(3); diff --git a/circuits/multiplier.circom b/circuits/multiplier.circom index 1d0cd87..fec5b77 100644 --- a/circuits/multiplier.circom +++ b/circuits/multiplier.circom @@ -27,6 +27,5 @@ template Multiplier(N){ comp[i+1].in1 <== comp[i].out; comp[i+1].in2 <== in[i+2]; } - out <== comp[N-2].out; } diff --git a/package.json b/package.json index bdde1c1..0326598 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "circom_tester": "^0.0.19", "circomlib": "^2.0.5", "circomlibjs": "^0.1.7", + "ejs": "^3.1.9", "gts": "^3.1.1", "mocha": "^10.2.0", "snarkjs": "^0.5.0", diff --git a/scripts/cli.sh b/scripts/cli.sh index 14a9758..05dceb2 100755 --- a/scripts/cli.sh +++ b/scripts/cli.sh @@ -78,9 +78,6 @@ case $FUNC in verify) verify $CIRCUIT $INPUT ;; - keygen) - compile $CIRCUIT && ptau $CIRCUIT $NUM_CONTRIBS - ;; *) echo "Usage:" echo " -f " diff --git a/scripts/functions/compile.sh b/scripts/functions/compile.sh index bb9a79a..3a0cd28 100755 --- a/scripts/functions/compile.sh +++ b/scripts/functions/compile.sh @@ -5,13 +5,14 @@ compile() { local CIRCOM_IN=./circuits/main/$CIRCUIT.circom local CIRCOM_OUT=./build/$CIRCUIT - echo "Compiler: $CLIENV_COMPILER_ARGS" + # generate the circuit main component + node ./scripts/instantiate.js $CIRCUIT # create build dir if not exists already mkdir -p $CIRCOM_OUT # compile with circom - circom $CIRCOM_IN -o $CIRCOM_OUT --r1cs --sym --wasm + circom $CIRCOM_IN -o $CIRCOM_OUT --r1cs --wasm --sym echo -e "${CLIENV_COLOR_LOG}Built artifacts under $CIRCOM_OUT${CLIENV_COLOR_RESET}" } diff --git a/scripts/functions/type.sh b/scripts/functions/type.sh index e38add6..bc1f678 100755 --- a/scripts/functions/type.sh +++ b/scripts/functions/type.sh @@ -1,11 +1,18 @@ -## Parse the symbols file and generate types for TypeScript +## Parse the template circuit that you are using for your main component +## and generate TypeScript interfaces for it +## TODO: not sure i need this yet type() { - echo -e "\n${CLIENV_COLOR_TITLE}=== Generating types ===${CLIENV_COLOR_RESET}" - CIRCUIT=$1 - SYM=./build/$CIRCUIT/$CIRCUIT.sym + set -e - # choose lines with 1 dot only (these are the signals in main file), extract their names - cat $SYM | grep -E '^.+main[^.]*\.[^.]*$' + echo -e "\n${CLIENV_COLOR_TITLE}=== Generating types ===${CLIENV_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}' echo -e "\n${CLIENV_COLOR_LOG}Types generated!${CLIENV_COLOR_RESET}" } + + # cat ./build/multiplier3/multiplier3.sym | awk -F '.' 'NF==2{print $2}' diff --git a/scripts/instantiate.js b/scripts/instantiate.js new file mode 100644 index 0000000..0026ad1 --- /dev/null +++ b/scripts/instantiate.js @@ -0,0 +1,17 @@ +const ejs = require('ejs'); +const {writeFileSync, readFileSync} = require('fs'); +const config = require('../circuit.config.cjs'); + +// read circuit from config +const target = process.argv[2]; +if (!(target in config)) { + throw new Error(`Target ${target} not found in config.`); +} + +// generate the main component code +let circuit = ejs.render(readFileSync('./circuits/main/_template.circom').toString(), config[target]); + +// output to file +const targetPath = `./circuits/main/${target}.circom`; +writeFileSync(targetPath, circuit); +console.log(`Main component created at: ${targetPath}\n`); diff --git a/scripts/utils/generate-types.js b/scripts/utils/generate-types.js deleted file mode 100644 index 5bec01b..0000000 --- a/scripts/utils/generate-types.js +++ /dev/null @@ -1,3 +0,0 @@ -for (let arg of process.argv.slice(2)) { - console.log(arg); -} diff --git a/tests/multiplier.test.ts b/tests/multiplier.test.ts index 51112a9..2a50584 100644 --- a/tests/multiplier.test.ts +++ b/tests/multiplier.test.ts @@ -1,5 +1,5 @@ import {compileCircuit} from '../utils'; -import {Circuit} from '../utils/circuit'; +import {ProofTester} from '../utils/proofTester'; import type {CircuitSignals, FullProof} from '../types/circuit'; import type {WasmTester} from '../types/wasmTester'; import {assert, expect} from 'chai'; @@ -10,7 +10,7 @@ const CIRCUIT_NAME = 'multiplier3'; describe(CIRCUIT_NAME, () => { const INPUT: CircuitSignals = input80; - describe('functionality', () => { + describe('witness computation', () => { let circuit: WasmTester; before(async () => { @@ -57,10 +57,10 @@ describe(CIRCUIT_NAME, () => { }); }); - describe('validation', () => { + describe('proof verification', () => { let fullProof: FullProof; - const circuit = new Circuit(CIRCUIT_NAME); + const circuit = new ProofTester(CIRCUIT_NAME); before(async () => { fullProof = await circuit.prove(INPUT); diff --git a/tests/sudoku.test.ts b/tests/sudoku.test.ts index 1d01c7e..915fdde 100644 --- a/tests/sudoku.test.ts +++ b/tests/sudoku.test.ts @@ -1,5 +1,4 @@ import {compileCircuit} from '../utils'; -import {Circuit} from '../utils/circuit'; import type {CircuitSignals, FullProof} from '../types/circuit'; import type {WasmTester} from '../types/wasmTester'; import {assert, expect} from 'chai'; @@ -10,7 +9,7 @@ const CIRCUIT_NAME = 'sudoku9'; describe(CIRCUIT_NAME, () => { const INPUT: CircuitSignals = inputfoo; - describe('functionality', () => { + describe('witness computation', () => { let circuit: WasmTester; before(async () => { diff --git a/types/circuit.ts b/types/circuit.ts index 609da2e..1ccd644 100644 --- a/types/circuit.ts +++ b/types/circuit.ts @@ -5,7 +5,8 @@ export type CircuitSignals = {[signalName: string]: any}; /** - * A witness is just an array of strings. + * A witness is an array of bigints, corresponding to the values of each wire in + * the evaluation of the circuit. */ export type WitnessType = bigint[]; diff --git a/types/proofTester.ts b/types/proofTester.ts new file mode 100644 index 0000000..e69de29 diff --git a/types/wasmTester.ts b/types/wasmTester.ts index 4d90519..64bf0aa 100644 --- a/types/wasmTester.ts +++ b/types/wasmTester.ts @@ -7,10 +7,15 @@ import {WitnessType, CircuitSignals} from './circuit'; export type WasmTester = { /** * Assert that constraints are valid. - * @param witness + * @param witness witness */ checkConstraints: (witness: WitnessType) => Promise; + /** + * Cleanup directory, should probably be called upon test completion + */ + release(): Promise; + /** * Assert the output of a given witness. * @param actualOut expected output signals @@ -32,7 +37,28 @@ export type WasmTester = { /** * List of constraints, must call `loadConstraints` before - * accessing this key. + * accessing this key */ constraints: any[] | undefined; + + /** + * Loads the symbols in a dictionary at `this.symbols` + * Symbols are stored under the .sym file + * Each line has 4 comma-separated values: + * 0: label index + * 1: variable index + * 2: component index + */ + loadSymbols(): Promise; + + /** + * A dictionary of symbols + */ + symbols: object; + + /** + * @deprecated this is buggy right now + * @param witness witness + */ + getDecoratedOutput(witness: WitnessType): Promise; }; diff --git a/utils/circuit.ts b/utils/proofTester.ts similarity index 98% rename from utils/circuit.ts rename to utils/proofTester.ts index 4e63a23..ad0a31f 100644 --- a/utils/circuit.ts +++ b/utils/proofTester.ts @@ -6,7 +6,7 @@ import {CircuitSignals, FullProof} from '../types/circuit'; * A more extensive Circuit class, able to generate proofs & verify them. * Assumes that prover key and verifier key have been computed. */ -export class Circuit { +export class ProofTester { private readonly wasmPath: string; private readonly proverKeyPath: string; private readonly verificationKey: object; diff --git a/yarn.lock b/yarn.lock index eeb699a..c09b6ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1083,6 +1083,13 @@ ejs@^3.1.6: dependencies: jake "^10.8.5" +ejs@^3.1.9: + version "3.1.9" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361" + integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ== + dependencies: + jake "^10.8.5" + elliptic@6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"