Skip to content

Commit 38149fa

Browse files
authored
elliptic-curve: add point::LookupTable and BasepointTable (#2023)
Implementation is originally from the `k256` crate, but written to be generic over a `Point` type which is bound by the `Group` trait. Adds a `LookupTable` type which supports precomputed lookup tables for a given curve point which can be selected from in constant time. Also adds a feature-gated `BasepointTable` type with a `const fn` constructor which builds a table for precomputed fixed-base scalar multiplication using the `Group::generator` as the base point. The `const fn` support for `BasepointTable` is achieved via a `LazyLock`-style pattern which computes the table lazily upon first use in a synchronized fashion. It supports using either `std::sync::LazyLock`, or for `no_std` targets it also supports using the `critical-section` feature of the `once_cell` crate.
1 parent 77f580d commit 38149fa

File tree

7 files changed

+191
-2
lines changed

7 files changed

+191
-2
lines changed

.github/workflows/elliptic-curve.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ jobs:
4545
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc
4646
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features arithmetic
4747
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features bits
48+
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features critical-section
4849
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features dev
4950
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features digest
5051
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features ecdh

Cargo.lock

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

elliptic-curve/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ ff = { version = "=0.14.0-pre.0", optional = true, default-features = false }
3030
group = { version = "=0.14.0-pre.0", optional = true, default-features = false }
3131
hkdf = { version = "0.13.0-rc.1", optional = true, default-features = false }
3232
hex-literal = { version = "1", optional = true }
33+
once_cell = { version = "1.21", optional = true, default-features = false }
3334
pem-rfc7468 = { version = "1.0.0-rc.2", optional = true, features = ["alloc"] }
3435
pkcs8 = { version = "0.11.0-rc.7", optional = true, default-features = false }
3536
sec1 = { version = "0.8.0-rc.10", optional = true, features = ["subtle", "zeroize"] }
@@ -52,11 +53,14 @@ alloc = [
5253
std = [
5354
"alloc",
5455
"rand_core/std",
56+
"once_cell?/std",
5557
"pkcs8?/std",
5658
"sec1?/std"
5759
]
5860

5961
arithmetic = ["group"]
62+
basepoint-table = ["arithmetic"]
63+
critical-section = ["basepoint-table", "once_cell/critical-section"]
6064
bits = ["arithmetic", "ff/bits"]
6165
dev = ["arithmetic", "dep:hex-literal", "pem", "pkcs8"]
6266
ecdh = ["arithmetic", "digest", "dep:hkdf"]

elliptic-curve/src/point.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
//! Traits for elliptic curve points.
22
3-
#[cfg(feature = "arithmetic")]
3+
mod basepoint_table;
4+
mod lookup_table;
45
mod non_identity;
56

67
#[cfg(feature = "arithmetic")]
7-
pub use {self::non_identity::NonIdentity, crate::CurveArithmetic};
8+
pub use {self::non_identity::NonIdentity, lookup_table::LookupTable};
9+
10+
#[cfg(feature = "basepoint-table")]
11+
pub use self::basepoint_table::BasepointTable;
812

913
use crate::{Curve, FieldBytes};
1014
use subtle::{Choice, CtOption};
1115

16+
#[cfg(feature = "arithmetic")]
17+
use crate::CurveArithmetic;
18+
1219
/// Affine point type for a given curve with a [`CurveArithmetic`]
1320
/// implementation.
1421
#[cfg(feature = "arithmetic")]
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//! Precomputed basepoint tables for accelerating fixed-base scalar multiplication.
2+
3+
#![cfg(feature = "basepoint-table")]
4+
#![allow(clippy::cast_possible_truncation, clippy::needless_range_loop)]
5+
6+
#[cfg(not(any(feature = "critical-section", feature = "std")))]
7+
compile_error!("`basepoint-table` feature requires either `critical-section` or `std`");
8+
9+
use crate::point::LookupTable;
10+
use group::Group;
11+
use subtle::ConditionallySelectable;
12+
use {core::ops::Deref, ff::PrimeField};
13+
14+
#[cfg(feature = "critical-section")]
15+
use once_cell::sync::Lazy as LazyLock;
16+
#[cfg(all(feature = "std", not(feature = "critical-section")))]
17+
use std::sync::LazyLock;
18+
19+
/// Precomputed lookup table of multiples of a base point, a.k.a. generator.
20+
///
21+
/// This type leverages lazy computation, and requires one of the following crate features to be
22+
/// enabled in order to work:
23+
/// - `std`: leverages `std::sync::LazyLock`
24+
/// - `critical-section`: leverages `once_cell::sync::Lazy` via the `critical-section` crate,
25+
/// enabling the feature to be used in `no_std` contexts.
26+
#[derive(Debug)]
27+
pub struct BasepointTable<Point, const N: usize> {
28+
tables: LazyLock<[LookupTable<Point>; N]>,
29+
}
30+
31+
impl<Point, const N: usize> BasepointTable<Point, N>
32+
where
33+
Point: ConditionallySelectable + Default + Group,
34+
{
35+
/// Create a new [`BasepointTable`] which is lazily initialized on first use and can be bound
36+
/// to a constant.
37+
///
38+
/// Computed using the `Point`'s [`Group::generator`] as the base point.
39+
pub const fn new() -> Self {
40+
/// Inner function to initialize the table.
41+
fn init_table<Point, const N: usize>() -> [LookupTable<Point>; N]
42+
where
43+
Point: ConditionallySelectable + Default + Group,
44+
{
45+
let mut generator = Point::generator();
46+
let mut res = [LookupTable::<Point>::default(); N];
47+
48+
for i in 0..N {
49+
res[i] = LookupTable::new(generator);
50+
// We are storing tables spaced by two radix steps,
51+
// to decrease the size of the precomputed data.
52+
for _ in 0..8 {
53+
generator = generator.double();
54+
}
55+
}
56+
57+
res
58+
}
59+
60+
// Ensure basepoint table contains the expected number of entries for the scalar's size
61+
assert!(
62+
N as u32 == 1 + Point::Scalar::NUM_BITS / 8,
63+
"incorrectly sized basepoint table"
64+
);
65+
66+
Self {
67+
tables: LazyLock::new(init_table),
68+
}
69+
}
70+
}
71+
72+
impl<Point, const N: usize> Default for BasepointTable<Point, N>
73+
where
74+
Point: ConditionallySelectable + Default + Group,
75+
{
76+
fn default() -> Self {
77+
Self::new()
78+
}
79+
}
80+
81+
impl<Point, const N: usize> Deref for BasepointTable<Point, N> {
82+
type Target = [LookupTable<Point>; N];
83+
84+
#[inline]
85+
fn deref(&self) -> &[LookupTable<Point>; N] {
86+
&self.tables
87+
}
88+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//! Precomputed lookup tables which allow multiples of an elliptic curve point to be selected in
2+
//! constant time.
3+
4+
#![cfg(feature = "arithmetic")]
5+
6+
use group::Group;
7+
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
8+
9+
/// Internal constant for the number of entries in a [`LookupTable`].
10+
///
11+
/// This is defined separately from `LookupTable::SIZE` because we can't use an inherent associated
12+
/// constant of a generic type in generic contexts, and this doesn't vary depending on `Point`.
13+
const LUT_SIZE: usize = 8;
14+
15+
/// Lookup table containing precomputed values `[p, 2p, 3p, ..., 8p]`
16+
#[derive(Clone, Copy, Debug, Default)]
17+
pub struct LookupTable<Point> {
18+
points: [Point; LUT_SIZE],
19+
}
20+
21+
impl<Point> LookupTable<Point>
22+
where
23+
Point: ConditionallySelectable + Group,
24+
{
25+
/// Number of entries in the lookup table.
26+
pub const SIZE: usize = LUT_SIZE;
27+
28+
/// Compute a new lookup table from the given point.
29+
pub fn new(p: Point) -> Self {
30+
let mut points = [p; LUT_SIZE];
31+
32+
for j in 0..(LUT_SIZE - 1) {
33+
points[j + 1] = p + points[j];
34+
}
35+
36+
Self { points }
37+
}
38+
39+
/// Given `-8 <= x <= 8`, returns `x * p` in constant time.
40+
#[allow(clippy::cast_sign_loss)]
41+
pub fn select(&self, x: i8) -> Point {
42+
debug_assert!((-8..=8).contains(&x));
43+
44+
// Compute xabs = |x|
45+
let xmask = x >> 7;
46+
let xabs = (x + xmask) ^ xmask;
47+
48+
// Get an array element in constant time
49+
let mut t = Point::identity();
50+
51+
#[allow(clippy::cast_possible_truncation)]
52+
for j in 1..(LUT_SIZE + 1) {
53+
let c = (xabs as u8).ct_eq(&(j as u8));
54+
t.conditional_assign(&self.points[j - 1], c);
55+
}
56+
// Now t == |x| * p.
57+
58+
let neg_mask = Choice::from((xmask & 1) as u8);
59+
t.conditional_assign(&-t, neg_mask);
60+
// Now t == x * p.
61+
62+
t
63+
}
64+
}

elliptic-curve/src/point/non_identity.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Non-identity point type.
22
3+
#![cfg(feature = "arithmetic")]
4+
35
use core::ops::{Deref, Mul};
46

57
use group::{Group, GroupEncoding, prime::PrimeCurveAffine};

0 commit comments

Comments
 (0)