Skip to content

Commit 0964f80

Browse files
curve: Support MSM #static scalars <= #static points (#668)
1 parent 83a57e5 commit 0964f80

File tree

4 files changed

+163
-11
lines changed

4 files changed

+163
-11
lines changed

curve25519-dalek/src/backend/serial/scalar_mul/precomputed_straus.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus {
7575

7676
let sp = self.static_lookup_tables.len();
7777
let dp = dynamic_lookup_tables.len();
78-
assert_eq!(sp, static_nafs.len());
78+
assert!(sp >= static_nafs.len());
7979
assert_eq!(dp, dynamic_nafs.len());
8080

8181
// We could save some doublings by looking for the highest
@@ -99,7 +99,7 @@ impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus {
9999
}
100100

101101
#[allow(clippy::needless_range_loop)]
102-
for i in 0..sp {
102+
for i in 0..static_nafs.len() {
103103
let t_ij = static_nafs[i][j];
104104
match t_ij.cmp(&0) {
105105
Ordering::Greater => {

curve25519-dalek/src/backend/vector/scalar_mul/precomputed_straus.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ pub mod spec {
8383

8484
let sp = self.static_lookup_tables.len();
8585
let dp = dynamic_lookup_tables.len();
86-
assert_eq!(sp, static_nafs.len());
86+
assert!(sp >= static_nafs.len());
8787
assert_eq!(dp, dynamic_nafs.len());
8888

8989
// We could save some doublings by looking for the highest
@@ -107,7 +107,7 @@ pub mod spec {
107107
}
108108

109109
#[allow(clippy::needless_range_loop)]
110-
for i in 0..sp {
110+
for i in 0..static_nafs.len() {
111111
let t_ij = static_nafs[i][j];
112112
match t_ij.cmp(&0) {
113113
Ordering::Greater => {

curve25519-dalek/src/ristretto.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,4 +1869,148 @@ mod test {
18691869
assert_eq!(P.compress(), R.compress());
18701870
assert_eq!(Q.compress(), R.compress());
18711871
}
1872+
1873+
#[test]
1874+
#[cfg(feature = "alloc")]
1875+
fn partial_precomputed_mixed_multiscalar_empty() {
1876+
let mut rng = rand::thread_rng();
1877+
1878+
let n_static = 16;
1879+
let n_dynamic = 8;
1880+
1881+
let static_points = (0..n_static)
1882+
.map(|_| RistrettoPoint::random(&mut rng))
1883+
.collect::<Vec<_>>();
1884+
1885+
// Use zero scalars
1886+
let static_scalars = Vec::new();
1887+
1888+
let dynamic_points = (0..n_dynamic)
1889+
.map(|_| RistrettoPoint::random(&mut rng))
1890+
.collect::<Vec<_>>();
1891+
1892+
let dynamic_scalars = (0..n_dynamic)
1893+
.map(|_| Scalar::random(&mut rng))
1894+
.collect::<Vec<_>>();
1895+
1896+
// Compute the linear combination using precomputed multiscalar multiplication
1897+
let precomputation = VartimeRistrettoPrecomputation::new(static_points.iter());
1898+
let result_multiscalar = precomputation.vartime_mixed_multiscalar_mul(
1899+
&static_scalars,
1900+
&dynamic_scalars,
1901+
&dynamic_points,
1902+
);
1903+
1904+
// Compute the linear combination manually
1905+
let mut result_manual = RistrettoPoint::identity();
1906+
for i in 0..static_scalars.len() {
1907+
result_manual += static_points[i] * static_scalars[i];
1908+
}
1909+
for i in 0..n_dynamic {
1910+
result_manual += dynamic_points[i] * dynamic_scalars[i];
1911+
}
1912+
1913+
assert_eq!(result_multiscalar, result_manual);
1914+
}
1915+
1916+
#[test]
1917+
#[cfg(feature = "alloc")]
1918+
fn partial_precomputed_mixed_multiscalar() {
1919+
let mut rng = rand::thread_rng();
1920+
1921+
let n_static = 16;
1922+
let n_dynamic = 8;
1923+
1924+
let static_points = (0..n_static)
1925+
.map(|_| RistrettoPoint::random(&mut rng))
1926+
.collect::<Vec<_>>();
1927+
1928+
// Use one fewer scalars
1929+
let static_scalars = (0..n_static - 1)
1930+
.map(|_| Scalar::random(&mut rng))
1931+
.collect::<Vec<_>>();
1932+
1933+
let dynamic_points = (0..n_dynamic)
1934+
.map(|_| RistrettoPoint::random(&mut rng))
1935+
.collect::<Vec<_>>();
1936+
1937+
let dynamic_scalars = (0..n_dynamic)
1938+
.map(|_| Scalar::random(&mut rng))
1939+
.collect::<Vec<_>>();
1940+
1941+
// Compute the linear combination using precomputed multiscalar multiplication
1942+
let precomputation = VartimeRistrettoPrecomputation::new(static_points.iter());
1943+
let result_multiscalar = precomputation.vartime_mixed_multiscalar_mul(
1944+
&static_scalars,
1945+
&dynamic_scalars,
1946+
&dynamic_points,
1947+
);
1948+
1949+
// Compute the linear combination manually
1950+
let mut result_manual = RistrettoPoint::identity();
1951+
for i in 0..static_scalars.len() {
1952+
result_manual += static_points[i] * static_scalars[i];
1953+
}
1954+
for i in 0..n_dynamic {
1955+
result_manual += dynamic_points[i] * dynamic_scalars[i];
1956+
}
1957+
1958+
assert_eq!(result_multiscalar, result_manual);
1959+
}
1960+
1961+
#[test]
1962+
#[cfg(feature = "alloc")]
1963+
fn partial_precomputed_multiscalar() {
1964+
let mut rng = rand::thread_rng();
1965+
1966+
let n_static = 16;
1967+
1968+
let static_points = (0..n_static)
1969+
.map(|_| RistrettoPoint::random(&mut rng))
1970+
.collect::<Vec<_>>();
1971+
1972+
// Use one fewer scalars
1973+
let static_scalars = (0..n_static - 1)
1974+
.map(|_| Scalar::random(&mut rng))
1975+
.collect::<Vec<_>>();
1976+
1977+
// Compute the linear combination using precomputed multiscalar multiplication
1978+
let precomputation = VartimeRistrettoPrecomputation::new(static_points.iter());
1979+
let result_multiscalar = precomputation.vartime_multiscalar_mul(&static_scalars);
1980+
1981+
// Compute the linear combination manually
1982+
let mut result_manual = RistrettoPoint::identity();
1983+
for i in 0..static_scalars.len() {
1984+
result_manual += static_points[i] * static_scalars[i];
1985+
}
1986+
1987+
assert_eq!(result_multiscalar, result_manual);
1988+
}
1989+
1990+
#[test]
1991+
#[cfg(feature = "alloc")]
1992+
fn partial_precomputed_multiscalar_empty() {
1993+
let mut rng = rand::thread_rng();
1994+
1995+
let n_static = 16;
1996+
1997+
let static_points = (0..n_static)
1998+
.map(|_| RistrettoPoint::random(&mut rng))
1999+
.collect::<Vec<_>>();
2000+
2001+
// Use zero scalars
2002+
let static_scalars = Vec::new();
2003+
2004+
// Compute the linear combination using precomputed multiscalar multiplication
2005+
let precomputation = VartimeRistrettoPrecomputation::new(static_points.iter());
2006+
let result_multiscalar = precomputation.vartime_multiscalar_mul(&static_scalars);
2007+
2008+
// Compute the linear combination manually
2009+
let mut result_manual = RistrettoPoint::identity();
2010+
for i in 0..static_scalars.len() {
2011+
result_manual += static_points[i] * static_scalars[i];
2012+
}
2013+
2014+
assert_eq!(result_multiscalar, result_manual);
2015+
}
18722016
}

curve25519-dalek/src/traits.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ pub trait VartimeMultiscalarMul {
285285
/// to be composed into the input iterators.
286286
///
287287
/// All methods require that the lengths of the input iterators be
288-
/// known and matching, as if they were `ExactSizeIterator`s. (It
288+
/// known, as if they were `ExactSizeIterator`s. (It
289289
/// does not require `ExactSizeIterator` only because that trait is
290290
/// broken).
291291
pub trait VartimePrecomputedMultiscalarMul: Sized {
@@ -306,8 +306,10 @@ pub trait VartimePrecomputedMultiscalarMul: Sized {
306306
/// $$
307307
/// where the \\(B_j\\) are the points that were supplied to `new`.
308308
///
309-
/// It is an error to call this function with iterators of
310-
/// inconsistent lengths.
309+
/// It is valid for \\(b_i\\) to have a shorter length than \\(B_j\\).
310+
/// In this case, any "unused" points are ignored in the computation.
311+
/// It is an error to call this function if \\(b_i\\) has a longer
312+
/// length than \\(B_j\\).
311313
///
312314
/// The trait bound aims for maximum flexibility: the input must
313315
/// be convertable to iterators (`I: IntoIter`), and the
@@ -337,8 +339,11 @@ pub trait VartimePrecomputedMultiscalarMul: Sized {
337339
/// $$
338340
/// where the \\(B_j\\) are the points that were supplied to `new`.
339341
///
340-
/// It is an error to call this function with iterators of
341-
/// inconsistent lengths.
342+
/// It is valid for \\(b_i\\) to have a shorter length than \\(B_j\\).
343+
/// In this case, any "unused" points are ignored in the computation.
344+
/// It is an error to call this function if \\(b_i\\) has a longer
345+
/// length than \\(B_j\\), or if \\(a_i\\) and \\(A_i\\) do not have
346+
/// the same length.
342347
///
343348
/// The trait bound aims for maximum flexibility: the inputs must be
344349
/// convertable to iterators (`I: IntoIter`), and the iterator's items
@@ -378,8 +383,11 @@ pub trait VartimePrecomputedMultiscalarMul: Sized {
378383
///
379384
/// If any of the dynamic points were `None`, return `None`.
380385
///
381-
/// It is an error to call this function with iterators of
382-
/// inconsistent lengths.
386+
/// It is valid for \\(b_i\\) to have a shorter length than \\(B_j\\).
387+
/// In this case, any "unused" points are ignored in the computation.
388+
/// It is an error to call this function if \\(b_i\\) has a longer
389+
/// length than \\(B_j\\), or if \\(a_i\\) and \\(A_i\\) do not have
390+
/// the same length.
383391
///
384392
/// This function is particularly useful when verifying statements
385393
/// involving compressed points. Accepting `Option<Point>` allows

0 commit comments

Comments
 (0)