Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
poseidon2_trace.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <array>
5#include <cstddef>
6#include <cstdint>
7
8#include "barretenberg/aztec/aztec_constants.hpp"
14
16
17namespace bb::avm2::tracegen {
18
19namespace {
20
21using C = Column;
22using StateCols = std::array<C, 4>;
23
24// Full-round end-of-round columns. After the external MDS the lanes are
25// permuted as (lane 0 = T_*_6, lane 1 = T_*_5, lane 2 = T_*_7, lane 3 = T_*_4).
26constexpr std::array<StateCols, 4> initial_full_round_cols = { {
27 { C::poseidon2_perm_T_0_6, C::poseidon2_perm_T_0_5, C::poseidon2_perm_T_0_7, C::poseidon2_perm_T_0_4 },
28 { C::poseidon2_perm_T_1_6, C::poseidon2_perm_T_1_5, C::poseidon2_perm_T_1_7, C::poseidon2_perm_T_1_4 },
29 { C::poseidon2_perm_T_2_6, C::poseidon2_perm_T_2_5, C::poseidon2_perm_T_2_7, C::poseidon2_perm_T_2_4 },
30 { C::poseidon2_perm_T_3_6, C::poseidon2_perm_T_3_5, C::poseidon2_perm_T_3_7, C::poseidon2_perm_T_3_4 },
31} };
32
33constexpr std::array<StateCols, 4> final_full_round_cols = { {
34 { C::poseidon2_perm_T_60_6, C::poseidon2_perm_T_60_5, C::poseidon2_perm_T_60_7, C::poseidon2_perm_T_60_4 },
35 { C::poseidon2_perm_T_61_6, C::poseidon2_perm_T_61_5, C::poseidon2_perm_T_61_7, C::poseidon2_perm_T_61_4 },
36 { C::poseidon2_perm_T_62_6, C::poseidon2_perm_T_62_5, C::poseidon2_perm_T_62_7, C::poseidon2_perm_T_62_4 },
37 { C::poseidon2_perm_T_63_6, C::poseidon2_perm_T_63_5, C::poseidon2_perm_T_63_7, C::poseidon2_perm_T_63_4 },
38} };
39
40// Partial-round columns.
41// We need to still commit to state[0] at every partial round. Check poseidon2_perm.pil for how this works.
42constexpr std::array<C, 56> partial_round_cols = {
43 C::poseidon2_perm_B_4_0, C::poseidon2_perm_B_5_0, C::poseidon2_perm_B_6_0, C::poseidon2_perm_B_7_0,
44 C::poseidon2_perm_B_8_0, C::poseidon2_perm_B_9_0, C::poseidon2_perm_B_10_0, C::poseidon2_perm_B_11_0,
45 C::poseidon2_perm_B_12_0, C::poseidon2_perm_B_13_0, C::poseidon2_perm_B_14_0, C::poseidon2_perm_B_15_0,
46 C::poseidon2_perm_B_16_0, C::poseidon2_perm_B_17_0, C::poseidon2_perm_B_18_0, C::poseidon2_perm_B_19_0,
47 C::poseidon2_perm_B_20_0, C::poseidon2_perm_B_21_0, C::poseidon2_perm_B_22_0, C::poseidon2_perm_B_23_0,
48 C::poseidon2_perm_B_24_0, C::poseidon2_perm_B_25_0, C::poseidon2_perm_B_26_0, C::poseidon2_perm_B_27_0,
49 C::poseidon2_perm_B_28_0, C::poseidon2_perm_B_29_0, C::poseidon2_perm_B_30_0, C::poseidon2_perm_B_31_0,
50 C::poseidon2_perm_B_32_0, C::poseidon2_perm_B_33_0, C::poseidon2_perm_B_34_0, C::poseidon2_perm_B_35_0,
51 C::poseidon2_perm_B_36_0, C::poseidon2_perm_B_37_0, C::poseidon2_perm_B_38_0, C::poseidon2_perm_B_39_0,
52 C::poseidon2_perm_B_40_0, C::poseidon2_perm_B_41_0, C::poseidon2_perm_B_42_0, C::poseidon2_perm_B_43_0,
53 C::poseidon2_perm_B_44_0, C::poseidon2_perm_B_45_0, C::poseidon2_perm_B_46_0, C::poseidon2_perm_B_47_0,
54 C::poseidon2_perm_B_48_0, C::poseidon2_perm_B_49_0, C::poseidon2_perm_B_50_0, C::poseidon2_perm_B_51_0,
55 C::poseidon2_perm_B_52_0, C::poseidon2_perm_B_53_0, C::poseidon2_perm_B_54_0, C::poseidon2_perm_B_55_0,
56 C::poseidon2_perm_B_56_0, C::poseidon2_perm_B_57_0, C::poseidon2_perm_B_58_0, C::poseidon2_perm_B_59_0,
57};
58
59} // namespace
60
70 TraceContainer& trace)
71{
72 uint32_t row = 1; // We start from row 1 because this trace contains shifted columns.
73 for (const auto& event : hash_events) {
74 auto input_size = event.inputs.size(); // Will be mutated in the loop below.
75 // Simulation guarantees that the number of intermediate states is 1 more than the number of permutation events.
76 const auto num_perm_events = event.intermediate_states.size() - 1;
77 // The padding size is the number of elements to add to the input to make it a multiple of 3.
78 // We have to map the modulo 3 values of input_size: 0 -> 0, 1 -> 2, 2 -> 1 to the padding size
79 // which corresponds to a multiplication by 2 modulo 3.
80 const auto padding_size = (2 * input_size) % 3;
81
82 for (size_t i = 0; i < num_perm_events; i++) {
83 std::array<FF, 3> perm_input = { 0, 0, 0 };
84 auto perm_state = event.intermediate_states[i];
85 const auto& perm_output = event.intermediate_states[i + 1]; // In range by definition of num_perm_events.
86 size_t chunk_size = std::min(input_size, static_cast<size_t>(3));
87 // Mix the input chunk into the previous permutation output state
88 for (size_t j = 0; j < chunk_size; j++) {
89 // Build up the input for the permutation
90 perm_input[j] = event.inputs[(i * 3) + j];
91 // Mix the input chunk into the previous permutation output state
92 perm_state[j] += perm_input[j];
93 }
94 trace.set(row,
95 { {
96 { C::poseidon2_hash_sel, 1 },
97 { C::poseidon2_hash_start, i == 0 ? 1 : 0 },
98 { C::poseidon2_hash_end, i == (num_perm_events - 1) ? 1 : 0 },
99 { C::poseidon2_hash_input_len, event.inputs.size() }, // Cannot use input_size as mutated.
100 { C::poseidon2_hash_padding, padding_size },
101 { C::poseidon2_hash_input_0, perm_input[0] },
102 { C::poseidon2_hash_input_1, perm_input[1] },
103 { C::poseidon2_hash_input_2, perm_input[2] },
104
105 { C::poseidon2_hash_num_perm_rounds_rem, num_perm_events - i },
106 { C::poseidon2_hash_num_perm_rounds_rem_min_one_inv,
107 num_perm_events - i - 1 }, // Will be batch inverted.
108
109 { C::poseidon2_hash_a_0, perm_state[0] },
110 { C::poseidon2_hash_a_1, perm_state[1] },
111 { C::poseidon2_hash_a_2, perm_state[2] },
112 { C::poseidon2_hash_a_3, perm_state[3] },
113
114 { C::poseidon2_hash_b_0, perm_output[0] },
115 { C::poseidon2_hash_b_1, perm_output[1] },
116 { C::poseidon2_hash_b_2, perm_output[2] },
117 { C::poseidon2_hash_b_3, perm_output[3] },
118 { C::poseidon2_hash_output, event.output },
119 } });
120 input_size -= chunk_size;
121 row++;
122 }
123 }
124
125 trace.invert_columns({ { C::poseidon2_hash_num_perm_rounds_rem_min_one_inv } });
126}
127
138 TraceContainer& trace)
139{
140 // Our current state
141 std::array<FF, 4> current_state;
142
143 uint32_t row = 0;
144
145 for (const auto& event : perm_events) {
146 // The bulk of this code is a copy of the Poseidon2Permutation::permute function from bb
147 // Note that the functions mutate current_state in place.
148 current_state = event.input;
149
150 // Apply 1st linear layer
152 trace.set(row,
153 { {
154 { C::poseidon2_perm_sel, 1 },
155 { C::poseidon2_perm_a_0, event.input[0] },
156 { C::poseidon2_perm_a_1, event.input[1] },
157 { C::poseidon2_perm_a_2, event.input[2] },
158 { C::poseidon2_perm_a_3, event.input[3] },
159
160 { C::poseidon2_perm_EXT_LAYER_6, current_state[0] },
161 { C::poseidon2_perm_EXT_LAYER_5, current_state[1] },
162 { C::poseidon2_perm_EXT_LAYER_7, current_state[2] },
163 { C::poseidon2_perm_EXT_LAYER_4, current_state[3] },
164
165 } });
166
167 // Perform rounds of the permutation algorithm
168 // Initial external (full) rounds
169 constexpr size_t rounds_f_beginning = Poseidon2Perm::rounds_f / 2;
170 for (size_t i = 0; i < rounds_f_beginning; ++i) {
172 Poseidon2Perm::apply_sbox(current_state);
174 const auto& cols = initial_full_round_cols[i];
175 trace.set(row,
176 { { { cols[0], current_state[0] },
177 { cols[1], current_state[1] },
178 { cols[2], current_state[2] },
179 { cols[3], current_state[3] } } });
180 }
181
182 // Internal (partial) rounds
183 // Only state[0] is committed (B_n_0). The other three state values are intermediate polys
184 const size_t p_end = rounds_f_beginning + Poseidon2Perm::rounds_p;
185 for (size_t i = rounds_f_beginning; i < p_end; ++i) {
186 current_state[0] += Poseidon2Perm::round_constants[i][0];
187 Poseidon2Perm::apply_single_sbox(current_state[0]);
189 const size_t partial_idx = i - rounds_f_beginning;
190 trace.set(row, { { { partial_round_cols[partial_idx], current_state[0] } } });
191 }
192
193 // Bridge: at the end of the partial-round chain we need to commit all four state B_59_{1,2,3}
194 // so the first final full round can S-box the standard 4-lane state.
195 trace.set(row,
196 { { { C::poseidon2_perm_B_59_1, current_state[1] },
197 { C::poseidon2_perm_B_59_2, current_state[2] },
198 { C::poseidon2_perm_B_59_3, current_state[3] } } });
199
200 // Remaining external (full) rounds.
201 for (size_t i = p_end; i < Poseidon2Perm::NUM_ROUNDS; ++i) {
203 Poseidon2Perm::apply_sbox(current_state);
205 const auto& cols = final_full_round_cols[i - p_end];
206 trace.set(row,
207 { { { cols[0], current_state[0] },
208 { cols[1], current_state[1] },
209 { cols[2], current_state[2] },
210 { cols[3], current_state[3] } } });
211 }
212 // Set the output
213 trace.set(row,
214 { {
215 { C::poseidon2_perm_b_0, current_state[0] },
216 { C::poseidon2_perm_b_1, current_state[1] },
217 { C::poseidon2_perm_b_2, current_state[2] },
218 { C::poseidon2_perm_b_3, current_state[3] },
219
220 } });
221 row++;
222 }
223}
224
235 TraceContainer& trace)
236{
237
238 // We rely on this assertion in circuit for the write memory tag.
239 // Namely, we pass `precomputed.zero` as the tag for the write memory operation.
240 // See the 4 lookups #[POS_WRITE_MEM_0] ... #[POS_WRITE_MEM_3] in poseidon2_mem.pil.
241 static_assert(static_cast<uint8_t>(MemoryTag::FF) == 0);
242
243 uint32_t row = 0;
244
245 for (const auto& event : perm_mem_events) {
246 // Addresses cast to uint64_t to capture overflows
247 const uint64_t src_addr = static_cast<uint64_t>(event.src_address);
248 const uint64_t dst_addr = static_cast<uint64_t>(event.dst_address);
249 // Error Handling, check that the addresses are within the valid range
250 // The max read address is src_addr + 3 since 4 input elements are read
251 // The max write address is dst_addr + 3 since 4 output elements are written
252 const bool src_out_of_range_err = src_addr + 3 > AVM_HIGHEST_MEM_ADDRESS;
253 const bool dst_out_of_range_err = dst_addr + 3 > AVM_HIGHEST_MEM_ADDRESS;
254 const bool should_read_mem = !(src_out_of_range_err || dst_out_of_range_err);
255
256 // Error Handling, check that the input tags are valid
257 bool invalid_tag =
258 std::ranges::any_of(event.input, [](const auto& input) { return input.get_tag() != MemoryTag::FF; });
259
260 FF batch_tag_inv = 0;
261
262 // No need to use batch inversion because in the happy path we do not perform any field inversion.
263 if (invalid_tag) {
264 uint32_t target_tag = static_cast<uint32_t>(MemoryTag::FF);
265 FF batched_tag_check = 0;
266 // Performs the batched tag check described in the circuit.
267 // see
268 // https://github.com/AztecProtocol/aztec-packages/blob/next/barretenberg/cpp/pil/vm2/docs/recipes.md#batching-comparison-of-n-bit-numbers
269 for (uint32_t i = 0; i < event.input.size(); i++) {
270 uint32_t exponent = 3 * i;
271 uint32_t current_tag = static_cast<uint32_t>(event.input[i].get_tag());
272 batched_tag_check += (FF(current_tag) - FF(target_tag)) * FF((1 << exponent));
273 }
274 batch_tag_inv = batched_tag_check.invert();
275 }
276
277 const bool err = src_out_of_range_err || dst_out_of_range_err || invalid_tag;
278
279 trace.set(row,
280 { {
281 { C::poseidon2_perm_mem_sel, 1 },
282 { C::poseidon2_perm_mem_execution_clk, event.execution_clk },
283 { C::poseidon2_perm_mem_space_id, event.space_id },
284 { C::poseidon2_perm_mem_max_mem_addr, AVM_HIGHEST_MEM_ADDRESS },
285 // Error Handling
286 { C::poseidon2_perm_mem_sel_src_out_of_range_err, src_out_of_range_err ? 1 : 0 },
287 { C::poseidon2_perm_mem_sel_dst_out_of_range_err, dst_out_of_range_err ? 1 : 0 },
288 { C::poseidon2_perm_mem_sel_invalid_tag_err, invalid_tag ? 1 : 0 },
289 { C::poseidon2_perm_mem_batch_tag_inv, batch_tag_inv },
290 { C::poseidon2_perm_mem_err, err ? 1 : 0 },
291 // Mem Ops
292 { C::poseidon2_perm_mem_sel_should_read_mem, should_read_mem ? 1 : 0 },
293 // Read Addresses
294 { C::poseidon2_perm_mem_read_address_0_, src_addr },
295 { C::poseidon2_perm_mem_read_address_1_, src_addr + 1 },
296 { C::poseidon2_perm_mem_read_address_2_, src_addr + 2 },
297 { C::poseidon2_perm_mem_read_address_3_, src_addr + 3 },
298 // Write Addresses
299 { C::poseidon2_perm_mem_write_address_0_, dst_addr },
300 { C::poseidon2_perm_mem_write_address_1_, dst_addr + 1 },
301 { C::poseidon2_perm_mem_write_address_2_, dst_addr + 2 },
302 { C::poseidon2_perm_mem_write_address_3_, dst_addr + 3 },
303 // Inputs
304 { C::poseidon2_perm_mem_input_0_, event.input[0].as_ff() },
305 { C::poseidon2_perm_mem_input_1_, event.input[1].as_ff() },
306 { C::poseidon2_perm_mem_input_2_, event.input[2].as_ff() },
307 { C::poseidon2_perm_mem_input_3_, event.input[3].as_ff() },
308 // Input Tags
309 { C::poseidon2_perm_mem_input_tag_0_, static_cast<uint8_t>(event.input[0].get_tag()) },
310 { C::poseidon2_perm_mem_input_tag_1_, static_cast<uint8_t>(event.input[1].get_tag()) },
311 { C::poseidon2_perm_mem_input_tag_2_, static_cast<uint8_t>(event.input[2].get_tag()) },
312 { C::poseidon2_perm_mem_input_tag_3_, static_cast<uint8_t>(event.input[3].get_tag()) },
313 // Outputs
314 { C::poseidon2_perm_mem_sel_should_exec, !err ? 1 : 0 },
315 { C::poseidon2_perm_mem_output_0_, event.output[0] },
316 { C::poseidon2_perm_mem_output_1_, event.output[1] },
317 { C::poseidon2_perm_mem_output_2_, event.output[2] },
318 { C::poseidon2_perm_mem_output_3_, event.output[3] },
319 } });
320 row++;
321 }
322}
323
327 // Poseidon2 Memory to Permutation Subtrace
328 .add<InteractionType::LookupSequential, lookup_poseidon2_mem_input_output_poseidon2_perm_settings>()
329 // Lookups to Greater Than Subtrace
331 .add<InteractionType::LookupGeneric, lookup_poseidon2_mem_check_dst_addr_in_range_settings>(C::gt_sel);
332
333} // namespace bb::avm2::tracegen
InteractionDefinition & add(auto &&... args)
static const InteractionDefinition interactions
void process_permutation(const simulation::EventEmitterInterface< simulation::Poseidon2PermutationEvent >::Container &perm_events, TraceContainer &trace)
Processes the permutation events for the Poseidon2 permutation function. It populates the columns for...
void process_permutation_with_memory(const simulation::EventEmitterInterface< simulation::Poseidon2PermutationMemoryEvent >::Container &perm_mem_events, TraceContainer &trace)
Processes the events for the Poseidon2 memory-aware permutation function. It populates the columns fo...
void process_hash(const simulation::EventEmitterInterface< simulation::Poseidon2HashEvent >::Container &hash_events, TraceContainer &trace)
Processes the hash events for the Poseidon2 hash function. It populates the columns for the poseidon2...
Applies the Poseidon2 permutation function from https://eprint.iacr.org/2023/323.
static constexpr void apply_single_sbox(FF &input)
S-box: x -> x^5.
static constexpr void matrix_multiplication_internal(State &input)
static constexpr void matrix_multiplication_external(State &input)
static constexpr void add_round_constants(State &input, const RoundConstants &rc)
static constexpr void apply_sbox(State &input)
static constexpr RoundConstantsContainer round_constants
uint32_t src_addr
uint32_t dst_addr
TestTraceContainer trace
AvmFlavorSettings::FF FF
Definition field.hpp:10
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
simulation::PublicDataTreeReadWriteEvent event
Settings to be passed ot GenericLookupRelationImpl.