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: Complete, auditors: [Sergei], commit: 777717f6af324188ecd6bb68c3c86ee7befef94d}
3// external_1: { status: Complete, auditors: [@ed25519 (Spearbit)], 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 share witness indices with the external
107 // block's propagate row, so they are the true external output. The relation forces the first
108 // compressed row's (w_r_shift, w_o_shift, w_4_shift) to state[0] at rounds start+1, +2, +3.
109 {
110 poseidon2_transition_entry_gate_<FF> in{
111 current_state[0].get_witness_index(),
112 current_state[1].get_witness_index(),
113 current_state[2].get_witness_index(),
114 current_state[3].get_witness_index(),
115 rounds_f_beginning,
116 };
117 builder->create_poseidon2_transition_entry_gate(in);
118 }
119
120 auto advance_internal_round = [](NativeState& state, const FF& round_constant) {
121 state[0] += round_constant;
122 Permutation::NativePermutation::apply_single_sbox(state[0]);
123 Permutation::NativePermutation::matrix_multiplication_internal(state);
124 };
125
126 // Helper: emit one K=4 compressed row (interior or terminal) and advance `current_state`
127 // by 4 internal rounds. The row wires are state[0] at rounds start, start+1, start+2, start+3.
128 auto emit_quad_row = [&](size_t quad_idx, bool is_terminal) {
129 const size_t start = rounds_f_beginning + (4 * quad_idx);
130 const size_t next_start = start + 4; // ignored on terminal
131
132 NativeState state_after_1 = current_native_state;
133 advance_internal_round(state_after_1, Permutation::round_constants[start + 0][0]);
134 auto s0_at_1 = Witness(builder, state_after_1[0]);
135
136 NativeState state_after_2 = state_after_1;
137 advance_internal_round(state_after_2, Permutation::round_constants[start + 1][0]);
138 auto s0_at_2 = Witness(builder, state_after_2[0]);
139
140 NativeState state_after_3 = state_after_2;
141 advance_internal_round(state_after_3, Permutation::round_constants[start + 2][0]);
142 auto s0_at_3 = Witness(builder, state_after_3[0]);
143
144 poseidon2_quad_internal_gate_<FF> in{
145 current_state[0].get_witness_index(), // state[0] at round start
146 s0_at_1.witness_index, // state[0] at round start+1
147 s0_at_2.witness_index, // state[0] at round start+2
148 s0_at_3.witness_index, // state[0] at round start+3
149 start,
150 next_start,
151 is_terminal,
152 };
153 builder->create_poseidon2_quad_internal_gate(in);
154
155 // Advance native state by the 4th round to land on state at round start+4.
156 current_native_state = state_after_3;
157 advance_internal_round(current_native_state, Permutation::round_constants[start + 3][0]);
158
159 // The next non-terminal compressed row only consumes state[0] at round start+4. The remaining limbs are
160 // derived inside the relation and do not need witnesses until the terminal row bridges back to the
161 // standard encoding consumed by the final external rounds.
162 current_state[0] = Witness(builder, current_native_state[0]);
163 if (is_terminal) {
164 for (size_t j = 1; j < Permutation::t; ++j) {
165 current_state[j] = Witness(builder, current_native_state[j]);
166 }
167 }
168 };
169
170 // 13 interior compressed rows (covering rounds 0..51 relative)
171 for (size_t q = 0; q < num_quad_rows - 1; ++q) {
172 emit_quad_row(q, /*is_terminal=*/false);
173 }
174 // 1 terminal compressed row (covering rounds 52..55 relative)
175 emit_quad_row(num_quad_rows - 1, /*is_terminal=*/true);
176
177 // Standard-transition bridge row: selector-unconstrained, holds state at round p_end in standard
178 // encoding. Shared witness indices with the first final-external gate below.
179 builder->create_unconstrained_gate(builder->blocks.poseidon2_quad_internal,
180 current_state[0].get_witness_index(),
181 current_state[1].get_witness_index(),
182 current_state[2].get_witness_index(),
183 current_state[3].get_witness_index());
184}
185
186} // namespace
187
188template <typename Builder>
191{
192 State current_state(input);
193 NativeState current_native_state;
194
195 matrix_multiplication_external(current_state);
196 sync_native_state_from_state<Builder>(current_native_state, current_state);
197
198 // First set of external rounds
199 constexpr size_t rounds_f_beginning = rounds_f / 2;
200 apply_external_rounds(builder, current_state, current_native_state, /*begin=*/0, /*end=*/rounds_f_beginning);
201
202 propagate_current_state_to_next_row(builder, current_state, builder->blocks.poseidon2_external);
203
204 // Internal rounds: Mega uses a K=4 compressed block; Ultra keeps the standard one-round layout.
205 const size_t p_end = rounds_f_beginning + rounds_p;
206 if constexpr (IsMegaBuilder<Builder>) {
207 apply_mega_internal_rounds(builder, current_state, current_native_state, rounds_f_beginning);
208 } else {
209 apply_standard_internal_rounds(builder, current_state, current_native_state, rounds_f_beginning, p_end);
210 }
211
212 // Remaining external rounds
213 apply_external_rounds(builder, current_state, current_native_state, /*begin=*/p_end, /*end=*/NUM_ROUNDS);
214
215 propagate_current_state_to_next_row(builder, current_state, builder->blocks.poseidon2_external);
216
217 return current_state;
218}
219
225template <typename Builder>
227 requires(!IsMegaBuilder<Builder>)
228{
229 const bb::fr two(2);
230 const bb::fr four(4);
231 // create the 6 gates for the initial matrix multiplication
232 // gate 1: Compute tmp1 = state[0] + state[1] + 2 * state[3]
233 field_t<Builder> tmp1 = state[0].add_two(state[1], state[3] * two);
234
235 // gate 2: Compute tmp2 = 2 * state[1] + state[2] + state[3]
236 field_t<Builder> tmp2 = state[2].add_two(state[1] * two, state[3]);
237
238 // gate 3: Compute v2 = 4 * state[0] + 4 * state[1] + tmp2
239 state[1] = tmp2.add_two(state[0] * four, state[1] * four);
240
241 // gate 4: Compute v1 = v2 + tmp1
242 state[0] = state[1] + tmp1;
243
244 // gate 5: Compute v4 = tmp1 + 4 * state[2] + 4 * state[3]
245 state[3] = tmp1.add_two(state[2] * four, state[3] * four);
246
247 // gate 6: Compute v3 = v4 + tmp2
248 state[2] = state[3] + tmp2;
249}
250
251template <typename Builder>
254{
255 Builder* builder = validate_context<Builder>(state);
256 BB_ASSERT(builder != nullptr, "Poseidon2 Mega initial external layer needs a builder context");
257
258 NativeState native_state;
259 for (size_t i = 0; i < t; ++i) {
260 native_state[i] = state[i].get_value();
261 }
262 NativePermutation::matrix_multiplication_external(native_state);
263
264 materialize_constants_for_initial_layer(builder, state);
265
266 poseidon2_initial_external_gate_<FF> in{ state[0].get_witness_index(),
267 state[1].get_witness_index(),
268 state[2].get_witness_index(),
269 state[3].get_witness_index() };
270 builder->create_poseidon2_initial_external_gate(in);
271 for (size_t j = 0; j < t; ++j) {
272 state[j] = witness_t<Builder>(builder, native_state[j]);
273 }
274}
275
276template class Poseidon2Permutation<MegaCircuitBuilder>;
277template class Poseidon2Permutation<UltraCircuitBuilder>;
278
279} // 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