diff --git a/packages/hardhat/contracts/PollManager.sol b/packages/hardhat/contracts/PollManager.sol index 0044b76..3601aaf 100644 --- a/packages/hardhat/contracts/PollManager.sol +++ b/packages/hardhat/contracts/PollManager.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; +pragma solidity ^0.8.20; -import "./maci-contracts/MACI.sol"; -import { Params } from "./maci-contracts/utilities/Params.sol"; -import { DomainObjs } from "./maci-contracts/utilities/DomainObjs.sol"; +import { MACIWrapper } from "./maci-contracts/MACIWrapper.sol"; +import { Params } from "maci-contracts/contracts/utilities/Params.sol"; +import { DomainObjs } from "maci-contracts/contracts/utilities/DomainObjs.sol"; contract PollManager is Params, DomainObjs { struct PollContracts { @@ -19,7 +19,7 @@ contract PollManager is Params, DomainObjs { string name; bytes encodedOptions; string metadata; - MACI.PollContracts pollContracts; + MACIWrapper.PollContracts pollContracts; uint256 startTime; uint256 endTime; uint256 numOfOptions; @@ -31,20 +31,19 @@ contract PollManager is Params, DomainObjs { mapping(address => uint256) public pollIdByAddress; // poll address => poll id uint256 public totalPolls; - MACI public maci; + MACIWrapper public maci; TreeDepths public treeDepths; PubKey public coordinatorPubKey; address public verifier; address public vkRegistry; bool public useSubsidy; - bool public isQv; event PollCreated( uint256 indexed pollId, uint256 indexed maciPollId, address indexed creator, - MACI.PollContracts pollContracts, + MACIWrapper.PollContracts pollContracts, string name, string[] options, string metadata, @@ -63,9 +62,8 @@ contract PollManager is Params, DomainObjs { _; } - constructor(MACI _maci, bool _isQv) { + constructor(MACIWrapper _maci) { maci = _maci; - isQv = _isQv; } function owner() public view returns (address) { @@ -76,26 +74,25 @@ contract PollManager is Params, DomainObjs { TreeDepths memory _treeDepths, PubKey memory _coordinatorPubKey, address _verifier, - address _vkRegistry, - bool _useSubsidy + address _vkRegistry ) public onlyOwner { treeDepths = _treeDepths; coordinatorPubKey = _coordinatorPubKey; verifier = _verifier; vkRegistry = _vkRegistry; - useSubsidy = _useSubsidy; } function createPoll( string calldata _name, string[] calldata _options, string calldata _metadata, - uint256 _duration - ) public onlyOwner { + uint256 _duration, + Mode isQv + ) public { // TODO: check if the number of options are more than limit // deploy the poll contracts - MACI.PollContracts memory pollContracts = maci.deployPoll( + MACIWrapper.PollContracts memory pollContracts = maci.deployPoll( _duration, treeDepths, coordinatorPubKey, diff --git a/packages/hardhat/contracts/maci-contracts/MACI.sol b/packages/hardhat/contracts/maci-contracts/MACI.sol deleted file mode 100644 index 43a40c7..0000000 --- a/packages/hardhat/contracts/maci-contracts/MACI.sol +++ /dev/null @@ -1,358 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { IPollFactory } from "./interfaces/IPollFactory.sol"; -import { IMessageProcessorFactory } from "./interfaces/IMPFactory.sol"; -import { ITallyFactory } from "./interfaces/ITallyFactory.sol"; -import { InitialVoiceCreditProxy } from "./initialVoiceCreditProxy/InitialVoiceCreditProxy.sol"; -import { SignUpGatekeeper } from "./gatekeepers/SignUpGatekeeper.sol"; -import { AccQueue } from "./trees/AccQueue.sol"; -import { AccQueueQuinaryBlankSl } from "./trees/AccQueueQuinaryBlankSl.sol"; -import { IMACI } from "./interfaces/IMACI.sol"; -import { Params } from "./utilities/Params.sol"; -import { TopupCredit } from "./TopupCredit.sol"; -import { Utilities } from "./utilities/Utilities.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; - -/// @title MACI - Minimum Anti-Collusion Infrastructure Version 1 -/// @notice A contract which allows users to sign up, and deploy new polls -contract MACI is IMACI, Params, Utilities, Ownable { - /// @notice The state tree depth is fixed. As such it should be as large as feasible - /// so that there can be as many users as possible. i.e. 5 ** 10 = 9765625 - /// this should also match the parameter of the circom circuits. - uint8 public immutable stateTreeDepth; - - /// @notice IMPORTANT: remember to change the ballot tree depth - /// in contracts/ts/genEmptyBallotRootsContract.ts file - /// if we change the state tree depth! - uint8 internal constant STATE_TREE_SUBDEPTH = 2; - uint8 internal constant TREE_ARITY = 5; - - /// @notice The hash of a blank state leaf - uint256 internal constant BLANK_STATE_LEAF_HASH = - uint256( - 6769006970205099520508948723718471724660867171122235270773600567925038008762 - ); - - /// @notice Each poll has an incrementing ID - uint256 public nextPollId; - - /// @notice A mapping of poll IDs to Poll contracts. - mapping(uint256 => address) public polls; - mapping(address => uint256) public pollIds; - - /// @notice Whether the subtrees have been merged (can merge root before new signup) - bool public subtreesMerged; - - /// @notice The number of signups - uint256 public numSignUps; - - /// @notice ERC20 contract that hold topup credits - TopupCredit public immutable topupCredit; - - /// @notice Factory contract that deploy a Poll contract - IPollFactory public immutable pollFactory; - - /// @notice Factory contract that deploy a MessageProcessor contract - IMessageProcessorFactory public immutable messageProcessorFactory; - - /// @notice Factory contract that deploy a Tally contract - ITallyFactory public immutable tallyFactory; - - /// @notice The state AccQueue. Represents a mapping between each user's public key - /// and their voice credit balance. - AccQueue public immutable stateAq; - - /// @notice Address of the SignUpGatekeeper, a contract which determines whether a - /// user may sign up to vote - SignUpGatekeeper public immutable signUpGatekeeper; - - /// @notice The contract which provides the values of the initial voice credit - /// balance per user - InitialVoiceCreditProxy public immutable initialVoiceCreditProxy; - - mapping(uint256 => mapping(uint256 => bool)) public isPublicKeyRegistered; // pubkey.x => pubkey.y => bool - - /// @notice Poll Manager contract that create polls - address public manager; - - /// @notice A struct holding the addresses of poll, mp and tally - struct PollContracts { - address poll; - address messageProcessor; - address tally; - } - - // Events - event SignUp( - uint256 _stateIndex, - uint256 indexed _userPubKeyX, - uint256 indexed _userPubKeyY, - uint256 _voiceCreditBalance, - uint256 _timestamp - ); - event DeployPoll( - uint256 _pollId, - uint256 indexed _coordinatorPubKeyX, - uint256 indexed _coordinatorPubKeyY, - PollContracts pollAddr - ); - - /// @notice Only allow a Poll contract to call the modified function. - modifier onlyPoll(uint256 _pollId) { - if (msg.sender != address(polls[_pollId])) - revert CallerMustBePoll(msg.sender); - _; - } - - /// @notice custom errors - error CallerMustBePoll(address _caller); - error PoseidonHashLibrariesNotLinked(); - error TooManySignups(); - error MaciPubKeyLargerThanSnarkFieldSize(); - error PreviousPollNotCompleted(uint256 pollId); - error PollDoesNotExist(uint256 pollId); - error PollAddressDoesNotExist(address pollAddr); - error SignupTemporaryBlocked(); - error PubKeyAlreadyRegistered(); - - /// @notice Create a new instance of the MACI contract. - /// @param _pollFactory The PollFactory contract - /// @param _messageProcessorFactory The MessageProcessorFactory contract - /// @param _tallyFactory The TallyFactory contract - /// @param _signUpGatekeeper The SignUpGatekeeper contract - /// @param _initialVoiceCreditProxy The InitialVoiceCreditProxy contract - /// @param _topupCredit The TopupCredit contract - /// @param _stateTreeDepth The depth of the state tree - constructor( - IPollFactory _pollFactory, - IMessageProcessorFactory _messageProcessorFactory, - ITallyFactory _tallyFactory, - SignUpGatekeeper _signUpGatekeeper, - InitialVoiceCreditProxy _initialVoiceCreditProxy, - TopupCredit _topupCredit, - uint8 _stateTreeDepth - ) payable { - // Deploy the state AccQueue - stateAq = new AccQueueQuinaryBlankSl(STATE_TREE_SUBDEPTH); - stateAq.enqueue(BLANK_STATE_LEAF_HASH); - - // because we add a blank leaf we need to count one signup - // so we don't allow max + 1 - unchecked { - numSignUps++; - } - - pollFactory = _pollFactory; - messageProcessorFactory = _messageProcessorFactory; - tallyFactory = _tallyFactory; - topupCredit = _topupCredit; - signUpGatekeeper = _signUpGatekeeper; - initialVoiceCreditProxy = _initialVoiceCreditProxy; - stateTreeDepth = _stateTreeDepth; - - // Verify linked poseidon libraries - if (hash2([uint256(1), uint256(1)]) == 0) - revert PoseidonHashLibrariesNotLinked(); - } - - function updateManager(address _manager) public onlyOwner { - manager = _manager; - } - - modifier onlyManager() { - require(msg.sender == manager, "only manager can create poll"); - _; - } - - /// @notice Allows any eligible user sign up. The sign-up gatekeeper should prevent - /// double sign-ups or ineligible users from doing so. This function will - /// only succeed if the sign-up deadline has not passed. It also enqueues a - /// fresh state leaf into the state AccQueue. - /// @param _pubKey The user's desired public key. - /// @param _signUpGatekeeperData Data to pass to the sign-up gatekeeper's - /// register() function. For instance, the POAPGatekeeper or - /// SignUpTokenGatekeeper requires this value to be the ABI-encoded - /// token ID. - /// @param _initialVoiceCreditProxyData Data to pass to the - /// InitialVoiceCreditProxy, which allows it to determine how many voice - /// credits this user should have. - function signUp( - PubKey memory _pubKey, - bytes memory _signUpGatekeeperData, - bytes memory _initialVoiceCreditProxyData - ) public virtual { - // check if the pubkey is already registered - if (isPublicKeyRegistered[_pubKey.x][_pubKey.y]) - revert PubKeyAlreadyRegistered(); - - // prevent new signups until we merge the roots (possible DoS) - if (subtreesMerged) revert SignupTemporaryBlocked(); - - // ensure we do not have more signups than what the circuits support - if (numSignUps >= uint256(TREE_ARITY) ** uint256(stateTreeDepth)) - revert TooManySignups(); - - if ( - _pubKey.x >= SNARK_SCALAR_FIELD || _pubKey.y >= SNARK_SCALAR_FIELD - ) { - revert MaciPubKeyLargerThanSnarkFieldSize(); - } - - // Increment the number of signups - // cannot overflow with realistic STATE_TREE_DEPTH - // values as numSignUps < 5 ** STATE_TREE_DEPTH -1 - unchecked { - numSignUps++; - } - - // Register the user via the sign-up gatekeeper. This function should - // throw if the user has already registered or if ineligible to do so. - signUpGatekeeper.register(msg.sender, _signUpGatekeeperData); - - // Get the user's voice credit balance. - uint256 voiceCreditBalance = initialVoiceCreditProxy.getVoiceCredits( - msg.sender, - _initialVoiceCreditProxyData - ); - - uint256 timestamp = block.timestamp; - // Create a state leaf and enqueue it. - uint256 stateLeaf = hashStateLeaf( - StateLeaf(_pubKey, voiceCreditBalance, timestamp) - ); - uint256 stateIndex = stateAq.enqueue(stateLeaf); - - isPublicKeyRegistered[_pubKey.x][_pubKey.y] = true; - - emit SignUp( - stateIndex, - _pubKey.x, - _pubKey.y, - voiceCreditBalance, - timestamp - ); - } - - /// @notice Deploy a new Poll contract. - /// @param _duration How long should the Poll last for - /// @param _treeDepths The depth of the Merkle trees - /// @param _coordinatorPubKey The coordinator's public key - /// @param _verifier The Verifier Contract - /// @param _vkRegistry The VkRegistry Contract - /// @param _isQv Whether to support QV or not - /// @return pollAddr a new Poll contract address - function deployPoll( - uint256 _duration, - TreeDepths memory _treeDepths, - PubKey memory _coordinatorPubKey, - address _verifier, - address _vkRegistry, - bool _isQv - ) public virtual onlyManager returns (PollContracts memory pollAddr) { - // cache the poll to a local variable so we can increment it - uint256 pollId = nextPollId; - - // Increment the poll ID for the next poll - // 2 ** 256 polls available - unchecked { - nextPollId++; - } - - // if (pollId > 0) { - // if (!stateAq.treeMerged()) revert PreviousPollNotCompleted(pollId); - // } - - MaxValues memory maxValues = MaxValues({ - maxMessages: uint256(TREE_ARITY) ** _treeDepths.messageTreeDepth, - maxVoteOptions: uint256(TREE_ARITY) ** - _treeDepths.voteOptionTreeDepth - }); - - address _owner = owner(); - - address p = pollFactory.deploy( - _duration, - maxValues, - _treeDepths, - _coordinatorPubKey, - address(this), - topupCredit, - _owner - ); - - address mp = messageProcessorFactory.deploy( - _verifier, - _vkRegistry, - p, - _owner, - _isQv - ); - address tally = tallyFactory.deploy( - _verifier, - _vkRegistry, - p, - mp, - _owner, - _isQv - ); - - polls[pollId] = p; - pollIds[p] = pollId; - - // store the addresses in a struct so they can be returned - pollAddr = PollContracts({ - poll: p, - messageProcessor: mp, - tally: tally - }); - - emit DeployPoll( - pollId, - _coordinatorPubKey.x, - _coordinatorPubKey.y, - pollAddr - ); - } - - /// @inheritdoc IMACI - function mergeStateAqSubRoots( - uint256 _numSrQueueOps, - uint256 _pollId - ) public onlyPoll(_pollId) { - stateAq.mergeSubRoots(_numSrQueueOps); - - // if we have merged all subtrees then put a block - if (stateAq.subTreesMerged()) { - subtreesMerged = true; - } - } - - /// @inheritdoc IMACI - function mergeStateAq( - uint256 _pollId - ) public onlyPoll(_pollId) returns (uint256 root) { - // remove block - subtreesMerged = false; - - root = stateAq.merge(stateTreeDepth); - } - - /// @inheritdoc IMACI - function getStateAqRoot() public view returns (uint256 root) { - root = stateAq.getMainRoot(stateTreeDepth); - } - - /// @notice Get the Poll details - /// @param _pollId The identifier of the Poll to retrieve - /// @return poll The Poll contract object - function getPoll(uint256 _pollId) public view returns (address poll) { - if (_pollId >= nextPollId) revert PollDoesNotExist(_pollId); - poll = polls[_pollId]; - } - - function getPollId(address _poll) public view returns (uint256 pollId) { - if (pollIds[_poll] >= nextPollId) revert PollAddressDoesNotExist(_poll); - pollId = pollIds[_poll]; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/MACIWrapper.sol b/packages/hardhat/contracts/maci-contracts/MACIWrapper.sol new file mode 100644 index 0000000..28505eb --- /dev/null +++ b/packages/hardhat/contracts/maci-contracts/MACIWrapper.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import { MACI } from "maci-contracts/contracts/MACI.sol"; +import { IPollFactory } from "maci-contracts/contracts/interfaces/IPollFactory.sol"; +import { IMessageProcessorFactory } from "maci-contracts/contracts/interfaces/IMPFactory.sol"; +import { ITallyFactory } from "maci-contracts/contracts/interfaces/ITallyFactory.sol"; +import { SignUpGatekeeper } from "maci-contracts/contracts/gatekeepers/SignUpGatekeeper.sol"; +import { InitialVoiceCreditProxy } from "maci-contracts/contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol"; +import { TopupCredit } from "maci-contracts/contracts/TopupCredit.sol"; + +/// @title MACI - Minimum Anti-Collusion Infrastructure Version 1 +/// @notice A contract which allows users to sign up, and deploy new polls +contract MACIWrapper is MACI { + mapping(address => uint256) public pollIds; + + // pubkey.x => pubkey.y => bool + mapping(uint256 => mapping(uint256 => bool)) public isPublicKeyRegistered; + + error PubKeyAlreadyRegistered(); + error PollAddressDoesNotExist(address _poll); + + constructor( + IPollFactory _pollFactory, + IMessageProcessorFactory _messageProcessorFactory, + ITallyFactory _tallyFactory, + SignUpGatekeeper _signUpGatekeeper, + InitialVoiceCreditProxy _initialVoiceCreditProxy, + TopupCredit _topupCredit, + uint8 _stateTreeDepth + ) MACI( + _pollFactory, + _messageProcessorFactory, + _tallyFactory, + _signUpGatekeeper, + _initialVoiceCreditProxy, + _topupCredit, + _stateTreeDepth + ) { + } + + /// @notice Allows any eligible user sign up. The sign-up gatekeeper should prevent + /// double sign-ups or ineligible users from doing so. This function will + /// only succeed if the sign-up deadline has not passed. It also enqueues a + /// fresh state leaf into the state AccQueue. + /// @param _pubKey The user's desired public key. + /// @param _signUpGatekeeperData Data to pass to the sign-up gatekeeper's + /// register() function. For instance, the POAPGatekeeper or + /// SignUpTokenGatekeeper requires this value to be the ABI-encoded + /// token ID. + /// @param _initialVoiceCreditProxyData Data to pass to the + /// InitialVoiceCreditProxy, which allows it to determine how many voice + /// credits this user should have. + function signUp( + PubKey memory _pubKey, + bytes memory _signUpGatekeeperData, + bytes memory _initialVoiceCreditProxyData + ) public override { + // check if the pubkey is already registered + if (isPublicKeyRegistered[_pubKey.x][_pubKey.y]) + revert PubKeyAlreadyRegistered(); + + super.signUp(_pubKey, _signUpGatekeeperData, _initialVoiceCreditProxyData); + + isPublicKeyRegistered[_pubKey.x][_pubKey.y] = true; + } + + /// @notice Deploy a new Poll contract. + /// @param _duration How long should the Poll last for + /// @param _treeDepths The depth of the Merkle trees + /// @param _coordinatorPubKey The coordinator's public key + /// @param _verifier The Verifier Contract + /// @param _vkRegistry The VkRegistry Contract + /// @param _mode Whether to support QV or not + /// @return pollAddr a new Poll contract address + function deployPoll( + uint256 _duration, + TreeDepths memory _treeDepths, + PubKey memory _coordinatorPubKey, + address _verifier, + address _vkRegistry, + Mode _mode + ) public override returns (PollContracts memory pollAddr) { + // cache the poll to a local variable so we can increment it + uint256 pollId = nextPollId; + + PollContracts memory p = super.deployPoll( + _duration, + _treeDepths, + _coordinatorPubKey, + _verifier, + _vkRegistry, + _mode + ); + + pollIds[p.poll] = pollId; + + return p; + } + + function getPollId(address _poll) public view returns (uint256 pollId) { + if (pollIds[_poll] >= nextPollId) revert PollAddressDoesNotExist(_poll); + pollId = pollIds[_poll]; + } +} diff --git a/packages/hardhat/contracts/maci-contracts/MessageProcessor.sol b/packages/hardhat/contracts/maci-contracts/MessageProcessor.sol deleted file mode 100644 index 1d895a7..0000000 --- a/packages/hardhat/contracts/maci-contracts/MessageProcessor.sol +++ /dev/null @@ -1,295 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { AccQueue } from "./trees/AccQueue.sol"; -import { IMACI } from "./interfaces/IMACI.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { IPoll } from "./interfaces/IPoll.sol"; -import { SnarkCommon } from "./crypto/SnarkCommon.sol"; -import { Hasher } from "./crypto/Hasher.sol"; -import { IVerifier } from "./interfaces/IVerifier.sol"; -import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; -import { IMessageProcessor } from "./interfaces/IMessageProcessor.sol"; -import { CommonUtilities } from "./utilities/CommonUtilities.sol"; - -/// @title MessageProcessor -/// @dev MessageProcessor is used to process messages published by signup users. -/// It will process message by batch due to large size of messages. -/// After it finishes processing, the sbCommitment will be used for Tally and Subsidy contracts. -contract MessageProcessor is Ownable, SnarkCommon, Hasher, CommonUtilities, IMessageProcessor { - /// @notice custom errors - error NoMoreMessages(); - error StateAqNotMerged(); - error MessageAqNotMerged(); - error InvalidProcessMessageProof(); - error VkNotSet(); - error MaxVoteOptionsTooLarge(); - error NumSignUpsTooLarge(); - error CurrentMessageBatchIndexTooLarge(); - error BatchEndIndexTooLarge(); - - // the number of children per node in the merkle trees - uint256 internal constant TREE_ARITY = 5; - - /// @inheritdoc IMessageProcessor - bool public processingComplete; - - /// @notice The number of batches processed - uint256 public numBatchesProcessed; - - /// @notice The current message batch index. When the coordinator runs - /// processMessages(), this action relates to messages - /// currentMessageBatchIndex to currentMessageBatchIndex + messageBatchSize. - uint256 public currentMessageBatchIndex; - - /// @inheritdoc IMessageProcessor - uint256 public sbCommitment; - - IPoll public immutable poll; - IVerifier public immutable verifier; - IVkRegistry public immutable vkRegistry; - bool public immutable isQv; - - /// @notice Create a new instance - /// @param _verifier The Verifier contract address - /// @param _vkRegistry The VkRegistry contract address - /// @param _poll The Poll contract address - /// @param _isQv Whether to support QV or not - constructor(address _verifier, address _vkRegistry, address _poll, bool _isQv) payable { - verifier = IVerifier(_verifier); - vkRegistry = IVkRegistry(_vkRegistry); - poll = IPoll(_poll); - isQv = _isQv; - } - - /// @notice Update the Poll's currentSbCommitment if the proof is valid. - /// @param _newSbCommitment The new state root and ballot root commitment - /// after all messages are processed - /// @param _proof The zk-SNARK proof - function processMessages(uint256 _newSbCommitment, uint256[8] memory _proof) external onlyOwner { - // ensure the voting period is over - _votingPeriodOver(poll); - - // There must be unprocessed messages - if (processingComplete) { - revert NoMoreMessages(); - } - - // The state AccQueue must be merged - if (!poll.stateAqMerged()) { - revert StateAqNotMerged(); - } - - // Retrieve stored vals - (, uint8 messageTreeSubDepth, uint8 messageTreeDepth, uint8 voteOptionTreeDepth) = poll.treeDepths(); - // calculate the message batch size from the message tree subdepth - uint256 messageBatchSize = TREE_ARITY ** messageTreeSubDepth; - - (, AccQueue messageAq, ) = poll.extContracts(); - - // Require that the message queue has been merged - uint256 messageRoot = messageAq.getMainRoot(messageTreeDepth); - if (messageRoot == 0) { - revert MessageAqNotMerged(); - } - - // Copy the state and ballot commitment and set the batch index if this - // is the first batch to process - if (numBatchesProcessed == 0) { - uint256 currentSbCommitment = poll.currentSbCommitment(); - sbCommitment = currentSbCommitment; - (, uint256 numMessages) = poll.numSignUpsAndMessages(); - uint256 r = numMessages % messageBatchSize; - - currentMessageBatchIndex = numMessages; - - if (currentMessageBatchIndex > 0) { - if (r == 0) { - currentMessageBatchIndex -= messageBatchSize; - } else { - currentMessageBatchIndex -= r; - } - } - } - - if ( - !verifyProcessProof( - currentMessageBatchIndex, - messageRoot, - sbCommitment, - _newSbCommitment, - messageTreeSubDepth, - messageTreeDepth, - voteOptionTreeDepth, - _proof - ) - ) { - revert InvalidProcessMessageProof(); - } - - { - (, uint256 numMessages) = poll.numSignUpsAndMessages(); - // Decrease the message batch start index to ensure that each - // message batch is processed in order - if (currentMessageBatchIndex > 0) { - currentMessageBatchIndex -= messageBatchSize; - } - - updateMessageProcessingData( - _newSbCommitment, - currentMessageBatchIndex, - numMessages <= messageBatchSize * (numBatchesProcessed + 1) - ); - } - } - - /// @notice Verify the proof for processMessage - /// @dev used to update the sbCommitment - /// @param _currentMessageBatchIndex The batch index of current message batch - /// @param _messageRoot The message tree root - /// @param _currentSbCommitment The current sbCommitment (state and ballot) - /// @param _newSbCommitment The new sbCommitment after we update this message batch - /// @param _messageTreeSubDepth The message tree subdepth - /// @param _messageTreeDepth The message tree depth - /// @param _voteOptionTreeDepth The vote option tree depth - /// @param _proof The zk-SNARK proof - /// @return isValid Whether the proof is valid - function verifyProcessProof( - uint256 _currentMessageBatchIndex, - uint256 _messageRoot, - uint256 _currentSbCommitment, - uint256 _newSbCommitment, - uint8 _messageTreeSubDepth, - uint8 _messageTreeDepth, - uint8 _voteOptionTreeDepth, - uint256[8] memory _proof - ) internal view returns (bool isValid) { - // get the tree depths - // get the message batch size from the message tree subdepth - // get the number of signups - (uint256 numSignUps, uint256 numMessages) = poll.numSignUpsAndMessages(); - (IMACI maci, , ) = poll.extContracts(); - - // Calculate the public input hash (a SHA256 hash of several values) - uint256 publicInputHash = genProcessMessagesPublicInputHash( - _currentMessageBatchIndex, - _messageRoot, - numSignUps, - numMessages, - _currentSbCommitment, - _newSbCommitment, - _messageTreeSubDepth, - _voteOptionTreeDepth - ); - - // Get the verifying key from the VkRegistry - IVkRegistry.Mode mode = isQv ? IVkRegistry.Mode.QV : IVkRegistry.Mode.NON_QV; - VerifyingKey memory vk = vkRegistry.getProcessVk( - maci.stateTreeDepth(), - _messageTreeDepth, - _voteOptionTreeDepth, - TREE_ARITY ** _messageTreeSubDepth, - mode - ); - - isValid = verifier.verify(_proof, vk, publicInputHash); - } - - /// @notice Returns the SHA256 hash of the packed values (see - /// genProcessMessagesPackedVals), the hash of the coordinator's public key, - /// the message root, and the commitment to the current state root and - /// ballot root. By passing the SHA256 hash of these values to the circuit - /// 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 longer. - /// @param _currentMessageBatchIndex The batch index of current message batch - /// @param _numSignUps The number of users that signup - /// @param _numMessages The number of messages - /// @param _currentSbCommitment The current sbCommitment (state and ballot root) - /// @param _newSbCommitment The new sbCommitment after we update this message batch - /// @param _messageTreeSubDepth The message tree subdepth - /// @return inputHash Returns the SHA256 hash of the packed values - function genProcessMessagesPublicInputHash( - uint256 _currentMessageBatchIndex, - uint256 _messageRoot, - uint256 _numSignUps, - uint256 _numMessages, - uint256 _currentSbCommitment, - uint256 _newSbCommitment, - uint8 _messageTreeSubDepth, - uint8 _voteOptionTreeDepth - ) public view returns (uint256 inputHash) { - uint256 coordinatorPubKeyHash = poll.coordinatorPubKeyHash(); - - // pack the values - uint256 packedVals = genProcessMessagesPackedVals( - _currentMessageBatchIndex, - _numSignUps, - _numMessages, - _messageTreeSubDepth, - _voteOptionTreeDepth - ); - - (uint256 deployTime, uint256 duration) = poll.getDeployTimeAndDuration(); - - // generate the circuit only public input - uint256[] memory input = new uint256[](6); - input[0] = packedVals; - input[1] = coordinatorPubKeyHash; - input[2] = _messageRoot; - input[3] = _currentSbCommitment; - input[4] = _newSbCommitment; - input[5] = deployTime + duration; - inputHash = sha256Hash(input); - } - - /// @notice One of the inputs to the ProcessMessages circuit is a 250-bit - /// representation of four 50-bit values. This function generates this - /// 250-bit value, which consists of the maximum number of vote options, the - /// number of signups, the current message batch index, and the end index of - /// the current batch. - /// @param _currentMessageBatchIndex batch index of current message batch - /// @param _numSignUps number of users that signup - /// @param _numMessages number of messages - /// @param _messageTreeSubDepth message tree subdepth - /// @param _voteOptionTreeDepth vote option tree depth - /// @return result The packed value - function genProcessMessagesPackedVals( - uint256 _currentMessageBatchIndex, - uint256 _numSignUps, - uint256 _numMessages, - uint8 _messageTreeSubDepth, - uint8 _voteOptionTreeDepth - ) public pure returns (uint256 result) { - uint256 maxVoteOptions = TREE_ARITY ** _voteOptionTreeDepth; - - // calculate the message batch size from the message tree subdepth - uint256 messageBatchSize = TREE_ARITY ** _messageTreeSubDepth; - uint256 batchEndIndex = _currentMessageBatchIndex + messageBatchSize; - if (batchEndIndex > _numMessages) { - batchEndIndex = _numMessages; - } - - if (maxVoteOptions >= 2 ** 50) revert MaxVoteOptionsTooLarge(); - if (_numSignUps >= 2 ** 50) revert NumSignUpsTooLarge(); - if (_currentMessageBatchIndex >= 2 ** 50) revert CurrentMessageBatchIndexTooLarge(); - if (batchEndIndex >= 2 ** 50) revert BatchEndIndexTooLarge(); - - result = maxVoteOptions + (_numSignUps << 50) + (_currentMessageBatchIndex << 100) + (batchEndIndex << 150); - } - - /// @notice update message processing state variables - /// @param _newSbCommitment sbCommitment to be updated - /// @param _currentMessageBatchIndex currentMessageBatchIndex to be updated - /// @param _processingComplete update flag that indicate processing is finished or not - function updateMessageProcessingData( - uint256 _newSbCommitment, - uint256 _currentMessageBatchIndex, - bool _processingComplete - ) internal { - sbCommitment = _newSbCommitment; - processingComplete = _processingComplete; - currentMessageBatchIndex = _currentMessageBatchIndex; - numBatchesProcessed++; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/MessageProcessorFactory.sol b/packages/hardhat/contracts/maci-contracts/MessageProcessorFactory.sol deleted file mode 100644 index b08d9c4..0000000 --- a/packages/hardhat/contracts/maci-contracts/MessageProcessorFactory.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { Params } from "./utilities/Params.sol"; -import { DomainObjs } from "./utilities/DomainObjs.sol"; -import { MessageProcessor } from "./MessageProcessor.sol"; -import { IMessageProcessorFactory } from "./interfaces/IMPFactory.sol"; - -/// @title MessageProcessorFactory -/// @notice A factory contract which deploys MessageProcessor contracts. -contract MessageProcessorFactory is Params, DomainObjs, IMessageProcessorFactory { - /// @inheritdoc IMessageProcessorFactory - function deploy( - address _verifier, - address _vkRegistry, - address _poll, - address _owner, - bool _isQv - ) public returns (address messageProcessorAddr) { - // deploy MessageProcessor for this Poll - MessageProcessor messageProcessor = new MessageProcessor(_verifier, _vkRegistry, _poll, _isQv); - messageProcessor.transferOwnership(_owner); - messageProcessorAddr = address(messageProcessor); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/Poll.sol b/packages/hardhat/contracts/maci-contracts/Poll.sol deleted file mode 100644 index 4af1cdc..0000000 --- a/packages/hardhat/contracts/maci-contracts/Poll.sol +++ /dev/null @@ -1,281 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { Params } from "./utilities/Params.sol"; -import { SnarkCommon } from "./crypto/SnarkCommon.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 { EmptyBallotRoots } from "./trees/EmptyBallotRoots.sol"; -import { IPoll } from "./interfaces/IPoll.sol"; -import { Utilities } from "./utilities/Utilities.sol"; - -/// @title Poll -/// @notice A Poll contract allows voters to submit encrypted messages -/// which can be either votes, key change messages or topup messages. -/// @dev Do not deploy this directly. Use PollFactory.deploy() which performs some -/// checks on the Poll constructor arguments. -contract Poll is Params, Utilities, SnarkCommon, Ownable, EmptyBallotRoots, IPoll { - using SafeERC20 for ERC20; - - /// @notice Whether the Poll has been initialized - bool internal isInit; - - /// @notice The coordinator's public key - PubKey public coordinatorPubKey; - - /// @notice Hash of the coordinator's public key - uint256 public immutable coordinatorPubKeyHash; - - /// @notice the state root of the state merkle tree - uint256 public mergedStateRoot; - - // The timestamp of the block at which the Poll was deployed - uint256 internal immutable deployTime; - - // The duration of the polling period, in seconds - uint256 internal immutable duration; - - /// @notice Whether the MACI contract's stateAq has been merged by this contract - bool public stateAqMerged; - - /// @notice Get the commitment to the state leaves and the ballots. This is - /// hash3(stateRoot, ballotRoot, salt). - /// Its initial value should be - /// hash(maciStateRootSnapshot, emptyBallotRoot, 0) - /// Each successful invocation of processMessages() should use a different - /// salt to update this value, so that an external observer cannot tell in - /// the case that none of the messages are valid. - uint256 public currentSbCommitment; - - /// @notice The number of messages that have been published - uint256 public numMessages; - - /// @notice The number of signups that have been processed - /// before the Poll ended (stateAq merged) - uint256 public numSignups; - - /// @notice Max values for the poll - MaxValues public maxValues; - - /// @notice Depths of the merkle trees - TreeDepths public treeDepths; - - /// @notice The contracts used by the Poll - ExtContracts public extContracts; - - error VotingPeriodOver(); - error VotingPeriodNotOver(); - error PollAlreadyInit(); - error TooManyMessages(); - error MaciPubKeyLargerThanSnarkFieldSize(); - error StateAqAlreadyMerged(); - error StateAqSubtreesNeedMerge(); - error InvalidBatchLength(); - - event PublishMessage(Message _message, PubKey _encPubKey); - event TopupMessage(Message _message); - event MergeMaciStateAqSubRoots(uint256 indexed _numSrQueueOps); - event MergeMaciStateAq(uint256 indexed _stateRoot, uint256 indexed _numSignups); - event MergeMessageAqSubRoots(uint256 indexed _numSrQueueOps); - event MergeMessageAq(uint256 indexed _messageRoot); - - /// @notice Each MACI instance can have multiple Polls. - /// When a Poll is deployed, its voting period starts immediately. - /// @param _duration The duration of the voting period, in seconds - /// @param _maxValues The maximum number of messages and vote options - /// @param _treeDepths The depths of the merkle trees - /// @param _coordinatorPubKey The coordinator's public key - /// @param _extContracts The external contracts - constructor( - uint256 _duration, - MaxValues memory _maxValues, - TreeDepths memory _treeDepths, - PubKey memory _coordinatorPubKey, - ExtContracts memory _extContracts - ) payable { - // check that the coordinator public key is valid - if (_coordinatorPubKey.x >= SNARK_SCALAR_FIELD || _coordinatorPubKey.y >= SNARK_SCALAR_FIELD) { - revert MaciPubKeyLargerThanSnarkFieldSize(); - } - - // store the pub key as object then calculate the hash - coordinatorPubKey = _coordinatorPubKey; - // we hash it ourselves to ensure we store the correct value - coordinatorPubKeyHash = hashLeftRight(_coordinatorPubKey.x, _coordinatorPubKey.y); - // store the external contracts to interact with - extContracts = _extContracts; - // store duration of the poll - duration = _duration; - // store max values - maxValues = _maxValues; - // store tree depth - treeDepths = _treeDepths; - // Record the current timestamp - deployTime = block.timestamp; - } - - /// @notice A modifier that causes the function to revert if the voting period is - /// not over. - modifier isAfterVotingDeadline() { - uint256 secondsPassed = block.timestamp - deployTime; - if (secondsPassed <= duration) revert VotingPeriodNotOver(); - _; - } - - /// @notice A modifier that causes the function to revert if the voting period is - /// over - modifier isWithinVotingDeadline() { - uint256 secondsPassed = block.timestamp - deployTime; - if (secondsPassed >= duration) revert VotingPeriodOver(); - _; - } - - /// @notice The initialization function. - /// @dev Should be called immediately after Poll creation - /// and messageAq ownership transferred - function init() public { - if (isInit) revert PollAlreadyInit(); - // set to true so it cannot be called again - isInit = true; - - unchecked { - numMessages++; - } - - // init messageAq here by inserting placeholderLeaf - uint256[2] memory dat = [NOTHING_UP_MY_SLEEVE, 0]; - - (Message memory _message, PubKey memory _padKey, uint256 placeholderLeaf) = padAndHashMessage(dat, 1); - extContracts.messageAq.enqueue(placeholderLeaf); - - emit PublishMessage(_message, _padKey); - } - - /// @inheritdoc IPoll - function topup(uint256 stateIndex, uint256 amount) public virtual isWithinVotingDeadline { - // we check that we do not exceed the max number of messages - if (numMessages >= maxValues.maxMessages) revert TooManyMessages(); - - // cannot realistically overflow - unchecked { - numMessages++; - } - - /// @notice topupCredit is a trusted token contract which reverts if the transfer fails - extContracts.topupCredit.transferFrom(msg.sender, address(this), amount); - - uint256[2] memory dat = [stateIndex, amount]; - (Message memory _message, , uint256 messageLeaf) = padAndHashMessage(dat, 2); - - extContracts.messageAq.enqueue(messageLeaf); - - emit TopupMessage(_message); - } - - /// @inheritdoc IPoll - function publishMessage(Message memory _message, PubKey calldata _encPubKey) public virtual isWithinVotingDeadline { - // we check that we do not exceed the max number of messages - if (numMessages >= maxValues.maxMessages) revert TooManyMessages(); - - // validate that the public key is valid - if (_encPubKey.x >= SNARK_SCALAR_FIELD || _encPubKey.y >= SNARK_SCALAR_FIELD) { - revert MaciPubKeyLargerThanSnarkFieldSize(); - } - - // cannot realistically overflow - unchecked { - numMessages++; - } - - // we enforce that msgType here is 1 so we don't need checks - // at the circuit level - _message.msgType = 1; - - uint256 messageLeaf = hashMessageAndEncPubKey(_message, _encPubKey); - extContracts.messageAq.enqueue(messageLeaf); - - emit PublishMessage(_message, _encPubKey); - } - - /// @notice submit a message batch - /// @dev Can only be submitted before the voting deadline - /// @param _messages the messages - /// @param _encPubKeys the encrypted public keys - function publishMessageBatch(Message[] calldata _messages, PubKey[] calldata _encPubKeys) external { - if (_messages.length != _encPubKeys.length) { - revert InvalidBatchLength(); - } - - uint256 len = _messages.length; - for (uint256 i = 0; i < len; ) { - // an event will be published by this function already - publishMessage(_messages[i], _encPubKeys[i]); - - unchecked { - i++; - } - } - } - - /// @inheritdoc IPoll - function mergeMaciStateAqSubRoots(uint256 _numSrQueueOps, uint256 _pollId) public onlyOwner isAfterVotingDeadline { - // This function cannot be called after the stateAq was merged - if (stateAqMerged) revert StateAqAlreadyMerged(); - - // merge subroots - extContracts.maci.mergeStateAqSubRoots(_numSrQueueOps, _pollId); - - emit MergeMaciStateAqSubRoots(_numSrQueueOps); - } - - /// @inheritdoc IPoll - function mergeMaciStateAq(uint256 _pollId) public onlyOwner isAfterVotingDeadline { - // This function can only be called once per Poll after the voting - // deadline - if (stateAqMerged) revert StateAqAlreadyMerged(); - - // set merged to true so it cannot be called again - stateAqMerged = true; - - // the subtrees must have been merged first - if (!extContracts.maci.stateAq().subTreesMerged()) revert StateAqSubtreesNeedMerge(); - - mergedStateRoot = extContracts.maci.mergeStateAq(_pollId); - - // Set currentSbCommitment - uint256[3] memory sb; - sb[0] = mergedStateRoot; - sb[1] = emptyBallotRoots[treeDepths.voteOptionTreeDepth - 1]; - sb[2] = uint256(0); - - currentSbCommitment = hash3(sb); - - numSignups = extContracts.maci.numSignUps(); - emit MergeMaciStateAq(mergedStateRoot, numSignups); - } - - /// @inheritdoc IPoll - function mergeMessageAqSubRoots(uint256 _numSrQueueOps) public onlyOwner isAfterVotingDeadline { - extContracts.messageAq.mergeSubRoots(_numSrQueueOps); - emit MergeMessageAqSubRoots(_numSrQueueOps); - } - - /// @inheritdoc IPoll - function mergeMessageAq() public onlyOwner isAfterVotingDeadline { - uint256 root = extContracts.messageAq.merge(treeDepths.messageTreeDepth); - emit MergeMessageAq(root); - } - - /// @inheritdoc IPoll - function getDeployTimeAndDuration() public view returns (uint256 pollDeployTime, uint256 pollDuration) { - pollDeployTime = deployTime; - pollDuration = duration; - } - - /// @inheritdoc IPoll - function numSignUpsAndMessages() public view returns (uint256 numSUps, uint256 numMsgs) { - numSUps = numSignups; - numMsgs = numMessages; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/PollFactory.sol b/packages/hardhat/contracts/maci-contracts/PollFactory.sol deleted file mode 100644 index ae62a1b..0000000 --- a/packages/hardhat/contracts/maci-contracts/PollFactory.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { IMACI } from "./interfaces/IMACI.sol"; -import { AccQueue } from "./trees/AccQueue.sol"; -import { AccQueueQuinaryMaci } from "./trees/AccQueueQuinaryMaci.sol"; -import { TopupCredit } from "./TopupCredit.sol"; -import { Params } from "./utilities/Params.sol"; -import { DomainObjs } from "./utilities/DomainObjs.sol"; -import { Poll } from "./Poll.sol"; -import { IPollFactory } from "./interfaces/IPollFactory.sol"; - -/// @title PollFactory -/// @notice 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, DomainObjs, IPollFactory { - // The number of children each node in the message tree has - uint256 internal constant TREE_ARITY = 5; - - // custom error - error InvalidMaxValues(); - - /// @notice The PollFactory constructor - // solhint-disable-next-line no-empty-blocks - constructor() payable {} - - /// @inheritdoc IPollFactory - function deploy( - uint256 _duration, - MaxValues calldata _maxValues, - TreeDepths calldata _treeDepths, - PubKey calldata _coordinatorPubKey, - address _maci, - TopupCredit _topupCredit, - address _pollOwner - ) public virtual returns (address pollAddr) { - /// @notice Validate _maxValues - /// maxVoteOptions must be less than 2 ** 50 due to circuit limitations; - /// it will be packed as a 50-bit value along with other values as one - /// of the inputs (aka packedVal) - if (_maxValues.maxVoteOptions >= (2 ** 50)) { - revert InvalidMaxValues(); - } - - /// @notice deploy a new AccQueue contract to store messages - AccQueue messageAq = new AccQueueQuinaryMaci(_treeDepths.messageTreeSubDepth); - - /// @notice the smart contracts that a Poll would interact with - ExtContracts memory extContracts = ExtContracts({ - maci: IMACI(_maci), - messageAq: messageAq, - topupCredit: _topupCredit - }); - - // deploy the poll - Poll poll = new Poll(_duration, _maxValues, _treeDepths, _coordinatorPubKey, extContracts); - - // Make the Poll contract own the messageAq contract, so only it can - // run enqueue/merge - messageAq.transferOwnership(address(poll)); - - // init Poll - poll.init(); - - poll.transferOwnership(_pollOwner); - - pollAddr = address(poll); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/SignUpToken.sol b/packages/hardhat/contracts/maci-contracts/SignUpToken.sol deleted file mode 100644 index 759a719..0000000 --- a/packages/hardhat/contracts/maci-contracts/SignUpToken.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; - -/// @title SignUpToken -/// @notice This contract is an ERC721 token contract which -/// can be used to allow users to sign up for a poll. -contract SignUpToken is ERC721, Ownable { - /// @notice The constructor which calls the ERC721 constructor - constructor() payable ERC721("SignUpToken", "SignUpToken") {} - - /// @notice Gives an ERC721 token to an address - /// @param to The address to give the token to - /// @param curTokenId The token id to give - function giveToken(address to, uint256 curTokenId) public onlyOwner { - _mint(to, curTokenId); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/Tally.sol b/packages/hardhat/contracts/maci-contracts/Tally.sol deleted file mode 100644 index bc98405..0000000 --- a/packages/hardhat/contracts/maci-contracts/Tally.sol +++ /dev/null @@ -1,373 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { IMACI } from "./interfaces/IMACI.sol"; -import { Hasher } from "./crypto/Hasher.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { IPoll } from "./interfaces/IPoll.sol"; -import { IMessageProcessor } from "./interfaces/IMessageProcessor.sol"; -import { SnarkCommon } from "./crypto/SnarkCommon.sol"; -import { IVerifier } from "./interfaces/IVerifier.sol"; -import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; -import { CommonUtilities } from "./utilities/CommonUtilities.sol"; - -/// @title Tally -/// @notice The Tally contract is used during votes tallying -/// and by users to verify the tally results. -contract Tally is Ownable, SnarkCommon, CommonUtilities, Hasher { - uint256 internal constant TREE_ARITY = 5; - - /// @notice The commitment to the tally results. Its initial value is 0, but after - /// the tally of each batch is proven on-chain via a zk-SNARK, it should be - /// updated to: - /// - /// QV: - /// hash3( - /// hashLeftRight(merkle root of current results, salt0) - /// hashLeftRight(number of spent voice credits, salt1), - /// hashLeftRight(merkle root of the no. of spent voice credits per vote option, salt2) - /// ) - /// - /// Non-QV: - /// hash2( - /// hashLeftRight(merkle root of current results, salt0) - /// hashLeftRight(number of spent voice credits, salt1), - /// ) - /// - /// Where each salt is unique and the merkle roots are of arrays of leaves - /// TREE_ARITY ** voteOptionTreeDepth long. - uint256 public tallyCommitment; - - uint256 public tallyBatchNum; - - // The final commitment to the state and ballot roots - uint256 public sbCommitment; - - IVerifier public immutable verifier; - IVkRegistry public immutable vkRegistry; - IPoll public immutable poll; - IMessageProcessor public immutable messageProcessor; - bool public immutable isQv; - - /// @notice custom errors - error ProcessingNotComplete(); - error InvalidTallyVotesProof(); - error AllBallotsTallied(); - error NumSignUpsTooLarge(); - error BatchStartIndexTooLarge(); - error TallyBatchSizeTooLarge(); - error NotSupported(); - - /// @notice Create a new Tally contract - /// @param _verifier The Verifier contract - /// @param _vkRegistry The VkRegistry contract - /// @param _poll The Poll contract - /// @param _mp The MessageProcessor contract - constructor(address _verifier, address _vkRegistry, address _poll, address _mp, bool _isQv) payable { - verifier = IVerifier(_verifier); - vkRegistry = IVkRegistry(_vkRegistry); - poll = IPoll(_poll); - messageProcessor = IMessageProcessor(_mp); - isQv = _isQv; - } - - /// @notice Pack the batch start index and number of signups into a 100-bit value. - /// @param _numSignUps: number of signups - /// @param _batchStartIndex: the start index of given batch - /// @param _tallyBatchSize: size of batch - /// @return result an uint256 representing the 3 inputs packed together - function genTallyVotesPackedVals( - uint256 _numSignUps, - uint256 _batchStartIndex, - uint256 _tallyBatchSize - ) public pure returns (uint256 result) { - if (_numSignUps >= 2 ** 50) revert NumSignUpsTooLarge(); - if (_batchStartIndex >= 2 ** 50) revert BatchStartIndexTooLarge(); - if (_tallyBatchSize >= 2 ** 50) revert TallyBatchSizeTooLarge(); - - result = (_batchStartIndex / _tallyBatchSize) + (_numSignUps << uint256(50)); - } - - /// @notice Check if all ballots are tallied - /// @return tallied whether all ballots are tallied - function isTallied() public view returns (bool tallied) { - (uint8 intStateTreeDepth, , , ) = poll.treeDepths(); - (uint256 numSignUps, ) = poll.numSignUpsAndMessages(); - - // Require that there are untallied ballots left - tallied = tallyBatchNum * (TREE_ARITY ** intStateTreeDepth) >= numSignUps; - } - - /// @notice generate hash of public inputs for tally circuit - /// @param _numSignUps: number of signups - /// @param _batchStartIndex: the start index of given batch - /// @param _tallyBatchSize: size of batch - /// @param _newTallyCommitment: the new tally commitment to be updated - /// @return inputHash hash of public inputs - function genTallyVotesPublicInputHash( - uint256 _numSignUps, - uint256 _batchStartIndex, - uint256 _tallyBatchSize, - uint256 _newTallyCommitment - ) public view returns (uint256 inputHash) { - uint256 packedVals = genTallyVotesPackedVals(_numSignUps, _batchStartIndex, _tallyBatchSize); - uint256[] memory input = new uint256[](4); - input[0] = packedVals; - input[1] = sbCommitment; - input[2] = tallyCommitment; - input[3] = _newTallyCommitment; - inputHash = sha256Hash(input); - } - - /// @notice Update the state and ballot root commitment - function updateSbCommitment() public onlyOwner { - // Require that all messages have been processed - if (!messageProcessor.processingComplete()) { - revert ProcessingNotComplete(); - } - - if (sbCommitment == 0) { - sbCommitment = messageProcessor.sbCommitment(); - } - } - - /// @notice Verify the result of a tally batch - /// @param _newTallyCommitment the new tally commitment to be verified - /// @param _proof the proof generated after tallying this batch - function tallyVotes(uint256 _newTallyCommitment, uint256[8] calldata _proof) public onlyOwner { - _votingPeriodOver(poll); - updateSbCommitment(); - - // get the batch size and start index - (uint8 intStateTreeDepth, , , ) = poll.treeDepths(); - uint256 tallyBatchSize = TREE_ARITY ** intStateTreeDepth; - uint256 batchStartIndex = tallyBatchNum * tallyBatchSize; - - // save some gas because we won't overflow uint256 - unchecked { - tallyBatchNum++; - } - - (uint256 numSignUps, ) = poll.numSignUpsAndMessages(); - - // Require that there are untallied ballots left - if (batchStartIndex >= numSignUps) { - revert AllBallotsTallied(); - } - - bool isValid = verifyTallyProof(_proof, numSignUps, batchStartIndex, tallyBatchSize, _newTallyCommitment); - - if (!isValid) { - revert InvalidTallyVotesProof(); - } - - // Update the tally commitment and the tally batch num - tallyCommitment = _newTallyCommitment; - } - - /// @notice Verify the tally proof using the verifying key - /// @param _proof the proof generated after processing all messages - /// @param _numSignUps number of signups for a given poll - /// @param _batchStartIndex the number of batches multiplied by the size of the batch - /// @param _tallyBatchSize batch size for the tally - /// @param _newTallyCommitment the tally commitment to be verified at a given batch index - /// @return isValid whether the proof is valid - function verifyTallyProof( - uint256[8] calldata _proof, - uint256 _numSignUps, - uint256 _batchStartIndex, - uint256 _tallyBatchSize, - uint256 _newTallyCommitment - ) public view returns (bool isValid) { - (uint8 intStateTreeDepth, , , uint8 voteOptionTreeDepth) = poll.treeDepths(); - - (IMACI maci, , ) = poll.extContracts(); - - // Get the verifying key - IVkRegistry.Mode mode = isQv ? IVkRegistry.Mode.QV : IVkRegistry.Mode.NON_QV; - VerifyingKey memory vk = vkRegistry.getTallyVk(maci.stateTreeDepth(), intStateTreeDepth, voteOptionTreeDepth, mode); - - // Get the public inputs - uint256 publicInputHash = genTallyVotesPublicInputHash( - _numSignUps, - _batchStartIndex, - _tallyBatchSize, - _newTallyCommitment - ); - - // Verify the proof - isValid = verifier.verify(_proof, vk, publicInputHash); - } - - /// @notice Compute the merkle root from the path elements - /// and a leaf - /// @param _depth the depth of the merkle tree - /// @param _index the index of the leaf - /// @param _leaf the leaf - /// @param _pathElements the path elements to reconstruct the merkle root - /// @return current The merkle root - function computeMerkleRootFromPath( - uint8 _depth, - uint256 _index, - uint256 _leaf, - uint256[][] calldata _pathElements - ) internal pure returns (uint256 current) { - uint256 pos = _index % TREE_ARITY; - current = _leaf; - uint8 k; - - uint256[TREE_ARITY] memory level; - - for (uint8 i = 0; i < _depth; ++i) { - for (uint8 j = 0; j < TREE_ARITY; ++j) { - if (j == pos) { - level[j] = current; - } else { - if (j > pos) { - k = j - 1; - } else { - k = j; - } - level[j] = _pathElements[i][k]; - } - } - - _index /= TREE_ARITY; - pos = _index % TREE_ARITY; - current = hash5(level); - } - } - - /// @notice Verify the number of spent voice credits from the tally.json - /// @param _totalSpent spent field retrieved in the totalSpentVoiceCredits object - /// @param _totalSpentSalt the corresponding salt in the totalSpentVoiceCredit object - /// @param _resultCommitment hashLeftRight(merkle root of the results.tally, results.salt) in tally.json file - /// @param _perVOSpentVoiceCreditsHash only for QV - hashLeftRight(merkle root of the no spent voice credits per vote option, salt) - /// @return isValid Whether the provided values are valid - function verifySpentVoiceCredits( - uint256 _totalSpent, - uint256 _totalSpentSalt, - uint256 _resultCommitment, - uint256 _perVOSpentVoiceCreditsHash - ) public view returns (bool isValid) { - uint256[3] memory tally; - tally[0] = _resultCommitment; - tally[1] = hashLeftRight(_totalSpent, _totalSpentSalt); - tally[2] = _perVOSpentVoiceCreditsHash; - - isValid = isQv - ? verifyQvSpentVoiceCredits(_totalSpent, _totalSpentSalt, _resultCommitment, _perVOSpentVoiceCreditsHash) - : verifyNonQvSpentVoiceCredits(_totalSpent, _totalSpentSalt, _resultCommitment); - } - - /// @notice Verify the number of spent voice credits for QV from the tally.json - /// @param _totalSpent spent field retrieved in the totalSpentVoiceCredits object - /// @param _totalSpentSalt the corresponding salt in the totalSpentVoiceCredit object - /// @param _resultCommitment hashLeftRight(merkle root of the results.tally, results.salt) in tally.json file - /// @param _perVOSpentVoiceCreditsHash hashLeftRight(merkle root of the no spent voice credits per vote option, salt) - /// @return isValid Whether the provided values are valid - function verifyQvSpentVoiceCredits( - uint256 _totalSpent, - uint256 _totalSpentSalt, - uint256 _resultCommitment, - uint256 _perVOSpentVoiceCreditsHash - ) internal view returns (bool isValid) { - uint256[3] memory tally; - tally[0] = _resultCommitment; - tally[1] = hashLeftRight(_totalSpent, _totalSpentSalt); - tally[2] = _perVOSpentVoiceCreditsHash; - - isValid = hash3(tally) == tallyCommitment; - } - - /// @notice Verify the number of spent voice credits for Non-QV from the tally.json - /// @param _totalSpent spent field retrieved in the totalSpentVoiceCredits object - /// @param _totalSpentSalt the corresponding salt in the totalSpentVoiceCredit object - /// @param _resultCommitment hashLeftRight(merkle root of the results.tally, results.salt) in tally.json file - /// @return isValid Whether the provided values are valid - function verifyNonQvSpentVoiceCredits( - uint256 _totalSpent, - uint256 _totalSpentSalt, - uint256 _resultCommitment - ) internal view returns (bool isValid) { - uint256[2] memory tally; - tally[0] = _resultCommitment; - tally[1] = hashLeftRight(_totalSpent, _totalSpentSalt); - - isValid = hash2(tally) == tallyCommitment; - } - - /// @notice Verify the number of spent voice credits per vote option from the tally.json - /// @param _voteOptionIndex the index of the vote option where credits were spent - /// @param _spent the spent voice credits for a given vote option index - /// @param _spentProof proof generated for the perVOSpentVoiceCredits - /// @param _spentSalt the corresponding salt given in the tally perVOSpentVoiceCredits object - /// @param _voteOptionTreeDepth depth of the vote option tree - /// @param _spentVoiceCreditsHash hashLeftRight(number of spent voice credits, spent salt) - /// @param _resultCommitment hashLeftRight(merkle root of the results.tally, results.salt) - // in the tally.json file - /// @return isValid Whether the provided proof is valid - function verifyPerVOSpentVoiceCredits( - uint256 _voteOptionIndex, - uint256 _spent, - uint256[][] calldata _spentProof, - uint256 _spentSalt, - uint8 _voteOptionTreeDepth, - uint256 _spentVoiceCreditsHash, - uint256 _resultCommitment - ) public view returns (bool isValid) { - if (!isQv) { - revert NotSupported(); - } - - uint256 computedRoot = computeMerkleRootFromPath(_voteOptionTreeDepth, _voteOptionIndex, _spent, _spentProof); - - uint256[3] memory tally; - tally[0] = _resultCommitment; - tally[1] = _spentVoiceCreditsHash; - tally[2] = hashLeftRight(computedRoot, _spentSalt); - - isValid = hash3(tally) == tallyCommitment; - } - - /// @notice Verify the result generated from the tally.json - /// @param _voteOptionIndex the index of the vote option to verify the correctness of the tally - /// @param _tallyResult Flattened array of the tally - /// @param _tallyResultProof Corresponding proof of the tally result - /// @param _tallyResultSalt the respective salt in the results object in the tally.json - /// @param _voteOptionTreeDepth depth of the vote option tree - /// @param _spentVoiceCreditsHash hashLeftRight(number of spent voice credits, spent salt) - /// @param _perVOSpentVoiceCreditsHash hashLeftRight(merkle root of the no spent voice - /// credits per vote option, perVOSpentVoiceCredits salt) - /// @return isValid Whether the provided proof is valid - function verifyTallyResult( - uint256 _voteOptionIndex, - uint256 _tallyResult, - uint256[][] calldata _tallyResultProof, - uint256 _tallyResultSalt, - uint8 _voteOptionTreeDepth, - uint256 _spentVoiceCreditsHash, - uint256 _perVOSpentVoiceCreditsHash - ) public view returns (bool isValid) { - uint256 computedRoot = computeMerkleRootFromPath( - _voteOptionTreeDepth, - _voteOptionIndex, - _tallyResult, - _tallyResultProof - ); - - if (isQv) { - uint256[3] memory tally = [ - hashLeftRight(computedRoot, _tallyResultSalt), - _spentVoiceCreditsHash, - _perVOSpentVoiceCreditsHash - ]; - - isValid = hash3(tally) == tallyCommitment; - } else { - uint256[2] memory tally = [hashLeftRight(computedRoot, _tallyResultSalt), _spentVoiceCreditsHash]; - - isValid = hash2(tally) == tallyCommitment; - } - } -} diff --git a/packages/hardhat/contracts/maci-contracts/TallyFactory.sol b/packages/hardhat/contracts/maci-contracts/TallyFactory.sol deleted file mode 100644 index dc458cc..0000000 --- a/packages/hardhat/contracts/maci-contracts/TallyFactory.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { Tally } from "./Tally.sol"; -import { ITallyFactory } from "./interfaces/ITallyFactory.sol"; - -/// @title TallyFactory -/// @notice A factory contract which deploys Tally contracts. -contract TallyFactory is ITallyFactory { - /// @inheritdoc ITallyFactory - function deploy( - address _verifier, - address _vkRegistry, - address _poll, - address _messageProcessor, - address _owner, - bool _isQv - ) public virtual returns (address tallyAddr) { - // deploy Tally for this Poll - Tally tally = new Tally(_verifier, _vkRegistry, _poll, _messageProcessor, _isQv); - tally.transferOwnership(_owner); - tallyAddr = address(tally); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/TopupCredit.sol b/packages/hardhat/contracts/maci-contracts/TopupCredit.sol deleted file mode 100644 index e3c09f7..0000000 --- a/packages/hardhat/contracts/maci-contracts/TopupCredit.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; - -/// @title TopupCredit -/// @notice A contract representing a token used to topup a MACI's voter -/// credits -contract TopupCredit is ERC20, Ownable { - uint8 public constant DECIMALS = 1; - uint256 public constant MAXIMUM_AIRDROP_AMOUNT = 100000 * 10 ** DECIMALS; - - /// @notice custom errors - error ExceedLimit(); - - /// @notice create a new TopupCredit token - constructor() payable ERC20("TopupCredit", "TopupCredit") {} - - /// @notice mint tokens to an account - /// @param account the account to mint tokens to - /// @param amount the amount of tokens to mint - function airdropTo(address account, uint256 amount) public onlyOwner { - if (amount >= MAXIMUM_AIRDROP_AMOUNT) { - revert ExceedLimit(); - } - - _mint(account, amount); - } - - /// @notice mint tokens to the contract owner - /// @param amount the amount of tokens to mint - function airdrop(uint256 amount) public onlyOwner { - if (amount >= MAXIMUM_AIRDROP_AMOUNT) { - revert ExceedLimit(); - } - - _mint(msg.sender, amount); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/VkRegistry.sol b/packages/hardhat/contracts/maci-contracts/VkRegistry.sol deleted file mode 100644 index e66bd83..0000000 --- a/packages/hardhat/contracts/maci-contracts/VkRegistry.sol +++ /dev/null @@ -1,210 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { SnarkCommon } from "./crypto/SnarkCommon.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { IVkRegistry } from "./interfaces/IVkRegistry.sol"; - -/// @title VkRegistry -/// @notice Stores verifying keys for the circuits. -/// Each circuit has a signature which is its compile-time constants represented -/// as a uint256. -contract VkRegistry is Ownable, SnarkCommon, IVkRegistry { - mapping(Mode => mapping(uint256 => VerifyingKey)) internal processVks; - mapping(Mode => mapping(uint256 => bool)) internal processVkSet; - - mapping(Mode => mapping(uint256 => VerifyingKey)) internal tallyVks; - mapping(Mode => mapping(uint256 => bool)) internal tallyVkSet; - - event ProcessVkSet(uint256 _sig, Mode mode); - event TallyVkSet(uint256 _sig, Mode mode); - - error ProcessVkAlreadySet(); - error TallyVkAlreadySet(); - error ProcessVkNotSet(); - error TallyVkNotSet(); - error SubsidyVkNotSet(); - - /// @notice Create a new instance of the VkRegistry contract - // solhint-disable-next-line no-empty-blocks - constructor() payable {} - - /// @notice Check if the process verifying key is set - /// @param _sig The signature - /// @param _mode QV or Non-QV - /// @return isSet whether the verifying key is set - function isProcessVkSet(uint256 _sig, Mode _mode) public view returns (bool isSet) { - isSet = processVkSet[_mode][_sig]; - } - - /// @notice Check if the tally verifying key is set - /// @param _sig The signature - /// @param _mode QV or Non-QV - /// @return isSet whether the verifying key is set - function isTallyVkSet(uint256 _sig, Mode _mode) public view returns (bool isSet) { - isSet = tallyVkSet[_mode][_sig]; - } - - /// @notice generate the signature for the process verifying key - /// @param _stateTreeDepth The state tree depth - /// @param _messageTreeDepth The message tree depth - /// @param _voteOptionTreeDepth The vote option tree depth - /// @param _messageBatchSize The message batch size - function genProcessVkSig( - uint256 _stateTreeDepth, - uint256 _messageTreeDepth, - uint256 _voteOptionTreeDepth, - uint256 _messageBatchSize - ) public pure returns (uint256 sig) { - sig = (_messageBatchSize << 192) + (_stateTreeDepth << 128) + (_messageTreeDepth << 64) + _voteOptionTreeDepth; - } - - /// @notice generate the signature for the tally verifying key - /// @param _stateTreeDepth The state tree depth - /// @param _intStateTreeDepth The intermediate state tree depth - /// @param _voteOptionTreeDepth The vote option tree depth - /// @return sig The signature - function genTallyVkSig( - uint256 _stateTreeDepth, - uint256 _intStateTreeDepth, - uint256 _voteOptionTreeDepth - ) public pure returns (uint256 sig) { - sig = (_stateTreeDepth << 128) + (_intStateTreeDepth << 64) + _voteOptionTreeDepth; - } - - /// @notice Set the process and tally verifying keys for a certain combination - /// of parameters - /// @param _stateTreeDepth The state tree depth - /// @param _intStateTreeDepth The intermediate state tree depth - /// @param _messageTreeDepth The message tree depth - /// @param _voteOptionTreeDepth The vote option tree depth - /// @param _messageBatchSize The message batch size - /// @param _mode QV or Non-QV - /// @param _processVk The process verifying key - /// @param _tallyVk The tally verifying key - function setVerifyingKeys( - uint256 _stateTreeDepth, - uint256 _intStateTreeDepth, - uint256 _messageTreeDepth, - uint256 _voteOptionTreeDepth, - uint256 _messageBatchSize, - Mode _mode, - VerifyingKey calldata _processVk, - VerifyingKey calldata _tallyVk - ) public onlyOwner { - uint256 processVkSig = genProcessVkSig(_stateTreeDepth, _messageTreeDepth, _voteOptionTreeDepth, _messageBatchSize); - - if (processVkSet[_mode][processVkSig]) revert ProcessVkAlreadySet(); - - uint256 tallyVkSig = genTallyVkSig(_stateTreeDepth, _intStateTreeDepth, _voteOptionTreeDepth); - - if (tallyVkSet[_mode][tallyVkSig]) revert TallyVkAlreadySet(); - - VerifyingKey storage processVk = processVks[_mode][processVkSig]; - processVk.alpha1 = _processVk.alpha1; - processVk.beta2 = _processVk.beta2; - processVk.gamma2 = _processVk.gamma2; - processVk.delta2 = _processVk.delta2; - - for (uint8 i = 0; i < _processVk.ic.length; i++) { - processVk.ic.push(_processVk.ic[i]); - } - - processVkSet[_mode][processVkSig] = true; - - VerifyingKey storage tallyVk = tallyVks[_mode][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++) { - tallyVk.ic.push(_tallyVk.ic[i]); - } - - tallyVkSet[_mode][tallyVkSig] = true; - - emit TallyVkSet(tallyVkSig, _mode); - emit ProcessVkSet(processVkSig, _mode); - } - - /// @notice Check if the process verifying key is set - /// @param _stateTreeDepth The state tree depth - /// @param _messageTreeDepth The message tree depth - /// @param _voteOptionTreeDepth The vote option tree depth - /// @param _messageBatchSize The message batch size - /// @param _mode QV or Non-QV - /// @return isSet whether the verifying key is set - function hasProcessVk( - uint256 _stateTreeDepth, - uint256 _messageTreeDepth, - uint256 _voteOptionTreeDepth, - uint256 _messageBatchSize, - Mode _mode - ) public view returns (bool isSet) { - uint256 sig = genProcessVkSig(_stateTreeDepth, _messageTreeDepth, _voteOptionTreeDepth, _messageBatchSize); - isSet = processVkSet[_mode][sig]; - } - - /// @notice Get the process verifying key by signature - /// @param _sig The signature - /// @param _mode QV or Non-QV - /// @return vk The verifying key - function getProcessVkBySig(uint256 _sig, Mode _mode) public view returns (VerifyingKey memory vk) { - if (!processVkSet[_mode][_sig]) revert ProcessVkNotSet(); - - vk = processVks[_mode][_sig]; - } - - /// @inheritdoc IVkRegistry - function getProcessVk( - uint256 _stateTreeDepth, - uint256 _messageTreeDepth, - uint256 _voteOptionTreeDepth, - uint256 _messageBatchSize, - Mode _mode - ) public view returns (VerifyingKey memory vk) { - uint256 sig = genProcessVkSig(_stateTreeDepth, _messageTreeDepth, _voteOptionTreeDepth, _messageBatchSize); - - vk = getProcessVkBySig(sig, _mode); - } - - /// @notice Check if the tally verifying key is set - /// @param _stateTreeDepth The state tree depth - /// @param _intStateTreeDepth The intermediate state tree depth - /// @param _voteOptionTreeDepth The vote option tree depth - /// @param _mode QV or Non-QV - /// @return isSet whether the verifying key is set - function hasTallyVk( - uint256 _stateTreeDepth, - uint256 _intStateTreeDepth, - uint256 _voteOptionTreeDepth, - Mode _mode - ) public view returns (bool isSet) { - uint256 sig = genTallyVkSig(_stateTreeDepth, _intStateTreeDepth, _voteOptionTreeDepth); - - isSet = tallyVkSet[_mode][sig]; - } - - /// @notice Get the tally verifying key by signature - /// @param _sig The signature - /// @param _mode QV or Non-QV - /// @return vk The verifying key - function getTallyVkBySig(uint256 _sig, Mode _mode) public view returns (VerifyingKey memory vk) { - if (!tallyVkSet[_mode][_sig]) revert TallyVkNotSet(); - - vk = tallyVks[_mode][_sig]; - } - - /// @inheritdoc IVkRegistry - function getTallyVk( - uint256 _stateTreeDepth, - uint256 _intStateTreeDepth, - uint256 _voteOptionTreeDepth, - Mode _mode - ) public view returns (VerifyingKey memory vk) { - uint256 sig = genTallyVkSig(_stateTreeDepth, _intStateTreeDepth, _voteOptionTreeDepth); - - vk = getTallyVkBySig(sig, _mode); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/benchmarks/HasherBenchmarks.sol b/packages/hardhat/contracts/maci-contracts/benchmarks/HasherBenchmarks.sol deleted file mode 100644 index 27327ef..0000000 --- a/packages/hardhat/contracts/maci-contracts/benchmarks/HasherBenchmarks.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { Hasher } from "../crypto/Hasher.sol"; - -/// @title HasherBenchmarks -/// @notice A contract used to benchmark the poseidon hash function -contract HasherBenchmarks is Hasher { - /// @notice Benchmark the poseidon hash function with 5 inputs - /// @param array The array of inputs to hash - /// @return result The hash of the inputs - function hash5Benchmark(uint256[5] memory array) public pure returns (uint256 result) { - result = hash5(array); - } - - /// @notice Benchmark the poseidon hash function with 2 inputs - /// @param _left The left input to hash - /// @param _right The right input to hash - /// @return result The hash of the two inputs - function hashLeftRightBenchmark(uint256 _left, uint256 _right) public pure returns (uint256 result) { - result = hashLeftRight(_left, _right); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/crypto/Hasher.sol b/packages/hardhat/contracts/maci-contracts/crypto/Hasher.sol deleted file mode 100644 index d332a8b..0000000 --- a/packages/hardhat/contracts/maci-contracts/crypto/Hasher.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { SnarkConstants } from "./SnarkConstants.sol"; -import { PoseidonT3 } from "./PoseidonT3.sol"; -import { PoseidonT4 } from "./PoseidonT4.sol"; -import { PoseidonT5 } from "./PoseidonT5.sol"; -import { PoseidonT6 } from "./PoseidonT6.sol"; - -/// @notice A SHA256 hash function for any number of input elements, and Poseidon hash -/// functions for 2, 3, 4, 5, and 12 input elements. -contract Hasher is SnarkConstants { - /// @notice Computes the SHA256 hash of an array of uint256 elements. - /// @param array The array of uint256 elements. - /// @return result The SHA256 hash of the array. - function sha256Hash(uint256[] memory array) public pure returns (uint256 result) { - result = uint256(sha256(abi.encodePacked(array))) % SNARK_SCALAR_FIELD; - } - - /// @notice Computes the Poseidon hash of two uint256 elements. - /// @param array An array of two uint256 elements. - /// @return result The Poseidon hash of the two elements. - function hash2(uint256[2] memory array) public pure returns (uint256 result) { - result = PoseidonT3.poseidon(array); - } - - /// @notice Computes the Poseidon hash of three uint256 elements. - /// @param array An array of three uint256 elements. - /// @return result The Poseidon hash of the three elements. - function hash3(uint256[3] memory array) public pure returns (uint256 result) { - result = PoseidonT4.poseidon(array); - } - - /// @notice Computes the Poseidon hash of four uint256 elements. - /// @param array An array of four uint256 elements. - /// @return result The Poseidon hash of the four elements. - function hash4(uint256[4] memory array) public pure returns (uint256 result) { - result = PoseidonT5.poseidon(array); - } - - /// @notice Computes the Poseidon hash of five uint256 elements. - /// @param array An array of five uint256 elements. - /// @return result The Poseidon hash of the five elements. - function hash5(uint256[5] memory array) public pure returns (uint256 result) { - result = PoseidonT6.poseidon(array); - } - - /// @notice Computes the Poseidon hash of two uint256 elements. - /// @param left the first element to hash. - /// @param right the second element to hash. - /// @return result The Poseidon hash of the two elements. - function hashLeftRight(uint256 left, uint256 right) public pure returns (uint256 result) { - uint256[2] memory input; - input[0] = left; - input[1] = right; - result = hash2(input); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/crypto/MockVerifier.sol b/packages/hardhat/contracts/maci-contracts/crypto/MockVerifier.sol deleted file mode 100644 index d9a8b00..0000000 --- a/packages/hardhat/contracts/maci-contracts/crypto/MockVerifier.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { SnarkConstants } from "./SnarkConstants.sol"; -import { SnarkCommon } from "./SnarkCommon.sol"; -import { IVerifier } from "../interfaces/IVerifier.sol"; - -/// @title MockVerifier -/// @notice a MockVerifier to be used for testing -contract MockVerifier is IVerifier, SnarkConstants, SnarkCommon { - /// @notice Verify a zk-SNARK proof (test only return always true) - /// @return result Whether the proof is valid given the verifying key and public - function verify(uint256[8] memory, VerifyingKey memory, uint256) public pure override returns (bool result) { - result = true; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/crypto/Pairing.sol b/packages/hardhat/contracts/maci-contracts/crypto/Pairing.sol deleted file mode 100644 index eed929f..0000000 --- a/packages/hardhat/contracts/maci-contracts/crypto/Pairing.sol +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright 2017 Christian Reitwiessner -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -// 2019 OKIMS - -pragma solidity ^0.8.10; - -/// @title Pairing -/// @notice A library implementing the alt_bn128 elliptic curve operations. -library Pairing { - uint256 public constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; - - struct G1Point { - uint256 x; - uint256 y; - } - - // Encoding of field elements is: X[0] * z + X[1] - struct G2Point { - uint256[2] x; - uint256[2] y; - } - - /// @notice custom errors - error PairingAddFailed(); - error PairingMulFailed(); - error PairingOpcodeFailed(); - - /// @notice The negation of p, i.e. p.plus(p.negate()) should be zero. - function negate(G1Point memory p) internal pure returns (G1Point memory) { - // The prime q in the base field F_q for G1 - if (p.x == 0 && p.y == 0) { - return G1Point(0, 0); - } else { - return G1Point(p.x, PRIME_Q - (p.y % PRIME_Q)); - } - } - - /// @notice r Returns the sum of two points of G1. - function plus(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) { - uint256[4] memory input; - input[0] = p1.x; - input[1] = p1.y; - input[2] = p2.x; - input[3] = p2.y; - bool success; - - // solhint-disable-next-line no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) - // Use "invalid" to make gas estimation work - switch success - case 0 { - invalid() - } - } - - if (!success) { - revert PairingAddFailed(); - } - } - - /// @notice r Return the product of a point on G1 and a scalar, i.e. - /// p == p.scalarMul(1) and p.plus(p) == p.scalarMul(2) for all - /// points p. - function scalarMul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) { - uint256[3] memory input; - input[0] = p.x; - input[1] = p.y; - input[2] = s; - bool success; - // solhint-disable-next-line no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) - // Use "invalid" to make gas estimation work - switch success - case 0 { - invalid() - } - } - - if (!success) { - revert PairingMulFailed(); - } - } - - /// @return isValid The result of computing the pairing check - /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 - /// For example, - /// pairing([P1(), P1().negate()], [P2(), P2()]) should return true. - function pairing( - G1Point memory a1, - G2Point memory a2, - G1Point memory b1, - G2Point memory b2, - G1Point memory c1, - G2Point memory c2, - G1Point memory d1, - G2Point memory d2 - ) internal view returns (bool isValid) { - G1Point[4] memory p1 = [a1, b1, c1, d1]; - G2Point[4] memory p2 = [a2, b2, c2, d2]; - - uint256 inputSize = 24; - uint256[] memory input = new uint256[](inputSize); - - for (uint256 i = 0; i < 4; i++) { - uint256 j = i * 6; - input[j + 0] = p1[i].x; - input[j + 1] = p1[i].y; - input[j + 2] = p2[i].x[0]; - input[j + 3] = p2[i].x[1]; - input[j + 4] = p2[i].y[0]; - input[j + 5] = p2[i].y[1]; - } - - uint256[1] memory out; - bool success; - - // solhint-disable-next-line no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) - // Use "invalid" to make gas estimation work - switch success - case 0 { - invalid() - } - } - - if (!success) { - revert PairingOpcodeFailed(); - } - - isValid = out[0] != 0; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/crypto/PoseidonT3.sol b/packages/hardhat/contracts/maci-contracts/crypto/PoseidonT3.sol deleted file mode 100644 index c9825d3..0000000 --- a/packages/hardhat/contracts/maci-contracts/crypto/PoseidonT3.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -/// @notice A library which provides functions for computing Pedersen hashes. -library PoseidonT3 { - // solhint-disable-next-line no-empty-blocks - function poseidon(uint256[2] memory input) public pure returns (uint256) {} -} diff --git a/packages/hardhat/contracts/maci-contracts/crypto/PoseidonT4.sol b/packages/hardhat/contracts/maci-contracts/crypto/PoseidonT4.sol deleted file mode 100644 index 615c955..0000000 --- a/packages/hardhat/contracts/maci-contracts/crypto/PoseidonT4.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -/// @notice A library which provides functions for computing Pedersen hashes. -library PoseidonT4 { - // solhint-disable-next-line no-empty-blocks - function poseidon(uint256[3] memory input) public pure returns (uint256) {} -} diff --git a/packages/hardhat/contracts/maci-contracts/crypto/PoseidonT5.sol b/packages/hardhat/contracts/maci-contracts/crypto/PoseidonT5.sol deleted file mode 100644 index 46bb22e..0000000 --- a/packages/hardhat/contracts/maci-contracts/crypto/PoseidonT5.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -/// @notice A library which provides functions for computing Pedersen hashes. -library PoseidonT5 { - // solhint-disable-next-line no-empty-blocks - function poseidon(uint256[4] memory input) public pure returns (uint256) {} -} diff --git a/packages/hardhat/contracts/maci-contracts/crypto/PoseidonT6.sol b/packages/hardhat/contracts/maci-contracts/crypto/PoseidonT6.sol deleted file mode 100644 index 1f677da..0000000 --- a/packages/hardhat/contracts/maci-contracts/crypto/PoseidonT6.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -/// @notice A library which provides functions for computing Pedersen hashes. -library PoseidonT6 { - // solhint-disable-next-line no-empty-blocks - function poseidon(uint256[5] memory input) public pure returns (uint256) {} -} diff --git a/packages/hardhat/contracts/maci-contracts/crypto/SnarkCommon.sol b/packages/hardhat/contracts/maci-contracts/crypto/SnarkCommon.sol deleted file mode 100644 index e63bd8e..0000000 --- a/packages/hardhat/contracts/maci-contracts/crypto/SnarkCommon.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; -import { Pairing } from "./Pairing.sol"; - -/// @title SnarkCommon -/// @notice a Contract which holds a struct -/// representing a Groth16 verifying key -contract SnarkCommon { - /// @notice a struct representing a Groth16 verifying key - struct VerifyingKey { - Pairing.G1Point alpha1; - Pairing.G2Point beta2; - Pairing.G2Point gamma2; - Pairing.G2Point delta2; - Pairing.G1Point[] ic; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/crypto/SnarkConstants.sol b/packages/hardhat/contracts/maci-contracts/crypto/SnarkConstants.sol deleted file mode 100644 index e263121..0000000 --- a/packages/hardhat/contracts/maci-contracts/crypto/SnarkConstants.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -/// @title SnarkConstants -/// @notice This contract contains constants related to the SNARK -/// components of MACI. -contract SnarkConstants { - /// @notice The scalar field - uint256 internal constant SNARK_SCALAR_FIELD = - 21888242871839275222246405745257275088548364400416034343698204186575808495617; - - /// @notice The public key here 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. - uint256 internal constant PAD_PUBKEY_X = - 10457101036533406547632367118273992217979173478358440826365724437999023779287; - uint256 internal constant PAD_PUBKEY_Y = - 19824078218392094440610104313265183977899662750282163392862422243483260492317; - - /// @notice The Keccack256 hash of 'Maci' - uint256 internal constant NOTHING_UP_MY_SLEEVE = - 8370432830353022751713833565135785980866757267633941821328460903436894336785; -} diff --git a/packages/hardhat/contracts/maci-contracts/crypto/Verifier.sol b/packages/hardhat/contracts/maci-contracts/crypto/Verifier.sol deleted file mode 100644 index 7654fe6..0000000 --- a/packages/hardhat/contracts/maci-contracts/crypto/Verifier.sol +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { Pairing } from "./Pairing.sol"; -import { SnarkConstants } from "./SnarkConstants.sol"; -import { SnarkCommon } from "./SnarkCommon.sol"; -import { IVerifier } from "../interfaces/IVerifier.sol"; - -/// @title Verifier -/// @notice a Groth16 verifier contract -contract Verifier is IVerifier, SnarkConstants, SnarkCommon { - struct Proof { - Pairing.G1Point a; - Pairing.G2Point b; - Pairing.G1Point c; - } - - using Pairing for *; - - uint256 public constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; - - /// @notice custom errors - error InvalidProofQ(); - error InvalidInputVal(); - - /// @notice Verify a zk-SNARK proof - /// @param _proof The proof - /// @param vk The verifying key - /// @param input The public inputs to the circuit - /// @return isValid Whether the proof is valid given the verifying key and public - /// input. Note that this function only supports one public input. - /// Refer to the Semaphore source code for a verifier that supports - /// multiple public inputs. - function verify( - uint256[8] memory _proof, - VerifyingKey memory vk, - uint256 input - ) public view override returns (bool isValid) { - Proof memory proof; - proof.a = Pairing.G1Point(_proof[0], _proof[1]); - proof.b = Pairing.G2Point([_proof[2], _proof[3]], [_proof[4], _proof[5]]); - proof.c = Pairing.G1Point(_proof[6], _proof[7]); - - // Make sure that proof.A, B, and C are each less than the prime q - checkPoint(proof.a.x); - checkPoint(proof.a.y); - checkPoint(proof.b.x[0]); - checkPoint(proof.b.y[0]); - checkPoint(proof.b.x[1]); - checkPoint(proof.b.y[1]); - checkPoint(proof.c.x); - checkPoint(proof.c.y); - - // Make sure that the input is less than the snark scalar field - if (input >= SNARK_SCALAR_FIELD) { - revert InvalidInputVal(); - } - - // Compute the linear combination vk_x - Pairing.G1Point memory vkX = Pairing.G1Point(0, 0); - - vkX = Pairing.plus(vkX, Pairing.scalarMul(vk.ic[1], input)); - - vkX = Pairing.plus(vkX, vk.ic[0]); - - isValid = Pairing.pairing( - Pairing.negate(proof.a), - proof.b, - vk.alpha1, - vk.beta2, - vkX, - vk.gamma2, - proof.c, - vk.delta2 - ); - } - - function checkPoint(uint256 point) internal pure { - if (point >= PRIME_Q) { - revert InvalidProofQ(); - } - } -} diff --git a/packages/hardhat/contracts/maci-contracts/gatekeepers/EASGatekeeper.sol b/packages/hardhat/contracts/maci-contracts/gatekeepers/EASGatekeeper.sol deleted file mode 100644 index 0379a8c..0000000 --- a/packages/hardhat/contracts/maci-contracts/gatekeepers/EASGatekeeper.sol +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; - -import { SignUpGatekeeper } from "./SignUpGatekeeper.sol"; -import { IEAS } from "../interfaces/IEAS.sol"; - -/// @title EASGatekeeper -/// @notice A gatekeeper contract which allows users to sign up to MACI -/// only if they've received an attestation of a specific schema from a trusted attester -contract EASGatekeeper is SignUpGatekeeper, Ownable { - // the reference to the EAS contract - IEAS private immutable eas; - - // the schema to check against - bytes32 public immutable schema; - - // the trusted attester - address public immutable attester; - - /// @notice the reference to the MACI contract - address public maci; - - // a mapping of attestations that have already registered - mapping(bytes32 => bool) public registeredAttestations; - - /// @notice custom errors - error AttestationRevoked(); - error AlreadyRegistered(); - error AttesterNotTrusted(); - error NotYourAttestation(); - error InvalidSchema(); - error OnlyMACI(); - error ZeroAddress(); - - /// @notice Deploy an instance of EASGatekeeper - /// @param _eas The EAS contract - /// @param _attester The trusted attester - /// @param _schema The schema UID - constructor(address _eas, address _attester, bytes32 _schema) payable Ownable() { - if (_eas == address(0) || _attester == address(0)) revert ZeroAddress(); - eas = IEAS(_eas); - schema = _schema; - attester = _attester; - } - - /// @notice Adds an uninitialised MACI instance to allow for token signups - /// @param _maci The MACI contract interface to be stored - function setMaciInstance(address _maci) public override onlyOwner { - if (_maci == address(0)) revert ZeroAddress(); - maci = _maci; - } - - /// @notice Register an user based on their attestation - /// @dev Throw if the attestation is not valid or just complete silently - /// @param _user The user's Ethereum address. - /// @param _data The ABI-encoded schemaId as a uint256. - function register(address _user, bytes memory _data) public override { - // decode the argument - bytes32 attestationId = abi.decode(_data, (bytes32)); - - // ensure that the caller is the MACI contract - if (maci != msg.sender) revert OnlyMACI(); - - // ensure that the attestation has not been registered yet - if (registeredAttestations[attestationId]) revert AlreadyRegistered(); - - // register the attestation so it cannot be called again with the same one - registeredAttestations[attestationId] = true; - - // get the attestation from the EAS contract - IEAS.Attestation memory attestation = eas.getAttestation(attestationId); - - // the schema must match - if (attestation.schema != schema) revert InvalidSchema(); - - // we check that the attestation attester is the trusted one - if (attestation.attester != attester) revert AttesterNotTrusted(); - - // we check that it was not revoked - if (attestation.revocationTime != 0) revert AttestationRevoked(); - - // one cannot register an attestation for another user - if (attestation.recipient != _user) revert NotYourAttestation(); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/gatekeepers/FreeForAllSignUpGatekeeper.sol b/packages/hardhat/contracts/maci-contracts/gatekeepers/FreeForAllSignUpGatekeeper.sol deleted file mode 100644 index 9637e02..0000000 --- a/packages/hardhat/contracts/maci-contracts/gatekeepers/FreeForAllSignUpGatekeeper.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { SignUpGatekeeper } from "./SignUpGatekeeper.sol"; - -/// @title FreeForAllGatekeeper -/// @notice A SignUpGatekeeper which allows anyone to sign up. -contract FreeForAllGatekeeper is SignUpGatekeeper { - /// @notice Create a new instance of FreeForAllGatekeeper - // solhint-disable-next-line no-empty-blocks - constructor() payable {} - - /// @notice setMaciInstance does nothing in this gatekeeper - /// @param _maci The MACI contract - // solhint-disable-next-line no-empty-blocks - function setMaciInstance(address _maci) public override {} - - /// @notice Registers the user without any restrictions. - /// @param _address The address of the user - /// @param _data memory additional data - // solhint-disable-next-line no-empty-blocks - function register(address _address, bytes memory _data) public override {} -} diff --git a/packages/hardhat/contracts/maci-contracts/gatekeepers/SignUpGatekeeper.sol b/packages/hardhat/contracts/maci-contracts/gatekeepers/SignUpGatekeeper.sol deleted file mode 100644 index 2e1a23b..0000000 --- a/packages/hardhat/contracts/maci-contracts/gatekeepers/SignUpGatekeeper.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -/// @title SignUpGatekeeper -/// @notice A gatekeeper contract which allows users to sign up for a poll. -abstract contract SignUpGatekeeper { - /// @notice Allows to set the MACI contract - // solhint-disable-next-line no-empty-blocks - function setMaciInstance(address _maci) public virtual {} - - /// @notice Registers the user - /// @param _user The address of the user - /// @param _data additional data - // solhint-disable-next-line no-empty-blocks - function register(address _user, bytes memory _data) public virtual {} -} diff --git a/packages/hardhat/contracts/maci-contracts/gatekeepers/SignUpTokenGatekeeper.sol b/packages/hardhat/contracts/maci-contracts/gatekeepers/SignUpTokenGatekeeper.sol deleted file mode 100644 index e3dcfd7..0000000 --- a/packages/hardhat/contracts/maci-contracts/gatekeepers/SignUpTokenGatekeeper.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; - -import { SignUpGatekeeper } from "./SignUpGatekeeper.sol"; -import { SignUpToken } from "../SignUpToken.sol"; - -/// @title SignUpTokenGatekeeper -/// @notice This contract allows to gatekeep MACI signups -/// by requiring new voters to own a certain ERC721 token -contract SignUpTokenGatekeeper is SignUpGatekeeper, Ownable { - /// @notice the reference to the SignUpToken contract - SignUpToken public token; - /// @notice the reference to the MACI contract - address public maci; - - /// @notice a mapping of tokenIds to whether they have been used to sign up - mapping(uint256 => bool) public registeredTokenIds; - - /// @notice custom errors - error AlreadyRegistered(); - error NotTokenOwner(); - error OnlyMACI(); - - /// @notice creates a new SignUpTokenGatekeeper - /// @param _token the address of the SignUpToken contract - constructor(SignUpToken _token) payable Ownable() { - token = _token; - } - - /// @notice Adds an uninitialised MACI instance to allow for token signups - /// @param _maci The MACI contract interface to be stored - function setMaciInstance(address _maci) public override onlyOwner { - maci = _maci; - } - - /// @notice Registers the user if they own the token with the token ID encoded in - /// _data. Throws if the user does not own the token or if the token has - /// already been used to sign up. - /// @param _user The user's Ethereum address. - /// @param _data The ABI-encoded tokenId as a uint256. - function register(address _user, bytes memory _data) public override { - if (maci != msg.sender) revert OnlyMACI(); - // Decode the given _data bytes into a uint256 which is the token ID - uint256 tokenId = abi.decode(_data, (uint256)); - - // Check if the user owns the token - bool ownsToken = token.ownerOf(tokenId) == _user; - if (!ownsToken) revert NotTokenOwner(); - - // Check if the token has already been used - bool alreadyRegistered = registeredTokenIds[tokenId]; - if (alreadyRegistered) revert AlreadyRegistered(); - - // Mark the token as already used - registeredTokenIds[tokenId] = true; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/gatekeepers/hatsGatekeepers/HatsGatekeeperBase.sol b/packages/hardhat/contracts/maci-contracts/gatekeepers/hatsGatekeepers/HatsGatekeeperBase.sol deleted file mode 100644 index 3dc86b4..0000000 --- a/packages/hardhat/contracts/maci-contracts/gatekeepers/hatsGatekeepers/HatsGatekeeperBase.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { SignUpGatekeeper } from "../SignUpGatekeeper.sol"; -import { IHats } from "../../interfaces/IHats.sol"; - -/// @title HatsGatekeeperBase -/// @notice Abstract contract containing the base elements of a Hats Gatekeeper contract -abstract contract HatsGatekeeperBase is SignUpGatekeeper, Ownable { - /*////////////////////////////////////////////////////////////// - CUSTOM ERRORS - //////////////////////////////////////////////////////////////*/ - - error OnlyMACI(); - error NotWearingCriterionHat(); - error AlreadyRegistered(); - error ZeroAddress(); - - /*////////////////////////////////////////////////////////////// - PUBLIC CONSTANTS - //////////////////////////////////////////////////////////////*/ - - /// @notice The Hats Protocol contract address - IHats public immutable hats; - - /*////////////////////////////////////////////////////////////// - PUBLIC STATE VARIABLES - //////////////////////////////////////////////////////////////*/ - - /// @notice the reference to the MACI contract - address public maci; - - /// @notice Tracks registered users - mapping(address => bool) public registered; - - /*////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////*/ - - /// @notice Deploy an instance of HatsGatekeeper - /// @param _hats The Hats Protocol contract - constructor(address _hats) payable { - hats = IHats(_hats); - } - - /*////////////////////////////////////////////////////////////// - OWNER FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /// @notice Allows to set the MACI contract - /// @param _maci The MACI contract interface to be stored - function setMaciInstance(address _maci) public override onlyOwner { - if (_maci == address(0)) revert ZeroAddress(); - maci = _maci; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/gatekeepers/hatsGatekeepers/HatsGatekeeperMultiple.sol b/packages/hardhat/contracts/maci-contracts/gatekeepers/hatsGatekeepers/HatsGatekeeperMultiple.sol deleted file mode 100644 index 674165a..0000000 --- a/packages/hardhat/contracts/maci-contracts/gatekeepers/hatsGatekeepers/HatsGatekeeperMultiple.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { HatsGatekeeperBase } from "./HatsGatekeeperBase.sol"; - -/// @title HatsGatekeeperMultiple -/// @notice A gatekeeper contract which allows users to sign up to MACI -/// only if they are wearing one of the specified hats -contract HatsGatekeeperMultiple is HatsGatekeeperBase { - /*////////////////////////////////////////////////////////////// - CUSTOM ERRORS - //////////////////////////////////////////////////////////////*/ - - error NotCriterionHat(); - - /*////////////////////////////////////////////////////////////// - PUBLIC CONSTANTS - //////////////////////////////////////////////////////////////*/ - - /// @notice Tracks hats that users must wear to be eligible to register - mapping(uint256 => bool) public criterionHat; - - /*////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////*/ - - /// @notice Deploy an instance of HatsGatekeeperMultiple - /// @param _hats The Hats Protocol contract - /// @param _criterionHats Array of accepted criterion hats - constructor(address _hats, uint256[] memory _criterionHats) payable HatsGatekeeperBase(_hats) { - // add the criterion hats - for (uint256 i; i < _criterionHats.length; ++i) { - criterionHat[_criterionHats[i]] = true; - } - } - - /*////////////////////////////////////////////////////////////// - GATEKEEPER FUNCTION - //////////////////////////////////////////////////////////////*/ - - /// @notice Registers the user - /// @param _user The address of the user - /// @param _data additional data - function register(address _user, bytes memory _data) public override { - // ensure that the caller is the MACI contract - if (maci != msg.sender) revert OnlyMACI(); - - // _user must not be already registered - if (registered[_user]) revert AlreadyRegistered(); - - // decode the _data as a hat - uint256 hat = abi.decode(_data, (uint256)); - - // the hat must be set as a criterion hat - if (!criterionHat[hat]) revert NotCriterionHat(); - - registered[_user] = true; - - // _user must be wearing the criterion hat - if (!hats.isWearerOfHat(_user, hat)) revert NotWearingCriterionHat(); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/gatekeepers/hatsGatekeepers/HatsGatekeeperSingle.sol b/packages/hardhat/contracts/maci-contracts/gatekeepers/hatsGatekeepers/HatsGatekeeperSingle.sol deleted file mode 100644 index 5d2f89a..0000000 --- a/packages/hardhat/contracts/maci-contracts/gatekeepers/hatsGatekeepers/HatsGatekeeperSingle.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { HatsGatekeeperBase } from "./HatsGatekeeperBase.sol"; - -/// @title HatsGatekeeperSingle -/// @notice A gatekeeper contract which allows users to sign up to MACI -/// only if they are wearing a specified hat -contract HatsGatekeeperSingle is HatsGatekeeperBase { - /*////////////////////////////////////////////////////////////// - PUBLIC CONSTANTS - //////////////////////////////////////////////////////////////*/ - - /// @notice The hat that users must wear to be eligible to register - uint256 public immutable criterionHat; - - /*////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////*/ - - /// @notice Deploy an instance of HatsGatekeeperSingle - /// @param _hats The Hats Protocol contract - /// @param _criterionHat The required hat - constructor(address _hats, uint256 _criterionHat) payable HatsGatekeeperBase(_hats) { - criterionHat = _criterionHat; - } - - /*////////////////////////////////////////////////////////////// - GATEKEEPER FUNCTION - //////////////////////////////////////////////////////////////*/ - - /// @notice Registers the user - /// @param _user The address of the user - function register(address _user, bytes memory /*_data*/) public override { - // ensure that the caller is the MACI contract - if (maci != msg.sender) revert OnlyMACI(); - - // _user must not be already registered - if (registered[_user]) revert AlreadyRegistered(); - - registered[_user] = true; - - // _user must be wearing the criterion hat - if (!hats.isWearerOfHat(_user, criterionHat)) revert NotWearingCriterionHat(); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/initialVoiceCreditProxy/ConstantInitialVoiceCreditProxy.sol b/packages/hardhat/contracts/maci-contracts/initialVoiceCreditProxy/ConstantInitialVoiceCreditProxy.sol deleted file mode 100644 index d9aefa7..0000000 --- a/packages/hardhat/contracts/maci-contracts/initialVoiceCreditProxy/ConstantInitialVoiceCreditProxy.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { InitialVoiceCreditProxy } from "./InitialVoiceCreditProxy.sol"; - -/// @title ConstantInitialVoiceCreditProxy -/// @notice This contract allows to set a constant initial voice credit balance -/// for MACI's voters. -contract ConstantInitialVoiceCreditProxy is InitialVoiceCreditProxy { - /// @notice the balance to be returned by getVoiceCredits - uint256 internal balance; - - /// @notice creates a new ConstantInitialVoiceCreditProxy - /// @param _balance the balance to be returned by getVoiceCredits - constructor(uint256 _balance) payable { - balance = _balance; - } - - /// @notice Returns the constant balance for any new MACI's voter - /// @return balance - function getVoiceCredits(address, bytes memory) public view override returns (uint256) { - return balance; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol b/packages/hardhat/contracts/maci-contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol deleted file mode 100644 index 246ec33..0000000 --- a/packages/hardhat/contracts/maci-contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -/// @title InitialVoiceCreditProxy -/// @notice This contract is the base contract for -/// InitialVoiceCreditProxy contracts. It allows to set a custom initial voice -/// credit balance for MACI's voters. -abstract contract InitialVoiceCreditProxy { - /// @notice Returns the initial voice credit balance for a new MACI's voter - /// @param _user the address of the voter - /// @param _data additional data - /// @return the balance - // solhint-disable-next-line no-empty-blocks - function getVoiceCredits(address _user, bytes memory _data) public view virtual returns (uint256) {} -} diff --git a/packages/hardhat/contracts/maci-contracts/interfaces/IEAS.sol b/packages/hardhat/contracts/maci-contracts/interfaces/IEAS.sol deleted file mode 100644 index ff96d2a..0000000 --- a/packages/hardhat/contracts/maci-contracts/interfaces/IEAS.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -/// @title IEAS -/// @notice An interface to the EAS contract. -interface IEAS { - /// @notice A struct representing a single attestation. - struct Attestation { - // A unique identifier of the attestation. - bytes32 uid; - // The unique identifier of the schema. - bytes32 schema; - // The time when the attestation was created (Unix timestamp). - uint64 time; - // The time when the attestation expires (Unix timestamp). - uint64 expirationTime; - // The time when the attestation was revoked (Unix timestamp). - uint64 revocationTime; - // The UID of the related attestation. - bytes32 refUID; - // The recipient of the attestation. - address recipient; - // The attester/sender of the attestation. - address attester; - // Whether the attestation is revocable. - bool revocable; - // Custom attestation data. - bytes data; - } - - /// Get an attestation by its unique identifier. - /// @param uid The unique identifier of the attestation. - /// @return attestation The attestation. - function getAttestation(bytes32 uid) external view returns (Attestation memory); -} diff --git a/packages/hardhat/contracts/maci-contracts/interfaces/IHats.sol b/packages/hardhat/contracts/maci-contracts/interfaces/IHats.sol deleted file mode 100644 index ad5337b..0000000 --- a/packages/hardhat/contracts/maci-contracts/interfaces/IHats.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -/// @title IHats -/// @notice Minimal interface for the Hats Protocol contract -/// @dev Includes only the functions required for the HatsGatekeepers and associated tests -interface IHats { - /// @notice Creates and mints a Hat that is its own admin, i.e. a "topHat" - /// @dev A topHat has no eligibility and no toggle - /// @param _target The address to which the newly created topHat is minted - /// @param _details A description of the Hat [optional]. Should not be larger than 7000 bytes - /// (enforced in changeHatDetails) - /// @param _imageURI The image uri for this top hat and the fallback for its - /// downstream hats [optional]. Should not be larger than 7000 bytes - /// (enforced in changeHatImageURI) - /// @return topHatId The id of the newly created topHat - function mintTopHat(address _target, string calldata _details, string calldata _imageURI) external returns (uint256); - - /// @notice Creates a new hat. The msg.sender must wear the `_admin` hat. - /// @dev Initializes a new Hat struct, but does not mint any tokens. - /// @param _details A description of the Hat. Should not be larger than 7000 bytes (enforced in changeHatDetails) - /// @param _maxSupply The total instances of the Hat that can be worn at once - /// @param _admin The id of the Hat that will control who wears the newly created hat - /// @param _eligibility The address that can report on the Hat wearer's status - /// @param _toggle The address that can deactivate the Hat - /// @param _mutable Whether the hat's properties are changeable after creation - /// @param _imageURI The image uri for this hat and the fallback for its - /// downstream hats [optional]. Should not be larger than 7000 bytes (enforced in changeHatImageURI) - /// @return newHatId The id of the newly created Hat - function createHat( - uint256 _admin, - string calldata _details, - uint32 _maxSupply, - address _eligibility, - address _toggle, - bool _mutable, - string calldata _imageURI - ) external returns (uint256); - - /// @notice Mints an ERC1155-similar token of the Hat to an eligible recipient, who then "wears" the hat - /// @dev The msg.sender must wear an admin Hat of `_hatId`, and the recipient must be eligible to wear `_hatId` - /// @param _hatId The id of the Hat to mint - /// @param _wearer The address to which the Hat is minted - /// @return success Whether the mint succeeded - function mintHat(uint256 _hatId, address _wearer) external returns (bool success); - - /// @notice Checks whether a given address wears a given Hat - /// @dev Convenience function that wraps `balanceOf` - /// @param account The address in question - /// @param hat The id of the Hat that the `_user` might wear - /// @return isWearer Whether the `_user` wears the Hat. - function isWearerOfHat(address account, uint256 hat) external view returns (bool); -} diff --git a/packages/hardhat/contracts/maci-contracts/interfaces/IMACI.sol b/packages/hardhat/contracts/maci-contracts/interfaces/IMACI.sol deleted file mode 100644 index 3060bc9..0000000 --- a/packages/hardhat/contracts/maci-contracts/interfaces/IMACI.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { AccQueue } from "../trees/AccQueue.sol"; - -/// @title IMACI -/// @notice MACI interface -interface IMACI { - /// @notice Get the depth of the state tree - /// @return The depth of the state tree - function stateTreeDepth() external view returns (uint8); - - /// @notice Return the main root of the StateAq contract - /// @return The Merkle root - function getStateAqRoot() external view returns (uint256); - - /// @notice Allow Poll contracts to merge the state subroots - /// @param _numSrQueueOps Number of operations - /// @param _pollId The ID of the active Poll - function mergeStateAqSubRoots(uint256 _numSrQueueOps, uint256 _pollId) external; - - /// @notice Allow Poll contracts to merge the state root - /// @param _pollId The active Poll ID - /// @return The calculated Merkle root - function mergeStateAq(uint256 _pollId) external returns (uint256); - - /// @notice Get the number of signups - /// @return numsignUps The number of signups - function numSignUps() external view returns (uint256); - - /// @notice Get the state AccQueue - /// @return The state AccQueue - function stateAq() external view returns (AccQueue); -} diff --git a/packages/hardhat/contracts/maci-contracts/interfaces/IMPFactory.sol b/packages/hardhat/contracts/maci-contracts/interfaces/IMPFactory.sol deleted file mode 100644 index d4d2d3e..0000000 --- a/packages/hardhat/contracts/maci-contracts/interfaces/IMPFactory.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -/// @title IMessageProcessorFactory -/// @notice MessageProcessorFactory interface -interface IMessageProcessorFactory { - /// @notice Deploy a new MessageProcessor contract and return the address. - /// @param _verifier Verifier contract - /// @param _vkRegistry VkRegistry contract - /// @param _poll Poll contract - /// @param _owner Owner of the MessageProcessor contract - /// @param _isQv Whether to support QV or not - /// @return The deployed MessageProcessor contract - function deploy( - address _verifier, - address _vkRegistry, - address _poll, - address _owner, - bool _isQv - ) external returns (address); -} diff --git a/packages/hardhat/contracts/maci-contracts/interfaces/IMessageProcessor.sol b/packages/hardhat/contracts/maci-contracts/interfaces/IMessageProcessor.sol deleted file mode 100644 index 95bc363..0000000 --- a/packages/hardhat/contracts/maci-contracts/interfaces/IMessageProcessor.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -/// @title IMessageProcessor -/// @notice MessageProcessor interface -interface IMessageProcessor { - /// @notice Get the result of whether there are unprocessed messages left - /// @return Whether there are unprocessed messages left - function processingComplete() external view returns (bool); - - /// @notice Get the commitment to the state and ballot roots - /// @return The commitment to the state and ballot roots - function sbCommitment() external view returns (uint256); -} diff --git a/packages/hardhat/contracts/maci-contracts/interfaces/IPoll.sol b/packages/hardhat/contracts/maci-contracts/interfaces/IPoll.sol deleted file mode 100644 index 4a3b1ac..0000000 --- a/packages/hardhat/contracts/maci-contracts/interfaces/IPoll.sol +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { DomainObjs } from "../utilities/DomainObjs.sol"; -import { IMACI } from "./IMACI.sol"; -import { AccQueue } from "../trees/AccQueue.sol"; -import { TopupCredit } from "../TopupCredit.sol"; - -/// @title IPoll -/// @notice Poll interface -interface IPoll { - /// @notice The number of messages which have been processed and the number of signups - /// @return numSignups The number of signups - /// @return numMsgs The number of messages sent by voters - function numSignUpsAndMessages() external view returns (uint256 numSignups, uint256 numMsgs); - - /// @notice 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) external; - - /// @notice Allows anyone to publish a message (an encrypted command and signature). - /// This function also enqueues the message. - /// @param _message The message to publish - /// @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 publishMessage(DomainObjs.Message memory _message, DomainObjs.PubKey calldata _encPubKey) external; - - /// @notice The first step of merging the MACI state AccQueue. This allows the - /// ProcessMessages circuit to access the latest state tree and ballots via - /// currentSbCommitment. - /// @param _numSrQueueOps Number of operations - /// @param _pollId The ID of the active Poll - function mergeMaciStateAqSubRoots(uint256 _numSrQueueOps, uint256 _pollId) external; - - /// @notice 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 active Poll - function mergeMaciStateAq(uint256 _pollId) external; - - /// @notice The first step in merging the message AccQueue so that the - /// ProcessMessages circuit can access the message root. - /// @param _numSrQueueOps The number of subroot queue operations to perform - function mergeMessageAqSubRoots(uint256 _numSrQueueOps) external; - - /// @notice The second step in merging the message AccQueue so that the - /// ProcessMessages circuit can access the message root. - function mergeMessageAq() external; - - /// @notice Returns the Poll's deploy time and duration - /// @return _deployTime The deployment timestamp - /// @return _duration The duration of the poll - function getDeployTimeAndDuration() external view returns (uint256 _deployTime, uint256 _duration); - - /// @notice Get the result of whether the MACI contract's stateAq has been merged by this contract - /// @return Whether the MACI contract's stateAq has been merged by this contract - function stateAqMerged() external view returns (bool); - - /// @notice Get the depths of the merkle trees - /// @return intStateTreeDepth The depth of the state tree - /// @return messageTreeSubDepth The subdepth of the message tree - /// @return messageTreeDepth The depth of the message tree - /// @return voteOptionTreeDepth The subdepth of the vote option tree - function treeDepths() - external - view - returns (uint8 intStateTreeDepth, uint8 messageTreeSubDepth, uint8 messageTreeDepth, uint8 voteOptionTreeDepth); - - /// @notice Get the max values for the poll - /// @return maxMessages The maximum number of messages - /// @return maxVoteOptions The maximum number of vote options - function maxValues() external view returns (uint256 maxMessages, uint256 maxVoteOptions); - - /// @notice Get the external contracts - /// @return maci The IMACI contract - /// @return messageAq The AccQueue contract - /// @return topupCredit The TopupCredit contract - function extContracts() external view returns (IMACI maci, AccQueue messageAq, TopupCredit topupCredit); - - /// @notice Get the hash of coordinator's public key - /// @return _coordinatorPubKeyHash the hash of coordinator's public key - function coordinatorPubKeyHash() external view returns (uint256 _coordinatorPubKeyHash); - - /// @notice Get the commitment to the state leaves and the ballots. This is - /// hash3(stateRoot, ballotRoot, salt). - /// Its initial value should be - /// hash(maciStateRootSnapshot, emptyBallotRoot, 0) - /// Each successful invocation of processMessages() should use a different - /// salt to update this value, so that an external observer cannot tell in - /// the case that none of the messages are valid. - /// @return The commitment to the state leaves and the ballots - function currentSbCommitment() external view returns (uint256); -} diff --git a/packages/hardhat/contracts/maci-contracts/interfaces/IPollFactory.sol b/packages/hardhat/contracts/maci-contracts/interfaces/IPollFactory.sol deleted file mode 100644 index ee903f3..0000000 --- a/packages/hardhat/contracts/maci-contracts/interfaces/IPollFactory.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { TopupCredit } from "../TopupCredit.sol"; -import { Params } from "../utilities/Params.sol"; -import { DomainObjs } from "../utilities/DomainObjs.sol"; - -/// @title IPollFactory -/// @notice PollFactory interface -interface IPollFactory { - /// @notice Deploy a new Poll contract and AccQueue contract for messages. - /// @param _duration The duration of the poll - /// @param _maxValues The max values for the poll - /// @param _treeDepths The depths of the merkle trees - /// @param _coordinatorPubKey The coordinator's public key - /// @param _maci The MACI contract interface reference - /// @param _topupCredit The TopupCredit contract - /// @param _pollOwner The owner of the poll - /// @return The deployed Poll contract - function deploy( - uint256 _duration, - Params.MaxValues memory _maxValues, - Params.TreeDepths memory _treeDepths, - DomainObjs.PubKey memory _coordinatorPubKey, - address _maci, - TopupCredit _topupCredit, - address _pollOwner - ) external returns (address); -} diff --git a/packages/hardhat/contracts/maci-contracts/interfaces/ITallyFactory.sol b/packages/hardhat/contracts/maci-contracts/interfaces/ITallyFactory.sol deleted file mode 100644 index 1e2c7e4..0000000 --- a/packages/hardhat/contracts/maci-contracts/interfaces/ITallyFactory.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -/// @title ITallyFactory -/// @notice TallyFactory interface -interface ITallyFactory { - /// @notice Deploy a new Tally contract and return the address. - /// @param _verifier Verifier contract - /// @param _vkRegistry VkRegistry contract - /// @param _poll Poll contract - /// @param _messageProcessor MessageProcessor contract - /// @param _owner Owner of the contract - /// @param _isQv Whether to support QV or not - /// @return The deployed contract - function deploy( - address _verifier, - address _vkRegistry, - address _poll, - address _messageProcessor, - address _owner, - bool _isQv - ) external returns (address); -} diff --git a/packages/hardhat/contracts/maci-contracts/interfaces/IVerifier.sol b/packages/hardhat/contracts/maci-contracts/interfaces/IVerifier.sol deleted file mode 100644 index aaf5891..0000000 --- a/packages/hardhat/contracts/maci-contracts/interfaces/IVerifier.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { SnarkCommon } from "../crypto/SnarkCommon.sol"; - -/// @title IVerifier -/// @notice an interface for a Groth16 verifier contract -interface IVerifier { - /// @notice Verify a zk-SNARK proof - /// @param _proof The proof - /// @param vk The verifying key - /// @param input The public inputs to the circuit - /// @return Whether the proof is valid given the verifying key and public - /// input. Note that this function only supports one public input. - /// Refer to the Semaphore source code for a verifier that supports - /// multiple public inputs. - function verify( - uint256[8] memory _proof, - SnarkCommon.VerifyingKey memory vk, - uint256 input - ) external view returns (bool); -} diff --git a/packages/hardhat/contracts/maci-contracts/interfaces/IVkRegistry.sol b/packages/hardhat/contracts/maci-contracts/interfaces/IVkRegistry.sol deleted file mode 100644 index baaf62b..0000000 --- a/packages/hardhat/contracts/maci-contracts/interfaces/IVkRegistry.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { SnarkCommon } from "../crypto/SnarkCommon.sol"; - -/// @title IVkRegistry -/// @notice VkRegistry interface -interface IVkRegistry { - /// @notice Multi store support for QV and Non-QV poll - enum Mode { - QV, - NON_QV - } - - /// @notice Get the tally verifying key - /// @param _stateTreeDepth The state tree depth - /// @param _intStateTreeDepth The intermediate state tree depth - /// @param _voteOptionTreeDepth The vote option tree depth - /// @param _mode QV or Non-QV - /// @return The verifying key - function getTallyVk( - uint256 _stateTreeDepth, - uint256 _intStateTreeDepth, - uint256 _voteOptionTreeDepth, - Mode _mode - ) external view returns (SnarkCommon.VerifyingKey memory); - - /// @notice Get the process verifying key - /// @param _stateTreeDepth The state tree depth - /// @param _messageTreeDepth The message tree depth - /// @param _voteOptionTreeDepth The vote option tree depth - /// @param _messageBatchSize The message batch size - /// @param _mode QV or Non-QV - /// @return The verifying key - function getProcessVk( - uint256 _stateTreeDepth, - uint256 _messageTreeDepth, - uint256 _voteOptionTreeDepth, - uint256 _messageBatchSize, - Mode _mode - ) external view returns (SnarkCommon.VerifyingKey memory); -} diff --git a/packages/hardhat/contracts/maci-contracts/mocks/MockHatsProtocol.sol b/packages/hardhat/contracts/maci-contracts/mocks/MockHatsProtocol.sol deleted file mode 100644 index f460b9a..0000000 --- a/packages/hardhat/contracts/maci-contracts/mocks/MockHatsProtocol.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { IHats } from "../interfaces/IHats.sol"; - -/// @title MockHatsProtocol -/// @notice A mock contract to test the HatsGatekeeper -contract MockHatsProtocol is IHats { - IHats public immutable hats; - - uint256 public lastTopHat; - uint256 public lastHat; - - /// @notice Deploy an instance of MockHatsProtocol - /// @param _hats The Hats Protocol contract - constructor(address _hats) payable { - hats = IHats(_hats); - } - - /// @inheritdoc IHats - function mintTopHat(address _target, string calldata _details, string calldata _imageURI) external returns (uint256) { - lastTopHat = hats.mintTopHat(_target, _details, _imageURI); - return lastTopHat; - } - - /// @inheritdoc IHats - function createHat( - uint256 _admin, - string calldata _details, - uint32 _maxSupply, - address _eligibility, - address _toggle, - bool _mutable, - string calldata _imageURI - ) external returns (uint256) { - lastHat = hats.createHat(_admin, _details, _maxSupply, _eligibility, _toggle, _mutable, _imageURI); - return lastHat; - } - - /// @inheritdoc IHats - function mintHat(uint256 _hatId, address _wearer) external returns (bool) { - return hats.mintHat(_hatId, _wearer); - } - - /// @inheritdoc IHats - function isWearerOfHat(address account, uint256 hat) external view override returns (bool) { - return hats.isWearerOfHat(account, hat); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/AccQueue.sol b/packages/hardhat/contracts/maci-contracts/trees/AccQueue.sol deleted file mode 100644 index 7e1f6fe..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/AccQueue.sol +++ /dev/null @@ -1,467 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { Hasher } from "../crypto/Hasher.sol"; - -/// @title AccQueue -/// @notice This contract defines a Merkle tree where each leaf insertion only updates a -/// subtree. To obtain the main tree root, the contract owner must merge the -/// subtrees together. Merging subtrees requires at least 2 operations: -/// mergeSubRoots(), and merge(). To get around the gas limit, -/// the mergeSubRoots() can be performed in multiple transactions. -abstract contract AccQueue is Ownable, Hasher { - // The maximum tree depth - uint256 public constant MAX_DEPTH = 32; - - /// @notice A Queue is a 2D array of Merkle roots and indices which represents nodes - /// in a Merkle tree while it is progressively updated. - struct Queue { - /// @notice IMPORTANT: the following declares an array of b elements of type T: T[b] - /// And the following declares an array of b elements of type T[a]: T[a][b] - /// As such, the following declares an array of MAX_DEPTH+1 arrays of - /// uint256[4] arrays, **not the other way round**: - uint256[4][MAX_DEPTH + 1] levels; - uint256[MAX_DEPTH + 1] indices; - } - - // The depth of each subtree - uint256 internal immutable subDepth; - - // The number of elements per hash operation. Should be either 2 (for - // binary trees) or 5 (quinary trees). The limit is 5 because that is the - // maximum supported number of inputs for the EVM implementation of the - // Poseidon hash function - uint256 internal immutable hashLength; - - // hashLength ** subDepth - uint256 internal immutable subTreeCapacity; - - // True hashLength == 2, false if hashLength == 5 - bool internal isBinary; - - // The index of the current subtree. e.g. the first subtree has index 0, the - // second has 1, and so on - uint256 internal currentSubtreeIndex; - - // Tracks the current subtree. - Queue internal leafQueue; - - // Tracks the smallest tree of subroots - Queue internal subRootQueue; - - // Subtree roots - mapping(uint256 => uint256) internal subRoots; - - // Merged roots - uint256[MAX_DEPTH + 1] internal mainRoots; - - // Whether the subtrees have been merged - bool public subTreesMerged; - - // Whether entire merkle tree has been merged - bool public treeMerged; - - // The root of the shortest possible tree which fits all current subtree - // roots - uint256 internal smallSRTroot; - - // Tracks the next subroot to queue - uint256 internal nextSubRootIndex; - - // The number of leaves inserted across all subtrees so far - uint256 public numLeaves; - - /// @notice custom errors - error SubDepthCannotBeZero(); - error SubdepthTooLarge(uint256 _subDepth, uint256 max); - error InvalidHashLength(); - error DepthCannotBeZero(); - error SubTreesAlreadyMerged(); - error NothingToMerge(); - error SubTreesNotMerged(); - error DepthTooLarge(uint256 _depth, uint256 max); - error DepthTooSmall(uint256 _depth, uint256 min); - error InvalidIndex(uint256 _index); - error InvalidLevel(); - - /// @notice Create a new AccQueue - /// @param _subDepth The depth of each subtree. - /// @param _hashLength The number of leaves per node (2 or 5). - constructor(uint256 _subDepth, uint256 _hashLength) payable { - /// validation - if (_subDepth == 0) revert SubDepthCannotBeZero(); - if (_subDepth > MAX_DEPTH) revert SubdepthTooLarge(_subDepth, MAX_DEPTH); - if (_hashLength != 2 && _hashLength != 5) revert InvalidHashLength(); - - isBinary = _hashLength == 2; - subDepth = _subDepth; - hashLength = _hashLength; - subTreeCapacity = _hashLength ** _subDepth; - } - - /// @notice Hash the contents of the specified level and the specified leaf. - /// This is a virtual function as the hash function which the overriding - /// contract uses will be either hashLeftRight or hash5, which require - /// different input array lengths. - /// @param _level The level to hash. - /// @param _leaf The leaf include with the level. - /// @return _hash The hash of the level and leaf. - // solhint-disable-next-line no-empty-blocks - function hashLevel(uint256 _level, uint256 _leaf) internal virtual returns (uint256 _hash) {} - - /// @notice Hash the contents of the specified level and the specified leaf. - /// This is a virtual function as the hash function which the overriding - /// contract uses will be either hashLeftRight or hash5, which require - /// different input array lengths. - /// @param _level The level to hash. - /// @param _leaf The leaf include with the level. - /// @return _hash The hash of the level and leaf. - // solhint-disable-next-line no-empty-blocks - function hashLevelLeaf(uint256 _level, uint256 _leaf) public view virtual returns (uint256 _hash) {} - - /// @notice Returns the zero leaf at a specified level. - /// This is a virtual function as the hash function which the overriding - /// contract uses will be either hashLeftRight or hash5, which will produce - /// different zero values (e.g. hashLeftRight(0, 0) vs - /// hash5([0, 0, 0, 0, 0]). Moreover, the zero value may be a - /// nothing-up-my-sleeve value. - /// @param _level The level at which to return the zero leaf. - /// @return zero The zero leaf at the specified level. - // solhint-disable-next-line no-empty-blocks - function getZero(uint256 _level) internal virtual returns (uint256 zero) {} - - /// @notice Add a leaf to the queue for the current subtree. - /// @param _leaf The leaf to add. - /// @return leafIndex The index of the leaf in the queue. - function enqueue(uint256 _leaf) public onlyOwner returns (uint256 leafIndex) { - leafIndex = numLeaves; - // Recursively queue the leaf - _enqueue(_leaf, 0); - - // Update the leaf counter - numLeaves = leafIndex + 1; - - // Now that a new leaf has been added, mainRoots and smallSRTroot are - // obsolete - delete mainRoots; - delete smallSRTroot; - subTreesMerged = false; - - // If a subtree is full - if (numLeaves % subTreeCapacity == 0) { - // Store the subroot - subRoots[currentSubtreeIndex] = leafQueue.levels[subDepth][0]; - - // Increment the index - currentSubtreeIndex++; - - // Delete ancillary data - delete leafQueue.levels[subDepth][0]; - delete leafQueue.indices; - } - } - - /// @notice Updates the queue at a given level and hashes any subroots - /// that need to be hashed. - /// @param _leaf The leaf to add. - /// @param _level The level at which to queue the leaf. - function _enqueue(uint256 _leaf, uint256 _level) internal { - if (_level > subDepth) { - revert InvalidLevel(); - } - - while (true) { - uint256 n = leafQueue.indices[_level]; - - if (n != hashLength - 1) { - // Just store the leaf - leafQueue.levels[_level][n] = _leaf; - - if (_level != subDepth) { - // Update the index - leafQueue.indices[_level]++; - } - - return; - } - - // Hash the leaves to next level - _leaf = hashLevel(_level, _leaf); - - // Reset the index for this level - delete leafQueue.indices[_level]; - - // Queue the hash of the leaves into to the next level - _level++; - } - } - - /// @notice Fill any empty leaves of the current subtree with zeros and store the - /// resulting subroot. - function fill() public onlyOwner { - if (numLeaves % subTreeCapacity == 0) { - // If the subtree is completely empty, then the subroot is a - // precalculated zero value - subRoots[currentSubtreeIndex] = getZero(subDepth); - } else { - // Otherwise, fill the rest of the subtree with zeros - _fill(0); - - // Store the subroot - subRoots[currentSubtreeIndex] = leafQueue.levels[subDepth][0]; - - // Reset the subtree data - delete leafQueue.levels; - - // Reset the merged roots - delete mainRoots; - } - - // Increment the subtree index - uint256 curr = currentSubtreeIndex + 1; - currentSubtreeIndex = curr; - - // Update the number of leaves - numLeaves = curr * subTreeCapacity; - - // Reset the subroot tree root now that it is obsolete - delete smallSRTroot; - - subTreesMerged = false; - } - - /// @notice A function that queues zeros to the specified level, hashes, - /// the level, and enqueues the hash to the next level. - /// @param _level The level at which to queue zeros. - // solhint-disable-next-line no-empty-blocks - function _fill(uint256 _level) internal virtual {} - - /// Insert a subtree. Used for batch enqueues. - function insertSubTree(uint256 _subRoot) public onlyOwner { - subRoots[currentSubtreeIndex] = _subRoot; - - // Increment the subtree index - currentSubtreeIndex++; - - // Update the number of leaves - numLeaves += subTreeCapacity; - - // Reset the subroot tree root now that it is obsolete - delete smallSRTroot; - - subTreesMerged = false; - } - - /// @notice Calculate the lowest possible height of a tree with - /// all the subroots merged together. - /// @return depth The lowest possible height of a tree with all the - function calcMinHeight() public view returns (uint256 depth) { - depth = 1; - while (true) { - if (hashLength ** depth >= currentSubtreeIndex) { - break; - } - depth++; - } - } - - /// @notice Merge all subtrees to form the shortest possible tree. - /// This function can be called either once to merge all subtrees in a - /// single transaction, or multiple times to do the same in multiple - /// transactions. - /// @param _numSrQueueOps The number of times this function will call - /// queueSubRoot(), up to the maximum number of times - /// necessary. If it is set to 0, it will call - /// queueSubRoot() as many times as is necessary. Set - /// this to a low number and call this function - /// multiple times if there are many subroots to - /// merge, or a single transaction could run out of - /// gas. - function mergeSubRoots(uint256 _numSrQueueOps) public onlyOwner { - // This function can only be called once unless a new subtree is created - if (subTreesMerged) revert SubTreesAlreadyMerged(); - - // There must be subtrees to merge - if (numLeaves == 0) revert NothingToMerge(); - - // Fill any empty leaves in the current subtree with zeros only if the - // current subtree is not full - if (numLeaves % subTreeCapacity != 0) { - fill(); - } - - // If there is only 1 subtree, use its root - if (currentSubtreeIndex == 1) { - smallSRTroot = getSubRoot(0); - subTreesMerged = true; - return; - } - - uint256 depth = calcMinHeight(); - - uint256 queueOpsPerformed = 0; - for (uint256 i = nextSubRootIndex; i < currentSubtreeIndex; i++) { - if (_numSrQueueOps != 0 && queueOpsPerformed == _numSrQueueOps) { - // If the limit is not 0, stop if the limit has been reached - return; - } - - // Queue the next subroot - queueSubRoot(getSubRoot(nextSubRootIndex), 0, depth); - - // Increment the next subroot counter - nextSubRootIndex++; - - // Increment the ops counter - queueOpsPerformed++; - } - - // The height of the tree of subroots - uint256 m = hashLength ** depth; - - // Queue zeroes to fill out the SRT - if (nextSubRootIndex == currentSubtreeIndex) { - uint256 z = getZero(subDepth); - for (uint256 i = currentSubtreeIndex; i < m; i++) { - queueSubRoot(z, 0, depth); - } - } - - // Store the smallest main root - smallSRTroot = subRootQueue.levels[depth][0]; - subTreesMerged = true; - } - - /// @notice Queues a subroot into the subroot tree. - /// @param _leaf The value to queue. - /// @param _level The level at which to queue _leaf. - /// @param _maxDepth The depth of the tree. - function queueSubRoot(uint256 _leaf, uint256 _level, uint256 _maxDepth) internal { - if (_level > _maxDepth) { - return; - } - - uint256 n = subRootQueue.indices[_level]; - - if (n != hashLength - 1) { - // Just store the leaf - subRootQueue.levels[_level][n] = _leaf; - subRootQueue.indices[_level]++; - } else { - // Hash the elements in this level and queue it in the next level - uint256 hashed; - if (isBinary) { - uint256[2] memory inputs; - inputs[0] = subRootQueue.levels[_level][0]; - inputs[1] = _leaf; - hashed = hash2(inputs); - } else { - uint256[5] memory inputs; - for (uint8 i = 0; i < n; i++) { - inputs[i] = subRootQueue.levels[_level][i]; - } - inputs[n] = _leaf; - hashed = hash5(inputs); - } - - // TODO: change recursion to a while loop - // Recurse - delete subRootQueue.indices[_level]; - queueSubRoot(hashed, _level + 1, _maxDepth); - } - } - - /// @notice Merge all subtrees to form a main tree with a desired depth. - /// @param _depth The depth of the main tree. It must fit all the leaves or - /// this function will revert. - /// @return root The root of the main tree. - function merge(uint256 _depth) public onlyOwner returns (uint256 root) { - // The tree depth must be more than 0 - if (_depth == 0) revert DepthCannotBeZero(); - - // Ensure that the subtrees have been merged - if (!subTreesMerged) revert SubTreesNotMerged(); - - // Check the depth - if (_depth > MAX_DEPTH) revert DepthTooLarge(_depth, MAX_DEPTH); - - // Calculate the SRT depth - uint256 srtDepth = subDepth; - while (true) { - if (hashLength ** srtDepth >= numLeaves) { - break; - } - srtDepth++; - } - - if (_depth < srtDepth) revert DepthTooSmall(_depth, srtDepth); - - // If the depth is the same as the SRT depth, just use the SRT root - if (_depth == srtDepth) { - mainRoots[_depth] = smallSRTroot; - treeMerged = true; - return smallSRTroot; - } else { - root = smallSRTroot; - - // Calculate the main root - - for (uint256 i = srtDepth; i < _depth; i++) { - uint256 z = getZero(i); - - if (isBinary) { - uint256[2] memory inputs; - inputs[0] = root; - inputs[1] = z; - root = hash2(inputs); - } else { - uint256[5] memory inputs; - inputs[0] = root; - inputs[1] = z; - inputs[2] = z; - inputs[3] = z; - inputs[4] = z; - root = hash5(inputs); - } - } - - mainRoots[_depth] = root; - treeMerged = true; - } - } - - /// @notice Returns the subroot at the specified index. Reverts if the index refers - /// to a subtree which has not been filled yet. - /// @param _index The subroot index. - /// @return subRoot The subroot at the specified index. - function getSubRoot(uint256 _index) public view returns (uint256 subRoot) { - if (currentSubtreeIndex <= _index) revert InvalidIndex(_index); - subRoot = subRoots[_index]; - } - - /// @notice Returns the subroot tree (SRT) root. Its value must first be computed - /// using mergeSubRoots. - /// @return smallSubTreeRoot The SRT root. - function getSmallSRTroot() public view returns (uint256 smallSubTreeRoot) { - if (!subTreesMerged) revert SubTreesNotMerged(); - smallSubTreeRoot = smallSRTroot; - } - - /// @notice Return the merged Merkle root of all the leaves at a desired depth. - /// @dev merge() or merged(_depth) must be called first. - /// @param _depth The depth of the main tree. It must first be computed - /// using mergeSubRoots() and merge(). - /// @return mainRoot The root of the main tree. - function getMainRoot(uint256 _depth) public view returns (uint256 mainRoot) { - if (hashLength ** _depth < numLeaves) revert DepthTooSmall(_depth, numLeaves); - - mainRoot = mainRoots[_depth]; - } - - /// @notice Get the next subroot index and the current subtree index. - function getSrIndices() public view returns (uint256 next, uint256 current) { - next = nextSubRootIndex; - current = currentSubtreeIndex; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/AccQueueBinary.sol b/packages/hardhat/contracts/maci-contracts/trees/AccQueueBinary.sol deleted file mode 100644 index c66172d..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/AccQueueBinary.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { AccQueue } from "./AccQueue.sol"; - -/// @title AccQueueBinary -/// @notice This contract defines a Merkle tree where each leaf insertion only updates a -/// subtree. To obtain the main tree root, the contract owner must merge the -/// subtrees together. Merging subtrees requires at least 2 operations: -/// mergeSubRoots(), and merge(). To get around the gas limit, -/// the mergeSubRoots() can be performed in multiple transactions. -/// @dev This contract is for a binary tree (2 leaves per node) -abstract contract AccQueueBinary is AccQueue { - /// @notice Create a new AccQueueBinary - constructor(uint256 _subDepth) AccQueue(_subDepth, 2) {} - - /// @notice Hash the contents of the specified level and the specified leaf. - /// @param _level The level to hash. - /// @param _leaf The leaf include with the level. - /// @return hashed The hash of the level and leaf. - function hashLevel(uint256 _level, uint256 _leaf) internal override returns (uint256 hashed) { - hashed = hashLeftRight(leafQueue.levels[_level][0], _leaf); - - // Free up storage slots to refund gas. - delete leafQueue.levels[_level][0]; - } - - /// @notice Hash the contents of the specified level and the specified leaf. - function hashLevelLeaf(uint256 _level, uint256 _leaf) public view override returns (uint256 hashed) { - hashed = hashLeftRight(leafQueue.levels[_level][0], _leaf); - } - - /// @notice An internal function which fills a subtree. - /// @param _level The level at which to fill the subtree. - function _fill(uint256 _level) internal override { - while (_level < subDepth) { - uint256 n = leafQueue.indices[_level]; - - if (n != 0) { - // Fill the subtree level with zeros and hash the level - uint256 hashed; - - uint256[2] memory inputs; - uint256 z = getZero(_level); - inputs[0] = leafQueue.levels[_level][0]; - inputs[1] = z; - hashed = hash2(inputs); - - // Update the subtree from the next level onwards with the new leaf - _enqueue(hashed, _level + 1); - } - - // Reset the current level - delete leafQueue.indices[_level]; - - _level++; - } - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/AccQueueBinary0.sol b/packages/hardhat/contracts/maci-contracts/trees/AccQueueBinary0.sol deleted file mode 100644 index 93fe50c..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/AccQueueBinary0.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { MerkleZeros as MerkleBinary0 } from "./zeros/MerkleBinary0.sol"; -import { AccQueueBinary } from "./AccQueueBinary.sol"; - -/// @title AccQueueBinary0 -/// @notice This contract extends AccQueueBinary and MerkleBinary0 -/// @dev This contract is used for creating a -/// Merkle tree with binary (2 leaves per node) structure -contract AccQueueBinary0 is AccQueueBinary, MerkleBinary0 { - /// @notice Constructor for creating AccQueueBinary0 contract - /// @param _subDepth The depth of each subtree - constructor(uint256 _subDepth) AccQueueBinary(_subDepth) {} - - /// @notice Returns the zero leaf at a specified level - /// @param _level The level at which to return the zero leaf - /// @return zero The zero leaf at the specified level - function getZero(uint256 _level) internal view override returns (uint256 zero) { - zero = zeros[_level]; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/AccQueueBinaryMaci.sol b/packages/hardhat/contracts/maci-contracts/trees/AccQueueBinaryMaci.sol deleted file mode 100644 index 5d545f2..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/AccQueueBinaryMaci.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { MerkleZeros as MerkleBinaryMaci } from "./zeros/MerkleBinaryMaci.sol"; -import { AccQueueBinary } from "./AccQueueBinary.sol"; - -/// @title AccQueueBinaryMaci -/// @notice This contract extends AccQueueBinary and MerkleBinaryMaci -/// @dev This contract is used for creating a -/// Merkle tree with binary (2 leaves per node) structure -contract AccQueueBinaryMaci is AccQueueBinary, MerkleBinaryMaci { - /// @notice Constructor for creating AccQueueBinaryMaci contract - /// @param _subDepth The depth of each subtree - constructor(uint256 _subDepth) AccQueueBinary(_subDepth) {} - - /// @notice Returns the zero leaf at a specified level - /// @param _level The level at which to return the zero leaf - function getZero(uint256 _level) internal view override returns (uint256 zero) { - zero = zeros[_level]; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/AccQueueQuinary.sol b/packages/hardhat/contracts/maci-contracts/trees/AccQueueQuinary.sol deleted file mode 100644 index 25d8d3d..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/AccQueueQuinary.sol +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { AccQueue } from "./AccQueue.sol"; - -/// @title AccQueueQuinary -/// @notice This contract defines a Merkle tree where each leaf insertion only updates a -/// subtree. To obtain the main tree root, the contract owner must merge the -/// subtrees together. Merging subtrees requires at least 2 operations: -/// mergeSubRoots(), and merge(). To get around the gas limit, -/// the mergeSubRoots() can be performed in multiple transactions. -/// @dev This contract is for a quinary tree (5 leaves per node) -abstract contract AccQueueQuinary is AccQueue { - /// @notice Create a new AccQueueQuinary instance - constructor(uint256 _subDepth) AccQueue(_subDepth, 5) {} - - /// @notice Hash the contents of the specified level and the specified leaf. - /// @dev it also frees up storage slots to refund gas. - /// @param _level The level to hash. - /// @param _leaf The leaf include with the level. - /// @return hashed The hash of the level and leaf. - function hashLevel(uint256 _level, uint256 _leaf) internal override returns (uint256 hashed) { - uint256[5] memory inputs; - inputs[0] = leafQueue.levels[_level][0]; - inputs[1] = leafQueue.levels[_level][1]; - inputs[2] = leafQueue.levels[_level][2]; - inputs[3] = leafQueue.levels[_level][3]; - inputs[4] = _leaf; - hashed = hash5(inputs); - - // Free up storage slots to refund gas. Note that using a loop here - // would result in lower gas savings. - delete leafQueue.levels[_level]; - } - - /// @notice Hash the contents of the specified level and the specified leaf. - /// @param _level The level to hash. - /// @param _leaf The leaf include with the level. - /// @return hashed The hash of the level and leaf. - function hashLevelLeaf(uint256 _level, uint256 _leaf) public view override returns (uint256 hashed) { - uint256[5] memory inputs; - inputs[0] = leafQueue.levels[_level][0]; - inputs[1] = leafQueue.levels[_level][1]; - inputs[2] = leafQueue.levels[_level][2]; - inputs[3] = leafQueue.levels[_level][3]; - inputs[4] = _leaf; - hashed = hash5(inputs); - } - - /// @notice An internal function which fills a subtree - /// @param _level The level at which to fill the subtree - function _fill(uint256 _level) internal override { - while (_level < subDepth) { - uint256 n = leafQueue.indices[_level]; - - if (n != 0) { - // Fill the subtree level with zeros and hash the level - uint256 hashed; - - uint256[5] memory inputs; - uint256 z = getZero(_level); - uint8 i = 0; - for (; i < n; i++) { - inputs[i] = leafQueue.levels[_level][i]; - } - - for (; i < hashLength; i++) { - inputs[i] = z; - } - hashed = hash5(inputs); - - // Update the subtree from the next level onwards with the new leaf - _enqueue(hashed, _level + 1); - } - - // Reset the current level - delete leafQueue.indices[_level]; - - _level++; - } - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/AccQueueQuinary0.sol b/packages/hardhat/contracts/maci-contracts/trees/AccQueueQuinary0.sol deleted file mode 100644 index f8edfcf..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/AccQueueQuinary0.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { MerkleZeros as MerkleQuinary0 } from "./zeros/MerkleQuinary0.sol"; -import { AccQueueQuinary } from "./AccQueueQuinary.sol"; - -/// @title AccQueueQuinary0 -/// @notice This contract extends AccQueueQuinary and MerkleQuinary0 -/// @dev This contract is used for creating a -/// Merkle tree with quinary (5 leaves per node) structure -contract AccQueueQuinary0 is AccQueueQuinary, MerkleQuinary0 { - /// @notice Constructor for creating AccQueueQuinary0 contract - /// @param _subDepth The depth of each subtree - constructor(uint256 _subDepth) AccQueueQuinary(_subDepth) {} - - /// @notice Returns the zero leaf at a specified level - /// @param _level The level at which to return the zero leaf - /// @return zero The zero leaf at the specified level - function getZero(uint256 _level) internal view override returns (uint256 zero) { - zero = zeros[_level]; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/AccQueueQuinaryBlankSl.sol b/packages/hardhat/contracts/maci-contracts/trees/AccQueueQuinaryBlankSl.sol deleted file mode 100644 index ca7e8c1..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/AccQueueQuinaryBlankSl.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { MerkleZeros as MerkleQuinaryBlankSl } from "./zeros/MerkleQuinaryBlankSl.sol"; -import { AccQueueQuinary } from "./AccQueueQuinary.sol"; - -/// @title AccQueueQuinaryBlankSl -/// @notice This contract extends AccQueueQuinary and MerkleQuinaryBlankSl -/// @dev This contract is used for creating a -/// Merkle tree with quinary (5 leaves per node) structure -contract AccQueueQuinaryBlankSl is AccQueueQuinary, MerkleQuinaryBlankSl { - /// @notice Constructor for creating AccQueueQuinaryBlankSl contract - /// @param _subDepth The depth of each subtree - constructor(uint256 _subDepth) AccQueueQuinary(_subDepth) {} - - /// @notice Returns the zero leaf at a specified level - /// @param _level The level at which to return the zero leaf - /// @return zero The zero leaf at the specified level - function getZero(uint256 _level) internal view override returns (uint256 zero) { - zero = zeros[_level]; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/AccQueueQuinaryMaci.sol b/packages/hardhat/contracts/maci-contracts/trees/AccQueueQuinaryMaci.sol deleted file mode 100644 index 01980fc..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/AccQueueQuinaryMaci.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { MerkleZeros as MerkleQuinaryMaci } from "./zeros/MerkleQuinaryMaci.sol"; -import { AccQueueQuinary } from "./AccQueueQuinary.sol"; - -/// @title AccQueueQuinaryMaci -/// @notice This contract extends AccQueueQuinary and MerkleQuinaryMaci -/// @dev This contract is used for creating a -/// Merkle tree with quinary (5 leaves per node) structure -contract AccQueueQuinaryMaci is AccQueueQuinary, MerkleQuinaryMaci { - /// @notice Constructor for creating AccQueueQuinaryMaci contract - /// @param _subDepth The depth of each subtree - constructor(uint256 _subDepth) AccQueueQuinary(_subDepth) {} - - /// @notice Returns the zero leaf at a specified level - /// @param _level The level at which to return the zero leaf - /// @return zero The zero leaf at the specified level - function getZero(uint256 _level) internal view override returns (uint256 zero) { - zero = zeros[_level]; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/EmptyBallotRoots.sol b/packages/hardhat/contracts/maci-contracts/trees/EmptyBallotRoots.sol deleted file mode 100644 index 854685c..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/EmptyBallotRoots.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -abstract contract EmptyBallotRoots { - // emptyBallotRoots contains the roots of Ballot trees of five leaf - // configurations. - // Each tree has a depth of 10, which is the hardcoded state tree depth. - // Each leaf is an empty ballot. A configuration refers to the depth of the - // voice option tree for that ballot. - - // The leaf for the root at index 0 contains hash(0, root of a VO tree with - // depth 1 and zero-value 0) - - // The leaf for the root at index 1 contains hash(0, root of a VO tree with - // depth 2 and zero-value 0) - - // ... and so on. - - // The first parameter to the hash function is the nonce, which is 0. - - uint256[5] internal emptyBallotRoots; - - constructor() { - emptyBallotRoots[0] = uint256(6579820437991406069687396372962263845395426835385368878767605633903648955255); - emptyBallotRoots[1] = uint256(9105453741665960449792281626882014222103501499246287334255160659262747058842); - emptyBallotRoots[2] = uint256(14830222164980158319423900821611648302565544940504586015002280367515043751869); - emptyBallotRoots[3] = uint256(12031563002271722465187541954825013132282571927669361737331626664787916495335); - emptyBallotRoots[4] = uint256(5204612805325639173251450278876337947880680931527922506745154187077640790699); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleBinary0.sol b/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleBinary0.sol deleted file mode 100644 index 91e0ac1..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleBinary0.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -abstract contract MerkleZeros { - uint256[33] internal zeros; - - // Binary tree zeros (0) - constructor() { - zeros[0] = uint256(0); - zeros[1] = uint256(14744269619966411208579211824598458697587494354926760081771325075741142829156); - zeros[2] = uint256(7423237065226347324353380772367382631490014989348495481811164164159255474657); - zeros[3] = uint256(11286972368698509976183087595462810875513684078608517520839298933882497716792); - zeros[4] = uint256(3607627140608796879659380071776844901612302623152076817094415224584923813162); - zeros[5] = uint256(19712377064642672829441595136074946683621277828620209496774504837737984048981); - zeros[6] = uint256(20775607673010627194014556968476266066927294572720319469184847051418138353016); - zeros[7] = uint256(3396914609616007258851405644437304192397291162432396347162513310381425243293); - zeros[8] = uint256(21551820661461729022865262380882070649935529853313286572328683688269863701601); - zeros[9] = uint256(6573136701248752079028194407151022595060682063033565181951145966236778420039); - zeros[10] = uint256(12413880268183407374852357075976609371175688755676981206018884971008854919922); - zeros[11] = uint256(14271763308400718165336499097156975241954733520325982997864342600795471836726); - zeros[12] = uint256(20066985985293572387227381049700832219069292839614107140851619262827735677018); - zeros[13] = uint256(9394776414966240069580838672673694685292165040808226440647796406499139370960); - zeros[14] = uint256(11331146992410411304059858900317123658895005918277453009197229807340014528524); - zeros[15] = uint256(15819538789928229930262697811477882737253464456578333862691129291651619515538); - zeros[16] = uint256(19217088683336594659449020493828377907203207941212636669271704950158751593251); - zeros[17] = uint256(21035245323335827719745544373081896983162834604456827698288649288827293579666); - zeros[18] = uint256(6939770416153240137322503476966641397417391950902474480970945462551409848591); - zeros[19] = uint256(10941962436777715901943463195175331263348098796018438960955633645115732864202); - zeros[20] = uint256(15019797232609675441998260052101280400536945603062888308240081994073687793470); - zeros[21] = uint256(11702828337982203149177882813338547876343922920234831094975924378932809409969); - zeros[22] = uint256(11217067736778784455593535811108456786943573747466706329920902520905755780395); - zeros[23] = uint256(16072238744996205792852194127671441602062027943016727953216607508365787157389); - zeros[24] = uint256(17681057402012993898104192736393849603097507831571622013521167331642182653248); - zeros[25] = uint256(21694045479371014653083846597424257852691458318143380497809004364947786214945); - zeros[26] = uint256(8163447297445169709687354538480474434591144168767135863541048304198280615192); - zeros[27] = uint256(14081762237856300239452543304351251708585712948734528663957353575674639038357); - zeros[28] = uint256(16619959921569409661790279042024627172199214148318086837362003702249041851090); - zeros[29] = uint256(7022159125197495734384997711896547675021391130223237843255817587255104160365); - zeros[30] = uint256(4114686047564160449611603615418567457008101555090703535405891656262658644463); - zeros[31] = uint256(12549363297364877722388257367377629555213421373705596078299904496781819142130); - zeros[32] = uint256(21443572485391568159800782191812935835534334817699172242223315142338162256601); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleBinaryMaci.sol b/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleBinaryMaci.sol deleted file mode 100644 index 2fd0a3b..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleBinaryMaci.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -abstract contract MerkleZeros { - uint256[33] internal zeros; - - // Binary tree zeros (Keccak hash of 'Maci') - constructor() { - zeros[0] = uint256(8370432830353022751713833565135785980866757267633941821328460903436894336785); - zeros[1] = uint256(13883108378505681706501741077199723943829197421795883447299356576923144768890); - zeros[2] = uint256(15419121528227002346615807695865368688447806543310218580451656713665933966440); - zeros[3] = uint256(6318262337906428951291657677634338300639543013249211096760913778778957055324); - zeros[4] = uint256(17768974272065709481357540291486641669761745417382244600494648537227290564775); - zeros[5] = uint256(1030673773521289386438564854581137730704523062376261329171486101180288653537); - zeros[6] = uint256(2456832313683926177308273721786391957119973242153180895324076357329047000368); - zeros[7] = uint256(8719489529991410281576768848178751308798998844697260960510058606396118487868); - zeros[8] = uint256(1562826620410077272445821684229580081819470607145780146992088471567204924361); - zeros[9] = uint256(2594027261737512958249111386518678417918764295906952540494120924791242533396); - zeros[10] = uint256(7454652670930646290900416353463196053308124896106736687630886047764171239135); - zeros[11] = uint256(5636576387316613237724264020484439958003062686927585603917058282562092206685); - zeros[12] = uint256(6668187911340361678685285736007075111202281125695563765600491898900267193410); - zeros[13] = uint256(11734657993452490720698582048616543923742816272311967755126326688155661525563); - zeros[14] = uint256(13463263143201754346725031241082259239721783038365287587742190796879610964010); - zeros[15] = uint256(7428603293293611296009716236093531014060986236553797730743998024965500409844); - zeros[16] = uint256(3220236805148173410173179641641444848417275827082321553459407052920864882112); - zeros[17] = uint256(5702296734156546101402281555025360809782656712426280862196339683480526959100); - zeros[18] = uint256(18054517726590450486276822815339944904333304893252063892146748222745553261079); - zeros[19] = uint256(15845875411090302918698896692858436856780638250734551924718281082237259235021); - zeros[20] = uint256(15856603049544947491266127020967880429380981635456797667765381929897773527801); - zeros[21] = uint256(16947753390809968528626765677597268982507786090032633631001054889144749318212); - zeros[22] = uint256(4409871880435963944009375001829093050579733540305802511310772748245088379588); - zeros[23] = uint256(3999924973235726549616800282209401324088787314476870617570702819461808743202); - zeros[24] = uint256(5910085476731597359542102744346894725393370185329725031545263392891885548800); - zeros[25] = uint256(8329789525184689042321668445575725185257025982565085347238469712583602374435); - zeros[26] = uint256(21731745958669991600655184668442493750937309130671773804712887133863507145115); - zeros[27] = uint256(13908786229946466860099145463206281117295829828306413881947857340025780878375); - zeros[28] = uint256(2746378384965515118858350021060497341885459652705230422460541446030288889144); - zeros[29] = uint256(4024247518003740702537513711866227003187955635058512298109553363285388770811); - zeros[30] = uint256(13465368596069181921705381841358161201578991047593533252870698635661853557810); - zeros[31] = uint256(1901585547727445451328488557530824986692473576054582208711800336656801352314); - zeros[32] = uint256(3444131905730490180878137209421656122704458854785641062326389124060978485990); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleQuinary0.sol b/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleQuinary0.sol deleted file mode 100644 index 31d363f..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleQuinary0.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -abstract contract MerkleZeros { - uint256[33] internal zeros; - - // Quinary tree zeros (0) - constructor() { - zeros[0] = uint256(0); - zeros[1] = uint256(14655542659562014735865511769057053982292279840403315552050801315682099828156); - zeros[2] = uint256(19261153649140605024552417994922546473530072875902678653210025980873274131905); - zeros[3] = uint256(21526503558325068664033192388586640128492121680588893182274749683522508994597); - zeros[4] = uint256(20017764101928005973906869479218555869286328459998999367935018992260318153770); - zeros[5] = uint256(16998355316577652097112514691750893516081130026395813155204269482715045879598); - zeros[6] = uint256(2612442706402737973181840577010736087708621987282725873936541279764292204086); - zeros[7] = uint256(17716535433480122581515618850811568065658392066947958324371350481921422579201); - zeros[8] = uint256(17437916409890180001398333108882255895598851862997171508841759030332444017770); - zeros[9] = uint256(20806704410832383274034364623685369279680495689837539882650535326035351322472); - zeros[10] = uint256(6821382292698461711184253213986441870942786410912797736722948342942530789476); - zeros[11] = uint256(5916648769022832355861175588931687601652727028178402815013820610204855544893); - zeros[12] = uint256(8979092375429814404031883906996857902016801693563521316025319397481362525766); - zeros[13] = uint256(2921214989930864339537708350754648834701757280474461132621735242274490553963); - zeros[14] = uint256(8930183771974746972686153669144011224662017420905079900118160414492327314176); - zeros[15] = uint256(235368305313252659057202520253547068193638476511860624369389264358598810396); - zeros[16] = uint256(11594802086624841314469980089838552727386894436467147447204224403068085066609); - zeros[17] = uint256(6527402365840056202903190531155009847198979121365335038364206235405082926579); - zeros[18] = uint256(7890267294950363768070024023123773394579161137981585347919627664365669195485); - zeros[19] = uint256(7743021844925994795008658518659888250339967931662466893787320922384170613250); - zeros[20] = uint256(3315762791558236426429898223445373782079540514426385620818139644150484427120); - zeros[21] = uint256(12047412166753578299610528762227103229354276396579409944098869901020016693788); - zeros[22] = uint256(7346375653460369101190037700418084792046605818930533590372465301789036536); - zeros[23] = uint256(16686328169837855831280640081580124364395471639440725186157725609010405016551); - zeros[24] = uint256(19105160640579355001844872723857900201603625359252284777965070378555675817865); - zeros[25] = uint256(17054399483511247964029303840879817843788388567881464290309597953132679359256); - zeros[26] = uint256(5296258093842160235704190490839277292290579093574356735268980000023915581697); - zeros[27] = uint256(8993437003863084469472897416707962588904917898547964184966432920162387360131); - zeros[28] = uint256(7234267096117283161039619077058835089667467648437312224957703988301725566335); - zeros[29] = uint256(21640448288319814375882234036901598260365718394023649234526744669922384765526); - zeros[30] = uint256(1595567811792178436811872247033324109773732075641399161664435302467654689847); - zeros[31] = uint256(15291095622175285816966181294098521638815701170497062413595539727181544870101); - zeros[32] = uint256(15837036953038489303182430773663047564827202645548797032627170282475341436016); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleQuinaryBlankSl.sol b/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleQuinaryBlankSl.sol deleted file mode 100644 index 2f67354..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleQuinaryBlankSl.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -abstract contract MerkleZeros { - uint256[33] internal zeros; - - // Quinary tree zeros (hash of a blank state leaf) - constructor() { - zeros[0] = uint256(6769006970205099520508948723718471724660867171122235270773600567925038008762); - zeros[1] = uint256(1817443256073160983037956906834195537015546107754139333779374752610409243040); - zeros[2] = uint256(5025334324706345710800763986625066818722194863275454698142520938431664775139); - zeros[3] = uint256(14192954438167108345302805021925904074255585459982294518284934685870159779036); - zeros[4] = uint256(20187882570958996766847085412101405873580281668670041750401431925441526137696); - zeros[5] = uint256(19003337309269317766726592380821628773167513668895143249995308839385810331053); - zeros[6] = uint256(8492845964288036916491732908697290386617362835683911619537012952509890847451); - zeros[7] = uint256(21317322053785868903775560086424946986124609731059541056518805391492871868814); - zeros[8] = uint256(4256218134522031233385262696416028085306220785615095518146227774336224649500); - zeros[9] = uint256(20901832483812704342876390942522900825096860186886589193649848721504734341597); - zeros[10] = uint256(9267454486648593048583319961333207622177969074484816717792204743506543655505); - zeros[11] = uint256(7650747654726613674993974917452464536868175649563857452207429547024788245109); - zeros[12] = uint256(12795449162487060618571749226308575208199045387848354123797521555997299022426); - zeros[13] = uint256(2618557044910497521493457299926978327841926538380467450910611798747947773417); - zeros[14] = uint256(4921285654960018268026585535199462620025474147042548993648101553653712920841); - zeros[15] = uint256(3955171118947393404895230582611078362154691627898437205118006583966987624963); - zeros[16] = uint256(14699122743207261418107167543163571550551347592030521489185842204376855027947); - zeros[17] = uint256(19194001556311522650950142975587831061973644651464593103195262630226529549573); - zeros[18] = uint256(6797319293744791648201295415173228627305696583566554220235084234134847845566); - zeros[19] = uint256(1267384159070923114421683251804507954363252272096341442482679590950570779538); - zeros[20] = uint256(3856223245980092789300785214737986268213218594679123772901587106666007826613); - zeros[21] = uint256(18676489457897260843888223351978541467312325190019940958023830749320128516742); - zeros[22] = uint256(1264182110328471160091364892521750324454825019784514769029658712768604765832); - zeros[23] = uint256(2656996430278859489720531694992812241970377217691981498421470018287262214836); - zeros[24] = uint256(18383091906017498328025573868990834275527351249551450291689105976789994000945); - zeros[25] = uint256(13529005048172217954112431586843818755284974925259175262114689118374272942448); - zeros[26] = uint256(12992932230018177961399273443546858115054107741258772159002781102941121463198); - zeros[27] = uint256(2863122912185356538647249583178796893334871904920344676880115119793539219810); - zeros[28] = uint256(21225940722224750787686036600289689346822264717843340643526494987845938066724); - zeros[29] = uint256(10287710058152238258370855601473179390407624438853416678054122418589867334291); - zeros[30] = uint256(19473882726731003241332772446613588021823731071450664115530121948154136765165); - zeros[31] = uint256(5317840242664832852914696563734700089268851122527105938301831862363938018455); - zeros[32] = uint256(16560004488485252485490851383643926099553282582813695748927880827248594395952); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleQuinaryMaci.sol b/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleQuinaryMaci.sol deleted file mode 100644 index 769b138..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleQuinaryMaci.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -abstract contract MerkleZeros { - uint256[33] internal zeros; - - // Quinary tree zeros (Keccak hash of 'Maci') - constructor() { - zeros[0] = uint256(8370432830353022751713833565135785980866757267633941821328460903436894336785); - zeros[1] = uint256(12915444503621073454579416579430905206970714557680052030066757042249102605307); - zeros[2] = uint256(15825388848727206932541662858173052318786639683743459477657913288690190505308); - zeros[3] = uint256(20672917177817295069558894035958266756825295443848082659014905185716743537191); - zeros[4] = uint256(448586013948167251740855715259393055429962470693972912240018559200278204556); - zeros[5] = uint256(3228865992178886480410396198366133115832717015233640381802715479176981303177); - zeros[6] = uint256(19116532419590876304532847271428641103751206695152259493043279205958851263600); - zeros[7] = uint256(13531983203936271379763604150672239370281863210813769735936250692178889682484); - zeros[8] = uint256(8276490051100115441938424474671329955897359239518198952109759468777824929104); - zeros[9] = uint256(1234816188709792521426066175633785051600533236493067959807265450339481920006); - zeros[10] = uint256(14253963034950198848796956783804665963745244419038717333683296599064556174281); - zeros[11] = uint256(6367560368479067766970398112009211893636892126125767203198799843543931913172); - zeros[12] = uint256(9086778412328290069463938062555298073857321633960448227011862356090607842391); - zeros[13] = uint256(1440983698234119608650157588008070947531139377294971527360643096251396484622); - zeros[14] = uint256(3957599085599383799297196095384587366602816424699353871878382158004571037876); - zeros[15] = uint256(2874250189355749385170216620368454832544508482778847425177457138604069991955); - zeros[16] = uint256(21009179226085449764156117702096359546848859855915028677582017987249294772778); - zeros[17] = uint256(11639371146919469643603772238908032714588430905217730187804009793768292270213); - zeros[18] = uint256(6279313411277883478350325643881386249374023631847602720184182017599127173896); - zeros[19] = uint256(21059196126634383551994255775761712285020874549906884292741523421591865338509); - zeros[20] = uint256(9444544622817172574621750245792527383369133221167610044960147559319164808325); - zeros[21] = uint256(5374570219497355452080912323548395721574511162814862844226178635172695078543); - zeros[22] = uint256(4155904241440251764630449308160227499466701168124519106689866311729092343061); - zeros[23] = uint256(15881609944326576145786405158479503217901875433072026818450276983706463215155); - zeros[24] = uint256(20831546672064137588434602157208687297579005252478070660473540633558666587287); - zeros[25] = uint256(3209071488384365842993449718919243416332014108747571544339190291353564426179); - zeros[26] = uint256(10030934989297780221224272248227257782450689603145083016739151821673604746295); - zeros[27] = uint256(16504852316033851373501270056537918974469380446508638487151124538300880427080); - zeros[28] = uint256(5226137093551352657015038416264755428944140743893702595442932837011856178457); - zeros[29] = uint256(18779994066356991319291039019820482828679702085087990978933303018673869446075); - zeros[30] = uint256(12037506572124351893114409509086276299115869080424687624451184925646292710978); - zeros[31] = uint256(12049750997011422639258622747494178076018128204515149991024639355149614767606); - zeros[32] = uint256(3171463916443906096008599541392648187002297410622977814790586531203175805057); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleQuinaryMaciWithSha256.sol b/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleQuinaryMaciWithSha256.sol deleted file mode 100644 index d5f4001..0000000 --- a/packages/hardhat/contracts/maci-contracts/trees/zeros/MerkleQuinaryMaciWithSha256.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -abstract contract MerkleZeros { - uint256[33] internal zeros; - - // Quinary tree (with SHA256) zeros (Keccak hash of 'Maci') - constructor() { - zeros[0] = uint256(8370432830353022751713833565135785980866757267633941821328460903436894336785); - zeros[1] = uint256(15325010760924867811060011598468731160102892305643597546418886993209427402124); - zeros[2] = uint256(5333436556486022924864323600267292046975057763731162279859756425998760869639); - zeros[3] = uint256(10977687713725258236554818575005054118245377800202335296681050076688083648086); - zeros[4] = uint256(17224613048028572295675465280070534343823303585304562923867790579733480935264); - zeros[5] = uint256(17706041913665507482150667409133417574717160680803140264120398284023956076290); - zeros[6] = uint256(9653598640710890037650186704093119545289422499247280741743943320819000499646); - zeros[7] = uint256(3206708589682338778875464217516564639886138074743860970529166723769308693331); - zeros[8] = uint256(20534426109262125257001376157024458165019301442070720434223770308413031897106); - zeros[9] = uint256(20045595674714290179477944839500625050021328745176742196691278453734645214461); - zeros[10] = uint256(1990443879384407462884849355600260727579259402554912319388203567178699099823); - zeros[11] = uint256(15030670756630149022405255264285307614365927334767433095054187593088567423357); - zeros[12] = uint256(18151643848963813172699123574112664048044995742942448148573079318091374187889); - zeros[13] = uint256(12716797128662011430654728594886216826078433472768452754660103993065334317074); - zeros[14] = uint256(1778668013271642889777963040449750701990462149853502233417669847457236064652); - zeros[15] = uint256(5310295518327181913512672840814534597220436900477241678956359474542866650820); - zeros[16] = uint256(13698756698956924170299002904918188137369325655270855940405108330875686641692); - zeros[17] = uint256(16978698509212058134355374823422776609466830925429320593002017159097039391798); - zeros[18] = uint256(21122904167710384374017181343962526230952584354459613272526061056824616537143); - zeros[19] = uint256(5985710021335277534018076016950505662155095700842597825798268278683684529911); - zeros[20] = uint256(12532916265365969493430834411976825909479396013951866588908395278818546013433); - zeros[21] = uint256(8930761113974965197874653050290197596753921117163820694764716198465769499045); - zeros[22] = uint256(7923291528963393397483250836756011061887097780645138922028359275174896145293); - zeros[23] = uint256(3165523255399386676797999966015195343651238663671903081942130824893771740953); - zeros[24] = uint256(16498953853801480907823499768835003172991697981391001961022924988055514153444); - zeros[25] = uint256(4646652977614280202033130495149148518982017582438403557846318228188699893314); - zeros[26] = uint256(16063634456514132367413661909718374200540548246284043795891576706199387111176); - zeros[27] = uint256(6432449679436816515158314256021560028822839412197804709124582783531979581762); - zeros[28] = uint256(16549548229658147491856279832226477385154640474741924661165993652668688816447); - zeros[29] = uint256(17839947633190642328550610337345984157351952156869520211179465702618934306508); - zeros[30] = uint256(12740635476725314448365579529753493622477881762096050379151557051600454293132); - zeros[31] = uint256(14450546044445547667240670175592035046062311068467905405735885913523641104070); - zeros[32] = uint256(16649881337797029358598450172037019406882299786178038601098631221224645092238); - } -} diff --git a/packages/hardhat/contracts/maci-contracts/utilities/CommonUtilities.sol b/packages/hardhat/contracts/maci-contracts/utilities/CommonUtilities.sol deleted file mode 100644 index 901d017..0000000 --- a/packages/hardhat/contracts/maci-contracts/utilities/CommonUtilities.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { IPoll } from "../interfaces/IPoll.sol"; - -/// @title CommonUtilities -/// @notice A contract that holds common utilities -/// which are to be used by multiple contracts -/// namely Tally and MessageProcessor -contract CommonUtilities { - error VotingPeriodNotPassed(); - - /// @notice common function for MessageProcessor, and Tally - /// @param _poll the poll to be checked - function _votingPeriodOver(IPoll _poll) internal view { - (uint256 deployTime, uint256 duration) = _poll.getDeployTimeAndDuration(); - // Require that the voting period is over - uint256 secondsPassed = block.timestamp - deployTime; - if (secondsPassed <= duration) { - revert VotingPeriodNotPassed(); - } - } -} diff --git a/packages/hardhat/contracts/maci-contracts/utilities/DomainObjs.sol b/packages/hardhat/contracts/maci-contracts/utilities/DomainObjs.sol deleted file mode 100644 index e4d7858..0000000 --- a/packages/hardhat/contracts/maci-contracts/utilities/DomainObjs.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -/// @title DomainObjs -/// @notice An utility contract that holds -/// a number of domain objects and functions -contract DomainObjs { - /// @notice the length of a MACI message - uint8 public constant MESSAGE_DATA_LENGTH = 10; - - /// @title Message - /// @notice this struct represents a MACI message - /// @dev msgType: 1 for vote message, 2 for topup message (size 2) - struct Message { - uint256 msgType; - uint256[MESSAGE_DATA_LENGTH] data; - } - - /// @title PubKey - /// @notice A MACI public key - struct PubKey { - uint256 x; - uint256 y; - } - - /// @title StateLeaf - /// @notice A MACI state leaf - /// @dev used to represent a user's state - /// in the state Merkle tree - struct StateLeaf { - PubKey pubKey; - uint256 voiceCreditBalance; - uint256 timestamp; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/utilities/Params.sol b/packages/hardhat/contracts/maci-contracts/utilities/Params.sol deleted file mode 100644 index 8d48e19..0000000 --- a/packages/hardhat/contracts/maci-contracts/utilities/Params.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -import { IMACI } from "../interfaces/IMACI.sol"; -import { AccQueue } from "../trees/AccQueue.sol"; -import { TopupCredit } from "../TopupCredit.sol"; - -/// @title Params -/// @notice This contracts contains a number of structures -/// which are to be passed as parameters to Poll contracts. -/// This way we can reduce the number of parameters -/// and avoid a stack too deep error during compilation. -contract Params { - /// @notice A struct holding the depths of the merkle trees - struct TreeDepths { - uint8 intStateTreeDepth; - uint8 messageTreeSubDepth; - uint8 messageTreeDepth; - uint8 voteOptionTreeDepth; - } - - /// @notice A struct holding the max values for the poll - struct MaxValues { - uint256 maxMessages; - uint256 maxVoteOptions; - } - - /// @notice A struct holding the external contracts - /// that are to be passed to a Poll contract on - /// deployment - struct ExtContracts { - IMACI maci; - AccQueue messageAq; - TopupCredit topupCredit; - } -} diff --git a/packages/hardhat/contracts/maci-contracts/utilities/Utilities.sol b/packages/hardhat/contracts/maci-contracts/utilities/Utilities.sol deleted file mode 100644 index 488bf39..0000000 --- a/packages/hardhat/contracts/maci-contracts/utilities/Utilities.sol +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; -import { DomainObjs } from "./DomainObjs.sol"; -import { Hasher } from "../crypto/Hasher.sol"; -import { SnarkConstants } from "../crypto/SnarkConstants.sol"; - -/// @title Utilities -/// @notice An utility contract that can be used to: -/// * hash a state leaf -/// * pad and hash a MACI message -/// * hash a MACI message and an encryption public key -contract Utilities is SnarkConstants, DomainObjs, Hasher { - /// @notice custom errors - error InvalidMessage(); - - /// @notice An utility function used to hash a state leaf - /// @param _stateLeaf the state leaf to be hashed - /// @return ciphertext The hash of the state leaf - function hashStateLeaf(StateLeaf memory _stateLeaf) public pure returns (uint256 ciphertext) { - uint256[4] memory plaintext; - plaintext[0] = _stateLeaf.pubKey.x; - plaintext[1] = _stateLeaf.pubKey.y; - plaintext[2] = _stateLeaf.voiceCreditBalance; - plaintext[3] = _stateLeaf.timestamp; - - ciphertext = hash4(plaintext); - } - - /// @notice An utility function used to pad and hash a MACI message - /// @param dataToPad the data to be padded - /// @param msgType the type of the message - /// @return message The padded message - /// @return padKey The padding public key - /// @return msgHash The hash of the padded message and encryption key - function padAndHashMessage( - uint256[2] memory dataToPad, - uint256 msgType - ) public pure returns (Message memory message, PubKey memory padKey, uint256 msgHash) { - // add data and pad it to 10 elements (automatically cause it's the default value) - uint256[10] memory dat; - dat[0] = dataToPad[0]; - dat[1] = dataToPad[1]; - - padKey = PubKey(PAD_PUBKEY_X, PAD_PUBKEY_Y); - message = Message({ msgType: msgType, data: dat }); - msgHash = hashMessageAndEncPubKey(message, padKey); - } - - /// @notice An utility function used to hash a MACI message and an encryption public key - /// @param _message the message to be hashed - /// @param _encPubKey the encryption public key to be hashed - /// @return msgHash The hash of the message and the encryption public key - function hashMessageAndEncPubKey( - Message memory _message, - PubKey memory _encPubKey - ) public pure returns (uint256 msgHash) { - if (_message.data.length != 10) { - revert InvalidMessage(); - } - - uint256[5] memory n; - n[0] = _message.data[0]; - n[1] = _message.data[1]; - n[2] = _message.data[2]; - n[3] = _message.data[3]; - n[4] = _message.data[4]; - - uint256[5] memory m; - m[0] = _message.data[5]; - m[1] = _message.data[6]; - m[2] = _message.data[7]; - m[3] = _message.data[8]; - m[4] = _message.data[9]; - - msgHash = hash5([_message.msgType, hash5(n), hash5(m), _encPubKey.x, _encPubKey.y]); - } -} diff --git a/packages/hardhat/deploy/08_maci.ts b/packages/hardhat/deploy/08_maci.ts index d9993a1..06e16fb 100644 --- a/packages/hardhat/deploy/08_maci.ts +++ b/packages/hardhat/deploy/08_maci.ts @@ -6,7 +6,7 @@ import { TopupCreditContractName, stateTreeDepth, } from "../constants"; -import { MACI, SignUpGatekeeper } from "../typechain-types"; +import { MACIWrapper, SignUpGatekeeper } from "../typechain-types"; // const STATE_TREE_SUBDEPTH = 2; @@ -24,7 +24,7 @@ const deployContracts: DeployFunction = async function (hre: HardhatRuntimeEnvir const messageProcessorFactory = await hre.ethers.getContract("MessageProcessorFactory", deployer); const tallyFactory = await hre.ethers.getContract("TallyFactory", deployer); - await hre.deployments.deploy("MACI", { + await hre.deployments.deploy("MACIWrapper", { from: deployer, args: [ await pollFactory.getAddress(), @@ -45,17 +45,11 @@ const deployContracts: DeployFunction = async function (hre: HardhatRuntimeEnvir autoMine: true, }); - const maci = await hre.ethers.getContract("MACI", deployer); + const maci = await hre.ethers.getContract("MACIWrapper", deployer); console.log(`The MACI contract is deployed at ${await maci.getAddress()}`); await gatekeeper.setMaciInstance(await maci.getAddress()); - - // // Save the acc queue to the deployments - // await hre.deployments.save("AccQueue", { - // abi: (await hre.artifacts.readArtifact("AccQueue")).abi, - // address: await maci.stateAq(), - // }); }; export default deployContracts; diff --git a/packages/hardhat/deploy/09_vk_registry.ts b/packages/hardhat/deploy/09_vk_registry.ts index 8c03d25..8b73a24 100644 --- a/packages/hardhat/deploy/09_vk_registry.ts +++ b/packages/hardhat/deploy/09_vk_registry.ts @@ -1,7 +1,7 @@ -import { extractVk } from "../maci-ts/circuits"; -import { VerifyingKey } from "../maci-ts/domainobjs"; +import { extractVk } from "maci-circuits"; +import { VerifyingKey } from "maci-domainobjs"; -import type { IVerifyingKeyStruct } from "../maci-ts/ts/types"; +import type { IVerifyingKeyStruct } from "maci-contracts"; import type { VkRegistry } from "../typechain-types"; import { HardhatRuntimeEnvironment } from "hardhat/types"; diff --git a/packages/hardhat/deploy/10_poll_manager.ts b/packages/hardhat/deploy/10_poll_manager.ts index 5f09a2c..4905dbf 100644 --- a/packages/hardhat/deploy/10_poll_manager.ts +++ b/packages/hardhat/deploy/10_poll_manager.ts @@ -1,9 +1,9 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import { DeployFunction } from "hardhat-deploy/types"; -import { MACI, PollManager, Verifier, VkRegistry } from "../typechain-types"; import fs from "fs"; -import { Keypair } from "../maci-ts/domainobjs"; -import { useQuadraticVoting } from "../constants"; +import { Keypair } from "maci-domainobjs"; + +import { MACIWrapper, PollManager, Verifier, VkRegistry } from "../typechain-types"; function fetchOrCreateKeyPair(filePath: string) { let keypair: Keypair | null = null; @@ -23,11 +23,11 @@ function fetchOrCreateKeyPair(filePath: string) { const deployContracts: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const { deployer } = await hre.getNamedAccounts(); - const maci = await hre.ethers.getContract("MACI", deployer); + const maci = await hre.ethers.getContract("MACIWrapper", deployer); await hre.deployments.deploy("PollManager", { from: deployer, - args: [await maci.getAddress(), useQuadraticVoting], + args: [await maci.getAddress()], log: true, autoMine: true, }); @@ -36,9 +36,6 @@ const deployContracts: DeployFunction = async function (hre: HardhatRuntimeEnvir console.log(`The poll manager is deployed at ${await pollManager.getAddress()}`); - // update the MACI contract with the address of the poll manager - await maci.updateManager(await pollManager.getAddress()); - // update the config on the poll manager const verifier = await hre.ethers.getContract("Verifier", deployer); const vkRegistry = await hre.ethers.getContract("VkRegistry", deployer); @@ -57,7 +54,6 @@ const deployContracts: DeployFunction = async function (hre: HardhatRuntimeEnvir coordinatorKeypair.pubKey.asContractParam(), await verifier.getAddress(), await vkRegistry.getAddress(), - false, ); }; diff --git a/packages/hardhat/deploy/11_generate_address_file.ts b/packages/hardhat/deploy/11_generate_address_file.ts index 1df133a..b5f106d 100644 --- a/packages/hardhat/deploy/11_generate_address_file.ts +++ b/packages/hardhat/deploy/11_generate_address_file.ts @@ -1,13 +1,13 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import { DeployFunction } from "hardhat-deploy/types"; -import { MACI } from "../typechain-types"; +import { MACIWrapper } from "../typechain-types"; import { GatekeeperContractName, InitialVoiceCreditProxyContractName } from "../constants"; import fs from "fs"; const deployContracts: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const { deployer } = await hre.getNamedAccounts(); - const maci = await hre.ethers.getContract("MACI", deployer); + const maci = await hre.ethers.getContract("MACIWrapper", deployer); const initialVoiceCreditProxy = await hre.ethers.getContract(InitialVoiceCreditProxyContractName, deployer); const gatekeeper = await hre.ethers.getContract(GatekeeperContractName, deployer); const verifier = await hre.ethers.getContract("Verifier", deployer); @@ -23,7 +23,6 @@ const deployContracts: DeployFunction = async function (hre: HardhatRuntimeEnvir JSON.stringify({ [hre.network.name]: { MACI: await maci.getAddress(), - StateAq: await maci.stateAq(), InitialVoiceCreditProxy: await initialVoiceCreditProxy.getAddress(), SignUpGatekeeper: await gatekeeper.getAddress(), Verifier: await verifier.getAddress(), diff --git a/packages/hardhat/deploy/99_generateTsAbis.ts b/packages/hardhat/deploy/99_generateTsAbis.ts index 76fb738..a158c6c 100644 --- a/packages/hardhat/deploy/99_generateTsAbis.ts +++ b/packages/hardhat/deploy/99_generateTsAbis.ts @@ -60,10 +60,17 @@ function getInheritedFunctions(sources: Record, contractName: strin const inheritedFunctions = {} as Record; for (const sourceContractName of actualSources) { - const sourcePath = Object.keys(sources).find(key => key.includes(`/${sourceContractName}`)); + let sourcePath = Object.keys(sources).find(key => key.includes(`/${sourceContractName}`)); if (sourcePath) { const sourceName = sourcePath?.split("/").pop()?.split(".sol")[0]; - const { abi } = JSON.parse(fs.readFileSync(`${ARTIFACTS_DIR}/${sourcePath}/${sourceName}.json`).toString()); + let abi; + try { + abi = JSON.parse(fs.readFileSync(`${ARTIFACTS_DIR}/${sourcePath}/${sourceName}.json`).toString()).abi; + } catch (e) { + sourcePath = sourcePath.replace(/contracts\//, ""); + sourcePath = sourcePath.replace(/maci-contracts\//, "maci-contracts/contracts/"); + abi = JSON.parse(fs.readFileSync(`${ARTIFACTS_DIR}/${sourcePath}/${sourceName}.json`).toString()).abi; + } for (const functionAbi of abi) { if (functionAbi.type === "function") { inheritedFunctions[functionAbi.name] = sourcePath; diff --git a/packages/hardhat/hardhat.config.ts b/packages/hardhat/hardhat.config.ts index 6808468..f9dfc89 100644 --- a/packages/hardhat/hardhat.config.ts +++ b/packages/hardhat/hardhat.config.ts @@ -1,6 +1,9 @@ import * as dotenv from "dotenv"; dotenv.config(); +import fs from "fs"; +import path from "path"; + import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-ethers"; import "@nomicfoundation/hardhat-chai-matchers"; @@ -11,6 +14,55 @@ import "@nomicfoundation/hardhat-verify"; import "hardhat-deploy"; import "hardhat-deploy-ethers"; import "hardhat-artifactor"; +import { task, subtask } from "hardhat/config"; + +/** + * Allow to copy a directory from source to target + * @param source - the source directory + * @param target - the target directory + */ +function copyDirectory(source: string, target: string): void { + if (!fs.existsSync(target)) { + fs.mkdirSync(target, { recursive: true }); + } + + if (!fs.existsSync(source)) { + return; + } + + const files = fs.readdirSync(source); + + files.forEach((file: string) => { + const sourcePath = path.join(source, file); + const targetPath = path.join(target, file); + + if (fs.lstatSync(sourcePath).isDirectory()) { + copyDirectory(sourcePath, targetPath); + } else { + fs.copyFileSync(sourcePath, targetPath); + } + }); +} + +// Define a subtask to copy artifacts +subtask("copy-maci-artifacts", async (_, { config }) => { + const sourceDir = path.resolve(__dirname, "node_modules/maci-contracts/build/artifacts/contracts/"); + const destDir = path.resolve(config.paths.artifacts, "maci-contracts", "contracts"); + + copyDirectory(sourceDir, destDir); +}); + +// Override the existing compile task +task("compile", async (args, hre, runSuper) => { + // Before compilation move over artifacts + await hre.run("copy-maci-artifacts"); + + // Run the original compile task + await runSuper(args); + + // After compilation, run the subtask to copy MACI artifacts + await hre.run("copy-maci-artifacts"); +}); // If not set, it uses ours Alchemy's default API key. // You can get your own at https://dashboard.alchemyapi.io diff --git a/packages/hardhat/maci-scripts/compileSol.sh b/packages/hardhat/maci-scripts/compileSol.sh deleted file mode 100755 index a56eeb8..0000000 --- a/packages/hardhat/maci-scripts/compileSol.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -set -o pipefail - -cd "$(dirname "$0")" -cd .. - -# Delete old files -rm -rf ./artifacts/* -rm -rf ./cache/* -rm -rf ./typechain-types/* - -echo 'Writing Merkle zeros contracts' -bash ./maci-scripts/writeMerkleZeroesContracts.sh - -echo 'Writing empty ballot tree root contract' -yarn exec ts-node maci-ts/ts/genEmptyBallotRootsContract.ts - -echo 'Building contracts with Hardhat' -TS_NODE_TRANSPILE_ONLY=1 yarn exec hardhat compile - -echo 'Building Poseidon libraries from bytecode' -yarn exec ts-node maci-ts/ts/buildPoseidon.ts diff --git a/packages/hardhat/maci-scripts/writeMerkleZeroesContracts.sh b/packages/hardhat/maci-scripts/writeMerkleZeroesContracts.sh deleted file mode 100755 index 0f10f73..0000000 --- a/packages/hardhat/maci-scripts/writeMerkleZeroesContracts.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -set -e - -cd "$(dirname "$0")" -cd .. - -# The nothing-up-my-sleeve value -maciNums="8370432830353022751713833565135785980866757267633941821328460903436894336785" - -# The hash of a blank state leaf -blankSl="6769006970205099520508948723718471724660867171122235270773600567925038008762" - -# Binary tree with zero = 0 -yarn exec ts-node maci-ts/ts/genZerosContract.ts \ - MerkleBinary0 0 2 33 "Binary tree zeros (0)" 0 0 \ - > contracts/maci-contracts/trees/zeros/MerkleBinary0.sol - -# Binary tree with zero = maciNums -yarn exec ts-node maci-ts/ts/genZerosContract.ts \ - MerkleBinaryMaci $maciNums 2 33 "Binary tree zeros (Keccak hash of 'Maci')" 0 0 \ - > contracts/maci-contracts/trees/zeros/MerkleBinaryMaci.sol - -# Quinary tree with zero = 0 -yarn exec ts-node maci-ts/ts/genZerosContract.ts \ - MerkleQuinary0 0 5 33 "Quinary tree zeros (0)" 0 0 \ - > contracts/maci-contracts/trees/zeros/MerkleQuinary0.sol - -# Quinary tree with zero = maciNums -yarn exec ts-node maci-ts/ts/genZerosContract.ts \ - MerkleQuinaryMaci $maciNums 5 33 "Quinary tree zeros (Keccak hash of 'Maci')" 0 0 \ - > contracts/maci-contracts/trees/zeros/MerkleQuinaryMaci.sol - -# Quinary tree with zero = blank state leaf -yarn exec ts-node maci-ts/ts/genZerosContract.ts \ - MerkleQuinaryBlankSl $blankSl 5 33 "Quinary tree zeros (hash of a blank state leaf)" 0 0 \ - > contracts/maci-contracts/trees/zeros/MerkleQuinaryBlankSl.sol - -## Quinary tree with SHA256 for subtrees and zero = maciNums -#yarn exec ts-node maci-ts/ts/genZerosContract.ts \ - #MerkleQuinaryMaciWithSha256 $maciNums 5 33 "Quinary tree (with SHA256) zeros (Keccak hash of 'Maci')" 1 2 \ - #> contracts/maci-contracts/trees/zeros/MerkleQuinaryMaciWithSha256.sol diff --git a/packages/hardhat/maci-ts/circuits/__tests__/CalculateTotal.test.ts b/packages/hardhat/maci-ts/circuits/__tests__/CalculateTotal.test.ts deleted file mode 100644 index 7ed1bea..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/CalculateTotal.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { type WitnessTester } from "circomkit"; - -import { circomkitInstance } from "./utils/utils"; - -describe("CalculateTotal circuit", () => { - let circuit: WitnessTester<["nums"], ["sum"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("calculateTotal", { - file: "trees/calculateTotal", - template: "CalculateTotal", - params: [6], - }); - }); - - it("should correctly sum a list of values", async () => { - const nums: number[] = []; - for (let i = 0; i < 6; i += 1) { - nums.push(Math.floor(Math.random() * 100)); - } - - const sum = nums.reduce((a, b) => a + b, 0); - - const circuitInputs = { - nums, - }; - - await circuit.expectPass(circuitInputs, { sum }); - }); -}); diff --git a/packages/hardhat/maci-ts/circuits/__tests__/CeremonyParams.test.ts b/packages/hardhat/maci-ts/circuits/__tests__/CeremonyParams.test.ts deleted file mode 100644 index 52239ef..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/CeremonyParams.test.ts +++ /dev/null @@ -1,354 +0,0 @@ -import { expect } from "chai"; -import { type WitnessTester } from "circomkit"; -import { MaciState, Poll, packProcessMessageSmallVals, STATE_TREE_ARITY } from "../../core"; -import { hash5, IncrementalQuinTree, NOTHING_UP_MY_SLEEVE, AccQueue } from "../../crypto"; -import { PrivKey, Keypair, PCommand, Message, Ballot } from "../../domainobjs"; - -import { IProcessMessagesInputs, ITallyVotesInputs } from "../types"; - -import { generateRandomIndex, getSignal, circomkitInstance } from "./utils/utils"; - -describe("Ceremony param tests", () => { - const params = { - // processMessages and Tally - stateTreeDepth: 6, - // processMessages - messageTreeDepth: 9, - // processMessages - messageBatchTreeDepth: 2, - // processMessages and Tally - voteOptionTreeDepth: 3, - // Tally - stateLeafBatchDepth: 2, - }; - - const maxValues = { - maxUsers: STATE_TREE_ARITY ** params.stateTreeDepth, - maxMessages: STATE_TREE_ARITY ** params.messageTreeDepth, - maxVoteOptions: STATE_TREE_ARITY ** params.voteOptionTreeDepth, - }; - - const treeDepths = { - intStateTreeDepth: params.messageBatchTreeDepth, - messageTreeDepth: params.messageTreeDepth, - messageTreeSubDepth: params.messageBatchTreeDepth, - voteOptionTreeDepth: params.voteOptionTreeDepth, - }; - - const messageBatchSize = STATE_TREE_ARITY ** params.messageBatchTreeDepth; - - const voiceCreditBalance = BigInt(100); - const duration = 30; - - const coordinatorKeypair = new Keypair(); - - describe("ProcessMessage circuit", function test() { - this.timeout(900000); - - let circuit: WitnessTester< - [ - "inputHash", - "packedVals", - "pollEndTimestamp", - "msgRoot", - "msgs", - "msgSubrootPathElements", - "coordPrivKey", - "coordPubKey", - "encPubKeys", - "currentStateRoot", - "currentStateLeaves", - "currentStateLeavesPathElements", - "currentSbCommitment", - "currentSbSalt", - "newSbCommitment", - "newSbSalt", - "currentBallotRoot", - "currentBallots", - "currentBallotsPathElements", - "currentVoteWeights", - "currentVoteWeightsPathElements", - ] - >; - - let hasherCircuit: WitnessTester< - ["packedVals", "coordPubKey", "msgRoot", "currentSbCommitment", "newSbCommitment", "pollEndTimestamp"], - ["maxVoteOptions", "numSignUps", "batchStartIndex", "batchEndIndex", "hash"] - >; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("processMessages", { - file: "processMessages", - template: "ProcessMessages", - params: [6, 9, 2, 3], - }); - - hasherCircuit = await circomkitInstance.WitnessTester("processMessageInputHasher", { - file: "processMessages", - template: "ProcessMessagesInputHasher", - }); - }); - - describe("1 user, 2 messages", () => { - const maciState = new MaciState(params.stateTreeDepth); - const voteWeight = BigInt(9); - const voteOptionIndex = BigInt(0); - let stateIndex: bigint; - let pollId: bigint; - let poll: Poll; - const messages: Message[] = []; - const commands: PCommand[] = []; - - before(() => { - // Sign up and publish - const userKeypair = new Keypair(new PrivKey(BigInt(1))); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); - - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - - // update the state - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - // First command (valid) - const command = new PCommand( - stateIndex, // BigInt(1), - userKeypair.pubKey, - voteOptionIndex, // voteOptionIndex, - voteWeight, // vote weight - BigInt(2), // nonce - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - messages.push(message); - commands.push(command); - - poll.publishMessage(message, ecdhKeypair.pubKey); - - // Second command (valid) - const command2 = new PCommand( - stateIndex, - userKeypair.pubKey, - voteOptionIndex, // voteOptionIndex, - BigInt(1), // vote weight - BigInt(1), // nonce - BigInt(pollId), - ); - const signature2 = command2.sign(userKeypair.privKey); - - const ecdhKeypair2 = new Keypair(); - const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); - const message2 = command2.encrypt(signature2, sharedKey2); - messages.push(message2); - commands.push(command2); - poll.publishMessage(message2, ecdhKeypair2.pubKey); - - // Use the accumulator queue to compare the root of the message tree - const accumulatorQueue: AccQueue = new AccQueue( - params.messageTreeDepth, - STATE_TREE_ARITY, - NOTHING_UP_MY_SLEEVE, - ); - accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey)); - accumulatorQueue.enqueue(message2.hash(ecdhKeypair2.pubKey)); - accumulatorQueue.mergeSubRoots(0); - accumulatorQueue.merge(params.messageTreeDepth); - - expect(poll.messageTree.root.toString()).to.be.eq( - accumulatorQueue.getMainRoots()[params.messageTreeDepth].toString(), - ); - }); - - it("should produce the correct state root and ballot root", async () => { - // The current roots - const emptyBallot = new Ballot(poll.maxValues.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); - const emptyBallotHash = emptyBallot.hash(); - const ballotTree = new IncrementalQuinTree(params.stateTreeDepth, emptyBallot.hash(), STATE_TREE_ARITY, hash5); - ballotTree.insert(emptyBallot.hash()); - - poll.stateLeaves.forEach(() => { - ballotTree.insert(emptyBallotHash); - }); - - const currentStateRoot = poll.stateTree?.root; - const currentBallotRoot = ballotTree.root; - - const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; - - // Calculate the witness - const witness = await circuit.calculateWitness(inputs); - await circuit.expectConstraintPass(witness); - - // The new roots, which should differ, since at least one of the - // messages modified a Ballot or State Leaf - const newStateRoot = poll.stateTree?.root; - const newBallotRoot = poll.ballotTree?.root; - - expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); - expect(newBallotRoot?.toString()).not.to.be.eq(currentBallotRoot.toString()); - - const packedVals = packProcessMessageSmallVals( - BigInt(maxValues.maxVoteOptions), - BigInt(poll.maciStateRef.numSignUps), - 0, - 2, - ); - - // Test the ProcessMessagesInputHasher circuit - const hasherCircuitInputs = { - packedVals, - coordPubKey: inputs.coordPubKey, - msgRoot: inputs.msgRoot, - currentSbCommitment: inputs.currentSbCommitment, - newSbCommitment: inputs.newSbCommitment, - pollEndTimestamp: inputs.pollEndTimestamp, - }; - - const hasherWitness = await hasherCircuit.calculateWitness(hasherCircuitInputs); - await hasherCircuit.expectConstraintPass(hasherWitness); - const hash = await getSignal(hasherCircuit, hasherWitness, "hash"); - expect(hash.toString()).to.be.eq(inputs.inputHash.toString()); - }); - }); - - describe("TallyVotes circuit", function test() { - this.timeout(900000); - - let testCircuit: WitnessTester< - [ - "stateRoot", - "ballotRoot", - "sbSalt", - "packedVals", - "sbCommitment", - "currentTallyCommitment", - "newTallyCommitment", - "inputHash", - "ballots", - "ballotPathElements", - "votes", - "currentResults", - "currentResultsRootSalt", - "currentSpentVoiceCreditSubtotal", - "currentSpentVoiceCreditSubtotalSalt", - "currentPerVOSpentVoiceCredits", - "currentPerVOSpentVoiceCreditsRootSalt", - "newResultsRootSalt", - "newPerVOSpentVoiceCreditsRootSalt", - "newSpentVoiceCreditSubtotalSalt", - ] - >; - - before(async () => { - testCircuit = await circomkitInstance.WitnessTester("tallyVotes", { - file: "tallyVotes", - template: "TallyVotes", - params: [6, 2, 3], - }); - }); - - describe("1 user, 2 messages", () => { - let stateIndex: bigint; - let pollId: bigint; - let poll: Poll; - let maciState: MaciState; - const voteWeight = BigInt(9); - const voteOptionIndex = BigInt(0); - - beforeEach(() => { - maciState = new MaciState(params.stateTreeDepth); - const messages: Message[] = []; - const commands: PCommand[] = []; - // Sign up and publish - const userKeypair = new Keypair(); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); - - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - - // update the state - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - // First command (valid) - const command = new PCommand( - stateIndex, - userKeypair.pubKey, - voteOptionIndex, // voteOptionIndex, - voteWeight, // vote weight - BigInt(1), // nonce - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - messages.push(message); - commands.push(command); - - poll.publishMessage(message, ecdhKeypair.pubKey); - // Use the accumulator queue to compare the root of the message tree - const accumulatorQueue: AccQueue = new AccQueue( - params.messageTreeDepth, - STATE_TREE_ARITY, - NOTHING_UP_MY_SLEEVE, - ); - accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey)); - accumulatorQueue.mergeSubRoots(0); - accumulatorQueue.merge(params.messageTreeDepth); - - expect(poll.messageTree.root.toString()).to.be.eq( - accumulatorQueue.getMainRoots()[params.messageTreeDepth].toString(), - ); - // Process messages - poll.processMessages(pollId); - }); - - it("should produce the correct result commitments", async () => { - const generatedInputs = poll.tallyVotes() as unknown as ITallyVotesInputs; - const witness = await testCircuit.calculateWitness(generatedInputs); - await testCircuit.expectConstraintPass(witness); - }); - - it("should produce the correct result if the initial tally is not zero", async () => { - const generatedInputs = poll.tallyVotes() as unknown as ITallyVotesInputs; - - // Start the tally from non-zero value - let randIdx = generateRandomIndex(Object.keys(generatedInputs).length); - while (randIdx === 0) { - randIdx = generateRandomIndex(Object.keys(generatedInputs).length); - } - - generatedInputs.currentResults[randIdx] = 1n; - - const witness = await testCircuit.calculateWitness(generatedInputs); - await testCircuit.expectConstraintPass(witness); - }); - }); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/circuits/__tests__/Ecdh.test.ts b/packages/hardhat/maci-ts/circuits/__tests__/Ecdh.test.ts deleted file mode 100644 index b449552..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/Ecdh.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import chai, { expect } from "chai"; -import chaiAsPromised from "chai-as-promised"; -import { type WitnessTester } from "circomkit"; -import { Keypair } from "../../domainobjs"; - -import { circomkitInstance } from "./utils/utils"; - -chai.use(chaiAsPromised); - -describe("Public key derivation circuit", () => { - let circuit: WitnessTester<["privKey", "pubKey"], ["sharedKey"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("ecdh", { - file: "ecdh", - template: "Ecdh", - }); - }); - - it("correctly computes a public key", async () => { - const keypair = new Keypair(); - const keypair2 = new Keypair(); - - const ecdhSharedKey = Keypair.genEcdhSharedKey(keypair.privKey, keypair2.pubKey); - - const circuitInputs = { - privKey: BigInt(keypair.privKey.asCircuitInputs()), - pubKey: keypair2.pubKey.rawPubKey as [bigint, bigint], - }; - - await circuit.expectPass(circuitInputs, { sharedKey: [ecdhSharedKey[0], ecdhSharedKey[1]] }); - }); - - it("should generate the same ECDH key given the same inputs", async () => { - const keypair = new Keypair(); - const keypair2 = new Keypair(); - - const circuitInputs = { - privKey: BigInt(keypair.privKey.asCircuitInputs()), - pubKey: keypair2.pubKey.asCircuitInputs() as unknown as bigint[], - }; - - // calculate first time witness and check constraints - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - - // read out - const out = await circuit.readWitnessSignals(witness, ["sharedKey"]); - - // calculate again - await circuit.expectPass(circuitInputs, { sharedKey: out.sharedKey }); - }); - - it("should throw when given invalid inputs (pubKey too short)", async () => { - const keypair = new Keypair(); - const keypair2 = new Keypair(); - - const circuitInputs = { - privKey: BigInt(keypair.privKey.asCircuitInputs()), - pubKey: keypair2.pubKey.asCircuitInputs().slice(0, 1) as unknown as [bigint, bigint], - }; - - await expect(circuit.calculateWitness(circuitInputs)).to.be.rejectedWith( - "Not enough values for input signal pubKey", - ); - }); -}); diff --git a/packages/hardhat/maci-ts/circuits/__tests__/Hasher.test.ts b/packages/hardhat/maci-ts/circuits/__tests__/Hasher.test.ts deleted file mode 100644 index d3b6784..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/Hasher.test.ts +++ /dev/null @@ -1,304 +0,0 @@ -import { expect } from "chai"; -import { type WitnessTester } from "circomkit"; -import { genRandomSalt, sha256Hash, hashLeftRight, hash13, hash5, hash4, hash3 } from "../../crypto"; -import { PCommand, Keypair } from "../../domainobjs"; - -import { getSignal, circomkitInstance } from "./utils/utils"; - -describe("Poseidon hash circuits", function test() { - this.timeout(30000); - - describe("SHA256", () => { - describe("Sha256HashLeftRight", () => { - let circuit: WitnessTester<["left", "right"], ["hash"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("sha256HashLeftRight", { - file: "hasherSha256", - template: "Sha256HashLeftRight", - }); - }); - - it("should correctly hash two random values", async () => { - const left = genRandomSalt(); - const right = genRandomSalt(); - - const circuitInputs = { left, right }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - const outputJS = sha256Hash([left, right]); - - expect(output.toString()).to.be.eq(outputJS.toString()); - }); - }); - - describe("Sha256Hasher4", () => { - let circuit: WitnessTester<["in"], ["hash"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("sha256Hasher4", { - file: "hasherSha256", - template: "Sha256Hasher4", - }); - }); - - it("should correctly hash 4 random values", async () => { - const preImages: bigint[] = []; - for (let i = 0; i < 4; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - in: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - const outputJS = sha256Hash(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); - }); - }); - - describe("Sha256Hasher6", () => { - let circuit: WitnessTester<["in"], ["hash"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("sha256Hasher6", { - file: "hasherSha256", - template: "Sha256Hasher6", - }); - }); - - it("should correctly hash 6 random values", async () => { - const preImages: bigint[] = []; - for (let i = 0; i < 6; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - in: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - const outputJS = sha256Hash(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); - }); - }); - }); - - describe("Poseidon", () => { - describe("Hasher5", () => { - let circuit: WitnessTester<["in"], ["hash"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("hasher5", { - file: "hasherPoseidon", - template: "Hasher5", - }); - }); - - it("correctly hashes 5 random values", async () => { - const preImages: bigint[] = []; - for (let i = 0; i < 5; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - in: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - const outputJS = hash5(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); - }); - }); - - describe("Hasher4", () => { - let circuit: WitnessTester<["in"], ["hash"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("hasher4", { - file: "hasherPoseidon", - template: "Hasher4", - }); - }); - - it("correctly hashes 4 random values", async () => { - const preImages: bigint[] = []; - for (let i = 0; i < 4; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - in: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - const outputJS = hash4(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); - }); - }); - - describe("Hasher3", () => { - let circuit: WitnessTester<["in"], ["hash"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("hasher3", { - file: "hasherPoseidon", - template: "Hasher3", - }); - }); - - it("correctly hashes 3 random values", async () => { - const preImages: bigint[] = []; - for (let i = 0; i < 3; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - in: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - const outputJS = hash3(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); - }); - }); - - describe("Hasher13", () => { - let circuit: WitnessTester<["in"], ["hash"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("hasher13", { - file: "hasherPoseidon", - template: "Hasher13", - }); - }); - - it("should correctly hash 13 random values", async () => { - const preImages: bigint[] = []; - for (let i = 0; i < 13; i += 1) { - preImages.push(genRandomSalt()); - } - const circuitInputs = { - in: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - const outputJS = hash13(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); - }); - }); - - describe("HashLeftRight", () => { - let circuit: WitnessTester<["left", "right"], ["hash"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("hashLeftRight", { - file: "hasherPoseidon", - template: "HashLeftRight", - }); - }); - - it("should correctly hash two random values", async () => { - const left = genRandomSalt(); - const right = genRandomSalt(); - - const circuitInputs = { left, right }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - const outputJS = hashLeftRight(left, right); - - expect(output.toString()).to.be.eq(outputJS.toString()); - }); - - it("should produce consistent results", async () => { - const left = genRandomSalt(); - const right = genRandomSalt(); - - const circuitInputs = { left, right }; - - let witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output2 = await getSignal(circuit, witness, "hash"); - - expect(output.toString()).to.be.eq(output2.toString()); - }); - }); - }); - - describe("MessageHasher", () => { - let circuit: WitnessTester<["in", "encPubKey"], ["hash"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("messageHasher", { - file: "messageHasher", - template: "MessageHasher", - }); - }); - - it("should correctly hash a message", async () => { - const k = new Keypair(); - const random50bitBigInt = (): bigint => - // eslint-disable-next-line no-bitwise - ((BigInt(1) << BigInt(50)) - BigInt(1)) & BigInt(genRandomSalt().toString()); - - const command: PCommand = new PCommand( - random50bitBigInt(), - k.pubKey, - random50bitBigInt(), - random50bitBigInt(), - random50bitBigInt(), - random50bitBigInt(), - genRandomSalt(), - ); - - const { privKey } = new Keypair(); - const ecdhSharedKey = Keypair.genEcdhSharedKey(privKey, k.pubKey); - const signature = command.sign(privKey); - const message = command.encrypt(signature, ecdhSharedKey); - const messageHash = message.hash(k.pubKey); - const circuitInputs = { - in: message.asCircuitInputs(), - encPubKey: k.pubKey.asCircuitInputs() as unknown as [bigint, bigint], - }; - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - expect(output.toString()).to.be.eq(messageHash.toString()); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/circuits/__tests__/IncrementalQuinTree.test.ts b/packages/hardhat/maci-ts/circuits/__tests__/IncrementalQuinTree.test.ts deleted file mode 100644 index 05457d4..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/IncrementalQuinTree.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -import chai, { expect } from "chai"; -import chaiAsPromised from "chai-as-promised"; -import { type WitnessTester } from "circomkit"; -import { IncrementalQuinTree, hash5 } from "../../crypto"; - -import { getSignal, circomkitInstance } from "./utils/utils"; - -chai.use(chaiAsPromised); - -describe("IncrementalQuinTree circuit", function test() { - this.timeout(50000); - - const leavesPerNode = 5; - const treeDepth = 3; - - let circuitLeafExists: WitnessTester<["leaf", "path_elements", "path_index", "root"]>; - let circuitGeneratePathIndices: WitnessTester<["in"], ["out"]>; - let circuitQuinSelector: WitnessTester<["in", "index"], ["out"]>; - let splicerCircuit: WitnessTester<["in", "leaf", "index"], ["out"]>; - - before(async () => { - circuitLeafExists = await circomkitInstance.WitnessTester("quinLeafExists", { - file: "./trees/incrementalQuinTree", - template: "QuinLeafExists", - params: [3], - }); - - circuitGeneratePathIndices = await circomkitInstance.WitnessTester("quinGeneratePathIndices", { - file: "./trees/incrementalQuinTree", - template: "QuinGeneratePathIndices", - params: [4], - }); - - circuitQuinSelector = await circomkitInstance.WitnessTester("quinSelector", { - file: "./trees/incrementalQuinTree", - template: "QuinSelector", - params: [5], - }); - - splicerCircuit = await circomkitInstance.WitnessTester("splicer", { - file: "./trees/incrementalQuinTree", - template: "Splicer", - params: [4], - }); - }); - - describe("QuinSelector", () => { - it("should return the correct value", async () => { - const circuitInputs = { - index: 0n, - in: [1n, 2n, 3n, 4n, 5n], - }; - - const witness = await circuitQuinSelector.calculateWitness(circuitInputs); - await circuitQuinSelector.expectConstraintPass(witness); - - const out = await getSignal(circuitQuinSelector, witness, "out"); - expect(out.toString()).to.be.eq("1"); - }); - - it("should throw when the index is out of range", async () => { - const circuitInputs = { - index: 5n, - in: [1n, 2n, 3n, 4n, 5n], - }; - - await expect(circuitQuinSelector.calculateWitness(circuitInputs)).to.be.rejectedWith("Assert Failed."); - }); - }); - - describe("Splicer", () => { - it("should insert a value at the correct index", async () => { - const circuitInputs = { - in: [5n, 3n, 20n, 44n], - leaf: 0n, - index: 2n, - }; - - const witness = await splicerCircuit.calculateWitness(circuitInputs); - await splicerCircuit.expectConstraintPass(witness); - - const out1 = await getSignal(splicerCircuit, witness, "out[0]"); - const out2 = await getSignal(splicerCircuit, witness, "out[1]"); - const out3 = await getSignal(splicerCircuit, witness, "out[2]"); - const out4 = await getSignal(splicerCircuit, witness, "out[3]"); - const out5 = await getSignal(splicerCircuit, witness, "out[4]"); - expect(out1.toString()).to.eq("5"); - expect(out2.toString()).to.eq("3"); - expect(out3.toString()).to.eq("0"); - expect(out4.toString()).to.eq("20"); - expect(out5.toString()).to.eq("44"); - }); - }); - - describe("QuinGeneratePathIndices", () => { - it("should generate the correct path indices", async () => { - const circuitInputs = { - in: 30n, - }; - - const witness = await circuitGeneratePathIndices.calculateWitness(circuitInputs); - await circuitGeneratePathIndices.expectConstraintPass(witness); - - const out1 = await getSignal(circuitGeneratePathIndices, witness, "out[0]"); - const out2 = await getSignal(circuitGeneratePathIndices, witness, "out[1]"); - const out3 = await getSignal(circuitGeneratePathIndices, witness, "out[2]"); - const out4 = await getSignal(circuitGeneratePathIndices, witness, "out[3]"); - - expect(out1.toString()).to.be.eq("0"); - expect(out2.toString()).to.be.eq("1"); - expect(out3.toString()).to.be.eq("1"); - expect(out4.toString()).to.be.eq("0"); - }); - }); - - describe("QuinLeafExists", () => { - it("should exit correctly when provided the correct leaf", async () => { - const leaves = [1n, 2n, 3n, 4n, 5n]; - const tree = new IncrementalQuinTree(treeDepth, 0n, leavesPerNode, hash5); - leaves.forEach(leaf => { - tree.insert(leaf); - }); - - const proof = tree.genProof(2); - - const circuitInputs = { - root: tree.root, - leaf: 3n, - path_elements: proof.pathElements, - path_index: proof.pathIndices, - }; - - const witness = await circuitLeafExists.calculateWitness(circuitInputs); - await circuitLeafExists.expectConstraintPass(witness); - }); - - it("should throw when provided an incorrect leaf", async () => { - const circuitInputs = { - root: 30n, - leaf: 0n, - path_elements: [ - [1n, 1n, 0n, 0n], - [1n, 1n, 0n, 1n], - [1n, 1n, 1n, 0n], - ], - path_index: [0n, 1n, 1n], - }; - - await expect(circuitLeafExists.calculateWitness(circuitInputs)).to.be.rejectedWith("Assert Failed."); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/circuits/__tests__/MessageToCommand.test.ts b/packages/hardhat/maci-ts/circuits/__tests__/MessageToCommand.test.ts deleted file mode 100644 index 952cee1..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/MessageToCommand.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { expect } from "chai"; -import { type WitnessTester } from "circomkit"; -import { genRandomSalt, genPrivKey } from "../../crypto"; -import { Keypair, PCommand, PrivKey } from "../../domainobjs"; - -import { circomkitInstance, getSignal } from "./utils/utils"; - -describe("MessageToCommand circuit", () => { - let circuit: WitnessTester< - ["message", "encPubKey", "encPubKey"], - [ - "stateIndex", - "newPubKey", - "voteOptionIndex", - "newVoteWeight", - "nonce", - "pollId", - "salt", - "sigR8", - "sigS", - "packedCommandOut", - ] - >; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("messageToCommand", { - file: "messageToCommand", - template: "MessageToCommand", - }); - }); - - it("should decrypt a Message and output the fields of a Command", async () => { - const { privKey } = new Keypair(); - const k = new Keypair(); - - const pubKey1 = k.pubKey; - - const newPubKey = k.pubKey; - - const ecdhSharedKey = Keypair.genEcdhSharedKey(privKey, pubKey1); - const random50bitBigInt = (): bigint => - // eslint-disable-next-line no-bitwise - ((BigInt(1) << BigInt(50)) - BigInt(1)) & BigInt(genRandomSalt().toString()); - - const command: PCommand = new PCommand( - random50bitBigInt(), - newPubKey, - random50bitBigInt(), - random50bitBigInt(), - random50bitBigInt(), - random50bitBigInt(), - // genRandomSalt(), - BigInt(123), - ); - const signature = command.sign(privKey); - const message = command.encrypt(signature, ecdhSharedKey); - - const circuitInputs = { - message: message.asCircuitInputs(), - encPrivKey: privKey.asCircuitInputs() as unknown as bigint, - encPubKey: pubKey1.asCircuitInputs() as unknown as bigint[], - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - - const stateIndexOut = await getSignal(circuit, witness, "stateIndex"); - expect(command.stateIndex.toString()).to.be.eq(stateIndexOut.toString()); - - const newPubKey0 = await getSignal(circuit, witness, "newPubKey[0]"); - expect(command.newPubKey.rawPubKey[0].toString()).to.be.eq(newPubKey0.toString()); - - const newPubKey1 = await getSignal(circuit, witness, "newPubKey[1]"); - expect(command.newPubKey.rawPubKey[1].toString()).to.be.eq(newPubKey1.toString()); - - const voteOptionIndex = await getSignal(circuit, witness, "voteOptionIndex"); - expect(command.voteOptionIndex.toString()).to.be.eq(voteOptionIndex.toString()); - - const newVoteWeight = await getSignal(circuit, witness, "newVoteWeight"); - expect(command.newVoteWeight.toString()).to.be.eq(newVoteWeight.toString()); - - const nonce = await getSignal(circuit, witness, "nonce"); - expect(command.nonce.toString()).to.be.eq(nonce.toString()); - - const pollId = await getSignal(circuit, witness, "pollId"); - expect(command.pollId.toString()).to.be.eq(pollId.toString()); - - const salt = await getSignal(circuit, witness, "salt"); - expect(command.salt.toString()).to.be.eq(salt.toString()); - - const sigR80 = await getSignal(circuit, witness, "sigR8[0]"); - expect(signature.R8[0].toString()).to.be.eq(sigR80.toString()); - - const sigR81 = await getSignal(circuit, witness, "sigR8[1]"); - expect(signature.R8[1].toString()).to.be.eq(sigR81.toString()); - - const sigS = await getSignal(circuit, witness, "sigS"); - expect(signature.S.toString()).to.be.eq(sigS.toString()); - }); - - it("should not throw when given an invalid key which cannot decrypt a Message", async () => { - const { privKey } = new Keypair(); - const k = new Keypair(); - - const pubKey1 = k.pubKey; - - const newPubKey = k.pubKey; - - const ecdhSharedKey = Keypair.genEcdhSharedKey(privKey, pubKey1); - const random50bitBigInt = (): bigint => - // eslint-disable-next-line no-bitwise - ((BigInt(1) << BigInt(50)) - BigInt(1)) & BigInt(genRandomSalt().toString()); - - const command: PCommand = new PCommand( - random50bitBigInt(), - newPubKey, - random50bitBigInt(), - random50bitBigInt(), - random50bitBigInt(), - random50bitBigInt(), - // genRandomSalt(), - BigInt(123), - ); - const signature = command.sign(privKey); - const message = command.encrypt(signature, ecdhSharedKey); - - const circuitInputs = { - message: message.asCircuitInputs(), - // invalid private key - encPrivKey: new PrivKey(genPrivKey()).asCircuitInputs() as unknown as bigint, - encPubKey: pubKey1.asCircuitInputs() as unknown as [bigint, bigint], - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - }); -}); diff --git a/packages/hardhat/maci-ts/circuits/__tests__/MessageValidator.test.ts b/packages/hardhat/maci-ts/circuits/__tests__/MessageValidator.test.ts deleted file mode 100644 index 3819345..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/MessageValidator.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { expect } from "chai"; -import { type WitnessTester } from "circomkit"; -import { SignalValueType } from "circomkit/dist/types/circuit"; -import { genRandomSalt } from "../../crypto"; -import { PCommand, Keypair } from "../../domainobjs"; - -import { type IMessageValidatorCircuitInputs } from "./utils/types"; -import { getSignal, circomkitInstance } from "./utils/utils"; - -describe("MessageValidator circuit", function test() { - this.timeout(90000); - - let circuitInputs: IMessageValidatorCircuitInputs; - - let circuit: WitnessTester< - [ - "stateTreeIndex", - "numSignUps", - "voteOptionIndex", - "maxVoteOptions", - "originalNonce", - "nonce", - "cmd", - "pubKey", - "sigR8", - "sigS", - "currentVoiceCreditBalance", - "currentVotesForOption", - "voteWeight", - "slTimestamp", - "pollEndTimestamp", - ], - ["isValid"] - >; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("messageValidator", { - file: "messageValidator", - template: "MessageValidator", - }); - }); - - before(() => { - const { privKey, pubKey } = new Keypair(); - - // Note that the command fields don't matter in this test - const command: PCommand = new PCommand( - BigInt(1), - pubKey, - BigInt(2), - BigInt(3), - BigInt(4), - BigInt(5), - genRandomSalt(), - ); - - const signature = command.sign(privKey); - - circuitInputs = { - stateTreeIndex: 0n as SignalValueType, - numSignUps: 1n, - voteOptionIndex: 0n, - maxVoteOptions: 1n, - originalNonce: 1n, - nonce: 2n, - cmd: command.asCircuitInputs(), - pubKey: pubKey.asCircuitInputs() as unknown as [bigint, bigint], - sigR8: signature.R8 as unknown as bigint, - sigS: signature.S as bigint, - currentVoiceCreditBalance: 100n, - currentVotesForOption: 0n, - voteWeight: 9n, - slTimestamp: 1n, - pollEndTimestamp: 2n, - }; - }); - - it("should pass if all inputs are valid", async () => { - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const isValid = await getSignal(circuit, witness, "isValid"); - expect(isValid.toString()).to.be.eq("1"); - }); - - it("should be invalid if the signature is invalid", async () => { - const circuitInputs2 = circuitInputs; - circuitInputs2.sigS = 0n; - const witness = await circuit.calculateWitness(circuitInputs2); - await circuit.expectConstraintPass(witness); - const isValid = await getSignal(circuit, witness, "isValid"); - expect(isValid.toString()).to.be.eq("0"); - }); - - it("should be invalid if the pubkey is invalid", async () => { - const circuitInputs2 = circuitInputs; - circuitInputs2.pubKey = [0n, 1n]; - const witness = await circuit.calculateWitness(circuitInputs2); - await circuit.expectConstraintPass(witness); - const isValid = await getSignal(circuit, witness, "isValid"); - expect(isValid.toString()).to.be.eq("0"); - }); - - it("should be invalid if there are insufficient voice credits", async () => { - const circuitInputs2 = circuitInputs; - circuitInputs2.voteWeight = 11n; - const witness = await circuit.calculateWitness(circuitInputs2); - await circuit.expectConstraintPass(witness); - const isValid = await getSignal(circuit, witness, "isValid"); - expect(isValid.toString()).to.be.eq("0"); - }); - - it("should be invalid if the nonce is invalid", async () => { - const circuitInputs2 = circuitInputs; - circuitInputs2.nonce = 3n; - const witness = await circuit.calculateWitness(circuitInputs2); - await circuit.expectConstraintPass(witness); - const isValid = await getSignal(circuit, witness, "isValid"); - expect(isValid.toString()).to.be.eq("0"); - }); - - it("should be invalid if the state leaf index is invalid", async () => { - const circuitInputs2 = circuitInputs; - circuitInputs2.stateTreeIndex = 2n; - const witness = await circuit.calculateWitness(circuitInputs2); - await circuit.expectConstraintPass(witness); - const isValid = await getSignal(circuit, witness, "isValid"); - expect(isValid.toString()).to.be.eq("0"); - }); - - it("should be invalid if the vote option index is invalid", async () => { - const circuitInputs2 = circuitInputs; - circuitInputs2.voteOptionIndex = 1n; - const witness = await circuit.calculateWitness(circuitInputs2); - await circuit.expectConstraintPass(witness); - const isValid = await getSignal(circuit, witness, "isValid"); - expect(isValid.toString()).to.be.eq("0"); - }); - - it("should be invalid if the vote option index is invalid", async () => { - const circuitInputs2 = circuitInputs; - circuitInputs2.voteOptionIndex = 6049261729n; - const witness = await circuit.calculateWitness(circuitInputs2); - await circuit.expectConstraintPass(witness); - const isValid = await getSignal(circuit, witness, "isValid"); - expect(isValid.toString()).to.be.eq("0"); - }); - - it("should be invalid if the state leaf timestamp is too high", async () => { - const circuitInputs2 = circuitInputs; - circuitInputs2.slTimestamp = 3n; - const witness = await circuit.calculateWitness(circuitInputs2); - await circuit.expectConstraintPass(witness); - const isValid = await getSignal(circuit, witness, "isValid"); - expect(isValid.toString()).to.be.eq("0"); - }); -}); diff --git a/packages/hardhat/maci-ts/circuits/__tests__/PrivToPubKey.test.ts b/packages/hardhat/maci-ts/circuits/__tests__/PrivToPubKey.test.ts deleted file mode 100644 index 14250f3..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/PrivToPubKey.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { expect } from "chai"; -import { type WitnessTester } from "circomkit"; -import { SNARK_FIELD_SIZE } from "../../crypto"; -import { Keypair } from "../../domainobjs"; - -import { circomkitInstance, getSignal } from "./utils/utils"; - -describe("Public key derivation circuit", function test() { - this.timeout(90000); - - let circuit: WitnessTester<["privKey"], ["pubKey"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("privToPubKey", { - file: "privToPubKey", - template: "PrivToPubKey", - }); - }); - - it("should correctly compute a public key", async () => { - const keypair = new Keypair(); - - const circuitInputs = { - privKey: keypair.privKey.asCircuitInputs() as unknown as bigint, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - - const derivedPubkey0 = await getSignal(circuit, witness, "pubKey[0]"); - const derivedPubkey1 = await getSignal(circuit, witness, "pubKey[1]"); - expect(derivedPubkey0.toString()).to.be.eq(keypair.pubKey.rawPubKey[0].toString()); - expect(derivedPubkey1.toString()).to.be.eq(keypair.pubKey.rawPubKey[1].toString()); - }); - - it("should produce an output that is within the baby jubjub curve", async () => { - const keypair = new Keypair(); - - const circuitInputs = { - privKey: keypair.privKey.asCircuitInputs() as unknown as bigint, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - - const derivedPubkey0 = await getSignal(circuit, witness, "pubKey[0]"); - const derivedPubkey1 = await getSignal(circuit, witness, "pubKey[1]"); - expect(derivedPubkey0 < SNARK_FIELD_SIZE).to.eq(true); - expect(derivedPubkey1 < SNARK_FIELD_SIZE).to.eq(true); - }); -}); diff --git a/packages/hardhat/maci-ts/circuits/__tests__/ProcessMessages.test.ts b/packages/hardhat/maci-ts/circuits/__tests__/ProcessMessages.test.ts deleted file mode 100644 index 9be4833..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/ProcessMessages.test.ts +++ /dev/null @@ -1,1063 +0,0 @@ -import { expect } from "chai"; -import { type WitnessTester } from "circomkit"; -import { MaciState, Poll, packProcessMessageSmallVals, STATE_TREE_ARITY } from "../../core"; -import { hash5, IncrementalQuinTree, NOTHING_UP_MY_SLEEVE, AccQueue } from "../../crypto"; -import { PrivKey, Keypair, PCommand, Message, Ballot, PubKey } from "../../domainobjs"; - -import { IProcessMessagesInputs } from "../types"; - -import { - STATE_TREE_DEPTH, - duration, - maxValues, - messageBatchSize, - treeDepths, - voiceCreditBalance, -} from "./utils/constants"; -import { getSignal, circomkitInstance } from "./utils/utils"; - -describe("ProcessMessage circuit", function test() { - this.timeout(900000); - - const coordinatorKeypair = new Keypair(); - - type ProcessMessageCircuitInputs = [ - "inputHash", - "packedVals", - "pollEndTimestamp", - "msgRoot", - "msgs", - "msgSubrootPathElements", - "coordPrivKey", - "coordPubKey", - "encPubKeys", - "currentStateRoot", - "currentStateLeaves", - "currentStateLeavesPathElements", - "currentSbCommitment", - "currentSbSalt", - "newSbCommitment", - "newSbSalt", - "currentBallotRoot", - "currentBallots", - "currentBallotsPathElements", - "currentVoteWeights", - "currentVoteWeightsPathElements", - ]; - - let circuit: WitnessTester; - - let circuitNonQv: WitnessTester; - - let hasherCircuit: WitnessTester< - ["packedVals", "coordPubKey", "msgRoot", "currentSbCommitment", "newSbCommitment", "pollEndTimestamp"], - ["maxVoteOptions", "numSignUps", "batchStartIndex", "batchEndIndex", "hash"] - >; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("processMessages", { - file: "processMessages", - template: "ProcessMessages", - params: [10, 2, 1, 2], - }); - - circuitNonQv = await circomkitInstance.WitnessTester("processMessagesNonQv", { - file: "processMessagesNonQv", - template: "ProcessMessagesNonQv", - params: [10, 2, 1, 2], - }); - - hasherCircuit = await circomkitInstance.WitnessTester("processMessageInputHasher", { - file: "processMessages", - template: "ProcessMessagesInputHasher", - }); - }); - - describe("1 user, 2 messages", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const voteWeight = BigInt(9); - const voteOptionIndex = BigInt(1); - let stateIndex: bigint; - let pollId: bigint; - let poll: Poll; - const messages: Message[] = []; - const commands: PCommand[] = []; - - before(() => { - // Sign up and publish - const userKeypair = new Keypair(new PrivKey(BigInt(1))); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); - - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - const nothing = new Message(1n, [ - 8370432830353022751713833565135785980866757267633941821328460903436894336785n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - ]); - - const encP = new PubKey([ - 10457101036533406547632367118273992217979173478358440826365724437999023779287n, - 19824078218392094440610104313265183977899662750282163392862422243483260492317n, - ]); - - poll.publishMessage(nothing, encP); - - // First command (valid) - const command = new PCommand( - stateIndex, // BigInt(1), - userKeypair.pubKey, - voteOptionIndex, // voteOptionIndex, - voteWeight, // vote weight - BigInt(2), // nonce - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - messages.push(message); - commands.push(command); - - poll.publishMessage(message, ecdhKeypair.pubKey); - - // Second command (valid) - const command2 = new PCommand( - stateIndex, - userKeypair.pubKey, - voteOptionIndex, // voteOptionIndex, - BigInt(1), // vote weight - BigInt(1), // nonce - BigInt(pollId), - ); - const signature2 = command2.sign(userKeypair.privKey); - - const ecdhKeypair2 = new Keypair(); - const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); - const message2 = command2.encrypt(signature2, sharedKey2); - messages.push(message2); - commands.push(command2); - poll.publishMessage(message2, ecdhKeypair2.pubKey); - // Use the accumulator queue to compare the root of the message tree - const accumulatorQueue: AccQueue = new AccQueue( - treeDepths.messageTreeSubDepth, - STATE_TREE_ARITY, - NOTHING_UP_MY_SLEEVE, - ); - accumulatorQueue.enqueue(nothing.hash(encP)); - accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey)); - accumulatorQueue.enqueue(message2.hash(ecdhKeypair2.pubKey)); - accumulatorQueue.mergeSubRoots(0); - accumulatorQueue.merge(treeDepths.messageTreeDepth); - - expect(poll.messageTree.root.toString()).to.be.eq( - accumulatorQueue.getMainRoots()[treeDepths.messageTreeDepth].toString(), - ); - }); - - it("should produce the correct state root and ballot root", async () => { - // The current roots - const emptyBallot = new Ballot(poll.maxValues.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); - const emptyBallotHash = emptyBallot.hash(); - const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash5); - - ballotTree.insert(emptyBallot.hash()); - - poll.stateLeaves.forEach(() => { - ballotTree.insert(emptyBallotHash); - }); - - const currentStateRoot = poll.stateTree?.root; - const currentBallotRoot = ballotTree.root; - - const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; - - // Calculate the witness - const witness = await circuit.calculateWitness(inputs); - await circuit.expectConstraintPass(witness); - - // The new roots, which should differ, since at least one of the - // messages modified a Ballot or State Leaf - const newStateRoot = poll.stateTree?.root; - const newBallotRoot = poll.ballotTree?.root; - - expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); - expect(newBallotRoot?.toString()).not.to.be.eq(currentBallotRoot.toString()); - - const packedVals = packProcessMessageSmallVals( - BigInt(maxValues.maxVoteOptions), - BigInt(poll.maciStateRef.numSignUps), - 0, - 3, - ); - - // Test the ProcessMessagesInputHasher circuit - const hasherCircuitInputs = { - packedVals, - coordPubKey: inputs.coordPubKey, - msgRoot: inputs.msgRoot, - currentSbCommitment: inputs.currentSbCommitment, - newSbCommitment: inputs.newSbCommitment, - pollEndTimestamp: inputs.pollEndTimestamp, - }; - - const hasherWitness = await hasherCircuit.calculateWitness(hasherCircuitInputs); - await hasherCircuit.expectConstraintPass(hasherWitness); - const hash = await getSignal(hasherCircuit, hasherWitness, "hash"); - expect(hash.toString()).to.be.eq(inputs.inputHash.toString()); - }); - }); - - describe("1 user, 2 messages (non-quadratic voting)", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const voteWeight = BigInt(9); - const voteOptionIndex = BigInt(0); - let stateIndex: bigint; - let pollId: bigint; - let poll: Poll; - const messages: Message[] = []; - const commands: PCommand[] = []; - - before(() => { - // Sign up and publish - const userKeypair = new Keypair(new PrivKey(BigInt(1))); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); - - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - // First command (valid) - const command = new PCommand( - stateIndex, // BigInt(1), - userKeypair.pubKey, - voteOptionIndex, // voteOptionIndex, - voteWeight, // vote weight - BigInt(2), // nonce - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - messages.push(message); - commands.push(command); - - poll.publishMessage(message, ecdhKeypair.pubKey); - - // Second command (valid) - const command2 = new PCommand( - stateIndex, - userKeypair.pubKey, - voteOptionIndex, // voteOptionIndex, - BigInt(1), // vote weight - BigInt(1), // nonce - BigInt(pollId), - ); - const signature2 = command2.sign(userKeypair.privKey); - - const ecdhKeypair2 = new Keypair(); - const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); - const message2 = command2.encrypt(signature2, sharedKey2); - messages.push(message2); - commands.push(command2); - poll.publishMessage(message2, ecdhKeypair2.pubKey); - }); - - it("should produce the correct state root and ballot root", async () => { - // The current roots - const emptyBallot = new Ballot(poll.maxValues.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); - const emptyBallotHash = emptyBallot.hash(); - const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash5); - - ballotTree.insert(emptyBallot.hash()); - - poll.stateLeaves.forEach(() => { - ballotTree.insert(emptyBallotHash); - }); - - const currentStateRoot = poll.stateTree?.root; - const currentBallotRoot = ballotTree.root; - - const inputs = poll.processMessages(pollId, false) as unknown as IProcessMessagesInputs; - - // Calculate the witness - const witness = await circuitNonQv.calculateWitness(inputs); - await circuitNonQv.expectConstraintPass(witness); - - // The new roots, which should differ, since at least one of the - // messages modified a Ballot or State Leaf - const newStateRoot = poll.stateTree?.root; - const newBallotRoot = poll.ballotTree?.root; - - expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); - expect(newBallotRoot?.toString()).not.to.be.eq(currentBallotRoot.toString()); - - const packedVals = packProcessMessageSmallVals( - BigInt(maxValues.maxVoteOptions), - BigInt(poll.maciStateRef.numSignUps), - 0, - 2, - ); - - // Test the ProcessMessagesInputHasher circuit - const hasherCircuitInputs = { - packedVals, - coordPubKey: inputs.coordPubKey, - msgRoot: inputs.msgRoot, - currentSbCommitment: inputs.currentSbCommitment, - newSbCommitment: inputs.newSbCommitment, - pollEndTimestamp: inputs.pollEndTimestamp, - }; - - const hasherWitness = await hasherCircuit.calculateWitness(hasherCircuitInputs); - await hasherCircuit.expectConstraintPass(hasherWitness); - const hash = await getSignal(hasherCircuit, hasherWitness, "hash"); - expect(hash.toString()).to.be.eq(inputs.inputHash.toString()); - }); - }); - - describe("2 users, 1 message", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - let pollId: bigint; - let poll: Poll; - const messages: Message[] = []; - const commands: PCommand[] = []; - - before(() => { - // Sign up and publish - const userKeypair = new Keypair(new PrivKey(BigInt(123))); - const userKeypair2 = new Keypair(new PrivKey(BigInt(456))); - - maciState.signUp( - userKeypair.pubKey, - voiceCreditBalance, - BigInt(1), // BigInt(Math.floor(Date.now() / 1000)), - ); - maciState.signUp( - userKeypair2.pubKey, - voiceCreditBalance, - BigInt(1), // BigInt(Math.floor(Date.now() / 1000)), - ); - - pollId = maciState.deployPoll( - BigInt(2 + duration), // BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - const command = new PCommand( - BigInt(1), - userKeypair.pubKey, - BigInt(0), // voteOptionIndex, - BigInt(1), // vote weight - BigInt(1), // nonce - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - messages.push(message); - commands.push(command); - - poll.publishMessage(message, ecdhKeypair.pubKey); - - // Use the accumulator queue to compare the root of the message tree - const accumulatorQueue: AccQueue = new AccQueue( - treeDepths.messageTreeSubDepth, - STATE_TREE_ARITY, - NOTHING_UP_MY_SLEEVE, - ); - accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey)); - accumulatorQueue.mergeSubRoots(0); - accumulatorQueue.merge(treeDepths.messageTreeDepth); - - expect(poll.messageTree.root.toString()).to.be.eq( - accumulatorQueue.getRoot(treeDepths.messageTreeDepth)?.toString(), - ); - }); - - it("should produce the correct state root and ballot root", async () => { - // The current roots - const emptyBallot = new Ballot(poll.maxValues.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); - const emptyBallotHash = emptyBallot.hash(); - const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash5); - - ballotTree.insert(emptyBallot.hash()); - - poll.stateLeaves.forEach(() => { - ballotTree.insert(emptyBallotHash); - }); - - const currentStateRoot = poll.stateTree?.root; - const currentBallotRoot = ballotTree.root; - - const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; - // Calculate the witness - const witness = await circuit.calculateWitness(inputs); - await circuit.expectConstraintPass(witness); - - // The new roots, which should differ, since at least one of the - // messages modified a Ballot or State Leaf - const newStateRoot = poll.stateTree?.root; - const newBallotRoot = poll.ballotTree?.root; - - expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); - expect(newBallotRoot?.toString()).not.to.be.eq(currentBallotRoot.toString()); - }); - }); - - describe("1 user, key-change", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const voteWeight = BigInt(9); - let stateIndex: number; - let pollId: bigint; - let poll: Poll; - const messages: Message[] = []; - const commands: PCommand[] = []; - - const NUM_BATCHES = 2; - - before(() => { - // Sign up and publish - const userKeypair = new Keypair(new PrivKey(BigInt(123))); - const userKeypair2 = new Keypair(new PrivKey(BigInt(456))); - - stateIndex = maciState.signUp( - userKeypair.pubKey, - voiceCreditBalance, - BigInt(1), // BigInt(Math.floor(Date.now() / 1000)), - ); - - pollId = maciState.deployPoll( - BigInt(2 + duration), // BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - // Vote for option 0 - const command = new PCommand( - BigInt(stateIndex), // BigInt(1), - userKeypair.pubKey, - BigInt(0), // voteOptionIndex, - voteWeight, // vote weight - BigInt(1), // nonce - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - messages.push(message); - commands.push(command); - - poll.publishMessage(message, ecdhKeypair.pubKey); - - // Vote for option 1 - const command2 = new PCommand( - BigInt(stateIndex), - userKeypair2.pubKey, - BigInt(1), // voteOptionIndex, - voteWeight, // vote weight - BigInt(2), // nonce - BigInt(pollId), - ); - const signature2 = command2.sign(userKeypair2.privKey); - - const ecdhKeypair2 = new Keypair(); - const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); - const message2 = command2.encrypt(signature2, sharedKey2); - messages.push(message2); - commands.push(command2); - poll.publishMessage(message2, ecdhKeypair2.pubKey); - - // Change key - const command3 = new PCommand( - BigInt(stateIndex), // BigInt(1), - userKeypair2.pubKey, - BigInt(1), // voteOptionIndex, - BigInt(0), // vote weight - BigInt(1), // nonce - BigInt(pollId), - ); - - const signature3 = command3.sign(userKeypair.privKey); - - const ecdhKeypair3 = new Keypair(); - const sharedKey3 = Keypair.genEcdhSharedKey(ecdhKeypair3.privKey, coordinatorKeypair.pubKey); - const message3 = command3.encrypt(signature3, sharedKey3); - messages.push(message3); - commands.push(command3); - poll.publishMessage(message3, ecdhKeypair3.pubKey); - // Use the accumulator queue to compare the root of the message tree - const accumulatorQueue: AccQueue = new AccQueue( - treeDepths.messageTreeSubDepth, - STATE_TREE_ARITY, - NOTHING_UP_MY_SLEEVE, - ); - accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey)); - accumulatorQueue.enqueue(message2.hash(ecdhKeypair2.pubKey)); - accumulatorQueue.enqueue(message3.hash(ecdhKeypair3.pubKey)); - accumulatorQueue.mergeSubRoots(0); - accumulatorQueue.merge(treeDepths.messageTreeDepth); - - expect(poll.messageTree.root.toString()).to.be.eq( - accumulatorQueue.getRoot(treeDepths.messageTreeDepth)?.toString(), - ); - }); - - describe(`1 user, ${messageBatchSize * NUM_BATCHES} messages`, () => { - it("should produce the correct state root and ballot root", async () => { - const state = new MaciState(STATE_TREE_DEPTH); - const userKeypair = new Keypair(); - const index = state.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); - - // Sign up and publish - const id = state.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - const selectedPoll = state.polls.get(id); - - selectedPoll?.updatePoll(BigInt(state.stateLeaves.length)); - - // Second batch is not a full batch - const numMessages = messageBatchSize * NUM_BATCHES - 1; - for (let i = 0; i < numMessages; i += 1) { - const command = new PCommand( - BigInt(index), - userKeypair.pubKey, - BigInt(i), // vote option index - BigInt(1), // vote weight - BigInt(numMessages - i), // nonce - BigInt(id), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - selectedPoll?.publishMessage(message, ecdhKeypair.pubKey); - } - - for (let i = 0; i < 2; i += 1) { - const inputs = selectedPoll?.processMessages(id) as unknown as IProcessMessagesInputs; - // eslint-disable-next-line no-await-in-loop - const witness = await circuit.calculateWitness(inputs); - // eslint-disable-next-line no-await-in-loop - await circuit.expectConstraintPass(witness); - } - }); - }); - }); - - describe("1 user, 1 topup, 2 messages", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const voteOptionIndex = BigInt(0); - let stateIndex: bigint; - let pollId: bigint; - let poll: Poll; - const userKeypair = new Keypair(); - - before(() => { - // Sign up and publish - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); - - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - }); - - it("should work when publishing 2 vote messages and a topup (the second vote uses more than initial voice credit balance)", async () => { - // First command (valid) - const command1 = new PCommand( - stateIndex, // BigInt(1), - userKeypair.pubKey, - voteOptionIndex + 1n, // voteOptionIndex, - 5n, // vote weight - BigInt(2), // nonce - BigInt(pollId), - ); - - const signature1 = command1.sign(userKeypair.privKey); - - const ecdhKeypair1 = new Keypair(); - const sharedKey1 = Keypair.genEcdhSharedKey(ecdhKeypair1.privKey, coordinatorKeypair.pubKey); - const message1 = command1.encrypt(signature1, sharedKey1); - - poll.publishMessage(message1, ecdhKeypair1.pubKey); - - poll.topupMessage(new Message(2n, [BigInt(stateIndex), 50n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n])); - - // First command (valid) - const command = new PCommand( - stateIndex, // BigInt(1), - userKeypair.pubKey, - voteOptionIndex, // voteOptionIndex, - 10n, // vote weight - BigInt(1), // nonce - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - - poll.publishMessage(message, ecdhKeypair.pubKey); - - const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; - const witness = await circuit.calculateWitness(inputs); - await circuit.expectConstraintPass(witness); - }); - }); - - describe("1 user, 2 messages", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const voteOptionIndex = 1n; - let stateIndex: bigint; - let pollId: bigint; - let poll: Poll; - const messages: Message[] = []; - const commands: PCommand[] = []; - - before(() => { - // Sign up and publish - const userKeypair = new Keypair(new PrivKey(BigInt(1))); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); - - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - const nothing = new Message(1n, [ - 8370432830353022751713833565135785980866757267633941821328460903436894336785n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - ]); - - const encP = new PubKey([ - 10457101036533406547632367118273992217979173478358440826365724437999023779287n, - 19824078218392094440610104313265183977899662750282163392862422243483260492317n, - ]); - - poll.publishMessage(nothing, encP); - - // First command (valid) - const command = new PCommand( - stateIndex, // BigInt(1), - userKeypair.pubKey, - 1n, // voteOptionIndex, - 2n, // vote weight - 2n, // nonce - pollId, - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - messages.push(message); - commands.push(command); - - poll.publishMessage(message, ecdhKeypair.pubKey); - - // Second command (valid) - const command2 = new PCommand( - stateIndex, - userKeypair.pubKey, - voteOptionIndex, // voteOptionIndex, - 9n, // vote weight 9 ** 2 = 81 - 1n, // nonce - pollId, - ); - const signature2 = command2.sign(userKeypair.privKey); - - const ecdhKeypair2 = new Keypair(); - const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); - const message2 = command2.encrypt(signature2, sharedKey2); - messages.push(message2); - commands.push(command2); - poll.publishMessage(message2, ecdhKeypair2.pubKey); - }); - - it("should produce the correct state root and ballot root", async () => { - // The current roots - const emptyBallot = new Ballot(poll.maxValues.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); - const emptyBallotHash = emptyBallot.hash(); - const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash5); - - ballotTree.insert(emptyBallot.hash()); - - poll.stateLeaves.forEach(() => { - ballotTree.insert(emptyBallotHash); - }); - - const currentStateRoot = poll.stateTree?.root; - const currentBallotRoot = ballotTree.root; - - const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; - - // Calculate the witness - const witness = await circuit.calculateWitness(inputs); - await circuit.expectConstraintPass(witness); - - // The new roots, which should differ, since at least one of the - // messages modified a Ballot or State Leaf - const newStateRoot = poll.stateTree?.root; - const newBallotRoot = poll.ballotTree?.root; - - expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); - expect(newBallotRoot?.toString()).not.to.be.eq(currentBallotRoot.toString()); - }); - }); - - describe("1 user, 2 messages in different batches", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const voteOptionIndex = 1n; - let stateIndex: bigint; - let pollId: bigint; - let poll: Poll; - const messages: Message[] = []; - const commands: PCommand[] = []; - - before(() => { - // Sign up and publish - const userKeypair = new Keypair(new PrivKey(BigInt(1))); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); - - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - const nothing = new Message(1n, [ - 8370432830353022751713833565135785980866757267633941821328460903436894336785n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - ]); - - const encP = new PubKey([ - 10457101036533406547632367118273992217979173478358440826365724437999023779287n, - 19824078218392094440610104313265183977899662750282163392862422243483260492317n, - ]); - - poll.publishMessage(nothing, encP); - - // First command (valid) - const command = new PCommand( - stateIndex, // BigInt(1), - userKeypair.pubKey, - 1n, // voteOptionIndex, - 2n, // vote weight - 2n, // nonce - pollId, - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - messages.push(message); - commands.push(command); - - poll.publishMessage(message, ecdhKeypair.pubKey); - - // fill the batch with nothing messages - for (let i = 0; i < messageBatchSize - 1; i += 1) { - poll.publishMessage(nothing, encP); - } - - // Second command (valid) in second batch (which is first due to reverse processing) - const command2 = new PCommand( - stateIndex, - userKeypair.pubKey, - voteOptionIndex, // voteOptionIndex, - 9n, // vote weight 9 ** 2 = 81 - 1n, // nonce - pollId, - ); - const signature2 = command2.sign(userKeypair.privKey); - - const ecdhKeypair2 = new Keypair(); - const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); - const message2 = command2.encrypt(signature2, sharedKey2); - messages.push(message2); - commands.push(command2); - poll.publishMessage(message2, ecdhKeypair2.pubKey); - }); - - it("should produce the correct state root and ballot root", async () => { - // The current roots - const emptyBallot = new Ballot(poll.maxValues.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); - const emptyBallotHash = emptyBallot.hash(); - const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash5); - - ballotTree.insert(emptyBallot.hash()); - - poll.stateLeaves.forEach(() => { - ballotTree.insert(emptyBallotHash); - }); - - while (poll.hasUnprocessedMessages()) { - const currentStateRoot = poll.stateTree?.root; - const currentBallotRoot = ballotTree.root; - const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; - - // Calculate the witness - // eslint-disable-next-line no-await-in-loop - const witness = await circuit.calculateWitness(inputs); - // eslint-disable-next-line no-await-in-loop - await circuit.expectConstraintPass(witness); - - // The new roots, which should differ, since at least one of the - // messages modified a Ballot or State Leaf - const newStateRoot = poll.stateTree?.root; - const newBallotRoot = poll.ballotTree?.root; - - expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); - expect(newBallotRoot?.toString()).not.to.be.eq(currentBallotRoot.toString()); - } - }); - }); - - describe("1 user, 3 messages in different batches", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const voteOptionIndex = 1n; - let stateIndex: bigint; - let pollId: bigint; - let poll: Poll; - const messages: Message[] = []; - const commands: PCommand[] = []; - - before(() => { - // Sign up and publish - const userKeypair = new Keypair(new PrivKey(BigInt(1))); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); - - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - const nothing = new Message(1n, [ - 8370432830353022751713833565135785980866757267633941821328460903436894336785n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - 0n, - ]); - - const encP = new PubKey([ - 10457101036533406547632367118273992217979173478358440826365724437999023779287n, - 19824078218392094440610104313265183977899662750282163392862422243483260492317n, - ]); - - poll.publishMessage(nothing, encP); - - const commandFinal = new PCommand( - stateIndex, // BigInt(1), - userKeypair.pubKey, - 1n, // voteOptionIndex, - 1n, // vote weight - 3n, // nonce - pollId, - ); - - const signatureFinal = commandFinal.sign(userKeypair.privKey); - - const ecdhKeypairFinal = new Keypair(); - const sharedKeyFinal = Keypair.genEcdhSharedKey(ecdhKeypairFinal.privKey, coordinatorKeypair.pubKey); - const messageFinal = commandFinal.encrypt(signatureFinal, sharedKeyFinal); - messages.push(messageFinal); - commands.push(commandFinal); - - poll.publishMessage(messageFinal, ecdhKeypairFinal.pubKey); - - // First command (valid) - const command = new PCommand( - stateIndex, // BigInt(1), - userKeypair.pubKey, - 1n, // voteOptionIndex, - 2n, // vote weight - 2n, // nonce - pollId, - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - messages.push(message); - commands.push(command); - - poll.publishMessage(message, ecdhKeypair.pubKey); - - // fill the batch with nothing messages - for (let i = 0; i < messageBatchSize - 1; i += 1) { - poll.publishMessage(nothing, encP); - } - - // Second command (valid) in second batch (which is first due to reverse processing) - const command2 = new PCommand( - stateIndex, - userKeypair.pubKey, - voteOptionIndex, // voteOptionIndex, - 9n, // vote weight 9 ** 2 = 81 - 1n, // nonce - pollId, - ); - const signature2 = command2.sign(userKeypair.privKey); - - const ecdhKeypair2 = new Keypair(); - const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); - const message2 = command2.encrypt(signature2, sharedKey2); - messages.push(message2); - commands.push(command2); - poll.publishMessage(message2, ecdhKeypair2.pubKey); - }); - - it("should produce the correct state root and ballot root", async () => { - // The current roots - const emptyBallot = new Ballot(poll.maxValues.maxVoteOptions, poll.treeDepths.voteOptionTreeDepth); - const emptyBallotHash = emptyBallot.hash(); - const ballotTree = new IncrementalQuinTree(STATE_TREE_DEPTH, emptyBallot.hash(), STATE_TREE_ARITY, hash5); - - ballotTree.insert(emptyBallot.hash()); - - poll.stateLeaves.forEach(() => { - ballotTree.insert(emptyBallotHash); - }); - - while (poll.hasUnprocessedMessages()) { - const currentStateRoot = poll.stateTree?.root; - const currentBallotRoot = ballotTree.root; - const inputs = poll.processMessages(pollId) as unknown as IProcessMessagesInputs; - - // Calculate the witness - // eslint-disable-next-line no-await-in-loop - const witness = await circuit.calculateWitness(inputs); - // eslint-disable-next-line no-await-in-loop - await circuit.expectConstraintPass(witness); - - // The new roots, which should differ, since at least one of the - // messages modified a Ballot or State Leaf - const newStateRoot = poll.stateTree?.root; - const newBallotRoot = poll.ballotTree?.root; - - expect(newStateRoot?.toString()).not.to.be.eq(currentStateRoot?.toString()); - expect(newBallotRoot?.toString()).not.to.be.eq(currentBallotRoot.toString()); - } - }); - }); -}); diff --git a/packages/hardhat/maci-ts/circuits/__tests__/QuinCheckRoot.test.ts b/packages/hardhat/maci-ts/circuits/__tests__/QuinCheckRoot.test.ts deleted file mode 100644 index b4e3d00..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/QuinCheckRoot.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import chai, { expect } from "chai"; -import chaiAsPromised from "chai-as-promised"; -import { type WitnessTester } from "circomkit"; -import { IncrementalQuinTree, hash5 } from "../../crypto"; - -import { getSignal, circomkitInstance } from "./utils/utils"; - -chai.use(chaiAsPromised); - -describe("QuinCheckRoot circuit", function test() { - this.timeout(50000); - - const leavesPerNode = 5; - const treeDepth = 3; - - let circuit: WitnessTester<["leaves"], ["root"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("checkRoot", { - file: "trees/checkRoot", - template: "QuinCheckRoot", - params: [3], - }); - }); - - it("should compute the correct merkle root", async () => { - const leaves = Array(leavesPerNode ** treeDepth).fill(5n); - - const circuitInputs = { - leaves, - }; - - const tree = new IncrementalQuinTree(3, 0n, 5, hash5); - leaves.forEach(leaf => { - tree.insert(leaf); - }); - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - - const circuitRoot = await getSignal(circuit, witness, "root"); - expect(circuitRoot.toString()).to.be.eq(tree.root.toString()); - }); - - it("should not accept less leaves than a full tree", async () => { - const leaves = Array(leavesPerNode ** treeDepth - 1).fill(5n); - - const circuitInputs = { - leaves, - }; - - await expect(circuit.calculateWitness(circuitInputs)).to.be.rejectedWith( - "Not enough values for input signal leaves", - ); - }); -}); diff --git a/packages/hardhat/maci-ts/circuits/__tests__/StateLeafAndBallotTransformer.test.ts b/packages/hardhat/maci-ts/circuits/__tests__/StateLeafAndBallotTransformer.test.ts deleted file mode 100644 index a02801e..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/StateLeafAndBallotTransformer.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { expect } from "chai"; -import { type WitnessTester } from "circomkit"; -import { genRandomSalt } from "../../crypto"; -import { PCommand, Keypair } from "../../domainobjs"; - -import { getSignal, circomkitInstance } from "./utils/utils"; - -describe("StateLeafAndBallotTransformer circuit", function test() { - this.timeout(90000); - - // variables needed for testing - const keypair = new Keypair(); - const stateIndex = BigInt(1); - const newPubKey = keypair.pubKey; - const voteOptionIndex = BigInt(0); - const newVoteWeight = BigInt(9); - const nonce = BigInt(1); - const pollId = BigInt(0); - const salt = genRandomSalt(); - const numSignUps = 25n; - const maxVoteOptions = 25n; - - const slKeypair = new Keypair(); - const slPubKey = slKeypair.pubKey; - - const slVoiceCreditBalance = BigInt(100); - const ballotNonce = BigInt(0); - const ballotCurrentVotesForOption = BigInt(0); - const slTimestamp = 1n; - const pollEndTimestamp = 2n; - - const command: PCommand = new PCommand(stateIndex, newPubKey, voteOptionIndex, newVoteWeight, nonce, pollId, salt); - - const signature = command.sign(slKeypair.privKey); - - let circuit: WitnessTester< - [ - "numSignUps", - "maxVoteOptions", - "slPubKey", - "slVoiceCreditBalance", - "slTimestamp", - "pollEndTimestamp", - "ballotNonce", - "ballotCurrentVotesForOption", - "cmdStateIndex", - "cmdNewPubKey", - "cmdVoteOptionIndex", - "cmdNewVoteWeight", - "cmdNonce", - "cmdPollId", - "cmdSalt", - "cmdSigR8", - "cmdSigS", - "packedCommand", - ], - ["newSlPubKey", "newBallotNonce", "isValid"] - >; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("stateLeafAndBallotTransformer", { - file: "stateLeafAndBallotTransformer", - template: "StateLeafAndBallotTransformer", - }); - }); - - it("should output new state leaf and ballot values if the command is valid", async () => { - const circuitInputs = { - numSignUps, - maxVoteOptions, - slPubKey: slPubKey.asCircuitInputs() as unknown as [bigint, bigint], - slVoiceCreditBalance, - slTimestamp, - pollEndTimestamp, - ballotNonce, - ballotCurrentVotesForOption, - cmdStateIndex: command.stateIndex, - cmdNewPubKey: command.newPubKey.asCircuitInputs() as unknown as [bigint, bigint], - cmdVoteOptionIndex: command.voteOptionIndex, - cmdNewVoteWeight: command.newVoteWeight, - cmdNonce: command.nonce, - cmdPollId: command.pollId, - cmdSalt: command.salt, - cmdSigR8: signature.R8 as [bigint, bigint], - cmdSigS: signature.S as bigint, - packedCommand: command.asCircuitInputs(), - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - - const newSlPubKey0 = await getSignal(circuit, witness, "newSlPubKey[0]"); - const newSlPubKey1 = await getSignal(circuit, witness, "newSlPubKey[1]"); - const newBallotNonce = await getSignal(circuit, witness, "newBallotNonce"); - - expect(newSlPubKey0.toString()).to.be.eq(command.newPubKey.rawPubKey[0].toString()); - expect(newSlPubKey1.toString()).to.be.eq(command.newPubKey.rawPubKey[1].toString()); - expect(newBallotNonce.toString()).to.be.eq(command.nonce.toString()); - - const isValid = await getSignal(circuit, witness, "isValid"); - expect(isValid.toString()).to.be.eq("1"); - }); - - it("should output existing state leaf and ballot values if the command is invalid", async () => { - const circuitInputs = { - numSignUps, - maxVoteOptions, - slPubKey: slPubKey.asCircuitInputs() as unknown as [bigint, bigint], - slVoiceCreditBalance, - slTimestamp, - pollEndTimestamp, - ballotNonce, - ballotCurrentVotesForOption, - cmdStateIndex: command.stateIndex, - cmdNewPubKey: command.newPubKey.asCircuitInputs() as unknown as [bigint, bigint], - cmdVoteOptionIndex: command.voteOptionIndex, - cmdNewVoteWeight: command.newVoteWeight, - cmdNonce: 2n, // invalid - cmdPollId: command.pollId, - cmdSalt: command.salt, - cmdSigR8: signature.R8 as [bigint, bigint], - cmdSigS: signature.S as bigint, - packedCommand: command.asCircuitInputs(), - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - - const newSlPubKey0 = await getSignal(circuit, witness, "newSlPubKey[0]"); - const newSlPubKey1 = await getSignal(circuit, witness, "newSlPubKey[1]"); - const newBallotNonce = await getSignal(circuit, witness, "newBallotNonce"); - - expect(newSlPubKey0.toString()).to.be.eq(slPubKey.rawPubKey[0].toString()); - expect(newSlPubKey1.toString()).to.be.eq(slPubKey.rawPubKey[1].toString()); - expect(newBallotNonce.toString()).to.be.eq("0"); - - const isValid = await getSignal(circuit, witness, "isValid"); - expect(isValid.toString()).to.be.eq("0"); - }); -}); diff --git a/packages/hardhat/maci-ts/circuits/__tests__/TallyVotes.test.ts b/packages/hardhat/maci-ts/circuits/__tests__/TallyVotes.test.ts deleted file mode 100644 index 8a01de7..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/TallyVotes.test.ts +++ /dev/null @@ -1,304 +0,0 @@ -import { expect } from "chai"; -import { type WitnessTester } from "circomkit"; -import { MaciState, Poll, STATE_TREE_ARITY } from "../../core"; -import { AccQueue, NOTHING_UP_MY_SLEEVE } from "../../crypto"; -import { Keypair, PCommand, Message } from "../../domainobjs"; - -import { ITallyVotesInputs } from "../types"; - -import { STATE_TREE_DEPTH, duration, maxValues, messageBatchSize, voiceCreditBalance } from "./utils/constants"; -import { generateRandomIndex, circomkitInstance } from "./utils/utils"; - -describe("TallyVotes circuit", function test() { - this.timeout(900000); - - const treeDepths = { - intStateTreeDepth: 1, - messageTreeDepth: 2, - messageTreeSubDepth: 1, - voteOptionTreeDepth: 2, - }; - - const coordinatorKeypair = new Keypair(); - - type TallyVotesCircuitInputs = [ - "stateRoot", - "ballotRoot", - "sbSalt", - "packedVals", - "sbCommitment", - "currentTallyCommitment", - "newTallyCommitment", - "inputHash", - "ballots", - "ballotPathElements", - "votes", - "currentResults", - "currentResultsRootSalt", - "currentSpentVoiceCreditSubtotal", - "currentSpentVoiceCreditSubtotalSalt", - "currentPerVOSpentVoiceCredits", - "currentPerVOSpentVoiceCreditsRootSalt", - "newResultsRootSalt", - "newPerVOSpentVoiceCreditsRootSalt", - "newSpentVoiceCreditSubtotalSalt", - ]; - - let circuit: WitnessTester; - - let circuitNonQv: WitnessTester; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("tallyVotes", { - file: "tallyVotes", - template: "TallyVotes", - params: [10, 1, 2], - }); - - circuitNonQv = await circomkitInstance.WitnessTester("tallyVotesNonQv", { - file: "tallyVotesNonQv", - template: "TallyVotesNonQv", - params: [10, 1, 2], - }); - }); - - describe("1 user, 2 messages", () => { - let stateIndex: bigint; - let pollId: bigint; - let poll: Poll; - let maciState: MaciState; - const voteWeight = BigInt(9); - const voteOptionIndex = BigInt(0); - - beforeEach(() => { - maciState = new MaciState(STATE_TREE_DEPTH); - const messages: Message[] = []; - const commands: PCommand[] = []; - // Sign up and publish - const userKeypair = new Keypair(); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); - - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - poll.updatePoll(stateIndex); - - // First command (valid) - const command = new PCommand( - stateIndex, - userKeypair.pubKey, - voteOptionIndex, // voteOptionIndex, - voteWeight, // vote weight - BigInt(1), // nonce - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - messages.push(message); - commands.push(command); - - poll.publishMessage(message, ecdhKeypair.pubKey); - // Use the accumulator queue to compare the root of the message tree - const accumulatorQueue: AccQueue = new AccQueue( - treeDepths.messageTreeSubDepth, - STATE_TREE_ARITY, - NOTHING_UP_MY_SLEEVE, - ); - accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey)); - accumulatorQueue.mergeSubRoots(0); - accumulatorQueue.merge(treeDepths.messageTreeDepth); - - expect(poll.messageTree.root.toString()).to.be.eq( - accumulatorQueue.getMainRoots()[treeDepths.messageTreeDepth].toString(), - ); - // Process messages - poll.processMessages(pollId); - }); - - it("should produce the correct result commitments", async () => { - const generatedInputs = poll.tallyVotes() as unknown as ITallyVotesInputs; - const witness = await circuit.calculateWitness(generatedInputs); - await circuit.expectConstraintPass(witness); - }); - - it("should produce the correct result if the initial tally is not zero", async () => { - const generatedInputs = poll.tallyVotes() as unknown as ITallyVotesInputs; - - // Start the tally from non-zero value - let randIdx = generateRandomIndex(Object.keys(generatedInputs).length); - while (randIdx === 0) { - randIdx = generateRandomIndex(Object.keys(generatedInputs).length); - } - - generatedInputs.currentResults[randIdx] = 1n; - const witness = await circuit.calculateWitness(generatedInputs); - await circuit.expectConstraintPass(witness); - }); - }); - - describe("1 user, 2 messages (non qv)", () => { - let stateIndex: bigint; - let pollId: bigint; - let poll: Poll; - let maciState: MaciState; - const voteWeight = BigInt(9); - const voteOptionIndex = BigInt(0); - - beforeEach(() => { - maciState = new MaciState(STATE_TREE_DEPTH); - const messages: Message[] = []; - const commands: PCommand[] = []; - // Sign up and publish - const userKeypair = new Keypair(); - stateIndex = BigInt( - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))), - ); - - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - poll.updatePoll(stateIndex); - - // First command (valid) - const command = new PCommand( - stateIndex, - userKeypair.pubKey, - voteOptionIndex, // voteOptionIndex, - voteWeight, // vote weight - BigInt(1), // nonce - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - messages.push(message); - commands.push(command); - - poll.publishMessage(message, ecdhKeypair.pubKey); - // Use the accumulator queue to compare the root of the message tree - const accumulatorQueue: AccQueue = new AccQueue( - treeDepths.messageTreeSubDepth, - STATE_TREE_ARITY, - NOTHING_UP_MY_SLEEVE, - ); - accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey)); - accumulatorQueue.mergeSubRoots(0); - accumulatorQueue.merge(treeDepths.messageTreeDepth); - - expect(poll.messageTree.root.toString()).to.be.eq( - accumulatorQueue.getMainRoots()[treeDepths.messageTreeDepth].toString(), - ); - // Process messages - poll.processMessages(pollId, false); - }); - - it("should produce the correct result commitments", async () => { - const generatedInputs = poll.tallyVotesNonQv() as unknown as ITallyVotesInputs; - - const witness = await circuitNonQv.calculateWitness(generatedInputs); - await circuitNonQv.expectConstraintPass(witness); - }); - - it("should produce the correct result if the initial tally is not zero", async () => { - const generatedInputs = poll.tallyVotesNonQv() as unknown as ITallyVotesInputs; - - // Start the tally from non-zero value - let randIdx = generateRandomIndex(Object.keys(generatedInputs).length); - while (randIdx === 0) { - randIdx = generateRandomIndex(Object.keys(generatedInputs).length); - } - - generatedInputs.currentResults[randIdx] = 1n; - const witness = await circuitNonQv.calculateWitness(generatedInputs); - await circuitNonQv.expectConstraintPass(witness); - }); - }); - - const NUM_BATCHES = 2; - const x = messageBatchSize * NUM_BATCHES; - - describe(`${x} users, ${x} messages`, () => { - it("should produce the correct state root and ballot root", async () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const userKeypairs: Keypair[] = []; - for (let i = 0; i < x; i += 1) { - const k = new Keypair(); - userKeypairs.push(k); - maciState.signUp(k.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000) + duration)); - } - - const pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - const poll = maciState.polls.get(pollId)!; - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - const numMessages = messageBatchSize * NUM_BATCHES; - for (let i = 0; i < numMessages; i += 1) { - const command = new PCommand( - BigInt(i), - userKeypairs[i].pubKey, - BigInt(i), // vote option index - BigInt(1), // vote weight - BigInt(1), // nonce - BigInt(pollId), - ); - - const signature = command.sign(userKeypairs[i].privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - } - - for (let i = 0; i < NUM_BATCHES; i += 1) { - poll.processMessages(pollId); - } - - for (let i = 0; i < NUM_BATCHES; i += 1) { - const generatedInputs = poll.tallyVotes() as unknown as ITallyVotesInputs; - - // For the 0th batch, the circuit should ignore currentResults, - // currentSpentVoiceCreditSubtotal, and - // currentPerVOSpentVoiceCredits - if (i === 0) { - generatedInputs.currentResults[0] = 123n; - generatedInputs.currentSpentVoiceCreditSubtotal = 456n; - generatedInputs.currentPerVOSpentVoiceCredits[0] = 789n; - } - - // eslint-disable-next-line no-await-in-loop - const witness = await circuit.calculateWitness(generatedInputs); - // eslint-disable-next-line no-await-in-loop - await circuit.expectConstraintPass(witness); - } - }); - }); -}); diff --git a/packages/hardhat/maci-ts/circuits/__tests__/UnpackElement.test.ts b/packages/hardhat/maci-ts/circuits/__tests__/UnpackElement.test.ts deleted file mode 100644 index ae06b98..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/UnpackElement.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { expect } from "chai"; -import { type WitnessTester } from "circomkit"; -import { genRandomSalt } from "../../crypto"; - -import { getSignal, circomkitInstance } from "./utils/utils"; - -describe("UnpackElement circuit", () => { - let circuit: WitnessTester<["in"], ["out"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("unpackElement", { - file: "unpackElement", - template: "UnpackElement", - params: [5], - }); - }); - - it("should unpack a field element with 5 packed values correctly", async () => { - const elements: string[] = []; - for (let i = 0; i < 5; i += 1) { - let e = (BigInt(genRandomSalt().toString()) % BigInt(2 ** 50)).toString(2); - while (e.length < 50) { - e = `0${e}`; - } - elements.push(e); - } - - const circuitInputs = { - in: BigInt(`0b${elements.join("")}`), - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - - for (let i = 0; i < 5; i += 1) { - // eslint-disable-next-line no-await-in-loop - const out = await getSignal(circuit, witness, `out[${i}]`); - expect(BigInt(`0b${BigInt(out).toString(2)}`).toString()).to.be.eq(BigInt(`0b${elements[i]}`).toString()); - } - }); - - describe("unpackElement4", () => { - before(async () => { - circuit = await circomkitInstance.WitnessTester("unpackElement", { - file: "unpackElement", - template: "UnpackElement", - params: [4], - }); - }); - - it("should unpack a field element with 4 packed values correctly", async () => { - const elements: string[] = []; - for (let i = 0; i < 4; i += 1) { - let e = (BigInt(genRandomSalt().toString()) % BigInt(2 ** 50)).toString(2); - while (e.length < 50) { - e = `0${e}`; - } - elements.push(e); - } - - const circuitInputs = { - in: BigInt(`0b${elements.join("")}`), - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - - for (let i = 0; i < 4; i += 1) { - // eslint-disable-next-line no-await-in-loop - const out = await getSignal(circuit, witness, `out[${i}]`); - expect(BigInt(`0b${BigInt(out).toString(2)}`).toString()).to.be.eq(BigInt(`0b${elements[i]}`).toString()); - } - }); - }); -}); diff --git a/packages/hardhat/maci-ts/circuits/__tests__/VerifySignature.test.ts b/packages/hardhat/maci-ts/circuits/__tests__/VerifySignature.test.ts deleted file mode 100644 index 88473d3..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/VerifySignature.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { expect } from "chai"; -import { type WitnessTester } from "circomkit"; -import { verifySignature, hash4 } from "../../crypto"; -import { Keypair, PCommand } from "../../domainobjs"; - -import { getSignal, circomkitInstance } from "./utils/utils"; - -describe("Signature verification circuit", function test() { - this.timeout(90000); - - let circuit: WitnessTester<["pubKey", "R8", "S", "preimage"], ["valid"]>; - - before(async () => { - circuit = await circomkitInstance.WitnessTester("verifySignature", { - file: "verifySignature", - template: "VerifySignature", - }); - }); - - it("should verify a valid signature", async () => { - const keypair = new Keypair(); - const command = new PCommand(BigInt(0), keypair.pubKey, BigInt(123), BigInt(123), BigInt(1), BigInt(2), BigInt(3)); - - const signer = new Keypair(); - const sig = command.sign(signer.privKey); - const plaintext = hash4(command.asArray()); - - expect(verifySignature(plaintext, sig, signer.pubKey.rawPubKey)).to.eq(true); - - const circuitInputs = { - pubKey: signer.pubKey.asCircuitInputs() as unknown as [bigint, bigint], - R8: sig.R8 as [bigint, bigint], - S: sig.S as bigint, - preimage: command.asCircuitInputs(), - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const isValid = await getSignal(circuit, witness, "valid"); - expect(isValid.toString()).to.be.eq("1"); - }); - - it("should reject an invalid signature (wrong signer)", async () => { - const keypair = new Keypair(); - const command = new PCommand(BigInt(0), keypair.pubKey, BigInt(123), BigInt(123), BigInt(1), BigInt(2), BigInt(3)); - - const signer = new Keypair(); - const wrongSigner = new Keypair(); - - expect(signer.privKey.rawPrivKey).not.to.be.eq(wrongSigner.privKey.rawPrivKey); - - const sig = command.sign(signer.privKey); - - const plaintext = hash4(command.asArray()); - - // The signature is signed by `signer` - expect(verifySignature(plaintext, sig, signer.pubKey.rawPubKey)).to.eq(true); - - // The signature is not signed by `wrongSigner` - expect(verifySignature(plaintext, sig, wrongSigner.pubKey.rawPubKey)).to.eq(false); - - const circuitInputs = { - pubKey: wrongSigner.pubKey.asCircuitInputs() as unknown as [bigint, bigint], - R8: sig.R8 as [bigint, bigint], - S: sig.S as bigint, - preimage: command.asCircuitInputs(), - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const isValid = await getSignal(circuit, witness, "valid"); - expect(isValid.toString()).to.be.eq("0"); - expect((await getSignal(circuit, witness, "verifier.isCcZero.out")).toString()).to.be.eq("1"); - }); - - it("should reject an invalid signature (wrong S)", async () => { - const keypair = new Keypair(); - const command = new PCommand(BigInt(0), keypair.pubKey, BigInt(123), BigInt(123), BigInt(1), BigInt(2), BigInt(3)); - - const signer = new Keypair(); - const sig = command.sign(signer.privKey); - const plaintext = hash4(command.asArray()); - - expect(verifySignature(plaintext, sig, signer.pubKey.rawPubKey)).to.eq(true); - - const circuitInputs = { - pubKey: signer.pubKey.asCircuitInputs() as unknown as [bigint, bigint], - R8: sig.R8 as [bigint, bigint], - S: BigInt("2736030358979909402780800718157159386076813972158567259200215660948447373040") + BigInt(1), - preimage: command.asCircuitInputs(), - }; - - expect(verifySignature(plaintext, sig, signer.pubKey.rawPubKey)).to.eq(true); - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const isValid = await getSignal(circuit, witness, "valid"); - expect(isValid.toString()).to.be.eq("0"); - expect((await getSignal(circuit, witness, "verifier.isCcZero.out")).toString()).to.be.eq("0"); - }); -}); diff --git a/packages/hardhat/maci-ts/circuits/__tests__/utils/constants.ts b/packages/hardhat/maci-ts/circuits/__tests__/utils/constants.ts deleted file mode 100644 index 460c85e..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/utils/constants.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const STATE_TREE_DEPTH = 10; -export const voiceCreditBalance = BigInt(100); -export const duration = 30; -export const maxValues = { - maxUsers: 25, - maxMessages: 25, - maxVoteOptions: 25, -}; -export const treeDepths = { - intStateTreeDepth: 2, - messageTreeDepth: 2, - messageTreeSubDepth: 1, - voteOptionTreeDepth: 2, -}; -export const messageBatchSize = 5; diff --git a/packages/hardhat/maci-ts/circuits/__tests__/utils/types.ts b/packages/hardhat/maci-ts/circuits/__tests__/utils/types.ts deleted file mode 100644 index bacbeb6..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/utils/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { type SignalValueType } from "circomkit/dist/types/circuit"; - -/** - * Circuit inputs for testing the MessageValidator circuit - */ -export interface IMessageValidatorCircuitInputs { - stateTreeIndex: SignalValueType; - numSignUps: SignalValueType; - voteOptionIndex: SignalValueType; - maxVoteOptions: SignalValueType; - originalNonce: SignalValueType; - nonce: SignalValueType; - cmd: SignalValueType; - pubKey: SignalValueType; - sigR8: SignalValueType; - sigS: SignalValueType; - currentVoiceCreditBalance: SignalValueType; - currentVotesForOption: SignalValueType; - voteWeight: SignalValueType; - slTimestamp: SignalValueType; - pollEndTimestamp: SignalValueType; -} diff --git a/packages/hardhat/maci-ts/circuits/__tests__/utils/utils.ts b/packages/hardhat/maci-ts/circuits/__tests__/utils/utils.ts deleted file mode 100644 index d37dbfb..0000000 --- a/packages/hardhat/maci-ts/circuits/__tests__/utils/utils.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Circomkit, type WitnessTester, type CircomkitConfig } from "circomkit"; - -import fs from "fs"; -import path from "path"; - -const configFilePath = path.resolve(__dirname, "..", "..", "..", "circomkit.json"); -const config = JSON.parse(fs.readFileSync(configFilePath, "utf-8")) as CircomkitConfig; - -export const circomkitInstance = new Circomkit({ - ...config, - verbose: false, -}); - -/** - * Convert a string to a bigint - * @param s - the string to convert - * @returns the bigint representation of the string - */ -export const str2BigInt = (s: string): bigint => BigInt(parseInt(Buffer.from(s).toString("hex"), 16)); - -/** - * Generate a random number within a certain threshold - * @param upper - the upper bound - * @returns the random index - */ -export const generateRandomIndex = (upper: number): number => Math.floor(Math.random() * (upper - 1)); - -// @note thanks https://github.com/Rate-Limiting-Nullifier/circom-rln/blob/main/test/utils.ts -// for the code below (modified version) -/** - * Get a signal from the circuit - * @param circuit - the circuit object - * @param witness - the witness - * @param name - the name of the signal - * @returns the signal value - */ -export const getSignal = async (tester: WitnessTester, witness: bigint[], name: string): Promise => { - const prefix = "main"; - // E.g. the full name of the signal "root" is "main.root" - // You can look up the signal names using `circuit.getDecoratedOutput(witness))` - const signalFullName = `${prefix}.${name}`; - - const out = await tester.readWitness(witness, [signalFullName]); - return BigInt(out[signalFullName]); -}; diff --git a/packages/hardhat/maci-ts/circuits/compile.ts b/packages/hardhat/maci-ts/circuits/compile.ts deleted file mode 100644 index f88a2ac..0000000 --- a/packages/hardhat/maci-ts/circuits/compile.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { type CircomkitConfig, type CircuitConfig, Circomkit } from "circomkit"; - -import { execFileSync } from "child_process"; -import fs from "fs"; -import path from "path"; - -import type { CircuitConfigWithName } from "./types"; - -/** - * Compile MACI's circuits using circomkit - * and setup the dir structure - * @param cWitness - whether to compile to the c witness generator - * or not - * @param outputPath - the path to the output folder - * @returns the build directory - */ -export const compileCircuits = async (cWitness?: boolean, outputPath?: string): Promise => { - // read circomkit config files - const configFilePath = path.resolve(__dirname, "..", "circomkit.json"); - const circomKitConfig = JSON.parse(fs.readFileSync(configFilePath, "utf-8")) as CircomkitConfig; - const circuitsConfigPath = path.resolve(__dirname, "..", "circom", "circuits.json"); - const circuitsConfigContent = JSON.parse(fs.readFileSync(circuitsConfigPath, "utf-8")) as unknown as Record< - string, - CircuitConfig - >; - const circuitsConfigs: CircuitConfigWithName[] = Object.entries(circuitsConfigContent).map(([name, config]) => ({ - name, - ...config, - })); - - // generate the absolute path to the output folder - const outputPathUpdated = outputPath ? path.resolve(outputPath) : undefined; - // set the config based on whether to compile the c witness or no - if (cWitness) { - circomKitConfig.cWitness = true; - } else { - circomKitConfig.cWitness = false; - } - - // update the build directory if we have an output path - if (outputPathUpdated) { - circomKitConfig.dirBuild = outputPathUpdated; - circomKitConfig.dirPtau = outputPathUpdated; - } - - // create an instance of circomkit with a custom config - const circomkitInstance = new Circomkit({ - ...circomKitConfig, - verbose: false, - }); - - // loop through each circuit config and compile them - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < circuitsConfigs.length; i += 1) { - const circuit = circuitsConfigs[i]; - // eslint-disable-next-line no-console - console.log(`Compiling ${circuit.name}...`); - - // eslint-disable-next-line no-await-in-loop - const outPath = await circomkitInstance.compile(circuit.name, circuit); - - // if the circuit is compiled with a c witness, then let's run make in the directory - if (cWitness) { - try { - // build - execFileSync("bash", ["-c", `cd ${outPath}/${circuit.name}_cpp && make`]); - } catch (error) { - throw new Error(`Failed to compile the c witness for ${circuit.name}`); - } - } - } - - // return the build directory - return circomKitConfig.dirBuild; -}; - -if (require.main === module) { - (async () => { - // check if we want to compile the c witness or not - const cWitness = process.argv.includes("--cWitness"); - // the output path is the next argument after the --outPath flag - // and is not mandatory - const outputPathIndex = process.argv.indexOf("--outPath"); - if (outputPathIndex === -1) { - await compileCircuits(cWitness); - } else { - const outputFolder = process.argv[outputPathIndex + 1]; - await compileCircuits(cWitness, outputFolder); - } - })(); -} diff --git a/packages/hardhat/maci-ts/circuits/genZkeys.ts b/packages/hardhat/maci-ts/circuits/genZkeys.ts deleted file mode 100644 index d56a31f..0000000 --- a/packages/hardhat/maci-ts/circuits/genZkeys.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { type CircomkitConfig, type CircuitConfig, Circomkit } from "circomkit"; - -import fs from "fs"; -import path from "path"; - -import type { CircuitConfigWithName } from "./types"; - -import { cleanThreads } from "./utils"; - -/** - * Generate the zkeys for MACI's circuits using circomkit - * @dev This should only be used for testing purposes, or to generate the genesis zkey - * for a new trusted setup ceremony. Never use zkeys that have not undergone a ceremony - * in production. - * @param outPath - the path to the output folder - */ -export const generateZkeys = async (outputPath?: string): Promise => { - // read circomkit config files - const configFilePath = path.resolve(__dirname, "..", "circomkit.json"); - const circomKitConfig = JSON.parse(fs.readFileSync(configFilePath, "utf-8")) as CircomkitConfig; - const circuitsConfigPath = path.resolve(__dirname, "..", "circom", "circuits.json"); - const circuitsConfigContent = JSON.parse(fs.readFileSync(circuitsConfigPath, "utf-8")) as unknown as Record< - string, - CircuitConfig - >; - const circuitsConfigs: CircuitConfigWithName[] = Object.entries(circuitsConfigContent).map(([name, config]) => ({ - name, - ...config, - })); - - const outPath = outputPath ? path.resolve(outputPath) : undefined; - // update the output directory - if (outPath) { - circomKitConfig.dirBuild = outPath; - circomKitConfig.dirPtau = outPath; - } - - const circomkitInstance = new Circomkit({ - ...circomKitConfig, - verbose: false, - }); - - // loop through each circuit config and compile them - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < circuitsConfigs.length; i += 1) { - const circuit = circuitsConfigs[i]; - - // eslint-disable-next-line no-console - console.log(`Generating zKey for ${circuit.name}...`); - - // eslint-disable-next-line no-await-in-loop - const { proverKeyPath } = await circomkitInstance.setup(circuit.name); - // rename the zkey - const zkeyPath = path.resolve(circomKitConfig.dirBuild, circuit.name, `${circuit.name}.0.zkey`); - fs.renameSync(proverKeyPath, zkeyPath); - } - - // clean up the threads so we can exit - await cleanThreads(); -}; - -if (require.main === module) { - (async () => { - const outputPathIndex = process.argv.indexOf("--outPath"); - if (outputPathIndex === -1) { - await generateZkeys(); - } else { - const outputFolder = process.argv[process.argv.indexOf("--outPath") + 1]; - await generateZkeys(outputFolder); - } - })(); -} diff --git a/packages/hardhat/maci-ts/circuits/index.ts b/packages/hardhat/maci-ts/circuits/index.ts deleted file mode 100644 index d631e38..0000000 --- a/packages/hardhat/maci-ts/circuits/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { genProof, verifyProof, extractVk } from "./proofs"; -export { cleanThreads } from "./utils"; -export type { ISnarkJSVerificationKey } from "./types"; diff --git a/packages/hardhat/maci-ts/circuits/proofs.ts b/packages/hardhat/maci-ts/circuits/proofs.ts deleted file mode 100644 index d3b88ea..0000000 --- a/packages/hardhat/maci-ts/circuits/proofs.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { stringifyBigInts } from "../crypto"; -import { - zKey, - groth16, - type FullProveResult, - type PublicSignals, - type Groth16Proof, - type ISnarkJSVerificationKey, -} from "snarkjs"; - -import { execFileSync } from "child_process"; -import fs from "fs"; -import { tmpdir } from "os"; -import path from "path"; - -import type { IGenProofOptions } from "./types"; - -import { cleanThreads, isArm } from "./utils"; - -/** - * Generate a zk-SNARK proof - * @dev if running on a intel chip we use rapidsnark for - * speed - on the other hand if running on ARM we need to use - * snark and a WASM witness - * @param inputs - the inputs to the circuit - * @param zkeyPath - the path to the zkey - * @param useWasm - whether we want to use the wasm witness or not - * @param rapidsnarkExePath - the path to the rapidnsark binary - * @param witnessExePath - the path to the compiled witness binary - * @param wasmPath - the path to the wasm witness - * @param silent - whether we want to print to the console or not - * @returns the zk-SNARK proof and public signals - */ -export const genProof = async ({ - inputs, - zkeyPath, - useWasm, - rapidsnarkExePath, - witnessExePath, - wasmPath, - silent = false, -}: IGenProofOptions): Promise => { - // if we want to use a wasm witness we use snarkjs - if (useWasm) { - if (!wasmPath) { - throw new Error("wasmPath must be specified"); - } - - if (!fs.existsSync(wasmPath)) { - throw new Error(`wasmPath ${wasmPath} does not exist`); - } - - const { proof, publicSignals } = await groth16.fullProve(inputs, wasmPath, zkeyPath); - return { proof, publicSignals }; - } - - if (isArm()) { - throw new Error("To use rapidnsnark you currently need to be running on an intel chip"); - } - - // intel chip flow (use rapidnsark) - // Create tmp directory - const tmpPath = path.resolve(tmpdir(), `tmp-${Date.now()}`); - fs.mkdirSync(tmpPath, { recursive: true }); - - const inputJsonPath = path.resolve(tmpPath, "input.json"); - const outputWtnsPath = path.resolve(tmpPath, "output.wtns"); - const proofJsonPath = path.resolve(tmpPath, "proof.json"); - const publicJsonPath = path.resolve(tmpPath, "public.json"); - - // Write input.json - const jsonData = JSON.stringify(stringifyBigInts(inputs)); - fs.writeFileSync(inputJsonPath, jsonData); - - // Generate the witness - execFileSync(witnessExePath!, [inputJsonPath, outputWtnsPath], { stdio: silent ? "ignore" : "pipe" }); - - if (!fs.existsSync(outputWtnsPath)) { - throw new Error(`Error executing ${witnessExePath} ${inputJsonPath} ${outputWtnsPath}`); - } - - // Generate the proof - execFileSync(rapidsnarkExePath!, [zkeyPath, outputWtnsPath, proofJsonPath, publicJsonPath], { - stdio: silent ? "ignore" : "pipe", - }); - - if (!fs.existsSync(proofJsonPath)) { - throw new Error( - `Error executing ${rapidsnarkExePath} ${zkeyPath} ${outputWtnsPath} ${proofJsonPath} ${publicJsonPath}`, - ); - } - - // Read the proof and public inputs - const proof = JSON.parse(fs.readFileSync(proofJsonPath).toString()) as Groth16Proof; - const publicSignals = JSON.parse(fs.readFileSync(publicJsonPath).toString()) as PublicSignals; - - // remove all artifacts - [proofJsonPath, publicJsonPath, inputJsonPath, outputWtnsPath].forEach(f => { - if (fs.existsSync(f)) { - fs.unlinkSync(f); - } - }); - - // remove tmp directory - fs.rmdirSync(tmpPath); - - return { proof, publicSignals }; -}; - -/** - * Verify a zk-SNARK proof using snarkjs - * @param publicInputs - the public inputs to the circuit - * @param proof - the proof - * @param vk - the verification key - * @returns whether the proof is valid or not - */ -export const verifyProof = async ( - publicInputs: PublicSignals, - proof: Groth16Proof, - vk: ISnarkJSVerificationKey, -): Promise => { - const isValid = await groth16.verify(vk, publicInputs, proof); - await cleanThreads(); - return isValid; -}; - -/** - * Extract the Verification Key from a zKey - * @param zkeyPath - the path to the zKey - * @returns the verification key - */ -export const extractVk = async (zkeyPath: string): Promise => { - const vk = await zKey.exportVerificationKey(zkeyPath); - await cleanThreads(); - return vk; -}; diff --git a/packages/hardhat/maci-ts/circuits/types.ts b/packages/hardhat/maci-ts/circuits/types.ts deleted file mode 100644 index 8929a14..0000000 --- a/packages/hardhat/maci-ts/circuits/types.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { CircuitConfig } from "circomkit"; -import type { CircuitInputs } from "../core"; -import type { ISnarkJSVerificationKey } from "snarkjs"; - -/** - * Parameters for the genProof function - */ -export interface IGenProofOptions { - inputs: CircuitInputs; - zkeyPath: string; - useWasm?: boolean; - rapidsnarkExePath?: string; - witnessExePath?: string; - wasmPath?: string; - silent?: boolean; -} - -/** - * Inputs for circuit ProcessMessages - */ -export interface IProcessMessagesInputs { - inputHash: bigint; - packedVals: bigint; - pollEndTimestamp: bigint; - msgRoot: bigint; - msgs: bigint[]; - msgSubrootPathElements: bigint[][]; - coordPrivKey: bigint; - coordPubKey: [bigint, bigint]; - encPubKeys: bigint[]; - currentStateRoot: bigint; - currentStateLeaves: bigint[]; - currentStateLeavesPathElements: bigint[][]; - currentSbCommitment: bigint; - currentSbSalt: bigint; - newSbCommitment: bigint; - newSbSalt: bigint; - currentBallotRoot: bigint; - currentBallots: bigint[]; - currentBallotsPathElements: bigint[][]; - currentVoteWeights: bigint[]; - currentVoteWeightsPathElements: bigint[][]; -} - -/** - * Inputs for circuit TallyVotes - */ -export interface ITallyVotesInputs { - stateRoot: bigint; - ballotRoot: bigint; - sbSalt: bigint; - packedVals: bigint; - sbCommitment: bigint; - currentTallyCommitment: bigint; - newTallyCommitment: bigint; - inputHash: bigint; - ballots: bigint[]; - ballotPathElements: bigint[]; - votes: bigint[][]; - currentResults: bigint[]; - currentResultsRootSalt: bigint; - currentSpentVoiceCreditSubtotal: bigint; - currentSpentVoiceCreditSubtotalSalt: bigint; - currentPerVOSpentVoiceCredits: bigint[]; - currentPerVOSpentVoiceCreditsRootSalt: bigint; - newResultsRootSalt: bigint; - newPerVOSpentVoiceCreditsRootSalt: bigint; - newSpentVoiceCreditSubtotalSalt: bigint; -} - -/** - * Extend CircuitConfig type to include the name of the circuit - */ -export interface CircuitConfigWithName extends CircuitConfig { - name: string; -} - -export type { ISnarkJSVerificationKey }; diff --git a/packages/hardhat/maci-ts/circuits/types/snarkjs.d.ts b/packages/hardhat/maci-ts/circuits/types/snarkjs.d.ts deleted file mode 100644 index 19cb66a..0000000 --- a/packages/hardhat/maci-ts/circuits/types/snarkjs.d.ts +++ /dev/null @@ -1,50 +0,0 @@ -declare module "snarkjs" { - export type NumericString = string; - export type PublicSignals = Record; - export type BigNumberish = number | string | bigint; - - export interface ISnarkJSVerificationKey { - protocol: BigNumberish; - curve: BigNumberish; - nPublic: BigNumberish; - vk_alpha_1: BigNumberish[]; - vk_beta_2: BigNumberish[][]; - vk_gamma_2: BigNumberish[][]; - vk_delta_2: BigNumberish[][]; - vk_alphabeta_12: BigNumberish[][][]; - IC: BigNumberish[][]; - } - - export interface FullProveResult { - proof: Groth16Proof; - publicSignals: PublicSignals; - } - - export interface Groth16Proof { - pi_a: NumericString[]; - pi_b: NumericString[][]; - pi_c: NumericString[]; - protocol: string; - curve: string; - } - - export namespace zKey { - function exportVerificationKey(zkeyName: string, logger?: unknown): Promise; - } - - export namespace groth16 { - function verify( - vk_verifier: ISnarkJSVerificationKey, - publicSignals: PublicSignals, - proof: Groth16Proof, - logger?: unknown, - ): Promise; - - function fullProve( - input: PublicSignals, - wasmFile: string, - zkeyFileName: string, - logger?: unknown, - ): Promise; - } -} diff --git a/packages/hardhat/maci-ts/circuits/utils.ts b/packages/hardhat/maci-ts/circuits/utils.ts deleted file mode 100644 index 72a895d..0000000 --- a/packages/hardhat/maci-ts/circuits/utils.ts +++ /dev/null @@ -1,35 +0,0 @@ -import os from "os"; - -declare global { - interface ITerminatable { - terminate: () => Promise; - } - - // eslint-disable-next-line vars-on-top, no-var, camelcase - var curve_bn128: ITerminatable | undefined; - - // eslint-disable-next-line vars-on-top, no-var, camelcase - var curve_bls12381: ITerminatable | undefined; -} - -/** - * Check if we are running on an arm chip - * @returns whether we are running on an arm chip - */ -export const isArm = (): boolean => os.arch().includes("arm"); - -/* - * https://github.com/iden3/snarkjs/issues/152 - * Need to cleanup the threads to avoid stalling - */ -export const cleanThreads = async (): Promise => { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!globalThis) { - return; - } - - const curves = ["curve_bn128", "curve_bls12381"]; - await Promise.all( - curves.map(curve => globalThis[curve as "curve_bn128" | "curve_bls12381"]?.terminate()).filter(Boolean), - ); -}; diff --git a/packages/hardhat/maci-ts/core/MaciState.ts b/packages/hardhat/maci-ts/core/MaciState.ts deleted file mode 100644 index f0db9bf..0000000 --- a/packages/hardhat/maci-ts/core/MaciState.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { type PubKey, type Keypair, StateLeaf, blankStateLeaf } from "../domainobjs"; - -import type { IJsonMaciState, IJsonPoll, IMaciState, MaxValues, TreeDepths } from "./utils/types"; - -import { Poll } from "./Poll"; -import { STATE_TREE_ARITY } from "./utils/constants"; - -/** - * A representation of the MACI contract. - */ -export class MaciState implements IMaciState { - // a MaciState can hold multiple polls - polls: Map = new Map(); - - // the leaves of the state tree - stateLeaves: StateLeaf[] = []; - - // how deep the state tree is - stateTreeDepth: number; - - numSignUps = 0; - - // to keep track if a poll is currently being processed - pollBeingProcessed?: boolean; - - currentPollBeingProcessed?: bigint; - - /** - * Constructs a new MaciState object. - * @param stateTreeDepth - The depth of the state tree. - */ - constructor(stateTreeDepth: number) { - this.stateTreeDepth = stateTreeDepth; - - // we put a blank state leaf to prevent a DoS attack - this.stateLeaves.push(blankStateLeaf); - // we need to increase the number of signups by one given - // that we already added the blank leaf - this.numSignUps += 1; - } - - /** - * Sign up a user with the given public key, initial voice credit balance, and timestamp. - * @param pubKey - The public key of the user. - * @param initialVoiceCreditBalance - The initial voice credit balance of the user. - * @param timestamp - The timestamp of the sign-up. - * @returns The index of the newly signed-up user in the state tree. - */ - signUp(pubKey: PubKey, initialVoiceCreditBalance: bigint, timestamp: bigint): number { - this.numSignUps += 1; - const stateLeaf = new StateLeaf(pubKey, initialVoiceCreditBalance, timestamp); - - return this.stateLeaves.push(stateLeaf.copy()) - 1; - } - - /** - * Deploy a new poll with the given parameters. - * @param pollEndTimestamp - The Unix timestamp at which the poll ends. - * @param maxValues - The maximum number of values for each vote option. - * @param treeDepths - The depths of the tree. - * @param messageBatchSize - The batch size for processing messages. - * @param coordinatorKeypair - The keypair of the MACI round coordinator. - * @returns The index of the newly deployed poll. - */ - deployPoll( - pollEndTimestamp: bigint, - maxValues: MaxValues, - treeDepths: TreeDepths, - messageBatchSize: number, - coordinatorKeypair: Keypair, - ): bigint { - const poll: Poll = new Poll( - pollEndTimestamp, - coordinatorKeypair, - treeDepths, - { - messageBatchSize, - tallyBatchSize: STATE_TREE_ARITY ** treeDepths.intStateTreeDepth, - }, - maxValues, - this, - ); - - this.polls.set(BigInt(this.polls.size), poll); - return BigInt(this.polls.size - 1); - } - - /** - * Deploy a null poll. - */ - deployNullPoll(): void { - this.polls.set(BigInt(this.polls.size), null as unknown as Poll); - } - - /** - * Create a deep copy of the MaciState object. - * @returns A new instance of the MaciState object with the same properties. - */ - copy = (): MaciState => { - const copied = new MaciState(this.stateTreeDepth); - - copied.stateLeaves = this.stateLeaves.map((x: StateLeaf) => x.copy()); - - copied.polls = new Map(Array.from(this.polls, ([key, value]) => [key, value.copy()])); - - return copied; - }; - - /** - * Check if the MaciState object is equal to another MaciState object. - * @param m - The MaciState object to compare. - * @returns True if the two MaciState objects are equal, false otherwise. - */ - equals = (m: MaciState): boolean => { - const result = - this.stateTreeDepth === m.stateTreeDepth && - this.polls.size === m.polls.size && - this.stateLeaves.length === m.stateLeaves.length; - - if (!result) { - return false; - } - - for (let i = 0; i < this.polls.size; i += 1) { - if (!this.polls.get(BigInt(i))?.equals(m.polls.get(BigInt(i))!)) { - return false; - } - } - for (let i = 0; i < this.stateLeaves.length; i += 1) { - if (!this.stateLeaves[i].equals(m.stateLeaves[i])) { - return false; - } - } - - return true; - }; - - /** - * Serialize the MaciState object to a JSON object. - * @returns A JSON object representing the MaciState object. - */ - toJSON(): IJsonMaciState { - return { - stateTreeDepth: this.stateTreeDepth, - polls: Array.from(this.polls.values()).map(poll => poll.toJSON()), - stateLeaves: this.stateLeaves.map(leaf => leaf.toJSON()), - pollBeingProcessed: Boolean(this.pollBeingProcessed), - currentPollBeingProcessed: this.currentPollBeingProcessed ? this.currentPollBeingProcessed.toString() : "", - numSignUps: this.numSignUps, - }; - } - - /** - * Create a new MaciState object from a JSON object. - * @param json - The JSON object representing the MaciState object. - * @returns A new instance of the MaciState object with the properties from the JSON object. - */ - static fromJSON(json: IJsonMaciState): MaciState { - const maciState = new MaciState(json.stateTreeDepth); - - // assign the json values to the new instance - maciState.stateLeaves = json.stateLeaves.map(leaf => StateLeaf.fromJSON(leaf)); - maciState.pollBeingProcessed = json.pollBeingProcessed; - maciState.currentPollBeingProcessed = BigInt(json.currentPollBeingProcessed); - maciState.numSignUps = json.numSignUps; - - // re-generate the polls and set the maci state reference - maciState.polls = new Map( - json.polls.map((jsonPoll: IJsonPoll, index) => [BigInt(index), Poll.fromJSON(jsonPoll, maciState)]), - ); - - return maciState; - } -} diff --git a/packages/hardhat/maci-ts/core/Poll.ts b/packages/hardhat/maci-ts/core/Poll.ts deleted file mode 100644 index 136f56d..0000000 --- a/packages/hardhat/maci-ts/core/Poll.ts +++ /dev/null @@ -1,1463 +0,0 @@ -import { - IncrementalQuinTree, - genRandomSalt, - SNARK_FIELD_SIZE, - NOTHING_UP_MY_SLEEVE, - hashLeftRight, - hash3, - hash5, - sha256Hash, - stringifyBigInts, - genTreeCommitment, -} from "../crypto"; -import { - PCommand, - TCommand, - Keypair, - Ballot, - PubKey, - PrivKey, - Message, - blankStateLeaf, - type ICommand, - type StateLeaf, - type IMessageContractParams, - type IJsonCommand, - type IJsonPCommand, - type IJsonTCommand, - blankStateLeafHash, -} from "../domainobjs"; - -import assert from "assert"; - -import type { MaciState } from "./MaciState"; -import type { - CircuitInputs, - TreeDepths, - MaxValues, - BatchSizes, - IPoll, - IJsonPoll, - IProcessMessagesOutput, - ITallyCircuitInputs, - IProcessMessagesCircuitInputs, -} from "./utils/types"; -import type { PathElements } from "../crypto"; - -import { STATE_TREE_ARITY, MESSAGE_TREE_ARITY } from "./utils/constants"; -import { ProcessMessageErrors, ProcessMessageError } from "./utils/errors"; -import { packTallyVotesSmallVals } from "./utils/utils"; - -/** - * A representation of the Poll contract. - */ -export class Poll implements IPoll { - // Note that we only store the PubKey on-chain while this class stores the - // Keypair for the sake of convenience - coordinatorKeypair: Keypair; - - treeDepths: TreeDepths; - - batchSizes: BatchSizes; - - maxValues: MaxValues; - - // the depth of the state tree - stateTreeDepth: number; - - pollEndTimestamp: bigint; - - ballots: Ballot[] = []; - - ballotTree?: IncrementalQuinTree; - - messages: Message[] = []; - - messageTree: IncrementalQuinTree; - - commands: ICommand[] = []; - - encPubKeys: PubKey[] = []; - - stateCopied = false; - - stateLeaves: StateLeaf[] = [blankStateLeaf]; - - stateTree?: IncrementalQuinTree; - - // For message processing - numBatchesProcessed = 0; - - currentMessageBatchIndex?: number; - - maciStateRef: MaciState; - - pollId: bigint; - - sbSalts: Record = {}; - - resultRootSalts: Record = {}; - - preVOSpentVoiceCreditsRootSalts: Record = {}; - - spentVoiceCreditSubtotalSalts: Record = {}; - - // For vote tallying - tallyResult: bigint[] = []; - - perVOSpentVoiceCredits: bigint[] = []; - - numBatchesTallied = 0; - - totalSpentVoiceCredits = 0n; - - // an empty ballot and its hash to be used as zero value in the - // ballot tree - emptyBallot: Ballot; - - emptyBallotHash?: bigint; - - // how many users signed up - private numSignups = 0n; - - /** - * Constructs a new Poll object. - * @param pollEndTimestamp - The Unix timestamp at which the poll ends. - * @param coordinatorKeypair - The keypair of the coordinator. - * @param treeDepths - The depths of the trees used in the poll. - * @param batchSizes - The sizes of the batches used in the poll. - * @param maxValues - The maximum values the MACI circuits can accept. - * @param maciStateRef - The reference to the MACI state. - */ - constructor( - pollEndTimestamp: bigint, - coordinatorKeypair: Keypair, - treeDepths: TreeDepths, - batchSizes: BatchSizes, - maxValues: MaxValues, - maciStateRef: MaciState, - ) { - this.pollEndTimestamp = pollEndTimestamp; - this.coordinatorKeypair = coordinatorKeypair; - this.treeDepths = treeDepths; - this.batchSizes = batchSizes; - this.maxValues = maxValues; - this.maciStateRef = maciStateRef; - this.pollId = BigInt(maciStateRef.polls.size); - this.stateTreeDepth = maciStateRef.stateTreeDepth; - - this.messageTree = new IncrementalQuinTree( - this.treeDepths.messageTreeDepth, - NOTHING_UP_MY_SLEEVE, - MESSAGE_TREE_ARITY, - hash5, - ); - - this.tallyResult = new Array(this.maxValues.maxVoteOptions).fill(0n) as bigint[]; - this.perVOSpentVoiceCredits = new Array(this.maxValues.maxVoteOptions).fill(0n) as bigint[]; - - // we put a blank state leaf to prevent a DoS attack - this.emptyBallot = Ballot.genBlankBallot(this.maxValues.maxVoteOptions, treeDepths.voteOptionTreeDepth); - this.ballots.push(this.emptyBallot); - } - - /** - * Update a Poll with data from MaciState. - * This is the step where we copy the state from the MaciState instance, - * and set the number of signups we have so far. - */ - updatePoll = (numSignups: bigint): void => { - // there might be occasions where we fetch logs after new signups have been made - // logs are fetched (and MaciState/Poll created locally) after stateAq have been - // merged in. If someone signs up after that and we fetch that record - // then we won't be able to verify the processing on chain as the data will - // not match. For this, we must only copy up to the number of signups - - // Copy the state tree, ballot tree, state leaves, and ballot leaves - - // start by setting the number of signups - this.setNumSignups(numSignups); - // copy up to numSignups state leaves - this.stateLeaves = this.maciStateRef.stateLeaves.slice(0, Number(this.numSignups)).map(x => x.copy()); - - // create a new state tree - this.stateTree = new IncrementalQuinTree(this.stateTreeDepth, blankStateLeafHash, STATE_TREE_ARITY, hash5); - // add all leaves - this.stateLeaves.forEach(stateLeaf => { - this.stateTree?.insert(stateLeaf.hash()); - }); - - // Create as many ballots as state leaves - this.emptyBallotHash = this.emptyBallot.hash(); - this.ballotTree = new IncrementalQuinTree(this.stateTreeDepth, this.emptyBallotHash, STATE_TREE_ARITY, hash5); - this.ballotTree.insert(this.emptyBallot.hash()); - - // we fill the ballotTree with empty ballots hashes to match the number of signups in the tree - while (this.ballots.length < this.stateLeaves.length) { - this.ballotTree.insert(this.emptyBallotHash); - this.ballots.push(this.emptyBallot); - } - - this.stateCopied = true; - }; - - /** - * Process one message. - * @param message - The message to process. - * @param encPubKey - The public key associated with the encryption private key. - * @returns A number of variables which will be used in the zk-SNARK circuit. - */ - processMessage = (message: Message, encPubKey: PubKey, qv = true): IProcessMessagesOutput => { - try { - // Decrypt the message - const sharedKey = Keypair.genEcdhSharedKey(this.coordinatorKeypair.privKey, encPubKey); - - const { command, signature } = PCommand.decrypt(message, sharedKey); - - const stateLeafIndex = command.stateIndex; - - // If the state tree index in the command is invalid, do nothing - if ( - stateLeafIndex >= BigInt(this.ballots.length) || - stateLeafIndex < 1n || - stateLeafIndex >= BigInt(this.stateTree?.nextIndex || -1) - ) { - throw new ProcessMessageError(ProcessMessageErrors.InvalidStateLeafIndex); - } - - // The user to update (or not) - const stateLeaf = this.stateLeaves[Number(stateLeafIndex)]; - - // The ballot to update (or not) - const ballot = this.ballots[Number(stateLeafIndex)]; - - // If the signature is invalid, do nothing - if (!command.verifySignature(signature, stateLeaf.pubKey)) { - throw new ProcessMessageError(ProcessMessageErrors.InvalidSignature); - } - - // If the nonce is invalid, do nothing - if (command.nonce !== ballot.nonce + 1n) { - throw new ProcessMessageError(ProcessMessageErrors.InvalidNonce); - } - - // If the vote option index is invalid, do nothing - if (command.voteOptionIndex < 0n || command.voteOptionIndex >= BigInt(this.maxValues.maxVoteOptions)) { - throw new ProcessMessageError(ProcessMessageErrors.InvalidVoteOptionIndex); - } - - const voteOptionIndex = Number(command.voteOptionIndex); - const originalVoteWeight = ballot.votes[voteOptionIndex]; - - // the voice credits left are: - // voiceCreditsBalance (how many the user has) + - // voiceCreditsPreviouslySpent (the original vote weight for this option) ** 2 - - // command.newVoteWeight ** 2 (the new vote weight squared) - // basically we are replacing the previous vote weight for this - // particular vote option with the new one - // but we need to ensure that we are not going >= balance - // @note that above comment is valid for quadratic voting - // for non quadratic voting, we simply remove the exponentiation - const voiceCreditsLeft = qv - ? stateLeaf.voiceCreditBalance + - originalVoteWeight * originalVoteWeight - - command.newVoteWeight * command.newVoteWeight - : stateLeaf.voiceCreditBalance + originalVoteWeight - command.newVoteWeight; - - // If the remaining voice credits is insufficient, do nothing - if (voiceCreditsLeft < 0n) { - throw new ProcessMessageError(ProcessMessageErrors.InsufficientVoiceCredits); - } - - // Deep-copy the state leaf and update its attributes - const newStateLeaf = stateLeaf.copy(); - newStateLeaf.voiceCreditBalance = voiceCreditsLeft; - // if the key changes, this is effectively a key-change message too - newStateLeaf.pubKey = command.newPubKey.copy(); - - // Deep-copy the ballot and update its attributes - const newBallot = ballot.copy(); - // increase the nonce - newBallot.nonce += 1n; - // we change the vote for this exact vote option - newBallot.votes[voteOptionIndex] = command.newVoteWeight; - - // calculate the path elements for the state tree given the original state tree (before any changes) - // changes could effectively be made by this new vote - either a key change or vote change - // would result in a different state leaf - const originalStateLeafPathElements = this.stateTree?.genProof(Number(stateLeafIndex)).pathElements; - // calculate the path elements for the ballot tree given the original ballot tree (before any changes) - // changes could effectively be made by this new ballot - const originalBallotPathElements = this.ballotTree?.genProof(Number(stateLeafIndex)).pathElements; - - // create a new quinary tree where we insert the votes of the origin (up until this message is processed) ballot - const vt = new IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, STATE_TREE_ARITY, hash5); - for (let i = 0; i < this.ballots[0].votes.length; i += 1) { - vt.insert(ballot.votes[i]); - } - // calculate the path elements for the vote option tree given the original vote option tree (before any changes) - const originalVoteWeightsPathElements = vt.genProof(voteOptionIndex).pathElements; - // we return the data which is then to be used in the processMessage circuit - // to generate a proof of processing - return { - stateLeafIndex: Number(stateLeafIndex), - newStateLeaf, - originalStateLeaf: stateLeaf.copy(), - originalStateLeafPathElements, - originalVoteWeight, - originalVoteWeightsPathElements, - newBallot, - originalBallot: ballot.copy(), - originalBallotPathElements, - command, - }; - } catch (e) { - if (e instanceof ProcessMessageError) { - throw e; - } else { - throw new ProcessMessageError(ProcessMessageErrors.FailedDecryption); - } - } - }; - - /** - * Top up the voice credit balance of a user. - * @param message - The message to top up the voice credit balance - */ - topupMessage = (message: Message): void => { - assert(message.msgType === 2n, "A Topup message must have msgType 2"); - - message.data.forEach(d => { - assert(d < SNARK_FIELD_SIZE, "The message data is not in the correct range"); - }); - - const padKey = new PubKey([ - BigInt("10457101036533406547632367118273992217979173478358440826365724437999023779287"), - BigInt("19824078218392094440610104313265183977899662750282163392862422243483260492317"), - ]); - - // save the message - this.messages.push(message); - // save the pad key - this.encPubKeys.push(padKey); - // insert the message into the message tree - this.messageTree.insert(message.hash(padKey)); - - // we create a topup command and save it - const command = new TCommand(message.data[0], message.data[1], BigInt(this.pollId)); - this.commands.push(command as ICommand); - }; - - /** - * Inserts a Message and the corresponding public key used to generate the - * ECDH shared key which was used to encrypt said message. - * @param message - The message to insert - * @param encPubKey - The public key used to encrypt the message - */ - publishMessage = (message: Message, encPubKey: PubKey): void => { - assert(message.msgType === 1n, "A vote or key change message must have msgType 1"); - assert( - encPubKey.rawPubKey[0] < SNARK_FIELD_SIZE && encPubKey.rawPubKey[1] < SNARK_FIELD_SIZE, - "The public key is not in the correct range", - ); - - message.data.forEach(d => { - assert(d < SNARK_FIELD_SIZE, "The message data is not in the correct range"); - }); - - // store the encryption pub key - this.encPubKeys.push(encPubKey); - // store the message locally - this.messages.push(message); - // add the message hash to the message tree - this.messageTree.insert(message.hash(encPubKey)); - - // Decrypt the message and store the Command - // step 1. we generate the shared key - const sharedKey = Keypair.genEcdhSharedKey(this.coordinatorKeypair.privKey, encPubKey); - try { - // step 2. we decrypt it - const { command } = PCommand.decrypt(message, sharedKey); - // step 3. we store it in the commands array - this.commands.push(command as ICommand); - } catch (e) { - // if there is an error we store an empty command - const keyPair = new Keypair(); - const command = new PCommand(0n, keyPair.pubKey, 0n, 0n, 0n, 0n, 0n); - this.commands.push(command as ICommand); - } - }; - - /** - * This method checks if there are any unprocessed messages in the Poll instance. - * @returns Returns true if the number of processed batches is - * less than the total number of batches, false otherwise. - */ - hasUnprocessedMessages = (): boolean => { - const batchSize = this.batchSizes.messageBatchSize; - - let totalBatches = this.messages.length <= batchSize ? 1 : Math.floor(this.messages.length / batchSize); - - if (this.messages.length > batchSize && this.messages.length % batchSize > 0) { - totalBatches += 1; - } - - return this.numBatchesProcessed < totalBatches; - }; - - /** - * Process _batchSize messages starting from the saved index. This - * function will process messages even if the number of messages is not an - * exact multiple of _batchSize. e.g. if there are 10 messages, index is - * 8, and _batchSize is 4, this function will only process the last two - * messages in this.messages, and finally update the zeroth state leaf. - * Note that this function will only process as many state leaves as there - * are ballots to prevent accidental inclusion of a new user after this - * poll has concluded. - * @param pollId The ID of the poll associated with the messages to - * process - * @param quiet - Whether to log errors or not - * @returns stringified circuit inputs - */ - processMessages = (pollId: bigint, qv = true, quiet = true): IProcessMessagesCircuitInputs => { - assert(this.hasUnprocessedMessages(), "No more messages to process"); - - const batchSize = this.batchSizes.messageBatchSize; - - if (this.numBatchesProcessed === 0) { - // The starting index of the batch of messages to process. - // Note that we process messages in reverse order. - // e.g if there are 8 messages and the batch size is 5, then - // the starting index should be 5. - assert( - this.currentMessageBatchIndex === undefined, - "The current message batch index should not be defined if this is the first batch", - ); - // Prevent other polls from being processed until this poll has - // been fully processed - this.maciStateRef.pollBeingProcessed = true; - this.maciStateRef.currentPollBeingProcessed = pollId; - } - - // Only allow one poll to be processed at a time - if (this.maciStateRef.pollBeingProcessed) { - assert(this.maciStateRef.currentPollBeingProcessed === pollId, "Another poll is currently being processed"); - } - - if (this.numBatchesProcessed === 0) { - const r = this.messages.length % batchSize; - - this.currentMessageBatchIndex = this.messages.length; - - // if there are messages - if (this.currentMessageBatchIndex > 0) { - if (r === 0) { - this.currentMessageBatchIndex -= batchSize; - } else { - this.currentMessageBatchIndex -= r; - } - } - - this.sbSalts[this.currentMessageBatchIndex] = 0n; - } - - // The starting index must be valid - assert(this.currentMessageBatchIndex! >= 0, "The starting index must be >= 0"); - assert(this.currentMessageBatchIndex! % batchSize === 0, "The starting index must be a multiple of the batch size"); - - // ensure we copy the state from MACI when we start processing the - // first batch - if (!this.stateCopied) { - throw new Error("You must update the poll with the correct data first"); - } - - // Generate circuit inputs - const circuitInputs = stringifyBigInts( - this.genProcessMessagesCircuitInputsPartial(this.currentMessageBatchIndex!), - ) as CircuitInputs; - - // we want to store the state leaves at this point in time - // and the path elements of the state tree - const currentStateLeaves: StateLeaf[] = []; - const currentStateLeavesPathElements: PathElements[] = []; - - // we want to store the ballots at this point in time - // and the path elements of the ballot tree - const currentBallots: Ballot[] = []; - const currentBallotsPathElements: PathElements[] = []; - - // we want to store the vote weights at this point in time - // and the path elements of the vote weight tree - const currentVoteWeights: bigint[] = []; - const currentVoteWeightsPathElements: PathElements[] = []; - - // loop through the batch of messages - for (let i = 0; i < batchSize; i += 1) { - // we process the messages in reverse order - const idx = this.currentMessageBatchIndex! + batchSize - i - 1; - assert(idx >= 0, "The message index must be >= 0"); - let message: Message; - let encPubKey: PubKey; - if (idx < this.messages.length) { - message = this.messages[idx]; - encPubKey = this.encPubKeys[idx]; - - // based on the message type we have to process it differently - switch (message.msgType) { - case 1n: - try { - // check if the command is valid - const r = this.processMessage(message, encPubKey, qv); - const index = r.stateLeafIndex!; - - // we add at position 0 the original data - currentStateLeaves.unshift(r.originalStateLeaf!); - currentBallots.unshift(r.originalBallot!); - currentVoteWeights.unshift(r.originalVoteWeight!); - currentVoteWeightsPathElements.unshift(r.originalVoteWeightsPathElements!); - currentStateLeavesPathElements.unshift(r.originalStateLeafPathElements!); - currentBallotsPathElements.unshift(r.originalBallotPathElements!); - - // update the state leaves with the new state leaf (result of processing the message) - this.stateLeaves[index] = r.newStateLeaf!.copy(); - - // we also update the state tree with the hash of the new state leaf - this.stateTree?.update(index, r.newStateLeaf!.hash()); - - // store the new ballot - this.ballots[index] = r.newBallot!; - // update the ballot tree - this.ballotTree?.update(index, r.newBallot!.hash()); - } catch (e) { - // if the error is not a ProcessMessageError we throw it and exit here - // otherwise we continue processing but add the default blank data instead of - // this invalid message - if (e instanceof ProcessMessageError) { - // if logging is enabled, and it's not the first message, print the error - if (!quiet && idx !== 0) { - // eslint-disable-next-line no-console - console.log(`Error at message index ${idx} - ${e.message}`); - } - - // @note we want to send the correct state leaf to the circuit - // even if a message is invalid - // this way if a message is invalid we can still generate a proof of processing - // we also want to prevent a DoS attack by a voter - // which sends a message that when force decrypted on the circuit - // results in a valid state index thus forcing the circuit to look - // for a valid state leaf, and failing to generate a proof - - // gen shared key - const sharedKey = Keypair.genEcdhSharedKey(this.coordinatorKeypair.privKey, encPubKey); - - // force decrypt it - const { command } = PCommand.decrypt(message, sharedKey, true); - - // cache state leaf index - const stateLeafIndex = command.stateIndex; - - // if the state leaf index is valid then use it - if (stateLeafIndex < this.stateLeaves.length) { - currentStateLeaves.unshift(this.stateLeaves[Number(stateLeafIndex)].copy()); - currentStateLeavesPathElements.unshift(this.stateTree!.genProof(Number(stateLeafIndex)).pathElements); - - // copy the ballot - const ballot = this.ballots[Number(stateLeafIndex)].copy(); - currentBallots.unshift(ballot); - currentBallotsPathElements.unshift(this.ballotTree!.genProof(Number(stateLeafIndex)).pathElements); - - // @note we check that command.voteOptionIndex is valid so < maxVoteOptions - // this might be unnecessary but we do it to prevent a possible DoS attack - // from voters who could potentially encrypt a message in such as way that - // when decrypted it results in a valid state leaf index but an invalid vote option index - if (command.voteOptionIndex < this.maxValues.maxVoteOptions) { - currentVoteWeights.unshift(ballot.votes[Number(command.voteOptionIndex)]); - - // create a new quinary tree and add all votes we have so far - const vt = new IncrementalQuinTree( - this.treeDepths.voteOptionTreeDepth, - 0n, - STATE_TREE_ARITY, - hash5, - ); - - // fill the vote option tree with the votes we have so far - for (let j = 0; j < this.ballots[0].votes.length; j += 1) { - vt.insert(ballot.votes[j]); - } - - // get the path elements for the first vote leaf - currentVoteWeightsPathElements.unshift(vt.genProof(Number(command.voteOptionIndex)).pathElements); - } else { - currentVoteWeights.unshift(ballot.votes[0]); - - // create a new quinary tree and add all votes we have so far - const vt = new IncrementalQuinTree( - this.treeDepths.voteOptionTreeDepth, - 0n, - STATE_TREE_ARITY, - hash5, - ); - - // fill the vote option tree with the votes we have so far - for (let j = 0; j < this.ballots[0].votes.length; j += 1) { - vt.insert(ballot.votes[j]); - } - - // get the path elements for the first vote leaf - currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements); - } - } else { - // just use state leaf index 0 - currentStateLeaves.unshift(this.stateLeaves[0].copy()); - currentStateLeavesPathElements.unshift(this.stateTree!.genProof(0).pathElements); - currentBallots.unshift(this.ballots[0].copy()); - currentBallotsPathElements.unshift(this.ballotTree!.genProof(0).pathElements); - - // Since the command is invalid, we use a zero vote weight - currentVoteWeights.unshift(this.ballots[0].votes[0]); - - // create a new quinary tree and add an empty vote - const vt = new IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, STATE_TREE_ARITY, hash5); - vt.insert(this.ballots[0].votes[0]); - // get the path elements for this empty vote weight leaf - currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements); - } - } else { - throw e; - } - } - break; - case 2n: - try { - // -------------------------------------- - // generate topup circuit inputs - const stateIndex = Number(message.data[0] >= BigInt(this.ballots.length) ? 0n : message.data[0]); - const amount = message.data[0] >= BigInt(this.ballots.length) ? 0n : message.data[1]; - - currentStateLeaves.unshift(this.stateLeaves[stateIndex].copy()); - currentStateLeavesPathElements.unshift(this.stateTree!.genProof(stateIndex).pathElements); - - // create a copy of the state leaf - const newStateLeaf = this.stateLeaves[stateIndex].copy(); - // update the voice credit balance - newStateLeaf.voiceCreditBalance += amount; - - // we should not be in this state as it means we are dealing with very large numbers which will cause problems in the circuits - if (newStateLeaf.voiceCreditBalance > SNARK_FIELD_SIZE) { - throw new Error( - "State leaf voice credit balance exceeds SNARK_FIELD_SIZE. This should not be a state MACI should find itself in, as it will cause complications in the circuits. Rounds should not accept topups with large values.", - ); - } - - // save it - this.stateLeaves[stateIndex] = newStateLeaf; - // update the state tree - this.stateTree?.update(stateIndex, newStateLeaf.hash()); - - // we still need them as placeholder for vote command - const currentBallot = this.ballots[stateIndex].copy(); - currentBallots.unshift(currentBallot); - currentBallotsPathElements.unshift(this.ballotTree!.genProof(Number(stateIndex)).pathElements); - currentVoteWeights.unshift(currentBallot.votes[0]); - - // create a quinary tree to fill with the votes of the current ballot - const vt = new IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, STATE_TREE_ARITY, hash5); - - for (let j = 0; j < this.ballots[0].votes.length; j += 1) { - vt.insert(currentBallot.votes[j]); - } - - // add to the first position the path elements of the vote weight tree - currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements); - } catch (e) { - if (!quiet) { - // eslint-disable-next-line no-console - console.log("Error processing topup message: ", (e as Error).message); - } - throw e; - } - break; - default: - break; - } - } else { - // Since we don't have a command at that position, use a blank state leaf - currentStateLeaves.unshift(this.stateLeaves[0].copy()); - currentStateLeavesPathElements.unshift(this.stateTree!.genProof(0).pathElements); - // since the command is invliad we use the blank ballot - currentBallots.unshift(this.ballots[0].copy()); - currentBallotsPathElements.unshift(this.ballotTree!.genProof(0).pathElements); - - // Since the command is invalid, we use a zero vote weight - currentVoteWeights.unshift(this.ballots[0].votes[0]); - - // create a new quinary tree and add an empty vote - const vt = new IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, STATE_TREE_ARITY, hash5); - vt.insert(this.ballots[0].votes[0]); - - // get the path elements for this empty vote weight leaf - currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements); - } - } - - // store the data in the circuit inputs object - circuitInputs.currentStateLeaves = currentStateLeaves.map(x => x.asCircuitInputs()); - circuitInputs.currentStateLeavesPathElements = currentStateLeavesPathElements; - circuitInputs.currentBallots = currentBallots.map(x => x.asCircuitInputs()); - circuitInputs.currentBallotsPathElements = currentBallotsPathElements; - circuitInputs.currentVoteWeights = currentVoteWeights; - circuitInputs.currentVoteWeightsPathElements = currentVoteWeightsPathElements; - - // record that we processed one batch - this.numBatchesProcessed += 1; - - if (this.currentMessageBatchIndex! > 0) { - this.currentMessageBatchIndex! -= batchSize; - } - - // ensure newSbSalt differs from currentSbSalt - let newSbSalt = genRandomSalt(); - while (this.sbSalts[this.currentMessageBatchIndex!] === newSbSalt) { - newSbSalt = genRandomSalt(); - } - this.sbSalts[this.currentMessageBatchIndex!] = newSbSalt; - - // store the salt in the circuit inputs - circuitInputs.newSbSalt = newSbSalt; - const newStateRoot = this.stateTree!.root; - const newBallotRoot = this.ballotTree!.root; - // create a commitment to the state and ballot tree roots - // this will be the hash of the roots with a salt - circuitInputs.newSbCommitment = hash3([newStateRoot, newBallotRoot, newSbSalt]); - - // here is important that a user validates it matches the one in the - // smart contract - const coordPubKeyHash = this.coordinatorKeypair.pubKey.hash(); - // create the input hash which is the only public input to the - // process messages circuit - circuitInputs.inputHash = sha256Hash([ - circuitInputs.packedVals as bigint, - coordPubKeyHash, - circuitInputs.msgRoot as bigint, - circuitInputs.currentSbCommitment as bigint, - circuitInputs.newSbCommitment, - this.pollEndTimestamp, - ]); - - // If this is the last batch, release the lock - if (this.numBatchesProcessed * batchSize >= this.messages.length) { - this.maciStateRef.pollBeingProcessed = false; - } - - return stringifyBigInts(circuitInputs) as unknown as IProcessMessagesCircuitInputs; - }; - - /** - * Generates partial circuit inputs for processing a batch of messages - * @param index - The index of the partial batch. - * @returns stringified partial circuit inputs - */ - private genProcessMessagesCircuitInputsPartial = (index: number): CircuitInputs => { - const { messageBatchSize } = this.batchSizes; - - assert(index <= this.messages.length, "The index must be <= the number of messages"); - assert(index % messageBatchSize === 0, "The index must be a multiple of the message batch size"); - - // fill the msgs array with a copy of the messages we have - // plus empty messages to fill the batch - - // @note create a message with state index 0 to add as padding - // this way the message will look for state leaf 0 - // and no effect will take place - - // create a random key - const key = new Keypair(); - // gen ecdh key - const ecdh = Keypair.genEcdhSharedKey(key.privKey, this.coordinatorKeypair.pubKey); - // create an empty command with state index 0n - const emptyCommand = new PCommand(0n, key.pubKey, 0n, 0n, 0n, 0n, 0n); - - // encrypt it - const msg = emptyCommand.encrypt(emptyCommand.sign(key.privKey), ecdh); - - // copy the messages to a new array - let msgs = this.messages.map(x => x.asCircuitInputs()); - - // pad with our state index 0 message - while (msgs.length % messageBatchSize > 0) { - msgs.push(msg.asCircuitInputs()); - } - - // we only take the messages we need for this batch - msgs = msgs.slice(index, index + messageBatchSize); - - // insert zero value in the message tree as padding - while (this.messageTree.nextIndex < index + messageBatchSize) { - this.messageTree.insert(this.messageTree.zeroValue); - } - - // generate the path to the subroot of the message tree for this batch - const messageSubrootPath = this.messageTree.genSubrootProof(index, index + messageBatchSize); - - // verify it - assert(this.messageTree.verifyProof(messageSubrootPath), "The message subroot path is invalid"); - - // validate that the batch index is correct, if not fix it - // this means that the end will be the last message - let batchEndIndex = index + messageBatchSize; - if (batchEndIndex > this.messages.length) { - batchEndIndex = this.messages.length; - } - - // copy the public keys, pad the array with the last keys if needed - let encPubKeys = this.encPubKeys.map(x => x.copy()); - while (encPubKeys.length % messageBatchSize > 0) { - // pad with the public key used to encrypt the message with state index 0 (padding) - encPubKeys.push(key.pubKey.copy()); - } - // then take the ones part of this batch - encPubKeys = encPubKeys.slice(index, index + messageBatchSize); - - // cache tree roots - const msgRoot = this.messageTree.root; - const currentStateRoot = this.stateTree!.root; - const currentBallotRoot = this.ballotTree!.root; - // calculate the current state and ballot root - // commitment which is the hash of the state tree - // root, the ballot tree root and a salt - const currentSbCommitment = hash3([ - currentStateRoot, - currentBallotRoot, - this.sbSalts[this.currentMessageBatchIndex!], - ]); - - // Generate a SHA256 hash of inputs which the contract provides - /* eslint-disable no-bitwise */ - const packedVals = - BigInt(this.maxValues.maxVoteOptions) + - (BigInt(this.numSignups) << 50n) + - (BigInt(index) << 100n) + - (BigInt(batchEndIndex) << 150n); - /* eslint-enable no-bitwise */ - - return stringifyBigInts({ - pollEndTimestamp: this.pollEndTimestamp, - packedVals, - msgRoot, - msgs, - msgSubrootPathElements: messageSubrootPath.pathElements, - coordPrivKey: this.coordinatorKeypair.privKey.asCircuitInputs(), - coordPubKey: this.coordinatorKeypair.pubKey.asCircuitInputs(), - encPubKeys: encPubKeys.map(x => x.asCircuitInputs()), - currentStateRoot, - currentBallotRoot, - currentSbCommitment, - currentSbSalt: this.sbSalts[this.currentMessageBatchIndex!], - }) as CircuitInputs; - }; - - /** - * Process all messages. This function does not update the ballots or state - * leaves; rather, it copies and then updates them. This makes it possible - * to test the result of multiple processMessage() invocations. - * @returns The state leaves and ballots of the poll - */ - processAllMessages = (): { stateLeaves: StateLeaf[]; ballots: Ballot[] } => { - const stateLeaves = this.stateLeaves.map(x => x.copy()); - const ballots = this.ballots.map(x => x.copy()); - - // process all messages in one go (batch by batch but without manual intervention) - while (this.hasUnprocessedMessages()) { - this.processMessages(this.pollId); - } - - return { stateLeaves, ballots }; - }; - - /** - * Checks whether there are any untallied ballots. - * @returns Whether there are any untallied ballots - */ - hasUntalliedBallots = (): boolean => this.numBatchesTallied * this.batchSizes.tallyBatchSize < this.ballots.length; - - /** - * This method tallies a ballots and updates the tally results. - * @returns the circuit inputs for the TallyVotes circuit. - */ - tallyVotes = (): ITallyCircuitInputs => { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (this.sbSalts[this.currentMessageBatchIndex!] === undefined) { - throw new Error("You must process the messages first"); - } - - const batchSize = this.batchSizes.tallyBatchSize; - - assert(this.hasUntalliedBallots(), "No more ballots to tally"); - - // calculate where we start tallying next - const batchStartIndex = this.numBatchesTallied * batchSize; - - // get the salts needed for the commitments - const currentResultsRootSalt = batchStartIndex === 0 ? 0n : this.resultRootSalts[batchStartIndex - batchSize]; - - const currentPerVOSpentVoiceCreditsRootSalt = - batchStartIndex === 0 ? 0n : this.preVOSpentVoiceCreditsRootSalts[batchStartIndex - batchSize]; - - const currentSpentVoiceCreditSubtotalSalt = - batchStartIndex === 0 ? 0n : this.spentVoiceCreditSubtotalSalts[batchStartIndex - batchSize]; - - // generate a commitment to the current results - const currentResultsCommitment = genTreeCommitment( - this.tallyResult, - currentResultsRootSalt, - this.treeDepths.voteOptionTreeDepth, - ); - - // generate a commitment to the current per VO spent voice credits - const currentPerVOSpentVoiceCreditsCommitment = this.genPerVOSpentVoiceCreditsCommitment( - currentPerVOSpentVoiceCreditsRootSalt, - batchStartIndex, - true, - ); - - // generate a commitment to the current spent voice credits - const currentSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment( - currentSpentVoiceCreditSubtotalSalt, - batchStartIndex, - true, - ); - - // the current commitment for the first batch will be 0 - // otherwise calculate as - // hash([ - // currentResultsCommitment, - // currentSpentVoiceCreditsCommitment, - // currentPerVOSpentVoiceCreditsCommitment - // ]) - const currentTallyCommitment = - batchStartIndex === 0 - ? 0n - : hash3([ - currentResultsCommitment, - currentSpentVoiceCreditsCommitment, - currentPerVOSpentVoiceCreditsCommitment, - ]); - - const ballots: Ballot[] = []; - const currentResults = this.tallyResult.map(x => BigInt(x.toString())); - const currentPerVOSpentVoiceCredits = this.perVOSpentVoiceCredits.map(x => BigInt(x.toString())); - const currentSpentVoiceCreditSubtotal = BigInt(this.totalSpentVoiceCredits.toString()); - - // loop in normal order to tally the ballots one by one - for (let i = this.numBatchesTallied * batchSize; i < this.numBatchesTallied * batchSize + batchSize; i += 1) { - // we stop if we have no more ballots to tally - if (i >= this.ballots.length) { - break; - } - - // save to the local ballot array - ballots.push(this.ballots[i]); - - // for each possible vote option we loop and calculate - for (let j = 0; j < this.maxValues.maxVoteOptions; j += 1) { - const v = this.ballots[i].votes[j]; - - // the vote itself will be a quadratic vote (sqrt(voiceCredits)) - this.tallyResult[j] += v; - - // the per vote option spent voice credits will be the sum of the squares of the votes - this.perVOSpentVoiceCredits[j] += v * v; - - // the total spent voice credits will be the sum of the squares of the votes - this.totalSpentVoiceCredits += v * v; - } - } - - const emptyBallot = new Ballot(this.maxValues.maxVoteOptions, this.treeDepths.voteOptionTreeDepth); - - // pad the ballots array - while (ballots.length < batchSize) { - ballots.push(emptyBallot); - } - - // generate the new salts - const newResultsRootSalt = genRandomSalt(); - const newPerVOSpentVoiceCreditsRootSalt = genRandomSalt(); - const newSpentVoiceCreditSubtotalSalt = genRandomSalt(); - - // and save them to be used in the next batch - this.resultRootSalts[batchStartIndex] = newResultsRootSalt; - this.preVOSpentVoiceCreditsRootSalts[batchStartIndex] = newPerVOSpentVoiceCreditsRootSalt; - this.spentVoiceCreditSubtotalSalts[batchStartIndex] = newSpentVoiceCreditSubtotalSalt; - - // generate the new results commitment with the new salts and data - const newResultsCommitment = genTreeCommitment( - this.tallyResult, - newResultsRootSalt, - this.treeDepths.voteOptionTreeDepth, - ); - - // generate the new spent voice credits commitment with the new salts and data - const newSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment( - newSpentVoiceCreditSubtotalSalt, - batchStartIndex + batchSize, - true, - ); - - // generate the new per VO spent voice credits commitment with the new salts and data - const newPerVOSpentVoiceCreditsCommitment = this.genPerVOSpentVoiceCreditsCommitment( - newPerVOSpentVoiceCreditsRootSalt, - batchStartIndex + batchSize, - true, - ); - - // generate the new tally commitment - const newTallyCommitment = hash3([ - newResultsCommitment, - newSpentVoiceCreditsCommitment, - newPerVOSpentVoiceCreditsCommitment, - ]); - - // cache vars - const stateRoot = this.stateTree!.root; - const ballotRoot = this.ballotTree!.root; - const sbSalt = this.sbSalts[this.currentMessageBatchIndex!]; - const sbCommitment = hash3([stateRoot, ballotRoot, sbSalt]); - - const packedVals = packTallyVotesSmallVals(batchStartIndex, batchSize, Number(this.numSignups)); - const inputHash = sha256Hash([packedVals, sbCommitment, currentTallyCommitment, newTallyCommitment]); - - const ballotSubrootProof = this.ballotTree?.genSubrootProof(batchStartIndex, batchStartIndex + batchSize); - - const votes = ballots.map(x => x.votes); - - const circuitInputs = stringifyBigInts({ - stateRoot, - ballotRoot, - sbSalt, - sbCommitment, - currentTallyCommitment, - newTallyCommitment, - packedVals, // contains numSignUps and batchStartIndex - inputHash, - ballots: ballots.map(x => x.asCircuitInputs()), - ballotPathElements: ballotSubrootProof!.pathElements, - votes, - currentResults, - currentResultsRootSalt, - currentSpentVoiceCreditSubtotal, - currentSpentVoiceCreditSubtotalSalt, - currentPerVOSpentVoiceCredits, - currentPerVOSpentVoiceCreditsRootSalt, - newResultsRootSalt, - newPerVOSpentVoiceCreditsRootSalt, - newSpentVoiceCreditSubtotalSalt, - }) as unknown as ITallyCircuitInputs; - - this.numBatchesTallied += 1; - - return circuitInputs; - }; - - tallyVotesNonQv = (): ITallyCircuitInputs => { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (this.sbSalts[this.currentMessageBatchIndex!] === undefined) { - throw new Error("You must process the messages first"); - } - - const batchSize = this.batchSizes.tallyBatchSize; - - assert(this.hasUntalliedBallots(), "No more ballots to tally"); - - // calculate where we start tallying next - const batchStartIndex = this.numBatchesTallied * batchSize; - - // get the salts needed for the commitments - const currentResultsRootSalt = batchStartIndex === 0 ? 0n : this.resultRootSalts[batchStartIndex - batchSize]; - - const currentSpentVoiceCreditSubtotalSalt = - batchStartIndex === 0 ? 0n : this.spentVoiceCreditSubtotalSalts[batchStartIndex - batchSize]; - - // generate a commitment to the current results - const currentResultsCommitment = genTreeCommitment( - this.tallyResult, - currentResultsRootSalt, - this.treeDepths.voteOptionTreeDepth, - ); - - // generate a commitment to the current spent voice credits - const currentSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment( - currentSpentVoiceCreditSubtotalSalt, - batchStartIndex, - false, - ); - - // the current commitment for the first batch will be 0 - // otherwise calculate as - // hash([ - // currentResultsCommitment, - // currentSpentVoiceCreditsCommitment, - // ]) - const currentTallyCommitment = - batchStartIndex === 0 ? 0n : hashLeftRight(currentResultsCommitment, currentSpentVoiceCreditsCommitment); - - const ballots: Ballot[] = []; - const currentResults = this.tallyResult.map(x => BigInt(x.toString())); - const currentSpentVoiceCreditSubtotal = BigInt(this.totalSpentVoiceCredits.toString()); - - // loop in normal order to tally the ballots one by one - for (let i = this.numBatchesTallied * batchSize; i < this.numBatchesTallied * batchSize + batchSize; i += 1) { - // we stop if we have no more ballots to tally - if (i >= this.ballots.length) { - break; - } - - // save to the local ballot array - ballots.push(this.ballots[i]); - - // for each possible vote option we loop and calculate - for (let j = 0; j < this.maxValues.maxVoteOptions; j += 1) { - const v = this.ballots[i].votes[j]; - - this.tallyResult[j] += v; - - // the total spent voice credits will be the sum of the votes - this.totalSpentVoiceCredits += v; - } - } - - const emptyBallot = new Ballot(this.maxValues.maxVoteOptions, this.treeDepths.voteOptionTreeDepth); - - // pad the ballots array - while (ballots.length < batchSize) { - ballots.push(emptyBallot); - } - - // generate the new salts - const newResultsRootSalt = genRandomSalt(); - const newSpentVoiceCreditSubtotalSalt = genRandomSalt(); - - // and save them to be used in the next batch - this.resultRootSalts[batchStartIndex] = newResultsRootSalt; - this.spentVoiceCreditSubtotalSalts[batchStartIndex] = newSpentVoiceCreditSubtotalSalt; - - // generate the new results commitment with the new salts and data - const newResultsCommitment = genTreeCommitment( - this.tallyResult, - newResultsRootSalt, - this.treeDepths.voteOptionTreeDepth, - ); - - // generate the new spent voice credits commitment with the new salts and data - const newSpentVoiceCreditsCommitment = this.genSpentVoiceCreditSubtotalCommitment( - newSpentVoiceCreditSubtotalSalt, - batchStartIndex + batchSize, - false, - ); - - // generate the new tally commitment - const newTallyCommitment = hashLeftRight(newResultsCommitment, newSpentVoiceCreditsCommitment); - - // cache vars - const stateRoot = this.stateTree!.root; - const ballotRoot = this.ballotTree!.root; - const sbSalt = this.sbSalts[this.currentMessageBatchIndex!]; - const sbCommitment = hash3([stateRoot, ballotRoot, sbSalt]); - - const packedVals = packTallyVotesSmallVals(batchStartIndex, batchSize, Number(this.numSignups)); - const inputHash = sha256Hash([packedVals, sbCommitment, currentTallyCommitment, newTallyCommitment]); - - const ballotSubrootProof = this.ballotTree?.genSubrootProof(batchStartIndex, batchStartIndex + batchSize); - - const votes = ballots.map(x => x.votes); - - const circuitInputs = stringifyBigInts({ - stateRoot, - ballotRoot, - sbSalt, - sbCommitment, - currentTallyCommitment, - newTallyCommitment, - packedVals, // contains numSignUps and batchStartIndex - inputHash, - ballots: ballots.map(x => x.asCircuitInputs()), - ballotPathElements: ballotSubrootProof!.pathElements, - votes, - currentResults, - currentResultsRootSalt, - currentSpentVoiceCreditSubtotal, - currentSpentVoiceCreditSubtotalSalt, - newResultsRootSalt, - newSpentVoiceCreditSubtotalSalt, - }) as unknown as ITallyCircuitInputs; - - this.numBatchesTallied += 1; - - return circuitInputs; - }; - - /** - * This method generates a commitment to the total spent voice credits. - * - * This is the hash of the total spent voice credits and a salt, computed as Poseidon([totalCredits, _salt]). - * @param salt - The salt used in the hash function. - * @param numBallotsToCount - The number of ballots to count for the calculation. - * @param useQuadraticVoting - Whether to use quadratic voting or not. Default is true. - * @returns Returns the hash of the total spent voice credits and a salt, computed as Poseidon([totalCredits, _salt]). - */ - private genSpentVoiceCreditSubtotalCommitment = ( - salt: bigint, - numBallotsToCount: number, - useQuadraticVoting = true, - ): bigint => { - let subtotal = 0n; - for (let i = 0; i < numBallotsToCount; i += 1) { - if (this.ballots.length <= i) { - break; - } - - for (let j = 0; j < this.tallyResult.length; j += 1) { - const v = BigInt(`${this.ballots[i].votes[j]}`); - subtotal += useQuadraticVoting ? v * v : v; - } - } - return hashLeftRight(subtotal, salt); - }; - - /** - * This method generates a commitment to the spent voice credits per vote option. - * - * This is the hash of the Merkle root of the spent voice credits per vote option and a salt, computed as Poseidon([root, _salt]). - * @param salt - The salt used in the hash function. - * @param numBallotsToCount - The number of ballots to count for the calculation. - * @param useQuadraticVoting - Whether to use quadratic voting or not. Default is true. - * @returns Returns the hash of the Merkle root of the spent voice credits per vote option and a salt, computed as Poseidon([root, _salt]). - */ - private genPerVOSpentVoiceCreditsCommitment = ( - salt: bigint, - numBallotsToCount: number, - useQuadraticVoting = true, - ): bigint => { - const leaves: bigint[] = Array(this.tallyResult.length).fill(0n); - - for (let i = 0; i < numBallotsToCount; i += 1) { - // check that is a valid index - if (i >= this.ballots.length) { - break; - } - - for (let j = 0; j < this.tallyResult.length; j += 1) { - const v = this.ballots[i].votes[j]; - leaves[j] += useQuadraticVoting ? v * v : v; - } - } - - return genTreeCommitment(leaves, salt, this.treeDepths.voteOptionTreeDepth); - }; - - /** - * Create a deep copy of the Poll object. - * @returns A new instance of the Poll object with the same properties. - */ - copy = (): Poll => { - const copied = new Poll( - BigInt(this.pollEndTimestamp.toString()), - this.coordinatorKeypair.copy(), - { - intStateTreeDepth: Number(this.treeDepths.intStateTreeDepth), - messageTreeDepth: Number(this.treeDepths.messageTreeDepth), - messageTreeSubDepth: Number(this.treeDepths.messageTreeSubDepth), - voteOptionTreeDepth: Number(this.treeDepths.voteOptionTreeDepth), - }, - { - tallyBatchSize: Number(this.batchSizes.tallyBatchSize.toString()), - messageBatchSize: Number(this.batchSizes.messageBatchSize.toString()), - }, - { - maxMessages: Number(this.maxValues.maxMessages.toString()), - maxVoteOptions: Number(this.maxValues.maxVoteOptions.toString()), - }, - this.maciStateRef, - ); - - copied.stateLeaves = this.stateLeaves.map(x => x.copy()); - copied.messages = this.messages.map(x => x.copy()); - copied.commands = this.commands.map(x => x.copy()); - copied.ballots = this.ballots.map(x => x.copy()); - copied.encPubKeys = this.encPubKeys.map(x => x.copy()); - - if (this.ballotTree) { - copied.ballotTree = this.ballotTree.copy(); - } - - copied.currentMessageBatchIndex = this.currentMessageBatchIndex; - copied.maciStateRef = this.maciStateRef; - copied.messageTree = this.messageTree.copy(); - copied.tallyResult = this.tallyResult.map((x: bigint) => BigInt(x.toString())); - copied.perVOSpentVoiceCredits = this.perVOSpentVoiceCredits.map((x: bigint) => BigInt(x.toString())); - - copied.numBatchesProcessed = Number(this.numBatchesProcessed.toString()); - copied.numBatchesTallied = Number(this.numBatchesTallied.toString()); - copied.pollId = this.pollId; - copied.totalSpentVoiceCredits = BigInt(this.totalSpentVoiceCredits.toString()); - - copied.sbSalts = {}; - copied.resultRootSalts = {}; - copied.preVOSpentVoiceCreditsRootSalts = {}; - copied.spentVoiceCreditSubtotalSalts = {}; - - Object.keys(this.sbSalts).forEach(k => { - copied.sbSalts[k] = BigInt(this.sbSalts[k].toString()); - }); - - Object.keys(this.resultRootSalts).forEach(k => { - copied.resultRootSalts[k] = BigInt(this.resultRootSalts[k].toString()); - }); - - Object.keys(this.preVOSpentVoiceCreditsRootSalts).forEach(k => { - copied.preVOSpentVoiceCreditsRootSalts[k] = BigInt(this.preVOSpentVoiceCreditsRootSalts[k].toString()); - }); - - Object.keys(this.spentVoiceCreditSubtotalSalts).forEach(k => { - copied.spentVoiceCreditSubtotalSalts[k] = BigInt(this.spentVoiceCreditSubtotalSalts[k].toString()); - }); - - // update the number of signups - copied.setNumSignups(this.numSignups); - - return copied; - }; - - /** - * Check if the Poll object is equal to another Poll object. - * @param p - The Poll object to compare. - * @returns True if the two Poll objects are equal, false otherwise. - */ - equals = (p: Poll): boolean => { - const result = - this.coordinatorKeypair.equals(p.coordinatorKeypair) && - this.treeDepths.intStateTreeDepth === p.treeDepths.intStateTreeDepth && - this.treeDepths.messageTreeDepth === p.treeDepths.messageTreeDepth && - this.treeDepths.messageTreeSubDepth === p.treeDepths.messageTreeSubDepth && - this.treeDepths.voteOptionTreeDepth === p.treeDepths.voteOptionTreeDepth && - this.batchSizes.tallyBatchSize === p.batchSizes.tallyBatchSize && - this.batchSizes.messageBatchSize === p.batchSizes.messageBatchSize && - this.maxValues.maxMessages === p.maxValues.maxMessages && - this.maxValues.maxVoteOptions === p.maxValues.maxVoteOptions && - this.messages.length === p.messages.length && - this.encPubKeys.length === p.encPubKeys.length && - this.numSignups === p.numSignups; - - if (!result) { - return false; - } - - for (let i = 0; i < this.messages.length; i += 1) { - if (!this.messages[i].equals(p.messages[i])) { - return false; - } - } - for (let i = 0; i < this.encPubKeys.length; i += 1) { - if (!this.encPubKeys[i].equals(p.encPubKeys[i])) { - return false; - } - } - return true; - }; - - /** - * Serialize the Poll object to a JSON object - * @returns a JSON object - */ - toJSON(): IJsonPoll { - return { - pollEndTimestamp: this.pollEndTimestamp.toString(), - treeDepths: this.treeDepths, - batchSizes: this.batchSizes, - maxValues: this.maxValues, - messages: this.messages.map(message => message.toJSON()), - commands: this.commands.map(command => command.toJSON() as IJsonCommand), - ballots: this.ballots.map(ballot => ballot.toJSON()), - encPubKeys: this.encPubKeys.map(encPubKey => encPubKey.serialize()), - currentMessageBatchIndex: this.currentMessageBatchIndex!, - stateLeaves: this.stateLeaves.map(leaf => leaf.toJSON()), - results: this.tallyResult.map(result => result.toString()), - numBatchesProcessed: this.numBatchesProcessed, - numSignups: this.numSignups.toString(), - }; - } - - /** - * Deserialize a json object into a Poll instance - * @param json the json object to deserialize - * @param maciState the reference to the MaciState Class - * @returns a new Poll instance - */ - static fromJSON(json: IJsonPoll, maciState: MaciState): Poll { - const poll = new Poll( - BigInt(json.pollEndTimestamp), - new Keypair(), - json.treeDepths, - json.batchSizes, - json.maxValues, - maciState, - ); - - // set all properties - poll.ballots = json.ballots.map(ballot => Ballot.fromJSON(ballot)); - poll.encPubKeys = json.encPubKeys.map((key: string) => PubKey.deserialize(key)); - poll.messages = json.messages.map(message => Message.fromJSON(message as IMessageContractParams)); - poll.commands = json.commands.map((command: IJsonCommand) => { - switch (command.cmdType) { - case "1": { - return PCommand.fromJSON(command as IJsonPCommand) as ICommand; - } - - case "2": { - return TCommand.fromJSON(command as IJsonTCommand) as ICommand; - } - - default: { - return { cmdType: command.cmdType } as unknown as ICommand; - } - } - }); - poll.tallyResult = json.results.map((result: string) => BigInt(result)); - poll.currentMessageBatchIndex = json.currentMessageBatchIndex; - poll.numBatchesProcessed = json.numBatchesProcessed; - - // fill the trees - for (let i = 0; i < poll.messages.length; i += 1) { - const messageLeaf = poll.messages[i].hash(poll.encPubKeys[i]); - poll.messageTree.insert(messageLeaf); - } - - // copy maci state - poll.updatePoll(BigInt(json.numSignups)); - - return poll; - } - - /** - * Set the coordinator's keypair - * @param serializedPrivateKey - the serialized private key - */ - setCoordinatorKeypair = (serializedPrivateKey: string): void => { - this.coordinatorKeypair = new Keypair(PrivKey.deserialize(serializedPrivateKey)); - }; - - /** - * Set the number of signups to match the ones from the contract - * @param numSignups - the number of signups - */ - setNumSignups = (numSignups: bigint): void => { - this.numSignups = numSignups; - }; - - /** - * Get the number of signups - * @returns The number of signups - */ - getNumSignups = (): bigint => this.numSignups; -} diff --git a/packages/hardhat/maci-ts/core/__benchmarks__/index.ts b/packages/hardhat/maci-ts/core/__benchmarks__/index.ts deleted file mode 100644 index e6e0d27..0000000 --- a/packages/hardhat/maci-ts/core/__benchmarks__/index.ts +++ /dev/null @@ -1,112 +0,0 @@ -import benny from "benny"; -import { Keypair, PCommand } from "../../domainobjs"; - -import { MaciState } from ".."; - -import { - COORDINATOR_KEYPAIR, - DURATION, - MAX_VALUES, - MESSAGE_BATCH_SIZE, - STATE_TREE_DEPTH, - TREE_DEPTHS, - VOICE_CREDIT_BALANCE, -} from "./utils/constants"; - -const NAME = "maci-core"; - -export default function runCore(): void { - benny.suite( - NAME, - - benny.add(`maci-core - Generate circuit inputs for 10 signups and 50 messages`, () => { - const voteWeight = 9n; - - const users: Keypair[] = []; - - const maciState = new MaciState(STATE_TREE_DEPTH); - // Sign up and vote - for (let i = 0; i < MESSAGE_BATCH_SIZE - 1; i += 1) { - const userKeypair = new Keypair(); - users.push(userKeypair); - - maciState.signUp(userKeypair.pubKey, VOICE_CREDIT_BALANCE, BigInt(Math.floor(Date.now() / 1000))); - } - - const pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + DURATION), - MAX_VALUES, - TREE_DEPTHS, - MESSAGE_BATCH_SIZE, - COORDINATOR_KEYPAIR, - ); - const poll = maciState.polls.get(pollId)!; - - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - // 24 valid votes - for (let i = 0; i < MESSAGE_BATCH_SIZE - 1; i += 1) { - const userKeypair = users[i]; - - const command = new PCommand( - BigInt(i + 1), - userKeypair.pubKey, - BigInt(i), // vote option index - voteWeight, - 1n, - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, COORDINATOR_KEYPAIR.pubKey); - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - } - - // 24 invalid votes - for (let i = 0; i < MESSAGE_BATCH_SIZE - 1; i += 1) { - const userKeypair = users[i]; - const command = new PCommand( - BigInt(i + 1), - userKeypair.pubKey, - BigInt(i), // vote option index - VOICE_CREDIT_BALANCE * 2n, // invalid vote weight - 1n, - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, COORDINATOR_KEYPAIR.pubKey); - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - } - - // Process messages - poll.processMessages(pollId); - - // Process messages - poll.processMessages(pollId); - - // Test processAllMessages - poll.processAllMessages(); - }), - - benny.cycle(), - benny.complete(results => { - results.results.forEach(result => { - // eslint-disable-next-line no-console - console.log(`${result.name}: mean time: ${result.details.mean.toFixed(2)}`); - }); - }), - - benny.save({ folder: "ts/__benchmarks__/results", file: NAME, version: "1.0.0", details: true }), - benny.save({ folder: "ts/__benchmarks__/results", file: NAME, format: "chart.html", details: true }), - benny.save({ folder: "ts/__benchmarks__/results", file: NAME, format: "table.html", details: true }), - ); -} - -runCore(); diff --git a/packages/hardhat/maci-ts/core/__benchmarks__/utils/constants.ts b/packages/hardhat/maci-ts/core/__benchmarks__/utils/constants.ts deleted file mode 100644 index 4d7cc77..0000000 --- a/packages/hardhat/maci-ts/core/__benchmarks__/utils/constants.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Keypair } from "../../../domainobjs"; - -export const VOICE_CREDIT_BALANCE = 100n; -export const DURATION = 30; -export const MESSAGE_BATCH_SIZE = 25; -export const COORDINATOR_KEYPAIR = new Keypair(); -export const STATE_TREE_DEPTH = 10; -export const MAX_VALUES = { - maxUsers: 25, - maxMessages: 25, - maxVoteOptions: 25, -}; - -export const TREE_DEPTHS = { - intStateTreeDepth: 2, - messageTreeDepth: 3, - messageTreeSubDepth: 2, - voteOptionTreeDepth: 4, -}; diff --git a/packages/hardhat/maci-ts/core/__tests__/MaciState.test.ts b/packages/hardhat/maci-ts/core/__tests__/MaciState.test.ts deleted file mode 100644 index 4331fdc..0000000 --- a/packages/hardhat/maci-ts/core/__tests__/MaciState.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { expect } from "chai"; -import { PCommand, Message, Keypair } from "../../domainobjs"; - -import fs from "fs"; - -import { MaciState } from "../MaciState"; -import { STATE_TREE_DEPTH } from "../utils/constants"; -import { IJsonMaciState } from "../utils/types"; - -import { - coordinatorKeypair, - duration, - maxValues, - messageBatchSize, - treeDepths, - voiceCreditBalance, -} from "./utils/constants"; - -describe("MaciState", function test() { - this.timeout(100000); - - describe("copy", () => { - let pollId: bigint; - let m1: MaciState; - const userKeypair = new Keypair(); - const stateFile = "./state.json"; - - after(() => { - if (fs.existsSync(stateFile)) { - fs.unlinkSync(stateFile); - } - }); - - before(() => { - m1 = new MaciState(STATE_TREE_DEPTH); - m1.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); - pollId = m1.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - const command = new PCommand(0n, userKeypair.pubKey, 0n, 0n, 0n, BigInt(pollId), 0n); - - const encKeypair = new Keypair(); - const signature = command.sign(encKeypair.privKey); - const sharedKey = Keypair.genEcdhSharedKey(encKeypair.privKey, coordinatorKeypair.pubKey); - const message: Message = command.encrypt(signature, sharedKey); - - m1.polls.get(pollId)!.publishMessage(message, encKeypair.pubKey); - m1.polls.get(pollId)!.publishMessage(message, encKeypair.pubKey); - }); - - it("should correctly deep-copy a MaciState object", () => { - const m2 = m1.copy(); - - // modify stateTreeDepth - m2.stateTreeDepth += 1; - expect(m1.equals(m2)).not.to.eq(true); - - // modify user.pubKey - const m3 = m1.copy(); - m3.stateLeaves[0].pubKey = new Keypair().pubKey; - expect(m1.equals(m3)).not.to.eq(true); - - // modify user.voiceCreditBalance - const m4 = m1.copy(); - m4.stateLeaves[0].voiceCreditBalance = BigInt(m4.stateLeaves[0].voiceCreditBalance) + 1n; - expect(m1.equals(m4)).not.to.eq(true); - - // modify poll.coordinatorKeypair - const m6 = m1.copy(); - m6.polls.get(pollId)!.coordinatorKeypair = new Keypair(); - expect(m1.equals(m6)).not.to.eq(true); - - // modify poll.treeDepths.intStateTreeDepth - const m9 = m1.copy(); - m9.polls.get(pollId)!.treeDepths.intStateTreeDepth += 1; - expect(m1.equals(m9)).not.to.eq(true); - - // modify poll.treeDepths.messageTreeDepth - const m10 = m1.copy(); - m10.polls.get(pollId)!.treeDepths.messageTreeDepth += 1; - expect(m1.equals(m10)).not.to.eq(true); - - // modify poll.treeDepths.messageTreeSubDepth - const m11 = m1.copy(); - m11.polls.get(pollId)!.treeDepths.messageTreeSubDepth += 1; - expect(m1.equals(m11)).not.to.eq(true); - - // modify poll.treeDepths.voteOptionTreeDepth - const m12 = m1.copy(); - m12.polls.get(pollId)!.treeDepths.voteOptionTreeDepth += 1; - expect(m1.equals(m12)).not.to.eq(true); - - // modify poll.batchSizes.tallyBatchSize - const m13 = m1.copy(); - m13.polls.get(pollId)!.batchSizes.tallyBatchSize += 1; - expect(m1.equals(m13)).not.to.eq(true); - - // modify poll.batchSizes.messageBatchSize - const m14 = m1.copy(); - m14.polls.get(pollId)!.batchSizes.messageBatchSize += 1; - expect(m1.equals(m14)).not.to.eq(true); - - // modify poll.maxValues.maxMessages - const m16 = m1.copy(); - m16.polls.get(pollId)!.maxValues.maxMessages += 1; - expect(m1.equals(m16)).not.to.eq(true); - - // modify poll.maxValues.maxVoteOptions - const m17 = m1.copy(); - m17.polls.get(pollId)!.maxValues.maxVoteOptions += 1; - expect(m1.equals(m17)).not.to.eq(true); - - // modify poll.messages - const m20 = m1.copy(); - m20.polls.get(pollId)!.messages[0].data[0] = BigInt(m20.polls.get(pollId)!.messages[0].data[0]) + 1n; - expect(m1.equals(m20)).not.to.eq(true); - - // modify poll.encPubKeys - const m21 = m1.copy(); - m21.polls.get(pollId)!.encPubKeys[0] = new Keypair().pubKey; - expect(m1.equals(m21)).not.to.eq(true); - }); - - it("should create a JSON object from a MaciState object", () => { - // test loading a topup message - m1.polls.get(pollId)!.topupMessage(new Message(2n, [0n, 5n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n])); - - // mock a message with invalid message type - m1.polls.get(pollId)!.messages[1].msgType = 3n; - const json = m1.toJSON(); - fs.writeFileSync(stateFile, JSON.stringify(json, null, 4)); - const content = JSON.parse(fs.readFileSync(stateFile).toString()) as IJsonMaciState; - const state = MaciState.fromJSON(content); - state.polls.forEach(poll => { - poll.setCoordinatorKeypair(coordinatorKeypair.privKey.serialize()); - expect(poll.coordinatorKeypair.equals(coordinatorKeypair)).to.eq(true); - }); - - expect(state.equals(m1)).to.eq(true); - }); - }); - - describe("deployNullPoll ", () => { - it("should deploy a Poll that is null", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - maciState.deployNullPoll(); - expect(maciState.polls.get(0n)).to.eq(null); - }); - }); - - describe("topup", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - const poll = maciState.polls.get(pollId)!; - - it("should allow to publish a topup message", () => { - const user1Keypair = new Keypair(); - // signup the user - const user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); - - const message = new Message(2n, [BigInt(user1StateIndex), 50n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]); - - poll.topupMessage(message); - - expect(poll.messages.length).to.eq(1); - }); - - it("should throw if the message has an invalid message type", () => { - const message = new Message(1n, [1n, 50n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]); - - expect(() => { - poll.topupMessage(message); - }).to.throw("A Topup message must have msgType 2"); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/core/__tests__/Poll.test.ts b/packages/hardhat/maci-ts/core/__tests__/Poll.test.ts deleted file mode 100644 index 96e5bb7..0000000 --- a/packages/hardhat/maci-ts/core/__tests__/Poll.test.ts +++ /dev/null @@ -1,608 +0,0 @@ -import { expect } from "chai"; -import { PCommand, Message, Keypair, StateLeaf, PrivKey, Ballot } from "../../domainobjs"; - -import { MaciState } from "../MaciState"; -import { Poll } from "../Poll"; -import { STATE_TREE_DEPTH } from "../utils/constants"; - -import { - coordinatorKeypair, - duration, - maxValues, - messageBatchSize, - treeDepths, - voiceCreditBalance, -} from "./utils/constants"; - -describe("Poll", function test() { - this.timeout(90000); - - describe("processMessage", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - const poll = maciState.polls.get(pollId)!; - - const user1Keypair = new Keypair(); - // signup the user - const user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); - - // copy the state from the MaciState ref - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - it("should throw if a message has an invalid state index", () => { - const command = new PCommand( - // invalid state index as it is one more than the number of state leaves - BigInt(user1StateIndex + 1), - user1Keypair.pubKey, - 0n, - 1n, - 0n, - BigInt(pollId), - ); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - expect(() => { - poll.processMessage(message, ecdhKeypair.pubKey); - }).to.throw("invalid state leaf index"); - }); - - it("should throw if a message has an invalid nonce", () => { - const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 0n, 0n, BigInt(pollId)); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - - expect(() => { - poll.processMessage(message, ecdhKeypair.pubKey); - }).to.throw("invalid nonce"); - }); - - it("should throw if a message has an invalid signature", () => { - const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 0n, 0n, BigInt(pollId)); - - const signature = command.sign(new PrivKey(0n)); - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - - expect(() => { - poll.processMessage(message, ecdhKeypair.pubKey); - }).to.throw("invalid signature"); - }); - - it("should throw if a message consumes more than the available voice credits for a user", () => { - const command = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, - 0n, - // voice credits spent would be this value ** this value - BigInt(Math.sqrt(Number.parseInt(voiceCreditBalance.toString(), 10)) + 1), - 1n, - BigInt(pollId), - ); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - - expect(() => { - poll.processMessage(message, ecdhKeypair.pubKey); - }).to.throw("insufficient voice credits"); - }); - - it("should throw if a message has an invalid vote option index (>= max vote options)", () => { - const command = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, - BigInt(maxValues.maxVoteOptions), - // voice credits spent would be this value ** this value - 1n, - 1n, - BigInt(pollId), - ); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - - expect(() => { - poll.processMessage(message, ecdhKeypair.pubKey); - }).to.throw("invalid vote option index"); - }); - - it("should throw if a message has an invalid vote option index (< 0)", () => { - const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, -1n, 1n, 1n, BigInt(pollId)); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - - expect(() => { - poll.processMessage(message, ecdhKeypair.pubKey); - }).to.throw("invalid vote option index"); - }); - - it("should throw when passed a message that cannot be decrypted (wrong encPubKey)", () => { - const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 1n, 1n, BigInt(pollId)); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(new Keypair().privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - - expect(() => { - poll.processMessage(message, user1Keypair.pubKey); - }).to.throw("failed decryption due to either wrong encryption public key or corrupted ciphertext"); - }); - - it("should throw when passed a corrupted message", () => { - const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 1n, 1n, BigInt(pollId)); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(user1Keypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - - message.data[0] = 0n; - - expect(() => { - poll.processMessage(message, user1Keypair.pubKey); - }).to.throw("failed decryption due to either wrong encryption public key or corrupted ciphertext"); - }); - - it("should throw when going over the voice credit limit (non qv)", () => { - const command = new PCommand( - // invalid state index as it is one more than the number of state leaves - BigInt(user1StateIndex), - user1Keypair.pubKey, - 0n, - voiceCreditBalance + 1n, - 1n, - BigInt(pollId), - ); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - expect(() => { - poll.processMessage(message, ecdhKeypair.pubKey, false); - }).to.throw("insufficient voice credits"); - }); - - it("should work when submitting a valid message (voteWeight === voiceCreditBalance and non qv)", () => { - const command = new PCommand( - // invalid state index as it is one more than the number of state leaves - BigInt(user1StateIndex), - user1Keypair.pubKey, - 0n, - voiceCreditBalance, - 1n, - BigInt(pollId), - ); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - poll.processMessage(message, ecdhKeypair.pubKey, false); - }); - }); - - describe("processMessages", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - const poll = maciState.polls.get(pollId)!; - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - const user1Keypair = new Keypair(); - // signup the user - const user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); - - it("should throw if this is the first batch and currentMessageBatchIndex is defined", () => { - const command = new PCommand(BigInt(user1StateIndex), user1Keypair.pubKey, 0n, 1n, 0n, BigInt(pollId)); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - - poll.publishMessage(message, ecdhKeypair.pubKey); - - // mock - poll.currentMessageBatchIndex = 0; - expect(() => poll.processMessages(pollId)).to.throw( - "The current message batch index should not be defined if this is the first batch", - ); - poll.currentMessageBatchIndex = undefined; - }); - - it("should throw if the state has not been copied prior to calling processMessages", () => { - const tmpPoll = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - expect(() => maciState.polls.get(tmpPoll)?.processMessages(pollId)).to.throw( - "You must update the poll with the correct data first", - ); - }); - - it("should succeed even if we send an invalid message", () => { - const command = new PCommand( - // we only signed up one user so the state index is invalid - BigInt(user1StateIndex + 1), - user1Keypair.pubKey, - 0n, - 1n, - 0n, - BigInt(pollId), - ); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - - poll.publishMessage(message, ecdhKeypair.pubKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - expect(() => { - poll.processMessage(message, ecdhKeypair.pubKey); - }).to.throw("invalid state leaf index"); - - // keep this call to complete processing - // eslint-disable-next-line no-unused-expressions - expect(() => poll.processMessages(pollId)).to.not.throw; - }); - - it("should correctly process a topup message and increase an user's voice credit balance", () => { - const topupMessage = new Message(2n, [BigInt(user1StateIndex), 50n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]); - - poll.topupMessage(topupMessage); - - const balanceBefore = poll.stateLeaves[user1StateIndex].voiceCreditBalance; - - poll.processMessages(pollId); - - // check balance - expect(poll.stateLeaves[user1StateIndex].voiceCreditBalance.toString()).to.eq((balanceBefore + 50n).toString()); - }); - - it("should throw when called after all messages have been processed", () => { - expect(() => poll.processMessages(pollId)).to.throw("No more messages to process"); - }); - }); - - describe("processAllMessages", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - const poll = maciState.polls.get(pollId)!; - - const user1Keypair = new Keypair(); - // signup the user - const user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); - - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - it("it should succeed even if send an invalid message", () => { - const command = new PCommand( - // we only signed up one user so the state index is invalid - BigInt(user1StateIndex + 1), - user1Keypair.pubKey, - 0n, - 1n, - 0n, - BigInt(pollId), - ); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - expect(() => { - poll.processMessage(message, ecdhKeypair.pubKey); - }).to.throw("invalid state leaf index"); - - expect(() => poll.processAllMessages()).to.not.throw(); - }); - - it("should return the correct state leaves and ballots", () => { - const command = new PCommand(BigInt(user1StateIndex + 1), user1Keypair.pubKey, 0n, 1n, 0n, BigInt(pollId)); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - expect(() => { - poll.processMessage(message, ecdhKeypair.pubKey); - }).to.throw("invalid state leaf index"); - - const { stateLeaves, ballots } = poll.processAllMessages(); - - stateLeaves.forEach((leaf: StateLeaf, index: number) => expect(leaf.equals(poll.stateLeaves[index])).to.eq(true)); - ballots.forEach((ballot: Ballot, index: number) => expect(ballot.equals(poll.ballots[index])).to.eq(true)); - }); - - it("should have processed all messages", () => { - const command = new PCommand(BigInt(user1StateIndex + 1), user1Keypair.pubKey, 0n, 1n, 0n, BigInt(pollId)); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - - // publish batch size + 1 - for (let i = 0; i <= messageBatchSize; i += 1) { - poll.publishMessage(message, ecdhKeypair.pubKey); - } - - poll.processAllMessages(); - - expect(poll.hasUnprocessedMessages()).to.eq(false); - }); - }); - - describe("tallyVotes", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - const poll = maciState.polls.get(pollId)!; - - const user1Keypair = new Keypair(); - const user2Keypair = new Keypair(); - - // signup the user - const user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); - - const user2StateIndex = maciState.signUp( - user2Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); - - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - const voteWeight = 5n; - const voteOption = 0n; - - const command = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, - voteOption, - voteWeight, - 1n, - BigInt(pollId), - ); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - - it("should throw if called before all messages have been processed", () => { - expect(() => poll.tallyVotes()).to.throw("You must process the messages first"); - }); - - it("should generate the correct results", () => { - poll.processAllMessages(); - poll.tallyVotes(); - - const spentVoiceCredits = poll.totalSpentVoiceCredits; - const results = poll.tallyResult; - expect(spentVoiceCredits).to.eq(voteWeight * voteWeight); - expect(results[Number.parseInt(voteOption.toString(), 10)]).to.eq(voteWeight); - expect(poll.perVOSpentVoiceCredits[Number.parseInt(voteOption.toString(), 10)]).to.eq(voteWeight * voteWeight); - }); - - it("should generate the correct results (non-qv)", () => { - // deploy a second poll - const secondPollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - const secondPoll = maciState.polls.get(secondPollId)!; - secondPoll.updatePoll(BigInt(maciState.stateLeaves.length)); - - const secondVoteWeight = 10n; - const secondVoteOption = 1n; - - const secondCommand = new PCommand( - BigInt(user2StateIndex), - user2Keypair.pubKey, - secondVoteOption, - secondVoteWeight, - 1n, - secondPollId, - ); - - const secondSignature = secondCommand.sign(user2Keypair.privKey); - - const secondEcdhKeypair = new Keypair(); - const secondSharedKey = Keypair.genEcdhSharedKey(secondEcdhKeypair.privKey, coordinatorKeypair.pubKey); - - const secondMessage = secondCommand.encrypt(secondSignature, secondSharedKey); - secondPoll.publishMessage(secondMessage, secondEcdhKeypair.pubKey); - - secondPoll.processAllMessages(); - secondPoll.tallyVotesNonQv(); - - const spentVoiceCredits = secondPoll.totalSpentVoiceCredits; - const results = secondPoll.tallyResult; - // spent voice credit is not vote weight * vote weight - expect(spentVoiceCredits).to.eq(secondVoteWeight); - expect(results[Number.parseInt(secondVoteOption.toString(), 10)]).to.eq(secondVoteWeight); - }); - - it("should throw when there are no more ballots to tally", () => { - expect(() => poll.tallyVotes()).to.throw("No more ballots to tally"); - }); - }); - - describe("setCoordinatorKeypair", () => { - it("should update the coordinator's Keypair", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - const poll = maciState.polls.get(pollId)!; - const newCoordinatorKeypair = new Keypair(); - poll.setCoordinatorKeypair(newCoordinatorKeypair.privKey.serialize()); - expect(poll.coordinatorKeypair.privKey.serialize()).to.deep.eq(newCoordinatorKeypair.privKey.serialize()); - expect(poll.coordinatorKeypair.pubKey.serialize()).to.deep.eq(newCoordinatorKeypair.pubKey.serialize()); - }); - }); - - describe("setNumSignups", () => { - it("should update the number of signups", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - maciState.signUp(new Keypair().pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); - - const poll = maciState.polls.get(pollId)!; - - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - expect(poll.getNumSignups()).to.eq(2n); - - // update it again - poll.setNumSignups(3n); - - expect(poll.getNumSignups()).to.eq(3n); - }); - }); - - describe("toJSON", () => { - it("should return the correct JSON", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - const pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - const poll = maciState.polls.get(pollId)!; - const json = poll.toJSON(); - - const pollFromJson = Poll.fromJSON(json, maciState); - pollFromJson.setCoordinatorKeypair(coordinatorKeypair.privKey.serialize()); - expect(pollFromJson.equals(poll)).to.eq(true); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/core/__tests__/e2e.test.ts b/packages/hardhat/maci-ts/core/__tests__/e2e.test.ts deleted file mode 100644 index 9bfa2a4..0000000 --- a/packages/hardhat/maci-ts/core/__tests__/e2e.test.ts +++ /dev/null @@ -1,894 +0,0 @@ -import { expect } from "chai"; -import { hash5, NOTHING_UP_MY_SLEEVE, IncrementalQuinTree, AccQueue } from "../../crypto"; -import { PCommand, Keypair, StateLeaf, blankStateLeafHash } from "../../domainobjs"; - -import { MaciState } from "../MaciState"; -import { Poll } from "../Poll"; -import { STATE_TREE_DEPTH, STATE_TREE_ARITY } from "../utils/constants"; -import { packProcessMessageSmallVals, unpackProcessMessageSmallVals } from "../utils/utils"; - -import { - coordinatorKeypair, - duration, - maxValues, - messageBatchSize, - treeDepths, - voiceCreditBalance, -} from "./utils/constants"; -import { TestHarness, calculateTotal } from "./utils/utils"; - -describe("MaciState/Poll e2e", function test() { - this.timeout(300000); - - describe("key changes", () => { - const user1Keypair = new Keypair(); - const user2Keypair = new Keypair(); - const user1SecondKeypair = new Keypair(); - const user2SecondKeypair = new Keypair(); - let pollId: bigint; - let user1StateIndex: number; - let user2StateIndex: number; - const user1VoteOptionIndex = 0n; - const user2VoteOptionIndex = 1n; - const user1VoteWeight = 9n; - const user2VoteWeight = 3n; - const user1NewVoteWeight = 5n; - const user2NewVoteWeight = 7n; - - describe("only user 1 changes key", () => { - const maciState: MaciState = new MaciState(STATE_TREE_DEPTH); - - before(() => { - // Sign up - user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); - user2StateIndex = maciState.signUp( - user2Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); - - // deploy a poll - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - maciState.polls.get(pollId)?.updatePoll(BigInt(maciState.stateLeaves.length)); - }); - - it("should submit a vote for each user", () => { - const poll = maciState.polls.get(pollId)!; - const command1 = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, - user1VoteOptionIndex, - user1VoteWeight, - 1n, - BigInt(pollId), - ); - - const signature1 = command1.sign(user1Keypair.privKey); - - const ecdhKeypair1 = new Keypair(); - const sharedKey1 = Keypair.genEcdhSharedKey(ecdhKeypair1.privKey, coordinatorKeypair.pubKey); - - const message1 = command1.encrypt(signature1, sharedKey1); - poll.publishMessage(message1, ecdhKeypair1.pubKey); - - const command2 = new PCommand( - BigInt(user2StateIndex), - user2Keypair.pubKey, - user2VoteOptionIndex, - user2VoteWeight, - 1n, - BigInt(pollId), - ); - - const signature2 = command2.sign(user2Keypair.privKey); - - const ecdhKeypair2 = new Keypair(); - const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); - - const message2 = command2.encrypt(signature2, sharedKey2); - poll.publishMessage(message2, ecdhKeypair2.pubKey); - }); - - it("user1 sends a keychange message with a new vote", () => { - const poll = maciState.polls.get(pollId)!; - const command = new PCommand( - BigInt(user1StateIndex), - user1SecondKeypair.pubKey, - user1VoteOptionIndex, - user1NewVoteWeight, - 1n, - BigInt(pollId), - ); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - }); - - it("should perform the processing and tallying correctly", () => { - const poll = maciState.polls.get(pollId)!; - poll.processMessages(pollId); - poll.tallyVotes(); - expect(poll.perVOSpentVoiceCredits[0].toString()).to.eq((user1NewVoteWeight * user1NewVoteWeight).toString()); - expect(poll.perVOSpentVoiceCredits[1].toString()).to.eq((user2VoteWeight * user2VoteWeight).toString()); - }); - - it("should confirm that the user key pair was changed (user's 2 one has not)", () => { - const poll = maciState.polls.get(pollId)!; - const stateLeaf1 = poll.stateLeaves[user1StateIndex]; - const stateLeaf2 = poll.stateLeaves[user2StateIndex]; - expect(stateLeaf1.pubKey.equals(user1SecondKeypair.pubKey)).to.eq(true); - expect(stateLeaf2.pubKey.equals(user2Keypair.pubKey)).to.eq(true); - }); - }); - - describe("both users change key", () => { - const maciState: MaciState = new MaciState(STATE_TREE_DEPTH); - let poll: Poll; - - before(() => { - // Sign up - user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); - user2StateIndex = maciState.signUp( - user2Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); - - // deploy a poll - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - }); - it("should submit a vote for each user", () => { - const command1 = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, - user1VoteOptionIndex, - user1VoteWeight, - 1n, - BigInt(pollId), - ); - - const signature1 = command1.sign(user1Keypair.privKey); - - const ecdhKeypair1 = new Keypair(); - const sharedKey1 = Keypair.genEcdhSharedKey(ecdhKeypair1.privKey, coordinatorKeypair.pubKey); - - const message1 = command1.encrypt(signature1, sharedKey1); - poll.publishMessage(message1, ecdhKeypair1.pubKey); - - const command2 = new PCommand( - BigInt(user2StateIndex), - user2Keypair.pubKey, - user2VoteOptionIndex, - user2VoteWeight, - 1n, - BigInt(pollId), - ); - - const signature2 = command2.sign(user2Keypair.privKey); - - const ecdhKeypair2 = new Keypair(); - const sharedKey2 = Keypair.genEcdhSharedKey(ecdhKeypair2.privKey, coordinatorKeypair.pubKey); - - const message2 = command2.encrypt(signature2, sharedKey2); - poll.publishMessage(message2, ecdhKeypair2.pubKey); - }); - - it("user1 sends a keychange message with a new vote", () => { - const command = new PCommand( - BigInt(user1StateIndex), - user1SecondKeypair.pubKey, - user1VoteOptionIndex, - user1NewVoteWeight, - 1n, - BigInt(pollId), - ); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - }); - - it("user2 sends a keychange message with a new vote", () => { - const command = new PCommand( - BigInt(user2StateIndex), - user2SecondKeypair.pubKey, - user2VoteOptionIndex, - user2NewVoteWeight, - 1n, - BigInt(pollId), - ); - - const signature = command.sign(user2Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - }); - - it("should perform the processing and tallying correctly", () => { - poll.processMessages(pollId); - poll.tallyVotes(); - expect(poll.perVOSpentVoiceCredits[0].toString()).to.eq((user1NewVoteWeight * user1NewVoteWeight).toString()); - expect(poll.perVOSpentVoiceCredits[1].toString()).to.eq((user2NewVoteWeight * user2NewVoteWeight).toString()); - }); - - it("should confirm that the users key pairs were changed", () => { - const stateLeaf1 = poll.stateLeaves[user1StateIndex]; - const stateLeaf2 = poll.stateLeaves[user2StateIndex]; - expect(stateLeaf1.pubKey.equals(user1SecondKeypair.pubKey)).to.eq(true); - expect(stateLeaf2.pubKey.equals(user2SecondKeypair.pubKey)).to.eq(true); - }); - }); - - describe("user1 changes key, but messages are in different batches", () => { - const maciState = new MaciState(STATE_TREE_DEPTH); - let poll: Poll; - - before(() => { - // Sign up - user1StateIndex = maciState.signUp( - user1Keypair.pubKey, - voiceCreditBalance, - BigInt(Math.floor(Date.now() / 1000)), - ); - - // deploy a poll - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - }); - - it("should submit a vote for one user in one batch", () => { - const command1 = new PCommand( - BigInt(user1StateIndex), - user1Keypair.pubKey, - user1VoteOptionIndex, - user1VoteWeight, - 1n, - BigInt(pollId), - ); - - const signature1 = command1.sign(user1Keypair.privKey); - - const ecdhKeypair1 = new Keypair(); - const sharedKey1 = Keypair.genEcdhSharedKey(ecdhKeypair1.privKey, coordinatorKeypair.pubKey); - - const message1 = command1.encrypt(signature1, sharedKey1); - poll.publishMessage(message1, ecdhKeypair1.pubKey); - }); - - it("should fill the batch with random messages", () => { - for (let i = 0; i < messageBatchSize - 1; i += 1) { - const command = new PCommand( - 1n, - user1Keypair.pubKey, - user1VoteOptionIndex, - user1VoteWeight, - 2n, - BigInt(pollId), - ); - - const signature = command.sign(user1Keypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - } - }); - - it("should submit a new message in a new batch", () => { - const command1 = new PCommand( - BigInt(user1StateIndex), - user1SecondKeypair.pubKey, - user1VoteOptionIndex, - user1NewVoteWeight, - 1n, - BigInt(pollId), - ); - - const signature1 = command1.sign(user1Keypair.privKey); - - const ecdhKeypair1 = new Keypair(); - const sharedKey1 = Keypair.genEcdhSharedKey(ecdhKeypair1.privKey, coordinatorKeypair.pubKey); - - const message1 = command1.encrypt(signature1, sharedKey1); - poll.publishMessage(message1, ecdhKeypair1.pubKey); - }); - - it("should perform the processing and tallying correctly", () => { - poll.processAllMessages(); - poll.tallyVotes(); - expect(poll.perVOSpentVoiceCredits[0].toString()).to.eq((user1NewVoteWeight * user1NewVoteWeight).toString()); - }); - - it("should confirm that the user key pair was changed", () => { - const stateLeaf1 = poll.stateLeaves[user1StateIndex]; - expect(stateLeaf1.pubKey.equals(user1SecondKeypair.pubKey)).to.eq(true); - }); - }); - }); - - describe("Process and tally 1 message from 1 user", () => { - let maciState: MaciState; - let pollId: bigint; - let poll: Poll; - let msgTree: IncrementalQuinTree; - let stateTree: IncrementalQuinTree; - const voteWeight = 9n; - const voteOptionIndex = 0n; - let stateIndex: number; - const userKeypair = new Keypair(); - - before(() => { - maciState = new MaciState(STATE_TREE_DEPTH); - msgTree = new IncrementalQuinTree(treeDepths.messageTreeDepth, NOTHING_UP_MY_SLEEVE, 5, hash5); - stateTree = new IncrementalQuinTree(STATE_TREE_DEPTH, blankStateLeafHash, STATE_TREE_ARITY, hash5); - - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - }); - - // The end result should be that option 0 gets 3 votes - // because the user spends 9 voice credits on it - it("the state root should be correct", () => { - const timestamp = BigInt(Math.floor(Date.now() / 1000)); - const stateLeaf = new StateLeaf(userKeypair.pubKey, voiceCreditBalance, timestamp); - - stateIndex = maciState.signUp(userKeypair.pubKey, voiceCreditBalance, timestamp); - stateTree.insert(blankStateLeafHash); - stateTree.insert(stateLeaf.hash()); - - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - expect(stateIndex.toString()).to.eq("1"); - expect(stateTree.root.toString()).to.eq(poll.stateTree?.root.toString()); - }); - - it("the message root should be correct", () => { - const command = new PCommand( - BigInt(stateIndex), - userKeypair.pubKey, - voteOptionIndex, - voteWeight, - 1n, - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - - poll.publishMessage(message, ecdhKeypair.pubKey); - msgTree.insert(message.hash(ecdhKeypair.pubKey)); - - // Use the accumulator queue to compare the root of the message tree - const accumulatorQueue: AccQueue = new AccQueue( - treeDepths.messageTreeSubDepth, - STATE_TREE_ARITY, - NOTHING_UP_MY_SLEEVE, - ); - accumulatorQueue.enqueue(message.hash(ecdhKeypair.pubKey)); - accumulatorQueue.mergeSubRoots(0); - accumulatorQueue.merge(treeDepths.messageTreeDepth); - - expect(accumulatorQueue.getRoot(treeDepths.messageTreeDepth)?.toString()).to.eq(msgTree.root.toString()); - }); - - it("packProcessMessageSmallVals and unpackProcessMessageSmallVals", () => { - const maxVoteOptions = 1n; - const numUsers = 2n; - const batchStartIndex = 5; - const batchEndIndex = 10; - const packedVals = packProcessMessageSmallVals(maxVoteOptions, numUsers, batchStartIndex, batchEndIndex); - - const unpacked = unpackProcessMessageSmallVals(packedVals); - expect(unpacked.maxVoteOptions.toString()).to.eq(maxVoteOptions.toString()); - expect(unpacked.numUsers.toString()).to.eq(numUsers.toString()); - expect(unpacked.batchStartIndex.toString()).to.eq(batchStartIndex.toString()); - expect(unpacked.batchEndIndex.toString()).to.eq(batchEndIndex.toString()); - }); - - it("Process a batch of messages (though only 1 message is in the batch)", () => { - poll.processMessages(pollId); - - // Check the ballot - expect(poll.ballots[1].votes[Number(voteOptionIndex)].toString()).to.eq(voteWeight.toString()); - // Check the state leaf in the poll - expect(poll.stateLeaves[1].voiceCreditBalance.toString()).to.eq( - (voiceCreditBalance - voteWeight * voteWeight).toString(), - ); - }); - - it("Tally ballots", () => { - const initialTotal = calculateTotal(poll.tallyResult); - expect(initialTotal.toString()).to.eq("0"); - - expect(poll.hasUntalliedBallots()).to.eq(true); - - poll.tallyVotes(); - - const finalTotal = calculateTotal(poll.tallyResult); - expect(finalTotal.toString()).to.eq(voteWeight.toString()); - }); - }); - - describe(`Process and tally ${messageBatchSize * 2} messages from ${messageBatchSize} users`, () => { - let maciState: MaciState; - let pollId: bigint; - let poll: Poll; - const voteWeight = 9n; - - const users: Keypair[] = []; - - before(() => { - maciState = new MaciState(STATE_TREE_DEPTH); - // Sign up and vote - for (let i = 0; i < messageBatchSize - 1; i += 1) { - const userKeypair = new Keypair(); - users.push(userKeypair); - - maciState.signUp(userKeypair.pubKey, voiceCreditBalance, BigInt(Math.floor(Date.now() / 1000))); - } - - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - poll = maciState.polls.get(pollId)!; - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - }); - - it("should process votes correctly", () => { - // 24 valid votes - for (let i = 0; i < messageBatchSize - 1; i += 1) { - const userKeypair = users[i]; - - const command = new PCommand( - BigInt(i + 1), - userKeypair.pubKey, - BigInt(i), // vote option index - voteWeight, - 1n, - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - } - - expect(poll.messages.length).to.eq(messageBatchSize - 1); - - // 24 invalid votes - for (let i = 0; i < messageBatchSize - 1; i += 1) { - const userKeypair = users[i]; - const command = new PCommand( - BigInt(i + 1), - userKeypair.pubKey, - BigInt(i), // vote option index - voiceCreditBalance * 2n, // invalid vote weight - 1n, - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - poll.publishMessage(message, ecdhKeypair.pubKey); - } - - // 48 messages in total - expect(poll.messages.length).to.eq(2 * (messageBatchSize - 1)); - - expect(poll.currentMessageBatchIndex).to.eq(undefined); - expect(poll.numBatchesProcessed).to.eq(0); - - // Process messages - poll.processMessages(pollId); - - // currentMessageBatchIndex is 0 because the current batch starts - // with index 0. - expect(poll.currentMessageBatchIndex).to.eq(0); - expect(poll.numBatchesProcessed).to.eq(1); - - // Process messages - poll.processMessages(pollId); - - expect(poll.currentMessageBatchIndex).to.eq(0); - expect(poll.numBatchesProcessed).to.eq(2); - - for (let i = 1; i < messageBatchSize; i += 1) { - const leaf = poll.ballots[i].votes[i - 1]; - expect(leaf.toString()).to.eq(voteWeight.toString()); - } - - // Test processAllMessages - const r = poll.processAllMessages(); - - expect(r.stateLeaves.length).to.eq(poll.stateLeaves.length); - - expect(r.ballots.length).to.eq(poll.ballots.length); - - expect(r.ballots.length).to.eq(r.stateLeaves.length); - - for (let i = 0; i < r.stateLeaves.length; i += 1) { - expect(r.stateLeaves[i].equals(poll.stateLeaves[i])).to.eq(true); - - expect(r.ballots[i].equals(poll.ballots[i])).to.eq(true); - } - }); - - it("should tally ballots correctly", () => { - // Start with tallyResult = [0...0] - const total = calculateTotal(poll.tallyResult); - expect(total.toString()).to.eq("0"); - - // Check that there are untallied results - expect(poll.hasUntalliedBallots()).to.eq(true); - - // First batch tally - poll.tallyVotes(); - - // Recall that each user `i` cast the same number of votes for - // their option `i` - for (let i = 0; i < poll.tallyResult.length - 1; i += 1) { - expect(poll.tallyResult[i].toString()).to.eq(voteWeight.toString()); - } - - expect(poll.hasUntalliedBallots()).to.eq(false); - - expect(() => { - poll.tallyVotes(); - }).to.throw(); - }); - }); - - describe("Process and tally with non quadratic voting", () => { - let maciState: MaciState; - let pollId: bigint; - let poll: Poll; - let msgTree: IncrementalQuinTree; - let stateTree: IncrementalQuinTree; - const voteWeight = 9n; - const voteOptionIndex = 0n; - let stateIndex: number; - const userKeypair = new Keypair(); - const useQv = false; - - before(() => { - maciState = new MaciState(STATE_TREE_DEPTH); - msgTree = new IncrementalQuinTree(treeDepths.messageTreeDepth, NOTHING_UP_MY_SLEEVE, 5, hash5); - stateTree = new IncrementalQuinTree(STATE_TREE_DEPTH, blankStateLeafHash, STATE_TREE_ARITY, hash5); - - pollId = maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - coordinatorKeypair, - ); - - poll = maciState.polls.get(pollId)!; - - const timestamp = BigInt(Math.floor(Date.now() / 1000)); - const stateLeaf = new StateLeaf(userKeypair.pubKey, voiceCreditBalance, timestamp); - - stateIndex = maciState.signUp(userKeypair.pubKey, voiceCreditBalance, timestamp); - stateTree.insert(blankStateLeafHash); - stateTree.insert(stateLeaf.hash()); - - poll.updatePoll(BigInt(maciState.stateLeaves.length)); - - const command = new PCommand( - BigInt(stateIndex), - userKeypair.pubKey, - voteOptionIndex, - voteWeight, - 1n, - BigInt(pollId), - ); - - const signature = command.sign(userKeypair.privKey); - - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - - poll.publishMessage(message, ecdhKeypair.pubKey); - msgTree.insert(message.hash(ecdhKeypair.pubKey)); - }); - - it("Process a batch of messages (though only 1 message is in the batch)", () => { - poll.processMessages(pollId, useQv); - - // Check the ballot - expect(poll.ballots[1].votes[Number(voteOptionIndex)].toString()).to.eq(voteWeight.toString()); - // Check the state leaf in the poll - expect(poll.stateLeaves[1].voiceCreditBalance.toString()).to.eq((voiceCreditBalance - voteWeight).toString()); - }); - - it("Tally ballots", () => { - const initialTotal = calculateTotal(poll.tallyResult); - expect(initialTotal.toString()).to.eq("0"); - - expect(poll.hasUntalliedBallots()).to.eq(true); - - poll.tallyVotesNonQv(); - - const finalTotal = calculateTotal(poll.tallyResult); - expect(finalTotal.toString()).to.eq(voteWeight.toString()); - - // check that the totalSpentVoiceCredits is correct - expect(poll.totalSpentVoiceCredits.toString()).to.eq(voteWeight.toString()); - }); - }); - - describe("Sanity checks", () => { - let testHarness: TestHarness; - let poll: Poll; - - beforeEach(() => { - testHarness = new TestHarness(); - - poll = testHarness.poll; - }); - - it("should process a valid message", () => { - const voteOptionIndex = 0n; - const voteWeight = 9n; - const nonce = 1n; - - const users = testHarness.createUsers(1); - testHarness.vote(users[0], testHarness.getStateIndex(users[0]), voteOptionIndex, voteWeight, nonce); - testHarness.finalizePoll(); - - const messageLengthResult = poll.messages.length; - const expectedNumVotes = users.length; - expect(messageLengthResult).to.eq(expectedNumVotes); - - const tallyResult = poll.tallyResult[0]; - const expectedTallyResult = 9n; - expect(tallyResult).to.eq(expectedTallyResult); - }); - - it("should not process messages twice", () => { - const voteOptionIndex = 0n; - const voteWeight = 9n; - const nonce = 1n; - - const users = testHarness.createUsers(1); - testHarness.vote(users[0], testHarness.getStateIndex(users[0]), voteOptionIndex, voteWeight, nonce); - - poll.updatePoll(BigInt(testHarness.maciState.stateLeaves.length)); - poll.processMessages(testHarness.pollId); - - expect(() => { - poll.processMessages(testHarness.pollId); - }).to.throw("No more messages to process"); - - poll.tallyVotes(); - - const messageLengthResult = poll.messages.length; - const expectedNumVotes = users.length; - expect(messageLengthResult).to.eq(expectedNumVotes); - - const tallyResult = poll.tallyResult[0]; - const expectedTallyResult = 9n; - expect(tallyResult).to.eq(expectedTallyResult); - }); - - it("should not process a message with an incorrect nonce", () => { - const voteOptionIndex = 0n; - const voteWeight = 9n; - - const users = testHarness.createUsers(5); - // generate a bunch of invalid votes with nonces that are not 1 - let nonce: bigint; - users.forEach(user => { - do { - nonce = BigInt(Math.floor(Math.random() * 100) - 50); - } while (nonce === 1n); - - testHarness.vote(user, testHarness.getStateIndex(user), voteOptionIndex, voteWeight, nonce); - }); - - testHarness.finalizePoll(); - - const messageLengthResult = poll.messages.length; - const expectedNumVotes = users.length; - expect(messageLengthResult).to.eq(expectedNumVotes); - - const tallyResult = poll.tallyResult[0]; - const expectedTallyResult = 0n; - expect(tallyResult).to.eq(expectedTallyResult); - }); - - // note: When voting, the voice credit is used. The amount of voice credit used is - // the square of the vote weight. Since the maximum voice credit is 100 here, - // the vote weight can only be a value between 1 and 10 - // (as these are the square roots of numbers up to 100). - it("should not process a message with an incorrect vote weight", () => { - const voteOptionIndex = 0n; - const nonce = 1n; - - const users = testHarness.createUsers(5); - - // generate a bunch of invalid votes with vote weights that are not between 1 and 10 - let voteWeight: bigint; - users.forEach(user => { - do { - voteWeight = BigInt(Math.floor(Math.random() * 100) - 50); - } while (voteWeight >= 1n && voteWeight <= 10n); - - testHarness.vote(user, testHarness.getStateIndex(user), voteOptionIndex, voteWeight, nonce); - }); - - testHarness.finalizePoll(); - - const messageLengthResult = poll.messages.length; - const expectedNumVotes = users.length; - expect(messageLengthResult).to.eq(expectedNumVotes); - - const tallyResult = poll.tallyResult[0]; - const expectedTallyResult = 0n; - expect(tallyResult).to.eq(expectedTallyResult); - }); - - it("should not process a message with an incorrect state tree index", () => { - const voteOptionIndex = 0n; - const nonce = 1n; - const voteWeight = 9n; - const numVotes = 5; - - const users = testHarness.createUsers(5); - - users.forEach(user => { - // generate a bunch of invalid votes with incorrect state tree index - testHarness.vote(user, testHarness.getStateIndex(user) + 1, voteOptionIndex, voteWeight, nonce); - }); - - testHarness.finalizePoll(); - - const messageLengthResult = poll.messages.length; - const expectedNumVotes = numVotes; - expect(messageLengthResult).to.eq(expectedNumVotes); - - const tallyResult = poll.tallyResult[0]; - const expectedTallyResult = 0n; - expect(tallyResult).to.eq(expectedTallyResult); - }); - - it("should not process a message with an incorrect signature", () => { - const voteOptionIndex = 0n; - const voteWeight = 9n; - const nonce = 1n; - - const users = testHarness.createUsers(2); - - const { command } = testHarness.createCommand( - users[0], - testHarness.getStateIndex(users[0]), - voteOptionIndex, - voteWeight, - nonce, - ); - - // create an invalid signature - const { signature: invalidSignature } = testHarness.createCommand( - users[1], - testHarness.getStateIndex(users[0]), - voteOptionIndex, - voteWeight, - nonce, - ); - - // sign the command with the invalid signature - const { message, encPubKey } = testHarness.createMessage( - command, - invalidSignature, - testHarness.coordinatorKeypair, - ); - - testHarness.poll.publishMessage(message, encPubKey); - testHarness.finalizePoll(); - - const messageLengthResult = poll.messages.length; - const expectedNumVotes = users.length - 1; - expect(messageLengthResult).to.eq(expectedNumVotes); - - const tallyResult = poll.tallyResult[0]; - const expectedTallyResult = 0n; - expect(tallyResult).to.eq(expectedTallyResult); - }); - - it("should not process a message with an invalid coordinator key", () => { - const voteOptionIndex = 0n; - const voteWeight = 9n; - const nonce = 1n; - - const users = testHarness.createUsers(1); - - const { command, signature } = testHarness.createCommand( - users[0], - testHarness.getStateIndex(users[0]), - voteOptionIndex, - voteWeight, - nonce, - ); - - const { message, encPubKey } = testHarness.createMessage(command, signature, new Keypair()); - - testHarness.poll.publishMessage(message, encPubKey); - testHarness.finalizePoll(); - - const messageLengthResult = poll.messages.length; - const expectedNumVotes = users.length; - expect(messageLengthResult).to.eq(expectedNumVotes); - - const tallyResult = poll.tallyResult[0]; - const expectedTallyResult = 0n; - expect(tallyResult).to.eq(expectedTallyResult); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/core/__tests__/utils.test.ts b/packages/hardhat/maci-ts/core/__tests__/utils.test.ts deleted file mode 100644 index 964aa89..0000000 --- a/packages/hardhat/maci-ts/core/__tests__/utils.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { expect } from "chai"; - -import { - genProcessVkSig, - genTallyVkSig, - packProcessMessageSmallVals, - unpackProcessMessageSmallVals, - packTallyVotesSmallVals, - unpackTallyVotesSmallVals, -} from "../utils/utils"; - -describe("Utils", () => { - it("genProcessVkSig should work", () => { - const result = genProcessVkSig(1, 2, 3, 4); - expect(result).to.equal(25108406941546723055683440059751604127909689873435325366275n); - }); - - it("genTallyVkSig should work", () => { - const result = genTallyVkSig(1, 2, 3); - expect(result).to.equal(340282366920938463500268095579187314691n); - }); - - it("packProcessMessageSmallVals should work", () => { - const result = packProcessMessageSmallVals(1n, 2n, 3, 4); - expect(result).to.equal(5708990770823843327184944562488436835454287873n); - }); - - it("unpackProcessMessageSmallVals should work", () => { - const result = unpackProcessMessageSmallVals(5708990770823843327184944562488436835454287873n); - expect(result).to.deep.equal({ - maxVoteOptions: 1n, - numUsers: 2n, - batchStartIndex: 3n, - batchEndIndex: 4n, - }); - }); - - it("packTallyVotesSmallVals should work", () => { - const result = packTallyVotesSmallVals(1, 2, 3); - expect(result).to.equal(3377699720527872n); - }); - - it("unpackTallyVotesSmallVals should work", () => { - const result = unpackTallyVotesSmallVals(3377699720527872n); - expect(result).to.deep.equal({ - numSignUps: 3n, - batchStartIndex: 0n, - }); - }); -}); diff --git a/packages/hardhat/maci-ts/core/__tests__/utils/constants.ts b/packages/hardhat/maci-ts/core/__tests__/utils/constants.ts deleted file mode 100644 index b9cef9e..0000000 --- a/packages/hardhat/maci-ts/core/__tests__/utils/constants.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Keypair } from "../../../domainobjs"; - -export const voiceCreditBalance = 100n; -export const duration = 30; -export const messageBatchSize = 25; -export const coordinatorKeypair = new Keypair(); -export const maxValues = { - maxUsers: 25, - maxMessages: 25, - maxVoteOptions: 25, -}; - -export const treeDepths = { - intStateTreeDepth: 2, - messageTreeDepth: 3, - messageTreeSubDepth: 2, - voteOptionTreeDepth: 4, -}; diff --git a/packages/hardhat/maci-ts/core/__tests__/utils/utils.ts b/packages/hardhat/maci-ts/core/__tests__/utils/utils.ts deleted file mode 100644 index ca331a5..0000000 --- a/packages/hardhat/maci-ts/core/__tests__/utils/utils.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { Signature } from "../../../crypto"; -import { PCommand, Message, Keypair, PubKey } from "../../../domainobjs"; - -import { MaciState } from "../../MaciState"; -import { Poll } from "../../Poll"; -import { STATE_TREE_DEPTH } from "../../utils/constants"; - -import { duration, maxValues, messageBatchSize, treeDepths, voiceCreditBalance } from "./constants"; - -/** - * Calculates the total of a tally result - * @param tallyResult - the tally result - * @returns the total of the tally result - */ -export const calculateTotal = (tallyResult: bigint[]): bigint => tallyResult.reduce((acc, v) => acc + v, 0n); - -/** - * A test harness for the MACI contract. - */ -export class TestHarness { - maciState = new MaciState(STATE_TREE_DEPTH); - - coordinatorKeypair = new Keypair(); - - poll: Poll; - - pollId: bigint; - - users: Keypair[] = []; - - stateIndices = new Map(); - - /** - * Constructs a new TestHarness object. - */ - constructor() { - this.pollId = this.maciState.deployPoll( - BigInt(Math.floor(Date.now() / 1000) + duration), - maxValues, - treeDepths, - messageBatchSize, - this.coordinatorKeypair, - ); - this.poll = this.maciState.polls.get(this.pollId)!; - } - - /** - * Creates a number of users and signs them up to the MACI state tree. - * @param numUsers - The number of users to create. - * @returns The keypairs of the newly created users. - */ - createUsers = (numUsers: number): Keypair[] => { - for (let i = 0; i < numUsers; i += 1) { - const user = new Keypair(); - this.users.push(user); - const stateIndex = this.signup(user); - this.stateIndices.set(user, stateIndex); - } - return this.users; - }; - - /** - * Signs up a user to the MACI state tree. - * @param user - The keypair of the user. - * @returns The index of the newly signed-up user in the state tree. - */ - signup = (user: Keypair): number => { - const timestamp = BigInt(Math.floor(Date.now() / 1000)); - const stateIndex = this.maciState.signUp(user.pubKey, voiceCreditBalance, timestamp); - return stateIndex; - }; - - /** - * Publishes a message to the MACI poll instance. - * @param user - The keypair of the user. - * @param stateIndex - The index of the user in the state tree. - * @param voteOptionIndex - The index of the vote option. - * @param voteWeight - The weight of the vote. - * @param nonce - The nonce of the vote. - */ - vote = (user: Keypair, stateIndex: number, voteOptionIndex: bigint, voteWeight: bigint, nonce: bigint): void => { - const { command, signature } = this.createCommand(user, stateIndex, voteOptionIndex, voteWeight, nonce); - - const { message, encPubKey } = this.createMessage(command, signature, this.coordinatorKeypair); - - this.poll.publishMessage(message, encPubKey); - }; - - /** - * Creates a message from a command and signature. - * @param command - The command to be encrypted. - * @param signature - The signature of the command signer. - * @param coordinatorKeypair - The keypair of the MACI round coordinator. - * @returns The message and the ephemeral public key used to encrypt the message. - */ - createMessage = ( - command: PCommand, - signature: Signature, - coordinatorKeypair: Keypair, - ): { message: Message; encPubKey: PubKey } => { - const ecdhKeypair = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, coordinatorKeypair.pubKey); - const message = command.encrypt(signature, sharedKey); - return { message, encPubKey: ecdhKeypair.pubKey }; - }; - - /** - * Creates a command and signature. - * @param user - The keypair of the user. - * @param stateIndex - The index of the user in the state tree. - * @param voteOptionIndex - The index of the vote option. - * @param voteWeight - The weight of the vote. - * @param nonce - The nonce of the vote. - * @returns The command and signature of the command. - */ - createCommand = ( - user: Keypair, - stateIndex: number, - voteOptionIndex: bigint, - voteWeight: bigint, - nonce: bigint, - ): { command: PCommand; signature: Signature } => { - const command = new PCommand( - BigInt(stateIndex), - user.pubKey, - voteOptionIndex, - voteWeight, - nonce, - BigInt(this.pollId), - ); - - const signature = command.sign(user.privKey); - - return { command, signature }; - }; - - /** - * Finalizes the poll. - * This processes all messages and tallies the votes. - * This should be called after all votes have been cast. - */ - finalizePoll = (): void => { - this.poll.updatePoll(BigInt(this.maciState.stateLeaves.length)); - this.poll.processMessages(this.pollId); - this.poll.tallyVotes(); - }; - - /** - * Returns the state index of a signed-up user. - * @param user - The keypair of the user. - * @returns The state index of the user. - */ - getStateIndex = (user: Keypair): number => this.stateIndices.get(user) || -1; -} diff --git a/packages/hardhat/maci-ts/core/index.ts b/packages/hardhat/maci-ts/core/index.ts deleted file mode 100644 index 0df07c8..0000000 --- a/packages/hardhat/maci-ts/core/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -export { MaciState } from "./MaciState"; - -export { Poll } from "./Poll"; - -export { - genProcessVkSig, - genTallyVkSig, - packProcessMessageSmallVals, - unpackProcessMessageSmallVals, - packTallyVotesSmallVals, - unpackTallyVotesSmallVals, -} from "./utils/utils"; - -export type { - ITallyCircuitInputs, - IProcessMessagesCircuitInputs, - CircuitInputs, - MaxValues, - TreeDepths, - BatchSizes, - IJsonMaciState, -} from "./utils/types"; - -export { STATE_TREE_ARITY } from "./utils/constants"; diff --git a/packages/hardhat/maci-ts/core/utils/constants.ts b/packages/hardhat/maci-ts/core/utils/constants.ts deleted file mode 100644 index 6e06744..0000000 --- a/packages/hardhat/maci-ts/core/utils/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const STATE_TREE_DEPTH = 10; -export const STATE_TREE_ARITY = 5; -export const STATE_TREE_SUBDEPTH = 2; -export const MESSAGE_TREE_ARITY = 5; -export const VOTE_OPTION_TREE_ARITY = 5; diff --git a/packages/hardhat/maci-ts/core/utils/errors.ts b/packages/hardhat/maci-ts/core/utils/errors.ts deleted file mode 100644 index 3eb7e3f..0000000 --- a/packages/hardhat/maci-ts/core/utils/errors.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * An enum describing the possible errors that can occur - * in Poll.processMessage() - */ -export enum ProcessMessageErrors { - InvalidCommand = "invalid command", - InvalidStateLeafIndex = "invalid state leaf index", - InvalidSignature = "invalid signature", - InvalidNonce = "invalid nonce", - InsufficientVoiceCredits = "insufficient voice credits", - InvalidVoteOptionIndex = "invalid vote option index", - FailedDecryption = "failed decryption due to either wrong encryption public key or corrupted ciphertext", -} - -/** - * A class which extends the Error class - * which is to be used when an error occurs - * in Poll.processMessage() - */ -export class ProcessMessageError extends Error { - /** - * Generate a new instance of the ProcessMessageError class - * @param code - the error code - */ - constructor(public code: ProcessMessageErrors) { - super(code); - this.name = this.constructor.name; - } -} diff --git a/packages/hardhat/maci-ts/core/utils/types.ts b/packages/hardhat/maci-ts/core/utils/types.ts deleted file mode 100644 index b153b9d..0000000 --- a/packages/hardhat/maci-ts/core/utils/types.ts +++ /dev/null @@ -1,197 +0,0 @@ -import type { MaciState } from "../MaciState"; -import type { Poll } from "../Poll"; -import type { PathElements } from "../../crypto"; -import type { - Ballot, - IJsonBallot, - IJsonCommand, - IJsonPCommand, - IJsonStateLeaf, - IJsonTCommand, - Keypair, - Message, - PCommand, - PubKey, - StateLeaf, - TCommand, -} from "../../domainobjs"; - -/** - * A circuit inputs for the circom circuit - */ -export type CircuitInputs = Record; - -/** - * This interface defines the tree depths. - * @property intStateTreeDepth - The depth of the intermediate state tree. - * @property messageTreeDepth - The depth of the message tree. - * @property messageTreeSubDepth - The depth of the message tree sub. - * @property voteOptionTreeDepth - The depth of the vote option tree. - */ -export interface TreeDepths { - intStateTreeDepth: number; - messageTreeDepth: number; - messageTreeSubDepth: number; - voteOptionTreeDepth: number; -} - -/** - * This interface defines the batch sizes. - * @property tallyBatchSize - The size of the tally batch. - * @property messageBatchSize - The size of the message batch. - */ -export interface BatchSizes { - tallyBatchSize: number; - messageBatchSize: number; -} - -/** - * This interface defines the maximum values that the circuit can handle. - * @property maxMessages - The maximum number of messages. - * @property maxVoteOptions - The maximum number of vote options. - */ -export interface MaxValues { - maxMessages: number; - maxVoteOptions: number; -} - -/** - * Represents the public API of the MaciState class. - */ -export interface IMaciState { - // This method is used for signing up users to the state tree. - signUp(pubKey: PubKey, initialVoiceCreditBalance: bigint, timestamp: bigint): number; - // This method is used for deploying poll. - deployPoll( - pollEndTimestamp: bigint, - maxValues: MaxValues, - treeDepths: TreeDepths, - messageBatchSize: number, - coordinatorKeypair: Keypair, - ): bigint; - // These methods are helper functions. - deployNullPoll(): void; - copy(): MaciState; - equals(m: MaciState): boolean; - toJSON(): IJsonMaciState; -} - -/** - * An interface which represents the public API of the Poll class. - */ -export interface IPoll { - // These methods are used for sending a message to the poll from user - publishMessage(message: Message, encPubKey: PubKey): void; - topupMessage(message: Message): void; - // These methods are used to generate circuit inputs - processMessages(pollId: bigint): IProcessMessagesCircuitInputs; - tallyVotes(): ITallyCircuitInputs; - // These methods are helper functions - hasUnprocessedMessages(): boolean; - processAllMessages(): { stateLeaves: StateLeaf[]; ballots: Ballot[] }; - hasUntalliedBallots(): boolean; - copy(): Poll; - equals(p: Poll): boolean; - toJSON(): IJsonPoll; - setCoordinatorKeypair(serializedPrivateKey: string): void; -} - -/** - * This interface defines the JSON representation of a Poll - */ -export interface IJsonPoll { - pollEndTimestamp: string; - treeDepths: TreeDepths; - batchSizes: BatchSizes; - maxValues: MaxValues; - messages: unknown[]; - commands: IJsonCommand[] | IJsonTCommand[] | IJsonPCommand[]; - ballots: IJsonBallot[]; - encPubKeys: string[]; - currentMessageBatchIndex: number; - stateLeaves: IJsonStateLeaf[]; - results: string[]; - numBatchesProcessed: number; - numSignups: string; -} - -/** - * This interface defines the JSON representation of a MaciState - */ -export interface IJsonMaciState { - stateTreeDepth: number; - polls: IJsonPoll[]; - stateLeaves: IJsonStateLeaf[]; - pollBeingProcessed: boolean; - currentPollBeingProcessed: string; - numSignUps: number; -} - -/** - * An interface describing the output of the processMessage function - */ -export interface IProcessMessagesOutput { - stateLeafIndex?: number; - newStateLeaf?: StateLeaf; - originalStateLeaf?: StateLeaf; - originalStateLeafPathElements?: PathElements; - originalVoteWeight?: bigint; - originalVoteWeightsPathElements?: PathElements; - newBallot?: Ballot; - originalBallot?: Ballot; - originalBallotPathElements?: PathElements; - command?: PCommand | TCommand; -} - -/** - * An interface describing the circuit inputs to the ProcessMessage circuit - */ -export interface IProcessMessagesCircuitInputs { - pollEndTimestamp: string; - packedVals: string; - msgRoot: string; - msgs: string[]; - msgSubrootPathElements: string[][]; - coordPrivKey: string; - coordPubKey: string; - encPubKeys: string[]; - currentStateRoot: string; - currentBallotRoot: string; - currentSbCommitment: string; - currentSbSalt: string; - currentStateLeaves: string[]; - currentStateLeavesPathElements: string[][]; - currentBallots: string[]; - currentBallotsPathElements: string[][]; - currentVoteWeights: string[]; - currentVoteWeightsPathElements: string[][]; - inputHash: string; - newSbSalt: string; - newSbCommitment: string; -} - -/** - * An interface describing the circuit inputs to the TallyVotes circuit - */ -export interface ITallyCircuitInputs { - stateRoot: string; - ballotRoot: string; - sbSalt: string; - sbCommitment: string; - currentTallyCommitment: string; - newTallyCommitment: string; - packedVals: string; - inputHash: string; - ballots: string[]; - ballotPathElements: PathElements; - votes: string[][]; - currentResults: string[]; - currentResultsRootSalt: string; - currentSpentVoiceCreditSubtotal: string; - currentSpentVoiceCreditSubtotalSalt: string; - currentPerVOSpentVoiceCredits?: string[]; - currentPerVOSpentVoiceCreditsRootSalt?: string; - newResultsRootSalt: string; - newPerVOSpentVoiceCreditsRootSalt?: string; - newSpentVoiceCreditSubtotalSalt: string; -} diff --git a/packages/hardhat/maci-ts/core/utils/utils.ts b/packages/hardhat/maci-ts/core/utils/utils.ts deleted file mode 100644 index 14b1622..0000000 --- a/packages/hardhat/maci-ts/core/utils/utils.ts +++ /dev/null @@ -1,125 +0,0 @@ -/* eslint-disable no-bitwise */ -import assert from "assert"; - -/** - * This function generates the signature of a ProcessMessage Verifying Key(VK). - * This can be used to check if a ProcessMessages' circuit VK is registered - * in a smart contract that holds several VKs. - * @param stateTreeDepth - The depth of the state tree. - * @param messageTreeDepth - The depth of the message tree. - * @param voteOptionTreeDepth - The depth of the vote option tree. - * @param batchSize - The size of the batch. - * @returns Returns a signature for querying if a verifying key with the given parameters is already registered in the contract. - */ -export const genProcessVkSig = ( - stateTreeDepth: number, - messageTreeDepth: number, - voteOptionTreeDepth: number, - batchSize: number, -): bigint => - (BigInt(batchSize) << 192n) + - (BigInt(stateTreeDepth) << 128n) + - (BigInt(messageTreeDepth) << 64n) + - BigInt(voteOptionTreeDepth); - -/** - * This function generates the signature of a Tally Verifying Key(VK). - * This can be used to check if a TallyVotes' circuit VK is registered - * in a smart contract that holds several VKs. - * @param _stateTreeDepth - The depth of the state tree. - * @param _intStateTreeDepth - The depth of the intermediate state tree. - * @param _voteOptionTreeDepth - The depth of the vote option tree. - * @returns Returns a signature for querying if a verifying key with - * the given parameters is already registered in the contract. - */ -export const genTallyVkSig = ( - _stateTreeDepth: number, - _intStateTreeDepth: number, - _voteOptionTreeDepth: number, -): bigint => (BigInt(_stateTreeDepth) << 128n) + (BigInt(_intStateTreeDepth) << 64n) + BigInt(_voteOptionTreeDepth); - -/** - * This function packs it's parameters into a single bigint. - * @param maxVoteOptions - The maximum number of vote options. - * @param numUsers - The number of users. - * @param batchStartIndex - The start index of the batch. - * @param batchEndIndex - The end index of the batch. - * @returns Returns a single bigint that contains the packed values. - */ -export const packProcessMessageSmallVals = ( - maxVoteOptions: bigint, - numUsers: bigint, - batchStartIndex: number, - batchEndIndex: number, -): bigint => { - const packedVals = - // Note: the << operator has lower precedence than + - BigInt(`${maxVoteOptions}`) + - (BigInt(`${numUsers}`) << 50n) + - (BigInt(batchStartIndex) << 100n) + - (BigInt(batchEndIndex) << 150n); - - return packedVals; -}; - -/** - * This function unpacks partial values for the ProcessMessages circuit from a single bigint. - * @param packedVals - The single bigint that contains the packed values. - * @returns Returns an object that contains the unpacked values. - */ -export const unpackProcessMessageSmallVals = ( - packedVals: bigint, -): { - maxVoteOptions: bigint; - numUsers: bigint; - batchStartIndex: bigint; - batchEndIndex: bigint; -} => { - let asBin = packedVals.toString(2); - assert(asBin.length <= 200); - while (asBin.length < 200) { - asBin = `0${asBin}`; - } - const maxVoteOptions = BigInt(`0b${asBin.slice(150, 200)}`); - const numUsers = BigInt(`0b${asBin.slice(100, 150)}`); - const batchStartIndex = BigInt(`0b${asBin.slice(50, 100)}`); - const batchEndIndex = BigInt(`0b${asBin.slice(0, 50)}`); - - return { - maxVoteOptions, - numUsers, - batchStartIndex, - batchEndIndex, - }; -}; - -/** - * This function packs it's parameters into a single bigint. - * @param batchStartIndex - The start index of the batch. - * @param batchSize - The size of the batch. - * @param numSignUps - The number of signups. - * @returns Returns a single bigint that contains the packed values. - */ -export const packTallyVotesSmallVals = (batchStartIndex: number, batchSize: number, numSignUps: number): bigint => { - // Note: the << operator has lower precedence than + - const packedVals = BigInt(batchStartIndex) / BigInt(batchSize) + (BigInt(numSignUps) << 50n); - - return packedVals; -}; - -/** - * This function unpacks partial values for the TallyVotes circuit from a single bigint. - * @param packedVals - The single bigint that contains the packed values. - * @returns Returns an object that contains the unpacked values. - */ -export const unpackTallyVotesSmallVals = (packedVals: bigint): { numSignUps: bigint; batchStartIndex: bigint } => { - let asBin = packedVals.toString(2); - assert(asBin.length <= 100); - while (asBin.length < 100) { - asBin = `0${asBin}`; - } - const numSignUps = BigInt(`0b${asBin.slice(0, 50)}`); - const batchStartIndex = BigInt(`0b${asBin.slice(50, 100)}`); - - return { numSignUps, batchStartIndex }; -}; diff --git a/packages/hardhat/maci-ts/crypto/AccQueue.ts b/packages/hardhat/maci-ts/crypto/AccQueue.ts deleted file mode 100644 index 4687bd6..0000000 --- a/packages/hardhat/maci-ts/crypto/AccQueue.ts +++ /dev/null @@ -1,627 +0,0 @@ -import assert from "assert"; - -import type { Leaf, Queue, StringifiedBigInts } from "./types"; - -import { deepCopyBigIntArray, stringifyBigInts, unstringifyBigInts } from "./bigIntUtils"; -import { sha256Hash, hashLeftRight, hash5 } from "./hashing"; -import { IncrementalQuinTree } from "./quinTree"; -import { calcDepthFromNumLeaves } from "./utils"; - -/** - * An Accumulator Queue which conforms to the implementation in AccQueue.sol. - * Each enqueue() operation updates a subtree, and a merge() operation combines - * all subtrees into a main tree. - * @notice It supports 2 or 5 elements per leaf. - */ -export class AccQueue { - private MAX_DEPTH = 32; - - // The depth per subtree - private subDepth: number; - - // The number of inputs per hash function - private hashLength: number; - - // The default value for empty leaves - private zeroValue: bigint; - - // The current subtree index. e.g. the first subtree has index 0, the - // second has 1, and so on - private currentSubtreeIndex = 0; - - // The number of leaves across all subtrees - private numLeaves = 0; - - // The current subtree - private leafQueue: Queue = { - levels: new Map(), - indices: [], - }; - - // For merging subtrees into the smallest tree - private nextSRindexToQueue = 0; - - private smallSRTroot = 0n; - - private subRootQueue: Queue = { - levels: new Map(), - indices: [], - }; - - // The root of each complete subtree - private subRoots: Leaf[] = []; - - // The root of merged subtrees - private mainRoots: Leaf[] = []; - - // The zero value per level. i.e. zeros[0] is zeroValue, - // zeros[1] is the hash of leavesPerNode zeros, and so on. - private zeros: bigint[] = []; - - // Whether the subtrees have been merged - private subTreesMerged = false; - - // The hash function to use for the subtrees - readonly subHashFunc: (leaves: Leaf[]) => bigint; - - // The hash function to use for rest of the tree (above the subroots) - readonly hashFunc: (leaves: Leaf[]) => bigint; - - /** - * Create a new instance of AccQueue - * @param subDepth - the depth of the subtrees - * @param hashLength - the number of leaves per node - * @param zeroValue - the default value for empty leaves - */ - constructor(subDepth: number, hashLength: number, zeroValue: bigint) { - // This class supports either 2 leaves per node, or 5 leaves per node. - // 5 is largest number of inputs which circomlib's Poseidon EVM hash - // function implementation supports. - - assert(hashLength === 2 || hashLength === 5); - assert(subDepth > 0); - - this.hashLength = hashLength; - this.subDepth = subDepth; - this.zeroValue = zeroValue; - - // Set this.hashFunc depending on the number of leaves per node - if (this.hashLength === 2) { - // Uses PoseidonT3 under the hood, which accepts 2 inputs - this.hashFunc = (inputs: bigint[]) => hashLeftRight(inputs[0], inputs[1]); - } else { - // Uses PoseidonT6 under the hood, which accepts up to 5 inputs - this.hashFunc = hash5; - } - - this.subHashFunc = sha256Hash; - - let hashed = this.zeroValue; - for (let i = 0; i < this.MAX_DEPTH; i += 1) { - this.zeros.push(hashed); - - let e: bigint[] = []; - if (this.hashLength === 2) { - e = [0n]; - hashed = this.hashFunc([hashed, hashed]); - } else { - e = [0n, 0n, 0n, 0n]; - hashed = this.hashFunc([hashed, hashed, hashed, hashed, hashed]); - } - - const levels = new Map(Object.entries(e).map(([key, value]) => [Number(key), value])); - - this.leafQueue.levels.set(this.leafQueue.levels.size, levels); - this.leafQueue.indices[i] = 0; - this.subRootQueue.levels.set(this.subRootQueue.levels.size, levels); - this.subRootQueue.indices[i] = 0; - } - } - - /** - * Get the small SRT root - * @returns small SRT root - */ - getSmallSRTroot(): bigint { - return this.smallSRTroot; - } - - /** - * Get the subroots - * @returns subroots - */ - getSubRoots(): Leaf[] { - return this.subRoots; - } - - /** - * Get the subdepth - * @returns subdepth - */ - getSubDepth(): number { - return this.subDepth; - } - - /** - * Get the root of merged subtrees - * @returns the root of merged subtrees - */ - getMainRoots(): Leaf[] { - return this.mainRoots; - } - - /** - * Get the zero values per level. i.e. zeros[0] is zeroValue, - * zeros[1] is the hash of leavesPerNode zeros, and so on. - * @returns zeros - */ - getZeros(): bigint[] { - return this.zeros; - } - - /** - * Get the subroot at a given index - * @param index - The index of the subroot - * @returns the subroot - */ - getSubRoot(index: number): Leaf { - return this.subRoots[index]; - } - - /** - * Get the number of inputs per hash function - * - * @returns the number of inputs - */ - getHashLength(): number { - return this.hashLength; - } - - /** - * Enqueue a leaf into the current subtree - * @param leaf The leaf to insert. - * @returns The index of the leaf - */ - enqueue(leaf: Leaf): number { - // validation - assert(this.numLeaves < this.hashLength ** this.MAX_DEPTH, "AccQueue is full"); - - this.enqueueOp(leaf, 0); - - // the index is the number of leaves (0-index) - const leafIndex = this.numLeaves; - - // increase the number of leaves - this.numLeaves += 1; - // we set merged false because there are new leaves - this.subTreesMerged = false; - // reset the smallSRTroot because it is obsolete - this.smallSRTroot = 0n; - - // @todo this can be moved in the constructor rather than computing every time - const subTreeCapacity = this.hashLength ** this.subDepth; - // If the current subtree is full - if (this.numLeaves % subTreeCapacity === 0) { - // store the subroot - const subRoot = this.leafQueue.levels.get(this.subDepth)?.get(0) ?? 0n; - - this.subRoots[this.currentSubtreeIndex] = subRoot; - this.currentSubtreeIndex += 1; - // reset the current subtree - this.leafQueue.levels.get(this.subDepth)?.set(0, 0n); - - for (let i = 0; i < this.MAX_DEPTH; i += 1) { - this.leafQueue.indices[i] = 0; - } - } - - return leafIndex; - } - - /** - * Private function that performs the actual enqueue operation - * @param leaf - The leaf to insert - * @param level - The level of the subtree - */ - private enqueueOp = (leaf: Leaf, level: number) => { - // small validation, do no throw - if (level > this.subDepth) { - return; - } - - // get the index to determine where to insert the next leaf - const n = this.leafQueue.indices[level]; - - // we check that the index is not the last one (1 or 4 depending on the hash length) - if (n !== this.hashLength - 1) { - // Just store the leaf - this.leafQueue.levels.get(level)?.set(n, leaf); - this.leafQueue.indices[level] += 1; - } else { - // if not we compute the root - let hashed: bigint; - if (this.hashLength === 2) { - const subRoot = this.leafQueue.levels.get(level)?.get(0) ?? 0n; - hashed = this.hashFunc([subRoot, leaf]); - this.leafQueue.levels.get(level)?.set(0, 0n); - } else { - const levelSlice = this.leafQueue.levels.get(level) ?? new Map(); - hashed = this.hashFunc(Array.from(levelSlice.values()).concat(leaf)); - - for (let i = 0; i < 4; i += 1) { - this.leafQueue.levels.get(level)?.set(i, 0n); - } - } - - this.leafQueue.indices[level] = 0; - - // Recurse - this.enqueueOp(hashed, level + 1); - } - }; - - /** - * Fill any empty leaves of the last subtree with zeros and store the - * resulting subroot. - */ - fill(): void { - // The total capacity of the subtree - const subTreeCapacity = this.hashLength ** this.subDepth; - - if (this.numLeaves % subTreeCapacity === 0) { - // If the subtree is completely empty, then the subroot is a - // precalculated zero value - this.subRoots[this.currentSubtreeIndex] = this.zeros[this.subDepth]; - } else { - this.fillOp(0); - - // Store the subroot - const subRoot = this.leafQueue.levels.get(this.subDepth)?.get(0) ?? 0n; - this.subRoots[this.currentSubtreeIndex] = subRoot; - - // Blank out the subtree data - for (let i = 0; i < this.subDepth + 1; i += 1) { - if (this.hashLength === 2) { - this.leafQueue.levels.get(i)?.set(0, 0n); - } else { - const levels = new Map(Object.entries([0n, 0n, 0n, 0n]).map(([key, value]) => [Number(key), value])); - this.leafQueue.levels.set(i, levels); - } - } - } - - // Update the subtree index - this.currentSubtreeIndex += 1; - - // Update the number of leaves - this.numLeaves = this.currentSubtreeIndex * subTreeCapacity; - - this.subTreesMerged = false; - this.smallSRTroot = 0n; - } - - /** - * Private function that performs the actual fill operation - * @param level - The level of the subtree - */ - private fillOp(level: number) { - if (level > this.subDepth) { - return; - } - - const n = this.leafQueue.indices[level]; - - if (n !== 0) { - // Fill the subtree level and hash it - let hashed: bigint; - if (this.hashLength === 2) { - hashed = this.hashFunc([this.leafQueue.levels.get(level)?.get(0) ?? 0n, this.zeros[level]]); - } else { - for (let i = n; i < this.hashLength; i += 1) { - this.leafQueue.levels.get(level)?.set(i, this.zeros[level]); - } - - const levelSlice = this.leafQueue.levels.get(level) ?? new Map(); - hashed = this.hashFunc(Array.from(levelSlice.values())); - } - - // Update the subtree from the next level onwards with the new leaf - this.enqueueOp(hashed, level + 1); - - // Reset the current level - this.leafQueue.indices[level] = 0; - } - - // Recurse - this.fillOp(level + 1); - } - - /** - * Calculate the depth of the smallest possible Merkle tree which fits all - * @returns the depth of the smallest possible Merkle tree which fits all - */ - calcSRTdepth(): number { - // Calculate the SRT depth - let srtDepth = this.subDepth; - const subTreeCapacity = this.hashLength ** this.subDepth; - while (this.hashLength ** srtDepth < this.subRoots.length * subTreeCapacity) { - srtDepth += 1; - } - - return srtDepth; - } - - /** - * Insert a subtree into the queue. This is used when the subtree is - * already computed. - * @param subRoot - The root of the subtree - */ - insertSubTree(subRoot: bigint): void { - // If the current subtree is not full, fill it. - const subTreeCapacity = this.hashLength ** this.subDepth; - - this.subRoots[this.currentSubtreeIndex] = subRoot; - - // Update the subtree index - this.currentSubtreeIndex += 1; - - // Update the number of leaves - this.numLeaves += subTreeCapacity; - - // Reset the subroot tree root now that it is obsolete - this.smallSRTroot = 0n; - - this.subTreesMerged = false; - } - - /** - * Merge all the subroots into a tree of a specified depth. - * It requires this.mergeSubRoots() to be run first. - */ - merge(depth: number): void { - assert(this.subTreesMerged); - assert(depth <= this.MAX_DEPTH); - - const srtDepth = this.calcSRTdepth(); - - assert(depth >= srtDepth); - - if (depth === srtDepth) { - this.mainRoots[depth] = this.smallSRTroot; - } else { - let root = this.smallSRTroot; - - // Calculate the main root - for (let i = srtDepth; i < depth; i += 1) { - const inputs: bigint[] = [root]; - const z = this.zeros[i]; - - for (let j = 1; j < this.hashLength; j += 1) { - inputs.push(z); - } - - root = this.hashFunc(inputs); - } - - this.mainRoots[depth] = root; - } - } - - /** - * Merge all the subroots into a tree of a specified depth. - * Uses an IncrementalQuinTree instead of the two-step method that - * AccQueue.sol uses. - */ - mergeDirect(depth: number): void { - // There must be subtrees to merge - assert(this.numLeaves > 0); - - const srtDepth = this.calcSRTdepth(); - - // The desired tree must be deep enough - assert(depth >= srtDepth); - - if (depth === this.subDepth) { - // If there is only 1 subtree, and the desired depth is the subtree - // depth, the subroot is the result - assert(this.numLeaves === this.hashLength ** this.subDepth); - const [subRoot] = this.subRoots; - this.mainRoots[depth] = subRoot; - this.subTreesMerged = true; - return; - } - - // The desired main tree must be deep enough to fit all leaves - assert(BigInt(depth ** this.hashLength) >= this.numLeaves); - - // Fill any empty leaves in the last subtree with zeros - if (this.numLeaves % this.hashLength ** this.subDepth > 0) { - this.fill(); - } - - const tree = new IncrementalQuinTree( - depth - this.subDepth, - this.zeros[this.subDepth], - this.hashLength, - this.hashFunc, - ); - - this.subRoots.forEach(subRoot => { - tree.insert(subRoot); - }); - - this.mainRoots[depth] = tree.root; - } - - /** - * Merge all subroots into the smallest possible Merkle tree which fits - * them. e.g. if there are 5 subroots and hashLength == 2, the tree depth - * is 3 since 2 ** 3 = 8 which is the next power of 2. - * @param numSrQueueOps - The number of subroots to queue into the SRT - */ - mergeSubRoots(numSrQueueOps = 0): void { - // This function can only be called once unless a new subtree is created - assert(!this.subTreesMerged); - - // There must be subtrees to merge - assert(this.numLeaves > 0); - - // Fill any empty leaves in the last subtree with zeros - if (this.numLeaves % this.hashLength ** this.subDepth !== 0) { - this.fill(); - } - - // If there is only 1 subtree, use its root - if (this.currentSubtreeIndex === 1) { - this.smallSRTroot = this.getSubRoot(0); - this.subTreesMerged = true; - return; - } - - // Compute the depth and maximum capacity of the smallMainTreeRoot - const depth = calcDepthFromNumLeaves(this.hashLength, this.currentSubtreeIndex); - - let numQueueOps = 0; - - for (let i = this.nextSRindexToQueue; i < this.currentSubtreeIndex; i += 1) { - // Stop if the limit has been reached - if (numSrQueueOps !== 0 && numQueueOps === numSrQueueOps) { - return; - } - - // Queue the next subroot - const subRoot = this.getSubRoot(this.nextSRindexToQueue); - this.queueSubRoot(subRoot, 0, depth); - - // Increment the next subroot counter - this.nextSRindexToQueue += 1; - numQueueOps += 1; - } - - // Queue zeros to get the SRT. `m` is the number of leaves in the - // main tree, which already has `this.currentSubtreeIndex` leaves - const m = this.hashLength ** depth; - if (this.nextSRindexToQueue === this.currentSubtreeIndex) { - for (let i = this.currentSubtreeIndex; i < m; i += 1) { - const z = this.zeros[this.subDepth]; - this.queueSubRoot(z, 0, depth); - } - } - - // Store the root - const subRoot = this.subRootQueue.levels.get(depth)?.get(0) ?? 0n; - this.smallSRTroot = subRoot; - this.subTreesMerged = true; - } - - /** - * Queues the leaf (a subroot) into queuedSRTlevels - * @param leaf - The leaf to insert - * @param level - The level of the subtree - * @param maxDepth - The maximum depth of the tree - */ - private queueSubRoot(leaf: bigint, level: number, maxDepth: number) { - if (level > maxDepth) { - return; - } - - const n = this.subRootQueue.indices[level]; - - if (n !== this.hashLength - 1) { - // Just store the leaf - this.subRootQueue.levels.get(level)?.set(n, leaf); - this.subRootQueue.indices[level] += 1; - } else { - // Hash the elements in this level and queue it in the next level - const inputs: bigint[] = []; - for (let i = 0; i < this.hashLength - 1; i += 1) { - inputs.push(this.subRootQueue.levels.get(level)?.get(i) ?? 0n); - } - inputs.push(leaf); - const hashed = this.hashFunc(inputs); - - // Recurse - this.subRootQueue.indices[level] = 0; - this.queueSubRoot(hashed, level + 1, maxDepth); - } - } - - /** - * Get the root at a certain depth - * @param depth - The depth of the tree - * @returns the root - */ - getRoot(depth: number): bigint | null | undefined { - return this.mainRoots[depth]; - } - - /** - * Check if the root at a certain depth exists (subtree root) - * @param depth - the depth of the tree - * @returns whether the root exists - */ - hasRoot(depth: number): boolean { - const root = this.getRoot(depth); - return !(root === null || root === undefined); - } - - /** - * @notice Deep-copies this object - * @returns a deep copy of this object - */ - copy(): AccQueue { - const newAccQueue = new AccQueue(this.subDepth, this.hashLength, this.zeroValue); - newAccQueue.currentSubtreeIndex = JSON.parse(JSON.stringify(this.currentSubtreeIndex)) as number; - newAccQueue.numLeaves = JSON.parse(JSON.stringify(this.numLeaves)) as number; - - const arrayLeafLevels = unstringifyBigInts( - JSON.parse(JSON.stringify(stringifyBigInts(this.mapToArray(this.leafQueue.levels)))) as StringifiedBigInts, - ) as bigint[][]; - newAccQueue.leafQueue.levels = this.arrayToMap(arrayLeafLevels); - newAccQueue.leafQueue.indices = JSON.parse(JSON.stringify(this.leafQueue.indices)) as number[]; - newAccQueue.subRoots = deepCopyBigIntArray(this.subRoots); - newAccQueue.mainRoots = deepCopyBigIntArray(this.mainRoots); - newAccQueue.zeros = deepCopyBigIntArray(this.zeros); - newAccQueue.subTreesMerged = !!this.subTreesMerged; - newAccQueue.nextSRindexToQueue = Number(this.nextSRindexToQueue.toString()); - newAccQueue.smallSRTroot = BigInt(this.smallSRTroot.toString()); - newAccQueue.subRootQueue.indices = JSON.parse(JSON.stringify(this.subRootQueue.indices)) as number[]; - - const arraySubRootLevels = unstringifyBigInts( - JSON.parse(JSON.stringify(stringifyBigInts(this.mapToArray(this.subRootQueue.levels)))) as StringifiedBigInts, - ) as bigint[][]; - newAccQueue.subRootQueue.levels = this.arrayToMap(arraySubRootLevels); - - return newAccQueue; - } - - /** - * Convert map to 2D array - * - * @param map - map representation of 2D array - * @returns 2D array - */ - private mapToArray(map: Map>): bigint[][] { - return Array.from(map.values()).map(v => Array.from(v.values())); - } - - /** - * Convert 2D array to its map representation - * - * @param array - 2D array - * @returns map representation of 2D array - */ - private arrayToMap(array: bigint[][]): Map> { - return new Map(array.map((level, i) => [i, new Map(level.map((leaf, j) => [j, leaf]))])); - } - - /** - * Hash an array of leaves - * @param leaves - The leaves to hash - * @returns the hash value of the leaves - */ - hash(leaves: bigint[]): bigint { - assert(leaves.length === this.hashLength); - return this.hashFunc(leaves); - } -} diff --git a/packages/hardhat/maci-ts/crypto/__benchmarks__/index.ts b/packages/hardhat/maci-ts/crypto/__benchmarks__/index.ts deleted file mode 100644 index a93b565..0000000 --- a/packages/hardhat/maci-ts/crypto/__benchmarks__/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import runTrees from "./suites/trees"; - -runTrees(); diff --git a/packages/hardhat/maci-ts/crypto/__benchmarks__/suites/trees.ts b/packages/hardhat/maci-ts/crypto/__benchmarks__/suites/trees.ts deleted file mode 100644 index 3108c3e..0000000 --- a/packages/hardhat/maci-ts/crypto/__benchmarks__/suites/trees.ts +++ /dev/null @@ -1,40 +0,0 @@ -import benny from "benny"; - -import { IncrementalQuinTree, hash5 } from "../../index"; - -const NAME = "merkle-trees"; - -export default function runTrees(): void { - const treeDepth = 2; - const numberOfLeaves = 5 ** treeDepth; - - benny.suite( - NAME, - - benny.add(`MACI - insert, update, generate and verify proof for ${numberOfLeaves} leaves`, () => { - const tree5 = new IncrementalQuinTree(treeDepth, BigInt(0), 5, hash5); - - for (let i = 0; i < numberOfLeaves; i += 1) { - tree5.insert(BigInt(i)); - } - - for (let i = 0; i < numberOfLeaves; i += 1) { - tree5.update(i, BigInt(0)); - } - - tree5.verifyProof(tree5.genProof(5)); - }), - - benny.cycle(), - benny.complete(results => { - results.results.forEach(result => { - // eslint-disable-next-line no-console - console.log(`${result.name}: mean time: ${result.details.mean.toFixed(2)}`); - }); - }), - - benny.save({ folder: "ts/__benchmarks__/results", file: NAME, version: "1.0.0", details: true }), - benny.save({ folder: "ts/__benchmarks__/results", file: NAME, format: "chart.html", details: true }), - benny.save({ folder: "ts/__benchmarks__/results", file: NAME, format: "table.html", details: true }), - ); -} diff --git a/packages/hardhat/maci-ts/crypto/__tests__/AccQueue.test.ts b/packages/hardhat/maci-ts/crypto/__tests__/AccQueue.test.ts deleted file mode 100644 index 9ac7e61..0000000 --- a/packages/hardhat/maci-ts/crypto/__tests__/AccQueue.test.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { expect } from "chai"; - -import { IncrementalQuinTree, AccQueue } from ".."; - -import { testMerge, testMergeExhaustive, testMergeShortest, testMergeShortestOne } from "./utils"; - -describe("AccQueue", function test() { - this.timeout(100000); - - describe("Enqueue", () => { - describe("Binary AccQueue", () => { - const HASH_LENGTH = 2; - const SUB_DEPTH = 2; - const ZERO = BigInt(0); - - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - - it("should enqueue leaves into a subtree", () => { - const tree0 = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - const subtreeCapacity = HASH_LENGTH ** SUB_DEPTH; - for (let i = 0; i < subtreeCapacity; i += 1) { - const leaf = BigInt(i + 1); - tree0.insert(leaf); - aq.enqueue(leaf); - } - expect(aq.getSubRoot(0).toString()).to.eq(tree0.root.toString()); - }); - - it("should enqueue another subtree", () => { - const tree1 = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - const subtreeCapacity = HASH_LENGTH ** SUB_DEPTH; - for (let i = 0; i < subtreeCapacity; i += 1) { - const leaf = BigInt(i + 1); - tree1.insert(leaf); - aq.enqueue(leaf); - } - expect(aq.getSubRoot(1).toString()).to.eq(tree1.root.toString()); - }); - }); - - describe("Quinary AccQueue", () => { - const HASH_LENGTH = 5; - const SUB_DEPTH = 2; - const ZERO = BigInt(0); - - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - - it("should enqueue leaves into a subtree", () => { - const tree0 = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - const subtreeCapacity = HASH_LENGTH ** SUB_DEPTH; - for (let i = 0; i < subtreeCapacity; i += 1) { - const leaf = BigInt(i + 1); - tree0.insert(leaf); - aq.enqueue(leaf); - } - expect(aq.getSubRoot(0).toString()).to.eq(tree0.root.toString()); - - const tree1 = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - for (let i = 0; i < subtreeCapacity; i += 1) { - const leaf = BigInt(i + 1); - tree1.insert(leaf); - aq.enqueue(leaf); - } - expect(aq.getSubRoot(1).toString()).to.eq(tree1.root.toString()); - }); - }); - }); - - describe("Fill", () => { - describe("Binary AccQueue", () => { - const HASH_LENGTH = 2; - const SUB_DEPTH = 2; - const ZERO = BigInt(0); - - it("Filling an empty subtree should create the correct subroot", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - aq.fill(); - expect(aq.getSubRoot(0).toString()).to.eq(tree.root.toString()); - }); - - it("should fill an incomplete subtree", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - const leaf = BigInt(1); - aq.enqueue(leaf); - tree.insert(leaf); - - aq.fill(); - - expect(aq.getSubRoot(0).toString()).to.eq(tree.root.toString()); - }); - - it("Filling an empty subtree again should create the correct subroot", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const leaf = BigInt(1); - - // Create the first subtree with one leaf - aq.enqueue(leaf); - aq.fill(); - - // Fill the second subtree with zeros - aq.fill(); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - expect(aq.getSubRoot(1).toString()).to.eq(tree.root.toString()); - }); - - it("fill() should be correct for every number of leaves in an incomplete subtree", () => { - for (let i = 0; i < 2; i += 1) { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - for (let j = 0; j < i; j += 1) { - const leaf = BigInt(i + 1); - aq.enqueue(leaf); - tree.insert(leaf); - } - aq.fill(); - - expect(aq.getSubRoot(0).toString()).to.eq(tree.root.toString()); - } - }); - }); - - describe("Quinary AccQueue", () => { - const HASH_LENGTH = 5; - const SUB_DEPTH = 2; - const ZERO = BigInt(0); - - it("Filling an empty subtree should create the correct subroot", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - aq.fill(); - expect(aq.getSubRoot(0).toString()).to.eq(tree.root.toString()); - }); - - it("should fill one incomplete subtree", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - const leaf = BigInt(1); - aq.enqueue(leaf); - tree.insert(leaf); - - aq.fill(); - - expect(aq.getSubRoot(0).toString()).to.eq(tree.root.toString()); - }); - - it("Filling an empty subtree again should create the correct subroot", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const leaf = BigInt(1); - - // Create the first subtree with one leaf - aq.enqueue(leaf); - aq.fill(); - - // Fill the second subtree with zeros - aq.fill(); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - expect(aq.getSubRoot(1).toString()).to.eq(tree.root.toString()); - }); - - it("fill() should be correct for every number of leaves in an incomplete subtree", () => { - const capacity = HASH_LENGTH ** SUB_DEPTH; - for (let i = 1; i < capacity - 1; i += 1) { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const tree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - for (let j = 0; j < i; j += 1) { - const leaf = BigInt(i + 1); - aq.enqueue(leaf); - tree.insert(leaf); - } - aq.fill(); - - expect(aq.getSubRoot(0).toString()).to.eq(tree.root.toString()); - } - }); - }); - }); - - describe("Merge", () => { - const SUB_DEPTH = 2; - const ZERO = BigInt(0); - const NUM_SUBTREES = 5; - const MAIN_DEPTH = 5; - - describe("Binary AccQueue", () => { - const HASH_LENGTH = 2; - - describe("merge()", () => { - it("should produce the correct main root", () => { - testMerge(SUB_DEPTH, HASH_LENGTH, ZERO, NUM_SUBTREES, MAIN_DEPTH); - }); - }); - - describe("mergeSubRoots()", () => { - it("should work progressively", () => { - testMergeShortest(SUB_DEPTH, HASH_LENGTH, ZERO, NUM_SUBTREES); - }); - - it("should fail if there are 0 leaves", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - expect(() => { - aq.mergeSubRoots(0); - }).to.throw(); - }); - - it("should a generate the same smallMainTreeRoot root from 1 subroot", () => { - testMergeShortestOne(SUB_DEPTH, HASH_LENGTH, ZERO); - }); - - it("Exhaustive test from 2 to 16 subtrees", () => { - const MAX = 16; - testMergeExhaustive(SUB_DEPTH, HASH_LENGTH, ZERO, MAX); - }); - }); - }); - - describe("Quinary AccQueue", () => { - const HASH_LENGTH = 5; - - describe("merge()", () => { - it("should produce the correct main root", () => { - testMerge(SUB_DEPTH, HASH_LENGTH, ZERO, NUM_SUBTREES, MAIN_DEPTH); - }); - }); - - describe("mergeSubRoots()", () => { - it("should work progressively", () => { - testMergeShortest(SUB_DEPTH, HASH_LENGTH, ZERO, NUM_SUBTREES); - }); - - it("should fail if there are 0 leaves", () => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - expect(() => { - aq.mergeSubRoots(0); - }).to.throw(); - }); - - it("should a generate the same smallMainTreeRoot root from 1 subroot", () => { - testMergeShortestOne(SUB_DEPTH, HASH_LENGTH, ZERO); - }); - - it("Exhaustive test from 2 to 16 subtrees", () => { - const MAX = 16; - testMergeExhaustive(SUB_DEPTH, HASH_LENGTH, ZERO, MAX); - }); - }); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/crypto/__tests__/Crypto.test.ts b/packages/hardhat/maci-ts/crypto/__tests__/Crypto.test.ts deleted file mode 100644 index 81327db..0000000 --- a/packages/hardhat/maci-ts/crypto/__tests__/Crypto.test.ts +++ /dev/null @@ -1,578 +0,0 @@ -import { expect } from "chai"; - -import { G1Point, G2Point, genRandomBabyJubValue } from "../babyjub"; -import { SNARK_FIELD_SIZE } from "../constants"; -import { - sha256Hash, - hash2, - hash3, - hash4, - hash5, - hash13, - hashLeftRight, - hashN, - hashOne, - poseidonT3, - poseidonT4, - poseidonT5, - poseidonT6, -} from "../hashing"; -import { genPubKey, genKeypair, genEcdhSharedKey, genRandomSalt, genPrivKey, packPubKey, unpackPubKey } from "../keys"; - -describe("Crypto", function test() { - this.timeout(100000); - - describe("G1Point", () => { - it("should create a new G1Point", () => { - const g1 = new G1Point(BigInt(1), BigInt(2)); - expect(g1.x).to.eq(BigInt(1)); - expect(g1.y).to.eq(BigInt(2)); - }); - it("equals should return true for equal G1Point instances", () => { - const g1 = new G1Point(BigInt(1), BigInt(2)); - const g2 = new G1Point(BigInt(1), BigInt(2)); - expect(g1.equals(g2)).to.eq(true); - }); - it("equals should return false for different G1Point instances", () => { - const g1 = new G1Point(BigInt(1), BigInt(2)); - const g2 = new G1Point(BigInt(2), BigInt(1)); - expect(g1.equals(g2)).to.eq(false); - }); - it("asContractParam should return the G1Point instance as an object with x and y properties", () => { - const g1 = new G1Point(BigInt(1), BigInt(2)); - const g1Obj = g1.asContractParam(); - expect(g1Obj.x).to.eq("1"); - expect(g1Obj.y).to.eq("2"); - expect(Object.keys(g1Obj).length).to.eq(2); - expect(Object.keys(g1Obj)).to.deep.eq(["x", "y"]); - }); - }); - describe("G2Point", () => { - it("should create a new G2Point", () => { - const g2 = new G2Point([BigInt(1)], [BigInt(2)]); - expect(g2.x).to.deep.eq([BigInt(1)]); - expect(g2.y).to.deep.eq([BigInt(2)]); - }); - it("equals should return true for equal G2Point instances", () => { - const g1 = new G2Point([BigInt(1)], [BigInt(2)]); - const g2 = new G2Point([BigInt(1)], [BigInt(2)]); - expect(g1.equals(g2)).to.eq(true); - }); - it("equals should return false for different G2Point instances", () => { - const g1 = new G2Point([BigInt(1)], [BigInt(2)]); - const g2 = new G2Point([BigInt(2)], [BigInt(1)]); - expect(g1.equals(g2)).to.eq(false); - }); - it("asContractParam should return the G2Point instance as an object with x and y properties", () => { - const g2 = new G2Point([BigInt(1)], [BigInt(2)]); - const g2Obj = g2.asContractParam(); - expect(g2Obj.x).to.deep.eq(["1"]); - expect(g2Obj.y).to.deep.eq(["2"]); - expect(Object.keys(g2Obj).length).to.eq(2); - expect(Object.keys(g2Obj)).to.deep.eq(["x", "y"]); - }); - }); - describe("sha256Hash", () => { - it("should return a hash of the input", () => { - const res = sha256Hash([BigInt(1), BigInt(2)]); - expect(res).to.not.eq(BigInt(0)); - }); - it("should produce the same hash for the same input", () => { - const res1 = sha256Hash([BigInt(1), BigInt(2)]); - const res2 = sha256Hash([BigInt(1), BigInt(2)]); - expect(res1).to.eq(res2); - }); - it("should produce different hashes for different inputs", () => { - const res1 = sha256Hash([BigInt(1), BigInt(2)]); - const res2 = sha256Hash([BigInt(2), BigInt(1)]); - expect(res1).to.not.eq(res2); - }); - it("should produce an output smaller than the snark field size", () => { - const hash = sha256Hash([BigInt(1), BigInt(2)]); - expect(hash < SNARK_FIELD_SIZE).to.eq(true); - }); - it("should produce the correct output", () => { - const s = sha256Hash([BigInt(0), BigInt(1)]); - expect(s.toString()).to.eq("21788914573420223731318033363701224062123674814818143146813863227479480390499"); - }); - }); - describe("poseidon", () => { - describe("poseidonT3", () => { - it("should produce the same output for the same input", () => { - const res1 = poseidonT3([BigInt(1), BigInt(2)]); - const res2 = poseidonT3([BigInt(1), BigInt(2)]); - expect(res1).to.eq(res2); - }); - it("should produce different outputs for different inputs", () => { - const res1 = poseidonT3([BigInt(1), BigInt(2)]); - const res2 = poseidonT3([BigInt(2), BigInt(1)]); - expect(res1).to.not.eq(res2); - }); - it("should produce a non zero value", () => { - const hash = poseidonT3([BigInt(1), BigInt(2)]); - expect(hash).to.not.eq(BigInt(0)); - }); - it("should only accept two inputs", () => { - expect(() => poseidonT3([BigInt(1), BigInt(2), BigInt(3)])).to.throw(); - }); - }); - describe("poseidonT4", () => { - it("should produce the same output for the same input", () => { - const res1 = poseidonT4([BigInt(1), BigInt(2), BigInt(3)]); - const res2 = poseidonT4([BigInt(1), BigInt(2), BigInt(3)]); - expect(res1).to.eq(res2); - }); - it("should produce different outputs for different inputs", () => { - const res1 = poseidonT4([BigInt(1), BigInt(2), BigInt(3)]); - const res2 = poseidonT4([BigInt(2), BigInt(1), BigInt(3)]); - expect(res1).to.not.eq(res2); - }); - it("should produce a non zero value", () => { - const hash = poseidonT4([BigInt(1), BigInt(2), BigInt(3)]); - expect(hash).to.not.eq(BigInt(0)); - }); - it("should only accept three inputs", () => { - expect(() => poseidonT4([BigInt(1), BigInt(2)])).to.throw(); - expect(() => poseidonT4([BigInt(1), BigInt(2), BigInt(3), BigInt(4)])).to.throw(); - }); - }); - describe("poseidonT5", () => { - it("should produce the same output for the same input", () => { - const res1 = poseidonT5([BigInt(1), BigInt(2), BigInt(3), BigInt(4)]); - const res2 = poseidonT5([BigInt(1), BigInt(2), BigInt(3), BigInt(4)]); - expect(res1).to.eq(res2); - }); - it("should produce different outputs for different inputs", () => { - const res1 = poseidonT5([BigInt(1), BigInt(2), BigInt(3), BigInt(4)]); - const res2 = poseidonT5([BigInt(2), BigInt(1), BigInt(3), BigInt(4)]); - expect(res1).to.not.eq(res2); - }); - it("should produce a non zero value", () => { - const hash = poseidonT5([BigInt(1), BigInt(2), BigInt(3), BigInt(4)]); - expect(hash).to.not.eq(BigInt(0)); - }); - it("should only accept four inputs", () => { - expect(() => poseidonT5([BigInt(1), BigInt(2)])).to.throw(); - expect(() => poseidonT5([BigInt(1), BigInt(2), BigInt(3)])).to.throw(); - expect(() => poseidonT5([BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)])).to.throw(); - }); - }); - describe("poseidonT6", () => { - it("should produce the same output for the same input", () => { - const res1 = poseidonT6([BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)]); - const res2 = poseidonT6([BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)]); - expect(res1).to.eq(res2); - }); - it("should produce different outputs for different inputs", () => { - const res1 = poseidonT6([BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)]); - const res2 = poseidonT6([BigInt(2), BigInt(1), BigInt(3), BigInt(4), BigInt(5)]); - expect(res1).to.not.eq(res2); - }); - it("should produce a non zero value", () => { - const hash = poseidonT6([BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)]); - expect(hash).to.not.eq(BigInt(0)); - }); - it("should only accept five inputs", () => { - expect(() => poseidonT6([BigInt(1), BigInt(2)])).to.throw(); - expect(() => poseidonT6([BigInt(1), BigInt(2), BigInt(3)])).to.throw(); - expect(() => poseidonT6([BigInt(1), BigInt(2), BigInt(3), BigInt(4)])).to.throw(); - expect(() => poseidonT6([BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5), BigInt(6)])).to.throw(); - }); - }); - describe("hashLeftRight", () => { - it("should produce the same output for the same input", () => { - const res1 = hashLeftRight(BigInt(1), BigInt(2)); - const res2 = hashLeftRight(BigInt(1), BigInt(2)); - expect(res1).to.eq(res2); - }); - it("should produce different outputs for different inputs", () => { - const res1 = hashLeftRight(BigInt(1), BigInt(2)); - const res2 = hashLeftRight(BigInt(2), BigInt(1)); - expect(res1).to.not.eq(res2); - }); - it("should produce a non zero value", () => { - const hash = hashLeftRight(BigInt(1), BigInt(2)); - expect(hash).to.not.eq(BigInt(0)); - }); - }); - describe("hashN", () => { - it("should produce the same output for the same input", () => { - const res1 = hashN(5, [BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)]); - const res2 = hashN(5, [BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)]); - expect(res1).to.eq(res2); - }); - it("should produce different outputs for different inputs", () => { - const res1 = hashN(5, [BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)]); - const res2 = hashN(5, [BigInt(2), BigInt(1), BigInt(3), BigInt(4), BigInt(5)]); - expect(res1).to.not.eq(res2); - }); - it("should produce a non zero value", () => { - const hash = hashN(5, [BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)]); - expect(hash).to.not.eq(BigInt(0)); - }); - it("should throw when elements is more than numElement", () => { - expect(() => hashN(5, [BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5), BigInt(6)])).to.throw( - "the length of the elements array should be at most 5; got 6", - ); - }); - it("should work (and apply padding) when passed less than numElement elements", () => { - const hash = hashN(5, [BigInt(1), BigInt(2), BigInt(3), BigInt(4)]); - expect(hash).to.not.eq(BigInt(0)); - }); - }); - describe("hash2", () => { - it("should produce the same output for the same input", () => { - const res1 = hash2([BigInt(1), BigInt(2)]); - const res2 = hash2([BigInt(1), BigInt(2)]); - expect(res1).to.eq(res2); - }); - it("should produce different outputs for different inputs", () => { - const res1 = hash2([BigInt(1), BigInt(2)]); - const res2 = hash2([BigInt(2), BigInt(1)]); - expect(res1).to.not.eq(res2); - }); - it("should produce a non zero value", () => { - const hash = hash2([BigInt(1), BigInt(2)]); - expect(hash).to.not.eq(BigInt(0)); - }); - it("should throw when elements is more than numElement", () => { - expect(() => hash2([BigInt(1), BigInt(2), BigInt(3)])).to.throw( - "the length of the elements array should be at most 2; got 3", - ); - }); - it("should work (and apply padding) when passed less than numElement elements", () => { - const hash = hash2([BigInt(1)]); - expect(hash).to.not.eq(BigInt(0)); - }); - }); - describe("hash3", () => { - it("should produce the same output for the same input", () => { - const res1 = hash3([BigInt(1), BigInt(2), BigInt(3)]); - const res2 = hash3([BigInt(1), BigInt(2), BigInt(3)]); - expect(res1).to.eq(res2); - }); - it("should produce different outputs for different inputs", () => { - const res1 = hash3([BigInt(1), BigInt(2), BigInt(3)]); - const res2 = hash3([BigInt(2), BigInt(1), BigInt(3)]); - expect(res1).to.not.eq(res2); - }); - it("should produce a non zero value", () => { - const hash = hash3([BigInt(1), BigInt(2), BigInt(3)]); - expect(hash).to.not.eq(BigInt(0)); - }); - it("should throw when elements is more than numElement", () => { - expect(() => hash3([BigInt(1), BigInt(2), BigInt(3), BigInt(4)])).to.throw( - "the length of the elements array should be at most 3; got 4", - ); - }); - it("should work (and apply padding) when passed less than numElement elements", () => { - const hash = hash3([BigInt(1), BigInt(2)]); - expect(hash).to.not.eq(BigInt(0)); - }); - }); - describe("hash4", () => { - it("should produce the same output for the same input", () => { - const res1 = hash4([BigInt(1), BigInt(2), BigInt(3), BigInt(4)]); - const res2 = hash4([BigInt(1), BigInt(2), BigInt(3), BigInt(4)]); - expect(res1).to.eq(res2); - }); - it("should produce different outputs for different inputs", () => { - const res1 = hash4([BigInt(1), BigInt(2), BigInt(3), BigInt(4)]); - const res2 = hash4([BigInt(2), BigInt(1), BigInt(3), BigInt(4)]); - expect(res1).to.not.eq(res2); - }); - it("should produce a non zero value", () => { - const hash = hash4([BigInt(1), BigInt(2), BigInt(3), BigInt(4)]); - expect(hash).to.not.eq(BigInt(0)); - }); - it("should throw when elements is more than numElement", () => { - expect(() => hash4([BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)])).to.throw( - "the length of the elements array should be at most 4; got 5", - ); - }); - it("should work (and apply padding) when passed less than numElement elements", () => { - const hash = hash4([BigInt(1), BigInt(2)]); - expect(hash).to.not.eq(BigInt(0)); - }); - }); - describe("hash5", () => { - it("should produce the same output for the same input", () => { - const res1 = hash5([BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)]); - const res2 = hash5([BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)]); - expect(res1).to.eq(res2); - }); - it("should produce different outputs for different inputs", () => { - const res1 = hash5([BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)]); - const res2 = hash5([BigInt(2), BigInt(1), BigInt(3), BigInt(4), BigInt(5)]); - expect(res1).to.not.eq(res2); - }); - it("should produce a non zero value", () => { - const hash = hash5([BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)]); - expect(hash).to.not.eq(BigInt(0)); - }); - it("should throw when elements is more than numElement", () => { - expect(() => hash5([BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5), BigInt(6)])).to.throw( - "the length of the elements array should be at most 5; got 6", - ); - }); - it("should work (and apply padding) when passed less than numElement elements", () => { - const hash = hash5([BigInt(1), BigInt(2)]); - expect(hash).to.not.eq(BigInt(0)); - }); - }); - describe("hash13", () => { - it("should produce the same output for the same input", () => { - const res1 = hash13([ - BigInt(1), - BigInt(2), - BigInt(3), - BigInt(4), - BigInt(5), - BigInt(6), - BigInt(7), - BigInt(8), - BigInt(9), - BigInt(10), - BigInt(11), - BigInt(12), - BigInt(13), - ]); - const res2 = hash13([ - BigInt(1), - BigInt(2), - BigInt(3), - BigInt(4), - BigInt(5), - BigInt(6), - BigInt(7), - BigInt(8), - BigInt(9), - BigInt(10), - BigInt(11), - BigInt(12), - BigInt(13), - ]); - expect(res1).to.eq(res2); - }); - it("should produce different outputs for different inputs", () => { - const res1 = hash13([ - BigInt(1), - BigInt(2), - BigInt(3), - BigInt(4), - BigInt(5), - BigInt(6), - BigInt(7), - BigInt(8), - BigInt(9), - BigInt(10), - BigInt(11), - BigInt(12), - BigInt(13), - ]); - const res2 = hash13([ - BigInt(2), - BigInt(1), - BigInt(3), - BigInt(4), - BigInt(5), - BigInt(6), - BigInt(7), - BigInt(8), - BigInt(9), - BigInt(10), - BigInt(11), - BigInt(12), - BigInt(13), - ]); - expect(res1).to.not.eq(res2); - }); - it("should produce a non zero value", () => { - const hash = hash13([ - BigInt(1), - BigInt(2), - BigInt(3), - BigInt(4), - BigInt(5), - BigInt(6), - BigInt(7), - BigInt(8), - BigInt(9), - BigInt(10), - BigInt(11), - BigInt(12), - BigInt(13), - ]); - expect(hash).to.not.eq(BigInt(0)); - }); - it("should throw when elements is more than numElement", () => { - expect(() => - hash13([ - BigInt(1), - BigInt(2), - BigInt(3), - BigInt(4), - BigInt(5), - BigInt(6), - BigInt(7), - BigInt(8), - BigInt(9), - BigInt(10), - BigInt(11), - BigInt(12), - BigInt(13), - BigInt(14), - ]), - ).to.throw("the length of the elements array should be at most 13; got 14"); - }); - it("should work (and apply padding) when passed less than numElement elements", () => { - const hash = hash13([BigInt(1), BigInt(2)]); - expect(hash).to.not.eq(BigInt(0)); - }); - }); - describe("hashOne", () => { - it("should produce the same output for the same input", () => { - const res1 = hashOne(BigInt(1)); - const res2 = hashOne(BigInt(1)); - expect(res1).to.eq(res2); - }); - it("should produce different outputs for different inputs", () => { - const res1 = hashOne(BigInt(1)); - const res2 = hashOne(BigInt(2)); - expect(res1).to.not.eq(res2); - }); - it("should produce a non zero value", () => { - const hash = hashOne(BigInt(1)); - expect(hash).to.not.eq(BigInt(0)); - }); - }); - }); - describe("utils", () => { - describe("genRandomSalt", () => { - it("should produce a random salt", () => { - const salt1 = genRandomSalt(); - const salt2 = genRandomSalt(); - expect(salt1).to.not.eq(salt2); - }); - it("should produce a salt smaller than the snark field size", () => { - const salt = genRandomSalt(); - expect(salt < SNARK_FIELD_SIZE).to.eq(true); - }); - it("should produce a non zero value", () => { - const salt = genRandomSalt(); - expect(salt).to.not.eq(BigInt(0)); - }); - }); - }); - describe("babyjub", () => { - describe("genRandomBabyJubValue", () => { - it("should generate a value what is < SNARK_FIELD_SIZE", () => { - const p = genRandomBabyJubValue(); - expect(p < SNARK_FIELD_SIZE).to.eq(true); - }); - it("should generate a random value", () => { - const p1 = genRandomBabyJubValue(); - const p2 = genRandomBabyJubValue(); - expect(p1).to.not.eq(p2); - }); - it("should generate a non zero value", () => { - const p = genRandomBabyJubValue(); - expect(p).to.not.eq(BigInt(0)); - }); - }); - describe("genPrivKey", () => { - it("should generate a random private key", () => { - const sk1 = genPrivKey(); - const sk2 = genPrivKey(); - expect(sk1).to.not.eq(sk2); - }); - it("should generate a non zero private key", () => { - const sk = genPrivKey(); - expect(sk).to.not.eq(BigInt(0)); - }); - }); - describe("genRandomSalt", () => { - it("should generate a salt that is < SNARK_FIELD_SIZE", () => { - const salt = genRandomSalt(); - expect(salt < SNARK_FIELD_SIZE).to.eq(true); - }); - it("should generate a random salt", () => { - const salt1 = genRandomSalt(); - const salt2 = genRandomSalt(); - expect(salt1).to.not.eq(salt2); - }); - it("should generate a non zero salt", () => { - const salt = genRandomSalt(); - expect(salt).to.not.eq(BigInt(0)); - }); - }); - describe("packPubKey", () => { - it("should pack a public key into a bigint", () => { - const pk = genPubKey(genPrivKey()); - const pkBuff = packPubKey(pk); - expect(typeof pkBuff).to.eq("bigint"); - }); - }); - describe("unpackPubKey", () => { - it("should unpack a Buffer into a public key", () => { - const pk = genPubKey(genPrivKey()); - const pkBuff = packPubKey(pk); - const pkUnpacked = unpackPubKey(pkBuff); - expect(pkUnpacked).to.deep.eq(pk); - }); - it("should produce a result which is < SNARK_FIELD_SIZE", () => { - const pk = genPubKey(genPrivKey()); - const pkBuff = packPubKey(pk); - const pkUnpacked = unpackPubKey(pkBuff); - expect(pkUnpacked[0] < SNARK_FIELD_SIZE).to.eq(true); - expect(pkUnpacked[1] < SNARK_FIELD_SIZE).to.eq(true); - }); - }); - describe("genPubKey", () => { - it("should produce a public key which is < SNARK_FIELD_SIZE", () => { - const pk = genPubKey(genPrivKey()); - expect(pk[0] < SNARK_FIELD_SIZE).to.eq(true); - expect(pk[1] < SNARK_FIELD_SIZE).to.eq(true); - }); - }); - describe("genKeypair", () => { - it("should produce a public key which is < SNARK_FIELD_SIZE", () => { - const { pubKey } = genKeypair(); - expect(pubKey[0] < SNARK_FIELD_SIZE).to.eq(true); - expect(pubKey[1] < SNARK_FIELD_SIZE).to.eq(true); - }); - }); - describe("genEcdhSharedKey", () => { - it("should produce a shared key which is < SNARK_FIELD_SIZE", () => { - const { privKey, pubKey } = genKeypair(); - const sharedKey = genEcdhSharedKey(privKey, pubKey); - expect(sharedKey[0] < SNARK_FIELD_SIZE).to.eq(true); - expect(sharedKey[1] < SNARK_FIELD_SIZE).to.eq(true); - }); - it("should generate a key which is different than the both privKey and pubKey", () => { - const { privKey, pubKey } = genKeypair(); - const sharedKey = genEcdhSharedKey(privKey, pubKey); - expect(sharedKey[0]).to.not.eq(privKey); - expect(sharedKey[1]).to.not.eq(privKey); - expect(sharedKey[0]).to.not.eq(pubKey[0]); - expect(sharedKey[1]).to.not.eq(pubKey[1]); - }); - it("should generate non zero points", () => { - const { privKey, pubKey } = genKeypair(); - const sharedKey = genEcdhSharedKey(privKey, pubKey); - expect(sharedKey[0]).to.not.eq(BigInt(0)); - expect(sharedKey[1]).to.not.eq(BigInt(0)); - }); - it("should produce consistent results", () => { - const { privKey: privKey1, pubKey: pubKey1 } = genKeypair(); - const { privKey: privKey2, pubKey: pubKey2 } = genKeypair(); - - const sharedKey1 = genEcdhSharedKey(privKey1, pubKey2); - const sharedKey2 = genEcdhSharedKey(privKey2, pubKey1); - - expect(sharedKey1[0]).to.eq(sharedKey2[0]); - expect(sharedKey1[1]).to.eq(sharedKey2[1]); - }); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/crypto/__tests__/IMT.test.ts b/packages/hardhat/maci-ts/crypto/__tests__/IMT.test.ts deleted file mode 100644 index 00b1f3e..0000000 --- a/packages/hardhat/maci-ts/crypto/__tests__/IMT.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { expect } from "chai"; - -import { hash5 } from "../hashing"; -import { IncrementalQuinTree } from "../quinTree"; - -describe("IMT comparison", () => { - describe("constructor", () => { - it("should calculate initial root and zero values", () => { - const mt1 = new IncrementalQuinTree(5, 0n, 5, hash5); - - expect(mt1.root).to.not.eq(null); - expect(mt1.zeros.length).to.be.gt(0); - }); - }); - - describe("insert", () => { - it("should update the root after one insertion", () => { - const mt1 = new IncrementalQuinTree(5, 0n, 5, hash5); - - const rootBefore = mt1.root; - mt1.insert(1n); - - expect(mt1.root).to.not.eq(rootBefore); - }); - }); - - describe("genProof", () => { - it("should generate a proof", () => { - const mt1 = new IncrementalQuinTree(5, 0n, 5, hash5); - - mt1.insert(1n); - - const proof1 = mt1.genProof(0); - - expect(proof1.leaf).to.eq(mt1.getNode(0)); - expect(proof1.pathElements.length).to.be.gt(0); - expect(proof1.pathIndices.length).to.be.gt(0); - expect(proof1.root).to.eq(mt1.root); - }); - - it("should throw when trying to generate a proof for an index < 0", () => { - const mt1 = new IncrementalQuinTree(5, 0n, 5, hash5); - - expect(() => mt1.genProof(-1)).to.throw("The leaf index must be greater or equal to 0"); - }); - - it("should throw when trying to generate a proof for an index > tree size", () => { - const mt1 = new IncrementalQuinTree(5, 0n, 5, hash5); - - const capacity = 5 ** 5; - expect(() => mt1.genProof(capacity + 1)).to.throw("The leaf index must be less than the tree capacity"); - }); - }); - - describe("genSubrootProof", () => { - it("should generate a valid proof for a subtree", () => { - const mt1 = new IncrementalQuinTree(5, 0n, 5, hash5); - - for (let i = 0; i < 100; i += 1) { - mt1.insert(BigInt(i)); - } - - const proof1 = mt1.genSubrootProof(5, 10); - - expect(mt1.verifyProof(proof1)).to.eq(true); - }); - - it("should throw when trying to generate a subroot proof and providing an end index > start index", () => { - const mt1 = new IncrementalQuinTree(5, 0n, 5, hash5); - - expect(() => mt1.genSubrootProof(5, 4)).to.throw("The start index must be less than the end index"); - }); - - it("should throw when providing a start index < 0", () => { - const mt1 = new IncrementalQuinTree(5, 0n, 5, hash5); - - expect(() => mt1.genSubrootProof(-1, 5)).to.throw("The start index must be greater or equal to 0"); - }); - - it("should throw when providing a leaves range not multiple of arity", () => { - const arity = 5; - const mt1 = new IncrementalQuinTree(arity, 0n, 5, hash5); - - expect(() => mt1.genSubrootProof(0, arity + 1)).to.throw( - "The number of leaves must be a multiple of the tree arity", - ); - }); - - it("should throw when the number of leaves is larger than the capacity", () => { - const arity = 5; - const mt1 = new IncrementalQuinTree(arity, 0n, 5, hash5); - - expect(() => mt1.genSubrootProof(0, arity ** 5)).to.throw( - "The number of leaves must be less than the tree capacity", - ); - }); - }); - - describe("verifyProof", () => { - it("should validate a proof", () => { - const mt1 = new IncrementalQuinTree(5, 0n, 5, hash5); - - mt1.insert(1n); - - const proof1 = mt1.genProof(0); - - expect(mt1.verifyProof(proof1)).to.eq(true); - }); - }); - - describe("calcParentIndices", () => { - it("should throw when the index is out of bounds", () => { - const arity = 5; - const mt1 = new IncrementalQuinTree(arity, 0n, 5, hash5); - - expect(() => mt1.calcParentIndices(arity ** 5)).to.throw( - `Index ${arity ** 5} is out of bounds. Can only get parents of leaves`, - ); - }); - }); - - describe("calcChildIndices", () => { - it("should throw when the index is out of bounds", () => { - const arity = 5; - const mt1 = new IncrementalQuinTree(arity, 0n, 5, hash5); - - const index = 2; - expect(() => mt1.calcChildIndices(index)).to.throw( - `Index ${index} is out of bounds. Can only get children of subroots`, - ); - }); - }); - - describe("setNode", () => { - it("should throw when trying to set the root directly", () => { - const arity = 5; - const mt1 = new IncrementalQuinTree(arity, 0n, 5, hash5); - - expect(() => { - mt1.setNode(mt1.numNodes, 1n); - }).to.throw("Index out of bounds"); - }); - - it("should throw when the index is out of bounds", () => { - const arity = 5; - const mt1 = new IncrementalQuinTree(arity, 0n, 5, hash5); - - expect(() => { - mt1.setNode(mt1.numNodes + 5, 1n); - }).to.throw("Index out of bounds"); - }); - }); - - describe("copy", () => { - it("should produce a copy of the tree", () => { - const arity = 5; - const mt1 = new IncrementalQuinTree(arity, 0n, 5, hash5); - - const mt2 = mt1.copy(); - - expect(mt2.root).to.eq(mt1.root); - expect(mt2.zeros).to.deep.eq(mt1.zeros); - expect(mt2.numNodes).to.eq(mt1.numNodes); - expect(mt2.arity).to.eq(mt1.arity); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/crypto/__tests__/Utils.test.ts b/packages/hardhat/maci-ts/crypto/__tests__/Utils.test.ts deleted file mode 100644 index f476939..0000000 --- a/packages/hardhat/maci-ts/crypto/__tests__/Utils.test.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { expect } from "chai"; - -import { bigInt2Buffer, fromRprLE, fromString, shiftRight, stringifyBigInts, unstringifyBigInts } from "../bigIntUtils"; -import { SNARK_FIELD_SIZE } from "../constants"; -import { genTreeCommitment, genTreeProof } from "../utils"; - -describe("Utils", () => { - describe("stringifyBigInts", () => { - it("should work on a BigInt input", () => { - expect(stringifyBigInts(BigInt(1))).to.eq("1"); - }); - - it("should work on a BigInt[] input", () => { - expect(stringifyBigInts([BigInt(1), BigInt(2)])).to.deep.eq(["1", "2"]); - }); - - it("should work on a BigInt[][] input", () => { - expect( - stringifyBigInts([ - [BigInt(1), BigInt(2)], - [BigInt(3), BigInt(4)], - ]), - ).to.deep.eq([ - ["1", "2"], - ["3", "4"], - ]); - }); - - it("should work on a BigInt[][][] input", () => { - expect( - stringifyBigInts([ - [ - [BigInt(1), BigInt(2)], - [BigInt(3), BigInt(4)], - ], - [ - [BigInt(5), BigInt(6)], - [BigInt(7), BigInt(8)], - ], - ]), - ).to.deep.eq([ - [ - ["1", "2"], - ["3", "4"], - ], - [ - ["5", "6"], - ["7", "8"], - ], - ]); - }); - - it("should work on a { [key: string]: BigInt } input", () => { - expect(stringifyBigInts({ a: BigInt(1), b: BigInt(2) })).to.deep.eq({ a: "1", b: "2" }); - }); - - it("should work on a null input", () => { - expect(stringifyBigInts(null)).to.eq(null); - }); - - it("should return the input if it is not a valid value", () => { - expect(stringifyBigInts("A")).to.eq("A"); - }); - - it("should work on a Uint8Array input", () => { - const input = new Uint8Array([1, 2, 3, 4]); - expect(stringifyBigInts(input)).to.eq("67305985"); - }); - }); - - describe("unstringifyBigInts", () => { - it("should work on a string input with decimal numbers", () => { - expect(unstringifyBigInts("1")).to.eq(BigInt(1)); - }); - - it("should work on a string input with hex number", () => { - expect(unstringifyBigInts("0xA")).to.eq(BigInt(10)); - }); - - it("should work on a string[] input", () => { - expect(unstringifyBigInts(["1", "2"])).to.deep.eq([BigInt(1), BigInt(2)]); - }); - - it("should work on a string[][] input", () => { - expect( - unstringifyBigInts([ - ["1", "2"], - ["3", "4"], - ]), - ).to.deep.eq([ - [BigInt(1), BigInt(2)], - [BigInt(3), BigInt(4)], - ]); - }); - - it("should work on a string[][][] input", () => { - expect( - unstringifyBigInts([ - [ - ["1", "2"], - ["3", "4"], - ], - [ - ["5", "6"], - ["7", "8"], - ], - ]), - ).to.deep.eq([ - [ - [BigInt(1), BigInt(2)], - [BigInt(3), BigInt(4)], - ], - [ - [BigInt(5), BigInt(6)], - [BigInt(7), BigInt(8)], - ], - ]); - }); - - it("should work on a { [key: string]: string } input", () => { - expect(unstringifyBigInts({ a: "1", b: "2" })).to.deep.eq({ a: BigInt(1), b: BigInt(2) }); - }); - - it("should work on a null input", () => { - expect(unstringifyBigInts(null)).to.eq(null); - }); - - it("should return the input if it is not a valid value", () => { - expect(unstringifyBigInts("A")).to.eq("A"); - }); - }); - - describe("bigInt2Buffer", () => { - it("should convert a BigInt to a Buffer", () => { - const bigInt = BigInt(123456789); - const buffer = bigInt2Buffer(bigInt); - expect(buffer).to.be.instanceOf(Buffer); - }); - - it("should produce a Buffer with the correct value", () => { - const bigInt = BigInt(123456789); - const buffer = bigInt2Buffer(bigInt); - - let hex = bigInt.toString(16); - - // Ensure even length. - if (hex.length % 2 !== 0) { - hex = `0${hex}`; - } - const expectedBuffer = Buffer.from(hex, "hex"); - expect(buffer.equals(expectedBuffer)).to.eq(true); - }); - - it("should produce a Buffer with the correct value even if not even length", () => { - const bigInt = BigInt(15); - const buffer = bigInt2Buffer(bigInt); - - const expectedBuffer = Buffer.from("0f", "hex"); - expect(buffer.equals(expectedBuffer)).to.eq(true); - }); - }); - - describe("genTreeCommitment", () => { - const leaves = [BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)]; - const salt = BigInt(6); - const depth = 3; - - it("should generate a commitment to the tree root using the provided salt", () => { - const commitment = genTreeCommitment(leaves, salt, depth); - expect(commitment).to.satisfy((num: bigint) => num > 0); - expect(commitment).to.satisfy((num: bigint) => num < SNARK_FIELD_SIZE); - }); - - it("should always generate the same commitment for the same inputs", () => { - const commitment = genTreeCommitment(leaves, salt, depth); - expect(commitment).to.satisfy((num: bigint) => num > 0); - expect(commitment).to.satisfy((num: bigint) => num < SNARK_FIELD_SIZE); - - const commitment2 = genTreeCommitment(leaves, salt, depth); - expect(commitment2).to.satisfy((num: bigint) => num > 0); - expect(commitment2).to.satisfy((num: bigint) => num < SNARK_FIELD_SIZE); - expect(commitment).to.eq(commitment2); - }); - }); - - describe("fromString", () => { - it("should convert a string with radix 10 to a bigint", () => { - expect(fromString("123456789", 10)).to.eq(BigInt(123456789)); - }); - - it("should convert a string with radix 16 to a bigint", () => { - expect(fromString("123456789", 16)).to.eq(BigInt(0x123456789)); - }); - - it("should convert a string with radix 16 and starting with 0x to a bigint", () => { - expect(fromString("0x123456789", 16)).to.eq(BigInt(0x123456789)); - }); - - it("should convert a string with radix != 10 && != 16 to a bigint", () => { - expect(fromString("123456789", 2)).to.eq(BigInt(123456789)); - }); - }); - - describe("genTreeProof", () => { - it("should return the path elements for the given index", () => { - const leaves = [BigInt(1), BigInt(2), BigInt(3), BigInt(4), BigInt(5)]; - const depth = 3; - const proof = genTreeProof(2, leaves, depth); - expect(proof.length).to.be.gt(0); - }); - }); - - describe("fromRprLE", () => { - it("should correctly parse a buffer with Little Endian Representation", () => { - const { buffer } = new Uint8Array([1, 2, 3, 4]); - const view = new DataView(buffer); - const expected = fromString("04030201", 16).toString(); - expect(fromRprLE(view)).to.eq(expected); - }); - - it("should correctly parse a buffer with Little Endian Representation with offset", () => { - const { buffer } = new Uint8Array([0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 0]); - const view = new DataView(buffer); - const expected = fromString("04030201", 16).toString(); - expect(fromRprLE(view, 4, 4)).to.eq(expected); - }); - - it("should correctly parse a buffer with Little Endian Representation with byte length", () => { - const { buffer } = new Uint8Array([1, 2, 3, 4, 5, 6]); - const view = new DataView(buffer); - const expected = fromString("04030201", 16).toString(); - expect(fromRprLE(view, 0, 4)).to.eq(expected); - }); - }); - - describe("shiftRight", () => { - it("should shift a bigint to the right by n bits", () => { - expect(shiftRight(16n, 2n)).to.eq(4n); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/crypto/__tests__/utils.ts b/packages/hardhat/maci-ts/crypto/__tests__/utils.ts deleted file mode 100644 index 08a3ce2..0000000 --- a/packages/hardhat/maci-ts/crypto/__tests__/utils.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { expect } from "chai"; - -import { AccQueue, IncrementalQuinTree, calcDepthFromNumLeaves } from ".."; - -/** - * Test a full merge - * @param SUB_DEPTH - * @param HASH_LENGTH - * @param ZERO - * @param NUM_SUBTREES - * @param MAIN_DEPTH - */ -export const testMerge = ( - SUB_DEPTH: number, - HASH_LENGTH: number, - ZERO: bigint, - NUM_SUBTREES: number, - MAIN_DEPTH: number, -): void => { - // const hashFunc = HASH_LENGTH === 5 ? hash5 : hash2 - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const aq2 = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const tree = new IncrementalQuinTree(MAIN_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - for (let i = 0; i < NUM_SUBTREES; i += 1) { - for (let j = 0; j < HASH_LENGTH ** SUB_DEPTH; j += 1) { - const leaf = BigInt(j + 1); - tree.insert(leaf); - aq.enqueue(leaf); - aq2.enqueue(leaf); - } - } - - // The main root should not exist yet - expect(aq.hasRoot(MAIN_DEPTH)).to.eq(false); - expect(aq2.hasRoot(MAIN_DEPTH)).to.eq(false); - - aq2.mergeSubRoots(0); - aq2.merge(MAIN_DEPTH); - - // For reference only - aq.mergeDirect(MAIN_DEPTH); - - // merge and mergeDirect should produce the same root - expect(aq.hasRoot(MAIN_DEPTH)).to.eq(true); - expect(aq2.hasRoot(MAIN_DEPTH)).to.eq(true); - expect(aq.getRoot(MAIN_DEPTH)!.toString()).to.eq(aq2.getRoot(MAIN_DEPTH)!.toString()); - - // merge and mergeDirect should produce the correct root - expect(aq.getRoot(MAIN_DEPTH)!.toString()).to.eq(tree.root.toString()); -}; - -/** - * Test merging the shortest subtree - * @param SUB_DEPTH - * @param HASH_LENGTH - * @param ZERO - * @param NUM_SUBTREES - */ -export const testMergeShortest = (SUB_DEPTH: number, HASH_LENGTH: number, ZERO: bigint, NUM_SUBTREES: number): void => { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const aq2 = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - - for (let i = 0; i < NUM_SUBTREES; i += 1) { - for (let j = 0; j < HASH_LENGTH ** SUB_DEPTH; j += 1) { - const leaf = BigInt(j + 1); - aq.enqueue(leaf); - aq2.enqueue(leaf); - } - } - - // Merge all subroots in aq - aq.mergeSubRoots(0); - - // Merge all but one subroot in aq2 - aq2.mergeSubRoots(2); - expect(aq.getSmallSRTroot().toString()).not.to.eq(aq2.getSmallSRTroot().toString()); - aq2.mergeSubRoots(2); - expect(aq.getSmallSRTroot().toString()).not.to.eq(aq2.getSmallSRTroot().toString()); - - // Merge the last subroot in aq2 - aq2.mergeSubRoots(1); - - expect(aq.getSmallSRTroot().toString()).to.eq(aq2.getSmallSRTroot().toString()); -}; - -/** - * Insert one leaf, then run mergeSubRoots - */ -export const testMergeShortestOne = (SUB_DEPTH: number, HASH_LENGTH: number, ZERO: bigint): void => { - const leaf = BigInt(123); - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - const smallTree = new IncrementalQuinTree(SUB_DEPTH, ZERO, HASH_LENGTH, aq.hashFunc); - - aq.enqueue(leaf); - smallTree.insert(leaf); - - aq.mergeSubRoots(0); - - expect(aq.getSmallSRTroot().toString()).to.eq(smallTree.root.toString()); - expect(aq.getSubRoot(0).toString()).to.eq(smallTree.root.toString()); -}; - -/** - * Create a number of subtrees, and merge them all - */ -export const testMergeExhaustive = (SUB_DEPTH: number, HASH_LENGTH: number, ZERO: bigint, MAX: number): void => { - for (let numSubtrees = 2; numSubtrees <= MAX; numSubtrees += 1) { - const aq = new AccQueue(SUB_DEPTH, HASH_LENGTH, ZERO); - - // Create numSubtrees subtrees - for (let i = 0; i < numSubtrees; i += 1) { - for (let j = 0; j < HASH_LENGTH ** SUB_DEPTH; j += 1) { - const leaf = BigInt(j + 1); - aq.enqueue(leaf); - } - } - - // Merge subroots - aq.mergeSubRoots(0); - - const depth = calcDepthFromNumLeaves(HASH_LENGTH, numSubtrees); - const smallTree = new IncrementalQuinTree(depth, aq.getZeros()[aq.getSubDepth()], HASH_LENGTH, aq.hashFunc); - - aq.getSubRoots().forEach(subRoot => { - smallTree.insert(subRoot); - }); - - expect(aq.getSmallSRTroot().toString()).to.eq(smallTree.root.toString()); - } -}; diff --git a/packages/hardhat/maci-ts/crypto/babyjub.ts b/packages/hardhat/maci-ts/crypto/babyjub.ts deleted file mode 100644 index a1f354b..0000000 --- a/packages/hardhat/maci-ts/crypto/babyjub.ts +++ /dev/null @@ -1,133 +0,0 @@ -import assert from "assert"; -import { randomBytes } from "crypto"; - -import type { PrivKey } from "./types"; - -import { SNARK_FIELD_SIZE } from "./constants"; -import { IG1ContractParams, IG2ContractParams } from "../domainobjs"; - -/** - * @notice A class representing a point on the first group (G1) - * of the Jubjub curve - */ -export class G1Point { - x: bigint; - - y: bigint; - - /** - * Create a new instance of G1Point - * @param x the x coordinate - * @param y the y coordinate - */ - constructor(x: bigint, y: bigint) { - assert(x < SNARK_FIELD_SIZE && x >= 0, "G1Point x out of range"); - assert(y < SNARK_FIELD_SIZE && y >= 0, "G1Point y out of range"); - this.x = x; - this.y = y; - } - - /** - * Check whether two points are equal - * @param pt the point to compare with - * @returns whether they are equal or not - */ - equals(pt: G1Point): boolean { - return this.x === pt.x && this.y === pt.y; - } - - /** - * Return the point as a contract param in the form of an object - * @returns the point as a contract param - */ - asContractParam(): IG1ContractParams { - return { - x: this.x, - y: this.y, - }; - } -} - -/** - * @notice A class representing a point on the second group (G2) - * of the Jubjub curve. This is usually an extension field of the - * base field of the curve. - */ -export class G2Point { - x: bigint[]; - - y: bigint[]; - - /** - * Create a new instance of G2Point - * @param x the x coordinate - * @param y the y coordinate - */ - constructor(x: bigint[], y: bigint[]) { - this.checkPointsRange(x, "x"); - this.checkPointsRange(y, "y"); - - this.x = x; - this.y = y; - } - - /** - * Check whether two points are equal - * @param pt the point to compare with - * @returns whether they are equal or not - */ - equals(pt: G2Point): boolean { - return this.x[0] === pt.x[0] && this.x[1] === pt.x[1] && this.y[0] === pt.y[0] && this.y[1] === pt.y[1]; - } - - /** - * Return the point as a contract param in the form of an object - * @returns the point as a contract param - */ - asContractParam(): IG2ContractParams { - return { - x: this.x, - y: this.y, - }; - } - - /** - * Check whether the points are in range - * @param x the x coordinate - * @param type the type of the coordinate - */ - private checkPointsRange(x: bigint[], type: "x" | "y") { - assert( - x.every(n => n < SNARK_FIELD_SIZE && n >= 0), - `G2Point ${type} out of range`, - ); - } -} - -/** - * Returns a BabyJub-compatible random value. We create it by first generating - * a random value (initially 256 bits large) modulo the snark field size as - * described in EIP197. This results in a key size of roughly 253 bits and no - * more than 254 bits. To prevent modulo bias, we then use this efficient - * algorithm: - * http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/lib/libc/crypt/arc4random_uniform.c - * @returns A BabyJub-compatible random value. - */ -export const genRandomBabyJubValue = (): bigint => { - // Prevent modulo bias - // const lim = BigInt('0x10000000000000000000000000000000000000000000000000000000000000000') - // const min = (lim - SNARK_FIELD_SIZE) % SNARK_FIELD_SIZE - const min = BigInt("6350874878119819312338956282401532410528162663560392320966563075034087161851"); - - let privKey: PrivKey = SNARK_FIELD_SIZE; - - do { - const rand = BigInt(`0x${randomBytes(32).toString("hex")}`); - - if (rand >= min) { - privKey = rand % SNARK_FIELD_SIZE; - } - } while (privKey >= SNARK_FIELD_SIZE); - - return privKey; -}; diff --git a/packages/hardhat/maci-ts/crypto/bigIntUtils.ts b/packages/hardhat/maci-ts/crypto/bigIntUtils.ts deleted file mode 100644 index 3a6097b..0000000 --- a/packages/hardhat/maci-ts/crypto/bigIntUtils.ts +++ /dev/null @@ -1,135 +0,0 @@ -import type { BigIntVariants, StringifiedBigInts } from "./types"; - -/** - * Given an input containing string values, convert them - * to bigint - * @param input - The input to convert - * @returns the input with string values converted to bigint - */ -export const unstringifyBigInts = (input: StringifiedBigInts): BigIntVariants => { - if (typeof input === "string" && /^[0-9]+$/.test(input)) { - return BigInt(input); - } - - if (typeof input === "string" && /^0x[0-9a-fA-F]+$/.test(input)) { - return BigInt(input); - } - - if (Array.isArray(input)) { - return input.map(unstringifyBigInts); - } - - if (input === null) { - return null; - } - - if (typeof input === "object") { - return Object.entries(input).reduce>((acc, [key, value]) => { - acc[key] = unstringifyBigInts(value) as bigint; - return acc; - }, {}); - } - - return input; -}; - -/** - * Converts a string to a bigint using the given radix - * @param str - The string to convert - * @param radix - The radix to use - * @returns The converted string as a bigint - */ -export const fromString = (str: string, radix: number): bigint => { - if (!radix || radix === 10) { - return BigInt(str); - } - - if (radix === 16) { - if (str.startsWith("0x")) { - return BigInt(str); - } - return BigInt(`0x${str}`); - } - - return BigInt(str); -}; - -/** - * Parses a buffer with Little Endian Representation - * @param buff - The buffer to parse - * @param o - The offset to start from - * @param n8 - The byte length - * @returns The parsed buffer as a string - */ -export const fromRprLE = (buff: ArrayBufferView, o = 0, n8: number = buff.byteLength): string => { - const v = new Uint32Array(buff.buffer, buff.byteOffset + o, n8 / 4); - const a: string[] = new Array(n8 / 4); - v.forEach((ch, i) => { - a[a.length - i - 1] = ch.toString(16).padStart(8, "0"); - }); - return fromString(a.join(""), 16).toString(); -}; - -/** - * Given an input of bigint values, convert them to their string representations - * @param input - The input to convert - * @returns The input with bigint values converted to string - */ -export const stringifyBigInts = (input: BigIntVariants): StringifiedBigInts => { - if (typeof input === "bigint") { - return input.toString(); - } - - if (input instanceof Uint8Array) { - return fromRprLE(input, 0); - } - - if (Array.isArray(input)) { - return input.map(stringifyBigInts); - } - - if (input === null) { - return null; - } - - if (typeof input === "object") { - return Object.entries(input).reduce>((acc, [key, value]) => { - acc[key] = stringifyBigInts(value); - return acc; - }, {}); - } - - return input; -}; - -/** - * Create a copy of a bigint array - * @param arr - the array of bigints to copy - * @returns a deep copy of the array - */ -export const deepCopyBigIntArray = (arr: bigint[]): bigint[] => arr.map(x => BigInt(x.toString())); - -/** - * Sihft a left by n bits - * @param a - The first bigint - * @param n - The second bigint - * @returns The result of shifting a right by n - */ -export const shiftRight = (a: bigint, n: bigint): bigint => - // eslint-disable-next-line no-bitwise - a >> n; - -/** - * Convert a BigInt to a Buffer - * @param i - the bigint to convert - * @returns the buffer - */ -export const bigInt2Buffer = (i: bigint): Buffer => { - let hex = i.toString(16); - - // Ensure even length. - if (hex.length % 2 !== 0) { - hex = `0${hex}`; - } - return Buffer.from(hex, "hex"); -}; diff --git a/packages/hardhat/maci-ts/crypto/constants.ts b/packages/hardhat/maci-ts/crypto/constants.ts deleted file mode 100644 index ce10528..0000000 --- a/packages/hardhat/maci-ts/crypto/constants.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { r } from "@zk-kit/baby-jubjub"; -import { keccak256, toUtf8Bytes } from "ethers"; - -import assert from "assert"; - -export const SNARK_FIELD_SIZE = r; - -// A nothing-up-my-sleeve zero value -// Should be equal to 8370432830353022751713833565135785980866757267633941821328460903436894336785 -export const NOTHING_UP_MY_SLEEVE = BigInt(keccak256(toUtf8Bytes("Maci"))) % SNARK_FIELD_SIZE; - -assert(NOTHING_UP_MY_SLEEVE === BigInt("8370432830353022751713833565135785980866757267633941821328460903436894336785")); diff --git a/packages/hardhat/maci-ts/crypto/hashing.ts b/packages/hardhat/maci-ts/crypto/hashing.ts deleted file mode 100644 index f86e112..0000000 --- a/packages/hardhat/maci-ts/crypto/hashing.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { poseidonPerm } from "@zk-kit/poseidon-cipher"; -import { solidityPackedSha256 } from "ethers"; - -import assert from "assert"; - -import type { Plaintext, PoseidonFuncs } from "./types"; - -import { SNARK_FIELD_SIZE } from "./constants"; - -/** - * Hash an array of uint256 values the same way that the EVM does. - * @param input - the array of values to hash - * @returns a EVM compatible sha256 hash - */ -export const sha256Hash = (input: bigint[]): bigint => { - const types: string[] = []; - - input.forEach(() => { - types.push("uint256"); - }); - - return ( - BigInt( - solidityPackedSha256( - types, - input.map(x => x.toString()), - ), - ) % SNARK_FIELD_SIZE - ); -}; - -/** - * Generate the poseidon hash of the inputs provided - * @param inputs The inputs to hash - * @returns the hash of the inputs - */ -export const poseidon = (inputs: bigint[]): bigint => poseidonPerm([BigInt(0), ...inputs.map(x => BigInt(x))])[0]; - -/** - * Hash up to 2 elements - * @param inputs The elements to hash - * @returns the hash of the elements - */ -export const poseidonT3 = (inputs: bigint[]): bigint => { - assert(inputs.length === 2); - return poseidon(inputs); -}; - -/** - * Hash up to 3 elements - * @param inputs The elements to hash - * @returns the hash of the elements - */ -export const poseidonT4 = (inputs: bigint[]): bigint => { - assert(inputs.length === 3); - return poseidon(inputs); -}; - -/** - * Hash up to 4 elements - * @param inputs The elements to hash - * @returns the hash of the elements - */ -export const poseidonT5 = (inputs: bigint[]): bigint => { - assert(inputs.length === 4); - return poseidon(inputs); -}; - -/** - * Hash up to 5 elements - * @param inputs The elements to hash - * @returns the hash of the elements - */ -export const poseidonT6 = (inputs: bigint[]): bigint => { - assert(inputs.length === 5); - return poseidon(inputs); -}; - -/** - * Hash two BigInts with the Poseidon hash function - * @param left The left-hand element to hash - * @param right The right-hand element to hash - * @returns The hash of the two elements - */ -export const hashLeftRight = (left: bigint, right: bigint): bigint => poseidonT3([left, right]); - -// hash functions -const funcs: PoseidonFuncs = { - 2: poseidonT3, - 3: poseidonT4, - 4: poseidonT5, - 5: poseidonT6, -}; - -/** - * Hash up to N elements - * @param numElements The number of elements to hash - * @param elements The elements to hash - * @returns The hash of the elements - */ -export const hashN = (numElements: number, elements: Plaintext): bigint => { - const elementLength = elements.length; - - if (elements.length > numElements) { - throw new TypeError(`the length of the elements array should be at most ${numElements}; got ${elements.length}`); - } - const elementsPadded = elements.slice(); - - if (elementLength < numElements) { - for (let i = elementLength; i < numElements; i += 1) { - elementsPadded.push(BigInt(0)); - } - } - - return funcs[numElements](elementsPadded); -}; - -// hash functions -export const hash2 = (elements: Plaintext): bigint => hashN(2, elements); -export const hash3 = (elements: Plaintext): bigint => hashN(3, elements); -export const hash4 = (elements: Plaintext): bigint => hashN(4, elements); -export const hash5 = (elements: Plaintext): bigint => hashN(5, elements); - -/** - * A convenience function to use Poseidon to hash a Plaintext with - * no more than 13 elements - * @param elements The elements to hash - * @returns The hash of the elements - */ -export const hash13 = (elements: Plaintext): bigint => { - const max = 13; - const elementLength = elements.length; - - if (elementLength > max) { - throw new TypeError(`the length of the elements array should be at most ${max}; got ${elements.length}`); - } - - const elementsPadded = elements.slice(); - - if (elementLength < max) { - for (let i = elementLength; i < max; i += 1) { - elementsPadded.push(BigInt(0)); - } - } - - return poseidonT6([ - elementsPadded[0], - poseidonT6(elementsPadded.slice(1, 6)), - poseidonT6(elementsPadded.slice(6, 11)), - elementsPadded[11], - elementsPadded[12], - ]); -}; - -/** - * Hash a single BigInt with the Poseidon hash function - * @param preImage The element to hash - * @returns The hash of the element - */ -export const hashOne = (preImage: bigint): bigint => poseidonT3([preImage, BigInt(0)]); diff --git a/packages/hardhat/maci-ts/crypto/index.ts b/packages/hardhat/maci-ts/crypto/index.ts deleted file mode 100644 index 45b1f5f..0000000 --- a/packages/hardhat/maci-ts/crypto/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -export { AccQueue } from "./AccQueue"; - -export { calcDepthFromNumLeaves, genTreeCommitment, genTreeProof } from "./utils"; - -export { IncrementalQuinTree } from "./quinTree"; - -export { bigInt2Buffer, stringifyBigInts, unstringifyBigInts, deepCopyBigIntArray } from "./bigIntUtils"; - -export { NOTHING_UP_MY_SLEEVE, SNARK_FIELD_SIZE } from "./constants"; - -export { - genPrivKey, - genRandomSalt, - formatPrivKeyForBabyJub, - genPubKey, - genKeypair, - genEcdhSharedKey, - packPubKey, - unpackPubKey, -} from "./keys"; - -export { G1Point, G2Point, genRandomBabyJubValue } from "./babyjub"; - -export { sha256Hash, hashLeftRight, hashN, hash2, hash3, hash4, hash5, hash13, hashOne } from "./hashing"; - -export { poseidonDecrypt, poseidonDecryptWithoutCheck, poseidonEncrypt } from "@zk-kit/poseidon-cipher"; - -export { verifySignature, signMessage as sign } from "@zk-kit/eddsa-poseidon"; - -export type { - PrivKey, - PubKey, - Point, - EcdhSharedKey, - Plaintext, - Ciphertext, - Queue, - Keypair, - Signature, - PoseidonFuncs, - Leaf, - PathElements, -} from "./types"; diff --git a/packages/hardhat/maci-ts/crypto/keys.ts b/packages/hardhat/maci-ts/crypto/keys.ts deleted file mode 100644 index 82e48ad..0000000 --- a/packages/hardhat/maci-ts/crypto/keys.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { mulPointEscalar } from "@zk-kit/baby-jubjub"; -import { derivePublicKey, deriveSecretScalar, packPublicKey, unpackPublicKey } from "@zk-kit/eddsa-poseidon"; - -import { randomBytes } from "crypto"; - -import { genRandomBabyJubValue } from "./babyjub"; -import { EcdhSharedKey, Keypair, Point, PrivKey, PubKey } from "./types"; - -/** - * Generate a private key - * @returns A random seed for a private key. - */ -export const genPrivKey = (): bigint => BigInt(`0x${randomBytes(32).toString("hex")}`); - -/** - * Generate a random value - * @returns A BabyJub-compatible salt. - */ -export const genRandomSalt = (): bigint => genRandomBabyJubValue(); - -/** - * An internal function which formats a random private key to be compatible - * with the BabyJub curve. This is the format which should be passed into the - * PubKey and other circuits. - * @param privKey A private key generated using genPrivKey() - * @returns A BabyJub-compatible private key. - */ -export const formatPrivKeyForBabyJub = (privKey: PrivKey): bigint => BigInt(deriveSecretScalar(privKey.toString())); - -/** - * Losslessly reduces the size of the representation of a public key - * @param pubKey The public key to pack - * @returns A packed public key - */ -export const packPubKey = (pubKey: PubKey): bigint => BigInt(packPublicKey(pubKey)); - -/** - * Restores the original PubKey from its packed representation - * @param packed The value to unpack - * @returns The unpacked public key - */ -export const unpackPubKey = (packed: bigint): PubKey => { - const pubKey = unpackPublicKey(packed); - return pubKey; -}; - -/** - * @param privKey A private key generated using genPrivKey() - * @returns A public key associated with the private key - */ -export const genPubKey = (privKey: PrivKey): PubKey => { - const key = derivePublicKey(privKey.toString()); - return [BigInt(key[0]), BigInt(key[1])]; -}; - -/** - * Generates a keypair. - * @returns a keypair - */ -export const genKeypair = (): Keypair => { - const privKey = genPrivKey(); - const pubKey = genPubKey(privKey); - - const keypair: Keypair = { privKey, pubKey }; - - return keypair; -}; - -/** - * Generates an Elliptic-Curve Diffie–Hellman (ECDH) shared key given a private - * key and a public key. - * @param privKey A private key generated using genPrivKey() - * @param pubKey A public key generated using genPubKey() - * @returns The ECDH shared key. - */ -export const genEcdhSharedKey = (privKey: PrivKey, pubKey: PubKey): EcdhSharedKey => - mulPointEscalar(pubKey as Point, formatPrivKeyForBabyJub(privKey)); diff --git a/packages/hardhat/maci-ts/crypto/quinTree.ts b/packages/hardhat/maci-ts/crypto/quinTree.ts deleted file mode 100644 index 345c4c5..0000000 --- a/packages/hardhat/maci-ts/crypto/quinTree.ts +++ /dev/null @@ -1,377 +0,0 @@ -import type { Leaf, Node, IMerkleProof } from "./types"; - -/** - * An implementation of an incremental Merkle tree - * @dev adapted from https://github.com/weijiekoh/optimisedmt - */ -export class IncrementalQuinTree { - // how many levels - depth: number; - - // the zero value - zeroValue: bigint; - - // the number of leaves per node - arity: number; - - // the hash function used in the tree - hashFunc: (leaves: Leaf[]) => bigint; - - // The the smallest empty leaf index - nextIndex = 0; - - // Contains the zero value per level. i.e. zeros[0] is zeroValue, - // zeros[1] is the hash of leavesPerNode zeros, and so on. - zeros: bigint[] = []; - - root: bigint; - - nodes: Node; - - numNodes: number; - - capacity: number; - - /** - * Create a new instance of the MaciQuinTree - * @param depth The depth of the tree - * @param zeroValue The zero value of the tree - * @param arity The arity of the tree - * @param hashFunc The hash function of the tree - */ - constructor(depth: number, zeroValue: bigint, arity: number, hashFunc: (leaves: bigint[]) => bigint) { - this.depth = depth; - this.zeroValue = zeroValue; - this.arity = arity; - this.hashFunc = hashFunc; - - // calculate the initial values - const { zeros, root } = this.calcInitialVals(this.arity, this.depth, this.zeroValue, this.hashFunc); - this.zeros = zeros; - this.root = root; - - // calculate the number of nodes - this.numNodes = (this.arity ** (this.depth + 1) - 1) / (this.arity - 1); - - // initialize the nodes - this.nodes = {}; - // set the root node - this.nodes[this.numNodes - 1] = root; - // calculate the capacity - this.capacity = this.arity ** this.depth; - } - - /** - * Insert a leaf at the next available index - * @param value The value to insert - */ - insert(value: Leaf): void { - // update the node with this leaf - this.update(this.nextIndex, value); - this.nextIndex += 1; - } - - /** - * Update a leaf at a given index - * @param index The index of the leaf to update - * @param value The value to update the leaf with - */ - update(index: number, value: Leaf): void { - // Set the leaf value - this.setNode(index, value); - - // Set the parent leaf value - // Get the parent indices - const parentIndices = this.calcParentIndices(index); - - parentIndices.forEach(parentIndex => { - const childIndices = this.calcChildIndices(parentIndex); - - const elements: Leaf[] = []; - childIndices.forEach(childIndex => { - elements.push(this.getNode(childIndex)); - }); - this.nodes[parentIndex] = this.hashFunc(elements); - }); - - this.root = this.nodes[this.numNodes - 1]; - } - - /** - * Calculate the indices of the leaves in the path to the root - * @param index The index of the leaf - * @returns The indices of the leaves in the path to the root - */ - calcLeafIndices(index: number): number[] { - const indices = new Array(this.depth); - - let r = index; - for (let i = 0; i < this.depth; i += 1) { - indices[i] = r % this.arity; - r = Math.floor(r / this.arity); - } - - return indices; - } - - /** - * Generate a proof for a given leaf index - * @param index The index of the leaf to generate a proof for - * @returns The proof - */ - genProof(index: number): IMerkleProof { - if (index < 0) { - throw new Error("The leaf index must be greater or equal to 0"); - } - - if (index >= this.capacity) { - throw new Error("+The leaf index must be less than the tree capacity"); - } - - const pathElements: bigint[][] = []; - const indices = this.calcLeafIndices(index); - - // Calculate path elements - let leafIndex = index; - let offset = 0; - - for (let i = 0; i < this.depth; i += 1) { - const elements: bigint[] = []; - const start = leafIndex - (leafIndex % this.arity) + offset; - - for (let j = 0; j < this.arity; j += 1) { - if (j !== indices[i]) { - const node = this.getNode(start + j); - elements.push(node); - } - } - - pathElements.push(elements); - leafIndex = Math.floor(leafIndex / this.arity); - offset += this.arity ** (this.depth - i); - } - - return { - pathElements, - pathIndices: indices, - root: this.root, - leaf: this.getNode(index), - }; - } - - /** - * Generates a Merkle proof from a subroot to the root. - * @param startIndex The index of the first leaf - * @param endIndex The index of the last leaf - * @returns The Merkle proof - */ - genSubrootProof( - // inclusive - startIndex: number, - // exclusive - endIndex: number, - ): IMerkleProof { - // The end index must be greater than the start index - if (startIndex >= endIndex) { - throw new Error("The start index must be less than the end index"); - } - - if (startIndex < 0) { - throw new Error("The start index must be greater or equal to 0"); - } - - // count the number of leaves - const numLeaves = endIndex - startIndex; - - // The number of leaves must be a multiple of the tree arity - if (numLeaves % this.arity !== 0) { - throw new Error("The number of leaves must be a multiple of the tree arity"); - } - - // The number of leaves must be lower than the maximum tree capacity - if (numLeaves >= this.capacity) { - throw new Error("The number of leaves must be less than the tree capacity"); - } - - // Calculate the subdepth - let subDepth = 0; - while (numLeaves !== this.arity ** subDepth && subDepth < this.depth) { - subDepth += 1; - } - - const subTree = new IncrementalQuinTree(subDepth, this.zeroValue, this.arity, this.hashFunc); - - for (let i = startIndex; i < endIndex; i += 1) { - subTree.insert(this.getNode(i)); - } - - const fullPath = this.genProof(startIndex); - fullPath.pathIndices = fullPath.pathIndices.slice(subDepth, this.depth); - fullPath.pathElements = fullPath.pathElements.slice(subDepth, this.depth); - fullPath.leaf = subTree.root; - - return fullPath; - } - - /** - * Verify a proof - * @param proof The proof to verify - * @returns Whether the proof is valid - */ - verifyProof = (proof: IMerkleProof): boolean => { - const { pathElements, leaf, root, pathIndices } = proof; - - // Hash the first level - const firstLevel: bigint[] = pathElements[0].map(BigInt); - - firstLevel.splice(Number(pathIndices[0]), 0, leaf); - - let currentLevelHash: bigint = this.hashFunc(firstLevel); - - // Verify the proof - for (let i = 1; i < pathElements.length; i += 1) { - const level: bigint[] = pathElements[i].map(BigInt); - level.splice(Number(pathIndices[i]), 0, currentLevelHash); - - currentLevelHash = this.hashFunc(level); - } - - // the path is valid if the root matches the calculated root - return currentLevelHash === root; - }; - - /** - * Calculate the indices of the parent - * @param index The index of the leaf - * @returns The indices of the parent - */ - calcParentIndices(index: number): number[] { - // can only calculate the parent for leaves not subroots - if (index >= this.capacity || index < 0) { - throw new Error(`Index ${index} is out of bounds. Can only get parents of leaves`); - } - - const indices = new Array(this.depth); - let r = index; - let levelCapacity = 0; - - for (let i = 0; i < this.depth; i += 1) { - levelCapacity += this.arity ** (this.depth - i); - r = Math.floor(r / this.arity); - indices.push(levelCapacity + r); - } - - return indices; - } - - /** - * Calculate the indices of the children of a node - * @param index The index of the node - * @returns The indices of the children - */ - calcChildIndices(index: number): number[] { - // cannot get the children of a leaf - if (index < this.capacity || index < 0) { - throw new Error(`Index ${index} is out of bounds. Can only get children of subroots`); - } - - // find the level - let level = 0; - let r = this.arity ** level; - do { - level += 1; - r += this.arity ** level; - } while (index >= r); - - const start = (index - this.arity ** level) * this.arity; - const indices = Array(this.arity) - .fill(0) - .map((_, i) => start + i); - - return indices; - } - - /** - * Get a node at a given index - * @param index The index of the node - * @returns The node - */ - getNode(index: number): Leaf { - // if we have it, just return it - if (this.nodes[index]) { - return this.nodes[index]; - } - - // find the zero value at that level - // first need to find the level - let runningTotal = 0; - let level = this.depth; - - while (level >= 0) { - runningTotal += this.arity ** level; - if (index < runningTotal) { - break; - } - - level -= 1; - } - - return this.zeros[this.depth - level]; - } - - /** - * Set a node (not the root) - * @param index the index of the node - * @param value the value of the node - */ - setNode(index: number, value: Leaf): void { - if (index > this.numNodes - 1 || index < 0) { - throw new Error("Index out of bounds"); - } - this.nodes[index] = value; - } - - /** - * Copy the tree to a new instance - * @returns The new instance - */ - copy(): IncrementalQuinTree { - const newTree = new IncrementalQuinTree(this.depth, this.zeroValue, this.arity, this.hashFunc); - - newTree.nodes = this.nodes; - newTree.numNodes = this.numNodes; - newTree.zeros = this.zeros; - newTree.root = this.root; - newTree.nextIndex = this.nextIndex; - - return newTree; - } - - /** - * Calculate the zeroes and the root of a tree - * @param arity The arity of the tree - * @param depth The depth of the tree - * @param zeroValue The zero value of the tree - * @param hashFunc The hash function of the tree - * @returns The zeros and the root - */ - private calcInitialVals = ( - arity: number, - depth: number, - zeroValue: bigint, - hashFunc: (leaves: bigint[]) => bigint, - ): { zeros: bigint[]; root: bigint } => { - const zeros: bigint[] = []; - let currentLevelHash = zeroValue; - - for (let i = 0; i < depth; i += 1) { - zeros.push(currentLevelHash); - - const z = Array(arity).fill(currentLevelHash); - - currentLevelHash = hashFunc(z); - } - - return { zeros, root: currentLevelHash }; - }; -} diff --git a/packages/hardhat/maci-ts/crypto/types.ts b/packages/hardhat/maci-ts/crypto/types.ts deleted file mode 100644 index 495d451..0000000 --- a/packages/hardhat/maci-ts/crypto/types.ts +++ /dev/null @@ -1,95 +0,0 @@ -// we define a bignumber as either a bigint or a string -// which is what we use the most in MACI -export type SnarkBigNumber = bigint | string; - -// a private key is a single BigNumber -export type PrivKey = SnarkBigNumber; - -// a public key is a pair of BigNumbers -export type PubKey = [N, N]; - -// a shared key is a pair of BigNumbers -export type EcdhSharedKey = [N, N]; - -// a point is a pair of BigNumbers -export type Point = [N, N]; - -// a plaintext is an array of BigNumbers -export type Plaintext = N[]; - -// a ciphertext is an array of BigNumbers -export type Ciphertext = N[]; - -// a merkle tree path elements -export type PathElements = bigint[][]; - -/** - * A acc queue - */ -export interface Queue { - levels: Map>; - indices: number[]; -} - -/** - * A private key and a public key - */ -export interface Keypair { - privKey: PrivKey; - pubKey: PubKey; -} - -// An EdDSA signature. -// R8 is a Baby Jubjub elliptic curve point and S is an element of the finite -// field of order `l` where `l` is the large prime number dividing the order of -// Baby Jubjub: see -// https://iden3-docs.readthedocs.io/en/latest/_downloads/a04267077fb3fdbf2b608e014706e004/Ed-DSA.pdf -export interface Signature { - R8: Point; - S: N; -} - -/** - * A interface for poseidon hash functions - */ -export interface PoseidonFuncs { - [key: number]: (inputs: bigint[]) => bigint; - 2: (inputs: bigint[]) => bigint; - 3: (inputs: bigint[]) => bigint; - 4: (inputs: bigint[]) => bigint; - 5: (inputs: bigint[]) => bigint; -} - -// a leaf is a single BigNumber -export type Leaf = bigint; - -// a node is a leaf or subroot in a quinary merkle tree -export type Node = Record; - -// a merkle proof is a set of path elements, path indices, and a root -export interface IMerkleProof { - pathElements: Leaf[][]; - pathIndices: number[]; - root: Leaf; - leaf: Leaf; -} - -export type StringifiedBigInts = - | StringifiedBigInts[] - | string - | string[] - | string[][] - | string[][][] - | { [key: string]: StringifiedBigInts } - | null; - -export type BigIntVariants = - | BigIntVariants[] - | StringifiedBigInts - | bigint - | bigint[] - | bigint[][] - | bigint[][][] - | { [key: string]: BigIntVariants } - | Uint8Array - | null; diff --git a/packages/hardhat/maci-ts/crypto/utils.ts b/packages/hardhat/maci-ts/crypto/utils.ts deleted file mode 100644 index f2e4f88..0000000 --- a/packages/hardhat/maci-ts/crypto/utils.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { hash5, hashLeftRight } from "./hashing"; -import { IncrementalQuinTree } from "./quinTree"; - -/** - * Calculate the depth of a tree given the number of leaves - * @param hashLength the hashing function param length - * @param numLeaves how many leaves - * @returns the depth - */ -export const calcDepthFromNumLeaves = (hashLength: number, numLeaves: number): number => { - let depth = 1; - let max = hashLength ** depth; - - while (BigInt(max) < numLeaves) { - depth += 1; - max = hashLength ** depth; - } - - return depth; -}; - -/** - * A helper function which hashes a list of results with a salt and returns the - * hash. - * @param leaves A list of values - * @param salt A random salt - * @param depth The tree depth - * @returns The hash of the leaves and the salt, with the salt last - */ -export const genTreeCommitment = (leaves: bigint[], salt: bigint, depth: number): bigint => { - const tree = new IncrementalQuinTree(depth, 0n, 5, hash5); - - leaves.forEach(leaf => { - tree.insert(leaf); - }); - - return hashLeftRight(tree.root, salt); -}; - -/** - * A helper function to generate the tree proof for the value at the given index in the leaves - * @param index The index of the value to generate the proof for - * @param leaves A list of values - * @param depth The tree depth - * @returns The proof - */ -export const genTreeProof = (index: number, leaves: bigint[], depth: number): bigint[][] => { - const tree = new IncrementalQuinTree(depth, 0n, 5, hash5); - leaves.forEach(leaf => { - tree.insert(leaf); - }); - - const proof = tree.genProof(index); - return proof.pathElements; -}; diff --git a/packages/hardhat/maci-ts/domainobjs/__tests__/ballot.test.ts b/packages/hardhat/maci-ts/domainobjs/__tests__/ballot.test.ts deleted file mode 100644 index 351382e..0000000 --- a/packages/hardhat/maci-ts/domainobjs/__tests__/ballot.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { expect } from "chai"; - -import { Ballot } from ".."; - -describe("Ballot", () => { - describe("constructor", () => { - it("should create an empty ballot", () => { - const b = new Ballot(0, 2); - expect(b.votes.length).to.eq(0); - }); - - it("should create a ballot with 1 vote", () => { - const b = new Ballot(1, 2); - expect(b.votes.length).to.eq(1); - expect(b.votes[0]).to.eq(BigInt(0)); - }); - }); - - describe("hash", () => { - it("should produce an hash of the ballot", () => { - const b = new Ballot(0, 2); - const h = b.hash(); - expect(h).to.not.eq(null); - }); - }); - - describe("copy", () => { - it("should produce a deep copy", () => { - const b1 = Ballot.genRandomBallot(2, 2); - const b2 = b1.copy(); - - expect(b1.voteOptionTreeDepth).to.eq(b2.voteOptionTreeDepth); - expect(b1.nonce).to.eq(b2.nonce); - expect(b1.votes.length).to.eq(b2.votes.length); - expect(b1.votes).to.deep.eq(b2.votes); - - expect(b1.equals(b2)).to.eq(true); - }); - }); - - describe("asCircuitInputs", () => { - it("should produce an array", () => { - const len = 2; - const b1 = Ballot.genRandomBallot(len, 2); - const arr = b1.asCircuitInputs(); - expect(arr).to.be.instanceOf(Array); - expect(arr.length).to.eq(len); - }); - }); - - describe("isEqual", () => { - it("should return false for ballots that are not equal (different votes length)", () => { - const b1 = Ballot.genRandomBallot(2, 2); - const b2 = Ballot.genRandomBallot(2, 3); - expect(b1.equals(b2)).to.eq(false); - }); - it("should return true for ballots that are equal", () => { - const b1 = new Ballot(0, 2); - const b2 = new Ballot(0, 2); - expect(b1.equals(b2)).to.eq(true); - }); - it("should return false for ballots that are not equal (different nonce)", () => { - const b1 = Ballot.genRandomBallot(3, 2); - const b2 = Ballot.genRandomBallot(2, 2); - b2.nonce = BigInt(1); - expect(b1.equals(b2)).to.eq(false); - }); - }); - - describe("asArray", () => { - it("should produce a valid result", () => { - const b1 = Ballot.genRandomBallot(2, 2); - b1.votes[0] = BigInt(1); - b1.votes[1] = BigInt(2); - b1.votes[2] = BigInt(3); - const arr = b1.asArray(); - expect(arr[0]).to.eq(b1.nonce); - }); - }); - - describe("genRandomBallot", () => { - it("should generate a ballot with a random nonce", () => { - const b1 = Ballot.genRandomBallot(2, 2); - const b2 = Ballot.genRandomBallot(2, 2); - expect(b1.nonce).to.not.eq(b2.nonce); - }); - }); - - describe("genBlankBallot", () => { - it("should generate a ballot with all votes set to 0", () => { - const b1 = Ballot.genBlankBallot(2, 2); - expect(b1.votes.every(v => v === BigInt(0))).to.eq(true); - }); - }); - - describe("serialization/deserialization", () => { - describe("toJSON", () => { - it("toJSON should produce a JSON object representing the ballot", () => { - const b1 = Ballot.genBlankBallot(2, 2); - const json = b1.toJSON(); - expect(json).to.have.property("votes"); - expect(json).to.have.property("nonce"); - expect(json).to.have.property("voteOptionTreeDepth"); - expect(json.votes.length).to.eq(b1.votes.length); - expect(json.nonce).to.eq(b1.nonce.toString()); - expect(json.voteOptionTreeDepth).to.eq(b1.voteOptionTreeDepth.toString()); - }); - }); - - describe("fromJSON", () => { - it("should create a ballot from a JSON object", () => { - const b1 = Ballot.genBlankBallot(2, 2); - const json = b1.toJSON(); - const b2 = Ballot.fromJSON(json); - expect(b1.equals(b2)).to.eq(true); - }); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/domainobjs/__tests__/commands.test.ts b/packages/hardhat/maci-ts/domainobjs/__tests__/commands.test.ts deleted file mode 100644 index b4fdd9d..0000000 --- a/packages/hardhat/maci-ts/domainobjs/__tests__/commands.test.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { expect } from "chai"; -import { genRandomSalt } from "../../crypto"; - -import { PCommand, Keypair, TCommand } from ".."; - -describe("Commands", () => { - const { privKey, pubKey } = new Keypair(); - - const ecdhSharedKey = Keypair.genEcdhSharedKey(privKey, pubKey); - // eslint-disable-next-line no-bitwise - const random50bitBigInt = (): bigint => ((BigInt(1) << BigInt(50)) - BigInt(1)) & BigInt(genRandomSalt().toString()); - - describe("constructor", () => { - it("should create a PCommand", () => { - const command: PCommand = new PCommand( - random50bitBigInt(), - pubKey, - random50bitBigInt(), - random50bitBigInt(), - random50bitBigInt(), - random50bitBigInt(), - genRandomSalt(), - ); - expect(command).to.not.eq(null); - }); - it("should create a TCommand", () => { - const command: TCommand = new TCommand(random50bitBigInt(), random50bitBigInt(), random50bitBigInt()); - expect(command).to.not.eq(null); - }); - }); - - describe("signature", () => { - const command: PCommand = new PCommand( - random50bitBigInt(), - pubKey, - random50bitBigInt(), - random50bitBigInt(), - random50bitBigInt(), - random50bitBigInt(), - genRandomSalt(), - ); - it("should produce a valid signature", () => { - const signature = command.sign(privKey); - expect(command.verifySignature(signature, pubKey)).to.eq(true); - }); - }); - - describe("encryption", () => { - const command: PCommand = new PCommand( - random50bitBigInt(), - pubKey, - random50bitBigInt(), - random50bitBigInt(), - random50bitBigInt(), - random50bitBigInt(), - genRandomSalt(), - ); - - const signature = command.sign(privKey); - - describe("encrypt", () => { - it("should encrypt a command", () => { - const message = command.encrypt(signature, ecdhSharedKey); - expect(message).to.not.eq(null); - }); - }); - - describe("decrypt", () => { - const message = command.encrypt(signature, ecdhSharedKey); - - const decrypted = PCommand.decrypt(message, ecdhSharedKey); - - it("should decrypt a message and keep the correct values", () => { - expect(decrypted).to.not.eq(null); - expect(decrypted.command.equals(command)).to.eq(true); - expect(decrypted.signature.R8[0].toString()).to.eq(signature.R8[0].toString()); - expect(decrypted.signature.R8[1].toString()).to.eq(signature.R8[1].toString()); - expect(decrypted.signature.S.toString()).to.eq(signature.S.toString()); - }); - - it("should have a valid signature after decryption", () => { - const decryptedForce = PCommand.decrypt(message, ecdhSharedKey, true); - - const isValid = decrypted.command.verifySignature(decrypted.signature, pubKey); - expect(isValid).to.eq(true); - - const isValidForce = decryptedForce.command.verifySignature(decryptedForce.signature, pubKey); - expect(isValidForce).to.eq(true); - }); - }); - }); - - describe("copy", () => { - it("should produce a deep copy for PCommand", () => { - const c1: PCommand = new PCommand(BigInt(10), pubKey, BigInt(0), BigInt(9), BigInt(1), BigInt(123)); - - // shallow copy - const c2 = c1; - c1.nonce = BigInt(9999); - expect(c1.nonce.toString()).to.eq(c2.nonce.toString()); - - // deep copy - const c3 = c1.copy(); - c1.nonce = BigInt(8888); - - expect(c1.nonce.toString()).not.to.eq(c3.nonce.toString()); - }); - - it("should produce a deep copy for TCommand", () => { - const c1: TCommand = new TCommand(BigInt(10), BigInt(0), BigInt(9)); - - // shallow copy - const c2 = c1; - c1.amount = BigInt(9999); - expect(c1.amount.toString()).to.eq(c2.amount.toString()); - - // deep copy - const c3 = c1.copy(); - c1.amount = BigInt(8888); - - expect(c1.amount.toString()).not.to.eq(c3.amount.toString()); - }); - }); - - describe("deserialization/serialization", () => { - describe("toJSON", () => { - it("should produce a JSON object with valid values", () => { - const c1: TCommand = new TCommand(BigInt(10), BigInt(0), BigInt(9)); - const json = c1.toJSON(); - expect(json).to.not.eq(null); - expect(json.cmdType).to.eq("2"); - expect(json.stateIndex).to.eq("10"); - expect(json.amount).to.eq("0"); - expect(json.pollId).to.eq("9"); - }); - it("should produce a JSON object with valid values", () => { - const c1: PCommand = new PCommand(BigInt(10), pubKey, BigInt(0), BigInt(9), BigInt(1), BigInt(123)); - const json = c1.toJSON(); - expect(json).to.not.eq(null); - expect(json.stateIndex).to.eq("10"); - expect(json.voteOptionIndex).to.eq("0"); - expect(json.newVoteWeight).to.eq("9"); - expect(json.nonce).to.eq("1"); - expect(json.pollId).to.eq("123"); - expect(json.cmdType).to.eq("1"); - }); - }); - - describe("fromJSON", () => { - it("should produce a TCommand from a JSON object", () => { - const c1: TCommand = new TCommand(BigInt(10), BigInt(0), BigInt(9)); - const json = c1.toJSON(); - const c2 = TCommand.fromJSON(json); - expect(c2.equals(c1)).to.eq(true); - }); - it("should produce a PCommand from a JSON object", () => { - const c1: PCommand = new PCommand(BigInt(10), pubKey, BigInt(0), BigInt(9), BigInt(1), BigInt(123)); - const json = c1.toJSON(); - const c2 = PCommand.fromJSON(json); - expect(c2.equals(c1)).to.eq(true); - }); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/domainobjs/__tests__/keypair.test.ts b/packages/hardhat/maci-ts/domainobjs/__tests__/keypair.test.ts deleted file mode 100644 index 86a5217..0000000 --- a/packages/hardhat/maci-ts/domainobjs/__tests__/keypair.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { expect } from "chai"; -import { genKeypair, genPrivKey } from "../../crypto"; - -import { Keypair, PrivKey } from ".."; - -describe("keypair", () => { - describe("constructor", () => { - it("should generate a random keypair if not provided a private key", () => { - const k1 = new Keypair(); - const k2 = new Keypair(); - - expect(k1.equals(k2)).to.eq(false); - - expect(k1.privKey.rawPrivKey).not.to.eq(k2.privKey.rawPrivKey); - }); - - it("should generate the correct public key given a private key", () => { - const rawKeyPair = genKeypair(); - const k = new Keypair(new PrivKey(rawKeyPair.privKey)); - expect(rawKeyPair.pubKey[0]).to.eq(k.pubKey.rawPubKey[0]); - expect(rawKeyPair.pubKey[1]).to.eq(k.pubKey.rawPubKey[1]); - }); - }); - - describe("equals", () => { - it("should return false for two completely different keypairs", () => { - const k1 = new Keypair(); - const k2 = new Keypair(); - expect(k1.equals(k2)).to.eq(false); - }); - - it("should return false for two keypairs with different private keys", () => { - const privateKey = new PrivKey(genPrivKey()); - const privateKey2 = new PrivKey(genPrivKey()); - const k1 = new Keypair(privateKey); - const k2 = new Keypair(privateKey2); - expect(k1.equals(k2)).to.eq(false); - }); - it("should throw when the private keys are equal but the public keys are not", () => { - const privateKey = new PrivKey(genPrivKey()); - const k1 = new Keypair(privateKey); - const k2 = new Keypair(privateKey); - k2.pubKey.rawPubKey[0] = BigInt(9); - expect(() => k1.equals(k2)).to.throw(); - }); - it("should return true for two identical keypairs", () => { - const k1 = new Keypair(); - const k2 = k1.copy(); - expect(k1.equals(k2)).to.eq(true); - }); - }); - - describe("copy", () => { - it("should produce a deep copy", () => { - const k1 = new Keypair(); - - // shallow copy - const k2 = k1; - - expect(k1.privKey.rawPrivKey.toString()).to.eq(k2.privKey.rawPrivKey.toString()); - k1.privKey.rawPrivKey = BigInt(0); - expect(k1.privKey.rawPrivKey.toString()).to.eq(k2.privKey.rawPrivKey.toString()); - - // deep copy - const k3 = new Keypair(); - const k4 = k3.copy(); - expect(k3.privKey.rawPrivKey.toString()).to.eq(k4.privKey.rawPrivKey.toString()); - - k3.privKey.rawPrivKey = BigInt(0); - expect(k3.privKey.rawPrivKey.toString()).not.to.eq(k4.privKey.rawPrivKey.toString()); - }); - }); - - describe("genEcdhSharedKey", () => { - it("should produce a shared key", () => { - const k1 = new Keypair(); - const k2 = new Keypair(); - const sharedKey = Keypair.genEcdhSharedKey(k1.privKey, k2.pubKey); - expect(sharedKey).to.not.eq(null); - }); - }); - - describe("serialization/deserialization", () => { - describe("toJSON", () => { - it("should produce a JSON object", () => { - const k1 = new Keypair(); - const json = k1.toJSON(); - expect(json).to.not.eq(null); - }); - it("should produce a JSON object with the correct keys", () => { - const k1 = new Keypair(); - const json = k1.toJSON(); - expect(Object.keys(json)).to.deep.eq(["privKey", "pubKey"]); - }); - it("should preserve the data correctly", () => { - const k1 = new Keypair(); - const json = k1.toJSON(); - - expect(k1.privKey.serialize()).to.eq(json.privKey); - expect(k1.pubKey.serialize()).to.eq(json.pubKey); - }); - }); - - describe("fromJSON", () => { - it("should produce a Keypair instance", () => { - const k1 = new Keypair(); - const json = k1.toJSON(); - const k2 = Keypair.fromJSON(json); - expect(k2).to.be.instanceOf(Keypair); - }); - it("should preserve the data correctly", () => { - const k1 = new Keypair(); - const json = k1.toJSON(); - const k2 = Keypair.fromJSON(json); - expect(k1.equals(k2)).to.eq(true); - }); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/domainobjs/__tests__/message.test.ts b/packages/hardhat/maci-ts/domainobjs/__tests__/message.test.ts deleted file mode 100644 index 5491e98..0000000 --- a/packages/hardhat/maci-ts/domainobjs/__tests__/message.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { expect } from "chai"; - -import { Message, Keypair } from ".."; - -describe("message", () => { - describe("constructor", () => { - it("should create a new message", () => { - const msg = new Message(BigInt(0), Array(10).fill(BigInt(0))); - expect(msg).to.not.eq(null); - }); - it("should throw an error if the data length is not 10", () => { - expect(() => new Message(BigInt(0), Array(9).fill(BigInt(0)))).to.throw(); - }); - }); - - describe("asCircuitInputs", () => { - it("should produce an array", () => { - const msg = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const arr = msg.asCircuitInputs(); - expect(arr).to.be.instanceOf(Array); - expect(arr.length).to.eq(11); - expect(arr).to.deep.eq([BigInt(0), ...Array(10).fill(BigInt(0))]); - }); - }); - - describe("asContractParam", () => { - it("should produce an object", () => { - const msg = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const obj = msg.asContractParam(); - expect(obj).to.be.instanceOf(Object); - expect(Object.keys(obj)).to.deep.eq(["msgType", "data"]); - }); - it("should produce an object with the correct values", () => { - const msg = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const obj = msg.asContractParam(); - expect(obj.msgType).to.eq("0"); - expect(obj.data).to.deep.eq(Array(10).fill("0")); - }); - }); - - describe("hash", () => { - const keypair = new Keypair(); - it("should produce a hash", () => { - const msg = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const h = msg.hash(keypair.pubKey); - expect(h).to.not.eq(null); - }); - it("should produce the same hash for the same ballot", () => { - const msg1 = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const msg2 = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const h1 = msg1.hash(keypair.pubKey); - const h2 = msg2.hash(keypair.pubKey); - expect(h1).to.eq(h2); - }); - }); - describe("copy", () => { - it("should produce a deep copy", () => { - const msg1 = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const msg2 = msg1.copy(); - expect(msg1.equals(msg2)).to.eq(true); - expect(msg1.data).to.deep.eq(msg2.data); - expect(msg1.msgType).to.eq(msg2.msgType); - }); - }); - describe("equals", () => { - it("should return false for messages that are not equal (different length)", () => { - const msg1 = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const msg2 = new Message(BigInt(0), Array(10).fill(BigInt(0))); - msg1.data[10] = BigInt(1); - expect(msg1.equals(msg2)).to.eq(false); - }); - it("should return true for messages that are equal", () => { - const msg1 = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const msg2 = new Message(BigInt(0), Array(10).fill(BigInt(0))); - expect(msg1.equals(msg2)).to.eq(true); - }); - it("should return false when the message type is not equal", () => { - const msg1 = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const msg2 = new Message(BigInt(1), Array(10).fill(BigInt(0))); - expect(msg1.equals(msg2)).to.eq(false); - }); - }); - - describe("serialization/deserialization", () => { - describe("toJSON", () => { - it("should produce a JSON object", () => { - const msg = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const json = msg.toJSON(); - expect(json).to.not.eq(null); - }); - it("should produce a JSON object with the correct keys", () => { - const msg = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const json = msg.toJSON(); - expect(Object.keys(json)).to.deep.eq(["msgType", "data"]); - }); - it("should preserve the data correctly", () => { - const msg = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const json = msg.toJSON(); - - expect(msg.msgType.toString()).to.eq(json.msgType); - expect(msg.data.map((x: bigint) => x.toString())).to.deep.eq(json.data); - }); - }); - - describe("fromJSON", () => { - it("should produce a Message instance", () => { - const msg = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const json = msg.toJSON(); - const msg2 = Message.fromJSON(json); - expect(msg2).to.be.instanceOf(Message); - }); - it("should preserve the data correctly", () => { - const msg = new Message(BigInt(0), Array(10).fill(BigInt(0))); - const json = msg.toJSON(); - const msg2 = Message.fromJSON(json); - expect(msg.equals(msg2)).to.eq(true); - }); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/domainobjs/__tests__/privateKey.test.ts b/packages/hardhat/maci-ts/domainobjs/__tests__/privateKey.test.ts deleted file mode 100644 index fc3119b..0000000 --- a/packages/hardhat/maci-ts/domainobjs/__tests__/privateKey.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { expect } from "chai"; -import { SNARK_FIELD_SIZE, genPrivKey } from "../../crypto"; - -import { Keypair, PrivKey } from ".."; - -describe("privateKey", function test() { - this.timeout(90000); - - describe("constructor", () => { - it("should create a private key", () => { - const priv = genPrivKey(); - const k = new PrivKey(priv); - expect(k).to.not.eq(null); - }); - it("should create the same private key object for the same raw key", () => { - const priv = genPrivKey(); - const k1 = new PrivKey(priv); - const k2 = new PrivKey(priv); - expect(k1.rawPrivKey.toString()).to.eq(k2.rawPrivKey.toString()); - }); - }); - - describe("serialization", () => { - describe("serialize", () => { - it("should serialize the private key", () => { - const priv = genPrivKey(); - const k = new PrivKey(priv); - const s = k.serialize(); - expect(s.startsWith("macisk.")).to.eq(true); - const d = `0x${s.slice(7)}`; - expect(priv.toString()).to.eq(BigInt(d).toString()); - }); - - it("should always return a key with the same length", () => { - for (let i = 0; i < 100; i += 1) { - const k = new Keypair(); - const s = k.privKey.serialize(); - expect(s.length).to.eq(71); - } - }); - }); - - describe("deserialize", () => { - it("should deserialize the private key", () => { - const priv = genPrivKey(); - const k = new PrivKey(priv); - const s = k.serialize(); - const k2 = PrivKey.deserialize(s); - expect(k.rawPrivKey.toString()).to.eq(k2.rawPrivKey.toString()); - }); - }); - - describe("isValidSerializedPrivKey", () => { - it("should return true for a valid serialized private key", () => { - const priv = genPrivKey(); - const k = new PrivKey(priv); - const s = k.serialize(); - expect(PrivKey.isValidSerializedPrivKey(s)).to.eq(true); - }); - it("should return false for an invalid serialized private key", () => { - const s = "macisk.0x1234567890"; - expect(PrivKey.isValidSerializedPrivKey(s)).to.eq(false); - }); - }); - - describe("toJSON", () => { - it("should produce a JSON object", () => { - const priv = genPrivKey(); - const k = new PrivKey(priv); - const json = k.toJSON(); - expect(json).to.not.eq(null); - }); - it("should produce a JSON object with the correct keys", () => { - const priv = genPrivKey(); - const k = new PrivKey(priv); - const json = k.toJSON(); - expect(Object.keys(json)).to.deep.eq(["privKey"]); - }); - it("should preserve the data correctly", () => { - const priv = genPrivKey(); - const k = new PrivKey(priv); - const json = k.toJSON(); - expect(k.serialize()).to.eq(json.privKey); - }); - }); - - describe("fromJSON", () => { - it("should produce a PrivKey instance", () => { - const priv = genPrivKey(); - const k = new PrivKey(priv); - const json = k.toJSON(); - const k2 = PrivKey.fromJSON(json); - expect(k2).to.be.instanceOf(PrivKey); - }); - }); - }); - - describe("copy", () => { - it("should produce a deep copy", () => { - const k = new Keypair(); - const sk1 = k.privKey; - - // shallow copy - const sk2 = sk1; - - expect(sk1.rawPrivKey.toString()).to.eq(sk2.rawPrivKey.toString()); - sk1.rawPrivKey = BigInt(0); - expect(sk1.rawPrivKey.toString()).to.eq(sk2.rawPrivKey.toString()); - - // deep copy - const k1 = new Keypair(); - const sk3 = k1.privKey; - const sk4 = sk3.copy(); - expect(sk3.rawPrivKey.toString()).to.eq(sk4.rawPrivKey.toString()); - sk4.rawPrivKey = BigInt(0); - expect(sk3.rawPrivKey.toString()).not.to.eq(sk4.rawPrivKey.toString()); - }); - }); - - describe("asCircuitInputs", () => { - it("should generate a value that is < SNARK_FIELD_SIZE", () => { - const k = new Keypair(); - const sk = k.privKey; - const circuitInputs = sk.asCircuitInputs(); - expect(BigInt(circuitInputs) < SNARK_FIELD_SIZE).to.eq(true); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/domainobjs/__tests__/publicKey.test.ts b/packages/hardhat/maci-ts/domainobjs/__tests__/publicKey.test.ts deleted file mode 100644 index 6747d88..0000000 --- a/packages/hardhat/maci-ts/domainobjs/__tests__/publicKey.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { expect } from "chai"; -import { SNARK_FIELD_SIZE, unpackPubKey } from "../../crypto"; - -import { Keypair, PubKey } from ".."; - -describe("public key", () => { - describe("constructor", () => { - it("should create a public key", () => { - const k = new Keypair(); - const pk = new PubKey(k.pubKey.rawPubKey); - expect(pk).to.not.eq(null); - }); - it("should create the same public key object for the same raw key", () => { - const k = new Keypair(); - const pk1 = new PubKey(k.pubKey.rawPubKey); - const pk2 = new PubKey(k.pubKey.rawPubKey); - expect(pk1.rawPubKey.toString()).to.eq(pk2.rawPubKey.toString()); - }); - it("should fail to create a public key if the raw key is invalid", () => { - expect(() => new PubKey([BigInt(0), BigInt(SNARK_FIELD_SIZE)])).to.throw(); - expect(() => new PubKey([BigInt(SNARK_FIELD_SIZE), BigInt(0)])).to.throw(); - expect(() => new PubKey([BigInt(SNARK_FIELD_SIZE), BigInt(SNARK_FIELD_SIZE)])).to.throw(); - }); - }); - - describe("copy", () => { - it("should produce a deep copy", () => { - const k = new Keypair(); - const pk1 = k.pubKey; - - // shallow copy - const pk2 = pk1; - - expect(pk1.rawPubKey.toString()).to.eq(pk2.rawPubKey.toString()); - pk1.rawPubKey = [BigInt(0), BigInt(0)]; - expect(pk1.rawPubKey.toString()).to.eq(pk2.rawPubKey.toString()); - - // deep copy - const k1 = new Keypair(); - const pk3 = k1.pubKey; - const pk4 = pk3.copy(); - expect(pk3.rawPubKey.toString()).to.eq(pk4.rawPubKey.toString()); - pk4.rawPubKey = [BigInt(0), BigInt(0)]; - expect(pk3.rawPubKey.toString()).not.to.eq(pk4.rawPubKey.toString()); - }); - }); - - describe("serialization", () => { - describe("serialize", () => { - it("should serialize into a string", () => { - const k = new Keypair(); - const pk1 = k.pubKey; - - const s = pk1.serialize(); - expect(s.startsWith("macipk.")).to.eq(true); - - const unpacked = unpackPubKey(BigInt(`0x${s.slice(7).toString()}`)); - - expect(unpacked[0].toString()).to.eq(pk1.rawPubKey[0].toString()); - expect(unpacked[1].toString()).to.eq(pk1.rawPubKey[1].toString()); - }); - it("should serialize into an invalid serialized key when the key is [bigint(0), bigint(0)]", () => { - const pk1 = new PubKey([BigInt(0), BigInt(0)]); - const s = pk1.serialize(); - expect(s).to.eq("macipk.z"); - }); - }); - - describe("deserialize", () => { - it("should deserialize the public key", () => { - const k = new Keypair(); - const pk1 = k.pubKey; - const s = pk1.serialize(); - const pk2 = PubKey.deserialize(s); - expect(pk1.rawPubKey.toString()).to.eq(pk2.rawPubKey.toString()); - }); - it("should deserialize into an invalid serialized key when the key is equal to macipk.z", () => { - const pk1 = PubKey.deserialize("macipk.z"); - expect(pk1.rawPubKey.toString()).to.eq([BigInt(0), BigInt(0)].toString()); - }); - }); - - describe("isValidSerializedPubKey", () => { - const k = new Keypair(); - const s = k.pubKey.serialize(); - it("should return true for keys that are serialized in the correct format", () => { - expect(PubKey.isValidSerializedPubKey(s)).to.eq(true); - }); - - it("should return false for keys that are not serialized in the correct format", () => { - expect(PubKey.isValidSerializedPubKey(`${s}ffffffffffffffffffffffffffffff`)).to.eq(false); - expect(PubKey.isValidSerializedPubKey(s.slice(1))).to.eq(false); - }); - }); - - describe("toJSON", () => { - it("should produce a JSON object", () => { - const k = new Keypair(); - const pk1 = k.pubKey; - const json = pk1.toJSON(); - expect(json).to.not.eq(null); - }); - it("should produce a JSON object with the correct keys", () => { - const k = new Keypair(); - const pk1 = k.pubKey; - const json = pk1.toJSON(); - expect(Object.keys(json)).to.deep.eq(["pubKey"]); - }); - it("should preserve the data correctly", () => { - const k = new Keypair(); - const pk1 = k.pubKey; - const json = pk1.toJSON(); - - expect(pk1.serialize()).to.eq(json.pubKey); - }); - }); - describe("fromJSON", () => { - it("should produce a public key", () => { - const k = new Keypair(); - const pk1 = k.pubKey; - const json = pk1.toJSON(); - const pk2 = PubKey.fromJSON(json); - expect(pk2).to.not.eq(null); - }); - it("should produce the same public key object for the same raw key", () => { - const k = new Keypair(); - const pk1 = k.pubKey; - const json = pk1.toJSON(); - const pk2 = PubKey.fromJSON(json); - expect(pk1.rawPubKey.toString()).to.eq(pk2.rawPubKey.toString()); - }); - }); - }); - - describe("asContractParam", () => { - it("should produce an object with the correct values", () => { - const k = new Keypair(); - const pk1 = k.pubKey; - const obj = pk1.asContractParam(); - expect(obj.x).to.eq(pk1.rawPubKey[0].toString()); - expect(obj.y).to.eq(pk1.rawPubKey[1].toString()); - }); - }); - - describe("asCircuitInputs", () => { - it("should produce an array with the two points of the public key", () => { - const k = new Keypair(); - const pk1 = k.pubKey; - const arr = pk1.asCircuitInputs(); - expect(arr).to.be.instanceOf(Array); - expect(arr.length).to.eq(2); - }); - }); - - describe("asArray", () => { - it("should produce an array with the two points of the public key", () => { - const k = new Keypair(); - const pk1 = k.pubKey; - const arr = pk1.asArray(); - expect(arr).to.be.instanceOf(Array); - expect(arr.length).to.eq(2); - expect(arr).to.deep.eq(pk1.rawPubKey); - }); - }); - - describe("hash", () => { - it("should produce a hash", () => { - const k = new Keypair(); - const pk1 = k.pubKey; - const h = pk1.hash(); - expect(h).to.not.eq(null); - }); - }); - - describe("equals", () => { - it("should return false for public keys that are not equal", () => { - const k1 = new Keypair(); - const pk1 = k1.pubKey; - const k2 = new Keypair(); - const pk2 = k2.pubKey; - expect(pk1.equals(pk2)).to.eq(false); - }); - it("should return true for public keys that are equal", () => { - const k1 = new Keypair(); - const pk1 = k1.pubKey; - const pk2 = pk1.copy(); - expect(pk1.equals(pk2)).to.eq(true); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/domainobjs/__tests__/stateLeaf.test.ts b/packages/hardhat/maci-ts/domainobjs/__tests__/stateLeaf.test.ts deleted file mode 100644 index 6c8e2ad..0000000 --- a/packages/hardhat/maci-ts/domainobjs/__tests__/stateLeaf.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { expect } from "chai"; - -import { Keypair, StateLeaf } from ".."; - -describe("stateLeaf", () => { - const { pubKey } = new Keypair(); - - describe("constructor", () => { - it("should create a state leaf", () => { - const stateLeaf = new StateLeaf(pubKey, BigInt(123), BigInt(1231267)); - expect(stateLeaf).to.not.eq(null); - expect(stateLeaf.pubKey.equals(pubKey)).to.eq(true); - expect(stateLeaf.voiceCreditBalance).to.eq(BigInt(123)); - expect(stateLeaf.timestamp).to.eq(BigInt(1231267)); - }); - }); - - describe("copy", () => { - it("should create an exact copy of the state leaf", () => { - const stateLeaf = new StateLeaf(pubKey, BigInt(123), BigInt(1231267)); - - const copy = stateLeaf.copy(); - - expect(stateLeaf.equals(copy)).to.eq(true); - }); - }); - - describe("genBlankLeaf", () => { - it("should return a blank leaf", () => { - const blankLeaf = StateLeaf.genBlankLeaf(); - expect(blankLeaf.pubKey.rawPubKey[0]).to.eq( - BigInt("10457101036533406547632367118273992217979173478358440826365724437999023779287"), - ); - expect(blankLeaf.pubKey.rawPubKey[1]).to.eq( - BigInt("19824078218392094440610104313265183977899662750282163392862422243483260492317"), - ); - expect(blankLeaf.voiceCreditBalance).to.eq(BigInt(0)); - expect(blankLeaf.timestamp).to.eq(BigInt(0)); - }); - }); - - describe("genRandomLeaf", () => { - it("should return a random leaf", () => { - const randomLeaf = StateLeaf.genRandomLeaf(); - const randomLeaf2 = StateLeaf.genRandomLeaf(); - expect(randomLeaf.equals(randomLeaf2)).to.eq(false); - }); - }); - - describe("equals", () => { - it("should return true when comparing two equal state leaves", () => { - const stateLeaf = new StateLeaf(pubKey, BigInt(123), BigInt(1231267)); - - const stateLeaf2 = new StateLeaf(pubKey, BigInt(123), BigInt(1231267)); - - expect(stateLeaf.equals(stateLeaf2)).to.eq(true); - }); - - it("should return false when comparing two different state leaves", () => { - const stateLeaf = new StateLeaf(pubKey, BigInt(123), BigInt(1231267)); - - const stateLeaf2 = new StateLeaf(pubKey, BigInt(123), BigInt(1231268)); - - expect(stateLeaf.equals(stateLeaf2)).to.eq(false); - }); - }); - - describe("serialization", () => { - describe("serialize", () => { - it("should work correctly", () => { - const stateLeaf = new StateLeaf(pubKey, BigInt(123), BigInt(1231267)); - - const serialized = stateLeaf.serialize(); - expect(serialized).to.not.eq(null); - }); - }); - - describe("deserialize", () => { - it("should work correctly", () => { - const stateLeaf = new StateLeaf(pubKey, BigInt(123), BigInt(1231267)); - - const serialized = stateLeaf.serialize(); - const deserialized = StateLeaf.deserialize(serialized); - expect(deserialized.equals(stateLeaf)).to.eq(true); - }); - }); - - describe("toJSON", () => { - it("should produce an object with the correct properties", () => { - const stateLeaf = new StateLeaf(pubKey, BigInt(123), BigInt(1231267)); - - const json = stateLeaf.toJSON(); - expect(json).to.not.eq(null); - - expect(Object.keys(json)).to.deep.eq(["pubKey", "voiceCreditBalance", "timestamp"]); - }); - }); - - describe("fromJSON", () => { - it("should produce a state leaf from a JSON object", () => { - const stateLeaf = new StateLeaf(pubKey, BigInt(123), BigInt(1231267)); - - const json = stateLeaf.toJSON(); - const deserialized = StateLeaf.fromJSON(json); - expect(deserialized.equals(stateLeaf)).to.eq(true); - }); - }); - }); - - describe("asCircuitInputs", () => { - it("should return an array", () => { - const stateLeaf = new StateLeaf(pubKey, BigInt(123), BigInt(1231267)); - - const arr = stateLeaf.asCircuitInputs(); - expect(arr).to.be.instanceOf(Array); - expect(arr.length).to.eq(4); - }); - }); - - describe("asContractParam", () => { - it("should return an object with the correct properties and values", () => { - const stateLeaf = new StateLeaf(pubKey, BigInt(123), BigInt(1231267)); - const obj = stateLeaf.asContractParam(); - expect(obj).to.not.eq(null); - expect(Object.keys(obj)).to.deep.eq(["pubKey", "voiceCreditBalance", "timestamp"]); - expect(obj.pubKey).to.deep.eq(pubKey.asContractParam()); - expect(obj.voiceCreditBalance).to.eq("123"); - expect(obj.timestamp).to.eq("1231267"); - }); - }); - - describe("hash", () => { - it("should hash into a single bigint value which is not null", () => { - const stateLeaf = new StateLeaf(pubKey, BigInt(123), BigInt(1231267)); - - const hash = stateLeaf.hash(); - expect(hash).to.not.eq(null); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/domainobjs/__tests__/verifyingKey.test.ts b/packages/hardhat/maci-ts/domainobjs/__tests__/verifyingKey.test.ts deleted file mode 100644 index 9254bd7..0000000 --- a/packages/hardhat/maci-ts/domainobjs/__tests__/verifyingKey.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { expect } from "chai"; -import { G1Point } from "../../crypto"; - -import fs from "fs"; -import path from "path"; - -import { IVkObjectParams, VerifyingKey } from ".."; - -describe("verifyingKey", () => { - describe("fromJSON", () => { - it("should convert a JSON file from snarkjs to a VerifyingKey", () => { - const file = path.join(__dirname, "./artifacts/test_vk.json"); - const j = fs.readFileSync(file).toString(); - const d = JSON.parse(j) as IVkObjectParams; - const vk = VerifyingKey.fromJSON(j); - - expect(d.vk_alpha_1[0]).to.eq(vk.alpha1.x.toString()); - expect(d.vk_alpha_1[1]).to.eq(vk.alpha1.y.toString()); - - expect(d.vk_beta_2[0][0]).to.eq(vk.beta2.x[1].toString()); - expect(d.vk_beta_2[0][1]).to.eq(vk.beta2.x[0].toString()); - expect(d.vk_beta_2[1][0]).to.eq(vk.beta2.y[1].toString()); - expect(d.vk_beta_2[1][1]).to.eq(vk.beta2.y[0].toString()); - - expect(d.vk_gamma_2[0][0]).to.eq(vk.gamma2.x[1].toString()); - expect(d.vk_gamma_2[0][1]).to.eq(vk.gamma2.x[0].toString()); - expect(d.vk_gamma_2[1][0]).to.eq(vk.gamma2.y[1].toString()); - expect(d.vk_gamma_2[1][1]).to.eq(vk.gamma2.y[0].toString()); - - expect(d.vk_delta_2[0][0]).to.eq(vk.delta2.x[1].toString()); - expect(d.vk_delta_2[0][1]).to.eq(vk.delta2.x[0].toString()); - expect(d.vk_delta_2[1][0]).to.eq(vk.delta2.y[1].toString()); - expect(d.vk_delta_2[1][1]).to.eq(vk.delta2.y[0].toString()); - - expect(d.IC.length).to.eq(vk.ic.length); - for (let i = 0; i < d.IC.length; i += 1) { - expect(d.IC[i][0]).to.eq(vk.ic[i].x.toString()); - expect(d.IC[i][1]).to.eq(vk.ic[i].y.toString()); - } - }); - }); - - describe("copy", () => { - it("Copy should generate a deep copy", () => { - const file = path.join(__dirname, "./artifacts/test_vk.json"); - const j = fs.readFileSync(file).toString(); - const vk = VerifyingKey.fromJSON(j); - - const vk2 = vk.copy(); - expect(vk.equals(vk2)).to.eq(true); - }); - }); - - describe("equals", () => { - it("should return true for equal verifying keys", () => { - const file = path.join(__dirname, "./artifacts/test_vk.json"); - const j = fs.readFileSync(file).toString(); - const vk = VerifyingKey.fromJSON(j); - const vk2 = vk.copy(); - expect(vk.equals(vk2)).to.eq(true); - }); - it("should return false for unequal verifying keys", () => { - const file = path.join(__dirname, "./artifacts/test_vk.json"); - const j = fs.readFileSync(file).toString(); - const vk = VerifyingKey.fromJSON(j); - const vk2 = vk.copy(); - vk2.alpha1.x = BigInt(123); - expect(vk.equals(vk2)).to.eq(false); - }); - it("should return false for unequal verifying keys (different ic)", () => { - const file = path.join(__dirname, "./artifacts/test_vk.json"); - const j = fs.readFileSync(file).toString(); - const vk = VerifyingKey.fromJSON(j); - const vk2 = vk.copy(); - vk2.ic[15] = {} as unknown as G1Point; - expect(vk.equals(vk2)).to.eq(false); - }); - }); - - describe("fromObj", () => { - it("should convert an object to a VerifyingKey", () => { - const file = path.join(__dirname, "./artifacts/test_vk.json"); - const j = fs.readFileSync(file).toString(); - const d = JSON.parse(j) as IVkObjectParams; - const vk = VerifyingKey.fromObj(d); - - expect(d.vk_alpha_1[0]).to.eq(vk.alpha1.x.toString()); - expect(d.vk_alpha_1[1]).to.eq(vk.alpha1.y.toString()); - - expect(d.vk_beta_2[0][0]).to.eq(vk.beta2.x[1].toString()); - expect(d.vk_beta_2[0][1]).to.eq(vk.beta2.x[0].toString()); - expect(d.vk_beta_2[1][0]).to.eq(vk.beta2.y[1].toString()); - expect(d.vk_beta_2[1][1]).to.eq(vk.beta2.y[0].toString()); - - expect(d.vk_gamma_2[0][0]).to.eq(vk.gamma2.x[1].toString()); - expect(d.vk_gamma_2[0][1]).to.eq(vk.gamma2.x[0].toString()); - expect(d.vk_gamma_2[1][0]).to.eq(vk.gamma2.y[1].toString()); - expect(d.vk_gamma_2[1][1]).to.eq(vk.gamma2.y[0].toString()); - - expect(d.vk_delta_2[0][0]).to.eq(vk.delta2.x[1].toString()); - expect(d.vk_delta_2[0][1]).to.eq(vk.delta2.x[0].toString()); - expect(d.vk_delta_2[1][0]).to.eq(vk.delta2.y[1].toString()); - expect(d.vk_delta_2[1][1]).to.eq(vk.delta2.y[0].toString()); - - expect(d.IC.length).to.eq(vk.ic.length); - for (let i = 0; i < d.IC.length; i += 1) { - expect(d.IC[i][0]).to.eq(vk.ic[i].x.toString()); - expect(d.IC[i][1]).to.eq(vk.ic[i].y.toString()); - } - }); - }); - - describe("asContractParam", () => { - it("should produce an object with the correct properties", () => { - const file = path.join(__dirname, "./artifacts/test_vk.json"); - const j = fs.readFileSync(file).toString(); - const vk = VerifyingKey.fromJSON(j); - const obj = vk.asContractParam(); - - expect(Object.keys(obj)).to.deep.eq(["alpha1", "beta2", "gamma2", "delta2", "ic"]); - }); - }); - - describe("fromContract", () => { - it("should produce a VerifyingKey from a contract object", () => { - const file = path.join(__dirname, "./artifacts/test_vk.json"); - const j = fs.readFileSync(file).toString(); - const vk = VerifyingKey.fromJSON(j); - const obj = vk.asContractParam(); - const vk2 = VerifyingKey.fromContract(obj); - - expect(vk.equals(vk2)).to.eq(true); - }); - }); -}); diff --git a/packages/hardhat/maci-ts/domainobjs/ballot.ts b/packages/hardhat/maci-ts/domainobjs/ballot.ts deleted file mode 100644 index 910b5a7..0000000 --- a/packages/hardhat/maci-ts/domainobjs/ballot.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { genRandomSalt, hash5, hashLeftRight, IncrementalQuinTree } from "../crypto"; - -import assert from "assert"; - -import type { IJsonBallot } from "./types"; - -/** - * A Ballot represents a User's votes in a Poll, as well as their next valid - * nonce. - */ -export class Ballot { - votes: bigint[] = []; - - nonce = BigInt(0); - - voteOptionTreeDepth: number; - - /** - * Create a new Ballot instance - * @param _numVoteOptions How many vote options are available in the poll - * @param _voteOptionTreeDepth The depth of the merkle tree holding the vote options - */ - constructor(_numVoteOptions: number, _voteOptionTreeDepth: number) { - this.voteOptionTreeDepth = _voteOptionTreeDepth; - assert(5 ** _voteOptionTreeDepth >= _numVoteOptions); - assert(_numVoteOptions >= 0); - for (let i = 0; i < _numVoteOptions; i += 1) { - this.votes.push(BigInt(0)); - } - } - - /** - * Generate an hash of this ballot - * @returns The hash of the ballot - */ - hash = (): bigint => { - const vals = this.asArray(); - return hashLeftRight(vals[0], vals[1]); - }; - - /** - * Convert in a format suitable for the circuit - * @returns the ballot as a BigInt array - */ - asCircuitInputs = (): bigint[] => this.asArray(); - - /** - * Convert in a an array of bigints - * @notice this is the nonce and the root of the vote option tree - * @returns the ballot as a bigint array - */ - asArray = (): bigint[] => { - const lastIndex = this.votes.length - 1; - const foundIndex = this.votes.findIndex((_, index) => this.votes[lastIndex - index] !== BigInt(0)); - const lastIndexToInsert = foundIndex < 0 ? -1 : lastIndex - foundIndex; - const voTree = new IncrementalQuinTree(this.voteOptionTreeDepth, BigInt(0), 5, hash5); - for (let i = 0; i <= lastIndexToInsert; i += 1) { - voTree.insert(this.votes[i]); - } - - return [this.nonce, voTree.root]; - }; - - /** - * Create a deep clone of this Ballot - * @returns a copy of the ballot - */ - 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; - }; - - /** - * Check if two ballots are equal (same votes and same nonce) - * @param b - The ballot to compare with - * @returns whether the two ballots are equal - */ - equals(b: Ballot): boolean { - const isEqualVotes = this.votes.every((vote, index) => vote === b.votes[index]); - return isEqualVotes ? b.nonce === this.nonce && this.votes.length === b.votes.length : false; - } - - /** - * Generate a random ballot - * @param numVoteOptions How many vote options are available - * @param voteOptionTreeDepth How deep is the merkle tree holding the vote options - * @returns a random Ballot - */ - static genRandomBallot(numVoteOptions: number, voteOptionTreeDepth: number): Ballot { - const ballot = new Ballot(numVoteOptions, voteOptionTreeDepth); - ballot.nonce = genRandomSalt(); - return ballot; - } - - /** - * Generate a blank ballot - * @param numVoteOptions How many vote options are available - * @param voteOptionTreeDepth How deep is the merkle tree holding the vote options - * @returns a Blank Ballot object - */ - static genBlankBallot(numVoteOptions: number, voteOptionTreeDepth: number): Ballot { - const ballot = new Ballot(numVoteOptions, voteOptionTreeDepth); - return ballot; - } - - /** - * Serialize to a JSON object - */ - toJSON(): IJsonBallot { - return { - votes: this.votes.map(x => x.toString()), - nonce: this.nonce.toString(), - voteOptionTreeDepth: this.voteOptionTreeDepth, - }; - } - - /** - * Deserialize into a Ballot instance - * @param json - the json representation - * @returns the deserialized object as a Ballot instance - */ - static fromJSON(json: IJsonBallot): Ballot { - const ballot = new Ballot(json.votes.length, Number.parseInt(json.voteOptionTreeDepth.toString(), 10)); - ballot.votes = json.votes.map(x => BigInt(x)); - ballot.nonce = BigInt(json.nonce); - return ballot; - } -} diff --git a/packages/hardhat/maci-ts/domainobjs/commands/PCommand.ts b/packages/hardhat/maci-ts/domainobjs/commands/PCommand.ts deleted file mode 100644 index b9c7dd6..0000000 --- a/packages/hardhat/maci-ts/domainobjs/commands/PCommand.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { - poseidonDecrypt, - poseidonEncrypt, - genRandomSalt, - hash4, - sign, - verifySignature, - type Signature, - type Ciphertext, - type EcdhSharedKey, - type Point, - poseidonDecryptWithoutCheck, -} from "../../crypto"; - -import assert from "assert"; - -import type { ICommand, IJsonPCommand } from "./types"; -import type { PrivKey } from "../privateKey"; - -import { Message } from "../message"; -import { PubKey } from "../publicKey"; - -export interface IDecryptMessage { - command: PCommand; - signature: Signature; -} - -/** - * @notice Unencrypted data whose fields include the user's public key, vote etc. - * This represents a Vote command. - */ -export class PCommand implements ICommand { - cmdType: bigint; - - stateIndex: bigint; - - newPubKey: PubKey; - - voteOptionIndex: bigint; - - newVoteWeight: bigint; - - nonce: bigint; - - pollId: bigint; - - salt: bigint; - - /** - * Create a new PCommand - * @param stateIndex the state index of the user - * @param newPubKey the new public key of the user - * @param voteOptionIndex the index of the vote option - * @param newVoteWeight the new vote weight of the user - * @param nonce the nonce of the message - * @param pollId the poll ID - * @param salt the salt of the message - */ - constructor( - stateIndex: bigint, - newPubKey: PubKey, - voteOptionIndex: bigint, - newVoteWeight: bigint, - nonce: bigint, - pollId: bigint, - salt: bigint = genRandomSalt(), - ) { - this.cmdType = BigInt(1); - - const limit50Bits = BigInt(2 ** 50); - assert(limit50Bits >= stateIndex); - assert(limit50Bits >= voteOptionIndex); - assert(limit50Bits >= newVoteWeight); - assert(limit50Bits >= nonce); - assert(limit50Bits >= pollId); - - this.stateIndex = stateIndex; - this.newPubKey = newPubKey; - this.voteOptionIndex = voteOptionIndex; - this.newVoteWeight = newVoteWeight; - this.nonce = nonce; - this.pollId = pollId; - this.salt = salt; - } - - /** - * Create a deep clone of this PCommand - * @returns a copy of the PCommand - */ - copy = (): T => - 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()), - ) as unknown as T; - - /** - * @notice 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. - * @returns bigint[] - the command as an array - */ - asArray = (): bigint[] => { - /* eslint-disable no-bitwise */ - const params = - BigInt(this.stateIndex) + - (BigInt(this.voteOptionIndex) << BigInt(50)) + - (BigInt(this.newVoteWeight) << BigInt(100)) + - (BigInt(this.nonce) << BigInt(150)) + - (BigInt(this.pollId) << BigInt(200)); - /* eslint-enable no-bitwise */ - - const command = [params, ...this.newPubKey.asArray(), this.salt]; - assert(command.length === 4); - - return command; - }; - - asCircuitInputs = (): bigint[] => this.asArray(); - - /* - * Check whether this command has deep equivalence to another command - */ - equals = (command: PCommand): boolean => - this.stateIndex === command.stateIndex && - this.newPubKey.equals(command.newPubKey) && - this.voteOptionIndex === command.voteOptionIndex && - this.newVoteWeight === command.newVoteWeight && - this.nonce === command.nonce && - this.pollId === command.pollId && - this.salt === command.salt; - - hash = (): bigint => hash4(this.asArray()); - - /** - * @notice Signs this command and returns a Signature. - */ - sign = (privKey: PrivKey): Signature => sign(privKey.rawPrivKey.toString(), this.hash()); - - /** - * @notice 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. - */ - verifySignature = (signature: Signature, pubKey: PubKey): boolean => - verifySignature(this.hash(), signature, pubKey.rawPubKey); - - /** - * @notice 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 - */ - encrypt = (signature: Signature, sharedKey: EcdhSharedKey): Message => { - const plaintext = [...this.asArray(), BigInt(signature.R8[0]), BigInt(signature.R8[1]), BigInt(signature.S)]; - - assert(plaintext.length === 7); - - const ciphertext: Ciphertext = poseidonEncrypt(plaintext, sharedKey, BigInt(0)); - - const message = new Message( - BigInt(1), - ciphertext as [bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint], - ); - - return message; - }; - - /** - * Decrypts a Message to produce a Command. - * @dev You can force decrypt the message by setting `force` to true. - * This is useful in case you don't want an invalid message to throw an error. - * @param {Message} message - the message to decrypt - * @param {EcdhSharedKey} sharedKey - the shared key to use for decryption - * @param {boolean} force - whether to force decryption or not - */ - static decrypt = (message: Message, sharedKey: EcdhSharedKey, force = false): IDecryptMessage => { - const decrypted = force - ? poseidonDecryptWithoutCheck(message.data as unknown as bigint[], sharedKey, BigInt(0), 7) - : poseidonDecrypt(message.data as unknown as bigint[], sharedKey, BigInt(0), 7); - - const p = BigInt(decrypted[0].toString()); - - // 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 => - // eslint-disable-next-line no-bitwise - BigInt((((BigInt(1) << BigInt(50)) - BigInt(1)) << BigInt(pos)) & 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]] as Point, - S: decrypted[6], - }; - - return { command, signature }; - }; - - /** - * Serialize into a JSON object - */ - toJSON(): IJsonPCommand { - return { - stateIndex: this.stateIndex.toString(), - newPubKey: this.newPubKey.serialize(), - voteOptionIndex: this.voteOptionIndex.toString(), - newVoteWeight: this.newVoteWeight.toString(), - nonce: this.nonce.toString(), - pollId: this.pollId.toString(), - salt: this.salt.toString(), - cmdType: this.cmdType.toString(), - }; - } - - /** - * Deserialize into a PCommand instance - * @param json - * @returns a PComamnd instance - */ - static fromJSON(json: IJsonPCommand): PCommand { - const command = new PCommand( - BigInt(json.stateIndex), - PubKey.deserialize(json.newPubKey), - BigInt(json.voteOptionIndex), - BigInt(json.newVoteWeight), - BigInt(json.nonce), - BigInt(json.pollId), - BigInt(json.salt), - ); - - return command; - } -} diff --git a/packages/hardhat/maci-ts/domainobjs/commands/TCommand.ts b/packages/hardhat/maci-ts/domainobjs/commands/TCommand.ts deleted file mode 100644 index 86f3405..0000000 --- a/packages/hardhat/maci-ts/domainobjs/commands/TCommand.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { ICommand, IJsonTCommand } from "./types"; - -/** - * @notice Command for submitting a topup request - */ -export class TCommand implements ICommand { - cmdType: bigint; - - stateIndex: bigint; - - amount: bigint; - - pollId: bigint; - - /** - * Create a new TCommand - * @param stateIndex the state index of the user - * @param amount the amount of voice credits - * @param pollId the poll ID - */ - constructor(stateIndex: bigint, amount: bigint, pollId: bigint) { - this.cmdType = BigInt(2); - this.stateIndex = stateIndex; - this.amount = amount; - this.pollId = pollId; - } - - /** - * Create a deep clone of this TCommand - * @returns a copy of the TCommand - */ - copy = (): T => new TCommand(this.stateIndex, this.amount, this.pollId) as T; - - /** - * Check whether this command has deep equivalence to another command - * @param command the command to compare with - * @returns whether they are equal or not - */ - equals = (command: TCommand): boolean => - this.stateIndex === command.stateIndex && - this.amount === command.amount && - this.pollId === command.pollId && - this.cmdType === command.cmdType; - - /** - * Serialize into a JSON object - */ - toJSON(): IJsonTCommand { - return { - stateIndex: this.stateIndex.toString(), - amount: this.amount.toString(), - cmdType: this.cmdType.toString(), - pollId: this.pollId.toString(), - }; - } - - /** - * Deserialize into a TCommand object - * @param json - the json representation - * @returns the TCommand instance - */ - static fromJSON(json: IJsonTCommand): TCommand { - return new TCommand(BigInt(json.stateIndex), BigInt(json.amount), BigInt(json.pollId)); - } -} diff --git a/packages/hardhat/maci-ts/domainobjs/commands/index.ts b/packages/hardhat/maci-ts/domainobjs/commands/index.ts deleted file mode 100644 index 24323c4..0000000 --- a/packages/hardhat/maci-ts/domainobjs/commands/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { TCommand } from "./TCommand"; -export { PCommand } from "./PCommand"; -export type { ICommand, IJsonCommand, IJsonTCommand, IJsonPCommand } from "./types"; diff --git a/packages/hardhat/maci-ts/domainobjs/commands/types.ts b/packages/hardhat/maci-ts/domainobjs/commands/types.ts deleted file mode 100644 index 03a96de..0000000 --- a/packages/hardhat/maci-ts/domainobjs/commands/types.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @notice A parent interface for all the commands - */ -export interface ICommand { - cmdType: bigint; - copy: () => T; - equals: (command: T) => boolean; - toJSON: () => unknown; -} - -/** - * @notice An interface representing a generic json command - */ -export interface IJsonCommand { - cmdType: string; -} - -/** - * @notice An interface representing a json T command - */ -export interface IJsonTCommand extends IJsonCommand { - stateIndex: string; - amount: string; - pollId: string; -} - -/** - * @notice An interface representing a json P command - */ -export interface IJsonPCommand extends IJsonCommand { - stateIndex: string; - newPubKey: string; - voteOptionIndex: string; - newVoteWeight: string; - nonce: string; - pollId: string; - salt: string; -} diff --git a/packages/hardhat/maci-ts/domainobjs/constants.ts b/packages/hardhat/maci-ts/domainobjs/constants.ts deleted file mode 100644 index aae4709..0000000 --- a/packages/hardhat/maci-ts/domainobjs/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { StateLeaf } from "./stateLeaf"; - -export const blankStateLeaf = StateLeaf.genBlankLeaf(); -export const blankStateLeafHash = blankStateLeaf.hash(); diff --git a/packages/hardhat/maci-ts/domainobjs/index.ts b/packages/hardhat/maci-ts/domainobjs/index.ts deleted file mode 100644 index 8d4a5ff..0000000 --- a/packages/hardhat/maci-ts/domainobjs/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -export { Ballot } from "./ballot"; - -export { Message } from "./message"; - -export { PrivKey, SERIALIZED_PRIV_KEY_PREFIX } from "./privateKey"; - -export { PubKey, SERIALIZED_PUB_KEY_PREFIX } from "./publicKey"; - -export { Keypair } from "./keyPair"; - -export { StateLeaf } from "./stateLeaf"; - -export { blankStateLeaf, blankStateLeafHash } from "./constants"; - -export type { - Proof, - IStateLeaf, - VoteOptionTreeLeaf, - IJsonKeyPair, - IJsonPrivateKey, - IJsonPublicKey, - IJsonStateLeaf, - IG1ContractParams, - IG2ContractParams, - IVkContractParams, - IVkObjectParams, - IStateLeafContractParams, - IMessageContractParams, - IJsonBallot, -} from "./types"; - -export { - type ICommand, - type IJsonCommand, - type IJsonTCommand, - type IJsonPCommand, - TCommand, - PCommand, -} from "./commands"; - -export { VerifyingKey } from "./verifyingKey"; diff --git a/packages/hardhat/maci-ts/domainobjs/keyPair.ts b/packages/hardhat/maci-ts/domainobjs/keyPair.ts deleted file mode 100644 index 137e07f..0000000 --- a/packages/hardhat/maci-ts/domainobjs/keyPair.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { EcdhSharedKey, genEcdhSharedKey, genKeypair, genPubKey } from "../crypto"; - -import assert from "assert"; - -import type { IJsonKeyPair } from "./types"; - -import { PrivKey } from "./privateKey"; -import { PubKey } from "./publicKey"; - -/** - * @notice A KeyPair is a pair of public and private keys - * This is a MACI keypair, which is not to be - * confused with an Ethereum public and private keypair. - * A MACI keypair is comprised of a MACI public key and a MACI private key - */ -export class Keypair { - privKey: PrivKey; - - pubKey: PubKey; - - /** - * Create a new instance of a Keypair - * @param privKey the private key (optional) - * @notice if no privKey is passed, it will automatically generate a new private key - */ - 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); - } - } - - /** - * Create a deep clone of this Keypair - * @returns a copy of the Keypair - */ - copy = (): Keypair => new Keypair(this.privKey.copy()); - - /** - * Generate a shared key - * @param privKey - * @param pubKey - * @returns - */ - static genEcdhSharedKey(privKey: PrivKey, pubKey: PubKey): EcdhSharedKey { - return genEcdhSharedKey(privKey.rawPrivKey, pubKey.rawPubKey); - } - - /** - * Check whether two Keypairs are equal - * @param keypair the keypair to compare with - * @returns whether they are equal or not - */ - 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 - // eslint-disable-next-line no-bitwise - assert(!(+equalPrivKey ^ +equalPubKey)); - - return equalPrivKey; - } - - /** - * Serialize into a JSON object - */ - toJSON(): IJsonKeyPair { - return { - privKey: this.privKey.serialize(), - pubKey: this.pubKey.serialize(), - }; - } - - /** - * Deserialize into a Keypair instance - * @param json - * @returns a keypair instance - */ - static fromJSON(json: IJsonKeyPair): Keypair { - return new Keypair(PrivKey.deserialize(json.privKey)); - } -} diff --git a/packages/hardhat/maci-ts/domainobjs/message.ts b/packages/hardhat/maci-ts/domainobjs/message.ts deleted file mode 100644 index 2a4ecc4..0000000 --- a/packages/hardhat/maci-ts/domainobjs/message.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { hash13 } from "../crypto"; - -import assert from "assert"; - -import type { PubKey } from "./publicKey"; -import type { IMessageContractParams } from "./types"; - -/** - * @notice An encrypted command and signature. - */ -export class Message { - msgType: bigint; - - data: [bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint]; - - static DATA_LENGTH = 10; - - /** - * Create a new instance of a Message - * @param msgType the type of the message - * @param data the data of the message - */ - constructor(msgType: bigint, data: [bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint]) { - assert(data.length === Message.DATA_LENGTH); - this.msgType = msgType; - this.data = data; - } - - /** - * Return the message as an array of bigints - * @returns the message as an array of bigints - */ - private asArray = (): bigint[] => [this.msgType].concat(this.data); - - /** - * Return the message as a contract param - * @returns the message as a contract param - */ - asContractParam = (): IMessageContractParams => ({ - msgType: this.msgType, - data: this.data as [bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint], - }); - - /** - * Return the message as a circuit input - * @returns the message as a circuit input - */ - asCircuitInputs = (): bigint[] => this.asArray(); - - /** - * Hash the message data and a public key - * @param encPubKey the public key that is used to encrypt this message - * @returns the hash of the message data and the public key - */ - hash = (encPubKey: PubKey): bigint => hash13([...[this.msgType], ...this.data, ...encPubKey.rawPubKey]); - - /** - * Create a copy of the message - * @returns a copy of the message - */ - copy = (): Message => - new Message( - BigInt(this.msgType.toString()), - this.data.map((x: bigint) => BigInt(x.toString())) as [ - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - ], - ); - - /** - * Check if two messages are equal - * @param m the message to compare with - * @returns the result of the comparison - */ - equals = (m: Message): boolean => { - if (this.data.length !== m.data.length) { - return false; - } - if (this.msgType !== m.msgType) { - return false; - } - - return this.data.every((data, index) => data === m.data[index]); - }; - - /** - * Serialize to a JSON object - */ - toJSON(): IMessageContractParams { - return this.asContractParam(); - } - - /** - * Deserialize into a Message instance - * @param json - the json representation - * @returns the deserialized object as a Message instance - */ - static fromJSON(json: IMessageContractParams): Message { - return new Message( - BigInt(json.msgType), - json.data.map(x => BigInt(x)) as [bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint], - ); - } -} diff --git a/packages/hardhat/maci-ts/domainobjs/privateKey.ts b/packages/hardhat/maci-ts/domainobjs/privateKey.ts deleted file mode 100644 index 17be2e8..0000000 --- a/packages/hardhat/maci-ts/domainobjs/privateKey.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { formatPrivKeyForBabyJub, type PrivKey as RawPrivKey } from "../crypto"; - -import type { IJsonPrivateKey } from "./types"; - -export const SERIALIZED_PRIV_KEY_PREFIX = "macisk."; - -/** - * @notice PrivKey is a TS Class representing a MACI PrivateKey (on the jubjub curve) - * This is a MACI private key, which is not to be - * confused with an Ethereum private key. - * A serialized MACI private key is prefixed by 'macisk.' - * A raw MACI private key can be thought as a point on the baby jubjub curve - */ -export class PrivKey { - rawPrivKey: RawPrivKey; - - /** - * Generate a new Private key object - * @param rawPrivKey the raw private key (a bigint) - */ - constructor(rawPrivKey: RawPrivKey) { - this.rawPrivKey = rawPrivKey; - } - - /** - * Create a copy of this Private key - * @returns a copy of the Private key - */ - copy = (): PrivKey => new PrivKey(BigInt(this.rawPrivKey.toString())); - - /** - * Return this Private key as a circuit input - * @returns the Private key as a circuit input - */ - asCircuitInputs = (): string => formatPrivKeyForBabyJub(this.rawPrivKey).toString(); - - /** - * Serialize the private key - * @returns the serialized private key - */ - serialize = (): string => { - let x = this.rawPrivKey.toString(16); - if (x.length % 2 !== 0) { - x = `0${x}`; - } - - return `${SERIALIZED_PRIV_KEY_PREFIX}${x.padStart(64, "0")}`; - }; - - /** - * Deserialize the private key - * @param s the serialized private key - * @returns the deserialized private key - */ - static deserialize = (s: string): PrivKey => { - const x = s.slice(SERIALIZED_PRIV_KEY_PREFIX.length); - return new PrivKey(BigInt(`0x${x}`)); - }; - - /** - * Check if the serialized private key is valid - * @param s the serialized private key - * @returns whether it is a valid serialized private key - */ - static isValidSerializedPrivKey = (s: string): boolean => { - const correctPrefix = s.startsWith(SERIALIZED_PRIV_KEY_PREFIX); - const x = s.slice(SERIALIZED_PRIV_KEY_PREFIX.length); - - return correctPrefix && x.length === 64; - }; - - /** - * Serialize this object - */ - toJSON(): IJsonPrivateKey { - return { - privKey: this.serialize(), - }; - } - - /** - * Deserialize this object from a JSON object - * @param json - the json object - * @returns the deserialized object as a PrivKey instance - */ - static fromJSON(json: IJsonPrivateKey): PrivKey { - return PrivKey.deserialize(json.privKey); - } -} diff --git a/packages/hardhat/maci-ts/domainobjs/publicKey.ts b/packages/hardhat/maci-ts/domainobjs/publicKey.ts deleted file mode 100644 index 1ad2cc8..0000000 --- a/packages/hardhat/maci-ts/domainobjs/publicKey.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { SNARK_FIELD_SIZE, hashLeftRight, packPubKey, unpackPubKey, type PubKey as RawPubKey } from "../crypto"; - -import assert from "assert"; - -import type { IJsonPublicKey, IG1ContractParams } from "./types"; - -export const SERIALIZED_PUB_KEY_PREFIX = "macipk."; - -/** - * @notice A class representing a public key - * This is a MACI public key, which is not to be - * confused with an Ethereum public key. - * A serialized MACI public key is prefixed by 'macipk.' - * A raw MACI public key can be thought as a pair of - * BigIntegers (x, y) representing a point on the baby jubjub curve - */ -export class PubKey { - rawPubKey: RawPubKey; - - /** - * Create a new instance of a public key - * @param rawPubKey the raw public key - */ - constructor(rawPubKey: RawPubKey) { - assert(rawPubKey[0] < SNARK_FIELD_SIZE); - assert(rawPubKey[1] < SNARK_FIELD_SIZE); - this.rawPubKey = rawPubKey; - } - - /** - * Create a copy of the public key - * @returns a copy of the public key - */ - copy = (): PubKey => new PubKey([BigInt(this.rawPubKey[0].toString()), BigInt(this.rawPubKey[1].toString())]); - - /** - * Return this public key as smart contract parameters - * @returns the public key as smart contract parameters - */ - asContractParam = (): IG1ContractParams => { - const [x, y] = this.rawPubKey; - - return { - x, - y, - }; - }; - - /** - * Return this public key as circuit inputs - * @returns an array of strings - */ - asCircuitInputs = (): string[] => this.rawPubKey.map(x => x.toString()); - - /** - * Return this public key as an array of bigints - * @returns the public key as an array of bigints - */ - asArray = (): bigint[] => [this.rawPubKey[0], this.rawPubKey[1]]; - - /** - * Generate a serialized public key from this public key object - * @returns the string representation of a serialized public key - */ - serialize = (): string => { - const { x, y } = this.asContractParam(); - // Blank leaves have pubkey [0, 0], which packPubKey does not support - if (BigInt(x) === BigInt(0) && BigInt(y) === BigInt(0)) { - return `${SERIALIZED_PUB_KEY_PREFIX}z`; - } - - const packed = packPubKey(this.rawPubKey).toString(16); - - if (packed.length % 2 !== 0) { - return `${SERIALIZED_PUB_KEY_PREFIX}0${packed}`; - } - - return `${SERIALIZED_PUB_KEY_PREFIX}${packed}`; - }; - - /** - * Hash the two baby jubjub coordinates - * @returns the hash of this public key - */ - hash = (): bigint => hashLeftRight(this.rawPubKey[0], this.rawPubKey[1]); - - /** - * Check whether this public key equals to another public key - * @param p the public key to compare with - * @returns whether they match - */ - equals = (p: PubKey): boolean => this.rawPubKey[0] === p.rawPubKey[0] && this.rawPubKey[1] === p.rawPubKey[1]; - - /** - * Deserialize a serialized public key - * @param s the serialized public key - * @returns the deserialized public key - */ - static deserialize = (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; - return new PubKey(unpackPubKey(BigInt(`0x${s.slice(len).toString()}`))); - }; - - /** - * Check whether a serialized public key is serialized correctly - * @param s the serialized public key - * @returns whether the serialized public key is valid - */ - static isValidSerializedPubKey = (s: string): boolean => { - const correctPrefix = s.startsWith(SERIALIZED_PUB_KEY_PREFIX); - - try { - PubKey.deserialize(s); - return correctPrefix; - } catch { - return false; - } - }; - - /** - * Serialize this object - */ - toJSON(): IJsonPublicKey { - return { - pubKey: this.serialize(), - }; - } - - /** - * Deserialize a JSON object into a PubKey instance - * @param json - the json object - * @returns PubKey - */ - static fromJSON(json: IJsonPublicKey): PubKey { - return PubKey.deserialize(json.pubKey); - } -} diff --git a/packages/hardhat/maci-ts/domainobjs/stateLeaf.ts b/packages/hardhat/maci-ts/domainobjs/stateLeaf.ts deleted file mode 100644 index c6a4ad4..0000000 --- a/packages/hardhat/maci-ts/domainobjs/stateLeaf.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { genRandomSalt, hash4 } from "../crypto"; - -import type { IJsonStateLeaf, IStateLeaf, IStateLeafContractParams } from "./types"; - -import { Keypair } from "./keyPair"; -import { PubKey } from "./publicKey"; - -/** - * @notice A leaf in the state tree, which maps - * public keys to voice credit balances - */ -export class StateLeaf implements IStateLeaf { - pubKey: PubKey; - - voiceCreditBalance: bigint; - - timestamp: bigint; - - /** - * Create a new instance of a state leaf - * @param pubKey the public key of the user signin up - * @param voiceCreditBalance the voice credit balance of the user - * @param timestamp the timestamp of when the user signed-up - */ - constructor(pubKey: PubKey, voiceCreditBalance: bigint, timestamp: bigint) { - this.pubKey = pubKey; - this.voiceCreditBalance = voiceCreditBalance; - this.timestamp = timestamp; - } - - /** - * Crate a deep copy of the object - * @returns a copy of the state leaf - */ - copy(): StateLeaf { - return new StateLeaf( - this.pubKey.copy(), - BigInt(this.voiceCreditBalance.toString()), - BigInt(this.timestamp.toString()), - ); - } - - /** - * Generate a blank state leaf - * @returns a blank state leaf - */ - 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), - ); - } - - /** - * Generate a random leaf (random salt and random key pair) - * @returns a random state leaf - */ - static genRandomLeaf(): StateLeaf { - const keypair = new Keypair(); - return new StateLeaf(keypair.pubKey, genRandomSalt(), BigInt(0)); - } - - /** - * Return this state leaf as an array of bigints - * @returns the state leaf as an array of bigints - */ - private asArray = (): bigint[] => [...this.pubKey.asArray(), this.voiceCreditBalance, this.timestamp]; - - /** - * Return this state leaf as an array of bigints - * @returns the state leaf as an array of bigints - */ - asCircuitInputs = (): bigint[] => this.asArray(); - - /** - * Hash this state leaf (first convert as array) - * @returns the has of the state leaf elements - */ - hash = (): bigint => hash4(this.asArray()); - - /** - * Return this state leaf as a contract param - * @returns the state leaf as a contract param (object) - */ - asContractParam(): IStateLeafContractParams { - return { - pubKey: this.pubKey.asContractParam(), - voiceCreditBalance: this.voiceCreditBalance, - timestamp: this.timestamp, - }; - } - - /** - * Check if two state leaves are equal - * @param s the state leaf to compare with - * @returns whether they are equal or not - */ - equals(s: StateLeaf): boolean { - return ( - this.pubKey.equals(s.pubKey) && this.voiceCreditBalance === s.voiceCreditBalance && this.timestamp === s.timestamp - ); - } - - /** - * Serialize the state leaf - * @notice serialize the public key - * @notice convert the voice credit balance and timestamp to a hex string - * @returns - */ - serialize = (): string => { - const j = [this.pubKey.serialize(), this.voiceCreditBalance.toString(16), this.timestamp.toString(16)]; - - return Buffer.from(JSON.stringify(j, null, 0), "utf8").toString("base64url"); - }; - - /** - * Deserialize the state leaf - * @param serialized the serialized state leaf - * @returns a deserialized state leaf - */ - static deserialize = (serialized: string): StateLeaf => { - const base64 = serialized.replace(/-/g, "+").replace(/_/g, "/"); - const json = JSON.parse(Buffer.from(base64, "base64").toString("utf8")) as [string, string, string]; - - return new StateLeaf(PubKey.deserialize(json[0]), BigInt(`0x${json[1]}`), BigInt(`0x${json[2]}`)); - }; - - /** - * Serialize to a JSON object - */ - toJSON(): IJsonStateLeaf { - return { - pubKey: this.pubKey.serialize(), - voiceCreditBalance: this.voiceCreditBalance.toString(), - timestamp: this.timestamp.toString(), - }; - } - - /** - * Deserialize into a StateLeaf instance - * @param json - the json representation - * @returns the deserialized object as a StateLeaf instance - */ - static fromJSON(json: IJsonStateLeaf): StateLeaf { - return new StateLeaf(PubKey.deserialize(json.pubKey), BigInt(json.voiceCreditBalance), BigInt(json.timestamp)); - } -} diff --git a/packages/hardhat/maci-ts/domainobjs/types.ts b/packages/hardhat/maci-ts/domainobjs/types.ts deleted file mode 100644 index c12b2d9..0000000 --- a/packages/hardhat/maci-ts/domainobjs/types.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { PubKey } from "./publicKey"; -import type { G1Point, G2Point } from "../crypto"; - -/** - * @notice An interface representing a zk-SNARK proof - */ -export interface Proof { - a: G1Point; - b: G2Point; - c: G1Point; -} - -/** - * @notice An interface representing a MACI state leaf - */ -export interface IStateLeaf { - pubKey: PubKey; - voiceCreditBalance: bigint; -} - -/** - * @notice An interface representing a MACI vote option leaf - */ -export interface VoteOptionTreeLeaf { - votes: bigint; -} - -export interface IJsonKeyPair { - privKey: string; - pubKey: string; -} - -export type IJsonPrivateKey = Pick; - -export type IJsonPublicKey = Pick; - -export interface IJsonStateLeaf { - pubKey: string; - voiceCreditBalance: string; - timestamp: string; -} - -export type BigNumberish = bigint; - -export interface IG1ContractParams { - x: BigNumberish; - y: BigNumberish; -} - -export interface IG2ContractParams { - x: BigNumberish[]; - y: BigNumberish[]; -} - -export interface IVkContractParams { - alpha1: IG1ContractParams; - beta2: IG2ContractParams; - gamma2: IG2ContractParams; - delta2: IG2ContractParams; - ic: IG1ContractParams[]; -} - -export interface IVkObjectParams { - protocol: BigNumberish; - curve: BigNumberish; - nPublic: BigNumberish; - vk_alpha_1: BigNumberish[]; - vk_beta_2: BigNumberish[][]; - vk_gamma_2: BigNumberish[][]; - vk_delta_2: BigNumberish[][]; - vk_alphabeta_12: BigNumberish[][][]; - IC: BigNumberish[][]; -} - -export interface IStateLeafContractParams { - pubKey: IG1ContractParams; - voiceCreditBalance: BigNumberish; - timestamp: BigNumberish; -} - -export interface IMessageContractParams { - msgType: BigNumberish; - data: [bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint]; -} - -export interface IJsonBallot { - votes: string[]; - nonce: string; - voteOptionTreeDepth: number; -} diff --git a/packages/hardhat/maci-ts/domainobjs/verifyingKey.ts b/packages/hardhat/maci-ts/domainobjs/verifyingKey.ts deleted file mode 100644 index 7b6869d..0000000 --- a/packages/hardhat/maci-ts/domainobjs/verifyingKey.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { G1Point, G2Point } from "../crypto"; - -import type { IVkContractParams, IVkObjectParams } from "./types"; - -/** - * @notice A TS Class representing a zk-SNARK VerifyingKey - */ -export class VerifyingKey { - alpha1: G1Point; - - beta2: G2Point; - - gamma2: G2Point; - - delta2: G2Point; - - ic: G1Point[]; - - /** - * Generate a new VerifyingKey - * @param alpha1 the alpha1 point - * @param beta2 the beta2 point - * @param gamma2 the gamma2 point - * @param delta2 the delta2 point - * @param ic the ic points - */ - 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; - } - - /** - * Return this as an object which can be passed - * to the smart contract - * @returns the object representation of this - */ - asContractParam(): IVkContractParams { - return { - alpha1: this.alpha1.asContractParam(), - beta2: this.beta2.asContractParam(), - gamma2: this.gamma2.asContractParam(), - delta2: this.delta2.asContractParam(), - ic: this.ic.map(x => x.asContractParam()), - }; - } - - /** - * Create a new verifying key from a contract representation of the VK - * @param data the object representation - * @returns a new VerifyingKey - */ - static fromContract(data: IVkContractParams): VerifyingKey { - const convertG2 = (point: IVkContractParams["beta2"]): G2Point => - 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 => new G1Point(BigInt(c.x), BigInt(c.y))), - ); - } - - /** - * Check whether this is equal to another verifying key - * @param vk the other verifying key - * @returns whether this is equal to the other verifying key - */ - equals(vk: VerifyingKey): boolean { - // Immediately return false if the length doesn't match - if (this.ic.length !== vk.ic.length) { - return false; - } - - const icEqual = this.ic.every((ic, index) => ic.equals(vk.ic[index])); - - return ( - this.alpha1.equals(vk.alpha1) && - this.beta2.equals(vk.beta2) && - this.gamma2.equals(vk.gamma2) && - this.delta2.equals(vk.delta2) && - icEqual - ); - } - - /** - * Produce a copy of this verifying key - * @returns the copy - */ - copy(): VerifyingKey { - const copyG2 = (point: G2Point): G2Point => - 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: G1Point) => new G1Point(BigInt(c.x.toString()), BigInt(c.y.toString()))), - ); - } - - /** - * Deserialize into a VerifyingKey instance - * @param json the JSON representation - * @returns the VerifyingKey - */ - static fromJSON = (json: string): VerifyingKey => { - const data = JSON.parse(json) as IVkObjectParams; - return VerifyingKey.fromObj(data); - }; - - /** - * Convert an object representation to a VerifyingKey - * @param data the object representation - * @returns the VerifyingKey - */ - static fromObj = (data: IVkObjectParams): 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(([x, y]) => new G1Point(BigInt(x), BigInt(y))); - - return new VerifyingKey(alpha1, beta2, gamma2, delta2, ic); - }; -} diff --git a/packages/hardhat/maci-ts/templates/EmptyBallotRoots.sol.template b/packages/hardhat/maci-ts/templates/EmptyBallotRoots.sol.template deleted file mode 100644 index f0ce1bf..0000000 --- a/packages/hardhat/maci-ts/templates/EmptyBallotRoots.sol.template +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -abstract contract EmptyBallotRoots { - // emptyBallotRoots contains the roots of Ballot trees of five leaf - // configurations. - // Each tree has a depth of 10, which is the hardcoded state tree depth. - // Each leaf is an empty ballot. A configuration refers to the depth of the - // voice option tree for that ballot. - - // The leaf for the root at index 0 contains hash(0, root of a VO tree with - // depth 1 and zero-value 0) - - // The leaf for the root at index 1 contains hash(0, root of a VO tree with - // depth 2 and zero-value 0) - - // ... and so on. - - // The first parameter to the hash function is the nonce, which is 0. - - uint256[5] internal emptyBallotRoots; - - constructor() { -<% ROOTS %> - } -} diff --git a/packages/hardhat/maci-ts/templates/MerkleZeros.sol.template b/packages/hardhat/maci-ts/templates/MerkleZeros.sol.template deleted file mode 100644 index 6e7d011..0000000 --- a/packages/hardhat/maci-ts/templates/MerkleZeros.sol.template +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -abstract contract MerkleZeros { - uint256[<% NUM_ZEROS %>] internal zeros; - - // <% COMMENT %> - constructor() { -<% ZEROS %> - } -} diff --git a/packages/hardhat/maci-ts/ts/abi.ts b/packages/hardhat/maci-ts/ts/abi.ts deleted file mode 100644 index f3ec2e7..0000000 --- a/packages/hardhat/maci-ts/ts/abi.ts +++ /dev/null @@ -1,52 +0,0 @@ -import fs from "fs"; -import path from "path"; - -import type { Fragment, JsonFragment } from "ethers"; - -import { abiDir } from "./constants"; - -// a type representing the ABI of a contract -export type TAbi = string | readonly (string | Fragment | JsonFragment)[]; - -/** - * Parse a contract artifact and return its ABI and bytecode. - * @param filename - the name of the contract - * @returns the ABI and bytecode of the contract - */ -export const parseArtifact = (filename: string): [TAbi, string] => { - let filePath = "contracts/maci-contracts/"; - if (filename.includes("Gatekeeper")) { - filePath += "gatekeepers/"; - filePath += `${filename}.sol`; - } - - if (filename.includes("VoiceCredit")) { - filePath += "initialVoiceCreditProxy/"; - filePath += `${filename}.sol`; - } - - if (filename.includes("Verifier")) { - filePath += "crypto/Verifier.sol/"; - } - - if (filename.includes("AccQueue")) { - filePath += `trees/${filename}.sol/`; - } - - if (filename.includes("Poll") || filename.includes("MessageAq")) { - filePath += "Poll.sol"; - } - - if (!filePath.includes(".sol")) { - filePath += `${filename}.sol`; - } - - const contractArtifact = JSON.parse( - fs.readFileSync(path.resolve(abiDir, filePath, `${filename}.json`)).toString(), - ) as { - abi: TAbi; - bytecode: string; - }; - - return [contractArtifact.abi, contractArtifact.bytecode]; -}; diff --git a/packages/hardhat/maci-ts/ts/buildPoseidon.ts b/packages/hardhat/maci-ts/ts/buildPoseidon.ts deleted file mode 100644 index 7b71d12..0000000 --- a/packages/hardhat/maci-ts/ts/buildPoseidon.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { poseidonContract } from "circomlibjs"; -import hre from "hardhat"; - -type ExtendedHre = typeof hre & { overwriteArtifact: (name: string, code: unknown) => Promise }; - -const buildPoseidon = async (numInputs: number) => { - await (hre as ExtendedHre).overwriteArtifact(`PoseidonT${numInputs + 1}`, poseidonContract.createCode(numInputs)); -}; - -export const buildPoseidonT3 = (): Promise => buildPoseidon(2); -export const buildPoseidonT4 = (): Promise => buildPoseidon(3); -export const buildPoseidonT5 = (): Promise => buildPoseidon(4); -export const buildPoseidonT6 = (): Promise => buildPoseidon(5); - -if (require.main === module) { - buildPoseidonT3(); - buildPoseidonT4(); - buildPoseidonT5(); - buildPoseidonT6(); -} diff --git a/packages/hardhat/maci-ts/ts/constants.ts b/packages/hardhat/maci-ts/ts/constants.ts deleted file mode 100644 index 913ee5c..0000000 --- a/packages/hardhat/maci-ts/ts/constants.ts +++ /dev/null @@ -1,14 +0,0 @@ -import path from "path"; - -// The directory where the contract artifacts are stored. -export const abiDir = path.resolve(__dirname, "..", "..", "artifacts"); -// The directory where the contract source files are stored. -export const solDir = path.resolve(__dirname, "..", "contracts"); - -/** - * Supported verification key modes - */ -export enum EMode { - QV, - NON_QV, -} diff --git a/packages/hardhat/maci-ts/ts/deploy.ts b/packages/hardhat/maci-ts/ts/deploy.ts deleted file mode 100644 index df1462e..0000000 --- a/packages/hardhat/maci-ts/ts/deploy.ts +++ /dev/null @@ -1,357 +0,0 @@ -import { type ContractFactory, type Signer, BaseContract } from "ethers"; - -import type { IDeployMaciArgs, IDeployedMaci, IDeployedPoseidonContracts } from "./types"; - -import { - AccQueueQuinaryMaci, - ConstantInitialVoiceCreditProxy, - FreeForAllGatekeeper, - PoseidonT3__factory as PoseidonT3Factory, - PoseidonT4__factory as PoseidonT4Factory, - PoseidonT5__factory as PoseidonT5Factory, - PoseidonT6__factory as PoseidonT6Factory, - MACI, - MockVerifier, - PollFactory, - MessageProcessorFactory, - TallyFactory, - PoseidonT3, - PoseidonT4, - PoseidonT5, - PoseidonT6, - SignUpToken, - SignUpTokenGatekeeper, - TopupCredit, - Verifier, - VkRegistry, -} from "../../typechain-types"; - -import { parseArtifact } from "./abi"; -import { getDefaultSigner, getFeeData, log } from "./utils"; - -/** - * Link Poseidon libraries to a Smart Contract - * @param solFileToLink - the name of the contract to link the libraries to - * @param poseidonT3Address - the address of the PoseidonT3 contract - * @param poseidonT4Address - the address of the PoseidonT4 contract - * @param poseidonT5Address - the address of the PoseidonT5 contract - * @param poseidonT6Address - the address of the PoseidonT6 contract - * @param signer - the signer to use to deploy the contract - * @param quiet - whether to suppress console output - * @returns a contract factory with the libraries linked - */ -export const linkPoseidonLibraries = async ( - solFileToLink: string, - poseidonT3Address: string, - poseidonT4Address: string, - poseidonT5Address: string, - poseidonT6Address: string, - signer?: Signer, - quiet = false, -): Promise => { - log(`Linking Poseidon libraries to ${solFileToLink}`, quiet); - const { ethers } = await import("hardhat"); - - const contractFactory = await ethers.getContractFactory(solFileToLink, { - signer: signer || (await getDefaultSigner()), - libraries: { - PoseidonT3: poseidonT3Address, - PoseidonT4: poseidonT4Address, - PoseidonT5: poseidonT5Address, - PoseidonT6: poseidonT6Address, - }, - }); - - return contractFactory; -}; - -/** - * Deploy a Smart Contract given a name and some arguments - * @param contractName - the name of the contract - * @param signer - the signer to use to deploy the contract - * @param quiet - whether to suppress console output - * @param args - the constructor arguments of the contract - */ -export const deployContract = async ( - contractName: string, - signer?: Signer, - quiet = false, - ...args: unknown[] -): Promise => { - log(`Deploying ${contractName}`, quiet); - const { ethers } = await import("hardhat"); - - const contractFactory = await ethers.getContractFactory(contractName, signer || (await getDefaultSigner())); - const feeData = await getFeeData(); - const contract = await contractFactory.deploy(...args, { - maxFeePerGas: feeData?.maxFeePerGas, - maxPriorityFeePerGas: feeData?.maxPriorityFeePerGas, - }); - await contract.deploymentTransaction()!.wait(); - - return contract as unknown as T; -}; - -/** - * Deploy a TopupCredit contract - * @param signer - the signer to use to deploy the contract - * @param quiet - whether to suppress console output - * @returns the deployed TopupCredit contract - */ -export const deployTopupCredit = async (signer?: Signer, quiet = false): Promise => - deployContract("TopupCredit", signer, quiet); - -/** - * Deploy a VkRegistry contract - * @param signer - the signer to use to deploy the contract - * @param quiet - whether to suppress console output - * @returns the deployed VkRegistry contract - */ -export const deployVkRegistry = async (signer?: Signer, quiet = false): Promise => - deployContract("VkRegistry", signer, quiet); - -/** - * Deploy a MockVerifier contract (testing only) - * @param signer - the signer to use to deploy the contract - * @param quiet - whether to suppress console output - * @returns the deployed MockVerifier contract - */ -export const deployMockVerifier = async (signer?: Signer, quiet = false): Promise => - deployContract("MockVerifier", signer, quiet); - -/** - * Deploy a Verifier contract - * @param signer - the signer to use to deploy the contract - * @param quiet - whether to suppress console output - * @returns the deployed Verifier contract - */ -export const deployVerifier = async (signer?: Signer, quiet = false): Promise => - deployContract("Verifier", signer, quiet); - -/** - * Deploy a constant initial voice credit proxy contract - * @param signer - the signer to use to deploy the contract - * @param amount - the amount of initial voice credit to give to each user - * @param quiet - whether to suppress console output - * @returns the deployed ConstantInitialVoiceCreditProxy contract - */ -export const deployConstantInitialVoiceCreditProxy = async ( - amount: number, - signer?: Signer, - quiet = false, -): Promise => - deployContract("ConstantInitialVoiceCreditProxy", signer, quiet, amount.toString()); - -/** - * Deploy a SignUpToken contract - * @param signer - the signer to use to deploy the contract - * @param quiet - whether to suppress console output - * @returns the deployed SignUpToken contract - */ -export const deploySignupToken = async (signer?: Signer, quiet = false): Promise => - deployContract("SignUpToken", signer, quiet); - -/** - * Deploy a SignUpTokenGatekeeper contract - * @param signUpTokenAddress - the address of the SignUpToken contract - * @param signer - the signer to use to deploy the contract - * @param quiet - whether to suppress console output - * @returns a SignUpTokenGatekeeper contract - */ -export const deploySignupTokenGatekeeper = async ( - signUpTokenAddress: string, - signer?: Signer, - quiet = false, -): Promise => - deployContract("SignUpTokenGatekeeper", signer, quiet, signUpTokenAddress); - -/** - * Deploy a FreeForAllGatekeeper contract - * @param signer - the signer to use to deploy the contract - * @param quiet - whether to suppress console output - * @returns the deployed FreeForAllGatekeeper contract - */ -export const deployFreeForAllSignUpGatekeeper = async (signer?: Signer, quiet = false): Promise => - deployContract("FreeForAllGatekeeper", signer, quiet); - -/** - * Deploy Poseidon contracts - * @param signer - the signer to use to deploy the contracts - * @param quiet - whether to suppress console output - * @returns the deployed Poseidon contracts - */ -export const deployPoseidonContracts = async ( - signer?: Signer, - { poseidonT3, poseidonT4, poseidonT5, poseidonT6 }: IDeployMaciArgs["poseidonAddresses"] = {}, - quiet = false, -): Promise => { - const [PoseidonT3Contract, PoseidonT4Contract, PoseidonT5Contract, PoseidonT6Contract] = await Promise.all([ - !poseidonT3 ? await deployContract("PoseidonT3", signer, quiet) : PoseidonT3Factory.connect(poseidonT3), - !poseidonT4 ? await deployContract("PoseidonT4", signer, quiet) : PoseidonT4Factory.connect(poseidonT4), - !poseidonT5 ? await deployContract("PoseidonT5", signer, quiet) : PoseidonT5Factory.connect(poseidonT5), - !poseidonT6 ? await deployContract("PoseidonT6", signer, quiet) : PoseidonT6Factory.connect(poseidonT6), - ]); - - return { - PoseidonT3Contract, - PoseidonT4Contract, - PoseidonT5Contract, - PoseidonT6Contract, - }; -}; - -/** - * Deploy a contract with linked libraries - * @param contractFactory - the contract factory to use - * @param name - the name of the contract - * @param quiet - whether to suppress console output - * @param args - the constructor arguments of the contract - * @returns the deployed contract instance - */ -export const deployContractWithLinkedLibraries = async ( - contractFactory: ContractFactory, - name: string, - quiet = false, - ...args: unknown[] -): Promise => { - log(`Deploying ${name}`, quiet); - const feeData = await getFeeData(); - const contract = await contractFactory.deploy(...args, { - maxFeePerGas: feeData?.maxFeePerGas, - maxPriorityFeePerGas: feeData?.maxPriorityFeePerGas, - }); - await contract.deploymentTransaction()!.wait(); - - return contract as T; -}; - -/** - * Deploy a Poll Factory contract - * @param signer - the signer object to use to deploy the contract - * @param quiet - whether to suppress console output - * @returns the deployed Poll Factory contract - */ -export const deployPollFactory = async (signer: Signer, quiet = false): Promise => { - const poseidonContracts = await deployPoseidonContracts(signer, {}, quiet); - const [poseidonT3Contract, poseidonT4Contract, poseidonT5Contract, poseidonT6Contract] = await Promise.all([ - poseidonContracts.PoseidonT3Contract.getAddress(), - poseidonContracts.PoseidonT4Contract.getAddress(), - poseidonContracts.PoseidonT5Contract.getAddress(), - poseidonContracts.PoseidonT6Contract.getAddress(), - ]); - const contractFactory = await linkPoseidonLibraries( - "PollFactory", - poseidonT3Contract, - poseidonT4Contract, - poseidonT5Contract, - poseidonT6Contract, - signer, - quiet, - ); - return deployContractWithLinkedLibraries(contractFactory, "PollFactory", quiet); -}; - -/** - * Deploy a MACI contract - * @param {IDeployMaciArgs} args - deploy arguments - * @returns {IDeployedMaci} the deployed MACI contract - */ -export const deployMaci = async ({ - signUpTokenGatekeeperContractAddress, - initialVoiceCreditBalanceAddress, - topupCreditContractAddress, - signer, - poseidonAddresses, - stateTreeDepth = 10, - quiet = true, -}: IDeployMaciArgs): Promise => { - const { PoseidonT3Contract, PoseidonT4Contract, PoseidonT5Contract, PoseidonT6Contract } = - await deployPoseidonContracts(signer, poseidonAddresses, quiet); - - const poseidonAddrs = await Promise.all([ - PoseidonT3Contract.getAddress(), - PoseidonT4Contract.getAddress(), - PoseidonT5Contract.getAddress(), - PoseidonT6Contract.getAddress(), - ]).then(([poseidonT3, poseidonT4, poseidonT5, poseidonT6]) => ({ - poseidonT3, - poseidonT4, - poseidonT5, - poseidonT6, - })); - - const contractsToLink = ["MACI", "PollFactory", "MessageProcessorFactory", "TallyFactory"]; - - // Link Poseidon contracts to MACI - const linkedContractFactories = await Promise.all( - contractsToLink.map(async (contractName: string) => - linkPoseidonLibraries( - contractName, - poseidonAddrs.poseidonT3, - poseidonAddrs.poseidonT4, - poseidonAddrs.poseidonT5, - poseidonAddrs.poseidonT6, - signer, - quiet, - ), - ), - ); - - const [maciContractFactory, pollFactoryContractFactory, messageProcessorFactory, tallyFactory] = await Promise.all( - linkedContractFactories, - ); - - const pollFactoryContract = await deployContractWithLinkedLibraries( - pollFactoryContractFactory, - "PollFactory", - quiet, - ); - - const messageProcessorFactoryContract = await deployContractWithLinkedLibraries( - messageProcessorFactory, - "MessageProcessorFactory", - quiet, - ); - - // deploy either the qv or non qv tally factory - they both implement the same interface - // so as long as maci is concerned, they are interchangeable - const tallyFactoryContract = await deployContractWithLinkedLibraries( - tallyFactory, - "TallyFactory", - quiet, - ); - - const [pollAddr, mpAddr, tallyAddr] = await Promise.all([ - pollFactoryContract.getAddress(), - messageProcessorFactoryContract.getAddress(), - tallyFactoryContract.getAddress(), - ]); - - const maciContract = await deployContractWithLinkedLibraries( - maciContractFactory, - "MACI", - quiet, - pollAddr, - mpAddr, - tallyAddr, - signUpTokenGatekeeperContractAddress, - initialVoiceCreditBalanceAddress, - topupCreditContractAddress, - stateTreeDepth, - ); - - const [AccQueueQuinaryMaciAbi] = parseArtifact("AccQueue"); - const stateAqContractAddress = await maciContract.stateAq(); - const stateAqContract = new BaseContract( - stateAqContractAddress, - AccQueueQuinaryMaciAbi, - await getDefaultSigner(), - ) as AccQueueQuinaryMaci; - - return { - maciContract, - stateAqContract, - pollFactoryContract, - poseidonAddrs, - }; -}; diff --git a/packages/hardhat/maci-ts/ts/deployer.ts b/packages/hardhat/maci-ts/ts/deployer.ts deleted file mode 100644 index f1fa930..0000000 --- a/packages/hardhat/maci-ts/ts/deployer.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { type Contract, type Signer, ContractFactory, Interface, JsonRpcProvider, Wallet } from "ethers"; - -import type { TAbi } from "./abi"; - -/** - * A class that can deploy smart contracts using a JSON-RPC provider. - */ -export class JSONRPCDeployer { - provider: JsonRpcProvider; - - signer: Signer; - - /** - * Generate a new JSONRPCDeployer instance. - * @param privateKey - the private key of the deployer - * @param providerUrl - the URL of the JSON-RPC provider - */ - constructor(privateKey: string, providerUrl: string) { - this.provider = new JsonRpcProvider(providerUrl); - this.signer = new Wallet(privateKey, this.provider); - } - - /** - * Deploy a new smart contract using the deployer's signer. - * @param abi - the ABI of the contract - * @param bytecode - the bytecode of the contract - * @param args - the constructor arguments of the contract - * @returns a Contract object - */ - async deploy(abi: TAbi, bytecode: string, ...args: unknown[]): Promise { - const contractInterface = new Interface(abi); - const factory = new ContractFactory(contractInterface, bytecode, this.signer); - const contract = await factory.deploy(...args); - - return contract as Contract; - } -} - -/** - * Generate a new JSONRPCDeployer instance. - * @param privateKey - the private key of the deployer - * @param url - the URL of the JSON-RPC provider - * @returns the deployer instance - */ -export const genJsonRpcDeployer = (privateKey: string, url: string): JSONRPCDeployer => - new JSONRPCDeployer(privateKey, url); diff --git a/packages/hardhat/maci-ts/ts/genEmptyBallotRootsContract.ts b/packages/hardhat/maci-ts/ts/genEmptyBallotRootsContract.ts deleted file mode 100644 index 7b8fe08..0000000 --- a/packages/hardhat/maci-ts/ts/genEmptyBallotRootsContract.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { IncrementalQuinTree, hash5 } from "../crypto"; -import { Ballot } from "../domainobjs"; - -import fs from "fs"; -import path from "path"; - -const genEmptyBallotRootsContract = (): string => { - const template = fs - .readFileSync(path.resolve(__dirname, "..", "templates", "EmptyBallotRoots.sol.template")) - .toString(); - - // This hard-coded value should be consistent with the value of `stateTreeDepth` of MACI.sol - const stateTreeDepth = process.env.STATE_TREE_DEPTH ? Number.parseInt(process.env.STATE_TREE_DEPTH, 10) : 10; - - let r = ""; - for (let i = 1; i < 6; i += 1) { - const ballot = new Ballot(0, i); - const z = ballot.hash(); - // The empty Ballot tree root - const ballotTree = new IncrementalQuinTree(stateTreeDepth, BigInt(z.toString()), 5, hash5); - - r += ` emptyBallotRoots[${i - 1}] = uint256(${ballotTree.root});${i !== 5 ? "\n" : ""}`; - } - - const generated = template.replace("<% ROOTS %>", r); - return generated.trim(); -}; - -if (require.main === module) { - const generated = genEmptyBallotRootsContract(); - fs.writeFileSync( - path.resolve(__dirname, "..", "..", "contracts", "maci-contracts", "trees", "EmptyBallotRoots.sol"), - `${generated}\n`, - ); -} - -export { genEmptyBallotRootsContract }; diff --git a/packages/hardhat/maci-ts/ts/genMaciState.ts b/packages/hardhat/maci-ts/ts/genMaciState.ts deleted file mode 100644 index 3163f6d..0000000 --- a/packages/hardhat/maci-ts/ts/genMaciState.ts +++ /dev/null @@ -1,388 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -import { type Provider, type Log, Interface, BaseContract } from "ethers"; -import { MaciState, STATE_TREE_ARITY } from "../core"; -import { type Keypair, PubKey, Message } from "../domainobjs"; - -import assert from "assert"; - -import type { Action } from "./types"; -import type { MACI, Poll } from "../../typechain-types"; - -import { parseArtifact } from "./abi"; -import { sleep, sortActions } from "./utils"; - -/** - * Generate a MaciState object from the events of a MACI and Poll smart contracts - * @param provider - the ethereum provider - * @param address - the address of the MACI contract - * @param coordinatorKeypair - the keypair of the coordinator - * @param pollId - the id of the poll for which we are fetching events - * @param fromBlock - the block number from which to start fetching events - * @param blocksPerRequest - the number of blocks to fetch in each request - * @param endBlock - the block number at which to stop fetching events - * @param sleepAmount - the amount of time to sleep between each request - * @returns an instance of MaciState - */ -export const genMaciStateFromContract = async ( - provider: Provider, - address: string, - coordinatorKeypair: Keypair, - pollId: bigint, - fromBlock = 0, - blocksPerRequest = 50, - endBlock: number | undefined = undefined, - sleepAmount: number | undefined = undefined, -): Promise => { - // ensure the pollId is valid - assert(pollId >= 0); - - const [pollContractAbi] = parseArtifact("Poll"); - const [maciContractAbi] = parseArtifact("MACI"); - - const maciContract = new BaseContract(address, maciContractAbi, provider) as MACI; - - const maciIface = new Interface(maciContractAbi); - const pollIface = new Interface(pollContractAbi); - - // Check stateTreeDepth - const stateTreeDepth = await maciContract.stateTreeDepth(); - - // we need to pass the stateTreeDepth - const maciState = new MaciState(Number(stateTreeDepth)); - // ensure it is set correctly - assert(stateTreeDepth === BigInt(maciState.stateTreeDepth)); - - let signUpLogs: Log[] = []; - let deployPollLogs: Log[] = []; - - // if no last block is set then we fetch until the current block number - const lastBlock = endBlock || (await provider.getBlockNumber()); - - // Fetch event logs in batches (lastBlock inclusive) - for (let i = fromBlock; i <= lastBlock; i += blocksPerRequest + 1) { - // the last block batch will be either current iteration block + blockPerRequest - // or the end block if it is set - const toBlock = i + blocksPerRequest >= lastBlock ? lastBlock : i + blocksPerRequest; - - const [tmpSignUpLogs, tmpDeployPollLogs] = - // eslint-disable-next-line no-await-in-loop - await Promise.all([ - maciContract.queryFilter(maciContract.filters.SignUp(), i, toBlock), - maciContract.queryFilter(maciContract.filters.DeployPoll(), i, toBlock), - ]); - - signUpLogs = signUpLogs.concat(tmpSignUpLogs); - deployPollLogs = deployPollLogs.concat(tmpDeployPollLogs); - - if (sleepAmount) { - // eslint-disable-next-line no-await-in-loop - await sleep(sleepAmount); - } - } - - let actions: Action[] = []; - - signUpLogs.forEach(log => { - assert(!!log); - const mutableLog = { ...log, topics: [...log.topics] }; - const event = maciIface.parseLog(mutableLog) as unknown as { - args: { - _stateIndex: number; - _userPubKeyX: string; - _userPubKeyY: string; - _voiceCreditBalance: number; - _timestamp: number; - }; - }; - - actions.push({ - type: "SignUp", - blockNumber: log.blockNumber, - transactionIndex: log.transactionIndex, - data: { - stateIndex: Number(event.args._stateIndex), - pubKey: new PubKey([BigInt(event.args._userPubKeyX), BigInt(event.args._userPubKeyY)]), - voiceCreditBalance: Number(event.args._voiceCreditBalance), - timestamp: Number(event.args._timestamp), - }, - }); - }); - - let index = 0n; - const foundPollIds: number[] = []; - const pollContractAddresses = new Map(); - - deployPollLogs.forEach(log => { - assert(!!log); - const mutableLogs = { ...log, topics: [...log.topics] }; - const event = maciIface.parseLog(mutableLogs) as unknown as { - args: { - _coordinatorPubKeyX: string; - _coordinatorPubKeyY: string; - _pollId: bigint; - pollAddr: { - poll: string; - messageProcessor: string; - tally: string; - }; - }; - }; - - const pubKey = new PubKey([BigInt(event.args._coordinatorPubKeyX), BigInt(event.args._coordinatorPubKeyY)]); - - const p = event.args._pollId; - assert(p === index); - - const pollAddr = event.args.pollAddr.poll; - actions.push({ - type: "DeployPoll", - blockNumber: log.blockNumber, - transactionIndex: log.transactionIndex, - data: { pollId: p, pollAddr, pubKey }, - }); - - foundPollIds.push(Number(p)); - pollContractAddresses.set(BigInt(p), pollAddr); - index += 1n; - }); - - // Check whether each pollId exists - assert(foundPollIds.includes(Number(pollId)), "Error: the specified pollId does not exist on-chain"); - - const pollContractAddress = pollContractAddresses.get(pollId)!; - const pollContract = new BaseContract(pollContractAddress, pollContractAbi, provider) as Poll; - - 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 maxValues = { - maxMessages: Number(onChainMaxValues.maxMessages), - maxVoteOptions: Number(onChainMaxValues.maxVoteOptions), - }; - const treeDepths = { - intStateTreeDepth: Number(onChainTreeDepths.intStateTreeDepth), - messageTreeDepth: Number(onChainTreeDepths.messageTreeDepth), - messageTreeSubDepth: Number(onChainTreeDepths.messageTreeSubDepth), - voteOptionTreeDepth: Number(onChainTreeDepths.voteOptionTreeDepth), - }; - const batchSizes = { - tallyBatchSize: STATE_TREE_ARITY ** Number(onChainTreeDepths.intStateTreeDepth), - messageBatchSize: STATE_TREE_ARITY ** Number(onChainTreeDepths.messageTreeSubDepth), - }; - - // fetch poll contract logs - let publishMessageLogs: Log[] = []; - let topupLogs: Log[] = []; - let mergeMaciStateAqSubRootsLogs: Log[] = []; - let mergeMaciStateAqLogs: Log[] = []; - let mergeMessageAqSubRootsLogs: Log[] = []; - let mergeMessageAqLogs: Log[] = []; - - for (let i = fromBlock; i <= lastBlock; i += blocksPerRequest + 1) { - const toBlock = i + blocksPerRequest >= lastBlock ? lastBlock : i + blocksPerRequest; - - const [ - tmpPublishMessageLogs, - tmpTopupLogs, - tmpMergeMaciStateAqSubRootsLogs, - tmpMergeMaciStateAqLogs, - tmpMergeMessageAqSubRootsLogs, - tmpMergeMessageAqLogs, - // eslint-disable-next-line no-await-in-loop - ] = await Promise.all([ - pollContract.queryFilter(pollContract.filters.PublishMessage(), i, toBlock), - pollContract.queryFilter(pollContract.filters.TopupMessage(), i, toBlock), - pollContract.queryFilter(pollContract.filters.MergeMaciStateAqSubRoots(), i, toBlock), - pollContract.queryFilter(pollContract.filters.MergeMaciStateAq(), i, toBlock), - pollContract.queryFilter(pollContract.filters.MergeMessageAqSubRoots(), i, toBlock), - pollContract.queryFilter(pollContract.filters.MergeMessageAq(), i, toBlock), - ]); - - publishMessageLogs = publishMessageLogs.concat(tmpPublishMessageLogs); - topupLogs = topupLogs.concat(tmpTopupLogs); - mergeMaciStateAqSubRootsLogs = mergeMaciStateAqSubRootsLogs.concat(tmpMergeMaciStateAqSubRootsLogs); - mergeMaciStateAqLogs = mergeMaciStateAqLogs.concat(tmpMergeMaciStateAqLogs); - mergeMessageAqSubRootsLogs = mergeMessageAqSubRootsLogs.concat(tmpMergeMessageAqSubRootsLogs); - mergeMessageAqLogs = mergeMessageAqLogs.concat(tmpMergeMessageAqLogs); - - if (sleepAmount) { - // eslint-disable-next-line no-await-in-loop - await sleep(sleepAmount); - } - } - - publishMessageLogs.forEach(log => { - assert(!!log); - const mutableLogs = { ...log, topics: [...log.topics] }; - const event = pollIface.parseLog(mutableLogs) as unknown as { - args: { _message: [string, string[]]; _encPubKey: string[] }; - }; - - const message = new Message( - BigInt(event.args._message[0]), - - event.args._message[1].map(x => BigInt(x)) as [ - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - ], - ); - - const encPubKey = new PubKey(event.args._encPubKey.map(x => BigInt(x.toString())) as [bigint, bigint]); - - actions.push({ - type: "PublishMessage", - blockNumber: log.blockNumber, - transactionIndex: log.transactionIndex, - data: { - message, - encPubKey, - }, - }); - }); - - topupLogs.forEach(log => { - assert(!!log); - const mutableLog = { ...log, topics: [...log.topics] }; - const event = pollIface.parseLog(mutableLog) as unknown as { - args: { _message: [string, string[]] }; - }; - const message = new Message( - BigInt(event.args._message[0]), - event.args._message[1].map(x => BigInt(x)) as [ - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - bigint, - ], - ); - - actions.push({ - type: "TopupMessage", - blockNumber: log.blockNumber, - transactionIndex: log.transactionIndex, - data: { - message, - }, - }); - }); - - mergeMessageAqSubRootsLogs.forEach(log => { - assert(!!log); - const mutableLogs = { ...log, topics: [...log.topics] }; - const event = pollIface.parseLog(mutableLogs) as unknown as { args: { _numSrQueueOps: string } }; - - const numSrQueueOps = Number(event.args._numSrQueueOps); - actions.push({ - type: "MergeMessageAqSubRoots", - blockNumber: log.blockNumber, - transactionIndex: log.transactionIndex, - data: { - numSrQueueOps, - }, - }); - }); - - mergeMessageAqLogs.forEach(log => { - assert(!!log); - const mutableLogs = { ...log, topics: [...log.topics] }; - const event = pollIface.parseLog(mutableLogs); - - const messageRoot = BigInt((event?.args as unknown as { _messageRoot: string })._messageRoot); - actions.push({ - type: "MergeMessageAq", - blockNumber: log.blockNumber, - transactionIndex: log.transactionIndex, - data: { messageRoot }, - }); - }); - - // Sort actions - actions = sortActions(actions); - - // Reconstruct MaciState in order - actions.forEach(action => { - switch (true) { - case action.type === "SignUp": { - const { pubKey, voiceCreditBalance, timestamp } = action.data; - - maciState.signUp(pubKey!, BigInt(voiceCreditBalance!), BigInt(timestamp!)); - break; - } - - case action.type === "DeployPoll" && action.data.pollId?.toString() === pollId.toString(): { - maciState.deployPoll( - BigInt(deployTime + duration), - maxValues, - treeDepths, - batchSizes.messageBatchSize, - coordinatorKeypair, - ); - break; - } - - case action.type === "DeployPoll" && action.data.pollId?.toString() !== pollId.toString(): { - maciState.deployNullPoll(); - break; - } - - case action.type === "PublishMessage": { - const { encPubKey, message } = action.data; - maciState.polls.get(pollId)?.publishMessage(message!, encPubKey!); - break; - } - - case action.type === "TopupMessage": { - const { message } = action.data; - maciState.polls.get(pollId)?.topupMessage(message!); - break; - } - - // ensure that the message root is correct (i.e. all messages have been published offchain) - case action.type === "MergeMessageAq": { - assert(maciState.polls.get(pollId)?.messageTree.root.toString() === action.data.messageRoot?.toString()); - break; - } - - default: - break; - } - }); - - // Set numSignUps - const numSignUpsAndMessages = await pollContract.numSignUpsAndMessages(); - - const poll = maciState.polls.get(pollId); - - // ensure all messages were recorded - assert(Number(numSignUpsAndMessages[1]) === poll?.messages.length); - // set the number of signups - poll.updatePoll(numSignUpsAndMessages[0]); - - // we need to ensure that the stateRoot is correct - assert(poll.stateTree?.root.toString() === (await pollContract.mergedStateRoot()).toString()); - - maciState.polls.set(pollId, poll); - - return maciState; -}; diff --git a/packages/hardhat/maci-ts/ts/genZerosContract.ts b/packages/hardhat/maci-ts/ts/genZerosContract.ts deleted file mode 100644 index 6ae7ba7..0000000 --- a/packages/hardhat/maci-ts/ts/genZerosContract.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { sha256Hash, hashLeftRight, hash5 } from "../crypto"; - -import assert from "assert"; -import fs from "fs"; -import path from "path"; - -const genZerosContract = ( - contractName: string, - zeroVal: bigint, - hashLength: number, - numZeros: number, - comment: string, - useSha256: boolean, - subDepth: number, -): string => { - assert(hashLength === 2 || hashLength === 5); - - const template = fs.readFileSync(path.resolve(__dirname, "..", "templates", "MerkleZeros.sol.template")).toString(); - - const zeros: bigint[] = [zeroVal]; - for (let i = 1; i < numZeros; i += 1) { - const z = zeros[i - 1]; - let hashed: bigint; - - if (useSha256 && i <= subDepth) { - if (hashLength === 2) { - hashed = sha256Hash([z, z]); - } else { - hashed = sha256Hash([z, z, z, z, z]); - } - } else if (hashLength === 2) { - hashed = hashLeftRight(z, z); - } else { - hashed = hash5([z, z, z, z, z]); - } - - zeros.push(hashed); - } - - let z = ""; - for (let i = 0; i < zeros.length; i += 1) { - z += ` zeros[${i}] = uint256(${zeros[i]});\n`; - } - - const generated = template - .replace("<% CONTRACT_NAME %>", contractName) - .replace("<% NUM_ZEROS %>", numZeros.toString()) - .replace("<% ZEROS %>", ` ${z.trim()}`) - .replace("<% COMMENT %>", comment.trim()); - - return generated.trim(); -}; - -if (require.main === module) { - const contractName = process.argv[2]; - const zero = BigInt(process.argv[3]); - const hashLength = Number(process.argv[4]); - const numZeros = Number(process.argv[5]); - const comment = process.argv[6]; - const useSha256 = process.argv[7] === "1"; - const subDepth = Number(process.argv[8]); - - const generated = genZerosContract(contractName, zero, hashLength, numZeros, comment, useSha256, subDepth); - // eslint-disable-next-line no-console - console.log(generated); -} - -export { genZerosContract }; diff --git a/packages/hardhat/maci-ts/ts/index.ts b/packages/hardhat/maci-ts/ts/index.ts deleted file mode 100644 index b6db2f8..0000000 --- a/packages/hardhat/maci-ts/ts/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -export { - deployMockVerifier, - deployTopupCredit, - deployVkRegistry, - deployMaci, - deployContract, - deployContractWithLinkedLibraries, - deploySignupToken, - deploySignupTokenGatekeeper, - deployConstantInitialVoiceCreditProxy, - deployFreeForAllSignUpGatekeeper, - deployPollFactory, - linkPoseidonLibraries, - deployPoseidonContracts, - deployVerifier, -} from "./deploy"; -export { genJsonRpcDeployer } from "./deployer"; -export { genMaciStateFromContract } from "./genMaciState"; -export { formatProofForVerifierContract, getDefaultSigner, getDefaultNetwork, getSigners } from "./utils"; -export { abiDir, solDir, EMode } from "./constants"; - -export type { IVerifyingKeyStruct, SnarkProof, Groth16Proof } from "./types"; -export * from "../../typechain-types"; diff --git a/packages/hardhat/maci-ts/ts/types.ts b/packages/hardhat/maci-ts/ts/types.ts deleted file mode 100644 index a1b7cf5..0000000 --- a/packages/hardhat/maci-ts/ts/types.ts +++ /dev/null @@ -1,180 +0,0 @@ -import type { - AccQueueQuinaryMaci, - ConstantInitialVoiceCreditProxy, - FreeForAllGatekeeper, - MACI, - MockVerifier, - PollFactory, - PoseidonT3, - PoseidonT4, - PoseidonT5, - PoseidonT6, - TopupCredit, - VkRegistry, -} from "../../typechain-types"; -import type { BigNumberish, Signer } from "ethers"; -import type { CircuitInputs } from "../core"; -import type { Message, PubKey } from "../domainobjs"; -import type { PublicSignals } from "snarkjs"; - -/** - * The data structure of the verifying key of the SNARK circuit. - */ -export interface IVerifyingKeyStruct { - alpha1: { - x: BigNumberish; - y: BigNumberish; - }; - beta2: { - x: [BigNumberish, BigNumberish]; - y: [BigNumberish, BigNumberish]; - }; - gamma2: { - x: [BigNumberish, BigNumberish]; - y: [BigNumberish, BigNumberish]; - }; - delta2: { - x: [BigNumberish, BigNumberish]; - y: [BigNumberish, BigNumberish]; - }; - ic: { - x: BigNumberish; - y: BigNumberish; - }[]; -} - -/** - * The data structure representing a SNARK proof. - */ -export interface SnarkProof { - pi_a: bigint[]; - pi_b: bigint[][]; - pi_c: bigint[]; -} - -/** - * The data structure representing a Groth16 proof. - */ -export interface Groth16Proof { - pi_a: string[]; - pi_b: string[][]; - pi_c: string[]; - protocol: string; - curve: string; -} - -/** - * The data structure representing a proof output - */ -export interface Proof { - proof: SnarkProof | Groth16Proof; - circuitInputs: CircuitInputs; - publicInputs: PublicSignals; -} - -/** - * An interface holding all of the smart contracts part of MACI. - */ -export interface IDeployedTestContracts { - mockVerifierContract: MockVerifier; - gatekeeperContract: FreeForAllGatekeeper; - constantIntialVoiceCreditProxyContract: ConstantInitialVoiceCreditProxy; - maciContract: MACI; - stateAqContract: AccQueueQuinaryMaci; - vkRegistryContract: VkRegistry; - topupCreditContract: TopupCredit; -} - -/** - * An interface that represents an action that should - * be applied to a MaciState and its Polls within the - * genMaciState function. - */ -export interface Action { - type: string; - data: Partial<{ - pubKey: PubKey; - encPubKey: PubKey; - message: Message; - voiceCreditBalance: number; - timestamp: number; - stateIndex: number; - numSrQueueOps: number; - pollId: bigint; - pollAddr: string; - stateRoot: bigint; - messageRoot: bigint; - }>; - blockNumber: number; - transactionIndex: number; -} - -/** - * An interface that represents the deployed Poseidon contracts. - */ -export interface IDeployedPoseidonContracts { - PoseidonT3Contract: PoseidonT3; - PoseidonT4Contract: PoseidonT4; - PoseidonT5Contract: PoseidonT5; - PoseidonT6Contract: PoseidonT6; -} - -/** - * An interface that represents the arguments for MACI contracts deployment. - */ -export interface IDeployMaciArgs { - /** - * The address of the SignUpTokenGatekeeper contract - */ - signUpTokenGatekeeperContractAddress: string; - - /** - * The address of the ConstantInitialVoiceCreditProxy contract - */ - initialVoiceCreditBalanceAddress: string; - - /** - * The address of the TopupCredit contract - */ - topupCreditContractAddress: string; - - /** - * The signer to use to deploy the contract - */ - signer?: Signer; - - /** - * Poseidon contract addresses (if not provided, they will be deployed automatically) - */ - poseidonAddresses?: Partial<{ - poseidonT3: string; - poseidonT4: string; - poseidonT5: string; - poseidonT6: string; - }>; - - /** - * The depth of the state tree - */ - stateTreeDepth?: number; - - /** - * Whether to suppress console output - */ - quiet?: boolean; -} - -/** - * An interface that represents the deployed MACI contracts. - */ -export interface IDeployedMaci { - maciContract: MACI; - stateAqContract: AccQueueQuinaryMaci; - pollFactoryContract: PollFactory; - poseidonAddrs: { - poseidonT3: string; - poseidonT4: string; - poseidonT5: string; - poseidonT6: string; - }; -} diff --git a/packages/hardhat/maci-ts/ts/utils.ts b/packages/hardhat/maci-ts/ts/utils.ts deleted file mode 100644 index 31281d0..0000000 --- a/packages/hardhat/maci-ts/ts/utils.ts +++ /dev/null @@ -1,145 +0,0 @@ -import type { Action, SnarkProof, Groth16Proof } from "./types"; -import type { Ownable } from "../../typechain-types"; -import type { BigNumberish, FeeData, Network, Signer } from "ethers"; - -/** - * Format a SnarkProof type to an array of strings - * which can be passed to the Groth16 verifier contract. - * @param proof the SnarkProof to format - * @returns an array of strings - */ -export const formatProofForVerifierContract = (proof: SnarkProof | Groth16Proof): string[] => - [ - 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_c[0], - proof.pi_c[1], - ].map(x => x.toString()); - -/** - * Pause the thread for n milliseconds - * @param ms - the amount of time to sleep in milliseconds - */ -export const sleep = async (ms: number): Promise => { - await new Promise(resolve => { - setTimeout(resolve, ms); - }); -}; - -/** - * The comparison function for Actions based on block number and transaction - * index. - * @param actions - the array of actions to sort - * @returns the sorted array of actions - */ -export function sortActions(actions: Action[]): Action[] { - return actions.slice().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; - }); -} - -/** - * Print to the console - * @param msg - the message to print - * @param quiet - whether to suppress console output - */ -export const log = (msg: string, quiet: boolean): void => { - if (!quiet) { - // eslint-disable-next-line no-console - console.log(msg); - } -}; - -/** - * Get the default signer from the hardhat node - * @returns the default signer - */ -export const getDefaultSigner = async (): Promise => { - const { ethers } = await import("hardhat"); - - const [signer] = await ethers.getSigners(); - - return signer; -}; - -/** - * Get the default signer network from the hardhat node - * @returns the default network - */ -export const getDefaultNetwork = async (): Promise => { - const signer = await getDefaultSigner(); - - return signer.provider?.getNetwork(); -}; - -/** - * Get all of the available signers from the hardhat node - * @dev to be used while testing - * @returns the signers - */ -export const getSigners = async (): Promise => { - const { ethers } = await import("hardhat"); - - return ethers.getSigners(); -}; - -/** - * Get the current fee data from the blockchain node. - * This is needed to ensure transaction go through in busy times - * @returns - the fee data - */ -export const getFeeData = async (): Promise => { - const signer = await getDefaultSigner(); - return signer.provider?.getFeeData(); -}; - -/** - * Transfer ownership of a contract (using Ownable from OpenZeppelin) - * @param contract - the contract to transfer ownership of - * @param newOwner - the address of the new owner - * @param quiet - whether to suppress console output - */ -export const transferOwnership = async ( - contract: T, - newOwner: string, - quiet = false, -): Promise => { - log(`Transferring ownership of ${await contract.getAddress()} to ${newOwner}`, quiet); - const tx = await contract.transferOwnership(newOwner, { - maxFeePerGas: await getFeeData().then(res => res?.maxFeePerGas), - }); - - await tx.wait(); -}; - -/** - * Convert bignumberish to hex - * - * @param value - bignumberish string - * @returns hex representation of it - */ -export function asHex(value: BigNumberish): string { - return `0x${BigInt(value).toString(16)}`; -} diff --git a/packages/hardhat/package.json b/packages/hardhat/package.json index 3f7b64e..d1964a9 100644 --- a/packages/hardhat/package.json +++ b/packages/hardhat/package.json @@ -4,7 +4,7 @@ "scripts": { "account": "hardhat run scripts/listAccount.ts", "chain": "hardhat node --network hardhat --no-deploy", - "compile": "./maci-scripts/compileSol.sh", + "compile": "hardhat compile", "deploy": "yarn compile && hardhat deploy", "flatten": "yarn compile && hardhat flatten", "fork": "MAINNET_FORKING_ENABLED=true hardhat node --network hardhat --no-deploy", @@ -19,7 +19,6 @@ }, "dependencies": { "@commander-js/extra-typings": "^12.0.1", - "@openzeppelin/contracts": "~4.8.1", "@typechain/ethers-v6": "~0.5.1", "@zk-kit/eddsa-poseidon": "^0.11.0", "@zk-kit/poseidon-cipher": "^0.3.0", @@ -29,6 +28,11 @@ "dotenv": "~16.0.3", "envfile": "~6.18.0", "hardhat-artifactor": "^0.2.0", + "maci-circuits": "0.0.0-ci.f6073a6", + "maci-cli": "0.0.0-ci.f6073a6", + "maci-contracts": "0.0.0-ci.f6073a6", + "maci-crypto": "0.0.0-ci.f6073a6", + "maci-domainobjs": "0.0.0-ci.f6073a6", "prompt": "^1.3.0", "qrcode": "~1.5.1", "snarkjs": "^0.7.3" diff --git a/packages/nextjs/app/_components/RegisterButton.tsx b/packages/nextjs/app/_components/RegisterButton.tsx index aa8cbb7..e22f877 100644 --- a/packages/nextjs/app/_components/RegisterButton.tsx +++ b/packages/nextjs/app/_components/RegisterButton.tsx @@ -1,4 +1,4 @@ -import { Keypair, PrivKey } from "@se-2/hardhat/maci-ts/domainobjs"; +import { Keypair, PrivKey } from "maci-domainobjs"; import { useAuthContext } from "~~/contexts/AuthContext"; import { useScaffoldContractWrite } from "~~/hooks/scaffold-eth"; @@ -6,9 +6,9 @@ export default function RegisterButton() { const { keypair, isRegistered, signMessageAsync } = useAuthContext(); const { writeAsync } = useScaffoldContractWrite({ - contractName: "MACI", + contractName: "MACIWrapper", functionName: "signUp", - args: [keypair?.pubKey.asContractParam(), "0x", "0x"], + args: [keypair?.pubKey.asContractParam() as { x: bigint; y: bigint }, "0x", "0x"], }); async function register() { @@ -19,7 +19,7 @@ export default function RegisterButton() { userKeypair = new Keypair(new PrivKey(signature)); } - await writeAsync({ args: [userKeypair?.pubKey.asContractParam(), "0x", "0x"] }); + await writeAsync({ args: [userKeypair?.pubKey.asContractParam() as { x: bigint; y: bigint }, "0x", "0x"] }); } catch (err) { console.log(err); } diff --git a/packages/nextjs/app/admin/_components/CreatePollModal.tsx b/packages/nextjs/app/admin/_components/CreatePollModal.tsx index 41b4c2e..df2bb4c 100644 --- a/packages/nextjs/app/admin/_components/CreatePollModal.tsx +++ b/packages/nextjs/app/admin/_components/CreatePollModal.tsx @@ -6,6 +6,7 @@ import { RxCross2 } from "react-icons/rx"; import Modal from "~~/components/Modal"; import { useScaffoldContractWrite } from "~~/hooks/scaffold-eth"; import { PollType } from "~~/types/poll"; +import { EMode } from "~~/types/poll"; import { notification } from "~~/utils/scaffold-eth"; export default function Example({ @@ -67,6 +68,7 @@ export default function Example({ pollData.options || [], JSON.stringify({ pollType: pollData.pollType }), duration > 0 ? BigInt(duration) : 0n, + EMode.QV, ], }); diff --git a/packages/nextjs/app/admin/page.tsx b/packages/nextjs/app/admin/page.tsx index 6e9c18c..c4ff097 100644 --- a/packages/nextjs/app/admin/page.tsx +++ b/packages/nextjs/app/admin/page.tsx @@ -16,7 +16,7 @@ export default function AdminPage() { const { address } = useAccount(); const [openCreatePollModal, setOpenCreatePollModal] = useState(false); const [currentPage, setCurrentPage] = useState(1); - const { data: admin } = useScaffoldContractRead({ contractName: "MACI", functionName: "owner" }); + const { data: admin } = useScaffoldContractRead({ contractName: "MACIWrapper", functionName: "owner" }); const [limit] = useState(10); const { totalPolls, polls, refetch: refetchPolls } = useFetchPolls(currentPage, limit); const totalPages = useTotalPages(totalPolls, limit); diff --git a/packages/nextjs/app/polls/[id]/page.tsx b/packages/nextjs/app/polls/[id]/page.tsx index b98123e..6d27adf 100644 --- a/packages/nextjs/app/polls/[id]/page.tsx +++ b/packages/nextjs/app/polls/[id]/page.tsx @@ -2,8 +2,8 @@ import { useEffect, useState } from "react"; import { useParams } from "next/navigation"; -import { genRandomSalt } from "@se-2/hardhat/maci-ts/crypto"; -import { Keypair, PCommand, PubKey } from "@se-2/hardhat/maci-ts/domainobjs"; +import { genRandomSalt } from "maci-crypto"; +import { Keypair, PCommand, PubKey } from "maci-domainobjs"; import { useContractRead, useContractWrite } from "wagmi"; import PollAbi from "~~/abi/Poll"; import VoteCard from "~~/components/card/VoteCard"; @@ -148,13 +148,25 @@ export default function PollDetail() { try { if (votesToMessage.length === 1) { await publishMessage({ - args: [votesToMessage[0].message.asContractParam(), votesToMessage[0].encKeyPair.pubKey.asContractParam()], + args: [ + votesToMessage[0].message.asContractParam() as unknown as { + msgType: bigint; + data: readonly [bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint]; + }, + votesToMessage[0].encKeyPair.pubKey.asContractParam() as unknown as { x: bigint; y: bigint }, + ], }); } else { await publishMessageBatch({ args: [ - votesToMessage.map(v => v.message.asContractParam()), - votesToMessage.map(v => v.encKeyPair.pubKey.asContractParam()), + votesToMessage.map( + v => + v.message.asContractParam() as unknown as { + msgType: bigint; + data: readonly [bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint]; + }, + ), + votesToMessage.map(v => v.encKeyPair.pubKey.asContractParam() as { x: bigint; y: bigint }), ], }); } diff --git a/packages/nextjs/components/Header.tsx b/packages/nextjs/components/Header.tsx index 3fa7ddc..94c4c2b 100644 --- a/packages/nextjs/components/Header.tsx +++ b/packages/nextjs/components/Header.tsx @@ -37,7 +37,7 @@ export const HeaderMenuLinks = () => { const { address } = useAccount(); - const { data: owner } = useScaffoldContractRead({ contractName: "MACI", functionName: "owner" }); + const { data: owner } = useScaffoldContractRead({ contractName: "MACIWrapper", functionName: "owner" }); return ( <> diff --git a/packages/nextjs/contexts/AuthContext.tsx b/packages/nextjs/contexts/AuthContext.tsx index 5a104f0..ff214ad 100644 --- a/packages/nextjs/contexts/AuthContext.tsx +++ b/packages/nextjs/contexts/AuthContext.tsx @@ -1,7 +1,7 @@ "use client"; import { createContext, useContext, useEffect, useState } from "react"; -import { Keypair, PrivKey } from "@se-2/hardhat/maci-ts/domainobjs"; +import { Keypair, PrivKey } from "maci-domainobjs"; import { useAccount, useSignMessage } from "wagmi"; import deployedContracts from "~~/contracts/deployedContracts"; import { useScaffoldContractRead, useScaffoldEventHistory, useScaffoldEventSubscriber } from "~~/hooks/scaffold-eth"; @@ -45,23 +45,23 @@ export default function AuthContextProvider({ children }: { children: React.Reac }, [signature]); const { data: isRegistered, refetch: refetchIsRegistered } = useScaffoldContractRead({ - contractName: "MACI", + contractName: "MACIWrapper", functionName: "isPublicKeyRegistered", - args: keypair ? keypair.pubKey.rawPubKey : [undefined, undefined], + args: keypair ? keypair.pubKey.rawPubKey : [0n, 0n], }); const chainId = scaffoldConfig.targetNetworks[0].id; const { - MACI: { deploymentBlockNumber }, + MACIWrapper: { deploymentBlockNumber }, } = deployedContracts[chainId]; const { data: SignUpEvents } = useScaffoldEventHistory({ - contractName: "MACI", + contractName: "MACIWrapper", eventName: "SignUp", filters: { - _userPubKeyX: keypair?.pubKey.asContractParam().x, - _userPubKeyY: keypair?.pubKey.asContractParam().y, + _userPubKeyX: BigInt(keypair?.pubKey.asContractParam().x || 0n), + _userPubKeyY: BigInt(keypair?.pubKey.asContractParam().y || 0n), }, fromBlock: BigInt(deploymentBlockNumber), }); @@ -77,7 +77,7 @@ export default function AuthContextProvider({ children }: { children: React.Reac }, [keypair, SignUpEvents]); useScaffoldEventSubscriber({ - contractName: "MACI", + contractName: "MACIWrapper", eventName: "SignUp", listener: logs => { logs.forEach(log => { diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts index 3452ee4..03e745a 100644 --- a/packages/nextjs/contracts/deployedContracts.ts +++ b/packages/nextjs/contracts/deployedContracts.ts @@ -45,9 +45,6 @@ const deployedContracts = { type: "function", }, ], - inheritedFunctions: { - getVoiceCredits: "contracts/maci-contracts/initialVoiceCreditProxy/InitialVoiceCreditProxy.sol", - }, deploymentBlockNumber: 1, }, FreeForAllGatekeeper: { @@ -90,10 +87,9 @@ const deployedContracts = { type: "function", }, ], - inheritedFunctions: {}, deploymentBlockNumber: 3, }, - MACI: { + MACIWrapper: { address: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", abi: [ { @@ -134,7 +130,7 @@ const deployedContracts = { type: "uint8", }, ], - stateMutability: "payable", + stateMutability: "nonpayable", type: "constructor", }, { @@ -148,6 +144,16 @@ const deployedContracts = { name: "CallerMustBePoll", type: "error", }, + { + inputs: [], + name: "DefaultZeroBadIndex", + type: "error", + }, + { + inputs: [], + name: "DepthTooLarge", + type: "error", + }, { inputs: [], name: "InvalidMessage", @@ -155,14 +161,41 @@ const deployedContracts = { }, { inputs: [], - name: "MaciPubKeyLargerThanSnarkFieldSize", + name: "InvalidPubKey", + type: "error", + }, + { + inputs: [], + name: "NumberOfLeavesCannotBeZero", type: "error", }, { inputs: [ { internalType: "address", - name: "pollAddr", + name: "owner", + type: "address", + }, + ], + name: "OwnableInvalidOwner", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "OwnableUnauthorizedAccount", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "_poll", type: "address", }, ], @@ -390,9 +423,9 @@ const deployedContracts = { type: "address", }, { - internalType: "bool", - name: "_isQv", - type: "bool", + internalType: "enum DomainObjs.Mode", + name: "_mode", + type: "uint8", }, ], name: "deployPoll", @@ -463,7 +496,7 @@ const deployedContracts = { }, { inputs: [], - name: "getStateAqRoot", + name: "getStateTreeRoot", outputs: [ { internalType: "uint256", @@ -709,52 +742,20 @@ const deployedContracts = { }, { inputs: [], - name: "manager", - outputs: [ - { - internalType: "address", - name: "", - type: "address", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint256", - name: "_pollId", - type: "uint256", - }, - ], - name: "mergeStateAq", + name: "lazyIMTData", outputs: [ { - internalType: "uint256", - name: "root", - type: "uint256", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { - internalType: "uint256", - name: "_numSrQueueOps", - type: "uint256", + internalType: "uint40", + name: "maxIndex", + type: "uint40", }, { - internalType: "uint256", - name: "_pollId", - type: "uint256", + internalType: "uint40", + name: "numberOfLeaves", + type: "uint40", }, ], - name: "mergeStateAqSubRoots", - outputs: [], - stateMutability: "nonpayable", + stateMutability: "view", type: "function", }, { @@ -789,7 +790,7 @@ const deployedContracts = { outputs: [ { internalType: "uint256", - name: "", + name: "signUps", type: "uint256", }, ], @@ -992,19 +993,6 @@ const deployedContracts = { stateMutability: "view", type: "function", }, - { - inputs: [], - name: "stateAq", - outputs: [ - { - internalType: "contract AccQueue", - name: "", - type: "address", - }, - ], - stateMutability: "view", - type: "function", - }, { inputs: [], name: "stateTreeDepth", @@ -1018,19 +1006,6 @@ const deployedContracts = { stateMutability: "view", type: "function", }, - { - inputs: [], - name: "subtreesMerged", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", - }, { inputs: [], name: "tallyFactory", @@ -1070,40 +1045,36 @@ const deployedContracts = { stateMutability: "nonpayable", type: "function", }, - { - inputs: [ - { - internalType: "address", - name: "_manager", - type: "address", - }, - ], - name: "updateManager", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, ], inheritedFunctions: { - getStateAqRoot: "contracts/maci-contracts/interfaces/IMACI.sol", - mergeStateAq: "contracts/maci-contracts/interfaces/IMACI.sol", - mergeStateAqSubRoots: "contracts/maci-contracts/interfaces/IMACI.sol", - numSignUps: "contracts/maci-contracts/interfaces/IMACI.sol", - stateAq: "contracts/maci-contracts/interfaces/IMACI.sol", - stateTreeDepth: "contracts/maci-contracts/interfaces/IMACI.sol", - MESSAGE_DATA_LENGTH: "contracts/maci-contracts/utilities/Utilities.sol", - hash2: "contracts/maci-contracts/utilities/Utilities.sol", - hash3: "contracts/maci-contracts/utilities/Utilities.sol", - hash4: "contracts/maci-contracts/utilities/Utilities.sol", - hash5: "contracts/maci-contracts/utilities/Utilities.sol", - hashLeftRight: "contracts/maci-contracts/utilities/Utilities.sol", - hashMessageAndEncPubKey: "contracts/maci-contracts/utilities/Utilities.sol", - hashStateLeaf: "contracts/maci-contracts/utilities/Utilities.sol", - padAndHashMessage: "contracts/maci-contracts/utilities/Utilities.sol", - sha256Hash: "contracts/maci-contracts/utilities/Utilities.sol", - owner: "@openzeppelin/contracts/access/Ownable.sol", - renounceOwnership: "@openzeppelin/contracts/access/Ownable.sol", - transferOwnership: "@openzeppelin/contracts/access/Ownable.sol", + MESSAGE_DATA_LENGTH: "maci-contracts/contracts/MACI.sol", + deployPoll: "maci-contracts/contracts/MACI.sol", + getPoll: "maci-contracts/contracts/MACI.sol", + getStateTreeRoot: "maci-contracts/contracts/MACI.sol", + hash2: "maci-contracts/contracts/MACI.sol", + hash3: "maci-contracts/contracts/MACI.sol", + hash4: "maci-contracts/contracts/MACI.sol", + hash5: "maci-contracts/contracts/MACI.sol", + hashLeftRight: "maci-contracts/contracts/MACI.sol", + hashMessageAndEncPubKey: "maci-contracts/contracts/MACI.sol", + hashStateLeaf: "maci-contracts/contracts/MACI.sol", + initialVoiceCreditProxy: "maci-contracts/contracts/MACI.sol", + lazyIMTData: "maci-contracts/contracts/MACI.sol", + messageProcessorFactory: "maci-contracts/contracts/MACI.sol", + nextPollId: "maci-contracts/contracts/MACI.sol", + numSignUps: "maci-contracts/contracts/MACI.sol", + owner: "maci-contracts/contracts/MACI.sol", + padAndHashMessage: "maci-contracts/contracts/MACI.sol", + pollFactory: "maci-contracts/contracts/MACI.sol", + polls: "maci-contracts/contracts/MACI.sol", + renounceOwnership: "maci-contracts/contracts/MACI.sol", + sha256Hash: "maci-contracts/contracts/MACI.sol", + signUp: "maci-contracts/contracts/MACI.sol", + signUpGatekeeper: "maci-contracts/contracts/MACI.sol", + stateTreeDepth: "maci-contracts/contracts/MACI.sol", + tallyFactory: "maci-contracts/contracts/MACI.sol", + topupCredit: "maci-contracts/contracts/MACI.sol", + transferOwnership: "maci-contracts/contracts/MACI.sol", }, deploymentBlockNumber: 23, }, @@ -1146,9 +1117,9 @@ const deployedContracts = { type: "address", }, { - internalType: "bool", - name: "_isQv", - type: "bool", + internalType: "enum DomainObjs.Mode", + name: "_mode", + type: "uint8", }, ], name: "deploy", @@ -1163,9 +1134,6 @@ const deployedContracts = { type: "function", }, ], - inheritedFunctions: { - MESSAGE_DATA_LENGTH: "contracts/maci-contracts/utilities/DomainObjs.sol", - }, deploymentBlockNumber: 19, }, PollFactory: { @@ -1290,27 +1258,18 @@ const deployedContracts = { type: "function", }, ], - inheritedFunctions: { - MESSAGE_DATA_LENGTH: "contracts/maci-contracts/utilities/DomainObjs.sol", - deploy: "contracts/maci-contracts/interfaces/IPollFactory.sol", - }, deploymentBlockNumber: 17, }, PollManager: { - address: "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + address: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c", abi: [ { inputs: [ { - internalType: "contract MACI", + internalType: "contract MACIWrapper", name: "_maci", type: "address", }, - { - internalType: "bool", - name: "_isQv", - type: "bool", - }, ], stateMutability: "nonpayable", type: "constructor", @@ -1471,6 +1430,11 @@ const deployedContracts = { name: "_duration", type: "uint256", }, + { + internalType: "enum DomainObjs.Mode", + name: "isQv", + type: "uint8", + }, ], name: "createPoll", outputs: [], @@ -1673,25 +1637,12 @@ const deployedContracts = { stateMutability: "view", type: "function", }, - { - inputs: [], - name: "isQv", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", - }, { inputs: [], name: "maci", outputs: [ { - internalType: "contract MACI", + internalType: "contract MACIWrapper", name: "", type: "address", }, @@ -1787,11 +1738,6 @@ const deployedContracts = { name: "_vkRegistry", type: "address", }, - { - internalType: "bool", - name: "_useSubsidy", - type: "bool", - }, ], name: "setConfig", outputs: [], @@ -1898,9 +1844,9 @@ const deployedContracts = { }, ], inheritedFunctions: { - MESSAGE_DATA_LENGTH: "contracts/maci-contracts/utilities/DomainObjs.sol", + MESSAGE_DATA_LENGTH: "maci-contracts/contracts/utilities/DomainObjs.sol", }, - deploymentBlockNumber: 29, + deploymentBlockNumber: 35, }, PoseidonT3: { address: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", @@ -1925,7 +1871,6 @@ const deployedContracts = { type: "function", }, ], - inheritedFunctions: {}, deploymentBlockNumber: 9, }, PoseidonT4: { @@ -1951,7 +1896,6 @@ const deployedContracts = { type: "function", }, ], - inheritedFunctions: {}, deploymentBlockNumber: 11, }, PoseidonT5: { @@ -1977,7 +1921,6 @@ const deployedContracts = { type: "function", }, ], - inheritedFunctions: {}, deploymentBlockNumber: 13, }, PoseidonT6: { @@ -2003,12 +1946,24 @@ const deployedContracts = { type: "function", }, ], - inheritedFunctions: {}, deploymentBlockNumber: 15, }, TallyFactory: { address: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", abi: [ + { + inputs: [], + name: "MESSAGE_DATA_LENGTH", + outputs: [ + { + internalType: "uint8", + name: "", + type: "uint8", + }, + ], + stateMutability: "view", + type: "function", + }, { inputs: [ { @@ -2037,9 +1992,9 @@ const deployedContracts = { type: "address", }, { - internalType: "bool", - name: "_isQv", - type: "bool", + internalType: "enum DomainObjs.Mode", + name: "_mode", + type: "uint8", }, ], name: "deploy", @@ -2054,9 +2009,6 @@ const deployedContracts = { type: "function", }, ], - inheritedFunctions: { - deploy: "contracts/maci-contracts/interfaces/ITallyFactory.sol", - }, deploymentBlockNumber: 21, }, TopupCredit: { @@ -2067,11 +2019,119 @@ const deployedContracts = { stateMutability: "payable", type: "constructor", }, + { + inputs: [ + { + internalType: "address", + name: "spender", + type: "address", + }, + { + internalType: "uint256", + name: "allowance", + type: "uint256", + }, + { + internalType: "uint256", + name: "needed", + type: "uint256", + }, + ], + name: "ERC20InsufficientAllowance", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address", + }, + { + internalType: "uint256", + name: "balance", + type: "uint256", + }, + { + internalType: "uint256", + name: "needed", + type: "uint256", + }, + ], + name: "ERC20InsufficientBalance", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "approver", + type: "address", + }, + ], + name: "ERC20InvalidApprover", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "receiver", + type: "address", + }, + ], + name: "ERC20InvalidReceiver", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address", + }, + ], + name: "ERC20InvalidSender", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "spender", + type: "address", + }, + ], + name: "ERC20InvalidSpender", + type: "error", + }, { inputs: [], name: "ExceedLimit", type: "error", }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "OwnableInvalidOwner", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "OwnableUnauthorizedAccount", + type: "error", + }, { anonymous: false, inputs: [ @@ -2231,7 +2291,7 @@ const deployedContracts = { }, { internalType: "uint256", - name: "amount", + name: "value", type: "uint256", }, ], @@ -2278,54 +2338,6 @@ const deployedContracts = { stateMutability: "view", type: "function", }, - { - inputs: [ - { - internalType: "address", - name: "spender", - type: "address", - }, - { - internalType: "uint256", - name: "subtractedValue", - type: "uint256", - }, - ], - name: "decreaseAllowance", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "spender", - type: "address", - }, - { - internalType: "uint256", - name: "addedValue", - type: "uint256", - }, - ], - name: "increaseAllowance", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, { inputs: [], name: "name", @@ -2394,7 +2406,7 @@ const deployedContracts = { }, { internalType: "uint256", - name: "amount", + name: "value", type: "uint256", }, ], @@ -2423,7 +2435,7 @@ const deployedContracts = { }, { internalType: "uint256", - name: "amount", + name: "value", type: "uint256", }, ], @@ -2452,22 +2464,6 @@ const deployedContracts = { type: "function", }, ], - inheritedFunctions: { - allowance: "@openzeppelin/contracts/token/ERC20/ERC20.sol", - approve: "@openzeppelin/contracts/token/ERC20/ERC20.sol", - balanceOf: "@openzeppelin/contracts/token/ERC20/ERC20.sol", - decimals: "@openzeppelin/contracts/token/ERC20/ERC20.sol", - decreaseAllowance: "@openzeppelin/contracts/token/ERC20/ERC20.sol", - increaseAllowance: "@openzeppelin/contracts/token/ERC20/ERC20.sol", - name: "@openzeppelin/contracts/token/ERC20/ERC20.sol", - symbol: "@openzeppelin/contracts/token/ERC20/ERC20.sol", - totalSupply: "@openzeppelin/contracts/token/ERC20/ERC20.sol", - transfer: "@openzeppelin/contracts/token/ERC20/ERC20.sol", - transferFrom: "@openzeppelin/contracts/token/ERC20/ERC20.sol", - owner: "@openzeppelin/contracts/access/Ownable.sol", - renounceOwnership: "@openzeppelin/contracts/access/Ownable.sol", - transferOwnership: "@openzeppelin/contracts/access/Ownable.sol", - }, deploymentBlockNumber: 7, }, Verifier: { @@ -2628,9 +2624,6 @@ const deployedContracts = { type: "function", }, ], - inheritedFunctions: { - verify: "contracts/maci-contracts/interfaces/IVerifier.sol", - }, deploymentBlockNumber: 5, }, VkRegistry: { @@ -2641,6 +2634,33 @@ const deployedContracts = { stateMutability: "payable", type: "constructor", }, + { + inputs: [], + name: "InvalidKeysParams", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "OwnableInvalidOwner", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "OwnableUnauthorizedAccount", + type: "error", + }, { inputs: [], name: "ProcessVkAlreadySet", @@ -2696,8 +2716,8 @@ const deployedContracts = { }, { indexed: false, - internalType: "enum IVkRegistry.Mode", - name: "mode", + internalType: "enum DomainObjs.Mode", + name: "_mode", type: "uint8", }, ], @@ -2715,14 +2735,27 @@ const deployedContracts = { }, { indexed: false, - internalType: "enum IVkRegistry.Mode", - name: "mode", + internalType: "enum DomainObjs.Mode", + name: "_mode", type: "uint8", }, ], name: "TallyVkSet", type: "event", }, + { + inputs: [], + name: "MESSAGE_DATA_LENGTH", + outputs: [ + { + internalType: "uint8", + name: "", + type: "uint8", + }, + ], + stateMutability: "view", + type: "function", + }, { inputs: [ { @@ -2809,7 +2842,7 @@ const deployedContracts = { type: "uint256", }, { - internalType: "enum IVkRegistry.Mode", + internalType: "enum DomainObjs.Mode", name: "_mode", type: "uint8", }, @@ -2920,7 +2953,7 @@ const deployedContracts = { type: "uint256", }, { - internalType: "enum IVkRegistry.Mode", + internalType: "enum DomainObjs.Mode", name: "_mode", type: "uint8", }, @@ -3041,7 +3074,7 @@ const deployedContracts = { type: "uint256", }, { - internalType: "enum IVkRegistry.Mode", + internalType: "enum DomainObjs.Mode", name: "_mode", type: "uint8", }, @@ -3152,7 +3185,7 @@ const deployedContracts = { type: "uint256", }, { - internalType: "enum IVkRegistry.Mode", + internalType: "enum DomainObjs.Mode", name: "_mode", type: "uint8", }, @@ -3278,7 +3311,7 @@ const deployedContracts = { type: "uint256", }, { - internalType: "enum IVkRegistry.Mode", + internalType: "enum DomainObjs.Mode", name: "_mode", type: "uint8", }, @@ -3312,7 +3345,7 @@ const deployedContracts = { type: "uint256", }, { - internalType: "enum IVkRegistry.Mode", + internalType: "enum DomainObjs.Mode", name: "_mode", type: "uint8", }, @@ -3336,7 +3369,7 @@ const deployedContracts = { type: "uint256", }, { - internalType: "enum IVkRegistry.Mode", + internalType: "enum DomainObjs.Mode", name: "_mode", type: "uint8", }, @@ -3360,7 +3393,7 @@ const deployedContracts = { type: "uint256", }, { - internalType: "enum IVkRegistry.Mode", + internalType: "enum DomainObjs.Mode", name: "_mode", type: "uint8", }, @@ -3424,7 +3457,7 @@ const deployedContracts = { type: "uint256", }, { - internalType: "enum IVkRegistry.Mode", + internalType: "enum DomainObjs.Mode", name: "_mode", type: "uint8", }, @@ -3618,6 +3651,228 @@ const deployedContracts = { stateMutability: "nonpayable", type: "function", }, + { + inputs: [ + { + internalType: "uint256", + name: "_stateTreeDepth", + type: "uint256", + }, + { + internalType: "uint256", + name: "_intStateTreeDepth", + type: "uint256", + }, + { + internalType: "uint256", + name: "_messageTreeDepth", + type: "uint256", + }, + { + internalType: "uint256", + name: "_voteOptionTreeDepth", + type: "uint256", + }, + { + internalType: "uint256", + name: "_messageBatchSize", + type: "uint256", + }, + { + internalType: "enum DomainObjs.Mode[]", + name: "_modes", + type: "uint8[]", + }, + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "x", + type: "uint256", + }, + { + internalType: "uint256", + name: "y", + type: "uint256", + }, + ], + internalType: "struct Pairing.G1Point", + name: "alpha1", + type: "tuple", + }, + { + components: [ + { + internalType: "uint256[2]", + name: "x", + type: "uint256[2]", + }, + { + internalType: "uint256[2]", + name: "y", + type: "uint256[2]", + }, + ], + internalType: "struct Pairing.G2Point", + name: "beta2", + type: "tuple", + }, + { + components: [ + { + internalType: "uint256[2]", + name: "x", + type: "uint256[2]", + }, + { + internalType: "uint256[2]", + name: "y", + type: "uint256[2]", + }, + ], + internalType: "struct Pairing.G2Point", + name: "gamma2", + type: "tuple", + }, + { + components: [ + { + internalType: "uint256[2]", + name: "x", + type: "uint256[2]", + }, + { + internalType: "uint256[2]", + name: "y", + type: "uint256[2]", + }, + ], + internalType: "struct Pairing.G2Point", + name: "delta2", + type: "tuple", + }, + { + components: [ + { + internalType: "uint256", + name: "x", + type: "uint256", + }, + { + internalType: "uint256", + name: "y", + type: "uint256", + }, + ], + internalType: "struct Pairing.G1Point[]", + name: "ic", + type: "tuple[]", + }, + ], + internalType: "struct SnarkCommon.VerifyingKey[]", + name: "_processVks", + type: "tuple[]", + }, + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "x", + type: "uint256", + }, + { + internalType: "uint256", + name: "y", + type: "uint256", + }, + ], + internalType: "struct Pairing.G1Point", + name: "alpha1", + type: "tuple", + }, + { + components: [ + { + internalType: "uint256[2]", + name: "x", + type: "uint256[2]", + }, + { + internalType: "uint256[2]", + name: "y", + type: "uint256[2]", + }, + ], + internalType: "struct Pairing.G2Point", + name: "beta2", + type: "tuple", + }, + { + components: [ + { + internalType: "uint256[2]", + name: "x", + type: "uint256[2]", + }, + { + internalType: "uint256[2]", + name: "y", + type: "uint256[2]", + }, + ], + internalType: "struct Pairing.G2Point", + name: "gamma2", + type: "tuple", + }, + { + components: [ + { + internalType: "uint256[2]", + name: "x", + type: "uint256[2]", + }, + { + internalType: "uint256[2]", + name: "y", + type: "uint256[2]", + }, + ], + internalType: "struct Pairing.G2Point", + name: "delta2", + type: "tuple", + }, + { + components: [ + { + internalType: "uint256", + name: "x", + type: "uint256", + }, + { + internalType: "uint256", + name: "y", + type: "uint256", + }, + ], + internalType: "struct Pairing.G1Point[]", + name: "ic", + type: "tuple[]", + }, + ], + internalType: "struct SnarkCommon.VerifyingKey[]", + name: "_tallyVks", + type: "tuple[]", + }, + ], + name: "setVerifyingKeysBatch", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, { inputs: [ { @@ -3632,13 +3887,6 @@ const deployedContracts = { type: "function", }, ], - inheritedFunctions: { - owner: "@openzeppelin/contracts/access/Ownable.sol", - renounceOwnership: "@openzeppelin/contracts/access/Ownable.sol", - transferOwnership: "@openzeppelin/contracts/access/Ownable.sol", - getProcessVk: "contracts/maci-contracts/interfaces/IVkRegistry.sol", - getTallyVk: "contracts/maci-contracts/interfaces/IVkRegistry.sol", - }, deploymentBlockNumber: 26, }, }, diff --git a/packages/nextjs/hooks/useMergeSignups.tsx b/packages/nextjs/hooks/useMergeSignups.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index a49b21e..f4dba0b 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -27,6 +27,8 @@ "circomkit": "^0.0.24", "circomlib": "^2.0.5", "daisyui": "4.5.0", + "maci-crypto": "0.0.0-ci.f6073a6", + "maci-domainobjs": "0.0.0-ci.f6073a6", "next": "^14.0.4", "next-themes": "^0.2.1", "nprogress": "^0.2.0", diff --git a/packages/nextjs/types/poll.ts b/packages/nextjs/types/poll.ts index c295160..0a7bb34 100644 --- a/packages/nextjs/types/poll.ts +++ b/packages/nextjs/types/poll.ts @@ -33,3 +33,11 @@ export enum PollType { MULTIPLE_VOTE, WEIGHTED_MULTIPLE_VOTE, } + +/** + * Supported verification key modes + */ +export enum EMode { + QV, + NON_QV, +} diff --git a/yarn.lock b/yarn.lock index 4ac6454..c1732e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,6 +19,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:1.10.1": + version: 1.10.1 + resolution: "@adraffy/ens-normalize@npm:1.10.1" + checksum: 0836f394ea256972ec19a0b5e78cb7f5bcdfd48d8a32c7478afc94dd53ae44c04d1aa2303d7f3077b4f3ac2323b1f557ab9188e8059978748fdcd83e04a80dcc + languageName: node + linkType: hard + "@adraffy/ens-normalize@npm:1.9.4": version: 1.9.4 resolution: "@adraffy/ens-normalize@npm:1.9.4" @@ -943,6 +950,16 @@ __metadata: languageName: node linkType: hard +"@iden3/binfileutils@npm:0.0.12": + version: 0.0.12 + resolution: "@iden3/binfileutils@npm:0.0.12" + dependencies: + fastfile: 0.0.20 + ffjavascript: ^0.3.0 + checksum: aa7a7ad3de0c0abc290c484967d7145d0722287279724bd792fd51ae534284dbc1be10e1f155e7a9243a94a3f816cb68de02bfaaa6780f915ca6c7954d666242 + languageName: node + linkType: hard + "@ioredis/commands@npm:^1.1.1": version: 1.2.0 resolution: "@ioredis/commands@npm:1.2.0" @@ -1320,6 +1337,85 @@ __metadata: languageName: node linkType: hard +"@nomicfoundation/edr-darwin-arm64@npm:0.3.7": + version: 0.3.7 + resolution: "@nomicfoundation/edr-darwin-arm64@npm:0.3.7" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@nomicfoundation/edr-darwin-x64@npm:0.3.7": + version: 0.3.7 + resolution: "@nomicfoundation/edr-darwin-x64@npm:0.3.7" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@nomicfoundation/edr-linux-arm64-gnu@npm:0.3.7": + version: 0.3.7 + resolution: "@nomicfoundation/edr-linux-arm64-gnu@npm:0.3.7" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@nomicfoundation/edr-linux-arm64-musl@npm:0.3.7": + version: 0.3.7 + resolution: "@nomicfoundation/edr-linux-arm64-musl@npm:0.3.7" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@nomicfoundation/edr-linux-x64-gnu@npm:0.3.7": + version: 0.3.7 + resolution: "@nomicfoundation/edr-linux-x64-gnu@npm:0.3.7" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@nomicfoundation/edr-linux-x64-musl@npm:0.3.7": + version: 0.3.7 + resolution: "@nomicfoundation/edr-linux-x64-musl@npm:0.3.7" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@nomicfoundation/edr-win32-x64-msvc@npm:0.3.7": + version: 0.3.7 + resolution: "@nomicfoundation/edr-win32-x64-msvc@npm:0.3.7" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@nomicfoundation/edr@npm:^0.3.5": + version: 0.3.7 + resolution: "@nomicfoundation/edr@npm:0.3.7" + dependencies: + "@nomicfoundation/edr-darwin-arm64": 0.3.7 + "@nomicfoundation/edr-darwin-x64": 0.3.7 + "@nomicfoundation/edr-linux-arm64-gnu": 0.3.7 + "@nomicfoundation/edr-linux-arm64-musl": 0.3.7 + "@nomicfoundation/edr-linux-x64-gnu": 0.3.7 + "@nomicfoundation/edr-linux-x64-musl": 0.3.7 + "@nomicfoundation/edr-win32-x64-msvc": 0.3.7 + dependenciesMeta: + "@nomicfoundation/edr-darwin-arm64": + optional: true + "@nomicfoundation/edr-darwin-x64": + optional: true + "@nomicfoundation/edr-linux-arm64-gnu": + optional: true + "@nomicfoundation/edr-linux-arm64-musl": + optional: true + "@nomicfoundation/edr-linux-x64-gnu": + optional: true + "@nomicfoundation/edr-linux-x64-musl": + optional: true + "@nomicfoundation/edr-win32-x64-msvc": + optional: true + checksum: bad2d4c916c01dc219086def3a3c29d17834f5eb3a8898f3a79e4e4270c013c2417ff7d2eea1401bf6d1e436fcdbf5d7e4db6a2fdf76ed2495f6f900ab3e0273 + languageName: node + linkType: hard + "@nomicfoundation/ethereumjs-block@npm:5.0.2": version: 5.0.2 resolution: "@nomicfoundation/ethereumjs-block@npm:5.0.2" @@ -1366,6 +1462,15 @@ __metadata: languageName: node linkType: hard +"@nomicfoundation/ethereumjs-common@npm:4.0.4": + version: 4.0.4 + resolution: "@nomicfoundation/ethereumjs-common@npm:4.0.4" + dependencies: + "@nomicfoundation/ethereumjs-util": 9.0.4 + checksum: ce3f6e4ae15b976efdb7ccda27e19aadb62b5ffee209f9503e68b4fd8633715d4d697c0cc10ccd35f5e4e977edd05100d0f214e28880ec64fff77341dc34fcdf + languageName: node + linkType: hard + "@nomicfoundation/ethereumjs-ethash@npm:3.0.2": version: 3.0.2 resolution: "@nomicfoundation/ethereumjs-ethash@npm:3.0.2" @@ -1405,6 +1510,15 @@ __metadata: languageName: node linkType: hard +"@nomicfoundation/ethereumjs-rlp@npm:5.0.4": + version: 5.0.4 + resolution: "@nomicfoundation/ethereumjs-rlp@npm:5.0.4" + bin: + rlp: bin/rlp.cjs + checksum: ee2c2e5776c73801dc5ed636f4988b599b4563c2d0037da542ea57eb237c69dd1ac555f6bcb5e06f70515b6459779ba0d68252a6e105132b4659ab4bf62919b0 + languageName: node + linkType: hard + "@nomicfoundation/ethereumjs-statemanager@npm:2.0.2": version: 2.0.2 resolution: "@nomicfoundation/ethereumjs-statemanager@npm:2.0.2" @@ -1446,6 +1560,23 @@ __metadata: languageName: node linkType: hard +"@nomicfoundation/ethereumjs-tx@npm:5.0.4": + version: 5.0.4 + resolution: "@nomicfoundation/ethereumjs-tx@npm:5.0.4" + dependencies: + "@nomicfoundation/ethereumjs-common": 4.0.4 + "@nomicfoundation/ethereumjs-rlp": 5.0.4 + "@nomicfoundation/ethereumjs-util": 9.0.4 + ethereum-cryptography: 0.1.3 + peerDependencies: + c-kzg: ^2.1.2 + peerDependenciesMeta: + c-kzg: + optional: true + checksum: 0f1c87716682ccbcf4d92ffc6cf8ab557e658b90319d82be3219a091a736859f8803c73c98e4863682e3e86d264751c472d33ff6d3c3daf4e75b5f01d0af8fa3 + languageName: node + linkType: hard + "@nomicfoundation/ethereumjs-util@npm:9.0.2": version: 9.0.2 resolution: "@nomicfoundation/ethereumjs-util@npm:9.0.2" @@ -1457,6 +1588,21 @@ __metadata: languageName: node linkType: hard +"@nomicfoundation/ethereumjs-util@npm:9.0.4": + version: 9.0.4 + resolution: "@nomicfoundation/ethereumjs-util@npm:9.0.4" + dependencies: + "@nomicfoundation/ethereumjs-rlp": 5.0.4 + ethereum-cryptography: 0.1.3 + peerDependencies: + c-kzg: ^2.1.2 + peerDependenciesMeta: + c-kzg: + optional: true + checksum: 754439f72b11cad2d8986707ad020077dcc763c4055f73e2668a0b4cadb22aa4407faa9b3c587d9eb5b97ac337afbe037eb642bc1d5a16197284f83db3462cbe + languageName: node + linkType: hard + "@nomicfoundation/ethereumjs-vm@npm:7.0.2": version: 7.0.2 resolution: "@nomicfoundation/ethereumjs-vm@npm:7.0.2" @@ -1495,7 +1641,7 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/hardhat-ethers@npm:~3.0.5": +"@nomicfoundation/hardhat-ethers@npm:^3.0.5, @nomicfoundation/hardhat-ethers@npm:~3.0.5": version: 3.0.5 resolution: "@nomicfoundation/hardhat-ethers@npm:3.0.5" dependencies: @@ -1519,6 +1665,32 @@ __metadata: languageName: node linkType: hard +"@nomicfoundation/hardhat-toolbox@npm:^5.0.0": + version: 5.0.0 + resolution: "@nomicfoundation/hardhat-toolbox@npm:5.0.0" + peerDependencies: + "@nomicfoundation/hardhat-chai-matchers": ^2.0.0 + "@nomicfoundation/hardhat-ethers": ^3.0.0 + "@nomicfoundation/hardhat-ignition-ethers": ^0.15.0 + "@nomicfoundation/hardhat-network-helpers": ^1.0.0 + "@nomicfoundation/hardhat-verify": ^2.0.0 + "@typechain/ethers-v6": ^0.5.0 + "@typechain/hardhat": ^9.0.0 + "@types/chai": ^4.2.0 + "@types/mocha": ">=9.1.0" + "@types/node": ">=18.0.0" + chai: ^4.2.0 + ethers: ^6.4.0 + hardhat: ^2.11.0 + hardhat-gas-reporter: ^1.0.8 + solidity-coverage: ^0.8.1 + ts-node: ">=8.0.0" + typechain: ^8.3.0 + typescript: ">=4.5.0" + checksum: 18890eaf1cc130afb7dc83ea48cb6ef23c499eb5d28c3fbb36e706082383a320118ee6d4491ede64acf684d2f1ffa117cf84ad80d8ebde9fa52a443f8780a898 + languageName: node + linkType: hard + "@nomicfoundation/hardhat-verify@npm:~2.0.3": version: 2.0.5 resolution: "@nomicfoundation/hardhat-verify@npm:2.0.5" @@ -1656,10 +1828,10 @@ __metadata: languageName: node linkType: hard -"@openzeppelin/contracts@npm:~4.8.1": - version: 4.8.3 - resolution: "@openzeppelin/contracts@npm:4.8.3" - checksum: aea130d38d46840c5cbe3adbaa9a7ac645e4bd66ad3f3baf2fa78588c408d1a686170b3408c9e2e5e05530fba22ecdc00d7efb6b27852a8b29f91accbc0af255 +"@openzeppelin/contracts@npm:^5.0.2": + version: 5.0.2 + resolution: "@openzeppelin/contracts@npm:5.0.2" + checksum: 0cce6fc284bd1d89e2a447027832a62f1356b44ee31088899453e10349a63a62df2f07da63d76e4c41aad9c86b96b650b2b6fc85439ef276850dda1170a047fd languageName: node linkType: hard @@ -1950,7 +2122,6 @@ __metadata: "@nomicfoundation/hardhat-ethers": ~3.0.5 "@nomicfoundation/hardhat-network-helpers": ~1.0.6 "@nomicfoundation/hardhat-verify": ~2.0.3 - "@openzeppelin/contracts": ~4.8.1 "@typechain/ethers-v5": ~10.1.0 "@typechain/ethers-v6": ~0.5.1 "@typechain/hardhat": ~9.1.0 @@ -1980,6 +2151,11 @@ __metadata: hardhat-deploy: ^0.12.2 hardhat-deploy-ethers: ~0.4.1 hardhat-gas-reporter: ~1.0.9 + maci-circuits: 0.0.0-ci.f6073a6 + maci-cli: 0.0.0-ci.f6073a6 + maci-contracts: 0.0.0-ci.f6073a6 + maci-crypto: 0.0.0-ci.f6073a6 + maci-domainobjs: 0.0.0-ci.f6073a6 prettier: ~2.8.4 prompt: ^1.3.0 qrcode: ~1.5.1 @@ -2020,6 +2196,9 @@ __metadata: eslint-config-next: ^14.0.4 eslint-config-prettier: ^8.5.0 eslint-plugin-prettier: ^4.2.1 + maci-contracts: 0.0.0-ci.f6073a6 + maci-crypto: 0.0.0-ci.f6073a6 + maci-domainobjs: 0.0.0-ci.f6073a6 next: ^14.0.4 next-themes: ^0.2.1 nprogress: ^0.2.0 @@ -3987,7 +4166,7 @@ __metadata: languageName: node linkType: hard -"@zk-kit/baby-jubjub@npm:0.3.0": +"@zk-kit/baby-jubjub@npm:0.3.0, @zk-kit/baby-jubjub@npm:^0.3.0": version: 0.3.0 resolution: "@zk-kit/baby-jubjub@npm:0.3.0" dependencies: @@ -4016,6 +4195,15 @@ __metadata: languageName: node linkType: hard +"@zk-kit/imt.sol@npm:2.0.0-beta.12": + version: 2.0.0-beta.12 + resolution: "@zk-kit/imt.sol@npm:2.0.0-beta.12" + dependencies: + poseidon-solidity: 0.0.5 + checksum: dc116c95ba05db90e77563c203e071afb883d24844fef8d7854bfd365421ec6f5663afddbc929b4b81afa1241ca68139be88356367198bd0a43fed5886a2f4d8 + languageName: node + linkType: hard + "@zk-kit/poseidon-cipher@npm:^0.3.0": version: 0.3.0 resolution: "@zk-kit/poseidon-cipher@npm:0.3.0" @@ -4484,6 +4672,16 @@ __metadata: languageName: node linkType: hard +"array-buffer-byte-length@npm:^1.0.1": + version: 1.0.1 + resolution: "array-buffer-byte-length@npm:1.0.1" + dependencies: + call-bind: ^1.0.5 + is-array-buffer: ^3.0.4 + checksum: 53524e08f40867f6a9f35318fafe467c32e45e9c682ba67b11943e167344d2febc0f6977a17e699b05699e805c3e8f073d876f8bbf1b559ed494ad2cd0fae09e + languageName: node + linkType: hard + "array-includes@npm:^3.1.6": version: 3.1.7 resolution: "array-includes@npm:3.1.7" @@ -4511,6 +4709,20 @@ __metadata: languageName: node linkType: hard +"array.prototype.findlast@npm:^1.2.2": + version: 1.2.5 + resolution: "array.prototype.findlast@npm:1.2.5" + dependencies: + call-bind: ^1.0.7 + define-properties: ^1.2.1 + es-abstract: ^1.23.2 + es-errors: ^1.3.0 + es-object-atoms: ^1.0.0 + es-shim-unscopables: ^1.0.2 + checksum: 83ce4ad95bae07f136d316f5a7c3a5b911ac3296c3476abe60225bc4a17938bf37541972fcc37dd5adbc99cbb9c928c70bbbfc1c1ce549d41a415144030bb446 + languageName: node + linkType: hard + "array.prototype.findlastindex@npm:^1.2.2": version: 1.2.3 resolution: "array.prototype.findlastindex@npm:1.2.3" @@ -4589,6 +4801,22 @@ __metadata: languageName: node linkType: hard +"arraybuffer.prototype.slice@npm:^1.0.3": + version: 1.0.3 + resolution: "arraybuffer.prototype.slice@npm:1.0.3" + dependencies: + array-buffer-byte-length: ^1.0.1 + call-bind: ^1.0.5 + define-properties: ^1.2.1 + es-abstract: ^1.22.3 + es-errors: ^1.2.1 + get-intrinsic: ^1.2.3 + is-array-buffer: ^3.0.4 + is-shared-array-buffer: ^1.0.2 + checksum: 352259cba534dcdd969c92ab002efd2ba5025b2e3b9bead3973150edbdf0696c629d7f4b3f061c5931511e8207bdc2306da614703c820b45dabce39e3daf7e3e + languageName: node + linkType: hard + "asap@npm:~2.0.6": version: 2.0.6 resolution: "asap@npm:2.0.6" @@ -4755,6 +4983,15 @@ __metadata: languageName: node linkType: hard +"available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: ^1.0.0 + checksum: 1aa3ffbfe6578276996de660848b6e95669d9a95ad149e3dd0c0cda77db6ee1dbd9d1dd723b65b6d277b882dd0c4b91a654ae9d3cf9e1254b7e93e4908d78fd3 + languageName: node + linkType: hard + "aws-sign2@npm:~0.7.0": version: 0.7.0 resolution: "aws-sign2@npm:0.7.0" @@ -5235,6 +5472,19 @@ __metadata: languageName: node linkType: hard +"call-bind@npm:^1.0.5, call-bind@npm:^1.0.6, call-bind@npm:^1.0.7": + version: 1.0.7 + resolution: "call-bind@npm:1.0.7" + dependencies: + es-define-property: ^1.0.0 + es-errors: ^1.3.0 + function-bind: ^1.1.2 + get-intrinsic: ^1.2.4 + set-function-length: ^1.2.1 + checksum: 295c0c62b90dd6522e6db3b0ab1ce26bdf9e7404215bda13cfee25b626b5ff1a7761324d58d38b1ef1607fc65aca2d06e44d2e18d0dfc6c14b465b00d8660029 + languageName: node + linkType: hard + "callsites@npm:^3.0.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" @@ -5524,6 +5774,17 @@ __metadata: languageName: node linkType: hard +"circom_runtime@npm:0.1.25": + version: 0.1.25 + resolution: "circom_runtime@npm:0.1.25" + dependencies: + ffjavascript: 0.3.0 + bin: + calcwit: calcwit.js + checksum: db2c786ba5bfe749c2824e8f36567d202a3a4c838a1940711b009fd63ca9fb178bd96888817becbd59e240dbdeb6dff83e673b93a91c4fa49d67b2dfdd53b1eb + languageName: node + linkType: hard + "circom_tester@npm:^0.0.19": version: 0.0.19 resolution: "circom_tester@npm:0.0.19" @@ -5554,6 +5815,20 @@ __metadata: languageName: node linkType: hard +"circomkit@npm:^0.1.0": + version: 0.1.0 + resolution: "circomkit@npm:0.1.0" + dependencies: + chai: ^4.3.7 + circom_tester: ^0.0.19 + loglevel: ^1.8.1 + snarkjs: ^0.7.0 + bin: + circomkit: dist/bin/index.js + checksum: 1681962d7147712a1b8ef6d802304d35bbe8fb2938920f37b46a92024e23f0af2c07f2b4b8fd104d1461922f51e9005278575451dcf3efb0d476aaefb2fe1ae0 + languageName: node + linkType: hard + "circomlib@npm:^2.0.5": version: 2.0.5 resolution: "circomlib@npm:2.0.5" @@ -5847,6 +6122,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^12.0.0": + version: 12.0.0 + resolution: "commander@npm:12.0.0" + checksum: bce9e243dc008baba6b8d923f95b251ad115e6e7551a15838d7568abebcca0fc832da1800cf37caf37852f35ce4b7fb794ba7a4824b88c5adb1395f9268642df + languageName: node + linkType: hard + "commander@npm:^2.20.3": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -6104,6 +6386,39 @@ __metadata: languageName: node linkType: hard +"data-view-buffer@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-buffer@npm:1.0.1" + dependencies: + call-bind: ^1.0.6 + es-errors: ^1.3.0 + is-data-view: ^1.0.1 + checksum: ce24348f3c6231223b216da92e7e6a57a12b4af81a23f27eff8feabdf06acfb16c00639c8b705ca4d167f761cfc756e27e5f065d0a1f840c10b907fdaf8b988c + languageName: node + linkType: hard + +"data-view-byte-length@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-byte-length@npm:1.0.1" + dependencies: + call-bind: ^1.0.7 + es-errors: ^1.3.0 + is-data-view: ^1.0.1 + checksum: dbb3200edcb7c1ef0d68979834f81d64fd8cab2f7691b3a4c6b97e67f22182f3ec2c8602efd7b76997b55af6ff8bce485829c1feda4fa2165a6b71fb7baa4269 + languageName: node + linkType: hard + +"data-view-byte-offset@npm:^1.0.0": + version: 1.0.0 + resolution: "data-view-byte-offset@npm:1.0.0" + dependencies: + call-bind: ^1.0.6 + es-errors: ^1.3.0 + is-data-view: ^1.0.1 + checksum: 7f0bf8720b7414ca719eedf1846aeec392f2054d7af707c5dc9a753cc77eb8625f067fa901e0b5127e831f9da9056138d894b9c2be79c27a21f6db5824f009c2 + languageName: node + linkType: hard + "death@npm:^1.1.0": version: 1.1.0 resolution: "death@npm:1.1.0" @@ -6226,6 +6541,17 @@ __metadata: languageName: node linkType: hard +"define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: ^1.0.0 + es-errors: ^1.3.0 + gopd: ^1.0.1 + checksum: 8068ee6cab694d409ac25936eb861eea704b7763f7f342adbdfe337fc27c78d7ae0eff2364b2917b58c508d723c7a074326d068eef2e45c4edcd85cf94d0313b + languageName: node + linkType: hard + "define-properties@npm:^1.1.2, define-properties@npm:^1.1.3, define-properties@npm:^1.1.4, define-properties@npm:^1.2.0, define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" @@ -6408,6 +6734,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.4.5": + version: 16.4.5 + resolution: "dotenv@npm:16.4.5" + checksum: 301a12c3d44fd49888b74eb9ccf9f07a1f5df43f489e7fcb89647a2edcd84c42d6bc349dc8df099cd18f07c35c7b04685c1a4f3e6a6a9e6b30f8d48c15b7f49c + languageName: node + linkType: hard + "dotenv@npm:~16.0.3": version: 16.0.3 resolution: "dotenv@npm:16.0.3" @@ -6641,6 +6974,60 @@ __metadata: languageName: node linkType: hard +"es-abstract@npm:^1.22.3, es-abstract@npm:^1.23.0, es-abstract@npm:^1.23.2": + version: 1.23.3 + resolution: "es-abstract@npm:1.23.3" + dependencies: + array-buffer-byte-length: ^1.0.1 + arraybuffer.prototype.slice: ^1.0.3 + available-typed-arrays: ^1.0.7 + call-bind: ^1.0.7 + data-view-buffer: ^1.0.1 + data-view-byte-length: ^1.0.1 + data-view-byte-offset: ^1.0.0 + es-define-property: ^1.0.0 + es-errors: ^1.3.0 + es-object-atoms: ^1.0.0 + es-set-tostringtag: ^2.0.3 + es-to-primitive: ^1.2.1 + function.prototype.name: ^1.1.6 + get-intrinsic: ^1.2.4 + get-symbol-description: ^1.0.2 + globalthis: ^1.0.3 + gopd: ^1.0.1 + has-property-descriptors: ^1.0.2 + has-proto: ^1.0.3 + has-symbols: ^1.0.3 + hasown: ^2.0.2 + internal-slot: ^1.0.7 + is-array-buffer: ^3.0.4 + is-callable: ^1.2.7 + is-data-view: ^1.0.1 + is-negative-zero: ^2.0.3 + is-regex: ^1.1.4 + is-shared-array-buffer: ^1.0.3 + is-string: ^1.0.7 + is-typed-array: ^1.1.13 + is-weakref: ^1.0.2 + object-inspect: ^1.13.1 + object-keys: ^1.1.1 + object.assign: ^4.1.5 + regexp.prototype.flags: ^1.5.2 + safe-array-concat: ^1.1.2 + safe-regex-test: ^1.0.3 + string.prototype.trim: ^1.2.9 + string.prototype.trimend: ^1.0.8 + string.prototype.trimstart: ^1.0.8 + typed-array-buffer: ^1.0.2 + typed-array-byte-length: ^1.0.1 + typed-array-byte-offset: ^1.0.2 + typed-array-length: ^1.0.6 + unbox-primitive: ^1.0.2 + which-typed-array: ^1.1.15 + checksum: f840cf161224252512f9527306b57117192696571e07920f777cb893454e32999206198b4f075516112af6459daca282826d1735c450528470356d09eff3a9ae + languageName: node + linkType: hard + "es-array-method-boxes-properly@npm:^1.0.0": version: 1.0.0 resolution: "es-array-method-boxes-properly@npm:1.0.0" @@ -6648,6 +7035,22 @@ __metadata: languageName: node linkType: hard +"es-define-property@npm:^1.0.0": + version: 1.0.0 + resolution: "es-define-property@npm:1.0.0" + dependencies: + get-intrinsic: ^1.2.4 + checksum: f66ece0a887b6dca71848fa71f70461357c0e4e7249696f81bad0a1f347eed7b31262af4a29f5d726dc026426f085483b6b90301855e647aa8e21936f07293c6 + languageName: node + linkType: hard + +"es-errors@npm:^1.2.1, es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: ec1414527a0ccacd7f15f4a3bc66e215f04f595ba23ca75cdae0927af099b5ec865f9f4d33e9d7e86f512f252876ac77d4281a7871531a50678132429b1271b5 + languageName: node + linkType: hard + "es-iterator-helpers@npm:^1.0.12": version: 1.0.15 resolution: "es-iterator-helpers@npm:1.0.15" @@ -6670,6 +7073,15 @@ __metadata: languageName: node linkType: hard +"es-object-atoms@npm:^1.0.0": + version: 1.0.0 + resolution: "es-object-atoms@npm:1.0.0" + dependencies: + es-errors: ^1.3.0 + checksum: 26f0ff78ab93b63394e8403c353842b2272836968de4eafe97656adfb8a7c84b9099bf0fe96ed58f4a4cddc860f6e34c77f91649a58a5daa4a9c40b902744e3c + languageName: node + linkType: hard + "es-set-tostringtag@npm:^2.0.1": version: 2.0.1 resolution: "es-set-tostringtag@npm:2.0.1" @@ -6681,6 +7093,17 @@ __metadata: languageName: node linkType: hard +"es-set-tostringtag@npm:^2.0.3": + version: 2.0.3 + resolution: "es-set-tostringtag@npm:2.0.3" + dependencies: + get-intrinsic: ^1.2.4 + has-tostringtag: ^1.0.2 + hasown: ^2.0.1 + checksum: 7227fa48a41c0ce83e0377b11130d324ac797390688135b8da5c28994c0165be8b252e15cd1de41e1325e5a5412511586960213e88f9ab4a5e7d028895db5129 + languageName: node + linkType: hard + "es-shim-unscopables@npm:^1.0.0": version: 1.0.0 resolution: "es-shim-unscopables@npm:1.0.0" @@ -6690,6 +7113,15 @@ __metadata: languageName: node linkType: hard +"es-shim-unscopables@npm:^1.0.2": + version: 1.0.2 + resolution: "es-shim-unscopables@npm:1.0.2" + dependencies: + hasown: ^2.0.0 + checksum: 432bd527c62065da09ed1d37a3f8e623c423683285e6188108286f4a1e8e164a5bcbfbc0051557c7d14633cd2a41ce24c7048e6bbb66a985413fd32f1be72626 + languageName: node + linkType: hard + "es-to-primitive@npm:^1.2.1": version: 1.2.1 resolution: "es-to-primitive@npm:1.2.1" @@ -7648,6 +8080,21 @@ __metadata: languageName: node linkType: hard +"ethers@npm:^6.12.0": + version: 6.12.1 + resolution: "ethers@npm:6.12.1" + dependencies: + "@adraffy/ens-normalize": 1.10.1 + "@noble/curves": 1.2.0 + "@noble/hashes": 1.3.2 + "@types/node": 18.15.13 + aes-js: 4.0.0-beta.5 + tslib: 2.4.0 + ws: 8.5.0 + checksum: ddf398c91f584b9e643740ec17a9c82b4a1c4ea3fb6efd00f1a043b89d1ec6f9427aa80894f75850ee805722e91b8d054bce18579a2c621226302c096774df90 + languageName: node + linkType: hard + "ethers@npm:~6.10.0": version: 6.10.0 resolution: "ethers@npm:6.10.0" @@ -7943,6 +8390,17 @@ __metadata: languageName: node linkType: hard +"ffjavascript@npm:0.3.0, ffjavascript@npm:^0.3.0": + version: 0.3.0 + resolution: "ffjavascript@npm:0.3.0" + dependencies: + wasmbuilder: 0.0.16 + wasmcurves: 0.2.2 + web-worker: 1.2.0 + checksum: eb1c3ee979299d3b670f371336e1a65ec5319a085da3a896465218283a079453b164e340483a5d66588227d8f1e8cf2ddb890d556bb21931a6f5b70d06f3d894 + languageName: node + linkType: hard + "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" @@ -8340,6 +8798,13 @@ __metadata: languageName: node linkType: hard +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 2b0ff4ce708d99715ad14a6d1f894e2a83242e4a52ccfcefaee5e40050562e5f6dafc1adbb4ce2d4ab47279a45dc736ab91ea5042d843c3c092820dfe032efb1 + languageName: node + linkType: hard + "function.prototype.name@npm:^1.1.5, function.prototype.name@npm:^1.1.6": version: 1.1.6 resolution: "function.prototype.name@npm:1.1.6" @@ -8432,6 +8897,19 @@ __metadata: languageName: node linkType: hard +"get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4": + version: 1.2.4 + resolution: "get-intrinsic@npm:1.2.4" + dependencies: + es-errors: ^1.3.0 + function-bind: ^1.1.2 + has-proto: ^1.0.1 + has-symbols: ^1.0.3 + hasown: ^2.0.0 + checksum: 414e3cdf2c203d1b9d7d33111df746a4512a1aa622770b361dadddf8ed0b5aeb26c560f49ca077e24bfafb0acb55ca908d1f709216ccba33ffc548ec8a79a951 + languageName: node + linkType: hard + "get-nonce@npm:^1.0.0": version: 1.0.1 resolution: "get-nonce@npm:1.0.1" @@ -8479,6 +8957,17 @@ __metadata: languageName: node linkType: hard +"get-symbol-description@npm:^1.0.2": + version: 1.0.2 + resolution: "get-symbol-description@npm:1.0.2" + dependencies: + call-bind: ^1.0.5 + es-errors: ^1.3.0 + get-intrinsic: ^1.2.4 + checksum: e1cb53bc211f9dbe9691a4f97a46837a553c4e7caadd0488dc24ac694db8a390b93edd412b48dcdd0b4bbb4c595de1709effc75fc87c0839deedc6968f5bd973 + languageName: node + linkType: hard + "get-tsconfig@npm:^4.5.0": version: 4.7.2 resolution: "get-tsconfig@npm:4.7.2" @@ -8791,7 +9280,7 @@ __metadata: languageName: node linkType: hard -"handlebars@npm:^4.0.1": +"handlebars@npm:^4.0.1, handlebars@npm:^4.7.7": version: 4.7.8 resolution: "handlebars@npm:4.7.8" dependencies: @@ -8891,6 +9380,67 @@ __metadata: languageName: node linkType: hard +"hardhat@npm:^2.22.3": + version: 2.22.3 + resolution: "hardhat@npm:2.22.3" + dependencies: + "@ethersproject/abi": ^5.1.2 + "@metamask/eth-sig-util": ^4.0.0 + "@nomicfoundation/edr": ^0.3.5 + "@nomicfoundation/ethereumjs-common": 4.0.4 + "@nomicfoundation/ethereumjs-tx": 5.0.4 + "@nomicfoundation/ethereumjs-util": 9.0.4 + "@nomicfoundation/solidity-analyzer": ^0.1.0 + "@sentry/node": ^5.18.1 + "@types/bn.js": ^5.1.0 + "@types/lru-cache": ^5.1.0 + adm-zip: ^0.4.16 + aggregate-error: ^3.0.0 + ansi-escapes: ^4.3.0 + boxen: ^5.1.2 + chalk: ^2.4.2 + chokidar: ^3.4.0 + ci-info: ^2.0.0 + debug: ^4.1.1 + enquirer: ^2.3.0 + env-paths: ^2.2.0 + ethereum-cryptography: ^1.0.3 + ethereumjs-abi: ^0.6.8 + find-up: ^2.1.0 + fp-ts: 1.19.3 + fs-extra: ^7.0.1 + glob: 7.2.0 + immutable: ^4.0.0-rc.12 + io-ts: 1.10.4 + keccak: ^3.0.2 + lodash: ^4.17.11 + mnemonist: ^0.38.0 + mocha: ^10.0.0 + p-map: ^4.0.0 + raw-body: ^2.4.1 + resolve: 1.17.0 + semver: ^6.3.0 + solc: 0.7.3 + source-map-support: ^0.5.13 + stacktrace-parser: ^0.1.10 + tsort: 0.0.1 + undici: ^5.14.0 + uuid: ^8.3.2 + ws: ^7.4.6 + peerDependencies: + ts-node: "*" + typescript: "*" + peerDependenciesMeta: + ts-node: + optional: true + typescript: + optional: true + bin: + hardhat: internal/cli/bootstrap.js + checksum: 7f0b8b2b803aca1f07b1e421605f9ff0243e93fe75f04b57d45292e7fedf7a50deb2ef435c47d0dde5b14988ea0493f58852d8973096f76719b35040927dcd55 + languageName: node + linkType: hard + "hardhat@npm:~2.19.4": version: 2.19.5 resolution: "hardhat@npm:2.19.5" @@ -8995,6 +9545,15 @@ __metadata: languageName: node linkType: hard +"has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: ^1.0.0 + checksum: fcbb246ea2838058be39887935231c6d5788babed499d0e9d0cc5737494c48aba4fe17ba1449e0d0fbbb1e36175442faa37f9c427ae357d6ccb1d895fbcd3de3 + languageName: node + linkType: hard + "has-proto@npm:^1.0.1": version: 1.0.1 resolution: "has-proto@npm:1.0.1" @@ -9002,6 +9561,13 @@ __metadata: languageName: node linkType: hard +"has-proto@npm:^1.0.3": + version: 1.0.3 + resolution: "has-proto@npm:1.0.3" + checksum: fe7c3d50b33f50f3933a04413ed1f69441d21d2d2944f81036276d30635cad9279f6b43bc8f32036c31ebdfcf6e731150f46c1907ad90c669ffe9b066c3ba5c4 + languageName: node + linkType: hard + "has-symbols@npm:^1.0.0, has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": version: 1.0.3 resolution: "has-symbols@npm:1.0.3" @@ -9018,6 +9584,15 @@ __metadata: languageName: node linkType: hard +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: ^1.0.3 + checksum: 999d60bb753ad714356b2c6c87b7fb74f32463b8426e159397da4bde5bca7e598ab1073f4d8d4deafac297f2eb311484cd177af242776bf05f0d11565680468d + languageName: node + linkType: hard + "has-unicode@npm:^2.0.1": version: 2.0.1 resolution: "has-unicode@npm:2.0.1" @@ -9065,6 +9640,15 @@ __metadata: languageName: node linkType: hard +"hasown@npm:^2.0.0, hasown@npm:^2.0.1, hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: ^1.1.2 + checksum: e8516f776a15149ca6c6ed2ae3110c417a00b62260e222590e54aa367cbcd6ed99122020b37b7fbdf05748df57b265e70095d7bf35a47660587619b15ffb93db + languageName: node + linkType: hard + "he@npm:1.2.0": version: 1.2.0 resolution: "he@npm:1.2.0" @@ -9367,6 +9951,17 @@ __metadata: languageName: node linkType: hard +"internal-slot@npm:^1.0.7": + version: 1.0.7 + resolution: "internal-slot@npm:1.0.7" + dependencies: + es-errors: ^1.3.0 + hasown: ^2.0.0 + side-channel: ^1.0.4 + checksum: cadc5eea5d7d9bc2342e93aae9f31f04c196afebb11bde97448327049f492cd7081e18623ae71388aac9cd237b692ca3a105be9c68ac39c1dec679d7409e33eb + languageName: node + linkType: hard + "interpret@npm:^1.0.0": version: 1.4.0 resolution: "interpret@npm:1.4.0" @@ -9444,6 +10039,16 @@ __metadata: languageName: node linkType: hard +"is-array-buffer@npm:^3.0.4": + version: 3.0.4 + resolution: "is-array-buffer@npm:3.0.4" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.2.1 + checksum: e4e3e6ef0ff2239e75371d221f74bc3c26a03564a22efb39f6bb02609b598917ddeecef4e8c877df2a25888f247a98198959842a5e73236bc7f22cabdf6351a7 + languageName: node + linkType: hard + "is-async-function@npm:^2.0.0": version: 2.0.0 resolution: "is-async-function@npm:2.0.0" @@ -9504,6 +10109,15 @@ __metadata: languageName: node linkType: hard +"is-data-view@npm:^1.0.1": + version: 1.0.1 + resolution: "is-data-view@npm:1.0.1" + dependencies: + is-typed-array: ^1.1.13 + checksum: 4ba4562ac2b2ec005fefe48269d6bd0152785458cd253c746154ffb8a8ab506a29d0cfb3b74af87513843776a88e4981ae25c89457bf640a33748eab1a7216b5 + languageName: node + linkType: hard + "is-date-object@npm:^1.0.1, is-date-object@npm:^1.0.5": version: 1.0.5 resolution: "is-date-object@npm:1.0.5" @@ -9605,6 +10219,13 @@ __metadata: languageName: node linkType: hard +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: c1e6b23d2070c0539d7b36022d5a94407132411d01aba39ec549af824231f3804b1aea90b5e4e58e807a65d23ceb538ed6e355ce76b267bdd86edb757ffcbdcd + languageName: node + linkType: hard + "is-number-object@npm:^1.0.4": version: 1.0.7 resolution: "is-number-object@npm:1.0.7" @@ -9661,6 +10282,15 @@ __metadata: languageName: node linkType: hard +"is-shared-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "is-shared-array-buffer@npm:1.0.3" + dependencies: + call-bind: ^1.0.7 + checksum: a4fff602c309e64ccaa83b859255a43bb011145a42d3f56f67d9268b55bc7e6d98a5981a1d834186ad3105d6739d21547083fe7259c76c0468483fc538e716d8 + languageName: node + linkType: hard + "is-stream@npm:^2.0.0": version: 2.0.1 resolution: "is-stream@npm:2.0.1" @@ -9702,6 +10332,15 @@ __metadata: languageName: node linkType: hard +"is-typed-array@npm:^1.1.13": + version: 1.1.13 + resolution: "is-typed-array@npm:1.1.13" + dependencies: + which-typed-array: ^1.1.14 + checksum: 150f9ada183a61554c91e1c4290086d2c100b0dff45f60b028519be72a8db964da403c48760723bf5253979b8dffe7b544246e0e5351dcd05c5fdb1dcc1dc0f0 + languageName: node + linkType: hard + "is-typedarray@npm:1.0.0, is-typedarray@npm:^1.0.0, is-typedarray@npm:~1.0.0": version: 1.0.0 resolution: "is-typedarray@npm:1.0.0" @@ -10642,6 +11281,96 @@ __metadata: languageName: node linkType: hard +"maci-circuits@npm:0.0.0-ci.f6073a6": + version: 0.0.0-ci.f6073a6 + resolution: "maci-circuits@npm:0.0.0-ci.f6073a6" + dependencies: + "@zk-kit/circuits": ^0.4.0 + circomkit: ^0.1.0 + circomlib: ^2.0.5 + maci-core: 0.0.0-ci.f6073a6 + maci-crypto: 0.0.0-ci.f6073a6 + maci-domainobjs: 0.0.0-ci.f6073a6 + snarkjs: ^0.7.4 + checksum: 3a48bda939d754db32a38036797ce90c5a7fc794dd48e65a654aa0cc3155fb6e102995303b92fadec5e0063651f1baeb253618b6e75481f19f692648a843fb69 + languageName: node + linkType: hard + +"maci-cli@npm:0.0.0-ci.f6073a6": + version: 0.0.0-ci.f6073a6 + resolution: "maci-cli@npm:0.0.0-ci.f6073a6" + dependencies: + "@commander-js/extra-typings": ^12.0.1 + "@nomicfoundation/hardhat-toolbox": ^5.0.0 + commander: ^12.0.0 + dotenv: ^16.4.5 + ethers: ^6.12.0 + hardhat: ^2.22.3 + maci-circuits: 0.0.0-ci.f6073a6 + maci-contracts: 0.0.0-ci.f6073a6 + maci-core: 0.0.0-ci.f6073a6 + maci-crypto: 0.0.0-ci.f6073a6 + maci-domainobjs: 0.0.0-ci.f6073a6 + prompt: ^1.3.0 + bin: + maci-cli: build/ts/index.js + checksum: 90f1497acb2aee3da9e1e395d027a896043a542021f1712e933581c45ad92d419a48bbda541ffa51e75bac7b486b29aa5b14e7b3f5cb88b369da4276a546033a + languageName: node + linkType: hard + +"maci-contracts@npm:0.0.0-ci.f6073a6": + version: 0.0.0-ci.f6073a6 + resolution: "maci-contracts@npm:0.0.0-ci.f6073a6" + dependencies: + "@nomicfoundation/hardhat-ethers": ^3.0.5 + "@nomicfoundation/hardhat-toolbox": ^5.0.0 + "@openzeppelin/contracts": ^5.0.2 + "@zk-kit/imt.sol": 2.0.0-beta.12 + circomlibjs: ^0.1.7 + ethers: ^6.12.0 + hardhat: ^2.22.3 + maci-circuits: 0.0.0-ci.f6073a6 + maci-core: 0.0.0-ci.f6073a6 + maci-crypto: 0.0.0-ci.f6073a6 + maci-domainobjs: 0.0.0-ci.f6073a6 + solidity-docgen: ^0.6.0-beta.36 + bin: + maci-contracts: build/ts/index.js + checksum: 825e1c8b62fc31641fe061f0a9fcacbbf4f3b12f85e6562cfa0eb6ba3086ed696934f10e79f96c371cec9874a32ef675c49c2a0ef33eef7b8b54451a3b32fefe + languageName: node + linkType: hard + +"maci-core@npm:0.0.0-ci.f6073a6": + version: 0.0.0-ci.f6073a6 + resolution: "maci-core@npm:0.0.0-ci.f6073a6" + dependencies: + maci-crypto: 0.0.0-ci.f6073a6 + maci-domainobjs: 0.0.0-ci.f6073a6 + checksum: 5f902acdd11a5f99e49bbe4ea1cbdf9818eb54496d56769bdf10b60e2fe5ddc3b99d992794511e656729d7bdeab95a873a513f719c3fd028faf0270dbabfbaea + languageName: node + linkType: hard + +"maci-crypto@npm:0.0.0-ci.f6073a6": + version: 0.0.0-ci.f6073a6 + resolution: "maci-crypto@npm:0.0.0-ci.f6073a6" + dependencies: + "@zk-kit/baby-jubjub": ^0.3.0 + "@zk-kit/eddsa-poseidon": ^0.11.0 + "@zk-kit/poseidon-cipher": ^0.3.0 + ethers: ^6.12.0 + checksum: 30870030e5bc0c9e7c5772435925d965bdca5a8569966a9c022e431e28009a0a04c44faeb3c76d62208093e3f87495b589aee2a58604bb0c0fe47a33614d3ed0 + languageName: node + linkType: hard + +"maci-domainobjs@npm:0.0.0-ci.f6073a6": + version: 0.0.0-ci.f6073a6 + resolution: "maci-domainobjs@npm:0.0.0-ci.f6073a6" + dependencies: + maci-crypto: 0.0.0-ci.f6073a6 + checksum: 11ee8b6636a1e63cc30ca4104d41d0f5446a84d5a4644b8991ff5e4bc2e9e793da5b3415198fb519e5f3bce82bea641bfc6114fe01260367277162260407ec2c + languageName: node + linkType: hard + "make-dir@npm:^3.1.0": version: 3.1.0 resolution: "make-dir@npm:3.1.0" @@ -11650,6 +12379,13 @@ __metadata: languageName: node linkType: hard +"object-inspect@npm:^1.13.1": + version: 1.13.1 + resolution: "object-inspect@npm:1.13.1" + checksum: 7d9fa9221de3311dcb5c7c307ee5dc011cdd31dc43624b7c184b3840514e118e05ef0002be5388304c416c0eb592feb46e983db12577fc47e47d5752fbbfb61f + languageName: node + linkType: hard + "object-keys@npm:^1.0.11, object-keys@npm:^1.1.1": version: 1.1.1 resolution: "object-keys@npm:1.1.1" @@ -11681,6 +12417,18 @@ __metadata: languageName: node linkType: hard +"object.assign@npm:^4.1.5": + version: 4.1.5 + resolution: "object.assign@npm:4.1.5" + dependencies: + call-bind: ^1.0.5 + define-properties: ^1.2.1 + has-symbols: ^1.0.3 + object-keys: ^1.1.1 + checksum: f9aeac0541661370a1fc86e6a8065eb1668d3e771f7dbb33ee54578201336c057b21ee61207a186dd42db0c62201d91aac703d20d12a79fc79c353eed44d4e25 + languageName: node + linkType: hard + "object.entries@npm:^1.1.6": version: 1.1.7 resolution: "object.entries@npm:1.1.7" @@ -12243,6 +12991,20 @@ __metadata: languageName: node linkType: hard +"poseidon-solidity@npm:0.0.5": + version: 0.0.5 + resolution: "poseidon-solidity@npm:0.0.5" + checksum: eb2d84962505a3c8a5756752c8e571ad32f90f4d95fafaffbd03552abfa040553945990d96d0243ddd993d6c2e456a816435c97739610a4f6aabb9249ebf47d7 + languageName: node + linkType: hard + +"possible-typed-array-names@npm:^1.0.0": + version: 1.0.0 + resolution: "possible-typed-array-names@npm:1.0.0" + checksum: b32d403ece71e042385cc7856385cecf1cd8e144fa74d2f1de40d1e16035dba097bc189715925e79b67bdd1472796ff168d3a90d296356c9c94d272d5b95f3ae + languageName: node + linkType: hard + "postcss-import@npm:^15.1.0": version: 15.1.0 resolution: "postcss-import@npm:15.1.0" @@ -12593,6 +13355,18 @@ __metadata: languageName: node linkType: hard +"r1csfile@npm:0.0.48": + version: 0.0.48 + resolution: "r1csfile@npm:0.0.48" + dependencies: + "@iden3/bigarray": 0.0.2 + "@iden3/binfileutils": 0.0.12 + fastfile: 0.0.20 + ffjavascript: 0.3.0 + checksum: 8929285ece5d0e8e560572e3a202e84165c5de3855cd04f221fec474b289c8bf9f3d0f734fc7303482fdc75f650e09ca0633bf5769c3367f642df93e83dbb90e + languageName: node + linkType: hard + "radix3@npm:^1.1.0": version: 1.1.0 resolution: "radix3@npm:1.1.0" @@ -12897,6 +13671,18 @@ __metadata: languageName: node linkType: hard +"regexp.prototype.flags@npm:^1.5.2": + version: 1.5.2 + resolution: "regexp.prototype.flags@npm:1.5.2" + dependencies: + call-bind: ^1.0.6 + define-properties: ^1.2.1 + es-errors: ^1.3.0 + set-function-name: ^2.0.1 + checksum: d7f333667d5c564e2d7a97c56c3075d64c722c9bb51b2b4df6822b2e8096d623a5e63088fb4c83df919b6951ef8113841de8b47de7224872fa6838bc5d8a7d64 + languageName: node + linkType: hard + "regexpp@npm:^3.2.0": version: 3.2.0 resolution: "regexpp@npm:3.2.0" @@ -13263,6 +14049,18 @@ __metadata: languageName: node linkType: hard +"safe-array-concat@npm:^1.1.2": + version: 1.1.2 + resolution: "safe-array-concat@npm:1.1.2" + dependencies: + call-bind: ^1.0.7 + get-intrinsic: ^1.2.4 + has-symbols: ^1.0.3 + isarray: ^2.0.5 + checksum: a3b259694754ddfb73ae0663829e396977b99ff21cbe8607f35a469655656da8e271753497e59da8a7575baa94d2e684bea3e10ddd74ba046c0c9b4418ffa0c4 + languageName: node + linkType: hard + "safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" @@ -13295,6 +14093,17 @@ __metadata: languageName: node linkType: hard +"safe-regex-test@npm:^1.0.3": + version: 1.0.3 + resolution: "safe-regex-test@npm:1.0.3" + dependencies: + call-bind: ^1.0.6 + es-errors: ^1.3.0 + is-regex: ^1.1.4 + checksum: 6c7d392ff1ae7a3ae85273450ed02d1d131f1d2c76e177d6b03eb88e6df8fa062639070e7d311802c1615f351f18dc58f9454501c58e28d5ffd9b8f502ba6489 + languageName: node + linkType: hard + "safe-stable-stringify@npm:^2.1.0": version: 2.4.3 resolution: "safe-stable-stringify@npm:2.4.3" @@ -13433,6 +14242,20 @@ __metadata: languageName: node linkType: hard +"set-function-length@npm:^1.2.1": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: ^1.1.4 + es-errors: ^1.3.0 + function-bind: ^1.1.2 + get-intrinsic: ^1.2.4 + gopd: ^1.0.1 + has-property-descriptors: ^1.0.2 + checksum: a8248bdacdf84cb0fab4637774d9fb3c7a8e6089866d04c817583ff48e14149c87044ce683d7f50759a8c50fb87c7a7e173535b06169c87ef76f5fb276dfff72 + languageName: node + linkType: hard + "set-function-name@npm:^2.0.0, set-function-name@npm:^2.0.1": version: 2.0.1 resolution: "set-function-name@npm:2.0.1" @@ -13630,6 +14453,26 @@ __metadata: languageName: node linkType: hard +"snarkjs@npm:^0.7.4": + version: 0.7.4 + resolution: "snarkjs@npm:0.7.4" + dependencies: + "@iden3/binfileutils": 0.0.12 + bfj: ^7.0.2 + blake2b-wasm: ^2.4.0 + circom_runtime: 0.1.25 + ejs: ^3.1.6 + fastfile: 0.0.20 + ffjavascript: 0.3.0 + js-sha3: ^0.8.0 + logplease: ^1.2.15 + r1csfile: 0.0.48 + bin: + snarkjs: build/cli.cjs + checksum: 239b12de4f837deb32ba7ca3b02ddea71d25152242016f8d789e98fc984ecf7588a2f396174e603894d3cdfa76cf0ce7f89afdb15a27115a2a7932285a7e77bd + languageName: node + linkType: hard + "socks-proxy-agent@npm:^7.0.0": version: 7.0.0 resolution: "socks-proxy-agent@npm:7.0.0" @@ -13670,6 +14513,15 @@ __metadata: languageName: node linkType: hard +"solidity-ast@npm:^0.4.38": + version: 0.4.56 + resolution: "solidity-ast@npm:0.4.56" + dependencies: + array.prototype.findlast: ^1.2.2 + checksum: 124cd54dc187860c83f4e8a3cbc41f890fbd0aaad4695356763034bdc782046eac414b161b7f354e423e075dba303d6bef213682df8932fee5d143d52135cd4e + languageName: node + linkType: hard + "solidity-coverage@npm:~0.8.5": version: 0.8.11 resolution: "solidity-coverage@npm:0.8.11" @@ -13701,6 +14553,18 @@ __metadata: languageName: node linkType: hard +"solidity-docgen@npm:^0.6.0-beta.36": + version: 0.6.0-beta.36 + resolution: "solidity-docgen@npm:0.6.0-beta.36" + dependencies: + handlebars: ^4.7.7 + solidity-ast: ^0.4.38 + peerDependencies: + hardhat: ^2.8.0 + checksum: 658204db9dc73904bf2e556015d36ca5d120c88b10ecd249f5822b75cb5ea259b039081018ad98d6d00423f0e7691c9a1bf515e640bb84fc51d0def9d80eca3a + languageName: node + linkType: hard + "sonic-boom@npm:^2.2.1": version: 2.8.0 resolution: "sonic-boom@npm:2.8.0" @@ -14004,6 +14868,18 @@ __metadata: languageName: node linkType: hard +"string.prototype.trim@npm:^1.2.9": + version: 1.2.9 + resolution: "string.prototype.trim@npm:1.2.9" + dependencies: + call-bind: ^1.0.7 + define-properties: ^1.2.1 + es-abstract: ^1.23.0 + es-object-atoms: ^1.0.0 + checksum: ea2df6ec1e914c9d4e2dc856fa08228e8b1be59b59e50b17578c94a66a176888f417264bb763d4aac638ad3b3dad56e7a03d9317086a178078d131aa293ba193 + languageName: node + linkType: hard + "string.prototype.trimend@npm:^1.0.7": version: 1.0.7 resolution: "string.prototype.trimend@npm:1.0.7" @@ -14015,6 +14891,17 @@ __metadata: languageName: node linkType: hard +"string.prototype.trimend@npm:^1.0.8": + version: 1.0.8 + resolution: "string.prototype.trimend@npm:1.0.8" + dependencies: + call-bind: ^1.0.7 + define-properties: ^1.2.1 + es-object-atoms: ^1.0.0 + checksum: cc3bd2de08d8968a28787deba9a3cb3f17ca5f9f770c91e7e8fa3e7d47f079bad70fadce16f05dda9f261788be2c6e84a942f618c3bed31e42abc5c1084f8dfd + languageName: node + linkType: hard + "string.prototype.trimstart@npm:^1.0.7": version: 1.0.7 resolution: "string.prototype.trimstart@npm:1.0.7" @@ -14026,6 +14913,17 @@ __metadata: languageName: node linkType: hard +"string.prototype.trimstart@npm:^1.0.8": + version: 1.0.8 + resolution: "string.prototype.trimstart@npm:1.0.8" + dependencies: + call-bind: ^1.0.7 + define-properties: ^1.2.1 + es-object-atoms: ^1.0.0 + checksum: df1007a7f580a49d692375d996521dc14fd103acda7f3034b3c558a60b82beeed3a64fa91e494e164581793a8ab0ae2f59578a49896a7af6583c1f20472bce96 + languageName: node + linkType: hard + "string_decoder@npm:^1.1.1": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" @@ -14836,6 +15734,17 @@ __metadata: languageName: node linkType: hard +"typed-array-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "typed-array-buffer@npm:1.0.2" + dependencies: + call-bind: ^1.0.7 + es-errors: ^1.3.0 + is-typed-array: ^1.1.13 + checksum: 02ffc185d29c6df07968272b15d5319a1610817916ec8d4cd670ded5d1efe72901541ff2202fcc622730d8a549c76e198a2f74e312eabbfb712ed907d45cbb0b + languageName: node + linkType: hard + "typed-array-byte-length@npm:^1.0.0": version: 1.0.0 resolution: "typed-array-byte-length@npm:1.0.0" @@ -14848,6 +15757,19 @@ __metadata: languageName: node linkType: hard +"typed-array-byte-length@npm:^1.0.1": + version: 1.0.1 + resolution: "typed-array-byte-length@npm:1.0.1" + dependencies: + call-bind: ^1.0.7 + for-each: ^0.3.3 + gopd: ^1.0.1 + has-proto: ^1.0.3 + is-typed-array: ^1.1.13 + checksum: f65e5ecd1cf76b1a2d0d6f631f3ea3cdb5e08da106c6703ffe687d583e49954d570cc80434816d3746e18be889ffe53c58bf3e538081ea4077c26a41055b216d + languageName: node + linkType: hard + "typed-array-byte-offset@npm:^1.0.0": version: 1.0.0 resolution: "typed-array-byte-offset@npm:1.0.0" @@ -14861,6 +15783,20 @@ __metadata: languageName: node linkType: hard +"typed-array-byte-offset@npm:^1.0.2": + version: 1.0.2 + resolution: "typed-array-byte-offset@npm:1.0.2" + dependencies: + available-typed-arrays: ^1.0.7 + call-bind: ^1.0.7 + for-each: ^0.3.3 + gopd: ^1.0.1 + has-proto: ^1.0.3 + is-typed-array: ^1.1.13 + checksum: c8645c8794a621a0adcc142e0e2c57b1823bbfa4d590ad2c76b266aa3823895cf7afb9a893bf6685e18454ab1b0241e1a8d885a2d1340948efa4b56add4b5f67 + languageName: node + linkType: hard + "typed-array-length@npm:^1.0.4": version: 1.0.4 resolution: "typed-array-length@npm:1.0.4" @@ -14872,6 +15808,20 @@ __metadata: languageName: node linkType: hard +"typed-array-length@npm:^1.0.6": + version: 1.0.6 + resolution: "typed-array-length@npm:1.0.6" + dependencies: + call-bind: ^1.0.7 + for-each: ^0.3.3 + gopd: ^1.0.1 + has-proto: ^1.0.3 + is-typed-array: ^1.1.13 + possible-typed-array-names: ^1.0.0 + checksum: f0315e5b8f0168c29d390ff410ad13e4d511c78e6006df4a104576844812ee447fcc32daab1f3a76c9ef4f64eff808e134528b5b2439de335586b392e9750e5c + languageName: node + linkType: hard + "typedarray-to-buffer@npm:3.1.5": version: 3.1.5 resolution: "typedarray-to-buffer@npm:3.1.5" @@ -15605,6 +16555,19 @@ __metadata: languageName: node linkType: hard +"which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.15": + version: 1.1.15 + resolution: "which-typed-array@npm:1.1.15" + dependencies: + available-typed-arrays: ^1.0.7 + call-bind: ^1.0.7 + for-each: ^0.3.3 + gopd: ^1.0.1 + has-tostringtag: ^1.0.2 + checksum: 65227dcbfadf5677aacc43ec84356d17b5500cb8b8753059bb4397de5cd0c2de681d24e1a7bd575633f976a95f88233abfd6549c2105ef4ebd58af8aa1807c75 + languageName: node + linkType: hard + "which@npm:1.3.1, which@npm:^1.1.1, which@npm:^1.3.1": version: 1.3.1 resolution: "which@npm:1.3.1"