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
565#[cfg(feature = "std")]
567pub(crate) fn prepare_tables() {
568 basepoint_table::prepare();
569}
570
571fn mul_basepoint(scalar: &[u8; 32]) -> Point {
577 #[cfg(feature = "std")]
578 {
579 basepoint_table::mul(scalar)
580 }
581 #[cfg(not(feature = "std"))]
582 {
583 mul_scalar_windowed(&basepoint(), scalar)
584 }
585}
586
587#[cfg(feature = "bench-internals")]
611#[doc(hidden)]
612pub fn double_scalar_mul_vartime_for_bench(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
613 double_scalar_mul_vartime(a, k, s)
614}
615
616#[cfg(any(not(feature = "std"), test))]
632fn mul_scalar_windowed(p: &Point, scalar: &[u8; 32]) -> Point {
633 let mut multiples = [*p; 8];
634 for i in 1..8 {
635 multiples[i] = multiples[i - 1].add(p);
636 }
637 let table: [Niels; 8] = core::array::from_fn(|i| multiples[i].to_niels());
638
639 let select = |digit: i8| -> Niels {
641 let negative = Choice::from_u8((digit as u8) >> 7);
642 let magnitude = ((digit as i16 ^ (digit as i16 >> 7)) - (digit as i16 >> 7)) as u8;
643 let mut out = Niels::IDENTITY;
644 for (i, entry) in table.iter().enumerate() {
645 out.cmov(entry, Choice::from_u8(u8::from(magnitude == (i as u8 + 1))));
646 }
647 out.conditional_negate(negative);
648 out
649 };
650
651 let digits = signed_digits(scalar);
652 let mut acc = Point::IDENTITY.add_niels(&select(digits[63])).to_extended();
653 for i in (0..63).rev() {
654 let mut q = acc.to_projective();
657 for _ in 0..3 {
658 q = q.double_projective();
659 }
660 acc = q.double().to_extended();
661 acc = acc.add_niels(&select(digits[i])).to_extended();
662 }
663 acc
664}
665
666fn odd_multiples(p: &Point) -> [Niels; 8] {
668 let twice = p.double();
669 let mut odd = [*p; 8];
670 for i in 1..8 {
671 odd[i] = odd[i - 1].add(&twice);
672 }
673 core::array::from_fn(|i| odd[i].to_niels())
674}
675
676fn signed_digits(scalar: &[u8; 32]) -> [i8; 64] {
692 debug_assert!(
693 scalar[31] <= 127,
694 "the top digit can only absorb the final carry for scalars below 2^255"
695 );
696
697 let mut nibbles = [0i8; 64];
698 for (i, byte) in scalar.iter().enumerate() {
699 nibbles[i * 2] = (byte & 0x0f) as i8;
700 nibbles[i * 2 + 1] = (byte >> 4) as i8;
701 }
702
703 for i in 0..63 {
704 let carry = (nibbles[i] + 8) >> 4;
705 nibbles[i] -= carry << 4;
706 nibbles[i + 1] += carry;
707 }
708 nibbles
709}
710
711#[cfg(feature = "std")]
712fn double_scalar_mul_vartime(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
713 basepoint_table::prepare();
714 shared_doublings(a, k, &wnaf(s, 8), |e, digit| {
715 let n = basepoint_table::odd_multiple((digit.unsigned_abs() as usize) / 2);
716 if digit > 0 {
717 e.add_affine_niels(n)
718 } else {
719 e.sub_affine_niels(n)
720 }
721 })
722}
723
724#[cfg(any(not(feature = "std"), test))]
732fn double_scalar_mul_vartime_no_table(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
733 let odd_b = odd_multiples(&basepoint());
734 shared_doublings(a, k, &wnaf(s, 5), |e, digit| {
735 let n = &odd_b[(digit.unsigned_abs() as usize) / 2];
736 if digit > 0 {
737 e.add_niels(n)
738 } else {
739 e.sub_niels(n)
740 }
741 })
742}
743
744#[inline(always)]
747fn shared_doublings(
748 a: &Point,
749 k: &[u8; 32],
750 naf_b: &[i8; 258],
751 add_b: impl Fn(&Point, i8) -> Completed,
752) -> Point {
753 let odd_a = odd_multiples(a);
755 let naf_a = wnaf(k, 5);
756
757 let mut i = 257;
760 while i > 0 && naf_a[i] == 0 && naf_b[i] == 0 {
761 i -= 1;
762 }
763
764 let mut acc = Point::IDENTITY.to_projective();
769 loop {
770 if naf_a[i] == 0 && naf_b[i] == 0 {
773 acc = acc.double_projective();
774 if i == 0 {
775 return acc.to_extended_from_projective();
776 }
777 i -= 1;
778 continue;
779 }
780 let mut t = acc.double();
781 if naf_a[i] != 0 {
782 let e = t.to_extended();
783 let n = &odd_a[(naf_a[i].unsigned_abs() as usize) / 2];
784 t = if naf_a[i] > 0 {
785 e.add_niels(n)
786 } else {
787 e.sub_niels(n)
788 };
789 }
790 if naf_b[i] != 0 {
791 t = add_b(&t.to_extended(), naf_b[i]);
792 }
793 if i == 0 {
794 return t.to_extended();
795 }
796 acc = t.to_projective();
797 i -= 1;
798 }
799}
800
801#[cfg(feature = "bench-internals")]
804#[doc(hidden)]
805pub fn mul_basepoint_for_bench(scalar: &[u8; 32]) -> Point {
806 mul_basepoint(scalar)
807}
808
809fn basepoint() -> Point {
810 BASEPOINT
811}
812
813const BASEPOINT: Point = Point {
822 x: Fe::from_limbs51([
823 1_738_742_601_995_546,
824 1_146_398_526_822_698,
825 2_070_867_633_025_821,
826 562_264_141_797_630,
827 587_772_402_128_613,
828 ]),
829 y: Fe::from_limbs51([
830 1_801_439_850_948_184,
831 1_351_079_888_211_148,
832 450_359_962_737_049,
833 900_719_925_474_099,
834 1_801_439_850_948_198,
835 ]),
836 z: Fe::ONE,
837 t: Fe::from_limbs51([
838 1_841_354_044_333_475,
839 16_398_895_984_059,
840 755_974_180_946_558,
841 900_171_276_175_154,
842 1_821_297_809_914_039,
843 ]),
844};
845
846pub struct Ed25519;
848
849impl Algorithm for Ed25519 {
850 const ID: &'static str = "ed25519";
851 const NAME: &'static str = "Ed25519";
852}
853
854fn expand_seed(seed: &[u8]) -> ([u8; 32], [u8; 32]) {
856 let h = Sha512::digest(seed);
857 let mut a = [0u8; 32];
858 let mut prefix = [0u8; 32];
859 a.copy_from_slice(&h.as_ref()[..32]);
860 prefix.copy_from_slice(&h.as_ref()[32..]);
861 a[0] &= 248;
862 a[31] &= 127;
863 a[31] |= 64;
864 (a, prefix)
865}
866
867fn wnaf(scalar: &[u8; 32], w: u32) -> [i8; 258] {
877 debug_assert!((2..=8).contains(&w), "window width out of range");
878 let half = 1i64 << (w - 1);
879 let full = 1i64 << w;
880 let mask = (full - 1) as u64;
881
882 let mut naf = [0i8; 258];
883 let mut k = [0u64; 5];
890 for (i, limb) in k.iter_mut().take(4).enumerate() {
891 let mut b = [0u8; 8];
892 b.copy_from_slice(&scalar[i * 8..i * 8 + 8]);
893 *limb = u64::from_le_bytes(b);
894 }
895
896 let mut i = 0;
897 while k.iter().any(|&x| x != 0) {
898 if k[0] & 1 == 1 {
899 let mut d = (k[0] & mask) as i64;
900 if d >= half {
901 d -= full;
902 }
903 naf[i] = d as i8;
904 if d > 0 {
905 sub_u64(&mut k, d as u64);
906 } else {
907 add_u64(&mut k, d.unsigned_abs());
908 }
909 }
910 shr1(&mut k);
911 i += 1;
912 }
913 naf
914}
915
916fn sub_u64(k: &mut [u64; 5], v: u64) {
918 let (d, mut borrow) = k[0].overflowing_sub(v);
919 k[0] = d;
920 for limb in k.iter_mut().skip(1) {
921 if !borrow {
922 break;
923 }
924 let (d, b) = limb.overflowing_sub(1);
925 *limb = d;
926 borrow = b;
927 }
928}
929
930fn add_u64(k: &mut [u64; 5], v: u64) {
932 let (d, mut carry) = k[0].overflowing_add(v);
933 k[0] = d;
934 for limb in k.iter_mut().skip(1) {
935 if !carry {
936 break;
937 }
938 let (d, c) = limb.overflowing_add(1);
939 *limb = d;
940 carry = c;
941 }
942}
943
944fn shr1(k: &mut [u64; 5]) {
946 for i in 0..4 {
947 k[i] = (k[i] >> 1) | (k[i + 1] << 63);
948 }
949 k[4] >>= 1;
950}
951
952fn hash_to_scalar(parts: &[&[u8]]) -> [u8; 32] {
954 let mut h = Sha512::new();
955 for p in parts {
956 h.update(p);
957 }
958 let digest = h.finalize();
959 let mut wide = [0u8; 64];
960 wide.copy_from_slice(digest.as_ref());
961 scalar::reduce_wide(&wide)
962}
963
964pub struct Ed25519Key {
981 scalar: [u8; 32],
983 prefix: [u8; 32],
985 public: [u8; 32],
987}
988
989impl Drop for Ed25519Key {
990 fn drop(&mut self) {
991 self.scalar.zeroize();
992 self.prefix.zeroize();
993 }
995}
996
997impl Ed25519Key {
998 pub fn from_seed(seed: &[u8]) -> Result<Self> {
1000 ensure!(seed.len() == 32, InvalidLength, "ed25519 seed");
1001 let (scalar, prefix) = expand_seed(seed);
1002 let public = mul_basepoint(&scalar).compress();
1003 Ok(Self {
1004 scalar,
1005 prefix,
1006 public,
1007 })
1008 }
1009
1010 pub fn public_key(&self) -> &[u8; 32] {
1012 &self.public
1013 }
1014
1015 pub fn sign(&self, message: &[u8], signature: &mut [u8]) -> Result<()> {
1017 ensure!(
1018 signature.len() == 64,
1019 InvalidLength,
1020 "ed25519 signature buffer"
1021 );
1022
1023 let mut r = hash_to_scalar(&[&self.prefix, message]);
1026 let big_r = mul_basepoint(&r).compress();
1027
1028 let k = hash_to_scalar(&[&big_r, &self.public, message]);
1029 let s = scalar::mul_add(&k, &self.scalar, &r);
1030
1031 signature[..32].copy_from_slice(&big_r);
1032 signature[32..].copy_from_slice(&s);
1033 r.zeroize();
1034 Ok(())
1035 }
1036}
1037
1038impl SignatureScheme for Ed25519 {
1039 const PRIVATE_KEY_LEN: usize = 32;
1040 const PUBLIC_KEY_LEN: usize = 32;
1041 const SIGNATURE_LEN: usize = 64;
1042
1043 fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()> {
1044 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1045 ensure!(out.len() == 32, InvalidLength, "ed25519 public key buffer");
1046 let (mut a, mut prefix) = expand_seed(private_key);
1047 out.copy_from_slice(&mul_basepoint(&a).compress());
1048 a.zeroize();
1049 prefix.zeroize();
1050 Ok(())
1051 }
1052
1053 fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()> {
1054 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1055 ensure!(
1056 signature.len() == 64,
1057 InvalidLength,
1058 "ed25519 signature buffer"
1059 );
1060
1061 Ed25519Key::from_seed(private_key)?.sign(message, signature)
1065 }
1066
1067 fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()> {
1068 Ed25519VerifyKey::from_bytes(public_key)?.verify(message, signature)
1072 }
1073}
1074
1075pub struct Ed25519VerifyKey {
1089 compressed: [u8; 32],
1091 neg_a: Point,
1093}
1094
1095impl Ed25519VerifyKey {
1096 pub fn from_bytes(public_key: &[u8]) -> Result<Self> {
1098 ensure!(public_key.len() == 32, InvalidLength, "ed25519 public key");
1099 let mut compressed = [0u8; 32];
1100 compressed.copy_from_slice(public_key);
1101 let a = Point::decompress(&compressed).ok_or(ic_core::err!(
1102 MalformedEncoding,
1103 "ed25519 public key is not on the curve"
1104 ))?;
1105 Ok(Self {
1106 compressed,
1107 neg_a: a.negate(),
1108 })
1109 }
1110
1111 pub fn as_bytes(&self) -> &[u8; 32] {
1113 &self.compressed
1114 }
1115
1116 pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> {
1118 ensure!(signature.len() == 64, InvalidLength, "ed25519 signature");
1119
1120 let mut big_r = [0u8; 32];
1121 big_r.copy_from_slice(&signature[..32]);
1122 let mut s = [0u8; 32];
1123 s.copy_from_slice(&signature[32..]);
1124
1125 ensure!(
1129 scalar::is_canonical(&s),
1130 MalformedEncoding,
1131 "ed25519 signature S is not reduced"
1132 );
1133
1134 let r_point = Point::decompress(&big_r).ok_or(ic_core::err!(
1135 MalformedEncoding,
1136 "ed25519 signature R is not on the curve"
1137 ))?;
1138
1139 let k = hash_to_scalar(&[&big_r, &self.compressed, message]);
1140
1141 #[cfg(feature = "std")]
1145 let lhs = double_scalar_mul_vartime(&self.neg_a, &k, &s);
1146 #[cfg(not(feature = "std"))]
1147 let lhs = double_scalar_mul_vartime_no_table(&self.neg_a, &k, &s);
1148
1149 if lhs.eq_projective(&r_point) {
1150 Ok(())
1151 } else {
1152 Err(ic_core::err!(AuthenticationFailed, "ed25519"))
1153 }
1154 }
1155}
1156
1157impl SelfTest for Ed25519 {
1158 fn self_test() -> Result<()> {
1159 let mut seed = [0u8; 32];
1161 ic_core::codec::hex_decode(
1162 b"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1163 &mut seed,
1164 )?;
1165 let mut want_pk = [0u8; 32];
1166 ic_core::codec::hex_decode(
1167 b"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1168 &mut want_pk,
1169 )?;
1170 let mut want_sig = [0u8; 64];
1171 ic_core::codec::hex_decode(
1172 b"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1173 &mut want_sig,
1174 )?;
1175
1176 let mut pk = [0u8; 32];
1177 <Self as SignatureScheme>::public_key(&seed, &mut pk)?;
1178 ensure!(
1179 ic_core::ct::verify(&want_pk, &pk),
1180 SelfTestFailed,
1181 "ed25519"
1182 );
1183
1184 let mut sig = [0u8; 64];
1185 <Self as SignatureScheme>::sign(&seed, b"", &mut sig)?;
1186 ensure!(
1187 ic_core::ct::verify(&want_sig, &sig),
1188 SelfTestFailed,
1189 "ed25519"
1190 );
1191
1192 <Self as SignatureScheme>::verify(&pk, b"", &sig)?;
1193
1194 sig[0] ^= 1;
1196 ensure!(
1197 <Self as SignatureScheme>::verify(&pk, b"", &sig).is_err(),
1198 SelfTestFailed,
1199 "ed25519"
1200 );
1201 Ok(())
1202 }
1203}
1204
1205#[cfg(test)]
1206mod tests {
1207 use super::*;
1208 use ic_core::codec::{hex, unhex};
1209
1210 #[test]
1211 fn curve_constants_are_correct() {
1212 let d = Fe::from_u64(121_665)
1214 .neg()
1215 .mul(&Fe::from_u64(121_666).invert());
1216 assert_eq!(hex(&D.to_bytes()), hex(&d.to_bytes()), "d");
1217 assert_eq!(hex(&D2.to_bytes()), hex(&d.add(&d).to_bytes()), "2d");
1218 assert_eq!(
1220 hex(&SQRT_M1.square().to_bytes()),
1221 hex(&Fe::ONE.neg().to_bytes()),
1222 "sqrt(-1)"
1223 );
1224 }
1225
1226 #[test]
1227 fn basepoint_has_the_expected_coordinates() {
1228 let b = basepoint();
1229 let expected_y = Fe::from_u64(4).mul(&Fe::from_u64(5).invert());
1231 let z_inv = b.z.invert();
1232 assert_eq!(
1233 hex(&b.y.mul(&z_inv).to_bytes()),
1234 hex(&expected_y.to_bytes())
1235 );
1236 assert_eq!(hex(&b.compress()), hex(&BASEPOINT_COMPRESSED));
1237 }
1238
1239 #[test]
1246 fn doubling_agrees_with_adding_a_point_to_itself() {
1247 let mut p = basepoint();
1248 let mut checked = 0;
1249 for _ in 0..16 {
1250 assert_eq!(
1251 p.double().compress(),
1252 p.add(&p).compress(),
1253 "dedicated doubling and self-addition differ"
1254 );
1255 p = p.add(&basepoint());
1256 checked += 1;
1257 }
1258 assert_eq!(checked, 16, "the comparison did not run");
1259
1260 assert_eq!(
1263 Point::IDENTITY.double().compress(),
1264 Point::IDENTITY.compress()
1265 );
1266 }
1267
1268 #[test]
1276 fn projective_equality_agrees_with_compressed_equality() {
1277 let b = basepoint();
1278 let mut points = std::vec![Point::IDENTITY, b];
1279 let mut p = b;
1280 for _ in 0..6 {
1281 p = p.double();
1282 points.push(p);
1283 }
1284
1285 let mut checked = 0;
1286 for (i, a) in points.iter().enumerate() {
1287 for (j, c) in points.iter().enumerate() {
1288 let projective = a.eq_projective(c);
1289 let compressed = a.compress() == c.compress();
1290 assert_eq!(
1291 projective, compressed,
1292 "projective and compressed equality differ for {i} vs {j}"
1293 );
1294 checked += 1;
1295 }
1296 }
1297 assert_eq!(checked, 64, "the comparison did not run");
1298
1299 let scaled = b.add(&Point::IDENTITY);
1302 assert!(b.eq_projective(&scaled), "equal points with different Z");
1303 assert_eq!(b.compress(), scaled.compress());
1304 }
1305
1306 #[test]
1307 fn group_law_is_consistent() {
1308 let b = basepoint();
1309 assert_eq!(hex(&b.add(&Point::IDENTITY).compress()), hex(&b.compress()));
1311 let mut two = [0u8; 32];
1313 two[0] = 2;
1314 assert_eq!(
1315 hex(&b.double().compress()),
1316 hex(&b.mul_scalar(&two).compress())
1317 );
1318 let mut three = [0u8; 32];
1320 three[0] = 3;
1321 assert_eq!(
1322 hex(&b.double().add(&b).compress()),
1323 hex(&b.mul_scalar(&three).compress())
1324 );
1325 }
1326
1327 #[test]
1328 fn order_of_the_basepoint_is_l() {
1329 assert_eq!(
1331 hex(&basepoint().mul_scalar(&scalar::L).compress()),
1332 hex(&Point::IDENTITY.compress())
1333 );
1334 }
1335
1336 #[test]
1337 fn compression_roundtrips() {
1338 let b = basepoint();
1339 for k in [1u8, 2, 3, 47, 200] {
1340 let mut s = [0u8; 32];
1341 s[0] = k;
1342 let p = b.mul_scalar(&s);
1343 let c = p.compress();
1344 let d = Point::decompress(&c).expect("valid point");
1345 assert_eq!(hex(&d.compress()), hex(&c), "k = {k}");
1346 }
1347 }
1348
1349 #[test]
1350 fn decompression_rejects_non_curve_points() {
1351 let mut bad = [0u8; 32];
1353 bad[0] = 2;
1354 assert!(Point::decompress(&bad).is_none());
1355 }
1356
1357 #[test]
1359 fn rfc8032_vectors() {
1360 let cases: [(&str, &str, &str, &str); 3] = [
1361 (
1362 "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1363 "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1364 "",
1365 "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1366 ),
1367 (
1368 "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
1369 "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
1370 "72",
1371 "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
1372 ),
1373 (
1374 "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
1375 "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
1376 "af82",
1377 "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
1378 ),
1379 ];
1380
1381 for (seed_hex, pk_hex, msg_hex, sig_hex) in cases {
1382 let seed = unhex(seed_hex).unwrap();
1383 let msg = unhex(msg_hex).unwrap();
1384
1385 let mut pk = [0u8; 32];
1386 Ed25519::public_key(&seed, &mut pk).unwrap();
1387 assert_eq!(hex(&pk), pk_hex, "public key for {seed_hex}");
1388
1389 let mut sig = [0u8; 64];
1390 Ed25519::sign(&seed, &msg, &mut sig).unwrap();
1391 assert_eq!(hex(&sig), sig_hex, "signature for {seed_hex}");
1392
1393 Ed25519::verify(&pk, &msg, &sig).unwrap();
1394 }
1395 }
1396
1397 #[test]
1398 fn verification_rejects_tampering() {
1399 let seed = [0x42u8; 32];
1400 let mut pk = [0u8; 32];
1401 Ed25519::public_key(&seed, &mut pk).unwrap();
1402 let mut sig = [0u8; 64];
1403 Ed25519::sign(&seed, b"authentic", &mut sig).unwrap();
1404 Ed25519::verify(&pk, b"authentic", &sig).unwrap();
1405
1406 assert!(Ed25519::verify(&pk, b"forged", &sig).is_err());
1408 let mut bad = sig;
1410 bad[0] ^= 1;
1411 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1412 let mut bad = sig;
1414 bad[40] ^= 1;
1415 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1416 let mut other_pk = [0u8; 32];
1418 Ed25519::public_key(&[0x43u8; 32], &mut other_pk).unwrap();
1419 assert!(Ed25519::verify(&other_pk, b"authentic", &sig).is_err());
1420 }
1421
1422 #[test]
1425 fn rejects_non_canonical_s() {
1426 let seed = [0x42u8; 32];
1427 let mut pk = [0u8; 32];
1428 Ed25519::public_key(&seed, &mut pk).unwrap();
1429 let mut sig = [0u8; 64];
1430 Ed25519::sign(&seed, b"msg", &mut sig).unwrap();
1431
1432 let mut carry = 0u16;
1435 for i in 0..32 {
1436 let t = sig[32 + i] as u16 + scalar::L[i] as u16 + carry;
1437 sig[32 + i] = t as u8;
1438 carry = t >> 8;
1439 }
1440 assert!(Ed25519::verify(&pk, b"msg", &sig).is_err());
1441 }
1442
1443 #[test]
1450 fn the_cached_key_signs_identically_to_the_seed() {
1451 let mut checked = 0;
1452 for seed in [[0x11u8; 32], [0x9du8; 32], [0xffu8; 32]] {
1453 for message in [&b""[..], &b"x"[..], &b"a longer message to sign"[..]] {
1454 let mut from_seed = [0u8; 64];
1455 Ed25519::sign(&seed, message, &mut from_seed).unwrap();
1456
1457 let key = Ed25519Key::from_seed(&seed).unwrap();
1458 let mut from_key = [0u8; 64];
1459 key.sign(message, &mut from_key).unwrap();
1460
1461 assert_eq!(from_seed, from_key, "the two signing paths diverged");
1462
1463 let mut derived = [0u8; 32];
1465 Ed25519::public_key(&seed, &mut derived).unwrap();
1466 assert_eq!(&derived, key.public_key());
1467
1468 Ed25519::verify(&derived, message, &from_key).unwrap();
1470 checked += 1;
1471 }
1472 }
1473 assert_eq!(checked, 9, "the comparison did not run");
1474 }
1475
1476 fn windowed_scalars() -> std::vec::Vec<[u8; 32]> {
1481 let mut one = [0u8; 32];
1482 one[0] = 1;
1483 let mut eight = [0u8; 32];
1484 eight[0] = 8;
1485 let mut top = [0xffu8; 32];
1486 top[31] = 0x7f;
1487 let mut clamped = [0x9du8; 32];
1488 clamped[0] &= 248;
1489 clamped[31] &= 127;
1490 clamped[31] |= 64;
1491 let mut l_minus_1 = scalar::L;
1492 l_minus_1[0] -= 1;
1493 let mut out = std::vec![[0u8; 32], one, eight, top, clamped, l_minus_1];
1494 for fill in [0x88u8, 0x77, 0x99, 0x55, 0xaa] {
1495 let mut s = [fill; 32];
1496 s[31] &= 0x7f;
1497 out.push(s);
1498 }
1499 out
1500 }
1501
1502 #[test]
1512 fn the_basepoint_constant_is_the_decompressed_encoding() {
1513 let decoded = Point::decompress(&BASEPOINT_COMPRESSED).expect("the RFC 8032 basepoint");
1514 let zinv = decoded.z.invert();
1515 for (name, constant, from_encoding) in [
1516 ("x", BASEPOINT.x, decoded.x.mul(&zinv)),
1517 ("y", BASEPOINT.y, decoded.y.mul(&zinv)),
1518 ("t", BASEPOINT.t, decoded.t.mul(&zinv)),
1519 ] {
1520 assert_eq!(
1521 constant.to_bytes(),
1522 from_encoding.to_bytes(),
1523 "{name} differs"
1524 );
1525 }
1526 assert_eq!(BASEPOINT.z.to_bytes(), Fe::ONE.to_bytes());
1527 assert_eq!(BASEPOINT.compress(), BASEPOINT_COMPRESSED);
1528 }
1529
1530 #[test]
1531 fn the_windowed_multiplication_agrees_with_the_ladder() {
1532 let b = basepoint();
1533 let mut seven = [0u8; 32];
1534 seven[0] = 7;
1535 let p = b.mul_scalar(&seven);
1536
1537 let mut checked = 0;
1538 for point in [b, p] {
1539 for scalar in windowed_scalars() {
1540 assert_eq!(
1541 mul_scalar_windowed(&point, &scalar).compress(),
1542 point.mul_scalar(&scalar).compress(),
1543 "windowed and ladder differ for {scalar:02x?}"
1544 );
1545 checked += 1;
1546 }
1547 }
1548 assert!(checked >= 20, "only {checked} comparisons ran");
1549 }
1550
1551 #[test]
1554 fn the_untabled_double_multiplication_agrees() {
1555 let b = basepoint();
1556 let mut checked = 0;
1557 for (i, k) in windowed_scalars().into_iter().enumerate() {
1558 let mut seed = [0u8; 32];
1559 seed[0] = 3 + i as u8;
1560 let a = b.mul_scalar(&seed);
1561 for s in [k, [0xffu8; 32], [0x9du8; 32]] {
1562 let untabled = double_scalar_mul_vartime_no_table(&a, &k, &s);
1563 let tabled = double_scalar_mul_vartime(&a, &k, &s);
1564 let ladders = a.mul_scalar(&k).add(&b.mul_scalar(&s));
1565 assert_eq!(
1566 untabled.compress(),
1567 tabled.compress(),
1568 "k={k:02x?} s={s:02x?}"
1569 );
1570 assert_eq!(
1571 untabled.compress(),
1572 ladders.compress(),
1573 "k={k:02x?} s={s:02x?}"
1574 );
1575 checked += 1;
1576 }
1577 }
1578 assert!(checked >= 30, "only {checked} comparisons ran");
1579 }
1580
1581 #[test]
1583 fn the_wnaf_digits_are_odd_sparse_and_faithful() {
1584 for scalar in [[1u8; 32], [0x9du8; 32], [0xffu8; 32], [0x55u8; 32]] {
1585 let naf = wnaf(&scalar, 5);
1586
1587 let mut previous_nonzero: Option<usize> = None;
1588 for (i, d) in naf.iter().enumerate() {
1589 if *d == 0 {
1590 continue;
1591 }
1592 assert!(d % 2 != 0, "digit {d} at {i} is not odd");
1593 assert!((-15..=15).contains(d), "digit {d} at {i} is out of range");
1594 if let Some(j) = previous_nonzero {
1595 assert!(i - j >= 5, "digits at {j} and {i} are adjacent");
1596 }
1597 previous_nonzero = Some(i);
1598 }
1599
1600 const M: u128 = 1_000_000_007;
1603 let mut from_digits = 0u128;
1604 let mut power = 1u128;
1605 for d in naf {
1606 let term = ((d as i128).rem_euclid(M as i128)) as u128;
1607 from_digits = (from_digits + term * power) % M;
1608 power = power * 2 % M;
1609 }
1610 let mut from_bytes = 0u128;
1611 let mut p = 1u128;
1612 for byte in scalar {
1613 from_bytes = (from_bytes + (byte as u128) * p) % M;
1614 p = p * 256 % M;
1615 }
1616 assert_eq!(from_digits, from_bytes, "recoding changed the value");
1617 }
1618 }
1619
1620 #[test]
1621 fn signing_is_deterministic() {
1622 let seed = [0x7fu8; 32];
1623 let mut a = [0u8; 64];
1624 let mut b = [0u8; 64];
1625 Ed25519::sign(&seed, b"same input", &mut a).unwrap();
1626 Ed25519::sign(&seed, b"same input", &mut b).unwrap();
1627 assert_eq!(a, b);
1628 }
1629
1630 #[test]
1631 fn rejects_wrong_lengths() {
1632 let mut out = [0u8; 32];
1633 assert!(Ed25519::public_key(&[0u8; 31], &mut out).is_err());
1634 assert!(Ed25519::sign(&[0u8; 32], b"", &mut [0u8; 63]).is_err());
1635 assert!(Ed25519::verify(&[0u8; 32], b"", &[0u8; 63]).is_err());
1636 }
1637
1638 #[test]
1639 fn self_test_passes() {
1640 Ed25519::self_test().unwrap();
1641 }
1642
1643 fn sample_points(n: usize) -> Vec<Point> {
1645 let mut out = Vec::new();
1646 let mut p = basepoint();
1647 for _ in 0..n {
1648 out.push(p);
1649 p = p.double().add(&basepoint());
1650 }
1651 out
1652 }
1653
1654 #[test]
1664 fn the_completed_doubling_agrees_with_the_extended_one() {
1665 for p in sample_points(40) {
1666 let want = p.double();
1667 let got = p.to_projective().double().to_extended();
1668 assert!(got.eq_projective(&want), "doubling disagrees");
1669 let chained = p.to_projective().double().to_projective().double();
1672 let twice = p.double().double();
1673 assert!(chained.to_extended().eq_projective(&twice), "two doublings");
1674 }
1675 }
1676
1677 #[test]
1679 fn niels_addition_agrees_with_the_general_one() {
1680 let pts = sample_points(20);
1681 for p in &pts {
1682 for q in &pts {
1683 let want = p.add(q);
1684 let got = p.add_niels(&q.to_niels()).to_extended();
1685 assert!(got.eq_projective(&want), "add_niels disagrees");
1686
1687 let want_sub = p.add(&q.negate());
1688 let got_sub = p.sub_niels(&q.to_niels()).to_extended();
1689 assert!(got_sub.eq_projective(&want_sub), "sub_niels disagrees");
1690 }
1691 }
1692 }
1693
1694 #[test]
1702 fn affine_niels_addition_agrees_with_the_general_one() {
1703 let pts = sample_points(20);
1704 for p in &pts {
1705 for q in &pts {
1706 let want = p.add(q);
1707 let got = p.add_affine_niels(&q.to_affine_niels()).to_extended();
1708 assert!(got.eq_projective(&want), "add_affine_niels disagrees");
1709
1710 let mut n = q.to_affine_niels();
1712 n.conditional_negate(ic_core::ct::Choice::from_u8(1));
1713 let want_neg = p.add(&q.negate());
1714 let got_neg = p.add_affine_niels(&n).to_extended();
1715 assert!(got_neg.eq_projective(&want_neg), "negated form disagrees");
1716 }
1717 }
1718 }
1719
1720 #[test]
1727 #[ignore = "diagnostic, not a test"]
1728 fn where_verify_spends_its_time() {
1729 use std::time::Instant;
1730
1731 let seed = [7u8; 32];
1732 let key = Ed25519Key::from_seed(&seed).unwrap();
1733 let msg = b"benchmark message";
1734 let mut sig = [0u8; 64];
1735 key.sign(msg, &mut sig).unwrap();
1736 let pk = *key.public_key();
1737
1738 let mut big_r = [0u8; 32];
1739 big_r.copy_from_slice(&sig[..32]);
1740 let mut s_sc = [0u8; 32];
1741 s_sc.copy_from_slice(&sig[32..]);
1742
1743 let n = 2000;
1744 let time = |label: &str, f: &mut dyn FnMut()| {
1745 let mut best = f64::INFINITY;
1746 for _ in 0..5 {
1747 let t = Instant::now();
1748 for _ in 0..n {
1749 f();
1750 }
1751 let e = t.elapsed().as_secs_f64() / n as f64 * 1e6;
1752 if e < best {
1753 best = e;
1754 }
1755 }
1756 println!(" {label:<34} {best:>9.2} us");
1757 best
1758 };
1759
1760 let a_point = Point::decompress(&pk).unwrap();
1761 let k = hash_to_scalar(&[&big_r, &pk, msg]);
1762
1763 println!(
1764 "
1765ed25519 verify, cost breakdown:"
1766 );
1767 let d = time("decompress (x2 per verify)", &mut || {
1768 core::hint::black_box(Point::decompress(&pk));
1769 });
1770 let h = time("hash_to_scalar", &mut || {
1771 core::hint::black_box(hash_to_scalar(&[&big_r, &pk, msg]));
1772 });
1773 let b = time("mul_basepoint (const time)", &mut || {
1774 core::hint::black_box(mul_basepoint(&s_sc));
1775 });
1776 let v = time("double_scalar_mul_vartime", &mut || {
1777 core::hint::black_box(double_scalar_mul_vartime(&a_point.negate(), &k, &s_sc));
1778 });
1779 time(" of which: wnaf(k,5)+wnaf(s,8)", &mut || {
1780 core::hint::black_box(wnaf(&k, 5));
1781 core::hint::black_box(wnaf(&s_sc, 8));
1782 });
1783 time(" of which: odd_a table build", &mut || {
1784 let twice = a_point.double();
1785 let mut odd = [a_point; 8];
1786 for i in 1..8 {
1787 odd[i] = odd[i - 1].add(&twice);
1788 }
1789 let t: [Niels; 8] = core::array::from_fn(|i| odd[i].to_niels());
1790 core::hint::black_box(t);
1791 });
1792 time(" of which: 255 doublings", &mut || {
1793 let mut p = a_point;
1794 for _ in 0..255 {
1795 p = p.double();
1796 }
1797 core::hint::black_box(p);
1798 });
1799 time(" of which: 79 additions", &mut || {
1800 let mut p = a_point;
1801 for _ in 0..79 {
1802 p = p.add(&a_point);
1803 }
1804 core::hint::black_box(p);
1805 });
1806 time("compress (one inversion)", &mut || {
1807 core::hint::black_box(a_point.compress());
1808 });
1809 println!(
1810 " {:<34} {:>9.2} us",
1811 "-- accounted for",
1812 2.0 * d + h + b + v
1813 );
1814
1815 println!(
1817 "
1818field and point primitives, nanoseconds:"
1819 );
1820 let nn = 200_000;
1821 let ns = |label: &str, f: &mut dyn FnMut()| {
1822 let mut best = f64::INFINITY;
1823 for _ in 0..5 {
1824 let t = Instant::now();
1825 for _ in 0..nn {
1826 f();
1827 }
1828 let e = t.elapsed().as_secs_f64() / nn as f64 * 1e9;
1829 if e < best {
1830 best = e;
1831 }
1832 }
1833 println!(" {label:<34} {best:>9.2} ns");
1834 };
1835 let fx = a_point.x;
1836 let fy = a_point.y;
1837 ns("Fe::mul", &mut || {
1838 core::hint::black_box(core::hint::black_box(&fx).mul(core::hint::black_box(&fy)));
1839 });
1840 ns("Fe::square", &mut || {
1841 core::hint::black_box(core::hint::black_box(&fx).square());
1842 });
1843 ns("Fe::add", &mut || {
1844 core::hint::black_box(core::hint::black_box(&fx).add(core::hint::black_box(&fy)));
1845 });
1846 ns("Fe::sub", &mut || {
1847 core::hint::black_box(core::hint::black_box(&fx).sub(core::hint::black_box(&fy)));
1848 });
1849 ns("Fe::neg", &mut || {
1850 core::hint::black_box(core::hint::black_box(&fx).neg());
1851 });
1852 let proj = a_point.to_projective();
1853 let comp = proj.double();
1854 let an = a_point.to_affine_niels();
1855 ns("Projective::double (4S)", &mut || {
1856 core::hint::black_box(core::hint::black_box(&proj).double());
1857 });
1858 ns("Projective::double_projective", &mut || {
1859 core::hint::black_box(core::hint::black_box(proj).double_projective());
1860 });
1861 ns("Completed::to_projective (3M)", &mut || {
1862 core::hint::black_box(core::hint::black_box(&comp).to_projective());
1863 });
1864 ns("Completed::to_extended (4M)", &mut || {
1865 core::hint::black_box(core::hint::black_box(&comp).to_extended());
1866 });
1867 ns("Point::add_affine_niels (3M)", &mut || {
1868 core::hint::black_box(
1869 core::hint::black_box(&a_point).add_affine_niels(core::hint::black_box(&an)),
1870 );
1871 });
1872 ns("Point::double", &mut || {
1873 core::hint::black_box(core::hint::black_box(&a_point).double());
1874 });
1875 ns("Point::add", &mut || {
1876 core::hint::black_box(
1877 core::hint::black_box(&a_point).add(core::hint::black_box(&a_point)),
1878 );
1879 });
1880 }
1881
1882 #[test]
1884 #[ignore = "diagnostic, not a test"]
1885 fn count_the_point_operations() {
1886 let mut doublings = 0usize;
1887 let mut adds_a = 0usize;
1888 let mut adds_b = 0usize;
1889 let mut state = 0x1234_5678_9abc_def0u64;
1890 let trials = 200;
1891 for _ in 0..trials {
1892 let mut kb = [0u8; 32];
1893 for c in kb.chunks_exact_mut(8) {
1894 state ^= state >> 12;
1895 state ^= state << 25;
1896 state ^= state >> 27;
1897 c.copy_from_slice(&state.wrapping_mul(0x2545_f491_4f6c_dd1d).to_le_bytes());
1898 }
1899 kb[31] &= 0x0f;
1900 let na = wnaf(&kb, 5);
1901 let nb = wnaf(&kb, 8);
1902 let mut i = 257;
1903 while i > 0 && na[i] == 0 && nb[i] == 0 {
1904 i -= 1;
1905 }
1906 doublings += i + 1;
1907 adds_a += na.iter().filter(|d| **d != 0).count();
1908 adds_b += nb.iter().filter(|d| **d != 0).count();
1909 }
1910 let d = doublings as f64 / trials as f64;
1911 let aa = adds_a as f64 / trials as f64;
1912 let ab = adds_b as f64 / trials as f64;
1913 println!(
1914 "
1915 per double-scalar multiplication, averaged over {trials} scalars:"
1916 );
1917 println!(" doublings {d:>8.1}");
1918 println!(" additions, w=5 table (A) {aa:>8.1}");
1919 println!(" additions, w=8 table (B) {ab:>8.1}");
1920 println!(" additions, building A {:>8.1}", 8.0);
1921 println!(" ---");
1922 println!(" total additions {:>8.1}", aa + ab + 8.0);
1923 println!(
1924 " field muls, at 4M+4S per doubling and 9M per addition: {:>6.0}",
1925 d * 8.0 + (aa + ab + 8.0) * 9.0
1926 );
1927 println!(
1928 " the same at dalek's 3M+4S and 7M: {:>6.0}",
1929 d * 7.0 + (aa + ab + 8.0) * 7.0
1930 );
1931 }
1932}