Skip to content

Commit cc35cab

Browse files
authored
Merge pull request #4937 from randombit/jack/pcurves-cond-audit
Add safety comments to if and while statements in pcurves
2 parents 0ec63a0 + 8fc7216 commit cc35cab

5 files changed

Lines changed: 127 additions & 19 deletions

File tree

src/lib/math/pcurves/pcurves_algos.h

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ auto to_affine_batch(std::span<const typename C::ProjectivePoint> projective) {
8787
any_identity = any_identity || pt.is_identity();
8888
}
8989

90+
// Conditional acceptable: N is public. State of points is not necessarily
91+
// public, but we don't leak which point was the identity. In practice with
92+
// the algorithms currently in use, the only time an identity can occur is
93+
// during mul2 where the two points g/h have a small relation (ie h = g*k for
94+
// some k < 16)
95+
9096
if(N <= 2 || any_identity.as_bool()) {
9197
// If there are identity elements, using the batch inversion gets
9298
// tricky. It can be done, but this should be a rare situation so
@@ -155,9 +161,16 @@ inline constexpr ProjectivePoint point_add(const ProjectivePoint& a, const Proje
155161
const auto H = U2 - U1;
156162
const auto r = S2 - S1;
157163

158-
// If a == -b then H == 0 && r != 0, in which case
159-
// at the end we'll set z = a.z * b.z * H = 0, resulting
160-
// in the correct output (point at infinity)
164+
/* Risky conditional
165+
*
166+
* This implementation uses projective coordinates, which do not have an efficient complete
167+
* addition formula. We rely on the design of the multiplication algorithms to avoid doublings.
168+
*
169+
* This conditional only comes into play for the actual doubling case, not x + (-x) which
170+
* is another exceptional case in some circumstances. Here if a == -b then H == 0 && r != 0,
171+
* in which case at the end we'll set z to a.z * b.z * H = 0, resulting in the correct
172+
* output (the identity element)
173+
*/
161174
if((r.is_zero() && H.is_zero() && !(a_is_identity && b_is_identity)).as_bool()) {
162175
return a.dbl();
163176
}
@@ -204,9 +217,16 @@ inline constexpr ProjectivePoint point_add_mixed(const ProjectivePoint& a,
204217
const auto H = U2 - a.x();
205218
const auto r = S2 - a.y();
206219

207-
// If r == H == 0 then we are in the doubling case
208-
// For a == -b we compute the correct result because
209-
// H will be zero, leading to Z3 being zero also
220+
/* Risky conditional
221+
*
222+
* This implementation uses projective coordinates, which do not have an efficient complete
223+
* addition formula. We rely on the design of the multiplication algorithms to avoid doublings.
224+
*
225+
* This conditional only comes into play for the actual doubling case, not x + (-x) which
226+
* is another exceptional case in some circumstances. Here if a == -b then H == 0 && r != 0,
227+
* in which case at the end we'll set z to a.z * H = 0, resulting in the correct output
228+
* (the identity element)
229+
*/
210230
if((r.is_zero() && H.is_zero() && !(a_is_identity && b_is_identity)).as_bool()) {
211231
return a.dbl();
212232
}
@@ -256,9 +276,16 @@ inline constexpr ProjectivePoint point_add_or_sub_mixed(const ProjectivePoint& a
256276
const auto H = U2 - a.x();
257277
const auto r = S2 - a.y();
258278

259-
// If r == H == 0 then we are in the doubling case
260-
// For a == -b we compute the correct result because
261-
// H will be zero, leading to Z3 being zero also
279+
/* Risky conditional
280+
*
281+
* This implementation uses projective coordinates, which do not have an efficient complete
282+
* addition formula. We rely on the design of the multiplication algorithms to avoid doublings.
283+
*
284+
* This conditional only comes into play for the actual doubling case, not x + (-x) which
285+
* is another exceptional case in some circumstances. Here if a == -b then H == 0 && r != 0,
286+
* in which case at the end we'll set z to a.z * H = 0, resulting in the correct output
287+
* (the identity element)
288+
*/
262289
if((r.is_zero() && H.is_zero() && !(a_is_identity && b_is_identity)).as_bool()) {
263290
return a.dbl();
264291
}
@@ -362,6 +389,8 @@ Pay 2S + 1*2 + 1half to save n*(1A + 1*4 + 1*8) + 1M
362389
363390
For generic A:
364391
Pay 2S + 1*2 + 1half to save n*(2S + 1*4 + 1*8)
392+
393+
The value of n is assumed to be public and should be a constant
365394
*/
366395
template <typename ProjectivePoint>
367396
inline constexpr ProjectivePoint dbl_n_a_minus_3(const ProjectivePoint& pt, size_t n) {
@@ -370,6 +399,7 @@ inline constexpr ProjectivePoint dbl_n_a_minus_3(const ProjectivePoint& pt, size
370399
auto nz = pt.z();
371400
auto w = nz.square().square();
372401

402+
// Conditional ok: loop iteration count is public
373403
while(n > 0) {
374404
const auto ny2 = ny.square();
375405
const auto ny4 = ny2.square();
@@ -379,6 +409,7 @@ inline constexpr ProjectivePoint dbl_n_a_minus_3(const ProjectivePoint& pt, size
379409
nz *= ny;
380410
ny = t1 * (t2 - nx).mul2() - ny4;
381411
n--;
412+
// Conditional ok: loop iteration count is public
382413
if(n > 0) {
383414
w *= ny4;
384415
}
@@ -392,6 +423,7 @@ inline constexpr ProjectivePoint dbl_n_a_zero(const ProjectivePoint& pt, size_t
392423
auto ny = pt.y().mul2();
393424
auto nz = pt.z();
394425

426+
// Conditional ok: loop iteration count is public
395427
while(n > 0) {
396428
const auto ny2 = ny.square();
397429
const auto ny4 = ny2.square();
@@ -412,6 +444,7 @@ inline constexpr ProjectivePoint dbl_n_generic(const ProjectivePoint& pt, const
412444
auto nz = pt.z();
413445
auto w = nz.square().square() * A;
414446

447+
// Conditional ok: loop iteration count is public
415448
while(n > 0) {
416449
const auto ny2 = ny.square();
417450
const auto ny4 = ny2.square();
@@ -421,6 +454,7 @@ inline constexpr ProjectivePoint dbl_n_generic(const ProjectivePoint& pt, const
421454
nz *= ny;
422455
ny = t1 * (t2 - nx).mul2() - ny4;
423456
n--;
457+
// Conditional ok: loop iteration count is public
424458
if(n > 0) {
425459
w *= ny4;
426460
}

src/lib/math/pcurves/pcurves_generic/pcurves_generic.cpp

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,17 @@ class GenericField final {
716716

717717
void _const_time_unpoison() const { CT::unpoison(m_val); }
718718

719+
static void conditional_swap(CT::Choice cond, GenericField& x, GenericField& y) {
720+
const W mask = CT::Mask<W>::from_choice(cond).value();
721+
722+
for(size_t i = 0; i != N; ++i) {
723+
auto nx = choose(mask, y.m_val[i], x.m_val[i]);
724+
auto ny = choose(mask, x.m_val[i], y.m_val[i]);
725+
x.m_val[i] = nx;
726+
y.m_val[i] = ny;
727+
}
728+
}
729+
719730
void conditional_assign(CT::Choice cond, const GenericField& nx) {
720731
const W mask = CT::Mask<W>::from_choice(cond).value();
721732

@@ -948,11 +959,13 @@ class GenericProjectivePoint final {
948959
* Convert a point from affine to projective form
949960
*/
950961
static Self from_affine(const GenericAffinePoint& pt) {
951-
if(pt.is_identity().as_bool()) {
952-
return Self::identity(pt.curve());
953-
} else {
954-
return GenericProjectivePoint(pt.x(), pt.y());
955-
}
962+
auto x = pt.x();
963+
auto y = pt.y();
964+
auto z = GenericField::one(x.curve());
965+
966+
// If pt is identity (0,0) swap y/z to convert (0,0,1) into (0,1,0)
967+
GenericField::conditional_swap(pt.is_identity(), y, z);
968+
return GenericProjectivePoint(x, y, z);
956969
}
957970

958971
/**

src/lib/math/pcurves/pcurves_impl/pcurves_impl.h

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,22 @@ class IntMod final {
350350
}
351351
}
352352

353+
/**
354+
* Conditional swap
355+
*
356+
* If `cond` is true, swaps the values of `x` and `y`
357+
*/
358+
static constexpr void conditional_swap(CT::Choice cond, Self& x, Self& y) {
359+
const W mask = CT::Mask<W>::from_choice(cond).value();
360+
361+
for(size_t i = 0; i != N; ++i) {
362+
auto nx = choose(mask, y.m_val[i], x.m_val[i]);
363+
auto ny = choose(mask, x.m_val[i], y.m_val[i]);
364+
x.m_val[i] = nx;
365+
y.m_val[i] = ny;
366+
}
367+
}
368+
353369
/**
354370
* Modular squaring
355371
*
@@ -423,6 +439,7 @@ class IntMod final {
423439
tbl[0] = (*this);
424440

425441
for(size_t i = 1; i != WindowElements; ++i) {
442+
// Conditional ok: table indexes are public here
426443
if(i % 2 == 1) {
427444
tbl[i] = tbl[i / 2].square();
428445
} else {
@@ -434,6 +451,7 @@ class IntMod final {
434451

435452
const size_t w0 = read_window_bits<WindowBits>(std::span{exp}, (Windows - 1) * WindowBits);
436453

454+
// Conditional ok: this function is variable time
437455
if(w0 > 0) {
438456
r = tbl[w0 - 1];
439457
}
@@ -443,6 +461,7 @@ class IntMod final {
443461

444462
const size_t w = read_window_bits<WindowBits>(std::span{exp}, (Windows - i - 1) * WindowBits);
445463

464+
// Conditional ok: this function is variable time
446465
if(w > 0) {
447466
r *= tbl[w - 1];
448467
}
@@ -476,11 +495,13 @@ class IntMod final {
476495
static constexpr void _invert_vartime_div2_helper(Self& a, Self& x) {
477496
constexpr auto INV_2 = p_div_2_plus_1(Rep::P);
478497

498+
// Conditional ok: this function is variable time
479499
while((a.m_val[0] & 1) != 1) {
480500
shift_right<1>(a.m_val);
481501

482502
W borrow = shift_right<1>(x.m_val);
483503

504+
// Conditional ok: this function is variable time
484505
if(borrow) {
485506
bigint_add2_nc(x.m_val.data(), N, INV_2.data(), N);
486507
}
@@ -523,6 +544,7 @@ class IntMod final {
523544
* a/y resp.
524545
*/
525546
constexpr Self invert_vartime() const {
547+
// Conditional ok: this function is variable time
526548
if(this->is_zero().as_bool()) {
527549
return Self::zero();
528550
}
@@ -541,6 +563,7 @@ class IntMod final {
541563
Self::_invert_vartime_div2_helper(a, y);
542564

543565
for(;;) {
566+
// Conditional ok: this function is variable time
544567
if(a.m_val == b.m_val) {
545568
// At this point it should be that a == b == 1
546569
auto r = y.negate();
@@ -564,6 +587,7 @@ class IntMod final {
564587
std::array<W, N> r;
565588
word carry = bigint_sub3(r.data(), b.data(), N, a.data(), N);
566589

590+
// Conditional ok: this function is variable time
567591
if(carry == 0) {
568592
// b > a
569593
b.m_val = r;
@@ -714,12 +738,14 @@ class IntMod final {
714738
* also rejected.
715739
*/
716740
static std::optional<Self> deserialize(std::span<const uint8_t> bytes) {
741+
// Conditional ok: input length is public
717742
if(bytes.size() != Self::BYTES) {
718743
return {};
719744
}
720745

721746
const auto words = bytes_to_words<W, N, BYTES>(bytes.first<Self::BYTES>());
722747

748+
// Conditional acceptable: std::optional is implicitly not constant time
723749
if(!bigint_ct_is_lt(words.data(), N, P.data(), N).as_bool()) {
724750
return {};
725751
}
@@ -749,6 +775,7 @@ class IntMod final {
749775
* modular reduces it.
750776
*/
751777
static constexpr std::optional<Self> from_wide_bytes_varlen(std::span<const uint8_t> bytes) {
778+
// Conditional ok: input length is public
752779
if(bytes.size() > 2 * Self::BYTES) {
753780
return {};
754781
}
@@ -784,6 +811,7 @@ class IntMod final {
784811
buf[0] &= mask;
785812
}
786813

814+
// Conditionals ok: rejection sampling reveals only values we didn't use
787815
if(auto s = Self::deserialize(buf)) {
788816
if(s.value().is_nonzero().as_bool()) {
789817
return s.value();
@@ -979,11 +1007,22 @@ class ProjectiveCurvePoint {
9791007
* Convert a point from affine to projective form
9801008
*/
9811009
static constexpr Self from_affine(const AffinePoint& pt) {
982-
if(pt.is_identity().as_bool()) {
983-
return Self::identity();
984-
} else {
985-
return ProjectiveCurvePoint(pt.x(), pt.y());
986-
}
1010+
/*
1011+
* If the point is the identity element (x=0, y=0) then instead of
1012+
* creating (x, y, 1) = (0, 0, 1) we want our projective identity
1013+
* encoding of (0, 1, 0)
1014+
*
1015+
* Which we can achieve by a conditional swap of y and z if the
1016+
* affine point is the identity.
1017+
*/
1018+
1019+
auto x = pt.x();
1020+
auto y = pt.y();
1021+
auto z = FieldElement::one();
1022+
1023+
FieldElement::conditional_swap(pt.is_identity(), y, z);
1024+
1025+
return ProjectiveCurvePoint(x, y, z);
9871026
}
9881027

9891028
/**
@@ -1097,6 +1136,7 @@ class ProjectiveCurvePoint {
10971136
// In certain contexts we may be called with a Null_RNG; in that case the
10981137
// caller is accepting that randomization will not occur
10991138

1139+
// Conditional ok: caller's RNG state (seeded vs not) is presumed public
11001140
if(rng.is_seeded()) {
11011141
auto r = FieldElement::random(rng);
11021142

@@ -1248,6 +1288,9 @@ class BlindedScalarBits final {
12481288
//
12491289
// This can return any value between 0 and the scalar bit length, as long
12501290
// as it is a multiple of the word size.
1291+
//
1292+
// TODO(Botan4) this function should be consteval but cannot currently to a bug
1293+
// in older versions of Clang. Change to consteval when minimum Clang is bumped.
12511294
static constexpr size_t blinding_bits(size_t sb) {
12521295
constexpr size_t wb = WordInfo<W>::bits;
12531296

@@ -1479,6 +1522,7 @@ class WindowedBoothMulTable final {
14791522
const size_t w_i = bits.get_window(idx);
14801523
const auto [tidx, tneg] = booth_recode<WindowBits>(w_i);
14811524

1525+
// Conditional ok: loop iteration count is public
14821526
if(i == 0) {
14831527
accum = ProjectivePoint::from_affine(m_table.ct_select(tidx));
14841528
accum.conditional_assign(tneg, accum.negate());
@@ -1488,6 +1532,7 @@ class WindowedBoothMulTable final {
14881532

14891533
accum = accum.dbl_n(WindowBits);
14901534

1535+
// Conditional ok: loop iteration count is public
14911536
if(i <= 3) {
14921537
accum.randomize_rep(rng);
14931538
}
@@ -1578,6 +1623,7 @@ class VartimeMul2Table final {
15781623
bool s1_is_zero = s1.is_zero().as_bool();
15791624
bool s2_is_zero = s2.is_zero().as_bool();
15801625

1626+
// Conditional ok: this function is variable time
15811627
if(s1_is_zero && s2_is_zero) {
15821628
return ProjectivePoint::identity();
15831629
}
@@ -1587,6 +1633,7 @@ class VartimeMul2Table final {
15871633
const size_t w_1 = bits1.get_window((Windows - i - 1) * WindowBits);
15881634
const size_t w_2 = bits2.get_window((Windows - i - 1) * WindowBits);
15891635
const size_t window = w_1 + (w_2 << WindowBits);
1636+
// Conditional ok: this function is variable time
15901637
if(window > 0) {
15911638
return std::make_pair(window, i);
15921639
}
@@ -1606,6 +1653,7 @@ class VartimeMul2Table final {
16061653

16071654
const size_t window = w_1 + (w_2 << WindowBits);
16081655

1656+
// Conditional ok: this function is variable time
16091657
if(window > 0) {
16101658
accum += m_table[window - 1];
16111659
}

0 commit comments

Comments
 (0)