diff --git a/src/lib/math/pcurves/pcurves.h b/src/lib/math/pcurves/pcurves.h index 9a74fe12e13..0755a24d15f 100644 --- a/src/lib/math/pcurves/pcurves.h +++ b/src/lib/math/pcurves/pcurves.h @@ -415,14 +415,22 @@ class BOTAN_TEST_API PrimeOrderCurve { virtual Scalar random_scalar(RandomNumberGenerator& rng) const = 0; /** - * RFC 9380 hash to curve + * RFC 9380 hash to curve (NU variant) * * This is currently only supported for a few specific curves */ - virtual ProjectivePoint hash_to_curve(std::string_view hash, - std::span input, - std::span domain_sep, - bool random_oracle) const = 0; + virtual AffinePoint hash_to_curve_nu(std::string_view hash, + std::span input, + std::span domain_sep) const = 0; + + /** + * RFC 9380 hash to curve (RO variant) + * + * This is currently only supported for a few specific curves + */ + virtual ProjectivePoint hash_to_curve_ro(std::string_view hash, + std::span input, + std::span domain_sep) const = 0; }; } // namespace Botan::PCurve diff --git a/src/lib/math/pcurves/pcurves_impl/pcurves_impl.h b/src/lib/math/pcurves/pcurves_impl/pcurves_impl.h index 2d53756c82b..26dda5a6aa4 100644 --- a/src/lib/math/pcurves/pcurves_impl/pcurves_impl.h +++ b/src/lib/math/pcurves/pcurves_impl/pcurves_impl.h @@ -20,6 +20,8 @@ namespace Botan { +namespace { + template class MontgomeryRep final { public: @@ -848,77 +850,6 @@ class ProjectiveCurvePoint { constexpr Self negate() const { return Self(x(), y().negate(), z()); } - constexpr AffinePoint to_affine() const { - // Not strictly required right? - default should work as long - // as (0,0) is identity and invert returns 0 on 0 - if(this->is_identity().as_bool()) { - return AffinePoint::identity(); - } - - const auto z_inv = m_z.invert(); - const auto z2_inv = z_inv.square(); - const auto z3_inv = z_inv * z2_inv; - - const auto x = m_x * z2_inv; - const auto y = m_y * z3_inv; - return AffinePoint(x, y); - } - - static std::vector to_affine_batch(std::span projective) { - const size_t N = projective.size(); - std::vector affine(N, AffinePoint::identity()); - - bool any_identity = false; - for(size_t i = 0; i != N; ++i) { - if(projective[i].is_identity().as_bool()) { - any_identity = true; - // If any of the elements are the identity we fall back to - // performing the conversion without a batch - break; - } - } - - if(N <= 2 || any_identity) { - for(size_t i = 0; i != N; ++i) { - affine[i] = projective[i].to_affine(); - } - } else { - std::vector c(N); - - /* - Batch projective->affine using Montgomery's trick - - See Algorithm 2.26 in "Guide to Elliptic Curve Cryptography" - (Hankerson, Menezes, Vanstone) - */ - - c[0] = projective[0].z(); - for(size_t i = 1; i != N; ++i) { - c[i] = c[i - 1] * projective[i].z(); - } - - auto s_inv = c[N - 1].invert(); - - for(size_t i = N - 1; i > 0; --i) { - const auto& p = projective[i]; - - const auto z_inv = s_inv * c[i - 1]; - const auto z2_inv = z_inv.square(); - const auto z3_inv = z_inv * z2_inv; - - s_inv = s_inv * p.z(); - - affine[i] = AffinePoint(p.x() * z2_inv, p.y() * z3_inv); - } - - const auto z2_inv = s_inv.square(); - const auto z3_inv = s_inv * z2_inv; - affine[0] = AffinePoint(projective[0].x() * z2_inv, projective[0].y() * z3_inv); - } - - return affine; - } - void randomize_rep(RandomNumberGenerator& rng) { if(!rng.is_seeded()) { return; @@ -1017,24 +948,11 @@ class EllipticCurve { (Params::Z != 0 && A.is_nonzero().as_bool() && B.is_nonzero().as_bool() && FieldElement::P_MOD_4 == 3); static constexpr bool OrderIsLessThanField = bigint_cmp(NW.data(), NW.size(), PW.data(), PW.size()) == -1; +}; - // (-B / A), will be zero if A == 0 or B == 0 or Z == 0 - static const FieldElement& SSWU_C1() - requires ValidForSswuHash - { - // We derive it from C2 to avoid a second inversion - static const auto C1 = (SSWU_C2() * SSWU_Z).negate(); - return C1; - } - - // (B / (Z * A)), will be zero if A == 0 or B == 0 or Z == 0 - static const FieldElement& SSWU_C2() - requires ValidForSswuHash - { - // This could use a variable time inversion - static const auto C2 = (B * (SSWU_Z * A).invert()); - return C2; - } +template +concept curve_supports_fe_invert2 = requires(const typename C::FieldElement& fe) { + { C::fe_invert2(fe) } -> std::same_as; }; /** @@ -1140,6 +1058,111 @@ class UnblindedScalarBits final { std::array m_bytes; }; +template +inline auto invert_field_element(const typename C::FieldElement& fe) { + if constexpr(curve_supports_fe_invert2) { + return C::fe_invert2(fe) * fe; + } else { + return fe.invert(); + } +} + +/// Convert a projective point into affine +template +auto to_affine(const typename C::ProjectivePoint& pt) { + // Not strictly required right? - default should work as long + // as (0,0) is identity and invert returns 0 on 0 + if(pt.is_identity().as_bool()) { + return C::AffinePoint::identity(); + } + + if constexpr(curve_supports_fe_invert2) { + const auto z2_inv = C::fe_invert2(pt.z()); + const auto z3_inv = z2_inv.square() * pt.z(); + return typename C::AffinePoint(pt.x() * z2_inv, pt.y() * z3_inv); + } else { + const auto z_inv = invert_field_element(pt.z()); + const auto z2_inv = z_inv.square(); + const auto z3_inv = z_inv * z2_inv; + return typename C::AffinePoint(pt.x() * z2_inv, pt.y() * z3_inv); + } +} + +/// Convert a projective point into affine and return x coordinate only +template +auto to_affine_x(const typename C::ProjectivePoint& pt) { + if constexpr(curve_supports_fe_invert2) { + return pt.x() * C::fe_invert2(pt.z()); + } else { + return to_affine(pt).x(); + } +} + +/** +* Batch projective->affine conversion +*/ +template +auto to_affine_batch(std::span projective) { + typedef typename C::AffinePoint AffinePoint; + typedef typename C::FieldElement FieldElement; + + const size_t N = projective.size(); + std::vector affine(N, AffinePoint::identity()); + + bool any_identity = false; + for(size_t i = 0; i != N; ++i) { + if(projective[i].is_identity().as_bool()) { + any_identity = true; + // If any of the elements are the identity we fall back to + // performing the conversion without a batch + break; + } + } + + if(N <= 2 || any_identity) { + // If there are identity elements, using the batch inversion gets + // tricky. It can be done, but this should be a rare situation so + // just punt to the serial conversion if it occurs + for(size_t i = 0; i != N; ++i) { + affine[i] = to_affine(projective[i]); + } + } else { + std::vector c(N); + + /* + Batch projective->affine using Montgomery's trick + + See Algorithm 2.26 in "Guide to Elliptic Curve Cryptography" + (Hankerson, Menezes, Vanstone) + */ + + c[0] = projective[0].z(); + for(size_t i = 1; i != N; ++i) { + c[i] = c[i - 1] * projective[i].z(); + } + + auto s_inv = invert_field_element(c[N - 1]); + + for(size_t i = N - 1; i > 0; --i) { + const auto& p = projective[i]; + + const auto z_inv = s_inv * c[i - 1]; + const auto z2_inv = z_inv.square(); + const auto z3_inv = z_inv * z2_inv; + + s_inv = s_inv * p.z(); + + affine[i] = AffinePoint(p.x() * z2_inv, p.y() * z3_inv); + } + + const auto z2_inv = s_inv.square(); + const auto z3_inv = s_inv * z2_inv; + affine[0] = AffinePoint(projective[0].x() * z2_inv, projective[0].y() * z3_inv); + } + + return affine; +} + /** * Base point precomputation table * @@ -1218,7 +1241,7 @@ class PrecomputedBaseMulTable final { accum = table[i + (WindowElements / 2)].dbl(); } - m_table = ProjectivePoint::to_affine_batch(table); + m_table = to_affine_batch(table); } ProjectivePoint mul(const Scalar& s, RandomNumberGenerator& rng) const { @@ -1297,7 +1320,7 @@ class WindowedMulTable final { } } - m_table = ProjectivePoint::to_affine_batch(table); + m_table = to_affine_batch(table); } ProjectivePoint mul(const Scalar& s, RandomNumberGenerator& rng) const { @@ -1439,7 +1462,7 @@ class WindowedMul2Table final { table.emplace_back(next_tbl_e()); } - m_table = ProjectivePoint::to_affine_batch(table); + m_table = to_affine_batch(table); } /** @@ -1484,14 +1507,34 @@ class WindowedMul2Table final { std::vector m_table; }; +// SSWU constant C1 - (B / (Z * A)) +template +const auto& SSWU_C2() + requires C::ValidForSswuHash +{ + // This could use a variable time inversion + static const typename C::FieldElement C2 = C::B * invert_field_element(C::SSWU_Z * C::A); + return C2; +} + +// SSWU constant C1 - (-B / A) +template +const auto& SSWU_C1() + requires C::ValidForSswuHash +{ + // We derive it from C2 to avoid a second inversion + static const typename C::FieldElement C1 = (SSWU_C2() * C::SSWU_Z).negate(); + return C1; +} + template inline auto map_to_curve_sswu(const typename C::FieldElement& u) -> typename C::AffinePoint { CT::poison(u); const auto z_u2 = C::SSWU_Z * u.square(); // z * u^2 const auto z2_u4 = z_u2.square(); - const auto tv1 = (z2_u4 + z_u2).invert(); - auto x1 = C::SSWU_C1() * (C::FieldElement::one() + tv1); - C::FieldElement::conditional_assign(x1, tv1.is_zero(), C::SSWU_C2()); + const auto tv1 = invert_field_element(z2_u4 + z_u2); + auto x1 = SSWU_C1() * (C::FieldElement::one() + tv1); + C::FieldElement::conditional_assign(x1, tv1.is_zero(), SSWU_C2()); const auto gx1 = C::AffinePoint::x3_ax_b(x1); const auto x2 = z_u2 * x1; @@ -1515,38 +1558,36 @@ inline auto map_to_curve_sswu(const typename C::FieldElement& u) -> typename C:: return pt; } -template -inline auto hash_to_curve_sswu(std::string_view hash, - bool random_oracle, - std::span pw, - std::span dst) -> typename C::ProjectivePoint { +template +inline auto hash_to_curve_sswu(std::string_view hash, std::span pw, std::span dst) { static_assert(C::ValidForSswuHash); #if defined(BOTAN_HAS_XMD) - const size_t SecurityLevel = (C::OrderBits + 1) / 2; - const size_t L = (C::PrimeFieldBits + SecurityLevel + 7) / 8; - - const size_t Cnt = (random_oracle ? 2 : 1); + constexpr size_t SecurityLevel = (C::OrderBits + 1) / 2; + constexpr size_t L = (C::PrimeFieldBits + SecurityLevel + 7) / 8; + constexpr size_t Cnt = RO ? 2 : 1; - std::vector xmd(L * Cnt); + std::array xmd; expand_message_xmd(hash, xmd, pw, dst); - if(Cnt == 1) { - const auto u = C::FieldElement::from_wide_bytes(std::span(xmd)); - return C::ProjectivePoint::from_affine(map_to_curve_sswu(u)); - } else { + if constexpr(RO) { const auto u0 = C::FieldElement::from_wide_bytes(std::span(xmd.data(), L)); const auto u1 = C::FieldElement::from_wide_bytes(std::span(xmd.data() + L, L)); auto accum = C::ProjectivePoint::from_affine(map_to_curve_sswu(u0)); accum += map_to_curve_sswu(u1); return accum; + } else { + const auto u = C::FieldElement::from_wide_bytes(std::span(xmd.data(), L)); + return map_to_curve_sswu(u); } #else throw Not_Implemented("Hash to curve not available due to missing XMD"); #endif } +} // namespace + } // namespace Botan #endif diff --git a/src/lib/math/pcurves/pcurves_impl/pcurves_wrap.h b/src/lib/math/pcurves/pcurves_impl/pcurves_wrap.h index 49ac0930d8a..114ebb97d74 100644 --- a/src/lib/math/pcurves/pcurves_impl/pcurves_wrap.h +++ b/src/lib/math/pcurves/pcurves_impl/pcurves_wrap.h @@ -12,11 +12,6 @@ namespace Botan::PCurve { -template -concept curve_supports_fe_invert2 = requires(const typename C::FieldElement& fe) { - { C::fe_invert2(fe) } -> std::same_as; -}; - template concept curve_supports_scalar_invert = requires(const typename C::Scalar& s) { { C::scalar_invert(s) } -> std::same_as; @@ -38,28 +33,6 @@ class PrimeOrderCurveImpl final : public PrimeOrderCurve { WindowedMul2Table m_table; }; - static typename C::AffinePoint curve_point_to_affine(const typename C::ProjectivePoint& pt) { - if constexpr(curve_supports_fe_invert2) { - if(pt.is_identity().as_bool()) { - return C::AffinePoint::identity(); - } - - const auto z2_inv = C::fe_invert2(pt.z()); - const auto z3_inv = z2_inv.square() * pt.z(); - return typename C::AffinePoint(pt.x() * z2_inv, pt.y() * z3_inv); - } else { - return pt.to_affine(); - } - } - - static typename C::FieldElement curve_point_to_affine_x(const typename C::ProjectivePoint& pt) { - if constexpr(curve_supports_fe_invert2) { - return pt.x() * C::fe_invert2(pt.z()); - } else { - return pt.to_affine().x(); - } - } - static_assert(C::OrderBits <= PrimeOrderCurve::MaximumBitLength); static_assert(C::PrimeFieldBits <= PrimeOrderCurve::MaximumBitLength); @@ -179,14 +152,14 @@ class PrimeOrderCurveImpl final : public PrimeOrderCurve { Scalar base_point_mul_x_mod_order(const Scalar& scalar, RandomNumberGenerator& rng) const override { auto pt = m_mul_by_g.mul(from_stash(scalar), rng); std::array x_bytes; - curve_point_to_affine_x(pt).serialize_to(std::span{x_bytes}); + to_affine_x(pt).serialize_to(std::span{x_bytes}); return stash(C::Scalar::from_wide_bytes(std::span{x_bytes})); } AffinePoint generator() const override { return stash(C::G); } AffinePoint point_to_affine(const ProjectivePoint& pt) const override { - return stash(curve_point_to_affine(from_stash(pt))); + return stash(to_affine(from_stash(pt))); } ProjectivePoint point_to_projective(const AffinePoint& pt) const override { @@ -260,12 +233,21 @@ class PrimeOrderCurveImpl final : public PrimeOrderCurve { } } - ProjectivePoint hash_to_curve(std::string_view hash, - std::span input, - std::span domain_sep, - bool random_oracle) const override { + AffinePoint hash_to_curve_nu(std::string_view hash, + std::span input, + std::span domain_sep) const override { + if constexpr(C::ValidForSswuHash) { + return stash(hash_to_curve_sswu(hash, input, domain_sep)); + } else { + throw Not_Implemented("Hash to curve is not implemented for this curve"); + } + } + + ProjectivePoint hash_to_curve_ro(std::string_view hash, + std::span input, + std::span domain_sep) const override { if constexpr(C::ValidForSswuHash) { - return stash(hash_to_curve_sswu(hash, random_oracle, input, domain_sep)); + return stash(hash_to_curve_sswu(hash, input, domain_sep)); } else { throw Not_Implemented("Hash to curve is not implemented for this curve"); } diff --git a/src/lib/pubkey/ec_group/ec_inner_data.cpp b/src/lib/pubkey/ec_group/ec_inner_data.cpp index 54045408842..81de3078ce9 100644 --- a/src/lib/pubkey/ec_group/ec_inner_data.cpp +++ b/src/lib/pubkey/ec_group/ec_inner_data.cpp @@ -141,7 +141,7 @@ std::unique_ptr EC_Group_Data::point_hash_to_curve_ro(std:: std::span input, std::span domain_sep) const { if(m_pcurve) { - auto pt = m_pcurve->hash_to_curve(hash_fn, input, domain_sep, true).to_affine(); + auto pt = m_pcurve->hash_to_curve_ro(hash_fn, input, domain_sep).to_affine(); return std::make_unique(shared_from_this(), pt.serialize()); } else { throw Not_Implemented("Hash to curve is not implemented for this curve"); @@ -152,7 +152,7 @@ std::unique_ptr EC_Group_Data::point_hash_to_curve_nu(std:: std::span input, std::span domain_sep) const { if(m_pcurve) { - auto pt = m_pcurve->hash_to_curve(hash_fn, input, domain_sep, false).to_affine(); + auto pt = m_pcurve->hash_to_curve_nu(hash_fn, input, domain_sep); return std::make_unique(shared_from_this(), pt.serialize()); } else { throw Not_Implemented("Hash to curve is not implemented for this curve"); diff --git a/src/lib/pubkey/ec_h2c/ec_h2c.cpp b/src/lib/pubkey/ec_h2c/ec_h2c.cpp new file mode 100644 index 00000000000..a4b7799173c --- /dev/null +++ b/src/lib/pubkey/ec_h2c/ec_h2c.cpp @@ -0,0 +1,43 @@ +/* +* (C) 2019,2020,2021 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +namespace Botan { + +EC_Point hash_to_curve_sswu(const EC_Group& group, + std::string_view hash_fn, + std::span input, + std::span domain_sep, + bool random_oracle) { + 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)) { + if(random_oracle) { + const auto pt = curve->hash_to_curve_ro(hash_fn, input, domain_sep); + return pt.to_affine().serialize(); + } else { + const auto pt = curve->hash_to_curve_nu(hash_fn, input, domain_sep); + return pt.serialize(); + } + } + } + + throw Not_Implemented("Hash to curve is not implemented for this curve"); +} + +} // namespace Botan