@crisp-e3/contracts 0.14.0 → 0.16.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.
@@ -7,125 +7,125 @@ pragma solidity >=0.8.21;
7
7
 
8
8
  uint256 constant N = 2097152;
9
9
  uint256 constant LOG_N = 21;
10
- uint256 constant NUMBER_OF_PUBLIC_INPUTS = 23;
11
- uint256 constant VK_HASH = 0x1363994dff01411729a9198cf1b2539577e31a836b6175370de2ebc9e5fe43cc;
10
+ uint256 constant NUMBER_OF_PUBLIC_INPUTS = 15;
11
+ uint256 constant VK_HASH = 0x0a7642f3693429035e756c034137ba93ed0bc3e7d3f370e24143d7e52a7aabc4;
12
12
  library HonkVerificationKey {
13
13
  function loadVerificationKey() internal pure returns (Honk.VerificationKey memory) {
14
14
  Honk.VerificationKey memory vk = Honk.VerificationKey({
15
15
  circuitSize: uint256(2097152),
16
16
  logCircuitSize: uint256(21),
17
- publicInputsSize: uint256(23),
17
+ publicInputsSize: uint256(15),
18
18
  ql: Honk.G1Point({
19
- x: uint256(0x188b8bd77632a8af920326bfc2d2281a3cf604913221391edf1ac9fe02120ed1),
20
- y: uint256(0x147f80c895be168b9689d122f5bdedb2296de44d941e5378c591d23574445cc4)
19
+ x: uint256(0x14493dc1077b2f351879feecaa600ca60f7ce233869e11f274bb6fc7d31427dd),
20
+ y: uint256(0x03269bd3e0e7fcd095c3a6eaddf8e30e33ca383497034c7e028ddc372df027da)
21
21
  }),
22
22
  qr: Honk.G1Point({
23
- x: uint256(0x1a02c9f1ae6e0b1037b0881654328eec39501f97ae51aaffeb05eb3a2b2de119),
24
- y: uint256(0x29eb45831f58c59b72bf876b4a67f63a98a3bbf96487afec9f7a2c2210972b5b)
23
+ x: uint256(0x1fa18580baf7ded1c161e13ca6a20c5670ef984a3396979faf7e0a656387e527),
24
+ y: uint256(0x2e94ddf45aecdaee626a6eeded33d0feda0b05fe0681f3ee3d6340a4a8c10896)
25
25
  }),
26
26
  qo: Honk.G1Point({
27
- x: uint256(0x26f8a24a0977ba8288b255e99a4529da51a051e128c629c3a748f5de3c5bb0ef),
28
- y: uint256(0x258179ad2b2e943faeae74eed595dd5f8a3849b28af17e01546cccd47619746c)
27
+ x: uint256(0x092b0d4c826cbe5e07c1e19020272e116ff633dc953763c357f5fd841722123e),
28
+ y: uint256(0x2c32eb8ab851dfe95645d6e6c15d5074c4e3d179d58aa0ed636f7dfafabecede)
29
29
  }),
30
30
  q4: Honk.G1Point({
31
- x: uint256(0x29c3b196a166824a0dad36a68b62c2ab74baa3acf00d43599e24149cabd77b3c),
32
- y: uint256(0x08866f6cafd256c09ccdf564dad394dc082ca16221bb3a8b4957437ac54e4d5b)
31
+ x: uint256(0x25306c50d5acbe8b63f31114c2a33abd494ee69eaf0d83dfd5a2a77c6c7694dc),
32
+ y: uint256(0x1a86fb908a0a332216ad4e0651be7f73f720d402f00ce3e8b32e94b89f25087f)
33
33
  }),
34
34
  qm: Honk.G1Point({
35
- x: uint256(0x0b70643590be080324d22dd1d1044b39f979d1b8c4a9994bad2c12fe031fdbd8),
36
- y: uint256(0x094b2f9ef76de679e3696371ad08d24c549f710efc6d635f114e9eff35e2a4cf)
35
+ x: uint256(0x26516743e498c0fd06b97618fb55478fd29807b4d6305ca3bb2d7b506264fd6f),
36
+ y: uint256(0x10bb22b28f6420cea07563e6114b960dd9bdbf203ad39a4bed6b42d014fff8f9)
37
37
  }),
38
38
  qc: Honk.G1Point({
39
- x: uint256(0x0934c5e9b8cffadce0e26c2a8b48ee06072f046e003950fb153d2bfa328c17fd),
40
- y: uint256(0x2a8e90dbce025a51265129b231df4f9f21e120fe106ef2049bf8a50041bfd877)
39
+ x: uint256(0x14e3747585344848708d74e5cac292bd806b04496ce2a1f19b832be2b06a46a0),
40
+ y: uint256(0x24e01d1273374ee4f50c390758c45738b0dee956af2038acbd53b58d4b74779a)
41
41
  }),
42
42
  qLookup: Honk.G1Point({
43
- x: uint256(0x1958b13f38f58756e23989a418de3c113d47a2256ba6f60a9e2ec0523c52a991),
44
- y: uint256(0x286c1d4c53af6654de284fbb620f90a9e00aa9470a9e3c58a56cc81e212df923)
43
+ x: uint256(0x0000000000000000000000000000000000000000000000000000000000000000),
44
+ y: uint256(0x0000000000000000000000000000000000000000000000000000000000000000)
45
45
  }),
46
46
  qArith: Honk.G1Point({
47
- x: uint256(0x1650ab20908fac8b39842f152937e151f60f4b68ca0a64de89b55769a5a388a9),
48
- y: uint256(0x1230774514e3201dd3195bffe89d5ad12c88a817767171ae920aa8c93d54d0b1)
47
+ x: uint256(0x176c5cf9eb8c27900484c632289e86dfbfab9d4bbc5d927e2fe3a79b36479290),
48
+ y: uint256(0x2fc22055c186e3bcd7b55e48688c899b5a5c804e3d7403f520776666dbb3f23e)
49
49
  }),
50
50
  qDeltaRange: Honk.G1Point({
51
- x: uint256(0x20dcdd6ad6159d21d6fd1cf6f70bb9f0fafb3fbd38d6b1590cce5fa678c75963),
52
- y: uint256(0x0576ae3c71f00267025a13bd23d1bd2db2c93254458f93ec097efd27676fd576)
51
+ x: uint256(0x2d0e8c12eebb1f0a1604752470e237658a92f612da2abc65a791cc777a35a341),
52
+ y: uint256(0x23ebdb221e2fc512e6bce94cc3701880ee87e340c16e2fcce619ee8e97949e56)
53
53
  }),
54
54
  qElliptic: Honk.G1Point({
55
- x: uint256(0x02c698a3d957cdc72eddea30de02c84ad9e7cc3455e39f1fec996a5618f72ec0),
56
- y: uint256(0x05c14a080f71c3ae6c538965a8707377b4cdea9213ef8679e17d53f09380c50c)
55
+ x: uint256(0x0000000000000000000000000000000000000000000000000000000000000000),
56
+ y: uint256(0x0000000000000000000000000000000000000000000000000000000000000000)
57
57
  }),
58
58
  qMemory: Honk.G1Point({
59
- x: uint256(0x1bc97809165931d0d7a0c80337a440c83466747f8f08e9765cc255168171063d),
60
- y: uint256(0x2c5715175aee9ce87f01b047f4e012b5b3ad76f7caa7a71d20b6e2d532e5b03f)
59
+ x: uint256(0x0f8820a8b43ed7f9a6e0aa355bf3ef04dc47c88a221a6c41a4f253e50783fab4),
60
+ y: uint256(0x2de4b7c386bedbf5694b899eb29dd3c1be6e7691ecc10d4074966cfe8efb0421)
61
61
  }),
62
62
  qNnf: Honk.G1Point({
63
- x: uint256(0x02cf02022c04bf434cc76bc44ef892fc94199c36e6010a0ff8bbdc570811c782),
64
- y: uint256(0x284e2925bfe6946ab3fc2ffcd8c4cc0a34e819a0e48804fb54bea83fd6f9b347)
63
+ x: uint256(0x19a8398c7b609f27c334c656127ebdc34db4982bb1f1bbec186873c113d2f0b1),
64
+ y: uint256(0x29c36f8a702f73fec6a32d581fdc17e59c4999625d7c9147e7eac48df66b1740)
65
65
  }),
66
66
  qPoseidon2External: Honk.G1Point({
67
- x: uint256(0x0970a7dbc60b224774948c22f5fb332a898c1ee3d58d0f43fb93264c83fa286c),
68
- y: uint256(0x2ae28ac80d915e2de014aae43275eaea6664633e567b9d0a9adc62df1a4d7d44)
67
+ x: uint256(0x00fd3df42aead1d3fe2aedac899b5a5a1d33072137bbf9cd98933a7f939ca4af),
68
+ y: uint256(0x0daa73db37e213845ad80630816c01724f21e0151759b18358a51651bbc5ef49)
69
69
  }),
70
70
  qPoseidon2Internal: Honk.G1Point({
71
- x: uint256(0x00fc2a23fcea7461ae9f30e40daa1e443797f672b3af8cc3d86160cbed2d6f38),
72
- y: uint256(0x0e5989283671addf0304e8efe910bfee62423958fd9f234a08c2afaf47085921)
71
+ x: uint256(0x0d04ebec0a4f4c5343b72c528f4de039629eab96223847720e5fb619b1a8f10d),
72
+ y: uint256(0x128f4178942625efadd7fe3adbaeb337fd961488236802d50ece4a7552e96370)
73
73
  }),
74
74
  s1: Honk.G1Point({
75
- x: uint256(0x116d95d8fa29df8b919b63e4e2bb8dc691b038a32397310dda7f19c23aebdf4f),
76
- y: uint256(0x10ac1b5d343fb30c2014703c77250f91a65501fc3b632308e796734f94eac535)
75
+ x: uint256(0x0774647b2dca67d5649beb99b794a2eb00df29cf5f41a651f1dbd4ef6e9efbf7),
76
+ y: uint256(0x2f0f9a00cfdd4915a84eeecae14991cf8aed2f0efc17685988b2c5513a868150)
77
77
  }),
78
78
  s2: Honk.G1Point({
79
- x: uint256(0x0ba3c1078be00a82bcba91e57ab491d78e5929b8121a39f16bd6e8b05816264f),
80
- y: uint256(0x11414a2d53f19eeff566eb1b12ed03595e46da88b56b0d5cd0951de93e21be81)
79
+ x: uint256(0x07fb7e3aefe3a10bf887114d2d23a0e6e625695d1edc4507aaa362cf13cbe95b),
80
+ y: uint256(0x27c70c4226f232a5649e44053b2b71db25553c54f57ade536e7a60a7e4321836)
81
81
  }),
82
82
  s3: Honk.G1Point({
83
- x: uint256(0x10f8060e4e4cab02ac9fa8c12d49a29b386fbc0061817b52cd5dfbc7675abac9),
84
- y: uint256(0x0c0b74f94500a395c0f8f078446403b3387ee9174cb76093eb8b902ae1d38d10)
83
+ x: uint256(0x28cd82d09235cab51c5909d625fd077e6b1c19e3f9d74963ebd6be11d165c8fa),
84
+ y: uint256(0x1f13e6a87683c7105f1ad021f09f02db97654f78c52d549b8b927794f5fdde58)
85
85
  }),
86
86
  s4: Honk.G1Point({
87
- x: uint256(0x1bb341a50347d9b07a53327a51b51f1650e84a9440b64f2abe6a1b2efdbded26),
88
- y: uint256(0x2d156d8cbf901c205ca4bfd997172e94392f9d2683313f3bdea9e1c1cabb1800)
87
+ x: uint256(0x2706b7dbb9755b086936581aa3d9c43e6e24f1dc44e8d6268d1ae9e80b52015e),
88
+ y: uint256(0x237ab989a520291d485957047de8e083cfd74afb501593c9429c9d40b5b8a070)
89
89
  }),
90
90
  t1: Honk.G1Point({
91
- x: uint256(0x099e3bd5a0a00ab7fe18040105b9b395b5d8b7b4a63b05df652b0d10ef146d26),
92
- y: uint256(0x0015b8d2515d76e2ccec99dcd194592129af3a637f5a622a32440f860d1e2a7f)
91
+ x: uint256(0x0000000000000000000000000000000000000000000000000000000000000000),
92
+ y: uint256(0x0000000000000000000000000000000000000000000000000000000000000000)
93
93
  }),
94
94
  t2: Honk.G1Point({
95
- x: uint256(0x1b917517920bad3d8bc01c9595092a222b888108dc25d1aa450e0b4bc212c37e),
96
- y: uint256(0x305e8992b148eedb22e6e992077a84482141c7ebe42000a1d58ccb74381f6d19)
95
+ x: uint256(0x0000000000000000000000000000000000000000000000000000000000000000),
96
+ y: uint256(0x0000000000000000000000000000000000000000000000000000000000000000)
97
97
  }),
98
98
  t3: Honk.G1Point({
99
- x: uint256(0x061f64497996e8915722501e9e367938ed8da2375186b518c7345c60b1134b2d),
100
- y: uint256(0x1b84d38339321f405ebaf6a2f830842ad3d7cb59792e11c0d2691f317fd50e6e)
99
+ x: uint256(0x0000000000000000000000000000000000000000000000000000000000000000),
100
+ y: uint256(0x0000000000000000000000000000000000000000000000000000000000000000)
101
101
  }),
102
102
  t4: Honk.G1Point({
103
- x: uint256(0x043d063b130adfb37342af45d0155a28edd1a7e46c840d9c943fdf45521c64ce),
104
- y: uint256(0x261522c4089330646aff96736194949330952ae74c573d1686d9cb4a00733854)
103
+ x: uint256(0x0000000000000000000000000000000000000000000000000000000000000000),
104
+ y: uint256(0x0000000000000000000000000000000000000000000000000000000000000000)
105
105
  }),
106
106
  id1: Honk.G1Point({
107
- x: uint256(0x09f2edefab63ac2d0924ec06a1cb75a8203a4cb5d5f7e6fb1344188435b8871d),
108
- y: uint256(0x28b7143a9786fa4ed00739bc9bb1cbbc06cc4eaed07228964927b44739152e36)
107
+ x: uint256(0x117f92d3a69b1401f1b3eb19da6bf4dacd4a0c7b0fbc2767112674ae33356634),
108
+ y: uint256(0x18d8c2c0d2dab140e273b4607eda1ebb4560dbb0c8299e21e702a20d2252b91a)
109
109
  }),
110
110
  id2: Honk.G1Point({
111
- x: uint256(0x26b0d12b95f8e2550c280cd762b8a7a0866786f5b50a27f07c2a3f468fd41906),
112
- y: uint256(0x1f4f477e5649bb899c050eef0d95d27e167b1a7b04c4f23183ea6711460271a0)
111
+ x: uint256(0x2cffdc37befb4d7147f9b1e40dc54b529684766c4f6ed722a08af2080129fa8c),
112
+ y: uint256(0x0f9ea00342ceb61caea3d52750c757d17b6d4d63fc7d855d4b3fdbad8566cab9)
113
113
  }),
114
114
  id3: Honk.G1Point({
115
- x: uint256(0x1432117a5e5aba3ca4c8251a388ba3eb64c890cd7a4c9f22c375545959d3dac2),
116
- y: uint256(0x257b48e60d07172dda9e5058f6e2b18cbc6d89b2a28f8fd757b2a3bb5f88aeaf)
115
+ x: uint256(0x04b9f40f2cd4123d03663d63758856d3f947c857cdb16e4c45587df95a5239c1),
116
+ y: uint256(0x149255bba819e1a349307d4a334143f77028d663bee3a4761177dd5f5610df21)
117
117
  }),
118
118
  id4: Honk.G1Point({
119
- x: uint256(0x2f923c18160c73a2066f82a3b355a59da4fb43d4da5568c2a4be9d95adb5009b),
120
- y: uint256(0x03e3b6a5cdce2e6cc0fc92ffe3f43a827b4b2a315a9e6d60465b0d8e3af13802)
119
+ x: uint256(0x2b8aba03c2291ad20411a8f5c9eafa2177e093539e88f085c376496455ce16ab),
120
+ y: uint256(0x2b2cda526df47bdb8ff2257b7c3b8eadfcd5c195ad0d619ddcc3a960ece05656)
121
121
  }),
122
122
  lagrangeFirst: Honk.G1Point({
123
- x: uint256(0x0000000000000000000000000000000000000000000000000000000000000001),
124
- y: uint256(0x0000000000000000000000000000000000000000000000000000000000000002)
123
+ x: uint256(0x2a56ce41f6b0be13b9c26747621b821eee81b23a887f299049b14c11e98460d6),
124
+ y: uint256(0x1aa98f2de3ddda547d8f6de4e725ded5827d6338c78656c0d12ca1aea6ef2c7c)
125
125
  }),
126
126
  lagrangeLast: Honk.G1Point({
127
- x: uint256(0x038e31f2fc154e946f6b7b0a6c0f4dd1cfbbd2e4b3a34aea3fcbbc8941347499),
128
- y: uint256(0x1e8a93342268018e092bc66947eb747178c7592b5277404a79701de88bba4c06)
127
+ x: uint256(0x100c5101d69d23da354afa62b1d0354277b5f12dc6dbd26b1b33eae7973aca5a),
128
+ y: uint256(0x2ae821e69f12831c7dd899a431ff8eb56f8637b07fbd23ca8abcaae6ea299326)
129
129
  })
130
130
  });
131
131
  return vk;
@@ -135,7 +135,33 @@ library HonkVerificationKey {
135
135
  pragma solidity ^0.8.27;
136
136
 
137
137
  interface IVerifier {
138
- function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external returns (bool);
138
+ function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool);
139
+ }
140
+
141
+ /**
142
+ * @notice Library of error codes
143
+ * @dev You can run `forge inspect Errors errors` to get the selectors for the optimised verifier
144
+ */
145
+ library Errors {
146
+ error ValueGeLimbMax();
147
+ error ValueGeGroupOrder();
148
+ error ValueGeFieldOrder();
149
+
150
+ error InvertOfZero();
151
+ error NotPowerOfTwo();
152
+ error ModExpFailed();
153
+
154
+ error ProofLengthWrong();
155
+ error ProofLengthWrongWithLogN(uint256 logN, uint256 actualLength, uint256 expectedLength);
156
+ error PublicInputsLengthWrong();
157
+ error SumcheckFailed();
158
+ error ShpleminiFailed();
159
+
160
+ error PointAtInfinity();
161
+
162
+ error ConsistencyCheckFailed();
163
+ error VerificationKeyConfigurationMismatch();
164
+ error GeminiChallengeInSubgroup();
139
165
  }
140
166
 
141
167
  type Fr is uint256;
@@ -144,7 +170,6 @@ using { add as + } for Fr global;
144
170
  using { sub as - } for Fr global;
145
171
  using { mul as * } for Fr global;
146
172
 
147
- using { exp as ^ } for Fr global;
148
173
  using { notEqual as != } for Fr global;
149
174
  using { equal as == } for Fr global;
150
175
 
@@ -156,29 +181,21 @@ Fr constant SUBGROUP_GENERATOR_INVERSE = Fr.wrap(0x204bd3277422fad364751ad938e2b
156
181
  Fr constant MINUS_ONE = Fr.wrap(MODULUS - 1);
157
182
  Fr constant ONE = Fr.wrap(1);
158
183
  Fr constant ZERO = Fr.wrap(0);
184
+
185
+ // SmallSubgroupIPA opening-claim layout — mirrors SMALL_IPA_CLAIMS in
186
+ // barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa_utils.hpp.
187
+ uint256 constant NUM_SMALL_IPA_OPENING_CLAIMS = 5;
188
+ uint256 constant SMALL_IPA_BOUNDARY_OPENING_IDX = 3;
189
+ uint256 constant NUM_SMALL_IPA_TRANSCRIPT_EVALS = 4;
159
190
  // Instantiation
160
191
 
161
192
  library FrLib {
162
- function from(uint256 value) internal pure returns (Fr) {
163
- unchecked {
164
- return Fr.wrap(value % MODULUS);
165
- }
166
- }
167
-
168
- function fromBytes32(bytes32 value) internal pure returns (Fr) {
169
- unchecked {
170
- return Fr.wrap(uint256(value) % MODULUS);
171
- }
172
- }
173
-
174
- function toBytes32(Fr value) internal pure returns (bytes32) {
175
- unchecked {
176
- return bytes32(Fr.unwrap(value));
177
- }
178
- }
193
+ bytes4 internal constant FRLIB_MODEXP_FAILED_SELECTOR = 0x1f7ec5f0;
179
194
 
180
195
  function invert(Fr value) internal view returns (Fr) {
181
196
  uint256 v = Fr.unwrap(value);
197
+ require(v != 0, Errors.InvertOfZero());
198
+
182
199
  uint256 result;
183
200
 
184
201
  // Call the modexp precompile to invert in the field
@@ -192,10 +209,11 @@ library FrLib {
192
209
  mstore(add(free, 0xa0), MODULUS)
193
210
  let success := staticcall(gas(), 0x05, free, 0xc0, 0x00, 0x20)
194
211
  if iszero(success) {
195
- revert(0, 0)
212
+ mstore(0x00, FRLIB_MODEXP_FAILED_SELECTOR)
213
+ revert(0, 0x04)
196
214
  }
197
215
  result := mload(0x00)
198
- mstore(0x40, add(free, 0x80))
216
+ mstore(0x40, add(free, 0xc0))
199
217
  }
200
218
 
201
219
  return Fr.wrap(result);
@@ -203,6 +221,8 @@ library FrLib {
203
221
 
204
222
  function pow(Fr base, uint256 v) internal view returns (Fr) {
205
223
  uint256 b = Fr.unwrap(base);
224
+ // Only works for power of 2
225
+ require(v > 0 && (v & (v - 1)) == 0, Errors.NotPowerOfTwo());
206
226
  uint256 result;
207
227
 
208
228
  // Call the modexp precompile to invert in the field
@@ -216,10 +236,11 @@ library FrLib {
216
236
  mstore(add(free, 0xa0), MODULUS)
217
237
  let success := staticcall(gas(), 0x05, free, 0xc0, 0x00, 0x20)
218
238
  if iszero(success) {
219
- revert(0, 0)
239
+ mstore(0x00, FRLIB_MODEXP_FAILED_SELECTOR)
240
+ revert(0, 0x04)
220
241
  }
221
242
  result := mload(0x00)
222
- mstore(0x40, add(free, 0x80))
243
+ mstore(0x40, add(free, 0xc0))
223
244
  }
224
245
 
225
246
  return Fr.wrap(result);
@@ -248,6 +269,27 @@ library FrLib {
248
269
  return Fr.wrap(MODULUS - Fr.unwrap(value));
249
270
  }
250
271
  }
272
+
273
+ function from(uint256 value) internal pure returns (Fr) {
274
+ unchecked {
275
+ require(value < MODULUS, Errors.ValueGeFieldOrder());
276
+ return Fr.wrap(value);
277
+ }
278
+ }
279
+
280
+ function fromBytes32(bytes32 value) internal pure returns (Fr) {
281
+ unchecked {
282
+ uint256 v = uint256(value);
283
+ require(v < MODULUS, Errors.ValueGeFieldOrder());
284
+ return Fr.wrap(v);
285
+ }
286
+ }
287
+
288
+ function toBytes32(Fr value) internal pure returns (bytes32) {
289
+ unchecked {
290
+ return bytes32(Fr.unwrap(value));
291
+ }
292
+ }
251
293
  }
252
294
 
253
295
  // Free functions
@@ -269,15 +311,6 @@ function sub(Fr a, Fr b) pure returns (Fr) {
269
311
  }
270
312
  }
271
313
 
272
- function exp(Fr base, Fr exponent) pure returns (Fr) {
273
- if (Fr.unwrap(exponent) == 0) return Fr.wrap(1);
274
- // Implement exponent with a loop as we will overflow otherwise
275
- for (uint256 i = 1; i < Fr.unwrap(exponent); i += i) {
276
- base = base * base;
277
- }
278
- return base;
279
- }
280
-
281
314
  function notEqual(Fr a, Fr b) pure returns (bool) {
282
315
  unchecked {
283
316
  return Fr.unwrap(a) != Fr.unwrap(b);
@@ -290,9 +323,9 @@ function equal(Fr a, Fr b) pure returns (bool) {
290
323
  }
291
324
  }
292
325
 
293
- uint256 constant CONST_PROOF_SIZE_LOG_N = 28;
326
+ uint256 constant CONST_PROOF_SIZE_LOG_N = 25;
294
327
 
295
- uint256 constant NUMBER_OF_SUBRELATIONS = 28;
328
+ uint256 constant NUMBER_OF_SUBRELATIONS = 29;
296
329
  uint256 constant BATCHED_RELATION_PARTIAL_LENGTH = 8;
297
330
  uint256 constant ZK_BATCHED_RELATION_PARTIAL_LENGTH = 9;
298
331
  uint256 constant NUMBER_OF_ENTITIES = 41;
@@ -302,7 +335,7 @@ uint256 constant NUMBER_OF_ENTITIES_ZK = NUMBER_OF_ENTITIES + NUM_MASKING_POLYNO
302
335
  uint256 constant NUMBER_UNSHIFTED = 36;
303
336
  uint256 constant NUMBER_UNSHIFTED_ZK = NUMBER_UNSHIFTED + NUM_MASKING_POLYNOMIALS;
304
337
  uint256 constant NUMBER_TO_BE_SHIFTED = 5;
305
- uint256 constant PAIRING_POINTS_SIZE = 16;
338
+ uint256 constant PAIRING_POINTS_SIZE = 8;
306
339
 
307
340
  uint256 constant FIELD_ELEMENT_SIZE = 0x20;
308
341
  uint256 constant GROUP_ELEMENT_SIZE = 0x40;
@@ -310,22 +343,8 @@ uint256 constant GROUP_ELEMENT_SIZE = 0x40;
310
343
  // Powers of alpha used to batch subrelations (alpha, alpha^2, ..., alpha^(NUM_SUBRELATIONS-1))
311
344
  uint256 constant NUMBER_OF_ALPHAS = NUMBER_OF_SUBRELATIONS - 1;
312
345
 
313
- // ENUM FOR WIRES
346
+ // Must match UltraFlavor_Generated::EntityId order.
314
347
  enum WIRE {
315
- Q_M,
316
- Q_C,
317
- Q_L,
318
- Q_R,
319
- Q_O,
320
- Q_4,
321
- Q_LOOKUP,
322
- Q_ARITH,
323
- Q_RANGE,
324
- Q_ELLIPTIC,
325
- Q_MEMORY,
326
- Q_NNF,
327
- Q_POSEIDON2_EXTERNAL,
328
- Q_POSEIDON2_INTERNAL,
329
348
  SIGMA_1,
330
349
  SIGMA_2,
331
350
  SIGMA_3,
@@ -334,12 +353,26 @@ enum WIRE {
334
353
  ID_2,
335
354
  ID_3,
336
355
  ID_4,
356
+ LAGRANGE_FIRST,
357
+ LAGRANGE_LAST,
358
+ Q_LOOKUP,
337
359
  TABLE_1,
338
360
  TABLE_2,
339
361
  TABLE_3,
340
362
  TABLE_4,
341
- LAGRANGE_FIRST,
342
- LAGRANGE_LAST,
363
+ Q_M,
364
+ Q_R,
365
+ Q_O,
366
+ Q_C,
367
+ Q_L,
368
+ Q_4,
369
+ Q_ARITH,
370
+ Q_RANGE,
371
+ Q_ELLIPTIC,
372
+ Q_MEMORY,
373
+ Q_NNF,
374
+ Q_POSEIDON2_EXTERNAL,
375
+ Q_POSEIDON2_INTERNAL,
343
376
  W_L,
344
377
  W_R,
345
378
  W_O,
@@ -404,8 +437,6 @@ library Honk {
404
437
  struct RelationParameters {
405
438
  // challenges
406
439
  Fr eta;
407
- Fr etaTwo;
408
- Fr etaThree;
409
440
  Fr beta;
410
441
  Fr gamma;
411
442
  // derived
@@ -525,8 +556,8 @@ library ZKTranscriptLib {
525
556
  // Split into two equal 127-bit chunks (254/2)
526
557
  uint256 lo = challengeU256 & 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // 127 bits
527
558
  uint256 hi = challengeU256 >> 127;
528
- first = FrLib.fromBytes32(bytes32(lo));
529
- second = FrLib.fromBytes32(bytes32(hi));
559
+ first = FrLib.from(lo);
560
+ second = FrLib.from(hi);
530
561
  }
531
562
 
532
563
  function generateRelationParametersChallenges(
@@ -536,9 +567,9 @@ library ZKTranscriptLib {
536
567
  uint256 publicInputsSize,
537
568
  Fr previousChallenge
538
569
  ) internal pure returns (Honk.RelationParameters memory rp, Fr nextPreviousChallenge) {
539
- (rp.eta, rp.etaTwo, rp.etaThree, previousChallenge) = generateEtaChallenge(proof, publicInputs, vkHash, publicInputsSize);
570
+ (rp.eta, previousChallenge) = generateEtaChallenge(proof, publicInputs, vkHash, publicInputsSize);
540
571
 
541
- (rp.beta, rp.gamma, nextPreviousChallenge) = generateBetaAndGammaChallenges(previousChallenge, proof);
572
+ (rp.beta, rp.gamma, nextPreviousChallenge) = generateBetaGammaChallenges(previousChallenge, proof);
542
573
  }
543
574
 
544
575
  function generateEtaChallenge(
@@ -546,13 +577,14 @@ library ZKTranscriptLib {
546
577
  bytes32[] calldata publicInputs,
547
578
  uint256 vkHash,
548
579
  uint256 publicInputsSize
549
- ) internal pure returns (Fr eta, Fr etaTwo, Fr etaThree, Fr previousChallenge) {
580
+ ) internal pure returns (Fr eta, Fr previousChallenge) {
550
581
  // Size: 1 (vkHash) + publicInputsSize + 8 (geminiMask(2) + 3 wires(6))
551
582
  bytes32[] memory round0 = new bytes32[](1 + publicInputsSize + 8);
552
583
  round0[0] = bytes32(vkHash);
553
584
 
554
585
  for (uint256 i = 0; i < publicInputsSize - PAIRING_POINTS_SIZE; i++) {
555
- round0[1 + i] = bytes32(publicInputs[i]);
586
+ require(uint256(publicInputs[i]) < P, Errors.ValueGeFieldOrder());
587
+ round0[1 + i] = publicInputs[i];
556
588
  }
557
589
  for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) {
558
590
  round0[1 + publicInputsSize - PAIRING_POINTS_SIZE + i] = FrLib.toBytes32(proof.pairingPointObject[i]);
@@ -571,14 +603,11 @@ library ZKTranscriptLib {
571
603
  round0[1 + publicInputsSize + 6] = bytes32(proof.w3.x);
572
604
  round0[1 + publicInputsSize + 7] = bytes32(proof.w3.y);
573
605
 
574
- previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round0)));
575
- (eta, etaTwo) = splitChallenge(previousChallenge);
576
- previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge))));
577
-
578
- (etaThree, ) = splitChallenge(previousChallenge);
606
+ previousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(round0))) % P);
607
+ (eta, ) = splitChallenge(previousChallenge);
579
608
  }
580
609
 
581
- function generateBetaAndGammaChallenges(
610
+ function generateBetaGammaChallenges(
582
611
  Fr previousChallenge,
583
612
  Honk.ZKProof memory proof
584
613
  ) internal pure returns (Fr beta, Fr gamma, Fr nextPreviousChallenge) {
@@ -591,7 +620,7 @@ library ZKTranscriptLib {
591
620
  round1[5] = bytes32(proof.w4.x);
592
621
  round1[6] = bytes32(proof.w4.y);
593
622
 
594
- nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round1)));
623
+ nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(round1))) % P);
595
624
  (beta, gamma) = splitChallenge(nextPreviousChallenge);
596
625
  }
597
626
 
@@ -608,7 +637,7 @@ library ZKTranscriptLib {
608
637
  alpha0[3] = proof.zPerm.x;
609
638
  alpha0[4] = proof.zPerm.y;
610
639
 
611
- nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(alpha0)));
640
+ nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(alpha0))) % P);
612
641
  Fr alpha;
613
642
  (alpha, ) = splitChallenge(nextPreviousChallenge);
614
643
 
@@ -623,7 +652,7 @@ library ZKTranscriptLib {
623
652
  Fr previousChallenge,
624
653
  uint256 logN
625
654
  ) internal pure returns (Fr[CONST_PROOF_SIZE_LOG_N] memory gateChallenges, Fr nextPreviousChallenge) {
626
- previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge))));
655
+ previousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge)))) % P);
627
656
  (gateChallenges[0], ) = splitChallenge(previousChallenge);
628
657
  for (uint256 i = 1; i < logN; i++) {
629
658
  gateChallenges[i] = gateChallenges[i - 1] * gateChallenges[i - 1];
@@ -641,7 +670,7 @@ library ZKTranscriptLib {
641
670
  challengeData[1] = proof.libraCommitments[0].x;
642
671
  challengeData[2] = proof.libraCommitments[0].y;
643
672
  challengeData[3] = Fr.unwrap(proof.libraSum);
644
- nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(challengeData)));
673
+ nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(challengeData))) % P);
645
674
  (libraChallenge, ) = splitChallenge(nextPreviousChallenge);
646
675
  }
647
676
 
@@ -657,7 +686,7 @@ library ZKTranscriptLib {
657
686
  for (uint256 j = 0; j < ZK_BATCHED_RELATION_PARTIAL_LENGTH; j++) {
658
687
  univariateChal[j + 1] = proof.sumcheckUnivariates[i][j];
659
688
  }
660
- prevChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(univariateChal)));
689
+ prevChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(univariateChal))) % P);
661
690
 
662
691
  (sumcheckChallenges[i], ) = splitChallenge(prevChallenge);
663
692
  }
@@ -680,7 +709,7 @@ library ZKTranscriptLib {
680
709
  rhoChallengeElements[i] = proof.libraCommitments[2].x;
681
710
  rhoChallengeElements[i + 1] = proof.libraCommitments[2].y;
682
711
 
683
- nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(rhoChallengeElements)));
712
+ nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(rhoChallengeElements))) % P);
684
713
  (rho, ) = splitChallenge(nextPreviousChallenge);
685
714
  }
686
715
 
@@ -697,7 +726,7 @@ library ZKTranscriptLib {
697
726
  gR[2 + i * 2] = proof.geminiFoldComms[i].y;
698
727
  }
699
728
 
700
- nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(gR)));
729
+ nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(gR))) % P);
701
730
 
702
731
  (geminiR, ) = splitChallenge(nextPreviousChallenge);
703
732
  }
@@ -720,7 +749,7 @@ library ZKTranscriptLib {
720
749
  libraIdx++;
721
750
  }
722
751
 
723
- nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(shplonkNuChallengeElements)));
752
+ nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(shplonkNuChallengeElements))) % P);
724
753
  (shplonkNu, ) = splitChallenge(nextPreviousChallenge);
725
754
  }
726
755
 
@@ -734,7 +763,7 @@ library ZKTranscriptLib {
734
763
  shplonkZChallengeElements[1] = proof.shplonkQ.x;
735
764
  shplonkZChallengeElements[2] = proof.shplonkQ.y;
736
765
 
737
- nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(shplonkZChallengeElements)));
766
+ nextPreviousChallenge = FrLib.from(uint256(keccak256(abi.encodePacked(shplonkZChallengeElements))) % P);
738
767
  (shplonkZ, ) = splitChallenge(nextPreviousChallenge);
739
768
  }
740
769
 
@@ -743,7 +772,10 @@ library ZKTranscriptLib {
743
772
 
744
773
  // Pairing point object
745
774
  for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) {
746
- p.pairingPointObject[i] = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]);
775
+ uint256 limb = uint256(bytes32(proof[boundary:boundary + FIELD_ELEMENT_SIZE]));
776
+ // lo limbs (even index) < 2^136, hi limbs (odd index) < 2^120
777
+ require(limb < 2 ** (i % 2 == 0 ? 136 : 120), Errors.ValueGeLimbMax());
778
+ p.pairingPointObject[i] = FrLib.from(limb);
747
779
  boundary += FIELD_ELEMENT_SIZE;
748
780
  }
749
781
 
@@ -823,17 +855,98 @@ library ZKTranscriptLib {
823
855
  }
824
856
  }
825
857
 
826
- // Field arithmetic libraries
827
-
828
858
  library RelationsLib {
859
+ struct EllipticParams {
860
+ // Points
861
+ Fr x_1;
862
+ Fr y_1;
863
+ Fr x_2;
864
+ Fr y_2;
865
+ Fr y_3;
866
+ Fr x_3;
867
+ // push accumulators into memory
868
+ Fr x_double_identity;
869
+ }
870
+
871
+ // Parameters used within the Memory Relation
872
+ // A struct is used to work around stack too deep. This relation has alot of variables
873
+ struct MemParams {
874
+ Fr memory_record_check;
875
+ Fr partial_record_check;
876
+ Fr next_gate_access_type;
877
+ Fr record_delta;
878
+ Fr index_delta;
879
+ Fr adjacent_values_match_if_adjacent_indices_match;
880
+ Fr adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation;
881
+ Fr access_check;
882
+ Fr next_gate_access_type_is_boolean;
883
+ Fr ROM_consistency_check_identity;
884
+ Fr RAM_consistency_check_identity;
885
+ Fr timestamp_delta;
886
+ Fr RAM_timestamp_check_identity;
887
+ Fr memory_identity;
888
+ Fr index_is_monotonically_increasing;
889
+ }
890
+
891
+ // Parameters used within the Non-Native Field Relation
892
+ // A struct is used to work around stack too deep. This relation has alot of variables
893
+ struct NnfParams {
894
+ Fr limb_subproduct;
895
+ Fr non_native_field_gate_1;
896
+ Fr non_native_field_gate_2;
897
+ Fr non_native_field_gate_3;
898
+ Fr limb_accumulator_1;
899
+ Fr limb_accumulator_2;
900
+ Fr nnf_identity;
901
+ }
902
+
903
+ struct PoseidonExternalParams {
904
+ Fr s1;
905
+ Fr s2;
906
+ Fr s3;
907
+ Fr s4;
908
+ Fr u1;
909
+ Fr u2;
910
+ Fr u3;
911
+ Fr u4;
912
+ Fr t0;
913
+ Fr t1;
914
+ Fr t2;
915
+ Fr t3;
916
+ Fr v1;
917
+ Fr v2;
918
+ Fr v3;
919
+ Fr v4;
920
+ Fr q_pos_by_scaling;
921
+ }
922
+
923
+ struct PoseidonInternalParams {
924
+ Fr u1;
925
+ Fr u2;
926
+ Fr u3;
927
+ Fr u4;
928
+ Fr u_sum;
929
+ Fr v1;
930
+ Fr v2;
931
+ Fr v3;
932
+ Fr v4;
933
+ Fr s1;
934
+ Fr q_pos_by_scaling;
935
+ }
936
+
829
937
  Fr internal constant GRUMPKIN_CURVE_B_PARAMETER_NEGATED = Fr.wrap(17); // -(-17)
938
+ uint256 internal constant NEG_HALF_MODULO_P = 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000;
939
+
940
+ // Constants for the Non-native Field relation
941
+ Fr internal constant LIMB_SIZE = Fr.wrap(uint256(1) << 68);
942
+ Fr internal constant SUBLIMB_SHIFT = Fr.wrap(uint256(1) << 14);
830
943
 
831
944
  function accumulateRelationEvaluations(
832
945
  Fr[NUMBER_OF_ENTITIES] memory purportedEvaluations,
833
946
  Honk.RelationParameters memory rp,
834
947
  Fr[NUMBER_OF_ALPHAS] memory subrelationChallenges,
835
948
  Fr powPartialEval
836
- ) internal pure returns (Fr accumulator) {
949
+ ) external pure returns (Fr accumulator) {
837
950
  Fr[NUMBER_OF_SUBRELATIONS] memory evaluations;
838
951
 
839
952
  // Accumulate all relations in Ultra Honk - each with varying number of subrelations
@@ -860,12 +973,10 @@ library RelationsLib {
860
973
  return p[uint256(_wire)];
861
974
  }
862
975
 
863
- uint256 internal constant NEG_HALF_MODULO_P = 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000;
864
976
  /**
865
977
  * Ultra Arithmetic Relation
866
978
  *
867
979
  */
868
-
869
980
  function accumulateArithmeticRelation(
870
981
  Fr[NUMBER_OF_ENTITIES] memory p,
871
982
  Fr[NUMBER_OF_SUBRELATIONS] memory evals,
@@ -887,7 +998,7 @@ library RelationsLib {
887
998
  accum = accum + (q_arith - ONE) * wire(p, WIRE.W_4_SHIFT);
888
999
  accum = accum * q_arith;
889
1000
  accum = accum * domainSep;
890
- evals[0] = accum;
1001
+ evals[6] = accum;
891
1002
  }
892
1003
 
893
1004
  // Relation 1
@@ -897,7 +1008,7 @@ library RelationsLib {
897
1008
  accum = accum * (q_arith - ONE);
898
1009
  accum = accum * q_arith;
899
1010
  accum = accum * domainSep;
900
- evals[1] = accum;
1011
+ evals[7] = accum;
901
1012
  }
902
1013
  }
903
1014
 
@@ -933,13 +1044,19 @@ library RelationsLib {
933
1044
 
934
1045
  acc = acc - ((wire(p, WIRE.Z_PERM_SHIFT) + (wire(p, WIRE.LAGRANGE_LAST) * rp.publicInputsDelta)) * grand_product_denominator);
935
1046
  acc = acc * domainSep;
936
- evals[2] = acc;
1047
+ evals[0] = acc;
937
1048
  }
938
1049
 
939
1050
  // Contribution 3
940
1051
  {
941
1052
  Fr acc = (wire(p, WIRE.LAGRANGE_LAST) * wire(p, WIRE.Z_PERM_SHIFT)) * domainSep;
942
- evals[3] = acc;
1053
+ evals[1] = acc;
1054
+ }
1055
+
1056
+ // Contribution 4: z_perm initialization check (lagrange_first * z_perm = 0)
1057
+ {
1058
+ Fr acc = (wire(p, WIRE.LAGRANGE_FIRST) * wire(p, WIRE.Z_PERM)) * domainSep;
1059
+ evals[2] = acc;
943
1060
  }
944
1061
  }
945
1062
 
@@ -949,49 +1066,53 @@ library RelationsLib {
949
1066
  Fr[NUMBER_OF_SUBRELATIONS] memory evals,
950
1067
  Fr domainSep
951
1068
  ) internal pure {
952
- Fr write_term;
953
- Fr read_term;
1069
+ Fr table_term;
1070
+ Fr lookup_term;
954
1071
 
955
1072
  // Calculate the write term (the table accumulation)
1073
+ // table_term = table_1 + γ + table_2 * β + table_3 * β² + table_4 * β³
956
1074
  {
957
- write_term =
1075
+ Fr beta_sqr = rp.beta * rp.beta;
1076
+ table_term =
958
1077
  wire(p, WIRE.TABLE_1) +
959
1078
  rp.gamma +
960
- (wire(p, WIRE.TABLE_2) * rp.eta) +
961
- (wire(p, WIRE.TABLE_3) * rp.etaTwo) +
962
- (wire(p, WIRE.TABLE_4) * rp.etaThree);
1079
+ (wire(p, WIRE.TABLE_2) * rp.beta) +
1080
+ (wire(p, WIRE.TABLE_3) * beta_sqr) +
1081
+ (wire(p, WIRE.TABLE_4) * beta_sqr * rp.beta);
963
1082
  }
964
1083
 
965
- // Calculate the write term
1084
+ // Calculate the read term
1085
+ // lookup_term = derived_entry_1 + γ + derived_entry_2 * β + derived_entry_3 * β² + q_index * β³
966
1086
  {
1087
+ Fr beta_sqr = rp.beta * rp.beta;
967
1088
  Fr derived_entry_1 = wire(p, WIRE.W_L) + rp.gamma + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_L_SHIFT));
968
1089
  Fr derived_entry_2 = wire(p, WIRE.W_R) + wire(p, WIRE.Q_M) * wire(p, WIRE.W_R_SHIFT);
969
1090
  Fr derived_entry_3 = wire(p, WIRE.W_O) + wire(p, WIRE.Q_C) * wire(p, WIRE.W_O_SHIFT);
970
1091
 
971
- read_term = derived_entry_1 + (derived_entry_2 * rp.eta) + (derived_entry_3 * rp.etaTwo) + (wire(p, WIRE.Q_O) * rp.etaThree);
1092
+ lookup_term = derived_entry_1 + (derived_entry_2 * rp.beta) + (derived_entry_3 * beta_sqr) + (wire(p, WIRE.Q_O) * beta_sqr * rp.beta);
972
1093
  }
973
1094
 
974
- Fr read_inverse = wire(p, WIRE.LOOKUP_INVERSES) * write_term;
975
- Fr write_inverse = wire(p, WIRE.LOOKUP_INVERSES) * read_term;
1095
+ Fr lookup_inverse = wire(p, WIRE.LOOKUP_INVERSES) * table_term;
1096
+ Fr table_inverse = wire(p, WIRE.LOOKUP_INVERSES) * lookup_term;
976
1097
 
977
1098
  Fr inverse_exists_xor = wire(p, WIRE.LOOKUP_READ_TAGS) +
978
1099
  wire(p, WIRE.Q_LOOKUP) -
979
1100
  (wire(p, WIRE.LOOKUP_READ_TAGS) * wire(p, WIRE.Q_LOOKUP));
980
1101
 
981
1102
  // Inverse calculated correctly relation
982
- Fr accumulatorNone = read_term * write_term * wire(p, WIRE.LOOKUP_INVERSES) - inverse_exists_xor;
1103
+ Fr accumulatorNone = lookup_term * table_term * wire(p, WIRE.LOOKUP_INVERSES) - inverse_exists_xor;
983
1104
  accumulatorNone = accumulatorNone * domainSep;
984
1105
 
985
1106
  // Inverse
986
- Fr accumulatorOne = wire(p, WIRE.Q_LOOKUP) * read_inverse - wire(p, WIRE.LOOKUP_READ_COUNTS) * write_inverse;
1107
+ Fr accumulatorOne = wire(p, WIRE.Q_LOOKUP) * lookup_inverse - wire(p, WIRE.LOOKUP_READ_COUNTS) * table_inverse;
987
1108
 
988
1109
  Fr read_tag = wire(p, WIRE.LOOKUP_READ_TAGS);
989
1110
 
990
1111
  Fr read_tag_boolean_relation = read_tag * read_tag - read_tag;
991
1112
 
992
- evals[4] = accumulatorNone;
993
- evals[5] = accumulatorOne;
994
- evals[6] = read_tag_boolean_relation * domainSep;
1113
+ evals[3] = accumulatorNone;
1114
+ evals[4] = accumulatorOne;
1115
+ evals[5] = read_tag_boolean_relation * domainSep;
995
1116
  }
996
1117
 
997
1118
  function accumulateDeltaRangeRelation(
@@ -1017,7 +1138,7 @@ library RelationsLib {
1017
1138
  acc = acc * (delta_1 + minus_three);
1018
1139
  acc = acc * wire(p, WIRE.Q_RANGE);
1019
1140
  acc = acc * domainSep;
1020
- evals[7] = acc;
1141
+ evals[8] = acc;
1021
1142
  }
1022
1143
 
1023
1144
  // Contribution 7
@@ -1028,7 +1149,7 @@ library RelationsLib {
1028
1149
  acc = acc * (delta_2 + minus_three);
1029
1150
  acc = acc * wire(p, WIRE.Q_RANGE);
1030
1151
  acc = acc * domainSep;
1031
- evals[8] = acc;
1152
+ evals[9] = acc;
1032
1153
  }
1033
1154
 
1034
1155
  // Contribution 8
@@ -1039,7 +1160,7 @@ library RelationsLib {
1039
1160
  acc = acc * (delta_3 + minus_three);
1040
1161
  acc = acc * wire(p, WIRE.Q_RANGE);
1041
1162
  acc = acc * domainSep;
1042
- evals[9] = acc;
1163
+ evals[10] = acc;
1043
1164
  }
1044
1165
 
1045
1166
  // Contribution 9
@@ -1050,22 +1171,10 @@ library RelationsLib {
1050
1171
  acc = acc * (delta_4 + minus_three);
1051
1172
  acc = acc * wire(p, WIRE.Q_RANGE);
1052
1173
  acc = acc * domainSep;
1053
- evals[10] = acc;
1174
+ evals[11] = acc;
1054
1175
  }
1055
1176
  }
1056
1177
 
1057
- struct EllipticParams {
1058
- // Points
1059
- Fr x_1;
1060
- Fr y_1;
1061
- Fr x_2;
1062
- Fr y_2;
1063
- Fr y_3;
1064
- Fr x_3;
1065
- // push accumulators into memory
1066
- Fr x_double_identity;
1067
- }
1068
-
1069
1178
  function accumulateEllipticRelation(
1070
1179
  Fr[NUMBER_OF_ENTITIES] memory p,
1071
1180
  Fr[NUMBER_OF_SUBRELATIONS] memory evals,
@@ -1097,7 +1206,7 @@ library RelationsLib {
1097
1206
  x_add_identity = x_add_identity * x_diff * x_diff;
1098
1207
  x_add_identity = x_add_identity - y2_sqr - y1_sqr + y1y2 + y1y2;
1099
1208
 
1100
- evals[11] = x_add_identity * partialEval * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double);
1209
+ evals[12] = x_add_identity * partialEval * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double);
1101
1210
  }
1102
1211
 
1103
1212
  // Contribution 11 point addition, x-coordinate check
@@ -1106,7 +1215,7 @@ library RelationsLib {
1106
1215
  Fr y1_plus_y3 = ep.y_1 + ep.y_3;
1107
1216
  Fr y_diff = ep.y_2 * q_sign - ep.y_1;
1108
1217
  Fr y_add_identity = y1_plus_y3 * x_diff + (ep.x_3 - ep.x_1) * y_diff;
1109
- evals[12] = y_add_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double);
1218
+ evals[13] = y_add_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * (ONE - q_is_double);
1110
1219
  }
1111
1220
 
1112
1221
  // Contribution 10 point doubling, x-coordinate check
@@ -1122,7 +1231,7 @@ library RelationsLib {
1122
1231
  ep.x_double_identity = (ep.x_3 + ep.x_1 + ep.x_1) * y1_sqr_mul_4 - x1_pow_4_mul_9;
1123
1232
 
1124
1233
  Fr acc = ep.x_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double;
1125
- evals[11] = evals[11] + acc;
1234
+ evals[12] = evals[12] + acc;
1126
1235
  }
1127
1236
 
1128
1237
  // Contribution 11 point doubling, y-coordinate check
@@ -1130,30 +1239,10 @@ library RelationsLib {
1130
1239
  {
1131
1240
  Fr x1_sqr_mul_3 = (ep.x_1 + ep.x_1 + ep.x_1) * ep.x_1;
1132
1241
  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);
1133
- evals[12] = evals[12] + y_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double;
1242
+ evals[13] = evals[13] + y_double_identity * domainSep * wire(p, WIRE.Q_ELLIPTIC) * q_is_double;
1134
1243
  }
1135
1244
  }
1136
1245
 
1137
- // Parameters used within the Memory Relation
1138
- // A struct is used to work around stack too deep. This relation has alot of variables
1139
- struct MemParams {
1140
- Fr memory_record_check;
1141
- Fr partial_record_check;
1142
- Fr next_gate_access_type;
1143
- Fr record_delta;
1144
- Fr index_delta;
1145
- Fr adjacent_values_match_if_adjacent_indices_match;
1146
- Fr adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation;
1147
- Fr access_check;
1148
- Fr next_gate_access_type_is_boolean;
1149
- Fr ROM_consistency_check_identity;
1150
- Fr RAM_consistency_check_identity;
1151
- Fr timestamp_delta;
1152
- Fr RAM_timestamp_check_identity;
1153
- Fr memory_identity;
1154
- Fr index_is_monotonically_increasing;
1155
- }
1156
-
1157
1246
  function accumulateMemoryRelation(
1158
1247
  Fr[NUMBER_OF_ENTITIES] memory p,
1159
1248
  Honk.RelationParameters memory rp,
@@ -1162,6 +1251,10 @@ library RelationsLib {
1162
1251
  ) internal pure {
1163
1252
  MemParams memory ap;
1164
1253
 
1254
+ // Compute eta powers locally
1255
+ Fr eta_two = rp.eta * rp.eta;
1256
+ Fr eta_three = eta_two * rp.eta;
1257
+
1165
1258
  /**
1166
1259
  * MEMORY
1167
1260
  *
@@ -1203,8 +1296,8 @@ library RelationsLib {
1203
1296
  *
1204
1297
  * For ROM gates, qc = 0
1205
1298
  */
1206
- ap.memory_record_check = wire(p, WIRE.W_O) * rp.etaThree;
1207
- ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_R) * rp.etaTwo);
1299
+ ap.memory_record_check = wire(p, WIRE.W_O) * eta_three;
1300
+ ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_R) * eta_two);
1208
1301
  ap.memory_record_check = ap.memory_record_check + (wire(p, WIRE.W_L) * rp.eta);
1209
1302
  ap.memory_record_check = ap.memory_record_check + wire(p, WIRE.Q_C);
1210
1303
  ap.partial_record_check = ap.memory_record_check; // used in RAM consistency check; deg 1 or 4
@@ -1233,11 +1326,11 @@ library RelationsLib {
1233
1326
 
1234
1327
  ap.adjacent_values_match_if_adjacent_indices_match = (ap.index_delta * MINUS_ONE + ONE) * ap.record_delta; // deg 2
1235
1328
 
1236
- evals[14] =
1329
+ evals[15] =
1237
1330
  ap.adjacent_values_match_if_adjacent_indices_match *
1238
1331
  (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) *
1239
1332
  (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5
1240
- evals[15] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5
1333
+ evals[16] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5
1241
1334
 
1242
1335
  ap.ROM_consistency_check_identity = ap.memory_record_check * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)); // deg 3 or 7
1243
1336
 
@@ -1265,8 +1358,8 @@ library RelationsLib {
1265
1358
 
1266
1359
  // reverse order we could re-use `ap.partial_record_check` 1 - ((w3' * eta + w2') * eta + w1') * eta
1267
1360
  // deg 1 or 4
1268
- ap.next_gate_access_type = wire(p, WIRE.W_O_SHIFT) * rp.etaThree;
1269
- ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_R_SHIFT) * rp.etaTwo);
1361
+ ap.next_gate_access_type = wire(p, WIRE.W_O_SHIFT) * eta_three;
1362
+ ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_R_SHIFT) * eta_two);
1270
1363
  ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_L_SHIFT) * rp.eta);
1271
1364
  ap.next_gate_access_type = wire(p, WIRE.W_4_SHIFT) - ap.next_gate_access_type;
1272
1365
 
@@ -1284,12 +1377,12 @@ library RelationsLib {
1284
1377
  ap.next_gate_access_type_is_boolean = ap.next_gate_access_type * ap.next_gate_access_type - ap.next_gate_access_type;
1285
1378
 
1286
1379
  // Putting it all together...
1287
- evals[16] =
1380
+ evals[17] =
1288
1381
  ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation *
1289
1382
  (wire(p, WIRE.Q_O)) *
1290
1383
  (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 or 8
1291
- evals[17] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4
1292
- evals[18] = ap.next_gate_access_type_is_boolean * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 6
1384
+ evals[18] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4
1385
+ evals[19] = ap.next_gate_access_type_is_boolean * (wire(p, WIRE.Q_O)) * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 6
1293
1386
 
1294
1387
  ap.RAM_consistency_check_identity = ap.access_check * (wire(p, WIRE.Q_O)); // deg 3 or 9
1295
1388
 
@@ -1319,23 +1412,7 @@ library RelationsLib {
1319
1412
 
1320
1413
  // (deg 3 or 9) + (deg 4) + (deg 3)
1321
1414
  ap.memory_identity = ap.memory_identity * (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 10
1322
- evals[13] = ap.memory_identity;
1323
- }
1324
-
1325
- // Constants for the Non-native Field relation
1326
- Fr constant LIMB_SIZE = Fr.wrap(uint256(1) << 68);
1327
- Fr constant SUBLIMB_SHIFT = Fr.wrap(uint256(1) << 14);
1328
-
1329
- // Parameters used within the Non-Native Field Relation
1330
- // A struct is used to work around stack too deep. This relation has alot of variables
1331
- struct NnfParams {
1332
- Fr limb_subproduct;
1333
- Fr non_native_field_gate_1;
1334
- Fr non_native_field_gate_2;
1335
- Fr non_native_field_gate_3;
1336
- Fr limb_accumulator_1;
1337
- Fr limb_accumulator_2;
1338
- Fr nnf_identity;
1415
+ evals[14] = ap.memory_identity;
1339
1416
  }
1340
1417
 
1341
1418
  function accumulateNnfRelation(Fr[NUMBER_OF_ENTITIES] memory p, Fr[NUMBER_OF_SUBRELATIONS] memory evals, Fr domainSep) internal pure {
@@ -1405,27 +1482,7 @@ library RelationsLib {
1405
1482
 
1406
1483
  ap.nnf_identity = non_native_field_identity + limb_accumulator_identity;
1407
1484
  ap.nnf_identity = ap.nnf_identity * (wire(p, WIRE.Q_NNF) * domainSep);
1408
- evals[19] = ap.nnf_identity;
1409
- }
1410
-
1411
- struct PoseidonExternalParams {
1412
- Fr s1;
1413
- Fr s2;
1414
- Fr s3;
1415
- Fr s4;
1416
- Fr u1;
1417
- Fr u2;
1418
- Fr u3;
1419
- Fr u4;
1420
- Fr t0;
1421
- Fr t1;
1422
- Fr t2;
1423
- Fr t3;
1424
- Fr v1;
1425
- Fr v2;
1426
- Fr v3;
1427
- Fr v4;
1428
- Fr q_pos_by_scaling;
1485
+ evals[20] = ap.nnf_identity;
1429
1486
  }
1430
1487
 
1431
1488
  function accumulatePoseidonExternalRelation(
@@ -1461,27 +1518,13 @@ library RelationsLib {
1461
1518
  ep.v3 = ep.t2 + ep.v4; // u_1 + 3u_2 + 5u_3 + 7u_4
1462
1519
 
1463
1520
  ep.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_EXTERNAL) * domainSep;
1464
- evals[20] = evals[20] + ep.q_pos_by_scaling * (ep.v1 - wire(p, WIRE.W_L_SHIFT));
1465
-
1466
- evals[21] = evals[21] + ep.q_pos_by_scaling * (ep.v2 - wire(p, WIRE.W_R_SHIFT));
1521
+ evals[21] = evals[21] + ep.q_pos_by_scaling * (ep.v1 - wire(p, WIRE.W_L_SHIFT));
1467
1522
 
1468
- evals[22] = evals[22] + ep.q_pos_by_scaling * (ep.v3 - wire(p, WIRE.W_O_SHIFT));
1523
+ evals[22] = evals[22] + ep.q_pos_by_scaling * (ep.v2 - wire(p, WIRE.W_R_SHIFT));
1469
1524
 
1470
- evals[23] = evals[23] + ep.q_pos_by_scaling * (ep.v4 - wire(p, WIRE.W_4_SHIFT));
1471
- }
1525
+ evals[23] = evals[23] + ep.q_pos_by_scaling * (ep.v3 - wire(p, WIRE.W_O_SHIFT));
1472
1526
 
1473
- struct PoseidonInternalParams {
1474
- Fr u1;
1475
- Fr u2;
1476
- Fr u3;
1477
- Fr u4;
1478
- Fr u_sum;
1479
- Fr v1;
1480
- Fr v2;
1481
- Fr v3;
1482
- Fr v4;
1483
- Fr s1;
1484
- Fr q_pos_by_scaling;
1527
+ evals[24] = evals[24] + ep.q_pos_by_scaling * (ep.v4 - wire(p, WIRE.W_4_SHIFT));
1485
1528
  }
1486
1529
 
1487
1530
  function accumulatePoseidonInternalRelation(
@@ -1513,16 +1556,16 @@ library RelationsLib {
1513
1556
  ip.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_INTERNAL) * domainSep;
1514
1557
 
1515
1558
  ip.v1 = ip.u1 * INTERNAL_MATRIX_DIAGONAL[0] + ip.u_sum;
1516
- evals[24] = evals[24] + ip.q_pos_by_scaling * (ip.v1 - wire(p, WIRE.W_L_SHIFT));
1559
+ evals[25] = evals[25] + ip.q_pos_by_scaling * (ip.v1 - wire(p, WIRE.W_L_SHIFT));
1517
1560
 
1518
1561
  ip.v2 = ip.u2 * INTERNAL_MATRIX_DIAGONAL[1] + ip.u_sum;
1519
- evals[25] = evals[25] + ip.q_pos_by_scaling * (ip.v2 - wire(p, WIRE.W_R_SHIFT));
1562
+ evals[26] = evals[26] + ip.q_pos_by_scaling * (ip.v2 - wire(p, WIRE.W_R_SHIFT));
1520
1563
 
1521
1564
  ip.v3 = ip.u3 * INTERNAL_MATRIX_DIAGONAL[2] + ip.u_sum;
1522
- evals[26] = evals[26] + ip.q_pos_by_scaling * (ip.v3 - wire(p, WIRE.W_O_SHIFT));
1565
+ evals[27] = evals[27] + ip.q_pos_by_scaling * (ip.v3 - wire(p, WIRE.W_O_SHIFT));
1523
1566
 
1524
1567
  ip.v4 = ip.u4 * INTERNAL_MATRIX_DIAGONAL[3] + ip.u_sum;
1525
- evals[27] = evals[27] + ip.q_pos_by_scaling * (ip.v4 - wire(p, WIRE.W_4_SHIFT));
1568
+ evals[28] = evals[28] + ip.q_pos_by_scaling * (ip.v4 - wire(p, WIRE.W_4_SHIFT));
1526
1569
  }
1527
1570
 
1528
1571
  // Batch subrelation evaluations using precomputed powers of alpha
@@ -1539,8 +1582,6 @@ library RelationsLib {
1539
1582
  }
1540
1583
  }
1541
1584
 
1542
- // Field arithmetic libraries - prevent littering the code with modmul / addmul
1543
-
1544
1585
  library CommitmentSchemeLib {
1545
1586
  using FrLib for Fr;
1546
1587
 
@@ -1556,8 +1597,8 @@ library CommitmentSchemeLib {
1556
1597
  Fr batchingChallenge;
1557
1598
  // Linear combination of multilinear (sumcheck) evaluations and powers of rho
1558
1599
  Fr batchedEvaluation;
1559
- Fr[4] denominators;
1560
- Fr[4] batchingScalars;
1600
+ Fr[NUM_SMALL_IPA_OPENING_CLAIMS] denominators;
1601
+ Fr[NUM_SMALL_IPA_OPENING_CLAIMS] batchingScalars;
1561
1602
  // 1/(z - r^{2^i}) for i = 0, ..., logSize, dynamically updated
1562
1603
  Fr posInvertedDenominator;
1563
1604
  // 1/(z + r^{2^i}) for i = 0, ..., logSize, dynamically updated
@@ -1570,16 +1611,7 @@ library CommitmentSchemeLib {
1570
1611
  Fr[] foldPosEvaluations;
1571
1612
  }
1572
1613
 
1573
- function computeSquares(Fr r, uint256 logN) internal pure returns (Fr[] memory) {
1574
- Fr[] memory squares = new Fr[](logN);
1575
- squares[0] = r;
1576
- for (uint256 i = 1; i < logN; ++i) {
1577
- squares[i] = squares[i - 1].sqr();
1578
- }
1579
- return squares;
1580
- }
1581
1614
  // Compute the evaluations Aₗ(r^{2ˡ}) for l = 0, ..., m-1
1582
-
1583
1615
  function computeFoldPosEvaluations(
1584
1616
  Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckUChallenges,
1585
1617
  Fr batchedEvalAccumulator,
@@ -1603,23 +1635,19 @@ library CommitmentSchemeLib {
1603
1635
  }
1604
1636
  return foldPosEvaluations;
1605
1637
  }
1606
- }
1607
1638
 
1608
- uint256 constant Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; // EC group order. F_q
1609
-
1610
- function bytes32ToString(bytes32 value) pure returns (string memory result) {
1611
- bytes memory alphabet = "0123456789abcdef";
1612
-
1613
- bytes memory str = new bytes(66);
1614
- str[0] = "0";
1615
- str[1] = "x";
1616
- for (uint256 i = 0; i < 32; i++) {
1617
- str[2 + i * 2] = alphabet[uint8(value[i] >> 4)];
1618
- str[3 + i * 2] = alphabet[uint8(value[i] & 0x0f)];
1639
+ function computeSquares(Fr r, uint256 logN) internal pure returns (Fr[] memory) {
1640
+ Fr[] memory squares = new Fr[](logN);
1641
+ squares[0] = r;
1642
+ for (uint256 i = 1; i < logN; ++i) {
1643
+ squares[i] = squares[i - 1].sqr();
1644
+ }
1645
+ return squares;
1619
1646
  }
1620
- result = string(str);
1621
1647
  }
1622
1648
 
1649
+ uint256 constant Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; // EC group order. F_q
1650
+
1623
1651
  // Fr utility
1624
1652
 
1625
1653
  function bytesToFr(bytes calldata proofSection) pure returns (Fr scalar) {
@@ -1628,51 +1656,63 @@ function bytesToFr(bytes calldata proofSection) pure returns (Fr scalar) {
1628
1656
 
1629
1657
  // EC Point utilities
1630
1658
  function bytesToG1Point(bytes calldata proofSection) pure returns (Honk.G1Point memory point) {
1631
- point = Honk.G1Point({ x: uint256(bytes32(proofSection[0x00:0x20])) % Q, y: uint256(bytes32(proofSection[0x20:0x40])) % Q });
1659
+ uint256 x = uint256(bytes32(proofSection[0x00:0x20]));
1660
+ uint256 y = uint256(bytes32(proofSection[0x20:0x40]));
1661
+ require(x < Q && y < Q, Errors.ValueGeGroupOrder());
1662
+
1663
+ // (0,0) is the canonical EIP-196 encoding of the identity. It is accepted here
1664
+ // because polynomial commitments to identically-zero polynomials (e.g. unused
1665
+ // selector or table polys) are legitimately the identity. On-curve validation
1666
+ // (y² = x³ + 3) is handled by the ecAdd/ecMul precompiles per EIP-196.
1667
+ point = Honk.G1Point({ x: x, y: y });
1632
1668
  }
1633
1669
 
1634
1670
  function negateInplace(Honk.G1Point memory point) pure returns (Honk.G1Point memory) {
1635
- point.y = (Q - point.y) % Q;
1671
+ // When y == 0 (order-2 point), negation is the same point. Q - 0 = Q which is >= Q.
1672
+ if (point.y != 0) {
1673
+ point.y = Q - point.y;
1674
+ }
1636
1675
  return point;
1637
1676
  }
1638
1677
 
1639
1678
  /**
1640
1679
  * Convert the pairing points to G1 points.
1641
1680
  *
1642
- * The pairing points are serialised as an array of 68 bit limbs representing two points
1643
- * The lhs of a pairing operation and the rhs of a pairing operation
1681
+ * The pairing points are serialised as an array of 2 limbs representing two points
1682
+ * (P0 and P1, used for lhs and rhs of pairing operation).
1644
1683
  *
1645
- * There are 4 fields for each group element, leaving 8 fields for each side of the pairing.
1684
+ * There are 2 limbs (lo, hi) for each coordinate, so 4 limbs per point, 8 total.
1685
+ * 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]
1646
1686
  *
1647
1687
  * @param pairingPoints The pairing points to convert.
1648
- * @return lhs
1649
- * @return rhs
1688
+ * @return lhs P0 point
1689
+ * @return rhs P1 point
1650
1690
  */
1651
1691
  function convertPairingPointsToG1(
1652
1692
  Fr[PAIRING_POINTS_SIZE] memory pairingPoints
1653
1693
  ) pure returns (Honk.G1Point memory lhs, Honk.G1Point memory rhs) {
1694
+ // P0 (lhs): x = lo | (hi << 136)
1654
1695
  uint256 lhsX = Fr.unwrap(pairingPoints[0]);
1655
- lhsX |= Fr.unwrap(pairingPoints[1]) << 68;
1656
- lhsX |= Fr.unwrap(pairingPoints[2]) << 136;
1657
- lhsX |= Fr.unwrap(pairingPoints[3]) << 204;
1658
- lhs.x = lhsX;
1696
+ lhsX |= Fr.unwrap(pairingPoints[1]) << 136;
1659
1697
 
1660
- uint256 lhsY = Fr.unwrap(pairingPoints[4]);
1661
- lhsY |= Fr.unwrap(pairingPoints[5]) << 68;
1662
- lhsY |= Fr.unwrap(pairingPoints[6]) << 136;
1663
- lhsY |= Fr.unwrap(pairingPoints[7]) << 204;
1664
- lhs.y = lhsY;
1698
+ uint256 lhsY = Fr.unwrap(pairingPoints[2]);
1699
+ lhsY |= Fr.unwrap(pairingPoints[3]) << 136;
1665
1700
 
1666
- uint256 rhsX = Fr.unwrap(pairingPoints[8]);
1667
- rhsX |= Fr.unwrap(pairingPoints[9]) << 68;
1668
- rhsX |= Fr.unwrap(pairingPoints[10]) << 136;
1669
- rhsX |= Fr.unwrap(pairingPoints[11]) << 204;
1670
- rhs.x = rhsX;
1701
+ // P1 (rhs): x = lo | (hi << 136)
1702
+ uint256 rhsX = Fr.unwrap(pairingPoints[4]);
1703
+ rhsX |= Fr.unwrap(pairingPoints[5]) << 136;
1704
+
1705
+ uint256 rhsY = Fr.unwrap(pairingPoints[6]);
1706
+ rhsY |= Fr.unwrap(pairingPoints[7]) << 136;
1671
1707
 
1672
- uint256 rhsY = Fr.unwrap(pairingPoints[12]);
1673
- rhsY |= Fr.unwrap(pairingPoints[13]) << 68;
1674
- rhsY |= Fr.unwrap(pairingPoints[14]) << 136;
1675
- rhsY |= Fr.unwrap(pairingPoints[15]) << 204;
1708
+ // Reconstructed coordinates must be < Q to prevent malleability.
1709
+ // Without this, two different limb encodings could map to the same curve point
1710
+ // (via mulmod reduction in on-curve checks) but produce different transcript hashes.
1711
+ require(lhsX < Q && lhsY < Q && rhsX < Q && rhsY < Q, Errors.ValueGeGroupOrder());
1712
+
1713
+ lhs.x = lhsX;
1714
+ lhs.y = lhsY;
1715
+ rhs.x = rhsX;
1676
1716
  rhs.y = rhsY;
1677
1717
  }
1678
1718
 
@@ -1710,7 +1750,7 @@ function generateRecursionSeparator(
1710
1750
  recursionSeparatorElements[6] = accRhs.x;
1711
1751
  recursionSeparatorElements[7] = accRhs.y;
1712
1752
 
1713
- recursionSeparator = FrLib.fromBytes32(keccak256(abi.encodePacked(recursionSeparatorElements)));
1753
+ recursionSeparator = FrLib.from(uint256(keccak256(abi.encodePacked(recursionSeparatorElements))) % P);
1714
1754
  }
1715
1755
 
1716
1756
  /**
@@ -1831,17 +1871,20 @@ function ecAdd(Honk.G1Point memory lhs, Honk.G1Point memory rhs) view returns (H
1831
1871
  return result;
1832
1872
  }
1833
1873
 
1834
- function validateOnCurve(Honk.G1Point memory point) pure {
1835
- uint256 x = point.x;
1836
- uint256 y = point.y;
1874
+ function rejectPointAtInfinity(Honk.G1Point memory point) pure {
1875
+ require((point.x | point.y) != 0, Errors.PointAtInfinity());
1876
+ }
1837
1877
 
1838
- bool success = false;
1839
- assembly {
1840
- let xx := mulmod(x, x, Q)
1841
- success := eq(mulmod(y, y, Q), addmod(mulmod(x, xx, Q), 3, Q))
1878
+ /**
1879
+ * Check if pairing point limbs are all zero (default/infinity).
1880
+ * Default pairing points indicate no recursive verification occurred.
1881
+ */
1882
+ function arePairingPointsDefault(Fr[PAIRING_POINTS_SIZE] memory pairingPoints) pure returns (bool) {
1883
+ uint256 acc = 0;
1884
+ for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) {
1885
+ acc |= Fr.unwrap(pairingPoints[i]);
1842
1886
  }
1843
-
1844
- require(success, "point is not on the curve");
1887
+ return acc == 0;
1845
1888
  }
1846
1889
 
1847
1890
  function pairing(Honk.G1Point memory rhs, Honk.G1Point memory lhs) view returns (bool decodedResult) {
@@ -1866,84 +1909,68 @@ function pairing(Honk.G1Point memory rhs, Honk.G1Point memory lhs) view returns
1866
1909
  decodedResult = success && abi.decode(result, (bool));
1867
1910
  }
1868
1911
 
1869
- // Field arithmetic libraries - prevent littering the code with modmul / addmul
1870
-
1871
1912
  abstract contract BaseZKHonkVerifier is IVerifier {
1872
1913
  using FrLib for Fr;
1873
1914
 
1874
- uint256 immutable $N;
1875
- uint256 immutable $LOG_N;
1876
- uint256 immutable $VK_HASH;
1877
- uint256 immutable $NUM_PUBLIC_INPUTS;
1878
- uint256 immutable $MSMSize;
1879
-
1880
- constructor(uint256 _N, uint256 _logN, uint256 _vkHash, uint256 _numPublicInputs) {
1881
- $N = _N;
1882
- $LOG_N = _logN;
1883
- $VK_HASH = _vkHash;
1884
- $NUM_PUBLIC_INPUTS = _numPublicInputs;
1885
- $MSMSize = NUMBER_UNSHIFTED_ZK + _logN + LIBRA_COMMITMENTS + 2;
1915
+ struct PairingInputs {
1916
+ Honk.G1Point P_0;
1917
+ Honk.G1Point P_1;
1886
1918
  }
1887
1919
 
1888
- // Errors
1889
- error ProofLengthWrong();
1890
- error ProofLengthWrongWithLogN(uint256 logN, uint256 actualLength, uint256 expectedLength);
1891
- error PublicInputsLengthWrong();
1892
- error SumcheckFailed();
1893
- error ShpleminiFailed();
1894
- error GeminiChallengeInSubgroup();
1895
- error ConsistencyCheckFailed();
1920
+ struct SmallSubgroupIpaIntermediates {
1921
+ Fr[SUBGROUP_SIZE] challengePolyLagrange;
1922
+ Fr challengePolyEval;
1923
+ Fr lagrangeFirst;
1924
+ Fr lagrangeLast;
1925
+ Fr rootPower;
1926
+ Fr[SUBGROUP_SIZE] denominators; // this has to disappear
1927
+ Fr diff;
1928
+ }
1896
1929
 
1897
1930
  // Constants for proof length calculation (matching UltraKeccakZKFlavor)
1898
- uint256 constant NUM_WITNESS_ENTITIES = 8 + NUM_MASKING_POLYNOMIALS;
1899
- uint256 constant NUM_ELEMENTS_COMM = 2; // uint256 elements for curve points
1900
- uint256 constant NUM_ELEMENTS_FR = 1; // uint256 elements for field elements
1901
- uint256 constant NUM_LIBRA_EVALUATIONS = 4; // libra evaluations
1931
+ uint256 internal constant NUM_WITNESS_ENTITIES = 8 + NUM_MASKING_POLYNOMIALS;
1932
+ uint256 internal constant NUM_ELEMENTS_COMM = 2; // uint256 elements for curve points
1933
+ uint256 internal constant NUM_ELEMENTS_FR = 1; // uint256 elements for field elements
1934
+ uint256 internal constant NUM_LIBRA_EVALUATIONS = 4; // libra evaluations
1902
1935
 
1903
- // Calculate proof size based on log_n (matching UltraKeccakZKFlavor formula)
1904
- function calculateProofSize(uint256 logN) internal pure returns (uint256) {
1905
- // Witness and Libra commitments
1906
- uint256 proofLength = NUM_WITNESS_ENTITIES * NUM_ELEMENTS_COMM; // witness commitments
1907
- proofLength += NUM_ELEMENTS_COMM * 3; // Libra concat, grand sum, quotient comms + Gemini masking
1908
-
1909
- // Sumcheck
1910
- proofLength += logN * ZK_BATCHED_RELATION_PARTIAL_LENGTH * NUM_ELEMENTS_FR; // sumcheck univariates
1911
- proofLength += NUMBER_OF_ENTITIES_ZK * NUM_ELEMENTS_FR; // sumcheck evaluations
1912
-
1913
- // Libra and Gemini
1914
- proofLength += NUM_ELEMENTS_FR * 2; // Libra sum, claimed eval
1915
- proofLength += logN * NUM_ELEMENTS_FR; // Gemini a evaluations
1916
- proofLength += NUM_LIBRA_EVALUATIONS * NUM_ELEMENTS_FR; // libra evaluations
1936
+ uint256 internal constant LIBRA_COMMITMENTS = 3;
1937
+ uint256 internal constant LIBRA_EVALUATIONS = 4;
1938
+ uint256 internal constant LIBRA_UNIVARIATES_LENGTH = 9;
1917
1939
 
1918
- // PCS commitments
1919
- proofLength += (logN - 1) * NUM_ELEMENTS_COMM; // Gemini Fold commitments
1920
- proofLength += NUM_ELEMENTS_COMM * 2; // Shplonk Q and KZG W commitments
1940
+ uint256 internal constant SHIFTED_COMMITMENTS_START = 30;
1941
+ uint256 internal constant PERMUTATION_ARGUMENT_VALUE_SEPARATOR = 1 << 28;
1921
1942
 
1922
- // Pairing points
1923
- proofLength += PAIRING_POINTS_SIZE; // pairing inputs carried on public inputs
1943
+ uint256 internal immutable $N;
1944
+ uint256 internal immutable $LOG_N;
1945
+ uint256 internal immutable $VK_HASH;
1946
+ uint256 internal immutable $NUM_PUBLIC_INPUTS;
1947
+ uint256 internal immutable $MSMSize;
1924
1948
 
1925
- return proofLength;
1949
+ constructor(uint256 _N, uint256 _logN, uint256 _vkHash, uint256 _numPublicInputs) {
1950
+ $N = _N;
1951
+ $LOG_N = _logN;
1952
+ $VK_HASH = _vkHash;
1953
+ $NUM_PUBLIC_INPUTS = _numPublicInputs;
1954
+ $MSMSize = NUMBER_UNSHIFTED_ZK + _logN + LIBRA_COMMITMENTS + 2;
1926
1955
  }
1927
1956
 
1928
- uint256 constant SHIFTED_COMMITMENTS_START = 30;
1929
-
1930
- function loadVerificationKey() internal pure virtual returns (Honk.VerificationKey memory);
1957
+ function validateVerificationKey(Honk.VerificationKey memory vk) internal view {
1958
+ require($N == vk.circuitSize, Errors.VerificationKeyConfigurationMismatch());
1959
+ require($LOG_N == vk.logCircuitSize, Errors.VerificationKeyConfigurationMismatch());
1960
+ require($NUM_PUBLIC_INPUTS == vk.publicInputsSize, Errors.VerificationKeyConfigurationMismatch());
1961
+ }
1931
1962
 
1932
1963
  function verify(bytes calldata proof, bytes32[] calldata publicInputs) public view override returns (bool verified) {
1933
1964
  // Calculate expected proof size based on $LOG_N
1934
1965
  uint256 expectedProofSize = calculateProofSize($LOG_N);
1935
1966
 
1936
1967
  // Check the received proof is the expected size where each field element is 32 bytes
1937
- if (proof.length != expectedProofSize * 32) {
1938
- revert ProofLengthWrongWithLogN($LOG_N, proof.length, expectedProofSize * 32);
1939
- }
1968
+ require(proof.length == expectedProofSize, Errors.ProofLengthWrongWithLogN($LOG_N, proof.length, expectedProofSize));
1940
1969
 
1941
1970
  Honk.VerificationKey memory vk = loadVerificationKey();
1942
1971
  Honk.ZKProof memory p = ZKTranscriptLib.loadProof(proof, $LOG_N);
1943
1972
 
1944
- if (publicInputs.length != vk.publicInputsSize - PAIRING_POINTS_SIZE) {
1945
- revert PublicInputsLengthWrong();
1946
- }
1973
+ require(publicInputs.length == vk.publicInputsSize - PAIRING_POINTS_SIZE, Errors.PublicInputsLengthWrong());
1947
1974
 
1948
1975
  // Generate the fiat shamir challenges for the whole protocol
1949
1976
  ZKTranscript memory t = ZKTranscriptLib.generateTranscript(p, publicInputs, $VK_HASH, $NUM_PUBLIC_INPUTS, $LOG_N);
@@ -1953,20 +1980,17 @@ abstract contract BaseZKHonkVerifier is IVerifier {
1953
1980
  publicInputs,
1954
1981
  p.pairingPointObject,
1955
1982
  t.relationParameters.beta,
1956
- t.relationParameters.gamma /*pubInputsOffset=*/,
1957
- 1
1983
+ t.relationParameters.gamma,
1984
+ 5 // pubInputsOffset = NUM_DISABLED_ROWS_IN_SUMCHECK + NUM_ZERO_ROWS = 4 + 1
1958
1985
  );
1959
1986
 
1960
1987
  // Sumcheck
1961
- if (!verifySumcheck(p, t)) revert SumcheckFailed();
1962
-
1963
- if (!verifyShplemini(p, vk, t)) revert ShpleminiFailed();
1988
+ require(verifySumcheck(p, t), Errors.SumcheckFailed());
1989
+ require(verifyShplemini(p, vk, t), Errors.ShpleminiFailed());
1964
1990
 
1965
1991
  verified = true;
1966
1992
  }
1967
1993
 
1968
- uint256 constant PERMUTATION_ARGUMENT_VALUE_SEPARATOR = 1 << 28;
1969
-
1970
1994
  function computePublicInputDelta(
1971
1995
  bytes32[] memory publicInputs,
1972
1996
  Fr[PAIRING_POINTS_SIZE] memory pairingPointObject,
@@ -2014,7 +2038,7 @@ abstract contract BaseZKHonkVerifier is IVerifier {
2014
2038
  for (uint256 round; round < $LOG_N; ++round) {
2015
2039
  Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariate = proof.sumcheckUnivariates[round];
2016
2040
  Fr totalSum = roundUnivariate[0] + roundUnivariate[1];
2017
- if (totalSum != roundTargetSum) revert SumcheckFailed();
2041
+ require(totalSum == roundTargetSum, Errors.SumcheckFailed());
2018
2042
 
2019
2043
  Fr roundChallenge = tp.sumCheckUChallenges[round];
2020
2044
 
@@ -2037,9 +2061,10 @@ abstract contract BaseZKHonkVerifier is IVerifier {
2037
2061
  powPartialEvaluation
2038
2062
  );
2039
2063
 
2064
+ // Row-disabling polynomial: 1 - ∏_{i≥2}(1 - u_i)
2040
2065
  Fr evaluation = Fr.wrap(1);
2041
2066
  for (uint256 i = 2; i < $LOG_N; i++) {
2042
- evaluation = evaluation * tp.sumCheckUChallenges[i];
2067
+ evaluation = evaluation * (Fr.wrap(1) - tp.sumCheckUChallenges[i]);
2043
2068
  }
2044
2069
 
2045
2070
  grandHonkRelationSum = grandHonkRelationSum * (Fr.wrap(1) - evaluation) + proof.libraEvaluation * tp.libraChallenge;
@@ -2085,15 +2110,6 @@ abstract contract BaseZKHonkVerifier is IVerifier {
2085
2110
  targetSum = targetSum * numeratorValue;
2086
2111
  }
2087
2112
 
2088
- uint256 constant LIBRA_COMMITMENTS = 3;
2089
- uint256 constant LIBRA_EVALUATIONS = 4;
2090
- uint256 constant LIBRA_UNIVARIATES_LENGTH = 9;
2091
-
2092
- struct PairingInputs {
2093
- Honk.G1Point P_0;
2094
- Honk.G1Point P_1;
2095
- }
2096
-
2097
2113
  function verifyShplemini(
2098
2114
  Honk.ZKProof memory proof,
2099
2115
  Honk.VerificationKey memory vk,
@@ -2174,34 +2190,34 @@ abstract contract BaseZKHonkVerifier is IVerifier {
2174
2190
 
2175
2191
  commitments[1] = proof.geminiMaskingPoly;
2176
2192
 
2177
- commitments[2] = vk.qm;
2178
- commitments[3] = vk.qc;
2179
- commitments[4] = vk.ql;
2180
- commitments[5] = vk.qr;
2181
- commitments[6] = vk.qo;
2182
- commitments[7] = vk.q4;
2183
- commitments[8] = vk.qLookup;
2184
- commitments[9] = vk.qArith;
2185
- commitments[10] = vk.qDeltaRange;
2186
- commitments[11] = vk.qElliptic;
2187
- commitments[12] = vk.qMemory;
2188
- commitments[13] = vk.qNnf;
2189
- commitments[14] = vk.qPoseidon2External;
2190
- commitments[15] = vk.qPoseidon2Internal;
2191
- commitments[16] = vk.s1;
2192
- commitments[17] = vk.s2;
2193
- commitments[18] = vk.s3;
2194
- commitments[19] = vk.s4;
2195
- commitments[20] = vk.id1;
2196
- commitments[21] = vk.id2;
2197
- commitments[22] = vk.id3;
2198
- commitments[23] = vk.id4;
2199
- commitments[24] = vk.t1;
2200
- commitments[25] = vk.t2;
2201
- commitments[26] = vk.t3;
2202
- commitments[27] = vk.t4;
2203
- commitments[28] = vk.lagrangeFirst;
2204
- commitments[29] = vk.lagrangeLast;
2193
+ commitments[2] = vk.s1;
2194
+ commitments[3] = vk.s2;
2195
+ commitments[4] = vk.s3;
2196
+ commitments[5] = vk.s4;
2197
+ commitments[6] = vk.id1;
2198
+ commitments[7] = vk.id2;
2199
+ commitments[8] = vk.id3;
2200
+ commitments[9] = vk.id4;
2201
+ commitments[10] = vk.lagrangeFirst;
2202
+ commitments[11] = vk.lagrangeLast;
2203
+ commitments[12] = vk.qLookup;
2204
+ commitments[13] = vk.t1;
2205
+ commitments[14] = vk.t2;
2206
+ commitments[15] = vk.t3;
2207
+ commitments[16] = vk.t4;
2208
+ commitments[17] = vk.qm;
2209
+ commitments[18] = vk.qr;
2210
+ commitments[19] = vk.qo;
2211
+ commitments[20] = vk.qc;
2212
+ commitments[21] = vk.ql;
2213
+ commitments[22] = vk.q4;
2214
+ commitments[23] = vk.qArith;
2215
+ commitments[24] = vk.qDeltaRange;
2216
+ commitments[25] = vk.qElliptic;
2217
+ commitments[26] = vk.qMemory;
2218
+ commitments[27] = vk.qNnf;
2219
+ commitments[28] = vk.qPoseidon2External;
2220
+ commitments[29] = vk.qPoseidon2Internal;
2205
2221
 
2206
2222
  // Accumulate proof points
2207
2223
  commitments[30] = proof.w1;
@@ -2281,22 +2297,40 @@ abstract contract BaseZKHonkVerifier is IVerifier {
2281
2297
 
2282
2298
  boundary += $LOG_N - 1;
2283
2299
 
2284
- // Finalize the batch opening claim
2285
- mem.denominators[0] = Fr.wrap(1).div(tp.shplonkZ - tp.geminiR);
2286
- mem.denominators[1] = Fr.wrap(1).div(tp.shplonkZ - SUBGROUP_GENERATOR * tp.geminiR);
2300
+ // Denominators 1/(z - point_i) for the five opening points {r, g*r, r, 1, r}.
2301
+ mem.denominators[0] = ONE.div(tp.shplonkZ - tp.geminiR);
2302
+ mem.denominators[1] = ONE.div(tp.shplonkZ - SUBGROUP_GENERATOR * tp.geminiR);
2287
2303
  mem.denominators[2] = mem.denominators[0];
2288
- mem.denominators[3] = mem.denominators[0];
2304
+ mem.denominators[SMALL_IPA_BOUNDARY_OPENING_IDX] = ONE.div(tp.shplonkZ - ONE);
2305
+ mem.denominators[NUM_SMALL_IPA_OPENING_CLAIMS - 1] = mem.denominators[0];
2289
2306
 
2290
- mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu * tp.shplonkNu;
2291
- for (uint256 i = 0; i < LIBRA_EVALUATIONS; i++) {
2307
+ // Iterate the opening claims in three segments — the inner loops can't be merged without an extra induction
2308
+ // variable, which pushes us into stack-too-deep.
2309
+ for (uint256 i = 0; i < SMALL_IPA_BOUNDARY_OPENING_IDX; i++) {
2292
2310
  Fr scalingFactor = mem.denominators[i] * mem.batchingChallenge;
2293
2311
  mem.batchingScalars[i] = scalingFactor.neg();
2294
2312
  mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu;
2295
2313
  mem.constantTermAccumulator = mem.constantTermAccumulator + scalingFactor * proof.libraPolyEvals[i];
2296
2314
  }
2315
+
2316
+ // Boundary slot: claimed value is hardcoded 0, so no constantTermAccumulator contribution.
2317
+ {
2318
+ Fr scalingFactor = mem.denominators[SMALL_IPA_BOUNDARY_OPENING_IDX] * mem.batchingChallenge;
2319
+ mem.batchingScalars[SMALL_IPA_BOUNDARY_OPENING_IDX] = scalingFactor.neg();
2320
+ mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu;
2321
+ }
2322
+
2323
+ for (uint256 i = SMALL_IPA_BOUNDARY_OPENING_IDX + 1; i < NUM_SMALL_IPA_OPENING_CLAIMS; i++) {
2324
+ Fr scalingFactor = mem.denominators[i] * mem.batchingChallenge;
2325
+ mem.batchingScalars[i] = scalingFactor.neg();
2326
+ mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu;
2327
+ mem.constantTermAccumulator = mem.constantTermAccumulator + scalingFactor * proof.libraPolyEvals[i - 1];
2328
+ }
2329
+
2330
+ // Group per-claim batching scalars by commitment: [G], [A] (three openings), [Q].
2297
2331
  scalars[boundary] = mem.batchingScalars[0];
2298
- scalars[boundary + 1] = mem.batchingScalars[1] + mem.batchingScalars[2];
2299
- scalars[boundary + 2] = mem.batchingScalars[3];
2332
+ scalars[boundary + 1] = mem.batchingScalars[1] + mem.batchingScalars[2] + mem.batchingScalars[SMALL_IPA_BOUNDARY_OPENING_IDX];
2333
+ scalars[boundary + 2] = mem.batchingScalars[NUM_SMALL_IPA_OPENING_CLAIMS - 1];
2300
2334
 
2301
2335
  for (uint256 i = 0; i < LIBRA_COMMITMENTS; i++) {
2302
2336
  commitments[boundary++] = proof.libraCommitments[i];
@@ -2305,9 +2339,10 @@ abstract contract BaseZKHonkVerifier is IVerifier {
2305
2339
  commitments[boundary] = Honk.G1Point({ x: 1, y: 2 });
2306
2340
  scalars[boundary++] = mem.constantTermAccumulator;
2307
2341
 
2308
- if (!checkEvalsConsistency(proof.libraPolyEvals, tp.geminiR, tp.sumCheckUChallenges, proof.libraEvaluation)) {
2309
- revert ConsistencyCheckFailed();
2310
- }
2342
+ require(
2343
+ checkEvalsConsistency(proof.libraPolyEvals, tp.geminiR, tp.sumCheckUChallenges, proof.libraEvaluation),
2344
+ Errors.ConsistencyCheckFailed()
2345
+ );
2311
2346
 
2312
2347
  Honk.G1Point memory quotient_commitment = proof.kzgQuotient;
2313
2348
 
@@ -2318,31 +2353,23 @@ abstract contract BaseZKHonkVerifier is IVerifier {
2318
2353
  pair.P_0 = batchMul(commitments, scalars);
2319
2354
  pair.P_1 = negateInplace(quotient_commitment);
2320
2355
 
2321
- // Aggregate pairing points
2322
- Fr recursionSeparator = generateRecursionSeparator(proof.pairingPointObject, pair.P_0, pair.P_1);
2323
- (Honk.G1Point memory P_0_other, Honk.G1Point memory P_1_other) = convertPairingPointsToG1(proof.pairingPointObject);
2356
+ // Aggregate pairing points (skip if default/infinity — no recursive verification occurred)
2357
+ if (!arePairingPointsDefault(proof.pairingPointObject)) {
2358
+ Fr recursionSeparator = generateRecursionSeparator(proof.pairingPointObject, pair.P_0, pair.P_1);
2359
+ (Honk.G1Point memory P_0_other, Honk.G1Point memory P_1_other) = convertPairingPointsToG1(proof.pairingPointObject);
2324
2360
 
2325
- // Validate the points from the proof are on the curve
2326
- validateOnCurve(P_0_other);
2327
- validateOnCurve(P_1_other);
2361
+ // Validate the points from the proof are on the curve
2362
+ rejectPointAtInfinity(P_0_other);
2363
+ rejectPointAtInfinity(P_1_other);
2328
2364
 
2329
- // accumulate with aggregate points in proof
2330
- pair.P_0 = mulWithSeperator(pair.P_0, P_0_other, recursionSeparator);
2331
- pair.P_1 = mulWithSeperator(pair.P_1, P_1_other, recursionSeparator);
2365
+ // accumulate with aggregate points in proof
2366
+ pair.P_0 = mulWithSeperator(pair.P_0, P_0_other, recursionSeparator);
2367
+ pair.P_1 = mulWithSeperator(pair.P_1, P_1_other, recursionSeparator);
2368
+ }
2332
2369
 
2333
2370
  return pairing(pair.P_0, pair.P_1);
2334
2371
  }
2335
2372
 
2336
- struct SmallSubgroupIpaIntermediates {
2337
- Fr[SUBGROUP_SIZE] challengePolyLagrange;
2338
- Fr challengePolyEval;
2339
- Fr lagrangeFirst;
2340
- Fr lagrangeLast;
2341
- Fr rootPower;
2342
- Fr[SUBGROUP_SIZE] denominators; // this has to disappear
2343
- Fr diff;
2344
- }
2345
-
2346
2373
  function checkEvalsConsistency(
2347
2374
  Fr[LIBRA_EVALUATIONS] memory libraPolyEvals,
2348
2375
  Fr geminiR,
@@ -2351,9 +2378,7 @@ abstract contract BaseZKHonkVerifier is IVerifier {
2351
2378
  ) internal view returns (bool check) {
2352
2379
  Fr one = Fr.wrap(1);
2353
2380
  Fr vanishingPolyEval = geminiR.pow(SUBGROUP_SIZE) - one;
2354
- if (vanishingPolyEval == Fr.wrap(0)) {
2355
- revert GeminiChallengeInSubgroup();
2356
- }
2381
+ require(vanishingPolyEval != Fr.wrap(0), Errors.GeminiChallengeInSubgroup());
2357
2382
 
2358
2383
  SmallSubgroupIpaIntermediates memory mem;
2359
2384
  mem.challengePolyLagrange[0] = one;
@@ -2394,10 +2419,10 @@ abstract contract BaseZKHonkVerifier is IVerifier {
2394
2419
  function batchMul(Honk.G1Point[] memory base, Fr[] memory scalars) internal view returns (Honk.G1Point memory result) {
2395
2420
  uint256 limit = $MSMSize;
2396
2421
 
2397
- // Validate all points are on the curve
2398
- for (uint256 i = 0; i < limit; ++i) {
2399
- validateOnCurve(base[i]);
2400
- }
2422
+ // Identity bases are accepted: VK selector/table polys may be identically zero,
2423
+ // and the ecAdd/ecMul precompiles treat (0,0) as the additive identity per EIP-196.
2424
+ // Soundness against an attacker substituting (0,0) for a non-zero commitment is
2425
+ // upheld by sumcheck/Shplemini, which would fail on inconsistent evaluations.
2401
2426
 
2402
2427
  bool success = true;
2403
2428
  assembly {
@@ -2426,11 +2451,42 @@ abstract contract BaseZKHonkVerifier is IVerifier {
2426
2451
  mstore(add(result, 0x20), mload(add(free, 0x20)))
2427
2452
  }
2428
2453
 
2429
- require(success, ShpleminiFailed());
2454
+ require(success, Errors.ShpleminiFailed());
2430
2455
  }
2456
+
2457
+ // Calculate proof size based on log_n (matching UltraKeccakZKFlavor formula)
2458
+ function calculateProofSize(uint256 logN) internal pure returns (uint256) {
2459
+ // Witness and Libra commitments
2460
+ uint256 proofLength = NUM_WITNESS_ENTITIES * NUM_ELEMENTS_COMM; // witness commitments
2461
+ proofLength += NUM_ELEMENTS_COMM * 3; // Libra concat, grand sum, quotient comms
2462
+
2463
+ // Sumcheck
2464
+ proofLength += logN * ZK_BATCHED_RELATION_PARTIAL_LENGTH * NUM_ELEMENTS_FR; // sumcheck univariates
2465
+ proofLength += NUMBER_OF_ENTITIES_ZK * NUM_ELEMENTS_FR; // sumcheck evaluations
2466
+
2467
+ // Libra and Gemini
2468
+ proofLength += NUM_ELEMENTS_FR * 2; // Libra sum, claimed eval
2469
+ proofLength += logN * NUM_ELEMENTS_FR; // Gemini a evaluations
2470
+ proofLength += NUM_LIBRA_EVALUATIONS * NUM_ELEMENTS_FR; // libra evaluations
2471
+
2472
+ // PCS commitments
2473
+ proofLength += (logN - 1) * NUM_ELEMENTS_COMM; // Gemini Fold commitments
2474
+ proofLength += NUM_ELEMENTS_COMM * 2; // Shplonk Q and KZG W commitments
2475
+
2476
+ // Pairing points
2477
+ proofLength += PAIRING_POINTS_SIZE; // pairing inputs carried on public inputs
2478
+
2479
+ return proofLength * 32;
2480
+ }
2481
+
2482
+ function loadVerificationKey() internal pure virtual returns (Honk.VerificationKey memory);
2431
2483
  }
2432
2484
 
2433
2485
  contract HonkVerifier is BaseZKHonkVerifier(N, LOG_N, VK_HASH, NUMBER_OF_PUBLIC_INPUTS) {
2486
+ constructor() {
2487
+ validateVerificationKey(HonkVerificationKey.loadVerificationKey());
2488
+ }
2489
+
2434
2490
  function loadVerificationKey() internal pure override returns (Honk.VerificationKey memory) {
2435
2491
  return HonkVerificationKey.loadVerificationKey();
2436
2492
  }