Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a constant time p*x + q*y ECC operation #4433

Merged
merged 1 commit into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/lib/math/pcurves/pcurves.h
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,17 @@ class BOTAN_TEST_API PrimeOrderCurve {
const Scalar& s1,
const Scalar& s2) const = 0;

/// Perform 2-ary multiplication (constant time)
///
/// Compute p*x + q*y
///
/// Returns nullopt if the produced point is the point at infinity
virtual std::optional<ProjectivePoint> mul_px_qy(const AffinePoint& p,
const Scalar& x,
const AffinePoint& q,
const Scalar& y,
RandomNumberGenerator& rng) const = 0;

/// Perform 2-ary multiplication (variable time), reducing x modulo order
///
/// Compute s1*pt1 + s2*pt2 in variable time, then extract the x
Expand Down
39 changes: 33 additions & 6 deletions src/lib/math/pcurves/pcurves_impl/pcurves_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1441,8 +1441,6 @@ class WindowedMul2Table final {

static constexpr size_t WindowBits = W;

static constexpr size_t Windows = (Scalar::BITS + WindowBits - 1) / WindowBits;

static constexpr size_t WindowSize = (1 << WindowBits);

// 2^(2*W) elements, less the identity element
Expand Down Expand Up @@ -1494,21 +1492,50 @@ class WindowedMul2Table final {
m_table = to_affine_batch<C>(table);
}

/**
* Constant time 2-ary multiplication
*/
ProjectivePoint mul2(const Scalar& s1, const Scalar& s2, RandomNumberGenerator& rng) const {
using BlindedScalar = BlindedScalarBits<C, WindowBits>;

BlindedScalar bits1(s1, rng);
BlindedScalar bits2(s2, rng);

constexpr size_t Windows = (BlindedScalar::Bits + WindowBits - 1) / WindowBits;

auto accum = ProjectivePoint::identity();

for(size_t i = 0; i != Windows; ++i) {
if(i > 0) {
accum = accum.dbl_n(WindowBits);
}

const size_t w_1 = bits1.get_window((Windows - i - 1) * WindowBits);
const size_t w_2 = bits2.get_window((Windows - i - 1) * WindowBits);
const size_t window = w_1 + (w_2 << WindowBits);
accum += AffinePoint::ct_select(m_table, window);

if(i <= 3) {
accum.randomize_rep(rng);
}
}

return accum;
}

/**
* Variable time 2-ary multiplication
*
* A common use of 2-ary multiplication is when verifying the commitments
* of an elliptic curve signature. Since in this case the inputs are all
* public, there is no problem with variable time computation.
*
* It may be useful to offer a constant time (+blinded) variant of this in
* the future for handling secret inputs, for example when computing
* Pedersen commitments
*
* TODO for variable time computation we could make use of a wNAF
* representation instead
*/
ProjectivePoint mul2_vartime(const Scalar& s1, const Scalar& s2) const {
constexpr size_t Windows = (Scalar::BITS + WindowBits - 1) / WindowBits;

const UnblindedScalarBits<C, W> bits1(s1);
const UnblindedScalarBits<C, W> bits2(s2);

Expand Down
14 changes: 14 additions & 0 deletions src/lib/math/pcurves/pcurves_impl/pcurves_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ class PrimeOrderCurveImpl final : public PrimeOrderCurve {
}
}

std::optional<ProjectivePoint> mul_px_qy(const AffinePoint& p,
const Scalar& x,
const AffinePoint& q,
const Scalar& y,
RandomNumberGenerator& rng) const override {
WindowedMul2Table<C, 2> tbl(from_stash(p), from_stash(q));
auto pt = tbl.mul2(from_stash(x), from_stash(y), rng);
if(pt.is_identity().as_bool()) {
return {};
} else {
return stash(pt);
}
}

bool mul2_vartime_x_mod_order_eq(const PrecomputedMul2Table& tableb,
const Scalar& v,
const Scalar& s1,
Expand Down
9 changes: 9 additions & 0 deletions src/lib/pubkey/ec_group/ec_apoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ 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) {
auto pt = p._inner().group()->mul_px_qy(p._inner(), x._inner(), q._inner(), y._inner(), rng);
return EC_AffinePoint(std::move(pt));
}

void EC_AffinePoint::serialize_x_to(std::span<uint8_t> bytes) const {
BOTAN_STATE_CHECK(!this->is_identity());
m_point->serialize_x_to(bytes);
Expand Down
9 changes: 9 additions & 0 deletions src/lib/pubkey/ec_group/ec_apoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ class BOTAN_UNSTABLE_API EC_AffinePoint final {
/// Workspace argument is transitional
EC_AffinePoint mul(const EC_Scalar& scalar, RandomNumberGenerator& rng, std::vector<BigInt>& ws) const;

/// 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);

/// Return the number of bytes of a field element
///
/// A point consists of two field elements, plus possibly a header
Expand Down
41 changes: 41 additions & 0 deletions src/lib/pubkey/ec_group/ec_inner_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,47 @@ std::unique_ptr<EC_AffinePoint_Data> EC_Group_Data::point_g_mul(const EC_Scalar_
}
}

std::unique_ptr<EC_AffinePoint_Data> EC_Group_Data::mul_px_qy(const EC_AffinePoint_Data& p,
const EC_Scalar_Data& x,
const EC_AffinePoint_Data& q,
const EC_Scalar_Data& y,
RandomNumberGenerator& rng) const {
if(m_pcurve) {
auto pt = m_pcurve->mul_px_qy(EC_AffinePoint_Data_PC::checked_ref(p).value(),
EC_Scalar_Data_PC::checked_ref(x).value(),
EC_AffinePoint_Data_PC::checked_ref(q).value(),
EC_Scalar_Data_PC::checked_ref(y).value(),
rng);

if(pt) {
return std::make_unique<EC_AffinePoint_Data_PC>(shared_from_this(), pt->to_affine());
} else {
return nullptr;
}
} else {
std::vector<BigInt> ws;
const auto& group = p.group();

// TODO this could be better!
EC_Point_Var_Point_Precompute p_mul(p.to_legacy_point(), rng, ws);
EC_Point_Var_Point_Precompute q_mul(q.to_legacy_point(), rng, ws);

const auto order = group->order() * group->cofactor(); // See #3800

auto px = p_mul.mul(EC_Scalar_Data_BN::checked_ref(x).value(), rng, order, ws);
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()) {
return std::make_unique<EC_AffinePoint_Data_BN>(shared_from_this(), std::move(px_qy));
} else {
return nullptr;
}
}
}

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
6 changes: 6 additions & 0 deletions src/lib/pubkey/ec_group/ec_inner_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,12 @@ class EC_Group_Data final : public std::enable_shared_from_this<EC_Group_Data> {
RandomNumberGenerator& rng,
std::vector<BigInt>& ws) const;

std::unique_ptr<EC_AffinePoint_Data> mul_px_qy(const EC_AffinePoint_Data& p,
const EC_Scalar_Data& x,
const EC_AffinePoint_Data& q,
const EC_Scalar_Data& y,
RandomNumberGenerator& rng) const;

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

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