curve25519_dalek/backend/serial/scalar_mul/
straus.rs

1// -*- mode: rust; -*-
2//
3// This file is part of curve25519-dalek.
4// Copyright (c) 2016-2021 isis lovecruft
5// Copyright (c) 2016-2019 Henry de Valence
6// See LICENSE for licensing information.
7//
8// Authors:
9// - isis agora lovecruft <isis@patternsinthevoid.net>
10// - Henry de Valence <hdevalence@hdevalence.ca>
11
12//! Implementation of the interleaved window method, also known as Straus' method.
13
14#![allow(non_snake_case)]
15
16use alloc::vec::Vec;
17
18use core::borrow::Borrow;
19use core::cmp::Ordering;
20
21use crate::edwards::EdwardsPoint;
22use crate::scalar::Scalar;
23use crate::traits::MultiscalarMul;
24use crate::traits::VartimeMultiscalarMul;
25
26/// Perform multiscalar multiplication by the interleaved window
27/// method, also known as Straus' method (since it was apparently
28/// [first published][solution] by Straus in 1964, as a solution to [a
29/// problem][problem] posted in the American Mathematical Monthly in
30/// 1963).
31///
32/// It is easy enough to reinvent, and has been repeatedly.  The basic
33/// idea is that when computing
34/// \\[
35/// Q = s_1 P_1 + \cdots + s_n P_n
36/// \\]
37/// by means of additions and doublings, the doublings can be shared
38/// across the \\( P_i \\\).
39///
40/// We implement two versions, a constant-time algorithm using fixed
41/// windows and a variable-time algorithm using sliding windows.  They
42/// are slight variations on the same idea, and are described in more
43/// detail in the respective implementations.
44///
45/// [solution]: https://www.jstor.org/stable/2310929
46/// [problem]: https://www.jstor.org/stable/2312273
47pub struct Straus {}
48
49impl MultiscalarMul for Straus {
50    type Point = EdwardsPoint;
51
52    /// Constant-time Straus using a fixed window of size \\(4\\).
53    ///
54    /// Our goal is to compute
55    /// \\[
56    /// Q = s_1 P_1 + \cdots + s_n P_n.
57    /// \\]
58    ///
59    /// For each point \\( P_i \\), precompute a lookup table of
60    /// \\[
61    /// P_i, 2P_i, 3P_i, 4P_i, 5P_i, 6P_i, 7P_i, 8P_i.
62    /// \\]
63    ///
64    /// For each scalar \\( s_i \\), compute its radix-\\(2^4\\)
65    /// signed digits \\( s_{i,j} \\), i.e.,
66    /// \\[
67    ///    s_i = s_{i,0} + s_{i,1} 16^1 + ... + s_{i,63} 16^{63},
68    /// \\]
69    /// with \\( -8 \leq s_{i,j} < 8 \\).  Since \\( 0 \leq |s_{i,j}|
70    /// \leq 8 \\), we can retrieve \\( s_{i,j} P_i \\) from the
71    /// lookup table with a conditional negation: using signed
72    /// digits halves the required table size.
73    ///
74    /// Then as in the single-base fixed window case, we have
75    /// \\[
76    /// \begin{aligned}
77    /// s_i P_i &= P_i (s_{i,0} +     s_{i,1} 16^1 + \cdots +     s_{i,63} 16^{63})   \\\\
78    /// s_i P_i &= P_i s_{i,0} + P_i s_{i,1} 16^1 + \cdots + P_i s_{i,63} 16^{63}     \\\\
79    /// s_i P_i &= P_i s_{i,0} + 16(P_i s_{i,1} + 16( \cdots +16P_i s_{i,63})\cdots )
80    /// \end{aligned}
81    /// \\]
82    /// so each \\( s_i P_i \\) can be computed by alternately adding
83    /// a precomputed multiple \\( P_i s_{i,j} \\) of \\( P_i \\) and
84    /// repeatedly doubling.
85    ///
86    /// Now consider the two-dimensional sum
87    /// \\[
88    /// \begin{aligned}
89    /// s\_1 P\_1 &=& P\_1 s\_{1,0} &+& 16 (P\_1 s\_{1,1} &+& 16 ( \cdots &+& 16 P\_1 s\_{1,63}&) \cdots ) \\\\
90    ///     +     & &      +        & &      +            & &             & &     +            &           \\\\
91    /// s\_2 P\_2 &=& P\_2 s\_{2,0} &+& 16 (P\_2 s\_{2,1} &+& 16 ( \cdots &+& 16 P\_2 s\_{2,63}&) \cdots ) \\\\
92    ///     +     & &      +        & &      +            & &             & &     +            &           \\\\
93    /// \vdots    & &  \vdots       & &   \vdots          & &             & &  \vdots          &           \\\\
94    ///     +     & &      +        & &      +            & &             & &     +            &           \\\\
95    /// s\_n P\_n &=& P\_n s\_{n,0} &+& 16 (P\_n s\_{n,1} &+& 16 ( \cdots &+& 16 P\_n s\_{n,63}&) \cdots )
96    /// \end{aligned}
97    /// \\]
98    /// The sum of the left-hand column is the result \\( Q \\); by
99    /// computing the two-dimensional sum on the right column-wise,
100    /// top-to-bottom, then right-to-left, we need to multiply by \\(
101    /// 16\\) only once per column, sharing the doublings across all
102    /// of the input points.
103    fn multiscalar_mul<I, J>(scalars: I, points: J) -> EdwardsPoint
104    where
105        I: IntoIterator,
106        I::Item: Borrow<Scalar>,
107        J: IntoIterator,
108        J::Item: Borrow<EdwardsPoint>,
109    {
110        use crate::backend::serial::curve_models::ProjectiveNielsPoint;
111        use crate::traits::Identity;
112        use crate::window::LookupTable;
113
114        let lookup_tables: Vec<_> = points
115            .into_iter()
116            .map(|point| LookupTable::<ProjectiveNielsPoint>::from(point.borrow()))
117            .collect();
118
119        // This puts the scalar digits into a heap-allocated Vec.
120        // To ensure that these are erased, pass ownership of the Vec into a
121        // Zeroizing wrapper.
122        #[cfg_attr(not(feature = "zeroize"), allow(unused_mut))]
123        let mut scalar_digits: Vec<_> = scalars
124            .into_iter()
125            .map(|s| s.borrow().as_radix_16())
126            .collect();
127
128        let mut Q = EdwardsPoint::identity();
129        for j in (0..64).rev() {
130            Q = Q.mul_by_pow_2(4);
131            let it = scalar_digits.iter().zip(lookup_tables.iter());
132            for (s_i, lookup_table_i) in it {
133                // R_i = s_{i,j} * P_i
134                let R_i = lookup_table_i.select(s_i[j]);
135                // Q = Q + R_i
136                Q = (&Q + &R_i).as_extended();
137            }
138        }
139
140        #[cfg(feature = "zeroize")]
141        zeroize::Zeroize::zeroize(&mut scalar_digits);
142
143        Q
144    }
145}
146
147impl VartimeMultiscalarMul for Straus {
148    type Point = EdwardsPoint;
149
150    /// Variable-time Straus using a non-adjacent form of width \\(5\\).
151    ///
152    /// This is completely similar to the constant-time code, but we
153    /// use a non-adjacent form for the scalar, and do not do table
154    /// lookups in constant time.
155    ///
156    /// The non-adjacent form has signed, odd digits.  Using only odd
157    /// digits halves the table size (since we only need odd
158    /// multiples), or gives fewer additions for the same table size.
159    fn optional_multiscalar_mul<I, J>(scalars: I, points: J) -> Option<EdwardsPoint>
160    where
161        I: IntoIterator,
162        I::Item: Borrow<Scalar>,
163        J: IntoIterator<Item = Option<EdwardsPoint>>,
164    {
165        use crate::backend::serial::curve_models::{
166            CompletedPoint, ProjectiveNielsPoint, ProjectivePoint,
167        };
168        use crate::traits::Identity;
169        use crate::window::NafLookupTable5;
170
171        let nafs: Vec<_> = scalars
172            .into_iter()
173            .map(|c| c.borrow().non_adjacent_form(5))
174            .collect();
175
176        let lookup_tables = points
177            .into_iter()
178            .map(|P_opt| P_opt.map(|P| NafLookupTable5::<ProjectiveNielsPoint>::from(&P)))
179            .collect::<Option<Vec<_>>>()?;
180
181        let mut r = ProjectivePoint::identity();
182
183        for i in (0..256).rev() {
184            let mut t: CompletedPoint = r.double();
185
186            for (naf, lookup_table) in nafs.iter().zip(lookup_tables.iter()) {
187                match naf[i].cmp(&0) {
188                    Ordering::Greater => {
189                        t = &t.as_extended() + &lookup_table.select(naf[i] as usize)
190                    }
191                    Ordering::Less => t = &t.as_extended() - &lookup_table.select(-naf[i] as usize),
192                    Ordering::Equal => {}
193                }
194            }
195
196            r = t.as_projective();
197        }
198
199        Some(r.as_extended())
200    }
201}