diff --git a/Cargo.toml b/Cargo.toml index 50b86c2..15f4922 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ subtle = { version = "2.3", default-features = false } lazy_static = { version = "1.4.0", optional = true } [features] -default = ["bits", "std"] +default = ["bits", "sqrt-table", "std"] bits = ["ff/bits"] +sqrt-table = ["std"] std = ["group/alloc", "lazy_static", "rand/getrandom"] diff --git a/src/arithmetic/fields.rs b/src/arithmetic/fields.rs index ce98a0f..4110858 100644 --- a/src/arithmetic/fields.rs +++ b/src/arithmetic/fields.rs @@ -4,16 +4,16 @@ use core::mem::size_of; use static_assertions::const_assert; -use subtle::Choice; - -#[cfg(not(feature = "std"))] -use subtle::CtOption; +use subtle::{Choice, ConditionallySelectable, CtOption}; #[cfg(feature = "std")] use super::Group; #[cfg(feature = "std")] -use std::{assert, boxed::Box, convert::TryInto, marker::PhantomData, vec::Vec}; +use std::assert; + +#[cfg(feature = "sqrt-table")] +use std::{boxed::Box, convert::TryInto, marker::PhantomData, vec::Vec}; const_assert!(size_of::() >= 4); @@ -55,7 +55,40 @@ pub trait SqrtRatio: ff::PrimeField { /// implementation of the SSWU hash-to-curve algorithm. /// /// The choice of root from sqrt is unspecified. - fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self); + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { + // General implementation: + // + // a = num * inv0(div) + // = { 0 if div is zero + // { num/div otherwise + // + // b = G_S * a + // = { 0 if div is zero + // { G_S*num/div otherwise + // + // Since G_S is non-square, a and b are either both zero (and both square), or + // only one them is square. We can therefore choose the square root to return + // based on whether a is square, but for the boolean output we need to handle the + // num != 0 && div == 0 case specifically. + + let a = div.invert().unwrap_or_else(Self::zero) * num; + let b = a * Self::root_of_unity(); + let sqrt_a = a.sqrt(); + let sqrt_b = b.sqrt(); + + let num_is_zero = num.is_zero(); + let div_is_zero = div.is_zero(); + let is_square = sqrt_a.is_some(); + let is_nonsquare = sqrt_b.is_some(); + assert!(bool::from( + num_is_zero | div_is_zero | (is_square ^ is_nonsquare) + )); + + ( + is_square & !(!num_is_zero & div_is_zero), + CtOption::conditional_select(&sqrt_b, &sqrt_a, is_square).unwrap(), + ) + } /// Equivalent to `Self::sqrt_ratio(self, one())`. fn sqrt_alt(&self) -> (Choice, Self) { @@ -120,13 +153,13 @@ pub trait FieldExt: SqrtRatio + From + Ord + Group { /// https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) /// /// `tm1d2` should be set to `(t - 1) // 2`, where `t = (modulus - 1) >> F::S`. -#[cfg(not(feature = "std"))] -#[cfg_attr(docsrs, doc(cfg(not(feature = "std"))))] +#[cfg(not(feature = "sqrt-table"))] +#[cfg_attr(docsrs, doc(cfg(not(feature = "sqrt-table"))))] pub(crate) fn sqrt_tonelli_shanks>( f: &F, tm1d2: S, ) -> CtOption { - use subtle::{ConditionallySelectable, ConstantTimeEq}; + use subtle::ConstantTimeEq; // w = self^((t - 1) // 2) let w = f.pow_vartime(tm1d2); @@ -167,8 +200,8 @@ pub(crate) fn sqrt_tonelli_shanks>( } /// Parameters for a perfect hash function used in square root computation. -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[cfg(feature = "sqrt-table")] +#[cfg_attr(docsrs, doc(cfg(feature = "sqrt-table")))] #[derive(Debug)] struct SqrtHasher { hash_xor: u32, @@ -176,7 +209,7 @@ struct SqrtHasher { marker: PhantomData, } -#[cfg(feature = "std")] +#[cfg(feature = "sqrt-table")] impl SqrtHasher { /// Returns a perfect hash of x for use with SqrtTables::inv. fn hash(&self, x: &F) -> usize { @@ -189,8 +222,8 @@ impl SqrtHasher { } /// Tables used for square root computation. -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[cfg(feature = "sqrt-table")] +#[cfg_attr(docsrs, doc(cfg(feature = "sqrt-table")))] #[derive(Debug)] pub struct SqrtTables { hasher: SqrtHasher, @@ -201,7 +234,7 @@ pub struct SqrtTables { g3: Box<[F; 129]>, } -#[cfg(feature = "std")] +#[cfg(feature = "sqrt-table")] impl SqrtTables { /// Build tables given parameters for the perfect hash. pub fn new(hash_xor: u32, hash_mod: usize) -> Self { diff --git a/src/fields/fp.rs b/src/fields/fp.rs index d56dd3f..9b51453 100644 --- a/src/fields/fp.rs +++ b/src/fields/fp.rs @@ -6,7 +6,7 @@ use ff::PrimeField; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; -#[cfg(feature = "std")] +#[cfg(feature = "sqrt-table")] use lazy_static::lazy_static; #[cfg(feature = "bits")] @@ -15,7 +15,10 @@ use ff::{FieldBits, PrimeFieldBits}; use crate::arithmetic::{adc, mac, sbb}; #[cfg(feature = "std")] -use crate::arithmetic::{FieldExt, Group, SqrtRatio, SqrtTables}; +use crate::arithmetic::{FieldExt, Group, SqrtRatio}; + +#[cfg(feature = "sqrt-table")] +use crate::arithmetic::SqrtTables; /// This represents an element of $\mathbb{F}_p$ where /// @@ -516,13 +519,13 @@ impl ff::Field for Fp { /// Computes the square root of this element, if it exists. fn sqrt(&self) -> CtOption { - #[cfg(feature = "std")] + #[cfg(feature = "sqrt-table")] { let (is_square, res) = FP_TABLES.sqrt_alt(self); CtOption::new(res, is_square) } - #[cfg(not(feature = "std"))] + #[cfg(not(feature = "sqrt-table"))] crate::arithmetic::sqrt_tonelli_shanks(self, &T_MINUS1_OVER2) } @@ -666,10 +669,10 @@ impl PrimeFieldBits for Fp { } } -#[cfg(feature = "std")] +#[cfg(feature = "sqrt-table")] lazy_static! { // The perfect hash parameters are found by `squareroottab.sage` in zcash/pasta. - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + #[cfg_attr(docsrs, doc(cfg(feature = "sqrt-table")))] static ref FP_TABLES: SqrtTables = SqrtTables::new(0x11BE, 1098); } @@ -716,10 +719,12 @@ impl SqrtRatio for Fp { tmp.0[0] as u32 } + #[cfg(feature = "sqrt-table")] fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { FP_TABLES.sqrt_ratio(num, div) } + #[cfg(feature = "sqrt-table")] fn sqrt_alt(&self) -> (Choice, Self) { FP_TABLES.sqrt_alt(self) } diff --git a/src/fields/fq.rs b/src/fields/fq.rs index 5b100fe..61b0683 100644 --- a/src/fields/fq.rs +++ b/src/fields/fq.rs @@ -6,7 +6,7 @@ use ff::PrimeField; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; -#[cfg(feature = "std")] +#[cfg(feature = "sqrt-table")] use lazy_static::lazy_static; #[cfg(feature = "bits")] @@ -15,7 +15,10 @@ use ff::{FieldBits, PrimeFieldBits}; use crate::arithmetic::{adc, mac, sbb}; #[cfg(feature = "std")] -use crate::arithmetic::{FieldExt, Group, SqrtRatio, SqrtTables}; +use crate::arithmetic::{FieldExt, Group, SqrtRatio}; + +#[cfg(feature = "sqrt-table")] +use crate::arithmetic::SqrtTables; /// This represents an element of $\mathbb{F}_q$ where /// @@ -516,13 +519,13 @@ impl ff::Field for Fq { /// Computes the square root of this element, if it exists. fn sqrt(&self) -> CtOption { - #[cfg(feature = "std")] + #[cfg(feature = "sqrt-table")] { let (is_square, res) = FQ_TABLES.sqrt_alt(self); CtOption::new(res, is_square) } - #[cfg(not(feature = "std"))] + #[cfg(not(feature = "sqrt-table"))] crate::arithmetic::sqrt_tonelli_shanks(self, &T_MINUS1_OVER2) } @@ -665,10 +668,10 @@ impl PrimeFieldBits for Fq { } } -#[cfg(feature = "std")] +#[cfg(feature = "sqrt-table")] lazy_static! { // The perfect hash parameters are found by `squareroottab.sage` in zcash/pasta. - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + #[cfg_attr(docsrs, doc(cfg(feature = "sqrt-table")))] static ref FQ_TABLES: SqrtTables = SqrtTables::new(0x116A9E, 1206); } @@ -715,10 +718,12 @@ impl SqrtRatio for Fq { tmp.0[0] as u32 } + #[cfg(feature = "sqrt-table")] fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { FQ_TABLES.sqrt_ratio(num, div) } + #[cfg(feature = "sqrt-table")] fn sqrt_alt(&self) -> (Choice, Self) { FQ_TABLES.sqrt_alt(self) }