From c0126da825d79e88810e3b2e0072a17360ba983a Mon Sep 17 00:00:00 2001 From: jeanmon Date: Thu, 25 Jan 2024 12:57:47 +0000 Subject: [PATCH] 3791 - Add unit tests for bytecode parsing and execution --- .../vm/avm_trace/AvmMini_common.hpp | 4 + .../vm/avm_trace/AvmMini_execution.cpp | 8 +- .../vm/avm_trace/AvmMini_execution.hpp | 5 +- .../vm/avm_trace/AvmMini_trace.cpp | 5 + .../vm/tests/AvmMini_arithmetic.test.cpp | 10 +- .../vm/tests/AvmMini_control_flow.test.cpp | 3 +- .../vm/tests/AvmMini_execution.test.cpp | 319 ++++++++++++++++++ .../vm/tests/AvmMini_memory.test.cpp | 5 +- 8 files changed, 347 insertions(+), 12 deletions(-) create mode 100644 barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_execution.test.cpp diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_common.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_common.hpp index 10b3c3473304..f8e06ef180bd 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_common.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_common.hpp @@ -2,6 +2,7 @@ #include "barretenberg/proof_system/circuit_builder/circuit_builder_base.hpp" #include "barretenberg/proof_system/circuit_builder/generated/AvmMini_circuit_builder.hpp" +#include using Flavor = bb::honk::flavor::AvmMiniFlavor; using FF = Flavor::FF; @@ -12,6 +13,9 @@ namespace avm_trace { // Number of rows static const size_t AVM_TRACE_SIZE = 256; enum class IntermRegister : uint32_t { IA = 0, IB = 1, IC = 2 }; + +// Keep following enum in sync with MAX_NEM_TAG below enum class AvmMemoryTag : uint32_t { U0 = 0, U8 = 1, U16 = 2, U32 = 3, U64 = 4, U128 = 5, FF = 6 }; +static const uint32_t MAX_MEM_TAG = 6; } // namespace avm_trace \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.cpp index 417b74e52572..52b3867b6630 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.cpp @@ -63,8 +63,11 @@ std::vector Execution::parse(std::vector const& bytecode) auto in_tag_u8 = static_cast(AvmMemoryTag::U0); if (Bytecode::has_in_tag(opcode)) { + if (pos + AVM_IN_TAG_BYTE_LENGTH > length) { + throw std::runtime_error("Instruction tag missing at position " + std::to_string(pos)); + } in_tag_u8 = bytecode.at(pos); - if (in_tag_u8 == static_cast(AvmMemoryTag::U0) || in_tag_u8 == 7) { + if (in_tag_u8 == static_cast(AvmMemoryTag::U0) || in_tag_u8 > MAX_MEM_TAG) { throw std::runtime_error("Instruction tag is invalid at position " + std::to_string(pos) + " value: " + std::to_string(in_tag_u8)); } @@ -109,8 +112,7 @@ std::vector Execution::parse(std::vector const& bytecode) } if (pos + operands_size > length) { - throw std::runtime_error("Bytecode does not contain enough bytes for operands at position " + - std::to_string(pos)); + throw std::runtime_error("Operand is missing at position " + std::to_string(pos)); } if (opcode == OpCode::SET && in_tag == AvmMemoryTag::U8) { diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.hpp index f6610ce9f2cb..b969510d8c41 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.hpp @@ -13,15 +13,14 @@ namespace avm_trace { class Execution { public: Execution() = default; - static plonk::proof run_and_prove(std::vector const& bytecode, std::vector const& calldata); + static size_t const AVM_OPERAND_BYTE_LENGTH = 4; // Keep in sync with TS code static size_t const AVM_OPCODE_BYTE_LENGTH = 1; // Keep in sync with TS code static size_t const AVM_IN_TAG_BYTE_LENGTH = 1; // Keep in sync with TS code - private: - AvmMiniTraceBuilder trace_builder{}; static std::vector parse(std::vector const& bytecode); static std::vector gen_trace(std::vector const& instructions, std::vector const& calldata); + static plonk::proof run_and_prove(std::vector const& bytecode, std::vector const& calldata); }; } // namespace avm_trace \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_trace.cpp index 771a1590ea1f..bd72d96afdd2 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_trace.cpp @@ -375,6 +375,11 @@ void AvmMiniTraceBuilder::call_data_copy(uint32_t cd_offset, */ std::vector AvmMiniTraceBuilder::return_op(uint32_t ret_offset, uint32_t ret_size) { + if (ret_size == 0) { + halt(); + return std::vector{}; + } + // We parallelize loading memory operations in chunk of 3, i.e., 1 per intermediate register. // The variable pos is an index pointing to the first storing operation (pertaining to intermediate // register Ia) relative to ret_offset: diff --git a/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_arithmetic.test.cpp b/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_arithmetic.test.cpp index 15f4e0a37eee..cc2bb3e908ec 100644 --- a/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_arithmetic.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_arithmetic.test.cpp @@ -3,8 +3,8 @@ #include "barretenberg/numeric/uint128/uint128.hpp" using namespace numeric; -using namespace tests_avm; namespace { +using namespace tests_avm; void common_validate_arithmetic_op(Row const& main_row, Row const& alu_row, @@ -35,7 +35,7 @@ void common_validate_arithmetic_op(Row const& main_row, // Check the instruction tag EXPECT_EQ(main_row.avmMini_in_tag, FF(static_cast(tag))); - // Check that intermediate rgiesters are correctly copied in Alu trace + // Check that intermediate registers are correctly copied in Alu trace EXPECT_EQ(alu_row.aluChip_alu_ia, a); EXPECT_EQ(alu_row.aluChip_alu_ib, b); EXPECT_EQ(alu_row.aluChip_alu_ic, c); @@ -184,7 +184,9 @@ std::vector gen_mutated_trace_mul(FF const& a, FF const& b, FF const& c_mut } // anonymous namespace +namespace tests_avm { using namespace avm_trace; + class AvmMiniArithmeticTests : public ::testing::Test { public: AvmMiniTraceBuilder trace_builder; @@ -1675,4 +1677,6 @@ TEST_F(AvmMiniArithmeticNegativeTestsU128, multiplication) FF{ uint256_t::from_uint128(c) }, AvmMemoryTag::U128); EXPECT_THROW_WITH_MESSAGE(validate_trace_proof(std::move(trace)), "ALU_MULTIPLICATION_OUT_U128"); -} \ No newline at end of file +} + +} // namespace tests_avm \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_control_flow.test.cpp b/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_control_flow.test.cpp index 657ae8e25b1b..11ede61acfcf 100644 --- a/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_control_flow.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_control_flow.test.cpp @@ -1,7 +1,7 @@ #include "AvmMini_common.test.hpp" +namespace tests_avm { using namespace avm_trace; -using namespace tests_avm; class AvmMiniControlFlowTests : public ::testing::Test { public: @@ -285,3 +285,4 @@ TEST_F(AvmMiniControlFlowTests, multipleCallsAndReturns) validate_trace_proof(std::move(trace)); } +} // namespace tests_avm \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_execution.test.cpp b/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_execution.test.cpp new file mode 100644 index 000000000000..433096fb5906 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_execution.test.cpp @@ -0,0 +1,319 @@ +#include "barretenberg/vm/avm_trace/AvmMini_execution.hpp" +#include "AvmMini_common.test.hpp" +#include "barretenberg/vm/avm_trace/AvmMini_common.hpp" +#include "barretenberg/vm/avm_trace/AvmMini_helper.hpp" +#include "barretenberg/vm/avm_trace/AvmMini_opcode.hpp" +#include "barretenberg/vm/tests/helpers.test.hpp" +#include +#include +#include +#include + +namespace { + +// TODO: move to a common utils BB file (following code copied from ecdsa.test.cpp) +std::vector hex_to_bytes(const std::string& hex) +{ + std::vector bytes; + + for (unsigned int i = 0; i < hex.length(); i += 2) { + std::string byteString = hex.substr(i, 2); + bytes.push_back(static_cast(strtol(byteString.c_str(), nullptr, 16))); + } + + return bytes; +} + +} // anonymous namespace + +namespace tests_avm { +using namespace avm_trace; + +class AvmMiniExecutionTests : public ::testing::Test { + public: + AvmMiniTraceBuilder trace_builder; + + protected: + // TODO(640): The Standard Honk on Grumpkin test suite fails unless the SRS is initialised for every test. + void SetUp() override + { + srs::init_crs_factory("../srs_db/ignition"); + trace_builder = AvmMiniTraceBuilder(); // Clean instance for every run. + }; +}; + +// Basic positive test with an ADD and RETURN opcode. +// Parsing, trace generation and proving is verified. +TEST_F(AvmMiniExecutionTests, basicAddReturn) +{ + std::string bytecode_hex = "00" // ADD + "01" // U8 + "00000007" // addr a 7 + "00000009" // addr b 9 + "00000001" // addr c 1 + "34" // RETURN + "00000000" // ret offset 0 + "00000000"; // ret size 0 + + auto bytecode = hex_to_bytes(bytecode_hex); + auto instructions = Execution::parse(bytecode); + + // 2 instructions + EXPECT_EQ(instructions.size(), 2); + + // ADD + EXPECT_EQ(instructions.at(0).op_code, OpCode::ADD); + EXPECT_EQ(instructions.at(0).operands.size(), 3); + EXPECT_EQ(instructions.at(0).operands.at(0), 7); + EXPECT_EQ(instructions.at(0).operands.at(1), 9); + EXPECT_EQ(instructions.at(0).operands.at(2), 1); + EXPECT_EQ(instructions.at(0).in_tag, AvmMemoryTag::U8); + + // RETURN + EXPECT_EQ(instructions.at(1).op_code, OpCode::RETURN); + EXPECT_EQ(instructions.at(1).operands.size(), 2); + EXPECT_EQ(instructions.at(1).operands.at(0), 0); + EXPECT_EQ(instructions.at(1).operands.at(0), 0); + + auto trace = Execution::gen_trace(instructions, std::vector{}); + auto trace_verif = trace; + validate_trace_proof(std::move(trace)); + + auto circuit_builder = AvmMiniCircuitBuilder(); + circuit_builder.set_trace(std::move(trace_verif)); + auto composer = honk::AvmMiniComposer(); + auto verifier = composer.create_verifier(circuit_builder); + + auto proof = Execution::run_and_prove(bytecode, std::vector{}); + + EXPECT_TRUE(verifier.verify_proof(proof)); +} + +// Positive test for SET and SUB opcodes +TEST_F(AvmMiniExecutionTests, setAndSubOpcodes) +{ + std::string bytecode_hex = "27" // SET 39 = 0x27 + "02" // U16 + "B813" // val 47123 + "000000AA" // dst_offset 170 + "27" // SET 39 = 0x27 + "02" // U16 + "9103" // val 37123 + "00000033" // dst_offset 51 + "01" // SUB + "02" // U16 + "000000AA" // addr a + "00000033" // addr b + "00000001" // addr c 1 + "34" // RETURN + "00000000" // ret offset 0 + "00000000"; // ret size 0 + + auto bytecode = hex_to_bytes(bytecode_hex); + auto instructions = Execution::parse(bytecode); + + EXPECT_EQ(instructions.size(), 4); + + // SET + EXPECT_EQ(instructions.at(0).op_code, OpCode::SET); + EXPECT_EQ(instructions.at(0).operands.size(), 2); + EXPECT_EQ(instructions.at(0).operands.at(0), 47123); + EXPECT_EQ(instructions.at(0).operands.at(1), 170); + EXPECT_EQ(instructions.at(0).in_tag, AvmMemoryTag::U16); + + // SET + EXPECT_EQ(instructions.at(1).op_code, OpCode::SET); + EXPECT_EQ(instructions.at(1).operands.size(), 2); + EXPECT_EQ(instructions.at(1).operands.at(0), 37123); + EXPECT_EQ(instructions.at(1).operands.at(1), 51); + EXPECT_EQ(instructions.at(1).in_tag, AvmMemoryTag::U16); + + // SUB + EXPECT_EQ(instructions.at(2).op_code, OpCode::SUB); + EXPECT_EQ(instructions.at(2).operands.size(), 3); + EXPECT_EQ(instructions.at(2).operands.at(0), 170); + EXPECT_EQ(instructions.at(2).operands.at(1), 51); + EXPECT_EQ(instructions.at(2).in_tag, AvmMemoryTag::U16); + + auto trace = Execution::gen_trace(instructions, std::vector{}); + + // Find the first row enabling the subtraction selector + auto row = std::ranges::find_if(trace.begin(), trace.end(), [](Row r) { return r.avmMini_sel_op_sub == 1; }); + EXPECT_EQ(row->avmMini_ic, 10000); // 47123 - 37123 = 10000 + + auto trace_verif = trace; + validate_trace_proof(std::move(trace)); + + auto circuit_builder = AvmMiniCircuitBuilder(); + circuit_builder.set_trace(std::move(trace_verif)); + auto composer = honk::AvmMiniComposer(); + auto verifier = composer.create_verifier(circuit_builder); + + auto proof = Execution::run_and_prove(bytecode, std::vector{}); + + EXPECT_TRUE(verifier.verify_proof(proof)); +} + +// Positive test for multiple MUL opcodes +// We compute 5^12 based on U64 multiplications +// 5 is stored at offset 0 and 1 at offset 1 +// Repeat 12 times a multiplication of value +// at offset 0 (5) with value at offset 1 and store +// the result at offset 1. +TEST_F(AvmMiniExecutionTests, powerWithMulOpcodes) +{ + std::string bytecode_hex = "27" // SET 39 = 0x27 + "04" // U64 + "00000000" // val 5 higher 32 bits + "00000005" // val 5 lower 32 bits + "00000000" // dst_offset 0 + "27" // SET 39 = 0x27 + "04" // U64 + "00000000" // val 1 higher 32 bits + "00000001" // val 1 lower 32 bits + "00000001"; // dst_offset 1 + + std::string const mul_hex = "02" // MUL + "04" // U64 + "00000000" // addr a + "00000001" // addr b + "00000001"; // addr c 1 + + std::string const ret_hex = "34" // RETURN + "00000000" // ret offset 0 + "00000000"; // ret size 0 + + uint8_t num = 12; + while (num-- > 0) { + bytecode_hex.append(mul_hex); + } + + bytecode_hex.append(ret_hex); + + auto bytecode = hex_to_bytes(bytecode_hex); + auto instructions = Execution::parse(bytecode); + + EXPECT_EQ(instructions.size(), 15); + + // MUL first pos + EXPECT_EQ(instructions.at(2).op_code, OpCode::MUL); + EXPECT_EQ(instructions.at(2).operands.size(), 3); + EXPECT_EQ(instructions.at(2).operands.at(0), 0); + EXPECT_EQ(instructions.at(2).operands.at(1), 1); + EXPECT_EQ(instructions.at(2).operands.at(2), 1); + EXPECT_EQ(instructions.at(2).in_tag, AvmMemoryTag::U64); + + // MUL last pos + EXPECT_EQ(instructions.at(13).op_code, OpCode::MUL); + EXPECT_EQ(instructions.at(13).operands.size(), 3); + EXPECT_EQ(instructions.at(13).operands.at(0), 0); + EXPECT_EQ(instructions.at(13).operands.at(1), 1); + EXPECT_EQ(instructions.at(13).operands.at(2), 1); + EXPECT_EQ(instructions.at(13).in_tag, AvmMemoryTag::U64); + + // RETURN + EXPECT_EQ(instructions.at(14).op_code, OpCode::RETURN); + EXPECT_EQ(instructions.at(14).operands.size(), 2); + EXPECT_EQ(instructions.at(14).operands.at(0), 0); + EXPECT_EQ(instructions.at(14).operands.at(0), 0); + + auto trace = Execution::gen_trace(instructions, std::vector{}); + + // Find the first row enabling the subtraction selector + auto row = std::ranges::find_if( + trace.begin(), trace.end(), [](Row r) { return r.avmMini_sel_op_mul == 1 && r.avmMini_pc == 13; }); + EXPECT_EQ(row->avmMini_ic, 244140625); // 5^12 = 244140625 + + auto trace_verif = trace; + validate_trace_proof(std::move(trace)); + + auto circuit_builder = AvmMiniCircuitBuilder(); + circuit_builder.set_trace(std::move(trace_verif)); + auto composer = honk::AvmMiniComposer(); + auto verifier = composer.create_verifier(circuit_builder); + + auto proof = Execution::run_and_prove(bytecode, std::vector{}); + + EXPECT_TRUE(verifier.verify_proof(proof)); +} + +// Negative test detecting an invalid opcode byte. +TEST_F(AvmMiniExecutionTests, invalidOpcode) +{ + std::string bytecode_hex = "00" // ADD + "02" // U16 + "00000007" // addr a 7 + "00000009" // addr b 9 + "00000001" // addr c 1 + "AB" // Invalid opcode byte + "00000000" // ret offset 0 + "00000000"; // ret size 0 + + auto bytecode = hex_to_bytes(bytecode_hex); + EXPECT_THROW_WITH_MESSAGE(Execution::parse(bytecode), "opcode"); +} + +// Negative test detecting an invalid memmory instruction tag. +TEST_F(AvmMiniExecutionTests, invalidInstructionTag) +{ + std::string bytecode_hex = "00" // ADD + "00" // Wrong type + "00000007" // addr a 7 + "00000009" // addr b 9 + "00000001" // addr c 1 + "34" // RETURN + "00000000" // ret offset 0 + "00000000"; // ret size 0 + + auto bytecode = hex_to_bytes(bytecode_hex); + EXPECT_THROW_WITH_MESSAGE(Execution::parse(bytecode), "Instruction tag is invalid"); +} + +// Negative test detecting SET opcode with instruction memory tag set to FF. +TEST_F(AvmMiniExecutionTests, ffInstructionTagSetOpcode) +{ + std::string bytecode_hex = "00" // ADD + "05" // U128 + "00000007" // addr a 7 + "00000009" // addr b 9 + "00000001" // addr c 1 + "27" // SET 39 = 0x27 + "06" // tag FF + "00002344"; // + + auto bytecode = hex_to_bytes(bytecode_hex); + EXPECT_THROW_WITH_MESSAGE(Execution::parse(bytecode), "Instruction tag for SET opcode is invalid"); +} + +// Negative test detecting an incomplete instruction: missing instruction tag +TEST_F(AvmMiniExecutionTests, truncatedInstructionNoTag) +{ + std::string bytecode_hex = "00" // ADD + "02" // U16 + "00000007" // addr a 7 + "00000009" // addr b 9 + "00000001" // addr c 1 + "01"; // SUB + + auto bytecode = hex_to_bytes(bytecode_hex); + EXPECT_THROW_WITH_MESSAGE(Execution::parse(bytecode), "Instruction tag missing"); +} + +// Negative test detecting an incomplete instruction: instruction tag present but an operand is missing +TEST_F(AvmMiniExecutionTests, truncatedInstructionNoOperand) +{ + std::string bytecode_hex = "00" // ADD + "02" // U16 + "00000007" // addr a 7 + "00000009" // addr b 9 + "00000001" // addr c 1 + "01" // SUB + "04" // U64 + "AB2373E7" // addr a + "FFFFFFBB"; // addr b and missing address for c = a-b + + auto bytecode = hex_to_bytes(bytecode_hex); + EXPECT_THROW_WITH_MESSAGE(Execution::parse(bytecode), "Operand is missing"); +} + +} // namespace tests_avm \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_memory.test.cpp b/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_memory.test.cpp index a5ccfd1076a7..79088fb0304a 100644 --- a/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_memory.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/tests/AvmMini_memory.test.cpp @@ -1,7 +1,7 @@ #include "AvmMini_common.test.hpp" - -using namespace tests_avm; +namespace tests_avm { using namespace avm_trace; + class AvmMiniMemoryTests : public ::testing::Test { public: AvmMiniTraceBuilder trace_builder; @@ -236,3 +236,4 @@ TEST_F(AvmMiniMemoryTests, consistentTagNoErrorViolation) EXPECT_THROW_WITH_MESSAGE(validate_trace_proof(std::move(trace)), "MEM_IN_TAG_CONSISTENCY_1"); } +} // namespace tests_avm \ No newline at end of file