Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
alu.cpp
Go to the documentation of this file.
2
3#include <cstdint>
4#include <stdexcept>
5#include <string>
6
13
14namespace bb::avm2::simulation {
15
25{
26 try {
27 MemoryValue c = a + b; // This will throw if the tags do not match.
28 events.emit({ .operation = AluOperation::ADD, .a = a, .b = b, .c = c });
29 return c;
30 } catch (const TagMismatchException& e) {
31 events.emit({ .operation = AluOperation::ADD, .a = a, .b = b, .error = true });
32 throw AluException("ADD, " + std::string(e.what()));
33 }
34}
35
45{
46 try {
47 MemoryValue c = a - b; // This will throw if the tags do not match.
48 events.emit({ .operation = AluOperation::SUB, .a = a, .b = b, .c = c });
49 return c;
50 } catch (const TagMismatchException& e) {
51 events.emit({ .operation = AluOperation::SUB, .a = a, .b = b, .error = true });
52 throw AluException("SUB, " + std::string(e.what()));
53 }
54}
55
65{
66 try {
67 MemoryValue c = a * b; // This will throw if the tags do not match.
68 uint256_t a_int = static_cast<uint256_t>(a.as_ff());
69 uint256_t b_int = static_cast<uint256_t>(b.as_ff());
70 MemoryTag tag = a.get_tag();
71 uint256_t c_hi = 0;
72 if (tag == MemoryTag::U128) {
73 // For u128, we decompose a and b into 64-bit chunks and discard the highest bits given by the product:
74 auto a_decomp = decompose_128(static_cast<uint128_t>(a.as_ff()));
75 auto b_decomp = decompose_128(static_cast<uint128_t>(b.as_ff()));
76 range_check.assert_range(a_decomp.lo, 64);
77 range_check.assert_range(a_decomp.hi, 64);
78 range_check.assert_range(b_decomp.lo, 64);
79 range_check.assert_range(b_decomp.hi, 64);
80 uint256_t hi_operand = static_cast<uint256_t>(a_decomp.hi) * static_cast<uint256_t>(b_decomp.hi);
81 // c_hi = (old_c_hi - a_hi * b_hi) % 2^64
82 // Make use of x % pow_of_two = x & (pow_of_two - 1)
83 c_hi = (((a_int * b_int) >> 128) - hi_operand) & static_cast<uint256_t>(MASK_64);
84 } else if (tag != MemoryTag::FF) {
85 c_hi = (a_int * b_int) >> static_cast<uint256_t>(get_tag_bits(tag));
86 }
87
88 range_check.assert_range(static_cast<uint128_t>(c_hi), 64);
89 events.emit({ .operation = AluOperation::MUL, .a = a, .b = b, .c = c });
90 return c;
91 } catch (const TagMismatchException& e) {
92 events.emit({ .operation = AluOperation::MUL, .a = a, .b = b, .error = true });
93 throw AluException("MUL, " + std::string(e.what()));
94 }
95}
96
109{
110 try {
111 MemoryValue c = a / b; // This will throw if the tags do not match or if we divide by 0.
112 MemoryValue remainder = a - c * b;
113 MemoryTag tag = a.get_tag();
114
115 if (tag == MemoryTag::FF) {
116 // DIV on a field is not a valid operation, but should be recoverable.
117 // It comes under the umbrella of tag errors (like NOT) but MemoryValue c = a / b does
118 // not throw.
119 events.emit({ .operation = AluOperation::DIV, .a = a, .b = b, .error = true });
120 throw AluException("DIV, Cannot perform integer division on a field element");
121 }
122
123 // Check remainder < b:
124 greater_than.gt(b, remainder);
125 // Range check that the remainder fits within max_bits (circuit pre-condition for gt call arguments).
126 range_check.assert_range(static_cast<uint128_t>(remainder.as_ff()), get_tag_bits(tag));
127 if (tag == MemoryTag::U128) {
128 // For u128, we decompose c and b into 64 bit chunks and discard the highest bits given by the product:
129 auto c_decomp = decompose_128(static_cast<uint128_t>(c.as_ff()));
130 auto b_decomp = decompose_128(static_cast<uint128_t>(b.as_ff()));
131 range_check.assert_range(c_decomp.lo, 64);
132 range_check.assert_range(c_decomp.hi, 64);
133 range_check.assert_range(b_decomp.lo, 64);
134 range_check.assert_range(b_decomp.hi, 64);
135 }
136 events.emit({ .operation = AluOperation::DIV, .a = a, .b = b, .c = c });
137 return c;
138 } catch (const TagMismatchException& e) {
139 events.emit({ .operation = AluOperation::DIV, .a = a, .b = b, .error = true });
140 throw AluException("DIV, " + std::string(e.what()));
141 } catch (const DivisionByZero& e) {
142 events.emit({ .operation = AluOperation::DIV, .a = a, .b = b, .error = true });
143 throw AluException("DIV, " + std::string(e.what()));
144 }
145}
146
159{
160 try {
161 MemoryValue c = a / b; // This will throw if the tags do not match or if we divide by 0.
162
163 if (a.get_tag() != MemoryTag::FF) {
164 // We cannot reach this case from execution because the tags are forced to be FF (see below*).
165 // It comes under the umbrella of tag errors (like NOT) but MemoryValue c = a / b does
166 // not throw.
167 events.emit({ .operation = AluOperation::FDIV, .a = a, .b = b, .error = true });
168 throw AluException("FDIV, Cannot perform field division on an integer");
169 }
170
171 events.emit({ .operation = AluOperation::FDIV, .a = a, .b = b, .c = c });
172 return c;
173 } catch (const TagMismatchException& e) {
174 // *This is unreachable from execution and exists to manage and test tag errors:
175 events.emit({ .operation = AluOperation::FDIV, .a = a, .b = b, .error = true });
176 throw AluException("FDIV, " + std::string(e.what()));
177 } catch (const DivisionByZero& e) {
178 events.emit({ .operation = AluOperation::FDIV, .a = a, .b = b, .error = true });
179 throw AluException("FDIV, " + std::string(e.what()));
180 }
181}
182
192{
193 // Brillig semantic enforces that tags match for EQ.
194 if (a.get_tag() != b.get_tag()) {
195 events.emit({ .operation = AluOperation::EQ, .a = a, .b = b, .error = true });
196 throw AluException("EQ, Tag mismatch between operands.");
197 }
198
199 MemoryValue c = MemoryValue::from<uint1_t>(a == b ? 1 : 0);
200 events.emit({ .operation = AluOperation::EQ, .a = a, .b = b, .c = c });
201 return c;
202}
203
213{
214 // Brillig semantic enforces that tags match for LT.
215 // This is special cased because comparison operators do not throw on tag mismatch.
216 if (a.get_tag() != b.get_tag()) {
217 events.emit({ .operation = AluOperation::LT, .a = a, .b = b, .error = true });
218 throw AluException("LT, Tag mismatch between operands.");
219 }
220 // Use the greater_than interface to check if b > a, which is the same as a < b.
221 bool res = greater_than.gt(b, a);
222 MemoryValue c = MemoryValue::from<uint1_t>(res);
223 events.emit({ .operation = AluOperation::LT, .a = a, .b = b, .c = c });
224 return c;
225}
226
236{
237 // Brillig semantic enforces that tags match for LTE.
238 if (a.get_tag() != b.get_tag()) {
239 events.emit({ .operation = AluOperation::LTE, .a = a, .b = b, .error = true });
240 throw AluException("LT, Tag mismatch between operands.");
241 }
242 // Note: the result of LTE is the opposite of GT
243 // Use the greater_than interface to check if a > b
244 bool res = greater_than.gt(a, b);
245 // The result of LTE is the opposite of the result of GT
246 MemoryValue c = MemoryValue::from<uint1_t>(!res);
247 events.emit({ .operation = AluOperation::LTE, .a = a, .b = b, .c = c });
248 return c;
249}
250
259{
260 try {
261 MemoryValue b = ~a; // Throws if the tag is not compatible with NOT operation (i.e. it is an FF type).
262 events.emit({ .operation = AluOperation::NOT, .a = a, .b = b });
263 return b;
264 } catch (const InvalidOperationTag& e) {
265 events.emit({ .operation = AluOperation::NOT, .a = a, .error = true });
266 throw AluException("NOT, " + std::string(e.what()));
267 }
268}
269
281{
282 try {
283 MemoryValue c = a << b; // This will throw if the tags do not match or are FF.
284 uint128_t a_num = static_cast<uint128_t>(a.as_ff());
285 uint128_t b_num = static_cast<uint128_t>(b.as_ff());
286 uint128_t max_bits = static_cast<uint128_t>(get_tag_bits(a.get_tag()));
287
288 bool overflow = b_num > max_bits;
289 uint128_t a_lo_bits = overflow ? max_bits : max_bits - b_num;
290 // We cast to uint256_t to be sure that the shift 1 << a_lo_bits has a defined behaviour.
291 // 1 << 128 is undefined behavior on uint128_t.
292 const uint128_t mask =
293 static_cast<uint128_t>((static_cast<uint256_t>(1) << static_cast<uint256_t>(a_lo_bits)) - 1);
294 // Make use of x % pow_of_two = x & (pow_of_two - 1)
295 uint128_t a_lo = overflow ? b_num - max_bits : a_num & mask;
296 uint128_t a_hi = a_lo_bits >= 128 ? 0 : a_num >> a_lo_bits; // 128-bit shift undefined behaviour guard.
297 uint128_t a_hi_bits = overflow ? max_bits : b_num;
298
299 range_check.assert_range(a_lo, static_cast<uint8_t>(a_lo_bits));
300 range_check.assert_range(a_hi, static_cast<uint8_t>(a_hi_bits));
301 events.emit({ .operation = AluOperation::SHL, .a = a, .b = b, .c = c });
302 return c;
303 } catch (const TagMismatchException& e) {
304 events.emit({ .operation = AluOperation::SHL, .a = a, .b = b, .error = true });
305 throw AluException("SHL, " + std::string(e.what()));
306 } catch (const InvalidOperationTag& e) {
307 events.emit({ .operation = AluOperation::SHL, .a = a, .b = b, .error = true });
308 throw AluException("SHL, " + std::string(e.what()));
309 }
310}
311
323{
324 try {
325 MemoryValue c = a >> b; // This will throw if the tags do not match or are FF.
326 uint128_t a_num = static_cast<uint128_t>(a.as_ff());
327 uint128_t b_num = static_cast<uint128_t>(b.as_ff());
328 uint128_t max_bits = static_cast<uint128_t>(get_tag_bits(a.get_tag()));
329
330 bool overflow = b_num > max_bits;
331 uint128_t a_lo_bits = overflow ? max_bits : b_num;
332 // We cast to uint256_t to be sure that the shift 1 << a_lo_bits has a defined behaviour.
333 // 1 << 128 is undefined behavior on uint128_t.
334 const uint128_t mask =
335 static_cast<uint128_t>((static_cast<uint256_t>(1) << static_cast<uint256_t>(a_lo_bits)) - 1);
336 // Make use of x % pow_of_two = x & (pow_of_two - 1)
337 uint128_t a_lo = overflow ? b_num - max_bits : a_num & mask;
338 uint128_t a_hi = a_lo_bits >= 128 ? 0 : a_num >> a_lo_bits; // 128-bit shift undefined behaviour guard.
339 uint128_t a_hi_bits = overflow ? max_bits : max_bits - b_num;
340
341 range_check.assert_range(a_lo, static_cast<uint8_t>(a_lo_bits));
342 range_check.assert_range(a_hi, static_cast<uint8_t>(a_hi_bits));
343 events.emit({ .operation = AluOperation::SHR, .a = a, .b = b, .c = c });
344 return c;
345 } catch (const TagMismatchException& e) {
346 events.emit({ .operation = AluOperation::SHR, .a = a, .b = b, .error = true });
347 throw AluException("SHR, " + std::string(e.what()));
348 } catch (const InvalidOperationTag& e) {
349 events.emit({ .operation = AluOperation::SHR, .a = a, .b = b, .error = true });
350 throw AluException("SHR, " + std::string(e.what()));
351 }
352}
353
362{
364
365 // Circuit leakage: range check for `mid` value defined by a = ic + mid * 2^dst_tag_bits + hi_128 * 2^128 and
366 // mid is (128 - dst_tag_bits) bits.
367 bool is_trivial = dst_tag == MemoryTag::FF || static_cast<uint256_t>(a) <= get_tag_max_value(dst_tag);
368 if (!is_trivial) {
369 uint128_t a_lo = 0;
370 if (static_cast<uint256_t>(a) >= (static_cast<uint256_t>(1) << 128)) {
371 // Canonical decomposition
372 U256Decomposition decomposition = field_gt.canon_dec(a);
373 a_lo = decomposition.lo;
374 } else {
375 a_lo = static_cast<uint128_t>(a);
376 }
377
378 // cpp standard on bitwise shifts:
379 // "If the value of rhs is negative or is not less than the number of bits in lhs, the behavior is undefined."
380 // For this reason, we handle the case where dst_tag is U128 separately as shifting of 128 bits is undefined.
381 const uint128_t mid = dst_tag == MemoryTag::U128 ? 0 : a_lo >> get_tag_bits(dst_tag);
382 range_check.assert_range(mid, 128 - get_tag_bits(dst_tag));
383 }
384
385 // We put dst_tag in b to have correct deduplication and also to encode it in the event.
386 // Note however that in alu subtrace, dst_tag will be set in ia_tag.
387 events.emit({ .operation = AluOperation::TRUNCATE,
389 .b = MemoryValue::from_tag(MemoryTag::FF, static_cast<uint8_t>(dst_tag)),
390 .c = c });
391 return c;
392}
393
394} // namespace bb::avm2::simulation
MemoryTag dst_tag
static TaggedValue from_tag_truncating(ValueTag tag, FF value)
static TaggedValue from_tag(ValueTag tag, FF value)
MemoryValue lte(const MemoryValue &a, const MemoryValue &b) override
Check if the first memory value is less than or equal to the second and emit an event of type AluEven...
Definition alu.cpp:235
MemoryValue fdiv(const MemoryValue &a, const MemoryValue &b) override
Perform field division on two memory values and emit an event of type AluEvent.
Definition alu.cpp:158
MemoryValue truncate(const FF &a, MemoryTag dst_tag) override
Truncate a field element to a specific memory tag and emit an event of type AluEvent.
Definition alu.cpp:361
MemoryValue eq(const MemoryValue &a, const MemoryValue &b) override
Check if two memory values are equal and emit an event of type AluEvent.
Definition alu.cpp:191
MemoryValue lt(const MemoryValue &a, const MemoryValue &b) override
Check if the first memory value is less than the second and emit an event of type AluEvent.
Definition alu.cpp:212
MemoryValue shl(const MemoryValue &a, const MemoryValue &b) override
Perform left shift operation on a memory value and emit an event of type AluEvent.
Definition alu.cpp:280
MemoryValue add(const MemoryValue &a, const MemoryValue &b) override
Add two memory values and emit an event of type AluEvent.
Definition alu.cpp:24
MemoryValue sub(const MemoryValue &a, const MemoryValue &b) override
Subtract two memory values and emit an event of type AluEvent.
Definition alu.cpp:44
FieldGreaterThanInterface & field_gt
Definition alu.hpp:40
MemoryValue mul(const MemoryValue &a, const MemoryValue &b) override
Multiply two memory values and emit an event of type AluEvent.
Definition alu.cpp:64
MemoryValue shr(const MemoryValue &a, const MemoryValue &b) override
Perform right shift operation on a memory value and emit an event of type AluEvent.
Definition alu.cpp:322
EventEmitterInterface< AluEvent > & events
Definition alu.hpp:42
MemoryValue op_not(const MemoryValue &a) override
Perform bitwise NOT operation on a memory value and emit an event of type AluEvent.
Definition alu.cpp:258
GreaterThanInterface & greater_than
Definition alu.hpp:39
MemoryValue div(const MemoryValue &a, const MemoryValue &b) override
Divide two memory values and emit an event of type AluEvent.
Definition alu.cpp:108
virtual U256Decomposition canon_dec(const FF &a)=0
virtual bool gt(const FF &a, const FF &b)=0
FF a
FF b
AVM range check gadget for witness generation.
U128Decomposition decompose_128(const uint128_t &x)
constexpr uint128_t MASK_64
Definition constants.hpp:23
AvmFlavorSettings::FF FF
Definition field.hpp:10
uint8_t get_tag_bits(ValueTag tag)
uint256_t get_tag_max_value(ValueTag tag)
unsigned __int128 uint128_t
Definition serialize.hpp:45