diff --git a/.gitignore b/.gitignore index a9d37c5..2da6cde 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target Cargo.lock +.idea \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 6a2ada7..f74738b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,8 @@ rand = { version = "0.5", features = ["i128_support"] } rustc-serialize = { version = "0.3", optional = true } byteorder = { version = "1.0", features = ["i128"] } crunchy = "0.2.1" +lazy_static = "*" +rustc-hex = "2" [dev-dependencies.bincode] version = "0.6" diff --git a/src/fields/fp.rs b/src/fields/fp.rs index cf26e10..17856fa 100644 --- a/src/fields/fp.rs +++ b/src/fields/fp.rs @@ -240,6 +240,44 @@ field_impl!( 0x9ede7d651eca6ac987d20782e4866389 ); +lazy_static! { + + static ref FQ: U256 = U256::from([ + 0x3c208c16d87cfd47, + 0x97816a916871ca8d, + 0xb85045b68181585d, + 0x30644e72e131a029 + ]); + + static ref FQ_MINUS3_DIV4: Fq = + Fq::new(3.into()).expect("3 is a valid field element and static; qed").neg() * + Fq::new(4.into()).expect("4 is a valid field element and static; qed").inverse() + .expect("4 has inverse in Fq and is static; qed"); + + static ref FQ_MINUS1_DIV2: Fq = + Fq::new(1.into()).expect("1 is a valid field element and static; qed").neg() * + Fq::new(2.into()).expect("2 is a valid field element and static; qed").inverse() + .expect("2 has inverse in Fq and is static; qed"); + +} + +impl Fq { + pub fn sqrt(&self) -> Option { + let a1 = self.pow(*FQ_MINUS3_DIV4); + let a1a = a1 * *self; + let a0 = a1 * (a1a); + + let mut am1 = *FQ; + am1.sub(&1.into(), &*FQ); + + if a0 == Fq::new(am1).unwrap() { + None + } else { + Some(a1a) + } + } +} + #[inline] pub fn const_fq(i: [u64; 4]) -> Fq { Fq(U256::from(i)) diff --git a/src/lib.rs b/src/lib.rs index e680648..ce24d0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ extern crate crunchy; extern crate rand; #[cfg(feature = "rustc-serialize")] extern crate rustc_serialize; +#[macro_use] extern crate lazy_static; pub mod arith; mod fields; @@ -108,6 +109,19 @@ pub enum FieldError { NotMember, } +#[derive(Debug)] +pub enum CurveError { + InvalidEncoding, + NotMember, + Field(FieldError), +} + +impl From for CurveError { + fn from(fe: FieldError) -> Self { + CurveError::Field(fe) + } +} + pub use groups::Error as GroupError; #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -166,6 +180,10 @@ impl Fq { pub fn modulus() -> arith::U256 { fields::Fq::modulus() } + + pub fn sqrt(&self) -> Option { + self.0.sqrt().map(Fq) + } } impl Add for Fq { @@ -328,6 +346,24 @@ impl G1 { pub fn b() -> Fq { Fq(G1Params::coeff_b()) } + + pub fn from_compressed(bytes: &[u8]) -> Result { + if bytes.len() != 33 { return Err(CurveError::InvalidEncoding); } + + let sign = bytes[0]; + let fq = Fq::from_slice(&bytes[1..])?; + let x = fq; + let y_squared = (fq * fq * fq) + Self::b(); + + let mut y = y_squared.sqrt().ok_or(CurveError::NotMember)?; + + if sign == 2 && y.into_u256().get_bit(0).expect("bit 0 always exist; qed") { y = y.neg(); } + else if sign == 3 && !y.into_u256().get_bit(0).expect("bit 0 always exist; qed") { y = y.neg(); } + else if sign != 3 && sign != 2 { + return Err(CurveError::InvalidEncoding); + } + AffineG1::new(x, y).map_err(|_| CurveError::NotMember).map(Into::into) + } } impl Group for G1 { @@ -580,3 +616,24 @@ impl From for G2 { G2(affine.0.to_jacobian()) } } + +#[cfg(test)] +mod tests { + extern crate rustc_hex as hex; + + use super::{G1, Fq}; + + fn hex(s: &'static str) -> Vec { + use self::hex::FromHex; + s.from_hex().unwrap() + } + + #[test] + fn g1_from_compressed() { + let g1 = G1::from_compressed(&hex("0230644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46")) + .expect("Invalid g1 decompress result"); + assert_eq!(g1.x(), Fq::from_str("21888242871839275222246405745257275088696311157297823662689037894645226208582").unwrap()); + assert_eq!(g1.y(), Fq::from_str("3969792565221544645472939191694882283483352126195956956354061729942568608776").unwrap()); + assert_eq!(g1.z(), Fq::one()); + } +}