1#[cfg(all(
38 feature = "precomputed-tables",
39 not(any(feature = "critical-section", feature = "std"))
40))]
41compile_error!("`precomputed-tables` feature requires either `critical-section` or `std`");
42
43use crate::arithmetic::{
44 scalar::{Scalar, WideScalar},
45 ProjectivePoint,
46};
47
48use core::ops::{Mul, MulAssign};
49use elliptic_curve::ops::LinearCombinationExt as LinearCombination;
50use elliptic_curve::{
51 ops::MulByGenerator,
52 scalar::IsHigh,
53 subtle::{Choice, ConditionallySelectable, ConstantTimeEq},
54};
55
56#[cfg(feature = "precomputed-tables")]
57use once_cell::sync::Lazy;
58
59#[derive(Copy, Clone, Default)]
61struct LookupTable([ProjectivePoint; 8]);
62
63impl From<&ProjectivePoint> for LookupTable {
64 fn from(p: &ProjectivePoint) -> Self {
65 let mut points = [*p; 8];
66 for j in 0..7 {
67 points[j + 1] = p + &points[j];
68 }
69 LookupTable(points)
70 }
71}
72
73impl LookupTable {
74 fn select(&self, x: i8) -> ProjectivePoint {
76 debug_assert!(x >= -8);
77 debug_assert!(x <= 8);
78
79 let xmask = x >> 7;
81 let xabs = (x + xmask) ^ xmask;
82
83 let mut t = ProjectivePoint::IDENTITY;
85 for j in 1..9 {
86 let c = (xabs as u8).ct_eq(&(j as u8));
87 t.conditional_assign(&self.0[j - 1], c);
88 }
89 let neg_mask = Choice::from((xmask & 1) as u8);
92 t.conditional_assign(&-t, neg_mask);
93 t
96 }
97}
98
99const MINUS_LAMBDA: Scalar = Scalar::from_bytes_unchecked(&[
100 0xac, 0x9c, 0x52, 0xb3, 0x3f, 0xa3, 0xcf, 0x1f, 0x5a, 0xd9, 0xe3, 0xfd, 0x77, 0xed, 0x9b, 0xa4,
101 0xa8, 0x80, 0xb9, 0xfc, 0x8e, 0xc7, 0x39, 0xc2, 0xe0, 0xcf, 0xc8, 0x10, 0xb5, 0x12, 0x83, 0xcf,
102]);
103
104const MINUS_B1: Scalar = Scalar::from_bytes_unchecked(&[
105 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
106 0xe4, 0x43, 0x7e, 0xd6, 0x01, 0x0e, 0x88, 0x28, 0x6f, 0x54, 0x7f, 0xa9, 0x0a, 0xbf, 0xe4, 0xc3,
107]);
108
109const MINUS_B2: Scalar = Scalar::from_bytes_unchecked(&[
110 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
111 0x8a, 0x28, 0x0a, 0xc5, 0x07, 0x74, 0x34, 0x6d, 0xd7, 0x65, 0xcd, 0xa8, 0x3d, 0xb1, 0x56, 0x2c,
112]);
113
114const G1: Scalar = Scalar::from_bytes_unchecked(&[
115 0x30, 0x86, 0xd2, 0x21, 0xa7, 0xd4, 0x6b, 0xcd, 0xe8, 0x6c, 0x90, 0xe4, 0x92, 0x84, 0xeb, 0x15,
116 0x3d, 0xaa, 0x8a, 0x14, 0x71, 0xe8, 0xca, 0x7f, 0xe8, 0x93, 0x20, 0x9a, 0x45, 0xdb, 0xb0, 0x31,
117]);
118
119const G2: Scalar = Scalar::from_bytes_unchecked(&[
120 0xe4, 0x43, 0x7e, 0xd6, 0x01, 0x0e, 0x88, 0x28, 0x6f, 0x54, 0x7f, 0xa9, 0x0a, 0xbf, 0xe4, 0xc4,
121 0x22, 0x12, 0x08, 0xac, 0x9d, 0xf5, 0x06, 0xc6, 0x15, 0x71, 0xb4, 0xae, 0x8a, 0xc4, 0x7f, 0x71,
122]);
123
124fn decompose_scalar(k: &Scalar) -> (Scalar, Scalar) {
231 let c1 = WideScalar::mul_shift_vartime(k, &G1, 384) * MINUS_B1;
233 let c2 = WideScalar::mul_shift_vartime(k, &G2, 384) * MINUS_B2;
234 let r2 = c1 + c2;
235 let r1 = k + r2 * MINUS_LAMBDA;
236
237 (r1, r2)
238}
239
240#[derive(Copy, Clone)]
244struct Radix16Decomposition<const D: usize>([i8; D]);
245
246impl<const D: usize> Radix16Decomposition<D> {
247 fn new(x: &Scalar) -> Self {
252 debug_assert!((x >> (4 * (D - 1))).is_zero().unwrap_u8() == 1);
253
254 let mut output = [0i8; D];
257
258 let bytes = x.to_bytes();
261 for i in 0..(D - 1) / 2 {
262 output[2 * i] = (bytes[31 - i] & 0xf) as i8;
263 output[2 * i + 1] = ((bytes[31 - i] >> 4) & 0xf) as i8;
264 }
265
266 for i in 0..(D - 1) {
268 let carry = (output[i] + 8) >> 4;
269 output[i] -= carry << 4;
270 output[i + 1] += carry;
271 }
272
273 Self(output)
274 }
275}
276
277impl<const D: usize> Default for Radix16Decomposition<D> {
278 fn default() -> Self {
279 Self([0i8; D])
280 }
281}
282
283impl<const N: usize> LinearCombination<[(ProjectivePoint, Scalar); N]> for ProjectivePoint {
284 fn lincomb_ext(points_and_scalars: &[(ProjectivePoint, Scalar); N]) -> Self {
285 let mut tables = [(LookupTable::default(), LookupTable::default()); N];
286 let mut digits = [(
287 Radix16Decomposition::<33>::default(),
288 Radix16Decomposition::<33>::default(),
289 ); N];
290
291 lincomb(points_and_scalars, &mut tables, &mut digits)
292 }
293}
294
295#[cfg(feature = "alloc")]
296impl LinearCombination<[(ProjectivePoint, Scalar)]> for ProjectivePoint {
297 fn lincomb_ext(points_and_scalars: &[(ProjectivePoint, Scalar)]) -> Self {
298 let mut tables =
299 vec![(LookupTable::default(), LookupTable::default()); points_and_scalars.len()];
300 let mut digits = vec![
301 (
302 Radix16Decomposition::<33>::default(),
303 Radix16Decomposition::<33>::default(),
304 );
305 points_and_scalars.len()
306 ];
307
308 lincomb(points_and_scalars, &mut tables, &mut digits)
309 }
310}
311
312fn lincomb(
313 xks: &[(ProjectivePoint, Scalar)],
314 tables: &mut [(LookupTable, LookupTable)],
315 digits: &mut [(Radix16Decomposition<33>, Radix16Decomposition<33>)],
316) -> ProjectivePoint {
317 xks.iter().enumerate().for_each(|(i, (x, k))| {
318 let (r1, r2) = decompose_scalar(k);
319 let x_beta = x.endomorphism();
320 let (r1_sign, r2_sign) = (r1.is_high(), r2.is_high());
321
322 let (r1_c, r2_c) = (
323 Scalar::conditional_select(&r1, &-r1, r1_sign),
324 Scalar::conditional_select(&r2, &-r2, r2_sign),
325 );
326
327 tables[i] = (
328 LookupTable::from(&ProjectivePoint::conditional_select(x, &-*x, r1_sign)),
329 LookupTable::from(&ProjectivePoint::conditional_select(
330 &x_beta, &-x_beta, r2_sign,
331 )),
332 );
333
334 digits[i] = (
335 Radix16Decomposition::<33>::new(&r1_c),
336 Radix16Decomposition::<33>::new(&r2_c),
337 )
338 });
339
340 let mut acc = ProjectivePoint::IDENTITY;
341 for component in 0..xks.len() {
342 let (digit1, digit2) = digits[component];
343 let (table1, table2) = tables[component];
344
345 acc += &table1.select(digit1.0[32]);
346 acc += &table2.select(digit2.0[32]);
347 }
348
349 for i in (0..32).rev() {
350 for _j in 0..4 {
351 acc = acc.double();
352 }
353
354 for component in 0..xks.len() {
355 let (digit1, digit2) = digits[component];
356 let (table1, table2) = tables[component];
357
358 acc += &table1.select(digit1.0[i]);
359 acc += &table2.select(digit2.0[i]);
360 }
361 }
362 acc
363}
364
365#[cfg(feature = "precomputed-tables")]
367static GEN_LOOKUP_TABLE: Lazy<[LookupTable; 33]> = Lazy::new(precompute_gen_lookup_table);
368
369#[cfg(feature = "precomputed-tables")]
370fn precompute_gen_lookup_table() -> [LookupTable; 33] {
371 let mut gen = ProjectivePoint::GENERATOR;
372 let mut res = [LookupTable::default(); 33];
373
374 for i in 0..33 {
375 res[i] = LookupTable::from(&gen);
376 for _ in 0..8 {
379 gen = gen.double();
380 }
381 }
382 res
383}
384
385impl MulByGenerator for ProjectivePoint {
386 #[cfg(not(feature = "precomputed-tables"))]
388 fn mul_by_generator(k: &Scalar) -> ProjectivePoint {
389 ProjectivePoint::GENERATOR * k
390 }
391
392 #[cfg(feature = "precomputed-tables")]
394 fn mul_by_generator(k: &Scalar) -> ProjectivePoint {
395 let digits = Radix16Decomposition::<65>::new(k);
396 let table = *GEN_LOOKUP_TABLE;
397 let mut acc = table[32].select(digits.0[64]);
398 let mut acc2 = ProjectivePoint::IDENTITY;
399 for i in (0..32).rev() {
400 acc2 += &table[i].select(digits.0[i * 2 + 1]);
401 acc += &table[i].select(digits.0[i * 2]);
402 }
403 for _ in 0..4 {
406 acc2 = acc2.double();
407 }
408 acc + acc2
409 }
410}
411
412#[inline(always)]
413fn mul(x: &ProjectivePoint, k: &Scalar) -> ProjectivePoint {
414 ProjectivePoint::lincomb_ext(&[(*x, *k)])
415}
416
417impl Mul<Scalar> for ProjectivePoint {
418 type Output = ProjectivePoint;
419
420 fn mul(self, other: Scalar) -> ProjectivePoint {
421 mul(&self, &other)
422 }
423}
424
425impl Mul<&Scalar> for &ProjectivePoint {
426 type Output = ProjectivePoint;
427
428 fn mul(self, other: &Scalar) -> ProjectivePoint {
429 mul(self, other)
430 }
431}
432
433impl Mul<&Scalar> for ProjectivePoint {
434 type Output = ProjectivePoint;
435
436 fn mul(self, other: &Scalar) -> ProjectivePoint {
437 mul(&self, other)
438 }
439}
440
441impl MulAssign<Scalar> for ProjectivePoint {
442 fn mul_assign(&mut self, rhs: Scalar) {
443 *self = mul(self, &rhs);
444 }
445}
446
447impl MulAssign<&Scalar> for ProjectivePoint {
448 fn mul_assign(&mut self, rhs: &Scalar) {
449 *self = mul(self, rhs);
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456 use crate::arithmetic::{ProjectivePoint, Scalar};
457 use elliptic_curve::{
458 ops::{LinearCombination as _, MulByGenerator},
459 rand_core::OsRng,
460 Field, Group,
461 };
462
463 #[test]
464 fn test_lincomb() {
465 let x = ProjectivePoint::random(&mut OsRng);
466 let y = ProjectivePoint::random(&mut OsRng);
467 let k = Scalar::random(&mut OsRng);
468 let l = Scalar::random(&mut OsRng);
469
470 let reference = &x * &k + &y * &l;
471 let test = ProjectivePoint::lincomb(&x, &k, &y, &l);
472 assert_eq!(reference, test);
473 }
474
475 #[test]
476 fn test_mul_by_generator() {
477 let k = Scalar::random(&mut OsRng);
478 let reference = &ProjectivePoint::GENERATOR * &k;
479 let test = ProjectivePoint::mul_by_generator(&k);
480 assert_eq!(reference, test);
481 }
482
483 #[cfg(feature = "alloc")]
484 #[test]
485 fn test_lincomb_slice() {
486 let x = ProjectivePoint::random(&mut OsRng);
487 let y = ProjectivePoint::random(&mut OsRng);
488 let k = Scalar::random(&mut OsRng);
489 let l = Scalar::random(&mut OsRng);
490
491 let reference = &x * &k + &y * &l;
492 let points_and_scalars = vec![(x, k), (y, l)];
493
494 let test = ProjectivePoint::lincomb_ext(points_and_scalars.as_slice());
495 assert_eq!(reference, test);
496 }
497}