Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
poseidon2_permutation.cpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: not started, auditors: [], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
8
10
11namespace bb::stdlib {
12namespace {
13
14template <typename Builder>
15void materialize_constants_for_initial_layer(Builder* builder, typename Poseidon2Permutation<Builder>::State& state)
16{
17 // The Mega initial-external custom gate records its four inputs by witness index. A constant field_t has no
18 // witness index until it is put into the builder's constant table, while the Ultra six-gate computation below can
19 // use constant field_t values directly.
20 for (auto& state_limb : state) {
21 if (state_limb.is_constant()) {
22 state_limb =
23 field_t<Builder>::from_witness_index(builder, builder->put_constant_variable(state_limb.get_value()));
24 }
25 }
26}
27
28template <typename Builder>
29void sync_native_state_from_state(typename Poseidon2Permutation<Builder>::NativeState& native_state,
30 const typename Poseidon2Permutation<Builder>::State& state)
31{
32 for (size_t i = 0; i < Poseidon2Permutation<Builder>::t; ++i) {
33 native_state[i] = state[i].get_value();
34 }
35}
36
37template <typename Builder>
38void apply_external_rounds(Builder* builder,
39 typename Poseidon2Permutation<Builder>::State& current_state,
40 typename Poseidon2Permutation<Builder>::NativeState& current_native_state,
41 const size_t begin,
42 const size_t end)
43{
44 using Permutation = Poseidon2Permutation<Builder>;
45 using FF = typename Permutation::FF;
46 using Witness = witness_t<Builder>;
47
48 for (size_t i = begin; i < end; ++i) {
49 poseidon2_external_gate_<FF> in{ current_state[0].get_witness_index(),
50 current_state[1].get_witness_index(),
51 current_state[2].get_witness_index(),
52 current_state[3].get_witness_index(),
53 i };
54 builder->create_poseidon2_external_gate(in);
55 Permutation::NativePermutation::add_round_constants(current_native_state, Permutation::round_constants[i]);
56 Permutation::NativePermutation::apply_sbox(current_native_state);
58 for (size_t j = 0; j < Permutation::t; ++j) {
59 current_state[j] = Witness(builder, current_native_state[j]);
60 }
61 }
62}
63
64template <typename Builder>
65void apply_standard_internal_rounds(Builder* builder,
66 typename Poseidon2Permutation<Builder>::State& current_state,
67 typename Poseidon2Permutation<Builder>::NativeState& current_native_state,
68 const size_t rounds_f_beginning,
69 const size_t p_end)
70{
71 using Permutation = Poseidon2Permutation<Builder>;
72 using Witness = witness_t<Builder>;
73
74 for (size_t i = rounds_f_beginning; i < p_end; ++i) {
75 poseidon2_internal_gate_<typename Permutation::FF> in{ current_state[0].get_witness_index(),
76 current_state[1].get_witness_index(),
77 current_state[2].get_witness_index(),
78 current_state[3].get_witness_index(),
79 i };
80 builder->create_poseidon2_internal_gate(in);
81 current_native_state[0] += Permutation::round_constants[i][0];
82 Permutation::NativePermutation::apply_single_sbox(current_native_state[0]);
83 Permutation::NativePermutation::matrix_multiplication_internal(current_native_state);
84 for (size_t j = 0; j < Permutation::t; ++j) {
85 current_state[j] = Witness(builder, current_native_state[j]);
86 }
87 }
88 Permutation::propagate_current_state_to_next_row(builder, current_state, builder->blocks.poseidon2_internal);
89}
90
91void apply_mega_internal_rounds(MegaCircuitBuilder* builder,
93 typename Poseidon2Permutation<MegaCircuitBuilder>::NativeState& current_native_state,
94 const size_t rounds_f_beginning)
95{
96 using Permutation = Poseidon2Permutation<MegaCircuitBuilder>;
97 using FF = typename Permutation::FF;
98 using NativeState = typename Permutation::NativeState;
99 using Witness = witness_t<MegaCircuitBuilder>;
100
101 // K=4 compressed encoding: w_l, w_r, w_o, w_4 = state[0] at rounds 4i+0, 4i+1, 4i+2, 4i+3.
102 // (s_1, s_2, s_3) at row-start are derived inside the relation via a 3x3 Vandermonde solve.
103 static_assert(Permutation::rounds_p % 4 == 0);
104 constexpr size_t num_quad_rows = Permutation::rounds_p / 4; // 14 rows for rounds_p = 56
105
106 // Entry transition row (standard encoding): its wires are the first external group's output state,
107 // pinned by that group's last external round relation via w_shift (the rows are contiguous in the
108 // shared `poseidon2` block). The relation forces the first compressed row's
109 // (w_r_shift, w_o_shift, w_4_shift) to state[0] at rounds start+1, +2, +3.
110 {
111 poseidon2_transition_entry_gate_<FF> in{
112 current_state[0].get_witness_index(),
113 current_state[1].get_witness_index(),
114 current_state[2].get_witness_index(),
115 current_state[3].get_witness_index(),
116 rounds_f_beginning,
117 };
118 builder->create_poseidon2_transition_entry_gate(in);
119 }
120
121 auto advance_internal_round = [](NativeState& state, const FF& round_constant) {
122 state[0] += round_constant;
123 Permutation::NativePermutation::apply_single_sbox(state[0]);
124 Permutation::NativePermutation::matrix_multiplication_internal(state);
125 };
126
127 // Helper: emit one K=4 compressed row (interior or terminal) and advance `current_state`
128 // by 4 internal rounds. The row wires are state[0] at rounds start, start+1, start+2, start+3.
129 auto emit_quad_row = [&](size_t quad_idx, bool is_terminal) {
130 const size_t start = rounds_f_beginning + (4 * quad_idx);
131 const size_t next_start = start + 4; // ignored on terminal
132
133 NativeState state_after_1 = current_native_state;
134 advance_internal_round(state_after_1, Permutation::round_constants[start + 0][0]);
135 auto s0_at_1 = Witness(builder, state_after_1[0]);
136
137 NativeState state_after_2 = state_after_1;
138 advance_internal_round(state_after_2, Permutation::round_constants[start + 1][0]);
139 auto s0_at_2 = Witness(builder, state_after_2[0]);
140
141 NativeState state_after_3 = state_after_2;
142 advance_internal_round(state_after_3, Permutation::round_constants[start + 2][0]);
143 auto s0_at_3 = Witness(builder, state_after_3[0]);
144
145 poseidon2_quad_internal_gate_<FF> in{
146 current_state[0].get_witness_index(), // state[0] at round start
147 s0_at_1.witness_index, // state[0] at round start+1
148 s0_at_2.witness_index, // state[0] at round start+2
149 s0_at_3.witness_index, // state[0] at round start+3
150 start,
151 next_start,
152 is_terminal,
153 };
154 builder->create_poseidon2_quad_internal_gate(in);
155
156 // Advance native state by the 4th round to land on state at round start+4.
157 current_native_state = state_after_3;
158 advance_internal_round(current_native_state, Permutation::round_constants[start + 3][0]);
159
160 // The next non-terminal compressed row only consumes state[0] at round start+4. The remaining limbs are
161 // derived inside the relation and do not need witnesses until the terminal row bridges back to the
162 // standard encoding consumed by the final external rounds.
163 current_state[0] = Witness(builder, current_native_state[0]);
164 if (is_terminal) {
165 for (size_t j = 1; j < Permutation::t; ++j) {
166 current_state[j] = Witness(builder, current_native_state[j]);
167 }
168 }
169 };
170
171 // 13 interior compressed rows (covering rounds 0..51 relative)
172 for (size_t q = 0; q < num_quad_rows - 1; ++q) {
173 emit_quad_row(q, /*is_terminal=*/false);
174 }
175 // 1 terminal compressed row (covering rounds 52..55 relative)
176 emit_quad_row(num_quad_rows - 1, /*is_terminal=*/true);
177}
178
179} // namespace
180
181template <typename Builder>
184{
185 State current_state(input);
186 NativeState current_native_state;
187
188 matrix_multiplication_external(current_state);
189 sync_native_state_from_state<Builder>(current_native_state, current_state);
190
191 // First set of external rounds
192 constexpr size_t rounds_f_beginning = rounds_f / 2;
193 apply_external_rounds(builder, current_state, current_native_state, /*begin=*/0, /*end=*/rounds_f_beginning);
194
195 // Ultra needs an explicit landing row for the first external group's output. On Mega every poseidon2 gate lives
196 // in the single `poseidon2` block, so the transition-entry row emitted next is the external relation's
197 // w_shift target directly -- no separate propagate row.
198 if constexpr (!IsMegaBuilder<Builder>) {
199 propagate_current_state_to_next_row(builder, current_state, builder->blocks.poseidon2_external);
200 }
201
202 // Internal rounds: Mega uses a K=4 compressed block; Ultra keeps the standard one-round layout.
203 const size_t p_end = rounds_f_beginning + rounds_p;
204 if constexpr (IsMegaBuilder<Builder>) {
205 apply_mega_internal_rounds(builder, current_state, current_native_state, rounds_f_beginning);
206 } else {
207 apply_standard_internal_rounds(builder, current_state, current_native_state, rounds_f_beginning, p_end);
208 }
209
210 // Remaining external rounds
211 apply_external_rounds(builder, current_state, current_native_state, /*begin=*/p_end, /*end=*/NUM_ROUNDS);
212
213 // Landing row for the final external round's output (the permutation result). On Mega it sits in the shared
214 // `poseidon2` block so the whole permutation remains contiguous.
215 if constexpr (IsMegaBuilder<Builder>) {
216 propagate_current_state_to_next_row(builder, current_state, builder->blocks.poseidon2);
217 } else {
218 propagate_current_state_to_next_row(builder, current_state, builder->blocks.poseidon2_external);
219 }
220
221 return current_state;
222}
223
229template <typename Builder>
231 requires(!IsMegaBuilder<Builder>)
232{
233 const bb::fr two(2);
234 const bb::fr four(4);
235 // create the 6 gates for the initial matrix multiplication
236 // gate 1: Compute tmp1 = state[0] + state[1] + 2 * state[3]
237 field_t<Builder> tmp1 = state[0].add_two(state[1], state[3] * two);
238
239 // gate 2: Compute tmp2 = 2 * state[1] + state[2] + state[3]
240 field_t<Builder> tmp2 = state[2].add_two(state[1] * two, state[3]);
241
242 // gate 3: Compute v2 = 4 * state[0] + 4 * state[1] + tmp2
243 state[1] = tmp2.add_two(state[0] * four, state[1] * four);
244
245 // gate 4: Compute v1 = v2 + tmp1
246 state[0] = state[1] + tmp1;
247
248 // gate 5: Compute v4 = tmp1 + 4 * state[2] + 4 * state[3]
249 state[3] = tmp1.add_two(state[2] * four, state[3] * four);
250
251 // gate 6: Compute v3 = v4 + tmp2
252 state[2] = state[3] + tmp2;
253}
254
255template <typename Builder>
258{
259 Builder* builder = validate_context<Builder>(state);
260 BB_ASSERT(builder != nullptr, "Poseidon2 Mega initial external layer needs a builder context");
261
262 NativeState native_state;
263 for (size_t i = 0; i < t; ++i) {
264 native_state[i] = state[i].get_value();
265 }
266 NativePermutation::matrix_multiplication_external(native_state);
267
268 materialize_constants_for_initial_layer(builder, state);
269
270 poseidon2_initial_external_gate_<FF> in{ state[0].get_witness_index(),
271 state[1].get_witness_index(),
272 state[2].get_witness_index(),
273 state[3].get_witness_index() };
274 builder->create_poseidon2_initial_external_gate(in);
275 for (size_t j = 0; j < t; ++j) {
276 state[j] = witness_t<Builder>(builder, native_state[j]);
277 }
278}
279
280template class Poseidon2Permutation<MegaCircuitBuilder>;
281template class Poseidon2Permutation<UltraCircuitBuilder>;
282
283} // namespace bb::stdlib
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
Circuit form of Poseidon2 permutation from https://eprint.iacr.org/2023/323.
static void propagate_current_state_to_next_row(Builder *builder, const State &state, auto &block)
The result of applying a round of Poseidon2 is stored in the next row and is accessed by Poseidon2 In...
static void matrix_multiplication_external(State &state)
In-circuit method to efficiently multiply the initial state by the external matrix .
static constexpr RoundConstantsContainer round_constants
std::array< field_t< Builder >, t > State
static State permutation(Builder *builder, const State &input)
Circuit form of Poseidon2 permutation from https://eprint.iacr.org/2023/323.
static field_t from_witness_index(Builder *ctx, uint32_t witness_index)
Definition field.cpp:67
field_t add_two(const field_t &add_b, const field_t &add_c) const
Efficiently compute (this + a + b) using big_mul gate.
Definition field.cpp:585
AluTraceBuilder builder
Definition alu.test.cpp:124
MegaCircuitBuilder_< field< Bn254FrParams > > MegaCircuitBuilder