feat: CommitmentHasher and Withdrawal circuits (#1)

# 🤖 Linear

Closes 0XB-72
Closes 0XB-70
Closes 0XB-69

---------

Co-authored-by: drgorillamd <83670532+drgorillamd@users.noreply.github.com>
This commit is contained in:
moebius
2025-01-07 08:42:57 +00:00
committed by GitHub
parent 2840e0d2e7
commit c45821535e
30 changed files with 1366 additions and 1561 deletions

View File

@@ -1,12 +1,18 @@
name: tests
name: CI
on:
push:
branches:
- main
on: [push]
concurrency:
group: ${{github.workflow}}-${{github.ref}}
cancel-in-progress: true
defaults:
run:
working-directory: packages/circuits
jobs:
test:
name: Run Circuits Tests
runs-on: ubuntu-latest
steps:
@@ -22,9 +28,9 @@ jobs:
nasm \
nlohmann-json3-dev
- name: Download Circom Binary v2.1.5
- name: Download Circom Binary v2.2.1
run: |
wget -qO /home/runner/work/circom https://github.com/iden3/circom/releases/download/v2.1.5/circom-linux-amd64
wget -qO /home/runner/work/circom https://github.com/iden3/circom/releases/download/v2.2.1/circom-linux-amd64
chmod +x /home/runner/work/circom
sudo mv /home/runner/work/circom /bin/circom
@@ -34,5 +40,8 @@ jobs:
- name: Install dependencies
run: yarn
- name: Compile circuits
run: yarn compile
- name: Run tests
run: yarn test

View File

@@ -1,3 +1,5 @@
To use circomspect locally: `cargo install circomspect` then `yarn circomspec` from the packages/circuits directory.
# Circomkit Examples
In this repository, we are using [Circomkit](https://github.com/erhant/circomkit) to test some example circuits using Mocha. The circuits and the statements that they prove are as follows:

View File

@@ -1,5 +1,6 @@
{
"version": "2.1.2",
"proofSystem": "plonk",
"curve": "bn128"
"proofSystem": "groth16",
"curve": "bn128",
"include": "../../node_modules/"
}

View File

@@ -1,39 +1,10 @@
{
"multiplier_3": {
"file": "multiplier",
"template": "Multiplier",
"params": [3]
"commitment": {
"file": "commitment",
"template": "CommitmentHasher"
},
"sha256_32": {
"file": "sha256",
"template": "Sha256Bytes",
"params": [32]
},
"sudoku_9x9": {
"file": "sudoku",
"template": "Sudoku",
"pubs": ["puzzle"],
"params": [3]
},
"sudoku_4x4": {
"file": "sudoku",
"template": "Sudoku",
"pubs": ["puzzle"],
"params": [2]
},
"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]
"merkleTree": {
"file": "merkleTree",
"template": "MerkleTreeChecker"
}
}

View File

@@ -0,0 +1,43 @@
pragma circom 2.2.0;
include "../../../node_modules/circomlib/circuits/poseidon.circom";
/**
* @title CommitmentHasher template
* @dev Template for computing commitment hashes, precommitments and nullifier hashes
*/
template CommitmentHasher() {
//////////////////////// SIGNALS ////////////////////////
signal input value; // Value of commitment
signal input label; // keccak256(pool_scope, nonce)
signal input nullifier; // Nullifier of commitment
signal input secret; // Secret of commitment
signal output commitment; // Commitment hash
signal output precommitmentHash; // Precommitment hash
signal output nullifierHash; // Nullifier hash
///////////////////// END OF SIGNALS /////////////////////
// 1. Compute nullifier hash
component nullifierHasher = Poseidon(1);
nullifierHasher.inputs[0] <== nullifier;
// 2. Compute precommitment
component precommitmentHasher = Poseidon(2);
precommitmentHasher.inputs[0] <== nullifier;
precommitmentHasher.inputs[1] <== secret;
// 3. Compute commitment hash
component commitmentHasher = Poseidon(3);
commitmentHasher.inputs[0] <== value;
commitmentHasher.inputs[1] <== label;
commitmentHasher.inputs[2] <== precommitmentHasher.out;
// 4. Populate output signals
commitment <== commitmentHasher.out;
precommitmentHash <== precommitmentHasher.out;
nullifierHash <== nullifierHasher.out;
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,75 @@
pragma circom 2.2.0;
include "../../../node_modules/circomlib/circuits/poseidon.circom";
include "../../../node_modules/circomlib/circuits/comparators.circom";
include "../../../node_modules/circomlib/circuits/mux1.circom";
/**
* @title LeanIMTInclusionProof template
* @dev Template for generating and verifying inclusion proofs in a Lean Incremental Merkle Tree
* @notice This circuit follows the LeanIMT design where:
* 1. Every node with two children is the hash of its left and right nodes
* 2. Every node with one child has the same value as its child node
* 3. Tree is always built from leaves to root
* 4. Tree is always balanced by construction
* 5. Tree depth is dynamic and can increase with insertion of new leaves
* @param maxDepth The maximum depth of the Merkle tree
*/
template LeanIMTInclusionProof(maxDepth) {
//////////////////////// SIGNALS ////////////////////////
signal input leaf; // The leaf value to prove inclusion for
signal input leafIndex; // The index of the leaf in the tree
signal input siblings[maxDepth]; // The sibling values along the path to the root
signal input actualDepth; // Current tree depth (unused as |siblings| <= actualDepth)
_ <== actualDepth; // Silence unused signal warning
signal output out; // The computed root value
/////////////////// INTERNAL SIGNALS ///////////////////
signal nodes[maxDepth + 1]; // Array to store computed node values at each level
// signal intermediateRoots[maxDepth + 1]; // Array to store intermediate root values
signal indices[maxDepth]; // Array to store path indices for each level
////////////////// COMPONENT SIGNALS //////////////////
component siblingIsEmpty[maxDepth]; // Checks if sibling node is empty (zero)
component hashInCorrectOrder[maxDepth]; // Orders node pairs for hashing
component latestValidHash[maxDepth]; // Selects between hash and propagation
component poseidons[maxDepth];
/////////////////////// LOGIC ///////////////////////
// Convert leaf index to binary path
component indexToPath = Num2Bits(maxDepth);
indexToPath.in <== leafIndex;
indices <== indexToPath.out;
// Initialize with leaf value
nodes[0] <== leaf;
// For each level up to maxDepth
for (var i = 0; i < maxDepth; i++) {
// Prepare node pairs for both possible orderings (left/right)
var childrenToSort[2][2] = [ [nodes[i], siblings[i]], [siblings[i], nodes[i]] ];
hashInCorrectOrder[i] = MultiMux1(2);
hashInCorrectOrder[i].c <== childrenToSort;
hashInCorrectOrder[i].s <== indices[i];
// hash the nodes
poseidons[i] = Poseidon(2);
poseidons[i].inputs <== hashInCorrectOrder[i].out;
// Check if sibling is empty (zero)
siblingIsEmpty[i] = IsZero();
siblingIsEmpty[i].in <== siblings[i];
// Either keep the previous hash (no more siblings) or the new one
nodes[i + 1] <== (nodes[i] - poseidons[i].out) * siblingIsEmpty[i].out + poseidons[i].out;
}
// Output final computed root
out <== nodes[maxDepth];
}

View File

@@ -1,53 +0,0 @@
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];
}

View File

@@ -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;
}
}

View File

@@ -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++;
}
}
}

View File

@@ -0,0 +1,108 @@
pragma circom 2.2.0;
include "./commitment.circom";
include "./merkleTree.circom";
include "../../../node_modules/circomlib/circuits/comparators.circom";
/**
* @title Withdraw template
* @dev Template for withdrawing value from a commitment
* @param maxTreeDepth The maximum depth of the Merkle trees
*/
template Withdraw(maxTreeDepth) {
//////////////////////// PUBLIC SIGNALS ////////////////////////
// Signals to compute commitments
signal input withdrawnValue; // Value being withdrawn
// Signals for merkle tree inclusion proofs
signal input stateRoot; // A known state root
signal input stateTreeDepth; // Current state tree depth
signal input ASPRoot; // Latest ASP root
signal input ASPTreeDepth; // Current ASP tree depth
signal input context; // keccak256(scope, Withdrawal)
//////////////////// END OF PUBLIC SIGNALS ////////////////////
/////////////////////// PRIVATE SIGNALS ///////////////////////
// Signals to compute commitments
signal input label; // keccak256(scope, nonce)
signal input existingValue; // Value of the existing commitment
signal input existingNullifier; // Nullifier of the existing commitment
signal input existingSecret; // Secret of the existing commitment
signal input newNullifier; // Nullifier for the new commitment
signal input newSecret; // Secret for the new commitment
// Signals for merkle tree inclusion proofs
signal input stateSiblings[maxTreeDepth]; // Siblings of the state tree
signal input stateIndex; // Indices for the state tree
signal input ASPSiblings[maxTreeDepth]; // Siblings of the ASP tree
signal input ASPIndex; // Indices for the ASP tree
/////////////////// END OF PRIVATE SIGNALS ///////////////////
/////////////////////// OUTPUT SIGNALS ///////////////////////
signal output newCommitmentHash; // Hash of new commitment
signal output existingNullifierHash; // Hash of the existing commitment nullifier
/////////////////// END OF OUTPUT SIGNALS ///////////////////
// 1. Compute existing commitment
component existingCommitmentHasher = CommitmentHasher();
existingCommitmentHasher.value <== existingValue;
existingCommitmentHasher.label <== label;
existingCommitmentHasher.nullifier <== existingNullifier;
existingCommitmentHasher.secret <== existingSecret;
signal existingCommitment <== existingCommitmentHasher.commitment;
// 2. Output existing nullifier hash
existingNullifierHash <== existingCommitmentHasher.nullifierHash;
_ <== existingCommitmentHasher.precommitmentHash;
// 3. Verify existing commitment is in state tree
component stateRootChecker = LeanIMTInclusionProof(maxTreeDepth);
stateRootChecker.leaf <== existingCommitment;
stateRootChecker.leafIndex <== stateIndex;
stateRootChecker.siblings <== stateSiblings;
stateRootChecker.actualDepth <== stateTreeDepth;
stateRoot === stateRootChecker.out;
// 4. Verify label is in ASP tree
component ASPRootChecker = LeanIMTInclusionProof(maxTreeDepth);
ASPRootChecker.leaf <== label;
ASPRootChecker.leafIndex <== ASPIndex;
ASPRootChecker.siblings <== ASPSiblings;
ASPRootChecker.actualDepth <== ASPTreeDepth;
ASPRoot === ASPRootChecker.out;
// 5. Check the withdrawn amount is valid
signal remainingValue <== existingValue - withdrawnValue;
component remainingValueRangeCheck = Num2Bits(128);
remainingValueRangeCheck.in <== remainingValue;
_ <== remainingValueRangeCheck.out;
component withdrawnValueRangeCheck = Num2Bits(128);
withdrawnValueRangeCheck.in <== withdrawnValue;
_ <== withdrawnValueRangeCheck.out;
// 6. Compute new commitment
component newCommitmentHasher = CommitmentHasher();
newCommitmentHasher.value <== remainingValue;
newCommitmentHasher.label <== label;
newCommitmentHasher.nullifier <== newNullifier;
newCommitmentHasher.secret <== newSecret;
// 7. Output new commitment hash
newCommitmentHash <== newCommitmentHasher.commitment;
_ <== newCommitmentHasher.precommitmentHash;
_ <== newCommitmentHasher.nullifierHash;
// 8. Square context for integrity
signal contextSquared <== context * context;
}

View File

@@ -1,3 +0,0 @@
{
"in": [1, 1]
}

View File

@@ -1,4 +0,0 @@
{
"e": ["1122", "1024"],
"m": ["7807742059002284", "7045130465601185"]
}

View File

@@ -1,3 +0,0 @@
{
"in": [2, 4, 10]
}

View File

@@ -1,6 +0,0 @@
{
"in": [
116, 111, 100, 97, 121, 32, 105, 115, 32, 97, 32, 103, 111, 111, 100, 32, 100, 97, 121, 44, 32, 110, 111, 116, 32,
101, 118, 101, 114, 121, 100, 97, 121, 32, 105, 115
]
}

View File

@@ -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]
]
}

View File

@@ -1,14 +1,22 @@
{
"name": "@privacy-pool-core/circuits",
"description": "Circomkit examples",
"version": "1.0.0",
"description": "Circom circuits for the Privacy Pool protocol",
"scripts": {
"start": "npx ts-node ./src/index.ts",
"test": "npx mocha"
"compile": "npx ts-node ./src/index.ts",
"test": "npx mocha",
"test:merkle": "npx mocha --ignore tests/commitment.test.ts --ignore tests/withdrawal.test.ts",
"test:withdraw": "npx mocha --ignore tests/commitment.test.ts --ignore tests/lean-imt.test.ts",
"circomspect": "circomspect -L ../../node_modules/circomlib/circuits/comparators.circom -L ../../node_modules/circomlib/circuits/mux1.circom -L ../../node_modules/circomlib/circuits/poseidon.circom ./circuits/*"
},
"dependencies": {
"circomkit": "^0.0.22",
"circomlib": "^2.0.5"
"@zk-kit/lean-imt": "^2.2.2",
"chai": "^5.1.2",
"circomkit": "^0.3.2",
"circomlib": "^2.0.5",
"maci-circuits": "^2.5.0",
"maci-crypto": "^2.5.0",
"viem": "^2.21.57"
},
"devDependencies": {
"@types/mocha": "^10.0.1",

View File

@@ -4,25 +4,32 @@ async function main() {
// create circomkit
const circomkit = new Circomkit({
protocol: "groth16",
include: ["../../node_modules/circomlib/circuits", "../../node_modules/maci-circuits/circom"],
inspect: true,
});
// artifacts output at `build/multiplier_3` directory
await circomkit.compile("multiplier_3", {
file: "multiplier",
template: "Multiplier",
params: [3],
// artifacts output at `build/commitment` directory
await circomkit.compile("commitment", {
file: "commitment",
template: "CommitmentHasher",
pubs: ["value", "label", "nullifier", "secret"],
});
// proof & public signals at `build/multiplier_3/my_input` directory
await circomkit.prove("multiplier_3", "my_input", { in: [3, 5, 7] });
// artifacts output at `build/withdraw` directory
await circomkit.compile("withdraw", {
file: "withdraw",
template: "Withdraw",
params: [32],
pubs: ["withdrawnValue", "stateRoot", "stateTreeDepth", "ASPRoot", "ASPTreeDepth", "context"],
});
// verify with proof & public signals at `build/multiplier_3/my_input`
const ok = await circomkit.verify("multiplier_3", "my_input");
if (ok) {
circomkit.log("Proof verified!", "success");
} else {
circomkit.log("Verification failed.", "error");
}
// artifacts output at `build/merkleTree` directory
await circomkit.compile("merkleTree", {
file: "merkleTree",
template: "LeanIMTInclusionProof",
params: [32],
pubs: ["leaf", "leafIndex", "siblings", "actualDepth"],
});
}
main()

View File

@@ -0,0 +1,212 @@
import { WitnessTester } from "circomkit";
import { circomkit, hashCommitment, randomBigInt, Commitment } from "./common";
import { poseidon } from "../../../node_modules/maci-crypto/build/ts/hashing.js"; // TODO: fix maci import
import { parseEther, hexToBigInt, getAddress } from "viem";
describe("CommitmentHasher Circuit", () => {
let circuit: WitnessTester<
["value", "label", "nullifier", "secret"],
["commitment", "precommitmentHash", "nullifierHash"]
>;
const depositor = getAddress("0x9F2db792a6F2dAdf25D894cEd791080950bDE56f");
const NONCE = BigInt(1);
const LABEL = poseidon([hexToBigInt(depositor), randomBigInt(), NONCE]);
before(async () => {
circuit = await circomkit.WitnessTester(`commitment`, {
file: "commitment",
template: "CommitmentHasher",
pubs: ["value", "label", "nullifier", "secret"],
});
});
it("should compute commitment hash correctly", async () => {
const input = {
value: parseEther("1"),
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
const [commitmentHash, precommitmentHash, nullifierHash] = hashCommitment(input);
await circuit.expectPass(
{
value: input.value,
label: LABEL,
nullifier: input.nullifier,
secret: input.secret,
},
{ commitment: commitmentHash, precommitmentHash, nullifierHash }
);
});
it("should compute child commitment hash correctly", async () => {
const parentInput: Commitment = {
value: parseEther("2"),
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
const [parentHash] = hashCommitment(parentInput);
const childInput: Commitment = {
value: parseEther("1"),
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
const [commitmentHash, precommitmentHash, nullifierHash] = hashCommitment(childInput);
await circuit.expectPass(
{
value: childInput.value,
label: LABEL,
nullifier: childInput.nullifier,
secret: childInput.secret,
},
{ commitment: commitmentHash, precommitmentHash, nullifierHash }
);
});
describe("Boundary Values", () => {
it("should handle minimum values", async () => {
const input: Commitment = {
value: BigInt(0),
label: LABEL,
nullifier: BigInt(0),
secret: BigInt(0),
};
const [commitmentHash, precommitmentHash, nullifierHash] = hashCommitment(input);
await circuit.expectPass(
{
value: input.value,
label: LABEL,
nullifier: input.nullifier,
secret: input.secret,
},
{ commitment: commitmentHash, precommitmentHash, nullifierHash }
);
});
it("should handle maximum field values", async () => {
// P is the Goldilocks field modulus used by circom
const P = BigInt("18446744073709551615");
const input: Commitment = {
value: P - BigInt(1),
label: LABEL,
nullifier: P - BigInt(1),
secret: P - BigInt(1),
};
const [commitmentHash, precommitmentHash, nullifierHash] = hashCommitment(input);
await circuit.expectPass(
{
value: input.value,
label: LABEL,
nullifier: input.nullifier,
secret: input.secret,
},
{ commitment: commitmentHash, precommitmentHash, nullifierHash }
);
});
it("should verify nullifier hash depends only on nullifier", async () => {
const base: Commitment = {
value: parseEther("1"),
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
const modified = { ...base, value: parseEther("2"), label: randomBigInt(), secret: randomBigInt() };
const [_, __, nullifierHash1] = hashCommitment(base);
const [___, ____, nullifierHash2] = hashCommitment(modified);
if (nullifierHash1 != nullifierHash2) {
throw new Error("Nullifier hashes don't match");
}
});
it("should verify precommitment hash depends only on nullifier and secret", async () => {
const base: Commitment = {
value: parseEther("1"),
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
const modified = {
...base,
value: parseEther("2"),
label: randomBigInt(),
};
const [_, precommitmentHash1] = hashCommitment(base);
const [__, precommitmentHash2] = hashCommitment(modified);
if (precommitmentHash1 != precommitmentHash2) {
throw new Error("Precommitment hashes don't match");
}
});
it("should produce consistent outputs with same inputs", async () => {
const input: Commitment = {
value: parseEther("1"),
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
const [hash1, pre1, null1] = hashCommitment(input);
const [hash2, pre2, null2] = hashCommitment(input);
const [hash3, pre3, null3] = hashCommitment(input);
if (hash1 != hash2 || hash2 != hash3) {
throw new Error("Commitment hashes don't match");
}
if (pre1 != pre2 || pre2 != pre3) {
throw new Error("Precommitment hashes don't match");
}
if (null1 != null2 || null2 != null3) {
throw new Error("Nullifier hashes don't match");
}
});
it("should produce different outputs when single input changes", async () => {
const base: Commitment = {
value: parseEther("1"),
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
const modifications: (keyof Commitment)[] = ["value", "label", "nullifier", "secret"];
const baseHash = hashCommitment(base)[0];
for (const field of modifications) {
const modified = { ...base };
if (field === "value") modified.value = parseEther("2");
else if (field === "label") modified.label = randomBigInt();
else modified[field] = randomBigInt();
const modifiedHash = hashCommitment(modified)[0];
if (modifiedHash == baseHash) {
throw new Error("Hashes shouldn't match");
}
}
});
});
});

View File

@@ -1,5 +1,34 @@
import { Circomkit } from "circomkit";
import { poseidon } from "../../../../node_modules/maci-crypto/build/ts/hashing.js";
export interface Commitment {
value: bigint;
label: bigint;
nullifier: bigint;
secret: bigint;
}
export const circomkit = new Circomkit({
verbose: false,
protocol: "groth16",
include: ["../../node_modules/circomlib/circuits", "../../node_modules/maci-circuits/circom"],
});
export function hashCommitment(input: Commitment): [bigint, bigint, bigint] {
const precommitment = poseidon([BigInt(input.nullifier), BigInt(input.secret)]);
const nullifierHash = poseidon([BigInt(input.nullifier)]);
const commitmentHash = poseidon([BigInt(input.value), BigInt(input.label), precommitment]);
return [commitmentHash, precommitment, nullifierHash];
}
export function randomBigInt(): bigint {
return BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));
}
export function padSiblings(siblings: bigint[], targetDepth: number): bigint[] {
const paddedSiblings = [...siblings];
while (paddedSiblings.length < targetDepth) {
paddedSiblings.push(BigInt(0));
}
return paddedSiblings;
}

View File

@@ -1,51 +0,0 @@
import { WitnessTester } from "circomkit";
import { circomkit } from "./common";
describe("fibonacci", () => {
const N = 7;
let circuit: WitnessTester<["in"], ["out"]>;
describe("vanilla", () => {
before(async () => {
circuit = await circomkit.WitnessTester(`fibonacci_${N}`, {
file: "fibonacci",
template: "Fibonacci",
params: [N],
});
console.log("#constraints:", await circuit.getConstraintCount());
});
it("should compute correctly", async () => {
await circuit.expectPass({ in: [1, 1] }, { out: fibonacci([1, 1], N) });
});
});
describe("recursive", () => {
before(async () => {
circuit = await circomkit.WitnessTester(`fibonacci_${N}_recursive`, {
file: "fibonacci",
template: "FibonacciRecursive",
params: [N],
});
console.log("#constraints:", await circuit.getConstraintCount());
});
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;
}

View File

@@ -1,427 +0,0 @@
import { WitnessTester } from "circomkit";
import { circomkit } from "./common";
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: WitnessTester<["e", "m"], ["e_out", "m_out"]>;
const k = 8;
const p = 23;
before(async () => {
circuit = await circomkit.WitnessTester("fp32", {
file: "float_add",
template: "FloatAdd",
params: [k, p],
});
});
it("should have correct number of constraints", async () => {
await circuit.expectConstraintCount(expectedConstraints.fp32);
});
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: WitnessTester<["e", "m"], ["e_out", "m_out"]>;
before(async () => {
circuit = await circomkit.WitnessTester("fp64", {
file: "float_add",
template: "FloatAdd",
params: [k, p],
});
});
it("should have correct number of constraints", async () => {
await circuit.expectConstraintCount(expectedConstraints.fp64);
});
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: WitnessTester<["in"], ["out"]>;
before(async () => {
circuit = await circomkit.WitnessTester(`cbl_${b}`, {
file: "float_add",
template: "CheckBitLength",
params: [b],
dir: "test/float_add",
});
});
it("should have correct number of constraints", async () => {
await circuit.expectConstraintCount(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: WitnessTester<["x", "shift", "skip_checks"], ["y"]>;
before(async () => {
circuit = await circomkit.WitnessTester(`shl_${shift_bound}`, {
file: "float_add",
template: "LeftShift",
dir: "test/float_add",
params: [shift_bound],
});
});
it("should have correct number of constraints", async () => {
await circuit.expectConstraintCount(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: WitnessTester<["x"], ["y"]>;
before(async () => {
circuit = await circomkit.WitnessTester(`shr_${b}`, {
file: "float_add",
template: "RightShift",
dir: "test/float_add",
params: [b, shift],
});
});
it("should have correct number of constraints", async () => {
await circuit.expectConstraintCount(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: WitnessTester<["e", "m", "skip_checks"], ["e_out", "m_out"]>;
before(async () => {
circuit = await circomkit.WitnessTester(`normalize_${k}_${p}_${P}`, {
file: "float_add",
template: "Normalize",
params: [k, p, P],
dir: "test/float_add",
});
});
it("should have correct number of constraints", async () => {
await circuit.expectConstraintCount(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: WitnessTester<["in", "skip_checks"], ["one_hot"]>;
before(async () => {
circuit = await circomkit.WitnessTester(`msnzb_${b}`, {
file: "float_add",
template: "MSNZB",
dir: "test/float_add",
params: [b],
});
});
it("should have correct number of constraints", async () => {
await circuit.expectConstraintCount(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" });
});
});
});

View File

@@ -0,0 +1,56 @@
import { WitnessTester } from "circomkit";
import { circomkit, randomBigInt, padSiblings } from "./common";
import { hashLeftRight } from "../../../node_modules/maci-crypto/build/ts/hashing.js"; // TODO: fix maci import
import { LeanIMT } from "@zk-kit/lean-imt";
describe("LeanIMTInclusionProof Circuit", () => {
let circuit: WitnessTester<["leaf", "leafIndex", "siblings", "actualDepth"], ["out"]>;
const hash = (a: bigint, b: bigint) => hashLeftRight(a, b);
const maxDepth = 8;
// Lean Incrementral Merkle tree
let tree: LeanIMT<bigint>;
before(async () => {
circuit = await circomkit.WitnessTester(`merkleTree`, {
file: "merkleTree",
template: "LeanIMTInclusionProof",
pubs: ["leaf", "leafIndex", "siblings", "actualDepth"],
params: [maxDepth],
});
});
// Flush trees before each test
beforeEach(async () => {
tree = new LeanIMT(hash);
});
it("Should compute roots correctly", async () => {
const LEAVES = 16;
let leavesIndexes = [];
// insert leaves
for (let i = 0; i < LEAVES; ++i) {
let leafValue = randomBigInt();
tree!.insert(leafValue);
leavesIndexes.push(tree.indexOf(leafValue));
}
for (let i = 0; i < LEAVES; ++i) {
let stateProof = tree.generateProof(i);
await circuit.expectPass(
{
leaf: stateProof.leaf,
leafIndex: stateProof.index,
siblings: padSiblings(stateProof.siblings, maxDepth),
actualDepth: tree.depth,
},
{ out: tree.root }
);
}
});
});

View File

@@ -1,42 +0,0 @@
import { WitnessTester } from "circomkit";
import { circomkit } from "./common";
describe("multiplier", () => {
const N = 3;
let circuit: WitnessTester<["in"], ["out"]>;
before(async () => {
circuit = await circomkit.WitnessTester(`multiplier_${N}`, {
file: "multiplier",
template: "Multiplier",
params: [N],
});
});
it("should have correct number of constraints", async () => {
await circuit.expectConstraintCount(N - 1);
});
it("should multiply 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) });
});
});
describe("multiplier utilities", () => {
describe("multiplication gate", () => {
let circuit: WitnessTester<["in"], ["out"]>;
before(async () => {
circuit = await circomkit.WitnessTester("mulgate", {
file: "multiplier",
template: "MultiplicationGate",
dir: "test/multiplier",
});
});
it("should multiply correctly", async () => {
await circuit.expectPass({ in: [7, 5] }, { out: 7 * 5 });
});
});
});

View File

@@ -1,38 +0,0 @@
import { WitnessTester } from "circomkit";
import { createHash } from "crypto";
import { circomkit } from "./common";
describe("sha256", () => {
let circuit: WitnessTester<["in"], ["out"]>;
// number of bytes for the sha256 input
const NUM_BYTES = 36;
// preimage and its byte array
const PREIMAGE = Buffer.from("today is a good day, not everyday is");
const PREIMAGE_BYTES = PREIMAGE.toJSON().data;
// digest and its byte array
const DIGEST = createHash("sha256").update(PREIMAGE).digest("hex");
const DIGEST_BYTES = Buffer.from(DIGEST, "hex").toJSON().data;
// circuit signals
const INPUT = {
in: PREIMAGE_BYTES,
};
const OUTPUT = {
out: DIGEST_BYTES,
};
before(async () => {
circuit = await circomkit.WitnessTester(`sha256_${NUM_BYTES}`, {
file: "sha256",
template: "Sha256Bytes",
params: [NUM_BYTES],
});
});
it("should compute hash correctly", async () => {
await circuit.expectPass(INPUT, OUTPUT);
});
});

View File

@@ -1,200 +0,0 @@
import { WitnessTester, CircuitSignals } from "circomkit";
import { circomkit } from "./common";
const BOARD_SIZES = [4, 9] as const;
const INPUTS: { [N in (typeof BOARD_SIZES)[number]]: 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],
],
},
};
BOARD_SIZES.map((N) =>
describe(`sudoku (${N} by ${N})`, () => {
const INPUT = INPUTS[N];
let circuit: WitnessTester<["solution", "puzzle"]>;
before(async () => {
circuit = await circomkit.WitnessTester(`sudoku_${N}x${N}`, {
file: "sudoku",
template: "Sudoku",
pubs: ["puzzle"],
params: [Math.sqrt(N)],
});
});
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: WitnessTester<["in"], []>;
before(async () => {
circuit = await circomkit.WitnessTester(`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: WitnessTester<["in"], []>;
before(async () => {
circuit = await circomkit.WitnessTester(`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: WitnessTester<["in"]>;
before(async () => {
circuit = await circomkit.WitnessTester(`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,
});
}
});
});
});

View File

@@ -0,0 +1,467 @@
import { WitnessTester } from "circomkit";
import { circomkit, hashCommitment, randomBigInt, padSiblings, Commitment } from "./common";
import { poseidon } from "../../../node_modules/maci-crypto/build/ts/hashing.js";
import { parseEther, hexToBigInt, getAddress } from "viem";
import { LeanIMT } from "@zk-kit/lean-imt";
// TODO: once the circuits are unchanged, add assertion on error messages
describe("Withdraw Circuit", () => {
let circuit: WitnessTester<
[
"withdrawnValue",
"stateRoot",
"stateTreeDepth",
"ASPRoot",
"ASPTreeDepth",
"context",
"label",
"existingValue",
"existingNullifier",
"existingSecret",
"newNullifier",
"newSecret",
"stateSiblings",
"stateIndex",
"ASPSiblings",
"ASPIndex"
],
["newCommitmentHash", "existingNullifierHash"]
>;
// Test constants
const maxTreeDepth = 32;
const LABEL = randomBigInt(); // Random label for original deposit. Should be keccak256(SCOPE, nonce)
const REMOVED_LEAF = poseidon([BigInt(0)]);
// Original deposit for 5 ETH
const deposit = {
value: parseEther("5"),
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
// Get deposit commitment hash
const [depositHash, , depositNullifierHash] = hashCommitment(deposit);
// Using Poseidon for hashing the tree nodes
const hash = (a: bigint, b: bigint) => poseidon([a, b]);
// Lean Incrementral Merkle trees
let stateTree: LeanIMT<bigint>;
let ASPTree: LeanIMT<bigint>;
// Instantiate circuit with public signals
before(async () => {
circuit = await circomkit.WitnessTester("withdraw", {
file: "withdraw",
template: "Withdraw",
params: [maxTreeDepth],
pubs: ["withdrawnValue", "stateRoot", "stateTreeDepth", "ASPRoot", "ASPTreeDepth", "context"],
});
});
// Flush trees before each test
beforeEach(async () => {
stateTree = new LeanIMT(hash);
ASPTree = new LeanIMT(hash);
});
it("Should allow withdrawing value from an approved deposit", async () => {
// Insert deposit commitment in state tree
stateTree!.insert(randomBigInt());
stateTree!.insert(randomBigInt());
stateTree!.insert(randomBigInt());
stateTree!.insert(depositHash);
// Insert deposit label in ASP tree (deposit is now validated)
ASPTree!.insert(randomBigInt());
ASPTree!.insert(randomBigInt());
ASPTree!.insert(randomBigInt());
ASPTree!.insert(LABEL);
// Withrdaw 1 ETH from deposit
const withdrawal = {
value: parseEther("4"), // New value after withdrawal
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
// Hash the new commitment
const [commitmentHash, ,] = hashCommitment(withdrawal);
// Generate merkle proofs for commitment and label
let stateProof = stateTree.generateProof(3);
let ASPProof = ASPTree.generateProof(3);
// Spend the deposit commitment and create a new commitment for the remaining value
await circuit.expectPass(
{
withdrawnValue: parseEther("1"),
stateRoot: stateProof.root,
stateTreeDepth: stateTree.depth,
ASPRoot: ASPProof.root,
ASPTreeDepth: ASPTree.depth,
context: randomBigInt(),
label: LABEL,
existingValue: deposit.value,
existingNullifier: deposit.nullifier,
existingSecret: deposit.secret,
newNullifier: withdrawal.nullifier,
newSecret: withdrawal.secret,
stateSiblings: padSiblings(stateProof.siblings, maxTreeDepth),
stateIndex: stateProof.index,
ASPSiblings: padSiblings(ASPProof.siblings, maxTreeDepth),
ASPIndex: ASPProof.index,
},
{
newCommitmentHash: commitmentHash,
existingNullifierHash: depositNullifierHash,
}
);
});
it("Should allow withdrawing value from a child commitment", async () => {
// Insert deposit in state tree
stateTree!.insert(randomBigInt());
stateTree!.insert(randomBigInt());
stateTree!.insert(depositHash);
// Insert deposit label in ASP tree to mark as validated
ASPTree!.insert(randomBigInt());
ASPTree!.insert(randomBigInt());
ASPTree!.insert(randomBigInt());
ASPTree!.insert(randomBigInt());
ASPTree!.insert(randomBigInt());
ASPTree!.insert(LABEL);
// First withdrawal
const firstChild = {
value: parseEther("4"), // New value after withdrawal
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
// Hash first withdrawal commitment
const [firstChildCommitmentHash, , firstChildNullifierHash] = hashCommitment(firstChild);
// Generate merkle proofs to spend deposit
let stateProof = stateTree.generateProof(2);
let ASPProof = ASPTree.generateProof(5);
// Spend the deposit commitment and create the new commitment for the remaining value
await circuit.expectPass(
{
withdrawnValue: parseEther("1"),
stateRoot: stateProof.root,
stateTreeDepth: stateTree.depth,
ASPRoot: ASPProof.root,
ASPTreeDepth: ASPTree.depth,
context: randomBigInt(),
label: LABEL,
existingValue: deposit.value,
existingNullifier: deposit.nullifier,
existingSecret: deposit.secret,
newNullifier: firstChild.nullifier,
newSecret: firstChild.secret,
stateSiblings: padSiblings(stateProof.siblings, maxTreeDepth),
stateIndex: stateProof.index,
ASPSiblings: padSiblings(ASPProof.siblings, maxTreeDepth),
ASPIndex: ASPProof.index,
},
{
newCommitmentHash: firstChildCommitmentHash,
existingNullifierHash: depositNullifierHash,
}
);
// Insert the new child commitment in the state tree
stateTree!.insert(firstChildCommitmentHash);
// Second child product of spending the first child and withdrawing 2.5 ETH
let secondChild = {
value: parseEther("1.5"), // New value after withdrawal
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
// Hash the second child commitment
const [secondChildComitmentHash, ,] = hashCommitment(secondChild);
// Regenerate state merkle proof
let newStateProof = stateTree.generateProof(3);
// Spend the first child and create the second one for the remaining 1.5 ETH
await circuit.expectPass(
{
withdrawnValue: parseEther("2.5"),
stateRoot: newStateProof.root,
stateTreeDepth: stateTree.depth,
ASPRoot: ASPProof.root,
ASPTreeDepth: ASPTree.depth,
context: randomBigInt(),
label: LABEL,
existingValue: firstChild.value,
existingNullifier: firstChild.nullifier,
existingSecret: firstChild.secret,
newNullifier: secondChild.nullifier,
newSecret: secondChild.secret,
stateSiblings: padSiblings(newStateProof.siblings, maxTreeDepth),
stateIndex: newStateProof.index,
ASPSiblings: padSiblings(ASPProof.siblings, maxTreeDepth),
ASPIndex: ASPProof.index,
},
{
newCommitmentHash: secondChildComitmentHash,
existingNullifierHash: firstChildNullifierHash,
}
);
});
it("Should allow for a full value withdrawal", async () => {
// Insert commitment in state
stateTree!.insert(randomBigInt());
stateTree!.insert(depositHash);
stateTree!.insert(randomBigInt());
stateTree!.insert(randomBigInt());
stateTree!.insert(randomBigInt());
stateTree!.insert(randomBigInt());
// Insert label in ASP to mark as validated
ASPTree!.insert(randomBigInt());
ASPTree!.insert(randomBigInt());
ASPTree!.insert(LABEL);
ASPTree!.insert(randomBigInt());
// Withdraw the full amount of the existing commitment. New value = 0 wei
let fullWithdrawal = {
value: parseEther("0"), // Empty value after full withdrawal
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
// Hash new commitment of zero value
const [commitmentHash, ,] = hashCommitment(fullWithdrawal);
// generate merkle proofs
let stateProof = stateTree.generateProof(1);
let ASPProof = ASPTree.generateProof(2);
// Fully spend the existing commitment
await circuit.expectPass(
{
withdrawnValue: parseEther("5"),
stateRoot: stateProof.root,
stateTreeDepth: stateTree.depth,
ASPRoot: ASPProof.root,
ASPTreeDepth: ASPTree.depth,
context: randomBigInt(),
label: LABEL,
existingValue: parseEther("5"),
existingNullifier: deposit.nullifier,
existingSecret: deposit.secret,
newNullifier: fullWithdrawal.nullifier,
newSecret: fullWithdrawal.secret,
stateSiblings: padSiblings(stateProof.siblings, maxTreeDepth),
stateIndex: stateProof.index,
ASPSiblings: padSiblings(ASPProof.siblings, maxTreeDepth),
ASPIndex: ASPProof.index,
},
{
newCommitmentHash: commitmentHash,
existingNullifierHash: depositNullifierHash,
}
);
});
it("Withdrawal should fail if existing commitment is not in the state tree", async () => {
// Insert commitment in state
stateTree!.insert(randomBigInt());
stateTree!.insert(randomBigInt());
// Insert leaves in ASP tree
ASPTree!.insert(randomBigInt());
ASPTree!.insert(randomBigInt());
// Withdraw any amount
let withdrawal = {
value: parseEther("1"),
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
// Generate ASP tree merkle proof for another commitment. Generating a proof for a commitment that is not in the tree will fail
let stateProof = stateTree.generateProof(1);
// Generate ASP tree merkle proof for another label. Generating a proof for a label that is not in the tree will fail
let ASPProof = ASPTree.generateProof(1);
// Fail when trying to spend a commitment that is not in the state tree
// TODO: add assertion on error message. Currently fails on line 72 => state tree inclusion check
await circuit.expectFail({
withdrawnValue: parseEther("4"),
stateRoot: stateProof.root,
stateTreeDepth: stateTree.depth,
ASPRoot: ASPProof.root,
ASPTreeDepth: ASPTree.depth,
context: randomBigInt(),
label: LABEL,
existingValue: parseEther("5"),
existingNullifier: deposit.nullifier, // User only knows the secrets of the deposit
existingSecret: deposit.secret, // User only knows the secrets of the deposit
newNullifier: withdrawal.nullifier,
newSecret: withdrawal.secret,
stateSiblings: padSiblings(stateProof.siblings, maxTreeDepth),
stateIndex: stateProof.index,
ASPSiblings: padSiblings(ASPProof.siblings, maxTreeDepth),
ASPIndex: ASPProof.index,
});
});
it("Withdrawal should fail if label is not present in the ASP tree", async () => {
// Insert commitment in state
stateTree!.insert(randomBigInt());
stateTree!.insert(randomBigInt());
stateTree!.insert(randomBigInt());
stateTree!.insert(randomBigInt());
stateTree!.insert(depositHash);
stateTree!.insert(randomBigInt());
stateTree!.insert(randomBigInt());
// Insert leaves in ASP tree
ASPTree!.insert(randomBigInt());
ASPTree!.insert(randomBigInt());
// Withdraw any amount
let withdrawal = {
value: parseEther("2.33"),
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
// Generate state tree merkle proof
let stateProof = stateTree.generateProof(4);
// Generate ASP tree merkle proof for another label. Generating a proof for a label that is not in the tree will fail
let ASPProof = ASPTree.generateProof(1);
// Fail when trying to spend the deposit with a label that is not included in the ASP tree
// TODO: add assertion on error message. Currently fails on line 82 => ASP inclusion check
await circuit.expectFail({
withdrawnValue: parseEther("1.77"),
stateRoot: stateProof.root,
stateTreeDepth: stateTree.depth,
ASPRoot: ASPProof.root,
ASPTreeDepth: ASPTree.depth,
context: randomBigInt(),
label: LABEL,
existingValue: parseEther("5"),
existingNullifier: deposit.nullifier,
existingSecret: deposit.secret,
newNullifier: withdrawal.nullifier,
newSecret: withdrawal.secret,
stateSiblings: padSiblings(stateProof.siblings, maxTreeDepth),
stateIndex: stateProof.index,
ASPSiblings: padSiblings(ASPProof.siblings, maxTreeDepth),
ASPIndex: ASPProof.index,
});
});
it("Withdrawal should fail if label is removed from the ASP tree", async () => {
// Insert commitment in state
stateTree!.insert(randomBigInt());
stateTree!.insert(randomBigInt());
stateTree!.insert(randomBigInt());
stateTree!.insert(depositHash);
// Insert label in ASP to mark as validated
ASPTree!.insert(randomBigInt());
ASPTree!.insert(randomBigInt());
ASPTree!.insert(randomBigInt());
ASPTree!.insert(LABEL);
// Withdraw a partial amount
let firstWithdrawal: Commitment = {
value: parseEther("4"),
label: LABEL,
nullifier: randomBigInt(),
secret: randomBigInt(),
};
// Hash the first withdrawal
const [firstWithdrawalHash, ,] = hashCommitment(firstWithdrawal);
// Generate merkle proofs
let stateProof = stateTree.generateProof(3);
let ASPProof = ASPTree.generateProof(3);
// Partially spend the deposit commitment
await circuit.expectPass(
{
withdrawnValue: parseEther("1"),
stateRoot: stateProof.root,
stateTreeDepth: stateTree.depth,
ASPRoot: ASPProof.root,
ASPTreeDepth: ASPTree.depth,
context: randomBigInt(),
label: LABEL,
existingValue: parseEther("5"),
existingNullifier: deposit.nullifier,
existingSecret: deposit.secret,
newNullifier: firstWithdrawal.nullifier,
newSecret: firstWithdrawal.secret,
stateSiblings: padSiblings(stateProof.siblings, maxTreeDepth),
stateIndex: stateProof.index,
ASPSiblings: padSiblings(ASPProof.siblings, maxTreeDepth),
ASPIndex: ASPProof.index,
},
{
newCommitmentHash: firstWithdrawalHash,
existingNullifierHash: depositNullifierHash,
}
);
// Insert new commitment in tree
stateTree!.insert(firstWithdrawalHash);
// Remove label from ASP tree
ASPTree!.update(3, REMOVED_LEAF);
// Generate new state root. ASP proof should remain the same
stateProof = stateTree.generateProof(4);
// Withdraw the full amount of the existing commitment
let secondWithdrawal: Commitment = {
value: parseEther("3"), // empty value after full withdrawal
label: LABEL,
nullifier: randomBigInt(), // new nullifier
secret: randomBigInt(), // new secret
};
// Fail when trying to spend the commitment with a removed label
// TODO: add assertion on error message. Currently fails on line 82 => ASP inclusion check
await circuit.expectFail({
withdrawnValue: parseEther("1"),
stateRoot: stateProof.root,
stateTreeDepth: stateTree.depth,
ASPRoot: ASPTree.root,
ASPTreeDepth: ASPTree.depth,
context: randomBigInt(),
label: LABEL,
existingValue: parseEther("4"),
existingNullifier: firstWithdrawal.nullifier,
existingSecret: firstWithdrawal.secret,
newNullifier: secondWithdrawal.nullifier,
newSecret: secondWithdrawal.secret,
stateSiblings: padSiblings(stateProof.siblings, maxTreeDepth),
stateIndex: stateProof.index,
ASPSiblings: padSiblings(ASPProof.siblings, maxTreeDepth),
ASPIndex: ASPProof.index,
});
});
});

View File

@@ -1,12 +1,8 @@
{
"name": "solidity-foundry-boilerplate",
"name": "@privacy-pool-core/contracts",
"version": "1.0.0",
"description": "Production ready Solidity boilerplate with Foundry",
"homepage": "https://github.com/defi-wonderland/solidity-foundry-boilerplate#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/defi-wonderland/solidity-foundry-boilerplate.git"
},
"description": "Solidity smart contracts for the Privacy Pool protocol",
"repository": "https://github.com/defi-wonderland/privacy-pool-core",
"license": "MIT",
"author": "Wonderland",
"scripts": {

324
yarn.lock
View File

@@ -2,6 +2,16 @@
# yarn lockfile v1
"@adraffy/ens-normalize@1.10.1":
version "1.10.1"
resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069"
integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==
"@adraffy/ens-normalize@^1.10.1":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33"
integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==
"@babel/code-frame@^7.0.0":
version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
@@ -226,6 +236,13 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@noble/curves@1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35"
integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==
dependencies:
"@noble/hashes" "1.3.2"
"@noble/curves@1.4.2", "@noble/curves@~1.4.0":
version "1.4.2"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9"
@@ -233,11 +250,33 @@
dependencies:
"@noble/hashes" "1.4.0"
"@noble/curves@1.7.0", "@noble/curves@^1.4.0", "@noble/curves@^1.6.0", "@noble/curves@~1.7.0":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.7.0.tgz#0512360622439256df892f21d25b388f52505e45"
integrity sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==
dependencies:
"@noble/hashes" "1.6.0"
"@noble/hashes@1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39"
integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==
"@noble/hashes@1.4.0", "@noble/hashes@~1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426"
integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==
"@noble/hashes@1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.0.tgz#d4bfb516ad6e7b5111c216a5cc7075f4cf19e6c5"
integrity sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==
"@noble/hashes@1.6.1", "@noble/hashes@^1.4.0", "@noble/hashes@^1.5.0", "@noble/hashes@~1.6.0":
version "1.6.1"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.1.tgz#df6e5943edcea504bac61395926d6fd67869a0d5"
integrity sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -269,6 +308,11 @@
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1"
integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==
"@scure/base@~1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.1.tgz#dd0b2a533063ca612c17aa9ad26424a2ff5aa865"
integrity sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==
"@scure/bip32@1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.4.0.tgz#4e1f1e196abedcef395b33b9674a042524e20d67"
@@ -278,6 +322,15 @@
"@noble/hashes" "~1.4.0"
"@scure/base" "~1.1.6"
"@scure/bip32@1.6.0", "@scure/bip32@^1.5.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.6.0.tgz#6dbc6b4af7c9101b351f41231a879d8da47e0891"
integrity sha512-82q1QfklrUUdXJzjuRU7iG7D7XiFx5PHYVS0+oeNKhyDLT7WPqs6pBcM2W5ZdwOwKCwoE1Vy1se+DHjcXwCYnA==
dependencies:
"@noble/curves" "~1.7.0"
"@noble/hashes" "~1.6.0"
"@scure/base" "~1.2.1"
"@scure/bip39@1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.3.0.tgz#0f258c16823ddd00739461ac31398b4e7d6a18c3"
@@ -286,6 +339,14 @@
"@noble/hashes" "~1.4.0"
"@scure/base" "~1.1.6"
"@scure/bip39@1.5.0", "@scure/bip39@^1.4.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.5.0.tgz#c8f9533dbd787641b047984356531d84485f19be"
integrity sha512-Dop+ASYhnrwm9+HA/HwXg7j2ZqM6yk2fyLWb5znexjctFY3+E+eU8cIWI0Pql0Qx4hPZCijlGq4OL71g+Uz30A==
dependencies:
"@noble/hashes" "~1.6.0"
"@scure/base" "~1.2.1"
"@solidity-parser/parser@^0.16.0":
version "0.16.2"
resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.2.tgz#42cb1e3d88b3e8029b0c9befff00b634cd92d2fa"
@@ -332,6 +393,13 @@
dependencies:
undici-types "~6.20.0"
"@types/node@22.7.5":
version "22.7.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b"
integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==
dependencies:
undici-types "~6.19.2"
"@types/node@^20.3.0":
version "20.17.11"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.11.tgz#2c05215fc37316b1596df7fbdba52151eaf83c50"
@@ -339,6 +407,31 @@
dependencies:
undici-types "~6.19.2"
"@zk-kit/baby-jubjub@1.0.3", "@zk-kit/baby-jubjub@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@zk-kit/baby-jubjub/-/baby-jubjub-1.0.3.tgz#8d2eccd20d729f1dbd39203dbff9a245a61dea76"
integrity sha512-Wl+QfV6XGOMk1yU2JTqHXeKWfJVXp83is0+dtqfj9wx4wsAPpb+qzYvwAxW5PBx5/Nu71Bh7jp/5vM+6QgHSwA==
dependencies:
"@zk-kit/utils" "1.2.1"
"@zk-kit/circuits@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@zk-kit/circuits/-/circuits-0.4.0.tgz#17a8333e8afe5a4e79260600a2dcefb2bc751a8f"
integrity sha512-Di7mokhwBS3qxVeCfHxGeNIpDg1kTnr1JXmsWiQMZLkRTn3Hugh6Tl07J394rWD0pIWRwPQsinaMVL2sB4F8yQ==
dependencies:
circomlib "^2.0.5"
"@zk-kit/eddsa-poseidon@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@zk-kit/eddsa-poseidon/-/eddsa-poseidon-1.1.0.tgz#08ef95ccbb2fbb5260617b6b5120dfb25a25229b"
integrity sha512-Djc+zOZjd73FpLLf32/XeVZi8GX4ShPQJGS4Iig1QMAR/2CggEi++6Jrkr9N2FM3M4MRCH1qxz2u22DjOLtASg==
dependencies:
"@zk-kit/baby-jubjub" "1.0.3"
"@zk-kit/utils" "1.2.1"
blakejs "^1.2.1"
buffer "6.0.3"
poseidon-lite "0.3.0"
"@zk-kit/lean-imt.sol@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@zk-kit/lean-imt.sol/-/lean-imt.sol-2.0.0.tgz#4b0aee47854b5844455f9361396062139416e12b"
@@ -346,6 +439,28 @@
dependencies:
poseidon-solidity "0.0.5"
"@zk-kit/lean-imt@^2.2.2":
version "2.2.2"
resolved "https://registry.yarnpkg.com/@zk-kit/lean-imt/-/lean-imt-2.2.2.tgz#79c8bd70fc0d444638328cb4781479b14c69a9dd"
integrity sha512-rscIPEgBBcu9vP/DJ3J+3187G/ObKETl343G5enPawNT81oeQSdHx3e2ZapTC+GfrZ/AS2AHHUOpRS1FfdSwjg==
dependencies:
"@zk-kit/utils" "1.2.1"
"@zk-kit/poseidon-cipher@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@zk-kit/poseidon-cipher/-/poseidon-cipher-0.3.2.tgz#a2afcc1e4fcfa9db3b245d584183cd7fb7fcd4ab"
integrity sha512-Ezz1e0mj/GRDlHdU5m0uhj5iHY72zWJU0BP8DsCCvPubU7LPI2tVaPpxrAjT4JQqatbVRQHLIhixW7F7BPzaFg==
dependencies:
"@zk-kit/baby-jubjub" "1.0.3"
"@zk-kit/utils" "1.2.1"
"@zk-kit/utils@1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@zk-kit/utils/-/utils-1.2.1.tgz#6cb38120535c73ab68cd0f09684882af148f256d"
integrity sha512-H2nTsyWdicVOyvqC5AjgU7tsTgmR6PDrruFJNmlmdhKp7RxEia/E1B1swMZjaasYa2QMp4Zc6oB7cWchty7B2Q==
dependencies:
buffer "^6.0.3"
JSONStream@^1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
@@ -359,6 +474,16 @@ abitype@0.7.1:
resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.7.1.tgz#16db20abe67de80f6183cf75f3de1ff86453b745"
integrity sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ==
abitype@1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.7.tgz#876a0005d211e1c9132825d45bcee7b46416b284"
integrity sha512-ZfYYSktDQUwc2eduYu8C4wOs+RDPmnRYMh7zNfzeMtGGgb0U+6tLGjixUic6mXf5xKKCcgT5Qp6cv39tOARVFw==
abitype@^1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.8.tgz#3554f28b2e9d6e9f35eb59878193eabd1b9f46ba"
integrity sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==
acorn-walk@^8.1.1:
version "8.3.4"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7"
@@ -371,6 +496,11 @@ acorn@^8.11.0, acorn@^8.4.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0"
integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
aes-js@4.0.0-beta.5:
version "4.0.0-beta.5"
resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873"
integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==
ajv@^6.12.6:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@@ -463,6 +593,11 @@ assertion-error@^1.1.0:
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
assertion-error@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7"
integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==
ast-parents@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/ast-parents/-/ast-parents-0.0.1.tgz#508fd0f05d0c48775d9eccda2e174423261e8dd3"
@@ -509,6 +644,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
bfj@^7.0.2:
version "7.1.0"
resolved "https://registry.yarnpkg.com/bfj/-/bfj-7.1.0.tgz#c5177d522103f9040e1b12980fe8c38cf41d3f8b"
@@ -533,6 +673,11 @@ blake2b-wasm@^2.4.0:
b4a "^1.0.1"
nanoassert "^2.0.0"
blakejs@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814"
integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==
bluebird@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
@@ -565,6 +710,14 @@ browser-stdout@^1.3.1:
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
buffer@6.0.3, buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840"
@@ -601,7 +754,7 @@ camelcase@^6.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
chai@^4.3.6, chai@^4.3.7:
chai@^4.3.6:
version "4.5.0"
resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8"
integrity sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==
@@ -614,6 +767,17 @@ chai@^4.3.6, chai@^4.3.7:
pathval "^1.1.1"
type-detect "^4.1.0"
chai@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.2.tgz#3afbc340b994ae3610ca519a6c70ace77ad4378d"
integrity sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==
dependencies:
assertion-error "^2.0.1"
check-error "^2.1.1"
deep-eql "^5.0.1"
loupe "^3.1.0"
pathval "^2.0.0"
chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
@@ -634,6 +798,11 @@ check-error@^1.0.3:
dependencies:
get-func-name "^2.0.2"
check-error@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc"
integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==
check-types@^11.2.3:
version "11.2.3"
resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.2.3.tgz#1ffdf68faae4e941fce252840b1787b8edc93b71"
@@ -687,15 +856,14 @@ circom_tester@^0.0.19:
tmp-promise "^3.0.3"
util "^0.12.4"
circomkit@^0.0.22:
version "0.0.22"
resolved "https://registry.yarnpkg.com/circomkit/-/circomkit-0.0.22.tgz#0d1425fdece61972b002fd69a5b636515289c65a"
integrity sha512-rfNiDCBrg/c9JI4nbvpKa123eFyrBFHBHUUeJFjHpzuG7Zw3KMbIUs4dw9xoIeqtzLYL4Hs4jXTMKluStEMxKA==
circomkit@^0.3.1, circomkit@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/circomkit/-/circomkit-0.3.2.tgz#9a0630cfc04595c494ffcfaf2be999fa77c85e3c"
integrity sha512-/ek/xtVNaBFlp2g12TPAfn9kSFUnZuKOiCvZFFnPn3JtZwi40r4eTDozPaaJKH4ZZx3alEgCr9MhOP24+o4CvA==
dependencies:
chai "^4.3.7"
circom_tester "^0.0.19"
loglevel "^1.8.1"
snarkjs "^0.7.0"
commander "^12.1.0"
loglevel "^1.9.2"
circomlib@^2.0.5:
version "2.0.5"
@@ -769,7 +937,7 @@ commander@^11.1.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906"
integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==
commander@^12.0.0, commander@~12.1.0:
commander@^12.0.0, commander@^12.1.0, commander@~12.1.0:
version "12.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==
@@ -886,6 +1054,11 @@ deep-eql@^4.1.3:
dependencies:
type-detect "^4.0.0"
deep-eql@^5.0.1:
version "5.0.2"
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341"
integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==
deep-is@~0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
@@ -1056,7 +1229,20 @@ ethereum-cryptography@^2.0.0:
"@scure/bip32" "1.4.0"
"@scure/bip39" "1.3.0"
eventemitter3@^5.0.1:
ethers@^6.13.4:
version "6.13.4"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.13.4.tgz#bd3e1c3dc1e7dc8ce10f9ffb4ee40967a651b53c"
integrity sha512-21YtnZVg4/zKkCQPjrDj38B1r4nQvTZLopUGMLQ1ePU2zV/joCfDC3t3iKQjWRzjjjbzR+mdAIoikeBRNkdllA==
dependencies:
"@adraffy/ens-normalize" "1.10.1"
"@noble/curves" "1.2.0"
"@noble/hashes" "1.3.2"
"@types/node" "22.7.5"
aes-js "4.0.0-beta.5"
tslib "2.7.0"
ws "8.17.1"
eventemitter3@5.0.1, eventemitter3@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
@@ -1450,6 +1636,11 @@ husky@>=9:
resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.7.tgz#d46a38035d101b46a70456a850ff4201344c0b2d"
integrity sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==
ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ignore@^5.2.4:
version "5.3.2"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
@@ -1606,6 +1797,11 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
isows@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7"
integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==
jake@^10.8.5:
version "10.9.2"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f"
@@ -1815,7 +2011,7 @@ log-update@^6.1.0:
strip-ansi "^7.1.0"
wrap-ansi "^9.0.0"
loglevel@^1.8.1:
loglevel@^1.9.2:
version "1.9.2"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.2.tgz#c2e028d6c757720107df4e64508530db6621ba08"
integrity sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==
@@ -1832,6 +2028,49 @@ loupe@^2.3.6:
dependencies:
get-func-name "^2.0.1"
loupe@^3.1.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.2.tgz#c86e0696804a02218f2206124c45d8b15291a240"
integrity sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==
maci-circuits@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/maci-circuits/-/maci-circuits-2.5.0.tgz#b8684917b7acc449fc5641ace80ff8218870d712"
integrity sha512-Jw7IupZ2IxMawgJeE9U4wqCalCNfKe6gcJIMTzAC2DzoDWI7Gy1Wr1VsbnsNcnBj0O9IwKnqU/y91z4W3ylfZg==
dependencies:
"@zk-kit/circuits" "^0.4.0"
circomkit "^0.3.1"
circomlib "^2.0.5"
maci-core "^2.5.0"
maci-crypto "^2.5.0"
maci-domainobjs "^2.5.0"
snarkjs "^0.7.5"
maci-core@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/maci-core/-/maci-core-2.5.0.tgz#18121bd7e66beb8e02203dcc2608f855788cbf50"
integrity sha512-Ko+cd7pQUR/mx8UTLcTi6zTkRXwDcX+1CWu6i9i7xYPaSPTbRkC9z8ggtJNYEKdVdTG6nJbhTV2TfCa5YV17qg==
dependencies:
maci-crypto "^2.5.0"
maci-domainobjs "^2.5.0"
maci-crypto@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/maci-crypto/-/maci-crypto-2.5.0.tgz#89a99921517b79564af8a356863c446b49e4cb0f"
integrity sha512-ozrLDH6kaK62TomNr5tnVgrUs6szXHCwcRyPzsQy07Wg2ZX61nyY0EFgWKAuU8kXqvYRdTtQdgflw6qpVz/4+w==
dependencies:
"@zk-kit/baby-jubjub" "^1.0.3"
"@zk-kit/eddsa-poseidon" "^1.1.0"
"@zk-kit/poseidon-cipher" "^0.3.2"
ethers "^6.13.4"
maci-domainobjs@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/maci-domainobjs/-/maci-domainobjs-2.5.0.tgz#f4710a1bdf4ffd613aaab109eae20b4db9da484e"
integrity sha512-MKyksmw+ndPG5HS80Ka8L7dEFDCuEis8HplYb3S+GqPDFwQ+Cl4vpMxF1Mr0d1SWAM4RfD4FQgAFaum4FWr1EA==
dependencies:
maci-crypto "^2.5.0"
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
@@ -1997,6 +2236,19 @@ os-tmpdir@~1.0.2:
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
ox@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ox/-/ox-0.1.2.tgz#0f791be2ccabeaf4928e6d423498fe1c8094e560"
integrity sha512-ak/8K0Rtphg9vnRJlbOdaX9R7cmxD2MiSthjWGaQdMk3D7hrAlDoM+6Lxn7hN52Za3vrXfZ7enfke/5WjolDww==
dependencies:
"@adraffy/ens-normalize" "^1.10.1"
"@noble/curves" "^1.6.0"
"@noble/hashes" "^1.5.0"
"@scure/bip32" "^1.5.0"
"@scure/bip39" "^1.4.0"
abitype "^1.0.6"
eventemitter3 "5.0.1"
p-limit@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
@@ -2077,6 +2329,11 @@ pathval@^1.1.1:
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==
pathval@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25"
integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==
picocolors@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
@@ -2097,6 +2354,11 @@ pluralize@^8.0.0:
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
poseidon-lite@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/poseidon-lite/-/poseidon-lite-0.3.0.tgz#93c42f6f9b870f154f2722dfd686b909c4285765"
integrity sha512-ilJj4MIve4uBEG7SrtPqUUNkvpJ/pLVbndxa0WvebcQqeIhe+h72JR4g0EvwchUzm9sOQDlOjiDNmRAgxNZl4A==
poseidon-solidity@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/poseidon-solidity/-/poseidon-solidity-0.0.5.tgz#3f93e01cfe25f6d2f2fac49734fbb00961b84655"
@@ -2321,7 +2583,7 @@ snarkjs@0.5.0:
logplease "^1.2.15"
r1csfile "0.0.41"
snarkjs@^0.7.0:
snarkjs@^0.7.5:
version "0.7.5"
resolved "https://registry.yarnpkg.com/snarkjs/-/snarkjs-0.7.5.tgz#334d83b61468bdffbbf922b20734ca47be50b8ab"
integrity sha512-h+3c4rXZKLhLuHk4LHydZCk/h5GcNvk5GjVKRRkHmfb6Ntf8gHOA9zea3g656iclRuhqQ3iKDWFgiD9ypLrKiA==
@@ -2587,6 +2849,11 @@ ts-node@^10.9.1:
v8-compile-cache-lib "^3.0.1"
yn "3.1.1"
tslib@2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
type-check@~0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
@@ -2652,6 +2919,21 @@ v8-compile-cache-lib@^3.0.1:
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
viem@^2.21.57:
version "2.21.57"
resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.57.tgz#bedbb444bb42e07ccc2264a9a0441903a113aab8"
integrity sha512-Mw4f4Dw0+Y/wSHdynVmP4uh+Cw15HEoj8BOKvKH5nGA6oFZYRxSy9Ruu7ZG8jexeAVCZ57aIuXb0gNg6Vb1x0g==
dependencies:
"@noble/curves" "1.7.0"
"@noble/hashes" "1.6.1"
"@scure/bip32" "1.6.0"
"@scure/bip39" "1.5.0"
abitype "1.0.7"
isows "1.0.6"
ox "0.1.2"
webauthn-p256 "0.0.10"
ws "8.18.0"
wasmbuilder@0.0.16:
version "0.0.16"
resolved "https://registry.yarnpkg.com/wasmbuilder/-/wasmbuilder-0.0.16.tgz#f34c1f2c047d2f6e1065cbfec5603988f16d8549"
@@ -2726,6 +3008,14 @@ web3-validator@^2.0.6:
web3-types "^1.6.0"
zod "^3.21.4"
webauthn-p256@0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.10.tgz#877e75abe8348d3e14485932968edf3325fd2fdd"
integrity sha512-EeYD+gmIT80YkSIDb2iWq0lq2zbHo1CxHlQTeJ+KkCILWpVy3zASH3ByD4bopzfk0uCwXxLqKGLqp2W4O28VFA==
dependencies:
"@noble/curves" "^1.4.0"
"@noble/hashes" "^1.4.0"
which-typed-array@^1.1.14, which-typed-array@^1.1.2:
version "1.1.16"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.16.tgz#db4db429c4706feca2f01677a144278e4a8c216b"
@@ -2784,6 +3074,16 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
ws@8.17.1:
version "8.17.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"
integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==
ws@8.18.0:
version "8.18.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"