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