1use crate::field::Fe;
11use crate::scalar;
12use ic_core::ct::Choice;
13
14#[cfg(feature = "std")]
16mod basepoint_table;
17use ic_core::traits::{Algorithm, Digest, SelfTest, SignatureScheme};
18use ic_core::{ensure, Result, Zeroize};
19use ic_hash::Sha512;
20
21#[cfg(test)]
23const BASEPOINT_COMPRESSED: [u8; 32] = [
24 0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
25 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
26];
27
28const D: Fe = Fe::from_limbs51([
30 929_955_233_495_203,
31 466_365_720_129_213,
32 1_662_059_464_998_953,
33 2_033_849_074_728_123,
34 1_442_794_654_840_575,
35]);
36
37const D2: Fe = Fe::from_limbs51([
39 1_859_910_466_990_425,
40 932_731_440_258_426,
41 1_072_319_116_312_658,
42 1_815_898_335_770_999,
43 633_789_495_995_903,
44]);
45
46const SQRT_M1: Fe = Fe::from_limbs51([
48 1_718_705_420_411_056,
49 234_908_883_556_509,
50 2_233_514_472_574_048,
51 2_117_202_627_021_982,
52 765_476_049_583_133,
53]);
54
55#[derive(Clone, Copy, Debug)]
57pub struct Point {
58 x: Fe,
59 y: Fe,
60 z: Fe,
61 t: Fe,
62}
63
64#[derive(Clone, Copy)]
74pub(crate) struct Completed {
75 x: Fe,
76 y: Fe,
77 z: Fe,
78 t: Fe,
79}
80
81#[derive(Clone, Copy)]
85pub(crate) struct Projective {
86 x: Fe,
87 y: Fe,
88 z: Fe,
89}
90
91#[derive(Clone, Copy)]
99pub(crate) struct Niels {
100 ypx: Fe,
101 ymx: Fe,
102 z: Fe,
103 t2d: Fe,
104}
105
106#[cfg(feature = "std")]
112#[derive(Clone, Copy)]
113pub(crate) struct AffineNiels {
114 ypx: Fe,
115 ymx: Fe,
116 t2d: Fe,
117}
118
119#[cfg(any(not(feature = "std"), test))]
123impl Niels {
124 const IDENTITY: Niels = Niels {
126 ypx: Fe::ONE,
127 ymx: Fe::ONE,
128 z: Fe::ONE,
129 t2d: Fe::ZERO,
130 };
131
132 fn conditional_negate(&mut self, choice: Choice) {
135 let swapped_p = self.ymx;
136 let swapped_m = self.ypx;
137 let nt = self.t2d.neg();
138 Fe::cmov(&mut self.ypx, &swapped_p, choice);
139 Fe::cmov(&mut self.ymx, &swapped_m, choice);
140 Fe::cmov(&mut self.t2d, &nt, choice);
141 }
142
143 fn cmov(&mut self, other: &Niels, choice: Choice) {
144 Fe::cmov(&mut self.ypx, &other.ypx, choice);
145 Fe::cmov(&mut self.ymx, &other.ymx, choice);
146 Fe::cmov(&mut self.z, &other.z, choice);
147 Fe::cmov(&mut self.t2d, &other.t2d, choice);
148 }
149}
150
151#[cfg(feature = "std")]
152impl AffineNiels {
153 pub(crate) const IDENTITY: AffineNiels = AffineNiels {
155 ypx: Fe::ONE,
156 ymx: Fe::ONE,
157 t2d: Fe::ZERO,
158 };
159
160 pub(crate) fn conditional_negate(&mut self, choice: Choice) {
163 let swapped_p = self.ymx;
164 let swapped_m = self.ypx;
165 let nt = self.t2d.neg();
166 Fe::cmov(&mut self.ypx, &swapped_p, choice);
167 Fe::cmov(&mut self.ymx, &swapped_m, choice);
168 Fe::cmov(&mut self.t2d, &nt, choice);
169 }
170
171 pub(crate) fn cmov(&mut self, other: &AffineNiels, choice: Choice) {
172 Fe::cmov(&mut self.ypx, &other.ypx, choice);
173 Fe::cmov(&mut self.ymx, &other.ymx, choice);
174 Fe::cmov(&mut self.t2d, &other.t2d, choice);
175 }
176}
177
178impl Completed {
179 fn to_projective(self) -> Projective {
181 Projective {
182 x: self.x.mul(&self.t),
183 y: self.y.mul(&self.z),
184 z: self.z.mul(&self.t),
185 }
186 }
187
188 fn to_extended(self) -> Point {
193 Point {
194 x: self.x.mul(&self.t),
195 y: self.y.mul(&self.z),
196 z: self.z.mul(&self.t),
197 t: self.x.mul(&self.y),
198 }
199 }
200}
201
202impl Projective {
203 fn to_extended_from_projective(self) -> Point {
208 Point {
215 x: self.x.mul(&self.z),
216 y: self.y.mul(&self.z),
217 z: self.z.square(),
218 t: self.x.mul(&self.y),
219 }
220 }
221
222 fn double_projective(self) -> Projective {
232 let xx = self.x.square();
233 let yy = self.y.square();
234 let zz2 = {
235 let t = self.z.square();
236 t.add(&t)
237 };
238 let xy_sq = self.x.add(&self.y).square();
239 let yy_plus_xx = yy.add(&xx);
240 let yy_minus_xx = yy.sub(&xx);
241
242 let cx = xy_sq.sub(&yy_plus_xx);
243 let cy = yy_plus_xx;
244 let cz = yy_minus_xx;
245 let ct = zz2.sub(&yy_minus_xx);
246
247 Projective {
248 x: cx.mul(&ct),
249 y: cy.mul(&cz),
250 z: cz.mul(&ct),
251 }
252 }
253
254 fn double(&self) -> Completed {
260 let xx = self.x.square();
261 let yy = self.y.square();
262 let zz2 = {
263 let t = self.z.square();
264 t.add(&t)
265 };
266 let xy_sq = self.x.add(&self.y).square();
267 let yy_plus_xx = yy.add(&xx);
268 let yy_minus_xx = yy.sub(&xx);
269 Completed {
270 x: xy_sq.sub(&yy_plus_xx),
271 y: yy_plus_xx,
272 z: yy_minus_xx,
273 t: zz2.sub(&yy_minus_xx),
274 }
275 }
276}
277
278impl Point {
279 pub const IDENTITY: Point = Point {
281 x: Fe::ZERO,
282 y: Fe::ONE,
283 z: Fe::ONE,
284 t: Fe::ZERO,
285 };
286
287 pub fn add(&self, other: &Point) -> Point {
289 let a = self.y.sub(&self.x).mul(&other.y.sub(&other.x));
290 let b = self.y.add(&self.x).mul(&other.y.add(&other.x));
291 let c = self.t.mul(&D2).mul(&other.t);
292 let d = self.z.mul(&other.z);
293 let d = d.add(&d);
294
295 let e = b.sub(&a);
296 let f = d.sub(&c);
297 let g = d.add(&c);
298 let h = b.add(&a);
299
300 Point {
301 x: e.mul(&f),
302 y: g.mul(&h),
303 t: e.mul(&h),
304 z: f.mul(&g),
305 }
306 }
307
308 pub fn double(&self) -> Point {
319 let aa = self.x.square();
320 let bb = self.y.square();
321 let c = self.z.square();
322 let c = c.add(&c);
323 let d = aa.neg();
325 let xy = self.x.add(&self.y);
327 let e = xy.square().sub(&aa).sub(&bb);
328 let g = d.add(&bb);
329 let f = g.sub(&c);
330 let h = d.sub(&bb);
331
332 Point {
333 x: e.mul(&f),
334 y: g.mul(&h),
335 t: e.mul(&h),
336 z: f.mul(&g),
337 }
338 }
339
340 fn to_projective(self) -> Projective {
342 Projective {
343 x: self.x,
344 y: self.y,
345 z: self.z,
346 }
347 }
348
349 fn to_niels(self) -> Niels {
351 Niels {
352 ypx: self.y.add(&self.x),
353 ymx: self.y.sub(&self.x),
354 z: self.z,
355 t2d: self.t.mul(&D2),
356 }
357 }
358
359 fn add_niels(&self, other: &Niels) -> Completed {
366 let pp = self.y.add(&self.x).mul(&other.ypx);
367 let mm = self.y.sub(&self.x).mul(&other.ymx);
368 let tt2d = self.t.mul(&other.t2d);
369 let zz = self.z.mul(&other.z);
370 let zz2 = zz.add(&zz);
371 Completed {
372 x: pp.sub(&mm),
373 y: pp.add(&mm),
374 z: zz2.add(&tt2d),
375 t: zz2.sub(&tt2d),
376 }
377 }
378
379 fn sub_niels(&self, other: &Niels) -> Completed {
385 let pp = self.y.add(&self.x).mul(&other.ymx);
386 let mm = self.y.sub(&self.x).mul(&other.ypx);
387 let tt2d = self.t.mul(&other.t2d);
388 let zz = self.z.mul(&other.z);
389 let zz2 = zz.add(&zz);
390 Completed {
391 x: pp.sub(&mm),
392 y: pp.add(&mm),
393 z: zz2.sub(&tt2d),
394 t: zz2.add(&tt2d),
395 }
396 }
397
398 #[cfg(feature = "std")]
404 pub(crate) fn to_affine_niels(self) -> AffineNiels {
405 let z_inv = self.z.invert();
406 let x = self.x.mul(&z_inv);
407 let y = self.y.mul(&z_inv);
408 AffineNiels {
409 ypx: y.add(&x),
410 ymx: y.sub(&x),
411 t2d: x.mul(&y).mul(&D2),
412 }
413 }
414
415 #[cfg(feature = "std")]
420 pub(crate) fn add_affine_niels(&self, other: &AffineNiels) -> Completed {
421 let pp = self.y.add(&self.x).mul(&other.ypx);
422 let mm = self.y.sub(&self.x).mul(&other.ymx);
423 let tt2d = self.t.mul(&other.t2d);
424 let zz2 = self.z.add(&self.z);
425 Completed {
426 x: pp.sub(&mm),
427 y: pp.add(&mm),
428 z: zz2.add(&tt2d),
429 t: zz2.sub(&tt2d),
430 }
431 }
432
433 #[cfg(feature = "std")]
442 pub(crate) fn sub_affine_niels(&self, other: &AffineNiels) -> Completed {
443 let pp = self.y.add(&self.x).mul(&other.ymx);
444 let mm = self.y.sub(&self.x).mul(&other.ypx);
445 let tt2d = self.t.mul(&other.t2d);
446 let zz2 = self.z.add(&self.z);
447 Completed {
448 x: pp.sub(&mm),
449 y: pp.add(&mm),
450 z: zz2.sub(&tt2d),
451 t: zz2.add(&tt2d),
452 }
453 }
454
455 fn cmov(&mut self, other: &Point, choice: Choice) {
457 Fe::cmov(&mut self.x, &other.x, choice);
458 Fe::cmov(&mut self.y, &other.y, choice);
459 Fe::cmov(&mut self.z, &other.z, choice);
460 Fe::cmov(&mut self.t, &other.t, choice);
461 }
462
463 pub fn mul_scalar(&self, s: &[u8; 32]) -> Point {
469 let mut acc = Point::IDENTITY;
470 for i in (0..256).rev() {
471 acc = acc.double();
472 let sum = acc.add(self);
473 let bit = Choice::from_u8((s[i / 8] >> (i % 8)) & 1);
474 acc.cmov(&sum, bit);
475 }
476 acc
477 }
478
479 fn negate(&self) -> Point {
481 Point {
482 x: self.x.neg(),
483 y: self.y,
484 z: self.z,
485 t: self.t.neg(),
486 }
487 }
488
489 fn eq_projective(&self, other: &Point) -> bool {
504 self.x.mul(&other.z).to_bytes() == other.x.mul(&self.z).to_bytes()
505 && self.y.mul(&other.z).to_bytes() == other.y.mul(&self.z).to_bytes()
506 }
507
508 pub fn compress(&self) -> [u8; 32] {
510 let z_inv = self.z.invert();
511 let x = self.x.mul(&z_inv);
512 let y = self.y.mul(&z_inv);
513 let mut out = y.to_bytes();
514 out[31] |= x.is_negative().unwrap_u8() << 7;
516 out
517 }
518
519 pub fn decompress(bytes: &[u8; 32]) -> Option<Point> {
521 let sign = Choice::from_u8(bytes[31] >> 7);
522 let mut y_bytes = *bytes;
523 y_bytes[31] &= 0x7f;
524 let y = Fe::from_bytes(&y_bytes);
525
526 let y2 = y.square();
528 let u = y2.sub(&Fe::ONE);
529 let v = y2.mul(&D).add(&Fe::ONE);
530
531 let v3 = v.square().mul(&v);
533 let v7 = v3.square().mul(&v);
534 let mut x = u.mul(&v3).mul(&u.mul(&v7).pow22523());
535
536 let check = v.mul(&x.square());
537 let correct = check.ct_eq(&u);
538 let flipped = check.ct_eq(&u.neg());
539 if !bool::from(correct.or(flipped)) {
540 return None;
542 }
543 let alt = x.mul(&SQRT_M1);
545 Fe::cmov(&mut x, &alt, flipped.and(correct.not()));
546
547 if bool::from(x.is_zero()) && bool::from(sign) {
549 return None;
550 }
551 let neg = x.neg();
553 let wrong_sign = Choice::from_u8(x.is_negative().unwrap_u8() ^ sign.unwrap_u8());
554 Fe::cmov(&mut x, &neg, wrong_sign);
555
556 Some(Point {
557 x,
558 y,
559 z: Fe::ONE,
560 t: x.mul(&y),
561 })
562 }
563}
564
565fn mul_basepoint(scalar: &[u8; 32]) -> Point {
572 #[cfg(feature = "std")]
573 {
574 basepoint_table::table().mul(scalar)
575 }
576 #[cfg(not(feature = "std"))]
577 {
578 mul_scalar_windowed(&basepoint(), scalar)
579 }
580}
581
582#[cfg(feature = "bench-internals")]
606#[doc(hidden)]
607pub fn double_scalar_mul_vartime_for_bench(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
608 double_scalar_mul_vartime(a, k, s)
609}
610
611#[cfg(any(not(feature = "std"), test))]
627fn mul_scalar_windowed(p: &Point, scalar: &[u8; 32]) -> Point {
628 let mut multiples = [*p; 8];
629 for i in 1..8 {
630 multiples[i] = multiples[i - 1].add(p);
631 }
632 let table: [Niels; 8] = core::array::from_fn(|i| multiples[i].to_niels());
633
634 let select = |digit: i8| -> Niels {
636 let negative = Choice::from_u8((digit as u8) >> 7);
637 let magnitude = ((digit as i16 ^ (digit as i16 >> 7)) - (digit as i16 >> 7)) as u8;
638 let mut out = Niels::IDENTITY;
639 for (i, entry) in table.iter().enumerate() {
640 out.cmov(entry, Choice::from_u8(u8::from(magnitude == (i as u8 + 1))));
641 }
642 out.conditional_negate(negative);
643 out
644 };
645
646 let digits = signed_digits(scalar);
647 let mut acc = Point::IDENTITY.add_niels(&select(digits[63])).to_extended();
648 for i in (0..63).rev() {
649 let mut q = acc.to_projective();
652 for _ in 0..3 {
653 q = q.double_projective();
654 }
655 acc = q.double().to_extended();
656 acc = acc.add_niels(&select(digits[i])).to_extended();
657 }
658 acc
659}
660
661fn odd_multiples(p: &Point) -> [Niels; 8] {
663 let twice = p.double();
664 let mut odd = [*p; 8];
665 for i in 1..8 {
666 odd[i] = odd[i - 1].add(&twice);
667 }
668 core::array::from_fn(|i| odd[i].to_niels())
669}
670
671fn signed_digits(scalar: &[u8; 32]) -> [i8; 64] {
687 debug_assert!(
688 scalar[31] <= 127,
689 "the top digit can only absorb the final carry for scalars below 2^255"
690 );
691
692 let mut nibbles = [0i8; 64];
693 for (i, byte) in scalar.iter().enumerate() {
694 nibbles[i * 2] = (byte & 0x0f) as i8;
695 nibbles[i * 2 + 1] = (byte >> 4) as i8;
696 }
697
698 for i in 0..63 {
699 let carry = (nibbles[i] + 8) >> 4;
700 nibbles[i] -= carry << 4;
701 nibbles[i + 1] += carry;
702 }
703 nibbles
704}
705
706#[cfg(feature = "std")]
707fn double_scalar_mul_vartime(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
708 let odd_b = basepoint_table::odd_multiples();
709 shared_doublings(a, k, &wnaf(s, 8), |e, digit| {
710 let n = &odd_b[(digit.unsigned_abs() as usize) / 2];
711 if digit > 0 {
712 e.add_affine_niels(n)
713 } else {
714 e.sub_affine_niels(n)
715 }
716 })
717}
718
719#[cfg(any(not(feature = "std"), test))]
727fn double_scalar_mul_vartime_no_table(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
728 let odd_b = odd_multiples(&basepoint());
729 shared_doublings(a, k, &wnaf(s, 5), |e, digit| {
730 let n = &odd_b[(digit.unsigned_abs() as usize) / 2];
731 if digit > 0 {
732 e.add_niels(n)
733 } else {
734 e.sub_niels(n)
735 }
736 })
737}
738
739#[inline(always)]
742fn shared_doublings(
743 a: &Point,
744 k: &[u8; 32],
745 naf_b: &[i8; 258],
746 add_b: impl Fn(&Point, i8) -> Completed,
747) -> Point {
748 let odd_a = odd_multiples(a);
750 let naf_a = wnaf(k, 5);
751
752 let mut i = 257;
755 while i > 0 && naf_a[i] == 0 && naf_b[i] == 0 {
756 i -= 1;
757 }
758
759 let mut acc = Point::IDENTITY.to_projective();
764 loop {
765 if naf_a[i] == 0 && naf_b[i] == 0 {
768 acc = acc.double_projective();
769 if i == 0 {
770 return acc.to_extended_from_projective();
771 }
772 i -= 1;
773 continue;
774 }
775 let mut t = acc.double();
776 if naf_a[i] != 0 {
777 let e = t.to_extended();
778 let n = &odd_a[(naf_a[i].unsigned_abs() as usize) / 2];
779 t = if naf_a[i] > 0 {
780 e.add_niels(n)
781 } else {
782 e.sub_niels(n)
783 };
784 }
785 if naf_b[i] != 0 {
786 t = add_b(&t.to_extended(), naf_b[i]);
787 }
788 if i == 0 {
789 return t.to_extended();
790 }
791 acc = t.to_projective();
792 i -= 1;
793 }
794}
795
796#[cfg(feature = "bench-internals")]
799#[doc(hidden)]
800pub fn mul_basepoint_for_bench(scalar: &[u8; 32]) -> Point {
801 mul_basepoint(scalar)
802}
803
804fn basepoint() -> Point {
805 BASEPOINT
806}
807
808const BASEPOINT: Point = Point {
817 x: Fe::from_limbs51([
818 1_738_742_601_995_546,
819 1_146_398_526_822_698,
820 2_070_867_633_025_821,
821 562_264_141_797_630,
822 587_772_402_128_613,
823 ]),
824 y: Fe::from_limbs51([
825 1_801_439_850_948_184,
826 1_351_079_888_211_148,
827 450_359_962_737_049,
828 900_719_925_474_099,
829 1_801_439_850_948_198,
830 ]),
831 z: Fe::ONE,
832 t: Fe::from_limbs51([
833 1_841_354_044_333_475,
834 16_398_895_984_059,
835 755_974_180_946_558,
836 900_171_276_175_154,
837 1_821_297_809_914_039,
838 ]),
839};
840
841pub struct Ed25519;
843
844impl Algorithm for Ed25519 {
845 const ID: &'static str = "ed25519";
846 const NAME: &'static str = "Ed25519";
847}
848
849fn expand_seed(seed: &[u8]) -> ([u8; 32], [u8; 32]) {
851 let h = Sha512::digest(seed);
852 let mut a = [0u8; 32];
853 let mut prefix = [0u8; 32];
854 a.copy_from_slice(&h.as_ref()[..32]);
855 prefix.copy_from_slice(&h.as_ref()[32..]);
856 a[0] &= 248;
857 a[31] &= 127;
858 a[31] |= 64;
859 (a, prefix)
860}
861
862fn wnaf(scalar: &[u8; 32], w: u32) -> [i8; 258] {
872 debug_assert!((2..=8).contains(&w), "window width out of range");
873 let half = 1i64 << (w - 1);
874 let full = 1i64 << w;
875 let mask = (full - 1) as u64;
876
877 let mut naf = [0i8; 258];
878 let mut k = [0u64; 5];
885 for (i, limb) in k.iter_mut().take(4).enumerate() {
886 let mut b = [0u8; 8];
887 b.copy_from_slice(&scalar[i * 8..i * 8 + 8]);
888 *limb = u64::from_le_bytes(b);
889 }
890
891 let mut i = 0;
892 while k.iter().any(|&x| x != 0) {
893 if k[0] & 1 == 1 {
894 let mut d = (k[0] & mask) as i64;
895 if d >= half {
896 d -= full;
897 }
898 naf[i] = d as i8;
899 if d > 0 {
900 sub_u64(&mut k, d as u64);
901 } else {
902 add_u64(&mut k, d.unsigned_abs());
903 }
904 }
905 shr1(&mut k);
906 i += 1;
907 }
908 naf
909}
910
911fn sub_u64(k: &mut [u64; 5], v: u64) {
913 let (d, mut borrow) = k[0].overflowing_sub(v);
914 k[0] = d;
915 for limb in k.iter_mut().skip(1) {
916 if !borrow {
917 break;
918 }
919 let (d, b) = limb.overflowing_sub(1);
920 *limb = d;
921 borrow = b;
922 }
923}
924
925fn add_u64(k: &mut [u64; 5], v: u64) {
927 let (d, mut carry) = k[0].overflowing_add(v);
928 k[0] = d;
929 for limb in k.iter_mut().skip(1) {
930 if !carry {
931 break;
932 }
933 let (d, c) = limb.overflowing_add(1);
934 *limb = d;
935 carry = c;
936 }
937}
938
939fn shr1(k: &mut [u64; 5]) {
941 for i in 0..4 {
942 k[i] = (k[i] >> 1) | (k[i + 1] << 63);
943 }
944 k[4] >>= 1;
945}
946
947fn hash_to_scalar(parts: &[&[u8]]) -> [u8; 32] {
949 let mut h = Sha512::new();
950 for p in parts {
951 h.update(p);
952 }
953 let digest = h.finalize();
954 let mut wide = [0u8; 64];
955 wide.copy_from_slice(digest.as_ref());
956 scalar::reduce_wide(&wide)
957}
958
959pub struct Ed25519Key {
976 scalar: [u8; 32],
978 prefix: [u8; 32],
980 public: [u8; 32],
982}
983
984impl Drop for Ed25519Key {
985 fn drop(&mut self) {
986 self.scalar.zeroize();
987 self.prefix.zeroize();
988 }
990}
991
992impl Ed25519Key {
993 pub fn from_seed(seed: &[u8]) -> Result<Self> {
995 ensure!(seed.len() == 32, InvalidLength, "ed25519 seed");
996 let (scalar, prefix) = expand_seed(seed);
997 let public = mul_basepoint(&scalar).compress();
998 Ok(Self {
999 scalar,
1000 prefix,
1001 public,
1002 })
1003 }
1004
1005 pub fn public_key(&self) -> &[u8; 32] {
1007 &self.public
1008 }
1009
1010 pub fn sign(&self, message: &[u8], signature: &mut [u8]) -> Result<()> {
1012 ensure!(
1013 signature.len() == 64,
1014 InvalidLength,
1015 "ed25519 signature buffer"
1016 );
1017
1018 let mut r = hash_to_scalar(&[&self.prefix, message]);
1021 let big_r = mul_basepoint(&r).compress();
1022
1023 let k = hash_to_scalar(&[&big_r, &self.public, message]);
1024 let s = scalar::mul_add(&k, &self.scalar, &r);
1025
1026 signature[..32].copy_from_slice(&big_r);
1027 signature[32..].copy_from_slice(&s);
1028 r.zeroize();
1029 Ok(())
1030 }
1031}
1032
1033impl SignatureScheme for Ed25519 {
1034 const PRIVATE_KEY_LEN: usize = 32;
1035 const PUBLIC_KEY_LEN: usize = 32;
1036 const SIGNATURE_LEN: usize = 64;
1037
1038 fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()> {
1039 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1040 ensure!(out.len() == 32, InvalidLength, "ed25519 public key buffer");
1041 let (mut a, mut prefix) = expand_seed(private_key);
1042 out.copy_from_slice(&mul_basepoint(&a).compress());
1043 a.zeroize();
1044 prefix.zeroize();
1045 Ok(())
1046 }
1047
1048 fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()> {
1049 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1050 ensure!(
1051 signature.len() == 64,
1052 InvalidLength,
1053 "ed25519 signature buffer"
1054 );
1055
1056 Ed25519Key::from_seed(private_key)?.sign(message, signature)
1060 }
1061
1062 fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()> {
1063 Ed25519VerifyKey::from_bytes(public_key)?.verify(message, signature)
1067 }
1068}
1069
1070pub struct Ed25519VerifyKey {
1084 compressed: [u8; 32],
1086 neg_a: Point,
1088}
1089
1090impl Ed25519VerifyKey {
1091 pub fn from_bytes(public_key: &[u8]) -> Result<Self> {
1093 ensure!(public_key.len() == 32, InvalidLength, "ed25519 public key");
1094 let mut compressed = [0u8; 32];
1095 compressed.copy_from_slice(public_key);
1096 let a = Point::decompress(&compressed).ok_or(ic_core::err!(
1097 MalformedEncoding,
1098 "ed25519 public key is not on the curve"
1099 ))?;
1100 Ok(Self {
1101 compressed,
1102 neg_a: a.negate(),
1103 })
1104 }
1105
1106 pub fn as_bytes(&self) -> &[u8; 32] {
1108 &self.compressed
1109 }
1110
1111 pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> {
1113 ensure!(signature.len() == 64, InvalidLength, "ed25519 signature");
1114
1115 let mut big_r = [0u8; 32];
1116 big_r.copy_from_slice(&signature[..32]);
1117 let mut s = [0u8; 32];
1118 s.copy_from_slice(&signature[32..]);
1119
1120 ensure!(
1124 scalar::is_canonical(&s),
1125 MalformedEncoding,
1126 "ed25519 signature S is not reduced"
1127 );
1128
1129 let r_point = Point::decompress(&big_r).ok_or(ic_core::err!(
1130 MalformedEncoding,
1131 "ed25519 signature R is not on the curve"
1132 ))?;
1133
1134 let k = hash_to_scalar(&[&big_r, &self.compressed, message]);
1135
1136 #[cfg(feature = "std")]
1140 let lhs = double_scalar_mul_vartime(&self.neg_a, &k, &s);
1141 #[cfg(not(feature = "std"))]
1142 let lhs = double_scalar_mul_vartime_no_table(&self.neg_a, &k, &s);
1143
1144 if lhs.eq_projective(&r_point) {
1145 Ok(())
1146 } else {
1147 Err(ic_core::err!(AuthenticationFailed, "ed25519"))
1148 }
1149 }
1150}
1151
1152impl SelfTest for Ed25519 {
1153 fn self_test() -> Result<()> {
1154 let mut seed = [0u8; 32];
1156 ic_core::codec::hex_decode(
1157 b"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1158 &mut seed,
1159 )?;
1160 let mut want_pk = [0u8; 32];
1161 ic_core::codec::hex_decode(
1162 b"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1163 &mut want_pk,
1164 )?;
1165 let mut want_sig = [0u8; 64];
1166 ic_core::codec::hex_decode(
1167 b"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1168 &mut want_sig,
1169 )?;
1170
1171 let mut pk = [0u8; 32];
1172 <Self as SignatureScheme>::public_key(&seed, &mut pk)?;
1173 ensure!(
1174 ic_core::ct::verify(&want_pk, &pk),
1175 SelfTestFailed,
1176 "ed25519"
1177 );
1178
1179 let mut sig = [0u8; 64];
1180 <Self as SignatureScheme>::sign(&seed, b"", &mut sig)?;
1181 ensure!(
1182 ic_core::ct::verify(&want_sig, &sig),
1183 SelfTestFailed,
1184 "ed25519"
1185 );
1186
1187 <Self as SignatureScheme>::verify(&pk, b"", &sig)?;
1188
1189 sig[0] ^= 1;
1191 ensure!(
1192 <Self as SignatureScheme>::verify(&pk, b"", &sig).is_err(),
1193 SelfTestFailed,
1194 "ed25519"
1195 );
1196 Ok(())
1197 }
1198}
1199
1200#[cfg(test)]
1201mod tests {
1202 use super::*;
1203 use ic_core::codec::{hex, unhex};
1204
1205 #[test]
1206 fn curve_constants_are_correct() {
1207 let d = Fe::from_u64(121_665)
1209 .neg()
1210 .mul(&Fe::from_u64(121_666).invert());
1211 assert_eq!(hex(&D.to_bytes()), hex(&d.to_bytes()), "d");
1212 assert_eq!(hex(&D2.to_bytes()), hex(&d.add(&d).to_bytes()), "2d");
1213 assert_eq!(
1215 hex(&SQRT_M1.square().to_bytes()),
1216 hex(&Fe::ONE.neg().to_bytes()),
1217 "sqrt(-1)"
1218 );
1219 }
1220
1221 #[test]
1222 fn basepoint_has_the_expected_coordinates() {
1223 let b = basepoint();
1224 let expected_y = Fe::from_u64(4).mul(&Fe::from_u64(5).invert());
1226 let z_inv = b.z.invert();
1227 assert_eq!(
1228 hex(&b.y.mul(&z_inv).to_bytes()),
1229 hex(&expected_y.to_bytes())
1230 );
1231 assert_eq!(hex(&b.compress()), hex(&BASEPOINT_COMPRESSED));
1232 }
1233
1234 #[test]
1241 fn doubling_agrees_with_adding_a_point_to_itself() {
1242 let mut p = basepoint();
1243 let mut checked = 0;
1244 for _ in 0..16 {
1245 assert_eq!(
1246 p.double().compress(),
1247 p.add(&p).compress(),
1248 "dedicated doubling and self-addition differ"
1249 );
1250 p = p.add(&basepoint());
1251 checked += 1;
1252 }
1253 assert_eq!(checked, 16, "the comparison did not run");
1254
1255 assert_eq!(
1258 Point::IDENTITY.double().compress(),
1259 Point::IDENTITY.compress()
1260 );
1261 }
1262
1263 #[test]
1271 fn projective_equality_agrees_with_compressed_equality() {
1272 let b = basepoint();
1273 let mut points = std::vec![Point::IDENTITY, b];
1274 let mut p = b;
1275 for _ in 0..6 {
1276 p = p.double();
1277 points.push(p);
1278 }
1279
1280 let mut checked = 0;
1281 for (i, a) in points.iter().enumerate() {
1282 for (j, c) in points.iter().enumerate() {
1283 let projective = a.eq_projective(c);
1284 let compressed = a.compress() == c.compress();
1285 assert_eq!(
1286 projective, compressed,
1287 "projective and compressed equality differ for {i} vs {j}"
1288 );
1289 checked += 1;
1290 }
1291 }
1292 assert_eq!(checked, 64, "the comparison did not run");
1293
1294 let scaled = b.add(&Point::IDENTITY);
1297 assert!(b.eq_projective(&scaled), "equal points with different Z");
1298 assert_eq!(b.compress(), scaled.compress());
1299 }
1300
1301 #[test]
1302 fn group_law_is_consistent() {
1303 let b = basepoint();
1304 assert_eq!(hex(&b.add(&Point::IDENTITY).compress()), hex(&b.compress()));
1306 let mut two = [0u8; 32];
1308 two[0] = 2;
1309 assert_eq!(
1310 hex(&b.double().compress()),
1311 hex(&b.mul_scalar(&two).compress())
1312 );
1313 let mut three = [0u8; 32];
1315 three[0] = 3;
1316 assert_eq!(
1317 hex(&b.double().add(&b).compress()),
1318 hex(&b.mul_scalar(&three).compress())
1319 );
1320 }
1321
1322 #[test]
1323 fn order_of_the_basepoint_is_l() {
1324 assert_eq!(
1326 hex(&basepoint().mul_scalar(&scalar::L).compress()),
1327 hex(&Point::IDENTITY.compress())
1328 );
1329 }
1330
1331 #[test]
1332 fn compression_roundtrips() {
1333 let b = basepoint();
1334 for k in [1u8, 2, 3, 47, 200] {
1335 let mut s = [0u8; 32];
1336 s[0] = k;
1337 let p = b.mul_scalar(&s);
1338 let c = p.compress();
1339 let d = Point::decompress(&c).expect("valid point");
1340 assert_eq!(hex(&d.compress()), hex(&c), "k = {k}");
1341 }
1342 }
1343
1344 #[test]
1345 fn decompression_rejects_non_curve_points() {
1346 let mut bad = [0u8; 32];
1348 bad[0] = 2;
1349 assert!(Point::decompress(&bad).is_none());
1350 }
1351
1352 #[test]
1354 fn rfc8032_vectors() {
1355 let cases: [(&str, &str, &str, &str); 3] = [
1356 (
1357 "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1358 "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1359 "",
1360 "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1361 ),
1362 (
1363 "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
1364 "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
1365 "72",
1366 "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
1367 ),
1368 (
1369 "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
1370 "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
1371 "af82",
1372 "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
1373 ),
1374 ];
1375
1376 for (seed_hex, pk_hex, msg_hex, sig_hex) in cases {
1377 let seed = unhex(seed_hex).unwrap();
1378 let msg = unhex(msg_hex).unwrap();
1379
1380 let mut pk = [0u8; 32];
1381 Ed25519::public_key(&seed, &mut pk).unwrap();
1382 assert_eq!(hex(&pk), pk_hex, "public key for {seed_hex}");
1383
1384 let mut sig = [0u8; 64];
1385 Ed25519::sign(&seed, &msg, &mut sig).unwrap();
1386 assert_eq!(hex(&sig), sig_hex, "signature for {seed_hex}");
1387
1388 Ed25519::verify(&pk, &msg, &sig).unwrap();
1389 }
1390 }
1391
1392 #[test]
1393 fn verification_rejects_tampering() {
1394 let seed = [0x42u8; 32];
1395 let mut pk = [0u8; 32];
1396 Ed25519::public_key(&seed, &mut pk).unwrap();
1397 let mut sig = [0u8; 64];
1398 Ed25519::sign(&seed, b"authentic", &mut sig).unwrap();
1399 Ed25519::verify(&pk, b"authentic", &sig).unwrap();
1400
1401 assert!(Ed25519::verify(&pk, b"forged", &sig).is_err());
1403 let mut bad = sig;
1405 bad[0] ^= 1;
1406 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1407 let mut bad = sig;
1409 bad[40] ^= 1;
1410 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1411 let mut other_pk = [0u8; 32];
1413 Ed25519::public_key(&[0x43u8; 32], &mut other_pk).unwrap();
1414 assert!(Ed25519::verify(&other_pk, b"authentic", &sig).is_err());
1415 }
1416
1417 #[test]
1420 fn rejects_non_canonical_s() {
1421 let seed = [0x42u8; 32];
1422 let mut pk = [0u8; 32];
1423 Ed25519::public_key(&seed, &mut pk).unwrap();
1424 let mut sig = [0u8; 64];
1425 Ed25519::sign(&seed, b"msg", &mut sig).unwrap();
1426
1427 let mut carry = 0u16;
1430 for i in 0..32 {
1431 let t = sig[32 + i] as u16 + scalar::L[i] as u16 + carry;
1432 sig[32 + i] = t as u8;
1433 carry = t >> 8;
1434 }
1435 assert!(Ed25519::verify(&pk, b"msg", &sig).is_err());
1436 }
1437
1438 #[test]
1445 fn the_cached_key_signs_identically_to_the_seed() {
1446 let mut checked = 0;
1447 for seed in [[0x11u8; 32], [0x9du8; 32], [0xffu8; 32]] {
1448 for message in [&b""[..], &b"x"[..], &b"a longer message to sign"[..]] {
1449 let mut from_seed = [0u8; 64];
1450 Ed25519::sign(&seed, message, &mut from_seed).unwrap();
1451
1452 let key = Ed25519Key::from_seed(&seed).unwrap();
1453 let mut from_key = [0u8; 64];
1454 key.sign(message, &mut from_key).unwrap();
1455
1456 assert_eq!(from_seed, from_key, "the two signing paths diverged");
1457
1458 let mut derived = [0u8; 32];
1460 Ed25519::public_key(&seed, &mut derived).unwrap();
1461 assert_eq!(&derived, key.public_key());
1462
1463 Ed25519::verify(&derived, message, &from_key).unwrap();
1465 checked += 1;
1466 }
1467 }
1468 assert_eq!(checked, 9, "the comparison did not run");
1469 }
1470
1471 fn windowed_scalars() -> std::vec::Vec<[u8; 32]> {
1476 let mut one = [0u8; 32];
1477 one[0] = 1;
1478 let mut eight = [0u8; 32];
1479 eight[0] = 8;
1480 let mut top = [0xffu8; 32];
1481 top[31] = 0x7f;
1482 let mut clamped = [0x9du8; 32];
1483 clamped[0] &= 248;
1484 clamped[31] &= 127;
1485 clamped[31] |= 64;
1486 let mut l_minus_1 = scalar::L;
1487 l_minus_1[0] -= 1;
1488 let mut out = std::vec![[0u8; 32], one, eight, top, clamped, l_minus_1];
1489 for fill in [0x88u8, 0x77, 0x99, 0x55, 0xaa] {
1490 let mut s = [fill; 32];
1491 s[31] &= 0x7f;
1492 out.push(s);
1493 }
1494 out
1495 }
1496
1497 #[test]
1507 fn the_basepoint_constant_is_the_decompressed_encoding() {
1508 let decoded = Point::decompress(&BASEPOINT_COMPRESSED).expect("the RFC 8032 basepoint");
1509 let zinv = decoded.z.invert();
1510 for (name, constant, from_encoding) in [
1511 ("x", BASEPOINT.x, decoded.x.mul(&zinv)),
1512 ("y", BASEPOINT.y, decoded.y.mul(&zinv)),
1513 ("t", BASEPOINT.t, decoded.t.mul(&zinv)),
1514 ] {
1515 assert_eq!(
1516 constant.to_bytes(),
1517 from_encoding.to_bytes(),
1518 "{name} differs"
1519 );
1520 }
1521 assert_eq!(BASEPOINT.z.to_bytes(), Fe::ONE.to_bytes());
1522 assert_eq!(BASEPOINT.compress(), BASEPOINT_COMPRESSED);
1523 }
1524
1525 #[test]
1526 fn the_windowed_multiplication_agrees_with_the_ladder() {
1527 let b = basepoint();
1528 let mut seven = [0u8; 32];
1529 seven[0] = 7;
1530 let p = b.mul_scalar(&seven);
1531
1532 let mut checked = 0;
1533 for point in [b, p] {
1534 for scalar in windowed_scalars() {
1535 assert_eq!(
1536 mul_scalar_windowed(&point, &scalar).compress(),
1537 point.mul_scalar(&scalar).compress(),
1538 "windowed and ladder differ for {scalar:02x?}"
1539 );
1540 checked += 1;
1541 }
1542 }
1543 assert!(checked >= 20, "only {checked} comparisons ran");
1544 }
1545
1546 #[test]
1549 fn the_untabled_double_multiplication_agrees() {
1550 let b = basepoint();
1551 let mut checked = 0;
1552 for (i, k) in windowed_scalars().into_iter().enumerate() {
1553 let mut seed = [0u8; 32];
1554 seed[0] = 3 + i as u8;
1555 let a = b.mul_scalar(&seed);
1556 for s in [k, [0xffu8; 32], [0x9du8; 32]] {
1557 let untabled = double_scalar_mul_vartime_no_table(&a, &k, &s);
1558 let tabled = double_scalar_mul_vartime(&a, &k, &s);
1559 let ladders = a.mul_scalar(&k).add(&b.mul_scalar(&s));
1560 assert_eq!(
1561 untabled.compress(),
1562 tabled.compress(),
1563 "k={k:02x?} s={s:02x?}"
1564 );
1565 assert_eq!(
1566 untabled.compress(),
1567 ladders.compress(),
1568 "k={k:02x?} s={s:02x?}"
1569 );
1570 checked += 1;
1571 }
1572 }
1573 assert!(checked >= 30, "only {checked} comparisons ran");
1574 }
1575
1576 #[test]
1578 fn the_wnaf_digits_are_odd_sparse_and_faithful() {
1579 for scalar in [[1u8; 32], [0x9du8; 32], [0xffu8; 32], [0x55u8; 32]] {
1580 let naf = wnaf(&scalar, 5);
1581
1582 let mut previous_nonzero: Option<usize> = None;
1583 for (i, d) in naf.iter().enumerate() {
1584 if *d == 0 {
1585 continue;
1586 }
1587 assert!(d % 2 != 0, "digit {d} at {i} is not odd");
1588 assert!((-15..=15).contains(d), "digit {d} at {i} is out of range");
1589 if let Some(j) = previous_nonzero {
1590 assert!(i - j >= 5, "digits at {j} and {i} are adjacent");
1591 }
1592 previous_nonzero = Some(i);
1593 }
1594
1595 const M: u128 = 1_000_000_007;
1598 let mut from_digits = 0u128;
1599 let mut power = 1u128;
1600 for d in naf {
1601 let term = ((d as i128).rem_euclid(M as i128)) as u128;
1602 from_digits = (from_digits + term * power) % M;
1603 power = power * 2 % M;
1604 }
1605 let mut from_bytes = 0u128;
1606 let mut p = 1u128;
1607 for byte in scalar {
1608 from_bytes = (from_bytes + (byte as u128) * p) % M;
1609 p = p * 256 % M;
1610 }
1611 assert_eq!(from_digits, from_bytes, "recoding changed the value");
1612 }
1613 }
1614
1615 #[test]
1616 fn signing_is_deterministic() {
1617 let seed = [0x7fu8; 32];
1618 let mut a = [0u8; 64];
1619 let mut b = [0u8; 64];
1620 Ed25519::sign(&seed, b"same input", &mut a).unwrap();
1621 Ed25519::sign(&seed, b"same input", &mut b).unwrap();
1622 assert_eq!(a, b);
1623 }
1624
1625 #[test]
1626 fn rejects_wrong_lengths() {
1627 let mut out = [0u8; 32];
1628 assert!(Ed25519::public_key(&[0u8; 31], &mut out).is_err());
1629 assert!(Ed25519::sign(&[0u8; 32], b"", &mut [0u8; 63]).is_err());
1630 assert!(Ed25519::verify(&[0u8; 32], b"", &[0u8; 63]).is_err());
1631 }
1632
1633 #[test]
1634 fn self_test_passes() {
1635 Ed25519::self_test().unwrap();
1636 }
1637
1638 fn sample_points(n: usize) -> Vec<Point> {
1640 let mut out = Vec::new();
1641 let mut p = basepoint();
1642 for _ in 0..n {
1643 out.push(p);
1644 p = p.double().add(&basepoint());
1645 }
1646 out
1647 }
1648
1649 #[test]
1659 fn the_completed_doubling_agrees_with_the_extended_one() {
1660 for p in sample_points(40) {
1661 let want = p.double();
1662 let got = p.to_projective().double().to_extended();
1663 assert!(got.eq_projective(&want), "doubling disagrees");
1664 let chained = p.to_projective().double().to_projective().double();
1667 let twice = p.double().double();
1668 assert!(chained.to_extended().eq_projective(&twice), "two doublings");
1669 }
1670 }
1671
1672 #[test]
1674 fn niels_addition_agrees_with_the_general_one() {
1675 let pts = sample_points(20);
1676 for p in &pts {
1677 for q in &pts {
1678 let want = p.add(q);
1679 let got = p.add_niels(&q.to_niels()).to_extended();
1680 assert!(got.eq_projective(&want), "add_niels disagrees");
1681
1682 let want_sub = p.add(&q.negate());
1683 let got_sub = p.sub_niels(&q.to_niels()).to_extended();
1684 assert!(got_sub.eq_projective(&want_sub), "sub_niels disagrees");
1685 }
1686 }
1687 }
1688
1689 #[test]
1697 fn affine_niels_addition_agrees_with_the_general_one() {
1698 let pts = sample_points(20);
1699 for p in &pts {
1700 for q in &pts {
1701 let want = p.add(q);
1702 let got = p.add_affine_niels(&q.to_affine_niels()).to_extended();
1703 assert!(got.eq_projective(&want), "add_affine_niels disagrees");
1704
1705 let mut n = q.to_affine_niels();
1707 n.conditional_negate(ic_core::ct::Choice::from_u8(1));
1708 let want_neg = p.add(&q.negate());
1709 let got_neg = p.add_affine_niels(&n).to_extended();
1710 assert!(got_neg.eq_projective(&want_neg), "negated form disagrees");
1711 }
1712 }
1713 }
1714
1715 #[test]
1722 #[ignore = "diagnostic, not a test"]
1723 fn where_verify_spends_its_time() {
1724 use std::time::Instant;
1725
1726 let seed = [7u8; 32];
1727 let key = Ed25519Key::from_seed(&seed).unwrap();
1728 let msg = b"benchmark message";
1729 let mut sig = [0u8; 64];
1730 key.sign(msg, &mut sig).unwrap();
1731 let pk = *key.public_key();
1732
1733 let mut big_r = [0u8; 32];
1734 big_r.copy_from_slice(&sig[..32]);
1735 let mut s_sc = [0u8; 32];
1736 s_sc.copy_from_slice(&sig[32..]);
1737
1738 let n = 2000;
1739 let time = |label: &str, f: &mut dyn FnMut()| {
1740 let mut best = f64::INFINITY;
1741 for _ in 0..5 {
1742 let t = Instant::now();
1743 for _ in 0..n {
1744 f();
1745 }
1746 let e = t.elapsed().as_secs_f64() / n as f64 * 1e6;
1747 if e < best {
1748 best = e;
1749 }
1750 }
1751 println!(" {label:<34} {best:>9.2} us");
1752 best
1753 };
1754
1755 let a_point = Point::decompress(&pk).unwrap();
1756 let k = hash_to_scalar(&[&big_r, &pk, msg]);
1757
1758 println!(
1759 "
1760ed25519 verify, cost breakdown:"
1761 );
1762 let d = time("decompress (x2 per verify)", &mut || {
1763 core::hint::black_box(Point::decompress(&pk));
1764 });
1765 let h = time("hash_to_scalar", &mut || {
1766 core::hint::black_box(hash_to_scalar(&[&big_r, &pk, msg]));
1767 });
1768 let b = time("mul_basepoint (const time)", &mut || {
1769 core::hint::black_box(mul_basepoint(&s_sc));
1770 });
1771 let v = time("double_scalar_mul_vartime", &mut || {
1772 core::hint::black_box(double_scalar_mul_vartime(&a_point.negate(), &k, &s_sc));
1773 });
1774 time(" of which: wnaf(k,5)+wnaf(s,8)", &mut || {
1775 core::hint::black_box(wnaf(&k, 5));
1776 core::hint::black_box(wnaf(&s_sc, 8));
1777 });
1778 time(" of which: odd_a table build", &mut || {
1779 let twice = a_point.double();
1780 let mut odd = [a_point; 8];
1781 for i in 1..8 {
1782 odd[i] = odd[i - 1].add(&twice);
1783 }
1784 let t: [Niels; 8] = core::array::from_fn(|i| odd[i].to_niels());
1785 core::hint::black_box(t);
1786 });
1787 time(" of which: 255 doublings", &mut || {
1788 let mut p = a_point;
1789 for _ in 0..255 {
1790 p = p.double();
1791 }
1792 core::hint::black_box(p);
1793 });
1794 time(" of which: 79 additions", &mut || {
1795 let mut p = a_point;
1796 for _ in 0..79 {
1797 p = p.add(&a_point);
1798 }
1799 core::hint::black_box(p);
1800 });
1801 time("compress (one inversion)", &mut || {
1802 core::hint::black_box(a_point.compress());
1803 });
1804 println!(
1805 " {:<34} {:>9.2} us",
1806 "-- accounted for",
1807 2.0 * d + h + b + v
1808 );
1809
1810 println!(
1812 "
1813field and point primitives, nanoseconds:"
1814 );
1815 let nn = 200_000;
1816 let ns = |label: &str, f: &mut dyn FnMut()| {
1817 let mut best = f64::INFINITY;
1818 for _ in 0..5 {
1819 let t = Instant::now();
1820 for _ in 0..nn {
1821 f();
1822 }
1823 let e = t.elapsed().as_secs_f64() / nn as f64 * 1e9;
1824 if e < best {
1825 best = e;
1826 }
1827 }
1828 println!(" {label:<34} {best:>9.2} ns");
1829 };
1830 let fx = a_point.x;
1831 let fy = a_point.y;
1832 ns("Fe::mul", &mut || {
1833 core::hint::black_box(core::hint::black_box(&fx).mul(core::hint::black_box(&fy)));
1834 });
1835 ns("Fe::square", &mut || {
1836 core::hint::black_box(core::hint::black_box(&fx).square());
1837 });
1838 ns("Fe::add", &mut || {
1839 core::hint::black_box(core::hint::black_box(&fx).add(core::hint::black_box(&fy)));
1840 });
1841 ns("Fe::sub", &mut || {
1842 core::hint::black_box(core::hint::black_box(&fx).sub(core::hint::black_box(&fy)));
1843 });
1844 ns("Fe::neg", &mut || {
1845 core::hint::black_box(core::hint::black_box(&fx).neg());
1846 });
1847 let proj = a_point.to_projective();
1848 let comp = proj.double();
1849 let an = a_point.to_affine_niels();
1850 ns("Projective::double (4S)", &mut || {
1851 core::hint::black_box(core::hint::black_box(&proj).double());
1852 });
1853 ns("Projective::double_projective", &mut || {
1854 core::hint::black_box(core::hint::black_box(proj).double_projective());
1855 });
1856 ns("Completed::to_projective (3M)", &mut || {
1857 core::hint::black_box(core::hint::black_box(&comp).to_projective());
1858 });
1859 ns("Completed::to_extended (4M)", &mut || {
1860 core::hint::black_box(core::hint::black_box(&comp).to_extended());
1861 });
1862 ns("Point::add_affine_niels (3M)", &mut || {
1863 core::hint::black_box(
1864 core::hint::black_box(&a_point).add_affine_niels(core::hint::black_box(&an)),
1865 );
1866 });
1867 ns("Point::double", &mut || {
1868 core::hint::black_box(core::hint::black_box(&a_point).double());
1869 });
1870 ns("Point::add", &mut || {
1871 core::hint::black_box(
1872 core::hint::black_box(&a_point).add(core::hint::black_box(&a_point)),
1873 );
1874 });
1875 }
1876
1877 #[test]
1879 #[ignore = "diagnostic, not a test"]
1880 fn count_the_point_operations() {
1881 let mut doublings = 0usize;
1882 let mut adds_a = 0usize;
1883 let mut adds_b = 0usize;
1884 let mut state = 0x1234_5678_9abc_def0u64;
1885 let trials = 200;
1886 for _ in 0..trials {
1887 let mut kb = [0u8; 32];
1888 for c in kb.chunks_exact_mut(8) {
1889 state ^= state >> 12;
1890 state ^= state << 25;
1891 state ^= state >> 27;
1892 c.copy_from_slice(&state.wrapping_mul(0x2545_f491_4f6c_dd1d).to_le_bytes());
1893 }
1894 kb[31] &= 0x0f;
1895 let na = wnaf(&kb, 5);
1896 let nb = wnaf(&kb, 8);
1897 let mut i = 257;
1898 while i > 0 && na[i] == 0 && nb[i] == 0 {
1899 i -= 1;
1900 }
1901 doublings += i + 1;
1902 adds_a += na.iter().filter(|d| **d != 0).count();
1903 adds_b += nb.iter().filter(|d| **d != 0).count();
1904 }
1905 let d = doublings as f64 / trials as f64;
1906 let aa = adds_a as f64 / trials as f64;
1907 let ab = adds_b as f64 / trials as f64;
1908 println!(
1909 "
1910 per double-scalar multiplication, averaged over {trials} scalars:"
1911 );
1912 println!(" doublings {d:>8.1}");
1913 println!(" additions, w=5 table (A) {aa:>8.1}");
1914 println!(" additions, w=8 table (B) {ab:>8.1}");
1915 println!(" additions, building A {:>8.1}", 8.0);
1916 println!(" ---");
1917 println!(" total additions {:>8.1}", aa + ab + 8.0);
1918 println!(
1919 " field muls, at 4M+4S per doubling and 9M per addition: {:>6.0}",
1920 d * 8.0 + (aa + ab + 8.0) * 9.0
1921 );
1922 println!(
1923 " the same at dalek's 3M+4S and 7M: {:>6.0}",
1924 d * 7.0 + (aa + ab + 8.0) * 7.0
1925 );
1926 }
1927}