Skip to content

Commit

Permalink
feat: Implement Embedded EC add and double opcodes (#3982)
Browse files Browse the repository at this point in the history
This PR resolves the following points from the issue
noir-lang/noir#3983:

- Add ACVM solvers for ECADD and ECDOUBLE
- Generate circuits for ECADD and ECDOUBLE in BB/dsl
- Add unit test in BB/DSL for these new opcodes
- Add integration test in Noir demonstrating the use of the opcodes
  • Loading branch information
guipublic authored Jan 25, 2024
1 parent d1e4377 commit ccb7bff
Show file tree
Hide file tree
Showing 12 changed files with 321 additions and 33 deletions.
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

0 comments on commit ccb7bff

Please sign in to comment.