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