Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,21 @@ void ControlFlow::process_finalize_with_return(FinalizeWithReturn instruction)
current_block = non_terminated_blocks.at(0);
}

void ControlFlow::process_finalize_with_revert(FinalizeWithRevert instruction)
{
if (this->current_block->terminator_type != TerminatorType::NONE) {
return;
}
current_block->finalize_with_revert(instruction.revert_options.return_size,
instruction.revert_options.return_value_tag,
instruction.revert_options.return_value_offset_index);
std::vector<ProgramBlock*> non_terminated_blocks = get_non_terminated_blocks();
if (non_terminated_blocks.size() == 0) {
return;
}
current_block = non_terminated_blocks.at(0);
}

void ControlFlow::process_switch_to_non_terminated_block(SwitchToNonTerminatedBlock instruction)
{
std::vector<ProgramBlock*> non_terminated_blocks = get_non_terminated_blocks();
Expand Down Expand Up @@ -214,6 +229,7 @@ void ControlFlow::process_cfg_instruction(CFGInstruction instruction)
[&](JumpToBlock arg) { process_jump_to_block(arg); },
[&](JumpIfToBlock arg) { process_jump_if_to_block(arg); },
[&](FinalizeWithReturn arg) { process_finalize_with_return(arg); },
[&](FinalizeWithRevert arg) { process_finalize_with_revert(arg); },
[&](SwitchToNonTerminatedBlock arg) { process_switch_to_non_terminated_block(arg); },
[&](InsertInternalCall arg) { process_insert_internal_call(arg); } },
instruction);
Expand All @@ -239,7 +255,8 @@ int predict_block_size(ProgramBlock* block)
auto bytecode_length = static_cast<int>(create_bytecode(block->get_instructions()).size());
switch (block->terminator_type) {
case TerminatorType::RETURN:
return bytecode_length; // finalized with return, already counted
case TerminatorType::REVERT:
return bytecode_length; // finalized with return/revert, already counted
case TerminatorType::JUMP:
return bytecode_length + JMP_SIZE; // finalized with jump
case TerminatorType::JUMP_IF: {
Expand All @@ -263,9 +280,9 @@ int predict_block_size(ProgramBlock* block)
return bytecode_length + JMP_IF_SIZE + JMP_SIZE; // finalized with jumpi
}
default:
throw std::runtime_error("Predict block size: Every block should be terminated with return, jump, or jumpi, "
"got " +
std::to_string(static_cast<int>(block->terminator_type)));
throw std::runtime_error(
"Predict block size: Every block should be terminated with return, revert, jump, or jumpi, got " +
std::to_string(static_cast<int>(block->terminator_type)));
}
throw std::runtime_error("Unreachable");
}
Expand Down Expand Up @@ -315,6 +332,9 @@ std::vector<uint8_t> ControlFlow::build_bytecode(const ReturnOptions& return_opt
case TerminatorType::RETURN: // finalized with return
// already terminated with return
break;
case TerminatorType::REVERT: // finalized with revert
// already terminated with revert
break;
case TerminatorType::JUMP: { // finalized with jump
ProgramBlock* target_block = block->successors.at(0);
size_t target_block_idx = find_block_idx(target_block, blocks);
Expand Down Expand Up @@ -347,7 +367,7 @@ std::vector<uint8_t> ControlFlow::build_bytecode(const ReturnOptions& return_opt
}
default:
throw std::runtime_error(
"Inject terminators: Every block should be terminated with return, jump, or jumpi");
"Inject terminators: Every block should be terminated with return, revert, jump, or jumpi");
}
block_bytecodes.push_back(create_bytecode(instructions));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ struct FinalizeWithReturn {
MSGPACK_FIELDS(return_options);
};

/// @brief finalizes the current block with Revert and switches to the first non-terminated block
struct FinalizeWithRevert {
ReturnOptions revert_options;
MSGPACK_FIELDS(revert_options);
};

/// @brief switches to the non-terminated block with the chosen index
struct SwitchToNonTerminatedBlock {
uint16_t non_terminated_block_idx;
Expand All @@ -83,6 +89,7 @@ using CFGInstruction = std::variant<InsertSimpleInstructionBlock,
JumpToBlock,
JumpIfToBlock,
FinalizeWithReturn,
FinalizeWithRevert,
SwitchToNonTerminatedBlock,
InsertInternalCall>;
template <class... Ts> struct overloaded_cfg_instruction : Ts... {
Expand Down Expand Up @@ -111,6 +118,10 @@ inline std::ostream& operator<<(std::ostream& os, const CFGInstruction& instruct
os << "FinalizeWithReturn " << arg.return_options.return_size << " "
<< arg.return_options.return_value_tag << " " << arg.return_options.return_value_offset_index;
},
[&](FinalizeWithRevert arg) {
os << "FinalizeWithRevert " << arg.revert_options.return_size << " "
<< arg.revert_options.return_value_tag << " " << arg.revert_options.return_value_offset_index;
},
[&](SwitchToNonTerminatedBlock arg) {
os << "SwitchToNonTerminatedBlock " << arg.non_terminated_block_idx;
},
Expand Down Expand Up @@ -160,6 +171,10 @@ class ControlFlow {
/// @param instruction the instruction to process
void process_finalize_with_return(FinalizeWithReturn instruction);

/// @brief terminates the current block with Revert and switches to the first non-terminated block
/// @param instruction the instruction to process
void process_finalize_with_revert(FinalizeWithRevert instruction);

/// @brief switches to the non-terminated block with the chosen index
/// @param instruction the instruction to process
void process_switch_to_non_terminated_block(SwitchToNonTerminatedBlock instruction);
Expand Down
228 changes: 228 additions & 0 deletions barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1646,3 +1646,231 @@ TEST(fuzz, StaticCallToNonStaticFunctionSuccessCopy)
EXPECT_EQ(result.reverted, false);
}
} // namespace external_calls

namespace crypto_ops {

TEST(fuzz, Poseidon2PermSmoke)
{
// Set up 4 FF values for Poseidon2 permutation input
std::vector<FuzzInstruction> instructions;
for (uint32_t i = 0; i < 4; i++) {
instructions.push_back(
SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF,
.result_address = AddressRef{ .address = i, .mode = AddressingMode::Direct },
.value = FF(i + 1) });
}
// Poseidon2 permutation: reads from address 0-3, writes to address 10-13
instructions.push_back(
POSEIDON2PERM_Instruction{ .src_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct },
.dst_address = AddressRef{ .address = 10, .mode = AddressingMode::Direct } });

auto instruction_blocks = std::vector<std::vector<FuzzInstruction>>{ instructions };
auto control_flow = ControlFlow(instruction_blocks);
control_flow.process_cfg_instruction(InsertSimpleInstructionBlock{ .instruction_block_idx = 0 });
auto bytecode = control_flow.build_bytecode(
ReturnOptions{ .return_size = 1, .return_value_tag = bb::avm2::MemoryTag::FF, .return_value_offset_index = 2 });

auto result = simulate_with_default_tx(bytecode, {});
EXPECT_FALSE(result.reverted);
}

TEST(fuzz, Keccakf1600Smoke)
{
// Set up 25 U64 values for Keccak-f[1600] permutation input
std::vector<FuzzInstruction> instructions;
for (uint32_t i = 0; i < 25; i++) {
instructions.push_back(
SET_64_Instruction{ .value_tag = bb::avm2::MemoryTag::U64,
.result_address = AddressRef{ .address = i, .mode = AddressingMode::Direct },
.value = static_cast<uint64_t>(i + 1) });
}
// Keccak-f[1600]: reads from address 0-24, writes to address 100-124
instructions.push_back(
KECCAKF1600_Instruction{ .src_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct },
.dst_address = AddressRef{ .address = 100, .mode = AddressingMode::Direct } });

auto instruction_blocks = std::vector<std::vector<FuzzInstruction>>{ instructions };
auto control_flow = ControlFlow(instruction_blocks);
control_flow.process_cfg_instruction(InsertSimpleInstructionBlock{ .instruction_block_idx = 0 });
auto bytecode = control_flow.build_bytecode(ReturnOptions{
.return_size = 1, .return_value_tag = bb::avm2::MemoryTag::U64, .return_value_offset_index = 25 });

auto result = simulate_with_default_tx(bytecode, {});
EXPECT_FALSE(result.reverted);
}

TEST(fuzz, Sha256CompressionSmoke)
{
// Set up 8 U32 values for state and 16 U32 values for input
std::vector<FuzzInstruction> instructions;

// State: addresses 0-7
for (uint32_t i = 0; i < 8; i++) {
instructions.push_back(
SET_32_Instruction{ .value_tag = bb::avm2::MemoryTag::U32,
.result_address = AddressRef{ .address = i, .mode = AddressingMode::Direct },
.value = i + 1 });
}
// Input: addresses 20-35
for (uint32_t i = 0; i < 16; i++) {
instructions.push_back(
SET_32_Instruction{ .value_tag = bb::avm2::MemoryTag::U32,
.result_address = AddressRef{ .address = 20 + i, .mode = AddressingMode::Direct },
.value = i + 100 });
}
// SHA256 compression: state at 0-7, input at 20-35, output at 50-57
instructions.push_back(
SHA256COMPRESSION_Instruction{ .state_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct },
.input_address = AddressRef{ .address = 20, .mode = AddressingMode::Direct },
.dst_address = AddressRef{ .address = 50, .mode = AddressingMode::Direct } });

auto instruction_blocks = std::vector<std::vector<FuzzInstruction>>{ instructions };
auto control_flow = ControlFlow(instruction_blocks);
control_flow.process_cfg_instruction(InsertSimpleInstructionBlock{ .instruction_block_idx = 0 });
auto bytecode = control_flow.build_bytecode(ReturnOptions{
.return_size = 1, .return_value_tag = bb::avm2::MemoryTag::U32, .return_value_offset_index = 12 });

auto result = simulate_with_default_tx(bytecode, {});
EXPECT_FALSE(result.reverted);
}

TEST(fuzz, EcaddSmoke)
{
std::vector<FuzzInstruction> instructions;

// Use the generator point G = (1, sqrt(-16)) as p1
// For simplicity, we'll use infinity points which should work
// Set p1 as infinity: x=0, y=0, infinite=1
instructions.push_back(
SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF,
.result_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct },
.value = FF(0) }); // p1.x
instructions.push_back(
SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF,
.result_address = AddressRef{ .address = 1, .mode = AddressingMode::Direct },
.value = FF(0) }); // p1.y
instructions.push_back(
SET_8_Instruction{ .value_tag = bb::avm2::MemoryTag::U1,
.result_address = AddressRef{ .address = 2, .mode = AddressingMode::Direct },
.value = 1 }); // p1.infinite = true

// Set p2 as infinity too
instructions.push_back(
SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF,
.result_address = AddressRef{ .address = 3, .mode = AddressingMode::Direct },
.value = FF(0) }); // p2.x
instructions.push_back(
SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF,
.result_address = AddressRef{ .address = 4, .mode = AddressingMode::Direct },
.value = FF(0) }); // p2.y
instructions.push_back(
SET_8_Instruction{ .value_tag = bb::avm2::MemoryTag::U1,
.result_address = AddressRef{ .address = 5, .mode = AddressingMode::Direct },
.value = 1 }); // p2.infinite = true

// ECADD instruction: infinity + infinity = infinity
instructions.push_back(ECADD_Instruction{ .p1_x = AddressRef{ .address = 0, .mode = AddressingMode::Direct },
.p1_y = AddressRef{ .address = 1, .mode = AddressingMode::Direct },
.p1_infinite = AddressRef{ .address = 2, .mode = AddressingMode::Direct },
.p2_x = AddressRef{ .address = 3, .mode = AddressingMode::Direct },
.p2_y = AddressRef{ .address = 4, .mode = AddressingMode::Direct },
.p2_infinite = AddressRef{ .address = 5, .mode = AddressingMode::Direct },
.result = AddressRef{ .address = 10, .mode = AddressingMode::Direct } });

auto instruction_blocks = std::vector<std::vector<FuzzInstruction>>{ instructions };
auto control_flow = ControlFlow(instruction_blocks);
control_flow.process_cfg_instruction(InsertSimpleInstructionBlock{ .instruction_block_idx = 0 });
// Return the infinite flag of the result (should be 1)
auto bytecode = control_flow.build_bytecode(
ReturnOptions{ .return_size = 1, .return_value_tag = bb::avm2::MemoryTag::U1, .return_value_offset_index = 3 });

auto result = simulate_with_default_tx(bytecode, {});
EXPECT_FALSE(result.reverted);
}

} // namespace crypto_ops

namespace conversions {

TEST(fuzz, ToRadixBESmoke)
{
std::vector<FuzzInstruction> instructions;

// Set value to convert (FF)
instructions.push_back(
SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF,
.result_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct },
.value = FF(255) });
// Set radix (U32) = 2 (binary)
instructions.push_back(
SET_32_Instruction{ .value_tag = bb::avm2::MemoryTag::U32,
.result_address = AddressRef{ .address = 1, .mode = AddressingMode::Direct },
.value = 2 });
// Set num_limbs (U32) = 8
instructions.push_back(
SET_32_Instruction{ .value_tag = bb::avm2::MemoryTag::U32,
.result_address = AddressRef{ .address = 2, .mode = AddressingMode::Direct },
.value = 8 });
// Set output_bits (U1) = 1 (since radix is 2)
instructions.push_back(
SET_8_Instruction{ .value_tag = bb::avm2::MemoryTag::U1,
.result_address = AddressRef{ .address = 3, .mode = AddressingMode::Direct },
.value = 1 });

// TORADIXBE instruction
instructions.push_back(
TORADIXBE_Instruction{ .value_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct },
.radix_address = AddressRef{ .address = 1, .mode = AddressingMode::Direct },
.num_limbs_address = AddressRef{ .address = 2, .mode = AddressingMode::Direct },
.output_bits_address = AddressRef{ .address = 3, .mode = AddressingMode::Direct },
.dst_address = AddressRef{ .address = 10, .mode = AddressingMode::Direct },
.is_output_bits = true });

auto instruction_blocks = std::vector<std::vector<FuzzInstruction>>{ instructions };
auto control_flow = ControlFlow(instruction_blocks);
control_flow.process_cfg_instruction(InsertSimpleInstructionBlock{ .instruction_block_idx = 0 });
auto bytecode = control_flow.build_bytecode(
ReturnOptions{ .return_size = 1, .return_value_tag = bb::avm2::MemoryTag::U1, .return_value_offset_index = 2 });

auto result = simulate_with_default_tx(bytecode, {});
EXPECT_FALSE(result.reverted);
}

} // namespace conversions

namespace l1_l2_messaging {

TEST(fuzz, L1ToL2MsgExistsSmoke)
{
std::vector<FuzzInstruction> instructions;

// Set msg_hash (FF)
instructions.push_back(
SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF,
.result_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct },
.value = FF(12345) });
// Set leaf_index (U64)
instructions.push_back(
SET_64_Instruction{ .value_tag = bb::avm2::MemoryTag::U64,
.result_address = AddressRef{ .address = 1, .mode = AddressingMode::Direct },
.value = 0 });

// L1TOL2MSGEXISTS instruction - check if message exists (it won't, but shouldn't revert)
instructions.push_back(
L1TOL2MSGEXISTS_Instruction{ .msg_hash_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct },
.leaf_index_address = AddressRef{ .address = 1, .mode = AddressingMode::Direct },
.result_address = AddressRef{ .address = 2, .mode = AddressingMode::Direct } });

auto instruction_blocks = std::vector<std::vector<FuzzInstruction>>{ instructions };
auto control_flow = ControlFlow(instruction_blocks);
control_flow.process_cfg_instruction(InsertSimpleInstructionBlock{ .instruction_block_idx = 0 });
auto bytecode = control_flow.build_bytecode(
ReturnOptions{ .return_size = 1, .return_value_tag = bb::avm2::MemoryTag::U1, .return_value_offset_index = 0 });

auto result = simulate_with_default_tx(bytecode, {});
EXPECT_FALSE(result.reverted);
// Message doesn't exist, so result should be 0
EXPECT_EQ(result.output.at(0), FF::zero());
}

} // namespace l1_l2_messaging
Original file line number Diff line number Diff line change
Expand Up @@ -1586,6 +1586,34 @@ void ProgramBlock::finalize_with_return(uint8_t return_size,
instructions.push_back(return_instruction);
}

void ProgramBlock::finalize_with_revert(uint8_t revert_size,
MemoryTagWrapper revert_value_tag,
uint16_t revert_value_offset_index)
{
this->terminator_type = TerminatorType::REVERT;

auto revert_addr = memory_manager.get_memory_offset_16(revert_value_tag.value, revert_value_offset_index);
if (!revert_addr.has_value()) {
revert_addr = std::optional<uint32_t>(0);
}

// Once we do more of the randomness in Instruction selection, revert_size_offset we shouldnt need to hardcode
uint16_t revert_size_offset = 5U;
// Ensure operands are created as U16 to match wire format (UINT16)
auto set_size_instruction = bb::avm2::testing::InstructionBuilder(bb::avm2::WireOpCode::SET_16)
.operand(revert_size_offset)
.operand(bb::avm2::MemoryTag::U32)
.operand(static_cast<uint16_t>(revert_size))
.build();
instructions.push_back(set_size_instruction);
// REVERT_16 expects UINT16 operands, ensure we cast to uint16_t explicitly
auto revert_instruction = bb::avm2::testing::InstructionBuilder(bb::avm2::WireOpCode::REVERT_16)
.operand(static_cast<uint16_t>(revert_size_offset))
.operand(revert_addr.value())
.build();
instructions.push_back(revert_instruction);
}

void ProgramBlock::finalize_with_jump(ProgramBlock* target_block, bool copy_memory_manager)
{
this->terminator_type = TerminatorType::JUMP;
Expand Down
Loading
Loading