Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
byte_array.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 "byte_array.hpp"
8
9#include "../circuit_builders/circuit_builders.hpp"
11
12using namespace bb;
13
14namespace bb::stdlib {
15
22template <typename Builder>
23byte_array<Builder>::byte_array(Builder* parent_context, std::vector<uint8_t> const& input)
24 : context(parent_context)
25 , values(input.size())
26{
28 for (size_t i = 0; i < input.size(); ++i) {
29 uint8_t c = input[i];
31 value.create_range_constraint(8, "byte_array: vector entry larger than 1 byte.");
32 values[i] = value;
33 }
35}
42template <typename Builder>
43byte_array<Builder>::byte_array(Builder* parent_context, const std::string& input)
44 : byte_array(parent_context, std::vector<uint8_t>(input.begin(), input.end()))
45{}
46
52template <typename Builder>
53byte_array<Builder> byte_array<Builder>::from_constants(Builder* parent_context, std::vector<uint8_t> const& input)
54{
55 bytes_t const_values;
56 const_values.reserve(input.size());
57 for (const auto& byte : input) {
58 // Create constant field elements - no witness, no constraints
59 const_values.push_back(field_t<Builder>(parent_context, byte));
60 }
61 return byte_array(parent_context, const_values);
109template <typename Builder>
111 const size_t num_bytes,
113{
115
116 static constexpr size_t max_num_bytes = 32;
117 static constexpr size_t midpoint = max_num_bytes / 2;
118 constexpr uint256_t one(1);
119
120 BB_ASSERT_LTE(num_bytes, max_num_bytes);
121 // If a test 256-bit is injected, use it to create a byte decomposition.
122 const uint256_t value = test_val.has_value() ? *test_val : static_cast<uint256_t>(input.get_value());
123
124 values.resize(num_bytes);
125
126 // To optimize the computation of the reconstructed_hi and reconstructed_lo, we use the `field_t::accumulate`
127 // method.
128 std::vector<field_t> accumulator_lo;
129 std::vector<field_t> accumulator_hi;
130
131 context = input.get_context();
132
133 for (size_t i = 0; i < num_bytes; ++i) {
134
135 size_t bit_start = (num_bytes - i - 1) * 8;
136 size_t bit_end = bit_start + 8;
137 const field_t scaling_factor = one << bit_start;
138
139 // Extract the current byte
140 const uint256_t byte_val = value.slice(bit_start, bit_end);
141 // Ensure that current `byte_array` element is a witness iff input is a witness and that it is range
142 // constrained.
143 field_t byte = input.is_constant() ? field_t(byte_val) : witness_t(context, byte_val);
144 byte.create_range_constraint(8, "byte_array: byte extraction failed.");
145 values[i] = byte;
146
147 if (i < midpoint) {
148 accumulator_hi.push_back(scaling_factor * byte);
149 } else {
150 accumulator_lo.push_back(scaling_factor * byte);
151 }
152 }
153
154 // Reconstruct the high and low limbs of input from the byte decomposition
155 const field_t reconstructed_lo = field_t::accumulate(accumulator_lo);
156 const field_t reconstructed_hi = field_t::accumulate(accumulator_hi);
157 const field_t reconstructed = reconstructed_hi + reconstructed_lo;
158
159 // Ensure that the reconstruction succeeded
160 input.assert_equal(reconstructed);
161
162 // Handle the case when the decomposition is not unique
163 if (num_bytes == 32) {
164 // For a modulus `r`, split `r - 1` into limbs
165 constexpr uint256_t modulus_minus_one = fr::modulus - 1;
166 constexpr uint256_t s_lo = modulus_minus_one.slice(0, 128);
167 constexpr uint256_t s_hi = modulus_minus_one.slice(128, 256);
168 const uint256_t shift = uint256_t(1) << 128;
169
170 // Ensure that `(r - 1).lo + 2 ^ 128 - reconstructed_lo` is a 129 bit integer by slicing it into a 128- and 1-
171 // bit chunks.
172 const field_t diff_lo = -reconstructed_lo + s_lo + shift;
173 const uint256_t diff_lo_value = diff_lo.get_value();
174
175 // Extract the "borrow" bit
176 const uint256_t diff_lo_hi_value = (diff_lo_value >> 128);
177 const field_t diff_lo_hi =
178 input.is_constant() ? field_t(diff_lo_hi_value) : witness_t<Builder>(context, diff_lo_hi_value);
179 diff_lo_hi.create_range_constraint(1, "byte_array: y_overlap is not a bit");
180
181 // Extract first 128 bits of `diff_lo`
182 const uint256_t lo_mask = shift - 1;
183 const uint256_t lo = diff_lo_value & lo_mask;
184 const field_t diff_lo_lo = input.is_constant() ? field_t(lo) : witness_t(context, lo);
185 diff_lo_lo.create_range_constraint(128, "byte_array: y_remainder doesn't fit in 128 bits.");
186
187 // Both chunks were computed out-of-circuit - need to constrain. The range constraints above ensure that
188 // they are not overlapping.
189 diff_lo.assert_equal(diff_lo_lo + diff_lo_hi * shift);
190
191 const field_t overlap = -diff_lo_hi + 1;
192 // Ensure that (r - 1).hi - reconstructed_hi/shift - overlap is positive.
193 // SAFETY: reconstructed_hi is always a multiple of 2^128 by construction
194 // (bytes 0-15 all have scaling factors ≥ 2^128)
195 const field_t diff_hi = (-reconstructed_hi / shift).add_two(s_hi, -overlap);
196 diff_hi.create_range_constraint(128, "byte_array: y_hi doesn't fit in 128 bits.");
197 }
198
199 set_origin_tag(input.tag);
200}
201
202template <typename Builder>
203byte_array<Builder>::byte_array(Builder* parent_context, bytes_t const& input)
204 : context(parent_context)
205 , values(input)
206{}
207
208template <typename Builder>
210 : context(parent_context)
211 , values(std::move(input))
212{}
213
214template <typename Builder>
216 : context(other.context)
217{
218 std::copy(other.values.begin(), other.values.end(), std::back_inserter(values));
219}
220
221template <typename Builder>
223 : context(other.context)
224 , values(std::move(other.values))
225{}
226
227template <typename Builder> byte_array<Builder>& byte_array<Builder>::operator=(const byte_array& other)
228{
229 context = other.context;
231 std::copy(other.values.begin(), other.values.end(), std::back_inserter(values));
232 return *this;
233}
234
235template <typename Builder> byte_array<Builder>& byte_array<Builder>::operator=(byte_array&& other) noexcept
236{
237 context = other.context;
238 values = std::move(other.values);
239 return *this;
240}
241
246template <typename Builder> byte_array<Builder>::operator field_t<Builder>() const
247{
248 const size_t bytes = values.size();
249 static constexpr uint256_t one(1);
250 std::vector<field_t<Builder>> scaled_values;
251
252 for (size_t i = 0; i < bytes; ++i) {
253 const field_t<Builder> scaling_factor(one << (8 * (bytes - i - 1)));
254 scaled_values.push_back(values[i] * scaling_factor);
255 }
256
257 return field_t<Builder>::accumulate(scaled_values);
258}
259
263template <typename Builder> byte_array<Builder>& byte_array<Builder>::write(byte_array const& other)
264{
265 values.insert(values.end(), other.bytes().begin(), other.bytes().end());
266 return *this;
267}
268
272template <typename Builder> byte_array<Builder>& byte_array<Builder>::write_at(byte_array const& other, size_t index)
273{
274 BB_ASSERT_LTE(index + other.values.size(), values.size());
275 for (size_t i = 0; i < other.values.size(); i++) {
276 values[i + index] = other.values[i];
277 }
278 return *this;
279}
280
284template <typename Builder> byte_array<Builder> byte_array<Builder>::slice(size_t offset) const
285{
286 BB_ASSERT_LTE(offset, values.size());
287 return byte_array(context, bytes_t(values.begin() + static_cast<ptrdiff_t>(offset), values.end()));
288}
289
294template <typename Builder> byte_array<Builder> byte_array<Builder>::slice(size_t offset, size_t length) const
295{
296 BB_ASSERT_LTE(offset, values.size());
297 // it's <= cause vector constructor doesn't include end point
298 BB_ASSERT_LTE(length, values.size() - offset);
299 auto start = values.begin() + static_cast<ptrdiff_t>(offset);
300 auto end = values.begin() + static_cast<ptrdiff_t>((offset + length));
301 return byte_array(context, bytes_t(start, end));
302}
303
307template <typename Builder> byte_array<Builder> byte_array<Builder>::reverse() const
308{
309 if (values.empty()) {
310 return *this;
311 }
312 bytes_t bytes(values.size());
313 size_t offset = bytes.size() - 1;
314 for (size_t i = 0; i < bytes.size(); i += 1, offset -= 1) {
315 bytes[offset] = values[i];
316 }
317 return byte_array(context, bytes);
318}
319
324template <typename Builder> std::vector<uint8_t> byte_array<Builder>::get_value() const
325{
326 const size_t length = values.size();
327 std::vector<uint8_t> bytes(length, 0);
328 for (size_t i = 0; i < length; ++i) {
329 bytes[i] = static_cast<uint8_t>(values[i].get_value());
330 }
331 return bytes;
332}
333
336
337} // namespace bb::stdlib
#define BB_ASSERT(expression,...)
Definition assert.hpp:67
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:152
constexpr uint256_t slice(uint64_t start, uint64_t end) const
Represents a dynamic array of bytes in-circuit.
byte_array(byte_array &&other) noexcept
byte_array slice(size_t offset) const
Slice bytes from the byte array starting at offset. Does not add any constraints.
byte_array reverse() const
Reverse the bytes in the byte array.
byte_array & write_at(byte_array const &other, size_t index)
Overwrites this byte_array starting at index with the contents of other.
byte_array & write(byte_array const &other)
Appends the contents of another byte_array (other) to the end of this one.
byte_array & operator=(const byte_array &other)
std::vector< uint8_t > get_value() const
A helper converting a byte_array into the vector of its uint8_t values.
bytes_t const & bytes() const
static byte_array from_constants(Builder *parent_context, std::vector< uint8_t > const &input)
Create a byte_array from constant values without adding range constraints.
void set_free_witness_tag()
Set the free witness flag for the byte array.
byte_array(Builder *parent_context, bytes_t const &input)
typename std::vector< field_t< Builder > > bytes_t
void assert_equal(const field_t &rhs, std::string const &msg="field_t::assert_equal") const
Copy constraint: constrain that *this field is equal to rhs element.
Definition field.cpp:930
static field_t accumulate(const std::vector< field_t > &input)
Efficiently compute the sum of vector entries. Using big_add_gate we reduce the number of gates neede...
Definition field.cpp:1167
void create_range_constraint(size_t num_bits, std::string const &msg="field_t::range_constraint") const
Let x = *this.normalize(), constrain x.v < 2^{num_bits}.
Definition field.cpp:909
Builder * get_context() const
Definition field.hpp:419
bb::fr get_value() const
Given a := *this, compute its value given by a.v * a.mul + a.add.
Definition field.cpp:828
bool is_constant() const
Definition field.hpp:429
StrictMock< MockContext > context
uint8_t const size_t length
Definition data_store.hpp:9
ssize_t offset
Definition engine.cpp:36
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static constexpr uint256_t modulus