Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
honk_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_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 = 25;
200
201uint256 constant NUMBER_OF_SUBRELATIONS = 29;
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// Transcript library to generate fiat shamir challenges
376struct Transcript {
377 // Oink
378 Honk.RelationParameters relationParameters;
379 Fr[NUMBER_OF_ALPHAS] alphas; // Powers of alpha: [alpha, alpha^2, ..., alpha^(NUM_SUBRELATIONS-1)]
380 Fr[CONST_PROOF_SIZE_LOG_N] gateChallenges;
381 // Sumcheck
382 Fr[CONST_PROOF_SIZE_LOG_N] sumCheckUChallenges;
383 // Gemini
384 Fr rho;
385 Fr geminiR;
386 // Shplonk
387 Fr shplonkNu;
388 Fr shplonkZ;
389}
390
391library TranscriptLib {
392 function generateTranscript(
393 Honk.Proof memory proof,
394 bytes32[] calldata publicInputs,
395 uint256 vkHash,
396 uint256 publicInputsSize,
397 uint256 logN
398 ) internal pure returns (Transcript memory t) {
399 Fr previousChallenge;
400 (t.relationParameters, previousChallenge) =
401 generateRelationParametersChallenges(proof, publicInputs, vkHash, publicInputsSize, previousChallenge);
402
403 (t.alphas, previousChallenge) = generateAlphaChallenges(previousChallenge, proof);
404
405 (t.gateChallenges, previousChallenge) = generateGateChallenges(previousChallenge, logN);
406
407 (t.sumCheckUChallenges, previousChallenge) = generateSumcheckChallenges(proof, previousChallenge, logN);
408
409 (t.rho, previousChallenge) = generateRhoChallenge(proof, previousChallenge);
410
411 (t.geminiR, previousChallenge) = generateGeminiRChallenge(proof, previousChallenge, logN);
412
413 (t.shplonkNu, previousChallenge) = generateShplonkNuChallenge(proof, previousChallenge, logN);
414
415 (t.shplonkZ, previousChallenge) = generateShplonkZChallenge(proof, previousChallenge);
416
417 return t;
418 }
419
420 function splitChallenge(Fr challenge) internal pure returns (Fr first, Fr second) {
421 uint256 challengeU256 = uint256(Fr.unwrap(challenge));
422 // Split into two equal 127-bit chunks (254/2)
423 uint256 lo = challengeU256 & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // 127 bits
424 uint256 hi = challengeU256 >> 127;
425 first = FrLib.from(lo);
426 second = FrLib.from(hi);
427 }
428
429 function generateRelationParametersChallenges(
430 Honk.Proof memory proof,
431 bytes32[] calldata publicInputs,
432 uint256 vkHash,
433 uint256 publicInputsSize,
434 Fr previousChallenge
435 ) internal pure returns (Honk.RelationParameters memory rp, Fr nextPreviousChallenge) {
436 (rp.eta, previousChallenge) = generateEtaChallenge(proof, publicInputs, vkHash, publicInputsSize);
437
438 (rp.beta, rp.gamma, nextPreviousChallenge) = generateBetaGammaChallenges(previousChallenge, proof);
439 }
440
441 function generateEtaChallenge(
442 Honk.Proof memory proof,
443 bytes32[] calldata publicInputs,
444 uint256 vkHash,
445 uint256 publicInputsSize
446 ) internal pure returns (Fr eta, Fr previousChallenge) {
447 bytes32[] memory round0 = new bytes32[](1 + publicInputsSize + 6);
448 round0[0] = bytes32(vkHash);
449
450 for (uint256 i = 0; i < publicInputsSize - PAIRING_POINTS_SIZE; i++) {
451 require(uint256(publicInputs[i]) < P, Errors.ValueGeFieldOrder());
452 round0[1 + i] = publicInputs[i];
453 }
454 for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) {
455 round0[1 + publicInputsSize - PAIRING_POINTS_SIZE + i] = FrLib.toBytes32(proof.pairingPointObject[i]);
456 }
457
458 // Create the first challenge
459 // Note: w4 is added to the challenge later on
460 round0[1 + publicInputsSize] = bytes32(proof.w1.x);
461 round0[1 + publicInputsSize + 1] = bytes32(proof.w1.y);
462 round0[1 + publicInputsSize + 2] = bytes32(proof.w2.x);
463 round0[1 + publicInputsSize + 3] = bytes32(proof.w2.y);
464 round0[1 + publicInputsSize + 4] = bytes32(proof.w3.x);
465 round0[1 + publicInputsSize + 5] = bytes32(proof.w3.y);
466
467 previousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(round0))) % P);
468 (eta,) = splitChallenge(previousChallenge);
469 }
470
471 function generateBetaGammaChallenges(Fr previousChallenge, Honk.Proof memory proof)
472 internal
473 pure
474 returns (Fr beta, Fr gamma, Fr nextPreviousChallenge)
475 {
476 bytes32[7] memory round1;
477 round1[0] = FrLib.toBytes32(previousChallenge);
478 round1[1] = bytes32(proof.lookupReadCounts.x);
479 round1[2] = bytes32(proof.lookupReadCounts.y);
480 round1[3] = bytes32(proof.lookupReadTags.x);
481 round1[4] = bytes32(proof.lookupReadTags.y);
482 round1[5] = bytes32(proof.w4.x);
483 round1[6] = bytes32(proof.w4.y);
484
485 nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(round1))) % P);
486 (beta, gamma) = splitChallenge(nextPreviousChallenge);
487 }
488
489 // Alpha challenges non-linearise the gate contributions
490 function generateAlphaChallenges(Fr previousChallenge, Honk.Proof memory proof)
491 internal
492 pure
493 returns (Fr[NUMBER_OF_ALPHAS] memory alphas, Fr nextPreviousChallenge)
494 {
495 // Generate the original sumcheck alpha 0 by hashing zPerm and zLookup
496 uint256[5] memory alpha0;
497 alpha0[0] = Fr.unwrap(previousChallenge);
498 alpha0[1] = proof.lookupInverses.x;
499 alpha0[2] = proof.lookupInverses.y;
500 alpha0[3] = proof.zPerm.x;
501 alpha0[4] = proof.zPerm.y;
502
503 nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(alpha0))) % P);
504 Fr alpha;
505 (alpha,) = splitChallenge(nextPreviousChallenge);
506
507 // Compute powers of alpha for batching subrelations
508 alphas[0] = alpha;
509 for (uint256 i = 1; i < NUMBER_OF_ALPHAS; i++) {
510 alphas[i] = alphas[i - 1] * alpha;
511 }
512 }
513
514 function generateGateChallenges(Fr previousChallenge, uint256 logN)
515 internal
516 pure
517 returns (Fr[CONST_PROOF_SIZE_LOG_N] memory gateChallenges, Fr nextPreviousChallenge)
518 {
519 previousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge)))) % P);
520 (gateChallenges[0],) = splitChallenge(previousChallenge);
521 for (uint256 i = 1; i < logN; i++) {
522 gateChallenges[i] = gateChallenges[i - 1] * gateChallenges[i - 1];
523 }
524 nextPreviousChallenge = previousChallenge;
525 }
526
527 function generateSumcheckChallenges(Honk.Proof memory proof, Fr prevChallenge, uint256 logN)
528 internal
529 pure
530 returns (Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckChallenges, Fr nextPreviousChallenge)
531 {
532 for (uint256 i = 0; i < logN; i++) {
533 Fr[BATCHED_RELATION_PARTIAL_LENGTH + 1] memory univariateChal;
534 univariateChal[0] = prevChallenge;
535
536 for (uint256 j = 0; j < BATCHED_RELATION_PARTIAL_LENGTH; j++) {
537 univariateChal[j + 1] = proof.sumcheckUnivariates[i][j];
538 }
539 prevChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(univariateChal))) % P);
540 Fr unused;
541 (sumcheckChallenges[i], unused) = splitChallenge(prevChallenge);
542 }
543 nextPreviousChallenge = prevChallenge;
544 }
545
546 function generateRhoChallenge(Honk.Proof memory proof, Fr prevChallenge)
547 internal
548 pure
549 returns (Fr rho, Fr nextPreviousChallenge)
550 {
551 Fr[NUMBER_OF_ENTITIES + 1] memory rhoChallengeElements;
552 rhoChallengeElements[0] = prevChallenge;
553
554 for (uint256 i = 0; i < NUMBER_OF_ENTITIES; i++) {
555 rhoChallengeElements[i + 1] = proof.sumcheckEvaluations[i];
556 }
557
558 nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(rhoChallengeElements))) % P);
559 Fr unused;
560 (rho, unused) = splitChallenge(nextPreviousChallenge);
561 }
562
563 function generateGeminiRChallenge(Honk.Proof memory proof, Fr prevChallenge, uint256 logN)
564 internal
565 pure
566 returns (Fr geminiR, Fr nextPreviousChallenge)
567 {
568 uint256[] memory gR = new uint256[]((logN - 1) * 2 + 1);
569 gR[0] = Fr.unwrap(prevChallenge);
570
571 for (uint256 i = 0; i < logN - 1; i++) {
572 gR[1 + i * 2] = proof.geminiFoldComms[i].x;
573 gR[2 + i * 2] = proof.geminiFoldComms[i].y;
574 }
575
576 nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(gR))) % P);
577 Fr unused;
578 (geminiR, unused) = splitChallenge(nextPreviousChallenge);
579 }
580
581 function generateShplonkNuChallenge(Honk.Proof memory proof, Fr prevChallenge, uint256 logN)
582 internal
583 pure
584 returns (Fr shplonkNu, Fr nextPreviousChallenge)
585 {
586 uint256[] memory shplonkNuChallengeElements = new uint256[](logN + 1);
587 shplonkNuChallengeElements[0] = Fr.unwrap(prevChallenge);
588
589 for (uint256 i = 0; i < logN; i++) {
590 shplonkNuChallengeElements[i + 1] = Fr.unwrap(proof.geminiAEvaluations[i]);
591 }
592
593 nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(shplonkNuChallengeElements))) % P);
594 Fr unused;
595 (shplonkNu, unused) = splitChallenge(nextPreviousChallenge);
596 }
597
598 function generateShplonkZChallenge(Honk.Proof memory proof, Fr prevChallenge)
599 internal
600 pure
601 returns (Fr shplonkZ, Fr nextPreviousChallenge)
602 {
603 uint256[3] memory shplonkZChallengeElements;
604 shplonkZChallengeElements[0] = Fr.unwrap(prevChallenge);
605
606 shplonkZChallengeElements[1] = proof.shplonkQ.x;
607 shplonkZChallengeElements[2] = proof.shplonkQ.y;
608
609 nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(shplonkZChallengeElements))) % P);
610 Fr unused;
611 (shplonkZ, unused) = splitChallenge(nextPreviousChallenge);
612 }
613
614 function loadProof(bytes calldata proof, uint256 logN) internal pure returns (Honk.Proof memory p) {
615 uint256 boundary = 0x00;
616
617 // Pairing point object
618 for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) {
619 uint256 limb = uint256(bytes32(proof[boundary:boundary + FIELD_ELEMENT_SIZE]));
620 // lo limbs (even index) < 2^136, hi limbs (odd index) < 2^120
621 require(limb < 2 ** (i % 2 == 0 ? 136 : 120), Errors.ValueGeLimbMax());
622 p.pairingPointObject[i] = FrLib.from(limb);
623 boundary += FIELD_ELEMENT_SIZE;
624 }
625 // Commitments
626 p.w1 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
627 boundary += GROUP_ELEMENT_SIZE;
628 p.w2 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
629 boundary += GROUP_ELEMENT_SIZE;
630 p.w3 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
631 boundary += GROUP_ELEMENT_SIZE;
632
633 // Lookup / Permutation Helper Commitments
634 p.lookupReadCounts = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
635 boundary += GROUP_ELEMENT_SIZE;
636 p.lookupReadTags = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
637 boundary += GROUP_ELEMENT_SIZE;
638 p.w4 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
639 boundary += GROUP_ELEMENT_SIZE;
640 p.lookupInverses = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
641 boundary += GROUP_ELEMENT_SIZE;
642 p.zPerm = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
643 boundary += GROUP_ELEMENT_SIZE;
644
645 // Sumcheck univariates
646 for (uint256 i = 0; i < logN; i++) {
647 for (uint256 j = 0; j < BATCHED_RELATION_PARTIAL_LENGTH; j++) {
648 p.sumcheckUnivariates[i][j] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]);
649 boundary += FIELD_ELEMENT_SIZE;
650 }
651 }
652 // Sumcheck evaluations
653 for (uint256 i = 0; i < NUMBER_OF_ENTITIES; i++) {
654 p.sumcheckEvaluations[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]);
655 boundary += FIELD_ELEMENT_SIZE;
656 }
657
658 // Gemini
659 // Read gemini fold univariates
660 for (uint256 i = 0; i < logN - 1; i++) {
661 p.geminiFoldComms[i] = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
662 boundary += GROUP_ELEMENT_SIZE;
663 }
664
665 // Read gemini a evaluations
666 for (uint256 i = 0; i < logN; i++) {
667 p.geminiAEvaluations[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]);
668 boundary += FIELD_ELEMENT_SIZE;
669 }
670
671 // Shplonk
672 p.shplonkQ = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
673 boundary += GROUP_ELEMENT_SIZE;
674 // KZG
675 p.kzgQuotient = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]);
676 }
677}
678
679library RelationsLib {
680 struct EllipticParams {
681 // Points
682 Fr x_1;
683 Fr y_1;
684 Fr x_2;
685 Fr y_2;
686 Fr y_3;
687 Fr x_3;
688 // push accumulators into memory
689 Fr x_double_identity;
690 }
691
692 // Parameters used within the Memory Relation
693 // A struct is used to work around stack too deep. This relation has alot of variables
694 struct MemParams {
695 Fr memory_record_check;
696 Fr partial_record_check;
697 Fr next_gate_access_type;
698 Fr record_delta;
699 Fr index_delta;
700 Fr adjacent_values_match_if_adjacent_indices_match;
701 Fr adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation;
702 Fr access_check;
703 Fr next_gate_access_type_is_boolean;
704 Fr ROM_consistency_check_identity;
705 Fr RAM_consistency_check_identity;
706 Fr timestamp_delta;
707 Fr RAM_timestamp_check_identity;
708 Fr memory_identity;
709 Fr index_is_monotonically_increasing;
710 }
711
712 // Parameters used within the Non-Native Field Relation
713 // A struct is used to work around stack too deep. This relation has alot of variables
714 struct NnfParams {
715 Fr limb_subproduct;
716 Fr non_native_field_gate_1;
717 Fr non_native_field_gate_2;
718 Fr non_native_field_gate_3;
719 Fr limb_accumulator_1;
720 Fr limb_accumulator_2;
721 Fr nnf_identity;
722 }
723
724 struct PoseidonExternalParams {
725 Fr s1;
726 Fr s2;
727 Fr s3;
728 Fr s4;
729 Fr u1;
730 Fr u2;
731 Fr u3;
732 Fr u4;
733 Fr t0;
734 Fr t1;
735 Fr t2;
736 Fr t3;
737 Fr v1;
738 Fr v2;
739 Fr v3;
740 Fr v4;
741 Fr q_pos_by_scaling;
742 }
743
744 struct PoseidonInternalParams {
745 Fr u1;
746 Fr u2;
747 Fr u3;
748 Fr u4;
749 Fr u_sum;
750 Fr v1;
751 Fr v2;
752 Fr v3;
753 Fr v4;
754 Fr s1;
755 Fr q_pos_by_scaling;
756 }
757
758 Fr internal constant GRUMPKIN_CURVE_B_PARAMETER_NEGATED = Fr.wrap(17); // -(-17)
759 uint256 internal constant NEG_HALF_MODULO_P = 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000;
760
761 // Constants for the Non-native Field relation
762 Fr internal constant LIMB_SIZE = Fr.wrap(uint256(1) << 68);
763 Fr internal constant SUBLIMB_SHIFT = Fr.wrap(uint256(1) << 14);
764
765 function accumulateRelationEvaluations(
766 Fr[NUMBER_OF_ENTITIES] memory purportedEvaluations,
767 Honk.RelationParameters memory rp,
768 Fr[NUMBER_OF_ALPHAS] memory subrelationChallenges,
769 Fr powPartialEval
770 ) external pure returns (Fr accumulator) {
771 Fr[NUMBER_OF_SUBRELATIONS] memory evaluations;
772
773 // Accumulate all relations in Ultra Honk - each with varying number of subrelations
774 accumulateArithmeticRelation(purportedEvaluations, evaluations, powPartialEval);
775 accumulatePermutationRelation(purportedEvaluations, rp, evaluations, powPartialEval);
776 accumulateLogDerivativeLookupRelation(purportedEvaluations, rp, evaluations, powPartialEval);
777 accumulateDeltaRangeRelation(purportedEvaluations, evaluations, powPartialEval);
778 accumulateEllipticRelation(purportedEvaluations, evaluations, powPartialEval);
779 accumulateMemoryRelation(purportedEvaluations, rp, evaluations, powPartialEval);
780 accumulateNnfRelation(purportedEvaluations, evaluations, powPartialEval);
781 accumulatePoseidonExternalRelation(purportedEvaluations, evaluations, powPartialEval);
782 accumulatePoseidonInternalRelation(purportedEvaluations, evaluations, powPartialEval);
783
784 // batch the subrelations with the precomputed alpha powers to obtain the full honk relation
785 accumulator = scaleAndBatchSubrelations(evaluations, subrelationChallenges);
786 }
787
793 function wire(Fr[NUMBER_OF_ENTITIES] memory p, WIRE _wire) internal pure returns (Fr) {
794 return p[uint256(_wire)];
795 }
796
801 function accumulateArithmeticRelation(
802 Fr[NUMBER_OF_ENTITIES] memory p,
803 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
804 Fr domainSep
805 ) internal pure {
806 // Relation 0
807 Fr q_arith = wire(p, WIRE.Q_ARITH);
808 {
809 Fr neg_half = Fr.wrap(NEG_HALF_MODULO_P);
810
811 Fr accum = (q_arith - Fr.wrap(3)) * (wire(p, WIRE.Q_M) * wire(p, WIRE.W_R) * wire(p, WIRE.W_L)) * neg_half;
812 accum = accum + (wire(p, WIRE.Q_L) * wire(p, WIRE.W_L)) + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_R))
813 + (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);
814 accum = accum + (q_arith - ONE) * wire(p, WIRE.W_4_SHIFT);
815 accum = accum * q_arith;
816 accum = accum * domainSep;
817 evals[0] = accum;
818 }
819
820 // Relation 1
821 {
822 Fr accum = wire(p, WIRE.W_L) + wire(p, WIRE.W_4) - wire(p, WIRE.W_L_SHIFT) + wire(p, WIRE.Q_M);
823 accum = accum * (q_arith - Fr.wrap(2));
824 accum = accum * (q_arith - ONE);
825 accum = accum * q_arith;
826 accum = accum * domainSep;
827 evals[1] = accum;
828 }
829 }
830
831 function accumulatePermutationRelation(
832 Fr[NUMBER_OF_ENTITIES] memory p,
833 Honk.RelationParameters memory rp,
834 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
835 Fr domainSep
836 ) internal pure {
837 Fr grand_product_numerator;
838 Fr grand_product_denominator;
839
840 {
841 Fr num = wire(p, WIRE.W_L) + wire(p, WIRE.ID_1) * rp.beta + rp.gamma;
842 num = num * (wire(p, WIRE.W_R) + wire(p, WIRE.ID_2) * rp.beta + rp.gamma);
843 num = num * (wire(p, WIRE.W_O) + wire(p, WIRE.ID_3) * rp.beta + rp.gamma);
844 num = num * (wire(p, WIRE.W_4) + wire(p, WIRE.ID_4) * rp.beta + rp.gamma);
845
846 grand_product_numerator = num;
847 }
848 {
849 Fr den = wire(p, WIRE.W_L) + wire(p, WIRE.SIGMA_1) * rp.beta + rp.gamma;
850 den = den * (wire(p, WIRE.W_R) + wire(p, WIRE.SIGMA_2) * rp.beta + rp.gamma);
851 den = den * (wire(p, WIRE.W_O) + wire(p, WIRE.SIGMA_3) * rp.beta + rp.gamma);
852 den = den * (wire(p, WIRE.W_4) + wire(p, WIRE.SIGMA_4) * rp.beta + rp.gamma);
853
854 grand_product_denominator = den;
855 }
856
857 // Contribution 2
858 {
859 Fr acc = (wire(p, WIRE.Z_PERM) + wire(p, WIRE.LAGRANGE_FIRST)) * grand_product_numerator;
860
861 acc = acc
862 - ((wire(p, WIRE.Z_PERM_SHIFT) + (wire(p, WIRE.LAGRANGE_LAST) * rp.publicInputsDelta))
863 * grand_product_denominator);
864 acc = acc * domainSep;
865 evals[2] = acc;
866 }
867
868 // Contribution 3
869 {
870 Fr acc = (wire(p, WIRE.LAGRANGE_LAST) * wire(p, WIRE.Z_PERM_SHIFT)) * domainSep;
871 evals[3] = acc;
872 }
873
874 // Contribution 4: z_perm initialization check (lagrange_first * z_perm = 0)
875 {
876 Fr acc = (wire(p, WIRE.LAGRANGE_FIRST) * wire(p, WIRE.Z_PERM)) * domainSep;
877 evals[4] = acc;
878 }
879 }
880
881 function accumulateLogDerivativeLookupRelation(
882 Fr[NUMBER_OF_ENTITIES] memory p,
883 Honk.RelationParameters memory rp,
884 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
885 Fr domainSep
886 ) internal pure {
887 Fr table_term;
888 Fr lookup_term;
889
890 // Calculate the write term (the table accumulation)
891 // table_term = table_1 + γ + table_2 * β + table_3 * β² + table_4 * β³
892 {
893 Fr beta_sqr = rp.beta * rp.beta;
894 table_term = wire(p, WIRE.TABLE_1) + rp.gamma + (wire(p, WIRE.TABLE_2) * rp.beta)
895 + (wire(p, WIRE.TABLE_3) * beta_sqr) + (wire(p, WIRE.TABLE_4) * beta_sqr * rp.beta);
896 }
897
898 // Calculate the read term
899 // lookup_term = derived_entry_1 + γ + derived_entry_2 * β + derived_entry_3 * β² + q_index * β³
900 {
901 Fr beta_sqr = rp.beta * rp.beta;
902 Fr derived_entry_1 = wire(p, WIRE.W_L) + rp.gamma + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_L_SHIFT));
903 Fr derived_entry_2 = wire(p, WIRE.W_R) + wire(p, WIRE.Q_M) * wire(p, WIRE.W_R_SHIFT);
904 Fr derived_entry_3 = wire(p, WIRE.W_O) + wire(p, WIRE.Q_C) * wire(p, WIRE.W_O_SHIFT);
905
906 lookup_term = derived_entry_1 + (derived_entry_2 * rp.beta) + (derived_entry_3 * beta_sqr)
907 + (wire(p, WIRE.Q_O) * beta_sqr * rp.beta);
908 }
909
910 Fr lookup_inverse = wire(p, WIRE.LOOKUP_INVERSES) * table_term;
911 Fr table_inverse = wire(p, WIRE.LOOKUP_INVERSES) * lookup_term;
912
913 Fr inverse_exists_xor =
914 wire(p, WIRE.LOOKUP_READ_TAGS) + wire(p, WIRE.Q_LOOKUP)
915 - (wire(p, WIRE.LOOKUP_READ_TAGS) * wire(p, WIRE.Q_LOOKUP));
916
917 // Inverse calculated correctly relation
918 Fr accumulatorNone = lookup_term * table_term * wire(p, WIRE.LOOKUP_INVERSES) - inverse_exists_xor;
919 accumulatorNone = accumulatorNone * domainSep;
920
921 // Inverse
922 Fr accumulatorOne = wire(p, WIRE.Q_LOOKUP) * lookup_inverse - wire(p, WIRE.LOOKUP_READ_COUNTS) * table_inverse;
923
924 Fr read_tag = wire(p, WIRE.LOOKUP_READ_TAGS);
925
926 Fr read_tag_boolean_relation = read_tag * read_tag - read_tag;
927
928 evals[5] = accumulatorNone;
929 evals[6] = accumulatorOne;
930 evals[7] = read_tag_boolean_relation * domainSep;
931 }
932
933 function accumulateDeltaRangeRelation(
934 Fr[NUMBER_OF_ENTITIES] memory p,
935 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
936 Fr domainSep
937 ) internal pure {
938 Fr minus_one = ZERO - ONE;
939 Fr minus_two = ZERO - Fr.wrap(2);
940 Fr minus_three = ZERO - Fr.wrap(3);
941
942 // Compute wire differences
943 Fr delta_1 = wire(p, WIRE.W_R) - wire(p, WIRE.W_L);
944 Fr delta_2 = wire(p, WIRE.W_O) - wire(p, WIRE.W_R);
945 Fr delta_3 = wire(p, WIRE.W_4) - wire(p, WIRE.W_O);
946 Fr delta_4 = wire(p, WIRE.W_L_SHIFT) - wire(p, WIRE.W_4);
947
948 // Contribution 6
949 {
950 Fr acc = delta_1;
951 acc = acc * (delta_1 + minus_one);
952 acc = acc * (delta_1 + minus_two);
953 acc = acc * (delta_1 + minus_three);
954 acc = acc * wire(p, WIRE.Q_RANGE);
955 acc = acc * domainSep;
956 evals[8] = acc;
957 }
958
959 // Contribution 7
960 {
961 Fr acc = delta_2;
962 acc = acc * (delta_2 + minus_one);
963 acc = acc * (delta_2 + minus_two);
964 acc = acc * (delta_2 + minus_three);
965 acc = acc * wire(p, WIRE.Q_RANGE);
966 acc = acc * domainSep;
967 evals[9] = acc;
968 }
969
970 // Contribution 8
971 {
972 Fr acc = delta_3;
973 acc = acc * (delta_3 + minus_one);
974 acc = acc * (delta_3 + minus_two);
975 acc = acc * (delta_3 + minus_three);
976 acc = acc * wire(p, WIRE.Q_RANGE);
977 acc = acc * domainSep;
978 evals[10] = acc;
979 }
980
981 // Contribution 9
982 {
983 Fr acc = delta_4;
984 acc = acc * (delta_4 + minus_one);
985 acc = acc * (delta_4 + minus_two);
986 acc = acc * (delta_4 + minus_three);
987 acc = acc * wire(p, WIRE.Q_RANGE);
988 acc = acc * domainSep;
989 evals[11] = acc;
990 }
991 }
992
993 function accumulateEllipticRelation(
994 Fr[NUMBER_OF_ENTITIES] memory p,
995 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
996 Fr domainSep
997 ) internal pure {
998 EllipticParams memory ep;
999 ep.x_1 = wire(p, WIRE.W_R);
1000 ep.y_1 = wire(p, WIRE.W_O);
1001
1002 ep.x_2 = wire(p, WIRE.W_L_SHIFT);
1003 ep.y_2 = wire(p, WIRE.W_4_SHIFT);
1004 ep.y_3 = wire(p, WIRE.W_O_SHIFT);
1005 ep.x_3 = wire(p, WIRE.W_R_SHIFT);
1006
1007 Fr q_sign = wire(p, WIRE.Q_L);
1008 Fr q_is_double = wire(p, WIRE.Q_M);
1009
1010 // Contribution 10 point addition, x-coordinate check
1011 // q_elliptic * (x3 + x2 + x1)(x2 - x1)(x2 - x1) - y2^2 - y1^2 + 2(y2y1)*q_sign = 0
1012 Fr x_diff = (ep.x_2 - ep.x_1);
1013 Fr y1_sqr = (ep.y_1 * ep.y_1);
1014 {
1015 // Move to top
1016 Fr partialEval = domainSep;
1017
1018 Fr y2_sqr = (ep.y_2 * ep.y_2);
1019 Fr y1y2 = ep.y_1 * ep.y_2 * q_sign;
1020 Fr x_add_identity = (ep.x_3 + ep.x_2 + ep.x_1);
1021 x_add_identity = x_add_identity * x_diff * x_diff;
1022 x_add_identity = x_add_identity - y2_sqr - y1_sqr + y1y2 + y1y2;
1023
1024 evals[12] = x_add_identity * partialEval * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double);
1025 }
1026
1027 // Contribution 11 point addition, x-coordinate check
1028 // q_elliptic * (q_sign * y1 + y3)(x2 - x1) + (x3 - x1)(y2 - q_sign * y1) = 0
1029 {
1030 Fr y1_plus_y3 = ep.y_1 + ep.y_3;
1031 Fr y_diff = ep.y_2 * q_sign - ep.y_1;
1032 Fr y_add_identity = y1_plus_y3 * x_diff + (ep.x_3 - ep.x_1) * y_diff;
1033 evals[13] = y_add_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double);
1034 }
1035
1036 // Contribution 10 point doubling, x-coordinate check
1037 // (x3 + x1 + x1) (4y1*y1) - 9 * x1 * x1 * x1 * x1 = 0
1038 // N.B. we're using the equivalence x1*x1*x1 === y1*y1 - curve_b to reduce degree by 1
1039 {
1040 Fr x_pow_4 = (y1_sqr + GRUMPKIN_CURVE_B_PARAMETER_NEGATED) * ep.x_1;
1041 Fr y1_sqr_mul_4 = y1_sqr + y1_sqr;
1042 y1_sqr_mul_4 = y1_sqr_mul_4 + y1_sqr_mul_4;
1043 Fr x1_pow_4_mul_9 = x_pow_4 * Fr.wrap(9);
1044
1045 // NOTE: pushed into memory (stack >:'( )
1046 ep.x_double_identity = (ep.x_3 + ep.x_1 + ep.x_1) * y1_sqr_mul_4 - x1_pow_4_mul_9;
1047
1048 Fr acc = ep.x_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double;
1049 evals[12] = evals[12] + acc;
1050 }
1051
1052 // Contribution 11 point doubling, y-coordinate check
1053 // (y1 + y1) (2y1) - (3 * x1 * x1)(x1 - x3) = 0
1054 {
1055 Fr x1_sqr_mul_3 = (ep.x_1 + ep.x_1 + ep.x_1) * ep.x_1;
1056 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);
1057 evals[13] = evals[13] + y_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double;
1058 }
1059 }
1060
1061 function accumulateMemoryRelation(
1062 Fr[NUMBER_OF_ENTITIES] memory p,
1063 Honk.RelationParameters memory rp,
1064 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
1065 Fr domainSep
1066 ) internal pure {
1067 MemParams memory ap;
1068
1069 // Compute eta powers locally
1070 Fr eta_two = rp.eta * rp.eta;
1071 Fr eta_three = eta_two * rp.eta;
1072
1114 ap.memory_record_check = wire(p, WIRE.W_O) * eta_three;
1115 ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_R) * eta_two);
1116 ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_L) * rp.eta);
1117 ap.memory_record_check = ap.memory_record_check + wire(p, WIRE.Q_C);
1118 ap.partial_record_check = ap.memory_record_check; // used in RAM consistency check; deg 1 or 4
1119 ap.memory_record_check = ap.memory_record_check - wire(p, WIRE.W_4);
1120
1137 ap.index_delta = wire(p, WIRE.W_L_SHIFT) - wire(p, WIRE.W_L);
1138 ap.record_delta = wire(p, WIRE.W_4_SHIFT) - wire(p, WIRE.W_4);
1139
1140 ap.index_is_monotonically_increasing = ap.index_delta * (ap.index_delta - Fr.wrap(1)); // deg 2
1141
1142 ap.adjacent_values_match_if_adjacent_indices_match = (ap.index_delta * MINUS_ONE + ONE) * ap.record_delta; // deg 2
1143
1144 evals[15] = ap.adjacent_values_match_if_adjacent_indices_match * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R))
1145 * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5
1146 evals[16] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R))
1147 * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5
1148
1149 ap.ROM_consistency_check_identity = ap.memory_record_check * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)); // deg 3 or 7
1150
1170 Fr access_type = (wire(p, WIRE.W_4) - ap.partial_record_check); // will be 0 or 1 for honest Prover; deg 1 or 4
1171 ap.access_check = access_type * (access_type - Fr.wrap(1)); // check value is 0 or 1; deg 2 or 8
1172
1173 // reverse order we could re-use `ap.partial_record_check` 1 - ((w3' * eta + w2') * eta + w1') * eta
1174 // deg 1 or 4
1175 ap.next_gate_access_type = wire(p, WIRE.W_O_SHIFT) * eta_three;
1176 ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_R_SHIFT) * eta_two);
1177 ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_L_SHIFT) * rp.eta);
1178 ap.next_gate_access_type = wire(p, WIRE.W_4_SHIFT) - ap.next_gate_access_type;
1179
1180 Fr value_delta = wire(p, WIRE.W_O_SHIFT) - wire(p, WIRE.W_O);
1181 ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation =
1182 (ap.index_delta * MINUS_ONE + ONE) * value_delta * (ap.next_gate_access_type * MINUS_ONE + ONE); // deg 3 or 6
1183
1184 // We can't apply the RAM consistency check identity on the final entry in the sorted list (the wires in the
1185 // next gate would make the identity fail). We need to validate that its 'access type' bool is correct. Can't
1186 // do with an arithmetic gate because of the `eta` factors. We need to check that the *next* gate's access
1187 // type is correct, to cover this edge case
1188 // deg 2 or 4
1189 ap.next_gate_access_type_is_boolean =
1190 ap.next_gate_access_type * ap.next_gate_access_type - ap.next_gate_access_type;
1191
1192 // Putting it all together...
1193 evals[17] = ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation
1194 * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 or 8
1195 evals[18] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4
1196 evals[19] = ap.next_gate_access_type_is_boolean * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 6
1197
1198 ap.RAM_consistency_check_identity = ap.access_check * (wire(p, WIRE.Q_O)); // deg 3 or 9
1199
1211 ap.timestamp_delta = wire(p, WIRE.W_R_SHIFT) - wire(p, WIRE.W_R);
1212 ap.RAM_timestamp_check_identity = (ap.index_delta * MINUS_ONE + ONE) * ap.timestamp_delta - wire(p, WIRE.W_O); // deg 3
1213
1219 ap.memory_identity = ap.ROM_consistency_check_identity; // deg 3 or 6
1220 ap.memory_identity =
1221 ap.memory_identity + ap.RAM_timestamp_check_identity * (wire(p, WIRE.Q_4) * wire(p, WIRE.Q_L)); // deg 4
1222 ap.memory_identity = ap.memory_identity + ap.memory_record_check * (wire(p, WIRE.Q_M) * wire(p, WIRE.Q_L)); // deg 3 or 6
1223 ap.memory_identity = ap.memory_identity + ap.RAM_consistency_check_identity; // deg 3 or 9
1224
1225 // (deg 3 or 9) + (deg 4) + (deg 3)
1226 ap.memory_identity = ap.memory_identity * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 10
1227 evals[14] = ap.memory_identity;
1228 }
1229
1230 function accumulateNnfRelation(
1231 Fr[NUMBER_OF_ENTITIES] memory p,
1232 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
1233 Fr domainSep
1234 ) internal pure {
1235 NnfParams memory ap;
1236
1249 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);
1250 ap.non_native_field_gate_2 =
1251 (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));
1252 ap.non_native_field_gate_2 = ap.non_native_field_gate_2 * LIMB_SIZE;
1253 ap.non_native_field_gate_2 = ap.non_native_field_gate_2 - wire(p, WIRE.W_4_SHIFT);
1254 ap.non_native_field_gate_2 = ap.non_native_field_gate_2 + ap.limb_subproduct;
1255 ap.non_native_field_gate_2 = ap.non_native_field_gate_2 * wire(p, WIRE.Q_4);
1256
1257 ap.limb_subproduct = ap.limb_subproduct * LIMB_SIZE;
1258 ap.limb_subproduct = ap.limb_subproduct + (wire(p, WIRE.W_L_SHIFT) * wire(p, WIRE.W_R_SHIFT));
1259 ap.non_native_field_gate_1 = ap.limb_subproduct;
1260 ap.non_native_field_gate_1 = ap.non_native_field_gate_1 - (wire(p, WIRE.W_O) + wire(p, WIRE.W_4));
1261 ap.non_native_field_gate_1 = ap.non_native_field_gate_1 * wire(p, WIRE.Q_O);
1262
1263 ap.non_native_field_gate_3 = ap.limb_subproduct;
1264 ap.non_native_field_gate_3 = ap.non_native_field_gate_3 + wire(p, WIRE.W_4);
1265 ap.non_native_field_gate_3 = ap.non_native_field_gate_3 - (wire(p, WIRE.W_O_SHIFT) + wire(p, WIRE.W_4_SHIFT));
1266 ap.non_native_field_gate_3 = ap.non_native_field_gate_3 * wire(p, WIRE.Q_M);
1267
1268 Fr non_native_field_identity =
1269 ap.non_native_field_gate_1 + ap.non_native_field_gate_2 + ap.non_native_field_gate_3;
1270 non_native_field_identity = non_native_field_identity * wire(p, WIRE.Q_R);
1271
1272 // ((((w2' * 2^14 + w1') * 2^14 + w3) * 2^14 + w2) * 2^14 + w1 - w4) * qm
1273 // deg 2
1274 ap.limb_accumulator_1 = wire(p, WIRE.W_R_SHIFT) * SUBLIMB_SHIFT;
1275 ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_L_SHIFT);
1276 ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT;
1277 ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_O);
1278 ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT;
1279 ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_R);
1280 ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT;
1281 ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_L);
1282 ap.limb_accumulator_1 = ap.limb_accumulator_1 - wire(p, WIRE.W_4);
1283 ap.limb_accumulator_1 = ap.limb_accumulator_1 * wire(p, WIRE.Q_4);
1284
1285 // ((((w3' * 2^14 + w2') * 2^14 + w1') * 2^14 + w4) * 2^14 + w3 - w4') * qm
1286 // deg 2
1287 ap.limb_accumulator_2 = wire(p, WIRE.W_O_SHIFT) * SUBLIMB_SHIFT;
1288 ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_R_SHIFT);
1289 ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT;
1290 ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_L_SHIFT);
1291 ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT;
1292 ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_4);
1293 ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT;
1294 ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_O);
1295 ap.limb_accumulator_2 = ap.limb_accumulator_2 - wire(p, WIRE.W_4_SHIFT);
1296 ap.limb_accumulator_2 = ap.limb_accumulator_2 * wire(p, WIRE.Q_M);
1297
1298 Fr limb_accumulator_identity = ap.limb_accumulator_1 + ap.limb_accumulator_2;
1299 limb_accumulator_identity = limb_accumulator_identity * wire(p, WIRE.Q_O); // deg 3
1300
1301 ap.nnf_identity = non_native_field_identity + limb_accumulator_identity;
1302 ap.nnf_identity = ap.nnf_identity * (wire(p, WIRE.Q_NNF) * domainSep);
1303 evals[20] = ap.nnf_identity;
1304 }
1305
1306 function accumulatePoseidonExternalRelation(
1307 Fr[NUMBER_OF_ENTITIES] memory p,
1308 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
1309 Fr domainSep
1310 ) internal pure {
1311 PoseidonExternalParams memory ep;
1312
1313 ep.s1 = wire(p, WIRE.W_L) + wire(p, WIRE.Q_L);
1314 ep.s2 = wire(p, WIRE.W_R) + wire(p, WIRE.Q_R);
1315 ep.s3 = wire(p, WIRE.W_O) + wire(p, WIRE.Q_O);
1316 ep.s4 = wire(p, WIRE.W_4) + wire(p, WIRE.Q_4);
1317
1318 ep.u1 = ep.s1 * ep.s1 * ep.s1 * ep.s1 * ep.s1;
1319 ep.u2 = ep.s2 * ep.s2 * ep.s2 * ep.s2 * ep.s2;
1320 ep.u3 = ep.s3 * ep.s3 * ep.s3 * ep.s3 * ep.s3;
1321 ep.u4 = ep.s4 * ep.s4 * ep.s4 * ep.s4 * ep.s4;
1322 // matrix mul v = M_E * u with 14 additions
1323 ep.t0 = ep.u1 + ep.u2; // u_1 + u_2
1324 ep.t1 = ep.u3 + ep.u4; // u_3 + u_4
1325 ep.t2 = ep.u2 + ep.u2 + ep.t1; // 2u_2
1326 // ep.t2 += ep.t1; // 2u_2 + u_3 + u_4
1327 ep.t3 = ep.u4 + ep.u4 + ep.t0; // 2u_4
1328 // ep.t3 += ep.t0; // u_1 + u_2 + 2u_4
1329 ep.v4 = ep.t1 + ep.t1;
1330 ep.v4 = ep.v4 + ep.v4 + ep.t3;
1331 // ep.v4 += ep.t3; // u_1 + u_2 + 4u_3 + 6u_4
1332 ep.v2 = ep.t0 + ep.t0;
1333 ep.v2 = ep.v2 + ep.v2 + ep.t2;
1334 // ep.v2 += ep.t2; // 4u_1 + 6u_2 + u_3 + u_4
1335 ep.v1 = ep.t3 + ep.v2; // 5u_1 + 7u_2 + u_3 + 3u_4
1336 ep.v3 = ep.t2 + ep.v4; // u_1 + 3u_2 + 5u_3 + 7u_4
1337
1338 ep.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_EXTERNAL) * domainSep;
1339 evals[21] = evals[21] + ep.q_pos_by_scaling * (ep.v1 - wire(p, WIRE.W_L_SHIFT));
1340
1341 evals[22] = evals[22] + ep.q_pos_by_scaling * (ep.v2 - wire(p, WIRE.W_R_SHIFT));
1342
1343 evals[23] = evals[23] + ep.q_pos_by_scaling * (ep.v3 - wire(p, WIRE.W_O_SHIFT));
1344
1345 evals[24] = evals[24] + ep.q_pos_by_scaling * (ep.v4 - wire(p, WIRE.W_4_SHIFT));
1346 }
1347
1348 function accumulatePoseidonInternalRelation(
1349 Fr[NUMBER_OF_ENTITIES] memory p,
1350 Fr[NUMBER_OF_SUBRELATIONS] memory evals,
1351 Fr domainSep
1352 ) internal pure {
1353 PoseidonInternalParams memory ip;
1354
1355 Fr[4] memory INTERNAL_MATRIX_DIAGONAL = [
1356 FrLib.from(0x10dc6e9c006ea38b04b1e03b4bd9490c0d03f98929ca1d7fb56821fd19d3b6e7),
1357 FrLib.from(0x0c28145b6a44df3e0149b3d0a30b3bb599df9756d4dd9b84a86b38cfb45a740b),
1358 FrLib.from(0x00544b8338791518b2c7645a50392798b21f75bb60e3596170067d00141cac15),
1359 FrLib.from(0x222c01175718386f2e2e82eb122789e352e105a3b8fa852613bc534433ee428b)
1360 ];
1361
1362 // add round constants
1363 ip.s1 = wire(p, WIRE.W_L) + wire(p, WIRE.Q_L);
1364
1365 // apply s-box round
1366 ip.u1 = ip.s1 * ip.s1 * ip.s1 * ip.s1 * ip.s1;
1367 ip.u2 = wire(p, WIRE.W_R);
1368 ip.u3 = wire(p, WIRE.W_O);
1369 ip.u4 = wire(p, WIRE.W_4);
1370
1371 // matrix mul with v = M_I * u 4 muls and 7 additions
1372 ip.u_sum = ip.u1 + ip.u2 + ip.u3 + ip.u4;
1373
1374 ip.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_INTERNAL) * domainSep;
1375
1376 ip.v1 = ip.u1 * INTERNAL_MATRIX_DIAGONAL[0] + ip.u_sum;
1377 evals[25] = evals[25] + ip.q_pos_by_scaling * (ip.v1 - wire(p, WIRE.W_L_SHIFT));
1378
1379 ip.v2 = ip.u2 * INTERNAL_MATRIX_DIAGONAL[1] + ip.u_sum;
1380 evals[26] = evals[26] + ip.q_pos_by_scaling * (ip.v2 - wire(p, WIRE.W_R_SHIFT));
1381
1382 ip.v3 = ip.u3 * INTERNAL_MATRIX_DIAGONAL[2] + ip.u_sum;
1383 evals[27] = evals[27] + ip.q_pos_by_scaling * (ip.v3 - wire(p, WIRE.W_O_SHIFT));
1384
1385 ip.v4 = ip.u4 * INTERNAL_MATRIX_DIAGONAL[3] + ip.u_sum;
1386 evals[28] = evals[28] + ip.q_pos_by_scaling * (ip.v4 - wire(p, WIRE.W_4_SHIFT));
1387 }
1388
1389 // Batch subrelation evaluations using precomputed powers of alpha
1390 // First subrelation is implicitly scaled by 1, subsequent ones use powers from the subrelationChallenges array
1391 function scaleAndBatchSubrelations(
1392 Fr[NUMBER_OF_SUBRELATIONS] memory evaluations,
1393 Fr[NUMBER_OF_ALPHAS] memory subrelationChallenges
1394 ) internal pure returns (Fr accumulator) {
1395 accumulator = evaluations[0];
1396
1397 for (uint256 i = 1; i < NUMBER_OF_SUBRELATIONS; ++i) {
1398 accumulator = accumulator + evaluations[i] * subrelationChallenges[i - 1];
1399 }
1400 }
1401}
1402
1403library CommitmentSchemeLib {
1404 using FrLib for Fr;
1405
1406 // Avoid stack too deep
1407 struct ShpleminiIntermediates {
1408 Fr unshiftedScalar;
1409 Fr shiftedScalar;
1410 Fr unshiftedScalarNeg;
1411 Fr shiftedScalarNeg;
1412 // Scalar to be multiplied by [1]₁
1413 Fr constantTermAccumulator;
1414 // Accumulator for powers of rho
1415 Fr batchingChallenge;
1416 // Linear combination of multilinear (sumcheck) evaluations and powers of rho
1417 Fr batchedEvaluation;
1418 Fr[4] denominators;
1419 Fr[4] batchingScalars;
1420 // 1/(z - r^{2^i}) for i = 0, ..., logSize, dynamically updated
1421 Fr posInvertedDenominator;
1422 // 1/(z + r^{2^i}) for i = 0, ..., logSize, dynamically updated
1423 Fr negInvertedDenominator;
1424 // ν^{2i} * 1/(z - r^{2^i})
1425 Fr scalingFactorPos;
1426 // ν^{2i+1} * 1/(z + r^{2^i})
1427 Fr scalingFactorNeg;
1428 // Fold_i(r^{2^i}) reconstructed by Verifier
1429 Fr[] foldPosEvaluations;
1430 }
1431
1432 // Compute the evaluations Aₗ(r^{2ˡ}) for l = 0, ..., m-1
1433 function computeFoldPosEvaluations(
1434 Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckUChallenges,
1435 Fr batchedEvalAccumulator,
1436 Fr[CONST_PROOF_SIZE_LOG_N] memory geminiEvaluations,
1437 Fr[] memory geminiEvalChallengePowers,
1438 uint256 logSize
1439 ) internal view returns (Fr[] memory) {
1440 Fr[] memory foldPosEvaluations = new Fr[](logSize);
1441 for (uint256 i = logSize; i > 0; --i) {
1442 Fr challengePower = geminiEvalChallengePowers[i - 1];
1443 Fr u = sumcheckUChallenges[i - 1];
1444
1445 Fr batchedEvalRoundAcc = ((challengePower * batchedEvalAccumulator * Fr.wrap(2)) - geminiEvaluations[i - 1]
1446 * (challengePower * (ONE - u) - u));
1447 // Divide by the denominator
1448 batchedEvalRoundAcc = batchedEvalRoundAcc * (challengePower * (ONE - u) + u).invert();
1449
1450 batchedEvalAccumulator = batchedEvalRoundAcc;
1451 foldPosEvaluations[i - 1] = batchedEvalRoundAcc;
1452 }
1453 return foldPosEvaluations;
1454 }
1455
1456 function computeSquares(Fr r, uint256 logN) internal pure returns (Fr[] memory) {
1457 Fr[] memory squares = new Fr[](logN);
1458 squares[0] = r;
1459 for (uint256 i = 1; i < logN; ++i) {
1460 squares[i] = squares[i - 1].sqr();
1461 }
1462 return squares;
1463 }
1464}
1465
1466uint256 constant Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; // EC group order. F_q
1467
1468// Fr utility
1469
1470function bytesToFr(bytes calldata proofSection) pure returns (Fr scalar) {
1471 scalar = FrLib.fromBytes32(bytes32(proofSection));
1472}
1473
1474// EC Point utilities
1475function bytesToG1Point(bytes calldata proofSection) pure returns (Honk.G1Point memory point) {
1476 uint256 x = uint256(bytes32(proofSection[0x00:0x20]));
1477 uint256 y = uint256(bytes32(proofSection[0x20:0x40]));
1478 require(x < Q && y < Q, Errors.ValueGeGroupOrder());
1479
1480 // (0,0) is the canonical EIP-196 encoding of the identity. It is accepted here
1481 // because polynomial commitments to identically-zero polynomials (e.g. unused
1482 // selector or table polys) are legitimately the identity. On-curve validation
1483 // (y² = x³ + 3) is handled by the ecAdd/ecMul precompiles per EIP-196.
1484 point = Honk.G1Point({x: x, y: y});
1485}
1486
1487function negateInplace(Honk.G1Point memory point) pure returns (Honk.G1Point memory) {
1488 // When y == 0 (order-2 point), negation is the same point. Q - 0 = Q which is >= Q.
1489 if (point.y != 0) {
1490 point.y = Q - point.y;
1491 }
1492 return point;
1493}
1494
1508function convertPairingPointsToG1(Fr[PAIRING_POINTS_SIZE] memory pairingPoints)
1509 pure
1510 returns (Honk.G1Point memory lhs, Honk.G1Point memory rhs)
1511{
1512 // P0 (lhs): x = lo | (hi << 136)
1513 uint256 lhsX = Fr.unwrap(pairingPoints[0]);
1514 lhsX |= Fr.unwrap(pairingPoints[1]) << 136;
1515
1516 uint256 lhsY = Fr.unwrap(pairingPoints[2]);
1517 lhsY |= Fr.unwrap(pairingPoints[3]) << 136;
1518
1519 // P1 (rhs): x = lo | (hi << 136)
1520 uint256 rhsX = Fr.unwrap(pairingPoints[4]);
1521 rhsX |= Fr.unwrap(pairingPoints[5]) << 136;
1522
1523 uint256 rhsY = Fr.unwrap(pairingPoints[6]);
1524 rhsY |= Fr.unwrap(pairingPoints[7]) << 136;
1525
1526 // Reconstructed coordinates must be < Q to prevent malleability.
1527 // Without this, two different limb encodings could map to the same curve point
1528 // (via mulmod reduction in on-curve checks) but produce different transcript hashes.
1529 require(lhsX < Q && lhsY < Q && rhsX < Q && rhsY < Q, Errors.ValueGeGroupOrder());
1530
1531 lhs.x = lhsX;
1532 lhs.y = lhsY;
1533 rhs.x = rhsX;
1534 rhs.y = rhsY;
1535}
1536
1545function generateRecursionSeparator(
1546 Fr[PAIRING_POINTS_SIZE] memory proofPairingPoints,
1547 Honk.G1Point memory accLhs,
1548 Honk.G1Point memory accRhs
1549) pure returns (Fr recursionSeparator) {
1550 // hash the proof aggregated X
1551 // hash the proof aggregated Y
1552 // hash the accum X
1553 // hash the accum Y
1554
1555 (Honk.G1Point memory proofLhs, Honk.G1Point memory proofRhs) = convertPairingPointsToG1(proofPairingPoints);
1556
1557 uint256[8] memory recursionSeparatorElements;
1558
1559 // Proof points
1560 recursionSeparatorElements[0] = proofLhs.x;
1561 recursionSeparatorElements[1] = proofLhs.y;
1562 recursionSeparatorElements[2] = proofRhs.x;
1563 recursionSeparatorElements[3] = proofRhs.y;
1564
1565 // Accumulator points
1566 recursionSeparatorElements[4] = accLhs.x;
1567 recursionSeparatorElements[5] = accLhs.y;
1568 recursionSeparatorElements[6] = accRhs.x;
1569 recursionSeparatorElements[7] = accRhs.y;
1570
1571 recursionSeparator = FrLib.from(uint256(keccak256(abi.encodePacked(recursionSeparatorElements))) % P);
1572}
1573
1583function mulWithSeperator(Honk.G1Point memory basePoint, Honk.G1Point memory other, Fr recursionSeperator)
1584 view
1585 returns (Honk.G1Point memory)
1586{
1587 Honk.G1Point memory result;
1588
1589 result = ecMul(recursionSeperator, basePoint);
1590 result = ecAdd(result, other);
1591
1592 return result;
1593}
1594
1603function ecMul(Fr value, Honk.G1Point memory point) view returns (Honk.G1Point memory) {
1604 Honk.G1Point memory result;
1605
1606 assembly {
1607 let free := mload(0x40)
1608 // Write the point into memory (two 32 byte words)
1609 // Memory layout:
1610 // Address | value
1611 // free | point.x
1612 // free + 0x20| point.y
1613 mstore(free, mload(point))
1614 mstore(add(free, 0x20), mload(add(point, 0x20)))
1615 // Write the scalar into memory (one 32 byte word)
1616 // Memory layout:
1617 // Address | value
1618 // free + 0x40| value
1619 mstore(add(free, 0x40), value)
1620
1621 // Call the ecMul precompile, it takes in the following
1622 // [point.x, point.y, scalar], and returns the result back into the free memory location.
1623 let success := staticcall(gas(), 0x07, free, 0x60, free, 0x40)
1624 if iszero(success) {
1625 revert(0, 0)
1626 }
1627 // Copy the result of the multiplication back into the result memory location.
1628 // Memory layout:
1629 // Address | value
1630 // result | result.x
1631 // result + 0x20| result.y
1632 mstore(result, mload(free))
1633 mstore(add(result, 0x20), mload(add(free, 0x20)))
1634
1635 mstore(0x40, add(free, 0x60))
1636 }
1637
1638 return result;
1639}
1640
1649function ecAdd(Honk.G1Point memory lhs, Honk.G1Point memory rhs) view returns (Honk.G1Point memory) {
1650 Honk.G1Point memory result;
1651
1652 assembly {
1653 let free := mload(0x40)
1654 // Write lhs into memory (two 32 byte words)
1655 // Memory layout:
1656 // Address | value
1657 // free | lhs.x
1658 // free + 0x20| lhs.y
1659 mstore(free, mload(lhs))
1660 mstore(add(free, 0x20), mload(add(lhs, 0x20)))
1661
1662 // Write rhs into memory (two 32 byte words)
1663 // Memory layout:
1664 // Address | value
1665 // free + 0x40| rhs.x
1666 // free + 0x60| rhs.y
1667 mstore(add(free, 0x40), mload(rhs))
1668 mstore(add(free, 0x60), mload(add(rhs, 0x20)))
1669
1670 // Call the ecAdd precompile, it takes in the following
1671 // [lhs.x, lhs.y, rhs.x, rhs.y], and returns their addition back into the free memory location.
1672 let success := staticcall(gas(), 0x06, free, 0x80, free, 0x40)
1673 if iszero(success) { revert(0, 0) }
1674
1675 // Copy the result of the addition back into the result memory location.
1676 // Memory layout:
1677 // Address | value
1678 // result | result.x
1679 // result + 0x20| result.y
1680 mstore(result, mload(free))
1681 mstore(add(result, 0x20), mload(add(free, 0x20)))
1682
1683 mstore(0x40, add(free, 0x80))
1684 }
1685
1686 return result;
1687}
1688
1689function rejectPointAtInfinity(Honk.G1Point memory point) pure {
1690 require((point.x | point.y) != 0, Errors.PointAtInfinity());
1691}
1692
1697function arePairingPointsDefault(Fr[PAIRING_POINTS_SIZE] memory pairingPoints) pure returns (bool) {
1698 uint256 acc = 0;
1699 for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) {
1700 acc |= Fr.unwrap(pairingPoints[i]);
1701 }
1702 return acc == 0;
1703}
1704
1705function pairing(Honk.G1Point memory rhs, Honk.G1Point memory lhs) view returns (bool decodedResult) {
1706 bytes memory input = abi.encodePacked(
1707 rhs.x,
1708 rhs.y,
1709 // Fixed G2 point
1710 uint256(0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2),
1711 uint256(0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed),
1712 uint256(0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b),
1713 uint256(0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa),
1714 lhs.x,
1715 lhs.y,
1716 // G2 point from VK
1717 uint256(0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1),
1718 uint256(0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0),
1719 uint256(0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4),
1720 uint256(0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55)
1721 );
1722
1723 (bool success, bytes memory result) = address(0x08).staticcall(input);
1724 decodedResult = success && abi.decode(result, (bool));
1725}
1726
1727abstract contract BaseHonkVerifier is IVerifier {
1728 using FrLib for Fr;
1729
1730 // Constants for proof length calculation (matching UltraKeccakFlavor)
1731 uint256 internal constant NUM_WITNESS_ENTITIES = 8;
1732 uint256 internal constant NUM_ELEMENTS_COMM = 2; // uint256 elements for curve points
1733 uint256 internal constant NUM_ELEMENTS_FR = 1; // uint256 elements for field elements
1734
1735 // Number of field elements in a ultra keccak honk proof for log_n = 25, including pairing point object.
1736 uint256 internal constant PROOF_SIZE = 350; // Legacy constant - will be replaced by calculateProofSize($LOG_N)
1737 uint256 internal constant SHIFTED_COMMITMENTS_START = 29;
1738
1739 uint256 internal constant PERMUTATION_ARGUMENT_VALUE_SEPARATOR = 1 << 28;
1740
1741 uint256 internal immutable $N;
1742 uint256 internal immutable $LOG_N;
1743 uint256 internal immutable $VK_HASH;
1744 uint256 internal immutable $NUM_PUBLIC_INPUTS;
1745
1746 constructor(uint256 _N, uint256 _logN, uint256 _vkHash, uint256 _numPublicInputs) {
1747 $N = _N;
1748 $LOG_N = _logN;
1749 $VK_HASH = _vkHash;
1750 $NUM_PUBLIC_INPUTS = _numPublicInputs;
1751 }
1752
1753 function verify(bytes calldata proof, bytes32[] calldata publicInputs) public view override returns (bool) {
1754 // Calculate expected proof size based on $LOG_N
1755 uint256 expectedProofSize = calculateProofSize($LOG_N);
1756
1757 // Check the received proof is the expected size where each field element is 32 bytes
1758 if (proof.length != expectedProofSize * 32) {
1759 revert Errors.ProofLengthWrongWithLogN($LOG_N, proof.length, expectedProofSize * 32);
1760 }
1761
1762 Honk.VerificationKey memory vk = loadVerificationKey();
1763 Honk.Proof memory p = TranscriptLib.loadProof(proof, $LOG_N);
1764 if (publicInputs.length != vk.publicInputsSize - PAIRING_POINTS_SIZE) {
1765 revert Errors.PublicInputsLengthWrong();
1766 }
1767
1768 // Generate the fiat shamir challenges for the whole protocol
1769 Transcript memory t = TranscriptLib.generateTranscript(p, publicInputs, $VK_HASH, $NUM_PUBLIC_INPUTS, $LOG_N);
1770
1771 // Derive public input delta
1772 t.relationParameters.publicInputsDelta = computePublicInputDelta(
1773 publicInputs,
1774 p.pairingPointObject,
1775 t.relationParameters.beta,
1776 t.relationParameters.gamma,
1777 5 // pubInputsOffset = NUM_DISABLED_ROWS_IN_SUMCHECK + NUM_ZERO_ROWS = 4 + 1
1778 );
1779
1780 // Sumcheck
1781 bool sumcheckVerified = verifySumcheck(p, t);
1782 if (!sumcheckVerified) revert Errors.SumcheckFailed();
1783
1784 bool shpleminiVerified = verifyShplemini(p, vk, t);
1785 if (!shpleminiVerified) revert Errors.ShpleminiFailed();
1786
1787 return sumcheckVerified && shpleminiVerified; // Boolean condition not required - nice for vanity :)
1788 }
1789
1790 function computePublicInputDelta(
1791 bytes32[] memory publicInputs,
1792 Fr[PAIRING_POINTS_SIZE] memory pairingPointObject,
1793 Fr beta,
1794 Fr gamma,
1795 uint256 offset
1796 ) internal view returns (Fr publicInputDelta) {
1797 Fr numerator = ONE;
1798 Fr denominator = ONE;
1799
1800 Fr numeratorAcc = gamma + (beta * FrLib.from(PERMUTATION_ARGUMENT_VALUE_SEPARATOR + offset));
1801 Fr denominatorAcc = gamma - (beta * FrLib.from(offset + 1));
1802
1803 {
1804 for (uint256 i = 0; i < $NUM_PUBLIC_INPUTS - PAIRING_POINTS_SIZE; i++) {
1805 Fr pubInput = FrLib.fromBytes32(publicInputs[i]);
1806
1807 numerator = numerator * (numeratorAcc + pubInput);
1808 denominator = denominator * (denominatorAcc + pubInput);
1809
1810 numeratorAcc = numeratorAcc + beta;
1811 denominatorAcc = denominatorAcc - beta;
1812 }
1813
1814 for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) {
1815 Fr pubInput = pairingPointObject[i];
1816
1817 numerator = numerator * (numeratorAcc + pubInput);
1818 denominator = denominator * (denominatorAcc + pubInput);
1819
1820 numeratorAcc = numeratorAcc + beta;
1821 denominatorAcc = denominatorAcc - beta;
1822 }
1823 }
1824
1825 publicInputDelta = FrLib.div(numerator, denominator);
1826 }
1827
1828 function verifySumcheck(Honk.Proof memory proof, Transcript memory tp) internal view returns (bool verified) {
1829 Fr roundTarget;
1830 Fr powPartialEvaluation = ONE;
1831
1832 // We perform sumcheck reductions over log n rounds ( the multivariate degree )
1833 for (uint256 round = 0; round < $LOG_N; ++round) {
1834 Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariate = proof.sumcheckUnivariates[round];
1835 bool valid = checkSum(roundUnivariate, roundTarget);
1836 if (!valid) revert Errors.SumcheckFailed();
1837
1838 Fr roundChallenge = tp.sumCheckUChallenges[round];
1839
1840 // Update the round target for the next rounf
1841 roundTarget = computeNextTargetSum(roundUnivariate, roundChallenge);
1842 powPartialEvaluation = partiallyEvaluatePOW(tp.gateChallenges[round], powPartialEvaluation, roundChallenge);
1843 }
1844
1845 // Last round
1846 Fr grandHonkRelationSum = RelationsLib.accumulateRelationEvaluations(
1847 proof.sumcheckEvaluations, tp.relationParameters, tp.alphas, powPartialEvaluation
1848 );
1849 verified = (grandHonkRelationSum == roundTarget);
1850 }
1851
1852 // Return the new target sum for the next sumcheck round
1853 function computeNextTargetSum(Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariates, Fr roundChallenge)
1854 internal
1855 view
1856 returns (Fr targetSum)
1857 {
1858 Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory BARYCENTRIC_LAGRANGE_DENOMINATORS = [
1859 Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51),
1860 Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000002d0),
1861 Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffff11),
1862 Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000000090),
1863 Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffff71),
1864 Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000000f0),
1865 Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31),
1866 Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000013b0)
1867 ];
1868 // To compute the next target sum, we evaluate the given univariate at a point u (challenge).
1869
1870 // Performing Barycentric evaluations
1871 // Compute B(x)
1872 Fr numeratorValue = ONE;
1873 for (uint256 i = 0; i < BATCHED_RELATION_PARTIAL_LENGTH; ++i) {
1874 numeratorValue = numeratorValue * (roundChallenge - Fr.wrap(i));
1875 }
1876
1877 Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory denominatorInverses;
1878 for (uint256 i = 0; i < BATCHED_RELATION_PARTIAL_LENGTH; ++i) {
1879 Fr inv = BARYCENTRIC_LAGRANGE_DENOMINATORS[i];
1880 inv = inv * (roundChallenge - Fr.wrap(i));
1881 inv = FrLib.invert(inv);
1882 denominatorInverses[i] = inv;
1883 }
1884
1885 for (uint256 i = 0; i < BATCHED_RELATION_PARTIAL_LENGTH; ++i) {
1886 Fr term = roundUnivariates[i];
1887 term = term * denominatorInverses[i];
1888 targetSum = targetSum + term;
1889 }
1890
1891 // Scale the sum by the value of B(x)
1892 targetSum = targetSum * numeratorValue;
1893 }
1894
1895 function verifyShplemini(Honk.Proof memory proof, Honk.VerificationKey memory vk, Transcript memory tp)
1896 internal
1897 view
1898 returns (bool verified)
1899 {
1900 CommitmentSchemeLib.ShpleminiIntermediates memory mem; // stack
1901
1902 // - Compute vector (r, r², ... , r²⁽ⁿ⁻¹⁾), where n = log_circuit_size
1903 Fr[] memory powers_of_evaluation_challenge = CommitmentSchemeLib.computeSquares(tp.geminiR, $LOG_N);
1904
1905 // Arrays hold values that will be linearly combined for the gemini and shplonk batch openings
1906 Fr[] memory scalars = new Fr[](NUMBER_UNSHIFTED + $LOG_N + 2);
1907 Honk.G1Point[] memory commitments = new Honk.G1Point[](NUMBER_UNSHIFTED + $LOG_N + 2);
1908
1909 mem.posInvertedDenominator = (tp.shplonkZ - powers_of_evaluation_challenge[0]).invert();
1910 mem.negInvertedDenominator = (tp.shplonkZ + powers_of_evaluation_challenge[0]).invert();
1911
1912 mem.unshiftedScalar = mem.posInvertedDenominator + (tp.shplonkNu * mem.negInvertedDenominator);
1913 mem.shiftedScalar =
1914 tp.geminiR.invert() * (mem.posInvertedDenominator - (tp.shplonkNu * mem.negInvertedDenominator));
1915
1916 scalars[0] = ONE;
1917 commitments[0] = proof.shplonkQ;
1918
1919 /* Batch multivariate opening claims, shifted and unshifted
1920 * The vector of scalars is populated as follows:
1921 * \f[
1922 * \left(
1923 * - \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right),
1924 * \ldots,
1925 * - \rho^{i+k-1} \times \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right),
1926 * - \rho^{i+k} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right),
1927 * \ldots,
1928 * - \rho^{k+m-1} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right)
1929 * \right)
1930 * \f]
1931 *
1932 * The following vector is concatenated to the vector of commitments:
1933 * \f[
1934 * f_0, \ldots, f_{m-1}, f_{\text{shift}, 0}, \ldots, f_{\text{shift}, k-1}
1935 * \f]
1936 *
1937 * Simultaneously, the evaluation of the multilinear polynomial
1938 * \f[
1939 * \sum \rho^i \cdot f_i + \sum \rho^{i+k} \cdot f_{\text{shift}, i}
1940 * \f]
1941 * at the challenge point \f$ (u_0,\ldots, u_{n-1}) \f$ is computed.
1942 *
1943 * This approach minimizes the number of iterations over the commitments to multilinear polynomials
1944 * and eliminates the need to store the powers of \f$ \rho \f$.
1945 */
1946 mem.batchingChallenge = ONE;
1947 mem.batchedEvaluation = ZERO;
1948
1949 mem.unshiftedScalarNeg = mem.unshiftedScalar.neg();
1950 mem.shiftedScalarNeg = mem.shiftedScalar.neg();
1951 for (uint256 i = 1; i <= NUMBER_UNSHIFTED; ++i) {
1952 scalars[i] = mem.unshiftedScalarNeg * mem.batchingChallenge;
1953 mem.batchedEvaluation = mem.batchedEvaluation + (proof.sumcheckEvaluations[i - 1] * mem.batchingChallenge);
1954 mem.batchingChallenge = mem.batchingChallenge * tp.rho;
1955 }
1956 // g commitments are accumulated at r
1957 // For each of the to be shifted commitments perform the shift in place by
1958 // adding to the unshifted value.
1959 // We do so, as the values are to be used in batchMul later, and as
1960 // `a * c + b * c = (a + b) * c` this will allow us to reduce memory and compute.
1961 // Applied to w1, w2, w3, w4 and zPerm
1962 for (uint256 i = 0; i < NUMBER_TO_BE_SHIFTED; ++i) {
1963 uint256 scalarOff = i + SHIFTED_COMMITMENTS_START;
1964 uint256 evaluationOff = i + NUMBER_UNSHIFTED;
1965
1966 scalars[scalarOff] = scalars[scalarOff] + (mem.shiftedScalarNeg * mem.batchingChallenge);
1967 mem.batchedEvaluation =
1968 mem.batchedEvaluation + (proof.sumcheckEvaluations[evaluationOff] * mem.batchingChallenge);
1969 mem.batchingChallenge = mem.batchingChallenge * tp.rho;
1970 }
1971
1972 commitments[1] = vk.qm;
1973 commitments[2] = vk.qc;
1974 commitments[3] = vk.ql;
1975 commitments[4] = vk.qr;
1976 commitments[5] = vk.qo;
1977 commitments[6] = vk.q4;
1978 commitments[7] = vk.qLookup;
1979 commitments[8] = vk.qArith;
1980 commitments[9] = vk.qDeltaRange;
1981 commitments[10] = vk.qElliptic;
1982 commitments[11] = vk.qMemory;
1983 commitments[12] = vk.qNnf;
1984 commitments[13] = vk.qPoseidon2External;
1985 commitments[14] = vk.qPoseidon2Internal;
1986 commitments[15] = vk.s1;
1987 commitments[16] = vk.s2;
1988 commitments[17] = vk.s3;
1989 commitments[18] = vk.s4;
1990 commitments[19] = vk.id1;
1991 commitments[20] = vk.id2;
1992 commitments[21] = vk.id3;
1993 commitments[22] = vk.id4;
1994 commitments[23] = vk.t1;
1995 commitments[24] = vk.t2;
1996 commitments[25] = vk.t3;
1997 commitments[26] = vk.t4;
1998 commitments[27] = vk.lagrangeFirst;
1999 commitments[28] = vk.lagrangeLast;
2000
2001 // Accumulate proof points
2002 commitments[29] = proof.w1;
2003 commitments[30] = proof.w2;
2004 commitments[31] = proof.w3;
2005 commitments[32] = proof.w4;
2006 commitments[33] = proof.zPerm;
2007 commitments[34] = proof.lookupInverses;
2008 commitments[35] = proof.lookupReadCounts;
2009 commitments[36] = proof.lookupReadTags;
2010
2011 /* Batch gemini claims from the prover
2012 * place the commitments to gemini aᵢ to the vector of commitments, compute the contributions from
2013 * aᵢ(−r²ⁱ) for i=1, … , n−1 to the constant term accumulator, add corresponding scalars
2014 *
2015 * 1. Moves the vector
2016 * \f[
2017 * \left( \text{com}(A_1), \text{com}(A_2), \ldots, \text{com}(A_{n-1}) \right)
2018 * \f]
2019 * to the 'commitments' vector.
2020 *
2021 * 2. Computes the scalars:
2022 * \f[
2023 * \frac{\nu^{2}}{z + r^2}, \frac{\nu^3}{z + r^4}, \ldots, \frac{\nu^{n-1}}{z + r^{2^{n-1}}}
2024 * \f]
2025 * and places them into the 'scalars' vector.
2026 *
2027 * 3. Accumulates the summands of the constant term:
2028 * \f[
2029 * \sum_{i=2}^{n-1} \frac{\nu^{i} \cdot A_i(-r^{2^i})}{z + r^{2^i}}
2030 * \f]
2031 * and adds them to the 'constant_term_accumulator'.
2032 */
2033
2034 // Compute the evaluations Aₗ(r^{2ˡ}) for l = 0, ..., $LOG_N - 1
2035 Fr[] memory foldPosEvaluations = CommitmentSchemeLib.computeFoldPosEvaluations(
2036 tp.sumCheckUChallenges,
2037 mem.batchedEvaluation,
2038 proof.geminiAEvaluations,
2039 powers_of_evaluation_challenge,
2040 $LOG_N
2041 );
2042
2043 // Compute the Shplonk constant term contributions from A₀(±r)
2044 mem.constantTermAccumulator = foldPosEvaluations[0] * mem.posInvertedDenominator;
2045 mem.constantTermAccumulator =
2046 mem.constantTermAccumulator + (proof.geminiAEvaluations[0] * tp.shplonkNu * mem.negInvertedDenominator);
2047
2048 mem.batchingChallenge = tp.shplonkNu.sqr();
2049
2050 // Compute Shplonk constant term contributions from Aₗ(± r^{2ˡ}) for l = 1, ..., m-1;
2051 // Compute scalar multipliers for each fold commitment
2052 for (uint256 i = 0; i < $LOG_N - 1; ++i) {
2053 // Update inverted denominators
2054 mem.posInvertedDenominator = (tp.shplonkZ - powers_of_evaluation_challenge[i + 1]).invert();
2055 mem.negInvertedDenominator = (tp.shplonkZ + powers_of_evaluation_challenge[i + 1]).invert();
2056
2057 // Compute the scalar multipliers for Aₗ(± r^{2ˡ}) and [Aₗ]
2058 mem.scalingFactorPos = mem.batchingChallenge * mem.posInvertedDenominator;
2059 mem.scalingFactorNeg = mem.batchingChallenge * tp.shplonkNu * mem.negInvertedDenominator;
2060 // [Aₗ] is multiplied by -v^{2l}/(z-r^{2^l}) - v^{2l+1} /(z+ r^{2^l})
2061 scalars[NUMBER_UNSHIFTED + 1 + i] = mem.scalingFactorNeg.neg() + mem.scalingFactorPos.neg();
2062
2063 // Accumulate the const term contribution given by
2064 // v^{2l} * Aₗ(r^{2ˡ}) /(z-r^{2^l}) + v^{2l+1} * Aₗ(-r^{2ˡ}) /(z+ r^{2^l})
2065 Fr accumContribution = mem.scalingFactorNeg * proof.geminiAEvaluations[i + 1];
2066
2067 accumContribution = accumContribution + mem.scalingFactorPos * foldPosEvaluations[i + 1];
2068 mem.constantTermAccumulator = mem.constantTermAccumulator + accumContribution;
2069 // Update the running power of v
2070 mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu * tp.shplonkNu;
2071
2072 commitments[NUMBER_UNSHIFTED + 1 + i] = proof.geminiFoldComms[i];
2073 }
2074
2075 // Finalize the batch opening claim
2076 commitments[NUMBER_UNSHIFTED + $LOG_N] = Honk.G1Point({x: 1, y: 2});
2077 scalars[NUMBER_UNSHIFTED + $LOG_N] = mem.constantTermAccumulator;
2078
2079 Honk.G1Point memory quotient_commitment = proof.kzgQuotient;
2080
2081 commitments[NUMBER_UNSHIFTED + $LOG_N + 1] = quotient_commitment;
2082 scalars[NUMBER_UNSHIFTED + $LOG_N + 1] = tp.shplonkZ; // evaluation challenge
2083
2084 Honk.G1Point memory P_0_agg = batchMul(commitments, scalars);
2085 Honk.G1Point memory P_1_agg = negateInplace(quotient_commitment);
2086
2087 // Aggregate pairing points (skip if default/infinity — no recursive verification occurred)
2088 if (!arePairingPointsDefault(proof.pairingPointObject)) {
2089 Fr recursionSeparator = generateRecursionSeparator(proof.pairingPointObject, P_0_agg, P_1_agg);
2090 (Honk.G1Point memory P_0_other, Honk.G1Point memory P_1_other) =
2091 convertPairingPointsToG1(proof.pairingPointObject);
2092
2093 // Validate the points from the proof are on the curve
2094 rejectPointAtInfinity(P_0_other);
2095 rejectPointAtInfinity(P_1_other);
2096
2097 // accumulate with aggregate points in proof
2098 P_0_agg = mulWithSeperator(P_0_agg, P_0_other, recursionSeparator);
2099 P_1_agg = mulWithSeperator(P_1_agg, P_1_other, recursionSeparator);
2100 }
2101
2102 return pairing(P_0_agg, P_1_agg);
2103 }
2104
2105 function batchMul(Honk.G1Point[] memory base, Fr[] memory scalars)
2106 internal
2107 view
2108 returns (Honk.G1Point memory result)
2109 {
2110 uint256 limit = NUMBER_UNSHIFTED + $LOG_N + 2;
2111
2112 // Identity bases are accepted: VK selector/table polys may be identically zero,
2113 // and the ecAdd/ecMul precompiles treat (0,0) as the additive identity per EIP-196.
2114 // Soundness against an attacker substituting (0,0) for a non-zero commitment is
2115 // upheld by sumcheck/Shplemini, which would fail on inconsistent evaluations.
2116
2117 bool success = true;
2118 assembly {
2119 let free := mload(0x40)
2120
2121 let count := 0x01
2122 for {} lt(count, add(limit, 1)) { count := add(count, 1) } {
2123 // Get loop offsets
2124 let base_base := add(base, mul(count, 0x20))
2125 let scalar_base := add(scalars, mul(count, 0x20))
2126
2127 mstore(add(free, 0x40), mload(mload(base_base)))
2128 mstore(add(free, 0x60), mload(add(0x20, mload(base_base))))
2129 // Add scalar
2130 mstore(add(free, 0x80), mload(scalar_base))
2131
2132 success := and(success, staticcall(gas(), 7, add(free, 0x40), 0x60, add(free, 0x40), 0x40))
2133 // accumulator = accumulator + accumulator_2
2134 success := and(success, staticcall(gas(), 6, free, 0x80, free, 0x40))
2135 }
2136
2137 // Return the result
2138 mstore(result, mload(free))
2139 mstore(add(result, 0x20), mload(add(free, 0x20)))
2140 }
2141
2142 require(success, Errors.ShpleminiFailed());
2143 }
2144
2145 // Calculate proof size based on log_n (matching UltraKeccakFlavor formula)
2146 function calculateProofSize(uint256 logN) internal pure returns (uint256) {
2147 // Witness commitments
2148 uint256 proofLength = NUM_WITNESS_ENTITIES * NUM_ELEMENTS_COMM; // witness commitments
2149
2150 // Sumcheck
2151 proofLength += logN * BATCHED_RELATION_PARTIAL_LENGTH * NUM_ELEMENTS_FR; // sumcheck univariates
2152 proofLength += NUMBER_OF_ENTITIES * NUM_ELEMENTS_FR; // sumcheck evaluations
2153
2154 // Gemini
2155 proofLength += (logN - 1) * NUM_ELEMENTS_COMM; // Gemini Fold commitments
2156 proofLength += logN * NUM_ELEMENTS_FR; // Gemini evaluations
2157
2158 // Shplonk and KZG commitments
2159 proofLength += NUM_ELEMENTS_COMM * 2; // Shplonk Q and KZG W commitments
2160
2161 // Pairing points
2162 proofLength += PAIRING_POINTS_SIZE; // pairing inputs carried on public inputs
2163
2164 return proofLength;
2165 }
2166
2167 function checkSum(Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariate, Fr roundTarget)
2168 internal
2169 pure
2170 returns (bool checked)
2171 {
2172 Fr totalSum = roundUnivariate[0] + roundUnivariate[1];
2173 checked = totalSum == roundTarget;
2174 }
2175
2176 // Univariate evaluation of the monomial ((1-X_l) + X_l.B_l) at the challenge point X_l=u_l
2177 function partiallyEvaluatePOW(Fr gateChallenge, Fr currentEvaluation, Fr roundChallenge)
2178 internal
2179 pure
2180 returns (Fr newEvaluation)
2181 {
2182 Fr univariateEval = ONE + (roundChallenge * (gateChallenge - ONE));
2183 newEvaluation = currentEvaluation * univariateEval;
2184 }
2185
2186 function loadVerificationKey() internal pure virtual returns (Honk.VerificationKey memory);
2187}
2188
2189contract HonkVerifier is BaseHonkVerifier(N, LOG_N, VK_HASH, NUMBER_OF_PUBLIC_INPUTS) {
2190 function loadVerificationKey() internal pure override returns (Honk.VerificationKey memory) {
2191 return HonkVerificationKey.loadVerificationKey();
2192 }
2193}
2194)";
2195
2196inline std::string get_honk_solidity_verifier(auto const& verification_key)
2197{
2198 std::ostringstream stream;
2199 output_vk_sol_ultra_honk(stream, verification_key, "HonkVerificationKey");
2200 return stream.str() + HONK_CONTRACT_SOURCE;
2201}
std::string get_honk_solidity_verifier(auto const &verification_key)
void output_vk_sol_ultra_honk(std::ostream &os, auto const &key, std::string const &class_name, bool include_types_import=false)