diff --git a/barretenberg/cpp/scripts/test_chonk_standalone_vks_havent_changed.sh b/barretenberg/cpp/scripts/test_chonk_standalone_vks_havent_changed.sh index 4dd00ee8b2ed..acbec1d6d4f0 100755 --- a/barretenberg/cpp/scripts/test_chonk_standalone_vks_havent_changed.sh +++ b/barretenberg/cpp/scripts/test_chonk_standalone_vks_havent_changed.sh @@ -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 { diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/blake2s_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/blake2s_constraint.cpp index 601404eb41fe..1fcdbb950b52 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/blake2s_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/blake2s_constraint.cpp @@ -18,11 +18,9 @@ template void create_blake2s_constraints(Builder& builder, co using byte_array_ct = stdlib::byte_array; using field_ct = stdlib::field_t; - // 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; @@ -31,8 +29,11 @@ template 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); } diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/blake3_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/blake3_constraint.cpp index a7272a7d7351..0d826808580d 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/blake3_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/blake3_constraint.cpp @@ -17,11 +17,9 @@ template void create_blake3_constraints(Builder& builder, con using byte_array_ct = bb::stdlib::byte_array; using field_ct = bb::stdlib::field_t; - // 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; @@ -30,8 +28,11 @@ template 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); } diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.cpp index d53e4aa01d3f..38df7d574f11 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.cpp @@ -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. @@ -60,7 +59,7 @@ void create_ecdsa_verify_constraints(typename Curve::Builder& builder, std::vector pub_x_fields = fields_from_witnesses(builder, input.pub_x_indices); std::vector 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 @@ -68,40 +67,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. + // 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 default_x_bytes; + std::array 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(result_field); // Constructor enforces result = 0 or 1 - bool_ct predicate = static_cast(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(hashed_message, public_key, { r, s }); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.hpp index c5047794c83b..a93bb1002e2d 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.hpp @@ -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; diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.test.cpp index 89aa56581f96..5d5125255fcd 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.test.cpp @@ -28,21 +28,23 @@ template 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 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 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" }; } }; @@ -63,7 +65,11 @@ template 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); @@ -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) @@ -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(); } diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/utils.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/utils.hpp index ab2f6de86e37..7267c381e676 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/utils.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/utils.hpp @@ -46,7 +46,7 @@ static std::vector> fields_from_witnesses(Builder& builder, std */ template byte_array fields_to_bytes(Builder& builder, std::vector>& fields) { - byte_array result(&builder); + byte_array result = byte_array::constant_padding(&builder, /*length*/ 0); for (auto& field : fields) { // Construct byte array of length 1 from the field element // The constructor enforces that `field` fits in one byte diff --git a/barretenberg/cpp/src/barretenberg/solidity_helpers/circuits/blake_circuit.hpp b/barretenberg/cpp/src/barretenberg/solidity_helpers/circuits/blake_circuit.hpp index cf9f993dc62c..cbab86290d22 100644 --- a/barretenberg/cpp/src/barretenberg/solidity_helpers/circuits/blake_circuit.hpp +++ b/barretenberg/cpp/src/barretenberg/solidity_helpers/circuits/blake_circuit.hpp @@ -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()); 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::hash(input_buffer); diff --git a/barretenberg/cpp/src/barretenberg/solidity_helpers/circuits/ecdsa_circuit.hpp b/barretenberg/cpp/src/barretenberg/solidity_helpers/circuits/ecdsa_circuit.hpp index d8dc9714fe14..3aacc3fbbcc3 100644 --- a/barretenberg/cpp/src/barretenberg/solidity_helpers/circuits/ecdsa_circuit.hpp +++ b/barretenberg/cpp/src/barretenberg/solidity_helpers/circuits/ecdsa_circuit.hpp @@ -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()); 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 diff --git a/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s.cpp b/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s.cpp index c6c0172a4812..2f12f924cda7 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s.cpp @@ -117,8 +117,15 @@ template void Blake2s::blake2s(blake2s_state& S, byt // Set last block. S.f[0] = field_t(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(size)); compress(S, final); } @@ -133,10 +140,13 @@ template byte_array Blake2s::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; } diff --git a/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s.test.cpp index 1c9dcff2d096..9bc25b8aad25 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s.test.cpp @@ -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 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 constant_vec = { '0', '1' }; - std::vector 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::hash(input_arr); + // for expected value calculation + std::vector constant_vec = { '0', '1' }; // create expected input vector by concatenating witness and constant parts std::vector 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::hash(input_arr); + // compute expected hash auto expected = crypto::blake2s(input_v); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/hash/blake3s/blake3s.cpp b/barretenberg/cpp/src/barretenberg/stdlib/hash/blake3s/blake3s.cpp index fa88ea260876..b396324ad10c 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/hash/blake3s/blake3s.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/hash/blake3s/blake3s.cpp @@ -90,11 +90,15 @@ void Blake3s::compress_xof(const field_t cv[8], */ for (size_t i = 0; i < (BLAKE3_STATE_SIZE >> 1); i++) { const auto lookup_1 = plookup_read::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 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::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 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); } } @@ -105,17 +109,14 @@ Blake3s::output_t Blake3s::make_output(const field_t 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; } @@ -129,10 +130,8 @@ template void Blake3s::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(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; @@ -160,9 +159,9 @@ void Blake3s::hasher_update(blake3_hasher* self, const byte_array 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 input_slice = input.slice(start_counter, take); + self->buf.write_at(input_slice, self->buf_len); self->buf_len = static_cast(self->buf_len + (uint8_t)take); input_len -= take; @@ -173,11 +172,11 @@ template void Blake3s::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 byte_array Blake3s::hash(const byte_array& input) @@ -185,11 +184,19 @@ template byte_array Blake3s::hash(const byt 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; } diff --git a/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.cpp b/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.cpp index 679c604be97b..258ce9f98d1f 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.cpp @@ -359,7 +359,8 @@ template byte_array SHA256::hash(const byte rolling_hash = sha256_block(rolling_hash, hash_input); } - std::vector 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 @@ -367,12 +368,9 @@ template byte_array SHA256::hash(const byte // - 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(ctx, output); + return result; } template class SHA256; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp index 3f54f4fcd121..922ea0a56dfa 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp @@ -353,35 +353,6 @@ template class bigfield { bb::fr(negative_prime_modulus_mod_binary_basis.slice(NUM_LIMB_BITS * 3, NUM_LIMB_BITS * 4).lo), }; - /** - * @brief Convert the bigfield element to a byte array. Concatenates byte arrays of the high (2L bits) and low (2L - * bits) parts of the bigfield element. - * - * @details Assumes that 2L is divisible by 8, i.e. (NUM_LIMB_BITS * 2) % 8 == 0. Also we check that the bigfield - * element is in the target field. - * - * @return byte_array - */ - byte_array to_byte_array() const - { - byte_array result(get_context()); - // Prevents aliases - assert_is_in_field(); - field_t lo = binary_basis_limbs[0].element + (binary_basis_limbs[1].element * shift_1); - field_t hi = binary_basis_limbs[2].element + (binary_basis_limbs[3].element * shift_1); - // n.b. this only works if NUM_LIMB_BITS * 2 is divisible by 8 - // - // We are packing two bigfield limbs each into the field elements `lo` and `hi`. - // Thus, each of `lo` and `hi` will contain (NUM_LIMB_BITS * 2) bits. We then convert - // `lo` and `hi` to `byte_array` each containing ((NUM_LIMB_BITS * 2) / 8) bytes. - // Therefore, it is necessary for (NUM_LIMB_BITS * 2) to be divisible by 8 for correctly - // converting `lo` and `hi` to `byte_array`s. - BB_ASSERT_EQ((NUM_LIMB_BITS * 2 / 8) * 8, NUM_LIMB_BITS * 2); - result.write(byte_array(hi, 32 - (NUM_LIMB_BITS / 4))); - result.write(byte_array(lo, (NUM_LIMB_BITS / 4))); - return result; - } - // Gets the integer (uint512_t) value of the bigfield element by combining the binary basis limbs. uint512_t get_value() const; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.test.cpp index 5ff30485f8c1..25a4a78d9103 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.test.cpp @@ -1469,26 +1469,6 @@ template class stdlib_bigfield : public testing::Test { EXPECT_EQ(result, true); } - static void test_to_byte_array() - { - auto builder = Builder(); - size_t num_repetitions = 10; - for (size_t i = 0; i < num_repetitions; ++i) { - auto [a_native, a_ct] = get_random_witness(&builder, true); // fq_native, fq_ct - byte_array_ct a_bytes_ct = a_ct.to_byte_array(); - - std::vector actual_bytes = a_bytes_ct.bytes(); - EXPECT_EQ(actual_bytes.size(), 32); - - for (size_t j = 0; j < actual_bytes.size(); ++j) { - const uint256_t expected = (uint256_t(a_native) >> (8 * j)).slice(0, 8); - EXPECT_EQ(actual_bytes[32 - 1 - j].get_value(), expected); - } - } - bool result = CircuitChecker::check(builder); - EXPECT_EQ(result, true); - } - // This check tests if elements are reduced to fit quotient into range proof static void test_quotient_completeness() { @@ -2447,10 +2427,6 @@ TYPED_TEST(stdlib_bigfield, byte_array_constructors) { TestFixture::test_byte_array_constructors(); } -TYPED_TEST(stdlib_bigfield, to_byte_array) -{ - TestFixture::test_to_byte_array(); -} TYPED_TEST(stdlib_bigfield, quotient_completeness_regression) { TestFixture::test_quotient_completeness(); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup.hpp index 52965cb440b0..32e2b90f68dc 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup.hpp @@ -187,19 +187,6 @@ template class element { element& operator=(const element& other); element& operator=(element&& other) noexcept; - /** - * @brief Serialize the element to a byte array in form: (yhi || ylo || xhi || xlo). - * - * @return byte_array - */ - byte_array to_byte_array() const - { - byte_array result(get_context()); - result.write(_y.to_byte_array()); - result.write(_x.to_byte_array()); - return result; - } - element checked_unconditional_add(const element& other) const; element checked_unconditional_subtract(const element& other) const; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.cpp index 3a491a79a59a..13a38d320fe2 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.cpp @@ -7,6 +7,7 @@ #include "bool.hpp" #include "../circuit_builders/circuit_builders.hpp" #include "barretenberg/common/assert.hpp" +#include "barretenberg/stdlib/primitives/field/field.hpp" #include "barretenberg/transcript/origin_tag.hpp" using namespace bb; @@ -121,15 +122,7 @@ template bool_t& bool_t::operator=(const bo /** * @brief Assigns a `bool_t` to a `bool_t` object. */ -template bool_t& bool_t::operator=(const bool_t& other) -{ - context = other.context; - witness_index = other.witness_index; - witness_bool = other.witness_bool; - witness_inverted = other.witness_inverted; - tag = other.tag; - return *this; -} +template bool_t& bool_t::operator=(const bool_t& other) = default; /** * @brief Assigns a `bool_t` to a `bool_t` object. @@ -165,15 +158,16 @@ template bool_t& bool_t::operator=(const wi */ template bool_t bool_t::operator&(const bool_t& other) const { - bool_t result(context ? context : other.context); + Builder* ctx = validate_context(context, other.context); + bool_t result(ctx); bool left = witness_inverted ^ witness_bool; bool right = other.witness_inverted ^ other.witness_bool; result.witness_bool = left && right; - BB_ASSERT(result.context || (is_constant() && other.is_constant())); + BB_ASSERT(ctx || (is_constant() && other.is_constant())); if (!is_constant() && !other.is_constant()) { bb::fr value = result.witness_bool ? bb::fr::one() : bb::fr::zero(); - result.witness_index = context->add_variable(value); + result.witness_index = ctx->add_variable(value); /** * A bool can be represented by a witness value `w` and an 'inverted' flag `i` @@ -212,8 +206,7 @@ template bool_t bool_t::operator&(const boo fr q_o{ -1 }; fr q_c{ i_a * i_b }; - context->create_poly_gate( - { witness_index, other.witness_index, result.witness_index, q_m, q_l, q_r, q_o, q_c }); + ctx->create_poly_gate({ witness_index, other.witness_index, result.witness_index, q_m, q_l, q_r, q_o, q_c }); } else if (!is_constant() && other.is_constant()) { BB_ASSERT(!other.witness_inverted); // If rhs is a constant true, the output is determined by the lhs. Otherwise the output is a constant @@ -236,14 +229,16 @@ template bool_t bool_t::operator&(const boo */ template bool_t bool_t::operator|(const bool_t& other) const { - bool_t result(context ? context : other.context); + Builder* ctx = validate_context(context, other.context); + + bool_t result(ctx); - BB_ASSERT(result.context || (is_constant() && other.is_constant())); + BB_ASSERT(ctx || (is_constant() && other.is_constant())); result.witness_bool = (witness_bool ^ witness_inverted) | (other.witness_bool ^ other.witness_inverted); bb::fr value = result.witness_bool ? bb::fr::one() : bb::fr::zero(); if (!is_constant() && !other.is_constant()) { - result.witness_index = context->add_variable(value); + result.witness_index = ctx->add_variable(value); // Let // a := lhs = *this; // b := rhs = other; @@ -264,8 +259,7 @@ template bool_t bool_t::operator|(const boo // Let r := a | b; // Constrain // q_m * w_a * w_b + q_l * w_a + q_r * w_b + q_o * r + q_c = 0 - context->create_poly_gate( - { witness_index, other.witness_index, result.witness_index, q_m, q_l, q_r, q_o, q_c }); + ctx->create_poly_gate({ witness_index, other.witness_index, result.witness_index, q_m, q_l, q_r, q_o, q_c }); } else if (!is_constant() && other.is_constant()) { BB_ASSERT_EQ(other.witness_inverted, false); @@ -288,15 +282,16 @@ template bool_t bool_t::operator|(const boo */ template bool_t bool_t::operator^(const bool_t& other) const { - bool_t result(context == nullptr ? other.context : context); + Builder* ctx = validate_context(context, other.context); + bool_t result(ctx); - BB_ASSERT(result.context || (is_constant() && other.is_constant())); + BB_ASSERT(ctx || (is_constant() && other.is_constant())); result.witness_bool = (witness_bool ^ witness_inverted) ^ (other.witness_bool ^ other.witness_inverted); bb::fr value = result.witness_bool ? bb::fr::one() : bb::fr::zero(); if (!is_constant() && !other.is_constant()) { - result.witness_index = context->add_variable(value); + result.witness_index = ctx->add_variable(value); // Let // a := lhs = *this; // b := rhs = other; @@ -319,8 +314,7 @@ template bool_t bool_t::operator^(const boo // Let r := a ^ b; // Constrain // q_m * w_a * w_b + q_l * w_a + q_r * w_b + q_o * r + q_c = 0 - context->create_poly_gate( - { witness_index, other.witness_index, result.witness_index, q_m, q_l, q_r, q_o, q_c }); + ctx->create_poly_gate({ witness_index, other.witness_index, result.witness_index, q_m, q_l, q_r, q_o, q_c }); } else if (!is_constant() && other.is_constant()) { // witness ^ 1 = !witness BB_ASSERT_EQ(other.witness_inverted, false); @@ -355,8 +349,9 @@ template bool_t bool_t::operator!() const */ template bool_t bool_t::operator==(const bool_t& other) const { - BB_ASSERT(context || other.context || (is_constant() && other.is_constant())); - bool_t result(context ? context : other.context); + Builder* ctx = validate_context(context, other.context); + bool_t result(ctx); + BB_ASSERT(ctx || (is_constant() && other.is_constant())); result.witness_bool = (witness_bool ^ witness_inverted) == (other.witness_bool ^ other.witness_inverted); if (!is_constant() && !other.is_constant()) { @@ -382,8 +377,7 @@ template bool_t bool_t::operator==(const bo bb::fr q_o{ bb::fr::neg_one() }; bb::fr q_c{ 1 - lhs_inverted - rhs_inverted + 2 * rhs_inverted * lhs_inverted }; - context->create_poly_gate( - { witness_index, other.witness_index, result.witness_index, q_m, q_r, q_l, q_o, q_c }); + ctx->create_poly_gate({ witness_index, other.witness_index, result.witness_index, q_m, q_l, q_r, q_o, q_c }); } else if (!is_constant() && (other.is_constant())) { // Compare *this with a constant other. If other == true, then we're checking *this == true. In this case we @@ -423,7 +417,7 @@ template bool_t bool_t::operator||(const bo template void bool_t::assert_equal(const bool_t& rhs, std::string const& msg) const { const bool_t lhs = *this; - Builder* ctx = lhs.get_context() ? lhs.get_context() : rhs.get_context(); + Builder* ctx = validate_context(rhs.get_context(), lhs.get_context()); (void)OriginTag(get_origin_tag(), rhs.get_origin_tag()); if (lhs.is_constant() && rhs.is_constant()) { BB_ASSERT_EQ(lhs.get_value(), rhs.get_value()); @@ -528,7 +522,7 @@ template bool_t bool_t::normalize() const bb::fr q_o = bb::fr::neg_one(); bb::fr q_m = bb::fr::zero(); bb::fr q_r = bb::fr::zero(); - context->create_poly_gate({ witness_index, witness_index, new_witness, q_m, q_l, q_r, q_o, q_c }); + context->create_poly_gate({ witness_index, context->zero_idx(), new_witness, q_m, q_l, q_r, q_o, q_c }); witness_index = new_witness; witness_bool = value; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.cpp index d32c13b8a7a1..f342d1189fa5 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.cpp @@ -13,17 +13,6 @@ using namespace bb; namespace bb::stdlib { -template -byte_array::byte_array(Builder* parent_context) - : context(parent_context) -{} - -template -byte_array::byte_array(Builder* parent_context, const size_t n) - : context(parent_context) - , values(std::vector>(n)) -{} - /** * @brief Create a byte array out of a vector of uint8_t bytes. * @@ -55,6 +44,23 @@ byte_array::byte_array(Builder* parent_context, const std::string& inpu : byte_array(parent_context, std::vector(input.begin(), input.end())) {} +/** + * @brief Create a byte_array from constant values without adding range constraints. + * @details This is safe for constant data (like padding) because constants cannot be manipulated by the prover. + * Use this for padding, initialization, or other constant data to avoid unnecessary constraints. + */ +template +byte_array byte_array::from_constants(Builder* parent_context, std::vector const& input) +{ + bytes_t const_values; + const_values.reserve(input.size()); + for (const auto& byte : input) { + // Create constant field elements - no witness, no constraints + const_values.push_back(field_t(parent_context, byte)); + } + return byte_array(parent_context, const_values); +} + /** * @brief Create a byte_array of length `num_bytes` out of a field element. * @@ -184,6 +190,8 @@ byte_array::byte_array(const field_t& input, const field_t overlap = -diff_lo_hi + 1; // Ensure that (r - 1).hi - reconstructed_hi/shift - overlap is positive. + // SAFETY: reconstructed_hi is always a multiple of 2^128 by construction + // (bytes 0-15 all have scaling factors ≥ 2^128) const field_t diff_hi = (-reconstructed_hi / shift).add_two(s_hi, -overlap); diff_hi.create_range_constraint(128, "byte_array: y_hi doesn't fit in 128 bits."); } @@ -200,7 +208,7 @@ byte_array::byte_array(Builder* parent_context, bytes_t const& input) template byte_array::byte_array(Builder* parent_context, bytes_t&& input) : context(parent_context) - , values(input) + , values(std::move(input)) {} template @@ -211,7 +219,7 @@ byte_array::byte_array(const byte_array& other) } template -byte_array::byte_array(byte_array&& other) +byte_array::byte_array(byte_array&& other) noexcept : context(other.context) , values(std::move(other.values)) {} @@ -224,7 +232,7 @@ template byte_array& byte_array::operator=( return *this; } -template byte_array& byte_array::operator=(byte_array&& other) +template byte_array& byte_array::operator=(byte_array&& other) noexcept { context = other.context; values = std::move(other.values); @@ -234,12 +242,10 @@ template byte_array& byte_array::operator=( /** * @brief Convert a byte array into a field element. * - * @details The transformation is injective when the size of the byte array is < 32, which covers all the use cases. **/ template byte_array::operator field_t() const { const size_t bytes = values.size(); - BB_ASSERT(bytes < 32); static constexpr uint256_t one(1); std::vector> scaled_values; @@ -250,6 +256,7 @@ template byte_array::operator field_t() con return field_t::accumulate(scaled_values); } + /** * @brief Appends the contents of another `byte_array` (`other`) to the end of this one. */ @@ -260,8 +267,7 @@ template byte_array& byte_array::write(byte } /** - * @brief Overwrites this byte_array starting at index with the contents of other. Asserts that the write does not - * exceed the current size. + * @brief Overwrites this byte_array starting at index with the contents of other. */ template byte_array& byte_array::write_at(byte_array const& other, size_t index) { @@ -277,7 +283,7 @@ template byte_array& byte_array::write_at(b */ template byte_array byte_array::slice(size_t offset) const { - BB_ASSERT_DEBUG(offset < values.size()); + BB_ASSERT_LTE(offset, values.size()); return byte_array(context, bytes_t(values.begin() + static_cast(offset), values.end())); } @@ -287,9 +293,9 @@ template byte_array byte_array::slice(size_ **/ template byte_array byte_array::slice(size_t offset, size_t length) const { - BB_ASSERT_DEBUG(offset < values.size()); + BB_ASSERT_LTE(offset, values.size()); // it's <= cause vector constructor doesn't include end point - BB_ASSERT_DEBUG(length <= values.size() - offset); + BB_ASSERT_LTE(length, values.size() - offset); auto start = values.begin() + static_cast(offset); auto end = values.begin() + static_cast((offset + length)); return byte_array(context, bytes_t(start, end)); @@ -300,6 +306,9 @@ template byte_array byte_array::slice(size_ **/ template byte_array byte_array::reverse() const { + if (values.empty()) { + return *this; + } bytes_t bytes(values.size()); size_t offset = bytes.size() - 1; for (size_t i = 0; i < bytes.size(); i += 1, offset -= 1) { @@ -322,16 +331,6 @@ template std::vector byte_array::get_value( return bytes; } -/** - * @brief Given a `byte_array`, compute a vector containing the values of its entries and convert it to a string. - * @note Used only in tests. - */ -template std::string byte_array::get_string() const -{ - auto v = get_value(); - return std::string(v.begin(), v.end()); -} - template class byte_array; template class byte_array; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.fuzzer.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.fuzzer.hpp index 47a90e721536..568b3f3ef5c8 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.fuzzer.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.fuzzer.hpp @@ -348,18 +348,7 @@ template class ByteArrayFuzzBase { byte_array_t byte_array{ nullptr, std::vector{} }; - static std::vector get_value(const byte_array_t& byte_array) - { - /* Based on the PRNG, alternate between retrieving an std::vector - * and a string. - * These should be functionally equivalent. - */ - if (static_cast(VarianceRNG.next() % 2)) { - return byte_array.get_value(); - } else { - return from_to>(byte_array.get_string()); - } - } + static std::vector get_value(const byte_array_t& byte_array) { return byte_array.get_value(); } static const std::vector& bool_to_vector(const bool& b) { static const std::vector false_{ 0 }; @@ -462,7 +451,7 @@ template class ByteArrayFuzzBase { { const auto& ref = this->reference_value; - switch (VarianceRNG.next() % 8) { + switch (VarianceRNG.next() % 5) { case 0: #ifdef SHOW_INFORMATION std::cout << "byte_array_t(e);" << std::cout; @@ -470,30 +459,13 @@ template class ByteArrayFuzzBase { /* Construct via byte_array */ return ExecutionHandler(ref, byte_array_t(this->byte_array)); case 1: -#ifdef SHOW_INFORMATION - std::cout << "e.get_string();" << std::cout; -#endif - /* Construct via std::string */ - return ExecutionHandler(ref, byte_array_t(builder, this->byte_array.get_string())); - case 2: #ifdef SHOW_INFORMATION std::cout << "e.get_value();" << std::cout; #endif /* Construct via std::vector */ return ExecutionHandler(ref, byte_array_t(builder, this->byte_array.get_value())); - case 3: -#ifdef SHOW_INFORMATION - std::cout << "e.bytes();" << std::cout; -#endif - /* Construct via bytes_t */ - return ExecutionHandler(ref, byte_array_t(builder, this->byte_array.bytes())); - case 4: -#ifdef SHOW_INFORMATION - std::cout << "std::move(e.bytes());" << std::cout; -#endif - /* Construct via bytes_t move constructor */ - return ExecutionHandler(ref, byte_array_t(builder, std::move(this->byte_array.bytes()))); - case 5: { + // case 2 and 3: Removed - tested private bytes_t constructors (redundant with cases 0-1) + case 2: { const auto field = to_field_t(); if (field == std::nullopt) { @@ -523,7 +495,7 @@ template class ByteArrayFuzzBase { return ExecutionHandler(new_ref, byte_array_t(*field, num_bytes)); } } - case 6: { + case 3: { /* Create a byte_array with gibberish. * * The purpose of this is to ascertain that no gibberish @@ -541,7 +513,7 @@ template class ByteArrayFuzzBase { return ExecutionHandler(ref, ba); } break; - case 7: { + case 4: { static_assert(suint_t::MAX_BIT_NUM > 0); const auto field = to_field_t( /* One bit must be reserved */ diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.hpp index cf36ae76ef98..417069ac477a 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.hpp @@ -32,39 +32,47 @@ template class byte_array { Builder* context; bytes_t values; - public: - byte_array(Builder* parent_context = nullptr); - byte_array(Builder* parent_context, size_t const n); - byte_array(Builder* parent_context, std::string const& input); - byte_array(Builder* parent_context, std::vector const& input); + // Internal constructors - do NOT add constraints + // Only for use by member functions (slice, reverse, from_constants) byte_array(Builder* parent_context, bytes_t const& input); byte_array(Builder* parent_context, bytes_t&& input); - byte_array(const field_t& input, - const size_t num_bytes = 32, - std::optional test_val = std::nullopt); - byte_array(const byte_array& other); - byte_array(byte_array&& other); + // Create byte_array from constant values without adding range constraints + // Safe for padding and other constant data - constants can't be manipulated by the prover + static byte_array from_constants(Builder* parent_context, std::vector const& input); - byte_array& operator=(const byte_array& other); - byte_array& operator=(byte_array&& other); + public: + explicit byte_array(Builder* parent_context, std::string const& input); + // Explicit to prevent implicit conversion from size_t to std::vector + explicit byte_array(Builder* parent_context, std::vector const& input); + // Explicit to prevent implicit conversions from size_t/int to field_t + explicit byte_array(const field_t& input, + const size_t num_bytes = 32, + std::optional test_val = std::nullopt); + + // Convenience method for creating constant padding (common use case) + static byte_array constant_padding(Builder* parent_context, size_t num_bytes, uint8_t value = 0) + { + return from_constants(parent_context, std::vector(num_bytes, value)); + } + // Copy and move operations + byte_array(const byte_array& other); + byte_array(byte_array&& other) noexcept; + byte_array& operator=(const byte_array& other); + byte_array& operator=(byte_array&& other) noexcept; explicit operator field_t() const; field_t operator[](const size_t index) const - { - assert(values.size() > 0); - return values[index]; - } - - field_t& operator[](const size_t index) { BB_ASSERT_LT(index, values.size()); - return values[index]; } + // Append another byte_array to this one byte_array& write(byte_array const& other); + + // Overwrite bytes starting at index with contents of other byte_array& write_at(byte_array const& other, size_t index); byte_array slice(size_t offset) const; @@ -79,7 +87,6 @@ template class byte_array { // Out-of-circuit methods std::vector get_value() const; - std::string get_string() const; // OriginTag-specific methods void set_origin_tag(bb::OriginTag tag) diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.test.cpp index 7299c28c8e8e..617e51b39be8 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.test.cpp @@ -67,15 +67,6 @@ template class ByteArrayTest : public ::testing::Test { EXPECT_EQ(reversed_arr.bytes()[2].get_origin_tag(), clear_tag); } - void test_from_string_constructor() - { - Builder builder; - - std::string a = "ascii"; - byte_array_ct arr(&builder, a); - EXPECT_EQ(arr.get_string(), a); - } - void test_into_bytes_decomposition_less_than_32_bytes() { for (size_t num_bytes = 1; num_bytes < 32; num_bytes++) { @@ -211,10 +202,14 @@ template class ByteArrayTest : public ::testing::Test { field_ct b = witness_ct(&builder, slice_to_n_bytes(b_expected, 31)); b.set_origin_tag(challenge_origin_tag); - byte_array_ct arr(&builder); + // byte_array_ct(field, num_bytes) constructor adds range constraints for each byte + byte_array_ct a_bytes(a, 31); + byte_array_ct b_bytes(b, 31); - arr.write(byte_array_ct(a, 31)); - arr.write(byte_array_ct(b, 31)); + // Build byte_array by writing constrained byte_arrays + byte_array_ct arr(&builder, std::vector()); + arr.write(a_bytes); + arr.write(b_bytes); EXPECT_EQ(arr.size(), 62UL); @@ -236,15 +231,16 @@ template class ByteArrayTest : public ::testing::Test { for (size_t arr_length = 1; arr_length < 32; arr_length++) { Builder builder; - byte_array_ct test_array(&builder, arr_length); + // Generate random bytes std::vector native_bytes(arr_length); for (size_t idx = 0; idx < arr_length; idx++) { - uint8_t byte = engine.get_random_uint8(); - native_bytes[idx] = byte; - test_array[idx] = witness_ct(&builder, byte); + native_bytes[idx] = engine.get_random_uint8(); } + // Create byte_array from vector (this creates witnesses for each byte) + byte_array_ct test_array(&builder, native_bytes); + // Convert to field_t using the byte_array conversion field_ct represented_field_elt = static_cast(test_array); @@ -284,11 +280,6 @@ TYPED_TEST(ByteArrayTest, Reverse) TestFixture::test_reverse(); } -TYPED_TEST(ByteArrayTest, ConstructFromString) -{ - TestFixture::test_from_string_constructor(); -} - TYPED_TEST(ByteArrayTest, ByteDecompositionUnique) { TestFixture::test_into_bytes_decomposition_less_than_32_bytes(); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/safe_uint/safe_uint.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/safe_uint/safe_uint.test.cpp index a6d3839fc37d..d4cf6847365b 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/safe_uint/safe_uint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/safe_uint/safe_uint.test.cpp @@ -759,26 +759,3 @@ TYPED_TEST(SafeUintTest, TestDivRemainderConstraint) bool result = CircuitChecker::check(builder); EXPECT_EQ(result, false); } - -TYPED_TEST(SafeUintTest, TestByteArrayConversion) -{ - STDLIB_TYPE_ALIASES - auto builder = Builder(); - - field_ct elt = witness_ct(&builder, 0x7f6f5f4f00010203); - elt.set_origin_tag(next_challenge_tag); - suint_ct safe(elt, 63); - // safe.value is a uint256_t, so we serialize to a 32-byte array - std::string expected = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x7f, 0x6f, 0x5f, 0x4f, 0x00, 0x01, 0x02, 0x03 }; - - byte_array_ct arr(&builder); - arr.write(static_cast(safe)); - EXPECT_EQ(arr.get_string(), expected); - // Conversion to byte_array preserves tags - for (const auto& single_byte : arr.bytes()) { - EXPECT_EQ(single_byte.get_origin_tag(), next_challenge_tag); - } - EXPECT_EQ(arr.get_origin_tag(), next_challenge_tag); -}