Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
safe_uint.cpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: not started, auditors: [], date: YYYY-MM-DD }
3// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
5// =====================
6
7#include "safe_uint.hpp"
8#include "../bool/bool.hpp"
9#include "../circuit_builders/circuit_builders.hpp"
12
13namespace bb::stdlib {
14
15template <typename Builder>
16
18{
19 return safe_uint_t((value + other.value), current_max + other.current_max, IS_UNSAFE);
20}
21
22template <typename Builder> safe_uint_t<Builder> safe_uint_t<Builder>::operator*(const safe_uint_t& other) const
23{
24
25 uint512_t new_max = uint512_t(current_max) * uint512_t(other.current_max);
26 BB_ASSERT_EQ(new_max.hi, 0U);
27 return safe_uint_t((value * other.value), new_max.lo, IS_UNSAFE);
28}
29
40template <typename Builder>
42 const size_t difference_bit_size,
43 std::string const& description) const
44{
45 BB_ASSERT_LTE(difference_bit_size, MAX_BIT_NUM);
46 ASSERT(!(this->value.is_constant() && other.value.is_constant()));
47
48 field_ct difference_val = this->value - other.value;
49 // Creates the range constraint that difference_val is in [0, (1<<difference_bit_size) - 1].
50 safe_uint_t<Builder> difference(difference_val, difference_bit_size, format("subtract: ", description));
51 // It is possible for underflow to happen and the range constraint to not catch it.
52 // This is when (a - b) + modulus <= (1<<difference_bit_size) - 1 (or difference.current_max)
53 // Checking that difference.current_max + max of (b - a) >= modulus will ensure that underflow is caught in all
54 // cases
55 if (difference.current_max + other.current_max > MAX_VALUE)
56 throw_or_abort("maximum value exceeded in safe_uint subtract");
57 return difference;
58}
59
74template <typename Builder> safe_uint_t<Builder> safe_uint_t<Builder>::operator-(const safe_uint_t& other) const
75{
76 // If both are constants and the operation is an underflow, throw an error since circuit itself underflows
77 ASSERT(!(this->value.is_constant() && other.value.is_constant() &&
78 static_cast<uint256_t>(value.get_value()) < static_cast<uint256_t>(other.value.get_value())));
79
80 field_ct difference_val = this->value - other.value;
81
82 // safe_uint_t constructor creates a range constraint which checks that `difference_val` is within [0,
83 // current_max].
84 safe_uint_t<Builder> difference(difference_val, (size_t)(current_max.get_msb() + 1), "- operator");
85
86 // Call the two operands a and b. If this operations is underflow and the range constraint fails to catch it,
87 // this means that (a-b) + modulus is IN the range [0, a.current_max].
88 // This is equivalent to the condition that (a - b) + modulus <= a.current_max.
89 // IF b.current_max >= modulus - a.current_max, then it is possible for this condition to be true
90 // because we can let a be 0, and b be b.current_max -> (0 - b.current_max) + modulus <= a.current_max is true.
91 // IF b.current_max < modulus - a.current_max, it is impossible for underflow to happen, no matter how you set a and
92 // b. Therefore, we check that b.current_max >= modulus - a.current_max, which is equivalent to
93 // difference.current_max + other.current_max > MAX_VALUE Note that we will throw an error sometimes even if a-b is
94 // not an underflow but we cannot distinguish it from a case that underflows, so we must throw an error.
95 if (difference.current_max + other.current_max > MAX_VALUE)
96 throw_or_abort("maximum value exceeded in safe_uint minus operator");
97 return difference;
98}
99
111template <typename Builder>
113 const safe_uint_t& other,
114 const size_t quotient_bit_size,
115 const size_t remainder_bit_size,
116 std::string const& description,
117 const std::function<std::pair<uint256_t, uint256_t>(uint256_t, uint256_t)>& get_quotient) const
118{
119 BB_ASSERT_EQ(this->value.is_constant(), false);
120 BB_ASSERT_LTE(quotient_bit_size, MAX_BIT_NUM);
121 BB_ASSERT_LTE(remainder_bit_size, MAX_BIT_NUM);
122 uint256_t val = this->value.get_value();
123 auto [quotient_val, remainder_val] = get_quotient(val, (uint256_t)other.value.get_value());
124 field_ct quotient_field(witness_t(value.context, quotient_val));
125 field_ct remainder_field(witness_t(value.context, remainder_val));
126 safe_uint_t<Builder> quotient(quotient_field, quotient_bit_size, format("divide method quotient: ", description));
127 safe_uint_t<Builder> remainder(
128 remainder_field, remainder_bit_size, format("divide method remainder: ", description));
129
130 const auto merged_tag = OriginTag(get_origin_tag(), other.get_origin_tag());
131 quotient.set_origin_tag(merged_tag);
132 remainder.set_origin_tag(merged_tag);
133
134 // This line implicitly checks we are not overflowing
135 safe_uint_t int_val = quotient * other + remainder;
136
137 // We constrain divisor - remainder - 1 to be non-negative to ensure that remainder < divisor.
138 // Define remainder_plus_one to avoid multiple subtractions
139 const safe_uint_t<Builder> remainder_plus_one = remainder + 1;
140 // Subtraction of safe_uint_t's imposes the desired range constraint
141 other - remainder_plus_one;
142
143 this->assert_equal(int_val, "divide method quotient and/or remainder incorrect");
144
145 return quotient;
146}
147
155template <typename Builder> safe_uint_t<Builder> safe_uint_t<Builder>::operator/(const safe_uint_t& other) const
156{
157 BB_ASSERT_EQ(this->value.is_constant(), false);
158
159 uint256_t val = this->value.get_value();
160 auto [quotient_val, remainder_val] = val.divmod((uint256_t)other.value.get_value());
161 field_ct quotient_field(witness_t(value.context, quotient_val));
162 field_ct remainder_field(witness_t(value.context, remainder_val));
163 safe_uint_t<Builder> quotient(quotient_field, (size_t)(current_max.get_msb() + 1), format("/ operator quotient"));
164 safe_uint_t<Builder> remainder(
165 remainder_field, (size_t)(other.current_max.get_msb() + 1), format("/ operator remainder"));
166
167 const auto merged_tag = OriginTag(get_origin_tag(), other.get_origin_tag());
168 quotient.set_origin_tag(merged_tag);
169 remainder.set_origin_tag(merged_tag);
170
171 // This line implicitly checks we are not overflowing
172 safe_uint_t int_val = quotient * other + remainder;
173
174 // We constrain divisor - remainder - 1 to be non-negative to ensure that remainder < divisor.
175 // // define remainder_plus_one to avoid multiple subtractions
176 const safe_uint_t<Builder> remainder_plus_one = remainder + 1;
177 // // subtraction of safe_uint_t's imposes the desired range constraint
178 other - remainder_plus_one;
179
180 this->assert_equal(int_val, "/ operator quotient and/or remainder incorrect");
181
182 return quotient;
183}
184
185template <typename Builder> safe_uint_t<Builder> safe_uint_t<Builder>::normalize() const
186{
187 auto norm_value = value.normalize();
188 return safe_uint_t(norm_value, current_max, IS_UNSAFE);
189}
190
191template <typename Builder> void safe_uint_t<Builder>::assert_is_zero(std::string const& msg) const
192{
193 value.assert_is_zero(msg);
194}
195
196template <typename Builder> void safe_uint_t<Builder>::assert_is_not_zero(std::string const& msg) const
197{
198 value.assert_is_not_zero(msg);
199}
200
201template <typename Builder> bool_t<Builder> safe_uint_t<Builder>::is_zero() const
202{
203 return value.is_zero();
204}
205
206template <typename Builder> bb::fr safe_uint_t<Builder>::get_value() const
207{
208 return value.get_value();
209}
210
211template <typename Builder> bool_t<Builder> safe_uint_t<Builder>::operator==(const safe_uint_t& other) const
212{
213 return value == other.value;
214}
215
216template <typename Builder> bool_t<Builder> safe_uint_t<Builder>::operator!=(const safe_uint_t& other) const
217{
218 return !operator==(other);
219}
220template <typename Builder>
221std::array<safe_uint_t<Builder>, 3> safe_uint_t<Builder>::slice(const uint8_t msb, const uint8_t lsb) const
222{
223 BB_ASSERT_GTE(msb, lsb);
225 const safe_uint_t lhs = *this;
226 Builder* ctx = lhs.get_context();
227
228 const uint256_t value = uint256_t(get_value());
229 // This should be caught by the proof itself, but the circuit creator will have now way of knowing where the issue
230 // is
232 const auto msb_plus_one = uint32_t(msb) + 1;
233 const auto hi_mask = ((uint256_t(1) << (256 - uint32_t(msb))) - 1);
234 const auto hi = (value >> msb_plus_one) & hi_mask;
235
236 const auto lo_mask = (uint256_t(1) << lsb) - 1;
237 const auto lo = value & lo_mask;
238
239 const auto slice_mask = ((uint256_t(1) << (uint32_t(msb - lsb) + 1)) - 1);
240 const auto slice = (value >> lsb) & slice_mask;
241 safe_uint_t lo_wit, slice_wit, hi_wit;
242 if (this->value.is_constant()) {
243 hi_wit = safe_uint_t(hi);
244 lo_wit = safe_uint_t(lo);
245 slice_wit = safe_uint_t(slice);
246
247 } else {
248 hi_wit = safe_uint_t(witness_t(ctx, hi), grumpkin::MAX_NO_WRAP_INTEGER_BIT_LENGTH - uint32_t(msb), "hi_wit");
249 lo_wit = safe_uint_t(witness_t(ctx, lo), lsb, "lo_wit");
250 slice_wit = safe_uint_t(witness_t(ctx, slice), msb_plus_one - lsb, "slice_wit");
251 }
252 assert_equal(((hi_wit * safe_uint_t(uint256_t(1) << msb_plus_one)) + lo_wit +
253 (slice_wit * safe_uint_t(uint256_t(1) << lsb))));
254
255 std::array<safe_uint_t, 3> result = { lo_wit, slice_wit, hi_wit };
256 OriginTag tag = get_origin_tag();
257 for (auto& element : result) {
258 element.set_origin_tag(tag);
259 }
260 return result;
261}
262
265
266} // namespace bb::stdlib
#define BB_ASSERT_GTE(left, right,...)
Definition assert.hpp:133
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:88
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:163
#define BB_ASSERT_LT(left, right,...)
Definition assert.hpp:148
#define ASSERT(expression,...)
Definition assert.hpp:77
constexpr std::pair< uint256_t, uint256_t > divmod(const uint256_t &b) const
constexpr uint64_t get_msb() const
Implements boolean logic in-circuit.
Definition bool.hpp:59
bb::fr get_value() const
Given a := *this, compute its value given by a.v * a.mul + a.add.
Definition field.cpp:829
bool is_constant() const
Definition field.hpp:407
void assert_is_zero(std::string const &msg="safe_uint_t::assert_is_zero") const
safe_uint_t subtract(const safe_uint_t &other, const size_t difference_bit_size, std::string const &description="") const
Subtraction when you have a pre-determined bound on the difference size.
Definition safe_uint.cpp:41
safe_uint_t operator/(const safe_uint_t &other) const
Potentially less efficient than divide function - bounds remainder and quotient by max of this.
bool_ct is_zero() const
OriginTag get_origin_tag() const
safe_uint_t normalize() const
std::array< safe_uint_t< Builder >, 3 > slice(const uint8_t msb, const uint8_t lsb) const
bool_ct operator==(const safe_uint_t &other) const
safe_uint_t operator-(const safe_uint_t &other) const
Subtraction on two safe_uint_t objects.
Definition safe_uint.cpp:74
void set_origin_tag(OriginTag tag) const
Builder * get_context() const
safe_uint_t operator*(const safe_uint_t &other) const
Definition safe_uint.cpp:22
safe_uint_t operator+(const safe_uint_t &other) const
Definition safe_uint.cpp:17
bool_ct operator!=(const safe_uint_t &other) const
safe_uint_t divide(const safe_uint_t &other, const size_t quotient_bit_size, const size_t remainder_bit_size, std::string const &description="", const std::function< std::pair< uint256_t, uint256_t >(uint256_t, uint256_t)> &get_quotient=[](uint256_t val, uint256_t divisor) { return std::make_pair((uint256_t)(val/(uint256_t) divisor),(uint256_t)(val %(uint256_t) divisor));}) const
division when you have a pre-determined bound on the sizes of the quotient and remainder
bb::fr get_value() const
void assert_is_not_zero(std::string const &msg="safe_uint_t::assert_is_not_zero") const
std::string format(Args... args)
Definition log.hpp:21
constexpr size_t MAX_NO_WRAP_INTEGER_BIT_LENGTH
Definition grumpkin.hpp:15
uintx< uint256_t > uint512_t
Definition uintx.hpp:307
std::conditional_t< IsGoblinBigGroup< C, Fq, Fr, G >, element_goblin::goblin_element< C, goblin_field< C >, Fr, G >, element_default::element< C, Fq, Fr, G > > element
element wraps either element_default::element or element_goblin::goblin_element depending on parametr...
C slice(C const &container, size_t start)
Definition container.hpp:9
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
BB_INLINE constexpr bool is_zero() const noexcept
void throw_or_abort(std::string const &err)