From e13ee2c8ffca208c8efb099c927650e9aa55eac2 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Mon, 4 Jan 2021 23:39:52 +0000 Subject: [PATCH 1/8] Add sqrt_ratio implementation. Co-authored-by: Jack Grigg Signed-off-by: Daira Hopwood --- Cargo.toml | 2 + src/arithmetic/fields.rs | 182 +++++++++++++++++++++++++++++++++++++++ src/pasta/fields/fp.rs | 59 ++++++++++++- src/pasta/fields/fq.rs | 59 ++++++++++++- 4 files changed, 300 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 792b2f1..109666e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,8 @@ metrics-macros = "=0.1.0-alpha.9" num_cpus = "1.13" rand = "0.7" blake2b_simd = "0.5" +lazy_static = "1.4.0" +static_assertions = "1.1.0" [features] sanity-checks = [] diff --git a/src/arithmetic/fields.rs b/src/arithmetic/fields.rs index 196e3ba..1d2a0b3 100644 --- a/src/arithmetic/fields.rs +++ b/src/arithmetic/fields.rs @@ -1,10 +1,16 @@ //! This module contains the `Field` abstraction that allows us to write //! code that generalizes over a pair of fields. +use core::mem::size_of; +use static_assertions::const_assert; +use std::assert; +use std::convert::TryInto; use subtle::{Choice, ConstantTimeEq, CtOption}; use super::Group; +const_assert!(size_of::() >= 4); + /// This trait is a common interface for dealing with elements of a finite /// field. pub trait FieldExt: @@ -16,6 +22,9 @@ pub trait FieldExt: /// Inverse of `ROOT_OF_UNITY` const ROOT_OF_UNITY_INV: Self; + /// The value $(T-1)/2$ such that $2^S \cdot T = p - 1$ with $T$ odd. + const T_MINUS1_OVER2: [u64; 4]; + /// Generator of the $t-order$ multiplicative subgroup const DELTA: Self; @@ -32,6 +41,15 @@ pub trait FieldExt: /// Element of multiplicative order $3$. const ZETA: Self; + /// XOR parameter of the perfect hash function used for SqrtTables. + const HASH_XOR: u32; + + /// Modulus of the perfect hash function used for SqrtTables. + const HASH_MOD: usize; + + /// Tables for square root computation. + fn get_tables() -> &'static SqrtTables; + /// This computes a random element of the field using system randomness. fn rand() -> Self { Self::random(rand::rngs::OsRng) @@ -58,6 +76,119 @@ pub trait FieldExt: /// byte representation of an integer. fn from_bytes_wide(bytes: &[u8; 64]) -> Self; + /// Computes: + /// + /// * (true, sqrt(num/div)), if num and div are nonzero and num/div is a square in the field; + /// * (true, 0), if num is zero; + /// * (false, 0), if num is nonzero and div is zero; + /// * (false, sqrt(ROOT_OF_UNITY * num/div)), if num and div are nonzero and num/div is a nonsquare in the field; + /// + /// where ROOT_OF_UNITY is a generator of the order 2^n subgroup (and therefore a nonsquare). + /// + /// The choice of root from sqrt is unspecified. + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { + // Based on: + // * [Sarkar2020](https://eprint.iacr.org/2020/1407) + // * [BDLSY2012](https://cr.yp.to/papers.html#ed25519) + // + // We need to calculate uv and v, where v = u^((m-1)/2), u = num/div, and p-1 = T * 2^S. + // We can rewrite as follows: + // + // v = (num/div)^((T-1)/2) + // = num^((T-1)/2) * div^(p-1 - (T-1)/2) [Fermat's Little Theorem] + // = " * div^(T * 2^S - (T-1)/2) + // = " * div^((2^(S+1) - 1)*(T-1)/2 + 2^S) + // = (num * div^(2^(S+1) - 1))^((T-1)/2) * div^(2^S) + // + // Let w = (num * div^(2^(S+1) - 1))^((T-1)/2) * div^(2^S - 1). + // Then v = w * div, and uv = num * v / div = num * w. + // + // We calculate: + // + // s = div^(2^S - 1) using an addition chain + // t = div^(2^(S+1) - 1) = s^2 * div + // w = (num * t)^((T-1)/2) * s using another addition chain + // + // then u and uv as above. The addition chains are given in + // https://github.com/zcash/pasta/blob/master/addchain_sqrt.py . + // The overall cost of this part is similar to a single full-width exponentiation, + // regardless of S. + + let sqr = |x: Self, i: u32| (0..i).fold(x, |x, _| x.square()); + + // s = div^(2^S - 1) + let s = (0..5).fold(*div, |d: Self, i| sqr(d, 1 << i) * d); + + // t == div^(2^(S+1) - 1) + let t = s.square() * div; + + // TODO: replace this with an addition chain. + let w = ff::Field::pow_vartime(&(t * num), &Self::T_MINUS1_OVER2) * s; + + // v == u^((T-1)/2) + let v = w * div; + + // uv = u * v + let uv = w * num; + + Self::sqrt_common(num, div, &uv, &v) + } + + /// Same as sqrt_ratio but given num, div, v = u^((T-1)/2), and uv = u * v as input. + /// + /// The choice of root from sqrt is unspecified. + fn sqrt_common(num: &Self, div: &Self, uv: &Self, v: &Self) -> (Choice, Self) { + let tab = Self::get_tables(); + let sqr = |x: Self, i: u32| (0..i).fold(x, |x, _| x.square()); + + let x3 = *uv * v; + let x2 = sqr(x3, 8); + let x1 = sqr(x2, 8); + let x0 = sqr(x1, 8); + + // i = 0, 1 + let mut t_: usize = tab.inv[x0.hash()] as usize; // = t >> 16 + // 1 == x0 * ROOT_OF_UNITY^(t_ << 24) + assert!(t_ < 0x100); + let alpha = x1 * tab.g2[t_]; + + // i = 2 + t_ += (tab.inv[alpha.hash()] as usize) << 8; // = t >> 8 + // 1 == x1 * ROOT_OF_UNITY^(t_ << 16) + assert!(t_ < 0x10000); + let alpha = x2 * tab.g1[t_ & 0xFF] * tab.g2[t_ >> 8]; + + // i = 3 + t_ += (tab.inv[alpha.hash()] as usize) << 16; // = t + // 1 == x2 * ROOT_OF_UNITY^(t_ << 8) + assert!(t_ < 0x1000000); + let alpha = x3 * tab.g0[t_ & 0xFF] * tab.g1[(t_ >> 8) & 0xFF] * tab.g2[t_ >> 16]; + + t_ += (tab.inv[alpha.hash()] as usize) << 24; // = t << 1 + // 1 == x3 * ROOT_OF_UNITY^t_ + t_ = (t_ + 1) >> 1; + assert!(t_ <= 0x80000000); + let res = *uv + * tab.g0[t_ & 0xFF] + * tab.g1[(t_ >> 8) & 0xFF] + * tab.g2[(t_ >> 16) & 0xFF] + * tab.g3[t_ >> 24]; + + let sqdiv = res.square() * div; + let is_square = (sqdiv - num).ct_is_zero(); + let is_nonsquare = (sqdiv - Self::ROOT_OF_UNITY * num).ct_is_zero(); + assert!(bool::from( + num.ct_is_zero() | div.ct_is_zero() | (is_square ^ is_nonsquare) + )); + + (is_square, res) + } + + /// Returns a perfect hash of this element for use with inv. + fn hash(&self) -> usize { + ((self.get_lower_32() ^ Self::HASH_XOR) as usize) % Self::HASH_MOD + } + /// Exponentiates `self` by `by`, where `by` is a little-endian order /// integer exponent. fn pow(&self, by: &[u64; 4]) -> Self { @@ -77,6 +208,10 @@ pub trait FieldExt: /// canonically. fn get_lower_128(&self) -> u128; + /// Gets the lower 32 bits of this field element when expressed + /// canonically. + fn get_lower_32(&self) -> u32; + /// Performs a batch inversion using Montgomery's trick, returns the product /// of every inverse. Zero inputs are ignored. fn batch_invert(v: &mut [Self]) -> Self { @@ -103,6 +238,53 @@ pub trait FieldExt: } } +/// Tables used for square root computation. +#[derive(Debug)] +pub struct SqrtTables { + inv: Vec, + g0: [F; 256], + g1: [F; 256], + g2: [F; 256], + g3: [F; 129], +} + +impl SqrtTables { + /// Build tables given parameters for the perfect hash. + pub fn init() -> Self { + let gtab: Vec> = (0..4) + .scan(F::ROOT_OF_UNITY, |gi, _| { + // gi == ROOT_OF_UNITY^(256^i) + let gtab_i: Vec = (0..256) + .scan(F::one(), |acc, _| { + let res = *acc; + *acc *= *gi; + Some(res) + }) + .collect(); + *gi = gtab_i[255] * *gi; + Some(gtab_i) + }) + .collect(); + + // Now invert gtab[3]. + let mut inv: Vec = vec![1; F::HASH_MOD]; + for j in 0..256 { + let hash = gtab[3][j].hash(); + // 1 is the last value to be assigned, so this ensures there are no collisions. + assert!(inv[hash] == 1); + inv[hash] = ((256 - j) & 0xFF) as u8; + } + + SqrtTables:: { + inv, + g0: gtab[0][..].try_into().unwrap(), + g1: gtab[1][..].try_into().unwrap(), + g2: gtab[2][..].try_into().unwrap(), + g3: gtab[3][0..129].try_into().unwrap(), + } + } +} + /// Compute a + b + carry, returning the result and the new carry over. #[inline(always)] pub(crate) const fn adc(a: u64, b: u64, carry: u64) -> (u64, u64) { diff --git a/src/pasta/fields/fp.rs b/src/pasta/fields/fp.rs index c28182e..3dae877 100644 --- a/src/pasta/fields/fp.rs +++ b/src/pasta/fields/fp.rs @@ -2,10 +2,11 @@ use bitvec::{array::BitArray, order::Lsb0}; use core::convert::TryInto; use core::fmt; use core::ops::{Add, Mul, Neg, Sub}; +use lazy_static::lazy_static; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; -use crate::arithmetic::{adc, mac, sbb, FieldExt, Group}; +use crate::arithmetic::{adc, mac, sbb, FieldExt, Group, SqrtTables}; /// This represents an element of $\mathbb{F}_p$ where /// @@ -643,6 +644,12 @@ impl FieldExt for Fp { 0xb4ed8e647196dad1, 0x2cd5282c53116b5c, ]); + const T_MINUS1_OVER2: [u64; 4] = [ + 0x04a67c8dcc969876, + 0x0000000011234c7e, + 0x0000000000000000, + 0x20000000, + ]; const DELTA: Self = DELTA; const TWO_INV: Self = Fp::from_raw([ 0xcc96987680000001, @@ -664,6 +671,16 @@ impl FieldExt for Fp { 0x12ccca834acdba71, ]); + const HASH_XOR: u32 = 0x11BE; + const HASH_MOD: usize = 1098; + + fn get_tables() -> &'static SqrtTables { + lazy_static! { + static ref FP_TABLES: SqrtTables = SqrtTables::init(); + } + &FP_TABLES + } + fn ct_is_zero(&self) -> Choice { self.ct_eq(&Self::zero()) } @@ -740,6 +757,13 @@ impl FieldExt for Fp { u128::from(tmp.0[0]) | (u128::from(tmp.0[1]) << 64) } + + fn get_lower_32(&self) -> u32 { + // TODO: don't reduce, just hash the Montgomery form. (Requires rebuilding perfect hash table.) + let tmp = Fp::montgomery_reduce(self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0); + + tmp.0[0] as u32 + } } #[cfg(test)] @@ -778,6 +802,39 @@ fn test_sqrt() { assert!(v == Fp::TWO_INV || (-v) == Fp::TWO_INV); } +#[test] +fn test_sqrt_ratio() { + // (true, sqrt(num/div)), if num and div are nonzero and num/div is a square in the field + let num = (Fp::TWO_INV).square(); + let div = Fp::from_u64(25); + let expected = Fp::TWO_INV * Fp::from_u64(5).invert().unwrap(); + let (is_square, v) = Fp::sqrt_ratio(&num, &div); + assert!(bool::from(is_square)); + assert!(v == expected || (-v) == expected); + + // (false, sqrt(ROOT_OF_UNITY * num/div)), if num and div are nonzero and num/div is a nonsquare in the field + let num = num * Fp::ROOT_OF_UNITY; + let expected = Fp::TWO_INV * Fp::ROOT_OF_UNITY * Fp::from_u64(5).invert().unwrap(); + let (is_square, v) = Fp::sqrt_ratio(&num, &div); + assert!(!bool::from(is_square)); + assert!(v == expected || (-v) == expected); + + // (true, 0), if num is zero + let num = Fp::zero(); + let expected = Fp::zero(); + let (is_square, v) = Fp::sqrt_ratio(&num, &div); + assert!(bool::from(is_square)); + assert!(v == expected); + + // (false, 0), if num is nonzero and div is zero + let num = (Fp::TWO_INV).square(); + let div = Fp::zero(); + let expected = Fp::zero(); + let (is_square, v) = Fp::sqrt_ratio(&num, &div); + assert!(!bool::from(is_square)); + assert!(v == expected); +} + #[test] fn test_zeta() { assert_eq!( diff --git a/src/pasta/fields/fq.rs b/src/pasta/fields/fq.rs index 6c3c989..c93ac54 100644 --- a/src/pasta/fields/fq.rs +++ b/src/pasta/fields/fq.rs @@ -2,10 +2,11 @@ use bitvec::{array::BitArray, order::Lsb0}; use core::convert::TryInto; use core::fmt; use core::ops::{Add, Mul, Neg, Sub}; +use lazy_static::lazy_static; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; -use crate::arithmetic::{adc, mac, sbb, FieldExt, Group}; +use crate::arithmetic::{adc, mac, sbb, FieldExt, Group, SqrtTables}; /// This represents an element of $\mathbb{F}_q$ where /// @@ -643,6 +644,12 @@ impl FieldExt for Fq { 0xf4c8f353124086c1, 0x2235e1a7415bf936, ]); + const T_MINUS1_OVER2: [u64; 4] = [ + 0x04ca546ec6237590, + 0x0000000011234c7e, + 0x0000000000000000, + 0x20000000, + ]; const DELTA: Self = DELTA; const TWO_INV: Self = Fq::from_raw([ 0xc623759080000001, @@ -664,6 +671,16 @@ impl FieldExt for Fq { 0x06819a58283e528e, ]); + const HASH_XOR: u32 = 0x116A9E; + const HASH_MOD: usize = 1206; + + fn get_tables() -> &'static SqrtTables { + lazy_static! { + static ref FQ_TABLES: SqrtTables = SqrtTables::init(); + } + &FQ_TABLES + } + fn ct_is_zero(&self) -> Choice { self.ct_eq(&Self::zero()) } @@ -740,6 +757,13 @@ impl FieldExt for Fq { u128::from(tmp.0[0]) | (u128::from(tmp.0[1]) << 64) } + + fn get_lower_32(&self) -> u32 { + // TODO: don't reduce, just hash the Montgomery form. (Requires rebuilding perfect hash table.) + let tmp = Fq::montgomery_reduce(self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0); + + tmp.0[0] as u32 + } } #[cfg(test)] @@ -778,6 +802,39 @@ fn test_sqrt() { assert!(v == Fq::TWO_INV || (-v) == Fq::TWO_INV); } +#[test] +fn test_sqrt_ratio() { + // (true, sqrt(num/div)), if num and div are nonzero and num/div is a square in the field + let num = (Fq::TWO_INV).square(); + let div = Fq::from_u64(25); + let expected = Fq::TWO_INV * Fq::from_u64(5).invert().unwrap(); + let (is_square, v) = Fq::sqrt_ratio(&num, &div); + assert!(bool::from(is_square)); + assert!(v == expected || (-v) == expected); + + // (false, sqrt(ROOT_OF_UNITY * num/div)), if num and div are nonzero and num/div is a nonsquare in the field + let num = num * Fq::ROOT_OF_UNITY; + let expected = Fq::TWO_INV * Fq::ROOT_OF_UNITY * Fq::from_u64(5).invert().unwrap(); + let (is_square, v) = Fq::sqrt_ratio(&num, &div); + assert!(!bool::from(is_square)); + assert!(v == expected || (-v) == expected); + + // (true, 0), if num is zero + let num = Fq::zero(); + let expected = Fq::zero(); + let (is_square, v) = Fq::sqrt_ratio(&num, &div); + assert!(bool::from(is_square)); + assert!(v == expected); + + // (false, 0), if num is nonzero and div is zero + let num = (Fq::TWO_INV).square(); + let div = Fq::zero(); + let expected = Fq::zero(); + let (is_square, v) = Fq::sqrt_ratio(&num, &div); + assert!(!bool::from(is_square)); + assert!(v == expected); +} + #[test] fn test_zeta() { assert_eq!( From 227025b7b39d5853e07594a065e68abdbcc6f24c Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Sun, 10 Jan 2021 00:23:13 +0000 Subject: [PATCH 2/8] Avoid exposing implementation details of the square root implementation. Signed-off-by: Daira Hopwood --- src/arithmetic/fields.rs | 270 +++++++++++++++++++++------------------ src/pasta/fields/fp.rs | 14 +- src/pasta/fields/fq.rs | 14 +- 3 files changed, 158 insertions(+), 140 deletions(-) diff --git a/src/arithmetic/fields.rs b/src/arithmetic/fields.rs index 1d2a0b3..07fcb88 100644 --- a/src/arithmetic/fields.rs +++ b/src/arithmetic/fields.rs @@ -5,6 +5,7 @@ use core::mem::size_of; use static_assertions::const_assert; use std::assert; use std::convert::TryInto; +use std::marker::PhantomData; use subtle::{Choice, ConstantTimeEq, CtOption}; use super::Group; @@ -41,14 +42,17 @@ pub trait FieldExt: /// Element of multiplicative order $3$. const ZETA: Self; - /// XOR parameter of the perfect hash function used for SqrtTables. - const HASH_XOR: u32; - - /// Modulus of the perfect hash function used for SqrtTables. - const HASH_MOD: usize; - - /// Tables for square root computation. - fn get_tables() -> &'static SqrtTables; + /// Computes: + /// + /// * (true, sqrt(num/div)), if num and div are nonzero and num/div is a square in the field; + /// * (true, 0), if num is zero; + /// * (false, 0), if num is nonzero and div is zero; + /// * (false, sqrt(ROOT_OF_UNITY * num/div)), if num and div are nonzero and num/div is a nonsquare in the field; + /// + /// where ROOT_OF_UNITY is a generator of the order 2^n subgroup (and therefore a nonsquare). + /// + /// The choice of root from sqrt is unspecified. + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self); /// This computes a random element of the field using system randomness. fn rand() -> Self { @@ -76,119 +80,6 @@ pub trait FieldExt: /// byte representation of an integer. fn from_bytes_wide(bytes: &[u8; 64]) -> Self; - /// Computes: - /// - /// * (true, sqrt(num/div)), if num and div are nonzero and num/div is a square in the field; - /// * (true, 0), if num is zero; - /// * (false, 0), if num is nonzero and div is zero; - /// * (false, sqrt(ROOT_OF_UNITY * num/div)), if num and div are nonzero and num/div is a nonsquare in the field; - /// - /// where ROOT_OF_UNITY is a generator of the order 2^n subgroup (and therefore a nonsquare). - /// - /// The choice of root from sqrt is unspecified. - fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { - // Based on: - // * [Sarkar2020](https://eprint.iacr.org/2020/1407) - // * [BDLSY2012](https://cr.yp.to/papers.html#ed25519) - // - // We need to calculate uv and v, where v = u^((m-1)/2), u = num/div, and p-1 = T * 2^S. - // We can rewrite as follows: - // - // v = (num/div)^((T-1)/2) - // = num^((T-1)/2) * div^(p-1 - (T-1)/2) [Fermat's Little Theorem] - // = " * div^(T * 2^S - (T-1)/2) - // = " * div^((2^(S+1) - 1)*(T-1)/2 + 2^S) - // = (num * div^(2^(S+1) - 1))^((T-1)/2) * div^(2^S) - // - // Let w = (num * div^(2^(S+1) - 1))^((T-1)/2) * div^(2^S - 1). - // Then v = w * div, and uv = num * v / div = num * w. - // - // We calculate: - // - // s = div^(2^S - 1) using an addition chain - // t = div^(2^(S+1) - 1) = s^2 * div - // w = (num * t)^((T-1)/2) * s using another addition chain - // - // then u and uv as above. The addition chains are given in - // https://github.com/zcash/pasta/blob/master/addchain_sqrt.py . - // The overall cost of this part is similar to a single full-width exponentiation, - // regardless of S. - - let sqr = |x: Self, i: u32| (0..i).fold(x, |x, _| x.square()); - - // s = div^(2^S - 1) - let s = (0..5).fold(*div, |d: Self, i| sqr(d, 1 << i) * d); - - // t == div^(2^(S+1) - 1) - let t = s.square() * div; - - // TODO: replace this with an addition chain. - let w = ff::Field::pow_vartime(&(t * num), &Self::T_MINUS1_OVER2) * s; - - // v == u^((T-1)/2) - let v = w * div; - - // uv = u * v - let uv = w * num; - - Self::sqrt_common(num, div, &uv, &v) - } - - /// Same as sqrt_ratio but given num, div, v = u^((T-1)/2), and uv = u * v as input. - /// - /// The choice of root from sqrt is unspecified. - fn sqrt_common(num: &Self, div: &Self, uv: &Self, v: &Self) -> (Choice, Self) { - let tab = Self::get_tables(); - let sqr = |x: Self, i: u32| (0..i).fold(x, |x, _| x.square()); - - let x3 = *uv * v; - let x2 = sqr(x3, 8); - let x1 = sqr(x2, 8); - let x0 = sqr(x1, 8); - - // i = 0, 1 - let mut t_: usize = tab.inv[x0.hash()] as usize; // = t >> 16 - // 1 == x0 * ROOT_OF_UNITY^(t_ << 24) - assert!(t_ < 0x100); - let alpha = x1 * tab.g2[t_]; - - // i = 2 - t_ += (tab.inv[alpha.hash()] as usize) << 8; // = t >> 8 - // 1 == x1 * ROOT_OF_UNITY^(t_ << 16) - assert!(t_ < 0x10000); - let alpha = x2 * tab.g1[t_ & 0xFF] * tab.g2[t_ >> 8]; - - // i = 3 - t_ += (tab.inv[alpha.hash()] as usize) << 16; // = t - // 1 == x2 * ROOT_OF_UNITY^(t_ << 8) - assert!(t_ < 0x1000000); - let alpha = x3 * tab.g0[t_ & 0xFF] * tab.g1[(t_ >> 8) & 0xFF] * tab.g2[t_ >> 16]; - - t_ += (tab.inv[alpha.hash()] as usize) << 24; // = t << 1 - // 1 == x3 * ROOT_OF_UNITY^t_ - t_ = (t_ + 1) >> 1; - assert!(t_ <= 0x80000000); - let res = *uv - * tab.g0[t_ & 0xFF] - * tab.g1[(t_ >> 8) & 0xFF] - * tab.g2[(t_ >> 16) & 0xFF] - * tab.g3[t_ >> 24]; - - let sqdiv = res.square() * div; - let is_square = (sqdiv - num).ct_is_zero(); - let is_nonsquare = (sqdiv - Self::ROOT_OF_UNITY * num).ct_is_zero(); - assert!(bool::from( - num.ct_is_zero() | div.ct_is_zero() | (is_square ^ is_nonsquare) - )); - - (is_square, res) - } - - /// Returns a perfect hash of this element for use with inv. - fn hash(&self) -> usize { - ((self.get_lower_32() ^ Self::HASH_XOR) as usize) % Self::HASH_MOD - } - /// Exponentiates `self` by `by`, where `by` is a little-endian order /// integer exponent. fn pow(&self, by: &[u64; 4]) -> Self { @@ -238,9 +129,25 @@ pub trait FieldExt: } } +/// Parameters for a perfect hash function used in square root computation. +#[derive(Debug)] +struct SqrtHasher { + hash_xor: u32, + hash_mod: usize, + marker: PhantomData, +} + +impl SqrtHasher { + /// Returns a perfect hash of x for use with SqrtTables::inv. + fn hash(&self, x: &F) -> usize { + ((x.get_lower_32() ^ self.hash_xor) as usize) % self.hash_mod + } +} + /// Tables used for square root computation. #[derive(Debug)] pub struct SqrtTables { + hasher: SqrtHasher, inv: Vec, g0: [F; 256], g1: [F; 256], @@ -250,7 +157,13 @@ pub struct SqrtTables { impl SqrtTables { /// Build tables given parameters for the perfect hash. - pub fn init() -> Self { + pub fn new(hash_xor: u32, hash_mod: usize) -> Self { + let hasher = SqrtHasher { + hash_xor, + hash_mod, + marker: PhantomData, + }; + let gtab: Vec> = (0..4) .scan(F::ROOT_OF_UNITY, |gi, _| { // gi == ROOT_OF_UNITY^(256^i) @@ -267,15 +180,16 @@ impl SqrtTables { .collect(); // Now invert gtab[3]. - let mut inv: Vec = vec![1; F::HASH_MOD]; + let mut inv: Vec = vec![1; hash_mod]; for j in 0..256 { - let hash = gtab[3][j].hash(); + let hash = hasher.hash(>ab[3][j]); // 1 is the last value to be assigned, so this ensures there are no collisions. assert!(inv[hash] == 1); inv[hash] = ((256 - j) & 0xFF) as u8; } SqrtTables:: { + hasher, inv, g0: gtab[0][..].try_into().unwrap(), g1: gtab[1][..].try_into().unwrap(), @@ -283,6 +197,114 @@ impl SqrtTables { g3: gtab[3][0..129].try_into().unwrap(), } } + + /// Computes: + /// + /// * (true, sqrt(num/div)), if num and div are nonzero and num/div is a square in the field; + /// * (true, 0), if num is zero; + /// * (false, 0), if num is nonzero and div is zero; + /// * (false, sqrt(ROOT_OF_UNITY * num/div)), if num and div are nonzero and num/div is a nonsquare in the field; + /// + /// where ROOT_OF_UNITY is a generator of the order 2^n subgroup (and therefore a nonsquare). + /// + /// The choice of root from sqrt is unspecified. + pub fn sqrt_ratio(&self, num: &F, div: &F) -> (Choice, F) { + // Based on: + // * [Sarkar2020](https://eprint.iacr.org/2020/1407) + // * [BDLSY2012](https://cr.yp.to/papers.html#ed25519) + // + // We need to calculate uv and v, where v = u^((m-1)/2), u = num/div, and p-1 = T * 2^S. + // We can rewrite as follows: + // + // v = (num/div)^((T-1)/2) + // = num^((T-1)/2) * div^(p-1 - (T-1)/2) [Fermat's Little Theorem] + // = " * div^(T * 2^S - (T-1)/2) + // = " * div^((2^(S+1) - 1)*(T-1)/2 + 2^S) + // = (num * div^(2^(S+1) - 1))^((T-1)/2) * div^(2^S) + // + // Let w = (num * div^(2^(S+1) - 1))^((T-1)/2) * div^(2^S - 1). + // Then v = w * div, and uv = num * v / div = num * w. + // + // We calculate: + // + // s = div^(2^S - 1) using an addition chain + // t = div^(2^(S+1) - 1) = s^2 * div + // w = (num * t)^((T-1)/2) * s using another addition chain + // + // then u and uv as above. The addition chains are given in + // https://github.com/zcash/pasta/blob/master/addchain_sqrt.py . + // The overall cost of this part is similar to a single full-width exponentiation, + // regardless of S. + + let sqr = |x: F, i: u32| (0..i).fold(x, |x, _| x.square()); + + // s = div^(2^S - 1) + let s = (0..5).fold(*div, |d: F, i| sqr(d, 1 << i) * d); + + // t == div^(2^(S+1) - 1) + let t = s.square() * div; + + // TODO: replace this with an addition chain. + let w = ff::Field::pow_vartime(&(t * num), &F::T_MINUS1_OVER2) * s; + + // v == u^((T-1)/2) + let v = w * div; + + // uv = u * v + let uv = w * num; + + self.sqrt_common(num, div, &uv, &v) + } + + /// Same as sqrt_ratio but given num, div, v = u^((T-1)/2), and uv = u * v as input. + /// + /// The choice of root from sqrt is unspecified. + fn sqrt_common(&self, num: &F, div: &F, uv: &F, v: &F) -> (Choice, F) { + let sqr = |x: F, i: u32| (0..i).fold(x, |x, _| x.square()); + let inv = |x: F| self.inv[self.hasher.hash(&x)] as usize; + + let x3 = *uv * v; + let x2 = sqr(x3, 8); + let x1 = sqr(x2, 8); + let x0 = sqr(x1, 8); + + // i = 0, 1 + let mut t_ = inv(x0); // = t >> 16 + // 1 == x0 * ROOT_OF_UNITY^(t_ << 24) + assert!(t_ < 0x100); + let alpha = x1 * self.g2[t_]; + + // i = 2 + t_ += inv(alpha) << 8; // = t >> 8 + // 1 == x1 * ROOT_OF_UNITY^(t_ << 16) + assert!(t_ < 0x10000); + let alpha = x2 * self.g1[t_ & 0xFF] * self.g2[t_ >> 8]; + + // i = 3 + t_ += inv(alpha) << 16; // = t + // 1 == x2 * ROOT_OF_UNITY^(t_ << 8) + assert!(t_ < 0x1000000); + let alpha = x3 * self.g0[t_ & 0xFF] * self.g1[(t_ >> 8) & 0xFF] * self.g2[t_ >> 16]; + + t_ += inv(alpha) << 24; // = t << 1 + // 1 == x3 * ROOT_OF_UNITY^t_ + t_ = (t_ + 1) >> 1; + assert!(t_ <= 0x80000000); + let res = *uv + * self.g0[t_ & 0xFF] + * self.g1[(t_ >> 8) & 0xFF] + * self.g2[(t_ >> 16) & 0xFF] + * self.g3[t_ >> 24]; + + let sqdiv = res.square() * div; + let is_square = (sqdiv - num).ct_is_zero(); + let is_nonsquare = (sqdiv - F::ROOT_OF_UNITY * num).ct_is_zero(); + assert!(bool::from( + num.ct_is_zero() | div.ct_is_zero() | (is_square ^ is_nonsquare) + )); + + (is_square, res) + } } /// Compute a + b + carry, returning the result and the new carry over. diff --git a/src/pasta/fields/fp.rs b/src/pasta/fields/fp.rs index 3dae877..0857de1 100644 --- a/src/pasta/fields/fp.rs +++ b/src/pasta/fields/fp.rs @@ -636,6 +636,10 @@ impl ff::PrimeField for Fp { } } +lazy_static! { + static ref FP_TABLES: SqrtTables = SqrtTables::new(0x11BE, 1098); +} + impl FieldExt for Fp { const ROOT_OF_UNITY: Self = ROOT_OF_UNITY; const ROOT_OF_UNITY_INV: Self = Fp::from_raw([ @@ -671,14 +675,8 @@ impl FieldExt for Fp { 0x12ccca834acdba71, ]); - const HASH_XOR: u32 = 0x11BE; - const HASH_MOD: usize = 1098; - - fn get_tables() -> &'static SqrtTables { - lazy_static! { - static ref FP_TABLES: SqrtTables = SqrtTables::init(); - } - &FP_TABLES + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { + FP_TABLES.sqrt_ratio(num, div) } fn ct_is_zero(&self) -> Choice { diff --git a/src/pasta/fields/fq.rs b/src/pasta/fields/fq.rs index c93ac54..9d80a81 100644 --- a/src/pasta/fields/fq.rs +++ b/src/pasta/fields/fq.rs @@ -636,6 +636,10 @@ impl ff::PrimeField for Fq { } } +lazy_static! { + static ref FQ_TABLES: SqrtTables = SqrtTables::new(0x116A9E, 1206); +} + impl FieldExt for Fq { const ROOT_OF_UNITY: Self = ROOT_OF_UNITY; const ROOT_OF_UNITY_INV: Self = Fq::from_raw([ @@ -671,14 +675,8 @@ impl FieldExt for Fq { 0x06819a58283e528e, ]); - const HASH_XOR: u32 = 0x116A9E; - const HASH_MOD: usize = 1206; - - fn get_tables() -> &'static SqrtTables { - lazy_static! { - static ref FQ_TABLES: SqrtTables = SqrtTables::init(); - } - &FQ_TABLES + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { + FQ_TABLES.sqrt_ratio(num, div) } fn ct_is_zero(&self) -> Choice { From 806748fbc4029007763a2f75eea99279bbffc4d7 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Sun, 10 Jan 2021 15:22:24 +0000 Subject: [PATCH 3/8] Use addition chains for powering by (T-1)/2. Signed-off-by: Daira Hopwood --- src/arithmetic/fields.rs | 10 ++++++++-- src/pasta/fields/fp.rs | 33 +++++++++++++++++++++++++++++++++ src/pasta/fields/fq.rs | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/arithmetic/fields.rs b/src/arithmetic/fields.rs index 07fcb88..4ad8fd3 100644 --- a/src/arithmetic/fields.rs +++ b/src/arithmetic/fields.rs @@ -103,6 +103,12 @@ pub trait FieldExt: /// canonically. fn get_lower_32(&self) -> u32; + /// Raise this field element to the power T_MINUS1_OVER2. + /// Field implementations may override this to use an efficient addition chain. + fn pow_by_t_minus1_over2(&self) -> Self { + ff::Field::pow_vartime(&self, &Self::T_MINUS1_OVER2) + } + /// Performs a batch inversion using Montgomery's trick, returns the product /// of every inverse. Zero inputs are ignored. fn batch_invert(v: &mut [Self]) -> Self { @@ -244,8 +250,8 @@ impl SqrtTables { // t == div^(2^(S+1) - 1) let t = s.square() * div; - // TODO: replace this with an addition chain. - let w = ff::Field::pow_vartime(&(t * num), &F::T_MINUS1_OVER2) * s; + // w = (num * t)^((T-1)/2) * s + let w = (t * num).pow_by_t_minus1_over2() * s; // v == u^((T-1)/2) let v = w * div; diff --git a/src/pasta/fields/fp.rs b/src/pasta/fields/fp.rs index 0857de1..2f038f3 100644 --- a/src/pasta/fields/fp.rs +++ b/src/pasta/fields/fp.rs @@ -762,6 +762,39 @@ impl FieldExt for Fp { tmp.0[0] as u32 } + + fn pow_by_t_minus1_over2(&self) -> Self { + let sqr = |x: Fp, i: u32| (0..i).fold(x, |x, _| x.square()); + + let r10 = self.square(); + let r11 = r10 * self; + let r110 = r11.square(); + let r111 = r110 * self; + let r1001 = r111 * r10; + let r1101 = r111 * r110; + let ra = sqr(*self, 129) * self; + let rb = sqr(ra, 7) * r1001; + let rc = sqr(rb, 7) * r1101; + let rd = sqr(rc, 4) * r11; + let re = sqr(rd, 6) * r111; + let rf = sqr(re, 3) * r111; + let rg = sqr(rf, 10) * r1001; + let rh = sqr(rg, 5) * r1001; + let ri = sqr(rh, 4) * r1001; + let rj = sqr(ri, 3) * r111; + let rk = sqr(rj, 4) * r1001; + let rl = sqr(rk, 5) * r11; + let rm = sqr(rl, 4) * r111; + let rn = sqr(rm, 4) * r11; + let ro = sqr(rn, 6) * r1001; + let rp = sqr(ro, 5) * r1101; + let rq = sqr(rp, 4) * r11; + let rr = sqr(rq, 7) * r111; + let rs = sqr(rr, 3) * r11; + let rt = rs.square(); + //assert!(rt == ff::Field::pow_vartime(&self, &Fp::T_MINUS1_OVER2)); + rt + } } #[cfg(test)] diff --git a/src/pasta/fields/fq.rs b/src/pasta/fields/fq.rs index 9d80a81..df22888 100644 --- a/src/pasta/fields/fq.rs +++ b/src/pasta/fields/fq.rs @@ -762,6 +762,39 @@ impl FieldExt for Fq { tmp.0[0] as u32 } + + fn pow_by_t_minus1_over2(&self) -> Self { + let sqr = |x: Fq, i: u32| (0..i).fold(x, |x, _| x.square()); + + let s10 = self.square(); + let s11 = s10 * self; + let s111 = s11.square() * self; + let s1001 = s111 * s10; + let s1011 = s1001 * s10; + let s1101 = s1011 * s10; + let sa = sqr(*self, 129) * self; + let sb = sqr(sa, 7) * s1001; + let sc = sqr(sb, 7) * s1101; + let sd = sqr(sc, 4) * s11; + let se = sqr(sd, 6) * s111; + let sf = sqr(se, 3) * s111; + let sg = sqr(sf, 10) * s1001; + let sh = sqr(sg, 4) * s1001; + let si = sqr(sh, 5) * s1001; + let sj = sqr(si, 5) * s1001; + let sk = sqr(sj, 3) * s1001; + let sl = sqr(sk, 4) * s1011; + let sm = sqr(sl, 4) * s1011; + let sn = sqr(sm, 5) * s11; + let so = sqr(sn, 4) * self; + let sp = sqr(so, 5) * s11; + let sq = sqr(sp, 4) * s111; + let sr = sqr(sq, 5) * s1011; + let ss = sqr(sr, 3) * self; + let st = sqr(ss, 4); + //assert!(st == ff::Field::pow_vartime(&self, &Fq::T_MINUS1_OVER2)); + st + } } #[cfg(test)] From af9834d68c3ef064977ff4a528e5331b9d8bdd0d Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Sun, 10 Jan 2021 16:51:18 +0000 Subject: [PATCH 4/8] Implement `sqrt_alt`, a more efficient way of doing `sqrt_ratio(num, one())`. Signed-off-by: Daira Hopwood --- src/arithmetic/fields.rs | 52 +++++++++++++++++++++++++++------------- src/pasta/fields/fp.rs | 19 ++++++++++++++- src/pasta/fields/fq.rs | 19 ++++++++++++++- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/src/arithmetic/fields.rs b/src/arithmetic/fields.rs index 4ad8fd3..c46ac81 100644 --- a/src/arithmetic/fields.rs +++ b/src/arithmetic/fields.rs @@ -54,6 +54,11 @@ pub trait FieldExt: /// The choice of root from sqrt is unspecified. fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self); + /// Equivalent to sqrt_ratio(self, one()). + fn sqrt_alt(&self) -> (Choice, Self) { + Self::sqrt_ratio(self, &Self::one()) + } + /// This computes a random element of the field using system randomness. fn rand() -> Self { Self::random(rand::rngs::OsRng) @@ -259,13 +264,35 @@ impl SqrtTables { // uv = u * v let uv = w * num; - self.sqrt_common(num, div, &uv, &v) + let res = self.sqrt_common(&uv, &v); + + let sqdiv = res.square() * div; + let is_square = (sqdiv - num).ct_is_zero(); + let is_nonsquare = (sqdiv - F::ROOT_OF_UNITY * num).ct_is_zero(); + assert!(bool::from( + num.ct_is_zero() | div.ct_is_zero() | (is_square ^ is_nonsquare) + )); + + (is_square, res) } - /// Same as sqrt_ratio but given num, div, v = u^((T-1)/2), and uv = u * v as input. - /// - /// The choice of root from sqrt is unspecified. - fn sqrt_common(&self, num: &F, div: &F, uv: &F, v: &F) -> (Choice, F) { + /// Same as sqrt_ratio(u, one()) but more efficient. + pub fn sqrt_alt(&self, u: &F) -> (Choice, F) { + let v = u.pow_by_t_minus1_over2(); + let uv = *u * v; + + let res = self.sqrt_common(&uv, &v); + + let sq = res.square(); + let is_square = (sq - u).ct_is_zero(); + let is_nonsquare = (sq - F::ROOT_OF_UNITY * u).ct_is_zero(); + assert!(bool::from(u.ct_is_zero() | (is_square ^ is_nonsquare))); + + (is_square, res) + } + + /// Common part of sqrt_ratio and sqrt_alt: return res given v = u^((T-1)/2) and uv = u * v. + fn sqrt_common(&self, uv: &F, v: &F) -> F { let sqr = |x: F, i: u32| (0..i).fold(x, |x, _| x.square()); let inv = |x: F| self.inv[self.hasher.hash(&x)] as usize; @@ -296,20 +323,11 @@ impl SqrtTables { // 1 == x3 * ROOT_OF_UNITY^t_ t_ = (t_ + 1) >> 1; assert!(t_ <= 0x80000000); - let res = *uv - * self.g0[t_ & 0xFF] + + *uv * self.g0[t_ & 0xFF] * self.g1[(t_ >> 8) & 0xFF] * self.g2[(t_ >> 16) & 0xFF] - * self.g3[t_ >> 24]; - - let sqdiv = res.square() * div; - let is_square = (sqdiv - num).ct_is_zero(); - let is_nonsquare = (sqdiv - F::ROOT_OF_UNITY * num).ct_is_zero(); - assert!(bool::from( - num.ct_is_zero() | div.ct_is_zero() | (is_square ^ is_nonsquare) - )); - - (is_square, res) + * self.g3[t_ >> 24] } } diff --git a/src/pasta/fields/fp.rs b/src/pasta/fields/fp.rs index 2f038f3..f03eb3f 100644 --- a/src/pasta/fields/fp.rs +++ b/src/pasta/fields/fp.rs @@ -679,6 +679,10 @@ impl FieldExt for Fp { FP_TABLES.sqrt_ratio(num, div) } + fn sqrt_alt(&self) -> (Choice, Self) { + FP_TABLES.sqrt_alt(self) + } + fn ct_is_zero(&self) -> Choice { self.ct_eq(&Self::zero()) } @@ -834,15 +838,20 @@ fn test_sqrt() { } #[test] -fn test_sqrt_ratio() { +fn test_sqrt_ratio_and_alt() { // (true, sqrt(num/div)), if num and div are nonzero and num/div is a square in the field let num = (Fp::TWO_INV).square(); let div = Fp::from_u64(25); + let div_inverse = div.invert().unwrap(); let expected = Fp::TWO_INV * Fp::from_u64(5).invert().unwrap(); let (is_square, v) = Fp::sqrt_ratio(&num, &div); assert!(bool::from(is_square)); assert!(v == expected || (-v) == expected); + let (is_square_alt, v_alt) = Fp::sqrt_alt(&(num * div_inverse)); + assert!(bool::from(is_square_alt)); + assert!(v_alt == v); + // (false, sqrt(ROOT_OF_UNITY * num/div)), if num and div are nonzero and num/div is a nonsquare in the field let num = num * Fp::ROOT_OF_UNITY; let expected = Fp::TWO_INV * Fp::ROOT_OF_UNITY * Fp::from_u64(5).invert().unwrap(); @@ -850,6 +859,10 @@ fn test_sqrt_ratio() { assert!(!bool::from(is_square)); assert!(v == expected || (-v) == expected); + let (is_square_alt, v_alt) = Fp::sqrt_alt(&(num * div_inverse)); + assert!(!bool::from(is_square_alt)); + assert!(v_alt == v); + // (true, 0), if num is zero let num = Fp::zero(); let expected = Fp::zero(); @@ -857,6 +870,10 @@ fn test_sqrt_ratio() { assert!(bool::from(is_square)); assert!(v == expected); + let (is_square_alt, v_alt) = Fp::sqrt_alt(&(num * div_inverse)); + assert!(bool::from(is_square_alt)); + assert!(v_alt == v); + // (false, 0), if num is nonzero and div is zero let num = (Fp::TWO_INV).square(); let div = Fp::zero(); diff --git a/src/pasta/fields/fq.rs b/src/pasta/fields/fq.rs index df22888..64dd760 100644 --- a/src/pasta/fields/fq.rs +++ b/src/pasta/fields/fq.rs @@ -679,6 +679,10 @@ impl FieldExt for Fq { FQ_TABLES.sqrt_ratio(num, div) } + fn sqrt_alt(&self) -> (Choice, Self) { + FQ_TABLES.sqrt_alt(self) + } + fn ct_is_zero(&self) -> Choice { self.ct_eq(&Self::zero()) } @@ -834,15 +838,20 @@ fn test_sqrt() { } #[test] -fn test_sqrt_ratio() { +fn test_sqrt_ratio_and_alt() { // (true, sqrt(num/div)), if num and div are nonzero and num/div is a square in the field let num = (Fq::TWO_INV).square(); let div = Fq::from_u64(25); + let div_inverse = div.invert().unwrap(); let expected = Fq::TWO_INV * Fq::from_u64(5).invert().unwrap(); let (is_square, v) = Fq::sqrt_ratio(&num, &div); assert!(bool::from(is_square)); assert!(v == expected || (-v) == expected); + let (is_square_alt, v_alt) = Fq::sqrt_alt(&(num * div_inverse)); + assert!(bool::from(is_square_alt)); + assert!(v_alt == v); + // (false, sqrt(ROOT_OF_UNITY * num/div)), if num and div are nonzero and num/div is a nonsquare in the field let num = num * Fq::ROOT_OF_UNITY; let expected = Fq::TWO_INV * Fq::ROOT_OF_UNITY * Fq::from_u64(5).invert().unwrap(); @@ -850,6 +859,10 @@ fn test_sqrt_ratio() { assert!(!bool::from(is_square)); assert!(v == expected || (-v) == expected); + let (is_square_alt, v_alt) = Fq::sqrt_alt(&(num * div_inverse)); + assert!(!bool::from(is_square_alt)); + assert!(v_alt == v); + // (true, 0), if num is zero let num = Fq::zero(); let expected = Fq::zero(); @@ -857,6 +870,10 @@ fn test_sqrt_ratio() { assert!(bool::from(is_square)); assert!(v == expected); + let (is_square_alt, v_alt) = Fq::sqrt_alt(&(num * div_inverse)); + assert!(bool::from(is_square_alt)); + assert!(v_alt == v); + // (false, 0), if num is nonzero and div is zero let num = (Fq::TWO_INV).square(); let div = Fq::zero(); From c5e48fdd06c6379341d15fbc6208a5fd4027d104 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Wed, 13 Jan 2021 00:12:12 +0000 Subject: [PATCH 5/8] Address @ebfull's review comments. Co-authored-by: Sean Bowe Signed-off-by: Daira Hopwood --- src/arithmetic/fields.rs | 16 ++++++++-------- src/pasta/fields/fp.rs | 8 +++++++- src/pasta/fields/fq.rs | 8 +++++++- src/poly/commitment.rs | 4 ++-- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/arithmetic/fields.rs b/src/arithmetic/fields.rs index c46ac81..c8f7916 100644 --- a/src/arithmetic/fields.rs +++ b/src/arithmetic/fields.rs @@ -160,10 +160,10 @@ impl SqrtHasher { pub struct SqrtTables { hasher: SqrtHasher, inv: Vec, - g0: [F; 256], - g1: [F; 256], - g2: [F; 256], - g3: [F; 129], + g0: Box<[F; 256]>, + g1: Box<[F; 256]>, + g2: Box<[F; 256]>, + g3: Box<[F; 129]>, } impl SqrtTables { @@ -202,10 +202,10 @@ impl SqrtTables { SqrtTables:: { hasher, inv, - g0: gtab[0][..].try_into().unwrap(), - g1: gtab[1][..].try_into().unwrap(), - g2: gtab[2][..].try_into().unwrap(), - g3: gtab[3][0..129].try_into().unwrap(), + g0: Box::new(gtab[0][..].try_into().unwrap()), + g1: Box::new(gtab[1][..].try_into().unwrap()), + g2: Box::new(gtab[2][..].try_into().unwrap()), + g3: Box::new(gtab[3][0..129].try_into().unwrap()), } } diff --git a/src/pasta/fields/fp.rs b/src/pasta/fields/fp.rs index f03eb3f..15fa46c 100644 --- a/src/pasta/fields/fp.rs +++ b/src/pasta/fields/fp.rs @@ -796,7 +796,6 @@ impl FieldExt for Fp { let rr = sqr(rq, 7) * r111; let rs = sqr(rr, 3) * r11; let rt = rs.square(); - //assert!(rt == ff::Field::pow_vartime(&self, &Fp::T_MINUS1_OVER2)); rt } } @@ -837,6 +836,13 @@ fn test_sqrt() { assert!(v == Fp::TWO_INV || (-v) == Fp::TWO_INV); } +#[test] +fn test_pow_by_t_minus1_over2() { + // NB: TWO_INV is standing in as a "random" field element + let v = (Fp::TWO_INV).pow_by_t_minus1_over2(); + assert!(v == ff::Field::pow_vartime(&Fp::TWO_INV, &Fp::T_MINUS1_OVER2)); +} + #[test] fn test_sqrt_ratio_and_alt() { // (true, sqrt(num/div)), if num and div are nonzero and num/div is a square in the field diff --git a/src/pasta/fields/fq.rs b/src/pasta/fields/fq.rs index 64dd760..1241561 100644 --- a/src/pasta/fields/fq.rs +++ b/src/pasta/fields/fq.rs @@ -796,7 +796,6 @@ impl FieldExt for Fq { let sr = sqr(sq, 5) * s1011; let ss = sqr(sr, 3) * self; let st = sqr(ss, 4); - //assert!(st == ff::Field::pow_vartime(&self, &Fq::T_MINUS1_OVER2)); st } } @@ -837,6 +836,13 @@ fn test_sqrt() { assert!(v == Fq::TWO_INV || (-v) == Fq::TWO_INV); } +#[test] +fn test_pow_by_t_minus1_over2() { + // NB: TWO_INV is standing in as a "random" field element + let v = (Fq::TWO_INV).pow_by_t_minus1_over2(); + assert!(v == ff::Field::pow_vartime(&Fq::TWO_INV, &Fq::T_MINUS1_OVER2)); +} + #[test] fn test_sqrt_ratio_and_alt() { // (true, sqrt(num/div)), if num and div are nonzero and num/div is a square in the field diff --git a/src/poly/commitment.rs b/src/poly/commitment.rs index a9b6c74..11e9d9e 100644 --- a/src/poly/commitment.rs +++ b/src/poly/commitment.rs @@ -38,8 +38,8 @@ impl Params { // This is usually a limitation on the curve, but we also want 32-bit // architectures to be supported. assert!(k < 32); - // No goofy hardware please. - assert!(core::mem::size_of::() >= 4); + + // In src/arithmetic/fields.rs we ensure that usize is at least 32 bits. let n: u64 = 1 << k; From 288a21ef1eb2a8983aa68d6fb1f2941fe549b4ab Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Wed, 13 Jan 2021 13:56:13 +0000 Subject: [PATCH 6/8] Replace the Tonelli-Shanks sqrt algorithm with the table-based one. Signed-off-by: Daira Hopwood --- src/pasta/fields/fp.rs | 41 ++--------------------------------------- src/pasta/fields/fq.rs | 41 ++--------------------------------------- 2 files changed, 4 insertions(+), 78 deletions(-) diff --git a/src/pasta/fields/fp.rs b/src/pasta/fields/fp.rs index 15fa46c..87b3bd3 100644 --- a/src/pasta/fields/fp.rs +++ b/src/pasta/fields/fp.rs @@ -492,45 +492,8 @@ impl ff::Field for Fp { /// Computes the square root of this element, if it exists. fn sqrt(&self) -> CtOption { - // Tonelli-Shank's algorithm for p mod 16 = 1 - // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) - - // w = self^((t - 1) // 2) - let w = self.pow_vartime(&[0x04a67c8dcc969876, 0x11234c7e, 0x0, 0x20000000]); - - let mut v = S; - let mut x = self * w; - let mut b = x * w; - - // Initialize z as the 2^S root of unity. - let mut z = ROOT_OF_UNITY; - - for max_v in (1..=S).rev() { - let mut k = 1; - let mut tmp = b.square(); - let mut j_less_than_v: Choice = 1.into(); - - for j in 2..max_v { - let tmp_is_one = tmp.ct_eq(&Fp::one()); - let squared = Fp::conditional_select(&tmp, &z, tmp_is_one).square(); - tmp = Fp::conditional_select(&squared, &tmp, tmp_is_one); - let new_z = Fp::conditional_select(&z, &squared, tmp_is_one); - j_less_than_v &= !j.ct_eq(&v); - k = u32::conditional_select(&j, &k, tmp_is_one); - z = Fp::conditional_select(&z, &new_z, j_less_than_v); - } - - let result = x * z; - x = Fp::conditional_select(&result, &x, b.ct_eq(&Fp::one())); - z = z.square(); - b *= z; - v = k; - } - - CtOption::new( - x, - (x * x).ct_eq(self), // Only return Some if it's the square root. - ) + let (is_square, res) = self.sqrt_alt(); + CtOption::new(res, is_square) } /// Computes the multiplicative inverse of this element, diff --git a/src/pasta/fields/fq.rs b/src/pasta/fields/fq.rs index 1241561..7f9cc0a 100644 --- a/src/pasta/fields/fq.rs +++ b/src/pasta/fields/fq.rs @@ -492,45 +492,8 @@ impl ff::Field for Fq { /// Computes the square root of this element, if it exists. fn sqrt(&self) -> CtOption { - // Tonelli-Shank's algorithm for q mod 16 = 1 - // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) - - // w = self^((t - 1) // 2) - let w = self.pow_vartime(&[0x04ca546ec6237590, 0x11234c7e, 0x0, 0x20000000]); - - let mut v = S; - let mut x = self * w; - let mut b = x * w; - - // Initialize z as the 2^S root of unity. - let mut z = ROOT_OF_UNITY; - - for max_v in (1..=S).rev() { - let mut k = 1; - let mut tmp = b.square(); - let mut j_less_than_v: Choice = 1.into(); - - for j in 2..max_v { - let tmp_is_one = tmp.ct_eq(&Fq::one()); - let squared = Fq::conditional_select(&tmp, &z, tmp_is_one).square(); - tmp = Fq::conditional_select(&squared, &tmp, tmp_is_one); - let new_z = Fq::conditional_select(&z, &squared, tmp_is_one); - j_less_than_v &= !j.ct_eq(&v); - k = u32::conditional_select(&j, &k, tmp_is_one); - z = Fq::conditional_select(&z, &new_z, j_less_than_v); - } - - let result = x * z; - x = Fq::conditional_select(&result, &x, b.ct_eq(&Fq::one())); - z = z.square(); - b *= z; - v = k; - } - - CtOption::new( - x, - (x * x).ct_eq(self), // Only return Some if it's the square root. - ) + let (is_square, res) = self.sqrt_alt(); + CtOption::new(res, is_square) } /// Computes the multiplicative inverse of this element, From adc3c9c2ea9236cba2f50df03ad924724a87ac90 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Sun, 17 Jan 2021 01:52:49 +0000 Subject: [PATCH 7/8] Fix incorrect variable name in a comment. Co-authored-by: str4d --- src/arithmetic/fields.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arithmetic/fields.rs b/src/arithmetic/fields.rs index c8f7916..584829a 100644 --- a/src/arithmetic/fields.rs +++ b/src/arithmetic/fields.rs @@ -224,7 +224,7 @@ impl SqrtTables { // * [Sarkar2020](https://eprint.iacr.org/2020/1407) // * [BDLSY2012](https://cr.yp.to/papers.html#ed25519) // - // We need to calculate uv and v, where v = u^((m-1)/2), u = num/div, and p-1 = T * 2^S. + // We need to calculate uv and v, where v = u^((T-1)/2), u = num/div, and p-1 = T * 2^S. // We can rewrite as follows: // // v = (num/div)^((T-1)/2) From c7a12ee178536e58b29d8ac97d8d0f15013c632a Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Sun, 17 Jan 2021 02:24:09 +0000 Subject: [PATCH 8/8] Add documentation of perfect hash parameters. --- src/arithmetic/fields.rs | 4 ++++ src/pasta/fields/fp.rs | 1 + src/pasta/fields/fq.rs | 1 + 3 files changed, 6 insertions(+) diff --git a/src/arithmetic/fields.rs b/src/arithmetic/fields.rs index 584829a..6a35cc0 100644 --- a/src/arithmetic/fields.rs +++ b/src/arithmetic/fields.rs @@ -151,6 +151,10 @@ struct SqrtHasher { impl SqrtHasher { /// Returns a perfect hash of x for use with SqrtTables::inv. fn hash(&self, x: &F) -> usize { + // This is just the simplest constant-time perfect hash construction that could + // possibly work. The 32 low-order bits are unique within the 2^S order subgroup, + // then the xor acts as "salt" to injectively randomize the output when taken modulo + // `hash_mod`. Since the table is small, we do not need anything more complicated. ((x.get_lower_32() ^ self.hash_xor) as usize) % self.hash_mod } } diff --git a/src/pasta/fields/fp.rs b/src/pasta/fields/fp.rs index 87b3bd3..07a6d3d 100644 --- a/src/pasta/fields/fp.rs +++ b/src/pasta/fields/fp.rs @@ -600,6 +600,7 @@ impl ff::PrimeField for Fp { } lazy_static! { + // The perfect hash parameters are found by `squareroottab.sage` in zcash/pasta. static ref FP_TABLES: SqrtTables = SqrtTables::new(0x11BE, 1098); } diff --git a/src/pasta/fields/fq.rs b/src/pasta/fields/fq.rs index 7f9cc0a..3bd0e47 100644 --- a/src/pasta/fields/fq.rs +++ b/src/pasta/fields/fq.rs @@ -600,6 +600,7 @@ impl ff::PrimeField for Fq { } lazy_static! { + // The perfect hash parameters are found by `squareroottab.sage` in zcash/pasta. static ref FQ_TABLES: SqrtTables = SqrtTables::new(0x116A9E, 1206); }