Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
avm_completeness_bitwise_sha256_collision.test.cpp
Go to the documentation of this file.
1#include <cstdint>
2#include <vector>
3
4#include <gtest/gtest.h>
5
14
15namespace bb::avm2 {
16namespace {
17
18using simulation::Instruction;
19using IB = testing::InstructionBuilder;
21using testing::PublicTxSimulationTester;
22using testing::TestEnqueuedCall;
23
24Instruction set8(uint8_t dst, MemoryTag tag, uint8_t value)
25{
26 return IB(WireOpCode::SET_8).operand(dst).operand(tag).operand(value).build();
27}
28Instruction set32(uint16_t dst, MemoryTag tag, uint32_t value)
29{
30 return IB(WireOpCode::SET_32).operand(dst).operand(tag).operand(value).build();
31}
32Instruction xor8(uint8_t a, uint8_t b, uint8_t dst)
33{
34 return IB(WireOpCode::XOR_8).operand(a).operand(b).operand(dst).build();
35}
36Instruction calldatacopy(uint16_t copy_size, uint16_t cd_start, uint16_t dst)
37{
38 return IB(WireOpCode::CALLDATACOPY).operand(copy_size).operand(cd_start).operand(dst).build();
39}
40Instruction call(uint16_t l2_gas, uint16_t da_gas, uint16_t addr, uint16_t args_size, uint16_t args)
41{
42 return IB(WireOpCode::CALL).operand(l2_gas).operand(da_gas).operand(addr).operand(args_size).operand(args).build();
43}
44Instruction sha256compression(uint16_t out, uint16_t state, uint16_t inputs)
45{
46 return IB(WireOpCode::SHA256COMPRESSION).operand(out).operand(state).operand(inputs).build();
47}
48Instruction ret(uint16_t copy_size, uint16_t return_offset)
49{
50 return IB(WireOpCode::RETURN).operand(copy_size).operand(return_offset).build();
51}
52
53// Inner contract: XOR of a U32 cell with a U16 cell (tag mismatch). The mismatch makes the
54// instruction halt exceptionally and emits a bitwise "error row".
55std::vector<uint8_t> make_inner_bytecode()
56{
57 return encode_to_bytecode({
58 set8(/*dst=*/0, MemoryTag::U32, /*value=*/0),
59 set8(/*dst=*/1, MemoryTag::U16, /*value=*/0),
60 xor8(/*a=*/0, /*b=*/1, /*dst=*/2),
61 ret(/*copySizeOffset=*/0, /*returnOffset=*/0),
62 });
63}
64
65// Outer contract: calls the inner contract (whose address arrives as calldata[0]), then runs a
66// SHA256COMPRESSION whose message schedule computes sigma0(w[1]=0) = XOR(U32(0), U32(0)). The
67// caller-side bitwise lookup tuple (0, 0, 0, XOR, U32) used to collide with the inner call's error
68// row, violating BITW_NO_EXTERNAL_START_ON_ERROR and making the honest tx unprovable.
69std::vector<uint8_t> make_outer_bytecode()
70{
71 std::vector<Instruction> instructions = {
72 set8(/*dst=*/0, MemoryTag::U32, /*value=*/0),
73 set8(/*dst=*/1, MemoryTag::U32, /*value=*/1),
74 set32(/*dst=*/2, MemoryTag::U32, /*value=*/100'000),
75 calldatacopy(/*copySizeOffset=*/1, /*cdStartOffset=*/0, /*dstOffset=*/3),
76 call(/*l2GasOffset=*/2, /*daGasOffset=*/2, /*addrOffset=*/3, /*argsSizeOffset=*/0, /*argsOffset=*/0),
77 };
78
79 // SHA256 state words [10..17] = 0.
80 for (uint16_t i = 0; i < 8; ++i) {
81 instructions.push_back(set8(/*dst=*/static_cast<uint8_t>(10 + i), MemoryTag::U32, /*value=*/0));
82 }
83 // First input word [18], then input words [19..33] = 0. w[1] (cell [19]) is 0, which drives the
84 // sigma0(0) = XOR(U32(0), U32(0)) lookup that triggered the regression.
85 instructions.push_back(set32(/*dst=*/18, MemoryTag::U32, /*value=*/0x61626364));
86 for (uint16_t i = 0; i < 15; ++i) {
87 instructions.push_back(set8(/*dst=*/static_cast<uint8_t>(19 + i), MemoryTag::U32, /*value=*/0));
88 }
89
90 instructions.push_back(sha256compression(/*outputOffset=*/34, /*stateOffset=*/10, /*inputsOffset=*/18));
91 instructions.push_back(ret(/*copySizeOffset=*/0, /*returnOffset=*/0));
92 return encode_to_bytecode(instructions);
93}
94
95PublicSimulatorConfig proving_config()
96{
97 PublicSimulatorConfig config = PublicTxSimulationTester::default_config();
98 config.collect_hints = true;
99 config.collect_public_inputs = true;
100 return config;
101}
102
103// Regression guard for a completeness bug: an honest tx whose inner call emits a bitwise error row
104// (XOR with tag mismatch) and whose outer call runs SHA256COMPRESSION could not be proven, because
105// the sha256 bitwise lookup collided with the inner's error row on the 5-tuple (0, 0, 0, XOR, U32).
106// The fix adds a second input tag to the bitwise lookup so error rows (tag_a != tag_b) can never
107// collide with the caller's (u32, u32) lookup. This test proves the tx still goes through.
108TEST(AvmCompleteness, BitwiseSha256ErrorRowCollision)
109{
110 PublicTxSimulationTester tester;
111 const auto inner = tester.deploy_contract(make_inner_bytecode());
112 const auto outer = tester.deploy_contract(make_outer_bytecode());
113
114 // Inner call reverts (tag mismatch), outer runs SHA256 and RETURNs OK → top-level OK.
115 const TxSimulationResult fast_result =
116 tester.simulate_tx({ TestEnqueuedCall{ .contract_address = outer.address, .calldata = { inner.address } } });
117 EXPECT_EQ(fast_result.revert_code, RevertCode::OK);
118
119 // Simulation for hint generation, then proving (check circuit).
120 const TxSimulationResult hint_result = tester.simulate_tx(
121 { TestEnqueuedCall{ .contract_address = outer.address, .calldata = { inner.address } } }, proving_config());
122 ASSERT_TRUE(hint_result.public_inputs.has_value());
123 ASSERT_TRUE(hint_result.hints.has_value());
124
125 const AvmProvingInputs proving_inputs{ .public_inputs = *hint_result.public_inputs, .hints = *hint_result.hints };
126 AvmAPI api;
127 EXPECT_TRUE(api.check_circuit(proving_inputs));
128}
129
130} // namespace
131} // namespace bb::avm2
FF a
FF b
uint64_t da_gas
uint64_t l2_gas
AvmProvingInputs inputs
std::vector< uint8_t > encode_to_bytecode(const std::vector< Instruction > &instructions)
Instruction
Enumeration of VM instructions that can be executed.
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
bb::VectorAffineElementPushSpan< BaseParams > out