Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
poseidon2_perm_impl.hpp
Go to the documentation of this file.
1// Hand-tuned implementation of the poseidon2_perm relation to make compile times tolerable
2//
3// Replaces the auto-generated `generated/relations/poseidon2_perm_impl.hpp`.
4//
5// Subrelation layout:
6// 0 boolean selector
7// 1..4 initial external-matrix layer on the input state
8// 5..20 4 initial full rounds (4 subrelations each)
9// 21..79 56 partial rounds, quad-compressed (see poseidon2_quad_params.hpp)
10// 80..95 4 final full rounds (4 subrelations each)
11// 96..99 outputs
12//
13// Note on reading constants
14// Round constants and the internal-matrix diagonal D_i are read directly as native `bb::fr` from the
15// shared `constexpr` tables in `poseidon2_params.hpp` / `poseidon2_quad_params.hpp`.
16//
17// While the underlying fields are the same, the proving build uses `FF == bb::fr` and the recursive build uses `FF ==
18// stdlib::field_t`. This difference in c++ type can subtly cause the recursive build to fail to compile if a native
19// constant is on the left of a multiply, because `bb::fr * stdlib::field_t` has no overload (field_t has no conversion
20// operator to bb::fr but there is an implicit conversion the other way).
21//
22// This really only impacts the Poseidon2QuadBn254Params multiplications since the standard round constants are used
23// additively. But this does mean we have to be careful to keep the native-loaded constants on the right of multiplies,
24// otherwise we get a compile error in the recursive build.
25#pragma once
26
27#include <array>
34
35namespace bb::avm2 {
36
37template <typename FF_>
38template <typename ContainerOverSubrelations, typename AllEntities>
39void optimized_poseidon2_permImpl<FF_>::accumulate(ContainerOverSubrelations& evals,
40 const AllEntities& in,
41 [[maybe_unused]] const RelationParameters<FF_>&,
42 [[maybe_unused]] const FF_& scaling_factor)
43{
44 // Read constants natively; see the file header for the operand-order rule that keeps the diagonal
45 // multiplies compiling under the recursive flavor.
48 using C = ColumnAndShifts;
49
50 //=========================================
51 // Helpers (state-update + constraint emission)
52 //=========================================
53 // Add the round constants to every lane.
54 const auto add_round_constant = []<typename T, typename U>(std::array<T, 4>& state, const std::array<U, 4>& rc) {
55 for (size_t k = 0; k < 4; ++k) {
56 state[k] += rc[k];
57 }
58 };
59
60 const auto power_of_5 = []<typename T>(const T& x) {
61 auto acc = x.sqr();
62 acc = acc.sqr();
63 return acc * x;
64 };
65
66 // Full-round S-box: x -> x^5 on every lane.
67 const auto s_box = [&]<typename T>(std::array<T, 4>& state) {
68 for (auto& x : state) {
69 x = power_of_5(x);
70 }
71 };
72
73 // External (MDS) matrix layer applied to `input`, constrained against the four witness output
74 // columns `out` at subrelations [Index, Index + 4). Computes the four Poseidon2 MDS outputs
75 // (t4..t7) from the input lanes and pins each witness output column to its value.
76 const auto constrain_external_matrix = [&]<size_t Index, typename T>(const std::array<T, 4>& input,
77 const std::array<C, 4>& out) {
78 const auto t0 = input[0] + input[1];
79 const auto t1 = input[2] + input[3];
80 const auto t2 = FF(2) * input[1] + t1;
81 const auto t3 = FF(2) * input[3] + t0;
82 const auto t4 = FF(4) * t1 + t3;
83 const auto t5 = FF(4) * t0 + t2;
84 const auto t6 = t3 + t5;
85 const auto t7 = t2 + t4;
86 {
87 using View = typename std::tuple_element_t<Index + 0, ContainerOverSubrelations>::View;
88 auto tmp =
89 static_cast<View>(in.get(C::poseidon2_perm_sel)) * (static_cast<View>(in.get(out[0])) - CView(t4));
90 std::get<Index + 0>(evals) += (tmp * scaling_factor);
91 }
92 {
93 using View = typename std::tuple_element_t<Index + 1, ContainerOverSubrelations>::View;
94 auto tmp =
95 static_cast<View>(in.get(C::poseidon2_perm_sel)) * (static_cast<View>(in.get(out[1])) - CView(t5));
96 std::get<Index + 1>(evals) += (tmp * scaling_factor);
97 }
98 {
99 using View = typename std::tuple_element_t<Index + 2, ContainerOverSubrelations>::View;
100 auto tmp =
101 static_cast<View>(in.get(C::poseidon2_perm_sel)) * (static_cast<View>(in.get(out[2])) - CView(t6));
102 std::get<Index + 2>(evals) += (tmp * scaling_factor);
103 }
104 {
105 using View = typename std::tuple_element_t<Index + 3, ContainerOverSubrelations>::View;
106 auto tmp =
107 static_cast<View>(in.get(C::poseidon2_perm_sel)) * (static_cast<View>(in.get(out[3])) - CView(t7));
108 std::get<Index + 3>(evals) += (tmp * scaling_factor);
109 }
110 };
111
112 //=========================================
113 // Subrelation 0: selector booleanity
114 //=========================================
115 {
116 using View = typename std::tuple_element_t<0, ContainerOverSubrelations>::View;
117 auto tmp = static_cast<View>(in.get(C::poseidon2_perm_sel)) *
118 (FF(1) - static_cast<View>(in.get(C::poseidon2_perm_sel)));
119 std::get<0>(evals) += (tmp * scaling_factor);
120 }
121
122 //=========================================
123 // Subrelations 1..4: initial external matrix on the input state
124 //=========================================
125 {
126 // Initial state is the input
127 const std::array input = {
128 in.get(C::poseidon2_perm_a_0),
129 in.get(C::poseidon2_perm_a_1),
130 in.get(C::poseidon2_perm_a_2),
131 in.get(C::poseidon2_perm_a_3),
132 };
133 constexpr std::array<C, 4> out = {
134 C::poseidon2_perm_EXT_LAYER_4,
135 C::poseidon2_perm_EXT_LAYER_5,
136 C::poseidon2_perm_EXT_LAYER_6,
137 C::poseidon2_perm_EXT_LAYER_7,
138 };
139 // 1 here is the index offset of the first subrelation in this block (the initial external-matrix layer is
140 // subrelations 1..4).
141 constrain_external_matrix.template operator()<1>(input, out);
142 }
143
144 //=========================================
145 // Subrelations 5..20: 4 initial full rounds
146 //=========================================
147 // The permutation rounds start at subrelation index 5
148 constexpr size_t START_RELATION_OF_PERM = 5;
149 // The initial 4 full rounds input and output columns
150 constexpr std::array<std::array<C, 4>, 4> initial_input_cols = { {
151 { C::poseidon2_perm_EXT_LAYER_6,
152 C::poseidon2_perm_EXT_LAYER_5,
153 C::poseidon2_perm_EXT_LAYER_7,
154 C::poseidon2_perm_EXT_LAYER_4 },
155 { C::poseidon2_perm_T_0_6, C::poseidon2_perm_T_0_5, C::poseidon2_perm_T_0_7, C::poseidon2_perm_T_0_4 },
156 { C::poseidon2_perm_T_1_6, C::poseidon2_perm_T_1_5, C::poseidon2_perm_T_1_7, C::poseidon2_perm_T_1_4 },
157 { C::poseidon2_perm_T_2_6, C::poseidon2_perm_T_2_5, C::poseidon2_perm_T_2_7, C::poseidon2_perm_T_2_4 },
158 } };
159 constexpr std::array<std::array<C, 4>, 4> initial_out_cols = { {
160 { C::poseidon2_perm_T_0_4, C::poseidon2_perm_T_0_5, C::poseidon2_perm_T_0_6, C::poseidon2_perm_T_0_7 },
161 { C::poseidon2_perm_T_1_4, C::poseidon2_perm_T_1_5, C::poseidon2_perm_T_1_6, C::poseidon2_perm_T_1_7 },
162 { C::poseidon2_perm_T_2_4, C::poseidon2_perm_T_2_5, C::poseidon2_perm_T_2_6, C::poseidon2_perm_T_2_7 },
163 { C::poseidon2_perm_T_3_4, C::poseidon2_perm_T_3_5, C::poseidon2_perm_T_3_6, C::poseidon2_perm_T_3_7 },
164 } };
165 // Execute the full rounds: ARK -> S-box -> external matrix.
166 bb::constexpr_for<0, 4, 1>([&]<size_t I>() {
167 constexpr size_t sub_index_offset = START_RELATION_OF_PERM + (4 * I);
168 std::array state = {
169 in.get(initial_input_cols[I][0]),
170 in.get(initial_input_cols[I][1]),
171 in.get(initial_input_cols[I][2]),
172 in.get(initial_input_cols[I][3]),
173 };
174 add_round_constant(state, PParams::round_constants[I]);
175 s_box(state);
176 constrain_external_matrix.template operator()<sub_index_offset>(state, initial_out_cols[I]);
177 });
178
179 //=========================================
180 // Subrelations 21..79: 56 partial rounds (K=4 quad-compressed chain)
181 //=========================================
182 // The 56 internal rounds constrain witnesses on state[0] only; the other three lanes evolve linearly so are
183 // handled by intermediate polys. We unroll the S-boxed lane into ALPHA and carry the three linear lanes as (X, Y,
184 // Z), so a single chain reproduces all 56 rounds. The diagonal coefficients are D_1..D_4 from the quad params.
185 //
186 // The chain follows: ARK_n = B_{n-1}_0 + C_n_0; ALPHA_n = ARK_n^5; (X, Y, Z) update.
187 // Stored as arrays with ALPHA[i] := ALPHA_{i+1}, X[i] := X_{i+1}, Y[i] := Y_{i+1}, Z[i] := Z_{i+1}.
188 // Read from the previous full round output
189 const auto poseidon2_perm_B_3_0 = in.get(C::poseidon2_perm_T_3_6);
190 const auto poseidon2_perm_B_3_1 = in.get(C::poseidon2_perm_T_3_5);
191 const auto poseidon2_perm_B_3_2 = in.get(C::poseidon2_perm_T_3_7);
192 const auto poseidon2_perm_B_3_3 = in.get(C::poseidon2_perm_T_3_4);
193 constexpr std::array<C, 56> B_partial_cols = {
194 C::poseidon2_perm_B_4_0, C::poseidon2_perm_B_5_0, C::poseidon2_perm_B_6_0, C::poseidon2_perm_B_7_0,
195 C::poseidon2_perm_B_8_0, C::poseidon2_perm_B_9_0, C::poseidon2_perm_B_10_0, C::poseidon2_perm_B_11_0,
196 C::poseidon2_perm_B_12_0, C::poseidon2_perm_B_13_0, C::poseidon2_perm_B_14_0, C::poseidon2_perm_B_15_0,
197 C::poseidon2_perm_B_16_0, C::poseidon2_perm_B_17_0, C::poseidon2_perm_B_18_0, C::poseidon2_perm_B_19_0,
198 C::poseidon2_perm_B_20_0, C::poseidon2_perm_B_21_0, C::poseidon2_perm_B_22_0, C::poseidon2_perm_B_23_0,
199 C::poseidon2_perm_B_24_0, C::poseidon2_perm_B_25_0, C::poseidon2_perm_B_26_0, C::poseidon2_perm_B_27_0,
200 C::poseidon2_perm_B_28_0, C::poseidon2_perm_B_29_0, C::poseidon2_perm_B_30_0, C::poseidon2_perm_B_31_0,
201 C::poseidon2_perm_B_32_0, C::poseidon2_perm_B_33_0, C::poseidon2_perm_B_34_0, C::poseidon2_perm_B_35_0,
202 C::poseidon2_perm_B_36_0, C::poseidon2_perm_B_37_0, C::poseidon2_perm_B_38_0, C::poseidon2_perm_B_39_0,
203 C::poseidon2_perm_B_40_0, C::poseidon2_perm_B_41_0, C::poseidon2_perm_B_42_0, C::poseidon2_perm_B_43_0,
204 C::poseidon2_perm_B_44_0, C::poseidon2_perm_B_45_0, C::poseidon2_perm_B_46_0, C::poseidon2_perm_B_47_0,
205 C::poseidon2_perm_B_48_0, C::poseidon2_perm_B_49_0, C::poseidon2_perm_B_50_0, C::poseidon2_perm_B_51_0,
206 C::poseidon2_perm_B_52_0, C::poseidon2_perm_B_53_0, C::poseidon2_perm_B_54_0, C::poseidon2_perm_B_55_0,
207 C::poseidon2_perm_B_56_0, C::poseidon2_perm_B_57_0, C::poseidon2_perm_B_58_0, C::poseidon2_perm_B_59_0
208 };
209 // This is the type of the element that is "chained" through the 56 partial rounds.
210 // Its type is the result of a univariate (for the prover) or an FF (for the verifier) multiplied by the
211 // diagonal constant D_2.
212 using ChainElem = std::decay_t<decltype(in.get(C::poseidon2_perm_T_3_5) * PQuad::D2)>;
213 std::array<ChainElem, 56> alphas_arr{};
217 // The first partial round reads the previous full round's output (B_3_0..3) and computes the first ALPHA/X/Y/Z.
218 {
219 const auto ark = poseidon2_perm_B_3_0 + PParams::round_constants[4][0];
220 alphas_arr[0] = power_of_5(ark);
221 xs_arrs[0] = poseidon2_perm_B_3_1 * PQuad::D2 + poseidon2_perm_B_3_2 + poseidon2_perm_B_3_3 + alphas_arr[0];
222 ys_arrs[0] = poseidon2_perm_B_3_1 + poseidon2_perm_B_3_2 * PQuad::D3 + poseidon2_perm_B_3_3 + alphas_arr[0];
223 zs_arrs[0] = poseidon2_perm_B_3_1 + poseidon2_perm_B_3_2 + poseidon2_perm_B_3_3 * PQuad::D4 + alphas_arr[0];
224 }
225 // Compute the remaining 55 partial rounds in a single chain.
226 bb::constexpr_for<1, 56, 1>([&]<size_t i>() {
227 const auto ark = in.get(B_partial_cols[i - 1]) + PParams::round_constants[i + 4][0];
228 alphas_arr[i] = power_of_5(ark);
229 if constexpr (i < 55) {
230 xs_arrs[i] = xs_arrs[i - 1] * PQuad::D2 + ys_arrs[i - 1] + zs_arrs[i - 1] + alphas_arr[i];
231 ys_arrs[i] = xs_arrs[i - 1] + ys_arrs[i - 1] * PQuad::D3 + zs_arrs[i - 1] + alphas_arr[i];
232 zs_arrs[i] = xs_arrs[i - 1] + ys_arrs[i - 1] + zs_arrs[i - 1] * PQuad::D4 + alphas_arr[i];
233 }
234 });
235 // Assign the witnesses to the subrelations in order, using the pre-computed ALPHA/X/Y/Z arrays.
236 // Subrelations 21..76: each partial round sets each B_n_0 column to D_1 * ALPHA + (carried lanes).
237 bb::constexpr_for<0, 56, 1>([&]<size_t i>() {
238 constexpr size_t PARTIAL_ROUND_SUB_INDEX = 21 + i;
239 using View = typename std::tuple_element_t<PARTIAL_ROUND_SUB_INDEX, ContainerOverSubrelations>::View;
240 if constexpr (i == 0) {
241 auto tmp = static_cast<View>(in.get(C::poseidon2_perm_sel)) *
242 (static_cast<View>(in.get(B_partial_cols[i])) -
243 (CView(PQuad::D1) * CView(alphas_arr[i]) + CView(poseidon2_perm_B_3_1) +
244 CView(poseidon2_perm_B_3_2) + CView(poseidon2_perm_B_3_3)));
245 std::get<PARTIAL_ROUND_SUB_INDEX>(evals) += (tmp * scaling_factor);
246 } else {
247 auto tmp = static_cast<View>(in.get(C::poseidon2_perm_sel)) *
248 (static_cast<View>(in.get(B_partial_cols[i])) -
249 (CView(PQuad::D1) * CView(alphas_arr[i]) + CView(xs_arrs[i - 1]) + CView(ys_arrs[i - 1]) +
250 CView(zs_arrs[i - 1])));
251 std::get<PARTIAL_ROUND_SUB_INDEX>(evals) += (tmp * scaling_factor);
252 }
253 });
254 // Subrelations 77..79: sets the remaining three lanes of the final partial state (B_59_1/2/3).
255 {
256 using View = typename std::tuple_element_t<77, ContainerOverSubrelations>::View;
257 auto tmp =
258 static_cast<View>(in.get(C::poseidon2_perm_sel)) *
259 (static_cast<View>(in.get(C::poseidon2_perm_B_59_1)) -
260 (CView(PQuad::D2) * CView(xs_arrs[54]) + CView(ys_arrs[54]) + CView(zs_arrs[54]) + CView(alphas_arr[55])));
261 std::get<77>(evals) += (tmp * scaling_factor);
262 }
263 {
264 using View = typename std::tuple_element_t<78, ContainerOverSubrelations>::View;
265 auto tmp =
266 static_cast<View>(in.get(C::poseidon2_perm_sel)) *
267 (static_cast<View>(in.get(C::poseidon2_perm_B_59_2)) -
268 (CView(xs_arrs[54]) + CView(PQuad::D3) * CView(ys_arrs[54]) + CView(zs_arrs[54]) + CView(alphas_arr[55])));
269 std::get<78>(evals) += (tmp * scaling_factor);
270 }
271 {
272 using View = typename std::tuple_element_t<79, ContainerOverSubrelations>::View;
273 auto tmp =
274 static_cast<View>(in.get(C::poseidon2_perm_sel)) *
275 (static_cast<View>(in.get(C::poseidon2_perm_B_59_3)) -
276 (CView(xs_arrs[54]) + CView(ys_arrs[54]) + CView(PQuad::D4) * CView(zs_arrs[54]) + CView(alphas_arr[55])));
277 std::get<79>(evals) += (tmp * scaling_factor);
278 }
279
280 //=========================================
281 // Subrelations 80..95: 4 final full rounds
282 //=========================================
283 // Round 60 reads the partial chain's terminal state {B_59_0..3} in order; rounds 61..63 read the
284 // previous round's outputs permuted as {out_2, out_1, out_3, out_0}.
285 constexpr std::array<std::array<C, 4>, 4> final_in_state = { {
286 { C::poseidon2_perm_B_59_0, C::poseidon2_perm_B_59_1, C::poseidon2_perm_B_59_2, C::poseidon2_perm_B_59_3 },
287 { C::poseidon2_perm_T_60_6, C::poseidon2_perm_T_60_5, C::poseidon2_perm_T_60_7, C::poseidon2_perm_T_60_4 },
288 { C::poseidon2_perm_T_61_6, C::poseidon2_perm_T_61_5, C::poseidon2_perm_T_61_7, C::poseidon2_perm_T_61_4 },
289 { C::poseidon2_perm_T_62_6, C::poseidon2_perm_T_62_5, C::poseidon2_perm_T_62_7, C::poseidon2_perm_T_62_4 },
290 } };
291 constexpr std::array<std::array<C, 4>, 4> final_out = { {
292 { C::poseidon2_perm_T_60_4, C::poseidon2_perm_T_60_5, C::poseidon2_perm_T_60_6, C::poseidon2_perm_T_60_7 },
293 { C::poseidon2_perm_T_61_4, C::poseidon2_perm_T_61_5, C::poseidon2_perm_T_61_6, C::poseidon2_perm_T_61_7 },
294 { C::poseidon2_perm_T_62_4, C::poseidon2_perm_T_62_5, C::poseidon2_perm_T_62_6, C::poseidon2_perm_T_62_7 },
295 { C::poseidon2_perm_T_63_4, C::poseidon2_perm_T_63_5, C::poseidon2_perm_T_63_6, C::poseidon2_perm_T_63_7 },
296 } };
297 bb::constexpr_for<0, 4, 1>([&]<size_t I>() {
298 constexpr size_t base = 80 + (4 * I);
299 std::array state = {
300 in.get(final_in_state[I][0]),
301 in.get(final_in_state[I][1]),
302 in.get(final_in_state[I][2]),
303 in.get(final_in_state[I][3]),
304 };
305 add_round_constant(state, PParams::round_constants[60 + I]);
306 s_box(state);
307 constrain_external_matrix.template operator()<base>(state, final_out[I]);
308 });
309
310 //=========================================
311 // Subrelations 96..99: outputs
312 //=========================================
313 // The output b_k equals the final permuted state {T_63_6, T_63_5, T_63_7, T_63_4}.
314 constexpr std::array<C, 4> output_cols = {
315 C::poseidon2_perm_b_0,
316 C::poseidon2_perm_b_1,
317 C::poseidon2_perm_b_2,
318 C::poseidon2_perm_b_3,
319 };
320 constexpr std::array<C, 4> final_state_cols = {
321 C::poseidon2_perm_T_63_6,
322 C::poseidon2_perm_T_63_5,
323 C::poseidon2_perm_T_63_7,
324 C::poseidon2_perm_T_63_4,
325 };
326 bb::constexpr_for<0, 4, 1>([&]<size_t I>() {
327 constexpr size_t sub_idx = 96 + I;
328 using View = typename std::tuple_element_t<sub_idx, ContainerOverSubrelations>::View;
329 auto tmp = static_cast<View>(in.get(C::poseidon2_perm_sel)) *
330 (static_cast<View>(in.get(output_cols[I])) - static_cast<View>(in.get(final_state_cols[I])));
331 std::get<sub_idx>(evals) += (tmp * scaling_factor);
332 });
333}
334
335} // namespace bb::avm2
static void accumulate(ContainerOverSubrelations &evals, const AllEntities &in, const RelationParameters< FF > &, const FF &scaling_factor)
AvmFlavorSettings::FF FF
Definition field.hpp:10
ColumnAndShifts
Definition columns.hpp:35
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
#define CView(v)
bb::VectorAffineElementPushSpan< BaseParams > out
Container for parameters used by the grand product (permutation, lookup) Honk relations.