mirror of
https://github.com/erhant/circomkit.git
synced 2026-05-05 03:00:37 -04:00
Merge pull request #11 from erhant/setup-groth16
Setup phase for Groth16, better testers
This commit is contained in:
313
README.md
313
README.md
@@ -26,265 +26,134 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
- [x] **Programmable Circuits**: The `main` component is created & compiled programmatically.
|
||||
- [x] **Simple CLI**: A straightforward CLI is provided as a wrapper around SnarkJS commands, exposed via NPM scripts!
|
||||
- [x] **Easily Configurable**: A single `circomkit.env` file stores the general configuration settings.
|
||||
- [x] **Constraint Testing**: You can test computations & assertions for every template in a circuit, with minimal code-repetition.
|
||||
- [x] **Proof Testing**: With prover & verification keys and the WASM circuit, you can test proof generation & verification.
|
||||
- [x] **Witness Manipulation**: You can parse the output from a witness, and furthermore create fake witnesses to try and fool the verifier.
|
||||
- [x] **Type-safe**: Witness & proof testers, as well as circuit signal inputs & outputs are all type-safe via generics.
|
||||
- [x] **Solidity Exports**: Export a verifier contract in Solidity, or export a calldata for your proofs & public signals.
|
||||
## Installation
|
||||
|
||||
Circomkit is an NPM package, which you can install via:
|
||||
|
||||
```sh
|
||||
yarn add circomkit # yarn
|
||||
npm install circomkit # NPM
|
||||
```
|
||||
|
||||
You will also need Circom, which can be installed following the instructions [here](https://docs.circom.io/getting-started/installation/).
|
||||
|
||||
## Usage
|
||||
|
||||
Using Circomkit is easy:
|
||||
Create an empty project, and install Circomkit. Then, you can setup the environment by simply executing:
|
||||
|
||||
1. Install [Circom](https://docs.circom.io/getting-started/installation/).
|
||||
2. Clone this repo (or use it as a template) and install packages (`yarn` or `npm install`).
|
||||
3. Write your circuit templates under the [circuits](./circuits/) folder. Your circuit code itself should be templates only; Circomkit programmatically generates the `main` component
|
||||
4. Write your tests under the [tests](./tests/) folder.
|
||||
5. Once you are ready, write the circuit configurations at [circuits.json](./circuits.json).
|
||||
6. Use NPM scripts (`yarn <script>` or `npm run <script>`) to compile your circuit, build keys, generate & verify proofs and much more!
|
||||
```sh
|
||||
npx circomkit init
|
||||
```
|
||||
|
||||
A circuit config looks like this:
|
||||
This command creates the following:
|
||||
|
||||
- An example Multiplier circuit, under `circuits` folder.
|
||||
- A circuit configuration file called `circuits.json`, with an example Multiplier circuit configuration.
|
||||
- An example input JSON file for Multiplier, under `inputs/multiplier` folder.
|
||||
- A test using Mocha, under `tests` folder.
|
||||
- A Mocha configuration file.
|
||||
|
||||
Although Circomkit initializes with a Mocha test, uses Chai in the background so you could use anything that supports Chai.
|
||||
|
||||
### Configuration
|
||||
|
||||
A circuit config within `circuits.json` looks like below, where the `key` is the circuit name to be used in commands, and the value is an object that describes the filename, template name, public signals and template parameters:
|
||||
|
||||
```js
|
||||
// the key is <circuit-name>
|
||||
sudoku_4x4: {
|
||||
file: 'sudoku', // file name (circuits/sudoku.circom)
|
||||
template: 'Sudoku', // template name
|
||||
pubs: ['puzzle'], // public signals
|
||||
params: [Math.sqrt(4)], // template parameters
|
||||
sudoku_9x9: {
|
||||
file: 'sudoku',
|
||||
template: 'Sudoku',
|
||||
pubs: ['puzzle'],
|
||||
params: [3], // sqrt(9)
|
||||
},
|
||||
```
|
||||
|
||||
You can omit `pubs` and `params` options, they default to `[]`. Afterwards, you can use the following commands:
|
||||
You can omit `pubs` and `params` options, they default to `[]`.
|
||||
|
||||
### Command Line Interface
|
||||
|
||||
Actions that require a circuit name can be called as follows:
|
||||
|
||||
```bash
|
||||
# Compile the circuit (generates the main component & compiles it)
|
||||
npx circomkit compile circuit-name
|
||||
# Compile the circuit
|
||||
npx circomkit compile circuit
|
||||
|
||||
# Circuit setup
|
||||
npx circomkit setup circuit-name -p phase1-ptau-path
|
||||
# Create the main component
|
||||
npx circomkit instantiate circuit
|
||||
|
||||
# Create a Solidity verifier contract
|
||||
npx circomkit contract circuit-name
|
||||
npx circomkit contract circuit
|
||||
|
||||
# Clean circuit artifacts
|
||||
npx circomkit clean circuit-name
|
||||
npx circomkit clean circuit
|
||||
|
||||
# Generate the `main` component without compiling it afterwards
|
||||
npx circomkit instantiate circuit-name
|
||||
# Circuit-specific setup
|
||||
npx circomkit setup circuit [ptau-path]
|
||||
```
|
||||
|
||||
You can change some general settings such as the configured proof system or the prime field under [circomkit.env](./circomkit.env).
|
||||
Circuit-specific setup optionally takes the path to a PTAU file as argument. If not provided, it will automatically decide the PTAU to use with respect to constraint count, and download it for you! This feature only works for `bn128` curve.
|
||||
|
||||
### Working with Input Signals
|
||||
|
||||
Some actions such as generating a witness, generating a proof and verifying a proof require JSON inputs to provide the signal values. For that, we specifically create our input files under the `inputs` folder, and under the target circuit name there. For example, an input named `foobar` for some circuit named `circ` would be at `inputs/circ/foobar.json`.
|
||||
Some actions such as generating a witness, generating a proof and verifying a proof require JSON inputs to provide the signal values. For that, we specifically create our input files under the `inputs` folder, and under the target circuit name there. For example, an input named `foo` for some circuit named `bar` would be at `inputs/bar/foo.json`.
|
||||
|
||||
```bash
|
||||
# Generate a witness for some input
|
||||
npx circomkit witness circuit-name [-i input-name (default: "default")]
|
||||
# Generate a witness
|
||||
npx circomkit witness circuit input
|
||||
|
||||
# Generate a proof for some input
|
||||
npx circomkit prove circuit-name [-i input-name (default: "default")]
|
||||
# Generate a proof
|
||||
npx circomkit prove circuit input
|
||||
|
||||
# Verify a proof for some input (public signals only)
|
||||
npx circomkit verify circuit-name [-i input-name (default: "default")]
|
||||
# Verify a proof with public signals
|
||||
npx circomkit verify circuit input
|
||||
|
||||
# Debug a witness of some input
|
||||
npx circomkit debug circuit-name input-name
|
||||
|
||||
# Export calldata to call your Solidity verifier contract
|
||||
npx circomkit calldata circuit-name [-i input-name (default: "default")]
|
||||
# Export Solidity calldata to console
|
||||
npx circomkit calldata circuit input
|
||||
```
|
||||
|
||||
### Example Circuits
|
||||
|
||||
We have several example circuits that you can check out. With them, you can prove the following statements:
|
||||
|
||||
- **Multiplier**: "I know `n` factors that make up some number".
|
||||
- **Fibonacci**: "I know the `n`'th Fibonacci number".
|
||||
- **SHA256**: "I know the `n`-byte preimage of some SHA256 digest".
|
||||
- **Sudoku**: "I know the solution to some `(n^2)x(n^2)` Sudoku puzzle".
|
||||
- **Floating-Point Addition**: "I know two floating-point numbers that make up some number with `e` exponent and `m` mantissa bits." (adapted from [Berkeley ZKP MOOC 2023 - Lab 1](https://github.com/rdi-berkeley/zkp-mooc-lab)).
|
||||
|
||||
### Witness Calculation
|
||||
|
||||
Witness calculation tests check whether your circuit computes the correct result based on your inputs, and makes sure that assertions are correct. We provide very useful utility functions to help write these tests.
|
||||
|
||||
```ts
|
||||
import WasmTester from '../utils/wasmTester';
|
||||
|
||||
const N = 3;
|
||||
describe('multiplier', () => {
|
||||
// type-safe signal names ✔
|
||||
let circuit: WasmTester<['in'], ['out']>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new(`multiplier_${N}`, {
|
||||
file: 'multiplier',
|
||||
template: 'Multiplier',
|
||||
params: [N], // template parameters ✔
|
||||
pubs: [], // public signals ✔
|
||||
});
|
||||
// constraint count checks ✔
|
||||
await circuit.checkConstraintCount(N - 1);
|
||||
});
|
||||
|
||||
it('should compute correctly', async () => {
|
||||
const randomNumbers = Array.from({length: N}, () => Math.floor(Math.random() * 100 * N));
|
||||
await circuit.expectPass({in: randomNumbers}, {out: randomNumbers.reduce((prev, acc) => acc * prev)});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
With the circuit object, we can do the following:
|
||||
|
||||
- `circuit.expectPass(input, output)` to test whether we get the expected output for some given input.
|
||||
- `circuit.expectPass(input)` to test whether the circuit assertions pass for some given input
|
||||
- `circuit.expectFail(input)` to test whether the circuit assertions pass for some given input
|
||||
|
||||
#### Witness
|
||||
|
||||
What if we would just like to see what the output is, instead of comparing it to some witness? Well, that would be a trouble because we would have to parse the witness array (which is huge for some circuits) with respect to which signals the output signals correspond to. Thankfully, Circomkit has a function for that:
|
||||
|
||||
```ts
|
||||
const output = await circuit.compute(INPUT, ['foo', 'bar']);
|
||||
/* {
|
||||
foo: [[1n, 2n], [3n, 4n]]
|
||||
bar: 42n
|
||||
} */
|
||||
```
|
||||
|
||||
Note that this operation requires parsing the symbols file (`.sym`) and reading the witness array, which may be costly for large circuits. Most of the time, you won't need this for testing; instead, you will likely use it to see what the circuit actually does for debugging.
|
||||
|
||||
On top of these, you can create a fake witness by overriding symbols in the witness. This is useful in case you think there is a soundness error and would like to try and generate an adversarial witness.
|
||||
|
||||
```ts
|
||||
// correct witness
|
||||
const witness = await circuit.calculateWitness(INPUT);
|
||||
// faked witness
|
||||
const fakeWitness = await circuit.fakeWitness(witness, {
|
||||
'symbol-names-here': 42n,
|
||||
});
|
||||
```
|
||||
|
||||
#### Multiple templates
|
||||
|
||||
You will often have multiple templates in your circuit code, and you might want to test them in the same test file of your main circuit too. Well, you can!
|
||||
|
||||
```ts
|
||||
describe('multiplier utilities', () => {
|
||||
describe('multiplication gate', () => {
|
||||
let circuit: WasmTester<['in'], ['out']>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new(circuitName, {
|
||||
file: 'multiplier',
|
||||
template: 'MultiplicationGate',
|
||||
dir: 'test/multiplier', // nested paths ✔
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass for in range', async () => {
|
||||
await circuit.expectPass({in: [7, 5]}, {out: 7 * 5});
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Proof Verification
|
||||
|
||||
If you have created the prover key, verification key & the circuit WASM file (which is simply `yarn keygen <circuit-name> -p <pptau-path>`), you can also test proof generation & verification.
|
||||
|
||||
```ts
|
||||
describe('multiplier proofs', () => {
|
||||
let fullProof: FullProof;
|
||||
let circuit: ProofTester<['in']>;
|
||||
|
||||
before(async () => {
|
||||
circuit = new ProofTester(`multiplier_${N}`);
|
||||
fullProof = await circuit.prove({
|
||||
in: Array.from({length: N}, () => Math.floor(Math.random() * 100 * N)),
|
||||
});
|
||||
});
|
||||
|
||||
it('should verify', async () => {
|
||||
await circuit.expectPass(fullProof.proof, fullProof.publicSignals);
|
||||
});
|
||||
|
||||
it('should NOT verify', async () => {
|
||||
await circuit.expectFail(fullProof.proof, ['13']);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
The two utility functions provided here are:
|
||||
|
||||
- `circuit.expectPass(proof, publicSignals)` that makes sure that the given proof is **accepted** by the verifier for the given public signals.
|
||||
- `circuit.expectFail(proof, publicSignals)` that makes sure that the given proof is **rejected** by the verifier for the given public signals.
|
||||
|
||||
## File Structure
|
||||
|
||||
The repository follows an _opinionated file structure_ shown below, abstracting away the pathing and orientation behind the scenes. Circomkit handles most of the work with respect to this structure.
|
||||
Circomkit follows an _opinionated file structure_, abstracting away the pathing and orientation behind the scenes. All of these can be customized by overriding the respective settings in `circomkit.json`.
|
||||
|
||||
Here is an example structure, where we have a generic Sudoku proof-of-solution circuit, and we instantiate it for a 9x9 board:
|
||||
|
||||
```sh
|
||||
circomkit
|
||||
├── circuits.json # configs for circuit main components
|
||||
├── circomkit.env # environment variables for cli
|
||||
├── circuits # where you write templates
|
||||
│ ├── main # auto-generated main components
|
||||
│ │ │── sudoku_9x9.circom # e.g. a 9x9 sudoku board
|
||||
│ │ └── ...
|
||||
│ ├── test # auto-generated test components
|
||||
│ │ └── ...
|
||||
│ │── sudoku.circom # a generic sudoku circuit template
|
||||
│ └── ...
|
||||
├── inputs # where you write JSON inputs per circuit
|
||||
│ ├── sudoku_9x9 # each main template has its own folder
|
||||
│ │ ├── example-input.json # e.g. a solution & its puzzle
|
||||
│ │ └── ...
|
||||
│ └── ...
|
||||
├── ptau # universal phase-1 setups
|
||||
│ ├── powersOfTau28_hez_final_12.ptau
|
||||
│ └── ...
|
||||
└── build # build artifacts, these are .gitignore'd
|
||||
│── sudoku_9x9 # each main template has its own folder
|
||||
│ │── sudoku_9x9_js # artifacts of compilation
|
||||
│ │ │── generate_witness.js
|
||||
│ │ │── witness_calculator.js
|
||||
│ │ └── sudoku_9x9.wasm
|
||||
│ │── example-input # artifacts of an input
|
||||
│ │ │── proof.json # generated proof object
|
||||
│ │ │── public.json # public signals
|
||||
│ │ └── witness.wtns # witness file
|
||||
│ │── ... # folders for other inputs
|
||||
│ │── sudoku_9x9.r1cs
|
||||
│ │── sudoku_9x9.sym
|
||||
│ │── prover_key.zkey
|
||||
│ └── verification_key.json
|
||||
└── ...
|
||||
├── circuits.json # circuit configs
|
||||
├── circomkit.json # customizations
|
||||
│
|
||||
├── circuits
|
||||
│ ├── main
|
||||
│ │ └── sudoku_9x9.circom
|
||||
│ └── sudoku.circom
|
||||
│
|
||||
├── inputs
|
||||
│ └── sudoku_9x9
|
||||
│ └── my_solution.json
|
||||
│
|
||||
├── ptau
|
||||
│ └── powersOfTau28_hez_final_12.ptau
|
||||
│
|
||||
└── build
|
||||
└── sudoku_9x9
|
||||
│── sudoku_9x9_js
|
||||
│ │── generate_witness.js
|
||||
│ │── witness_calculator.js
|
||||
│ └── sudoku_9x9.wasm
|
||||
│
|
||||
│── my_solution
|
||||
│ │── proof.json
|
||||
│ │── public.json
|
||||
│ └── witness.wtns
|
||||
│
|
||||
│── sudoku_9x9.r1cs
|
||||
│── sudoku_9x9.sym
|
||||
│── prover_key.zkey
|
||||
└── verifier_key.json
|
||||
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
To run tests do the following:
|
||||
|
||||
```bash
|
||||
# test a specific circuit
|
||||
yarn test <circuit-name>
|
||||
|
||||
# test all circuits
|
||||
yarn test:all
|
||||
```
|
||||
|
||||
You can test both witness calculations and proof generation & verification. We describe both in their respective sections, going over an example of "Multiplication" circuit.
|
||||
|
||||
## Styling
|
||||
|
||||
We use [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html) for the TypeScript codes.
|
||||
Circomkit uses [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html).
|
||||
|
||||
```bash
|
||||
# check the formatting
|
||||
|
||||
@@ -3,32 +3,5 @@
|
||||
"file": "multiplier",
|
||||
"template": "Multiplier",
|
||||
"params": [3]
|
||||
},
|
||||
"sudoku_9x9": {
|
||||
"file": "sudoku",
|
||||
"template": "Sudoku",
|
||||
"pubs": ["puzzle"],
|
||||
"params": [9]
|
||||
},
|
||||
"sudoku_4x4": {
|
||||
"file": "sudoku",
|
||||
"template": "Sudoku",
|
||||
"pubs": ["puzzle"],
|
||||
"params": [4]
|
||||
},
|
||||
"fp64": {
|
||||
"file": "float_add",
|
||||
"template": "FloatAdd",
|
||||
"params": [11, 52]
|
||||
},
|
||||
"fp32": {
|
||||
"file": "float_add",
|
||||
"template": "FloatAdd",
|
||||
"params": [8, 23]
|
||||
},
|
||||
"fibonacci_11": {
|
||||
"file": "fibonacci",
|
||||
"template": "Fibonacci",
|
||||
"params": [11]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
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];
|
||||
}
|
||||
|
||||
// Fibonacci with custom starting numbers, recursive & inefficient
|
||||
template FibonacciRecursive(n) {
|
||||
signal input in[2];
|
||||
signal output out;
|
||||
component f1, f2;
|
||||
if (n <= 1) {
|
||||
out <== in[n];
|
||||
} else {
|
||||
f1 = FibonacciRecursive(n-1);
|
||||
f1.in <== in;
|
||||
f2 = FibonacciRecursive(n-2);
|
||||
f2.in <== in;
|
||||
out <== f1.out + f2.out;
|
||||
}
|
||||
}
|
||||
@@ -1,417 +0,0 @@
|
||||
pragma circom 2.0.0;
|
||||
|
||||
// circuits adapted from https://github.com/rdi-berkeley/zkp-mooc-lab
|
||||
|
||||
include "circomlib/circuits/comparators.circom";
|
||||
include "circomlib/circuits/switcher.circom";
|
||||
include "circomlib/circuits/gates.circom";
|
||||
include "circomlib/circuits/bitify.circom";
|
||||
|
||||
/*
|
||||
* Finds Math.floor(log2(n))
|
||||
*/
|
||||
function log2(n) {
|
||||
var tmp = 1, ans = 1;
|
||||
while (tmp < n) {
|
||||
ans++;
|
||||
tmp *= 2;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
/*
|
||||
* Basically `out = cond ? ifTrue : ifFalse`
|
||||
*/
|
||||
template IfElse() {
|
||||
signal input cond;
|
||||
signal input ifTrue;
|
||||
signal input ifFalse;
|
||||
signal output out;
|
||||
|
||||
// cond * T - cond * F + F
|
||||
// 0 * T - 0 * F + F = 0 - 0 + F = F
|
||||
// 1 * T - 1 * F + F = T - F + F = T
|
||||
out <== cond * (ifTrue - ifFalse) + ifFalse;
|
||||
}
|
||||
|
||||
/*
|
||||
* Outputs `out` = 1 if `in` is at most `b` bits long, and 0 otherwise.
|
||||
*/
|
||||
template CheckBitLength(b) {
|
||||
assert(b < 254);
|
||||
signal input in;
|
||||
signal output out;
|
||||
|
||||
// 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
|
||||
component eq = IsEqual();
|
||||
eq.in[0] <== sum_of_bits;
|
||||
eq.in[1] <== in;
|
||||
out <== eq.out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enforces the well-formedness of an exponent-mantissa pair (e, m), which is defined as follows:
|
||||
* if `e` is zero, then `m` must be zero
|
||||
* else, `e` must be at most `k` bits long, and `m` must be in the range [2^p, 2^p+1)
|
||||
*/
|
||||
template CheckWellFormedness(k, p) {
|
||||
signal input e;
|
||||
signal input m;
|
||||
|
||||
// check if `e` is zero
|
||||
component is_e_zero = IsZero();
|
||||
is_e_zero.in <== e;
|
||||
|
||||
// Case I: `e` is zero
|
||||
//// `m` must be zero
|
||||
component is_m_zero = IsZero();
|
||||
is_m_zero.in <== m;
|
||||
|
||||
// Case II: `e` is nonzero
|
||||
//// `e` is `k` bits
|
||||
component check_e_bits = CheckBitLength(k);
|
||||
check_e_bits.in <== e;
|
||||
//// `m` is `p`+1 bits with the MSB equal to 1
|
||||
//// equivalent to check `m` - 2^`p` is in `p` bits
|
||||
component check_m_bits = CheckBitLength(p);
|
||||
check_m_bits.in <== m - (1 << p);
|
||||
|
||||
// choose the right checks based on `is_e_zero`
|
||||
component if_else = IfElse();
|
||||
if_else.cond <== is_e_zero.out;
|
||||
if_else.ifTrue <== is_m_zero.out;
|
||||
//// check_m_bits.out * check_e_bits.out is equivalent to check_m_bits.out AND check_e_bits.out
|
||||
if_else.ifFalse <== check_m_bits.out * check_e_bits.out;
|
||||
|
||||
// assert that those checks passed
|
||||
if_else.out === 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Right-shifts `x` by `shift` bits to output `y`, where `shift` is a public circuit parameter.
|
||||
*/
|
||||
template RightShift(b, shift) {
|
||||
assert(shift < b);
|
||||
signal input x;
|
||||
signal output y;
|
||||
|
||||
// convert number to bits
|
||||
component x_bits = Num2Bits(b);
|
||||
x_bits.in <== x;
|
||||
|
||||
// do the shifting
|
||||
signal y_bits[b-shift];
|
||||
for (var i = 0; i < b-shift; i++) {
|
||||
y_bits[i] <== x_bits.out[shift+i];
|
||||
}
|
||||
|
||||
// convert shifted bits to number
|
||||
component y_num = Bits2Num(b-shift);
|
||||
y_num.in <== y_bits;
|
||||
y <== y_num.out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Rounds the input floating-point number and checks to ensure that rounding does not make the mantissa unnormalized.
|
||||
* Rounding is necessary to prevent the bitlength of the mantissa from growing with each successive operation.
|
||||
* The input is a normalized floating-point number (e, m) with precision `P`, where `e` is a `k`-bit exponent and `m` is a `P`+1-bit mantissa.
|
||||
* The output is a normalized floating-point number (e_out, m_out) representing the same value with a lower precision `p`.
|
||||
*/
|
||||
template RoundAndCheck(k, p, P) {
|
||||
signal input e;
|
||||
signal input m;
|
||||
signal output e_out;
|
||||
signal output m_out;
|
||||
assert(P > p);
|
||||
|
||||
// check if no overflow occurs
|
||||
component if_no_overflow = LessThan(P+1);
|
||||
if_no_overflow.in[0] <== m;
|
||||
if_no_overflow.in[1] <== (1 << (P+1)) - (1 << (P-p-1));
|
||||
signal no_overflow <== if_no_overflow.out;
|
||||
|
||||
var round_amt = P-p;
|
||||
// Case I: no overflow
|
||||
// compute (m + 2^{round_amt-1}) >> round_amt
|
||||
var m_prime = m + (1 << (round_amt-1));
|
||||
component right_shift = RightShift(P+2, round_amt);
|
||||
right_shift.x <== m_prime;
|
||||
var m_out_1 = right_shift.y;
|
||||
var e_out_1 = e;
|
||||
|
||||
// Case II: overflow
|
||||
var e_out_2 = e + 1;
|
||||
var m_out_2 = (1 << p);
|
||||
|
||||
// select right output based on no_overflow
|
||||
component if_else[2];
|
||||
for (var i = 0; i < 2; i++) {
|
||||
if_else[i] = IfElse();
|
||||
if_else[i].cond <== no_overflow;
|
||||
}
|
||||
if_else[0].ifTrue <== e_out_1;
|
||||
if_else[0].ifFalse <== e_out_2;
|
||||
if_else[1].ifTrue <== m_out_1;
|
||||
if_else[1].ifFalse <== m_out_2;
|
||||
e_out <== if_else[0].out;
|
||||
m_out <== if_else[1].out;
|
||||
}
|
||||
|
||||
template Num2BitsWithSkipChecks(b) {
|
||||
signal input in;
|
||||
signal input skip_checks;
|
||||
signal output out[b];
|
||||
|
||||
for (var i = 0; i < b; i++) {
|
||||
out[i] <-- (in >> i) & 1;
|
||||
out[i] * (1 - out[i]) === 0;
|
||||
}
|
||||
var sum_of_bits = 0;
|
||||
for (var i = 0; i < b; i++) {
|
||||
sum_of_bits += (2 ** i) * out[i];
|
||||
}
|
||||
|
||||
// is always true if skip_checks is 1
|
||||
(sum_of_bits - in) * (1 - skip_checks) === 0;
|
||||
}
|
||||
|
||||
template LessThanWithSkipChecks(n) {
|
||||
assert(n <= 252);
|
||||
signal input in[2];
|
||||
signal input skip_checks;
|
||||
signal output out;
|
||||
|
||||
component n2b = Num2BitsWithSkipChecks(n+1);
|
||||
n2b.in <== in[0] + (1<<n) - in[1];
|
||||
n2b.skip_checks <== skip_checks;
|
||||
out <== 1-n2b.out[n];
|
||||
}
|
||||
|
||||
/*
|
||||
* Left-shifts `x` by `shift` bits to output `y`.
|
||||
* Enforces 0 <= `shift` < `shift_bound`.
|
||||
* If `skip_checks` = 1, then we don't care about the output
|
||||
* and the `shift_bound` constraint is not enforced.
|
||||
*/
|
||||
template LeftShift(shift_bound) {
|
||||
signal input x;
|
||||
signal input shift;
|
||||
signal input skip_checks;
|
||||
signal output y;
|
||||
|
||||
// find number of bits in shift_bound
|
||||
var n = log2(shift_bound) + 1;
|
||||
|
||||
// convert "shift" to bits
|
||||
component shift_bits = Num2BitsWithSkipChecks(n);
|
||||
shift_bits.in <== shift;
|
||||
shift_bits.skip_checks <== skip_checks;
|
||||
|
||||
// check "shift" < "shift_bound"
|
||||
component less_than = LessThanWithSkipChecks(n);
|
||||
less_than.in[0] <== shift;
|
||||
less_than.in[1] <== shift_bound;
|
||||
less_than.skip_checks <== skip_checks;
|
||||
(less_than.out - 1) * (1 - skip_checks) === 0;
|
||||
|
||||
// compute pow2_shift from bits
|
||||
// represents the shift amount
|
||||
var pow2_shift = 1;
|
||||
component muxes[n];
|
||||
for (var i = 0; i < n; i++) {
|
||||
muxes[i] = IfElse();
|
||||
muxes[i].cond <== shift_bits.out[i];
|
||||
muxes[i].ifTrue <== pow2_shift * (2 ** (2 ** i));
|
||||
muxes[i].ifFalse <== pow2_shift;
|
||||
pow2_shift = muxes[i].out;
|
||||
}
|
||||
|
||||
// if skip checks, set pow2_shift to 0
|
||||
component if_else = IfElse();
|
||||
if_else.cond <== skip_checks;
|
||||
if_else.ifTrue <== 0;
|
||||
if_else.ifFalse <== pow2_shift;
|
||||
pow2_shift = if_else.out; // not <== because it's a variable
|
||||
|
||||
// do the shift
|
||||
y <== x * pow2_shift;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the Most-Significant Non-Zero Bit (MSNZB) of `in`, where `in` is assumed to be non-zero value of `b` bits.
|
||||
* Outputs the MSNZB as a one-hot vector `one_hot` of `b` bits, where `one_hot`[i] = 1 if MSNZB(`in`) = i and 0 otherwise.
|
||||
* The MSNZB is output as a one-hot vector to reduce the number of constraints in the subsequent `Normalize` template.
|
||||
* Enforces that `in` is non-zero as MSNZB(0) is undefined.
|
||||
* If `skip_checks` = 1, then we don't care about the output and the non-zero constraint is not enforced.
|
||||
*/
|
||||
template MSNZB(b) {
|
||||
signal input in;
|
||||
signal input skip_checks;
|
||||
signal output one_hot[b];
|
||||
|
||||
// compute ell, ensuring that it is made of bits too
|
||||
for (var i = 0; i < b; i++) {
|
||||
var temp;
|
||||
if (((1 << i) <= in) && (in < (1 << (i + 1)))) {
|
||||
temp = 1;
|
||||
} else {
|
||||
temp = 0;
|
||||
}
|
||||
one_hot[i] <-- temp;
|
||||
}
|
||||
|
||||
// verify that one_hot only has bits, and has only one set bit
|
||||
var sum_of_bits = 0;
|
||||
for (var i = 0; i < b; i++) {
|
||||
sum_of_bits += one_hot[i];
|
||||
one_hot[i] * (1 - one_hot[i]) === 0; // is bit
|
||||
}
|
||||
(1 - sum_of_bits) * (1 - skip_checks) === 0;
|
||||
|
||||
// verify that the set bit is at correct place
|
||||
var pow2_ell = 0;
|
||||
var pow2_ell_plus1 = 0;
|
||||
for (var i = 0; i < b; i++) {
|
||||
pow2_ell += one_hot[i] * (1 << i);
|
||||
pow2_ell_plus1 += one_hot[i] * (1 << (i + 1));
|
||||
}
|
||||
component lt1 = LessThan(b+1);
|
||||
lt1.in[0] <== in;
|
||||
lt1.in[1] <== pow2_ell_plus1;
|
||||
(lt1.out - 1) * (1 - skip_checks) === 0;
|
||||
component lt2 = LessThan(b);
|
||||
lt2.in[0] <== pow2_ell - 1;
|
||||
lt2.in[1] <== in;
|
||||
(lt2.out - 1) * (1 - skip_checks) === 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Normalizes the input floating-point number.
|
||||
* The input is a floating-point number with a `k`-bit exponent `e` and a `P`+1-bit *unnormalized* mantissa `m` with precision `p`, where `m` is assumed to be non-zero.
|
||||
* The output is a floating-point number representing the same value with exponent `e_out` and a *normalized* mantissa `m_out` of `P`+1-bits and precision `P`.
|
||||
* Enforces that `m` is non-zero as a zero-value can not be normalized.
|
||||
* If `skip_checks` = 1, then we don't care about the output and the non-zero constraint is not enforced.
|
||||
*/
|
||||
template Normalize(k, p, P) {
|
||||
signal input e;
|
||||
signal input m;
|
||||
signal input skip_checks;
|
||||
signal output e_out;
|
||||
signal output m_out;
|
||||
assert(P > p);
|
||||
|
||||
// compute ell = MSNZB
|
||||
component msnzb = MSNZB(P+1);
|
||||
msnzb.in <== m;
|
||||
msnzb.skip_checks <== skip_checks;
|
||||
|
||||
// compute ell and L = 2 ** (P - ell)
|
||||
var ell, L;
|
||||
for (var i = 0; i < P+1; i++) {
|
||||
ell += msnzb.one_hot[i] * i;
|
||||
L += msnzb.one_hot[i] * (1 << (P - i));
|
||||
}
|
||||
|
||||
// return
|
||||
e_out <== e + ell - p;
|
||||
m_out <== m * L;
|
||||
}
|
||||
|
||||
/*
|
||||
* Adds two floating-point numbers.
|
||||
* The inputs are normalized floating-point numbers with `k`-bit exponents `e` and `p`+1-bit mantissas `m` with scale `p`.
|
||||
* Does not assume that the inputs are well-formed and makes appropriate checks for the same.
|
||||
* The output is a normalized floating-point number with exponent `e_out` and mantissa `m_out` of `p`+1-bits and scale `p`.
|
||||
* Enforces that inputs are well-formed.
|
||||
*/
|
||||
template FloatAdd(k, p) {
|
||||
signal input e[2];
|
||||
signal input m[2];
|
||||
signal output e_out;
|
||||
signal output m_out;
|
||||
|
||||
// check well formedness
|
||||
component well_form[2];
|
||||
for (var i = 0; i < 2; i++) {
|
||||
well_form[i] = CheckWellFormedness(k, p);
|
||||
well_form[i].e <== e[i];
|
||||
well_form[i].m <== m[i];
|
||||
}
|
||||
|
||||
// find the larger magnitude
|
||||
var magn[2];
|
||||
component larger_magn = LessThan(k+p+1);
|
||||
for (var i = 0; i < 2; i++) {
|
||||
magn[i] = (e[i] * (1 << (p+1))) + m[i];
|
||||
larger_magn.in[i] <== magn[i];
|
||||
}
|
||||
signal is_input2_larger <== larger_magn.out;
|
||||
|
||||
// arrange by magnitude
|
||||
var input_1[2] = [e[0], m[0]];
|
||||
var input_2[2] = [e[1], m[1]];
|
||||
component switcher[2];
|
||||
for (var i = 0; i < 2; i++) {
|
||||
switcher[i] = Switcher();
|
||||
switcher[i].sel <== is_input2_larger;
|
||||
switcher[i].L <== input_1[i];
|
||||
switcher[i].R <== input_2[i];
|
||||
}
|
||||
var alpha_e = switcher[0].outL;
|
||||
var alpha_m = switcher[1].outL;
|
||||
var beta_e = switcher[0].outR;
|
||||
var beta_m = switcher[1].outR;
|
||||
|
||||
// if-else part
|
||||
var diff_e = alpha_e - beta_e;
|
||||
component compare_diff_e = LessThan(k);
|
||||
compare_diff_e.in[0] <== p+1;
|
||||
compare_diff_e.in[1] <== diff_e;
|
||||
component is_alpha_e_zero = IsZero();
|
||||
is_alpha_e_zero.in <== alpha_e;
|
||||
|
||||
//// case 1
|
||||
component or = OR();
|
||||
or.a <== compare_diff_e.out;
|
||||
or.b <== is_alpha_e_zero.out;
|
||||
signal is_case_1 <== or.out; // true branch
|
||||
var case_1_output[2] = [alpha_e, alpha_m];
|
||||
|
||||
//// case 2
|
||||
component shl = LeftShift(p+2);
|
||||
shl.x <== alpha_m;
|
||||
shl.shift <== diff_e;
|
||||
shl.skip_checks <== is_case_1; // skip if this isnt the case
|
||||
alpha_m = shl.y;
|
||||
var mantissa = alpha_m + beta_m;
|
||||
var exponent = beta_e;
|
||||
component normalize = Normalize(k, p, 2*p + 1);
|
||||
normalize.m <== mantissa;
|
||||
normalize.e <== exponent;
|
||||
normalize.skip_checks <== is_case_1;
|
||||
component rnc = RoundAndCheck(k, p, 2*p + 1);
|
||||
rnc.m <== normalize.m_out;
|
||||
rnc.e <== normalize.e_out;
|
||||
var case_2_output[2] = [rnc.e_out, rnc.m_out];
|
||||
|
||||
// return
|
||||
component if_else[2];
|
||||
for (var i = 0; i < 2; i++) {
|
||||
if_else[i] = IfElse();
|
||||
if_else[i].cond <== is_case_1;
|
||||
if_else[i].ifTrue <== case_1_output[i];
|
||||
if_else[i].ifFalse <== case_2_output[i];
|
||||
}
|
||||
e_out <== if_else[0].out;
|
||||
m_out <== if_else[1].out;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// auto-generated by instantiate.js
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "../fibonacci.circom";
|
||||
|
||||
component main = Fibonacci(11);
|
||||
@@ -1,6 +0,0 @@
|
||||
// auto-generated by instantiate.js
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "../float_add.circom";
|
||||
|
||||
component main = FloatAdd(8, 23);
|
||||
@@ -1,6 +0,0 @@
|
||||
// auto-generated by instantiate.js
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "../float_add.circom";
|
||||
|
||||
component main = FloatAdd(11, 52);
|
||||
@@ -1,6 +0,0 @@
|
||||
// auto-generated by circomkit
|
||||
pragma circom 2.1.0;
|
||||
|
||||
include "../multiplier.circom";
|
||||
|
||||
component main = Multiplier(3);
|
||||
@@ -1,6 +0,0 @@
|
||||
// auto-generated by instantiate.js
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "../sudoku.circom";
|
||||
|
||||
component main {public[puzzle]} = Sudoku(2);
|
||||
@@ -1,6 +0,0 @@
|
||||
// auto-generated by instantiate.js
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "../sudoku.circom";
|
||||
|
||||
component main {public[puzzle]} = Sudoku(3);
|
||||
@@ -1,53 +1,16 @@
|
||||
pragma circom 2.0.0;
|
||||
|
||||
template MultiplicationGate() {
|
||||
signal input in[2];
|
||||
signal output out <== in[0] * in[1];
|
||||
}
|
||||
|
||||
template Multiplier(N) {
|
||||
assert(N > 1);
|
||||
signal input in[N];
|
||||
signal output out;
|
||||
component gate[N-1];
|
||||
|
||||
// instantiate gates
|
||||
for (var i = 0; i < N-1; i++) {
|
||||
gate[i] = MultiplicationGate();
|
||||
}
|
||||
|
||||
// multiply
|
||||
gate[0].in <== [in[0], in[1]];
|
||||
for (var i = 0; i < N-2; i++) {
|
||||
gate[i+1].in <== [gate[i].out, in[i+2]];
|
||||
}
|
||||
out <== gate[N-2].out;
|
||||
}
|
||||
|
||||
// Alternative way using anonymous components
|
||||
template MultiplierAnonymous(N) {
|
||||
assert(N > 1);
|
||||
signal input in[N];
|
||||
signal output out;
|
||||
|
||||
signal inner[N-1];
|
||||
inner[0] <== MultiplicationGate()([in[0], in[1]]);
|
||||
for(var i = 0; i < N-2; i++) {
|
||||
inner[i+1] <== MultiplicationGate()([inner[i], in[i+2]]);
|
||||
}
|
||||
out <== inner[N-2];
|
||||
}
|
||||
|
||||
// Alternative way without the gate component
|
||||
template MultiplierSimple(N) {
|
||||
assert(N > 1);
|
||||
signal input in[N];
|
||||
signal output out;
|
||||
|
||||
signal inner[N-1];
|
||||
inner[0] <== in[0] * in[1];
|
||||
for(var i = 2; i < N; i++) {
|
||||
inner[i-1] <== inner[i-2] * in[i];
|
||||
}
|
||||
|
||||
out <== inner[N-2];
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "circomlib/circuits/sha256/sha256.circom";
|
||||
include "circomlib/circuits/bitify.circom";
|
||||
|
||||
/**
|
||||
* Wrapper around SHA256 to support bytes as input instead of bits
|
||||
* @param N The number of input bytes
|
||||
* @input in The input bytes
|
||||
* @output out The SHA256 output of the n input bytes, in bytes
|
||||
*
|
||||
* SOURCE: https://github.com/celer-network/zk-benchmark/blob/main/circom/circuits/sha256/sha256_bytes.circom
|
||||
*/
|
||||
template Sha256Bytes(N) {
|
||||
signal input in[N];
|
||||
signal output out[32];
|
||||
|
||||
// convert input bytes to bits
|
||||
component byte_to_bits[N];
|
||||
for (var i = 0; i < N; i++) {
|
||||
byte_to_bits[i] = Num2Bits(8);
|
||||
byte_to_bits[i].in <== in[i];
|
||||
}
|
||||
|
||||
// sha256 over bits
|
||||
component sha256 = Sha256(N*8);
|
||||
for (var i = 0; i < N; i++) {
|
||||
for (var j = 0; j < 8; j++) {
|
||||
sha256.in[i*8+j] <== byte_to_bits[i].out[7-j];
|
||||
}
|
||||
}
|
||||
|
||||
// convert output bytes to bits
|
||||
component bits_to_bytes[32];
|
||||
for (var i = 0; i < 32; i++) {
|
||||
bits_to_bytes[i] = Bits2Num(8);
|
||||
for (var j = 0; j < 8; j++) {
|
||||
bits_to_bytes[i].in[7-j] <== sha256.out[i*8+j];
|
||||
}
|
||||
out[i] <== bits_to_bytes[i].out;
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "circomlib/circuits/bitify.circom";
|
||||
|
||||
// Finds Math.floor(log2(n))
|
||||
function log2(n) {
|
||||
var tmp = 1, ans = 1;
|
||||
while (tmp < n) {
|
||||
ans++;
|
||||
tmp *= 2;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
// Assert that two elements are not equal
|
||||
template NonEqual() {
|
||||
signal input in[2];
|
||||
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;
|
||||
}
|
||||
|
||||
// Assert that number is representable by b-bits
|
||||
template AssertBitLength(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;
|
||||
}
|
||||
|
||||
// Checks that `in` is in range [MIN, MAX]
|
||||
template InRange(MIN, MAX) {
|
||||
assert(MIN < MAX);
|
||||
signal input in;
|
||||
|
||||
// number of bits to represent MAX
|
||||
var b = log2(MAX) + 1;
|
||||
|
||||
component lowerBound = AssertBitLength(b);
|
||||
component upperBound = AssertBitLength(b);
|
||||
lowerBound.in <== in - MIN; // e.g. 1 - 1 = 0 (for 0 <= in)
|
||||
upperBound.in <== in + (2 ** b) - MAX - 1; // e.g. 9 + (15 - 9) = 15 (for in <= 15)
|
||||
}
|
||||
|
||||
// Assert that all given values are unique
|
||||
template Distinct(n) {
|
||||
signal input in[n];
|
||||
component nonEqual[n][n]; // TODO; has extra comps here
|
||||
for(var i = 0; i < n; i++){
|
||||
for(var j = 0; j < i; j++){
|
||||
nonEqual[i][j] = NonEqual();
|
||||
nonEqual[i][j].in <== [in[i], in[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template Sudoku(n_sqrt) {
|
||||
var n = n_sqrt * n_sqrt;
|
||||
signal input solution[n][n]; // solution is a 2D array of numbers
|
||||
signal input puzzle[n][n]; // puzzle is the same, but a zero indicates a blank
|
||||
|
||||
// ensure that solution & puzzle agrees
|
||||
for (var row_i = 0; row_i < n; row_i++) {
|
||||
for (var col_i = 0; col_i < n; col_i++) {
|
||||
// puzzle is either empty (0), or the same as solution
|
||||
puzzle[row_i][col_i] * (puzzle[row_i][col_i] - solution[row_i][col_i]) === 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ensure all values in the solution are in range
|
||||
component inRange[n][n];
|
||||
for (var row_i = 0; row_i < n; row_i++) {
|
||||
for (var col_i = 0; col_i < n; col_i++) {
|
||||
inRange[row_i][col_i] = InRange(1, n);
|
||||
inRange[row_i][col_i].in <== solution[row_i][col_i];
|
||||
}
|
||||
}
|
||||
|
||||
// ensure all values in the solution are distinct
|
||||
component distinctRows[n];
|
||||
for (var row_i = 0; row_i < n; row_i++) {
|
||||
for (var col_i = 0; col_i < n; col_i++) {
|
||||
if (row_i == 0) {
|
||||
distinctRows[col_i] = Distinct(n);
|
||||
}
|
||||
distinctRows[col_i].in[row_i] <== solution[row_i][col_i];
|
||||
}
|
||||
}
|
||||
component distinctCols[n];
|
||||
for (var col_i = 0; col_i < n; col_i++) {
|
||||
for (var row_i = 0; row_i < n; row_i++) {
|
||||
if (col_i == 0) {
|
||||
distinctCols[row_i] = Distinct(n);
|
||||
}
|
||||
distinctCols[row_i].in[col_i] <== solution[row_i][col_i];
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that all values in squares are distinct
|
||||
component distinctSquares[n];
|
||||
var s_i = 0;
|
||||
for (var sr_i = 0; sr_i < n_sqrt; sr_i++) {
|
||||
for (var sc_i = 0; sc_i < n_sqrt; sc_i++) {
|
||||
// square index
|
||||
distinctSquares[s_i] = Distinct(n);
|
||||
|
||||
// (r, c) now marks the start of this square
|
||||
var r = sr_i * n_sqrt;
|
||||
var c = sc_i * n_sqrt;
|
||||
var i = 0;
|
||||
for (var row_i = r; row_i < r + n_sqrt; row_i++) {
|
||||
for (var col_i = c; col_i < c + n_sqrt; col_i++) {
|
||||
distinctSquares[s_i].in[i] <== solution[row_i][col_i];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
s_i++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"in": [1, 1]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"e": ["1122", "1024"],
|
||||
"m": ["7807742059002284", "7045130465601185"]
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"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]
|
||||
]
|
||||
}
|
||||
@@ -20,7 +20,7 @@ const USAGE = `Usage:
|
||||
contract circuit
|
||||
|
||||
Commence circuit-specific setup.
|
||||
setup circuit
|
||||
setup circuit [ptau-path]
|
||||
|
||||
Download the PTAU file needed for the circuit.
|
||||
ptau circuit
|
||||
|
||||
209
src/circomkit.ts
209
src/circomkit.ts
@@ -1,15 +1,24 @@
|
||||
const snarkjs = require('snarkjs');
|
||||
const wasm_tester = require('circom_tester').wasm;
|
||||
import {writeFileSync, readFileSync, existsSync, mkdirSync} from 'fs';
|
||||
import {writeFileSync, readFileSync, existsSync, mkdirSync, rmSync, renameSync} from 'fs';
|
||||
import {readFile, rm, writeFile} from 'fs/promises';
|
||||
import {instantiate} from './utils/instantiate';
|
||||
import {downloadPtau, getPtauName} from './utils/ptau';
|
||||
import type {CircuitConfig, R1CSInfoType} from './types/circuit';
|
||||
import type {CircomkitConfig, CircuitInputPathBuilders, CircuitPathBuilders} from './types/circomkit';
|
||||
import type {
|
||||
CircomkitConfig,
|
||||
CircomkitConfigOverrides,
|
||||
CircuitInputPathBuilders,
|
||||
CircuitPathBuilders,
|
||||
} from './types/circomkit';
|
||||
import {randomBytes} from 'crypto';
|
||||
import {CircomWasmTester} from './types/wasmTester';
|
||||
import WasmTester from './testers/wasmTester';
|
||||
import ProofTester from './testers/proofTester';
|
||||
|
||||
/** Default configurations */
|
||||
const defaultConfig: Readonly<CircomkitConfig> = {
|
||||
proofSystem: 'plonk',
|
||||
proofSystem: 'groth16',
|
||||
curve: 'bn128',
|
||||
version: '2.1.0',
|
||||
silent: false,
|
||||
@@ -26,14 +35,24 @@ const defaultConfig: Readonly<CircomkitConfig> = {
|
||||
json: false,
|
||||
include: ['./node_modules'],
|
||||
},
|
||||
groth16: {
|
||||
numContributions: 1,
|
||||
askForEntropy: false,
|
||||
},
|
||||
colors: {
|
||||
title: '\u001b[0;34m', // blue
|
||||
log: '\u001b[2;37m', // gray
|
||||
error: '\u001b[0;31m', // red
|
||||
success: '\u001b[0;32m', // green
|
||||
warn: '\u001b[0;33m', // yellow
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: maybe pass a logger using logplease to snarkjs?
|
||||
|
||||
// TODO: write a "check dependencies" module to decide which
|
||||
// components to create if missing, such as WASM / R1CS or PKEY
|
||||
|
||||
/**
|
||||
* Circomkit is an opinionated wrapper around a few
|
||||
* [Snarkjs](../../node_modules/snarkjs/main.js) functions.
|
||||
@@ -45,7 +64,7 @@ const defaultConfig: Readonly<CircomkitConfig> = {
|
||||
export class Circomkit {
|
||||
readonly config: CircomkitConfig;
|
||||
|
||||
constructor(overrides: Partial<CircomkitConfig> = {}) {
|
||||
constructor(overrides: CircomkitConfigOverrides = {}) {
|
||||
// override default options with the user-provided ones
|
||||
const config: CircomkitConfig = Object.assign({}, overrides, defaultConfig);
|
||||
|
||||
@@ -111,7 +130,7 @@ export class Circomkit {
|
||||
* @param type path type
|
||||
* @returns path
|
||||
*/
|
||||
private path2(circuit: string, input: string, type: CircuitInputPathBuilders): string {
|
||||
private pathWithInput(circuit: string, input: string, type: CircuitInputPathBuilders): string {
|
||||
const dir = `${this.config.dirs.build}/${circuit}/${input}`;
|
||||
switch (type) {
|
||||
case 'dir':
|
||||
@@ -129,6 +148,18 @@ export class Circomkit {
|
||||
}
|
||||
}
|
||||
|
||||
/** Given a PTAU name, returns the relative path. */
|
||||
private pathPtau(ptauName: string): string {
|
||||
return `${this.config.dirs.ptau}/${ptauName}`;
|
||||
}
|
||||
|
||||
/** Given a circuit & id name, returns the relative path to phase-2 PTAU.
|
||||
* This is used in particular by Groth16's circuit-specific setup phase.
|
||||
*/
|
||||
private pathZkey(circuit: string, id: number): string {
|
||||
return `${this.config.dirs.build}/${circuit}/${circuit}_${id}.zkey`;
|
||||
}
|
||||
|
||||
/** Clean build files and the main component. */
|
||||
async clean(circuit: string) {
|
||||
await Promise.all([
|
||||
@@ -156,10 +187,14 @@ export class Circomkit {
|
||||
const ptauName = getPtauName(constraints);
|
||||
|
||||
// return if ptau exists already
|
||||
const ptauPath = `${this.config.dirs.ptau}/${ptauName}`;
|
||||
const ptauPath = this.pathPtau(ptauName);
|
||||
if (existsSync(ptauPath)) {
|
||||
return ptauPath;
|
||||
} else {
|
||||
if (this.config.curve !== 'bn128') {
|
||||
throw new Error('Auto-downloading PTAU only allowed for bn128 at the moment.');
|
||||
}
|
||||
mkdirSync(this.config.dirs.ptau);
|
||||
this.log('Downloading ' + ptauName + '...');
|
||||
return await downloadPtau(ptauName, this.config.dirs.ptau);
|
||||
}
|
||||
@@ -176,7 +211,7 @@ export class Circomkit {
|
||||
const targetPath = this.path(circuit, 'target');
|
||||
|
||||
if (!existsSync(targetPath)) {
|
||||
this.log('Main component does not exist, creating it now.');
|
||||
this.log('Main component does not exist, creating it now.', 'warn');
|
||||
const path = this.instantiate(circuit);
|
||||
this.log('Main component created at: ' + path);
|
||||
}
|
||||
@@ -220,7 +255,9 @@ export class Circomkit {
|
||||
async calldata(circuit: string, input: string) {
|
||||
const [pubs, proof] = (
|
||||
await Promise.all(
|
||||
[this.path2(circuit, input, 'pubs'), this.path2(circuit, input, 'proof')].map(path => readFile(path, 'utf-8'))
|
||||
(['pubs', 'proof'] as const)
|
||||
.map(type => this.pathWithInput(circuit, input, type))
|
||||
.map(path => readFile(path, 'utf-8'))
|
||||
)
|
||||
).map(content => JSON.parse(content));
|
||||
return await snarkjs[this.config.proofSystem].exportSolidityCallData(proof, pubs);
|
||||
@@ -229,40 +266,59 @@ export class Circomkit {
|
||||
/** Instantiate the `main` component.
|
||||
* @returns path to created main component
|
||||
*/
|
||||
instantiate(circuit: string) {
|
||||
const circuitConfig = this.readCircuitConfig(circuit);
|
||||
const target = instantiate(circuit, {
|
||||
...circuitConfig,
|
||||
dir: this.config.dirs.main,
|
||||
version: this.config.version,
|
||||
});
|
||||
return target;
|
||||
instantiate(circuit: string, config?: CircuitConfig) {
|
||||
if (config) {
|
||||
return instantiate(circuit, config);
|
||||
} else {
|
||||
const circuitConfig = this.readCircuitConfig(circuit);
|
||||
return instantiate(circuit, {
|
||||
...circuitConfig,
|
||||
dir: this.config.dirs.main,
|
||||
version: this.config.version,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Generate a proof.
|
||||
* @returns path to directory where public signals and proof are created
|
||||
*/
|
||||
async prove(circuit: string, input: string) {
|
||||
const jsonInput = JSON.parse(readFileSync(this.path2(circuit, input, 'in'), 'utf-8'));
|
||||
const fullProof = await snarkjs[this.config.proofSystem].fullProve(
|
||||
jsonInput,
|
||||
this.path(circuit, 'wasm'),
|
||||
this.path(circuit, 'pkey')
|
||||
);
|
||||
// create WASM if needed
|
||||
const wasmPath = this.path(circuit, 'wasm');
|
||||
if (!existsSync(wasmPath)) {
|
||||
this.log('WASM file does not exist, creating it now...', 'warn');
|
||||
await this.compile(circuit);
|
||||
}
|
||||
|
||||
const dir = this.path2(circuit, input, 'dir');
|
||||
// create PKEY if needed
|
||||
const pkeyPath = this.path(circuit, 'pkey');
|
||||
if (!existsSync(pkeyPath)) {
|
||||
this.log('Prover key does not exist, creating it now...', 'warn');
|
||||
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 fullProof = await snarkjs[this.config.proofSystem].fullProve(jsonInput, wasmPath, pkeyPath);
|
||||
|
||||
const dir = this.pathWithInput(circuit, input, 'dir');
|
||||
mkdirSync(dir, {recursive: true});
|
||||
await Promise.all([
|
||||
writeFile(this.path2(circuit, input, 'pubs'), this.prettyStringify(fullProof.publicSignals)),
|
||||
writeFile(this.path2(circuit, input, 'proof'), this.prettyStringify(fullProof.proof)),
|
||||
writeFile(this.pathWithInput(circuit, input, 'pubs'), this.prettyStringify(fullProof.publicSignals)),
|
||||
writeFile(this.pathWithInput(circuit, input, 'proof'), this.prettyStringify(fullProof.proof)),
|
||||
]);
|
||||
return dir;
|
||||
}
|
||||
|
||||
/** Commence a circuit-specific setup.
|
||||
* @returns path to prover key
|
||||
* @returns path to verifier key
|
||||
*/
|
||||
async setup(circuit: string) {
|
||||
async setup(circuit: string, ptauPath?: string) {
|
||||
const r1csPath = this.path(circuit, 'r1cs');
|
||||
const pkeyPath = this.path(circuit, 'pkey');
|
||||
const vkeyPath = this.path(circuit, 'vkey');
|
||||
@@ -274,14 +330,49 @@ export class Circomkit {
|
||||
}
|
||||
|
||||
// get ptau path
|
||||
this.log('Checking for PTAU file...');
|
||||
const ptau = await this.ptau(circuit);
|
||||
if (ptauPath) {
|
||||
this.log('Using provided PTAU: ' + ptauPath);
|
||||
} else {
|
||||
this.log('Checking for PTAU file...');
|
||||
ptauPath = await this.ptau(circuit);
|
||||
}
|
||||
|
||||
// circuit specific setup
|
||||
if (this.config.proofSystem === 'plonk') {
|
||||
await snarkjs.plonk.setup(this.path(circuit, 'r1cs'), ptau, pkeyPath);
|
||||
this.log('Beginning setup phase!');
|
||||
if (this.config.proofSystem === 'plonk' || this.config.proofSystem === 'fflonk') {
|
||||
// PLONK or FFLONK don't need specific setup
|
||||
await snarkjs[this.config.proofSystem].setup(r1csPath, ptauPath, pkeyPath);
|
||||
} else {
|
||||
throw new Error('Not implemented.');
|
||||
// Groth16 needs a specific setup with its own PTAU ceremony
|
||||
// initialize phase 2
|
||||
const ptau2Init = this.pathPtau(`${circuit}_init.zkey`);
|
||||
await snarkjs.powersOfTau.preparePhase2(ptauPath, ptau2Init);
|
||||
|
||||
// start PTAU generation
|
||||
let curZkey = this.pathZkey(circuit, 0);
|
||||
await snarkjs.zKey.newZKey(r1csPath, ptau2Init, curZkey);
|
||||
rmSync(ptau2Init);
|
||||
|
||||
// make contributions
|
||||
for (let contrib = 1; contrib <= this.config.groth16.numContributions; contrib++) {
|
||||
const nextZkey = this.pathZkey(circuit, contrib);
|
||||
|
||||
// entropy, if user wants to prompt give undefined
|
||||
this.log(`Making contribution: ${contrib}`);
|
||||
await snarkjs.zKey.contribute(
|
||||
curZkey,
|
||||
nextZkey,
|
||||
`${circuit}_${contrib}`,
|
||||
this.config.groth16.askForEntropy ? undefined : randomBytes(32) // entropy
|
||||
);
|
||||
|
||||
// remove current key, and move on to next one
|
||||
rmSync(curZkey);
|
||||
curZkey = nextZkey;
|
||||
}
|
||||
|
||||
// finally, rename the resulting key to pkey
|
||||
renameSync(curZkey, pkeyPath);
|
||||
}
|
||||
|
||||
// export verification key
|
||||
@@ -294,9 +385,11 @@ export class Circomkit {
|
||||
async verify(circuit: string, input: string) {
|
||||
const [vkey, pubs, proof] = (
|
||||
await Promise.all(
|
||||
[this.path(circuit, 'vkey'), this.path2(circuit, input, 'pubs'), this.path2(circuit, input, 'proof')].map(
|
||||
path => readFile(path, 'utf-8')
|
||||
)
|
||||
[
|
||||
this.path(circuit, 'vkey'),
|
||||
this.pathWithInput(circuit, input, 'pubs'),
|
||||
this.pathWithInput(circuit, input, 'proof'),
|
||||
].map(path => readFile(path, 'utf-8'))
|
||||
)
|
||||
).map(content => JSON.parse(content));
|
||||
|
||||
@@ -306,12 +399,52 @@ export class Circomkit {
|
||||
/** Calculates the witness for the given circuit and input. */
|
||||
async witness(circuit: string, input: string) {
|
||||
const wasmPath = this.path(circuit, 'wasm');
|
||||
const wtnsPath = this.path2(circuit, input, 'wtns');
|
||||
const outDir = this.path2(circuit, input, 'dir');
|
||||
const jsonInput = JSON.parse(readFileSync(this.path2(circuit, input, 'in'), 'utf-8'));
|
||||
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'));
|
||||
|
||||
mkdirSync(outDir, {recursive: true});
|
||||
await snarkjs.wtns.calculate(jsonInput, wasmPath, wtnsPath);
|
||||
return wtnsPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles and reutrns a circuit tester class instance.
|
||||
* @param circuit name of circuit
|
||||
* @param config circuit configuration
|
||||
* @returns a `WasmTester` instance
|
||||
*/
|
||||
async WasmTester<IN extends string[] = [], OUT extends string[] = []>(circuit: string, config: CircuitConfig) {
|
||||
config.dir ||= 'test';
|
||||
|
||||
// create circuit
|
||||
const targetPath = this.instantiate(circuit, config);
|
||||
const circomWasmTester: CircomWasmTester = await wasm_tester(targetPath, {
|
||||
output: undefined, // todo: check if this is ok
|
||||
prime: this.config.curve,
|
||||
verbose: this.config.compiler.verbose,
|
||||
O: this.config.compiler.optimization,
|
||||
json: this.config.compiler.json,
|
||||
include: this.config.compiler.include,
|
||||
wasm: true,
|
||||
sym: true,
|
||||
recompile: true,
|
||||
});
|
||||
|
||||
return new WasmTester<IN, OUT>(circomWasmTester);
|
||||
}
|
||||
|
||||
async ProofTester<IN extends string[] = []>(circuit: string, ptauPath?: string) {
|
||||
const wasmPath = this.path(circuit, 'wasm');
|
||||
const pkeyPath = this.path(circuit, 'pkey');
|
||||
const vkeyPath = this.path(circuit, 'vkey');
|
||||
|
||||
// create keys if required
|
||||
if (!existsSync(vkeyPath)) {
|
||||
this.log('Verifier key does not exist, creating it now...');
|
||||
await this.setup(circuit, ptauPath);
|
||||
}
|
||||
|
||||
return new ProofTester<IN>(wasmPath, pkeyPath, vkeyPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {Circomkit} from './circomkit';
|
||||
import ProofTester from './testers/proofTester';
|
||||
import WasmTester from './testers/wasmTester';
|
||||
import {instantiate} from './utils/instantiate';
|
||||
import {Circomkit} from './circomkit';
|
||||
export * from './types/circuit';
|
||||
|
||||
export {ProofTester, WasmTester, instantiate, Circomkit};
|
||||
export {Circomkit, ProofTester, WasmTester};
|
||||
|
||||
@@ -1,49 +1,20 @@
|
||||
import {readFileSync, existsSync} from 'fs';
|
||||
const snarkjs = require('snarkjs');
|
||||
import {expect} from 'chai';
|
||||
import {readFileSync} from 'fs';
|
||||
import type {CircuitSignals, FullProof} from '../types/circuit';
|
||||
import type {CircomkitConfig} from '../types/circomkit';
|
||||
|
||||
/** Allowed proof systems as defined in SnarkJS. */
|
||||
const PROOF_SYSTEMS = ['groth16', 'plonk'] as const;
|
||||
|
||||
/**
|
||||
* A more extensive Circuit class, able to generate proofs & verify them.
|
||||
* Assumes that prover key and verifier key have been computed.
|
||||
*/
|
||||
/** A tester that is able to generate proofs & verify them.
|
||||
* Use `expectFail` and `expectPass` to test out evaluations. */
|
||||
export default class ProofTester<IN extends string[] = []> {
|
||||
public readonly protocol: 'groth16' | 'plonk';
|
||||
private readonly wasmPath: string;
|
||||
private readonly proverKeyPath: string;
|
||||
private readonly verificationKeyPath: string;
|
||||
public readonly protocol: CircomkitConfig['proofSystem'];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private readonly verificationKey: any;
|
||||
public readonly verificationKey: any;
|
||||
|
||||
/**
|
||||
* Sets the paths & loads the verification key. The underlying proof system is checked by looking
|
||||
* at `verificationKey.protocol`.
|
||||
* @param circuit a proof tester
|
||||
*/
|
||||
constructor(circuit: string) {
|
||||
// find paths (computed w.r.t circuit name)
|
||||
this.wasmPath = `./build/${circuit}/${circuit}_js/${circuit}.wasm`;
|
||||
this.proverKeyPath = `./build/${circuit}/prover_key.zkey`;
|
||||
this.verificationKeyPath = `./build/${circuit}/verification_key.json`;
|
||||
constructor(readonly wasmPath: string, readonly pkeyPath: string, readonly vkeyPath: string) {
|
||||
this.verificationKey = JSON.parse(readFileSync(vkeyPath).toString()) as typeof this.verificationKey;
|
||||
|
||||
// ensure that paths exist
|
||||
const missing = [this.wasmPath, this.proverKeyPath, this.verificationKeyPath].filter(p => !existsSync(p));
|
||||
if (missing.length !== 0) {
|
||||
throw new Error('Missing files for' + circuit + ':\n' + missing.join('\n'));
|
||||
}
|
||||
|
||||
// load verification key
|
||||
this.verificationKey = JSON.parse(readFileSync(this.verificationKeyPath).toString()) as typeof this.verificationKey;
|
||||
|
||||
// check proof system
|
||||
const protocol = this.verificationKey.protocol;
|
||||
if (!PROOF_SYSTEMS.includes(protocol)) {
|
||||
throw new Error('Unknown protocol in verification key: ' + protocol);
|
||||
}
|
||||
this.protocol = protocol;
|
||||
this.protocol = this.verificationKey.protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,7 +24,7 @@ export default class ProofTester<IN extends string[] = []> {
|
||||
* @returns a proof and public signals
|
||||
*/
|
||||
async prove(input: CircuitSignals<IN>): Promise<FullProof> {
|
||||
return await snarkjs[this.protocol].fullProve(input, this.wasmPath, this.proverKeyPath);
|
||||
return await snarkjs[this.protocol].fullProve(input, this.wasmPath, this.pkeyPath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
const wasm_tester = require('circom_tester').wasm;
|
||||
import type {WitnessType, CircuitSignals, SymbolsType, SignalValueType, CircuitConfig} from '../types/circuit';
|
||||
import type {WitnessType, CircuitSignals, SymbolsType, SignalValueType} from '../types/circuit';
|
||||
import type {CircomWasmTester} from '../types/wasmTester';
|
||||
import {assert, expect} from 'chai';
|
||||
import {instantiate} from '../utils/instantiate';
|
||||
|
||||
/** A utility class to test your circuits. Use `expectFail` and `expectPass` to test out evaluations. */
|
||||
export default class WasmTester<IN extends readonly string[] = [], OUT extends readonly string[] = []> {
|
||||
@@ -12,23 +10,6 @@ export default class WasmTester<IN extends readonly string[] = [], OUT extends r
|
||||
symbols: SymbolsType | undefined;
|
||||
/** List of constraints, see {@link loadConstraints} */
|
||||
constraints: unknown[] | undefined;
|
||||
/**
|
||||
* Compiles and reutrns a circuit tester class instance.
|
||||
* @param circuit name of circuit
|
||||
* @param dir directory to read the circuit from, defaults to `test`
|
||||
* @returns a `WasmTester` instance
|
||||
*/
|
||||
static async new<IN extends string[] = [], OUT extends string[] = []>(
|
||||
circuitName: string,
|
||||
circuitConfig: CircuitConfig
|
||||
): Promise<WasmTester<IN, OUT>> {
|
||||
const dir = circuitConfig.dir || 'test';
|
||||
instantiate(circuitName, circuitConfig);
|
||||
const circomWasmTester: CircomWasmTester = await wasm_tester(`./circuits/${dir}/${circuitName}.circom`, {
|
||||
include: 'node_modules', // will link circomlib circuits
|
||||
});
|
||||
return new WasmTester<IN, OUT>(circomWasmTester);
|
||||
}
|
||||
|
||||
constructor(circomWasmTester: CircomWasmTester) {
|
||||
this.circomWasmTester = circomWasmTester;
|
||||
@@ -60,9 +41,7 @@ export default class WasmTester<IN extends readonly string[] = [], OUT extends r
|
||||
return this.circomWasmTester.calculateWitness(input, sanityCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of R1CS constraints to `this.constraints`
|
||||
*/
|
||||
/** Loads the list of R1CS constraints to `this.constraints`. */
|
||||
async loadConstraints(): Promise<void> {
|
||||
await this.circomWasmTester.loadConstraints();
|
||||
this.constraints = this.circomWasmTester.constraints;
|
||||
|
||||
@@ -3,7 +3,7 @@ type VersionType = `${number}.${number}.${number}`;
|
||||
|
||||
export type CircomkitConfig = {
|
||||
/** Proof system to be used. */
|
||||
proofSystem: 'groth16' | 'plonk';
|
||||
proofSystem: 'groth16' | 'plonk' | 'fflonk';
|
||||
/** Curve to be used, which defines the underlying prime field. */
|
||||
curve: 'bn128' | 'bls12381' | 'goldilocks';
|
||||
/** Directory overrides, it is best you leave this as is. */
|
||||
@@ -19,6 +19,13 @@ export type CircomkitConfig = {
|
||||
/** Directory to output circuit build files. */
|
||||
build: string;
|
||||
};
|
||||
/** Groth16-specific configurations */
|
||||
groth16: {
|
||||
/** Number of contributions */
|
||||
numContributions: number;
|
||||
/** Ask user input to create entropy */
|
||||
askForEntropy: boolean;
|
||||
};
|
||||
/** Version number for main components. */
|
||||
version: VersionType;
|
||||
/** Hide Circomkit logs */
|
||||
@@ -44,6 +51,7 @@ export type CircomkitConfig = {
|
||||
success: ColorType;
|
||||
log: ColorType;
|
||||
error: ColorType;
|
||||
warn: ColorType;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -52,3 +60,12 @@ export type CircuitPathBuilders = 'target' | 'sym' | 'pkey' | 'vkey' | 'wasm' |
|
||||
|
||||
/** Shorthand notations for which path to build in Circomkit. These paths require a circuit name and input name. */
|
||||
export type CircuitInputPathBuilders = 'pubs' | 'proof' | 'wtns' | 'in' | 'dir';
|
||||
|
||||
type RecursivePartial<T> = {
|
||||
[P in keyof T]?: T[P] extends (infer U)[]
|
||||
? RecursivePartial<U>[]
|
||||
: T[P] extends object | undefined
|
||||
? RecursivePartial<T[P]>
|
||||
: T[P];
|
||||
};
|
||||
export type CircomkitConfigOverrides = RecursivePartial<CircomkitConfig>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/** An integer value is a numerical string, a number, or a bigint. */
|
||||
type IntegerValueType = `${number}` | number | bigint;
|
||||
export type IntegerValueType = `${number}` | number | bigint;
|
||||
|
||||
/** A signal value is a number, or an array of numbers (recursively). */
|
||||
export type SignalValueType = IntegerValueType | SignalValueType[];
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import {WasmTester} from '../src';
|
||||
|
||||
describe('fibonacci', () => {
|
||||
let circuit: WasmTester<['in'], ['out']>;
|
||||
const N = 19;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new(`fibonacci_${N}`, {
|
||||
file: 'fibonacci',
|
||||
template: 'Fibonacci',
|
||||
params: [N],
|
||||
});
|
||||
await circuit.checkConstraintCount();
|
||||
});
|
||||
|
||||
it('should compute correctly', async () => {
|
||||
await circuit.expectPass({in: [1, 1]}, {out: fibonacci([1, 1], N)});
|
||||
});
|
||||
});
|
||||
|
||||
// skipping because this takes a bit longer
|
||||
describe.skip('fibonacci recursive', () => {
|
||||
let circuit: WasmTester<['in'], ['out']>;
|
||||
|
||||
const N = 19;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new(`fibonacci_${N}_recursive`, {
|
||||
file: 'fibonacci',
|
||||
template: 'FibonacciRecursive',
|
||||
params: [N],
|
||||
});
|
||||
await circuit.checkConstraintCount();
|
||||
});
|
||||
|
||||
it('should compute correctly', async () => {
|
||||
await circuit.expectPass({in: [1, 1]}, {out: fibonacci([1, 1], N)});
|
||||
});
|
||||
});
|
||||
|
||||
// simple fibonacci with 2 variables
|
||||
function fibonacci(init: [number, number], n: number): number {
|
||||
if (n < 0) {
|
||||
throw new Error('N must be positive');
|
||||
}
|
||||
|
||||
let [a, b] = init;
|
||||
for (let i = 2; i <= n; i++) {
|
||||
b = a + b;
|
||||
a = b - a;
|
||||
}
|
||||
return n === 0 ? a : b;
|
||||
}
|
||||
@@ -1,405 +0,0 @@
|
||||
import {WasmTester} from '../src';
|
||||
|
||||
const expectedConstraints = {
|
||||
fp32: 401,
|
||||
fp64: 819,
|
||||
checkBitLength: (bits: number) => bits + 2,
|
||||
leftShift: (shiftBound: number) => shiftBound + 2,
|
||||
right: (bits: number) => bits + 2,
|
||||
normalize: (P: number) => 3 * P + 1 + 1, // MSNZB + 1
|
||||
msnzb: (bits: number) => 3 * bits + 1,
|
||||
};
|
||||
|
||||
// tests adapted from https://github.com/rdi-berkeley/zkp-mooc-lab
|
||||
describe('float_add 32-bit', () => {
|
||||
let circuit: WasmTester<['e', 'm'], ['e_out', 'm_out']>;
|
||||
|
||||
const k = 8;
|
||||
const p = 23;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new('fp32', {
|
||||
file: 'float_add',
|
||||
template: 'FloatAdd',
|
||||
params: [k, p],
|
||||
});
|
||||
await circuit.checkConstraintCount(401);
|
||||
});
|
||||
|
||||
it('case I test', async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ['43', '5'],
|
||||
m: ['11672136', '10566265'],
|
||||
},
|
||||
{e_out: '43', m_out: '11672136'}
|
||||
);
|
||||
});
|
||||
|
||||
it('case II test 1', async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ['104', '106'],
|
||||
m: ['12444445', '14159003'],
|
||||
},
|
||||
{e_out: '107', m_out: '8635057'}
|
||||
);
|
||||
});
|
||||
|
||||
it('case II test 2', async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ['176', '152'],
|
||||
m: ['16777215', '16777215'],
|
||||
},
|
||||
{e_out: '177', m_out: '8388608'}
|
||||
);
|
||||
});
|
||||
|
||||
it('case II test 3', async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ['142', '142'],
|
||||
m: ['13291872', '13291872'],
|
||||
},
|
||||
{e_out: '143', m_out: '13291872'}
|
||||
);
|
||||
});
|
||||
|
||||
it('one input zero test', async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ['0', '43'],
|
||||
m: ['0', '10566265'],
|
||||
},
|
||||
{e_out: '43', m_out: '10566265'}
|
||||
);
|
||||
});
|
||||
|
||||
it('both inputs zero test', async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ['0', '0'],
|
||||
m: ['0', '0'],
|
||||
},
|
||||
{e_out: '0', m_out: '0'}
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail - exponent zero but mantissa non-zero', async () => {
|
||||
await circuit.expectFail({
|
||||
e: ['0', '0'],
|
||||
m: ['0', '10566265'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail - mantissa ≥ 2^(p+1)', async () => {
|
||||
await circuit.expectFail({
|
||||
e: ['0', '43'],
|
||||
m: ['0', '16777216'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail - mantissa < 2^p', async () => {
|
||||
await circuit.expectFail({
|
||||
e: ['0', '43'],
|
||||
m: ['0', '6777216'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail - exponent ≥ 2^k', async () => {
|
||||
await circuit.expectFail({
|
||||
e: ['0', '256'],
|
||||
m: ['0', '10566265'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('float_add 64-bit', () => {
|
||||
const k = 11;
|
||||
const p = 52;
|
||||
let circuit: WasmTester<['e', 'm'], ['e_out', 'm_out']>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new('fp64', {
|
||||
file: 'float_add',
|
||||
template: 'FloatAdd',
|
||||
params: [k, p],
|
||||
});
|
||||
await circuit.checkConstraintCount(819);
|
||||
});
|
||||
|
||||
it('case I test', async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ['1122', '1024'],
|
||||
m: ['7807742059002284', '7045130465601185'],
|
||||
},
|
||||
{e_out: '1122', m_out: '7807742059002284'}
|
||||
);
|
||||
});
|
||||
|
||||
it('case II test 1', async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ['1056', '1053'],
|
||||
m: ['8879495032259305', '5030141535601637'],
|
||||
},
|
||||
{e_out: '1057', m_out: '4754131362104755'}
|
||||
);
|
||||
});
|
||||
|
||||
it('case II test 2', async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ['1035', '982'],
|
||||
m: ['4804509148660890', '8505192799372177'],
|
||||
},
|
||||
{e_out: '1035', m_out: '4804509148660891'}
|
||||
);
|
||||
});
|
||||
|
||||
it('case II test 3', async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ['982', '982'],
|
||||
m: ['8505192799372177', '8505192799372177'],
|
||||
},
|
||||
{e_out: '983', m_out: '8505192799372177'}
|
||||
);
|
||||
});
|
||||
|
||||
it('one input zero test', async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ['0', '982'],
|
||||
m: ['0', '8505192799372177'],
|
||||
},
|
||||
{e_out: '982', m_out: '8505192799372177'}
|
||||
);
|
||||
});
|
||||
|
||||
it('both inputs zero test', async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: ['0', '0'],
|
||||
m: ['0', '0'],
|
||||
},
|
||||
{e_out: '0', m_out: '0'}
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail - exponent zero but mantissa non-zero', async () => {
|
||||
await circuit.expectFail({
|
||||
e: ['0', '0'],
|
||||
m: ['0', '8505192799372177'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail - mantissa < 2^p', async () => {
|
||||
await circuit.expectFail({
|
||||
e: ['0', '43'],
|
||||
m: ['0', '16777216'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('float_add utilities', () => {
|
||||
describe('check bit length', () => {
|
||||
const b = 23; // bit count
|
||||
let circuit: WasmTester<['in'], ['out']>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new(`cbl_${b}`, {
|
||||
file: 'float_add',
|
||||
template: 'CheckBitLength',
|
||||
params: [b],
|
||||
dir: 'test/float_add',
|
||||
});
|
||||
await circuit.checkConstraintCount(expectedConstraints.checkBitLength(b));
|
||||
});
|
||||
|
||||
it('should give 1 for in ≤ b', async () => {
|
||||
await circuit.expectPass({in: '4903265'}, {out: '1'});
|
||||
});
|
||||
|
||||
it('should give 0 for in > b', async () => {
|
||||
await circuit.expectPass({in: '13291873'}, {out: '0'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('left shift', () => {
|
||||
const shift_bound = 25;
|
||||
let circuit: WasmTester<['x', 'shift', 'skip_checks'], ['y']>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new(`shl_${shift_bound}`, {
|
||||
file: 'float_add',
|
||||
template: 'LeftShift',
|
||||
dir: 'test/float_add',
|
||||
params: [shift_bound],
|
||||
});
|
||||
await circuit.checkConstraintCount(expectedConstraints.leftShift(shift_bound));
|
||||
});
|
||||
|
||||
it("should pass test 1 - don't skip checks", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
x: '65',
|
||||
shift: '24',
|
||||
skip_checks: '0',
|
||||
},
|
||||
{y: '1090519040'}
|
||||
);
|
||||
});
|
||||
|
||||
it("should pass test 2 - don't skip checks", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
x: '65',
|
||||
shift: '0',
|
||||
skip_checks: '0',
|
||||
},
|
||||
{y: '65'}
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail - don't skip checks", async () => {
|
||||
await circuit.expectFail({
|
||||
x: '65',
|
||||
shift: '25',
|
||||
skip_checks: '0',
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass when skip_checks = 1 and shift is ≥ shift_bound', async () => {
|
||||
await circuit.expectPass({
|
||||
x: '65',
|
||||
shift: '25',
|
||||
skip_checks: '1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('right shift', () => {
|
||||
const b = 49;
|
||||
const shift = 24;
|
||||
let circuit: WasmTester<['x'], ['y']>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new(`shr_${b}`, {
|
||||
file: 'float_add',
|
||||
template: 'RightShift',
|
||||
dir: 'test/float_add',
|
||||
params: [b, shift],
|
||||
});
|
||||
await circuit.checkConstraintCount(b);
|
||||
});
|
||||
|
||||
it('should pass - small bitwidth', async () => {
|
||||
await circuit.expectPass({x: '82263136010365'}, {y: '4903265'});
|
||||
});
|
||||
|
||||
it('should fail - large bitwidth', async () => {
|
||||
await circuit.expectFail({x: '15087340228765024367'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalize', () => {
|
||||
const k = 8;
|
||||
const p = 23;
|
||||
const P = 47;
|
||||
let circuit: WasmTester<['e', 'm', 'skip_checks'], ['e_out', 'm_out']>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new(`normalize_${k}_${p}_${P}`, {
|
||||
file: 'float_add',
|
||||
template: 'Normalize',
|
||||
params: [k, p, P],
|
||||
dir: 'test/float_add',
|
||||
});
|
||||
await circuit.checkConstraintCount(expectedConstraints.normalize(P));
|
||||
});
|
||||
|
||||
it("should pass - don't skip checks", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: '100',
|
||||
m: '20565784002591',
|
||||
skip_checks: '0',
|
||||
},
|
||||
{e_out: '121', m_out: '164526272020728'}
|
||||
);
|
||||
});
|
||||
|
||||
it("should pass - already normalized and don't skip checks", async () => {
|
||||
await circuit.expectPass(
|
||||
{
|
||||
e: '100',
|
||||
m: '164526272020728',
|
||||
skip_checks: '0',
|
||||
},
|
||||
{e_out: '124', m_out: '164526272020728'}
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail when m = 0 - don't skip checks", async () => {
|
||||
await circuit.expectFail({
|
||||
e: '100',
|
||||
m: '0',
|
||||
skip_checks: '0',
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass when skip_checks = 1 and m is 0', async () => {
|
||||
await circuit.expectPass({
|
||||
e: '100',
|
||||
m: '0',
|
||||
skip_checks: '1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('msnzb', () => {
|
||||
const b = 48;
|
||||
let circuit: WasmTester<['in', 'skip_checks'], ['one_hot']>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new(`msnzb_${b}`, {
|
||||
file: 'float_add',
|
||||
template: 'MSNZB',
|
||||
dir: 'test/float_add',
|
||||
params: [b],
|
||||
});
|
||||
await circuit.checkConstraintCount(expectedConstraints.msnzb(b));
|
||||
});
|
||||
|
||||
it("should pass test 1 - don't skip checks", async () => {
|
||||
await circuit.expectPass(
|
||||
{in: '1', skip_checks: '0'},
|
||||
{
|
||||
// prettier-ignore
|
||||
one_hot: ['1', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should pass test 2 - don't skip checks", async () => {
|
||||
await circuit.expectPass(
|
||||
{in: '281474976710655', skip_checks: '0'},
|
||||
{
|
||||
// prettier-ignore
|
||||
one_hot: ['0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1'],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail when in = 0 - don't skip checks", async () => {
|
||||
await circuit.expectFail({in: '0', skip_checks: '0'});
|
||||
});
|
||||
|
||||
it('should pass when skip_checks = 1 and in is 0', async () => {
|
||||
await circuit.expectPass({in: '0', skip_checks: '1'});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,19 +1,23 @@
|
||||
import {FullProof, ProofTester, WasmTester} from '../src';
|
||||
import {Circomkit, CircuitConfig, FullProof, ProofTester, WasmTester} from '../src';
|
||||
|
||||
function createConfig(n: number): CircuitConfig {
|
||||
return {
|
||||
file: 'multiplier',
|
||||
template: 'Multiplier',
|
||||
params: [n],
|
||||
};
|
||||
}
|
||||
|
||||
const N = 3;
|
||||
const circuitName = `multiplier_${N}`;
|
||||
const circomkit = new Circomkit();
|
||||
|
||||
describe('multiplier', () => {
|
||||
const N = 3;
|
||||
|
||||
let circuit: WasmTester<['in'], ['out']>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new(`multiplier_${N}`, {
|
||||
file: 'multiplier',
|
||||
template: 'Multiplier',
|
||||
params: [N],
|
||||
});
|
||||
|
||||
// constraint count checks!
|
||||
await circuit.checkConstraintCount(N - 1);
|
||||
circuit = await circomkit.WasmTester(circuitName, createConfig(N));
|
||||
await circuit.checkConstraintCount(N);
|
||||
});
|
||||
|
||||
it('should multiply correctly', async () => {
|
||||
@@ -22,31 +26,12 @@ describe('multiplier', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiplier utilities', () => {
|
||||
describe('multiplication gate', () => {
|
||||
let circuit: WasmTester<['in'], ['out']>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new('mulgate', {
|
||||
file: 'multiplier',
|
||||
template: 'MultiplicationGate',
|
||||
dir: 'test/multiplier',
|
||||
});
|
||||
});
|
||||
|
||||
it('should multiply correctly', async () => {
|
||||
await circuit.expectPass({in: [7, 5]}, {out: 7 * 5});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('multiplier proofs', () => {
|
||||
const N = 3;
|
||||
describe('multiplier proofs', () => {
|
||||
let fullProof: FullProof;
|
||||
let circuit: ProofTester<['in']>;
|
||||
|
||||
before(async () => {
|
||||
const circuitName = 'multiplier_' + N;
|
||||
circuit = new ProofTester(circuitName);
|
||||
circuit = await circomkit.ProofTester(circuitName);
|
||||
fullProof = await circuit.prove({
|
||||
in: Array.from({length: N}, () => Math.floor(Math.random() * 100 * N)),
|
||||
});
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import {expect} from 'chai';
|
||||
import {WasmTester} from '../src';
|
||||
import {randomBytes, createHash} from 'crypto';
|
||||
|
||||
describe('sha256', () => {
|
||||
let circuit: WasmTester<['in'], ['out']>;
|
||||
|
||||
const NUM_BYTES = 36;
|
||||
const BYTES = randomBytes(NUM_BYTES);
|
||||
const LOCAL_HASH = createHash('sha256').update(BYTES).digest('hex');
|
||||
const INPUT = {
|
||||
in: BYTES.toJSON().data,
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new('sha256', {
|
||||
file: 'sha256',
|
||||
template: 'Sha256Bytes',
|
||||
params: [NUM_BYTES],
|
||||
});
|
||||
await circuit.checkConstraintCount();
|
||||
});
|
||||
|
||||
it('should compute hash correctly', async () => {
|
||||
const {out} = await circuit.compute(INPUT, ['out']);
|
||||
const outputBytes = (out as bigint[]).map(b => parseInt(b.toString()));
|
||||
const outputHex = Buffer.from(outputBytes).toString('hex');
|
||||
expect(outputHex).to.eq(LOCAL_HASH);
|
||||
});
|
||||
});
|
||||
@@ -1,202 +0,0 @@
|
||||
import {WasmTester, CircuitSignals} from '../src';
|
||||
|
||||
type BoardSizes = 4 | 9;
|
||||
|
||||
const INPUTS: {[N in BoardSizes]: CircuitSignals<['solution', 'puzzle']>} = {
|
||||
9: {
|
||||
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],
|
||||
],
|
||||
},
|
||||
4: {
|
||||
solution: [
|
||||
[4, 1, 3, 2],
|
||||
[3, 2, 4, 1],
|
||||
[2, 4, 1, 3],
|
||||
[1, 3, 2, 4],
|
||||
],
|
||||
puzzle: [
|
||||
[0, 1, 0, 2],
|
||||
[3, 2, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[1, 0, 0, 0],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
([4, 9] as BoardSizes[]).map(N =>
|
||||
describe(`sudoku (${N} by ${N})`, () => {
|
||||
const INPUT = INPUTS[N];
|
||||
let circuit: WasmTester<['solution', 'puzzle']>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new(`sudoku_${N}x${N}`, {
|
||||
file: 'sudoku',
|
||||
template: 'Sudoku',
|
||||
pubs: ['puzzle'],
|
||||
params: [Math.sqrt(N)],
|
||||
});
|
||||
await circuit.checkConstraintCount();
|
||||
});
|
||||
|
||||
it('should compute correctly', async () => {
|
||||
await circuit.expectPass(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.expectFail(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.expectFail(badInput);
|
||||
});
|
||||
|
||||
it('should NOT accept non-distinct square', async () => {
|
||||
const badInput = JSON.parse(JSON.stringify(INPUT));
|
||||
badInput.solution[0][0] = badInput.solution[1][1];
|
||||
await circuit.expectFail(badInput);
|
||||
});
|
||||
|
||||
it('should NOT accept empty value in solution', async () => {
|
||||
const badInput = JSON.parse(JSON.stringify(INPUT));
|
||||
badInput.solution[0][0] = 0;
|
||||
await circuit.expectFail(badInput);
|
||||
});
|
||||
|
||||
it('should NOT accept out-of-range values', async () => {
|
||||
const badInput = JSON.parse(JSON.stringify(INPUT));
|
||||
badInput.solution[0][0] = 99999;
|
||||
await circuit.expectFail(badInput);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
describe('sudoku utilities', () => {
|
||||
describe('assert bit length', () => {
|
||||
const b = 3; // bit count
|
||||
|
||||
let circuit: WasmTester<['in'], []>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new(`bitlen_${b}`, {
|
||||
file: 'sudoku',
|
||||
template: 'AssertBitLength',
|
||||
dir: 'test/sudoku',
|
||||
params: [b],
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass for input < 2^b', async () => {
|
||||
await circuit.expectPass({
|
||||
in: 2 ** b - 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail for input ≥ 2^b ', async () => {
|
||||
await circuit.expectFail({
|
||||
in: 2 ** b,
|
||||
});
|
||||
await circuit.expectFail({
|
||||
in: 2 ** b + 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('distinct', () => {
|
||||
const n = 3;
|
||||
|
||||
let circuit: WasmTester<['in'], []>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new(`distinct_${n}`, {
|
||||
file: 'sudoku',
|
||||
template: 'Distinct',
|
||||
dir: 'test/sudoku',
|
||||
params: [n],
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass if all inputs are unique', async () => {
|
||||
await circuit.expectPass({
|
||||
in: Array(n)
|
||||
.fill(0)
|
||||
.map((v, i) => v + i),
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if there is a duplicate', async () => {
|
||||
const arr = Array(n)
|
||||
.fill(0)
|
||||
.map((v, i) => v + i);
|
||||
// make a duplicate
|
||||
arr[0] = arr[arr.length - 1];
|
||||
await circuit.expectFail({
|
||||
in: arr,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('in range', () => {
|
||||
const MIN = 1;
|
||||
const MAX = 9;
|
||||
let circuit: WasmTester<['in'], []>;
|
||||
|
||||
before(async () => {
|
||||
circuit = await WasmTester.new(`inRange_${MIN}_${MAX}`, {
|
||||
file: 'sudoku',
|
||||
template: 'InRange',
|
||||
dir: 'test/sudoku',
|
||||
params: [MIN, MAX],
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass for in range', async () => {
|
||||
await circuit.expectPass({
|
||||
in: MAX,
|
||||
});
|
||||
await circuit.expectPass({
|
||||
in: MIN,
|
||||
});
|
||||
await circuit.expectPass({
|
||||
in: Math.floor((MIN + MAX) / 2),
|
||||
});
|
||||
});
|
||||
|
||||
it('should FAIL for out of range (upper bound)', async () => {
|
||||
await circuit.expectFail({
|
||||
in: MAX + 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should FAIL for out of range (lower bound)', async () => {
|
||||
if (MIN > 0) {
|
||||
await circuit.expectFail({
|
||||
in: MIN - 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user