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