diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml
index 9e7f080..2b03192 100644
--- a/.github/actions/setup/action.yml
+++ b/.github/actions/setup/action.yml
@@ -8,8 +8,11 @@ runs:
- name: Setup node
uses: actions/setup-node@v3
with:
- node-version: "16.18.x"
+ node-version: "20.x"
cache: npm
- name: Install packages
run: npm install
shell: bash
+ - name: Generate verifiers
+ run: npx hardhat zkit verifiers
+ shell: bash
diff --git a/.gitignore b/.gitignore
index fb84cd3..2b14c92 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,5 @@ generated-types
# Hardhat migrate
.storage.json
+
+contracts/verifiers
diff --git a/README.md b/README.md
index 7281f47..87337b6 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,36 @@
-# Basic Implementation of ZK Multisig Smart Contracts
+# Private ZK Multisig Smart Contracts
-This project consists of a basic implementation of ZK (Zero-Knowledge) multisig smart contracts.
+This project consists of a basic implementation of Private ZK (Zero-Knowledge) multisig smart contracts.
+
+It allows multisig participants to approve or reject proposals
+without revealing who voted and how until everyone has voted.
The contracts are divided into two parts:
- **ZK Multisig Factory** - Manages and deploys multisig contracts.
- **ZK Multisig** - The implementation of the multisig contract itself.
-For more details, refer to the documentation: [Link to Documentation]
+## Key Features
+- Anonymous membership via Cartesian Merkle proofs.
+- ECC ElGamal encrypted votes with ciphertext aggregation.
+- Non-interactive DKG-based keys.
+- On-chain ZK verification of core operations.
+
+For more details, refer to the [original paper](https://ethresear.ch/t/private-multisig-v0-1/23244).
+
+## Limitations
+- All participants are required to vote.
+- Only one proposal can be in the voting state at a time.
+- Votes revelation and results computation scale linearly with the number of participants.
## Steps to Build the Project
-1. Compile the contracts:
- ```bash
- npm run compile
- npm run test
- ```
+1. Generate circuit verifiers
+ ```bash
+ npx hardhat zkit verifiers
+ ```
+2. Compile the contracts and run tests:
+ ```bash
+ npm run compile
+ npm run test
+ ```
diff --git a/circuits/ElGamal.circom b/circuits/ElGamal.circom
new file mode 100644
index 0000000..808c534
--- /dev/null
+++ b/circuits/ElGamal.circom
@@ -0,0 +1,43 @@
+pragma circom 2.1.6;
+
+include "./babyjubjub/babyjub.circom";
+include "./babyjubjub/escalarmulany.circom";
+
+template ElGamal() {
+ signal input pk[2];
+ signal input M[2];
+ signal input nonce;
+
+ signal output C[2];
+ signal output D[2];
+
+ component pbk = BabyPbk();
+ pbk.in <== nonce;
+
+ C[0] <== pbk.Ax;
+ C[1] <== pbk.Ay;
+
+ signal rP[2];
+
+ component nonceBits = Num2Bits(253);
+ nonceBits.in <== nonce;
+
+ component mul = EscalarMulAny(253);
+ mul.p <== pk;
+
+ var i;
+ for (i = 0; i < 253; i++) {
+ mul.e[i] <== nonceBits.out[i];
+ }
+
+ rP <== mul.out;
+
+ component add = BabyAdd();
+ add.x1 <== M[0];
+ add.y1 <== M[1];
+ add.x2 <== rP[0];
+ add.y2 <== rP[1];
+
+ D[0] <== add.xout;
+ D[1] <== add.yout;
+}
diff --git a/circuits/IsGOrInf.circom b/circuits/IsGOrInf.circom
new file mode 100644
index 0000000..7901b82
--- /dev/null
+++ b/circuits/IsGOrInf.circom
@@ -0,0 +1,34 @@
+pragma circom 2.1.6;
+
+include "@solarity/circom-lib/bitify/comparators.circom";
+
+template IsGOrInf() {
+ var Gx = 5299619240641551281634865583518297030282874472190772894086521144482721001553;
+ var Gy = 16950150798460657717958625567821834550301663161624707787222815936182638968203;
+
+ signal input x;
+ signal input y;
+
+ component diffGx = IsZero();
+ diffGx.in <== x - Gx;
+
+ component diffGy = IsZero();
+ diffGy.in <== y - Gy;
+
+ signal isG;
+ isG <== diffGx.out * diffGy.out;
+
+ component diffInfX = IsZero();
+ diffInfX.in <== x - 0;
+
+ component diffInfY = IsZero();
+ diffInfY.in <== y - 1;
+
+ signal isInf;
+ isInf <== diffInfX.out * diffInfY.out;
+
+ signal isValid;
+ isValid <== isG + isInf;
+
+ isValid === 1;
+}
diff --git a/circuits/ProposalCreation.circom b/circuits/ProposalCreation.circom
new file mode 100644
index 0000000..47553fe
--- /dev/null
+++ b/circuits/ProposalCreation.circom
@@ -0,0 +1,47 @@
+pragma circom 2.1.6;
+
+include "./babyjubjub/babyjub.circom";
+include "@solarity/circom-lib/hasher/poseidon/poseidon.circom";
+include "@solarity/circom-lib/data-structures/CartesianMerkleTree.circom";
+
+template ProposalCreation(proofSize) {
+ signal input cmtRoot;
+ signal input challenge;
+
+ signal input sk;
+
+ signal input siblings[proofSize];
+ signal input siblingsLength[proofSize/2];
+ signal input directionBits[proofSize/2];
+
+ component pbk = BabyPbk();
+ pbk.in <== sk;
+
+ signal publicKey[2];
+ publicKey[0] <== pbk.Ax;
+ publicKey[1] <== pbk.Ay;
+
+ component keyHash = Poseidon(3);
+ keyHash.in[0] <== publicKey[0];
+ keyHash.in[1] <== publicKey[1];
+ keyHash.in[2] <== 1;
+ keyHash.dummy <== 0;
+
+ signal key;
+ key <== keyHash.out;
+
+ component cmt = CartesianMerkleTree(proofSize);
+ cmt.root <== cmtRoot;
+ cmt.siblings <== siblings;
+ cmt.siblingsLength <== siblingsLength;
+ cmt.directionBits <== directionBits;
+ cmt.key <== key;
+ cmt.nonExistenceKey <== 0;
+ cmt.isExclusion <== 0;
+ cmt.dummy <== 0;
+
+ signal challengeConstraint;
+ challengeConstraint <== challenge * key;
+}
+
+component main {public [cmtRoot, challenge]} = ProposalCreation(40);
diff --git a/circuits/Voting.circom b/circuits/Voting.circom
new file mode 100644
index 0000000..631052f
--- /dev/null
+++ b/circuits/Voting.circom
@@ -0,0 +1,188 @@
+pragma circom 2.1.6;
+
+include "./IsGOrInf.circom";
+include "./ElGamal.circom";
+include "./babyjubjub/babyjub.circom";
+include "./babyjubjub/escalarmulany.circom";
+include "@solarity/circom-lib/hasher/poseidon/poseidon.circom";
+include "@solarity/circom-lib/data-structures/CartesianMerkleTree.circom";
+
+template Voting(proofSize) {
+ signal input decryptionKeyShare;
+ signal input encryptionKey[2];
+ signal input challenge;
+ signal input proposalId;
+ signal input cmtRoot;
+
+ signal input sk1;
+ signal input sk2;
+ signal input newSk2;
+
+ signal input vote[2];
+ signal input k;
+
+ // CMT inclusion proof
+ signal input siblings[2][proofSize];
+ signal input siblingsLength[2][proofSize/2];
+ signal input directionBits[2][proofSize/2];
+
+ signal output blinder;
+ signal output nullifier;
+ signal output C1[2];
+ signal output C2[2];
+
+ signal output rotationKey[2];
+
+ // pk = skG
+ component pbk1 = BabyPbk();
+ pbk1.in <== sk1;
+
+ signal publicKey1[2];
+ publicKey1[0] <== pbk1.Ax;
+ publicKey1[1] <== pbk1.Ay;
+
+ component pbk2 = BabyPbk();
+ pbk2.in <== sk2;
+
+ signal publicKey2[2];
+ publicKey2[0] <== pbk2.Ax;
+ publicKey2[1] <== pbk2.Ay;
+
+ component rotationPbk = BabyPbk();
+ rotationPbk.in <== newSk2;
+
+ rotationKey[0] <== rotationPbk.Ax;
+ rotationKey[1] <== rotationPbk.Ay;
+
+ component keyHash1 = Poseidon(3);
+ keyHash1.in[0] <== publicKey1[0];
+ keyHash1.in[1] <== publicKey1[1];
+ keyHash1.in[2] <== 1;
+ keyHash1.dummy <== 0;
+
+ signal key1;
+ key1 <== keyHash1.out;
+
+ component cmt1 = CartesianMerkleTree(proofSize);
+ cmt1.root <== cmtRoot;
+ cmt1.siblings <== siblings[0];
+ cmt1.siblingsLength <== siblingsLength[0];
+ cmt1.directionBits <== directionBits[0];
+ cmt1.key <== key1;
+ cmt1.nonExistenceKey <== 0;
+ cmt1.isExclusion <== 0;
+ cmt1.dummy <== 0;
+
+ component keyHash2 = Poseidon(3);
+ keyHash2.in[0] <== publicKey2[0];
+ keyHash2.in[1] <== publicKey2[1];
+ keyHash2.in[2] <== 2;
+ keyHash2.dummy <== 0;
+
+ signal key2;
+ key2 <== keyHash2.out;
+
+ component cmt2 = CartesianMerkleTree(proofSize);
+ cmt2.root <== cmtRoot;
+ cmt2.siblings <== siblings[1];
+ cmt2.siblingsLength <== siblingsLength[1];
+ cmt2.directionBits <== directionBits[1];
+ cmt2.key <== key2;
+ cmt2.nonExistenceKey <== 0;
+ cmt2.isExclusion <== 0;
+ cmt2.dummy <== 0;
+
+ // blinder check
+ component blinderHash = Poseidon(2);
+ blinderHash.in[0] <== sk1;
+ blinderHash.in[1] <== proposalId;
+ blinderHash.dummy <== 0;
+
+ blinder <== blinderHash.out;
+
+ // sk2 nullifier check
+ component nullifierHash = Poseidon(1);
+ nullifierHash.in[0] <== sk2;
+ nullifierHash.dummy <== 0;
+
+ nullifier <== nullifierHash.out;
+
+ // decryption key share calculation
+ component h1Hash = Poseidon(1);
+ h1Hash.in[0] <== challenge;
+ h1Hash.dummy <== 0;
+
+ signal h1;
+ h1 <== h1Hash.out;
+
+ component h2Hash = Poseidon(1);
+ h2Hash.in[0] <== h1;
+ h2Hash.dummy <== 0;
+
+ signal h2;
+ h2 <== h2Hash.out;
+
+ signal hpk1[2];
+ signal hpk2[2];
+
+ component h1Bits = Num2Bits(254);
+ h1Bits.in <== h1;
+
+ component pk1Mul = EscalarMulAny(254);
+ pk1Mul.p <== publicKey1;
+
+ var i;
+ for (i = 0; i < 254; i++) {
+ pk1Mul.e[i] <== h1Bits.out[i];
+ }
+
+ hpk1 <== pk1Mul.out;
+
+ component h2Bits = Num2Bits(254);
+ h2Bits.in <== h2;
+
+ component pk2Mul = EscalarMulAny(254);
+ pk2Mul.p <== publicKey2;
+
+ for (i = 0; i < 254; i++) {
+ pk2Mul.e[i] <== h2Bits.out[i];
+ }
+
+ hpk2 <== pk2Mul.out;
+
+ signal expectedEncryptionKeyShare[2];
+ component add = BabyAdd();
+ add.x1 <== hpk1[0];
+ add.y1 <== hpk1[1];
+ add.x2 <== hpk2[0];
+ add.y2 <== hpk2[1];
+
+ expectedEncryptionKeyShare[0] <== add.xout;
+ expectedEncryptionKeyShare[1] <== add.yout;
+
+ signal encryptionKeyShare[2];
+ component encKey = BabyPbk();
+ encKey.in <== decryptionKeyShare;
+
+ encryptionKeyShare[0] <== encKey.Ax;
+ encryptionKeyShare[1] <== encKey.Ay;
+
+ encryptionKeyShare[0] === expectedEncryptionKeyShare[0];
+ encryptionKeyShare[1] === expectedEncryptionKeyShare[1];
+
+ // check for the vote to be either G or inf
+ component voteChecker = IsGOrInf();
+ voteChecker.x <== vote[0];
+ voteChecker.y <== vote[1];
+
+ // vote ElGamal encryption
+ component elgamal = ElGamal();
+ elgamal.pk <== encryptionKey;
+ elgamal.M <== vote;
+ elgamal.nonce <== k;
+
+ C1 <== elgamal.C;
+ C2 <== elgamal.D;
+}
+
+component main {public [decryptionKeyShare, encryptionKey, challenge, proposalId, cmtRoot, decryptionKeyShare]} = Voting(40);
diff --git a/circuits/babyjubjub/babyjub.circom b/circuits/babyjubjub/babyjub.circom
new file mode 100644
index 0000000..58165b7
--- /dev/null
+++ b/circuits/babyjubjub/babyjub.circom
@@ -0,0 +1,107 @@
+/*
+ Copyright 2018 0KIMS association.
+
+ This file is part of circom (Zero Knowledge Circuit Compiler).
+
+ circom is a free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ circom is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with circom. If not, see .
+*/
+pragma circom 2.1.6;
+
+include "./escalarmulfix.circom";
+include "@solarity/circom-lib/bitify/bitify.circom";
+
+template BabyAdd() {
+ signal input x1;
+ signal input y1;
+ signal input x2;
+ signal input y2;
+ signal output xout;
+ signal output yout;
+
+ signal beta;
+ signal gamma;
+ signal delta;
+ signal tau;
+
+ var a = 168700;
+ var d = 168696;
+
+ beta <== x1*y2;
+ gamma <== y1*x2;
+ delta <== (-a*x1+y1)*(x2 + y2);
+ tau <== beta * gamma;
+
+ xout <-- (beta + gamma) / (1+ d*tau);
+ (1+ d*tau) * xout === (beta + gamma);
+
+ yout <-- (delta + a*beta - gamma) / (1-d*tau);
+ (1-d*tau)*yout === (delta + a*beta - gamma);
+}
+
+template BabyDbl() {
+ signal input x;
+ signal input y;
+ signal output xout;
+ signal output yout;
+
+ component adder = BabyAdd();
+ adder.x1 <== x;
+ adder.y1 <== y;
+ adder.x2 <== x;
+ adder.y2 <== y;
+
+ adder.xout ==> xout;
+ adder.yout ==> yout;
+}
+
+
+template BabyCheck() {
+ signal input x;
+ signal input y;
+
+ signal x2;
+ signal y2;
+
+ var a = 168700;
+ var d = 168696;
+
+ x2 <== x*x;
+ y2 <== y*y;
+
+ a*x2 + y2 === 1 + d*x2*y2;
+}
+
+// Extracts the public key from private key
+template BabyPbk() {
+ signal input in;
+ signal output Ax;
+ signal output Ay;
+
+ var BASE8[2] = [
+ 5299619240641551281634865583518297030282874472190772894086521144482721001553,
+ 16950150798460657717958625567821834550301663161624707787222815936182638968203
+ ];
+
+ component pvkBits = Num2Bits(253);
+ pvkBits.in <== in;
+
+ component mulFix = EscalarMulFix(253, BASE8);
+
+ var i;
+ for (i=0; i<253; i++) {
+ mulFix.e[i] <== pvkBits.out[i];
+ }
+ Ax <== mulFix.out[0];
+ Ay <== mulFix.out[1];
+}
diff --git a/circuits/babyjubjub/escalarmulany.circom b/circuits/babyjubjub/escalarmulany.circom
new file mode 100644
index 0000000..85952eb
--- /dev/null
+++ b/circuits/babyjubjub/escalarmulany.circom
@@ -0,0 +1,197 @@
+/*
+ Copyright 2018 0KIMS association.
+
+ This file is part of circom (Zero Knowledge Circuit Compiler).
+
+ circom is a free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ circom is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with circom. If not, see .
+*/
+pragma circom 2.1.6;
+
+include "./montgomery.circom";
+include "./babyjub.circom";
+include "@solarity/circom-lib/bitify/comparators.circom";
+
+template Multiplexor2() {
+ signal input sel;
+ signal input in[2][2];
+ signal output out[2];
+
+ out[0] <== (in[1][0] - in[0][0])*sel + in[0][0];
+ out[1] <== (in[1][1] - in[0][1])*sel + in[0][1];
+}
+
+template BitElementMulAny() {
+ signal input sel;
+ signal input dblIn[2];
+ signal input addIn[2];
+ signal output dblOut[2];
+ signal output addOut[2];
+
+ component doubler = MontgomeryDouble();
+ component adder = MontgomeryAdd();
+ component selector = Multiplexor2();
+
+
+ sel ==> selector.sel;
+
+ dblIn[0] ==> doubler.in[0];
+ dblIn[1] ==> doubler.in[1];
+ doubler.out[0] ==> adder.in1[0];
+ doubler.out[1] ==> adder.in1[1];
+ addIn[0] ==> adder.in2[0];
+ addIn[1] ==> adder.in2[1];
+ addIn[0] ==> selector.in[0][0];
+ addIn[1] ==> selector.in[0][1];
+ adder.out[0] ==> selector.in[1][0];
+ adder.out[1] ==> selector.in[1][1];
+
+ doubler.out[0] ==> dblOut[0];
+ doubler.out[1] ==> dblOut[1];
+ selector.out[0] ==> addOut[0];
+ selector.out[1] ==> addOut[1];
+}
+
+// p is montgomery point
+// n must be <= 248
+// returns out in twisted edwards
+// Double is in montgomery to be linked;
+
+template SegmentMulAny(n) {
+ signal input e[n];
+ signal input p[2];
+ signal output out[2];
+ signal output dbl[2];
+
+ component bits[n-1];
+
+ component e2m = Edwards2Montgomery();
+
+ p[0] ==> e2m.in[0];
+ p[1] ==> e2m.in[1];
+
+ var i;
+
+ bits[0] = BitElementMulAny();
+ e2m.out[0] ==> bits[0].dblIn[0];
+ e2m.out[1] ==> bits[0].dblIn[1];
+ e2m.out[0] ==> bits[0].addIn[0];
+ e2m.out[1] ==> bits[0].addIn[1];
+ e[1] ==> bits[0].sel;
+
+ for (i=1; i bits[i].dblIn[0];
+ bits[i-1].dblOut[1] ==> bits[i].dblIn[1];
+ bits[i-1].addOut[0] ==> bits[i].addIn[0];
+ bits[i-1].addOut[1] ==> bits[i].addIn[1];
+ e[i+1] ==> bits[i].sel;
+ }
+
+ bits[n-2].dblOut[0] ==> dbl[0];
+ bits[n-2].dblOut[1] ==> dbl[1];
+
+ component m2e = Montgomery2Edwards();
+
+ bits[n-2].addOut[0] ==> m2e.in[0];
+ bits[n-2].addOut[1] ==> m2e.in[1];
+
+ component eadder = BabyAdd();
+
+ m2e.out[0] ==> eadder.x1;
+ m2e.out[1] ==> eadder.y1;
+ -p[0] ==> eadder.x2;
+ p[1] ==> eadder.y2;
+
+ component lastSel = Multiplexor2();
+
+ e[0] ==> lastSel.sel;
+ eadder.xout ==> lastSel.in[0][0];
+ eadder.yout ==> lastSel.in[0][1];
+ m2e.out[0] ==> lastSel.in[1][0];
+ m2e.out[1] ==> lastSel.in[1][1];
+
+ lastSel.out[0] ==> out[0];
+ lastSel.out[1] ==> out[1];
+}
+
+// This function assumes that p is in the subgroup and it is different to 0
+
+template EscalarMulAny(n) {
+ signal input e[n]; // Input in binary format
+ signal input p[2]; // Point (Twisted format)
+ signal output out[2]; // Point (Twisted format)
+
+ var nsegments = (n-1)\148 +1;
+ var nlastsegment = n - (nsegments-1)*148;
+
+ component segments[nsegments];
+ component doublers[nsegments-1];
+ component m2e[nsegments-1];
+ component adders[nsegments-1];
+ component zeropoint = IsZero();
+ zeropoint.in <== p[0];
+
+ var s;
+ var i;
+ var nseg;
+
+ for (s=0; s segments[s].e[i];
+ }
+
+ if (s==0) {
+ // force G8 point if input point is zero
+ segments[s].p[0] <== p[0] + (5299619240641551281634865583518297030282874472190772894086521144482721001553 - p[0])*zeropoint.out;
+ segments[s].p[1] <== p[1] + (16950150798460657717958625567821834550301663161624707787222815936182638968203 - p[1])*zeropoint.out;
+ } else {
+ doublers[s-1] = MontgomeryDouble();
+ m2e[s-1] = Montgomery2Edwards();
+ adders[s-1] = BabyAdd();
+
+ segments[s-1].dbl[0] ==> doublers[s-1].in[0];
+ segments[s-1].dbl[1] ==> doublers[s-1].in[1];
+
+ doublers[s-1].out[0] ==> m2e[s-1].in[0];
+ doublers[s-1].out[1] ==> m2e[s-1].in[1];
+
+ m2e[s-1].out[0] ==> segments[s].p[0];
+ m2e[s-1].out[1] ==> segments[s].p[1];
+
+ if (s==1) {
+ segments[s-1].out[0] ==> adders[s-1].x1;
+ segments[s-1].out[1] ==> adders[s-1].y1;
+ } else {
+ adders[s-2].xout ==> adders[s-1].x1;
+ adders[s-2].yout ==> adders[s-1].y1;
+ }
+ segments[s].out[0] ==> adders[s-1].x2;
+ segments[s].out[1] ==> adders[s-1].y2;
+ }
+ }
+
+ if (nsegments == 1) {
+ segments[0].out[0]*(1-zeropoint.out) ==> out[0];
+ segments[0].out[1]+(1-segments[0].out[1])*zeropoint.out ==> out[1];
+ } else {
+ adders[nsegments-2].xout*(1-zeropoint.out) ==> out[0];
+ adders[nsegments-2].yout+(1-adders[nsegments-2].yout)*zeropoint.out ==> out[1];
+ }
+}
diff --git a/circuits/babyjubjub/escalarmulfix.circom b/circuits/babyjubjub/escalarmulfix.circom
new file mode 100644
index 0000000..e93098a
--- /dev/null
+++ b/circuits/babyjubjub/escalarmulfix.circom
@@ -0,0 +1,299 @@
+/*
+ Copyright 2018 0KIMS association.
+
+ This file is part of circom (Zero Knowledge Circuit Compiler).
+
+ circom is a free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ circom is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with circom. If not, see .
+*/
+pragma circom 2.1.6;
+
+include "./mux3.circom";
+include "./montgomery.circom";
+include "./babyjub.circom";
+
+/*
+ Window of 3 elements, it calculates
+ out = base + base*in[0] + 2*base*in[1] + 4*base*in[2]
+ out4 = 4*base
+
+ The result should be compensated.
+ */
+
+/*
+
+ The scalar is s = a0 + a1*2^3 + a2*2^6 + ...... + a81*2^243
+ First We calculate Q = B + 2^3*B + 2^6*B + ......... + 2^246*B
+
+ Then we calculate S1 = 2*2^246*B + (1 + a0)*B + (2^3 + a1)*B + .....+ (2^243 + a81)*B
+
+ And Finaly we compute the result: RES = SQ - Q
+
+ As you can see the input of the adders cannot be equal nor zero, except for the last
+ substraction that it's done in montgomery.
+
+ A good way to see it is that the accumulator input of the adder >= 2^247*B and the other input
+ is the output of the windows that it's going to be <= 2^246*B
+ */
+template WindowMulFix() {
+ signal input in[3];
+ signal input base[2];
+ signal output out[2];
+ signal output out8[2]; // Returns 8*Base (To be linked)
+
+ component mux = MultiMux3(2);
+
+ mux.s[0] <== in[0];
+ mux.s[1] <== in[1];
+ mux.s[2] <== in[2];
+
+ component dbl2 = MontgomeryDouble();
+ component adr3 = MontgomeryAdd();
+ component adr4 = MontgomeryAdd();
+ component adr5 = MontgomeryAdd();
+ component adr6 = MontgomeryAdd();
+ component adr7 = MontgomeryAdd();
+ component adr8 = MontgomeryAdd();
+
+// in[0] -> 1*BASE
+
+ mux.c[0][0] <== base[0];
+ mux.c[1][0] <== base[1];
+
+// in[1] -> 2*BASE
+ dbl2.in[0] <== base[0];
+ dbl2.in[1] <== base[1];
+ mux.c[0][1] <== dbl2.out[0];
+ mux.c[1][1] <== dbl2.out[1];
+
+// in[2] -> 3*BASE
+ adr3.in1[0] <== base[0];
+ adr3.in1[1] <== base[1];
+ adr3.in2[0] <== dbl2.out[0];
+ adr3.in2[1] <== dbl2.out[1];
+ mux.c[0][2] <== adr3.out[0];
+ mux.c[1][2] <== adr3.out[1];
+
+// in[3] -> 4*BASE
+ adr4.in1[0] <== base[0];
+ adr4.in1[1] <== base[1];
+ adr4.in2[0] <== adr3.out[0];
+ adr4.in2[1] <== adr3.out[1];
+ mux.c[0][3] <== adr4.out[0];
+ mux.c[1][3] <== adr4.out[1];
+
+// in[4] -> 5*BASE
+ adr5.in1[0] <== base[0];
+ adr5.in1[1] <== base[1];
+ adr5.in2[0] <== adr4.out[0];
+ adr5.in2[1] <== adr4.out[1];
+ mux.c[0][4] <== adr5.out[0];
+ mux.c[1][4] <== adr5.out[1];
+
+// in[5] -> 6*BASE
+ adr6.in1[0] <== base[0];
+ adr6.in1[1] <== base[1];
+ adr6.in2[0] <== adr5.out[0];
+ adr6.in2[1] <== adr5.out[1];
+ mux.c[0][5] <== adr6.out[0];
+ mux.c[1][5] <== adr6.out[1];
+
+// in[6] -> 7*BASE
+ adr7.in1[0] <== base[0];
+ adr7.in1[1] <== base[1];
+ adr7.in2[0] <== adr6.out[0];
+ adr7.in2[1] <== adr6.out[1];
+ mux.c[0][6] <== adr7.out[0];
+ mux.c[1][6] <== adr7.out[1];
+
+// in[7] -> 8*BASE
+ adr8.in1[0] <== base[0];
+ adr8.in1[1] <== base[1];
+ adr8.in2[0] <== adr7.out[0];
+ adr8.in2[1] <== adr7.out[1];
+ mux.c[0][7] <== adr8.out[0];
+ mux.c[1][7] <== adr8.out[1];
+
+ out8[0] <== adr8.out[0];
+ out8[1] <== adr8.out[1];
+
+ out[0] <== mux.out[0];
+ out[1] <== mux.out[1];
+}
+
+
+/*
+ This component does a multiplication of a escalar times a fix base
+ Signals:
+ e: The scalar in bits
+ base: the base point in edwards format
+ out: The result
+ dbl: Point in Edwards to be linked to the next segment.
+ */
+
+template SegmentMulFix(nWindows) {
+ signal input e[nWindows*3];
+ signal input base[2];
+ signal output out[2];
+ signal output dbl[2];
+
+ var i;
+ var j;
+
+ // Convert the base to montgomery
+
+ component e2m = Edwards2Montgomery();
+ e2m.in[0] <== base[0];
+ e2m.in[1] <== base[1];
+
+ component windows[nWindows];
+ component adders[nWindows];
+ component cadders[nWindows];
+
+ // In the last step we add an extra doubler so that numbers do not match.
+ component dblLast = MontgomeryDouble();
+
+ for (i=0; i out[0];
+ cAdd.yout ==> out[1];
+
+ windows[nWindows-1].out8[0] ==> dbl[0];
+ windows[nWindows-1].out8[1] ==> dbl[1];
+}
+
+
+/*
+This component multiplies a escalar times a fixed point BASE (twisted edwards format)
+ Signals
+ e: The escalar in binary format
+ out: The output point in twisted edwards
+ */
+template EscalarMulFix(n, BASE) {
+ signal input e[n]; // Input in binary format
+ signal output out[2]; // Point (Twisted format)
+
+ var nsegments = (n-1)\246 +1; // 249 probably would work. But I'm not sure and for security I keep 246
+ var nlastsegment = n - (nsegments-1)*249;
+
+ component segments[nsegments];
+
+ component m2e[nsegments-1];
+ component adders[nsegments-1];
+
+ var s;
+ var i;
+ var nseg;
+ var nWindows;
+
+ for (s=0; s m2e[s-1].in[0];
+ segments[s-1].dbl[1] ==> m2e[s-1].in[1];
+
+ m2e[s-1].out[0] ==> segments[s].base[0];
+ m2e[s-1].out[1] ==> segments[s].base[1];
+
+ if (s==1) {
+ segments[s-1].out[0] ==> adders[s-1].x1;
+ segments[s-1].out[1] ==> adders[s-1].y1;
+ } else {
+ adders[s-2].xout ==> adders[s-1].x1;
+ adders[s-2].yout ==> adders[s-1].y1;
+ }
+ segments[s].out[0] ==> adders[s-1].x2;
+ segments[s].out[1] ==> adders[s-1].y2;
+ }
+ }
+
+ if (nsegments == 1) {
+ segments[0].out[0] ==> out[0];
+ segments[0].out[1] ==> out[1];
+ } else {
+ adders[nsegments-2].xout ==> out[0];
+ adders[nsegments-2].yout ==> out[1];
+ }
+}
diff --git a/circuits/babyjubjub/montgomery.circom b/circuits/babyjubjub/montgomery.circom
new file mode 100644
index 0000000..11f56fd
--- /dev/null
+++ b/circuits/babyjubjub/montgomery.circom
@@ -0,0 +1,142 @@
+/*
+ Copyright 2018 0KIMS association.
+
+ This file is part of circom (Zero Knowledge Circuit Compiler).
+
+ circom is a free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ circom is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with circom. If not, see .
+*/
+
+/*
+ Source: https://en.wikipedia.org/wiki/Montgomery_curve
+
+ 1 + y 1 + y
+ [u, v] = [ ------- , ---------- ]
+ 1 - y (1 - y)x
+
+ */
+pragma circom 2.1.6;
+
+template Edwards2Montgomery() {
+ signal input in[2];
+ signal output out[2];
+
+ out[0] <-- (1 + in[1]) / (1 - in[1]);
+ out[1] <-- out[0] / in[0];
+
+
+ out[0] * (1-in[1]) === (1 + in[1]);
+ out[1] * in[0] === out[0];
+}
+
+/*
+
+ u u - 1
+ [x, y] = [ ---, ------- ]
+ v u + 1
+
+ */
+template Montgomery2Edwards() {
+ signal input in[2];
+ signal output out[2];
+
+ out[0] <-- in[0] / in[1];
+ out[1] <-- (in[0] - 1) / (in[0] + 1);
+
+ out[0] * in[1] === in[0];
+ out[1] * (in[0] + 1) === in[0] - 1;
+}
+
+
+/*
+ x2 - x1
+ lamda = ---------
+ y2 - y1
+
+ x3 + A + x1 + x2
+ x3 = B * lamda^2 - A - x1 -x2 => lamda^2 = ------------------
+ B
+
+ y3 = (2*x1 + x2 + A)*lamda - B*lamda^3 - y1 =>
+
+
+ => y3 = lamda * ( 2*x1 + x2 + A - x3 - A - x1 - x2) - y1 =>
+
+ => y3 = lamda * ( x1 - x3 ) - y1
+
+----------
+
+ y2 - y1
+ lamda = ---------
+ x2 - x1
+
+ x3 = B * lamda^2 - A - x1 -x2
+
+ y3 = lamda * ( x1 - x3 ) - y1
+
+ */
+
+template MontgomeryAdd() {
+ signal input in1[2];
+ signal input in2[2];
+ signal output out[2];
+
+ var a = 168700;
+ var d = 168696;
+
+ var A = (2 * (a + d)) / (a - d);
+ var B = 4 / (a - d);
+
+ signal lamda;
+
+ lamda <-- (in2[1] - in1[1]) / (in2[0] - in1[0]);
+ lamda * (in2[0] - in1[0]) === (in2[1] - in1[1]);
+
+ out[0] <== B*lamda*lamda - A - in1[0] -in2[0];
+ out[1] <== lamda * (in1[0] - out[0]) - in1[1];
+}
+
+/*
+
+ x1_2 = x1*x1
+
+ 3*x1_2 + 2*A*x1 + 1
+ lamda = ---------------------
+ 2*B*y1
+
+ x3 = B * lamda^2 - A - x1 -x1
+
+ y3 = lamda * ( x1 - x3 ) - y1
+
+ */
+template MontgomeryDouble() {
+ signal input in[2];
+ signal output out[2];
+
+ var a = 168700;
+ var d = 168696;
+
+ var A = (2 * (a + d)) / (a - d);
+ var B = 4 / (a - d);
+
+ signal lamda;
+ signal x1_2;
+
+ x1_2 <== in[0] * in[0];
+
+ lamda <-- (3*x1_2 + 2*A*in[0] + 1 ) / (2*B*in[1]);
+ lamda * (2*B*in[1]) === (3*x1_2 + 2*A*in[0] + 1 );
+
+ out[0] <== B*lamda*lamda - A - 2*in[0];
+ out[1] <== lamda * (in[0] - out[0]) - in[1];
+}
diff --git a/circuits/babyjubjub/mux3.circom b/circuits/babyjubjub/mux3.circom
new file mode 100644
index 0000000..33d16eb
--- /dev/null
+++ b/circuits/babyjubjub/mux3.circom
@@ -0,0 +1,75 @@
+/*
+ Copyright 2018 0KIMS association.
+
+ This file is part of circom (Zero Knowledge Circuit Compiler).
+
+ circom is a free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ circom is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with circom. If not, see .
+*/
+pragma circom 2.1.6;
+
+template MultiMux3(n) {
+ signal input c[n][8]; // Constants
+ signal input s[3]; // Selector
+ signal output out[n];
+
+ signal a210[n];
+ signal a21[n];
+ signal a20[n];
+ signal a2[n];
+
+ signal a10[n];
+ signal a1[n];
+ signal a0[n];
+ signal a[n];
+
+ // 4 constrains for the intermediary variables
+ signal s10;
+ s10 <== s[1] * s[0];
+
+ for (var i=0; i mux.s[i];
+ }
+
+ mux.out[0] ==> out;
+}
diff --git a/contracts/ZKMultisig.sol b/contracts/ZKMultisig.sol
index e1cc0ab..d6aaef1 100644
--- a/contracts/ZKMultisig.sol
+++ b/contracts/ZKMultisig.sol
@@ -1,41 +1,57 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.4;
+pragma solidity ^0.8.21;
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
-import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
-import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
-import {SparseMerkleTree} from "@solarity/solidity-lib/libs/data-structures/SparseMerkleTree.sol";
+import {CartesianMerkleTree} from "@solarity/solidity-lib/libs/data-structures/CartesianMerkleTree.sol";
import {PRECISION, PERCENTAGE_100} from "@solarity/solidity-lib/utils/Globals.sol";
import {Paginator} from "@solarity/solidity-lib/libs/arrays/Paginator.sol";
-import {VerifierHelper} from "@solarity/solidity-lib/libs/zkp/snarkjs/VerifierHelper.sol";
+import {Groth16VerifierHelper} from "@solarity/solidity-lib/libs/zkp/Groth16VerifierHelper.sol";
+import {ED256} from "@solarity/solidity-lib/libs/crypto/ED256.sol";
import {IZKMultisig} from "./interfaces/IZKMultisig.sol";
-import {PoseidonUnit1L} from "./libs/Poseidon.sol";
+import {PoseidonUnit1L, PoseidonUnit3L} from "./libs/Poseidon.sol";
+import {BabyJubJub} from "./libs/BabyJubJub.sol";
-contract ZKMultisig is UUPSUpgradeable, IZKMultisig {
+contract ZKMultisig is UUPSUpgradeable, EIP712Upgradeable, IZKMultisig {
+ using BabyJubJub for *;
using EnumerableSet for *;
using Paginator for EnumerableSet.UintSet;
- using SparseMerkleTree for SparseMerkleTree.UintSMT;
- using VerifierHelper for address;
+ using CartesianMerkleTree for CartesianMerkleTree.UintCMT;
+ using Groth16VerifierHelper for address;
using Address for address;
using Math for uint256;
- uint256 public constant PARTICIPANTS_TREE_DEPTH = 20;
- uint256 public constant MIN_QUORUM_SIZE = 1;
-
- address public participantVerifier;
+ // bytes32(uint256(keccak256("private.multisig.contract.ZKMultisig")) - 1)
+ bytes32 private constant ZK_MULTISIG_STORAGE =
+ 0x498bc96b7d273653a6dbed08a392bbda0eadd6b7201a83a295108683ba304490;
- SparseMerkleTree.UintSMT internal _participantsSMT;
- EnumerableSet.UintSet internal _participants;
- EnumerableSet.UintSet internal _proposalIds;
+ bytes32 public constant KDF_ROTATION_MSG_TYPEHASH =
+ keccak256("KDF(address zkMultisigAddr,uint256 proposalId)");
- uint256 private _quorumPercentage;
+ uint256 public constant PARTICIPANTS_TREE_DEPTH = 20;
+ uint256 public constant MIN_QUORUM_SIZE = 1;
- mapping(uint256 => ProposalData) private _proposals;
+ struct ZKMultisigStorage {
+ address creationVerifier;
+ address votingVerifier;
+ uint256 quorumPercentage;
+ uint256 currentProposalId;
+ CartesianMerkleTree.UintCMT participantsCMT;
+ EnumerableSet.UintSet proposalIds;
+ EnumerableSet.UintSet participantsPermanent;
+ EnumerableSet.UintSet participantsRotation;
+ ED256.PPoint cumulativePermanentKey;
+ ED256.PPoint cumulativeRotationKey;
+ mapping(uint256 => ED256.APoint) participantPermanentKeys;
+ mapping(uint256 => ED256.APoint) participantRotationKeys;
+ mapping(uint256 => bool) rotationKeyNullifiers;
+ mapping(uint256 => ProposalData) proposals;
+ }
modifier onlyThis() {
_validateMsgSender();
@@ -47,133 +63,189 @@ contract ZKMultisig is UUPSUpgradeable, IZKMultisig {
}
function initialize(
- uint256[] memory participants_,
+ ED256.APoint[] calldata permanentKeys_,
+ ED256.APoint[] calldata rotationKeys_,
uint256 quorumPercentage_,
- address participantVerifier_
+ address creationVerifier_,
+ address votingVerifier_
) external initializer {
- _participantsSMT.initialize(uint32(PARTICIPANTS_TREE_DEPTH));
+ __EIP712_init("ZKMultisig", "1");
+
+ ZKMultisigStorage storage $ = _getZKMultisigStorage();
- _updateParticipantVerifier(participantVerifier_);
+ $.participantsCMT.initialize(uint32(PARTICIPANTS_TREE_DEPTH));
+ $.participantsCMT.setHasher(poseidon3);
+
+ _updateCreationVerifier(creationVerifier_);
+ _updateVotingVerifier(votingVerifier_);
_updateQuorumPercentage(quorumPercentage_);
- _addParticipants(participants_);
+
+ $.cumulativePermanentKey = ED256.pInfinity();
+ $.cumulativeRotationKey = ED256.pInfinity();
+
+ _addParticipants(permanentKeys_, rotationKeys_);
}
- function addParticipants(uint256[] calldata participantsToAdd_) external onlyThis {
- _addParticipants(participantsToAdd_);
+ function addParticipants(
+ ED256.APoint[] calldata permanentKeys_,
+ ED256.APoint[] calldata rotationKeys_
+ ) external onlyThis {
+ _addParticipants(permanentKeys_, rotationKeys_);
}
- function removeParticipants(uint256[] calldata participantsToRemove_) external onlyThis {
- _removeParticipants(participantsToRemove_);
+ function removeParticipants(ED256.APoint[] calldata permanentKeys_) external onlyThis {
+ _removeParticipants(permanentKeys_);
}
function updateQuorumPercentage(uint256 newQuorumPercentage_) external onlyThis {
_updateQuorumPercentage(newQuorumPercentage_);
}
- function updateParticipantVerifier(address participantVerifier_) external onlyThis {
- _updateParticipantVerifier(participantVerifier_);
+ function updateCreationVerifier(address creationVerifier_) external onlyThis {
+ _updateCreationVerifier(creationVerifier_);
+ }
+
+ function updateVotingVerifier(address votingVerifier_) external onlyThis {
+ _updateVotingVerifier(votingVerifier_);
}
function create(
ProposalContent calldata content_,
- uint256 duration_,
uint256 salt_,
ZKParams calldata proofData_
) external returns (uint256) {
// validate inputs
- require(duration_ > 0, "ZKMultisig: Invalid duration");
- require(content_.target != address(0), "ZKMultisig: Invalid target");
+ if (content_.target == address(0)) revert ZeroTarget();
+
+ ZKMultisigStorage storage $ = _getZKMultisigStorage();
+
+ if ($.proposals[$.currentProposalId].status == ProposalStatus.VOTING) {
+ revert ActiveProposal($.currentProposalId);
+ }
uint256 proposalId_ = computeProposalId(content_, salt_);
+ ProposalData storage proposal = $.proposals[proposalId_];
+
// validate proposal state
- require(
- getProposalStatus(proposalId_) == ProposalStatus.NONE,
- "ZKMultisig: Proposal already exists"
- );
+ if (proposal.status != ProposalStatus.NONE) {
+ revert ProposalExists(proposalId_);
+ }
+
+ uint256 challenge_ = uint256(
+ keccak256(abi.encode(block.chainid, address(this), proposalId_))
+ ) % BabyJubJub.curve().p;
+
+ _validateCreationZKParams(challenge_, proofData_);
+
+ $.currentProposalId = proposalId_;
// create proposal
- ProposalData storage _proposal = _proposals[proposalId_];
- _proposalIds.add(proposalId_);
+ $.proposalIds.add(proposalId_);
- _proposal.content = content_;
- _proposal.proposalEndTime = block.timestamp + duration_;
+ proposal.content = content_;
+ proposal.roots.add($.participantsCMT.getRoot());
- // vote on behalf of the creator
- // zk validation is processed inside _vote
- _vote(proposalId_, proofData_);
+ proposal.challenge = challenge_;
+ proposal.encryptionKey = _computeEncryptionKey(challenge_);
+
+ proposal.status = ProposalStatus.VOTING;
+
+ $.cumulativeRotationKey = ED256.pInfinity();
+ $.participantsRotation.clear();
emit ProposalCreated(proposalId_, content_);
return proposalId_;
}
- function vote(uint256 proposalId_, ZKParams calldata proofData_) external {
- _vote(proposalId_, proofData_);
+ function vote(VoteParams calldata params_) external {
+ _vote(_getZKMultisigStorage().currentProposalId, params_);
}
- function execute(uint256 proposalId_) external payable {
- require(
- getProposalStatus(proposalId_) == ProposalStatus.ACCEPTED,
- "ZKMultisig: Proposal is not accepted"
- );
+ function revealAndExecute(uint256 approvalVoteCount_) external payable {
+ uint256 proposalId_ = _getZKMultisigStorage().currentProposalId;
- ProposalData storage _proposal = _proposals[proposalId_];
+ _reveal(proposalId_, approvalVoteCount_);
- require(msg.value == _proposal.content.value, "ZKMultisig: Invalid value");
+ _execute(proposalId_);
+ }
- _proposal.content.target.functionCallWithValue(
- _proposal.content.data,
- _proposal.content.value
- );
+ function reveal(uint256 approvalVoteCount_) external {
+ _reveal(_getZKMultisigStorage().currentProposalId, approvalVoteCount_);
+ }
- _proposal.executed = true;
+ function execute(uint256 proposalId_) external payable {
+ _execute(proposalId_);
+ }
- emit ProposalExecuted(proposalId_);
+ function getRotationKDFMSGToSign(uint256 proposalId_) external view returns (bytes32) {
+ return
+ _hashTypedDataV4(
+ keccak256(abi.encode(KDF_ROTATION_MSG_TYPEHASH, address(this), proposalId_))
+ );
}
- function getParticipantsSMTRoot() external view returns (bytes32) {
- return _participantsSMT.getRoot();
+ function getParticipantsCMTRoot() external view returns (bytes32) {
+ return _getZKMultisigStorage().participantsCMT.getRoot();
}
- function getParticipantsSMTProof(
- bytes32 publicKeyHash_
- ) external view override returns (SparseMerkleTree.Proof memory) {
- return _participantsSMT.getProof(publicKeyHash_);
+ function getParticipantsCMTProof(
+ uint256 publicKeyHash_,
+ uint32 desiredProofSize_
+ ) external view override returns (CartesianMerkleTree.Proof memory) {
+ return _getZKMultisigStorage().participantsCMT.getProof(publicKeyHash_, desiredProofSize_);
}
function getParticipantsCount() external view returns (uint256) {
- return _participants.length();
+ return _getZKMultisigStorage().participantsPermanent.length();
}
- function getParticipants() external view returns (uint256[] memory) {
- return _participants.values();
+ function getParticipants()
+ external
+ view
+ returns (ED256.APoint[] memory permanentKeys_, ED256.APoint[] memory rotationKeys_)
+ {
+ ZKMultisigStorage storage $ = _getZKMultisigStorage();
+
+ uint256 permanentKeysCount_ = $.participantsPermanent.length();
+ permanentKeys_ = new ED256.APoint[](permanentKeysCount_);
+
+ for (uint256 i = 0; i < permanentKeysCount_; i++) {
+ permanentKeys_[i] = $.participantPermanentKeys[$.participantsPermanent.at(i)];
+ }
+
+ uint256 rotationKeysCount_ = $.participantsRotation.length();
+ rotationKeys_ = new ED256.APoint[](rotationKeysCount_);
+
+ for (uint256 i = 0; i < rotationKeysCount_; i++) {
+ rotationKeys_[i] = $.participantRotationKeys[$.participantsRotation.at(i)];
+ }
}
function getProposalsCount() external view returns (uint256) {
- return _proposalIds.length();
+ return _getZKMultisigStorage().proposalIds.length();
}
function getProposalsIds(
uint256 offset,
uint256 limit
) external view override returns (uint256[] memory) {
- return _proposalIds.part(offset, limit);
+ return _getZKMultisigStorage().proposalIds.part(offset, limit);
}
function getQuorumPercentage() external view returns (uint256) {
- return _quorumPercentage;
+ return _getZKMultisigStorage().quorumPercentage;
}
function getProposalInfo(uint256 proposalId_) external view returns (ProposalInfoView memory) {
- ProposalData storage _proposal = _proposals[proposalId_];
+ ProposalData storage proposal = _getZKMultisigStorage().proposals[proposalId_];
return
ProposalInfoView({
- content: _proposal.content,
- proposalEndTime: _proposal.proposalEndTime,
- status: getProposalStatus(proposalId_),
- votesCount: _proposal.blinders.length(),
+ content: proposal.content,
+ status: proposal.status,
+ votesCount: proposal.blinders.length(),
requiredQuorum: getRequiredQuorum()
});
}
@@ -183,164 +255,324 @@ contract ZKMultisig is UUPSUpgradeable, IZKMultisig {
uint256 salt_
) public pure returns (uint256) {
return
- uint256(keccak256(abi.encode(content_.target, content_.value, content_.data, salt_)));
+ uint256(keccak256(abi.encode(content_.target, content_.value, content_.data, salt_))) %
+ BabyJubJub.curve().p;
}
- function getProposalChallenge(uint256 proposalId_) public view returns (uint256) {
- return
- PoseidonUnit1L.poseidon(
- [
- uint256(
- uint248(
- uint256(
- keccak256(abi.encode(block.chainid, address(this), proposalId_))
- )
- )
- )
- ]
- );
+ function getEncryptionKey(uint256 proposalId_) external view returns (ED256.APoint memory) {
+ return _getZKMultisigStorage().proposals[proposalId_].encryptionKey;
+ }
+
+ function getProposalChallenge(uint256 proposalId_) external view returns (uint256) {
+ return _getZKMultisigStorage().proposals[proposalId_].challenge;
}
function isBlinderVoted(
uint256 proposalId_,
uint256 blinderToCheck_
) public view returns (bool) {
- return _proposals[proposalId_].blinders.contains(blinderToCheck_);
+ return _getZKMultisigStorage().proposals[proposalId_].blinders.contains(blinderToCheck_);
}
- function getProposalStatus(uint256 proposalId_) public view returns (ProposalStatus) {
- ProposalData storage _proposal = _proposals[proposalId_];
+ function getProposalStatus(uint256 proposalId_) external view returns (ProposalStatus) {
+ return _getZKMultisigStorage().proposals[proposalId_].status;
+ }
- // Check if the proposal exists by verifying the end time
- if (_proposal.proposalEndTime == 0) {
- return ProposalStatus.NONE;
- }
+ // return the required quorum amount (not percentage) for a given number of participants
+ function getRequiredQuorum() public view returns (uint256) {
+ ZKMultisigStorage storage $ = _getZKMultisigStorage();
+
+ return
+ (($.participantsPermanent.length() * $.quorumPercentage) / PERCENTAGE_100).max(
+ MIN_QUORUM_SIZE
+ );
+ }
+
+ function poseidon3(bytes32 el1_, bytes32 el2_, bytes32 el3_) public pure returns (bytes32) {
+ return bytes32(PoseidonUnit3L.poseidon([uint256(el1_), uint256(el2_), uint256(el3_)]));
+ }
+
+ function _authorizeUpgrade(address newImplementation_) internal override onlyThis {}
+
+ function _addParticipants(
+ ED256.APoint[] calldata permanentKeys_,
+ ED256.APoint[] calldata rotationKeys_
+ ) internal {
+ uint256 participantsToAdd_ = permanentKeys_.length;
+
+ if (participantsToAdd_ == 0) revert NoParticipantsToProcess();
+ if (participantsToAdd_ != rotationKeys_.length) revert KeyLenMismatch();
- // Check if the proposal has been executed
- if (_proposal.executed) {
- return ProposalStatus.EXECUTED;
+ ZKMultisigStorage storage $ = _getZKMultisigStorage();
+
+ uint256 totalNodesCount_ = $.participantsCMT.getNodesCount() + participantsToAdd_ * 2;
+
+ if (totalNodesCount_ > 2 ** PARTICIPANTS_TREE_DEPTH) {
+ revert TooManyParticipants(totalNodesCount_);
}
- // Check if the proposal has met the quorum requirement
- if (_proposal.blinders.length() >= getRequiredQuorum()) {
- return ProposalStatus.ACCEPTED;
+ for (uint256 i = 0; i < participantsToAdd_; i++) {
+ uint256 permanentKeyHash_ = PoseidonUnit3L.poseidon(
+ [permanentKeys_[i].x, permanentKeys_[i].y, 1]
+ );
+
+ uint256 rotationKeyHash_ = PoseidonUnit3L.poseidon(
+ [rotationKeys_[i].x, rotationKeys_[i].y, 2]
+ );
+
+ if (!$.participantsPermanent.contains(permanentKeyHash_)) {
+ $.participantsCMT.add(permanentKeyHash_);
+ $.participantsCMT.add(rotationKeyHash_);
+
+ $.participantsPermanent.add(permanentKeyHash_);
+ $.participantsRotation.add(rotationKeyHash_);
+
+ $.participantPermanentKeys[permanentKeyHash_] = permanentKeys_[i];
+ $.participantRotationKeys[rotationKeyHash_] = rotationKeys_[i];
+
+ $.cumulativePermanentKey = $.cumulativePermanentKey.add(permanentKeys_[i]);
+ $.cumulativeRotationKey = $.cumulativeRotationKey.add(rotationKeys_[i]);
+
+ emit ParticipantAdded(permanentKeys_[i], rotationKeys_[i]);
+ }
}
+ }
+
+ function _removeParticipants(ED256.APoint[] calldata permanentKeys_) internal {
+ if (permanentKeys_.length == 0) revert NoParticipantsToProcess();
+
+ ZKMultisigStorage storage $ = _getZKMultisigStorage();
+
+ for (uint256 i = 0; i < permanentKeys_.length; i++) {
+ uint256 permanentKeyHash_ = PoseidonUnit3L.poseidon(
+ [permanentKeys_[i].x, permanentKeys_[i].y, 1]
+ );
- // Check if the proposal is still within the voting period
- if (_proposal.proposalEndTime > block.timestamp) {
- return ProposalStatus.VOTING;
+ if ($.participantsPermanent.contains(permanentKeyHash_)) {
+ $.participantsCMT.remove(permanentKeyHash_);
+ $.participantsPermanent.remove(permanentKeyHash_);
+ delete $.participantPermanentKeys[permanentKeyHash_];
+
+ $.cumulativePermanentKey = $.cumulativePermanentKey.subA(permanentKeys_[i]);
+
+ emit ParticipantRemoved(permanentKeys_[i]);
+ }
}
- // If the proposal has not met the quorum and the voting period has expired
- return ProposalStatus.EXPIRED;
+ if (_getZKMultisigStorage().participantsPermanent.length() == 0) {
+ revert RemovingAllParticipants();
+ }
}
- // return the required quorum amount (not percentage) for a given number of participants
- function getRequiredQuorum() public view returns (uint256) {
- return
- ((_participants.length() * _quorumPercentage) / PERCENTAGE_100).max(MIN_QUORUM_SIZE);
+ function _updateQuorumPercentage(uint256 newQuorumPercentage_) internal {
+ ZKMultisigStorage storage $ = _getZKMultisigStorage();
+
+ if (
+ newQuorumPercentage_ == 0 ||
+ newQuorumPercentage_ > PERCENTAGE_100 ||
+ newQuorumPercentage_ == $.quorumPercentage
+ ) {
+ revert InvalidQuorum(newQuorumPercentage_);
+ }
+
+ $.quorumPercentage = newQuorumPercentage_;
}
- function _authorizeUpgrade(address newImplementation_) internal override onlyThis {}
+ function _updateCreationVerifier(address creationVerifier_) internal {
+ ZKMultisigStorage storage $ = _getZKMultisigStorage();
- function _addParticipants(uint256[] memory participantsToAdd_) internal {
- require(
- _participants.length() + participantsToAdd_.length <= 2 ** PARTICIPANTS_TREE_DEPTH,
- "ZKMultisig: Too many participants"
- );
+ _validateVerifier(creationVerifier_, $.creationVerifier);
- _processParticipants(participantsToAdd_, true);
+ $.creationVerifier = creationVerifier_;
}
- function _removeParticipants(uint256[] memory participantsToRemove_) internal {
- _processParticipants(participantsToRemove_, false);
+ function _updateVotingVerifier(address votingVerifier_) internal {
+ ZKMultisigStorage storage $ = _getZKMultisigStorage();
- require(_participants.length() > 0, "ZKMultisig: Cannot remove all participants");
+ _validateVerifier(votingVerifier_, $.votingVerifier);
+
+ $.votingVerifier = votingVerifier_;
}
- function _updateQuorumPercentage(uint256 newQuorumPercentage_) internal {
- require(
- newQuorumPercentage_ > 0 &&
- newQuorumPercentage_ <= PERCENTAGE_100 &&
- newQuorumPercentage_ != _quorumPercentage,
- "ZKMultisig: Invalid quorum percentage"
- );
+ function _vote(uint256 proposalId_, VoteParams calldata params_) internal {
+ ZKMultisigStorage storage $ = _getZKMultisigStorage();
- _quorumPercentage = newQuorumPercentage_;
- }
+ ProposalData storage proposal = $.proposals[proposalId_];
+
+ if (proposal.status != ProposalStatus.VOTING) {
+ revert NotVoting(proposalId_);
+ }
+
+ if ($.rotationKeyNullifiers[params_.keyNullifier]) {
+ revert UsedNullifier(params_.keyNullifier);
+ }
+
+ if (isBlinderVoted(proposalId_, params_.blinder)) {
+ revert UsedBlinder(params_.blinder);
+ }
+
+ if (!proposal.roots.contains(params_.cmtRoot)) {
+ revert InvalidCMTRoot(params_.cmtRoot);
+ }
- function _updateParticipantVerifier(address participantVerifier_) internal {
- require(participantVerifier_ != address(0), "ZKMultisig: Invalid verifier address");
- require(
- participantVerifier_ != participantVerifier,
- "ZKMultisig: The same verifier address"
+ $.rotationKeyNullifiers[params_.keyNullifier] = true;
+ proposal.blinders.add(params_.blinder);
+
+ ED256.APoint[2] memory vote_ = abi.decode(params_.encryptedVote, (ED256.APoint[2]));
+
+ _validateVotingZKParams(proposalId_, params_, vote_);
+
+ proposal.aggregatedVotes[0] = proposal.aggregatedVotes[0].add(vote_[0]);
+ proposal.aggregatedVotes[1] = proposal.aggregatedVotes[1].add(vote_[1]);
+
+ proposal.decryptionKey =
+ (proposal.decryptionKey + params_.decryptionKeyShare) %
+ BabyJubJub.curve().n;
+
+ uint256 rotationKeyHash_ = PoseidonUnit3L.poseidon(
+ [params_.rotationKey.x, params_.rotationKey.y, 2]
);
- require(participantVerifier_.code.length > 0, "ZKMultisig: Not a contract");
- participantVerifier = participantVerifier_;
+ $.participantsCMT.add(rotationKeyHash_);
+ proposal.roots.add($.participantsCMT.getRoot());
+
+ $.participantsRotation.add(rotationKeyHash_);
+ $.participantRotationKeys[rotationKeyHash_] = params_.rotationKey;
+
+ $.cumulativeRotationKey = $.cumulativeRotationKey.add(params_.rotationKey);
+
+ emit ProposalVoted(proposalId_, params_.blinder);
}
- function _vote(uint256 proposalId_, ZKParams calldata proofData_) internal {
- require(
- getProposalStatus(proposalId_) == ProposalStatus.VOTING,
- "ZKMultisig: Proposal is not in voting state"
- );
+ function _reveal(uint256 proposalId_, uint256 approvalVoteCount_) internal {
+ ZKMultisigStorage storage $ = _getZKMultisigStorage();
+
+ ProposalData storage proposal = $.proposals[proposalId_];
+
+ ED256.PPoint memory xC1_ = proposal.aggregatedVotes[0].mul(proposal.decryptionKey);
+ ED256.PPoint memory T_ = proposal.aggregatedVotes[1].subP(xC1_);
+
+ if (!T_.verifyScalarMult(approvalVoteCount_)) revert VoteCountMismatch();
- _validateZKParams(proposalId_, proofData_);
+ bool isAccepted_ = approvalVoteCount_ >= getRequiredQuorum();
- _proposals[proposalId_].blinders.add(proofData_.inputs[0]);
+ proposal.status = isAccepted_ ? ProposalStatus.ACCEPTED : ProposalStatus.REJECTED;
- emit ProposalVoted(proposalId_, proofData_.inputs[0]);
+ emit ProposalRevealed(proposalId_, isAccepted_);
}
- function _validateZKParams(uint256 proposalId_, ZKParams calldata proofData_) internal view {
- require(proofData_.inputs.length == 3, "ZKMultisig: Invalid proof data");
+ function _execute(uint256 proposalId_) internal {
+ ProposalData storage proposal = _getZKMultisigStorage().proposals[proposalId_];
- require(
- !isBlinderVoted(proposalId_, proofData_.inputs[0]),
- "ZKMultisig: Blinder already voted"
- );
+ if (proposal.status != ProposalStatus.ACCEPTED) {
+ revert ProposalNotAccepted(proposalId_);
+ }
- require(
- proofData_.inputs[1] == getProposalChallenge(proposalId_),
- "ZKMultisig: Invalid challenge"
- );
+ if (msg.value != proposal.content.value) {
+ revert InvalidValue(msg.value, proposal.content.value);
+ }
- require(
- proofData_.inputs[2] == uint256(_participantsSMT.getRoot()),
- "ZKMultisig: Invalid SMT root"
+ proposal.content.target.functionCallWithValue(
+ proposal.content.data,
+ proposal.content.value
);
- require(
- participantVerifier.verifyProof(
- proofData_.inputs,
- VerifierHelper.ProofPoints({a: proofData_.a, b: proofData_.b, c: proofData_.c})
- ),
- "ZKMultisig: Invalid proof"
- );
+ proposal.status = ProposalStatus.EXECUTED;
+
+ emit ProposalExecuted(proposalId_);
}
- function _processParticipants(uint256[] memory participants_, bool isAdding_) private {
- require(participants_.length > 0, "Multisig: No participants to process");
+ function _computeEncryptionKey(
+ uint256 challenge_
+ ) internal view returns (ED256.APoint memory) {
+ uint256 h1_ = PoseidonUnit1L.poseidon([challenge_]);
+ uint256 h2_ = PoseidonUnit1L.poseidon([h1_]);
- for (uint256 i = 0; i < participants_.length; i++) {
- uint256 participant_ = participants_[i];
+ ZKMultisigStorage storage $ = _getZKMultisigStorage();
- if (isAdding_) {
- if (!_participants.contains(participant_)) {
- _participantsSMT.add(bytes32(participant_), participant_);
- _participants.add(participant_);
- }
- } else {
- if (_participants.contains(participant_)) {
- _participantsSMT.remove(bytes32(participant_));
- _participants.remove(participant_);
- }
- }
+ return $.cumulativePermanentKey.mul2($.cumulativeRotationKey, h1_, h2_);
+ }
+
+ function _validateVerifier(address verifier_, address actualVerifier_) internal view {
+ if (verifier_ == address(0)) revert ZeroVerifier();
+ if (verifier_ == actualVerifier_) revert DuplicateVerifier();
+ if (verifier_.code.length == 0) revert NotAContract(verifier_);
+ }
+
+ function _validateCreationZKParams(
+ uint256 challenge_,
+ ZKParams calldata proofData_
+ ) internal view {
+ ZKMultisigStorage storage $ = _getZKMultisigStorage();
+
+ uint256[] memory inputs_ = new uint256[](2);
+ inputs_[0] = uint256($.participantsCMT.getRoot());
+ inputs_[1] = challenge_;
+
+ if (
+ !$.creationVerifier.verifyProof(
+ Groth16VerifierHelper.Groth16Proof({
+ proofPoints: Groth16VerifierHelper.ProofPoints({
+ a: proofData_.a,
+ b: proofData_.b,
+ c: proofData_.c
+ }),
+ publicSignals: inputs_
+ })
+ )
+ ) {
+ revert InvalidProof();
+ }
+ }
+
+ function _validateVotingZKParams(
+ uint256 proposalId_,
+ VoteParams calldata params_,
+ ED256.APoint[2] memory vote_
+ ) internal view {
+ ZKMultisigStorage storage $ = _getZKMultisigStorage();
+
+ ProposalData storage proposal = $.proposals[proposalId_];
+
+ uint256[] memory inputs_ = new uint256[](14);
+ inputs_[0] = params_.blinder;
+ inputs_[1] = params_.keyNullifier;
+ inputs_[2] = vote_[0].x;
+ inputs_[3] = vote_[0].y;
+ inputs_[4] = vote_[1].x;
+ inputs_[5] = vote_[1].y;
+ inputs_[6] = params_.rotationKey.x;
+ inputs_[7] = params_.rotationKey.y;
+ inputs_[8] = params_.decryptionKeyShare;
+ inputs_[9] = proposal.encryptionKey.x;
+ inputs_[10] = proposal.encryptionKey.y;
+ inputs_[11] = proposal.challenge;
+ inputs_[12] = proposalId_;
+ inputs_[13] = uint256(params_.cmtRoot);
+
+ if (
+ !$.votingVerifier.verifyProof(
+ Groth16VerifierHelper.Groth16Proof({
+ proofPoints: Groth16VerifierHelper.ProofPoints({
+ a: params_.proofData.a,
+ b: params_.proofData.b,
+ c: params_.proofData.c
+ }),
+ publicSignals: inputs_
+ })
+ )
+ ) {
+ revert InvalidProof();
}
}
function _validateMsgSender() private view {
- require(msg.sender == address(this), "ZKMultisig: Not authorized call");
+ if (msg.sender != address(this)) revert NotAuthorizedCall();
+ }
+
+ function _getZKMultisigStorage() private pure returns (ZKMultisigStorage storage $) {
+ assembly {
+ $.slot := ZK_MULTISIG_STORAGE
+ }
}
}
diff --git a/contracts/ZKMultisigFactory.sol b/contracts/ZKMultisigFactory.sol
index 5d1baf3..e819921 100644
--- a/contracts/ZKMultisigFactory.sol
+++ b/contracts/ZKMultisigFactory.sol
@@ -1,61 +1,100 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.4;
+pragma solidity ^0.8.21;
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
-import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
+import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import {Paginator} from "@solarity/solidity-lib/libs/arrays/Paginator.sol";
+import {ED256} from "@solarity/solidity-lib/libs/crypto/ED256.sol";
import {IZKMultisigFactory} from "./interfaces/IZKMultisigFactory.sol";
import {IZKMultisig} from "./interfaces/IZKMultisig.sol";
-contract ZKMultisigFactory is EIP712, IZKMultisigFactory {
+contract ZKMultisigFactory is EIP712Upgradeable, IZKMultisigFactory {
using EnumerableSet for EnumerableSet.AddressSet;
using Paginator for EnumerableSet.AddressSet;
- bytes32 public constant KDF_MESSAGE_TYPEHASH = keccak256("KDF(address zkMultisigAddress)");
+ // bytes32(uint256(keccak256("private.multisig.contract.ZKMultisigFactory")) - 1)
+ bytes32 private constant ZK_MULTISIG_FACTORY_STORAGE =
+ 0x38978376a0cb2da5e8822474eb0dac781c45e719147d5ca8642e873e8011dbbb;
- address public immutable PARTICIPANT_VERIFIER;
- address public immutable ZK_MULTISIG_IMPL;
+ bytes32 public constant KDF_MESSAGE_TYPEHASH = keccak256("KDF(address zkMultisigAddress)");
- EnumerableSet.AddressSet private _zkMultisigs;
+ struct ZKMultisigFactoryStorage {
+ address zkMultisigImpl;
+ address creationVerifier;
+ address votingVerifier;
+ EnumerableSet.AddressSet zkMultisigs;
+ }
- constructor(
+ function initialize(
address zkMultisigImplementation_,
- address participantVerifier_
- ) EIP712("ZKMultisigFactory", "1") {
- require(
- zkMultisigImplementation_.code.length > 0 && participantVerifier_.code.length > 0,
- "ZKMultisigFactory: Invalid implementation or verifier address"
- );
+ address creationVerifier_,
+ address votingVerifier_
+ ) external initializer {
+ __EIP712_init("ZKMultisigFactory", "1");
+
+ if (
+ zkMultisigImplementation_.code.length == 0 ||
+ creationVerifier_.code.length == 0 ||
+ votingVerifier_.code.length == 0
+ ) {
+ revert InvalidImplementationOrVerifier();
+ }
- PARTICIPANT_VERIFIER = participantVerifier_;
- ZK_MULTISIG_IMPL = zkMultisigImplementation_;
+ ZKMultisigFactoryStorage storage $ = _getZKMultisigFactoryStorage();
+
+ $.creationVerifier = creationVerifier_;
+ $.votingVerifier = votingVerifier_;
+
+ $.zkMultisigImpl = zkMultisigImplementation_;
}
function createMultisig(
- uint256[] calldata participants_,
+ ED256.APoint[] calldata permanentKeys_,
+ ED256.APoint[] calldata rotationKeys_,
uint256 quorumPercentage_,
uint256 salt_
) external returns (address) {
+ ZKMultisigFactoryStorage storage $ = _getZKMultisigFactoryStorage();
+
address zkMultisigAddress_ = address(
- new ERC1967Proxy{salt: keccak256(abi.encode(msg.sender, salt_))}(ZK_MULTISIG_IMPL, "")
+ new ERC1967Proxy{salt: keccak256(abi.encode(msg.sender, salt_))}($.zkMultisigImpl, "")
);
IZKMultisig(zkMultisigAddress_).initialize(
- participants_,
+ permanentKeys_,
+ rotationKeys_,
quorumPercentage_,
- PARTICIPANT_VERIFIER
+ $.creationVerifier,
+ $.votingVerifier
);
- _zkMultisigs.add(zkMultisigAddress_);
+ $.zkMultisigs.add(zkMultisigAddress_);
- emit ZKMultisigCreated(zkMultisigAddress_, participants_, quorumPercentage_);
+ emit ZKMultisigCreated(
+ zkMultisigAddress_,
+ permanentKeys_,
+ rotationKeys_,
+ quorumPercentage_
+ );
return zkMultisigAddress_;
}
+ function getZKMultisigImplementation() external view returns (address) {
+ return _getZKMultisigFactoryStorage().zkMultisigImpl;
+ }
+
+ function getCreationVerifier() external view returns (address) {
+ return _getZKMultisigFactoryStorage().creationVerifier;
+ }
+
+ function getVotingVerifier() external view returns (address) {
+ return _getZKMultisigFactoryStorage().votingVerifier;
+ }
+
function computeZKMultisigAddress(
address deployer_,
uint256 salt_
@@ -66,25 +105,25 @@ contract ZKMultisigFactory is EIP712, IZKMultisigFactory {
keccak256(
abi.encodePacked(
type(ERC1967Proxy).creationCode,
- abi.encode(ZK_MULTISIG_IMPL, "")
+ abi.encode(_getZKMultisigFactoryStorage().zkMultisigImpl, "")
)
)
);
}
function getZKMultisigsCount() external view returns (uint256) {
- return _zkMultisigs.length();
+ return _getZKMultisigFactoryStorage().zkMultisigs.length();
}
function getZKMultisigs(
uint256 offset_,
uint256 limit_
) external view returns (address[] memory) {
- return _zkMultisigs.part(offset_, limit_);
+ return _getZKMultisigFactoryStorage().zkMultisigs.part(offset_, limit_);
}
function isZKMultisig(address multisigAddress_) external view returns (bool) {
- return _zkMultisigs.contains(multisigAddress_);
+ return _getZKMultisigFactoryStorage().zkMultisigs.contains(multisigAddress_);
}
function getDefaultKDFMSGToSign() external view returns (bytes32) {
@@ -98,4 +137,14 @@ contract ZKMultisigFactory is EIP712, IZKMultisigFactory {
function getKDFMSGHash(address zkMutlisigAddress_) private pure returns (bytes32) {
return keccak256(abi.encode(KDF_MESSAGE_TYPEHASH, zkMutlisigAddress_));
}
+
+ function _getZKMultisigFactoryStorage()
+ private
+ pure
+ returns (ZKMultisigFactoryStorage storage $)
+ {
+ assembly {
+ $.slot := ZK_MULTISIG_FACTORY_STORAGE
+ }
+ }
}
diff --git a/contracts/interfaces/IZKMultisig.sol b/contracts/interfaces/IZKMultisig.sol
index 3834332..e32798a 100644
--- a/contracts/interfaces/IZKMultisig.sol
+++ b/contracts/interfaces/IZKMultisig.sol
@@ -1,15 +1,17 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.4;
+pragma solidity ^0.8.21;
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
-import {SparseMerkleTree} from "@solarity/solidity-lib/libs/data-structures/SparseMerkleTree.sol";
+
+import {ED256} from "@solarity/solidity-lib/libs/crypto/ED256.sol";
+import {CartesianMerkleTree} from "@solarity/solidity-lib/libs/data-structures/CartesianMerkleTree.sol";
interface IZKMultisig {
enum ProposalStatus {
NONE,
VOTING,
ACCEPTED,
- EXPIRED,
+ REJECTED,
EXECUTED
}
@@ -17,7 +19,16 @@ interface IZKMultisig {
uint256[2] a;
uint256[2][2] b;
uint256[2] c;
- uint256[] inputs; // 0 -> blinder, 1 -> challenge, 2 -> SMT root
+ }
+
+ struct VoteParams {
+ bytes encryptedVote;
+ uint256 decryptionKeyShare;
+ uint256 keyNullifier;
+ uint256 blinder;
+ bytes32 cmtRoot;
+ ED256.APoint rotationKey;
+ ZKParams proofData;
}
struct ProposalContent {
@@ -27,81 +38,118 @@ interface IZKMultisig {
}
struct ProposalData {
+ ProposalStatus status;
ProposalContent content;
- uint256 proposalEndTime;
EnumerableSet.UintSet blinders;
- bool executed;
+ EnumerableSet.Bytes32Set roots;
+ uint256 challenge;
+ uint256 decryptionKey;
+ ED256.APoint encryptionKey;
+ ED256.PPoint[2] aggregatedVotes;
}
struct ProposalInfoView {
ProposalContent content;
- uint256 proposalEndTime;
ProposalStatus status;
uint256 votesCount;
uint256 requiredQuorum;
}
+ error ZeroTarget();
+ error ProposalExists(uint256 proposalId);
+ error ProposalNotAccepted(uint256 proposalId);
+ error ActiveProposal(uint256 proposalId);
+ error InvalidValue(uint256 actualValue, uint256 expectedValue);
+ error KeyLenMismatch();
+ error TooManyParticipants(uint256 participants);
+ error RemovingAllParticipants();
+ error InvalidQuorum(uint256 quorumPercentage);
+ error ZeroVerifier();
+ error DuplicateVerifier();
+ error NotAContract(address verifier);
+ error NotVoting(uint256 proposalId);
+ error UsedNullifier(uint256 nullifier);
+ error InvalidCMTRoot(bytes32 root);
+ error UsedBlinder(uint256 blinder);
+ error InvalidProof();
+ error NoParticipantsToProcess();
+ error VoteCountMismatch();
+ error NotAuthorizedCall();
+
+ event ParticipantAdded(ED256.APoint permanentKey, ED256.APoint rotationKey);
+ event ParticipantRemoved(ED256.APoint permanentKey);
event ProposalCreated(uint256 indexed proposalId, ProposalContent content);
-
event ProposalVoted(uint256 indexed proposalId, uint256 voterBlinder);
-
+ event ProposalRevealed(uint256 indexed proposalId, bool isAccepted);
event ProposalExecuted(uint256 indexed proposalId);
function initialize(
- uint256[] memory participants_,
+ ED256.APoint[] memory permanentKeys_,
+ ED256.APoint[] memory rotationKeys_,
uint256 quorumPercentage_,
- address participantVerifier_
+ address creationVerifier_,
+ address votingVerifier_
) external;
- function addParticipants(uint256[] calldata participantsToAdd) external;
+ function addParticipants(
+ ED256.APoint[] calldata permanentKeys_,
+ ED256.APoint[] calldata rotationKeys_
+ ) external;
- function removeParticipants(uint256[] calldata participantsToRemove) external;
+ function removeParticipants(ED256.APoint[] calldata permanentKeys_) external;
- function updateQuorumPercentage(uint256 newQuorumPercentage) external;
+ function updateQuorumPercentage(uint256 newQuorumPercentage_) external;
function create(
- ProposalContent calldata content,
- uint256 duration,
- uint256 salt,
- ZKParams calldata proofData
+ ProposalContent calldata content_,
+ uint256 salt_,
+ ZKParams calldata proofData_
) external returns (uint256);
- function vote(uint256 proposalId, ZKParams calldata proofData) external;
+ function vote(VoteParams calldata params_) external;
+
+ function reveal(uint256 approvalVoteCount_) external;
+
+ function revealAndExecute(uint256 approvalVoteCount_) external payable;
- function execute(uint256 proposalId) external payable;
+ function execute(uint256 proposalId_) external payable;
- function getParticipantsSMTRoot() external view returns (bytes32);
+ function getParticipantsCMTRoot() external view returns (bytes32);
- function getParticipantsSMTProof(
- bytes32 publicKeyHash
- ) external view returns (SparseMerkleTree.Proof memory);
+ function getParticipantsCMTProof(
+ uint256 publicKeyHash_,
+ uint32 desiredProofSize_
+ ) external view returns (CartesianMerkleTree.Proof memory);
function getParticipantsCount() external view returns (uint256);
- function getParticipants() external view returns (uint256[] memory);
+ function getParticipants()
+ external
+ view
+ returns (ED256.APoint[] memory, ED256.APoint[] memory);
function getProposalsCount() external view returns (uint256);
function getProposalsIds(
- uint256 offset,
- uint256 limit
+ uint256 offset_,
+ uint256 limit_
) external view returns (uint256[] memory);
function getQuorumPercentage() external view returns (uint256);
- function getProposalInfo(uint256 proposalId) external view returns (ProposalInfoView memory);
+ function getProposalInfo(uint256 proposalId_) external view returns (ProposalInfoView memory);
- function getProposalStatus(uint256 proposalId) external view returns (ProposalStatus);
+ function getProposalStatus(uint256 proposalId_) external view returns (ProposalStatus);
- function getProposalChallenge(uint256 proposalId) external view returns (uint256);
+ function getProposalChallenge(uint256 proposalId_) external view returns (uint256);
function computeProposalId(
- ProposalContent calldata content,
- uint256 salt
+ ProposalContent calldata content_,
+ uint256 salt_
) external view returns (uint256);
function isBlinderVoted(
- uint256 proposalId,
- uint256 blinderToCheck
+ uint256 proposalId_,
+ uint256 blinderToCheck_
) external view returns (bool);
}
diff --git a/contracts/interfaces/IZKMultisigFactory.sol b/contracts/interfaces/IZKMultisigFactory.sol
index 18dea8d..c85c88c 100644
--- a/contracts/interfaces/IZKMultisigFactory.sol
+++ b/contracts/interfaces/IZKMultisigFactory.sol
@@ -1,15 +1,21 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.4;
+pragma solidity ^0.8.21;
+
+import {ED256} from "@solarity/solidity-lib/libs/crypto/ED256.sol";
interface IZKMultisigFactory {
+ error InvalidImplementationOrVerifier();
+
event ZKMultisigCreated(
address indexed zkMultisigAddress,
- uint256[] initialParticipants,
+ ED256.APoint[] initialParticipantsPerm,
+ ED256.APoint[] initialParticipantsRot,
uint256 initialQuorumPercentage
);
function createMultisig(
- uint256[] calldata participants_,
+ ED256.APoint[] calldata permanentKeys_,
+ ED256.APoint[] calldata rotationKeys_,
uint256 quorumPercentage_,
uint256 salt_
) external returns (address);
diff --git a/contracts/libs/BabyJubJub.sol b/contracts/libs/BabyJubJub.sol
new file mode 100644
index 0000000..eca7f7a
--- /dev/null
+++ b/contracts/libs/BabyJubJub.sol
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.21;
+
+import {ED256} from "@solarity/solidity-lib/libs/crypto/ED256.sol";
+
+library BabyJubJub {
+ using ED256 for *;
+
+ function curve() internal pure returns (ED256.Curve memory) {
+ return
+ ED256.Curve({
+ a: 168700,
+ d: 168696,
+ p: 21888242871839275222246405745257275088548364400416034343698204186575808495617,
+ n: 2736030358979909402780800718157159386076813972158567259200215660948447373041,
+ gx: 5299619240641551281634865583518297030282874472190772894086521144482721001553,
+ gy: 16950150798460657717958625567821834550301663161624707787222815936182638968203
+ });
+ }
+
+ function add(
+ ED256.PPoint memory pPoint_,
+ ED256.APoint memory aPoint_
+ ) internal pure returns (ED256.PPoint memory) {
+ return curve().pAddPoint(pPoint_, aPoint_.toProjective());
+ }
+
+ function subA(
+ ED256.PPoint memory pPoint_,
+ ED256.APoint memory aPoint_
+ ) internal pure returns (ED256.PPoint memory) {
+ return curve().pSubPoint(pPoint_, aPoint_.toProjective());
+ }
+
+ function subP(
+ ED256.PPoint memory p1_,
+ ED256.PPoint memory p2_
+ ) internal pure returns (ED256.PPoint memory) {
+ return curve().pSubPoint(p1_, p2_);
+ }
+
+ function mul(
+ ED256.PPoint memory p_,
+ uint256 scalar_
+ ) internal pure returns (ED256.PPoint memory) {
+ return curve().pMultShamir(p_, scalar_);
+ }
+
+ function mul2(
+ ED256.PPoint memory p1_,
+ ED256.PPoint memory p2_,
+ uint256 scalar1_,
+ uint256 scalar2_
+ ) internal view returns (ED256.APoint memory) {
+ ED256.Curve memory babyJubJub_ = curve();
+
+ return babyJubJub_.toAffine(babyJubJub_.pMultShamir2(p1_, p2_, scalar1_, scalar2_));
+ }
+
+ function verifyScalarMult(
+ ED256.PPoint memory p_,
+ uint256 scalar_
+ ) internal view returns (bool) {
+ ED256.Curve memory babyJubJub_ = curve();
+
+ return babyJubJub_.pEqual(p_, babyJubJub_.pMultShamir(babyJubJub_.pBasepoint(), scalar_));
+ }
+}
diff --git a/contracts/libs/Poseidon.sol b/contracts/libs/Poseidon.sol
index 9892599..32d4e4a 100644
--- a/contracts/libs/Poseidon.sol
+++ b/contracts/libs/Poseidon.sol
@@ -1,6 +1,10 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.4;
+pragma solidity ^0.8.21;
library PoseidonUnit1L {
function poseidon(uint256[1] calldata) public pure returns (uint256) {}
}
+
+library PoseidonUnit3L {
+ function poseidon(uint256[3] calldata) public pure returns (uint256) {}
+}
diff --git a/contracts/mock/EthReceiverMock.sol b/contracts/mock/EthReceiverMock.sol
new file mode 100644
index 0000000..97444fa
--- /dev/null
+++ b/contracts/mock/EthReceiverMock.sol
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.21;
+
+contract EthReceiverMock {
+ event ReceivedEth(address from_, uint256 amount_);
+
+ receive() external payable {
+ emit ReceivedEth(msg.sender, msg.value);
+ }
+}
diff --git a/contracts/mock/VerifierMock.sol b/contracts/mock/VerifierMock.sol
deleted file mode 100644
index ec0a8f1..0000000
--- a/contracts/mock/VerifierMock.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.4;
-
-contract PositiveVerifierMock {
- function verifyProof(
- uint256[2] calldata,
- uint256[2][2] calldata,
- uint256[2] calldata,
- uint256[3] calldata
- ) public pure returns (bool) {
- return true;
- }
-}
-
-contract NegativeVerifierMock {
- function verifyProof(
- uint256[2] calldata,
- uint256[2][2] calldata,
- uint256[2] calldata,
- uint256[3] calldata
- ) public pure returns (bool) {
- return false;
- }
-}
diff --git a/contracts/mock/ZKMultisigMock.sol b/contracts/mock/ZKMultisigMock.sol
new file mode 100644
index 0000000..3973896
--- /dev/null
+++ b/contracts/mock/ZKMultisigMock.sol
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.21;
+
+import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
+
+import {ED256} from "@solarity/solidity-lib/libs/crypto/ED256.sol";
+
+import {ZKMultisig} from "../ZKMultisig.sol";
+import {BabyJubJub} from "../libs/BabyJubJub.sol";
+
+contract ZKMultisigMock is ZKMultisig {
+ using EnumerableSet for *;
+ using ED256 for *;
+
+ receive() external payable {}
+
+ function addParticipantsExternal(
+ ED256.APoint[] calldata permanentKeys_,
+ ED256.APoint[] calldata rotationKeys_
+ ) external {
+ _addParticipants(permanentKeys_, rotationKeys_);
+ }
+
+ function removeParticipantsExternal(ED256.APoint[] calldata permanentKeys_) external {
+ _removeParticipants(permanentKeys_);
+ }
+
+ function updateQuorumPercentageExternal(uint256 newQuorumPercentage_) external {
+ _updateQuorumPercentage(newQuorumPercentage_);
+ }
+
+ function updateVerifierExternal(address verifier_, bool creation_) external {
+ if (creation_) {
+ _updateCreationVerifier(verifier_);
+ } else {
+ _updateVotingVerifier(verifier_);
+ }
+ }
+
+ function deactivateProposal(uint256 proposalId_) external {
+ _getZKMultisigMockStorage().proposals[proposalId_].status = ProposalStatus.EXECUTED;
+ }
+
+ function getCreationVerifier() external view returns (address) {
+ return _getZKMultisigMockStorage().creationVerifier;
+ }
+
+ function getVotingVerifier() external view returns (address) {
+ return _getZKMultisigMockStorage().votingVerifier;
+ }
+
+ function getCumulativePermanentKey() external view returns (ED256.APoint memory) {
+ ED256.Curve memory babyJubJub_ = BabyJubJub.curve();
+
+ return ED256.toAffine(babyJubJub_, _getZKMultisigMockStorage().cumulativePermanentKey);
+ }
+
+ function getCumulativeRotationKey() external view returns (ED256.APoint memory) {
+ ED256.Curve memory babyJubJub_ = BabyJubJub.curve();
+
+ return ED256.toAffine(babyJubJub_, _getZKMultisigMockStorage().cumulativeRotationKey);
+ }
+
+ function getCurrentProposalId() external view returns (uint256) {
+ return _getZKMultisigMockStorage().currentProposalId;
+ }
+
+ function getProposalBlinders(uint256 proposalId_) external view returns (uint256[] memory) {
+ return _getZKMultisigMockStorage().proposals[proposalId_].blinders.values();
+ }
+
+ function getProposalDecryptionKey(uint256 proposalId_) external view returns (uint256) {
+ return _getZKMultisigMockStorage().proposals[proposalId_].decryptionKey;
+ }
+
+ function getProposalAggregatedVotes(
+ uint256 proposalId_
+ ) external view returns (ED256.APoint[2] memory) {
+ ED256.PPoint[2] memory votes_ = _getZKMultisigMockStorage()
+ .proposals[proposalId_]
+ .aggregatedVotes;
+
+ ED256.Curve memory babyJubJub_ = BabyJubJub.curve();
+
+ return [ED256.toAffine(babyJubJub_, votes_[0]), ED256.toAffine(babyJubJub_, votes_[1])];
+ }
+
+ function isRotationKeyNullifierUsed(uint256 nullifier_) external view returns (bool) {
+ return _getZKMultisigMockStorage().rotationKeyNullifiers[nullifier_];
+ }
+
+ function _getZKMultisigMockStorage() private pure returns (ZKMultisigStorage storage $) {
+ assembly {
+ $.slot := 0x498bc96b7d273653a6dbed08a392bbda0eadd6b7201a83a295108683ba304490
+ }
+ }
+}
diff --git a/hardhat.config.ts b/hardhat.config.ts
index 58adf99..9012016 100644
--- a/hardhat.config.ts
+++ b/hardhat.config.ts
@@ -3,6 +3,7 @@ import "@nomicfoundation/hardhat-chai-matchers";
import "@solarity/hardhat-migrate";
import "@solarity/hardhat-gobind";
import "@solarity/hardhat-markup";
+import "@solarity/hardhat-zkit";
import "@typechain/hardhat";
import "hardhat-contract-sizer";
import "hardhat-gas-reporter";
@@ -22,6 +23,7 @@ const config: HardhatUserConfig = {
networks: {
hardhat: {
initialDate: "1970-01-01T00:00:00Z",
+ allowUnlimitedContractSize: true,
},
localhost: {
url: "http://127.0.0.1:8545",
@@ -71,7 +73,7 @@ const config: HardhatUserConfig = {
},
},
solidity: {
- version: "0.8.20",
+ version: "0.8.22",
settings: {
optimizer: {
enabled: true,
diff --git a/package-lock.json b/package-lock.json
index 5587c20..5f31e18 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,9 +9,10 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
- "@openzeppelin/contracts": "5.0.2",
- "@openzeppelin/contracts-upgradeable": "5.0.2",
- "@solarity/solidity-lib": "2.7.11",
+ "@openzeppelin/contracts": "5.4.0",
+ "@openzeppelin/contracts-upgradeable": "5.4.0",
+ "@solarity/circom-lib": "0.2.2",
+ "@solarity/solidity-lib": "3.2.8",
"dotenv": "16.4.5",
"hardhat": "2.20.1",
"typechain": "8.3.2"
@@ -25,11 +26,13 @@
"@solarity/hardhat-gobind": "^1.2.2",
"@solarity/hardhat-markup": "^1.0.7",
"@solarity/hardhat-migrate": "^2.1.7",
+ "@solarity/hardhat-zkit": "^0.5.17",
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"@types/chai": "^4.3.14",
"@types/mocha": "^10.0.6",
"@types/node": "^18.16.0",
+ "@zk-kit/baby-jubjub": "^1.0.3",
"bignumber.js": "^9.1.2",
"chai": "^4.4.1",
"circomlibjs": "^0.1.7",
@@ -125,6 +128,17 @@
"node": ">=12"
}
},
+ "node_modules/@distributedlab/circom-parser": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/@distributedlab/circom-parser/-/circom-parser-0.2.6.tgz",
+ "integrity": "sha512-bAjcvZJ+sZMaTJtPh1RBZ3JlogCkZvywXPlSw44XzL8j6y3PbEEz9P0bz+RKx0QlsvO90T8f6Jnv0H9fpApM0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "antlr4": "4.13.1-patch-1",
+ "ejs": "3.1.10"
+ }
+ },
"node_modules/@ensdomains/address-encoder": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/@ensdomains/address-encoder/-/address-encoder-0.1.9.tgz",
@@ -1000,11 +1014,58 @@
"node": ">=14"
}
},
+ "node_modules/@iden3/bigarray": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@iden3/bigarray/-/bigarray-0.0.2.tgz",
+ "integrity": "sha512-Xzdyxqm1bOFF6pdIsiHLLl3HkSLjbhqJHVyqaTxXt3RqXBEnmsUmEW47H7VOi/ak7TdkRpNkxjyK5Zbkm+y52g==",
+ "dev": true,
+ "license": "GPL-3.0"
+ },
+ "node_modules/@iden3/binfileutils": {
+ "version": "0.0.12",
+ "resolved": "https://registry.npmjs.org/@iden3/binfileutils/-/binfileutils-0.0.12.tgz",
+ "integrity": "sha512-naAmzuDufRIcoNfQ1d99d7hGHufLA3wZSibtr4dMe6ZeiOPV1KwOZWTJ1YVz4HbaWlpDuzVU72dS4ATQS4PXBQ==",
+ "dev": true,
+ "license": "GPL-3.0",
+ "dependencies": {
+ "fastfile": "0.0.20",
+ "ffjavascript": "^0.3.0"
+ }
+ },
+ "node_modules/@iden3/binfileutils/node_modules/ffjavascript": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.3.1.tgz",
+ "integrity": "sha512-4PbK1WYodQtuF47D4pRI5KUg3Q392vuP5WjE1THSnceHdXwU3ijaoS0OqxTzLknCtz4Z2TtABzkBdBdMn3B/Aw==",
+ "dev": true,
+ "license": "GPL-3.0",
+ "dependencies": {
+ "wasmbuilder": "0.0.16",
+ "wasmcurves": "0.2.2",
+ "web-worker": "1.2.0"
+ }
+ },
"node_modules/@iden3/js-crypto": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@iden3/js-crypto/-/js-crypto-1.1.0.tgz",
- "integrity": "sha512-MbL7OpOxBoCybAPoorxrp+fwjDVESyDe6giIWxErjEIJy0Q2n1DU4VmKh4vDoCyhJx/RdVgT8Dkb59lKwISqsw==",
- "dev": true
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@iden3/js-crypto/-/js-crypto-1.3.2.tgz",
+ "integrity": "sha512-B1Fk8NLIhvEahFf02VKkmmXrw6Q+n+b3pbiFUYA9eesgoTZTSPtYtb+8A2jIKZrfdYKS5PlKJC8nNWmHUGk+6w==",
+ "dev": true,
+ "license": "AGPL-3.0",
+ "dependencies": {
+ "@noble/hashes": "^2.0.1"
+ }
+ },
+ "node_modules/@iden3/js-crypto/node_modules/@noble/hashes": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz",
+ "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
@@ -2035,16 +2096,18 @@
"dev": true
},
"node_modules/@openzeppelin/contracts": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.0.2.tgz",
- "integrity": "sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA=="
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.4.0.tgz",
+ "integrity": "sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A==",
+ "license": "MIT"
},
"node_modules/@openzeppelin/contracts-upgradeable": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.0.2.tgz",
- "integrity": "sha512-0MmkHSHiW2NRFiT9/r5Lu4eJq5UJ4/tzlOgYXNAIj/ONkQTVnz22pLxDvp4C4uZ9he7ZFvGn3Driptn1/iU7tQ==",
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.4.0.tgz",
+ "integrity": "sha512-STJKyDzUcYuB35Zub1JpWW58JxvrFFVgQ+Ykdr8A9PGXgtq/obF5uoh07k2XmFyPxfnZdPdBdhkJ/n2YxJ87HQ==",
+ "license": "MIT",
"peerDependencies": {
- "@openzeppelin/contracts": "5.0.2"
+ "@openzeppelin/contracts": "5.4.0"
}
},
"node_modules/@pkgjs/parseargs": {
@@ -2306,6 +2369,12 @@
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
+ "node_modules/@solarity/circom-lib": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/@solarity/circom-lib/-/circom-lib-0.2.2.tgz",
+ "integrity": "sha512-HZWY6DO39Ng/DTrGcpU8jnnB1130gr91MITtUX33HGLZrNfe8h8D6Jw7gJ0917dHxbYy963Di7VtOSxJg4itDQ==",
+ "license": "MIT"
+ },
"node_modules/@solarity/hardhat-gobind": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@solarity/hardhat-gobind/-/hardhat-gobind-1.2.2.tgz",
@@ -2352,28 +2421,223 @@
"typechain": "^8.0.0"
}
},
+ "node_modules/@solarity/hardhat-zkit": {
+ "version": "0.5.17",
+ "resolved": "https://registry.npmjs.org/@solarity/hardhat-zkit/-/hardhat-zkit-0.5.17.tgz",
+ "integrity": "sha512-3Xd+92EEs8nSzqc3AVFosoc/2S4NF40la5vlD8yMKTz+uqYwwkqQJRVIbgIrVoZt4qUN+MZW5SMq/cxwIM/w1g==",
+ "dev": true,
+ "license": "MIT",
+ "workspaces": [
+ "test/fixture-projects/*"
+ ],
+ "dependencies": {
+ "@distributedlab/circom-parser": "0.2.6",
+ "@solarity/zkit": "0.3.7",
+ "@solarity/zktype": "0.4.6",
+ "@wasmer/wasi": "0.12.0",
+ "chalk": "4.1.2",
+ "cli-progress": "3.12.0",
+ "cli-table3": "0.6.5",
+ "debug": "4.3.5",
+ "is-typed-array": "1.1.13",
+ "lodash": "4.17.21",
+ "ora": "5.4.1",
+ "path-browserify": "1.0.1",
+ "resolve": "1.22.8",
+ "semver": "7.6.3",
+ "snarkjs": "0.7.5",
+ "uuid": "9.0.1",
+ "zod": "3.23.8"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "hardhat": "^2.16.0"
+ }
+ },
+ "node_modules/@solarity/hardhat-zkit/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@solarity/hardhat-zkit/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/@solarity/hardhat-zkit/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/@solarity/hardhat-zkit/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@solarity/hardhat-zkit/node_modules/debug": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
+ "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@solarity/hardhat-zkit/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@solarity/hardhat-zkit/node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@solarity/hardhat-zkit/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/@solarity/solidity-lib": {
- "version": "2.7.11",
- "resolved": "https://registry.npmjs.org/@solarity/solidity-lib/-/solidity-lib-2.7.11.tgz",
- "integrity": "sha512-iQCh/Rx+ha9TX9+x9OrRUnn0cNkR9m3Dg8aTFnE5WrKBr0rAU/RbnowSMcBl6VsSH3iAo81AOf9Z4p8ZOvhdaQ==",
+ "version": "3.2.8",
+ "resolved": "https://registry.npmjs.org/@solarity/solidity-lib/-/solidity-lib-3.2.8.tgz",
+ "integrity": "sha512-tqU8WW0Zn+jiAy/RxB01K5hnHb+Ebse80J7Sb2P7M1Xey0cd4OPGF5ZVX8yChaofVYqmNTs7CPHTIhsnSoO/rw==",
+ "license": "MIT",
+ "dependencies": {
+ "@openzeppelin/contracts": "5.4.0",
+ "@openzeppelin/contracts-upgradeable": "5.4.0",
+ "solady": "0.1.24"
+ }
+ },
+ "node_modules/@solarity/zkit": {
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/@solarity/zkit/-/zkit-0.3.7.tgz",
+ "integrity": "sha512-8cWoViIJV3emLiSjhHujT0kTVd5NesqnUErIv9mKQv77kHb1DMq4SWmm8NvDxhNjKcOROHs8F8Koy6xO3uhb2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@iden3/binfileutils": "0.0.12",
+ "ejs": "3.1.10",
+ "ffjavascript": "0.3.1",
+ "readline": "1.3.0",
+ "snarkjs": "0.7.5"
+ }
+ },
+ "node_modules/@solarity/zkit/node_modules/ffjavascript": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.3.1.tgz",
+ "integrity": "sha512-4PbK1WYodQtuF47D4pRI5KUg3Q392vuP5WjE1THSnceHdXwU3ijaoS0OqxTzLknCtz4Z2TtABzkBdBdMn3B/Aw==",
+ "dev": true,
+ "license": "GPL-3.0",
"dependencies": {
- "@openzeppelin/contracts": "4.9.6",
- "@openzeppelin/contracts-upgradeable": "4.9.6",
- "@uniswap/v2-core": "1.0.1",
- "@uniswap/v2-periphery": "1.1.0-beta.0",
- "@uniswap/v3-core": "1.0.1",
- "@uniswap/v3-periphery": "1.4.4"
+ "wasmbuilder": "0.0.16",
+ "wasmcurves": "0.2.2",
+ "web-worker": "1.2.0"
}
},
- "node_modules/@solarity/solidity-lib/node_modules/@openzeppelin/contracts": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.6.tgz",
- "integrity": "sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA=="
+ "node_modules/@solarity/zktype": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/@solarity/zktype/-/zktype-0.4.6.tgz",
+ "integrity": "sha512-LSZStVegnycT+Gt06cyrZNFNzzZtdEtvldlIDfAIpWMBowsyTXEvEmA26+Eyp/ZqjGokENxNxUDwJ3k6OZd05g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ejs": "3.1.10",
+ "prettier": "3.4.2",
+ "typescript": "5.5.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@solarity/zkit": "^0.3.7"
+ }
},
- "node_modules/@solarity/solidity-lib/node_modules/@openzeppelin/contracts-upgradeable": {
- "version": "4.9.6",
- "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.6.tgz",
- "integrity": "sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA=="
+ "node_modules/@solarity/zktype/node_modules/prettier": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
+ "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
},
"node_modules/@solidity-parser/parser": {
"version": "0.18.0",
@@ -3436,76 +3700,62 @@
"@types/node": "*"
}
},
- "node_modules/@uniswap/lib": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@uniswap/lib/-/lib-1.1.1.tgz",
- "integrity": "sha512-2yK7sLpKIT91TiS5sewHtOa7YuM8IuBXVl4GZv2jZFys4D2sY7K5vZh6MqD25TPA95Od+0YzCVq6cTF2IKrOmg==",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@uniswap/v2-core": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.1.tgz",
- "integrity": "sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@uniswap/v2-periphery": {
- "version": "1.1.0-beta.0",
- "resolved": "https://registry.npmjs.org/@uniswap/v2-periphery/-/v2-periphery-1.1.0-beta.0.tgz",
- "integrity": "sha512-6dkwAMKza8nzqYiXEr2D86dgW3TTavUvCR0w2Tu33bAbM8Ah43LKAzH7oKKPRT5VJQaMi1jtkGs1E8JPor1n5g==",
+ "node_modules/@wasmer/wasi": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/@wasmer/wasi/-/wasi-0.12.0.tgz",
+ "integrity": "sha512-FJhLZKAfLWm/yjQI7eCRHNbA8ezmb7LSpUYFkHruZXs2mXk2+DaQtSElEtOoNrVQ4vApTyVaAd5/b7uEu8w6wQ==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "@uniswap/lib": "1.1.1",
- "@uniswap/v2-core": "1.0.0"
- },
- "engines": {
- "node": ">=10"
+ "browser-process-hrtime": "^1.0.0",
+ "buffer-es6": "^4.9.3",
+ "path-browserify": "^1.0.0",
+ "randomfill": "^1.0.4"
}
},
- "node_modules/@uniswap/v2-periphery/node_modules/@uniswap/v2-core": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.0.tgz",
- "integrity": "sha512-BJiXrBGnN8mti7saW49MXwxDBRFiWemGetE58q8zgfnPPzQKq55ADltEILqOt6VFZ22kVeVKbF8gVd8aY3l7pA==",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@uniswap/v3-core": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@uniswap/v3-core/-/v3-core-1.0.1.tgz",
- "integrity": "sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ==",
- "engines": {
- "node": ">=10"
+ "node_modules/@zk-kit/baby-jubjub": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@zk-kit/baby-jubjub/-/baby-jubjub-1.0.3.tgz",
+ "integrity": "sha512-Wl+QfV6XGOMk1yU2JTqHXeKWfJVXp83is0+dtqfj9wx4wsAPpb+qzYvwAxW5PBx5/Nu71Bh7jp/5vM+6QgHSwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@zk-kit/utils": "1.2.1"
}
},
- "node_modules/@uniswap/v3-periphery": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/@uniswap/v3-periphery/-/v3-periphery-1.4.4.tgz",
- "integrity": "sha512-S4+m+wh8HbWSO3DKk4LwUCPZJTpCugIsHrWR86m/OrUyvSqGDTXKFfc2sMuGXCZrD1ZqO3rhQsKgdWg3Hbb2Kw==",
+ "node_modules/@zk-kit/utils": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@zk-kit/utils/-/utils-1.2.1.tgz",
+ "integrity": "sha512-H2nTsyWdicVOyvqC5AjgU7tsTgmR6PDrruFJNmlmdhKp7RxEia/E1B1swMZjaasYa2QMp4Zc6oB7cWchty7B2Q==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "@openzeppelin/contracts": "3.4.2-solc-0.7",
- "@uniswap/lib": "^4.0.1-alpha",
- "@uniswap/v2-core": "^1.0.1",
- "@uniswap/v3-core": "^1.0.0",
- "base64-sol": "1.0.1"
- },
- "engines": {
- "node": ">=10"
+ "buffer": "^6.0.3"
}
},
- "node_modules/@uniswap/v3-periphery/node_modules/@openzeppelin/contracts": {
- "version": "3.4.2-solc-0.7",
- "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz",
- "integrity": "sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA=="
- },
- "node_modules/@uniswap/v3-periphery/node_modules/@uniswap/lib": {
- "version": "4.0.1-alpha",
- "resolved": "https://registry.npmjs.org/@uniswap/lib/-/lib-4.0.1-alpha.tgz",
- "integrity": "sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==",
- "engines": {
- "node": ">=10"
+ "node_modules/@zk-kit/utils/node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
}
},
"node_modules/abbrev": {
@@ -3950,11 +4200,6 @@
}
]
},
- "node_modules/base64-sol": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/base64-sol/-/base64-sol-1.0.1.tgz",
- "integrity": "sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg=="
- },
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@@ -3976,6 +4221,23 @@
"integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==",
"dev": true
},
+ "node_modules/bfj": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.1.0.tgz",
+ "integrity": "sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bluebird": "^3.7.2",
+ "check-types": "^11.2.3",
+ "hoopy": "^0.1.4",
+ "jsonpath": "^1.1.1",
+ "tryer": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
"node_modules/big-integer": {
"version": "1.6.36",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.36.tgz",
@@ -4043,6 +4305,7 @@
"integrity": "sha512-Igj8YowDu1PRkRsxZA7NVkdFNxH5rKv5cpLxQ0CVXSIA77pVYwCPRQJ2sMew/oneUpfuYRyjG6r8SmmmnbZb1w==",
"dev": true,
"hasInstallScript": true,
+ "license": "MIT",
"dependencies": {
"node-addon-api": "^3.0.0",
"node-gyp-build": "^4.2.2",
@@ -4056,13 +4319,15 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz",
"integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/blake2b": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/blake2b/-/blake2b-2.1.4.tgz",
"integrity": "sha512-AyBuuJNI64gIvwx13qiICz6H6hpmjvYS5DGkG6jbXMOT8Z3WUJ3V1X0FlhIoT1b/5JtHE3ki+xjtMvu1nn+t9A==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"blake2b-wasm": "^2.4.0",
"nanoassert": "^2.0.0"
@@ -4271,6 +4536,13 @@
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="
},
+ "node_modules/browser-process-hrtime": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
+ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
"node_modules/browser-stdout": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
@@ -4331,6 +4603,13 @@
"ieee754": "^1.1.13"
}
},
+ "node_modules/buffer-es6": {
+ "version": "4.9.3",
+ "resolved": "https://registry.npmjs.org/buffer-es6/-/buffer-es6-4.9.3.tgz",
+ "integrity": "sha512-Ibt+oXxhmeYJSsCkODPqNpPmyegefiD8rfutH1NYGhMZQhSp95Rz7haemgnJ6dxa6LT+JLLbtgOMORRluwKktw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -4550,6 +4829,13 @@
"node": "*"
}
},
+ "node_modules/check-types": {
+ "version": "11.2.3",
+ "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz",
+ "integrity": "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cheerio": {
"version": "1.0.0-rc.12",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
@@ -4656,26 +4942,813 @@
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
"dependencies": {
- "inherits": "^2.0.1",
- "safe-buffer": "^5.0.1"
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/circom_runtime": {
+ "version": "0.1.28",
+ "resolved": "https://registry.npmjs.org/circom_runtime/-/circom_runtime-0.1.28.tgz",
+ "integrity": "sha512-ACagpQ7zBRLKDl5xRZ4KpmYIcZDUjOiNRuxvXLqhnnlLSVY1Dbvh73TI853nqoR0oEbihtWmMSjgc5f+pXf/jQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "ffjavascript": "0.3.1"
+ },
+ "bin": {
+ "calcwit": "calcwit.js"
+ }
+ },
+ "node_modules/circom_runtime/node_modules/ffjavascript": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.3.1.tgz",
+ "integrity": "sha512-4PbK1WYodQtuF47D4pRI5KUg3Q392vuP5WjE1THSnceHdXwU3ijaoS0OqxTzLknCtz4Z2TtABzkBdBdMn3B/Aw==",
+ "dev": true,
+ "license": "GPL-3.0",
+ "dependencies": {
+ "wasmbuilder": "0.0.16",
+ "wasmcurves": "0.2.2",
+ "web-worker": "1.2.0"
+ }
+ },
+ "node_modules/circomlibjs": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/circomlibjs/-/circomlibjs-0.1.7.tgz",
+ "integrity": "sha512-GRAUoAlKAsiiTa+PA725G9RmEmJJRc8tRFxw/zKktUxlQISGznT4hH4ESvW8FNTsrGg/nNd06sGP/Wlx0LUHVg==",
+ "dev": true,
+ "license": "GPL-3.0",
+ "dependencies": {
+ "blake-hash": "^2.0.0",
+ "blake2b": "^2.1.3",
+ "ethers": "^5.5.1",
+ "ffjavascript": "^0.2.45"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/abi": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.8.0.tgz",
+ "integrity": "sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/address": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/constants": "^5.8.0",
+ "@ethersproject/hash": "^5.8.0",
+ "@ethersproject/keccak256": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/abstract-provider": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.8.0.tgz",
+ "integrity": "sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/networks": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/transactions": "^5.8.0",
+ "@ethersproject/web": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/abstract-signer": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.8.0.tgz",
+ "integrity": "sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abstract-provider": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/address": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.8.0.tgz",
+ "integrity": "sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/keccak256": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/rlp": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/base64": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.8.0.tgz",
+ "integrity": "sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/basex": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.8.0.tgz",
+ "integrity": "sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/bignumber": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.8.0.tgz",
+ "integrity": "sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "bn.js": "^5.2.1"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/bytes": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz",
+ "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/logger": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/constants": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.8.0.tgz",
+ "integrity": "sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/contracts": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.8.0.tgz",
+ "integrity": "sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abi": "^5.8.0",
+ "@ethersproject/abstract-provider": "^5.8.0",
+ "@ethersproject/abstract-signer": "^5.8.0",
+ "@ethersproject/address": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/constants": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/transactions": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/hash": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.8.0.tgz",
+ "integrity": "sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abstract-signer": "^5.8.0",
+ "@ethersproject/address": "^5.8.0",
+ "@ethersproject/base64": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/keccak256": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/hdnode": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.8.0.tgz",
+ "integrity": "sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abstract-signer": "^5.8.0",
+ "@ethersproject/basex": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/pbkdf2": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/sha2": "^5.8.0",
+ "@ethersproject/signing-key": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0",
+ "@ethersproject/transactions": "^5.8.0",
+ "@ethersproject/wordlists": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/json-wallets": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.8.0.tgz",
+ "integrity": "sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abstract-signer": "^5.8.0",
+ "@ethersproject/address": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/hdnode": "^5.8.0",
+ "@ethersproject/keccak256": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/pbkdf2": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/random": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0",
+ "@ethersproject/transactions": "^5.8.0",
+ "aes-js": "3.0.0",
+ "scrypt-js": "3.0.1"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/keccak256": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz",
+ "integrity": "sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "js-sha3": "0.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/logger": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz",
+ "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/networks": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.8.0.tgz",
+ "integrity": "sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/logger": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/pbkdf2": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.8.0.tgz",
+ "integrity": "sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/sha2": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/properties": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.8.0.tgz",
+ "integrity": "sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/logger": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/providers": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.8.0.tgz",
+ "integrity": "sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abstract-provider": "^5.8.0",
+ "@ethersproject/abstract-signer": "^5.8.0",
+ "@ethersproject/address": "^5.8.0",
+ "@ethersproject/base64": "^5.8.0",
+ "@ethersproject/basex": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/constants": "^5.8.0",
+ "@ethersproject/hash": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/networks": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/random": "^5.8.0",
+ "@ethersproject/rlp": "^5.8.0",
+ "@ethersproject/sha2": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0",
+ "@ethersproject/transactions": "^5.8.0",
+ "@ethersproject/web": "^5.8.0",
+ "bech32": "1.1.4",
+ "ws": "8.18.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/random": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.8.0.tgz",
+ "integrity": "sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/rlp": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.8.0.tgz",
+ "integrity": "sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/sha2": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.8.0.tgz",
+ "integrity": "sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "hash.js": "1.1.7"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/signing-key": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.8.0.tgz",
+ "integrity": "sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "bn.js": "^5.2.1",
+ "elliptic": "6.6.1",
+ "hash.js": "1.1.7"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/solidity": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.8.0.tgz",
+ "integrity": "sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/keccak256": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/sha2": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/strings": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.8.0.tgz",
+ "integrity": "sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/constants": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/transactions": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.8.0.tgz",
+ "integrity": "sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/address": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/constants": "^5.8.0",
+ "@ethersproject/keccak256": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/rlp": "^5.8.0",
+ "@ethersproject/signing-key": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/units": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.8.0.tgz",
+ "integrity": "sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/constants": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/wallet": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.8.0.tgz",
+ "integrity": "sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abstract-provider": "^5.8.0",
+ "@ethersproject/abstract-signer": "^5.8.0",
+ "@ethersproject/address": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/hash": "^5.8.0",
+ "@ethersproject/hdnode": "^5.8.0",
+ "@ethersproject/json-wallets": "^5.8.0",
+ "@ethersproject/keccak256": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/random": "^5.8.0",
+ "@ethersproject/signing-key": "^5.8.0",
+ "@ethersproject/transactions": "^5.8.0",
+ "@ethersproject/wordlists": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/web": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.8.0.tgz",
+ "integrity": "sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/base64": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/@ethersproject/wordlists": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.8.0.tgz",
+ "integrity": "sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/hash": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0"
}
},
- "node_modules/circomlibjs": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/circomlibjs/-/circomlibjs-0.1.7.tgz",
- "integrity": "sha512-GRAUoAlKAsiiTa+PA725G9RmEmJJRc8tRFxw/zKktUxlQISGznT4hH4ESvW8FNTsrGg/nNd06sGP/Wlx0LUHVg==",
+ "node_modules/circomlibjs/node_modules/aes-js": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz",
+ "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/circomlibjs/node_modules/elliptic": {
+ "version": "6.6.1",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz",
+ "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "blake-hash": "^2.0.0",
- "blake2b": "^2.1.3",
- "ethers": "^5.5.1",
- "ffjavascript": "^0.2.45"
+ "bn.js": "^4.11.9",
+ "brorand": "^1.1.0",
+ "hash.js": "^1.0.0",
+ "hmac-drbg": "^1.0.1",
+ "inherits": "^2.0.4",
+ "minimalistic-assert": "^1.0.1",
+ "minimalistic-crypto-utils": "^1.0.1"
}
},
+ "node_modules/circomlibjs/node_modules/elliptic/node_modules/bn.js": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
+ "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/circomlibjs/node_modules/ethers": {
- "version": "5.7.2",
- "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz",
- "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==",
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.8.0.tgz",
+ "integrity": "sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg==",
"dev": true,
"funding": [
{
@@ -4687,37 +5760,60 @@
"url": "https://www.buymeacoffee.com/ricmoo"
}
],
+ "license": "MIT",
"dependencies": {
- "@ethersproject/abi": "5.7.0",
- "@ethersproject/abstract-provider": "5.7.0",
- "@ethersproject/abstract-signer": "5.7.0",
- "@ethersproject/address": "5.7.0",
- "@ethersproject/base64": "5.7.0",
- "@ethersproject/basex": "5.7.0",
- "@ethersproject/bignumber": "5.7.0",
- "@ethersproject/bytes": "5.7.0",
- "@ethersproject/constants": "5.7.0",
- "@ethersproject/contracts": "5.7.0",
- "@ethersproject/hash": "5.7.0",
- "@ethersproject/hdnode": "5.7.0",
- "@ethersproject/json-wallets": "5.7.0",
- "@ethersproject/keccak256": "5.7.0",
- "@ethersproject/logger": "5.7.0",
- "@ethersproject/networks": "5.7.1",
- "@ethersproject/pbkdf2": "5.7.0",
- "@ethersproject/properties": "5.7.0",
- "@ethersproject/providers": "5.7.2",
- "@ethersproject/random": "5.7.0",
- "@ethersproject/rlp": "5.7.0",
- "@ethersproject/sha2": "5.7.0",
- "@ethersproject/signing-key": "5.7.0",
- "@ethersproject/solidity": "5.7.0",
- "@ethersproject/strings": "5.7.0",
- "@ethersproject/transactions": "5.7.0",
- "@ethersproject/units": "5.7.0",
- "@ethersproject/wallet": "5.7.0",
- "@ethersproject/web": "5.7.1",
- "@ethersproject/wordlists": "5.7.0"
+ "@ethersproject/abi": "5.8.0",
+ "@ethersproject/abstract-provider": "5.8.0",
+ "@ethersproject/abstract-signer": "5.8.0",
+ "@ethersproject/address": "5.8.0",
+ "@ethersproject/base64": "5.8.0",
+ "@ethersproject/basex": "5.8.0",
+ "@ethersproject/bignumber": "5.8.0",
+ "@ethersproject/bytes": "5.8.0",
+ "@ethersproject/constants": "5.8.0",
+ "@ethersproject/contracts": "5.8.0",
+ "@ethersproject/hash": "5.8.0",
+ "@ethersproject/hdnode": "5.8.0",
+ "@ethersproject/json-wallets": "5.8.0",
+ "@ethersproject/keccak256": "5.8.0",
+ "@ethersproject/logger": "5.8.0",
+ "@ethersproject/networks": "5.8.0",
+ "@ethersproject/pbkdf2": "5.8.0",
+ "@ethersproject/properties": "5.8.0",
+ "@ethersproject/providers": "5.8.0",
+ "@ethersproject/random": "5.8.0",
+ "@ethersproject/rlp": "5.8.0",
+ "@ethersproject/sha2": "5.8.0",
+ "@ethersproject/signing-key": "5.8.0",
+ "@ethersproject/solidity": "5.8.0",
+ "@ethersproject/strings": "5.8.0",
+ "@ethersproject/transactions": "5.8.0",
+ "@ethersproject/units": "5.8.0",
+ "@ethersproject/wallet": "5.8.0",
+ "@ethersproject/web": "5.8.0",
+ "@ethersproject/wordlists": "5.8.0"
+ }
+ },
+ "node_modules/circomlibjs/node_modules/ws": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
}
},
"node_modules/class-is": {
@@ -4757,6 +5853,19 @@
"node": ">=8"
}
},
+ "node_modules/cli-progress": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz",
+ "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "string-width": "^4.2.3"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/cli-spinners": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
@@ -4770,10 +5879,11 @@
}
},
"node_modules/cli-table3": {
- "version": "0.6.4",
- "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.4.tgz",
- "integrity": "sha512-Lm3L0p+/npIQWNIiyF/nAn7T5dnOwR3xNTHXYEBFBFVPXzCVNZ5lqEC/1eo/EVfpDsQ1I+TX4ORPQgp+UI0CRw==",
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz",
+ "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"string-width": "^4.2.0"
},
@@ -5590,6 +6700,22 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"dev": true
},
+ "node_modules/ejs": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+ "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jake": "^10.8.5"
+ },
+ "bin": {
+ "ejs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/elliptic": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
@@ -6435,6 +7561,13 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
+ "node_modules/fastfile": {
+ "version": "0.0.20",
+ "resolved": "https://registry.npmjs.org/fastfile/-/fastfile-0.0.20.tgz",
+ "integrity": "sha512-r5ZDbgImvVWCP0lA/cGNgQcZqR+aYdFx3u+CtJqUE510pBUVGMn4ulL/iRTI4tACTYsNJ736uzFxEBXesPAktA==",
+ "dev": true,
+ "license": "GPL-3.0"
+ },
"node_modules/fastq": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
@@ -6449,12 +7582,46 @@
"resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.63.tgz",
"integrity": "sha512-dBgdsfGks58b66JnUZeZpGxdMIDQ4QsD3VYlRJyFVrKQHb2kJy4R2gufx5oetrTxXPT+aEjg0dOvOLg1N0on4A==",
"dev": true,
+ "license": "GPL-3.0",
"dependencies": {
"wasmbuilder": "0.0.16",
"wasmcurves": "0.2.2",
"web-worker": "1.2.0"
}
},
+ "node_modules/filelist": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
+ "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "minimatch": "^5.0.1"
+ }
+ },
+ "node_modules/filelist/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/filelist/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -7692,6 +8859,16 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
+ "node_modules/hoopy": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
+ "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
"node_modules/hosted-git-info": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
@@ -8044,6 +9221,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-data-view": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz",
@@ -8363,6 +9556,31 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
+ "node_modules/jake": {
+ "version": "10.9.4",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
+ "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "async": "^3.2.6",
+ "filelist": "^1.0.4",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "jake": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jake/node_modules/async": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/js-sdsl": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.2.tgz",
@@ -8459,6 +9677,38 @@
"graceful-fs": "^4.1.6"
}
},
+ "node_modules/jsonpath": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz",
+ "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esprima": "1.2.2",
+ "static-eval": "2.0.2",
+ "underscore": "1.12.1"
+ }
+ },
+ "node_modules/jsonpath/node_modules/esprima": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz",
+ "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==",
+ "dev": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/jsonpath/node_modules/underscore": {
+ "version": "1.12.1",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz",
+ "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/jsonschema": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz",
@@ -8749,6 +9999,13 @@
"node": ">=8"
}
},
+ "node_modules/logplease": {
+ "version": "1.2.15",
+ "resolved": "https://registry.npmjs.org/logplease/-/logplease-1.2.15.tgz",
+ "integrity": "sha512-jLlHnlsPSJjpwUfcNyUxXCl33AYg2cHhIf9QhGL2T4iPT0XPB+xP1LRKFPgIg1M/sg9kAJvy94w9CzBNrfnstA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/loupe": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
@@ -9915,6 +11172,13 @@
"upper-case-first": "^1.1.0"
}
},
+ "node_modules/path-browserify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
+ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/path-case": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/path-case/-/path-case-2.1.1.tgz",
@@ -10025,10 +11289,11 @@
"dev": true
},
"node_modules/picocolors": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
- "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
- "dev": true
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -10293,6 +11558,31 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/r1csfile": {
+ "version": "0.0.48",
+ "resolved": "https://registry.npmjs.org/r1csfile/-/r1csfile-0.0.48.tgz",
+ "integrity": "sha512-kHRkKUJNaor31l05f2+RFzvcH5XSa7OfEfd/l4hzjte6NL6fjRkSMfZ4BjySW9wmfdwPOtq3mXurzPvPGEf5Tw==",
+ "dev": true,
+ "license": "GPL-3.0",
+ "dependencies": {
+ "@iden3/bigarray": "0.0.2",
+ "@iden3/binfileutils": "0.0.12",
+ "fastfile": "0.0.20",
+ "ffjavascript": "0.3.0"
+ }
+ },
+ "node_modules/r1csfile/node_modules/ffjavascript": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.3.0.tgz",
+ "integrity": "sha512-l7sR5kmU3gRwDy8g0Z2tYBXy5ttmafRPFOqY7S6af5cq51JqJWt5eQ/lSR/rs2wQNbDYaYlQr5O+OSUf/oMLoQ==",
+ "dev": true,
+ "license": "GPL-3.0",
+ "dependencies": {
+ "wasmbuilder": "0.0.16",
+ "wasmcurves": "0.2.2",
+ "web-worker": "1.2.0"
+ }
+ },
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -10301,6 +11591,17 @@
"safe-buffer": "^5.1.0"
}
},
+ "node_modules/randomfill": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "randombytes": "^2.0.5",
+ "safe-buffer": "^5.1.0"
+ }
+ },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -10447,6 +11748,13 @@
"node": ">=8.10.0"
}
},
+ "node_modules/readline": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz",
+ "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==",
+ "dev": true,
+ "license": "BSD"
+ },
"node_modules/rechoir": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
@@ -10949,13 +12257,11 @@
}
},
"node_modules/semver": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
- "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"dev": true,
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
+ "license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
@@ -10963,24 +12269,6 @@
"node": ">=10"
}
},
- "node_modules/semver/node_modules/lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/semver/node_modules/yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
- },
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
@@ -11363,6 +12651,46 @@
"no-case": "^2.2.0"
}
},
+ "node_modules/snarkjs": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/snarkjs/-/snarkjs-0.7.5.tgz",
+ "integrity": "sha512-h+3c4rXZKLhLuHk4LHydZCk/h5GcNvk5GjVKRRkHmfb6Ntf8gHOA9zea3g656iclRuhqQ3iKDWFgiD9ypLrKiA==",
+ "dev": true,
+ "license": "GPL-3.0",
+ "dependencies": {
+ "@iden3/binfileutils": "0.0.12",
+ "bfj": "^7.0.2",
+ "blake2b-wasm": "^2.4.0",
+ "circom_runtime": "0.1.28",
+ "ejs": "^3.1.6",
+ "fastfile": "0.0.20",
+ "ffjavascript": "0.3.1",
+ "js-sha3": "^0.8.0",
+ "logplease": "^1.2.15",
+ "r1csfile": "0.0.48"
+ },
+ "bin": {
+ "snarkjs": "build/cli.cjs"
+ }
+ },
+ "node_modules/snarkjs/node_modules/ffjavascript": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.3.1.tgz",
+ "integrity": "sha512-4PbK1WYodQtuF47D4pRI5KUg3Q392vuP5WjE1THSnceHdXwU3ijaoS0OqxTzLknCtz4Z2TtABzkBdBdMn3B/Aw==",
+ "dev": true,
+ "license": "GPL-3.0",
+ "dependencies": {
+ "wasmbuilder": "0.0.16",
+ "wasmcurves": "0.2.2",
+ "web-worker": "1.2.0"
+ }
+ },
+ "node_modules/solady": {
+ "version": "0.1.24",
+ "resolved": "https://registry.npmjs.org/solady/-/solady-0.1.24.tgz",
+ "integrity": "sha512-uFTtYane4KMn2Tbth+7f8svTOrQ5+SUksFyTA9Vqwffwxak7OduHZxBYxSz34foBGnbsKtleM2FbgiAP1NYB1A==",
+ "license": "MIT"
+ },
"node_modules/solc": {
"version": "0.4.26",
"resolved": "https://registry.npmjs.org/solc/-/solc-0.4.26.tgz",
@@ -11916,6 +13244,16 @@
"node": ">=8"
}
},
+ "node_modules/static-eval": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz",
+ "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escodegen": "^1.8.1"
+ }
+ },
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -12158,6 +13496,19 @@
"node": ">=4"
}
},
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/swap-case": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz",
@@ -12550,6 +13901,13 @@
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"dev": true
},
+ "node_modules/tryer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
+ "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/ts-command-line-args": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz",
@@ -12936,9 +14294,10 @@
}
},
"node_modules/typescript": {
- "version": "5.4.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
- "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
+ "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
+ "license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -14294,6 +15653,16 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zod": {
+ "version": "3.23.8",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
+ "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/package.json b/package.json
index bae5500..06c9b2c 100644
--- a/package.json
+++ b/package.json
@@ -30,14 +30,16 @@
"lint-sol-fix": "prettier --write \"contracts/**/*.sol\""
},
"dependencies": {
- "@openzeppelin/contracts": "5.0.2",
- "@openzeppelin/contracts-upgradeable": "5.0.2",
- "@solarity/solidity-lib": "2.7.11",
+ "@openzeppelin/contracts": "5.4.0",
+ "@openzeppelin/contracts-upgradeable": "5.4.0",
+ "@solarity/circom-lib": "0.2.2",
+ "@solarity/solidity-lib": "3.2.8",
"dotenv": "16.4.5",
"hardhat": "2.20.1",
"typechain": "8.3.2"
},
"devDependencies": {
+ "@iden3/js-crypto": "^1.1.0",
"@metamask/eth-sig-util": "^7.0.1",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.6",
"@nomicfoundation/hardhat-ethers": "^3.0.5",
@@ -45,13 +47,16 @@
"@solarity/hardhat-gobind": "^1.2.2",
"@solarity/hardhat-markup": "^1.0.7",
"@solarity/hardhat-migrate": "^2.1.7",
+ "@solarity/hardhat-zkit": "^0.5.17",
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"@types/chai": "^4.3.14",
"@types/mocha": "^10.0.6",
"@types/node": "^18.16.0",
+ "@zk-kit/baby-jubjub": "^1.0.3",
"bignumber.js": "^9.1.2",
"chai": "^4.4.1",
+ "circomlibjs": "^0.1.7",
"ethers": "^6.11.1",
"hardhat-contract-sizer": "^2.10.0",
"hardhat-gas-reporter": "^2.0.2",
@@ -64,8 +69,6 @@
"solidity-coverage": "^0.8.11",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
- "typescript": "^5.4.3",
- "circomlibjs": "^0.1.7",
- "@iden3/js-crypto": "^1.1.0"
+ "typescript": "^5.4.3"
}
}
diff --git a/test/ZKMultisig.tests.ts b/test/ZKMultisig.tests.ts
index 0910689..4cdeefc 100644
--- a/test/ZKMultisig.tests.ts
+++ b/test/ZKMultisig.tests.ts
@@ -2,103 +2,110 @@ import { PRECISION, ZERO_ADDR } from "@/scripts/utils/constants";
import { Reverter } from "@/test/helpers/reverter";
import {
IZKMultisig,
- NegativeVerifierMock,
- PositiveVerifierMock,
- ZKMultisig,
- ZKMultisig__factory,
+ ProposalCreationGroth16Verifier,
+ VotingGroth16Verifier,
+ ZKMultisigMock,
ZKMultisigFactory,
} from "@ethers-v6";
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
import { expect } from "chai";
-import { randomBytes } from "crypto";
-import { AbiCoder, BytesLike } from "ethers";
import { ethers } from "hardhat";
-import { getPoseidon } from "./helpers";
-
-type ZKParams = IZKMultisig.ZKParamsStruct;
+import { getPoseidon, poseidonHash } from "./helpers";
+import { addPoint, mulPointEscalar } from "@zk-kit/baby-jubjub";
+import { zkit } from "hardhat";
+import { ProposalCreation } from "@zkit";
+import { CartesianMerkleTree, ED256 } from "@/generated-types/ethers/contracts/ZKMultisig";
+import APointStruct = ED256.APointStruct;
+import ProofStructOutput = CartesianMerkleTree.ProofStructOutput;
+import {
+ aggregateDecryptionKeyShares,
+ aggregatePoints,
+ aggregateVotes,
+ arrayToPoints,
+ createProposal,
+ encodePoint,
+ generateParticipants,
+ getBlinder,
+ getCumulativeKeys,
+ numberToArray,
+ parseNumberToBitsArray,
+ pointsToArray,
+ randomNumber,
+ vote,
+} from "@/test/helpers/zk-multisig";
+import { ZeroAddress } from "ethers";
type ProposalContent = IZKMultisig.ProposalContentStruct;
enum ProposalStatus {
NONE,
VOTING,
ACCEPTED,
- EXPIRED,
+ REJECTED,
EXECUTED,
}
describe("ZKMultisig", () => {
const reverter = new Reverter();
- const randomNumber = () => BigInt("0x" + randomBytes(32).toString("hex"));
const MIN_QUORUM = BigInt(80) * PRECISION;
- const DAY_IN_SECONDS = 60 * 60 * 24;
+
+ const proofSize = 40;
+
+ const randomZKParams = {
+ a: [randomNumber(), randomNumber()],
+ b: [
+ [randomNumber(), randomNumber()],
+ [randomNumber(), randomNumber()],
+ ],
+ c: [randomNumber(), randomNumber()],
+ inputs: [randomNumber(), randomNumber()],
+ };
let alice: SignerWithAddress;
- let positiveParticipantVerifier: PositiveVerifierMock;
- let negativeParticipantVerifier: NegativeVerifierMock;
+ let creationVerifier: ProposalCreationGroth16Verifier;
+ let votingVerifier: VotingGroth16Verifier;
- let zkMultisig: ZKMultisig;
+ let zkMultisig: ZKMultisigMock;
let zkMultisigFactory: ZKMultisigFactory;
- let initialParticipants: bigint[];
-
- let zkParams: ZKParams;
+ let initialParticipantsPerm: APointStruct[];
+ let initialParticipantsRot: APointStruct[];
let proposalContent: ProposalContent;
- const encode = (types: ReadonlyArray, values: ReadonlyArray): string => {
- return AbiCoder.defaultAbiCoder().encode(types, values);
- };
-
- const generateParticipants = (length: number) => {
- const participants: bigint[] = [];
- for (let i = 0; i < length; i++) {
- participants.push(randomNumber());
- }
-
- return participants;
- };
-
before(async () => {
[alice] = await ethers.getSigners();
- const positiveVerifier__factory = await ethers.getContractFactory("PositiveVerifierMock");
- positiveParticipantVerifier = await positiveVerifier__factory.deploy();
-
- await positiveParticipantVerifier.waitForDeployment();
-
- const negativeVerifier__factory = await ethers.getContractFactory("NegativeVerifierMock");
- negativeParticipantVerifier = await negativeVerifier__factory.deploy();
+ creationVerifier = await ethers.deployContract("ProposalCreationGroth16Verifier");
+ votingVerifier = await ethers.deployContract("VotingGroth16Verifier");
- await negativeParticipantVerifier.waitForDeployment();
-
- const zkMultisig__factory = await ethers.getContractFactory("ZKMultisig", {
+ const zkMultisigImpl = await ethers.deployContract("ZKMultisigMock", {
libraries: {
PoseidonUnit1L: await (await getPoseidon(1)).getAddress(),
+ PoseidonUnit3L: await (await getPoseidon(3)).getAddress(),
},
});
- const zkMultisigImpl = await zkMultisig__factory.deploy();
- await zkMultisigImpl.waitForDeployment();
+ zkMultisigFactory = await ethers.deployContract("ZKMultisigFactory");
- const zkMultisigFactory__factory = await ethers.getContractFactory("ZKMultisigFactory");
- zkMultisigFactory = await zkMultisigFactory__factory.deploy(
+ await zkMultisigFactory.initialize(
await zkMultisigImpl.getAddress(),
- await positiveParticipantVerifier.getAddress(),
+ await creationVerifier.getAddress(),
+ await votingVerifier.getAddress(),
);
- await zkMultisigFactory.waitForDeployment();
-
const salt = randomNumber();
- initialParticipants = generateParticipants(5);
+ [initialParticipantsPerm, initialParticipantsRot] = generateParticipants(5);
// create multisig
- await zkMultisigFactory.connect(alice).createMultisig(initialParticipants, MIN_QUORUM, salt);
+ await zkMultisigFactory
+ .connect(alice)
+ .createMultisig(initialParticipantsPerm, initialParticipantsRot, MIN_QUORUM, salt);
// get deployed proxy
const address = await zkMultisigFactory.computeZKMultisigAddress(alice.address, salt);
// attach proxy address to zkMultisig
- zkMultisig = zkMultisigImpl.attach(address) as ZKMultisig;
+ zkMultisig = zkMultisigImpl.attach(address) as ZKMultisigMock;
// default proposal content
proposalContent = {
@@ -107,283 +114,922 @@ describe("ZKMultisig", () => {
data: "0x",
};
- // default zk params
- zkParams = {
- a: [randomNumber(), randomNumber()],
- b: [
- [randomNumber(), randomNumber()],
- [randomNumber(), randomNumber()],
- ],
- c: [randomNumber(), randomNumber()],
- inputs: [randomNumber(), randomNumber(), randomNumber()],
- };
-
await reverter.snapshot();
});
afterEach(reverter.revert);
- describe("initial", async () => {
+ describe("initialize", () => {
it("should have correct initial state", async () => {
- expect(await zkMultisig.getParticipantsSMTRoot()).to.be.ok;
+ expect(await zkMultisig.getParticipantsCMTRoot()).to.be.ok;
- expect(await zkMultisig.getParticipantsCount()).to.be.eq(initialParticipants.length);
- expect(await zkMultisig.getParticipants()).to.be.deep.eq(initialParticipants);
+ expect(await zkMultisig.getParticipantsCount()).to.be.eq(initialParticipantsPerm.length);
- expect((await zkMultisig.getParticipantsSMTProof(ethers.toBeHex(initialParticipants[0]))).existence).to.be.true;
- expect((await zkMultisig.getParticipantsSMTProof(ethers.toBeHex(randomNumber()))).existence).to.be.false;
+ const expectedParticipants = [initialParticipantsPerm, initialParticipantsRot].map((keyArray) => {
+ return keyArray.map((pointStruct) => {
+ return [pointStruct.x, pointStruct.y];
+ });
+ });
+ expect(await zkMultisig.getParticipants()).to.be.deep.eq(expectedParticipants);
+
+ expect((await zkMultisig.getParticipantsCMTProof(encodePoint(initialParticipantsPerm[0]), proofSize)).existence)
+ .to.be.true;
+ expect((await zkMultisig.getParticipantsCMTProof(encodePoint(initialParticipantsRot[3], 2), proofSize)).existence)
+ .to.be.true;
+ expect((await zkMultisig.getParticipantsCMTProof(encodePoint({ x: 10n, y: 100n }), proofSize)).existence).to.be
+ .false;
expect(await zkMultisig.getProposalsCount()).to.be.eq(0);
expect(await zkMultisig.getProposalsIds(0, 10)).to.be.deep.eq([]);
expect(await zkMultisig.getQuorumPercentage()).to.be.eq(MIN_QUORUM);
+
+ expect(await zkMultisig.getProposalsCount()).to.be.eq(0);
+ expect(await zkMultisig.getCurrentProposalId()).to.be.eq(0);
+
+ const [cumulativePermanentKey, cumulativeRotationKey] = getCumulativeKeys(
+ initialParticipantsPerm,
+ initialParticipantsRot,
+ );
+ expect(await zkMultisig.getCumulativePermanentKey()).to.be.deep.eq(cumulativePermanentKey);
+ expect(await zkMultisig.getCumulativeRotationKey()).to.be.deep.eq(cumulativeRotationKey);
+
+ expect(await zkMultisig.getCreationVerifier()).to.be.eq(await creationVerifier.getAddress());
+ expect(await zkMultisig.getVotingVerifier()).to.be.eq(await votingVerifier.getAddress());
});
it("should not allow to initialize twice", async () => {
await expect(
- zkMultisig.initialize(initialParticipants, MIN_QUORUM, positiveParticipantVerifier),
- ).to.be.revertedWithCustomError({ interface: ZKMultisig__factory.createInterface() }, "InvalidInitialization");
+ zkMultisig.initialize(
+ initialParticipantsPerm,
+ initialParticipantsRot,
+ MIN_QUORUM,
+ creationVerifier,
+ votingVerifier,
+ ),
+ ).to.be.revertedWithCustomError(zkMultisig, "InvalidInitialization");
});
it("should not allow to call proposals functions directly", async () => {
- await expect(zkMultisig.addParticipants(generateParticipants(3))).to.be.revertedWith(
- "ZKMultisig: Not authorized call",
+ const [permanentKeys, rotationKeys] = generateParticipants(3);
+
+ await expect(zkMultisig.addParticipants(permanentKeys, rotationKeys)).to.be.revertedWithCustomError(
+ zkMultisig,
+ "NotAuthorizedCall",
+ );
+
+ await expect(zkMultisig.removeParticipants(permanentKeys)).to.be.revertedWithCustomError(
+ zkMultisig,
+ "NotAuthorizedCall",
+ );
+
+ await expect(zkMultisig.updateQuorumPercentage(MIN_QUORUM)).to.be.revertedWithCustomError(
+ zkMultisig,
+ "NotAuthorizedCall",
+ );
+
+ await expect(zkMultisig.updateCreationVerifier(ZERO_ADDR)).to.be.revertedWithCustomError(
+ zkMultisig,
+ "NotAuthorizedCall",
+ );
+
+ await expect(zkMultisig.updateVotingVerifier(ZERO_ADDR)).to.be.revertedWithCustomError(
+ zkMultisig,
+ "NotAuthorizedCall",
+ );
+ });
+ });
+
+ describe("addParticipants", () => {
+ it("should add participants correctly", async () => {
+ const initialCMTRoot = await zkMultisig.getParticipantsCMTRoot();
+
+ const newPermanentPoints = [
+ { x: 5, y: 6 },
+ { x: 12, y: 24 },
+ ];
+ const newRotationPoints = [
+ { x: 7, y: 8 },
+ { x: 13, y: 26 },
+ ];
+
+ const tx = await zkMultisig.addParticipantsExternal(newPermanentPoints, newRotationPoints);
+
+ await expect(tx).to.emit(zkMultisig, "ParticipantAdded").withArgs([5, 6], [7, 8]);
+ await expect(tx).to.emit(zkMultisig, "ParticipantAdded").withArgs([12, 24], [13, 26]);
+
+ expect(await zkMultisig.getParticipantsCount()).to.be.eq(7);
+ expect(await zkMultisig.getParticipantsCMTRoot()).not.to.be.eq(initialCMTRoot);
+
+ const participants = await zkMultisig.getParticipants();
+ expect(participants[0][5]).to.be.deep.eq([5, 6]);
+ expect(participants[1][5]).to.be.deep.eq([7, 8]);
+ expect(participants[0][6]).to.be.deep.eq([12, 24]);
+ expect(participants[1][6]).to.be.deep.eq([13, 26]);
+
+ const [cumulativePermanentKey, cumulativeRotationKey] = getCumulativeKeys(
+ [...initialParticipantsPerm, ...newPermanentPoints],
+ [...initialParticipantsRot, ...newRotationPoints],
+ );
+ expect(await zkMultisig.getCumulativePermanentKey()).to.be.deep.eq(cumulativePermanentKey);
+ expect(await zkMultisig.getCumulativeRotationKey()).to.be.deep.eq(cumulativeRotationKey);
+ });
+
+ it("should revert if msg.sender is not ZKMultisig", async () => {
+ await expect(zkMultisig.addParticipants([], [])).to.be.revertedWithCustomError(zkMultisig, "NotAuthorizedCall");
+ });
+
+ it("should revert if no participants are provided", async () => {
+ await expect(zkMultisig.addParticipantsExternal([], [])).to.be.revertedWithCustomError(
+ zkMultisig,
+ "NoParticipantsToProcess",
+ );
+ });
+
+ it("should revert if arrays with different length are provided", async () => {
+ await expect(
+ zkMultisig.addParticipantsExternal(
+ [
+ { x: 1, y: 1 },
+ { x: 2, y: 2 },
+ ],
+ [{ x: 3, y: 3 }],
+ ),
+ ).to.be.revertedWithCustomError(zkMultisig, "KeyLenMismatch");
+ });
+
+ it("should not add participant keys that are already added", async () => {
+ await zkMultisig.addParticipantsExternal(
+ [
+ { x: 1, y: 1 },
+ { x: 1, y: 1 },
+ ],
+ [
+ { x: 3, y: 3 },
+ { x: 2, y: 1 },
+ ],
+ );
+
+ expect(await zkMultisig.getParticipantsCount()).to.be.eq(6);
+ });
+ });
+
+ describe("removeParticipants", () => {
+ it("should remove participants correctly", async () => {
+ const initialCMTRoot = await zkMultisig.getParticipantsCMTRoot();
+
+ const tx = await zkMultisig.removeParticipantsExternal([initialParticipantsPerm[0], initialParticipantsPerm[2]]);
+
+ await expect(tx)
+ .to.emit(zkMultisig, "ParticipantRemoved")
+ .withArgs([initialParticipantsPerm[0].x, initialParticipantsPerm[0].y]);
+ await expect(tx)
+ .to.emit(zkMultisig, "ParticipantRemoved")
+ .withArgs([initialParticipantsPerm[2].x, initialParticipantsPerm[2].y]);
+
+ expect(await zkMultisig.getParticipantsCount()).to.be.eq(3);
+ expect(await zkMultisig.getParticipantsCMTRoot()).not.to.be.eq(initialCMTRoot);
+
+ const participants = await zkMultisig.getParticipants();
+ const initialParticipantsPermArr = pointsToArray(initialParticipantsPerm);
+
+ for (const [j, key] of initialParticipantsPermArr.entries()) {
+ const foundKey = participants[0].some((item) => item.every((v, i) => v === key[i]));
+
+ const shouldExist = ![0, 2].includes(j);
+ expect(foundKey).to.equal(shouldExist);
+ }
+
+ const [, cumulativeRotationKey] = getCumulativeKeys(initialParticipantsPerm, initialParticipantsRot);
+ const [cumulativePermanentKey] = getCumulativeKeys(
+ initialParticipantsPerm.filter((_, i) => ![0, 2].includes(i)),
+ initialParticipantsRot,
+ );
+ expect(await zkMultisig.getCumulativePermanentKey()).to.be.deep.eq(cumulativePermanentKey);
+ expect(await zkMultisig.getCumulativeRotationKey()).to.be.deep.eq(cumulativeRotationKey);
+ });
+
+ it("should revert if msg.sender is not ZKMultisig", async () => {
+ await expect(zkMultisig.removeParticipants([])).to.be.revertedWithCustomError(zkMultisig, "NotAuthorizedCall");
+ });
+
+ it("should revert if no participants are provided", async () => {
+ await expect(zkMultisig.removeParticipantsExternal([])).to.be.revertedWithCustomError(
+ zkMultisig,
+ "NoParticipantsToProcess",
);
+ });
- await expect(zkMultisig.removeParticipants(generateParticipants(3))).to.be.revertedWith(
- "ZKMultisig: Not authorized call",
+ it("should revert if removing all participants", async () => {
+ await expect(zkMultisig.removeParticipantsExternal(initialParticipantsPerm)).to.be.revertedWithCustomError(
+ zkMultisig,
+ "RemovingAllParticipants",
);
+ });
+
+ it("should not remove participant keys that don't exist", async () => {
+ const keysToRemove = [{ x: 1, y: 2 }];
+
+ const removeParticipantsData = zkMultisig.interface.encodeFunctionData("removeParticipants", [keysToRemove]);
+
+ const proposalContent = {
+ target: await zkMultisig.getAddress(),
+ value: 0,
+ data: removeParticipantsData,
+ };
+
+ const salt = randomNumber();
+
+ const proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
+
+ await createProposal(zkMultisig, salt, proposalContent);
+
+ await vote(zkMultisig, 5n, 6n, proposalId);
+ await vote(zkMultisig, 1n, 2n, proposalId);
+ await vote(zkMultisig, 3n, 4n, proposalId);
+ await vote(zkMultisig, 9n, 10n, proposalId, false);
+ await vote(zkMultisig, 7n, 8n, proposalId);
+
+ const tx = await zkMultisig.revealAndExecute(4);
+
+ await expect(tx).not.to.emit(zkMultisig, "ParticipantRemoved");
+ });
+ });
+
+ describe("updateQuorumPercentage", () => {
+ it("should update quorum percentage correctly", async () => {
+ const newQuorum = BigInt(50) * PRECISION;
+
+ const updateQuorumPercentageData = zkMultisig.interface.encodeFunctionData("updateQuorumPercentage", [newQuorum]);
+
+ const proposalContent = {
+ target: await zkMultisig.getAddress(),
+ value: 0,
+ data: updateQuorumPercentageData,
+ };
+
+ const salt = randomNumber();
+
+ const proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
- await expect(zkMultisig.updateQuorumPercentage(MIN_QUORUM)).to.be.revertedWith("ZKMultisig: Not authorized call");
+ await createProposal(zkMultisig, salt, proposalContent);
- await expect(zkMultisig.updateParticipantVerifier(ZERO_ADDR)).to.be.revertedWith(
- "ZKMultisig: Not authorized call",
+ await vote(zkMultisig, 5n, 6n, proposalId, false);
+ await vote(zkMultisig, 1n, 2n, proposalId);
+ await vote(zkMultisig, 3n, 4n, proposalId);
+ await vote(zkMultisig, 9n, 10n, proposalId);
+ await vote(zkMultisig, 7n, 8n, proposalId);
+
+ await zkMultisig.revealAndExecute(4);
+
+ expect(await zkMultisig.getQuorumPercentage()).to.be.eq(newQuorum);
+ expect(await zkMultisig.getRequiredQuorum()).to.be.eq(2);
+ });
+
+ it("should revert if quorum percentage value is invalid", async () => {
+ await expect(zkMultisig.updateQuorumPercentageExternal(0))
+ .to.be.revertedWithCustomError(zkMultisig, "InvalidQuorum")
+ .withArgs(0);
+ await expect(zkMultisig.updateQuorumPercentageExternal(110n * PRECISION))
+ .to.be.revertedWithCustomError(zkMultisig, "InvalidQuorum")
+ .withArgs(110n * PRECISION);
+ await expect(zkMultisig.updateQuorumPercentageExternal(MIN_QUORUM))
+ .to.be.revertedWithCustomError(zkMultisig, "InvalidQuorum")
+ .withArgs(MIN_QUORUM);
+ });
+
+ it("should revert if msg.sender is not ZKMultisig", async () => {
+ await expect(zkMultisig.updateQuorumPercentage(60n * PRECISION)).to.be.revertedWithCustomError(
+ zkMultisig,
+ "NotAuthorizedCall",
);
});
});
- describe("proposal flow", async () => {
- const createProposal = async (data: BytesLike): Promise<{ proposalId: bigint; zkParams: ZKParams }> => {
- proposalContent.data = data;
+ describe("updateVerifier", () => {
+ it("should update verifiers correctly", async () => {
+ const updateCreationVerifierData = zkMultisig.interface.encodeFunctionData("updateCreationVerifier", [
+ await zkMultisigFactory.getAddress(),
+ ]);
+ const updateVotingVerifierData = zkMultisig.interface.encodeFunctionData("updateVotingVerifier", [
+ await zkMultisigFactory.getAddress(),
+ ]);
+
+ const proposalContentCreation = {
+ target: await zkMultisig.getAddress(),
+ value: 0,
+ data: updateCreationVerifierData,
+ };
+ const proposalContentVoting = {
+ target: await zkMultisig.getAddress(),
+ value: 0,
+ data: updateVotingVerifierData,
+ };
+
+ let salt = randomNumber();
+
+ let proposalId = await zkMultisig.computeProposalId(proposalContentCreation, salt);
+
+ await createProposal(zkMultisig, salt, proposalContentCreation);
+
+ const v1 = await vote(zkMultisig, 1n, 2n, proposalId);
+ const v2 = await vote(zkMultisig, 7n, 8n, proposalId);
+ const v3 = await vote(zkMultisig, 3n, 4n, proposalId);
+ const v4 = await vote(zkMultisig, 9n, 10n, proposalId);
+ const v5 = await vote(zkMultisig, 5n, 6n, proposalId);
+
+ await zkMultisig.revealAndExecute(5);
+
+ expect(await zkMultisig.getCreationVerifier()).to.be.eq(await zkMultisigFactory.getAddress());
+
+ await zkMultisig.updateVerifierExternal(creationVerifier, true);
+
+ salt = randomNumber();
+
+ proposalId = await zkMultisig.computeProposalId(proposalContentVoting, salt);
+
+ await createProposal(zkMultisig, salt, proposalContentVoting);
+
+ await vote(zkMultisig, 1n, v1.newSk2, proposalId, false);
+ await vote(zkMultisig, 7n, v2.newSk2, proposalId);
+ await vote(zkMultisig, 3n, v3.newSk2, proposalId);
+ await vote(zkMultisig, 9n, v4.newSk2, proposalId);
+ await vote(zkMultisig, 5n, v5.newSk2, proposalId);
+
+ await zkMultisig.reveal(4);
+ await zkMultisig.execute(proposalId);
+
+ expect(await zkMultisig.getVotingVerifier()).to.be.eq(await zkMultisigFactory.getAddress());
+ });
+
+ it("should revert if invalid verifier address is provided", async () => {
+ const [signer] = await ethers.getSigners();
+
+ await expect(zkMultisig.updateVerifierExternal(ZeroAddress, true)).to.be.revertedWithCustomError(
+ zkMultisig,
+ "ZeroVerifier",
+ );
+ await expect(zkMultisig.updateVerifierExternal(creationVerifier, true)).to.be.revertedWithCustomError(
+ zkMultisig,
+ "DuplicateVerifier",
+ );
+ await expect(zkMultisig.updateVerifierExternal(votingVerifier, false)).to.be.revertedWithCustomError(
+ zkMultisig,
+ "DuplicateVerifier",
+ );
+ await expect(zkMultisig.updateVerifierExternal(signer, false))
+ .to.be.revertedWithCustomError(zkMultisig, "NotAContract")
+ .withArgs(signer.address);
+ });
+
+ it("should revert if msg.sender is not ZKMultisig", async () => {
+ await expect(zkMultisig.updateCreationVerifier(zkMultisigFactory)).to.be.revertedWithCustomError(
+ zkMultisig,
+ "NotAuthorizedCall",
+ );
+ await expect(zkMultisig.updateVotingVerifier(zkMultisigFactory)).to.be.revertedWithCustomError(
+ zkMultisig,
+ "NotAuthorizedCall",
+ );
+ });
+ });
+
+ describe("create", () => {
+ it("should create proposal correctly", async () => {
const salt = randomNumber();
- // blinder
- zkParams.inputs[0] = randomNumber();
- // challange
const proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
- zkParams.inputs[1] = await zkMultisig.getProposalChallenge(proposalId);
- // root
- zkParams.inputs[2] = await zkMultisig.getParticipantsSMTRoot();
- const tx = await zkMultisig.create(proposalContent, DAY_IN_SECONDS, salt, zkParams);
+ expect(await zkMultisig.getProposalStatus(proposalId)).to.be.eq(ProposalStatus.NONE);
+
+ const tx = await createProposal(zkMultisig, salt, proposalContent);
- expect(tx).to.emit(zkMultisigFactory, "ZKMultisigCreated").withArgs(proposalId, proposalContent);
expect(tx).to.emit(zkMultisig, "ProposalCreated").withArgs(proposalId, proposalContent);
- expect(tx).to.emit(zkMultisig, "ProposalVoted").withArgs(proposalId, zkParams.inputs[0]);
+ expect((await zkMultisig.getParticipants())[1]).to.be.empty;
+ expect(await zkMultisig.getCumulativeRotationKey()).to.be.deep.eq([0, 1]);
+
+ expect(await zkMultisig.getCurrentProposalId()).to.be.eq(proposalId);
expect(await zkMultisig.getProposalsCount()).to.be.eq(1);
- expect(await zkMultisig.getProposalsIds(0, 10)).to.be.deep.eq([proposalId]);
+ expect(await zkMultisig.getProposalsIds(0, 100)).to.be.deep.eq([proposalId]);
+ expect(await zkMultisig.getProposalStatus(proposalId)).to.be.eq(ProposalStatus.VOTING);
+ expect(await zkMultisig.getProposalInfo(proposalId)).to.be.deep.eq([
+ [proposalContent.target, proposalContent.value, proposalContent.data],
+ 1,
+ 0,
+ 4,
+ ]);
- expect(await zkMultisig.getProposalStatus(proposalId)).to.be.eq(BigInt(ProposalStatus.VOTING));
+ const challenge = await zkMultisig.getProposalChallenge(proposalId);
+ const h1 = poseidonHash(ethers.toBeHex(challenge, 32));
+ const h2 = poseidonHash(h1);
- return { proposalId, zkParams };
- };
+ const [cumulativePermanentKey, cumulativeRotationKey] = getCumulativeKeys(
+ initialParticipantsPerm,
+ initialParticipantsRot,
+ );
+ const expectedEncryptionKey = addPoint(
+ mulPointEscalar(cumulativePermanentKey, BigInt(h1)),
+ mulPointEscalar(cumulativeRotationKey, BigInt(h2)),
+ );
+ expect(await zkMultisig.getEncryptionKey(proposalId)).to.be.deep.eq(expectedEncryptionKey);
+ });
- const vote = async (proposalId: bigint, zkParams: ZKParams) => {
- const tx = await zkMultisig.vote(proposalId, zkParams);
+ it("should revert if the provided target is zero address", async () => {
+ const proposalContent = {
+ target: ethers.ZeroAddress,
+ value: 0,
+ data: "0x",
+ };
- expect(tx).to.emit(zkMultisig, "ProposalVoted").withArgs(proposalId, zkParams.inputs[0]);
- expect(await zkMultisig.isBlinderVoted(proposalId, zkParams.inputs[0])).to.be.true;
- expect(await zkMultisig.getProposalStatus(proposalId)).to.be.oneOf([
- BigInt(ProposalStatus.VOTING),
- BigInt(ProposalStatus.ACCEPTED),
- ]);
- };
+ await expect(zkMultisig.create(proposalContent, randomNumber(), randomZKParams)).to.be.revertedWithCustomError(
+ zkMultisig,
+ "ZeroTarget",
+ );
+ });
- const execute = async (proposalId: bigint) => {
- const tx = await zkMultisig.execute(proposalId);
+ it("should revert if proposal with the same proposal ID exists", async () => {
+ const salt = randomNumber();
- expect(tx).to.emit(zkMultisig, "ProposalExecuted").withArgs(proposalId);
- expect(await zkMultisig.getProposalStatus(proposalId)).to.be.eq(BigInt(ProposalStatus.EXECUTED));
- };
+ const proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
- describe("add particpants", async () => {
- it("create", async () => {
- const newParticipants = generateParticipants(2);
- const data = ZKMultisig__factory.createInterface().encodeFunctionData("addParticipants(uint256[])", [
- newParticipants,
- ]);
+ await createProposal(zkMultisig, salt, proposalContent);
- await createProposal(data);
- });
+ await zkMultisig.deactivateProposal(proposalId);
- it("vote", async () => {
- const newParticipants = generateParticipants(2);
- const data = ZKMultisig__factory.createInterface().encodeFunctionData("addParticipants(uint256[])", [
- newParticipants,
- ]);
+ await expect(zkMultisig.create(proposalContent, salt, randomZKParams))
+ .to.be.revertedWithCustomError(zkMultisig, "ProposalExists")
+ .withArgs(proposalId);
+ });
- const { proposalId, zkParams } = await createProposal(data);
+ it("should revert if active proposal already exists", async () => {
+ const salt = randomNumber();
- //update blinder as af
- zkParams.inputs[0] = randomNumber();
- await vote(proposalId, zkParams);
- });
+ const proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
+
+ await createProposal(zkMultisig, salt, proposalContent);
- it("execute", async () => {
- const newParticipants = generateParticipants(2);
- const data = ZKMultisig__factory.createInterface().encodeFunctionData("addParticipants(uint256[])", [
- newParticipants,
- ]);
+ await expect(zkMultisig.create(proposalContent, salt, randomZKParams))
+ .to.be.revertedWithCustomError(zkMultisig, "ActiveProposal")
+ .withArgs(proposalId);
+ });
- const { proposalId, zkParams } = await createProposal(data);
+ it("should revert if invalid zk params are provided", async () => {
+ const salt = randomNumber();
- while ((await zkMultisig.getProposalStatus(proposalId)) != BigInt(ProposalStatus.ACCEPTED)) {
- //update blinder as af
- zkParams.inputs[0] = randomNumber();
- await vote(proposalId, zkParams);
- }
+ const proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
- await execute(proposalId);
+ const cmtProof = await zkMultisig.getParticipantsCMTProof(encodePoint(initialParticipantsPerm[1]), proofSize);
- expect(await zkMultisig.getParticipantsCount()).to.be.eq(initialParticipants.length + newParticipants.length);
- expect(await zkMultisig.getParticipants()).to.be.deep.eq([...initialParticipants, ...newParticipants]);
+ const circuit: ProposalCreation = await zkit.getCircuit("ProposalCreation");
- expect((await zkMultisig.getParticipantsSMTProof(ethers.toBeHex(newParticipants[0]))).existence).to.be.true;
- expect((await zkMultisig.getParticipantsSMTProof(ethers.toBeHex(newParticipants[1]))).existence).to.be.true;
+ const proof = await circuit.generateProof({
+ cmtRoot: BigInt(cmtProof[0]),
+ challenge: proposalId,
+ sk: 3,
+ siblings: cmtProof[1].map((h) => BigInt(h)),
+ siblingsLength: numberToArray(BigInt(cmtProof[2]), proofSize),
+ directionBits: parseNumberToBitsArray(BigInt(cmtProof[3]), BigInt(cmtProof[2]) / 2n, proofSize),
});
+
+ const pi_b = proof.proof.pi_b;
+
+ const zkParams = {
+ a: [proof.proof.pi_a[0], proof.proof.pi_a[1]],
+ b: [
+ [pi_b[0][1], pi_b[0][0]],
+ [pi_b[1][1], pi_b[1][0]],
+ ],
+ c: [proof.proof.pi_c[0], proof.proof.pi_c[1]],
+ };
+
+ zkParams.b[0][0] = pi_b[0][0];
+ await expect(zkMultisig.create(proposalContent, salt, zkParams)).to.be.revertedWithCustomError(
+ zkMultisig,
+ "InvalidProof",
+ );
});
+ });
- describe("remove particpants", async () => {
- it("create", async () => {
- const participantsToDelete = (await zkMultisig.getParticipants()).slice(0, 2);
- const data = ZKMultisig__factory.createInterface().encodeFunctionData("removeParticipants(uint256[])", [
- participantsToDelete,
- ]);
+ describe("vote", () => {
+ it("should vote on the proposal correctly", async () => {
+ const salt = randomNumber();
- await createProposal(data);
- });
+ const proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
- it("vote", async () => {
- const participantsToDelete = (await zkMultisig.getParticipants()).slice(0, 2);
- const data = ZKMultisig__factory.createInterface().encodeFunctionData("removeParticipants(uint256[])", [
- participantsToDelete,
- ]);
+ await createProposal(zkMultisig, salt, proposalContent);
- const { proposalId, zkParams } = await createProposal(data);
+ const initialCMTRoot = await zkMultisig.getParticipantsCMTRoot();
- //update blinder as af
- zkParams.inputs[0] = randomNumber();
- await vote(proposalId, zkParams);
- });
+ const cmtProof1: ProofStructOutput = await zkMultisig.getParticipantsCMTProof(
+ encodePoint(initialParticipantsPerm[4]),
+ proofSize,
+ );
+ const cmtProof2: ProofStructOutput = await zkMultisig.getParticipantsCMTProof(
+ encodePoint(initialParticipantsRot[4], 2),
+ proofSize,
+ );
+
+ // first vote
+ const keyNullifier1 = BigInt(poseidonHash(ethers.toBeHex(2n, 32)));
+ const blinder1 = getBlinder(1n, proposalId);
- it("execute", async () => {
- const participantsToDelete = (await zkMultisig.getParticipants()).slice(0, 2);
- const data = ZKMultisig__factory.createInterface().encodeFunctionData("removeParticipants(uint256[])", [
- participantsToDelete,
- ]);
+ expect(await zkMultisig.isRotationKeyNullifierUsed(keyNullifier1)).to.be.false;
- const { proposalId, zkParams } = await createProposal(data);
+ const v1 = await vote(zkMultisig, 1n, 2n, proposalId);
- while ((await zkMultisig.getProposalStatus(proposalId)) != BigInt(ProposalStatus.ACCEPTED)) {
- //update blinder as af
- zkParams.inputs[0] = randomNumber();
- await vote(proposalId, zkParams);
- }
+ expect(v1.tx).to.emit(zkMultisig, "ProposalVoted").withArgs(proposalId, blinder1);
- await execute(proposalId);
+ expect(await zkMultisig.isRotationKeyNullifierUsed(keyNullifier1)).to.be.true;
+ expect(await zkMultisig.getProposalInfo(proposalId)).to.be.deep.eq([
+ [proposalContent.target, proposalContent.value, proposalContent.data],
+ 1,
+ 1,
+ 4,
+ ]);
+ expect(await zkMultisig.getProposalBlinders(proposalId)).to.be.deep.eq([blinder1]);
+ expect(await zkMultisig.getProposalDecryptionKey(proposalId)).to.be.eq(v1.decryptionKeyShare);
+ expect(await zkMultisig.getProposalAggregatedVotes(proposalId)).to.be.deep.eq(v1.vote);
+
+ let participants = await zkMultisig.getParticipants();
+ expect(await zkMultisig.getParticipantsCMTRoot()).not.to.be.eq(initialCMTRoot);
+ expect(await zkMultisig.getParticipantsCount()).to.be.eq(5);
+ expect(participants[0].length).to.be.eq(5);
+ expect(participants[1].length).to.be.eq(1);
+ expect(participants[1]).to.be.deep.eq([v1.newPk2]);
+
+ let [cumulativePermanentKey] = getCumulativeKeys(initialParticipantsPerm, initialParticipantsRot);
+ expect(await zkMultisig.getCumulativePermanentKey()).to.be.deep.eq(cumulativePermanentKey);
+ expect(await zkMultisig.getCumulativeRotationKey()).to.be.deep.eq(v1.newPk2);
+
+ // other votes
+ const v2 = await vote(zkMultisig, 5n, 6n, proposalId);
+ const v3 = await vote(zkMultisig, 9n, 10n, proposalId, false, initialCMTRoot, [cmtProof1, cmtProof2]);
+ const v4 = await vote(zkMultisig, 3n, 4n, proposalId);
+
+ const blinder2 = getBlinder(5n, proposalId);
+ const blinder3 = getBlinder(9n, proposalId);
+ const blinder4 = getBlinder(3n, proposalId);
+
+ const keyNullifier2 = BigInt(poseidonHash(ethers.toBeHex(6n, 32)));
+ const keyNullifier3 = BigInt(poseidonHash(ethers.toBeHex(10n, 32)));
+ const keyNullifier4 = BigInt(poseidonHash(ethers.toBeHex(4n, 32)));
+
+ expect(v2.tx).to.emit(zkMultisig, "ProposalVoted").withArgs(proposalId, blinder2);
+ expect(v3.tx).to.emit(zkMultisig, "ProposalVoted").withArgs(proposalId, blinder3);
+ expect(v4.tx).to.emit(zkMultisig, "ProposalVoted").withArgs(proposalId, blinder4);
+
+ expect(await zkMultisig.isRotationKeyNullifierUsed(keyNullifier2)).to.be.true;
+ expect(await zkMultisig.isRotationKeyNullifierUsed(keyNullifier3)).to.be.true;
+ expect(await zkMultisig.isRotationKeyNullifierUsed(keyNullifier4)).to.be.true;
+ expect(await zkMultisig.getProposalInfo(proposalId)).to.be.deep.eq([
+ [proposalContent.target, proposalContent.value, proposalContent.data],
+ 1,
+ 4,
+ 4,
+ ]);
+ expect(await zkMultisig.getProposalBlinders(proposalId)).to.be.deep.eq([blinder1, blinder2, blinder3, blinder4]);
+ expect(await zkMultisig.getProposalDecryptionKey(proposalId)).to.be.eq(
+ aggregateDecryptionKeyShares([
+ v1.decryptionKeyShare,
+ v2.decryptionKeyShare,
+ v3.decryptionKeyShare,
+ v4.decryptionKeyShare,
+ ]),
+ );
+ expect(await zkMultisig.getProposalAggregatedVotes(proposalId)).to.be.deep.eq(
+ aggregateVotes([v1.vote, v2.vote, v3.vote, v4.vote]),
+ );
- expect(await zkMultisig.getParticipantsCount()).to.be.eq(
- initialParticipants.length - participantsToDelete.length,
- );
+ participants = await zkMultisig.getParticipants();
+ expect(await zkMultisig.getParticipantsCount()).to.be.eq(5);
+ expect(participants[0].length).to.be.eq(5);
+ expect(participants[1].length).to.be.eq(4);
+ expect(participants[1]).to.be.deep.eq([v1.newPk2, v2.newPk2, v3.newPk2, v4.newPk2]);
- expect((await zkMultisig.getParticipantsSMTProof(ethers.toBeHex(initialParticipants[0]))).existence).to.be
- .false;
- expect((await zkMultisig.getParticipantsSMTProof(ethers.toBeHex(initialParticipants[4]))).existence).to.be.true;
- });
+ [cumulativePermanentKey] = getCumulativeKeys(initialParticipantsPerm, initialParticipantsRot);
+ expect(await zkMultisig.getCumulativePermanentKey()).to.be.deep.eq(cumulativePermanentKey);
+ expect(await zkMultisig.getCumulativeRotationKey()).to.be.deep.eq(
+ aggregatePoints([v1.newPk2, v2.newPk2, v3.newPk2, v4.newPk2]),
+ );
});
- describe("update quorum percentage", async () => {
- it("create", async () => {
- const newQuorum = MIN_QUORUM + BigInt(10) * PRECISION;
- const data = ZKMultisig__factory.createInterface().encodeFunctionData("updateQuorumPercentage(uint256)", [
- newQuorum,
- ]);
+ it("should revert if proposal is not in the voting status", async () => {
+ const invalidProposalId = randomNumber();
- await createProposal(data);
- });
+ await expect(vote(zkMultisig, 1n, 2n, invalidProposalId))
+ .to.be.revertedWithCustomError(zkMultisig, "NotVoting")
+ .withArgs(0);
+ });
- it("vote", async () => {
- const newQuorum = MIN_QUORUM + BigInt(10) * PRECISION;
- const data = ZKMultisig__factory.createInterface().encodeFunctionData("updateQuorumPercentage(uint256)", [
- newQuorum,
- ]);
+ it("should revert if using already rotated pk2", async () => {
+ const salt = randomNumber();
- const { proposalId, zkParams } = await createProposal(data);
+ const proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
- //update blinder as af
- zkParams.inputs[0] = randomNumber();
- await vote(proposalId, zkParams);
- });
+ await createProposal(zkMultisig, salt, proposalContent);
- it("execute", async () => {
- const newQuorum = MIN_QUORUM + BigInt(10) * PRECISION;
- const data = ZKMultisig__factory.createInterface().encodeFunctionData("updateQuorumPercentage(uint256)", [
- newQuorum,
- ]);
+ await vote(zkMultisig, 3n, 4n, proposalId);
- const { proposalId, zkParams } = await createProposal(data);
+ await expect(vote(zkMultisig, 3n, 4n, proposalId, false))
+ .to.be.revertedWithCustomError(zkMultisig, "UsedNullifier")
+ .withArgs(BigInt(poseidonHash(ethers.toBeHex(4n, 32))));
+ });
- while ((await zkMultisig.getProposalStatus(proposalId)) != BigInt(ProposalStatus.ACCEPTED)) {
- //update blinder as af
- zkParams.inputs[0] = randomNumber();
- await vote(proposalId, zkParams);
- }
+ it("should revert if the same user tries to vote twice", async () => {
+ const salt = randomNumber();
- await execute(proposalId);
+ const proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
- expect(await zkMultisig.getQuorumPercentage()).to.be.eq(newQuorum);
- });
+ await createProposal(zkMultisig, salt, proposalContent);
+
+ await vote(zkMultisig, 3n, 4n, proposalId);
+
+ const blinder = getBlinder(3n, proposalId);
+
+ const encodedVote = ethers.AbiCoder.defaultAbiCoder().encode(
+ ["tuple(uint256,uint256)[2]"],
+ [
+ [
+ [10n, 20n],
+ [30n, 40n],
+ ],
+ ],
+ );
+
+ const voteParams = {
+ encryptedVote: encodedVote,
+ decryptionKeyShare: 100,
+ keyNullifier: 100,
+ blinder,
+ cmtRoot: await zkMultisig.getParticipantsCMTRoot(),
+ rotationKey: { x: 10, y: 20 },
+ proofData: randomZKParams,
+ };
+
+ await expect(zkMultisig.vote(voteParams))
+ .to.be.revertedWithCustomError(zkMultisig, "UsedBlinder")
+ .withArgs(blinder);
});
- describe("update participant verifier", async () => {
- it("create", async () => {
- const data = ZKMultisig__factory.createInterface().encodeFunctionData("updateParticipantVerifier(address)", [
- await negativeParticipantVerifier.getAddress(),
- ]);
+ it("should revert if invalid CMT root is provided", async () => {
+ const salt = randomNumber();
- await createProposal(data);
- });
+ const proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
- it("vote", async () => {
- const data = ZKMultisig__factory.createInterface().encodeFunctionData("updateParticipantVerifier(address)", [
- await negativeParticipantVerifier.getAddress(),
- ]);
+ await createProposal(zkMultisig, salt, proposalContent);
- const { proposalId, zkParams } = await createProposal(data);
+ await vote(zkMultisig, 3n, 4n, proposalId);
- //update blinder as af
- zkParams.inputs[0] = randomNumber();
- await vote(proposalId, zkParams);
- });
+ const invalidRoot = ethers.toBeHex(randomNumber(), 32);
+
+ await expect(vote(zkMultisig, 5n, 6n, proposalId, false, invalidRoot))
+ .to.be.revertedWithCustomError(zkMultisig, "InvalidCMTRoot")
+ .withArgs(invalidRoot);
+ });
+
+ it("should revert if invalid zk params are provided", async () => {
+ const salt = randomNumber();
+
+ await createProposal(zkMultisig, salt, proposalContent);
+
+ const encodedVote = ethers.AbiCoder.defaultAbiCoder().encode(
+ ["tuple(uint256,uint256)[2]"],
+ [
+ [
+ [1n, 2n],
+ [3n, 4n],
+ ],
+ ],
+ );
+
+ const voteParams = {
+ encryptedVote: encodedVote,
+ decryptionKeyShare: 100,
+ keyNullifier: 100,
+ blinder: 100,
+ cmtRoot: await zkMultisig.getParticipantsCMTRoot(),
+ rotationKey: { x: 10, y: 20 },
+ proofData: randomZKParams,
+ };
+
+ await expect(zkMultisig.vote(voteParams)).to.be.revertedWithCustomError(zkMultisig, "InvalidProof");
+ });
+ });
+
+ describe("reveal", () => {
+ it("should reveal votes correctly", async () => {
+ let salt = randomNumber();
+
+ let proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
+
+ await createProposal(zkMultisig, salt, proposalContent);
+
+ const v1 = await vote(zkMultisig, 5n, 6n, proposalId);
+ const v2 = await vote(zkMultisig, 1n, 2n, proposalId, false);
+ const v3 = await vote(zkMultisig, 3n, 4n, proposalId);
+ const v4 = await vote(zkMultisig, 9n, 10n, proposalId);
+ const v5 = await vote(zkMultisig, 7n, 8n, proposalId);
+
+ let tx = await zkMultisig.reveal(4);
- it("execute", async () => {
- const data = ZKMultisig__factory.createInterface().encodeFunctionData("updateParticipantVerifier(address)", [
- await negativeParticipantVerifier.getAddress(),
- ]);
+ await expect(tx).to.emit(zkMultisig, "ProposalRevealed").withArgs(proposalId, true);
- const { proposalId, zkParams } = await createProposal(data);
+ expect(await zkMultisig.getProposalStatus(proposalId)).to.be.eq(ProposalStatus.ACCEPTED);
- while ((await zkMultisig.getProposalStatus(proposalId)) != BigInt(ProposalStatus.ACCEPTED)) {
- //update blinder as af
- zkParams.inputs[0] = randomNumber();
- await vote(proposalId, zkParams);
- }
+ salt = randomNumber();
- await execute(proposalId);
+ proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
- expect(await zkMultisig.participantVerifier()).to.be.eq(await negativeParticipantVerifier.getAddress());
+ await createProposal(zkMultisig, salt, proposalContent);
+
+ await vote(zkMultisig, 5n, v1.newSk2, proposalId, false);
+ await vote(zkMultisig, 1n, v2.newSk2, proposalId, false);
+ await vote(zkMultisig, 3n, v3.newSk2, proposalId);
+ await vote(zkMultisig, 9n, v4.newSk2, proposalId, false);
+ await vote(zkMultisig, 7n, v5.newSk2, proposalId);
+
+ tx = await zkMultisig.reveal(2);
+
+ await expect(tx).to.emit(zkMultisig, "ProposalRevealed").withArgs(proposalId, false);
+
+ expect(await zkMultisig.getProposalStatus(proposalId)).to.be.eq(ProposalStatus.REJECTED);
+ });
+
+ it("should revert if the provided approvalVoteCount is incorrect or not everyone has voted", async () => {
+ let salt = randomNumber();
+
+ let proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
+
+ await createProposal(zkMultisig, salt, proposalContent);
+
+ await vote(zkMultisig, 1n, 2n, proposalId, false);
+ await vote(zkMultisig, 3n, 4n, proposalId);
+ await vote(zkMultisig, 9n, 10n, proposalId);
+ await vote(zkMultisig, 7n, 8n, proposalId);
+
+ await expect(zkMultisig.reveal(3)).to.be.revertedWithCustomError(zkMultisig, "VoteCountMismatch");
+
+ await vote(zkMultisig, 5n, 6n, proposalId);
+
+ await expect(zkMultisig.reveal(3)).to.be.revertedWithCustomError(zkMultisig, "VoteCountMismatch");
+ });
+ });
+
+ describe("execute", () => {
+ it("should execute proposal correctly", async () => {
+ const newPermanentPoints = [
+ { x: 1, y: 11 },
+ { x: 3, y: 33 },
+ ];
+ const newRotationPoints = [
+ { x: 2, y: 22 },
+ { x: 4, y: 44 },
+ ];
+
+ const addParticipantsData = zkMultisig.interface.encodeFunctionData("addParticipants", [
+ newPermanentPoints,
+ newRotationPoints,
+ ]);
+
+ const proposalContent = {
+ target: await zkMultisig.getAddress(),
+ value: 0,
+ data: addParticipantsData,
+ };
+
+ const salt = randomNumber();
+
+ const proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
+
+ await createProposal(zkMultisig, salt, proposalContent);
+
+ const v1 = await vote(zkMultisig, 5n, 6n, proposalId);
+ const v2 = await vote(zkMultisig, 1n, 2n, proposalId);
+ const v3 = await vote(zkMultisig, 3n, 4n, proposalId);
+ const v4 = await vote(zkMultisig, 9n, 10n, proposalId, false);
+ const v5 = await vote(zkMultisig, 7n, 8n, proposalId);
+
+ await zkMultisig.reveal(4);
+
+ const tx = await zkMultisig.execute(proposalId);
+
+ await expect(tx).to.emit(zkMultisig, "ProposalExecuted").withArgs(proposalId);
+ await expect(tx).to.emit(zkMultisig, "ParticipantAdded").withArgs([1, 11], [2, 22]);
+ await expect(tx).to.emit(zkMultisig, "ParticipantAdded").withArgs([3, 33], [4, 44]);
+
+ expect(await zkMultisig.getProposalStatus(proposalId)).to.be.eq(ProposalStatus.EXECUTED);
+ expect(await zkMultisig.getParticipantsCount()).to.be.eq(7);
+
+ const participants = await zkMultisig.getParticipants();
+ expect(participants[0][5]).to.be.deep.eq([1, 11]);
+ expect(participants[1][5]).to.be.deep.eq([2, 22]);
+ expect(participants[0][6]).to.be.deep.eq([3, 33]);
+ expect(participants[1][6]).to.be.deep.eq([4, 44]);
+
+ const [cumulativePermanentKey, cumulativeRotationKey] = getCumulativeKeys(
+ [...initialParticipantsPerm, ...newPermanentPoints],
+ [...arrayToPoints([v1.newPk2, v2.newPk2, v3.newPk2, v4.newPk2, v5.newPk2]), ...newRotationPoints],
+ );
+ expect(await zkMultisig.getCumulativePermanentKey()).to.be.deep.eq(cumulativePermanentKey);
+ expect(await zkMultisig.getCumulativeRotationKey()).to.be.deep.eq(cumulativeRotationKey);
+ });
+
+ it("should execute proposal with value correctly", async () => {
+ await (
+ await ethers.getSigners()
+ )[0].sendTransaction({
+ to: await zkMultisig.getAddress(),
+ value: ethers.parseEther("1"),
});
+
+ const ethReceiver = await ethers.deployContract("EthReceiverMock");
+
+ const proposalContent = {
+ target: await ethReceiver.getAddress(),
+ value: ethers.parseEther("1"),
+ data: "0x",
+ };
+
+ const salt = randomNumber();
+
+ const proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
+
+ await createProposal(zkMultisig, salt, proposalContent);
+
+ await vote(zkMultisig, 1n, 2n, proposalId);
+ await vote(zkMultisig, 7n, 8n, proposalId);
+ await vote(zkMultisig, 5n, 6n, proposalId);
+ await vote(zkMultisig, 9n, 10n, proposalId);
+ await vote(zkMultisig, 3n, 4n, proposalId);
+
+ await zkMultisig.reveal(5);
+
+ const tx = await zkMultisig.execute(proposalId, { value: ethers.parseEther("1") });
+
+ await expect(tx).to.emit(zkMultisig, "ProposalExecuted").withArgs(proposalId);
+ await expect(tx)
+ .to.emit(ethReceiver, "ReceivedEth")
+ .withArgs(await zkMultisig.getAddress(), ethers.parseEther("1"));
+ });
+
+ it("should revert if the proposal is not accepted", async () => {
+ const invalidProposalId = randomNumber();
+
+ await expect(zkMultisig.execute(invalidProposalId))
+ .to.be.revertedWithCustomError(zkMultisig, "ProposalNotAccepted")
+ .withArgs(invalidProposalId);
+
+ const salt = randomNumber();
+
+ const proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
+
+ await createProposal(zkMultisig, salt, proposalContent);
+
+ await vote(zkMultisig, 1n, 2n, proposalId);
+ await vote(zkMultisig, 7n, 8n, proposalId, false);
+ await vote(zkMultisig, 5n, 6n, proposalId, false);
+ await vote(zkMultisig, 9n, 10n, proposalId);
+ await vote(zkMultisig, 3n, 4n, proposalId);
+
+ await expect(zkMultisig.execute(proposalId))
+ .to.be.revertedWithCustomError(zkMultisig, "ProposalNotAccepted")
+ .withArgs(proposalId);
+
+ await zkMultisig.reveal(3);
+
+ await expect(zkMultisig.execute(proposalId))
+ .to.be.revertedWithCustomError(zkMultisig, "ProposalNotAccepted")
+ .withArgs(proposalId);
+ });
+
+ it("should revert if invalid value is provided", async () => {
+ const proposalContent = {
+ target: await zkMultisigFactory.getAddress(),
+ value: ethers.parseEther("1"),
+ data: "0x",
+ };
+
+ const salt = randomNumber();
+
+ const proposalId = await zkMultisig.computeProposalId(proposalContent, salt);
+
+ await createProposal(zkMultisig, salt, proposalContent);
+
+ await vote(zkMultisig, 1n, 2n, proposalId);
+ await vote(zkMultisig, 7n, 8n, proposalId);
+ await vote(zkMultisig, 5n, 6n, proposalId, false);
+ await vote(zkMultisig, 9n, 10n, proposalId);
+ await vote(zkMultisig, 3n, 4n, proposalId);
+
+ await zkMultisig.reveal(4);
+
+ await expect(zkMultisig.execute(proposalId))
+ .to.be.revertedWithCustomError(zkMultisig, "InvalidValue")
+ .withArgs(0, ethers.parseEther("1"));
+ await expect(zkMultisig.execute(proposalId, { value: ethers.parseEther("0.5") }))
+ .to.be.revertedWithCustomError(zkMultisig, "InvalidValue")
+ .withArgs(ethers.parseEther("0.5"), ethers.parseEther("1"));
});
});
});
diff --git a/test/ZKMultisigFactory.tests.ts b/test/ZKMultisigFactory.tests.ts
index faf875d..56fa752 100644
--- a/test/ZKMultisigFactory.tests.ts
+++ b/test/ZKMultisigFactory.tests.ts
@@ -2,25 +2,27 @@ import { PRECISION, ZERO_ADDR } from "@/scripts/utils/constants";
import { Reverter } from "@/test/helpers/reverter";
import {
ERC1967Proxy__factory,
- NegativeVerifierMock,
- PositiveVerifierMock,
- ZKMultisig,
+ ProposalCreationGroth16Verifier,
+ VotingGroth16Verifier,
+ ZKMultisigMock,
ZKMultisigFactory,
} from "@ethers-v6";
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
import { expect } from "chai";
import { randomBytes } from "crypto";
-import { AbiCoder, solidityPacked as encodePacked, keccak256, TypedDataDomain } from "ethers";
+import { AbiCoder, solidityPacked as encodePacked, keccak256, TypedDataDomain, ZeroAddress } from "ethers";
import { ethers } from "hardhat";
import { getPoseidon } from "./helpers";
+import { generateParticipants, pointsToArray } from "@/test/helpers/zk-multisig";
-describe("ZKMultisig Factory", () => {
+describe("ZKMultisigFactory", () => {
const reverter = new Reverter();
let alice: SignerWithAddress;
- let participantVerifier: PositiveVerifierMock | NegativeVerifierMock;
- let zkMultisig: ZKMultisig;
+ let creationVerifier: ProposalCreationGroth16Verifier;
+ let votingVerifier: VotingGroth16Verifier;
+ let zkMultisig: ZKMultisigMock;
let zkMultisigFactory: ZKMultisigFactory;
const encode = (types: ReadonlyArray, values: ReadonlyArray): string => {
@@ -32,37 +34,30 @@ describe("ZKMultisig Factory", () => {
before(async () => {
[alice] = await ethers.getSigners();
- const verifier__factory = await ethers.getContractFactory("PositiveVerifierMock");
- participantVerifier = await verifier__factory.deploy();
+ creationVerifier = await ethers.deployContract("ProposalCreationGroth16Verifier");
+ votingVerifier = await ethers.deployContract("VotingGroth16Verifier");
- await participantVerifier.waitForDeployment();
-
- const zkMultisig__factory = await ethers.getContractFactory("ZKMultisig", {
+ zkMultisig = await ethers.deployContract("ZKMultisigMock", {
libraries: {
PoseidonUnit1L: await (await getPoseidon(1)).getAddress(),
+ PoseidonUnit3L: await (await getPoseidon(3)).getAddress(),
},
});
- zkMultisig = await zkMultisig__factory.deploy();
-
- await zkMultisig.waitForDeployment();
- const zkMultisigFactory__factory = await ethers.getContractFactory("ZKMultisigFactory");
- zkMultisigFactory = await zkMultisigFactory__factory.deploy(
- await zkMultisig.getAddress(),
- await participantVerifier.getAddress(),
- );
+ zkMultisigFactory = await ethers.deployContract("ZKMultisigFactory");
- await zkMultisigFactory.waitForDeployment();
+ await zkMultisigFactory.initialize(zkMultisig, creationVerifier, votingVerifier);
await reverter.snapshot();
});
afterEach(reverter.revert);
- describe("initial", () => {
+ describe("initialize", () => {
it("should set parameters correctly", async () => {
- expect(await zkMultisigFactory.ZK_MULTISIG_IMPL()).to.eq(await zkMultisig.getAddress());
- expect(await zkMultisigFactory.PARTICIPANT_VERIFIER()).to.eq(await participantVerifier.getAddress());
+ expect(await zkMultisigFactory.getZKMultisigImplementation()).to.eq(await zkMultisig.getAddress());
+ expect(await zkMultisigFactory.getCreationVerifier()).to.eq(await creationVerifier.getAddress());
+ expect(await zkMultisigFactory.getVotingVerifier()).to.eq(await votingVerifier.getAddress());
});
it("should have correct initial state", async () => {
@@ -70,13 +65,27 @@ describe("ZKMultisig Factory", () => {
expect(await zkMultisigFactory.getZKMultisigs(0, 1)).to.be.deep.eq([]);
});
- it("should revert if contructor parameters are incorrect", async () => {
- const factory = await ethers.getContractFactory("ZKMultisigFactory");
- const err = "ZKMultisigFactory: Invalid implementation or verifier address";
+ it("should revert if initialize parameters are incorrect", async () => {
+ const factory = await ethers.deployContract("ZKMultisigFactory");
+
+ await expect(factory.initialize(ZeroAddress, creationVerifier, votingVerifier)).to.be.revertedWithCustomError(
+ zkMultisigFactory,
+ "InvalidImplementationOrVerifier",
+ );
+ await expect(factory.initialize(zkMultisig, ZeroAddress, votingVerifier)).to.be.revertedWithCustomError(
+ zkMultisigFactory,
+ "InvalidImplementationOrVerifier",
+ );
+ await expect(factory.initialize(zkMultisig, creationVerifier, ZeroAddress)).to.be.revertedWithCustomError(
+ zkMultisigFactory,
+ "InvalidImplementationOrVerifier",
+ );
+ });
- // deploy multisig factory with zero address
- await expect(factory.deploy(ZERO_ADDR, await participantVerifier.getAddress())).to.be.revertedWith(err);
- await expect(factory.deploy(await zkMultisig.getAddress(), ZERO_ADDR)).to.be.revertedWith(err);
+ it("should revert if ctrying to initialize twice", async () => {
+ await expect(
+ zkMultisigFactory.initialize(zkMultisig, creationVerifier, creationVerifier),
+ ).to.be.revertedWithCustomError(zkMultisigFactory, "InvalidInitialization");
});
});
@@ -131,16 +140,15 @@ describe("ZKMultisig Factory", () => {
expect(await zkMultisigFactory.getZKMultisigs(0, 1)).to.be.deep.eq([]);
// add participants
- let participants: bigint[] = [];
- for (let i = 0; i < 5; i++) {
- participants.push(randomNumber());
- }
+ const [participantsPerm, participantsRot] = generateParticipants(10);
const quorum = BigInt(80) * PRECISION;
- const tx = zkMultisigFactory.connect(alice).createMultisig(participants, quorum, salt);
+ const tx = zkMultisigFactory.connect(alice).createMultisig(participantsPerm, participantsRot, quorum, salt);
- await expect(tx).to.emit(zkMultisigFactory, "ZKMultisigCreated").withArgs(multisigAddress, participants, quorum);
+ await expect(tx)
+ .to.emit(zkMultisigFactory, "ZKMultisigCreated")
+ .withArgs(multisigAddress, pointsToArray(participantsPerm), pointsToArray(participantsRot), quorum);
expect(await zkMultisigFactory.isZKMultisig(multisigAddress)).to.be.eq(true);
expect(await zkMultisigFactory.getZKMultisigsCount()).to.be.eq(1);
diff --git a/test/helpers/zk-multisig.ts b/test/helpers/zk-multisig.ts
new file mode 100644
index 0000000..3df7f66
--- /dev/null
+++ b/test/helpers/zk-multisig.ts
@@ -0,0 +1,280 @@
+import { ethers, zkit } from "hardhat";
+import { addPoint, Base8, mulPointEscalar, Point } from "@zk-kit/baby-jubjub";
+import { poseidonHash } from "@/test/helpers/poseidon-hash";
+import { ProposalCreation, Voting } from "@/generated-types/zkit";
+import { IZKMultisig, ZKMultisigMock } from "@ethers-v6";
+import { CartesianMerkleTree, ED256 } from "@/generated-types/ethers/contracts/ZKMultisig";
+import ProofStructOutput = CartesianMerkleTree.ProofStructOutput;
+import { randomBytes } from "crypto";
+import APointStruct = ED256.APointStruct;
+import ProposalContent = IZKMultisig.ProposalContentStruct;
+
+const proofSize = 40;
+const inf: Point = [0n, 1n];
+const babyJubJubN = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
+
+export function randomNumber() {
+ return BigInt("0x" + randomBytes(32).toString("hex"));
+}
+
+export async function createProposal(zkMultisig: ZKMultisigMock, salt: bigint, content: ProposalContent) {
+ const proposalId = await zkMultisig.computeProposalId(content, salt);
+
+ const challengeEncoded = ethers.AbiCoder.defaultAbiCoder().encode(
+ ["uint256", "address", "uint256"],
+ [(await ethers.provider.getNetwork()).chainId, await zkMultisig.getAddress(), proposalId],
+ );
+
+ const challenge = BigInt(ethers.keccak256(challengeEncoded));
+
+ const cmtProof = await zkMultisig.getParticipantsCMTProof(encodePoint({ x: Base8[0], y: Base8[1] }), proofSize);
+
+ const circuit: ProposalCreation = await zkit.getCircuit("ProposalCreation");
+
+ const proof = await circuit.generateProof({
+ cmtRoot: BigInt(cmtProof[0]),
+ challenge,
+ sk: 1,
+ siblings: cmtProof[1].map((h) => BigInt(h)),
+ siblingsLength: numberToArray(BigInt(cmtProof[2]), proofSize),
+ directionBits: parseNumberToBitsArray(BigInt(cmtProof[3]), BigInt(cmtProof[2]) / 2n, proofSize),
+ });
+
+ const pi_b = proof.proof.pi_b;
+
+ const zkParams = {
+ a: [proof.proof.pi_a[0], proof.proof.pi_a[1]],
+ b: [
+ [pi_b[0][1], pi_b[0][0]],
+ [pi_b[1][1], pi_b[1][0]],
+ ],
+ c: [proof.proof.pi_c[0], proof.proof.pi_c[1]],
+ inputs: [proof.publicSignals.cmtRoot, proof.publicSignals.proposalId],
+ };
+
+ return await zkMultisig.create(content, salt, zkParams);
+}
+
+export async function vote(
+ zkMultisig: ZKMultisigMock,
+ sk1: bigint,
+ sk2: bigint,
+ proposalId: bigint,
+ forVote: boolean = true,
+ cmtProofRoot?: string,
+ cmtProofs?: ProofStructOutput[2],
+) {
+ const pk1 = mulPointEscalar(Base8, sk1);
+ const pk2 = mulPointEscalar(Base8, sk2);
+
+ const cmtProof1 = cmtProofs
+ ? cmtProofs[0]
+ : await zkMultisig.getParticipantsCMTProof(encodePoint({ x: pk1[0], y: pk1[1] }), proofSize);
+ const cmtProof2 = cmtProofs
+ ? cmtProofs[1]
+ : await zkMultisig.getParticipantsCMTProof(encodePoint({ x: pk2[0], y: pk2[1] }, 2), proofSize);
+
+ const encryptionKey = await zkMultisig.getEncryptionKey(proposalId);
+
+ const M = forVote ? Base8 : inf;
+
+ const k = randomNumber() % babyJubJubN;
+ const C1 = mulPointEscalar(Base8, k);
+ const C2 = addPoint(M, mulPointEscalar(encryptionKey, k));
+
+ const challenge = await zkMultisig.getProposalChallenge(proposalId);
+ const h1 = poseidonHash(ethers.toBeHex(challenge, 32));
+ const h2 = poseidonHash(h1);
+
+ const decryptionKeyShare = (BigInt(h1) * sk1 + BigInt(h2) * sk2) % babyJubJubN;
+
+ const keyNullifier = BigInt(poseidonHash(ethers.toBeHex(sk2, 32)));
+
+ const cmtRoot = cmtProofRoot ?? cmtProof1[0];
+
+ const rotationMsg = await zkMultisig.getRotationKDFMSGToSign(proposalId);
+
+ // mock signature
+ const newSk2 = (BigInt(rotationMsg) + sk2) % babyJubJubN;
+ const newPk2 = mulPointEscalar(Base8, newSk2);
+
+ const circuit: Voting = await zkit.getCircuit("Voting");
+
+ const proof = await circuit.generateProof({
+ encryptionKey,
+ challenge,
+ proposalId,
+ cmtRoot: BigInt(cmtProof1[0]),
+ sk1,
+ sk2,
+ newSk2,
+ vote: M,
+ k,
+ decryptionKeyShare,
+ siblings: [cmtProof1[1].map((h) => BigInt(h)), cmtProof2[1].map((h) => BigInt(h))],
+ siblingsLength: [numberToArray(BigInt(cmtProof1[2]), proofSize), numberToArray(BigInt(cmtProof2[2]), proofSize)],
+ directionBits: [
+ parseNumberToBitsArray(BigInt(cmtProof1[3]), BigInt(cmtProof1[2]) / 2n, proofSize),
+ parseNumberToBitsArray(BigInt(cmtProof2[3]), BigInt(cmtProof2[2]) / 2n, proofSize),
+ ],
+ });
+
+ const pi_b = proof.proof.pi_b;
+ const pub = proof.publicSignals;
+
+ const zkParams = {
+ a: [proof.proof.pi_a[0], proof.proof.pi_a[1]],
+ b: [
+ [pi_b[0][1], pi_b[0][0]],
+ [pi_b[1][1], pi_b[1][0]],
+ ],
+ c: [proof.proof.pi_c[0], proof.proof.pi_c[1]],
+ };
+
+ const encodedVote = ethers.AbiCoder.defaultAbiCoder().encode(["tuple(uint256,uint256)[2]"], [[C1, C2]]);
+
+ const rotationKey = {
+ x: newPk2[0],
+ y: newPk2[1],
+ };
+
+ const voteParams = {
+ encryptedVote: encodedVote,
+ decryptionKeyShare,
+ keyNullifier,
+ blinder: pub.blinder,
+ cmtRoot,
+ rotationKey,
+ proofData: zkParams,
+ };
+
+ const tx = await zkMultisig.vote(voteParams);
+
+ return {
+ tx,
+ decryptionKeyShare,
+ vote: [C1, C2],
+ newSk2,
+ newPk2,
+ };
+}
+
+export function aggregateDecryptionKeyShares(keyShares: bigint[]) {
+ let sum = 0n;
+
+ for (let i = 0; i < keyShares.length; i++) {
+ sum += keyShares[i];
+ }
+
+ return sum % babyJubJubN;
+}
+
+export function aggregateVotes(votes: [bigint, bigint][2][]) {
+ let sumC1 = inf;
+ let sumC2 = inf;
+
+ for (let i = 0; i < votes.length; i++) {
+ sumC1 = addPoint(sumC1, votes[i][0]);
+ sumC2 = addPoint(sumC2, votes[i][1]);
+ }
+
+ return [sumC1, sumC2];
+}
+
+export function aggregatePoints(points: [bigint, bigint][]) {
+ let sum = inf;
+
+ for (let i = 0; i < points.length; i++) {
+ sum = addPoint(sum, points[i]);
+ }
+
+ return sum;
+}
+
+export function parseNumberToBitsArray(num: bigint, expectedPathLen: bigint, desiredProofSize: number): number[] {
+ const binary = num.toString(2);
+
+ let resultArr = Array(desiredProofSize / 2).fill(0);
+
+ let j = 0;
+ for (let i = Math.abs(Number(expectedPathLen) - binary.length); i < Number(expectedPathLen); i++) {
+ resultArr[i] = Number(binary[j]);
+
+ j++;
+ }
+
+ return resultArr;
+}
+
+export function numberToArray(proofSiblingsLength: any, desiredProofSize: number) {
+ let siblingsLength = Array(desiredProofSize / 2).fill(0);
+
+ for (let i = 0; i < proofSiblingsLength / 2n; i++) {
+ siblingsLength[i] = 1;
+ }
+
+ return siblingsLength;
+}
+
+export function encodePoint(point: APointStruct, keyType: number = 1) {
+ const x1 = ethers.toBeHex(point.x, 32).slice(2);
+ const y1 = ethers.toBeHex(point.y, 32).slice(2);
+ const typeHash = ethers.toBeHex(keyType, 32).slice(2);
+
+ return poseidonHash("0x" + x1 + y1 + typeHash);
+}
+
+export function getBlinder(sk: bigint, proposalId: bigint) {
+ const skHex = ethers.toBeHex(sk, 32).slice(2);
+ const proposalIdHex = ethers.toBeHex(proposalId, 32).slice(2);
+
+ return BigInt(poseidonHash("0x" + skHex + proposalIdHex));
+}
+
+export function pointsToArray(points: APointStruct[]) {
+ return points.map(({ x, y }) => [x, y]);
+}
+
+export function arrayToPoints(arr: [bigint, bigint][]): APointStruct[] {
+ return arr.map(([x, y]) => ({ x, y }));
+}
+
+export function generateParticipants(length: number) {
+ const permanentKeys: APointStruct[] = [];
+ const rotationKeys: APointStruct[] = [];
+
+ let currentSk = 1;
+
+ for (let i = 0; i < length; i++) {
+ const permanentKey = mulPointEscalar(Base8, BigInt(currentSk++));
+ permanentKeys.push({
+ x: permanentKey[0],
+ y: permanentKey[1],
+ });
+
+ const rotationKey = mulPointEscalar(Base8, BigInt(currentSk++));
+ rotationKeys.push({
+ x: rotationKey[0],
+ y: rotationKey[1],
+ });
+ }
+
+ return [permanentKeys, rotationKeys];
+}
+
+export function getCumulativeKeys(permanentPoints: APointStruct[], rotationPoints: APointStruct[]) {
+ const sumPoints = (points: APointStruct[]): Point => {
+ return points.reduce>(
+ (acc, p) => {
+ const point: Point = [BigInt(p.x), BigInt(p.y)];
+ return addPoint(acc, point);
+ },
+ [0n, 1n],
+ );
+ };
+
+ const permPointsSum = sumPoints(permanentPoints);
+ const rotPointsSum = sumPoints(rotationPoints);
+
+ return [permPointsSum, rotPointsSum];
+}