Skip to content
Merged
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
10 changes: 10 additions & 0 deletions cpp/src/barretenberg/proof_system/arithmetization/gate_data.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ template <typename FF> struct poly_triple_ {

using poly_triple = poly_triple_<barretenberg::fr>;

struct ecc_op_tuple {
uint32_t op;
uint32_t x_lo;
uint32_t x_hi;
uint32_t y_lo;
uint32_t y_hi;
uint32_t z_lo;
uint32_t z_hi;
};

template <typename B> inline void read(B& buf, poly_triple& constraint)
{
using serialize::read;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#include "barretenberg/crypto/generators/generator_data.hpp"
#include "ultra_circuit_builder.hpp"
#include <gtest/gtest.h>

using namespace barretenberg;

namespace {
auto& engine = numeric::random::get_debug_engine();
}
namespace proof_system {

/**
* @brief Test the queueing of simple ecc ops via the Goblin builder
* @details There are two things to check here: 1) When ecc ops are queued by the builder, the corresponding native
* operations are performed correctly by the internal ecc op queue, and 2) The ecc op gate operands are correctly
* encoded in the op_wires, i.e. the operands can be reconstructed as expected.
*
*/
TEST(UltraCircuitBuilder, GoblinSimple)
{
auto builder = UltraCircuitBuilder();

// Compute a simple point accumulation natively
auto P1 = g1::affine_element::random_element();
auto P2 = g1::affine_element::random_element();
auto z = fr::random_element();
auto P_expected = P1 + P2 * z;

// Add gates corresponding to the above operations
builder.queue_ecc_add_accum(P1);
builder.queue_ecc_mul_accum(P2, z);

// Add equality op gates based on the internal accumulator
auto P_result = builder.queue_ecc_eq();

// Check that value returned from internal accumulator is correct
EXPECT_EQ(P_result, P_expected);

// Check that the accumulator in the op queue has been reset to 0
auto accumulator = builder.op_queue.get_accumulator();
EXPECT_EQ(accumulator, g1::affine_point_at_infinity);

// Check number of ecc op "gates"/rows = 3 ops * 2 rows per op = 6
EXPECT_EQ(builder.num_ecc_op_gates, 6);

// Check that the expected op codes have been correctly recorded in the 1st op wire
EXPECT_EQ(builder.ecc_op_wire_1[0], EccOpCode::ADD_ACCUM);
EXPECT_EQ(builder.ecc_op_wire_1[2], EccOpCode::MUL_ACCUM);
EXPECT_EQ(builder.ecc_op_wire_1[4], EccOpCode::EQUALITY);

// Check that we can reconstruct the coordinates of P1 from the op_wires
auto chunk_size = plonk::NUM_LIMB_BITS_IN_FIELD_SIMULATION * 2;
auto P1_x_lo = uint256_t(builder.variables[builder.ecc_op_wire_2[0]]);
auto P1_x_hi = uint256_t(builder.variables[builder.ecc_op_wire_3[0]]);
auto P1_x = P1_x_lo + (P1_x_hi << chunk_size);
EXPECT_EQ(P1_x, uint256_t(P1.x));
auto P1_y_lo = uint256_t(builder.variables[builder.ecc_op_wire_4[0]]);
auto P1_y_hi = uint256_t(builder.variables[builder.ecc_op_wire_2[1]]);
auto P1_y = P1_y_lo + (P1_y_hi << chunk_size);
EXPECT_EQ(P1_y, uint256_t(P1.y));

// Check that we can reconstruct the coordinates of P2 from the op_wires
auto P2_x_lo = uint256_t(builder.variables[builder.ecc_op_wire_2[2]]);
auto P2_x_hi = uint256_t(builder.variables[builder.ecc_op_wire_3[2]]);
auto P2_x = P2_x_lo + (P2_x_hi << chunk_size);
EXPECT_EQ(P2_x, uint256_t(P2.x));
auto P2_y_lo = uint256_t(builder.variables[builder.ecc_op_wire_4[2]]);
auto P2_y_hi = uint256_t(builder.variables[builder.ecc_op_wire_2[3]]);
auto P2_y = P2_y_lo + (P2_y_hi << chunk_size);
EXPECT_EQ(P2_y, uint256_t(P2.y));

// Check that we can reconstruct the coordinates of P_result from the op_wires
auto P_expected_x_lo = uint256_t(builder.variables[builder.ecc_op_wire_2[4]]);
auto P_expected_x_hi = uint256_t(builder.variables[builder.ecc_op_wire_3[4]]);
auto P_expected_x = P_expected_x_lo + (P_expected_x_hi << chunk_size);
EXPECT_EQ(P_expected_x, uint256_t(P_expected.x));
auto P_expected_y_lo = uint256_t(builder.variables[builder.ecc_op_wire_4[4]]);
auto P_expected_y_hi = uint256_t(builder.variables[builder.ecc_op_wire_2[5]]);
auto P_expected_y = P_expected_y_lo + (P_expected_y_hi << chunk_size);
EXPECT_EQ(P_expected_y, uint256_t(P_expected.y));
}

/**
* @brief Test correctness of native ecc batch mul performed behind the scenes when adding ecc op gates for a batch mul
*
*/
TEST(UltraCircuitBuilder, GoblinBatchMul)
{
using Point = g1::affine_element;
using Scalar = fr;

auto builder = UltraCircuitBuilder();
const size_t num_muls = 3;

// Compute some random points and scalars to batch multiply
std::vector<Point> points;
std::vector<Scalar> scalars;
auto batched_expected = Point::infinity();
for (size_t i = 0; i < num_muls; ++i) {
points.emplace_back(Point::random_element());
scalars.emplace_back(Scalar::random_element());
batched_expected = batched_expected + points[i] * scalars[i];
}

// Populate the batch mul operands in the op wires and natively compute the result
auto batched_result = builder.batch_mul(points, scalars);

// Extract current accumulator point from the op queue and check the result
EXPECT_EQ(batched_result, batched_expected);
}

} // namespace proof_system
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,154 @@ template <typename FF> uint32_t UltraCircuitBuilder_<FF>::put_constant_variable(
}
}

/**
* ** Goblin Methods **
*/

/**
* @brief Add gates corresponding to a batched mul
*
* @param points
* @param scalars
* @return g1::affine_element Result of batched mul
*/
template <typename FF>
g1::affine_element UltraCircuitBuilder_<FF>::batch_mul(const std::vector<g1::affine_element>& points,
const std::vector<fr>& scalars)
{
// TODO(luke): Do we necessarily want to check accum == 0? Other checks?
ASSERT(op_queue.get_accumulator().is_point_at_infinity());

size_t num_muls = points.size();
for (size_t idx = 0; idx < num_muls; ++idx) {
queue_ecc_mul_accum(points[idx], scalars[idx]);
}
return op_queue.get_accumulator();
}

/**
* @brief Add gates for simple point addition without scalar and compute corresponding op natively
*
* @param point
*/
template <typename FF> void UltraCircuitBuilder_<FF>::queue_ecc_add_accum(const barretenberg::g1::affine_element& point)
{
// Add raw op to queue
op_queue.add_accumulate(point);

// Add ecc op gates
add_ecc_op_gates(EccOpCode::ADD_ACCUM, point);
}

/**
* @brief Add gates for point mul and add and compute corresponding op natively
*
* @param point
* @param scalar
*/
template <typename FF>
void UltraCircuitBuilder_<FF>::queue_ecc_mul_accum(const barretenberg::g1::affine_element& point,
const barretenberg::fr& scalar)
{
// Add raw op to op queue
op_queue.mul_accumulate(point, scalar);

// Add ecc op gates
add_ecc_op_gates(EccOpCode::MUL_ACCUM, point, scalar);
}

/**
* @brief Add point equality gates
*
* @return point to which equality has been asserted
*/
template <typename FF> barretenberg::g1::affine_element UltraCircuitBuilder_<FF>::queue_ecc_eq()
{
// Add raw op to op queue
auto point = op_queue.eq();

// Add ecc op gates
add_ecc_op_gates(EccOpCode::EQUALITY, point);

return point;
}

/**
* @brief Add ecc op gates given an op code and its operands
*
* @param op Op code
* @param point
* @param scalar
*/
template <typename FF>
void UltraCircuitBuilder_<FF>::add_ecc_op_gates(uint32_t op, const g1::affine_element& point, const fr& scalar)
{
auto op_tuple = make_ecc_op_tuple(op, point, scalar);

record_ecc_op(op_tuple);
}

/**
* @brief Decompose ecc operands into components, add corresponding variables, return ecc op tuple
*
* @param op
* @param point
* @param scalar
* @return ecc_op_tuple Tuple of indices into variables array used to construct pair of ecc op gates
*/
template <typename FF>
ecc_op_tuple UltraCircuitBuilder_<FF>::make_ecc_op_tuple(uint32_t op, const g1::affine_element& point, const fr& scalar)
{
const size_t CHUNK_SIZE = 2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS;
auto x_256 = uint256_t(point.x);
auto y_256 = uint256_t(point.y);
auto x_lo_idx = this->add_variable(x_256.slice(0, CHUNK_SIZE));
auto x_hi_idx = this->add_variable(x_256.slice(CHUNK_SIZE, CHUNK_SIZE * 2));
auto y_lo_idx = this->add_variable(y_256.slice(0, CHUNK_SIZE));
auto y_hi_idx = this->add_variable(y_256.slice(CHUNK_SIZE, CHUNK_SIZE * 2));

// Split scalar into 128 bit endomorphism scalars
fr z_1 = 0;
fr z_2 = 0;
// TODO(luke): do this montgomery conversion here?
// auto converted = scalar.from_montgomery_form();
// fr::split_into_endomorphism_scalars(converted, z_1, z_2);
// z_1 = z_1.to_montgomery_form();
// z_2 = z_2.to_montgomery_form();
fr::split_into_endomorphism_scalars(scalar, z_1, z_2);
auto z_1_idx = this->add_variable(z_1);
auto z_2_idx = this->add_variable(z_2);

return { op, x_lo_idx, x_hi_idx, y_lo_idx, y_hi_idx, z_1_idx, z_2_idx };
}

/**
* @brief Add ecc operation to queue
*
* @param in Variables array indices corresponding to operation inputs
* @note We dont explicitly set values for the selectors here since their values are fully determined by
* num_ecc_op_gates. E.g. in the composer we can reconstruct q_ecc_op as the indicator on the first num_ecc_op_gates
* indices. All other selectors are simply 0 on this domain.
*/
template <typename FF> void UltraCircuitBuilder_<FF>::record_ecc_op(const ecc_op_tuple& in)
{
ecc_op_wire_1.emplace_back(in.op);
ecc_op_wire_2.emplace_back(in.x_lo);
ecc_op_wire_3.emplace_back(in.x_hi);
ecc_op_wire_4.emplace_back(in.y_lo);

ecc_op_wire_1.emplace_back(in.op); // TODO(luke): second op val is sort of a dummy. use "op" again?
ecc_op_wire_2.emplace_back(in.y_hi);
ecc_op_wire_3.emplace_back(in.z_lo);
ecc_op_wire_4.emplace_back(in.z_hi);

num_ecc_op_gates += 2;
};

/**
* End of Goblin Methods
*/

template <typename FF> plookup::BasicTable& UltraCircuitBuilder_<FF>::get_table(const plookup::BasicTableId id)
{
for (plookup::BasicTable& table : lookup_tables) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "barretenberg/plonk/proof_system/types/prover_settings.hpp"
#include "barretenberg/polynomials/polynomial.hpp"
#include "barretenberg/proof_system/arithmetization/arithmetization.hpp"
#include "barretenberg/proof_system/op_queue/ecc_op_queue.hpp"
#include "barretenberg/proof_system/plookup_tables/plookup_tables.hpp"
#include "barretenberg/proof_system/plookup_tables/types.hpp"
#include "barretenberg/proof_system/types/merkle_hash_type.hpp"
Expand Down Expand Up @@ -35,6 +36,12 @@ template <typename FF> class UltraCircuitBuilder_ : public CircuitBuilderBase<ar
static constexpr size_t NUM_RESERVED_GATES = 4;
// number of gates created per non-native field operation in process_non_native_field_multiplications
static constexpr size_t GATES_PER_NON_NATIVE_FIELD_MULTIPLICATION_ARITHMETIC = 7;

size_t num_ecc_op_gates = 0; // number of ecc op "gates" (rows); these are placed at the start of the circuit

// Stores record of ecc operations and performs corresponding native operations internally
ECCOpQueue op_queue;

struct non_native_field_witnesses {
// first 4 array elements = limbs
// 5th element = prime basis limb
Expand Down Expand Up @@ -532,6 +539,14 @@ template <typename FF> class UltraCircuitBuilder_ : public CircuitBuilderBase<ar
WireVector& w_o = std::get<2>(this->wires);
WireVector& w_4 = std::get<3>(this->wires);

// Wires storing ecc op queue data; values are indices into the variables array
std::array<WireVector, arithmetization::Ultra<FF>::NUM_WIRES> ecc_op_wires;

WireVector& ecc_op_wire_1 = std::get<0>(ecc_op_wires);
WireVector& ecc_op_wire_2 = std::get<1>(ecc_op_wires);
WireVector& ecc_op_wire_3 = std::get<2>(ecc_op_wires);
WireVector& ecc_op_wire_4 = std::get<3>(ecc_op_wires);

SelectorVector& q_m = this->selectors.q_m;
SelectorVector& q_c = this->selectors.q_c;
SelectorVector& q_1 = this->selectors.q_1;
Expand Down Expand Up @@ -688,6 +703,20 @@ template <typename FF> class UltraCircuitBuilder_ : public CircuitBuilderBase<ar

uint32_t put_constant_variable(const FF& variable);

/**
* ** Goblin Methods ** (methods for add ecc op queue gates)
**/
void queue_ecc_add_accum(const g1::affine_element& point);
void queue_ecc_mul_accum(const g1::affine_element& point, const fr& scalar);
g1::affine_element queue_ecc_eq();
g1::affine_element batch_mul(const std::vector<g1::affine_element>& points, const std::vector<fr>& scalars);

private:
void record_ecc_op(const ecc_op_tuple& in);
void add_ecc_op_gates(uint32_t op, const g1::affine_element& point, const fr& scalar = fr::zero());
ecc_op_tuple make_ecc_op_tuple(uint32_t op, const g1::affine_element& point, const fr& scalar = fr::zero());

public:
size_t get_num_constant_gates() const override { return 0; }

/**
Expand Down
Loading