Skip to content

Commit

Permalink
Merge pull request #4433 from randombit/jack/mul-px-qy
Browse files Browse the repository at this point in the history
Add a constant time p*x + q*y ECC operation
  • Loading branch information
randombit authored Nov 15, 2024
2 parents a43c20e + 1eb0769 commit 162a389
Show file tree
Hide file tree
Showing 9 changed files with 5,079 additions and 6 deletions.
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

0 comments on commit 162a389

Please sign in to comment.