From e392ef524cc154a8ea6e80a6b8eea99b32a5c1f3 Mon Sep 17 00:00:00 2001 From: jeanmon Date: Tue, 23 Jan 2024 10:45:58 +0000 Subject: [PATCH] 3791 - bytecode parsing and proof generation --- .../vm/avm_trace/AvmMini_execution.cpp | 225 ++++++++++++++++++ .../vm/avm_trace/AvmMini_execution.hpp | 27 +++ .../vm/avm_trace/AvmMini_instructions.hpp | 23 ++ .../vm/avm_trace/AvmMini_opcode.cpp | 153 ++++++++++++ .../vm/avm_trace/AvmMini_opcode.hpp | 104 ++++++++ .../vm/avm_trace/AvmMini_trace.hpp | 1 + .../src/avm/opcodes/instruction.ts | 4 +- .../acir-simulator/src/avm/opcodes/opcodes.ts | 2 +- 8 files changed, 536 insertions(+), 3 deletions(-) create mode 100644 barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.cpp create mode 100644 barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.hpp create mode 100644 barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_instructions.hpp create mode 100644 barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.cpp create mode 100644 barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.hpp diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.cpp new file mode 100644 index 000000000000..417b74e52572 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.cpp @@ -0,0 +1,225 @@ +#include "AvmMini_execution.hpp" +#include "barretenberg/common/serialize.hpp" +#include "barretenberg/proof_system/circuit_builder/generated/AvmMini_circuit_builder.hpp" +#include "barretenberg/vm/avm_trace/AvmMini_common.hpp" +#include "barretenberg/vm/avm_trace/AvmMini_instructions.hpp" +#include "barretenberg/vm/avm_trace/AvmMini_opcode.hpp" +#include "barretenberg/vm/avm_trace/AvmMini_trace.hpp" +#include "barretenberg/vm/generated/AvmMini_composer.hpp" +#include +#include +#include +#include + +namespace avm_trace { + +/** + * @brief Run the bytecode, generate the corresponding execution trace and prove the correctness + * of the execution of the supplied bytecode. + * + * @param bytecode A vector of bytes representing the bytecode to execute. + * @param calldata expressed as a vector of finite field elements. + * @throws runtime_error exception when the bytecode is invalid. + * @return A zk proof of the execution. + */ +plonk::proof Execution::run_and_prove(std::vector const& bytecode, std::vector const& calldata) +{ + auto instructions = parse(bytecode); + auto trace = gen_trace(instructions, calldata); + auto circuit_builder = bb::AvmMiniCircuitBuilder(); + circuit_builder.set_trace(std::move(trace)); + + auto composer = bb::honk::AvmMiniComposer(); + auto prover = composer.create_prover(circuit_builder); + return prover.construct_proof(); +} + +/** + * @brief Parsing of the supplied bytecode into a vector of instructions. It essentially + * checks that each opcode value is in the defined range and extracts the operands + * for each opcode. + * + * @param bytecode The bytecode to be parsed as a vector of bytes/uint8_t + * @throws runtime_error exception when the bytecode is invalid. + * @return Vector of instructions + */ +std::vector Execution::parse(std::vector const& bytecode) +{ + std::vector instructions; + size_t pos = 0; + const auto length = bytecode.size(); + + static_assert(sizeof(uint32_t) / sizeof(uint8_t) == AVM_OPERAND_BYTE_LENGTH); + + while (pos < length) { + const uint8_t opcode_byte = bytecode.at(pos); + pos += AVM_OPCODE_BYTE_LENGTH; + + if (!Bytecode::is_valid(opcode_byte)) { + throw std::runtime_error("Invalid opcode byte: " + std::to_string(opcode_byte)); + } + + const auto opcode = static_cast(opcode_byte); + auto in_tag_u8 = static_cast(AvmMemoryTag::U0); + + if (Bytecode::has_in_tag(opcode)) { + in_tag_u8 = bytecode.at(pos); + if (in_tag_u8 == static_cast(AvmMemoryTag::U0) || in_tag_u8 == 7) { + throw std::runtime_error("Instruction tag is invalid at position " + std::to_string(pos) + + " value: " + std::to_string(in_tag_u8)); + } + pos += AVM_IN_TAG_BYTE_LENGTH; + } + + auto const in_tag = static_cast(in_tag_u8); + std::vector operands{}; + size_t num_of_operands{}; + size_t operands_size{}; + + if (opcode == OpCode::SET) { + switch (in_tag) { + case AvmMemoryTag::U8: + num_of_operands = 2; + operands_size = 5; + break; + case AvmMemoryTag::U16: + num_of_operands = 2; + operands_size = 6; + break; + case AvmMemoryTag::U32: + num_of_operands = 2; + operands_size = 8; + break; + case AvmMemoryTag::U64: + num_of_operands = 3; + operands_size = 12; + break; + case AvmMemoryTag::U128: + num_of_operands = 5; + operands_size = 20; + break; + default: + throw std::runtime_error("Instruction tag for SET opcode is invalid at position " + + std::to_string(pos) + " value: " + std::to_string(in_tag_u8)); + break; + } + } else { + num_of_operands = Bytecode::OPERANDS_NUM.at(opcode); + operands_size = AVM_OPERAND_BYTE_LENGTH * num_of_operands; + } + + if (pos + operands_size > length) { + throw std::runtime_error("Bytecode does not contain enough bytes for operands at position " + + std::to_string(pos)); + } + + if (opcode == OpCode::SET && in_tag == AvmMemoryTag::U8) { + operands.push_back(static_cast(bytecode.at(pos))); + uint8_t const* ptr = &bytecode.at(pos + 1); + uint32_t operand{}; + serialize::read(ptr, operand); + operands.push_back(operand); + pos += operands_size; + } else if (opcode == OpCode::SET && in_tag == AvmMemoryTag::U16) { + uint8_t const* ptr = &bytecode.at(pos); + uint16_t operand{}; + serialize::read(ptr, operand); + operands.push_back(static_cast(operand)); + ptr = &bytecode.at(pos + 2); + uint32_t operand2{}; + serialize::read(ptr, operand2); + operands.push_back(operand2); + pos += operands_size; + } else { + for (size_t i = 0; i < num_of_operands; i++) { + uint8_t const* ptr = &bytecode.at(pos); + uint32_t operand{}; + serialize::read(ptr, operand); + operands.push_back(operand); + pos += AVM_OPERAND_BYTE_LENGTH; + } + } + + instructions.emplace_back(opcode, operands, static_cast(in_tag)); + } + + return instructions; +} + +/** + * @brief Generate the execution trace pertaining to the supplied instructions. + * + * @param instructions A vector of the instructions to be executed. + * @param calldata expressed as a vector of finite field elements. + * @return The trace as a vector of Row. + */ +std::vector Execution::gen_trace(std::vector const& instructions, std::vector const& calldata) +{ + AvmMiniTraceBuilder trace_builder{}; + + for (auto const& inst : instructions) { + switch (inst.op_code) { + case OpCode::ADD: + trace_builder.add(inst.operands.at(0), inst.operands.at(1), inst.operands.at(2), inst.in_tag); + break; + case OpCode::SUB: + trace_builder.sub(inst.operands.at(0), inst.operands.at(1), inst.operands.at(2), inst.in_tag); + break; + case OpCode::MUL: + trace_builder.mul(inst.operands.at(0), inst.operands.at(1), inst.operands.at(2), inst.in_tag); + break; + case OpCode::DIV: + trace_builder.div(inst.operands.at(0), inst.operands.at(1), inst.operands.at(2), inst.in_tag); + break; + case OpCode::CALLDATACOPY: + trace_builder.call_data_copy(inst.operands.at(0), inst.operands.at(1), inst.operands.at(2), calldata); + break; + case OpCode::JUMP: + trace_builder.jump(inst.operands.at(0)); + break; + case OpCode::INTERNALCALL: + trace_builder.internal_call(inst.operands.at(0)); + break; + case OpCode::INTERNALRETURN: + trace_builder.internal_return(); + break; + case OpCode::SET: { + uint32_t dst_offset{}; + uint128_t val{}; + switch (inst.in_tag) { + case AvmMemoryTag::U8: + case AvmMemoryTag::U16: + case AvmMemoryTag::U32: + val = inst.operands.at(0); + dst_offset = inst.operands.at(1); + break; + case AvmMemoryTag::U64: + val = inst.operands.at(0); + val <<= 32; + val += inst.operands.at(1); + dst_offset = inst.operands.at(2); + break; + case AvmMemoryTag::U128: + for (size_t i = 0; i < 4; i++) { + val += inst.operands.at(i); + val <<= 32; + } + dst_offset = inst.operands.at(4); + break; + default: + break; + } + trace_builder.set(val, dst_offset, inst.in_tag); + break; + } + case OpCode::RETURN: + trace_builder.return_op(inst.operands.at(0), inst.operands.at(1)); + break; + default: + break; + } + } + return trace_builder.finalize(); +} + +} // namespace avm_trace \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.hpp new file mode 100644 index 000000000000..f6610ce9f2cb --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_execution.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "barretenberg/plonk/proof_system/types/proof.hpp" +#include "barretenberg/vm/avm_trace/AvmMini_common.hpp" +#include "barretenberg/vm/avm_trace/AvmMini_instructions.hpp" +#include "barretenberg/vm/avm_trace/AvmMini_trace.hpp" +#include +#include +#include + +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); +}; + +} // namespace avm_trace \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_instructions.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_instructions.hpp new file mode 100644 index 000000000000..0cc18e560875 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_instructions.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "barretenberg/vm/avm_trace/AvmMini_common.hpp" +#include "barretenberg/vm/avm_trace/AvmMini_opcode.hpp" +#include +#include + +namespace avm_trace { + +class Instruction { + public: + OpCode op_code; + std::vector operands; + AvmMemoryTag in_tag; + + Instruction() = delete; + explicit Instruction(OpCode op_code, std::vector operands, AvmMemoryTag in_tag) + : op_code(op_code) + , operands(std::move(operands)) + , in_tag(in_tag){}; +}; + +} // namespace avm_trace \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.cpp new file mode 100644 index 000000000000..ec3d7568f095 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.cpp @@ -0,0 +1,153 @@ +#include "AvmMini_opcode.hpp" +#include + +namespace avm_trace { + +const std::unordered_map Bytecode::OPERANDS_NUM = { + // Compute + // Compute - Arithmetic + { OpCode::ADD, 3 }, + { OpCode::SUB, 3 }, + { OpCode::MUL, 3 }, + { OpCode::DIV, 3 }, + //// Compute - Comparators + //{OpCode::EQ, }, + //{OpCode::LT, }, + //{OpCode::LTE, }, + //// Compute - Bitwise + //{OpCode::AND, }, + //{OpCode::OR, }, + //{OpCode::XOR, }, + //{OpCode::NOT, }, + //{OpCode::SHL, }, + //{OpCode::SHR, }, + //// Compute - Type Conversions + //{OpCode::CAST, }, + + //// Execution Environment + //{OpCode::ADDRESS, }, + //{OpCode::STORAGEADDRESS, }, + //{OpCode::ORIGIN, }, + //{OpCode::SENDER, }, + //{OpCode::PORTAL, }, + //{OpCode::FEEPERL1GAS, }, + //{OpCode::FEEPERL2GAS, }, + //{OpCode::FEEPERDAGAS, }, + //{OpCode::CONTRACTCALLDEPTH, }, + //// Execution Environment - Globals + //{OpCode::CHAINID, }, + //{OpCode::VERSION, }, + //{OpCode::BLOCKNUMBER, }, + //{OpCode::TIMESTAMP, }, + //{OpCode::COINBASE, }, + //{OpCode::BLOCKL1GASLIMIT, }, + //{OpCode::BLOCKL2GASLIMIT, }, + //{OpCode::BLOCKDAGASLIMIT, }, + // Execution Environment - Calldata + { OpCode::CALLDATACOPY, 3 }, + + //// Machine State + // Machine State - Gas + //{ OpCode::L1GASLEFT, }, + //{ OpCode::L2GASLEFT, }, + //{ OpCode::DAGASLEFT, }, + //// Machine State - Internal Control Flow + { OpCode::JUMP, 1 }, + { OpCode::JUMPI, 1 }, + { OpCode::INTERNALCALL, 1 }, + { OpCode::INTERNALRETURN, 0 }, + + //// Machine State - Memory + { OpCode::SET, 5 }, + //{ OpCode::MOV, }, + //{ OpCode::CMOV, }, + + //// World State + //{ OpCode::BLOCKHEADERBYNUMBER, }, + //{ OpCode::SLOAD, }, // Public Storage + //{ OpCode::SSTORE, }, // Public Storage + //{ OpCode::READL1TOL2MSG, }, // Messages + //{ OpCode::SENDL2TOL1MSG, }, // Messages + //{ OpCode::EMITNOTEHASH, }, // Notes & Nullifiers + //{ OpCode::EMITNULLIFIER, }, // Notes & Nullifiers + + //// Accrued Substate + //{ OpCode::EMITUNENCRYPTEDLOG, }, + + //// Control Flow - Contract Calls + //{ OpCode::CALL, }, + //{ OpCode::STATICCALL, }, + { OpCode::RETURN, 2 }, + // { OpCode::REVERT, }, + + //// Gadgets + //{ OpCode::KECCAK, }, + //{ OpCode::POSEIDON, }, +}; + +/** + * @brief Test whether a given byte reprents a valid opcode. + * + * @param byte The input byte. + * @return A boolean telling whether a corresponding opcode does match the input byte. + */ +bool Bytecode::is_valid(const uint8_t byte) +{ + return byte <= static_cast(OpCode::POSEIDON); +} + +/** + * @brief A function returning whether a supplied opcode has an instruction tag as argument. + * + * @param op_code The opcode + * @return A boolean set to true if the corresponding instruction needs a tag as argument. + */ +bool Bytecode::has_in_tag(OpCode const op_code) +{ + switch (op_code) { + case OpCode::ADDRESS: + case OpCode::STORAGEADDRESS: + case OpCode::ORIGIN: + case OpCode::SENDER: + case OpCode::PORTAL: + case OpCode::FEEPERL1GAS: + case OpCode::FEEPERL2GAS: + case OpCode::FEEPERDAGAS: + case OpCode::CONTRACTCALLDEPTH: + case OpCode::CHAINID: + case OpCode::VERSION: + case OpCode::BLOCKNUMBER: + case OpCode::TIMESTAMP: + case OpCode::COINBASE: + case OpCode::BLOCKL1GASLIMIT: + case OpCode::BLOCKL2GASLIMIT: + case OpCode::BLOCKDAGASLIMIT: + case OpCode::CALLDATACOPY: + case OpCode::L1GASLEFT: + case OpCode::L2GASLEFT: + case OpCode::DAGASLEFT: + case OpCode::JUMP: + case OpCode::JUMPI: + case OpCode::INTERNALCALL: + case OpCode::INTERNALRETURN: + case OpCode::MOV: + case OpCode::CMOV: + case OpCode::BLOCKHEADERBYNUMBER: + case OpCode::SLOAD: + case OpCode::SSTORE: + case OpCode::READL1TOL2MSG: + case OpCode::SENDL2TOL1MSG: + case OpCode::EMITNOTEHASH: + case OpCode::EMITNULLIFIER: + case OpCode::EMITUNENCRYPTEDLOG: + case OpCode::CALL: + case OpCode::STATICCALL: + case OpCode::RETURN: + case OpCode::REVERT: + return false; + default: + return true; + } +} + +} // namespace avm_trace \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.hpp new file mode 100644 index 000000000000..431682faf909 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_opcode.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include +#include + +namespace avm_trace { + +/** + * All AVM opcodes (Keep in sync with TS counterpart code opcodes.ts) + * TODO: Once opcode values are definitive, we should assign them explicitly in the enum below + * and typescript code. This would increase robustness against unintended modifications. + * i.e.: ADD = 0, SUB = 1, etc, .... + * CAUTION: Any change in the list below needs to be carefully followed by + * a potential adaptation of Bytecode::is_valid method. + */ +enum class OpCode : uint8_t { + // Compute + // Compute - Arithmetic + ADD, + SUB, + MUL, + DIV, + // Compute - Comparators + EQ, + LT, + LTE, + // Compute - Bitwise + AND, + OR, + XOR, + NOT, + SHL, + SHR, + // Compute - Type Conversions + CAST, + + // Execution Environment + ADDRESS, + STORAGEADDRESS, + ORIGIN, + SENDER, + PORTAL, + FEEPERL1GAS, + FEEPERL2GAS, + FEEPERDAGAS, + CONTRACTCALLDEPTH, + // Execution Environment - Globals + CHAINID, + VERSION, + BLOCKNUMBER, + TIMESTAMP, + COINBASE, + BLOCKL1GASLIMIT, + BLOCKL2GASLIMIT, + BLOCKDAGASLIMIT, + // Execution Environment - Calldata + CALLDATACOPY, + + // Machine State + // Machine State - Gas + L1GASLEFT, + L2GASLEFT, + DAGASLEFT, + // Machine State - Internal Control Flow + JUMP, + JUMPI, + INTERNALCALL, + INTERNALRETURN, + // Machine State - Memory + SET, + MOV, + CMOV, + + // World State + BLOCKHEADERBYNUMBER, + SLOAD, // Public Storage + SSTORE, // Public Storage + READL1TOL2MSG, // Messages + SENDL2TOL1MSG, // Messages + EMITNOTEHASH, // Notes & Nullifiers + EMITNULLIFIER, // Notes & Nullifiers + + // Accrued Substate + EMITUNENCRYPTEDLOG, + + // Control Flow - Contract Calls + CALL, + STATICCALL, + RETURN, + REVERT, + + // Gadgets + KECCAK, + POSEIDON, +}; + +class Bytecode { + public: + static bool is_valid(uint8_t byte); + static bool has_in_tag(OpCode); + static const std::unordered_map OPERANDS_NUM; +}; + +} // namespace avm_trace \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_trace.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_trace.hpp index af09f2e4d142..e9e7b35dd1f3 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_trace.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/AvmMini_trace.hpp @@ -4,6 +4,7 @@ #include "AvmMini_alu_trace.hpp" #include "AvmMini_common.hpp" +#include "AvmMini_instructions.hpp" #include "AvmMini_mem_trace.hpp" #include "barretenberg/common/throw_or_abort.hpp" diff --git a/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts index c77a1a9ddd38..05f45f711268 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/instruction.ts @@ -1,8 +1,8 @@ import { AvmMachineState } from '../avm_machine_state.js'; import { AvmStateManager } from '../avm_state_manager.js'; -export const AVM_OPERAND_BYTE_LENGTH = 4; -export const AVM_OPCODE_BYTE_LENGTH = 1; +export const AVM_OPERAND_BYTE_LENGTH = 4; // Keep in sync with cpp code +export const AVM_OPCODE_BYTE_LENGTH = 1; // Keep in sync with cpp code /** * Opcode base class diff --git a/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts b/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts index 6f3f37090078..97bdfa3181d8 100644 --- a/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts +++ b/yarn-project/acir-simulator/src/avm/opcodes/opcodes.ts @@ -1,5 +1,5 @@ /** - * All AVM opcodes. + * All AVM opcodes. (Keep in sync with cpp counterpart code AvmMini_opcode.hpp). * Source: https://yp-aztec.netlify.app/docs/public-vm/instruction-set */ export enum Opcode {