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