diff --git a/src/pasta.rs b/src/pasta.rs index a8313e3..924ec13 100644 --- a/src/pasta.rs +++ b/src/pasta.rs @@ -12,7 +12,6 @@ pub mod vesta; pub use curves::*; pub use fields::*; -use hashtocurve::*; #[test] fn test_endo_consistency() { diff --git a/src/pasta/curves.rs b/src/pasta/curves.rs index 8404faf..a362306 100644 --- a/src/pasta/curves.rs +++ b/src/pasta/curves.rs @@ -687,16 +687,7 @@ macro_rules! new_curve_impl { macro_rules! impl_projective_curve_specific { ($name:ident, $name_affine:ident, $iso_affine:ident, $base:ident, special_a0_b5) => { fn hasher(domain_prefix: &str) -> Box Self + 'static> { - use super::hashtocurve::SimplifiedSWUWithDegree3Isogeny; - - let swu: SimplifiedSWUWithDegree3Isogeny<$base, $name_affine, $iso_affine> = - SimplifiedSWUWithDegree3Isogeny::new( - $name::Z, - $name::ISOGENY_CONSTANTS, - $name::MINUS_B_OVER_A, - $name::B_OVER_ZA, - $name::THETA, - ); + use super::hashtocurve; let domain_separation_tag: String = format!( "{}-{}_{}_{}_RO_", @@ -708,12 +699,25 @@ macro_rules! impl_projective_curve_specific { Box::new(move |message| { let mut us = [Field::zero(); 2]; - SimplifiedSWUWithDegree3Isogeny::<$base, $name_affine, $iso_affine>::hash_to_field( - message, - domain_separation_tag.as_bytes(), - &mut us, + hashtocurve::hash_to_field(message, domain_separation_tag.as_bytes(), &mut us); + let q0 = hashtocurve::map_to_curve::<$base, $name_affine, $iso_affine>( + &us[0], + $name::THETA, + $name::Z, + $name::B_OVER_ZA, ); - swu.field_elements_to_curve(&us[0], &us[1]) + let q1 = hashtocurve::map_to_curve::<$base, $name_affine, $iso_affine>( + &us[1], + $name::THETA, + $name::Z, + $name::B_OVER_ZA, + ); + let r = q0 + &q1; + assert!(bool::from(r.is_on_curve())); + hashtocurve::iso_map::<$base, $name_affine, $iso_affine>( + &r, + &$name::ISOGENY_CONSTANTS, + ) }) } diff --git a/src/pasta/hashtocurve.rs b/src/pasta/hashtocurve.rs index 32305b1..bd073ae 100644 --- a/src/pasta/hashtocurve.rs +++ b/src/pasta/hashtocurve.rs @@ -2,260 +2,147 @@ //! with a = 0. use byteorder::{BigEndian, WriteBytesExt}; -use core::fmt::Debug; -use core::marker::PhantomData; use subtle::ConstantTimeEq; -use crate::arithmetic::{Curve, CurveAffine, Field, FieldExt}; +use crate::arithmetic::{Curve, CurveAffine, FieldExt}; -/// Implementation of the "simplified SWU" hashing to short Weierstrass curves -/// with a = 0. Internally uses SHAKE128. -#[derive(Debug)] -pub struct SimplifiedSWUWithDegree3Isogeny< - F: FieldExt, - C: CurveAffine, - I: CurveAffine, -> { - /// `Z` parameter (ξ in [WB2019](https://eprint.iacr.org/2019/403)). - pub z: F, +/// Hashes over a message and writes the output to all of `buf`. +pub fn hash_to_field(message: &[u8], domain_separation_tag: &[u8], buf: &mut [F]) { + use sha3::digest::{ExtendableOutput, Update}; + assert!(domain_separation_tag.len() < 256); - /// Precomputed -b/a for the isogenous curve. - pub minus_b_over_a: F, + // Assume that the field size is 32 bytes and k is 256, where k is defined in + // . + const CHUNKLEN: usize = 64; - /// Precomputed b/Za for the isogenous curve. - pub b_over_za: F, + let outlen = buf.len() * CHUNKLEN; + let mut outlen_enc = vec![]; + outlen_enc.write_u32::(outlen as u32).unwrap(); - /// Precomputed sqrt(Z / ROOT_OF_UNITY). - pub theta: F, + let mut xof = sha3::Shake128::default(); + xof.update(message); + xof.update(outlen_enc); + xof.update([domain_separation_tag.len() as u8]); + xof.update(domain_separation_tag); - /// Constants for the isogeny. - pub isogeny_constants: [F; 13], - - _marker_c: PhantomData, - _marker_i: PhantomData, -} - -impl, I: CurveAffine> - SimplifiedSWUWithDegree3Isogeny -{ - /// Create a SimplifiedSWUWithDegree3Isogeny method for the given parameters. - /// - /// # Panics - /// Panics if z is square. - pub fn new( - z: F, - isogeny_constants: [F; 13], - minus_b_over_a: F, - b_over_za: F, - theta: F, - ) -> Self { - SimplifiedSWUWithDegree3Isogeny { - z: z, - minus_b_over_a, - b_over_za, - theta, - isogeny_constants: isogeny_constants, - _marker_c: PhantomData, - _marker_i: PhantomData, - } - } - - /// The full hash from an input message to a curve point. - /// - /// `domain_prefix` should identify the application protocol, usage - /// within that protocol, and version, e.g. "z.cash:Orchard-V1". - /// Other fields required to conform to [IRTF-CFRG-Hash-to-Curve] - /// will be added automatically. There may be a length limitation on - /// `domain_prefix`. - /// - /// For example, the resulting full domain separation tag for the - /// Pallas curve using `Shake128` and the simplified SWU map might be - /// b"z.cash:Orchard-V1-pallas_XOF:SHAKE128_SSWU_RO_". - pub fn hash_to_curve(&self, domain_prefix: &str) -> Box C::Projective + '_> { - let domain_separation_tag: String = format!( - "{}-{}_{}_{}_RO_", - domain_prefix, - C::CURVE_ID, - "XOF:SHAKE128", - "SSWU" - ); - - Box::new(move |message| { - let mut us = [Field::zero(); 2]; - Self::hash_to_field(message, domain_separation_tag.as_bytes(), &mut us); - self.field_elements_to_curve(&us[0], &us[1]) - }) - } - - /// A non-uniform hash from an input message to a curve point. - /// This is *not* suitable for applications requiring a random oracle. - /// Use `hash_to_curve` instead unless you are really sure that a - /// non-uniform map is sufficient. - /// - /// `domain_prefix` is as described for `hash_to_curve`. - /// - /// For example, the resulting full domain separation tag for the - /// Pallas curve using `Shake128` and the simplified SWU map might be - /// b"z.cash:Orchard-V1-pallas_XOF:SHAKE128_SSWU_NU_". - pub fn encode_to_curve(&self, domain_prefix: &str) -> Box C::Projective + '_> { - let domain_separation_tag: String = format!( - "{}-{}_{}_{}_NU_", - domain_prefix, - C::CURVE_ID, - "XOF:SHAKE128", - "SSWU" - ); - - Box::new(move |message| { - let mut us = [Field::zero(); 1]; - Self::hash_to_field(message, domain_separation_tag.as_bytes(), &mut us); - let r = self.map_to_curve(&us[0]); - self.iso_map(&r) - }) - } - - /// Hashes over a message and writes the output to all of `buf`. - pub fn hash_to_field(message: &[u8], domain_separation_tag: &[u8], buf: &mut [F]) { - use sha3::digest::{ExtendableOutput, Update}; - assert!(domain_separation_tag.len() < 256); - - // Assume that the field size is 32 bytes and k is 256, where k is defined in - // . - const CHUNKLEN: usize = 64; - - let outlen = buf.len() * CHUNKLEN; - let mut outlen_enc = vec![]; - outlen_enc.write_u32::(outlen as u32).unwrap(); - - let mut xof = sha3::Shake128::default(); - xof.update(message); - xof.update(outlen_enc); - xof.update([domain_separation_tag.len() as u8]); - xof.update(domain_separation_tag); - - for (big, buf) in xof - .finalize_boxed(outlen) - .chunks(CHUNKLEN) - .zip(buf.iter_mut()) - { - let mut little = [0u8; CHUNKLEN]; - little.copy_from_slice(big); - little.reverse(); - *buf = F::from_bytes_wide(&little); - } - } - - /// Maps a field element to the isogenous curve. - pub fn map_to_curve(&self, u: &F) -> I::Projective { - // 1. tv1 = inv0(Z^2 * u^4 + Z * u^2) - // 2. x1 = (-B / A) * (1 + tv1) - // 3. If tv1 == 0, set x1 = B / (Z * A) - // 4. gx1 = x1^3 + A * x1 + B - // - // We use the "Avoiding inversions" optimization in [WB2019, section 4.2] - // (not to be confused with section 4.3): - // - // here [WB2019] - // ------- --------------------------------- - // Z ξ - // u t - // Z * u^2 ξ * t^2 (called u, confusingly) - // x1 X_0(t) - // x2 X_1(t) - // gx1 g(X_0(t)) - // gx2 g(X_1(t)) - // - // Using the "here" names: - // x1 = num_x1/div = [B*(Z^2 * u^4 + Z * u^2 + 1)] / [-A*(Z^2 * u^4 + Z * u^2] - // gx1 = num_gx1/div_gx1 = [num_x1^3 + A * num_x1 * div^2 + B * div^3] / div^3 - - let a = I::a(); - let b = I::b(); - let z_u2 = self.z * u.square(); - let ta = z_u2.square() + z_u2; - let num_x1 = b * (ta + F::one()); - let div = -a * ta; - let num2_x1 = num_x1.square(); - let div2 = div.square(); - let div3 = div2 * div; - let ta_is_zero = ta.ct_is_zero(); - let num_gx1 = F::conditional_select( - &((num2_x1 + a * div2) * num_x1 + b * div3), - &self.b_over_za, - ta_is_zero, - ); - let div_gx1 = F::conditional_select(&div3, &F::one(), ta_is_zero); - - // 5. x2 = Z * u^2 * x1 - let num_x2 = z_u2 * num_x1; // same div - - // 6. gx2 = x2^3 + A * x2 + B [optimized out; see below] - // 7. If is_square(gx1), set x = x1 and y = sqrt(gx1) - // 8. Else set x = x2 and y = sqrt(gx2) - let (gx1_square, y1) = F::sqrt_ratio(&num_gx1, &div_gx1); - - // This magic also comes from a generalization of [WB2019, section 4.2]. - // - // The Sarkar square root algorithm with input s gives us a square root of - // h * s for free when s is not square, where h is a fixed nonsquare. - // In our implementation, h = ROOT_OF_UNITY. - // We know that Z / h is a square since both Z and h are - // nonsquares. Precompute theta as a square root of Z / ROOT_OF_UNITY. - // - // We have gx2 = g(Z * u^2 * x1) = Z^3 * u^6 * gx1 - // = (Z * u^3)^2 * (Z/h * h * gx1) - // = (Z * theta * u^3)^2 * (h * gx1) - // - // When gx1 is not square, y1 is a square root of h * gx1, and so Z * theta * u^3 * y1 - // is a square root of gx2. Note that we don't actually need to compute gx2. - - let y2 = self.theta * z_u2 * u * y1; - let num_x = F::conditional_select(&num_x2, &num_x1, gx1_square); - let y = F::conditional_select(&y2, &y1, gx1_square); - - // 9. If sgn0(u) != sgn0(y), set y = -y - let y = F::conditional_select( - &(-y), - &y, - (u.get_lower_32() % 2).ct_eq(&(y.get_lower_32() % 2)), - ); - - I::Projective::new_jacobian(num_x * div, y * div3, div).unwrap() - } - - /// Implements a degree 3 isogeny map. - pub fn iso_map(&self, p: &I::Projective) -> C::Projective { - // The input and output are in Jacobian coordinates, using the method - // in "Avoiding inversions" [WB2019, section 4.3]. - - let iso = self.isogeny_constants; - let (x, y, z) = p.jacobian_coordinates(); - - let z2 = z.square(); - let z3 = z2 * z; - let z4 = z2.square(); - let z6 = z3.square(); - - let num_x = ((iso[0] * x + iso[1] * z2) * x + iso[2] * z4) * x + iso[3] * z6; - let div_x = (z2 * x + iso[4] * z4) * x + iso[5] * z6; - - let num_y = (((iso[6] * x + iso[7] * z2) * x + iso[8] * z4) * x + iso[9] * z6) * y; - let div_y = (((x + iso[10] * z2) * x + iso[11] * z4) * x + iso[12] * z6) * z3; - - let zo = div_x * div_y; - let xo = num_x * div_y * zo; - let yo = num_y * div_x * zo.square(); - - C::Projective::new_jacobian(xo, yo, zo).unwrap() - } - - /// Map two field elements to a curve point. - pub fn field_elements_to_curve(&self, u0: &C::Base, u1: &C::Base) -> C::Projective { - let q0 = self.map_to_curve(u0); - let q1 = self.map_to_curve(u1); - let r: I::Projective = q0 + &q1; - assert!(bool::from(r.is_on_curve())); - // here is where we would scale by the cofactor if we supported nonprime-order curves - self.iso_map(&r) + for (big, buf) in xof + .finalize_boxed(outlen) + .chunks(CHUNKLEN) + .zip(buf.iter_mut()) + { + let mut little = [0u8; CHUNKLEN]; + little.copy_from_slice(big); + little.reverse(); + *buf = F::from_bytes_wide(&little); } } + +/// Implements a degree 3 isogeny map. +pub fn iso_map, I: CurveAffine>( + p: &I::Projective, + iso: &[C::Base; 13], +) -> C::Projective { + // The input and output are in Jacobian coordinates, using the method + // in "Avoiding inversions" [WB2019, section 4.3]. + + let (x, y, z) = p.jacobian_coordinates(); + + let z2 = z.square(); + let z3 = z2 * z; + let z4 = z2.square(); + let z6 = z3.square(); + + let num_x = ((iso[0] * x + iso[1] * z2) * x + iso[2] * z4) * x + iso[3] * z6; + let div_x = (z2 * x + iso[4] * z4) * x + iso[5] * z6; + + let num_y = (((iso[6] * x + iso[7] * z2) * x + iso[8] * z4) * x + iso[9] * z6) * y; + let div_y = (((x + iso[10] * z2) * x + iso[11] * z4) * x + iso[12] * z6) * z3; + + let zo = div_x * div_y; + let xo = num_x * div_y * zo; + let yo = num_y * div_x * zo.square(); + + C::Projective::new_jacobian(xo, yo, zo).unwrap() +} + +pub fn map_to_curve, I: CurveAffine>( + u: &F, + theta: F, + z: F, + b_over_za: F, +) -> I::Projective { + // 1. tv1 = inv0(Z^2 * u^4 + Z * u^2) + // 2. x1 = (-B / A) * (1 + tv1) + // 3. If tv1 == 0, set x1 = B / (Z * A) + // 4. gx1 = x1^3 + A * x1 + B + // + // We use the "Avoiding inversions" optimization in [WB2019, section 4.2] + // (not to be confused with section 4.3): + // + // here [WB2019] + // ------- --------------------------------- + // Z ξ + // u t + // Z * u^2 ξ * t^2 (called u, confusingly) + // x1 X_0(t) + // x2 X_1(t) + // gx1 g(X_0(t)) + // gx2 g(X_1(t)) + // + // Using the "here" names: + // x1 = num_x1/div = [B*(Z^2 * u^4 + Z * u^2 + 1)] / [-A*(Z^2 * u^4 + Z * u^2] + // gx1 = num_gx1/div_gx1 = [num_x1^3 + A * num_x1 * div^2 + B * div^3] / div^3 + + let a = I::a(); + let b = I::b(); + let z_u2 = z * u.square(); + let ta = z_u2.square() + z_u2; + let num_x1 = b * (ta + F::one()); + let div = -a * ta; + let num2_x1 = num_x1.square(); + let div2 = div.square(); + let div3 = div2 * div; + let ta_is_zero = ta.ct_is_zero(); + let num_gx1 = F::conditional_select( + &((num2_x1 + a * div2) * num_x1 + b * div3), + &b_over_za, + ta_is_zero, + ); + let div_gx1 = F::conditional_select(&div3, &F::one(), ta_is_zero); + + // 5. x2 = Z * u^2 * x1 + let num_x2 = z_u2 * num_x1; // same div + + // 6. gx2 = x2^3 + A * x2 + B [optimized out; see below] + // 7. If is_square(gx1), set x = x1 and y = sqrt(gx1) + // 8. Else set x = x2 and y = sqrt(gx2) + let (gx1_square, y1) = F::sqrt_ratio(&num_gx1, &div_gx1); + + // This magic also comes from a generalization of [WB2019, section 4.2]. + // + // The Sarkar square root algorithm with input s gives us a square root of + // h * s for free when s is not square, where h is a fixed nonsquare. + // In our implementation, h = ROOT_OF_UNITY. + // We know that Z / h is a square since both Z and h are + // nonsquares. Precompute theta as a square root of Z / ROOT_OF_UNITY. + // + // We have gx2 = g(Z * u^2 * x1) = Z^3 * u^6 * gx1 + // = (Z * u^3)^2 * (Z/h * h * gx1) + // = (Z * theta * u^3)^2 * (h * gx1) + // + // When gx1 is not square, y1 is a square root of h * gx1, and so Z * theta * u^3 * y1 + // is a square root of gx2. Note that we don't actually need to compute gx2. + + let y2 = theta * z_u2 * u * y1; + let num_x = F::conditional_select(&num_x2, &num_x1, gx1_square); + let y = F::conditional_select(&y2, &y1, gx1_square); + + // 9. If sgn0(u) != sgn0(y), set y = -y + let y = F::conditional_select( + &(-y), + &y, + (u.get_lower_32() % 2).ct_eq(&(y.get_lower_32() % 2)), + ); + + I::Projective::new_jacobian(num_x * div, y * div3, div).unwrap() +} diff --git a/src/pasta/pallas.rs b/src/pasta/pallas.rs index a52dd37..b5a451a 100644 --- a/src/pasta/pallas.rs +++ b/src/pasta/pallas.rs @@ -1,8 +1,5 @@ //! The Pallas and iso-Pallas elliptic curve groups. -use lazy_static::lazy_static; - -use super::SimplifiedSWUWithDegree3Isogeny; use super::{Ep, EpAffine, Fp, Fq, IsoEp, IsoEpAffine}; /// The base field of the Pallas and iso-Pallas curves. @@ -23,72 +20,9 @@ pub type IsoPoint = IsoEp; /// A iso-Pallas point in the affine coordinate space (or the point at infinity). pub type IsoAffine = IsoEpAffine; -lazy_static! { - /// The iso-Pallas -> Pallas degree 3 isogeny map. - pub static ref MAP: SimplifiedSWUWithDegree3Isogeny = { - SimplifiedSWUWithDegree3Isogeny::new( - Point::Z, - Point::ISOGENY_CONSTANTS, - Point::MINUS_B_OVER_A, - Point::B_OVER_ZA, - Point::THETA - ) - }; -} - -#[test] -fn test_iso_map() { - use crate::arithmetic::Curve; - - // This is a regression test (it's the same input to iso_map as for hash_to_curve - // with domain prefix "z.cash:test", Shake128, and input b"hello"). - let r = IsoPoint::new_jacobian( - Base::from_raw([ - 0xc37f111df5c4419e, - 0x593c053e5e2337ad, - 0x9c6cfc47bce1aba6, - 0x0a881e4d556945aa, - ]), - Base::from_raw([ - 0xf234e04434502b47, - 0x6979f7f2b0acf188, - 0xa62eec46f662cb4e, - 0x035e5c8a06d5cfb4, - ]), - Base::from_raw([ - 0x11ab791d4fb6f6b4, - 0x575baa717958ef1f, - 0x6ac4e343558dcbf3, - 0x3af37975b0933125, - ]), - ) - .unwrap(); - let p = MAP.iso_map(&r); - let (x, y, z) = p.jacobian_coordinates(); - assert!( - format!("{:?}", x) == "0x318cc15f281662b3f26d0175cab97b924870c837879cac647e877be51a85e898" - ); - assert!( - format!("{:?}", y) == "0x1e91e2fa2a5a6a5bc86ff9564ae9336084470e7119dffcb85ae8c1383a3defd7" - ); - assert!( - format!("{:?}", z) == "0x1e049436efa754f5f189aec69c2c3a4a559eca6a12b45c3f2e4a769deeca6187" - ); -} - #[test] fn test_map_to_curve_pallas() { - use crate::arithmetic::{Curve, CurveAffine, FieldExt}; - use std::collections::HashSet; - - assert!(MAP.minus_b_over_a * IsoAffine::a() == -IsoAffine::b()); - assert!(MAP.b_over_za * MAP.z * IsoAffine::a() == IsoAffine::b()); - assert!(MAP.theta.square() * Base::ROOT_OF_UNITY == MAP.z); - - let set: HashSet<_> = (0..10000) - .map(|i| MAP.map_to_curve(&Base::from(i)).to_affine()) - .collect(); - assert!(set.len() == 10000); + use crate::arithmetic::Curve; let hash = Point::hasher("z.cash:test"); let p: Point = hash(b"hello"); diff --git a/src/pasta/vesta.rs b/src/pasta/vesta.rs index 4003542..fa0c50b 100644 --- a/src/pasta/vesta.rs +++ b/src/pasta/vesta.rs @@ -1,8 +1,5 @@ //! The Vesta and iso-Vesta elliptic curve groups. -use lazy_static::lazy_static; - -use super::SimplifiedSWUWithDegree3Isogeny; use super::{Eq, EqAffine, Fp, Fq, IsoEq, IsoEqAffine}; /// The base field of the Vesta and iso-Vesta curves. @@ -23,32 +20,9 @@ pub type IsoPoint = IsoEq; /// A iso-Vesta point in the affine coordinate space (or the point at infinity). pub type IsoAffine = IsoEqAffine; -lazy_static! { - /// The iso-Vesta -> Vesta degree 3 isogeny map. - pub static ref MAP: SimplifiedSWUWithDegree3Isogeny = { - SimplifiedSWUWithDegree3Isogeny::new( - Point::Z, - Point::ISOGENY_CONSTANTS, - Point::MINUS_B_OVER_A, - Point::B_OVER_ZA, - Point::THETA - ) - }; -} - #[test] fn test_map_to_curve_vesta() { - use crate::arithmetic::{Curve, CurveAffine, FieldExt}; - use std::collections::HashSet; - - assert!(MAP.minus_b_over_a * IsoAffine::a() == -IsoAffine::b()); - assert!(MAP.b_over_za * MAP.z * IsoAffine::a() == IsoAffine::b()); - assert!(MAP.theta.square() * Base::ROOT_OF_UNITY == MAP.z); - - let set: HashSet<_> = (0..10000) - .map(|i| MAP.map_to_curve(&Base::from(i)).to_affine()) - .collect(); - assert!(set.len() == 10000); + use crate::arithmetic::Curve; let hash = Point::hasher("z.cash:test"); let p: Point = hash(b"hello");