Skip to content

Commit

Permalink
feat(contracts): add zupass gatekeeper and simplify tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
Crisgarner authored and ctrlc03 committed Jun 10, 2024
1 parent 6319120 commit 6fc93d0
Show file tree
Hide file tree
Showing 13 changed files with 1,859 additions and 18 deletions.
87 changes: 87 additions & 0 deletions contracts/contracts/gatekeepers/zupass/ZupassGatekeeper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

import { SignUpGatekeeper } from "../SignUpGatekeeper.sol";

import { ZupassGroth16Verifier } from "./ZupassGroth16Verifier.sol";

/// @title ZupassGatekeeper
/// @notice This contract allows to gatekeep MACI signups
/// by requiring new voters to own a certain Zupass event ticket
contract ZupassGatekeeper is SignUpGatekeeper, Ownable(msg.sender) {
/// @notice the Zupass event UUID converted to bigint
uint256 public immutable validEventId;

/// @notice the Zupass event signer converted to bigint
uint256 public immutable validSigner1;
uint256 public immutable validSigner2;

/// @notice the ZupassGroth16Verifier contract address
ZupassGroth16Verifier public immutable verifier;

/// @notice the reference to the MACI contract
address public maci;

/// @notice a mapping of ticket IDs to whether they have been used
mapping(uint256 => bool) public registeredTickets;

/// @notice custom errors
error AlreadyRegistered();
error InvalidProof();
error InvalidEventId();
error InvalidSigners();
error InvalidWatermark();
error OnlyMACI();
error ZeroAddress();

/// @notice Creates a new ZupassGatekeeper
/// @param _validEventId Zupass event UUID converted to bigint
/// @param _validSigner1 Zupass event signer[0] converted to bigint
/// @param _validSigner2 Zupass event signer[1] converted to bigint
/// @param _verifier The ZupassGroth16Verifier contract address
constructor(uint256 _validEventId, uint256 _validSigner1, uint256 _validSigner2, address _verifier) payable {
validEventId = _validEventId;
validSigner1 = _validSigner1;
validSigner2 = _validSigner2;
verifier = ZupassGroth16Verifier(_verifier);
}

/// @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 Registers the user only if they have the Zupass event ticket
/// @param _user The user's Ethereum address.
/// @param _data The ABI-encoded proof and public signals.
function register(address _user, bytes memory _data) public override {
if (maci != msg.sender) revert OnlyMACI();

// Decode the given _data bytes
(uint256[2] memory _pA, uint256[2][2] memory _pB, uint256[2] memory _pC, uint256[38] memory _pubSignals) = abi
.decode(_data, (uint256[2], uint256[2][2], uint256[2], uint256[38]));

// Ticket ID is stored at index 0
uint256 ticketId = _pubSignals[0];
if (registeredTickets[ticketId]) revert AlreadyRegistered();

registeredTickets[ticketId] = true;

// Verify proof
if (!verifier.verifyProof(_pA, _pB, _pC, _pubSignals)) revert InvalidProof();

// Event id is stored at index 15
if (_pubSignals[15] != validEventId) revert InvalidEventId();

// Signers are stored at index 13 and 14
if (_pubSignals[13] != validSigner1 || _pubSignals[14] != validSigner2) revert InvalidSigners();

// Watermark is stored at index 37
// user address converted to bigint is used as the watermark
if (_pubSignals[37] != uint256(uint160(_user))) revert InvalidWatermark();
}
}
428 changes: 428 additions & 0 deletions contracts/contracts/gatekeepers/zupass/ZupassGroth16Verifier.sol

Large diffs are not rendered by default.

19 changes: 15 additions & 4 deletions contracts/deploy-config-example.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@
"deploy": true,
"easAddress": "0xC2679fBD37d54388Ce493F1DB75320D236e1815e",
"schema": "0xe2636f31239f7948afdd9a9c477048b7fc2a089c347af60e3aa1251e5bf63e5c",
"attester": "attester-address"
"attester": "the-attester-address"
},
"ZupassGatekeeper": {
"deploy": false,
"signer1": "13908133709081944902758389525983124100292637002438232157513257158004852609027",
"signer2": "7654374482676219729919246464135900991450848628968334062174564799457623790084",
"eventId": "69c0caaa-c65d-5345-a20c-867774f18c67",
"zupassVerifier": "0x2272cdb3596617886d0F48524DA486044E0376d6"
},
"MACI": {
"stateTreeDepth": 10,
Expand All @@ -26,16 +33,20 @@
"zkeys": {
"qv": {
"processMessagesZkey": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test.0.zkey",
"tallyVotesZkey": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey"
"tallyVotesZkey": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test.0.zkey",
"processWasm": "../cli/zkeys/ProcessMessages_10-2-1-2_test/ProcessMessages_10-2-1-2_test_js/ProcessMessages_10-2-1-2_test.wasm",
"tallyWasm": "../cli/zkeys/TallyVotes_10-1-2_test/TallyVotes_10-1-2_test_js/TallyVotes_10-1-2_test.wasm"
},
"nonQv": {
"processMessagesZkey": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test.0.zkey",
"tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey"
"tallyVotesZkey": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test.0.zkey",
"processWasm": "../cli/zkeys/ProcessMessagesNonQv_10-2-1-2_test/ProcessMessagesNonQv_10-2-1-2_test_js/ProcessMessagesNonQv_10-2-1-2_test.wasm",
"tallyWasm": "../cli/zkeys/TallyVotesNonQv_10-1-2_test/TallyVotesNonQv_10-1-2_test_js/TallyVotesNonQv_10-1-2_test.wasm"
}
}
},
"Poll": {
"pollDuration": 30,
"pollDuration": 3600,
"coordinatorPubkey": "macipk.9a59264310d95cfd8eb7083aebeba221b5c26e77427f12b7c0f50bc1cc35e621",
"useQuadraticVoting": true
}
Expand Down
4 changes: 3 additions & 1 deletion contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@
"maci-core": "^1.2.3",
"maci-crypto": "^1.2.3",
"maci-domainobjs": "^1.2.3",
"solidity-docgen": "^0.6.0-beta.36"
"solidity-docgen": "^0.6.0-beta.36",
"uuid": "^9.0.0"
},
"devDependencies": {
"@types/chai": "^4.3.11",
Expand All @@ -90,6 +91,7 @@
"@types/mocha": "^10.0.6",
"@types/node": "^20.12.7",
"@types/snarkjs": "^0.7.8",
"@types/uuid": "^9.0.2",
"chai": "^4.3.10",
"dotenv": "^16.4.5",
"hardhat-artifactor": "^0.2.0",
Expand Down
40 changes: 39 additions & 1 deletion contracts/tasks/deploy/maci/02-gatekeepers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ESupportedChains } from "../../helpers/constants";
import { ContractStorage } from "../../helpers/ContractStorage";
import { Deployment } from "../../helpers/Deployment";
import { uuidToBigInt } from "../../helpers/numericParser";
import { EContracts, IDeployParams } from "../../helpers/types";

const deployment = Deployment.getInstance();
Expand All @@ -16,17 +17,21 @@ deployment.deployTask("full:deploy-gatekeepers", "Deploy gatekeepers").then((tas

const freeForAllGatekeeperContractAddress = storage.getAddress(EContracts.FreeForAllGatekeeper, hre.network.name);
const easGatekeeperContractAddress = storage.getAddress(EContracts.EASGatekeeper, hre.network.name);
const zupassGatekeeperContractAddress = storage.getAddress(EContracts.ZupassGatekeeper, hre.network.name);
const deployFreeForAllGatekeeper = deployment.getDeployConfigField(EContracts.FreeForAllGatekeeper, "deploy");
const deployEASGatekeeper = deployment.getDeployConfigField(EContracts.EASGatekeeper, "deploy");
const deployZupassGatekeeper = deployment.getDeployConfigField(EContracts.ZupassGatekeeper, "deploy");

const skipDeployFreeForAllGatekeeper = deployFreeForAllGatekeeper === false;
const skipDeployEASGatekeeper = deployEASGatekeeper === false;
const skipDeployZupassGatekeeper = deployZupassGatekeeper !== true;

const canSkipDeploy =
incremental &&
(freeForAllGatekeeperContractAddress || skipDeployFreeForAllGatekeeper) &&
(easGatekeeperContractAddress || skipDeployEASGatekeeper) &&
(!skipDeployFreeForAllGatekeeper || !skipDeployEASGatekeeper);
(zupassGatekeeperContractAddress || skipDeployZupassGatekeeper) &&
(!skipDeployFreeForAllGatekeeper || !skipDeployEASGatekeeper || !skipDeployZupassGatekeeper);

if (canSkipDeploy) {
return;
Expand Down Expand Up @@ -72,5 +77,38 @@ deployment.deployTask("full:deploy-gatekeepers", "Deploy gatekeepers").then((tas
network: hre.network.name,
});
}

if (!skipDeployZupassGatekeeper) {
const eventId = deployment.getDeployConfigField<string>(EContracts.ZupassGatekeeper, "eventId", true);
const validEventId = uuidToBigInt(eventId);
const validSigner1 = deployment.getDeployConfigField<string>(EContracts.ZupassGatekeeper, "signer1", true);
const validSigner2 = deployment.getDeployConfigField<string>(EContracts.ZupassGatekeeper, "signer2", true);
let verifier = deployment.getDeployConfigField<string | undefined>(EContracts.ZupassGatekeeper, "zupassVerifier");

if (!verifier) {
const verifierContract = await deployment.deployContract({
name: EContracts.ZupassGroth16Verifier,
signer: deployer,
});
verifier = await verifierContract.getAddress();
}

const ZupassGatekeeperContract = await deployment.deployContract(
{
name: EContracts.ZupassGatekeeper,
signer: deployer,
},
validEventId,
validSigner1,
validSigner2,
verifier,
);
await storage.register({
id: EContracts.ZupassGatekeeper,
contract: ZupassGatekeeperContract,
args: [validEventId.toString(), validSigner1, validSigner2, verifier],
network: hre.network.name,
});
}
}),
);
9 changes: 8 additions & 1 deletion contracts/tasks/deploy/maci/09-maci.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { EASGatekeeper, MACI } from "../../../typechain-types";
import type { EASGatekeeper, ZupassGatekeeper, MACI } from "../../../typechain-types";

import { ContractStorage } from "../../helpers/ContractStorage";
import { Deployment } from "../../helpers/Deployment";
Expand Down Expand Up @@ -76,6 +76,13 @@ deployment.deployTask("full:deploy-maci", "Deploy MACI contract").then((task) =>
});
const maciInstanceAddress = await maciContract.getAddress();

await gatekeeperContract.setMaciInstance(maciInstanceAddress).then((tx) => tx.wait());
} else if (gatekeeper === EContracts.ZupassGatekeeper) {
const gatekeeperContract = await deployment.getContract<ZupassGatekeeper>({
name: EContracts.ZupassGatekeeper,
address: gatekeeperContractAddress,
});
const maciInstanceAddress = await maciContract.getAddress();
await gatekeeperContract.setMaciInstance(maciInstanceAddress).then((tx) => tx.wait());
}

Expand Down
10 changes: 10 additions & 0 deletions contracts/tasks/helpers/numericParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { parse as uuidParse } from "uuid";

/**
* Converts a UUID string into a bigint.
*/
export function uuidToBigInt(v: string): bigint {
// a uuid is just a particular representation of 16 bytes
const bytes = uuidParse(v);
return BigInt(`0x${Buffer.from(bytes).toString("hex")}`);
}
2 changes: 2 additions & 0 deletions contracts/tasks/helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,8 @@ export enum EContracts {
ConstantInitialVoiceCreditProxy = "ConstantInitialVoiceCreditProxy",
FreeForAllGatekeeper = "FreeForAllGatekeeper",
EASGatekeeper = "EASGatekeeper",
ZupassGatekeeper = "ZupassGatekeeper",
ZupassGroth16Verifier = "ZupassGroth16Verifier",
Verifier = "Verifier",
TopupCredit = "TopupCredit",
MACI = "MACI",
Expand Down
Loading

0 comments on commit 6fc93d0

Please sign in to comment.