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 const CHEAP_INVERSION: bool = false;
97
98 #[cfg(all(feature = "std", any(test, feature = "bench-internals")))]
100 fn to_portable(self) -> Fe {
101 Fe::from_bytes(&self.to_bytes())
102 }
103
104 #[cfg(all(feature = "std", any(test, feature = "bench-internals")))]
106 fn from_portable(x: &Fe) -> Self {
107 Self::from_bytes(&x.to_bytes())
108 }
109}
110
111impl EdField for Fe {
112 const D: Fe = Fe::from_limbs51(D_LIMBS);
113 const D2: Fe = Fe::from_limbs51(D2_LIMBS);
114 const SQRT_M1: Fe = Fe::from_limbs51(SQRT_M1_LIMBS);
115
116 #[cfg(all(feature = "std", any(test, feature = "bench-internals")))]
117 fn to_portable(self) -> Fe {
118 self
119 }
120
121 #[cfg(all(feature = "std", any(test, feature = "bench-internals")))]
122 fn from_portable(x: &Fe) -> Self {
123 *x
124 }
125}
126
127#[cfg(all(target_arch = "x86_64", not(any(ic_limb32, ic_ec_portable))))]
128impl EdField for crate::fe64::Fe64 {
129 const D: Self = crate::fe64::Fe64::from_limbs51(D_LIMBS);
130 const D2: Self = crate::fe64::Fe64::from_limbs51(D2_LIMBS);
131 const SQRT_M1: Self = crate::fe64::Fe64::from_limbs51(SQRT_M1_LIMBS);
132 const CHEAP_INVERSION: bool = true;
134}
135
136#[derive(Clone, Copy, Debug)]
142pub struct Point(Ext<Fe>);
143
144impl Point {
145 pub const IDENTITY: Point = Point(Ext::IDENTITY);
147
148 pub fn add(&self, other: &Point) -> Point {
150 Point(self.0.add(&other.0))
151 }
152
153 pub fn double(&self) -> Point {
155 Point(self.0.double())
156 }
157
158 pub fn mul_scalar(&self, s: &[u8; 32]) -> Point {
160 Point(self.0.mul_scalar(s))
161 }
162
163 pub fn compress(&self) -> [u8; 32] {
165 self.0.compress()
166 }
167
168 pub fn decompress(bytes: &[u8; 32]) -> Option<Point> {
171 Ext::decompress(bytes).map(Point)
172 }
173}
174
175#[derive(Clone, Copy, Debug)]
177pub(crate) struct Ext<F> {
178 x: F,
179 y: F,
180 z: F,
181 t: F,
182}
183
184#[derive(Clone, Copy)]
194pub(crate) struct Completed<F> {
195 x: F,
196 y: F,
197 z: F,
198 t: F,
199}
200
201#[derive(Clone, Copy)]
205pub(crate) struct Projective<F> {
206 x: F,
207 y: F,
208 z: F,
209}
210
211#[derive(Clone, Copy)]
219pub(crate) struct Niels<F> {
220 ypx: F,
221 ymx: F,
222 z: F,
223 t2d: F,
224}
225
226#[cfg(feature = "std")]
235#[derive(Clone, Copy)]
236#[repr(C)]
237pub(crate) struct AffineNiels<F> {
238 ypx: F,
239 ymx: F,
240 t2d: F,
241}
242
243#[cfg(any(not(feature = "std"), test))]
247impl<F: EdField> Niels<F> {
248 const IDENTITY: Self = Niels {
250 ypx: F::ONE,
251 ymx: F::ONE,
252 z: F::ONE,
253 t2d: F::ZERO,
254 };
255
256 fn conditional_negate(&mut self, choice: Choice) {
259 let swapped_p = self.ymx;
260 let swapped_m = self.ypx;
261 let nt = self.t2d.neg();
262 F::cmov(&mut self.ypx, &swapped_p, choice);
263 F::cmov(&mut self.ymx, &swapped_m, choice);
264 F::cmov(&mut self.t2d, &nt, choice);
265 }
266
267 fn cmov(&mut self, other: &Niels<F>, choice: Choice) {
268 F::cmov(&mut self.ypx, &other.ypx, choice);
269 F::cmov(&mut self.ymx, &other.ymx, choice);
270 F::cmov(&mut self.z, &other.z, choice);
271 F::cmov(&mut self.t2d, &other.t2d, choice);
272 }
273}
274
275#[cfg(feature = "std")]
276impl<F: EdField> AffineNiels<F> {
277 pub(crate) const IDENTITY: Self = AffineNiels {
279 ypx: F::ONE,
280 ymx: F::ONE,
281 t2d: F::ZERO,
282 };
283
284 pub(crate) fn conditional_negate(&mut self, bit: u64) {
290 let swapped_p = self.ymx;
291 let swapped_m = self.ypx;
292 let nt = self.t2d.neg();
293 F::cmov_bit(&mut self.ypx, &swapped_p, bit);
294 F::cmov_bit(&mut self.ymx, &swapped_m, bit);
295 F::cmov_bit(&mut self.t2d, &nt, bit);
296 }
297
298 pub(crate) fn cmov(&mut self, other: &AffineNiels<F>, bit: u64) {
300 F::cmov_bit(&mut self.ypx, &other.ypx, bit);
301 F::cmov_bit(&mut self.ymx, &other.ymx, bit);
302 F::cmov_bit(&mut self.t2d, &other.t2d, bit);
303 }
304}
305
306impl<F: EdField> Completed<F> {
307 fn to_projective(self) -> Projective<F> {
309 Projective {
310 x: self.x.mul(&self.t),
311 y: self.y.mul(&self.z),
312 z: self.z.mul(&self.t),
313 }
314 }
315
316 fn to_extended(self) -> Ext<F> {
321 Ext {
322 x: self.x.mul(&self.t),
323 y: self.y.mul(&self.z),
324 z: self.z.mul(&self.t),
325 t: self.x.mul(&self.y),
326 }
327 }
328}
329
330impl<F: EdField> Projective<F> {
331 fn to_extended_from_projective(self) -> Ext<F> {
336 Ext {
343 x: self.x.mul(&self.z),
344 y: self.y.mul(&self.z),
345 z: self.z.square(),
346 t: self.x.mul(&self.y),
347 }
348 }
349
350 fn double_projective(self) -> Projective<F> {
360 let xx = self.x.square();
361 let yy = self.y.square();
362 let zz2 = {
363 let t = self.z.square();
364 t.add(&t)
365 };
366 let xy_sq = self.x.add(&self.y).square();
367 let yy_plus_xx = yy.add(&xx);
368 let yy_minus_xx = yy.sub(&xx);
369
370 let cx = xy_sq.sub(&yy_plus_xx);
371 let cy = yy_plus_xx;
372 let cz = yy_minus_xx;
373 let ct = zz2.sub(&yy_minus_xx);
374
375 Projective {
376 x: cx.mul(&ct),
377 y: cy.mul(&cz),
378 z: cz.mul(&ct),
379 }
380 }
381
382 fn double(&self) -> Completed<F> {
388 let xx = self.x.square();
389 let yy = self.y.square();
390 let zz2 = {
391 let t = self.z.square();
392 t.add(&t)
393 };
394 let xy_sq = self.x.add(&self.y).square();
395 let yy_plus_xx = yy.add(&xx);
396 let yy_minus_xx = yy.sub(&xx);
397 Completed {
398 x: xy_sq.sub(&yy_plus_xx),
399 y: yy_plus_xx,
400 z: yy_minus_xx,
401 t: zz2.sub(&yy_minus_xx),
402 }
403 }
404}
405
406impl<F: EdField> Ext<F> {
407 pub(crate) fn from_affine(x: F, y: F) -> Ext<F> {
410 Ext {
411 x,
412 y,
413 z: F::ONE,
414 t: x.mul(&y),
415 }
416 }
417
418 #[cfg(all(feature = "std", any(test, feature = "bench-internals")))]
420 pub(crate) fn convert<G: EdField>(&self) -> Ext<G> {
421 let c = |v: &F| G::from_portable(&v.to_portable());
422 Ext {
423 x: c(&self.x),
424 y: c(&self.y),
425 z: c(&self.z),
426 t: c(&self.t),
427 }
428 }
429
430 pub const IDENTITY: Self = Ext {
431 x: F::ZERO,
432 y: F::ONE,
433 z: F::ONE,
434 t: F::ZERO,
435 };
436
437 pub fn add(&self, other: &Ext<F>) -> Ext<F> {
439 let a = self.y.sub(&self.x).mul(&other.y.sub(&other.x));
440 let b = self.y.add(&self.x).mul(&other.y.add(&other.x));
441 let c = self.t.mul(&F::D2).mul(&other.t);
442 let d = self.z.mul(&other.z);
443 let d = d.add(&d);
444
445 let e = b.sub(&a);
446 let f = d.sub(&c);
447 let g = d.add(&c);
448 let h = b.add(&a);
449
450 Ext {
451 x: e.mul(&f),
452 y: g.mul(&h),
453 t: e.mul(&h),
454 z: f.mul(&g),
455 }
456 }
457
458 pub fn double(&self) -> Ext<F> {
469 let aa = self.x.square();
470 let bb = self.y.square();
471 let c = self.z.square();
472 let c = c.add(&c);
473 let d = aa.neg();
475 let xy = self.x.add(&self.y);
477 let e = xy.square().sub(&aa).sub(&bb);
478 let g = d.add(&bb);
479 let f = g.sub(&c);
480 let h = d.sub(&bb);
481
482 Ext {
483 x: e.mul(&f),
484 y: g.mul(&h),
485 t: e.mul(&h),
486 z: f.mul(&g),
487 }
488 }
489
490 fn to_projective(self) -> Projective<F> {
492 Projective {
493 x: self.x,
494 y: self.y,
495 z: self.z,
496 }
497 }
498
499 fn to_niels(self) -> Niels<F> {
501 Niels {
502 ypx: self.y.add(&self.x),
503 ymx: self.y.sub(&self.x),
504 z: self.z,
505 t2d: self.t.mul(&F::D2),
506 }
507 }
508
509 fn add_niels(&self, other: &Niels<F>) -> Completed<F> {
516 let pp = self.y.add(&self.x).mul(&other.ypx);
517 let mm = self.y.sub(&self.x).mul(&other.ymx);
518 let tt2d = self.t.mul(&other.t2d);
519 let zz = self.z.mul(&other.z);
520 let zz2 = zz.add(&zz);
521 Completed {
522 x: pp.sub(&mm),
523 y: pp.add(&mm),
524 z: zz2.add(&tt2d),
525 t: zz2.sub(&tt2d),
526 }
527 }
528
529 fn sub_niels(&self, other: &Niels<F>) -> Completed<F> {
535 let pp = self.y.add(&self.x).mul(&other.ymx);
536 let mm = self.y.sub(&self.x).mul(&other.ypx);
537 let tt2d = self.t.mul(&other.t2d);
538 let zz = self.z.mul(&other.z);
539 let zz2 = zz.add(&zz);
540 Completed {
541 x: pp.sub(&mm),
542 y: pp.add(&mm),
543 z: zz2.sub(&tt2d),
544 t: zz2.add(&tt2d),
545 }
546 }
547
548 #[cfg(feature = "std")]
554 pub(crate) fn to_affine_niels(self) -> AffineNiels<F> {
555 let z_inv = self.z.invert();
556 let x = self.x.mul(&z_inv);
557 let y = self.y.mul(&z_inv);
558 AffineNiels {
559 ypx: y.add(&x),
560 ymx: y.sub(&x),
561 t2d: x.mul(&y).mul(&F::D2),
562 }
563 }
564
565 #[cfg(feature = "std")]
570 pub(crate) fn add_affine_niels(&self, other: &AffineNiels<F>) -> Completed<F> {
571 let pp = self.y.add(&self.x).mul(&other.ypx);
572 let mm = self.y.sub(&self.x).mul(&other.ymx);
573 let tt2d = self.t.mul(&other.t2d);
574 let zz2 = self.z.add(&self.z);
575 Completed {
576 x: pp.sub(&mm),
577 y: pp.add(&mm),
578 z: zz2.add(&tt2d),
579 t: zz2.sub(&tt2d),
580 }
581 }
582
583 #[cfg(feature = "std")]
592 pub(crate) fn sub_affine_niels(&self, other: &AffineNiels<F>) -> Completed<F> {
593 let pp = self.y.add(&self.x).mul(&other.ymx);
594 let mm = self.y.sub(&self.x).mul(&other.ypx);
595 let tt2d = self.t.mul(&other.t2d);
596 let zz2 = self.z.add(&self.z);
597 Completed {
598 x: pp.sub(&mm),
599 y: pp.add(&mm),
600 z: zz2.sub(&tt2d),
601 t: zz2.add(&tt2d),
602 }
603 }
604
605 fn cmov(&mut self, other: &Ext<F>, choice: Choice) {
607 F::cmov(&mut self.x, &other.x, choice);
608 F::cmov(&mut self.y, &other.y, choice);
609 F::cmov(&mut self.z, &other.z, choice);
610 F::cmov(&mut self.t, &other.t, choice);
611 }
612
613 pub fn mul_scalar(&self, s: &[u8; 32]) -> Ext<F> {
619 let mut acc = Ext::IDENTITY;
620 for i in (0..256).rev() {
621 acc = acc.double();
622 let sum = acc.add(self);
623 let bit = Choice::from_u8((s[i / 8] >> (i % 8)) & 1);
624 acc.cmov(&sum, bit);
625 }
626 acc
627 }
628
629 fn negate(&self) -> Ext<F> {
631 Ext {
632 x: self.x.neg(),
633 y: self.y,
634 z: self.z,
635 t: self.t.neg(),
636 }
637 }
638
639 fn has_small_order(&self) -> bool {
656 self.double()
657 .double()
658 .double()
659 .eq_projective(&Ext::<F>::IDENTITY)
660 }
661
662 fn eq_projective(&self, other: &Ext<F>) -> bool {
663 self.x.mul(&other.z).to_bytes() == other.x.mul(&self.z).to_bytes()
664 && self.y.mul(&other.z).to_bytes() == other.y.mul(&self.z).to_bytes()
665 }
666
667 pub fn compress(&self) -> [u8; 32] {
669 let z_inv = self.z.invert();
670 let x = self.x.mul(&z_inv);
671 let y = self.y.mul(&z_inv);
672 let mut out = y.to_bytes();
673 out[31] |= x.is_negative().unwrap_u8() << 7;
675 out
676 }
677
678 pub fn decompress(bytes: &[u8; 32]) -> Option<Ext<F>> {
686 let start = Decompression::<F>::start(bytes)?;
687 let pow = start.uv7.pow22523();
688 start.finish(&pow)
689 }
690
691 fn decompress_pair(a: &[u8; 32], b: &[u8; 32]) -> (Option<Ext<F>>, Option<Ext<F>>) {
699 match (Decompression::<F>::start(a), Decompression::<F>::start(b)) {
700 (Some(sa), Some(sb)) => {
701 let (pa, pb) = F::pow22523_pair(&sa.uv7, &sb.uv7);
702 (sa.finish(&pa), sb.finish(&pb))
703 }
704 (sa, sb) => (
705 sa.and_then(|s| s.finish(&s.uv7.pow22523())),
706 sb.and_then(|s| s.finish(&s.uv7.pow22523())),
707 ),
708 }
709 }
710}
711
712struct Decompression<F> {
715 y: F,
716 u: F,
717 v: F,
718 uv3: F,
719 uv7: F,
720 sign: Choice,
721}
722
723impl<F: EdField> Decompression<F> {
724 fn start(bytes: &[u8; 32]) -> Option<Self> {
725 let sign = Choice::from_u8(bytes[31] >> 7);
726 let mut y_bytes = *bytes;
727 y_bytes[31] &= 0x7f;
728 let y = F::from_bytes(&y_bytes);
729 if y.to_bytes() != y_bytes {
731 return None;
732 }
733
734 let y2 = y.square();
736 let u = y2.sub(&F::ONE);
737 let v = y2.mul(&F::D).add(&F::ONE);
738
739 let v3 = v.square().mul(&v);
741 let v7 = v3.square().mul(&v);
742 Some(Self {
743 y,
744 u,
745 v,
746 uv3: u.mul(&v3),
747 uv7: u.mul(&v7),
748 sign,
749 })
750 }
751
752 fn finish(&self, pow: &F) -> Option<Ext<F>> {
754 let (y, u, v, sign) = (self.y, self.u, self.v, self.sign);
755 let mut x = self.uv3.mul(pow);
756
757 let check = v.mul(&x.square());
758 let correct = check.ct_eq(&u);
759 let flipped = check.ct_eq(&u.neg());
760 if !bool::from(correct.or(flipped)) {
761 return None;
763 }
764 let alt = x.mul(&F::SQRT_M1);
766 F::cmov(&mut x, &alt, flipped.and(correct.not()));
767
768 if bool::from(x.is_zero()) && bool::from(sign) {
770 return None;
771 }
772 let neg = x.neg();
774 let wrong_sign = Choice::from_u8(x.is_negative().unwrap_u8() ^ sign.unwrap_u8());
775 F::cmov(&mut x, &neg, wrong_sign);
776
777 Some(Ext {
778 x,
779 y,
780 z: F::ONE,
781 t: x.mul(&y),
782 })
783 }
784}
785
786#[cfg(feature = "std")]
789pub(crate) fn prepare_tables() {
790 fn prepare<F: Curve>() {
791 basepoint_table::prepare::<F>();
792 }
793 dispatch!(prepare());
794 #[cfg(all(target_arch = "x86_64", not(any(ic_limb32, ic_ec_portable))))]
797 if crate::fe64::available()
798 && crate::f51x4::Ifma::get().is_none()
799 && crate::fe64::edwards::Avx2::get().is_some()
800 {
801 fast::prepare_wide();
802 }
803}
804
805fn mul_basepoint<F: Curve>(scalar: &[u8; 32]) -> Ext<F> {
811 #[cfg(feature = "std")]
812 {
813 basepoint_table::mul::<F>(scalar)
814 }
815 #[cfg(not(feature = "std"))]
816 {
817 mul_scalar_windowed(&basepoint::<F>(), scalar)
818 }
819}
820
821fn mul_basepoint_compressed(scalar: &[u8; 32]) -> [u8; 32] {
824 fn on<F: Curve>(scalar: &[u8; 32]) -> [u8; 32] {
825 mul_basepoint::<F>(scalar).compress()
826 }
827 dispatch!(on(scalar))
828}
829
830#[cfg(feature = "bench-internals")]
854#[doc(hidden)]
855pub fn double_scalar_mul_vartime_for_bench(a: &Point, k: &[u8; 32], s: &[u8; 32]) -> Point {
856 fn on<F: Curve>(a: &Ext<Fe>, k: &[u8; 32], s: &[u8; 32]) -> Point {
857 Point(double_scalar_mul_vartime(&a.convert::<F>(), k, s).convert::<Fe>())
858 }
859 dispatch!(on(&a.0, k, s))
860}
861
862#[cfg(any(not(feature = "std"), test))]
878fn mul_scalar_windowed<F: EdField>(p: &Ext<F>, scalar: &[u8; 32]) -> Ext<F> {
879 let mut multiples = [*p; 8];
880 for i in 1..8 {
881 multiples[i] = multiples[i - 1].add(p);
882 }
883 let table: [Niels<F>; 8] = core::array::from_fn(|i| multiples[i].to_niels());
884
885 let select = |digit: i8| -> Niels<F> {
887 let negative = Choice::from_u8((digit as u8) >> 7);
888 let magnitude = ((digit as i16 ^ (digit as i16 >> 7)) - (digit as i16 >> 7)) as u8;
889 let mut out = Niels::IDENTITY;
890 for (i, entry) in table.iter().enumerate() {
891 out.cmov(entry, Choice::from_u8(u8::from(magnitude == (i as u8 + 1))));
892 }
893 out.conditional_negate(negative);
894 out
895 };
896
897 let digits = signed_digits(scalar);
898 let mut acc = Ext::IDENTITY.add_niels(&select(digits[63])).to_extended();
899 for i in (0..63).rev() {
900 let mut q = acc.to_projective();
903 for _ in 0..3 {
904 q = q.double_projective();
905 }
906 acc = q.double().to_extended();
907 acc = acc.add_niels(&select(digits[i])).to_extended();
908 }
909 acc
910}
911
912fn odd_multiples<F: EdField>(p: &Ext<F>) -> [Niels<F>; 8] {
914 let twice = p.double();
915 let mut odd = [*p; 8];
916 for i in 1..8 {
917 odd[i] = odd[i - 1].add(&twice);
918 }
919 core::array::from_fn(|i| odd[i].to_niels())
920}
921
922#[cfg(all(target_arch = "x86_64", not(any(ic_limb32, ic_ec_portable))))]
925pub(crate) const DIGITS32: usize = 52;
926
927#[cfg(all(target_arch = "x86_64", not(any(ic_limb32, ic_ec_portable))))]
928#[cfg_attr(not(feature = "std"), allow(dead_code))]
929pub(crate) fn signed_digits32(scalar: &[u8; 32]) -> [i8; DIGITS32] {
937 #[inline(always)]
939 fn field(scalar: &[u8; 32], i: usize) -> i8 {
940 let bit = 5 * i;
941 let lo = u16::from(scalar[bit / 8]);
942 let hi = u16::from(*scalar.get(bit / 8 + 1).unwrap_or(&0));
943 (((lo | (hi << 8)) >> (bit % 8)) & 31) as i8
944 }
945 macro_rules! fields {
950 ($($i:literal)*) => { [$(field(scalar, $i)),*] };
951 }
952 let mut d: [i8; DIGITS32] = fields!(
953 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
954 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
955 );
956 macro_rules! carries {
957 ($($i:literal)*) => {$({
958 let carry = (d[$i] + 16) >> 5;
959 d[$i] -= carry << 5;
960 d[$i + 1] += carry;
961 })*};
962 }
963 carries!(
964 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
965 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
966 );
967 d
968}
969
970fn signed_digits(scalar: &[u8; 32]) -> [i8; 64] {
986 debug_assert!(
987 scalar[31] <= 127,
988 "the top digit can only absorb the final carry for scalars below 2^255"
989 );
990
991 let mut nibbles = [0i8; 64];
992 for (i, byte) in scalar.iter().enumerate() {
993 nibbles[i * 2] = (byte & 0x0f) as i8;
994 nibbles[i * 2 + 1] = (byte >> 4) as i8;
995 }
996
997 for i in 0..63 {
998 let carry = (nibbles[i] + 8) >> 4;
999 nibbles[i] -= carry << 4;
1000 nibbles[i + 1] += carry;
1001 }
1002 nibbles
1003}
1004
1005#[cfg(feature = "std")]
1006fn double_scalar_mul_vartime<F: Curve>(a: &Ext<F>, k: &[u8; 32], s: &[u8; 32]) -> Ext<F> {
1007 F::double_mul(a, k, s)
1008}
1009
1010#[cfg(feature = "std")]
1013fn double_scalar_mul_vartime_generic<F: Curve>(a: &Ext<F>, k: &[u8; 32], s: &[u8; 32]) -> Ext<F> {
1014 basepoint_table::prepare::<F>();
1015 shared_doublings(a, k, &wnaf(s, 8), |e, digit| {
1016 let n = basepoint_table::odd_multiple::<F>((digit.unsigned_abs() as usize) / 2);
1017 if digit > 0 {
1018 e.add_affine_niels(n)
1019 } else {
1020 e.sub_affine_niels(n)
1021 }
1022 })
1023}
1024
1025#[cfg(any(not(feature = "std"), test))]
1033fn double_scalar_mul_vartime_no_table<F: EdField>(
1034 a: &Ext<F>,
1035 k: &[u8; 32],
1036 s: &[u8; 32],
1037) -> Ext<F> {
1038 let odd_b = odd_multiples(&basepoint::<F>());
1039 shared_doublings(a, k, &wnaf(s, 5), |e, digit| {
1040 let n = &odd_b[(digit.unsigned_abs() as usize) / 2];
1041 if digit > 0 {
1042 e.add_niels(n)
1043 } else {
1044 e.sub_niels(n)
1045 }
1046 })
1047}
1048
1049#[inline(always)]
1052fn shared_doublings<F: EdField>(
1053 a: &Ext<F>,
1054 k: &[u8; 32],
1055 naf_b: &[i8; 258],
1056 add_b: impl Fn(&Ext<F>, i8) -> Completed<F>,
1057) -> Ext<F> {
1058 let odd_a = odd_multiples(a);
1060 let naf_a = wnaf(k, 5);
1061
1062 let mut i = 257;
1065 while i > 0 && naf_a[i] == 0 && naf_b[i] == 0 {
1066 i -= 1;
1067 }
1068
1069 let mut acc = Ext::IDENTITY.to_projective();
1074 loop {
1075 if naf_a[i] == 0 && naf_b[i] == 0 {
1078 acc = acc.double_projective();
1079 if i == 0 {
1080 return acc.to_extended_from_projective();
1081 }
1082 i -= 1;
1083 continue;
1084 }
1085 let mut t = acc.double();
1086 if naf_a[i] != 0 {
1087 let e = t.to_extended();
1088 let n = &odd_a[(naf_a[i].unsigned_abs() as usize) / 2];
1089 t = if naf_a[i] > 0 {
1090 e.add_niels(n)
1091 } else {
1092 e.sub_niels(n)
1093 };
1094 }
1095 if naf_b[i] != 0 {
1096 t = add_b(&t.to_extended(), naf_b[i]);
1097 }
1098 if i == 0 {
1099 return t.to_extended();
1100 }
1101 acc = t.to_projective();
1102 i -= 1;
1103 }
1104}
1105
1106#[cfg(feature = "bench-internals")]
1109#[doc(hidden)]
1110pub fn mul_basepoint_for_bench(scalar: &[u8; 32]) -> Point {
1111 fn on<F: Curve>(scalar: &[u8; 32]) -> Point {
1112 Point(mul_basepoint::<F>(scalar).convert::<Fe>())
1113 }
1114 dispatch!(on(scalar))
1115}
1116
1117fn basepoint<F: EdField>() -> Ext<F> {
1119 Ext {
1120 x: F::from_limbs51(BASEPOINT_X),
1121 y: F::from_limbs51(BASEPOINT_Y),
1122 z: F::ONE,
1123 t: F::from_limbs51(BASEPOINT_T),
1124 }
1125}
1126
1127const BASEPOINT_X: [u64; 5] = [
1136 1_738_742_601_995_546,
1137 1_146_398_526_822_698,
1138 2_070_867_633_025_821,
1139 562_264_141_797_630,
1140 587_772_402_128_613,
1141];
1142const BASEPOINT_Y: [u64; 5] = [
1143 1_801_439_850_948_184,
1144 1_351_079_888_211_148,
1145 450_359_962_737_049,
1146 900_719_925_474_099,
1147 1_801_439_850_948_198,
1148];
1149const BASEPOINT_T: [u64; 5] = [
1150 1_841_354_044_333_475,
1151 16_398_895_984_059,
1152 755_974_180_946_558,
1153 900_171_276_175_154,
1154 1_821_297_809_914_039,
1155];
1156
1157pub struct Ed25519;
1159
1160impl Algorithm for Ed25519 {
1161 const ID: &'static str = "ed25519";
1162 const NAME: &'static str = "Ed25519";
1163}
1164
1165fn expand_seed(seed: &[u8]) -> ([u8; 32], [u8; 32]) {
1167 let h = Sha512::digest(seed);
1168 let mut a = [0u8; 32];
1169 let mut prefix = [0u8; 32];
1170 a.copy_from_slice(&h.as_ref()[..32]);
1171 prefix.copy_from_slice(&h.as_ref()[32..]);
1172 a[0] &= 248;
1173 a[31] &= 127;
1174 a[31] |= 64;
1175 (a, prefix)
1176}
1177
1178fn wnaf(scalar: &[u8; 32], w: u32) -> [i8; 258] {
1188 debug_assert!((2..=8).contains(&w), "window width out of range");
1189 let half = 1i64 << (w - 1);
1190 let full = 1i64 << w;
1191 let mask = (full - 1) as u64;
1192
1193 let mut naf = [0i8; 258];
1194 let mut k = [0u64; 5];
1201 for (i, limb) in k.iter_mut().take(4).enumerate() {
1202 let mut b = [0u8; 8];
1203 b.copy_from_slice(&scalar[i * 8..i * 8 + 8]);
1204 *limb = u64::from_le_bytes(b);
1205 }
1206
1207 let mut i = 0;
1208 while k.iter().any(|&x| x != 0) {
1209 if k[0] & 1 == 1 {
1210 let mut d = (k[0] & mask) as i64;
1211 if d >= half {
1212 d -= full;
1213 }
1214 naf[i] = d as i8;
1215 if d > 0 {
1216 sub_u64(&mut k, d as u64);
1217 } else {
1218 add_u64(&mut k, d.unsigned_abs());
1219 }
1220 }
1221 shr1(&mut k);
1222 i += 1;
1223 }
1224 naf
1225}
1226
1227fn sub_u64(k: &mut [u64; 5], v: u64) {
1229 let (d, mut borrow) = k[0].overflowing_sub(v);
1230 k[0] = d;
1231 for limb in k.iter_mut().skip(1) {
1232 if !borrow {
1233 break;
1234 }
1235 let (d, b) = limb.overflowing_sub(1);
1236 *limb = d;
1237 borrow = b;
1238 }
1239}
1240
1241fn add_u64(k: &mut [u64; 5], v: u64) {
1243 let (d, mut carry) = k[0].overflowing_add(v);
1244 k[0] = d;
1245 for limb in k.iter_mut().skip(1) {
1246 if !carry {
1247 break;
1248 }
1249 let (d, c) = limb.overflowing_add(1);
1250 *limb = d;
1251 carry = c;
1252 }
1253}
1254
1255fn shr1(k: &mut [u64; 5]) {
1257 for i in 0..4 {
1258 k[i] = (k[i] >> 1) | (k[i + 1] << 63);
1259 }
1260 k[4] >>= 1;
1261}
1262
1263fn hash_to_scalar(parts: &[&[u8]]) -> [u8; 32] {
1265 let mut h = Sha512::new();
1266 for p in parts {
1267 h.update(p);
1268 }
1269 let digest = h.finalize();
1270 let mut wide = [0u8; 64];
1271 wide.copy_from_slice(digest.as_ref());
1272 scalar::reduce_wide(&wide)
1273}
1274
1275pub struct Ed25519Key {
1292 scalar: [u8; 32],
1294 prefix: [u8; 32],
1296 public: [u8; 32],
1298}
1299
1300impl Drop for Ed25519Key {
1301 fn drop(&mut self) {
1302 self.scalar.zeroize();
1303 self.prefix.zeroize();
1304 }
1306}
1307
1308impl Ed25519Key {
1309 pub fn from_seed(seed: &[u8]) -> Result<Self> {
1311 ic_core::module::operational()?;
1312 ensure!(seed.len() == 32, InvalidLength, "ed25519 seed");
1313 let (scalar, prefix) = expand_seed(seed);
1314 let public = mul_basepoint_compressed(&scalar);
1315 Ok(Self {
1316 scalar,
1317 prefix,
1318 public,
1319 })
1320 }
1321
1322 pub fn public_key(&self) -> &[u8; 32] {
1324 &self.public
1325 }
1326
1327 pub fn sign(&self, message: &[u8], signature: &mut [u8]) -> Result<()> {
1329 ic_core::module::operational()?;
1330 ensure!(
1331 signature.len() == 64,
1332 InvalidLength,
1333 "ed25519 signature buffer"
1334 );
1335
1336 let mut r = hash_to_scalar(&[&self.prefix, message]);
1339 let big_r = mul_basepoint_compressed(&r);
1340
1341 let k = hash_to_scalar(&[&big_r, &self.public, message]);
1342 let s = scalar::mul_add(&k, &self.scalar, &r);
1343
1344 signature[..32].copy_from_slice(&big_r);
1345 signature[32..].copy_from_slice(&s);
1346 r.zeroize();
1347 Ok(())
1348 }
1349}
1350
1351impl SignatureScheme for Ed25519 {
1352 const PRIVATE_KEY_LEN: usize = 32;
1353 const PUBLIC_KEY_LEN: usize = 32;
1354 const SIGNATURE_LEN: usize = 64;
1355
1356 fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()> {
1357 ic_core::module::operational()?;
1358 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1359 ensure!(out.len() == 32, InvalidLength, "ed25519 public key buffer");
1360 let (mut a, mut prefix) = expand_seed(private_key);
1361 out.copy_from_slice(&mul_basepoint_compressed(&a));
1362 a.zeroize();
1363 prefix.zeroize();
1364 Ok(())
1365 }
1366
1367 fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()> {
1368 ic_core::module::operational()?;
1369 ensure!(private_key.len() == 32, InvalidLength, "ed25519 seed");
1370 ensure!(
1371 signature.len() == 64,
1372 InvalidLength,
1373 "ed25519 signature buffer"
1374 );
1375
1376 Ed25519Key::from_seed(private_key)?.sign(message, signature)
1380 }
1381
1382 fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()> {
1383 ic_core::module::operational()?;
1384 if public_key.len() == 32 && signature.len() == 64 {
1391 let mut pk = [0u8; 32];
1392 pk.copy_from_slice(public_key);
1393 let mut big_r = [0u8; 32];
1394 big_r.copy_from_slice(&signature[..32]);
1395 let mut s = [0u8; 32];
1396 s.copy_from_slice(&signature[32..]);
1397 if scalar::is_canonical(&s) {
1398 let k = hash_to_scalar(&[&big_r, &pk, message]);
1399 return match dispatch!(check_one_shot(&pk, &big_r, &k, &s)) {
1402 OneShot::Key(KeyDecode::Malformed) => Err(ic_core::err!(
1403 MalformedEncoding,
1404 "ed25519 public key is not on the curve or not canonical"
1405 )),
1406 OneShot::Key(_) => Err(ic_core::err!(
1407 InvalidParameter,
1408 "ed25519 public key has small order"
1409 )),
1410 OneShot::Signature(c) => c.into_result(),
1411 };
1412 }
1413 }
1414 Ed25519VerifyKey::from_bytes(public_key)?.verify(message, signature)
1415 }
1416}
1417
1418pub struct Ed25519VerifyKey {
1432 compressed: [u8; 32],
1434 neg_a: ([u8; 32], [u8; 32]),
1437}
1438
1439enum KeyDecode {
1441 Malformed,
1443 SmallOrder,
1445 Negated([u8; 32], [u8; 32]),
1447}
1448
1449fn decode_key<F: Curve>(bytes: &[u8; 32]) -> KeyDecode {
1451 match Ext::<F>::decompress(bytes) {
1452 None => KeyDecode::Malformed,
1453 Some(a) if a.has_small_order() => KeyDecode::SmallOrder,
1454 Some(a) => {
1456 let n = a.negate();
1457 KeyDecode::Negated(n.x.to_bytes(), n.y.to_bytes())
1458 }
1459 }
1460}
1461
1462#[derive(PartialEq, Eq, Debug)]
1464enum Check {
1465 RMalformed,
1467 RSmallOrder,
1469 Mismatch,
1471 Holds,
1473}
1474
1475impl Check {
1476 fn into_result(self) -> Result<()> {
1477 match self {
1478 Check::RMalformed => Err(ic_core::err!(
1479 MalformedEncoding,
1480 "ed25519 signature R is not on the curve or not canonical"
1481 )),
1482 Check::RSmallOrder => Err(ic_core::err!(
1483 MalformedEncoding,
1484 "ed25519 signature R has small order"
1485 )),
1486 Check::Mismatch => Err(ic_core::err!(AuthenticationFailed, "ed25519")),
1487 Check::Holds => Ok(()),
1488 }
1489 }
1490}
1491
1492enum OneShot {
1495 Key(KeyDecode),
1496 Signature(Check),
1497}
1498
1499fn check_one_shot<F: Curve>(
1509 pk: &[u8; 32],
1510 big_r: &[u8; 32],
1511 k: &[u8; 32],
1512 s: &[u8; 32],
1513) -> OneShot {
1514 let (a, r) = if F::CHEAP_INVERSION {
1515 (Ext::<F>::decompress(pk), None)
1516 } else {
1517 let (a, r) = Ext::<F>::decompress_pair(pk, big_r);
1518 (a, Some(r))
1519 };
1520 let a = match a {
1521 None => return OneShot::Key(KeyDecode::Malformed),
1522 Some(a) if a.has_small_order() => return OneShot::Key(KeyDecode::SmallOrder),
1523 Some(a) => a,
1524 };
1525 OneShot::Signature(match r {
1526 None => verdict(&a.negate(), big_r, k, s),
1527 Some(r) => verdict_decoded(&a.negate(), r, k, s),
1528 })
1529}
1530
1531fn verdict_decoded<F: Curve>(
1533 neg_a: &Ext<F>,
1534 r: Option<Ext<F>>,
1535 k: &[u8; 32],
1536 s: &[u8; 32],
1537) -> Check {
1538 match r {
1539 None => Check::RMalformed,
1540 Some(r) if r.has_small_order() => Check::RSmallOrder,
1541 Some(r) if left_side(neg_a, k, s).eq_projective(&r) => Check::Holds,
1542 Some(_) => Check::Mismatch,
1543 }
1544}
1545
1546fn left_side<F: Curve>(neg_a: &Ext<F>, k: &[u8; 32], s: &[u8; 32]) -> Ext<F> {
1550 #[cfg(feature = "std")]
1551 let lhs = double_scalar_mul_vartime(neg_a, k, s);
1552 #[cfg(not(feature = "std"))]
1553 let lhs = double_scalar_mul_vartime_no_table(neg_a, k, s);
1554 lhs
1555}
1556
1557fn check_equation<F: Curve>(
1559 neg_a: &([u8; 32], [u8; 32]),
1560 big_r: &[u8; 32],
1561 k: &[u8; 32],
1562 s: &[u8; 32],
1563) -> Check {
1564 let a = Ext::from_affine(F::from_bytes(&neg_a.0), F::from_bytes(&neg_a.1));
1565 verdict(&a, big_r, k, s)
1566}
1567
1568fn verdict<F: Curve>(neg_a: &Ext<F>, big_r: &[u8; 32], k: &[u8; 32], s: &[u8; 32]) -> Check {
1585 let lhs = left_side(neg_a, k, s);
1586 if lhs.compress() == *big_r {
1587 if lhs.has_small_order() {
1588 Check::RSmallOrder
1589 } else {
1590 Check::Holds
1591 }
1592 } else {
1593 match Ext::<F>::decompress(big_r) {
1594 None => Check::RMalformed,
1595 Some(r) if r.has_small_order() => Check::RSmallOrder,
1596 Some(_) => Check::Mismatch,
1597 }
1598 }
1599}
1600
1601impl Ed25519VerifyKey {
1602 pub fn from_bytes(public_key: &[u8]) -> Result<Self> {
1613 ensure!(public_key.len() == 32, InvalidLength, "ed25519 public key");
1614 let mut compressed = [0u8; 32];
1615 compressed.copy_from_slice(public_key);
1616 match dispatch!(decode_key(&compressed)) {
1617 KeyDecode::Malformed => Err(ic_core::err!(
1618 MalformedEncoding,
1619 "ed25519 public key is not on the curve or not canonical"
1620 )),
1621 KeyDecode::SmallOrder => Err(ic_core::err!(
1622 InvalidParameter,
1623 "ed25519 public key has small order"
1624 )),
1625 KeyDecode::Negated(x, y) => Ok(Self {
1626 compressed,
1627 neg_a: (x, y),
1628 }),
1629 }
1630 }
1631
1632 pub fn as_bytes(&self) -> &[u8; 32] {
1634 &self.compressed
1635 }
1636
1637 pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> {
1639 ic_core::module::operational()?;
1640 ensure!(signature.len() == 64, InvalidLength, "ed25519 signature");
1641
1642 let mut big_r = [0u8; 32];
1643 big_r.copy_from_slice(&signature[..32]);
1644 let mut s = [0u8; 32];
1645 s.copy_from_slice(&signature[32..]);
1646
1647 ensure!(
1651 scalar::is_canonical(&s),
1652 MalformedEncoding,
1653 "ed25519 signature S is not reduced"
1654 );
1655
1656 let k = hash_to_scalar(&[&big_r, &self.compressed, message]);
1657 dispatch!(check_equation(&self.neg_a, &big_r, &k, &s)).into_result()
1658 }
1659}
1660
1661impl SelfTest for Ed25519 {
1662 fn self_test() -> Result<()> {
1663 let mut seed = [0u8; 32];
1665 ic_core::codec::hex_decode(
1666 b"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1667 &mut seed,
1668 )?;
1669 let mut want_pk = [0u8; 32];
1670 ic_core::codec::hex_decode(
1671 b"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1672 &mut want_pk,
1673 )?;
1674 let mut want_sig = [0u8; 64];
1675 ic_core::codec::hex_decode(
1676 b"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1677 &mut want_sig,
1678 )?;
1679
1680 let mut pk = [0u8; 32];
1681 <Self as SignatureScheme>::public_key(&seed, &mut pk)?;
1682 ensure!(
1683 ic_core::ct::verify(&want_pk, &pk),
1684 SelfTestFailed,
1685 "ed25519"
1686 );
1687
1688 let mut sig = [0u8; 64];
1689 <Self as SignatureScheme>::sign(&seed, b"", &mut sig)?;
1690 ensure!(
1691 ic_core::ct::verify(&want_sig, &sig),
1692 SelfTestFailed,
1693 "ed25519"
1694 );
1695
1696 <Self as SignatureScheme>::verify(&pk, b"", &sig)?;
1697
1698 sig[0] ^= 1;
1700 ensure!(
1701 <Self as SignatureScheme>::verify(&pk, b"", &sig).is_err(),
1702 SelfTestFailed,
1703 "ed25519"
1704 );
1705 Ok(())
1706 }
1707}
1708
1709#[cfg(test)]
1710mod tests {
1711 use super::*;
1712
1713 type P = Ext<Fe>;
1715 const D: Fe = <Fe as EdField>::D;
1716 const D2: Fe = <Fe as EdField>::D2;
1717 const SQRT_M1: Fe = <Fe as EdField>::SQRT_M1;
1718
1719 fn basepoint() -> P {
1720 super::basepoint::<Fe>()
1721 }
1722
1723 fn mul_basepoint(s: &[u8; 32]) -> P {
1724 super::mul_basepoint::<Fe>(s)
1725 }
1726
1727 fn neg_a(vk: &Ed25519VerifyKey) -> P {
1729 P::from_affine(Fe::from_bytes(&vk.neg_a.0), Fe::from_bytes(&vk.neg_a.1))
1730 }
1731 use ic_core::codec::{hex, unhex};
1732
1733 #[test]
1734 fn curve_constants_are_correct() {
1735 let d = Fe::from_u64(121_665)
1737 .neg()
1738 .mul(&Fe::from_u64(121_666).invert());
1739 assert_eq!(hex(&D.to_bytes()), hex(&d.to_bytes()), "d");
1740 assert_eq!(hex(&D2.to_bytes()), hex(&d.add(&d).to_bytes()), "2d");
1741 assert_eq!(
1743 hex(&SQRT_M1.square().to_bytes()),
1744 hex(&Fe::ONE.neg().to_bytes()),
1745 "sqrt(-1)"
1746 );
1747 }
1748
1749 #[test]
1750 fn basepoint_has_the_expected_coordinates() {
1751 let b = basepoint();
1752 let expected_y = Fe::from_u64(4).mul(&Fe::from_u64(5).invert());
1754 let z_inv = b.z.invert();
1755 assert_eq!(
1756 hex(&b.y.mul(&z_inv).to_bytes()),
1757 hex(&expected_y.to_bytes())
1758 );
1759 assert_eq!(hex(&b.compress()), hex(&BASEPOINT_COMPRESSED));
1760 }
1761
1762 #[test]
1769 fn doubling_agrees_with_adding_a_point_to_itself() {
1770 let mut p = basepoint();
1771 let mut checked = 0;
1772 for _ in 0..16 {
1773 assert_eq!(
1774 p.double().compress(),
1775 p.add(&p).compress(),
1776 "dedicated doubling and self-addition differ"
1777 );
1778 p = p.add(&basepoint());
1779 checked += 1;
1780 }
1781 assert_eq!(checked, 16, "the comparison did not run");
1782
1783 assert_eq!(P::IDENTITY.double().compress(), P::IDENTITY.compress());
1786 }
1787
1788 #[test]
1796 fn projective_equality_agrees_with_compressed_equality() {
1797 let b = basepoint();
1798 let mut points = std::vec![P::IDENTITY, b];
1799 let mut p = b;
1800 for _ in 0..6 {
1801 p = p.double();
1802 points.push(p);
1803 }
1804
1805 let mut checked = 0;
1806 for (i, a) in points.iter().enumerate() {
1807 for (j, c) in points.iter().enumerate() {
1808 let projective = a.eq_projective(c);
1809 let compressed = a.compress() == c.compress();
1810 assert_eq!(
1811 projective, compressed,
1812 "projective and compressed equality differ for {i} vs {j}"
1813 );
1814 checked += 1;
1815 }
1816 }
1817 assert_eq!(checked, 64, "the comparison did not run");
1818
1819 let scaled = b.add(&P::IDENTITY);
1822 assert!(b.eq_projective(&scaled), "equal points with different Z");
1823 assert_eq!(b.compress(), scaled.compress());
1824 }
1825
1826 #[test]
1827 fn group_law_is_consistent() {
1828 let b = basepoint();
1829 assert_eq!(hex(&b.add(&P::IDENTITY).compress()), hex(&b.compress()));
1831 let mut two = [0u8; 32];
1833 two[0] = 2;
1834 assert_eq!(
1835 hex(&b.double().compress()),
1836 hex(&b.mul_scalar(&two).compress())
1837 );
1838 let mut three = [0u8; 32];
1840 three[0] = 3;
1841 assert_eq!(
1842 hex(&b.double().add(&b).compress()),
1843 hex(&b.mul_scalar(&three).compress())
1844 );
1845 }
1846
1847 #[test]
1848 fn order_of_the_basepoint_is_l() {
1849 assert_eq!(
1851 hex(&basepoint().mul_scalar(&scalar::L).compress()),
1852 hex(&P::IDENTITY.compress())
1853 );
1854 }
1855
1856 #[test]
1857 fn compression_roundtrips() {
1858 let b = basepoint();
1859 for k in [1u8, 2, 3, 47, 200] {
1860 let mut s = [0u8; 32];
1861 s[0] = k;
1862 let p = b.mul_scalar(&s);
1863 let c = p.compress();
1864 let d = P::decompress(&c).expect("valid point");
1865 assert_eq!(hex(&d.compress()), hex(&c), "k = {k}");
1866 }
1867 }
1868
1869 #[test]
1874 fn a_signature_under_the_identity_key_is_refused() {
1875 let identity = P::IDENTITY.compress();
1876 let s = scalar::reduce(&[7u8; 32]);
1877 let r = mul_basepoint(&s).compress();
1878 let k = hash_to_scalar(&[&r, &identity, b"any message"]);
1879 let neg_a = P::IDENTITY.negate();
1880 let lhs = double_scalar_mul_vartime(&neg_a, &k, &s);
1881 assert!(
1882 lhs.eq_projective(&P::decompress(&r).unwrap()),
1883 "the forgery satisfies the equation"
1884 );
1885
1886 let err = Ed25519VerifyKey::from_bytes(&identity).err().unwrap();
1887 assert_eq!(err.kind(), ic_core::ErrorKind::InvalidParameter);
1888 let mut sig = [0u8; 64];
1889 sig[..32].copy_from_slice(&r);
1890 sig[32..].copy_from_slice(&s);
1891 assert!(Ed25519::verify(&identity, b"any message", &sig).is_err());
1892 }
1893
1894 #[test]
1898 fn a_signature_with_an_identity_r_is_refused() {
1899 let key = Ed25519Key::from_seed(&[0x42u8; 32]).unwrap();
1900 let r = P::IDENTITY.compress();
1901 let message = b"identity R";
1902 let k = hash_to_scalar(&[&r, &key.public, message]);
1903 let s = scalar::mul_add(&k, &key.scalar, &[0u8; 32]);
1904 let mut sig = [0u8; 64];
1905 sig[..32].copy_from_slice(&r);
1906 sig[32..].copy_from_slice(&s);
1907
1908 let vk = Ed25519VerifyKey::from_bytes(&key.public).unwrap();
1909 let lhs = double_scalar_mul_vartime(&neg_a(&vk), &k, &s);
1910 assert!(
1911 lhs.eq_projective(&P::IDENTITY),
1912 "the signature satisfies the equation"
1913 );
1914 let err = vk.verify(message, &sig).unwrap_err();
1915 assert_eq!(err.kind(), ic_core::ErrorKind::MalformedEncoding);
1916 assert_eq!(lhs.compress(), r);
1919 let err = Ed25519::verify(&key.public, message, &sig).unwrap_err();
1920 assert_eq!(err.kind(), ic_core::ErrorKind::MalformedEncoding);
1921 }
1922
1923 #[test]
1927 fn non_canonical_encodings_do_not_decode() {
1928 for sign in [0u8, 0x80] {
1929 let mut canonical = [0u8; 32];
1930 canonical[0] = 3;
1931 canonical[31] |= sign;
1932 let point = P::decompress(&canonical).expect("y = 3 is on the curve");
1933 assert!(!point.has_small_order());
1934 let mut alias = [0xffu8; 32];
1936 alias[0] = 0xf0;
1937 alias[31] = 0x7f | sign;
1938 assert!(P::decompress(&alias).is_none(), "sign {sign:#x}");
1939 }
1940 }
1941
1942 #[test]
1943 fn decompression_rejects_non_curve_points() {
1944 let mut bad = [0u8; 32];
1946 bad[0] = 2;
1947 assert!(P::decompress(&bad).is_none());
1948 }
1949
1950 #[test]
1952 fn rfc8032_vectors() {
1953 let cases: [(&str, &str, &str, &str); 3] = [
1954 (
1955 "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
1956 "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
1957 "",
1958 "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
1959 ),
1960 (
1961 "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
1962 "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
1963 "72",
1964 "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
1965 ),
1966 (
1967 "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7",
1968 "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
1969 "af82",
1970 "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
1971 ),
1972 ];
1973
1974 for (seed_hex, pk_hex, msg_hex, sig_hex) in cases {
1975 let seed = unhex(seed_hex).unwrap();
1976 let msg = unhex(msg_hex).unwrap();
1977
1978 let mut pk = [0u8; 32];
1979 Ed25519::public_key(&seed, &mut pk).unwrap();
1980 assert_eq!(hex(&pk), pk_hex, "public key for {seed_hex}");
1981
1982 let mut sig = [0u8; 64];
1983 Ed25519::sign(&seed, &msg, &mut sig).unwrap();
1984 assert_eq!(hex(&sig), sig_hex, "signature for {seed_hex}");
1985
1986 Ed25519::verify(&pk, &msg, &sig).unwrap();
1987 }
1988 }
1989
1990 #[test]
1991 fn verification_rejects_tampering() {
1992 let seed = [0x42u8; 32];
1993 let mut pk = [0u8; 32];
1994 Ed25519::public_key(&seed, &mut pk).unwrap();
1995 let mut sig = [0u8; 64];
1996 Ed25519::sign(&seed, b"authentic", &mut sig).unwrap();
1997 Ed25519::verify(&pk, b"authentic", &sig).unwrap();
1998
1999 assert!(Ed25519::verify(&pk, b"forged", &sig).is_err());
2001 let mut bad = sig;
2003 bad[0] ^= 1;
2004 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
2005 let mut bad = sig;
2007 bad[40] ^= 1;
2008 assert!(Ed25519::verify(&pk, b"authentic", &bad).is_err());
2009 let mut other_pk = [0u8; 32];
2011 Ed25519::public_key(&[0x43u8; 32], &mut other_pk).unwrap();
2012 assert!(Ed25519::verify(&other_pk, b"authentic", &sig).is_err());
2013
2014 use ic_core::ErrorKind::{AuthenticationFailed, MalformedEncoding};
2019 let vk = Ed25519VerifyKey::from_bytes(&pk).unwrap();
2020 let kind = |r: Result<()>| r.unwrap_err().kind();
2021 assert_eq!(
2022 kind(Ed25519::verify(&pk, b"forged", &sig)),
2023 AuthenticationFailed
2024 );
2025 assert_eq!(kind(vk.verify(b"forged", &sig)), AuthenticationFailed);
2026 let mut bad = sig;
2027 bad[40] ^= 1;
2028 assert_eq!(kind(vk.verify(b"authentic", &bad)), AuthenticationFailed);
2029 let mut bad = sig;
2031 bad[..32].copy_from_slice(&[0u8; 32]);
2032 bad[0] = 2;
2033 assert!(Ext::<Fe>::decompress(&bad[..32].try_into().unwrap()).is_none());
2034 assert_eq!(
2035 kind(Ed25519::verify(&pk, b"authentic", &bad)),
2036 MalformedEncoding
2037 );
2038 assert_eq!(kind(vk.verify(b"authentic", &bad)), MalformedEncoding);
2039 }
2040
2041 #[test]
2044 fn rejects_non_canonical_s() {
2045 let seed = [0x42u8; 32];
2046 let mut pk = [0u8; 32];
2047 Ed25519::public_key(&seed, &mut pk).unwrap();
2048 let mut sig = [0u8; 64];
2049 Ed25519::sign(&seed, b"msg", &mut sig).unwrap();
2050
2051 let mut carry = 0u16;
2054 for i in 0..32 {
2055 let t = sig[32 + i] as u16 + scalar::L[i] as u16 + carry;
2056 sig[32 + i] = t as u8;
2057 carry = t >> 8;
2058 }
2059 assert!(Ed25519::verify(&pk, b"msg", &sig).is_err());
2060 }
2061
2062 #[test]
2069 fn the_cached_key_signs_identically_to_the_seed() {
2070 let mut checked = 0;
2071 for seed in [[0x11u8; 32], [0x9du8; 32], [0xffu8; 32]] {
2072 for message in [&b""[..], &b"x"[..], &b"a longer message to sign"[..]] {
2073 let mut from_seed = [0u8; 64];
2074 Ed25519::sign(&seed, message, &mut from_seed).unwrap();
2075
2076 let key = Ed25519Key::from_seed(&seed).unwrap();
2077 let mut from_key = [0u8; 64];
2078 key.sign(message, &mut from_key).unwrap();
2079
2080 assert_eq!(from_seed, from_key, "the two signing paths diverged");
2081
2082 let mut derived = [0u8; 32];
2084 Ed25519::public_key(&seed, &mut derived).unwrap();
2085 assert_eq!(&derived, key.public_key());
2086
2087 Ed25519::verify(&derived, message, &from_key).unwrap();
2089 checked += 1;
2090 }
2091 }
2092 assert_eq!(checked, 9, "the comparison did not run");
2093 }
2094
2095 fn windowed_scalars() -> std::vec::Vec<[u8; 32]> {
2100 let mut one = [0u8; 32];
2101 one[0] = 1;
2102 let mut eight = [0u8; 32];
2103 eight[0] = 8;
2104 let mut top = [0xffu8; 32];
2105 top[31] = 0x7f;
2106 let mut clamped = [0x9du8; 32];
2107 clamped[0] &= 248;
2108 clamped[31] &= 127;
2109 clamped[31] |= 64;
2110 let mut l_minus_1 = scalar::L;
2111 l_minus_1[0] -= 1;
2112 let mut out = std::vec![[0u8; 32], one, eight, top, clamped, l_minus_1];
2113 for fill in [0x88u8, 0x77, 0x99, 0x55, 0xaa] {
2114 let mut s = [fill; 32];
2115 s[31] &= 0x7f;
2116 out.push(s);
2117 }
2118 out
2119 }
2120
2121 #[test]
2131 fn the_basepoint_constant_is_the_decompressed_encoding() {
2132 let decoded = P::decompress(&BASEPOINT_COMPRESSED).expect("the RFC 8032 basepoint");
2133 let zinv = decoded.z.invert();
2134 for (name, constant, from_encoding) in [
2135 ("x", basepoint().x, decoded.x.mul(&zinv)),
2136 ("y", basepoint().y, decoded.y.mul(&zinv)),
2137 ("t", basepoint().t, decoded.t.mul(&zinv)),
2138 ] {
2139 assert_eq!(
2140 constant.to_bytes(),
2141 from_encoding.to_bytes(),
2142 "{name} differs"
2143 );
2144 }
2145 assert_eq!(basepoint().z.to_bytes(), Fe::ONE.to_bytes());
2146 assert_eq!(basepoint().compress(), BASEPOINT_COMPRESSED);
2147 }
2148
2149 #[test]
2150 fn the_windowed_multiplication_agrees_with_the_ladder() {
2151 let b = basepoint();
2152 let mut seven = [0u8; 32];
2153 seven[0] = 7;
2154 let p = b.mul_scalar(&seven);
2155
2156 let mut checked = 0;
2157 for point in [b, p] {
2158 for scalar in windowed_scalars() {
2159 assert_eq!(
2160 mul_scalar_windowed(&point, &scalar).compress(),
2161 point.mul_scalar(&scalar).compress(),
2162 "windowed and ladder differ for {scalar:02x?}"
2163 );
2164 checked += 1;
2165 }
2166 }
2167 assert!(checked >= 20, "only {checked} comparisons ran");
2168 }
2169
2170 #[test]
2173 fn the_untabled_double_multiplication_agrees() {
2174 let b = basepoint();
2175 let mut checked = 0;
2176 for (i, k) in windowed_scalars().into_iter().enumerate() {
2177 let mut seed = [0u8; 32];
2178 seed[0] = 3 + i as u8;
2179 let a = b.mul_scalar(&seed);
2180 for s in [k, [0xffu8; 32], [0x9du8; 32]] {
2181 let untabled = double_scalar_mul_vartime_no_table(&a, &k, &s);
2182 let tabled = double_scalar_mul_vartime(&a, &k, &s);
2183 let ladders = a.mul_scalar(&k).add(&b.mul_scalar(&s));
2184 assert_eq!(
2185 untabled.compress(),
2186 tabled.compress(),
2187 "k={k:02x?} s={s:02x?}"
2188 );
2189 assert_eq!(
2190 untabled.compress(),
2191 ladders.compress(),
2192 "k={k:02x?} s={s:02x?}"
2193 );
2194 checked += 1;
2195 }
2196 }
2197 assert!(checked >= 30, "only {checked} comparisons ran");
2198 }
2199
2200 #[test]
2202 fn the_wnaf_digits_are_odd_sparse_and_faithful() {
2203 for scalar in [[1u8; 32], [0x9du8; 32], [0xffu8; 32], [0x55u8; 32]] {
2204 let naf = wnaf(&scalar, 5);
2205
2206 let mut previous_nonzero: Option<usize> = None;
2207 for (i, d) in naf.iter().enumerate() {
2208 if *d == 0 {
2209 continue;
2210 }
2211 assert!(d % 2 != 0, "digit {d} at {i} is not odd");
2212 assert!((-15..=15).contains(d), "digit {d} at {i} is out of range");
2213 if let Some(j) = previous_nonzero {
2214 assert!(i - j >= 5, "digits at {j} and {i} are adjacent");
2215 }
2216 previous_nonzero = Some(i);
2217 }
2218
2219 const M: u128 = 1_000_000_007;
2222 let mut from_digits = 0u128;
2223 let mut power = 1u128;
2224 for d in naf {
2225 let term = ((d as i128).rem_euclid(M as i128)) as u128;
2226 from_digits = (from_digits + term * power) % M;
2227 power = power * 2 % M;
2228 }
2229 let mut from_bytes = 0u128;
2230 let mut p = 1u128;
2231 for byte in scalar {
2232 from_bytes = (from_bytes + (byte as u128) * p) % M;
2233 p = p * 256 % M;
2234 }
2235 assert_eq!(from_digits, from_bytes, "recoding changed the value");
2236 }
2237 }
2238
2239 #[test]
2240 fn signing_is_deterministic() {
2241 let seed = [0x7fu8; 32];
2242 let mut a = [0u8; 64];
2243 let mut b = [0u8; 64];
2244 Ed25519::sign(&seed, b"same input", &mut a).unwrap();
2245 Ed25519::sign(&seed, b"same input", &mut b).unwrap();
2246 assert_eq!(a, b);
2247 }
2248
2249 #[test]
2250 fn rejects_wrong_lengths() {
2251 let mut out = [0u8; 32];
2252 assert!(Ed25519::public_key(&[0u8; 31], &mut out).is_err());
2253 assert!(Ed25519::sign(&[0u8; 32], b"", &mut [0u8; 63]).is_err());
2254 assert!(Ed25519::verify(&[0u8; 32], b"", &[0u8; 63]).is_err());
2255 }
2256
2257 #[test]
2258 fn self_test_passes() {
2259 Ed25519::self_test().unwrap();
2260 }
2261
2262 fn sample_points(n: usize) -> Vec<P> {
2264 let mut out = Vec::new();
2265 let mut p = basepoint();
2266 for _ in 0..n {
2267 out.push(p);
2268 p = p.double().add(&basepoint());
2269 }
2270 out
2271 }
2272
2273 #[test]
2283 fn the_completed_doubling_agrees_with_the_extended_one() {
2284 for p in sample_points(40) {
2285 let want = p.double();
2286 let got = p.to_projective().double().to_extended();
2287 assert!(got.eq_projective(&want), "doubling disagrees");
2288 let chained = p.to_projective().double().to_projective().double();
2291 let twice = p.double().double();
2292 assert!(chained.to_extended().eq_projective(&twice), "two doublings");
2293 }
2294 }
2295
2296 #[test]
2298 fn niels_addition_agrees_with_the_general_one() {
2299 let pts = sample_points(20);
2300 for p in &pts {
2301 for q in &pts {
2302 let want = p.add(q);
2303 let got = p.add_niels(&q.to_niels()).to_extended();
2304 assert!(got.eq_projective(&want), "add_niels disagrees");
2305
2306 let want_sub = p.add(&q.negate());
2307 let got_sub = p.sub_niels(&q.to_niels()).to_extended();
2308 assert!(got_sub.eq_projective(&want_sub), "sub_niels disagrees");
2309 }
2310 }
2311 }
2312
2313 #[test]
2321 fn affine_niels_addition_agrees_with_the_general_one() {
2322 let pts = sample_points(20);
2323 for p in &pts {
2324 for q in &pts {
2325 let want = p.add(q);
2326 let got = p.add_affine_niels(&q.to_affine_niels()).to_extended();
2327 assert!(got.eq_projective(&want), "add_affine_niels disagrees");
2328
2329 let mut n = q.to_affine_niels();
2331 n.conditional_negate(1);
2332 let want_neg = p.add(&q.negate());
2333 let got_neg = p.add_affine_niels(&n).to_extended();
2334 assert!(got_neg.eq_projective(&want_neg), "negated form disagrees");
2335 }
2336 }
2337 }
2338
2339 #[test]
2346 #[ignore = "diagnostic, not a test"]
2347 fn where_verify_spends_its_time() {
2348 use std::time::Instant;
2349
2350 let seed = [7u8; 32];
2351 let key = Ed25519Key::from_seed(&seed).unwrap();
2352 let msg = b"benchmark message";
2353 let mut sig = [0u8; 64];
2354 key.sign(msg, &mut sig).unwrap();
2355 let pk = *key.public_key();
2356
2357 let mut big_r = [0u8; 32];
2358 big_r.copy_from_slice(&sig[..32]);
2359 let mut s_sc = [0u8; 32];
2360 s_sc.copy_from_slice(&sig[32..]);
2361
2362 let n = 2000;
2363 let time = |label: &str, f: &mut dyn FnMut()| {
2364 let mut best = f64::INFINITY;
2365 for _ in 0..5 {
2366 let t = Instant::now();
2367 for _ in 0..n {
2368 f();
2369 }
2370 let e = t.elapsed().as_secs_f64() / n as f64 * 1e6;
2371 if e < best {
2372 best = e;
2373 }
2374 }
2375 println!(" {label:<34} {best:>9.2} us");
2376 best
2377 };
2378
2379 let a_point = P::decompress(&pk).unwrap();
2380 let k = hash_to_scalar(&[&big_r, &pk, msg]);
2381
2382 println!(
2383 "
2384ed25519 verify, cost breakdown:"
2385 );
2386 let d = time("decompress (x2 per verify)", &mut || {
2387 core::hint::black_box(P::decompress(&pk));
2388 });
2389 let h = time("hash_to_scalar", &mut || {
2390 core::hint::black_box(hash_to_scalar(&[&big_r, &pk, msg]));
2391 });
2392 let b = time("mul_basepoint (const time)", &mut || {
2393 core::hint::black_box(mul_basepoint(&s_sc));
2394 });
2395 let v = time("double_scalar_mul_vartime", &mut || {
2396 core::hint::black_box(double_scalar_mul_vartime(&a_point.negate(), &k, &s_sc));
2397 });
2398 time(" of which: wnaf(k,5)+wnaf(s,8)", &mut || {
2399 core::hint::black_box(wnaf(&k, 5));
2400 core::hint::black_box(wnaf(&s_sc, 8));
2401 });
2402 time(" of which: odd_a table build", &mut || {
2403 let twice = a_point.double();
2404 let mut odd = [a_point; 8];
2405 for i in 1..8 {
2406 odd[i] = odd[i - 1].add(&twice);
2407 }
2408 let t: [Niels<Fe>; 8] = core::array::from_fn(|i| odd[i].to_niels());
2409 core::hint::black_box(t);
2410 });
2411 time(" of which: 255 doublings", &mut || {
2412 let mut p = a_point;
2413 for _ in 0..255 {
2414 p = p.double();
2415 }
2416 core::hint::black_box(p);
2417 });
2418 time(" of which: 79 additions", &mut || {
2419 let mut p = a_point;
2420 for _ in 0..79 {
2421 p = p.add(&a_point);
2422 }
2423 core::hint::black_box(p);
2424 });
2425 time("compress (one inversion)", &mut || {
2426 core::hint::black_box(a_point.compress());
2427 });
2428 println!(
2429 " {:<34} {:>9.2} us",
2430 "-- accounted for",
2431 2.0 * d + h + b + v
2432 );
2433
2434 println!(
2436 "
2437field and point primitives, nanoseconds:"
2438 );
2439 let nn = 200_000;
2440 let ns = |label: &str, f: &mut dyn FnMut()| {
2441 let mut best = f64::INFINITY;
2442 for _ in 0..5 {
2443 let t = Instant::now();
2444 for _ in 0..nn {
2445 f();
2446 }
2447 let e = t.elapsed().as_secs_f64() / nn as f64 * 1e9;
2448 if e < best {
2449 best = e;
2450 }
2451 }
2452 println!(" {label:<34} {best:>9.2} ns");
2453 };
2454 let fx = a_point.x;
2455 let fy = a_point.y;
2456 ns("Fe::mul", &mut || {
2457 core::hint::black_box(core::hint::black_box(&fx).mul(core::hint::black_box(&fy)));
2458 });
2459 ns("Fe::square", &mut || {
2460 core::hint::black_box(core::hint::black_box(&fx).square());
2461 });
2462 ns("Fe::add", &mut || {
2463 core::hint::black_box(core::hint::black_box(&fx).add(core::hint::black_box(&fy)));
2464 });
2465 ns("Fe::sub", &mut || {
2466 core::hint::black_box(core::hint::black_box(&fx).sub(core::hint::black_box(&fy)));
2467 });
2468 ns("Fe::neg", &mut || {
2469 core::hint::black_box(core::hint::black_box(&fx).neg());
2470 });
2471 let proj = a_point.to_projective();
2472 let comp = proj.double();
2473 let an = a_point.to_affine_niels();
2474 ns("Projective::double (4S)", &mut || {
2475 core::hint::black_box(core::hint::black_box(&proj).double());
2476 });
2477 ns("Projective::double_projective", &mut || {
2478 core::hint::black_box(core::hint::black_box(proj).double_projective());
2479 });
2480 ns("Completed::to_projective (3M)", &mut || {
2481 core::hint::black_box(core::hint::black_box(&comp).to_projective());
2482 });
2483 ns("Completed::to_extended (4M)", &mut || {
2484 core::hint::black_box(core::hint::black_box(&comp).to_extended());
2485 });
2486 ns("P::add_affine_niels (3M)", &mut || {
2487 core::hint::black_box(
2488 core::hint::black_box(&a_point).add_affine_niels(core::hint::black_box(&an)),
2489 );
2490 });
2491 ns("P::double", &mut || {
2492 core::hint::black_box(core::hint::black_box(&a_point).double());
2493 });
2494 ns("P::add", &mut || {
2495 core::hint::black_box(
2496 core::hint::black_box(&a_point).add(core::hint::black_box(&a_point)),
2497 );
2498 });
2499 }
2500
2501 #[test]
2503 #[ignore = "diagnostic, not a test"]
2504 fn count_the_point_operations() {
2505 let mut doublings = 0usize;
2506 let mut adds_a = 0usize;
2507 let mut adds_b = 0usize;
2508 let mut state = 0x1234_5678_9abc_def0u64;
2509 let trials = 200;
2510 for _ in 0..trials {
2511 let mut kb = [0u8; 32];
2512 for c in kb.chunks_exact_mut(8) {
2513 state ^= state >> 12;
2514 state ^= state << 25;
2515 state ^= state >> 27;
2516 c.copy_from_slice(&state.wrapping_mul(0x2545_f491_4f6c_dd1d).to_le_bytes());
2517 }
2518 kb[31] &= 0x0f;
2519 let na = wnaf(&kb, 5);
2520 let nb = wnaf(&kb, 8);
2521 let mut i = 257;
2522 while i > 0 && na[i] == 0 && nb[i] == 0 {
2523 i -= 1;
2524 }
2525 doublings += i + 1;
2526 adds_a += na.iter().filter(|d| **d != 0).count();
2527 adds_b += nb.iter().filter(|d| **d != 0).count();
2528 }
2529 let d = doublings as f64 / trials as f64;
2530 let aa = adds_a as f64 / trials as f64;
2531 let ab = adds_b as f64 / trials as f64;
2532 println!(
2533 "
2534 per double-scalar multiplication, averaged over {trials} scalars:"
2535 );
2536 println!(" doublings {d:>8.1}");
2537 println!(" additions, w=5 table (A) {aa:>8.1}");
2538 println!(" additions, w=8 table (B) {ab:>8.1}");
2539 println!(" additions, building A {:>8.1}", 8.0);
2540 println!(" ---");
2541 println!(" total additions {:>8.1}", aa + ab + 8.0);
2542 println!(
2543 " field muls, at 4M+4S per doubling and 9M per addition: {:>6.0}",
2544 d * 8.0 + (aa + ab + 8.0) * 9.0
2545 );
2546 println!(
2547 " the same at dalek's 3M+4S and 7M: {:>6.0}",
2548 d * 7.0 + (aa + ab + 8.0) * 7.0
2549 );
2550 }
2551}