Skip to content

Commit

Permalink
EC_AffinePoint API improvements
Browse files Browse the repository at this point in the history
This adds point negation and point addition.

Point addition is not particularly efficient since it converts the
result immediately to an affine coordinate, as we do not currently
expose projective points at all. That said it is more than sufficient
for simple protocols that just need to add a few points together.

This also corrects the return result of EC_AffinePoint::mul_px_qy,
which would previously crash if the resulting point was the identity
element. Instead it should return nullopt.

Discussion in #4027
  • Loading branch information
randombit committed Nov 25, 2024
1 parent e54502e commit e6e4c7a
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 24 deletions.
28 changes: 28 additions & 0 deletions doc/api_ref/ecc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -256,10 +256,38 @@ Only curves over prime fields are supported.

Fixed base scalar multiplication. Constant time with blinding.

.. cpp:function:: static std::optional<EC_AffinePoint> mul_px_qy(const EC_AffinePoint& p, \
const EC_Scalar& x, \
const EC_AffinePoint& q, \
const EC_Scalar& y, \
RandomNumberGenerator& rng)

Constant time 2-ary multiscalar multiplication. Returns p*x + q*y, or
nullopt if the resulting point was the identity element.

.. cpp:function:: EC_AffinePoint mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector<BigInt>& ws) const

Variable base scalar multiplication. Constant time with blinding.

.. cpp:function:: static EC_AffinePoint add(const EC_AffinePoint& p, const EC_AffinePoint& q)

Elliptic curve point addition.

.. note::

This point addition operation is relatively quite expensive since it
must convert the point directly from projective to affine coordinates,
which requires an expensive field inversion. This is, however,
sufficient for protocols which just require a small number of point
additions. In the future a type for projective coordinate points may
also be added, to better handle protocols which require many point
additions. If you are implementing such a protocol using this interface
please open an issue on Github.

.. cpp:function:: EC_AffinePoint negate() const

Return the negation of this point.

.. cpp:function:: static EC_AffinePoint hash_to_curve_ro(const EC_Group& group, \
std::string_view hash_fn, \
std::span<const uint8_t> input, \
Expand Down
4 changes: 4 additions & 0 deletions src/cli/perf_ec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class PerfTest_EllipticCurve final : public PerfTest {

auto bp_timer = config.make_timer(group_name + " base point mul");
auto vp_timer = config.make_timer(group_name + " variable point mul");
auto add_timer = config.make_timer(group_name + " point addition");
auto der_uc_timer = config.make_timer(group_name + " point deserialize (uncompressed)");
auto der_c_timer = config.make_timer(group_name + " point deserialize (compressed)");
auto mul2_setup_timer = config.make_timer(group_name + " mul2 setup");
Expand Down Expand Up @@ -64,6 +65,8 @@ class PerfTest_EllipticCurve final : public PerfTest {
const auto r2_bytes = r2.serialize_uncompressed();
BOTAN_ASSERT_EQUAL(r1_bytes, r2_bytes, "Same result for multiplication");

add_timer->run([&]() { r1.add(r2); });

der_uc_timer->run([&]() { Botan::EC_AffinePoint::deserialize(group, r1_bytes); });

const auto r1_cbytes = r1.serialize_compressed();
Expand All @@ -81,6 +84,7 @@ class PerfTest_EllipticCurve final : public PerfTest {
}
}

config.record_result(*add_timer);
config.record_result(*bp_timer);
config.record_result(*vp_timer);
config.record_result(*mul2_setup_timer);
Expand Down
9 changes: 6 additions & 3 deletions src/lib/math/pcurves/pcurves.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ class BOTAN_TEST_API PrimeOrderCurve {
return bytes;
}

/**
* Point negation
*/
AffinePoint negate() const { return m_curve->point_negate(*this); }

/**
* Return true if this is the curve identity element (aka the point at infinity)
*/
Expand Down Expand Up @@ -243,8 +248,6 @@ class BOTAN_TEST_API PrimeOrderCurve {

ProjectivePoint dbl() const { return m_curve->point_double(*this); }

ProjectivePoint negate() const { return m_curve->point_negate(*this); }

friend ProjectivePoint operator+(const ProjectivePoint& x, const ProjectivePoint& y) {
return x.m_curve->point_add(x, y);
}
Expand Down Expand Up @@ -376,7 +379,7 @@ class BOTAN_TEST_API PrimeOrderCurve {

virtual ProjectivePoint point_double(const ProjectivePoint& pt) const = 0;

virtual ProjectivePoint point_negate(const ProjectivePoint& pt) const = 0;
virtual AffinePoint point_negate(const AffinePoint& pt) const = 0;

virtual ProjectivePoint point_add(const ProjectivePoint& a, const ProjectivePoint& b) const = 0;

Expand Down
2 changes: 1 addition & 1 deletion src/lib/math/pcurves/pcurves_impl/pcurves_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ class PrimeOrderCurveImpl final : public PrimeOrderCurve {
return stash(from_stash(a) + from_stash(b));
}

ProjectivePoint point_negate(const ProjectivePoint& pt) const override { return stash(from_stash(pt).negate()); }
AffinePoint point_negate(const AffinePoint& pt) const override { return stash(from_stash(pt).negate()); }

bool affine_point_is_identity(const AffinePoint& pt) const override {
return from_stash(pt).is_identity().as_bool();
Expand Down
24 changes: 19 additions & 5 deletions src/lib/pubkey/ec_group/ec_apoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,26 @@ EC_AffinePoint EC_AffinePoint::mul(const EC_Scalar& scalar, RandomNumberGenerato
return EC_AffinePoint(inner().mul(scalar._inner(), rng, ws));
}

EC_AffinePoint EC_AffinePoint::mul_px_qy(const EC_AffinePoint& p,
const EC_Scalar& x,
const EC_AffinePoint& q,
const EC_Scalar& y,
RandomNumberGenerator& rng) {
std::optional<EC_AffinePoint> EC_AffinePoint::mul_px_qy(const EC_AffinePoint& p,
const EC_Scalar& x,
const EC_AffinePoint& q,
const EC_Scalar& y,
RandomNumberGenerator& rng) {
auto pt = p._inner().group()->mul_px_qy(p._inner(), x._inner(), q._inner(), y._inner(), rng);
if(pt) {
return EC_AffinePoint(std::move(pt));
} else {
return {};
}
}

EC_AffinePoint EC_AffinePoint::add(const EC_AffinePoint& q) const {
auto pt = _inner().group()->affine_add(_inner(), q._inner());
return EC_AffinePoint(std::move(pt));
}

EC_AffinePoint EC_AffinePoint::negate() const {
auto pt = this->_inner().group()->affine_neg(this->_inner());
return EC_AffinePoint(std::move(pt));
}

Expand Down
28 changes: 23 additions & 5 deletions src/lib/pubkey/ec_group/ec_apoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class EC_Point;
class EC_Group_Data;
class EC_AffinePoint_Data;

/// Elliptic Curve in Affine Representation
///
class BOTAN_UNSTABLE_API EC_AffinePoint final {
public:
/// Point deserialization. Throws if wrong length or not a valid point
Expand Down Expand Up @@ -79,11 +81,27 @@ class BOTAN_UNSTABLE_API EC_AffinePoint final {
/// Compute 2-ary multiscalar multiplication - p*x + q*y
///
/// This operation runs in constant time with respect to p, x, q, and y
static EC_AffinePoint mul_px_qy(const EC_AffinePoint& p,
const EC_Scalar& x,
const EC_AffinePoint& q,
const EC_Scalar& y,
RandomNumberGenerator& rng);
///
/// Returns nullopt if the result was the point at infinity
static std::optional<EC_AffinePoint> mul_px_qy(const EC_AffinePoint& p,
const EC_Scalar& x,
const EC_AffinePoint& q,
const EC_Scalar& y,
RandomNumberGenerator& rng);

/// Point addition
///
/// Note that this is quite slow since it converts the resulting
/// projective point immediately to affine coordinates, which requires a
/// field inversion. This can be sufficient when implementing protocols
/// that just need to perform a few additions.
///
/// In the future a cooresponding EC_ProjectivePoint type may be added
/// which would avoid the expensive affine conversions
EC_AffinePoint add(const EC_AffinePoint& q) const;

/// Point negation
EC_AffinePoint negate() const;

/// Return the number of bytes of a field element
///
Expand Down
27 changes: 26 additions & 1 deletion src/lib/pubkey/ec_group/ec_inner_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,16 +317,41 @@ std::unique_ptr<EC_AffinePoint_Data> EC_Group_Data::mul_px_qy(const EC_AffinePoi
auto qy = q_mul.mul(EC_Scalar_Data_BN::checked_ref(y).value(), rng, order, ws);

auto px_qy = px + qy;
px_qy.force_affine();

if(!px_qy.is_zero()) {
px_qy.force_affine();
return std::make_unique<EC_AffinePoint_Data_BN>(shared_from_this(), std::move(px_qy));
} else {
return nullptr;
}
}
}

std::unique_ptr<EC_AffinePoint_Data> EC_Group_Data::affine_add(const EC_AffinePoint_Data& p,
const EC_AffinePoint_Data& q) const {
if(m_pcurve) {
auto pt = m_pcurve->point_add_mixed(
PCurve::PrimeOrderCurve::ProjectivePoint::from_affine(EC_AffinePoint_Data_PC::checked_ref(p).value()),
EC_AffinePoint_Data_PC::checked_ref(q).value());

return std::make_unique<EC_AffinePoint_Data_PC>(shared_from_this(), pt.to_affine());
} else {
auto pt = p.to_legacy_point() + q.to_legacy_point();
return std::make_unique<EC_AffinePoint_Data_BN>(shared_from_this(), std::move(pt));
}
}

std::unique_ptr<EC_AffinePoint_Data> EC_Group_Data::affine_neg(const EC_AffinePoint_Data& p) const {
if(m_pcurve) {
auto pt = m_pcurve->point_negate(EC_AffinePoint_Data_PC::checked_ref(p).value());
return std::make_unique<EC_AffinePoint_Data_PC>(shared_from_this(), pt);
} else {
auto pt = p.to_legacy_point();
pt.negate(); // negates in place
return std::make_unique<EC_AffinePoint_Data_BN>(shared_from_this(), std::move(pt));
}
}

std::unique_ptr<EC_Mul2Table_Data> EC_Group_Data::make_mul2_table(const EC_AffinePoint_Data& h) const {
if(m_pcurve) {
EC_AffinePoint_Data_PC g(shared_from_this(), m_pcurve->generator());
Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/ec_group/ec_inner_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ class EC_Group_Data final : public std::enable_shared_from_this<EC_Group_Data> {
const EC_Scalar_Data& y,
RandomNumberGenerator& rng) const;

std::unique_ptr<EC_AffinePoint_Data> affine_add(const EC_AffinePoint_Data& p, const EC_AffinePoint_Data& q) const;

std::unique_ptr<EC_AffinePoint_Data> affine_neg(const EC_AffinePoint_Data& p) const;

std::unique_ptr<EC_Mul2Table_Data> make_mul2_table(const EC_AffinePoint_Data& pt) const;

const PCurve::PrimeOrderCurve& pcurve() const {
Expand Down
Loading

0 comments on commit e6e4c7a

Please sign in to comment.