From 5fb4b1fc1838165f08308bf4c644afa14b3085ab Mon Sep 17 00:00:00 2001 From: erhant Date: Sun, 11 Feb 2024 17:08:34 +0300 Subject: [PATCH] more docs, arrays, more todos --- .vscode/settings.json | 4 +- book/src/README.md | 11 +++ book/src/SUMMARY.md | 8 +- book/src/arrays/README.md | 111 ++++++++++++++++++++++- book/src/arrays/distinct.md | 53 +++++++++++ book/src/comparators/range-check.md | 10 +- book/src/hashing/README.md | 1 + book/src/hashing/mimc.md | 1 + book/src/hashing/poseidon.md | 1 + book/src/merkle-trees/README.md | 2 +- book/src/preliminary/README.md | 1 + book/src/preliminary/commitments.md | 28 ++++++ book/src/preliminary/group-theory.md | 3 + circuits/arrays/distinct.circom | 52 +++++++++++ circuits/arrays/index.circom | 22 ----- circuits/basics/fibonacci.circom | 8 +- circuits/basics/magic.circom | 10 ++ circuits/basics/multiplier.circom | 13 ++- circuits/basics/sudoku.circom | 10 +- circuits/bits/gates.circom | 44 ++++++++- circuits/bits/index.circom | 33 +++++++ circuits/comparators/compconstant.circom | 9 +- circuits/comparators/index.circom | 71 ++++++++++++--- circuits/comparators/range.circom | 9 +- circuits/control-flow/index.circom | 19 ++-- tests/arrays/distinct.test.ts | 52 +++++++++++ tests/arrays/index.test.ts | 35 +------ 27 files changed, 531 insertions(+), 90 deletions(-) create mode 100644 book/src/arrays/distinct.md create mode 100644 book/src/hashing/README.md create mode 100644 book/src/hashing/mimc.md create mode 100644 book/src/hashing/poseidon.md create mode 100644 book/src/preliminary/README.md create mode 100644 book/src/preliminary/commitments.md create mode 100644 book/src/preliminary/group-theory.md create mode 100644 circuits/arrays/distinct.circom create mode 100644 tests/arrays/distinct.test.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 16ff40f..e4117f1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,6 +12,8 @@ "basics": "Resolver", "control-flow": "Routes", "comparators": "Benchmark", - "bits": "Base" + "bits": "Base", + "merkle-trees": "Context", + "hashing": "App" } } diff --git a/book/src/README.md b/book/src/README.md index 19e1ddb..f984c7e 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -4,6 +4,17 @@ Zero-knowledge cryptography is a fascinating area, where a party can prove a sta Circom is a hardware description language (HDL) specifically designed for creating zero-knowledge proofs. It enables developers to create arithmetic circuits, which are then used to generate proofs that demonstrate a certain statement is true, without revealing any additional information about the inputs used to make that statement. Circom has opened up new possibilities for creating privacy-preserving applications, such as digital identities, voting systems, and financial transactions, where proving the validity of a statement is necessary, but keeping the underlying data private is critical. +## Organization + +The book is organized as follows: + +- [Preliminary](./preliminary/README.md) has some preliminary theory for those who are interested in it. It is not 100% required to know the theory & math behind the relevant cryptography areas to write good Circom code; however, it could allow the reader to: + - Write more efficient code by utilizing mathematical tricks. + - Understand certain low-level code. + - Consider more edge cases & write more secure code. +- [Basics](./basics/README.md) has several hello-world level Circom programs, to get the reader started. The reader is free to skip this part & come back later, however it is strongly recommended to understand how the programs here work. +- [Bits](./bits/README.md) describe methods related to bits: signals with value `0` or `1`. + ## Resources There are many resources about Circom & ZK out there, to list a few: diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index ef06306..2d2890f 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -1,7 +1,9 @@ # Summary - [Introduction](./README.md) - +- [Preliminary](./preliminary/README.md) + - [Group Theory](./preliminary/group-theory.md) + - [Commitments](./preliminary/commitments.md) - [Basics](./basics/README.md) - [Multiplier](./basics/multiplier.md) - [Fibonacci](./basics/fibonacci.md) @@ -16,7 +18,11 @@ - [Control Flow](./control-flow/README.md) - [Multiplexing](./control-flow/mux.md) - [Arrays](./arrays/README.md) + - [Distinct](./arrays/distinct.md) - [Sorting](./arrays/sorting.md) +- [Hashing](./hashing/README.md) + - [Poseidon](./hashing/poseidon.md) + - [MiMC](./hashing/mimc.md) - [Merkle Trees](./merkle-trees/README.md) - [Dense Merkle Tree](./merkle-trees/dense.md) - [Sparse Merkle Tree](./merkle-trees/smt.md) diff --git a/book/src/arrays/README.md b/book/src/arrays/README.md index 2598f86..788238c 100644 --- a/book/src/arrays/README.md +++ b/book/src/arrays/README.md @@ -1,3 +1,112 @@ # Arrays -TODO +Circom arrays are a different kind of beast. The main reason is that in Circom, array operations must be **known** at compile-time. What do we mean by this: + +- Array sizes are fixed, e.g. you can't define an array based on the user input after compiling the circuit. +- Array indexing should be known at compile time, e.g. you can't ask a user for index `i` and return `arr[i]` like you _normally_ do. + +Before we get to the problematic unknown-at-compile-time stuff, let's quickly recap the known-time array operations. An array of signals (with fixed size) is defined with: + +```cs +signal arr[N]; +``` + +> Arrays can be multi-dimensional too: +> +> ```cs +> signal arr[N][M]; +> ``` + +With that, we can read & write to this array as with the usual notation in programming: + +```cs +// for some compile-time known `i`: +arr[i] <== foo; +bar <== arr[i]; +``` + +So now, what if we want to read or write to an index unknown at compile time? + +## `ArrayRead` + +```cs +template ArrayRead(n) { + signal input in[n]; + signal input index; + signal output out; + + signal intermediate[n]; + for (var i = 0; i < n; i++) { + var isIndex = IsEqual()([index, i]); + intermediate[i] <== isIndex * in[i]; + } + + out <== Sum(n)(intermediate); +} +``` + +To read an unknown index, we could instead read ALL signals (which is an operation known-at-compile-time) and then return a linear combination of them, with each value multiplied with an equality-check with our index. + +```py +arr_i = + A[0] * (i == 0) ++ A[1] * (i == 1) ++ ... ++ A[n-1] * (i == n-1) +``` + +This way, our array accesses are known at compile-time but we are still able to get the value at index `i`. + +```py +arr_i = + A[0] * 0 ++ A[1] * 0 ++ ... ++ A[i] * 1 ++ ... ++ A[n-1] * 0 += A[i] +``` + +Note that this will incur some contraint costs. + +> The `Sum(n)` here is rather straight-forward: +> +> ```cs +> template Sum(n) { +> signal input in[n]; +> signal output out; +> +> var lc = 0; +> for (var i = 0; i < n; i++) { +> lc += in[i]; +> } +> out <== lc; +> } +> ``` +> +> `lc` here means "linear combination" and its just a variable that stores: +> +> ```py +> lc = in[0] + in[1] + ... + in[n-1] +> ``` + +## `ArrayWrite` + +```cs +template ArrayWrite(n) { + signal input in[n]; + signal input index; + signal input value; + signal output out[n]; + + for (var i = 0; i < n; i++) { + var isIndex = IsEqual()([index, i]); + out[i] <== IfElse()(isIndex, value, in[i]); + } +} +``` + +Writing to an unknown-index works in a similar way to reading one. The idea is to simply copy the input signal array to an output array, but only at the index `i` we will use our new value instead of the existing one at `in[i]`. + +We had defined the `IfElse` template at [control-flow](../control-flow/) section. diff --git a/book/src/arrays/distinct.md b/book/src/arrays/distinct.md new file mode 100644 index 0000000..edb1c98 --- /dev/null +++ b/book/src/arrays/distinct.md @@ -0,0 +1,53 @@ +# Distinct + +We may often require to check if an array has non-repeating values, also known as unique values or distinct values. A common example of this would be for a Sudoku circuit, e.g. you would like to assert that all values in a row are in range [1, 9] and that the row has distinct values. + +## `AssertDistinct` + +```cs +template AssertDistinct(n) { + signal input in[n]; + + for (var i = 0; i < n-1; i++){ + for (var j = i+1; j < n; j++){ + var eq = IsEqual()([in[i], in[j]]); + eq === 0; + } + } +} +``` + +To assert that an array has distinct values, we can loop over the values and check each unique pair to be non-equal using the `IsEqual` template. For example, in an array of 4 elements this corresponds to the following checks: + +```cs +IsEqual()([in[0], in[1]]) === 0 +IsEqual()([in[0], in[2]]) === 0 +IsEqual()([in[0], in[3]]) === 0 +IsEqual()([in[1], in[2]]) === 0 +IsEqual()([in[1], in[3]]) === 0 +IsEqual()([in[2], in[3]]) === 0 +``` + +## `IsDistinct` + +```cs +template IsDistinct(n) { + signal input in[n]; + signal output out; + + var acc = 0; + for (var i = 0; i < n-1; i++){ + for (var j = i+1; j < n; j++){ + var eq = IsEqual()([in[i], in[j]]); + acc += eq; + } + } + + signal outs <== acc; + out <== IsZero()(outs); +} +``` + +If you would like to return a bit-signal based on whether an array has all distinct values or not, you can slightly modify the `AssertDistinct` template to obtain that functionality. Instead of asserting each `IsZero` check, we accumulate them and then return whether that final sum is equal to zero or not. + +> Note that technically it is possible for `acc` to overflow and wrap back to 0, however, that is unlikely to happen given how large the prime-field is and we would need that many components to be able to overflow using 1+1+...+1 only. diff --git a/book/src/comparators/range-check.md b/book/src/comparators/range-check.md index 41ec8fe..3bff9b9 100644 --- a/book/src/comparators/range-check.md +++ b/book/src/comparators/range-check.md @@ -1,4 +1,10 @@ -# `AssertInRange` +# Range Check + +A range-check is a common utility, asserting that a number is in some range `[MIN, MAX]`. It is important to keep in mind that you can make use of bit-decompositions to check for range `[0, 2^n)`, which would be done by simply checking if a number is representable with `n`-bits. + +> Lookup-tables are often used for range checks in other zkDSLs, but I dont yet know how to use them in Circom. + +## `AssertInRange` ```cs template AssertInRange(MIN, MAX) { @@ -15,7 +21,7 @@ template AssertInRange(MIN, MAX) { } ``` -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: +Above is one way of doing a generic range check. Our approach here is to check: - `in - MIN` is a b-bit value - `in + 2^b - 1 - MAX` is a b-bit value diff --git a/book/src/hashing/README.md b/book/src/hashing/README.md new file mode 100644 index 0000000..8910a2e --- /dev/null +++ b/book/src/hashing/README.md @@ -0,0 +1 @@ +# Hashing diff --git a/book/src/hashing/mimc.md b/book/src/hashing/mimc.md new file mode 100644 index 0000000..a25a68f --- /dev/null +++ b/book/src/hashing/mimc.md @@ -0,0 +1 @@ +# MiMC diff --git a/book/src/hashing/poseidon.md b/book/src/hashing/poseidon.md new file mode 100644 index 0000000..ae0c70c --- /dev/null +++ b/book/src/hashing/poseidon.md @@ -0,0 +1 @@ +# Poseidon diff --git a/book/src/merkle-trees/README.md b/book/src/merkle-trees/README.md index a27a3a8..e54faf1 100644 --- a/book/src/merkle-trees/README.md +++ b/book/src/merkle-trees/README.md @@ -1,3 +1,3 @@ # Merkle Trees -TODO +If you have been in the world of crypto for a while, it is highly likely that you have heard the term Merkle Tree. A Merkle Tree is a cryptographic commitment scheme. diff --git a/book/src/preliminary/README.md b/book/src/preliminary/README.md new file mode 100644 index 0000000..2274ec5 --- /dev/null +++ b/book/src/preliminary/README.md @@ -0,0 +1 @@ +# Preliminaries diff --git a/book/src/preliminary/commitments.md b/book/src/preliminary/commitments.md new file mode 100644 index 0000000..7bdb5f3 --- /dev/null +++ b/book/src/preliminary/commitments.md @@ -0,0 +1,28 @@ +# Commitments + +In a commitment scheme, Alice can commit to a value $x$ to obtain a commitment $\boxed{x}$. At some point later, Bob would like for Alice to reveal the committed value behind $\boxed{x}$, and show that indeed Alice was the one that had committed in the first place. + +A rough informal sketch of this interaction can be shown as: + +```mermaid +sequenceDiagram + actor A as Alice + actor B as Bob + + note over A: Alice commits to x + note over A: [x] := x + A ->> B: [x] + + note over A, B: sometime later + B ->> A: show me! + A ->> B: x + + note over B: im convinced. +``` + +A cryptographic commitment scheme has two notable properties: + +- **Hiding**: A commitment $\boxed{x}$ should reveal nothing about the underlying $x$. +- **Binding**: A commitment $\boxed{x}$, should only be revealed to $x$, i.e. for some $x' \ne x$ we shouldn't be able to compute a commitment $\boxed{x'} = \boxed{x}$. + +TODO: more needed diff --git a/book/src/preliminary/group-theory.md b/book/src/preliminary/group-theory.md new file mode 100644 index 0000000..493619a --- /dev/null +++ b/book/src/preliminary/group-theory.md @@ -0,0 +1,3 @@ +# Group Theory + +TODO diff --git a/circuits/arrays/distinct.circom b/circuits/arrays/distinct.circom new file mode 100644 index 0000000..5c4837a --- /dev/null +++ b/circuits/arrays/distinct.circom @@ -0,0 +1,52 @@ +pragma circom 2.1.0; + +include "../comparators/index.circom"; + +// Asserts that values in an array are unique. +// +// Parameters: +// - n: length of `in` +// +// Inputs: +// - in: an array of `n` values +template AssertDistinct(n) { + signal input in[n]; + + for (var i = 0; i < n-1; i++){ + for (var j = i+1; j < n; j++){ + var eq = IsEqual()([in[i], in[j]]); + eq === 0; + } + } +} + + +// Returns 1 if all values are distinct in a given array. +// +// Parameters: +// - n: length of `in` +// +// Inputs: +// - in: an array of `n` values +// +// Outputs: +// - out: 1 if all values are distinct +template IsDistinct(n) { + signal input in[n]; + signal output out; + + var acc = 0; + for (var i = 0; i < n-1; i++){ + for (var j = i+1; j < n; j++){ + var eq = IsEqual()([in[i], in[j]]); + acc += eq; + } + } + + // note that technically it is possible for `acc` to overflow + // and wrap back to 0, however, that is unlikely to happen given + // how large the prime-field is and we would need that many components + // to be able to overflow + signal outs <== acc; + out <== IsZero()(outs); +} diff --git a/circuits/arrays/index.circom b/circuits/arrays/index.circom index a9204a7..eced56e 100644 --- a/circuits/arrays/index.circom +++ b/circuits/arrays/index.circom @@ -15,7 +15,6 @@ include "../control-flow/index.circom"; // // Outputs: // - `out`: value at `in[index]` -// template ArrayRead(n) { signal input in[n]; signal input index; @@ -42,7 +41,6 @@ template ArrayRead(n) { // // Outputs: // - `out`: array -// template ArrayWrite(n) { signal input in[n]; signal input index; @@ -65,7 +63,6 @@ template ArrayWrite(n) { // // Outputs: // - `out`: sum of all values in `in` -// template Sum(n) { signal input in[n]; signal output out; @@ -76,22 +73,3 @@ template Sum(n) { } out <== lc; } - -// Asserts that values in an array are unique. -// -// Parameters: -// - `n`: length of `in` -// -// Inputs: -// - `in`: an array of `n` values -// -template AssertDistinct(n) { - signal input in[n]; - - for (var i = 0; i < n-1; i++){ - for (var j = i+1; j < n; j++){ - var eq = IsEqual()([in[i], in[j]]); - eq === 0; - } - } -} diff --git a/circuits/basics/fibonacci.circom b/circuits/basics/fibonacci.circom index 8542e26..3f1a68c 100644 --- a/circuits/basics/fibonacci.circom +++ b/circuits/basics/fibonacci.circom @@ -1,6 +1,12 @@ pragma circom 2.1.0; // Fibonacci with custom starting numbers +// +// Inputs: +// - in: the starting two numbers, usually 1 and 1 +// +// Outputs: +// - out: the `n`th Fibonacci number, w.r.t given starting numbers. template Fibonacci(n) { assert(n >= 2); signal input in[2]; @@ -14,4 +20,4 @@ template Fibonacci(n) { } out <== fib[n]; -} \ No newline at end of file +} diff --git a/circuits/basics/magic.circom b/circuits/basics/magic.circom index 46fb971..d0eac12 100644 --- a/circuits/basics/magic.circom +++ b/circuits/basics/magic.circom @@ -1,5 +1,15 @@ pragma circom 2.1.0; +// Prove that you know an N-by-N Magic Square with the magic `sum`. +// +// Parameters: +// - n: width/height of the square +// +// Inputs: +// - in: an N-by-N square +// +// Outputs: +// - sum: the magic sum template MagicSquare(n) { signal input in[n][n]; signal output sum; diff --git a/circuits/basics/multiplier.circom b/circuits/basics/multiplier.circom index 9895bc0..5b1f462 100644 --- a/circuits/basics/multiplier.circom +++ b/circuits/basics/multiplier.circom @@ -1,6 +1,17 @@ pragma circom 2.1.0; // Multiply `n` numbers. +// +// Note that the multiplication result, if greater than the prime modulus, will overflow and wrap back around! +// +// Parameters: +// - n: how many numbers there are +// +// Inputs: +// - in: `n` numbers +// +// Outputs: +// - out: `in[0] * in[1] * ... * in[n-1]` template Multiplier(n) { assert(n > 1); signal input in[n]; @@ -13,4 +24,4 @@ template Multiplier(n) { } out <== inner[n-2]; -} \ No newline at end of file +} diff --git a/circuits/basics/sudoku.circom b/circuits/basics/sudoku.circom index 6ec047a..cfa0209 100644 --- a/circuits/basics/sudoku.circom +++ b/circuits/basics/sudoku.circom @@ -1,10 +1,18 @@ pragma circom 2.1.0; include "../bits/index.circom"; -include "../arrays/index.circom"; +include "../arrays/distinct.circom"; include "../comparators/index.circom"; include "../comparators/range.circom"; +// Asserts that a solution is valid w.r.t a given public puzzle of an NxN square board. +// +// Parameters: +// - n_sqrt: Square root of `N` as described above +// +// Inputs: +// - solution[N][N]: the solution +// - puzzle[N][N]: the initial puzzle board template Sudoku(n_sqrt) { var n = n_sqrt * n_sqrt; // board size is a perfect square signal input solution[n][n]; // solution is a 2D array of numbers diff --git a/circuits/bits/gates.circom b/circuits/bits/gates.circom index 9e8105d..4ef5ae9 100644 --- a/circuits/bits/gates.circom +++ b/circuits/bits/gates.circom @@ -1,5 +1,12 @@ pragma circom 2.1.0; +// Logical AND operation. +// +// Inputs: +// - in: two bit-signals +// +// Outputs: +// - out: result of the logical operation template AND() { signal input in[2]; signal output out; @@ -7,6 +14,13 @@ template AND() { out <== in[0]*in[1]; } +// Logical OR operation. +// +// Inputs: +// - in: two bit-signals +// +// Outputs: +// - out: result of the logical operation template OR() { signal input in[2]; signal output out; @@ -14,6 +28,13 @@ template OR() { out <== in[0] + in[1] - in[0]*in[1]; } +// Logical XOR operation. +// +// Inputs: +// - in: two bit-signals +// +// Outputs: +// - out: result of the logical operation template XOR() { signal input in[2]; signal output out; @@ -21,13 +42,27 @@ template XOR() { out <== in[0] + in[1] - 2*in[0]*in[1]; } +// Logical NOT operation. +// +// Inputs: +// - in: a bit-signal +// +// Outputs: +// - out: result of the logical operation template NOT() { signal input in; signal output out; - out <== 1 + in - 2*in; + out <== 1 - in; } +// Logical NAND operation, i.e. AND followed by NOT. +// +// Inputs: +// - in: two bit-signals +// +// Outputs: +// - out: result of the logical operation template NAND() { signal input in[2]; signal output out; @@ -35,6 +70,13 @@ template NAND() { out <== 1 - (in[0]*in[1]); } +// Logical NOT operation, i.e. OR followed by NOT. +// +// Inputs: +// - in: two bit-signals +// +// Outputs: +// - out: result of the logical operation template NOR() { signal input in[2]; signal output out; diff --git a/circuits/bits/index.circom b/circuits/bits/index.circom index 3f6b02b..6180877 100644 --- a/circuits/bits/index.circom +++ b/circuits/bits/index.circom @@ -1,6 +1,9 @@ pragma circom 2.1.0; // Returns the minimum number of bits needed to represent `n` +// +// Inputs: +// - n: a number function nbits(n) { var ans = 0; @@ -14,13 +17,27 @@ function nbits(n) { } // Asserts that a given input is binary. +// +// Inputs: +// - in: an input signal, expected to be 0 or 1. template AssertBit() { signal input in; in * (in - 1) === 0; } + // Converts a number to bits while asserting that // it is `n`-bit representable. +// +// Parameters: +// - n: number of bits +// +// Inputs: +// - in: an input signal +// +// Outputs: +// - out: `n`-bits, with `out[0]` being the least-significant bit +// and `out[n-1]` being the most-significant bit. template Num2Bits(n) { assert(n < 254); signal input in; @@ -41,6 +58,16 @@ template Num2Bits(n) { } // Converts an `n`-bit number in binary form to decimal form. +// +// Parameters: +// - n: number of bits +// +// Inputs: +// - in: `n`-bits, with `in[0]` being the least-significant bit +// and `in[n-1]` being the most-significant bit +// +// Outputs: +// - out: the signal value corresponding to the given bits template Bits2Num(n) { assert(n < 254); signal input in[n]; @@ -59,6 +86,12 @@ template Bits2Num(n) { } // Asserts that a number is `n`-bit representable. +// +// Parameters: +// - n: number of bits +// +// Inputs: +// - in: an input signal template AssertBits(n) { assert(n < 254); signal input in; diff --git a/circuits/comparators/compconstant.circom b/circuits/comparators/compconstant.circom index fed7b25..19a28c5 100644 --- a/circuits/comparators/compconstant.circom +++ b/circuits/comparators/compconstant.circom @@ -5,11 +5,14 @@ include "../bits/index.circom"; // Compares a signal against a constant value. // If `in` is greater than constant, returns 1. // +// Parameters: +// - constant: a constant value +// // Inputs: -// - `in`: 254-bit binary representation of a number +// - in: 254-bit binary representation of a number // // Outputs: -// - `out`: 1 if `in > constant` +// - out: 1 if `in > constant` template CompConstant(constant) { signal input in[254]; signal output out; @@ -34,7 +37,7 @@ template CompConstant(constant) { var part_bit_a; var part_bit_b; - if ((constant_msb == 0) && (constant_lsb == 0)) { + /**/ if ((constant_msb == 0) && (constant_lsb == 0)) { part_bit_a = 1; part_bit_b = in_msb + in_lsb - in_msb*in_lsb; } diff --git a/circuits/comparators/index.circom b/circuits/comparators/index.circom index 537dd92..b5f015f 100644 --- a/circuits/comparators/index.circom +++ b/circuits/comparators/index.circom @@ -2,7 +2,13 @@ pragma circom 2.1.0; include "../bits/index.circom"; -// Returns 1 if `in == 0`. +// Returns a bit-signal indicating whether the input signal is 0. +// +// Inputs: +// - in: input signal +// +// Outputs: +// - out: 1 if `in` is zero, 0 otherwise. template IsZero() { signal input in; signal output out; @@ -13,7 +19,14 @@ template IsZero() { in * out === 0; } -// Returns 1 if `in[0] == in[1]`. +// Returns a bit-signal indicating whether the input signals are equal. +// This is equivalent to checking if their difference is zero. +// +// Inputs: +// - in[2]: two signals +// +// Outputs: +// - out: 1 if `in[0] == in[1]`, 0 otherwise. template IsEqual() { signal input in[2]; signal output out; @@ -21,7 +34,17 @@ template IsEqual() { out <== IsZero()(in[1] - in[0]); } -// Returns 1 if `in[0] < in[1]` and both are `n` bits. +// Returns a bit-signal indicating whether `in[0] < in[1]` and both are `n` bits. +// If one of the signals is larger than `n` bits, this will give an error. +// +// Parameters: +// - n: number of bits +// +// Inputs: +// - in[2]: two signals +// +// Outputs: +// - out: 1 if `in[0] < in[1]`, 0 otherwise. template LessThan(n) { assert(n <= 252); signal input in[2]; @@ -29,16 +52,24 @@ template LessThan(n) { // convert in[0] - in[1] + 2^n to bits // if in[0] > in[1], 2^n'th bit should be set - // if in[0] < in[1], 2^n'th bit should be reset due to borrow + // if in[0] < in[1], 2^n'th bit should be reset due to subtraction borrow component toBits = Num2Bits(n+1); toBits.in <== ((1 << n) + in[0]) - in[1]; out <== 1 - toBits.out[n]; } - -// N is the number of bits the input have. -// The MSF is the sign bit. +// Returns a bit-signal indicating whether `in[0] <= in[1]` and both are `n` bits. +// If one of the signals is larger than `n` bits, this will give an error. +// +// Parameters: +// - n: number of bits +// +// Inputs: +// - in[2]: two signals +// +// Outputs: +// - out: 1 if `in[0] <= in[1]`, 0 otherwise. template LessEqThan(n) { signal input in[2]; signal output out; @@ -46,8 +77,17 @@ template LessEqThan(n) { out <== LessThan(n)([in[0], in[1]+1]); } -// N is the number of bits the input have. -// The MSF is the sign bit. +// Returns a bit-signal indicating whether `in[0] > in[1]` and both are `n` bits. +// If one of the signals is larger than `n` bits, this will give an error. +// +// Parameters: +// - n: number of bits +// +// Inputs: +// - in[2]: two signals +// +// Outputs: +// - out: 1 if `in[0] > in[1]`, 0 otherwise. template GreaterThan(n) { signal input in[2]; signal output out; @@ -55,8 +95,17 @@ template GreaterThan(n) { out <== LessThan(n)([in[1], in[0]]); } -// N is the number of bits the input have. -// The MSF is the sign bit. +// Returns a bit-signal indicating whether `in[0] >= in[1]` and both are `n` bits. +// If one of the signals is larger than `n` bits, this will give an error. +// +// Parameters: +// - n: number of bits +// +// Inputs: +// - in[2]: two signals +// +// Outputs: +// - out: 1 if `in[0] >= in[1]`, 0 otherwise. template GreaterEqThan(n) { signal input in[2]; signal output out; diff --git a/circuits/comparators/range.circom b/circuits/comparators/range.circom index fd77b84..df46320 100644 --- a/circuits/comparators/range.circom +++ b/circuits/comparators/range.circom @@ -2,8 +2,15 @@ pragma circom 2.1.0; include "../bits/index.circom"; -// Checks that `in` is in range [MIN, MAX] +// Asserts that `in` is in range [MIN, MAX]. // In other words, `MIN <= in <= MAX`. +// +// Parameters: +// - MIN: minimum allowed value +// - MAX: maximum allowed value +// +// Inputs: +// - in: an input signal template AssertInRange(MIN, MAX) { assert(MIN < MAX); signal input in; diff --git a/circuits/control-flow/index.circom b/circuits/control-flow/index.circom index 3a6e0fe..4e08b5e 100644 --- a/circuits/control-flow/index.circom +++ b/circuits/control-flow/index.circom @@ -1,15 +1,14 @@ pragma circom 2.1.0; -// If-else branching. +// Conditionally returns one of the signals. // // Inputs: -// - `cond`: a boolean condition -// - `ifTrue`: signal to be returned if condition is true -// - `ifFalse`: signal to be returned if condition is false +// - cond: a boolean condition +// - ifTrue: signal to be returned if condition is true +// - ifFalse: signal to be returned if condition is false // // Outputs: -// - `out`: equals `cond ? ifTrue : ifFalse` -// +// - out: equals `cond ? ifTrue : ifFalse` template IfElse() { signal input cond; signal input ifTrue; @@ -19,14 +18,14 @@ template IfElse() { out <== cond * (ifTrue - ifFalse) + ifFalse; } -// Swaps in[0] ~ in[1] if `cond` is true. +// Swaps `in[0]` and `in[1]` if `cond` is true. // // Inputs: -// - `cond`: a boolean condition -// - `in`: two signals +// - cond: a boolean condition +// - in: two signals // // Outputs: -// - `out`: two signals either swapped or not +// - out: the two input signals, either swapped or not // template Switch() { signal input cond; diff --git a/tests/arrays/distinct.test.ts b/tests/arrays/distinct.test.ts new file mode 100644 index 0000000..1752a3f --- /dev/null +++ b/tests/arrays/distinct.test.ts @@ -0,0 +1,52 @@ +import type { WitnessTester } from "circomkit"; +import { circomkit } from "../common"; + +describe("distinct", () => { + const N = 3; + // arr = [0, 1, 2, 3, 4, 5, ...] + const distinctArr = Array.from({ length: N }, (_, i) => i); + // arr = [0, 0, 1, 1, 2, 2, ...] + const notDistinctArr = Array.from({ length: N }, (_, i) => i >> 1); + + describe("assert distinct", () => { + let circuit: WitnessTester<["in"]>; + + beforeAll(async () => { + circuit = await circomkit.WitnessTester(`distinct_${N}`, { + file: "arrays/distinct", + template: "AssertDistinct", + dir: "test/arrays", + params: [N], + }); + }); + + it("should pass if all inputs are unique", async () => { + await circuit.expectPass({ in: distinctArr }); + }); + + it("should fail if there is a duplicate", async () => { + await circuit.expectFail({ in: notDistinctArr }); + }); + }); + + describe("is distinct", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + beforeAll(async () => { + circuit = await circomkit.WitnessTester(`is_distinct_${N}`, { + file: "arrays/distinct", + template: "IsDistinct", + dir: "test/arrays", + params: [N], + }); + }); + + it("should return 1 if all inputs are unique", async () => { + await circuit.expectPass({ in: distinctArr }, { out: 1 }); + }); + + it("should return 0 if there is a duplicate", async () => { + await circuit.expectPass({ in: notDistinctArr }, { out: 0 }); + }); + }); +}); diff --git a/tests/arrays/index.test.ts b/tests/arrays/index.test.ts index 249dda0..4fb2066 100644 --- a/tests/arrays/index.test.ts +++ b/tests/arrays/index.test.ts @@ -1,37 +1,6 @@ import type { WitnessTester } from "circomkit"; import { circomkit } from "../common"; -describe("arrays", () => { - describe("distinct", () => { - const N = 3; - let circuit: WitnessTester<["in"]>; - - beforeAll(async () => { - circuit = await circomkit.WitnessTester(`distinct_${N}`, { - file: "arrays/index", - template: "AssertDistinct", - dir: "test/arrays", - params: [N], - }); - }); - - it("should pass if all inputs are unique", async () => { - await circuit.expectPass({ - in: Array(N) - .fill(0) - .map((_, i) => 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.skip("arrays", () => { + // TODO });