Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
cf791df
Issue 15 - fix selector order
iakovenkos Oct 21, 2025
1fb4b6e
Issue 12 - use zero_idx() in `normalize`
iakovenkos Oct 21, 2025
a9f6608
Issue 11 - add missing context validation
iakovenkos Oct 21, 2025
6585762
Issue 1 - remove size_t byte_array constructor
iakovenkos Oct 21, 2025
aab257e
making constructors safer and unconstrained behavior more explicit
iakovenkos Oct 21, 2025
ee89b08
fix fuzzing build
iakovenkos Oct 21, 2025
d1dc61e
fix keccak byte_array constructor issues
iakovenkos Oct 21, 2025
bf58e85
Issue 4 - add missing assert to [] operator
iakovenkos Oct 21, 2025
f899a23
Issue 3 - remove assert from byte_array to field conversion
iakovenkos Oct 21, 2025
e897223
remove non-const [] operator
iakovenkos Oct 21, 2025
de5b87e
Secure byte_array API to prevent unconstrained values in hash functions
iakovenkos Oct 22, 2025
61852f2
fuzzer is a friend of byte_array
iakovenkos Oct 22, 2025
378ba22
unfriend byte_array fuzzer, don't fuzz unconstrained private construc…
iakovenkos Oct 22, 2025
5f916a6
Issue 6 - slice() empty array edge case
iakovenkos Oct 22, 2025
f6ab2c1
Merge remote-tracking branch 'origin/merge-train/barretenberg' into s…
iakovenkos Oct 22, 2025
2cf61e6
Issue 10 - remove get_string()
iakovenkos Oct 22, 2025
1ab6b0a
fix fuzzing build
iakovenkos Oct 22, 2025
8d9f4e1
Issue 7 - add a missing divisibility remark
iakovenkos Oct 22, 2025
6cf505d
Issue 14 - return early when reversing empty byte_arrays
iakovenkos Oct 22, 2025
eaa34b7
Issue 13 - byte_array move operator moves the data
iakovenkos Oct 22, 2025
bd21929
Merge remote-tracking branch 'origin/merge-train/barretenberg' into s…
iakovenkos Oct 22, 2025
d0032b2
refactor: make bool_t::conditional_assign always return normalized re…
iakovenkos Oct 23, 2025
843eab6
address review
iakovenkos Oct 29, 2025
fe3c8c1
Merge remote-tracking branch 'origin/merge-train/barretenberg' into s…
iakovenkos Nov 7, 2025
ad1db17
Merge remote-tracking branch 'origin/merge-train/barretenberg' into s…
iakovenkos Nov 7, 2025
7e9fa40
fix blake2s test
iakovenkos Nov 7, 2025
a3cd0f1
sha uses correct byte_array constructor
iakovenkos Nov 7, 2025
eab412c
fix ecdsa constraint
iakovenkos Nov 7, 2025
f95fef5
Fix ecdsa
federicobarbacovi Nov 7, 2025
aeab802
Further fix
federicobarbacovi Nov 7, 2025
8c1f77d
vk update
iakovenkos Nov 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ template <typename Builder> void create_blake2s_constraints(Builder& builder, co
using byte_array_ct = stdlib::byte_array<Builder>;
using field_ct = stdlib::field_t<Builder>;

// Create byte array struct
byte_array_ct arr(&builder);
// Build input byte array by appending constrained byte_arrays
byte_array_ct arr = byte_array_ct::constant_padding(&builder, 0); // Start with empty array

// Get the witness assignment for each witness index
// Write the witness assignment to the byte_array
for (const auto& witness_index_num_bits : constraint.inputs) {
auto witness_index = witness_index_num_bits.blackbox_input;
auto num_bits = witness_index_num_bits.num_bits;
Expand All @@ -31,8 +29,11 @@ template <typename Builder> void create_blake2s_constraints(Builder& builder, co
auto num_bytes = round_to_nearest_byte(num_bits);

field_ct element = to_field_ct(witness_index, builder);

// byte_array_ct(field, num_bytes) constructor adds range constraints for each byte
byte_array_ct element_bytes(element, num_bytes);

// Safe write: both arr and element_bytes are constrained
arr.write(element_bytes);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ template <typename Builder> void create_blake3_constraints(Builder& builder, con
using byte_array_ct = bb::stdlib::byte_array<Builder>;
using field_ct = bb::stdlib::field_t<Builder>;

// Create byte array struct
byte_array_ct arr(&builder);
// Build input byte array by appending constrained byte_arrays
byte_array_ct arr = byte_array_ct::constant_padding(&builder, 0); // Start with empty array

// Get the witness assignment for each witness index
// Write the witness assignment to the byte_array
for (const auto& witness_index_num_bits : constraint.inputs) {
auto witness_index = witness_index_num_bits.blackbox_input;
auto num_bits = witness_index_num_bits.num_bits;
Expand All @@ -30,8 +28,11 @@ template <typename Builder> void create_blake3_constraints(Builder& builder, con
auto num_bytes = round_to_nearest_byte(num_bits);
BB_ASSERT_LTE(num_bytes, 1024U, "barretenberg does not support blake3 inputs with more than 1024 bytes");
field_ct element = to_field_ct(witness_index, builder);

// byte_array_ct(field, num_bytes) constructor adds range constraints for each byte
byte_array_ct element_bytes(element, num_bytes);

// Safe write: both arr and element_bytes are constrained
arr.write(element_bytes);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,40 +68,47 @@ void create_ecdsa_verify_constraints(typename Curve::Builder& builder,
builder, hashed_message_fields, r_fields, s_fields, pub_x_fields, pub_y_fields, result_field);
}

// Step 1.
bool_ct result = static_cast<bool_ct>(result_field); // Constructor enforces result = 0 or 1
bool_ct predicate = static_cast<bool_ct>(predicate_field); // Constructor enforces predicate = 0 or 1

// Step 1: Conditionally assign field values when predicate is false
// There is one remaining edge case that happens with negligible probability, see here:
// https://github.com/AztecProtocol/barretenberg/issues/1570
if (!input.predicate.is_constant) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@federicobarbacovi can you check that this is fine? I think the values need to be conditionally assigned first and then fed to byte_array constructors

// Set first byte of r and s to 1 when predicate is false
r_fields[0] = field_ct::conditional_assign(predicate, r_fields[0], field_ct(1)); // 0 < r < n
s_fields[0] = field_ct::conditional_assign(predicate, s_fields[0], field_ct(1)); // 0 < s < n/2

// Set public key to 2*generator when predicate is false
// Compute as native type to get byte representation
typename Curve::AffineElementNative default_point_native(Curve::g1::one + Curve::g1::one);
std::array<uint8_t, 32> default_x_bytes;
std::array<uint8_t, 32> default_y_bytes;
Curve::fq::serialize_to_buffer(default_point_native.x, default_x_bytes.data());
Curve::fq::serialize_to_buffer(default_point_native.y, default_y_bytes.data());

for (size_t i = 0; i < 32; ++i) {
pub_x_fields[i] = field_ct::conditional_assign(predicate, pub_x_fields[i], field_ct(default_x_bytes[i]));
pub_y_fields[i] = field_ct::conditional_assign(predicate, pub_y_fields[i], field_ct(default_y_bytes[i]));
}
} else {
BB_ASSERT(input.predicate.value, "Creating ECDSA constraints with a constant predicate equal to false.");
}

// Step 2: Convert conditionally-assigned fields to byte arrays (adds range constraints on the correct values)
byte_array_ct hashed_message = fields_to_bytes(builder, hashed_message_fields);
byte_array_ct pub_x_bytes = fields_to_bytes(builder, pub_x_fields);
byte_array_ct pub_y_bytes = fields_to_bytes(builder, pub_y_fields);
byte_array_ct r = fields_to_bytes(builder, r_fields);
byte_array_ct s = fields_to_bytes(builder, s_fields);
bool_ct result = static_cast<bool_ct>(result_field); // Constructor enforces result = 0 or 1
bool_ct predicate = static_cast<bool_ct>(predicate_field); // Constructor enforces predicate = 0 or 1

// Step 2.
// Step 3: Construct public key from byte arrays
Fq pub_x(pub_x_bytes);
Fq pub_y(pub_y_bytes);
// This constructor sets the infinity flag of public_key to false. This is OK because the point at infinity is not a
// point on the curve and we check tha public_key is on the curve.
G1 public_key(pub_x, pub_y);

// Step 3.
// There is one remaining edge case that happens with negligible probability, see here:
// https://github.com/AztecProtocol/barretenberg/issues/1570
if (!input.predicate.is_constant) {
r[0] = field_ct::conditional_assign(predicate, r[0], field_ct(1)); // 0 < r < n
s[0] = field_ct::conditional_assign(predicate, s[0], field_ct(1)); // 0 < s < n/2

// P is on the curve
typename Curve::AffineElement default_point(Curve::g1::one + Curve::g1::one);
// BIGGROUP_AUDITTODO: mutable accessor needed for conditional_assign(). Could add a conditional_assign method
// to biggroup or could just perform these operations on the underlying fields prior to constructing the
// biggroup element.
public_key.x() = Fq::conditional_assign(predicate, public_key.x(), default_point.x());
public_key.y() = Fq::conditional_assign(predicate, public_key.y(), default_point.y());
} else {
BB_ASSERT(input.predicate.value, "Creating ECDSA constraints with a constant predicate equal to false.");
}

// Step 4.
bool_ct signature_result =
stdlib::ecdsa_verify_signature<Builder, Curve, Fq, Fr, G1>(hashed_message, public_key, { r, s });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ static std::vector<field_t<Builder>> fields_from_witnesses(Builder& builder, std
*/
template <typename Builder> byte_array<Builder> fields_to_bytes(Builder& builder, std::vector<field_t<Builder>>& fields)
{
byte_array<Builder> result(&builder);
byte_array<Builder> result = byte_array<Builder>::constant_padding(&builder, /*length*/ 0);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@federicobarbacovi just flagging that now it's the canonical way to create an empty array

for (auto& field : fields) {
// Construct byte array of length 1 from the field element
// The constructor enforces that `field` fits in one byte
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ class BlakeCircuit {
{
Builder builder;

byte_array_ct input_buffer(&builder);
// Build byte array from field elements with proper constraints using write() pattern
byte_array_ct input_buffer(&builder, std::vector<uint8_t>());
for (size_t i = 0; i < NUM_PUBLIC_INPUTS; ++i) {
input_buffer.write(byte_array_ct(field_ct(public_witness_ct(&builder, public_inputs[i]))));
field_ct field_element = public_witness_ct(&builder, public_inputs[i]);
// byte_array_ct(field_t) constructor adds range constraints for each byte
byte_array_ct field_bytes(field_element);
input_buffer.write(field_bytes);
}

bb::stdlib::Blake2s<Builder>::hash(input_buffer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ class EcdsaCircuit {
Builder builder;

// IN CIRCUIT
// Create an input buffer the same size as our inputs
typename curve::byte_array_ct input_buffer(&builder, NUM_PUBLIC_INPUTS);
// Create an input buffer from public inputs (treating each as a single byte)
typename curve::byte_array_ct input_buffer(&builder, std::vector<uint8_t>());
for (size_t i = 0; i < NUM_PUBLIC_INPUTS; ++i) {
input_buffer[i] = public_witness_ct(&builder, public_inputs[i]);
field_ct byte_value = public_witness_ct(&builder, public_inputs[i]);
// Constrain to be a single byte and create byte_array
typename curve::byte_array_ct single_byte(byte_value, 1);
input_buffer.write(single_byte);
}

// This is the message that we would like to confirm
Expand Down
20 changes: 15 additions & 5 deletions barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,15 @@ template <typename Builder> void Blake2s<Builder>::blake2s(blake2s_state& S, byt
// Set last block.
S.f[0] = field_t<Builder>(uint256_t((uint32_t)-1));

byte_array_ct final(in.get_context());
final.write(in.slice(offset)).write(byte_array_ct(in.get_context(), BLAKE2S_BLOCKBYTES - size));
// Build final block: remaining input + constant padding
Builder* ctx = in.get_context();
auto remaining = in.slice(offset);

// Combine remaining bytes and constant padding (no constraints needed for constants)
byte_array_ct final = remaining; // Copy constrained remaining bytes
byte_array_ct padding = byte_array_ct::constant_padding(ctx, BLAKE2S_BLOCKBYTES - size);
final.write(padding);

increment_counter(S, static_cast<uint32_t>(size));
compress(S, final);
}
Expand All @@ -133,10 +140,13 @@ template <typename Builder> byte_array<Builder> Blake2s<Builder>::hash(const byt

blake2s(S, input);

byte_array_ct result(input.get_context());
for (auto h : S.h) {
// Build result from state values
byte_array_ct result = byte_array_ct::constant_padding(input.get_context(), 0);
for (const auto& h : S.h) {
// byte_array_ct(field, num_bytes) constructor adds range constraints for each byte
byte_array_ct v(h, 4);
result.write(v.reverse());
auto reversed = v.reverse();
result.write(reversed);
}
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,28 +76,27 @@ TEST(stdlib_blake2s, test_witness_and_constant)
// create a byte array that is a circuit witness
std::string witness_str = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz";
std::vector<uint8_t> witness_str_vec(witness_str.begin(), witness_str.end());
byte_array_ct witness_str_ct(&builder, witness_str_vec);

// create a byte array that is a circuit constant
std::vector<uint8_t> constant_vec = { '0', '1' };
std::vector<field_ct> constant_vec_field_ct;
for (auto b : constant_vec) {
constant_vec_field_ct.emplace_back(field_ct(bb::fr(b)));
}
byte_array_ct constant_vec_ct(&builder, constant_vec_field_ct);

// create a byte array that is part circuit witness and part circuit constant
byte_array_ct input_arr(&builder);
input_arr.write(witness_str_ct).write(constant_vec_ct);
// start with the witness part, then append constant padding
byte_array_ct input_arr(&builder, witness_str_vec);
input_arr.write(byte_array_ct::constant_padding(&builder, 1, '0'))
.write(byte_array_ct::constant_padding(&builder, 1, '1'));

// hash the combined byte array
byte_array_ct output = stdlib::Blake2s<Builder>::hash(input_arr);
// for expected value calculation
std::vector<uint8_t> constant_vec = { '0', '1' };

// create expected input vector by concatenating witness and constant parts
std::vector<uint8_t> input_v;
input_v.insert(input_v.end(), witness_str_vec.begin(), witness_str_vec.end());
input_v.insert(input_v.end(), constant_vec.begin(), constant_vec.end());

// Verify the circuit input matches the expected input
EXPECT_EQ(input_arr.get_value(), input_v);

// hash the combined byte array
byte_array_ct output = stdlib::Blake2s<Builder>::hash(input_arr);

// compute expected hash
auto expected = crypto::blake2s(input_v);

Expand Down
49 changes: 28 additions & 21 deletions barretenberg/cpp/src/barretenberg/stdlib/hash/blake3s/blake3s.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,15 @@ void Blake3s<Builder>::compress_xof(const field_t<Builder> cv[8],
*/
for (size_t i = 0; i < (BLAKE3_STATE_SIZE >> 1); i++) {
const auto lookup_1 = plookup_read<Builder>::get_lookup_accumulators(BLAKE_XOR, state[i], state[i + 8], true);
// byte_array(field, num_bytes) constructor adds range constraints for each byte
byte_array<Builder> out_bytes_1(lookup_1[ColumnIdx::C3][0], 4);
// Safe write: both out and out_bytes_1 are constrained
out.write_at(out_bytes_1.reverse(), i * 4);

const auto lookup_2 = plookup_read<Builder>::get_lookup_accumulators(BLAKE_XOR, state[i + 8], cv[i], true);
// byte_array(field, num_bytes) constructor adds range constraints for each byte
byte_array<Builder> out_bytes_2(lookup_2[ColumnIdx::C3][0], 4);
// Safe write: both out and out_bytes_2 are constrained
out.write_at(out_bytes_2.reverse(), (i + 8) * 4);
}
}
Expand All @@ -105,17 +109,14 @@ Blake3s<Builder>::output_t Blake3s<Builder>::make_output(const field_t<Builder>
uint8_t block_len,
uint8_t flags)
{
output_t ret;
byte_array_ct block_copy = block;
// Initialize output_t with all fields
output_t ret{ .input_cv = {}, .block = block_copy, .block_len = block_len, .flags = flags };

for (size_t i = 0; i < (BLAKE3_OUT_LEN >> 2); ++i) {
ret.input_cv[i] = input_cv[i];
}

ret.block = byte_array_ct(block.get_context(), BLAKE3_BLOCK_LEN);
for (size_t i = 0; i < BLAKE3_BLOCK_LEN; i++) {
ret.block[i] = block[i];
}
ret.block_len = block_len;
ret.flags = flags;
return ret;
}

Expand All @@ -129,10 +130,8 @@ template <typename Builder> void Blake3s<Builder>::hasher_init(blake3_hasher* se
self->key[i] = field_ct(uint256_t(IV[i]));
self->cv[i] = field_ct(uint256_t(IV[i]));
}
self->buf = byte_array_ct(self->context, BLAKE3_BLOCK_LEN);
for (size_t i = 0; i < BLAKE3_BLOCK_LEN; i++) {
self->buf[i] = field_t<Builder>(self->context, 0);
}
// Create zero-filled constant buffer (no constraints needed)
self->buf = byte_array_ct::constant_padding(self->context, BLAKE3_BLOCK_LEN);
self->buf_len = 0;
self->blocks_compressed = 0;
self->flags = 0;
Expand Down Expand Up @@ -160,9 +159,9 @@ void Blake3s<Builder>::hasher_update(blake3_hasher* self, const byte_array<Build
if (take > input_len) {
take = input_len;
}
for (size_t i = 0; i < take; i++) {
self->buf[self->buf_len + i] = input[i + start_counter];
}
// Copy bytes from input to buf (input is constrained)
byte_array<Builder> input_slice = input.slice(start_counter, take);
self->buf.write_at(input_slice, self->buf_len);

self->buf_len = static_cast<uint8_t>(self->buf_len + (uint8_t)take);
input_len -= take;
Expand All @@ -173,23 +172,31 @@ template <typename Builder> void Blake3s<Builder>::hasher_finalize(const blake3_
uint8_t block_flags = self->flags | maybe_start_flag(self) | CHUNK_END;
output_t output = make_output(self->cv, self->buf, self->buf_len, block_flags);

byte_array_ct wide_buf(out.get_context(), BLAKE3_BLOCK_LEN);
// Create zero-filled constant buffer for compress_xof output (no constraints needed)
byte_array_ct wide_buf = byte_array_ct::constant_padding(out.get_context(), BLAKE3_BLOCK_LEN);
compress_xof(output.input_cv, output.block, output.block_len, output.flags | ROOT, wide_buf);
for (size_t i = 0; i < BLAKE3_OUT_LEN; i++) {
out[i] = wide_buf[i];
}
// Extract the output bytes by slicing (propagates constraint status)
out = wide_buf.slice(0, BLAKE3_OUT_LEN);
}

template <typename Builder> byte_array<Builder> Blake3s<Builder>::hash(const byte_array<Builder>& input)
{
BB_ASSERT(input.size() <= BLAKE3_CHUNK_LEN,
"Barretenberg does not support blake3s with input lengths greater than 1024 bytes.");

blake3_hasher hasher = {};
hasher.context = input.get_context();
// Create zero-filled constant buffer for hasher (will be properly initialized by hasher_init)
Builder* ctx = input.get_context();
byte_array_ct buf = byte_array_ct::constant_padding(ctx, BLAKE3_BLOCK_LEN);

blake3_hasher hasher{
.key = {}, .cv = {}, .buf = buf, .buf_len = 0, .blocks_compressed = 0, .flags = 0, .context = ctx
};

hasher_init(&hasher);
hasher_update(&hasher, input, input.size());
byte_array_ct result(input.get_context(), BLAKE3_OUT_LEN);

// Create output buffer (constants)
byte_array_ct result = byte_array_ct::constant_padding(ctx, BLAKE3_OUT_LEN);
hasher_finalize(&hasher, result);
return result;
}
Expand Down
10 changes: 4 additions & 6 deletions barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,20 +359,18 @@ template <typename Builder> byte_array<Builder> SHA256<Builder>::hash(const byte
rolling_hash = sha256_block(rolling_hash, hash_input);
}

std::vector<field_ct> output;
// Build result by writing constrained byte_arrays
byte_array_ct result = byte_array_ct::constant_padding(ctx, 0);
// Each element of rolling_hash is a 4-byte field_t, decompose rolling hash into bytes.
for (const auto& word : rolling_hash) {
// This constructor constrains
// - word length to be <=4 bytes
// - the element reconstructed from bytes is equal to the given input.
// - each entry to be a byte
byte_array_ct word_byte_decomposition(word, 4);
for (size_t i = 0; i < 4; i++) {
output.push_back(word_byte_decomposition[i]);
}
result.write(word_byte_decomposition);
}
//
return byte_array<Builder>(ctx, output);
return result;
}

template class SHA256<bb::UltraCircuitBuilder>;
Expand Down
Loading
Loading