From 763e6bf6799308faca8134d853e0fb7dd9e91abb Mon Sep 17 00:00:00 2001 From: IlyasRidhuan Date: Tue, 4 Jun 2024 09:29:38 +0000 Subject: [PATCH] feat(avm): ecc --- avm-transpiler/src/transpile.rs | 28 ++--- .../vm/avm_trace/avm_deserialization.cpp | 10 ++ .../vm/avm_trace/avm_execution.cpp | 10 ++ .../vm/avm_trace/avm_gas_trace.hpp | 3 +- .../barretenberg/vm/avm_trace/avm_opcode.hpp | 1 + .../barretenberg/vm/avm_trace/avm_trace.cpp | 107 ++++++++++++++++++ .../barretenberg/vm/avm_trace/avm_trace.hpp | 11 ++ .../vm/avm_trace/gadgets/avm_ecc.cpp | 33 ++++++ .../vm/avm_trace/gadgets/avm_ecc.hpp | 29 +++++ .../vm/tests/avm_execution.test.cpp | 62 ++++++++++ .../simulator/src/avm/avm_simulator.test.ts | 3 +- .../simulator/src/avm/opcodes/ec_add.test.ts | 16 +-- .../simulator/src/avm/opcodes/ec_add.ts | 74 ++++++------ 13 files changed, 330 insertions(+), 57 deletions(-) create mode 100644 barretenberg/cpp/src/barretenberg/vm/avm_trace/gadgets/avm_ecc.cpp create mode 100644 barretenberg/cpp/src/barretenberg/vm/avm_trace/gadgets/avm_ecc.hpp diff --git a/avm-transpiler/src/transpile.rs b/avm-transpiler/src/transpile.rs index 057f1866595..67d0f8043a6 100644 --- a/avm-transpiler/src/transpile.rs +++ b/avm-transpiler/src/transpile.rs @@ -831,24 +831,26 @@ fn handle_black_box_function(avm_instrs: &mut Vec, operation: &B ], }); } + // This will be changed to utilise relative memory offsets BlackBoxOp::EmbeddedCurveAdd { - input1_x, - input1_y, - input1_infinite, - input2_x, - input2_y, - input2_infinite, + input1_x: p1_x_offset, + input1_y: p1_y_offset, + input1_infinite: p1_infinite_offset, + input2_x: p2_x_offset, + input2_y: p2_y_offset, + input2_infinite: p2_infinite_offset, result, } => avm_instrs.push(AvmInstruction { opcode: AvmOpcode::ECADD, - indirect: Some(ALL_DIRECT), + // The result (SIXTH operand) is indirect. + indirect: Some(0b1000000), operands: vec![ - AvmOperand::U32 { value: input1_x.0 as u32 }, - AvmOperand::U32 { value: input1_y.0 as u32 }, - AvmOperand::U8 { value: input1_infinite.0 as u8 }, - AvmOperand::U32 { value: input2_x.0 as u32 }, - AvmOperand::U32 { value: input2_y.0 as u32 }, - AvmOperand::U8 { value: input2_infinite.0 as u8 }, + AvmOperand::U32 { value: p1_x_offset.0 as u32 }, + AvmOperand::U32 { value: p1_y_offset.0 as u32 }, + AvmOperand::U32 { value: p1_infinite_offset.0 as u32 }, + AvmOperand::U32 { value: p2_x_offset.0 as u32 }, + AvmOperand::U32 { value: p2_y_offset.0 as u32 }, + AvmOperand::U32 { value: p2_infinite_offset.0 as u32 }, AvmOperand::U32 { value: result.pointer.0 as u32 }, ], ..Default::default() diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_deserialization.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_deserialization.cpp index 358eba7e3c8..6b10ab40afc 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_deserialization.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_deserialization.cpp @@ -148,6 +148,16 @@ const std::unordered_map> OPCODE_WIRE_FORMAT = { OpCode::SHA256, { OperandType::INDIRECT, OperandType::UINT32, OperandType::UINT32, OperandType::UINT32 } }, { OpCode::PEDERSEN, { OperandType::INDIRECT, OperandType::UINT32, OperandType::UINT32, OperandType::UINT32, OperandType::UINT32 } }, + // TEMP ECADD without relative memory + { OpCode::ECADD, + { OperandType::INDIRECT, + OperandType::UINT32, // lhs.x + OperandType::UINT32, // lhs.y + OperandType::UINT32, // lhs.is_infinite + OperandType::UINT32, // rhs.x + OperandType::UINT32, // rhs.y + OperandType::UINT32, // rhs.is_infinite + OperandType::UINT32 } }, // dst_offset // Gadget - Conversion { OpCode::TORADIXLE, { OperandType::INDIRECT, OperandType::UINT32, OperandType::UINT32, OperandType::UINT32, OperandType::UINT32 } }, diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_execution.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_execution.cpp index 27dc5548b79..46f037e42ea 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_execution.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_execution.cpp @@ -664,6 +664,16 @@ std::vector Execution::gen_trace(std::vector const& instructio std::get(inst.operands.at(3)), std::get(inst.operands.at(4))); break; + case OpCode::ECADD: + trace_builder.op_ec_add(std::get(inst.operands.at(0)), + std::get(inst.operands.at(1)), + std::get(inst.operands.at(2)), + std::get(inst.operands.at(3)), + std::get(inst.operands.at(4)), + std::get(inst.operands.at(5)), + std::get(inst.operands.at(6)), + std::get(inst.operands.at(7))); + break; case OpCode::REVERT: trace_builder.op_revert(std::get(inst.operands.at(0)), std::get(inst.operands.at(1)), diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_gas_trace.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_gas_trace.hpp index cc8e53aeb36..9934164ad23 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_gas_trace.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_gas_trace.hpp @@ -98,6 +98,7 @@ static const inline std::unordered_map GAS_COST_TABLE = { { OpCode::POSEIDON2, temp_default_gas_entry }, { OpCode::SHA256, temp_default_gas_entry }, { OpCode::PEDERSEN, temp_default_gas_entry }, + { OpCode::ECADD, temp_default_gas_entry }, // Conversions { OpCode::TORADIXLE, temp_default_gas_entry }, @@ -146,4 +147,4 @@ class AvmGasTraceBuilder { uint32_t remaining_da_gas = 0; }; -} // namespace bb::avm_trace \ No newline at end of file +} // namespace bb::avm_trace diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp index 87e0f03fe7b..83de32e6568 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp @@ -104,6 +104,7 @@ enum class OpCode : uint8_t { POSEIDON2, SHA256, PEDERSEN, + ECADD, // Conversions TORADIXLE, // Future Gadgets -- pending changes in noir diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp index a45bde206d7..fb9e8b7e576 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp @@ -3520,6 +3520,113 @@ void AvmTraceBuilder::op_pedersen_hash(uint8_t indirect, write_slice_to_memory( call_ptr, clk, output_offset, AvmMemoryTag::FF, AvmMemoryTag::FF, FF(internal_return_ptr), { output }); } + +void AvmTraceBuilder::op_ec_add(uint8_t indirect, + uint32_t lhs_x_offset, + uint32_t lhs_y_offset, + uint32_t lhs_is_inf_offset, + uint32_t rhs_x_offset, + uint32_t rhs_y_offset, + uint32_t rhs_is_inf_offset, + uint32_t output_offset) +{ + auto clk = static_cast(main_trace.size()) + 1; + // Load lhs point + auto lhs_x_read = mem_trace_builder.read_and_load_from_memory( + call_ptr, clk, IntermRegister::IA, lhs_x_offset, AvmMemoryTag::FF, AvmMemoryTag::U0); + auto lhs_y_read = mem_trace_builder.read_and_load_from_memory( + call_ptr, clk, IntermRegister::IB, lhs_y_offset, AvmMemoryTag::FF, AvmMemoryTag::U0); + // Load rhs point + auto rhs_x_read = mem_trace_builder.read_and_load_from_memory( + call_ptr, clk, IntermRegister::IC, rhs_x_offset, AvmMemoryTag::FF, AvmMemoryTag::U0); + auto rhs_y_read = mem_trace_builder.read_and_load_from_memory( + call_ptr, clk, IntermRegister::ID, rhs_y_offset, AvmMemoryTag::FF, AvmMemoryTag::U0); + + // Save this clk time to line up with the gadget op. + auto ecc_clk = clk; + main_trace.push_back(Row{ + .avm_main_clk = clk, + .avm_main_ia = lhs_x_read.val, + .avm_main_ib = lhs_y_read.val, + .avm_main_ic = rhs_x_read.val, + .avm_main_id = rhs_y_read.val, + .avm_main_internal_return_ptr = FF(internal_return_ptr), + .avm_main_mem_idx_a = FF(lhs_x_offset), + .avm_main_mem_idx_b = FF(lhs_y_offset), + .avm_main_mem_idx_c = FF(rhs_x_offset), + .avm_main_mem_idx_d = FF(rhs_y_offset), + .avm_main_mem_op_a = FF(1), + .avm_main_mem_op_b = FF(1), + .avm_main_mem_op_c = FF(1), + .avm_main_mem_op_d = FF(1), + .avm_main_pc = FF(pc++), + .avm_main_r_in_tag = FF(static_cast(AvmMemoryTag::FF)), + }); + clk++; + // Load the infinite bools separately since they have a different memory tag + auto lhs_is_inf_read = mem_trace_builder.read_and_load_from_memory( + call_ptr, clk, IntermRegister::IA, lhs_is_inf_offset, AvmMemoryTag::U8, AvmMemoryTag::U0); + auto rhs_is_inf_read = mem_trace_builder.read_and_load_from_memory( + call_ptr, clk, IntermRegister::IB, rhs_is_inf_offset, AvmMemoryTag::U8, AvmMemoryTag::U0); + + main_trace.push_back(Row{ + .avm_main_clk = clk, + .avm_main_ia = lhs_is_inf_read.val, + .avm_main_ib = rhs_is_inf_read.val, + .avm_main_internal_return_ptr = FF(internal_return_ptr), + .avm_main_mem_idx_a = FF(lhs_is_inf_offset), + .avm_main_mem_idx_b = FF(rhs_is_inf_offset), + .avm_main_mem_op_a = FF(1), + .avm_main_mem_op_b = FF(1), + .avm_main_pc = FF(pc), + .avm_main_r_in_tag = FF(static_cast(AvmMemoryTag::U8)), + }); + clk++; + grumpkin::g1::affine_element lhs = uint8_t(lhs_is_inf_read.val) == 1 + ? grumpkin::g1::affine_element::infinity() + : grumpkin::g1::affine_element{ lhs_x_read.val, lhs_y_read.val }; + grumpkin::g1::affine_element rhs = uint8_t(rhs_is_inf_read.val) == 1 + ? grumpkin::g1::affine_element::infinity() + : grumpkin::g1::affine_element{ rhs_x_read.val, rhs_y_read.val }; + auto result = ecc_trace_builder.embedded_curve_add(lhs, rhs, ecc_clk); + // Write across two lines since we have different mem_tags + uint32_t direct_output_offset = output_offset; + bool indirect_flag_output = is_operand_indirect(indirect, 6); + if (indirect_flag_output) { + auto read_ind_output = + mem_trace_builder.indirect_read_and_load_from_memory(call_ptr, clk, IndirectRegister::IND_A, output_offset); + direct_output_offset = uint32_t(read_ind_output.val); + } + + mem_trace_builder.write_into_memory( + call_ptr, clk, IntermRegister::IA, direct_output_offset, result.x, AvmMemoryTag::U0, AvmMemoryTag::FF); + mem_trace_builder.write_into_memory( + call_ptr, clk, IntermRegister::IB, direct_output_offset + 1, result.y, AvmMemoryTag::U0, AvmMemoryTag::FF); + main_trace.push_back(Row{ + .avm_main_clk = clk, + .avm_main_ia = result.x, + .avm_main_ib = result.y, + .avm_main_ind_a = indirect_flag_output ? FF(output_offset) : FF(0), + .avm_main_ind_op_a = FF(static_cast(indirect_flag_output)), + .avm_main_internal_return_ptr = FF(internal_return_ptr), + .avm_main_mem_idx_a = FF(direct_output_offset), + .avm_main_mem_idx_b = FF(direct_output_offset + 1), + .avm_main_mem_op_a = FF(1), + .avm_main_mem_op_b = FF(1), + .avm_main_pc = FF(pc), + .avm_main_rwa = FF(1), + .avm_main_rwb = FF(1), + .avm_main_w_in_tag = FF(static_cast(AvmMemoryTag::FF)), + }); + clk++; + write_slice_to_memory(call_ptr, + clk, + direct_output_offset + 2, + AvmMemoryTag::U8, + AvmMemoryTag::U8, + FF(internal_return_ptr), + { result.is_point_at_infinity() }); +} // Finalise Lookup Counts // // For log derivative lookups, we require a column that contains the number of times each lookup is consumed diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.hpp index 70d45adb3d5..b0e99b791d6 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.hpp @@ -11,6 +11,7 @@ #include "barretenberg/vm/avm_trace/avm_opcode.hpp" #include "barretenberg/vm/avm_trace/constants.hpp" #include "barretenberg/vm/avm_trace/gadgets/avm_conversion_trace.hpp" +#include "barretenberg/vm/avm_trace/gadgets/avm_ecc.hpp" #include "barretenberg/vm/avm_trace/gadgets/avm_keccak.hpp" #include "barretenberg/vm/avm_trace/gadgets/avm_pedersen.hpp" #include "barretenberg/vm/avm_trace/gadgets/avm_poseidon2.hpp" @@ -192,6 +193,15 @@ class AvmTraceBuilder { uint32_t output_offset, uint32_t input_offset, uint32_t input_size_offset); + // Embedded EC Add - the offsets are temporary + void op_ec_add(uint8_t indirect, + uint32_t lhs_x_offset, + uint32_t lhs_y_offset, + uint32_t lhs_is_inf_offset, + uint32_t rhs_x_offset, + uint32_t rhs_y_offset, + uint32_t rhs_is_inf_offset, + uint32_t output_offset); private: // Used for the standard indirect address resolution of three operands opcode. @@ -217,6 +227,7 @@ class AvmTraceBuilder { AvmPoseidon2TraceBuilder poseidon2_trace_builder; AvmKeccakTraceBuilder keccak_trace_builder; AvmPedersenTraceBuilder pedersen_trace_builder; + AvmEccTraceBuilder ecc_trace_builder; /** * @brief Create a kernel lookup opcode object diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/gadgets/avm_ecc.cpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/gadgets/avm_ecc.cpp new file mode 100644 index 00000000000..fd3fc8955d1 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/gadgets/avm_ecc.cpp @@ -0,0 +1,33 @@ +#include "barretenberg/vm/avm_trace/gadgets/avm_ecc.hpp" +#include "barretenberg/vm/avm_trace/avm_common.hpp" + +namespace bb::avm_trace { +using element = grumpkin::g1::affine_element; + +AvmEccTraceBuilder::AvmEccTraceBuilder() +{ + ecc_trace.reserve(AVM_TRACE_SIZE); +} + +std::vector AvmEccTraceBuilder::finalize() +{ + return std::move(ecc_trace); +} + +void AvmEccTraceBuilder::reset() +{ + ecc_trace.clear(); +} + +element AvmEccTraceBuilder::embedded_curve_add(element lhs, element rhs, uint32_t clk) +{ + element result = lhs + rhs; + std::tuple p1 = { lhs.x, lhs.y, lhs.is_point_at_infinity() }; + std::tuple p2 = { rhs.x, rhs.y, rhs.is_point_at_infinity() }; + std::tuple result_tuple = { result.x, result.y, result.is_point_at_infinity() }; + ecc_trace.push_back({ clk, p1, p2, result_tuple }); + + return result; +} + +} // namespace bb::avm_trace diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/gadgets/avm_ecc.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/gadgets/avm_ecc.hpp new file mode 100644 index 00000000000..6450a33db39 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/gadgets/avm_ecc.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" +#include "barretenberg/ecc/groups/affine_element.hpp" +#include "barretenberg/vm/avm_trace/avm_common.hpp" + +namespace bb::avm_trace { +class AvmEccTraceBuilder { + public: + struct EccTraceEntry { + uint32_t clk = 0; + std::tuple p1; // x, y, is_infinity + std::tuple p2; + std::tuple result; + }; + + AvmEccTraceBuilder(); + void reset(); + // Finalize the trace + std::vector finalize(); + grumpkin::g1::affine_element embedded_curve_add(grumpkin::g1::affine_element lhs, + grumpkin::g1::affine_element rhs, + uint32_t clk); + + private: + std::vector ecc_trace; +}; + +} // namespace bb::avm_trace diff --git a/barretenberg/cpp/src/barretenberg/vm/tests/avm_execution.test.cpp b/barretenberg/cpp/src/barretenberg/vm/tests/avm_execution.test.cpp index b3a227b9dc5..69d47e3f7d4 100644 --- a/barretenberg/cpp/src/barretenberg/vm/tests/avm_execution.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/tests/avm_execution.test.cpp @@ -1331,6 +1331,68 @@ TEST_F(AvmExecutionTests, pedersenHashOpCode) validate_trace(std::move(trace)); } +// +// Positive test with EmbeddedCurveAdd +TEST_F(AvmExecutionTests, embeddedCurveAddOpCode) +{ + // TODO: Look for hardcoded test vectors since bb is missing them + grumpkin::g1::affine_element a = grumpkin::g1::affine_element::random_element(); + auto a_is_inf = a.is_point_at_infinity(); + grumpkin::g1::affine_element b = grumpkin::g1::affine_element::random_element(); + auto b_is_inf = b.is_point_at_infinity(); + grumpkin::g1::affine_element res = a + b; + auto expected_output = std::vector{ res.x, res.y, res.is_point_at_infinity() }; + std::string bytecode_hex = to_hex(OpCode::CALLDATACOPY) + // Calldatacopy + "00" // Indirect flag + "00000000" // cd_offset + "00000002" // copy_size + "00000000" // dst_offset + + to_hex(OpCode::SET) + // opcode SET for direct src_length + "00" // Indirect flag + "01" // U8 + + to_hex(a_is_inf) + // + "00000002" // dst_offset + + to_hex(OpCode::CALLDATACOPY) + // calldatacopy + "00" // Indirect flag + "00000002" // cd_offset + "00000002" // copy_size + "00000003" // dst_offset + + to_hex(OpCode::SET) + // opcode SET for direct src_length + "00" // Indirect flag + "01" // U32 + + to_hex(b_is_inf) + // value 2 + "00000005" // dst_offset + + to_hex(OpCode::SET) + // opcode SET for direct src_length + "00" // Indirect flag + "03" // U32 + "00000007" // value + "00000006" // dst_offset + + to_hex(OpCode::ECADD) + // opcode ECADD + "40" // Indirect flag (sixth operand indirect) + "00000000" // hash_index offset (direct) + "00000001" // dest offset (direct) + "00000002" // input offset (indirect) + "00000003" // length offset (direct) + "00000004" // length offset (direct) + "00000005" // length offset (direct) + "00000006" // length offset (direct) + + to_hex(OpCode::RETURN) + // opcode RETURN + "00" // Indirect flag + "00000007" // ret offset 3 + "00000003"; // ret size 1 + + auto bytecode = hex_to_bytes(bytecode_hex); + auto instructions = Deserialization::parse(bytecode); + + // Assign a vector that we will mutate internally in gen_trace to store the return values; + std::vector returndata; + std::vector calldata = { a.x, a.y, b.x, b.y }; + auto trace = Execution::gen_trace(instructions, returndata, calldata, public_inputs_vec); + + EXPECT_EQ(returndata, expected_output); + + validate_trace(std::move(trace)); +} // Positive test for Kernel Input opcodes TEST_F(AvmExecutionTests, kernelInputOpcodes) diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 8f97bbb67cb..f1e8c98fb90 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -97,8 +97,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { }); it('elliptic curve operations', async () => { - const calldata: Fr[] = []; - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + const context = initContext(); const bytecode = getAvmTestContractBytecode('elliptic_curve_add_and_double'); const results = await new AvmSimulator(context).executeBytecode(bytecode); diff --git a/yarn-project/simulator/src/avm/opcodes/ec_add.test.ts b/yarn-project/simulator/src/avm/opcodes/ec_add.test.ts index 894f47b6dfe..2c665378238 100644 --- a/yarn-project/simulator/src/avm/opcodes/ec_add.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/ec_add.test.ts @@ -4,7 +4,7 @@ import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { beforeEach } from '@jest/globals'; import { type AvmContext } from '../avm_context.js'; -import { Field, Uint8, Uint32 } from '../avm_memory_types.js'; +import { Field, Uint32 } from '../avm_memory_types.js'; import { initContext } from '../fixtures/index.js'; import { EcAdd } from './ec_add.js'; @@ -20,17 +20,17 @@ describe('EC Instructions', () => { it('Should (de)serialize correctly', () => { const buf = Buffer.from([ EcAdd.opcode, // opcode - 0x01, // indirect + 0x20, // indirect ...Buffer.from('12345670', 'hex'), // p1x ...Buffer.from('12345671', 'hex'), // p1y - ...Buffer.from('00', 'hex'), // p1IsInfinite + ...Buffer.from('00000000', 'hex'), // p1IsInfinite ...Buffer.from('12345672', 'hex'), // p2x ...Buffer.from('12345673', 'hex'), // p2y - ...Buffer.from('01', 'hex'), // p2IsInfinite + ...Buffer.from('00000001', 'hex'), // p2IsInfinite ...Buffer.from('12345674', 'hex'), // dstOffset ]); const inst = new EcAdd( - /*indirect=*/ 0x01, + /*indirect=*/ 0x20, /*p1X=*/ 0x12345670, /*p1Y=*/ 0x12345671, /*p1IsInfinite=*/ 0, @@ -47,7 +47,7 @@ describe('EC Instructions', () => { it(`Should double correctly`, async () => { const x = new Field(grumpkin.generator().x); const y = new Field(grumpkin.generator().y); - const zero = new Uint8(0); + const zero = new Uint32(0); context.machineState.memory.set(0, x); context.machineState.memory.set(1, y); @@ -55,7 +55,7 @@ describe('EC Instructions', () => { context.machineState.memory.set(3, x); context.machineState.memory.set(4, y); context.machineState.memory.set(5, zero); - context.machineState.memory.set(6, new Uint32(6)); + // context.machineState.memory.set(6, new Uint32(6)); await new EcAdd( /*indirect=*/ 0, @@ -76,7 +76,7 @@ describe('EC Instructions', () => { it('Should add correctly', async () => { const G2 = grumpkin.add(grumpkin.generator(), grumpkin.generator()); - const zero = new Uint8(0); + const zero = new Uint32(0); const x1 = new Field(grumpkin.generator().x); const y1 = new Field(grumpkin.generator().y); diff --git a/yarn-project/simulator/src/avm/opcodes/ec_add.ts b/yarn-project/simulator/src/avm/opcodes/ec_add.ts index 2acbda553c1..044ca5e31a4 100644 --- a/yarn-project/simulator/src/avm/opcodes/ec_add.ts +++ b/yarn-project/simulator/src/avm/opcodes/ec_add.ts @@ -2,9 +2,9 @@ import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { Point } from '@aztec/foundation/fields'; import { type AvmContext } from '../avm_context.js'; -import { getBaseGasCost, getGasCostForTypeTag, getMemoryGasCost, sumGas } from '../avm_gas.js'; -import { Field, MemoryOperations, TypeTag } from '../avm_memory_types.js'; +import { Field } from '../avm_memory_types.js'; import { Opcode, OperandType } from '../serialization/instruction_serialization.js'; +import { Addressing } from './addressing_mode.js'; import { Instruction } from './instruction.js'; export class EcAdd extends Instruction { @@ -17,68 +17,76 @@ export class EcAdd extends Instruction { OperandType.UINT8, // indirect OperandType.UINT32, // p1X OperandType.UINT32, // p1Y - OperandType.UINT8, // p1IsInfinite + OperandType.UINT32, // p1IsInfinite OperandType.UINT32, // p2X OperandType.UINT32, // p2Y - OperandType.UINT8, // p2IsInfinite + OperandType.UINT32, // p2IsInfinite OperandType.UINT32, // dst ]; constructor( private indirect: number, - private p1X: number, - private p1Y: number, - private p1IsInfinite: number, - private p2X: number, - private p2Y: number, - private p2IsInfinite: number, + private p1XOffset: number, + private p1YOffset: number, + private p1IsInfiniteOffset: number, + private p2XOffset: number, + private p2YOffset: number, + private p2IsInfiniteOffset: number, private dstOffset: number, ) { super(); } public async execute(context: AvmContext): Promise { - const memoryOperations = { reads: 5, writes: 3, indirect: this.indirect }; + const memoryOperations = { reads: 6, writes: 3, indirect: this.indirect }; const memory = context.machineState.memory.track(this.type); context.machineState.consumeGas(this.gasCost(memoryOperations)); - memory.checkTags(TypeTag.FIELD, this.p1X, this.p1Y, this.p2X, this.p2Y); - memory.checkTags(TypeTag.UINT8, this.p1IsInfinite, this.p2IsInfinite); + const [p1XOffset, p1YOffset, p1IsInfiniteOffset, p2XOffset, p2YOffset, p2IsInfiniteOffset, dstOffset] = + Addressing.fromWire(this.indirect).resolve( + [ + this.p1XOffset, + this.p1YOffset, + this.p1IsInfiniteOffset, + this.p2XOffset, + this.p2YOffset, + this.p2IsInfiniteOffset, + this.dstOffset, + ], + memory, + ); - const p1X = memory.get(this.p1X); - const p1Y = memory.get(this.p1Y); - // unused. Point doesn't store this information - // const p1IsInfinite = memory.get(this.p1IsInfinite); + const p1X = memory.get(p1XOffset); + const p1Y = memory.get(p1YOffset); + const p1IsInfinite = memory.get(p1IsInfiniteOffset).toNumber() === 1; const p1 = new Point(p1X.toFr(), p1Y.toFr()); if (!p1.isOnGrumpkin()) { throw new Error(`Point1 is not on the curve`); } - const p2X = memory.get(this.p2X); - const p2Y = memory.get(this.p2Y); + const p2X = memory.get(p2XOffset); + const p2Y = memory.get(p2YOffset); // unused. Point doesn't store this information - // const p2IsInfinite = memory.get(this.p2IsInfinite); + const p2IsInfinite = memory.get(p2IsInfiniteOffset).toNumber() === 1; const p2 = new Point(p2X.toFr(), p2Y.toFr()); if (!p2.isOnGrumpkin()) { throw new Error(`Point1 is not on the curve`); } - // const dest = p1.add(p2); const grumpkin = new Grumpkin(); - const dest = grumpkin.add(p1, p2); - const dstOffsetResolved = Number(memory.get(this.dstOffset).toBigInt()); - - memory.set(dstOffsetResolved, new Field(dest.x)); - memory.set(dstOffsetResolved + 1, new Field(dest.y)); - memory.set(dstOffsetResolved + 2, new Field(dest.equals(Point.ZERO) ? 1 : 0)); + let dest = grumpkin.add(p1, p2); + // Temporary, + if (p1IsInfinite) { + dest = p2; + } else if (p2IsInfinite) { + dest = p1; + } + memory.set(dstOffset, new Field(dest.x)); + memory.set(dstOffset + 1, new Field(dest.y)); + // Check representation of infinity for grumpkin + memory.set(dstOffset + 2, new Field(dest.equals(Point.ZERO) ? 1 : 0)); memory.assert(memoryOperations); context.machineState.incrementPc(); } - - protected override gasCost(memoryOps: Partial) { - const baseGasCost = getGasCostForTypeTag(TypeTag.FIELD, getBaseGasCost(this.opcode)); - const memoryGasCost = getMemoryGasCost(memoryOps); - return sumGas(baseGasCost, memoryGasCost); - } }