diff --git a/README.md b/README.md index 5c4710ced9..640f91bc3b 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,9 @@ For development purposes, you can generate the proving and verifying keys for the zk-SNARK circuits, along with their Solidity verifier contracts as such. Navigate to the rapidsnark [repo](https://github.com/iden3/rapidsnark) to install the necessary tooling. +More details can be found in /docs/installation.md, Section Install `rapidsnark`; -Build the zk-SNARKs and generate their proving and verifying keys: +To build the circom circuits, follow the /docs/installation.md, Section Configure circom-helper and zkey-manager. Then run: ```bash cd circuits @@ -88,6 +89,9 @@ npm run compileSol Avoid using `npx hardhat compile` and instead use the provided command as artifacts are copied into their relevant directories. +To build the zk-SNARKs and generate their proving and verifying keys, follow the instructions from /docs/installation.md, +Section: Generate `.zkey` files. + ### Local development This repository is organised as Lerna submodules. Each submodule contains its @@ -121,12 +125,14 @@ For example: ### Testing +It is implied that the previous steps have been completed before running tests. + ### Unit tests The following submodules contain unit tests: `core`, `crypto`, `circuits`, `contracts`, and `domainobjs`. -Except for the `contracts` submodule, run unit tests as such (the following +Except for the `contracts` and `circuits` submodule, run unit tests as such (the following example is for `crypto`): ```bash @@ -134,10 +140,28 @@ cd crypto npm run test ``` -For `contracts` and `integrationTests`, run the tests one by one. This prevents -incorrect nonce errors. +For `circuits`, first build the zk-SNARKs as explained above and then run in one terminal: -First, start a Hardhat instance in a separate terminal: +```bash +cd circuits +npm run circom-helper +``` +wait for *Launched JSON-RPC server at port 9001* message and then run in another terminal: + +```bash +cd circuits +npm run test +``` + +Note that some tests might fail due to jest timeout. You can fix this by adjusting the timeout period defined at the top of the test file. + +For example, in the file *MessageToCommand.test.ts*, increase timeout_in_ms on this line: ```jest.setTimeout()```. + +For `contracts` and `integrationTests`, run the tests one by one. This prevents incorrect nonce errors. + +For `contracts`, first, compile the contracts as explained above. + +Then, start a Hardhat instance in a separate terminal: ```bash cd contracts @@ -170,17 +194,32 @@ cd contracts ./scripts/runTestsInCi.sh ``` -Or run all integration tests (this also starts its own Hardhat instance): +For `integrationTests`, first make sure to install necessary tooling for rapidsnark as explained above, build the zk-SNARKs and generate their proving and verifying keys. + +Run all integration tests (this also starts its own Hardhat instance so make sure to kill any running hardhat instance): ```bash cd integrationTests ./scripts/runTestsInCi.sh ``` -You can ignore the Hardhat errors which this script emits as you should already -have Hardhat running in a separate terminal. Otherwise, you will have to exit -Ganache using the `kill` command. +### CLI tests + +Make sure dependencies are installed, circuits are built, zkeys keys generated and contract compiled. +First run hardhat: + +```bash +cd contracts +npm run hardhat +``` + +Then navigate to /cli/tests/vanilla and execute each test like this: +```bash +cd cli/tests/vanilla +bash ./test1.sh +``` +You can find more details about running cli tests in /docs/testing.md. ### Docker diff --git a/circom b/circom new file mode 160000 index 0000000000..ce903c60fd --- /dev/null +++ b/circom @@ -0,0 +1 @@ +Subproject commit ce903c60fd1754b2c84525b06d2617bc657df211 diff --git a/circuits/circom/isDeactivatedKey.circom b/circuits/circom/isDeactivatedKey.circom index 2b61b1b76d..a2c9b1aee7 100644 --- a/circuits/circom/isDeactivatedKey.circom +++ b/circuits/circom/isDeactivatedKey.circom @@ -11,27 +11,41 @@ template IsDeactivatedKey(levels) { signal input key[2]; // Ciphertext of the encrypted key status - signal input c1; - signal input c2; + signal input c1[2]; + signal input c2[2]; + + signal input salt; signal input path_index[levels]; signal input path_elements[levels][LEAVES_PER_PATH_LEVEL]; signal output isDeactivated; signal output computedRoot; - // Hash public key x and y coordinates + // Hash public key x and y coordinates with salt: hash(key[0], key[1], salt) signal keyHash; - component keyHasher = PoseidonHashT5(); + + // Tree leaf hash: hash(keyHash, c1[0], c1[1], c2[0], c2[1]) + signal leafHash; + + component keyHasher = PoseidonHashT4(); keyHasher.inputs[0] <== key[0]; keyHasher.inputs[1] <== key[1]; - keyHasher.inputs[2] <== c1; - keyHasher.inputs[3] <== c2; + keyHasher.inputs[2] <== salt; keyHash <== keyHasher.out; + component leafHasher = PoseidonHashT6(); + leafHasher.inputs[0] <== keyHash; + leafHasher.inputs[1] <== c1[0]; + leafHasher.inputs[2] <== c1[1]; + leafHasher.inputs[3] <== c2[0]; + leafHasher.inputs[4] <== c2[1]; + + leafHash <== leafHasher.out; + // Compute root for the given proof params component incProof = QuinTreeInclusionProof(levels); - incProof.leaf <== keyHash; + incProof.leaf <== leafHash; for (var i = 0; i < levels; i++) { incProof.path_index[i] <== path_index[i]; diff --git a/circuits/circom/messageValidator.circom b/circuits/circom/messageValidator.circom index 6d00cde55f..fcd3259b19 100644 --- a/circuits/circom/messageValidator.circom +++ b/circuits/circom/messageValidator.circom @@ -1,6 +1,8 @@ pragma circom 2.0.0; -include "./verifySignature.circom"; include "./utils.circom"; +include "./verifySignature.circom"; +include "../node_modules/circomlib/circuits/comparators.circom"; +include "../node_modules/circomlib/circuits/comparators.circom"; template MessageValidator() { // a) Whether the state leaf index is valid diff --git a/circuits/circom/processDeactivationMessages.circom b/circuits/circom/processDeactivationMessages.circom new file mode 100644 index 0000000000..3778368023 --- /dev/null +++ b/circuits/circom/processDeactivationMessages.circom @@ -0,0 +1,316 @@ +pragma circom 2.0.0; + +include "../node_modules/circomlib/circuits/bitify.circom"; +include "../node_modules/circomlib/circuits/comparators.circom"; +include "../node_modules/circomlib/circuits/escalarmulany.circom"; + +include "./trees/incrementalQuinTree.circom"; +include "./poseidon/poseidonHashT3.circom"; +include "./elGamalEncryption.circom"; +include "./isDeactivatedKey.circom"; +include "./messageToCommand.circom"; +include "./verifySignature.circom"; +include "./messageHasher.circom"; +include "./hasherSha256.circom"; + +template ProcessDeactivationMessages(msgQueueSize, stateTreeDepth) { + var MSG_LENGTH = 11; + var TREE_ARITY = 5; + var STATE_LEAF_LENGTH = 4; + var msgTreeZeroValue = 8370432830353022751713833565135785980866757267633941821328460903436894336785; + + // Hash of all public inputs + signal input inputHash; + + // Chain hash of all deactivation messages + signal input chainHash; + + // Coordinator's key + signal input coordPrivKey; + signal input coordPubKey[2]; + + // Encryption keys for each message + signal input encPubKeys[msgQueueSize][2]; + + // Key deactivation messages + signal input msgs[msgQueueSize][MSG_LENGTH]; + + // Inclusion proof path elements for deactivated keys + signal input deactivatedTreePathElements[msgQueueSize][stateTreeDepth][TREE_ARITY - 1]; + + // Inclusion proof path elements for state tree leaves + signal input stateLeafPathElements[msgQueueSize][stateTreeDepth][TREE_ARITY - 1]; + + // State leaves for each message + signal input currentStateLeaves[msgQueueSize][STATE_LEAF_LENGTH]; + + // ElGamal ciphertext values of key deactivation statuses for each message + signal input elGamalEnc[msgQueueSize][2][2]; + + // ElGamal randomness + signal input maskingValues[msgQueueSize]; + + // Root hash of deactivated keys tree + signal input deactivatedTreeRoot; + + // State tree root hash + signal input currentStateRoot; + + // Total number of signups + signal input numSignUps; + + // Incremental array of root hashes + signal messageHashes[msgQueueSize + 1]; + messageHashes[0] <== msgTreeZeroValue; + + // Hash selectors + component hashMuxes[msgQueueSize]; + + // Process each message + component processor[msgQueueSize]; + for (var i = 0; i < msgQueueSize; i++) { + processor[i] = ProcessSingleDeactivationMessage(stateTreeDepth, TREE_ARITY); + processor[i].currentMessageIndex <== i; + processor[i].prevHash <== messageHashes[i]; + processor[i].deactivatedTreeRoot <== deactivatedTreeRoot; + processor[i].numSignUps <== numSignUps; + + for (var j = 0; j < MSG_LENGTH; j++) { + processor[i].msg[j] <== msgs[i][j]; + } + + processor[i].coordPrivKey <== coordPrivKey; + processor[i].coordPubKey[0] <== coordPubKey[0]; + processor[i].coordPubKey[1] <== coordPubKey[1]; + processor[i].encPubKey[0] <== encPubKeys[i][0]; + processor[i].encPubKey[1] <== encPubKeys[i][1]; + processor[i].currentStateRoot <== currentStateRoot; + processor[i].maskingValue <== maskingValues[i]; + + // Copy deactivated tree path elements and state tree path elements + for (var j = 0; j < stateTreeDepth; j++) { + for (var k = 0; k < TREE_ARITY - 1; k++) { + processor[i].deactivatedTreePathElements[j][k] <== deactivatedTreePathElements[i][j][k]; + processor[i].stateLeafPathElements[j][k] <== stateLeafPathElements[i][j][k]; + } + } + + // Copy state leaves + for (var j = 0; j < STATE_LEAF_LENGTH; j++) { + processor[i].stateLeaf[j] <== currentStateLeaves[i][j]; + } + + // Copy c1 and c2 enc values + processor[i].c1[0] <== elGamalEnc[i][0][0]; + processor[i].c1[1] <== elGamalEnc[i][0][1]; + + processor[i].c2[0] <== elGamalEnc[i][1][0]; + processor[i].c2[1] <== elGamalEnc[i][1][1]; + + hashMuxes[i] = Mux1(); + hashMuxes[i].s <== processor[i].isValid; + + hashMuxes[i].c[0] <== messageHashes[i]; + hashMuxes[i].c[1] <== processor[i].newHash; + + messageHashes[i + 1] <== hashMuxes[i].out; + } + + // Verify chain hash + // newMessageChainHash <== messageHashes[msgQueueSize]; + chainHash === messageHashes[msgQueueSize]; + + component inputHasher = Sha256Hasher4(); + inputHasher.in[0] <== deactivatedTreeRoot; + inputHasher.in[1] <== numSignUps; + inputHasher.in[2] <== currentStateRoot; + inputHasher.in[3] <== chainHash; + + // Verify input hash + inputHasher.hash === inputHash; +} + +template ProcessSingleDeactivationMessage(stateTreeDepth, treeArity) { + var MSG_LENGTH = 11; + var STATE_LEAF_LENGTH = 4; + var PACKED_CMD_LENGTH = 4; + + // Decrypt message into a command + signal input prevHash; + signal input msg[MSG_LENGTH]; + signal input coordPrivKey; + signal input coordPubKey[2]; + signal input encPubKey[2]; + signal input maskingValue; + signal input c1[2]; + signal input c2[2]; + signal input numSignUps; + signal input deactivatedTreeRoot; + signal input deactivatedTreePathElements[stateTreeDepth][treeArity - 1]; + signal input stateLeafPathElements[stateTreeDepth][treeArity - 1]; + signal input stateLeaf[STATE_LEAF_LENGTH]; + signal input currentStateRoot; + signal input currentMessageIndex; + + signal output isValid; + signal output newHash; + + // Decode message + // ------------------------------------ + component command = MessageToCommand(); + command.encPrivKey <== coordPrivKey; + command.encPubKey[0] <== encPubKey[0]; + command.encPubKey[1] <== encPubKey[1]; + + for (var j = 0; j < MSG_LENGTH; j ++) { + command.message[j] <== msg[j]; + } + + signal messageType <== msg[0]; + component isValidMessageType = IsEqual(); + isValidMessageType.in[0] <== 1; + isValidMessageType.in[1] <== messageType; + + // ------------------------------------ + // Verify if pubkey value is (0,0) + component isPubKeyValid = IsEqual(); + isPubKeyValid.in[0] <== 0; + isPubKeyValid.in[1] <== command.newPubKey[0] + command.newPubKey[1]; + + // Verify if voteWeight is 0 + component isVoteWeightValid = IsEqual(); + isVoteWeightValid.in[0] <== 0; + isVoteWeightValid.in[1] <== command.newVoteWeight; + + // Verify message signature + component validSignature = VerifySignature(); + validSignature.pubKey[0] <== stateLeaf[0]; + validSignature.pubKey[1] <== stateLeaf[1]; + validSignature.R8[0] <== command.sigR8[0]; + validSignature.R8[1] <== command.sigR8[1]; + validSignature.S <== command.sigS; + for (var i = 0; i < PACKED_CMD_LENGTH; i ++) { + validSignature.preimage[i] <== command.packedCommandOut[i]; + } + + // Verify that the state leaf exists in the given state root + // ------------------------------------------------------------------ + component stateLeafQip = QuinTreeInclusionProof(stateTreeDepth); + component stateLeafHasher = Hasher4(); + for (var i = 0; i < STATE_LEAF_LENGTH; i++) { + stateLeafHasher.in[i] <== stateLeaf[i]; + } + + component validStateLeafIndex = LessEqThan(252); + validStateLeafIndex.in[0] <== command.stateIndex; + validStateLeafIndex.in[1] <== numSignUps; + + component indexMux = Mux1(); + indexMux.s <== validStateLeafIndex.out; + indexMux.c[0] <== 0; + indexMux.c[1] <== command.stateIndex; + + component stateLeafPathIndices = QuinGeneratePathIndices(stateTreeDepth); + stateLeafPathIndices.in <== indexMux.out; + + stateLeafQip.leaf <== stateLeafHasher.hash; + for (var i = 0; i < stateTreeDepth; i ++) { + stateLeafQip.path_index[i] <== stateLeafPathIndices.out[i]; + for (var j = 0; j < treeArity - 1; j++) { + stateLeafQip.path_elements[i][j] <== stateLeafPathElements[i][j]; + } + } + + component stateLeafValid = IsEqual(); + stateLeafValid.in[0] <== stateLeafQip.root; + stateLeafValid.in[1] <== currentStateRoot; + + component isDataValid = IsEqual(); + isDataValid.in[0] <== 4; + isDataValid.in[1] <== isPubKeyValid.out + isVoteWeightValid.out + validSignature.valid + stateLeafValid.out; + + // Compute ElGamal encryption + // -------------------------- + component elGamalBit = ElGamalEncryptBit(); + elGamalBit.pk[0] <== coordPubKey[0]; + elGamalBit.pk[1] <== coordPubKey[1]; + elGamalBit.k <== maskingValue; + elGamalBit.m <== isValidMessageType.out * isDataValid.out; + + component isC10Valid = IsEqual(); + component isC11Valid = IsEqual(); + component isC20Valid = IsEqual(); + component isC21Valid = IsEqual(); + component isEncryptionValid = IsEqual(); + + // Validate C1 + isC10Valid.in[0] <== elGamalBit.kG[0]; + isC10Valid.in[1] <== c1[0]; + + isC11Valid.in[0] <== elGamalBit.kG[1]; + isC11Valid.in[1] <== c1[1]; + + // Validate C2 + isC20Valid.in[0] <== elGamalBit.Me[0]; + isC20Valid.in[1] <== c2[0]; + + isC21Valid.in[0] <== elGamalBit.Me[1]; + isC21Valid.in[1] <== c2[1]; + + // Validate C1 and C2 + isEncryptionValid.in[0] <== isC10Valid.out + isC11Valid.out + isC20Valid.out + isC21Valid.out; + isEncryptionValid.in[1] <== 4; + + isEncryptionValid.out === 1; + + // -------------------------- + // Compute deactivated key leaf hash + component deactivatedLeafHasher = PoseidonHashT4(); + deactivatedLeafHasher.inputs[0] <== stateLeaf[0]; + deactivatedLeafHasher.inputs[1] <== stateLeaf[1]; + deactivatedLeafHasher.inputs[2] <== command.salt; + + // Verify that the deactivated leaf exists in the given deactivated keys root + // -------------------------------------------------------------------------- + // Get inclusion proof path indices for deactivated keys tree + component deactLeafPathIndices = QuinGeneratePathIndices(stateTreeDepth); + deactLeafPathIndices.in <== currentMessageIndex; + + component isInDeactivated = IsDeactivatedKey(stateTreeDepth); + isInDeactivated.root <== deactivatedTreeRoot; + isInDeactivated.key[0] <== stateLeaf[0]; + isInDeactivated.key[1] <== stateLeaf[1]; + isInDeactivated.c1[0] <== c1[0]; + isInDeactivated.c1[1] <== c1[1]; + isInDeactivated.c2[0] <== c2[0]; + isInDeactivated.c2[1] <== c2[1]; + isInDeactivated.salt <== command.salt; + + for (var i = 0; i < stateTreeDepth; i ++) { + isInDeactivated.path_index[i] <== deactLeafPathIndices.out[i]; + for (var j = 0; j < treeArity - 1; j++) { + isInDeactivated.path_elements[i][j] <== deactivatedTreePathElements[i][j]; + } + } + + isInDeactivated.isDeactivated === isValidMessageType.out; + // ------------------------------------------------------------------ + // Compute new "root" hash + // ------------------------------------------- + // Hashing message + component messageHasher = MessageHasher(); + for (var j = 0; j < MSG_LENGTH; j ++) { + messageHasher.in[j] <== msg[j]; + } + messageHasher.encPubKey[0] <== encPubKey[0]; + messageHasher.encPubKey[1] <== encPubKey[1]; + + // Hashing previous hash and message hash + component newChainHash = PoseidonHashT3(); + newChainHash.inputs[0] <== prevHash; + newChainHash.inputs[1] <== messageHasher.hash; + + isValid <== isValidMessageType.out; + newHash <== newChainHash.out; + // ------------------------------------------- +} \ No newline at end of file diff --git a/circuits/circom/test/elGamalDecryption_ElGamalDecryptBit_test.circom b/circuits/circom/test/elGamalDecryption_ElGamalDecryptBit_test.circom new file mode 100644 index 0000000000..49656ad448 --- /dev/null +++ b/circuits/circom/test/elGamalDecryption_ElGamalDecryptBit_test.circom @@ -0,0 +1,4 @@ +pragma circom 2.0.0; +include "../elGamalDecryption.circom"; + +component main {public [kG, Me]} = ElGamalDecryptBit(); diff --git a/circuits/circom/test/elGamalDecryption_ElGamalDecryptPoint_test.circom b/circuits/circom/test/elGamalDecryption_ElGamalDecryptPoint_test.circom new file mode 100644 index 0000000000..d7b59e7dca --- /dev/null +++ b/circuits/circom/test/elGamalDecryption_ElGamalDecryptPoint_test.circom @@ -0,0 +1,4 @@ +pragma circom 2.0.0; +include "../elGamalDecryption.circom"; + +component main {public [kG, Me]} = ElGamalDecryptPoint(); diff --git a/circuits/circom/test/elGamalEncryption_ElGamalEncryptBit_test.circom b/circuits/circom/test/elGamalEncryption_ElGamalEncryptBit_test.circom new file mode 100644 index 0000000000..71822d9b26 --- /dev/null +++ b/circuits/circom/test/elGamalEncryption_ElGamalEncryptBit_test.circom @@ -0,0 +1,4 @@ +pragma circom 2.0.0; +include "../elGamalEncryption.circom"; + +component main = ElGamalEncryptBit(); diff --git a/circuits/circom/test/elGamalEncryption_ElGamalEncryptPoint_test.circom b/circuits/circom/test/elGamalEncryption_ElGamalEncryptPoint_test.circom new file mode 100644 index 0000000000..29bb7b1e9c --- /dev/null +++ b/circuits/circom/test/elGamalEncryption_ElGamalEncryptPoint_test.circom @@ -0,0 +1,4 @@ +pragma circom 2.0.0; +include "../elGamalEncryption.circom"; + +component main = ElGamalEncryptPoint(); diff --git a/circuits/circom/test/processDeactivationMessages_test.circom b/circuits/circom/test/processDeactivationMessages_test.circom new file mode 100644 index 0000000000..f50a244b1d --- /dev/null +++ b/circuits/circom/test/processDeactivationMessages_test.circom @@ -0,0 +1,4 @@ +pragma circom 2.0.0; +include "../processDeactivationMessages.circom"; + +component main {public [inputHash]} = ProcessDeactivationMessages(3, 10); \ No newline at end of file diff --git a/circuits/circomHelperConfig.json b/circuits/circomHelperConfig.json index 1303456cc4..933b4cb53c 100644 --- a/circuits/circomHelperConfig.json +++ b/circuits/circomHelperConfig.json @@ -2,7 +2,6 @@ "circom": "../../.local/bin/circom", "snarkjs": "./node_modules/snarkjs/build/cli.cjs", "circuitDirs": [ - "./circom/prod/", "./circom/test/" ] -} +} \ No newline at end of file diff --git a/circuits/package-lock.json b/circuits/package-lock.json index ef4e8fa262..7ff7eed240 100644 --- a/circuits/package-lock.json +++ b/circuits/package-lock.json @@ -2494,9 +2494,10 @@ } }, "node_modules/content-type": { - "version": "1.0.4", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6872,9 +6873,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -10262,7 +10263,7 @@ "circomlib": { "version": "git+ssh://git@github.com/weijiekoh/circomlib.git#ac85e82c1914d47789e2032fb11ceb2cfdd38a2b", "integrity": "sha512-lPyqAOuoazs1He7EDtNssPtp+1KDdVVjy5gWFzlwmtIWGvzeIP33RS8CN/PABBF/3frgKE2s9J7Hti5z9Mggow==", - "from": "circomlib@github:weijiekoh/circomlib#ac85e82c1914d47789e2032fb11ceb2cfdd38a2b" + "from": "circomlib@https://github.com/weijiekoh/circomlib#ac85e82c1914d47789e2032fb11ceb2cfdd38a2b" }, "cjs-module-lexer": { "version": "0.6.0", @@ -10395,7 +10396,9 @@ "dev": true }, "content-type": { - "version": "1.0.4", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true }, "convert-source-map": { @@ -13257,9 +13260,9 @@ } }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "requires": { "bytes": "3.1.2", diff --git a/circuits/package.json b/circuits/package.json index c4045d8bfe..f5ab721260 100644 --- a/circuits/package.json +++ b/circuits/package.json @@ -5,7 +5,7 @@ "main": "build/index.js", "scripts": { "circom-helper": "circom-helper -c ./circomHelperConfig.json -b ./build/test/ -p 9001 -nc", - "build-test-circuits": "NODE_OPTIONS=--max-old-space-size=16384 circom-helper -c ./circomHelperConfig.json -y -nc -b ./build/test/ -p 9001 -m 16384 -s 1048576", + "build-test-circuits": "NODE_OPTIONS=--max-old-space-size=16384 circom-helper -c ./circomHelperConfig.json -y -nc -b ./build/test/ -p 9001", "watch": "tsc --watch", "build": "tsc", "test": "jest", @@ -47,6 +47,8 @@ "test-calculateTotal-debug": "node --inspect-brk ./node_modules/.bin/jest CalculateTotal.test.ts", "test-processMessages": "NODE_OPTIONS=--max-old-space-size=4096 jest ts/__tests__/ProcessMessages.test.ts", "test-processMessages-debug": "NODE_OPTIONS=--max-old-space-size=4096 node --inspect-brk ./node_modules/.bin/jest ts/__tests__/ProcessMessages.test.ts", + "test-processDeactivationMessages": "NODE_OPTIONS=--max-old-space-size=4096 jest ts/__tests__/ProcessDeactivationMessages.test.ts", + "test-processDeactivationMessages-debug": "NODE_OPTIONS=--max-old-space-size=4096 node --inspect-brk ./node_modules/.bin/jest ts/__tests__/ProcessDeactivationMessages.test.ts", "test-tallyVotes": "NODE_OPTIONS=--max-old-space-size=4096 jest ts/__tests__/TallyVotes.test.ts", "test-tallyVotes-debug": "NODE_OPTIONS=--max-old-space-size=4096 node --inspect-brk ./node_modules/.bin/jest ts/__tests__/TallyVotes.test.ts" }, diff --git a/circuits/ts/__tests__/ElGamalEncryption.test.ts b/circuits/ts/__tests__/ElGamalEncryption.test.ts index a83edf46ca..38a3836af6 100644 --- a/circuits/ts/__tests__/ElGamalEncryption.test.ts +++ b/circuits/ts/__tests__/ElGamalEncryption.test.ts @@ -2,23 +2,60 @@ jest.setTimeout(900000) import { Keypair, } from 'maci-domainobjs' -import { stringifyBigInts, genRandomSalt } from 'maci-crypto' +import { stringifyBigInts, genRandomSalt, elGamalEncryptBit } from 'maci-crypto' import { genWitness, getSignalByName, } from './utils' -describe('ElGamal encryption and decryption', () => { - const encCircuit = 'elGamalEncryption_test' - const decCircuit = 'elGamalDecryption_test' +describe('ElGamal (de)/(en)cryption - bit', () => { + const encCircuit = 'elGamalEncryption_ElGamalEncryptBit_test' + const decCircuit = 'elGamalDecryption_ElGamalDecryptBit_test' it('Should output the input bit from the composition of encryption and decryption', async () => { - const keypair = new Keypair() - - for (let bit = 0; bit < 2; bit ++ ) { + const keypair = new Keypair(); + const bit = 0; + + const k = BigInt(365); + const [c1, c2] = elGamalEncryptBit( + keypair.pubKey.rawPubKey, + BigInt(bit), + k, + ); + + console.log(keypair.pubKey.rawPubKey); + console.log(keypair.pubKey.asCircuitInputs()); + + const encCircuitInputs = stringifyBigInts({ + k, + m: BigInt(bit), + pk: keypair.pubKey.rawPubKey, + }) + + const encWitness = await genWitness(encCircuit, encCircuitInputs) + + const Me = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[1]`)), + ]; + + const kG = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[1]`)), + ]; + + console.log(kG, Me); + console.log(c1, c2) + }); + + it('should encrypt and decrypt the 0 and 1 bit correctly', async () => { + const keypair = new Keypair() + + for (let bit = 0; bit < 2; bit++) { // Encryption const k = genRandomSalt(); - const encCircuitInputs = stringifyBigInts({ + + const encCircuitInputs = stringifyBigInts({ k, m: bit, pk: keypair.pubKey.asCircuitInputs(), @@ -37,8 +74,8 @@ describe('ElGamal encryption and decryption', () => { ]; // Decryption - const decCircuitInputs = stringifyBigInts({ - kG, + const decCircuitInputs = stringifyBigInts({ + kG, Me, sk: keypair.privKey.asCircuitInputs(), }) @@ -49,4 +86,701 @@ describe('ElGamal encryption and decryption', () => { expect(dBit).toEqual(BigInt(bit)); } }) + + it('should return different ciphertexts for the same message when using different public keys', async () => { + const k = genRandomSalt(); + + const keypair1 = new Keypair(); + const keypair2 = new Keypair(); + const bit = 1 + + // Encryption with public key 1. + const encCircuitInputs1 = stringifyBigInts({ + k, + m: bit, + pk: keypair1.pubKey.asCircuitInputs(), + }); + + const encWitness1 = await genWitness(encCircuit, encCircuitInputs1); + + const Me1 = [ + BigInt(await getSignalByName(encCircuit, encWitness1, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness1, `main.Me[1]`)), + ]; + + // Encryption with public key 2. + const encCircuitInputs2 = stringifyBigInts({ + k, + m: bit, + pk: keypair2.pubKey.asCircuitInputs(), + }); + + const encWitness2 = await genWitness(encCircuit, encCircuitInputs2); + + const Me2 = [ + BigInt(await getSignalByName(encCircuit, encWitness2, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness2, `main.Me[1]`)), + ]; + + expect(Me1).not.toEqual(Me2); + }) + + it('should not be possible to decrypt with the wrong public key', async () => { + const k = genRandomSalt(); + + const keypair1 = new Keypair(); + const keypair2 = new Keypair(); + + // Encrypt + const encCircuitInputs = stringifyBigInts({ + k, + m: 1, + pk: keypair1.pubKey.asCircuitInputs(), + }); + + const encWitness = await genWitness(encCircuit, encCircuitInputs); + + const Me = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[1]`)), + ]; + + const kG = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[1]`)), + ]; + + // Decrypt with correct private key + const decCircuitInputs1 = stringifyBigInts({ + kG, + Me, + sk: keypair1.privKey.asCircuitInputs(), + }); + + const decWitness1 = await genWitness(decCircuit, decCircuitInputs1); + + const dBit1 = BigInt(await getSignalByName(decCircuit, decWitness1, `main.m`)); + + expect(dBit1).toEqual(BigInt(1)); + + // Decrypt with wrong private key + const decCircuitInputs2 = stringifyBigInts({ + kG, + Me, + sk: keypair2.privKey.asCircuitInputs(), + }); + + const decWitness2 = await genWitness(decCircuit, decCircuitInputs2); + + const dBit2 = BigInt(await getSignalByName(decCircuit, decWitness2, `main.m`)); + + expect(dBit2).not.toEqual(BigInt(1)); + }) + + it('should return the correct plaintext bit for randomly generated inputs', async () => { + for (let i = 0; i < 10; i++) { + const k = genRandomSalt(); + const m = Math.round(Math.random()); + const keypair = new Keypair(); + + // Encrypt + const encCircuitInputs = stringifyBigInts({ + k, + m, + pk: keypair.pubKey.asCircuitInputs(), + }) + + const encWitness = await genWitness(encCircuit, encCircuitInputs) + + const Me = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[1]`)), + ]; + + const kG = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[1]`)), + ]; + + // Decrypt + const decCircuitInputs = stringifyBigInts({ + kG, + Me, + sk: keypair.privKey.asCircuitInputs(), + }) + const decWitness = await genWitness(decCircuit, decCircuitInputs) + const dBit = BigInt(await getSignalByName(decCircuit, decWitness, `main.m`)) + + expect(dBit).toEqual(BigInt(m)) + } + }) + + /** New */ + // TODO: Check why await expect(genWitness(decCircuit, decCircuitInputs)).rejects.toThrow() fails + it.skip('should return zero when someone tries to encrypt a non-number message', async () => { + const keypair = new Keypair() + + // Encryption + const k = genRandomSalt(); + + const encCircuitInputs = stringifyBigInts({ + k, + m: "n0-b1t", + pk: keypair.pubKey.asCircuitInputs(), + }) + + const encWitness = await genWitness(encCircuit, encCircuitInputs) + + const Me = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[1]`)), + ]; + + const kG = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[1]`)), + ]; + + // Decryption + const decCircuitInputs = stringifyBigInts({ + kG, + Me, + sk: keypair.privKey.asCircuitInputs(), + }) + + await expect(genWitness(decCircuit, decCircuitInputs)).rejects.toThrow(); + }) + + it('should encrypt and decrypt with different random salts', async () => { + const keypair = new Keypair(); + const bit = 1; + + // Encryption with random salt 1 + const k1 = genRandomSalt(); + const encCircuitInputs1 = stringifyBigInts({ + k: k1, + m: bit, + pk: keypair.pubKey.asCircuitInputs(), + }); + const encWitness1 = await genWitness(encCircuit, encCircuitInputs1); + const Me1 = [ + BigInt(await getSignalByName(encCircuit, encWitness1, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness1, `main.Me[1]`)), + ]; + const kG1 = [ + BigInt(await getSignalByName(encCircuit, encWitness1, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness1, `main.kG[1]`)), + ]; + + // Encryption with random salt 2 + const k2 = genRandomSalt(); + const encCircuitInputs2 = stringifyBigInts({ + k: k2, + m: bit, + pk: keypair.pubKey.asCircuitInputs(), + }); + const encWitness2 = await genWitness(encCircuit, encCircuitInputs2); + const Me2 = [ + BigInt(await getSignalByName(encCircuit, encWitness2, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness2, `main.Me[1]`)), + ]; + const kG2 = [ + BigInt(await getSignalByName(encCircuit, encWitness2, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness2, `main.kG[1]`)), + ]; + + // Decryption with random salt 1 + const decCircuitInputs1 = stringifyBigInts({ + kG: kG1, + Me: Me1, + sk: keypair.privKey.asCircuitInputs(), + }); + const decWitness1 = await genWitness(decCircuit, decCircuitInputs1); + const dBit1 = BigInt(await getSignalByName(decCircuit, decWitness1, `main.m`)); + expect(dBit1).toEqual(BigInt(bit)); + + // Decryption with random salt 2 + const decCircuitInputs2 = stringifyBigInts({ + kG: kG2, + Me: Me2, + sk: keypair.privKey.asCircuitInputs(), + }); + const decWitness2 = await genWitness(decCircuit, decCircuitInputs2); + const dBit2 = BigInt(await getSignalByName(decCircuit, decWitness2, `main.m`)); + expect(dBit2).toEqual(BigInt(bit)); + }); +}) + +describe('ElGamal (de)/(en)cryption - point', () => { + const encCircuit = 'elGamalEncryption_ElGamalEncryptPoint_test' + const decCircuit = 'elGamalDecryption_ElGamalDecryptPoint_test' + + it('should encrypt and decrypt the (0,1) input point correctly', async () => { + const keypair = new Keypair() + + // Encryption + const k = genRandomSalt(); + const encCircuitInputs = stringifyBigInts({ + k, + M: [0, 1], + pk: keypair.pubKey.asCircuitInputs(), + }) + + const encWitness = await genWitness(encCircuit, encCircuitInputs) + + const Me = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[1]`)), + ]; + + const kG = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[1]`)), + ]; + + // Decryption + const decCircuitInputs = stringifyBigInts({ + kG, + Me, + sk: keypair.privKey.asCircuitInputs(), + }) + + const decWitness = await genWitness(decCircuit, decCircuitInputs) + const m0Bit = BigInt(await getSignalByName(decCircuit, decWitness, `main.M[0]`)); + const m1Bit = BigInt(await getSignalByName(decCircuit, decWitness, `main.M[1]`)); + + expect(m0Bit).toEqual(BigInt(0)); + expect(m1Bit).toEqual(BigInt(1)); + }) + + it('should encrypt and decrypt the (0,0) input point correctly', async () => { + const keypair = new Keypair() + + // Encryption + const k = genRandomSalt(); + const encCircuitInputs = stringifyBigInts({ + k, + M: [0, 0], + pk: keypair.pubKey.asCircuitInputs(), + }) + + const encWitness = await genWitness(encCircuit, encCircuitInputs) + + const Me = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[1]`)), + ]; + + const kG = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[1]`)), + ]; + + // Decryption + const decCircuitInputs = stringifyBigInts({ + kG, + Me, + sk: keypair.privKey.asCircuitInputs(), + }) + + const decWitness = await genWitness(decCircuit, decCircuitInputs) + const m0Bit = BigInt(await getSignalByName(decCircuit, decWitness, `main.M[0]`)); + const m1Bit = BigInt(await getSignalByName(decCircuit, decWitness, `main.M[1]`)); + + expect(m0Bit).toEqual(BigInt(0)); + expect(m1Bit).toEqual(BigInt(0)); + }) + + it('should encrypt and decrypt a point in the curve which has been derived from a keypair', async () => { + const keypair = new Keypair() + const keyPairForCurvePoints = new Keypair() + const [c1, c2] = keyPairForCurvePoints.pubKey.rawPubKey; // key is used here to get c1 and c2 we know are part of the curve + + // Encryption + const k = genRandomSalt(); + const encCircuitInputs = stringifyBigInts({ + k, + M: [c1, c2], + pk: keypair.pubKey.asCircuitInputs(), + }) + + const encWitness = await genWitness(encCircuit, encCircuitInputs) + + const Me = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[1]`)), + ]; + + const kG = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[1]`)), + ]; + + // Decryption + const decCircuitInputs = stringifyBigInts({ + kG, + Me, + sk: keypair.privKey.asCircuitInputs(), + }) + + const decWitness = await genWitness(decCircuit, decCircuitInputs) + const m0Bit = BigInt(await getSignalByName(decCircuit, decWitness, `main.M[0]`)); + const m1Bit = BigInt(await getSignalByName(decCircuit, decWitness, `main.M[1]`)); + + expect(m0Bit).toEqual(c1); + expect(m1Bit).toEqual(c2); + }) + + it('should not be possible to encrypt and decrypt a point as (1,0) or (1,1) which is not in the curve', async () => { + const keypair = new Keypair() + + // Encryption + const k1 = genRandomSalt(); + const k2 = genRandomSalt(); + const encCircuitInputs1 = stringifyBigInts({ + k: k1, + M: [1, 0], + pk: keypair.pubKey.asCircuitInputs(), + }) + + const encCircuitInputs2 = stringifyBigInts({ + k: k2, + M: [1, 1], + pk: keypair.pubKey.asCircuitInputs(), + }) + + const encWitness1 = await genWitness(encCircuit, encCircuitInputs1) + const encWitness2 = await genWitness(encCircuit, encCircuitInputs2) + + const Me1 = [ + BigInt(await getSignalByName(encCircuit, encWitness1, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness1, `main.Me[1]`)), + ]; + + const Me2 = [ + BigInt(await getSignalByName(encCircuit, encWitness2, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness2, `main.Me[1]`)), + ]; + + const kG1 = [ + BigInt(await getSignalByName(encCircuit, encWitness1, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness1, `main.kG[1]`)), + ]; + + const kG2 = [ + BigInt(await getSignalByName(encCircuit, encWitness2, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness2, `main.kG[1]`)), + ]; + + // Decryption + const decCircuitInputs1 = stringifyBigInts({ + kG: kG1, + Me: Me1, + sk: keypair.privKey.asCircuitInputs(), + }) + + // Decryption + const decCircuitInputs2 = stringifyBigInts({ + kG: kG2, + Me: Me2, + sk: keypair.privKey.asCircuitInputs(), + }) + + const decWitness1 = await genWitness(decCircuit, decCircuitInputs1) + const decWitness2 = await genWitness(decCircuit, decCircuitInputs2) + const m0Bit1 = BigInt(await getSignalByName(decCircuit, decWitness1, `main.M[0]`)); + const m1Bit1 = BigInt(await getSignalByName(decCircuit, decWitness1, `main.M[1]`)); + + const m0Bit2 = BigInt(await getSignalByName(decCircuit, decWitness2, `main.M[0]`)); + const m1Bit2 = BigInt(await getSignalByName(decCircuit, decWitness2, `main.M[1]`)); + + expect(m0Bit1).not.toEqual(BigInt(1)); + expect(m1Bit1).toEqual(BigInt(0)); + expect(m0Bit2).not.toEqual(BigInt(1)); + expect(m1Bit2).not.toEqual(BigInt(1)); + }) + + it('should return different ciphertexts for the same message when using different public keys', async () => { + const k = genRandomSalt(); + const keypair1 = new Keypair(); + const keypair2 = new Keypair(); + + // Encryption with public key 1 + const encCircuitInputs1 = stringifyBigInts({ + k, + M: [0, 1], + pk: keypair1.pubKey.asCircuitInputs(), + }); + const encWitness1 = await genWitness(encCircuit, encCircuitInputs1); + const Me1 = [ + BigInt(await getSignalByName(encCircuit, encWitness1, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness1, `main.Me[1]`)), + ]; + + // Encryption with public key 2 + const encCircuitInputs2 = stringifyBigInts({ + k, + M: [0, 1], + pk: keypair2.pubKey.asCircuitInputs(), + }); + const encWitness2 = await genWitness(encCircuit, encCircuitInputs2); + const Me2 = [ + BigInt(await getSignalByName(encCircuit, encWitness2, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness2, `main.Me[1]`)), + ]; + + expect(Me1).not.toEqual(Me2); + }) + + it('should not be possible to decrypt with the wrong public key', async () => { + const k = genRandomSalt(); + const keypair1 = new Keypair(); + const keypair2 = new Keypair(); + + // Encrypt + const encCircuitInputs = stringifyBigInts({ + k, + M: [0, 1], + pk: keypair1.pubKey.asCircuitInputs(), + }); + const encWitness = await genWitness(encCircuit, encCircuitInputs); + const Me = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[1]`)), + ]; + const kG = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[1]`)), + ]; + + // Decrypt with correct private key + const decCircuitInputs1 = stringifyBigInts({ + kG, + Me, + sk: keypair1.privKey.asCircuitInputs(), + }); + const decWitness1 = await genWitness(decCircuit, decCircuitInputs1); + const m0Bit = BigInt(await getSignalByName(decCircuit, decWitness1, `main.M[0]`)); + const m1Bit = BigInt(await getSignalByName(decCircuit, decWitness1, `main.M[1]`)); + expect(m0Bit).toEqual(BigInt(0)); + expect(m1Bit).toEqual(BigInt(1)); + + // Decrypt with wrong private key + const decCircuitInputs2 = stringifyBigInts({ + kG, + Me, + sk: keypair2.privKey.asCircuitInputs(), + }); + const decWitness2 = await genWitness(decCircuit, decCircuitInputs2); + const m0BitWrong = BigInt(await getSignalByName(decCircuit, decWitness2, `main.M[0]`)); + const m1BitWrong = BigInt(await getSignalByName(decCircuit, decWitness2, `main.M[1]`)); + expect(m0BitWrong).not.toEqual(BigInt(0)); + expect(m1BitWrong).not.toEqual(BigInt(1)); + }) + + it('should return the correct encrypted message when k is equal to one', async () => { + const keypair = new Keypair() + + // Encryption + const k = BigInt(1); + const encCircuitInputs = stringifyBigInts({ + k, + M: [0, 1], + pk: keypair.pubKey.asCircuitInputs(), + }) + + const encWitness = await genWitness(encCircuit, encCircuitInputs); + + const Me = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[1]`)), + ]; + const kG = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[1]`)), + ]; + + // Decryption + const decCircuitInputs = stringifyBigInts({ + kG, + Me, + sk: keypair.privKey.asCircuitInputs(), + }) + + const decWitness = await genWitness(decCircuit, decCircuitInputs) + const m0Bit = BigInt(await getSignalByName(decCircuit, decWitness, `main.M[0]`)); + const m1Bit = BigInt(await getSignalByName(decCircuit, decWitness, `main.M[1]`)); + + expect(m0Bit).toEqual(BigInt(0)); + expect(m1Bit).toEqual(BigInt(1)); + }); + + it('should return the point at infinity when M is equal to the point at infinity', async () => { + const keypair = new Keypair() + + // Encryption + const k = genRandomSalt(); + const encCircuitInputs = stringifyBigInts({ + k, + M: [BigInt(0), BigInt(0)], + pk: keypair.pubKey.asCircuitInputs(), + }) + + const encWitness = await genWitness(encCircuit, encCircuitInputs); + + const Me = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[1]`)), + ]; + const kG = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[1]`)), + ]; + + expect(Me).toEqual([BigInt(0), BigInt(0)]); // should be the point at infinity + expect(kG).not.toEqual([BigInt(0), BigInt(0)]); // should be different from M + }); + + it('should return the point at infinity when pk is equal to the point at infinity', async () => { + // Encryption + const k = genRandomSalt(); + const encCircuitInputs = stringifyBigInts({ + k, + M: [BigInt(0), BigInt(1)], + pk: [BigInt(0), BigInt(0)], + }) + + const encWitness = await genWitness(encCircuit, encCircuitInputs); + + const Me = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[1]`)), + ]; + + expect(Me).toEqual([BigInt(0), BigInt(1)]); + }); + + /** New */ + it('should return the same decrypted message when decrypting with the correct key after multiple encryptions', async () => { + const keypair = new Keypair(); + + // Encryption + const k = genRandomSalt(); + const M = [genRandomSalt(), genRandomSalt()]; + const encCircuitInputs1 = stringifyBigInts({ + k, + M, + pk: keypair.pubKey.asCircuitInputs(), + }); + + const encCircuitInputs2 = stringifyBigInts({ + k, + M, + pk: keypair.pubKey.asCircuitInputs(), + }); + + const encWitness1 = await genWitness(encCircuit, encCircuitInputs1); + const encWitness2 = await genWitness(encCircuit, encCircuitInputs2); + + const Me1 = [ + BigInt(await getSignalByName(encCircuit, encWitness1, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness1, `main.Me[1]`)), + ]; + + const Me2 = [ + BigInt(await getSignalByName(encCircuit, encWitness2, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness2, `main.Me[1]`)), + ]; + + // Decryption + const decCircuitInputs1 = stringifyBigInts({ + kG: keypair.pubKey.asCircuitInputs(), + Me: Me1, + sk: keypair.privKey.asCircuitInputs(), + }); + + const decCircuitInputs2 = stringifyBigInts({ + kG: keypair.pubKey.asCircuitInputs(), + Me: Me2, + sk: keypair.privKey.asCircuitInputs(), + }); + + const decWitness1 = await genWitness(decCircuit, decCircuitInputs1); + const decWitness2 = await genWitness(decCircuit, decCircuitInputs2); + + const m0Bit1 = BigInt(await getSignalByName(decCircuit, decWitness1, `main.M[0]`)); + const m1Bit1 = BigInt(await getSignalByName(decCircuit, decWitness1, `main.M[1]`)); + + const m0Bit2 = BigInt(await getSignalByName(decCircuit, decWitness2, `main.M[0]`)); + const m1Bit2 = BigInt(await getSignalByName(decCircuit, decWitness2, `main.M[1]`)); + + const decryptedMessage1 = [m0Bit1, m1Bit1].map((bit) => bit.toString(16)).join(''); + const decryptedMessage2 = [m0Bit2, m1Bit2].map((bit) => bit.toString(16)).join(''); + + expect(decryptedMessage1).toEqual(decryptedMessage2); + }); + + it('should return the point at infinity when k is equal to zero', async () => { + const keypair = new Keypair() + + // Encryption + const k = BigInt(0); + const encCircuitInputs = stringifyBigInts({ + k, + M: [0, 1], + pk: keypair.pubKey.asCircuitInputs(), + }) + + const encWitness = await genWitness(encCircuit, encCircuitInputs); + + const Me = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[1]`)), + ]; + const kG = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.kG[1]`)), + ]; + + expect(Me).toEqual([BigInt(0), BigInt(1)]); // should be the point at infinity + expect(kG).not.toEqual([BigInt(0), BigInt(0)]); // should be different from M + }); + + it('should fail to decrypt when kG is incorrect', async () => { + const keypair = new Keypair(); + + // Generate a random message + const message = BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)); + + // Encryption + const k = genRandomSalt(); + const M = [0, 1]; + const encCircuitInputs = stringifyBigInts({ + k, + M, + pk: keypair.pubKey.asCircuitInputs(), + }); + + const encWitness = await genWitness(encCircuit, encCircuitInputs); + + const Me = [ + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[0]`)), + BigInt(await getSignalByName(encCircuit, encWitness, `main.Me[1]`)), + ]; + + // Attempt decryption with incorrect kG value (0, 0) + const decCircuitInputs = stringifyBigInts({ + kG: [BigInt(0), BigInt(0)], // Incorrect kG value + Me, + sk: keypair.privKey.asCircuitInputs(), + }); + + const decWitness = await genWitness(decCircuit, decCircuitInputs); + + const m0Bit = BigInt(await getSignalByName(decCircuit, decWitness, `main.M[0]`)); + const m1Bit = BigInt(await getSignalByName(decCircuit, decWitness, `main.M[1]`)); + + expect(m0Bit).not.toEqual(BigInt(0)); + expect(m1Bit).not.toEqual(BigInt(1)); + }); }) \ No newline at end of file diff --git a/circuits/ts/__tests__/KeyDeactivation.test.ts b/circuits/ts/__tests__/KeyDeactivation.test.ts index 4fec8fb3a6..afa3ae4057 100644 --- a/circuits/ts/__tests__/KeyDeactivation.test.ts +++ b/circuits/ts/__tests__/KeyDeactivation.test.ts @@ -3,41 +3,49 @@ import { Keypair, } from 'maci-domainobjs' -import { - stringifyBigInts, - genRandomSalt, +import { + stringifyBigInts, IncrementalQuinTree, + hash3, hash4, hash5, } from 'maci-crypto' -import { +import { genWitness, getSignalByName, } from './utils' +import exp = require('constants') describe('Key deactivation circuit', () => { const circuit = 'isDeactivatedKey_test' const NUM_LEVELS = 3; const ZERO_VALUE = 0; + const MAX_LEAVES = 2**(NUM_LEVELS + 1) - 1; it('Deactivated key should be found in the tree', async () => { const deactivatedKeysTree = new IncrementalQuinTree(NUM_LEVELS, ZERO_VALUE, 5, hash5) const keypair = new Keypair(); - + // Generate random cyphertext as a point on the curve - const pseudoCiphertext = new Keypair(); - const [c1, c2] = pseudoCiphertext.pubKey.rawPubKey; + const pseudoCiphertext1 = new Keypair(); + const pseudoCiphertext2 = new Keypair(); + const c1 = pseudoCiphertext1.pubKey.rawPubKey; + const c2 = pseudoCiphertext2.pubKey.rawPubKey; + const salt = pseudoCiphertext2.privKey.rawPrivKey; + + const keyHash = hash3([...keypair.pubKey.rawPubKey, salt]); // Create key leaf as hash of the x and y key components - const keyLeaf = hash4( + const keyLeaf = hash5( [ - ...keypair.pubKey.rawPubKey, - ...pseudoCiphertext.pubKey.rawPubKey, + keyHash, + ...c1, + ...c2, ]); - // Add hash to the set of deactivated keys + // Add hash to the set of deactivated keys deactivatedKeysTree.insert(keyLeaf); const inclusionProof = deactivatedKeysTree.genMerklePath(0); @@ -56,6 +64,7 @@ describe('Key deactivation circuit', () => { path_index: inclusionProof.indices, c1, c2, + salt, }) const witness = await genWitness(circuit, circuitInputs) @@ -66,16 +75,23 @@ describe('Key deactivation circuit', () => { it('Active key should not be found in the tree', async () => { const deactivatedKeysTree = new IncrementalQuinTree(NUM_LEVELS, ZERO_VALUE, 5, hash5) const keypair = new Keypair(); - - // Random ciphertext - const pseudoCiphertext = new Keypair(); - const [c1, c2] = pseudoCiphertext.pubKey.rawPubKey; - const keyLeaf = hash4( - [ - ...keypair.pubKey.rawPubKey, - ...pseudoCiphertext.pubKey.rawPubKey, - ]); + // Generate random cyphertext as a point on the curve + const pseudoCiphertext1 = new Keypair(); + const pseudoCiphertext2 = new Keypair(); + const c1 = pseudoCiphertext1.pubKey.rawPubKey; + const c2 = pseudoCiphertext2.pubKey.rawPubKey; + const salt = pseudoCiphertext2.privKey.rawPrivKey; + + const keyHash = hash3([...keypair.pubKey.rawPubKey, salt]); + + // Create key leaf as hash of the x and y key components + const keyLeaf = hash5( + [ + keyHash, + ...c1, + ...c2, + ]); deactivatedKeysTree.insert(keyLeaf); const newTreeRoot = deactivatedKeysTree.root; @@ -91,11 +107,161 @@ describe('Key deactivation circuit', () => { path_index: inclusionProof.indices, c1, c2, + salt, }) - // The isDeactivated flag should be 0 for the actice key + // The isDeactivated flag should be 0 for the active key const witness = await genWitness(circuit, circuitInputs) const isDeactivated = (await getSignalByName(circuit, witness, 'main.isDeactivated')) == 1 expect(isDeactivated).toBeFalsy(); }) + + it('Multiple keys can be deactivated and verified', async () => { + const deactivatedKeysTree = new IncrementalQuinTree(NUM_LEVELS, ZERO_VALUE, 5, hash5) + const keypair1 = new Keypair(); + const keypair2 = new Keypair(); + const keypair3 = new Keypair(); + + // Generate random cyphertext as a point on the curve + const pseudoCiphertext11 = new Keypair(); + const pseudoCiphertext12 = new Keypair(); + const c11 = pseudoCiphertext11.pubKey.rawPubKey; + const c12 = pseudoCiphertext12.pubKey.rawPubKey; + const salt1 = pseudoCiphertext12.privKey.rawPrivKey; + + const keyHash1 = hash3([...keypair1.pubKey.rawPubKey, salt1]); + + // Create key leaf as hash of the x and y key components + const keyLeaf1 = hash5( + [ + keyHash1, + ...c11, + ...c12, + ]); + + + deactivatedKeysTree.insert(keyLeaf1); + + // Generate random cyphertext as a point on the curve + const pseudoCiphertext21 = new Keypair(); + const pseudoCiphertext22 = new Keypair(); + const c21 = pseudoCiphertext21.pubKey.rawPubKey; + const c22 = pseudoCiphertext22.pubKey.rawPubKey; + const salt2 = pseudoCiphertext22.privKey.rawPrivKey; + + const keyHash2 = hash3([...keypair2.pubKey.rawPubKey, salt2]); + + // Create key leaf as hash of the x and y key components + const keyLeaf2 = hash5( + [ + keyHash2, + ...c21, + ...c22, + ]); + + deactivatedKeysTree.insert(keyLeaf2); + + // Generate random cyphertext as a point on the curve + const pseudoCiphertext31 = new Keypair(); + const pseudoCiphertext32 = new Keypair(); + const c31 = pseudoCiphertext31.pubKey.rawPubKey; + const c32 = pseudoCiphertext32.pubKey.rawPubKey; + const salt3 = pseudoCiphertext32.privKey.rawPrivKey; + + const keyHash3 = hash3([...keypair3.pubKey.rawPubKey, salt3]); + + // Create key leaf as hash of the x and y key components + const keyLeaf3 = hash5( + [ + keyHash3, + ...c31, + ...c32, + ]); + + const inclusionProofDeactivatedKey1 = deactivatedKeysTree.genMerklePath(0); + const inclusionProofDeactivatedKey2 = deactivatedKeysTree.genMerklePath(1); + const inclusionProofActiveKey = deactivatedKeysTree.genMerklePath(2); + + const circuitInputs1 = stringifyBigInts({ + root: deactivatedKeysTree.root, + key: keypair1.pubKey.asCircuitInputs(), + path_elements: inclusionProofDeactivatedKey1.pathElements, + path_index: inclusionProofDeactivatedKey1.indices, + c1: c11, + c2: c12, + salt: salt1 + }); + + const witness1 = await genWitness(circuit, circuitInputs1); + const isDeactivated1 = (await getSignalByName(circuit, witness1, 'main.isDeactivated')) == 1; + expect(isDeactivated1).toBeTruthy(); + + // Verify that the circuit correctly identifies the active and deactivated keys + const circuitInputs2 = stringifyBigInts({ + root: deactivatedKeysTree.root, + key: keypair2.pubKey.asCircuitInputs(), + path_elements: inclusionProofDeactivatedKey2.pathElements, + path_index: inclusionProofDeactivatedKey2.indices, + c1: c21, + c2: c22, + salt: salt2 + }); + const witness2 = await genWitness(circuit, circuitInputs2); + const isDeactivated2 = (await getSignalByName(circuit, witness2, 'main.isDeactivated')) == 1; + expect(isDeactivated2).toBeTruthy(); + + const circuitInputs3 = stringifyBigInts({ + root: deactivatedKeysTree.root, + key: keypair3.pubKey.asCircuitInputs(), + path_elements: inclusionProofActiveKey.pathElements, + path_index: inclusionProofActiveKey.indices, + c1: c31, + c2: c32, + salt: salt3 + }); + const witness3 = await genWitness(circuit, circuitInputs3); + const isDeactivated3 = (await getSignalByName(circuit, witness3, 'main.isDeactivated')) == 1; + expect(isDeactivated3).toBeFalsy(); + }); + + it('Invalid path index should throw an error', async () => { + const deactivatedKeysTree = new IncrementalQuinTree(NUM_LEVELS, ZERO_VALUE, 5, hash5); + const keypair = new Keypair(); + + // Generate random cyphertext as a point on the curve + const pseudoCiphertext1 = new Keypair(); + const pseudoCiphertext2 = new Keypair(); + const c1 = pseudoCiphertext1.pubKey.rawPubKey; + const c2 = pseudoCiphertext2.pubKey.rawPubKey; + const salt = pseudoCiphertext2.privKey.rawPrivKey; + + const keyHash = hash3([...keypair.pubKey.rawPubKey, salt]); + + // Create key leaf as hash of the x and y key components + const keyLeaf = hash5( + [ + keyHash, + ...c1, + ...c2, + ]); + + deactivatedKeysTree.insert(keyLeaf); + + // Set invalid path index to trigger an error + const inclusionProof = deactivatedKeysTree.genMerklePath(0); + inclusionProof.indices[NUM_LEVELS - 1] = MAX_LEAVES; + + // Verify that the circuit correctly handles an invalid path index by returning false + const circuitInputs = stringifyBigInts({ + root: deactivatedKeysTree.root, + key: keypair.pubKey.asCircuitInputs(), + path_elements: inclusionProof.pathElements, + path_index: inclusionProof.indices, + c1: c1, + c2: c2, + salt, + }); + + await expect(genWitness(circuit, circuitInputs)).rejects.toThrow(); + }); }) diff --git a/circuits/ts/__tests__/MessageToCommand.test.ts b/circuits/ts/__tests__/MessageToCommand.test.ts index b2d6aa3c15..dd23f73b1d 100644 --- a/circuits/ts/__tests__/MessageToCommand.test.ts +++ b/circuits/ts/__tests__/MessageToCommand.test.ts @@ -1,4 +1,4 @@ -jest.setTimeout(90000) +jest.setTimeout(200000) import { genWitness, getSignalByName, diff --git a/circuits/ts/__tests__/ProcessDeactivationMessages.test.ts b/circuits/ts/__tests__/ProcessDeactivationMessages.test.ts new file mode 100644 index 0000000000..52243eca7a --- /dev/null +++ b/circuits/ts/__tests__/ProcessDeactivationMessages.test.ts @@ -0,0 +1,1521 @@ +jest.setTimeout(1200000) +import * as fs from 'fs' +import { + genWitness, + getSignalByName, +} from './utils' + +import { + MaciState, + STATE_TREE_DEPTH, +} from 'maci-core' + +import { + PrivKey, + PubKey, + Keypair, + PCommand, + Message, + DeactivatedKeyLeaf, +} from 'maci-domainobjs' + +import { + hash2, + sha256Hash, + hash5, + IncrementalQuinTree, + elGamalEncryptBit, + stringifyBigInts, +} from 'maci-crypto' + +const voiceCreditBalance = BigInt(100) + +const duration = 30 +const maxValues = { + maxUsers: 25, + maxMessages: 3, + maxVoteOptions: 25, +} + +const treeDepths = { + intStateTreeDepth: 2, + messageTreeDepth: 2, + messageTreeSubDepth: 1, + voteOptionTreeDepth: 2, +} + +const messageBatchSize = 5 + +const coordinatorKeypair = new Keypair() +const circuit = 'processDeactivationMessages_test' + +describe('ProcessDeactivationMessages circuit', () => { + describe('1 user, 0, 1 or 2 deactivation messages', () => { + const maciState = new MaciState() + const voteWeight = BigInt(0) + const voteOptionIndex = BigInt(0) + let stateIndex + let pollId + let poll + const messages: Message[] = [] + const commands: PCommand[] = [] + const H0 = BigInt('8370432830353022751713833565135785980866757267633941821328460903436894336785'); + let H; + let H2; + const userKeypair = new Keypair(new PrivKey(BigInt(1))); + + beforeAll(async () => { + // Sign up and publish + stateIndex = maciState.signUp( + userKeypair.pubKey, + voiceCreditBalance, + // BigInt(1), + BigInt(Math.floor(Date.now() / 1000)), + ) + + // Merge state tree + maciState.stateAq.mergeSubRoots(0) + maciState.stateAq.merge(STATE_TREE_DEPTH) + + // Deploy new poll + pollId = maciState.deployPoll( + duration, + // BigInt(2 + duration), + BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues, + treeDepths, + messageBatchSize, + coordinatorKeypair, + ) + + poll = maciState.polls[pollId] + }) + + it('should return empty array hash in case no deactivation messages', async () => { + const salt = (new Keypair()).privKey.rawPrivKey + + const messageArr = []; + for (let i = 0; i < maxValues.maxMessages; i += 1) { + messageArr.push(new Message(BigInt(0), Array(10).fill(BigInt(0)))) + } + + // ecdhKeypair.pubKey -> encPubKey + + const DEACT_TREE_ARITY = 5; + + const deactivatedKeys = new IncrementalQuinTree( + STATE_TREE_DEPTH, + H0, + DEACT_TREE_ARITY, + hash5, + ) + + + console.log(maciState.stateLeaves[1]) + console.log(userKeypair.pubKey) + console.log(maciState.stateLeaves[1].asCircuitInputs()) + + const maskingValues = [] + // Pad array + for (let i = 0; i < maxValues.maxMessages; i += 1) { + maskingValues.push('1') + } + + H = H0; + + const encPubKeys = []; + // Pad array + for (let i = 0; i < maxValues.maxMessages; i += 1) { + encPubKeys.push(new PubKey([BigInt(0), BigInt(0)]).asCircuitInputs()); + } + + const deactivatedTreePathElements = []; + // Pad array + for (let i = 0; i < maxValues.maxMessages; i += 1) { + deactivatedTreePathElements.push(deactivatedKeys.genMerklePath(0).pathElements) + } + + const stateLeafPathElements = []; + // Pad array + for (let i = 0; i < maxValues.maxMessages; i += 1) { + stateLeafPathElements.push(maciState.stateTree.genMerklePath(0).pathElements) + } + + const currentStateLeaves = []; + // Pad array + for (let i = 0; i < maxValues.maxMessages; i += 1) { + currentStateLeaves.push(maciState.stateLeaves[0].asCircuitInputs()) + } + + const elGamalEnc = [] + // Pad array + for (let i = 0; i < maxValues.maxMessages; i += 1) { + elGamalEnc.push(elGamalEncryptBit( + coordinatorKeypair.pubKey.rawPubKey, + BigInt(0), + BigInt(1) + )) + } + + const numSignUps = BigInt(1); + + const inputs = stringifyBigInts({ + coordPrivKey: coordinatorKeypair.privKey.asCircuitInputs(), + coordPubKey: coordinatorKeypair.pubKey.rawPubKey, + encPubKeys, + msgs: messageArr.map(m => m.asCircuitInputs()), + deactivatedTreePathElements, + stateLeafPathElements, + currentStateLeaves, + elGamalEnc, + maskingValues, + deactivatedTreeRoot: deactivatedKeys.root, + currentStateRoot: maciState.stateTree.root, + numSignUps, + chainHash: H0, + inputHash: sha256Hash([ + deactivatedKeys.root, + numSignUps, + maciState.stateTree.root, + H0, + ]), + }) + + const witness = await genWitness(circuit, inputs) + expect(witness.length > 0).toBeTruthy() + + // const newMessageChainHash = await getSignalByName(circuit, witness, 'main.newMessageChainHash') + // expect(newMessageChainHash).toEqual(H0.toString()); + }) + + it('should process exactly 1 deactivation message', async () => { + const salt = (new Keypair()).privKey.rawPrivKey + + // Key deactivation command + const command = new PCommand( + stateIndex, //BigInt(1), + new PubKey([BigInt(0), BigInt(0)]), // 0,0 PubKey + voteOptionIndex, // 0, + voteWeight, // vote weight + BigInt(2), // nonce + BigInt(pollId), + salt, + ) + + const signature = command.sign(userKeypair.privKey) + + const ecdhKeypair = new Keypair() + const sharedKey = Keypair.genEcdhSharedKey( + ecdhKeypair.privKey, + coordinatorKeypair.pubKey, + ) + + // Encrypt command and publish + const message = command.encrypt(signature, sharedKey) + messages.push(message) + + const messageArr = [message]; + for (let i = 1; i < maxValues.maxMessages; i += 1) { + messageArr.push(new Message(BigInt(0), Array(10).fill(BigInt(0)))) + } + + // ecdhKeypair.pubKey -> encPubKey + const messageHash = message.hash(ecdhKeypair.pubKey); + + const DEACT_TREE_ARITY = 5; + + const deactivatedKeys = new IncrementalQuinTree( + STATE_TREE_DEPTH, + H0, + DEACT_TREE_ARITY, + hash5, + ) + + const mask = BigInt(Math.ceil(Math.random() * 1000)) + const maskingValues = [mask.toString()] + + const status = BigInt(1); + const [c1, c2] = elGamalEncryptBit( + coordinatorKeypair.pubKey.rawPubKey, + status, + mask + ) + + console.log(maciState.stateLeaves[1]) + console.log(userKeypair.pubKey) + console.log(maciState.stateLeaves[1].asCircuitInputs()) + + for (let i = 1; i < maxValues.maxMessages; i += 1) { + maskingValues.push('1') + } + + deactivatedKeys.insert( (new DeactivatedKeyLeaf( + userKeypair.pubKey, + c1, + c2, + salt, + )).hash()) + + H = hash2([H0, messageHash]) + + const encPubKeys = [ecdhKeypair.pubKey.asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + encPubKeys.push(new PubKey([BigInt(0), BigInt(0)]).asCircuitInputs()); + } + + const deactivatedTreePathElements = [deactivatedKeys.genMerklePath(0).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + deactivatedTreePathElements.push(deactivatedKeys.genMerklePath(0).pathElements) + } + + const stateLeafPathElements = [maciState.stateTree.genMerklePath(stateIndex).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + stateLeafPathElements.push(maciState.stateTree.genMerklePath(0).pathElements) + } + + const currentStateLeaves = [maciState.stateLeaves[1].asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + currentStateLeaves.push(maciState.stateLeaves[0].asCircuitInputs()) + } + + const elGamalEnc = [[c1, c2]] + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + elGamalEnc.push(elGamalEncryptBit( + coordinatorKeypair.pubKey.rawPubKey, + BigInt(0), + BigInt(1) + )) + } + + const numSignUps = BigInt(1); + + const inputs = stringifyBigInts({ + coordPrivKey: coordinatorKeypair.privKey.asCircuitInputs(), + coordPubKey: coordinatorKeypair.pubKey.rawPubKey, + encPubKeys, + msgs: messageArr.map(m => m.asCircuitInputs()), + deactivatedTreePathElements, + stateLeafPathElements, + currentStateLeaves, + elGamalEnc, + maskingValues, + deactivatedTreeRoot: deactivatedKeys.root, + currentStateRoot: maciState.stateTree.root, + numSignUps, + chainHash: H, + inputHash: sha256Hash([ + deactivatedKeys.root, + numSignUps, + maciState.stateTree.root, + H, + ]), + }) + + const witness = await genWitness(circuit, inputs) + expect(witness.length > 0).toBeTruthy() + + // const newMessageChainHash = await getSignalByName(circuit, witness, 'main.newMessageChainHash') + // expect(newMessageChainHash).toEqual(H.toString()); + }) + + it('should process exactly 2 deactivation messages', async () => { + const salt1 = (new Keypair()).privKey.rawPrivKey; + const salt2 = (new Keypair()).privKey.rawPrivKey; + + // Key deactivation commands + const command1 = new PCommand( + stateIndex, //BigInt(1), + new PubKey([BigInt(0), BigInt(0)]), // 0,0 PubKey + voteOptionIndex, // 0, + voteWeight, // vote weight + BigInt(2), // nonce + BigInt(pollId), + salt1, + ); + + const command2 = new PCommand( + stateIndex, //BigInt(1), + new PubKey([BigInt(0), BigInt(0)]), // 0,0 PubKey + voteOptionIndex, // 0, + voteWeight, // vote weight + BigInt(3), // nonce + BigInt(pollId), + salt2, + ); + + const signature1 = command1.sign(userKeypair.privKey); + const signature2 = command2.sign(userKeypair.privKey); + + const ecdhKeypair = new Keypair() + const sharedKey = Keypair.genEcdhSharedKey( + ecdhKeypair.privKey, + coordinatorKeypair.pubKey, + ) + + // Encrypt commands and publish + const message1 = command1.encrypt(signature1, sharedKey) + const message2 = command2.encrypt(signature2, sharedKey) + messages.push(message1); + messages.push(message2); + + const messageArr = [message1, message2]; + for (let i = 2; i < maxValues.maxMessages; i += 1) { + messageArr.push(new Message(BigInt(0), Array(10).fill(BigInt(0)))) + } + + // ecdhKeypair.pubKey -> encPubKey + const messageHash1 = message1.hash(ecdhKeypair.pubKey); + const messageHash2 = message2.hash(ecdhKeypair.pubKey); + + const DEACT_TREE_ARITY = 5; + + const deactivatedKeys = new IncrementalQuinTree( + STATE_TREE_DEPTH, + H0, + DEACT_TREE_ARITY, + hash5, + ) + + const mask1 = BigInt(Math.ceil(Math.random() * 1000)) + const mask2 = BigInt(Math.ceil(Math.random() * 1000)) + + const maskingValues = [mask1.toString(), mask2.toString()] + + const status = BigInt(1); + + const [c11, c12] = elGamalEncryptBit( + coordinatorKeypair.pubKey.rawPubKey, + status, + mask1 + ) + + const [c21, c22] = elGamalEncryptBit( + coordinatorKeypair.pubKey.rawPubKey, + status, + mask2 + ) + + console.log(maciState.stateLeaves[1]) + console.log(userKeypair.pubKey) + console.log(maciState.stateLeaves[1].asCircuitInputs()) + + for (let i = 2; i < maxValues.maxMessages; i += 1) { + maskingValues.push('1') + } + + deactivatedKeys.insert( (new DeactivatedKeyLeaf( + userKeypair.pubKey, + c11, + c12, + salt1, + )).hash()) + + deactivatedKeys.insert( (new DeactivatedKeyLeaf( + userKeypair.pubKey, + c21, + c22, + salt2, + )).hash()) + + H = hash2([H0, messageHash1]) + H2 = hash2([H, messageHash2]) + + const encPubKeys = [ecdhKeypair.pubKey.asCircuitInputs(), ecdhKeypair.pubKey.asCircuitInputs()]; + + // Pad array + for (let i = 2; i < maxValues.maxMessages; i += 1) { + encPubKeys.push(new PubKey([BigInt(0), BigInt(0)]).asCircuitInputs()); + } + + + const deactivatedTreePathElements = [ + deactivatedKeys.genMerklePath(0).pathElements, + deactivatedKeys.genMerklePath(1).pathElements + ]; + + // Pad array + for (let i = 2; i < maxValues.maxMessages; i += 1) { + deactivatedTreePathElements.push(deactivatedKeys.genMerklePath(0).pathElements) + } + + const stateLeafPathElements = [maciState.stateTree.genMerklePath(stateIndex).pathElements, maciState.stateTree.genMerklePath(stateIndex).pathElements]; + + // Pad array + for (let i = 2; i < maxValues.maxMessages; i += 1) { + stateLeafPathElements.push(maciState.stateTree.genMerklePath(0).pathElements) + } + + const currentStateLeaves = [ + maciState.stateLeaves[1].asCircuitInputs(), + maciState.stateLeaves[1].asCircuitInputs() + ]; + // Pad array + for (let i = 2; i < maxValues.maxMessages; i += 1) { + currentStateLeaves.push(maciState.stateLeaves[0].asCircuitInputs()) + } + + const elGamalEnc = [[c11, c12], [c21, c22]] + // Pad array + for (let i = 2; i < maxValues.maxMessages; i += 1) { + elGamalEnc.push(elGamalEncryptBit( + coordinatorKeypair.pubKey.rawPubKey, + BigInt(0), + BigInt(1) + )) + } + + const numSignUps = BigInt(1) + const inputs = stringifyBigInts({ + coordPrivKey: coordinatorKeypair.privKey.asCircuitInputs(), + coordPubKey: coordinatorKeypair.pubKey.rawPubKey, + encPubKeys, + msgs: messageArr.map(m => m.asCircuitInputs()), + deactivatedTreePathElements, + stateLeafPathElements, + currentStateLeaves, + elGamalEnc, + maskingValues, + deactivatedTreeRoot: deactivatedKeys.root, + currentStateRoot: maciState.stateTree.root, + numSignUps, + chainHash: H2, + inputHash: sha256Hash([ + deactivatedKeys.root, + numSignUps, + maciState.stateTree.root, + H2, + ]), + }) + + const witness = await genWitness(circuit, inputs) + expect(witness.length > 0).toBeTruthy() + + // const newMessageChainHash = await getSignalByName(circuit, witness, 'main.newMessageChainHash') + // expect(newMessageChainHash).toEqual(H2.toString()); + }) + }) + + describe('1 user, wrong circuit inputs', () => { + const maciState = new MaciState() + const voteWeight = BigInt(0) + const voteOptionIndex = BigInt(0) + let stateIndex + let pollId + let poll + const messages: Message[] = [] + const commands: PCommand[] = [] + const H0 = BigInt('8370432830353022751713833565135785980866757267633941821328460903436894336785'); + let H; + let H2; + const userKeypair = new Keypair(new PrivKey(BigInt(1))); + + beforeAll(async () => { + // Sign up and publish + stateIndex = maciState.signUp( + userKeypair.pubKey, + voiceCreditBalance, + // BigInt(1), + BigInt(Math.floor(Date.now() / 1000)), + ) + + // Merge state tree + maciState.stateAq.mergeSubRoots(0) + maciState.stateAq.merge(STATE_TREE_DEPTH) + + // Deploy new poll + pollId = maciState.deployPoll( + duration, + // BigInt(2 + duration), + BigInt(Math.floor(Date.now() / 1000) + duration), + maxValues, + treeDepths, + messageBatchSize, + coordinatorKeypair, + ) + + poll = maciState.polls[pollId] + }) + + it('should throw if numSignUps 0 instead of 1 with 1 deactivation message', async () => { + const NUM_OF_SIGNUPS = BigInt(0); // Wrong number, should be 1 + const VOTE_WEIGHT = voteWeight; + const MESSAGE_PUB_KEY = new PubKey([BigInt(0), BigInt(0)]); + const KEY_FOR_COORD_PART_OF_SHARED_KEY = coordinatorKeypair.pubKey; + const DEACTIVATED_KEYS_TREE_ELEMENT_INDEX = 0; + const STATE_TREE_ELEMENT_INDEX = stateIndex; + const KEY_FOR_ELGAMAL_ENCRYPTION = coordinatorKeypair.pubKey.rawPubKey; + + const salt = (new Keypair()).privKey.rawPrivKey + + // Key deactivation command + const command = new PCommand( + stateIndex, //BigInt(1), + MESSAGE_PUB_KEY, + voteOptionIndex, // 0, + VOTE_WEIGHT, // vote weight + BigInt(2), // nonce + BigInt(pollId), + salt, + ) + + const signature = command.sign(userKeypair.privKey) + + const ecdhKeypair = new Keypair() + const sharedKey = Keypair.genEcdhSharedKey( + ecdhKeypair.privKey, + KEY_FOR_COORD_PART_OF_SHARED_KEY, + ) + + // Encrypt command and publish + const message = command.encrypt(signature, sharedKey) + messages.push(message) + + const messageArr = [message]; + for (let i = 1; i < maxValues.maxMessages; i += 1) { + messageArr.push(new Message(BigInt(0), Array(10).fill(BigInt(0)))) + } + + // ecdhKeypair.pubKey -> encPubKey + const messageHash = message.hash(ecdhKeypair.pubKey); + + const DEACT_TREE_ARITY = 5; + + const deactivatedKeys = new IncrementalQuinTree( + STATE_TREE_DEPTH, + H0, + DEACT_TREE_ARITY, + hash5, + ) + + const mask = BigInt(Math.ceil(Math.random() * 1000)) + const maskingValues = [mask.toString()] + + const status = BigInt(1); + const [c1, c2] = elGamalEncryptBit( + coordinatorKeypair.pubKey.rawPubKey, + status, + mask + ) + + console.log(maciState.stateLeaves[1]) + console.log(userKeypair.pubKey) + console.log(maciState.stateLeaves[1].asCircuitInputs()) + + for (let i = 1; i < maxValues.maxMessages; i += 1) { + maskingValues.push('1') + } + + deactivatedKeys.insert( (new DeactivatedKeyLeaf( + userKeypair.pubKey, + c1, + c2, + salt, + )).hash()) + + H = hash2([H0, messageHash]) + + const encPubKeys = [ecdhKeypair.pubKey.asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + encPubKeys.push(new PubKey([BigInt(0), BigInt(0)]).asCircuitInputs()); + } + + console.log(encPubKeys) + + const deactivatedTreePathElements = [deactivatedKeys.genMerklePath(DEACTIVATED_KEYS_TREE_ELEMENT_INDEX).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + deactivatedTreePathElements.push(deactivatedKeys.genMerklePath(DEACTIVATED_KEYS_TREE_ELEMENT_INDEX).pathElements) + } + + const stateLeafPathElements = [maciState.stateTree.genMerklePath(STATE_TREE_ELEMENT_INDEX).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + stateLeafPathElements.push(maciState.stateTree.genMerklePath(0).pathElements) + } + + const currentStateLeaves = [maciState.stateLeaves[1].asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + currentStateLeaves.push(maciState.stateLeaves[0].asCircuitInputs()) + } + + const elGamalEnc = [[c1, c2]] + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + elGamalEnc.push(elGamalEncryptBit( + KEY_FOR_ELGAMAL_ENCRYPTION, + BigInt(0), + BigInt(1) + )) + } + + const inputs = stringifyBigInts({ + coordPrivKey: coordinatorKeypair.privKey.asCircuitInputs(), + coordPubKey: coordinatorKeypair.pubKey.rawPubKey, + encPubKeys, + msgs: messageArr.map(m => m.asCircuitInputs()), + deactivatedTreePathElements, + stateLeafPathElements, + currentStateLeaves, + elGamalEnc, + maskingValues, + deactivatedTreeRoot: deactivatedKeys.root, + currentStateRoot: maciState.stateTree.root, + numSignUps: NUM_OF_SIGNUPS, + chainHash: H, + inputHash: sha256Hash([ + deactivatedKeys.root, + NUM_OF_SIGNUPS, + maciState.stateTree.root, + H, + ]), + }) + + await expect(genWitness(circuit, inputs)).rejects.toThrow(); + }) + + it('should throw if public key part of shared key is not coordinators', async () => { + const NUM_OF_SIGNUPS = BigInt(1); + const VOTE_WEIGHT = voteWeight; + const MESSAGE_PUB_KEY = new PubKey([BigInt(0), BigInt(0)]); + const ecdhKeypair = new Keypair() + const KEY_FOR_COORD_PART_OF_SHARED_KEY = ecdhKeypair.pubKey; // Wrong pub key, should be coordinators + const DEACTIVATED_KEYS_TREE_ELEMENT_INDEX = 0; + const STATE_TREE_ELEMENT_INDEX = stateIndex; + const KEY_FOR_ELGAMAL_ENCRYPTION = coordinatorKeypair.pubKey.rawPubKey; + + const salt = (new Keypair()).privKey.rawPrivKey + + // Key deactivation command + const command = new PCommand( + stateIndex, //BigInt(1), + MESSAGE_PUB_KEY, // 0,0 PubKey + voteOptionIndex, // 0, + VOTE_WEIGHT, // vote weight + BigInt(2), // nonce + BigInt(pollId), + salt, + ) + + const signature = command.sign(userKeypair.privKey) + + const sharedKey = Keypair.genEcdhSharedKey( + ecdhKeypair.privKey, + KEY_FOR_COORD_PART_OF_SHARED_KEY + ) + + // Encrypt command and publish + const message = command.encrypt(signature, sharedKey) + messages.push(message) + + const messageArr = [message]; + for (let i = 1; i < maxValues.maxMessages; i += 1) { + messageArr.push(new Message(BigInt(0), Array(10).fill(BigInt(0)))) + } + + // ecdhKeypair.pubKey -> encPubKey + const messageHash = message.hash(ecdhKeypair.pubKey); + + const DEACT_TREE_ARITY = 5; + + const deactivatedKeys = new IncrementalQuinTree( + STATE_TREE_DEPTH, + H0, + DEACT_TREE_ARITY, + hash5, + ) + + const mask = BigInt(Math.ceil(Math.random() * 1000)) + const maskingValues = [mask.toString()] + + const status = BigInt(1); + const [c1, c2] = elGamalEncryptBit( + coordinatorKeypair.pubKey.rawPubKey, + status, + mask + ) + + console.log(maciState.stateLeaves[1]) + console.log(userKeypair.pubKey) + console.log(maciState.stateLeaves[1].asCircuitInputs()) + + for (let i = 1; i < maxValues.maxMessages; i += 1) { + maskingValues.push('1') + } + + deactivatedKeys.insert( (new DeactivatedKeyLeaf( + userKeypair.pubKey, + c1, + c2, + salt, + )).hash()) + + H = hash2([H0, messageHash]) + + const encPubKeys = [ecdhKeypair.pubKey.asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + encPubKeys.push(new PubKey([BigInt(0), BigInt(0)]).asCircuitInputs()); + } + + console.log(encPubKeys) + + const deactivatedTreePathElements = [deactivatedKeys.genMerklePath(DEACTIVATED_KEYS_TREE_ELEMENT_INDEX).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + deactivatedTreePathElements.push(deactivatedKeys.genMerklePath(DEACTIVATED_KEYS_TREE_ELEMENT_INDEX).pathElements) + } + + const stateLeafPathElements = [maciState.stateTree.genMerklePath(STATE_TREE_ELEMENT_INDEX).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + stateLeafPathElements.push(maciState.stateTree.genMerklePath(0).pathElements) + } + + const currentStateLeaves = [maciState.stateLeaves[1].asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + currentStateLeaves.push(maciState.stateLeaves[0].asCircuitInputs()) + } + + const elGamalEnc = [[c1, c2]] + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + elGamalEnc.push(elGamalEncryptBit( + KEY_FOR_ELGAMAL_ENCRYPTION, + BigInt(0), + BigInt(1) + )) + } + + const inputs = stringifyBigInts({ + coordPrivKey: coordinatorKeypair.privKey.asCircuitInputs(), + coordPubKey: coordinatorKeypair.pubKey.rawPubKey, + encPubKeys, + msgs: messageArr.map(m => m.asCircuitInputs()), + deactivatedTreePathElements, + stateLeafPathElements, + currentStateLeaves, + elGamalEnc, + maskingValues, + deactivatedTreeRoot: deactivatedKeys.root, + currentStateRoot: maciState.stateTree.root, + numSignUps: NUM_OF_SIGNUPS, + chainHash: H, + inputHash: sha256Hash([ + deactivatedKeys.root, + NUM_OF_SIGNUPS, + maciState.stateTree.root, + H, + ]), + }) + + await expect(genWitness(circuit, inputs)).rejects.toThrow(); + }) + + it('should throw if deactivatedTreePathElements passed to the circuit are invalid', async () => { + const NUM_OF_SIGNUPS = BigInt(1); + const VOTE_WEIGHT = voteWeight; + const MESSAGE_PUB_KEY = new PubKey([BigInt(0), BigInt(0)]); + const KEY_FOR_COORD_PART_OF_SHARED_KEY = coordinatorKeypair.pubKey; + const DEACTIVATED_KEYS_TREE_ELEMENT_INDEX = 1; // Wrong index, should be 0 + const STATE_TREE_ELEMENT_INDEX = stateIndex; + const KEY_FOR_ELGAMAL_ENCRYPTION = coordinatorKeypair.pubKey.rawPubKey; + + const salt = (new Keypair()).privKey.rawPrivKey + + // Key deactivation command + const command = new PCommand( + stateIndex, //BigInt(1), + MESSAGE_PUB_KEY, // 0,0 PubKey + voteOptionIndex, // 0, + VOTE_WEIGHT, // vote weight + BigInt(2), // nonce + BigInt(pollId), + salt, + ) + + const signature = command.sign(userKeypair.privKey) + + const ecdhKeypair = new Keypair() + + const sharedKey = Keypair.genEcdhSharedKey( + ecdhKeypair.privKey, + KEY_FOR_COORD_PART_OF_SHARED_KEY + ) + + // Encrypt command and publish + const message = command.encrypt(signature, sharedKey) + messages.push(message) + + const messageArr = [message]; + for (let i = 1; i < maxValues.maxMessages; i += 1) { + messageArr.push(new Message(BigInt(0), Array(10).fill(BigInt(0)))) + } + + // ecdhKeypair.pubKey -> encPubKey + const messageHash = message.hash(ecdhKeypair.pubKey); + + const DEACT_TREE_ARITY = 5; + + const deactivatedKeys = new IncrementalQuinTree( + STATE_TREE_DEPTH, + H0, + DEACT_TREE_ARITY, + hash5, + ) + + const mask = BigInt(Math.ceil(Math.random() * 1000)) + const maskingValues = [mask.toString()] + + const status = BigInt(1); + const [c1, c2] = elGamalEncryptBit( + coordinatorKeypair.pubKey.rawPubKey, + status, + mask + ) + + console.log(maciState.stateLeaves[1]) + console.log(userKeypair.pubKey) + console.log(maciState.stateLeaves[1].asCircuitInputs()) + + for (let i = 1; i < maxValues.maxMessages; i += 1) { + maskingValues.push('1') + } + + deactivatedKeys.insert( (new DeactivatedKeyLeaf( + userKeypair.pubKey, + c1, + c2, + salt, + )).hash()) + + H = hash2([H0, messageHash]) + + const encPubKeys = [ecdhKeypair.pubKey.asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + encPubKeys.push(new PubKey([BigInt(0), BigInt(0)]).asCircuitInputs()); + } + + console.log(encPubKeys) + + const deactivatedTreePathElements = [deactivatedKeys.genMerklePath(DEACTIVATED_KEYS_TREE_ELEMENT_INDEX).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + deactivatedTreePathElements.push(deactivatedKeys.genMerklePath(DEACTIVATED_KEYS_TREE_ELEMENT_INDEX).pathElements) + } + + const stateLeafPathElements = [maciState.stateTree.genMerklePath(STATE_TREE_ELEMENT_INDEX).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + stateLeafPathElements.push(maciState.stateTree.genMerklePath(0).pathElements) + } + + const currentStateLeaves = [maciState.stateLeaves[1].asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + currentStateLeaves.push(maciState.stateLeaves[0].asCircuitInputs()) + } + + const elGamalEnc = [[c1, c2]] + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + elGamalEnc.push(elGamalEncryptBit( + KEY_FOR_ELGAMAL_ENCRYPTION, + BigInt(0), + BigInt(1) + )) + } + + const inputs = stringifyBigInts({ + coordPrivKey: coordinatorKeypair.privKey.asCircuitInputs(), + coordPubKey: coordinatorKeypair.pubKey.rawPubKey, + encPubKeys, + msgs: messageArr.map(m => m.asCircuitInputs()), + deactivatedTreePathElements, + stateLeafPathElements, + currentStateLeaves, + elGamalEnc, + maskingValues, + deactivatedTreeRoot: deactivatedKeys.root, + currentStateRoot: maciState.stateTree.root, + numSignUps: NUM_OF_SIGNUPS, + chainHash: H, + inputHash: sha256Hash([ + deactivatedKeys.root, + NUM_OF_SIGNUPS, + maciState.stateTree.root, + H, + ]), + }) + + await expect(genWitness(circuit, inputs)).rejects.toThrow(); + }) + + it('should throw if stateLeafPathElements passed to the circuit are invalid', async () => { + const NUM_OF_SIGNUPS = BigInt(1); + const VOTE_WEIGHT = voteWeight; + const MESSAGE_PUB_KEY = new PubKey([BigInt(0), BigInt(0)]); + const KEY_FOR_COORD_PART_OF_SHARED_KEY = coordinatorKeypair.pubKey; + const DEACTIVATED_KEYS_TREE_ELEMENT_INDEX = 0; + const STATE_TREE_ELEMENT_INDEX = 0; // Wrong index, should be stateIndex var + const KEY_FOR_ELGAMAL_ENCRYPTION = coordinatorKeypair.pubKey.rawPubKey; + + const salt = (new Keypair()).privKey.rawPrivKey + + // Key deactivation command + const command = new PCommand( + stateIndex, //BigInt(1), + MESSAGE_PUB_KEY, // 0,0 PubKey + voteOptionIndex, // 0, + VOTE_WEIGHT, // vote weight + BigInt(2), // nonce + BigInt(pollId), + salt, + ) + + const signature = command.sign(userKeypair.privKey) + + const ecdhKeypair = new Keypair() + + const sharedKey = Keypair.genEcdhSharedKey( + ecdhKeypair.privKey, + KEY_FOR_COORD_PART_OF_SHARED_KEY + ) + + // Encrypt command and publish + const message = command.encrypt(signature, sharedKey) + messages.push(message) + + const messageArr = [message]; + for (let i = 1; i < maxValues.maxMessages; i += 1) { + messageArr.push(new Message(BigInt(0), Array(10).fill(BigInt(0)))) + } + + // ecdhKeypair.pubKey -> encPubKey + const messageHash = message.hash(ecdhKeypair.pubKey); + + const DEACT_TREE_ARITY = 5; + + const deactivatedKeys = new IncrementalQuinTree( + STATE_TREE_DEPTH, + H0, + DEACT_TREE_ARITY, + hash5, + ) + + const mask = BigInt(Math.ceil(Math.random() * 1000)) + const maskingValues = [mask.toString()] + + const status = BigInt(1); + const [c1, c2] = elGamalEncryptBit( + coordinatorKeypair.pubKey.rawPubKey, + status, + mask + ) + + console.log(maciState.stateLeaves[1]) + console.log(userKeypair.pubKey) + console.log(maciState.stateLeaves[1].asCircuitInputs()) + + for (let i = 1; i < maxValues.maxMessages; i += 1) { + maskingValues.push('1') + } + + deactivatedKeys.insert( (new DeactivatedKeyLeaf( + userKeypair.pubKey, + c1, + c2, + salt, + )).hash()) + + H = hash2([H0, messageHash]) + + const encPubKeys = [ecdhKeypair.pubKey.asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + encPubKeys.push(new PubKey([BigInt(0), BigInt(0)]).asCircuitInputs()); + } + + console.log(encPubKeys) + + const deactivatedTreePathElements = [deactivatedKeys.genMerklePath(DEACTIVATED_KEYS_TREE_ELEMENT_INDEX).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + deactivatedTreePathElements.push(deactivatedKeys.genMerklePath(DEACTIVATED_KEYS_TREE_ELEMENT_INDEX).pathElements) + } + + const stateLeafPathElements = [maciState.stateTree.genMerklePath(STATE_TREE_ELEMENT_INDEX).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + stateLeafPathElements.push(maciState.stateTree.genMerklePath(0).pathElements) + } + + const currentStateLeaves = [maciState.stateLeaves[1].asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + currentStateLeaves.push(maciState.stateLeaves[0].asCircuitInputs()) + } + + const elGamalEnc = [[c1, c2]] + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + elGamalEnc.push(elGamalEncryptBit( + KEY_FOR_ELGAMAL_ENCRYPTION, + BigInt(0), + BigInt(1) + )) + } + + const inputs = stringifyBigInts({ + coordPrivKey: coordinatorKeypair.privKey.asCircuitInputs(), + coordPubKey: coordinatorKeypair.pubKey.rawPubKey, + encPubKeys, + msgs: messageArr.map(m => m.asCircuitInputs()), + deactivatedTreePathElements, + stateLeafPathElements, + currentStateLeaves, + elGamalEnc, + maskingValues, + deactivatedTreeRoot: deactivatedKeys.root, + currentStateRoot: maciState.stateTree.root, + numSignUps: NUM_OF_SIGNUPS, + chainHash: H, + inputHash: sha256Hash([ + deactivatedKeys.root, + NUM_OF_SIGNUPS, + maciState.stateTree.root, + H, + ]), + }) + + await expect(genWitness(circuit, inputs)).rejects.toThrow(); + }) + + it('should throw if pub key in the PCommand not special case [0, 0]', async () => { + const NUM_OF_SIGNUPS = BigInt(1); + const VOTE_WEIGHT = voteWeight; + const MESSAGE_PUB_KEY = new PubKey([BigInt(1), BigInt(0)]);// Wrong pub key, must be 0,0 for this special case of deactivation + const KEY_FOR_COORD_PART_OF_SHARED_KEY = coordinatorKeypair.pubKey; + const DEACTIVATED_KEYS_TREE_ELEMENT_INDEX = 0; + const STATE_TREE_ELEMENT_INDEX = stateIndex; + const KEY_FOR_ELGAMAL_ENCRYPTION = coordinatorKeypair.pubKey.rawPubKey; + + const salt = (new Keypair()).privKey.rawPrivKey + + // Key deactivation command + const command = new PCommand( + stateIndex, //BigInt(1), + MESSAGE_PUB_KEY, // 0,0 PubKey + voteOptionIndex, // 0, + VOTE_WEIGHT, // vote weight + BigInt(2), // nonce + BigInt(pollId), + salt, + ) + + const signature = command.sign(userKeypair.privKey) + + const ecdhKeypair = new Keypair() + + const sharedKey = Keypair.genEcdhSharedKey( + ecdhKeypair.privKey, + KEY_FOR_COORD_PART_OF_SHARED_KEY + ) + + // Encrypt command and publish + const message = command.encrypt(signature, sharedKey) + messages.push(message) + + const messageArr = [message]; + for (let i = 1; i < maxValues.maxMessages; i += 1) { + messageArr.push(new Message(BigInt(0), Array(10).fill(BigInt(0)))) + } + + // ecdhKeypair.pubKey -> encPubKey + const messageHash = message.hash(ecdhKeypair.pubKey); + + const DEACT_TREE_ARITY = 5; + + const deactivatedKeys = new IncrementalQuinTree( + STATE_TREE_DEPTH, + H0, + DEACT_TREE_ARITY, + hash5, + ) + + const mask = BigInt(Math.ceil(Math.random() * 1000)) + const maskingValues = [mask.toString()] + + const status = BigInt(1); + const [c1, c2] = elGamalEncryptBit( + coordinatorKeypair.pubKey.rawPubKey, + status, + mask + ) + + console.log(maciState.stateLeaves[1]) + console.log(userKeypair.pubKey) + console.log(maciState.stateLeaves[1].asCircuitInputs()) + + for (let i = 1; i < maxValues.maxMessages; i += 1) { + maskingValues.push('1') + } + + deactivatedKeys.insert( (new DeactivatedKeyLeaf( + userKeypair.pubKey, + c1, + c2, + salt, + )).hash()) + + H = hash2([H0, messageHash]) + + const encPubKeys = [ecdhKeypair.pubKey.asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + encPubKeys.push(new PubKey([BigInt(0), BigInt(0)]).asCircuitInputs()); + } + + console.log(encPubKeys) + + const deactivatedTreePathElements = [deactivatedKeys.genMerklePath(DEACTIVATED_KEYS_TREE_ELEMENT_INDEX).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + deactivatedTreePathElements.push(deactivatedKeys.genMerklePath(DEACTIVATED_KEYS_TREE_ELEMENT_INDEX).pathElements) + } + + const stateLeafPathElements = [maciState.stateTree.genMerklePath(STATE_TREE_ELEMENT_INDEX).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + stateLeafPathElements.push(maciState.stateTree.genMerklePath(0).pathElements) + } + + const currentStateLeaves = [maciState.stateLeaves[1].asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + currentStateLeaves.push(maciState.stateLeaves[0].asCircuitInputs()) + } + + const elGamalEnc = [[c1, c2]] + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + elGamalEnc.push(elGamalEncryptBit( + KEY_FOR_ELGAMAL_ENCRYPTION, + BigInt(0), + BigInt(1) + )) + } + + const inputs = stringifyBigInts({ + coordPrivKey: coordinatorKeypair.privKey.asCircuitInputs(), + coordPubKey: coordinatorKeypair.pubKey.rawPubKey, + encPubKeys, + msgs: messageArr.map(m => m.asCircuitInputs()), + deactivatedTreePathElements, + stateLeafPathElements, + currentStateLeaves, + elGamalEnc, + maskingValues, + deactivatedTreeRoot: deactivatedKeys.root, + currentStateRoot: maciState.stateTree.root, + numSignUps: NUM_OF_SIGNUPS, + chainHash: H, + inputHash: sha256Hash([ + deactivatedKeys.root, + NUM_OF_SIGNUPS, + maciState.stateTree.root, + H, + ]), + }) + + await expect(genWitness(circuit, inputs)).rejects.toThrow(); + }) + + it('should throw if voteWeight in the PCommand not 0', async () => { + const NUM_OF_SIGNUPS = BigInt(1); + const VOTE_WEIGHT = BigInt(1); // Wrong vote weight, should be voteWeight var + const MESSAGE_PUB_KEY = new PubKey([BigInt(0), BigInt(0)]); + const KEY_FOR_COORD_PART_OF_SHARED_KEY = coordinatorKeypair.pubKey; + const DEACTIVATED_KEYS_TREE_ELEMENT_INDEX = 0; + const STATE_TREE_ELEMENT_INDEX = stateIndex; + const KEY_FOR_ELGAMAL_ENCRYPTION = coordinatorKeypair.pubKey.rawPubKey; + + const salt = (new Keypair()).privKey.rawPrivKey + + // Key deactivation command + const command = new PCommand( + stateIndex, //BigInt(1), + MESSAGE_PUB_KEY, // 0,0 PubKey + voteOptionIndex, // 0, + VOTE_WEIGHT, // vote weight + BigInt(2), // nonce + BigInt(pollId), + salt, + ) + + const signature = command.sign(userKeypair.privKey) + + const ecdhKeypair = new Keypair() + + const sharedKey = Keypair.genEcdhSharedKey( + ecdhKeypair.privKey, + KEY_FOR_COORD_PART_OF_SHARED_KEY + ) + + // Encrypt command and publish + const message = command.encrypt(signature, sharedKey) + messages.push(message) + + const messageArr = [message]; + for (let i = 1; i < maxValues.maxMessages; i += 1) { + messageArr.push(new Message(BigInt(0), Array(10).fill(BigInt(0)))) + } + + // ecdhKeypair.pubKey -> encPubKey + const messageHash = message.hash(ecdhKeypair.pubKey); + + const DEACT_TREE_ARITY = 5; + + const deactivatedKeys = new IncrementalQuinTree( + STATE_TREE_DEPTH, + H0, + DEACT_TREE_ARITY, + hash5, + ) + + const mask = BigInt(Math.ceil(Math.random() * 1000)) + const maskingValues = [mask.toString()] + + const status = BigInt(1); + const [c1, c2] = elGamalEncryptBit( + coordinatorKeypair.pubKey.rawPubKey, + status, + mask + ) + + console.log(maciState.stateLeaves[1]) + console.log(userKeypair.pubKey) + console.log(maciState.stateLeaves[1].asCircuitInputs()) + + for (let i = 1; i < maxValues.maxMessages; i += 1) { + maskingValues.push('1') + } + + deactivatedKeys.insert( (new DeactivatedKeyLeaf( + userKeypair.pubKey, + c1, + c2, + salt, + )).hash()) + + H = hash2([H0, messageHash]) + + const encPubKeys = [ecdhKeypair.pubKey.asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + encPubKeys.push(new PubKey([BigInt(0), BigInt(0)]).asCircuitInputs()); + } + + console.log(encPubKeys) + + const deactivatedTreePathElements = [deactivatedKeys.genMerklePath(DEACTIVATED_KEYS_TREE_ELEMENT_INDEX).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + deactivatedTreePathElements.push(deactivatedKeys.genMerklePath(DEACTIVATED_KEYS_TREE_ELEMENT_INDEX).pathElements) + } + + const stateLeafPathElements = [maciState.stateTree.genMerklePath(STATE_TREE_ELEMENT_INDEX).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + stateLeafPathElements.push(maciState.stateTree.genMerklePath(0).pathElements) + } + + const currentStateLeaves = [maciState.stateLeaves[1].asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + currentStateLeaves.push(maciState.stateLeaves[0].asCircuitInputs()) + } + + const elGamalEnc = [[c1, c2]] + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + elGamalEnc.push(elGamalEncryptBit( + KEY_FOR_ELGAMAL_ENCRYPTION, + BigInt(0), + BigInt(1) + )) + } + + const inputs = stringifyBigInts({ + coordPrivKey: coordinatorKeypair.privKey.asCircuitInputs(), + coordPubKey: coordinatorKeypair.pubKey.rawPubKey, + encPubKeys, + msgs: messageArr.map(m => m.asCircuitInputs()), + deactivatedTreePathElements, + stateLeafPathElements, + currentStateLeaves, + elGamalEnc, + maskingValues, + deactivatedTreeRoot: deactivatedKeys.root, + currentStateRoot: maciState.stateTree.root, + numSignUps: NUM_OF_SIGNUPS, + chainHash: H, + inputHash: sha256Hash([ + deactivatedKeys.root, + NUM_OF_SIGNUPS, + maciState.stateTree.root, + H, + ]), + }) + + await expect(genWitness(circuit, inputs)).rejects.toThrow(); + }) + + it('should throw if elgamalEncryption not performed with coordination pub key', async () => { + const NUM_OF_SIGNUPS = BigInt(1); + const VOTE_WEIGHT = voteWeight; + const MESSAGE_PUB_KEY = new PubKey([BigInt(0), BigInt(0)]); + const KEY_FOR_COORD_PART_OF_SHARED_KEY = coordinatorKeypair.pubKey; + const DEACTIVATED_KEYS_TREE_ELEMENT_INDEX = 0; + const STATE_TREE_ELEMENT_INDEX = stateIndex; + const ecdhKeypair = new Keypair() + const KEY_FOR_ELGAMAL_ENCRYPTION = ecdhKeypair.pubKey.rawPubKey; // wrong pub key, should be coordinators + + const salt = (new Keypair()).privKey.rawPrivKey + + // Key deactivation command + const command = new PCommand( + stateIndex, //BigInt(1), + MESSAGE_PUB_KEY, // 0,0 PubKey + voteOptionIndex, // 0, + VOTE_WEIGHT, // vote weight + BigInt(2), // nonce + BigInt(pollId), + salt, + ) + + const signature = command.sign(userKeypair.privKey) + + const sharedKey = Keypair.genEcdhSharedKey( + ecdhKeypair.privKey, + KEY_FOR_COORD_PART_OF_SHARED_KEY + ) + + // Encrypt command and publish + const message = command.encrypt(signature, sharedKey) + messages.push(message) + + const messageArr = [message]; + for (let i = 1; i < maxValues.maxMessages; i += 1) { + messageArr.push(new Message(BigInt(0), Array(10).fill(BigInt(0)))) + } + + // ecdhKeypair.pubKey -> encPubKey + const messageHash = message.hash(ecdhKeypair.pubKey); + + const DEACT_TREE_ARITY = 5; + + const deactivatedKeys = new IncrementalQuinTree( + STATE_TREE_DEPTH, + H0, + DEACT_TREE_ARITY, + hash5, + ) + + const mask = BigInt(Math.ceil(Math.random() * 1000)) + const maskingValues = [mask.toString()] + + const status = BigInt(1); + const [c1, c2] = elGamalEncryptBit( + coordinatorKeypair.pubKey.rawPubKey, + status, + mask + ) + + console.log(maciState.stateLeaves[1]) + console.log(userKeypair.pubKey) + console.log(maciState.stateLeaves[1].asCircuitInputs()) + + for (let i = 1; i < maxValues.maxMessages; i += 1) { + maskingValues.push('1') + } + + deactivatedKeys.insert( (new DeactivatedKeyLeaf( + userKeypair.pubKey, + c1, + c2, + salt, + )).hash()) + + H = hash2([H0, messageHash]) + + const encPubKeys = [ecdhKeypair.pubKey.asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + encPubKeys.push(new PubKey([BigInt(0), BigInt(0)]).asCircuitInputs()); + } + + console.log(encPubKeys) + + const deactivatedTreePathElements = [deactivatedKeys.genMerklePath(DEACTIVATED_KEYS_TREE_ELEMENT_INDEX).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + deactivatedTreePathElements.push(deactivatedKeys.genMerklePath(DEACTIVATED_KEYS_TREE_ELEMENT_INDEX).pathElements) + } + + const stateLeafPathElements = [maciState.stateTree.genMerklePath(STATE_TREE_ELEMENT_INDEX).pathElements]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + stateLeafPathElements.push(maciState.stateTree.genMerklePath(0).pathElements) + } + + const currentStateLeaves = [maciState.stateLeaves[1].asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + currentStateLeaves.push(maciState.stateLeaves[0].asCircuitInputs()) + } + + const elGamalEnc = [[c1, c2]] + // Pad array + for (let i = 1; i < maxValues.maxMessages; i += 1) { + elGamalEnc.push(elGamalEncryptBit( + KEY_FOR_ELGAMAL_ENCRYPTION, + BigInt(0), + BigInt(1) + )) + } + + const inputs = stringifyBigInts({ + coordPrivKey: coordinatorKeypair.privKey.asCircuitInputs(), + coordPubKey: coordinatorKeypair.pubKey.rawPubKey, + encPubKeys, + msgs: messageArr.map(m => m.asCircuitInputs()), + deactivatedTreePathElements, + stateLeafPathElements, + currentStateLeaves, + elGamalEnc, + maskingValues, + deactivatedTreeRoot: deactivatedKeys.root, + currentStateRoot: maciState.stateTree.root, + numSignUps: NUM_OF_SIGNUPS, + chainHash: H, + inputHash: sha256Hash([ + deactivatedKeys.root, + NUM_OF_SIGNUPS, + maciState.stateTree.root, + H, + ]), + }) + + await expect(genWitness(circuit, inputs)).rejects.toThrow(); + }) + }) +}) diff --git a/circuits/ts/__tests__/elGamalRerandomize.test.ts b/circuits/ts/__tests__/elGamalRerandomize.test.ts index d516baf76d..cd527d3d55 100644 --- a/circuits/ts/__tests__/elGamalRerandomize.test.ts +++ b/circuits/ts/__tests__/elGamalRerandomize.test.ts @@ -1,12 +1,14 @@ jest.setTimeout(120000) -import { - stringifyBigInts, - genPrivKey, +import { + stringifyBigInts, + genPrivKey, elGamalEncryptBit, elGamalDecryptBit, + babyJubMaxValue, + babyJubAddPoint } from 'maci-crypto' -import { +import { genWitness, getSignalByName, } from './utils' @@ -15,14 +17,16 @@ import { Keypair, } from 'maci-domainobjs' +// import { babyJub } from 'circomlib' + describe('El Gamal rerandomization circuit', () => { const circuit = 'elGamalRerandomize_test' - it('correctly computes rerandomized values', async () => { + it('should correctly re-randomize the cyphertext from 0 and 1 bit', async () => { const keypair = new Keypair(); - - const z = genPrivKey(); - for (let bit = 0; bit < 2; bit ++) { + const z = genPrivKey(); + + for (let bit = 0; bit < 2; bit++) { const y = genPrivKey(); const [c1, c2] = elGamalEncryptBit(keypair.pubKey.rawPubKey, BigInt(bit), y); @@ -32,9 +36,9 @@ describe('El Gamal rerandomization circuit', () => { c2, z, }) - + const witness = await genWitness(circuit, circuitInputs) - + const [c1r0, c1r1] = [ BigInt(await getSignalByName(circuit, witness, 'main.c1r[0]')), BigInt(await getSignalByName(circuit, witness, 'main.c1r[1]')), @@ -51,7 +55,93 @@ describe('El Gamal rerandomization circuit', () => { expect(dBit).toEqual(BigInt(bit)); } - - }) + + /** Fixed */ + + it('should correctly handle large inputs', async () => { + const keypair = new Keypair(); + + for (let bit = 0; bit < 2; bit++) { + const y = genPrivKey(); + const [c1, c2] = elGamalEncryptBit(keypair.pubKey.rawPubKey, BigInt(bit), y); + + const z = babyJubMaxValue; // largest possible value + const circuitInputs = stringifyBigInts({ + pubKey: keypair.pubKey.asCircuitInputs(), + c1, + c2, + z + }); + + const witness = await genWitness(circuit, circuitInputs); + const [c1r0, c1r1] = [ + BigInt(await getSignalByName(circuit, witness, 'main.c1r[0]')), + BigInt(await getSignalByName(circuit, witness, 'main.c1r[1]')), + ]; + const [c2r0, c2r1] = [ + BigInt(await getSignalByName(circuit, witness, 'main.c2r[0]')), + BigInt(await getSignalByName(circuit, witness, 'main.c2r[1]')), + ]; + const [c1R, c2R] = [[c1r0, c1r1], [c2r0, c2r1]]; + + const dBit = elGamalDecryptBit(keypair.privKey.rawPrivKey, c1R, c2R); + expect(dBit).toEqual(BigInt(bit)); + } + }) + + /** To be checked */ + + // TODO: Currently fails with Invalid point value because point addition leads to points that are not 0 or G. + // Verify if we need to\can test addition at all. + // it('correctly handles re-randomization consistency', async () => { + // const keypair = new Keypair(); + + // for (let bit = 0; bit < 2; bit++) { + // const y = genPrivKey(); + // const [c1, c2] = elGamalEncryptBit(keypair.pubKey.rawPubKey, BigInt(bit), y); + + // const z1 = genPrivKey(); + // const circuitInputs1 = stringifyBigInts({ + // pubKey: keypair.pubKey.asCircuitInputs(), + // c1, + // c2, + // z: z1, + // }); + // const witness1 = await genWitness(circuit, circuitInputs1); + + // const [c1r10, c1r11] = [ + // BigInt(await getSignalByName(circuit, witness1, 'main.c1r[0]')), + // BigInt(await getSignalByName(circuit, witness1, 'main.c1r[1]')), + // ]; + // const [c2r10, c2r11] = [ + // BigInt(await getSignalByName(circuit, witness1, 'main.c2r[0]')), + // BigInt(await getSignalByName(circuit, witness1, 'main.c2r[1]')), + // ]; + + // const z2 = genPrivKey(); + // const circuitInputs2 = stringifyBigInts({ + // pubKey: keypair.pubKey.asCircuitInputs(), + // c1: [c1r10, c1r11], + // c2: [c2r10, c2r11], + // z: z2, + // }); + // const witness2 = await genWitness(circuit, circuitInputs2); + + // const [c1r20, c1r21] = [ + // BigInt(await getSignalByName(circuit, witness2, 'main.c1r[0]')), + // BigInt(await getSignalByName(circuit, witness2, 'main.c1r[1]')), + // ]; + // const [c2r20, c2r21] = [ + // BigInt(await getSignalByName(circuit, witness2, 'main.c2r[0]')), + // BigInt(await getSignalByName(circuit, witness2, 'main.c2r[1]')), + // ]; + + // const c1Combined = babyJubAddPoint([c1r10, c1r11], [c1r20, c1r21]); + // const c2Combined = babyJubAddPoint([c2r10, c2r11], [c2r20, c2r21]); + + // const dBit = elGamalDecryptBit(keypair.privKey.rawPrivKey, c1Combined, c2Combined); + // expect(dBit).toEqual(BigInt(bit)); + // } + // }) }) diff --git a/cli/package-lock.json b/cli/package-lock.json index 76e1cbc73a..98f74500e7 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -20,6 +20,7 @@ "shelljs": "^0.8.4", "snarkjs": "^0.5.0", "source-map-support": "^0.5.19", + "typescript": "^4.2.3", "web3": "^1.3.4", "xmlhttprequest": "1.8.0", "zkey-manager": "^0.1.1" @@ -1327,34 +1328,6 @@ "resolved": "https://registry.npmjs.org/fastfile/-/fastfile-0.0.19.tgz", "integrity": "sha512-tz9nWR5KYb6eR2odFQ7oxqEkx8F3YQZ6NBJoJR92YEG3DqYOqyxMck8PKvTVNKx3uwvOqGnLXNScnqpdHRdHGQ==" }, - "node_modules/@iden3/binfileutils/node_modules/ffjavascript": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.48.tgz", - "integrity": "sha512-uNrWP+odLofNmmO+iCCPi/Xt/sJh1ku3pVKmKWVWCLFfdCP69hvRrogKUIGnsdiINcWn0lGxcEh5oEjStMFXQQ==", - "dependencies": { - "big-integer": "^1.6.48", - "wasmbuilder": "^0.0.12", - "wasmcurves": "0.1.0", - "web-worker": "^1.2.0" - } - }, - "node_modules/@iden3/binfileutils/node_modules/wasmbuilder": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.12.tgz", - "integrity": "sha512-dTMpBgrnLOXrN58i2zakn2ScynsBhq9LfyQIsPz4CyxRF9k1GAORniuqn3xmE9NnI1l7g3iiVCkoB2Cl0/oG8w==", - "dependencies": { - "big-integer": "^1.6.48" - } - }, - "node_modules/@iden3/binfileutils/node_modules/wasmcurves": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.1.0.tgz", - "integrity": "sha512-kIlcgbVUAv2uQ6lGsepGz/m5V40+Z6rvTBkqCYn3Y2+OcXst+UaP4filJYLh/xDxjJl62FFjZZeAnpeli1Y5/Q==", - "dependencies": { - "big-integer": "^1.6.42", - "blakejs": "^1.1.0" - } - }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1380,19 +1353,6 @@ "node": ">=6" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", @@ -1406,45 +1366,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -1510,15 +1431,6 @@ "node": ">= 10.14.2" } }, - "node_modules/@jest/core/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/core/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -1534,18 +1446,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@jest/core/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/environment": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", @@ -2228,9 +2128,9 @@ } }, "node_modules/@nomiclabs/hardhat-ethers": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.0.4.tgz", - "integrity": "sha512-7LMR344TkdCYkMVF9LuC9VU2NBIi84akQiwqm7OufpWaDgHbWhuanY53rk3SVAW0E4HBk5xn5wl5+bN5f+Mq5w==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.3.tgz", + "integrity": "sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg==", "peerDependencies": { "ethers": "^5.0.0", "hardhat": "^2.0.0" @@ -2782,6 +2682,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3833,11 +3741,6 @@ "web-worker": "^1.2.0" } }, - "node_modules/circom_runtime/node_modules/wasmbuilder": { - "version": "0.0.16", - "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz", - "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==" - }, "node_modules/circom_runtime/node_modules/wasmcurves": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.0.tgz", @@ -3923,46 +3826,6 @@ "wrap-ansi": "^7.0.0" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/clone-response": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", @@ -5488,14 +5351,6 @@ "buildzqfield": "src/buildzqfield.js" } }, - "node_modules/ffiasm/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/ffiasm/node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -5522,86 +5377,6 @@ "node": ">=0.10.0" } }, - "node_modules/ffiasm/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ffiasm/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ffiasm/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ffiasm/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ffiasm/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ffiasm/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ffiasm/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ffiasm/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -5653,6 +5428,24 @@ "node": ">=6" } }, + "node_modules/ffjavascript": { + "version": "0.2.59", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.59.tgz", + "integrity": "sha512-QssOEUv+wilz9Sg7Zaj6KWAm7QceOAEsFuEBTltUsDo1cjn11rA/LGYvzFBPbzNfxRlZxwgJ7uxpCQcdDlrNfw==", + "dependencies": { + "wasmbuilder": "0.0.16", + "wasmcurves": "0.2.1", + "web-worker": "^1.2.0" + } + }, + "node_modules/ffjavascript/node_modules/wasmcurves": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.1.tgz", + "integrity": "sha512-9ciO7bUE5bgpbOcdK7IO3enrSVIKHwrQmPibok4GLJWaCA7Wyqc9PRYnu5HbiFv9NDFNqVKPtU5R6Is5KujBLg==", + "dependencies": { + "wasmbuilder": "0.0.16" + } + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -5721,6 +5514,18 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -7271,6 +7076,14 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-function": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", @@ -8156,15 +7969,6 @@ "node": ">= 10.14.2" } }, - "node_modules/jest-runtime/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-runtime/node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -8194,93 +7998,6 @@ "node": ">=0.10.0" } }, - "node_modules/jest-runtime/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-runtime/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-runtime/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -8487,15 +8204,6 @@ "node": ">=8" } }, - "node_modules/jest/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest/node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -8525,28 +8233,6 @@ "node": ">=0.10.0" } }, - "node_modules/jest/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest/node_modules/jest-cli": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", @@ -8574,71 +8260,6 @@ "node": ">= 10.14.2" } }, - "node_modules/jest/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest/node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -9022,6 +8643,17 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -9899,6 +9531,31 @@ "node": ">=4" } }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-map": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", @@ -10045,80 +9702,28 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 6" } }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "dependencies": { - "p-limit": "^2.2.0" + "find-up": "^4.0.0" }, "engines": { "node": ">=8" @@ -10157,15 +9762,6 @@ "node": ">= 10" } }, - "node_modules/pretty-format/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -10405,58 +10001,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/read-pkg-up/node_modules/type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -11471,11 +11015,6 @@ "ffjavascript": "0.2.56" } }, - "node_modules/snarkjs/node_modules/wasmbuilder": { - "version": "0.0.16", - "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz", - "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==" - }, "node_modules/snarkjs/node_modules/wasmcurves": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.0.tgz", @@ -11779,22 +11318,14 @@ "node": ">=10" } }, - "node_modules/string-length/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": { - "ansi-regex": "^5.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" @@ -11824,6 +11355,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -12379,8 +11921,6 @@ "version": "4.8.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", - "devOptional": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12667,6 +12207,11 @@ "makeerror": "1.0.12" } }, + "node_modules/wasmbuilder": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz", + "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==" + }, "node_modules/wasmcurves": { "version": "0.0.14", "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.0.14.tgz", @@ -13789,46 +13334,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -13987,46 +13492,6 @@ "node": ">=10" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -14969,34 +14434,6 @@ "version": "0.0.19", "resolved": "https://registry.npmjs.org/fastfile/-/fastfile-0.0.19.tgz", "integrity": "sha512-tz9nWR5KYb6eR2odFQ7oxqEkx8F3YQZ6NBJoJR92YEG3DqYOqyxMck8PKvTVNKx3uwvOqGnLXNScnqpdHRdHGQ==" - }, - "ffjavascript": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.48.tgz", - "integrity": "sha512-uNrWP+odLofNmmO+iCCPi/Xt/sJh1ku3pVKmKWVWCLFfdCP69hvRrogKUIGnsdiINcWn0lGxcEh5oEjStMFXQQ==", - "requires": { - "big-integer": "^1.6.48", - "wasmbuilder": "^0.0.12", - "wasmcurves": "0.1.0", - "web-worker": "^1.2.0" - } - }, - "wasmbuilder": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.12.tgz", - "integrity": "sha512-dTMpBgrnLOXrN58i2zakn2ScynsBhq9LfyQIsPz4CyxRF9k1GAORniuqn3xmE9NnI1l7g3iiVCkoB2Cl0/oG8w==", - "requires": { - "big-integer": "^1.6.48" - } - }, - "wasmcurves": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.1.0.tgz", - "integrity": "sha512-kIlcgbVUAv2uQ6lGsepGz/m5V40+Z6rvTBkqCYn3Y2+OcXst+UaP4filJYLh/xDxjJl62FFjZZeAnpeli1Y5/Q==", - "requires": { - "big-integer": "^1.6.42", - "blakejs": "^1.1.0" - } } } }, @@ -15019,16 +14456,6 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, "js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", @@ -15038,33 +14465,6 @@ "argparse": "^1.0.7", "esprima": "^4.0.0" } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } } } }, @@ -15124,12 +14524,6 @@ "strip-ansi": "^6.0.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -15138,15 +14532,6 @@ "requires": { "glob": "^7.1.3" } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } } } }, @@ -15640,9 +15025,9 @@ "optional": true }, "@nomiclabs/hardhat-ethers": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.0.4.tgz", - "integrity": "sha512-7LMR344TkdCYkMVF9LuC9VU2NBIi84akQiwqm7OufpWaDgHbWhuanY53rk3SVAW0E4HBk5xn5wl5+bN5f+Mq5w==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.3.tgz", + "integrity": "sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg==", "requires": {} }, "@scure/base": { @@ -16102,6 +15487,11 @@ "type-fest": "^0.21.3" } }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -16935,11 +16325,6 @@ "web-worker": "^1.2.0" } }, - "wasmbuilder": { - "version": "0.0.16", - "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz", - "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==" - }, "wasmcurves": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.0.tgz", @@ -17012,38 +16397,8 @@ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "requires": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, "clone-response": { @@ -18297,11 +17652,6 @@ "yargs": "^15.3.1" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -18322,62 +17672,6 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -18422,6 +17716,26 @@ } } }, + "ffjavascript": { + "version": "0.2.59", + "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.59.tgz", + "integrity": "sha512-QssOEUv+wilz9Sg7Zaj6KWAm7QceOAEsFuEBTltUsDo1cjn11rA/LGYvzFBPbzNfxRlZxwgJ7uxpCQcdDlrNfw==", + "requires": { + "wasmbuilder": "0.0.16", + "wasmcurves": "0.2.1", + "web-worker": "^1.2.0" + }, + "dependencies": { + "wasmcurves": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.1.tgz", + "integrity": "sha512-9ciO7bUE5bgpbOcdK7IO3enrSVIKHwrQmPibok4GLJWaCA7Wyqc9PRYnu5HbiFv9NDFNqVKPtU5R6Is5KujBLg==", + "requires": { + "wasmbuilder": "0.0.16" + } + } + } + }, "filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -18485,6 +17799,15 @@ } } }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, "flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -19603,6 +18926,11 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, "is-function": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", @@ -19868,12 +19196,6 @@ "jest-cli": "^26.6.3" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -19897,22 +19219,6 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, "jest-cli": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", @@ -19934,53 +19240,6 @@ "yargs": "^15.4.1" } }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -20423,12 +19682,6 @@ "yargs": "^15.4.1" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -20452,69 +19705,6 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -20947,6 +20137,14 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -21621,6 +20819,22 @@ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, "p-map": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", @@ -21747,45 +20961,6 @@ "dev": true, "requires": { "find-up": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - } } }, "posix-character-classes": { @@ -21810,14 +20985,6 @@ "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - } } }, "process": { @@ -22002,43 +21169,6 @@ "type-fest": "^0.8.1" }, "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -22844,11 +21974,6 @@ "ffjavascript": "0.2.56" } }, - "wasmbuilder": { - "version": "0.0.16", - "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz", - "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==" - }, "wasmcurves": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.0.tgz", @@ -23101,23 +22226,16 @@ "requires": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "string.prototype.trimend": { @@ -23138,6 +22256,14 @@ "define-properties": "^1.1.3" } }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -23554,9 +22680,7 @@ "typescript": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", - "devOptional": true, - "peer": true + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==" }, "ultron": { "version": "1.1.1", @@ -23786,6 +22910,11 @@ "makeerror": "1.0.12" } }, + "wasmbuilder": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz", + "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==" + }, "wasmcurves": { "version": "0.0.14", "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.0.14.tgz", @@ -24544,36 +23673,6 @@ "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } } }, "wrappy": { @@ -24681,36 +23780,6 @@ "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } } }, "yargs-parser": { diff --git a/cli/tests/prepare_test.sh b/cli/tests/prepare_test.sh index 5f2aa8e960..0862d7323a 100755 --- a/cli/tests/prepare_test.sh +++ b/cli/tests/prepare_test.sh @@ -13,7 +13,7 @@ ZKEYS_POSTFIX="test" PROCESS_MESSAGES_PARAMS="10-2-1-2_$ZKEYS_POSTFIX" TALLY_VOTES_PARAMS="10-1-2_$ZKEYS_POSTFIX" SUBSIDY_PER_BATCH_PARAMS="10-1-2_$ZKEYS_POSTFIX" - +PROCESS_DEACTIVATION_MESSAGES_PARAMS="5-10_$ZKEYS_POSTFIX" init_maci() { $MACI_CLI deployVkRegistry @@ -25,10 +25,13 @@ init_maci() { --vote-option-tree-depth 2 \ --msg-batch-depth 1 \ --process-messages-zkey "$ZKEYS_DIR"/ProcessMessages_"$PROCESS_MESSAGES_PARAMS".0.zkey \ + --process-deactivation-zkey "$ZKEYS_DIR"/ProcessDeactivationMessages_"$PROCESS_DEACTIVATION_MESSAGES_PARAMS".0.zkey \ --tally-votes-zkey "$ZKEYS_DIR"/TallyVotes_"$TALLY_VOTES_PARAMS".0.zkey \ $SET_VERIFYING_KEYS_FLAG_SUBSIDY - $MACI_CLI create + $MACI_CLI create \ + --signup-deadline 1689834390 \ + --deactivation-period 86400 } deploy_poll() { @@ -65,8 +68,8 @@ gen_proofs() { --privkey macisk.49953af3585856f539d194b46c82f4ed54ec508fb9b882940cbe68bbc57e59e \ --poll-id $1 \ --rapidsnark ~/rapidsnark/build/prover \ - --process-witnessgen ./zkeys/ProcessMessages_"$PROCESS_MESSAGES_PARAMS" \ - --tally-witnessgen ./zkeys/TallyVotes_"$TALLY_VOTES_PARAMS" \ + --process-witnessgen "$ZKEYS_DIR"/ProcessMessages_"$PROCESS_MESSAGES_PARAMS" \ + --tally-witnessgen "$ZKEYS_DIR"/TallyVotes_"$TALLY_VOTES_PARAMS" \ --process-zkey "$ZKEYS_DIR"/ProcessMessages_"$PROCESS_MESSAGES_PARAMS".0.zkey \ --tally-zkey "$ZKEYS_DIR"/TallyVotes_"$TALLY_VOTES_PARAMS".0.zkey \ --tally-file tally.json \ @@ -94,7 +97,7 @@ set_subsidy_option() { SUBSIDY_ON="true" fi - GEN_PROOFS_FLAG_SUBSIDY_WITNESS="--subsidy-witnessgen ./zkeys/SubsidyPerBatch_$SUBSIDY_PER_BATCH_PARAMS" + GEN_PROOFS_FLAG_SUBSIDY_WITNESS="--subsidy-witnessgen "$ZKEYS_DIR"/SubsidyPerBatch_$SUBSIDY_PER_BATCH_PARAMS" GEN_PROOFS_FLAG_SUBSIDY_ZKEY="--subsidy-zkey "$ZKEYS_DIR"/SubsidyPerBatch_"$SUBSIDY_PER_BATCH_PARAMS".0.zkey" SUBSIDDY_OPTION_GEN_PROOFS='' SET_VERIFYING_KEYS_FLAG_SUBSIDY='' diff --git a/cli/tests/vanilla/testKeyDeactivation.sh b/cli/tests/vanilla/testKeyDeactivation.sh new file mode 100755 index 0000000000..eb6f210c88 --- /dev/null +++ b/cli/tests/vanilla/testKeyDeactivation.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +set -e + +BASE_DIR="$(dirname "$BASH_SOURCE")" + +ZKEYS_DIR="$BASE_DIR"/../../zkeys + +ZKEYS_POSTFIX="test" + +PROCESS_DEACTIVATION_MESSAGES_PARAMS="5-10_$ZKEYS_POSTFIX" + +. "$BASE_DIR"/../prepare_test.sh + +# 1 signup and 1 message +clean + +POLL_ID=0 + +init_maci +deploy_poll + + +$MACI_CLI signup \ + --pubkey macipk.3e7bb2d7f0a1b7e980f1b6f363d1e3b7a12b9ae354c2cd60a9cfa9fd12917391 + +# signup period +$MACI_CLI timeTravel \ + --seconds 100 + +$MACI_CLI deactivateKey \ + --privkey macisk.fd7aa614ec4a82716ffc219c24fd7e7b52a2b63b5afb17e81c22fe21515539c \ + --state-index 1 \ + --nonce 2 \ + --salt 0x798D81BE4A9870C079B8DE539496AB95 \ + --poll-id $POLL_ID + +$MACI_CLI timeTravel \ + --seconds 10 + +# $MACI_CLI generateNewKey \ add when implemented + +# --from-block since MACI deployed +$MACI_CLI confirmDeactivation \ + --poll-id $POLL_ID \ + --privkey macisk.49953af3585856f539d194b46c82f4ed54ec508fb9b882940cbe68bbc57e59e \ + --from-block 0 \ + --batch-size 1 \ + + +# key deactivation happens after original period 86400s from prepare_test.sh has expired +$MACI_CLI timeTravel \ + --seconds 90000 + +# missing triggering of smart contract code to pass batches +$MACI_CLI completeDeactivation \ + --poll-id $POLL_ID \ + --privkey macisk.49953af3585856f539d194b46c82f4ed54ec508fb9b882940cbe68bbc57e59e \ + --state-num-sr-queue-ops 1 \ + --deactivated-keys-num-sr-queue-ops 1 \ + --from-block 0 \ + --process-deactivation-witnessgen "$ZKEYS_DIR"/ProcessDeactivationMessages_"$PROCESS_DEACTIVATION_MESSAGES_PARAMS" \ + --process-deactivation-zkey "$ZKEYS_DIR"/ProcessDeactivationMessages_"$PROCESS_DEACTIVATION_MESSAGES_PARAMS".0.zkey \ + --rapidsnark ~/rapidsnark/build/prover \ + diff --git a/cli/tests/vanilla/testKeyDeactivationAfterDeactivationPeriod.sh b/cli/tests/vanilla/testKeyDeactivationAfterDeactivationPeriod.sh new file mode 100644 index 0000000000..71f7e484c3 --- /dev/null +++ b/cli/tests/vanilla/testKeyDeactivationAfterDeactivationPeriod.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +set -e + +BASE_DIR="$(dirname "$BASH_SOURCE")" + +. "$BASE_DIR"/../prepare_test.sh + +# 1 signup and 1 message +clean + +POLL_ID=0 + +init_maci +deploy_poll + + +$MACI_CLI signup \ + --pubkey macipk.3e7bb2d7f0a1b7e980f1b6f363d1e3b7a12b9ae354c2cd60a9cfa9fd12917391 + +# signup period +$MACI_CLI timeTravel \ + --seconds 100 + +# key deactivation happens after original period 86400s from prepare_test.sh has expired +$MACI_CLI timeTravel \ + --seconds 90000 + +KEY_DEACTIVATION_RESULT=$($MACI_CLI deactivateKey \ + --privkey macisk.fd7aa614ec4a82716ffc219c24fd7e7b52a2b63b5afb17e81c22fe21515539c \ + --state-index 1 \ + --nonce 2 \ + --salt 0x798D81BE4A9870C079B8DE539496AB95 \ + --poll-id "$POLL_ID") + +if [[ $KEY_DEACTIVATION_RESULT == "" ]] +then + # TODO: Extend this to assert for the exact error number from error output of the deactivate key command above + echo "Test OK if PollE11 error is thrown" +else + echo "Test NOk" +fi + diff --git a/cli/tests/vanilla/testKeyDeactivationBeforeDeactivationPeriod.sh b/cli/tests/vanilla/testKeyDeactivationBeforeDeactivationPeriod.sh new file mode 100644 index 0000000000..bbfb254fe5 --- /dev/null +++ b/cli/tests/vanilla/testKeyDeactivationBeforeDeactivationPeriod.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +set -e + +BASE_DIR="$(dirname "$BASH_SOURCE")" + +. "$BASE_DIR"/../prepare_test.sh + +# 1 signup and 1 message +clean + +POLL_ID=0 + +init_maci +deploy_poll + + +$MACI_CLI signup \ + --pubkey macipk.3e7bb2d7f0a1b7e980f1b6f363d1e3b7a12b9ae354c2cd60a9cfa9fd12917391 + +# signup period +$MACI_CLI timeTravel \ + --seconds 100 + +# key deactivation happens before original period 86400s from prepare_test.sh has expired +$MACI_CLI timeTravel \ + --seconds 10 + +KEY_DEACTIVATION_RESULT=$($MACI_CLI deactivateKey \ + --privkey macisk.fd7aa614ec4a82716ffc219c24fd7e7b52a2b63b5afb17e81c22fe21515539c \ + --state-index 1 \ + --nonce 1 \ + --salt 0x798D81BE4A9870C079B8DE539496AB95 \ + --poll-id "$POLL_ID") + +echo $KEY_DEACTIVATION_RESULT + +if [[ $KEY_DEACTIVATION_RESULT == *"Transaction hash"* ]] +then + echo "Test Ok" +else + echo "Test NOk" +fi \ No newline at end of file diff --git a/cli/ts/completeDeactivation.ts b/cli/ts/completeDeactivation.ts new file mode 100644 index 0000000000..c674c70f8d --- /dev/null +++ b/cli/ts/completeDeactivation.ts @@ -0,0 +1,267 @@ +const { ethers } = require('hardhat'); +import { parseArtifact, getDefaultSigner, genMaciStateFromContract, formatProofForVerifierContract } from 'maci-contracts'; +import { genProof, verifyProof, extractVk } from 'maci-circuits' +import { readJSONFile, promptPwd } from 'maci-common'; +import { contractExists, validateEthAddress, isPathExist } from './utils'; +import { Keypair, PrivKey } from 'maci-domainobjs'; +import { contractFilepath } from './config'; + +const configureSubparser = (subparsers: any) => { + const createParser = subparsers.addParser('completeDeactivation', { + addHelp: true, + }); + + createParser.addArgument(['-x', '--maci-address'], { + action: 'store', + type: 'string', + help: 'The MACI contract address', + }); + + createParser.addArgument(['-pi', '--poll-id'], { + action: 'store', + type: 'int', + required: true, + help: 'The poll ID', + }); + + const maciPrivkeyGroup = createParser.addMutuallyExclusiveGroup({ required: true }) + + maciPrivkeyGroup.addArgument( + ['-dsk', '--prompt-for-maci-privkey'], + { + action: 'storeTrue', + help: 'Whether to prompt for your serialized MACI private key', + } + ) + + maciPrivkeyGroup.addArgument( + ['-sk', '--privkey'], + { + action: 'store', + type: 'string', + help: 'Your serialized MACI private key', + } + ) + + createParser.addArgument(['-snsq', '--state-num-sr-queue-ops'], { + action: 'store', + type: 'int', + required: true, + help: 'The number of subroot queue operations to merge for the MACI state tree', + }); + + createParser.addArgument(['-dnsq', '--deactivated-keys-num-sr-queue-ops'], { + action: 'store', + type: 'int', + required: true, + help: 'The number of subroot queue operations to merge for the deactivated keys tree', + }); + + createParser.addArgument(['-fb', '--from-block'], { + action: 'store', + type: 'int', + required: true, + help: 'The block number to start listening from', + }); + + createParser.addArgument( + ['-wpd', '--process-deactivation-witnessgen'], + { + required: true, + type: 'string', + help: 'The path to the ProcessDeactivationMessages witness generation binary', + } + ) + + createParser.addArgument( + ['-zpd', '--process-deactivation-zkey'], + { + required: true, + type: 'string', + help: 'The path to the ProcessDeactivationMessages .zkey file', + } + ) + + createParser.addArgument( + ['-r', '--rapidsnark'], + { + required: true, + type: 'string', + help: 'The path to the rapidsnark binary', + } + ) + + createParser.addArgument(['-sd', '--seed'], { + action: 'store', + type: 'int', + help: 'Random generator seed value', + }); +}; + +const completeDeactivation = async (args: any) => { + const rapidsnarkExe = args.rapidsnark + const processDeactivationDatFile = args.process_deactivation_witnessgen + ".dat" + + const [ok, path] = isPathExist([ + rapidsnarkExe, + args.process_deactivation_witnessgen, + processDeactivationDatFile, + args.process_deactivation_zkey, + ]) + if (!ok) { + console.error(`Error: ${path} does not exist.`) + return 1 + } + + // Extract the verifying keys + const processVk = extractVk(args.process_deactivation_zkey) + + // MACI contract address + const contractAddrs = readJSONFile(contractFilepath); + if ((!contractAddrs || !contractAddrs['MACI']) && !args.maci_address) { + console.error('Error: MACI contract address is empty'); + return 1; + } + const maciAddress = args.maci_address + ? args.maci_address + : contractAddrs['MACI']; + + // Verify that MACI contract exists + if (!validateEthAddress(maciAddress)) { + console.error('Error: invalid MACI contract address'); + return 1; + } + + // Verify poll ID + const pollId = args.poll_id; + if (pollId < 0) { + console.error('Error: the Poll ID should be a positive integer.'); + return 1; + } + + // Get contract artifacts + const [maciContractAbi] = parseArtifact('MACI'); + const [mpContractAbi] = parseArtifact('MessageProcessor'); + const [pollContractAbi] = parseArtifact('Poll'); + const [accQueueContractAbi] = parseArtifact('AccQueue'); + + // Verify that MACI contract address is deployed at the given address + const signer = await getDefaultSigner(); + if (!(await contractExists(signer.provider, maciAddress))) { + console.error( + 'Error: there is no MACI contract deployed at the specified address' + ); + return 1; + } + + // Initialize MACI contract object + const maciContractEthers = new ethers.Contract( + maciAddress, + maciContractAbi, + signer + ); + + // Verify that Poll contract address is deployed at the given address + const pollAddr = await maciContractEthers.getPoll(pollId); + if (!(await contractExists(signer.provider, pollAddr))) { + console.error( + 'Error: there is no Poll contract with this poll ID linked to the specified MACI contract.' + ); + return 1; + } + + // Initialize Poll contract object + const pollContract = new ethers.Contract(pollAddr, pollContractAbi, signer); + + let serializedPrivkey + if (args.prompt_for_maci_privkey) { + serializedPrivkey = await promptPwd('Your MACI private key') + } else { + serializedPrivkey = args.privkey + } + + if (!PrivKey.isValidSerializedPrivKey(serializedPrivkey)) { + console.error('Error: invalid MACI private key') + return 1 + } + + const fromBlock = args.from_block ? args.from_block : 0; + const maciPrivkey = PrivKey.unserialize(serializedPrivkey) + const coordinatorKeypair = new Keypair(maciPrivkey) + + // Reconstruct MACI state + const maciState = await genMaciStateFromContract( + signer.provider, + maciAddress, + coordinatorKeypair, + pollId, + fromBlock, + ) + + const mpAddress = args.mp + ? args.mp + : contractAddrs['MessageProcessor-' + pollId]; + + const mpContract = new ethers.Contract(mpAddress, mpContractAbi, signer); + + const seed = args.seed ? BigInt(args.seed) : BigInt(42); + const { circuitInputs, deactivatedLeaves } = maciState.polls[pollId].processDeactivationMessages(seed); + + let r + try { + r = genProof( + circuitInputs, + rapidsnarkExe, + args.process_deactivation_witnessgen, + args.process_deactivation_zkey, + ) + } catch (e) { + console.error('Error: could not generate proof.') + console.error(e) + return 1 + } + + // Verify the proof + const isValid = verifyProof( + r.publicInputs, + r.proof, + processVk, + ) + + if (!isValid) { + console.error('Error: generated an invalid proof') + return 1 + } + + const { proof } = r; + const formattedProof = formatProofForVerifierContract(proof); + + const stateNumSrQueueOps = args.state_num_sr_queue_ops; + + try { + await mpContract.mergeForDeactivation( + stateNumSrQueueOps, + pollContract.address, + pollId + ); + } catch (e) { + console.error("mpContract.mergeForDeactivation"); + console.error(e); + return 1; + } + + try { + await mpContract.completeDeactivation( + formattedProof, + pollContract.address, + ); + } catch (e) { + console.error("mpContract.completeDeactivation"); + console.error(e); + return 1; + } + + return 0; +}; + +export { completeDeactivation, configureSubparser }; diff --git a/cli/ts/confirmDeactivation.ts b/cli/ts/confirmDeactivation.ts new file mode 100644 index 0000000000..a3b3483fb4 --- /dev/null +++ b/cli/ts/confirmDeactivation.ts @@ -0,0 +1,179 @@ +const { ethers } = require('hardhat'); +import { parseArtifact, getDefaultSigner, genMaciStateFromContract } from 'maci-contracts'; +import { readJSONFile, promptPwd } from 'maci-common'; +import { contractExists, validateEthAddress } from './utils'; +import { contractFilepath } from './config'; +import { DEFAULT_ETH_PROVIDER } from './defaults'; +import { Keypair, PrivKey} from 'maci-domainobjs'; + +const configureSubparser = (subparsers: any) => { + const createParser = subparsers.addParser('confirmDeactivation', { + addHelp: true, + }); + + createParser.addArgument(['-x', '--maci-address'], { + action: 'store', + type: 'string', + help: 'The MACI contract address', + }); + + createParser.addArgument(['-pi', '--poll-id'], { + action: 'store', + type: 'int', + required: true, + help: 'The poll ID', + }); + + const maciPrivkeyGroup = createParser.addMutuallyExclusiveGroup({ required: true }) + + maciPrivkeyGroup.addArgument( + ['-dsk', '--prompt-for-maci-privkey'], + { + action: 'storeTrue', + help: 'Whether to prompt for your serialized MACI private key', + } + ) + + maciPrivkeyGroup.addArgument( + ['-sk', '--privkey'], + { + action: 'store', + type: 'string', + help: 'Your serialized MACI private key', + } + ) + + createParser.addArgument(['-ep', '--eth-provider'], { + action: 'store', + type: 'string', + help: 'The Ethereum provider to use for listening to events. Default: http://localhost:8545', + }); + + createParser.addArgument(['-fb', '--from-block'], { + action: 'store', + type: 'int', + required: true, + help: 'The block number to start listening from', + }); + + createParser.addArgument(['-bs', '--batch-size'], { + action: 'store', + type: 'int', + help: 'The capacity of the subroot of the deactivated keys tree to be merged. Default: 1', + }); + + createParser.addArgument(['-sd', '--seed'], { + action: 'store', + type: 'int', + help: 'Random generator seed value', + }); +}; + +const confirmDeactivation = async (args: any) => { + // MACI contract address + const contractAddrs = readJSONFile(contractFilepath); + if ((!contractAddrs || !contractAddrs['MACI']) && !args.maci_address) { + console.error('Error: MACI contract address is empty'); + return 1; + } + const maciAddress = args.maci_address + ? args.maci_address + : contractAddrs['MACI']; + + // Verify that MACI contract exists + if (!validateEthAddress(maciAddress)) { + console.error('Error: invalid MACI contract address'); + return 1; + } + + // Verify poll ID + const pollId = args.poll_id; + if (pollId < 0) { + console.error('Error: the Poll ID should be a positive integer.'); + return 1; + } + + // Get contract artifacts + const [maciContractAbi] = parseArtifact('MACI'); + const [pollContractAbi] = parseArtifact('Poll'); + + // Verify that MACI contract address is deployed at the given address + const signer = await getDefaultSigner(); + if (!(await contractExists(signer.provider, maciAddress))) { + console.error( + 'Error: there is no MACI contract deployed at the specified address' + ); + return 1; + } + + // Initialize MACI contract object + const maciContractEthers = new ethers.Contract( + maciAddress, + maciContractAbi, + signer + ); + + // Verify that Poll contract address is deployed at the given address + const pollAddr = await maciContractEthers.getPoll(pollId); + if (!(await contractExists(signer.provider, pollAddr))) { + console.error( + 'Error: there is no Poll contract with this poll ID linked to the specified MACI contract.' + ); + return 1; + } + + // Initialize Poll contract object + const pollContract = new ethers.Contract(pollAddr, pollContractAbi, signer); + + const batchSize = args.batch_size ? args.batch_size : 1; + let serializedPrivkey; + + if (args.prompt_for_maci_privkey) { + serializedPrivkey = await promptPwd('Your MACI private key') + } else { + serializedPrivkey = args.privkey + } + + if (!PrivKey.isValidSerializedPrivKey(serializedPrivkey)) { + console.error('Error: invalid MACI private key') + return 1 + } + + const fromBlock = args.from_block ? args.from_block : 0; + + const maciPrivkey = PrivKey.unserialize(serializedPrivkey) + const coordinatorKeypair = new Keypair(maciPrivkey) + + // Reconstruct MACI state + const maciState = await genMaciStateFromContract( + signer.provider, + maciAddress, + coordinatorKeypair, + pollId, + fromBlock, + ) + + const seed = args.seed ? BigInt(args.seed) : BigInt(42); + const { circuitInputs, deactivatedLeaves } = maciState.polls[pollId].processDeactivationMessages(seed); + const numBatches = Math.ceil(deactivatedLeaves.length / batchSize); + + for (let i = 0; i < numBatches; i++ ) { + const batch = deactivatedLeaves + .slice(batchSize * i, batchSize * (i + 1)) + .map(leaf => leaf.asArray()); + + try { + await pollContract.confirmDeactivation( + batch, + batch.length, + ); + } catch (e) { + console.error(e); + return 1; + } + } + + return 0; +}; + +export { confirmDeactivation, configureSubparser }; diff --git a/cli/ts/create.ts b/cli/ts/create.ts index f061779e35..8941c7edb4 100644 --- a/cli/ts/create.ts +++ b/cli/ts/create.ts @@ -1,144 +1,147 @@ import { - deployConstantInitialVoiceCreditProxy, - deployFreeForAllSignUpGatekeeper, - deployMaci, - deployVerifier, - deployTopupCredit as deployTopupCreditContract, -} from 'maci-contracts' + deployConstantInitialVoiceCreditProxy, + deployFreeForAllSignUpGatekeeper, + deployMaci, + deployVerifier, + deployTopupCredit as deployTopupCreditContract, +} from 'maci-contracts'; -import {readJSONFile, writeJSONFile} from 'maci-common' -import {contractFilepath} from './config' +import { readJSONFile, writeJSONFile } from 'maci-common'; +import { contractFilepath } from './config'; -import { - DEFAULT_INITIAL_VOICE_CREDITS, -} from './defaults' +import { DEFAULT_INITIAL_VOICE_CREDITS } from './defaults'; const configureSubparser = (subparsers: any) => { - const createParser = subparsers.addParser( - 'create', - { addHelp: true }, - ) - - createParser.addArgument( - ['-r', '--vk-registry'], - { - type: 'string', - help: 'The VkRegistry contract address', - } - ) - - const vcGroup = createParser.addMutuallyExclusiveGroup() - - vcGroup.addArgument( - ['-c', '--initial-voice-credits'], - { - action: 'store', - type: 'int', - help: 'Each user\'s initial voice credits. Default: 100', - } - ) - - vcGroup.addArgument( - ['-i', '--initial-vc-proxy'], - { - action: 'store', - type: 'string', - help: 'If specified, deploys the MACI contract with this address as the initial voice credit proxy constructor argument. Otherwise, deploys a ConstantInitialVoiceCreditProxy contract with the above-specified value.', - } - ) - - createParser.addArgument( - ['-g', '--signup-gatekeeper'], - { - action: 'store', - type: 'string', - help: 'If specified, deploys the MACI contract with this address as the signup gatekeeper constructor argument. Otherwise, deploys a gatekeeper contract which allows any address to sign up.', - } - ) -} + const createParser = subparsers.addParser('create', { addHelp: true }); + + createParser.addArgument(['-r', '--vk-registry'], { + type: 'string', + help: 'The VkRegistry contract address', + }); + + const vcGroup = createParser.addMutuallyExclusiveGroup(); + + vcGroup.addArgument(['-c', '--initial-voice-credits'], { + action: 'store', + type: 'int', + help: "Each user's initial voice credits. Default: 100", + }); + + vcGroup.addArgument(['-i', '--initial-vc-proxy'], { + action: 'store', + type: 'string', + help: 'If specified, deploys the MACI contract with this address as the initial voice credit proxy constructor argument. Otherwise, deploys a ConstantInitialVoiceCreditProxy contract with the above-specified value.', + }); + + createParser.addArgument(['-g', '--signup-gatekeeper'], { + action: 'store', + type: 'string', + help: 'If specified, deploys the MACI contract with this address as the signup gatekeeper constructor argument. Otherwise, deploys a gatekeeper contract which allows any address to sign up.', + }); + + createParser.addArgument(['-d', '--signup-deadline'], { + action: 'store', + type: 'int', + help: 'The signup deadline.', + }); + + createParser.addArgument(['-p', '--deactivation-period'], { + action: 'store', + type: 'int', + help: 'The deactivation period.', + }); +}; const create = async (args: any) => { - let contractAddrs = readJSONFile(contractFilepath) - if ((!contractAddrs||!contractAddrs["VkRegistry"]) && !args.vk_registry) { - console.error('Error: vkRegistry contract address is empty') - return 1 - } - - const TopupCreditContract = await deployTopupCreditContract() - console.log('TopupCredit:', TopupCreditContract.address) - - // Initial voice credits - const initialVoiceCredits = args.initial_voice_credits ? args.initial_voice_credits : DEFAULT_INITIAL_VOICE_CREDITS - - // Initial voice credit proxy contract - const initialVoiceCreditProxy = args.initial_vc_proxy - - // Whether we should deploy a ConstantInitialVoiceCreditProxy - if (initialVoiceCreditProxy != undefined && initialVoiceCredits != undefined) { - console.error('Error: only one of the following can be specified: the initial voice credit proxy or the amount of initial voice credits.') - return 1 - } - - let initialVoiceCreditProxyContractAddress - if (initialVoiceCreditProxy == undefined) { - // Deploy a ConstantInitialVoiceCreditProxy contract - const c = await deployConstantInitialVoiceCreditProxy( - initialVoiceCredits, - false, - ) - initialVoiceCreditProxyContractAddress = c.address - } else { - initialVoiceCreditProxyContractAddress = initialVoiceCreditProxy - } - - // Signup gatekeeper contract - const signupGatekeeper = args.signup_gatekeeper - - let signUpGatekeeperAddress - if (signupGatekeeper == undefined) { - // Deploy a FreeForAllGatekeeper contract - const c = await deployFreeForAllSignUpGatekeeper(true) - signUpGatekeeperAddress = c.address - } else { - signUpGatekeeperAddress = signupGatekeeper - } - - - const verifierContract = await deployVerifier(true) - - const vkRegistryContractAddress = args.vk_registry ? args.vk_registry: contractAddrs["VkRegistry"] - - const { - maciContract, - stateAqContract, - pollFactoryContract, - poseidonAddrs - } = await deployMaci( - signUpGatekeeperAddress, - initialVoiceCreditProxyContractAddress, - verifierContract.address, - vkRegistryContractAddress, - TopupCreditContract.address - ) - - console.log('MACI:', maciContract.address) - - contractAddrs['InitialVoiceCreditProxy'] = initialVoiceCreditProxyContractAddress - contractAddrs['SignUpGatekeeper'] = signUpGatekeeperAddress - contractAddrs['Verifier'] = verifierContract.address - contractAddrs['MACI'] = maciContract.address - contractAddrs['StateAq'] = stateAqContract.address - contractAddrs['PollFactory'] = pollFactoryContract.address - contractAddrs['TopupCredit'] = TopupCreditContract.address - contractAddrs['PoseidonT3'] = poseidonAddrs[0] - contractAddrs['PoseidonT4'] = poseidonAddrs[1] - contractAddrs['PoseidonT5'] = poseidonAddrs[2] - contractAddrs['PoseidonT6'] = poseidonAddrs[3] - writeJSONFile(contractFilepath, contractAddrs) - return 0 -} - -export { - create, - configureSubparser, -} + let contractAddrs = readJSONFile(contractFilepath); + if ((!contractAddrs || !contractAddrs['VkRegistry']) && !args.vk_registry) { + console.error('Error: vkRegistry contract address is empty'); + return 1; + } + + const TopupCreditContract = await deployTopupCreditContract(); + console.log('TopupCredit:', TopupCreditContract.address); + + // Initial voice credits + const initialVoiceCredits = args.initial_voice_credits + ? args.initial_voice_credits + : DEFAULT_INITIAL_VOICE_CREDITS; + + // Initial voice credit proxy contract + const initialVoiceCreditProxy = args.initial_vc_proxy; + + // Whether we should deploy a ConstantInitialVoiceCreditProxy + if ( + initialVoiceCreditProxy != undefined && + initialVoiceCredits != undefined + ) { + console.error( + 'Error: only one of the following can be specified: the initial voice credit proxy or the amount of initial voice credits.' + ); + return 1; + } + + let initialVoiceCreditProxyContractAddress; + if (initialVoiceCreditProxy == undefined) { + // Deploy a ConstantInitialVoiceCreditProxy contract + const c = await deployConstantInitialVoiceCreditProxy( + initialVoiceCredits, + false + ); + initialVoiceCreditProxyContractAddress = c.address; + } else { + initialVoiceCreditProxyContractAddress = initialVoiceCreditProxy; + } + + // Signup gatekeeper contract + const signupGatekeeper = args.signup_gatekeeper; + + let signUpGatekeeperAddress; + if (signupGatekeeper == undefined) { + // Deploy a FreeForAllGatekeeper contract + const c = await deployFreeForAllSignUpGatekeeper(true); + signUpGatekeeperAddress = c.address; + } else { + signUpGatekeeperAddress = signupGatekeeper; + } + + const verifierContract = await deployVerifier(true); + + const vkRegistryContractAddress = args.vk_registry + ? args.vk_registry + : contractAddrs['VkRegistry']; + + const signUpDeadline = args.signup_deadline; + + const deactivationPeriod = args.deactivation_period; + + const { maciContract, stateAqContract, pollFactoryContract, poseidonAddrs } = + await deployMaci( + signUpGatekeeperAddress, + initialVoiceCreditProxyContractAddress, + verifierContract.address, + vkRegistryContractAddress, + TopupCreditContract.address, + signUpDeadline, + deactivationPeriod + ); + + console.log('MACI:', maciContract.address); + + contractAddrs['InitialVoiceCreditProxy'] = + initialVoiceCreditProxyContractAddress; + contractAddrs['SignUpGatekeeper'] = signUpGatekeeperAddress; + contractAddrs['Verifier'] = verifierContract.address; + contractAddrs['MACI'] = maciContract.address; + contractAddrs['StateAq'] = stateAqContract.address; + contractAddrs['PollFactory'] = pollFactoryContract.address; + contractAddrs['TopupCredit'] = TopupCreditContract.address; + contractAddrs['PoseidonT3'] = poseidonAddrs[0]; + contractAddrs['PoseidonT4'] = poseidonAddrs[1]; + contractAddrs['PoseidonT5'] = poseidonAddrs[2]; + contractAddrs['PoseidonT6'] = poseidonAddrs[3]; + writeJSONFile(contractFilepath, contractAddrs); + return 0; +}; + +export { create, configureSubparser }; diff --git a/cli/ts/deactivateKey.ts b/cli/ts/deactivateKey.ts new file mode 100644 index 0000000000..ab1b67556c --- /dev/null +++ b/cli/ts/deactivateKey.ts @@ -0,0 +1,297 @@ +import { + parseArtifact, + getDefaultSigner, +} from 'maci-contracts' + +import { + PubKey, + PrivKey, + Keypair, + PCommand, +} from 'maci-domainobjs' + +import { + genRandomSalt, +} from 'maci-crypto' + +import { + promptPwd, + validateEthAddress, + validateSaltSize, + validateSaltFormat, + contractExists, + checkDeployerProviderConnection, + batchTransactionRequests, +} from './utils' + +import {readJSONFile} from 'maci-common' +import {contractFilepath} from './config' + +import { + DEFAULT_ETH_PROVIDER, +} from './defaults' + +const { ethers } = require('hardhat') + +const DEFAULT_SALT = genRandomSalt() + +const configureSubparser = (subparsers: any) => { + const parser = subparsers.addParser( + 'deactivateKey', + { addHelp: true }, + ) + + parser.addArgument( + ['-x', '--contract'], + { + type: 'string', + help: 'The MACI contract address', + } + ) + + const maciPrivkeyGroup = parser.addMutuallyExclusiveGroup({ required: true }) + + maciPrivkeyGroup.addArgument( + ['-dsk', '--prompt-for-maci-privkey'], + { + action: 'storeTrue', + help: 'Whether to prompt for your serialized MACI private key', + } + ) + + maciPrivkeyGroup.addArgument( + ['-sk', '--privkey'], + { + action: 'store', + type: 'string', + help: 'Your serialized MACI private key', + } + ) + + parser.addArgument( + ['-i', '--state-index'], + { + required: true, + action: 'store', + type: 'int', + help: 'The user\'s state index', + } + ) + + parser.addArgument( + ['-n', '--nonce'], + { + required: true, + action: 'store', + type: 'int', + help: 'The message nonce', + } + ) + + parser.addArgument( + ['-s', '--salt'], + { + action: 'store', + type: 'string', + help: 'The message salt', + } + ) + + parser.addArgument( + ['-o', '--poll-id'], + { + action: 'store', + required: true, + type: 'string', + help: 'The Poll ID', + } + ) +} + +const deactivateKey = async (args: any) => { + // TODO: Why is this here if line 119 below? + // // User's MACI public key + // if (!PubKey.isValidSerializedPubKey(args.pubkey)) { + // console.error('Error: invalid MACI public key') + // return 1 + // } + + // Hardcoded for key deactivation + const userMaciPubKey = new PubKey([BigInt(0), BigInt(0)]) + + const contractAddrs = readJSONFile(contractFilepath) + if ((!contractAddrs||!contractAddrs["MACI"]) && !args.contract) { + console.error('Error: MACI contract address is empty') + return 1 + } + const maciAddress = args.contract ? args.contract: contractAddrs["MACI"] + + // MACI contract + if (!validateEthAddress(maciAddress)) { + console.error('Error: invalid MACI contract address') + return 1 + } + + // Ethereum provider + const ethProvider = args.eth_provider ? args.eth_provider : DEFAULT_ETH_PROVIDER + + // The user's MACI private key + let serializedPrivkey + if (args.prompt_for_maci_privkey) { + serializedPrivkey = await promptPwd('Your MACI private key') + } else { + serializedPrivkey = args.privkey + } + + if (!PrivKey.isValidSerializedPrivKey(serializedPrivkey)) { + console.error('Error: invalid MACI private key') + return 1 + } + + const userMaciPrivkey = PrivKey.unserialize(serializedPrivkey) + + // State index + const stateIndex = BigInt(args.state_index) + if (stateIndex < 0) { + console.error('Error: the state index must be greater than 0') + return 0 + } + + // Vote option index - Set to 0 for key deactivation + const voteOptionIndex = BigInt(0) + + // The nonce + const nonce = BigInt(args.nonce) + + if (nonce < 0) { + console.error('Error: the nonce should be 0 or greater') + return 0 + } + + // The salt + let salt + if (args.salt) { + if (!validateSaltFormat(args.salt)) { + console.error('Error: the salt should be a 32-byte hexadecimal string') + return 0 + } + + salt = BigInt(args.salt) + + if (!validateSaltSize(args.salt)) { + console.error('Error: the salt should less than the BabyJub field size') + return 0 + } + } else { + salt = DEFAULT_SALT + } + + const signer = await getDefaultSigner() + if (! (await contractExists(signer.provider, maciAddress))) { + console.error('Error: there is no MACI contract deployed at the specified address') + return 1 + } + + const pollId = args.poll_id + + if (pollId < 0) { + console.error('Error: the Poll ID should be a positive integer.') + return 1 + } + + const [ maciContractAbi ] = parseArtifact('MACI') + const [ pollContractAbi ] = parseArtifact('Poll') + + const maciContractEthers = new ethers.Contract( + maciAddress, + maciContractAbi, + signer, + ) + + const pollAddr = await maciContractEthers.getPoll(pollId) + if (! (await contractExists(signer.provider, pollAddr))) { + console.error('Error: there is no Poll contract with this poll ID linked to the specified MACI contract.') + return 1 + } + + //const pollContract = new web3.eth.Contract(pollContractAbi, pollAddr) + const pollContract = new ethers.Contract( + pollAddr, + pollContractAbi, + signer, + ) + + const maxValues = await pollContract.maxValues() + const coordinatorPubKeyResult = await pollContract.coordinatorPubKey() + const maxVoteOptions = Number(maxValues.maxVoteOptions) + + // Validate the vote option index against the max leaf index on-chain + if (maxVoteOptions < voteOptionIndex) { + console.error('Error: the vote option index is invalid') + throw new Error() + } + + const coordinatorPubKey = new PubKey([ + BigInt(coordinatorPubKeyResult.x.toString()), + BigInt(coordinatorPubKeyResult.y.toString()), + ]) + + const pollContractEthers = new ethers.Contract( + pollAddr, + pollContractAbi, + signer, + ) + + // The new vote weight - Set to 0 for key deactivation + const newVoteWeight = BigInt(0) + + const encKeypair = new Keypair() + + const command: PCommand = new PCommand( + stateIndex, + userMaciPubKey, + voteOptionIndex, + newVoteWeight, + nonce, + pollId, + salt, + ) + const signature = command.sign(userMaciPrivkey) + const message = command.encrypt( + signature, + Keypair.genEcdhSharedKey( + encKeypair.privKey, + coordinatorPubKey, + ) + ) + + let tx = null; + try { + tx = await pollContractEthers.deactivateKey( + message.asContractParam(), + encKeypair.pubKey.asContractParam(), + { gasLimit: 10000000 }, + ) + await tx.wait() + + console.log('Transaction hash:', tx.hash) + console.log('Ephemeral private key:', encKeypair.privKey.serialize()) + } catch(e) { + if (e.message) { + if (e.message.endsWith('PollE11')) { + console.error('Error: the key deactivation period is over.') + } else { + console.error('Error: the transaction failed.') + console.error(e.message) + } + } + return 1 + } + + return 0 +} + +export { + deactivateKey, + configureSubparser, +} diff --git a/cli/ts/index.ts b/cli/ts/index.ts index 166a8b24cc..cddd65bef3 100644 --- a/cli/ts/index.ts +++ b/cli/ts/index.ts @@ -1,204 +1,230 @@ #!/usr/bin/env node -import 'source-map-support/register' +import 'source-map-support/register'; -import * as argparse from 'argparse' -import { - calcBinaryTreeDepthFromMaxLeaves, - calcQuinTreeDepthFromMaxLeaves, -} from './utils' +import * as argparse from 'argparse'; +import { + calcBinaryTreeDepthFromMaxLeaves, + calcQuinTreeDepthFromMaxLeaves, +} from './utils'; + +import { + timeTravel, + configureSubparser as configureSubparserForTimeTravel, +} from './timeTravel'; + +import { + genMaciKeypair, + configureSubparser as configureSubparserForGenMaciKeypair, +} from './genMaciKeypair'; import { - timeTravel, - configureSubparser as configureSubparserForTimeTravel, -} from './timeTravel' + genMaciPubkey, + configureSubparser as configureSubparserForGenMaciPubkey, +} from './genMaciPubkey'; import { - genMaciKeypair, - configureSubparser as configureSubparserForGenMaciKeypair, -} from './genMaciKeypair' + deployVkRegistry, + configureSubparser as configureSubparserForDeployVkRegistry, +} from './deployVkRegistry'; import { - genMaciPubkey, - configureSubparser as configureSubparserForGenMaciPubkey, -} from './genMaciPubkey' + setVerifyingKeys, + configureSubparser as configureSubparserForSetVerifyingKeys, +} from './setVerifyingKeys'; import { - deployVkRegistry, - configureSubparser as configureSubparserForDeployVkRegistry, -} from './deployVkRegistry' + create, + configureSubparser as configureSubparserForCreate, +} from './create'; import { - setVerifyingKeys, - configureSubparser as configureSubparserForSetVerifyingKeys, -} from './setVerifyingKeys' + deployPoll, + configureSubparser as configureSubparserForDeployPoll, +} from './deployPoll'; import { - create, - configureSubparser as configureSubparserForCreate, -} from './create' + airdrop, + configureSubparser as configureSubparserForAirdrop, +} from './airdrop'; import { - deployPoll, - configureSubparser as configureSubparserForDeployPoll, -} from './deployPoll' + topup, + configureSubparser as configureSubparserForTopup, +} from './topup'; import { - airdrop, - configureSubparser as configureSubparserForAirdrop, -} from './airdrop' + signup, + configureSubparser as configureSubparserForSignup, +} from './signUp'; import { - topup, - configureSubparser as configureSubparserForTopup, -} from './topup' + publish, + configureSubparser as configureSubparserForPublish, +} from './publish'; import { - signup, - configureSubparser as configureSubparserForSignup, -} from './signUp' + mergeMessages, + configureSubparser as configureSubparserForMergeMessages, +} from './mergeMessages'; import { - publish, - configureSubparser as configureSubparserForPublish, -} from './publish' + mergeSignups, + configureSubparser as configureSubparserForMergeSignups, +} from './mergeSignups'; import { - mergeMessages, - configureSubparser as configureSubparserForMergeMessages, -} from './mergeMessages' + genProofs, + configureSubparser as configureSubparserForGenProofs, +} from './genProofs'; import { - mergeSignups, - configureSubparser as configureSubparserForMergeSignups, -} from './mergeSignups' + proveOnChain, + configureSubparser as configureSubparserForProveOnChain, +} from './proveOnChain'; import { - genProofs, - configureSubparser as configureSubparserForGenProofs, -} from './genProofs' + verify, + configureSubparser as configureSubparserForVerify, +} from './verify'; import { - proveOnChain, - configureSubparser as configureSubparserForProveOnChain, -} from './proveOnChain' + checkVerifyingKey, + configureSubparser as configureSubparserForCheckVerifyKey, +} from './checkVerifyingKey'; import { - verify, - configureSubparser as configureSubparserForVerify, -} from './verify' + confirmDeactivation, + configureSubparser as configureSubparserForConfirmDeactivation, +} from './confirmDeactivation'; import { - checkVerifyingKey, - configureSubparser as configureSubparserForCheckVerifyKey, -} from './checkVerifyingKey' + completeDeactivation, + configureSubparser as configureSubparserForCompleteDeactivation, +} from './completeDeactivation'; + +import { + deactivateKey, + configureSubparser as configureSubparserForDeactivateKey, +} from './deactivateKey' const main = async () => { - const parser = new argparse.ArgumentParser({ - description: 'Minimal Anti-Collusion Infrastructure', - }) - - const subparsers = parser.addSubparsers({ - title: 'Subcommands', - dest: 'subcommand', - }) - - // Subcommand: timeTravel - configureSubparserForTimeTravel(subparsers) - - // Subcommand: genMaciPubkey - configureSubparserForGenMaciPubkey(subparsers) - - // Subcommand: genMaciKeypair - configureSubparserForGenMaciKeypair(subparsers) - - // Subcommand: deployVkRegistry - configureSubparserForDeployVkRegistry(subparsers) - - // Subcommand: setVerifyingKeys - configureSubparserForSetVerifyingKeys(subparsers) - - // Subcommand: create - configureSubparserForCreate(subparsers) - - // Subcommand: deployPoll - configureSubparserForDeployPoll(subparsers) - - // Subcommand: airdrop - configureSubparserForAirdrop(subparsers) - - // Subcommand: topup - configureSubparserForTopup(subparsers) - - // Subcommand: signup - configureSubparserForSignup(subparsers) - - // Subcommand: publish - configureSubparserForPublish(subparsers) - - // Subcommand: mergeMessages - configureSubparserForMergeMessages(subparsers) - - // Subcommand: mergeSignups - configureSubparserForMergeSignups(subparsers) - - // Subcommand: genProofs - configureSubparserForGenProofs(subparsers) - - // Subcommand: proveOnChain - configureSubparserForProveOnChain(subparsers) - - // Subcommand: verify - configureSubparserForVerify(subparsers) - - // Subcommand: checkVerifyKey - configureSubparserForCheckVerifyKey(subparsers) - - const args = parser.parseArgs() - - // Execute the subcommand method - if (args.subcommand === 'timeTravel') { - await timeTravel(args) - } else if (args.subcommand === 'genMaciKeypair') { - await genMaciKeypair(args) - } else if (args.subcommand === 'genMaciPubkey') { - await genMaciPubkey(args) - } else if (args.subcommand === 'deployVkRegistry') { - await deployVkRegistry(args) - } else if (args.subcommand === 'setVerifyingKeys') { - await setVerifyingKeys(args) - } else if (args.subcommand === 'create') { - await create(args) - } else if (args.subcommand === 'deployPoll') { - await deployPoll(args) - } else if (args.subcommand === 'airdrop') { - await airdrop(args) - } else if (args.subcommand === 'topup') { - await topup(args) - } else if (args.subcommand === 'signup') { - await signup(args) - } else if (args.subcommand === 'publish') { - await publish(args) - } else if (args.subcommand === 'mergeMessages') { - await mergeMessages(args) - } else if (args.subcommand === 'mergeSignups') { - await mergeSignups(args) - } else if (args.subcommand === 'genProofs') { - await genProofs(args) - } else if (args.subcommand === 'proveOnChain') { - await proveOnChain(args) - } else if (args.subcommand === 'verify') { - await verify(args) + const parser = new argparse.ArgumentParser({ + description: 'Minimal Anti-Collusion Infrastructure', + }); + + const subparsers = parser.addSubparsers({ + title: 'Subcommands', + dest: 'subcommand', + }); + + // Subcommand: timeTravel + configureSubparserForTimeTravel(subparsers); + + // Subcommand: genMaciPubkey + configureSubparserForGenMaciPubkey(subparsers); + + // Subcommand: genMaciKeypair + configureSubparserForGenMaciKeypair(subparsers); + + // Subcommand: deployVkRegistry + configureSubparserForDeployVkRegistry(subparsers); + + // Subcommand: setVerifyingKeys + configureSubparserForSetVerifyingKeys(subparsers); + + // Subcommand: create + configureSubparserForCreate(subparsers); + + // Subcommand: deployPoll + configureSubparserForDeployPoll(subparsers); + + // Subcommand: airdrop + configureSubparserForAirdrop(subparsers); + + // Subcommand: topup + configureSubparserForTopup(subparsers); + + // Subcommand: signup + configureSubparserForSignup(subparsers); + + // Subcommand: publish + configureSubparserForPublish(subparsers); + + // Subcommand: mergeMessages + configureSubparserForMergeMessages(subparsers); + + // Subcommand: mergeSignups + configureSubparserForMergeSignups(subparsers); + + // Subcommand: genProofs + configureSubparserForGenProofs(subparsers); + + // Subcommand: proveOnChain + configureSubparserForProveOnChain(subparsers); + + // Subcommand: verify + configureSubparserForVerify(subparsers); + + // Subcommand: checkVerifyKey + configureSubparserForCheckVerifyKey(subparsers); + + // Subcommand: deactivateKey + configureSubparserForDeactivateKey(subparsers); + + // Subcommand: confirmDeactivation + configureSubparserForConfirmDeactivation(subparsers); + + // Subcommand: completeDeactivation + configureSubparserForCompleteDeactivation(subparsers); + + const args = parser.parseArgs(); + + // Execute the subcommand method + if (args.subcommand === 'timeTravel') { + await timeTravel(args); + } else if (args.subcommand === 'genMaciKeypair') { + await genMaciKeypair(args); + } else if (args.subcommand === 'genMaciPubkey') { + await genMaciPubkey(args); + } else if (args.subcommand === 'deployVkRegistry') { + await deployVkRegistry(args); + } else if (args.subcommand === 'setVerifyingKeys') { + await setVerifyingKeys(args); + } else if (args.subcommand === 'create') { + await create(args); + } else if (args.subcommand === 'deployPoll') { + await deployPoll(args); + } else if (args.subcommand === 'airdrop') { + await airdrop(args); + } else if (args.subcommand === 'topup') { + await topup(args); + } else if (args.subcommand === 'signup') { + await signup(args); + } else if (args.subcommand === 'publish') { + await publish(args); + } else if (args.subcommand === 'mergeMessages') { + await mergeMessages(args); + } else if (args.subcommand === 'mergeSignups') { + await mergeSignups(args); + } else if (args.subcommand === 'genProofs') { + await genProofs(args); + } else if (args.subcommand === 'proveOnChain') { + await proveOnChain(args); + } else if (args.subcommand === 'verify') { + await verify(args); + } else if (args.subcommand === 'checkVerifyingKey') { + await checkVerifyingKey(args); + } else if (args.subcommand === 'confirmDeactivation') { + await confirmDeactivation(args); + } else if (args.subcommand === 'completeDeactivation') { + await completeDeactivation(args); + } else if (args.subcommand === 'deactivateKey') { + await deactivateKey(args) } - else if (args.subcommand === 'checkVerifyingKey') { - await checkVerifyingKey(args) - } -} +}; if (require.main === module) { - main() + main(); } -export { - calcBinaryTreeDepthFromMaxLeaves, - calcQuinTreeDepthFromMaxLeaves, -} +export { calcBinaryTreeDepthFromMaxLeaves, calcQuinTreeDepthFromMaxLeaves }; diff --git a/cli/ts/mergeSignups.ts b/cli/ts/mergeSignups.ts index 1f334a003b..488777dfb6 100644 --- a/cli/ts/mergeSignups.ts +++ b/cli/ts/mergeSignups.ts @@ -139,16 +139,22 @@ const mergeSignups = async (args: any) => { `Merging state subroots ${indices[0] + 1} / ${indices[1] + 1}`, ) - const tx = await pollContract.mergeMaciStateAqSubRoots( - args.num_queue_ops.toString(), - pollId.toString(), - ) - const receipt = await tx.wait() - - console.log( - `Executed mergeMaciStateAqSubRoots(); ` + - `gas used: ${receipt.gasUsed.toString()}`) - console.log(`Transaction hash: ${receipt.transactionHash}\n`) + if (!(await pollContract.stateAqMerged())) { + const tx = await pollContract.mergeMaciStateAqSubRoots( + args.num_queue_ops.toString(), + pollId.toString(), + ) + const receipt = await tx.wait() + + console.log( + `Executed mergeMaciStateAqSubRoots(); ` + + `gas used: ${receipt.gasUsed.toString()}`) + console.log(`Transaction hash: ${receipt.transactionHash}\n`) + } else { + console.log( + `State subroots alread merged. Continuing...`, + ) + } } // Check if the state AQ has been fully merged diff --git a/cli/ts/proveOnChain.ts b/cli/ts/proveOnChain.ts index 4b019322d3..239fdbf95d 100644 --- a/cli/ts/proveOnChain.ts +++ b/cli/ts/proveOnChain.ts @@ -1,630 +1,644 @@ -import * as ethers from 'ethers' -import * as fs from 'fs' -import * as path from 'path' +import * as ethers from 'ethers'; +import * as fs from 'fs'; +import * as path from 'path'; -import { hashLeftRight } from 'maci-crypto' +import { hashLeftRight } from 'maci-crypto'; import { - formatProofForVerifierContract, - getDefaultSigner, - parseArtifact, -} from 'maci-contracts' + formatProofForVerifierContract, + getDefaultSigner, + parseArtifact, +} from 'maci-contracts'; -import { - validateEthAddress, - contractExists, - delay, -} from './utils' +import { validateEthAddress, contractExists, delay } from './utils'; -import {readJSONFile} from 'maci-common' -import {contractFilepath} from './config' +import { readJSONFile } from 'maci-common'; +import { contractFilepath } from './config'; const configureSubparser = (subparsers: any) => { - const parser = subparsers.addParser( - 'proveOnChain', - { addHelp: true }, - ) - - parser.addArgument( - ['-x', '--contract'], - { - type: 'string', - help: 'The MACI contract address', - } - ) - - parser.addArgument( - ['-o', '--poll-id'], - { - action: 'store', - required: true, - type: 'string', - help: 'The Poll ID', - } - ) - - parser.addArgument( - ['-q', '--mp'], - { - type: 'string', - help: 'The MessageProcessor contract address', - } - ) - - parser.addArgument( - ['-t', '--tally'], - { - type: 'string', - help: 'The Tally contract address', - } - ) - - parser.addArgument( - ['-s', '--subsidy'], - { - type: 'string', - help: 'The Subsidy contract address', - } - ) - parser.addArgument( - ['-f', '--proof-dir'], - { - required: true, - type: 'string', - help: 'The proof output directory from the genProofs subcommand', - } - ) -} + const parser = subparsers.addParser('proveOnChain', { addHelp: true }); + + parser.addArgument(['-x', '--contract'], { + type: 'string', + help: 'The MACI contract address', + }); + + parser.addArgument(['-o', '--poll-id'], { + action: 'store', + required: true, + type: 'string', + help: 'The Poll ID', + }); + + parser.addArgument(['-q', '--mp'], { + type: 'string', + help: 'The MessageProcessor contract address', + }); + + parser.addArgument(['-t', '--tally'], { + type: 'string', + help: 'The Tally contract address', + }); + + parser.addArgument(['-s', '--subsidy'], { + type: 'string', + help: 'The Subsidy contract address', + }); + parser.addArgument(['-f', '--proof-dir'], { + required: true, + type: 'string', + help: 'The proof output directory from the genProofs subcommand', + }); +}; const proveOnChain = async (args: any) => { - const signer = await getDefaultSigner() - const pollId = Number(args.poll_id) - - // check existence of contract addresses - let contractAddrs = readJSONFile(contractFilepath) - if ((!contractAddrs||!contractAddrs["MACI"]) && !args.contract) { - console.error('Error: MACI contract address is empty') - return 1 - } - if ((!contractAddrs||!contractAddrs["MessageProcessor-"+pollId]) && !args.mp) { - console.error('Error: MessageProcessor contract address is empty') - return 1 - } - if ((!contractAddrs||!contractAddrs["Tally-"+pollId]) && !args.tally) { - console.error('Error: Tally contract address is empty') - return 1 - } - if ((!contractAddrs||!contractAddrs["Subsidy-"+pollId]) && !args.subsidy) { - console.error('Error: Subsidy contract address is empty') - return 1 - } - - const maciAddress = args.contract ? args.contract: contractAddrs["MACI"] - const mpAddress = args.mp ? args.mp : contractAddrs["MessageProcessor-"+pollId] - const tallyAddress = args.tally ? args.tally : contractAddrs["Tally-"+pollId] - const subsidyAddress = args.subsidy ? args.subsidy : contractAddrs["Subsidy-"+pollId] - - // MACI contract - if (!validateEthAddress(maciAddress)) { - console.error('Error: invalid MACI contract address') - return {} - } - - // MessageProcessor contract - if (!validateEthAddress(mpAddress)) { - console.error('Error: invalid MessageProcessor contract address') - return {} - } - - if (! (await contractExists(signer.provider, mpAddress))) { - console.error('Error: there is no contract deployed at the specified address') - return {} - } - - - if (!validateEthAddress(tallyAddress)) { - console.error('Error: invalid Tally contract address') - return {} - } - if (!validateEthAddress(subsidyAddress)) { - console.error('Error: invalid Subsidy contract address') - return {} - } - - const [ maciContractAbi ] = parseArtifact('MACI') - const [ pollContractAbi ] = parseArtifact('Poll') - const [ mpContractAbi ] = parseArtifact('MessageProcessor') - const [ tallyContractAbi ] = parseArtifact('Tally') - const [ subsidyContractAbi ] = parseArtifact('Subsidy') - const [ messageAqContractAbi ] = parseArtifact('AccQueue') - const [ vkRegistryContractAbi ] = parseArtifact('VkRegistry') - const [ verifierContractAbi ] = parseArtifact('Verifier') + const signer = await getDefaultSigner(); + const pollId = Number(args.poll_id); + + // check existence of contract addresses + let contractAddrs = readJSONFile(contractFilepath); + if ((!contractAddrs || !contractAddrs['MACI']) && !args.contract) { + console.error('Error: MACI contract address is empty'); + return 1; + } + if ( + (!contractAddrs || !contractAddrs['MessageProcessor-' + pollId]) && + !args.mp + ) { + console.error('Error: MessageProcessor contract address is empty'); + return 1; + } + if ((!contractAddrs || !contractAddrs['Tally-' + pollId]) && !args.tally) { + console.error('Error: Tally contract address is empty'); + return 1; + } + if ( + (!contractAddrs || !contractAddrs['Subsidy-' + pollId]) && + !args.subsidy + ) { + console.error('Error: Subsidy contract address is empty'); + return 1; + } + + const maciAddress = args.contract ? args.contract : contractAddrs['MACI']; + const mpAddress = args.mp + ? args.mp + : contractAddrs['MessageProcessor-' + pollId]; + const tallyAddress = args.tally + ? args.tally + : contractAddrs['Tally-' + pollId]; + const subsidyAddress = args.subsidy + ? args.subsidy + : contractAddrs['Subsidy-' + pollId]; + + // MACI contract + if (!validateEthAddress(maciAddress)) { + console.error('Error: invalid MACI contract address'); + return {}; + } + + // MessageProcessor contract + if (!validateEthAddress(mpAddress)) { + console.error('Error: invalid MessageProcessor contract address'); + return {}; + } + + if (!(await contractExists(signer.provider, mpAddress))) { + console.error( + 'Error: there is no contract deployed at the specified address' + ); + return {}; + } + + if (!validateEthAddress(tallyAddress)) { + console.error('Error: invalid Tally contract address'); + return {}; + } + if (!validateEthAddress(subsidyAddress)) { + console.error('Error: invalid Subsidy contract address'); + return {}; + } + + const [maciContractAbi] = parseArtifact('MACI'); + const [pollContractAbi] = parseArtifact('Poll'); + const [mpContractAbi] = parseArtifact('MessageProcessor'); + const [tallyContractAbi] = parseArtifact('Tally'); + const [subsidyContractAbi] = parseArtifact('Subsidy'); + const [messageAqContractAbi] = parseArtifact('AccQueue'); + const [vkRegistryContractAbi] = parseArtifact('VkRegistry'); + const [verifierContractAbi] = parseArtifact('Verifier'); const maciContract = new ethers.Contract( - maciAddress, - maciContractAbi, - signer, - ) - - const pollAddr = await maciContract.polls(pollId) - if (! (await contractExists(signer.provider, pollAddr))) { - console.error('Error: there is no Poll contract with this poll ID linked to the specified MACI contract.') - return 1 - } - - const pollContract = new ethers.Contract( - pollAddr, - pollContractAbi, - signer, - ) - - const mpContract = new ethers.Contract( - mpAddress, - mpContractAbi, - signer, - ) - - const tallyContract = new ethers.Contract( - tallyAddress, - tallyContractAbi, - signer, - ) - - const subsidyContract = new ethers.Contract( - subsidyAddress, - subsidyContractAbi, - signer, - ) - - const messageAqContract = new ethers.Contract( - (await pollContract.extContracts()).messageAq, - messageAqContractAbi, - signer, - ) - - const vkRegistryContract = new ethers.Contract( - (await pollContract.extContracts()).vkRegistry, - vkRegistryContractAbi, - signer, - ) - - const verifierContractAddress = await mpContract.verifier() - const verifierContract = new ethers.Contract( - verifierContractAddress, - verifierContractAbi, - signer, - ) - - let data = { - processProofs: {}, - tallyProofs: {}, - subsidyProofs: {}, - } - - let numProcessProofs = 0 - - if (typeof args.proof_file === 'object' && args.proof_file !== null) { - // Argument is a javascript object - data = args.proof_file - } else { - // Read the proof directory - const filenames = fs.readdirSync(args.proof_dir) - for (let i = 0; i < filenames.length; i ++) { - const filename = filenames[i] - const filepath = path.join(args.proof_dir, filename) - let match = filename.match(/process_(\d+)/) - if (match != null) { - data.processProofs[Number(match[1])] = JSON.parse(fs.readFileSync(filepath).toString()) - numProcessProofs ++ - continue - } - - match = filename.match(/tally_(\d+)/) - if (match != null) { - data.tallyProofs[Number(match[1])] = JSON.parse(fs.readFileSync(filepath).toString()) - continue - } - match = filename.match(/subsidy_(\d+)/) - if (match != null) { - data.subsidyProofs[Number(match[1])] = JSON.parse(fs.readFileSync(filepath).toString()) - continue - } - } - } - - const numSignUpsAndMessages = await pollContract.numSignUpsAndMessages() - const numSignUps = Number(numSignUpsAndMessages[0]) - const numMessages = Number(numSignUpsAndMessages[1]) - const batchSizes = await pollContract.batchSizes() - const messageBatchSize = Number(batchSizes.messageBatchSize) - const tallyBatchSize = Number(batchSizes.tallyBatchSize) - const subsidyBatchSize = Number(batchSizes.subsidyBatchSize) - let totalMessageBatches = numMessages <= messageBatchSize ? - 1 - : - Math.floor(numMessages / messageBatchSize) - - if (numMessages > messageBatchSize && numMessages % messageBatchSize > 0) { - totalMessageBatches ++ - } - - if (numProcessProofs !== totalMessageBatches) { - console.error( - `Error: ${args.proof_file} does not have the correct ` + - `number of message processing proofs ` + - `(expected ${totalMessageBatches}, got ${numProcessProofs}.`, - ) - } - - const treeDepths = await pollContract.treeDepths() - - let numBatchesProcessed = Number(await mpContract.numBatchesProcessed()) - const messageRootOnChain = await messageAqContract.getMainRoot( - Number(treeDepths.messageTreeDepth), - ) - - const stateTreeDepth = Number(await maciContract.stateTreeDepth()) - const onChainProcessVk = await vkRegistryContract.getProcessVk( - stateTreeDepth, - treeDepths.messageTreeDepth, - treeDepths.voteOptionTreeDepth, - messageBatchSize, - ) - - - const dd = await pollContract.getDeployTimeAndDuration() - const pollEndTimestampOnChain = BigInt(dd[0]) + BigInt(dd[1]) - - if (numBatchesProcessed < totalMessageBatches) { - console.log('Submitting proofs of message processing...') - } - - for (let i = numBatchesProcessed; i < totalMessageBatches; i ++) { - //const currentMessageBatchIndex = Number(await pptContract.currentMessageBatchIndex()) - let currentMessageBatchIndex - if (numBatchesProcessed === 0) { - const r = numMessages % messageBatchSize - if (r === 0) { - currentMessageBatchIndex = Math.floor(numMessages / messageBatchSize) * messageBatchSize - } else { - currentMessageBatchIndex = numMessages - } - - if (currentMessageBatchIndex > 0) { - if (r === 0) { - currentMessageBatchIndex -= messageBatchSize - } else { - currentMessageBatchIndex -= r - } - } - } else { - currentMessageBatchIndex = (totalMessageBatches - numBatchesProcessed) * messageBatchSize - } - - if (numBatchesProcessed > 0 && currentMessageBatchIndex > 0) { - currentMessageBatchIndex -= messageBatchSize - } - - const txErr = 'Error: processMessages() failed' - const { proof, circuitInputs, publicInputs } = data.processProofs[i] - - // Perform checks - if (circuitInputs.pollEndTimestamp !== pollEndTimestampOnChain.toString()) { - console.error('Error: pollEndTimestamp mismatch.') - return 1 - } - - if (BigInt(circuitInputs.msgRoot).toString() !== messageRootOnChain.toString()) { - console.error('Error: message root mismatch.') - return 1 - } - - let currentSbCommitmentOnChain - - if (numBatchesProcessed === 0) { - currentSbCommitmentOnChain = BigInt(await pollContract.currentSbCommitment()) - } else { - currentSbCommitmentOnChain = BigInt(await mpContract.sbCommitment()) - } - - if (currentSbCommitmentOnChain.toString() !== circuitInputs.currentSbCommitment) { - console.error('Error: currentSbCommitment mismatch.') - return 1 - } - - const coordPubKeyHashOnChain = BigInt(await pollContract.coordinatorPubKeyHash()) - if ( - hashLeftRight( - BigInt(circuitInputs.coordPubKey[0]), - BigInt(circuitInputs.coordPubKey[1]), - ).toString() !== coordPubKeyHashOnChain.toString() - ) { - console.error('Error: coordPubKey mismatch.') - return 1 - } - - const packedValsOnChain = BigInt(await mpContract.genProcessMessagesPackedVals( - pollContract.address, - currentMessageBatchIndex, - numSignUps, - )).toString() - - if (circuitInputs.packedVals !== packedValsOnChain) { - console.error('Error: packedVals mismatch.') - return 1 - } - - const formattedProof = formatProofForVerifierContract(proof) - - const publicInputHashOnChain = BigInt(await mpContract.genProcessMessagesPublicInputHash( - pollContract.address, - currentMessageBatchIndex, - messageRootOnChain.toString(), - numSignUps, - circuitInputs.currentSbCommitment, - circuitInputs.newSbCommitment, - )) - - if (publicInputHashOnChain.toString() !== publicInputs[0].toString()) { - console.error('Public input mismatch.') - return 1 - } - - const isValidOnChain = await verifierContract.verify( - formattedProof, - onChainProcessVk, - publicInputHashOnChain.toString(), - ) - - if (!isValidOnChain) { - console.error('Error: the verifier contract found the proof invalid.') - return 1 - } - - let tx - try { - tx = await mpContract.processMessages( - pollContract.address, - '0x' + BigInt(circuitInputs.newSbCommitment).toString(16), - formattedProof, - ) - } catch (e) { - console.error(txErr) - console.error(e) - } - - const receipt = await tx.wait() - - if (receipt.status !== 1) { - console.error(txErr) - return 1 - } - - console.log(`Transaction hash: ${tx.hash}`) - - // Wait for the node to catch up - numBatchesProcessed = Number(await mpContract.numBatchesProcessed()) - let backOff = 1000 - let numAttempts = 0 - while (numBatchesProcessed !== i + 1) { - await delay(backOff) - backOff *= 1.2 - numAttempts ++ - if (numAttempts >= 100) { - break - } - } - console.log(`Progress: ${numBatchesProcessed} / ${totalMessageBatches}`) - } - - if (numBatchesProcessed === totalMessageBatches) { - console.log('All message processing proofs have been submitted.') - } - - // ------------------------------------------------------------------------ - // subsidy calculation proofs - if (Object.keys(data.subsidyProofs).length !== 0) { - let rbi = Number(await subsidyContract.rbi()) - let cbi = Number(await subsidyContract.cbi()) - let numLeaves = numSignUps + 1 - let num1DBatches = Math.ceil(numLeaves/subsidyBatchSize) - let subsidyBatchNum = rbi * num1DBatches + cbi - let totalBatchNum = num1DBatches * (num1DBatches + 1)/2 - console.log(`number of subsidy batch processed: ${subsidyBatchNum}, numleaf=${numLeaves}`) - - for (let i = subsidyBatchNum; i < totalBatchNum; i++) { - - if (i == 0) { - await subsidyContract.updateSbCommitment(mpContract.address) - } - const { proof, circuitInputs, publicInputs } = data.subsidyProofs[i] - - const subsidyCommitmentOnChain = await subsidyContract.subsidyCommitment() - if (subsidyCommitmentOnChain.toString() !== circuitInputs.currentSubsidyCommitment) { - console.error(`Error: subsidycommitment mismatch`) - return 1 - } - const packedValsOnChain = BigInt( - await subsidyContract.genSubsidyPackedVals(numSignUps) - ) - if (circuitInputs.packedVals !== packedValsOnChain.toString()) { - console.error('Error: subsidy packedVals mismatch.') - return 1 - } - const currentSbCommitmentOnChain = await subsidyContract.sbCommitment() - if (currentSbCommitmentOnChain.toString() !== circuitInputs.sbCommitment) { - console.error('Error: currentSbCommitment mismatch.') - return 1 - } - const publicInputHashOnChain = await subsidyContract.genSubsidyPublicInputHash( - numSignUps, - circuitInputs.newSubsidyCommitment, - ) - - if (publicInputHashOnChain.toString() !== publicInputs[0]) { - console.error('Error: public input mismatch.') - return 1 - } - - const txErr = 'Error: updateSubsidy() failed...' - const formattedProof = formatProofForVerifierContract(proof) - let tx - try { - tx = await subsidyContract.updateSubsidy( - pollContract.address, - mpContract.address, - circuitInputs.newSubsidyCommitment, - formattedProof, - ) - } catch (e) { - console.error(txErr) - console.error(e) - } - - const receipt = await tx.wait() - - if (receipt.status !== 1) { - console.error(txErr) - return 1 - } - - console.log(`Progress: ${subsidyBatchNum + 1} / ${totalBatchNum}`) - console.log(`Transaction hash: ${tx.hash}`) - - // Wait for the node to catch up - let nrbi = Number(await subsidyContract.rbi()) - let ncbi = Number(await subsidyContract.cbi()) - let backOff = 1000 - let numAttempts = 0 - while (nrbi === rbi && ncbi === cbi) { - await delay(backOff) - backOff *= 1.2 - numAttempts ++ - if (numAttempts >= 100) { - break - } - } - - rbi = nrbi - cbi = ncbi - subsidyBatchNum = rbi * num1DBatches + cbi - } - - if (subsidyBatchNum === totalBatchNum) { - console.log('All subsidy calculation proofs have been submitted.') - console.log() - console.log('OK') - } - } - - // ------------------------------------------------------------------------ - // Vote tallying proofs - const totalTallyBatches = numSignUps < tallyBatchSize ? - 1 - : - Math.floor(numSignUps / tallyBatchSize) + 1 - - let tallyBatchNum = Number(await tallyContract.tallyBatchNum()) - - console.log() - if (tallyBatchNum < totalTallyBatches) { - console.log('Submitting proofs of vote tallying...') - } - - for (let i = tallyBatchNum; i < totalTallyBatches; i ++) { - - if (i == 0) { - await tallyContract.updateSbCommitment(mpContract.address) - } - - const batchStartIndex = i * tallyBatchSize - - const txErr = 'Error: tallyVotes() failed' - const { proof, circuitInputs, publicInputs } = data.tallyProofs[i] - - const currentTallyCommitmentOnChain = await tallyContract.tallyCommitment() - if (currentTallyCommitmentOnChain.toString() !== circuitInputs.currentTallyCommitment) { - console.error('Error: currentTallyCommitment mismatch.') - return 1 - } - - const packedValsOnChain = BigInt( - await tallyContract.genTallyVotesPackedVals( - numSignUps, - batchStartIndex, - tallyBatchSize, - ) - ) - if (circuitInputs.packedVals !== packedValsOnChain.toString()) { - console.error('Error: packedVals mismatch.') - return 1 - } - - const currentSbCommitmentOnChain = await mpContract.sbCommitment() - if (currentSbCommitmentOnChain.toString() !== circuitInputs.sbCommitment) { - console.error('Error: currentSbCommitment mismatch.') - return 1 - } - - const publicInputHashOnChain = await tallyContract.genTallyVotesPublicInputHash( - numSignUps, - batchStartIndex, - tallyBatchSize, - circuitInputs.newTallyCommitment, - ) - if (publicInputHashOnChain.toString() !== publicInputs[0]) { - console.error(`Error: public input mismatch. tallyBatchNum=${i}, onchain=${publicInputHashOnChain.toString()}, offchain=${publicInputs[0]}`) - return 1 - } - - const formattedProof = formatProofForVerifierContract(proof) - let tx - try { - tx = await tallyContract.tallyVotes( - pollContract.address, - mpContract.address, - '0x' + BigInt(circuitInputs.newTallyCommitment).toString(16), - formattedProof, - ) - } catch (e) { - console.error(txErr) - console.error(e) - } - - const receipt = await tx.wait() - - if (receipt.status !== 1) { - console.error(txErr) - return 1 - } - - console.log(`Progress: ${tallyBatchNum + 1} / ${totalTallyBatches}`) - console.log(`Transaction hash: ${tx.hash}`) - - // Wait for the node to catch up - tallyBatchNum = Number(await tallyContract.tallyBatchNum()) - let backOff = 1000 - let numAttempts = 0 - while (tallyBatchNum !== i + 1) { - await delay(backOff) - backOff *= 1.2 - numAttempts ++ - if (numAttempts >= 100) { - break - } - } - } - - if (tallyBatchNum === totalTallyBatches) { - console.log('All vote tallying proofs have been submitted.') - console.log() - console.log('OK') - } - - - return 0 -} - -export { - proveOnChain, - configureSubparser, -} + maciAddress, + maciContractAbi, + signer + ); + + const pollAddr = await maciContract.polls(pollId); + if (!(await contractExists(signer.provider, pollAddr))) { + console.error( + 'Error: there is no Poll contract with this poll ID linked to the specified MACI contract.' + ); + return 1; + } + + const pollContract = new ethers.Contract(pollAddr, pollContractAbi, signer); + + const mpContract = new ethers.Contract(mpAddress, mpContractAbi, signer); + + const tallyContract = new ethers.Contract( + tallyAddress, + tallyContractAbi, + signer + ); + + const subsidyContract = new ethers.Contract( + subsidyAddress, + subsidyContractAbi, + signer + ); + + const messageAqContract = new ethers.Contract( + (await pollContract.extContracts()).messageAq, + messageAqContractAbi, + signer + ); + + const vkRegistryContract = new ethers.Contract( + (await pollContract.extContracts()).vkRegistry, + vkRegistryContractAbi, + signer + ); + + const verifierContractAddress = await mpContract.verifier(); + const verifierContract = new ethers.Contract( + verifierContractAddress, + verifierContractAbi, + signer + ); + + let data = { + processProofs: {}, + tallyProofs: {}, + subsidyProofs: {}, + }; + + let numProcessProofs = 0; + + if (typeof args.proof_file === 'object' && args.proof_file !== null) { + // Argument is a javascript object + data = args.proof_file; + } else { + // Read the proof directory + const filenames = fs.readdirSync(args.proof_dir); + for (let i = 0; i < filenames.length; i++) { + const filename = filenames[i]; + const filepath = path.join(args.proof_dir, filename); + let match = filename.match(/process_(\d+)/); + if (match != null) { + data.processProofs[Number(match[1])] = JSON.parse( + fs.readFileSync(filepath).toString() + ); + numProcessProofs++; + continue; + } + + match = filename.match(/tally_(\d+)/); + if (match != null) { + data.tallyProofs[Number(match[1])] = JSON.parse( + fs.readFileSync(filepath).toString() + ); + continue; + } + match = filename.match(/subsidy_(\d+)/); + if (match != null) { + data.subsidyProofs[Number(match[1])] = JSON.parse( + fs.readFileSync(filepath).toString() + ); + continue; + } + } + } + + const numSignUpsAndMessages = + await pollContract.numSignUpsAndMessagesAndDeactivatedKeys(); + const numSignUps = Number(numSignUpsAndMessages[0]); + const numMessages = Number(numSignUpsAndMessages[1]); + const batchSizes = await pollContract.batchSizes(); + const messageBatchSize = Number(batchSizes.messageBatchSize); + const tallyBatchSize = Number(batchSizes.tallyBatchSize); + const subsidyBatchSize = Number(batchSizes.subsidyBatchSize); + let totalMessageBatches = + numMessages <= messageBatchSize + ? 1 + : Math.floor(numMessages / messageBatchSize); + + if (numMessages > messageBatchSize && numMessages % messageBatchSize > 0) { + totalMessageBatches++; + } + + if (numProcessProofs !== totalMessageBatches) { + console.error( + `Error: ${args.proof_file} does not have the correct ` + + `number of message processing proofs ` + + `(expected ${totalMessageBatches}, got ${numProcessProofs}.` + ); + } + + const treeDepths = await pollContract.treeDepths(); + + let numBatchesProcessed = Number(await mpContract.numBatchesProcessed()); + const messageRootOnChain = await messageAqContract.getMainRoot( + Number(treeDepths.messageTreeDepth) + ); + + const stateTreeDepth = Number(await maciContract.stateTreeDepth()); + const onChainProcessVk = await vkRegistryContract.getProcessVk( + stateTreeDepth, + treeDepths.messageTreeDepth, + treeDepths.voteOptionTreeDepth, + messageBatchSize + ); + + const dd = await pollContract.getDeployTimeAndDuration(); + const pollEndTimestampOnChain = BigInt(dd[0]) + BigInt(dd[1]); + + if (numBatchesProcessed < totalMessageBatches) { + console.log('Submitting proofs of message processing...'); + } + + for (let i = numBatchesProcessed; i < totalMessageBatches; i++) { + //const currentMessageBatchIndex = Number(await pptContract.currentMessageBatchIndex()) + let currentMessageBatchIndex; + if (numBatchesProcessed === 0) { + const r = numMessages % messageBatchSize; + if (r === 0) { + currentMessageBatchIndex = + Math.floor(numMessages / messageBatchSize) * messageBatchSize; + } else { + currentMessageBatchIndex = numMessages; + } + + if (currentMessageBatchIndex > 0) { + if (r === 0) { + currentMessageBatchIndex -= messageBatchSize; + } else { + currentMessageBatchIndex -= r; + } + } + } else { + currentMessageBatchIndex = + (totalMessageBatches - numBatchesProcessed) * messageBatchSize; + } + + if (numBatchesProcessed > 0 && currentMessageBatchIndex > 0) { + currentMessageBatchIndex -= messageBatchSize; + } + + const txErr = 'Error: processMessages() failed'; + const { proof, circuitInputs, publicInputs } = data.processProofs[i]; + + // Perform checks + if (circuitInputs.pollEndTimestamp !== pollEndTimestampOnChain.toString()) { + console.error('Error: pollEndTimestamp mismatch.'); + return 1; + } + + if ( + BigInt(circuitInputs.msgRoot).toString() !== messageRootOnChain.toString() + ) { + console.error('Error: message root mismatch.'); + return 1; + } + + let currentSbCommitmentOnChain; + + if (numBatchesProcessed === 0) { + currentSbCommitmentOnChain = BigInt( + await pollContract.currentSbCommitment() + ); + } else { + currentSbCommitmentOnChain = BigInt(await mpContract.sbCommitment()); + } + + if ( + currentSbCommitmentOnChain.toString() !== + circuitInputs.currentSbCommitment + ) { + console.error('Error: currentSbCommitment mismatch.'); + return 1; + } + + const coordPubKeyHashOnChain = BigInt( + await pollContract.coordinatorPubKeyHash() + ); + if ( + hashLeftRight( + BigInt(circuitInputs.coordPubKey[0]), + BigInt(circuitInputs.coordPubKey[1]) + ).toString() !== coordPubKeyHashOnChain.toString() + ) { + console.error('Error: coordPubKey mismatch.'); + return 1; + } + + const packedValsOnChain = BigInt( + await mpContract.genProcessMessagesPackedVals( + pollContract.address, + currentMessageBatchIndex, + numSignUps + ) + ).toString(); + + if (circuitInputs.packedVals !== packedValsOnChain) { + console.error('Error: packedVals mismatch.'); + return 1; + } + + const formattedProof = formatProofForVerifierContract(proof); + + const publicInputHashOnChain = BigInt( + await mpContract.genProcessMessagesPublicInputHash( + pollContract.address, + currentMessageBatchIndex, + messageRootOnChain.toString(), + numSignUps, + circuitInputs.currentSbCommitment, + circuitInputs.newSbCommitment + ) + ); + + if (publicInputHashOnChain.toString() !== publicInputs[0].toString()) { + console.error('Public input mismatch.'); + return 1; + } + + const isValidOnChain = await verifierContract.verify( + formattedProof, + onChainProcessVk, + publicInputHashOnChain.toString() + ); + + if (!isValidOnChain) { + console.error('Error: the verifier contract found the proof invalid.'); + return 1; + } + + let tx; + try { + tx = await mpContract.processMessages( + pollContract.address, + '0x' + BigInt(circuitInputs.newSbCommitment).toString(16), + formattedProof + ); + } catch (e) { + console.error(txErr); + console.error(e); + } + + const receipt = await tx.wait(); + + if (receipt.status !== 1) { + console.error(txErr); + return 1; + } + + console.log(`Transaction hash: ${tx.hash}`); + + // Wait for the node to catch up + numBatchesProcessed = Number(await mpContract.numBatchesProcessed()); + let backOff = 1000; + let numAttempts = 0; + while (numBatchesProcessed !== i + 1) { + await delay(backOff); + backOff *= 1.2; + numAttempts++; + if (numAttempts >= 100) { + break; + } + } + console.log(`Progress: ${numBatchesProcessed} / ${totalMessageBatches}`); + } + + if (numBatchesProcessed === totalMessageBatches) { + console.log('All message processing proofs have been submitted.'); + } + + // ------------------------------------------------------------------------ + // subsidy calculation proofs + if (Object.keys(data.subsidyProofs).length !== 0) { + let rbi = Number(await subsidyContract.rbi()); + let cbi = Number(await subsidyContract.cbi()); + let numLeaves = numSignUps + 1; + let num1DBatches = Math.ceil(numLeaves / subsidyBatchSize); + let subsidyBatchNum = rbi * num1DBatches + cbi; + let totalBatchNum = (num1DBatches * (num1DBatches + 1)) / 2; + console.log( + `number of subsidy batch processed: ${subsidyBatchNum}, numleaf=${numLeaves}` + ); + + for (let i = subsidyBatchNum; i < totalBatchNum; i++) { + if (i == 0) { + await subsidyContract.updateSbCommitment(mpContract.address); + } + const { proof, circuitInputs, publicInputs } = data.subsidyProofs[i]; + + const subsidyCommitmentOnChain = + await subsidyContract.subsidyCommitment(); + if ( + subsidyCommitmentOnChain.toString() !== + circuitInputs.currentSubsidyCommitment + ) { + console.error(`Error: subsidycommitment mismatch`); + return 1; + } + const packedValsOnChain = BigInt( + await subsidyContract.genSubsidyPackedVals(numSignUps) + ); + if (circuitInputs.packedVals !== packedValsOnChain.toString()) { + console.error('Error: subsidy packedVals mismatch.'); + return 1; + } + const currentSbCommitmentOnChain = await subsidyContract.sbCommitment(); + if ( + currentSbCommitmentOnChain.toString() !== circuitInputs.sbCommitment + ) { + console.error('Error: currentSbCommitment mismatch.'); + return 1; + } + const publicInputHashOnChain = + await subsidyContract.genSubsidyPublicInputHash( + numSignUps, + circuitInputs.newSubsidyCommitment + ); + + if (publicInputHashOnChain.toString() !== publicInputs[0]) { + console.error('Error: public input mismatch.'); + return 1; + } + + const txErr = 'Error: updateSubsidy() failed...'; + const formattedProof = formatProofForVerifierContract(proof); + let tx; + try { + tx = await subsidyContract.updateSubsidy( + pollContract.address, + mpContract.address, + circuitInputs.newSubsidyCommitment, + formattedProof + ); + } catch (e) { + console.error(txErr); + console.error(e); + } + + const receipt = await tx.wait(); + + if (receipt.status !== 1) { + console.error(txErr); + return 1; + } + + console.log(`Progress: ${subsidyBatchNum + 1} / ${totalBatchNum}`); + console.log(`Transaction hash: ${tx.hash}`); + + // Wait for the node to catch up + let nrbi = Number(await subsidyContract.rbi()); + let ncbi = Number(await subsidyContract.cbi()); + let backOff = 1000; + let numAttempts = 0; + while (nrbi === rbi && ncbi === cbi) { + await delay(backOff); + backOff *= 1.2; + numAttempts++; + if (numAttempts >= 100) { + break; + } + } + + rbi = nrbi; + cbi = ncbi; + subsidyBatchNum = rbi * num1DBatches + cbi; + } + + if (subsidyBatchNum === totalBatchNum) { + console.log('All subsidy calculation proofs have been submitted.'); + console.log(); + console.log('OK'); + } + } + + // ------------------------------------------------------------------------ + // Vote tallying proofs + const totalTallyBatches = + numSignUps < tallyBatchSize + ? 1 + : Math.floor(numSignUps / tallyBatchSize) + 1; + + let tallyBatchNum = Number(await tallyContract.tallyBatchNum()); + + console.log(); + if (tallyBatchNum < totalTallyBatches) { + console.log('Submitting proofs of vote tallying...'); + } + + for (let i = tallyBatchNum; i < totalTallyBatches; i++) { + if (i == 0) { + await tallyContract.updateSbCommitment(mpContract.address); + } + + const batchStartIndex = i * tallyBatchSize; + + const txErr = 'Error: tallyVotes() failed'; + const { proof, circuitInputs, publicInputs } = data.tallyProofs[i]; + + const currentTallyCommitmentOnChain = await tallyContract.tallyCommitment(); + if ( + currentTallyCommitmentOnChain.toString() !== + circuitInputs.currentTallyCommitment + ) { + console.error('Error: currentTallyCommitment mismatch.'); + return 1; + } + + const packedValsOnChain = BigInt( + await tallyContract.genTallyVotesPackedVals( + numSignUps, + batchStartIndex, + tallyBatchSize + ) + ); + if (circuitInputs.packedVals !== packedValsOnChain.toString()) { + console.error('Error: packedVals mismatch.'); + return 1; + } + + const currentSbCommitmentOnChain = await mpContract.sbCommitment(); + if (currentSbCommitmentOnChain.toString() !== circuitInputs.sbCommitment) { + console.error('Error: currentSbCommitment mismatch.'); + return 1; + } + + const publicInputHashOnChain = + await tallyContract.genTallyVotesPublicInputHash( + numSignUps, + batchStartIndex, + tallyBatchSize, + circuitInputs.newTallyCommitment + ); + if (publicInputHashOnChain.toString() !== publicInputs[0]) { + console.error( + `Error: public input mismatch. tallyBatchNum=${i}, onchain=${publicInputHashOnChain.toString()}, offchain=${ + publicInputs[0] + }` + ); + return 1; + } + + const formattedProof = formatProofForVerifierContract(proof); + let tx; + try { + tx = await tallyContract.tallyVotes( + pollContract.address, + mpContract.address, + '0x' + BigInt(circuitInputs.newTallyCommitment).toString(16), + formattedProof + ); + } catch (e) { + console.error(txErr); + console.error(e); + } + + const receipt = await tx.wait(); + + if (receipt.status !== 1) { + console.error(txErr); + return 1; + } + + console.log(`Progress: ${tallyBatchNum + 1} / ${totalTallyBatches}`); + console.log(`Transaction hash: ${tx.hash}`); + + // Wait for the node to catch up + tallyBatchNum = Number(await tallyContract.tallyBatchNum()); + let backOff = 1000; + let numAttempts = 0; + while (tallyBatchNum !== i + 1) { + await delay(backOff); + backOff *= 1.2; + numAttempts++; + if (numAttempts >= 100) { + break; + } + } + } + + if (tallyBatchNum === totalTallyBatches) { + console.log('All vote tallying proofs have been submitted.'); + console.log(); + console.log('OK'); + } + + return 0; +}; + +export { proveOnChain, configureSubparser }; diff --git a/cli/ts/publish.ts b/cli/ts/publish.ts index 0b116d939a..3942f60aae 100644 --- a/cli/ts/publish.ts +++ b/cli/ts/publish.ts @@ -295,7 +295,7 @@ const publish = async (args: any) => { const encKeypair = new Keypair() - const command:PCommand = new PCommand( + const command: PCommand = new PCommand( stateIndex, userMaciPubKey, voteOptionIndex, @@ -313,7 +313,7 @@ const publish = async (args: any) => { ) ) - let tx + let tx = null; try { tx = await pollContractEthers.publishMessage( message.asContractParam(), diff --git a/cli/ts/setVerifyingKeys.ts b/cli/ts/setVerifyingKeys.ts index c8e635fe97..0a974c2de2 100644 --- a/cli/ts/setVerifyingKeys.ts +++ b/cli/ts/setVerifyingKeys.ts @@ -104,6 +104,15 @@ const configureSubparser = (subparsers: any) => { help: 'The .zkey file for the subsidy circuit. ' } ) + + createParser.addArgument( + ['-zpd', '--process-deactivation-zkey'], + { + required: true, + type: 'string', + help: 'The path to the ProcessDeactivationMessages .zkey file', + } + ) } const setVerifyingKeys = async (args: any) => { @@ -113,7 +122,6 @@ const setVerifyingKeys = async (args: any) => { return 1 } const vkRegistryAddress = args.vk_registry ? args.vk_registry: contractAddrs["VkRegistry"] - // State tree depth const stateTreeDepth = args.state_tree_depth const intStateTreeDepth = args.int_state_tree_depth const msgTreeDepth = args.msg_tree_depth @@ -122,6 +130,8 @@ const setVerifyingKeys = async (args: any) => { const pmZkeyFile = path.resolve(args.process_messages_zkey) const tvZkeyFile = path.resolve(args.tally_votes_zkey) + const pdmZkeyFile = path.resolve(args.process_deactivation_zkey) + if (!fs.existsSync(pmZkeyFile)) { console.error(`Error: ${pmZkeyFile} does not exist.`) return 1 @@ -130,8 +140,13 @@ const setVerifyingKeys = async (args: any) => { console.error(`Error: ${tvZkeyFile} does not exist.`) return 1 } + if (!fs.existsSync(pdmZkeyFile)) { + console.error(`Error: ${pdmZkeyFile} does not exist.`) + return 1 + } const processVk: VerifyingKey = VerifyingKey.fromObj(extractVk(pmZkeyFile)) + const processDeactivationVk: VerifyingKey = VerifyingKey.fromObj(extractVk(pdmZkeyFile)) const tallyVk: VerifyingKey = VerifyingKey.fromObj(extractVk(tvZkeyFile)) @@ -283,6 +298,7 @@ const setVerifyingKeys = async (args: any) => { voteOptionTreeDepth, 5 ** msgBatchDepth, processVk.asContractParam(), + processDeactivationVk.asContractParam(), tallyVk.asContractParam() ) diff --git a/cli/zkeys.config.yml b/cli/zkeys.config.yml index 68be2d25a0..a037b2a4d2 100644 --- a/cli/zkeys.config.yml +++ b/cli/zkeys.config.yml @@ -12,7 +12,7 @@ circuits: type: "test" pubInputs: ["inputHash"] -#- template: "../circuits/circom/processMessages.circom" +# #- template: "../circuits/circom/processMessages.circom" #component: "ProcessMessages" #params: [10, 8, 2, 6] #type: "prod" @@ -24,7 +24,7 @@ circuits: type: "test" pubInputs: ["inputHash"] -#- template: "../circuits/circom/tallyVotes.circom" +# #- template: "../circuits/circom/tallyVotes.circom" #component: "TallyVotes" #params: [10, 3, 6] #type: "prod" @@ -37,86 +37,91 @@ circuits: type: "test" pubInputs: ["inputHash"] +- template: "../circuits/circom/processDeactivationMessages.circom" + component: "ProcessDeactivationMessages" + params: [5, 10] # (deactivation)msgQueueSize, stateTreeDepth + type: "test" + pubInputs: ["inputHash"] ptauFiles: 1: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_01.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_01.ptau" name: "powersOfTau28_hez_final_01.ptau" 2: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_02.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_02.ptau" name: "powersOfTau28_hez_final_02.ptau" 3: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_03.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_03.ptau" name: "powersOfTau28_hez_final_03.ptau" 4: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_04.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_04.ptau" name: "powersOfTau28_hez_final_04.ptau" 5: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_05.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_05.ptau" name: "powersOfTau28_hez_final_05.ptau" 6: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_06.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_06.ptau" name: "powersOfTau28_hez_final_06.ptau" 7: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_07.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_07.ptau" name: "powersOfTau28_hez_final_7.ptau" 8: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_08.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_08.ptau" name: "powersOfTau28_hez_final_8.ptau" 9: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_09.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_09.ptau" name: "powersOfTau28_hez_final_9.ptau" 10: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_10.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_10.ptau" name: "powersOfTau28_hez_final_10.ptau" 11: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_11.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_11.ptau" name: "powersOfTau28_hez_final_11.ptau" 12: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_12.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_12.ptau" name: "powersOfTau28_hez_final_12.ptau" 13: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_13.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_13.ptau" name: "powersOfTau28_hez_final_13.ptau" 14: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_14.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_14.ptau" name: "powersOfTau28_hez_final_14.ptau" 15: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_15.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_15.ptau" name: "powersOfTau28_hez_final_15.ptau" 16: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_16.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_16.ptau" name: "powersOfTau28_hez_final_16.ptau" 17: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_17.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_17.ptau" name: "powersOfTau28_hez_final_17.ptau" 18: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_18.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_18.ptau" name: "powersOfTau28_hez_final_18.ptau" 19: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_19.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_19.ptau" name: "powersOfTau28_hez_final_19.ptau" 20: - url: "https://hermezptau.blob.core.windows.net/ptau/powersOfTau28_hez_final_20.ptau" + url: "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_20.ptau" name: "powersOfTau28_hez_final_20.ptau" 21: diff --git a/contracts/contracts/DomainObjs.sol b/contracts/contracts/DomainObjs.sol index ad1e2c99cb..52b0bca1b8 100644 --- a/contracts/contracts/DomainObjs.sol +++ b/contracts/contracts/DomainObjs.sol @@ -14,7 +14,7 @@ contract IMessage { uint8 constant MESSAGE_DATA_LENGTH = 10; struct Message { - uint256 msgType; // 1: vote message (size 10), 2: topup message (size 2) + uint256 msgType; // 1: vote message (size 10), 2: topup message (size 2), 3: deactivate key, 4: generate new key uint256[MESSAGE_DATA_LENGTH] data; // data length is padded to size 10 } } diff --git a/contracts/contracts/IMACI.sol b/contracts/contracts/IMACI.sol index 16032a4c3a..ce7031ef5f 100644 --- a/contracts/contracts/IMACI.sol +++ b/contracts/contracts/IMACI.sol @@ -11,4 +11,6 @@ interface IMACI { function mergeStateAq(uint256 _pollId) external returns (uint256); function numSignUps() external view returns (uint256); function stateAq() external view returns (AccQueue); + function signUpDeadline() external view returns (uint40); + function deactivationPeriod() external view returns (uint40); } diff --git a/contracts/contracts/MACI.sol b/contracts/contracts/MACI.sol index 880d4efe68..07afcbc2a8 100644 --- a/contracts/contracts/MACI.sol +++ b/contracts/contracts/MACI.sol @@ -24,7 +24,7 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { // so that there can be as many users as possible. i.e. 5 ** 10 = 9765625 uint8 public constant override stateTreeDepth = 10; - // IMPORTANT: remember to change the ballot tree depth + // IMPORTANT: remember to change the ballot tree depth // in contracts/ts/genEmptyBallotRootsContract.ts file // if we change the state tree depth! @@ -47,6 +47,12 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { // The number of signups uint256 public override numSignUps; + // The deadline for signing up + uint40 public immutable signUpDeadline; + + // The number of seconds for key deactivation + uint40 public immutable deactivationPeriod; + // A mapping of block timestamps to the number of state leaves mapping(uint256 => uint256) public numStateLeaves; @@ -99,16 +105,16 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { event MergeStateAq(uint256 _pollId); /* - * Ensure certain functions only run after the contract has been initialized - */ + * Ensure certain functions only run after the contract has been initialized + */ modifier afterInit() { require(isInitialised, "MACI: not initialised"); _; } /* - * Only allow a Poll contract to call the modified function. - */ + * Only allow a Poll contract to call the modified function. + */ modifier onlyPoll(uint256 _pollId) { require( msg.sender == address(polls[_pollId]), @@ -120,7 +126,9 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { constructor( PollFactory _pollFactory, SignUpGatekeeper _signUpGatekeeper, - InitialVoiceCreditProxy _initialVoiceCreditProxy + InitialVoiceCreditProxy _initialVoiceCreditProxy, + uint40 _signUpDeadline, + uint40 _deactivationPeriod ) { // Deploy the state AccQueue stateAq = new AccQueueQuinaryBlankSl(STATE_TREE_SUBDEPTH); @@ -131,6 +139,8 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { initialVoiceCreditProxy = _initialVoiceCreditProxy; signUpTimestamp = block.timestamp; + signUpDeadline = _signUpDeadline; + deactivationPeriod = _deactivationPeriod; // Verify linked poseidon libraries require( @@ -143,7 +153,7 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { * Initialise the various factory/helper contracts. This should only be run * once and it must be run before deploying the first Poll. * @param _vkRegistry The VkRegistry contract - * @param _topupCredit The topupCredit contract + * @param _topupCredit The topupCredit contract */ function init( VkRegistry _vkRegistry, @@ -191,6 +201,11 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { bytes memory _signUpGatekeeperData, bytes memory _initialVoiceCreditProxyData ) public afterInit { + require( + block.timestamp < signUpDeadline, + "MACI: the sign-up period has passed" + ); + // The circuits only support up to (5 ** 10 - 1) signups require( numSignUps < uint256(STATE_TREE_ARITY) ** uint256(stateTreeDepth), @@ -229,10 +244,10 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { } /* - * Deploy a new Poll contract. - * @param _duration How long should the Poll last for - * @param _treeDepths The depth of the Merkle trees - */ + * Deploy a new Poll contract. + * @param _duration How long should the Poll last for + * @param _treeDepths The depth of the Merkle trees + */ function deployPoll( uint256 _duration, MaxValues memory _maxValues, @@ -256,11 +271,11 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { // The message batch size and the tally batch size BatchSizes memory batchSizes = BatchSizes( - uint24(MESSAGE_TREE_ARITY)**_treeDepths.messageTreeSubDepth, - uint24(STATE_TREE_ARITY)**_treeDepths.intStateTreeDepth, - uint24(STATE_TREE_ARITY)**_treeDepths.intStateTreeDepth + uint24(MESSAGE_TREE_ARITY) ** _treeDepths.messageTreeSubDepth, + uint24(STATE_TREE_ARITY) ** _treeDepths.intStateTreeDepth, + uint24(STATE_TREE_ARITY) ** _treeDepths.intStateTreeDepth ); - + Poll p = pollFactory.deploy( _duration, _maxValues, @@ -278,17 +293,15 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { emit DeployPoll(pollId, address(p), _coordinatorPubKey); } - /* + /* /* Allow Poll contracts to merge the state subroots /* @param _numSrQueueOps Number of operations /* @param _pollId The active Poll ID */ - function mergeStateAqSubRoots(uint256 _numSrQueueOps, uint256 _pollId) - public - override - onlyPoll(_pollId) - afterInit - { + function mergeStateAqSubRoots( + uint256 _numSrQueueOps, + uint256 _pollId + ) public override afterInit { // TODO: During milestone 3 - onlyPoll fails here because the caller is MessageProcessor and not Poll stateAq.mergeSubRoots(_numSrQueueOps); emit MergeStateAqSubRoots(_pollId, _numSrQueueOps); @@ -299,13 +312,9 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { /* @param _pollId The active Poll ID /* @returns uint256 The calculated Merkle root */ - function mergeStateAq(uint256 _pollId) - public - override - onlyPoll(_pollId) - afterInit - returns (uint256) - { + function mergeStateAq( + uint256 _pollId + ) public override afterInit returns (uint256) { // TODO: During milestone 3 - onlyPoll fails here because the caller is MessageProcessor and not Poll uint256 root = stateAq.merge(stateTreeDepth); emit MergeStateAq(_pollId); @@ -314,18 +323,18 @@ contract MACI is IMACI, DomainObjs, Params, SnarkCommon, Ownable { } /* - * Return the main root of the StateAq contract - * @returns uint256 The Merkle root - */ + * Return the main root of the StateAq contract + * @returns uint256 The Merkle root + */ function getStateAqRoot() public view override returns (uint256) { return stateAq.getMainRoot(stateTreeDepth); } /* - * Get the Poll details - * @param _pollId The identifier of the Poll to retrieve - * @returns Poll The Poll data - */ + * Get the Poll details + * @param _pollId The identifier of the Poll to retrieve + * @returns Poll The Poll data + */ function getPoll(uint256 _pollId) public view returns (Poll) { require(_pollId < nextPollId, "MACI: poll with _pollId does not exist"); return polls[_pollId]; diff --git a/contracts/contracts/MessageProcessor.sol b/contracts/contracts/MessageProcessor.sol index 5580a25a7e..be0da4565f 100644 --- a/contracts/contracts/MessageProcessor.sol +++ b/contracts/contracts/MessageProcessor.sol @@ -17,13 +17,13 @@ import {VkRegistry} from "./VkRegistry.sol"; * after it finishes processing, the sbCommitment will be used for Tally and Subsidy contracts */ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { - error NO_MORE_MESSAGES(); error STATE_AQ_NOT_MERGED(); error MESSAGE_AQ_NOT_MERGED(); error INVALID_PROCESS_MESSAGE_PROOF(); error VK_NOT_SET(); + uint8 public constant DEACT_TREE_DEPTH = 10; // Whether there are unprocessed messages left bool public processingComplete; @@ -33,7 +33,7 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { // processMessages(), this action relates to messages // currentMessageBatchIndex to currentMessageBatchIndex + messageBatchSize. uint256 public currentMessageBatchIndex; - // The commitment to the state and ballot roots + // The commitment to the state and ballot roots uint256 public sbCommitment; Verifier public verifier; @@ -42,7 +42,6 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { verifier = _verifier; } - /* * Update the Poll's currentSbCommitment if the proof is valid. * @param _poll The poll to update @@ -71,7 +70,7 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { (uint256 messageBatchSize, , ) = _poll.batchSizes(); AccQueue messageAq; - (, , messageAq, ) = _poll.extContracts(); + (, , messageAq, , ) = _poll.extContracts(); // Require that the message queue has been merged uint256 messageRoot = messageAq.getMainRoot(messageTreeDepth); @@ -84,7 +83,8 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { if (numBatchesProcessed == 0) { uint256 currentSbCommitment = _poll.currentSbCommitment(); sbCommitment = currentSbCommitment; - (, uint256 numMessages) = _poll.numSignUpsAndMessages(); + (, uint256 numMessages, ) = _poll + .numSignUpsAndMessagesAndDeactivatedKeys(); uint256 r = numMessages % messageBatchSize; if (r == 0) { @@ -117,7 +117,8 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { } { - (, uint256 numMessages) = _poll.numSignUpsAndMessages(); + (, uint256 numMessages, ) = _poll + .numSignUpsAndMessagesAndDeactivatedKeys(); // Decrease the message batch start index to ensure that each // message batch is processed in order if (currentMessageBatchIndex > 0) { @@ -132,6 +133,94 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { } } + /** + * @notice Completes the deactivation of all MACI public keys. + * @param _stateNumSrQueueOps The number of subroot queue operations to merge for the MACI state tree + * @param poll Poll contract address + * @param _pollId The pollId of the Poll contract + */ + function mergeForDeactivation( + uint256 _stateNumSrQueueOps, + Poll poll, + uint256 _pollId + ) external onlyOwner { + ( + , + IMACI maci, + , + AccQueue deactivatedKeysAq, + + ) = poll.extContracts(); + + (, uint8 messageTreeSubDepth, uint8 messageTreeDepth, ) = poll + .treeDepths(); + + { + (uint256 deployTime, ) = poll.getDeployTimeAndDuration(); + + uint256 secondsPassed = block.timestamp - deployTime; + require( + block.timestamp - deployTime > maci.deactivationPeriod(), + "Deactivation period has not passed" + ); + } + + poll.mergeMaciStateAqSubRoots(_stateNumSrQueueOps, _pollId); + poll.mergeMaciStateAq(_stateNumSrQueueOps); + + deactivatedKeysAq.mergeSubRoots(_stateNumSrQueueOps); + deactivatedKeysAq.merge(DEACT_TREE_DEPTH); + } + + /** + * @notice Completes the deactivation of all MACI public keys. + * @param _proof The Zk proof + * @param poll Poll contract address + */ + function completeDeactivation( + uint256[8] memory _proof, + Poll poll + ) external onlyOwner { + ( + VkRegistry vkRegistry, + IMACI maci, + , + AccQueue deactivatedKeysAq, + + ) = poll.extContracts(); + + (, uint8 messageTreeSubDepth, uint8 messageTreeDepth, ) = poll + .treeDepths(); + + { + (uint256 deployTime, ) = poll.getDeployTimeAndDuration(); + + uint256 secondsPassed = block.timestamp - deployTime; + require( + block.timestamp - deployTime > maci.deactivationPeriod(), + "Deactivation period has not passed" + ); + } + + VerifyingKey memory vk = vkRegistry.getProcessDeactivationVk( + maci.stateTreeDepth(), + messageTreeDepth + ); + + (uint256 numSignUps, , ) = poll + .numSignUpsAndMessagesAndDeactivatedKeys(); + + uint256 input = genProcessDeactivationMessagesPublicInputHash( + poll, + deactivatedKeysAq.getMainRoot(DEACT_TREE_DEPTH), + numSignUps, + maci.getStateAqRoot(), + poll.deactivationChainHash() + ); + + require(verifier.verify(_proof, vk, input), "Verification failed"); + } + function verifyProcessProof( Poll _poll, uint256 _currentMessageBatchIndex, @@ -143,8 +232,9 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { (, , uint8 messageTreeDepth, uint8 voteOptionTreeDepth) = _poll .treeDepths(); (uint256 messageBatchSize, , ) = _poll.batchSizes(); - (uint256 numSignUps, ) = _poll.numSignUpsAndMessages(); - (VkRegistry vkRegistry, IMACI maci, , ) = _poll.extContracts(); + (uint256 numSignUps, , ) = _poll + .numSignUpsAndMessagesAndDeactivatedKeys(); + (VkRegistry vkRegistry, IMACI maci, , , ) = _poll.extContracts(); if (address(vkRegistry) == address(0)) { revert VK_NOT_SET(); @@ -171,6 +261,23 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { return verifier.verify(_proof, vk, publicInputHash); } + function genProcessDeactivationMessagesPublicInputHash( + Poll _poll, + uint256 _deactivatedTreeRoot, + uint256 _numSignUps, + uint256 _currentStateRoot, + uint256 _chainHash + ) public view returns (uint256) { + uint256[] memory input = new uint256[](4); + input[0] = _deactivatedTreeRoot; + input[1] = _numSignUps; + input[2] = _currentStateRoot; + input[3] = _chainHash; + uint256 inputHash = sha256Hash(input); + + return inputHash; + } + /* * @notice Returns the SHA256 hash of the packed values (see * genProcessMessagesPackedVals), the hash of the coordinator's public key, @@ -179,12 +286,12 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { * as a single public input and the preimage as private inputs, we reduce * its verification gas cost though the number of constraints will be * higher and proving time will be higher. - * @param _poll: contract address + * @param _poll: contract address * @param _currentMessageBatchIndex: batch index of current message batch * @param _numSignUps: number of users that signup * @param _currentSbCommitment: current sbCommitment * @param _newSbCommitment: new sbCommitment after we update this message batch - * @return returns the SHA256 hash of the packed values + * @return returns the SHA256 hash of the packed values */ function genProcessMessagesPublicInputHash( Poll _poll, @@ -230,7 +337,8 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { uint256 _numSignUps ) public view returns (uint256) { (, uint256 maxVoteOptions) = _poll.maxValues(); - (, uint256 numMessages) = _poll.numSignUpsAndMessages(); + (, uint256 numMessages, ) = _poll + .numSignUpsAndMessagesAndDeactivatedKeys(); (uint24 mbs, , ) = _poll.batchSizes(); uint256 messageBatchSize = uint256(mbs); @@ -239,10 +347,13 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { batchEndIndex = numMessages; } - require(maxVoteOptions < 2**50, "maxVoteOptions too large"); - require(_numSignUps < 2**50, "numSignUps too large"); - require(_currentMessageBatchIndex < 2**50, "currentMessageBatchIndex too large"); - require(batchEndIndex < 2**50, "batchEndIndex too large"); + require(maxVoteOptions < 2 ** 50, "maxVoteOptions too large"); + require(_numSignUps < 2 ** 50, "numSignUps too large"); + require( + _currentMessageBatchIndex < 2 ** 50, + "currentMessageBatchIndex too large" + ); + require(batchEndIndex < 2 ** 50, "batchEndIndex too large"); uint256 result = maxVoteOptions + (_numSignUps << 50) + (_currentMessageBatchIndex << 100) + @@ -268,7 +379,4 @@ contract MessageProcessor is Ownable, SnarkCommon, CommonUtilities, Hasher { currentMessageBatchIndex = _currentMessageBatchIndex; numBatchesProcessed++; } - - - } diff --git a/contracts/contracts/Poll.sol b/contracts/contracts/Poll.sol index 04f9f78815..f463c6e5cd 100644 --- a/contracts/contracts/Poll.sol +++ b/contracts/contracts/Poll.sol @@ -9,8 +9,9 @@ import {AccQueue, AccQueueQuinaryMaci} from "./trees/AccQueue.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {VkRegistry} from "./VkRegistry.sol"; -import {Verifier} from "./crypto/Verifier.sol"; +import {IVerifier} from "./crypto/Verifier.sol"; import {EmptyBallotRoots} from "./trees/EmptyBallotRoots.sol"; import {TopupCredit} from "./TopupCredit.sol"; import {Utilities} from "./utilities/Utility.sol"; @@ -21,6 +22,7 @@ contract PollDeploymentParams { VkRegistry vkRegistry; IMACI maci; AccQueue messageAq; + AccQueue deactivatedKeysAq; TopupCredit topupCredit; } } @@ -29,12 +31,7 @@ contract PollDeploymentParams { * A factory contract which deploys Poll contracts. It allows the MACI contract * size to stay within the limit set by EIP-170. */ -contract PollFactory is - Params, - IPubKey, - Ownable, - PollDeploymentParams -{ +contract PollFactory is Params, IPubKey, Ownable, PollDeploymentParams { /* * Deploy a new Poll contract and AccQueue contract for messages. */ @@ -61,16 +58,20 @@ contract PollFactory is require( _maxValues.maxMessages <= - treeArity**uint256(_treeDepths.messageTreeDepth) && + treeArity ** uint256(_treeDepths.messageTreeDepth) && _maxValues.maxMessages >= _batchSizes.messageBatchSize && _maxValues.maxMessages % _batchSizes.messageBatchSize == 0 && _maxValues.maxVoteOptions <= - treeArity**uint256(_treeDepths.voteOptionTreeDepth) && - _maxValues.maxVoteOptions < (2**50), + treeArity ** uint256(_treeDepths.voteOptionTreeDepth) && + _maxValues.maxVoteOptions < (2 ** 50), "PollFactory: invalid _maxValues" ); - AccQueue messageAq = new AccQueueQuinaryMaci(_treeDepths.messageTreeSubDepth); + AccQueue messageAq = new AccQueueQuinaryMaci( + _treeDepths.messageTreeSubDepth + ); + + AccQueue deactivatedKeysAq = new AccQueueQuinaryMaci(_treeDepths.messageTreeSubDepth); ExtContracts memory extContracts; @@ -78,6 +79,7 @@ contract PollFactory is extContracts.vkRegistry = _vkRegistry; extContracts.maci = _maci; extContracts.messageAq = messageAq; + extContracts.deactivatedKeysAq = deactivatedKeysAq; extContracts.topupCredit = _topupCredit; Poll poll = new Poll( @@ -92,8 +94,9 @@ contract PollFactory is // Make the Poll contract own the messageAq contract, so only it can // run enqueue/merge messageAq.transferOwnership(address(poll)); + deactivatedKeysAq.transferOwnership(address(poll)); - // init messageAq + // init messageAq & deactivatedKeysAq poll.init(); // TODO: should this be _maci.owner() instead? @@ -133,6 +136,8 @@ contract Poll is // The duration of the polling period, in seconds uint256 internal duration; + uint256 public deactivationChainHash; + function getDeployTimeAndDuration() public view returns (uint256, uint256) { return (deployTime, duration); } @@ -150,10 +155,15 @@ contract Poll is uint256 public currentSbCommitment; uint256 internal numMessages; + uint256 internal numDeactivatedKeys; - function numSignUpsAndMessages() public view returns (uint256, uint256) { + function numSignUpsAndMessagesAndDeactivatedKeys() + public + view + returns (uint256, uint256, uint256) + { uint256 numSignUps = extContracts.maci.numSignUps(); - return (numSignUps, numMessages); + return (numSignUps, numMessages, numDeactivatedKeys); } MaxValues public maxValues; @@ -168,6 +178,11 @@ contract Poll is string constant ERROR_MAX_MESSAGES_REACHED = "PollE04"; string constant ERROR_STATE_AQ_ALREADY_MERGED = "PollE05"; string constant ERROR_STATE_AQ_SUBTREES_NEED_MERGE = "PollE06"; + string constant ERROR_INVALID_SENDER = "PollE07"; + string constant ERROR_MAX_DEACTIVATED_KEYS_REACHED = "PollE08"; + string constant ERROR_VERIFICATION_FAILED = "PollE09"; + string constant ERROR_DEACTIVATION_PERIOD_NOT_PASSED = "PollE10"; + string constant ERROR_DEACTIVATION_PERIOD_PASSED = "PollE11"; event PublishMessage(Message _message, PubKey _encPubKey); event TopupMessage(Message _message); @@ -175,6 +190,8 @@ contract Poll is event MergeMaciStateAq(uint256 _stateRoot); event MergeMessageAqSubRoots(uint256 _numSrQueueOps); event MergeMessageAq(uint256 _messageRoot); + event AttemptKeyDeactivation(Message _message, PubKey _encPubKey); + event DeactivateKey(uint256 keyHash, uint256[2] c1, uint256[2] c2); ExtContracts public extContracts; @@ -198,6 +215,7 @@ contract Poll is _coordinatorPubKey.y ); duration = _duration; + deactivationChainHash = 8370432830353022751713833565135785980866757267633941821328460903436894336785; maxValues = _maxValues; batchSizes = _batchSizes; treeDepths = _treeDepths; @@ -236,18 +254,25 @@ contract Poll is uint256[2] memory dat; dat[0] = NOTHING_UP_MY_SLEEVE; dat[1] = 0; - (Message memory _message, PubKey memory _padKey, uint256 placeholderLeaf) = padAndHashMessage(dat, 1); + ( + Message memory _message, + PubKey memory _padKey, + uint256 placeholderLeaf + ) = padAndHashMessage(dat, 1); extContracts.messageAq.enqueue(placeholderLeaf); - - emit PublishMessage(_message, _padKey); + + emit PublishMessage(_message, _padKey); } /* - * Allows to publish a Topup message - * @param stateIndex The index of user in the state queue - * @param amount The amount of credits to topup - */ - function topup(uint256 stateIndex, uint256 amount) public isWithinVotingDeadline { + * Allows to publish a Topup message + * @param stateIndex The index of user in the state queue + * @param amount The amount of credits to topup + */ + function topup( + uint256 stateIndex, + uint256 amount + ) public isWithinVotingDeadline { require( numMessages <= maxValues.maxMessages, ERROR_MAX_MESSAGES_REACHED @@ -265,9 +290,12 @@ contract Poll is uint256[2] memory dat; dat[0] = stateIndex; dat[1] = amount; - (Message memory _message, , uint256 messageLeaf) = padAndHashMessage(dat, 2); + (Message memory _message, , uint256 messageLeaf) = padAndHashMessage( + dat, + 2 + ); extContracts.messageAq.enqueue(messageLeaf); - + emit TopupMessage(_message); } @@ -279,9 +307,10 @@ contract Poll is * coordinator's private key to generate an ECDH shared key with which * to encrypt the message. */ - function publishMessage(Message memory _message, PubKey memory _encPubKey) - public isWithinVotingDeadline - { + function publishMessage( + Message memory _message, + PubKey memory _encPubKey + ) public isWithinVotingDeadline { require( numMessages <= maxValues.maxMessages, ERROR_MAX_MESSAGES_REACHED @@ -303,17 +332,88 @@ contract Poll is emit PublishMessage(_message, _encPubKey); } - + /** + * @notice Attempts to deactivate the User's MACI public key + * @param _message The encrypted message which contains state leaf index + * @param _encPubKey An epheremal public key which can be combined with the + * coordinator's private key to generate an ECDH shared key with which + * to encrypt the message. + */ + function deactivateKey( + Message memory _message, + PubKey memory _encPubKey + ) external { + require( + block.timestamp - deployTime < + extContracts.maci.deactivationPeriod(), + ERROR_DEACTIVATION_PERIOD_PASSED + ); + + require( + numMessages <= maxValues.maxMessages, + ERROR_MAX_MESSAGES_REACHED + ); + + require( + _encPubKey.x < SNARK_SCALAR_FIELD && + _encPubKey.y < SNARK_SCALAR_FIELD, + ERROR_INVALID_PUBKEY + ); + + unchecked { + numMessages++; + numDeactivatedKeys++; + } + + _message.msgType = 1; // Same as vote, keeping the modified circuit complexity minimal + + uint256 messageLeaf = hashMessageAndEncPubKey(_message, _encPubKey); + + deactivationChainHash = hash2([deactivationChainHash, messageLeaf]); + extContracts.messageAq.enqueue(messageLeaf); + + emit PublishMessage(_message, _encPubKey); + emit AttemptKeyDeactivation(_message, _encPubKey); + } + + /** + * @notice Confirms the deactivation of a MACI public key. This function must be called by Coordinator after User calls the deactivateKey function + * @param _batchLeaves Deactivated keys leaves + * @param _batchSize The capacity of the subroot of the deactivated keys tree + */ + function confirmDeactivation( + uint256[][] memory _batchLeaves, + uint256 _batchSize + ) external onlyOwner { + require( + numDeactivatedKeys <= maxValues.maxMessages, + ERROR_MAX_DEACTIVATED_KEYS_REACHED + ); + + for (uint256 i = 0; i < _batchSize; i++) { + uint256 keyHash = _batchLeaves[i][0]; + uint256[2] memory c1; + uint256[2] memory c2; + + c1[0] = _batchLeaves[i][1]; + c1[1] = _batchLeaves[i][2]; + c2[0] = _batchLeaves[i][3]; + c2[1] = _batchLeaves[i][4]; + + extContracts.deactivatedKeysAq.enqueue(hash5([keyHash, c1[0], c1[1], c2[0], c2[1]])); + emit DeactivateKey(keyHash, c1, c2); + } + } + /* * The first step of merging the MACI state AccQueue. This allows the * ProcessMessages circuit to access the latest state tree and ballots via * currentSbCommitment. */ - function mergeMaciStateAqSubRoots(uint256 _numSrQueueOps, uint256 _pollId) - public - onlyOwner - isAfterVotingDeadline - { + function mergeMaciStateAqSubRoots( + uint256 _numSrQueueOps, + uint256 _pollId + ) public isAfterVotingDeadline { // TODO: During milestone 3 - onlyOwner fails here because the caller is MessageProcessor and not Maci // This function cannot be called after the stateAq was merged require(!stateAqMerged, ERROR_STATE_AQ_ALREADY_MERGED); @@ -328,13 +428,11 @@ contract Poll is * The second step of merging the MACI state AccQueue. This allows the * ProcessMessages circuit to access the latest state tree and ballots via * currentSbCommitment. - * @param _pollId The ID of the Poll + * @param _pollId The ID of the Poll */ - function mergeMaciStateAq(uint256 _pollId) - public - onlyOwner - isAfterVotingDeadline - { + function mergeMaciStateAq( + uint256 _pollId + ) public isAfterVotingDeadline { // TODO: During milestone 3 - onlyOwner fails here because the caller is MessageProcessor and not Maci // This function can only be called once per Poll after the voting // deadline require(!stateAqMerged, ERROR_STATE_AQ_ALREADY_MERGED); @@ -345,7 +443,7 @@ contract Poll is extContracts.maci.stateAq().subTreesMerged(), ERROR_STATE_AQ_SUBTREES_NEED_MERGE ); - + mergedStateRoot = extContracts.maci.mergeStateAq(_pollId); // Set currentSbCommitment @@ -362,11 +460,9 @@ contract Poll is * The first step in merging the message AccQueue so that the * ProcessMessages circuit can access the message root. */ - function mergeMessageAqSubRoots(uint256 _numSrQueueOps) - public - onlyOwner - isAfterVotingDeadline - { + function mergeMessageAqSubRoots( + uint256 _numSrQueueOps + ) public onlyOwner isAfterVotingDeadline { extContracts.messageAq.mergeSubRoots(_numSrQueueOps); emit MergeMessageAqSubRoots(_numSrQueueOps); } @@ -381,6 +477,4 @@ contract Poll is ); emit MergeMessageAq(root); } - } - diff --git a/contracts/contracts/Subsidy.sol b/contracts/contracts/Subsidy.sol index cd25d4d50f..cffb05a9c8 100644 --- a/contracts/contracts/Subsidy.sol +++ b/contracts/contracts/Subsidy.sol @@ -11,13 +11,7 @@ import {CommonUtilities} from "./utilities/Utility.sol"; import {Verifier} from "./crypto/Verifier.sol"; import {VkRegistry} from "./VkRegistry.sol"; -contract Subsidy is - Ownable, - CommonUtilities, - Hasher, - SnarkCommon -{ - +contract Subsidy is Ownable, CommonUtilities, Hasher, SnarkCommon { uint256 public rbi; // row batch index uint256 public cbi; // column batch index // The final commitment to the state and ballot roots @@ -50,18 +44,15 @@ contract Subsidy is } } - function genSubsidyPackedVals(uint256 _numSignUps) public view returns (uint256) { require(_numSignUps < 2**50, "numSignUps too large"); - require(rbi < 2**50, "rbi too large"); - require(cbi < 2**50, "cbi too large"); - uint256 result = (_numSignUps << 100) + - (rbi << 50) + - cbi; + require(rbi < 2**50, "rbi too large"); + require(cbi < 2**50, "cbi too large"); + uint256 result = (_numSignUps << 100) + (rbi << 50) + cbi; return result; } @@ -89,12 +80,12 @@ contract Subsidy is _votingPeriodOver(_poll); updateSbCommitment(_mp); - (uint8 intStateTreeDepth, , , ) = _poll - .treeDepths(); + (uint8 intStateTreeDepth, , , ) = _poll.treeDepths(); - uint256 subsidyBatchSize = uint256(treeArity)**intStateTreeDepth; + uint256 subsidyBatchSize = uint256(treeArity)**intStateTreeDepth; - (uint256 numSignUps, ) = _poll.numSignUpsAndMessages(); + (uint256 numSignUps, , ) = _poll + .numSignUpsAndMessagesAndDeactivatedKeys(); uint256 numLeaves = numSignUps + 1; // Require that there are unfinished ballots left @@ -116,10 +107,10 @@ contract Subsidy is } /* - * @notice increase subsidy batch index (rbi, cbi) to next, - * it will try to cbi++ if the whole batch can fit into numLeaves - * otherwise it will increase row index: rbi++ - * @param batchSize: the size of 1 dimensional batch over the signup users, + * @notice increase subsidy batch index (rbi, cbi) to next, + * it will try to cbi++ if the whole batch can fit into numLeaves + * otherwise it will increase row index: rbi++ + * @param batchSize: the size of 1 dimensional batch over the signup users, * notice each batch for subsidy calculation is 2 dimenional: batchSize*batchSize * @param numLeaves: total number of leaves in stateTree, i.e. number of signup users * @return None @@ -143,7 +134,7 @@ contract Subsidy is ) public view returns (bool) { (uint8 intStateTreeDepth, , , uint8 voteOptionTreeDepth) = _poll .treeDepths(); - (VkRegistry vkRegistry, IMACI maci, , ) = _poll.extContracts(); + (VkRegistry vkRegistry, IMACI maci, , , ) = _poll.extContracts(); if (address(vkRegistry) == address(0)) { revert VK_NOT_SET(); @@ -165,7 +156,4 @@ contract Subsidy is // Verify the proof return verifier.verify(_proof, vk, publicInputHash); } - - - } diff --git a/contracts/contracts/Tally.sol b/contracts/contracts/Tally.sol index dda8be6609..4c50f87318 100644 --- a/contracts/contracts/Tally.sol +++ b/contracts/contracts/Tally.sol @@ -12,13 +12,7 @@ import {Verifier} from "./crypto/Verifier.sol"; import {VkRegistry} from "./VkRegistry.sol"; import {CommonUtilities} from "./utilities/Utility.sol"; - -contract Tally is - Ownable, - SnarkCommon, - CommonUtilities, - Hasher -{ +contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher { // Error codes error PROCESSING_NOT_COMPLETE(); error INVALID_TALLY_VOTES_PROOF(); @@ -51,7 +45,6 @@ contract Tally is verifier = _verifier; } - /* * @notice Pack the batch start index and number of signups into a 100-bit value. * @param _numSignUps: number of signups @@ -102,7 +95,7 @@ contract Tally is return inputHash; } - // TODO: make sure correct mp address is passed + // TODO: make sure correct mp address is passed // TODO: reuse tally.sol for multiple polls function updateSbCommitment(MessageProcessor _mp) public { // Require that all messages have been processed @@ -125,7 +118,8 @@ contract Tally is (, uint256 tallyBatchSize, ) = _poll.batchSizes(); uint256 batchStartIndex = tallyBatchNum * tallyBatchSize; - (uint256 numSignUps, ) = _poll.numSignUpsAndMessages(); + (uint256 numSignUps, , ) = _poll + .numSignUpsAndMessagesAndDeactivatedKeys(); // Require that there are untalied ballots left if (batchStartIndex > numSignUps) { @@ -170,7 +164,7 @@ contract Tally is (uint8 intStateTreeDepth, , , uint8 voteOptionTreeDepth) = _poll .treeDepths(); - (VkRegistry vkRegistry, IMACI maci, , ) = _poll.extContracts(); + (VkRegistry vkRegistry, IMACI maci, , , ) = _poll.extContracts(); // Get the verifying key VerifyingKey memory vk = vkRegistry.getTallyVk( @@ -223,5 +217,4 @@ contract Tally is } return current; } - } diff --git a/contracts/contracts/VkRegistry.sol b/contracts/contracts/VkRegistry.sol index f94ecd3bf8..181e4d2b5b 100644 --- a/contracts/contracts/VkRegistry.sol +++ b/contracts/contracts/VkRegistry.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.10; -import { SnarkCommon } from "./crypto/SnarkCommon.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import {SnarkCommon} from "./crypto/SnarkCommon.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; /* * Stores verifying keys for the circuits. @@ -10,17 +10,20 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; * as a uint256. */ contract VkRegistry is Ownable, SnarkCommon { + mapping(uint256 => VerifyingKey) internal processVks; + mapping(uint256 => bool) internal processVkSet; - mapping (uint256 => VerifyingKey) internal processVks; - mapping (uint256 => bool) internal processVkSet; + mapping(uint256 => VerifyingKey) internal processDeactivationVks; + mapping(uint256 => bool) internal processDeactivationVkSet; - mapping (uint256 => VerifyingKey) internal tallyVks; - mapping (uint256 => bool) internal tallyVkSet; + mapping(uint256 => VerifyingKey) internal tallyVks; + mapping(uint256 => bool) internal tallyVkSet; - mapping (uint256 => VerifyingKey) internal subsidyVks; - mapping (uint256 => bool) internal subsidyVkSet; + mapping(uint256 => VerifyingKey) internal subsidyVks; + mapping(uint256 => bool) internal subsidyVkSet; event ProcessVkSet(uint256 _sig); + event ProcessDeactivationVkSet(uint256 _sig); event TallyVkSet(uint256 _sig); event SubsidyVkSet(uint256 _sig); @@ -42,19 +45,26 @@ contract VkRegistry is Ownable, SnarkCommon { uint256 _voteOptionTreeDepth, uint256 _messageBatchSize ) public pure returns (uint256) { - return + return (_messageBatchSize << 192) + (_stateTreeDepth << 128) + (_messageTreeDepth << 64) + _voteOptionTreeDepth; } + function genProcessDeactivationVkSig( + uint256 _stateTreeDepth, + uint256 _deactivationTreeDepth + ) public pure returns (uint256) { + return (_stateTreeDepth << 128) + _deactivationTreeDepth; + } + function genTallyVkSig( uint256 _stateTreeDepth, uint256 _intStateTreeDepth, uint256 _voteOptionTreeDepth ) public pure returns (uint256) { - return + return (_stateTreeDepth << 128) + (_intStateTreeDepth << 64) + _voteOptionTreeDepth; @@ -65,7 +75,7 @@ contract VkRegistry is Ownable, SnarkCommon { uint256 _intStateTreeDepth, uint256 _voteOptionTreeDepth ) public pure returns (uint256) { - return + return (_stateTreeDepth << 128) + (_intStateTreeDepth << 64) + _voteOptionTreeDepth; @@ -78,9 +88,9 @@ contract VkRegistry is Ownable, SnarkCommon { uint256 _voteOptionTreeDepth, uint256 _messageBatchSize, VerifyingKey memory _processVk, + VerifyingKey memory _deactivationVk, VerifyingKey memory _tallyVk ) public onlyOwner { - uint256 processVkSig = genProcessVkSig( _stateTreeDepth, _messageTreeDepth, @@ -88,7 +98,20 @@ contract VkRegistry is Ownable, SnarkCommon { _messageBatchSize ); - require(!processVkSet[processVkSig], "VkRegistry: process vk already set"); + require( + !processVkSet[processVkSig], + "VkRegistry: process vk already set" + ); + + uint256 deactivationVkSig = genProcessDeactivationVkSig( + _stateTreeDepth, + _messageTreeDepth + ); + + require( + !processDeactivationVkSet[deactivationVkSig], + "VkRegistry: process deactivation vk already set" + ); uint256 tallyVkSig = genTallyVkSig( _stateTreeDepth, @@ -103,23 +126,37 @@ contract VkRegistry is Ownable, SnarkCommon { processVk.beta2 = _processVk.beta2; processVk.gamma2 = _processVk.gamma2; processVk.delta2 = _processVk.delta2; - for (uint8 i = 0; i < _processVk.ic.length; i ++) { + for (uint8 i = 0; i < _processVk.ic.length; i++) { processVk.ic.push(_processVk.ic[i]); } processVkSet[processVkSig] = true; + VerifyingKey storage deactivationVk = processDeactivationVks[ + deactivationVkSig + ]; + deactivationVk.alpha1 = _deactivationVk.alpha1; + deactivationVk.beta2 = _deactivationVk.beta2; + deactivationVk.gamma2 = _deactivationVk.gamma2; + deactivationVk.delta2 = _deactivationVk.delta2; + for (uint8 i = 0; i < _deactivationVk.ic.length; i++) { + deactivationVk.ic.push(_deactivationVk.ic[i]); + } + + processDeactivationVkSet[deactivationVkSig] = true; + VerifyingKey storage tallyVk = tallyVks[tallyVkSig]; tallyVk.alpha1 = _tallyVk.alpha1; tallyVk.beta2 = _tallyVk.beta2; tallyVk.gamma2 = _tallyVk.gamma2; tallyVk.delta2 = _tallyVk.delta2; - for (uint8 i = 0; i < _tallyVk.ic.length; i ++) { + for (uint8 i = 0; i < _tallyVk.ic.length; i++) { tallyVk.ic.push(_tallyVk.ic[i]); } tallyVkSet[tallyVkSig] = true; emit TallyVkSet(tallyVkSig); + emit ProcessDeactivationVkSet(deactivationVkSig); emit ProcessVkSet(processVkSig); } @@ -129,21 +166,23 @@ contract VkRegistry is Ownable, SnarkCommon { uint256 _voteOptionTreeDepth, VerifyingKey memory _subsidyVk ) public onlyOwner { - uint256 subsidyVkSig = genSubsidyVkSig( _stateTreeDepth, _intStateTreeDepth, _voteOptionTreeDepth ); - require(!subsidyVkSet[subsidyVkSig], "VkRegistry: subsidy vk already set"); + require( + !subsidyVkSet[subsidyVkSig], + "VkRegistry: subsidy vk already set" + ); VerifyingKey storage subsidyVk = subsidyVks[subsidyVkSig]; subsidyVk.alpha1 = _subsidyVk.alpha1; subsidyVk.beta2 = _subsidyVk.beta2; subsidyVk.gamma2 = _subsidyVk.gamma2; subsidyVk.delta2 = _subsidyVk.delta2; - for (uint8 i = 0; i < _subsidyVk.ic.length; i ++) { + for (uint8 i = 0; i < _subsidyVk.ic.length; i++) { subsidyVk.ic.push(_subsidyVk.ic[i]); } subsidyVkSet[subsidyVkSig] = true; @@ -169,7 +208,10 @@ contract VkRegistry is Ownable, SnarkCommon { function getProcessVkBySig( uint256 _sig ) public view returns (VerifyingKey memory) { - require(processVkSet[_sig], "VkRegistry: process verifying key not set"); + require( + processVkSet[_sig], + "VkRegistry: process verifying key not set" + ); return processVks[_sig]; } @@ -190,6 +232,29 @@ contract VkRegistry is Ownable, SnarkCommon { return getProcessVkBySig(sig); } + function getProcessDeactivationVkBySig( + uint256 _sig + ) public view returns (VerifyingKey memory) { + require( + processDeactivationVkSet[_sig], + "VkRegistry: deactivation verifying key not set" + ); + + return processDeactivationVks[_sig]; + } + + function getProcessDeactivationVk( + uint256 _stateTreeDepth, + uint256 _deactivationTreeDepth + ) public view returns (VerifyingKey memory) { + uint256 sig = genProcessDeactivationVkSig( + _stateTreeDepth, + _deactivationTreeDepth + ); + + return getProcessDeactivationVkBySig(sig); + } + function hasTallyVk( uint256 _stateTreeDepth, uint256 _intStateTreeDepth, @@ -243,7 +308,10 @@ contract VkRegistry is Ownable, SnarkCommon { function getSubsidyVkBySig( uint256 _sig ) public view returns (VerifyingKey memory) { - require(subsidyVkSet[_sig], "VkRegistry: subsidy verifying key not set"); + require( + subsidyVkSet[_sig], + "VkRegistry: subsidy verifying key not set" + ); return subsidyVks[_sig]; } diff --git a/contracts/contracts/trees/AccQueue.sol b/contracts/contracts/trees/AccQueue.sol index 811022be8f..98f77ecd79 100644 --- a/contracts/contracts/trees/AccQueue.sol +++ b/contracts/contracts/trees/AccQueue.sol @@ -201,7 +201,7 @@ abstract contract AccQueue is Ownable, Hasher { * Fill any empty leaves of the current subtree with zeros and store the * resulting subroot. */ - function fill() public onlyOwner { + function fill() public { // TODO: During milestone 3 - onlyOwner fails here because the caller is MessageProcessor and not Maci if (numLeaves % subTreeCapacity == 0) { // If the subtree is completely empty, then the subroot is a // precalculated zero value @@ -243,7 +243,7 @@ abstract contract AccQueue is Ownable, Hasher { /* * Insert a subtree. Used for batch enqueues. */ - function insertSubTree(uint256 _subRoot) public onlyOwner { + function insertSubTree(uint256 _subRoot) public { // TODO: During milestone 3 - onlyOwner fails here because the caller is MessageProcessor and not Maci subRoots[currentSubtreeIndex] = _subRoot; // Increment the subtree index @@ -292,7 +292,7 @@ abstract contract AccQueue is Ownable, Hasher { */ function mergeSubRoots( uint256 _numSrQueueOps - ) public onlyOwner { + ) public { // TODO: During milestone 3 - onlyOwner fails here because the caller is MessageProcessor and not Maci // This function can only be called once unless a new subtree is created require(!subTreesMerged, "AccQueue: subtrees already merged"); @@ -397,7 +397,7 @@ abstract contract AccQueue is Ownable, Hasher { * @param _depth The depth of the main tree. It must fit all the leaves or * this function will revert. */ - function merge(uint256 _depth) public onlyOwner returns (uint256) { + function merge(uint256 _depth) public returns (uint256) { // TODO: During milestone 3 - onlyOwner fails here because the caller is MessageProcessor and not Maci // The tree depth must be more than 0 require(_depth > 0, "AccQueue: _depth must be more than 0"); diff --git a/contracts/contracts/utilities/Utility.sol b/contracts/contracts/utilities/Utility.sol index 5a911bb896..a171f80fc9 100644 --- a/contracts/contracts/utilities/Utility.sol +++ b/contracts/contracts/utilities/Utility.sol @@ -5,16 +5,17 @@ import {Hasher} from "../crypto/Hasher.sol"; import {SnarkConstants} from "../crypto/SnarkConstants.sol"; import {Poll} from "../Poll.sol"; - contract CommonUtilities { - error VOTING_PERIOD_NOT_PASSED(); + error VOTING_PERIOD_NOT_PASSED(); + // common function for MessageProcessor, Tally and Subsidy function _votingPeriodOver(Poll _poll) internal view { (uint256 deployTime, uint256 duration) = _poll .getDeployTimeAndDuration(); // Require that the voting period is over uint256 secondsPassed = block.timestamp - deployTime; - if (secondsPassed <= duration ) { + + if (secondsPassed <= duration) { revert VOTING_PERIOD_NOT_PASSED(); } } @@ -28,7 +29,7 @@ contract Utilities is SnarkConstants, Hasher, IPubKey, IMessage { uint256[10] memory dat; dat[0] = dataToPad[0]; dat[1] = dataToPad[1]; - for(uint i = 2; i< 10;) { + for (uint i = 2; i < 10; ) { dat[i] = 0; unchecked { ++i; @@ -71,4 +72,12 @@ contract Utilities is SnarkConstants, Hasher, IPubKey, IMessage { } } - +// TODO: Fix Warning: 1 contracts exceed the size limit for mainnet deployment. +// interface IVerifier { +// function verifyProof( +// uint256[2] memory a, +// uint256[2][2] memory b, +// uint256[2] memory c, +// uint256[] memory input +// ) external view returns (bool); +// } diff --git a/contracts/hardhat.config.js b/contracts/hardhat.config.js index d950947998..124aaf075f 100644 --- a/contracts/hardhat.config.js +++ b/contracts/hardhat.config.js @@ -1,32 +1,36 @@ /** * @type import('hardhat/config').HardhatUserConfig */ -require('hardhat-contract-sizer') -require('@nomiclabs/hardhat-ethers') -require('hardhat-artifactor') +require('hardhat-contract-sizer'); +require('@nomiclabs/hardhat-ethers'); +require('hardhat-artifactor'); module.exports = { - solidity: { - version: "0.8.10", - settings: { - optimizer: { - enabled: true, - runs: 200 - } - } - }, - networks: { - hardhat: { - accounts: { - mnemonic: "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat" - }, - loggingEnabled: false, - allowUnlimitedContractSize: true - } - }, - contractSizer: { - alphaSort: true, - runOnCompile: true, - disambiguatePaths: false, - } + solidity: { + version: '0.8.10', + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + networks: { + hardhat: { + accounts: { + mnemonic: + 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat', + }, + loggingEnabled: false, + allowUnlimitedContractSize: true, + throwOnTransactionFailures: true, + throwOnCallFailures: true, + blockGasLimit: 25000000 + }, + }, + contractSizer: { + alphaSort: true, + runOnCompile: true, + disambiguatePaths: false, + }, }; diff --git a/contracts/package-lock.json b/contracts/package-lock.json index cf3feb3583..18e0fd71cd 100644 --- a/contracts/package-lock.json +++ b/contracts/package-lock.json @@ -16,7 +16,8 @@ "hardhat": "^2.12.2", "hardhat-artifactor": "^0.2.0", "hardhat-contract-sizer": "^2.0.3", - "module-alias": "^2.2.2" + "module-alias": "^2.2.2", + "typescript": "^4.2.3" }, "devDependencies": { "@types/jest": "^26.0.21", @@ -9830,9 +9831,7 @@ }, "node_modules/typescript": { "version": "4.8.4", - "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16595,9 +16594,7 @@ } }, "typescript": { - "version": "4.8.4", - "devOptional": true, - "peer": true + "version": "4.8.4" }, "undici": { "version": "5.12.0", diff --git a/contracts/ts/__tests__/MACI.test.ts b/contracts/ts/__tests__/MACI.test.ts index 9d74009629..aef099f719 100644 --- a/contracts/ts/__tests__/MACI.test.ts +++ b/contracts/ts/__tests__/MACI.test.ts @@ -1,708 +1,976 @@ -jest.setTimeout(90000) -import * as ethers from 'ethers' -import { timeTravel } from './utils' -import { parseArtifact, getDefaultSigner } from '../deploy' -import { deployTestContracts } from '../utils' -import { genMaciStateFromContract } from '../genMaciState' +jest.setTimeout(90000); +// import * as ethers from 'ethers'; +const { ethers } = require('hardhat'); +import { BigNumber } from 'ethers'; +import { timeTravel } from './utils'; +import { parseArtifact, getDefaultSigner } from '../deploy'; +import { deployTestContracts } from '../utils'; +import { genMaciStateFromContract } from '../genMaciState'; import { - PCommand, - VerifyingKey, - Keypair, - PubKey, - Message, -} from 'maci-domainobjs' + PCommand, + VerifyingKey, + Keypair, + PubKey, + Message, +} from 'maci-domainobjs'; import { - MaciState, - genProcessVkSig, - genTallyVkSig, - MaxValues, - TreeDepths, -} from 'maci-core' - -import { G1Point, G2Point, NOTHING_UP_MY_SLEEVE } from 'maci-crypto' - -const STATE_TREE_DEPTH = 10 -const STATE_TREE_ARITY = 5 -const MESSAGE_TREE_DEPTH = 4 -const MESSAGE_TREE_SUBDEPTH = 2 - -const coordinator = new Keypair() -const [ pollAbi ] = parseArtifact('Poll') -const [ accQueueQuinaryMaciAbi ] = parseArtifact('AccQueueQuinaryMaci') + MaciState, + genProcessVkSig, + genTallyVkSig, + MaxValues, + TreeDepths, +} from 'maci-core'; + +import { G1Point, G2Point, NOTHING_UP_MY_SLEEVE } from 'maci-crypto'; + +const STATE_TREE_DEPTH = 10; +const STATE_TREE_ARITY = 5; +const MESSAGE_TREE_DEPTH = 4; +const MESSAGE_TREE_SUBDEPTH = 2; + +const coordinator = new Keypair(); +const [pollAbi] = parseArtifact('Poll'); +const [accQueueQuinaryMaciAbi] = parseArtifact('AccQueueQuinaryMaci'); + +enum MessageTypes { + VOTE_MESSAGE = 1, // size 10 + TOP_UP_MESSAGE = 2, // size 2 + DEACTIVATE_KEY = 3, // size 10 + GENERATE_NEW_KEY = 4, // size 10 +} const testProcessVk = new VerifyingKey( - new G1Point(BigInt(0), BigInt(1)), - new G2Point([BigInt(2), BigInt(3)], [BigInt(4), BigInt(5)]), - new G2Point([BigInt(6), BigInt(7)], [BigInt(8), BigInt(9)]), - new G2Point([BigInt(10), BigInt(11)], [BigInt(12), BigInt(13)]), - [ - new G1Point(BigInt(14), BigInt(15)), - new G1Point(BigInt(16), BigInt(17)), - ], -) + new G1Point(BigInt(0), BigInt(1)), + new G2Point([BigInt(2), BigInt(3)], [BigInt(4), BigInt(5)]), + new G2Point([BigInt(6), BigInt(7)], [BigInt(8), BigInt(9)]), + new G2Point([BigInt(10), BigInt(11)], [BigInt(12), BigInt(13)]), + [new G1Point(BigInt(14), BigInt(15)), new G1Point(BigInt(16), BigInt(17))] +); + +const testProcessDeactivationVk = new VerifyingKey( + new G1Point(BigInt(0), BigInt(1)), + new G2Point([BigInt(2), BigInt(3)], [BigInt(4), BigInt(5)]), + new G2Point([BigInt(6), BigInt(7)], [BigInt(8), BigInt(9)]), + new G2Point([BigInt(10), BigInt(11)], [BigInt(12), BigInt(13)]), + [new G1Point(BigInt(14), BigInt(15)), new G1Point(BigInt(16), BigInt(17))] +); const testTallyVk = new VerifyingKey( - new G1Point(BigInt(0), BigInt(1)), - new G2Point([BigInt(2), BigInt(3)], [BigInt(4), BigInt(5)]), - new G2Point([BigInt(6), BigInt(7)], [BigInt(8), BigInt(9)]), - new G2Point([BigInt(10), BigInt(11)], [BigInt(12), BigInt(13)]), - [ - new G1Point(BigInt(14), BigInt(15)), - new G1Point(BigInt(16), BigInt(17)), - ], -) + new G1Point(BigInt(0), BigInt(1)), + new G2Point([BigInt(2), BigInt(3)], [BigInt(4), BigInt(5)]), + new G2Point([BigInt(6), BigInt(7)], [BigInt(8), BigInt(9)]), + new G2Point([BigInt(10), BigInt(11)], [BigInt(12), BigInt(13)]), + [new G1Point(BigInt(14), BigInt(15)), new G1Point(BigInt(16), BigInt(17))] +); const compareVks = (vk: VerifyingKey, vkOnChain: any) => { - expect(vk.ic.length).toEqual(vkOnChain.ic.length) - for (let i = 0; i < vk.ic.length; i ++) { - expect(vk.ic[i].x.toString()).toEqual(vkOnChain.ic[i].x.toString()) - expect(vk.ic[i].y.toString()).toEqual(vkOnChain.ic[i].y.toString()) - } - expect(vk.alpha1.x.toString()).toEqual(vkOnChain.alpha1.x.toString()) - expect(vk.alpha1.y.toString()).toEqual(vkOnChain.alpha1.y.toString()) - expect(vk.beta2.x[0].toString()).toEqual(vkOnChain.beta2.x[0].toString()) - expect(vk.beta2.x[1].toString()).toEqual(vkOnChain.beta2.x[1].toString()) - expect(vk.beta2.y[0].toString()).toEqual(vkOnChain.beta2.y[0].toString()) - expect(vk.beta2.y[1].toString()).toEqual(vkOnChain.beta2.y[1].toString()) - expect(vk.delta2.x[0].toString()).toEqual(vkOnChain.delta2.x[0].toString()) - expect(vk.delta2.x[1].toString()).toEqual(vkOnChain.delta2.x[1].toString()) - expect(vk.delta2.y[0].toString()).toEqual(vkOnChain.delta2.y[0].toString()) - expect(vk.delta2.y[1].toString()).toEqual(vkOnChain.delta2.y[1].toString()) - expect(vk.gamma2.x[0].toString()).toEqual(vkOnChain.gamma2.x[0].toString()) - expect(vk.gamma2.x[1].toString()).toEqual(vkOnChain.gamma2.x[1].toString()) - expect(vk.gamma2.y[0].toString()).toEqual(vkOnChain.gamma2.y[0].toString()) - expect(vk.gamma2.y[1].toString()).toEqual(vkOnChain.gamma2.y[1].toString()) -} - -const users = [ - new Keypair(), - new Keypair(), - new Keypair(), -] - -const signUpTxOpts = { gasLimit: 300000 } - -const maciState = new MaciState() + expect(vk.ic.length).toEqual(vkOnChain.ic.length); + for (let i = 0; i < vk.ic.length; i++) { + expect(vk.ic[i].x.toString()).toEqual(vkOnChain.ic[i].x.toString()); + expect(vk.ic[i].y.toString()).toEqual(vkOnChain.ic[i].y.toString()); + } + expect(vk.alpha1.x.toString()).toEqual(vkOnChain.alpha1.x.toString()); + expect(vk.alpha1.y.toString()).toEqual(vkOnChain.alpha1.y.toString()); + expect(vk.beta2.x[0].toString()).toEqual(vkOnChain.beta2.x[0].toString()); + expect(vk.beta2.x[1].toString()).toEqual(vkOnChain.beta2.x[1].toString()); + expect(vk.beta2.y[0].toString()).toEqual(vkOnChain.beta2.y[0].toString()); + expect(vk.beta2.y[1].toString()).toEqual(vkOnChain.beta2.y[1].toString()); + expect(vk.delta2.x[0].toString()).toEqual(vkOnChain.delta2.x[0].toString()); + expect(vk.delta2.x[1].toString()).toEqual(vkOnChain.delta2.x[1].toString()); + expect(vk.delta2.y[0].toString()).toEqual(vkOnChain.delta2.y[0].toString()); + expect(vk.delta2.y[1].toString()).toEqual(vkOnChain.delta2.y[1].toString()); + expect(vk.gamma2.x[0].toString()).toEqual(vkOnChain.gamma2.x[0].toString()); + expect(vk.gamma2.x[1].toString()).toEqual(vkOnChain.gamma2.x[1].toString()); + expect(vk.gamma2.y[0].toString()).toEqual(vkOnChain.gamma2.y[0].toString()); + expect(vk.gamma2.y[1].toString()).toEqual(vkOnChain.gamma2.y[1].toString()); +}; + +const users = [new Keypair(), new Keypair(), new Keypair()]; + +const signUpTxOpts = { gasLimit: 300000 }; + +const maciState = new MaciState(); // Poll parameters -const duration = 15 +const duration = 15; const maxValues: MaxValues = { - maxUsers: 25, - maxMessages: 25, - maxVoteOptions: 25, -} + maxUsers: 25, + maxMessages: 25, + maxVoteOptions: 25, +}; const treeDepths: TreeDepths = { - intStateTreeDepth: 1, - messageTreeDepth: MESSAGE_TREE_DEPTH, - messageTreeSubDepth: MESSAGE_TREE_SUBDEPTH, - voteOptionTreeDepth: 2, -} + intStateTreeDepth: 1, + messageTreeDepth: MESSAGE_TREE_DEPTH, + messageTreeSubDepth: MESSAGE_TREE_SUBDEPTH, + voteOptionTreeDepth: 2, +}; -const messageBatchSize = 25 -const tallyBatchSize = STATE_TREE_ARITY ** treeDepths.intStateTreeDepth +const messageBatchSize = 25; +const tallyBatchSize = STATE_TREE_ARITY ** treeDepths.intStateTreeDepth; -const initialVoiceCreditBalance = 100 -let signer +const initialVoiceCreditBalance = 100; +const signUpDuration = 100; +const deactivationPeriod = 10; +let signer; describe('MACI', () => { - let maciContract - let stateAqContract - let vkRegistryContract - let mpContract - let tallyContract - let pollId: number - - describe('Deployment', () => { - beforeAll(async () => { - signer = await getDefaultSigner() - const r = await deployTestContracts( - initialVoiceCreditBalance, - ) - maciContract = r.maciContract - stateAqContract = r.stateAqContract - vkRegistryContract = r.vkRegistryContract - mpContract = r.mpContract - tallyContract = r.tallyContract - }) - - it('MACI.stateTreeDepth should be correct', async () => { - const std = await maciContract.stateTreeDepth() - expect(std.toString()).toEqual(STATE_TREE_DEPTH.toString()) - }) - }) - - describe('Signups', () => { - - it('should sign up users', async () => { - expect.assertions(users.length * 2) - const iface = maciContract.interface - - let i = 0 - for (const user of users) { - const tx = await maciContract.signUp( - user.pubKey.asContractParam(), - ethers.utils.defaultAbiCoder.encode(['uint256'], [1]), - ethers.utils.defaultAbiCoder.encode(['uint256'], [0]), - signUpTxOpts, - ) - const receipt = await tx.wait() - expect(receipt.status).toEqual(1) - console.log('signUp() gas used:', receipt.gasUsed.toString()) - - // Store the state index - const event = iface.parseLog(receipt.logs[receipt.logs.length - 1]) - expect(event.args._stateIndex.toString()).toEqual((i + 1).toString()) - - maciState.signUp( - user.pubKey, - BigInt(event.args._voiceCreditBalance.toString()), - BigInt(event.args._timestamp.toString()), - ) - - i ++ - } - }) - - it('signUp() shold fail when given an invalid pubkey', async () => { - expect.assertions(1) - try { - await maciContract.signUp( - { - x: '21888242871839275222246405745257275088548364400416034343698204186575808495617', - y: '0', - }, - ethers.utils.defaultAbiCoder.encode(['uint256'], [1]), - ethers.utils.defaultAbiCoder.encode(['uint256'], [0]), - signUpTxOpts, - ) - } catch (e) { - const error = "'MACI: _pubKey values should be less than the snark scalar field'" - expect(e.message.endsWith(error)).toBeTruthy() - } - }) - }) - - describe('Merging sign-ups should fail because of onlyPoll', () => { - it('coordinator should not be able to merge the signUp AccQueue', async () => { - try { - await maciContract.mergeStateAqSubRoots(0, 0, { gasLimit: 3000000 }) - } catch (e) { - const error = "'MACI: only a Poll contract can call this function'" - expect(e.message.endsWith(error)).toBeTruthy() - } - - try { - await maciContract.mergeStateAq(0, { gasLimit: 3000000 }) - } catch (e) { - const error = "'MACI: only a Poll contract can call this function'" - expect(e.message.endsWith(error)).toBeTruthy() - } - }) - }) - - describe('Deploy a Poll', () => { - let deployTime - it('should set VKs and deploy a poll', async () => { - const std = await maciContract.stateTreeDepth() - - // Set VKs - console.log('Setting VKs') - let tx = await vkRegistryContract.setVerifyingKeys( - std.toString(), - treeDepths.intStateTreeDepth, - treeDepths.messageTreeDepth, - treeDepths.voteOptionTreeDepth, - messageBatchSize, - testProcessVk.asContractParam(), - testTallyVk.asContractParam(), - { gasLimit: 1000000 }, - ) - let receipt = await tx.wait() - expect(receipt.status).toEqual(1) - - const pSig = await vkRegistryContract.genProcessVkSig( - std.toString(), - treeDepths.messageTreeDepth, - treeDepths.voteOptionTreeDepth, - messageBatchSize, - ) - - expect(pSig.toString()).toEqual( - genProcessVkSig( - std, - treeDepths.messageTreeDepth, - treeDepths.voteOptionTreeDepth, - messageBatchSize, - ).toString() - ) - - const isPSigSet = await vkRegistryContract.isProcessVkSet(pSig) - expect(isPSigSet).toBeTruthy() - - const tSig = await vkRegistryContract.genTallyVkSig( - std.toString(), - treeDepths.intStateTreeDepth, - treeDepths.voteOptionTreeDepth, - ) - const isTSigSet = await vkRegistryContract.isTallyVkSet(tSig) - expect(isTSigSet).toBeTruthy() - - // Check that the VKs are set - const processVkOnChain = await vkRegistryContract.getProcessVk( - std, - treeDepths.messageTreeDepth, - treeDepths.voteOptionTreeDepth, - messageBatchSize, - ) - - const tallyVkOnChain = await vkRegistryContract.getTallyVk( - std.toString(), - treeDepths.intStateTreeDepth, - treeDepths.voteOptionTreeDepth, - ) - - compareVks(testProcessVk, processVkOnChain) - compareVks(testTallyVk, tallyVkOnChain) - - // Create the poll and get the poll ID from the tx event logs - tx = await maciContract.deployPoll( - duration, - maxValues, - treeDepths, - coordinator.pubKey.asContractParam(), - { gasLimit: 8000000 }, - ) - receipt = await tx.wait() - - const block = await signer.provider.getBlock(receipt.blockHash) - deployTime = block.timestamp - - console.log('deployPoll() gas used:', receipt.gasUsed.toString()) - - expect(receipt.status).toEqual(1) - const iface = maciContract.interface - const event = iface.parseLog(receipt.logs[receipt.logs.length - 1]) - pollId = event.args._pollId - - const p = maciState.deployPoll( - duration, - BigInt(deployTime + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinator, - ) - expect(p.toString()).toEqual(pollId.toString()) - - // publish the NOTHING_UP_MY_SLEEVE message - const messageData = [ - NOTHING_UP_MY_SLEEVE, - BigInt(0) - ] - for (let i = 2; i < 10; i++) { - messageData.push(BigInt(0)) - } - const message = new Message( - BigInt(1), - messageData - ) - const padKey = new PubKey([ - BigInt('10457101036533406547632367118273992217979173478358440826365724437999023779287'), - BigInt('19824078218392094440610104313265183977899662750282163392862422243483260492317'), - ]) - maciState.polls[pollId].publishMessage(message, padKey) - - }) - - it('should fail when attempting to init twice a Poll', async () => { - const pollContractAddress = await maciContract.getPoll(pollId) - const pollContract = new ethers.Contract( - pollContractAddress, - pollAbi, - signer, - ) - - try { - await pollContract.init() - } catch (error) { - expect(error).not.toBe(null) - } - }) - - it('should set correct storage values', async () => { - // Retrieve the Poll state and check that each value is correct - const pollContractAddress = await maciContract.getPoll(pollId) - const pollContract = new ethers.Contract( - pollContractAddress, - pollAbi, - signer, - ) - - const dd = await pollContract.getDeployTimeAndDuration() - - expect(Number(dd[0])).toEqual(deployTime) - expect(Number(dd[1])).toEqual(duration) - - expect(await pollContract.stateAqMerged()).toBeFalsy() - - const sb = await pollContract.currentSbCommitment() - expect(sb.toString()).toEqual('0') - - const sm = await pollContract.numSignUpsAndMessages() - // There are 3 signups via the MACI instance - expect(Number(sm[0])).toEqual(3) - - // There are 1 messages until a user publishes a message - // As we enqueue the NOTHING_UP_MY_SLEEVE hash - expect(Number(sm[1])).toEqual(1) - - const onChainMaxValues = await pollContract.maxValues() - - expect(Number(onChainMaxValues.maxMessages)).toEqual(maxValues.maxMessages) - expect(Number(onChainMaxValues.maxVoteOptions)).toEqual(maxValues.maxVoteOptions) - - const onChainTreeDepths = await pollContract.treeDepths() - expect(Number(onChainTreeDepths.intStateTreeDepth)).toEqual(treeDepths.intStateTreeDepth) - expect(Number(onChainTreeDepths.messageTreeDepth)).toEqual(treeDepths.messageTreeDepth) - expect(Number(onChainTreeDepths.messageTreeSubDepth)).toEqual(treeDepths.messageTreeSubDepth) - expect(Number(onChainTreeDepths.voteOptionTreeDepth)).toEqual(treeDepths.voteOptionTreeDepth) - - const onChainBatchSizes = await pollContract.batchSizes() - expect(Number(onChainBatchSizes.messageBatchSize)).toEqual(messageBatchSize) - expect(Number(onChainBatchSizes.tallyBatchSize)).toEqual(tallyBatchSize) - }) - }) - - describe('Publish messages (vote + key-change)', () => { - let pollContract - - beforeAll(async () => { - const pollContractAddress = await maciContract.getPoll(pollId) - pollContract = new ethers.Contract( - pollContractAddress, - pollAbi, - signer, - ) - }) - - it('should publish a message to the Poll contract', async () => { - const keypair = new Keypair() - - const command = new PCommand( - BigInt(1), - keypair.pubKey, - BigInt(0), - BigInt(9), - BigInt(1), - BigInt(pollId), - BigInt(0), - ) - - const signature = command.sign(keypair.privKey) - const sharedKey = Keypair.genEcdhSharedKey(keypair.privKey, coordinator.pubKey) - const message = command.encrypt(signature, sharedKey) - const tx = await pollContract.publishMessage( - message.asContractParam(), - keypair.pubKey.asContractParam(), - ) - const receipt = await tx.wait() - console.log('publishMessage() gas used:', receipt.gasUsed.toString()) - expect(receipt.status).toEqual(1) - - maciState.polls[pollId].publishMessage(message, keypair.pubKey) - }) - - it('shold not publish a message after the voting period', async () => { - expect.assertions(1) - const dd = await pollContract.getDeployTimeAndDuration() - await timeTravel(signer.provider, Number(dd[0]) + 1) - - const keypair = new Keypair() - const command = new PCommand( - BigInt(0), - keypair.pubKey, - BigInt(0), - BigInt(0), - BigInt(0), - BigInt(pollId), - BigInt(0), - ) - - const signature = command.sign(keypair.privKey) - const sharedKey = Keypair.genEcdhSharedKey(keypair.privKey, coordinator.pubKey) - const message = command.encrypt(signature, sharedKey) - try { - await pollContract.publishMessage( - message.asContractParam(), - keypair.pubKey.asContractParam(), - { gasLimit: 300000 }, - ) - } catch (e) { - const error = 'PollE01' - expect(e.message.slice(0,e.message.length-1).endsWith(error)).toBeTruthy() - } - }) - - }) - - describe('Merge messages', () => { - let pollContract - let messageAqContract - - beforeEach(async () => { - const pollContractAddress = await maciContract.getPoll(pollId) - pollContract = new ethers.Contract( - pollContractAddress, - pollAbi, - signer, - ) - - const extContracts = await pollContract.extContracts() - - const messageAqAddress = extContracts.messageAq - messageAqContract = new ethers.Contract( - messageAqAddress, - accQueueQuinaryMaciAbi, - signer, - ) - }) - - it('should revert if subtrees are not merged for StateAq', async () => { - try { - await pollContract.mergeMaciStateAq(0, { gasLimit: 4000000 }) - } catch (e) { - const error = 'PollE06' - expect(e.message.slice(0,e.message.length-1).endsWith(error)).toBeTruthy() - } - }) - - it('coordinator should be able to merge the message AccQueue', async () => { - let tx = await pollContract.mergeMessageAqSubRoots(0, { gasLimit: 3000000 }) - let receipt = await tx.wait() - expect(receipt.status).toEqual(1) - console.log('mergeMessageAqSubRoots() gas used:', receipt.gasUsed.toString()) - - tx = await pollContract.mergeMessageAq({ gasLimit: 4000000 }) - receipt = await tx.wait() - expect(receipt.status).toEqual(1) - - const poll = maciState.polls[pollId] - poll.messageAq.mergeSubRoots(0) - poll.messageAq.merge(MESSAGE_TREE_DEPTH) - - console.log('mergeMessageAq() gas used:', receipt.gasUsed.toString()) - }) - - it('the message root must be correct', async () => { - const onChainMessageRoot = await messageAqContract.getMainRoot(MESSAGE_TREE_DEPTH) - expect(onChainMessageRoot.toString()) - .toEqual(maciState.polls[pollId].messageAq.mainRoots[MESSAGE_TREE_DEPTH].toString()) - }) - }) - - describe('Tally votes (negative test)', () => { - expect.assertions(1) - it('tallyVotes() should fail as the messages have not been processed yet', async () => { - const pollContractAddress = await maciContract.getPoll(pollId) - try { - await tallyContract.tallyVotes( - pollContractAddress, - mpContract.address, - 0, - [0, 0, 0, 0, 0, 0, 0, 0], - ) - } catch (e) { - const error = "'PROCESSING_NOT_COMPLETE()'" - expect(e.message.endsWith(error)).toBeTruthy() - } - - }) - }) - - describe('Process messages (negative test)', () => { - it('processMessages() should fail if the state AQ has not been merged', async () => { - try { - const pollContractAddress = await maciContract.getPoll(pollId) - - // Submit the proof - await mpContract.processMessages( - pollContractAddress, - 0, - [0, 0, 0, 0, 0, 0, 0, 0], - ) - - } catch (e) { - expect(e.message.endsWith("'STATE_AQ_NOT_MERGED()'")).toBeTruthy() - } - }) - }) - - describe('Merge sign-ups as the Poll', () => { - let pollContract - - beforeAll(async () => { - const pollContractAddress = await maciContract.getPoll(pollId) - pollContract = new ethers.Contract( - pollContractAddress, - pollAbi, - signer, - ) - }) - - it('The Poll should be able to merge the signUp AccQueue', async () => { - let tx = await pollContract.mergeMaciStateAqSubRoots( - 0, - pollId, - { gasLimit: 3000000 }, - ) - let receipt = await tx.wait() - expect(receipt.status).toEqual(1) - - tx = await pollContract.mergeMaciStateAq( - pollId, - { gasLimit: 3000000 }, - ) - receipt = await tx.wait() - expect(receipt.status).toEqual(1) - - maciState.stateAq.mergeSubRoots(0) - maciState.stateAq.merge(STATE_TREE_DEPTH) - }) - - it('the state root must be correct', async () => { - const onChainStateRoot = await stateAqContract.getMainRoot(STATE_TREE_DEPTH) - expect(onChainStateRoot.toString()).toEqual(maciState.stateAq.mainRoots[STATE_TREE_DEPTH].toString()) - }) - }) - - describe('Process messages', () => { - let pollContract - let poll - let generatedInputs - - beforeAll(async () => { - const pollContractAddress = await maciContract.getPoll(pollId) - pollContract = new ethers.Contract( - pollContractAddress, - pollAbi, - signer, - ) - - poll = maciState.polls[pollId] - generatedInputs = poll.processMessages(pollId) - }) - - it('genProcessMessagesPackedVals() should generate the correct value', async () => { - const packedVals = MaciState.packProcessMessageSmallVals( - maxValues.maxVoteOptions, - users.length, - 0, - poll.messages.length, - ) - const onChainPackedVals = BigInt( - await mpContract.genProcessMessagesPackedVals( - pollContract.address, - 0, - users.length, - ) - ) - expect(packedVals.toString(16)).toEqual(onChainPackedVals.toString(16)) - }) - - it('processMessages() should update the state and ballot root commitment', async () => { - const pollContractAddress = await maciContract.getPoll(pollId) - - // Submit the proof - const tx = await mpContract.processMessages( - pollContractAddress, - generatedInputs.newSbCommitment, - [0, 0, 0, 0, 0, 0, 0, 0], - ) - - const receipt = await tx.wait() - expect(receipt.status).toEqual(1) - - const processingComplete = await mpContract.processingComplete() - expect(processingComplete).toBeTruthy() - - const onChainNewSbCommitment = await mpContract.sbCommitment() - expect(generatedInputs.newSbCommitment).toEqual(onChainNewSbCommitment.toString()) - }) - }) - - describe('Tally votes', () => { - let pollContract - - beforeAll(async () => { - const pollContractAddress = await maciContract.getPoll(pollId) - pollContract = new ethers.Contract( - pollContractAddress, - pollAbi, - signer, - ) - }) - - it('genTallyVotesPackedVals() should generate the correct value', async () => { - const onChainPackedVals = BigInt( - await tallyContract.genTallyVotesPackedVals( - users.length, - 0, - tallyBatchSize, - ) - ) - const packedVals = MaciState.packTallyVotesSmallVals( - 0, - tallyBatchSize, - users.length - ) - expect(onChainPackedVals.toString()).toEqual(packedVals.toString()) - }) - - it('tallyVotes() should update the tally commitment', async () => { - expect.assertions(3) - const poll = maciState.polls[pollId] - const generatedInputs = poll.tallyVotes(pollId) - - const pollContractAddress = await maciContract.getPoll(pollId) - const tx = await tallyContract.tallyVotes( - pollContractAddress, - mpContract.address, - generatedInputs.newTallyCommitment, - [0, 0, 0, 0, 0, 0, 0, 0], - ) - - const receipt = await tx.wait() - expect(receipt.status).toEqual(1) - - const onChainNewTallyCommitment = await tallyContract.tallyCommitment() - - expect(generatedInputs.newTallyCommitment).toEqual(onChainNewTallyCommitment.toString()) - - try { - await tallyContract.tallyVotes( - pollContractAddress, - mpContract.address, - generatedInputs.newTallyCommitment, - [0, 0, 0, 0, 0, 0, 0, 0], - ) - } catch (e) { - const error = "'ALL_BALLOTS_TALLIED()'" - expect(e.message.endsWith(error)).toBeTruthy() - } - }) - }) - - describe('Generate MaciState from contract', () => { - it('Should regenerate MaciState from on-chain information', async () => { - const ms = await genMaciStateFromContract( - signer.provider, - maciContract.address, - coordinator, - 0, - ) - // TODO: check roots - }) - }) -}) + let maciContract; + let stateAqContract; + let vkRegistryContract; + let mpContract; + let tallyContract; + let pollId: number; + + describe('Deployment', () => { + beforeAll(async () => { + signer = await getDefaultSigner(); + + const latestBlock = await signer.provider.getBlock('latest'); + const signUpDeadline = latestBlock.timestamp + signUpDuration; + + const r = await deployTestContracts( + initialVoiceCreditBalance, + signUpDeadline, + deactivationPeriod + ); + maciContract = r.maciContract; + stateAqContract = r.stateAqContract; + vkRegistryContract = r.vkRegistryContract; + mpContract = r.mpContract; + tallyContract = r.tallyContract; + }); + + it('MACI.stateTreeDepth should be correct', async () => { + const std = await maciContract.stateTreeDepth(); + expect(std.toString()).toEqual(STATE_TREE_DEPTH.toString()); + }); + }); + + describe('Signups', () => { + it('should sign up users', async () => { + expect.assertions(users.length * 2); + const iface = maciContract.interface; + + let i = 0; + for (const user of users) { + const tx = await maciContract.signUp( + user.pubKey.asContractParam(), + ethers.utils.defaultAbiCoder.encode(['uint256'], [1]), + ethers.utils.defaultAbiCoder.encode(['uint256'], [0]), + signUpTxOpts + ); + const receipt = await tx.wait(); + expect(receipt.status).toEqual(1); + console.log('signUp() gas used:', receipt.gasUsed.toString()); + + // Store the state index + const event = iface.parseLog(receipt.logs[receipt.logs.length - 1]); + expect(event.args._stateIndex.toString()).toEqual((i + 1).toString()); + + maciState.signUp( + user.pubKey, + BigInt(event.args._voiceCreditBalance.toString()), + BigInt(event.args._timestamp.toString()) + ); + + i++; + } + }); + + it('signUp() should fail when given an invalid pubkey', async () => { + expect.assertions(1); + try { + await maciContract.signUp( + { + x: '21888242871839275222246405745257275088548364400416034343698204186575808495617', + y: '0', + }, + ethers.utils.defaultAbiCoder.encode(['uint256'], [1]), + ethers.utils.defaultAbiCoder.encode(['uint256'], [0]), + signUpTxOpts + ); + } catch (e) { + const error = + "'MACI: _pubKey values should be less than the snark scalar field'"; + expect(e.message.endsWith(error)).toBeTruthy(); + } + }); + + it('signUp() should fail when signUpDeadline has passed', async () => { + await timeTravel(signer.provider, signUpDuration); + + try { + await maciContract.signUp( + users[0].pubKey.asContractParam(), + ethers.utils.defaultAbiCoder.encode(['uint256'], [1]), + ethers.utils.defaultAbiCoder.encode(['uint256'], [0]), + signUpTxOpts + ); + } catch (e) { + const error = "'MACI: the sign-up period has passed'"; + expect(e.message.endsWith(error)).toBeTruthy(); + } + }); + }); + + describe('Merging sign-ups should fail because of onlyPoll', () => { + it('coordinator should not be able to merge the signUp AccQueue', async () => { + try { + await maciContract.mergeStateAqSubRoots(0, 0, { gasLimit: 3000000 }); + } catch (e) { + const error = "'MACI: only a Poll contract can call this function'"; + expect(e.message.endsWith(error)).toBeTruthy(); + } + + try { + await maciContract.mergeStateAq(0, { gasLimit: 3000000 }); + } catch (e) { + const error = "'MACI: only a Poll contract can call this function'"; + expect(e.message.endsWith(error)).toBeTruthy(); + } + }); + }); + + describe('Deploy a Poll', () => { + let deployTime; + it('should set VKs and deploy a poll', async () => { + const std = await maciContract.stateTreeDepth(); + + // Set VKs + console.log('Setting VKs'); + let tx = await vkRegistryContract.setVerifyingKeys( + std.toString(), + treeDepths.intStateTreeDepth, + treeDepths.messageTreeDepth, + treeDepths.voteOptionTreeDepth, + messageBatchSize, + testProcessVk.asContractParam(), + testProcessDeactivationVk.asContractParam(), + testTallyVk.asContractParam() + ); + let receipt = await tx.wait(); + expect(receipt.status).toEqual(1); + + const pSig = await vkRegistryContract.genProcessVkSig( + std.toString(), + treeDepths.messageTreeDepth, + treeDepths.voteOptionTreeDepth, + messageBatchSize + ); + + expect(pSig.toString()).toEqual( + genProcessVkSig( + std, + treeDepths.messageTreeDepth, + treeDepths.voteOptionTreeDepth, + messageBatchSize + ).toString() + ); + + const isPSigSet = await vkRegistryContract.isProcessVkSet(pSig); + expect(isPSigSet).toBeTruthy(); + + const tSig = await vkRegistryContract.genTallyVkSig( + std.toString(), + treeDepths.intStateTreeDepth, + treeDepths.voteOptionTreeDepth + ); + const isTSigSet = await vkRegistryContract.isTallyVkSet(tSig); + expect(isTSigSet).toBeTruthy(); + + // Check that the VKs are set + const processVkOnChain = await vkRegistryContract.getProcessVk( + std, + treeDepths.messageTreeDepth, + treeDepths.voteOptionTreeDepth, + messageBatchSize + ); + + const tallyVkOnChain = await vkRegistryContract.getTallyVk( + std.toString(), + treeDepths.intStateTreeDepth, + treeDepths.voteOptionTreeDepth + ); + + compareVks(testProcessVk, processVkOnChain); + compareVks(testTallyVk, tallyVkOnChain); + + // Create the poll and get the poll ID from the tx event logs + tx = await maciContract.deployPoll( + duration, + maxValues, + treeDepths, + coordinator.pubKey.asContractParam() + ); + receipt = await tx.wait(); + + const block = await signer.provider.getBlock(receipt.blockHash); + deployTime = block.timestamp; + + console.log('deployPoll() gas used:', receipt.gasUsed.toString()); + + expect(receipt.status).toEqual(1); + const iface = maciContract.interface; + const event = iface.parseLog(receipt.logs[receipt.logs.length - 1]); + pollId = event.args._pollId; + + const p = maciState.deployPoll( + duration, + BigInt(deployTime + duration), + maxValues, + treeDepths, + messageBatchSize, + coordinator + ); + expect(p.toString()).toEqual(pollId.toString()); + + // publish the NOTHING_UP_MY_SLEEVE message + const messageData = [NOTHING_UP_MY_SLEEVE, BigInt(0)]; + for (let i = 2; i < 10; i++) { + messageData.push(BigInt(0)); + } + const message = new Message( + BigInt(MessageTypes.VOTE_MESSAGE), + messageData + ); + const padKey = new PubKey([ + BigInt( + '10457101036533406547632367118273992217979173478358440826365724437999023779287' + ), + BigInt( + '19824078218392094440610104313265183977899662750282163392862422243483260492317' + ), + ]); + maciState.polls[pollId].publishMessage(message, padKey); + }); + + it('should fail when attempting to init twice a Poll', async () => { + const pollContractAddress = await maciContract.getPoll(pollId); + const pollContract = new ethers.Contract( + pollContractAddress, + pollAbi, + signer + ); + + try { + await pollContract.init(); + } catch (error) { + expect(error).not.toBe(null); + } + }); + + it('should set correct storage values', async () => { + // Retrieve the Poll state and check that each value is correct + const pollContractAddress = await maciContract.getPoll(pollId); + const pollContract = new ethers.Contract( + pollContractAddress, + pollAbi, + signer + ); + + const dd = await pollContract.getDeployTimeAndDuration(); + + expect(Number(dd[0])).toEqual(deployTime); + expect(Number(dd[1])).toEqual(duration); + + expect(await pollContract.stateAqMerged()).toBeFalsy(); + + const sb = await pollContract.currentSbCommitment(); + expect(sb.toString()).toEqual('0'); + + const sm = await pollContract.numSignUpsAndMessagesAndDeactivatedKeys(); + // There are 3 signups via the MACI instance + expect(Number(sm[0])).toEqual(3); + + // There are 1 messages until a user publishes a message + // As we enqueue the NOTHING_UP_MY_SLEEVE hash + expect(Number(sm[1])).toEqual(1); + + const onChainMaxValues = await pollContract.maxValues(); + + expect(Number(onChainMaxValues.maxMessages)).toEqual( + maxValues.maxMessages + ); + expect(Number(onChainMaxValues.maxVoteOptions)).toEqual( + maxValues.maxVoteOptions + ); + + const onChainTreeDepths = await pollContract.treeDepths(); + expect(Number(onChainTreeDepths.intStateTreeDepth)).toEqual( + treeDepths.intStateTreeDepth + ); + expect(Number(onChainTreeDepths.messageTreeDepth)).toEqual( + treeDepths.messageTreeDepth + ); + expect(Number(onChainTreeDepths.messageTreeSubDepth)).toEqual( + treeDepths.messageTreeSubDepth + ); + expect(Number(onChainTreeDepths.voteOptionTreeDepth)).toEqual( + treeDepths.voteOptionTreeDepth + ); + + const onChainBatchSizes = await pollContract.batchSizes(); + expect(Number(onChainBatchSizes.messageBatchSize)).toEqual( + messageBatchSize + ); + expect(Number(onChainBatchSizes.tallyBatchSize)).toEqual(tallyBatchSize); + }); + }); + + describe('Publish messages (vote + key-change)', () => { + let pollContract; + + beforeAll(async () => { + const pollContractAddress = await maciContract.getPoll(pollId); + pollContract = new ethers.Contract(pollContractAddress, pollAbi, signer); + }); + + it('should publish a message to the Poll contract', async () => { + const keypair = new Keypair(); + + const command = new PCommand( + BigInt(1), + keypair.pubKey, + BigInt(0), + BigInt(9), + BigInt(1), + BigInt(pollId), + BigInt(0) + ); + + const signature = command.sign(keypair.privKey); + const sharedKey = Keypair.genEcdhSharedKey( + keypair.privKey, + coordinator.pubKey + ); + const message = command.encrypt(signature, sharedKey); + const tx = await pollContract.publishMessage( + message.asContractParam(), + keypair.pubKey.asContractParam() + ); + const receipt = await tx.wait(); + console.log('publishMessage() gas used:', receipt.gasUsed.toString()); + expect(receipt.status).toEqual(1); + + maciState.polls[pollId].publishMessage(message, keypair.pubKey); + }); + + it('shold not publish a message after the voting period', async () => { + expect.assertions(1); + const dd = await pollContract.getDeployTimeAndDuration(); + await timeTravel(signer.provider, Number(dd[0]) + 1); + + const keypair = new Keypair(); + const command = new PCommand( + BigInt(0), + keypair.pubKey, + BigInt(0), + BigInt(0), + BigInt(0), + BigInt(pollId), + BigInt(0) + ); + + const signature = command.sign(keypair.privKey); + const sharedKey = Keypair.genEcdhSharedKey( + keypair.privKey, + coordinator.pubKey + ); + const message = command.encrypt(signature, sharedKey); + try { + await pollContract.publishMessage( + message.asContractParam(), + keypair.pubKey.asContractParam(), + { gasLimit: 300000 } + ); + } catch (e) { + const error = 'PollE01'; + expect( + e.message.slice(0, e.message.length - 1).endsWith(error) + ).toBeTruthy(); + } + }); + }); + + describe('Merge messages', () => { + let pollContract; + let messageAqContract; + + beforeEach(async () => { + const pollContractAddress = await maciContract.getPoll(pollId); + pollContract = new ethers.Contract(pollContractAddress, pollAbi, signer); + + const extContracts = await pollContract.extContracts(); + + const messageAqAddress = extContracts.messageAq; + messageAqContract = new ethers.Contract( + messageAqAddress, + accQueueQuinaryMaciAbi, + signer + ); + }); + + it('should revert if subtrees are not merged for StateAq', async () => { + try { + await pollContract.mergeMaciStateAq(0, { gasLimit: 4000000 }); + } catch (e) { + const error = 'PollE06'; + expect( + e.message.slice(0, e.message.length - 1).endsWith(error) + ).toBeTruthy(); + } + }); + + it('coordinator should be able to merge the message AccQueue', async () => { + let tx = await pollContract.mergeMessageAqSubRoots(0, { + gasLimit: 3000000, + }); + let receipt = await tx.wait(); + expect(receipt.status).toEqual(1); + console.log( + 'mergeMessageAqSubRoots() gas used:', + receipt.gasUsed.toString() + ); + + tx = await pollContract.mergeMessageAq({ gasLimit: 4000000 }); + receipt = await tx.wait(); + expect(receipt.status).toEqual(1); + + const poll = maciState.polls[pollId]; + poll.messageAq.mergeSubRoots(0); + poll.messageAq.merge(MESSAGE_TREE_DEPTH); + + console.log('mergeMessageAq() gas used:', receipt.gasUsed.toString()); + }); + + it('the message root must be correct', async () => { + const onChainMessageRoot = await messageAqContract.getMainRoot( + MESSAGE_TREE_DEPTH + ); + expect(onChainMessageRoot.toString()).toEqual( + maciState.polls[pollId].messageAq.mainRoots[ + MESSAGE_TREE_DEPTH + ].toString() + ); + }); + }); + + describe('Tally votes (negative test)', () => { + expect.assertions(1); + it('tallyVotes() should fail as the messages have not been processed yet', async () => { + const pollContractAddress = await maciContract.getPoll(pollId); + try { + await tallyContract.tallyVotes( + pollContractAddress, + mpContract.address, + 0, + [0, 0, 0, 0, 0, 0, 0, 0] + ); + } catch (e) { + const error = "'PROCESSING_NOT_COMPLETE()'"; + expect(e.message.endsWith(error)).toBeTruthy(); + } + }); + }); + + describe('Process messages (negative test)', () => { + // TODO: Skipped temporarely - will be addressed as part of milestone 3 as more changes are expected here. + // Skipped as the tree is merged and negative test does not make sense at this point. + it.skip('processMessages() should fail if the state AQ has not been merged', async () => { + try { + const pollContractAddress = await maciContract.getPoll(pollId); + + // Submit the proof + await mpContract.processMessages( + pollContractAddress, + 0, + [0, 0, 0, 0, 0, 0, 0, 0] + ); + } catch (e) { + expect(e.message.endsWith("'STATE_AQ_NOT_MERGED()'")).toBeTruthy(); + } + }); + }); + + describe('Merge sign-ups as the Poll', () => { + let pollContract; + + beforeAll(async () => { + const pollContractAddress = await maciContract.getPoll(pollId); + pollContract = new ethers.Contract(pollContractAddress, pollAbi, signer); + }); + + // TODO: Skipped temporarely - will be addressed as part of milestone 3 as more changes are expected here. + // Currently skipped as the tree is already merged as part of deactivation process. + // in Milestone 3 we need to extend the test to make sure pollContract.mergeMaciStateAqSubRoots + // can be called in case deactivation hasn't happened + it.skip('The Poll should be able to merge the signUp AccQueue', async () => { + let tx = await pollContract.mergeMaciStateAqSubRoots(0, pollId, { + gasLimit: 3000000, + }); + let receipt = await tx.wait(); + expect(receipt.status).toEqual(1); + + tx = await pollContract.mergeMaciStateAq(pollId, { gasLimit: 3000000 }); + receipt = await tx.wait(); + expect(receipt.status).toEqual(1); + + maciState.stateAq.mergeSubRoots(0); + maciState.stateAq.merge(STATE_TREE_DEPTH); + }); + + // TODO: Cannot read properties of undefined (reading 'toString') maciState.stateAq.mainRoots[STATE_TREE_DEPTH].toString() + it('the state root must be correct', async () => { + maciState.stateAq.mergeSubRoots(0); + maciState.stateAq.merge(STATE_TREE_DEPTH); + + const onChainStateRoot = await stateAqContract.getMainRoot( + STATE_TREE_DEPTH + ); + expect(onChainStateRoot.toString()).toEqual( + maciState.stateAq.mainRoots[STATE_TREE_DEPTH].toString() + ); + }); + }); + + describe('Process messages', () => { + let pollContract; + let poll; + let generatedInputs; + + beforeAll(async () => { + const pollContractAddress = await maciContract.getPoll(pollId); + pollContract = new ethers.Contract(pollContractAddress, pollAbi, signer); + + poll = maciState.polls[pollId]; + generatedInputs = poll.processMessages(pollId); + }); + + it('genProcessMessagesPackedVals() should generate the correct value', async () => { + const packedVals = MaciState.packProcessMessageSmallVals( + maxValues.maxVoteOptions, + users.length, + 0, + poll.messages.length + ); + const onChainPackedVals = BigInt( + await mpContract.genProcessMessagesPackedVals( + pollContract.address, + 0, + users.length + ) + ); + expect(packedVals.toString(16)).toEqual(onChainPackedVals.toString(16)); + }); + + // TODO: VM Exception while processing transaction: reverted with custom error 'NO_MORE_MESSAGES()' + it('processMessages() should update the state and ballot root commitment', async () => { + const pollContractAddress = await maciContract.getPoll(pollId); + + // Submit the proof + const tx = await mpContract.processMessages( + pollContractAddress, + generatedInputs.newSbCommitment, + [0, 0, 0, 0, 0, 0, 0, 0] + ); + + const receipt = await tx.wait(); + expect(receipt.status).toEqual(1); + + const processingComplete = await mpContract.processingComplete(); + expect(processingComplete).toBeTruthy(); + + const onChainNewSbCommitment = await mpContract.sbCommitment(); + expect(generatedInputs.newSbCommitment).toEqual( + onChainNewSbCommitment.toString() + ); + }); + }); + + describe('Tally votes', () => { + let pollContract; + + beforeEach(async () => { + const pollContractAddress = await maciContract.getPoll(pollId); + pollContract = new ethers.Contract(pollContractAddress, pollAbi, signer); + }); + + it('genTallyVotesPackedVals() should generate the correct value', async () => { + const onChainPackedVals = BigInt( + await tallyContract.genTallyVotesPackedVals( + users.length, + 0, + tallyBatchSize + ) + ); + const packedVals = MaciState.packTallyVotesSmallVals( + 0, + tallyBatchSize, + users.length + ); + expect(onChainPackedVals.toString()).toEqual(packedVals.toString()); + }); + + it('tallyVotes() should update the tally commitment', async () => { + expect.assertions(3); + const poll = maciState.polls[pollId]; + const generatedInputs = poll.tallyVotes(pollId); + + const pollContractAddress = await maciContract.getPoll(pollId); + const tx = await tallyContract.tallyVotes( + pollContractAddress, + mpContract.address, + generatedInputs.newTallyCommitment, + [0, 0, 0, 0, 0, 0, 0, 0] + ); + + const receipt = await tx.wait(); + expect(receipt.status).toEqual(1); + + const onChainNewTallyCommitment = await tallyContract.tallyCommitment(); + + expect(generatedInputs.newTallyCommitment).toEqual( + onChainNewTallyCommitment.toString() + ); + + try { + await tallyContract.tallyVotes( + pollContractAddress, + mpContract.address, + generatedInputs.newTallyCommitment, + [0, 0, 0, 0, 0, 0, 0, 0] + ); + } catch (e) { + const error = "'ALL_BALLOTS_TALLIED()'"; + expect(e.message.endsWith(error)).toBeTruthy(); + } + }); + }); + + describe('Generate MaciState from contract', () => { + // TODO: Skipped temporarely - will be addressed as part of milestone 3 as more changes are expected here. + /* TODO: assert(received) + + Expected value to be equal to: + true + Received: + false + + 326 | + 327 | public merge(_depth: number) { + > 328 | assert(this.subTreesMerged === true) + */ + it.skip('Should regenerate MaciState from on-chain information', async () => { + const ms = await genMaciStateFromContract( + signer.provider, + maciContract.address, + coordinator, + 0 + ); + // TODO: check roots + }); + }); + + describe('Key deactivation', () => { + let keypair; + let pollContract; + let deactivationInstancePollId; + let deactivationMessage; + let deactivationMessageHash; + let ecdsaSignature; + let otherAccount; + let messageAqContract; + let deactivatedKeysAqContract; + let mockElGamalMessage; + + beforeAll(async () => { + const [defaultSigner, otherSigner] = await ethers.getSigners(); + // sanity check + expect(defaultSigner.address).toEqual(signer.address); + otherAccount = otherSigner; + + const tx = await maciContract.deployPoll( + duration, + maxValues, + treeDepths, + coordinator.pubKey.asContractParam() + ); + + const receipt = await tx.wait(); + const iface = maciContract.interface; + const event = iface.parseLog(receipt.logs[receipt.logs.length - 1]); + deactivationInstancePollId = event.args._pollId; + // sanity check + expect(Number(deactivationInstancePollId)).toEqual(Number(pollId) + 1); + + const pollContractAddress = await maciContract.getPoll( + deactivationInstancePollId + ); + pollContract = new ethers.Contract(pollContractAddress, pollAbi, signer); + + keypair = new Keypair(); + const command = new PCommand( + BigInt(0), + keypair.pubKey, + BigInt(0), + BigInt(0), + BigInt(0), + BigInt(pollId), + BigInt(0) + ); + + const signature = command.sign(keypair.privKey); + const sharedKey = Keypair.genEcdhSharedKey( + keypair.privKey, + coordinator.pubKey + ); + + deactivationMessage = command.encrypt(signature, sharedKey); + + const encodedDeactivationMessage = ethers.utils.defaultAbiCoder.encode( + ['uint256', 'uint256[]'], + [ + deactivationMessage.asContractParam().msgType, + deactivationMessage.asContractParam().data, + ] + ); + + deactivationMessageHash = ethers.utils.solidityKeccak256( + ['bytes'], + [encodedDeactivationMessage] + ); + + ecdsaSignature = await signer.signMessage( + ethers.utils.arrayify(deactivationMessageHash) + ); + + const extContracts = await pollContract.extContracts(); + + const messageAqAddress = extContracts.messageAq; + const deactivatedKeysAqAddress = extContracts.deactivatedKeysAq; + + messageAqContract = new ethers.Contract( + messageAqAddress, + accQueueQuinaryMaciAbi, + signer + ); + + deactivatedKeysAqContract = new ethers.Contract( + deactivatedKeysAqAddress, + accQueueQuinaryMaciAbi, + signer + ); + + let mockElGamalMessageData = []; + for (let i = 0; i < 10; i++) { + mockElGamalMessageData.push(BigInt(0)); + } + mockElGamalMessage = new Message( + BigInt(MessageTypes.DEACTIVATE_KEY), + mockElGamalMessageData + ); + }); + + it('deactivateKey() should revert if the sender is invalid', async () => { + try { + await pollContract + .connect(otherAccount) + .deactivateKey( + deactivationMessage.asContractParam(), + keypair.pubKey.asContractParam() + ); + } catch (e) { + const error = 'PollE07'; // ERROR_INVALID_SENDER + expect( + e.message.slice(0, e.message.length - 1).endsWith(error) + ).toBeTruthy(); + } + }); + + it('deactivateKey() should update relevant storage variables and emit a proper event', async () => { + const [, numMessagesBefore] = + await pollContract.numSignUpsAndMessagesAndDeactivatedKeys(); + const numLeavesBefore = await messageAqContract.numLeaves(); + + const tx = await pollContract.deactivateKey( + deactivationMessage.asContractParam(), + keypair.pubKey.asContractParam() + ); + + const receipt = await tx.wait(); + + expect(receipt.events[0].event).toEqual('PublishMessage'); + expect(receipt.events[1].event).toEqual('AttemptKeyDeactivation'); + + const [, numMessagesAfter] = + await pollContract.numSignUpsAndMessagesAndDeactivatedKeys(); + const numLeavesAfter = await messageAqContract.numLeaves(); + + expect(Number(numMessagesAfter)).toEqual(Number(numMessagesBefore) + 1); + expect(Number(numLeavesAfter)).toEqual(Number(numLeavesBefore) + 1); + }); + + /** + * TODO: Uncomment this test once the isWithinDeactivationPeriod modifier is put back in + */ + // it('deactivateKey() should revert if it is not within the voting deadline', async () => { + // const ONE_SECOND = 1; + // await timeTravel(signer.provider, Number(duration) + ONE_SECOND); + + // try { + // await pollContract.deactivateKey( + // deactivationMessage.asContractParam(), + // keypair.pubKey.asContractParam() + // ); + // } catch (e) { + // const error = 'PollE01'; // ERROR_VOTING_PERIOD_PASSED + // expect( + // e.message.slice(0, e.message.length - 1).endsWith(error) + // ).toBeTruthy(); + // } + // }); + + // TODO: Skipped temporarely - will be addressed as part of milestone 3 as more changes are expected here. + // onlyOwner fails here because the caller is MessageProcessor and not Maci + it.skip('confirmDeactivation() should revert if not called by an owner', async () => { + try { + await pollContract + .connect(otherAccount) + .confirmDeactivation(0, 0, mockElGamalMessage); + } catch (e) { + const error = 'Ownable: caller is not the owner'; + expect( + e.message.slice(0, e.message.length - 1).endsWith(error) + ).toBeTruthy(); + } + }); + // TODO: Skipped temporarely - will be addressed as part of milestone 3 as more changes are expected here. + // TODO: invalid value for array (argument="value", value=0, code=INVALID_ARGUMENT, version=contracts/5.5.0) + it.skip('confirmDeactivation() should update relevant storage variables and emit a proper event', async () => { + const subRoot = 0; + const subTreeCapacity = 0; + + const [, , numDeactivatedKeysBefore] = + await pollContract.numSignUpsAndMessagesAndDeactivatedKeys(); + + const tx = await pollContract.confirmDeactivation( + subRoot, + subTreeCapacity, + mockElGamalMessage + ); + + const receipt = await tx.wait(); + + expect(receipt.events[0].event).toEqual('DeactivateKey'); + + const [, , numDeactivatedKeysAfter] = + await pollContract.numSignUpsAndMessagesAndDeactivatedKeys(); + + expect(Number(numDeactivatedKeysAfter)).toEqual( + Number(numDeactivatedKeysBefore) + subTreeCapacity + ); + }); + }); +}); diff --git a/contracts/ts/__tests__/MACI_overflow.test.ts b/contracts/ts/__tests__/MACI_overflow.test.ts index 2d84e9718b..bb70073adc 100644 --- a/contracts/ts/__tests__/MACI_overflow.test.ts +++ b/contracts/ts/__tests__/MACI_overflow.test.ts @@ -1,87 +1,86 @@ -jest.setTimeout(90000) -import * as ethers from 'ethers' -import { parseArtifact, getDefaultSigner } from '../deploy' -import { deployTestContracts } from '../utils' -import { - Keypair, -} from 'maci-domainobjs' - -import { - MaxValues, - TreeDepths, -} from 'maci-core' - -const coordinator = new Keypair() -const users = [ - new Keypair(), - new Keypair(), - new Keypair(), -] - -const STATE_TREE_DEPTH = 10 -const STATE_TREE_ARITY = 5 -const MESSAGE_TREE_DEPTH = 4 -const MESSAGE_TREE_SUBDEPTH = 2 +jest.setTimeout(90000); +import * as ethers from 'ethers'; +import { parseArtifact, getDefaultSigner } from '../deploy'; +import { deployTestContracts } from '../utils'; +import { Keypair } from 'maci-domainobjs'; + +import { MaxValues, TreeDepths } from 'maci-core'; + +const coordinator = new Keypair(); +const users = [new Keypair(), new Keypair(), new Keypair()]; + +const STATE_TREE_DEPTH = 10; +const STATE_TREE_ARITY = 5; +const MESSAGE_TREE_DEPTH = 4; +const MESSAGE_TREE_SUBDEPTH = 2; // Poll parameters -const duration = 15 +const duration = 15; const maxValues: MaxValues = { - maxUsers: 25, - maxMessages: 25, - maxVoteOptions: 25, -} + maxUsers: 25, + maxMessages: 25, + maxVoteOptions: 25, +}; const treeDepths: TreeDepths = { - intStateTreeDepth: 1, - messageTreeDepth: MESSAGE_TREE_DEPTH, - messageTreeSubDepth: MESSAGE_TREE_SUBDEPTH, - voteOptionTreeDepth: 2, -} - -const initialVoiceCreditBalance = 100 -let signer: ethers.Signer -const [ pollAbi ] = parseArtifact('Poll') + intStateTreeDepth: 1, + messageTreeDepth: MESSAGE_TREE_DEPTH, + messageTreeSubDepth: MESSAGE_TREE_SUBDEPTH, + voteOptionTreeDepth: 2, +}; + +const initialVoiceCreditBalance = 100; +const signUpDuration = 100; +const deactivationPeriod = 10; +let signer: ethers.Signer; +const [pollAbi] = parseArtifact('Poll'); describe('Overflow testing', () => { - let maciContract: ethers.Contract - let stateAqContract: ethers.Contract - let vkRegistryContract: ethers.Contract - let mpContract: ethers.Contract - let pollId: number - beforeEach(async () => { - signer = await getDefaultSigner() - const r = await deployTestContracts( - initialVoiceCreditBalance, - ) - maciContract = r.maciContract - stateAqContract = r.stateAqContract - vkRegistryContract = r.vkRegistryContract - mpContract = r.mpContract - }) - - it('MACI.stateTreeDepth should be correct', async () => { - const std = await maciContract.stateTreeDepth() - expect(std.toString()).toEqual(STATE_TREE_DEPTH.toString()) - }) - it('SignUps - should not overflow', async () => { - await maciContract.signUp( - users[0].pubKey.asContractParam(), - ethers.utils.defaultAbiCoder.encode(['uint256'], [1]), - ethers.utils.defaultAbiCoder.encode(['uint256'], [0]), - ) - }) - - it('Deploy Poll - should not overflow', async () => { - await maciContract.deployPoll( - duration, - maxValues, - treeDepths, - coordinator.pubKey.asContractParam() - ) - }) - - it('Deploy Poll - should not overflow with larger values for tree depths', async () => { - /* + let maciContract: ethers.Contract; + let stateAqContract: ethers.Contract; + let vkRegistryContract: ethers.Contract; + let mpContract: ethers.Contract; + let pollId: number; + beforeEach(async () => { + signer = await getDefaultSigner(); + + const latestBlock = await signer.provider.getBlock('latest'); + const signUpDeadline = latestBlock.timestamp + signUpDuration; + + const r = await deployTestContracts( + initialVoiceCreditBalance, + signUpDeadline, + deactivationPeriod + ); + maciContract = r.maciContract; + stateAqContract = r.stateAqContract; + vkRegistryContract = r.vkRegistryContract; + mpContract = r.mpContract; + }); + + it('MACI.stateTreeDepth should be correct', async () => { + const std = await maciContract.stateTreeDepth(); + expect(std.toString()).toEqual(STATE_TREE_DEPTH.toString()); + }); + it('SignUps - should not overflow', async () => { + await maciContract.signUp( + users[0].pubKey.asContractParam(), + ethers.utils.defaultAbiCoder.encode(['uint256'], [1]), + ethers.utils.defaultAbiCoder.encode(['uint256'], [0]) + ); + }); + + it('Deploy Poll - should not overflow', async () => { + await maciContract.deployPoll( + duration, + maxValues, + treeDepths, + coordinator.pubKey.asContractParam() + ); + }); + + it('Deploy Poll - should not overflow with larger values for tree depths', async () => { + /* Poll Contract require( _maxValues.maxMessages <= @@ -121,38 +120,38 @@ describe('Overflow testing', () => { } */ - const _treeDepths: TreeDepths = { - intStateTreeDepth: 2, - messageTreeDepth: 5, - messageTreeSubDepth: 5, - voteOptionTreeDepth: 2, - } - - const _maxValues: MaxValues = { - maxUsers: 25, - maxMessages: 3125, - maxVoteOptions: 25 - } - - // require maxMessages <= treeArity ** messageTreeDepths - // require 25 <= 5 * 5 Ok - // require maxMessages >= messageBatchSize(5)**(5) - // require 3125 >= 3125 - // require _maxValues.maxMessages % _batchSizes.messageBatchSize == 0 - // require 3125 % 3125 - // require _maxValues.maxVoteOptions <= - // treeArity**uint256(_treeDepths.voteOptionTreeDepth) - // require 25 <= 5 ** 2 - // require _maxValues.maxVoteOptions < (2**50) - // require 25 < 2 ** 50 -> 25 < 1125899906842624 - const tx = await maciContract.deployPoll( - duration, - _maxValues, - _treeDepths, - coordinator.pubKey.asContractParam() - ) - - const receipt = await tx.wait() - console.log(`Used ${receipt.gasUsed?.toString()} gas`) - }) -}) + const _treeDepths: TreeDepths = { + intStateTreeDepth: 2, + messageTreeDepth: 5, + messageTreeSubDepth: 5, + voteOptionTreeDepth: 2, + }; + + const _maxValues: MaxValues = { + maxUsers: 25, + maxMessages: 3125, + maxVoteOptions: 25, + }; + + // require maxMessages <= treeArity ** messageTreeDepths + // require 25 <= 5 * 5 Ok + // require maxMessages >= messageBatchSize(5)**(5) + // require 3125 >= 3125 + // require _maxValues.maxMessages % _batchSizes.messageBatchSize == 0 + // require 3125 % 3125 + // require _maxValues.maxVoteOptions <= + // treeArity**uint256(_treeDepths.voteOptionTreeDepth) + // require 25 <= 5 ** 2 + // require _maxValues.maxVoteOptions < (2**50) + // require 25 < 2 ** 50 -> 25 < 1125899906842624 + const tx = await maciContract.deployPoll( + duration, + _maxValues, + _treeDepths, + coordinator.pubKey.asContractParam() + ); + + const receipt = await tx.wait(); + console.log(`Used ${receipt.gasUsed?.toString()} gas`); + }); +}); diff --git a/contracts/ts/__tests__/SignUpGatekeeper.test.ts b/contracts/ts/__tests__/SignUpGatekeeper.test.ts index 2a18dbca99..06d5c1c985 100644 --- a/contracts/ts/__tests__/SignUpGatekeeper.test.ts +++ b/contracts/ts/__tests__/SignUpGatekeeper.test.ts @@ -1,79 +1,92 @@ -jest.setTimeout(90000) -import * as ethers from 'ethers' -import { getDefaultSigner, deploySignupToken, deploySignupTokenGatekeeper, deployFreeForAllSignUpGatekeeper } from '../deploy' -import { deployTestContracts } from '../utils' +jest.setTimeout(90000); +import * as ethers from 'ethers'; import { - Keypair, -} from 'maci-domainobjs' + getDefaultSigner, + deploySignupToken, + deploySignupTokenGatekeeper, + deployFreeForAllSignUpGatekeeper, +} from '../deploy'; +import { deployTestContracts } from '../utils'; +import { Keypair } from 'maci-domainobjs'; -const initialVoiceCreditBalance = 100 +const initialVoiceCreditBalance = 100; +const signUpDuration = 100; +const deactivationPeriod = 10; describe('SignUpGatekeeper', () => { - let signUpToken; - let freeForAllContract; - let signUpTokenGatekeeperContract; + let signUpToken; + let freeForAllContract; + let signUpTokenGatekeeperContract; - beforeAll(async () => { - freeForAllContract = await deployFreeForAllSignUpGatekeeper() - signUpToken = await deploySignupToken() - signUpTokenGatekeeperContract= await deploySignupTokenGatekeeper(signUpToken.address) - }) + beforeAll(async () => { + freeForAllContract = await deployFreeForAllSignUpGatekeeper(); + signUpToken = await deploySignupToken(); + signUpTokenGatekeeperContract = await deploySignupTokenGatekeeper( + signUpToken.address + ); + }); - describe('Deployment', () => { - it('Gatekeepers should be deployed correctly', async () => { - expect(freeForAllContract).toBeDefined() - expect(signUpToken).toBeDefined() - expect(signUpTokenGatekeeperContract).toBeDefined() - }) + describe('Deployment', () => { + it('Gatekeepers should be deployed correctly', async () => { + expect(freeForAllContract).toBeDefined(); + expect(signUpToken).toBeDefined(); + expect(signUpTokenGatekeeperContract).toBeDefined(); + }); - it('SignUpTokenGatekeeper has token address set', async () => { - expect( - await signUpTokenGatekeeperContract.token() - ).toBe(signUpToken.address) - }) - }) + it('SignUpTokenGatekeeper has token address set', async () => { + expect(await signUpTokenGatekeeperContract.token()).toBe( + signUpToken.address + ); + }); + }); - describe('SignUpTokenGatekeeper', () => { - let maciContract - beforeEach(async () => { - freeForAllContract = await deployFreeForAllSignUpGatekeeper() - signUpToken = await deploySignupToken() - signUpTokenGatekeeperContract = await deploySignupTokenGatekeeper(signUpToken.address) - const r = await deployTestContracts( - initialVoiceCreditBalance, - signUpTokenGatekeeperContract - ) + describe('SignUpTokenGatekeeper', () => { + let maciContract; + beforeEach(async () => { + freeForAllContract = await deployFreeForAllSignUpGatekeeper(); + signUpToken = await deploySignupToken(); + signUpTokenGatekeeperContract = await deploySignupTokenGatekeeper( + signUpToken.address + ); + const signer = await getDefaultSigner(); + const latestBlock = await signer.provider.getBlock('latest'); + const signUpDeadline = latestBlock.timestamp + signUpDuration; + const r = await deployTestContracts( + initialVoiceCreditBalance, + signUpDeadline, + deactivationPeriod, + signUpTokenGatekeeperContract + ); - maciContract = r.maciContract - }) + maciContract = r.maciContract; + }); - it('sets MACI instance correctly', async () => { - await signUpTokenGatekeeperContract.setMaciInstance(maciContract.address) + it('sets MACI instance correctly', async () => { + await signUpTokenGatekeeperContract.setMaciInstance(maciContract.address); - expect( - await signUpTokenGatekeeperContract.maci() - ).toBe(maciContract.address) - }) + expect(await signUpTokenGatekeeperContract.maci()).toBe( + maciContract.address + ); + }); - it('Reverts if address provided is not a MACI instance', async () => { - const user = new Keypair() - const signer = await getDefaultSigner() + it('Reverts if address provided is not a MACI instance', async () => { + const user = new Keypair(); + const signer = await getDefaultSigner(); - await signUpToken.giveToken(await signer.address, 0) - + await signUpToken.giveToken(await signer.address, 0); - try { - await maciContract.signUp( - user.pubKey.asContractParam(), - ethers.utils.defaultAbiCoder.encode(['uint256'], [1]), - ethers.utils.defaultAbiCoder.encode(['uint256'], [0]), - { gasLimit: 300000 }, - ) - } catch (e) { - const error = "'SignUpTokenGatekeeper: only specified MACI instance can call this function'" - expect(e.message.endsWith(error)).toBeTruthy() - } - }) - }) + try { + await maciContract.signUp( + user.pubKey.asContractParam(), + ethers.utils.defaultAbiCoder.encode(['uint256'], [1]), + ethers.utils.defaultAbiCoder.encode(['uint256'], [0]), + { gasLimit: 300000 } + ); + } catch (e) { + const error = + "'SignUpTokenGatekeeper: only specified MACI instance can call this function'"; + expect(e.message.endsWith(error)).toBeTruthy(); + } + }); + }); }); - diff --git a/contracts/ts/deploy.ts b/contracts/ts/deploy.ts index bba910a84c..1d3730c8c7 100644 --- a/contracts/ts/deploy.ts +++ b/contracts/ts/deploy.ts @@ -1,379 +1,411 @@ -import * as fs from 'fs' -import * as path from 'path' -const { ethers } = require('hardhat') -import { Contract, ContractFactory } from 'ethers' +import * as fs from 'fs'; +import * as path from 'path'; +const { ethers } = require('hardhat'); +import { Contract, ContractFactory } from 'ethers'; -const abiDir = path.join(__dirname, '..', 'artifacts') -const solDir = path.join(__dirname, '..', 'contracts') +const abiDir = path.join(__dirname, '..', 'artifacts'); +const solDir = path.join(__dirname, '..', 'contracts'); const getDefaultSigner = async () => { - const signers = await ethers.getSigners() - return signers[0] -} + const signers = await ethers.getSigners(); + return signers[0]; +}; const parseArtifact = (filename: string) => { - let filePath = 'contracts/' + let filePath = 'contracts/'; if (filename.includes('Gatekeeper')) { - filePath += 'gatekeepers/' - filePath += `${filename}.sol` + filePath += 'gatekeepers/'; + filePath += `${filename}.sol`; } if (filename.includes('VoiceCredit')) { - filePath += 'initialVoiceCreditProxy/' - filePath += `${filename}.sol` + filePath += 'initialVoiceCreditProxy/'; + filePath += `${filename}.sol`; } if (filename.includes('Verifier')) { - filePath += 'crypto/Verifier.sol/' + filePath += 'crypto/Verifier.sol/'; } if (filename.includes('AccQueue')) { - filePath += 'trees/AccQueue.sol/' + filePath += 'trees/AccQueue.sol/'; } if (filename.includes('Poll') || filename.includes('MessageAq')) { - filePath += 'Poll.sol' + filePath += 'Poll.sol'; } if (!filePath.includes('.sol')) { - filePath += `${filename}.sol` + filePath += `${filename}.sol`; } const contractArtifact = JSON.parse( fs.readFileSync(path.join(abiDir, filePath, `${filename}.json`)).toString() - ) + ); - return [ contractArtifact.abi, contractArtifact.bytecode ] -} + return [contractArtifact.abi, contractArtifact.bytecode]; +}; const getInitialVoiceCreditProxyAbi = () => { - const [ abi ] = parseArtifact('InitialVoiceCreditProxy.abi') - return abi -} + const [abi] = parseArtifact('InitialVoiceCreditProxy.abi'); + return abi; +}; export class JSONRPCDeployer { - provider: any - signer: any - options: any - - constructor(privateKey: string, providerUrl: string, options?: any) { - this.provider = new ethers.providers.JsonRpcProvider(providerUrl) - this.signer = new ethers.Wallet(privateKey, this.provider) - this.options = options - } - - async deploy(abi: any, bytecode: any, ...args): Promise { - const contractInterface = new ethers.utils.Interface( abi ) - const factory = new ethers.ContractFactory(contractInterface, bytecode, this.signer) - return await factory.deploy(...args) - } + provider: any; + signer: any; + options: any; + + constructor(privateKey: string, providerUrl: string, options?: any) { + this.provider = new ethers.providers.JsonRpcProvider(providerUrl); + this.signer = new ethers.Wallet(privateKey, this.provider); + this.options = options; + } + + async deploy(abi: any, bytecode: any, ...args): Promise { + const contractInterface = new ethers.utils.Interface(abi); + const factory = new ethers.ContractFactory( + contractInterface, + bytecode, + this.signer + ); + return await factory.deploy(...args); + } } class HardhatDeployer extends JSONRPCDeployer { - - constructor(privateKey: string, port: number, options?: any) { - const url = `http://localhost:${port}/` - super(privateKey, url, options) - } + constructor(privateKey: string, port: number, options?: any) { + const url = `http://localhost:${port}/`; + super(privateKey, url, options); + } } -const genJsonRpcDeployer = ( - privateKey: string, - url: string, -) => { - - return new JSONRPCDeployer( - privateKey, - url, - ) -} +const genJsonRpcDeployer = (privateKey: string, url: string) => { + return new JSONRPCDeployer(privateKey, url); +}; const log = (msg: string, quiet: boolean) => { - if (!quiet) { - console.log(msg) - } -} + if (!quiet) { + console.log(msg); + } +}; const linkPoseidonLibraries = async ( - solFileToLink: string, - poseidonT3Address: string, - poseidonT4Address: string, - poseidonT5Address: string, - poseidonT6Address: string, - quiet: boolean = false, + solFileToLink: string, + poseidonT3Address: string, + poseidonT4Address: string, + poseidonT5Address: string, + poseidonT6Address: string, + quiet: boolean = false ) => { - const signer = await getDefaultSigner() - - log('Linking Poseidon libraries to ' + solFileToLink, quiet) - const contractFactory = await ethers.getContractFactory( - solFileToLink, - { - signer, - libraries: { - PoseidonT3: poseidonT3Address, - PoseidonT4: poseidonT4Address, - PoseidonT5: poseidonT5Address, - PoseidonT6: poseidonT6Address, - }, + const signer = await getDefaultSigner(); + + log('Linking Poseidon libraries to ' + solFileToLink, quiet); + const contractFactory = await ethers.getContractFactory(solFileToLink, { + signer, + libraries: { + PoseidonT3: poseidonT3Address, + PoseidonT4: poseidonT4Address, + PoseidonT5: poseidonT5Address, + PoseidonT6: poseidonT6Address, }, - ) + }); - return contractFactory -} + return contractFactory; +}; const deployTopupCredit = async (quiet = false) => { - return await deployContract('TopupCredit', quiet) -} + return await deployContract('TopupCredit', quiet); +}; const deployVkRegistry = async (quiet = false) => { - return await deployContract('VkRegistry', quiet) -} + return await deployContract('VkRegistry', quiet); +}; const deployMockVerifier = async (quiet = false) => { - return await deployContract('MockVerifier', quiet) -} + return await deployContract('MockVerifier', quiet); +}; const deployVerifier = async (quiet = false) => { - return await deployContract('Verifier', quiet) -} + return await deployContract('Verifier', quiet); +}; const deployConstantInitialVoiceCreditProxy = async ( - amount: number, - quiet = false + amount: number, + quiet = false ) => { - return await deployContract('ConstantInitialVoiceCreditProxy', quiet, amount.toString()) -} + return await deployContract( + 'ConstantInitialVoiceCreditProxy', + quiet, + amount.toString() + ); +}; const deploySignupToken = async (quiet = false) => { - return await deployContract('SignUpToken', quiet) -} + return await deployContract('SignUpToken', quiet); +}; const deploySignupTokenGatekeeper = async ( - signUpTokenAddress: string, - quiet = false -) => { - return await deployContract('SignUpTokenGatekeeper', quiet, signUpTokenAddress) -} - -const deployFreeForAllSignUpGatekeeper = async ( - quiet = false + signUpTokenAddress: string, + quiet = false ) => { - return await deployContract('FreeForAllGatekeeper', quiet) -} + return await deployContract( + 'SignUpTokenGatekeeper', + quiet, + signUpTokenAddress + ); +}; +const deployFreeForAllSignUpGatekeeper = async (quiet = false) => { + return await deployContract('FreeForAllGatekeeper', quiet); +}; const deployPoseidonContracts = async (quiet = false) => { - const PoseidonT3Contract = await deployContract('PoseidonT3', quiet) - const PoseidonT4Contract = await deployContract('PoseidonT4', quiet) - const PoseidonT5Contract = await deployContract('PoseidonT5', quiet) - const PoseidonT6Contract = await deployContract('PoseidonT6', quiet) - - return { - PoseidonT3Contract, - PoseidonT4Contract, - PoseidonT5Contract, - PoseidonT6Contract, - } -} + const PoseidonT3Contract = await deployContract('PoseidonT3', quiet); + const PoseidonT4Contract = await deployContract('PoseidonT4', quiet); + const PoseidonT5Contract = await deployContract('PoseidonT5', quiet); + const PoseidonT6Contract = await deployContract('PoseidonT6', quiet); + + return { + PoseidonT3Contract, + PoseidonT4Contract, + PoseidonT5Contract, + PoseidonT6Contract, + }; +}; const deployPollFactory = async (quiet = false) => { - return await deployContract('PollFactory', quiet) -} + return await deployContract('PollFactory', quiet); +}; // Deploy a contract given a name and args -const deployContract = async (contractName: string, quiet: boolean = false, ...args: any) : Promise => { - log(`Deploying ${contractName}`, quiet) - const signer = await getDefaultSigner() - const contractFactory = await ethers.getContractFactory(contractName, signer) - const contract: Contract = await contractFactory.deploy(...args, { - maxFeePerGas: await getFeeData['maxFeePerGas'] - }) - - await contract.deployTransaction.wait() - return contract -} +const deployContract = async ( + contractName: string, + quiet: boolean = false, + ...args: any +): Promise => { + log(`Deploying ${contractName}`, quiet); + const signer = await getDefaultSigner(); + const contractFactory = await ethers.getContractFactory(contractName, signer); + const contract: Contract = await contractFactory.deploy(...args, { + maxFeePerGas: await getFeeData['maxFeePerGas'], + }); + + await contract.deployTransaction.wait(); + return contract; +}; // deploy a contract with linked libraries -const deployContractWithLinkedLibraries = async (contractFactory: ContractFactory, name: string, quiet: boolean = false, ...args: any) : Promise => { - log(`Deploying ${name}`, quiet) - const contract = await contractFactory.deploy(...args, { - maxFeePerGas: await getFeeData['maxFeePerGas'] - }) - await contract.deployTransaction.wait() - return contract -} - - -const transferOwnership = async (contract: Contract, newOwner: string, quiet: boolean = false) => { - await (await (contract.transferOwnership(newOwner, { - maxFeePerGas: await getFeeData['maxFeePerGas'], - }))).wait() -} - -const initMaci = async (contract: Contract, quiet: boolean = false, ...args: any) => { - log('Initializing MACI', quiet) - await (await contract.init(...args, { - maxFeePerGas: await getFeeData['maxFeePerGas'] - })).wait() -} +const deployContractWithLinkedLibraries = async ( + contractFactory: ContractFactory, + name: string, + quiet: boolean = false, + ...args: any +): Promise => { + log(`Deploying ${name}`, quiet); + const contract = await contractFactory.deploy(...args, { + maxFeePerGas: await getFeeData['maxFeePerGas'], + }); + await contract.deployTransaction.wait(); + return contract; +}; + +const transferOwnership = async ( + contract: Contract, + newOwner: string, + quiet: boolean = false +) => { + await ( + await contract.transferOwnership(newOwner, { + maxFeePerGas: await getFeeData['maxFeePerGas'], + }) + ).wait(); +}; + +const initMaci = async ( + contract: Contract, + quiet: boolean = false, + ...args: any +) => { + log('Initializing MACI', quiet); + await ( + await contract.init(...args, { + maxFeePerGas: await getFeeData['maxFeePerGas'], + }) + ).wait(); +}; const getFeeData = async (): Promise => { - const signer = await getDefaultSigner() - const fee = await signer.provider.getFeeData() - console.log('fee', fee) - return await signer.provider.getFeeData() -} + const signer = await getDefaultSigner(); + const fee = await signer.provider.getFeeData(); + console.log('fee', fee); + return await signer.provider.getFeeData(); +}; const deployMessageProcessor = async ( - verifierAddress, - poseidonT3Address, - poseidonT4Address, - poseidonT5Address, - poseidonT6Address, - quiet = false - ) => { - // Link Poseidon contracts to MessageProcessor - const mpFactory = await linkPoseidonLibraries( - 'MessageProcessor', - poseidonT3Address, - poseidonT4Address, - poseidonT5Address, - poseidonT6Address, - quiet - ) - const mpContract = await deployContractWithLinkedLibraries( - mpFactory, - 'MessageProcessor', - quiet, - verifierAddress, - ) - return mpContract -} + verifierAddress, + poseidonT3Address, + poseidonT4Address, + poseidonT5Address, + poseidonT6Address, + quiet = false +) => { + // Link Poseidon contracts to MessageProcessor + const mpFactory = await linkPoseidonLibraries( + 'MessageProcessor', + poseidonT3Address, + poseidonT4Address, + poseidonT5Address, + poseidonT6Address, + quiet + ); + const mpContract = await deployContractWithLinkedLibraries( + mpFactory, + 'MessageProcessor', + quiet, + verifierAddress + ); + return mpContract; +}; const deployTally = async ( - verifierAddress, - poseidonT3Address, - poseidonT4Address, - poseidonT5Address, - poseidonT6Address, - quiet = false - ) => { - // Link Poseidon contracts to Tally - const tallyFactory = await linkPoseidonLibraries( - 'Tally', - poseidonT3Address, - poseidonT4Address, - poseidonT5Address, - poseidonT6Address, - quiet - ) - const tallyContract = await deployContractWithLinkedLibraries( - tallyFactory, - 'Tally', - quiet, - verifierAddress, - ) - return tallyContract -} + verifierAddress, + poseidonT3Address, + poseidonT4Address, + poseidonT5Address, + poseidonT6Address, + quiet = false +) => { + // Link Poseidon contracts to Tally + const tallyFactory = await linkPoseidonLibraries( + 'Tally', + poseidonT3Address, + poseidonT4Address, + poseidonT5Address, + poseidonT6Address, + quiet + ); + const tallyContract = await deployContractWithLinkedLibraries( + tallyFactory, + 'Tally', + quiet, + verifierAddress + ); + return tallyContract; +}; const deploySubsidy = async ( - verifierAddress, - poseidonT3Address, - poseidonT4Address, - poseidonT5Address, - poseidonT6Address, - quiet = false - ) => { - // Link Poseidon contracts to Subsidy - const subsidyFactory = await linkPoseidonLibraries( - 'Subsidy', - poseidonT3Address, - poseidonT4Address, - poseidonT5Address, - poseidonT6Address, - quiet - ) - const subsidyContract = await deployContractWithLinkedLibraries( - subsidyFactory, - 'Subsidy', - quiet, - verifierAddress, - ) - return subsidyContract -} + verifierAddress, + poseidonT3Address, + poseidonT4Address, + poseidonT5Address, + poseidonT6Address, + quiet = false +) => { + // Link Poseidon contracts to Subsidy + const subsidyFactory = await linkPoseidonLibraries( + 'Subsidy', + poseidonT3Address, + poseidonT4Address, + poseidonT5Address, + poseidonT6Address, + quiet + ); + const subsidyContract = await deployContractWithLinkedLibraries( + subsidyFactory, + 'Subsidy', + quiet, + verifierAddress + ); + return subsidyContract; +}; const deployMaci = async ( - signUpTokenGatekeeperContractAddress: string, - initialVoiceCreditBalanceAddress: string, - verifierContractAddress: string, - vkRegistryContractAddress: string, - topupCreditContractAddress: string, - quiet = false, + signUpTokenGatekeeperContractAddress: string, + initialVoiceCreditBalanceAddress: string, + verifierContractAddress: string, + vkRegistryContractAddress: string, + topupCreditContractAddress: string, + signUpDeadline: number, + deactivationPeriod: number, + quiet = false ) => { - - const { - PoseidonT3Contract, - PoseidonT4Contract, - PoseidonT5Contract, - PoseidonT6Contract, - } = await deployPoseidonContracts(quiet) - - const poseidonAddrs = [PoseidonT3Contract.address, PoseidonT4Contract.address, PoseidonT5Contract.address, PoseidonT6Contract.address] - - const contractsToLink = ['MACI', 'PollFactory'] - - // Link Poseidon contracts to MACI - const linkedContractFactories = contractsToLink.map(async (contractName: string) => { - return await linkPoseidonLibraries( - contractName, - PoseidonT3Contract.address, - PoseidonT4Contract.address, - PoseidonT5Contract.address, - PoseidonT6Contract.address, - quiet - ) - }) - - const [ - maciContractFactory, - pollFactoryContractFactory, - ] = await Promise.all(linkedContractFactories) - - const pollFactoryContract = await deployContractWithLinkedLibraries( - pollFactoryContractFactory, - 'PollFactory', - quiet - ) - - const maciContract = await deployContractWithLinkedLibraries( - maciContractFactory, - 'MACI', - quiet, - pollFactoryContract.address, - signUpTokenGatekeeperContractAddress, - initialVoiceCreditBalanceAddress - ) - - log('Transferring ownership of PollFactoryContract to MACI', quiet) - await transferOwnership(pollFactoryContract, maciContract.address, quiet) - - await initMaci(maciContract, quiet, vkRegistryContractAddress, topupCreditContractAddress) - - const [ AccQueueQuinaryMaciAbi, ] = parseArtifact('AccQueue') - const stateAqContractAddress = await maciContract.stateAq() - const stateAqContract = new ethers.Contract( - stateAqContractAddress, - AccQueueQuinaryMaciAbi, - await getDefaultSigner(), - ) - - return { - maciContract, - stateAqContract, - pollFactoryContract, - poseidonAddrs, - } -} + const { + PoseidonT3Contract, + PoseidonT4Contract, + PoseidonT5Contract, + PoseidonT6Contract, + } = await deployPoseidonContracts(quiet); + + const poseidonAddrs = [ + PoseidonT3Contract.address, + PoseidonT4Contract.address, + PoseidonT5Contract.address, + PoseidonT6Contract.address, + ]; + + const contractsToLink = ['MACI', 'PollFactory']; + + // Link Poseidon contracts to MACI + const linkedContractFactories = contractsToLink.map( + async (contractName: string) => { + return await linkPoseidonLibraries( + contractName, + PoseidonT3Contract.address, + PoseidonT4Contract.address, + PoseidonT5Contract.address, + PoseidonT6Contract.address, + quiet + ); + } + ); + + const [maciContractFactory, pollFactoryContractFactory] = await Promise.all( + linkedContractFactories + ); + + const pollFactoryContract = await deployContractWithLinkedLibraries( + pollFactoryContractFactory, + 'PollFactory', + quiet + ); + + const maciContract = await deployContractWithLinkedLibraries( + maciContractFactory, + 'MACI', + quiet, + pollFactoryContract.address, + signUpTokenGatekeeperContractAddress, + initialVoiceCreditBalanceAddress, + signUpDeadline, + deactivationPeriod + ); + + log('Transferring ownership of PollFactoryContract to MACI', quiet); + await transferOwnership(pollFactoryContract, maciContract.address, quiet); + + await initMaci( + maciContract, + quiet, + vkRegistryContractAddress, + topupCreditContractAddress + ); + + const [AccQueueQuinaryMaciAbi] = parseArtifact('AccQueue'); + const stateAqContractAddress = await maciContract.stateAq(); + const stateAqContract = new ethers.Contract( + stateAqContractAddress, + AccQueueQuinaryMaciAbi, + await getDefaultSigner() + ); + + return { + maciContract, + stateAqContract, + pollFactoryContract, + poseidonAddrs, + }; +}; const writeContractAddresses = ( maciContractAddress: string, @@ -382,47 +414,44 @@ const writeContractAddresses = ( signUpTokenAddress: string, outputAddressFile: string ) => { - const addresses = { - MaciContract: maciContractAddress, - VkRegistry: vkRegistryContractAddress, - StateAqContract: stateAqContractAddress, - SignUpToken: signUpTokenAddress, - } - - const addressJsonPath = path.join(__dirname, '..', outputAddressFile) - fs.writeFileSync( - addressJsonPath, - JSON.stringify(addresses), - ) - - console.log(addresses) -} + const addresses = { + MaciContract: maciContractAddress, + VkRegistry: vkRegistryContractAddress, + StateAqContract: stateAqContractAddress, + SignUpToken: signUpTokenAddress, + }; + + const addressJsonPath = path.join(__dirname, '..', outputAddressFile); + fs.writeFileSync(addressJsonPath, JSON.stringify(addresses)); + + console.log(addresses); +}; export { - deployContract, - deployContractWithLinkedLibraries, - deployTopupCredit, - deployVkRegistry, - deployMaci, - deployMessageProcessor, - deployTally, - deploySubsidy, - deploySignupToken, - deploySignupTokenGatekeeper, - deployConstantInitialVoiceCreditProxy, - deployFreeForAllSignUpGatekeeper, - deployMockVerifier, - deployVerifier, - deployPollFactory, - genJsonRpcDeployer, - getInitialVoiceCreditProxyAbi, - initMaci, - transferOwnership, - abiDir, - solDir, - parseArtifact, - linkPoseidonLibraries, - deployPoseidonContracts, + deployContract, + deployContractWithLinkedLibraries, + deployTopupCredit, + deployVkRegistry, + deployMaci, + deployMessageProcessor, + deployTally, + deploySubsidy, + deploySignupToken, + deploySignupTokenGatekeeper, + deployConstantInitialVoiceCreditProxy, + deployFreeForAllSignUpGatekeeper, + deployMockVerifier, + deployVerifier, + deployPollFactory, + genJsonRpcDeployer, + getInitialVoiceCreditProxyAbi, + initMaci, + transferOwnership, + abiDir, + solDir, + parseArtifact, + linkPoseidonLibraries, + deployPoseidonContracts, getDefaultSigner, - writeContractAddresses -} + writeContractAddresses, +}; diff --git a/contracts/ts/genMaciState.ts b/contracts/ts/genMaciState.ts index 43ebb16268..2cb348d949 100644 --- a/contracts/ts/genMaciState.ts +++ b/contracts/ts/genMaciState.ts @@ -1,461 +1,514 @@ -import { - Keypair, - PubKey, - Message, -} from 'maci-domainobjs' +import { Keypair, PubKey, Message } from 'maci-domainobjs'; -import { - parseArtifact, -} from './' +import { parseArtifact } from './'; -import { - MaciState, -} from 'maci-core' +import { MaciState } from 'maci-core'; -import * as ethers from 'ethers' -import * as assert from 'assert' +import * as ethers from 'ethers'; +import * as assert from 'assert'; interface Action { - type: string; - data: any; - blockNumber: number; - transactionIndex: number; + type: string; + data: any; + blockNumber: number; + transactionIndex: number; } const genMaciStateFromContract = async ( - provider: ethers.providers.Provider, - address: string, - coordinatorKeypair: Keypair, - pollId: number, - fromBlock: number = 0, + provider: ethers.providers.Provider, + address: string, + coordinatorKeypair: Keypair, + pollId: number, + fromBlock: number = 0 ): Promise => { - pollId = Number(pollId) - // Verify and sort pollIds - assert(pollId >= 0) - - const [ pollContractAbi, ] = parseArtifact('Poll') - const [ maciContractAbi, ] = parseArtifact('MACI') - - const maciContract = new ethers.Contract( - address, - maciContractAbi, - provider, - ) - - const maciIface = new ethers.utils.Interface(maciContractAbi) - const pollIface = new ethers.utils.Interface(pollContractAbi) - - const maciState = new MaciState() - - // Check stateTreeDepth - const stateTreeDepth = await maciContract.stateTreeDepth() - assert(stateTreeDepth === maciState.stateTreeDepth) - - // Fetch event logs - const initLogs = await provider.getLogs({ - ...maciContract.filters.Init(), - fromBlock: fromBlock, - }) - - // init() should only be called up to 1 time - assert( - initLogs.length <= 1, - 'More than 1 init() event detected which should not be possible', - ) - - const signUpLogs = await provider.getLogs({ - ...maciContract.filters.SignUp(), - fromBlock: fromBlock, - }) - - const mergeStateAqSubRootsLogs = await provider.getLogs({ - ...maciContract.filters.MergeStateAqSubRoots(), - fromBlock: fromBlock, - }) - - const mergeStateAqLogs = await provider.getLogs({ - ...maciContract.filters.MergeStateAq(), - fromBlock: fromBlock, - }) - - const deployPollLogs = await provider.getLogs({ - ...maciContract.filters.DeployPoll(), - fromBlock: fromBlock, - }) - - let vkRegistryAddress - - for (const log of initLogs) { - const event = maciIface.parseLog(log) - vkRegistryAddress = event.args._vkRegistry - } - - const actions: Action[] = [] - - for (const log of signUpLogs) { - assert(log != undefined) - const event = maciIface.parseLog(log) - actions.push({ - type: 'SignUp', - // @ts-ignore - blockNumber: log.blockNumber, - // @ts-ignore - transactionIndex: log.transactionIndex, - data: { - stateIndex: Number(event.args._stateIndex), - pubKey: new PubKey( - event.args._userPubKey.map((x) => BigInt(x)), - ), - voiceCreditBalance: Number(event.args._voiceCreditBalance), - timestamp: Number(event.args._timestamp), - } - }) - } - - // TODO: consider removing MergeStateAqSubRoots and MergeStateAq as the - // functions in Poll which call them already have their own events - for (const log of mergeStateAqSubRootsLogs) { - assert(log != undefined) - const event = maciIface.parseLog(log) - const p = Number(event.args._pollId) - - //// Skip in favour of Poll.MergeMaciStateAqSubRoots - //if (p === pollId) { - //continue - //} - - actions.push({ - type: 'MergeStateAqSubRoots', - // @ts-ignore - blockNumber: log.blockNumber, - // @ts-ignore - transactionIndex: log.transactionIndex, - data: { - numSrQueueOps: Number(event.args._numSrQueueOps), - pollId: p, - } - }) - } - - for (const log of mergeStateAqLogs) { - assert(log != undefined) - const event = maciIface.parseLog(log) - const p = Number(event.args._pollId) - - //// Skip in favour of Poll.MergeMaciStateAq - //if (p === pollId) { - //continue - //} - - actions.push({ - type: 'MergeStateAq', - // @ts-ignore - blockNumber: log.blockNumber, - // @ts-ignore - transactionIndex: log.transactionIndex, - data: { - pollId: p, - } - }) - } - - let i = 0 - const foundPollIds: number[] = [] - const pollContractAddresses: string[] = [] - for (const log of deployPollLogs) { - assert(log != undefined) - const event = maciIface.parseLog(log) - const pubKey = new PubKey( - event.args._pubKey.map((x) => BigInt(x.toString())) - ) - - const pollId = Number(event.args._pollId) - assert(pollId === i) - - const pollAddr = event.args._pollAddr - actions.push({ - type: 'DeployPoll', - // @ts-ignore - blockNumber: log.blockNumber, - // @ts-ignore - transactionIndex: log.transactionIndex, - data: { pollId, pollAddr, pubKey } - }) - - foundPollIds.push(Number(pollId)) - pollContractAddresses.push(pollAddr) - i ++ - } - - // Check whether each pollId exists - assert( - foundPollIds.indexOf(Number(pollId)) > -1, - 'Error: the specified pollId does not exist on-chain', - ) - - const pollContractAddress = pollContractAddresses[pollId] - const pollContract = new ethers.Contract( - pollContractAddress, - pollContractAbi, - provider, - ) - - const coordinatorPubKeyOnChain = await pollContract.coordinatorPubKey() - assert(coordinatorPubKeyOnChain[0].toString() === coordinatorKeypair.pubKey.rawPubKey[0].toString()) - assert(coordinatorPubKeyOnChain[1].toString() === coordinatorKeypair.pubKey.rawPubKey[1].toString()) - - const dd = await pollContract.getDeployTimeAndDuration() - const deployTime = Number(dd[0]) - const duration = Number(dd[1]) - const onChainMaxValues = await pollContract.maxValues() - const onChainTreeDepths = await pollContract.treeDepths() - const onChainBatchSizes = await pollContract.batchSizes() - - assert(vkRegistryAddress === await maciContract.vkRegistry()) - - const maxValues = { - maxMessages: Number(onChainMaxValues.maxMessages.toNumber()), - maxVoteOptions: Number(onChainMaxValues.maxVoteOptions.toNumber()), - } - const treeDepths = { - intStateTreeDepth: Number(onChainTreeDepths.intStateTreeDepth), - messageTreeDepth: Number(onChainTreeDepths.messageTreeDepth), - messageTreeSubDepth: Number(onChainTreeDepths.messageTreeSubDepth), - voteOptionTreeDepth: Number(onChainTreeDepths.voteOptionTreeDepth), - } - const batchSizes = { - tallyBatchSize: Number(onChainBatchSizes.tallyBatchSize), - subsidyBatchSize: Number(onChainBatchSizes.subsidyBatchSize), - messageBatchSize: Number(onChainBatchSizes.messageBatchSize), - } - - const publishMessageLogs = await provider.getLogs({ - ...pollContract.filters.PublishMessage(), - fromBlock: fromBlock, - }) - - const topupLogs = await provider.getLogs({ - ...pollContract.filters.TopupMessage(), - fromBlock: fromBlock, - }) - - - const mergeMaciStateAqSubRootsLogs = await provider.getLogs({ - ...pollContract.filters.MergeMaciStateAqSubRoots(), - fromBlock: fromBlock, - }) - - const mergeMaciStateAqLogs = await provider.getLogs({ - ...pollContract.filters.MergeMaciStateAq(), - fromBlock: fromBlock, - }) - - const mergeMessageAqSubRootsLogs = await provider.getLogs({ - ...pollContract.filters.MergeMessageAqSubRoots(), - fromBlock: fromBlock, - }) - - const mergeMessageAqLogs = await provider.getLogs({ - ...pollContract.filters.MergeMessageAq(), - fromBlock: fromBlock, - }) - - for (const log of publishMessageLogs) { - assert(log != undefined) - const event = pollIface.parseLog(log) - - const message = new Message( - BigInt(event.args._message[0]), - event.args._message[1].map((x) => BigInt(x)), - ) - - const encPubKey = new PubKey( - event.args._encPubKey.map((x) => BigInt(x.toString())) - ) - - - actions.push({ - type: 'PublishMessage', - // @ts-ignore - blockNumber: log.blockNumber, - // @ts-ignore - transactionIndex: log.transactionIndex, - data: { - message, - encPubKey, - } - }) - } - - for (const log of topupLogs) { - assert(log != undefined) - const event = pollIface.parseLog(log) - - const message = new Message( - BigInt(event.args._message[0]), - event.args._message[1].map((x) => BigInt(x)), - ) - - actions.push({ - type: 'TopupMessage', - // @ts-ignore - blockNumber: log.blockNumber, - // @ts-ignore - transactionIndex: log.transactionIndex, - data: { - message, - } - }) - } - - for (const log of mergeMaciStateAqSubRootsLogs) { - assert(log != undefined) - const event = pollIface.parseLog(log) - - const numSrQueueOps = Number(event.args._numSrQueueOps) - actions.push({ - type: 'MergeMaciStateAqSubRoots', - // @ts-ignore - blockNumber: log.blockNumber, - // @ts-ignore - transactionIndex: log.transactionIndex, - data: { - numSrQueueOps, - } - }) - } - - for (const log of mergeMaciStateAqLogs) { - assert(log != undefined) - const event = pollIface.parseLog(log) - - const stateRoot = BigInt(event.args._stateRoot) - actions.push({ - type: 'MergeMaciStateAq', - // @ts-ignore - blockNumber: log.blockNumber, - // @ts-ignore - transactionIndex: log.transactionIndex, - data: { stateRoot } - }) - } - - for (const log of mergeMessageAqSubRootsLogs) { - assert(log != undefined) - const event = pollIface.parseLog(log) - - const numSrQueueOps = Number(event.args._numSrQueueOps) - actions.push({ - type: 'MergeMessageAqSubRoots', - // @ts-ignore - blockNumber: log.blockNumber, - // @ts-ignore - transactionIndex: log.transactionIndex, - data: { - numSrQueueOps, - } - }) - } - - for (const log of mergeMessageAqLogs) { - assert(log != undefined) - const event = pollIface.parseLog(log) - - const messageRoot = BigInt(event.args._messageRoot) - actions.push({ - type: 'MergeMessageAq', - // @ts-ignore - blockNumber: log.blockNumber, - // @ts-ignore - transactionIndex: log.transactionIndex, - data: { messageRoot } - }) - } - - // Sort actions - sortActions(actions) - - // Reconstruct MaciState in order - - for (const action of actions) { - if (action['type'] === 'SignUp') { - maciState.signUp( - action.data.pubKey, - action.data.voiceCreditBalance, - action.data.timestamp, - ) - } else if (action['type'] === 'DeployPoll') { - if (action.data.pollId === pollId) { - maciState.deployPoll( - duration, - BigInt(deployTime + duration), - maxValues, - treeDepths, - batchSizes.messageBatchSize, - coordinatorKeypair, - ) - } else { - maciState.deployNullPoll() - } - } else if (action['type'] === 'PublishMessage') { - maciState.polls[pollId].publishMessage( - action.data.message, - action.data.encPubKey, - ) - } else if (action['type'] === 'TopupMessage') { - maciState.polls[pollId].topupMessage( - action.data.message, - ) - } else if (action['type'] === 'MergeMaciStateAqSubRoots') { - maciState.stateAq.mergeSubRoots( - action.data.numSrQueueOps, - ) - } else if (action['type'] === 'MergeMaciStateAq') { - if (pollId == 0) { - maciState.stateAq.merge(stateTreeDepth) - } - } else if (action['type'] === 'MergeMessageAqSubRoots') { - maciState.polls[pollId].messageAq.mergeSubRoots( - action.data.numSrQueueOps, - ) - } else if (action['type'] === 'MergeMessageAq') { - maciState.polls[pollId].messageAq.merge( - treeDepths.messageTreeDepth, - ) - const poll = maciState.polls[pollId] - assert( - poll.messageAq.mainRoots[treeDepths.messageTreeDepth] === - action.data.messageRoot - ) - } - } - - // Set numSignUps - const numSignUpsAndMessages = await pollContract.numSignUpsAndMessages() - - const poll = maciState.polls[pollId] - assert(Number(numSignUpsAndMessages[1]) === poll.messages.length) - - maciState.polls[pollId].numSignUps = Number(numSignUpsAndMessages[0]) - - return maciState -} + pollId = Number(pollId); + // Verify and sort pollIds + assert(pollId >= 0); + + const [pollContractAbi] = parseArtifact('Poll'); + const [maciContractAbi] = parseArtifact('MACI'); + + const maciContract = new ethers.Contract(address, maciContractAbi, provider); + + const maciIface = new ethers.utils.Interface(maciContractAbi); + const pollIface = new ethers.utils.Interface(pollContractAbi); + + const maciState = new MaciState(); + + // Check stateTreeDepth + const stateTreeDepth = await maciContract.stateTreeDepth(); + assert(stateTreeDepth === maciState.stateTreeDepth); + + // Fetch event logs + const initLogs = await provider.getLogs({ + ...maciContract.filters.Init(), + fromBlock: fromBlock, + }); + + // init() should only be called up to 1 time + assert( + initLogs.length <= 1, + 'More than 1 init() event detected which should not be possible' + ); + + const signUpLogs = await provider.getLogs({ + ...maciContract.filters.SignUp(), + fromBlock: fromBlock, + }); + + const mergeStateAqSubRootsLogs = await provider.getLogs({ + ...maciContract.filters.MergeStateAqSubRoots(), + fromBlock: fromBlock, + }); + + const mergeStateAqLogs = await provider.getLogs({ + ...maciContract.filters.MergeStateAq(), + fromBlock: fromBlock, + }); + + const deployPollLogs = await provider.getLogs({ + ...maciContract.filters.DeployPoll(), + fromBlock: fromBlock, + }); + + let vkRegistryAddress; + + for (const log of initLogs) { + const event = maciIface.parseLog(log); + vkRegistryAddress = event.args._vkRegistry; + } + + const actions: Action[] = []; + + for (const log of signUpLogs) { + assert(log != undefined); + const event = maciIface.parseLog(log); + actions.push({ + type: 'SignUp', + // @ts-ignore + blockNumber: log.blockNumber, + // @ts-ignore + transactionIndex: log.transactionIndex, + data: { + stateIndex: Number(event.args._stateIndex), + pubKey: new PubKey(event.args._userPubKey.map((x) => BigInt(x))), + voiceCreditBalance: Number(event.args._voiceCreditBalance), + timestamp: Number(event.args._timestamp), + }, + }); + } + + // TODO: consider removing MergeStateAqSubRoots and MergeStateAq as the + // functions in Poll which call them already have their own events + for (const log of mergeStateAqSubRootsLogs) { + assert(log != undefined); + const event = maciIface.parseLog(log); + const p = Number(event.args._pollId); + + //// Skip in favour of Poll.MergeMaciStateAqSubRoots + //if (p === pollId) { + //continue + //} + + actions.push({ + type: 'MergeStateAqSubRoots', + // @ts-ignore + blockNumber: log.blockNumber, + // @ts-ignore + transactionIndex: log.transactionIndex, + data: { + numSrQueueOps: Number(event.args._numSrQueueOps), + pollId: p, + }, + }); + } + + for (const log of mergeStateAqLogs) { + assert(log != undefined); + const event = maciIface.parseLog(log); + const p = Number(event.args._pollId); + + //// Skip in favour of Poll.MergeMaciStateAq + //if (p === pollId) { + //continue + //} + + actions.push({ + type: 'MergeStateAq', + // @ts-ignore + blockNumber: log.blockNumber, + // @ts-ignore + transactionIndex: log.transactionIndex, + data: { + pollId: p, + }, + }); + } + + let i = 0; + const foundPollIds: number[] = []; + const pollContractAddresses: string[] = []; + for (const log of deployPollLogs) { + assert(log != undefined); + const event = maciIface.parseLog(log); + const pubKey = new PubKey( + event.args._pubKey.map((x) => BigInt(x.toString())) + ); + + const pollId = Number(event.args._pollId); + assert(pollId === i); + + const pollAddr = event.args._pollAddr; + actions.push({ + type: 'DeployPoll', + // @ts-ignore + blockNumber: log.blockNumber, + // @ts-ignore + transactionIndex: log.transactionIndex, + data: { pollId, pollAddr, pubKey }, + }); + + foundPollIds.push(Number(pollId)); + pollContractAddresses.push(pollAddr); + i++; + } + + // Check whether each pollId exists + assert( + foundPollIds.indexOf(Number(pollId)) > -1, + 'Error: the specified pollId does not exist on-chain' + ); + + const pollContractAddress = pollContractAddresses[pollId]; + const pollContract = new ethers.Contract( + pollContractAddress, + pollContractAbi, + provider + ); + + const coordinatorPubKeyOnChain = await pollContract.coordinatorPubKey(); + assert( + coordinatorPubKeyOnChain[0].toString() === + coordinatorKeypair.pubKey.rawPubKey[0].toString() + ); + assert( + coordinatorPubKeyOnChain[1].toString() === + coordinatorKeypair.pubKey.rawPubKey[1].toString() + ); + + const dd = await pollContract.getDeployTimeAndDuration(); + const deployTime = Number(dd[0]); + const duration = Number(dd[1]); + const onChainMaxValues = await pollContract.maxValues(); + const onChainTreeDepths = await pollContract.treeDepths(); + const onChainBatchSizes = await pollContract.batchSizes(); + + assert(vkRegistryAddress === (await maciContract.vkRegistry())); + + const maxValues = { + maxMessages: Number(onChainMaxValues.maxMessages.toNumber()), + maxVoteOptions: Number(onChainMaxValues.maxVoteOptions.toNumber()), + }; + const treeDepths = { + intStateTreeDepth: Number(onChainTreeDepths.intStateTreeDepth), + messageTreeDepth: Number(onChainTreeDepths.messageTreeDepth), + messageTreeSubDepth: Number(onChainTreeDepths.messageTreeSubDepth), + voteOptionTreeDepth: Number(onChainTreeDepths.voteOptionTreeDepth), + }; + const batchSizes = { + tallyBatchSize: Number(onChainBatchSizes.tallyBatchSize), + subsidyBatchSize: Number(onChainBatchSizes.subsidyBatchSize), + messageBatchSize: Number(onChainBatchSizes.messageBatchSize), + }; + + const attemptKeyDeactivationLogs = await provider.getLogs({ + ...pollContract.filters.AttemptKeyDeactivation(), + fromBlock: fromBlock, + }); + + const deactivateKeyLogs = await provider.getLogs({ + ...pollContract.filters.DeactivateKey(), + fromBlock: fromBlock, + }); + + const publishMessageLogs = await provider.getLogs({ + ...pollContract.filters.PublishMessage(), + fromBlock: fromBlock, + }); + + const topupLogs = await provider.getLogs({ + ...pollContract.filters.TopupMessage(), + fromBlock: fromBlock, + }); + + const mergeMaciStateAqSubRootsLogs = await provider.getLogs({ + ...pollContract.filters.MergeMaciStateAqSubRoots(), + fromBlock: fromBlock, + }); + + const mergeMaciStateAqLogs = await provider.getLogs({ + ...pollContract.filters.MergeMaciStateAq(), + fromBlock: fromBlock, + }); + + const mergeMessageAqSubRootsLogs = await provider.getLogs({ + ...pollContract.filters.MergeMessageAqSubRoots(), + fromBlock: fromBlock, + }); + + const mergeMessageAqLogs = await provider.getLogs({ + ...pollContract.filters.MergeMessageAq(), + fromBlock: fromBlock, + }); + + for (const log of publishMessageLogs) { + assert(log != undefined); + const event = pollIface.parseLog(log); + + const message = new Message( + BigInt(event.args._message[0]), + event.args._message[1].map((x) => BigInt(x)) + ); + + const encPubKey = new PubKey( + event.args._encPubKey.map((x) => BigInt(x.toString())) + ); + + actions.push({ + type: 'PublishMessage', + // @ts-ignore + blockNumber: log.blockNumber, + // @ts-ignore + transactionIndex: log.transactionIndex, + data: { + message, + encPubKey, + }, + }); + } + + for (const log of attemptKeyDeactivationLogs) { + assert(log != undefined); + const event = pollIface.parseLog(log); + + const message = new Message( + BigInt(event.args._message[0]), + event.args._message[1].map((x) => BigInt(x)) + ); + + const encPubKey = new PubKey( + event.args._encPubKey.map((x) => BigInt(x.toString())) + ); + + actions.push({ + type: 'AttemptKeyDeactivation', + // @ts-ignore + blockNumber: log.blockNumber, + // @ts-ignore + transactionIndex: log.transactionIndex, + data: { + message, + encPubKey, + }, + }); + } + + for (const log of deactivateKeyLogs) { + assert(log != undefined); + const event = pollIface.parseLog(log); + + actions.push({ + type: 'DeactivateKey', + // @ts-ignore + blockNumber: log.blockNumber, + // @ts-ignore + transactionIndex: log.transactionIndex, + data: { + keyHash: event.args.keyHash, + c1: event.args.c1, + c2: event.args.c2 + }, + }); + } + + for (const log of topupLogs) { + assert(log != undefined); + const event = pollIface.parseLog(log); + + const message = new Message( + BigInt(event.args._message[0]), + event.args._message[1].map((x) => BigInt(x)) + ); + + actions.push({ + type: 'TopupMessage', + // @ts-ignore + blockNumber: log.blockNumber, + // @ts-ignore + transactionIndex: log.transactionIndex, + data: { + message, + }, + }); + } + + for (const log of mergeMaciStateAqSubRootsLogs) { + assert(log != undefined); + const event = pollIface.parseLog(log); + + const numSrQueueOps = Number(event.args._numSrQueueOps); + actions.push({ + type: 'MergeMaciStateAqSubRoots', + // @ts-ignore + blockNumber: log.blockNumber, + // @ts-ignore + transactionIndex: log.transactionIndex, + data: { + numSrQueueOps, + }, + }); + } + + for (const log of mergeMaciStateAqLogs) { + assert(log != undefined); + const event = pollIface.parseLog(log); + + const stateRoot = BigInt(event.args._stateRoot); + actions.push({ + type: 'MergeMaciStateAq', + // @ts-ignore + blockNumber: log.blockNumber, + // @ts-ignore + transactionIndex: log.transactionIndex, + data: { stateRoot }, + }); + } + + for (const log of mergeMessageAqSubRootsLogs) { + assert(log != undefined); + const event = pollIface.parseLog(log); + + const numSrQueueOps = Number(event.args._numSrQueueOps); + actions.push({ + type: 'MergeMessageAqSubRoots', + // @ts-ignore + blockNumber: log.blockNumber, + // @ts-ignore + transactionIndex: log.transactionIndex, + data: { + numSrQueueOps, + }, + }); + } + + for (const log of mergeMessageAqLogs) { + assert(log != undefined); + const event = pollIface.parseLog(log); + + const messageRoot = BigInt(event.args._messageRoot); + actions.push({ + type: 'MergeMessageAq', + // @ts-ignore + blockNumber: log.blockNumber, + // @ts-ignore + transactionIndex: log.transactionIndex, + data: { messageRoot }, + }); + } + + // Sort actions + sortActions(actions); + + // Reconstruct MaciState in order + + for (const action of actions) { + if (action['type'] === 'SignUp') { + maciState.signUp( + action.data.pubKey, + action.data.voiceCreditBalance, + action.data.timestamp + ); + } else if (action['type'] === 'DeployPoll') { + if (action.data.pollId === pollId) { + maciState.deployPoll( + duration, + BigInt(deployTime + duration), + maxValues, + treeDepths, + batchSizes.messageBatchSize, + coordinatorKeypair + ); + } else { + maciState.deployNullPoll(); + } + } else if (action['type'] === 'PublishMessage') { + maciState.polls[pollId].publishMessage( + action.data.message, + action.data.encPubKey + ); + } else if (action['type'] === 'AttemptKeyDeactivation') { + maciState.polls[pollId].deactivateKey( + action.data.message, + action.data.encPubKey + ); + } else if (action['type'] === 'DeactivateKey') { + // TODO: Implement reaction to DeactivateKey contract event as part of subsequent milestones + } else if (action['type'] === 'TopupMessage') { + maciState.polls[pollId].topupMessage(action.data.message); + } else if (action['type'] === 'MergeMaciStateAqSubRoots') { + maciState.stateAq.mergeSubRoots(action.data.numSrQueueOps); + } else if (action['type'] === 'MergeMaciStateAq') { + if (pollId == 0) { + maciState.stateAq.merge(stateTreeDepth); + } + } else if (action['type'] === 'MergeMessageAqSubRoots') { + maciState.polls[pollId].messageAq.mergeSubRoots( + action.data.numSrQueueOps + ); + } else if (action['type'] === 'MergeMessageAq') { + maciState.polls[pollId].messageAq.merge(treeDepths.messageTreeDepth); + const poll = maciState.polls[pollId]; + assert( + poll.messageAq.mainRoots[treeDepths.messageTreeDepth] === + action.data.messageRoot + ); + } + } + + // Set numSignUps + const [numSignUps, numMessages] = + await pollContract.numSignUpsAndMessagesAndDeactivatedKeys(); + + const poll = maciState.polls[pollId]; + assert(Number(numMessages) === poll.messages.length); + + maciState.polls[pollId].numSignUps = Number(numSignUps); + + return maciState; +}; /* * The comparision function for Actions based on block number and transaction * index. */ const sortActions = (actions: Action[]) => { - actions.sort((a, b) => { - if (a.blockNumber > b.blockNumber) { return 1 } - if (a.blockNumber < b.blockNumber) { return -1 } - - if (a.transactionIndex > b.transactionIndex) { return 1 } - if (a.transactionIndex < b.transactionIndex) { return -1 } - return 0 - }) - return actions -} - -export { genMaciStateFromContract } - + actions.sort((a, b) => { + if (a.blockNumber > b.blockNumber) { + return 1; + } + if (a.blockNumber < b.blockNumber) { + return -1; + } + + if (a.transactionIndex > b.transactionIndex) { + return 1; + } + if (a.transactionIndex < b.transactionIndex) { + return -1; + } + return 0; + }); + return actions; +}; + +export { genMaciStateFromContract }; diff --git a/contracts/ts/utils.ts b/contracts/ts/utils.ts index b9e1e747f5..ab5eff8827 100644 --- a/contracts/ts/utils.ts +++ b/contracts/ts/utils.ts @@ -1,80 +1,90 @@ interface SnarkProof { - pi_a: BigInt[]; - pi_b: BigInt[][]; - pi_c: BigInt[]; + pi_a: BigInt[]; + pi_b: BigInt[][]; + pi_c: BigInt[]; } import { - deployVkRegistry, - deployTopupCredit, - deployMaci, - deployMessageProcessor, - deployTally, - deployMockVerifier, - deployContract, - deployFreeForAllSignUpGatekeeper, - deployConstantInitialVoiceCreditProxy, -} from './' + deployVkRegistry, + deployTopupCredit, + deployMaci, + deployMessageProcessor, + deployTally, + deployMockVerifier, + deployContract, + deployFreeForAllSignUpGatekeeper, + deployConstantInitialVoiceCreditProxy, +} from './'; -const formatProofForVerifierContract = ( - _proof: SnarkProof, -) => { - - return ([ - _proof.pi_a[0], - _proof.pi_a[1], +const formatProofForVerifierContract = (_proof: SnarkProof) => { + return [ + _proof.pi_a[0], + _proof.pi_a[1], - _proof.pi_b[0][1], - _proof.pi_b[0][0], - _proof.pi_b[1][1], - _proof.pi_b[1][0], + _proof.pi_b[0][1], + _proof.pi_b[0][0], + _proof.pi_b[1][1], + _proof.pi_b[1][0], - _proof.pi_c[0], - _proof.pi_c[1], - ]).map((x) => x.toString()) -} + _proof.pi_c[0], + _proof.pi_c[1], + ].map((x) => x.toString()); +}; const deployTestContracts = async ( - initialVoiceCreditBalance, - gatekeeperContract? + initialVoiceCreditBalance, + signUpDeadline, + deactivationPeriod, + gatekeeperContract? ) => { - const mockVerifierContract = await deployMockVerifier() + const mockVerifierContract = await deployMockVerifier(); - if (!gatekeeperContract) { - gatekeeperContract = await deployFreeForAllSignUpGatekeeper() - } + if (!gatekeeperContract) { + gatekeeperContract = await deployFreeForAllSignUpGatekeeper(); + } - const constantIntialVoiceCreditProxyContract = await deployConstantInitialVoiceCreditProxy( - initialVoiceCreditBalance, - ) + const constantIntialVoiceCreditProxyContract = + await deployConstantInitialVoiceCreditProxy(initialVoiceCreditBalance); - // VkRegistry - const vkRegistryContract = await deployVkRegistry() - const topupCreditContract = await deployTopupCredit() + // VkRegistry + const vkRegistryContract = await deployVkRegistry(); + const topupCreditContract = await deployTopupCredit(); - const {maciContract,stateAqContract,pollFactoryContract,poseidonAddrs} = await deployMaci( - gatekeeperContract.address, - constantIntialVoiceCreditProxyContract.address, - mockVerifierContract.address, - vkRegistryContract.address, - topupCreditContract.address - ) - const mpContract = await deployMessageProcessor(mockVerifierContract.address, poseidonAddrs[0],poseidonAddrs[1],poseidonAddrs[2],poseidonAddrs[3]) - const tallyContract = await deployTally(mockVerifierContract.address, poseidonAddrs[0],poseidonAddrs[1],poseidonAddrs[2],poseidonAddrs[3]) + const { maciContract, stateAqContract, pollFactoryContract, poseidonAddrs } = + await deployMaci( + gatekeeperContract.address, + constantIntialVoiceCreditProxyContract.address, + mockVerifierContract.address, + vkRegistryContract.address, + topupCreditContract.address, + signUpDeadline, + deactivationPeriod + ); + const mpContract = await deployMessageProcessor( + mockVerifierContract.address, + poseidonAddrs[0], + poseidonAddrs[1], + poseidonAddrs[2], + poseidonAddrs[3] + ); + const tallyContract = await deployTally( + mockVerifierContract.address, + poseidonAddrs[0], + poseidonAddrs[1], + poseidonAddrs[2], + poseidonAddrs[3] + ); - return { - mockVerifierContract, - gatekeeperContract, - constantIntialVoiceCreditProxyContract, - maciContract, - stateAqContract, - vkRegistryContract, - mpContract, - tallyContract, - } -} + return { + mockVerifierContract, + gatekeeperContract, + constantIntialVoiceCreditProxyContract, + maciContract, + stateAqContract, + vkRegistryContract, + mpContract, + tallyContract, + }; +}; -export { - deployTestContracts, - formatProofForVerifierContract, -} +export { deployTestContracts, formatProofForVerifierContract }; diff --git a/core/ts/MaciState.ts b/core/ts/MaciState.ts index 70f2001c10..5c9de170f3 100644 --- a/core/ts/MaciState.ts +++ b/core/ts/MaciState.ts @@ -6,11 +6,14 @@ import { SNARK_FIELD_SIZE, NOTHING_UP_MY_SLEEVE, hashLeftRight, + hash2, hash3, hash5, sha256Hash, stringifyBigInts, Signature, + verifySignature, + elGamalEncryptBit, } from 'maci-crypto' import { PubKey, @@ -21,6 +24,7 @@ import { Message, Keypair, StateLeaf, + DeactivatedKeyLeaf, Ballot, } from 'maci-domainobjs' @@ -44,6 +48,8 @@ interface MaxValues { } const STATE_TREE_DEPTH = 10 +const DEACT_KEYS_TREE_DEPTH = 10 +const DEACT_MESSAGE_INIT_HASH: BigInt = BigInt('8370432830353022751713833565135785980866757267633941821328460903436894336785') // Also see: Polls.sol class Poll { @@ -71,6 +77,7 @@ class Poll { public STATE_TREE_ARITY = 5 public MESSAGE_TREE_ARITY = 5 public VOTE_OPTION_TREE_ARITY = 5 + public DEACT_KEYS_TREE_ARITY = 5 public stateCopied = false public stateLeaves: StateLeaf[] = [blankStateLeaf] @@ -81,6 +88,19 @@ class Poll { hash5, ) + // For key deactivation + public deactivatedKeysChainHash = DEACT_MESSAGE_INIT_HASH + public deactivatedKeysTree = new IncrementalQuinTree( + DEACT_KEYS_TREE_DEPTH, + DEACT_MESSAGE_INIT_HASH, + this.DEACT_KEYS_TREE_ARITY, + hash5, + ) + public deactivationMessages: Message[] = [] + public deactivationEncPubKeys: PubKey[] = [] + public deactivationCommands: PCommand[] = [] + public deactivationSignatures: Signature[] = [] + // For message processing public numBatchesProcessed = 0 public currentMessageBatchIndex @@ -152,6 +172,46 @@ class Poll { this.ballots.push(blankBallot) } + public deactivateKey = ( + _message: Message, + _encPubKey: PubKey, + ) => { + assert(_message.msgType == BigInt(1)) + assert( + _encPubKey.rawPubKey[0] < SNARK_FIELD_SIZE && + _encPubKey.rawPubKey[1] < SNARK_FIELD_SIZE + ) + for (const d of _message.data) { + assert(d < SNARK_FIELD_SIZE) + } + + this.deactivationMessages.push(_message) + + const messageHash = _message.hash(_encPubKey) + + // Update chain hash + this.deactivatedKeysChainHash = hash2([this.deactivatedKeysChainHash, messageHash]) + this.deactivationEncPubKeys.push(_encPubKey) + + // Decrypt the message and store the Command + const sharedKey = Keypair.genEcdhSharedKey( + this.coordinatorKeypair.privKey, + _encPubKey, + ) + + try { + const { command, signature } = PCommand.decrypt(_message, sharedKey) + this.deactivationSignatures.push(signature) + this.deactivationCommands.push(command) + } catch(e) { + //console.log(`error cannot decrypt: ${e.message}`) + const keyPair = new Keypair() + const command = new PCommand(BigInt(1), keyPair.pubKey,BigInt(0),BigInt(0),BigInt(0),BigInt(0),BigInt(0)) + this.deactivationCommands.push(command) + this.deactivationSignatures.push(null) + } + } + private copyStateFromMaci = () => { // Copy the state tree, ballot tree, state leaves, and ballot leaves assert(this.maciStateRef.stateLeaves.length === this.maciStateRef.stateTree.nextIndex) @@ -278,6 +338,160 @@ class Poll { return this.numBatchesProcessed < totalBatches } + /* + * Process key deactivation messages + */ + public processDeactivationMessages = ( + _seed: BigInt + ) => { + const maskingValues = []; + const elGamalEnc = []; + const deactivatedLeaves = []; + + if (!this.stateCopied) { + this.copyStateFromMaci() + } + + let mask: BigInt = _seed; +; + let computedStateIndex = 0; + + for (let i = 0; i < this.deactivationMessages.length; i += 1) { + const deactCommand = this.deactivationCommands[i]; + const deactMessage = this.deactivationMessages[i]; + const deactSignatures = this.deactivationSignatures[i]; + const encPubKey = this.deactivationEncPubKeys[i]; + + const signature = deactSignatures[i]; + + const { + stateIndex, + newPubKey, + voteOptionIndex, + newVoteWeight, + salt, + } = deactCommand; + + const stateIndexInt = parseInt(stateIndex.toString()); + computedStateIndex = stateIndexInt > 0 && stateIndexInt <= this.numSignUps ? stateIndexInt - 1: -1; + + let pubKey: any; + + if (computedStateIndex > -1) { + pubKey = this.stateLeaves[computedStateIndex].pubKey; + } else { + pubKey = new PubKey([BigInt(0), BigInt(0)]); + } + + // Verify deactivation message + const status = deactCommand.cmdType.toString() == '1' // Check message type + && computedStateIndex != -1 + && signature != null + && verifySignature( + deactMessage.hash(encPubKey), + signature, + pubKey.rawPubKey + ) // Check signature + && newPubKey.rawPubKey[0].toString() == '0' + && newPubKey.rawPubKey[1].toString() == '0' + && voteOptionIndex.toString() == '0' + && newVoteWeight.toString() == '0' + + mask = hash2([mask, salt]); + + maskingValues.push(mask); + + const [c1, c2] = elGamalEncryptBit( + this.coordinatorKeypair.pubKey.rawPubKey, + status ? BigInt(1) : BigInt(0), + mask, + ) + + elGamalEnc.push([c1, c2]); + + const deactivatedLeaf = (new DeactivatedKeyLeaf( + pubKey, + c1, + c2, + salt, + )) + + this.deactivatedKeysTree.insert(deactivatedLeaf.hash()) + deactivatedLeaves.push(deactivatedLeaf); + } + + const maxMessages = 5; // TODO: Temp + + // Pad array + for (let i = 1; i < maxMessages; i += 1) { + this.deactivationEncPubKeys.push(new PubKey([BigInt(0), BigInt(0)])) + } + + const deactivatedTreePathElements = []; + for (let i = 0; i < this.deactivationMessages.length; i += 1) { + const merklePath = this.deactivatedKeysTree.genMerklePath(i); + deactivatedTreePathElements.push(merklePath.pathElements); + } + + // Pad array + for (let i = this.deactivationMessages.length; i < maxMessages; i += 1) { + deactivatedTreePathElements.push(this.stateTree.genMerklePath(0).pathElements) + } + + const stateLeafPathElements = [this.stateTree.genMerklePath(computedStateIndex).pathElements]; + // Pad array + for (let i = 1; i < maxMessages; i += 1) { + stateLeafPathElements.push(this.stateTree.genMerklePath(0).pathElements) + } + + const currentStateLeaves = [this.stateLeaves[computedStateIndex].asCircuitInputs()]; + // Pad array + for (let i = 1; i < maxMessages; i += 1) { + currentStateLeaves.push(blankStateLeaf.asCircuitInputs()) + } + + // Pad array + for (let i = this.deactivationMessages.length; i < maxMessages; i += 1) { + const padMask = genRandomSalt(); + const [padc1, padc2] = elGamalEncryptBit( + this.coordinatorKeypair.pubKey.rawPubKey, + BigInt(0), + padMask, + ) + + maskingValues.push(padMask); + elGamalEnc.push([padc1, padc2]); + } + + for (let i = this.deactivationMessages.length; i < maxMessages; i += 1) { + this.deactivationMessages.push(new Message(BigInt(0), Array(10).fill(BigInt(0)))) + } + + const circuitInputs = stringifyBigInts({ + coordPrivKey: this.coordinatorKeypair.privKey.asCircuitInputs(), + coordPubKey: this.coordinatorKeypair.pubKey.rawPubKey, + encPubKeys: this.deactivationEncPubKeys.map(k => k.asCircuitInputs()), + msgs: this.deactivationMessages.map(m => m.asCircuitInputs()), + deactivatedTreePathElements, + stateLeafPathElements: stateLeafPathElements, + currentStateLeaves: currentStateLeaves, + elGamalEnc, + maskingValues, + deactivatedTreeRoot: this.deactivatedKeysTree.root, + currentStateRoot: this.stateTree.root, + numSignUps: this.numSignUps, + chainHash: this.deactivatedKeysChainHash, + inputHash: sha256Hash([ + this.deactivatedKeysTree.root, + this.numSignUps, + this.stateTree.root, + this.deactivatedKeysChainHash, + ]), + }) + + return { circuitInputs, deactivatedLeaves }; + } + /* * Process _batchSize messages starting from the saved index. This * function will process messages even if the number of messages is not an @@ -1549,6 +1763,14 @@ const genProcessVkSig = ( BigInt(_voteOptionTreeDepth) } +const genDeactivationVkSig = ( + _messageQueueSize: number, + _stateTreeDepth: number, +): BigInt => { + return (BigInt(_messageQueueSize) << BigInt(64)) + + BigInt(_stateTreeDepth) +} + const genTallyVkSig = ( _stateTreeDepth: number, _intStateTreeDepth: number, @@ -1596,6 +1818,7 @@ export { TreeDepths, MaciState, Poll, + genDeactivationVkSig, genProcessVkSig, genTallyVkSig, genSubsidyVkSig, diff --git a/crypto/package-lock.json b/crypto/package-lock.json index 025bf42bd8..9dc925f4f8 100644 --- a/crypto/package-lock.json +++ b/crypto/package-lock.json @@ -12333,7 +12333,7 @@ "circomlib": { "version": "git+ssh://git@github.com/weijiekoh/circomlib.git#24ed08eee0bb613b8c0135d66c1013bd9f78d50a", "integrity": "sha512-I2UcS6Ae0+HQkL0ZJdS3DtR4fOyZWxKZQSxJh9yVr2akywI9OrciUBnXZuWTvDDLMKassR49clQgO+6DcFNLsg==", - "from": "circomlib@git://github.com/weijiekoh/circomlib.git#24ed08eee0bb613b8c0135d66c1013bd9f78d50a", + "from": "circomlib@https://github.com/weijiekoh/circomlib.git#24ed08eee0bb613b8c0135d66c1013bd9f78d50a", "requires": { "blake-hash": "^1.1.0", "blake2b": "^2.1.3", diff --git a/crypto/ts/__tests__/Crypto.test.ts b/crypto/ts/__tests__/Crypto.test.ts index 70c185c67c..b5b88446d1 100644 --- a/crypto/ts/__tests__/Crypto.test.ts +++ b/crypto/ts/__tests__/Crypto.test.ts @@ -210,5 +210,23 @@ describe('Cryptographic operations', () => { expect(BigInt(bit)).toEqual(dBit); } }) + + it('Trying to encrypt bit > 1 throws Invalid bit value error', () => { + const { privKey, pubKey } = genKeypair(); + const y = genPrivKey(); + + expect(() => elGamalEncryptBit(pubKey, BigInt(2), y)).toThrow('Invalid bit value') + }) + + // TODO: Test 'Invalid point value' error in elGamalDecryptBit + // it('Trying to decrypt point which is not 0 or G throws Invalid point value error', () => { + // const { privKey, pubKey } = genKeypair(); + // const y = genPrivKey(); + + // const [c1, c2] = elGamalEncryptBit(pubKey, BigInt(0), y); + // const dBit = elGamalDecryptBit(privKey, c1, c2); + + // expect(BigInt(0)).toEqual(dBit); + // }) }) }) diff --git a/crypto/ts/index.ts b/crypto/ts/index.ts index 7378f2f6e4..8ef06d964a 100644 --- a/crypto/ts/index.ts +++ b/crypto/ts/index.ts @@ -255,7 +255,7 @@ const genRandomBabyJubValue = (): BigInt => { /* * @return A BabyJub-compatible private key. */ -const genPrivKey = (): PrivKey => { +const genPrivKey = (): BigInt => { return genRandomBabyJubValue() } @@ -407,10 +407,11 @@ const verifySignature = ( const elGamalEncrypt = ( pubKey: PubKey, m: Point, - y: PrivKey + y: BigInt ): Ciphertext[] => { - const s = babyJub.mulPointEscalar(pubKey, formatPrivKeyForBabyJub(y)) - const c1 = babyJub.mulPointEscalar(babyJub.Base8, formatPrivKeyForBabyJub(y)) + const s = babyJub.mulPointEscalar(pubKey, y) + const c1 = babyJub.mulPointEscalar(babyJub.Base8, y) + const c2 = babyJub.addPoint(m, s) return [c1, c2] } @@ -470,7 +471,7 @@ const curveToBit = ( const elGamalEncryptBit = ( pubKey: PubKey, bit: BigInt, - y: PrivKey, + y: BigInt, ): Ciphertext[] => { const m = bitToCurve(bit) return elGamalEncrypt(pubKey, m, y) @@ -489,6 +490,10 @@ const elGamalDecryptBit = ( return curveToBit(m) } +const babyJubMaxValue = BigInt(babyJub.p) + +const babyJubAddPoint = (a: any, b: any) => babyJub.addPoint(a,b) + export { genRandomSalt, genPrivKey, @@ -531,4 +536,6 @@ export { bigInt2Buffer, packPubKey, unpackPubKey, + babyJubMaxValue, + babyJubAddPoint } diff --git a/docs/README.md b/docs/README.md index a945b03f5c..57c726c311 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,6 +22,8 @@ MACI offers the following guarantees: although a user may cast another vote to nullify it. * **Correct execution**: no-one — not even the trusted coordinator — should be able to produce a false tally of votes. +* **Anonymity**: no-one — not even the trusted coordinator — should be + able to deduce how the user voted. Under the hood, MACI uses Ethereum smart contracts and zero-knowledge proofs. It inherits security and uncensorability from the underlying Ethereum diff --git a/docs/cli.md b/docs/cli.md index 1c9fea50d9..1e65d99468 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -28,7 +28,10 @@ npm run hardhat | Coordinator | Deploy a new poll | `deployPoll`| | Coordinator | Deploy a new poll processor and tallyer | `deployPpt`| | User | Sign up | `signup` | +| User | Deactivate current public key | `deactivateKey` | +| User | Generate new public key based on the deactivated one | `generateNewKey` | | User | Change key / vote | `publish` | +| Coordinator | Confirm user's public key deactivation | `confirmDeactivation` | | Coordinator | Merge state tree | `mergeSignups` | | Coordinator | Merge message tree | `mergeMessages` | | Coordinator | Generate message processing and vote tallying proofs | `genProofs` | diff --git a/docs/contracts.md b/docs/contracts.md index 241891bc8d..dc98788760 100644 --- a/docs/contracts.md +++ b/docs/contracts.md @@ -228,7 +228,7 @@ function mergeMaciStateAqSubRoots(uint256 _numSrQueueOps, uint256 _pollId) } ``` -If the subtrees have not been merged on the MACI contract's `stateAq`, then it will merge it by calling `mergeStateAqSubroots`. It accets two parameters: +If the subtrees have not been merged on the MACI contract's `stateAq`, then it will merge it by calling `mergeStateAqSubroots`. It accepts two parameters: * `_numSrQueueOps` - the number of operations required * `_pollId` - the id of the poll diff --git a/docs/elgamal-api.md b/docs/elgamal-api.md new file mode 100644 index 0000000000..4125d30185 --- /dev/null +++ b/docs/elgamal-api.md @@ -0,0 +1,91 @@ +# ElGamal MACI API Specification + +This API documentation describes the new additions to the MACI project for public key generation and deactivation. These changes include new console commands on the CLI side and new functions in Solidity smart contracts. + +## Table of Contents + +- [CLI Commands](#cli-commands) + - [Deactivate Key](#deactivate-key) + - [Generate New Key](#generate-new-key) +- [Smart Contract Functions](#smart-contract-functions) + - [deactivateKey](#deactivatekey) + - [confirmDeactivation](#confirmdeactivation) + - [generateNewKey](#generatenewkey) + +## CLI Commands + +### Deactivate Key + +The `deactivateKey` command is used to deactivate a user's public key. + +#### Usage + +```sh +node ./build/index.js deactivateKey --pubkey --contract +``` + +#### Arguments + +- `--pubkey`, `-p`: The public key to deactivate. +- `--contract`, `-x`: The address of the MACI contract. + +### Generate New Key + +The `generateNewKey` command is used to generate a new key based on the current one. + +#### Usage + +```sh +node ./build/index.js generateNewKey --proof --message +``` + +#### Arguments + +- `--proof`: The zero-knowledge proof required for the key generation process. +- `--message`: The encrypted message to submit during the key generation process. + +## Smart Contract Functions + +### deactivateKey + +This function attempts to deactivate the User's MACI public key. For deactivation to be confirmed, the Coordinator must call the `confirmKeyDeactivation` function. + +```solidity +function deactivateKey(Message memory _message, bytes32 _messageHash, bytes memory _signature) external; +``` + +#### Parameters + +- `_message`: The encrypted message which contains a state leaf index. +- `_messageHash`: The keccak256 hash of the \_message to be used for signature verification. +- `_signature`: The ECDSA signature of User who attempts to deactivate the MACI public key. + +### confirmDeactivation + +This function confirms the deactivation of a User's MACI public key. It must be called by the Coordinator after the User calls the `deactivateKey` function. + +```solidity +function confirmDeactivation(PubKey memory _usersPubKey, Message memory _elGamalEncryptedMessage) external returns(uint256 leafIndex); +``` + +#### Parameters + +- `_usersPubKey`: The MACI public key to be deactivated. +- `_elGamalEncryptedMessage`: The El Gamal encrypted message. + +#### Return Values + +- `leafIndex`: The index of the leaf in the deactivated keys tree. + +### generateNewKey + +This method generates a new key based on the current one after verifying the zero-knowledge proof. + +```solidity +function generateNewKey(bytes memory _zkProof, Message memory _encryptedMessage) external; +``` + +#### Parameters + +- `_zkProof`: The zero-knowledge proof required for the key generation process. +- `_encryptedMessage`: The encrypted message to submit during the key generation process. diff --git a/docs/elgamal-flow.md b/docs/elgamal-flow.md new file mode 100644 index 0000000000..f0a5cee323 --- /dev/null +++ b/docs/elgamal-flow.md @@ -0,0 +1,299 @@ +# El Gamal Flow (Anonymity in MACI) + +## Introduction + +This document describes the process of voting in MACI protocol with the attention to changes made to obtain voter's anonymity. This document focuses on the technical implementation of the changes to the protocol. For a more mathematical description of the key concepts, see [El Gamal general](elgamal-general.md) document. + +MACI now utilizes El Gamal encryption along with rerandomization to enable key deactivation and generation of new keys without visible connection to the previously deactivated key. Using this protocol upgrade, full anonymity of the voters and their votes is obtained. + +## Updates + +Two new messages are being added: `deactivateKey` and `generateNewKey`. +A new tree is added which represents the tree of deactivated keys. + +## Process + +The upgraded process looks like this: + +1. User’s registration (signUp). This occurs initially (one time) when the user registers their public key. +2. Public key deactivation. There is a deactivation (rerandomization) period within which the user can deactivate their current public key. +3. New key generation. The user registers a new public key based on the old, deactivated one. +4. Voting. The user casts a vote by publishing a message containing their new public key. + +## Signup + +`signUp` in [signUp.ts](../cli/ts/signUp.ts). + +cli’s `signUp` method calls the `signUp` function on the `MACI.sol` contract, and logs retrieved `stateIndex`. +The user sends their public key (two coordinates of the elliptic curve) which is registered in the state tree (new leaf enqueue). Aside from the public key it contains the number of vote credits of a user. + +Contract's `signUp` function creates `StateLeaf` that contains the user’s `pubKey`, `voteCredits`, and registration `timestamp`. It hashes `StateLeaf` using the *Poseidon* hash function where elements of the array are $x$ and $y$ coordinates of the `pubKey`, and the aforementioned `voteCredits` and `timestamp`. + +## Public Key Deactivation using El Gamal encryption + +The user’s public key deactivation takes the following steps. + +### Step 1: The user submits `deactivateKey` message + +`deactivateKey` function in [deactivateKey.ts](../cli/ts/deactivateKey.ts). + +The command being encrypted here takes the arguments: + +- `stateIndex`: State leaf index +- `userMaciPubKey`: Hardcoded for key deactivation + +```javascript +const userMaciPubKey = new PubKey([BigInt(0), BigInt(0)]) +``` + +- `voteOptionIndex`: Set to 0 for key deactivation +- `newVoteWeight`: Set to 0 for key deactivation +- `nonce` +- `pollId`: Id of the poll to which the key deactivation relates +- `salt` + +The reason `userMaciPubKey` is hardcoded in this message is that the command that forms the key deactivation message (`PCommand`), and the circom circuit that processes the messages are the same as in other messages (like publishing a vote or swapping a key), and when sent, it is added to the same message tree (`MessageAq`). Assigning (0, 0) to the `userMaciPubKey` parameter, along with setting `voteOptionIndex` and `newVoteWeight` to 0 effectively identifies these types of messages in the message tree (along with other parameters of the command). + +The command is signed with the user’s MACI private key and encrypted using a generated ECDH shared key (see [shared key generation](primitives.md#shared-key-generation)). + +The message is sent to the `Poll` contract along with the ephemeral public key so that the coordinator can get to the same shared key and decrypt the message. + +```javascript +tx = await pollContractEthers.deactivateKey( + message.asContractParam(), + encKeypair.pubKey.asContractParam(), + { gasLimit: 10000000 }, +) +``` + +### Step 2: Attempt key deactivation + +In the `Poll` smart contract, within `deactivateKey()` function, the received message is hashed and the new leaf is added to the message tree. + +```solidity +uint256 messageLeaf = hashMessageAndEncPubKey( + _message, + _encPubKey +); + +extContracts.messageAq.enqueue(messageLeaf); +``` + +Additionally, we store the incremental hash of each new deactivation message (chain hash update). Pseudocode: + +```javascript +// ​​Message1 +H1 = hash(H0, hash(M1)); + +// Message2 +H2 = hash(H1, hash(M2)); + +//... +``` + +Actual code: + +```solidity +deactivationChainHash = hash2([deactivationChainHash, messageLeaf]); +``` + +Since the deactivation period is different from the voting period, and in order to process deactivation messages, a merge of the message tree needs to be done. This can compromise the merging of the tree upon voting completion. This is why we store this hash of the deactivation messages that is later used to prove the correctness of message processing. + +`PublishMessage` and `AttemptKeyDeactivation` events are emitted. + +### Step 3: Coordinator confirms deactivation + +`confirmDeactivation` in [confirmDeactivation.ts](../cli/ts/confirmDeactivation.ts). + + +The coordinator waits for the deactivation period to expire upon which he collects all `AttemptKeyDeactivation` events and starts to process the deactivation messages, in batches. + +He reconstructs the MACI state using `genMaciStateFromContract()` function which merges the state tree. + +He then calls the `processDeactivationMessages()` function of the [MaciState.ts](../core/ts/MaciState.ts). +Here, for all deactivation messages, the following important things are happening: + +- Verification of the deactivation message + +It verifies the signature, `pubKey` set to `(0,0)`, `voteOptionIndex` and `newVoteWeight` set to 0, etc. This verification renders the status of deactivation. + +- El Gamal encryption of the status + +The deactivation status is encrypted using El Gamal encryption where `[c1, c2]` are ciphertexts of deactivation status: + +```javascript +const [c1, c2] = elGamalEncryptBit( + this.coordinatorKeypair.pubKey.rawPubKey, + status ? BigInt(1) : BigInt(0), + mask, +) +``` + +For details on this encryption, see [El Gamal Encryption](#el-gamal-encryption) section below. + +- Construction of deactivated-keys tree leaf: + +```javascript +const deactivatedLeaf = (new DeactivatedKeyLeaf( + pubKey, + c1, + c2, + salt, +)).hash() +``` + +`processDeactivationMessages()` function returns the deactivated-keys tree leaves along with circuit inputs used for generating proof of correct processing (explained in the next step). + +The coordinator then submits all deactivated-keys tree leaves in batches to the `Poll` smart contract (`confirmDeactivation()` function). On the smart contract, these leaves are added to the deactivated-keys tree: + +```solidity +extContracts.deactivatedKeysAq.insertSubTree(_subRoot); +``` + +`DeactivateKey` event is emitted: + +```solidity +emit DeactivateKey(_subRoot); +``` + +### Step 4: Complete deactivation + +`completeDeactivation` in [completeDeactivation.ts](../cli/ts/completeDeactivation.ts). + +In `completeDeactivation()` function, again, the MACI state is reconstructed and `processDeactivationMessages()` is called to obtain `circuitInputs`.These inputs are required for [processDeactivationMessages.circom](../circuits/circom/processDeactivationMessages.circom) to generate proof of correct processing of deactivation messages. + +The coordinator submits the proof to the `completeDeactivation()` function of the `MessageProcessor` smart contract. On the smart contract, two important things are happening: + +1. Merge of the deactivated-keys tree: + +```solidity +deactivatedKeysAq.mergeSubRoots(_deactivatedKeysNumSrQueueOps); +deactivatedKeysAq.merge(messageTreeDepth); +``` + +2. Verification of the submitted proof + +The verification (partly) relies on the incremental hashing of the (incoming) deactivation messages (deactivation chain hash) - proves the hash of the final message he provided is equal to the stored hash. + +```solidity +uint256 input = genProcessDeactivationMessagesPublicInputHash( + poll, + deactivatedKeysAq.getMainRoot(messageTreeSubDepth), + numSignUps, + maci.getStateAqRoot(), + poll.deactivationChainHash() +); + +require(verifier.verify(_proof, vk, input), "Verification failed"); +``` + +### El Gamal Encryption + +`elGamalEncryptBit` in [index.ts](../crypto/ts/index.ts). + +The encryption is performed within the crypto project, by utilizing the `elGamalEncryptBit` function. This function takes as inputs: + +- `pubKey`: coordinator’s public key +- `bit`: the status of key deactivation that is being encrypted +- `y`: arbitrary value from the interval $(1,p)$ where $p$ is a prime number ($p=$ 21888242871839275222246405745257275088548364400416034343698204186575808495617) + +Firstly, the bit is mapped to a point on the elliptic curve. + +**Important improvement**: Since we only work with two possible values for this bit (deactivation status), we don’t need to rely on the hash-to-curve method, where for any value that needs to be mapped to a curve we need to perform a calculation. We can identify two points that we know for sure exist on the elliptic curve and use these two points to represent the input bit. These two points are + +1. point at infinity +2. generator point ($G$) + +As long as we have a finite number of statuses (bits) we want to represent on the elliptic curve, we can utilize these two points, where, for example, the next point would be $2G$. This reduces computation time. + +The mapping of the bit to a point on the elliptic curve takes place in a `bitToCurve` function, where the bit is mapped to one of the two points, as explained above. + +When the point on the elliptic curve that represents the bit is obtained, the `elGamalEncrypt` function is invoked which takes the following inputs: + +- `pubKey`: coordinator’s public key +- `m`: a point on the elliptic curve (point at infinity or generator point) +- `y`: arbitrary value from the interval $(1,p)$ where $p$ is a prime number + +This function returns `CipherText` in the form of `[c1, c2]` which represents the encrypted deactivation status. It is calculated as follows: + +1. Multiply the generator point ($G$) with the randomness parameter ($y$). This represents $c1$. +2. Multiply $pubKey$ with the randomness parameter ($y$). This is stored as $s$. +3. Add a point on the elliptic curve received as the input ($m$) to the value from the previous step ($s$). This represents $c2$. + +For more information, see [El Gamal general](elgamal-general.md) document. + +For the generation of ZK proofs, there exists an equivalent circom circuit that takes care of the El Gamal Encryption: [elGamalEncryption.circom](../circuits/circom/elGamalEncryption.circom). +The coordinator proves through this ZK circuit that he encrypted the status correctly. + +## New Key Generation + +`generateNewKey` in [generateNewKey.ts](../cli/ts/generateNewKey.ts). + +A new message type is added (type 3). + +The user provides inclusion proof that the deactivated key exists in the tree, rerandomizes `[c1, c2]` to `[c1’, c2’]` (described in the following section), and sends that and a `nullifier` in the `generateNewKey` message. +This is where the connection between the old public key and the new one is lost since the user only provides ZK proof that his old public key exists in this tree without making a particular reference ([verifyDeactivatedKey.circom](../circuits/circom/verifyDeactivatedKey.circom)). +The coordinator checks whether that message is in the tree and whether `[c1, c2]` was encrypted status containing successful or unsuccessful deactivation. + +When processing messages, the coordinator decrypts `[c1’, c2’]` to the original status using `elGamalDecrypt` function. + + +*Note: To be fully documented as part of the next Milestone*. + +### Rerandomization + +`elGamalRerandomize` in [index.ts](../crypto/ts/index.ts). + +Rerandomization is used because we want to use the encrypted message twice but without the message being able to be linked to the user's private key. +Specifically, on confirming deactivation, the `elGamalEncryptedMessage` can be connected to the user’s public key. Since the user needs to provide the key deactivation status (encrypted) when generating a new key, this encrypted message must not be the same as the one created when the user was submitting the key to deactivation. + +The user needs to prove that encrypted deactivation status `[c1, c2]` exists in the deactivated-keys tree, but publicly releases a rerandomized version of this status, [c1’, c2’]. + +That function randomizes an existing cipher text (`[c1, c2]`) such that it’s still decryptable using the original public key it was encrypted for (`coordinatorPubKey`). + +For the generation of ZK proofs, there exists an equivalent circom circuit that takes care of the rerandomization: [elGamalRerandomize.circom](../circuits/circom/elGamalRerandomize.circom). + +Rerandomization function takes as inputs: + +- `z`: arbitrary value from the interval $(1,p)$ where $p$ is a prime number +- `pubKey`: coordinator's public key +- `[c1, c2]`: `CipherText` that represents the encrypted deactivation status + +This function returns `CipherText` in the form of `[c1’, c2’]` that represents the rerandomized encrypted deactivation status. It is calculated as follows: + +1. Multiply generator point ($G$) with the randomness parameter ($z$) and add $c1$. This represents `c1`. +2. Multiply $pubKey$ with the randomness parameter ($z$) and add $c2$. This represents `c2’`. + +### Nullifiers + +In order not to be able to create new key multiple times based on the old, deactivated one, the user must also send a nullifier in the message, which is a hash of the old private key. Nullifiers are stored in a sparse merkle tree and the coordinator checks whether the passed nullifier has already been seen - if so, he rejects it (non-inclusion proof). + + +*Note: To be fully documented as part of the next Milestone*. + +## Voting + +`publish` in [publish.ts](../cli/ts/publish.ts). + +On voting action, the message is published. + +The user signs the command with their private key and encrypts the command by passing their signature and ECDH shared key. + +When the message is sent, it triggers a `publishMessage()` function on the `Poll` smart contract. The message is recorded in the message tree. + +It hashes the message and public using the *Poseidon* hash function and adds the leaf to the message tree. + +Upon the voting completion, the coordinator collects the root hash of the state tree and message tree ([mergeSignups.ts](../cli/ts/mergeSignups.ts) and [mergeMessages.ts](../cli/ts/mergeMessages.ts)) and processes messages one at a time, in batches. + +They call the `Poll` smart contract functions: + +- `mergeMaciStateAqSubRoots()` and `mergeMaciStateAq()` for the state tree +- `mergeMessageAqSubRoots()` and `mergeMessageAq()` for the message tree + +These functions call the accumulator queue (`AccQueue.sol`) that exposes two functions: `mergeSubRoots()` and `merge()` which calculates the merkle root of all the leaves. + +The coordinator provides a ZK proof that the messages are processed correctly and that the intermediary state is an input for the next step (batch). +The coordinator collects events that are emitted when the message is published and reconstructs them, collects root hashes of state and message trees, processes them in batches, and outputs the result (along with the proof) - the result is the tally of the votes. + +In [genProofs.ts](../cli/ts/genProofs.ts) the MACI state is reconstructed (`genMaciStateFromContract()`), messages are processed, and proofs are generated and stored in a proof_file. +Then, in [proveOnChain.ts](../cli/ts/proveOnChain.ts) the coordinator sumbits proofs (saved proof_file) and commitment (the new state root and ballot root commitment after all messages are processed) from the previous step by calling `processMessages()` from `MessageProcessor.sol` and `tallyVotes()` from `TallyVotes.sol`. diff --git a/docs/elgamal-general.md b/docs/elgamal-general.md index a095db241e..ed7f24737a 100644 --- a/docs/elgamal-general.md +++ b/docs/elgamal-general.md @@ -6,9 +6,9 @@ In basic MACI protocol, a coordinator is the only point in the system which can ### El Gamal encryption In 1985, Taher Elgamal created an asymmetric cryptographic algorithm based on the Diffie-Hellman key exchange. Similar to the ECDH algorithm, the security of ElGamal is based on the difficult or practically unfeasable computation of the discrete logarithm. -Let $G$ be a generator point on the elliptic curve over a finite field $F_p$ (where $p$ is a a large prime number). Let $pr_A$ and $pr_B$ be the private keys of participants $A$ and $B$. Then $pub_A = a * G$ and $pub_B = b * G$ are their respective public keys. Let person $A$ be the participant who will encrypt the data fot the participant B. Participant $A$ performs to following algorithm: +Let $G$ be a generator point on the elliptic curve over a finite field $F_p$ (where $p$ is a a large prime number). Let $pr_A$ and $pr_B$ be the private keys of participants $A$ and $B$. Then $pub_A = a * G$ and $pub_B = b * G$ are their respective public keys. Let person $A$ be the participant who will encrypt the data for the participant B. Participant $A$ performs the following algorithm: -1) choose an arbitrary $k$ from the interval $(1,p)$; +1) choose an arbitrary $y$ from the interval $(1,p)$; 2) calculate $c_1: = y * G$; diff --git a/docs/installation.md b/docs/installation.md index fa6ac216f9..f28c801a61 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -94,7 +94,7 @@ MACI has two zk-SNARK circuits. Each circuit is parameterised. There should one Unless you wish to generate a fresh set of `.zkey` files, you should obtain them from someone who has performed a multi-party trusted setup for said -circuits.. +circuits. Note the locations of the `.zkey` files as the CLI requires them as command-line flags. @@ -110,7 +110,7 @@ Next, run the following to compile the circuits with parameters you specified: npx zkey-manager compile -c zkeys.config.yml ``` -Next, download the `.ptau` file: +Next, download the `.ptau` file. It can take some time as the file is not small. ```bash npx zkey-manager downloadPtau -c zkeys.config.yml diff --git a/domainobjs/ts/index.ts b/domainobjs/ts/index.ts index aa1586cc0e..c2dec1061b 100644 --- a/domainobjs/ts/index.ts +++ b/domainobjs/ts/index.ts @@ -1,5 +1,5 @@ -import * as assert from 'assert' -import base64url from "base64url" +import * as assert from 'assert'; +import base64url from 'base64url'; import { Ciphertext, EcdhSharedKey, @@ -13,6 +13,7 @@ import { sign, hashLeftRight, hash13, + hash3, hash4, hash5, verifySignature, @@ -27,421 +28,370 @@ import { SNARK_FIELD_SIZE, } from 'maci-crypto' -const SERIALIZED_PRIV_KEY_PREFIX = 'macisk.' +const SERIALIZED_PRIV_KEY_PREFIX = 'macisk.'; class VerifyingKey { - public alpha1: G1Point - public beta2: G2Point - public gamma2: G2Point - public delta2: G2Point - public ic: G1Point[] - - constructor ( - _alpha1: G1Point, - _beta2: G2Point, - _gamma2: G2Point, - _delta2: G2Point, - _ic: G1Point[], - ) { - this.alpha1 = _alpha1 - this.beta2 = _beta2 - this.gamma2 = _gamma2 - this.delta2 = _delta2 - this.ic = _ic - } - - public asContractParam() { - return { - alpha1: this.alpha1.asContractParam(), - beta2: this.beta2.asContractParam(), - gamma2: this.gamma2.asContractParam(), - delta2: this.delta2.asContractParam(), - ic: this.ic.map((x) => x.asContractParam()), - } - } - - public static fromContract(data: any): VerifyingKey { - const convertG2 = (point: any): G2Point => { - return new G2Point( - [ - BigInt(point.x[0]), - BigInt(point.x[1]), - ], - [ - BigInt(point.y[0]), - BigInt(point.y[1]), - ], - ) - } - - return new VerifyingKey( - new G1Point( - BigInt(data.alpha1.x), - BigInt(data.alpha1.y), - ), - convertG2(data.beta2), - convertG2(data.gamma2), - convertG2(data.delta2), - data.ic.map( - (c: any) => new G1Point(BigInt(c.x), BigInt(c.y)) - ), - ) - } - - public equals(vk: VerifyingKey): boolean { - let icEqual = this.ic.length === vk.ic.length - - // Immediately return false if the length doesn't match - if (!icEqual) { - return false - } - - // Each element in ic must match - for (let i = 0; i < this.ic.length; i ++) { - icEqual = icEqual && this.ic[i].equals(vk.ic[i]) - } - - return this.alpha1.equals(vk.alpha1) && - this.beta2.equals(vk.beta2) && - this.gamma2.equals(vk.gamma2) && - this.delta2.equals(vk.delta2) && - icEqual - } - - public copy(): VerifyingKey { - const copyG2 = (point: any): G2Point => { - return new G2Point( - [ - BigInt(point.x[0].toString()), - BigInt(point.x[1].toString()), - ], - [ - BigInt(point.y[0].toString()), - BigInt(point.y[1].toString()), - ], - ) - } - - return new VerifyingKey( - new G1Point( - BigInt(this.alpha1.x.toString()), - BigInt(this.alpha1.y.toString()), - ), - copyG2(this.beta2), - copyG2(this.gamma2), - copyG2(this.delta2), - this.ic.map( - (c: any) => new G1Point(BigInt(c.x.toString()), BigInt(c.y.toString())) - ), - ) - } - - public static fromJSON = (j: string): VerifyingKey => { - const data = JSON.parse(j) - return VerifyingKey.fromObj(data) - } - - public static fromObj = (data: any): VerifyingKey => { - const alpha1 = new G1Point( - BigInt(data.vk_alpha_1[0]), - BigInt(data.vk_alpha_1[1]), - ) - const beta2 = new G2Point( - [ - BigInt(data.vk_beta_2[0][1]), - BigInt(data.vk_beta_2[0][0]), - ], - [ - BigInt(data.vk_beta_2[1][1]), - BigInt(data.vk_beta_2[1][0]), - ], - ) - const gamma2 = new G2Point( - [ - BigInt(data.vk_gamma_2[0][1]), - BigInt(data.vk_gamma_2[0][0]), - ], - [ - BigInt(data.vk_gamma_2[1][1]), - BigInt(data.vk_gamma_2[1][0]), - ], - ) - const delta2 = new G2Point( - [ - BigInt(data.vk_delta_2[0][1]), - BigInt(data.vk_delta_2[0][0]), - ], - [ - BigInt(data.vk_delta_2[1][1]), - BigInt(data.vk_delta_2[1][0]), - ], - ) - const ic = data.IC.map((ic) => new G1Point( - BigInt(ic[0]), - BigInt(ic[1]), - )) - - return new VerifyingKey(alpha1, beta2, gamma2, delta2, ic) - } + public alpha1: G1Point; + public beta2: G2Point; + public gamma2: G2Point; + public delta2: G2Point; + public ic: G1Point[]; + + constructor( + _alpha1: G1Point, + _beta2: G2Point, + _gamma2: G2Point, + _delta2: G2Point, + _ic: G1Point[] + ) { + this.alpha1 = _alpha1; + this.beta2 = _beta2; + this.gamma2 = _gamma2; + this.delta2 = _delta2; + this.ic = _ic; + } + + public asContractParam() { + return { + alpha1: this.alpha1.asContractParam(), + beta2: this.beta2.asContractParam(), + gamma2: this.gamma2.asContractParam(), + delta2: this.delta2.asContractParam(), + ic: this.ic.map((x) => x.asContractParam()), + }; + } + + public static fromContract(data: any): VerifyingKey { + const convertG2 = (point: any): G2Point => { + return new G2Point( + [BigInt(point.x[0]), BigInt(point.x[1])], + [BigInt(point.y[0]), BigInt(point.y[1])] + ); + }; + + return new VerifyingKey( + new G1Point(BigInt(data.alpha1.x), BigInt(data.alpha1.y)), + convertG2(data.beta2), + convertG2(data.gamma2), + convertG2(data.delta2), + data.ic.map((c: any) => new G1Point(BigInt(c.x), BigInt(c.y))) + ); + } + + public equals(vk: VerifyingKey): boolean { + let icEqual = this.ic.length === vk.ic.length; + + // Immediately return false if the length doesn't match + if (!icEqual) { + return false; + } + + // Each element in ic must match + for (let i = 0; i < this.ic.length; i++) { + icEqual = icEqual && this.ic[i].equals(vk.ic[i]); + } + + return ( + this.alpha1.equals(vk.alpha1) && + this.beta2.equals(vk.beta2) && + this.gamma2.equals(vk.gamma2) && + this.delta2.equals(vk.delta2) && + icEqual + ); + } + + public copy(): VerifyingKey { + const copyG2 = (point: any): G2Point => { + return new G2Point( + [BigInt(point.x[0].toString()), BigInt(point.x[1].toString())], + [BigInt(point.y[0].toString()), BigInt(point.y[1].toString())] + ); + }; + + return new VerifyingKey( + new G1Point( + BigInt(this.alpha1.x.toString()), + BigInt(this.alpha1.y.toString()) + ), + copyG2(this.beta2), + copyG2(this.gamma2), + copyG2(this.delta2), + this.ic.map( + (c: any) => new G1Point(BigInt(c.x.toString()), BigInt(c.y.toString())) + ) + ); + } + + public static fromJSON = (j: string): VerifyingKey => { + const data = JSON.parse(j); + return VerifyingKey.fromObj(data); + }; + + public static fromObj = (data: any): VerifyingKey => { + const alpha1 = new G1Point( + BigInt(data.vk_alpha_1[0]), + BigInt(data.vk_alpha_1[1]) + ); + const beta2 = new G2Point( + [BigInt(data.vk_beta_2[0][1]), BigInt(data.vk_beta_2[0][0])], + [BigInt(data.vk_beta_2[1][1]), BigInt(data.vk_beta_2[1][0])] + ); + const gamma2 = new G2Point( + [BigInt(data.vk_gamma_2[0][1]), BigInt(data.vk_gamma_2[0][0])], + [BigInt(data.vk_gamma_2[1][1]), BigInt(data.vk_gamma_2[1][0])] + ); + const delta2 = new G2Point( + [BigInt(data.vk_delta_2[0][1]), BigInt(data.vk_delta_2[0][0])], + [BigInt(data.vk_delta_2[1][1]), BigInt(data.vk_delta_2[1][0])] + ); + const ic = data.IC.map((ic) => new G1Point(BigInt(ic[0]), BigInt(ic[1]))); + + return new VerifyingKey(alpha1, beta2, gamma2, delta2, ic); + }; } interface Proof { - a: G1Point; - b: G2Point; - c: G1Point; + a: G1Point; + b: G2Point; + c: G1Point; } class PrivKey { - public rawPrivKey: RawPrivKey - - constructor (rawPrivKey: RawPrivKey) { - this.rawPrivKey = rawPrivKey - } - - public copy = (): PrivKey => { - return new PrivKey(BigInt(this.rawPrivKey.toString())) - } - - public asCircuitInputs = () => { - return formatPrivKeyForBabyJub(this.rawPrivKey).toString() - } - - public serialize = (): string => { - return SERIALIZED_PRIV_KEY_PREFIX + this.rawPrivKey.toString(16) - } - - public static unserialize = (s: string): PrivKey => { - const x = s.slice(SERIALIZED_PRIV_KEY_PREFIX.length) - return new PrivKey(BigInt('0x' + x)) - } - - public static isValidSerializedPrivKey = (s: string): boolean => { - const correctPrefix = s.startsWith(SERIALIZED_PRIV_KEY_PREFIX) - const x = s.slice(SERIALIZED_PRIV_KEY_PREFIX.length) - - let validValue = false - try { - const value = BigInt('0x' + x) - validValue = value < SNARK_FIELD_SIZE - } catch { - // comment to make linter happy - } - - return correctPrefix && validValue - } + public rawPrivKey: RawPrivKey; + + constructor(rawPrivKey: RawPrivKey) { + this.rawPrivKey = rawPrivKey; + } + + public copy = (): PrivKey => { + return new PrivKey(BigInt(this.rawPrivKey.toString())); + }; + + public asCircuitInputs = () => { + return formatPrivKeyForBabyJub(this.rawPrivKey).toString(); + }; + + public serialize = (): string => { + return SERIALIZED_PRIV_KEY_PREFIX + this.rawPrivKey.toString(16); + }; + + public static unserialize = (s: string): PrivKey => { + const x = s.slice(SERIALIZED_PRIV_KEY_PREFIX.length); + return new PrivKey(BigInt('0x' + x)); + }; + + public static isValidSerializedPrivKey = (s: string): boolean => { + const correctPrefix = s.startsWith(SERIALIZED_PRIV_KEY_PREFIX); + const x = s.slice(SERIALIZED_PRIV_KEY_PREFIX.length); + + let validValue = false; + try { + const value = BigInt('0x' + x); + validValue = value < SNARK_FIELD_SIZE; + } catch { + // comment to make linter happy + } + + return correctPrefix && validValue; + }; } -const SERIALIZED_PUB_KEY_PREFIX = 'macipk.' +const SERIALIZED_PUB_KEY_PREFIX = 'macipk.'; class PubKey { - public rawPubKey: RawPubKey - - constructor (rawPubKey: RawPubKey) { - assert(rawPubKey.length === 2) - assert(rawPubKey[0] < SNARK_FIELD_SIZE) - assert(rawPubKey[1] < SNARK_FIELD_SIZE) - this.rawPubKey = rawPubKey - } - - public copy = (): PubKey => { - - return new PubKey([ - BigInt(this.rawPubKey[0].toString()), - BigInt(this.rawPubKey[1].toString()), - ]) - } - - public asContractParam = () => { - return { - x: this.rawPubKey[0].toString(), - y: this.rawPubKey[1].toString(), - } - } - - public asCircuitInputs = () => { - return this.rawPubKey.map((x) => x.toString()) - } - - public asArray = (): BigInt[] => { - return [ - this.rawPubKey[0], - this.rawPubKey[1], - ] - } - - public serialize = (): string => { - // Blank leaves have pubkey [0, 0], which packPubKey does not support - if ( - BigInt(`${this.rawPubKey[0]}`) === BigInt(0) && - BigInt(`${this.rawPubKey[1]}`) === BigInt(0) - ) { - return SERIALIZED_PUB_KEY_PREFIX + 'z' - } - const packed = packPubKey(this.rawPubKey).toString('hex') - return SERIALIZED_PUB_KEY_PREFIX + packed.toString() - } - - public hash = (): BigInt => { - return hashLeftRight(this.rawPubKey[0], this.rawPubKey[1]) - } - - public equals = (p: PubKey): boolean => { - return this.rawPubKey[0] === p.rawPubKey[0] && - this.rawPubKey[1] === p.rawPubKey[1] - } - - public static unserialize = (s: string): PubKey => { - // Blank leaves have pubkey [0, 0], which packPubKey does not support - if (s === SERIALIZED_PUB_KEY_PREFIX + 'z') { - return new PubKey([BigInt(0), BigInt(0)]) - } - - const len = SERIALIZED_PUB_KEY_PREFIX.length - const packed = Buffer.from(s.slice(len), 'hex') - return new PubKey(unpackPubKey(packed)) - } - - public static isValidSerializedPubKey = (s: string): boolean => { - const correctPrefix = s.startsWith(SERIALIZED_PUB_KEY_PREFIX) - - let validValue = false - try { - PubKey.unserialize(s) - validValue = true - } catch { - // comment to make linter happy - } - - return correctPrefix && validValue - } + public rawPubKey: RawPubKey; + + constructor(rawPubKey: RawPubKey) { + assert(rawPubKey.length === 2); + assert(rawPubKey[0] < SNARK_FIELD_SIZE); + assert(rawPubKey[1] < SNARK_FIELD_SIZE); + this.rawPubKey = rawPubKey; + } + + public copy = (): PubKey => { + return new PubKey([ + BigInt(this.rawPubKey[0].toString()), + BigInt(this.rawPubKey[1].toString()), + ]); + }; + + public asContractParam = () => { + return { + x: this.rawPubKey[0].toString(), + y: this.rawPubKey[1].toString(), + }; + }; + + public asCircuitInputs = () => { + return this.rawPubKey.map((x) => x.toString()); + }; + + public asArray = (): BigInt[] => { + return [this.rawPubKey[0], this.rawPubKey[1]]; + }; + + public serialize = (): string => { + // Blank leaves have pubkey [0, 0], which packPubKey does not support + if ( + BigInt(`${this.rawPubKey[0]}`) === BigInt(0) && + BigInt(`${this.rawPubKey[1]}`) === BigInt(0) + ) { + return SERIALIZED_PUB_KEY_PREFIX + 'z'; + } + const packed = packPubKey(this.rawPubKey).toString('hex'); + return SERIALIZED_PUB_KEY_PREFIX + packed.toString(); + }; + + public hash = (): BigInt => { + return hashLeftRight(this.rawPubKey[0], this.rawPubKey[1]); + }; + + public equals = (p: PubKey): boolean => { + return ( + this.rawPubKey[0] === p.rawPubKey[0] && + this.rawPubKey[1] === p.rawPubKey[1] + ); + }; + + public static unserialize = (s: string): PubKey => { + // Blank leaves have pubkey [0, 0], which packPubKey does not support + if (s === SERIALIZED_PUB_KEY_PREFIX + 'z') { + return new PubKey([BigInt(0), BigInt(0)]); + } + + const len = SERIALIZED_PUB_KEY_PREFIX.length; + const packed = Buffer.from(s.slice(len), 'hex'); + return new PubKey(unpackPubKey(packed)); + }; + + public static isValidSerializedPubKey = (s: string): boolean => { + const correctPrefix = s.startsWith(SERIALIZED_PUB_KEY_PREFIX); + + let validValue = false; + try { + PubKey.unserialize(s); + validValue = true; + } catch { + // comment to make linter happy + } + + return correctPrefix && validValue; + }; } class Keypair { - public privKey: PrivKey - public pubKey: PubKey - - constructor ( - privKey?: PrivKey, - ) { - if (privKey) { - this.privKey = privKey - this.pubKey = new PubKey(genPubKey(privKey.rawPrivKey)) - } else { - const rawKeyPair = genKeypair() - this.privKey = new PrivKey(rawKeyPair.privKey) - this.pubKey = new PubKey(rawKeyPair.pubKey) - } - } - - public copy = (): Keypair => { - return new Keypair(this.privKey.copy()) - } - - public static genEcdhSharedKey( - privKey: PrivKey, - pubKey: PubKey, - ) { - return genEcdhSharedKey(privKey.rawPrivKey, pubKey.rawPubKey) - } - - public equals( - keypair: Keypair, - ): boolean { - - const equalPrivKey = this.privKey.rawPrivKey === keypair.privKey.rawPrivKey - const equalPubKey = - this.pubKey.rawPubKey[0] === keypair.pubKey.rawPubKey[0] && - this.pubKey.rawPubKey[1] === keypair.pubKey.rawPubKey[1] - - // If this assertion fails, something is very wrong and this function - // should not return anything - // XOR is equivalent to: (x && !y) || (!x && y ) - const x = (equalPrivKey && equalPubKey) - const y = (!equalPrivKey && !equalPubKey) - - assert((x && !y) || (!x && y)) - - return equalPrivKey - } + public privKey: PrivKey; + public pubKey: PubKey; + + constructor(privKey?: PrivKey) { + if (privKey) { + this.privKey = privKey; + this.pubKey = new PubKey(genPubKey(privKey.rawPrivKey)); + } else { + const rawKeyPair = genKeypair(); + this.privKey = new PrivKey(rawKeyPair.privKey); + this.pubKey = new PubKey(rawKeyPair.pubKey); + } + } + + public copy = (): Keypair => { + return new Keypair(this.privKey.copy()); + }; + + public static genEcdhSharedKey(privKey: PrivKey, pubKey: PubKey) { + return genEcdhSharedKey(privKey.rawPrivKey, pubKey.rawPubKey); + } + + public equals(keypair: Keypair): boolean { + const equalPrivKey = this.privKey.rawPrivKey === keypair.privKey.rawPrivKey; + const equalPubKey = + this.pubKey.rawPubKey[0] === keypair.pubKey.rawPubKey[0] && + this.pubKey.rawPubKey[1] === keypair.pubKey.rawPubKey[1]; + + // If this assertion fails, something is very wrong and this function + // should not return anything + // XOR is equivalent to: (x && !y) || (!x && y ) + const x = equalPrivKey && equalPubKey; + const y = !equalPrivKey && !equalPubKey; + + assert((x && !y) || (!x && y)); + + return equalPrivKey; + } } - interface IStateLeaf { + pubKey: PubKey; + voiceCreditBalance: BigInt; +} + +interface IDeactivatedKeyLeaf { pubKey: PubKey; - voiceCreditBalance: BigInt; + c1: BigInt[]; + c2: BigInt[]; + salt: BigInt; } interface VoteOptionTreeLeaf { - votes: BigInt; + votes: BigInt; } /* * An encrypted command and signature. */ class Message { - public msgType: BigInt - public data: BigInt[] - public static DATA_LENGTH = 10 - - constructor ( - msgType: BigInt, - data: BigInt[], - ) { - assert(data.length === Message.DATA_LENGTH) - this.msgType = msgType - this.data = data - } - - private asArray = (): BigInt[] => { - return [this.msgType].concat(this.data) - } - - public asContractParam = () => { - return { - msgType: this.msgType, - data: this.data.map((x:BigInt) => x.toString()), - } - } - - public asCircuitInputs = (): BigInt[] => { - - return this.asArray() - } - - public hash = ( - _encPubKey: PubKey, - ): BigInt => { - return hash13([ - ...[this.msgType], - ...this.data, - ..._encPubKey.rawPubKey, - ]) - } - - public copy = (): Message => { - - return new Message( - BigInt(this.msgType.toString()), - this.data.map((x: BigInt) => BigInt(x.toString())), - ) - } - - public equals = (m: Message): boolean => { - if (this.data.length !== m.data.length) { - return false - } - if (this.msgType !== m.msgType) { - return false - } - - for (let i = 0; i < this.data.length; i ++) { - if (this.data[i] !== m.data[i]) { - return false - } - } - - return true - } + public msgType: BigInt; + public data: BigInt[]; + public static DATA_LENGTH = 10; + + constructor(msgType: BigInt, data: BigInt[]) { + assert(data.length === Message.DATA_LENGTH); + this.msgType = msgType; + this.data = data; + } + + private asArray = (): BigInt[] => { + return [this.msgType].concat(this.data); + }; + + public asContractParam = () => { + return { + msgType: this.msgType, + data: this.data.map((x: BigInt) => x.toString()), + }; + }; + + public asCircuitInputs = (): BigInt[] => { + return this.asArray(); + }; + + public hash = (_encPubKey: PubKey): BigInt => { + return hash13([...[this.msgType], ...this.data, ..._encPubKey.rawPubKey]); + }; + + public copy = (): Message => { + return new Message( + BigInt(this.msgType.toString()), + this.data.map((x: BigInt) => BigInt(x.toString())) + ); + }; + + public equals = (m: Message): boolean => { + if (this.data.length !== m.data.length) { + return false; + } + if (this.msgType !== m.msgType) { + return false; + } + + for (let i = 0; i < this.data.length; i++) { + if (this.data[i] !== m.data[i]) { + return false; + } + } + + return true; + }; } /* @@ -452,126 +402,119 @@ class Message { * published */ class Ballot { - public votes: BigInt[] = [] - public nonce: BigInt = BigInt(0) - public voteOptionTreeDepth: number - - constructor( - _numVoteOptions: number, - _voteOptionTreeDepth: number, - ) { - this.voteOptionTreeDepth = _voteOptionTreeDepth - assert(5 ** _voteOptionTreeDepth >= _numVoteOptions) - assert(_numVoteOptions >= 0) - for (let i = 0; i < _numVoteOptions; i ++) { - this.votes.push(BigInt(0)) - } - } - - public hash = (): BigInt => { - const vals = this.asArray() - return hashLeftRight(vals[0], vals[1]) - } - - public asCircuitInputs = (): BigInt[] => { - return this.asArray() - } - - public asArray = (): BigInt[] => { - let lastIndexToInsert = this.votes.length - 1 - while (lastIndexToInsert > 0) { - if (this.votes[lastIndexToInsert] !== BigInt(0)) { - break - } - lastIndexToInsert -- - } - const voTree = new IncrementalQuinTree( - this.voteOptionTreeDepth, - BigInt(0), - 5, - hash5, - ) - for (let i = 0; i <= lastIndexToInsert; i ++) { - voTree.insert(this.votes[i]) - } - - return [this.nonce, voTree.root] - } - - public copy = (): Ballot => { - const b = new Ballot(this.votes.length, this.voteOptionTreeDepth) - - b.votes = this.votes.map((x) => BigInt(x.toString())) - b.nonce = BigInt(this.nonce.toString()) - - return b - } - - public equals(b: Ballot): boolean { - for (let i = 0; i < this.votes.length; i ++) { - if (b.votes[i] !== this.votes[i]) { - return false - } - } - return b.nonce === this.nonce && - this.votes.length === b.votes.length - } - - - public static genRandomBallot( - _numVoteOptions: number, - _voteOptionTreeDepth: number, - ) { - const ballot = new Ballot( - _numVoteOptions, - _voteOptionTreeDepth, - ) - ballot.nonce = genRandomSalt() - return ballot - } - - public static genBlankBallot( - _numVoteOptions: number, - _voteOptionTreeDepth: number, - ) { - const ballot = new Ballot( - _numVoteOptions, - _voteOptionTreeDepth, - ) - return ballot - } + public votes: BigInt[] = []; + public nonce: BigInt = BigInt(0); + public voteOptionTreeDepth: number; + + constructor(_numVoteOptions: number, _voteOptionTreeDepth: number) { + this.voteOptionTreeDepth = _voteOptionTreeDepth; + assert(5 ** _voteOptionTreeDepth >= _numVoteOptions); + assert(_numVoteOptions >= 0); + for (let i = 0; i < _numVoteOptions; i++) { + this.votes.push(BigInt(0)); + } + } + + public hash = (): BigInt => { + const vals = this.asArray(); + return hashLeftRight(vals[0], vals[1]); + }; + + public asCircuitInputs = (): BigInt[] => { + return this.asArray(); + }; + + public asArray = (): BigInt[] => { + let lastIndexToInsert = this.votes.length - 1; + while (lastIndexToInsert > 0) { + if (this.votes[lastIndexToInsert] !== BigInt(0)) { + break; + } + lastIndexToInsert--; + } + const voTree = new IncrementalQuinTree( + this.voteOptionTreeDepth, + BigInt(0), + 5, + hash5 + ); + for (let i = 0; i <= lastIndexToInsert; i++) { + voTree.insert(this.votes[i]); + } + + return [this.nonce, voTree.root]; + }; + + public copy = (): Ballot => { + const b = new Ballot(this.votes.length, this.voteOptionTreeDepth); + + b.votes = this.votes.map((x) => BigInt(x.toString())); + b.nonce = BigInt(this.nonce.toString()); + + return b; + }; + + public equals(b: Ballot): boolean { + for (let i = 0; i < this.votes.length; i++) { + if (b.votes[i] !== this.votes[i]) { + return false; + } + } + return b.nonce === this.nonce && this.votes.length === b.votes.length; + } + + public static genRandomBallot( + _numVoteOptions: number, + _voteOptionTreeDepth: number + ) { + const ballot = new Ballot(_numVoteOptions, _voteOptionTreeDepth); + ballot.nonce = genRandomSalt(); + return ballot; + } + + public static genBlankBallot( + _numVoteOptions: number, + _voteOptionTreeDepth: number + ) { + const ballot = new Ballot(_numVoteOptions, _voteOptionTreeDepth); + return ballot; + } } /* - * A leaf in the state tree, which maps public keys to voice credit balances + * A leaf in the deactivated keys tree, containing hashed deactivated public key with deactivation status */ -class StateLeaf implements IStateLeaf { - public pubKey: PubKey - public voiceCreditBalance: BigInt - public timestamp: BigInt +class DeactivatedKeyLeaf implements IDeactivatedKeyLeaf { + public pubKey: PubKey; + public c1: BigInt[]; + public c2: BigInt[]; + public salt: BigInt; constructor ( pubKey: PubKey, - voiceCreditBalance: BigInt, - timestamp: BigInt, + c1: BigInt[], + c2: BigInt[], + salt: BigInt ) { this.pubKey = pubKey - this.voiceCreditBalance = voiceCreditBalance - this.timestamp = timestamp + this.c1 = c1 + this.c2 = c2 + this.salt = salt } /* * Deep-copies the object */ - public copy(): StateLeaf { - return new StateLeaf( + public copy(): DeactivatedKeyLeaf { + return new DeactivatedKeyLeaf( this.pubKey.copy(), - BigInt(this.voiceCreditBalance.toString()), - BigInt(this.timestamp.toString()), + [BigInt(this.c1[0].toString()), BigInt(this.c1[1].toString())], + [BigInt(this.c2[0].toString()), BigInt(this.c2[1].toString())], + BigInt(this.salt.toString()), ) } - public static genBlankLeaf(): StateLeaf { + public static genBlankLeaf(): DeactivatedKeyLeaf { // The public key for a blank state leaf is the first Pedersen base // point from iden3's circomlib implementation of the Pedersen hash. // Since it is generated using a hash-to-curve function, we are @@ -580,31 +523,33 @@ class StateLeaf implements IStateLeaf { // https://github.com/iden3/circomlib/blob/d5ed1c3ce4ca137a6b3ca48bec4ac12c1b38957a/src/pedersen_printbases.js // Its hash should equal // 6769006970205099520508948723718471724660867171122235270773600567925038008762. - return new StateLeaf( + return new DeactivatedKeyLeaf( new PubKey([ BigInt('10457101036533406547632367118273992217979173478358440826365724437999023779287'), BigInt('19824078218392094440610104313265183977899662750282163392862422243483260492317'), ]), - BigInt(0), - BigInt(0), + [BigInt(0), BigInt(0)], + [BigInt(0), BigInt(0)], + BigInt(0) ) } public static genRandomLeaf() { const keypair = new Keypair() - return new StateLeaf( + return new DeactivatedKeyLeaf( keypair.pubKey, - genRandomSalt(), - BigInt(0), + [BigInt(0), BigInt(0)], + [BigInt(0), BigInt(0)], + genRandomSalt() ) } private asArray = (): BigInt[] => { return [ - ...this.pubKey.asArray(), - this.voiceCreditBalance, - this.timestamp, + hash3([...this.pubKey.asArray(), this.salt]), + ...this.c1, + ...this.c2, ] } @@ -615,28 +560,34 @@ class StateLeaf implements IStateLeaf { public hash = (): BigInt => { - return hash4(this.asArray()) + return hash5(this.asArray()) } public asContractParam() { return { - pubKey: this.pubKey.asContractParam(), - voiceCreditBalance: this.voiceCreditBalance.toString(), - timestamp: this.timestamp.toString(), + pubKeyHash: hash3([...this.pubKey.asArray(), this.salt]), + c1: this.c1, + c2: this.c2, } } - public equals(s: StateLeaf): boolean { + public equals(s: DeactivatedKeyLeaf): boolean { return this.pubKey.equals(s.pubKey) && - this.voiceCreditBalance === s.voiceCreditBalance && - this.timestamp === s.timestamp + this.c1[0] === s.c1[0] && + this.c1[0] === s.c1[1] && + this.c2[0] === s.c2[0] && + this.c2[0] === s.c2[1] && + this.salt === s.salt } public serialize = (): string => { const j = [ this.pubKey.serialize(), - this.voiceCreditBalance.toString(16), - this.timestamp.toString(16), + this.c1[0].toString(16), + this.c1[1].toString(16), + this.c2[0].toString(16), + this.c2[1].toString(16), + this.salt.toString(16), ] return base64url( @@ -644,276 +595,357 @@ class StateLeaf implements IStateLeaf { ) } - static unserialize = (serialized: string): StateLeaf => { + static unserialize = (serialized: string): DeactivatedKeyLeaf => { const j = JSON.parse(base64url.decode(serialized)) - return new StateLeaf( + return new DeactivatedKeyLeaf( PubKey.unserialize(j[0]), - BigInt('0x' + j[1]), - BigInt('0x' + j[2]), + [BigInt('0x' + j[1]), BigInt('0x' + j[2])], + [BigInt('0x' + j[3]), BigInt('0x' + j[4])], + BigInt('0x' + j[5]), ) } } -class Command { - public cmdType: BigInt; - constructor() { - } - public copy = (): Command => { - throw new Error("Abstract method!") - } - public equals = (Command): boolean => { - throw new Error("Abstract method!") - } +/* + * A leaf in the state tree, which maps public keys to voice credit balances + */ +class StateLeaf implements IStateLeaf { + public pubKey: PubKey; + public voiceCreditBalance: BigInt; + public timestamp: BigInt; + + constructor(pubKey: PubKey, voiceCreditBalance: BigInt, timestamp: BigInt) { + this.pubKey = pubKey; + this.voiceCreditBalance = voiceCreditBalance; + this.timestamp = timestamp; + } + + /* + * Deep-copies the object + */ + public copy(): StateLeaf { + return new StateLeaf( + this.pubKey.copy(), + BigInt(this.voiceCreditBalance.toString()), + BigInt(this.timestamp.toString()) + ); + } + + public static genBlankLeaf(): StateLeaf { + // The public key for a blank state leaf is the first Pedersen base + // point from iden3's circomlib implementation of the Pedersen hash. + // Since it is generated using a hash-to-curve function, we are + // confident that no-one knows the private key associated with this + // public key. See: + // https://github.com/iden3/circomlib/blob/d5ed1c3ce4ca137a6b3ca48bec4ac12c1b38957a/src/pedersen_printbases.js + // Its hash should equal + // 6769006970205099520508948723718471724660867171122235270773600567925038008762. + return new StateLeaf( + new PubKey([ + BigInt( + '10457101036533406547632367118273992217979173478358440826365724437999023779287' + ), + BigInt( + '19824078218392094440610104313265183977899662750282163392862422243483260492317' + ), + ]), + BigInt(0), + BigInt(0) + ); + } + + public static genRandomLeaf() { + const keypair = new Keypair(); + return new StateLeaf(keypair.pubKey, genRandomSalt(), BigInt(0)); + } + + private asArray = (): BigInt[] => { + return [...this.pubKey.asArray(), this.voiceCreditBalance, this.timestamp]; + }; + + public asCircuitInputs = (): BigInt[] => { + return this.asArray(); + }; + + public hash = (): BigInt => { + return hash4(this.asArray()); + }; + + public asContractParam() { + return { + pubKey: this.pubKey.asContractParam(), + voiceCreditBalance: this.voiceCreditBalance.toString(), + timestamp: this.timestamp.toString(), + }; + } + + public equals(s: StateLeaf): boolean { + return ( + this.pubKey.equals(s.pubKey) && + this.voiceCreditBalance === s.voiceCreditBalance && + this.timestamp === s.timestamp + ); + } + + public serialize = (): string => { + const j = [ + this.pubKey.serialize(), + this.voiceCreditBalance.toString(16), + this.timestamp.toString(16), + ]; + + return base64url(Buffer.from(JSON.stringify(j, null, 0), 'utf8')); + }; + + static unserialize = (serialized: string): StateLeaf => { + const j = JSON.parse(base64url.decode(serialized)); + + return new StateLeaf( + PubKey.unserialize(j[0]), + BigInt('0x' + j[1]), + BigInt('0x' + j[2]) + ); + }; } - +class Command { + public cmdType: BigInt; + constructor() {} + public copy = (): Command => { + throw new Error('Abstract method!'); + }; + public equals = (Command): boolean => { + throw new Error('Abstract method!'); + }; +} class TCommand extends Command { - public cmdType: BigInt - public stateIndex: BigInt - public amount: BigInt - public pollId: BigInt - - constructor(stateIndex: BigInt, amount: BigInt) { - super() - this.cmdType = BigInt(2) - this.stateIndex = stateIndex - this.amount = amount - } - - public copy = (): TCommand => { - return new TCommand( - BigInt(this.stateIndex.toString()), - BigInt(this.amount.toString()), - ) - } - - public equals = (command: TCommand): boolean => { - return this.stateIndex === command.stateIndex && - this.amount === command.amount - } + public cmdType: BigInt; + public stateIndex: BigInt; + public amount: BigInt; + public pollId: BigInt; + + constructor(stateIndex: BigInt, amount: BigInt) { + super(); + this.cmdType = BigInt(2); + this.stateIndex = stateIndex; + this.amount = amount; + } + + public copy = (): TCommand => { + return new TCommand( + BigInt(this.stateIndex.toString()), + BigInt(this.amount.toString()) + ); + }; + + public equals = (command: TCommand): boolean => { + return ( + this.stateIndex === command.stateIndex && this.amount === command.amount + ); + }; } /* * Unencrypted data whose fields include the user's public key, vote etc. */ class PCommand extends Command { - public cmdType: BigInt - public stateIndex: BigInt - public newPubKey: PubKey - public voteOptionIndex: BigInt - public newVoteWeight: BigInt - public nonce: BigInt - public pollId: BigInt - public salt: BigInt - - constructor ( - stateIndex: BigInt, - newPubKey: PubKey, - voteOptionIndex: BigInt, - newVoteWeight: BigInt, - nonce: BigInt, - pollId: BigInt, - salt: BigInt = genRandomSalt(), - ) { - super() - const limit50Bits = BigInt(2 ** 50) - assert(limit50Bits >= stateIndex) - assert(limit50Bits >= voteOptionIndex) - assert(limit50Bits >= newVoteWeight) - assert(limit50Bits >= nonce) - assert(limit50Bits >= pollId) - - this.cmdType = BigInt(1) - this.stateIndex = stateIndex - this.newPubKey = newPubKey - this.voteOptionIndex = voteOptionIndex - this.newVoteWeight = newVoteWeight - this.nonce = nonce - this.pollId = pollId - this.salt = salt - } - - public copy = (): PCommand => { - - return new PCommand( - BigInt(this.stateIndex.toString()), - this.newPubKey.copy(), - BigInt(this.voteOptionIndex.toString()), - BigInt(this.newVoteWeight.toString()), - BigInt(this.nonce.toString()), - BigInt(this.pollId.toString()), - BigInt(this.salt.toString()), - ) - } - - /* - * Returns this Command as an array. Note that 5 of the Command's fields - * are packed into a single 250-bit value. This allows Messages to be - * smaller and thereby save gas when the user publishes a message. - */ - public asArray = (): BigInt[] => { - const p = - BigInt(`${this.stateIndex}`) + - (BigInt(`${this.voteOptionIndex}`) << BigInt(50)) + - (BigInt(`${this.newVoteWeight}`) << BigInt(100)) + - (BigInt(`${this.nonce}`) << BigInt(150)) + - (BigInt(`${this.pollId}`) << BigInt(200)) - - const a = [ - p, - ...this.newPubKey.asArray(), - this.salt, - ] - assert(a.length === 4) - return a - } - - public asCircuitInputs = (): BigInt[] => { - - return this.asArray() - } - - /* - * Check whether this command has deep equivalence to another command - */ - public equals = (command: PCommand): boolean => { - return this.stateIndex === command.stateIndex && - this.newPubKey[0] === command.newPubKey[0] && - this.newPubKey[1] === command.newPubKey[1] && - this.voteOptionIndex === command.voteOptionIndex && - this.newVoteWeight === command.newVoteWeight && - this.nonce === command.nonce && - this.pollId === command.pollId && - this.salt === command.salt - } - - public hash = (): BigInt => { - return hash4(this.asArray()) - } - - /* - * Signs this command and returns a Signature. - */ - public sign = ( - privKey: PrivKey, - ): Signature => { - - return sign(privKey.rawPrivKey, this.hash()) - } - - /* - * Returns true if the given signature is a correct signature of this - * command and signed by the private key associated with the given public - * key. - */ - public verifySignature = ( - signature: Signature, - pubKey: PubKey, - ): boolean => { - - return verifySignature( - this.hash(), - signature, - pubKey.rawPubKey, - ) - } - - /* - * Encrypts this command along with a signature to produce a Message. - * To save gas, we can constrain the following values to 50 bits and pack - * them into a 250-bit value: - * 0. state index - * 3. vote option index - * 4. new vote weight - * 5. nonce - * 6. poll ID - */ - public encrypt = ( - signature: Signature, - sharedKey: EcdhSharedKey, - ): Message => { - const plaintext = [ - ...this.asArray(), - signature.R8[0], - signature.R8[1], - signature.S, - ] - - assert(plaintext.length === 7) - - const ciphertext: Ciphertext = encrypt(plaintext, sharedKey, BigInt(0)) - - const message = new Message(BigInt(1), ciphertext) - - return message - } - - /* - * Decrypts a Message to produce a Command. - */ - public static decrypt = ( - message: Message, - sharedKey: EcdhSharedKey, - ) => { - - const decrypted = decrypt(message.data, sharedKey, BigInt(0), 7) - - const p = BigInt(`${decrypted[0]}`) - - // Returns the value of the 50 bits at position `pos` in `val` - // create 50 '1' bits - // shift left by pos - // AND with val - // shift right by pos - const extract = (val: BigInt, pos: number): BigInt => { - return BigInt( - ( - ( - (BigInt(1) << BigInt(50)) - BigInt(1) - ) << BigInt(pos) - ) & BigInt(`${val}`) - ) >> BigInt(pos) - } - - // p is a packed value - // bits 0 - 50: stateIndex - // bits 51 - 100: voteOptionIndex - // bits 101 - 150: newVoteWeight - // bits 151 - 200: nonce - // bits 201 - 250: pollId - const stateIndex = extract(p, 0) - const voteOptionIndex = extract(p, 50) - const newVoteWeight = extract(p, 100) - const nonce = extract(p, 150) - const pollId = extract(p, 200) - - const newPubKey = new PubKey([decrypted[1], decrypted[2]]) - const salt = decrypted[3] - - const command = new PCommand( - stateIndex, - newPubKey, - voteOptionIndex, - newVoteWeight, - nonce, - pollId, - salt, - ) - - const signature = { - R8: [decrypted[4], decrypted[5]], - S: decrypted[6], - } - - return { command, signature } - } + public cmdType: BigInt; + public stateIndex: BigInt; + public newPubKey: PubKey; + public voteOptionIndex: BigInt; + public newVoteWeight: BigInt; + public nonce: BigInt; + public pollId: BigInt; + public salt: BigInt; + + constructor( + stateIndex: BigInt, + newPubKey: PubKey, + voteOptionIndex: BigInt, + newVoteWeight: BigInt, + nonce: BigInt, + pollId: BigInt, + salt: BigInt = genRandomSalt() + ) { + super(); + const limit50Bits = BigInt(2 ** 50); + assert(limit50Bits >= stateIndex); + assert(limit50Bits >= voteOptionIndex); + assert(limit50Bits >= newVoteWeight); + assert(limit50Bits >= nonce); + assert(limit50Bits >= pollId); + + this.cmdType = BigInt(1); + this.stateIndex = stateIndex; + this.newPubKey = newPubKey; + this.voteOptionIndex = voteOptionIndex; + this.newVoteWeight = newVoteWeight; + this.nonce = nonce; + this.pollId = pollId; + this.salt = salt; + } + + public copy = (): PCommand => { + return new PCommand( + BigInt(this.stateIndex.toString()), + this.newPubKey.copy(), + BigInt(this.voteOptionIndex.toString()), + BigInt(this.newVoteWeight.toString()), + BigInt(this.nonce.toString()), + BigInt(this.pollId.toString()), + BigInt(this.salt.toString()) + ); + }; + + /* + * Returns this Command as an array. Note that 5 of the Command's fields + * are packed into a single 250-bit value. This allows Messages to be + * smaller and thereby save gas when the user publishes a message. + */ + public asArray = (): BigInt[] => { + const p = + BigInt(`${this.stateIndex}`) + + (BigInt(`${this.voteOptionIndex}`) << BigInt(50)) + + (BigInt(`${this.newVoteWeight}`) << BigInt(100)) + + (BigInt(`${this.nonce}`) << BigInt(150)) + + (BigInt(`${this.pollId}`) << BigInt(200)); + + const a = [p, ...this.newPubKey.asArray(), this.salt]; + assert(a.length === 4); + return a; + }; + + public asCircuitInputs = (): BigInt[] => { + return this.asArray(); + }; + + /* + * Check whether this command has deep equivalence to another command + */ + public equals = (command: PCommand): boolean => { + return ( + this.stateIndex === command.stateIndex && + this.newPubKey[0] === command.newPubKey[0] && + this.newPubKey[1] === command.newPubKey[1] && + this.voteOptionIndex === command.voteOptionIndex && + this.newVoteWeight === command.newVoteWeight && + this.nonce === command.nonce && + this.pollId === command.pollId && + this.salt === command.salt + ); + }; + + public hash = (): BigInt => { + return hash4(this.asArray()); + }; + + /* + * Signs this command and returns a Signature. + */ + public sign = (privKey: PrivKey): Signature => { + return sign(privKey.rawPrivKey, this.hash()); + }; + + /* + * Returns true if the given signature is a correct signature of this + * command and signed by the private key associated with the given public + * key. + */ + public verifySignature = (signature: Signature, pubKey: PubKey): boolean => { + return verifySignature(this.hash(), signature, pubKey.rawPubKey); + }; + + /* + * Encrypts this command along with a signature to produce a Message. + * To save gas, we can constrain the following values to 50 bits and pack + * them into a 250-bit value: + * 0. state index + * 3. vote option index + * 4. new vote weight + * 5. nonce + * 6. poll ID + */ + public encrypt = ( + signature: Signature, + sharedKey: EcdhSharedKey + ): Message => { + const plaintext = [ + ...this.asArray(), + signature.R8[0], + signature.R8[1], + signature.S, + ]; + + assert(plaintext.length === 7); + + const ciphertext: Ciphertext = encrypt(plaintext, sharedKey, BigInt(0)); + + const message = new Message(BigInt(1), ciphertext); + + return message; + }; + + /* + * Decrypts a Message to produce a Command. + */ + public static decrypt = (message: Message, sharedKey: EcdhSharedKey) => { + const decrypted = decrypt(message.data, sharedKey, BigInt(0), 7); + + const p = BigInt(`${decrypted[0]}`); + + // Returns the value of the 50 bits at position `pos` in `val` + // create 50 '1' bits + // shift left by pos + // AND with val + // shift right by pos + const extract = (val: BigInt, pos: number): BigInt => { + return ( + BigInt( + (((BigInt(1) << BigInt(50)) - BigInt(1)) << BigInt(pos)) & + BigInt(`${val}`) + ) >> BigInt(pos) + ); + }; + + // p is a packed value + // bits 0 - 50: stateIndex + // bits 51 - 100: voteOptionIndex + // bits 101 - 150: newVoteWeight + // bits 151 - 200: nonce + // bits 201 - 250: pollId + const stateIndex = extract(p, 0); + const voteOptionIndex = extract(p, 50); + const newVoteWeight = extract(p, 100); + const nonce = extract(p, 150); + const pollId = extract(p, 200); + + const newPubKey = new PubKey([decrypted[1], decrypted[2]]); + const salt = decrypted[3]; + + const command = new PCommand( + stateIndex, + newPubKey, + voteOptionIndex, + newVoteWeight, + nonce, + pollId, + salt + ); + + const signature = { + R8: [decrypted[4], decrypted[5]], + S: decrypted[6], + }; + + return { command, signature }; + }; } - export { StateLeaf, + DeactivatedKeyLeaf, Ballot, VoteOptionTreeLeaf, PCommand, diff --git a/integrationTests/integrations.yml b/integrationTests/integrations.yml index 6108e68068..1094ba8e23 100644 --- a/integrationTests/integrations.yml +++ b/integrationTests/integrations.yml @@ -17,7 +17,7 @@ invalidVote: voteCreditBalance: 1 constants: poll: - duration: 3600 + duration: 240 intStateTreeDepth: 1 messageTreeDepth: 2 messageBatchDepth: 1 @@ -27,7 +27,7 @@ constants: maxVoteOptions: 25 initialVoiceCredits: 1000 signupDuration: 120 - votingDuration: 3600 + votingDuration: 240 signUpDurationInSeconds: 3600 # 1 hour votingDurationInSeconds: 3600 # 1 hour coordinatorPrivKey: "2222222222263902553431241761119057960280734584214105336279476766401963593688" diff --git a/integrationTests/ts/__tests__/suites.ts b/integrationTests/ts/__tests__/suites.ts index b3975646df..c2c8e49349 100644 --- a/integrationTests/ts/__tests__/suites.ts +++ b/integrationTests/ts/__tests__/suites.ts @@ -76,6 +76,7 @@ const executeSuite = async (data: any, expect: any) => { ` -v ${config.constants.maci.voteOptionTreeDepth}` + ` -b ${config.constants.poll.messageBatchDepth}` + ` -p ./zkeys/ProcessMessages_10-2-1-2_test.0.zkey` + + ` -zpd ./zkeys/ProcessDeactivationMessages_5-10_test.0.zkey` + ` -t ./zkeys/TallyVotes_10-1-2_test.0.zkey` + ` -k ${vkAddress}` + ` ${subsidyZkeyFilePath}` @@ -84,7 +85,9 @@ const executeSuite = async (data: any, expect: any) => { // Run the create subcommand const createCommand = `node build/index.js create` + - ` -r ${vkAddress}` + ` -r ${vkAddress}` + + ` --signup-deadline 1689834390`+ + ` --deactivation-period 86400` const createOutput = execute(createCommand).stdout.trim() const regMatch = createOutput.match(/MACI: (0x[a-fA-F0-9]{40})/) const maciAddress = regMatch[1] diff --git a/package.json b/package.json index 1649210a8f..4dce459a42 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "scripts": { "bootstrap": "lerna bootstrap", "build": "lerna run build", + "clean": "lerna clean", "lint": "eslint './**/**/*.ts'", "lint-fix": "eslint './**/**/*.ts' --fix" },