Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 22 additions & 0 deletions barretenberg/cpp/pil/vm2/sha256.pil
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,28 @@ namespace sha256;
pol commit h;
start * (1 - err) * (h - init_h) = 0;

// Propagate initial state values across all rounds.
Comment thread
dbanks12 marked this conversation as resolved.
// Initial states must be propagated correctly becuse they are used in the `last` row
// when adding to the compression output. `last` is mutually exclusive to `perform_round` but
// these constraints don't need to use `last` because on the 64th row of `perform_round` we
// will constrain the next row (i.e. the `last` row) to be correct.
#[PROPAGATE_INIT_A]
perform_round * (init_a' - init_a) = 0;
#[PROPAGATE_INIT_B]
perform_round * (init_b' - init_b) = 0;
#[PROPAGATE_INIT_C]
perform_round * (init_c' - init_c) = 0;
#[PROPAGATE_INIT_D]
perform_round * (init_d' - init_d) = 0;
#[PROPAGATE_INIT_E]
perform_round * (init_e' - init_e) = 0;
#[PROPAGATE_INIT_F]
perform_round * (init_f' - init_f) = 0;
#[PROPAGATE_INIT_G]
perform_round * (init_g' - init_g) = 0;
#[PROPAGATE_INIT_H]
perform_round * (init_h' - init_h) = 0;

// ========== COMPUTE W =============
// w is only computed on the 16th round (as 0-15 are populated from the input)
// s0 := (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor (w[i-15] rightshift 3)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -738,5 +738,74 @@ TEST(Sha256MemoryConstrainingTest, InputAddrTamperingIsCaughtByConstraint)
"CONTINUITY_INPUT_ADDR");
}

//////////////////////////////////////////
/// Negative Test - init_* Propagation Constraint (PROP-001)
//////////////////////////////////////////

// This test verifies that init_a through init_h are properly propagated across multi-row computation.
//
// BACKGROUND: SHA256 compression uses init_a-init_h values loaded from memory on row 0, and these
// values are used in the final output calculation (OUT_X = x + init_x) on the last row.
//
// The PROPAGATE_INIT_* constraints (sha256.pil lines 111-126) ensure that init values remain
// constant across all rounds. Without these constraints, a malicious prover could:
// 1. Set correct init_a on row 0 (to pass memory read constraint)
// 2. Set arbitrary init_a on later rows
// 3. Corrupt the final SHA256 output
//
// This test verifies that tampering with init_a is caught by the PROPAGATE_INIT_A constraint.
TEST(Sha256ConstrainingTest, InitStateTamperingIsCaughtByPropagationConstraint)
{
// Generate a valid SHA256 compression trace
MemoryStore mem;
StrictMock<MockExecutionIdManager> execution_id_manager;
EXPECT_CALL(execution_id_manager, get_execution_id()).WillRepeatedly(Return(1));
PureGreaterThan gt;
PureBitwise bitwise;

EventEmitter<Sha256CompressionEvent> sha256_event_emitter;
Sha256 sha256_gadget(execution_id_manager, bitwise, gt, sha256_event_emitter);

// Set up valid memory for state and input
std::array<uint32_t, 8> state = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
MemoryAddress state_addr = 0;
for (uint32_t i = 0; i < 8; ++i) {
mem.set(state_addr + i, MemoryValue::from<uint32_t>(state[i]));
}

std::array<uint32_t, 16> input = { 0x61626380, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x18 };
MemoryAddress input_addr = 8;
for (uint32_t i = 0; i < 16; ++i) {
mem.set(input_addr + i, MemoryValue::from<uint32_t>(input[i]));
}
MemoryAddress output_addr = 25;

sha256_gadget.compression(mem, state_addr, input_addr, output_addr);

TestTraceContainer trace;
trace.set(C::precomputed_first_row, 0, 1);
Sha256TraceBuilder builder;
builder.process(sha256_event_emitter.dump_events(), trace);

// Verify the trace is valid before tampering
ASSERT_NO_THROW(check_relation<sha256>(trace));

// Find a row where perform_round=1 (any round row works, but use row 1 for simplicity)
// Row 0 is start, rows 1-64 are rounds with perform_round=1
constexpr uint32_t TAMPER_ROW = 1;
ASSERT_EQ(trace.get(C::sha256_perform_round, TAMPER_ROW), FF(1)) << "Row 1 should have perform_round=1";

// Tamper with init_a on row 1 (making it different from row 0)
FF original_init_a = trace.get(C::sha256_init_a, TAMPER_ROW);
FF tampered_init_a = original_init_a + FF(0x12345678);
trace.set(C::sha256_init_a, TAMPER_ROW, tampered_init_a);

// The PROPAGATE_INIT_A constraint should catch this:
// perform_round * (init_a' - init_a) = 0
// On row 0: perform_round=1, init_a'=tampered, init_a=original -> constraint violated
EXPECT_THROW_WITH_MESSAGE(check_relation<sha256>(trace, sha256::SR_PROPAGATE_INIT_A), "PROPAGATE_INIT_A");
Comment on lines +799 to +807

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.

Before fix, this tampering did not fail constraints

}

} // namespace
} // namespace bb::avm2::constraining
14 changes: 7 additions & 7 deletions barretenberg/cpp/src/barretenberg/vm2/generated/columns.hpp

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ namespace bb::avm2 {
struct AvmFlavorVariables {
static constexpr size_t NUM_PRECOMPUTED_ENTITIES = 123;
static constexpr size_t NUM_WITNESS_ENTITIES = 3061;
static constexpr size_t NUM_SHIFTED_ENTITIES = 345;
static constexpr size_t NUM_SHIFTED_ENTITIES = 353;
static constexpr size_t NUM_WIRES = 2595;
static constexpr size_t NUM_ALL_ENTITIES = 3529;
static constexpr size_t NUM_ALL_ENTITIES = 3537;

// Need to be templated for recursive verifier
template <typename FF_>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ template <typename FF_> class sha256Impl {
public:
using FF = FF_;

static constexpr std::array<size_t, 90> SUBRELATION_PARTIAL_LENGTHS = {
3, 2, 4, 4, 3, 4, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
static constexpr std::array<size_t, 98> SUBRELATION_PARTIAL_LENGTHS = {
3, 2, 4, 4, 3, 4, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3
};

template <typename AllEntities> inline static bool skip(const AllEntities& in)
Expand All @@ -38,9 +38,36 @@ template <typename FF> class sha256 : public Relation<sha256Impl<FF>> {
public:
static constexpr const std::string_view NAME = "sha256";

// Subrelation indices constants, to be used in tests.
static constexpr size_t SR_PROPAGATE_INIT_A = 16;
static constexpr size_t SR_PROPAGATE_INIT_B = 17;
static constexpr size_t SR_PROPAGATE_INIT_C = 18;
static constexpr size_t SR_PROPAGATE_INIT_D = 19;
static constexpr size_t SR_PROPAGATE_INIT_E = 20;
static constexpr size_t SR_PROPAGATE_INIT_F = 21;
static constexpr size_t SR_PROPAGATE_INIT_G = 22;
static constexpr size_t SR_PROPAGATE_INIT_H = 23;

static std::string get_subrelation_label(size_t index)
{
switch (index) {}
switch (index) {
case SR_PROPAGATE_INIT_A:
return "PROPAGATE_INIT_A";
case SR_PROPAGATE_INIT_B:
return "PROPAGATE_INIT_B";
case SR_PROPAGATE_INIT_C:
return "PROPAGATE_INIT_C";
case SR_PROPAGATE_INIT_D:
return "PROPAGATE_INIT_D";
case SR_PROPAGATE_INIT_E:
return "PROPAGATE_INIT_E";
case SR_PROPAGATE_INIT_F:
return "PROPAGATE_INIT_F";
case SR_PROPAGATE_INIT_G:
return "PROPAGATE_INIT_G";
case SR_PROPAGATE_INIT_H:
return "PROPAGATE_INIT_H";
}
return std::to_string(index);
}
};
Expand Down
Loading
Loading