From e5b68f122c69a9f58a0976ed23498d8ce3c43813 Mon Sep 17 00:00:00 2001 From: Erhan Tezcan Date: Sat, 1 Apr 2023 16:22:02 +0300 Subject: [PATCH] todo test & dir refactors --- circuit.config.cjs | 2 +- circuits/float_add.circom | 10 ++-- circuits/sudoku.circom | 29 ++++++++-- circuits/test/cbl_3.circom | 6 +++ package.json | 1 - scripts/cli.sh | 10 +++- scripts/functions/compile.sh | 8 +-- scripts/instantiate.js | 19 ++----- tests/fibonacci.test.ts | 30 +++++------ tests/float_add.test.ts | 27 ++++++++++ tests/multiplier.proofs.test.ts | 26 +++++++++ tests/multiplier.test.ts | 70 +++++++++--------------- tests/sudoku.test.ts | 94 +++++++++++++++++++++++++++------ yarn.lock | 9 +--- 14 files changed, 226 insertions(+), 115 deletions(-) create mode 100644 circuits/test/cbl_3.circom create mode 100644 tests/float_add.test.ts create mode 100644 tests/multiplier.proofs.test.ts diff --git a/circuit.config.cjs b/circuit.config.cjs index dc9612a..4e1d8f0 100644 --- a/circuit.config.cjs +++ b/circuit.config.cjs @@ -38,7 +38,7 @@ const config = { templateParams: [11], }, // checks that a number fits to given bit count - checkBitLength: { + cbl_3: { file: 'float_add', template: 'CheckBitLength', publicInputs: [], diff --git a/circuits/float_add.circom b/circuits/float_add.circom index fa85ece..ebf4bdf 100644 --- a/circuits/float_add.circom +++ b/circuits/float_add.circom @@ -2,12 +2,12 @@ pragma circom 2.0.0; // this code is taken from ZKP MOOC 2023 lab, and then modified -include "circomlib/circuits/comparators.sol"; -include "circomlib/circuits/switcher.sol"; -include "circomlib/circuits/gates.sol"; -include "circomlib/circuits/bitify.sol"; +include "circomlib/circuits/comparators.circom"; +include "circomlib/circuits/switcher.circom"; +include "circomlib/circuits/gates.circom"; +include "circomlib/circuits/bitify.circom"; -include "./math/bits.sol"; +include "functions/bits.circom"; /* * Basically `out = cond ? ifTrue : ifFalse` diff --git a/circuits/sudoku.circom b/circuits/sudoku.circom index 39f02ce..bd20200 100644 --- a/circuits/sudoku.circom +++ b/circuits/sudoku.circom @@ -1,17 +1,37 @@ pragma circom 2.0.0; include "circomlib/circuits/bitify.circom"; -include "./functions/bits.circom"; +include "functions/bits.circom"; + +// Ensures that number is representable by b-bits +template CheckBitLength(b) { + assert(b < 254); + signal input in; + + // compute b-bit representation of the number + signal bits[b]; + var sum_of_bits = 0; + for (var i = 0; i < b; i++) { + bits[i] <-- (in >> i) & 1; + bits[i] * (1 - bits[i]) === 0; + sum_of_bits += (2 ** i) * bits[i]; + } + + // check if sum is equal to number itself + in === sum_of_bits; +} // Assert that two elements are not equal template NonEqual() { signal input in[2]; + signal output out; signal inv; // we check if (in[0] - in[1] != 0) // because 1/0 results in 0, so the constraint won't hold inv <-- 1 / (in[1] - in[0]); - inv * (in[1] - in[0]) === 1; + 0 * inv === 0; // silence error + out <== inv * (in[1] - in[0]); } // Assert that all given values are unique @@ -22,6 +42,7 @@ template Distinct(n) { for(var j = 0; j < i; j++){ nonEqual[i][j] = NonEqual(); nonEqual[i][j].in <== [in[i], in[j]]; + nonEqual[i][j].out === 1; } } } @@ -31,8 +52,8 @@ template InRange(MIN, MAX) { signal input in; var b = numOfBits(MAX); - component lowerBound = Num2Bits(b); - component upperBound = Num2Bits(b); + component lowerBound = CheckBitLength(b); + component upperBound = CheckBitLength(b); lowerBound.in <== in - MIN; // e.g. 1 - 1 = 0 (for 0 <= in) upperBound.in <== in + (2 ** b) - MAX - 1; // e.g. 9 + 6 = 15 (for in <= 15) } diff --git a/circuits/test/cbl_3.circom b/circuits/test/cbl_3.circom new file mode 100644 index 0000000..5743c1a --- /dev/null +++ b/circuits/test/cbl_3.circom @@ -0,0 +1,6 @@ +// auto-generated by instantiate.js +pragma circom 2.0.0; + +include "../float_add.circom"; + +component main = CheckBitLength(3); diff --git a/package.json b/package.json index 4281655..1736a91 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "author": "erhant", "devDependencies": { "@types/chai": "^4.3.4", - "@types/chai-as-promised": "^7.1.5", "@types/mocha": "^10.0.1", "@types/node": "^18.11.18", "chai": "^4.3.7", diff --git a/scripts/cli.sh b/scripts/cli.sh index 05dceb2..b841f5a 100755 --- a/scripts/cli.sh +++ b/scripts/cli.sh @@ -17,12 +17,17 @@ source ./scripts/functions/compile.sh # get arguments NUM_CONTRIBS=1 # default value -while getopts "f:c:n:i:p:" opt; do +COMPILE_DIR="main" # default dir +while getopts "f:c:n:i:p:d:" opt; do case $opt in # function to call f) FUNC="$OPTARG" ;; + # director for compilation (default: main) + d) + COMPILE_DIR="$OPTARG" + ;; # circuit name c) CIRCUIT="$OPTARG" @@ -61,7 +66,7 @@ case $FUNC in clean $CIRCUIT ;; compile) - compile $CIRCUIT + compile $CIRCUIT $COMPILE_DIR ;; type) type $CIRCUIT @@ -89,6 +94,7 @@ case $FUNC in echo " prove Prove an input" echo " verify Verify a proof & public signals" echo " -c " + echo " -d " echo " -n (default: 1)" echo " -i " echo " -p " diff --git a/scripts/functions/compile.sh b/scripts/functions/compile.sh index 3f12f79..75f3c91 100755 --- a/scripts/functions/compile.sh +++ b/scripts/functions/compile.sh @@ -2,11 +2,13 @@ compile() { echo -e "\n${CLIENV_COLOR_TITLE}=== Compiling the circuit ===${CLIENV_COLOR_RESET}" local CIRCUIT=$1 + local DIR=$2 + local CIRCOM_IN=./circuits/$DIR/$CIRCUIT.circom + local CIRCOM_OUT=./build/$CIRCUIT # generate the circuit main component - node ./scripts/instantiate.js $CIRCUIT - local CIRCOM_IN=./circuits/main/$CIRCUIT.circom - local CIRCOM_OUT=./build/$CIRCUIT + mkdir -p ./circuits/$DIR + node ./scripts/instantiate.js $CIRCUIT $DIR # create build dir if not exists already mkdir -p $CIRCOM_OUT diff --git a/scripts/instantiate.js b/scripts/instantiate.js index cce681e..0d5b0b0 100644 --- a/scripts/instantiate.js +++ b/scripts/instantiate.js @@ -1,28 +1,19 @@ const ejs = require('ejs'); -const {writeFileSync, readFileSync, existsSync, mkdirSync} = require('fs'); +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 templatePath = './circuits/ejs/template.circom'; -let circuit = ejs.render(readFileSync(templatePath).toString(), config[target]); +const ejsPath = './circuits/ejs/template.circom'; +let circuit = ejs.render(readFileSync(ejsPath).toString(), config[target]); // output to file -const dirName = config[target].dir ? config[target].dir : 'main'; -if (typeof dirName !== 'string') { - throw new Error(`Bad target type.`); -} - -const dir = `./circuits/${dirName}`; -if (!existsSync(dir)) { - mkdirSync(dir, {recursive: true}); -} - -const targetPath = `${dir}/${target}.circom`; +const targetPath = `./circuits/${dir}/${target}.circom`; writeFileSync(targetPath, circuit); console.log(`Main component created at: ${targetPath}\n`); diff --git a/tests/fibonacci.test.ts b/tests/fibonacci.test.ts index 311d334..60f71ab 100644 --- a/tests/fibonacci.test.ts +++ b/tests/fibonacci.test.ts @@ -21,25 +21,23 @@ describe(CIRCUIT_NAME, () => { in: [1, 1], }; - describe('witness computation', () => { - let circuit: Awaited>; + let circuit: Awaited>; - before(async () => { - circuit = await createWasmTester(CIRCUIT_NAME); - }); + before(async () => { + circuit = await createWasmTester(CIRCUIT_NAME); + }); - it('should compute correctly', async () => { - // compute witness - const witness = await circuit.calculateWitness(INPUT, true); + 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 valid constraints + await circuit.checkConstraints(witness); - // witness should have correct output - const output = { - out: fibonacci(INPUT.in, 11), - }; - await circuit.assertOut(witness, output); - }); + // witness should have correct output + const output = { + out: fibonacci(INPUT.in, 11), + }; + await circuit.assertOut(witness, output); }); }); diff --git a/tests/float_add.test.ts b/tests/float_add.test.ts new file mode 100644 index 0000000..06aa14c --- /dev/null +++ b/tests/float_add.test.ts @@ -0,0 +1,27 @@ +import {createWasmTester} from '../utils/wasmTester'; +import {ProofTester} from '../utils/proofTester'; +import type {CircuitSignals, FullProof} from '../types/circuit'; +import {assert, expect} from 'chai'; + +// TODO: write tests +const CIRCUIT_NAME = 'cbl_32'; +describe('utils', () => { + let circuit: Awaited>; + + before(async () => { + circuit = await createWasmTester(CIRCUIT_NAME, 'test'); + }); + + it('should compute correctly', async () => { + const witness = await circuit.calculateWitness( + { + in: 3, + }, + true + ); + await circuit.checkConstraints(witness); + await circuit.assertOut(witness, { + out: 1, + }); + }); +}); diff --git a/tests/multiplier.proofs.test.ts b/tests/multiplier.proofs.test.ts new file mode 100644 index 0000000..6fe9439 --- /dev/null +++ b/tests/multiplier.proofs.test.ts @@ -0,0 +1,26 @@ +import {ProofTester} from '../utils/proofTester'; +import type {CircuitSignals, FullProof} from '../types/circuit'; +import {assert, expect} from 'chai'; +// read inputs from file +import input80 from '../inputs/multiplier3/80.json'; + +const CIRCUIT_NAME = 'multiplier3'; +describe(CIRCUIT_NAME + ' (proofs)', () => { + const INPUT: CircuitSignals = input80; + + let fullProof: FullProof; + const circuit = new ProofTester(CIRCUIT_NAME); + + before(async () => { + fullProof = await circuit.prove(INPUT); + }); + + it('should verify', async () => { + expect(await circuit.verify(fullProof.proof, fullProof.publicSignals)).to.be.true; + }); + + 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; + }); +}); diff --git a/tests/multiplier.test.ts b/tests/multiplier.test.ts index b6b2256..0c95afc 100644 --- a/tests/multiplier.test.ts +++ b/tests/multiplier.test.ts @@ -9,56 +9,36 @@ const CIRCUIT_NAME = 'multiplier3'; describe(CIRCUIT_NAME, () => { const INPUT: CircuitSignals = input80; - describe('witness computation', () => { - let circuit: Awaited>; + let circuit: Awaited>; - before(async () => { - circuit = await createWasmTester(CIRCUIT_NAME); - }); - - it('should compute correctly', async () => { - const witness = await circuit.calculateWitness(INPUT, true); - - // witness should have valid constraints - await circuit.checkConstraints(witness); - - // witness should have correct output - const output = { - 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 () => { - const fewInputs = INPUT.in.slice(1); - await circuit.calculateWitness({in: fewInputs}, true).then( - () => assert.fail('expected to fail on fewer inputs'), - 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('expected to fail on too many inputs'), - err => expect(err.message).to.eq('Too many values for input signal in\n') - ); - }); + before(async () => { + circuit = await createWasmTester(CIRCUIT_NAME); }); - describe('proof verification', () => { - let fullProof: FullProof; - const circuit = new ProofTester(CIRCUIT_NAME); + it('should compute correctly', async () => { + const witness = await circuit.calculateWitness(INPUT, true); - before(async () => { - fullProof = await circuit.prove(INPUT); - }); + // witness should have valid constraints + await circuit.checkConstraints(witness); - it('should verify', async () => { - expect(await circuit.verify(fullProof.proof, fullProof.publicSignals)).to.be.true; - }); + // witness should have correct output + const output = { + out: BigInt(INPUT.in.reduce((prev: bigint, acc: bigint) => acc * prev)), + }; + await circuit.assertOut(witness, output); + }); - 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; - }); + 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') + ); }); }); diff --git a/tests/sudoku.test.ts b/tests/sudoku.test.ts index 3846985..a9e3790 100644 --- a/tests/sudoku.test.ts +++ b/tests/sudoku.test.ts @@ -1,26 +1,88 @@ import {createWasmTester} from '../utils/wasmTester'; -import type {CircuitSignals, FullProof} from '../types/circuit'; import {assert, expect} from 'chai'; -// read inputs from file -import inputfoo from '../inputs/sudoku_9x9/example.json'; -const CIRCUIT_NAME = 'sudoku9'; +const CIRCUIT_NAME = 'sudoku_9x9'; describe(CIRCUIT_NAME, () => { - const INPUT: CircuitSignals = inputfoo; + const INPUT = { + solution: [ + [1, 9, 4, 8, 6, 5, 2, 3, 7], + [7, 3, 5, 4, 1, 2, 9, 6, 8], + [8, 6, 2, 3, 9, 7, 1, 4, 5], + [9, 2, 1, 7, 4, 8, 3, 5, 6], + [6, 7, 8, 5, 3, 1, 4, 2, 9], + [4, 5, 3, 9, 2, 6, 8, 7, 1], + [3, 8, 9, 6, 5, 4, 7, 1, 2], + [2, 4, 6, 1, 7, 9, 5, 8, 3], + [5, 1, 7, 2, 8, 3, 6, 9, 4], + ], + puzzle: [ + [0, 0, 0, 8, 6, 0, 2, 3, 0], + [7, 0, 5, 0, 0, 0, 9, 0, 8], + [0, 6, 0, 3, 0, 7, 0, 4, 0], + [0, 2, 0, 7, 0, 8, 0, 5, 0], + [0, 7, 8, 5, 0, 0, 0, 0, 0], + [4, 0, 0, 9, 0, 6, 0, 7, 0], + [3, 0, 9, 0, 5, 0, 7, 0, 2], + [0, 4, 0, 1, 0, 9, 0, 8, 0], + [5, 0, 7, 0, 8, 0, 0, 9, 4], + ], + }; - describe('witness computation', () => { - let circuit: Awaited>; + let circuit: Awaited>; - before(async () => { - circuit = await createWasmTester(CIRCUIT_NAME); - }); + before(async () => { + circuit = await createWasmTester(CIRCUIT_NAME); + }); - it('should compute correctly', async () => { - // compute witness - const witness = await circuit.calculateWitness(INPUT, true); + 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 valid constraints + await circuit.checkConstraints(witness); + }); + + it('should NOT accept non-distinct rows', async () => { + const badInput = JSON.parse(JSON.stringify(INPUT)); + + badInput.solution[0][0] = badInput.solution[0][1]; + console.log(badInput.solution[0], badInput.solution[1]); + await circuit.calculateWitness(INPUT, true).then( + () => assert.fail(), + err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.') + ); + }); + + it('should NOT accept non-distinct columns', async () => { + const badInput = JSON.parse(JSON.stringify(INPUT)); + + badInput.solution[0][0] = badInput.solution[1][0]; + console.log(badInput.solution[0], badInput.solution[1]); + await circuit.calculateWitness(INPUT, true).then( + () => assert.fail(), + err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.') + ); + }); + + it('should NOT accept non-distinct square', async () => { + const badInput: typeof INPUT = JSON.parse(JSON.stringify(INPUT)); + + badInput.solution[0][0] = badInput.solution[1][1]; + console.log(badInput.solution[0], badInput.solution[1]); + await circuit.calculateWitness(INPUT, true).then( + () => assert.fail(), + err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.') + ); + }); + + it('should NOT accept empty value in solution', async () => { + const badInput = JSON.parse(JSON.stringify(INPUT)); + + badInput.solution[0][0] = 0; + console.log(badInput.solution[0], badInput.solution[1]); + await circuit.calculateWitness(badInput, true).then( + () => assert.fail(), + err => expect(err.message.slice(0, 21)).to.eq('Error: Assert Failed.') + ); }); }); diff --git a/yarn.lock b/yarn.lock index e008e8a..c09b6ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -480,14 +480,7 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== -"@types/chai-as-promised@^7.1.5": - version "7.1.5" - resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz#6e016811f6c7a64f2eed823191c3a6955094e255" - integrity sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ== - dependencies: - "@types/chai" "*" - -"@types/chai@*", "@types/chai@^4.3.4": +"@types/chai@^4.3.4": version "4.3.4" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.4.tgz#e913e8175db8307d78b4e8fa690408ba6b65dee4" integrity sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==