Skip to content

Commit

Permalink
feat(avm): ecc
Browse files Browse the repository at this point in the history
  • Loading branch information
IlyasRidhuan committed Jun 11, 2024
1 parent fa63a71 commit 763e6bf
Show file tree
Hide file tree
Showing 13 changed files with 330 additions and 57 deletions.
28 changes: 15 additions & 13 deletions avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -831,24 +831,26 @@ fn handle_black_box_function(avm_instrs: &mut Vec<AvmInstruction>, 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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@ const std::unordered_map<OpCode, std::vector<OperandType>> 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 } },
Expand Down
10 changes: 10 additions & 0 deletions barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_execution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,16 @@ std::vector<Row> Execution::gen_trace(std::vector<Instruction> const& instructio
std::get<uint32_t>(inst.operands.at(3)),
std::get<uint32_t>(inst.operands.at(4)));
break;
case OpCode::ECADD:
trace_builder.op_ec_add(std::get<uint8_t>(inst.operands.at(0)),
std::get<uint32_t>(inst.operands.at(1)),
std::get<uint32_t>(inst.operands.at(2)),
std::get<uint32_t>(inst.operands.at(3)),
std::get<uint32_t>(inst.operands.at(4)),
std::get<uint32_t>(inst.operands.at(5)),
std::get<uint32_t>(inst.operands.at(6)),
std::get<uint32_t>(inst.operands.at(7)));
break;
case OpCode::REVERT:
trace_builder.op_revert(std::get<uint8_t>(inst.operands.at(0)),
std::get<uint32_t>(inst.operands.at(1)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ static const inline std::unordered_map<OpCode, GasTableEntry> 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 },
Expand Down Expand Up @@ -146,4 +147,4 @@ class AvmGasTraceBuilder {
uint32_t remaining_da_gas = 0;
};

} // namespace bb::avm_trace
} // namespace bb::avm_trace
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ enum class OpCode : uint8_t {
POSEIDON2,
SHA256,
PEDERSEN,
ECADD,
// Conversions
TORADIXLE,
// Future Gadgets -- pending changes in noir
Expand Down
107 changes: 107 additions & 0 deletions barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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
Expand Down
11 changes: 11 additions & 0 deletions barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_trace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down
33 changes: 33 additions & 0 deletions barretenberg/cpp/src/barretenberg/vm/avm_trace/gadgets/avm_ecc.cpp
Original file line number Diff line number Diff line change
@@ -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::EccTraceEntry> 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<FF, FF, bool> p1 = { lhs.x, lhs.y, lhs.is_point_at_infinity() };
std::tuple<FF, FF, bool> p2 = { rhs.x, rhs.y, rhs.is_point_at_infinity() };
std::tuple<FF, FF, bool> 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
29 changes: 29 additions & 0 deletions barretenberg/cpp/src/barretenberg/vm/avm_trace/gadgets/avm_ecc.hpp
Original file line number Diff line number Diff line change
@@ -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<FF, FF, bool> p1; // x, y, is_infinity
std::tuple<FF, FF, bool> p2;
std::tuple<FF, FF, bool> result;
};

AvmEccTraceBuilder();
void reset();
// Finalize the trace
std::vector<EccTraceEntry> finalize();
grumpkin::g1::affine_element embedded_curve_add(grumpkin::g1::affine_element lhs,
grumpkin::g1::affine_element rhs,
uint32_t clk);

private:
std::vector<EccTraceEntry> ecc_trace;
};

} // namespace bb::avm_trace
62 changes: 62 additions & 0 deletions barretenberg/cpp/src/barretenberg/vm/tests/avm_execution.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<FF>{ 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<uint8_t>(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<uint8_t>(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<FF> returndata;
std::vector<FF> 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)
Expand Down
3 changes: 1 addition & 2 deletions yarn-project/simulator/src/avm/avm_simulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 763e6bf

Please sign in to comment.