Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
honk_zk_contract.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Planned, auditors: [], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
9#include <iostream>
10
11// Source code for the Ultrahonk Solidity verifier.
12// It's expected that the AcirComposer will inject a library which will load the verification key into memory.
13// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
14static const char HONK_ZK_CONTRACT_SOURCE[] = R"(
15pragma solidity ^0.8.27;
16
17interface IVerifier {
18 function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool);
19}
20
25library Errors {
26 error ValueGeLimbMax();
27 error ValueGeGroupOrder();
28 error ValueGeFieldOrder();
29
30 error InvertOfZero();
31 error NotPowerOfTwo();
32 error ModExpFailed();
33
34 error ProofLengthWrong();
35 error ProofLengthWrongWithLogN(uint256 logN, uint256 actualLength, uint256 expectedLength);
36 error PublicInputsLengthWrong();
37 error SumcheckFailed();
38 error ShpleminiFailed();
39
40 error PointAtInfinity();
41
42 error ConsistencyCheckFailed();
43 error GeminiChallengeInSubgroup();
44}
45
46type Fr is uint256;
47
48using {add as +} for Fr global;
49using {sub as -} for Fr global;
50using {mul as *} for Fr global;
51
52using {notEqual as !=} for Fr global;
53using {equal as ==} for Fr global;
54
55uint256 constant SUBGROUP_SIZE = 256;
56uint256 constant MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617; // Prime field order
57uint256 constant P = MODULUS;
58Fr constant SUBGROUP_GENERATOR = Fr.wrap(0x07b0c561a6148404f086204a9f36ffb0617942546750f230c893619174a57a76);
59Fr constant SUBGROUP_GENERATOR_INVERSE = Fr.wrap(0x204bd3277422fad364751ad938e2b5e6a54cf8c68712848a692c553d0329f5d6);
60Fr constant MINUS_ONE = Fr.wrap(MODULUS - 1);
61Fr constant ONE = Fr.wrap(1);
62Fr constant ZERO = Fr.wrap(0);
63
64// SmallSubgroupIPA opening-claim layout — mirrors SMALL_IPA_CLAIMS in
65// barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa_utils.hpp.
66uint256 constant NUM_SMALL_IPA_OPENING_CLAIMS = 5;
67uint256 constant SMALL_IPA_BOUNDARY_OPENING_IDX = 3;
68uint256 constant NUM_SMALL_IPA_TRANSCRIPT_EVALS = 4;
69// Instantiation
70
71library FrLib {
72 bytes4 internal constant FRLIB_MODEXP_FAILED_SELECTOR = 0xf8d61709;
73
74 function invert(Fr value) internal view returns (Fr) {
75 uint256 v = Fr.unwrap(value);
76 require(v != 0, Errors.InvertOfZero());
77
78 uint256 result;
79
80 // Call the modexp precompile to invert in the field
81 assembly {
82 let free := mload(0x40)
83 mstore(free, 0x20)
84 mstore(add(free, 0x20), 0x20)
85 mstore(add(free, 0x40), 0x20)
86 mstore(add(free, 0x60), v)
87 mstore(add(free, 0x80), sub(MODULUS, 2))
88 mstore(add(free, 0xa0), MODULUS)
89 let success := staticcall(gas(), 0x05, free, 0xc0, 0x00, 0x20)
90 if iszero(success) {
91 mstore(0x00, FRLIB_MODEXP_FAILED_SELECTOR)
92 revert(0, 0x04)
93 }
94 result := mload(0x00)
95 mstore(0x40, add(free, 0xc0))
96 }
97
98 return Fr.wrap(result);
99 }
100
101 function pow(Fr base, uint256 v) internal view returns (Fr) {
102 uint256 b = Fr.unwrap(base);
103 // Only works for power of 2
104 require(v > 0 && (v & (v - 1)) == 0, Errors.NotPowerOfTwo());
105 uint256 result;
106
107 // Call the modexp precompile to invert in the field
108 assembly {
109 let free := mload(0x40)
110 mstore(free, 0x20)
111 mstore(add(free, 0x20), 0x20)
112 mstore(add(free, 0x40), 0x20)
113 mstore(add(free, 0x60), b)
114 mstore(add(free, 0x80), v)
115 mstore(add(free, 0xa0), MODULUS)
116 let success := staticcall(gas(), 0x05, free, 0xc0, 0x00, 0x20)
117 if iszero(success) {
118 mstore(0x00, FRLIB_MODEXP_FAILED_SELECTOR)
119 revert(0, 0x04)
120 }
121 result := mload(0x00)
122 mstore(0x40, add(free, 0xc0))
123 }
124
125 return Fr.wrap(result);
126 }
127
128 function div(Fr numerator, Fr denominator) internal view returns (Fr) {
129 unchecked {
130 return numerator * invert(denominator);
131 }
132 }
133
134 function sqr(Fr value) internal pure returns (Fr) {
135 unchecked {
136 return value * value;
137 }
138 }
139
140 function unwrap(Fr value) internal pure returns (uint256) {
141 unchecked {
142 return Fr.unwrap(value);
143 }
144 }
145
146 function neg(Fr value) internal pure returns (Fr) {
147 unchecked {
148 return Fr.wrap(MODULUS - Fr.unwrap(value));
149 }
150 }
151
152 function from(uint256 value) internal pure returns (Fr) {
153 unchecked {
154 require(value < MODULUS, Errors.ValueGeFieldOrder());
155 return Fr.wrap(value);
156 }
157 }
158
159 function fromBytes32(bytes32 value) internal pure returns (Fr) {
160 unchecked {
161 uint256 v = uint256(value);
162 require(v < MODULUS, Errors.ValueGeFieldOrder());
163 return Fr.wrap(v);
164 }
165 }
166
167 function toBytes32(Fr value) internal pure returns (bytes32) {
168 unchecked {
169 return bytes32(Fr.unwrap(value));
170 }
171 }
172}
173
174// Free functions
175function add(Fr a, Fr b) pure returns (Fr) {
176 unchecked {
177 return Fr.wrap(addmod(Fr.unwrap(a), Fr.unwrap(b), MODULUS));
178 }
179}
180
181function mul(Fr a, Fr b) pure returns (Fr) {
182 unchecked {
183 return Fr.wrap(mulmod(Fr.unwrap(a), Fr.unwrap(b), MODULUS));
184 }
185}
186
187function sub(Fr a, Fr b) pure returns (Fr) {
188 unchecked {
189 return Fr.wrap(addmod(Fr.unwrap(a), MODULUS - Fr.unwrap(b), MODULUS));
190 }
191}
192
193function notEqual(Fr a, Fr b) pure returns (bool) {
194 unchecked {
195 return Fr.unwrap(a) != Fr.unwrap(b);
196 }
197}
198
199function equal(Fr a, Fr b) pure returns (bool) {
200 unchecked {
201 return Fr.unwrap(a) == Fr.unwrap(b);
202 }
203}
204
205uint256 constant CONST_PROOF_SIZE_LOG_N = 25;
206
207uint256 constant NUMBER_OF_SUBRELATIONS = 31;
208uint256 constant BATCHED_RELATION_PARTIAL_LENGTH = 8;
209uint256 constant ZK_BATCHED_RELATION_PARTIAL_LENGTH = 9;
210uint256 constant NUMBER_OF_ENTITIES = 41;
211// The number of entities added for ZK (gemini_masking_poly)
212uint256 constant NUM_MASKING_POLYNOMIALS = 1;
213uint256 constant NUMBER_OF_ENTITIES_ZK = NUMBER_OF_ENTITIES + NUM_MASKING_POLYNOMIALS;
214uint256 constant NUMBER_UNSHIFTED = 36;
215uint256 constant NUMBER_UNSHIFTED_ZK = NUMBER_UNSHIFTED + NUM_MASKING_POLYNOMIALS;
216uint256 constant NUMBER_TO_BE_SHIFTED = 5;
217uint256 constant PAIRING_POINTS_SIZE = 8;
218
219uint256 constant FIELD_ELEMENT_SIZE = 0x20;
220uint256 constant GROUP_ELEMENT_SIZE = 0x40;
221
222// Powers of alpha used to batch subrelations (alpha, alpha^2, ..., alpha^(NUM_SUBRELATIONS-1))
223uint256 constant NUMBER_OF_ALPHAS = NUMBER_OF_SUBRELATIONS - 1;
224
225// Must match UltraFlavor_Generated::EntityId order.
226enum WIRE {
227 SIGMA_1,
228 SIGMA_2,
229 SIGMA_3,
230 SIGMA_4,
231 ID_1,
232 ID_2,
233 ID_3,
234 ID_4,
235 LAGRANGE_FIRST,
236 LAGRANGE_LAST,
237 Q_LOOKUP,
238 TABLE_1,
239 TABLE_2,
240 TABLE_3,
241 TABLE_4,
242 Q_M,
243 Q_R,
244 Q_O,
245 Q_C,
246 Q_L,
247 Q_4,
248 Q_ARITH,
249 Q_RANGE,
250 Q_ELLIPTIC,
251 Q_MEMORY,
252 Q_NNF,
253 Q_POSEIDON2_EXTERNAL,
254 Q_POSEIDON2_INTERNAL,
255 W_L,
256 W_R,
257 W_O,
258 W_4,
259 Z_PERM,
260 LOOKUP_INVERSES,
261 LOOKUP_READ_COUNTS,
262 LOOKUP_READ_TAGS,
263 W_L_SHIFT,
264 W_R_SHIFT,
265 W_O_SHIFT,
266 W_4_SHIFT,
267 Z_PERM_SHIFT
268}
269
270library Honk {
271 struct G1Point {
272 uint256 x;
273 uint256 y;
274 }
275
276 struct VerificationKey {
277 // Misc Params
278 uint256 circuitSize;
279 uint256 logCircuitSize;
280 uint256 publicInputsSize;
281 // Selectors
282 G1Point qm;
283 G1Point qc;
284 G1Point ql;
285 G1Point qr;
286 G1Point qo;
287 G1Point q4;
288 G1Point qLookup; // Lookup
289 G1Point qArith; // Arithmetic widget
290 G1Point qDeltaRange; // Delta Range sort
291 G1Point qMemory; // Memory
292 G1Point qNnf; // Non-native Field
293 G1Point qElliptic; // Auxillary
294 G1Point qPoseidon2External;
295 G1Point qPoseidon2Internal;
296 // Copy constraints
297 G1Point s1;
298 G1Point s2;
299 G1Point s3;
300 G1Point s4;
301 // Copy identity
302 G1Point id1;
303 G1Point id2;
304 G1Point id3;
305 G1Point id4;
306 // Precomputed lookup table
307 G1Point t1;
308 G1Point t2;
309 G1Point t3;
310 G1Point t4;
311 // Fixed first and last
312 G1Point lagrangeFirst;
313 G1Point lagrangeLast;
314 }
315
316 struct RelationParameters {
317 // challenges
318 Fr eta;
319 Fr romLogupGamma; // ROM-LogUp additive offset
320 Fr beta;
321 Fr gamma;
322 // derived
323 Fr publicInputsDelta;
324 }
325
326 struct Proof {
327 // Pairing point object
328 Fr[PAIRING_POINTS_SIZE] pairingPointObject;
329 // Free wires
330 G1Point w1;
331 G1Point w2;
332 G1Point w3;
333 G1Point w4;
334 // Lookup helpers - Permutations
335 G1Point zPerm;
336 // Lookup helpers - logup
337 G1Point lookupReadCounts;
338 G1Point lookupReadTags;
339 G1Point lookupInverses;
340 // Sumcheck
341 Fr[BATCHED_RELATION_PARTIAL_LENGTH][CONST_PROOF_SIZE_LOG_N] sumcheckUnivariates;
342 Fr[NUMBER_OF_ENTITIES] sumcheckEvaluations;
343 // Shplemini
344 G1Point[CONST_PROOF_SIZE_LOG_N - 1] geminiFoldComms;
345 Fr[CONST_PROOF_SIZE_LOG_N] geminiAEvaluations;
346 G1Point shplonkQ;
347 G1Point kzgQuotient;
348 }
349
351 struct ZKProof {
352 // Pairing point object
353 Fr[PAIRING_POINTS_SIZE] pairingPointObject;
354 // ZK: Gemini masking polynomial commitment (sent first, right after public inputs)
355 G1Point geminiMaskingPoly;
356 // Commitments to wire polynomials
357 G1Point w1;
358 G1Point w2;
359 G1Point w3;
360 G1Point w4;
361 // Commitments to logup witness polynomials
362 G1Point lookupReadCounts;
363 G1Point lookupReadTags;
364 G1Point lookupInverses;
365 // Commitment to grand permutation polynomial
366 G1Point zPerm;
367 G1Point[3] libraCommitments;
368 // Sumcheck
369 Fr libraSum;
370 Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH][CONST_PROOF_SIZE_LOG_N] sumcheckUnivariates;
371 Fr libraEvaluation;
372 Fr[NUMBER_OF_ENTITIES_ZK] sumcheckEvaluations; // Includes gemini_masking_poly eval at index 0 (first position)
373 // Shplemini
374 G1Point[CONST_PROOF_SIZE_LOG_N - 1] geminiFoldComms;
375 Fr[CONST_PROOF_SIZE_LOG_N] geminiAEvaluations;
376 Fr[4] libraPolyEvals;
377 G1Point shplonkQ;
378 G1Point kzgQuotient;
379 }
380}
381
382// ZKTranscript library to generate fiat shamir challenges, the ZK transcript only differest
384struct ZKTranscript {
385 // Oink
386 Honk.RelationParameters relationParameters;
387 Fr[NUMBER_OF_ALPHAS] alphas; // Powers of alpha: [alpha, alpha^2, ..., alpha^(NUM_SUBRELATIONS-1)]
388 Fr[CONST_PROOF_SIZE_LOG_N] gateChallenges;
389 // Sumcheck
390 Fr libraChallenge;
391 Fr[CONST_PROOF_SIZE_LOG_N] sumCheckUChallenges;
392 // Shplemini
393 Fr rho;
394 Fr geminiR;
395 Fr shplonkNu;
396 Fr shplonkZ;
397 // Derived
398 Fr publicInputsDelta;
399}
400
401library ZKTranscriptLib {
402 function generateTranscript(
403 Honk.ZKProof memory proof,
404 bytes32[] calldata publicInputs,
405 uint256 vkHash,
406 uint256 publicInputsSize,
407 uint256 logN
408 ) external pure returns (ZKTranscript memory t) {
409 Fr previousChallenge;
410 (t.relationParameters, previousChallenge) =
411 generateRelationParametersChallenges(proof, publicInputs, vkHash, publicInputsSize, previousChallenge);
412
413 (t.alphas, previousChallenge) = generateAlphaChallenges(previousChallenge, proof);
414
415 (t.gateChallenges, previousChallenge) = generateGateChallenges(previousChallenge, logN);
416 (t.libraChallenge, previousChallenge) = generateLibraChallenge(previousChallenge, proof);
417 (t.sumCheckUChallenges, previousChallenge) = generateSumcheckChallenges(proof, previousChallenge, logN);
418
419 (t.rho, previousChallenge) = generateRhoChallenge(proof, previousChallenge);
420
421 (t.geminiR, previousChallenge) = generateGeminiRChallenge(proof, previousChallenge, logN);
422
423 (t.shplonkNu, previousChallenge) = generateShplonkNuChallenge(proof, previousChallenge, logN);
424
425 (t.shplonkZ, previousChallenge) = generateShplonkZChallenge(proof, previousChallenge);
426 return t;
427 }
428
429 function generateRelationParametersChallenges(
430 Honk.ZKProof memory proof,
431 bytes32[] calldata publicInputs,
432 uint256 vkHash,
433 uint256 publicInputsSize,
434 Fr previousChallenge
435 ) internal pure returns (Honk.RelationParameters memory rp, Fr nextPreviousChallenge) {
436 (rp.eta, rp.romLogupGamma, previousChallenge) =
437 generateEtaChallenge(proof, publicInputs, vkHash, publicInputsSize);
438
439 (rp.beta, rp.gamma, nextPreviousChallenge) = generateBetaGammaChallenges(previousChallenge, proof);
440 }
441
442 function generateEtaChallenge(
443 Honk.ZKProof memory proof,
444 bytes32[] calldata publicInputs,
445 uint256 vkHash,
446 uint256 publicInputsSize
447 ) internal pure returns (Fr eta, Fr romLogupGamma, Fr previousChallenge) {
448 // Size: 1 (vkHash) + publicInputsSize + 8 (geminiMask(2) + 3 wires(6))
449 bytes32[] memory round0 = new bytes32[](1 + publicInputsSize + 8);
450 round0[0] = bytes32(vkHash);
451
452 for (uint256 i = 0; i < publicInputsSize - PAIRING_POINTS_SIZE; i++) {
453 require(uint256(publicInputs[i]) < P, Errors.ValueGeFieldOrder());
454 round0[1 + i] = publicInputs[i];
455 }
456 for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) {
457 round0[1 + publicInputsSize - PAIRING_POINTS_SIZE + i] = FrLib.toBytes32(proof.pairingPointObject[i]);
458 }
459
460 // For ZK flavors: hash the gemini masking poly commitment (sent right after public inputs)
461 round0[1 + publicInputsSize] = bytes32(proof.geminiMaskingPoly.x);
462 round0[1 + publicInputsSize + 1] = bytes32(proof.geminiMaskingPoly.y);
463
464 // Create the first challenge
465 // Note: w4 is added to the challenge later on
466 round0[1 + publicInputsSize + 2] = bytes32(proof.w1.x);
467 round0[1 + publicInputsSize + 3] = bytes32(proof.w1.y);
468 round0[1 + publicInputsSize + 4] = bytes32(proof.w2.x);
469 round0[1 + publicInputsSize + 5] = bytes32(proof.w2.y);
470 round0[1 + publicInputsSize + 6] = bytes32(proof.w3.x);
471 round0[1 + publicInputsSize + 7] = bytes32(proof.w3.y);
472
473 eta = FrLib.from(uint256(keccak256(abi.encodePacked(round0))) % P);
474 romLogupGamma = FrLib.from(uint256(keccak256(abi.encodePacked(Fr.unwrap(eta)))) % P);
475 previousChallenge = romLogupGamma;
476 }
477
478 function generateBetaGammaChallenges(Fr previousChallenge, Honk.ZKProof memory proof)
479 internal
480 pure
481 returns (Fr beta, Fr gamma, Fr nextPreviousChallenge)
482 {
483 bytes32[7] memory round1;
484 round1[0] = FrLib.toBytes32(previousChallenge);
485 round1[1] = bytes32(proof.lookupReadCounts.x);
486 round1[2] = bytes32(proof.lookupReadCounts.y);
487 round1[3] = bytes32(proof.lookupReadTags.x);
488 round1[4] = bytes32(proof.lookupReadTags.y);
489 round1[5] = bytes32(proof.w4.x);
490 round1[6] = bytes32(proof.w4.y);
491
492 beta = FrLib.from(uint256(keccak256(abi.encodePacked(round1))) % P);
493 gamma = FrLib.from(uint256(keccak256(abi.encodePacked(Fr.unwrap(beta)))) % P);
494 nextPreviousChallenge = gamma;
495 }
496
497 // Alpha challenges non-linearise the gate contributions
498 function generateAlphaChallenges(Fr previousChallenge, Honk.ZKProof memory proof)
499 internal
500 pure
501 returns (Fr[NUMBER_OF_ALPHAS] memory alphas, Fr nextPreviousChallenge)
502 {
503 // Generate the original sumcheck alpha 0 by hashing zPerm and zLookup
504 uint256[5] memory alpha0;
505 alpha0[0] = Fr.unwrap(previousChallenge);
506 alpha0[1] = proof.lookupInverses.x;
507 alpha0[2] = proof.lookupInverses.y;
508 alpha0[3] = proof.zPerm.x;
509 alpha0[4] = proof.zPerm.y;
510
511 nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(alpha0))) % P);
512 Fr alpha = nextPreviousChallenge;
513
514 // Compute powers of alpha for batching subrelations
515 alphas[0] = alpha;
516 for (uint256 i = 1; i < NUMBER_OF_ALPHAS; i++) {
517 alphas[i] = alphas[i - 1] * alpha;
518 }
519 }
520
521 function generateGateChallenges(Fr previousChallenge, uint256 logN)
522 internal
523 pure
524 returns (Fr[CONST_PROOF_SIZE_LOG_N] memory gateChallenges, Fr nextPreviousChallenge)
525 {
526 previousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge)))) % P);
527 gateChallenges[0] = previousChallenge;
528 for (uint256 i = 1; i < logN; i++) {
529 gateChallenges[i] = gateChallenges[i - 1] * gateChallenges[i - 1];
530 }
531 nextPreviousChallenge = previousChallenge;
532 }
533
534 function generateLibraChallenge(Fr previousChallenge, Honk.ZKProof memory proof)
535 internal
536 pure
537 returns (Fr libraChallenge, Fr nextPreviousChallenge)
538 {
539 // 2 comm, 1 sum, 1 challenge
540 uint256[4] memory challengeData;
541 challengeData[0] = Fr.unwrap(previousChallenge);
542 challengeData[1] = proof.libraCommitments[0].x;
543 challengeData[2] = proof.libraCommitments[0].y;
544 challengeData[3] = Fr.unwrap(proof.libraSum);
545 nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(challengeData))) % P);
546 libraChallenge = nextPreviousChallenge;
547 }
548
549 function generateSumcheckChallenges(Honk.ZKProof memory proof, Fr prevChallenge, uint256 logN)
550 internal
551 pure
552 returns (Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckChallenges, Fr nextPreviousChallenge)
553 {
554 for (uint256 i = 0; i < logN; i++) {
555 Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH + 1] memory univariateChal;
556 univariateChal[0] = prevChallenge;
557
558 for (uint256 j = 0; j < ZK_BATCHED_RELATION_PARTIAL_LENGTH; j++) {
559 univariateChal[j + 1] = proof.sumcheckUnivariates[i][j];
560 }
561 prevChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(univariateChal))) % P);
562 sumcheckChallenges[i] = prevChallenge;
563 }
564 nextPreviousChallenge = prevChallenge;
565 }
566
567 // We add Libra claimed eval + 2 libra commitments (grand_sum, quotient)
568 function generateRhoChallenge(Honk.ZKProof memory proof, Fr prevChallenge)
569 internal
570 pure
571 returns (Fr rho, Fr nextPreviousChallenge)
572 {
573 uint256[NUMBER_OF_ENTITIES_ZK + 6] memory rhoChallengeElements;
574 rhoChallengeElements[0] = Fr.unwrap(prevChallenge);
575 uint256 i;
576 for (i = 1; i <= NUMBER_OF_ENTITIES_ZK; i++) {
577 rhoChallengeElements[i] = Fr.unwrap(proof.sumcheckEvaluations[i - 1]);
578 }
579 rhoChallengeElements[i] = Fr.unwrap(proof.libraEvaluation);
580 i += 1;
581 rhoChallengeElements[i] = proof.libraCommitments[1].x;
582 rhoChallengeElements[i + 1] = proof.libraCommitments[1].y;
583 i += 2;
584 rhoChallengeElements[i] = proof.libraCommitments[2].x;
585 rhoChallengeElements[i + 1] = proof.libraCommitments[2].y;
586
587 nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(rhoChallengeElements))) % P);
588 rho = nextPreviousChallenge;
589 }
590
591 function generateGeminiRChallenge(Honk.ZKProof memory proof, Fr prevChallenge, uint256 logN)
592 internal
593 pure
594 returns (Fr geminiR, Fr nextPreviousChallenge)
595 {
596 uint256[] memory gR = new uint256[]((logN - 1) * 2 + 1);
597 gR[0] = Fr.unwrap(prevChallenge);
598
599 for (uint256 i = 0; i < logN - 1; i++) {
600 gR[1 + i * 2] = proof.geminiFoldComms[i].x;
601 gR[2 + i * 2] = proof.geminiFoldComms[i].y;
602 }
603
604 nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(gR))) % P);
605 geminiR = nextPreviousChallenge;
606 }
607
608 function generateShplonkNuChallenge(Honk.ZKProof memory proof, Fr prevChallenge, uint256 logN)
609 internal
610 pure
611 returns (Fr shplonkNu, Fr nextPreviousChallenge)
612 {
613 uint256[] memory shplonkNuChallengeElements = new uint256[](logN + 1 + 4);
614 shplonkNuChallengeElements[0] = Fr.unwrap(prevChallenge);
615
616 for (uint256 i = 1; i <= logN; i++) {
617 shplonkNuChallengeElements[i] = Fr.unwrap(proof.geminiAEvaluations[i - 1]);
618 }
619
620 uint256 libraIdx = 0;
621 for (uint256 i = logN + 1; i <= logN + 4; i++) {
622 shplonkNuChallengeElements[i] = Fr.unwrap(proof.libraPolyEvals[libraIdx]);
623 libraIdx++;
624 }
625
626 nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(shplonkNuChallengeElements))) % P);
627 shplonkNu = nextPreviousChallenge;
628 }
629
630 function generateShplonkZChallenge(Honk.ZKProof memory proof, Fr prevChallenge)
631 internal
632 pure
633 returns (Fr shplonkZ, Fr nextPreviousChallenge)
634 {
635 uint256[3] memory shplonkZChallengeElements;
636 shplonkZChallengeElements[0] = Fr.unwrap(prevChallenge);
637
638 shplonkZChallengeElements[1] = proof.shplonkQ.x;
639 shplonkZChallengeElements[2] = proof.shplonkQ.y;
640
641 nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(shplonkZChallengeElements))) % P);
642 shplonkZ = nextPreviousChallenge;
643 }
644
645 function loadProof(bytes calldata proof, uint256 logN) internal pure returns (Honk.ZKProof memory p) {
646 uint256 boundary = 0x0;
647
648 // Pairing point object
649 for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) {
650 uint256 limb = uint256(bytes32(proof[boundary:boundary + FIELD_ELEMENT_SIZE]));
651 // lo limbs (even index) < 2^136, hi limbs (odd index) < 2^120
652 require(limb < 2 ** (i % 2 == 0 ? 136 : 120), Errors.ValueGeLimbMax());
653 p.pairingPointObject[i] = FrLib.from(limb);
654 boundary += FIELD_ELEMENT_SIZE;
655 }
656
657 // Gemini masking polynomial commitment (sent first in ZK flavors, right after pairing points)
658 p.geminiMaskingPoly = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
659 boundary += GROUP_ELEMENT_SIZE;
660
661 // Commitments
662 p.w1 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
663 boundary += GROUP_ELEMENT_SIZE;
664 p.w2 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
665 boundary += GROUP_ELEMENT_SIZE;
666 p.w3 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
667 boundary += GROUP_ELEMENT_SIZE;
668
669 // Lookup / Permutation Helper Commitments
670 p.lookupReadCounts = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
671 boundary += GROUP_ELEMENT_SIZE;
672 p.lookupReadTags = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
673 boundary += GROUP_ELEMENT_SIZE;
674 p.w4 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
675 boundary += GROUP_ELEMENT_SIZE;
676 p.lookupInverses = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
677 boundary += GROUP_ELEMENT_SIZE;
678 p.zPerm = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
679 boundary += GROUP_ELEMENT_SIZE;
680 p.libraCommitments[0] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
681 boundary += GROUP_ELEMENT_SIZE;
682
683 p.libraSum = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]);
684 boundary += FIELD_ELEMENT_SIZE;
685 // Sumcheck univariates
686 for (uint256 i = 0; i < logN; i++) {
687 for (uint256 j = 0; j < ZK_BATCHED_RELATION_PARTIAL_LENGTH; j++) {
688 p.sumcheckUnivariates[i][j] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]);
689 boundary += FIELD_ELEMENT_SIZE;
690 }
691 }
692
693 // Sumcheck evaluations (includes gemini_masking_poly eval at index 0 for ZK flavors)
694 for (uint256 i = 0; i < NUMBER_OF_ENTITIES_ZK; i++) {
695 p.sumcheckEvaluations[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]);
696 boundary += FIELD_ELEMENT_SIZE;
697 }
698
699 p.libraEvaluation = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]);
700 boundary += FIELD_ELEMENT_SIZE;
701
702 p.libraCommitments[1] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
703 boundary += GROUP_ELEMENT_SIZE;
704 p.libraCommitments[2] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
705 boundary += GROUP_ELEMENT_SIZE;
706
707 // Gemini
708 // Read gemini fold univariates
709 for (uint256 i = 0; i < logN - 1; i++) {
710 p.geminiFoldComms[i] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
711 boundary += GROUP_ELEMENT_SIZE;
712 }
713
714 // Read gemini a evaluations
715 for (uint256 i = 0; i < logN; i++) {
716 p.geminiAEvaluations[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]);
717 boundary += FIELD_ELEMENT_SIZE;
718 }
719
720 for (uint256 i = 0; i < 4; i++) {
721 p.libraPolyEvals[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]);
722 boundary += FIELD_ELEMENT_SIZE;
723 }
724
725 // Shplonk
726 p.shplonkQ = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
727 boundary += GROUP_ELEMENT_SIZE;
728 // KZG
729 p.kzgQuotient = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
730 }
731}
732
733library RelationsLib {
734 struct EllipticParams {
735 // Points
736 Fr x_1;
737 Fr y_1;
738 Fr x_2;
739 Fr y_2;
740 Fr y_3;
741 Fr x_3;
742 // push accumulators into memory
743 Fr x_double_identity;
744 }
745
746 // Parameters used within the Memory Relation
747 // A struct is used to work around stack too deep. This relation has alot of variables
748 struct MemParams {
749 Fr memory_record_check;
750 Fr partial_record_check;
751 Fr next_gate_access_type;
752 Fr record_delta;
753 Fr index_delta;
754 Fr adjacent_values_match_if_adjacent_indices_match;
755 Fr adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation;
756 Fr access_check;
757 Fr next_gate_access_type_is_boolean;
758 Fr ROM_consistency_check_identity;
759 Fr RAM_consistency_check_identity;
760 Fr timestamp_delta;
761 Fr RAM_timestamp_check_identity;
762 Fr memory_identity;
763 Fr index_is_monotonically_increasing;
764 }
765
766 // Parameters used within the Non-Native Field Relation
767 // A struct is used to work around stack too deep. This relation has alot of variables
768 struct NnfParams {
769 Fr limb_subproduct;
770 Fr non_native_field_gate_1;
771 Fr non_native_field_gate_2;
772 Fr non_native_field_gate_3;
773 Fr limb_accumulator_1;
774 Fr limb_accumulator_2;
775 Fr nnf_identity;
776 }
777
778 struct PoseidonExternalParams {
779 Fr s1;
780 Fr s2;
781 Fr s3;
782 Fr s4;
783 Fr u1;
784 Fr u2;
785 Fr u3;
786 Fr u4;
787 Fr t0;
788 Fr t1;
789 Fr t2;
790 Fr t3;
791 Fr v1;
792 Fr v2;
793 Fr v3;
794 Fr v4;
795 Fr q_pos_by_scaling;
796 }
797
798 struct PoseidonInternalParams {
799 Fr u1;
800 Fr u2;
801 Fr u3;
802 Fr u4;
803 Fr u_sum;
804 Fr v1;
805 Fr v2;
806 Fr v3;
807 Fr v4;
808 Fr s1;
809 Fr q_pos_by_scaling;
810 }
811
812 Fr internal constant GRUMPKIN_CURVE_B_PARAMETER_NEGATED = Fr.wrap(17); // -(-17)
813 uint256 internal constant NEG_HALF_MODULO_P = 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000;
814
815 // Constants for the Non-native Field relation
816 Fr internal constant LIMB_SIZE = Fr.wrap(uint256(1) << 68);
817 Fr internal constant SUBLIMB_SHIFT = Fr.wrap(uint256(1) << 14);
818
819 function accumulateRelationEvaluations(
820 Fr[NUMBER_OF_ENTITIES] memory purportedEvaluations,
821 Honk.RelationParameters memory rp,
822 Fr[NUMBER_OF_ALPHAS] memory subrelationChallenges,
823 Fr powPartialEval
824 ) external pure returns (Fr accumulator) {
825 Fr[NUMBER_OF_SUBRELATIONS] memory evaluations;
826
827 // Accumulate all relations in Ultra Honk - each with varying number of subrelations
828 accumulateArithmeticRelation(purportedEvaluations, evaluations, powPartialEval);
829 accumulatePermutationRelation(purportedEvaluations, rp, evaluations, powPartialEval);
830 accumulateLogDerivativeLookupRelation(purportedEvaluations, rp, evaluations, powPartialEval);
831 accumulateDeltaRangeRelation(purportedEvaluations, evaluations, powPartialEval);
832 accumulateEllipticRelation(purportedEvaluations, evaluations, powPartialEval);
833 accumulateMemoryRelation(purportedEvaluations, rp, evaluations, powPartialEval);
834 accumulateRomLogupRelation(purportedEvaluations, rp, evaluations, powPartialEval);
835 accumulateNnfRelation(purportedEvaluations, evaluations, powPartialEval);
836 accumulatePoseidonExternalRelation(purportedEvaluations, evaluations, powPartialEval);
837 accumulatePoseidonInternalRelation(purportedEvaluations, evaluations, powPartialEval);
838
839 // batch the subrelations with the precomputed alpha powers to obtain the full honk relation
840 accumulator = scaleAndBatchSubrelations(evaluations, subrelationChallenges);
841 }
842
848 function wire(Fr[NUMBER_OF_ENTITIES] memory p, WIRE _wire) internal pure returns (Fr) {
849 return p[uint256(_wire)];
850 }
851
856 function accumulateArithmeticRelation(
857 Fr[NUMBER_OF_ENTITIES] memory p,
858 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
859 Fr domainSep
860 ) internal pure {
861 // Relation 0
862 Fr q_arith = wire(p, WIRE.Q_ARITH);
863 {
864 Fr neg_half = Fr.wrap(NEG_HALF_MODULO_P);
865
866 Fr accum = (q_arith - Fr.wrap(3)) * (wire(p, WIRE.Q_M) * wire(p, WIRE.W_R) * wire(p, WIRE.W_L)) * neg_half;
867 accum = accum + (wire(p, WIRE.Q_L) * wire(p, WIRE.W_L)) + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_R))
868 + (wire(p, WIRE.Q_O) * wire(p, WIRE.W_O)) + (wire(p, WIRE.Q_4) * wire(p, WIRE.W_4)) + wire(p, WIRE.Q_C);
869 accum = accum + (q_arith - ONE) * wire(p, WIRE.W_4_SHIFT);
870 accum = accum * q_arith;
871 accum = accum * domainSep;
872 evals[6] = accum;
873 }
874
875 // Relation 1
876 {
877 Fr accum = wire(p, WIRE.W_L) + wire(p, WIRE.W_4) - wire(p, WIRE.W_L_SHIFT) + wire(p, WIRE.Q_M);
878 accum = accum * (q_arith - Fr.wrap(2));
879 accum = accum * (q_arith - ONE);
880 accum = accum * q_arith;
881 accum = accum * domainSep;
882 evals[7] = accum;
883 }
884 }
885
886 function accumulatePermutationRelation(
887 Fr[NUMBER_OF_ENTITIES] memory p,
888 Honk.RelationParameters memory rp,
889 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
890 Fr domainSep
891 ) internal pure {
892 Fr grand_product_numerator;
893 Fr grand_product_denominator;
894
895 {
896 Fr num = wire(p, WIRE.W_L) + wire(p, WIRE.ID_1) * rp.beta + rp.gamma;
897 num = num * (wire(p, WIRE.W_R) + wire(p, WIRE.ID_2) * rp.beta + rp.gamma);
898 num = num * (wire(p, WIRE.W_O) + wire(p, WIRE.ID_3) * rp.beta + rp.gamma);
899 num = num * (wire(p, WIRE.W_4) + wire(p, WIRE.ID_4) * rp.beta + rp.gamma);
900
901 grand_product_numerator = num;
902 }
903 {
904 Fr den = wire(p, WIRE.W_L) + wire(p, WIRE.SIGMA_1) * rp.beta + rp.gamma;
905 den = den * (wire(p, WIRE.W_R) + wire(p, WIRE.SIGMA_2) * rp.beta + rp.gamma);
906 den = den * (wire(p, WIRE.W_O) + wire(p, WIRE.SIGMA_3) * rp.beta + rp.gamma);
907 den = den * (wire(p, WIRE.W_4) + wire(p, WIRE.SIGMA_4) * rp.beta + rp.gamma);
908
909 grand_product_denominator = den;
910 }
911
912 // Contribution 2
913 {
914 Fr acc = (wire(p, WIRE.Z_PERM) + wire(p, WIRE.LAGRANGE_FIRST)) * grand_product_numerator;
915
916 acc = acc
917 - ((wire(p, WIRE.Z_PERM_SHIFT) + (wire(p, WIRE.LAGRANGE_LAST) * rp.publicInputsDelta))
918 * grand_product_denominator);
919 acc = acc * domainSep;
920 evals[0] = acc;
921 }
922
923 // Contribution 3
924 {
925 Fr acc = (wire(p, WIRE.LAGRANGE_LAST) * wire(p, WIRE.Z_PERM_SHIFT)) * domainSep;
926 evals[1] = acc;
927 }
928
929 // Contribution 4: z_perm initialization check (lagrange_first * z_perm = 0)
930 {
931 Fr acc = (wire(p, WIRE.LAGRANGE_FIRST) * wire(p, WIRE.Z_PERM)) * domainSep;
932 evals[2] = acc;
933 }
934 }
935
936 function accumulateLogDerivativeLookupRelation(
937 Fr[NUMBER_OF_ENTITIES] memory p,
938 Honk.RelationParameters memory rp,
939 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
940 Fr domainSep
941 ) internal pure {
942 Fr table_term;
943 Fr lookup_term;
944
945 // Calculate the write term (the table accumulation)
946 // table_term = table_1 + γ + table_2 * β + table_3 * β² + table_4 * β³
947 {
948 Fr beta_sqr = rp.beta * rp.beta;
949 table_term = wire(p, WIRE.TABLE_1) + rp.gamma + (wire(p, WIRE.TABLE_2) * rp.beta)
950 + (wire(p, WIRE.TABLE_3) * beta_sqr) + (wire(p, WIRE.TABLE_4) * beta_sqr * rp.beta);
951 }
952
953 // Calculate the read term
954 // lookup_term = derived_entry_1 + γ + derived_entry_2 * β + derived_entry_3 * β² + q_index * β³
955 {
956 Fr beta_sqr = rp.beta * rp.beta;
957 Fr derived_entry_1 = wire(p, WIRE.W_L) + rp.gamma + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_L_SHIFT));
958 Fr derived_entry_2 = wire(p, WIRE.W_R) + wire(p, WIRE.Q_M) * wire(p, WIRE.W_R_SHIFT);
959 Fr derived_entry_3 = wire(p, WIRE.W_O) + wire(p, WIRE.Q_C) * wire(p, WIRE.W_O_SHIFT);
960
961 lookup_term = derived_entry_1 + (derived_entry_2 * rp.beta) + (derived_entry_3 * beta_sqr)
962 + (wire(p, WIRE.Q_O) * beta_sqr * rp.beta);
963 }
964
965 Fr lookup_inverse = wire(p, WIRE.LOOKUP_INVERSES) * table_term;
966 Fr table_inverse = wire(p, WIRE.LOOKUP_INVERSES) * lookup_term;
967
968 Fr inverse_exists_xor =
969 wire(p, WIRE.LOOKUP_READ_TAGS) + wire(p, WIRE.Q_LOOKUP)
970 - (wire(p, WIRE.LOOKUP_READ_TAGS) * wire(p, WIRE.Q_LOOKUP));
971
972 // Inverse calculated correctly relation
973 Fr accumulatorNone = lookup_term * table_term * wire(p, WIRE.LOOKUP_INVERSES) - inverse_exists_xor;
974 accumulatorNone = accumulatorNone * domainSep;
975
976 // Inverse
977 Fr accumulatorOne = wire(p, WIRE.Q_LOOKUP) * lookup_inverse - wire(p, WIRE.LOOKUP_READ_COUNTS) * table_inverse;
978
979 Fr read_tag = wire(p, WIRE.LOOKUP_READ_TAGS);
980
981 Fr read_tag_boolean_relation = read_tag * read_tag - read_tag;
982
983 evals[3] = accumulatorNone;
984 evals[4] = accumulatorOne;
985 evals[5] = read_tag_boolean_relation * domainSep;
986 }
987
988 function accumulateDeltaRangeRelation(
989 Fr[NUMBER_OF_ENTITIES] memory p,
990 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
991 Fr domainSep
992 ) internal pure {
993 Fr minus_one = ZERO - ONE;
994 Fr minus_two = ZERO - Fr.wrap(2);
995 Fr minus_three = ZERO - Fr.wrap(3);
996
997 // Compute wire differences
998 Fr delta_1 = wire(p, WIRE.W_R) - wire(p, WIRE.W_L);
999 Fr delta_2 = wire(p, WIRE.W_O) - wire(p, WIRE.W_R);
1000 Fr delta_3 = wire(p, WIRE.W_4) - wire(p, WIRE.W_O);
1001 Fr delta_4 = wire(p, WIRE.W_L_SHIFT) - wire(p, WIRE.W_4);
1002
1003 // Contribution 6
1004 {
1005 Fr acc = delta_1;
1006 acc = acc * (delta_1 + minus_one);
1007 acc = acc * (delta_1 + minus_two);
1008 acc = acc * (delta_1 + minus_three);
1009 acc = acc * wire(p, WIRE.Q_RANGE);
1010 acc = acc * domainSep;
1011 evals[8] = acc;
1012 }
1013
1014 // Contribution 7
1015 {
1016 Fr acc = delta_2;
1017 acc = acc * (delta_2 + minus_one);
1018 acc = acc * (delta_2 + minus_two);
1019 acc = acc * (delta_2 + minus_three);
1020 acc = acc * wire(p, WIRE.Q_RANGE);
1021 acc = acc * domainSep;
1022 evals[9] = acc;
1023 }
1024
1025 // Contribution 8
1026 {
1027 Fr acc = delta_3;
1028 acc = acc * (delta_3 + minus_one);
1029 acc = acc * (delta_3 + minus_two);
1030 acc = acc * (delta_3 + minus_three);
1031 acc = acc * wire(p, WIRE.Q_RANGE);
1032 acc = acc * domainSep;
1033 evals[10] = acc;
1034 }
1035
1036 // Contribution 9
1037 {
1038 Fr acc = delta_4;
1039 acc = acc * (delta_4 + minus_one);
1040 acc = acc * (delta_4 + minus_two);
1041 acc = acc * (delta_4 + minus_three);
1042 acc = acc * wire(p, WIRE.Q_RANGE);
1043 acc = acc * domainSep;
1044 evals[11] = acc;
1045 }
1046 }
1047
1048 function accumulateEllipticRelation(
1049 Fr[NUMBER_OF_ENTITIES] memory p,
1050 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
1051 Fr domainSep
1052 ) internal pure {
1053 EllipticParams memory ep;
1054 ep.x_1 = wire(p, WIRE.W_R);
1055 ep.y_1 = wire(p, WIRE.W_O);
1056
1057 ep.x_2 = wire(p, WIRE.W_L_SHIFT);
1058 ep.y_2 = wire(p, WIRE.W_4_SHIFT);
1059 ep.y_3 = wire(p, WIRE.W_O_SHIFT);
1060 ep.x_3 = wire(p, WIRE.W_R_SHIFT);
1061
1062 Fr q_sign = wire(p, WIRE.Q_L);
1063 Fr q_is_double = wire(p, WIRE.Q_M);
1064
1065 // Contribution 10 point addition, x-coordinate check
1066 // q_elliptic * (x3 + x2 + x1)(x2 - x1)(x2 - x1) - y2^2 - y1^2 + 2(y2y1)*q_sign = 0
1067 Fr x_diff = (ep.x_2 - ep.x_1);
1068 Fr y1_sqr = (ep.y_1 * ep.y_1);
1069 {
1070 // Move to top
1071 Fr partialEval = domainSep;
1072
1073 Fr y2_sqr = (ep.y_2 * ep.y_2);
1074 Fr y1y2 = ep.y_1 * ep.y_2 * q_sign;
1075 Fr x_add_identity = (ep.x_3 + ep.x_2 + ep.x_1);
1076 x_add_identity = x_add_identity * x_diff * x_diff;
1077 x_add_identity = x_add_identity - y2_sqr - y1_sqr + y1y2 + y1y2;
1078
1079 evals[12] = x_add_identity * partialEval * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double);
1080 }
1081
1082 // Contribution 11 point addition, x-coordinate check
1083 // q_elliptic * (q_sign * y1 + y3)(x2 - x1) + (x3 - x1)(y2 - q_sign * y1) = 0
1084 {
1085 Fr y1_plus_y3 = ep.y_1 + ep.y_3;
1086 Fr y_diff = ep.y_2 * q_sign - ep.y_1;
1087 Fr y_add_identity = y1_plus_y3 * x_diff + (ep.x_3 - ep.x_1) * y_diff;
1088 evals[13] = y_add_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double);
1089 }
1090
1091 // Contribution 10 point doubling, x-coordinate check
1092 // (x3 + x1 + x1) (4y1*y1) - 9 * x1 * x1 * x1 * x1 = 0
1093 // N.B. we're using the equivalence x1*x1*x1 === y1*y1 - curve_b to reduce degree by 1
1094 {
1095 Fr x_pow_4 = (y1_sqr + GRUMPKIN_CURVE_B_PARAMETER_NEGATED) * ep.x_1;
1096 Fr y1_sqr_mul_4 = y1_sqr + y1_sqr;
1097 y1_sqr_mul_4 = y1_sqr_mul_4 + y1_sqr_mul_4;
1098 Fr x1_pow_4_mul_9 = x_pow_4 * Fr.wrap(9);
1099
1100 // NOTE: pushed into memory (stack >:'( )
1101 ep.x_double_identity = (ep.x_3 + ep.x_1 + ep.x_1) * y1_sqr_mul_4 - x1_pow_4_mul_9;
1102
1103 Fr acc = ep.x_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double;
1104 evals[12] = evals[12] + acc;
1105 }
1106
1107 // Contribution 11 point doubling, y-coordinate check
1108 // (y1 + y1) (2y1) - (3 * x1 * x1)(x1 - x3) = 0
1109 {
1110 Fr x1_sqr_mul_3 = (ep.x_1 + ep.x_1 + ep.x_1) * ep.x_1;
1111 Fr y_double_identity = x1_sqr_mul_3 * (ep.x_1 - ep.x_3) - (ep.y_1 + ep.y_1) * (ep.y_1 + ep.y_3);
1112 evals[13] = evals[13] + y_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double;
1113 }
1114 }
1115
1116 function accumulateMemoryRelation(
1117 Fr[NUMBER_OF_ENTITIES] memory p,
1118 Honk.RelationParameters memory rp,
1119 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
1120 Fr domainSep
1121 ) internal pure {
1122 MemParams memory ap;
1123
1124 // Compute eta powers locally
1125 Fr eta_two = rp.eta * rp.eta;
1126 Fr eta_three = eta_two * rp.eta;
1127
1169 ap.memory_record_check = wire(p, WIRE.W_O) * eta_three;
1170 ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_R) * eta_two);
1171 ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_L) * rp.eta);
1172 ap.memory_record_check = ap.memory_record_check + wire(p, WIRE.Q_C);
1173 ap.partial_record_check = ap.memory_record_check; // used in RAM consistency check; deg 1 or 4
1174 ap.memory_record_check = ap.memory_record_check - wire(p, WIRE.W_4);
1175
1192 ap.index_delta = wire(p, WIRE.W_L_SHIFT) - wire(p, WIRE.W_L);
1193 ap.record_delta = wire(p, WIRE.W_4_SHIFT) - wire(p, WIRE.W_4);
1194
1195 ap.index_is_monotonically_increasing = ap.index_delta * (ap.index_delta - Fr.wrap(1)); // deg 2
1196
1197 ap.adjacent_values_match_if_adjacent_indices_match = (ap.index_delta * MINUS_ONE + ONE) * ap.record_delta; // deg 2
1198
1199 evals[15] = ap.adjacent_values_match_if_adjacent_indices_match * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R))
1200 * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5
1201 evals[16] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R))
1202 * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5
1203
1204 ap.ROM_consistency_check_identity = ap.memory_record_check * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)); // deg 3 or 7
1205
1225 Fr access_type = (wire(p, WIRE.W_4) - ap.partial_record_check); // will be 0 or 1 for honest Prover; deg 1 or 4
1226 ap.access_check = access_type * (access_type - Fr.wrap(1)); // check value is 0 or 1; deg 2 or 8
1227
1228 // reverse order we could re-use `ap.partial_record_check` 1 - ((w3' * eta + w2') * eta + w1') * eta
1229 // deg 1 or 4
1230 ap.next_gate_access_type = wire(p, WIRE.W_O_SHIFT) * eta_three;
1231 ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_R_SHIFT) * eta_two);
1232 ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_L_SHIFT) * rp.eta);
1233 ap.next_gate_access_type = wire(p, WIRE.W_4_SHIFT) - ap.next_gate_access_type;
1234
1235 Fr value_delta = wire(p, WIRE.W_O_SHIFT) - wire(p, WIRE.W_O);
1236 ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation =
1237 (ap.index_delta * MINUS_ONE + ONE) * value_delta * (ap.next_gate_access_type * MINUS_ONE + ONE); // deg 3 or 6
1238
1239 // We can't apply the RAM consistency check identity on the final entry in the sorted list (the wires in the
1240 // next gate would make the identity fail). We need to validate that its 'access type' bool is correct. Can't
1241 // do with an arithmetic gate because of the `eta` factors. We need to check that the *next* gate's access
1242 // type is correct, to cover this edge case
1243 // deg 2 or 4
1244 ap.next_gate_access_type_is_boolean =
1245 ap.next_gate_access_type * ap.next_gate_access_type - ap.next_gate_access_type;
1246
1247 // Putting it all together...
1248 evals[17] = ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation
1249 * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 or 8
1250 evals[18] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4
1251 evals[19] = ap.next_gate_access_type_is_boolean * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 6
1252
1253 ap.RAM_consistency_check_identity = ap.access_check * (wire(p, WIRE.Q_O)); // deg 3 or 9
1254
1266 ap.timestamp_delta = wire(p, WIRE.W_R_SHIFT) - wire(p, WIRE.W_R);
1267 ap.RAM_timestamp_check_identity = (ap.index_delta * MINUS_ONE + ONE) * ap.timestamp_delta - wire(p, WIRE.W_O); // deg 3
1268
1274 ap.memory_identity = ap.ROM_consistency_check_identity; // deg 3 or 6
1275 ap.memory_identity =
1276 ap.memory_identity + ap.RAM_timestamp_check_identity * (wire(p, WIRE.Q_4) * wire(p, WIRE.Q_L)); // deg 4
1277 ap.memory_identity = ap.memory_identity + ap.memory_record_check * (wire(p, WIRE.Q_M) * wire(p, WIRE.Q_L)); // deg 3 or 6
1278 ap.memory_identity = ap.memory_identity + ap.RAM_consistency_check_identity; // deg 3 or 9
1279
1280 // (deg 3 or 9) + (deg 4) + (deg 3)
1281 ap.memory_identity = ap.memory_identity * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 10
1282 evals[14] = ap.memory_identity;
1283 }
1284
1298 function accumulateRomLogupRelation(
1299 Fr[NUMBER_OF_ENTITIES] memory p,
1300 Honk.RelationParameters memory rp,
1301 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
1302 Fr domainSep
1303 ) internal pure {
1304 Fr eta_two = rp.eta * rp.eta;
1305
1306 Fr q_logup_table = wire(p, WIRE.Q_R) * (ONE - wire(p, WIRE.Q_L));
1307 Fr q_logup_read = wire(p, WIRE.Q_4) * (ONE - wire(p, WIRE.Q_L));
1308 Fr denom = rp.romLogupGamma + wire(p, WIRE.W_L) + (wire(p, WIRE.W_R) * rp.eta) + (wire(p, WIRE.Q_C) * eta_two);
1309
1310 // Subrelation 6: per-row inverse correctness (linearly independent, scaled by domainSep). deg 5.
1311 evals[20] =
1312 (q_logup_table + q_logup_read) * (wire(p, WIRE.W_4) * denom - ONE) * (wire(p, WIRE.Q_MEMORY) * domainSep);
1313
1314 // Subrelation 7: LogUp sum identity. Linearly dependent: summed across the trace, so NOT scaled by
1315 // domainSep (mirrors the log-derivative lookup subrelation). deg 5.
1316 evals[21] = (q_logup_read - q_logup_table * wire(p, WIRE.W_O)) * wire(p, WIRE.W_4) * wire(p, WIRE.Q_MEMORY);
1317 }
1318
1319 function accumulateNnfRelation(
1320 Fr[NUMBER_OF_ENTITIES] memory p,
1321 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
1322 Fr domainSep
1323 ) internal pure {
1324 NnfParams memory ap;
1325
1338 ap.limb_subproduct = wire(p, WIRE.W_L) * wire(p, WIRE.W_R_SHIFT) + wire(p, WIRE.W_L_SHIFT) * wire(p, WIRE.W_R);
1339 ap.non_native_field_gate_2 =
1340 (wire(p, WIRE.W_L) * wire(p, WIRE.W_4) + wire(p, WIRE.W_R) * wire(p, WIRE.W_O) - wire(p, WIRE.W_O_SHIFT));
1341 ap.non_native_field_gate_2 = ap.non_native_field_gate_2 * LIMB_SIZE;
1342 ap.non_native_field_gate_2 = ap.non_native_field_gate_2 - wire(p, WIRE.W_4_SHIFT);
1343 ap.non_native_field_gate_2 = ap.non_native_field_gate_2 + ap.limb_subproduct;
1344 ap.non_native_field_gate_2 = ap.non_native_field_gate_2 * wire(p, WIRE.Q_4);
1345
1346 ap.limb_subproduct = ap.limb_subproduct * LIMB_SIZE;
1347 ap.limb_subproduct = ap.limb_subproduct + (wire(p, WIRE.W_L_SHIFT) * wire(p, WIRE.W_R_SHIFT));
1348 ap.non_native_field_gate_1 = ap.limb_subproduct;
1349 ap.non_native_field_gate_1 = ap.non_native_field_gate_1 - (wire(p, WIRE.W_O) + wire(p, WIRE.W_4));
1350 ap.non_native_field_gate_1 = ap.non_native_field_gate_1 * wire(p, WIRE.Q_O);
1351
1352 ap.non_native_field_gate_3 = ap.limb_subproduct;
1353 ap.non_native_field_gate_3 = ap.non_native_field_gate_3 + wire(p, WIRE.W_4);
1354 ap.non_native_field_gate_3 = ap.non_native_field_gate_3 - (wire(p, WIRE.W_O_SHIFT) + wire(p, WIRE.W_4_SHIFT));
1355 ap.non_native_field_gate_3 = ap.non_native_field_gate_3 * wire(p, WIRE.Q_M);
1356
1357 Fr non_native_field_identity =
1358 ap.non_native_field_gate_1 + ap.non_native_field_gate_2 + ap.non_native_field_gate_3;
1359 non_native_field_identity = non_native_field_identity * wire(p, WIRE.Q_R);
1360
1361 // ((((w2' * 2^14 + w1') * 2^14 + w3) * 2^14 + w2) * 2^14 + w1 - w4) * qm
1362 // deg 2
1363 ap.limb_accumulator_1 = wire(p, WIRE.W_R_SHIFT) * SUBLIMB_SHIFT;
1364 ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_L_SHIFT);
1365 ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT;
1366 ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_O);
1367 ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT;
1368 ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_R);
1369 ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT;
1370 ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_L);
1371 ap.limb_accumulator_1 = ap.limb_accumulator_1 - wire(p, WIRE.W_4);
1372 ap.limb_accumulator_1 = ap.limb_accumulator_1 * wire(p, WIRE.Q_4);
1373
1374 // ((((w3' * 2^14 + w2') * 2^14 + w1') * 2^14 + w4) * 2^14 + w3 - w4') * qm
1375 // deg 2
1376 ap.limb_accumulator_2 = wire(p, WIRE.W_O_SHIFT) * SUBLIMB_SHIFT;
1377 ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_R_SHIFT);
1378 ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT;
1379 ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_L_SHIFT);
1380 ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT;
1381 ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_4);
1382 ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT;
1383 ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_O);
1384 ap.limb_accumulator_2 = ap.limb_accumulator_2 - wire(p, WIRE.W_4_SHIFT);
1385 ap.limb_accumulator_2 = ap.limb_accumulator_2 * wire(p, WIRE.Q_M);
1386
1387 Fr limb_accumulator_identity = ap.limb_accumulator_1 + ap.limb_accumulator_2;
1388 limb_accumulator_identity = limb_accumulator_identity * wire(p, WIRE.Q_O); // deg 3
1389
1390 ap.nnf_identity = non_native_field_identity + limb_accumulator_identity;
1391 ap.nnf_identity = ap.nnf_identity * (wire(p, WIRE.Q_NNF) * domainSep);
1392 evals[22] = ap.nnf_identity;
1393 }
1394
1395 function accumulatePoseidonExternalRelation(
1396 Fr[NUMBER_OF_ENTITIES] memory p,
1397 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
1398 Fr domainSep
1399 ) internal pure {
1400 PoseidonExternalParams memory ep;
1401
1402 ep.s1 = wire(p, WIRE.W_L) + wire(p, WIRE.Q_L);
1403 ep.s2 = wire(p, WIRE.W_R) + wire(p, WIRE.Q_R);
1404 ep.s3 = wire(p, WIRE.W_O) + wire(p, WIRE.Q_O);
1405 ep.s4 = wire(p, WIRE.W_4) + wire(p, WIRE.Q_4);
1406
1407 ep.u1 = ep.s1 * ep.s1 * ep.s1 * ep.s1 * ep.s1;
1408 ep.u2 = ep.s2 * ep.s2 * ep.s2 * ep.s2 * ep.s2;
1409 ep.u3 = ep.s3 * ep.s3 * ep.s3 * ep.s3 * ep.s3;
1410 ep.u4 = ep.s4 * ep.s4 * ep.s4 * ep.s4 * ep.s4;
1411 // matrix mul v = M_E * u with 14 additions
1412 ep.t0 = ep.u1 + ep.u2; // u_1 + u_2
1413 ep.t1 = ep.u3 + ep.u4; // u_3 + u_4
1414 ep.t2 = ep.u2 + ep.u2 + ep.t1; // 2u_2
1415 // ep.t2 += ep.t1; // 2u_2 + u_3 + u_4
1416 ep.t3 = ep.u4 + ep.u4 + ep.t0; // 2u_4
1417 // ep.t3 += ep.t0; // u_1 + u_2 + 2u_4
1418 ep.v4 = ep.t1 + ep.t1;
1419 ep.v4 = ep.v4 + ep.v4 + ep.t3;
1420 // ep.v4 += ep.t3; // u_1 + u_2 + 4u_3 + 6u_4
1421 ep.v2 = ep.t0 + ep.t0;
1422 ep.v2 = ep.v2 + ep.v2 + ep.t2;
1423 // ep.v2 += ep.t2; // 4u_1 + 6u_2 + u_3 + u_4
1424 ep.v1 = ep.t3 + ep.v2; // 5u_1 + 7u_2 + u_3 + 3u_4
1425 ep.v3 = ep.t2 + ep.v4; // u_1 + 3u_2 + 5u_3 + 7u_4
1426
1427 ep.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_EXTERNAL) * domainSep;
1428 evals[23] = evals[23] + ep.q_pos_by_scaling * (ep.v1 - wire(p, WIRE.W_L_SHIFT));
1429
1430 evals[24] = evals[24] + ep.q_pos_by_scaling * (ep.v2 - wire(p, WIRE.W_R_SHIFT));
1431
1432 evals[25] = evals[25] + ep.q_pos_by_scaling * (ep.v3 - wire(p, WIRE.W_O_SHIFT));
1433
1434 evals[26] = evals[26] + ep.q_pos_by_scaling * (ep.v4 - wire(p, WIRE.W_4_SHIFT));
1435 }
1436
1437 function accumulatePoseidonInternalRelation(
1438 Fr[NUMBER_OF_ENTITIES] memory p,
1439 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
1440 Fr domainSep
1441 ) internal pure {
1442 PoseidonInternalParams memory ip;
1443
1444 Fr[4] memory INTERNAL_MATRIX_DIAGONAL = [
1445 FrLib.from(0x10dc6e9c006ea38b04b1e03b4bd9490c0d03f98929ca1d7fb56821fd19d3b6e7),
1446 FrLib.from(0x0c28145b6a44df3e0149b3d0a30b3bb599df9756d4dd9b84a86b38cfb45a740b),
1447 FrLib.from(0x00544b8338791518b2c7645a50392798b21f75bb60e3596170067d00141cac15),
1448 FrLib.from(0x222c01175718386f2e2e82eb122789e352e105a3b8fa852613bc534433ee428b)
1449 ];
1450
1451 // add round constants
1452 ip.s1 = wire(p, WIRE.W_L) + wire(p, WIRE.Q_L);
1453
1454 // apply s-box round
1455 ip.u1 = ip.s1 * ip.s1 * ip.s1 * ip.s1 * ip.s1;
1456 ip.u2 = wire(p, WIRE.W_R);
1457 ip.u3 = wire(p, WIRE.W_O);
1458 ip.u4 = wire(p, WIRE.W_4);
1459
1460 // matrix mul with v = M_I * u 4 muls and 7 additions
1461 ip.u_sum = ip.u1 + ip.u2 + ip.u3 + ip.u4;
1462
1463 ip.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_INTERNAL) * domainSep;
1464
1465 ip.v1 = ip.u1 * INTERNAL_MATRIX_DIAGONAL[0] + ip.u_sum;
1466 evals[27] = evals[27] + ip.q_pos_by_scaling * (ip.v1 - wire(p, WIRE.W_L_SHIFT));
1467
1468 ip.v2 = ip.u2 * INTERNAL_MATRIX_DIAGONAL[1] + ip.u_sum;
1469 evals[28] = evals[28] + ip.q_pos_by_scaling * (ip.v2 - wire(p, WIRE.W_R_SHIFT));
1470
1471 ip.v3 = ip.u3 * INTERNAL_MATRIX_DIAGONAL[2] + ip.u_sum;
1472 evals[29] = evals[29] + ip.q_pos_by_scaling * (ip.v3 - wire(p, WIRE.W_O_SHIFT));
1473
1474 ip.v4 = ip.u4 * INTERNAL_MATRIX_DIAGONAL[3] + ip.u_sum;
1475 evals[30] = evals[30] + ip.q_pos_by_scaling * (ip.v4 - wire(p, WIRE.W_4_SHIFT));
1476 }
1477
1478 // Batch subrelation evaluations using precomputed powers of alpha
1479 // First subrelation is implicitly scaled by 1, subsequent ones use powers from the subrelationChallenges array
1480 function scaleAndBatchSubrelations(
1481 Fr[NUMBER_OF_SUBRELATIONS] memory evaluations,
1482 Fr[NUMBER_OF_ALPHAS] memory subrelationChallenges
1483 ) internal pure returns (Fr accumulator) {
1484 accumulator = evaluations[0];
1485
1486 for (uint256 i = 1; i < NUMBER_OF_SUBRELATIONS; ++i) {
1487 accumulator = accumulator + evaluations[i] * subrelationChallenges[i - 1];
1488 }
1489 }
1490}
1491
1492library CommitmentSchemeLib {
1493 using FrLib for Fr;
1494
1495 // Avoid stack too deep
1496 struct ShpleminiIntermediates {
1497 Fr unshiftedScalar;
1498 Fr shiftedScalar;
1499 Fr unshiftedScalarNeg;
1500 Fr shiftedScalarNeg;
1501 // Scalar to be multiplied by [1]₁
1502 Fr constantTermAccumulator;
1503 // Accumulator for powers of rho
1504 Fr batchingChallenge;
1505 // Linear combination of multilinear (sumcheck) evaluations and powers of rho
1506 Fr batchedEvaluation;
1507 Fr[NUM_SMALL_IPA_OPENING_CLAIMS] denominators;
1508 Fr[NUM_SMALL_IPA_OPENING_CLAIMS] batchingScalars;
1509 // 1/(z - r^{2^i}) for i = 0, ..., logSize, dynamically updated
1510 Fr posInvertedDenominator;
1511 // 1/(z + r^{2^i}) for i = 0, ..., logSize, dynamically updated
1512 Fr negInvertedDenominator;
1513 // ν^{2i} * 1/(z - r^{2^i})
1514 Fr scalingFactorPos;
1515 // ν^{2i+1} * 1/(z + r^{2^i})
1516 Fr scalingFactorNeg;
1517 // Fold_i(r^{2^i}) reconstructed by Verifier
1518 Fr[] foldPosEvaluations;
1519 }
1520
1521 // Compute the evaluations Aₗ(r^{2ˡ}) for l = 0, ..., m-1
1522 function computeFoldPosEvaluations(
1523 Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckUChallenges,
1524 Fr batchedEvalAccumulator,
1525 Fr[CONST_PROOF_SIZE_LOG_N] memory geminiEvaluations,
1526 Fr[] memory geminiEvalChallengePowers,
1527 uint256 logSize
1528 ) internal view returns (Fr[] memory) {
1529 Fr[] memory foldPosEvaluations = new Fr[](logSize);
1530 for (uint256 i = logSize; i > 0; --i) {
1531 Fr challengePower = geminiEvalChallengePowers[i - 1];
1532 Fr u = sumcheckUChallenges[i - 1];
1533
1534 Fr batchedEvalRoundAcc = ((challengePower * batchedEvalAccumulator * Fr.wrap(2)) - geminiEvaluations[i - 1]
1535 * (challengePower * (ONE - u) - u));
1536 // Divide by the denominator
1537 batchedEvalRoundAcc = batchedEvalRoundAcc * (challengePower * (ONE - u) + u).invert();
1538
1539 batchedEvalAccumulator = batchedEvalRoundAcc;
1540 foldPosEvaluations[i - 1] = batchedEvalRoundAcc;
1541 }
1542 return foldPosEvaluations;
1543 }
1544
1545 function computeSquares(Fr r, uint256 logN) internal pure returns (Fr[] memory) {
1546 Fr[] memory squares = new Fr[](logN);
1547 squares[0] = r;
1548 for (uint256 i = 1; i < logN; ++i) {
1549 squares[i] = squares[i - 1].sqr();
1550 }
1551 return squares;
1552 }
1553}
1554
1555uint256 constant Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; // EC group order. F_q
1556
1557// Fr utility
1558
1559function bytesToFr(bytes calldata proofSection) pure returns (Fr scalar) {
1560 scalar = FrLib.fromBytes32(bytes32(proofSection));
1561}
1562
1563// EC Point utilities
1564function bytesToG1Point(bytes calldata proofSection) pure returns (Honk.G1Point memory point) {
1565 uint256 x = uint256(bytes32(proofSection[0x00:0x20]));
1566 uint256 y = uint256(bytes32(proofSection[0x20:0x40]));
1567 require(x < Q && y < Q, Errors.ValueGeGroupOrder());
1568
1569 // (0,0) is the canonical EIP-196 encoding of the identity. It is accepted here
1570 // because polynomial commitments to identically-zero polynomials (e.g. unused
1571 // selector or table polys) are legitimately the identity. On-curve validation
1572 // (y² = x³ + 3) is handled by the ecAdd/ecMul precompiles per EIP-196.
1573 point = Honk.G1Point({x: x, y: y});
1574}
1575
1576function negateInplace(Honk.G1Point memory point) pure returns (Honk.G1Point memory) {
1577 // When y == 0 (order-2 point), negation is the same point. Q - 0 = Q which is >= Q.
1578 if (point.y != 0) {
1579 point.y = Q - point.y;
1580 }
1581 return point;
1582}
1583
1597function convertPairingPointsToG1(Fr[PAIRING_POINTS_SIZE] memory pairingPoints)
1598 pure
1599 returns (Honk.G1Point memory lhs, Honk.G1Point memory rhs)
1600{
1601 // P0 (lhs): x = lo | (hi << 136)
1602 uint256 lhsX = Fr.unwrap(pairingPoints[0]);
1603 lhsX |= Fr.unwrap(pairingPoints[1]) << 136;
1604
1605 uint256 lhsY = Fr.unwrap(pairingPoints[2]);
1606 lhsY |= Fr.unwrap(pairingPoints[3]) << 136;
1607
1608 // P1 (rhs): x = lo | (hi << 136)
1609 uint256 rhsX = Fr.unwrap(pairingPoints[4]);
1610 rhsX |= Fr.unwrap(pairingPoints[5]) << 136;
1611
1612 uint256 rhsY = Fr.unwrap(pairingPoints[6]);
1613 rhsY |= Fr.unwrap(pairingPoints[7]) << 136;
1614
1615 // Reconstructed coordinates must be < Q to prevent malleability.
1616 // Without this, two different limb encodings could map to the same curve point
1617 // (via mulmod reduction in on-curve checks) but produce different transcript hashes.
1618 require(lhsX < Q && lhsY < Q && rhsX < Q && rhsY < Q, Errors.ValueGeGroupOrder());
1619
1620 lhs.x = lhsX;
1621 lhs.y = lhsY;
1622 rhs.x = rhsX;
1623 rhs.y = rhsY;
1624}
1625
1634function generateRecursionSeparator(
1635 Fr[PAIRING_POINTS_SIZE] memory proofPairingPoints,
1636 Honk.G1Point memory accLhs,
1637 Honk.G1Point memory accRhs
1638) pure returns (Fr recursionSeparator) {
1639 // hash the proof aggregated X
1640 // hash the proof aggregated Y
1641 // hash the accum X
1642 // hash the accum Y
1643
1644 (Honk.G1Point memory proofLhs, Honk.G1Point memory proofRhs) = convertPairingPointsToG1(proofPairingPoints);
1645
1646 uint256[8] memory recursionSeparatorElements;
1647
1648 // Proof points
1649 recursionSeparatorElements[0] = proofLhs.x;
1650 recursionSeparatorElements[1] = proofLhs.y;
1651 recursionSeparatorElements[2] = proofRhs.x;
1652 recursionSeparatorElements[3] = proofRhs.y;
1653
1654 // Accumulator points
1655 recursionSeparatorElements[4] = accLhs.x;
1656 recursionSeparatorElements[5] = accLhs.y;
1657 recursionSeparatorElements[6] = accRhs.x;
1658 recursionSeparatorElements[7] = accRhs.y;
1659
1660 recursionSeparator = FrLib.from(uint256(keccak256(abi.encodePacked(recursionSeparatorElements))) % P);
1661}
1662
1672function mulWithSeperator(Honk.G1Point memory basePoint, Honk.G1Point memory other, Fr recursionSeperator)
1673 view
1674 returns (Honk.G1Point memory)
1675{
1676 Honk.G1Point memory result;
1677
1678 result = ecMul(recursionSeperator, basePoint);
1679 result = ecAdd(result, other);
1680
1681 return result;
1682}
1683
1692function ecMul(Fr value, Honk.G1Point memory point) view returns (Honk.G1Point memory) {
1693 Honk.G1Point memory result;
1694
1695 assembly {
1696 let free := mload(0x40)
1697 // Write the point into memory (two 32 byte words)
1698 // Memory layout:
1699 // Address | value
1700 // free | point.x
1701 // free + 0x20| point.y
1702 mstore(free, mload(point))
1703 mstore(add(free, 0x20), mload(add(point, 0x20)))
1704 // Write the scalar into memory (one 32 byte word)
1705 // Memory layout:
1706 // Address | value
1707 // free + 0x40| value
1708 mstore(add(free, 0x40), value)
1709
1710 // Call the ecMul precompile, it takes in the following
1711 // [point.x, point.y, scalar], and returns the result back into the free memory location.
1712 let success := staticcall(gas(), 0x07, free, 0x60, free, 0x40)
1713 if iszero(success) {
1714 revert(0, 0)
1715 }
1716 // Copy the result of the multiplication back into the result memory location.
1717 // Memory layout:
1718 // Address | value
1719 // result | result.x
1720 // result + 0x20| result.y
1721 mstore(result, mload(free))
1722 mstore(add(result, 0x20), mload(add(free, 0x20)))
1723
1724 mstore(0x40, add(free, 0x60))
1725 }
1726
1727 return result;
1728}
1729
1738function ecAdd(Honk.G1Point memory lhs, Honk.G1Point memory rhs) view returns (Honk.G1Point memory) {
1739 Honk.G1Point memory result;
1740
1741 assembly {
1742 let free := mload(0x40)
1743 // Write lhs into memory (two 32 byte words)
1744 // Memory layout:
1745 // Address | value
1746 // free | lhs.x
1747 // free + 0x20| lhs.y
1748 mstore(free, mload(lhs))
1749 mstore(add(free, 0x20), mload(add(lhs, 0x20)))
1750
1751 // Write rhs into memory (two 32 byte words)
1752 // Memory layout:
1753 // Address | value
1754 // free + 0x40| rhs.x
1755 // free + 0x60| rhs.y
1756 mstore(add(free, 0x40), mload(rhs))
1757 mstore(add(free, 0x60), mload(add(rhs, 0x20)))
1758
1759 // Call the ecAdd precompile, it takes in the following
1760 // [lhs.x, lhs.y, rhs.x, rhs.y], and returns their addition back into the free memory location.
1761 let success := staticcall(gas(), 0x06, free, 0x80, free, 0x40)
1762 if iszero(success) { revert(0, 0) }
1763
1764 // Copy the result of the addition back into the result memory location.
1765 // Memory layout:
1766 // Address | value
1767 // result | result.x
1768 // result + 0x20| result.y
1769 mstore(result, mload(free))
1770 mstore(add(result, 0x20), mload(add(free, 0x20)))
1771
1772 mstore(0x40, add(free, 0x80))
1773 }
1774
1775 return result;
1776}
1777
1778function rejectPointAtInfinity(Honk.G1Point memory point) pure {
1779 require((point.x | point.y) != 0, Errors.PointAtInfinity());
1780}
1781
1786function arePairingPointsDefault(Fr[PAIRING_POINTS_SIZE] memory pairingPoints) pure returns (bool) {
1787 uint256 acc = 0;
1788 for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) {
1789 acc |= Fr.unwrap(pairingPoints[i]);
1790 }
1791 return acc == 0;
1792}
1793
1794function pairing(Honk.G1Point memory rhs, Honk.G1Point memory lhs) view returns (bool decodedResult) {
1795 bytes memory input = abi.encodePacked(
1796 rhs.x,
1797 rhs.y,
1798 // Fixed G2 point
1799 uint256(0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2),
1800 uint256(0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed),
1801 uint256(0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b),
1802 uint256(0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa),
1803 lhs.x,
1804 lhs.y,
1805 // G2 point from VK
1806 uint256(0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1),
1807 uint256(0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0),
1808 uint256(0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4),
1809 uint256(0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55)
1810 );
1811
1812 (bool success, bytes memory result) = address(0x08).staticcall(input);
1813 decodedResult = success && abi.decode(result, (bool));
1814}
1815
1816abstract contract BaseZKHonkVerifier is IVerifier {
1817 using FrLib for Fr;
1818
1819 struct PairingInputs {
1820 Honk.G1Point P_0;
1821 Honk.G1Point P_1;
1822 }
1823
1824 struct SmallSubgroupIpaIntermediates {
1825 Fr[SUBGROUP_SIZE] challengePolyLagrange;
1826 Fr challengePolyEval;
1827 Fr lagrangeFirst;
1828 Fr lagrangeLast;
1829 Fr rootPower;
1830 Fr[SUBGROUP_SIZE] denominators; // this has to disappear
1831 Fr diff;
1832 }
1833
1834 // Constants for proof length calculation (matching UltraKeccakZKFlavor)
1835 uint256 internal constant NUM_WITNESS_ENTITIES = 8 + NUM_MASKING_POLYNOMIALS;
1836 uint256 internal constant NUM_ELEMENTS_COMM = 2; // uint256 elements for curve points
1837 uint256 internal constant NUM_ELEMENTS_FR = 1; // uint256 elements for field elements
1838 uint256 internal constant NUM_LIBRA_EVALUATIONS = 4; // libra evaluations
1839
1840 uint256 internal constant LIBRA_COMMITMENTS = 3;
1841 uint256 internal constant LIBRA_EVALUATIONS = 4;
1842 uint256 internal constant LIBRA_UNIVARIATES_LENGTH = 9;
1843
1844 uint256 internal constant SHIFTED_COMMITMENTS_START = 30;
1845 uint256 internal constant PERMUTATION_ARGUMENT_VALUE_SEPARATOR = 1 << 28;
1846
1847 uint256 internal immutable $N;
1848 uint256 internal immutable $LOG_N;
1849 uint256 internal immutable $VK_HASH;
1850 uint256 internal immutable $NUM_PUBLIC_INPUTS;
1851 uint256 internal immutable $MSMSize;
1852
1853 constructor(uint256 _N, uint256 _logN, uint256 _vkHash, uint256 _numPublicInputs) {
1854 $N = _N;
1855 $LOG_N = _logN;
1856 $VK_HASH = _vkHash;
1857 $NUM_PUBLIC_INPUTS = _numPublicInputs;
1858 $MSMSize = NUMBER_UNSHIFTED_ZK + _logN + LIBRA_COMMITMENTS + 2;
1859 }
1860
1861 function verify(bytes calldata proof, bytes32[] calldata publicInputs)
1862 public
1863 view
1864 override
1865 returns (bool verified)
1866 {
1867 // Calculate expected proof size based on $LOG_N
1868 uint256 expectedProofSize = calculateProofSize($LOG_N);
1869
1870 // Check the received proof is the expected size where each field element is 32 bytes
1871 require(
1872 proof.length == expectedProofSize, Errors.ProofLengthWrongWithLogN($LOG_N, proof.length, expectedProofSize)
1873 );
1874
1875 Honk.VerificationKey memory vk = loadVerificationKey();
1876 Honk.ZKProof memory p = ZKTranscriptLib.loadProof(proof, $LOG_N);
1877
1878 require(publicInputs.length == vk.publicInputsSize - PAIRING_POINTS_SIZE, Errors.PublicInputsLengthWrong());
1879
1880 // Generate the fiat shamir challenges for the whole protocol
1881 ZKTranscript memory t =
1882 ZKTranscriptLib.generateTranscript(p, publicInputs, $VK_HASH, $NUM_PUBLIC_INPUTS, $LOG_N);
1883
1884 // Derive public input delta
1885 t.relationParameters.publicInputsDelta = computePublicInputDelta(
1886 publicInputs,
1887 p.pairingPointObject,
1888 t.relationParameters.beta,
1889 t.relationParameters.gamma,
1890 5 // pubInputsOffset = NUM_DISABLED_ROWS_IN_SUMCHECK + NUM_ZERO_ROWS = 4 + 1
1891 );
1892
1893 // Sumcheck
1894 require(verifySumcheck(p, t), Errors.SumcheckFailed());
1895 require(verifyShplemini(p, vk, t), Errors.ShpleminiFailed());
1896
1897 verified = true;
1898 }
1899
1900 function computePublicInputDelta(
1901 bytes32[] memory publicInputs,
1902 Fr[PAIRING_POINTS_SIZE] memory pairingPointObject,
1903 Fr beta,
1904 Fr gamma,
1905 uint256 offset
1906 ) internal view returns (Fr publicInputDelta) {
1907 Fr numerator = Fr.wrap(1);
1908 Fr denominator = Fr.wrap(1);
1909
1910 Fr numeratorAcc = gamma + (beta * FrLib.from(PERMUTATION_ARGUMENT_VALUE_SEPARATOR + offset));
1911 Fr denominatorAcc = gamma - (beta * FrLib.from(offset + 1));
1912
1913 {
1914 for (uint256 i = 0; i < $NUM_PUBLIC_INPUTS - PAIRING_POINTS_SIZE; i++) {
1915 Fr pubInput = FrLib.fromBytes32(publicInputs[i]);
1916
1917 numerator = numerator * (numeratorAcc + pubInput);
1918 denominator = denominator * (denominatorAcc + pubInput);
1919
1920 numeratorAcc = numeratorAcc + beta;
1921 denominatorAcc = denominatorAcc - beta;
1922 }
1923
1924 for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) {
1925 Fr pubInput = pairingPointObject[i];
1926
1927 numerator = numerator * (numeratorAcc + pubInput);
1928 denominator = denominator * (denominatorAcc + pubInput);
1929
1930 numeratorAcc = numeratorAcc + beta;
1931 denominatorAcc = denominatorAcc - beta;
1932 }
1933 }
1934
1935 // Fr delta = numerator / denominator; // TOOO: batch invert later?
1936 publicInputDelta = FrLib.div(numerator, denominator);
1937 }
1938
1939 function verifySumcheck(Honk.ZKProof memory proof, ZKTranscript memory tp) internal view returns (bool verified) {
1940 Fr roundTargetSum = tp.libraChallenge * proof.libraSum; // default 0
1941 Fr powPartialEvaluation = Fr.wrap(1);
1942
1943 // We perform sumcheck reductions over log n rounds ( the multivariate degree )
1944 for (uint256 round; round < $LOG_N; ++round) {
1945 Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariate = proof.sumcheckUnivariates[round];
1946 Fr totalSum = roundUnivariate[0] + roundUnivariate[1];
1947 require(totalSum == roundTargetSum, Errors.SumcheckFailed());
1948
1949 Fr roundChallenge = tp.sumCheckUChallenges[round];
1950
1951 // Update the round target for the next rounf
1952 roundTargetSum = computeNextTargetSum(roundUnivariate, roundChallenge);
1953 powPartialEvaluation =
1954 powPartialEvaluation * (Fr.wrap(1) + roundChallenge * (tp.gateChallenges[round] - Fr.wrap(1)));
1955 }
1956
1957 // Last round
1958 // For ZK flavors: sumcheckEvaluations has 42 elements
1959 // Index 0 is gemini_masking_poly, indices 1-41 are the regular entities used in relations
1960 Fr[NUMBER_OF_ENTITIES] memory relationsEvaluations;
1961 for (uint256 i = 0; i < NUMBER_OF_ENTITIES; i++) {
1962 relationsEvaluations[i] = proof.sumcheckEvaluations[i + NUM_MASKING_POLYNOMIALS]; // Skip gemini_masking_poly at index 0
1963 }
1964 Fr grandHonkRelationSum = RelationsLib.accumulateRelationEvaluations(
1965 relationsEvaluations, tp.relationParameters, tp.alphas, powPartialEvaluation
1966 );
1967
1968 // Row-disabling polynomial: 1 - ∏_{i≥2}(1 - u_i)
1969 Fr evaluation = Fr.wrap(1);
1970 for (uint256 i = 2; i < $LOG_N; i++) {
1971 evaluation = evaluation * (Fr.wrap(1) - tp.sumCheckUChallenges[i]);
1972 }
1973
1974 grandHonkRelationSum =
1975 grandHonkRelationSum * (Fr.wrap(1) - evaluation) + proof.libraEvaluation * tp.libraChallenge;
1976 verified = (grandHonkRelationSum == roundTargetSum);
1977 }
1978
1979 // Return the new target sum for the next sumcheck round
1980 function computeNextTargetSum(Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariates, Fr roundChallenge)
1981 internal
1982 view
1983 returns (Fr targetSum)
1984 {
1985 Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory BARYCENTRIC_LAGRANGE_DENOMINATORS = [
1986 Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000009d80),
1987 Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51),
1988 Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000005a0),
1989 Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31),
1990 Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000000240),
1991 Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31),
1992 Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000005a0),
1993 Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51),
1994 Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000009d80)
1995 ];
1996
1997 // To compute the next target sum, we evaluate the given univariate at a point u (challenge).
1998
1999 // Performing Barycentric evaluations
2000 // Compute B(x)
2001 Fr numeratorValue = Fr.wrap(1);
2002 for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) {
2003 numeratorValue = numeratorValue * (roundChallenge - Fr.wrap(i));
2004 }
2005
2006 Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory denominatorInverses;
2007 for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) {
2008 denominatorInverses[i] = FrLib.invert(BARYCENTRIC_LAGRANGE_DENOMINATORS[i] * (roundChallenge - Fr.wrap(i)));
2009 }
2010
2011 for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) {
2012 targetSum = targetSum + roundUnivariates[i] * denominatorInverses[i];
2013 }
2014
2015 // Scale the sum by the value of B(x)
2016 targetSum = targetSum * numeratorValue;
2017 }
2018
2019 function verifyShplemini(Honk.ZKProof memory proof, Honk.VerificationKey memory vk, ZKTranscript memory tp)
2020 internal
2021 view
2022 returns (bool verified)
2023 {
2024 CommitmentSchemeLib.ShpleminiIntermediates memory mem; // stack
2025
2026 // - Compute vector (r, r², ... , r²⁽ⁿ⁻¹⁾), where n = log_circuit_size
2027 Fr[] memory powers_of_evaluation_challenge = CommitmentSchemeLib.computeSquares(tp.geminiR, $LOG_N);
2028 // Arrays hold values that will be linearly combined for the gemini and shplonk batch openings
2029 Fr[] memory scalars = new Fr[]($MSMSize);
2030 Honk.G1Point[] memory commitments = new Honk.G1Point[]($MSMSize);
2031
2032 mem.posInvertedDenominator = (tp.shplonkZ - powers_of_evaluation_challenge[0]).invert();
2033 mem.negInvertedDenominator = (tp.shplonkZ + powers_of_evaluation_challenge[0]).invert();
2034
2035 mem.unshiftedScalar = mem.posInvertedDenominator + (tp.shplonkNu * mem.negInvertedDenominator);
2036 mem.shiftedScalar =
2037 tp.geminiR.invert() * (mem.posInvertedDenominator - (tp.shplonkNu * mem.negInvertedDenominator));
2038
2039 scalars[0] = Fr.wrap(1);
2040 commitments[0] = proof.shplonkQ;
2041
2042 /* Batch multivariate opening claims, shifted and unshifted
2043 * The vector of scalars is populated as follows:
2044 * \f[
2045 * \left(
2046 * - \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right),
2047 * \ldots,
2048 * - \rho^{i+k-1} \times \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right),
2049 * - \rho^{i+k} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right),
2050 * \ldots,
2051 * - \rho^{k+m-1} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right)
2052 * \right)
2053 * \f]
2054 *
2055 * The following vector is concatenated to the vector of commitments:
2056 * \f[
2057 * f_0, \ldots, f_{m-1}, f_{\text{shift}, 0}, \ldots, f_{\text{shift}, k-1}
2058 * \f]
2059 *
2060 * Simultaneously, the evaluation of the multilinear polynomial
2061 * \f[
2062 * \sum \rho^i \cdot f_i + \sum \rho^{i+k} \cdot f_{\text{shift}, i}
2063 * \f]
2064 * at the challenge point \f$ (u_0,\ldots, u_{n-1}) \f$ is computed.
2065 *
2066 * This approach minimizes the number of iterations over the commitments to multilinear polynomials
2067 * and eliminates the need to store the powers of \f$ \rho \f$.
2068 */
2069 // For ZK flavors: evaluations array is [gemini_masking_poly, qm, qc, ql, qr, ...]
2070 // Start batching challenge at 1, not rho, to match non-ZK pattern
2071 mem.batchingChallenge = Fr.wrap(1);
2072 mem.batchedEvaluation = Fr.wrap(0);
2073
2074 mem.unshiftedScalarNeg = mem.unshiftedScalar.neg();
2075 mem.shiftedScalarNeg = mem.shiftedScalar.neg();
2076
2077 // Process all NUMBER_UNSHIFTED_ZK evaluations (includes gemini_masking_poly at index 0)
2078 for (uint256 i = 1; i <= NUMBER_UNSHIFTED_ZK; ++i) {
2079 scalars[i] = mem.unshiftedScalarNeg * mem.batchingChallenge;
2080 mem.batchedEvaluation = mem.batchedEvaluation
2081 + (proof.sumcheckEvaluations[i - NUM_MASKING_POLYNOMIALS] * mem.batchingChallenge);
2082 mem.batchingChallenge = mem.batchingChallenge * tp.rho;
2083 }
2084 // g commitments are accumulated at r
2085 // For each of the to be shifted commitments perform the shift in place by
2086 // adding to the unshifted value.
2087 // We do so, as the values are to be used in batchMul later, and as
2088 // `a * c + b * c = (a + b) * c` this will allow us to reduce memory and compute.
2089 // Applied to w1, w2, w3, w4 and zPerm
2090 for (uint256 i = 0; i < NUMBER_TO_BE_SHIFTED; ++i) {
2091 uint256 scalarOff = i + SHIFTED_COMMITMENTS_START;
2092 uint256 evaluationOff = i + NUMBER_UNSHIFTED_ZK;
2093
2094 scalars[scalarOff] = scalars[scalarOff] + (mem.shiftedScalarNeg * mem.batchingChallenge);
2095 mem.batchedEvaluation =
2096 mem.batchedEvaluation + (proof.sumcheckEvaluations[evaluationOff] * mem.batchingChallenge);
2097 mem.batchingChallenge = mem.batchingChallenge * tp.rho;
2098 }
2099
2100 commitments[1] = proof.geminiMaskingPoly;
2101
2102 commitments[2] = vk.s1;
2103 commitments[3] = vk.s2;
2104 commitments[4] = vk.s3;
2105 commitments[5] = vk.s4;
2106 commitments[6] = vk.id1;
2107 commitments[7] = vk.id2;
2108 commitments[8] = vk.id3;
2109 commitments[9] = vk.id4;
2110 commitments[10] = vk.lagrangeFirst;
2111 commitments[11] = vk.lagrangeLast;
2112 commitments[12] = vk.qLookup;
2113 commitments[13] = vk.t1;
2114 commitments[14] = vk.t2;
2115 commitments[15] = vk.t3;
2116 commitments[16] = vk.t4;
2117 commitments[17] = vk.qm;
2118 commitments[18] = vk.qr;
2119 commitments[19] = vk.qo;
2120 commitments[20] = vk.qc;
2121 commitments[21] = vk.ql;
2122 commitments[22] = vk.q4;
2123 commitments[23] = vk.qArith;
2124 commitments[24] = vk.qDeltaRange;
2125 commitments[25] = vk.qElliptic;
2126 commitments[26] = vk.qMemory;
2127 commitments[27] = vk.qNnf;
2128 commitments[28] = vk.qPoseidon2External;
2129 commitments[29] = vk.qPoseidon2Internal;
2130
2131 // Accumulate proof points
2132 commitments[30] = proof.w1;
2133 commitments[31] = proof.w2;
2134 commitments[32] = proof.w3;
2135 commitments[33] = proof.w4;
2136 commitments[34] = proof.zPerm;
2137 commitments[35] = proof.lookupInverses;
2138 commitments[36] = proof.lookupReadCounts;
2139 commitments[37] = proof.lookupReadTags;
2140
2141 /* Batch gemini claims from the prover
2142 * place the commitments to gemini aᵢ to the vector of commitments, compute the contributions from
2143 * aᵢ(−r²ⁱ) for i=1, … , n−1 to the constant term accumulator, add corresponding scalars
2144 *
2145 * 1. Moves the vector
2146 * \f[
2147 * \left( \text{com}(A_1), \text{com}(A_2), \ldots, \text{com}(A_{n-1}) \right)
2148 * \f]
2149 * to the 'commitments' vector.
2150 *
2151 * 2. Computes the scalars:
2152 * \f[
2153 * \frac{\nu^{2}}{z + r^2}, \frac{\nu^3}{z + r^4}, \ldots, \frac{\nu^{n-1}}{z + r^{2^{n-1}}}
2154 * \f]
2155 * and places them into the 'scalars' vector.
2156 *
2157 * 3. Accumulates the summands of the constant term:
2158 * \f[
2159 * \sum_{i=2}^{n-1} \frac{\nu^{i} \cdot A_i(-r^{2^i})}{z + r^{2^i}}
2160 * \f]
2161 * and adds them to the 'constant_term_accumulator'.
2162 */
2163
2164 // Add contributions from A₀(r) and A₀(-r) to constant_term_accumulator:
2165 // Compute the evaluations Aₗ(r^{2ˡ}) for l = 0, ..., $LOG_N - 1
2166 Fr[] memory foldPosEvaluations = CommitmentSchemeLib.computeFoldPosEvaluations(
2167 tp.sumCheckUChallenges,
2168 mem.batchedEvaluation,
2169 proof.geminiAEvaluations,
2170 powers_of_evaluation_challenge,
2171 $LOG_N
2172 );
2173
2174 mem.constantTermAccumulator = foldPosEvaluations[0] * mem.posInvertedDenominator;
2175 mem.constantTermAccumulator =
2176 mem.constantTermAccumulator + (proof.geminiAEvaluations[0] * tp.shplonkNu * mem.negInvertedDenominator);
2177
2178 mem.batchingChallenge = tp.shplonkNu.sqr();
2179 uint256 boundary = NUMBER_UNSHIFTED_ZK + 1;
2180
2181 // Compute Shplonk constant term contributions from Aₗ(± r^{2ˡ}) for l = 1, ..., m-1;
2182 // Compute scalar multipliers for each fold commitment
2183 for (uint256 i = 0; i < $LOG_N - 1; ++i) {
2184 bool dummy_round = i >= ($LOG_N - 1);
2185
2186 if (!dummy_round) {
2187 // Update inverted denominators
2188 mem.posInvertedDenominator = (tp.shplonkZ - powers_of_evaluation_challenge[i + 1]).invert();
2189 mem.negInvertedDenominator = (tp.shplonkZ + powers_of_evaluation_challenge[i + 1]).invert();
2190
2191 // Compute the scalar multipliers for Aₗ(± r^{2ˡ}) and [Aₗ]
2192 mem.scalingFactorPos = mem.batchingChallenge * mem.posInvertedDenominator;
2193 mem.scalingFactorNeg = mem.batchingChallenge * tp.shplonkNu * mem.negInvertedDenominator;
2194 scalars[boundary + i] = mem.scalingFactorNeg.neg() + mem.scalingFactorPos.neg();
2195
2196 // Accumulate the const term contribution given by
2197 // v^{2l} * Aₗ(r^{2ˡ}) /(z-r^{2^l}) + v^{2l+1} * Aₗ(-r^{2ˡ}) /(z+ r^{2^l})
2198 Fr accumContribution = mem.scalingFactorNeg * proof.geminiAEvaluations[i + 1];
2199 accumContribution = accumContribution + mem.scalingFactorPos * foldPosEvaluations[i + 1];
2200 mem.constantTermAccumulator = mem.constantTermAccumulator + accumContribution;
2201 }
2202 // Update the running power of v
2203 mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu * tp.shplonkNu;
2204
2205 commitments[boundary + i] = proof.geminiFoldComms[i];
2206 }
2207
2208 boundary += $LOG_N - 1;
2209
2210 // Denominators 1/(z - point_i) for the five opening points {r, g*r, r, 1, r}.
2211 mem.denominators[0] = ONE.div(tp.shplonkZ - tp.geminiR);
2212 mem.denominators[1] = ONE.div(tp.shplonkZ - SUBGROUP_GENERATOR * tp.geminiR);
2213 mem.denominators[2] = mem.denominators[0];
2214 mem.denominators[SMALL_IPA_BOUNDARY_OPENING_IDX] = ONE.div(tp.shplonkZ - ONE);
2215 mem.denominators[NUM_SMALL_IPA_OPENING_CLAIMS - 1] = mem.denominators[0];
2216
2217 // Iterate the opening claims in three segments — the inner loops can't be merged without an extra induction
2218 // variable, which pushes us into stack-too-deep.
2219 for (uint256 i = 0; i < SMALL_IPA_BOUNDARY_OPENING_IDX; i++) {
2220 Fr scalingFactor = mem.denominators[i] * mem.batchingChallenge;
2221 mem.batchingScalars[i] = scalingFactor.neg();
2222 mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu;
2223 mem.constantTermAccumulator = mem.constantTermAccumulator + scalingFactor * proof.libraPolyEvals[i];
2224 }
2225
2226 // Boundary slot: claimed value is hardcoded 0, so no constantTermAccumulator contribution.
2227 {
2228 Fr scalingFactor = mem.denominators[SMALL_IPA_BOUNDARY_OPENING_IDX] * mem.batchingChallenge;
2229 mem.batchingScalars[SMALL_IPA_BOUNDARY_OPENING_IDX] = scalingFactor.neg();
2230 mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu;
2231 }
2232
2233 for (uint256 i = SMALL_IPA_BOUNDARY_OPENING_IDX + 1; i < NUM_SMALL_IPA_OPENING_CLAIMS; i++) {
2234 Fr scalingFactor = mem.denominators[i] * mem.batchingChallenge;
2235 mem.batchingScalars[i] = scalingFactor.neg();
2236 mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu;
2237 mem.constantTermAccumulator = mem.constantTermAccumulator + scalingFactor * proof.libraPolyEvals[i - 1];
2238 }
2239
2240 // Group per-claim batching scalars by commitment: [G], [A] (three openings), [Q].
2241 scalars[boundary] = mem.batchingScalars[0];
2242 scalars[boundary + 1] =
2243 mem.batchingScalars[1] + mem.batchingScalars[2] + mem.batchingScalars[SMALL_IPA_BOUNDARY_OPENING_IDX];
2244 scalars[boundary + 2] = mem.batchingScalars[NUM_SMALL_IPA_OPENING_CLAIMS - 1];
2245
2246 for (uint256 i = 0; i < LIBRA_COMMITMENTS; i++) {
2247 commitments[boundary++] = proof.libraCommitments[i];
2248 }
2249
2250 commitments[boundary] = Honk.G1Point({x: 1, y: 2});
2251 scalars[boundary++] = mem.constantTermAccumulator;
2252
2253 require(
2254 checkEvalsConsistency(proof.libraPolyEvals, tp.geminiR, tp.sumCheckUChallenges, proof.libraEvaluation),
2255 Errors.ConsistencyCheckFailed()
2256 );
2257
2258 Honk.G1Point memory quotient_commitment = proof.kzgQuotient;
2259
2260 commitments[boundary] = quotient_commitment;
2261 scalars[boundary] = tp.shplonkZ; // evaluation challenge
2262
2263 PairingInputs memory pair;
2264 pair.P_0 = batchMul(commitments, scalars);
2265 pair.P_1 = negateInplace(quotient_commitment);
2266
2267 // Aggregate pairing points (skip if default/infinity — no recursive verification occurred)
2268 if (!arePairingPointsDefault(proof.pairingPointObject)) {
2269 Fr recursionSeparator = generateRecursionSeparator(proof.pairingPointObject, pair.P_0, pair.P_1);
2270 (Honk.G1Point memory P_0_other, Honk.G1Point memory P_1_other) =
2271 convertPairingPointsToG1(proof.pairingPointObject);
2272
2273 // Validate the points from the proof are on the curve
2274 rejectPointAtInfinity(P_0_other);
2275 rejectPointAtInfinity(P_1_other);
2276
2277 // accumulate with aggregate points in proof
2278 pair.P_0 = mulWithSeperator(pair.P_0, P_0_other, recursionSeparator);
2279 pair.P_1 = mulWithSeperator(pair.P_1, P_1_other, recursionSeparator);
2280 }
2281
2282 return pairing(pair.P_0, pair.P_1);
2283 }
2284
2285 function checkEvalsConsistency(
2286 Fr[LIBRA_EVALUATIONS] memory libraPolyEvals,
2287 Fr geminiR,
2288 Fr[CONST_PROOF_SIZE_LOG_N] memory uChallenges,
2289 Fr libraEval
2290 ) internal view returns (bool check) {
2291 Fr one = Fr.wrap(1);
2292 Fr vanishingPolyEval = geminiR.pow(SUBGROUP_SIZE) - one;
2293 require(vanishingPolyEval != Fr.wrap(0), Errors.GeminiChallengeInSubgroup());
2294
2295 SmallSubgroupIpaIntermediates memory mem;
2296 mem.challengePolyLagrange[0] = one;
2297 for (uint256 round = 0; round < $LOG_N; round++) {
2298 uint256 currIdx = 1 + LIBRA_UNIVARIATES_LENGTH * round;
2299 mem.challengePolyLagrange[currIdx] = one;
2300 for (uint256 idx = currIdx + 1; idx < currIdx + LIBRA_UNIVARIATES_LENGTH; idx++) {
2301 mem.challengePolyLagrange[idx] = mem.challengePolyLagrange[idx - 1] * uChallenges[round];
2302 }
2303 }
2304
2305 mem.rootPower = one;
2306 mem.challengePolyEval = Fr.wrap(0);
2307 for (uint256 idx = 0; idx < SUBGROUP_SIZE; idx++) {
2308 mem.denominators[idx] = mem.rootPower * geminiR - one;
2309 mem.denominators[idx] = mem.denominators[idx].invert();
2310 mem.challengePolyEval = mem.challengePolyEval + mem.challengePolyLagrange[idx] * mem.denominators[idx];
2311 mem.rootPower = mem.rootPower * SUBGROUP_GENERATOR_INVERSE;
2312 }
2313
2314 Fr numerator = vanishingPolyEval * Fr.wrap(SUBGROUP_SIZE).invert();
2315 mem.challengePolyEval = mem.challengePolyEval * numerator;
2316 mem.lagrangeFirst = mem.denominators[0] * numerator;
2317 mem.lagrangeLast = mem.denominators[SUBGROUP_SIZE - 1] * numerator;
2318
2319 mem.diff = mem.lagrangeFirst * libraPolyEvals[2];
2320
2321 mem.diff = mem.diff + (geminiR - SUBGROUP_GENERATOR_INVERSE)
2322 * (libraPolyEvals[1] - libraPolyEvals[2] - libraPolyEvals[0] * mem.challengePolyEval);
2323 mem.diff = mem.diff + mem.lagrangeLast * (libraPolyEvals[2] - libraEval) - vanishingPolyEval * libraPolyEvals[3];
2324
2325 check = mem.diff == Fr.wrap(0);
2326 }
2327
2328 // This implementation is the same as above with different constants
2329 function batchMul(Honk.G1Point[] memory base, Fr[] memory scalars)
2330 internal
2331 view
2332 returns (Honk.G1Point memory result)
2333 {
2334 uint256 limit = $MSMSize;
2335
2336 // Identity bases are accepted: VK selector/table polys may be identically zero,
2337 // and the ecAdd/ecMul precompiles treat (0,0) as the additive identity per EIP-196.
2338 // Soundness against an attacker substituting (0,0) for a non-zero commitment is
2339 // upheld by sumcheck/Shplemini, which would fail on inconsistent evaluations.
2340
2341 bool success = true;
2342 assembly {
2343 let free := mload(0x40)
2344
2345 let count := 0x01
2346 for {} lt(count, add(limit, 1)) { count := add(count, 1) } {
2347 // Get loop offsets
2348 let base_base := add(base, mul(count, 0x20))
2349 let scalar_base := add(scalars, mul(count, 0x20))
2350
2351 mstore(add(free, 0x40), mload(mload(base_base)))
2352 mstore(add(free, 0x60), mload(add(0x20, mload(base_base))))
2353 // Add scalar
2354 mstore(add(free, 0x80), mload(scalar_base))
2355
2356 success := and(success, staticcall(gas(), 7, add(free, 0x40), 0x60, add(free, 0x40), 0x40))
2357 // accumulator = accumulator + accumulator_2
2358 success := and(success, staticcall(gas(), 6, free, 0x80, free, 0x40))
2359 }
2360
2361 // Return the result
2362 mstore(result, mload(free))
2363 mstore(add(result, 0x20), mload(add(free, 0x20)))
2364 }
2365
2366 require(success, Errors.ShpleminiFailed());
2367 }
2368
2369 // Calculate proof size based on log_n (matching UltraKeccakZKFlavor formula)
2370 function calculateProofSize(uint256 logN) internal pure returns (uint256) {
2371 // Witness and Libra commitments
2372 uint256 proofLength = NUM_WITNESS_ENTITIES * NUM_ELEMENTS_COMM; // witness commitments
2373 proofLength += NUM_ELEMENTS_COMM * 3; // Libra concat, grand sum, quotient comms + Gemini masking
2374
2375 // Sumcheck
2376 proofLength += logN * ZK_BATCHED_RELATION_PARTIAL_LENGTH * NUM_ELEMENTS_FR; // sumcheck univariates
2377 proofLength += NUMBER_OF_ENTITIES_ZK * NUM_ELEMENTS_FR; // sumcheck evaluations
2378
2379 // Libra and Gemini
2380 proofLength += NUM_ELEMENTS_FR * 2; // Libra sum, claimed eval
2381 proofLength += logN * NUM_ELEMENTS_FR; // Gemini a evaluations
2382 proofLength += NUM_LIBRA_EVALUATIONS * NUM_ELEMENTS_FR; // libra evaluations
2383
2384 // PCS commitments
2385 proofLength += (logN - 1) * NUM_ELEMENTS_COMM; // Gemini Fold commitments
2386 proofLength += NUM_ELEMENTS_COMM * 2; // Shplonk Q and KZG W commitments
2387
2388 // Pairing points
2389 proofLength += PAIRING_POINTS_SIZE; // pairing inputs carried on public inputs
2390
2391 return proofLength * 32;
2392 }
2393
2394 function loadVerificationKey() internal pure virtual returns (Honk.VerificationKey memory);
2395}
2396
2397contract HonkVerifier is BaseZKHonkVerifier(N, LOG_N, VK_HASH, NUMBER_OF_PUBLIC_INPUTS) {
2398 function loadVerificationKey() internal pure override returns (Honk.VerificationKey memory) {
2399 return HonkVerificationKey.loadVerificationKey();
2400 }
2401}
2402)";
2403
2404inline std::string get_honk_zk_solidity_verifier(auto const& verification_key)
2405{
2406 std::ostringstream stream;
2407 output_vk_sol_ultra_honk(stream, verification_key, "HonkVerificationKey");
2408 return stream.str() + HONK_ZK_CONTRACT_SOURCE;
2409}
void output_vk_sol_ultra_honk(std::ostream &os, auto const &key, std::string const &class_name, bool include_types_import=false)
std::string get_honk_zk_solidity_verifier(auto const &verification_key)