diff --git a/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.hpp b/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.hpp index 7a5d7c48bce2..cb03ce0ee119 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.hpp @@ -8,7 +8,6 @@ #include "barretenberg/stdlib_circuit_builders/plookup_tables/plookup_tables.hpp" #include -#include "barretenberg/numeric/bitop/sparse_form.hpp" #include "barretenberg/stdlib/hash/hash_utils.hpp" #include "barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp" @@ -120,12 +119,7 @@ template class SHA256 { struct sparse_value { sparse_value(const field_ct& in = 0) : normal(in) - { - if (normal.is_constant()) { - sparse = field_ct(in.get_context(), - bb::fr(numeric::map_into_sparse_form<16>(uint256_t(in.get_value()).data[0]))); - } - } + {} sparse_value(const sparse_value& other) = default; sparse_value(sparse_value&& other) = default; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.test.cpp index 64ec9e1706ef..e61c68ea53df 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256.test.cpp @@ -26,6 +26,20 @@ TYPED_TEST_SUITE(Sha256Test, BuilderTypes); using bb::stdlib::test_utils::check_circuit_and_gate_count; +// Common test data: SHA-256 initial hash values (FIPS 180-4 section 5.3.3) +constexpr std::array SHA256_IV = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 }; + +// Padded block for "abc" (FIPS 180-4 section 5.1.1) +constexpr std::array ABC_PADDED_BLOCK = { 0x61626380, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000018 }; + +// Expected output: SHA-256("abc") from NIST +constexpr std::array ABC_EXPECTED = { 0xba7816bf, 0x8f01cfea, 0x414140de, 0x5dae2223, + 0xb00361a3, 0x96177a9c, 0xb410ff61, 0xf20015ad }; + /** * @brief Test sha256_block against NIST vector one ("abc") * @@ -40,42 +54,23 @@ TYPED_TEST(Sha256Test, BlockNistVectorOne) { STDLIB_TYPE_ALIASES - auto builder = Builder(); - - // SHA-256 initial hash values (FIPS 180-4 section 5.3.3) - constexpr std::array H_INIT = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, - 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 }; - - // Manually padded block for "abc" (FIPS 180-4 section 5.1.1) - // Message "abc" = 0x616263 - // Pad: append 1 bit, then zeros, then 64-bit length - // Block: 0x61626380 00000000 ... 00000000 00000018 - constexpr std::array PADDED_BLOCK = { - 0x61626380, // "abc" + padding bit - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000018 // length in bits (24) - }; - - // Expected output: SHA-256("abc") from NIST - constexpr std::array EXPECTED = { 0xba7816bf, 0x8f01cfea, 0x414140de, 0x5dae2223, - 0xb00361a3, 0x96177a9c, 0xb410ff61, 0xf20015ad }; + Builder builder; // Verify native implementation first - auto native_output = crypto::sha256_block(H_INIT, PADDED_BLOCK); + auto native_output = crypto::sha256_block(SHA256_IV, ABC_PADDED_BLOCK); for (size_t i = 0; i < 8; i++) { - EXPECT_EQ(native_output[i], EXPECTED[i]) << "Native mismatch at index " << i; + EXPECT_EQ(native_output[i], ABC_EXPECTED[i]) << "Native mismatch at index " << i; } // Create circuit witnesses std::array h_init; for (size_t i = 0; i < 8; i++) { - h_init[i] = witness_ct(&builder, H_INIT[i]); + h_init[i] = witness_ct(&builder, SHA256_IV[i]); } std::array block; for (size_t i = 0; i < 16; i++) { - block[i] = witness_ct(&builder, PADDED_BLOCK[i]); + block[i] = witness_ct(&builder, ABC_PADDED_BLOCK[i]); } // Run circuit compression @@ -84,7 +79,7 @@ TYPED_TEST(Sha256Test, BlockNistVectorOne) // Compare outputs for (size_t i = 0; i < 8; i++) { uint32_t circuit_val = static_cast(uint256_t(circuit_output[i].get_value())); - EXPECT_EQ(circuit_val, EXPECTED[i]) << "Circuit mismatch at index " << i; + EXPECT_EQ(circuit_val, ABC_EXPECTED[i]) << "Circuit mismatch at index " << i; } check_circuit_and_gate_count(builder, 6702); @@ -105,15 +100,8 @@ TYPED_TEST(Sha256Test, BlockNistVectorTwo) { STDLIB_TYPE_ALIASES - auto builder = Builder(); - - // SHA-256 initial hash values - constexpr std::array H_INIT = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, - 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 }; + Builder builder; - // First block: first 64 bytes of padded message - // "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" = 56 bytes - // After padding bit, need second block for length constexpr std::array BLOCK_1 = { 0x61626364, 0x62636465, 0x63646566, 0x64656667, // "abcd" "bcde" "cdef" "defg" 0x65666768, 0x66676869, 0x6768696a, 0x68696a6b, // "efgh" "fghi" "ghij" "hijk" @@ -121,18 +109,16 @@ TYPED_TEST(Sha256Test, BlockNistVectorTwo) 0x6d6e6f70, 0x6e6f7071, 0x80000000, 0x00000000 // "mnop" "nopq" + padding bit }; - // Second block: zeros + 64-bit length (448 bits = 0x1c0) constexpr std::array BLOCK_2 = { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000001c0 }; - // Expected output from NIST constexpr std::array EXPECTED = { 0x248d6a61, 0xd20638b8, 0xe5c02693, 0x0c3e6039, 0xa33ce459, 0x64ff2167, 0xf6ecedd4, 0x19db06c1 }; // Verify native implementation - auto h_after_block1 = crypto::sha256_block(H_INIT, BLOCK_1); + auto h_after_block1 = crypto::sha256_block(SHA256_IV, BLOCK_1); auto native_output = crypto::sha256_block(h_after_block1, BLOCK_2); for (size_t i = 0; i < 8; i++) { EXPECT_EQ(native_output[i], EXPECTED[i]) << "Native mismatch at index " << i; @@ -141,7 +127,7 @@ TYPED_TEST(Sha256Test, BlockNistVectorTwo) // Circuit: first block std::array h_init; for (size_t i = 0; i < 8; i++) { - h_init[i] = witness_ct(&builder, H_INIT[i]); + h_init[i] = witness_ct(&builder, SHA256_IV[i]); } std::array block1; @@ -169,6 +155,204 @@ TYPED_TEST(Sha256Test, BlockNistVectorTwo) EXPECT_EQ(builder.get_tables_size(), 35992); } +/** + * @brief Test sha256_block with all-constant inputs produces correct output with zero gates. + * + * When both h_init and input are circuit constants (not witnesses), every plookup operation + * takes the constant path (no gate creation), and add_normalize_unsafe returns constants directly. + */ +TYPED_TEST(Sha256Test, BlockAllConstants) +{ + using Builder = TypeParam; + using field_ct = field_t; + + Builder builder; + + // Create all inputs as constants (with builder context) + std::array h_init; + for (size_t i = 0; i < 8; i++) { + h_init[i] = field_ct(&builder, SHA256_IV[i]); + ASSERT_TRUE(h_init[i].is_constant()); + } + + std::array block; + for (size_t i = 0; i < 16; i++) { + block[i] = field_ct(&builder, ABC_PADDED_BLOCK[i]); + ASSERT_TRUE(block[i].is_constant()); + } + + auto circuit_output = SHA256::sha256_block(h_init, block); + + for (size_t i = 0; i < 8; i++) { + uint32_t circuit_val = static_cast(uint256_t(circuit_output[i].get_value())); + EXPECT_EQ(circuit_val, ABC_EXPECTED[i]) << "All-constant mismatch at index " << i; + } + + check_circuit_and_gate_count(builder, 0); + EXPECT_EQ(builder.get_tables_size(), 0); +} + +/** + * @brief Test sha256_block with constant h_init and witness input block. + * + * This is the natural use case for the first block of a SHA-256 hash: the IV is known + * at compile time, but the message is a witness. The constant h_init values take the + * constant plookup path for their initial sparse form conversions, saving gates compared + * to the all-witness case. + */ +TYPED_TEST(Sha256Test, BlockConstantHinitWitnessInput) +{ + STDLIB_TYPE_ALIASES + + Builder builder; + + std::array h_init; + for (size_t i = 0; i < 8; i++) { + h_init[i] = field_ct(&builder, SHA256_IV[i]); + } + + std::array block; + for (size_t i = 0; i < 16; i++) { + block[i] = witness_ct(&builder, ABC_PADDED_BLOCK[i]); + } + + auto circuit_output = SHA256::sha256_block(h_init, block); + + for (size_t i = 0; i < 8; i++) { + uint32_t circuit_val = static_cast(uint256_t(circuit_output[i].get_value())); + EXPECT_EQ(circuit_val, ABC_EXPECTED[i]) << "Constant h_init mismatch at index " << i; + } + + check_circuit_and_gate_count(builder, 6650); + EXPECT_EQ(builder.get_tables_size(), 35992); +} + +/** + * @brief Test sha256_block with witness h_init and constant input block. + * + * This models the second block in a two-block hash where the intermediate hash state + * is a witness (output of first compression) but the padding block is all constants. + * The constant message words fold through extend_witness without creating lookup gates, + * yielding significantly fewer gates than the all-witness case. + */ +TYPED_TEST(Sha256Test, BlockWitnessHinitConstantInput) +{ + STDLIB_TYPE_ALIASES + + Builder builder; + + std::array h_init; + for (size_t i = 0; i < 8; i++) { + h_init[i] = witness_ct(&builder, SHA256_IV[i]); + } + + std::array block; + for (size_t i = 0; i < 16; i++) { + block[i] = field_ct(&builder, ABC_PADDED_BLOCK[i]); + } + + auto circuit_output = SHA256::sha256_block(h_init, block); + + for (size_t i = 0; i < 8; i++) { + uint32_t circuit_val = static_cast(uint256_t(circuit_output[i].get_value())); + EXPECT_EQ(circuit_val, ABC_EXPECTED[i]) << "Witness h_init mismatch at index " << i; + } + + check_circuit_and_gate_count(builder, 5523); + EXPECT_EQ(builder.get_tables_size(), 15120); +} + +/** + * @brief Test sha256_block with interleaved constant and witness values within both arrays. + * + * Even-indexed h_init and input words are constants, odd-indexed are witnesses. This exercises + * the mixed-input paths through plookup (some lookups constant, some witness), extend_witness + * (lazy sparse conversion with mixed provenance), and add_normalize_unsafe (one constant operand, + * one witness operand). + */ +TYPED_TEST(Sha256Test, BlockMixedConstantsAndWitnesses) +{ + STDLIB_TYPE_ALIASES + + Builder builder; + + // Even-indexed = constant, odd-indexed = witness + std::array h_init; + for (size_t i = 0; i < 8; i++) { + if (i % 2 == 0) { + h_init[i] = field_ct(&builder, SHA256_IV[i]); + } else { + h_init[i] = witness_ct(&builder, SHA256_IV[i]); + } + } + + std::array block; + for (size_t i = 0; i < 16; i++) { + if (i % 2 == 0) { + block[i] = field_ct(&builder, ABC_PADDED_BLOCK[i]); + } else { + block[i] = witness_ct(&builder, ABC_PADDED_BLOCK[i]); + } + } + + auto circuit_output = SHA256::sha256_block(h_init, block); + + for (size_t i = 0; i < 8; i++) { + uint32_t circuit_val = static_cast(uint256_t(circuit_output[i].get_value())); + EXPECT_EQ(circuit_val, ABC_EXPECTED[i]) << "Mixed mismatch at index " << i; + } + + check_circuit_and_gate_count(builder, 6644); + EXPECT_EQ(builder.get_tables_size(), 35992); +} + +/** + * @brief Test extend_witness with mixed constant and witness message words. + * + * Only input[0] and input[15] are witnesses; the rest are constants. This exercises + * the lazy sparse conversion and context propagation in extend_witness when inputs + * have mixed constantness. + */ +TYPED_TEST(Sha256Test, ExtendWitnessMixedInputs) +{ + STDLIB_TYPE_ALIASES + + Builder builder; + + // Make first and last words witnesses, rest constants + std::array input; + for (size_t i = 0; i < 16; i++) { + if (i == 0 || i == 15) { + input[i] = witness_ct(&builder, ABC_PADDED_BLOCK[i]); + } else { + input[i] = field_ct(&builder, ABC_PADDED_BLOCK[i]); + } + } + + // Compute circuit extension + std::array w_ext = SHA256::extend_witness(input); + + // Compute native extension for comparison + std::array w_native; + for (size_t i = 0; i < 16; i++) { + w_native[i] = ABC_PADDED_BLOCK[i]; + } + for (size_t i = 16; i < 64; i++) { + uint32_t s0 = std::rotr(w_native[i - 15], 7) ^ std::rotr(w_native[i - 15], 18) ^ (w_native[i - 15] >> 3); + uint32_t s1 = std::rotr(w_native[i - 2], 17) ^ std::rotr(w_native[i - 2], 19) ^ (w_native[i - 2] >> 10); + w_native[i] = w_native[i - 16] + s0 + w_native[i - 7] + s1; + } + + // Verify all 64 words match + for (size_t i = 0; i < 64; i++) { + uint32_t circuit_val = static_cast(uint256_t(w_ext[i].get_value())); + EXPECT_EQ(circuit_val, w_native[i]) << "extend_witness mismatch at index " << i; + } + + check_circuit_and_gate_count(builder, 3815); + EXPECT_EQ(builder.get_tables_size(), 20872); +} + /** * @brief Test extend_witness constraints (boomerang attack regression) * @@ -183,7 +367,7 @@ TYPED_TEST(Sha256Test, ExtendWitnessTamperingFailure) BB_DISABLE_ASSERTS(); - auto builder = Builder(); + Builder builder; std::array input; // Create random input witnesses and ensure they are constrained diff --git a/bootstrap.sh b/bootstrap.sh index 01918cd9def2..8bbd30ce9839 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -835,7 +835,7 @@ case "$cmd" in export NATIVE_PRESET=debug export AVM=0 export AVM_TRANSPILER=0 - barretenberg/cpp/bootstrap.sh build + barretenberg/cpp/bootstrap.sh ci ;; "ci-barretenberg") export CI=1 diff --git a/ci.sh b/ci.sh index fcde0b3e3c61..451c3a235d1b 100755 --- a/ci.sh +++ b/ci.sh @@ -128,7 +128,8 @@ case "$cmd" in barretenberg-debug) export CI_DASHBOARD="nightly" export JOB_ID="x-$cmd" - export CPUS=16 + export CPUS=192 + export AWS_SHUTDOWN_TIME=120 bootstrap_ec2 "./bootstrap.sh ci-$cmd" ;; avm-inputs-collection|avm-check-circuit)