-
Notifications
You must be signed in to change notification settings - Fork 154
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// 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 proofValidTime; | ||
Check warning Code scanning / Slither State variables that could be declared immutable Warning
AnonAadhaarGatekeeper.proofValidTime should be immutable
|
||
|
||
/// @notice The registered identities | ||
mapping(uint256 => bool) public registeredAadhaars; | ||
|
||
/// @notice Errors | ||
error ZeroAddress(); | ||
error OnlyMACI(); | ||
error AlreadyRegistered(); | ||
error InvalidProof(); | ||
error proofTooOld(uint timestamp); | ||
|
||
/// @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 { | ||
Check warning Code scanning / Slither Conformance to Solidity naming conventions Warning
Parameter AnonAadhaarGatekeeper.setMaciInstance(address)._maci is not in mixedCase
|
||
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
|
||
// 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])); | ||
|
||
// 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(block.timestamp); | ||
|
||
// 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 |
||
|
||
/// @notice Get the trait of the gatekeeper | ||
/// @return The type of the gatekeeper | ||
function getTrait() public pure override returns (string memory) { | ||
return "AnonAadhaar"; | ||
} | ||
} | ||
Check warning Code scanning / Slither Contracts that lock Ether Medium
Contract locking ether found:
Contract AnonAadhaarGatekeeper has payable functions: - AnonAadhaarGatekeeper.constructor(address,uint256) But does not have a function to withdraw the ether |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.19; | ||
Check warning Code scanning / Slither Incorrect versions of Solidity Warning
Version constraint ^0.8.19 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- VerbatimInvalidDeduplication - FullInlinerNonExpressionSplitArgumentEvaluationOrder - MissingSideEffectsOnSelectorAccess. It is used by: - ^0.8.19 |
||
|
||
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,157 @@ | ||
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 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(); | ||
encodedProof = AbiCoder.defaultAbiCoder().encode( | ||
["uint256", "uint256", "uint256", "address", "uint256[4]", "uint256[8]"], | ||
[ | ||
mockProof.nullifierSeed, | ||
mockProof.nullifier, | ||
mockProof.timestamp, | ||
signerAddress, | ||
[mockProof.ageAbove18, mockProof.gender, mockProof.pincode, mockProof.state], | ||
mockProof.packedGroth16Proof, | ||
], | ||
); | ||
AnonAadhaarGatekeeper = await deployAnonAadhaarGatekeeper(MockAnonAadhaarAddress, proofValidTime, signer, true); | ||
}); | ||
|
||
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", "address", "uint256[4]", "uint256[8]"], | ||
[ | ||
oldProof.nullifierSeed, | ||
oldProof.nullifier, | ||
oldProof.timestamp, | ||
signerAddress, | ||
[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 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"); | ||
}); | ||
}); | ||
}); |