-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(contracts): implement anon aadhaar gatekeeper
- Loading branch information
1 parent
07552f4
commit 70b971e
Showing
6 changed files
with
313 additions
and
0 deletions.
There are no files selected for viewing
93 changes: 93 additions & 0 deletions
93
packages/contracts/contracts/gatekeepers/AnonAadhaarGatekeeper.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 immutable proofValidTime; | ||
|
||
/// @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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters