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
62impl Point {
63 pub const IDENTITY: Point = Point {
65 x: Fe::ZERO,
66 y: Fe::ONE,
67 z: Fe::ONE,
68 t: Fe::ZERO,
69 };
70
71 pub fn add(&self, other: &Point) -> Point {
73 let a = self.y.sub(&self.x).mul(&other.y.sub(&other.x));
74 let b = self.y.add(&self.x).mul(&other.y.add(&other.x));
75 let c = self.t.mul(&D2).mul(&other.t);
76 let d = self.z.mul(&other.z);
77 let d = d.add(&d);
78
79 let e = b.sub(&a);
80 let f = d.sub(&c);
81 let g = d.add(&c);
82 let h = b.add(&a);
83
84 Point {
85 x: e.mul(&f),
86 y: g.mul(&h),
87 t: e.mul(&h),
88 z: f.mul(&g),
89 }
90 }
91
92 pub fn double(&self) -> Point {
103 let aa = self.x.square();
104 let bb = self.y.square();
105 let c = self.z.square();
106 let c = c.add(&c);
107 let d = aa.neg();
109 let xy = self.x.add(&self.y);
111 let e = xy.square().sub(&aa).sub(&bb);
112 let g = d.add(&bb);
113 let f = g.sub(&c);
114 let h = d.sub(&bb);
115
116 Point {
117 x: e.mul(&f),
118 y: g.mul(&h),
119 t: e.mul(&h),
120 z: f.mul(&g),
121 }
122 }
123
124 fn conditional_negate(&mut self, choice: Choice) {
131 let nx = self.x.neg();
132 let nt = self.t.neg();
133 Fe::cmov(&mut self.x, &nx, choice);
134 Fe::cmov(&mut self.t, &nt, choice);
135 }
136
137 fn cmov(&mut self, other: &Point, choice: Choice) {
138 Fe::cmov(&mut self.x, &other.x, choice);
139 Fe::cmov(&mut self.y, &other.y, choice);
140 Fe::cmov(&mut self.z, &other.z, choice);
141 Fe::cmov(&mut self.t, &other.t, choice);
142 }
143
144 pub fn mul_scalar(&self, s: &[u8; 32]) -> Point {
150 let mut acc = Point::IDENTITY;
151 for i in (0..256).rev() {
152 acc = acc.double();
153 let sum = acc.add(self);
154 let bit = Choice::from_u8((s[i / 8] >> (i % 8)) & 1);
155 acc.cmov(&sum, bit);
156 }
157 acc
158 }
159
160 fn negate(&self) -> Point {
162 Point {
163 x: self.x.neg(),
164 y: self.y,
165 z: self.z,
166 t: self.t.neg(),
167 }
168 }
169
170 pub fn mul_scalar_vartime(&self, scalar: &[u8; 32]) -> Point {
193 let twice = self.double();
195 let mut odd = [*self; 8];
196 for i in 1..8 {
197 odd[i] = odd[i - 1].add(&twice);
198 }
199
200 let naf = wnaf5(scalar);
201 let mut acc = Point::IDENTITY;
202 for digit in naf.iter().rev() {
203 acc = acc.double();
204 if *digit != 0 {
205 let entry = &odd[(digit.unsigned_abs() as usize) / 2];
207 acc = if *digit > 0 {
208 acc.add(entry)
209 } else {
210 acc.add(&entry.negate())
211 };
212 }
213 }
214 acc
215 }
216
217 fn eq_projective(&self, other: &Point) -> bool {
232 self.x.mul(&other.z).to_bytes() == other.x.mul(&self.z).to_bytes()
233 && self.y.mul(&other.z).to_bytes() == other.y.mul(&self.z).to_bytes()
234 }
235
236 pub fn compress(&self) -> [u8; 32] {
238 let z_inv = self.z.invert();
239 let x = self.x.mul(&z_inv);
240 let y = self.y.mul(&z_inv);
241 let mut out = y.to_bytes();
242 out[31] |= x.is_negative().unwrap_u8() << 7;
244 out
245 }
246
247 pub fn decompress(bytes: &[u8; 32]) -> Option<Point> {
249 let sign = Choice::from_u8(bytes[31] >> 7);
250 let mut y_bytes = *bytes;
251 y_bytes[31] &= 0x7f;
252 let y = Fe::from_bytes(&y_bytes);
253
254 let y2 = y.square();
256 let u = y2.sub(&Fe::ONE);
257 let v = y2.mul(&D).add(&Fe::ONE);
258
259 let v3 = v.square().mul(&v);
261 let v7 = v3.square().mul(&v);
262 let mut x = u.mul(&v3).mul(&u.mul(&v7).pow22523());
263
264 let check = v.mul(&x.square());
265 let correct = check.ct_eq(&u);
266 let flipped = check.ct_eq(&u.neg());
267 if !bool::from(correct.or(flipped)) {
268 return None;
270 }
271 let alt = x.mul(&SQRT_M1);
273 Fe::cmov(&mut x, &alt, flipped.and(correct.not()));
274
275 if bool::from(x.is_zero()) && bool::from(sign) {
277 return None;
278 }
279 let neg = x.neg();
281 let wrong_sign = Choice::from_u8(x.is_negative().unwrap_u8() ^ sign.unwrap_u8());
282 Fe::cmov(&mut x, &neg, wrong_sign);
283
284 Some(Point {
285 x,
286 y,
287 z: Fe::ONE,
288 t: x.mul(&y),
289 })
290 }
291}
292
293fn mul_basepoint(scalar: &[u8; 32]) -> Point {
300 #[cfg(feature = "std")]
301 {
302 basepoint_table::table().mul(scalar)
303 }
304 #[cfg(not(feature = "std"))]
305 {
306 basepoint().mul_scalar(scalar)
307 }
308}
309
310#[cfg(feature = "std")]
328fn double_scalar_mul_vartime(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
329 let twice = a.double();
331 let mut odd_a = [*a; 8];
332 for i in 1..8 {
333 odd_a[i] = odd_a[i - 1].add(&twice);
334 }
335 let odd_b = basepoint_table::odd_multiples();
336
337 let naf_a = wnaf(k, 5);
338 let naf_b = wnaf(s, 8);
339
340 let mut i = 257;
343 while i > 0 && naf_a[i] == 0 && naf_b[i] == 0 {
344 i -= 1;
345 }
346
347 let mut acc = Point::IDENTITY;
348 loop {
349 acc = acc.double();
350 if naf_a[i] != 0 {
351 let e = &odd_a[(naf_a[i].unsigned_abs() as usize) / 2];
352 acc = if naf_a[i] > 0 {
353 acc.add(e)
354 } else {
355 acc.add(&e.negate())
356 };
357 }
358 if naf_b[i] != 0 {
359 let e = &odd_b[(naf_b[i].unsigned_abs() as usize) / 2];
360 acc = if naf_b[i] > 0 {
361 acc.add(e)
362 } else {
363 acc.add(&e.negate())
364 };
365 }
366 if i == 0 {
367 break;
368 }
369 i -= 1;
370 }
371 acc
372}
373
374fn basepoint() -> Point {
375 Point::decompress(&BASEPOINT_COMPRESSED).unwrap_or(Point::IDENTITY)
378}
379
380pub struct Ed25519;
382
383impl Algorithm for Ed25519 {
384 const ID: &'static str = "ed25519";
385 const NAME: &'static str = "Ed25519";
386}
387
388fn expand_seed(seed: &[u8]) -> ([u8; 32], [u8; 32]) {
390 let h = Sha512::digest(seed);
391 let mut a = [0u8; 32];
392 let mut prefix = [0u8; 32];
393 a.copy_from_slice(&h.as_ref()[..32]);
394 prefix.copy_from_slice(&h.as_ref()[32..]);
395 a[0] &= 248;
396 a[31] &= 127;
397 a[31] |= 64;
398 (a, prefix)
399}
400
401fn wnaf5(scalar: &[u8; 32]) -> [i8; 258] {
410 wnaf(scalar, 5)
411}
412
413fn wnaf(scalar: &[u8; 32], w: u32) -> [i8; 258] {
423 debug_assert!((2..=8).contains(&w), "window width out of range");
424 let half = 1i64 << (w - 1);
425 let full = 1i64 << w;
426 let mask = (full - 1) as u64;
427
428 let mut naf = [0i8; 258];
429 let mut k = [0u64; 5];
436 for (i, limb) in k.iter_mut().take(4).enumerate() {
437 let mut b = [0u8; 8];
438 b.copy_from_slice(&scalar[i * 8..i * 8 + 8]);
439 *limb = u64::from_le_bytes(b);
440 }
441
442 let mut i = 0;
443 while k.iter().any(|&x| x != 0) {
444 if k[0] & 1 == 1 {
445 let mut d = (k[0] & mask) as i64;
446 if d >= half {
447 d -= full;
448 }
449 naf[i] = d as i8;
450 if d > 0 {
451 sub_u64(&mut k, d as u64);
452 } else {
453 add_u64(&mut k, d.unsigned_abs());
454 }
455 }
456 shr1(&mut k);
457 i += 1;
458 }
459 naf
460}
461
462fn sub_u64(k: &mut [u64; 5], v: u64) {
464 let (d, mut borrow) = k[0].overflowing_sub(v);
465 k[0] = d;
466 for limb in k.iter_mut().skip(1) {
467 if !borrow {
468 break;
469 }
470 let (d, b) = limb.overflowing_sub(1);
471 *limb = d;
472 borrow = b;
473 }
474}
475
476fn add_u64(k: &mut [u64; 5], v: u64) {
478 let (d, mut carry) = k[0].overflowing_add(v);
479 k[0] = d;
480 for limb in k.iter_mut().skip(1) {
481 if !carry {
482 break;
483 }
484 let (d, c) = limb.overflowing_add(1);
485 *limb = d;
486 carry = c;
487 }
488}
489
490fn shr1(k: &mut [u64; 5]) {
492 for i in 0..4 {
493 k[i] = (k[i] >> 1) | (k[i + 1] << 63);
494 }
495 k[4] >>= 1;
496}
497
498fn hash_to_scalar(parts: &[&[u8]]) -> [u8; 32] {
500 let mut h = Sha512::new();
501 for p in parts {
502 h.update(p);
503 }
504 let digest = h.finalize();
505 let mut wide = [0u8; 64];
506 wide.copy_from_slice(digest.as_ref());
507 scalar::reduce_wide(&wide)
508}
509
510pub struct Ed25519Key {
527 scalar: [u8; 32],
529 prefix: [u8; 32],
531 public: [u8; 32],
533}
534
535impl Drop for Ed25519Key {
536 fn drop(&mut self) {
537 self.scalar.zeroize();
538 self.prefix.zeroize();
539 }
541}
542
543impl Ed25519Key {
544 pub fn from_seed(seed: &[u8]) -> Result<Self> {
546 ensure!(seed.len() == 32, InvalidLength, "ed25519 seed");
547 let (scalar, prefix) = expand_seed(seed);
548 let public = mul_basepoint(&scalar).compress();
549 Ok(Self {
550 scalar,
551 prefix,
552 public,
553 })
554 }
555
556 pub fn public_key(&self) -> &[u8; 32] {
558 &self.public
559 }
560
561 pub fn sign(&self, message: &[u8], signature: &mut [u8]) -> Result<()> {
563 ensure!(
564 signature.len() == 64,
565 InvalidLength,
566 "ed25519 signature buffer"
567 );
568
569 let mut r = hash_to_scalar(&[&self.prefix, message]);
572 let big_r = mul_basepoint(&r).compress();
573
574 let k = hash_to_scalar(&[&big_r, &self.public, message]);
575 let s = scalar::mul_add(&k, &self.scalar, &r);
576
577 signature[..32].copy_from_slice(&big_r);
578 signature[32..].copy_from_slice(&s);
579 r.zeroize();
580 Ok(())
581 }
582}
583
584impl SignatureScheme for Ed25519 {
585 const PRIVATE_KEY_LEN: usize = 32;
586 const PUBLIC_KEY_LEN: usize = 32;
587 const SIGNATURE_LEN: usize = 64;
588
589 fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()> {
590 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
591 ensure!(out.len() == 32, InvalidLength, "ed25519 public key buffer");
592 let (mut a, mut prefix) = expand_seed(private_key);
593 out.copy_from_slice(&mul_basepoint(&a).compress());
594 a.zeroize();
595 prefix.zeroize();
596 Ok(())
597 }
598
599 fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()> {
600 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
601 ensure!(
602 signature.len() == 64,
603 InvalidLength,
604 "ed25519 signature buffer"
605 );
606
607 Ed25519Key::from_seed(private_key)?.sign(message, signature)
611 }
612
613 fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()> {
614 ensure!(public_key.len() == 32, InvalidLength, "ed25519 public key");
615 ensure!(signature.len() == 64, InvalidLength, "ed25519 signature");
616
617 let mut big_r = [0u8; 32];
618 big_r.copy_from_slice(&signature[..32]);
619 let mut s = [0u8; 32];
620 s.copy_from_slice(&signature[32..]);
621
622 ensure!(
626 scalar::is_canonical(&s),
627 MalformedEncoding,
628 "ed25519 signature S is not reduced"
629 );
630
631 let mut pk_bytes = [0u8; 32];
632 pk_bytes.copy_from_slice(public_key);
633 let a_point = Point::decompress(&pk_bytes).ok_or(ic_core::err!(
634 MalformedEncoding,
635 "ed25519 public key is not on the curve"
636 ))?;
637 let r_point = Point::decompress(&big_r).ok_or(ic_core::err!(
638 MalformedEncoding,
639 "ed25519 signature R is not on the curve"
640 ))?;
641
642 let k = hash_to_scalar(&[&big_r, &pk_bytes, message]);
643
644 #[cfg(feature = "std")]
653 let lhs = double_scalar_mul_vartime(&a_point.negate(), &k, &s);
654 #[cfg(not(feature = "std"))]
655 let lhs = mul_basepoint(&s).add(&a_point.negate().mul_scalar_vartime(&k));
656 let rhs = r_point;
657
658 if lhs.eq_projective(&rhs) {
663 Ok(())
664 } else {
665 Err(ic_core::err!(AuthenticationFailed, "ed25519"))
666 }
667 }
668}
669
670impl SelfTest for Ed25519 {
671 fn self_test() -> Result<()> {
672 let mut seed = [0u8; 32];
674 ic_core::codec::hex_decode(
675 b"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
676 &mut seed,
677 )?;
678 let mut want_pk = [0u8; 32];
679 ic_core::codec::hex_decode(
680 b"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
681 &mut want_pk,
682 )?;
683 let mut want_sig = [0u8; 64];
684 ic_core::codec::hex_decode(
685 b"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
686 &mut want_sig,
687 )?;
688
689 let mut pk = [0u8; 32];
690 <Self as SignatureScheme>::public_key(&seed, &mut pk)?;
691 ensure!(
692 ic_core::ct::verify(&want_pk, &pk),
693 SelfTestFailed,
694 "ed25519"
695 );
696
697 let mut sig = [0u8; 64];
698 <Self as SignatureScheme>::sign(&seed, b"", &mut sig)?;
699 ensure!(
700 ic_core::ct::verify(&want_sig, &sig),
701 SelfTestFailed,
702 "ed25519"
703 );
704
705 <Self as SignatureScheme>::verify(&pk, b"", &sig)?;
706
707 sig[0] ^= 1;
709 ensure!(
710 <Self as SignatureScheme>::verify(&pk, b"", &sig).is_err(),
711 SelfTestFailed,
712 "ed25519"
713 );
714 Ok(())
715 }
716}
717
718#[cfg(test)]
719mod tests {
720 use super::*;
721 use ic_core::codec::{hex, unhex};
722
723 #[test]
724 fn curve_constants_are_correct() {
725 let d = Fe::from_u64(121_665)
727 .neg()
728 .mul(&Fe::from_u64(121_666).invert());
729 assert_eq!(hex(&D.to_bytes()), hex(&d.to_bytes()), "d");
730 assert_eq!(hex(&D2.to_bytes()), hex(&d.add(&d).to_bytes()), "2d");
731 assert_eq!(
733 hex(&SQRT_M1.square().to_bytes()),
734 hex(&Fe::ONE.neg().to_bytes()),
735 "sqrt(-1)"
736 );
737 }
738
739 #[test]
740 fn basepoint_has_the_expected_coordinates() {
741 let b = basepoint();
742 let expected_y = Fe::from_u64(4).mul(&Fe::from_u64(5).invert());
744 let z_inv = b.z.invert();
745 assert_eq!(
746 hex(&b.y.mul(&z_inv).to_bytes()),
747 hex(&expected_y.to_bytes())
748 );
749 assert_eq!(hex(&b.compress()), hex(&BASEPOINT_COMPRESSED));
750 }
751
752 #[test]
759 fn doubling_agrees_with_adding_a_point_to_itself() {
760 let mut p = basepoint();
761 let mut checked = 0;
762 for _ in 0..16 {
763 assert_eq!(
764 p.double().compress(),
765 p.add(&p).compress(),
766 "dedicated doubling and self-addition differ"
767 );
768 p = p.add(&basepoint());
769 checked += 1;
770 }
771 assert_eq!(checked, 16, "the comparison did not run");
772
773 assert_eq!(
776 Point::IDENTITY.double().compress(),
777 Point::IDENTITY.compress()
778 );
779 }
780
781 #[test]
789 fn projective_equality_agrees_with_compressed_equality() {
790 let b = basepoint();
791 let mut points = std::vec![Point::IDENTITY, b];
792 let mut p = b;
793 for _ in 0..6 {
794 p = p.double();
795 points.push(p);
796 }
797
798 let mut checked = 0;
799 for (i, a) in points.iter().enumerate() {
800 for (j, c) in points.iter().enumerate() {
801 let projective = a.eq_projective(c);
802 let compressed = a.compress() == c.compress();
803 assert_eq!(
804 projective, compressed,
805 "projective and compressed equality differ for {i} vs {j}"
806 );
807 checked += 1;
808 }
809 }
810 assert_eq!(checked, 64, "the comparison did not run");
811
812 let scaled = b.add(&Point::IDENTITY);
815 assert!(b.eq_projective(&scaled), "equal points with different Z");
816 assert_eq!(b.compress(), scaled.compress());
817 }
818
819 #[test]
820 fn group_law_is_consistent() {
821 let b = basepoint();
822 assert_eq!(hex(&b.add(&Point::IDENTITY).compress()), hex(&b.compress()));
824 let mut two = [0u8; 32];
826 two[0] = 2;
827 assert_eq!(
828 hex(&b.double().compress()),
829 hex(&b.mul_scalar(&two).compress())
830 );
831 let mut three = [0u8; 32];
833 three[0] = 3;
834 assert_eq!(
835 hex(&b.double().add(&b).compress()),
836 hex(&b.mul_scalar(&three).compress())
837 );
838 }
839
840 #[test]
841 fn order_of_the_basepoint_is_l() {
842 assert_eq!(
844 hex(&basepoint().mul_scalar(&scalar::L).compress()),
845 hex(&Point::IDENTITY.compress())
846 );
847 }
848
849 #[test]
850 fn compression_roundtrips() {
851 let b = basepoint();
852 for k in [1u8, 2, 3, 47, 200] {
853 let mut s = [0u8; 32];
854 s[0] = k;
855 let p = b.mul_scalar(&s);
856 let c = p.compress();
857 let d = Point::decompress(&c).expect("valid point");
858 assert_eq!(hex(&d.compress()), hex(&c), "k = {k}");
859 }
860 }
861
862 #[test]
863 fn decompression_rejects_non_curve_points() {
864 let mut bad = [0u8; 32];
866 bad[0] = 2;
867 assert!(Point::decompress(&bad).is_none());
868 }
869
870 #[test]
872 fn rfc8032_vectors() {
873 let cases: [(&str, &str, &str, &str); 3] = [
874 (
875 "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
876 "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
877 "",
878 "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
879 ),
880 (
881 "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
882 "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
883 "72",
884 "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
885 ),
886 (
887 "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
888 "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
889 "af82",
890 "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
891 ),
892 ];
893
894 for (seed_hex, pk_hex, msg_hex, sig_hex) in cases {
895 let seed = unhex(seed_hex).unwrap();
896 let msg = unhex(msg_hex).unwrap();
897
898 let mut pk = [0u8; 32];
899 Ed25519::public_key(&seed, &mut pk).unwrap();
900 assert_eq!(hex(&pk), pk_hex, "public key for {seed_hex}");
901
902 let mut sig = [0u8; 64];
903 Ed25519::sign(&seed, &msg, &mut sig).unwrap();
904 assert_eq!(hex(&sig), sig_hex, "signature for {seed_hex}");
905
906 Ed25519::verify(&pk, &msg, &sig).unwrap();
907 }
908 }
909
910 #[test]
911 fn verification_rejects_tampering() {
912 let seed = [0x42u8; 32];
913 let mut pk = [0u8; 32];
914 Ed25519::public_key(&seed, &mut pk).unwrap();
915 let mut sig = [0u8; 64];
916 Ed25519::sign(&seed, b"authentic", &mut sig).unwrap();
917 Ed25519::verify(&pk, b"authentic", &sig).unwrap();
918
919 assert!(Ed25519::verify(&pk, b"forged", &sig).is_err());
921 let mut bad = sig;
923 bad[0] ^= 1;
924 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
925 let mut bad = sig;
927 bad[40] ^= 1;
928 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
929 let mut other_pk = [0u8; 32];
931 Ed25519::public_key(&[0x43u8; 32], &mut other_pk).unwrap();
932 assert!(Ed25519::verify(&other_pk, b"authentic", &sig).is_err());
933 }
934
935 #[test]
938 fn rejects_non_canonical_s() {
939 let seed = [0x42u8; 32];
940 let mut pk = [0u8; 32];
941 Ed25519::public_key(&seed, &mut pk).unwrap();
942 let mut sig = [0u8; 64];
943 Ed25519::sign(&seed, b"msg", &mut sig).unwrap();
944
945 let mut carry = 0u16;
948 for i in 0..32 {
949 let t = sig[32 + i] as u16 + scalar::L[i] as u16 + carry;
950 sig[32 + i] = t as u8;
951 carry = t >> 8;
952 }
953 assert!(Ed25519::verify(&pk, b"msg", &sig).is_err());
954 }
955
956 #[test]
963 fn the_cached_key_signs_identically_to_the_seed() {
964 let mut checked = 0;
965 for seed in [[0x11u8; 32], [0x9du8; 32], [0xffu8; 32]] {
966 for message in [&b""[..], &b"x"[..], &b"a longer message to sign"[..]] {
967 let mut from_seed = [0u8; 64];
968 Ed25519::sign(&seed, message, &mut from_seed).unwrap();
969
970 let key = Ed25519Key::from_seed(&seed).unwrap();
971 let mut from_key = [0u8; 64];
972 key.sign(message, &mut from_key).unwrap();
973
974 assert_eq!(from_seed, from_key, "the two signing paths diverged");
975
976 let mut derived = [0u8; 32];
978 Ed25519::public_key(&seed, &mut derived).unwrap();
979 assert_eq!(&derived, key.public_key());
980
981 Ed25519::verify(&derived, message, &from_key).unwrap();
983 checked += 1;
984 }
985 }
986 assert_eq!(checked, 9, "the comparison did not run");
987 }
988
989 #[test]
997 fn the_vartime_multiplication_agrees_with_the_ladder() {
998 let p = basepoint();
999
1000 let mut one = [0u8; 32];
1001 one[0] = 1;
1002 let mut two = [0u8; 32];
1003 two[0] = 2;
1004 let mut top = [0xffu8; 32];
1005 top[31] = 0x7f;
1006
1007 let mut checked = 0;
1008 for scalar in [
1009 [0u8; 32],
1010 one,
1011 two,
1012 [0xffu8; 32],
1013 [0x55u8; 32],
1014 [0xaau8; 32],
1015 top,
1016 [0x9du8; 32],
1017 ] {
1018 let fast = p.mul_scalar_vartime(&scalar);
1019 let slow = p.mul_scalar(&scalar);
1020 assert_eq!(
1021 fast.compress(),
1022 slow.compress(),
1023 "vartime and ladder differ for {scalar:02x?}"
1024 );
1025 checked += 1;
1026 }
1027 assert_eq!(checked, 8, "the comparison did not run");
1028 }
1029
1030 #[test]
1032 fn the_wnaf_digits_are_odd_sparse_and_faithful() {
1033 for scalar in [[1u8; 32], [0x9du8; 32], [0xffu8; 32], [0x55u8; 32]] {
1034 let naf = wnaf5(&scalar);
1035
1036 let mut previous_nonzero: Option<usize> = None;
1037 for (i, d) in naf.iter().enumerate() {
1038 if *d == 0 {
1039 continue;
1040 }
1041 assert!(d % 2 != 0, "digit {d} at {i} is not odd");
1042 assert!((-15..=15).contains(d), "digit {d} at {i} is out of range");
1043 if let Some(j) = previous_nonzero {
1044 assert!(i - j >= 5, "digits at {j} and {i} are adjacent");
1045 }
1046 previous_nonzero = Some(i);
1047 }
1048
1049 const M: u128 = 1_000_000_007;
1052 let mut from_digits = 0u128;
1053 let mut power = 1u128;
1054 for d in naf {
1055 let term = ((d as i128).rem_euclid(M as i128)) as u128;
1056 from_digits = (from_digits + term * power) % M;
1057 power = power * 2 % M;
1058 }
1059 let mut from_bytes = 0u128;
1060 let mut p = 1u128;
1061 for byte in scalar {
1062 from_bytes = (from_bytes + (byte as u128) * p) % M;
1063 p = p * 256 % M;
1064 }
1065 assert_eq!(from_digits, from_bytes, "recoding changed the value");
1066 }
1067 }
1068
1069 #[test]
1070 fn signing_is_deterministic() {
1071 let seed = [0x7fu8; 32];
1072 let mut a = [0u8; 64];
1073 let mut b = [0u8; 64];
1074 Ed25519::sign(&seed, b"same input", &mut a).unwrap();
1075 Ed25519::sign(&seed, b"same input", &mut b).unwrap();
1076 assert_eq!(a, b);
1077 }
1078
1079 #[test]
1080 fn rejects_wrong_lengths() {
1081 let mut out = [0u8; 32];
1082 assert!(Ed25519::public_key(&[0u8; 31], &mut out).is_err());
1083 assert!(Ed25519::sign(&[0u8; 32], b"", &mut [0u8; 63]).is_err());
1084 assert!(Ed25519::verify(&[0u8; 32], b"", &[0u8; 63]).is_err());
1085 }
1086
1087 #[test]
1088 fn self_test_passes() {
1089 Ed25519::self_test().unwrap();
1090 }
1091
1092 #[test]
1099 #[ignore = "diagnostic, not a test"]
1100 fn where_verify_spends_its_time() {
1101 use std::time::Instant;
1102
1103 let seed = [7u8; 32];
1104 let key = Ed25519Key::from_seed(&seed).unwrap();
1105 let msg = b"benchmark message";
1106 let mut sig = [0u8; 64];
1107 key.sign(msg, &mut sig).unwrap();
1108 let pk = *key.public_key();
1109
1110 let mut big_r = [0u8; 32];
1111 big_r.copy_from_slice(&sig[..32]);
1112 let mut s_sc = [0u8; 32];
1113 s_sc.copy_from_slice(&sig[32..]);
1114
1115 let n = 2000;
1116 let time = |label: &str, f: &mut dyn FnMut()| {
1117 let mut best = f64::INFINITY;
1118 for _ in 0..5 {
1119 let t = Instant::now();
1120 for _ in 0..n {
1121 f();
1122 }
1123 let e = t.elapsed().as_secs_f64() / n as f64 * 1e6;
1124 if e < best {
1125 best = e;
1126 }
1127 }
1128 println!(" {label:<34} {best:>9.2} us");
1129 best
1130 };
1131
1132 let a_point = Point::decompress(&pk).unwrap();
1133 let k = hash_to_scalar(&[&big_r, &pk, msg]);
1134
1135 println!(
1136 "
1137ed25519 verify, cost breakdown:"
1138 );
1139 let d = time("decompress (x2 per verify)", &mut || {
1140 core::hint::black_box(Point::decompress(&pk));
1141 });
1142 let h = time("hash_to_scalar", &mut || {
1143 core::hint::black_box(hash_to_scalar(&[&big_r, &pk, msg]));
1144 });
1145 let b = time("mul_basepoint (const time)", &mut || {
1146 core::hint::black_box(mul_basepoint(&s_sc));
1147 });
1148 let v = time("mul_scalar_vartime", &mut || {
1149 core::hint::black_box(a_point.mul_scalar_vartime(&k));
1150 });
1151 println!(
1152 " {:<34} {:>9.2} us",
1153 "-- accounted for",
1154 2.0 * d + h + b + v
1155 );
1156
1157 println!(
1159 "
1160field and point primitives, nanoseconds:"
1161 );
1162 let nn = 200_000;
1163 let ns = |label: &str, f: &mut dyn FnMut()| {
1164 let mut best = f64::INFINITY;
1165 for _ in 0..5 {
1166 let t = Instant::now();
1167 for _ in 0..nn {
1168 f();
1169 }
1170 let e = t.elapsed().as_secs_f64() / nn as f64 * 1e9;
1171 if e < best {
1172 best = e;
1173 }
1174 }
1175 println!(" {label:<34} {best:>9.2} ns");
1176 };
1177 let fx = a_point.x;
1178 let fy = a_point.y;
1179 ns("Fe::mul", &mut || {
1180 core::hint::black_box(core::hint::black_box(&fx).mul(core::hint::black_box(&fy)));
1181 });
1182 ns("Fe::square", &mut || {
1183 core::hint::black_box(core::hint::black_box(&fx).square());
1184 });
1185 ns("Fe::add", &mut || {
1186 core::hint::black_box(core::hint::black_box(&fx).add(core::hint::black_box(&fy)));
1187 });
1188 ns("Point::double", &mut || {
1189 core::hint::black_box(core::hint::black_box(&a_point).double());
1190 });
1191 ns("Point::add", &mut || {
1192 core::hint::black_box(
1193 core::hint::black_box(&a_point).add(core::hint::black_box(&a_point)),
1194 );
1195 });
1196 }
1197}