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