Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement Embedded EC add and double opcodes #3982

Merged
merged 18 commits into from
Jan 25, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,12 @@ void build_constraints(Builder& builder, AcirFormat const& constraint_system, bo

// Add ec add constraints
for (const auto& constraint : constraint_system.ec_add_constraints) {
create_ec_add_constraint(builder, constraint);
create_ec_add_constraint(builder, constraint, has_valid_witness_assignments);
}

// Add ec double
for (const auto& constraint : constraint_system.ec_double_constraints) {
create_ec_double_constraint(builder, constraint);
create_ec_double_constraint(builder, constraint, has_valid_witness_assignments);
}

// Add block constraints
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,22 @@ void handle_blackbox_func_call(Circuit::Opcode::BlackBoxFuncCall const& arg, Aci
.pub_key_x = arg.outputs[0].value,
.pub_key_y = arg.outputs[1].value,
});
} else if constexpr (std::is_same_v<T, Circuit::BlackBoxFuncCall::EmbeddedCurveAdd>) {
af.ec_add_constraints.push_back(EcAdd{
.input1_x = arg.input1_x.witness.value,
.input1_y = arg.input1_y.witness.value,
.input2_x = arg.input2_x.witness.value,
.input2_y = arg.input2_y.witness.value,
.result_x = arg.outputs[0].value,
.result_y = arg.outputs[1].value,
});
} else if constexpr (std::is_same_v<T, Circuit::BlackBoxFuncCall::EmbeddedCurveDouble>) {
af.ec_double_constraints.push_back(EcDouble{
.input_x = arg.input_x.witness.value,
.input_y = arg.input_y.witness.value,
.result_x = arg.outputs[0].value,
.result_y = arg.outputs[1].value,
});
} else if constexpr (std::is_same_v<T, Circuit::BlackBoxFuncCall::Keccak256>) {
af.keccak_constraints.push_back(KeccakConstraint{
.inputs = map(arg.inputs,
Expand Down
75 changes: 63 additions & 12 deletions barretenberg/cpp/src/barretenberg/dsl/acir_format/ec_operations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,81 @@
#include "barretenberg/dsl/types.hpp"
#include "barretenberg/ecc/curves/bn254/fr.hpp"
#include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp"
#include "barretenberg/ecc/groups/affine_element.hpp"
#include "barretenberg/proof_system/arithmetization/gate_data.hpp"

namespace acir_format {

template <typename Builder> void create_ec_add_constraint(Builder& builder, const EcAdd& input)
template <typename Builder>
void create_ec_add_constraint(Builder& builder, const EcAdd& input, bool has_valid_witness_assignments)
{
// TODO
builder.assert_equal(input.input1_x, input.input1_x);
ASSERT(false);
// Input to cycle_group points
using cycle_group_ct = bb::stdlib::cycle_group<Builder>;
using field_ct = bb::stdlib::field_t<Builder>;

auto x1 = field_ct::from_witness_index(&builder, input.input1_x);
auto y1 = field_ct::from_witness_index(&builder, input.input1_y);
auto x2 = field_ct::from_witness_index(&builder, input.input2_x);
auto y2 = field_ct::from_witness_index(&builder, input.input2_y);
if (!has_valid_witness_assignments) {
auto g1 = grumpkin::g1::affine_one;
// We need to have correct values representing points on the curve
builder.variables[input.input1_x] = g1.x;
builder.variables[input.input1_y] = g1.y;
builder.variables[input.input2_x] = g1.x;
builder.variables[input.input2_y] = g1.y;
}

cycle_group_ct input1_point(x1, y1, false);
cycle_group_ct input2_point(x2, y2, false);

// Addition
cycle_group_ct result = input1_point + input2_point;

auto x_normalized = result.x.normalize();
auto y_normalized = result.y.normalize();
builder.assert_equal(x_normalized.witness_index, input.result_x);
builder.assert_equal(y_normalized.witness_index, input.result_y);
}

template void create_ec_add_constraint<UltraCircuitBuilder>(UltraCircuitBuilder& builder, const EcAdd& input);
template void create_ec_add_constraint<UltraCircuitBuilder>(UltraCircuitBuilder& builder,
const EcAdd& input,
bool has_valid_witness_assignments);
template void create_ec_add_constraint<GoblinUltraCircuitBuilder>(GoblinUltraCircuitBuilder& builder,
const EcAdd& input);
const EcAdd& input,
bool has_valid_witness_assignments);

template <typename Builder> void create_ec_double_constraint(Builder& builder, const EcDouble& input)
template <typename Builder>
void create_ec_double_constraint(Builder& builder, const EcDouble& input, bool has_valid_witness_assignments)
{
// TODO
builder.assert_equal(input.input_x, input.input_x);
ASSERT(false);
using cycle_group_ct = bb::stdlib::cycle_group<Builder>;
using field_ct = bb::stdlib::field_t<Builder>;
// Input to cycle_group point
auto x = field_ct::from_witness_index(&builder, input.input_x);
auto y = field_ct::from_witness_index(&builder, input.input_y);

if (!has_valid_witness_assignments) {
auto g1 = grumpkin::g1::affine_one;
// We need to have correct values representing point on the curve
builder.variables[input.input_x] = g1.x;
builder.variables[input.input_y] = g1.y;
}
cycle_group_ct input_point(x, y, false);

// Doubling
cycle_group_ct result = input_point.dbl();

auto x_normalized = result.x.normalize();
auto y_normalized = result.y.normalize();
builder.assert_equal(x_normalized.witness_index, input.result_x);
builder.assert_equal(y_normalized.witness_index, input.result_y);
}

template void create_ec_double_constraint<UltraCircuitBuilder>(UltraCircuitBuilder& builder, const EcDouble& input);
template void create_ec_double_constraint<UltraCircuitBuilder>(UltraCircuitBuilder& builder,
const EcDouble& input,
bool has_valid_witness_assignments);
template void create_ec_double_constraint<GoblinUltraCircuitBuilder>(GoblinUltraCircuitBuilder& builder,
const EcDouble& input);
const EcDouble& input,
bool has_valid_witness_assignments);

} // namespace acir_format
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ struct EcAdd {
friend bool operator==(EcAdd const& lhs, EcAdd const& rhs) = default;
};

template <typename Builder> void create_ec_add_constraint(Builder& builder, const EcAdd& input);
template <typename Builder>
void create_ec_add_constraint(Builder& builder, const EcAdd& input, bool has_valid_witness_assignments);

struct EcDouble {
uint32_t input_x;
Expand All @@ -31,5 +32,6 @@ struct EcDouble {
friend bool operator==(EcDouble const& lhs, EcDouble const& rhs) = default;
};

template <typename Builder> void create_ec_double_constraint(Builder& builder, const EcDouble& input);
template <typename Builder>
void create_ec_double_constraint(Builder& builder, const EcDouble& input, bool has_valid_witness_assignments);
} // namespace acir_format
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#include "ec_operations.hpp"
#include "acir_format.hpp"
#include "barretenberg/plonk/proof_system/types/proof.hpp"
#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp"

#include <gtest/gtest.h>
#include <vector>

namespace acir_format::tests {
using curve_ct = bb::stdlib::secp256k1<Builder>;

class EcOperations : public ::testing::Test {
protected:
static void SetUpTestSuite() { bb::srs::init_crs_factory("../srs_db/ignition"); }
};

size_t generate_ec_add_constraint(EcAdd& ec_add_constraint, WitnessVector& witness_values)
{
using cycle_group_ct = bb::stdlib::cycle_group<Builder>;
witness_values.push_back(0);
auto g1 = grumpkin::g1::affine_one;
cycle_group_ct input_point(g1);
// Doubling
cycle_group_ct result = input_point.dbl();
// add: x,y,x2,y2
witness_values.push_back(g1.x);
witness_values.push_back(g1.y);
witness_values.push_back(g1.x);
witness_values.push_back(g1.y);
witness_values.push_back(result.x.get_value());
witness_values.push_back(result.y.get_value());
ec_add_constraint = EcAdd{
.input1_x = 1,
.input1_y = 2,
.input2_x = 3,
.input2_y = 4,
.result_x = 5,
.result_y = 6,
};
return witness_values.size();
}

size_t generate_ec_double_constraint(EcDouble& ec_double_constraint, WitnessVector& witness_values)
{

using cycle_group_ct = bb::stdlib::cycle_group<Builder>;
auto g1 = grumpkin::g1::affine_one;
cycle_group_ct input_point(g1);
// Doubling
cycle_group_ct result = input_point.dbl();
// add: x,y,x2,y2
uint32_t result_x_witness_index = static_cast<uint32_t>(witness_values.size());

witness_values.push_back(result.x.get_value());
uint32_t result_y_witness_index = static_cast<uint32_t>(witness_values.size());
witness_values.push_back(result.y.get_value());
ec_double_constraint = EcDouble{
.input_x = 1,
.input_y = 2,
.result_x = result_x_witness_index,
.result_y = result_y_witness_index,
};
return witness_values.size();
}

TEST_F(EcOperations, TestECOperations)
{
EcAdd ec_add_constraint;
EcDouble ec_double_constraint;

WitnessVector witness_values;
generate_ec_add_constraint(ec_add_constraint, witness_values);
size_t num_variables = generate_ec_double_constraint(ec_double_constraint, witness_values);

poly_triple constrain_5_is_7{
.a = 5,
.b = 7,
.c = 0,
.q_m = 0,
.q_l = 1,
.q_r = -1,
.q_o = 0,
.q_c = 0,
};
poly_triple constrain_6_is_8{
.a = 6,
.b = 8,
.c = 0,
.q_m = 0,
.q_l = 1,
.q_r = -1,
.q_o = 0,
.q_c = 0,
};

AcirFormat constraint_system{
.varnum = static_cast<uint32_t>(num_variables + 1),
.public_inputs = {},
.logic_constraints = {},
.range_constraints = {},
.sha256_constraints = {},
.schnorr_constraints = {},
.ecdsa_k1_constraints = {},
.ecdsa_r1_constraints = {},
.blake2s_constraints = {},
.blake3_constraints = {},
.keccak_constraints = {},
.keccak_var_constraints = {},
.keccak_permutations = {},
.pedersen_constraints = {},
.pedersen_hash_constraints = {},
.fixed_base_scalar_mul_constraints = {},
.ec_add_constraints = { ec_add_constraint },
.ec_double_constraints = { ec_double_constraint },
.recursion_constraints = {},
.bigint_from_le_bytes_constraints = {},
.bigint_operations = {},
.constraints = { constrain_5_is_7, constrain_6_is_8 },
.block_constraints = {},
};

auto builder = create_circuit(constraint_system, /*size_hint*/ 0, witness_values);

auto composer = Composer();
auto prover = composer.create_prover(builder);

auto proof = prover.construct_proof();
EXPECT_TRUE(builder.check_circuit());
auto verifier = composer.create_verifier(builder);
EXPECT_EQ(verifier.verify_proof(proof), true);
}

} // namespace acir_format::tests
8 changes: 4 additions & 4 deletions noir/acvm-repo/acir/src/circuit/black_box_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ impl BlackBoxFunc {
BlackBoxFunc::PedersenHash => "pedersen_hash",
BlackBoxFunc::EcdsaSecp256k1 => "ecdsa_secp256k1",
BlackBoxFunc::FixedBaseScalarMul => "fixed_base_scalar_mul",
BlackBoxFunc::EmbeddedCurveAdd => "ec_add",
BlackBoxFunc::EmbeddedCurveDouble => "ec_double",
BlackBoxFunc::EmbeddedCurveAdd => "embedded_curve_add",
BlackBoxFunc::EmbeddedCurveDouble => "embedded_curve_double",
BlackBoxFunc::AND => "and",
BlackBoxFunc::XOR => "xor",
BlackBoxFunc::RANGE => "range",
Expand Down Expand Up @@ -109,8 +109,8 @@ impl BlackBoxFunc {
"ecdsa_secp256k1" => Some(BlackBoxFunc::EcdsaSecp256k1),
"ecdsa_secp256r1" => Some(BlackBoxFunc::EcdsaSecp256r1),
"fixed_base_scalar_mul" => Some(BlackBoxFunc::FixedBaseScalarMul),
"ec_add" => Some(BlackBoxFunc::EmbeddedCurveAdd),
"ec_double" => Some(BlackBoxFunc::EmbeddedCurveDouble),
"embedded_curve_add" => Some(BlackBoxFunc::EmbeddedCurveAdd),
"embedded_curve_double" => Some(BlackBoxFunc::EmbeddedCurveDouble),
"and" => Some(BlackBoxFunc::AND),
"xor" => Some(BlackBoxFunc::XOR),
"range" => Some(BlackBoxFunc::RANGE),
Expand Down
31 changes: 31 additions & 0 deletions noir/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,34 @@ pub(super) fn fixed_base_scalar_mul(

Ok(())
}

pub(super) fn embedded_curve_double(
backend: &impl BlackBoxFunctionSolver,
initial_witness: &mut WitnessMap,
input_x: FunctionInput,
input_y: FunctionInput,
outputs: (Witness, Witness),
) -> Result<(), OpcodeResolutionError> {
embedded_curve_add(backend, initial_witness, input_x, input_y, input_x, input_y, outputs)
}

pub(super) fn embedded_curve_add(
backend: &impl BlackBoxFunctionSolver,
initial_witness: &mut WitnessMap,
input1_x: FunctionInput,
input1_y: FunctionInput,
input2_x: FunctionInput,
input2_y: FunctionInput,
outputs: (Witness, Witness),
) -> Result<(), OpcodeResolutionError> {
let input1_x = witness_to_value(initial_witness, input1_x.witness)?;
let input1_y = witness_to_value(initial_witness, input1_y.witness)?;
let input2_x = witness_to_value(initial_witness, input2_x.witness)?;
let input2_y = witness_to_value(initial_witness, input2_y.witness)?;
let (res_x, res_y) = backend.ec_add(input1_x, input1_y, input2_x, input2_y)?;

insert_value(&outputs.0, res_x, initial_witness)?;
insert_value(&outputs.1, res_y, initial_witness)?;

Ok(())
}
17 changes: 13 additions & 4 deletions noir/acvm-repo/acvm/src/pwg/blackbox/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod range;
mod signature;

use fixed_base_scalar_mul::fixed_base_scalar_mul;
use fixed_base_scalar_mul::{embedded_curve_add, embedded_curve_double};
// Hash functions should eventually be exposed for external consumers.
use hash::solve_generic_256_hash_opcode;
use logic::{and, xor};
Expand Down Expand Up @@ -177,11 +178,19 @@ pub(crate) fn solve(
BlackBoxFuncCall::FixedBaseScalarMul { low, high, outputs } => {
fixed_base_scalar_mul(backend, initial_witness, *low, *high, *outputs)
}
BlackBoxFuncCall::EmbeddedCurveAdd { .. } => {
todo!();
BlackBoxFuncCall::EmbeddedCurveAdd { input1_x, input1_y, input2_x, input2_y, outputs } => {
embedded_curve_add(
backend,
initial_witness,
*input1_x,
*input1_y,
*input2_x,
*input2_y,
*outputs,
)
}
BlackBoxFuncCall::EmbeddedCurveDouble { .. } => {
todo!();
BlackBoxFuncCall::EmbeddedCurveDouble { input_x, input_y, outputs } => {
embedded_curve_double(backend, initial_witness, *input_x, *input_y, *outputs)
}
// Recursive aggregation will be entirely handled by the backend and is not solved by the ACVM
BlackBoxFuncCall::RecursiveAggregation { .. } => Ok(()),
Expand Down
20 changes: 20 additions & 0 deletions noir/acvm-repo/bn254_blackbox_solver/src/fixed_base_scalar_mul.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,26 @@ pub fn fixed_base_scalar_mul(
}
}

pub fn embedded_curve_add(
input1_x: FieldElement,
input1_y: FieldElement,
input2_x: FieldElement,
input2_y: FieldElement,
) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> {
let mut point1 = grumpkin::SWAffine::new(input1_x.into_repr(), input1_y.into_repr());
let point2 = grumpkin::SWAffine::new(input2_x.into_repr(), input2_y.into_repr());
let res = point1 + point2;
point1 = res.into();
if let Some((res_x, res_y)) = point1.xy() {
Ok((FieldElement::from_repr(*res_x), FieldElement::from_repr(*res_y)))
} else {
Err(BlackBoxResolutionError::Failed(
BlackBoxFunc::EmbeddedCurveAdd,
"Point is not on curve".to_string(),
))
}
}

#[cfg(test)]
mod grumpkin_fixed_base_scalar_mul {
use ark_ff::BigInteger;
Expand Down
Loading
Loading