Skip to content

Commit

Permalink
to jest, more docs
Browse files Browse the repository at this point in the history
  • Loading branch information
erhant committed Feb 4, 2024
1 parent a797188 commit 5627a5d
Show file tree
Hide file tree
Showing 23 changed files with 2,567 additions and 359 deletions.
6 changes: 0 additions & 6 deletions .mocharc.json

This file was deleted.

20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ yarn install

## Book

We use `mdbook` to create the book, see the [book](./book/) folder. Serve the book locally with:
We use `mdbook` to create the book, see the [book](./book/) folder. You can build the book with the following:

```sh
yarn book
yarn book:build # without serving
yarn book # automatically serve the book
yarn book:build # build without serving
```

## Tests
Expand All @@ -42,6 +42,20 @@ Run circuit tests with:

```sh
yarn test

# with pattern matching
yarn test pattern-name

# with explicit paths
yarn test ./path/to/some.test.ts
```

The tests make use of [Circomkit](https://github.com/erhant/circomkit).

## Style

Check the formatting of test codes using:

```sh
yarn format
```
20 changes: 20 additions & 0 deletions book/src/bits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,23 @@ $$
We use `bit_value` to keep track of $2^i$, and this entire sum expression is stored within the `lc` (linear combination). In the end, we constrain the output signal to be equal to this expression.

> Note that for both `Num2Bits` and `Bits2Num`, the most-significant bit is the last element of the array, and least-significant bit is the first element of the array. To demonstrate, consider the 4-bit number 11, normally shown as $(1011)_2$ in maths. However, in these circuits we store the array `[1, 1, 0, 1]`, in the opposite order!

## Function: `nbits`

```cs
function nbits(n) {
var ans = 0;

var tmp = 1;
while (tmp - 1 < n) {
ans++;
tmp <<= 1;
}

return ans;
}
```

We might want to find the minimum number of bits required to represent a **known** value; we use a function called `nbits` (as in Circomlib) for that. Here, our parameter `n` is known at compile time, perhaps a generic argument or some constant somewhere.

The idea of this function is to construct $2^i - 1$ until it becomes greater than our input.
28 changes: 26 additions & 2 deletions book/src/comparators/range-check.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
# Range Check
# `AssertInRange`

TODO
```cs
template AssertInRange(MIN, MAX) {
assert(MIN < MAX);
signal input in;

var b = nbits(MAX);

component lowerBound = AssertBits(b);
lowerBound.in <== in - MIN;

component upperBound = AssertBits(b);
upperBound.in <== in + (2 ** b) - MAX - 1;
}
```

A range-check is a common utility, asserting that a number is in some range `[MIN, MAX]`. Above is one way of doing that. Our approach here is to check:

- `in - MIN` is a b-bit value
- `in + 2^b - 1 - MAX` is a b-bit value

where `b` is the minimum number of bits required to represent `MAX`. The `nbits` function is described within the [bits section](../bits/).

If `in < MIN`, the operation `in - MIN` will underflow and we will have a huge number, definitely not be b-bit representable (assuming a not-so-large MAX). As an edge case, if `in == MIN` we get 0, which is definitely b-bits.

If `in > MAX`, the operation `in + 2^b - 1 - MAX` becomes larger than `2^b - 1`, which is not b-bit representable. As an edge case, if `in == MAX` we get `2^b - 1`, which is the largest b-bit number.
17 changes: 17 additions & 0 deletions book/src/control-flow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ template IsFive() {

If `isEnabled = 1` both circuits are equivalent, but if `isEnabled = 0` this circuit will always have `0 === 0` regardless of the `in` signal.

> Circomlib has a circuit that makes use of this trick, see [`ForceEqualIfEnabled`](https://github.com/iden3/circomlib/blob/master/circuits/comparators.circom).
## `Switch`

```cs
Expand All @@ -86,3 +88,18 @@ template Switch() {
```

It is often useful to switch the places of two signals based on a condition, which can be achieved with two `IfElse` lines together.

> You can do this with a single multiplication too!
>
> ```cs
> template Switch() {
> signal input cond;
> signal input in[2];
> signal output out[2];
>
> aux <== (in[1] - in[0]) * cond;
>
> out[0] <== aux + in[0];
> out[1] <== -aux + in[1];
> }
> ```
1 change: 1 addition & 0 deletions circuits/basics/sudoku.circom
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma circom 2.1.0;
include "../bits/index.circom";
include "../arrays/index.circom";
include "../comparators/index.circom";
include "../comparators/range.circom";

template Sudoku(n_sqrt) {
var n = n_sqrt * n_sqrt; // board size is a perfect square
Expand Down
17 changes: 10 additions & 7 deletions circuits/bits/index.circom
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
pragma circom 2.1.0;

// Returns the minimum number of bits needed to represent `n`
function numBits(n) {
var tmp = 1, ans = 1;
while (tmp < n) {
ans++;
tmp <<= 1;
function nbits(n) {
var ans = 0;

var tmp = 1;
while (tmp - 1 < n) {
ans++;
tmp <<= 1;
}
return ans + 1;

return ans;
}

// Asserts that a given input is binary.
Expand Down Expand Up @@ -72,4 +75,4 @@ template AssertBits(n) {
}

lc === in;
}
}
12 changes: 0 additions & 12 deletions circuits/comparators/index.circom
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,3 @@ template GreaterEqThan(n) {
out <== LessThan(n)([in[1], in[0]+1]);
}

// Checks that `in` is in range [MIN, MAX]
// In other words, `MIN <= in <= MAX`.
template AssertInRange(MIN, MAX) {
assert(MIN < MAX);
signal input in;

var b = numBits(MAX);
component lowerBound = Num2Bits(b);
component upperBound = Num2Bits(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)
}
26 changes: 26 additions & 0 deletions circuits/comparators/range.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
pragma circom 2.1.0;

include "../bits/index.circom";

// Checks that `in` is in range [MIN, MAX]
// In other words, `MIN <= in <= MAX`.
template AssertInRange(MIN, MAX) {
assert(MIN < MAX);
signal input in;

var b = nbits(MAX);

// find `in - MIN`:
// if `in > MIN` this is a b-bit value
// otherwise, it will underflow due to negation,
// and may be larger than MAX
component lowerBound = AssertBits(b);
lowerBound.in <== in - MIN; // e.g. 1 - 1 = 0 (for 0 <= in)

// find `in + 2^b - MAX - 1`
// if `in < MAX` this will result in a value less than `2^b - 1`
// ensuring that it is a b-bit value, otherwise this will be a value
// larger than b-bits
component upperBound = AssertBits(b);
upperBound.in <== in + (2 ** b) - MAX - 1; // e.g. 9 + (15 - 9) = 15 (for in <= 15)
}
13 changes: 7 additions & 6 deletions circuits/control-flow/index.circom
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,19 @@ template IfElse() {
//
// Inputs:
// - `cond`: a boolean condition
// - `in`: two numbers
// - `in`: two signals
//
// Outputs:
// - `out`: two numbers either swapped or not
// - `out`: two signals either swapped or not
//
template Switch() {
signal input cond;
signal input in[2];
signal output out[2];

out[0] <== cond * (in[1] - in[0]) + in[0];
out[1] <== cond * (in[0] - in[1]) + in[1];
}
// auxillary variable to store the condition multiplication
signal aux <== (in[1] - in[0]) * cond;

// TODO: add muxes
out[0] <== aux + in[0];
out[1] <== -aux + in[1];
}
15 changes: 15 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { JestConfigWithTsJest } from "ts-jest";

const config: JestConfigWithTsJest = {
// ts-jest defaults
preset: "ts-jest",
testEnvironment: "node",
transform: {
"^.+\\.(ts|js)$": "ts-jest",
},
verbose: true,
testTimeout: 40_000,
testPathIgnorePatterns: ["/node_modules/", "/examples/", "/book/", "/circuits/"],
};

export default config;
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"private": true,
"license": "MIT",
"scripts": {
"test": "npx mocha",
"test": "npx jest",
"format": "npx prettier --check ./tests/**/*.ts",
"book": "cd book && mdbook serve --open",
"book:build": "cd book && mdbook build"
},
Expand All @@ -12,9 +13,11 @@
"circomlib": "^2.0.5"
},
"devDependencies": {
"@types/mocha": "^10.0.1",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.16",
"mocha": "^10.2.0",
"jest": "^29.7.0",
"prettier": "^3.2.5",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.1",
"typescript": "^5.1.6"
},
Expand Down
2 changes: 1 addition & 1 deletion tests/arrays/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe("arrays", () => {
const N = 3;
let circuit: WitnessTester<["in"]>;

before(async () => {
beforeAll(async () => {
circuit = await circomkit.WitnessTester(`distinct_${N}`, {
file: "arrays/index",
template: "AssertDistinct",
Expand Down
2 changes: 1 addition & 1 deletion tests/basics/fibonacci.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const N = 14;
describe("fibonacci", () => {
let circuit: WitnessTester<["in"], ["out"]>;

before(async () => {
beforeAll(async () => {
circuit = await circomkit.WitnessTester(`fibonacci_${N}`, {
file: "basics/fibonacci",
template: "Fibonacci",
Expand Down
4 changes: 2 additions & 2 deletions tests/basics/magic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ MAGIC_SIZES.map((N) =>
};
let circuit: WitnessTester<["in"], ["sum"]>;

before(async () => {
beforeAll(async () => {
circuit = await circomkit.WitnessTester(`magic_${N}x${N}`, {
file: "basics/magic",
template: "MagicSquare",
Expand All @@ -24,5 +24,5 @@ MAGIC_SIZES.map((N) =>
});

// TODO fail checks
})
}),
);
2 changes: 1 addition & 1 deletion tests/basics/multiplier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const N = 3;
describe("multiplier", () => {
let circuit: WitnessTester<["in"], ["out"]>;

before(async () => {
beforeAll(async () => {
circuit = await circomkit.WitnessTester(`multiplier_${N}`, {
file: "basics/multiplier",
template: "Multiplier",
Expand Down
4 changes: 2 additions & 2 deletions tests/basics/sudoku.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ BOARD_SIZES.map((N) =>
const INPUT = SUDOKU_INPUTS[N];
let circuit: WitnessTester<["solution", "puzzle"]>;

before(async () => {
beforeAll(async () => {
circuit = await circomkit.WitnessTester(`sudoku_${N}x${N}`, {
file: "basics/sudoku",
template: "Sudoku",
Expand Down Expand Up @@ -50,5 +50,5 @@ BOARD_SIZES.map((N) =>
badInput.solution[0][0] = 99999;
await circuit.expectFail(badInput);
});
})
}),
);
26 changes: 24 additions & 2 deletions tests/bits/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,32 @@ describe("bits", () => {
const NUM = 11;
const BITS = [1, 1, 0, 1, 0];

describe("bits function", () => {
function nbits(n: number) {
let ans = 0;
let tmp = 1;
while (tmp - 1 < n) {
ans++;
tmp <<= 1;
}
return ans;
}

it("should compute minimum number of bits correctly", () => {
expect(nbits(0)).toBe(0);
expect(nbits(1)).toBe(1);
expect(nbits(2)).toBe(2);
expect(nbits(3)).toBe(2);
expect(nbits(4)).toBe(3);
expect(nbits(7)).toBe(3);
expect(nbits(8)).toBe(4);
});
});

describe("number to bits", () => {
let circuit: WitnessTester<["in"], ["out"]>;

before(async () => {
beforeAll(async () => {
circuit = await circomkit.WitnessTester(`num2bits_${N}`, {
file: "bits/index",
template: "Num2Bits",
Expand All @@ -26,7 +48,7 @@ describe("bits", () => {
describe("bits to number", () => {
let circuit: WitnessTester<["in"], ["out"]>;

before(async () => {
beforeAll(async () => {
circuit = await circomkit.WitnessTester(`bits2num_${N}`, {
file: "bits/index",
template: "Bits2Num",
Expand Down
2 changes: 1 addition & 1 deletion tests/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Circomkit, type CircomkitConfig } from "circomkit";

export const circomkit = new Circomkit({ verbose: false });
export const circomkit = new Circomkit({ verbose: false, inspect: true });

// Convert a number to its binary representation with `bits` bits.
// By default, returns 254 bit representation.
Expand Down
2 changes: 1 addition & 1 deletion tests/comparators/compconstant.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe("compconstant", () => {

const CONSTANT = Math.round(Math.random() * 10000);

before(async () => {
beforeAll(async () => {
circuit = await circomkit.WitnessTester(`compconstant`, {
file: "comparators/compconstant",
template: "CompConstant",
Expand Down
Loading

0 comments on commit 5627a5d

Please sign in to comment.