Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
to_radix_trace.cpp
Go to the documentation of this file.
2
3#include <cstddef>
4#include <cstdint>
5#include <vector>
6
15
16namespace bb::avm2::tracegen {
17
18using C = Column;
19
34 TraceContainer& trace)
35{
36 const auto& p_limbs_per_radix = get_p_limbs_per_radix();
37
38 uint32_t row = 1; // We start from row 1 because this trace contains shifted columns.
39 for (const auto& event : events) {
40 const FF& value = event.value;
41 const uint32_t radix = event.radix;
42 BB_ASSERT(radix >= 2 && radix <= 256, "Invalid radix");
43 const auto& p_limbs = p_limbs_per_radix[static_cast<size_t>(radix)];
44
45 // Number of safe limbs. It means that the first `safe_limbs` limbs will not overflow
46 // the field modulus p. The limb at index `safe_limbs` is considered unsafe and we must
47 // ensure that the accumulator is under p. Past this point, we have padding limbs that we must
48 // assert to be zero in circuit.
49 const uint32_t safe_limbs = static_cast<uint32_t>(p_limbs.size()) - 1;
50
51 FF acc = 0;
52 FF power = 1; // Successive powers of the radix. After unsafe limbs, we set it to 0.
53 bool found = false; // Whether the value has been found in the decomposition.
54 bool acc_under_p = false; // Whether the accumulator is under p.
55
56 for (uint32_t i = 0; i < event.limbs.size(); ++i) {
57 const bool is_padding = i > safe_limbs;
58 const uint8_t limb = event.limbs[i]; // Safe access by the precondition that i < event.limbs.size().
59 const uint8_t p_limb = is_padding ? 0 : p_limbs[static_cast<size_t>(i)];
60
61 // If the new limb is equal to the p limb, this will not change the boolean value of acc_under_p.
62 if (limb != p_limb) {
63 acc_under_p = limb < p_limb;
64 }
65
66 // Remain 0 if limb == p_limb otherwise will be overwritten below.
67 FF limb_p_diff = 0;
68
69 if (limb > p_limb) {
70 limb_p_diff = limb - p_limb - 1;
71 } else if (limb < p_limb) {
72 limb_p_diff = p_limb - limb - 1;
73 }
74
75 bool is_unsafe_limb = i == safe_limbs;
76 FF safety_diff = FF(i) - FF(safe_limbs);
77
78 acc += power * FF(limb);
79
80 FF rem = value - acc;
81 found = rem.is_zero();
82
83 bool end = i == (event.limbs.size() - 1);
84
85 trace.set(row,
86 { {
87 { C::to_radix_sel, 1 },
88 { C::to_radix_value, value },
89 { C::to_radix_radix, radix },
90 { C::to_radix_limb_index, i },
91 { C::to_radix_limb, limb },
92 { C::to_radix_start, i == 0 ? 1 : 0 },
93 { C::to_radix_end, end ? 1 : 0 },
94 { C::to_radix_power, power },
95 { C::to_radix_not_padding_limb, !is_padding ? 1 : 0 },
96 { C::to_radix_acc, acc },
97 { C::to_radix_found, found ? 1 : 0 },
98 { C::to_radix_limb_radix_diff, radix - limb - 1 },
99 { C::to_radix_rem_inverse, rem }, // Will be inverted in batch later
100 { C::to_radix_safe_limbs, safe_limbs },
101 { C::to_radix_is_unsafe_limb, is_unsafe_limb ? 1 : 0 },
102 { C::to_radix_safety_diff_inverse, safety_diff }, // Will be inverted in batch later
103 { C::to_radix_p_limb, p_limb },
104 { C::to_radix_acc_under_p, acc_under_p ? 1 : 0 },
105 { C::to_radix_limb_lt_p, limb < p_limb ? 1 : 0 },
106 { C::to_radix_limb_eq_p, limb == p_limb ? 1 : 0 },
107 { C::to_radix_limb_p_diff, limb_p_diff },
108 } });
109
110 row++;
111 if (is_unsafe_limb) {
112 power = 0; // Our constraints require that power is 0 after the unsafe limb.
113 } else {
114 power *= FF(radix);
115 }
116 }
117 }
118
119 // Batch invert the columns.
120 trace.invert_columns({ { C::to_radix_safety_diff_inverse, C::to_radix_rem_inverse } });
121}
122
144{
145 uint32_t row = 1; // We start from row 1 because this trace contains shifted columns.
146 for (const auto& event : events) {
147 // Helpers
148 const uint32_t num_limbs = event.num_limbs;
149 bool num_limbs_is_zero = num_limbs == 0;
150 bool value_is_zero = event.value == FF(0);
151
152 // Error Handling - Out of Memory Access
153 uint64_t dst_addr = static_cast<uint64_t>(event.dst_addr); // Will be incremented in the loop below.
154 uint64_t write_addr_upper_bound = dst_addr + num_limbs;
155 bool write_out_of_range = write_addr_upper_bound > AVM_MEMORY_SIZE;
156
157 // Error Handling - Radix Range
158 bool invalid_radix = (event.radix < 2 || event.radix > 256);
159
160 // Error Handling - Bitwise Radix
161 bool radix_eq_2 = event.radix == 2;
162 bool invalid_bitwise_radix = event.is_output_bits && !radix_eq_2;
163
164 // Error Handling - Num Limbs and Value
165 bool invalid_num_limbs = num_limbs_is_zero && !value_is_zero;
166
167 // Common values for the first row
168 trace.set(row,
169 { {
170 { C::to_radix_mem_sel, 1 },
171 { C::to_radix_mem_start, 1 },
172 // Inputs from dispatch to to_radix (see #DISPATCH_TO_TO_RADIX in execution.pil)
173 // must always be set unconditionally.
174 { C::to_radix_mem_execution_clk, event.execution_clk },
175 { C::to_radix_mem_space_id, event.space_id },
176 { C::to_radix_mem_dst_addr, dst_addr },
177 { C::to_radix_mem_value_to_decompose, event.value },
178 { C::to_radix_mem_radix, event.radix },
179 { C::to_radix_mem_num_limbs, num_limbs },
180 { C::to_radix_mem_is_output_bits, event.is_output_bits ? 1 : 0 },
181 // Helpers
182 { C::to_radix_mem_max_mem_size, static_cast<uint64_t>(AVM_MEMORY_SIZE) },
183 { C::to_radix_mem_write_addr_upper_bound, write_addr_upper_bound },
184 { C::to_radix_mem_two, 2 },
185 { C::to_radix_mem_two_five_six, 256 },
186 { C::to_radix_mem_sel_num_limbs_is_zero, num_limbs_is_zero ? 1 : 0 },
187 { C::to_radix_mem_num_limbs_inv, num_limbs }, // Will be inverted in batch later
188 { C::to_radix_mem_sel_value_is_zero, value_is_zero ? 1 : 0 },
189 { C::to_radix_mem_value_inv, event.value }, // Will be inverted in batch later
190 { C::to_radix_mem_sel_radix_eq_2, radix_eq_2 ? 1 : 0 },
191 { C::to_radix_mem_radix_min_two_inv, FF(event.radix) - FF(2) }, // Will be inverted in batch later
192 } });
193
194 // Input validation errors
195 if (write_out_of_range || invalid_radix || invalid_bitwise_radix || invalid_num_limbs) {
196 trace.set(row,
197 { {
198 { C::to_radix_mem_last, 1 },
199 { C::to_radix_mem_input_validation_error, 1 },
200 { C::to_radix_mem_err, 1 },
201 { C::to_radix_mem_sel_dst_out_of_range_err, write_out_of_range ? 1 : 0 },
202 { C::to_radix_mem_sel_radix_lt_2_err, event.radix < 2 ? 1 : 0 },
203 { C::to_radix_mem_sel_radix_gt_256_err, event.radix > 256 ? 1 : 0 },
204 { C::to_radix_mem_sel_invalid_bitwise_radix, invalid_bitwise_radix ? 1 : 0 },
205 } });
206 row++;
207 continue;
208 }
209
210 // If no error occured, the following invariant must hold (honest simulation):
211 BB_ASSERT(event.limbs.size() == static_cast<size_t>(num_limbs), "Number of limbs does not match");
212
213 // From now on, accessing event.limbs[i] for any 0<=i<num_limbs is safe.
214
215 // Num limbs = 0 short circuit
216 if (num_limbs == 0) {
217 trace.set(row,
218 { {
219 { C::to_radix_mem_last, 1 },
220 } });
221 row++;
222 continue;
223 }
224
225 // At this point, a decomposition has happened, so we can process the limbs.
226
227 // Compute found for the given decomposition
228 FF acc = 0;
229 FF power = 1;
230 std::vector<bool> found(num_limbs, false);
231 for (size_t i = 0; i < num_limbs; ++i) {
232 // Limbs are BE, we compute found in LE since the to_radix subtrace is little endian
233 size_t reverse_index = event.limbs.size() - i - 1;
234 FF limb_value = event.limbs[reverse_index].as_ff();
235 acc += power * limb_value;
236 power *= event.radix;
237 found[reverse_index] = acc == event.value;
238 // Once `found[reverse_index]` is set to true, it will never be set to false.
239 // This is guaranteed by a honest simulation that `limb_value == 0` from this point
240 // on and therefore `acc` remains constant.
241 }
242
243 // Truncation error. A radix decomposition in the non-memory aware to_radix subtrace is performed.
244 const bool truncation_error = (num_limbs != 0) && !found.at(0);
245
246 // We only populate a single row to retrieve `value_found` from the non-memory aware to_radix subtrace
247 // at the last limb (little endian) corresponding to the first limb in the big endian decomposition.
248 if (truncation_error) {
249 trace.set(row,
250 { {
251 { C::to_radix_mem_last, 1 },
252 { C::to_radix_mem_err, 1 },
253 // Decomposition
254 { C::to_radix_mem_sel_should_decompose, 1 },
255 { C::to_radix_mem_limb_index_to_lookup, num_limbs - 1 },
256 { C::to_radix_mem_limb_value, event.limbs[0].as_ff() },
257 // Default C::to_radix_mem_value_found to zero.
258 } });
259
260 row++;
261 continue;
262 }
263
264 uint32_t remaining_limbs = num_limbs;
265
266 // Base case
267 // Here we have the following guarantees:
268 // - num_limbs > 0
269 // - No error occured
270 for (uint32_t i = 0; i < num_limbs; ++i) {
271 const MemoryValue& limb_value = event.limbs[i];
272 bool last = i == (num_limbs - 1);
273
274 // Note that the following columns at i == 0 were already set above but code is simpler this way:
275 // - C::to_radix_mem_sel
276 // - C::to_radix_mem_execution_clk
277 // - C::to_radix_mem_space_id
278 // - C::to_radix_mem_dst_addr
279 // - C::to_radix_mem_value_to_decompose
280 // - C::to_radix_mem_radix
281 // - C::to_radix_mem_num_limbs
282 // - C::to_radix_mem_is_output_bits
283 trace.set(row,
284 { {
285 { C::to_radix_mem_sel, 1 },
286 { C::to_radix_mem_num_limbs, remaining_limbs },
287 { C::to_radix_mem_num_limbs_minus_one_inv,
288 FF(remaining_limbs - 1) }, // Will be inverted in batch later
289 { C::to_radix_mem_last, last ? 1 : 0 },
290 // Decomposition
291 { C::to_radix_mem_sel_should_decompose, 1 },
292 { C::to_radix_mem_value_to_decompose, event.value },
293 { C::to_radix_mem_limb_index_to_lookup, remaining_limbs - 1 },
294 { C::to_radix_mem_radix, event.radix },
295 { C::to_radix_mem_limb_value, limb_value.as_ff() },
296 { C::to_radix_mem_value_found, found.at(i) ? 1 : 0 },
297 // Memory write
298 { C::to_radix_mem_sel_should_write_mem, 1 },
299 { C::to_radix_mem_execution_clk, event.execution_clk },
300 { C::to_radix_mem_space_id, event.space_id },
301 { C::to_radix_mem_dst_addr, dst_addr },
302 { C::to_radix_mem_output_tag, static_cast<uint8_t>(limb_value.get_tag()) },
303 { C::to_radix_mem_is_output_bits, event.is_output_bits ? 1 : 0 },
304 } });
305
306 remaining_limbs--; // Decrement the number of limbs
307 dst_addr++; // Increment the destination address for the next limb
308
309 row++;
310 }
311 }
312
313 // Batch invert the columns.
314 trace.invert_columns({ {
315 C::to_radix_mem_num_limbs_inv,
316 C::to_radix_mem_value_inv,
317 C::to_radix_mem_num_limbs_minus_one_inv,
318 C::to_radix_mem_radix_min_two_inv,
319 } });
320}
321
324 // Non-Memory Aware To Radix (to_radix.pil)
326 .add<lookup_to_radix_limb_less_than_radix_range_settings, InteractionType::LookupIntoIndexedByRow>()
328 .add<lookup_to_radix_fetch_p_limb_settings, InteractionType::LookupIntoPDecomposition>()
330 // Mem Aware To Radix (to_radix_mem.pil)
331 // GT checks
332 .add<lookup_to_radix_mem_check_dst_addr_in_range_settings, InteractionType::LookupGeneric>(C::gt_sel)
334 .add<lookup_to_radix_mem_check_radix_gt_256_settings, InteractionType::LookupGeneric>(C::gt_sel)
335 // Dispatch to To Radix
336 // Cannnot be sequential because the non-memory aware to_radix subtrace rows are ordered
337 // by the little endian decomposition while the memory aware to_radix subtrace rows are ordered
338 // by the big endian decomposition.
340 InteractionType::LookupGeneric>(); // CANNOT BE SEQUENTIAL!
341
342} // namespace bb::avm2::tracegen
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define AVM_MEMORY_SIZE
ValueTag get_tag() const
InteractionDefinition & add(auto &&... args)
void process(const simulation::EventEmitterInterface< simulation::ToRadixEvent >::Container &events, TraceContainer &trace)
Processes the non-memory aware to_radix subtrace ingesting ToRadixEvent events. The populated number ...
static const InteractionDefinition interactions
void process_with_memory(const simulation::EventEmitterInterface< simulation::ToRadixMemoryEvent >::Container &events, TraceContainer &trace)
Processes the memory aware to_radix subtrace ingesting ToRadixMemoryEvent events. The populated numbe...
uint32_t dst_addr
TestTraceContainer trace
lookup_settings< lookup_to_radix_limb_p_diff_range_settings_ > lookup_to_radix_limb_p_diff_range_settings
const std::array< std::vector< uint8_t >, 257 > & get_p_limbs_per_radix()
Gets the p limbs per radix array. Each element is a vector containing the little endian decomposition...
Definition to_radix.cpp:60
lookup_settings< lookup_to_radix_mem_check_radix_lt_2_settings_ > lookup_to_radix_mem_check_radix_lt_2_settings
lookup_settings< lookup_to_radix_limb_range_settings_ > lookup_to_radix_limb_range_settings
AvmFlavorSettings::FF FF
Definition field.hpp:10
lookup_settings< lookup_to_radix_fetch_safe_limbs_settings_ > lookup_to_radix_fetch_safe_limbs_settings
lookup_settings< lookup_to_radix_mem_input_output_to_radix_settings_ > lookup_to_radix_mem_input_output_to_radix_settings
simulation::PublicDataTreeReadWriteEvent event
BB_INLINE constexpr bool is_zero() const noexcept