diff --git a/Cargo.toml b/Cargo.toml index 879ffdb4d..f654ee68a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,5 @@ ed448-goldilocks = { path = "ed448-goldilocks" } hash2curve = { path = "hash2curve" } primefield = { path = "primefield" } primeorder = { path = "primeorder" } + +#rustcrypto-group = { git = "https://github.com/RustCrypto/group", branch = "dense-wnaf" } diff --git a/p256/tests/projective.proptest-regressions b/p256/tests/projective.proptest-regressions new file mode 100644 index 000000000..dc9f6ab50 --- /dev/null +++ b/p256/tests/projective.proptest-regressions @@ -0,0 +1,8 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc e19ee42c127b7289fbe7e42df47abf141eb644afcbd13ac141e39b9960362174 # shrinks to point = ProjectivePoint { x: FieldElement(0x823CD15F6DD3C71933565064513A6B2BD183E554C6A08622F713EBBBFACE98BE), y: FieldElement(0x55DF5D5850F47BAD82149139979369FE498A9022A412B5E0BEDD2CFC21C3ED91), z: FieldElement(0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5) }, scalar = Scalar(0x0000000000000000000000000000000000000000000000000000000000000001) +cc 67d76546dee30db7f75f666ed335f84d90da4ce8775d612dcdb88a3058ef7071 # shrinks to point = ProjectivePoint { x: FieldElement(0x823CD15F6DD3C71933565064513A6B2BD183E554C6A08622F713EBBBFACE98BE), y: FieldElement(0x55DF5D5850F47BAD82149139979369FE498A9022A412B5E0BEDD2CFC21C3ED91), z: FieldElement(0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5) }, scalar = Scalar(0xAE74000000000000000000000000000000000000000000000000000000000000) diff --git a/p256/tests/projective.rs b/p256/tests/projective.rs index 5be8291eb..056781367 100644 --- a/p256/tests/projective.rs +++ b/p256/tests/projective.rs @@ -8,7 +8,7 @@ use elliptic_curve::{ consts::U32, group::{GroupEncoding, ff::PrimeField}, ops::{LinearCombination, Reduce, ReduceNonZero}, - point::NonIdentity, + point::{AffineCoordinates, NonIdentity}, sec1::{self, ToSec1Point}, }; use p256::{ @@ -16,7 +16,10 @@ use p256::{ test_vectors::group::{ADD_TEST_VECTORS, MUL_TEST_VECTORS}, }; use primeorder::test_projective_arithmetic; -use proptest::{prelude::any, prop_compose, proptest}; +use proptest::{prelude::*, prop_compose, proptest}; + +#[cfg(feature = "alloc")] +use elliptic_curve::group::Wnaf; test_projective_arithmetic!( AffinePoint, @@ -26,6 +29,26 @@ test_projective_arithmetic!( MUL_TEST_VECTORS ); +#[cfg(feature = "alloc")] +#[test] +fn wnaf() { + for (k, coords) in ADD_TEST_VECTORS.iter().enumerate() { + let scalar = Scalar::from(k as u64 + 1); + dbg!(&scalar, coords); + + let mut wnaf = Wnaf::new(); + let p = wnaf + .scalar(&scalar) + .base(ProjectivePoint::GENERATOR) + .to_affine(); + // let mut wnaf_base = wnaf.base(ProjectivePoint::GENERATOR, 1); + // let p = wnaf_base.scalar(&scalar).to_affine(); + + let (x, _y) = (p.x(), p.y()); + assert_eq!(x.0, coords.0); + } +} + #[test] fn projective_identity_to_bytes() { // This is technically an invalid SEC1 encoding, but is preferable to panicking. @@ -52,6 +75,17 @@ prop_compose! { // TODO: move to `primeorder::test_projective_arithmetic`. proptest! { + #[cfg(feature = "alloc")] + #[test] + fn wnaf_proptest( + point in projective(), + scalar in scalar(), + ) { + let result = point * scalar; + let wnaf_result = Wnaf::new().scalar(&scalar).base(point); + prop_assert_eq!(result.to_affine(), wnaf_result.to_affine()); + } + #[test] fn batch_normalize( a in non_identity(), diff --git a/primeorder/src/projective.rs b/primeorder/src/projective.rs index b94eb6d3b..17c304810 100644 --- a/primeorder/src/projective.rs +++ b/primeorder/src/projective.rs @@ -31,7 +31,7 @@ use elliptic_curve::{ }; #[cfg(feature = "alloc")] -use alloc::vec::Vec; +use {alloc::vec::Vec, elliptic_curve::group::WnafGroup}; #[cfg(feature = "serde")] use serdect::serde::{Deserialize, Serialize, de, ser}; @@ -598,6 +598,35 @@ where } } +#[cfg(feature = "alloc")] +impl WnafGroup for ProjectivePoint +where + C: PrimeCurveParams, + FieldBytes: Copy, +{ + fn recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize { + // Empirical heuristic from the zcash/bellman implementation. + if num_scalars >= 32 { + 3 + } else if num_scalars >= 1 { + 2 + } else { + 4 + } + } + + fn scalar_repr_to_le_bytes( + repr: & as PrimeField>::Repr, + ) -> Vec { + // SEC1/NIST curves use big-endian scalar representations; + // reverse to get little-endian for wNAF decomposition. + let mut le: Vec = + AsRef::<[u8]>::as_ref(repr).to_vec(); + le.reverse(); + le + } +} + // // `core::ops` trait impls //