1use crate::field::Fe;
11use crate::field_ops::{dispatch, Field};
12use crate::scalar;
13use ic_core::ct::Choice;
14
15#[cfg(feature = "std")]
17mod basepoint_table;
18#[cfg(all(
21 feature = "std",
22 target_arch = "x86_64",
23 not(any(ic_limb32, ic_ec_portable))
24))]
25mod fast;
26#[cfg(all(
28 feature = "std",
29 target_arch = "x86_64",
30 not(any(ic_limb32, ic_ec_portable))
31))]
32mod vector;
33use ic_core::traits::internal::Digest;
34use ic_core::traits::{Algorithm, SelfTest, SignatureScheme};
35use ic_core::{ensure, Result, Zeroize};
36use ic_hash::Sha512;
37
38#[cfg(not(feature = "std"))]
39pub(crate) use self::EdField as Curve;
40#[cfg(feature = "std")]
43pub(crate) use basepoint_table::TableField as Curve;
44
45#[cfg(test)]
47const BASEPOINT_COMPRESSED: [u8; 32] = [
48 0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
49 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
50];
51
52const D_LIMBS: [u64; 5] = [
54 929_955_233_495_203,
55 466_365_720_129_213,
56 1_662_059_464_998_953,
57 2_033_849_074_728_123,
58 1_442_794_654_840_575,
59];
60
61const D2_LIMBS: [u64; 5] = [
63 1_859_910_466_990_425,
64 932_731_440_258_426,
65 1_072_319_116_312_658,
66 1_815_898_335_770_999,
67 633_789_495_995_903,
68];
69
70const SQRT_M1_LIMBS: [u64; 5] = [
72 1_718_705_420_411_056,
73 234_908_883_556_509,
74 2_233_514_472_574_048,
75 2_117_202_627_021_982,
76 765_476_049_583_133,
77];
78
79pub(crate) trait EdField: Field + core::fmt::Debug + 'static {
87 const D: Self;
89 const D2: Self;
91 const SQRT_M1: Self;
93
94 #[cfg(all(feature = "std", any(test, feature = "bench-internals")))]
96 fn to_portable(self) -> Fe {
97 Fe::from_bytes(&self.to_bytes())
98 }
99
100 #[cfg(all(feature = "std", any(test, feature = "bench-internals")))]
102 fn from_portable(x: &Fe) -> Self {
103 Self::from_bytes(&x.to_bytes())
104 }
105}
106
107impl EdField for Fe {
108 const D: Fe = Fe::from_limbs51(D_LIMBS);
109 const D2: Fe = Fe::from_limbs51(D2_LIMBS);
110 const SQRT_M1: Fe = Fe::from_limbs51(SQRT_M1_LIMBS);
111
112 #[cfg(all(feature = "std", any(test, feature = "bench-internals")))]
113 fn to_portable(self) -> Fe {
114 self
115 }
116
117 #[cfg(all(feature = "std", any(test, feature = "bench-internals")))]
118 fn from_portable(x: &Fe) -> Self {
119 *x
120 }
121}
122
123#[cfg(all(target_arch = "x86_64", not(any(ic_limb32, ic_ec_portable))))]
124impl EdField for crate::fe64::Fe64 {
125 const D: Self = crate::fe64::Fe64::from_limbs51(D_LIMBS);
126 const D2: Self = crate::fe64::Fe64::from_limbs51(D2_LIMBS);
127 const SQRT_M1: Self = crate::fe64::Fe64::from_limbs51(SQRT_M1_LIMBS);
128}
129
130#[derive(Clone, Copy, Debug)]
136pub struct Point(Ext<Fe>);
137
138impl Point {
139 pub const IDENTITY: Point = Point(Ext::IDENTITY);
141
142 pub fn add(&self, other: &Point) -> Point {
144 Point(self.0.add(&other.0))
145 }
146
147 pub fn double(&self) -> Point {
149 Point(self.0.double())
150 }
151
152 pub fn mul_scalar(&self, s: &[u8; 32]) -> Point {
154 Point(self.0.mul_scalar(s))
155 }
156
157 pub fn compress(&self) -> [u8; 32] {
159 self.0.compress()
160 }
161
162 pub fn decompress(bytes: &[u8; 32]) -> Option<Point> {
165 Ext::decompress(bytes).map(Point)
166 }
167}
168
169#[derive(Clone, Copy, Debug)]
171pub(crate) struct Ext<F> {
172 x: F,
173 y: F,
174 z: F,
175 t: F,
176}
177
178#[derive(Clone, Copy)]
188pub(crate) struct Completed<F> {
189 x: F,
190 y: F,
191 z: F,
192 t: F,
193}
194
195#[derive(Clone, Copy)]
199pub(crate) struct Projective<F> {
200 x: F,
201 y: F,
202 z: F,
203}
204
205#[derive(Clone, Copy)]
213pub(crate) struct Niels<F> {
214 ypx: F,
215 ymx: F,
216 z: F,
217 t2d: F,
218}
219
220#[cfg(feature = "std")]
226#[derive(Clone, Copy)]
227pub(crate) struct AffineNiels<F> {
228 ypx: F,
229 ymx: F,
230 t2d: F,
231}
232
233#[cfg(any(not(feature = "std"), test))]
237impl<F: EdField> Niels<F> {
238 const IDENTITY: Self = Niels {
240 ypx: F::ONE,
241 ymx: F::ONE,
242 z: F::ONE,
243 t2d: F::ZERO,
244 };
245
246 fn conditional_negate(&mut self, choice: Choice) {
249 let swapped_p = self.ymx;
250 let swapped_m = self.ypx;
251 let nt = self.t2d.neg();
252 F::cmov(&mut self.ypx, &swapped_p, choice);
253 F::cmov(&mut self.ymx, &swapped_m, choice);
254 F::cmov(&mut self.t2d, &nt, choice);
255 }
256
257 fn cmov(&mut self, other: &Niels<F>, choice: Choice) {
258 F::cmov(&mut self.ypx, &other.ypx, choice);
259 F::cmov(&mut self.ymx, &other.ymx, choice);
260 F::cmov(&mut self.z, &other.z, choice);
261 F::cmov(&mut self.t2d, &other.t2d, choice);
262 }
263}
264
265#[cfg(feature = "std")]
266impl<F: EdField> AffineNiels<F> {
267 pub(crate) const IDENTITY: Self = AffineNiels {
269 ypx: F::ONE,
270 ymx: F::ONE,
271 t2d: F::ZERO,
272 };
273
274 pub(crate) fn conditional_negate(&mut self, bit: u64) {
280 let swapped_p = self.ymx;
281 let swapped_m = self.ypx;
282 let nt = self.t2d.neg();
283 F::cmov_bit(&mut self.ypx, &swapped_p, bit);
284 F::cmov_bit(&mut self.ymx, &swapped_m, bit);
285 F::cmov_bit(&mut self.t2d, &nt, bit);
286 }
287
288 pub(crate) fn cmov(&mut self, other: &AffineNiels<F>, bit: u64) {
290 F::cmov_bit(&mut self.ypx, &other.ypx, bit);
291 F::cmov_bit(&mut self.ymx, &other.ymx, bit);
292 F::cmov_bit(&mut self.t2d, &other.t2d, bit);
293 }
294}
295
296impl<F: EdField> Completed<F> {
297 fn to_projective(self) -> Projective<F> {
299 Projective {
300 x: self.x.mul(&self.t),
301 y: self.y.mul(&self.z),
302 z: self.z.mul(&self.t),
303 }
304 }
305
306 fn to_extended(self) -> Ext<F> {
311 Ext {
312 x: self.x.mul(&self.t),
313 y: self.y.mul(&self.z),
314 z: self.z.mul(&self.t),
315 t: self.x.mul(&self.y),
316 }
317 }
318}
319
320impl<F: EdField> Projective<F> {
321 fn to_extended_from_projective(self) -> Ext<F> {
326 Ext {
333 x: self.x.mul(&self.z),
334 y: self.y.mul(&self.z),
335 z: self.z.square(),
336 t: self.x.mul(&self.y),
337 }
338 }
339
340 fn double_projective(self) -> Projective<F> {
350 let xx = self.x.square();
351 let yy = self.y.square();
352 let zz2 = {
353 let t = self.z.square();
354 t.add(&t)
355 };
356 let xy_sq = self.x.add(&self.y).square();
357 let yy_plus_xx = yy.add(&xx);
358 let yy_minus_xx = yy.sub(&xx);
359
360 let cx = xy_sq.sub(&yy_plus_xx);
361 let cy = yy_plus_xx;
362 let cz = yy_minus_xx;
363 let ct = zz2.sub(&yy_minus_xx);
364
365 Projective {
366 x: cx.mul(&ct),
367 y: cy.mul(&cz),
368 z: cz.mul(&ct),
369 }
370 }
371
372 fn double(&self) -> Completed<F> {
378 let xx = self.x.square();
379 let yy = self.y.square();
380 let zz2 = {
381 let t = self.z.square();
382 t.add(&t)
383 };
384 let xy_sq = self.x.add(&self.y).square();
385 let yy_plus_xx = yy.add(&xx);
386 let yy_minus_xx = yy.sub(&xx);
387 Completed {
388 x: xy_sq.sub(&yy_plus_xx),
389 y: yy_plus_xx,
390 z: yy_minus_xx,
391 t: zz2.sub(&yy_minus_xx),
392 }
393 }
394}
395
396impl<F: EdField> Ext<F> {
397 pub(crate) fn from_affine(x: F, y: F) -> Ext<F> {
400 Ext {
401 x,
402 y,
403 z: F::ONE,
404 t: x.mul(&y),
405 }
406 }
407
408 #[cfg(all(feature = "std", any(test, feature = "bench-internals")))]
410 pub(crate) fn convert<G: EdField>(&self) -> Ext<G> {
411 let c = |v: &F| G::from_portable(&v.to_portable());
412 Ext {
413 x: c(&self.x),
414 y: c(&self.y),
415 z: c(&self.z),
416 t: c(&self.t),
417 }
418 }
419
420 pub const IDENTITY: Self = Ext {
421 x: F::ZERO,
422 y: F::ONE,
423 z: F::ONE,
424 t: F::ZERO,
425 };
426
427 pub fn add(&self, other: &Ext<F>) -> Ext<F> {
429 let a = self.y.sub(&self.x).mul(&other.y.sub(&other.x));
430 let b = self.y.add(&self.x).mul(&other.y.add(&other.x));
431 let c = self.t.mul(&F::D2).mul(&other.t);
432 let d = self.z.mul(&other.z);
433 let d = d.add(&d);
434
435 let e = b.sub(&a);
436 let f = d.sub(&c);
437 let g = d.add(&c);
438 let h = b.add(&a);
439
440 Ext {
441 x: e.mul(&f),
442 y: g.mul(&h),
443 t: e.mul(&h),
444 z: f.mul(&g),
445 }
446 }
447
448 pub fn double(&self) -> Ext<F> {
459 let aa = self.x.square();
460 let bb = self.y.square();
461 let c = self.z.square();
462 let c = c.add(&c);
463 let d = aa.neg();
465 let xy = self.x.add(&self.y);
467 let e = xy.square().sub(&aa).sub(&bb);
468 let g = d.add(&bb);
469 let f = g.sub(&c);
470 let h = d.sub(&bb);
471
472 Ext {
473 x: e.mul(&f),
474 y: g.mul(&h),
475 t: e.mul(&h),
476 z: f.mul(&g),
477 }
478 }
479
480 fn to_projective(self) -> Projective<F> {
482 Projective {
483 x: self.x,
484 y: self.y,
485 z: self.z,
486 }
487 }
488
489 fn to_niels(self) -> Niels<F> {
491 Niels {
492 ypx: self.y.add(&self.x),
493 ymx: self.y.sub(&self.x),
494 z: self.z,
495 t2d: self.t.mul(&F::D2),
496 }
497 }
498
499 fn add_niels(&self, other: &Niels<F>) -> Completed<F> {
506 let pp = self.y.add(&self.x).mul(&other.ypx);
507 let mm = self.y.sub(&self.x).mul(&other.ymx);
508 let tt2d = self.t.mul(&other.t2d);
509 let zz = self.z.mul(&other.z);
510 let zz2 = zz.add(&zz);
511 Completed {
512 x: pp.sub(&mm),
513 y: pp.add(&mm),
514 z: zz2.add(&tt2d),
515 t: zz2.sub(&tt2d),
516 }
517 }
518
519 fn sub_niels(&self, other: &Niels<F>) -> Completed<F> {
525 let pp = self.y.add(&self.x).mul(&other.ymx);
526 let mm = self.y.sub(&self.x).mul(&other.ypx);
527 let tt2d = self.t.mul(&other.t2d);
528 let zz = self.z.mul(&other.z);
529 let zz2 = zz.add(&zz);
530 Completed {
531 x: pp.sub(&mm),
532 y: pp.add(&mm),
533 z: zz2.sub(&tt2d),
534 t: zz2.add(&tt2d),
535 }
536 }
537
538 #[cfg(feature = "std")]
544 pub(crate) fn to_affine_niels(self) -> AffineNiels<F> {
545 let z_inv = self.z.invert();
546 let x = self.x.mul(&z_inv);
547 let y = self.y.mul(&z_inv);
548 AffineNiels {
549 ypx: y.add(&x),
550 ymx: y.sub(&x),
551 t2d: x.mul(&y).mul(&F::D2),
552 }
553 }
554
555 #[cfg(feature = "std")]
560 pub(crate) fn add_affine_niels(&self, other: &AffineNiels<F>) -> Completed<F> {
561 let pp = self.y.add(&self.x).mul(&other.ypx);
562 let mm = self.y.sub(&self.x).mul(&other.ymx);
563 let tt2d = self.t.mul(&other.t2d);
564 let zz2 = self.z.add(&self.z);
565 Completed {
566 x: pp.sub(&mm),
567 y: pp.add(&mm),
568 z: zz2.add(&tt2d),
569 t: zz2.sub(&tt2d),
570 }
571 }
572
573 #[cfg(feature = "std")]
582 pub(crate) fn sub_affine_niels(&self, other: &AffineNiels<F>) -> Completed<F> {
583 let pp = self.y.add(&self.x).mul(&other.ymx);
584 let mm = self.y.sub(&self.x).mul(&other.ypx);
585 let tt2d = self.t.mul(&other.t2d);
586 let zz2 = self.z.add(&self.z);
587 Completed {
588 x: pp.sub(&mm),
589 y: pp.add(&mm),
590 z: zz2.sub(&tt2d),
591 t: zz2.add(&tt2d),
592 }
593 }
594
595 fn cmov(&mut self, other: &Ext<F>, choice: Choice) {
597 F::cmov(&mut self.x, &other.x, choice);
598 F::cmov(&mut self.y, &other.y, choice);
599 F::cmov(&mut self.z, &other.z, choice);
600 F::cmov(&mut self.t, &other.t, choice);
601 }
602
603 pub fn mul_scalar(&self, s: &[u8; 32]) -> Ext<F> {
609 let mut acc = Ext::IDENTITY;
610 for i in (0..256).rev() {
611 acc = acc.double();
612 let sum = acc.add(self);
613 let bit = Choice::from_u8((s[i / 8] >> (i % 8)) & 1);
614 acc.cmov(&sum, bit);
615 }
616 acc
617 }
618
619 fn negate(&self) -> Ext<F> {
621 Ext {
622 x: self.x.neg(),
623 y: self.y,
624 z: self.z,
625 t: self.t.neg(),
626 }
627 }
628
629 fn has_small_order(&self) -> bool {
646 self.double()
647 .double()
648 .double()
649 .eq_projective(&Ext::<F>::IDENTITY)
650 }
651
652 fn eq_projective(&self, other: &Ext<F>) -> bool {
653 self.x.mul(&other.z).to_bytes() == other.x.mul(&self.z).to_bytes()
654 && self.y.mul(&other.z).to_bytes() == other.y.mul(&self.z).to_bytes()
655 }
656
657 pub fn compress(&self) -> [u8; 32] {
659 let z_inv = self.z.invert();
660 let x = self.x.mul(&z_inv);
661 let y = self.y.mul(&z_inv);
662 let mut out = y.to_bytes();
663 out[31] |= x.is_negative().unwrap_u8() << 7;
665 out
666 }
667
668 pub fn decompress(bytes: &[u8; 32]) -> Option<Ext<F>> {
676 let start = Decompression::<F>::start(bytes)?;
677 let pow = start.uv7.pow22523();
678 start.finish(&pow)
679 }
680
681 fn decompress_pair(a: &[u8; 32], b: &[u8; 32]) -> (Option<Ext<F>>, Option<Ext<F>>) {
688 match (Decompression::<F>::start(a), Decompression::<F>::start(b)) {
689 (Some(sa), Some(sb)) => {
690 let (pa, pb) = F::pow22523_pair(&sa.uv7, &sb.uv7);
691 (sa.finish(&pa), sb.finish(&pb))
692 }
693 (sa, sb) => (
694 sa.and_then(|s| s.finish(&s.uv7.pow22523())),
695 sb.and_then(|s| s.finish(&s.uv7.pow22523())),
696 ),
697 }
698 }
699}
700
701struct Decompression<F> {
704 y: F,
705 u: F,
706 v: F,
707 uv3: F,
708 uv7: F,
709 sign: Choice,
710}
711
712impl<F: EdField> Decompression<F> {
713 fn start(bytes: &[u8; 32]) -> Option<Self> {
714 let sign = Choice::from_u8(bytes[31] >> 7);
715 let mut y_bytes = *bytes;
716 y_bytes[31] &= 0x7f;
717 let y = F::from_bytes(&y_bytes);
718 if y.to_bytes() != y_bytes {
720 return None;
721 }
722
723 let y2 = y.square();
725 let u = y2.sub(&F::ONE);
726 let v = y2.mul(&F::D).add(&F::ONE);
727
728 let v3 = v.square().mul(&v);
730 let v7 = v3.square().mul(&v);
731 Some(Self {
732 y,
733 u,
734 v,
735 uv3: u.mul(&v3),
736 uv7: u.mul(&v7),
737 sign,
738 })
739 }
740
741 fn finish(&self, pow: &F) -> Option<Ext<F>> {
743 let (y, u, v, sign) = (self.y, self.u, self.v, self.sign);
744 let mut x = self.uv3.mul(pow);
745
746 let check = v.mul(&x.square());
747 let correct = check.ct_eq(&u);
748 let flipped = check.ct_eq(&u.neg());
749 if !bool::from(correct.or(flipped)) {
750 return None;
752 }
753 let alt = x.mul(&F::SQRT_M1);
755 F::cmov(&mut x, &alt, flipped.and(correct.not()));
756
757 if bool::from(x.is_zero()) && bool::from(sign) {
759 return None;
760 }
761 let neg = x.neg();
763 let wrong_sign = Choice::from_u8(x.is_negative().unwrap_u8() ^ sign.unwrap_u8());
764 F::cmov(&mut x, &neg, wrong_sign);
765
766 Some(Ext {
767 x,
768 y,
769 z: F::ONE,
770 t: x.mul(&y),
771 })
772 }
773}
774
775#[cfg(feature = "std")]
778pub(crate) fn prepare_tables() {
779 fn prepare<F: Curve>() {
780 basepoint_table::prepare::<F>();
781 }
782 dispatch!(prepare())
783}
784
785fn mul_basepoint<F: Curve>(scalar: &[u8; 32]) -> Ext<F> {
791 #[cfg(feature = "std")]
792 {
793 basepoint_table::mul::<F>(scalar)
794 }
795 #[cfg(not(feature = "std"))]
796 {
797 mul_scalar_windowed(&basepoint::<F>(), scalar)
798 }
799}
800
801fn mul_basepoint_compressed(scalar: &[u8; 32]) -> [u8; 32] {
804 fn on<F: Curve>(scalar: &[u8; 32]) -> [u8; 32] {
805 mul_basepoint::<F>(scalar).compress()
806 }
807 dispatch!(on(scalar))
808}
809
810#[cfg(feature = "bench-internals")]
834#[doc(hidden)]
835pub fn double_scalar_mul_vartime_for_bench(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
836 fn on<F: Curve>(a: &Ext<Fe>, k: &[u8; 32], s: &[u8; 32]) -> Point {
837 Point(double_scalar_mul_vartime(&a.convert::<F>(), k, s).convert::<Fe>())
838 }
839 dispatch!(on(&a.0, k, s))
840}
841
842#[cfg(any(not(feature = "std"), test))]
858fn mul_scalar_windowed<F: EdField>(p: &Ext<F>, scalar: &[u8; 32]) -> Ext<F> {
859 let mut multiples = [*p; 8];
860 for i in 1..8 {
861 multiples[i] = multiples[i - 1].add(p);
862 }
863 let table: [Niels<F>; 8] = core::array::from_fn(|i| multiples[i].to_niels());
864
865 let select = |digit: i8| -> Niels<F> {
867 let negative = Choice::from_u8((digit as u8) >> 7);
868 let magnitude = ((digit as i16 ^ (digit as i16 >> 7)) - (digit as i16 >> 7)) as u8;
869 let mut out = Niels::IDENTITY;
870 for (i, entry) in table.iter().enumerate() {
871 out.cmov(entry, Choice::from_u8(u8::from(magnitude == (i as u8 + 1))));
872 }
873 out.conditional_negate(negative);
874 out
875 };
876
877 let digits = signed_digits(scalar);
878 let mut acc = Ext::IDENTITY.add_niels(&select(digits[63])).to_extended();
879 for i in (0..63).rev() {
880 let mut q = acc.to_projective();
883 for _ in 0..3 {
884 q = q.double_projective();
885 }
886 acc = q.double().to_extended();
887 acc = acc.add_niels(&select(digits[i])).to_extended();
888 }
889 acc
890}
891
892fn odd_multiples<F: EdField>(p: &Ext<F>) -> [Niels<F>; 8] {
894 let twice = p.double();
895 let mut odd = [*p; 8];
896 for i in 1..8 {
897 odd[i] = odd[i - 1].add(&twice);
898 }
899 core::array::from_fn(|i| odd[i].to_niels())
900}
901
902fn signed_digits(scalar: &[u8; 32]) -> [i8; 64] {
918 debug_assert!(
919 scalar[31] <= 127,
920 "the top digit can only absorb the final carry for scalars below 2^255"
921 );
922
923 let mut nibbles = [0i8; 64];
924 for (i, byte) in scalar.iter().enumerate() {
925 nibbles[i * 2] = (byte & 0x0f) as i8;
926 nibbles[i * 2 + 1] = (byte >> 4) as i8;
927 }
928
929 for i in 0..63 {
930 let carry = (nibbles[i] + 8) >> 4;
931 nibbles[i] -= carry << 4;
932 nibbles[i + 1] += carry;
933 }
934 nibbles
935}
936
937#[cfg(feature = "std")]
938fn double_scalar_mul_vartime<F: Curve>(a: &Ext<F>, k: &[u8; 32], s: &[u8; 32]) -> Ext<F> {
939 F::double_mul(a, k, s)
940}
941
942#[cfg(feature = "std")]
945fn double_scalar_mul_vartime_generic<F: Curve>(a: &Ext<F>, k: &[u8; 32], s: &[u8; 32]) -> Ext<F> {
946 basepoint_table::prepare::<F>();
947 shared_doublings(a, k, &wnaf(s, 8), |e, digit| {
948 let n = basepoint_table::odd_multiple::<F>((digit.unsigned_abs() as usize) / 2);
949 if digit > 0 {
950 e.add_affine_niels(n)
951 } else {
952 e.sub_affine_niels(n)
953 }
954 })
955}
956
957#[cfg(any(not(feature = "std"), test))]
965fn double_scalar_mul_vartime_no_table<F: EdField>(
966 a: &Ext<F>,
967 k: &[u8; 32],
968 s: &[u8; 32],
969) -> Ext<F> {
970 let odd_b = odd_multiples(&basepoint::<F>());
971 shared_doublings(a, k, &wnaf(s, 5), |e, digit| {
972 let n = &odd_b[(digit.unsigned_abs() as usize) / 2];
973 if digit > 0 {
974 e.add_niels(n)
975 } else {
976 e.sub_niels(n)
977 }
978 })
979}
980
981#[inline(always)]
984fn shared_doublings<F: EdField>(
985 a: &Ext<F>,
986 k: &[u8; 32],
987 naf_b: &[i8; 258],
988 add_b: impl Fn(&Ext<F>, i8) -> Completed<F>,
989) -> Ext<F> {
990 let odd_a = odd_multiples(a);
992 let naf_a = wnaf(k, 5);
993
994 let mut i = 257;
997 while i > 0 && naf_a[i] == 0 && naf_b[i] == 0 {
998 i -= 1;
999 }
1000
1001 let mut acc = Ext::IDENTITY.to_projective();
1006 loop {
1007 if naf_a[i] == 0 && naf_b[i] == 0 {
1010 acc = acc.double_projective();
1011 if i == 0 {
1012 return acc.to_extended_from_projective();
1013 }
1014 i -= 1;
1015 continue;
1016 }
1017 let mut t = acc.double();
1018 if naf_a[i] != 0 {
1019 let e = t.to_extended();
1020 let n = &odd_a[(naf_a[i].unsigned_abs() as usize) / 2];
1021 t = if naf_a[i] > 0 {
1022 e.add_niels(n)
1023 } else {
1024 e.sub_niels(n)
1025 };
1026 }
1027 if naf_b[i] != 0 {
1028 t = add_b(&t.to_extended(), naf_b[i]);
1029 }
1030 if i == 0 {
1031 return t.to_extended();
1032 }
1033 acc = t.to_projective();
1034 i -= 1;
1035 }
1036}
1037
1038#[cfg(feature = "bench-internals")]
1041#[doc(hidden)]
1042pub fn mul_basepoint_for_bench(scalar: &[u8; 32]) -> Point {
1043 fn on<F: Curve>(scalar: &[u8; 32]) -> Point {
1044 Point(mul_basepoint::<F>(scalar).convert::<Fe>())
1045 }
1046 dispatch!(on(scalar))
1047}
1048
1049fn basepoint<F: EdField>() -> Ext<F> {
1051 Ext {
1052 x: F::from_limbs51(BASEPOINT_X),
1053 y: F::from_limbs51(BASEPOINT_Y),
1054 z: F::ONE,
1055 t: F::from_limbs51(BASEPOINT_T),
1056 }
1057}
1058
1059const BASEPOINT_X: [u64; 5] = [
1068 1_738_742_601_995_546,
1069 1_146_398_526_822_698,
1070 2_070_867_633_025_821,
1071 562_264_141_797_630,
1072 587_772_402_128_613,
1073];
1074const BASEPOINT_Y: [u64; 5] = [
1075 1_801_439_850_948_184,
1076 1_351_079_888_211_148,
1077 450_359_962_737_049,
1078 900_719_925_474_099,
1079 1_801_439_850_948_198,
1080];
1081const BASEPOINT_T: [u64; 5] = [
1082 1_841_354_044_333_475,
1083 16_398_895_984_059,
1084 755_974_180_946_558,
1085 900_171_276_175_154,
1086 1_821_297_809_914_039,
1087];
1088
1089pub struct Ed25519;
1091
1092impl Algorithm for Ed25519 {
1093 const ID: &'static str = "ed25519";
1094 const NAME: &'static str = "Ed25519";
1095}
1096
1097fn expand_seed(seed: &[u8]) -> ([u8; 32], [u8; 32]) {
1099 let h = Sha512::digest(seed);
1100 let mut a = [0u8; 32];
1101 let mut prefix = [0u8; 32];
1102 a.copy_from_slice(&h.as_ref()[..32]);
1103 prefix.copy_from_slice(&h.as_ref()[32..]);
1104 a[0] &= 248;
1105 a[31] &= 127;
1106 a[31] |= 64;
1107 (a, prefix)
1108}
1109
1110fn wnaf(scalar: &[u8; 32], w: u32) -> [i8; 258] {
1120 debug_assert!((2..=8).contains(&w), "window width out of range");
1121 let half = 1i64 << (w - 1);
1122 let full = 1i64 << w;
1123 let mask = (full - 1) as u64;
1124
1125 let mut naf = [0i8; 258];
1126 let mut k = [0u64; 5];
1133 for (i, limb) in k.iter_mut().take(4).enumerate() {
1134 let mut b = [0u8; 8];
1135 b.copy_from_slice(&scalar[i * 8..i * 8 + 8]);
1136 *limb = u64::from_le_bytes(b);
1137 }
1138
1139 let mut i = 0;
1140 while k.iter().any(|&x| x != 0) {
1141 if k[0] & 1 == 1 {
1142 let mut d = (k[0] & mask) as i64;
1143 if d >= half {
1144 d -= full;
1145 }
1146 naf[i] = d as i8;
1147 if d > 0 {
1148 sub_u64(&mut k, d as u64);
1149 } else {
1150 add_u64(&mut k, d.unsigned_abs());
1151 }
1152 }
1153 shr1(&mut k);
1154 i += 1;
1155 }
1156 naf
1157}
1158
1159fn sub_u64(k: &mut [u64; 5], v: u64) {
1161 let (d, mut borrow) = k[0].overflowing_sub(v);
1162 k[0] = d;
1163 for limb in k.iter_mut().skip(1) {
1164 if !borrow {
1165 break;
1166 }
1167 let (d, b) = limb.overflowing_sub(1);
1168 *limb = d;
1169 borrow = b;
1170 }
1171}
1172
1173fn add_u64(k: &mut [u64; 5], v: u64) {
1175 let (d, mut carry) = k[0].overflowing_add(v);
1176 k[0] = d;
1177 for limb in k.iter_mut().skip(1) {
1178 if !carry {
1179 break;
1180 }
1181 let (d, c) = limb.overflowing_add(1);
1182 *limb = d;
1183 carry = c;
1184 }
1185}
1186
1187fn shr1(k: &mut [u64; 5]) {
1189 for i in 0..4 {
1190 k[i] = (k[i] >> 1) | (k[i + 1] << 63);
1191 }
1192 k[4] >>= 1;
1193}
1194
1195fn hash_to_scalar(parts: &[&[u8]]) -> [u8; 32] {
1197 let mut h = Sha512::new();
1198 for p in parts {
1199 h.update(p);
1200 }
1201 let digest = h.finalize();
1202 let mut wide = [0u8; 64];
1203 wide.copy_from_slice(digest.as_ref());
1204 scalar::reduce_wide(&wide)
1205}
1206
1207pub struct Ed25519Key {
1224 scalar: [u8; 32],
1226 prefix: [u8; 32],
1228 public: [u8; 32],
1230}
1231
1232impl Drop for Ed25519Key {
1233 fn drop(&mut self) {
1234 self.scalar.zeroize();
1235 self.prefix.zeroize();
1236 }
1238}
1239
1240impl Ed25519Key {
1241 pub fn from_seed(seed: &[u8]) -> Result<Self> {
1243 ic_core::module::operational()?;
1244 ensure!(seed.len() == 32, InvalidLength, "ed25519 seed");
1245 let (scalar, prefix) = expand_seed(seed);
1246 let public = mul_basepoint_compressed(&scalar);
1247 Ok(Self {
1248 scalar,
1249 prefix,
1250 public,
1251 })
1252 }
1253
1254 pub fn public_key(&self) -> &[u8; 32] {
1256 &self.public
1257 }
1258
1259 pub fn sign(&self, message: &[u8], signature: &mut [u8]) -> Result<()> {
1261 ic_core::module::operational()?;
1262 ensure!(
1263 signature.len() == 64,
1264 InvalidLength,
1265 "ed25519 signature buffer"
1266 );
1267
1268 let mut r = hash_to_scalar(&[&self.prefix, message]);
1271 let big_r = mul_basepoint_compressed(&r);
1272
1273 let k = hash_to_scalar(&[&big_r, &self.public, message]);
1274 let s = scalar::mul_add(&k, &self.scalar, &r);
1275
1276 signature[..32].copy_from_slice(&big_r);
1277 signature[32..].copy_from_slice(&s);
1278 r.zeroize();
1279 Ok(())
1280 }
1281}
1282
1283impl SignatureScheme for Ed25519 {
1284 const PRIVATE_KEY_LEN: usize = 32;
1285 const PUBLIC_KEY_LEN: usize = 32;
1286 const SIGNATURE_LEN: usize = 64;
1287
1288 fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()> {
1289 ic_core::module::operational()?;
1290 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1291 ensure!(out.len() == 32, InvalidLength, "ed25519 public key buffer");
1292 let (mut a, mut prefix) = expand_seed(private_key);
1293 out.copy_from_slice(&mul_basepoint_compressed(&a));
1294 a.zeroize();
1295 prefix.zeroize();
1296 Ok(())
1297 }
1298
1299 fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()> {
1300 ic_core::module::operational()?;
1301 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1302 ensure!(
1303 signature.len() == 64,
1304 InvalidLength,
1305 "ed25519 signature buffer"
1306 );
1307
1308 Ed25519Key::from_seed(private_key)?.sign(message, signature)
1312 }
1313
1314 fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()> {
1315 ic_core::module::operational()?;
1316 if public_key.len() == 32 && signature.len() == 64 {
1323 let mut pk = [0u8; 32];
1324 pk.copy_from_slice(public_key);
1325 let mut big_r = [0u8; 32];
1326 big_r.copy_from_slice(&signature[..32]);
1327 let mut s = [0u8; 32];
1328 s.copy_from_slice(&signature[32..]);
1329 if scalar::is_canonical(&s) {
1330 let k = hash_to_scalar(&[&big_r, &pk, message]);
1331 return match dispatch!(check_one_shot(&pk, &big_r, &k, &s)) {
1334 OneShot::Key(KeyDecode::Malformed) => Err(ic_core::err!(
1335 MalformedEncoding,
1336 "ed25519 public key is not on the curve or not canonical"
1337 )),
1338 OneShot::Key(_) => Err(ic_core::err!(
1339 InvalidParameter,
1340 "ed25519 public key has small order"
1341 )),
1342 OneShot::Signature(c) => c.into_result(),
1343 };
1344 }
1345 }
1346 Ed25519VerifyKey::from_bytes(public_key)?.verify(message, signature)
1347 }
1348}
1349
1350pub struct Ed25519VerifyKey {
1364 compressed: [u8; 32],
1366 neg_a: ([u8; 32], [u8; 32]),
1369}
1370
1371enum KeyDecode {
1373 Malformed,
1375 SmallOrder,
1377 Negated([u8; 32], [u8; 32]),
1379}
1380
1381fn decode_key<F: Curve>(bytes: &[u8; 32]) -> KeyDecode {
1383 match Ext::<F>::decompress(bytes) {
1384 None => KeyDecode::Malformed,
1385 Some(a) if a.has_small_order() => KeyDecode::SmallOrder,
1386 Some(a) => {
1388 let n = a.negate();
1389 KeyDecode::Negated(n.x.to_bytes(), n.y.to_bytes())
1390 }
1391 }
1392}
1393
1394#[derive(PartialEq, Eq, Debug)]
1396enum Check {
1397 RMalformed,
1399 RSmallOrder,
1401 Mismatch,
1403 Holds,
1405}
1406
1407impl Check {
1408 fn into_result(self) -> Result<()> {
1409 match self {
1410 Check::RMalformed => Err(ic_core::err!(
1411 MalformedEncoding,
1412 "ed25519 signature R is not on the curve or not canonical"
1413 )),
1414 Check::RSmallOrder => Err(ic_core::err!(
1415 MalformedEncoding,
1416 "ed25519 signature R has small order"
1417 )),
1418 Check::Mismatch => Err(ic_core::err!(AuthenticationFailed, "ed25519")),
1419 Check::Holds => Ok(()),
1420 }
1421 }
1422}
1423
1424enum OneShot {
1427 Key(KeyDecode),
1428 Signature(Check),
1429}
1430
1431fn check_one_shot<F: Curve>(
1434 pk: &[u8; 32],
1435 big_r: &[u8; 32],
1436 k: &[u8; 32],
1437 s: &[u8; 32],
1438) -> OneShot {
1439 let (a, r) = Ext::<F>::decompress_pair(pk, big_r);
1440 let a = match a {
1441 None => return OneShot::Key(KeyDecode::Malformed),
1442 Some(a) if a.has_small_order() => return OneShot::Key(KeyDecode::SmallOrder),
1443 Some(a) => a,
1444 };
1445 OneShot::Signature(equation_holds(&a.negate(), r, k, s))
1446}
1447
1448fn check_equation<F: Curve>(
1450 neg_a: &([u8; 32], [u8; 32]),
1451 big_r: &[u8; 32],
1452 k: &[u8; 32],
1453 s: &[u8; 32],
1454) -> Check {
1455 let a = Ext::from_affine(F::from_bytes(&neg_a.0), F::from_bytes(&neg_a.1));
1456 equation_holds(&a, Ext::<F>::decompress(big_r), k, s)
1457}
1458
1459fn equation_holds<F: Curve>(
1461 neg_a: &Ext<F>,
1462 r_point: Option<Ext<F>>,
1463 k: &[u8; 32],
1464 s: &[u8; 32],
1465) -> Check {
1466 let r_point = match r_point {
1467 None => return Check::RMalformed,
1468 Some(p) => p,
1469 };
1470 if r_point.has_small_order() {
1474 return Check::RSmallOrder;
1475 }
1476 #[cfg(feature = "std")]
1480 let lhs = double_scalar_mul_vartime(neg_a, k, s);
1481 #[cfg(not(feature = "std"))]
1482 let lhs = double_scalar_mul_vartime_no_table(neg_a, k, s);
1483 if lhs.eq_projective(&r_point) {
1484 Check::Holds
1485 } else {
1486 Check::Mismatch
1487 }
1488}
1489
1490impl Ed25519VerifyKey {
1491 pub fn from_bytes(public_key: &[u8]) -> Result<Self> {
1502 ensure!(public_key.len() == 32, InvalidLength, "ed25519 public key");
1503 let mut compressed = [0u8; 32];
1504 compressed.copy_from_slice(public_key);
1505 match dispatch!(decode_key(&compressed)) {
1506 KeyDecode::Malformed => Err(ic_core::err!(
1507 MalformedEncoding,
1508 "ed25519 public key is not on the curve or not canonical"
1509 )),
1510 KeyDecode::SmallOrder => Err(ic_core::err!(
1511 InvalidParameter,
1512 "ed25519 public key has small order"
1513 )),
1514 KeyDecode::Negated(x, y) => Ok(Self {
1515 compressed,
1516 neg_a: (x, y),
1517 }),
1518 }
1519 }
1520
1521 pub fn as_bytes(&self) -> &[u8; 32] {
1523 &self.compressed
1524 }
1525
1526 pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> {
1528 ic_core::module::operational()?;
1529 ensure!(signature.len() == 64, InvalidLength, "ed25519 signature");
1530
1531 let mut big_r = [0u8; 32];
1532 big_r.copy_from_slice(&signature[..32]);
1533 let mut s = [0u8; 32];
1534 s.copy_from_slice(&signature[32..]);
1535
1536 ensure!(
1540 scalar::is_canonical(&s),
1541 MalformedEncoding,
1542 "ed25519 signature S is not reduced"
1543 );
1544
1545 let k = hash_to_scalar(&[&big_r, &self.compressed, message]);
1546 dispatch!(check_equation(&self.neg_a, &big_r, &k, &s)).into_result()
1547 }
1548}
1549
1550impl SelfTest for Ed25519 {
1551 fn self_test() -> Result<()> {
1552 let mut seed = [0u8; 32];
1554 ic_core::codec::hex_decode(
1555 b"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1556 &mut seed,
1557 )?;
1558 let mut want_pk = [0u8; 32];
1559 ic_core::codec::hex_decode(
1560 b"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1561 &mut want_pk,
1562 )?;
1563 let mut want_sig = [0u8; 64];
1564 ic_core::codec::hex_decode(
1565 b"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1566 &mut want_sig,
1567 )?;
1568
1569 let mut pk = [0u8; 32];
1570 <Self as SignatureScheme>::public_key(&seed, &mut pk)?;
1571 ensure!(
1572 ic_core::ct::verify(&want_pk, &pk),
1573 SelfTestFailed,
1574 "ed25519"
1575 );
1576
1577 let mut sig = [0u8; 64];
1578 <Self as SignatureScheme>::sign(&seed, b"", &mut sig)?;
1579 ensure!(
1580 ic_core::ct::verify(&want_sig, &sig),
1581 SelfTestFailed,
1582 "ed25519"
1583 );
1584
1585 <Self as SignatureScheme>::verify(&pk, b"", &sig)?;
1586
1587 sig[0] ^= 1;
1589 ensure!(
1590 <Self as SignatureScheme>::verify(&pk, b"", &sig).is_err(),
1591 SelfTestFailed,
1592 "ed25519"
1593 );
1594 Ok(())
1595 }
1596}
1597
1598#[cfg(test)]
1599mod tests {
1600 use super::*;
1601
1602 type P = Ext<Fe>;
1604 const D: Fe = <Fe as EdField>::D;
1605 const D2: Fe = <Fe as EdField>::D2;
1606 const SQRT_M1: Fe = <Fe as EdField>::SQRT_M1;
1607
1608 fn basepoint() -> P {
1609 super::basepoint::<Fe>()
1610 }
1611
1612 fn mul_basepoint(s: &[u8; 32]) -> P {
1613 super::mul_basepoint::<Fe>(s)
1614 }
1615
1616 fn neg_a(vk: &Ed25519VerifyKey) -> P {
1618 P::from_affine(Fe::from_bytes(&vk.neg_a.0), Fe::from_bytes(&vk.neg_a.1))
1619 }
1620 use ic_core::codec::{hex, unhex};
1621
1622 #[test]
1623 fn curve_constants_are_correct() {
1624 let d = Fe::from_u64(121_665)
1626 .neg()
1627 .mul(&Fe::from_u64(121_666).invert());
1628 assert_eq!(hex(&D.to_bytes()), hex(&d.to_bytes()), "d");
1629 assert_eq!(hex(&D2.to_bytes()), hex(&d.add(&d).to_bytes()), "2d");
1630 assert_eq!(
1632 hex(&SQRT_M1.square().to_bytes()),
1633 hex(&Fe::ONE.neg().to_bytes()),
1634 "sqrt(-1)"
1635 );
1636 }
1637
1638 #[test]
1639 fn basepoint_has_the_expected_coordinates() {
1640 let b = basepoint();
1641 let expected_y = Fe::from_u64(4).mul(&Fe::from_u64(5).invert());
1643 let z_inv = b.z.invert();
1644 assert_eq!(
1645 hex(&b.y.mul(&z_inv).to_bytes()),
1646 hex(&expected_y.to_bytes())
1647 );
1648 assert_eq!(hex(&b.compress()), hex(&BASEPOINT_COMPRESSED));
1649 }
1650
1651 #[test]
1658 fn doubling_agrees_with_adding_a_point_to_itself() {
1659 let mut p = basepoint();
1660 let mut checked = 0;
1661 for _ in 0..16 {
1662 assert_eq!(
1663 p.double().compress(),
1664 p.add(&p).compress(),
1665 "dedicated doubling and self-addition differ"
1666 );
1667 p = p.add(&basepoint());
1668 checked += 1;
1669 }
1670 assert_eq!(checked, 16, "the comparison did not run");
1671
1672 assert_eq!(P::IDENTITY.double().compress(), P::IDENTITY.compress());
1675 }
1676
1677 #[test]
1685 fn projective_equality_agrees_with_compressed_equality() {
1686 let b = basepoint();
1687 let mut points = std::vec![P::IDENTITY, b];
1688 let mut p = b;
1689 for _ in 0..6 {
1690 p = p.double();
1691 points.push(p);
1692 }
1693
1694 let mut checked = 0;
1695 for (i, a) in points.iter().enumerate() {
1696 for (j, c) in points.iter().enumerate() {
1697 let projective = a.eq_projective(c);
1698 let compressed = a.compress() == c.compress();
1699 assert_eq!(
1700 projective, compressed,
1701 "projective and compressed equality differ for {i} vs {j}"
1702 );
1703 checked += 1;
1704 }
1705 }
1706 assert_eq!(checked, 64, "the comparison did not run");
1707
1708 let scaled = b.add(&P::IDENTITY);
1711 assert!(b.eq_projective(&scaled), "equal points with different Z");
1712 assert_eq!(b.compress(), scaled.compress());
1713 }
1714
1715 #[test]
1716 fn group_law_is_consistent() {
1717 let b = basepoint();
1718 assert_eq!(hex(&b.add(&P::IDENTITY).compress()), hex(&b.compress()));
1720 let mut two = [0u8; 32];
1722 two[0] = 2;
1723 assert_eq!(
1724 hex(&b.double().compress()),
1725 hex(&b.mul_scalar(&two).compress())
1726 );
1727 let mut three = [0u8; 32];
1729 three[0] = 3;
1730 assert_eq!(
1731 hex(&b.double().add(&b).compress()),
1732 hex(&b.mul_scalar(&three).compress())
1733 );
1734 }
1735
1736 #[test]
1737 fn order_of_the_basepoint_is_l() {
1738 assert_eq!(
1740 hex(&basepoint().mul_scalar(&scalar::L).compress()),
1741 hex(&P::IDENTITY.compress())
1742 );
1743 }
1744
1745 #[test]
1746 fn compression_roundtrips() {
1747 let b = basepoint();
1748 for k in [1u8, 2, 3, 47, 200] {
1749 let mut s = [0u8; 32];
1750 s[0] = k;
1751 let p = b.mul_scalar(&s);
1752 let c = p.compress();
1753 let d = P::decompress(&c).expect("valid point");
1754 assert_eq!(hex(&d.compress()), hex(&c), "k = {k}");
1755 }
1756 }
1757
1758 #[test]
1763 fn a_signature_under_the_identity_key_is_refused() {
1764 let identity = P::IDENTITY.compress();
1765 let s = scalar::reduce(&[7u8; 32]);
1766 let r = mul_basepoint(&s).compress();
1767 let k = hash_to_scalar(&[&r, &identity, b"any message"]);
1768 let neg_a = P::IDENTITY.negate();
1769 let lhs = double_scalar_mul_vartime(&neg_a, &k, &s);
1770 assert!(
1771 lhs.eq_projective(&P::decompress(&r).unwrap()),
1772 "the forgery satisfies the equation"
1773 );
1774
1775 let err = Ed25519VerifyKey::from_bytes(&identity).err().unwrap();
1776 assert_eq!(err.kind(), ic_core::ErrorKind::InvalidParameter);
1777 let mut sig = [0u8; 64];
1778 sig[..32].copy_from_slice(&r);
1779 sig[32..].copy_from_slice(&s);
1780 assert!(Ed25519::verify(&identity, b"any message", &sig).is_err());
1781 }
1782
1783 #[test]
1787 fn a_signature_with_an_identity_r_is_refused() {
1788 let key = Ed25519Key::from_seed(&[0x42u8; 32]).unwrap();
1789 let r = P::IDENTITY.compress();
1790 let message = b"identity R";
1791 let k = hash_to_scalar(&[&r, &key.public, message]);
1792 let s = scalar::mul_add(&k, &key.scalar, &[0u8; 32]);
1793 let mut sig = [0u8; 64];
1794 sig[..32].copy_from_slice(&r);
1795 sig[32..].copy_from_slice(&s);
1796
1797 let vk = Ed25519VerifyKey::from_bytes(&key.public).unwrap();
1798 let lhs = double_scalar_mul_vartime(&neg_a(&vk), &k, &s);
1799 assert!(
1800 lhs.eq_projective(&P::IDENTITY),
1801 "the signature satisfies the equation"
1802 );
1803 let err = vk.verify(message, &sig).unwrap_err();
1804 assert_eq!(err.kind(), ic_core::ErrorKind::MalformedEncoding);
1805 }
1806
1807 #[test]
1811 fn non_canonical_encodings_do_not_decode() {
1812 for sign in [0u8, 0x80] {
1813 let mut canonical = [0u8; 32];
1814 canonical[0] = 3;
1815 canonical[31] |= sign;
1816 let point = P::decompress(&canonical).expect("y = 3 is on the curve");
1817 assert!(!point.has_small_order());
1818 let mut alias = [0xffu8; 32];
1820 alias[0] = 0xf0;
1821 alias[31] = 0x7f | sign;
1822 assert!(P::decompress(&alias).is_none(), "sign {sign:#x}");
1823 }
1824 }
1825
1826 #[test]
1827 fn decompression_rejects_non_curve_points() {
1828 let mut bad = [0u8; 32];
1830 bad[0] = 2;
1831 assert!(P::decompress(&bad).is_none());
1832 }
1833
1834 #[test]
1836 fn rfc8032_vectors() {
1837 let cases: [(&str, &str, &str, &str); 3] = [
1838 (
1839 "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1840 "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1841 "",
1842 "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1843 ),
1844 (
1845 "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
1846 "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
1847 "72",
1848 "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
1849 ),
1850 (
1851 "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
1852 "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
1853 "af82",
1854 "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
1855 ),
1856 ];
1857
1858 for (seed_hex, pk_hex, msg_hex, sig_hex) in cases {
1859 let seed = unhex(seed_hex).unwrap();
1860 let msg = unhex(msg_hex).unwrap();
1861
1862 let mut pk = [0u8; 32];
1863 Ed25519::public_key(&seed, &mut pk).unwrap();
1864 assert_eq!(hex(&pk), pk_hex, "public key for {seed_hex}");
1865
1866 let mut sig = [0u8; 64];
1867 Ed25519::sign(&seed, &msg, &mut sig).unwrap();
1868 assert_eq!(hex(&sig), sig_hex, "signature for {seed_hex}");
1869
1870 Ed25519::verify(&pk, &msg, &sig).unwrap();
1871 }
1872 }
1873
1874 #[test]
1875 fn verification_rejects_tampering() {
1876 let seed = [0x42u8; 32];
1877 let mut pk = [0u8; 32];
1878 Ed25519::public_key(&seed, &mut pk).unwrap();
1879 let mut sig = [0u8; 64];
1880 Ed25519::sign(&seed, b"authentic", &mut sig).unwrap();
1881 Ed25519::verify(&pk, b"authentic", &sig).unwrap();
1882
1883 assert!(Ed25519::verify(&pk, b"forged", &sig).is_err());
1885 let mut bad = sig;
1887 bad[0] ^= 1;
1888 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1889 let mut bad = sig;
1891 bad[40] ^= 1;
1892 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
1893 let mut other_pk = [0u8; 32];
1895 Ed25519::public_key(&[0x43u8; 32], &mut other_pk).unwrap();
1896 assert!(Ed25519::verify(&other_pk, b"authentic", &sig).is_err());
1897 }
1898
1899 #[test]
1902 fn rejects_non_canonical_s() {
1903 let seed = [0x42u8; 32];
1904 let mut pk = [0u8; 32];
1905 Ed25519::public_key(&seed, &mut pk).unwrap();
1906 let mut sig = [0u8; 64];
1907 Ed25519::sign(&seed, b"msg", &mut sig).unwrap();
1908
1909 let mut carry = 0u16;
1912 for i in 0..32 {
1913 let t = sig[32 + i] as u16 + scalar::L[i] as u16 + carry;
1914 sig[32 + i] = t as u8;
1915 carry = t >> 8;
1916 }
1917 assert!(Ed25519::verify(&pk, b"msg", &sig).is_err());
1918 }
1919
1920 #[test]
1927 fn the_cached_key_signs_identically_to_the_seed() {
1928 let mut checked = 0;
1929 for seed in [[0x11u8; 32], [0x9du8; 32], [0xffu8; 32]] {
1930 for message in [&b""[..], &b"x"[..], &b"a longer message to sign"[..]] {
1931 let mut from_seed = [0u8; 64];
1932 Ed25519::sign(&seed, message, &mut from_seed).unwrap();
1933
1934 let key = Ed25519Key::from_seed(&seed).unwrap();
1935 let mut from_key = [0u8; 64];
1936 key.sign(message, &mut from_key).unwrap();
1937
1938 assert_eq!(from_seed, from_key, "the two signing paths diverged");
1939
1940 let mut derived = [0u8; 32];
1942 Ed25519::public_key(&seed, &mut derived).unwrap();
1943 assert_eq!(&derived, key.public_key());
1944
1945 Ed25519::verify(&derived, message, &from_key).unwrap();
1947 checked += 1;
1948 }
1949 }
1950 assert_eq!(checked, 9, "the comparison did not run");
1951 }
1952
1953 fn windowed_scalars() -> std::vec::Vec<[u8; 32]> {
1958 let mut one = [0u8; 32];
1959 one[0] = 1;
1960 let mut eight = [0u8; 32];
1961 eight[0] = 8;
1962 let mut top = [0xffu8; 32];
1963 top[31] = 0x7f;
1964 let mut clamped = [0x9du8; 32];
1965 clamped[0] &= 248;
1966 clamped[31] &= 127;
1967 clamped[31] |= 64;
1968 let mut l_minus_1 = scalar::L;
1969 l_minus_1[0] -= 1;
1970 let mut out = std::vec![[0u8; 32], one, eight, top, clamped, l_minus_1];
1971 for fill in [0x88u8, 0x77, 0x99, 0x55, 0xaa] {
1972 let mut s = [fill; 32];
1973 s[31] &= 0x7f;
1974 out.push(s);
1975 }
1976 out
1977 }
1978
1979 #[test]
1989 fn the_basepoint_constant_is_the_decompressed_encoding() {
1990 let decoded = P::decompress(&BASEPOINT_COMPRESSED).expect("the RFC 8032 basepoint");
1991 let zinv = decoded.z.invert();
1992 for (name, constant, from_encoding) in [
1993 ("x", basepoint().x, decoded.x.mul(&zinv)),
1994 ("y", basepoint().y, decoded.y.mul(&zinv)),
1995 ("t", basepoint().t, decoded.t.mul(&zinv)),
1996 ] {
1997 assert_eq!(
1998 constant.to_bytes(),
1999 from_encoding.to_bytes(),
2000 "{name} differs"
2001 );
2002 }
2003 assert_eq!(basepoint().z.to_bytes(), Fe::ONE.to_bytes());
2004 assert_eq!(basepoint().compress(), BASEPOINT_COMPRESSED);
2005 }
2006
2007 #[test]
2008 fn the_windowed_multiplication_agrees_with_the_ladder() {
2009 let b = basepoint();
2010 let mut seven = [0u8; 32];
2011 seven[0] = 7;
2012 let p = b.mul_scalar(&seven);
2013
2014 let mut checked = 0;
2015 for point in [b, p] {
2016 for scalar in windowed_scalars() {
2017 assert_eq!(
2018 mul_scalar_windowed(&point, &scalar).compress(),
2019 point.mul_scalar(&scalar).compress(),
2020 "windowed and ladder differ for {scalar:02x?}"
2021 );
2022 checked += 1;
2023 }
2024 }
2025 assert!(checked >= 20, "only {checked} comparisons ran");
2026 }
2027
2028 #[test]
2031 fn the_untabled_double_multiplication_agrees() {
2032 let b = basepoint();
2033 let mut checked = 0;
2034 for (i, k) in windowed_scalars().into_iter().enumerate() {
2035 let mut seed = [0u8; 32];
2036 seed[0] = 3 + i as u8;
2037 let a = b.mul_scalar(&seed);
2038 for s in [k, [0xffu8; 32], [0x9du8; 32]] {
2039 let untabled = double_scalar_mul_vartime_no_table(&a, &k, &s);
2040 let tabled = double_scalar_mul_vartime(&a, &k, &s);
2041 let ladders = a.mul_scalar(&k).add(&b.mul_scalar(&s));
2042 assert_eq!(
2043 untabled.compress(),
2044 tabled.compress(),
2045 "k={k:02x?} s={s:02x?}"
2046 );
2047 assert_eq!(
2048 untabled.compress(),
2049 ladders.compress(),
2050 "k={k:02x?} s={s:02x?}"
2051 );
2052 checked += 1;
2053 }
2054 }
2055 assert!(checked >= 30, "only {checked} comparisons ran");
2056 }
2057
2058 #[test]
2060 fn the_wnaf_digits_are_odd_sparse_and_faithful() {
2061 for scalar in [[1u8; 32], [0x9du8; 32], [0xffu8; 32], [0x55u8; 32]] {
2062 let naf = wnaf(&scalar, 5);
2063
2064 let mut previous_nonzero: Option<usize> = None;
2065 for (i, d) in naf.iter().enumerate() {
2066 if *d == 0 {
2067 continue;
2068 }
2069 assert!(d % 2 != 0, "digit {d} at {i} is not odd");
2070 assert!((-15..=15).contains(d), "digit {d} at {i} is out of range");
2071 if let Some(j) = previous_nonzero {
2072 assert!(i - j >= 5, "digits at {j} and {i} are adjacent");
2073 }
2074 previous_nonzero = Some(i);
2075 }
2076
2077 const M: u128 = 1_000_000_007;
2080 let mut from_digits = 0u128;
2081 let mut power = 1u128;
2082 for d in naf {
2083 let term = ((d as i128).rem_euclid(M as i128)) as u128;
2084 from_digits = (from_digits + term * power) % M;
2085 power = power * 2 % M;
2086 }
2087 let mut from_bytes = 0u128;
2088 let mut p = 1u128;
2089 for byte in scalar {
2090 from_bytes = (from_bytes + (byte as u128) * p) % M;
2091 p = p * 256 % M;
2092 }
2093 assert_eq!(from_digits, from_bytes, "recoding changed the value");
2094 }
2095 }
2096
2097 #[test]
2098 fn signing_is_deterministic() {
2099 let seed = [0x7fu8; 32];
2100 let mut a = [0u8; 64];
2101 let mut b = [0u8; 64];
2102 Ed25519::sign(&seed, b"same input", &mut a).unwrap();
2103 Ed25519::sign(&seed, b"same input", &mut b).unwrap();
2104 assert_eq!(a, b);
2105 }
2106
2107 #[test]
2108 fn rejects_wrong_lengths() {
2109 let mut out = [0u8; 32];
2110 assert!(Ed25519::public_key(&[0u8; 31], &mut out).is_err());
2111 assert!(Ed25519::sign(&[0u8; 32], b"", &mut [0u8; 63]).is_err());
2112 assert!(Ed25519::verify(&[0u8; 32], b"", &[0u8; 63]).is_err());
2113 }
2114
2115 #[test]
2116 fn self_test_passes() {
2117 Ed25519::self_test().unwrap();
2118 }
2119
2120 fn sample_points(n: usize) -> Vec<P> {
2122 let mut out = Vec::new();
2123 let mut p = basepoint();
2124 for _ in 0..n {
2125 out.push(p);
2126 p = p.double().add(&basepoint());
2127 }
2128 out
2129 }
2130
2131 #[test]
2141 fn the_completed_doubling_agrees_with_the_extended_one() {
2142 for p in sample_points(40) {
2143 let want = p.double();
2144 let got = p.to_projective().double().to_extended();
2145 assert!(got.eq_projective(&want), "doubling disagrees");
2146 let chained = p.to_projective().double().to_projective().double();
2149 let twice = p.double().double();
2150 assert!(chained.to_extended().eq_projective(&twice), "two doublings");
2151 }
2152 }
2153
2154 #[test]
2156 fn niels_addition_agrees_with_the_general_one() {
2157 let pts = sample_points(20);
2158 for p in &pts {
2159 for q in &pts {
2160 let want = p.add(q);
2161 let got = p.add_niels(&q.to_niels()).to_extended();
2162 assert!(got.eq_projective(&want), "add_niels disagrees");
2163
2164 let want_sub = p.add(&q.negate());
2165 let got_sub = p.sub_niels(&q.to_niels()).to_extended();
2166 assert!(got_sub.eq_projective(&want_sub), "sub_niels disagrees");
2167 }
2168 }
2169 }
2170
2171 #[test]
2179 fn affine_niels_addition_agrees_with_the_general_one() {
2180 let pts = sample_points(20);
2181 for p in &pts {
2182 for q in &pts {
2183 let want = p.add(q);
2184 let got = p.add_affine_niels(&q.to_affine_niels()).to_extended();
2185 assert!(got.eq_projective(&want), "add_affine_niels disagrees");
2186
2187 let mut n = q.to_affine_niels();
2189 n.conditional_negate(1);
2190 let want_neg = p.add(&q.negate());
2191 let got_neg = p.add_affine_niels(&n).to_extended();
2192 assert!(got_neg.eq_projective(&want_neg), "negated form disagrees");
2193 }
2194 }
2195 }
2196
2197 #[test]
2204 #[ignore = "diagnostic, not a test"]
2205 fn where_verify_spends_its_time() {
2206 use std::time::Instant;
2207
2208 let seed = [7u8; 32];
2209 let key = Ed25519Key::from_seed(&seed).unwrap();
2210 let msg = b"benchmark message";
2211 let mut sig = [0u8; 64];
2212 key.sign(msg, &mut sig).unwrap();
2213 let pk = *key.public_key();
2214
2215 let mut big_r = [0u8; 32];
2216 big_r.copy_from_slice(&sig[..32]);
2217 let mut s_sc = [0u8; 32];
2218 s_sc.copy_from_slice(&sig[32..]);
2219
2220 let n = 2000;
2221 let time = |label: &str, f: &mut dyn FnMut()| {
2222 let mut best = f64::INFINITY;
2223 for _ in 0..5 {
2224 let t = Instant::now();
2225 for _ in 0..n {
2226 f();
2227 }
2228 let e = t.elapsed().as_secs_f64() / n as f64 * 1e6;
2229 if e < best {
2230 best = e;
2231 }
2232 }
2233 println!(" {label:<34} {best:>9.2} us");
2234 best
2235 };
2236
2237 let a_point = P::decompress(&pk).unwrap();
2238 let k = hash_to_scalar(&[&big_r, &pk, msg]);
2239
2240 println!(
2241 "
2242ed25519 verify, cost breakdown:"
2243 );
2244 let d = time("decompress (x2 per verify)", &mut || {
2245 core::hint::black_box(P::decompress(&pk));
2246 });
2247 let h = time("hash_to_scalar", &mut || {
2248 core::hint::black_box(hash_to_scalar(&[&big_r, &pk, msg]));
2249 });
2250 let b = time("mul_basepoint (const time)", &mut || {
2251 core::hint::black_box(mul_basepoint(&s_sc));
2252 });
2253 let v = time("double_scalar_mul_vartime", &mut || {
2254 core::hint::black_box(double_scalar_mul_vartime(&a_point.negate(), &k, &s_sc));
2255 });
2256 time(" of which: wnaf(k,5)+wnaf(s,8)", &mut || {
2257 core::hint::black_box(wnaf(&k, 5));
2258 core::hint::black_box(wnaf(&s_sc, 8));
2259 });
2260 time(" of which: odd_a table build", &mut || {
2261 let twice = a_point.double();
2262 let mut odd = [a_point; 8];
2263 for i in 1..8 {
2264 odd[i] = odd[i - 1].add(&twice);
2265 }
2266 let t: [Niels<Fe>; 8] = core::array::from_fn(|i| odd[i].to_niels());
2267 core::hint::black_box(t);
2268 });
2269 time(" of which: 255 doublings", &mut || {
2270 let mut p = a_point;
2271 for _ in 0..255 {
2272 p = p.double();
2273 }
2274 core::hint::black_box(p);
2275 });
2276 time(" of which: 79 additions", &mut || {
2277 let mut p = a_point;
2278 for _ in 0..79 {
2279 p = p.add(&a_point);
2280 }
2281 core::hint::black_box(p);
2282 });
2283 time("compress (one inversion)", &mut || {
2284 core::hint::black_box(a_point.compress());
2285 });
2286 println!(
2287 " {:<34} {:>9.2} us",
2288 "-- accounted for",
2289 2.0 * d + h + b + v
2290 );
2291
2292 println!(
2294 "
2295field and point primitives, nanoseconds:"
2296 );
2297 let nn = 200_000;
2298 let ns = |label: &str, f: &mut dyn FnMut()| {
2299 let mut best = f64::INFINITY;
2300 for _ in 0..5 {
2301 let t = Instant::now();
2302 for _ in 0..nn {
2303 f();
2304 }
2305 let e = t.elapsed().as_secs_f64() / nn as f64 * 1e9;
2306 if e < best {
2307 best = e;
2308 }
2309 }
2310 println!(" {label:<34} {best:>9.2} ns");
2311 };
2312 let fx = a_point.x;
2313 let fy = a_point.y;
2314 ns("Fe::mul", &mut || {
2315 core::hint::black_box(core::hint::black_box(&fx).mul(core::hint::black_box(&fy)));
2316 });
2317 ns("Fe::square", &mut || {
2318 core::hint::black_box(core::hint::black_box(&fx).square());
2319 });
2320 ns("Fe::add", &mut || {
2321 core::hint::black_box(core::hint::black_box(&fx).add(core::hint::black_box(&fy)));
2322 });
2323 ns("Fe::sub", &mut || {
2324 core::hint::black_box(core::hint::black_box(&fx).sub(core::hint::black_box(&fy)));
2325 });
2326 ns("Fe::neg", &mut || {
2327 core::hint::black_box(core::hint::black_box(&fx).neg());
2328 });
2329 let proj = a_point.to_projective();
2330 let comp = proj.double();
2331 let an = a_point.to_affine_niels();
2332 ns("Projective::double (4S)", &mut || {
2333 core::hint::black_box(core::hint::black_box(&proj).double());
2334 });
2335 ns("Projective::double_projective", &mut || {
2336 core::hint::black_box(core::hint::black_box(proj).double_projective());
2337 });
2338 ns("Completed::to_projective (3M)", &mut || {
2339 core::hint::black_box(core::hint::black_box(&comp).to_projective());
2340 });
2341 ns("Completed::to_extended (4M)", &mut || {
2342 core::hint::black_box(core::hint::black_box(&comp).to_extended());
2343 });
2344 ns("P::add_affine_niels (3M)", &mut || {
2345 core::hint::black_box(
2346 core::hint::black_box(&a_point).add_affine_niels(core::hint::black_box(&an)),
2347 );
2348 });
2349 ns("P::double", &mut || {
2350 core::hint::black_box(core::hint::black_box(&a_point).double());
2351 });
2352 ns("P::add", &mut || {
2353 core::hint::black_box(
2354 core::hint::black_box(&a_point).add(core::hint::black_box(&a_point)),
2355 );
2356 });
2357 }
2358
2359 #[test]
2361 #[ignore = "diagnostic, not a test"]
2362 fn count_the_point_operations() {
2363 let mut doublings = 0usize;
2364 let mut adds_a = 0usize;
2365 let mut adds_b = 0usize;
2366 let mut state = 0x1234_5678_9abc_def0u64;
2367 let trials = 200;
2368 for _ in 0..trials {
2369 let mut kb = [0u8; 32];
2370 for c in kb.chunks_exact_mut(8) {
2371 state ^= state >> 12;
2372 state ^= state << 25;
2373 state ^= state >> 27;
2374 c.copy_from_slice(&state.wrapping_mul(0x2545_f491_4f6c_dd1d).to_le_bytes());
2375 }
2376 kb[31] &= 0x0f;
2377 let na = wnaf(&kb, 5);
2378 let nb = wnaf(&kb, 8);
2379 let mut i = 257;
2380 while i > 0 && na[i] == 0 && nb[i] == 0 {
2381 i -= 1;
2382 }
2383 doublings += i + 1;
2384 adds_a += na.iter().filter(|d| **d != 0).count();
2385 adds_b += nb.iter().filter(|d| **d != 0).count();
2386 }
2387 let d = doublings as f64 / trials as f64;
2388 let aa = adds_a as f64 / trials as f64;
2389 let ab = adds_b as f64 / trials as f64;
2390 println!(
2391 "
2392 per double-scalar multiplication, averaged over {trials} scalars:"
2393 );
2394 println!(" doublings {d:>8.1}");
2395 println!(" additions, w=5 table (A) {aa:>8.1}");
2396 println!(" additions, w=8 table (B) {ab:>8.1}");
2397 println!(" additions, building A {:>8.1}", 8.0);
2398 println!(" ---");
2399 println!(" total additions {:>8.1}", aa + ab + 8.0);
2400 println!(
2401 " field muls, at 4M+4S per doubling and 9M per addition: {:>6.0}",
2402 d * 8.0 + (aa + ab + 8.0) * 9.0
2403 );
2404 println!(
2405 " the same at dalek's 3M+4S and 7M: {:>6.0}",
2406 d * 7.0 + (aa + ab + 8.0) * 7.0
2407 );
2408 }
2409}