diff --git a/src/lib/pubkey/ec_group/ec_apoint.cpp b/src/lib/pubkey/ec_group/ec_apoint.cpp new file mode 100644 index 00000000000..1baa53bfc36 --- /dev/null +++ b/src/lib/pubkey/ec_group/ec_apoint.cpp @@ -0,0 +1,113 @@ +/* +* (C) 2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +namespace Botan { + +EC_AffinePoint::EC_AffinePoint(std::unique_ptr point) : m_point(std::move(point)) { + BOTAN_ASSERT_NONNULL(m_point); +} + +EC_AffinePoint::EC_AffinePoint(const EC_AffinePoint& other) : m_point(other.inner().clone()) {} + +EC_AffinePoint::EC_AffinePoint(EC_AffinePoint&& other) noexcept : m_point(std::move(other.m_point)) {} + +EC_AffinePoint& EC_AffinePoint::operator=(const EC_AffinePoint& other) { + if(this != &other) { + m_point = other.inner().clone(); + } + return (*this); +} + +EC_AffinePoint& EC_AffinePoint::operator=(EC_AffinePoint&& other) noexcept { + m_point.swap(other.m_point); + return (*this); +} + +EC_AffinePoint::EC_AffinePoint(const EC_Group& group, std::span bytes) { + m_point = group._data()->point_deserialize(bytes); + if(!m_point) { + throw Decoding_Error("Failed to deserialize elliptic curve point"); + } +} + +EC_AffinePoint::EC_AffinePoint(const EC_Group& group, const EC_Point& pt) : + EC_AffinePoint(group, pt.encode(EC_Point_Format::Uncompressed)) {} + +size_t EC_AffinePoint::field_element_bytes() const { + return inner().field_element_bytes(); +} + +EC_AffinePoint EC_AffinePoint::hash_to_curve_ro(const EC_Group& group, + std::string_view hash_fn, + std::span input, + std::span domain_sep) { + auto pt = group._data()->point_hash_to_curve_ro(hash_fn, input, domain_sep); + return EC_AffinePoint(std::move(pt)); +} + +EC_AffinePoint EC_AffinePoint::hash_to_curve_nu(const EC_Group& group, + std::string_view hash_fn, + std::span input, + std::span domain_sep) { + auto pt = group._data()->point_hash_to_curve_nu(hash_fn, input, domain_sep); + return EC_AffinePoint(std::move(pt)); +} + +EC_AffinePoint::~EC_AffinePoint() = default; + +std::optional EC_AffinePoint::deserialize(const EC_Group& group, std::span bytes) { + auto pt = group._data()->point_deserialize(bytes); + return EC_AffinePoint(std::move(pt)); +} + +EC_AffinePoint EC_AffinePoint::g_mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& ws) { + auto pt = scalar._inner().group()->point_g_mul(scalar.inner(), rng, ws); + return EC_AffinePoint(std::move(pt)); +} + +EC_AffinePoint EC_AffinePoint::mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& ws) const { + return EC_AffinePoint(inner().mul(scalar._inner(), rng, ws)); +} + +void EC_AffinePoint::serialize_x_to(std::span bytes) const { + m_point->serialize_x_to(bytes); +} + +void EC_AffinePoint::serialize_y_to(std::span bytes) const { + m_point->serialize_y_to(bytes); +} + +void EC_AffinePoint::serialize_xy_to(std::span bytes) const { + m_point->serialize_xy_to(bytes); +} + +void EC_AffinePoint::serialize_compressed_to(std::span bytes) const { + m_point->serialize_compressed_to(bytes); +} + +void EC_AffinePoint::serialize_uncompressed_to(std::span bytes) const { + m_point->serialize_uncompressed_to(bytes); +} + +EC_Point EC_AffinePoint::to_legacy_point() const { + return m_point->to_legacy_point(); +} + +EC_AffinePoint EC_AffinePoint::_from_inner(std::unique_ptr inner) { + return EC_AffinePoint(std::move(inner)); +} + +const std::shared_ptr& EC_AffinePoint::_group() const { + return inner().group(); +} + +} // namespace Botan diff --git a/src/lib/pubkey/ec_group/ec_apoint.h b/src/lib/pubkey/ec_group/ec_apoint.h new file mode 100644 index 00000000000..592e06e5d4a --- /dev/null +++ b/src/lib/pubkey/ec_group/ec_apoint.h @@ -0,0 +1,168 @@ +/* +* (C) 2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_EC_APOINT_H_ +#define BOTAN_EC_APOINT_H_ + +#include +#include +#include +#include +#include +#include +#include + +namespace Botan { + +class BigInt; +class RandomNumberGenerator; +class EC_Group; +class EC_Scalar; +class EC_Point; + +class EC_Group_Data; +class EC_AffinePoint_Data; + +class BOTAN_UNSTABLE_API EC_AffinePoint final { + public: + /// Point deserialization. Returns nullopt if wrong length or not a valid point + static std::optional deserialize(const EC_Group& group, std::span bytes); + + /// Multiply by the group generator returning a complete point + /// + /// Workspace argument is transitional + static EC_AffinePoint g_mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& ws); + + /// Hash to curve (RFC 9380), random oracle variant + /// + /// Only supported for specific groups + static EC_AffinePoint hash_to_curve_ro(const EC_Group& group, + std::string_view hash_fn, + std::span input, + std::span domain_sep); + + /// Hash to curve (RFC 9380), non uniform variant + /// + /// Only supported for specific groups + static EC_AffinePoint hash_to_curve_nu(const EC_Group& group, + std::string_view hash_fn, + std::span input, + std::span domain_sep); + + /// Multiply a point by a scalar returning a complete point + /// + /// Workspace argument is transitional + EC_AffinePoint mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& ws) const; + + /// Return the number of bytes of a field element + /// + /// A point consists of two field elements, plus possibly a header + size_t field_element_bytes() const; + + /// Write the fixed length encoding of affine x coordinate + /// + /// The output span must be exactly field_element_bytes long + void serialize_x_to(std::span bytes) const; + + /// Write the fixed length encoding of affine y coordinate + /// + /// The output span must be exactly field_element_bytes long + void serialize_y_to(std::span bytes) const; + + /// Write the fixed length encoding of affine x and y coordinates + /// + /// The output span must be exactly 2*field_element_bytes long + void serialize_xy_to(std::span bytes) const; + + /// Write the fixed length SEC1 compressed encoding + /// + /// The output span must be exactly 1 + field_element_bytes long + void serialize_compressed_to(std::span bytes) const; + + /// Return the fixed length encoding of SEC1 uncompressed encoding + /// + /// The output span must be exactly 1 + 2*field_element_bytes long + void serialize_uncompressed_to(std::span bytes) const; + + /// Return the bytes of the affine x coordinate in a container + template > + T x_bytes() const { + T bytes(this->field_element_bytes()); + this->serialize_x_to(bytes); + return bytes; + } + + /// Return the bytes of the affine y coordinate in a container + template > + T y_bytes() const { + T bytes(this->field_element_bytes()); + this->serialize_y_to(bytes); + return bytes; + } + + /// Return the bytes of the affine x and y coordinates in a container + template > + T xy_bytes() const { + T bytes(2 * this->field_element_bytes()); + this->serialize_xy_to(bytes); + return bytes; + } + + /// Return the bytes of the affine x and y coordinates in a container + template > + T serialize_uncompressed() const { + T bytes(1 + 2 * this->field_element_bytes()); + this->serialize_uncompressed_to(bytes); + return bytes; + } + + /// Return the bytes of the affine x and y coordinates in a container + template > + T serialize_compressed() const { + T bytes(1 + this->field_element_bytes()); + this->serialize_compressed_to(bytes); + return bytes; + } + + EC_AffinePoint(const EC_AffinePoint& other); + EC_AffinePoint(EC_AffinePoint&& other) noexcept; + + EC_AffinePoint& operator=(const EC_AffinePoint& other); + EC_AffinePoint& operator=(EC_AffinePoint&& other) noexcept; + + EC_AffinePoint(const EC_Group& group, std::span bytes); + + /** + * Deprecated conversion + */ + EC_AffinePoint(const EC_Group& group, const EC_Point& pt); + + /** + * Deprecated conversion + */ + EC_Point to_legacy_point() const; + + ~EC_AffinePoint(); + + const EC_AffinePoint_Data& _inner() const { return inner(); } + + static EC_AffinePoint _from_inner(std::unique_ptr inner); + + const std::shared_ptr& _group() const; + + private: + friend class EC_Mul2Table; + + EC_AffinePoint(std::unique_ptr point); + + const EC_AffinePoint_Data& inner() const { return *m_point; } + + std::unique_ptr m_point; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/ec_group/ec_group.cpp b/src/lib/pubkey/ec_group/ec_group.cpp index e8c9afc960c..2d66d313705 100644 --- a/src/lib/pubkey/ec_group/ec_group.cpp +++ b/src/lib/pubkey/ec_group/ec_group.cpp @@ -2,7 +2,7 @@ * ECC Domain Parameters * * (C) 2007 Falko Strenzke, FlexSecure GmbH -* (C) 2008,2018 Jack Lloyd +* (C) 2008,2018,2024 Jack Lloyd * (C) 2018 Tobias Niemann * * Botan is released under the Simplified BSD License (see license.txt) @@ -16,132 +16,14 @@ #include #include #include +#include #include #include #include #include -#if defined(BOTAN_HAS_EC_HASH_TO_CURVE) - #include -#endif - namespace Botan { -class EC_Group_Data final { - public: - EC_Group_Data(const BigInt& p, - const BigInt& a, - const BigInt& b, - const BigInt& g_x, - const BigInt& g_y, - const BigInt& order, - const BigInt& cofactor, - const OID& oid, - EC_Group_Source source) : - m_curve(p, a, b), - m_base_point(m_curve, g_x, g_y), - m_g_x(g_x), - m_g_y(g_y), - m_order(order), - m_cofactor(cofactor), - m_mod_order(order), - m_base_mult(m_base_point, m_mod_order), - m_oid(oid), - m_p_bits(p.bits()), - m_order_bits(order.bits()), - m_a_is_minus_3(a == p - 3), - m_a_is_zero(a.is_zero()), - m_source(source) {} - - bool params_match(const BigInt& p, - const BigInt& a, - const BigInt& b, - const BigInt& g_x, - const BigInt& g_y, - const BigInt& order, - const BigInt& cofactor) const { - return (this->p() == p && this->a() == a && this->b() == b && this->order() == order && - this->cofactor() == cofactor && this->g_x() == g_x && this->g_y() == g_y); - } - - bool params_match(const EC_Group_Data& other) const { - return params_match( - other.p(), other.a(), other.b(), other.g_x(), other.g_y(), other.order(), other.cofactor()); - } - - void set_oid(const OID& oid) { - BOTAN_STATE_CHECK(m_oid.empty()); - m_oid = oid; - } - - const OID& oid() const { return m_oid; } - - const BigInt& p() const { return m_curve.get_p(); } - - const BigInt& a() const { return m_curve.get_a(); } - - const BigInt& b() const { return m_curve.get_b(); } - - const BigInt& order() const { return m_order; } - - const BigInt& cofactor() const { return m_cofactor; } - - const BigInt& g_x() const { return m_g_x; } - - const BigInt& g_y() const { return m_g_y; } - - size_t p_bits() const { return m_p_bits; } - - size_t p_bytes() const { return (m_p_bits + 7) / 8; } - - size_t order_bits() const { return m_order_bits; } - - size_t order_bytes() const { return (m_order_bits + 7) / 8; } - - const CurveGFp& curve() const { return m_curve; } - - const EC_Point& base_point() const { return m_base_point; } - - bool a_is_minus_3() const { return m_a_is_minus_3; } - - bool a_is_zero() const { return m_a_is_zero; } - - BigInt mod_order(const BigInt& x) const { return m_mod_order.reduce(x); } - - BigInt square_mod_order(const BigInt& x) const { return m_mod_order.square(x); } - - BigInt multiply_mod_order(const BigInt& x, const BigInt& y) const { return m_mod_order.multiply(x, y); } - - BigInt multiply_mod_order(const BigInt& x, const BigInt& y, const BigInt& z) const { - return m_mod_order.multiply(m_mod_order.multiply(x, y), z); - } - - BigInt inverse_mod_order(const BigInt& x) const { return inverse_mod(x, m_order); } - - EC_Point blinded_base_point_multiply(const BigInt& k, RandomNumberGenerator& rng, std::vector& ws) const { - return m_base_mult.mul(k, rng, m_order, ws); - } - - EC_Group_Source source() const { return m_source; } - - private: - CurveGFp m_curve; - EC_Point m_base_point; - - BigInt m_g_x; - BigInt m_g_y; - BigInt m_order; - BigInt m_cofactor; - Modular_Reducer m_mod_order; - EC_Point_Base_Point_Precompute m_base_mult; - OID m_oid; - size_t m_p_bits; - size_t m_order_bits; - bool m_a_is_minus_3; - bool m_a_is_zero; - EC_Group_Source m_source; -}; - class EC_Group_Data_Map final { public: EC_Group_Data_Map() = default; @@ -564,7 +446,7 @@ const BigInt& EC_Group::get_b() const { return data().b(); } -const EC_Point& EC_Group::get_base_point() const { +const EC_Point& EC_Group::generator() const { return data().base_point(); } @@ -584,6 +466,10 @@ const BigInt& EC_Group::get_cofactor() const { return data().cofactor(); } +bool EC_Group::has_cofactor() const { + return data().has_cofactor(); +} + BigInt EC_Group::mod_order(const BigInt& k) const { return data().mod_order(k); } @@ -691,19 +577,13 @@ EC_Point EC_Group::hash_to_curve(std::string_view hash_fn, const uint8_t domain_sep[], size_t domain_sep_len, bool random_oracle) const { -#if defined(BOTAN_HAS_EC_HASH_TO_CURVE) - - // Only have SSWU currently - if(get_a().is_zero() || get_b().is_zero() || get_p() % 4 == 1) { - throw Not_Implemented("EC_Group::hash_to_curve not available for this curve type"); + if(random_oracle) { + return EC_AffinePoint::hash_to_curve_ro(*this, hash_fn, {input, input_len}, {domain_sep, domain_sep_len}) + .to_legacy_point(); + } else { + return EC_AffinePoint::hash_to_curve_nu(*this, hash_fn, {input, input_len}, {domain_sep, domain_sep_len}) + .to_legacy_point(); } - - return hash_to_curve_sswu(*this, hash_fn, {input, input_len}, {domain_sep, domain_sep_len}, random_oracle); - -#else - BOTAN_UNUSED(hash_fn, random_oracle, input, input_len, domain_sep, domain_sep_len); - throw Not_Implemented("EC_Group::hash_to_curve functionality not available in this configuration"); -#endif } std::vector EC_Group::DER_encode(EC_Group_Encoding form) const { @@ -777,7 +657,7 @@ bool EC_Group::verify_public_element(const EC_Point& point) const { return false; } - if(get_cofactor() > 1) { + if(has_cofactor()) { if((point * get_cofactor()).is_zero()) { return false; } @@ -856,4 +736,35 @@ bool EC_Group::verify_group(RandomNumberGenerator& rng, bool strong) const { return true; } +EC_Group::Mul2Table::Mul2Table(const EC_Group& group, const EC_Point& h) : + EC_Group::Mul2Table(EC_AffinePoint(group, h)) {} + +EC_Group::Mul2Table::Mul2Table(const EC_AffinePoint& h) : m_tbl(h._group()->make_mul2_table(h._inner())) {} + +EC_Group::Mul2Table::~Mul2Table() = default; + +std::optional EC_Group::Mul2Table::mul2_vartime(const EC_Scalar& x, const EC_Scalar& y) const { + auto pt = m_tbl->mul2_vartime(x._inner(), y._inner()); + if(pt) { + return EC_AffinePoint::_from_inner(std::move(pt)); + } else { + return {}; + } +} + +std::optional EC_Group::Mul2Table::mul2_vartime_x_mod_order(const EC_Scalar& x, const EC_Scalar& y) const { + auto s = m_tbl->mul2_vartime_x_mod_order(x._inner(), y._inner()); + if(s) { + return EC_Scalar::_from_inner(std::move(s)); + } else { + return {}; + } +} + +std::optional EC_Group::Mul2Table::mul2_vartime_x_mod_order(const EC_Scalar& c, + const EC_Scalar& x, + const EC_Scalar& y) const { + return this->mul2_vartime_x_mod_order(c * x, c * y); +} + } // namespace Botan diff --git a/src/lib/pubkey/ec_group/ec_group.h b/src/lib/pubkey/ec_group/ec_group.h index 502cce4bf73..9bdd063e3e7 100644 --- a/src/lib/pubkey/ec_group/ec_group.h +++ b/src/lib/pubkey/ec_group/ec_group.h @@ -2,7 +2,7 @@ * ECC Domain Parameters * * (C) 2007 Falko Strenzke, FlexSecure GmbH -* 2008-2010 Jack Lloyd +* 2008-2010,2024 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -11,7 +11,9 @@ #define BOTAN_ECC_DOMAIN_PARAMETERS_H_ #include +#include #include +#include #include #include #include @@ -36,6 +38,7 @@ enum class EC_Group_Source { ExternalSource, }; +class EC_Mul2Table_Data; class EC_Group_Data; class EC_Group_Data_Map; @@ -212,6 +215,62 @@ class BOTAN_PUBLIC_API(2, 0) EC_Group final { */ bool verify_public_element(const EC_Point& y) const; + /// Table for computing g*x + h*y + class Mul2Table final { + public: + /** + * Internal transition function + * + * @warning this will be removed in 3.6.0, NOT COVERED BY SEMVER + */ + Mul2Table(const EC_Group& group, const EC_Point& h); + + /** + * Create a table for computing g*x + h*y + */ + Mul2Table(const EC_AffinePoint& h); + + /** + * Return the elliptic curve point g*x + h*y + * + * Where g is the group generator and h is the value passed to the constructor + * + * Returns nullopt if g*x + h*y was the point at infinity + * + * @warning this function is variable time with respect to x and y + */ + std::optional mul2_vartime(const EC_Scalar& x, const EC_Scalar& y) const; + + /** + * Return the x coordinate of g*x + h*y, reduced modulo the order + * + * Where g is the group generator and h is the value passed to the constructor + * + * Returns nullopt if g*x + h*y was the point at infinity + * + * @warning this function is variable time with respect to x and y + */ + std::optional mul2_vartime_x_mod_order(const EC_Scalar& x, const EC_Scalar& y) const; + + /** + * Return the x coordinate of g*x*c + h*y*c, reduced modulo the order + * + * Where g is the group generator and h is the value passed to the constructor + * + * Returns nullopt if g*x*c + h*y*c was the point at infinity + * + * @warning this function is variable time with respect to c, x and y + */ + std::optional mul2_vartime_x_mod_order(const EC_Scalar& c, + const EC_Scalar& x, + const EC_Scalar& y) const; + + ~Mul2Table(); + + private: + std::unique_ptr m_tbl; + }; + /** * Return the OID of these domain parameters * @result the OID @@ -237,7 +296,13 @@ class BOTAN_PUBLIC_API(2, 0) EC_Group final { * Return group base point * @result base point */ - const EC_Point& get_base_point() const; + const EC_Point& get_base_point() const { return generator(); } + + /** + * Return the canonical group generator + * @result standard generator of the curve + */ + const EC_Point& generator() const; /** * Return the x coordinate of the base point @@ -261,10 +326,16 @@ class BOTAN_PUBLIC_API(2, 0) EC_Group final { */ const BigInt& get_cofactor() const; + /** + * Return true if the cofactor is > 1 + */ + bool has_cofactor() const; + /** * Multi exponentiate. Not constant time. * @return base_point*x + pt*y */ + BOTAN_DEPRECATED("Use EC_Group::Mul2Table") EC_Point point_multiply(const BigInt& x, const EC_Point& pt, const BigInt& y) const; /** @@ -274,6 +345,7 @@ class BOTAN_PUBLIC_API(2, 0) EC_Group final { * @param ws a temp workspace * @return base_point*k */ + BOTAN_DEPRECATED("Use EC_AffinePoint and EC_Scalar") EC_Point blinded_base_point_multiply(const BigInt& k, RandomNumberGenerator& rng, std::vector& ws) const; /** @@ -285,6 +357,7 @@ class BOTAN_PUBLIC_API(2, 0) EC_Group final { * @param ws a temp workspace * @return x coordinate of base_point*k */ + BOTAN_DEPRECATED("Use EC_AffinePoint and EC_Scalar") BigInt blinded_base_point_multiply_x(const BigInt& k, RandomNumberGenerator& rng, std::vector& ws) const; /** @@ -295,6 +368,7 @@ class BOTAN_PUBLIC_API(2, 0) EC_Group final { * @param ws a temp workspace * @return point*k */ + BOTAN_DEPRECATED("Use EC_AffinePoint and EC_Scalar") EC_Point blinded_var_point_multiply(const EC_Point& point, const BigInt& k, RandomNumberGenerator& rng, @@ -303,7 +377,7 @@ class BOTAN_PUBLIC_API(2, 0) EC_Group final { /** * Return a random scalar ie an integer in [1,order) */ - BigInt random_scalar(RandomNumberGenerator& rng) const; + BOTAN_DEPRECATED("Use EC_Scalar::random") BigInt random_scalar(RandomNumberGenerator& rng) const; /** * Hash onto the curve. @@ -414,7 +488,7 @@ class BOTAN_PUBLIC_API(2, 0) EC_Group final { /* * Reduce (x*y*z) modulo the order */ - BOTAN_DEPRECATED("Deprecated no replacement") + BOTAN_DEPRECATED("Use EC_Scalar") BigInt multiply_mod_order(const BigInt& x, const BigInt& y, const BigInt& z) const; /* @@ -449,6 +523,11 @@ class BOTAN_PUBLIC_API(2, 0) EC_Group final { */ static OID EC_group_identity_from_order(const BigInt& order); + /* + * For internal use only + */ + const std::shared_ptr& _data() const { return m_data; } + private: static EC_Group_Data_Map& ec_group_data(); @@ -465,8 +544,9 @@ class BOTAN_PUBLIC_API(2, 0) EC_Group final { const char* order, const OID& oid); - // Member data const EC_Group_Data& data() const; + + // Member data std::shared_ptr m_data; bool m_explicit_encoding = false; }; diff --git a/src/lib/pubkey/ec_group/ec_inner_bn.cpp b/src/lib/pubkey/ec_group/ec_inner_bn.cpp new file mode 100644 index 00000000000..710fec32063 --- /dev/null +++ b/src/lib/pubkey/ec_group/ec_inner_bn.cpp @@ -0,0 +1,180 @@ +/* +* (C) 2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +namespace Botan { + +const EC_Scalar_Data_BN& EC_Scalar_Data_BN::checked_ref(const EC_Scalar_Data& data) { + const auto* p = dynamic_cast(&data); + if(!p) { + throw Invalid_State("Failed conversion to EC_Scalar_Data_BN"); + } + return *p; +} + +const std::shared_ptr& EC_Scalar_Data_BN::group() const { + return m_group; +} + +size_t EC_Scalar_Data_BN::bytes() const { + return this->group()->order_bytes(); +} + +std::unique_ptr EC_Scalar_Data_BN::clone() const { + return std::make_unique(this->group(), this->value()); +} + +bool EC_Scalar_Data_BN::is_zero() const { + return this->value().is_zero(); +} + +bool EC_Scalar_Data_BN::is_eq(const EC_Scalar_Data& other) const { + return (value() == checked_ref(other).value()); +} + +void EC_Scalar_Data_BN::assign(const EC_Scalar_Data& other) { + m_v = checked_ref(other).value(); +} + +void EC_Scalar_Data_BN::square_self() { + m_group->square_mod_order(m_v); +} + +std::unique_ptr EC_Scalar_Data_BN::negate() const { + return std::make_unique(m_group, m_group->mod_order(-m_v)); +} + +std::unique_ptr EC_Scalar_Data_BN::invert() const { + return std::make_unique(m_group, m_group->inverse_mod_order(m_v)); +} + +std::unique_ptr EC_Scalar_Data_BN::add(const EC_Scalar_Data& other) const { + return std::make_unique(m_group, m_group->mod_order(m_v + checked_ref(other).value())); +} + +std::unique_ptr EC_Scalar_Data_BN::sub(const EC_Scalar_Data& other) const { + return std::make_unique(m_group, m_group->mod_order(m_v - checked_ref(other).value())); +} + +std::unique_ptr EC_Scalar_Data_BN::mul(const EC_Scalar_Data& other) const { + return std::make_unique(m_group, m_group->multiply_mod_order(m_v, checked_ref(other).value())); +} + +void EC_Scalar_Data_BN::serialize_to(std::span bytes) const { + BOTAN_ARG_CHECK(bytes.size() == m_group->order_bytes(), "Invalid output length"); + m_v.serialize_to(bytes); +} + +EC_AffinePoint_Data_BN::EC_AffinePoint_Data_BN(std::shared_ptr group, EC_Point pt) : + m_group(std::move(group)), m_pt(std::move(pt)) { + m_pt.force_affine(); + m_xy = m_pt.xy_bytes(); +} + +EC_AffinePoint_Data_BN::EC_AffinePoint_Data_BN(std::shared_ptr group, + std::span pt) : + m_group(std::move(group)) { + BOTAN_ASSERT_NONNULL(m_group); + m_pt = Botan::OS2ECP(pt.data(), pt.size(), m_group->curve()); + m_xy = m_pt.xy_bytes(); +} + +std::unique_ptr EC_AffinePoint_Data_BN::clone() const { + return std::make_unique(m_group, m_pt); +} + +const std::shared_ptr& EC_AffinePoint_Data_BN::group() const { + return m_group; +} + +std::unique_ptr EC_AffinePoint_Data_BN::mul(const EC_Scalar_Data& scalar, + RandomNumberGenerator& rng, + std::vector& ws) const { + BOTAN_ARG_CHECK(scalar.group() == m_group, "Curve mismatch"); + const auto& bn = EC_Scalar_Data_BN::checked_ref(scalar); + + EC_Point_Var_Point_Precompute mul(m_pt, rng, ws); + + const auto order = m_group->order() * m_group->cofactor(); + auto pt = mul.mul(bn.value(), rng, order, ws); + return std::make_unique(m_group, std::move(pt)); +} + +size_t EC_AffinePoint_Data_BN::field_element_bytes() const { + return m_xy.size() / 2; +} + +void EC_AffinePoint_Data_BN::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_xy}.first(fe_bytes)); +} + +void EC_AffinePoint_Data_BN::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_xy}.last(fe_bytes)); +} + +void EC_AffinePoint_Data_BN::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, m_xy); +} + +void EC_AffinePoint_Data_BN::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_xy[m_xy.size() - 1] & 0x01) == 0x01; + + BufferStuffer stuffer(bytes); + stuffer.append(y_is_odd ? 0x03 : 0x02); + serialize_x_to(stuffer.next(fe_bytes)); +} + +void EC_AffinePoint_Data_BN::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"); + BufferStuffer stuffer(bytes); + stuffer.append(0x04); + stuffer.append(m_xy); +} + +EC_Mul2Table_Data_BN::EC_Mul2Table_Data_BN(const EC_AffinePoint_Data& g, const EC_AffinePoint_Data& h) : + m_group(g.group()), m_tbl(g.to_legacy_point(), h.to_legacy_point()) { + BOTAN_ARG_CHECK(h.group() == m_group, "Curve mismatch"); +} + +std::unique_ptr EC_Mul2Table_Data_BN::mul2_vartime(const EC_Scalar_Data& x, + const EC_Scalar_Data& y) const { + BOTAN_ARG_CHECK(x.group() == m_group && y.group() == m_group, "Curve mismatch"); + + const auto& bn_x = EC_Scalar_Data_BN::checked_ref(x); + const auto& bn_y = EC_Scalar_Data_BN::checked_ref(y); + auto pt = m_tbl.multi_exp(bn_x.value(), bn_y.value()); + + if(pt.is_zero()) { + return nullptr; + } + return std::make_unique(m_group, std::move(pt)); +} + +std::unique_ptr EC_Mul2Table_Data_BN::mul2_vartime_x_mod_order(const EC_Scalar_Data& x, + const EC_Scalar_Data& y) const { + BOTAN_ARG_CHECK(x.group() == m_group && y.group() == m_group, "Curve mismatch"); + + const auto& bn_x = EC_Scalar_Data_BN::checked_ref(x); + const auto& bn_y = EC_Scalar_Data_BN::checked_ref(y); + auto pt = m_tbl.multi_exp(bn_x.value(), bn_y.value()); + + if(pt.is_zero()) { + return nullptr; + } + return std::make_unique(m_group, m_group->mod_order(pt.get_affine_x())); +} + +} // namespace Botan diff --git a/src/lib/pubkey/ec_group/ec_inner_bn.h b/src/lib/pubkey/ec_group/ec_inner_bn.h new file mode 100644 index 00000000000..a130ccf263a --- /dev/null +++ b/src/lib/pubkey/ec_group/ec_inner_bn.h @@ -0,0 +1,105 @@ +/* +* (C) 2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_EC_INNER_DATA_BN_H_ +#define BOTAN_EC_INNER_DATA_BN_H_ + +#include + +namespace Botan { + +class EC_Scalar_Data_BN final : public EC_Scalar_Data { + public: + EC_Scalar_Data_BN(std::shared_ptr group, BigInt v) : + m_group(std::move(group)), m_v(std::move(v)) {} + + static const EC_Scalar_Data_BN& 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 BigInt& value() const { return m_v; } + + private: + std::shared_ptr m_group; + BigInt m_v; +}; + +class EC_AffinePoint_Data_BN final : public EC_AffinePoint_Data { + public: + EC_AffinePoint_Data_BN(std::shared_ptr group, EC_Point pt); + + EC_AffinePoint_Data_BN(std::shared_ptr group, std::span pt); + + 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; + + EC_Point to_legacy_point() const override { return m_pt; } + + private: + std::shared_ptr m_group; + EC_Point m_pt; + secure_vector m_xy; +}; + +class EC_Mul2Table_Data_BN final : public EC_Mul2Table_Data { + public: + EC_Mul2Table_Data_BN(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; + EC_Point_Multi_Point_Precompute m_tbl; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/ec_group/ec_inner_data.cpp b/src/lib/pubkey/ec_group/ec_inner_data.cpp new file mode 100644 index 00000000000..1ec53cf9519 --- /dev/null +++ b/src/lib/pubkey/ec_group/ec_inner_data.cpp @@ -0,0 +1,159 @@ +/* +* (C) 2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include + +#if defined(BOTAN_HAS_EC_HASH_TO_CURVE) + #include +#endif + +namespace Botan { + +EC_Group_Data::EC_Group_Data(const BigInt& p, + const BigInt& a, + const BigInt& b, + const BigInt& g_x, + const BigInt& g_y, + const BigInt& order, + const BigInt& cofactor, + const OID& oid, + EC_Group_Source source) : + m_curve(p, a, b), + m_base_point(m_curve, g_x, g_y), + m_g_x(g_x), + m_g_y(g_y), + m_order(order), + m_cofactor(cofactor), + m_mod_order(order), + m_base_mult(m_base_point, m_mod_order), + m_oid(oid), + m_p_bits(p.bits()), + m_order_bits(order.bits()), + m_order_bytes((m_order_bits + 7) / 8), + m_a_is_minus_3(a == p - 3), + m_a_is_zero(a.is_zero()), + m_has_cofactor(m_cofactor != 1), + m_source(source) {} + +bool EC_Group_Data::params_match(const BigInt& p, + const BigInt& a, + const BigInt& b, + const BigInt& g_x, + const BigInt& g_y, + const BigInt& order, + const BigInt& cofactor) const { + return (this->p() == p && this->a() == a && this->b() == b && this->order() == order && + this->cofactor() == cofactor && this->g_x() == g_x && this->g_y() == g_y); +} + +bool EC_Group_Data::params_match(const EC_Group_Data& other) const { + return params_match(other.p(), other.a(), other.b(), other.g_x(), other.g_y(), other.order(), other.cofactor()); +} + +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)); +} + +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))); +} + +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)); +} + +std::unique_ptr EC_Group_Data::scalar_zero() const { + 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()); +} + +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); +} + +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(); + } 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) { + if(bytes.size() != m_order_bytes) { + return nullptr; + } + + BigInt r(bytes.data(), bytes.size()); + + if(r.is_zero() || r >= m_order) { + return nullptr; + } + + 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)); + } catch(...) { + return nullptr; + } +} + +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 +} + +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 +} + +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)); +} + +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); +} + +} // namespace Botan diff --git a/src/lib/pubkey/ec_group/ec_inner_data.h b/src/lib/pubkey/ec_group/ec_inner_data.h new file mode 100644 index 00000000000..c6462ebbaea --- /dev/null +++ b/src/lib/pubkey/ec_group/ec_inner_data.h @@ -0,0 +1,236 @@ +/* +* (C) 2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_EC_INNER_DATA_H_ +#define BOTAN_EC_INNER_DATA_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace Botan { + +class EC_Group_Data; + +class EC_Scalar_Data { + public: + virtual ~EC_Scalar_Data() = default; + + virtual const std::shared_ptr& group() const = 0; + + virtual size_t bytes() const = 0; + + virtual std::unique_ptr clone() const = 0; + + virtual bool is_zero() const = 0; + + virtual bool is_eq(const EC_Scalar_Data& y) const = 0; + + virtual void assign(const EC_Scalar_Data& y) = 0; + + virtual void square_self() = 0; + + virtual std::unique_ptr negate() const = 0; + + virtual std::unique_ptr invert() const = 0; + + virtual std::unique_ptr add(const EC_Scalar_Data& other) const = 0; + + virtual std::unique_ptr sub(const EC_Scalar_Data& other) const = 0; + + virtual std::unique_ptr mul(const EC_Scalar_Data& other) const = 0; + + virtual void serialize_to(std::span bytes) const = 0; +}; + +class EC_AffinePoint_Data { + public: + virtual ~EC_AffinePoint_Data() = default; + + virtual const std::shared_ptr& group() const = 0; + + virtual std::unique_ptr clone() const = 0; + + // Return size of a field element + virtual size_t field_element_bytes() const = 0; + + // Writes 1 field element worth of data to bytes + virtual void serialize_x_to(std::span bytes) const = 0; + + // Writes 1 field element worth of data to bytes + virtual void serialize_y_to(std::span bytes) const = 0; + + // Writes 2 field elements worth of data to bytes + virtual void serialize_xy_to(std::span bytes) const = 0; + + // Writes 1 byte + 1 field element worth of data to bytes + virtual void serialize_compressed_to(std::span bytes) const = 0; + + // Writes 1 byte + 2 field elements worth of data to bytes + virtual void serialize_uncompressed_to(std::span bytes) const = 0; + + virtual std::unique_ptr mul(const EC_Scalar_Data& scalar, + RandomNumberGenerator& rng, + std::vector& ws) const = 0; + + virtual EC_Point to_legacy_point() const = 0; +}; + +class EC_Mul2Table_Data { + public: + virtual ~EC_Mul2Table_Data() = default; + + // Returns nullptr if g*x + h*y was point at infinity + virtual std::unique_ptr mul2_vartime(const EC_Scalar_Data& x, + const EC_Scalar_Data& y) const = 0; + + // Returns nullptr if g*x + h*y was point at infinity + virtual std::unique_ptr mul2_vartime_x_mod_order(const EC_Scalar_Data& x, + const EC_Scalar_Data& y) const = 0; +}; + +class EC_Group_Data final : public std::enable_shared_from_this { + public: + EC_Group_Data(const BigInt& p, + const BigInt& a, + const BigInt& b, + const BigInt& g_x, + const BigInt& g_y, + const BigInt& order, + const BigInt& cofactor, + const OID& oid, + EC_Group_Source source); + + bool params_match(const BigInt& p, + const BigInt& a, + const BigInt& b, + const BigInt& g_x, + const BigInt& g_y, + const BigInt& order, + const BigInt& cofactor) const; + + bool params_match(const EC_Group_Data& other) const; + + void set_oid(const OID& oid) { + BOTAN_STATE_CHECK(m_oid.empty()); + m_oid = oid; + } + + const OID& oid() const { return m_oid; } + + const BigInt& p() const { return m_curve.get_p(); } + + const BigInt& a() const { return m_curve.get_a(); } + + const BigInt& b() const { return m_curve.get_b(); } + + const BigInt& order() const { return m_order; } + + const BigInt& cofactor() const { return m_cofactor; } + + bool has_cofactor() const { return m_has_cofactor; } + + const BigInt& g_x() const { return m_g_x; } + + const BigInt& g_y() const { return m_g_y; } + + size_t p_bits() const { return m_p_bits; } + + size_t p_bytes() const { return (m_p_bits + 7) / 8; } + + size_t order_bits() const { return m_order_bits; } + + size_t order_bytes() const { return m_order_bytes; } + + const CurveGFp& curve() const { return m_curve; } + + const EC_Point& base_point() const { return m_base_point; } + + bool a_is_minus_3() const { return m_a_is_minus_3; } + + bool a_is_zero() const { return m_a_is_zero; } + + BigInt mod_order(const BigInt& x) const { return m_mod_order.reduce(x); } + + BigInt square_mod_order(const BigInt& x) const { return m_mod_order.square(x); } + + BigInt multiply_mod_order(const BigInt& x, const BigInt& y) const { return m_mod_order.multiply(x, y); } + + BigInt multiply_mod_order(const BigInt& x, const BigInt& y, const BigInt& z) const { + return m_mod_order.multiply(m_mod_order.multiply(x, y), z); + } + + BigInt inverse_mod_order(const BigInt& x) const { return inverse_mod(x, m_order); } + + EC_Point blinded_base_point_multiply(const BigInt& k, RandomNumberGenerator& rng, std::vector& ws) const { + return m_base_mult.mul(k, rng, m_order, ws); + } + + EC_Group_Source source() const { return m_source; } + + std::unique_ptr scalar_from_bytes_with_trunc(std::span bytes) const; + + std::unique_ptr scalar_from_bytes_mod_order(std::span bytes) const; + + std::unique_ptr scalar_from_bigint(const BigInt& bn) const; + + std::unique_ptr scalar_random(RandomNumberGenerator& rng) const; + + std::unique_ptr scalar_zero() const; + + std::unique_ptr scalar_one() const; + + std::unique_ptr gk_x_mod_order(const EC_Scalar_Data& scalar, + RandomNumberGenerator& rng, + std::vector& ws) const; + + std::unique_ptr scalar_deserialize(std::span bytes); + + std::unique_ptr point_deserialize(std::span bytes) const; + + std::unique_ptr point_hash_to_curve_ro(std::string_view hash_fn, + std::span input, + std::span domain_sep) const; + + std::unique_ptr point_hash_to_curve_nu(std::string_view hash_fn, + std::span input, + std::span domain_sep) const; + + std::unique_ptr point_g_mul(const EC_Scalar_Data& scalar, + RandomNumberGenerator& rng, + std::vector& ws) const; + + std::unique_ptr make_mul2_table(const EC_AffinePoint_Data& pt) const; + + private: + CurveGFp m_curve; + EC_Point m_base_point; + + BigInt m_g_x; + BigInt m_g_y; + BigInt m_order; + BigInt m_cofactor; + Modular_Reducer m_mod_order; + EC_Point_Base_Point_Precompute m_base_mult; + OID m_oid; + size_t m_p_bits; + size_t m_order_bits; + size_t m_order_bytes; + bool m_a_is_minus_3; + bool m_a_is_zero; + bool m_has_cofactor; + EC_Group_Source m_source; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/ec_group/ec_scalar.cpp b/src/lib/pubkey/ec_group/ec_scalar.cpp new file mode 100644 index 00000000000..7021b76b601 --- /dev/null +++ b/src/lib/pubkey/ec_group/ec_scalar.cpp @@ -0,0 +1,145 @@ +/* +* (C) 2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan { + +EC_Scalar EC_Scalar::_from_inner(std::unique_ptr inner) { + return EC_Scalar(std::move(inner)); +} + +EC_Scalar::EC_Scalar(std::unique_ptr scalar) : m_scalar(std::move(scalar)) { + BOTAN_ASSERT_NONNULL(m_scalar); +} + +EC_Scalar::EC_Scalar(const EC_Scalar& other) : m_scalar(other.inner().clone()) {} + +EC_Scalar::EC_Scalar(EC_Scalar&& other) noexcept : m_scalar(std::move(other.m_scalar)) {} + +EC_Scalar& EC_Scalar::operator=(const EC_Scalar& other) { + if(this != &other) { + this->assign(other); + } + return (*this); +} + +EC_Scalar& EC_Scalar::operator=(EC_Scalar&& other) noexcept { + BOTAN_ARG_CHECK(_inner().group() == other._inner().group(), "Curve mismatch"); + std::swap(m_scalar, other.m_scalar); + return (*this); +} + +EC_Scalar::~EC_Scalar() = default; + +size_t EC_Scalar::bytes() const { + return m_scalar->bytes(); +} + +EC_Scalar EC_Scalar::from_bytes_with_trunc(const EC_Group& group, std::span bytes) { + return EC_Scalar(group._data()->scalar_from_bytes_with_trunc(bytes)); +} + +EC_Scalar EC_Scalar::from_bytes_mod_order(const EC_Group& group, std::span bytes) { + return EC_Scalar(group._data()->scalar_from_bytes_mod_order(bytes)); +} + +EC_Scalar EC_Scalar::random(const EC_Group& group, RandomNumberGenerator& rng) { + return EC_Scalar(group._data()->scalar_random(rng)); +} + +EC_Scalar EC_Scalar::one(const EC_Group& group) { + return EC_Scalar(group._data()->scalar_one()); +} + +EC_Scalar EC_Scalar::from_bigint(const EC_Group& group, const BigInt& bn) { + BOTAN_ARG_CHECK(bn.is_positive() && bn <= group._data()->order(), "EC_Scalar::from_bigint out of range"); + return EC_Scalar(group._data()->scalar_from_bigint(bn)); +} + +EC_Scalar EC_Scalar::gk_x_mod_order(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& ws) { + const auto& group = scalar._inner().group(); + return EC_Scalar(group->gk_x_mod_order(scalar.inner(), rng, ws)); +} + +void EC_Scalar::serialize_to(std::span bytes) const { + inner().serialize_to(bytes); +} + +void EC_Scalar::serialize_pair_to(std::span bytes, const EC_Scalar& r, const EC_Scalar& s) { + BOTAN_ARG_CHECK(r._inner().group() == s._inner().group(), "Curve mismatch"); + const size_t scalar_bytes = r.bytes(); + BOTAN_ARG_CHECK(bytes.size() == 2 * scalar_bytes, "Invalid output length"); + r.serialize_to(bytes.first(scalar_bytes)); + s.serialize_to(bytes.last(scalar_bytes)); +} + +std::optional> EC_Scalar::deserialize_pair(const EC_Group& group, + std::span bytes) { + if(bytes.size() % 2 != 0) { + return {}; + } + + const size_t half = bytes.size() / 2; + + auto r = EC_Scalar::deserialize(group, bytes.first(half)); + auto s = EC_Scalar::deserialize(group, bytes.last(half)); + + if(r && s) { + return std::make_pair(r.value(), s.value()); + } else { + return {}; + } +} + +std::optional EC_Scalar::deserialize(const EC_Group& group, std::span bytes) { + if(auto v = group._data()->scalar_deserialize(bytes)) { + return EC_Scalar(std::move(v)); + } else { + return {}; + } +} + +bool EC_Scalar::is_zero() const { + return inner().is_zero(); +} + +EC_Scalar EC_Scalar::invert() const { + return EC_Scalar(inner().invert()); +} + +EC_Scalar EC_Scalar::negate() const { + return EC_Scalar(inner().negate()); +} + +void EC_Scalar::square_self() { + m_scalar->square_self(); +} + +EC_Scalar EC_Scalar::add(const EC_Scalar& x) const { + return EC_Scalar(inner().add(x.inner())); +} + +EC_Scalar EC_Scalar::sub(const EC_Scalar& x) const { + return EC_Scalar(inner().sub(x.inner())); +} + +EC_Scalar EC_Scalar::mul(const EC_Scalar& x) const { + return EC_Scalar(inner().mul(x.inner())); +} + +void EC_Scalar::assign(const EC_Scalar& x) { + m_scalar->assign(x.inner()); +} + +bool EC_Scalar::is_eq(const EC_Scalar& x) const { + return inner().is_eq(x.inner()); +} + +} // namespace Botan diff --git a/src/lib/pubkey/ec_group/ec_scalar.h b/src/lib/pubkey/ec_group/ec_scalar.h new file mode 100644 index 00000000000..fc8e21b426d --- /dev/null +++ b/src/lib/pubkey/ec_group/ec_scalar.h @@ -0,0 +1,215 @@ +/* +* (C) 2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_EC_SCALAR_H_ +#define BOTAN_EC_SCALAR_H_ + +#include +#include + +#include +#include +#include + +namespace Botan { + +class BigInt; +class RandomNumberGenerator; +class EC_Group; +class EC_Group_Data; +class EC_Scalar_Data; + +/** +* Represents an integer modulo the prime group order of an elliptic curve +*/ +class BOTAN_UNSTABLE_API EC_Scalar final { + public: + /** + * Deserialize a scalar + * + * The span must be exactly bytes() long; this function does not accept + * either short inputs (eg [1] to encode the integer 1) or inputs with + * excess leading zero bytes. + * + * Returns nullopt if the length is incorrect or if the integer is not + * within the range [0,p) where p is the group order. + */ + static std::optional deserialize(const EC_Group& group, std::span bytes); + + /** + * Convert a bytestring to an EC_Scalar + * + * This uses the truncation rules from ECDSA + */ + static EC_Scalar from_bytes_with_trunc(const EC_Group& group, std::span bytes); + + /** + * Convert a bytestring to an EC_Scalar + * + * This reduces the bytes modulo the group order. The input can be at most + * 2*bytes() long + */ + static EC_Scalar from_bytes_mod_order(const EC_Group& group, std::span bytes); + + /** + * Deserialize a pair of scalars + * + * Returns nullopt if the length is not 2*bytes(), or if either scalar is + * out of range or zero + */ + static std::optional> deserialize_pair(const EC_Group& group, + std::span bytes); + + /** + * Return a new random scalar value + */ + static EC_Scalar random(const EC_Group& group, RandomNumberGenerator& rng); + + /** + * Return the scalar value 1 + */ + static EC_Scalar one(const EC_Group& group); + + /** + * Convert from the argument BigInt to a EC_Scalar + * + * Throws an exception if the provided bn is negative or too large + */ + static EC_Scalar from_bigint(const EC_Group& group, const BigInt& bn); + + /** + * Compute the elliptic curve scalar multiplication (g*k) where g is the + * standard base point on the curve. Then extract the x coordinate of + * the resulting point, and reduce it modulo the group order. + * + * Workspace argument is transitional + */ + static EC_Scalar gk_x_mod_order(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector& ws); + + /** + * Return the byte size of this scalar + */ + size_t bytes() const; + + /** + * Write the fixed length serialization to bytes + * + * The provided span must be exactly bytes() long + */ + void serialize_to(std::span bytes) const; + + /** + * Return the bytes of the encoded scalar in a container + */ + template > + T serialize() const { + T s(this->bytes()); + this->serialize_to(s); + return s; + } + + /** + * Write the fixed length serialization to bytes + * + * The provided span must be exactly bytes() long + */ + static void serialize_pair_to(std::span bytes, const EC_Scalar& r, const EC_Scalar& s); + + /** + * Return the bytes of the encoded scalar in a container + */ + template > + static T serialize_pair(const EC_Scalar& r, const EC_Scalar& s) { + T bytes(r.bytes() + s.bytes()); + serialize_pair_to(bytes, r, s); + return bytes; + } + + /** + * Return true if this EC_Scalar is zero + */ + bool is_zero() const; + + /** + * Return true if this EC_Scalar is not zero + */ + bool is_nonzero() const { return !is_zero(); } + + /** + * Return the modular inverse of this EC_Scalar + * + * If the EC_Scalar is zero, then invert() returns zero also + * Since the EC_Scalar + */ + EC_Scalar invert() const; + + /** + */ + EC_Scalar negate() const; + + /** + * Scalar addition (modulo p) + */ + EC_Scalar add(const EC_Scalar& x) const; + + /** + * Scalar subtraction (modulo p) + */ + EC_Scalar sub(const EC_Scalar& x) const; + + /** + * Scalar multiplication (modulo p) + */ + EC_Scalar mul(const EC_Scalar& x) const; + + /** + * Assign a scalar + */ + void assign(const EC_Scalar& x); + + /** + * Set *this to its own square modulo p + */ + void square_self(); + + /** + * Test for equality + */ + bool is_eq(const EC_Scalar& x) const; + + friend EC_Scalar operator+(const EC_Scalar& x, const EC_Scalar& y) { return x.add(y); } + + friend EC_Scalar operator-(const EC_Scalar& x, const EC_Scalar& y) { return x.sub(y); } + + friend EC_Scalar operator*(const EC_Scalar& x, const EC_Scalar& y) { return x.mul(y); } + + friend bool operator==(const EC_Scalar& x, const EC_Scalar& y) { return x.is_eq(y); } + + EC_Scalar(const EC_Scalar& other); + EC_Scalar(EC_Scalar&& other) noexcept; + + EC_Scalar& operator=(const EC_Scalar& other); + EC_Scalar& operator=(EC_Scalar&& other) noexcept; + + ~EC_Scalar(); + + const EC_Scalar_Data& _inner() const { return inner(); } + + static EC_Scalar _from_inner(std::unique_ptr inner); + + private: + friend class EC_AffinePoint; + + EC_Scalar(std::unique_ptr scalar); + + const EC_Scalar_Data& inner() const { return *m_scalar; } + + std::unique_ptr m_scalar; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/ec_group/info.txt b/src/lib/pubkey/ec_group/info.txt index 6fd38259984..d918f52f054 100644 --- a/src/lib/pubkey/ec_group/info.txt +++ b/src/lib/pubkey/ec_group/info.txt @@ -16,10 +16,14 @@ pem point_mul.h +ec_inner_bn.h +ec_inner_data.h curve_gfp.h ec_group.h ec_point.h +ec_apoint.h +ec_scalar.h diff --git a/src/lib/pubkey/ec_group/point_mul.cpp b/src/lib/pubkey/ec_group/point_mul.cpp index 6e996a3396d..b600a2fe52b 100644 --- a/src/lib/pubkey/ec_group/point_mul.cpp +++ b/src/lib/pubkey/ec_group/point_mul.cpp @@ -158,14 +158,17 @@ EC_Point EC_Point_Base_Point_Precompute::mul(const BigInt& k, return R; } -EC_Point_Var_Point_Precompute::EC_Point_Var_Point_Precompute(const EC_Point& point, +EC_Point_Var_Point_Precompute::EC_Point_Var_Point_Precompute(const EC_Point& ipoint, RandomNumberGenerator& rng, std::vector& ws) : - m_curve(point.get_curve()), m_p_words(m_curve.get_p_words()), m_window_bits(4) { + m_curve(ipoint.get_curve()), m_p_words(m_curve.get_p_words()), m_window_bits(4) { if(ws.size() < EC_Point::WORKSPACE_SIZE) { ws.resize(EC_Point::WORKSPACE_SIZE); } + auto point = ipoint; + point.randomize_repr(rng); + std::vector U(static_cast(1) << m_window_bits); U[0] = point.zero(); U[1] = point; @@ -259,7 +262,7 @@ EC_Point EC_Point_Var_Point_Precompute::mul(const BigInt& k, windows--; } - BOTAN_DEBUG_ASSERT(R.on_the_curve()); + BOTAN_ASSERT_NOMSG(R.on_the_curve()); return R; } diff --git a/src/lib/pubkey/ec_h2c/ec_h2c.cpp b/src/lib/pubkey/ec_h2c/ec_h2c.cpp index 0a7018eca25..e3a68a903ca 100644 --- a/src/lib/pubkey/ec_h2c/ec_h2c.cpp +++ b/src/lib/pubkey/ec_h2c/ec_h2c.cpp @@ -7,6 +7,7 @@ #include #include +#include #include namespace Botan { @@ -16,10 +17,18 @@ EC_Point hash_to_curve_sswu(const EC_Group& group, std::span input, std::span domain_sep, bool random_oracle) { - if(auto group_id = PCurve::PrimeOrderCurveId::from_oid(group.get_curve_oid())) { + return group.OS2ECP(hash_to_curve_sswu(*group._data(), hash_fn, input, domain_sep, random_oracle)); +} + +std::vector hash_to_curve_sswu(const EC_Group_Data& group, + std::string_view hash_fn, + std::span input, + std::span domain_sep, + bool random_oracle) { + if(auto group_id = PCurve::PrimeOrderCurveId::from_oid(group.oid())) { if(auto curve = PCurve::PrimeOrderCurve::from_id(*group_id)) { const auto pt = curve->hash_to_curve(hash_fn, input, domain_sep, random_oracle); - return group.OS2ECP(pt.to_affine().serialize()); + return pt.to_affine().serialize(); } } diff --git a/src/lib/pubkey/ec_h2c/ec_h2c.h b/src/lib/pubkey/ec_h2c/ec_h2c.h index bcdf1fc9d04..0d6a09c7010 100644 --- a/src/lib/pubkey/ec_h2c/ec_h2c.h +++ b/src/lib/pubkey/ec_h2c/ec_h2c.h @@ -15,6 +15,7 @@ namespace Botan { class EC_Group; +class EC_Group_Data; /** * Hash an input onto an elliptic curve point using the @@ -29,6 +30,19 @@ EC_Point hash_to_curve_sswu(const EC_Group& group, std::span domain_sep, bool random_oracle); +/** +* Hash an input onto an elliptic curve point using the +* methods from RFC 9380 +* +* This method requires that the ECC group have (a*b) != 0 +* which excludes certain groups including secp256k1 +*/ +std::vector hash_to_curve_sswu(const EC_Group_Data& group_data, + std::string_view hash_fn, + std::span input, + std::span domain_sep, + bool random_oracle); + } // namespace Botan #endif diff --git a/src/lib/pubkey/ecdh/ecdh.cpp b/src/lib/pubkey/ecdh/ecdh.cpp index cd66c9bd636..dcdada0bf06 100644 --- a/src/lib/pubkey/ecdh/ecdh.cpp +++ b/src/lib/pubkey/ecdh/ecdh.cpp @@ -26,28 +26,44 @@ namespace { class ECDH_KA_Operation final : public PK_Ops::Key_Agreement_with_KDF { public: ECDH_KA_Operation(const ECDH_PrivateKey& key, std::string_view kdf, RandomNumberGenerator& rng) : - PK_Ops::Key_Agreement_with_KDF(kdf), m_group(key.domain()), m_rng(rng) { - m_l_times_priv = m_group.inverse_mod_order(m_group.get_cofactor()) * key.private_value(); - } + PK_Ops::Key_Agreement_with_KDF(kdf), + m_group(key.domain()), + m_l_times_priv(mul_cofactor_inv(m_group, key.private_value())), + m_rng(rng) {} size_t agreed_value_size() const override { return m_group.get_p_bytes(); } secure_vector raw_agree(const uint8_t w[], size_t w_len) override { - EC_Point input_point = m_group.get_cofactor() * m_group.OS2ECP(w, w_len); - input_point.randomize_repr(m_rng); - - const EC_Point S = m_group.blinded_var_point_multiply(input_point, m_l_times_priv, m_rng, m_ws); - - if(S.on_the_curve() == false) { - throw Internal_Error("ECDH agreed value was not on the curve"); + if(m_group.has_cofactor()) { + EC_AffinePoint input_point(m_group, m_group.get_cofactor() * m_group.OS2ECP(w, w_len)); + return input_point.mul(m_l_times_priv, m_rng, m_ws).x_bytes(); + } else { + if(auto input_point = EC_AffinePoint::deserialize(m_group, {w, w_len})) { + return input_point->mul(m_l_times_priv, m_rng, m_ws).x_bytes(); + } else { + throw Decoding_Error("ECDH - Invalid elliptic curve point"); + } } - - return S.x_bytes(); } private: + static EC_Scalar mul_cofactor_inv(const EC_Group& group, const BigInt& bn) { + // We implement BSI TR-03111 ECKAEG which only matters in the (rare/deprecated) + // case of a curve with cofactor. + + auto private_key = EC_Scalar::from_bigint(group, bn); + + if(group.has_cofactor()) { + // We could precompute this but cofactors are rare + auto l = group.inverse_mod_order(group.get_cofactor()); + return private_key * EC_Scalar::from_bigint(group, l); + } else { + return private_key; + } + } + const EC_Group m_group; - BigInt m_l_times_priv; + const EC_Scalar m_l_times_priv; RandomNumberGenerator& m_rng; std::vector m_ws; }; diff --git a/src/lib/pubkey/ecdsa/ecdsa.cpp b/src/lib/pubkey/ecdsa/ecdsa.cpp index d02e9262eea..d990552e1e8 100644 --- a/src/lib/pubkey/ecdsa/ecdsa.cpp +++ b/src/lib/pubkey/ecdsa/ecdsa.cpp @@ -2,7 +2,7 @@ * ECDSA implemenation * (C) 2007 Manuel Hartl, FlexSecure GmbH * 2007 Falko Strenzke, FlexSecure GmbH -* 2008-2010,2015,2016,2018 Jack Lloyd +* 2008-2010,2015,2016,2018,2024 Jack Lloyd * 2016 René Korthaus * * Botan is released under the Simplified BSD License (see license.txt) @@ -10,10 +10,8 @@ #include -#include #include #include -#include #if defined(BOTAN_HAS_RFC6979_GENERATOR) #include @@ -25,7 +23,7 @@ namespace { EC_Point recover_ecdsa_public_key( const EC_Group& group, const std::vector& msg, const BigInt& r, const BigInt& s, uint8_t v) { - if(group.get_cofactor() != 1) { + if(group.has_cofactor()) { throw Invalid_Argument("ECDSA public key recovery only supported for prime order groups"); } @@ -44,9 +42,6 @@ EC_Point recover_ecdsa_public_key( const size_t p_bytes = group.get_p_bytes(); try { - const BigInt e = BigInt::from_bytes_with_max_bits(msg.data(), msg.size(), group.get_order_bits()); - const BigInt r_inv = group.inverse_mod_order(r); - BigInt x = r + add_order * group_order; std::vector X(p_bytes + 1); @@ -54,16 +49,18 @@ EC_Point recover_ecdsa_public_key( X[0] = 0x02 | y_odd; x.serialize_to(std::span{X}.subspan(1)); - const EC_Point R = group.OS2ECP(X); + if(auto R = EC_AffinePoint::deserialize(group, X)) { + // Compute r_inv * (-eG + s*R) + const auto ne = EC_Scalar::from_bytes_with_trunc(group, msg).negate(); + const auto ss = EC_Scalar::from_bigint(group, s); - if((R * group_order).is_zero() == false) { - throw Decoding_Error("Unable to recover ECDSA public key"); - } + const auto r_inv = EC_Scalar::from_bigint(group, r).invert(); - // Compute r_inv * (s*R - eG) - EC_Point_Multi_Point_Precompute RG_mul(R, group.get_base_point()); - const BigInt ne = group.mod_order(group_order - e); - return r_inv * RG_mul.multi_exp(s, ne); + EC_Group::Mul2Table GR_mul(R.value()); + if(auto egsr = GR_mul.mul2_vartime(ne * r_inv, ss * r_inv)) { + return egsr->to_legacy_point(); + } + } } catch(...) { // continue on and throw } @@ -121,13 +118,15 @@ namespace { class ECDSA_Signature_Operation final : public PK_Ops::Signature_with_Hash { public: ECDSA_Signature_Operation(const ECDSA_PrivateKey& ecdsa, std::string_view padding, RandomNumberGenerator& rng) : - PK_Ops::Signature_with_Hash(padding), m_group(ecdsa.domain()), m_x(ecdsa.private_value()) { + PK_Ops::Signature_with_Hash(padding), + m_group(ecdsa.domain()), + m_x(EC_Scalar::from_bigint(m_group, ecdsa.private_value())), + m_b(EC_Scalar::random(m_group, rng)), + m_b_inv(m_b.invert()) { #if defined(BOTAN_HAS_RFC6979_GENERATOR) - m_rfc6979 = std::make_unique(this->rfc6979_hash_function(), m_group.get_order(), m_x); + m_rfc6979 = std::make_unique( + this->rfc6979_hash_function(), m_group.get_order(), ecdsa.private_value()); #endif - - m_b = m_group.random_scalar(rng); - m_b_inv = m_group.inverse_mod_order(m_b); } size_t signature_length() const override { return 2 * m_group.get_order_bytes(); } @@ -138,7 +137,7 @@ class ECDSA_Signature_Operation final : public PK_Ops::Signature_with_Hash { private: const EC_Group m_group; - const BigInt m_x; + const EC_Scalar m_x; #if defined(BOTAN_HAS_RFC6979_GENERATOR) std::unique_ptr m_rfc6979; @@ -146,7 +145,8 @@ class ECDSA_Signature_Operation final : public PK_Ops::Signature_with_Hash { std::vector m_ws; - BigInt m_b, m_b_inv; + EC_Scalar m_b; + EC_Scalar m_b_inv; }; AlgorithmIdentifier ECDSA_Signature_Operation::algorithm_identifier() const { @@ -158,35 +158,34 @@ AlgorithmIdentifier ECDSA_Signature_Operation::algorithm_identifier() const { secure_vector ECDSA_Signature_Operation::raw_sign(const uint8_t msg[], size_t msg_len, RandomNumberGenerator& rng) { - BigInt m = m_group.mod_order(BigInt::from_bytes_with_max_bits(msg, msg_len, m_group.get_order_bits())); + const auto m = EC_Scalar::from_bytes_with_trunc(m_group, std::span{msg, msg_len}); #if defined(BOTAN_HAS_RFC6979_GENERATOR) - const BigInt k = m_rfc6979->nonce_for(m); + const auto k = m_rfc6979->nonce_for(m_group, m); #else - const BigInt k = m_group.random_scalar(rng); + const auto k = EC_Scalar::random(m_group, rng); #endif - const BigInt r = m_group.mod_order(m_group.blinded_base_point_multiply_x(k, rng, m_ws)); + const auto r = EC_Scalar::gk_x_mod_order(k, rng, m_ws); - const BigInt k_inv = m_group.inverse_mod_order(k); + const auto k_inv = k.invert(); /* * Blind the input message and compute x*r+m as (x*r*b + m*b)/b */ - m_b = m_group.square_mod_order(m_b); - m_b_inv = m_group.square_mod_order(m_b_inv); + m_b.square_self(); + m_b_inv.square_self(); - m = m_group.multiply_mod_order(m_b, m_group.mod_order(m)); - const BigInt xr_m = m_group.mod_order(m_group.multiply_mod_order(m_x, m_b, r) + m); + const auto xr_m = ((m_x * m_b) * r) + (m * m_b); - const BigInt s = m_group.multiply_mod_order(k_inv, xr_m, m_b_inv); + const auto s = (k_inv * xr_m) * m_b_inv; // With overwhelming probability, a bug rather than actual zero r/s if(r.is_zero() || s.is_zero()) { throw Internal_Error("During ECDSA signature generated zero r/s"); } - return BigInt::encode_fixed_length_int_pair(r, s, m_group.get_order_bytes()); + return EC_Scalar::serialize_pair>(r, s); } /** @@ -195,53 +194,36 @@ secure_vector ECDSA_Signature_Operation::raw_sign(const uint8_t msg[], class ECDSA_Verification_Operation final : public PK_Ops::Verification_with_Hash { public: ECDSA_Verification_Operation(const ECDSA_PublicKey& ecdsa, std::string_view padding) : - PK_Ops::Verification_with_Hash(padding), - m_group(ecdsa.domain()), - m_gy_mul(m_group.get_base_point(), ecdsa.public_point()) {} + PK_Ops::Verification_with_Hash(padding), m_group(ecdsa.domain()), m_gy_mul(m_group, ecdsa.public_point()) {} ECDSA_Verification_Operation(const ECDSA_PublicKey& ecdsa, const AlgorithmIdentifier& alg_id) : PK_Ops::Verification_with_Hash(alg_id, "ECDSA", true), m_group(ecdsa.domain()), - m_gy_mul(m_group.get_base_point(), ecdsa.public_point()) {} + m_gy_mul(m_group, ecdsa.public_point()) {} bool verify(const uint8_t msg[], size_t msg_len, const uint8_t sig[], size_t sig_len) override; private: const EC_Group m_group; - const EC_Point_Multi_Point_Precompute m_gy_mul; + const EC_Group::Mul2Table m_gy_mul; }; bool ECDSA_Verification_Operation::verify(const uint8_t msg[], size_t msg_len, const uint8_t sig[], size_t sig_len) { - if(sig_len != m_group.get_order_bytes() * 2) { - return false; - } + if(auto rs = EC_Scalar::deserialize_pair(m_group, std::span{sig, sig_len})) { + const auto& [r, s] = rs.value(); - const BigInt e = BigInt::from_bytes_with_max_bits(msg, msg_len, m_group.get_order_bits()); + if(r.is_nonzero() && s.is_nonzero()) { + const auto m = EC_Scalar::from_bytes_with_trunc(m_group, std::span{msg, msg_len}); - const BigInt r(sig, sig_len / 2); - const BigInt s(sig + sig_len / 2, sig_len / 2); + const auto w = s.invert(); - // Cannot be negative here since we just decoded from binary - if(r.is_zero() || s.is_zero()) { - return false; - } - - if(r >= m_group.get_order() || s >= m_group.get_order()) { - return false; - } - - const BigInt w = m_group.inverse_mod_order(s); - - const BigInt u1 = m_group.multiply_mod_order(m_group.mod_order(e), w); - const BigInt u2 = m_group.multiply_mod_order(r, w); - const EC_Point R = m_gy_mul.multi_exp(u1, u2); - - if(R.is_zero()) { - return false; + if(const auto v = m_gy_mul.mul2_vartime_x_mod_order(w, m, r)) { + return (v == r); + } + } } - const BigInt v = m_group.mod_order(R.get_affine_x()); - return (v == r); + return false; } } // namespace diff --git a/src/lib/pubkey/ecgdsa/ecgdsa.cpp b/src/lib/pubkey/ecgdsa/ecgdsa.cpp index cb97c405c43..502e4d30b23 100644 --- a/src/lib/pubkey/ecgdsa/ecgdsa.cpp +++ b/src/lib/pubkey/ecgdsa/ecgdsa.cpp @@ -1,17 +1,15 @@ /* * ECGDSA (BSI-TR-03111, version 2.0) * (C) 2016 René Korthaus -* (C) 2018 Jack Lloyd +* (C) 2018,2024 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include -#include #include #include -#include namespace Botan { @@ -39,7 +37,9 @@ namespace { class ECGDSA_Signature_Operation final : public PK_Ops::Signature_with_Hash { public: ECGDSA_Signature_Operation(const ECGDSA_PrivateKey& ecgdsa, std::string_view emsa) : - PK_Ops::Signature_with_Hash(emsa), m_group(ecgdsa.domain()), m_x(ecgdsa.private_value()) {} + PK_Ops::Signature_with_Hash(emsa), + m_group(ecgdsa.domain()), + m_x(EC_Scalar::from_bigint(m_group, ecgdsa.private_value())) {} secure_vector raw_sign(const uint8_t msg[], size_t msg_len, RandomNumberGenerator& rng) override; @@ -49,7 +49,7 @@ class ECGDSA_Signature_Operation final : public PK_Ops::Signature_with_Hash { private: const EC_Group m_group; - const BigInt m_x; + const EC_Scalar m_x; std::vector m_ws; }; @@ -62,22 +62,20 @@ AlgorithmIdentifier ECGDSA_Signature_Operation::algorithm_identifier() const { secure_vector ECGDSA_Signature_Operation::raw_sign(const uint8_t msg[], size_t msg_len, RandomNumberGenerator& rng) { - const BigInt m = BigInt::from_bytes_with_max_bits(msg, msg_len, m_group.get_order_bits()); - - const BigInt k = m_group.random_scalar(rng); + const auto m = EC_Scalar::from_bytes_with_trunc(m_group, std::span{msg, msg_len}); - const BigInt r = m_group.mod_order(m_group.blinded_base_point_multiply_x(k, rng, m_ws)); + const auto k = EC_Scalar::random(m_group, rng); - const BigInt kr = m_group.multiply_mod_order(k, r); + const auto r = EC_Scalar::gk_x_mod_order(k, rng, m_ws); - const BigInt s = m_group.multiply_mod_order(m_x, kr - m); + const auto s = m_x * ((k * r) - m); // With overwhelming probability, a bug rather than actual zero r/s if(r.is_zero() || s.is_zero()) { throw Internal_Error("During ECGDSA signature generated zero r/s"); } - return BigInt::encode_fixed_length_int_pair(r, s, m_group.get_order_bytes()); + return EC_Scalar::serialize_pair>(r, s); } /** @@ -88,46 +86,36 @@ class ECGDSA_Verification_Operation final : public PK_Ops::Verification_with_Has ECGDSA_Verification_Operation(const ECGDSA_PublicKey& ecgdsa, std::string_view padding) : PK_Ops::Verification_with_Hash(padding), m_group(ecgdsa.domain()), - m_gy_mul(m_group.get_base_point(), ecgdsa.public_point()) {} + m_gy_mul(m_group, ecgdsa.public_point()) {} ECGDSA_Verification_Operation(const ECGDSA_PublicKey& ecgdsa, const AlgorithmIdentifier& alg_id) : PK_Ops::Verification_with_Hash(alg_id, "ECGDSA"), m_group(ecgdsa.domain()), - m_gy_mul(m_group.get_base_point(), ecgdsa.public_point()) {} + m_gy_mul(m_group, ecgdsa.public_point()) {} bool verify(const uint8_t msg[], size_t msg_len, const uint8_t sig[], size_t sig_len) override; private: const EC_Group m_group; - const EC_Point_Multi_Point_Precompute m_gy_mul; + const EC_Group::Mul2Table m_gy_mul; }; bool ECGDSA_Verification_Operation::verify(const uint8_t msg[], size_t msg_len, const uint8_t sig[], size_t sig_len) { - if(sig_len != m_group.get_order_bytes() * 2) { - return false; - } - - const BigInt e = BigInt::from_bytes_with_max_bits(msg, msg_len, m_group.get_order_bits()); + if(auto rs = EC_Scalar::deserialize_pair(m_group, std::span{sig, sig_len})) { + const auto& [r, s] = rs.value(); - const BigInt r(sig, sig_len / 2); - const BigInt s(sig + sig_len / 2, sig_len / 2); + if(r.is_nonzero() && s.is_nonzero()) { + const auto m = EC_Scalar::from_bytes_with_trunc(m_group, std::span{msg, msg_len}); - if(r <= 0 || r >= m_group.get_order() || s <= 0 || s >= m_group.get_order()) { - return false; - } - - const BigInt w = m_group.inverse_mod_order(r); + const auto w = r.invert(); - const BigInt u1 = m_group.multiply_mod_order(e, w); - const BigInt u2 = m_group.multiply_mod_order(s, w); - const EC_Point R = m_gy_mul.multi_exp(u1, u2); - - if(R.is_zero()) { - return false; + if(const auto v = m_gy_mul.mul2_vartime_x_mod_order(w, m, s)) { + return (v == r); + } + } } - const BigInt v = m_group.mod_order(R.get_affine_x()); - return (v == r); + return false; } } // namespace diff --git a/src/lib/pubkey/ecies/ecies.cpp b/src/lib/pubkey/ecies/ecies.cpp index 78785ef18df..681060ed296 100644 --- a/src/lib/pubkey/ecies/ecies.cpp +++ b/src/lib/pubkey/ecies/ecies.cpp @@ -66,17 +66,12 @@ class ECIES_ECDH_KA_Operation final : public PK_Ops::Key_Agreement_with_KDF { secure_vector raw_agree(const uint8_t w[], size_t w_len) override { const EC_Group& group = m_key.domain(); - - EC_Point input_point = group.OS2ECP(w, w_len); - input_point.randomize_repr(m_rng); - - const EC_Point S = group.blinded_var_point_multiply(input_point, m_key.private_value(), m_rng, m_ws); - - if(S.on_the_curve() == false) { - throw Internal_Error("ECDH agreed value was not on the curve"); + const auto x = EC_Scalar::from_bigint(group, m_key.private_value()); + if(auto input_point = EC_AffinePoint::deserialize(group, {w, w_len})) { + return input_point->mul(x, m_rng, m_ws).x_bytes(); + } else { + throw Decoding_Error("ECIES - Invalid elliptic curve point"); } - - return S.x_bytes(); } private: diff --git a/src/lib/pubkey/eckcdsa/eckcdsa.cpp b/src/lib/pubkey/eckcdsa/eckcdsa.cpp index fef3f800951..728d0d4be72 100644 --- a/src/lib/pubkey/eckcdsa/eckcdsa.cpp +++ b/src/lib/pubkey/eckcdsa/eckcdsa.cpp @@ -1,7 +1,7 @@ /* * ECKCDSA (ISO/IEC 14888-3:2006/Cor.2:2009) * (C) 2016 René Korthaus, Sirrix AG -* (C) 2018 Jack Lloyd +* (C) 2018,2024 Jack Lloyd * (C) 2023 Philippe Lieser - Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) @@ -10,13 +10,11 @@ #include #include -#include #include #include #include #include #include -#include #include #include @@ -119,7 +117,7 @@ class ECKCDSA_Signature_Operation final : public PK_Ops::Signature { public: ECKCDSA_Signature_Operation(const ECKCDSA_PrivateKey& eckcdsa, std::string_view padding) : m_group(eckcdsa.domain()), - m_x(eckcdsa.private_value()), + m_x(EC_Scalar::from_bigint(m_group, eckcdsa.private_value())), m_hash(eckcdsa_signature_hash(padding)), m_prefix_used(false) { m_prefix = eckcdsa_prefix(eckcdsa.public_point(), m_hash->hash_block_size()); @@ -150,7 +148,7 @@ class ECKCDSA_Signature_Operation final : public PK_Ops::Signature { secure_vector raw_sign(const uint8_t msg[], size_t msg_len, RandomNumberGenerator& rng); const EC_Group m_group; - const BigInt m_x; + const EC_Scalar m_x; std::unique_ptr m_hash; std::vector m_prefix; std::vector m_ws; @@ -166,26 +164,24 @@ AlgorithmIdentifier ECKCDSA_Signature_Operation::algorithm_identifier() const { secure_vector ECKCDSA_Signature_Operation::raw_sign(const uint8_t msg[], size_t msg_len, RandomNumberGenerator& rng) { - const BigInt k = m_group.random_scalar(rng); - const BigInt k_times_P_x = m_group.blinded_base_point_multiply_x(k, rng, m_ws); + const auto k = EC_Scalar::random(m_group, rng); - auto hash = m_hash->new_object(); - hash->update(k_times_P_x.serialize(m_group.get_order_bytes())); - secure_vector c = hash->final(); + m_hash->update(EC_AffinePoint::g_mul(k, rng, m_ws).x_bytes()); + secure_vector c = m_hash->final(); truncate_hash_if_needed(c, m_group.get_order_bytes()); const auto r = c; BOTAN_ASSERT_NOMSG(msg_len == c.size()); xor_buf(c, msg, c.size()); - const BigInt w = m_group.mod_order(BigInt::from_bytes(c)); + const auto w = EC_Scalar::from_bytes_mod_order(m_group, c); - const BigInt s = m_group.multiply_mod_order(m_x, k - w); + const auto s = m_x * (k - w); if(s.is_zero()) { throw Internal_Error("During ECKCDSA signature generation created zero s"); } - return concat(r, s.serialize(m_group.get_order_bytes())); + return concat(r, s.serialize()); } /** @@ -195,7 +191,7 @@ class ECKCDSA_Verification_Operation final : public PK_Ops::Verification { public: ECKCDSA_Verification_Operation(const ECKCDSA_PublicKey& eckcdsa, std::string_view padding) : m_group(eckcdsa.domain()), - m_gy_mul(m_group.get_base_point(), eckcdsa.public_point()), + m_gy_mul(m_group, eckcdsa.public_point()), m_hash(eckcdsa_signature_hash(padding)), m_prefix_used(false) { m_prefix = eckcdsa_prefix(eckcdsa.public_point(), m_hash->hash_block_size()); @@ -203,7 +199,7 @@ class ECKCDSA_Verification_Operation final : public PK_Ops::Verification { ECKCDSA_Verification_Operation(const ECKCDSA_PublicKey& eckcdsa, const AlgorithmIdentifier& alg_id) : m_group(eckcdsa.domain()), - m_gy_mul(m_group.get_base_point(), eckcdsa.public_point()), + m_gy_mul(m_group, eckcdsa.public_point()), m_hash(eckcdsa_signature_hash(alg_id)), m_prefix_used(false) { m_prefix = eckcdsa_prefix(eckcdsa.public_point(), m_hash->hash_block_size()); @@ -219,7 +215,7 @@ class ECKCDSA_Verification_Operation final : public PK_Ops::Verification { bool verify(const uint8_t msg[], size_t msg_len, const uint8_t sig[], size_t sig_len); const EC_Group m_group; - const EC_Point_Multi_Point_Precompute m_gy_mul; + const EC_Group::Mul2Table m_gy_mul; std::vector m_prefix; std::unique_ptr m_hash; bool m_prefix_used; @@ -252,29 +248,21 @@ bool ECKCDSA_Verification_Operation::verify(const uint8_t msg[], size_t msg_len, secure_vector r(sig, sig + size_r); - // check that 0 < s < q - const BigInt s(sig + size_r, order_bytes); + if(auto s = EC_Scalar::deserialize(m_group, std::span{sig, sig_len}.last(order_bytes))) { + secure_vector r_xor_e(r); + xor_buf(r_xor_e, msg, r.size()); - if(s <= 0 || s >= m_group.get_order()) { - return false; - } - - secure_vector r_xor_e(r); - xor_buf(r_xor_e, msg, r.size()); + const auto w = EC_Scalar::from_bytes_mod_order(m_group, r_xor_e); - const BigInt w = m_group.mod_order(BigInt::from_bytes(r_xor_e)); + if(auto q = m_gy_mul.mul2_vartime(w, s.value())) { + secure_vector v = m_hash->process(q->x_bytes()); + truncate_hash_if_needed(v, m_group.get_order_bytes()); - const EC_Point q = m_gy_mul.multi_exp(w, s); - if(q.is_zero()) { - return false; + return (v == r); + } } - auto c_hash = m_hash->new_object(); - c_hash->update(q.x_bytes()); - secure_vector v = c_hash->final(); - truncate_hash_if_needed(v, m_group.get_order_bytes()); - - return (v == r); + return false; } } // namespace diff --git a/src/lib/pubkey/gost_3410/gost_3410.cpp b/src/lib/pubkey/gost_3410/gost_3410.cpp index 99439fd89d4..f8740fc4cce 100644 --- a/src/lib/pubkey/gost_3410/gost_3410.cpp +++ b/src/lib/pubkey/gost_3410/gost_3410.cpp @@ -2,7 +2,7 @@ * GOST 34.10-2012 * (C) 2007 Falko Strenzke, FlexSecure GmbH * Manuel Hartl, FlexSecure GmbH -* (C) 2008-2010,2015,2018 Jack Lloyd +* (C) 2008-2010,2015,2018,2024 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -11,10 +11,8 @@ #include #include -#include #include #include -#include namespace Botan { @@ -72,7 +70,7 @@ GOST_3410_PublicKey::GOST_3410_PublicKey(const AlgorithmIdentifier& alg_id, std: BER_Decoder(key_bits).decode(bits, ASN1_Type::OctetString); if(bits.size() != 2 * (p_bits / 8)) { - throw Decoding_Error("GOST-34.10-2020 invalid encoding of public key"); + throw Decoding_Error("GOST-34.10-2012 invalid encoding of public key"); } const size_t part_size = bits.size() / 2; @@ -105,14 +103,15 @@ std::unique_ptr GOST_3410_PrivateKey::public_key() const { namespace { -BigInt decode_le(const uint8_t msg[], size_t msg_len) { - secure_vector msg_le(msg, msg + msg_len); +EC_Scalar gost_msg_to_scalar(const EC_Group& group, std::span msg) { + std::vector rev_bytes(msg.rbegin(), msg.rend()); - for(size_t i = 0; i != msg_le.size() / 2; ++i) { - std::swap(msg_le[i], msg_le[msg_le.size() - 1 - i]); + auto ie = EC_Scalar::from_bytes_mod_order(group, rev_bytes); + if(ie.is_zero()) { + return EC_Scalar::one(group); + } else { + return ie; } - - return BigInt(msg_le.data(), msg_le.size()); } /** @@ -121,7 +120,9 @@ BigInt decode_le(const uint8_t msg[], size_t msg_len) { class GOST_3410_Signature_Operation final : public PK_Ops::Signature_with_Hash { public: GOST_3410_Signature_Operation(const GOST_3410_PrivateKey& gost_3410, std::string_view emsa) : - PK_Ops::Signature_with_Hash(emsa), m_group(gost_3410.domain()), m_x(gost_3410.private_value()) {} + PK_Ops::Signature_with_Hash(emsa), + m_group(gost_3410.domain()), + m_x(EC_Scalar::from_bigint(m_group, gost_3410.private_value())) {} size_t signature_length() const override { return 2 * m_group.get_order_bytes(); } @@ -131,7 +132,7 @@ class GOST_3410_Signature_Operation final : public PK_Ops::Signature_with_Hash { private: const EC_Group m_group; - const BigInt m_x; + const EC_Scalar m_x; std::vector m_ws; }; @@ -161,24 +162,17 @@ AlgorithmIdentifier GOST_3410_Signature_Operation::algorithm_identifier() const secure_vector GOST_3410_Signature_Operation::raw_sign(const uint8_t msg[], size_t msg_len, RandomNumberGenerator& rng) { - const BigInt k = m_group.random_scalar(rng); - - BigInt e = decode_le(msg, msg_len); - - e = m_group.mod_order(e); - if(e.is_zero()) { - e = BigInt::one(); - } - - const BigInt r = m_group.mod_order(m_group.blinded_base_point_multiply_x(k, rng, m_ws)); + const auto e = gost_msg_to_scalar(m_group, std::span{msg, msg_len}); - const BigInt s = m_group.mod_order(m_group.multiply_mod_order(r, m_x) + m_group.multiply_mod_order(k, e)); + const auto k = EC_Scalar::random(m_group, rng); + const auto r = EC_Scalar::gk_x_mod_order(k, rng, m_ws); + const auto s = (r * m_x) + (k * e); - if(r == 0 || s == 0) { + if(r.is_zero() || s.is_zero()) { throw Internal_Error("GOST 34.10 signature generation failed, r/s equal to zero"); } - return BigInt::encode_fixed_length_int_pair(s, r, m_group.get_order_bytes()); + return EC_Scalar::serialize_pair>(s, r); } std::string gost_hash_from_algid(const AlgorithmIdentifier& alg_id) { @@ -209,57 +203,39 @@ std::string gost_hash_from_algid(const AlgorithmIdentifier& alg_id) { class GOST_3410_Verification_Operation final : public PK_Ops::Verification_with_Hash { public: GOST_3410_Verification_Operation(const GOST_3410_PublicKey& gost, std::string_view padding) : - PK_Ops::Verification_with_Hash(padding), - m_group(gost.domain()), - m_gy_mul(m_group.get_base_point(), gost.public_point()) {} + PK_Ops::Verification_with_Hash(padding), m_group(gost.domain()), m_gy_mul(m_group, gost.public_point()) {} GOST_3410_Verification_Operation(const GOST_3410_PublicKey& gost, const AlgorithmIdentifier& alg_id) : PK_Ops::Verification_with_Hash(gost_hash_from_algid(alg_id)), m_group(gost.domain()), - m_gy_mul(m_group.get_base_point(), gost.public_point()) {} + m_gy_mul(m_group, gost.public_point()) {} bool verify(const uint8_t msg[], size_t msg_len, const uint8_t sig[], size_t sig_len) override; private: const EC_Group m_group; - const EC_Point_Multi_Point_Precompute m_gy_mul; + const EC_Group::Mul2Table m_gy_mul; }; bool GOST_3410_Verification_Operation::verify(const uint8_t msg[], size_t msg_len, const uint8_t sig[], size_t sig_len) { - if(sig_len != m_group.get_order_bytes() * 2) { - return false; - } - - const BigInt s(sig, sig_len / 2); - const BigInt r(sig + sig_len / 2, sig_len / 2); - - const BigInt& order = m_group.get_order(); - - if(r <= 0 || r >= order || s <= 0 || s >= order) { - return false; - } - - BigInt e = decode_le(msg, msg_len); - e = m_group.mod_order(e); - if(e.is_zero()) { - e = BigInt::one(); - } - - const BigInt v = m_group.inverse_mod_order(e); + if(auto sr = EC_Scalar::deserialize_pair(m_group, std::span{sig, sig_len})) { + const auto& [s, r] = sr.value(); - const BigInt z1 = m_group.multiply_mod_order(s, v); - const BigInt z2 = m_group.multiply_mod_order(-r, v); + if(r.is_nonzero() && s.is_nonzero()) { + const auto e = gost_msg_to_scalar(m_group, std::span{msg, msg_len}); - const EC_Point R = m_gy_mul.multi_exp(z1, z2); + const auto v = e.invert(); - if(R.is_zero()) { - return false; + if(const auto w = m_gy_mul.mul2_vartime_x_mod_order(v, s, r.negate())) { + return (w == r); + } + } } - return (R.get_affine_x() == r); + return false; } } // namespace diff --git a/src/lib/pubkey/rfc6979/rfc6979.cpp b/src/lib/pubkey/rfc6979/rfc6979.cpp index d504e05cc02..cccf3448750 100644 --- a/src/lib/pubkey/rfc6979/rfc6979.cpp +++ b/src/lib/pubkey/rfc6979/rfc6979.cpp @@ -26,24 +26,54 @@ RFC6979_Nonce_Generator::RFC6979_Nonce_Generator(std::string_view hash, const Bi RFC6979_Nonce_Generator::~RFC6979_Nonce_Generator() = default; -const BigInt& RFC6979_Nonce_Generator::nonce_for(const BigInt& m) { - m.serialize_to(std::span{m_rng_in}.subspan(m_rlen)); - m_hmac_drbg->clear(); - m_hmac_drbg->initialize_with(m_rng_in.data(), m_rng_in.size()); +BigInt RFC6979_Nonce_Generator::nonce_for(const BigInt& m) { + m.serialize_to(std::span{m_rng_in}.last(m_rlen)); + + m_hmac_drbg->initialize_with(m_rng_in); + + const size_t shift = 8 * m_rlen - m_qlen; + BOTAN_ASSERT_NOMSG(shift < 8); + + BigInt k; do { - m_hmac_drbg->randomize(m_rng_out.data(), m_rng_out.size()); - m_k._assign_from_bytes(m_rng_out); - m_k >>= (8 * m_rlen - m_qlen); - } while(m_k == 0 || m_k >= m_order); + m_hmac_drbg->randomize(m_rng_out); + k._assign_from_bytes(m_rng_out); - return m_k; -} + if(shift > 0) { + k >>= shift; + } + } while(k == 0 || k >= m_order); -BigInt generate_rfc6979_nonce(const BigInt& x, const BigInt& q, const BigInt& h, std::string_view hash) { - RFC6979_Nonce_Generator gen(hash, q, x); - BigInt k = gen.nonce_for(h); return k; } +#if defined(BOTAN_HAS_ECC_GROUP) +EC_Scalar RFC6979_Nonce_Generator::nonce_for(const EC_Group& group, const EC_Scalar& m) { + m.serialize_to(std::span{m_rng_in}.last(m_rlen)); + + m_hmac_drbg->initialize_with(m_rng_in); + + const size_t shift = 8 * m_rlen - m_qlen; + BOTAN_ASSERT_NOMSG(shift < 8); + + for(;;) { + m_hmac_drbg->randomize(m_rng_out); + + if(shift > 0) { + uint8_t carry = 0; + for(uint8_t& b : m_rng_out) { + const uint8_t w = b; + b = (w >> shift) | carry; + carry = w << (8 - shift); + } + } + + if(auto k = EC_Scalar::deserialize(group, m_rng_out)) { + return *k; + } + } +} +#endif + } // namespace Botan diff --git a/src/lib/pubkey/rfc6979/rfc6979.h b/src/lib/pubkey/rfc6979/rfc6979.h index 6bb20987a14..8dabc7f269e 100644 --- a/src/lib/pubkey/rfc6979/rfc6979.h +++ b/src/lib/pubkey/rfc6979/rfc6979.h @@ -12,6 +12,10 @@ #include #include +#if defined(BOTAN_HAS_ECC_GROUP) + #include +#endif + namespace Botan { class HMAC_DRBG; @@ -25,11 +29,14 @@ class BOTAN_TEST_API RFC6979_Nonce_Generator final { ~RFC6979_Nonce_Generator(); - const BigInt& nonce_for(const BigInt& m); +#if defined(BOTAN_HAS_ECC_GROUP) + EC_Scalar nonce_for(const EC_Group& group, const EC_Scalar& m); +#endif + + BigInt nonce_for(const BigInt& m); private: const BigInt& m_order; - BigInt m_k; size_t m_qlen, m_rlen; std::unique_ptr m_hmac_drbg; secure_vector m_rng_in, m_rng_out; @@ -41,8 +48,10 @@ class BOTAN_TEST_API RFC6979_Nonce_Generator final { * @param h the message hash already reduced mod q * @param hash the hash function used to generate h */ -BOTAN_TEST_API -BigInt generate_rfc6979_nonce(const BigInt& x, const BigInt& q, const BigInt& h, std::string_view hash); +inline BigInt generate_rfc6979_nonce(const BigInt& x, const BigInt& q, const BigInt& h, std::string_view hash) { + RFC6979_Nonce_Generator gen(hash, q, x); + return gen.nonce_for(h); +} } // namespace Botan diff --git a/src/lib/pubkey/sm2/sm2.cpp b/src/lib/pubkey/sm2/sm2.cpp index 7e32fc70943..d3f04ca8063 100644 --- a/src/lib/pubkey/sm2/sm2.cpp +++ b/src/lib/pubkey/sm2/sm2.cpp @@ -1,7 +1,7 @@ /* * SM2 Signatures * (C) 2017,2018 Ribose Inc -* (C) 2018 Jack Lloyd +* (C) 2018,2024 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -14,7 +14,6 @@ #include #include #include -#include namespace Botan { @@ -88,7 +87,9 @@ namespace { class SM2_Signature_Operation final : public PK_Ops::Signature { public: SM2_Signature_Operation(const SM2_PrivateKey& sm2, std::string_view ident, std::string_view hash) : - m_group(sm2.domain()), m_x(sm2.private_value()), m_da_inv(sm2.get_da_inv()) { + m_group(sm2.domain()), + m_x(EC_Scalar::from_bigint(m_group, sm2.private_value())), + m_da_inv(EC_Scalar::from_bigint(m_group, sm2.get_da_inv())) { if(hash == "Raw") { // m_hash is null, m_za is empty } else { @@ -115,8 +116,8 @@ class SM2_Signature_Operation final : public PK_Ops::Signature { private: const EC_Group m_group; - const BigInt m_x; - const BigInt m_da_inv; + const EC_Scalar m_x; + const EC_Scalar m_da_inv; std::vector m_za; secure_vector m_digest; @@ -125,22 +126,25 @@ class SM2_Signature_Operation final : public PK_Ops::Signature { }; secure_vector SM2_Signature_Operation::sign(RandomNumberGenerator& rng) { - BigInt e; - if(m_hash) { - e = BigInt::from_bytes(m_hash->final()); - // prepend ZA for next signature if any - m_hash->update(m_za); - } else { - e = BigInt::from_bytes(m_digest); - m_digest.clear(); - } + const auto e = [&]() { + if(m_hash) { + auto ie = EC_Scalar::from_bytes_mod_order(m_group, m_hash->final()); + // prepend ZA for next signature if any + m_hash->update(m_za); + return ie; + } else { + auto ie = EC_Scalar::from_bytes_mod_order(m_group, m_digest); + m_digest.clear(); + return ie; + } + }(); - const BigInt k = m_group.random_scalar(rng); + const auto k = EC_Scalar::random(m_group, rng); - const BigInt r = m_group.mod_order(m_group.blinded_base_point_multiply_x(k, rng, m_ws) + e); - const BigInt s = m_group.multiply_mod_order(m_da_inv, m_group.mod_order(k - r * m_x)); + const auto r = EC_Scalar::gk_x_mod_order(k, rng, m_ws) + e; + const auto s = (k - r * m_x) * m_da_inv; - return BigInt::encode_fixed_length_int_pair(r, s, m_group.get_order_bytes()); + return EC_Scalar::serialize_pair>(r, s); } /** @@ -149,7 +153,7 @@ secure_vector SM2_Signature_Operation::sign(RandomNumberGenerator& rng) class SM2_Verification_Operation final : public PK_Ops::Verification { public: SM2_Verification_Operation(const SM2_PublicKey& sm2, std::string_view ident, std::string_view hash) : - m_group(sm2.domain()), m_gy_mul(m_group.get_base_point(), sm2.public_point()) { + m_group(sm2.domain()), m_gy_mul(m_group, sm2.public_point()) { if(hash == "Raw") { // m_hash is null, m_za is empty } else { @@ -174,48 +178,39 @@ class SM2_Verification_Operation final : public PK_Ops::Verification { private: const EC_Group m_group; - const EC_Point_Multi_Point_Precompute m_gy_mul; + const EC_Group::Mul2Table m_gy_mul; secure_vector m_digest; std::vector m_za; std::unique_ptr m_hash; }; bool SM2_Verification_Operation::is_valid_signature(const uint8_t sig[], size_t sig_len) { - BigInt e; - if(m_hash) { - e = BigInt::from_bytes(m_hash->final()); - // prepend ZA for next signature if any - m_hash->update(m_za); - } else { - e = BigInt::from_bytes(m_digest); - m_digest.clear(); - } - - if(sig_len != m_group.get_order_bytes() * 2) { - return false; - } - - const BigInt r(sig, sig_len / 2); - const BigInt s(sig + sig_len / 2, sig_len / 2); - - if(r <= 0 || r >= m_group.get_order() || s <= 0 || s >= m_group.get_order()) { - return false; - } - - const BigInt t = m_group.mod_order(r + s); - - if(t == 0) { - return false; - } + const auto e = [&]() { + if(m_hash) { + auto ie = EC_Scalar::from_bytes_mod_order(m_group, m_hash->final()); + // prepend ZA for next signature if any + m_hash->update(m_za); + return ie; + } else { + auto ie = EC_Scalar::from_bytes_mod_order(m_group, m_digest); + m_digest.clear(); + return ie; + } + }(); - const EC_Point R = m_gy_mul.multi_exp(s, t); + if(auto rs = EC_Scalar::deserialize_pair(m_group, std::span{sig, sig_len})) { + const auto& [r, s] = rs.value(); - // ??? - if(R.is_zero()) { - return false; + if(r.is_nonzero() && s.is_nonzero()) { + const auto t = r + s; + if(t.is_nonzero()) { + if(const auto v = m_gy_mul.mul2_vartime_x_mod_order(s, t)) { + return (v.value() + e) == r; + } + } + } } - - return (m_group.mod_order(R.get_affine_x() + e) == r); + return false; } void parse_sm2_param_string(std::string_view params, std::string& userid, std::string& hash) { diff --git a/src/lib/pubkey/sm2/sm2_enc.cpp b/src/lib/pubkey/sm2/sm2_enc.cpp index 96acf8d2ec6..c54601333af 100644 --- a/src/lib/pubkey/sm2/sm2_enc.cpp +++ b/src/lib/pubkey/sm2/sm2_enc.cpp @@ -14,7 +14,6 @@ #include #include #include -#include namespace Botan { @@ -22,14 +21,10 @@ namespace { class SM2_Encryption_Operation final : public PK_Ops::Encryption { public: - SM2_Encryption_Operation(const SM2_Encryption_PublicKey& key, - RandomNumberGenerator& rng, - std::string_view kdf_hash) : - m_group(key.domain()), m_ws(EC_Point::WORKSPACE_SIZE), m_mul_public_point(key.public_point(), rng, m_ws) { + SM2_Encryption_Operation(const SM2_Encryption_PublicKey& key, std::string_view kdf_hash) : + m_group(key.domain()), m_peer(m_group, key.public_point()), m_ws(EC_Point::WORKSPACE_SIZE) { m_hash = HashFunction::create_or_throw(kdf_hash); - - const std::string kdf_name = fmt("KDF2({})", kdf_hash); - m_kdf = KDF::create_or_throw(kdf_name); + m_kdf = KDF::create_or_throw(fmt("KDF2({})", kdf_hash)); } size_t max_input_bits() const override { @@ -45,13 +40,11 @@ class SM2_Encryption_Operation final : public PK_Ops::Encryption { } secure_vector encrypt(const uint8_t msg[], size_t msg_len, RandomNumberGenerator& rng) override { - const BigInt k = m_group.random_scalar(rng); + const auto k = EC_Scalar::random(m_group, rng); - const EC_Point C1 = m_group.blinded_base_point_multiply(k, rng, m_ws); - const BigInt x1 = C1.get_affine_x(); - const BigInt y1 = C1.get_affine_y(); + const EC_AffinePoint C1 = EC_AffinePoint::g_mul(k, rng, m_ws); - const EC_Point kPB = m_mul_public_point.mul(k, rng, m_group.get_order(), m_ws); + const EC_AffinePoint kPB = m_peer.mul(k, rng, m_ws); const auto x2_bytes = kPB.x_bytes(); const auto y2_bytes = kPB.y_bytes(); @@ -72,8 +65,8 @@ class SM2_Encryption_Operation final : public PK_Ops::Encryption { return DER_Encoder() .start_sequence() - .encode(x1) - .encode(y1) + .encode(BigInt(C1.x_bytes())) + .encode(BigInt(C1.y_bytes())) .encode(C3, ASN1_Type::OctetString) .encode(masked_msg, ASN1_Type::OctetString) .end_cons() @@ -82,10 +75,10 @@ class SM2_Encryption_Operation final : public PK_Ops::Encryption { private: const EC_Group m_group; + const EC_AffinePoint m_peer; std::unique_ptr m_hash; std::unique_ptr m_kdf; std::vector m_ws; - EC_Point_Var_Point_Precompute m_mul_public_point; }; class SM2_Decryption_Operation final : public PK_Ops::Decryption { @@ -93,7 +86,7 @@ class SM2_Decryption_Operation final : public PK_Ops::Decryption { SM2_Decryption_Operation(const SM2_Encryption_PrivateKey& key, RandomNumberGenerator& rng, std::string_view kdf_hash) : - m_key(key), m_rng(rng) { + m_group(key.domain()), m_x(EC_Scalar::from_bigint(m_group, key.private_value())), m_rng(rng) { m_hash = HashFunction::create_or_throw(kdf_hash); const std::string kdf_name = fmt("KDF2({})", kdf_hash); @@ -105,7 +98,7 @@ class SM2_Decryption_Operation final : public PK_Ops::Decryption { * This ignores the DER encoding and so overestimates the * plaintext length by 12 bytes or so */ - const size_t elem_size = m_key.domain().get_order_bytes(); + const size_t elem_size = m_group.get_order_bytes(); if(ptext_len < 2 * elem_size + m_hash->output_length()) { return 0; @@ -115,9 +108,8 @@ class SM2_Decryption_Operation final : public PK_Ops::Decryption { } secure_vector decrypt(uint8_t& valid_mask, const uint8_t ciphertext[], size_t ciphertext_len) override { - const EC_Group& group = m_key.domain(); - const BigInt& cofactor = group.get_cofactor(); - const size_t p_bytes = group.get_p_bytes(); + const BigInt& cofactor = m_group.get_cofactor(); + const size_t p_bytes = m_group.get_p_bytes(); valid_mask = 0x00; @@ -155,8 +147,7 @@ class SM2_Decryption_Operation final : public PK_Ops::Decryption { return secure_vector(); } - EC_Point C1 = group.point(x1, y1); - C1.randomize_repr(m_rng); + EC_Point C1 = m_group.point(x1, y1); // Here C1 is publically invalid, so no problem with early return: if(!C1.on_the_curve()) { @@ -167,24 +158,18 @@ class SM2_Decryption_Operation final : public PK_Ops::Decryption { return secure_vector(); } - const EC_Point dbC1 = group.blinded_var_point_multiply(C1, m_key.private_value(), m_rng, m_ws); - + const auto dbC1 = EC_AffinePoint(m_group, C1).mul(m_x, m_rng, m_ws); const auto x2_bytes = dbC1.x_bytes(); const auto y2_bytes = dbC1.y_bytes(); - secure_vector kdf_input; - kdf_input += x2_bytes; - kdf_input += y2_bytes; - - const secure_vector kdf_output = - m_kdf->derive_key(masked_msg.size(), kdf_input.data(), kdf_input.size()); + const auto kdf_output = m_kdf->derive_key(masked_msg.size(), dbC1.xy_bytes()); xor_buf(masked_msg.data(), kdf_output.data(), kdf_output.size()); m_hash->update(x2_bytes); m_hash->update(masked_msg); m_hash->update(y2_bytes); - secure_vector u = m_hash->final(); + const auto u = m_hash->final(); if(!CT::is_equal(u.data(), C3.data(), m_hash->output_length()).as_bool()) { return secure_vector(); @@ -195,7 +180,8 @@ class SM2_Decryption_Operation final : public PK_Ops::Decryption { } private: - const SM2_Encryption_PrivateKey& m_key; + const EC_Group m_group; + const EC_Scalar m_x; RandomNumberGenerator& m_rng; std::vector m_ws; std::unique_ptr m_hash; @@ -207,11 +193,13 @@ class SM2_Decryption_Operation final : public PK_Ops::Decryption { std::unique_ptr SM2_PublicKey::create_encryption_op(RandomNumberGenerator& rng, std::string_view params, std::string_view provider) const { + BOTAN_UNUSED(rng); + if(provider == "base" || provider.empty()) { if(params.empty()) { - return std::make_unique(*this, rng, "SM3"); + return std::make_unique(*this, "SM3"); } else { - return std::make_unique(*this, rng, params); + return std::make_unique(*this, params); } } diff --git a/src/tests/data/pubkey/ecdsa_verify.vec b/src/tests/data/pubkey/ecdsa_verify.vec index 8bf5b572983..dd538ff9f68 100644 --- a/src/tests/data/pubkey/ecdsa_verify.vec +++ b/src/tests/data/pubkey/ecdsa_verify.vec @@ -69,14 +69,6 @@ Py = 81333916963110019576228330948951168219884247801435258672405011123948094 Msg = 0679246D6C4216DE0DAA08E5523FB2674DB2B6599C3B72FF946B488A15290B62 Signature = 30cf3ae9da8c18ef37664e358e43b07f93ded599653e64acd171e197a1c72f9ad521e5e2e091e9fe4c27f1110265ec5cbb701a6faf3569304774de5f -# From CryptoFuzz -Group = secp256k1 -Valid = 0 -Px = 70000000000000000 -Py = 0 -Msg = d1a0b2537473db2edf298bab7cb968882ab9d558c2a6298b4556ee9d36298cae -Signature = 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798D1A0B2537473DB2EDF298BAB7CB968882AB9D558C2A6298B4556EE9D36298CAE - # OSS-Fuzz 32470 Group = secp256k1 Valid = 1 diff --git a/src/tests/test_ecc_pointmul.cpp b/src/tests/test_ecc_pointmul.cpp index 8953b855590..6a096f7feef 100644 --- a/src/tests/test_ecc_pointmul.cpp +++ b/src/tests/test_ecc_pointmul.cpp @@ -47,6 +47,14 @@ class ECC_Basepoint_Mul_Tests final : public Text_Based_Test { const Botan::EC_Point p3 = group.blinded_var_point_multiply(base_point, k, this->rng(), ws); result.test_eq("blinded_var_point_multiply", p3, pt); + const auto scalar = Botan::EC_Scalar::from_bigint(group, k); + const auto apg = Botan::EC_AffinePoint::g_mul(scalar, this->rng(), ws); + result.test_eq("AffinePoint::g_mul", apg.serialize_uncompressed(), P_bytes); + + const auto ag = Botan::EC_AffinePoint(group, base_point); + const auto ap = ag.mul(scalar, this->rng(), ws); + result.test_eq("AffinePoint::mul", ap.to_legacy_point(), pt); + return result; } };