@pvium/p2id-core 0.1.0

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