@@ -103,10 +103,10 @@ pub fn scalar_exp_vartime(x: &Scalar, mut n: u64) -> Scalar {
103
103
}
104
104
105
105
/// Computes the sum of all the powers of \\(x\\) \\(S(n) = (x^0 + \dots + x^{n-1})\\)
106
- /// using \\(O(\lg n)\\) multiplications and additions . Length \\(n\\) is not considered secret
107
- /// and algorithm is fastest when \\(n\\) is the power of two.
106
+ /// using \\(O(\lg n)\\) multiplications. Length \\(n\\) is not considered secret
107
+ /// and algorithm is fastest when \\(n\\) is the power of two (\\(2\lg n + 1\\) multiplications) .
108
108
///
109
- /// ### Algorithm overview
109
+ /// ### Algorithm description
110
110
///
111
111
/// First, let \\(n\\) be a power of two.
112
112
/// Then, we can divide the polynomial in two halves like so:
@@ -127,9 +127,26 @@ pub fn scalar_exp_vartime(x: &Scalar, mut n: u64) -> Scalar {
127
127
/// s_i &= s_{i-1} + x^{2^{i-1}} s_{i-1}
128
128
/// \end{aligned}
129
129
/// \\]
130
- /// This representation allows us to square \\(x\\) only \\(\lg n\\) times.
130
+ /// This representation allows us to do only \\(2 \cdot \lg n\\) multiplications:
131
+ /// squaring \\(x\\) and multiplying it by \\(s_{i-1}\\) at each iteration.
131
132
///
132
- /// Lets apply this to \\(n\\) which is not a power of two (\\(2^{k-1} < n < 2^k\\)) which can be represented in binary using
133
+ /// Lets apply this to \\(n\\) which is not a power of two. The intuition behind the generalized
134
+ /// algorithm is to combine all intermediate power-of-two-degree polynomials corresponding to the
135
+ /// bits of \\(n\\) that are equal to 1.
136
+ ///
137
+ /// 1. Represent \\(n\\) in binary.
138
+ /// 2. For each bit which is set (from the lowest to the highest):
139
+ /// 1. Compute a corresponding power-of-two-degree polynomial using the above algorithm.
140
+ /// Since we can reuse all intermediate polynomials, this adds no overhead to computing
141
+ /// a polynomial for the highest bit.
142
+ /// 2. Multiply the polynomial by the next power of \\(x\\), relative to the degree of the
143
+ /// already computed result. This effectively _offsets_ the polynomial to a correct range of
144
+ /// powers, so it can be added directly with the rest.
145
+ /// The next power of \\(x\\) is computed along all the intermediate polynomials,
146
+ /// by multiplying it by power-of-two power of \\(x\\) computed in step 2.1.
147
+ /// 3. Add to the result.
148
+ ///
149
+ /// (\\(2^{k-1} < n < 2^k\\)) which can be represented in binary using
133
150
/// bits \\(b_i\\) in \\(\\{0,1\\}\\):
134
151
/// \\[
135
152
/// n = b_0 2^0 + \dots + b_{k-1} 2^{k-1}
@@ -151,16 +168,16 @@ pub fn scalar_exp_vartime(x: &Scalar, mut n: u64) -> Scalar {
151
168
/// \\]
152
169
pub fn sum_of_powers(x: &Scalar, mut n: usize) -> Scalar {
153
170
let mut result = Scalar::zero();
154
- let mut f = Scalar::one(); // power of x to offset subsequent polynomials based on lower bits of n.
155
- let mut s = Scalar::one(); // power-of-two polynomial: 1, 1+x, 1+x+x^2+x^3, ...
156
- let mut p = *x; // x , x^2, x^4, ..., x^{2^i}
171
+ let mut f = Scalar::one(); // next- power-of- x to offset subsequent polynomials based on preceding bits of n.
172
+ let mut s = Scalar::one(); // power-of-two polynomials: ( 1, 1+x, 1+x+x^2+x^3, 1+ ...+x^7, , 1+...+x^15, ...)
173
+ let mut p = *x; // power-of-two powers of x: (x , x^2, x^4, ..., x^{2^i})
157
174
while n > 0 {
158
175
// take a bit from n
159
176
let bit = n & 1;
160
177
n = n >> 1;
161
178
162
179
if bit == 1 {
163
- // bits of `n` are not secret, so it's okay to be vartime because of `n` value .
180
+ // `n` is not secret, so it's okay to be vartime on bits of `n`.
164
181
result += f * s;
165
182
if n > 0 { // avoid multiplication if no bits left
166
183
f = f * p;
0 commit comments