-
Notifications
You must be signed in to change notification settings - Fork 139
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; | ||
|
||
import { SignUpGatekeeper } from "./SignUpGatekeeper.sol"; | ||
import { IAnonAadhaar } from "../interfaces/IAnonAadhaar.sol"; | ||
|
||
/// @title AnonAadhaarGatekeeper | ||
/// @notice A gatekeeper contract which allows users to sign up to MACI | ||
/// only if they can prove they are valid Aadhaar owners. | ||
/// @dev Please note that once a identity is used to register, it cannot be used again. | ||
/// This is because we store the nullifier of the proof. | ||
contract AnonAadhaarGatekeeper is SignUpGatekeeper, Ownable(msg.sender) { | ||
/// @notice The anonAadhaar contract | ||
IAnonAadhaar public immutable anonAadhaarContract; | ||
|
||
/// @notice The address of the MACI contract | ||
address public maci; | ||
|
||
/// @notice The time window in which the proof is valid | ||
uint256 public immutable proofValidTime; | ||
|
||
/// @notice The registered identities | ||
mapping(uint256 => bool) public registeredAadhaars; | ||
|
||
/// @notice Errors | ||
error ZeroAddress(); | ||
error OnlyMACI(); | ||
error AlreadyRegistered(); | ||
error InvalidProof(); | ||
error ProofTooOld(); | ||
error InvalidSignal(); | ||
|
||
/// @notice Create a new instance of the gatekeeper | ||
/// @param _anonAadhaarVerifierAddr The address of the anonAadhaar contract | ||
constructor(address _anonAadhaarVerifierAddr, uint256 _proofValidTime) payable { | ||
if (_anonAadhaarVerifierAddr == address(0)) revert ZeroAddress(); | ||
anonAadhaarContract = IAnonAadhaar(_anonAadhaarVerifierAddr); | ||
proofValidTime = _proofValidTime; | ||
} | ||
|
||
/// @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 if they can prove anonAadhaar proof | ||
/// @dev Throw if the proof is not valid or just complete silently | ||
/// @param _data The ABI-encoded data containing nullifierSeed, nullifier, timestamp, signal, revealArray, and groth16Proof. | ||
function register(address _user, bytes memory _data) public override { | ||
Check warning Code scanning / Slither Conformance to Solidity naming conventions Warning
Parameter AnonAadhaarGatekeeper.register(address,bytes)._data is not in mixedCase
Check warning Code scanning / Slither Conformance to Solidity naming conventions Warning
Parameter AnonAadhaarGatekeeper.register(address,bytes)._user is not in mixedCase
|
||
// decode the argument | ||
( | ||
uint nullifierSeed, | ||
uint nullifier, | ||
uint timestamp, | ||
uint signal, | ||
uint[4] memory revealArray, | ||
uint[8] memory groth16Proof | ||
) = abi.decode(_data, (uint, uint, uint, uint, uint[4], uint[8])); | ||
Check warning on line 62 in packages/contracts/contracts/gatekeepers/AnonAadhaarGatekeeper.sol GitHub Actions / check (lint:sol)
|
||
|
||
// ensure that the caller is the MACI contract | ||
if (maci != msg.sender) revert OnlyMACI(); | ||
|
||
// ensure that the proof is not too old | ||
if (block.timestamp - timestamp > proofValidTime) revert ProofTooOld(); | ||
|
||
// ensure that the signal is correct | ||
if (signal != addressToUint256(_user)) revert InvalidSignal(); | ||
|
||
// ensure that the nullifier has not been registered yet | ||
if (registeredAadhaars[nullifier]) revert AlreadyRegistered(); | ||
|
||
// register the nullifier so it cannot be called again with the same one | ||
registeredAadhaars[nullifier] = true; | ||
|
||
// check if the proof validates | ||
if ( | ||
!anonAadhaarContract.verifyAnonAadhaarProof( | ||
nullifierSeed, | ||
nullifier, | ||
timestamp, | ||
signal, | ||
revealArray, | ||
groth16Proof | ||
) | ||
) revert InvalidProof(); | ||
} | ||
Check notice Code scanning / Slither Block timestamp Low
AnonAadhaarGatekeeper.register(address,bytes) uses timestamp for comparisons
Dangerous comparisons: - block.timestamp - timestamp > proofValidTime |
||
|
||
/// @dev Convert an address to uint256, used to check against signal. | ||
/// @param _addr: msg.sender address. | ||
/// @return Address msg.sender's address in uint256 | ||
function addressToUint256(address _addr) private pure returns (uint256) { | ||
Check warning Code scanning / Slither Conformance to Solidity naming conventions Warning
Parameter AnonAadhaarGatekeeper.addressToUint256(address)._addr is not in mixedCase
|
||
return uint256(uint160(_addr)); | ||
} | ||
|
||
/// @notice Get the trait of the gatekeeper | ||
/// @return The type of the gatekeeper | ||
function getTrait() public pure override returns (string memory) { | ||
return "AnonAadhaar"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
interface IAnonAadhaar { | ||
function verifyAnonAadhaarProof( | ||
uint nullifierSeed, | ||
uint nullifier, | ||
uint timestamp, | ||
uint signal, | ||
uint[4] memory revealArray, | ||
uint[8] memory groth16Proof | ||
) external view returns (bool); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import { IAnonAadhaar } from "../interfaces/IAnonAadhaar.sol"; | ||
|
||
/// @title MockAnonAadhaar | ||
/// @notice A mock contract to test the AnonAadhaarGatekeeper | ||
contract MockAnonAadhaar is IAnonAadhaar { | ||
bool public valid = true; | ||
|
||
/// @notice Mock function to flip the valid state | ||
function flipValid() external { | ||
valid = !valid; | ||
} | ||
|
||
/// @notice Mock implementation of verifyAnonAadhaarProof | ||
function verifyAnonAadhaarProof( | ||
uint nullifierSeed, | ||
uint nullifier, | ||
uint timestamp, | ||
uint signal, | ||
uint[4] memory revealArray, | ||
uint[8] memory groth16Proof | ||
) external view override returns (bool) { | ||
return valid; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import { expect } from "chai"; | ||
import { AbiCoder, Signer, ZeroAddress } from "ethers"; | ||
import { Keypair } from "maci-domainobjs"; | ||
|
||
import { deployAnonAadhaarGatekeeper, deployContract } from "../ts/deploy"; | ||
import { getDefaultSigner, getSigners } from "../ts/utils"; | ||
import { MACI, AnonAadhaarGatekeeper, MockAnonAadhaar } from "../typechain-types"; | ||
|
||
import { STATE_TREE_DEPTH, initialVoiceCreditBalance } from "./constants"; | ||
import { deployTestContracts } from "./utils"; | ||
|
||
describe("AnonAadhaar Gatekeeper", () => { | ||
let anonAadhaarGatekeeper: AnonAadhaarGatekeeper; | ||
let mockAnonAadhaar: MockAnonAadhaar; | ||
let signer: Signer; | ||
let signerAddressUint256: bigint; | ||
let signerAddress: string; | ||
let encodedProof: string; | ||
|
||
const user = new Keypair(); | ||
|
||
// set proof valid time to 3 hours | ||
const proofValidTime = 3 * 60 * 60; | ||
|
||
// Mock AnonAadhaar proof | ||
const mockProof = { | ||
timestamp: Math.floor(new Date().getTime() / 1000) - 2 * 60 * 60, | ||
nullifierSeed: "1234", | ||
nullifier: "7946664694698614794431553425553810756961743235367295886353548733878558886762", | ||
ageAbove18: "1", | ||
gender: "77", | ||
pincode: "110051", | ||
state: "452723500356", | ||
packedGroth16Proof: ["0", "1", "2", "3", "4", "5", "6", "7"], | ||
}; | ||
|
||
before(async () => { | ||
signer = await getDefaultSigner(); | ||
mockAnonAadhaar = await deployContract("MockAnonAadhaar", signer, true); | ||
const mockAnonAadhaarAddress = await mockAnonAadhaar.getAddress(); | ||
signerAddress = await signer.getAddress(); | ||
anonAadhaarGatekeeper = await deployAnonAadhaarGatekeeper(mockAnonAadhaarAddress, proofValidTime, signer, true); | ||
signerAddressUint256 = BigInt(signerAddress); | ||
encodedProof = AbiCoder.defaultAbiCoder().encode( | ||
["uint256", "uint256", "uint256", "uint256", "uint256[4]", "uint256[8]"], | ||
[ | ||
mockProof.nullifierSeed, | ||
mockProof.nullifier, | ||
mockProof.timestamp, | ||
signerAddressUint256, | ||
[mockProof.ageAbove18, mockProof.gender, mockProof.pincode, mockProof.state], | ||
mockProof.packedGroth16Proof, | ||
], | ||
); | ||
}); | ||
|
||
describe("Deployment", () => { | ||
it("The gatekeeper should be deployed correctly", () => { | ||
expect(anonAadhaarGatekeeper).to.not.eq(undefined); | ||
}); | ||
}); | ||
|
||
describe("Gatekeeper", () => { | ||
let maciContract: MACI; | ||
|
||
before(async () => { | ||
const r = await deployTestContracts({ | ||
initialVoiceCreditBalance, | ||
stateTreeDepth: STATE_TREE_DEPTH, | ||
signer, | ||
gatekeeper: anonAadhaarGatekeeper, | ||
}); | ||
|
||
maciContract = r.maciContract; | ||
}); | ||
|
||
it("sets MACI instance correctly", async () => { | ||
const maciAddress = await maciContract.getAddress(); | ||
await anonAadhaarGatekeeper.setMaciInstance(maciAddress).then((tx) => tx.wait()); | ||
|
||
expect(await anonAadhaarGatekeeper.maci()).to.eq(maciAddress); | ||
}); | ||
|
||
it("should fail to set MACI instance when the caller is not the owner", async () => { | ||
const [, secondSigner] = await getSigners(); | ||
await expect( | ||
anonAadhaarGatekeeper.connect(secondSigner).setMaciInstance(signerAddress), | ||
).to.be.revertedWithCustomError(anonAadhaarGatekeeper, "OwnableUnauthorizedAccount"); | ||
}); | ||
|
||
it("should fail to set MACI instance when the MACI instance is not valid", async () => { | ||
await expect(anonAadhaarGatekeeper.setMaciInstance(ZeroAddress)).to.be.revertedWithCustomError( | ||
anonAadhaarGatekeeper, | ||
"ZeroAddress", | ||
); | ||
}); | ||
|
||
it("should revert if proof is older than the proof valid time", async () => { | ||
const oldProof = { | ||
...mockProof, | ||
timestamp: Math.floor(new Date().getTime() / 1000) - 4 * 60 * 60, | ||
}; | ||
|
||
const encodedOldProof = AbiCoder.defaultAbiCoder().encode( | ||
["uint256", "uint256", "uint256", "uint256", "uint256[4]", "uint256[8]"], | ||
[ | ||
oldProof.nullifierSeed, | ||
oldProof.nullifier, | ||
oldProof.timestamp, | ||
signerAddressUint256, | ||
[oldProof.ageAbove18, oldProof.gender, oldProof.pincode, oldProof.state], | ||
oldProof.packedGroth16Proof, | ||
], | ||
); | ||
|
||
await expect( | ||
maciContract.signUp( | ||
user.pubKey.asContractParam(), | ||
encodedOldProof, | ||
AbiCoder.defaultAbiCoder().encode(["uint256"], [1]), | ||
), | ||
).to.be.revertedWithCustomError(anonAadhaarGatekeeper, "ProofTooOld"); | ||
}); | ||
|
||
it("should revert if the signal is invalid", async () => { | ||
const encodedInvalidProof = AbiCoder.defaultAbiCoder().encode( | ||
["uint256", "uint256", "uint256", "uint256", "uint256[4]", "uint256[8]"], | ||
[ | ||
mockProof.nullifierSeed, | ||
mockProof.nullifier, | ||
mockProof.timestamp, | ||
BigInt(ZeroAddress), | ||
[mockProof.ageAbove18, mockProof.gender, mockProof.pincode, mockProof.state], | ||
mockProof.packedGroth16Proof, | ||
], | ||
); | ||
await expect( | ||
maciContract.signUp( | ||
user.pubKey.asContractParam(), | ||
encodedInvalidProof, | ||
AbiCoder.defaultAbiCoder().encode(["uint256"], [0]), | ||
), | ||
).to.be.revertedWithCustomError(anonAadhaarGatekeeper, "InvalidSignal"); | ||
}); | ||
|
||
it("should revert if the proof is invalid (mock)", async () => { | ||
await mockAnonAadhaar.flipValid(); | ||
await expect( | ||
maciContract.signUp( | ||
user.pubKey.asContractParam(), | ||
encodedProof, | ||
AbiCoder.defaultAbiCoder().encode(["uint256"], [1]), | ||
), | ||
).to.be.revertedWithCustomError(anonAadhaarGatekeeper, "InvalidProof"); | ||
await mockAnonAadhaar.flipValid(); | ||
}); | ||
|
||
it("should register a user if the register function is called with the valid data", async () => { | ||
const tx = await maciContract.signUp( | ||
user.pubKey.asContractParam(), | ||
encodedProof, | ||
AbiCoder.defaultAbiCoder().encode(["uint256"], [1]), | ||
); | ||
|
||
const receipt = await tx.wait(); | ||
|
||
expect(receipt?.status).to.eq(1); | ||
}); | ||
|
||
it("should prevent signing up twice", async () => { | ||
await expect( | ||
maciContract.signUp( | ||
user.pubKey.asContractParam(), | ||
encodedProof, | ||
AbiCoder.defaultAbiCoder().encode(["uint256"], [1]), | ||
), | ||
).to.be.revertedWithCustomError(anonAadhaarGatekeeper, "AlreadyRegistered"); | ||
}); | ||
}); | ||
}); |