1use crate::field::Fe;
11use crate::scalar;
12use ic_core::ct::Choice;
13
14mod basepoint_table;
16use ic_core::traits::{Algorithm, Digest, SelfTest, SignatureScheme};
17use ic_core::{ensure, Result, Zeroize};
18use ic_hash::Sha512;
19
20const BASEPOINT_COMPRESSED: [u8; 32] = [
22 0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
23 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
24];
25
26const D: Fe = Fe([
28 929_955_233_495_203,
29 466_365_720_129_213,
30 1_662_059_464_998_953,
31 2_033_849_074_728_123,
32 1_442_794_654_840_575,
33]);
34
35const D2: Fe = Fe([
37 1_859_910_466_990_425,
38 932_731_440_258_426,
39 1_072_319_116_312_658,
40 1_815_898_335_770_999,
41 633_789_495_995_903,
42]);
43
44const SQRT_M1: Fe = Fe([
46 1_718_705_420_411_056,
47 234_908_883_556_509,
48 2_233_514_472_574_048,
49 2_117_202_627_021_982,
50 765_476_049_583_133,
51]);
52
53#[derive(Clone, Copy, Debug)]
55pub struct Point {
56 x: Fe,
57 y: Fe,
58 z: Fe,
59 t: Fe,
60}
61
62#[derive(Clone, Copy)]
72pub(crate) struct Completed {
73 x: Fe,
74 y: Fe,
75 z: Fe,
76 t: Fe,
77}
78
79#[derive(Clone, Copy)]
83pub(crate) struct Projective {
84 x: Fe,
85 y: Fe,
86 z: Fe,
87}
88
89#[derive(Clone, Copy)]
97pub(crate) struct Niels {
98 ypx: Fe,
99 ymx: Fe,
100 z: Fe,
101 t2d: Fe,
102}
103
104#[derive(Clone, Copy)]
110pub(crate) struct AffineNiels {
111 ypx: Fe,
112 ymx: Fe,
113 t2d: Fe,
114}
115
116impl AffineNiels {
117 pub(crate) const IDENTITY: AffineNiels = AffineNiels {
119 ypx: Fe::ONE,
120 ymx: Fe::ONE,
121 t2d: Fe::ZERO,
122 };
123
124 pub(crate) fn conditional_negate(&mut self, choice: Choice) {
127 let swapped_p = self.ymx;
128 let swapped_m = self.ypx;
129 let nt = self.t2d.neg();
130 Fe::cmov(&mut self.ypx, &swapped_p, choice);
131 Fe::cmov(&mut self.ymx, &swapped_m, choice);
132 Fe::cmov(&mut self.t2d, &nt, choice);
133 }
134
135 pub(crate) fn cmov(&mut self, other: &AffineNiels, choice: Choice) {
136 Fe::cmov(&mut self.ypx, &other.ypx, choice);
137 Fe::cmov(&mut self.ymx, &other.ymx, choice);
138 Fe::cmov(&mut self.t2d, &other.t2d, choice);
139 }
140}
141
142impl Completed {
143 fn to_projective(self) -> Projective {
145 Projective {
146 x: self.x.mul(&self.t),
147 y: self.y.mul(&self.z),
148 z: self.z.mul(&self.t),
149 }
150 }
151
152 fn to_extended(self) -> Point {
157 Point {
158 x: self.x.mul(&self.t),
159 y: self.y.mul(&self.z),
160 z: self.z.mul(&self.t),
161 t: self.x.mul(&self.y),
162 }
163 }
164}
165
166impl Projective {
167 fn to_extended_from_projective(self) -> Point {
172 Point {
179 x: self.x.mul(&self.z),
180 y: self.y.mul(&self.z),
181 z: self.z.square(),
182 t: self.x.mul(&self.y),
183 }
184 }
185
186 fn double_projective(self) -> Projective {
196 let xx = self.x.square();
197 let yy = self.y.square();
198 let zz2 = {
199 let t = self.z.square();
200 t.add(&t)
201 };
202 let xy_sq = self.x.add(&self.y).square();
203 let yy_plus_xx = yy.add(&xx);
204 let yy_minus_xx = yy.sub(&xx);
205
206 let cx = xy_sq.sub(&yy_plus_xx);
207 let cy = yy_plus_xx;
208 let cz = yy_minus_xx;
209 let ct = zz2.sub(&yy_minus_xx);
210
211 Projective {
212 x: cx.mul(&ct),
213 y: cy.mul(&cz),
214 z: cz.mul(&ct),
215 }
216 }
217
218 fn double(&self) -> Completed {
224 let xx = self.x.square();
225 let yy = self.y.square();
226 let zz2 = {
227 let t = self.z.square();
228 t.add(&t)
229 };
230 let xy_sq = self.x.add(&self.y).square();
231 let yy_plus_xx = yy.add(&xx);
232 let yy_minus_xx = yy.sub(&xx);
233 Completed {
234 x: xy_sq.sub(&yy_plus_xx),
235 y: yy_plus_xx,
236 z: yy_minus_xx,
237 t: zz2.sub(&yy_minus_xx),
238 }
239 }
240}
241
242impl Point {
243 pub const IDENTITY: Point = Point {
245 x: Fe::ZERO,
246 y: Fe::ONE,
247 z: Fe::ONE,
248 t: Fe::ZERO,
249 };
250
251 pub fn add(&self, other: &Point) -> Point {
253 let a = self.y.sub(&self.x).mul(&other.y.sub(&other.x));
254 let b = self.y.add(&self.x).mul(&other.y.add(&other.x));
255 let c = self.t.mul(&D2).mul(&other.t);
256 let d = self.z.mul(&other.z);
257 let d = d.add(&d);
258
259 let e = b.sub(&a);
260 let f = d.sub(&c);
261 let g = d.add(&c);
262 let h = b.add(&a);
263
264 Point {
265 x: e.mul(&f),
266 y: g.mul(&h),
267 t: e.mul(&h),
268 z: f.mul(&g),
269 }
270 }
271
272 pub fn double(&self) -> Point {
283 let aa = self.x.square();
284 let bb = self.y.square();
285 let c = self.z.square();
286 let c = c.add(&c);
287 let d = aa.neg();
289 let xy = self.x.add(&self.y);
291 let e = xy.square().sub(&aa).sub(&bb);
292 let g = d.add(&bb);
293 let f = g.sub(&c);
294 let h = d.sub(&bb);
295
296 Point {
297 x: e.mul(&f),
298 y: g.mul(&h),
299 t: e.mul(&h),
300 z: f.mul(&g),
301 }
302 }
303
304 fn to_projective(self) -> Projective {
306 Projective {
307 x: self.x,
308 y: self.y,
309 z: self.z,
310 }
311 }
312
313 fn to_niels(self) -> Niels {
315 Niels {
316 ypx: self.y.add(&self.x),
317 ymx: self.y.sub(&self.x),
318 z: self.z,
319 t2d: self.t.mul(&D2),
320 }
321 }
322
323 fn add_niels(&self, other: &Niels) -> Completed {
330 let pp = self.y.add(&self.x).mul(&other.ypx);
331 let mm = self.y.sub(&self.x).mul(&other.ymx);
332 let tt2d = self.t.mul(&other.t2d);
333 let zz = self.z.mul(&other.z);
334 let zz2 = zz.add(&zz);
335 Completed {
336 x: pp.sub(&mm),
337 y: pp.add(&mm),
338 z: zz2.add(&tt2d),
339 t: zz2.sub(&tt2d),
340 }
341 }
342
343 fn sub_niels(&self, other: &Niels) -> Completed {
349 let pp = self.y.add(&self.x).mul(&other.ymx);
350 let mm = self.y.sub(&self.x).mul(&other.ypx);
351 let tt2d = self.t.mul(&other.t2d);
352 let zz = self.z.mul(&other.z);
353 let zz2 = zz.add(&zz);
354 Completed {
355 x: pp.sub(&mm),
356 y: pp.add(&mm),
357 z: zz2.sub(&tt2d),
358 t: zz2.add(&tt2d),
359 }
360 }
361
362 pub(crate) fn to_affine_niels(self) -> AffineNiels {
368 let z_inv = self.z.invert();
369 let x = self.x.mul(&z_inv);
370 let y = self.y.mul(&z_inv);
371 AffineNiels {
372 ypx: y.add(&x),
373 ymx: y.sub(&x),
374 t2d: x.mul(&y).mul(&D2),
375 }
376 }
377
378 pub(crate) fn add_affine_niels(&self, other: &AffineNiels) -> Completed {
383 let pp = self.y.add(&self.x).mul(&other.ypx);
384 let mm = self.y.sub(&self.x).mul(&other.ymx);
385 let tt2d = self.t.mul(&other.t2d);
386 let zz2 = self.z.add(&self.z);
387 Completed {
388 x: pp.sub(&mm),
389 y: pp.add(&mm),
390 z: zz2.add(&tt2d),
391 t: zz2.sub(&tt2d),
392 }
393 }
394
395 pub(crate) fn sub_affine_niels(&self, other: &AffineNiels) -> Completed {
404 let pp = self.y.add(&self.x).mul(&other.ymx);
405 let mm = self.y.sub(&self.x).mul(&other.ypx);
406 let tt2d = self.t.mul(&other.t2d);
407 let zz2 = self.z.add(&self.z);
408 Completed {
409 x: pp.sub(&mm),
410 y: pp.add(&mm),
411 z: zz2.sub(&tt2d),
412 t: zz2.add(&tt2d),
413 }
414 }
415
416 fn cmov(&mut self, other: &Point, choice: Choice) {
418 Fe::cmov(&mut self.x, &other.x, choice);
419 Fe::cmov(&mut self.y, &other.y, choice);
420 Fe::cmov(&mut self.z, &other.z, choice);
421 Fe::cmov(&mut self.t, &other.t, choice);
422 }
423
424 pub fn mul_scalar(&self, s: &[u8; 32]) -> Point {
430 let mut acc = Point::IDENTITY;
431 for i in (0..256).rev() {
432 acc = acc.double();
433 let sum = acc.add(self);
434 let bit = Choice::from_u8((s[i / 8] >> (i % 8)) & 1);
435 acc.cmov(&sum, bit);
436 }
437 acc
438 }
439
440 fn negate(&self) -> Point {
442 Point {
443 x: self.x.neg(),
444 y: self.y,
445 z: self.z,
446 t: self.t.neg(),
447 }
448 }
449
450 pub fn mul_scalar_vartime(&self, scalar: &[u8; 32]) -> Point {
473 let twice = self.double();
475 let mut odd = [*self; 8];
476 for i in 1..8 {
477 odd[i] = odd[i - 1].add(&twice);
478 }
479
480 let naf = wnaf5(scalar);
481 let mut acc = Point::IDENTITY;
482 for digit in naf.iter().rev() {
483 acc = acc.double();
484 if *digit != 0 {
485 let entry = &odd[(digit.unsigned_abs() as usize) / 2];
487 acc = if *digit > 0 {
488 acc.add(entry)
489 } else {
490 acc.add(&entry.negate())
491 };
492 }
493 }
494 acc
495 }
496
497 fn eq_projective(&self, other: &Point) -> bool {
512 self.x.mul(&other.z).to_bytes() == other.x.mul(&self.z).to_bytes()
513 && self.y.mul(&other.z).to_bytes() == other.y.mul(&self.z).to_bytes()
514 }
515
516 pub fn compress(&self) -> [u8; 32] {
518 let z_inv = self.z.invert();
519 let x = self.x.mul(&z_inv);
520 let y = self.y.mul(&z_inv);
521 let mut out = y.to_bytes();
522 out[31] |= x.is_negative().unwrap_u8() << 7;
524 out
525 }
526
527 pub fn decompress(bytes: &[u8; 32]) -> Option<Point> {
529 let sign = Choice::from_u8(bytes[31] >> 7);
530 let mut y_bytes = *bytes;
531 y_bytes[31] &= 0x7f;
532 let y = Fe::from_bytes(&y_bytes);
533
534 let y2 = y.square();
536 let u = y2.sub(&Fe::ONE);
537 let v = y2.mul(&D).add(&Fe::ONE);
538
539 let v3 = v.square().mul(&v);
541 let v7 = v3.square().mul(&v);
542 let mut x = u.mul(&v3).mul(&u.mul(&v7).pow22523());
543
544 let check = v.mul(&x.square());
545 let correct = check.ct_eq(&u);
546 let flipped = check.ct_eq(&u.neg());
547 if !bool::from(correct.or(flipped)) {
548 return None;
550 }
551 let alt = x.mul(&SQRT_M1);
553 Fe::cmov(&mut x, &alt, flipped.and(correct.not()));
554
555 if bool::from(x.is_zero()) && bool::from(sign) {
557 return None;
558 }
559 let neg = x.neg();
561 let wrong_sign = Choice::from_u8(x.is_negative().unwrap_u8() ^ sign.unwrap_u8());
562 Fe::cmov(&mut x, &neg, wrong_sign);
563
564 Some(Point {
565 x,
566 y,
567 z: Fe::ONE,
568 t: x.mul(&y),
569 })
570 }
571}
572
573fn mul_basepoint(scalar: &[u8; 32]) -> Point {
580 #[cfg(feature = "std")]
581 {
582 basepoint_table::table().mul(scalar)
583 }
584 #[cfg(not(feature = "std"))]
585 {
586 basepoint().mul_scalar(scalar)
587 }
588}
589
590#[cfg(feature = "std")]
614#[doc(hidden)]
615pub fn double_scalar_mul_vartime_for_bench(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
616 double_scalar_mul_vartime(a, k, s)
617}
618
619#[cfg(feature = "std")]
620fn double_scalar_mul_vartime(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
621 let twice = a.double();
623 let mut odd = [*a; 8];
624 for i in 1..8 {
625 odd[i] = odd[i - 1].add(&twice);
626 }
627 let odd_a: [Niels; 8] = core::array::from_fn(|i| odd[i].to_niels());
628 let odd_b = basepoint_table::odd_multiples();
629
630 let naf_a = wnaf(k, 5);
631 let naf_b = wnaf(s, 8);
632
633 let mut i = 257;
636 while i > 0 && naf_a[i] == 0 && naf_b[i] == 0 {
637 i -= 1;
638 }
639
640 let mut acc = Point::IDENTITY.to_projective();
645 loop {
646 if naf_a[i] == 0 && naf_b[i] == 0 {
649 acc = acc.double_projective();
650 if i == 0 {
651 return acc.to_extended_from_projective();
652 }
653 i -= 1;
654 continue;
655 }
656 let mut t = acc.double();
657 if naf_a[i] != 0 {
658 let e = t.to_extended();
659 let n = &odd_a[(naf_a[i].unsigned_abs() as usize) / 2];
660 t = if naf_a[i] > 0 {
661 e.add_niels(n)
662 } else {
663 e.sub_niels(n)
664 };
665 }
666 if naf_b[i] != 0 {
667 let e = t.to_extended();
668 let n = &odd_b[(naf_b[i].unsigned_abs() as usize) / 2];
669 t = if naf_b[i] > 0 {
670 e.add_affine_niels(n)
671 } else {
672 e.sub_affine_niels(n)
673 };
674 }
675 if i == 0 {
676 return t.to_extended();
677 }
678 acc = t.to_projective();
679 i -= 1;
680 }
681}
682
683#[doc(hidden)]
686pub fn mul_basepoint_for_bench(scalar: &[u8; 32]) -> Point {
687 mul_basepoint(scalar)
688}
689
690fn basepoint() -> Point {
691 Point::decompress(&BASEPOINT_COMPRESSED).unwrap_or(Point::IDENTITY)
694}
695
696pub struct Ed25519;
698
699impl Algorithm for Ed25519 {
700 const ID: &'static str = "ed25519";
701 const NAME: &'static str = "Ed25519";
702}
703
704fn expand_seed(seed: &[u8]) -> ([u8; 32], [u8; 32]) {
706 let h = Sha512::digest(seed);
707 let mut a = [0u8; 32];
708 let mut prefix = [0u8; 32];
709 a.copy_from_slice(&h.as_ref()[..32]);
710 prefix.copy_from_slice(&h.as_ref()[32..]);
711 a[0] &= 248;
712 a[31] &= 127;
713 a[31] |= 64;
714 (a, prefix)
715}
716
717fn wnaf5(scalar: &[u8; 32]) -> [i8; 258] {
726 wnaf(scalar, 5)
727}
728
729fn wnaf(scalar: &[u8; 32], w: u32) -> [i8; 258] {
739 debug_assert!((2..=8).contains(&w), "window width out of range");
740 let half = 1i64 << (w - 1);
741 let full = 1i64 << w;
742 let mask = (full - 1) as u64;
743
744 let mut naf = [0i8; 258];
745 let mut k = [0u64; 5];
752 for (i, limb) in k.iter_mut().take(4).enumerate() {
753 let mut b = [0u8; 8];
754 b.copy_from_slice(&scalar[i * 8..i * 8 + 8]);
755 *limb = u64::from_le_bytes(b);
756 }
757
758 let mut i = 0;
759 while k.iter().any(|&x| x != 0) {
760 if k[0] & 1 == 1 {
761 let mut d = (k[0] & mask) as i64;
762 if d >= half {
763 d -= full;
764 }
765 naf[i] = d as i8;
766 if d > 0 {
767 sub_u64(&mut k, d as u64);
768 } else {
769 add_u64(&mut k, d.unsigned_abs());
770 }
771 }
772 shr1(&mut k);
773 i += 1;
774 }
775 naf
776}
777
778fn sub_u64(k: &mut [u64; 5], v: u64) {
780 let (d, mut borrow) = k[0].overflowing_sub(v);
781 k[0] = d;
782 for limb in k.iter_mut().skip(1) {
783 if !borrow {
784 break;
785 }
786 let (d, b) = limb.overflowing_sub(1);
787 *limb = d;
788 borrow = b;
789 }
790}
791
792fn add_u64(k: &mut [u64; 5], v: u64) {
794 let (d, mut carry) = k[0].overflowing_add(v);
795 k[0] = d;
796 for limb in k.iter_mut().skip(1) {
797 if !carry {
798 break;
799 }
800 let (d, c) = limb.overflowing_add(1);
801 *limb = d;
802 carry = c;
803 }
804}
805
806fn shr1(k: &mut [u64; 5]) {
808 for i in 0..4 {
809 k[i] = (k[i] >> 1) | (k[i + 1] << 63);
810 }
811 k[4] >>= 1;
812}
813
814fn hash_to_scalar(parts: &[&[u8]]) -> [u8; 32] {
816 let mut h = Sha512::new();
817 for p in parts {
818 h.update(p);
819 }
820 let digest = h.finalize();
821 let mut wide = [0u8; 64];
822 wide.copy_from_slice(digest.as_ref());
823 scalar::reduce_wide(&wide)
824}
825
826pub struct Ed25519Key {
843 scalar: [u8; 32],
845 prefix: [u8; 32],
847 public: [u8; 32],
849}
850
851impl Drop for Ed25519Key {
852 fn drop(&mut self) {
853 self.scalar.zeroize();
854 self.prefix.zeroize();
855 }
857}
858
859impl Ed25519Key {
860 pub fn from_seed(seed: &[u8]) -> Result<Self> {
862 ensure!(seed.len() == 32, InvalidLength, "ed25519 seed");
863 let (scalar, prefix) = expand_seed(seed);
864 let public = mul_basepoint(&scalar).compress();
865 Ok(Self {
866 scalar,
867 prefix,
868 public,
869 })
870 }
871
872 pub fn public_key(&self) -> &[u8; 32] {
874 &self.public
875 }
876
877 pub fn sign(&self, message: &[u8], signature: &mut [u8]) -> Result<()> {
879 ensure!(
880 signature.len() == 64,
881 InvalidLength,
882 "ed25519 signature buffer"
883 );
884
885 let mut r = hash_to_scalar(&[&self.prefix, message]);
888 let big_r = mul_basepoint(&r).compress();
889
890 let k = hash_to_scalar(&[&big_r, &self.public, message]);
891 let s = scalar::mul_add(&k, &self.scalar, &r);
892
893 signature[..32].copy_from_slice(&big_r);
894 signature[32..].copy_from_slice(&s);
895 r.zeroize();
896 Ok(())
897 }
898}
899
900impl SignatureScheme for Ed25519 {
901 const PRIVATE_KEY_LEN: usize = 32;
902 const PUBLIC_KEY_LEN: usize = 32;
903 const SIGNATURE_LEN: usize = 64;
904
905 fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()> {
906 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
907 ensure!(out.len() == 32, InvalidLength, "ed25519 public key buffer");
908 let (mut a, mut prefix) = expand_seed(private_key);
909 out.copy_from_slice(&mul_basepoint(&a).compress());
910 a.zeroize();
911 prefix.zeroize();
912 Ok(())
913 }
914
915 fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()> {
916 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
917 ensure!(
918 signature.len() == 64,
919 InvalidLength,
920 "ed25519 signature buffer"
921 );
922
923 Ed25519Key::from_seed(private_key)?.sign(message, signature)
927 }
928
929 fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()> {
930 Ed25519VerifyKey::from_bytes(public_key)?.verify(message, signature)
934 }
935}
936
937pub struct Ed25519VerifyKey {
951 compressed: [u8; 32],
953 neg_a: Point,
955}
956
957impl Ed25519VerifyKey {
958 pub fn from_bytes(public_key: &[u8]) -> Result<Self> {
960 ensure!(public_key.len() == 32, InvalidLength, "ed25519 public key");
961 let mut compressed = [0u8; 32];
962 compressed.copy_from_slice(public_key);
963 let a = Point::decompress(&compressed).ok_or(ic_core::err!(
964 MalformedEncoding,
965 "ed25519 public key is not on the curve"
966 ))?;
967 Ok(Self {
968 compressed,
969 neg_a: a.negate(),
970 })
971 }
972
973 pub fn as_bytes(&self) -> &[u8; 32] {
975 &self.compressed
976 }
977
978 pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> {
980 ensure!(signature.len() == 64, InvalidLength, "ed25519 signature");
981
982 let mut big_r = [0u8; 32];
983 big_r.copy_from_slice(&signature[..32]);
984 let mut s = [0u8; 32];
985 s.copy_from_slice(&signature[32..]);
986
987 ensure!(
991 scalar::is_canonical(&s),
992 MalformedEncoding,
993 "ed25519 signature S is not reduced"
994 );
995
996 let r_point = Point::decompress(&big_r).ok_or(ic_core::err!(
997 MalformedEncoding,
998 "ed25519 signature R is not on the curve"
999 ))?;
1000
1001 let k = hash_to_scalar(&[&big_r, &self.compressed, message]);
1002
1003 #[cfg(feature = "std")]
1007 let lhs = double_scalar_mul_vartime(&self.neg_a, &k, &s);
1008 #[cfg(not(feature = "std"))]
1009 let lhs = mul_basepoint(&s).add(&self.neg_a.mul_scalar_vartime(&k));
1010
1011 if lhs.eq_projective(&r_point) {
1012 Ok(())
1013 } else {
1014 Err(ic_core::err!(AuthenticationFailed, "ed25519"))
1015 }
1016 }
1017}
1018
1019impl SelfTest for Ed25519 {
1020 fn self_test() -> Result<()> {
1021 let mut seed = [0u8; 32];
1023 ic_core::codec::hex_decode(
1024 b"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1025 &mut seed,
1026 )?;
1027 let mut want_pk = [0u8; 32];
1028 ic_core::codec::hex_decode(
1029 b"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1030 &mut want_pk,
1031 )?;
1032 let mut want_sig = [0u8; 64];
1033 ic_core::codec::hex_decode(
1034 b"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1035 &mut want_sig,
1036 )?;
1037
1038 let mut pk = [0u8; 32];
1039 <Self as SignatureScheme>::public_key(&seed, &mut pk)?;
1040 ensure!(
1041 ic_core::ct::verify(&want_pk, &pk),
1042 SelfTestFailed,
1043 "ed25519"
1044 );
1045
1046 let mut sig = [0u8; 64];
1047 <Self as SignatureScheme>::sign(&seed, b"", &mut sig)?;
1048 ensure!(
1049 ic_core::ct::verify(&want_sig, &sig),
1050 SelfTestFailed,
1051 "ed25519"
1052 );
1053
1054 <Self as SignatureScheme>::verify(&pk, b"", &sig)?;
1055
1056 sig[0] ^= 1;
1058 ensure!(
1059 <Self as SignatureScheme>::verify(&pk, b"", &sig).is_err(),
1060 SelfTestFailed,
1061 "ed25519"
1062 );
1063 Ok(())
1064 }
1065}
1066
1067#[cfg(test)]
1068mod tests {
1069 use super::*;
1070 use ic_core::codec::{hex, unhex};
1071
1072 #[test]
1073 fn curve_constants_are_correct() {
1074 let d = Fe::from_u64(121_665)
1076 .neg()
1077 .mul(&Fe::from_u64(121_666).invert());
1078 assert_eq!(hex(&D.to_bytes()), hex(&d.to_bytes()), "d");
1079 assert_eq!(hex(&D2.to_bytes()), hex(&d.add(&d).to_bytes()), "2d");
1080 assert_eq!(
1082 hex(&SQRT_M1.square().to_bytes()),
1083 hex(&Fe::ONE.neg().to_bytes()),
1084 "sqrt(-1)"
1085 );
1086 }
1087
1088 #[test]
1089 fn basepoint_has_the_expected_coordinates() {
1090 let b = basepoint();
1091 let expected_y = Fe::from_u64(4).mul(&Fe::from_u64(5).invert());
1093 let z_inv = b.z.invert();
1094 assert_eq!(
1095 hex(&b.y.mul(&z_inv).to_bytes()),
1096 hex(&expected_y.to_bytes())
1097 );
1098 assert_eq!(hex(&b.compress()), hex(&BASEPOINT_COMPRESSED));
1099 }
1100
1101 #[test]
1108 fn doubling_agrees_with_adding_a_point_to_itself() {
1109 let mut p = basepoint();
1110 let mut checked = 0;
1111 for _ in 0..16 {
1112 assert_eq!(
1113 p.double().compress(),
1114 p.add(&p).compress(),
1115 "dedicated doubling and self-addition differ"
1116 );
1117 p = p.add(&basepoint());
1118 checked += 1;
1119 }
1120 assert_eq!(checked, 16, "the comparison did not run");
1121
1122 assert_eq!(
1125 Point::IDENTITY.double().compress(),
1126 Point::IDENTITY.compress()
1127 );
1128 }
1129
1130 #[test]
1138 fn projective_equality_agrees_with_compressed_equality() {
1139 let b = basepoint();
1140 let mut points = std::vec![Point::IDENTITY, b];
1141 let mut p = b;
1142 for _ in 0..6 {
1143 p = p.double();
1144 points.push(p);
1145 }
1146
1147 let mut checked = 0;
1148 for (i, a) in points.iter().enumerate() {
1149 for (j, c) in points.iter().enumerate() {
1150 let projective = a.eq_projective(c);
1151 let compressed = a.compress() == c.compress();
1152 assert_eq!(
1153 projective, compressed,
1154 "projective and compressed equality differ for {i} vs {j}"
1155 );
1156 checked += 1;
1157 }
1158 }
1159 assert_eq!(checked, 64, "the comparison did not run");
1160
1161 let scaled = b.add(&Point::IDENTITY);
1164 assert!(b.eq_projective(&scaled), "equal points with different Z");
1165 assert_eq!(b.compress(), scaled.compress());
1166 }
1167
1168 #[test]
1169 fn group_law_is_consistent() {
1170 let b = basepoint();
1171 assert_eq!(hex(&b.add(&Point::IDENTITY).compress()), hex(&b.compress()));
1173 let mut two = [0u8; 32];
1175 two[0] = 2;
1176 assert_eq!(
1177 hex(&b.double().compress()),
1178 hex(&b.mul_scalar(&two).compress())
1179 );
1180 let mut three = [0u8; 32];
1182 three[0] = 3;
1183 assert_eq!(
1184 hex(&b.double().add(&b).compress()),
1185 hex(&b.mul_scalar(&three).compress())
1186 );
1187 }
1188
1189 #[test]
1190 fn order_of_the_basepoint_is_l() {
1191 assert_eq!(
1193 hex(&basepoint().mul_scalar(&scalar::L).compress()),
1194 hex(&Point::IDENTITY.compress())
1195 );
1196 }
1197
1198 #[test]
1199 fn compression_roundtrips() {
1200 let b = basepoint();
1201 for k in [1u8, 2, 3, 47, 200] {
1202 let mut s = [0u8; 32];
1203 s[0] = k;
1204 let p = b.mul_scalar(&s);
1205 let c = p.compress();
1206 let d = Point::decompress(&c).expect("valid point");
1207 assert_eq!(hex(&d.compress()), hex(&c), "k = {k}");
1208 }
1209 }
1210
1211 #[test]
1212 fn decompression_rejects_non_curve_points() {
1213 let mut bad = [0u8; 32];
1215 bad[0] = 2;
1216 assert!(Point::decompress(&bad).is_none());
1217 }
1218
1219 #[test]
1221 fn rfc8032_vectors() {
1222 let cases: [(&str, &str, &str, &str); 3] = [
1223 (
1224 "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1225 "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1226 "",
1227 "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1228 ),
1229 (
1230 "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
1231 "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
1232 "72",
1233 "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
1234 ),
1235 (
1236 "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
1237 "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
1238 "af82",
1239 "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
1240 ),
1241 ];
1242
1243 for (seed_hex, pk_hex, msg_hex, sig_hex) in cases {
1244 let seed = unhex(seed_hex).unwrap();
1245 let msg = unhex(msg_hex).unwrap();
1246
1247 let mut pk = [0u8; 32];
1248 Ed25519::public_key(&seed, &mut pk).unwrap();
1249 assert_eq!(hex(&pk), pk_hex, "public key for {seed_hex}");
1250
1251 let mut sig = [0u8; 64];
1252 Ed25519::sign(&seed, &msg, &mut sig).unwrap();
1253 assert_eq!(hex(&sig), sig_hex, "signature for {seed_hex}");
1254
1255 Ed25519::verify(&pk, &msg, &sig).unwrap();
1256 }
1257 }
1258
1259 #[test]
1260 fn verification_rejects_tampering() {
1261 let seed = [0x42u8; 32];
1262 let mut pk = [0u8; 32];
1263 Ed25519::public_key(&seed, &mut pk).unwrap();
1264 let mut sig = [0u8; 64];
1265 Ed25519::sign(&seed, b"authentic", &mut sig).unwrap();
1266 Ed25519::verify(&pk, b"authentic", &sig).unwrap();
1267
1268 assert!(Ed25519::verify(&pk, b"forged", &sig).is_err());
1270 let mut bad = sig;
1272 bad[0] ^= 1;
1273 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1274 let mut bad = sig;
1276 bad[40] ^= 1;
1277 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1278 let mut other_pk = [0u8; 32];
1280 Ed25519::public_key(&[0x43u8; 32], &mut other_pk).unwrap();
1281 assert!(Ed25519::verify(&other_pk, b"authentic", &sig).is_err());
1282 }
1283
1284 #[test]
1287 fn rejects_non_canonical_s() {
1288 let seed = [0x42u8; 32];
1289 let mut pk = [0u8; 32];
1290 Ed25519::public_key(&seed, &mut pk).unwrap();
1291 let mut sig = [0u8; 64];
1292 Ed25519::sign(&seed, b"msg", &mut sig).unwrap();
1293
1294 let mut carry = 0u16;
1297 for i in 0..32 {
1298 let t = sig[32 + i] as u16 + scalar::L[i] as u16 + carry;
1299 sig[32 + i] = t as u8;
1300 carry = t >> 8;
1301 }
1302 assert!(Ed25519::verify(&pk, b"msg", &sig).is_err());
1303 }
1304
1305 #[test]
1312 fn the_cached_key_signs_identically_to_the_seed() {
1313 let mut checked = 0;
1314 for seed in [[0x11u8; 32], [0x9du8; 32], [0xffu8; 32]] {
1315 for message in [&b""[..], &b"x"[..], &b"a longer message to sign"[..]] {
1316 let mut from_seed = [0u8; 64];
1317 Ed25519::sign(&seed, message, &mut from_seed).unwrap();
1318
1319 let key = Ed25519Key::from_seed(&seed).unwrap();
1320 let mut from_key = [0u8; 64];
1321 key.sign(message, &mut from_key).unwrap();
1322
1323 assert_eq!(from_seed, from_key, "the two signing paths diverged");
1324
1325 let mut derived = [0u8; 32];
1327 Ed25519::public_key(&seed, &mut derived).unwrap();
1328 assert_eq!(&derived, key.public_key());
1329
1330 Ed25519::verify(&derived, message, &from_key).unwrap();
1332 checked += 1;
1333 }
1334 }
1335 assert_eq!(checked, 9, "the comparison did not run");
1336 }
1337
1338 #[test]
1346 fn the_vartime_multiplication_agrees_with_the_ladder() {
1347 let p = basepoint();
1348
1349 let mut one = [0u8; 32];
1350 one[0] = 1;
1351 let mut two = [0u8; 32];
1352 two[0] = 2;
1353 let mut top = [0xffu8; 32];
1354 top[31] = 0x7f;
1355
1356 let mut checked = 0;
1357 for scalar in [
1358 [0u8; 32],
1359 one,
1360 two,
1361 [0xffu8; 32],
1362 [0x55u8; 32],
1363 [0xaau8; 32],
1364 top,
1365 [0x9du8; 32],
1366 ] {
1367 let fast = p.mul_scalar_vartime(&scalar);
1368 let slow = p.mul_scalar(&scalar);
1369 assert_eq!(
1370 fast.compress(),
1371 slow.compress(),
1372 "vartime and ladder differ for {scalar:02x?}"
1373 );
1374 checked += 1;
1375 }
1376 assert_eq!(checked, 8, "the comparison did not run");
1377 }
1378
1379 #[test]
1381 fn the_wnaf_digits_are_odd_sparse_and_faithful() {
1382 for scalar in [[1u8; 32], [0x9du8; 32], [0xffu8; 32], [0x55u8; 32]] {
1383 let naf = wnaf5(&scalar);
1384
1385 let mut previous_nonzero: Option<usize> = None;
1386 for (i, d) in naf.iter().enumerate() {
1387 if *d == 0 {
1388 continue;
1389 }
1390 assert!(d % 2 != 0, "digit {d} at {i} is not odd");
1391 assert!((-15..=15).contains(d), "digit {d} at {i} is out of range");
1392 if let Some(j) = previous_nonzero {
1393 assert!(i - j >= 5, "digits at {j} and {i} are adjacent");
1394 }
1395 previous_nonzero = Some(i);
1396 }
1397
1398 const M: u128 = 1_000_000_007;
1401 let mut from_digits = 0u128;
1402 let mut power = 1u128;
1403 for d in naf {
1404 let term = ((d as i128).rem_euclid(M as i128)) as u128;
1405 from_digits = (from_digits + term * power) % M;
1406 power = power * 2 % M;
1407 }
1408 let mut from_bytes = 0u128;
1409 let mut p = 1u128;
1410 for byte in scalar {
1411 from_bytes = (from_bytes + (byte as u128) * p) % M;
1412 p = p * 256 % M;
1413 }
1414 assert_eq!(from_digits, from_bytes, "recoding changed the value");
1415 }
1416 }
1417
1418 #[test]
1419 fn signing_is_deterministic() {
1420 let seed = [0x7fu8; 32];
1421 let mut a = [0u8; 64];
1422 let mut b = [0u8; 64];
1423 Ed25519::sign(&seed, b"same input", &mut a).unwrap();
1424 Ed25519::sign(&seed, b"same input", &mut b).unwrap();
1425 assert_eq!(a, b);
1426 }
1427
1428 #[test]
1429 fn rejects_wrong_lengths() {
1430 let mut out = [0u8; 32];
1431 assert!(Ed25519::public_key(&[0u8; 31], &mut out).is_err());
1432 assert!(Ed25519::sign(&[0u8; 32], b"", &mut [0u8; 63]).is_err());
1433 assert!(Ed25519::verify(&[0u8; 32], b"", &[0u8; 63]).is_err());
1434 }
1435
1436 #[test]
1437 fn self_test_passes() {
1438 Ed25519::self_test().unwrap();
1439 }
1440
1441 fn sample_points(n: usize) -> Vec<Point> {
1443 let mut out = Vec::new();
1444 let mut p = basepoint();
1445 for _ in 0..n {
1446 out.push(p);
1447 p = p.double().add(&basepoint());
1448 }
1449 out
1450 }
1451
1452 #[test]
1462 fn the_completed_doubling_agrees_with_the_extended_one() {
1463 for p in sample_points(40) {
1464 let want = p.double();
1465 let got = p.to_projective().double().to_extended();
1466 assert!(got.eq_projective(&want), "doubling disagrees");
1467 let chained = p.to_projective().double().to_projective().double();
1470 let twice = p.double().double();
1471 assert!(chained.to_extended().eq_projective(&twice), "two doublings");
1472 }
1473 }
1474
1475 #[test]
1477 fn niels_addition_agrees_with_the_general_one() {
1478 let pts = sample_points(20);
1479 for p in &pts {
1480 for q in &pts {
1481 let want = p.add(q);
1482 let got = p.add_niels(&q.to_niels()).to_extended();
1483 assert!(got.eq_projective(&want), "add_niels disagrees");
1484
1485 let want_sub = p.add(&q.negate());
1486 let got_sub = p.sub_niels(&q.to_niels()).to_extended();
1487 assert!(got_sub.eq_projective(&want_sub), "sub_niels disagrees");
1488 }
1489 }
1490 }
1491
1492 #[test]
1500 fn affine_niels_addition_agrees_with_the_general_one() {
1501 let pts = sample_points(20);
1502 for p in &pts {
1503 for q in &pts {
1504 let want = p.add(q);
1505 let got = p.add_affine_niels(&q.to_affine_niels()).to_extended();
1506 assert!(got.eq_projective(&want), "add_affine_niels disagrees");
1507
1508 let mut n = q.to_affine_niels();
1510 n.conditional_negate(ic_core::ct::Choice::from_u8(1));
1511 let want_neg = p.add(&q.negate());
1512 let got_neg = p.add_affine_niels(&n).to_extended();
1513 assert!(got_neg.eq_projective(&want_neg), "negated form disagrees");
1514 }
1515 }
1516 }
1517
1518 #[test]
1525 #[ignore = "diagnostic, not a test"]
1526 fn where_verify_spends_its_time() {
1527 use std::time::Instant;
1528
1529 let seed = [7u8; 32];
1530 let key = Ed25519Key::from_seed(&seed).unwrap();
1531 let msg = b"benchmark message";
1532 let mut sig = [0u8; 64];
1533 key.sign(msg, &mut sig).unwrap();
1534 let pk = *key.public_key();
1535
1536 let mut big_r = [0u8; 32];
1537 big_r.copy_from_slice(&sig[..32]);
1538 let mut s_sc = [0u8; 32];
1539 s_sc.copy_from_slice(&sig[32..]);
1540
1541 let n = 2000;
1542 let time = |label: &str, f: &mut dyn FnMut()| {
1543 let mut best = f64::INFINITY;
1544 for _ in 0..5 {
1545 let t = Instant::now();
1546 for _ in 0..n {
1547 f();
1548 }
1549 let e = t.elapsed().as_secs_f64() / n as f64 * 1e6;
1550 if e < best {
1551 best = e;
1552 }
1553 }
1554 println!(" {label:<34} {best:>9.2} us");
1555 best
1556 };
1557
1558 let a_point = Point::decompress(&pk).unwrap();
1559 let k = hash_to_scalar(&[&big_r, &pk, msg]);
1560
1561 println!(
1562 "
1563ed25519 verify, cost breakdown:"
1564 );
1565 let d = time("decompress (x2 per verify)", &mut || {
1566 core::hint::black_box(Point::decompress(&pk));
1567 });
1568 let h = time("hash_to_scalar", &mut || {
1569 core::hint::black_box(hash_to_scalar(&[&big_r, &pk, msg]));
1570 });
1571 let b = time("mul_basepoint (const time)", &mut || {
1572 core::hint::black_box(mul_basepoint(&s_sc));
1573 });
1574 let v = time("double_scalar_mul_vartime", &mut || {
1575 core::hint::black_box(double_scalar_mul_vartime(&a_point.negate(), &k, &s_sc));
1576 });
1577 time(" of which: wnaf(k,5)+wnaf(s,8)", &mut || {
1578 core::hint::black_box(wnaf(&k, 5));
1579 core::hint::black_box(wnaf(&s_sc, 8));
1580 });
1581 time(" of which: odd_a table build", &mut || {
1582 let twice = a_point.double();
1583 let mut odd = [a_point; 8];
1584 for i in 1..8 {
1585 odd[i] = odd[i - 1].add(&twice);
1586 }
1587 let t: [Niels; 8] = core::array::from_fn(|i| odd[i].to_niels());
1588 core::hint::black_box(t);
1589 });
1590 time(" of which: 255 doublings", &mut || {
1591 let mut p = a_point;
1592 for _ in 0..255 {
1593 p = p.double();
1594 }
1595 core::hint::black_box(p);
1596 });
1597 time(" of which: 79 additions", &mut || {
1598 let mut p = a_point;
1599 for _ in 0..79 {
1600 p = p.add(&a_point);
1601 }
1602 core::hint::black_box(p);
1603 });
1604 time("compress (one inversion)", &mut || {
1605 core::hint::black_box(a_point.compress());
1606 });
1607 println!(
1608 " {:<34} {:>9.2} us",
1609 "-- accounted for",
1610 2.0 * d + h + b + v
1611 );
1612
1613 println!(
1615 "
1616field and point primitives, nanoseconds:"
1617 );
1618 let nn = 200_000;
1619 let ns = |label: &str, f: &mut dyn FnMut()| {
1620 let mut best = f64::INFINITY;
1621 for _ in 0..5 {
1622 let t = Instant::now();
1623 for _ in 0..nn {
1624 f();
1625 }
1626 let e = t.elapsed().as_secs_f64() / nn as f64 * 1e9;
1627 if e < best {
1628 best = e;
1629 }
1630 }
1631 println!(" {label:<34} {best:>9.2} ns");
1632 };
1633 let fx = a_point.x;
1634 let fy = a_point.y;
1635 ns("Fe::mul", &mut || {
1636 core::hint::black_box(core::hint::black_box(&fx).mul(core::hint::black_box(&fy)));
1637 });
1638 ns("Fe::square", &mut || {
1639 core::hint::black_box(core::hint::black_box(&fx).square());
1640 });
1641 ns("Fe::add", &mut || {
1642 core::hint::black_box(core::hint::black_box(&fx).add(core::hint::black_box(&fy)));
1643 });
1644 ns("Fe::sub", &mut || {
1645 core::hint::black_box(core::hint::black_box(&fx).sub(core::hint::black_box(&fy)));
1646 });
1647 ns("Fe::neg", &mut || {
1648 core::hint::black_box(core::hint::black_box(&fx).neg());
1649 });
1650 let proj = a_point.to_projective();
1651 let comp = proj.double();
1652 let an = a_point.to_affine_niels();
1653 ns("Projective::double (4S)", &mut || {
1654 core::hint::black_box(core::hint::black_box(&proj).double());
1655 });
1656 ns("Projective::double_projective", &mut || {
1657 core::hint::black_box(core::hint::black_box(proj).double_projective());
1658 });
1659 ns("Completed::to_projective (3M)", &mut || {
1660 core::hint::black_box(core::hint::black_box(&comp).to_projective());
1661 });
1662 ns("Completed::to_extended (4M)", &mut || {
1663 core::hint::black_box(core::hint::black_box(&comp).to_extended());
1664 });
1665 ns("Point::add_affine_niels (3M)", &mut || {
1666 core::hint::black_box(
1667 core::hint::black_box(&a_point).add_affine_niels(core::hint::black_box(&an)),
1668 );
1669 });
1670 ns("Point::double", &mut || {
1671 core::hint::black_box(core::hint::black_box(&a_point).double());
1672 });
1673 ns("Point::add", &mut || {
1674 core::hint::black_box(
1675 core::hint::black_box(&a_point).add(core::hint::black_box(&a_point)),
1676 );
1677 });
1678 }
1679
1680 #[test]
1682 #[ignore = "diagnostic, not a test"]
1683 fn count_the_point_operations() {
1684 let mut doublings = 0usize;
1685 let mut adds_a = 0usize;
1686 let mut adds_b = 0usize;
1687 let mut state = 0x1234_5678_9abc_def0u64;
1688 let trials = 200;
1689 for _ in 0..trials {
1690 let mut kb = [0u8; 32];
1691 for c in kb.chunks_exact_mut(8) {
1692 state ^= state >> 12;
1693 state ^= state << 25;
1694 state ^= state >> 27;
1695 c.copy_from_slice(&state.wrapping_mul(0x2545_f491_4f6c_dd1d).to_le_bytes());
1696 }
1697 kb[31] &= 0x0f;
1698 let na = wnaf(&kb, 5);
1699 let nb = wnaf(&kb, 8);
1700 let mut i = 257;
1701 while i > 0 && na[i] == 0 && nb[i] == 0 {
1702 i -= 1;
1703 }
1704 doublings += i + 1;
1705 adds_a += na.iter().filter(|d| **d != 0).count();
1706 adds_b += nb.iter().filter(|d| **d != 0).count();
1707 }
1708 let d = doublings as f64 / trials as f64;
1709 let aa = adds_a as f64 / trials as f64;
1710 let ab = adds_b as f64 / trials as f64;
1711 println!(
1712 "
1713 per double-scalar multiplication, averaged over {trials} scalars:"
1714 );
1715 println!(" doublings {d:>8.1}");
1716 println!(" additions, w=5 table (A) {aa:>8.1}");
1717 println!(" additions, w=8 table (B) {ab:>8.1}");
1718 println!(" additions, building A {:>8.1}", 8.0);
1719 println!(" ---");
1720 println!(" total additions {:>8.1}", aa + ab + 8.0);
1721 println!(
1722 " field muls, at 4M+4S per doubling and 9M per addition: {:>6.0}",
1723 d * 8.0 + (aa + ab + 8.0) * 9.0
1724 );
1725 println!(
1726 " the same at dalek's 3M+4S and 7M: {:>6.0}",
1727 d * 7.0 + (aa + ab + 8.0) * 7.0
1728 );
1729 }
1730}