Skip to content

Commit 42466ca

Browse files
committed
primeorder: faster lincomb_vartime when alloc enabled
Companion PR to RustCrypto/group#14, which adds `WnafBase::multiscalar_mul` using Straus' method. When the `alloc` feature is enabled, this is used to compute `LinearCombination::lincomb_vartime`. Also includes updated tests and benchmarks for `p256` to ensure `lincomb_vartime` computes the same results as using the `Add` and `Mul` traits, and show the relative performance improvement, which is a ~28% speedup on `p256` for a 3 scalar/point input: ProjectivePoint operations/point-scalar lincomb (variable-time) time: [149.13 µs 149.80 µs 150.84 µs] change: [−27.999% −27.645% −27.267%] (p = 0.00 < 0.05)
1 parent 45fe401 commit 42466ca

6 files changed

Lines changed: 113 additions & 52 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@ ed448-goldilocks = { path = "ed448-goldilocks" }
2626
hash2curve = { path = "hash2curve" }
2727
primefield = { path = "primefield" }
2828
primeorder = { path = "primeorder" }
29+
30+
rustcrypto-group = { git = "https://github.com/RustCrypto/group", branch = "multiscalar-mul" }

p256/benches/point.rs

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,73 @@
11
//! p256 `ProjectivePoint` benchmarks
22
3-
use criterion::{Criterion, criterion_group, criterion_main};
4-
use p256::ProjectivePoint;
3+
use criterion::{
4+
BenchmarkGroup, Criterion, criterion_group, criterion_main, measurement::Measurement,
5+
};
6+
use hex_literal::hex;
7+
use p256::{
8+
ProjectivePoint, Scalar,
9+
elliptic_curve::{
10+
Group, PrimeField,
11+
ops::{LinearCombination, MulByGeneratorVartime, MulVartime},
12+
},
13+
};
514
use primefield::subtle::ConstantTimeEq;
15+
use std::hint::black_box;
616

7-
fn bench_projective(c: &mut Criterion) {
8-
let mut group = c.benchmark_group("ProjectivePoint constant time operations");
17+
fn test_scalar() -> Scalar {
18+
Scalar::from_repr(
19+
hex!("519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464").into(),
20+
)
21+
.unwrap()
22+
}
23+
24+
fn bench_point_lincomb<M: Measurement>(group: &mut BenchmarkGroup<'_, M>) {
25+
let p = ProjectivePoint::GENERATOR;
26+
let s = test_scalar();
27+
28+
group.bench_function("point-scalar lincomb", |b| {
29+
b.iter(|| ProjectivePoint::lincomb(&[(p, s), (p, s), (p, s)]))
30+
});
31+
group.bench_function("point-scalar lincomb (variable-time)", |b| {
32+
b.iter(|| ProjectivePoint::lincomb_vartime(&[(p, s), (p, s), (p, s)]))
33+
});
34+
}
35+
36+
fn bench_point_mul<M: Measurement>(group: &mut BenchmarkGroup<'_, M>) {
37+
let p = ProjectivePoint::GENERATOR;
38+
let s = test_scalar();
39+
group.bench_function("point-scalar mul", |b| {
40+
b.iter(|| black_box(p) * black_box(s))
41+
});
42+
group.bench_function("point-scalar mul (variable-time)", |b| {
43+
b.iter(|| black_box(p).mul_vartime(&black_box(s)))
44+
});
45+
}
46+
47+
fn bench_point_mul_by_generator<M: Measurement>(group: &mut BenchmarkGroup<'_, M>) {
48+
let m = test_scalar();
49+
let s = Scalar::from_repr(m.into()).unwrap();
50+
group.bench_function("generator-scalar mul", |b| {
51+
b.iter(|| ProjectivePoint::mul_by_generator(&black_box(s)))
52+
});
53+
group.bench_function("generator-scalar mul (variable-time)", |b| {
54+
b.iter(|| ProjectivePoint::mul_by_generator_vartime(&black_box(s)))
55+
});
56+
}
57+
58+
fn bench_point(c: &mut Criterion) {
59+
let mut group = c.benchmark_group("ProjectivePoint operations");
60+
61+
bench_point_lincomb(&mut group);
62+
bench_point_mul(&mut group);
63+
bench_point_mul_by_generator(&mut group);
964

10-
group.bench_function("point_generator_ct_eq", |b| {
65+
group.bench_function("ct_eq", |b| {
1166
b.iter(|| ProjectivePoint::GENERATOR.ct_eq(&ProjectivePoint::GENERATOR))
1267
});
1368

1469
group.finish();
1570
}
1671

17-
criterion_group!(benches, bench_projective);
72+
criterion_group!(benches, bench_point);
1873
criterion_main!(benches);

p256/benches/scalar.rs

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,7 @@ use criterion::{
44
BenchmarkGroup, Criterion, criterion_group, criterion_main, measurement::Measurement,
55
};
66
use hex_literal::hex;
7-
use p256::{
8-
ProjectivePoint, Scalar,
9-
elliptic_curve::{
10-
Group,
11-
group::ff::PrimeField,
12-
ops::{MulByGeneratorVartime, MulVartime},
13-
},
14-
};
7+
use p256::{Scalar, elliptic_curve::PrimeField};
158
use std::hint::black_box;
169

1710
fn test_scalar_x() -> Scalar {
@@ -28,29 +21,6 @@ fn test_scalar_y() -> Scalar {
2821
.unwrap()
2922
}
3023

31-
fn bench_point_mul<M: Measurement>(group: &mut BenchmarkGroup<'_, M>) {
32-
let p = ProjectivePoint::GENERATOR;
33-
let m = test_scalar_x();
34-
let s = Scalar::from_repr(m.into()).unwrap();
35-
group.bench_function("point-scalar mul", |b| {
36-
b.iter(|| black_box(p) * black_box(s))
37-
});
38-
group.bench_function("point-scalar mul (variable-time)", |b| {
39-
b.iter(|| black_box(p).mul_vartime(&black_box(s)))
40-
});
41-
}
42-
43-
fn bench_point_mul_by_generator<M: Measurement>(group: &mut BenchmarkGroup<'_, M>) {
44-
let m = test_scalar_x();
45-
let s = Scalar::from_repr(m.into()).unwrap();
46-
group.bench_function("generator-scalar mul", |b| {
47-
b.iter(|| ProjectivePoint::mul_by_generator(&black_box(s)))
48-
});
49-
group.bench_function("generator-scalar mul (variable-time)", |b| {
50-
b.iter(|| ProjectivePoint::mul_by_generator_vartime(&black_box(s)))
51-
});
52-
}
53-
5424
fn bench_scalar_sub<M: Measurement>(group: &mut BenchmarkGroup<'_, M>) {
5525
let x = test_scalar_x();
5626
let y = test_scalar_y();
@@ -79,22 +49,15 @@ fn bench_scalar_invert<M: Measurement>(group: &mut BenchmarkGroup<'_, M>) {
7949
group.bench_function("invert", |b| b.iter(|| x.invert()));
8050
}
8151

82-
fn bench_point(c: &mut Criterion) {
83-
let mut group = c.benchmark_group("point operations");
84-
bench_point_mul(&mut group);
85-
group.finish();
86-
}
87-
8852
fn bench_scalar(c: &mut Criterion) {
8953
let mut group = c.benchmark_group("scalar operations");
9054
bench_scalar_sub(&mut group);
9155
bench_scalar_add(&mut group);
9256
bench_scalar_mul(&mut group);
93-
bench_point_mul_by_generator(&mut group);
9457
bench_scalar_negate(&mut group);
9558
bench_scalar_invert(&mut group);
9659
group.finish();
9760
}
9861

99-
criterion_group!(benches, bench_point, bench_scalar);
62+
criterion_group!(benches, bench_scalar);
10063
criterion_main!(benches);

p256/tests/projective.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,11 @@ proptest! {
105105
s1 in scalar(),
106106
p2 in projective(),
107107
s2 in scalar(),
108+
p3 in projective(),
109+
s3 in scalar(),
108110
) {
109-
let reference = p1 * s1 + p2 * s2;
110-
let test = ProjectivePoint::lincomb(&[(p1, s1), (p2, s2)]);
111+
let reference = p1 * s1 + p2 * s2 + p3 * s3;
112+
let test = ProjectivePoint::lincomb(&[(p1, s1), (p2, s2), (p3, s3)]);
111113
assert_eq!(reference, test);
112114
}
113115

@@ -118,9 +120,26 @@ proptest! {
118120
s1 in scalar(),
119121
p2 in projective(),
120122
s2 in scalar(),
123+
p3 in projective(),
124+
s3 in scalar(),
121125
) {
122-
let reference = p1 * s1 + p2 * s2;
123-
let test = ProjectivePoint::lincomb(vec![(p1, s1), (p2, s2)].as_slice());
126+
let reference = p1 * s1 + p2 * s2 + p3 * s3;
127+
let test = ProjectivePoint::lincomb(vec![(p1, s1), (p2, s2), (p3, s3)].as_slice());
128+
assert_eq!(reference, test);
129+
}
130+
131+
#[test]
132+
#[cfg(feature = "alloc")]
133+
fn lincomb_vartime(
134+
p1 in projective(),
135+
s1 in scalar(),
136+
p2 in projective(),
137+
s2 in scalar(),
138+
p3 in projective(),
139+
s3 in scalar(),
140+
) {
141+
let reference = p1 * s1 + p2 * s2 + p3 * s3;
142+
let test = ProjectivePoint::lincomb_vartime(vec![(p1, s1), (p2, s2), (p3, s3)].as_slice());
124143
assert_eq!(reference, test);
125144
}
126145
}

primeorder/src/projective.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use elliptic_curve::{
3131
#[cfg(feature = "alloc")]
3232
use {
3333
alloc::vec::Vec,
34-
elliptic_curve::group::{Wnaf, WnafGroup},
34+
elliptic_curve::group::{Wnaf, WnafBase, WnafGroup, WnafScalar},
3535
};
3636

3737
#[cfg(all(feature = "alloc", feature = "basepoint-table"))]
@@ -494,6 +494,23 @@ where
494494

495495
lincomb::<C>(&mut ks, &mut pcs)
496496
}
497+
498+
#[cfg(feature = "alloc")]
499+
fn lincomb_vartime(points_and_scalars: &[(Self, Scalar<C>)]) -> Self {
500+
// TODO(tarcieri): make this customizable?
501+
const WINDOW_SIZE: usize = 4;
502+
let points = points_and_scalars
503+
.iter()
504+
.map(|(point, _)| WnafBase::<Self, WINDOW_SIZE>::new(*point))
505+
.collect::<Vec<_>>();
506+
507+
let scalars = points_and_scalars
508+
.iter()
509+
.map(|(_, scalar)| WnafScalar::<Scalar<C>, WINDOW_SIZE>::new(scalar))
510+
.collect::<Vec<_>>();
511+
512+
WnafBase::multiscalar_mul(scalars.iter(), points.iter())
513+
}
497514
}
498515

499516
impl<C, const N: usize> LinearCombination<[(Self, Scalar<C>); N]> for ProjectivePoint<C>
@@ -508,6 +525,11 @@ where
508525

509526
lincomb::<C>(&mut ks, &mut pcs)
510527
}
528+
529+
#[cfg(feature = "alloc")]
530+
fn lincomb_vartime(points_and_scalars: &[(Self, Scalar<C>); N]) -> Self {
531+
Self::lincomb_vartime(points_and_scalars.as_slice())
532+
}
511533
}
512534

513535
fn lincomb<C: PrimeCurveParams>(
@@ -950,6 +972,7 @@ where
950972

951973
// When we're guaranteed *not* to have basepoint tables available (because they need `alloc`)
952974
// use linear combinations for this computation, but they're slower when they are available
975+
// TODO(tarcieri): `WnafBase::multiscalar_mul` w\ basepoint table when `alloc` *is* available
953976
#[cfg(not(feature = "alloc"))]
954977
fn mul_by_generator_and_mul_add_vartime(
955978
a: &Self::Scalar,

0 commit comments

Comments
 (0)