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}