Skip to content

Commit d001dd9

Browse files
authored
Merge pull request #96 from recmo/recmo/chore-cleanup-verifier
Cleanup Verifier.sol and make error handling consistent
2 parents 3682496 + 2ddf0f0 commit d001dd9

File tree

7 files changed

+75
-148
lines changed

7 files changed

+75
-148
lines changed

contracts/base/SemaphoreCore.sol

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,32 @@ contract SemaphoreCore is ISemaphoreCore {
1414
/// It is used to prevent double-signaling.
1515
mapping(uint256 => bool) internal nullifierHashes;
1616

17-
/// @dev Returns true if no nullifier already exists and if the zero-knowledge proof is valid.
18-
/// Otherwise it returns false.
17+
/// @dev Asserts that no nullifier already exists and if the zero-knowledge proof is valid.
18+
/// Otherwise it reverts.
1919
/// @param signal: Semaphore signal.
2020
/// @param root: Root of the Merkle tree.
2121
/// @param nullifierHash: Nullifier hash.
2222
/// @param externalNullifier: External nullifier.
2323
/// @param proof: Zero-knowledge proof.
2424
/// @param verifier: Verifier address.
25-
function _isValidProof(
25+
function _verifyProof(
2626
bytes32 signal,
2727
uint256 root,
2828
uint256 nullifierHash,
2929
uint256 externalNullifier,
3030
uint256[8] calldata proof,
3131
IVerifier verifier
32-
) internal view returns (bool) {
32+
) internal view {
3333
require(!nullifierHashes[nullifierHash], "SemaphoreCore: you cannot use the same nullifier twice");
3434

3535
uint256 signalHash = _hashSignal(signal);
3636

37-
return
38-
verifier.verifyProof(
39-
[proof[0], proof[1]],
40-
[[proof[2], proof[3]], [proof[4], proof[5]]],
41-
[proof[6], proof[7]],
42-
[root, nullifierHash, signalHash, externalNullifier]
43-
);
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+
);
4443
}
4544

4645
/// @dev Stores the nullifier hash to prevent double-signaling.

contracts/base/Verifier.sol

Lines changed: 60 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,29 @@
99
// fixed linter warnings
1010
// added requiere error messages
1111
//
12+
// 2021 Remco Bloemen
13+
// cleaned up code
14+
// added InvalidProve() error
15+
// always revert with InvalidProof() on invalid proof
16+
// make Pairing strict
1217
//
1318
// SPDX-License-Identifier: GPL-3.0
1419
pragma solidity ^0.8.4;
1520

1621
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+
1730
struct G1Point {
1831
uint256 X;
1932
uint256 Y;
2033
}
34+
2135
// Encoding of field elements is: X[0] * z + X[1]
2236
struct G2Point {
2337
uint256[2] X;
@@ -31,7 +45,6 @@ library Pairing {
3145

3246
/// @return the generator of G2
3347
function P2() internal pure returns (G2Point memory) {
34-
// Original code point
3548
return
3649
G2Point(
3750
[
@@ -43,28 +56,21 @@ library Pairing {
4356
8495653923123431417604973247489272438418190587263600148770280649306958101930
4457
]
4558
);
46-
47-
/*
48-
// Changed by Jordi point
49-
return G2Point(
50-
[10857046999023057135944570762232829481370756359578518086990519993285655852781,
51-
11559732032986387107991004021392285783925812861821192530917403151452391805634],
52-
[8495653923123431417604973247489272438418190587263600148770280649306958101930,
53-
4082367875863433681332203403145435568316851327593401208105741076214120093531]
54-
);
55-
*/
5659
}
5760

5861
/// @return r the negation of p, i.e. p.addition(p.negate()) should be zero.
5962
function negate(G1Point memory p) internal pure returns (G1Point memory r) {
60-
// The prime q in the base field F_q for G1
61-
uint256 q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
6263
if (p.X == 0 && p.Y == 0) return G1Point(0, 0);
63-
return G1Point(p.X, q - (p.Y % q));
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);
6468
}
6569

6670
/// @return r the sum of two points of G1
6771
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.
6874
uint256[4] memory input;
6975
input[0] = p1.X;
7076
input[1] = p1.Y;
@@ -74,18 +80,16 @@ library Pairing {
7480
// solium-disable-next-line security/no-inline-assembly
7581
assembly {
7682
success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60)
77-
// Use "invalid" to make gas estimation work
78-
switch success
79-
case 0 {
80-
invalid()
81-
}
8283
}
83-
require(success, "pairing-add-failed");
84+
if (!success) revert InvalidProof();
8485
}
8586

8687
/// @return r the product of a point on G1 and a scalar, i.e.
8788
/// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p.
8889
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();
8993
uint256[3] memory input;
9094
input[0] = p.X;
9195
input[1] = p.Y;
@@ -94,21 +98,17 @@ library Pairing {
9498
// solium-disable-next-line security/no-inline-assembly
9599
assembly {
96100
success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60)
97-
// Use "invalid" to make gas estimation work
98-
switch success
99-
case 0 {
100-
invalid()
101-
}
102101
}
103-
require(success, "pairing-mul-failed");
102+
if (!success) revert InvalidProof();
104103
}
105104

106-
/// @return the result of computing the pairing check
105+
/// Asserts the pairing check
107106
/// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
108-
/// For example pairing([P1(), P1().negate()], [P2(), P2()]) should
109-
/// return true.
110-
function pairing(G1Point[] memory p1, G2Point[] memory p2) internal view returns (bool) {
111-
require(p1.length == p2.length, "pairing-lengths-failed");
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();
112112
uint256 elements = p1.length;
113113
uint256 inputSize = elements * 6;
114114
uint256[] memory input = new uint256[](inputSize);
@@ -125,86 +125,22 @@ library Pairing {
125125
// solium-disable-next-line security/no-inline-assembly
126126
assembly {
127127
success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
128-
// Use "invalid" to make gas estimation work
129-
switch success
130-
case 0 {
131-
invalid()
132-
}
133128
}
134-
require(success, "pairing-opcode-failed");
135-
return out[0] != 0;
136-
}
137-
138-
/// Convenience method for a pairing check for two pairs.
139-
function pairingProd2(
140-
G1Point memory a1,
141-
G2Point memory a2,
142-
G1Point memory b1,
143-
G2Point memory b2
144-
) internal view returns (bool) {
145-
G1Point[] memory p1 = new G1Point[](2);
146-
G2Point[] memory p2 = new G2Point[](2);
147-
p1[0] = a1;
148-
p1[1] = b1;
149-
p2[0] = a2;
150-
p2[1] = b2;
151-
return pairing(p1, p2);
152-
}
153-
154-
/// Convenience method for a pairing check for three pairs.
155-
function pairingProd3(
156-
G1Point memory a1,
157-
G2Point memory a2,
158-
G1Point memory b1,
159-
G2Point memory b2,
160-
G1Point memory c1,
161-
G2Point memory c2
162-
) internal view returns (bool) {
163-
G1Point[] memory p1 = new G1Point[](3);
164-
G2Point[] memory p2 = new G2Point[](3);
165-
p1[0] = a1;
166-
p1[1] = b1;
167-
p1[2] = c1;
168-
p2[0] = a2;
169-
p2[1] = b2;
170-
p2[2] = c2;
171-
return pairing(p1, p2);
172-
}
173-
174-
/// Convenience method for a pairing check for four pairs.
175-
function pairingProd4(
176-
G1Point memory a1,
177-
G2Point memory a2,
178-
G1Point memory b1,
179-
G2Point memory b2,
180-
G1Point memory c1,
181-
G2Point memory c2,
182-
G1Point memory d1,
183-
G2Point memory d2
184-
) internal view returns (bool) {
185-
G1Point[] memory p1 = new G1Point[](4);
186-
G2Point[] memory p2 = new G2Point[](4);
187-
p1[0] = a1;
188-
p1[1] = b1;
189-
p1[2] = c1;
190-
p1[3] = d1;
191-
p2[0] = a2;
192-
p2[1] = b2;
193-
p2[2] = c2;
194-
p2[3] = d2;
195-
return pairing(p1, p2);
129+
if (!success || out[0] != 1) revert InvalidProof();
196130
}
197131
}
198132

199133
contract Verifier {
200134
using Pairing for *;
135+
201136
struct VerifyingKey {
202137
Pairing.G1Point alfa1;
203138
Pairing.G2Point beta2;
204139
Pairing.G2Point gamma2;
205140
Pairing.G2Point delta2;
206141
Pairing.G1Point[] IC;
207142
}
143+
208144
struct Proof {
209145
Pairing.G1Point A;
210146
Pairing.G2Point B;
@@ -275,42 +211,40 @@ contract Verifier {
275211
);
276212
}
277213

278-
function verify(uint256[] memory input, Proof memory proof) internal view returns (uint256) {
279-
uint256 snark_scalar_field = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
280-
VerifyingKey memory vk = verifyingKey();
281-
require(input.length + 1 == vk.IC.length, "verifier-bad-input");
282-
// Compute the linear combination vk_x
283-
Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0);
284-
for (uint256 i = 0; i < input.length; i++) {
285-
require(input[i] < snark_scalar_field, "verifier-gte-snark-scalar-field");
286-
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i]));
287-
}
288-
vk_x = Pairing.addition(vk_x, vk.IC[0]);
289-
if (
290-
!Pairing.pairingProd4(Pairing.negate(proof.A), proof.B, vk.alfa1, vk.beta2, vk_x, vk.gamma2, proof.C, vk.delta2)
291-
) return 1;
292-
return 0;
293-
}
294-
295-
/// @return r bool true if proof is valid
214+
/// @dev Verifies a Semaphore proof. Reverts with InvalidProof if the proof is invalid.
296215
function verifyProof(
297216
uint256[2] memory a,
298217
uint256[2][2] memory b,
299218
uint256[2] memory c,
300219
uint256[4] memory input
301-
) public view returns (bool r) {
220+
) public view {
221+
// If the values are not in the correct range, the Pairing contract will revert.
302222
Proof memory proof;
303223
proof.A = Pairing.G1Point(a[0], a[1]);
304224
proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
305225
proof.C = Pairing.G1Point(c[0], c[1]);
306-
uint256[] memory inputValues = new uint256[](input.length);
307-
for (uint256 i = 0; i < input.length; i++) {
308-
inputValues[i] = input[i];
309-
}
310-
if (verify(inputValues, proof) == 0) {
311-
return true;
312-
} else {
313-
return false;
314-
}
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);
315249
}
316250
}

contracts/extensions/SemaphoreVoting.sol

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,7 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
8888
uint256 root = getRoot(pollId);
8989
IVerifier verifier = verifiers[depth];
9090

91-
require(
92-
_isValidProof(vote, root, nullifierHash, pollId, proof, verifier),
93-
"SemaphoreVoting: the proof is not valid"
94-
);
91+
_verifyProof(vote, root, nullifierHash, pollId, proof, verifier);
9592

9693
// Prevent double-voting (nullifierHash = hash(pollId + identityNullifier)).
9794
_saveNullifierHash(nullifierHash);

contracts/extensions/SemaphoreWhistleblowing.sol

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,7 @@ contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreCore, Sem
8181
uint256 root = getRoot(entityId);
8282
IVerifier verifier = verifiers[depth];
8383

84-
require(
85-
_isValidProof(leak, root, nullifierHash, entityId, proof, verifier),
86-
"SemaphoreWhistleblowing: the proof is not valid"
87-
);
84+
_verifyProof(leak, root, nullifierHash, entityId, proof, verifier);
8885

8986
emit LeakPublished(entityId, leak);
9087
}

contracts/interfaces/IVerifier.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ interface IVerifier {
99
uint256[2][2] memory b,
1010
uint256[2] memory c,
1111
uint256[4] memory input
12-
) external view returns (bool);
12+
) external view;
1313
}

test/SemaphoreVoting.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ describe("SemaphoreVoting", () => {
163163

164164
const transaction = contract.connect(accounts[1]).castVote(bytes32Vote, nullifierHash, pollIds[1], solidityProof)
165165

166-
await expect(transaction).to.be.revertedWith("SemaphoreVoting: the proof is not valid")
166+
await expect(transaction).to.be.revertedWith("InvalidProof()")
167167
})
168168

169169
it("Should cast a vote", async () => {

test/SemaphoreWhistleblowing.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ describe("SemaphoreWhistleblowing", () => {
158158
.connect(accounts[1])
159159
.publishLeak(bytes32Leak, nullifierHash, entityIds[1], solidityProof)
160160

161-
await expect(transaction).to.be.revertedWith("SemaphoreWhistleblowing: the proof is not valid")
161+
await expect(transaction).to.be.revertedWith("InvalidProof()")
162162
})
163163

164164
it("Should publish a leak", async () => {

0 commit comments

Comments
 (0)