diff --git a/src/lib/pubkey/ec_group/ec_inner_data.cpp b/src/lib/pubkey/ec_group/ec_inner_data.cpp index 1ec53cf9519..f46e9c8d925 100644 --- a/src/lib/pubkey/ec_group/ec_inner_data.cpp +++ b/src/lib/pubkey/ec_group/ec_inner_data.cpp @@ -7,10 +7,8 @@ #include #include - -#if defined(BOTAN_HAS_EC_HASH_TO_CURVE) - #include -#endif +#include +#include namespace Botan { @@ -38,7 +36,13 @@ EC_Group_Data::EC_Group_Data(const BigInt& p, m_a_is_minus_3(a == p - 3), m_a_is_zero(a.is_zero()), m_has_cofactor(m_cofactor != 1), - m_source(source) {} + m_source(source) { + if(m_oid.has_value()) { + if(const auto id = PCurve::PrimeOrderCurveId::from_oid(m_oid)) { + m_pcurve = PCurve::PrimeOrderCurve::from_id(*id); + } + } +} bool EC_Group_Data::params_match(const BigInt& p, const BigInt& a, @@ -56,63 +60,119 @@ bool EC_Group_Data::params_match(const EC_Group_Data& other) const { } std::unique_ptr EC_Group_Data::scalar_from_bytes_with_trunc(std::span bytes) const { - auto bn = BigInt::from_bytes_with_max_bits(bytes.data(), bytes.size(), m_order_bits); - return std::make_unique(shared_from_this(), mod_order(bn)); + if(m_pcurve) { + return std::make_unique(shared_from_this(), m_pcurve->scalar_from_bits_with_trunc(bytes)); + } else { + auto bn = BigInt::from_bytes_with_max_bits(bytes.data(), bytes.size(), m_order_bits); + return std::make_unique(shared_from_this(), mod_order(bn)); + } } std::unique_ptr EC_Group_Data::scalar_from_bytes_mod_order(std::span bytes) const { BOTAN_ARG_CHECK(bytes.size() <= 2 * order_bytes(), "Input too large"); - return std::make_unique(shared_from_this(), mod_order(BigInt(bytes))); + + if(m_pcurve) { + if(auto s = m_pcurve->scalar_from_wide_bytes(bytes)) { + return std::make_unique(shared_from_this(), std::move(*s)); + } else { + throw Invalid_Argument("Failed to complete reduction of scalar bytes"); + } + } else { + return std::make_unique(shared_from_this(), mod_order(BigInt(bytes))); + } } std::unique_ptr EC_Group_Data::scalar_random(RandomNumberGenerator& rng) const { - return std::make_unique(shared_from_this(), BigInt::random_integer(rng, BigInt::one(), m_order)); + if(m_pcurve) { + return std::make_unique(shared_from_this(), m_pcurve->random_scalar(rng)); + } else { + return std::make_unique(shared_from_this(), + BigInt::random_integer(rng, BigInt::one(), m_order)); + } } std::unique_ptr EC_Group_Data::scalar_zero() const { - return std::make_unique(shared_from_this(), BigInt::zero()); + if(m_pcurve) { + return std::make_unique(shared_from_this(), m_pcurve->scalar_zero()); + } else { + return std::make_unique(shared_from_this(), BigInt::zero()); + } } std::unique_ptr EC_Group_Data::scalar_one() const { - return std::make_unique(shared_from_this(), BigInt::one()); + if(m_pcurve) { + return std::make_unique(shared_from_this(), m_pcurve->scalar_one()); + } else { + return std::make_unique(shared_from_this(), BigInt::one()); + } } std::unique_ptr EC_Group_Data::scalar_from_bigint(const BigInt& bn) const { - // Assumed to have been already checked as in range - return std::make_unique(shared_from_this(), bn); + BOTAN_ARG_CHECK(bn >= 0 && bn < m_order, "EC_Scalar BigInt out of range"); + + if(m_pcurve) { + std::vector bytes(m_order_bytes); + bn.serialize_to(bytes); + return this->scalar_deserialize(bytes); + } else { + return std::make_unique(shared_from_this(), bn); + } } std::unique_ptr EC_Group_Data::gk_x_mod_order(const EC_Scalar_Data& scalar, RandomNumberGenerator& rng, std::vector& ws) const { - const auto& bn = EC_Scalar_Data_BN::checked_ref(scalar); - const auto pt = m_base_mult.mul(bn.value(), rng, m_order, ws); - - if(pt.is_zero()) { - return scalar_zero(); + if(m_pcurve) { + const auto& k = EC_Scalar_Data_PC::checked_ref(scalar); + auto gk_x_mod_order = m_pcurve->base_point_mul_x_mod_order(k.value(), rng); + return std::make_unique(shared_from_this(), gk_x_mod_order); } else { - return std::make_unique(shared_from_this(), mod_order(pt.get_affine_x())); + const auto& k = EC_Scalar_Data_BN::checked_ref(scalar); + const auto pt = m_base_mult.mul(k.value(), rng, m_order, ws); + + if(pt.is_zero()) { + return scalar_zero(); + } else { + return std::make_unique(shared_from_this(), mod_order(pt.get_affine_x())); + } } } -std::unique_ptr EC_Group_Data::scalar_deserialize(std::span bytes) { +std::unique_ptr EC_Group_Data::scalar_deserialize(std::span bytes) const { if(bytes.size() != m_order_bytes) { return nullptr; } - BigInt r(bytes.data(), bytes.size()); + if(m_pcurve) { + if(auto s = m_pcurve->deserialize_scalar(bytes)) { + return std::make_unique(shared_from_this(), *s); + } else { + return nullptr; + } + } else { + BigInt r(bytes.data(), bytes.size()); - if(r.is_zero() || r >= m_order) { - return nullptr; - } + if(r.is_zero() || r >= m_order) { + return nullptr; + } - return std::make_unique(shared_from_this(), std::move(r)); + return std::make_unique(shared_from_this(), std::move(r)); + } } std::unique_ptr EC_Group_Data::point_deserialize(std::span bytes) const { try { - auto pt = Botan::OS2ECP(bytes.data(), bytes.size(), curve()); - return std::make_unique(shared_from_this(), std::move(pt)); + if(m_pcurve) { + auto pt = m_pcurve->deserialize_point(bytes); + if(pt) { + return std::make_unique(shared_from_this(), std::move(*pt)); + } else { + return nullptr; + } + } else { + auto pt = Botan::OS2ECP(bytes.data(), bytes.size(), curve()); + return std::make_unique(shared_from_this(), std::move(pt)); + } } catch(...) { return nullptr; } @@ -121,39 +181,48 @@ std::unique_ptr EC_Group_Data::point_deserialize(std::span< std::unique_ptr EC_Group_Data::point_hash_to_curve_ro(std::string_view hash_fn, std::span input, std::span domain_sep) const { -#if defined(BOTAN_HAS_EC_HASH_TO_CURVE) - auto pt = hash_to_curve_sswu(*this, hash_fn, input, domain_sep, true); - return std::make_unique(shared_from_this(), std::move(pt)); -#else - BOTAN_UNUSED(hash_fn, input, domain_sep); - throw Not_Implemented("Hashing to curve not available in this build"); -#endif + if(m_pcurve) { + const auto pt = m_pcurve->hash_to_curve(hash_fn, input, domain_sep, true); + return std::make_unique(shared_from_this(), pt.to_affine()); + } else { + throw Not_Implemented("Hash to curve is not implemented for this curve"); + } } std::unique_ptr EC_Group_Data::point_hash_to_curve_nu(std::string_view hash_fn, std::span input, std::span domain_sep) const { -#if defined(BOTAN_HAS_EC_HASH_TO_CURVE) - auto pt = hash_to_curve_sswu(*this, hash_fn, input, domain_sep, false); - return std::make_unique(shared_from_this(), std::move(pt)); -#else - BOTAN_UNUSED(hash_fn, input, domain_sep); - throw Not_Implemented("Hashing to curve not available in this build"); -#endif + if(m_pcurve) { + const auto pt = m_pcurve->hash_to_curve(hash_fn, input, domain_sep, false); + return std::make_unique(shared_from_this(), pt.to_affine()); + } else { + throw Not_Implemented("Hash to curve is not implemented for this curve"); + } } std::unique_ptr EC_Group_Data::point_g_mul(const EC_Scalar_Data& scalar, RandomNumberGenerator& rng, std::vector& ws) const { - const auto& group = scalar.group(); - const auto& bn = EC_Scalar_Data_BN::checked_ref(scalar); - auto pt = group->blinded_base_point_multiply(bn.value(), rng, ws); - return std::make_unique(shared_from_this(), std::move(pt)); + if(m_pcurve) { + const auto& k = EC_Scalar_Data_PC::checked_ref(scalar); + auto pt = m_pcurve->mul_by_g(k.value(), rng).to_affine(); + return std::make_unique(shared_from_this(), std::move(pt)); + } else { + const auto& group = scalar.group(); + const auto& bn = EC_Scalar_Data_BN::checked_ref(scalar); + auto pt = group->blinded_base_point_multiply(bn.value(), rng, ws); + return std::make_unique(shared_from_this(), std::move(pt)); + } } std::unique_ptr EC_Group_Data::make_mul2_table(const EC_AffinePoint_Data& h) const { - EC_AffinePoint_Data_BN g(shared_from_this(), this->base_point()); - return std::make_unique(g, h); + if(m_pcurve) { + EC_AffinePoint_Data_PC g(shared_from_this(), m_pcurve->generator()); + return std::make_unique(g, h); + } else { + EC_AffinePoint_Data_BN g(shared_from_this(), this->base_point()); + return std::make_unique(g, h); + } } } // namespace Botan diff --git a/src/lib/pubkey/ec_group/ec_inner_data.h b/src/lib/pubkey/ec_group/ec_inner_data.h index c6462ebbaea..7766088a53c 100644 --- a/src/lib/pubkey/ec_group/ec_inner_data.h +++ b/src/lib/pubkey/ec_group/ec_inner_data.h @@ -19,6 +19,12 @@ namespace Botan { +namespace PCurve { + +class PrimeOrderCurve; + +} + class EC_Group_Data; class EC_Scalar_Data { @@ -193,7 +199,7 @@ class EC_Group_Data final : public std::enable_shared_from_this { RandomNumberGenerator& rng, std::vector& ws) const; - std::unique_ptr scalar_deserialize(std::span bytes); + std::unique_ptr scalar_deserialize(std::span bytes) const; std::unique_ptr point_deserialize(std::span bytes) const; @@ -211,7 +217,15 @@ class EC_Group_Data final : public std::enable_shared_from_this { std::unique_ptr make_mul2_table(const EC_AffinePoint_Data& pt) const; + const PCurve::PrimeOrderCurve& pcurve() const { + BOTAN_ASSERT_NONNULL(m_pcurve); + return *m_pcurve; + } + private: + // Possibly nullptr (if pcurves not available or not a standard curve) + std::shared_ptr m_pcurve; + CurveGFp m_curve; EC_Point m_base_point; diff --git a/src/lib/pubkey/ec_group/ec_inner_pc.cpp b/src/lib/pubkey/ec_group/ec_inner_pc.cpp new file mode 100644 index 00000000000..8d5491e318a --- /dev/null +++ b/src/lib/pubkey/ec_group/ec_inner_pc.cpp @@ -0,0 +1,209 @@ +/* +* (C) 2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +namespace Botan { + +namespace { + +PCurve::PrimeOrderCurve::AffinePoint deserialize_pcurve_pt(const PCurve::PrimeOrderCurve& curve, + std::span bytes) { + if(auto pt = curve.deserialize_point(bytes)) { + return *pt; + } else { + throw Decoding_Error("Invalid elliptic curve point encoding"); + } +} + +} // namespace + +const EC_Scalar_Data_PC& EC_Scalar_Data_PC::checked_ref(const EC_Scalar_Data& data) { + const auto* p = dynamic_cast(&data); + if(!p) { + throw Invalid_State("Failed conversion to EC_Scalar_Data_PC"); + } + return *p; +} + +const std::shared_ptr& EC_Scalar_Data_PC::group() const { + return m_group; +} + +size_t EC_Scalar_Data_PC::bytes() const { + return this->group()->order_bytes(); +} + +std::unique_ptr EC_Scalar_Data_PC::clone() const { + return std::make_unique(this->group(), this->value()); +} + +bool EC_Scalar_Data_PC::is_zero() const { + return this->value().is_zero(); +} + +bool EC_Scalar_Data_PC::is_eq(const EC_Scalar_Data& other) const { + return (value() == checked_ref(other).value()); +} + +void EC_Scalar_Data_PC::assign(const EC_Scalar_Data& other) { + m_v = checked_ref(other).value(); +} + +void EC_Scalar_Data_PC::square_self() { + // TODO square in place + m_v = m_v.square(); +} + +std::unique_ptr EC_Scalar_Data_PC::negate() const { + return std::make_unique(m_group, m_v.negate()); +} + +std::unique_ptr EC_Scalar_Data_PC::invert() const { + return std::make_unique(m_group, m_v.invert()); +} + +std::unique_ptr EC_Scalar_Data_PC::add(const EC_Scalar_Data& other) const { + return std::make_unique(m_group, m_v + checked_ref(other).value()); +} + +std::unique_ptr EC_Scalar_Data_PC::sub(const EC_Scalar_Data& other) const { + return std::make_unique(m_group, m_v - checked_ref(other).value()); +} + +std::unique_ptr EC_Scalar_Data_PC::mul(const EC_Scalar_Data& other) const { + return std::make_unique(m_group, m_v * checked_ref(other).value()); +} + +void EC_Scalar_Data_PC::serialize_to(std::span bytes) const { + BOTAN_ARG_CHECK(bytes.size() == m_group->order_bytes(), "Invalid output length"); + m_group->pcurve().serialize_scalar(bytes, m_v); +} + +EC_AffinePoint_Data_PC::EC_AffinePoint_Data_PC(std::shared_ptr group, + PCurve::PrimeOrderCurve::AffinePoint pt) : + m_group(std::move(group)), m_pt(std::move(pt)) { + m_bytes = m_pt.serialize>(); +} + +EC_AffinePoint_Data_PC::EC_AffinePoint_Data_PC(std::shared_ptr group, + std::span bytes) : + m_group(std::move(group)), m_pt(deserialize_pcurve_pt(m_group->pcurve(), bytes)) { + m_bytes = m_pt.serialize>(); +} + +const EC_AffinePoint_Data_PC& EC_AffinePoint_Data_PC::checked_ref(const EC_AffinePoint_Data& data) { + const auto* p = dynamic_cast(&data); + if(!p) { + throw Invalid_State("Failed conversion to EC_AffinePoint_Data_PC"); + } + return *p; +} + +std::unique_ptr EC_AffinePoint_Data_PC::clone() const { + return std::make_unique(m_group, m_pt); +} + +const std::shared_ptr& EC_AffinePoint_Data_PC::group() const { + return m_group; +} + +std::unique_ptr EC_AffinePoint_Data_PC::mul(const EC_Scalar_Data& scalar, + RandomNumberGenerator& rng, + std::vector& ws) const { + BOTAN_UNUSED(ws); + + BOTAN_ARG_CHECK(scalar.group() == m_group, "Curve mismatch"); + const auto& k = EC_Scalar_Data_PC::checked_ref(scalar).value(); + auto pt = m_group->pcurve().mul(m_pt, k, rng).to_affine(); + return std::make_unique(m_group, std::move(pt)); +} + +size_t EC_AffinePoint_Data_PC::field_element_bytes() const { + return m_group->pcurve().field_element_bytes(); +} + +void EC_AffinePoint_Data_PC::serialize_x_to(std::span bytes) const { + const size_t fe_bytes = this->field_element_bytes(); + BOTAN_ARG_CHECK(bytes.size() == fe_bytes, "Invalid output size"); + copy_mem(bytes, std::span{m_bytes}.subspan(1, fe_bytes)); +} + +void EC_AffinePoint_Data_PC::serialize_y_to(std::span bytes) const { + const size_t fe_bytes = this->field_element_bytes(); + BOTAN_ARG_CHECK(bytes.size() == fe_bytes, "Invalid output size"); + copy_mem(bytes, std::span{m_bytes}.last(fe_bytes)); +} + +void EC_AffinePoint_Data_PC::serialize_xy_to(std::span bytes) const { + const size_t fe_bytes = this->field_element_bytes(); + BOTAN_ARG_CHECK(bytes.size() == 2 * fe_bytes, "Invalid output size"); + copy_mem(bytes, std::span{m_bytes}.last(2 * fe_bytes)); +} + +void EC_AffinePoint_Data_PC::serialize_compressed_to(std::span bytes) const { + const size_t fe_bytes = this->field_element_bytes(); + BOTAN_ARG_CHECK(bytes.size() == 1 + fe_bytes, "Invalid output size"); + const bool y_is_odd = (m_bytes[m_bytes.size() - 1] & 0x01) == 0x01; + + BufferStuffer stuffer(bytes); + stuffer.append(y_is_odd ? 0x03 : 0x02); + this->serialize_x_to(stuffer.next(fe_bytes)); +} + +EC_Point EC_AffinePoint_Data_PC::to_legacy_point() const { + const size_t fe_bytes = this->field_element_bytes(); + auto x = BigInt::from_bytes(std::span{m_bytes}.subspan(1, fe_bytes)); + auto y = BigInt::from_bytes(std::span{m_bytes}.last(fe_bytes)); + + return EC_Point(m_group->curve(), x, y); +} + +void EC_AffinePoint_Data_PC::serialize_uncompressed_to(std::span bytes) const { + const size_t fe_bytes = this->field_element_bytes(); + BOTAN_ARG_CHECK(bytes.size() == 1 + 2 * fe_bytes, "Invalid output size"); + copy_mem(bytes, m_bytes); +} + +EC_Mul2Table_Data_PC::EC_Mul2Table_Data_PC(const EC_AffinePoint_Data& g, const EC_AffinePoint_Data& h) : + m_group(g.group()) { + BOTAN_ARG_CHECK(h.group() == m_group, "Curve mismatch"); + + const auto& pt_g = EC_AffinePoint_Data_PC::checked_ref(g); + const auto& pt_h = EC_AffinePoint_Data_PC::checked_ref(h); + + m_tbl = m_group->pcurve().mul2_setup(pt_g.value(), pt_h.value()); +} + +std::unique_ptr EC_Mul2Table_Data_PC::mul2_vartime(const EC_Scalar_Data& xd, + const EC_Scalar_Data& yd) const { + BOTAN_ARG_CHECK(xd.group() == m_group && yd.group() == m_group, "Curve mismatch"); + + const auto& x = EC_Scalar_Data_PC::checked_ref(xd); + const auto& y = EC_Scalar_Data_PC::checked_ref(yd); + + if(auto pt = m_group->pcurve().mul2_vartime(*m_tbl, x.value(), y.value())) { + return std::make_unique(m_group, pt->to_affine()); + } else { + return nullptr; + } +} + +std::unique_ptr EC_Mul2Table_Data_PC::mul2_vartime_x_mod_order(const EC_Scalar_Data& xd, + const EC_Scalar_Data& yd) const { + BOTAN_ARG_CHECK(xd.group() == m_group && yd.group() == m_group, "Curve mismatch"); + + const auto& x = EC_Scalar_Data_PC::checked_ref(xd); + const auto& y = EC_Scalar_Data_PC::checked_ref(yd); + + if(auto s = m_group->pcurve().mul2_vartime_x_mod_order(*m_tbl, x.value(), y.value())) { + return std::make_unique(m_group, std::move(*s)); + } else { + return nullptr; + } +} + +} // namespace Botan diff --git a/src/lib/pubkey/ec_group/ec_inner_pc.h b/src/lib/pubkey/ec_group/ec_inner_pc.h new file mode 100644 index 00000000000..59200138aa5 --- /dev/null +++ b/src/lib/pubkey/ec_group/ec_inner_pc.h @@ -0,0 +1,111 @@ +/* +* (C) 2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_EC_INNER_DATA_PC_H_ +#define BOTAN_EC_INNER_DATA_PC_H_ + +#include + +#include + +namespace Botan { + +class EC_Scalar_Data_PC final : public EC_Scalar_Data { + public: + EC_Scalar_Data_PC(std::shared_ptr group, PCurve::PrimeOrderCurve::Scalar v) : + m_group(std::move(group)), m_v(std::move(v)) {} + + static const EC_Scalar_Data_PC& checked_ref(const EC_Scalar_Data& data); + + const std::shared_ptr& group() const override; + + std::unique_ptr clone() const override; + + size_t bytes() const override; + + bool is_zero() const override; + + bool is_eq(const EC_Scalar_Data& y) const override; + + void assign(const EC_Scalar_Data& y) override; + + void square_self() override; + + std::unique_ptr negate() const override; + + std::unique_ptr invert() const override; + + std::unique_ptr add(const EC_Scalar_Data& other) const override; + + std::unique_ptr sub(const EC_Scalar_Data& other) const override; + + std::unique_ptr mul(const EC_Scalar_Data& other) const override; + + void serialize_to(std::span bytes) const override; + + const auto& value() const { return m_v; } + + private: + std::shared_ptr m_group; + PCurve::PrimeOrderCurve::Scalar m_v; +}; + +class EC_AffinePoint_Data_PC final : public EC_AffinePoint_Data { + public: + EC_AffinePoint_Data_PC(std::shared_ptr group, PCurve::PrimeOrderCurve::AffinePoint pt); + + EC_AffinePoint_Data_PC(std::shared_ptr group, std::span pt); + + static const EC_AffinePoint_Data_PC& checked_ref(const EC_AffinePoint_Data& data); + + const std::shared_ptr& group() const override; + + std::unique_ptr clone() const override; + + size_t field_element_bytes() const override; + + void serialize_x_to(std::span bytes) const override; + + void serialize_y_to(std::span bytes) const override; + + void serialize_xy_to(std::span bytes) const override; + + void serialize_compressed_to(std::span bytes) const override; + + void serialize_uncompressed_to(std::span bytes) const override; + + std::unique_ptr mul(const EC_Scalar_Data& scalar, + RandomNumberGenerator& rng, + std::vector& ws) const override; + + const PCurve::PrimeOrderCurve::AffinePoint& value() const { return m_pt; } + + EC_Point to_legacy_point() const override; + + private: + std::shared_ptr m_group; + PCurve::PrimeOrderCurve::AffinePoint m_pt; + secure_vector m_bytes; +}; + +class EC_Mul2Table_Data_PC final : public EC_Mul2Table_Data { + public: + EC_Mul2Table_Data_PC(const EC_AffinePoint_Data& g, const EC_AffinePoint_Data& h); + + std::unique_ptr mul2_vartime(const EC_Scalar_Data& x, + const EC_Scalar_Data& y) const override; + + std::unique_ptr mul2_vartime_x_mod_order(const EC_Scalar_Data& x, + const EC_Scalar_Data& y) const override; + + private: + std::shared_ptr m_group; + std::unique_ptr m_tbl; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/ec_group/info.txt b/src/lib/pubkey/ec_group/info.txt index d918f52f054..ebf7011df97 100644 --- a/src/lib/pubkey/ec_group/info.txt +++ b/src/lib/pubkey/ec_group/info.txt @@ -11,12 +11,14 @@ brief -> "Wrapper for elliptic curve groups" asn1 numbertheory +pcurves pem point_mul.h ec_inner_bn.h +ec_inner_pc.h ec_inner_data.h