diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 8195002..5570ade 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -496,6 +496,14 @@ macro_rules! curve_impl { fn to_affine(&self) -> $affine { (*self).into() } + + fn recommended_wnaf_for_scalar(scalar: ::Repr) -> Option { + Self::empirical_recommended_wnaf_for_scalar(scalar) + } + + fn recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize { + Self::empirical_recommended_wnaf_for_num_scalars(num_scalars) + } } // The affine point X, Y is represented in the jacobian @@ -555,8 +563,8 @@ macro_rules! curve_impl { pub mod g1 { use rand::{Rand, Rng}; - use super::super::{Fq, Fr}; - use ::{CurveProjective, CurveAffine, PrimeField, Field, BitIterator}; + use super::super::{Fq, Fr, FrRepr}; + use ::{CurveProjective, CurveAffine, PrimeField, PrimeFieldRepr, Field, BitIterator}; curve_impl!(G1, G1Affine, G1Prepared, Fq, Fr); @@ -574,6 +582,40 @@ pub mod g1 { } } + impl G1 { + fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> Option + { + const RECOMMENDATIONS: [usize; 3] = [12, 34, 130]; + + let mut ret = None; + let num_bits = scalar.num_bits() as usize; + + for (i, r) in RECOMMENDATIONS.iter().enumerate() { + if *r >= num_bits { + ret = Some(i + 2) + } + } + + ret + } + + fn empirical_recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize + { + const RECOMMENDATIONS: [usize; 12] = [1, 3, 7, 20, 43, 120, 273, 563, 1630, 3128, 7933, 62569]; + + let mut ret = 4; + for r in RECOMMENDATIONS.iter() { + if num_scalars > *r { + ret += 1; + } else { + break + } + } + + ret + } + } + #[derive(Clone)] pub struct G1Prepared(pub(crate) G1Affine); @@ -838,8 +880,8 @@ pub mod g1 { pub mod g2 { use rand::{Rand, Rng}; - use super::super::{Fq2, Fr}; - use ::{CurveProjective, CurveAffine, PrimeField, Field, BitIterator}; + use super::super::{Fq2, Fr, FrRepr}; + use ::{CurveProjective, CurveAffine, PrimeField, PrimeFieldRepr, Field, BitIterator}; curve_impl!(G2, G2Affine, G2Prepared, Fq2, Fr); @@ -866,6 +908,40 @@ pub mod g2 { } } + impl G2 { + fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> Option + { + const RECOMMENDATIONS: [usize; 3] = [13, 37, 103]; + + let mut ret = None; + let num_bits = scalar.num_bits() as usize; + + for (i, r) in RECOMMENDATIONS.iter().enumerate() { + if *r >= num_bits { + ret = Some(i + 2) + } + } + + ret + } + + fn empirical_recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize + { + const RECOMMENDATIONS: [usize; 11] = [1, 3, 8, 20, 47, 126, 260, 826, 1501, 4555, 84071]; + + let mut ret = 4; + for r in RECOMMENDATIONS.iter() { + if num_scalars > *r { + ret += 1; + } else { + break + } + } + + ret + } + } + #[derive(Clone)] pub struct G2Prepared { pub(crate) coeffs: Vec<(Fq2, Fq2, Fq2)>, diff --git a/src/lib.rs b/src/lib.rs index 0affbbf..3b9c47a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ extern crate rand; pub mod tests; pub mod bls12_381; +pub mod wnaf; use std::fmt; @@ -123,6 +124,15 @@ pub trait CurveProjective: PartialEq + /// Converts this element into its affine representation. fn to_affine(&self) -> Self::Affine; + + /// Recommends a wNAF window table size given a scalar. Returns `None` if normal + /// scalar multiplication is encouraged. If `Some` is returned, it will be between + /// 2 and 22, inclusive. + fn recommended_wnaf_for_scalar(scalar: ::Repr) -> Option; + + /// Recommends a wNAF window size given the number of scalars you intend to multiply + /// a base by. Always returns a number between 2 and 22, inclusive. + fn recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize; } /// Affine representation of an elliptic curve point guaranteed to be diff --git a/src/tests/curve.rs b/src/tests/curve.rs index 9e7c8dd..0356b64 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -58,6 +58,32 @@ pub fn curve_tests() random_doubling_tests::(); random_negation_tests::(); random_transformation_tests::(); + random_wnaf_tests::(); +} + +fn random_wnaf_tests() { + use ::wnaf::*; + use ::PrimeField; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let mut table = vec![]; + let mut wnaf = vec![]; + + for w in 2..14 { + for _ in 0..100 { + let g = G::rand(&mut rng); + let s = G::Scalar::rand(&mut rng).into_repr(); + let mut g1 = g; + g1.mul_assign(s); + + wnaf_table(&mut table, g, w); + wnaf_form(&mut wnaf, s, w); + let g2 = wnaf_exp(&table, &wnaf); + + assert_eq!(g1, g2); + } + } } fn random_negation_tests() { diff --git a/src/wnaf.rs b/src/wnaf.rs new file mode 100644 index 0000000..0c5ae35 --- /dev/null +++ b/src/wnaf.rs @@ -0,0 +1,84 @@ +use super::{CurveProjective, PrimeFieldRepr}; + +/// Replaces the contents of `table` with a wNAF window table for the given window size. +/// +/// This function will panic if provided a window size below two, or above 22. +pub fn wnaf_table(table: &mut Vec, mut base: G, window: usize) +{ + assert!(window < 23); + assert!(window > 1); + + table.truncate(0); + table.reserve(1 << (window-1)); + + let mut dbl = base; + dbl.double(); + + for _ in 0..(1 << (window-1)) { + table.push(base); + base.add_assign(&dbl); + } +} + +/// Replaces the contents of `wnaf` with the wNAF representation of a scalar. +/// +/// This function will panic if provided a window size below two, or above 22. +pub fn wnaf_form(wnaf: &mut Vec, mut c: S, window: usize) +{ + assert!(window < 23); + assert!(window > 1); + + wnaf.truncate(0); + + while !c.is_zero() { + let mut u; + if c.is_odd() { + u = (c.as_ref()[0] % (1 << (window+1))) as i64; + + if u > (1 << window) { + u -= 1 << (window+1); + } + + if u > 0 { + c.sub_noborrow(&S::from(u as u64)); + } else { + c.add_nocarry(&S::from((-u) as u64)); + } + } else { + u = 0; + } + + wnaf.push(u); + + c.div2(); + } +} + +/// Performs wNAF exponentiation with the provided window table and wNAF-form scalar. +/// +/// This function must be provided a `table` and `wnaf` that were constructed with +/// the same window size; otherwise, it may panic or produce invalid results. +pub fn wnaf_exp(table: &[G], wnaf: &[i64]) -> G +{ + let mut result = G::zero(); + + let mut found_one = false; + + for n in wnaf.iter().rev() { + if found_one { + result.double(); + } + + if *n != 0 { + found_one = true; + + if *n > 0 { + result.add_assign(&table[(n/2) as usize]); + } else { + result.sub_assign(&table[((-n)/2) as usize]); + } + } + } + + result +}