Skip to main content

k256/arithmetic/
mul.rs

1//! From libsecp256k1:
2//!
3//! The Secp256k1 curve has an endomorphism, where lambda * (x, y) = (beta * x, y), where
4//! lambda is {0x53,0x63,0xad,0x4c,0xc0,0x5c,0x30,0xe0,0xa5,0x26,0x1c,0x02,0x88,0x12,0x64,0x5a,
5//!         0x12,0x2e,0x22,0xea,0x20,0x81,0x66,0x78,0xdf,0x02,0x96,0x7c,0x1b,0x23,0xbd,0x72}
6//!
7//! "Guide to Elliptic Curve Cryptography" (Hankerson, Menezes, Vanstone) gives an algorithm
8//! (algorithm 3.74) to find k1 and k2 given k, such that k1 + k2 * lambda == k mod n, and k1
9//! and k2 have a small size.
10//! It relies on constants a1, b1, a2, b2. These constants for the value of lambda above are:
11//!
12//! - a1 =      {0x30,0x86,0xd2,0x21,0xa7,0xd4,0x6b,0xcd,0xe8,0x6c,0x90,0xe4,0x92,0x84,0xeb,0x15}
13//! - b1 =     -{0xe4,0x43,0x7e,0xd6,0x01,0x0e,0x88,0x28,0x6f,0x54,0x7f,0xa9,0x0a,0xbf,0xe4,0xc3}
14//! - a2 = {0x01,0x14,0xca,0x50,0xf7,0xa8,0xe2,0xf3,0xf6,0x57,0xc1,0x10,0x8d,0x9d,0x44,0xcf,0xd8}
15//! - b2 =      {0x30,0x86,0xd2,0x21,0xa7,0xd4,0x6b,0xcd,0xe8,0x6c,0x90,0xe4,0x92,0x84,0xeb,0x15}
16//!
17//! The algorithm then computes c1 = round(b1 * k / n) and c2 = round(b2 * k / n), and gives
18//! k1 = k - (c1*a1 + c2*a2) and k2 = -(c1*b1 + c2*b2). Instead, we use modular arithmetic, and
19//! compute k1 as k - k2 * lambda, avoiding the need for constants a1 and a2.
20//!
21//! g1, g2 are precomputed constants used to replace division with a rounded multiplication
22//! when decomposing the scalar for an endomorphism-based point multiplication.
23//!
24//! The possibility of using precomputed estimates is mentioned in "Guide to Elliptic Curve
25//! Cryptography" (Hankerson, Menezes, Vanstone) in section 3.5.
26//!
27//! The derivation is described in the paper "Efficient Software Implementation of Public-Key
28//! Cryptography on Sensor Networks Using the MSP430X Microcontroller" (Gouvea, Oliveira, Lopez),
29//! Section 4.3 (here we use a somewhat higher-precision estimate):
30//! d = a1*b2 - b1*a2
31//! g1 = round((2^384)*b2/d)
32//! g2 = round((2^384)*(-b1)/d)
33//!
34//! (Note that 'd' is also equal to the curve order here because `[a1,b1]` and `[a2,b2]` are found
35//! as outputs of the Extended Euclidean Algorithm on inputs 'order' and 'lambda').
36
37mod glv;
38
39use super::{ProjectivePoint, scalar::Scalar};
40use core::array;
41use elliptic_curve::{
42    array::sizes::{U5, U33},
43    ops::{LinearCombination, Mul, MulAssign, MulByGeneratorVartime, MulVartime},
44    scalar::IsHigh,
45    subtle::ConditionallySelectable,
46};
47use primeorder::Radix16Decomposition;
48
49#[cfg(feature = "alloc")]
50use alloc::vec::Vec;
51#[cfg(feature = "precomputed-tables")]
52use {super::tables::BASEPOINT_TABLE, elliptic_curve::array::sizes::U65};
53
54/// Lookup table for multiples of a given point.
55type LookupTable = primeorder::LookupTable<ProjectivePoint>;
56
57/// w-NAF window size to use by default.
58type WnafWindowSize = U5;
59
60/// `WnafBase` specialized for `k256`.
61type WnafBase = wnaf::WnafBase<ProjectivePoint, WnafWindowSize>;
62
63/// `WnafScalar` specialized for `k256`.
64type WnafScalar = wnaf::WnafScalar<Scalar, WnafWindowSize>;
65
66impl<const N: usize> LinearCombination<[(ProjectivePoint, Scalar); N]> for ProjectivePoint {
67    fn lincomb(points_and_scalars: &[(ProjectivePoint, Scalar); N]) -> Self {
68        let mut tables = [(LookupTable::default(), LookupTable::default()); N];
69        let mut digits: [(Radix16Decomposition<U33>, Radix16Decomposition<U33>); N] =
70            array::from_fn(|_| Default::default());
71        lincomb(points_and_scalars, &mut tables, &mut digits)
72    }
73
74    fn lincomb_vartime(points_and_scalars: &[(ProjectivePoint, Scalar); N]) -> Self {
75        let decomposed: [_; N] = array::from_fn(|i| {
76            let (x, k) = &points_and_scalars[i];
77            glv::decompose_wnaf(x, k)
78        });
79
80        lincomb_vartime_glv_wnaf(&decomposed)
81    }
82}
83
84impl LinearCombination<[(ProjectivePoint, Scalar)]> for ProjectivePoint {
85    #[cfg(feature = "alloc")]
86    fn lincomb(points_and_scalars: &[(ProjectivePoint, Scalar)]) -> Self {
87        let mut tables =
88            vec![(LookupTable::default(), LookupTable::default()); points_and_scalars.len()];
89        let mut digits = vec![
90            (
91                Radix16Decomposition::<U33>::default(),
92                Radix16Decomposition::<U33>::default(),
93            );
94            points_and_scalars.len()
95        ];
96
97        lincomb(points_and_scalars, &mut tables, &mut digits)
98    }
99
100    #[cfg(feature = "alloc")]
101    fn lincomb_vartime(points_and_scalars: &[(ProjectivePoint, Scalar)]) -> Self {
102        let decomposed: Vec<_> = points_and_scalars
103            .iter()
104            .map(|(x, k)| glv::decompose_wnaf(x, k))
105            .collect();
106
107        lincomb_vartime_glv_wnaf(&decomposed)
108    }
109}
110
111/// Linear combination (a.k.a. multiscalar multiplication) implemented in constant-time.
112fn lincomb(
113    xks: &[(ProjectivePoint, Scalar)],
114    tables: &mut [(LookupTable, LookupTable)],
115    digits: &mut [(Radix16Decomposition<U33>, Radix16Decomposition<U33>)],
116) -> ProjectivePoint {
117    xks.iter().enumerate().for_each(|(i, (x, k))| {
118        let (r1, r2) = glv::decompose_scalar(k);
119        let x_beta = x.endomorphism();
120        let (r1_sign, r2_sign) = (r1.is_high(), r2.is_high());
121
122        let (r1_c, r2_c) = (
123            Scalar::conditional_select(&r1, &-r1, r1_sign),
124            Scalar::conditional_select(&r2, &-r2, r2_sign),
125        );
126
127        tables[i] = (
128            LookupTable::new(ProjectivePoint::conditional_select(x, &-*x, r1_sign)),
129            LookupTable::new(ProjectivePoint::conditional_select(
130                &x_beta, &-x_beta, r2_sign,
131            )),
132        );
133
134        digits[i] = (
135            Radix16Decomposition::<U33>::new(&r1_c),
136            Radix16Decomposition::<U33>::new(&r2_c),
137        );
138    });
139
140    let mut acc = ProjectivePoint::IDENTITY;
141    for component in 0..xks.len() {
142        let (digit1, digit2) = &digits[component];
143        let (table1, table2) = tables[component];
144
145        acc += &table1.select(digit1[32]);
146        acc += &table2.select(digit2[32]);
147    }
148
149    for i in (0..32).rev() {
150        for _j in 0..4 {
151            acc.double_in_place();
152        }
153
154        for component in 0..xks.len() {
155            let (digit1, digit2) = &digits[component];
156            let (table1, table2) = tables[component];
157
158            acc += &table1.select(digit1[i]);
159            acc += &table2.select(digit2[i]);
160        }
161    }
162    acc
163}
164
165/// Linear combination / multiscalar multiplication using inputs decomposed for the GLV endomorphism
166/// (using `glv::decompose_wnaf`) in combination with w-NAF scalar multiplication.
167fn lincomb_vartime_glv_wnaf(
168    decomposed_xks: &[([WnafBase; 2], [WnafScalar; 2])],
169) -> ProjectivePoint {
170    let terms = decomposed_xks
171        .iter()
172        .flat_map(|(bases, scalars)| bases.iter().zip(scalars.iter()));
173
174    WnafBase::multiscalar_mul(terms)
175}
176
177impl ProjectivePoint {
178    /// Calculates `k * G`, where `G` is the generator.
179    #[must_use]
180    pub fn mul_by_generator(k: &Scalar) -> ProjectivePoint {
181        #[cfg(feature = "precomputed-tables")]
182        {
183            let digits = Radix16Decomposition::<U65>::new(k);
184            let table = *BASEPOINT_TABLE;
185            let mut acc = table[32].select(digits[64]);
186            let mut acc2 = ProjectivePoint::IDENTITY;
187            for i in (0..32).rev() {
188                acc2 += &table[i].select(digits[i * 2 + 1]);
189                acc += &table[i].select(digits[i * 2]);
190            }
191            // This is the price of halving the precomputed table size (from 60kb to 30kb)
192            // The performance hit is minor, about 3%.
193            for _ in 0..4 {
194                acc2.double_in_place();
195            }
196            acc + acc2
197        }
198
199        #[cfg(not(feature = "precomputed-tables"))]
200        {
201            ProjectivePoint::GENERATOR * k
202        }
203    }
204
205    /// Calculates `k * G` in variable-time, where `G` is the generator.
206    #[must_use]
207    pub fn mul_by_generator_vartime(k: &Scalar) -> ProjectivePoint {
208        #[cfg(feature = "precomputed-tables")]
209        {
210            let digits = Radix16Decomposition::<U65>::new(k);
211            let table = *BASEPOINT_TABLE;
212            let mut acc = table[32].select_vartime(digits[64]);
213            let mut acc2 = ProjectivePoint::IDENTITY;
214            for i in (0..32).rev() {
215                acc2 += &table[i].select_vartime(digits[i * 2 + 1]);
216                acc += &table[i].select_vartime(digits[i * 2]);
217            }
218
219            // This is the price of halving the precomputed table size (from 60kb to 30kb)
220            // The performance hit is minor, about 3%.
221            for _ in 0..4 {
222                acc2.double_in_place();
223            }
224
225            acc + acc2
226        }
227
228        #[cfg(not(feature = "precomputed-tables"))]
229        {
230            ProjectivePoint::GENERATOR.mul_vartime(k)
231        }
232    }
233}
234
235#[inline]
236fn mul(x: &ProjectivePoint, k: &Scalar) -> ProjectivePoint {
237    ProjectivePoint::lincomb(&[(*x, *k)])
238}
239
240/// Variable-time `k * self` using width-5 wNAF + GLV endomorphism.
241#[inline]
242fn mul_vartime(x: &ProjectivePoint, k: &Scalar) -> ProjectivePoint {
243    let mut bases = [WnafBase::default(), WnafBase::default()];
244    let mut scalars = [WnafScalar::default(), WnafScalar::default()];
245    glv::decompose_wnaf_into(x, k, &mut bases, &mut scalars);
246    WnafBase::multiscalar_mul([(&bases[0], &scalars[0]), (&bases[1], &scalars[1])].into_iter())
247}
248
249impl Mul<Scalar> for ProjectivePoint {
250    type Output = ProjectivePoint;
251
252    #[inline]
253    fn mul(self, other: Scalar) -> ProjectivePoint {
254        mul(&self, &other)
255    }
256}
257
258impl Mul<&Scalar> for &ProjectivePoint {
259    type Output = ProjectivePoint;
260
261    #[inline]
262    fn mul(self, other: &Scalar) -> ProjectivePoint {
263        mul(self, other)
264    }
265}
266
267impl Mul<&Scalar> for ProjectivePoint {
268    type Output = ProjectivePoint;
269
270    #[inline]
271    fn mul(self, other: &Scalar) -> ProjectivePoint {
272        mul(&self, other)
273    }
274}
275
276impl MulVartime<Scalar> for ProjectivePoint {
277    #[inline]
278    fn mul_vartime(self, other: Scalar) -> ProjectivePoint {
279        mul_vartime(&self, &other)
280    }
281}
282
283impl MulVartime<&Scalar> for &ProjectivePoint {
284    #[inline]
285    fn mul_vartime(self, other: &Scalar) -> ProjectivePoint {
286        mul_vartime(self, other)
287    }
288}
289
290impl MulVartime<&Scalar> for ProjectivePoint {
291    #[inline]
292    fn mul_vartime(self, other: &Scalar) -> ProjectivePoint {
293        mul_vartime(&self, other)
294    }
295}
296
297impl MulByGeneratorVartime for ProjectivePoint {
298    #[inline]
299    fn mul_by_generator_vartime(k: &Scalar) -> ProjectivePoint {
300        Self::mul_by_generator_vartime(k)
301    }
302
303    fn mul_by_generator_and_mul_add_vartime(a: &Self::Scalar, b: &Self::Scalar, p: &Self) -> Self {
304        let decomposed = [
305            glv::decompose_wnaf(&ProjectivePoint::GENERATOR, a),
306            glv::decompose_wnaf(p, b),
307        ];
308
309        lincomb_vartime_glv_wnaf(&decomposed)
310    }
311}
312
313impl MulAssign<Scalar> for ProjectivePoint {
314    fn mul_assign(&mut self, rhs: Scalar) {
315        *self = mul(self, &rhs);
316    }
317}
318
319impl MulAssign<&Scalar> for ProjectivePoint {
320    fn mul_assign(&mut self, rhs: &Scalar) {
321        *self = mul(self, rhs);
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328    use crate::arithmetic::{ProjectivePoint, Scalar};
329
330    #[cfg(feature = "getrandom")]
331    use elliptic_curve::Generate;
332
333    #[test]
334    #[cfg(feature = "getrandom")]
335    fn test_lincomb() {
336        let x = ProjectivePoint::generate();
337        let y = ProjectivePoint::generate();
338        let k = Scalar::generate();
339        let l = Scalar::generate();
340
341        let reference = x * k + y * l;
342        let test = ProjectivePoint::lincomb(&[(x, k), (y, l)]);
343        assert_eq!(reference, test);
344    }
345
346    #[test]
347    #[cfg(feature = "getrandom")]
348    fn test_mul_by_generator() {
349        let k = Scalar::generate();
350        let reference = ProjectivePoint::GENERATOR * k;
351        let test = ProjectivePoint::mul_by_generator(&k);
352        assert_eq!(reference, test);
353    }
354
355    #[test]
356    fn test_mul_vartime() {
357        let p = ProjectivePoint::GENERATOR;
358        assert_eq!(p.mul(&Scalar::ZERO), ProjectivePoint::IDENTITY);
359        assert_eq!(p.mul(&Scalar::ONE), p);
360        assert_eq!(p.mul(&-Scalar::ONE), -p);
361        assert_eq!(
362            ProjectivePoint::IDENTITY.mul(&Scalar::ONE),
363            ProjectivePoint::IDENTITY
364        );
365    }
366
367    #[cfg(all(feature = "alloc", feature = "getrandom"))]
368    #[test]
369    fn test_lincomb_slice() {
370        let x = ProjectivePoint::generate();
371        let y = ProjectivePoint::generate();
372        let k = Scalar::generate();
373        let l = Scalar::generate();
374
375        let reference = x * k + y * l;
376        let points_and_scalars = vec![(x, k), (y, l)];
377
378        let test = ProjectivePoint::lincomb(points_and_scalars.as_slice());
379        assert_eq!(reference, test);
380    }
381}