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
21const BASEPOINT_COMPRESSED: [u8; 32] = [
23 0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
24 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
25];
26
27const D: Fe = Fe([
29 929_955_233_495_203,
30 466_365_720_129_213,
31 1_662_059_464_998_953,
32 2_033_849_074_728_123,
33 1_442_794_654_840_575,
34]);
35
36const D2: Fe = Fe([
38 1_859_910_466_990_425,
39 932_731_440_258_426,
40 1_072_319_116_312_658,
41 1_815_898_335_770_999,
42 633_789_495_995_903,
43]);
44
45const SQRT_M1: Fe = Fe([
47 1_718_705_420_411_056,
48 234_908_883_556_509,
49 2_233_514_472_574_048,
50 2_117_202_627_021_982,
51 765_476_049_583_133,
52]);
53
54#[derive(Clone, Copy, Debug)]
56pub struct Point {
57 x: Fe,
58 y: Fe,
59 z: Fe,
60 t: Fe,
61}
62
63#[cfg(feature = "std")]
73#[derive(Clone, Copy)]
74pub(crate) struct Completed {
75 x: Fe,
76 y: Fe,
77 z: Fe,
78 t: Fe,
79}
80
81#[cfg(feature = "std")]
85#[derive(Clone, Copy)]
86pub(crate) struct Projective {
87 x: Fe,
88 y: Fe,
89 z: Fe,
90}
91
92#[cfg(feature = "std")]
100#[derive(Clone, Copy)]
101pub(crate) struct Niels {
102 ypx: Fe,
103 ymx: Fe,
104 z: Fe,
105 t2d: Fe,
106}
107
108#[cfg(feature = "std")]
114#[derive(Clone, Copy)]
115pub(crate) struct AffineNiels {
116 ypx: Fe,
117 ymx: Fe,
118 t2d: Fe,
119}
120
121#[cfg(feature = "std")]
122impl AffineNiels {
123 pub(crate) const IDENTITY: AffineNiels = AffineNiels {
125 ypx: Fe::ONE,
126 ymx: Fe::ONE,
127 t2d: Fe::ZERO,
128 };
129
130 pub(crate) fn conditional_negate(&mut self, choice: Choice) {
133 let swapped_p = self.ymx;
134 let swapped_m = self.ypx;
135 let nt = self.t2d.neg();
136 Fe::cmov(&mut self.ypx, &swapped_p, choice);
137 Fe::cmov(&mut self.ymx, &swapped_m, choice);
138 Fe::cmov(&mut self.t2d, &nt, choice);
139 }
140
141 pub(crate) fn cmov(&mut self, other: &AffineNiels, choice: Choice) {
142 Fe::cmov(&mut self.ypx, &other.ypx, choice);
143 Fe::cmov(&mut self.ymx, &other.ymx, choice);
144 Fe::cmov(&mut self.t2d, &other.t2d, choice);
145 }
146}
147
148#[cfg(feature = "std")]
149impl Completed {
150 fn to_projective(self) -> Projective {
152 Projective {
153 x: self.x.mul(&self.t),
154 y: self.y.mul(&self.z),
155 z: self.z.mul(&self.t),
156 }
157 }
158
159 fn to_extended(self) -> Point {
164 Point {
165 x: self.x.mul(&self.t),
166 y: self.y.mul(&self.z),
167 z: self.z.mul(&self.t),
168 t: self.x.mul(&self.y),
169 }
170 }
171}
172
173#[cfg(feature = "std")]
174impl Projective {
175 fn to_extended_from_projective(self) -> Point {
180 Point {
187 x: self.x.mul(&self.z),
188 y: self.y.mul(&self.z),
189 z: self.z.square(),
190 t: self.x.mul(&self.y),
191 }
192 }
193
194 fn double_projective(self) -> Projective {
204 let xx = self.x.square();
205 let yy = self.y.square();
206 let zz2 = {
207 let t = self.z.square();
208 t.add(&t)
209 };
210 let xy_sq = self.x.add(&self.y).square();
211 let yy_plus_xx = yy.add(&xx);
212 let yy_minus_xx = yy.sub(&xx);
213
214 let cx = xy_sq.sub(&yy_plus_xx);
215 let cy = yy_plus_xx;
216 let cz = yy_minus_xx;
217 let ct = zz2.sub(&yy_minus_xx);
218
219 Projective {
220 x: cx.mul(&ct),
221 y: cy.mul(&cz),
222 z: cz.mul(&ct),
223 }
224 }
225
226 fn double(&self) -> Completed {
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 Completed {
242 x: xy_sq.sub(&yy_plus_xx),
243 y: yy_plus_xx,
244 z: yy_minus_xx,
245 t: zz2.sub(&yy_minus_xx),
246 }
247 }
248}
249
250impl Point {
251 pub const IDENTITY: Point = Point {
253 x: Fe::ZERO,
254 y: Fe::ONE,
255 z: Fe::ONE,
256 t: Fe::ZERO,
257 };
258
259 pub fn add(&self, other: &Point) -> Point {
261 let a = self.y.sub(&self.x).mul(&other.y.sub(&other.x));
262 let b = self.y.add(&self.x).mul(&other.y.add(&other.x));
263 let c = self.t.mul(&D2).mul(&other.t);
264 let d = self.z.mul(&other.z);
265 let d = d.add(&d);
266
267 let e = b.sub(&a);
268 let f = d.sub(&c);
269 let g = d.add(&c);
270 let h = b.add(&a);
271
272 Point {
273 x: e.mul(&f),
274 y: g.mul(&h),
275 t: e.mul(&h),
276 z: f.mul(&g),
277 }
278 }
279
280 pub fn double(&self) -> Point {
291 let aa = self.x.square();
292 let bb = self.y.square();
293 let c = self.z.square();
294 let c = c.add(&c);
295 let d = aa.neg();
297 let xy = self.x.add(&self.y);
299 let e = xy.square().sub(&aa).sub(&bb);
300 let g = d.add(&bb);
301 let f = g.sub(&c);
302 let h = d.sub(&bb);
303
304 Point {
305 x: e.mul(&f),
306 y: g.mul(&h),
307 t: e.mul(&h),
308 z: f.mul(&g),
309 }
310 }
311
312 #[cfg(feature = "std")]
314 fn to_projective(self) -> Projective {
315 Projective {
316 x: self.x,
317 y: self.y,
318 z: self.z,
319 }
320 }
321
322 #[cfg(feature = "std")]
324 fn to_niels(self) -> Niels {
325 Niels {
326 ypx: self.y.add(&self.x),
327 ymx: self.y.sub(&self.x),
328 z: self.z,
329 t2d: self.t.mul(&D2),
330 }
331 }
332
333 #[cfg(feature = "std")]
340 fn add_niels(&self, other: &Niels) -> Completed {
341 let pp = self.y.add(&self.x).mul(&other.ypx);
342 let mm = self.y.sub(&self.x).mul(&other.ymx);
343 let tt2d = self.t.mul(&other.t2d);
344 let zz = self.z.mul(&other.z);
345 let zz2 = zz.add(&zz);
346 Completed {
347 x: pp.sub(&mm),
348 y: pp.add(&mm),
349 z: zz2.add(&tt2d),
350 t: zz2.sub(&tt2d),
351 }
352 }
353
354 #[cfg(feature = "std")]
360 fn sub_niels(&self, other: &Niels) -> Completed {
361 let pp = self.y.add(&self.x).mul(&other.ymx);
362 let mm = self.y.sub(&self.x).mul(&other.ypx);
363 let tt2d = self.t.mul(&other.t2d);
364 let zz = self.z.mul(&other.z);
365 let zz2 = zz.add(&zz);
366 Completed {
367 x: pp.sub(&mm),
368 y: pp.add(&mm),
369 z: zz2.sub(&tt2d),
370 t: zz2.add(&tt2d),
371 }
372 }
373
374 #[cfg(feature = "std")]
380 pub(crate) fn to_affine_niels(self) -> AffineNiels {
381 let z_inv = self.z.invert();
382 let x = self.x.mul(&z_inv);
383 let y = self.y.mul(&z_inv);
384 AffineNiels {
385 ypx: y.add(&x),
386 ymx: y.sub(&x),
387 t2d: x.mul(&y).mul(&D2),
388 }
389 }
390
391 #[cfg(feature = "std")]
396 pub(crate) fn add_affine_niels(&self, other: &AffineNiels) -> Completed {
397 let pp = self.y.add(&self.x).mul(&other.ypx);
398 let mm = self.y.sub(&self.x).mul(&other.ymx);
399 let tt2d = self.t.mul(&other.t2d);
400 let zz2 = self.z.add(&self.z);
401 Completed {
402 x: pp.sub(&mm),
403 y: pp.add(&mm),
404 z: zz2.add(&tt2d),
405 t: zz2.sub(&tt2d),
406 }
407 }
408
409 #[cfg(feature = "std")]
418 pub(crate) fn sub_affine_niels(&self, other: &AffineNiels) -> Completed {
419 let pp = self.y.add(&self.x).mul(&other.ymx);
420 let mm = self.y.sub(&self.x).mul(&other.ypx);
421 let tt2d = self.t.mul(&other.t2d);
422 let zz2 = self.z.add(&self.z);
423 Completed {
424 x: pp.sub(&mm),
425 y: pp.add(&mm),
426 z: zz2.sub(&tt2d),
427 t: zz2.add(&tt2d),
428 }
429 }
430
431 fn cmov(&mut self, other: &Point, choice: Choice) {
433 Fe::cmov(&mut self.x, &other.x, choice);
434 Fe::cmov(&mut self.y, &other.y, choice);
435 Fe::cmov(&mut self.z, &other.z, choice);
436 Fe::cmov(&mut self.t, &other.t, choice);
437 }
438
439 pub fn mul_scalar(&self, s: &[u8; 32]) -> Point {
445 let mut acc = Point::IDENTITY;
446 for i in (0..256).rev() {
447 acc = acc.double();
448 let sum = acc.add(self);
449 let bit = Choice::from_u8((s[i / 8] >> (i % 8)) & 1);
450 acc.cmov(&sum, bit);
451 }
452 acc
453 }
454
455 fn negate(&self) -> Point {
457 Point {
458 x: self.x.neg(),
459 y: self.y,
460 z: self.z,
461 t: self.t.neg(),
462 }
463 }
464
465 #[cfg(any(not(feature = "std"), test))]
494 pub(crate) fn mul_scalar_vartime(&self, scalar: &[u8; 32]) -> Point {
495 let twice = self.double();
497 let mut odd = [*self; 8];
498 for i in 1..8 {
499 odd[i] = odd[i - 1].add(&twice);
500 }
501
502 let naf = wnaf5(scalar);
503 let mut acc = Point::IDENTITY;
504 for digit in naf.iter().rev() {
505 acc = acc.double();
506 if *digit != 0 {
507 let entry = &odd[(digit.unsigned_abs() as usize) / 2];
509 acc = if *digit > 0 {
510 acc.add(entry)
511 } else {
512 acc.add(&entry.negate())
513 };
514 }
515 }
516 acc
517 }
518
519 fn eq_projective(&self, other: &Point) -> bool {
534 self.x.mul(&other.z).to_bytes() == other.x.mul(&self.z).to_bytes()
535 && self.y.mul(&other.z).to_bytes() == other.y.mul(&self.z).to_bytes()
536 }
537
538 pub fn compress(&self) -> [u8; 32] {
540 let z_inv = self.z.invert();
541 let x = self.x.mul(&z_inv);
542 let y = self.y.mul(&z_inv);
543 let mut out = y.to_bytes();
544 out[31] |= x.is_negative().unwrap_u8() << 7;
546 out
547 }
548
549 pub fn decompress(bytes: &[u8; 32]) -> Option<Point> {
551 let sign = Choice::from_u8(bytes[31] >> 7);
552 let mut y_bytes = *bytes;
553 y_bytes[31] &= 0x7f;
554 let y = Fe::from_bytes(&y_bytes);
555
556 let y2 = y.square();
558 let u = y2.sub(&Fe::ONE);
559 let v = y2.mul(&D).add(&Fe::ONE);
560
561 let v3 = v.square().mul(&v);
563 let v7 = v3.square().mul(&v);
564 let mut x = u.mul(&v3).mul(&u.mul(&v7).pow22523());
565
566 let check = v.mul(&x.square());
567 let correct = check.ct_eq(&u);
568 let flipped = check.ct_eq(&u.neg());
569 if !bool::from(correct.or(flipped)) {
570 return None;
572 }
573 let alt = x.mul(&SQRT_M1);
575 Fe::cmov(&mut x, &alt, flipped.and(correct.not()));
576
577 if bool::from(x.is_zero()) && bool::from(sign) {
579 return None;
580 }
581 let neg = x.neg();
583 let wrong_sign = Choice::from_u8(x.is_negative().unwrap_u8() ^ sign.unwrap_u8());
584 Fe::cmov(&mut x, &neg, wrong_sign);
585
586 Some(Point {
587 x,
588 y,
589 z: Fe::ONE,
590 t: x.mul(&y),
591 })
592 }
593}
594
595fn mul_basepoint(scalar: &[u8; 32]) -> Point {
602 #[cfg(feature = "std")]
603 {
604 basepoint_table::table().mul(scalar)
605 }
606 #[cfg(not(feature = "std"))]
607 {
608 basepoint().mul_scalar(scalar)
609 }
610}
611
612#[cfg(feature = "bench-internals")]
636#[doc(hidden)]
637pub fn double_scalar_mul_vartime_for_bench(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
638 double_scalar_mul_vartime(a, k, s)
639}
640
641#[cfg(feature = "std")]
642fn double_scalar_mul_vartime(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
643 let twice = a.double();
645 let mut odd = [*a; 8];
646 for i in 1..8 {
647 odd[i] = odd[i - 1].add(&twice);
648 }
649 let odd_a: [Niels; 8] = core::array::from_fn(|i| odd[i].to_niels());
650 let odd_b = basepoint_table::odd_multiples();
651
652 let naf_a = wnaf(k, 5);
653 let naf_b = wnaf(s, 8);
654
655 let mut i = 257;
658 while i > 0 && naf_a[i] == 0 && naf_b[i] == 0 {
659 i -= 1;
660 }
661
662 let mut acc = Point::IDENTITY.to_projective();
667 loop {
668 if naf_a[i] == 0 && naf_b[i] == 0 {
671 acc = acc.double_projective();
672 if i == 0 {
673 return acc.to_extended_from_projective();
674 }
675 i -= 1;
676 continue;
677 }
678 let mut t = acc.double();
679 if naf_a[i] != 0 {
680 let e = t.to_extended();
681 let n = &odd_a[(naf_a[i].unsigned_abs() as usize) / 2];
682 t = if naf_a[i] > 0 {
683 e.add_niels(n)
684 } else {
685 e.sub_niels(n)
686 };
687 }
688 if naf_b[i] != 0 {
689 let e = t.to_extended();
690 let n = &odd_b[(naf_b[i].unsigned_abs() as usize) / 2];
691 t = if naf_b[i] > 0 {
692 e.add_affine_niels(n)
693 } else {
694 e.sub_affine_niels(n)
695 };
696 }
697 if i == 0 {
698 return t.to_extended();
699 }
700 acc = t.to_projective();
701 i -= 1;
702 }
703}
704
705#[cfg(feature = "bench-internals")]
708#[doc(hidden)]
709pub fn mul_basepoint_for_bench(scalar: &[u8; 32]) -> Point {
710 mul_basepoint(scalar)
711}
712
713fn basepoint() -> Point {
714 Point::decompress(&BASEPOINT_COMPRESSED).unwrap_or(Point::IDENTITY)
717}
718
719pub struct Ed25519;
721
722impl Algorithm for Ed25519 {
723 const ID: &'static str = "ed25519";
724 const NAME: &'static str = "Ed25519";
725}
726
727fn expand_seed(seed: &[u8]) -> ([u8; 32], [u8; 32]) {
729 let h = Sha512::digest(seed);
730 let mut a = [0u8; 32];
731 let mut prefix = [0u8; 32];
732 a.copy_from_slice(&h.as_ref()[..32]);
733 prefix.copy_from_slice(&h.as_ref()[32..]);
734 a[0] &= 248;
735 a[31] &= 127;
736 a[31] |= 64;
737 (a, prefix)
738}
739
740#[cfg(any(not(feature = "std"), test))]
749fn wnaf5(scalar: &[u8; 32]) -> [i8; 258] {
750 wnaf(scalar, 5)
751}
752
753fn wnaf(scalar: &[u8; 32], w: u32) -> [i8; 258] {
763 debug_assert!((2..=8).contains(&w), "window width out of range");
764 let half = 1i64 << (w - 1);
765 let full = 1i64 << w;
766 let mask = (full - 1) as u64;
767
768 let mut naf = [0i8; 258];
769 let mut k = [0u64; 5];
776 for (i, limb) in k.iter_mut().take(4).enumerate() {
777 let mut b = [0u8; 8];
778 b.copy_from_slice(&scalar[i * 8..i * 8 + 8]);
779 *limb = u64::from_le_bytes(b);
780 }
781
782 let mut i = 0;
783 while k.iter().any(|&x| x != 0) {
784 if k[0] & 1 == 1 {
785 let mut d = (k[0] & mask) as i64;
786 if d >= half {
787 d -= full;
788 }
789 naf[i] = d as i8;
790 if d > 0 {
791 sub_u64(&mut k, d as u64);
792 } else {
793 add_u64(&mut k, d.unsigned_abs());
794 }
795 }
796 shr1(&mut k);
797 i += 1;
798 }
799 naf
800}
801
802fn sub_u64(k: &mut [u64; 5], v: u64) {
804 let (d, mut borrow) = k[0].overflowing_sub(v);
805 k[0] = d;
806 for limb in k.iter_mut().skip(1) {
807 if !borrow {
808 break;
809 }
810 let (d, b) = limb.overflowing_sub(1);
811 *limb = d;
812 borrow = b;
813 }
814}
815
816fn add_u64(k: &mut [u64; 5], v: u64) {
818 let (d, mut carry) = k[0].overflowing_add(v);
819 k[0] = d;
820 for limb in k.iter_mut().skip(1) {
821 if !carry {
822 break;
823 }
824 let (d, c) = limb.overflowing_add(1);
825 *limb = d;
826 carry = c;
827 }
828}
829
830fn shr1(k: &mut [u64; 5]) {
832 for i in 0..4 {
833 k[i] = (k[i] >> 1) | (k[i + 1] << 63);
834 }
835 k[4] >>= 1;
836}
837
838fn hash_to_scalar(parts: &[&[u8]]) -> [u8; 32] {
840 let mut h = Sha512::new();
841 for p in parts {
842 h.update(p);
843 }
844 let digest = h.finalize();
845 let mut wide = [0u8; 64];
846 wide.copy_from_slice(digest.as_ref());
847 scalar::reduce_wide(&wide)
848}
849
850pub struct Ed25519Key {
867 scalar: [u8; 32],
869 prefix: [u8; 32],
871 public: [u8; 32],
873}
874
875impl Drop for Ed25519Key {
876 fn drop(&mut self) {
877 self.scalar.zeroize();
878 self.prefix.zeroize();
879 }
881}
882
883impl Ed25519Key {
884 pub fn from_seed(seed: &[u8]) -> Result<Self> {
886 ensure!(seed.len() == 32, InvalidLength, "ed25519 seed");
887 let (scalar, prefix) = expand_seed(seed);
888 let public = mul_basepoint(&scalar).compress();
889 Ok(Self {
890 scalar,
891 prefix,
892 public,
893 })
894 }
895
896 pub fn public_key(&self) -> &[u8; 32] {
898 &self.public
899 }
900
901 pub fn sign(&self, message: &[u8], signature: &mut [u8]) -> Result<()> {
903 ensure!(
904 signature.len() == 64,
905 InvalidLength,
906 "ed25519 signature buffer"
907 );
908
909 let mut r = hash_to_scalar(&[&self.prefix, message]);
912 let big_r = mul_basepoint(&r).compress();
913
914 let k = hash_to_scalar(&[&big_r, &self.public, message]);
915 let s = scalar::mul_add(&k, &self.scalar, &r);
916
917 signature[..32].copy_from_slice(&big_r);
918 signature[32..].copy_from_slice(&s);
919 r.zeroize();
920 Ok(())
921 }
922}
923
924impl SignatureScheme for Ed25519 {
925 const PRIVATE_KEY_LEN: usize = 32;
926 const PUBLIC_KEY_LEN: usize = 32;
927 const SIGNATURE_LEN: usize = 64;
928
929 fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()> {
930 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
931 ensure!(out.len() == 32, InvalidLength, "ed25519 public key buffer");
932 let (mut a, mut prefix) = expand_seed(private_key);
933 out.copy_from_slice(&mul_basepoint(&a).compress());
934 a.zeroize();
935 prefix.zeroize();
936 Ok(())
937 }
938
939 fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()> {
940 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
941 ensure!(
942 signature.len() == 64,
943 InvalidLength,
944 "ed25519 signature buffer"
945 );
946
947 Ed25519Key::from_seed(private_key)?.sign(message, signature)
951 }
952
953 fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()> {
954 Ed25519VerifyKey::from_bytes(public_key)?.verify(message, signature)
958 }
959}
960
961pub struct Ed25519VerifyKey {
975 compressed: [u8; 32],
977 neg_a: Point,
979}
980
981impl Ed25519VerifyKey {
982 pub fn from_bytes(public_key: &[u8]) -> Result<Self> {
984 ensure!(public_key.len() == 32, InvalidLength, "ed25519 public key");
985 let mut compressed = [0u8; 32];
986 compressed.copy_from_slice(public_key);
987 let a = Point::decompress(&compressed).ok_or(ic_core::err!(
988 MalformedEncoding,
989 "ed25519 public key is not on the curve"
990 ))?;
991 Ok(Self {
992 compressed,
993 neg_a: a.negate(),
994 })
995 }
996
997 pub fn as_bytes(&self) -> &[u8; 32] {
999 &self.compressed
1000 }
1001
1002 pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> {
1004 ensure!(signature.len() == 64, InvalidLength, "ed25519 signature");
1005
1006 let mut big_r = [0u8; 32];
1007 big_r.copy_from_slice(&signature[..32]);
1008 let mut s = [0u8; 32];
1009 s.copy_from_slice(&signature[32..]);
1010
1011 ensure!(
1015 scalar::is_canonical(&s),
1016 MalformedEncoding,
1017 "ed25519 signature S is not reduced"
1018 );
1019
1020 let r_point = Point::decompress(&big_r).ok_or(ic_core::err!(
1021 MalformedEncoding,
1022 "ed25519 signature R is not on the curve"
1023 ))?;
1024
1025 let k = hash_to_scalar(&[&big_r, &self.compressed, message]);
1026
1027 #[cfg(feature = "std")]
1031 let lhs = double_scalar_mul_vartime(&self.neg_a, &k, &s);
1032 #[cfg(not(feature = "std"))]
1033 let lhs = mul_basepoint(&s).add(&self.neg_a.mul_scalar_vartime(&k));
1034
1035 if lhs.eq_projective(&r_point) {
1036 Ok(())
1037 } else {
1038 Err(ic_core::err!(AuthenticationFailed, "ed25519"))
1039 }
1040 }
1041}
1042
1043impl SelfTest for Ed25519 {
1044 fn self_test() -> Result<()> {
1045 let mut seed = [0u8; 32];
1047 ic_core::codec::hex_decode(
1048 b"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1049 &mut seed,
1050 )?;
1051 let mut want_pk = [0u8; 32];
1052 ic_core::codec::hex_decode(
1053 b"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1054 &mut want_pk,
1055 )?;
1056 let mut want_sig = [0u8; 64];
1057 ic_core::codec::hex_decode(
1058 b"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1059 &mut want_sig,
1060 )?;
1061
1062 let mut pk = [0u8; 32];
1063 <Self as SignatureScheme>::public_key(&seed, &mut pk)?;
1064 ensure!(
1065 ic_core::ct::verify(&want_pk, &pk),
1066 SelfTestFailed,
1067 "ed25519"
1068 );
1069
1070 let mut sig = [0u8; 64];
1071 <Self as SignatureScheme>::sign(&seed, b"", &mut sig)?;
1072 ensure!(
1073 ic_core::ct::verify(&want_sig, &sig),
1074 SelfTestFailed,
1075 "ed25519"
1076 );
1077
1078 <Self as SignatureScheme>::verify(&pk, b"", &sig)?;
1079
1080 sig[0] ^= 1;
1082 ensure!(
1083 <Self as SignatureScheme>::verify(&pk, b"", &sig).is_err(),
1084 SelfTestFailed,
1085 "ed25519"
1086 );
1087 Ok(())
1088 }
1089}
1090
1091#[cfg(test)]
1092mod tests {
1093 use super::*;
1094 use ic_core::codec::{hex, unhex};
1095
1096 #[test]
1097 fn curve_constants_are_correct() {
1098 let d = Fe::from_u64(121_665)
1100 .neg()
1101 .mul(&Fe::from_u64(121_666).invert());
1102 assert_eq!(hex(&D.to_bytes()), hex(&d.to_bytes()), "d");
1103 assert_eq!(hex(&D2.to_bytes()), hex(&d.add(&d).to_bytes()), "2d");
1104 assert_eq!(
1106 hex(&SQRT_M1.square().to_bytes()),
1107 hex(&Fe::ONE.neg().to_bytes()),
1108 "sqrt(-1)"
1109 );
1110 }
1111
1112 #[test]
1113 fn basepoint_has_the_expected_coordinates() {
1114 let b = basepoint();
1115 let expected_y = Fe::from_u64(4).mul(&Fe::from_u64(5).invert());
1117 let z_inv = b.z.invert();
1118 assert_eq!(
1119 hex(&b.y.mul(&z_inv).to_bytes()),
1120 hex(&expected_y.to_bytes())
1121 );
1122 assert_eq!(hex(&b.compress()), hex(&BASEPOINT_COMPRESSED));
1123 }
1124
1125 #[test]
1132 fn doubling_agrees_with_adding_a_point_to_itself() {
1133 let mut p = basepoint();
1134 let mut checked = 0;
1135 for _ in 0..16 {
1136 assert_eq!(
1137 p.double().compress(),
1138 p.add(&p).compress(),
1139 "dedicated doubling and self-addition differ"
1140 );
1141 p = p.add(&basepoint());
1142 checked += 1;
1143 }
1144 assert_eq!(checked, 16, "the comparison did not run");
1145
1146 assert_eq!(
1149 Point::IDENTITY.double().compress(),
1150 Point::IDENTITY.compress()
1151 );
1152 }
1153
1154 #[test]
1162 fn projective_equality_agrees_with_compressed_equality() {
1163 let b = basepoint();
1164 let mut points = std::vec![Point::IDENTITY, b];
1165 let mut p = b;
1166 for _ in 0..6 {
1167 p = p.double();
1168 points.push(p);
1169 }
1170
1171 let mut checked = 0;
1172 for (i, a) in points.iter().enumerate() {
1173 for (j, c) in points.iter().enumerate() {
1174 let projective = a.eq_projective(c);
1175 let compressed = a.compress() == c.compress();
1176 assert_eq!(
1177 projective, compressed,
1178 "projective and compressed equality differ for {i} vs {j}"
1179 );
1180 checked += 1;
1181 }
1182 }
1183 assert_eq!(checked, 64, "the comparison did not run");
1184
1185 let scaled = b.add(&Point::IDENTITY);
1188 assert!(b.eq_projective(&scaled), "equal points with different Z");
1189 assert_eq!(b.compress(), scaled.compress());
1190 }
1191
1192 #[test]
1193 fn group_law_is_consistent() {
1194 let b = basepoint();
1195 assert_eq!(hex(&b.add(&Point::IDENTITY).compress()), hex(&b.compress()));
1197 let mut two = [0u8; 32];
1199 two[0] = 2;
1200 assert_eq!(
1201 hex(&b.double().compress()),
1202 hex(&b.mul_scalar(&two).compress())
1203 );
1204 let mut three = [0u8; 32];
1206 three[0] = 3;
1207 assert_eq!(
1208 hex(&b.double().add(&b).compress()),
1209 hex(&b.mul_scalar(&three).compress())
1210 );
1211 }
1212
1213 #[test]
1214 fn order_of_the_basepoint_is_l() {
1215 assert_eq!(
1217 hex(&basepoint().mul_scalar(&scalar::L).compress()),
1218 hex(&Point::IDENTITY.compress())
1219 );
1220 }
1221
1222 #[test]
1223 fn compression_roundtrips() {
1224 let b = basepoint();
1225 for k in [1u8, 2, 3, 47, 200] {
1226 let mut s = [0u8; 32];
1227 s[0] = k;
1228 let p = b.mul_scalar(&s);
1229 let c = p.compress();
1230 let d = Point::decompress(&c).expect("valid point");
1231 assert_eq!(hex(&d.compress()), hex(&c), "k = {k}");
1232 }
1233 }
1234
1235 #[test]
1236 fn decompression_rejects_non_curve_points() {
1237 let mut bad = [0u8; 32];
1239 bad[0] = 2;
1240 assert!(Point::decompress(&bad).is_none());
1241 }
1242
1243 #[test]
1245 fn rfc8032_vectors() {
1246 let cases: [(&str, &str, &str, &str); 3] = [
1247 (
1248 "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1249 "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1250 "",
1251 "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1252 ),
1253 (
1254 "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
1255 "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
1256 "72",
1257 "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
1258 ),
1259 (
1260 "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
1261 "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
1262 "af82",
1263 "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
1264 ),
1265 ];
1266
1267 for (seed_hex, pk_hex, msg_hex, sig_hex) in cases {
1268 let seed = unhex(seed_hex).unwrap();
1269 let msg = unhex(msg_hex).unwrap();
1270
1271 let mut pk = [0u8; 32];
1272 Ed25519::public_key(&seed, &mut pk).unwrap();
1273 assert_eq!(hex(&pk), pk_hex, "public key for {seed_hex}");
1274
1275 let mut sig = [0u8; 64];
1276 Ed25519::sign(&seed, &msg, &mut sig).unwrap();
1277 assert_eq!(hex(&sig), sig_hex, "signature for {seed_hex}");
1278
1279 Ed25519::verify(&pk, &msg, &sig).unwrap();
1280 }
1281 }
1282
1283 #[test]
1284 fn verification_rejects_tampering() {
1285 let seed = [0x42u8; 32];
1286 let mut pk = [0u8; 32];
1287 Ed25519::public_key(&seed, &mut pk).unwrap();
1288 let mut sig = [0u8; 64];
1289 Ed25519::sign(&seed, b"authentic", &mut sig).unwrap();
1290 Ed25519::verify(&pk, b"authentic", &sig).unwrap();
1291
1292 assert!(Ed25519::verify(&pk, b"forged", &sig).is_err());
1294 let mut bad = sig;
1296 bad[0] ^= 1;
1297 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1298 let mut bad = sig;
1300 bad[40] ^= 1;
1301 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1302 let mut other_pk = [0u8; 32];
1304 Ed25519::public_key(&[0x43u8; 32], &mut other_pk).unwrap();
1305 assert!(Ed25519::verify(&other_pk, b"authentic", &sig).is_err());
1306 }
1307
1308 #[test]
1311 fn rejects_non_canonical_s() {
1312 let seed = [0x42u8; 32];
1313 let mut pk = [0u8; 32];
1314 Ed25519::public_key(&seed, &mut pk).unwrap();
1315 let mut sig = [0u8; 64];
1316 Ed25519::sign(&seed, b"msg", &mut sig).unwrap();
1317
1318 let mut carry = 0u16;
1321 for i in 0..32 {
1322 let t = sig[32 + i] as u16 + scalar::L[i] as u16 + carry;
1323 sig[32 + i] = t as u8;
1324 carry = t >> 8;
1325 }
1326 assert!(Ed25519::verify(&pk, b"msg", &sig).is_err());
1327 }
1328
1329 #[test]
1336 fn the_cached_key_signs_identically_to_the_seed() {
1337 let mut checked = 0;
1338 for seed in [[0x11u8; 32], [0x9du8; 32], [0xffu8; 32]] {
1339 for message in [&b""[..], &b"x"[..], &b"a longer message to sign"[..]] {
1340 let mut from_seed = [0u8; 64];
1341 Ed25519::sign(&seed, message, &mut from_seed).unwrap();
1342
1343 let key = Ed25519Key::from_seed(&seed).unwrap();
1344 let mut from_key = [0u8; 64];
1345 key.sign(message, &mut from_key).unwrap();
1346
1347 assert_eq!(from_seed, from_key, "the two signing paths diverged");
1348
1349 let mut derived = [0u8; 32];
1351 Ed25519::public_key(&seed, &mut derived).unwrap();
1352 assert_eq!(&derived, key.public_key());
1353
1354 Ed25519::verify(&derived, message, &from_key).unwrap();
1356 checked += 1;
1357 }
1358 }
1359 assert_eq!(checked, 9, "the comparison did not run");
1360 }
1361
1362 #[test]
1370 fn the_vartime_multiplication_agrees_with_the_ladder() {
1371 let p = basepoint();
1372
1373 let mut one = [0u8; 32];
1374 one[0] = 1;
1375 let mut two = [0u8; 32];
1376 two[0] = 2;
1377 let mut top = [0xffu8; 32];
1378 top[31] = 0x7f;
1379
1380 let mut checked = 0;
1381 for scalar in [
1382 [0u8; 32],
1383 one,
1384 two,
1385 [0xffu8; 32],
1386 [0x55u8; 32],
1387 [0xaau8; 32],
1388 top,
1389 [0x9du8; 32],
1390 ] {
1391 let fast = p.mul_scalar_vartime(&scalar);
1392 let slow = p.mul_scalar(&scalar);
1393 assert_eq!(
1394 fast.compress(),
1395 slow.compress(),
1396 "vartime and ladder differ for {scalar:02x?}"
1397 );
1398 checked += 1;
1399 }
1400 assert_eq!(checked, 8, "the comparison did not run");
1401 }
1402
1403 #[test]
1405 fn the_wnaf_digits_are_odd_sparse_and_faithful() {
1406 for scalar in [[1u8; 32], [0x9du8; 32], [0xffu8; 32], [0x55u8; 32]] {
1407 let naf = wnaf5(&scalar);
1408
1409 let mut previous_nonzero: Option<usize> = None;
1410 for (i, d) in naf.iter().enumerate() {
1411 if *d == 0 {
1412 continue;
1413 }
1414 assert!(d % 2 != 0, "digit {d} at {i} is not odd");
1415 assert!((-15..=15).contains(d), "digit {d} at {i} is out of range");
1416 if let Some(j) = previous_nonzero {
1417 assert!(i - j >= 5, "digits at {j} and {i} are adjacent");
1418 }
1419 previous_nonzero = Some(i);
1420 }
1421
1422 const M: u128 = 1_000_000_007;
1425 let mut from_digits = 0u128;
1426 let mut power = 1u128;
1427 for d in naf {
1428 let term = ((d as i128).rem_euclid(M as i128)) as u128;
1429 from_digits = (from_digits + term * power) % M;
1430 power = power * 2 % M;
1431 }
1432 let mut from_bytes = 0u128;
1433 let mut p = 1u128;
1434 for byte in scalar {
1435 from_bytes = (from_bytes + (byte as u128) * p) % M;
1436 p = p * 256 % M;
1437 }
1438 assert_eq!(from_digits, from_bytes, "recoding changed the value");
1439 }
1440 }
1441
1442 #[test]
1443 fn signing_is_deterministic() {
1444 let seed = [0x7fu8; 32];
1445 let mut a = [0u8; 64];
1446 let mut b = [0u8; 64];
1447 Ed25519::sign(&seed, b"same input", &mut a).unwrap();
1448 Ed25519::sign(&seed, b"same input", &mut b).unwrap();
1449 assert_eq!(a, b);
1450 }
1451
1452 #[test]
1453 fn rejects_wrong_lengths() {
1454 let mut out = [0u8; 32];
1455 assert!(Ed25519::public_key(&[0u8; 31], &mut out).is_err());
1456 assert!(Ed25519::sign(&[0u8; 32], b"", &mut [0u8; 63]).is_err());
1457 assert!(Ed25519::verify(&[0u8; 32], b"", &[0u8; 63]).is_err());
1458 }
1459
1460 #[test]
1461 fn self_test_passes() {
1462 Ed25519::self_test().unwrap();
1463 }
1464
1465 fn sample_points(n: usize) -> Vec<Point> {
1467 let mut out = Vec::new();
1468 let mut p = basepoint();
1469 for _ in 0..n {
1470 out.push(p);
1471 p = p.double().add(&basepoint());
1472 }
1473 out
1474 }
1475
1476 #[test]
1486 fn the_completed_doubling_agrees_with_the_extended_one() {
1487 for p in sample_points(40) {
1488 let want = p.double();
1489 let got = p.to_projective().double().to_extended();
1490 assert!(got.eq_projective(&want), "doubling disagrees");
1491 let chained = p.to_projective().double().to_projective().double();
1494 let twice = p.double().double();
1495 assert!(chained.to_extended().eq_projective(&twice), "two doublings");
1496 }
1497 }
1498
1499 #[test]
1501 fn niels_addition_agrees_with_the_general_one() {
1502 let pts = sample_points(20);
1503 for p in &pts {
1504 for q in &pts {
1505 let want = p.add(q);
1506 let got = p.add_niels(&q.to_niels()).to_extended();
1507 assert!(got.eq_projective(&want), "add_niels disagrees");
1508
1509 let want_sub = p.add(&q.negate());
1510 let got_sub = p.sub_niels(&q.to_niels()).to_extended();
1511 assert!(got_sub.eq_projective(&want_sub), "sub_niels disagrees");
1512 }
1513 }
1514 }
1515
1516 #[test]
1524 fn affine_niels_addition_agrees_with_the_general_one() {
1525 let pts = sample_points(20);
1526 for p in &pts {
1527 for q in &pts {
1528 let want = p.add(q);
1529 let got = p.add_affine_niels(&q.to_affine_niels()).to_extended();
1530 assert!(got.eq_projective(&want), "add_affine_niels disagrees");
1531
1532 let mut n = q.to_affine_niels();
1534 n.conditional_negate(ic_core::ct::Choice::from_u8(1));
1535 let want_neg = p.add(&q.negate());
1536 let got_neg = p.add_affine_niels(&n).to_extended();
1537 assert!(got_neg.eq_projective(&want_neg), "negated form disagrees");
1538 }
1539 }
1540 }
1541
1542 #[test]
1549 #[ignore = "diagnostic, not a test"]
1550 fn where_verify_spends_its_time() {
1551 use std::time::Instant;
1552
1553 let seed = [7u8; 32];
1554 let key = Ed25519Key::from_seed(&seed).unwrap();
1555 let msg = b"benchmark message";
1556 let mut sig = [0u8; 64];
1557 key.sign(msg, &mut sig).unwrap();
1558 let pk = *key.public_key();
1559
1560 let mut big_r = [0u8; 32];
1561 big_r.copy_from_slice(&sig[..32]);
1562 let mut s_sc = [0u8; 32];
1563 s_sc.copy_from_slice(&sig[32..]);
1564
1565 let n = 2000;
1566 let time = |label: &str, f: &mut dyn FnMut()| {
1567 let mut best = f64::INFINITY;
1568 for _ in 0..5 {
1569 let t = Instant::now();
1570 for _ in 0..n {
1571 f();
1572 }
1573 let e = t.elapsed().as_secs_f64() / n as f64 * 1e6;
1574 if e < best {
1575 best = e;
1576 }
1577 }
1578 println!(" {label:<34} {best:>9.2} us");
1579 best
1580 };
1581
1582 let a_point = Point::decompress(&pk).unwrap();
1583 let k = hash_to_scalar(&[&big_r, &pk, msg]);
1584
1585 println!(
1586 "
1587ed25519 verify, cost breakdown:"
1588 );
1589 let d = time("decompress (x2 per verify)", &mut || {
1590 core::hint::black_box(Point::decompress(&pk));
1591 });
1592 let h = time("hash_to_scalar", &mut || {
1593 core::hint::black_box(hash_to_scalar(&[&big_r, &pk, msg]));
1594 });
1595 let b = time("mul_basepoint (const time)", &mut || {
1596 core::hint::black_box(mul_basepoint(&s_sc));
1597 });
1598 let v = time("double_scalar_mul_vartime", &mut || {
1599 core::hint::black_box(double_scalar_mul_vartime(&a_point.negate(), &k, &s_sc));
1600 });
1601 time(" of which: wnaf(k,5)+wnaf(s,8)", &mut || {
1602 core::hint::black_box(wnaf(&k, 5));
1603 core::hint::black_box(wnaf(&s_sc, 8));
1604 });
1605 time(" of which: odd_a table build", &mut || {
1606 let twice = a_point.double();
1607 let mut odd = [a_point; 8];
1608 for i in 1..8 {
1609 odd[i] = odd[i - 1].add(&twice);
1610 }
1611 let t: [Niels; 8] = core::array::from_fn(|i| odd[i].to_niels());
1612 core::hint::black_box(t);
1613 });
1614 time(" of which: 255 doublings", &mut || {
1615 let mut p = a_point;
1616 for _ in 0..255 {
1617 p = p.double();
1618 }
1619 core::hint::black_box(p);
1620 });
1621 time(" of which: 79 additions", &mut || {
1622 let mut p = a_point;
1623 for _ in 0..79 {
1624 p = p.add(&a_point);
1625 }
1626 core::hint::black_box(p);
1627 });
1628 time("compress (one inversion)", &mut || {
1629 core::hint::black_box(a_point.compress());
1630 });
1631 println!(
1632 " {:<34} {:>9.2} us",
1633 "-- accounted for",
1634 2.0 * d + h + b + v
1635 );
1636
1637 println!(
1639 "
1640field and point primitives, nanoseconds:"
1641 );
1642 let nn = 200_000;
1643 let ns = |label: &str, f: &mut dyn FnMut()| {
1644 let mut best = f64::INFINITY;
1645 for _ in 0..5 {
1646 let t = Instant::now();
1647 for _ in 0..nn {
1648 f();
1649 }
1650 let e = t.elapsed().as_secs_f64() / nn as f64 * 1e9;
1651 if e < best {
1652 best = e;
1653 }
1654 }
1655 println!(" {label:<34} {best:>9.2} ns");
1656 };
1657 let fx = a_point.x;
1658 let fy = a_point.y;
1659 ns("Fe::mul", &mut || {
1660 core::hint::black_box(core::hint::black_box(&fx).mul(core::hint::black_box(&fy)));
1661 });
1662 ns("Fe::square", &mut || {
1663 core::hint::black_box(core::hint::black_box(&fx).square());
1664 });
1665 ns("Fe::add", &mut || {
1666 core::hint::black_box(core::hint::black_box(&fx).add(core::hint::black_box(&fy)));
1667 });
1668 ns("Fe::sub", &mut || {
1669 core::hint::black_box(core::hint::black_box(&fx).sub(core::hint::black_box(&fy)));
1670 });
1671 ns("Fe::neg", &mut || {
1672 core::hint::black_box(core::hint::black_box(&fx).neg());
1673 });
1674 let proj = a_point.to_projective();
1675 let comp = proj.double();
1676 let an = a_point.to_affine_niels();
1677 ns("Projective::double (4S)", &mut || {
1678 core::hint::black_box(core::hint::black_box(&proj).double());
1679 });
1680 ns("Projective::double_projective", &mut || {
1681 core::hint::black_box(core::hint::black_box(proj).double_projective());
1682 });
1683 ns("Completed::to_projective (3M)", &mut || {
1684 core::hint::black_box(core::hint::black_box(&comp).to_projective());
1685 });
1686 ns("Completed::to_extended (4M)", &mut || {
1687 core::hint::black_box(core::hint::black_box(&comp).to_extended());
1688 });
1689 ns("Point::add_affine_niels (3M)", &mut || {
1690 core::hint::black_box(
1691 core::hint::black_box(&a_point).add_affine_niels(core::hint::black_box(&an)),
1692 );
1693 });
1694 ns("Point::double", &mut || {
1695 core::hint::black_box(core::hint::black_box(&a_point).double());
1696 });
1697 ns("Point::add", &mut || {
1698 core::hint::black_box(
1699 core::hint::black_box(&a_point).add(core::hint::black_box(&a_point)),
1700 );
1701 });
1702 }
1703
1704 #[test]
1706 #[ignore = "diagnostic, not a test"]
1707 fn count_the_point_operations() {
1708 let mut doublings = 0usize;
1709 let mut adds_a = 0usize;
1710 let mut adds_b = 0usize;
1711 let mut state = 0x1234_5678_9abc_def0u64;
1712 let trials = 200;
1713 for _ in 0..trials {
1714 let mut kb = [0u8; 32];
1715 for c in kb.chunks_exact_mut(8) {
1716 state ^= state >> 12;
1717 state ^= state << 25;
1718 state ^= state >> 27;
1719 c.copy_from_slice(&state.wrapping_mul(0x2545_f491_4f6c_dd1d).to_le_bytes());
1720 }
1721 kb[31] &= 0x0f;
1722 let na = wnaf(&kb, 5);
1723 let nb = wnaf(&kb, 8);
1724 let mut i = 257;
1725 while i > 0 && na[i] == 0 && nb[i] == 0 {
1726 i -= 1;
1727 }
1728 doublings += i + 1;
1729 adds_a += na.iter().filter(|d| **d != 0).count();
1730 adds_b += nb.iter().filter(|d| **d != 0).count();
1731 }
1732 let d = doublings as f64 / trials as f64;
1733 let aa = adds_a as f64 / trials as f64;
1734 let ab = adds_b as f64 / trials as f64;
1735 println!(
1736 "
1737 per double-scalar multiplication, averaged over {trials} scalars:"
1738 );
1739 println!(" doublings {d:>8.1}");
1740 println!(" additions, w=5 table (A) {aa:>8.1}");
1741 println!(" additions, w=8 table (B) {ab:>8.1}");
1742 println!(" additions, building A {:>8.1}", 8.0);
1743 println!(" ---");
1744 println!(" total additions {:>8.1}", aa + ab + 8.0);
1745 println!(
1746 " field muls, at 4M+4S per doubling and 9M per addition: {:>6.0}",
1747 d * 8.0 + (aa + ab + 8.0) * 9.0
1748 );
1749 println!(
1750 " the same at dalek's 3M+4S and 7M: {:>6.0}",
1751 d * 7.0 + (aa + ab + 8.0) * 7.0
1752 );
1753 }
1754}