Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -13,7 +13,7 @@ cd ..
# - Generate a hash for versioning: sha256sum bb-chonk-inputs.tar.gz
# - Upload the compressed results: aws s3 cp bb-chonk-inputs.tar.gz s3://aztec-ci-artifacts/protocol/bb-chonk-inputs-[hash(0:8)].tar.gz
# Note: In case of the "Test suite failed to run ... Unexpected token 'with' " error, need to run: docker pull aztecprotocol/build:3.0
pinned_short_hash="6322e510"
pinned_short_hash="8fa51383"
pinned_chonk_inputs_url="https://aztec-ci-artifacts.s3.us-east-2.amazonaws.com/protocol/bb-chonk-inputs-${pinned_short_hash}.tar.gz"

function compress_and_upload {
Expand Down
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 @@ -25,10 +25,9 @@ using namespace bb;
* coordinates.
* 3. Conditionally select the public key, the signature, and the hash of the message when the predicate is witness
* false. This ensures that the circuit is satisfied when the predicate is false. We set:
* - The first byte of r and s to 1 (NOTE: This only works when the order of the curve divided by two is bigger
* than \f$2^{241}\f$).
* - r = s = H(m) = 1 (the hash is set to 1 to avoid failures in the byte_array constructor)
* - The public key to 2 times the generator of the curve (this is to avoid problems with lookup tables in
* secp265r1).
* secp265r1)
* 4. Verify the signature against the public key and the hash of the message. We return a bool_t bearing witness to
* whether the signature verification was successfull or not.
* 5. Enforce that the result of the signature verification matches the expected result.
Expand Down Expand Up @@ -60,48 +59,55 @@ void create_ecdsa_verify_constraints(typename Curve::Builder& builder,
std::vector<field_ct> pub_x_fields = fields_from_witnesses(builder, input.pub_x_indices);
std::vector<field_ct> pub_y_fields = fields_from_witnesses(builder, input.pub_y_indices);
field_ct result_field = field_ct::from_witness_index(&builder, input.result);
field_ct predicate_field = to_field_ct(input.predicate, builder);
bool_ct predicate(to_field_ct(input.predicate, builder)); // Constructor enforces predicate = 0 or 1

if (!has_valid_witness_assignments) {
// Fill builder variables in case of empty witness assignment
create_dummy_ecdsa_constraint<Curve>(
builder, hashed_message_fields, r_fields, s_fields, pub_x_fields, pub_y_fields, result_field);
}

// Step 1.
// Step 1: Conditionally assign field values when predicate is false
if (!predicate.is_constant()) {
// Set r = s = H(m) = 1 when the predicate is false
for (size_t idx = 0; idx < 32; idx++) {
r_fields[idx] = field_ct::conditional_assign(predicate, r_fields[idx], field_ct(idx == 0 ? 1 : 0));
s_fields[idx] = field_ct::conditional_assign(predicate, s_fields[idx], field_ct(idx == 0 ? 1 : 0));
hashed_message_fields[idx] =
field_ct::conditional_assign(predicate, hashed_message_fields[idx], field_ct(idx == 0 ? 1 : 0));
}

// 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
bool_ct result(result_field); // Constructor enforces result = 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 @@ -34,15 +34,8 @@ using namespace bb;
* predicate is witness false, then the constraint is disabled, i.e it must not fail and can return whatever. When
* `predicate` is set to witness false, we override some values to ensure that all the circuit constraints are
* satisfied:
* - We set the first byte of each component of the signature to 1 (NOTE: This only works when the order of the
* curve divided by two is bigger than \f$2^{241}\f$).
* - We set - r = s = H(m) = 1 (the hash is set to 1 to avoid failures in the byte_array constructor)
* - We set the public key to be 2 times the generator of the curve.
*
* @note There is a small chance that when the predicate is witness false, the circuit still fails. This is due to ECDSA
* verification checking that \f$u_1 * G + u_2 * P\f$ is not the point at infinity. When the predicate is witness false,
* we set \f$P = 2G\f$, so the result of the scalar multiplication is the point at infinity when \f$u_1 + 2 u_2 = H(m)
* * s^{-1} + 2 * r * s^{-1} = 0 \mod n\f$, which means \f$H(m) + 2 * r = 0 \mod n\f$. Given that \f$r\f$ and \f$H(m)\f$
* are both random 256-bit numbers, the probability of this happening is negligible.
*/
struct EcdsaConstraint {
bb::CurveType type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,23 @@ template <class Curve> class EcdsaTestingFunctions {
public:
enum class Target : uint8_t {
None,
R, // Invalidate R component of signature
ZeroS, // Set S=0 (tests ECDSA validation)
HighS, // Set S=high (tests malleability protection)
P, // Invalidate public key
Result // Invalid signature with claimed valid result
HashIsNotAByteArray, // Set one element of the hash > 255
ZeroR, // Set R=0 (tests ECDSA validation)
ZeroS, // Set S=0 (tests ECDSA validation)
HighS, // Set S=high (tests malleability protection)
P, // Invalidate public key
Result // Invalid signature with claimed valid result
};

static std::vector<Target> get_all()
{
return { Target::None, Target::R, Target::ZeroS, Target::HighS, Target::P, Target::Result };
return { Target::None, Target::HashIsNotAByteArray, Target::ZeroR, Target::ZeroS, Target::HighS, Target::P,
Target::Result };
}

static std::vector<std::string> get_labels()
{
return { "None", "R", "Zero S", "High S", "Public key", "Result" };
return { "None", "Hash is not a byte array", "Zero R", "Zero S", "High S", "Public key", "Result" };
}
};

Expand All @@ -63,7 +65,11 @@ template <class Curve> class EcdsaTestingFunctions {
}

switch (invalid_witness_target) {
case InvalidWitness::Target::R:
case InvalidWitness::Target::HashIsNotAByteArray:
// Set first byte of hash to 256 (invalid byte)
witness_values[ecdsa_constraints.hashed_message[0]] = bb::fr(256);
break;
case InvalidWitness::Target::ZeroR:
// Set r = 0 (invalid ECDSA signature component)
for (size_t idx = 0; idx < 32; idx++) {
witness_values[ecdsa_constraints.signature[idx]] = bb::fr(0);
Expand Down Expand Up @@ -179,13 +185,13 @@ TYPED_TEST(EcdsaConstraintsTest, GenerateVKFromConstraints)
TYPED_TEST(EcdsaConstraintsTest, ConstantTrue)
{
BB_DISABLE_ASSERTS();
TestFixture::test_constant_true(TestFixture::InvalidWitnessTarget::R);
TestFixture::test_constant_true(TestFixture::InvalidWitnessTarget::Result);
}

TYPED_TEST(EcdsaConstraintsTest, WitnessTrue)
{
BB_DISABLE_ASSERTS();
TestFixture::test_witness_true(TestFixture::InvalidWitnessTarget::R);
TestFixture::test_witness_true(TestFixture::InvalidWitnessTarget::Result);
}

TYPED_TEST(EcdsaConstraintsTest, WitnessFalse)
Expand All @@ -198,7 +204,6 @@ TYPED_TEST(EcdsaConstraintsTest, WitnessFalseSlow)
{
// This test is equal to WitnessFalse but also checks that each configuration would have failed if the
// predicate were witness true. It can be useful for debugging.
GTEST_SKIP();
BB_DISABLE_ASSERTS();
TestFixture::test_witness_false_slow();
}
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
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
Loading
Loading