Constant-time field square root

WARNING: THIS IS NOT FULLY CONSTANT TIME YET!

This will be fixed once we migrate to the jubjub and bls12_381 crates.
This commit is contained in:
Jack Grigg
2019-05-15 10:35:14 +01:00
parent 40749da9a7
commit 3d2acf48ce
14 changed files with 223 additions and 345 deletions

View File

@@ -97,7 +97,7 @@ macro_rules! curve_impl {
///
/// If and only if `greatest` is set will the lexicographically
/// largest y-coordinate be selected.
fn get_point_from_x(x: $basefield, greatest: bool) -> Option<$affine> {
fn get_point_from_x(x: $basefield, greatest: bool) -> CtOption<$affine> {
// Compute x^3 + b
let mut x3b = x.square();
x3b.mul_assign(&x);
@@ -199,8 +199,9 @@ macro_rules! curve_impl {
let x = $basefield::random(rng);
let greatest = rng.next_u32() % 2 != 0;
if let Some(p) = $affine::get_point_from_x(x, greatest) {
let p = p.scale_by_cofactor();
let p = $affine::get_point_from_x(x, greatest);
if p.is_some().into() {
let p = p.unwrap().scale_by_cofactor();
if !p.is_zero() {
return p;
@@ -603,6 +604,7 @@ pub mod g1 {
use rand_core::RngCore;
use std::fmt;
use std::ops::{AddAssign, MulAssign, Neg, SubAssign};
use subtle::CtOption;
curve_impl!(
"G1",
@@ -807,7 +809,12 @@ pub mod g1 {
let x = Fq::from_repr(x)
.map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate", e))?;
G1Affine::get_point_from_x(x, greatest).ok_or(GroupDecodingError::NotOnCurve)
let ret = G1Affine::get_point_from_x(x, greatest);
if ret.is_some().into() {
Ok(ret.unwrap())
} else {
Err(GroupDecodingError::NotOnCurve)
}
}
}
fn from_affine(affine: G1Affine) -> Self {
@@ -919,7 +926,9 @@ pub mod g1 {
rhs.mul_assign(&x);
rhs.add_assign(&G1Affine::get_coeff_b());
if let Some(y) = rhs.sqrt() {
let y = rhs.sqrt();
if y.is_some().into() {
let y = y.unwrap();
let yrepr = y.into_repr();
let negy = y.neg();
let negyrepr = negy.into_repr();
@@ -1270,6 +1279,7 @@ pub mod g2 {
use rand_core::RngCore;
use std::fmt;
use std::ops::{AddAssign, MulAssign, Neg, SubAssign};
use subtle::CtOption;
curve_impl!(
"G2",
@@ -1498,7 +1508,12 @@ pub mod g2 {
})?,
};
G2Affine::get_point_from_x(x, greatest).ok_or(GroupDecodingError::NotOnCurve)
let ret = G2Affine::get_point_from_x(x, greatest);
if ret.is_some().into() {
Ok(ret.unwrap())
} else {
Err(GroupDecodingError::NotOnCurve)
}
}
}
fn from_affine(affine: G2Affine) -> Self {
@@ -1623,7 +1638,9 @@ pub mod g2 {
rhs.mul_assign(&x);
rhs.add_assign(&G2Affine::get_coeff_b());
if let Some(y) = rhs.sqrt() {
let y = rhs.sqrt();
if y.is_some().into() {
let y = y.unwrap();
let negy = y.neg();
let p = G2Affine {

View File

@@ -2074,8 +2074,9 @@ fn test_fq_sqrt() {
// Ensure sqrt(a)^2 = a for random a
let a = Fq::random(&mut rng);
if let Some(tmp) = a.sqrt() {
assert_eq!(a, tmp.square());
let tmp = a.sqrt();
if tmp.is_some().into() {
assert_eq!(a, tmp.unwrap().square());
}
}
}
@@ -2205,7 +2206,7 @@ fn test_fq_root_of_unity() {
Fq::root_of_unity()
);
assert_eq!(Fq::root_of_unity().pow([1 << Fq::S]), Fq::one());
assert!(Fq::multiplicative_generator().sqrt().is_none());
assert!(bool::from(Fq::multiplicative_generator().sqrt().is_none()));
}
#[test]
@@ -2231,40 +2232,3 @@ fn test_fq_ordering() {
fn fq_repr_tests() {
crate::tests::repr::random_repr_tests::<Fq>();
}
#[test]
fn test_fq_legendre() {
use ff::LegendreSymbol::*;
use ff::SqrtField;
assert_eq!(QuadraticResidue, Fq::one().legendre());
assert_eq!(Zero, Fq::zero().legendre());
assert_eq!(
QuadraticNonResidue,
Fq::from_repr(FqRepr::from(2)).unwrap().legendre()
);
assert_eq!(
QuadraticResidue,
Fq::from_repr(FqRepr::from(4)).unwrap().legendre()
);
let e = FqRepr([
0x52a112f249778642,
0xd0bedb989b7991f,
0xdad3b6681aa63c05,
0xf2efc0bb4721b283,
0x6057a98f18c24733,
0x1022c2fd122889e4,
]);
assert_eq!(QuadraticNonResidue, Fq::from_repr(e).unwrap().legendre());
let e = FqRepr([
0x6dae594e53a96c74,
0x19b16ca9ba64b37b,
0x5c764661a59bfc68,
0xaa346e9b31c60a,
0x346059f9d87a9fa9,
0x1d61ac6bfd5c88b,
]);
assert_eq!(QuadraticResidue, Fq::from_repr(e).unwrap().legendre());
}

View File

@@ -244,15 +244,13 @@ impl Field for Fq2 {
}
impl SqrtField for Fq2 {
fn legendre(&self) -> ::ff::LegendreSymbol {
self.norm().legendre()
}
fn sqrt(&self) -> Option<Self> {
/// WARNING: THIS IS NOT ACTUALLY CONSTANT TIME YET!
/// THIS WILL BE REPLACED BY THE bls12_381 CRATE, WHICH IS CONSTANT TIME!
fn sqrt(&self) -> CtOption<Self> {
// Algorithm 9, https://eprint.iacr.org/2012/685.pdf
if self.is_zero() {
Some(Self::zero())
CtOption::new(Self::zero(), Choice::from(1))
} else {
// a1 = self^((q - 3) / 4)
let mut a1 = self.pow([
@@ -275,7 +273,7 @@ impl SqrtField for Fq2 {
};
if a0 == neg1 {
None
CtOption::new(Self::zero(), Choice::from(0))
} else {
a1.mul_assign(self);
@@ -298,7 +296,7 @@ impl SqrtField for Fq2 {
a1.mul_assign(&alpha);
}
Some(a1)
CtOption::new(a1, Choice::from(1))
}
}
}
@@ -993,18 +991,6 @@ fn test_fq2_sqrt() {
);
}
#[test]
fn test_fq2_legendre() {
use ff::LegendreSymbol::*;
assert_eq!(Zero, Fq2::zero().legendre());
// i^2 = -1
let mut m1 = Fq2::one().neg();
assert_eq!(QuadraticResidue, m1.legendre());
m1.mul_by_nonresidue();
assert_eq!(QuadraticNonResidue, m1.legendre());
}
#[cfg(test)]
use rand_core::SeedableRng;
#[cfg(test)]

View File

@@ -278,30 +278,6 @@ fn test_fr_repr_sub_noborrow() {
);
}
#[test]
fn test_fr_legendre() {
use ff::LegendreSymbol::*;
use ff::SqrtField;
assert_eq!(QuadraticResidue, Fr::one().legendre());
assert_eq!(Zero, Fr::zero().legendre());
let e = FrRepr([
0x0dbc5349cd5664da,
0x8ac5b6296e3ae29d,
0x127cb819feceaa3b,
0x3a6b21fb03867191,
]);
assert_eq!(QuadraticResidue, Fr::from_repr(e).unwrap().legendre());
let e = FrRepr([
0x96341aefd047c045,
0x9b5f4254500a4d65,
0x1ee08223b68ac240,
0x31d9cd545c0ec7c6,
]);
assert_eq!(QuadraticNonResidue, Fr::from_repr(e).unwrap().legendre());
}
#[test]
fn test_fr_repr_add_nocarry() {
let mut rng = XorShiftRng::from_seed([
@@ -833,8 +809,9 @@ fn test_fr_sqrt() {
// Ensure sqrt(a)^2 = a for random a
let a = Fr::random(&mut rng);
if let Some(tmp) = a.sqrt() {
assert_eq!(a, tmp.square());
let tmp = a.sqrt();
if tmp.is_some().into() {
assert_eq!(a, tmp.unwrap().square());
}
}
}
@@ -996,7 +973,7 @@ fn test_fr_root_of_unity() {
Fr::root_of_unity()
);
assert_eq!(Fr::root_of_unity().pow([1 << Fr::S]), Fr::one());
assert!(Fr::multiplicative_generator().sqrt().is_none());
assert!(bool::from(Fr::multiplicative_generator().sqrt().is_none()));
}
#[test]

View File

@@ -193,7 +193,10 @@ fn test_g1_uncompressed_invalid_vectors() {
x3b.mul_assign(&x);
x3b.add_assign(&Fq::from_repr(FqRepr::from(4)).unwrap()); // TODO: perhaps expose coeff_b through API?
if let Some(y) = x3b.sqrt() {
let y = x3b.sqrt();
if y.is_some().into() {
let y = y.unwrap();
// We know this is on the curve, but it's likely not going to be in the correct subgroup.
x.into_repr().write_be(&mut o.as_mut()[0..]).unwrap();
y.into_repr().write_be(&mut o.as_mut()[48..]).unwrap();
@@ -332,7 +335,10 @@ fn test_g2_uncompressed_invalid_vectors() {
c1: Fq::from_repr(FqRepr::from(4)).unwrap(),
}); // TODO: perhaps expose coeff_b through API?
if let Some(y) = x3b.sqrt() {
let y = x3b.sqrt();
if y.is_some().into() {
let y = y.unwrap();
// We know this is on the curve, but it's likely not going to be in the correct subgroup.
x.c1.into_repr().write_be(&mut o.as_mut()[0..]).unwrap();
x.c0.into_repr().write_be(&mut o.as_mut()[48..]).unwrap();
@@ -424,7 +430,7 @@ fn test_g1_compressed_invalid_vectors() {
x3b.mul_assign(&x);
x3b.add_assign(&Fq::from_repr(FqRepr::from(4)).unwrap()); // TODO: perhaps expose coeff_b through API?
if let Some(_) = x3b.sqrt() {
if x3b.sqrt().is_some().into() {
x.add_assign(&Fq::one());
} else {
x.into_repr().write_be(&mut o.as_mut()[0..]).unwrap();
@@ -448,7 +454,7 @@ fn test_g1_compressed_invalid_vectors() {
x3b.mul_assign(&x);
x3b.add_assign(&Fq::from_repr(FqRepr::from(4)).unwrap()); // TODO: perhaps expose coeff_b through API?
if let Some(_) = x3b.sqrt() {
if x3b.sqrt().is_some().into() {
// We know this is on the curve, but it's likely not going to be in the correct subgroup.
x.into_repr().write_be(&mut o.as_mut()[0..]).unwrap();
o.as_mut()[0] |= 0b1000_0000;
@@ -556,7 +562,7 @@ fn test_g2_compressed_invalid_vectors() {
c1: Fq::from_repr(FqRepr::from(4)).unwrap(),
}); // TODO: perhaps expose coeff_b through API?
if let Some(_) = x3b.sqrt() {
if x3b.sqrt().is_some().into() {
x.add_assign(&Fq2::one());
} else {
x.c1.into_repr().write_be(&mut o.as_mut()[0..]).unwrap();
@@ -587,7 +593,7 @@ fn test_g2_compressed_invalid_vectors() {
c1: Fq::from_repr(FqRepr::from(4)).unwrap(),
}); // TODO: perhaps expose coeff_b through API?
if let Some(_) = x3b.sqrt() {
if x3b.sqrt().is_some().into() {
// We know this is on the curve, but it's likely not going to be in the correct subgroup.
x.c1.into_repr().write_be(&mut o.as_mut()[0..]).unwrap();
x.c0.into_repr().write_be(&mut o.as_mut()[48..]).unwrap();

View File

@@ -1,4 +1,4 @@
use ff::{Field, LegendreSymbol, PrimeField, SqrtField};
use ff::{Field, PrimeField, SqrtField};
use rand_core::{RngCore, SeedableRng};
use rand_xorshift::XorShiftRng;
@@ -32,7 +32,6 @@ pub fn random_sqrt_tests<F: SqrtField>() {
for _ in 0..10000 {
let a = F::random(&mut rng);
let b = a.square();
assert_eq!(b.legendre(), LegendreSymbol::QuadraticResidue);
let b = b.sqrt().unwrap();
let negb = b.neg();
@@ -43,7 +42,6 @@ pub fn random_sqrt_tests<F: SqrtField>() {
let mut c = F::one();
for _ in 0..10000 {
let mut b = c.square();
assert_eq!(b.legendre(), LegendreSymbol::QuadraticResidue);
b = b.sqrt().unwrap();