@semaphore-protocol/contracts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Semaphore.sol ADDED
@@ -0,0 +1,96 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.4;
3
+
4
+ import "./interfaces/ISemaphore.sol";
5
+ import "./interfaces/IVerifier.sol";
6
+ import "./base/SemaphoreCore.sol";
7
+ import "./base/SemaphoreGroups.sol";
8
+
9
+ /// @title Semaphore
10
+ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
11
+ /// @dev Gets a tree depth and returns its verifier address.
12
+ mapping(uint8 => IVerifier) public verifiers;
13
+
14
+ /// @dev Gets a group id and returns the group admin address.
15
+ mapping(uint256 => address) public groupAdmins;
16
+
17
+ /// @dev Checks if the group admin is the transaction sender.
18
+ /// @param groupId: Id of the group.
19
+ modifier onlyGroupAdmin(uint256 groupId) {
20
+ require(groupAdmins[groupId] == _msgSender(), "Semaphore: caller is not the group admin");
21
+ _;
22
+ }
23
+
24
+ /// @dev Checks if there is a verifier for the given tree depth.
25
+ /// @param depth: Depth of the tree.
26
+ modifier onlySupportedDepth(uint8 depth) {
27
+ require(address(verifiers[depth]) != address(0), "Semaphore: tree depth is not supported");
28
+ _;
29
+ }
30
+
31
+ /// @dev Initializes the Semaphore verifiers used to verify the user's ZK proofs.
32
+ /// @param _verifiers: List of Semaphore verifiers (address and related Merkle tree depth).
33
+ constructor(Verifier[] memory _verifiers) {
34
+ for (uint8 i = 0; i < _verifiers.length; i++) {
35
+ verifiers[_verifiers[i].merkleTreeDepth] = IVerifier(_verifiers[i].contractAddress);
36
+ }
37
+ }
38
+
39
+ /// @dev See {ISemaphore-createGroup}.
40
+ function createGroup(
41
+ uint256 groupId,
42
+ uint8 depth,
43
+ uint256 zeroValue,
44
+ address admin
45
+ ) external override onlySupportedDepth(depth) {
46
+ _createGroup(groupId, depth, zeroValue);
47
+
48
+ groupAdmins[groupId] = admin;
49
+
50
+ emit GroupAdminUpdated(groupId, address(0), admin);
51
+ }
52
+
53
+ /// @dev See {ISemaphore-updateGroupAdmin}.
54
+ function updateGroupAdmin(uint256 groupId, address newAdmin) external override onlyGroupAdmin(groupId) {
55
+ groupAdmins[groupId] = newAdmin;
56
+
57
+ emit GroupAdminUpdated(groupId, _msgSender(), newAdmin);
58
+ }
59
+
60
+ /// @dev See {ISemaphore-addMember}.
61
+ function addMember(uint256 groupId, uint256 identityCommitment) external override onlyGroupAdmin(groupId) {
62
+ _addMember(groupId, identityCommitment);
63
+ }
64
+
65
+ /// @dev See {ISemaphore-removeMember}.
66
+ function removeMember(
67
+ uint256 groupId,
68
+ uint256 identityCommitment,
69
+ uint256[] calldata proofSiblings,
70
+ uint8[] calldata proofPathIndices
71
+ ) external override onlyGroupAdmin(groupId) {
72
+ _removeMember(groupId, identityCommitment, proofSiblings, proofPathIndices);
73
+ }
74
+
75
+ /// @dev See {ISemaphore-verifyProof}.
76
+ function verifyProof(
77
+ uint256 groupId,
78
+ bytes32 signal,
79
+ uint256 nullifierHash,
80
+ uint256 externalNullifier,
81
+ uint256[8] calldata proof
82
+ ) external override {
83
+ uint256 root = getRoot(groupId);
84
+ uint8 depth = getDepth(groupId);
85
+
86
+ require(depth != 0, "Semaphore: group does not exist");
87
+
88
+ IVerifier verifier = verifiers[depth];
89
+
90
+ _verifyProof(signal, root, nullifierHash, externalNullifier, proof, verifier);
91
+
92
+ _saveNullifierHash(nullifierHash);
93
+
94
+ emit ProofVerified(groupId, signal);
95
+ }
96
+ }
@@ -0,0 +1,4 @@
1
+ //SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.4;
3
+
4
+ uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
@@ -0,0 +1,59 @@
1
+ //SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.4;
3
+
4
+ import "../interfaces/ISemaphoreCore.sol";
5
+ import "../interfaces/IVerifier.sol";
6
+
7
+ /// @title Semaphore core contract.
8
+ /// @notice Minimal code to allow users to signal their endorsement of an arbitrary string.
9
+ /// @dev The following code verifies that the proof is correct and saves the hash of the
10
+ /// nullifier to prevent double-signaling. External nullifier and Merkle trees (i.e. groups) must be
11
+ /// managed externally.
12
+ contract SemaphoreCore is ISemaphoreCore {
13
+ /// @dev Gets a nullifier hash and returns true or false.
14
+ /// It is used to prevent double-signaling.
15
+ mapping(uint256 => bool) internal nullifierHashes;
16
+
17
+ /// @dev Asserts that no nullifier already exists and if the zero-knowledge proof is valid.
18
+ /// Otherwise it reverts.
19
+ /// @param signal: Semaphore signal.
20
+ /// @param root: Root of the Merkle tree.
21
+ /// @param nullifierHash: Nullifier hash.
22
+ /// @param externalNullifier: External nullifier.
23
+ /// @param proof: Zero-knowledge proof.
24
+ /// @param verifier: Verifier address.
25
+ function _verifyProof(
26
+ bytes32 signal,
27
+ uint256 root,
28
+ uint256 nullifierHash,
29
+ uint256 externalNullifier,
30
+ uint256[8] calldata proof,
31
+ IVerifier verifier
32
+ ) internal view {
33
+ require(!nullifierHashes[nullifierHash], "SemaphoreCore: you cannot use the same nullifier twice");
34
+
35
+ uint256 signalHash = _hashSignal(signal);
36
+
37
+ verifier.verifyProof(
38
+ [proof[0], proof[1]],
39
+ [[proof[2], proof[3]], [proof[4], proof[5]]],
40
+ [proof[6], proof[7]],
41
+ [root, nullifierHash, signalHash, externalNullifier]
42
+ );
43
+ }
44
+
45
+ /// @dev Stores the nullifier hash to prevent double-signaling.
46
+ /// Attention! Remember to call it when you verify a proof if you
47
+ /// need to prevent double-signaling.
48
+ /// @param nullifierHash: Semaphore nullifier hash.
49
+ function _saveNullifierHash(uint256 nullifierHash) internal {
50
+ nullifierHashes[nullifierHash] = true;
51
+ }
52
+
53
+ /// @dev Creates a keccak256 hash of the signal.
54
+ /// @param signal: Semaphore signal.
55
+ /// @return Hash of the signal.
56
+ function _hashSignal(bytes32 signal) private pure returns (uint256) {
57
+ return uint256(keccak256(abi.encodePacked(signal))) >> 8;
58
+ }
59
+ }
@@ -0,0 +1,83 @@
1
+ //SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.4;
3
+
4
+ import {SNARK_SCALAR_FIELD} from "./SemaphoreConstants.sol";
5
+ import "../interfaces/ISemaphoreGroups.sol";
6
+ import "@zk-kit/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol";
7
+ import "@openzeppelin/contracts/utils/Context.sol";
8
+
9
+ /// @title Semaphore groups contract.
10
+ /// @dev The following code allows you to create groups, add and remove members.
11
+ /// You can use getters to obtain informations about groups (root, depth, number of leaves).
12
+ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
13
+ using IncrementalBinaryTree for IncrementalTreeData;
14
+
15
+ /// @dev Gets a group id and returns the group/tree data.
16
+ mapping(uint256 => IncrementalTreeData) internal groups;
17
+
18
+ /// @dev Creates a new group by initializing the associated tree.
19
+ /// @param groupId: Id of the group.
20
+ /// @param depth: Depth of the tree.
21
+ /// @param zeroValue: Zero value of the tree.
22
+ function _createGroup(
23
+ uint256 groupId,
24
+ uint8 depth,
25
+ uint256 zeroValue
26
+ ) internal virtual {
27
+ require(groupId < SNARK_SCALAR_FIELD, "SemaphoreGroups: group id must be < SNARK_SCALAR_FIELD");
28
+ require(getDepth(groupId) == 0, "SemaphoreGroups: group already exists");
29
+
30
+ groups[groupId].init(depth, zeroValue);
31
+
32
+ emit GroupCreated(groupId, depth, zeroValue);
33
+ }
34
+
35
+ /// @dev Adds an identity commitment to an existing group.
36
+ /// @param groupId: Id of the group.
37
+ /// @param identityCommitment: New identity commitment.
38
+ function _addMember(uint256 groupId, uint256 identityCommitment) internal virtual {
39
+ require(getDepth(groupId) != 0, "SemaphoreGroups: group does not exist");
40
+
41
+ groups[groupId].insert(identityCommitment);
42
+
43
+ uint256 root = getRoot(groupId);
44
+
45
+ emit MemberAdded(groupId, identityCommitment, root);
46
+ }
47
+
48
+ /// @dev Removes an identity commitment from an existing group. A proof of membership is
49
+ /// needed to check if the node to be deleted is part of the tree.
50
+ /// @param groupId: Id of the group.
51
+ /// @param identityCommitment: Existing identity commitment to be deleted.
52
+ /// @param proofSiblings: Array of the sibling nodes of the proof of membership.
53
+ /// @param proofPathIndices: Path of the proof of membership.
54
+ function _removeMember(
55
+ uint256 groupId,
56
+ uint256 identityCommitment,
57
+ uint256[] calldata proofSiblings,
58
+ uint8[] calldata proofPathIndices
59
+ ) internal virtual {
60
+ require(getDepth(groupId) != 0, "SemaphoreGroups: group does not exist");
61
+
62
+ groups[groupId].remove(identityCommitment, proofSiblings, proofPathIndices);
63
+
64
+ uint256 root = getRoot(groupId);
65
+
66
+ emit MemberRemoved(groupId, identityCommitment, root);
67
+ }
68
+
69
+ /// @dev See {ISemaphoreGroups-getRoot}.
70
+ function getRoot(uint256 groupId) public view virtual override returns (uint256) {
71
+ return groups[groupId].root;
72
+ }
73
+
74
+ /// @dev See {ISemaphoreGroups-getDepth}.
75
+ function getDepth(uint256 groupId) public view virtual override returns (uint8) {
76
+ return groups[groupId].depth;
77
+ }
78
+
79
+ /// @dev See {ISemaphoreGroups-getNumberOfLeaves}.
80
+ function getNumberOfLeaves(uint256 groupId) public view virtual override returns (uint256) {
81
+ return groups[groupId].numberOfLeaves;
82
+ }
83
+ }
@@ -0,0 +1,250 @@
1
+ //
2
+ // Copyright 2017 Christian Reitwiessner
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+ // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
5
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
6
+ //
7
+ // 2019 OKIMS
8
+ // ported to solidity 0.6
9
+ // fixed linter warnings
10
+ // added requiere error messages
11
+ //
12
+ // 2021 Remco Bloemen
13
+ // cleaned up code
14
+ // added InvalidProve() error
15
+ // always revert with InvalidProof() on invalid proof
16
+ // make Pairing strict
17
+ //
18
+ // SPDX-License-Identifier: GPL-3.0
19
+ pragma solidity ^0.8.4;
20
+
21
+ library Pairing {
22
+ error InvalidProof();
23
+
24
+ // The prime q in the base field F_q for G1
25
+ uint256 constant BASE_MODULUS = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
26
+
27
+ // The prime moludus of the scalar field of G1.
28
+ uint256 constant SCALAR_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
29
+
30
+ struct G1Point {
31
+ uint256 X;
32
+ uint256 Y;
33
+ }
34
+
35
+ // Encoding of field elements is: X[0] * z + X[1]
36
+ struct G2Point {
37
+ uint256[2] X;
38
+ uint256[2] Y;
39
+ }
40
+
41
+ /// @return the generator of G1
42
+ function P1() internal pure returns (G1Point memory) {
43
+ return G1Point(1, 2);
44
+ }
45
+
46
+ /// @return the generator of G2
47
+ function P2() internal pure returns (G2Point memory) {
48
+ return
49
+ G2Point(
50
+ [
51
+ 11559732032986387107991004021392285783925812861821192530917403151452391805634,
52
+ 10857046999023057135944570762232829481370756359578518086990519993285655852781
53
+ ],
54
+ [
55
+ 4082367875863433681332203403145435568316851327593401208105741076214120093531,
56
+ 8495653923123431417604973247489272438418190587263600148770280649306958101930
57
+ ]
58
+ );
59
+ }
60
+
61
+ /// @return r the negation of p, i.e. p.addition(p.negate()) should be zero.
62
+ function negate(G1Point memory p) internal pure returns (G1Point memory r) {
63
+ if (p.X == 0 && p.Y == 0) return G1Point(0, 0);
64
+ // Validate input or revert
65
+ if (p.X >= BASE_MODULUS || p.Y >= BASE_MODULUS) revert InvalidProof();
66
+ // We know p.Y > 0 and p.Y < BASE_MODULUS.
67
+ return G1Point(p.X, BASE_MODULUS - p.Y);
68
+ }
69
+
70
+ /// @return r the sum of two points of G1
71
+ function addition(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) {
72
+ // By EIP-196 all input is validated to be less than the BASE_MODULUS and form points
73
+ // on the curve.
74
+ uint256[4] memory input;
75
+ input[0] = p1.X;
76
+ input[1] = p1.Y;
77
+ input[2] = p2.X;
78
+ input[3] = p2.Y;
79
+ bool success;
80
+ // solium-disable-next-line security/no-inline-assembly
81
+ assembly {
82
+ success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60)
83
+ }
84
+ if (!success) revert InvalidProof();
85
+ }
86
+
87
+ /// @return r the product of a point on G1 and a scalar, i.e.
88
+ /// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p.
89
+ function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) {
90
+ // By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and
91
+ // form a valid point on the curve. But the scalar is not verified, so we do that explicitelly.
92
+ if (s >= SCALAR_MODULUS) revert InvalidProof();
93
+ uint256[3] memory input;
94
+ input[0] = p.X;
95
+ input[1] = p.Y;
96
+ input[2] = s;
97
+ bool success;
98
+ // solium-disable-next-line security/no-inline-assembly
99
+ assembly {
100
+ success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60)
101
+ }
102
+ if (!success) revert InvalidProof();
103
+ }
104
+
105
+ /// Asserts the pairing check
106
+ /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
107
+ /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should succeed
108
+ function pairingCheck(G1Point[] memory p1, G2Point[] memory p2) internal view {
109
+ // By EIP-197 all input is verified to be less than the BASE_MODULUS and form elements in their
110
+ // respective groups of the right order.
111
+ if (p1.length != p2.length) revert InvalidProof();
112
+ uint256 elements = p1.length;
113
+ uint256 inputSize = elements * 6;
114
+ uint256[] memory input = new uint256[](inputSize);
115
+ for (uint256 i = 0; i < elements; i++) {
116
+ input[i * 6 + 0] = p1[i].X;
117
+ input[i * 6 + 1] = p1[i].Y;
118
+ input[i * 6 + 2] = p2[i].X[0];
119
+ input[i * 6 + 3] = p2[i].X[1];
120
+ input[i * 6 + 4] = p2[i].Y[0];
121
+ input[i * 6 + 5] = p2[i].Y[1];
122
+ }
123
+ uint256[1] memory out;
124
+ bool success;
125
+ // solium-disable-next-line security/no-inline-assembly
126
+ assembly {
127
+ success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
128
+ }
129
+ if (!success || out[0] != 1) revert InvalidProof();
130
+ }
131
+ }
132
+
133
+ contract Verifier {
134
+ using Pairing for *;
135
+
136
+ struct VerifyingKey {
137
+ Pairing.G1Point alfa1;
138
+ Pairing.G2Point beta2;
139
+ Pairing.G2Point gamma2;
140
+ Pairing.G2Point delta2;
141
+ Pairing.G1Point[] IC;
142
+ }
143
+
144
+ struct Proof {
145
+ Pairing.G1Point A;
146
+ Pairing.G2Point B;
147
+ Pairing.G1Point C;
148
+ }
149
+
150
+ function verifyingKey() internal pure returns (VerifyingKey memory vk) {
151
+ vk.alfa1 = Pairing.G1Point(
152
+ 20491192805390485299153009773594534940189261866228447918068658471970481763042,
153
+ 9383485363053290200918347156157836566562967994039712273449902621266178545958
154
+ );
155
+
156
+ vk.beta2 = Pairing.G2Point(
157
+ [
158
+ 4252822878758300859123897981450591353533073413197771768651442665752259397132,
159
+ 6375614351688725206403948262868962793625744043794305715222011528459656738731
160
+ ],
161
+ [
162
+ 21847035105528745403288232691147584728191162732299865338377159692350059136679,
163
+ 10505242626370262277552901082094356697409835680220590971873171140371331206856
164
+ ]
165
+ );
166
+ vk.gamma2 = Pairing.G2Point(
167
+ [
168
+ 11559732032986387107991004021392285783925812861821192530917403151452391805634,
169
+ 10857046999023057135944570762232829481370756359578518086990519993285655852781
170
+ ],
171
+ [
172
+ 4082367875863433681332203403145435568316851327593401208105741076214120093531,
173
+ 8495653923123431417604973247489272438418190587263600148770280649306958101930
174
+ ]
175
+ );
176
+ vk.delta2 = Pairing.G2Point(
177
+ [
178
+ 12599857379517512478445603412764121041984228075771497593287716170335433683702,
179
+ 7912208710313447447762395792098481825752520616755888860068004689933335666613
180
+ ],
181
+ [
182
+ 11502426145685875357967720478366491326865907869902181704031346886834786027007,
183
+ 21679208693936337484429571887537508926366191105267550375038502782696042114705
184
+ ]
185
+ );
186
+ vk.IC = new Pairing.G1Point[](5);
187
+
188
+ vk.IC[0] = Pairing.G1Point(
189
+ 19918517214839406678907482305035208173510172567546071380302965459737278553528,
190
+ 7151186077716310064777520690144511885696297127165278362082219441732663131220
191
+ );
192
+
193
+ vk.IC[1] = Pairing.G1Point(
194
+ 690581125971423619528508316402701520070153774868732534279095503611995849608,
195
+ 21271996888576045810415843612869789314680408477068973024786458305950370465558
196
+ );
197
+
198
+ vk.IC[2] = Pairing.G1Point(
199
+ 16461282535702132833442937829027913110152135149151199860671943445720775371319,
200
+ 2814052162479976678403678512565563275428791320557060777323643795017729081887
201
+ );
202
+
203
+ vk.IC[3] = Pairing.G1Point(
204
+ 4319780315499060392574138782191013129592543766464046592208884866569377437627,
205
+ 13920930439395002698339449999482247728129484070642079851312682993555105218086
206
+ );
207
+
208
+ vk.IC[4] = Pairing.G1Point(
209
+ 3554830803181375418665292545416227334138838284686406179598687755626325482686,
210
+ 5951609174746846070367113593675211691311013364421437923470787371738135276998
211
+ );
212
+ }
213
+
214
+ /// @dev Verifies a Semaphore proof. Reverts with InvalidProof if the proof is invalid.
215
+ function verifyProof(
216
+ uint256[2] memory a,
217
+ uint256[2][2] memory b,
218
+ uint256[2] memory c,
219
+ uint256[4] memory input
220
+ ) public view {
221
+ // If the values are not in the correct range, the Pairing contract will revert.
222
+ Proof memory proof;
223
+ proof.A = Pairing.G1Point(a[0], a[1]);
224
+ proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
225
+ proof.C = Pairing.G1Point(c[0], c[1]);
226
+
227
+ VerifyingKey memory vk = verifyingKey();
228
+
229
+ // Compute the linear combination vk_x of inputs times IC
230
+ if (input.length + 1 != vk.IC.length) revert Pairing.InvalidProof();
231
+ Pairing.G1Point memory vk_x = vk.IC[0];
232
+ vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[1], input[0]));
233
+ vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[2], input[1]));
234
+ vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[3], input[2]));
235
+ vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[4], input[3]));
236
+
237
+ // Check pairing
238
+ Pairing.G1Point[] memory p1 = new Pairing.G1Point[](4);
239
+ Pairing.G2Point[] memory p2 = new Pairing.G2Point[](4);
240
+ p1[0] = Pairing.negate(proof.A);
241
+ p2[0] = proof.B;
242
+ p1[1] = vk.alfa1;
243
+ p2[1] = vk.beta2;
244
+ p1[2] = vk_x;
245
+ p2[2] = vk.gamma2;
246
+ p1[3] = proof.C;
247
+ p2[3] = vk.delta2;
248
+ Pairing.pairingCheck(p1, p2);
249
+ }
250
+ }
@@ -0,0 +1,107 @@
1
+ //SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.4;
3
+
4
+ import "../interfaces/ISemaphoreVoting.sol";
5
+ import "../base/SemaphoreCore.sol";
6
+ import "../base/SemaphoreGroups.sol";
7
+
8
+ /// @title Semaphore voting contract.
9
+ /// @dev The following code allows you to create polls, add voters and allow them to vote anonymously.
10
+ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
11
+ /// @dev Gets a tree depth and returns its verifier address.
12
+ mapping(uint8 => IVerifier) internal verifiers;
13
+
14
+ /// @dev Gets a poll id and returns the poll data.
15
+ mapping(uint256 => Poll) internal polls;
16
+
17
+ /// @dev Since there can be multiple verifier contracts (each associated with a certain tree depth),
18
+ /// it is necessary to pass the addresses of the previously deployed contracts with the associated
19
+ /// tree depth. Depending on the depth chosen when creating the poll, a certain verifier will be
20
+ /// used to verify that the proof is correct.
21
+ /// @param depths: Three depths used in verifiers.
22
+ /// @param verifierAddresses: Verifier addresses.
23
+ constructor(uint8[] memory depths, address[] memory verifierAddresses) {
24
+ require(
25
+ depths.length == verifierAddresses.length,
26
+ "SemaphoreVoting: parameters lists does not have the same length"
27
+ );
28
+
29
+ for (uint8 i = 0; i < depths.length; i++) {
30
+ verifiers[depths[i]] = IVerifier(verifierAddresses[i]);
31
+ }
32
+ }
33
+
34
+ /// @dev Checks if the poll coordinator is the transaction sender.
35
+ /// @param pollId: Id of the poll.
36
+ modifier onlyCoordinator(uint256 pollId) {
37
+ require(polls[pollId].coordinator == _msgSender(), "SemaphoreVoting: caller is not the poll coordinator");
38
+ _;
39
+ }
40
+
41
+ /// @dev See {ISemaphoreVoting-createPoll}.
42
+ function createPoll(
43
+ uint256 pollId,
44
+ address coordinator,
45
+ uint8 depth
46
+ ) public override {
47
+ require(address(verifiers[depth]) != address(0), "SemaphoreVoting: depth value is not supported");
48
+
49
+ _createGroup(pollId, depth, 0);
50
+
51
+ Poll memory poll;
52
+
53
+ poll.coordinator = coordinator;
54
+
55
+ polls[pollId] = poll;
56
+
57
+ emit PollCreated(pollId, coordinator);
58
+ }
59
+
60
+ /// @dev See {ISemaphoreVoting-addVoter}.
61
+ function addVoter(uint256 pollId, uint256 identityCommitment) public override onlyCoordinator(pollId) {
62
+ require(polls[pollId].state == PollState.Created, "SemaphoreVoting: voters can only be added before voting");
63
+
64
+ _addMember(pollId, identityCommitment);
65
+ }
66
+
67
+ /// @dev See {ISemaphoreVoting-addVoter}.
68
+ function startPoll(uint256 pollId, uint256 encryptionKey) public override onlyCoordinator(pollId) {
69
+ require(polls[pollId].state == PollState.Created, "SemaphoreVoting: poll has already been started");
70
+
71
+ polls[pollId].state = PollState.Ongoing;
72
+
73
+ emit PollStarted(pollId, _msgSender(), encryptionKey);
74
+ }
75
+
76
+ /// @dev See {ISemaphoreVoting-castVote}.
77
+ function castVote(
78
+ bytes32 vote,
79
+ uint256 nullifierHash,
80
+ uint256 pollId,
81
+ uint256[8] calldata proof
82
+ ) public override onlyCoordinator(pollId) {
83
+ Poll memory poll = polls[pollId];
84
+
85
+ require(poll.state == PollState.Ongoing, "SemaphoreVoting: vote can only be cast in an ongoing poll");
86
+
87
+ uint8 depth = getDepth(pollId);
88
+ uint256 root = getRoot(pollId);
89
+ IVerifier verifier = verifiers[depth];
90
+
91
+ _verifyProof(vote, root, nullifierHash, pollId, proof, verifier);
92
+
93
+ // Prevent double-voting (nullifierHash = hash(pollId + identityNullifier)).
94
+ _saveNullifierHash(nullifierHash);
95
+
96
+ emit VoteAdded(pollId, vote);
97
+ }
98
+
99
+ /// @dev See {ISemaphoreVoting-publishDecryptionKey}.
100
+ function endPoll(uint256 pollId, uint256 decryptionKey) public override onlyCoordinator(pollId) {
101
+ require(polls[pollId].state == PollState.Ongoing, "SemaphoreVoting: poll is not ongoing");
102
+
103
+ polls[pollId].state = PollState.Ended;
104
+
105
+ emit PollEnded(pollId, _msgSender(), decryptionKey);
106
+ }
107
+ }
@@ -0,0 +1,88 @@
1
+ //SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.4;
3
+
4
+ import "../interfaces/ISemaphoreWhistleblowing.sol";
5
+ import "../base/SemaphoreCore.sol";
6
+ import "../base/SemaphoreGroups.sol";
7
+
8
+ /// @title Semaphore whistleblowing contract.
9
+ /// @dev The following code allows you to create entities for whistleblowers (e.g. non-profit
10
+ /// organization, newspaper) and to allow them to publish news leaks anonymously.
11
+ /// Leaks can be IPFS hashes, permanent links or other kinds of reference.
12
+ contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreCore, SemaphoreGroups {
13
+ /// @dev Gets a tree depth and returns its verifier address.
14
+ mapping(uint8 => IVerifier) internal verifiers;
15
+
16
+ /// @dev Gets an editor address and return their entity.
17
+ mapping(address => uint256) private entities;
18
+
19
+ /// @dev Since there can be multiple verifier contracts (each associated with a certain tree depth),
20
+ /// it is necessary to pass the addresses of the previously deployed contracts with the associated
21
+ /// tree depth. Depending on the depth chosen when creating the entity, a certain verifier will be
22
+ /// used to verify that the proof is correct.
23
+ /// @param depths: Three depths used in verifiers.
24
+ /// @param verifierAddresses: Verifier addresses.
25
+ constructor(uint8[] memory depths, address[] memory verifierAddresses) {
26
+ require(
27
+ depths.length == verifierAddresses.length,
28
+ "SemaphoreWhistleblowing: parameters lists does not have the same length"
29
+ );
30
+
31
+ for (uint8 i = 0; i < depths.length; i++) {
32
+ verifiers[depths[i]] = IVerifier(verifierAddresses[i]);
33
+ }
34
+ }
35
+
36
+ /// @dev Checks if the editor is the transaction sender.
37
+ /// @param entityId: Id of the entity.
38
+ modifier onlyEditor(uint256 entityId) {
39
+ require(entityId == entities[_msgSender()], "SemaphoreWhistleblowing: caller is not the editor");
40
+ _;
41
+ }
42
+
43
+ /// @dev See {ISemaphoreWhistleblowing-createEntity}.
44
+ function createEntity(
45
+ uint256 entityId,
46
+ address editor,
47
+ uint8 depth
48
+ ) public override {
49
+ require(address(verifiers[depth]) != address(0), "SemaphoreWhistleblowing: depth value is not supported");
50
+
51
+ _createGroup(entityId, depth, 0);
52
+
53
+ entities[editor] = entityId;
54
+
55
+ emit EntityCreated(entityId, editor);
56
+ }
57
+
58
+ /// @dev See {ISemaphoreWhistleblowing-addWhistleblower}.
59
+ function addWhistleblower(uint256 entityId, uint256 identityCommitment) public override onlyEditor(entityId) {
60
+ _addMember(entityId, identityCommitment);
61
+ }
62
+
63
+ /// @dev See {ISemaphoreWhistleblowing-removeWhistleblower}.
64
+ function removeWhistleblower(
65
+ uint256 entityId,
66
+ uint256 identityCommitment,
67
+ uint256[] calldata proofSiblings,
68
+ uint8[] calldata proofPathIndices
69
+ ) public override onlyEditor(entityId) {
70
+ _removeMember(entityId, identityCommitment, proofSiblings, proofPathIndices);
71
+ }
72
+
73
+ /// @dev See {ISemaphoreWhistleblowing-publishLeak}.
74
+ function publishLeak(
75
+ bytes32 leak,
76
+ uint256 nullifierHash,
77
+ uint256 entityId,
78
+ uint256[8] calldata proof
79
+ ) public override onlyEditor(entityId) {
80
+ uint8 depth = getDepth(entityId);
81
+ uint256 root = getRoot(entityId);
82
+ IVerifier verifier = verifiers[depth];
83
+
84
+ _verifyProof(leak, root, nullifierHash, entityId, proof, verifier);
85
+
86
+ emit LeakPublished(entityId, leak);
87
+ }
88
+ }
@@ -0,0 +1,72 @@
1
+ //SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.4;
3
+
4
+ /// @title Semaphore interface.
5
+ /// @dev Interface of a Semaphore contract.
6
+ interface ISemaphore {
7
+ struct Verifier {
8
+ address contractAddress;
9
+ uint8 merkleTreeDepth;
10
+ }
11
+
12
+ /// @dev Emitted when an admin is assigned to a group.
13
+ /// @param groupId: Id of the group.
14
+ /// @param oldAdmin: Old admin of the group.
15
+ /// @param newAdmin: New admin of the group.
16
+ event GroupAdminUpdated(uint256 indexed groupId, address indexed oldAdmin, address indexed newAdmin);
17
+
18
+ /// @dev Emitted when a Semaphore proof is verified.
19
+ /// @param groupId: Id of the group.
20
+ /// @param signal: Semaphore signal.
21
+ event ProofVerified(uint256 indexed groupId, bytes32 signal);
22
+
23
+ /// @dev Saves the nullifier hash to avoid double signaling and emits an event
24
+ /// if the zero-knowledge proof is valid.
25
+ /// @param groupId: Id of the group.
26
+ /// @param signal: Semaphore signal.
27
+ /// @param nullifierHash: Nullifier hash.
28
+ /// @param externalNullifier: External nullifier.
29
+ /// @param proof: Zero-knowledge proof.
30
+ function verifyProof(
31
+ uint256 groupId,
32
+ bytes32 signal,
33
+ uint256 nullifierHash,
34
+ uint256 externalNullifier,
35
+ uint256[8] calldata proof
36
+ ) external;
37
+
38
+ /// @dev Creates a new group. Only the admin will be able to add or remove members.
39
+ /// @param groupId: Id of the group.
40
+ /// @param depth: Depth of the tree.
41
+ /// @param zeroValue: Zero value of the tree.
42
+ /// @param admin: Admin of the group.
43
+ function createGroup(
44
+ uint256 groupId,
45
+ uint8 depth,
46
+ uint256 zeroValue,
47
+ address admin
48
+ ) external;
49
+
50
+ /// @dev Updates the group admin.
51
+ /// @param groupId: Id of the group.
52
+ /// @param newAdmin: New admin of the group.
53
+ function updateGroupAdmin(uint256 groupId, address newAdmin) external;
54
+
55
+ /// @dev Adds a new member to an existing group.
56
+ /// @param groupId: Id of the group.
57
+ /// @param identityCommitment: New identity commitment.
58
+ function addMember(uint256 groupId, uint256 identityCommitment) external;
59
+
60
+ /// @dev Removes a member from an existing group. A proof of membership is
61
+ /// needed to check if the node to be removed is part of the tree.
62
+ /// @param groupId: Id of the group.
63
+ /// @param identityCommitment: Identity commitment to be deleted.
64
+ /// @param proofSiblings: Array of the sibling nodes of the proof of membership.
65
+ /// @param proofPathIndices: Path of the proof of membership.
66
+ function removeMember(
67
+ uint256 groupId,
68
+ uint256 identityCommitment,
69
+ uint256[] calldata proofSiblings,
70
+ uint8[] calldata proofPathIndices
71
+ ) external;
72
+ }
@@ -0,0 +1,10 @@
1
+ //SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.4;
3
+
4
+ /// @title SemaphoreCore interface.
5
+ /// @dev Interface of SemaphoreCore contract.
6
+ interface ISemaphoreCore {
7
+ /// @notice Emitted when a proof is verified correctly and a new nullifier hash is added.
8
+ /// @param nullifierHash: Hash of external and identity nullifiers.
9
+ event NullifierHashAdded(uint256 nullifierHash);
10
+ }
@@ -0,0 +1,39 @@
1
+ //SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.4;
3
+
4
+ /// @title SemaphoreGroups interface.
5
+ /// @dev Interface of a SemaphoreGroups contract.
6
+ interface ISemaphoreGroups {
7
+ /// @dev Emitted when a new group is created.
8
+ /// @param groupId: Id of the group.
9
+ /// @param depth: Depth of the tree.
10
+ /// @param zeroValue: Zero value of the tree.
11
+ event GroupCreated(uint256 indexed groupId, uint8 depth, uint256 zeroValue);
12
+
13
+ /// @dev Emitted when a new identity commitment is added.
14
+ /// @param groupId: Group id of the group.
15
+ /// @param identityCommitment: New identity commitment.
16
+ /// @param root: New root hash of the tree.
17
+ event MemberAdded(uint256 indexed groupId, uint256 identityCommitment, uint256 root);
18
+
19
+ /// @dev Emitted when a new identity commitment is removed.
20
+ /// @param groupId: Group id of the group.
21
+ /// @param identityCommitment: New identity commitment.
22
+ /// @param root: New root hash of the tree.
23
+ event MemberRemoved(uint256 indexed groupId, uint256 identityCommitment, uint256 root);
24
+
25
+ /// @dev Returns the last root hash of a group.
26
+ /// @param groupId: Id of the group.
27
+ /// @return Root hash of the group.
28
+ function getRoot(uint256 groupId) external view returns (uint256);
29
+
30
+ /// @dev Returns the depth of the tree of a group.
31
+ /// @param groupId: Id of the group.
32
+ /// @return Depth of the group tree.
33
+ function getDepth(uint256 groupId) external view returns (uint8);
34
+
35
+ /// @dev Returns the number of tree leaves of a group.
36
+ /// @param groupId: Id of the group.
37
+ /// @return Number of tree leaves.
38
+ function getNumberOfLeaves(uint256 groupId) external view returns (uint256);
39
+ }
@@ -0,0 +1,14 @@
1
+ //SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.4;
3
+
4
+ /// @title SemaphoreNullifiers interface.
5
+ /// @dev Interface of SemaphoreNullifiers contract.
6
+ interface ISemaphoreNullifiers {
7
+ /// @dev Emitted when a external nullifier is added.
8
+ /// @param externalNullifier: External Semaphore nullifier.
9
+ event ExternalNullifierAdded(uint256 externalNullifier);
10
+
11
+ /// @dev Emitted when a external nullifier is removed.
12
+ /// @param externalNullifier: External Semaphore nullifier.
13
+ event ExternalNullifierRemoved(uint256 externalNullifier);
14
+ }
@@ -0,0 +1,76 @@
1
+ //SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.4;
3
+
4
+ /// @title SemaphoreVoting interface.
5
+ /// @dev Interface of SemaphoreVoting contract.
6
+ interface ISemaphoreVoting {
7
+ enum PollState {
8
+ Created,
9
+ Ongoing,
10
+ Ended
11
+ }
12
+
13
+ struct Poll {
14
+ address coordinator;
15
+ PollState state;
16
+ }
17
+
18
+ /// @dev Emitted when a new poll is created.
19
+ /// @param pollId: Id of the poll.
20
+ /// @param coordinator: Coordinator of the poll.
21
+ event PollCreated(uint256 pollId, address indexed coordinator);
22
+
23
+ /// @dev Emitted when a poll is started.
24
+ /// @param pollId: Id of the poll.
25
+ /// @param coordinator: Coordinator of the poll.
26
+ /// @param encryptionKey: Key to encrypt the poll votes.
27
+ event PollStarted(uint256 pollId, address indexed coordinator, uint256 encryptionKey);
28
+
29
+ /// @dev Emitted when a user votes on a poll.
30
+ /// @param pollId: Id of the poll.
31
+ /// @param vote: User encrypted vote.
32
+ event VoteAdded(uint256 indexed pollId, bytes32 vote);
33
+
34
+ /// @dev Emitted when a poll is ended.
35
+ /// @param pollId: Id of the poll.
36
+ /// @param coordinator: Coordinator of the poll.
37
+ /// @param decryptionKey: Key to decrypt the poll votes.
38
+ event PollEnded(uint256 pollId, address indexed coordinator, uint256 decryptionKey);
39
+
40
+ /// @dev Creates a poll and the associated Merkle tree/group.
41
+ /// @param pollId: Id of the poll.
42
+ /// @param coordinator: Coordinator of the poll.
43
+ /// @param depth: Depth of the tree.
44
+ function createPoll(
45
+ uint256 pollId,
46
+ address coordinator,
47
+ uint8 depth
48
+ ) external;
49
+
50
+ /// @dev Adds a voter to a poll.
51
+ /// @param pollId: Id of the poll.
52
+ /// @param identityCommitment: Identity commitment of the group member.
53
+ function addVoter(uint256 pollId, uint256 identityCommitment) external;
54
+
55
+ /// @dev Starts a pull and publishes the key to encrypt the votes.
56
+ /// @param pollId: Id of the poll.
57
+ /// @param encryptionKey: Key to encrypt poll votes.
58
+ function startPoll(uint256 pollId, uint256 encryptionKey) external;
59
+
60
+ /// @dev Casts an anonymous vote in a poll.
61
+ /// @param vote: Encrypted vote.
62
+ /// @param nullifierHash: Nullifier hash.
63
+ /// @param pollId: Id of the poll.
64
+ /// @param proof: Private zk-proof parameters.
65
+ function castVote(
66
+ bytes32 vote,
67
+ uint256 nullifierHash,
68
+ uint256 pollId,
69
+ uint256[8] calldata proof
70
+ ) external;
71
+
72
+ /// @dev Ends a pull and publishes the key to decrypt the votes.
73
+ /// @param pollId: Id of the poll.
74
+ /// @param decryptionKey: Key to decrypt poll votes.
75
+ function endPoll(uint256 pollId, uint256 decryptionKey) external;
76
+ }
@@ -0,0 +1,55 @@
1
+ //SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.4;
3
+
4
+ /// @title SemaphoreWhistleblowing interface.
5
+ /// @dev Interface of SemaphoreWhistleblowing contract.
6
+ interface ISemaphoreWhistleblowing {
7
+ /// @dev Emitted when a new entity is created.
8
+ /// @param entityId: Id of the entity.
9
+ /// @param editor: Editor of the entity.
10
+ event EntityCreated(uint256 entityId, address indexed editor);
11
+
12
+ /// @dev Emitted when a whistleblower publish a new leak.
13
+ /// @param entityId: Id of the entity.
14
+ /// @param leak: News leak.
15
+ event LeakPublished(uint256 indexed entityId, bytes32 leak);
16
+
17
+ /// @dev Creates an entity and the associated Merkle tree/group.
18
+ /// @param entityId: Id of the entity.
19
+ /// @param editor: Editor of the entity.
20
+ /// @param depth: Depth of the tree.
21
+ function createEntity(
22
+ uint256 entityId,
23
+ address editor,
24
+ uint8 depth
25
+ ) external;
26
+
27
+ /// @dev Adds a whistleblower to an entity.
28
+ /// @param entityId: Id of the entity.
29
+ /// @param identityCommitment: Identity commitment of the group member.
30
+ function addWhistleblower(uint256 entityId, uint256 identityCommitment) external;
31
+
32
+ /// @dev Removes a whistleblower from an entity.
33
+ /// @param entityId: Id of the entity.
34
+ /// @param identityCommitment: Identity commitment of the group member.
35
+ /// @param proofSiblings: Array of the sibling nodes of the proof of membership.
36
+ /// @param proofPathIndices: Path of the proof of membership.
37
+ function removeWhistleblower(
38
+ uint256 entityId,
39
+ uint256 identityCommitment,
40
+ uint256[] calldata proofSiblings,
41
+ uint8[] calldata proofPathIndices
42
+ ) external;
43
+
44
+ /// @dev Allows whistleblowers to publish leaks anonymously.
45
+ /// @param leak: News leak.
46
+ /// @param nullifierHash: Nullifier hash.
47
+ /// @param entityId: Id of the entity.
48
+ /// @param proof: Private zk-proof parameters.
49
+ function publishLeak(
50
+ bytes32 leak,
51
+ uint256 nullifierHash,
52
+ uint256 entityId,
53
+ uint256[8] calldata proof
54
+ ) external;
55
+ }
@@ -0,0 +1,13 @@
1
+ //SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.4;
3
+
4
+ /// @title Verifier interface.
5
+ /// @dev Interface of Verifier contract.
6
+ interface IVerifier {
7
+ function verifyProof(
8
+ uint256[2] memory a,
9
+ uint256[2][2] memory b,
10
+ uint256[2] memory c,
11
+ uint256[4] memory input
12
+ ) external view;
13
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@semaphore-protocol/contracts",
3
+ "version": "0.1.0",
4
+ "description": "A privacy gadget for creating anonymous proof of membership on Ethereum.",
5
+ "license": "MIT",
6
+ "files": [
7
+ "**/*.sol"
8
+ ],
9
+ "keywords": [
10
+ "blockchain",
11
+ "ethereum",
12
+ "hardhat",
13
+ "smart-contracts",
14
+ "semaphore",
15
+ "identity",
16
+ "solidity",
17
+ "zero-knowledge",
18
+ "zk-snarks",
19
+ "zero-knowledge-proofs",
20
+ "circom",
21
+ "proof-of-membership"
22
+ ],
23
+ "homepage": "https://github.com/appliedzkp/semaphore.git#readme",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/appliedzkp/semaphore.git.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/appliedzkp/semaphore.git/issues"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "dependencies": {
35
+ "@openzeppelin/contracts": "^4.4.2",
36
+ "@zk-kit/incremental-merkle-tree.sol": "^0.3.1"
37
+ }
38
+ }