-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPoolOfHumanity.sol
245 lines (212 loc) · 9.48 KB
/
PoolOfHumanity.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./MerkleTreeWithHistory.sol";
// Interface for the humanity verifier contract.
interface IHumanityVerifier {
function verifyProof(
uint256[2] calldata a,
uint256[2][2] calldata b,
uint256[2] calldata c,
uint256[5] calldata input
) external view returns (bool);
}
// Interface for the update verifier contract.
interface IUpdateVerifier {
function verifyProof(
uint256[2] calldata a,
uint256[2][2] calldata b,
uint256[2] calldata c,
uint256[47] calldata input
) external view returns (bool);
}
// Interface for the Poseidon hash function for 3 inputs.
interface IHasher3 {
function poseidon(bytes32[3] calldata leftRight)
external
pure
returns (bytes32);
}
// Status of a submission in the Proof of Humanity.
enum Status {
None,
Vouching,
PendingRegistration,
PendingRemoval
}
// Interface for the Proof of Humanity contract.
interface IProofOfHumanity {
function submissionDuration() external view returns (uint64);
function getSubmissionInfo(address _submissionID) external view returns (
Status status,
uint64 submissionTime,
uint64 index,
bool registered,
bool hasVouched,
uint numberOfRequests
);
}
/**
* @title PoolOfHumanity
* This contract manages a pool of users who are registered for the Proof of Humanity. Users who have a submission in the Proof of
* Humanity that has finised vouching and is not currently being challenged can register for the Pool. Users can then
* prove that they have a submission in the Proof of Humanity without revealing their identity.
*
* Registration to the pool requires a small deposit, which is returned if the user unregisters. If the user's Proof of Humanity
* registration is revoked, the deposit can be claimed by someone who updates the revoked user's registration in the pool.
*/
contract PoolOfHumanity is MerkleTreeWithHistory {
event Registered(address indexed user, uint index, bytes32 pubKey, uint submissionTime);
event Updated(address indexed user, uint submissionTime, bool registered);
uint32 constant HEIGHT = 20; // Height of the merkle tree
uint public depositAmount = 0.05 ether;
IHumanityVerifier public immutable humanityVerifier;
IUpdateVerifier public immutable updateVerifier;
IProofOfHumanity public immutable poh;
IHasher3 public immutable hasher3;
mapping (address => bytes32) public users; // Maps users to their public key
constructor(
address _humanityVerifier,
address _updateVerifier,
address _poh,
address _hasher2,
address _hasher3
) MerkleTreeWithHistory(HEIGHT, _hasher2) {
humanityVerifier = IHumanityVerifier(_humanityVerifier);
updateVerifier = IUpdateVerifier(_updateVerifier);
poh = IProofOfHumanity(_poh);
hasher3 = IHasher3(_hasher3);
}
/**
* @dev Registers a user for the pool. The user must have a submission in the Proof of Humanity that has finished vouching
* and is not currently being challenged.
* @param pubkey The user's public key. The public key is the poseidon hash of the private key. This private key is required
* to verify a user's registration in the pool.
*/
function register(bytes32 pubkey) public payable {
require(users[msg.sender] == 0, "already in pool");
require(msg.value == depositAmount, "incorrect deposit amount");
Status status;
uint64 submissionTime;
bool registered;
(status, submissionTime, , registered, , ) = poh.getSubmissionInfo(msg.sender);
require(registered, "not registered");
require(status == Status.None, "incorrect status");
bytes32 submissionTimeB = bytes32(uint256(submissionTime));
bytes32[3] memory leafHashElements = [pubkey, submissionTimeB, bytes32(uint(1))];
bytes32 leafHash = hasher3.poseidon(leafHashElements);
uint index = _insert(leafHash);
emit Registered(msg.sender, index, pubkey, submissionTime);
users[msg.sender] = pubkey;
}
/**
* @dev Updates a submission in the pool to match the user's current submission in the Proof of Humanity.
* If the user's registration status changes from true => false, the deposit will be returned to the caller.
* If the user's registration status changes from false => true, the deposit amount must be paid.
* @param user The address of the user whose submission is being updated.
* @param previousSubmissionTime The submission time of the user's previous submission in the pool.
* @param previouslyRegistered Whether the user's previous submission in the pool was registered.
* @param currentPath The path of the user's submission in the merkle tree to the root.
* @param updatedPath The path of the user's updated submission in the merkle tree to the root.
* @param a The first part of the proof.
* @param b The second part of the proof.
* @param c The third part of the proof.
*/
function updateSubmission(
address user,
uint previousSubmissionTime,
uint previouslyRegistered,
bytes32[] memory currentPath,
bytes32[] memory updatedPath,
uint[2] memory a,
uint[2][2] memory b,
uint[2] memory c
) public payable {
require(roots[currentRootIndex] == currentPath[20], "current root not on current path");
Status status;
uint64 submissionTime;
bool registered;
(status, submissionTime, , registered, , ) = poh.getSubmissionInfo(user);
require(status == Status.None, "incorrect status");
// If the user was not previously registered, they must pay the deposit
if (previouslyRegistered == 0 && registered == true) {
require(msg.value == depositAmount, "incorrect deposit amount");
}
bytes32 pubKey = users[user];
uint[2 * HEIGHT + 7] memory inputs;
inputs[0] = uint(pubKey);
inputs[1] = uint(previousSubmissionTime);
inputs[2] = previouslyRegistered;
for (uint i = 0; i < HEIGHT + 1; i++) {
inputs[i + 3] = uint(currentPath[i]);
}
inputs[HEIGHT + 4] = uint(submissionTime);
inputs[HEIGHT + 5] = uint(registered ? 1 : 0);
for (uint i = 0; i < HEIGHT + 1; i++) {
inputs[i + 6 + HEIGHT] = uint(updatedPath[i]);
}
require(updateVerifier.verifyProof(a, b, c, inputs), "update not verified");
_update(currentPath, updatedPath);
emit Updated(msg.sender, submissionTime, registered);
}
/**
* @dev Unregisters a user from the pool. Returns the deposit to the user.
* @param submissionTime The user's submission time.
* @param currentPath The current path of the user's registration in the merkle tree.
* @param updatedPath The updated path of the user's registration in the merkle tree after setting
* the user's registration to false.
* @param a The first element of the update proof.
* @param b The second element of the update proof.
* @param c The third element of the update proof.
*/
function unregister(
uint submissionTime,
bytes32[] memory currentPath,
bytes32[] memory updatedPath,
uint[2] memory a,
uint[2][2] memory b,
uint[2] memory c
) public {
require(users[msg.sender] != 0, "not in pool");
bytes32 pubkey = users[msg.sender];
uint[2 * HEIGHT + 7] memory inputs;
inputs[0] = uint(pubkey);
inputs[1] = uint(submissionTime);
inputs[2] = 1;
for (uint i = 0; i < HEIGHT + 1; i++) {
inputs[i + 3] = uint(currentPath[i]);
}
inputs[HEIGHT + 4] = uint(submissionTime);
inputs[HEIGHT + 5] = uint(0);
for (uint i = 0; i < HEIGHT + 1; i++) {
inputs[i + 6 + HEIGHT] = uint(updatedPath[i]);
}
require(updateVerifier.verifyProof(a, b, c, inputs), "update not verified");
_update(currentPath, updatedPath);
emit Updated(msg.sender, submissionTime, false);
(bool ok, ) = payable(msg.sender).call{value: depositAmount}("");
require(ok == true, "transfer failed");
}
/**
* @dev Checks if a user is registered in the pool and their submission is not yet expired
* @param root The root of the merkle tree used to generate the proof
* @param currentTime The current timestamp used to generate the proof
* @param appID The application ID used to generate the proof
* @param expectedAppNullifier poseidonHash (privateKey, appID, 42)
* @param proof The ZK snark proof
* @return True if the user is registered in the pool, false otherwise.
*/
function checkHumanity(
bytes32 root,
uint currentTime,
uint appID,
uint expectedAppNullifier,
uint[8] memory proof
) public view returns (bool) {
require(isKnownRoot(root), "unknown root");
uint[2] memory a = [proof[0], proof[1]];
uint[2][2] memory b = [[proof[2], proof[3]], [proof[4], proof[5]]];
uint[2] memory c = [proof[6], proof[7]];
uint[5] memory inputs = [expectedAppNullifier, currentTime, appID, uint(root), uint(poh.submissionDuration())];
return humanityVerifier.verifyProof(a, b, c, inputs);
}
}