Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 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 @@ -55,16 +55,15 @@ void create_ecdsa_verify_constraints(typename Curve::Builder& builder,

// Lambda to convert std::vector<field_ct> to byte_array_ct
auto fields_to_bytes = [](Builder& builder, std::vector<field_ct>& fields) -> byte_array_ct {
byte_array_ct result(&builder);
for (auto& field : fields) {
// Create an empty byte_array
byte_array_ct out(&builder, std::vector<uint8_t>());
for (const auto& field : fields) {
// Construct byte array of length 1 from the field element
// The constructor enforces that `field` fits in one byte
byte_array_ct byte_to_append(field, /*num_bytes=*/1);
// Append the new byte to the result
result.write(byte_to_append);
// The constructor enforces that `field` fits in one byte and adds range constraints
out.write(byte_array_ct(field, /*num_bytes=*/1));
}

return result;
// Create byte_array from the constrained bytes
return out;
};

// Define builder variables based on the witness indices
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
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ std::array<field_t<C>, 2> schnorr_verify_signature_internal(const byte_array<C>&
auto x_3 = cycle_group<C>::batch_mul({ g1, pub_key }, { sig.s, sig.e }).x;
// build input (pedersen(([s]g + [e]pub).x | pub.x | pub.y) | message) to hash function
// pedersen hash ([r].x | pub.x) to make sure the size of `hash_input` is <= 64 bytes for a 32 byte message
// Explicit conversion: pedersen_hash returns field_ct, convert to byte_array (32 bytes)
byte_array<C> hash_input(pedersen_hash<C>::hash({ x_3, pub_key.x, pub_key.y }));
// Append message (message parameter is constrained)
hash_input.write(message);

// compute e' = hash(([s]g + [e]pub).x | message)
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 @@ -113,8 +113,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 @@ -129,10 +136,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(input.get_context(), std::vector<uint8_t>()); // Start with empty constrained array
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
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
83 changes: 26 additions & 57 deletions barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,23 +525,16 @@ void keccak<Builder>::sponge_absorb(keccak_state& internal,

template <typename Builder> byte_array<Builder> keccak<Builder>::sponge_squeeze(keccak_state& internal)
{
byte_array_ct result(internal.context);
// Build result by writing constrained byte_arrays
byte_array_ct result(internal.context, std::vector<uint8_t>());

// Each hash limb represents a little-endian integer. Need to reverse bytes before we write into the output array
for (size_t i = 0; i < 4; ++i) {
field_ct output_limb = plookup_read<Builder>::read_from_1_to_2_table(KECCAK_FORMAT_OUTPUT, internal.state[i]);
byte_array_ct limb_bytes(output_limb, 8);
byte_array_ct little_endian_limb_bytes(internal.context, 8);
little_endian_limb_bytes[0] = limb_bytes[7];
little_endian_limb_bytes[1] = limb_bytes[6];
little_endian_limb_bytes[2] = limb_bytes[5];
little_endian_limb_bytes[3] = limb_bytes[4];
little_endian_limb_bytes[4] = limb_bytes[3];
little_endian_limb_bytes[5] = limb_bytes[2];
little_endian_limb_bytes[6] = limb_bytes[1];
little_endian_limb_bytes[7] = limb_bytes[0];
result.write(little_endian_limb_bytes);
result.write(limb_bytes.reverse());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Q: Since output limb is the result of the lookup table, is it range-constrained implicitly? In that case, do we need a way to "opt-out" of range constraining while performing write?

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.

You're right. There's no need to constrain it. Otoh, hash and all its helpers are un-used. So I'd just keep it like that and nuke in a follow-up. We only need keccak1600 from this module.

}

return result;
}

Expand All @@ -568,35 +561,26 @@ template <typename Builder> std::vector<field_t<Builder>> keccak<Builder>::forma
byte_array_ct block_bytes(input);

const size_t byte_difference = max_blocks_length - input_size;
byte_array_ct padding_bytes(ctx, byte_difference);
for (size_t i = 0; i < byte_difference; ++i) {
padding_bytes[i] = witness_ct::create_constant_witness(ctx, 0);
}
// Create constant padding bytes (no constraints needed for constants)
byte_array_ct padding_bytes = byte_array_ct::constant_padding(ctx, byte_difference);
// Safe write: both block_bytes and padding_bytes are constrained
block_bytes.write(padding_bytes);

// Keccak requires that 0x1 is appended after the final byte of input data.
// Similarly, the final byte of the final padded block must be 0x80.
const auto terminating_byte = input_size;
const auto terminating_block_byte = block_bytes.size() - 1;
block_bytes[terminating_byte] = witness_ct::create_constant_witness(ctx, 0x1);
block_bytes[terminating_block_byte] = witness_ct::create_constant_witness(ctx, 0x80);

// keccak lanes interpret memory as little-endian integers,
// means we need to swap our byte ordering...
// The final byte of the final padded block must be 0x80.
byte_array_ct terminating_byte_0x1(field_ct(ctx, 0x1), 1);
byte_array_ct terminating_byte_0x80(field_ct(ctx, 0x80), 1);
block_bytes.write_at(terminating_byte_0x1, input_size);
block_bytes.write_at(terminating_byte_0x80, block_bytes.size() - 1);

// Keccak lanes interpret memory as little-endian integers, so reverse byte order in 8-byte chunks
byte_array_ct reversed_block(ctx, std::vector<uint8_t>());
for (size_t i = 0; i < block_bytes.size(); i += 8) {
std::array<field_ct, 8> temp;
for (size_t j = 0; j < 8; ++j) {
temp[j] = block_bytes[i + j];
}
block_bytes[i] = temp[7];
block_bytes[i + 1] = temp[6];
block_bytes[i + 2] = temp[5];
block_bytes[i + 3] = temp[4];
block_bytes[i + 4] = temp[3];
block_bytes[i + 5] = temp[2];
block_bytes[i + 6] = temp[1];
block_bytes[i + 7] = temp[0];
size_t chunk_size = std::min(size_t(8), block_bytes.size() - i);
reversed_block.write(block_bytes.slice(i, chunk_size).reverse());
}

block_bytes = reversed_block;
const size_t byte_size = block_bytes.size();

const size_t num_limbs = byte_size / WORD_SIZE;
Expand Down Expand Up @@ -683,12 +667,8 @@ stdlib::byte_array<Builder> keccak<Builder>::hash_using_permutation_opcode(byte_

if (ctx == nullptr) {
// if buffer is constant compute hash and return w/o creating constraints
byte_array_ct output(nullptr, 32);
const std::vector<uint8_t> result = hash_native(input.get_value());
for (size_t i = 0; i < 32; ++i) {
output[i] = result[i];
}
return output;
return byte_array_ct(nullptr, result);
}

// convert the input byte array into 64-bit keccak lanes (+ apply padding)
Expand All @@ -708,12 +688,8 @@ template <typename Builder> stdlib::byte_array<Builder> keccak<Builder>::hash(by
auto ctx = input.get_context();

const auto constant_case = [&] { // if buffer is constant, compute hash and return w/o creating constraints
byte_array_ct output(nullptr, 32);
const std::vector<uint8_t> result = hash_native(input.get_value());
for (size_t i = 0; i < 32; ++i) {
output[i] = result[i];
}
return output;
const std::vector<uint8_t> result_uint = hash_native(input.get_value());
return byte_array_ct(ctx, result_uint);
};

if (ctx == nullptr) {
Expand Down Expand Up @@ -765,22 +741,15 @@ template <typename Builder>
stdlib::byte_array<Builder> keccak<Builder>::sponge_squeeze_for_permutation_opcode(
std::array<field_t<Builder>, NUM_KECCAK_LANES> lanes, Builder* context)
{
byte_array_ct result(context);
// Build result by writing constrained byte_arrays
byte_array_ct result(context, std::vector<uint8_t>());

// Each hash limb represents a little-endian integer. Need to reverse bytes before we write into the output array
for (size_t i = 0; i < 4; ++i) {
byte_array_ct limb_bytes(lanes[i], 8);
byte_array_ct little_endian_limb_bytes(context, 8);
little_endian_limb_bytes[0] = limb_bytes[7];
little_endian_limb_bytes[1] = limb_bytes[6];
little_endian_limb_bytes[2] = limb_bytes[5];
little_endian_limb_bytes[3] = limb_bytes[4];
little_endian_limb_bytes[4] = limb_bytes[3];
little_endian_limb_bytes[5] = limb_bytes[2];
little_endian_limb_bytes[6] = limb_bytes[1];
little_endian_limb_bytes[7] = limb_bytes[0];
result.write(little_endian_limb_bytes);
result.write(limb_bytes.reverse());
}

return result;
}

Expand Down
Loading