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 ic_core::module::operational()?;
1001 ensure!(seed.len() == 32, InvalidLength, "ed25519 seed");
1002 let (scalar, prefix) = expand_seed(seed);
1003 let public = mul_basepoint(&scalar).compress();
1004 Ok(Self {
1005 scalar,
1006 prefix,
1007 public,
1008 })
1009 }
1010
1011 pub fn public_key(&self) -> &[u8; 32] {
1013 &self.public
1014 }
1015
1016 pub fn sign(&self, message: &[u8], signature: &mut [u8]) -> Result<()> {
1018 ic_core::module::operational()?;
1019 ensure!(
1020 signature.len() == 64,
1021 InvalidLength,
1022 "ed25519 signature buffer"
1023 );
1024
1025 let mut r = hash_to_scalar(&[&self.prefix, message]);
1028 let big_r = mul_basepoint(&r).compress();
1029
1030 let k = hash_to_scalar(&[&big_r, &self.public, message]);
1031 let s = scalar::mul_add(&k, &self.scalar, &r);
1032
1033 signature[..32].copy_from_slice(&big_r);
1034 signature[32..].copy_from_slice(&s);
1035 r.zeroize();
1036 Ok(())
1037 }
1038}
1039
1040impl SignatureScheme for Ed25519 {
1041 const PRIVATE_KEY_LEN: usize = 32;
1042 const PUBLIC_KEY_LEN: usize = 32;
1043 const SIGNATURE_LEN: usize = 64;
1044
1045 fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()> {
1046 ic_core::module::operational()?;
1047 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1048 ensure!(out.len() == 32, InvalidLength, "ed25519 public key buffer");
1049 let (mut a, mut prefix) = expand_seed(private_key);
1050 out.copy_from_slice(&mul_basepoint(&a).compress());
1051 a.zeroize();
1052 prefix.zeroize();
1053 Ok(())
1054 }
1055
1056 fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()> {
1057 ic_core::module::operational()?;
1058 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1059 ensure!(
1060 signature.len() == 64,
1061 InvalidLength,
1062 "ed25519 signature buffer"
1063 );
1064
1065 Ed25519Key::from_seed(private_key)?.sign(message, signature)
1069 }
1070
1071 fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()> {
1072 ic_core::module::operational()?;
1073 Ed25519VerifyKey::from_bytes(public_key)?.verify(message, signature)
1077 }
1078}
1079
1080pub struct Ed25519VerifyKey {
1094 compressed: [u8; 32],
1096 neg_a: Point,
1098}
1099
1100impl Ed25519VerifyKey {
1101 pub fn from_bytes(public_key: &[u8]) -> Result<Self> {
1103 ensure!(public_key.len() == 32, InvalidLength, "ed25519 public key");
1104 let mut compressed = [0u8; 32];
1105 compressed.copy_from_slice(public_key);
1106 let a = Point::decompress(&compressed).ok_or(ic_core::err!(
1107 MalformedEncoding,
1108 "ed25519 public key is not on the curve"
1109 ))?;
1110 Ok(Self {
1111 compressed,
1112 neg_a: a.negate(),
1113 })
1114 }
1115
1116 pub fn as_bytes(&self) -> &[u8; 32] {
1118 &self.compressed
1119 }
1120
1121 pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> {
1123 ic_core::module::operational()?;
1124 ensure!(signature.len() == 64, InvalidLength, "ed25519 signature");
1125
1126 let mut big_r = [0u8; 32];
1127 big_r.copy_from_slice(&signature[..32]);
1128 let mut s = [0u8; 32];
1129 s.copy_from_slice(&signature[32..]);
1130
1131 ensure!(
1135 scalar::is_canonical(&s),
1136 MalformedEncoding,
1137 "ed25519 signature S is not reduced"
1138 );
1139
1140 let r_point = Point::decompress(&big_r).ok_or(ic_core::err!(
1141 MalformedEncoding,
1142 "ed25519 signature R is not on the curve"
1143 ))?;
1144
1145 let k = hash_to_scalar(&[&big_r, &self.compressed, message]);
1146
1147 #[cfg(feature = "std")]
1151 let lhs = double_scalar_mul_vartime(&self.neg_a, &k, &s);
1152 #[cfg(not(feature = "std"))]
1153 let lhs = double_scalar_mul_vartime_no_table(&self.neg_a, &k, &s);
1154
1155 if lhs.eq_projective(&r_point) {
1156 Ok(())
1157 } else {
1158 Err(ic_core::err!(AuthenticationFailed, "ed25519"))
1159 }
1160 }
1161}
1162
1163impl SelfTest for Ed25519 {
1164 fn self_test() -> Result<()> {
1165 let mut seed = [0u8; 32];
1167 ic_core::codec::hex_decode(
1168 b"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1169 &mut seed,
1170 )?;
1171 let mut want_pk = [0u8; 32];
1172 ic_core::codec::hex_decode(
1173 b"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1174 &mut want_pk,
1175 )?;
1176 let mut want_sig = [0u8; 64];
1177 ic_core::codec::hex_decode(
1178 b"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1179 &mut want_sig,
1180 )?;
1181
1182 let mut pk = [0u8; 32];
1183 <Self as SignatureScheme>::public_key(&seed, &mut pk)?;
1184 ensure!(
1185 ic_core::ct::verify(&want_pk, &pk),
1186 SelfTestFailed,
1187 "ed25519"
1188 );
1189
1190 let mut sig = [0u8; 64];
1191 <Self as SignatureScheme>::sign(&seed, b"", &mut sig)?;
1192 ensure!(
1193 ic_core::ct::verify(&want_sig, &sig),
1194 SelfTestFailed,
1195 "ed25519"
1196 );
1197
1198 <Self as SignatureScheme>::verify(&pk, b"", &sig)?;
1199
1200 sig[0] ^= 1;
1202 ensure!(
1203 <Self as SignatureScheme>::verify(&pk, b"", &sig).is_err(),
1204 SelfTestFailed,
1205 "ed25519"
1206 );
1207 Ok(())
1208 }
1209}
1210
1211#[cfg(test)]
1212mod tests {
1213 use super::*;
1214 use ic_core::codec::{hex, unhex};
1215
1216 #[test]
1217 fn curve_constants_are_correct() {
1218 let d = Fe::from_u64(121_665)
1220 .neg()
1221 .mul(&Fe::from_u64(121_666).invert());
1222 assert_eq!(hex(&D.to_bytes()), hex(&d.to_bytes()), "d");
1223 assert_eq!(hex(&D2.to_bytes()), hex(&d.add(&d).to_bytes()), "2d");
1224 assert_eq!(
1226 hex(&SQRT_M1.square().to_bytes()),
1227 hex(&Fe::ONE.neg().to_bytes()),
1228 "sqrt(-1)"
1229 );
1230 }
1231
1232 #[test]
1233 fn basepoint_has_the_expected_coordinates() {
1234 let b = basepoint();
1235 let expected_y = Fe::from_u64(4).mul(&Fe::from_u64(5).invert());
1237 let z_inv = b.z.invert();
1238 assert_eq!(
1239 hex(&b.y.mul(&z_inv).to_bytes()),
1240 hex(&expected_y.to_bytes())
1241 );
1242 assert_eq!(hex(&b.compress()), hex(&BASEPOINT_COMPRESSED));
1243 }
1244
1245 #[test]
1252 fn doubling_agrees_with_adding_a_point_to_itself() {
1253 let mut p = basepoint();
1254 let mut checked = 0;
1255 for _ in 0..16 {
1256 assert_eq!(
1257 p.double().compress(),
1258 p.add(&p).compress(),
1259 "dedicated doubling and self-addition differ"
1260 );
1261 p = p.add(&basepoint());
1262 checked += 1;
1263 }
1264 assert_eq!(checked, 16, "the comparison did not run");
1265
1266 assert_eq!(
1269 Point::IDENTITY.double().compress(),
1270 Point::IDENTITY.compress()
1271 );
1272 }
1273
1274 #[test]
1282 fn projective_equality_agrees_with_compressed_equality() {
1283 let b = basepoint();
1284 let mut points = std::vec![Point::IDENTITY, b];
1285 let mut p = b;
1286 for _ in 0..6 {
1287 p = p.double();
1288 points.push(p);
1289 }
1290
1291 let mut checked = 0;
1292 for (i, a) in points.iter().enumerate() {
1293 for (j, c) in points.iter().enumerate() {
1294 let projective = a.eq_projective(c);
1295 let compressed = a.compress() == c.compress();
1296 assert_eq!(
1297 projective, compressed,
1298 "projective and compressed equality differ for {i} vs {j}"
1299 );
1300 checked += 1;
1301 }
1302 }
1303 assert_eq!(checked, 64, "the comparison did not run");
1304
1305 let scaled = b.add(&Point::IDENTITY);
1308 assert!(b.eq_projective(&scaled), "equal points with different Z");
1309 assert_eq!(b.compress(), scaled.compress());
1310 }
1311
1312 #[test]
1313 fn group_law_is_consistent() {
1314 let b = basepoint();
1315 assert_eq!(hex(&b.add(&Point::IDENTITY).compress()), hex(&b.compress()));
1317 let mut two = [0u8; 32];
1319 two[0] = 2;
1320 assert_eq!(
1321 hex(&b.double().compress()),
1322 hex(&b.mul_scalar(&two).compress())
1323 );
1324 let mut three = [0u8; 32];
1326 three[0] = 3;
1327 assert_eq!(
1328 hex(&b.double().add(&b).compress()),
1329 hex(&b.mul_scalar(&three).compress())
1330 );
1331 }
1332
1333 #[test]
1334 fn order_of_the_basepoint_is_l() {
1335 assert_eq!(
1337 hex(&basepoint().mul_scalar(&scalar::L).compress()),
1338 hex(&Point::IDENTITY.compress())
1339 );
1340 }
1341
1342 #[test]
1343 fn compression_roundtrips() {
1344 let b = basepoint();
1345 for k in [1u8, 2, 3, 47, 200] {
1346 let mut s = [0u8; 32];
1347 s[0] = k;
1348 let p = b.mul_scalar(&s);
1349 let c = p.compress();
1350 let d = Point::decompress(&c).expect("valid point");
1351 assert_eq!(hex(&d.compress()), hex(&c), "k = {k}");
1352 }
1353 }
1354
1355 #[test]
1356 fn decompression_rejects_non_curve_points() {
1357 let mut bad = [0u8; 32];
1359 bad[0] = 2;
1360 assert!(Point::decompress(&bad).is_none());
1361 }
1362
1363 #[test]
1365 fn rfc8032_vectors() {
1366 let cases: [(&str, &str, &str, &str); 3] = [
1367 (
1368 "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1369 "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1370 "",
1371 "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1372 ),
1373 (
1374 "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
1375 "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
1376 "72",
1377 "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
1378 ),
1379 (
1380 "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
1381 "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
1382 "af82",
1383 "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
1384 ),
1385 ];
1386
1387 for (seed_hex, pk_hex, msg_hex, sig_hex) in cases {
1388 let seed = unhex(seed_hex).unwrap();
1389 let msg = unhex(msg_hex).unwrap();
1390
1391 let mut pk = [0u8; 32];
1392 Ed25519::public_key(&seed, &mut pk).unwrap();
1393 assert_eq!(hex(&pk), pk_hex, "public key for {seed_hex}");
1394
1395 let mut sig = [0u8; 64];
1396 Ed25519::sign(&seed, &msg, &mut sig).unwrap();
1397 assert_eq!(hex(&sig), sig_hex, "signature for {seed_hex}");
1398
1399 Ed25519::verify(&pk, &msg, &sig).unwrap();
1400 }
1401 }
1402
1403 #[test]
1404 fn verification_rejects_tampering() {
1405 let seed = [0x42u8; 32];
1406 let mut pk = [0u8; 32];
1407 Ed25519::public_key(&seed, &mut pk).unwrap();
1408 let mut sig = [0u8; 64];
1409 Ed25519::sign(&seed, b"authentic", &mut sig).unwrap();
1410 Ed25519::verify(&pk, b"authentic", &sig).unwrap();
1411
1412 assert!(Ed25519::verify(&pk, b"forged", &sig).is_err());
1414 let mut bad = sig;
1416 bad[0] ^= 1;
1417 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1418 let mut bad = sig;
1420 bad[40] ^= 1;
1421 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1422 let mut other_pk = [0u8; 32];
1424 Ed25519::public_key(&[0x43u8; 32], &mut other_pk).unwrap();
1425 assert!(Ed25519::verify(&other_pk, b"authentic", &sig).is_err());
1426 }
1427
1428 #[test]
1431 fn rejects_non_canonical_s() {
1432 let seed = [0x42u8; 32];
1433 let mut pk = [0u8; 32];
1434 Ed25519::public_key(&seed, &mut pk).unwrap();
1435 let mut sig = [0u8; 64];
1436 Ed25519::sign(&seed, b"msg", &mut sig).unwrap();
1437
1438 let mut carry = 0u16;
1441 for i in 0..32 {
1442 let t = sig[32 + i] as u16 + scalar::L[i] as u16 + carry;
1443 sig[32 + i] = t as u8;
1444 carry = t >> 8;
1445 }
1446 assert!(Ed25519::verify(&pk, b"msg", &sig).is_err());
1447 }
1448
1449 #[test]
1456 fn the_cached_key_signs_identically_to_the_seed() {
1457 let mut checked = 0;
1458 for seed in [[0x11u8; 32], [0x9du8; 32], [0xffu8; 32]] {
1459 for message in [&b""[..], &b"x"[..], &b"a longer message to sign"[..]] {
1460 let mut from_seed = [0u8; 64];
1461 Ed25519::sign(&seed, message, &mut from_seed).unwrap();
1462
1463 let key = Ed25519Key::from_seed(&seed).unwrap();
1464 let mut from_key = [0u8; 64];
1465 key.sign(message, &mut from_key).unwrap();
1466
1467 assert_eq!(from_seed, from_key, "the two signing paths diverged");
1468
1469 let mut derived = [0u8; 32];
1471 Ed25519::public_key(&seed, &mut derived).unwrap();
1472 assert_eq!(&derived, key.public_key());
1473
1474 Ed25519::verify(&derived, message, &from_key).unwrap();
1476 checked += 1;
1477 }
1478 }
1479 assert_eq!(checked, 9, "the comparison did not run");
1480 }
1481
1482 fn windowed_scalars() -> std::vec::Vec<[u8; 32]> {
1487 let mut one = [0u8; 32];
1488 one[0] = 1;
1489 let mut eight = [0u8; 32];
1490 eight[0] = 8;
1491 let mut top = [0xffu8; 32];
1492 top[31] = 0x7f;
1493 let mut clamped = [0x9du8; 32];
1494 clamped[0] &= 248;
1495 clamped[31] &= 127;
1496 clamped[31] |= 64;
1497 let mut l_minus_1 = scalar::L;
1498 l_minus_1[0] -= 1;
1499 let mut out = std::vec![[0u8; 32], one, eight, top, clamped, l_minus_1];
1500 for fill in [0x88u8, 0x77, 0x99, 0x55, 0xaa] {
1501 let mut s = [fill; 32];
1502 s[31] &= 0x7f;
1503 out.push(s);
1504 }
1505 out
1506 }
1507
1508 #[test]
1518 fn the_basepoint_constant_is_the_decompressed_encoding() {
1519 let decoded = Point::decompress(&BASEPOINT_COMPRESSED).expect("the RFC 8032 basepoint");
1520 let zinv = decoded.z.invert();
1521 for (name, constant, from_encoding) in [
1522 ("x", BASEPOINT.x, decoded.x.mul(&zinv)),
1523 ("y", BASEPOINT.y, decoded.y.mul(&zinv)),
1524 ("t", BASEPOINT.t, decoded.t.mul(&zinv)),
1525 ] {
1526 assert_eq!(
1527 constant.to_bytes(),
1528 from_encoding.to_bytes(),
1529 "{name} differs"
1530 );
1531 }
1532 assert_eq!(BASEPOINT.z.to_bytes(), Fe::ONE.to_bytes());
1533 assert_eq!(BASEPOINT.compress(), BASEPOINT_COMPRESSED);
1534 }
1535
1536 #[test]
1537 fn the_windowed_multiplication_agrees_with_the_ladder() {
1538 let b = basepoint();
1539 let mut seven = [0u8; 32];
1540 seven[0] = 7;
1541 let p = b.mul_scalar(&seven);
1542
1543 let mut checked = 0;
1544 for point in [b, p] {
1545 for scalar in windowed_scalars() {
1546 assert_eq!(
1547 mul_scalar_windowed(&point, &scalar).compress(),
1548 point.mul_scalar(&scalar).compress(),
1549 "windowed and ladder differ for {scalar:02x?}"
1550 );
1551 checked += 1;
1552 }
1553 }
1554 assert!(checked >= 20, "only {checked} comparisons ran");
1555 }
1556
1557 #[test]
1560 fn the_untabled_double_multiplication_agrees() {
1561 let b = basepoint();
1562 let mut checked = 0;
1563 for (i, k) in windowed_scalars().into_iter().enumerate() {
1564 let mut seed = [0u8; 32];
1565 seed[0] = 3 + i as u8;
1566 let a = b.mul_scalar(&seed);
1567 for s in [k, [0xffu8; 32], [0x9du8; 32]] {
1568 let untabled = double_scalar_mul_vartime_no_table(&a, &k, &s);
1569 let tabled = double_scalar_mul_vartime(&a, &k, &s);
1570 let ladders = a.mul_scalar(&k).add(&b.mul_scalar(&s));
1571 assert_eq!(
1572 untabled.compress(),
1573 tabled.compress(),
1574 "k={k:02x?} s={s:02x?}"
1575 );
1576 assert_eq!(
1577 untabled.compress(),
1578 ladders.compress(),
1579 "k={k:02x?} s={s:02x?}"
1580 );
1581 checked += 1;
1582 }
1583 }
1584 assert!(checked >= 30, "only {checked} comparisons ran");
1585 }
1586
1587 #[test]
1589 fn the_wnaf_digits_are_odd_sparse_and_faithful() {
1590 for scalar in [[1u8; 32], [0x9du8; 32], [0xffu8; 32], [0x55u8; 32]] {
1591 let naf = wnaf(&scalar, 5);
1592
1593 let mut previous_nonzero: Option<usize> = None;
1594 for (i, d) in naf.iter().enumerate() {
1595 if *d == 0 {
1596 continue;
1597 }
1598 assert!(d % 2 != 0, "digit {d} at {i} is not odd");
1599 assert!((-15..=15).contains(d), "digit {d} at {i} is out of range");
1600 if let Some(j) = previous_nonzero {
1601 assert!(i - j >= 5, "digits at {j} and {i} are adjacent");
1602 }
1603 previous_nonzero = Some(i);
1604 }
1605
1606 const M: u128 = 1_000_000_007;
1609 let mut from_digits = 0u128;
1610 let mut power = 1u128;
1611 for d in naf {
1612 let term = ((d as i128).rem_euclid(M as i128)) as u128;
1613 from_digits = (from_digits + term * power) % M;
1614 power = power * 2 % M;
1615 }
1616 let mut from_bytes = 0u128;
1617 let mut p = 1u128;
1618 for byte in scalar {
1619 from_bytes = (from_bytes + (byte as u128) * p) % M;
1620 p = p * 256 % M;
1621 }
1622 assert_eq!(from_digits, from_bytes, "recoding changed the value");
1623 }
1624 }
1625
1626 #[test]
1627 fn signing_is_deterministic() {
1628 let seed = [0x7fu8; 32];
1629 let mut a = [0u8; 64];
1630 let mut b = [0u8; 64];
1631 Ed25519::sign(&seed, b"same input", &mut a).unwrap();
1632 Ed25519::sign(&seed, b"same input", &mut b).unwrap();
1633 assert_eq!(a, b);
1634 }
1635
1636 #[test]
1637 fn rejects_wrong_lengths() {
1638 let mut out = [0u8; 32];
1639 assert!(Ed25519::public_key(&[0u8; 31], &mut out).is_err());
1640 assert!(Ed25519::sign(&[0u8; 32], b"", &mut [0u8; 63]).is_err());
1641 assert!(Ed25519::verify(&[0u8; 32], b"", &[0u8; 63]).is_err());
1642 }
1643
1644 #[test]
1645 fn self_test_passes() {
1646 Ed25519::self_test().unwrap();
1647 }
1648
1649 fn sample_points(n: usize) -> Vec<Point> {
1651 let mut out = Vec::new();
1652 let mut p = basepoint();
1653 for _ in 0..n {
1654 out.push(p);
1655 p = p.double().add(&basepoint());
1656 }
1657 out
1658 }
1659
1660 #[test]
1670 fn the_completed_doubling_agrees_with_the_extended_one() {
1671 for p in sample_points(40) {
1672 let want = p.double();
1673 let got = p.to_projective().double().to_extended();
1674 assert!(got.eq_projective(&want), "doubling disagrees");
1675 let chained = p.to_projective().double().to_projective().double();
1678 let twice = p.double().double();
1679 assert!(chained.to_extended().eq_projective(&twice), "two doublings");
1680 }
1681 }
1682
1683 #[test]
1685 fn niels_addition_agrees_with_the_general_one() {
1686 let pts = sample_points(20);
1687 for p in &pts {
1688 for q in &pts {
1689 let want = p.add(q);
1690 let got = p.add_niels(&q.to_niels()).to_extended();
1691 assert!(got.eq_projective(&want), "add_niels disagrees");
1692
1693 let want_sub = p.add(&q.negate());
1694 let got_sub = p.sub_niels(&q.to_niels()).to_extended();
1695 assert!(got_sub.eq_projective(&want_sub), "sub_niels disagrees");
1696 }
1697 }
1698 }
1699
1700 #[test]
1708 fn affine_niels_addition_agrees_with_the_general_one() {
1709 let pts = sample_points(20);
1710 for p in &pts {
1711 for q in &pts {
1712 let want = p.add(q);
1713 let got = p.add_affine_niels(&q.to_affine_niels()).to_extended();
1714 assert!(got.eq_projective(&want), "add_affine_niels disagrees");
1715
1716 let mut n = q.to_affine_niels();
1718 n.conditional_negate(ic_core::ct::Choice::from_u8(1));
1719 let want_neg = p.add(&q.negate());
1720 let got_neg = p.add_affine_niels(&n).to_extended();
1721 assert!(got_neg.eq_projective(&want_neg), "negated form disagrees");
1722 }
1723 }
1724 }
1725
1726 #[test]
1733 #[ignore = "diagnostic, not a test"]
1734 fn where_verify_spends_its_time() {
1735 use std::time::Instant;
1736
1737 let seed = [7u8; 32];
1738 let key = Ed25519Key::from_seed(&seed).unwrap();
1739 let msg = b"benchmark message";
1740 let mut sig = [0u8; 64];
1741 key.sign(msg, &mut sig).unwrap();
1742 let pk = *key.public_key();
1743
1744 let mut big_r = [0u8; 32];
1745 big_r.copy_from_slice(&sig[..32]);
1746 let mut s_sc = [0u8; 32];
1747 s_sc.copy_from_slice(&sig[32..]);
1748
1749 let n = 2000;
1750 let time = |label: &str, f: &mut dyn FnMut()| {
1751 let mut best = f64::INFINITY;
1752 for _ in 0..5 {
1753 let t = Instant::now();
1754 for _ in 0..n {
1755 f();
1756 }
1757 let e = t.elapsed().as_secs_f64() / n as f64 * 1e6;
1758 if e < best {
1759 best = e;
1760 }
1761 }
1762 println!(" {label:<34} {best:>9.2} us");
1763 best
1764 };
1765
1766 let a_point = Point::decompress(&pk).unwrap();
1767 let k = hash_to_scalar(&[&big_r, &pk, msg]);
1768
1769 println!(
1770 "
1771ed25519 verify, cost breakdown:"
1772 );
1773 let d = time("decompress (x2 per verify)", &mut || {
1774 core::hint::black_box(Point::decompress(&pk));
1775 });
1776 let h = time("hash_to_scalar", &mut || {
1777 core::hint::black_box(hash_to_scalar(&[&big_r, &pk, msg]));
1778 });
1779 let b = time("mul_basepoint (const time)", &mut || {
1780 core::hint::black_box(mul_basepoint(&s_sc));
1781 });
1782 let v = time("double_scalar_mul_vartime", &mut || {
1783 core::hint::black_box(double_scalar_mul_vartime(&a_point.negate(), &k, &s_sc));
1784 });
1785 time(" of which: wnaf(k,5)+wnaf(s,8)", &mut || {
1786 core::hint::black_box(wnaf(&k, 5));
1787 core::hint::black_box(wnaf(&s_sc, 8));
1788 });
1789 time(" of which: odd_a table build", &mut || {
1790 let twice = a_point.double();
1791 let mut odd = [a_point; 8];
1792 for i in 1..8 {
1793 odd[i] = odd[i - 1].add(&twice);
1794 }
1795 let t: [Niels; 8] = core::array::from_fn(|i| odd[i].to_niels());
1796 core::hint::black_box(t);
1797 });
1798 time(" of which: 255 doublings", &mut || {
1799 let mut p = a_point;
1800 for _ in 0..255 {
1801 p = p.double();
1802 }
1803 core::hint::black_box(p);
1804 });
1805 time(" of which: 79 additions", &mut || {
1806 let mut p = a_point;
1807 for _ in 0..79 {
1808 p = p.add(&a_point);
1809 }
1810 core::hint::black_box(p);
1811 });
1812 time("compress (one inversion)", &mut || {
1813 core::hint::black_box(a_point.compress());
1814 });
1815 println!(
1816 " {:<34} {:>9.2} us",
1817 "-- accounted for",
1818 2.0 * d + h + b + v
1819 );
1820
1821 println!(
1823 "
1824field and point primitives, nanoseconds:"
1825 );
1826 let nn = 200_000;
1827 let ns = |label: &str, f: &mut dyn FnMut()| {
1828 let mut best = f64::INFINITY;
1829 for _ in 0..5 {
1830 let t = Instant::now();
1831 for _ in 0..nn {
1832 f();
1833 }
1834 let e = t.elapsed().as_secs_f64() / nn as f64 * 1e9;
1835 if e < best {
1836 best = e;
1837 }
1838 }
1839 println!(" {label:<34} {best:>9.2} ns");
1840 };
1841 let fx = a_point.x;
1842 let fy = a_point.y;
1843 ns("Fe::mul", &mut || {
1844 core::hint::black_box(core::hint::black_box(&fx).mul(core::hint::black_box(&fy)));
1845 });
1846 ns("Fe::square", &mut || {
1847 core::hint::black_box(core::hint::black_box(&fx).square());
1848 });
1849 ns("Fe::add", &mut || {
1850 core::hint::black_box(core::hint::black_box(&fx).add(core::hint::black_box(&fy)));
1851 });
1852 ns("Fe::sub", &mut || {
1853 core::hint::black_box(core::hint::black_box(&fx).sub(core::hint::black_box(&fy)));
1854 });
1855 ns("Fe::neg", &mut || {
1856 core::hint::black_box(core::hint::black_box(&fx).neg());
1857 });
1858 let proj = a_point.to_projective();
1859 let comp = proj.double();
1860 let an = a_point.to_affine_niels();
1861 ns("Projective::double (4S)", &mut || {
1862 core::hint::black_box(core::hint::black_box(&proj).double());
1863 });
1864 ns("Projective::double_projective", &mut || {
1865 core::hint::black_box(core::hint::black_box(proj).double_projective());
1866 });
1867 ns("Completed::to_projective (3M)", &mut || {
1868 core::hint::black_box(core::hint::black_box(&comp).to_projective());
1869 });
1870 ns("Completed::to_extended (4M)", &mut || {
1871 core::hint::black_box(core::hint::black_box(&comp).to_extended());
1872 });
1873 ns("Point::add_affine_niels (3M)", &mut || {
1874 core::hint::black_box(
1875 core::hint::black_box(&a_point).add_affine_niels(core::hint::black_box(&an)),
1876 );
1877 });
1878 ns("Point::double", &mut || {
1879 core::hint::black_box(core::hint::black_box(&a_point).double());
1880 });
1881 ns("Point::add", &mut || {
1882 core::hint::black_box(
1883 core::hint::black_box(&a_point).add(core::hint::black_box(&a_point)),
1884 );
1885 });
1886 }
1887
1888 #[test]
1890 #[ignore = "diagnostic, not a test"]
1891 fn count_the_point_operations() {
1892 let mut doublings = 0usize;
1893 let mut adds_a = 0usize;
1894 let mut adds_b = 0usize;
1895 let mut state = 0x1234_5678_9abc_def0u64;
1896 let trials = 200;
1897 for _ in 0..trials {
1898 let mut kb = [0u8; 32];
1899 for c in kb.chunks_exact_mut(8) {
1900 state ^= state >> 12;
1901 state ^= state << 25;
1902 state ^= state >> 27;
1903 c.copy_from_slice(&state.wrapping_mul(0x2545_f491_4f6c_dd1d).to_le_bytes());
1904 }
1905 kb[31] &= 0x0f;
1906 let na = wnaf(&kb, 5);
1907 let nb = wnaf(&kb, 8);
1908 let mut i = 257;
1909 while i > 0 && na[i] == 0 && nb[i] == 0 {
1910 i -= 1;
1911 }
1912 doublings += i + 1;
1913 adds_a += na.iter().filter(|d| **d != 0).count();
1914 adds_b += nb.iter().filter(|d| **d != 0).count();
1915 }
1916 let d = doublings as f64 / trials as f64;
1917 let aa = adds_a as f64 / trials as f64;
1918 let ab = adds_b as f64 / trials as f64;
1919 println!(
1920 "
1921 per double-scalar multiplication, averaged over {trials} scalars:"
1922 );
1923 println!(" doublings {d:>8.1}");
1924 println!(" additions, w=5 table (A) {aa:>8.1}");
1925 println!(" additions, w=8 table (B) {ab:>8.1}");
1926 println!(" additions, building A {:>8.1}", 8.0);
1927 println!(" ---");
1928 println!(" total additions {:>8.1}", aa + ab + 8.0);
1929 println!(
1930 " field muls, at 4M+4S per doubling and 9M per addition: {:>6.0}",
1931 d * 8.0 + (aa + ab + 8.0) * 9.0
1932 );
1933 println!(
1934 " the same at dalek's 3M+4S and 7M: {:>6.0}",
1935 d * 7.0 + (aa + ab + 8.0) * 7.0
1936 );
1937 }
1938}