From 2acca41581bca2dfa5186a516dfb5d2646abef89 Mon Sep 17 00:00:00 2001 From: zac-williamson Date: Tue, 15 Aug 2023 18:21:09 +0000 Subject: [PATCH] added cycle_group class --- .../arithmetization/gate_data.hpp | 7 + .../circuit_builder/circuit_builder_base.hpp | 7 + .../standard_circuit_builder.cpp | 231 ++++++++++++ .../standard_circuit_builder.hpp | 2 + .../circuit_builder/turbo_circuit_builder.cpp | 230 ++++++++++++ .../circuit_builder/turbo_circuit_builder.hpp | 13 +- .../circuit_builder/ultra_circuit_builder.cpp | 99 ++++++ .../circuit_builder/ultra_circuit_builder.hpp | 3 +- .../stdlib/primitives/group/cycle_group.cpp | 291 +++++++++++++++ .../stdlib/primitives/group/cycle_group.hpp | 130 +++++++ .../primitives/group/cycle_group.test.cpp | 335 ++++++++++++++++++ 11 files changed, 1342 insertions(+), 6 deletions(-) create mode 100644 circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.cpp create mode 100644 circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.hpp create mode 100644 circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.test.cpp diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/arithmetization/gate_data.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/arithmetization/gate_data.hpp index dbdd25061d5..6b10d1adfe9 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/arithmetization/gate_data.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/arithmetization/gate_data.hpp @@ -127,4 +127,11 @@ template struct ecc_add_gate_ { FF endomorphism_coefficient; FF sign_coefficient; }; + +template struct ecc_dbl_gate_ { + uint32_t x1; + uint32_t y1; + uint32_t x3; + uint32_t y3; +}; } // namespace proof_system diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/circuit_builder_base.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/circuit_builder_base.hpp index 4e5e29510d6..a9d3f83d614 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/circuit_builder_base.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/circuit_builder_base.hpp @@ -1,6 +1,7 @@ #pragma once #include "barretenberg/common/slab_allocator.hpp" #include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" #include "barretenberg/proof_system/arithmetization/arithmetization.hpp" #include "barretenberg/proof_system/arithmetization/gate_data.hpp" #include @@ -11,6 +12,9 @@ static constexpr uint32_t DUMMY_TAG = 0; template class CircuitBuilderBase { public: using FF = typename Arithmetization::FF; + using EmbeddedCurve = + std::conditional_t, barretenberg::g1, grumpkin::g1>; + static constexpr size_t NUM_WIRES = Arithmetization::NUM_WIRES; // Keeping NUM_WIRES, at least temporarily, for backward compatibility static constexpr size_t program_width = Arithmetization::NUM_WIRES; @@ -86,6 +90,9 @@ template class CircuitBuilderBase { virtual void create_mul_gate(const mul_triple_& in) = 0; virtual void create_bool_gate(const uint32_t a) = 0; virtual void create_poly_gate(const poly_triple_& in) = 0; + virtual void create_ecc_add_gate(const ecc_add_gate_& in) = 0; + virtual void create_ecc_dbl_gate(const ecc_dbl_gate_& in) = 0; + virtual size_t get_num_constant_gates() const = 0; /** diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/standard_circuit_builder.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/standard_circuit_builder.cpp index 100e4045706..c7752996e99 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/standard_circuit_builder.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/standard_circuit_builder.cpp @@ -240,6 +240,237 @@ template void StandardCircuitBuilder_::create_poly_gate(const ++this->num_gates; } +/** + * @brief Create a gate where we validate an elliptic curve point addition + * (x1, y1) + (x2, y2) = (x3, y3) + * N.B. uses incomplete addition formula. Use with caution + * @tparam FF + * @param in + */ +template void StandardCircuitBuilder_::create_ecc_add_gate(const ecc_add_gate_& in) +{ + const auto sign_coefficient = in.sign_coefficient; + const auto x1 = this->get_variable(in.x1); + const auto x2 = this->get_variable(in.x2); + const auto x3 = this->get_variable(in.x3); + const auto y1 = this->get_variable(in.y1); + const auto y2 = sign_coefficient * this->get_variable(in.y2); + + bool collision = x2 == x1; + if (collision) { + this->failure("create_ecc_add_gate incomplete formula collision"); + } + const auto lambda_v = collision ? 0 : (y2 - y1) / (x2 - x1); + const auto lambda = this->add_variable(lambda_v); + + // (x2 - x1) * lambda - y2 + y1 = 0 + const auto x2_minus_x1_v = x2 - x1; + const auto x2_minus_x1 = this->add_variable(x2_minus_x1_v); + create_poly_gate({ + .a = in.x2, + .b = in.x1, + .c = x2_minus_x1, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = -1, + .q_c = 0, + }); + const auto t2_v = lambda_v * x2_minus_x1_v; + const auto t2 = this->add_variable(t2_v); + create_poly_gate({ + .a = lambda, + .b = x2_minus_x1, + .c = t2, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + create_poly_gate({ + .a = t2, + .b = in.y2, + .c = in.y1, + .q_m = 0, + .q_l = 1, + .q_r = -sign_coefficient, + .q_o = 1, + .q_c = 0, + }); + + // lambda * lambda - x2 - x1 = x3 + const auto x2_plus_x1_v = x2 + x1; + const auto x2_plus_x1 = this->add_variable(x2_plus_x1_v); + create_poly_gate({ + .a = in.x2, + .b = in.x1, + .c = x2_plus_x1, + .q_m = 0, + .q_l = 1, + .q_r = 1, + .q_o = -1, + .q_c = 0, + }); + const auto lambda_sqr_v = lambda_v * lambda_v; + const auto lambda_sqr = this->add_variable(lambda_sqr_v); + create_poly_gate({ + .a = lambda, + .b = lambda, + .c = lambda_sqr, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + create_poly_gate({ + .a = lambda_sqr, + .b = x2_plus_x1, + .c = in.x3, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = -1, + .q_c = 0, + }); + + // lambda * (x1 - x3) - y1 - y3 = 0 + const auto x1_sub_x3_v = x1 - x3; + const auto x1_sub_x3 = this->add_variable(x1_sub_x3_v); + create_poly_gate({ + .a = in.x1, + .b = in.x3, + .c = x1_sub_x3, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = -1, + .q_c = 0, + }); + const auto lambda_mul_x1_sub_x3_v = lambda_v * x1_sub_x3_v; + const auto lambda_mul_x1_sub_x3 = this->add_variable(lambda_mul_x1_sub_x3_v); + create_poly_gate({ + .a = lambda, + .b = x1_sub_x3, + .c = lambda_mul_x1_sub_x3, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + create_poly_gate({ + .a = lambda_mul_x1_sub_x3, + .b = in.y1, + .c = in.y3, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = -1, + .q_c = 0, + }); +} + +/** + * @brief Create a gate where we validate an elliptic curve point doubling + * (x1, y1) * 2 = (x3, y3) + * @tparam FF + * @param in + */ +template void StandardCircuitBuilder_::create_ecc_dbl_gate(const ecc_dbl_gate_& in) +{ + const auto x1 = this->get_variable(in.x1); + const auto x3 = this->get_variable(in.x3); + const auto y1 = this->get_variable(in.y1); + + // lambda = 3x^2 / 2y + const auto three_x1_sqr_v = x1 * x1 * 3; + const auto three_x1_sqr = this->add_variable(three_x1_sqr_v); + create_poly_gate({ + .a = in.x1, + .b = in.x1, + .c = three_x1_sqr, + .q_m = 3, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + const auto lambda_v = three_x1_sqr_v / (y1 + y1); + const auto lambda = this->add_variable(lambda_v); + create_poly_gate({ + .a = lambda, + .b = in.y1, + .c = three_x1_sqr, + .q_m = 2, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + + // lambda * lambda - x2 - x1 = 0 + const auto lambda_sqr_v = lambda_v * lambda_v; + const auto lambda_sqr = this->add_variable(lambda_sqr_v); + create_poly_gate({ + .a = lambda, + .b = lambda, + .c = lambda_sqr, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + create_poly_gate({ + .a = lambda_sqr, + .b = in.x1, + .c = this->zero_idx, + .q_m = 0, + .q_l = 1, + .q_r = -2, + .q_o = 0, + .q_c = 0, + }); + + // lambda * (x1 - x3) - y1 = 0 + const auto x1_sub_x3_v = x1 - x3; + const auto x1_sub_x3 = this->add_variable(x1_sub_x3_v); + create_poly_gate({ + .a = in.x1, + .b = in.x3, + .c = x1_sub_x3, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = -1, + .q_c = 0, + }); + const auto lambda_mul_x1_sub_x3_v = lambda_v * x1_sub_x3_v; + const auto lambda_mul_x1_sub_x3 = this->add_variable(lambda_mul_x1_sub_x3_v); + create_poly_gate({ + .a = lambda, + .b = x1_sub_x3, + .c = lambda_mul_x1_sub_x3, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + create_poly_gate({ + .a = lambda_mul_x1_sub_x3, + .b = in.y1, + .c = in.y3, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = -1, + .q_c = 0, + }); +} + template std::vector StandardCircuitBuilder_::decompose_into_base4_accumulators(const uint32_t witness_index, const size_t num_bits, diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/standard_circuit_builder.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/standard_circuit_builder.hpp index 2c023001609..dbd2838092e 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/standard_circuit_builder.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/standard_circuit_builder.hpp @@ -83,6 +83,8 @@ template class StandardCircuitBuilder_ : public CircuitBuilderBase void create_fixed_group_add_gate_with_init(const fixed_group_add_quad_& in, const fixed_group_init_quad_& init); void create_fixed_group_add_gate_final(const add_quad_& in); + void create_ecc_add_gate(const ecc_add_gate_& in) override; + void create_ecc_dbl_gate(const ecc_dbl_gate_& in) override; fixed_group_add_quad_ previous_add_quad; diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/turbo_circuit_builder.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/turbo_circuit_builder.cpp index ba7a5d213ce..50f1c622794 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/turbo_circuit_builder.cpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/turbo_circuit_builder.cpp @@ -372,6 +372,236 @@ template void TurboCircuitBuilder_::fix_witness(const uint32_t ++this->num_gates; } +/** + * @brief Create a gate where we validate an elliptic curve point addition + * (x1, y1) + (x2, y2) = (x3, y3) + * N.B. uses incomplete addition formula. Use with caution + * @tparam FF + * @param in + */ +template void TurboCircuitBuilder_::create_ecc_add_gate(const ecc_add_gate_& in) +{ + const auto sign_coefficient = in.sign_coefficient; + const auto x1 = this->get_variable(in.x1); + const auto x2 = this->get_variable(in.x2); + const auto x3 = this->get_variable(in.x3); + const auto y1 = this->get_variable(in.y1); + const auto y2 = sign_coefficient * this->get_variable(in.y2); + + bool collision = x2 == x1; + if (collision) { + this->failure("create_ecc_add_gate incomplete formula collision"); + } + const auto lambda_v = collision ? 0 : (y2 - y1) / (x2 - x1); + const auto lambda = this->add_variable(lambda_v); + + // (x2 - x1) * lambda - y2 + y1 = 0 + const auto x2_minus_x1_v = x2 - x1; + const auto x2_minus_x1 = this->add_variable(x2_minus_x1_v); + create_poly_gate({ + .a = in.x2, + .b = in.x1, + .c = x2_minus_x1, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = -1, + .q_c = 0, + }); + const auto t2_v = lambda_v * x2_minus_x1_v; + const auto t2 = this->add_variable(t2_v); + create_poly_gate({ + .a = lambda, + .b = x2_minus_x1, + .c = t2, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + create_poly_gate({ + .a = t2, + .b = in.y2, + .c = in.y1, + .q_m = 0, + .q_l = 1, + .q_r = -sign_coefficient, + .q_o = 1, + .q_c = 0, + }); + + // lambda * lambda - x2 - x1 = x3 + const auto x2_plus_x1_v = x2 + x1; + const auto x2_plus_x1 = this->add_variable(x2_plus_x1_v); + create_poly_gate({ + .a = in.x2, + .b = in.x1, + .c = x2_plus_x1, + .q_m = 0, + .q_l = 1, + .q_r = 1, + .q_o = -1, + .q_c = 0, + }); + const auto lambda_sqr_v = lambda_v * lambda_v; + const auto lambda_sqr = this->add_variable(lambda_sqr_v); + create_poly_gate({ + .a = lambda, + .b = lambda, + .c = lambda_sqr, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + create_poly_gate({ + .a = lambda_sqr, + .b = x2_plus_x1, + .c = in.x3, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = -1, + .q_c = 0, + }); + + // lambda * (x1 - x3) - y1 - y3 = 0 + const auto x1_sub_x3_v = x1 - x3; + const auto x1_sub_x3 = this->add_variable(x1_sub_x3_v); + create_poly_gate({ + .a = in.x1, + .b = in.x3, + .c = x1_sub_x3, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = -1, + .q_c = 0, + }); + const auto lambda_mul_x1_sub_x3_v = lambda_v * x1_sub_x3_v; + const auto lambda_mul_x1_sub_x3 = this->add_variable(lambda_mul_x1_sub_x3_v); + create_poly_gate({ + .a = lambda, + .b = x1_sub_x3, + .c = lambda_mul_x1_sub_x3, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + create_poly_gate({ + .a = lambda_mul_x1_sub_x3, + .b = in.y1, + .c = in.y3, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = -1, + .q_c = 0, + }); +} + +/** + * @brief Create a gate where we validate an elliptic curve point doubling + * (x1, y1) * 2 = (x3, y3) + * @tparam FF + * @param in + */ +template void TurboCircuitBuilder_::create_ecc_dbl_gate(const ecc_dbl_gate_& in) +{ + const auto x1 = this->get_variable(in.x1); + const auto x3 = this->get_variable(in.x3); + const auto y1 = this->get_variable(in.y1); + + // lambda = 3x^2 / 2y + const auto three_x1_sqr_v = x1 * x1 * 3; + const auto three_x1_sqr = this->add_variable(three_x1_sqr_v); + create_poly_gate({ + .a = in.x1, + .b = in.x1, + .c = three_x1_sqr, + .q_m = 3, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + const auto lambda_v = three_x1_sqr_v / (y1 + y1); + const auto lambda = this->add_variable(lambda_v); + create_poly_gate({ + .a = lambda, + .b = in.y1, + .c = three_x1_sqr, + .q_m = 2, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + + // lambda * lambda - x2 - x1 = 0 + const auto lambda_sqr_v = lambda_v * lambda_v; + const auto lambda_sqr = this->add_variable(lambda_sqr_v); + create_poly_gate({ + .a = lambda, + .b = lambda, + .c = lambda_sqr, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + create_poly_gate({ + .a = lambda_sqr, + .b = in.x1, + .c = this->zero_idx, + .q_m = 0, + .q_l = 1, + .q_r = -2, + .q_o = 0, + .q_c = 0, + }); + + // lambda * (x1 - x3) - y1 = 0 + const auto x1_sub_x3_v = x1 - x3; + const auto x1_sub_x3 = this->add_variable(x1_sub_x3_v); + create_poly_gate({ + .a = in.x1, + .b = in.x3, + .c = x1_sub_x3, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = -1, + .q_c = 0, + }); + const auto lambda_mul_x1_sub_x3_v = lambda_v * x1_sub_x3_v; + const auto lambda_mul_x1_sub_x3 = this->add_variable(lambda_mul_x1_sub_x3_v); + create_poly_gate({ + .a = lambda, + .b = x1_sub_x3, + .c = lambda_mul_x1_sub_x3, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + create_poly_gate({ + .a = lambda_mul_x1_sub_x3, + .b = in.y1, + .c = in.y3, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = -1, + .q_c = 0, + }); +} /** * Create a constraint placing the witness in 2^{num_bits} range. * diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/turbo_circuit_builder.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/turbo_circuit_builder.hpp index 7ab81feef2d..ca2247b3fb6 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/turbo_circuit_builder.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/turbo_circuit_builder.hpp @@ -54,20 +54,23 @@ template class TurboCircuitBuilder_ : public CircuitBuilderBase& in); + void create_add_gate(const add_triple_& in) override; void create_big_add_gate(const add_quad_& in); void create_big_add_gate_with_bit_extraction(const add_quad_& in); void create_big_mul_gate(const mul_quad_& in); void create_balanced_add_gate(const add_quad_& in); - void create_mul_gate(const mul_triple_& in); - void create_bool_gate(const uint32_t a); - void create_poly_gate(const poly_triple_& in); + void create_mul_gate(const mul_triple_& in) override; + void create_bool_gate(const uint32_t a) override; + void create_poly_gate(const poly_triple_& in) override; void create_fixed_group_add_gate(const fixed_group_add_quad_& in); void create_fixed_group_add_gate_with_init(const fixed_group_add_quad_& in, const fixed_group_init_quad_& init); void create_fixed_group_add_gate_final(const add_quad_& in); + void create_ecc_add_gate(const ecc_add_gate_& in) override; + void create_ecc_dbl_gate(const ecc_dbl_gate_& in) override; + void fix_witness(const uint32_t witness_index, const FF& witness_value); FF arithmetic_gate_evaluation(const size_t index, const FF alpha_base); @@ -100,7 +103,7 @@ template class TurboCircuitBuilder_ : public CircuitBuilderBase void UltraCircuitBuilder_::create_ecc_add_gate(const ++this->num_gates; } +/** + * @brief Create a gate where we validate an elliptic curve point doubling + * (x1, y1) * 2 = (x3, y3) + * @tparam FF + * @param in + */ +template void UltraCircuitBuilder_::create_ecc_dbl_gate(const ecc_dbl_gate_& in) +{ + const auto x1 = this->get_variable(in.x1); + const auto x3 = this->get_variable(in.x3); + const auto y1 = this->get_variable(in.y1); + + // lambda = 3x^2 / 2y + const auto three_x1_sqr_v = x1 * x1 * 3; + const auto three_x1_sqr = this->add_variable(three_x1_sqr_v); + create_poly_gate({ + .a = in.x1, + .b = in.x1, + .c = three_x1_sqr, + .q_m = 3, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + const auto lambda_v = three_x1_sqr_v / (y1 + y1); + const auto lambda = this->add_variable(lambda_v); + create_poly_gate({ + .a = lambda, + .b = in.y1, + .c = three_x1_sqr, + .q_m = 2, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + + // lambda * lambda - x2 - x1 = 0 + const auto lambda_sqr_v = lambda_v * lambda_v; + const auto lambda_sqr = this->add_variable(lambda_sqr_v); + create_poly_gate({ + .a = lambda, + .b = lambda, + .c = lambda_sqr, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + create_poly_gate({ + .a = lambda_sqr, + .b = in.x1, + .c = this->zero_idx, + .q_m = 0, + .q_l = 1, + .q_r = -2, + .q_o = 0, + .q_c = 0, + }); + + // lambda * (x1 - x3) - y1 = 0 + const auto x1_sub_x3_v = x1 - x3; + const auto x1_sub_x3 = this->add_variable(x1_sub_x3_v); + create_poly_gate({ + .a = in.x1, + .b = in.x3, + .c = x1_sub_x3, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = -1, + .q_c = 0, + }); + const auto lambda_mul_x1_sub_x3_v = lambda_v * x1_sub_x3_v; + const auto lambda_mul_x1_sub_x3 = this->add_variable(lambda_mul_x1_sub_x3_v); + create_poly_gate({ + .a = lambda, + .b = x1_sub_x3, + .c = lambda_mul_x1_sub_x3, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }); + create_poly_gate({ + .a = lambda_mul_x1_sub_x3, + .b = in.y1, + .c = in.y3, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = -1, + .q_c = 0, + }); +} + /** * @brief Add a gate equating a particular witness to a constant, fixing it the value * diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp index 2ff9f0eb70a..c81ba1f2734 100644 --- a/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp @@ -654,7 +654,8 @@ template class UltraCircuitBuilder_ : public CircuitBuilderBase& in) override; void create_bool_gate(const uint32_t a) override; void create_poly_gate(const poly_triple_& in) override; - void create_ecc_add_gate(const ecc_add_gate_& in); + void create_ecc_add_gate(const ecc_add_gate_& in) override; + void create_ecc_dbl_gate(const ecc_dbl_gate_& in) override; void fix_witness(const uint32_t witness_index, const FF& witness_value); diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.cpp new file mode 100644 index 00000000000..6aaed689233 --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.cpp @@ -0,0 +1,291 @@ +#include "../field/field.hpp" +#include "barretenberg/crypto/pedersen_commitment/pedersen.hpp" +#include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" + +#include "../../hash/pedersen/pedersen.hpp" +#include "../../hash/pedersen/pedersen_gates.hpp" + +#include "./cycle_group.hpp" +namespace proof_system::plonk::stdlib { + +template Composer* cycle_group::get_context(const cycle_group& other) +{ + if (get_context() != nullptr) { + return get_context(); + } + if (other.get_context() != nullptr) { + return other.get_context(); + } + return nullptr; +} + +/** + * @brief Evaluates a doubling + * + * @tparam Composer + * @return cycle_group + */ +template cycle_group cycle_group::dbl() +{ + auto context = get_context(); + + auto p1 = get_value(); + affine_element p3(element(p1).dbl()); + cycle_group result = cycle_group::from_witness(context, p3); + + proof_system::ecc_dbl_gate_ dbl_gate{ + .x1 = x.get_witness_index(), + .y1 = y.get_witness_index(), + .x3 = result.x.get_witness_index(), + .y3 = result.y.get_witness_index(), + }; + + context->create_ecc_dbl_gate(dbl_gate); + return result; +} + +/** + * @brief Will evaluate ECC point addition over `*this` and `other`. + * Incomplete addition formula edge cases are *NOT* checked! + * Only use this method if you know the x-coordinates of the operands cannot collide + * + * @tparam Composer + * @param other + * @return cycle_group + */ +template cycle_group cycle_group::unconditional_add(const cycle_group& other) +{ + auto context = get_context(other); + + const bool lhs_constant = is_constant(); + const bool rhs_constant = other.is_constant(); + if (lhs_constant && !rhs_constant) { + auto lhs = cycle_group::from_witness(context, get_value()); + return lhs.unconditional_add(other); + } + if (!lhs_constant && rhs_constant) { + auto rhs = cycle_group::from_witness(context, other.get_value()); + return unconditional_add(rhs); + } + + const auto p1 = get_value(); + const auto p2 = other.get_value(); + affine_element p3(element(p1) + element(p2)); + if (lhs_constant && rhs_constant) { + return cycle_group(p3); + } + cycle_group result = cycle_group::from_witness(context, p3); + + proof_system::ecc_add_gate_ add_gate{ + .x1 = x.get_witness_index(), + .y1 = y.get_witness_index(), + .x2 = other.x.get_witness_index(), + .y2 = other.y.get_witness_index(), + .x3 = result.x.get_witness_index(), + .y3 = result.y.get_witness_index(), + .endomorphism_coefficient = 1, + .sign_coefficient = 1, + }; + context->create_ecc_add_gate(add_gate); + + return result; +} + +/** + * @brief will evaluate ECC point subtraction over `*this` and `other`. + * Incomplete addition formula edge cases are *NOT* checked! + * Only use this method if you know the x-coordinates of the operands cannot collide + * + * @tparam Composer + * @param other + * @return cycle_group + */ +template +cycle_group cycle_group::unconditional_subtract(const cycle_group& other) +{ + auto context = get_context(other); + + const bool lhs_constant = is_constant(); + const bool rhs_constant = other.is_constant(); + + if (lhs_constant && !rhs_constant) { + auto lhs = cycle_group::from_witness(context, get_value()); + return lhs.unconditional_subtract(other); + } + if (!lhs_constant && rhs_constant) { + auto rhs = cycle_group::from_witness(context, other.get_value()); + return unconditional_subtract(rhs); + } + auto p1 = get_value(); + auto p2 = other.get_value(); + affine_element p3(element(p1) - element(p2)); + if (lhs_constant && rhs_constant) { + return cycle_group(p3); + } + cycle_group result = cycle_group::from_witness(context, p3); + + proof_system::ecc_add_gate_ add_gate{ + .x1 = x.get_witness_index(), + .y1 = y.get_witness_index(), + .x2 = other.x.get_witness_index(), + .y2 = other.y.get_witness_index(), + .x3 = result.x.get_witness_index(), + .y3 = result.y.get_witness_index(), + .endomorphism_coefficient = 1, + .sign_coefficient = -1, + }; + context->create_ecc_add_gate(add_gate); + + return result; +} + +/** + * @brief Will evaluate ECC point addition over `*this` and `other`. + * Uses incomplete addition formula + * If incomplete addition formula edge cases are triggered (x-coordinates of operands collide), + * the constraints produced by this method will be unsatisfiable. + * Useful when an honest prover will not produce a point collision with overwhelming probability, + * but a cheating prover will be able to. + * + * @tparam Composer + * @param other + * @return cycle_group + */ +template +cycle_group cycle_group::constrained_unconditional_add(const cycle_group& other) +{ + field_t x_delta = x - other.x; + x_delta.assert_is_not_zero("cycle_group::constrained_unconditional_add, x-coordinate collision"); + return unconditional_add(other); +} + +/** + * @brief Will evaluate ECC point subtraction over `*this` and `other`. + * Uses incomplete addition formula + * If incomplete addition formula edge cases are triggered (x-coordinates of operands collide), + * the constraints produced by this method will be unsatisfiable. + * Useful when an honest prover will not produce a point collision with overwhelming probability, + * but a cheating prover will be able to. + * + * @tparam Composer + * @param other + * @return cycle_group + */ +template +cycle_group cycle_group::constrained_unconditional_subtract(const cycle_group& other) +{ + field_t x_delta = x - other.x; + x_delta.assert_is_not_zero("cycle_group::constrained_unconditional_subtract, x-coordinate collision"); + return unconditional_subtract(other); +} + +/** + * @brief Will evaluate ECC point addition over `*this` and `other`. + * This method uses complete addition i.e. is compatible with edge cases. + * Method is expensive due to needing to evaluate both an addition, a doubling, + * plus conditional logic to handle points at infinity. + * + * @tparam Composer + * @param other + * @return cycle_group + */ +template cycle_group cycle_group::operator+(const cycle_group& other) +{ + + Composer* context = get_context(other); + auto add_result = unconditional_add(other); + auto dbl_result = dbl(); + + // dbl if x_match, y_match + // infinity if x_match, !y_match + const bool_t x_coordinates_match = (x == other.x); + const bool_t y_coordinates_match = (y == other.y); + const bool_t double_predicate = (x_coordinates_match && y_coordinates_match).normalize(); + const bool_t infinity_predicate = (x_coordinates_match && !y_coordinates_match).normalize(); + cycle_group result(context); + result.x = field_t::conditional_assign(double_predicate, dbl_result.x, add_result.x); + result.y = field_t::conditional_assign(double_predicate, dbl_result.y, add_result.y); + + const bool_t lhs_infinity = is_infinity; + const bool_t rhs_infinity = other.is_infinity; + // if lhs infinity, return rhs + result.x = field_t::conditional_assign(lhs_infinity, other.x, result.x); + result.y = field_t::conditional_assign(lhs_infinity, other.y, result.y); + + // if rhs infinity, return lhs + result.x = field_t::conditional_assign(rhs_infinity, x, result.x); + result.y = field_t::conditional_assign(rhs_infinity, y, result.y); + + // is result point at infinity? + // yes = infinity_predicate && !lhs_infinity && !rhs_infinity + // yes = lhs_infinity && rhs_infinity + // n.b. can likely optimise this + bool_t result_is_infinity = infinity_predicate && (!lhs_infinity && !rhs_infinity); + result_is_infinity = result_is_infinity || (lhs_infinity && rhs_infinity); + result.is_infinity = result_is_infinity; + return result; +} + +/** + * @brief Will evaluate ECC point subtraction over `*this` and `other`. + * This method uses complete addition i.e. is compatible with edge cases. + * Method is expensive due to needing to evaluate both an addition, a doubling, + * plus conditional logic to handle points at infinity. + * + * @tparam Composer + * @param other + * @return cycle_group + */ +template cycle_group cycle_group::operator-(const cycle_group& other) +{ + + Composer* context = get_context(other); + auto add_result = unconditional_subtract(other); + auto dbl_result = dbl(); + + // dbl if x_match, !y_match + // infinity if x_match, y_match + const bool_t x_coordinates_match = (x == other.x); + const bool_t y_coordinates_match = (y == other.y); + const bool_t double_predicate = (x_coordinates_match && !y_coordinates_match).normalize(); + const bool_t infinity_predicate = (x_coordinates_match && y_coordinates_match).normalize(); + cycle_group result(context); + result.x = field_t::conditional_assign(double_predicate, dbl_result.x, add_result.x); + result.y = field_t::conditional_assign(double_predicate, dbl_result.y, add_result.y); + + const bool_t lhs_infinity = is_infinity; + const bool_t rhs_infinity = other.is_infinity; + // if lhs infinity, return -rhs + result.x = field_t::conditional_assign(lhs_infinity, other.x, result.x); + result.y = field_t::conditional_assign(lhs_infinity, (-other.y).normalize(), result.y); + + // if rhs infinity, return lhs + result.x = field_t::conditional_assign(rhs_infinity, x, result.x); + result.y = field_t::conditional_assign(rhs_infinity, y, result.y); + + // is result point at infinity? + // yes = infinity_predicate && !lhs_infinity && !rhs_infinity + // yes = lhs_infinity && rhs_infinity + // n.b. can likely optimise this + bool_t result_is_infinity = infinity_predicate && (!lhs_infinity && !rhs_infinity); + result_is_infinity = result_is_infinity || (lhs_infinity && rhs_infinity); + result.is_infinity = result_is_infinity; + + return result; +} + +template cycle_group& cycle_group::operator+=(const cycle_group& other) +{ + *this = *this + other; + return *this; +} + +template cycle_group& cycle_group::operator-=(const cycle_group& other) +{ + *this = *this - other; + return *this; +} + +INSTANTIATE_STDLIB_TYPE(cycle_group); + +} // namespace proof_system::plonk::stdlib diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.hpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.hpp new file mode 100644 index 00000000000..d5fc6518aba --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.hpp @@ -0,0 +1,130 @@ +#pragma once + +#include "../field/field.hpp" +#include "barretenberg/crypto/pedersen_commitment/pedersen.hpp" +#include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" + +#include "../../hash/pedersen/pedersen.hpp" +#include "../../hash/pedersen/pedersen_gates.hpp" +#include + +namespace proof_system::plonk::stdlib { + +using namespace barretenberg; +using namespace crypto::generators; + +/** + * @brief cycle_group represents a group element of the proving system's embedded curve + * i.e. a curve with a cofactor 1 defined over a field equal to the circuit's native field Composer::FF + * + * (todo @zac-williamson) once the pedersen refactor project is finished, this class will supercede + * `stdlib::group` + * + * @tparam Composer + */ +template class cycle_group { + public: + using field_t = field_t; + using bool_t = bool_t; + using witness_t = witness_t; + using FF = typename Composer::FF; + using G1 = typename Composer::EmbeddedCurve; + using element = typename G1::element; + using affine_element = typename G1::affine_element; + + Composer* get_context(const cycle_group& other); + + cycle_group(Composer* _context = nullptr) + : context(_context) + , x(0) + , y(0) + , is_infinity(true) + , _is_constant(true) + {} + + cycle_group(Composer* _context, field_t _x, field_t _y, bool_t _is_infinity) + : context(_context) + , x(_x.normalize()) + , y(_y.normalize()) + , is_infinity(_is_infinity) + , _is_constant(_x.is_constant() && _y.is_constant() && _is_infinity.is_constant()) + {} + + cycle_group(const FF& _x, const FF& _y, bool _is_infinity) + : context(nullptr) + , x(_x) + , y(_y) + , is_infinity(_is_infinity) + , _is_constant(true) + {} + + cycle_group(const affine_element& _in) + : context(nullptr) + , x(_in.x) + , y(_in.y) + , is_infinity(_in.is_point_at_infinity()) + , _is_constant(true) + {} + + /** + * @brief + * + * N.B. make sure _in is not the point at infinity! + * (todo: shoul we validate on curve?) + * @param _context + * @param _in + * @return cycle_group + */ + static cycle_group from_witness(Composer* _context, const affine_element& _in) + { + cycle_group result(_context); + result.x = field_t(witness_t(_context, _in.x)); + result.y = field_t(witness_t(_context, _in.y)); + result.is_infinity = false; + result._is_constant = false; + return result; + } + + Composer* get_context() const { return context; } + [[nodiscard]] bool is_constant() const { return _is_constant; } + + affine_element get_value() const + { + affine_element result(x.get_value(), y.get_value()); + if (is_infinity.get_value()) { + result.self_set_infinity(); + } + return result; + } + + cycle_group dbl(); + cycle_group unconditional_add(const cycle_group& other); + cycle_group constrained_unconditional_add(const cycle_group& other); + cycle_group operator+(const cycle_group& other); + cycle_group unconditional_subtract(const cycle_group& other); + cycle_group constrained_unconditional_subtract(const cycle_group& other); + cycle_group operator-(const cycle_group& other); + cycle_group& operator+=(const cycle_group& other); + cycle_group& operator-=(const cycle_group& other); + + static cycle_group fixed_base_batch_mul(const std::vector& scalars, + const std::vector& generator_indices); + static cycle_group variable_base_batch_mul(const std::vector& scalars, + const std::vector& base_points); + + Composer* context; + field_t x; + field_t y; + bool_t is_infinity; + bool _is_constant; +}; + +template +inline std::ostream& operator<<(std::ostream& os, cycle_group const& v) +{ + return os << v.get_value(); +} + +EXTERN_STDLIB_TYPE(cycle_group); + +} // namespace proof_system::plonk::stdlib diff --git a/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.test.cpp b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.test.cpp new file mode 100644 index 00000000000..3d25ae420c7 --- /dev/null +++ b/circuits/cpp/barretenberg/cpp/src/barretenberg/stdlib/primitives/group/cycle_group.test.cpp @@ -0,0 +1,335 @@ +#include "barretenberg/numeric/random/engine.hpp" +#include "barretenberg/stdlib/primitives/field/field.hpp" +#include "barretenberg/stdlib/primitives/group/cycle_group.hpp" +#include "barretenberg/stdlib/primitives/witness/witness.hpp" +#include + +#define STDLIB_TYPE_ALIASES \ + using Composer = TypeParam; \ + using cycle_group_ct = stdlib::cycle_group; \ + using G1 = typename Composer::EmbeddedCurve; \ + using element = typename G1::element; \ + using affine_element = typename G1::affine_element; + +namespace stdlib_cycle_group_tests { +using namespace barretenberg; +using namespace proof_system::plonk; + +namespace { +auto& engine = numeric::random::get_debug_engine(); +} + +template class CycleGroupTest : public ::testing::Test { + public: + using G1 = typename Composer::EmbeddedCurve; + using FF = typename G1::subgroup_field; + + using element = typename G1::element; + using affine_element = typename G1::affine_element; + + static constexpr size_t num_generators = 10; + static inline std::array generators{}; + + static void SetUpTestSuite() + { + for (size_t i = 0; i < num_generators; ++i) { + generators[i] = G1::one * FF::random_element(&engine); + } + }; +}; + +using CircuitTypes = ::testing:: + Types; +TYPED_TEST_SUITE(CycleGroupTest, CircuitTypes); + +TYPED_TEST(CycleGroupTest, TestDbl) +{ + STDLIB_TYPE_ALIASES; + auto composer = Composer(); + + auto lhs = TestFixture::generators[0]; + cycle_group_ct a = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct c = a.dbl(); + affine_element expected(element(lhs).dbl()); + affine_element result = c.get_value(); + EXPECT_EQ(result, expected); + + bool proof_result = composer.check_circuit(); + EXPECT_EQ(proof_result, false); +} + +TYPED_TEST(CycleGroupTest, TestUnconditionalAdd) +{ + STDLIB_TYPE_ALIASES; + auto composer = Composer(); + + auto add = + [&](const affine_element& lhs, const affine_element& rhs, const bool lhs_constant, const bool rhs_constant) { + cycle_group_ct a = lhs_constant ? cycle_group_ct(lhs) : cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct b = rhs_constant ? cycle_group_ct(rhs) : cycle_group_ct::from_witness(&composer, rhs); + cycle_group_ct c = a.unconditional_add(b); + affine_element expected(element(lhs) + element(rhs)); + affine_element result = c.get_value(); + EXPECT_EQ(result, expected); + }; + + add(TestFixture::generators[0], TestFixture::generators[1], false, false); + add(TestFixture::generators[0], TestFixture::generators[1], false, true); + add(TestFixture::generators[0], TestFixture::generators[1], true, false); + add(TestFixture::generators[0], TestFixture::generators[1], true, true); + + bool proof_result = composer.check_circuit(); + EXPECT_EQ(proof_result, true); +} + +TYPED_TEST(CycleGroupTest, TestConstrainedUnconditionalAddSucceed) +{ + STDLIB_TYPE_ALIASES; + auto composer = Composer(); + + auto lhs = TestFixture::generators[0]; + auto rhs = TestFixture::generators[1]; + + // case 1. valid unconditional add + cycle_group_ct a = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct b = cycle_group_ct::from_witness(&composer, rhs); + cycle_group_ct c = a.constrained_unconditional_add(b); + affine_element expected(element(lhs) + element(rhs)); + affine_element result = c.get_value(); + EXPECT_EQ(result, expected); + + bool proof_result = composer.check_circuit(); + EXPECT_EQ(proof_result, true); +} + +TYPED_TEST(CycleGroupTest, TestConstrainedUnconditionalAddFail) +{ + using Composer = TypeParam; + using cycle_group_ct = stdlib::cycle_group; + auto composer = Composer(); + + auto lhs = TestFixture::generators[0]; + auto rhs = -TestFixture::generators[0]; // ruh roh + + // case 2. invalid unconditional add + cycle_group_ct a = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct b = cycle_group_ct::from_witness(&composer, rhs); + a.constrained_unconditional_add(b); + + EXPECT_TRUE(composer.failed()); + + bool proof_result = composer.check_circuit(); + EXPECT_EQ(proof_result, false); +} + +TYPED_TEST(CycleGroupTest, TestAdd) +{ + STDLIB_TYPE_ALIASES; + using bool_ct = stdlib::bool_t; + using witness_ct = stdlib::witness_t; + auto composer = Composer(); + + auto lhs = TestFixture::generators[0]; + auto rhs = -TestFixture::generators[1]; + + cycle_group_ct point_at_infinity = cycle_group_ct::from_witness(&composer, rhs); + point_at_infinity.is_infinity = bool_ct(witness_ct(&composer, true)); + + // case 1. no edge-cases triggered + { + cycle_group_ct a = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct b = cycle_group_ct::from_witness(&composer, rhs); + cycle_group_ct c = a + b; + affine_element expected(element(lhs) + element(rhs)); + affine_element result = c.get_value(); + EXPECT_EQ(result, expected); + } + + // case 2. lhs is point at infinity + { + cycle_group_ct a = point_at_infinity; + cycle_group_ct b = cycle_group_ct::from_witness(&composer, rhs); + cycle_group_ct c = a + b; + affine_element result = c.get_value(); + EXPECT_EQ(result, rhs); + } + + // case 3. rhs is point at infinity + { + cycle_group_ct a = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct b = point_at_infinity; + cycle_group_ct c = a + b; + affine_element result = c.get_value(); + EXPECT_EQ(result, lhs); + } + + // case 4. both points are at infinity + { + cycle_group_ct a = point_at_infinity; + cycle_group_ct b = point_at_infinity; + cycle_group_ct c = a + b; + EXPECT_TRUE(c.is_infinity.get_value()); + EXPECT_TRUE(c.get_value().is_point_at_infinity()); + } + + // case 5. lhs = -rhs + { + cycle_group_ct a = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct b = cycle_group_ct::from_witness(&composer, -lhs); + cycle_group_ct c = a + b; + EXPECT_TRUE(c.is_infinity.get_value()); + EXPECT_TRUE(c.get_value().is_point_at_infinity()); + } + + // case 6. lhs = rhs + { + cycle_group_ct a = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct b = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct c = a + b; + affine_element expected((element(lhs)).dbl()); + affine_element result = c.get_value(); + EXPECT_EQ(result, expected); + } + + bool proof_result = composer.check_circuit(); + EXPECT_EQ(proof_result, false); +} + +TYPED_TEST(CycleGroupTest, TestUnconditionalSubtract) +{ + STDLIB_TYPE_ALIASES; + auto composer = Composer(); + + auto add = + [&](const affine_element& lhs, const affine_element& rhs, const bool lhs_constant, const bool rhs_constant) { + cycle_group_ct a = lhs_constant ? cycle_group_ct(lhs) : cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct b = rhs_constant ? cycle_group_ct(rhs) : cycle_group_ct::from_witness(&composer, rhs); + cycle_group_ct c = a.unconditional_subtract(b); + affine_element expected(element(lhs) - element(rhs)); + affine_element result = c.get_value(); + EXPECT_EQ(result, expected); + }; + + add(TestFixture::generators[0], TestFixture::generators[1], false, false); + add(TestFixture::generators[0], TestFixture::generators[1], false, true); + add(TestFixture::generators[0], TestFixture::generators[1], true, false); + add(TestFixture::generators[0], TestFixture::generators[1], true, true); + + bool proof_result = composer.check_circuit(); + EXPECT_EQ(proof_result, true); +} + +TYPED_TEST(CycleGroupTest, TestConstrainedUnconditionalSubtractSucceed) +{ + STDLIB_TYPE_ALIASES; + auto composer = Composer(); + + auto lhs = TestFixture::generators[0]; + auto rhs = TestFixture::generators[1]; + + // case 1. valid unconditional add + cycle_group_ct a = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct b = cycle_group_ct::from_witness(&composer, rhs); + cycle_group_ct c = a.constrained_unconditional_subtract(b); + affine_element expected(element(lhs) - element(rhs)); + affine_element result = c.get_value(); + EXPECT_EQ(result, expected); + + bool proof_result = composer.check_circuit(); + EXPECT_EQ(proof_result, true); +} + +TYPED_TEST(CycleGroupTest, TestConstrainedUnconditionalSubtractFail) +{ + using Composer = TypeParam; + using cycle_group_ct = stdlib::cycle_group; + auto composer = Composer(); + + auto lhs = TestFixture::generators[0]; + auto rhs = -TestFixture::generators[0]; // ruh roh + + // case 2. invalid unconditional add + cycle_group_ct a = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct b = cycle_group_ct::from_witness(&composer, rhs); + a.constrained_unconditional_subtract(b); + + EXPECT_TRUE(composer.failed()); + + bool proof_result = composer.check_circuit(); + EXPECT_EQ(proof_result, false); +} + +TYPED_TEST(CycleGroupTest, TestSubtract) +{ + STDLIB_TYPE_ALIASES; + using bool_ct = stdlib::bool_t; + using witness_ct = stdlib::witness_t; + auto composer = Composer(); + + auto lhs = TestFixture::generators[0]; + auto rhs = -TestFixture::generators[1]; + + cycle_group_ct point_at_infinity = cycle_group_ct::from_witness(&composer, rhs); + point_at_infinity.is_infinity = bool_ct(witness_ct(&composer, true)); + + // case 1. no edge-cases triggered + { + cycle_group_ct a = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct b = cycle_group_ct::from_witness(&composer, rhs); + cycle_group_ct c = a - b; + affine_element expected(element(lhs) - element(rhs)); + affine_element result = c.get_value(); + EXPECT_EQ(result, expected); + } + + // case 2. lhs is point at infinity + { + cycle_group_ct a = point_at_infinity; + cycle_group_ct b = cycle_group_ct::from_witness(&composer, rhs); + cycle_group_ct c = a - b; + affine_element result = c.get_value(); + EXPECT_EQ(result, -rhs); + } + + // case 3. rhs is point at infinity + { + cycle_group_ct a = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct b = point_at_infinity; + cycle_group_ct c = a - b; + affine_element result = c.get_value(); + EXPECT_EQ(result, lhs); + } + + // case 4. both points are at infinity + { + cycle_group_ct a = point_at_infinity; + cycle_group_ct b = point_at_infinity; + cycle_group_ct c = a - b; + EXPECT_TRUE(c.is_infinity.get_value()); + EXPECT_TRUE(c.get_value().is_point_at_infinity()); + } + + // case 5. lhs = -rhs + { + cycle_group_ct a = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct b = cycle_group_ct::from_witness(&composer, -lhs); + cycle_group_ct c = a - b; + affine_element expected((element(lhs)).dbl()); + affine_element result = c.get_value(); + EXPECT_EQ(result, expected); + } + + // case 6. lhs = rhs + { + cycle_group_ct a = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct b = cycle_group_ct::from_witness(&composer, lhs); + cycle_group_ct c = a - b; + EXPECT_TRUE(c.is_infinity.get_value()); + EXPECT_TRUE(c.get_value().is_point_at_infinity()); + } + + bool proof_result = composer.check_circuit(); + EXPECT_EQ(proof_result, false); +} + +} // namespace stdlib_cycle_group_tests \ No newline at end of file