@crisp-e3/contracts 0.15.0 → 0.17.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/contracts/CRISPOnchainVerifier.sol +2493 -0
- package/contracts/CRISPProgram.sol +223 -67
- package/contracts/CRISPVerifier.sol +524 -468
- package/contracts/Mocks/MockInterfold.sol +32 -2
- package/contracts/Mocks/MockVotesToken.sol +48 -0
- package/contracts/interfaces/IERC6372Clock.sol +16 -0
- package/contracts/interfaces/IHonkVerifier.sol +19 -0
- package/contracts/interfaces/IVotesToken.sol +17 -0
- package/package.json +4 -4
|
@@ -10,10 +10,14 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
|
|
|
10
10
|
import { IE3Program } from "@interfold/contracts/contracts/interfaces/IE3Program.sol";
|
|
11
11
|
import { IInterfold } from "@interfold/contracts/contracts/interfaces/IInterfold.sol";
|
|
12
12
|
import { E3 } from "@interfold/contracts/contracts/interfaces/IE3.sol";
|
|
13
|
+
import { Risc0ComputeProof } from "@interfold/contracts/contracts/lib/Risc0ComputeProof.sol";
|
|
13
14
|
import { LazyIMTData, InternalLazyIMT } from "@zk-kit/lazy-imt.sol/InternalLazyIMT.sol";
|
|
14
|
-
import {
|
|
15
|
+
import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
|
|
16
|
+
import { IHonkVerifier } from "./interfaces/IHonkVerifier.sol";
|
|
17
|
+
import { IVotesToken } from "./interfaces/IVotesToken.sol";
|
|
18
|
+
import { IERC6372Clock } from "./interfaces/IERC6372Clock.sol";
|
|
15
19
|
|
|
16
|
-
contract CRISPProgram is IE3Program, Ownable {
|
|
20
|
+
contract CRISPProgram is IE3Program, Ownable, EIP712 {
|
|
17
21
|
using InternalLazyIMT for LazyIMTData;
|
|
18
22
|
|
|
19
23
|
/// @notice Enum to represent credit modes
|
|
@@ -42,7 +46,12 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
42
46
|
/// @notice Derived from token balances by the coordinator. The default.
|
|
43
47
|
TOKEN,
|
|
44
48
|
/// @notice Supplied by the requester via `getCensus(uint256 e3Id) returns (address[])`.
|
|
45
|
-
BY_REQUESTER
|
|
49
|
+
BY_REQUESTER,
|
|
50
|
+
/// @notice Read from the token by this contract, one input at a time. No list is enumerated
|
|
51
|
+
/// and no root is posted, so there is no census producer to trust. `publishInput` calls
|
|
52
|
+
/// `getPastVotes` for the slot and passes the result to the circuit as a public input, which
|
|
53
|
+
/// is why this mode uses the `crisp_onchain` verifier rather than the `crisp` one.
|
|
54
|
+
ONCHAIN
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
/// @notice Struct to store all data related to a voting round
|
|
@@ -54,6 +63,16 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
54
63
|
uint256 numOptions;
|
|
55
64
|
CreditMode creditMode;
|
|
56
65
|
CensusMode censusMode;
|
|
66
|
+
/// @notice The token that voting power is read from. Only used by `CensusMode.ONCHAIN`.
|
|
67
|
+
address token;
|
|
68
|
+
/// @notice The smallest voting power that may cast an input. Only used by
|
|
69
|
+
/// `CensusMode.ONCHAIN`, where eligibility is checked per input instead of by a census.
|
|
70
|
+
uint256 minVotingPower;
|
|
71
|
+
/// @notice Credits given to each eligible voter under `CreditMode.CONSTANT`.
|
|
72
|
+
uint256 credits;
|
|
73
|
+
/// @notice The timepoint that voting power is read at, in the ERC-6372 clock units of the
|
|
74
|
+
/// token. Recorded when the round is requested, so it is the same for every input.
|
|
75
|
+
uint48 snapshot;
|
|
57
76
|
}
|
|
58
77
|
|
|
59
78
|
// Constants
|
|
@@ -74,7 +93,17 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
74
93
|
IInterfold public interfold;
|
|
75
94
|
IRiscZeroVerifier public risc0Verifier;
|
|
76
95
|
bytes32 public imageId;
|
|
77
|
-
|
|
96
|
+
/// @notice Verifies ballots for the census modes that prove membership of a Merkle tree.
|
|
97
|
+
IHonkVerifier private immutable honkVerifier;
|
|
98
|
+
/// @notice Verifies ballots for `CensusMode.ONCHAIN`, whose circuit has no Merkle inputs and
|
|
99
|
+
/// takes voting power as a public input instead.
|
|
100
|
+
IHonkVerifier private immutable onchainHonkVerifier;
|
|
101
|
+
|
|
102
|
+
/// @notice The EIP-712 type of the message a voter signs to authorise one ballot.
|
|
103
|
+
/// @dev The digest binds the signature to the round, the slot, and this exact ciphertext. The
|
|
104
|
+
/// chain id and this contract address come from the EIP-712 domain, so a signature cannot be
|
|
105
|
+
/// replayed onto another round, another slot, another ballot, or another deployment.
|
|
106
|
+
bytes32 private constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 e3Id,address slot,bytes32 ciphertextCommitment)");
|
|
78
107
|
|
|
79
108
|
// Mappings
|
|
80
109
|
mapping(uint256 e3Id => RoundData) e3Data;
|
|
@@ -93,6 +122,16 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
93
122
|
/// @notice A requester-supplied census names who may vote, not how much each vote weighs, so it
|
|
94
123
|
/// only has meaning when every voter carries the same credits.
|
|
95
124
|
error CensusModeRequiresConstantCredits();
|
|
125
|
+
/// @notice `CensusMode.ONCHAIN` reads voting power from a token, so it cannot run without one
|
|
126
|
+
/// that answers `getPastVotes`.
|
|
127
|
+
error CensusModeRequiresToken();
|
|
128
|
+
/// @notice An `ONCHAIN` round with constant credits must grant a non-zero allowance, or it
|
|
129
|
+
/// bounds every ballot to zero and can only accept masks.
|
|
130
|
+
error InvalidCredits();
|
|
131
|
+
/// @notice The slot holds less voting power than the round requires, so it cannot be written to.
|
|
132
|
+
/// @dev Raised for mask inputs as well as real votes. Under `CensusMode.ONCHAIN` this check
|
|
133
|
+
/// replaces the Merkle membership proof that gates both branches in the other modes.
|
|
134
|
+
error SlotNotEligible();
|
|
96
135
|
error InvalidCensusMode();
|
|
97
136
|
error SlotIsEmpty();
|
|
98
137
|
error MerkleRootNotSet();
|
|
@@ -100,6 +139,7 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
100
139
|
error InputDeadlinePassed(uint256 e3Id, uint256 deadline);
|
|
101
140
|
error KeyNotPublished(uint256 e3Id);
|
|
102
141
|
error E3NotAcceptingInputs(uint256 e3Id);
|
|
142
|
+
error InvalidComputeContext();
|
|
103
143
|
|
|
104
144
|
// Events
|
|
105
145
|
event InputPublished(uint256 indexed e3Id, bytes encryptedVote, uint256 index);
|
|
@@ -109,17 +149,38 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
109
149
|
/// @param _risc0Verifier The RISC Zero verifier address
|
|
110
150
|
/// @param _honkVerifier The honk verifier address
|
|
111
151
|
/// @param _imageId The image ID for the guest program
|
|
112
|
-
constructor(
|
|
152
|
+
constructor(
|
|
153
|
+
IInterfold _interfold,
|
|
154
|
+
IRiscZeroVerifier _risc0Verifier,
|
|
155
|
+
IHonkVerifier _honkVerifier,
|
|
156
|
+
IHonkVerifier _onchainHonkVerifier,
|
|
157
|
+
bytes32 _imageId
|
|
158
|
+
) Ownable(msg.sender) EIP712("CRISP", "1") {
|
|
113
159
|
if (address(_interfold) == address(0)) revert InterfoldAddressZero();
|
|
114
160
|
if (address(_risc0Verifier) == address(0)) revert Risc0VerifierAddressZero();
|
|
115
161
|
if (address(_honkVerifier) == address(0)) revert InvalidHonkVerifier();
|
|
162
|
+
if (address(_onchainHonkVerifier) == address(0)) revert InvalidHonkVerifier();
|
|
116
163
|
|
|
117
164
|
interfold = _interfold;
|
|
118
165
|
risc0Verifier = _risc0Verifier;
|
|
119
166
|
honkVerifier = _honkVerifier;
|
|
167
|
+
onchainHonkVerifier = _onchainHonkVerifier;
|
|
120
168
|
imageId = _imageId;
|
|
121
169
|
}
|
|
122
170
|
|
|
171
|
+
/// @notice The digest a voter signs to authorise one ballot.
|
|
172
|
+
/// @dev Computed for every input, and for mask inputs as well as real votes. The circuit ignores
|
|
173
|
+
/// it on the mask branch, but the contract must not skip it: a digest that were computed only
|
|
174
|
+
/// for real votes would let an observer tell the two apart on-chain, which is exactly what mask
|
|
175
|
+
/// inputs exist to prevent.
|
|
176
|
+
/// @param e3Id The E3 the ballot belongs to.
|
|
177
|
+
/// @param slot The slot address the ballot is written to.
|
|
178
|
+
/// @param ciphertextCommitment The commitment to the ballot ciphertext.
|
|
179
|
+
/// @return The EIP-712 digest.
|
|
180
|
+
function ballotDigest(uint256 e3Id, address slot, bytes32 ciphertextCommitment) public view returns (bytes32) {
|
|
181
|
+
return _hashTypedDataV4(keccak256(abi.encode(BALLOT_TYPEHASH, e3Id, slot, ciphertextCommitment)));
|
|
182
|
+
}
|
|
183
|
+
|
|
123
184
|
/// @notice Sets the Merkle root for an E3 program. Can only be set once.
|
|
124
185
|
/// @param _e3Id The E3 program ID
|
|
125
186
|
/// @param _root The Merkle root to set.
|
|
@@ -172,7 +233,7 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
172
233
|
paramsHash = round.paramsHash;
|
|
173
234
|
numOptions = round.numOptions;
|
|
174
235
|
creditMode = round.creditMode;
|
|
175
|
-
inputRoot = round.votes._root(
|
|
236
|
+
inputRoot = round.votes._root();
|
|
176
237
|
numberOfVotes = round.votes.numberOfLeaves;
|
|
177
238
|
}
|
|
178
239
|
|
|
@@ -197,34 +258,9 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
197
258
|
if (msg.sender != address(interfold) && msg.sender != owner()) revert CallerNotAuthorized();
|
|
198
259
|
if (e3Data[e3Id].paramsHash != bytes32(0)) revert E3AlreadyInitialized();
|
|
199
260
|
|
|
200
|
-
//
|
|
201
|
-
// stack limit that holding
|
|
202
|
-
|
|
203
|
-
// One decode, every field required. `censusMode` is read as a uint and range-checked rather
|
|
204
|
-
// than decoded straight into the enum, so an unrecognised value gives a named error instead
|
|
205
|
-
// of a bare panic.
|
|
206
|
-
(, , uint256 numOptions, CreditMode creditMode, , uint256 rawCensusMode) = abi.decode(
|
|
207
|
-
customParams,
|
|
208
|
-
(address, uint256, uint256, CreditMode, uint256, uint256)
|
|
209
|
-
);
|
|
210
|
-
// The circuit asserts `num_options <= MAX_OPTIONS`, so a round configured above it accepts no
|
|
211
|
-
// ballot at all. Reject at request time rather than stranding a round nobody can vote in.
|
|
212
|
-
if (numOptions < 2 || numOptions > MAX_VOTE_OPTIONS) revert InvalidNumOptions();
|
|
213
|
-
if (rawCensusMode > uint256(type(CensusMode).max)) revert InvalidCensusMode();
|
|
214
|
-
|
|
215
|
-
// Rejected here rather than by the coordinator, so a combination that can never work costs
|
|
216
|
-
// nothing: this reverts in the same transaction that requests the E3, before any fee is paid.
|
|
217
|
-
if (CensusMode(rawCensusMode) == CensusMode.BY_REQUESTER && creditMode != CreditMode.CONSTANT) {
|
|
218
|
-
revert CensusModeRequiresConstantCredits();
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// we need to know the number of options for decoding the tally
|
|
222
|
-
e3Data[e3Id].numOptions = numOptions;
|
|
223
|
-
// we want to save the credit mode so it can be verified on chain by everyone
|
|
224
|
-
e3Data[e3Id].creditMode = creditMode;
|
|
225
|
-
// recorded so anyone can verify which electorate the round was requested against
|
|
226
|
-
e3Data[e3Id].censusMode = CensusMode(rawCensusMode);
|
|
227
|
-
}
|
|
261
|
+
// Delegated to its own frame rather than scoped inline: `validate` is close enough to the
|
|
262
|
+
// stack limit that holding the six decoded values alongside the parameters exceeds it.
|
|
263
|
+
_initRound(e3Id, customParams);
|
|
228
264
|
|
|
229
265
|
e3Data[e3Id].paramsHash = keccak256(e3ProgramParams);
|
|
230
266
|
|
|
@@ -234,6 +270,79 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
234
270
|
return ENCRYPTION_SCHEME_ID;
|
|
235
271
|
}
|
|
236
272
|
|
|
273
|
+
/// @notice Decode the round configuration and record it.
|
|
274
|
+
/// @dev One decode, every field required. `censusMode` is read as a uint and range-checked
|
|
275
|
+
/// rather than decoded straight into the enum, so an unrecognised value gives a named error
|
|
276
|
+
/// instead of a bare panic.
|
|
277
|
+
/// @param e3Id The E3 being configured.
|
|
278
|
+
/// @param customParams The ABI-encoded round configuration.
|
|
279
|
+
function _initRound(uint256 e3Id, bytes calldata customParams) internal {
|
|
280
|
+
(address token, uint256 minVotingPower, uint256 numOptions, CreditMode creditMode, uint256 credits, uint256 rawCensusMode) = abi.decode(
|
|
281
|
+
customParams,
|
|
282
|
+
(address, uint256, uint256, CreditMode, uint256, uint256)
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// The circuit asserts `num_options <= MAX_OPTIONS`, so a round configured above it accepts no
|
|
286
|
+
// ballot at all. Reject at request time rather than stranding a round nobody can vote in.
|
|
287
|
+
if (numOptions < 2 || numOptions > MAX_VOTE_OPTIONS) revert InvalidNumOptions();
|
|
288
|
+
if (rawCensusMode > uint256(type(CensusMode).max)) revert InvalidCensusMode();
|
|
289
|
+
|
|
290
|
+
// Rejected here rather than by the coordinator, so a combination that can never work costs
|
|
291
|
+
// nothing: this reverts in the same transaction that requests the E3, before any fee is paid.
|
|
292
|
+
if (CensusMode(rawCensusMode) == CensusMode.BY_REQUESTER && creditMode != CreditMode.CONSTANT) {
|
|
293
|
+
revert CensusModeRequiresConstantCredits();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ONCHAIN reads every voter's power from this token, so a round without one accepts no ballot
|
|
297
|
+
// at all. Same reasoning as the numOptions bound: fail before the fee is paid.
|
|
298
|
+
if (CensusMode(rawCensusMode) == CensusMode.ONCHAIN && token == address(0)) {
|
|
299
|
+
revert CensusModeRequiresToken();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// An ONCHAIN round hands `credits` to the circuit as the voting-power bound, so zero credits
|
|
303
|
+
// bound every ballot to zero: only a mask would be accepted, and the round would tally
|
|
304
|
+
// nothing. Checked for ONCHAIN only — the Merkle modes take the bound from the census leaf,
|
|
305
|
+
// where `credits` never reaches the circuit and the contract has nothing to check.
|
|
306
|
+
if (CensusMode(rawCensusMode) == CensusMode.ONCHAIN && creditMode == CreditMode.CONSTANT && credits == 0) {
|
|
307
|
+
revert InvalidCredits();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
RoundData storage round = e3Data[e3Id];
|
|
311
|
+
// we need to know the number of options for decoding the tally
|
|
312
|
+
round.numOptions = numOptions;
|
|
313
|
+
// we want to save the credit mode so it can be verified on chain by everyone
|
|
314
|
+
round.creditMode = creditMode;
|
|
315
|
+
// recorded so anyone can verify which electorate the round was requested against
|
|
316
|
+
round.censusMode = CensusMode(rawCensusMode);
|
|
317
|
+
round.token = token;
|
|
318
|
+
round.minVotingPower = minVotingPower;
|
|
319
|
+
round.credits = credits;
|
|
320
|
+
|
|
321
|
+
// The snapshot is taken here rather than supplied by the requester. This function runs in the
|
|
322
|
+
// transaction that requests the E3, so `clock() - 1` is the last finalized timepoint of the
|
|
323
|
+
// round, and a requester cannot name a timepoint that suits it. Recording it once also makes
|
|
324
|
+
// every input of the round read the same electorate.
|
|
325
|
+
if (CensusMode(rawCensusMode) == CensusMode.ONCHAIN) {
|
|
326
|
+
// Checked before any call is attempted. A call to an address with no code succeeds and
|
|
327
|
+
// returns nothing, so `clock()` fails while decoding the empty return data rather than
|
|
328
|
+
// inside the call — and a decode failure is not what `try/catch` is there to catch. An EOA
|
|
329
|
+
// would otherwise be refused by a bare panic instead of a named error.
|
|
330
|
+
if (token.code.length == 0) revert CensusModeRequiresToken();
|
|
331
|
+
|
|
332
|
+
uint48 snapshot = _previousTimepoint(token);
|
|
333
|
+
|
|
334
|
+
// Probe the exact call every input will make. `_previousTimepoint` swallows a missing
|
|
335
|
+
// `clock()` and falls back to block numbers, which is right for a token that predates
|
|
336
|
+
// ERC-6372 but also lets an address that is not a votes token pass validation — and then
|
|
337
|
+
// every `publishInput` reverts inside `getPastVotes`, after the fee is paid.
|
|
338
|
+
try IVotesToken(token).getPastVotes(address(0), snapshot) returns (uint256) {} catch {
|
|
339
|
+
revert CensusModeRequiresToken();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
round.snapshot = snapshot;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
237
346
|
/// @inheritdoc IE3Program
|
|
238
347
|
function publishInput(uint256 e3Id, bytes memory data) external {
|
|
239
348
|
E3 memory e3 = interfold.getE3(e3Id);
|
|
@@ -254,9 +363,6 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
254
363
|
revert E3NotAcceptingInputs(e3Id);
|
|
255
364
|
}
|
|
256
365
|
|
|
257
|
-
// We need to ensure that the CRISP admin set the merkle root of the census.
|
|
258
|
-
if (e3Data[e3Id].merkleRoot == 0) revert MerkleRootNotSet();
|
|
259
|
-
|
|
260
366
|
if (data.length == 0) revert EmptyInputData();
|
|
261
367
|
|
|
262
368
|
(bytes memory noirProof, address slotAddress, bytes32 encryptedVoteCommitment, bytes memory encryptedVote) = abi.decode(
|
|
@@ -264,26 +370,81 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
264
370
|
(bytes, address, bytes32, bytes)
|
|
265
371
|
);
|
|
266
372
|
|
|
373
|
+
// The two census families differ here and nowhere else. A Merkle round proves membership
|
|
374
|
+
// inside the circuit against a posted root. An ONCHAIN round reads the power from the token
|
|
375
|
+
// and gives it to the circuit, so the eligibility check has to happen here instead.
|
|
376
|
+
(bytes32 eligibility, IHonkVerifier verifier) = _eligibility(e3Id, slotAddress);
|
|
377
|
+
|
|
267
378
|
(uint40 voteIndex, bytes32 previousEncryptedVoteCommitment) = _processVote(e3Id, slotAddress, encryptedVoteCommitment);
|
|
268
379
|
|
|
269
380
|
// Set the public inputs for the proof. Order must match Noir circuit.
|
|
270
|
-
bytes32[] memory noirPublicInputs = new bytes32[](
|
|
381
|
+
bytes32[] memory noirPublicInputs = new bytes32[](9);
|
|
271
382
|
noirPublicInputs[0] = previousEncryptedVoteCommitment;
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
383
|
+
// A Keccak digest does not fit in one field element, so it enters the circuit as its two
|
|
384
|
+
// 16-byte halves. The circuit rebuilds the 32 bytes with `digest_from_halves`.
|
|
385
|
+
{
|
|
386
|
+
uint256 digest = uint256(ballotDigest(e3Id, slotAddress, encryptedVoteCommitment));
|
|
387
|
+
noirPublicInputs[1] = bytes32(digest >> 128);
|
|
388
|
+
noirPublicInputs[2] = bytes32(digest & type(uint128).max);
|
|
389
|
+
}
|
|
390
|
+
noirPublicInputs[3] = bytes32(uint256(uint160(slotAddress)));
|
|
391
|
+
noirPublicInputs[4] = eligibility;
|
|
392
|
+
noirPublicInputs[5] = bytes32(uint256(previousEncryptedVoteCommitment == bytes32(0) ? 1 : 0));
|
|
393
|
+
noirPublicInputs[6] = bytes32(e3Data[e3Id].numOptions);
|
|
394
|
+
noirPublicInputs[7] = encryptedVoteCommitment;
|
|
395
|
+
noirPublicInputs[8] = e3.committeePublicKey;
|
|
278
396
|
|
|
279
397
|
// Check if the ciphertext was encrypted correctly
|
|
280
|
-
if (!
|
|
398
|
+
if (!verifier.verify(noirProof, noirPublicInputs)) {
|
|
281
399
|
revert InvalidNoirProof();
|
|
282
400
|
}
|
|
283
401
|
|
|
284
402
|
emit InputPublished(e3Id, encryptedVote, voteIndex);
|
|
285
403
|
}
|
|
286
404
|
|
|
405
|
+
/// @notice Resolve the eligibility public input and the verifier for a round.
|
|
406
|
+
/// @dev Returns the value that occupies index 4 of the circuit public inputs. The `crisp` and
|
|
407
|
+
/// `crisp_onchain` circuits agree on every other position, so this one value and the verifier
|
|
408
|
+
/// address are the whole difference between the two paths.
|
|
409
|
+
/// @param e3Id The E3 the input belongs to.
|
|
410
|
+
/// @param slotAddress The slot the input is written to.
|
|
411
|
+
/// @return eligibility The Merkle root of the census, or the voting power of the slot.
|
|
412
|
+
/// @return verifier The verifier that matches the circuit of this round.
|
|
413
|
+
function _eligibility(uint256 e3Id, address slotAddress) internal view returns (bytes32 eligibility, IHonkVerifier verifier) {
|
|
414
|
+
RoundData storage round = e3Data[e3Id];
|
|
415
|
+
|
|
416
|
+
if (round.censusMode != CensusMode.ONCHAIN) {
|
|
417
|
+
// We need to ensure that the CRISP admin set the merkle root of the census.
|
|
418
|
+
if (round.merkleRoot == 0) revert MerkleRootNotSet();
|
|
419
|
+
return (bytes32(round.merkleRoot), honkVerifier);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
uint256 power = IVotesToken(round.token).getPastVotes(slotAddress, round.snapshot);
|
|
423
|
+
|
|
424
|
+
// A slot with no power is never writable, whatever the round configured. Without this floor a
|
|
425
|
+
// round with `minVotingPower == 0` would let anyone mask any address, including addresses that
|
|
426
|
+
// never held the token.
|
|
427
|
+
uint256 threshold = round.minVotingPower == 0 ? 1 : round.minVotingPower;
|
|
428
|
+
if (power < threshold) revert SlotNotEligible();
|
|
429
|
+
|
|
430
|
+
// Eligibility comes from the power at the snapshot. The weight the circuit enforces comes from
|
|
431
|
+
// the credit mode, so a CONSTANT round gives every eligible slot the same credits.
|
|
432
|
+
return (bytes32(round.creditMode == CreditMode.CONSTANT ? round.credits : power), onchainHonkVerifier);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/// @notice The last finalized timepoint of a token, in its ERC-6372 clock units.
|
|
436
|
+
/// @dev Falls back to block numbers when the token has no `clock()`, which matches the default
|
|
437
|
+
/// ERC20Votes clock.
|
|
438
|
+
/// @param token The token to read the clock of.
|
|
439
|
+
/// @return The timepoint before the current one.
|
|
440
|
+
function _previousTimepoint(address token) internal view returns (uint48) {
|
|
441
|
+
try IERC6372Clock(token).clock() returns (uint48 current) {
|
|
442
|
+
return current == 0 ? 0 : current - 1;
|
|
443
|
+
} catch {
|
|
444
|
+
return uint48(block.number - 1);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
287
448
|
/// @notice Decode the tally from the plaintext output
|
|
288
449
|
/// @param e3Id The E3 program ID
|
|
289
450
|
/// @return votes - an array of vote counts for each option
|
|
@@ -342,17 +503,25 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
342
503
|
bytes32 ciphertextCommitment,
|
|
343
504
|
bytes memory proof
|
|
344
505
|
) external view override returns (bool) {
|
|
506
|
+
E3 memory e3 = interfold.getE3(e3Id);
|
|
345
507
|
bytes32 paramsHash = getParamsHash(e3Id);
|
|
508
|
+
bytes32 inputRoot = bytes32(e3Data[e3Id].votes._root());
|
|
509
|
+
Risc0ComputeProof.Proof memory computeProof = Risc0ComputeProof.decode(proof);
|
|
510
|
+
if (computeProof.paramsHash != paramsHash || computeProof.inputRoot != inputRoot) revert InvalidComputeContext();
|
|
511
|
+
|
|
512
|
+
bytes memory journal = Risc0ComputeProof.journal(
|
|
513
|
+
bytes32(block.chainid),
|
|
514
|
+
bytes32(uint256(uint160(address(interfold)))),
|
|
515
|
+
bytes32(e3Id),
|
|
516
|
+
e3.encryptionSchemeId,
|
|
517
|
+
e3.committeePublicKey,
|
|
518
|
+
ciphertextOutputHash,
|
|
519
|
+
ciphertextCommitment,
|
|
520
|
+
paramsHash,
|
|
521
|
+
inputRoot
|
|
522
|
+
);
|
|
346
523
|
|
|
347
|
-
|
|
348
|
-
bytes memory journal = new bytes(528); // (32 + 1) * 4 * 4
|
|
349
|
-
|
|
350
|
-
_encodeLengthPrefixAndHash(journal, 0, ciphertextOutputHash);
|
|
351
|
-
_encodeLengthPrefixAndHash(journal, 132, ciphertextCommitment);
|
|
352
|
-
_encodeLengthPrefixAndHash(journal, 264, paramsHash);
|
|
353
|
-
_encodeLengthPrefixAndHash(journal, 396, inputRoot);
|
|
354
|
-
|
|
355
|
-
risc0Verifier.verify(proof, imageId, sha256(journal));
|
|
524
|
+
risc0Verifier.verify(computeProof.seal, imageId, sha256(journal));
|
|
356
525
|
return true;
|
|
357
526
|
}
|
|
358
527
|
|
|
@@ -381,19 +550,6 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
381
550
|
}
|
|
382
551
|
}
|
|
383
552
|
|
|
384
|
-
/// @notice Encode length prefix and hash
|
|
385
|
-
/// @param journal The journal to encode into
|
|
386
|
-
/// @param startIndex The start index in the journal
|
|
387
|
-
/// @param hashVal The hash value to encode
|
|
388
|
-
function _encodeLengthPrefixAndHash(bytes memory journal, uint256 startIndex, bytes32 hashVal) internal pure {
|
|
389
|
-
journal[startIndex] = 0x20;
|
|
390
|
-
startIndex += 4;
|
|
391
|
-
|
|
392
|
-
for (uint256 i = 0; i < 32; i++) {
|
|
393
|
-
journal[startIndex + i * 4] = hashVal[i];
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
553
|
/// @notice Decode bytes to uint64 array
|
|
398
554
|
/// @param data The bytes to decode (must be multiple of 8)
|
|
399
555
|
/// @return result Array of uint64 values
|