From 5b33ff9cabf5f92fcf18bb590e4ae9d6425a1fd8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 2 Feb 2021 10:24:33 -0700 Subject: [PATCH] Consolidate the hashtocurve module traits into a single structure. --- src/arithmetic/hashtocurve.rs | 245 +++++++++++++--------------------- src/pasta/pallas.rs | 8 +- src/pasta/vesta.rs | 6 +- 3 files changed, 98 insertions(+), 161 deletions(-) diff --git a/src/arithmetic/hashtocurve.rs b/src/arithmetic/hashtocurve.rs index 8432081..20f042f 100644 --- a/src/arithmetic/hashtocurve.rs +++ b/src/arithmetic/hashtocurve.rs @@ -8,148 +8,8 @@ use subtle::ConstantTimeEq; use super::{Curve, CurveAffine, Field, FieldExt}; -/// A method of hashing to an elliptic curve. -/// -/// This is intended to conform to the work-in-progress Internet Draft -/// [IRTF-CFRG-Hash-to-Curve](https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.html). -pub trait HashToCurve { - /// Curve that may or may not be isogenous to the target curve C. - type IsogenousCurve: Curve; - - /// The MAP_ID of this method as specified in - /// . - fn map_id(&self) -> &str; - - /// A non-uniform map from a field element to the isogenous curve. - fn map_to_curve(&self, u: &C::Base) -> Self::IsogenousCurve; - - /// The isogeny map from curve I to curve C. - /// (If no isogeny is required, this should be the identity function.) - fn iso_map(&self, p: &Self::IsogenousCurve) -> C::Projective; - - /// The random oracle map. - fn field_elements_to_curve(&self, u0: &C::Base, u1: &C::Base) -> C::Projective; - - /// 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_". - fn hash_to_curve( - &self, - domain_prefix: &str, - hasher: impl MessageHasher, - ) -> Box C::Projective + '_> { - let domain_separation_tag: String = format!( - "{}-{}_{}_{}_RO_", - domain_prefix, - C::CURVE_ID, - hasher.hash_name(), - self.map_id() - ); - - Box::new(move |message| { - let mut us = [Field::zero(); 2]; - hasher.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_". - fn encode_to_curve( - &self, - domain_prefix: &str, - hasher: impl MessageHasher, - ) -> Box C::Projective + '_> { - let domain_separation_tag: String = format!( - "{}-{}_{}_{}_NU_", - domain_prefix, - C::CURVE_ID, - hasher.hash_name(), - self.map_id() - ); - - Box::new(move |message| { - let mut us = [Field::zero(); 1]; - hasher.hash_to_field(message, domain_separation_tag.as_bytes(), &mut us); - let r = self.map_to_curve(&us[0]); - self.iso_map(&r) - }) - } -} - -/// Method of hashing a message and domain_separation_tag to field elements. -pub trait MessageHasher: 'static { - /// The HASH_NAME of this message hasher as specified in - /// . - fn hash_name(&self) -> &str; - - /// Hash the given message and domain separation tag to produce `buf.len()` - /// field elements, which are written to `buf`. - fn hash_to_field(&self, message: &[u8], domain_separation_tag: &[u8], buf: &mut [F]); -} - -/// A MessageHasher for SHAKE128 -/// [FIPS202](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf). -/// It does not support domain separation tags longer than 128 bytes. -#[derive(Debug, Default)] -pub struct Shake128 { - _marker: PhantomData, -} - -impl MessageHasher for Shake128 { - fn hash_name(&self) -> &str { - "XOF:SHAKE128" - } - - fn hash_to_field(&self, 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); - } - } -} - -/// The simplified SWU hash-to-curve method, using an isogenous curve -/// y^2 = x^3 + a*x + b. This currently only supports prime-order curves. +/// Implementation of the "simplified SWU" hashing to short Weierstrass curves +/// with a = 0. Internally uses SHAKE128. #[derive(Debug)] pub struct SimplifiedSWUWithDegree3Isogeny< F: FieldExt, @@ -182,7 +42,7 @@ impl, I: CurveAffine> /// /// # Panics /// Panics if z is square. - pub fn new(z: &F, isogeny_constants: &[F; 13]) -> Self { + pub fn new(z: &F, isogeny_constants: [F; 13]) -> Self { let a = I::a(); let b = I::b(); @@ -191,23 +51,99 @@ impl, I: CurveAffine> minus_b_over_a: (-b) * &(a.invert().unwrap()), b_over_za: b * &((*z * a).invert().unwrap()), theta: (F::ROOT_OF_UNITY.invert().unwrap() * z).sqrt().unwrap(), - isogeny_constants: *isogeny_constants, + isogeny_constants: isogeny_constants, _marker_c: PhantomData, _marker_i: PhantomData, } } -} -impl, I: CurveAffine> HashToCurve - for SimplifiedSWUWithDegree3Isogeny -{ - type IsogenousCurve = I::Projective; + /// 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" + ); - fn map_id(&self) -> &str { - "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]) + }) } - fn map_to_curve(&self, u: &F) -> Self::IsogenousCurve { + /// 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) @@ -285,7 +221,7 @@ impl, I: CurveAffine> HashToCurv } /// Implements a degree 3 isogeny map. - fn iso_map(&self, p: &I::Projective) -> C::Projective { + 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]. @@ -310,7 +246,8 @@ impl, I: CurveAffine> HashToCurv C::Projective::new_jacobian(xo, yo, zo).unwrap() } - fn field_elements_to_curve(&self, u0: &C::Base, u1: &C::Base) -> C::Projective { + /// 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; diff --git a/src/pasta/pallas.rs b/src/pasta/pallas.rs index 612280a..8df350f 100644 --- a/src/pasta/pallas.rs +++ b/src/pasta/pallas.rs @@ -108,13 +108,13 @@ lazy_static! { ]; let z = -Base::from_u64(13); - SimplifiedSWUWithDegree3Isogeny::new(&z, &isogeny_constants) + SimplifiedSWUWithDegree3Isogeny::new(&z, isogeny_constants) }; } #[test] fn test_iso_map() { - use crate::arithmetic::{Curve, HashToCurve}; + 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"). @@ -154,7 +154,7 @@ fn test_iso_map() { #[test] fn test_map_to_curve_pallas() { - use crate::arithmetic::{Curve, CurveAffine, HashToCurve, Shake128}; + use crate::arithmetic::{Curve, CurveAffine}; use std::collections::HashSet; assert!(MAP.minus_b_over_a * IsoAffine::a() == -IsoAffine::b()); @@ -166,7 +166,7 @@ fn test_map_to_curve_pallas() { .collect(); assert!(set.len() == 10000); - let hash = MAP.hash_to_curve("z.cash:test", Shake128::default()); + let hash = MAP.hash_to_curve("z.cash:test"); let p: Point = hash(b"hello"); let (x, y, z) = p.jacobian_coordinates(); println!("{:?}", p); diff --git a/src/pasta/vesta.rs b/src/pasta/vesta.rs index ec7a111..e0e5967 100644 --- a/src/pasta/vesta.rs +++ b/src/pasta/vesta.rs @@ -108,13 +108,13 @@ lazy_static! { ]; let z = -Base::from_u64(13); - SimplifiedSWUWithDegree3Isogeny::new(&z, &isogeny_constants) + SimplifiedSWUWithDegree3Isogeny::new(&z, isogeny_constants) }; } #[test] fn test_map_to_curve_vesta() { - use crate::arithmetic::{Curve, CurveAffine, HashToCurve, Shake128}; + use crate::arithmetic::{Curve, CurveAffine}; use std::collections::HashSet; assert!(MAP.minus_b_over_a * IsoAffine::a() == -IsoAffine::b()); @@ -126,7 +126,7 @@ fn test_map_to_curve_vesta() { .collect(); assert!(set.len() == 10000); - let hash = MAP.hash_to_curve("z.cash:test", Shake128::default()); + let hash = MAP.hash_to_curve("z.cash:test"); let p: Point = hash(b"hello"); let (x, y, z) = p.jacobian_coordinates(); println!("{:?}", p);