From fa3afc29bbf499f4a8e66af69a5da4680e074c4d Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Wed, 13 Jan 2021 13:24:47 +0000 Subject: [PATCH 01/28] Add an implementation of simplified SWU hash-to-curve. Signed-off-by: Daira Hopwood --- Cargo.toml | 6 + benches/hashtocurve.rs | 47 +++++ src/arithmetic.rs | 2 + src/arithmetic/curves.rs | 26 ++- src/arithmetic/fields.rs | 2 +- src/arithmetic/hashtocurve.rs | 330 ++++++++++++++++++++++++++++++++++ src/pasta/curves.rs | 296 ++++++++++++++++++++++-------- src/pasta/fields/fp.rs | 2 +- src/pasta/fields/fq.rs | 2 +- src/pasta/pallas.rs | 183 ++++++++++++++++++- src/pasta/vesta.rs | 143 ++++++++++++++- 11 files changed, 941 insertions(+), 98 deletions(-) create mode 100644 benches/hashtocurve.rs create mode 100644 src/arithmetic/hashtocurve.rs diff --git a/Cargo.toml b/Cargo.toml index 0ad9658..b45e69c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,10 @@ criterion = "0.3" name = "arithmetic" harness = false +[[bench]] +name = "hashtocurve" +harness = false + [[bench]] name = "plonk" harness = false @@ -43,8 +47,10 @@ metrics = "0.14.2" num_cpus = "1.13" rand = "0.8" blake2b_simd = "0.5" +sha3 = "0.9.1" lazy_static = "1.4.0" static_assertions = "1.1.0" +byteorder = "1.4.2" # Temporary workaround for https://github.com/myrrlyn/funty/issues/3 funty = "=1.1.0" diff --git a/benches/hashtocurve.rs b/benches/hashtocurve.rs new file mode 100644 index 0000000..0175b8c --- /dev/null +++ b/benches/hashtocurve.rs @@ -0,0 +1,47 @@ +//! Benchmarks for hashing to the Pasta curves. + +use criterion::{criterion_group, criterion_main, Criterion}; + +use halo2::arithmetic::{HashToCurve, Shake128}; +use halo2::pasta::{pallas, vesta}; + +fn criterion_benchmark(c: &mut Criterion) { + bench_hash_to_curve(c); + bench_encode_to_curve(c); + bench_map_to_curve(c); +} + +fn bench_hash_to_curve(c: &mut Criterion) { + let mut group = c.benchmark_group("hash-to-curve"); + + let hash_pallas = pallas::MAP.hash_to_curve("z.cash:test", Shake128::default()); + group.bench_function("Pallas", |b| b.iter(|| hash_pallas(b"benchmark"))); + + let hash_vesta = vesta::MAP.hash_to_curve("z.cash:test", Shake128::default()); + group.bench_function("Vesta", |b| b.iter(|| hash_vesta(b"benchmark"))); +} + +fn bench_encode_to_curve(c: &mut Criterion) { + let mut group = c.benchmark_group("encode-to-curve"); + + let encode_pallas = pallas::MAP.encode_to_curve("z.cash:test", Shake128::default()); + group.bench_function("Pallas", |b| b.iter(|| encode_pallas(b"benchmark"))); + + let encode_vesta = vesta::MAP.encode_to_curve("z.cash:test", Shake128::default()); + group.bench_function("Vesta", |b| b.iter(|| encode_vesta(b"benchmark"))); +} + +fn bench_map_to_curve(c: &mut Criterion) { + let mut group = c.benchmark_group("map-to-curve"); + + let pallas_input = &pallas::Base::one(); + group.bench_function("Pallas", |b| { + b.iter(|| pallas::MAP.map_to_curve(pallas_input)) + }); + + let vesta_input = &vesta::Base::one(); + group.bench_function("Vesta", |b| b.iter(|| vesta::MAP.map_to_curve(vesta_input))); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/src/arithmetic.rs b/src/arithmetic.rs index dcb4189..b6dc672 100644 --- a/src/arithmetic.rs +++ b/src/arithmetic.rs @@ -6,9 +6,11 @@ use ff::Field; mod curves; mod fields; +mod hashtocurve; pub use curves::*; pub use fields::*; +pub use hashtocurve::*; /// This represents an element of a group with basic operations that can be /// performed. This allows an FFT implementation (for example) to operate diff --git a/src/arithmetic/curves.rs b/src/arithmetic/curves.rs index 50bc2d9..e23151f 100644 --- a/src/arithmetic/curves.rs +++ b/src/arithmetic/curves.rs @@ -11,7 +11,7 @@ use super::{FieldExt, Group}; use std::io::{self, Read, Write}; /// This trait is a common interface for dealing with elements of an elliptic -/// curve group in the "projective" form, where that arithmetic is usually more +/// curve group in a "projective" form, where that arithmetic is usually more /// efficient. pub trait Curve: Sized @@ -60,7 +60,7 @@ pub trait Curve: /// Obtains the additive identity. fn zero() -> Self; - /// Obtains the base point of the curve. + /// Obtains the base point of the curve, if defined. fn one() -> Self; /// Doubles this element. @@ -76,6 +76,9 @@ pub trait Curve: /// Converts this element into its affine form. fn to_affine(&self) -> Self::Affine; + /// Return the Jacobian coordinates of this point. + fn jacobian_coordinates(&self) -> (Self::Base, Self::Base, Self::Base); + /// Returns whether or not this element is on the curve; should /// always be true unless an "unchecked" API was used. fn is_on_curve(&self) -> Choice; @@ -84,8 +87,15 @@ pub trait Curve: /// sizes of the slices are different. fn batch_to_affine(v: &[Self], target: &mut [Self::Affine]); - /// Returns the curve constant b + /// Returns the curve constant a. + fn a() -> Self::Base; + + /// Returns the curve constant b. fn b() -> Self::Base; + + /// Obtains a point given Jacobian coordinates $X : Y : Z$, failing + /// if the coordinates are not on the curve. + fn new_jacobian(x: Self::Base, y: Self::Base, z: Self::Base) -> CtOption; } /// This trait is the affine counterpart to `Curve` and is used for @@ -128,10 +138,13 @@ pub trait CurveAffine: /// random string. const BLAKE2B_PERSONALIZATION: &'static [u8; 16]; + /// CURVE_ID used for hash-to-curve. + const CURVE_ID: &'static str; + /// Obtains the additive identity. fn zero() -> Self; - /// Obtains the base point of the curve. + /// Obtains the base point of the curve, if defined. fn one() -> Self; /// Returns whether or not this element is the identity. @@ -182,6 +195,9 @@ pub trait CurveAffine: /// element. fn to_bytes_wide(&self) -> [u8; 64]; - /// Returns the curve constant $b$ + /// Returns the curve constant $a$. + fn a() -> Self::Base; + + /// Returns the curve constant $b$. fn b() -> Self::Base; } diff --git a/src/arithmetic/fields.rs b/src/arithmetic/fields.rs index 7c8a788..59bd863 100644 --- a/src/arithmetic/fields.rs +++ b/src/arithmetic/fields.rs @@ -315,7 +315,7 @@ impl SqrtTables { (is_square, res) } - /// Common part of sqrt_ratio and sqrt_alt: return res given v = u^((T-1)/2) and uv = u * v. + /// Common part of sqrt_ratio and sqrt_alt: return their result 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; diff --git a/src/arithmetic/hashtocurve.rs b/src/arithmetic/hashtocurve.rs new file mode 100644 index 0000000..4824a03 --- /dev/null +++ b/src/arithmetic/hashtocurve.rs @@ -0,0 +1,330 @@ +//! This module implements "simplified SWU" hashing to short Weierstrass curves +//! with a = 0. + +use byteorder::{BigEndian, WriteBytesExt}; +use core::fmt::Debug; +use core::marker::PhantomData; +use subtle::ConstantTimeEq; + +use super::{Curve, CurveAffine, FieldExt}; + +/// A method of hashing to an elliptic curve. +/// (If no isogeny is required, then C and I should be the same.) +/// +/// 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, C: CurveAffine> { + /// 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) -> I::Projective; + + /// 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: &I::Projective) -> 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 + 'static, + ) -> Box C::Projective + '_> { + let domain_separation_tag = format!( + "{}-{}_{}_{}_RO_", + domain_prefix, + C::CURVE_ID, + hasher.hash_name(), + self.map_id() + ); + + Box::new(move |message| { + let us = hasher.hash_to_field(message, domain_separation_tag.as_bytes(), 2); + 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 + 'static, + ) -> Box C::Projective + '_> { + let domain_separation_tag = format!( + "{}-{}_{}_{}_NU_", + domain_prefix, + C::CURVE_ID, + hasher.hash_name(), + self.map_id() + ); + + Box::new(move |message| { + let us = hasher.hash_to_field(message, domain_separation_tag.as_bytes(), 1); + 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 { + /// The HASH_NAME of this message hasher as specified in + /// . + fn hash_name(&self) -> &str; + + /// Hash the given message and domain separation tag to give `count` + /// field elements. + fn hash_to_field(&self, message: &[u8], domain_separation_tag: &[u8], count: usize) -> Vec; +} + +/// 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], count: usize) -> Vec { + 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 = count * 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); + + xof.finalize_boxed(outlen) + .chunks(CHUNKLEN) + .map(|big| { + let mut little = [0u8; CHUNKLEN]; + little.copy_from_slice(big); + little.reverse(); + F::from_bytes_wide(&little) + }) + .collect() + } +} + +/// A MessageHasher for BLAKE2b. +#[derive(Debug, Default)] +pub struct Blake2bXof { + marker: PhantomData, +} + +impl MessageHasher for Blake2bXof { + fn hash_name(&self) -> &str { + "XOF:BLAKE2b" + } + + #[allow(unused_variables)] + fn hash_to_field(&self, message: &[u8], domain_separation_tag: &[u8], count: usize) -> Vec { + todo!() + } +} + +/// 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. +#[derive(Debug)] +pub struct SimplifiedSWUWithDegree3Isogeny< + F: FieldExt, + I: CurveAffine, + C: CurveAffine, +> { + /// `Z` parameter (ξ in [WB2019]). + pub z: F, + + /// Precomputed -b/a for the isogenous curve. + pub minus_b_over_a: F, + + /// Precomputed b/Za for the isogenous curve. + pub b_over_za: F, + + /// Precomputed sqrt(Z / ROOT_OF_UNITY). + pub theta: F, + + /// Constants for the isogeny. + pub isogeny_constants: [F; 13], + + marker_curve: PhantomData, + marker_iso: PhantomData, +} + +impl, C: 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]) -> Self { + let a = I::a(); + let b = I::b(); + + SimplifiedSWUWithDegree3Isogeny { + z: *z, + 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, + marker_curve: PhantomData, + marker_iso: PhantomData, + } + } +} + +impl, C: CurveAffine> HashToCurve + for SimplifiedSWUWithDegree3Isogeny +{ + fn map_id(&self) -> &str { + "SSWU" + } + + 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 + // ROOT_OF_UNITY * s for free when s is not square, where h is a fixed nonsquare. + // We know that Z / ROOT_OF_UNITY is a square since both Z and ROOT_OF_UNITY 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. + 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() + } + + 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) + } +} diff --git a/src/pasta/curves.rs b/src/pasta/curves.rs index 23d0fcb..57ee84c 100644 --- a/src/pasta/curves.rs +++ b/src/pasta/curves.rs @@ -3,6 +3,7 @@ use core::cmp; use core::fmt::Debug; +use core::hash::{Hash, Hasher}; use core::ops::{Add, Mul, Neg, Sub}; use ff::Field; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; @@ -11,7 +12,8 @@ use super::{Fp, Fq}; use crate::arithmetic::{Curve, CurveAffine, FieldExt, Group}; macro_rules! new_curve_impl { - ($name:ident, $name_affine:ident, $base:ident, $scalar:ident, $blake2b_personalization:literal) => { + ($name:ident, $name_affine:ident, $base:ident, $scalar:ident, $blake2b_personalization:literal, + $curve_id:literal, $a_raw:expr, $b_raw:expr, $curve_type:ident) => { /// Represents a point in the projective coordinate space. #[derive(Copy, Clone, Debug)] pub struct $name { @@ -21,9 +23,12 @@ macro_rules! new_curve_impl { } impl $name { + const fn curve_constant_a() -> $base { + $base::from_raw($a_raw) + } + const fn curve_constant_b() -> $base { - // NOTE: this is specific to b = 5 - $base::from_raw([5, 0, 0, 0]) + $base::from_raw($b_raw) } } @@ -51,6 +56,8 @@ macro_rules! new_curve_impl { type Scalar = $scalar; type Base = $base; + impl_projective_curve_specific!($name, $base, $curve_type); + fn zero() -> Self { Self { x: $base::zero(), @@ -59,19 +66,6 @@ macro_rules! new_curve_impl { } } - fn one() -> Self { - // NOTE: This is specific to b = 5 - - const NEGATIVE_ONE: $base = $base::neg(&$base::one()); - const TWO: $base = $base::from_raw([2, 0, 0, 0]); - - Self { - x: NEGATIVE_ONE, - y: TWO, - z: $base::one(), - } - } - fn is_zero(&self) -> Choice { self.z.ct_is_zero() } @@ -92,56 +86,27 @@ macro_rules! new_curve_impl { $name_affine::conditional_select(&tmp, &$name_affine::zero(), zinv.ct_is_zero()) } - fn double(&self) -> Self { - // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l - // - // There are no points of order 2. - - let a = self.x.square(); - let b = self.y.square(); - let c = b.square(); - let d = self.x + b; - let d = d.square(); - let d = d - a - c; - let d = d + d; - let e = a + a + a; - let f = e.square(); - let z3 = self.z * self.y; - let z3 = z3 + z3; - let x3 = f - (d + d); - let c = c + c; - let c = c + c; - let c = c + c; - let y3 = e * (d - x3) - c; - - let tmp = $name { - x: x3, - y: y3, - z: z3, - }; - - $name::conditional_select(&tmp, &$name::zero(), self.is_zero()) - } - - /// Apply the curve endomorphism by multiplying the x-coordinate - /// by an element of multiplicative order 3. - fn endo(&self) -> Self { - $name { - x: self.x * $base::ZETA, - y: self.y, - z: self.z, - } + fn a() -> Self::Base { + $name::curve_constant_a() } fn b() -> Self::Base { $name::curve_constant_b() } - fn is_on_curve(&self) -> Choice { - // Y^2 - X^3 = 5(Z^6) + fn jacobian_coordinates(&self) -> ($base, $base, $base) { + (self.x, self.y, self.z) + } - (self.y.square() - (self.x.square() * self.x)) - .ct_eq(&((self.z.square() * self.z).square() * $name::curve_constant_b())) + fn is_on_curve(&self) -> Choice { + // Y^2 = X^3 + AX(Z^4) + b(Z^6) + // Y^2 - (X^2 + A(Z^4))X = b(Z^6) + + let z2 = self.z.square(); + let z4 = z2.square(); + let z6 = z4 * z2; + (self.y.square() - (self.x.square() + $name::curve_constant_a() * z4) * self.x) + .ct_eq(&(z6 * $name::curve_constant_b())) | self.z.ct_is_zero() } @@ -182,6 +147,11 @@ macro_rules! new_curve_impl { *q = $name_affine::conditional_select(&q, &$name_affine::zero(), skip); } } + + fn new_jacobian(x: Self::Base, y: Self::Base, z: Self::Base) -> CtOption { + let p = $name { x, y, z }; + CtOption::new(p, p.is_on_curve()) + } } impl<'a> From<&'a $name_affine> for $name { @@ -508,6 +478,9 @@ macro_rules! new_curve_impl { type Base = $base; const BLAKE2B_PERSONALIZATION: &'static [u8; 16] = $blake2b_personalization; + const CURVE_ID: &'static str = $curve_id; + + impl_affine_curve_specific!($name, $base, $curve_type); fn zero() -> Self { Self { @@ -517,26 +490,13 @@ macro_rules! new_curve_impl { } } - fn one() -> Self { - // NOTE: This is specific to b = 5 - - const NEGATIVE_ONE: $base = $base::neg(&$base::from_raw([1, 0, 0, 0])); - const TWO: $base = $base::from_raw([2, 0, 0, 0]); - - Self { - x: NEGATIVE_ONE, - y: TWO, - infinity: Choice::from(0u8), - } - } - fn is_zero(&self) -> Choice { self.infinity } fn is_on_curve(&self) -> Choice { - // y^2 - x^3 ?= b - (self.y.square() - (self.x.square() * self.x)).ct_eq(&$name::curve_constant_b()) + // y^2 - x^3 - ax ?= b + (self.y.square() - (self.x.square() + &$name::curve_constant_a()) * self.x).ct_eq(&$name::curve_constant_b()) | self.infinity } @@ -636,6 +596,10 @@ macro_rules! new_curve_impl { } } + fn a() -> Self::Base { + $name::curve_constant_a() + } + fn b() -> Self::Base { $name::curve_constant_b() } @@ -686,6 +650,14 @@ macro_rules! new_curve_impl { } } + impl Hash for $name_affine { + fn hash(&self, state: &mut H) { + self.x.hash(state); + self.y.hash(state); + bool::from(self.infinity).hash(state) + } + } + impl_binops_additive!($name, $name); impl_binops_additive!($name, $name_affine); impl_binops_additive_specify_output!($name_affine, $name_affine, $name); @@ -712,5 +684,177 @@ macro_rules! new_curve_impl { }; } -new_curve_impl!(Ep, EpAffine, Fp, Fq, b"halo2_____pallas"); -new_curve_impl!(Eq, EqAffine, Fq, Fp, b"halo2______vesta"); +macro_rules! impl_projective_curve_specific { + ($name:ident, $base:ident, special_a0_b5) => { + fn one() -> Self { + // NOTE: This is specific to b = 5 + + const NEGATIVE_ONE: $base = $base::neg(&$base::one()); + const TWO: $base = $base::from_raw([2, 0, 0, 0]); + + Self { + x: NEGATIVE_ONE, + y: TWO, + z: $base::one(), + } + } + + /// Apply the curve endomorphism by multiplying the x-coordinate + /// by an element of multiplicative order 3. + fn endo(&self) -> Self { + $name { + x: self.x * $base::ZETA, + y: self.y, + z: self.z, + } + } + + fn double(&self) -> Self { + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l + // + // There are no points of order 2. + + let a = self.x.square(); + let b = self.y.square(); + let c = b.square(); + let d = self.x + b; + let d = d.square(); + let d = d - a - c; + let d = d + d; + let e = a + a + a; + let f = e.square(); + let z3 = self.z * self.y; + let z3 = z3 + z3; + let x3 = f - (d + d); + let c = c + c; + let c = c + c; + let c = c + c; + let y3 = e * (d - x3) - c; + + let tmp = $name { + x: x3, + y: y3, + z: z3, + }; + + $name::conditional_select(&tmp, &$name::zero(), self.is_zero()) + } + }; + ($name:ident, $base:ident, general) => { + /// Unimplemented: there is no standard generator for this curve. + fn one() -> Self { + unimplemented!() + } + + /// Unimplemented: no endomorphism is supported for this curve. + fn endo(&self) -> Self { + unimplemented!() + } + + fn double(&self) -> Self { + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl + // + // There are no points of order 2. + + let xx = self.x.square(); + let yy = self.y.square(); + let a = yy.square(); + let zz = self.z.square(); + let s = (self.x + yy).square() - xx - a; + let s = s + s; + let m = xx + xx + xx + $name::curve_constant_a() * zz.square(); + let x3 = m.square() - (s + s); + let a = a + a; + let a = a + a; + let a = a + a; + let y3 = m * (s - x3) - a; + let z3 = (self.x + self.y).square() - yy - zz; + + let tmp = $name { + x: x3, + y: y3, + z: z3, + }; + + $name::conditional_select(&tmp, &$name::zero(), self.is_zero()) + } + }; +} + +macro_rules! impl_affine_curve_specific { + ($name:ident, $base:ident, special_a0_b5) => { + fn one() -> Self { + // NOTE: This is specific to b = 5 + + const NEGATIVE_ONE: $base = $base::neg(&$base::from_raw([1, 0, 0, 0])); + const TWO: $base = $base::from_raw([2, 0, 0, 0]); + + Self { + x: NEGATIVE_ONE, + y: TWO, + infinity: Choice::from(0u8), + } + } + }; + ($name:ident, $base:ident, general) => { + /// Unimplemented: there is no standard generator for this curve. + fn one() -> Self { + unimplemented!() + } + }; +} + +new_curve_impl!( + Ep, + EpAffine, + Fp, + Fq, + b"halo2_____pallas", + "pallas", + [0, 0, 0, 0], + [5, 0, 0, 0], + special_a0_b5 +); +new_curve_impl!( + Eq, + EqAffine, + Fq, + Fp, + b"halo2______vesta", + "vesta", + [0, 0, 0, 0], + [5, 0, 0, 0], + special_a0_b5 +); +new_curve_impl!( + IsoEp, + IsoEpAffine, + Fp, + Fq, + b"halo2_iso_pallas", + "iso-pallas", + [ + 0x92bb4b0b657a014b, + 0xb74134581a27a59f, + 0x49be2d7258370742, + 0x18354a2eb0ea8c9c, + ], + [1265, 0, 0, 0], + general +); +new_curve_impl!( + IsoEq, + IsoEqAffine, + Fq, + Fp, + b"halo2__iso_vesta", + "iso-vesta", + [ + 0xc515ad7242eaa6b1, + 0x9673928c7d01b212, + 0x81639c4d96f78773, + 0x267f9b2ee592271a, + ], + [1265, 0, 0, 0], + general +); diff --git a/src/pasta/fields/fp.rs b/src/pasta/fields/fp.rs index 737789c..c43fe2c 100644 --- a/src/pasta/fields/fp.rs +++ b/src/pasta/fields/fp.rs @@ -16,7 +16,7 @@ use crate::arithmetic::{adc, mac, sbb, FieldExt, Group, SqrtTables}; // The internal representation of this type is four 64-bit unsigned // integers in little-endian order. `Fp` values are always in // Montgomery form; i.e., Fp(a) = aR mod p, with R = 2^256. -#[derive(Clone, Copy, Eq)] +#[derive(Clone, Copy, Eq, Hash)] pub struct Fp(pub(crate) [u64; 4]); impl fmt::Debug for Fp { diff --git a/src/pasta/fields/fq.rs b/src/pasta/fields/fq.rs index 56e73c7..544fcb6 100644 --- a/src/pasta/fields/fq.rs +++ b/src/pasta/fields/fq.rs @@ -16,7 +16,7 @@ use crate::arithmetic::{adc, mac, sbb, FieldExt, Group, SqrtTables}; // The internal representation of this type is four 64-bit unsigned // integers in little-endian order. `Fq` values are always in // Montgomery form; i.e., Fq(a) = aR mod q, with R = 2^256. -#[derive(Clone, Copy, Eq)] +#[derive(Clone, Copy, Eq, Hash)] pub struct Fq(pub(crate) [u64; 4]); impl fmt::Debug for Fq { diff --git a/src/pasta/pallas.rs b/src/pasta/pallas.rs index 78b2f64..0b7e249 100644 --- a/src/pasta/pallas.rs +++ b/src/pasta/pallas.rs @@ -1,13 +1,182 @@ -//! The Pallas elliptic curve group. +//! The Pallas and iso-Pallas elliptic curve groups. + +use lazy_static::lazy_static; + +use super::{Ep, EpAffine, Fp, Fq, IsoEp, IsoEpAffine}; +use crate::arithmetic::{FieldExt, SimplifiedSWUWithDegree3Isogeny}; + +/// The base field of the Pallas and iso-Pallas curves. +pub type Base = Fp; + +/// The scalar field of the Pallas and iso-Pallas curves. +pub type Scalar = Fq; /// A Pallas point in the projective coordinate space. -pub type Point = super::Ep; +pub type Point = Ep; /// A Pallas point in the affine coordinate space (or the point at infinity). -pub type Affine = super::EpAffine; +pub type Affine = EpAffine; -/// The base field of the Pallas group. -pub type Base = super::Fp; +/// An iso-Pallas point in the projective coordinate space. +pub type IsoPoint = IsoEp; -/// The scalar field of the Pallas group. -pub type Scalar = super::Fq; +/// 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 = { + let isogeny_constants: [Base; 13] = [ + Base::from_raw([ + 0x775f6034aaaaaaab, + 0x4081775473d8375b, + 0xe38e38e38e38e38e, + 0x0e38e38e38e38e38, + ]), + Base::from_raw([ + 0x8cf863b02814fb76, + 0x0f93b82ee4b99495, + 0x267c7ffa51cf412a, + 0x3509afd51872d88e, + ]), + Base::from_raw([ + 0x0eb64faef37ea4f7, + 0x380af066cfeb6d69, + 0x98c7d7ac3d98fd13, + 0x17329b9ec5253753, + ]), + Base::from_raw([ + 0xeebec06955555580, + 0x8102eea8e7b06eb6, + 0xc71c71c71c71c71c, + 0x1c71c71c71c71c71, + ]), + Base::from_raw([ + 0xc47f2ab668bcd71f, + 0x9c434ac1c96b6980, + 0x5a607fcce0494a79, + 0x1d572e7ddc099cff, + ]), + Base::from_raw([ + 0x2aa3af1eae5b6604, + 0xb4abf9fb9a1fc81c, + 0x1d13bf2a7f22b105, + 0x325669becaecd5d1, + ]), + Base::from_raw([ + 0x5ad985b5e38e38e4, + 0x7642b01ad461bad2, + 0x4bda12f684bda12f, + 0x1a12f684bda12f68, + ]), + Base::from_raw([ + 0xc67c31d8140a7dbb, + 0x07c9dc17725cca4a, + 0x133e3ffd28e7a095, + 0x1a84d7ea8c396c47, + ]), + Base::from_raw([ + 0x02e2be87d225b234, + 0x1765e924f7459378, + 0x303216cce1db9ff1, + 0x3fb98ff0d2ddcadd, + ]), + Base::from_raw([ + 0x93e53ab371c71c4f, + 0x0ac03e8e134eb3e4, + 0x7b425ed097b425ed, + 0x025ed097b425ed09, + ]), + Base::from_raw([ + 0x5a28279b1d1b42ae, + 0x5941a3a4a97aa1b3, + 0x0790bfb3506defb6, + 0x0c02c5bcca0e6b7f, + ]), + Base::from_raw([ + 0x4d90ab820b12320a, + 0xd976bbfabbc5661d, + 0x573b3d7f7d681310, + 0x17033d3c60c68173, + ]), + Base::from_raw([ + 0x992d30ecfffffde5, + 0x224698fc094cf91b, + 0x0000000000000000, + 0x4000000000000000, + ]), + ]; + + let z = -Base::from_u64(13); + SimplifiedSWUWithDegree3Isogeny::new(&z, &isogeny_constants) + }; +} + +#[test] +fn test_iso_map() { + use crate::arithmetic::{Curve, HashToCurve}; + + // 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, HashToCurve, Shake128}; + 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); + + let hash = MAP.hash_to_curve("z.cash:test", Shake128::default()); + let p: Point = hash(b"hello"); + let (x, y, z) = p.jacobian_coordinates(); + println!("{:?}", p); + assert!( + format!("{:?}", x) == "0x318cc15f281662b3f26d0175cab97b924870c837879cac647e877be51a85e898" + ); + assert!( + format!("{:?}", y) == "0x1e91e2fa2a5a6a5bc86ff9564ae9336084470e7119dffcb85ae8c1383a3defd7" + ); + assert!( + format!("{:?}", z) == "0x1e049436efa754f5f189aec69c2c3a4a559eca6a12b45c3f2e4a769deeca6187" + ); +} diff --git a/src/pasta/vesta.rs b/src/pasta/vesta.rs index c20d9b6..c867fee 100644 --- a/src/pasta/vesta.rs +++ b/src/pasta/vesta.rs @@ -1,13 +1,142 @@ -//! The Vesta elliptic curve group. +//! The Vesta and iso-Vesta elliptic curve groups. + +use lazy_static::lazy_static; + +use super::{Eq, EqAffine, Fp, Fq, IsoEq, IsoEqAffine}; +use crate::arithmetic::{FieldExt, SimplifiedSWUWithDegree3Isogeny}; + +/// The base field of the Vesta and iso-Vesta curves. +pub type Base = Fq; + +/// The scalar field of the Vesta and iso-Vesta curves. +pub type Scalar = Fp; /// A Vesta point in the projective coordinate space. -pub type Point = super::Eq; +pub type Point = Eq; /// A Vesta point in the affine coordinate space (or the point at infinity). -pub type Affine = super::EqAffine; +pub type Affine = EqAffine; -/// The base field of the Vesta group. -pub type Base = super::Fq; +/// An iso-Vesta point in the projective coordinate space. +pub type IsoPoint = IsoEq; -/// The scalar field of the Vesta group. -pub type Scalar = super::Fp; +/// 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 = { + let isogeny_constants: [Base; 13] = [ + Base::from_raw([ + 0x43cd42c800000001, + 0x0205dd51cfa0961a, + 0x8e38e38e38e38e39, + 0x38e38e38e38e38e3, + ]), + Base::from_raw([ + 0x8b95c6aaf703bcc5, + 0x216b8861ec72bd5d, + 0xacecf10f5f7c09a2, + 0x1d935247b4473d17, + ]), + Base::from_raw([ + 0xaeac67bbeb586a3d, + 0xd59d03d23b39cb11, + 0xed7ee4a9cdf78f8f, + 0x18760c7f7a9ad20d, + ]), + Base::from_raw([ + 0xfb539a6f0000002b, + 0xe1c521a795ac8356, + 0x1c71c71c71c71c71, + 0x31c71c71c71c71c7, + ]), + Base::from_raw([ + 0xb7284f7eaf21a2e9, + 0xa3ad678129b604d3, + 0x1454798a5b5c56b2, + 0x0a2de485568125d5, + ]), + Base::from_raw([ + 0xf169c187d2533465, + 0x30cd6d53df49d235, + 0x0c621de8b91c242a, + 0x14735171ee542778, + ]), + Base::from_raw([ + 0x6bef1642aaaaaaab, + 0x5601f4709a8adcb3, + 0xda12f684bda12f68, + 0x12f684bda12f684b, + ]), + Base::from_raw([ + 0x8bee58e5fb81de63, + 0x21d910aefb03b31d, + 0xd6767887afbe04d1, + 0x2ec9a923da239e8b, + ]), + Base::from_raw([ + 0x4986913ab4443034, + 0x97a3ca5c24e9ea63, + 0x66d1466e9de10e64, + 0x19b0d87e16e25788, + ]), + Base::from_raw([ + 0x8f64842c55555533, + 0x8bc32d36fb21a6a3, + 0x425ed097b425ed09, + 0x1ed097b425ed097b, + ]), + Base::from_raw([ + 0x58dfecce86b2745e, + 0x06a767bfc35b5bac, + 0x9e7eb64f890a820c, + 0x2f44d6c801c1b8bf, + ]), + Base::from_raw([ + 0xd43d449776f99d2f, + 0x926847fb9ddd76a1, + 0x252659ba2b546c7e, + 0x3d59f455cafc7668, + ]), + Base::from_raw([ + 0x8c46eb20fffffde5, + 0x224698fc0994a8dd, + 0x0000000000000000, + 0x4000000000000000, + ]), + ]; + + let z = -Base::from_u64(13); + SimplifiedSWUWithDegree3Isogeny::new(&z, &isogeny_constants) + }; +} + +#[test] +fn test_map_to_curve_vesta() { + use crate::arithmetic::{Curve, CurveAffine, HashToCurve, Shake128}; + 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); + + let hash = MAP.hash_to_curve("z.cash:test", Shake128::default()); + let p: Point = hash(b"hello"); + let (x, y, z) = p.jacobian_coordinates(); + println!("{:?}", p); + assert!( + format!("{:?}", x) == "0x3984612258b3b43b4f6e046f7f796bbd35ffd8908804bcf47b9537d3ec7645c9" + ); + assert!( + format!("{:?}", y) == "0x2573c035293d745a288a65a7a37709ef99bcf31b77cfb3a1126a61e3adeebc4b" + ); + assert!( + format!("{:?}", z) == "0x1cb99da94a634842b09a3ee1e5b462233e1fc23d0b357ec7fb0d1c409be30720" + ); +} From db11c470459eeaadbef1c07d140ffbbdd4364755 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Sat, 30 Jan 2021 22:08:17 +0000 Subject: [PATCH 02/28] Apply suggestions from code review Co-authored-by: ying tong --- src/arithmetic/hashtocurve.rs | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/src/arithmetic/hashtocurve.rs b/src/arithmetic/hashtocurve.rs index 4824a03..c4c3106 100644 --- a/src/arithmetic/hashtocurve.rs +++ b/src/arithmetic/hashtocurve.rs @@ -105,7 +105,7 @@ pub trait MessageHasher { /// It does not support domain separation tags longer than 128 bytes. #[derive(Debug, Default)] pub struct Shake128 { - marker: PhantomData, + _marker: PhantomData, } impl MessageHasher for Shake128 { @@ -143,23 +143,6 @@ impl MessageHasher for Shake128 { } } -/// A MessageHasher for BLAKE2b. -#[derive(Debug, Default)] -pub struct Blake2bXof { - marker: PhantomData, -} - -impl MessageHasher for Blake2bXof { - fn hash_name(&self) -> &str { - "XOF:BLAKE2b" - } - - #[allow(unused_variables)] - fn hash_to_field(&self, message: &[u8], domain_separation_tag: &[u8], count: usize) -> Vec { - todo!() - } -} - /// 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. #[derive(Debug)] @@ -168,7 +151,7 @@ pub struct SimplifiedSWUWithDegree3Isogeny< I: CurveAffine, C: CurveAffine, > { - /// `Z` parameter (ξ in [WB2019]). + /// `Z` parameter (ξ in [WB2019](https://eprint.iacr.org/2019/403)). pub z: F, /// Precomputed -b/a for the isogenous curve. @@ -268,8 +251,9 @@ impl, C: CurveAffine> HashToCurv // 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 - // ROOT_OF_UNITY * s for free when s is not square, where h is a fixed nonsquare. - // We know that Z / ROOT_OF_UNITY is a square since both Z and ROOT_OF_UNITY are + // 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 From e4e8aef5b6c193d2fb8ed3484dd72385b0f62076 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 2 Feb 2021 09:26:08 -0700 Subject: [PATCH 03/28] Simplify HashToCurve trait. --- src/arithmetic/hashtocurve.rs | 77 +++++++++++++++++++---------------- src/pasta/pallas.rs | 2 +- src/pasta/vesta.rs | 2 +- 3 files changed, 44 insertions(+), 37 deletions(-) diff --git a/src/arithmetic/hashtocurve.rs b/src/arithmetic/hashtocurve.rs index c4c3106..8432081 100644 --- a/src/arithmetic/hashtocurve.rs +++ b/src/arithmetic/hashtocurve.rs @@ -6,24 +6,26 @@ use core::fmt::Debug; use core::marker::PhantomData; use subtle::ConstantTimeEq; -use super::{Curve, CurveAffine, FieldExt}; +use super::{Curve, CurveAffine, Field, FieldExt}; /// A method of hashing to an elliptic curve. -/// (If no isogeny is required, then C and I should be the same.) /// /// 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, C: CurveAffine> { +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) -> I::Projective; + 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: &I::Projective) -> C::Projective; + 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; @@ -42,9 +44,9 @@ pub trait HashToCurve, C: CurveAffine + 'static, + hasher: impl MessageHasher, ) -> Box C::Projective + '_> { - let domain_separation_tag = format!( + let domain_separation_tag: String = format!( "{}-{}_{}_{}_RO_", domain_prefix, C::CURVE_ID, @@ -53,7 +55,8 @@ pub trait HashToCurve, C: CurveAffine, C: CurveAffine + 'static, + hasher: impl MessageHasher, ) -> Box C::Projective + '_> { - let domain_separation_tag = format!( + let domain_separation_tag: String = format!( "{}-{}_{}_{}_NU_", domain_prefix, C::CURVE_ID, @@ -82,7 +85,8 @@ pub trait HashToCurve, C: CurveAffine, C: CurveAffine { +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 give `count` - /// field elements. - fn hash_to_field(&self, message: &[u8], domain_separation_tag: &[u8], count: usize) -> Vec; + /// 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 @@ -113,7 +117,7 @@ impl MessageHasher for Shake128 { "XOF:SHAKE128" } - fn hash_to_field(&self, message: &[u8], domain_separation_tag: &[u8], count: usize) -> Vec { + 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); @@ -121,7 +125,7 @@ impl MessageHasher for Shake128 { // . const CHUNKLEN: usize = 64; - let outlen = count * CHUNKLEN; + let outlen = buf.len() * CHUNKLEN; let mut outlen_enc = vec![]; outlen_enc.write_u32::(outlen as u32).unwrap(); @@ -131,15 +135,16 @@ impl MessageHasher for Shake128 { xof.update([domain_separation_tag.len() as u8]); xof.update(domain_separation_tag); - xof.finalize_boxed(outlen) + for (big, buf) in xof + .finalize_boxed(outlen) .chunks(CHUNKLEN) - .map(|big| { - let mut little = [0u8; CHUNKLEN]; - little.copy_from_slice(big); - little.reverse(); - F::from_bytes_wide(&little) - }) - .collect() + .zip(buf.iter_mut()) + { + let mut little = [0u8; CHUNKLEN]; + little.copy_from_slice(big); + little.reverse(); + *buf = F::from_bytes_wide(&little); + } } } @@ -148,8 +153,8 @@ impl MessageHasher for Shake128 { #[derive(Debug)] pub struct SimplifiedSWUWithDegree3Isogeny< F: FieldExt, - I: CurveAffine, C: CurveAffine, + I: CurveAffine, > { /// `Z` parameter (ξ in [WB2019](https://eprint.iacr.org/2019/403)). pub z: F, @@ -166,12 +171,12 @@ pub struct SimplifiedSWUWithDegree3Isogeny< /// Constants for the isogeny. pub isogeny_constants: [F; 13], - marker_curve: PhantomData, - marker_iso: PhantomData, + _marker_c: PhantomData, + _marker_i: PhantomData, } -impl, C: CurveAffine> - SimplifiedSWUWithDegree3Isogeny +impl, I: CurveAffine> + SimplifiedSWUWithDegree3Isogeny { /// Create a SimplifiedSWUWithDegree3Isogeny method for the given parameters. /// @@ -187,20 +192,22 @@ impl, C: CurveAffine> b_over_za: b * &((*z * a).invert().unwrap()), theta: (F::ROOT_OF_UNITY.invert().unwrap() * z).sqrt().unwrap(), isogeny_constants: *isogeny_constants, - marker_curve: PhantomData, - marker_iso: PhantomData, + _marker_c: PhantomData, + _marker_i: PhantomData, } } } -impl, C: CurveAffine> HashToCurve - for SimplifiedSWUWithDegree3Isogeny +impl, I: CurveAffine> HashToCurve + for SimplifiedSWUWithDegree3Isogeny { + type IsogenousCurve = I::Projective; + fn map_id(&self) -> &str { "SSWU" } - fn map_to_curve(&self, u: &F) -> I::Projective { + fn map_to_curve(&self, u: &F) -> Self::IsogenousCurve { // 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) diff --git a/src/pasta/pallas.rs b/src/pasta/pallas.rs index 0b7e249..612280a 100644 --- a/src/pasta/pallas.rs +++ b/src/pasta/pallas.rs @@ -25,7 +25,7 @@ pub type IsoAffine = IsoEpAffine; lazy_static! { /// The iso-Pallas -> Pallas degree 3 isogeny map. - pub static ref MAP: SimplifiedSWUWithDegree3Isogeny = { + pub static ref MAP: SimplifiedSWUWithDegree3Isogeny = { let isogeny_constants: [Base; 13] = [ Base::from_raw([ 0x775f6034aaaaaaab, diff --git a/src/pasta/vesta.rs b/src/pasta/vesta.rs index c867fee..ec7a111 100644 --- a/src/pasta/vesta.rs +++ b/src/pasta/vesta.rs @@ -25,7 +25,7 @@ pub type IsoAffine = IsoEqAffine; lazy_static! { /// The iso-Vesta -> Vesta degree 3 isogeny map. - pub static ref MAP: SimplifiedSWUWithDegree3Isogeny = { + pub static ref MAP: SimplifiedSWUWithDegree3Isogeny = { let isogeny_constants: [Base; 13] = [ Base::from_raw([ 0x43cd42c800000001, From 5b33ff9cabf5f92fcf18bb590e4ae9d6425a1fd8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 2 Feb 2021 10:24:33 -0700 Subject: [PATCH 04/28] 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); From b134a73ef58b331e669991254f1aa961333d40be Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 2 Feb 2021 11:04:17 -0700 Subject: [PATCH 05/28] Hardcode isogeny constants and constants for hash to curve. --- src/arithmetic/hashtocurve.rs | 19 +-- src/pasta/curves.rs | 236 ++++++++++++++++++++++++++++++++++ src/pasta/pallas.rs | 94 ++------------ src/pasta/vesta.rs | 94 ++------------ 4 files changed, 265 insertions(+), 178 deletions(-) diff --git a/src/arithmetic/hashtocurve.rs b/src/arithmetic/hashtocurve.rs index 20f042f..0b19376 100644 --- a/src/arithmetic/hashtocurve.rs +++ b/src/arithmetic/hashtocurve.rs @@ -42,15 +42,18 @@ impl, I: CurveAffine> /// /// # Panics /// Panics if z is square. - pub fn new(z: &F, isogeny_constants: [F; 13]) -> Self { - let a = I::a(); - let b = I::b(); - + 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) * &(a.invert().unwrap()), - b_over_za: b * &((*z * a).invert().unwrap()), - theta: (F::ROOT_OF_UNITY.invert().unwrap() * z).sqrt().unwrap(), + z: z, + minus_b_over_a, + b_over_za, + theta, isogeny_constants: isogeny_constants, _marker_c: PhantomData, _marker_i: PhantomData, diff --git a/src/pasta/curves.rs b/src/pasta/curves.rs index 57ee84c..c5a836c 100644 --- a/src/pasta/curves.rs +++ b/src/pasta/curves.rs @@ -858,3 +858,239 @@ new_curve_impl!( [1265, 0, 0, 0], general ); + +impl IsoEpAffine { + /// Constants used for computing the isogeny from IsoEp to Ep. + pub const ISOGENY_CONSTANTS: [Fp; 13] = [ + Fp::from_raw([ + 0x775f6034aaaaaaab, + 0x4081775473d8375b, + 0xe38e38e38e38e38e, + 0x0e38e38e38e38e38, + ]), + Fp::from_raw([ + 0x8cf863b02814fb76, + 0x0f93b82ee4b99495, + 0x267c7ffa51cf412a, + 0x3509afd51872d88e, + ]), + Fp::from_raw([ + 0x0eb64faef37ea4f7, + 0x380af066cfeb6d69, + 0x98c7d7ac3d98fd13, + 0x17329b9ec5253753, + ]), + Fp::from_raw([ + 0xeebec06955555580, + 0x8102eea8e7b06eb6, + 0xc71c71c71c71c71c, + 0x1c71c71c71c71c71, + ]), + Fp::from_raw([ + 0xc47f2ab668bcd71f, + 0x9c434ac1c96b6980, + 0x5a607fcce0494a79, + 0x1d572e7ddc099cff, + ]), + Fp::from_raw([ + 0x2aa3af1eae5b6604, + 0xb4abf9fb9a1fc81c, + 0x1d13bf2a7f22b105, + 0x325669becaecd5d1, + ]), + Fp::from_raw([ + 0x5ad985b5e38e38e4, + 0x7642b01ad461bad2, + 0x4bda12f684bda12f, + 0x1a12f684bda12f68, + ]), + Fp::from_raw([ + 0xc67c31d8140a7dbb, + 0x07c9dc17725cca4a, + 0x133e3ffd28e7a095, + 0x1a84d7ea8c396c47, + ]), + Fp::from_raw([ + 0x02e2be87d225b234, + 0x1765e924f7459378, + 0x303216cce1db9ff1, + 0x3fb98ff0d2ddcadd, + ]), + Fp::from_raw([ + 0x93e53ab371c71c4f, + 0x0ac03e8e134eb3e4, + 0x7b425ed097b425ed, + 0x025ed097b425ed09, + ]), + Fp::from_raw([ + 0x5a28279b1d1b42ae, + 0x5941a3a4a97aa1b3, + 0x0790bfb3506defb6, + 0x0c02c5bcca0e6b7f, + ]), + Fp::from_raw([ + 0x4d90ab820b12320a, + 0xd976bbfabbc5661d, + 0x573b3d7f7d681310, + 0x17033d3c60c68173, + ]), + Fp::from_raw([ + 0x992d30ecfffffde5, + 0x224698fc094cf91b, + 0x0000000000000000, + 0x4000000000000000, + ]), + ]; + + /// Z = -13 + pub const Z: Fp = Fp::from_raw([ + 0x992d30ecfffffff4, + 0x224698fc094cf91b, + 0x0000000000000000, + 0x4000000000000000, + ]); + + /// `(-b) * &(a.invert().unwrap())` where a and b correspond with curve + /// constants for the isogenous curve. + pub const MINUS_B_OVER_A: Fp = Fp::from_raw([ + 0x1c3006d89470d7f8, + 0x7612d2d7211b7b10, + 0xd97cab452a13c1eb, + 0x3d115d87af7b3324, + ]); + + /// `b * &((*z * a).invert().unwrap())` where a and b correspond with curve + /// constants for the isogenous curve + pub const B_OVER_ZA: Fp = Fp::from_raw([ + 0xaf333253bca63800, + 0xf6ca6e5ce0e2b674, + 0xe9585bf1a0c67160, + 0x2c150731d26bf03d, + ]); + + /// `(F::ROOT_OF_UNITY.invert().unwrap() * z).sqrt().unwrap()` + pub const THETA: Fp = Fp::from_raw([ + 0xca330bcc09ac318e, + 0x51f64fc4dc888857, + 0x4647aef782d5cdc8, + 0x0f7bdb65814179b4, + ]); +} + +impl IsoEqAffine { + /// Constants used for computing the isogeny from IsoEq to Eq. + pub const ISOGENY_CONSTANTS: [Fq; 13] = [ + Fq::from_raw([ + 0x43cd42c800000001, + 0x0205dd51cfa0961a, + 0x8e38e38e38e38e39, + 0x38e38e38e38e38e3, + ]), + Fq::from_raw([ + 0x8b95c6aaf703bcc5, + 0x216b8861ec72bd5d, + 0xacecf10f5f7c09a2, + 0x1d935247b4473d17, + ]), + Fq::from_raw([ + 0xaeac67bbeb586a3d, + 0xd59d03d23b39cb11, + 0xed7ee4a9cdf78f8f, + 0x18760c7f7a9ad20d, + ]), + Fq::from_raw([ + 0xfb539a6f0000002b, + 0xe1c521a795ac8356, + 0x1c71c71c71c71c71, + 0x31c71c71c71c71c7, + ]), + Fq::from_raw([ + 0xb7284f7eaf21a2e9, + 0xa3ad678129b604d3, + 0x1454798a5b5c56b2, + 0x0a2de485568125d5, + ]), + Fq::from_raw([ + 0xf169c187d2533465, + 0x30cd6d53df49d235, + 0x0c621de8b91c242a, + 0x14735171ee542778, + ]), + Fq::from_raw([ + 0x6bef1642aaaaaaab, + 0x5601f4709a8adcb3, + 0xda12f684bda12f68, + 0x12f684bda12f684b, + ]), + Fq::from_raw([ + 0x8bee58e5fb81de63, + 0x21d910aefb03b31d, + 0xd6767887afbe04d1, + 0x2ec9a923da239e8b, + ]), + Fq::from_raw([ + 0x4986913ab4443034, + 0x97a3ca5c24e9ea63, + 0x66d1466e9de10e64, + 0x19b0d87e16e25788, + ]), + Fq::from_raw([ + 0x8f64842c55555533, + 0x8bc32d36fb21a6a3, + 0x425ed097b425ed09, + 0x1ed097b425ed097b, + ]), + Fq::from_raw([ + 0x58dfecce86b2745e, + 0x06a767bfc35b5bac, + 0x9e7eb64f890a820c, + 0x2f44d6c801c1b8bf, + ]), + Fq::from_raw([ + 0xd43d449776f99d2f, + 0x926847fb9ddd76a1, + 0x252659ba2b546c7e, + 0x3d59f455cafc7668, + ]), + Fq::from_raw([ + 0x8c46eb20fffffde5, + 0x224698fc0994a8dd, + 0x0000000000000000, + 0x4000000000000000, + ]), + ]; + + /// Z = -13 + pub const Z: Fq = Fq::from_raw([ + 0x8c46eb20fffffff4, + 0x224698fc0994a8dd, + 0x0000000000000000, + 0x4000000000000000, + ]); + + /// `(-b) * &(a.invert().unwrap())` where a and b correspond with curve + /// constants for the isogenous curve. + pub const MINUS_B_OVER_A: Fq = Fq::from_raw([ + 0x6dab74e8ef9dc7d3, + 0xbb4a015f2450502c, + 0x5385df3f6207bb22, + 0x23447efd3c451b98, + ]); + + /// `b * &((*z * a).invert().unwrap())` where a and b correspond with curve + /// constants for the isogenous curve + pub const B_OVER_ZA: Fq = Fq::from_raw([ + 0xb66e73e89c4736c2, + 0x6fa1dc53f442887a, + 0xcb59112c429e2216, + 0x252ca74e8e7b7846, + ]); + + /// `(F::ROOT_OF_UNITY.invert().unwrap() * z).sqrt().unwrap()` + pub const THETA: Fq = Fq::from_raw([ + 0x632cae9872df1b5d, + 0x38578ccadf03ac27, + 0x53c3808d9e2f2357, + 0x2b3483a1ee9a382f, + ]); +} diff --git a/src/pasta/pallas.rs b/src/pasta/pallas.rs index 8df350f..2361e50 100644 --- a/src/pasta/pallas.rs +++ b/src/pasta/pallas.rs @@ -3,7 +3,7 @@ use lazy_static::lazy_static; use super::{Ep, EpAffine, Fp, Fq, IsoEp, IsoEpAffine}; -use crate::arithmetic::{FieldExt, SimplifiedSWUWithDegree3Isogeny}; +use crate::arithmetic::SimplifiedSWUWithDegree3Isogeny; /// The base field of the Pallas and iso-Pallas curves. pub type Base = Fp; @@ -26,89 +26,13 @@ pub type IsoAffine = IsoEpAffine; lazy_static! { /// The iso-Pallas -> Pallas degree 3 isogeny map. pub static ref MAP: SimplifiedSWUWithDegree3Isogeny = { - let isogeny_constants: [Base; 13] = [ - Base::from_raw([ - 0x775f6034aaaaaaab, - 0x4081775473d8375b, - 0xe38e38e38e38e38e, - 0x0e38e38e38e38e38, - ]), - Base::from_raw([ - 0x8cf863b02814fb76, - 0x0f93b82ee4b99495, - 0x267c7ffa51cf412a, - 0x3509afd51872d88e, - ]), - Base::from_raw([ - 0x0eb64faef37ea4f7, - 0x380af066cfeb6d69, - 0x98c7d7ac3d98fd13, - 0x17329b9ec5253753, - ]), - Base::from_raw([ - 0xeebec06955555580, - 0x8102eea8e7b06eb6, - 0xc71c71c71c71c71c, - 0x1c71c71c71c71c71, - ]), - Base::from_raw([ - 0xc47f2ab668bcd71f, - 0x9c434ac1c96b6980, - 0x5a607fcce0494a79, - 0x1d572e7ddc099cff, - ]), - Base::from_raw([ - 0x2aa3af1eae5b6604, - 0xb4abf9fb9a1fc81c, - 0x1d13bf2a7f22b105, - 0x325669becaecd5d1, - ]), - Base::from_raw([ - 0x5ad985b5e38e38e4, - 0x7642b01ad461bad2, - 0x4bda12f684bda12f, - 0x1a12f684bda12f68, - ]), - Base::from_raw([ - 0xc67c31d8140a7dbb, - 0x07c9dc17725cca4a, - 0x133e3ffd28e7a095, - 0x1a84d7ea8c396c47, - ]), - Base::from_raw([ - 0x02e2be87d225b234, - 0x1765e924f7459378, - 0x303216cce1db9ff1, - 0x3fb98ff0d2ddcadd, - ]), - Base::from_raw([ - 0x93e53ab371c71c4f, - 0x0ac03e8e134eb3e4, - 0x7b425ed097b425ed, - 0x025ed097b425ed09, - ]), - Base::from_raw([ - 0x5a28279b1d1b42ae, - 0x5941a3a4a97aa1b3, - 0x0790bfb3506defb6, - 0x0c02c5bcca0e6b7f, - ]), - Base::from_raw([ - 0x4d90ab820b12320a, - 0xd976bbfabbc5661d, - 0x573b3d7f7d681310, - 0x17033d3c60c68173, - ]), - Base::from_raw([ - 0x992d30ecfffffde5, - 0x224698fc094cf91b, - 0x0000000000000000, - 0x4000000000000000, - ]), - ]; - - let z = -Base::from_u64(13); - SimplifiedSWUWithDegree3Isogeny::new(&z, isogeny_constants) + SimplifiedSWUWithDegree3Isogeny::new( + IsoAffine::Z, + IsoAffine::ISOGENY_CONSTANTS, + IsoAffine::MINUS_B_OVER_A, + IsoAffine::B_OVER_ZA, + IsoAffine::THETA + ) }; } @@ -154,7 +78,7 @@ fn test_iso_map() { #[test] fn test_map_to_curve_pallas() { - use crate::arithmetic::{Curve, CurveAffine}; + use crate::arithmetic::{Curve, CurveAffine, FieldExt}; use std::collections::HashSet; assert!(MAP.minus_b_over_a * IsoAffine::a() == -IsoAffine::b()); diff --git a/src/pasta/vesta.rs b/src/pasta/vesta.rs index e0e5967..a2ffc8a 100644 --- a/src/pasta/vesta.rs +++ b/src/pasta/vesta.rs @@ -3,7 +3,7 @@ use lazy_static::lazy_static; use super::{Eq, EqAffine, Fp, Fq, IsoEq, IsoEqAffine}; -use crate::arithmetic::{FieldExt, SimplifiedSWUWithDegree3Isogeny}; +use crate::arithmetic::SimplifiedSWUWithDegree3Isogeny; /// The base field of the Vesta and iso-Vesta curves. pub type Base = Fq; @@ -26,95 +26,19 @@ pub type IsoAffine = IsoEqAffine; lazy_static! { /// The iso-Vesta -> Vesta degree 3 isogeny map. pub static ref MAP: SimplifiedSWUWithDegree3Isogeny = { - let isogeny_constants: [Base; 13] = [ - Base::from_raw([ - 0x43cd42c800000001, - 0x0205dd51cfa0961a, - 0x8e38e38e38e38e39, - 0x38e38e38e38e38e3, - ]), - Base::from_raw([ - 0x8b95c6aaf703bcc5, - 0x216b8861ec72bd5d, - 0xacecf10f5f7c09a2, - 0x1d935247b4473d17, - ]), - Base::from_raw([ - 0xaeac67bbeb586a3d, - 0xd59d03d23b39cb11, - 0xed7ee4a9cdf78f8f, - 0x18760c7f7a9ad20d, - ]), - Base::from_raw([ - 0xfb539a6f0000002b, - 0xe1c521a795ac8356, - 0x1c71c71c71c71c71, - 0x31c71c71c71c71c7, - ]), - Base::from_raw([ - 0xb7284f7eaf21a2e9, - 0xa3ad678129b604d3, - 0x1454798a5b5c56b2, - 0x0a2de485568125d5, - ]), - Base::from_raw([ - 0xf169c187d2533465, - 0x30cd6d53df49d235, - 0x0c621de8b91c242a, - 0x14735171ee542778, - ]), - Base::from_raw([ - 0x6bef1642aaaaaaab, - 0x5601f4709a8adcb3, - 0xda12f684bda12f68, - 0x12f684bda12f684b, - ]), - Base::from_raw([ - 0x8bee58e5fb81de63, - 0x21d910aefb03b31d, - 0xd6767887afbe04d1, - 0x2ec9a923da239e8b, - ]), - Base::from_raw([ - 0x4986913ab4443034, - 0x97a3ca5c24e9ea63, - 0x66d1466e9de10e64, - 0x19b0d87e16e25788, - ]), - Base::from_raw([ - 0x8f64842c55555533, - 0x8bc32d36fb21a6a3, - 0x425ed097b425ed09, - 0x1ed097b425ed097b, - ]), - Base::from_raw([ - 0x58dfecce86b2745e, - 0x06a767bfc35b5bac, - 0x9e7eb64f890a820c, - 0x2f44d6c801c1b8bf, - ]), - Base::from_raw([ - 0xd43d449776f99d2f, - 0x926847fb9ddd76a1, - 0x252659ba2b546c7e, - 0x3d59f455cafc7668, - ]), - Base::from_raw([ - 0x8c46eb20fffffde5, - 0x224698fc0994a8dd, - 0x0000000000000000, - 0x4000000000000000, - ]), - ]; - - let z = -Base::from_u64(13); - SimplifiedSWUWithDegree3Isogeny::new(&z, isogeny_constants) + SimplifiedSWUWithDegree3Isogeny::new( + IsoAffine::Z, + IsoAffine::ISOGENY_CONSTANTS, + IsoAffine::MINUS_B_OVER_A, + IsoAffine::B_OVER_ZA, + IsoAffine::THETA + ) }; } #[test] fn test_map_to_curve_vesta() { - use crate::arithmetic::{Curve, CurveAffine}; + use crate::arithmetic::{Curve, CurveAffine, FieldExt}; use std::collections::HashSet; assert!(MAP.minus_b_over_a * IsoAffine::a() == -IsoAffine::b()); From 68a7a19d3b8494f32305619be13892dd19794ed5 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 2 Feb 2021 11:13:32 -0700 Subject: [PATCH 06/28] Move hashtocurve module into pasta module. --- src/arithmetic.rs | 4 +--- src/pasta.rs | 2 ++ src/{arithmetic => pasta}/hashtocurve.rs | 2 +- src/pasta/pallas.rs | 2 +- src/pasta/vesta.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename src/{arithmetic => pasta}/hashtocurve.rs (99%) diff --git a/src/arithmetic.rs b/src/arithmetic.rs index b6dc672..51a70e8 100644 --- a/src/arithmetic.rs +++ b/src/arithmetic.rs @@ -2,15 +2,13 @@ //! field and polynomial arithmetic. use crossbeam_utils::thread; -use ff::Field; +pub use ff::Field; mod curves; mod fields; -mod hashtocurve; pub use curves::*; pub use fields::*; -pub use hashtocurve::*; /// This represents an element of a group with basic operations that can be /// performed. This allows an FFT implementation (for example) to operate diff --git a/src/pasta.rs b/src/pasta.rs index c851450..a8313e3 100644 --- a/src/pasta.rs +++ b/src/pasta.rs @@ -6,11 +6,13 @@ mod macros; mod curves; mod fields; +mod hashtocurve; pub mod pallas; pub mod vesta; pub use curves::*; pub use fields::*; +use hashtocurve::*; #[test] fn test_endo_consistency() { diff --git a/src/arithmetic/hashtocurve.rs b/src/pasta/hashtocurve.rs similarity index 99% rename from src/arithmetic/hashtocurve.rs rename to src/pasta/hashtocurve.rs index 0b19376..32305b1 100644 --- a/src/arithmetic/hashtocurve.rs +++ b/src/pasta/hashtocurve.rs @@ -6,7 +6,7 @@ use core::fmt::Debug; use core::marker::PhantomData; use subtle::ConstantTimeEq; -use super::{Curve, CurveAffine, Field, FieldExt}; +use crate::arithmetic::{Curve, CurveAffine, Field, FieldExt}; /// Implementation of the "simplified SWU" hashing to short Weierstrass curves /// with a = 0. Internally uses SHAKE128. diff --git a/src/pasta/pallas.rs b/src/pasta/pallas.rs index 2361e50..5e0da73 100644 --- a/src/pasta/pallas.rs +++ b/src/pasta/pallas.rs @@ -2,8 +2,8 @@ use lazy_static::lazy_static; +use super::SimplifiedSWUWithDegree3Isogeny; use super::{Ep, EpAffine, Fp, Fq, IsoEp, IsoEpAffine}; -use crate::arithmetic::SimplifiedSWUWithDegree3Isogeny; /// The base field of the Pallas and iso-Pallas curves. pub type Base = Fp; diff --git a/src/pasta/vesta.rs b/src/pasta/vesta.rs index a2ffc8a..8dac763 100644 --- a/src/pasta/vesta.rs +++ b/src/pasta/vesta.rs @@ -2,8 +2,8 @@ use lazy_static::lazy_static; +use super::SimplifiedSWUWithDegree3Isogeny; use super::{Eq, EqAffine, Fp, Fq, IsoEq, IsoEqAffine}; -use crate::arithmetic::SimplifiedSWUWithDegree3Isogeny; /// The base field of the Vesta and iso-Vesta curves. pub type Base = Fq; From 83e2656c3efb1f6ee13a38792f5dad19338ac8ac Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 2 Feb 2021 11:36:06 -0700 Subject: [PATCH 07/28] Introduce Curve::hasher abstraction. --- src/arithmetic/curves.rs | 6 +++++ src/pasta/curves.rs | 52 +++++++++++++++++++++++++++++++++++----- src/pasta/pallas.rs | 12 +++++----- src/pasta/vesta.rs | 12 +++++----- 4 files changed, 64 insertions(+), 18 deletions(-) diff --git a/src/arithmetic/curves.rs b/src/arithmetic/curves.rs index e23151f..1d946d2 100644 --- a/src/arithmetic/curves.rs +++ b/src/arithmetic/curves.rs @@ -79,6 +79,12 @@ pub trait Curve: /// Return the Jacobian coordinates of this point. fn jacobian_coordinates(&self) -> (Self::Base, Self::Base, Self::Base); + /// Requests a hasher that accepts messages and returns near-uniformly + /// distributed elements in the group, given domain prefix `hasher`. + /// + /// This method is suitable for use as a random oracle. + fn hasher(domain_prefix: &str) -> Box Self + 'static>; + /// Returns whether or not this element is on the curve; should /// always be true unless an "unchecked" API was used. fn is_on_curve(&self) -> Choice; diff --git a/src/pasta/curves.rs b/src/pasta/curves.rs index c5a836c..8404faf 100644 --- a/src/pasta/curves.rs +++ b/src/pasta/curves.rs @@ -12,7 +12,7 @@ use super::{Fp, Fq}; use crate::arithmetic::{Curve, CurveAffine, FieldExt, Group}; macro_rules! new_curve_impl { - ($name:ident, $name_affine:ident, $base:ident, $scalar:ident, $blake2b_personalization:literal, + ($name:ident, $name_affine:ident, $iso_affine:ident, $base:ident, $scalar:ident, $blake2b_personalization:literal, $curve_id:literal, $a_raw:expr, $b_raw:expr, $curve_type:ident) => { /// Represents a point in the projective coordinate space. #[derive(Copy, Clone, Debug)] @@ -56,7 +56,7 @@ macro_rules! new_curve_impl { type Scalar = $scalar; type Base = $base; - impl_projective_curve_specific!($name, $base, $curve_type); + impl_projective_curve_specific!($name, $name_affine, $iso_affine, $base, $curve_type); fn zero() -> Self { Self { @@ -685,7 +685,38 @@ macro_rules! new_curve_impl { } macro_rules! impl_projective_curve_specific { - ($name:ident, $base:ident, special_a0_b5) => { + ($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, + ); + + let domain_separation_tag: String = format!( + "{}-{}_{}_{}_RO_", + domain_prefix, + $name_affine::CURVE_ID, + "XOF:SHAKE128", + "SSWU" + ); + + 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, + ); + swu.field_elements_to_curve(&us[0], &us[1]) + }) + } + fn one() -> Self { // NOTE: This is specific to b = 5 @@ -740,7 +771,12 @@ macro_rules! impl_projective_curve_specific { $name::conditional_select(&tmp, &$name::zero(), self.is_zero()) } }; - ($name:ident, $base:ident, general) => { + ($name:ident, $name_affine:ident, $iso_affine:ident, $base:ident, general) => { + /// Unimplemented: hashing to this curve is not supported + fn hasher(_domain_prefix: &str) -> Box Self + 'static> { + unimplemented!() + } + /// Unimplemented: there is no standard generator for this curve. fn one() -> Self { unimplemented!() @@ -807,6 +843,7 @@ macro_rules! impl_affine_curve_specific { new_curve_impl!( Ep, EpAffine, + IsoEpAffine, Fp, Fq, b"halo2_____pallas", @@ -818,6 +855,7 @@ new_curve_impl!( new_curve_impl!( Eq, EqAffine, + IsoEqAffine, Fq, Fp, b"halo2______vesta", @@ -829,6 +867,7 @@ new_curve_impl!( new_curve_impl!( IsoEp, IsoEpAffine, + EpAffine, Fp, Fq, b"halo2_iso_pallas", @@ -845,6 +884,7 @@ new_curve_impl!( new_curve_impl!( IsoEq, IsoEqAffine, + EqAffine, Fq, Fp, b"halo2__iso_vesta", @@ -859,7 +899,7 @@ new_curve_impl!( general ); -impl IsoEpAffine { +impl Ep { /// Constants used for computing the isogeny from IsoEp to Ep. pub const ISOGENY_CONSTANTS: [Fp; 13] = [ Fp::from_raw([ @@ -977,7 +1017,7 @@ impl IsoEpAffine { ]); } -impl IsoEqAffine { +impl Eq { /// Constants used for computing the isogeny from IsoEq to Eq. pub const ISOGENY_CONSTANTS: [Fq; 13] = [ Fq::from_raw([ diff --git a/src/pasta/pallas.rs b/src/pasta/pallas.rs index 5e0da73..a52dd37 100644 --- a/src/pasta/pallas.rs +++ b/src/pasta/pallas.rs @@ -27,11 +27,11 @@ lazy_static! { /// The iso-Pallas -> Pallas degree 3 isogeny map. pub static ref MAP: SimplifiedSWUWithDegree3Isogeny = { SimplifiedSWUWithDegree3Isogeny::new( - IsoAffine::Z, - IsoAffine::ISOGENY_CONSTANTS, - IsoAffine::MINUS_B_OVER_A, - IsoAffine::B_OVER_ZA, - IsoAffine::THETA + Point::Z, + Point::ISOGENY_CONSTANTS, + Point::MINUS_B_OVER_A, + Point::B_OVER_ZA, + Point::THETA ) }; } @@ -90,7 +90,7 @@ fn test_map_to_curve_pallas() { .collect(); assert!(set.len() == 10000); - let hash = MAP.hash_to_curve("z.cash:test"); + let hash = Point::hasher("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 8dac763..4003542 100644 --- a/src/pasta/vesta.rs +++ b/src/pasta/vesta.rs @@ -27,11 +27,11 @@ lazy_static! { /// The iso-Vesta -> Vesta degree 3 isogeny map. pub static ref MAP: SimplifiedSWUWithDegree3Isogeny = { SimplifiedSWUWithDegree3Isogeny::new( - IsoAffine::Z, - IsoAffine::ISOGENY_CONSTANTS, - IsoAffine::MINUS_B_OVER_A, - IsoAffine::B_OVER_ZA, - IsoAffine::THETA + Point::Z, + Point::ISOGENY_CONSTANTS, + Point::MINUS_B_OVER_A, + Point::B_OVER_ZA, + Point::THETA ) }; } @@ -50,7 +50,7 @@ fn test_map_to_curve_vesta() { .collect(); assert!(set.len() == 10000); - let hash = MAP.hash_to_curve("z.cash:test"); + let hash = Point::hasher("z.cash:test"); let p: Point = hash(b"hello"); let (x, y, z) = p.jacobian_coordinates(); println!("{:?}", p); From 783e602e851b2711e937b2c392f318a14e783e2b Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 2 Feb 2021 11:53:30 -0700 Subject: [PATCH 08/28] Remove `SimplifiedSWUWithDegree3Isogeny` structure because state is no longer necessary. --- src/pasta.rs | 1 - src/pasta/curves.rs | 34 ++-- src/pasta/hashtocurve.rs | 383 ++++++++++++++------------------------- src/pasta/pallas.rs | 68 +------ src/pasta/vesta.rs | 28 +-- 5 files changed, 156 insertions(+), 358 deletions(-) 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"); From c48229ce0fff1c62205ded7f1998980e330e95e4 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 2 Feb 2021 11:57:21 -0700 Subject: [PATCH 09/28] Remove dependency on byteorder crate --- Cargo.toml | 1 - src/pasta/hashtocurve.rs | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b45e69c..67b501d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,6 @@ blake2b_simd = "0.5" sha3 = "0.9.1" lazy_static = "1.4.0" static_assertions = "1.1.0" -byteorder = "1.4.2" # Temporary workaround for https://github.com/myrrlyn/funty/issues/3 funty = "=1.1.0" diff --git a/src/pasta/hashtocurve.rs b/src/pasta/hashtocurve.rs index bd073ae..2467e6f 100644 --- a/src/pasta/hashtocurve.rs +++ b/src/pasta/hashtocurve.rs @@ -1,7 +1,6 @@ //! This module implements "simplified SWU" hashing to short Weierstrass curves //! with a = 0. -use byteorder::{BigEndian, WriteBytesExt}; use subtle::ConstantTimeEq; use crate::arithmetic::{Curve, CurveAffine, FieldExt}; @@ -16,12 +15,10 @@ pub fn hash_to_field(message: &[u8], domain_separation_tag: &[u8], 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(&(outlen as u32).to_be_bytes()); xof.update([domain_separation_tag.len() as u8]); xof.update(domain_separation_tag); From f6f008f90511e8d9b8c560c3fceac23f4ce23d01 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 2 Feb 2021 12:30:02 -0700 Subject: [PATCH 10/28] Remove `MINUS_B_OVER_A` constant. --- src/pasta/curves.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/pasta/curves.rs b/src/pasta/curves.rs index a362306..b6d5f64 100644 --- a/src/pasta/curves.rs +++ b/src/pasta/curves.rs @@ -994,15 +994,6 @@ impl Ep { 0x4000000000000000, ]); - /// `(-b) * &(a.invert().unwrap())` where a and b correspond with curve - /// constants for the isogenous curve. - pub const MINUS_B_OVER_A: Fp = Fp::from_raw([ - 0x1c3006d89470d7f8, - 0x7612d2d7211b7b10, - 0xd97cab452a13c1eb, - 0x3d115d87af7b3324, - ]); - /// `b * &((*z * a).invert().unwrap())` where a and b correspond with curve /// constants for the isogenous curve pub const B_OVER_ZA: Fp = Fp::from_raw([ @@ -1112,15 +1103,6 @@ impl Eq { 0x4000000000000000, ]); - /// `(-b) * &(a.invert().unwrap())` where a and b correspond with curve - /// constants for the isogenous curve. - pub const MINUS_B_OVER_A: Fq = Fq::from_raw([ - 0x6dab74e8ef9dc7d3, - 0xbb4a015f2450502c, - 0x5385df3f6207bb22, - 0x23447efd3c451b98, - ]); - /// `b * &((*z * a).invert().unwrap())` where a and b correspond with curve /// constants for the isogenous curve pub const B_OVER_ZA: Fq = Fq::from_raw([ From dc069dff3165649e8cf301d0baf07ba601133362 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 2 Feb 2021 12:33:41 -0700 Subject: [PATCH 11/28] Rename hasher to hash_to_curve. --- src/arithmetic/curves.rs | 2 +- src/pasta/curves.rs | 4 ++-- src/pasta/pallas.rs | 2 +- src/pasta/vesta.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/arithmetic/curves.rs b/src/arithmetic/curves.rs index 1d946d2..07d5320 100644 --- a/src/arithmetic/curves.rs +++ b/src/arithmetic/curves.rs @@ -83,7 +83,7 @@ pub trait Curve: /// distributed elements in the group, given domain prefix `hasher`. /// /// This method is suitable for use as a random oracle. - fn hasher(domain_prefix: &str) -> Box Self + 'static>; + fn hash_to_curve(domain_prefix: &str) -> Box Self + 'static>; /// Returns whether or not this element is on the curve; should /// always be true unless an "unchecked" API was used. diff --git a/src/pasta/curves.rs b/src/pasta/curves.rs index b6d5f64..c41610d 100644 --- a/src/pasta/curves.rs +++ b/src/pasta/curves.rs @@ -686,7 +686,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> { + fn hash_to_curve(domain_prefix: &str) -> Box Self + 'static> { use super::hashtocurve; let domain_separation_tag: String = format!( @@ -777,7 +777,7 @@ macro_rules! impl_projective_curve_specific { }; ($name:ident, $name_affine:ident, $iso_affine:ident, $base:ident, general) => { /// Unimplemented: hashing to this curve is not supported - fn hasher(_domain_prefix: &str) -> Box Self + 'static> { + fn hash_to_curve(_domain_prefix: &str) -> Box Self + 'static> { unimplemented!() } diff --git a/src/pasta/pallas.rs b/src/pasta/pallas.rs index b5a451a..7cb478a 100644 --- a/src/pasta/pallas.rs +++ b/src/pasta/pallas.rs @@ -24,7 +24,7 @@ pub type IsoAffine = IsoEpAffine; fn test_map_to_curve_pallas() { use crate::arithmetic::Curve; - let hash = Point::hasher("z.cash:test"); + let hash = Point::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 fa0c50b..05db6f1 100644 --- a/src/pasta/vesta.rs +++ b/src/pasta/vesta.rs @@ -24,7 +24,7 @@ pub type IsoAffine = IsoEqAffine; fn test_map_to_curve_vesta() { use crate::arithmetic::Curve; - let hash = Point::hasher("z.cash:test"); + let hash = Point::hash_to_curve("z.cash:test"); let p: Point = hash(b"hello"); let (x, y, z) = p.jacobian_coordinates(); println!("{:?}", p); From b488355e1373ea3027c12f58b7454d79d7e0d485 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 2 Feb 2021 12:39:58 -0700 Subject: [PATCH 12/28] Add example to hash_to_curve doc comment. --- src/arithmetic/curves.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/arithmetic/curves.rs b/src/arithmetic/curves.rs index 07d5320..35dce17 100644 --- a/src/arithmetic/curves.rs +++ b/src/arithmetic/curves.rs @@ -80,9 +80,21 @@ pub trait Curve: fn jacobian_coordinates(&self) -> (Self::Base, Self::Base, Self::Base); /// Requests a hasher that accepts messages and returns near-uniformly - /// distributed elements in the group, given domain prefix `hasher`. + /// distributed elements in the group, given domain prefix `domain_prefix`. /// /// This method is suitable for use as a random oracle. + /// + /// # Example + /// + /// ``` + /// use halo2::arithmetic::{Curve, CurveAffine}; + /// fn pedersen_commitment(x: C::Scalar, r: C::Scalar) -> C { + /// let hasher = C::Projective::hash_to_curve("z.cash:example_pedersen_commitment"); + /// let g = hasher(b"g"); + /// let h = hasher(b"h"); + /// (g * x + h * r).to_affine() + /// } + /// ``` fn hash_to_curve(domain_prefix: &str) -> Box Self + 'static>; /// Returns whether or not this element is on the curve; should From d14d2314a188a0b3545ac30b354f8158fb2b0a18 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 2 Feb 2021 12:53:28 -0700 Subject: [PATCH 13/28] Remove isogenous curve from public API. --- src/pasta/curves.rs | 10 +++++++--- src/pasta/pallas.rs | 8 +------- src/pasta/vesta.rs | 8 +------- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/pasta/curves.rs b/src/pasta/curves.rs index c41610d..57e5c2c 100644 --- a/src/pasta/curves.rs +++ b/src/pasta/curves.rs @@ -12,11 +12,11 @@ use super::{Fp, Fq}; use crate::arithmetic::{Curve, CurveAffine, FieldExt, Group}; macro_rules! new_curve_impl { - ($name:ident, $name_affine:ident, $iso_affine:ident, $base:ident, $scalar:ident, $blake2b_personalization:literal, + (($($privacy:tt)*), $name:ident, $name_affine:ident, $iso_affine:ident, $base:ident, $scalar:ident, $blake2b_personalization:literal, $curve_id:literal, $a_raw:expr, $b_raw:expr, $curve_type:ident) => { /// Represents a point in the projective coordinate space. #[derive(Copy, Clone, Debug)] - pub struct $name { + $($privacy)* struct $name { x: $base, y: $base, z: $base, @@ -35,7 +35,7 @@ macro_rules! new_curve_impl { /// Represents a point in the affine coordinate space (or the point at /// infinity). #[derive(Copy, Clone)] - pub struct $name_affine { + $($privacy)* struct $name_affine { x: $base, y: $base, infinity: Choice, @@ -845,6 +845,7 @@ macro_rules! impl_affine_curve_specific { } new_curve_impl!( + (pub), Ep, EpAffine, IsoEpAffine, @@ -857,6 +858,7 @@ new_curve_impl!( special_a0_b5 ); new_curve_impl!( + (pub), Eq, EqAffine, IsoEqAffine, @@ -869,6 +871,7 @@ new_curve_impl!( special_a0_b5 ); new_curve_impl!( + (pub(crate)), IsoEp, IsoEpAffine, EpAffine, @@ -886,6 +889,7 @@ new_curve_impl!( general ); new_curve_impl!( + (pub(crate)), IsoEq, IsoEqAffine, EqAffine, diff --git a/src/pasta/pallas.rs b/src/pasta/pallas.rs index 7cb478a..4af4ae7 100644 --- a/src/pasta/pallas.rs +++ b/src/pasta/pallas.rs @@ -1,6 +1,6 @@ //! The Pallas and iso-Pallas elliptic curve groups. -use super::{Ep, EpAffine, Fp, Fq, IsoEp, IsoEpAffine}; +use super::{Ep, EpAffine, Fp, Fq}; /// The base field of the Pallas and iso-Pallas curves. pub type Base = Fp; @@ -14,12 +14,6 @@ pub type Point = Ep; /// A Pallas point in the affine coordinate space (or the point at infinity). pub type Affine = EpAffine; -/// An iso-Pallas point in the projective coordinate space. -pub type IsoPoint = IsoEp; - -/// A iso-Pallas point in the affine coordinate space (or the point at infinity). -pub type IsoAffine = IsoEpAffine; - #[test] fn test_map_to_curve_pallas() { use crate::arithmetic::Curve; diff --git a/src/pasta/vesta.rs b/src/pasta/vesta.rs index 05db6f1..a92956d 100644 --- a/src/pasta/vesta.rs +++ b/src/pasta/vesta.rs @@ -1,6 +1,6 @@ //! The Vesta and iso-Vesta elliptic curve groups. -use super::{Eq, EqAffine, Fp, Fq, IsoEq, IsoEqAffine}; +use super::{Eq, EqAffine, Fp, Fq}; /// The base field of the Vesta and iso-Vesta curves. pub type Base = Fq; @@ -14,12 +14,6 @@ pub type Point = Eq; /// A Vesta point in the affine coordinate space (or the point at infinity). pub type Affine = EqAffine; -/// An iso-Vesta point in the projective coordinate space. -pub type IsoPoint = IsoEq; - -/// A iso-Vesta point in the affine coordinate space (or the point at infinity). -pub type IsoAffine = IsoEqAffine; - #[test] fn test_map_to_curve_vesta() { use crate::arithmetic::Curve; From a757bc4e4398d9f69fa6c3227f11b93942f024f9 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 2 Feb 2021 14:26:32 -0700 Subject: [PATCH 14/28] Update hashtocurve benchmark --- benches/hashtocurve.rs | 30 +++--------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/benches/hashtocurve.rs b/benches/hashtocurve.rs index 0175b8c..cd3e987 100644 --- a/benches/hashtocurve.rs +++ b/benches/hashtocurve.rs @@ -2,46 +2,22 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use halo2::arithmetic::{HashToCurve, Shake128}; +use halo2::arithmetic::Curve; use halo2::pasta::{pallas, vesta}; fn criterion_benchmark(c: &mut Criterion) { bench_hash_to_curve(c); - bench_encode_to_curve(c); - bench_map_to_curve(c); } fn bench_hash_to_curve(c: &mut Criterion) { let mut group = c.benchmark_group("hash-to-curve"); - let hash_pallas = pallas::MAP.hash_to_curve("z.cash:test", Shake128::default()); + let hash_pallas = pallas::Point::hash_to_curve("z.cash:test"); group.bench_function("Pallas", |b| b.iter(|| hash_pallas(b"benchmark"))); - let hash_vesta = vesta::MAP.hash_to_curve("z.cash:test", Shake128::default()); + let hash_vesta = vesta::Point::hash_to_curve("z.cash:test"); group.bench_function("Vesta", |b| b.iter(|| hash_vesta(b"benchmark"))); } -fn bench_encode_to_curve(c: &mut Criterion) { - let mut group = c.benchmark_group("encode-to-curve"); - - let encode_pallas = pallas::MAP.encode_to_curve("z.cash:test", Shake128::default()); - group.bench_function("Pallas", |b| b.iter(|| encode_pallas(b"benchmark"))); - - let encode_vesta = vesta::MAP.encode_to_curve("z.cash:test", Shake128::default()); - group.bench_function("Vesta", |b| b.iter(|| encode_vesta(b"benchmark"))); -} - -fn bench_map_to_curve(c: &mut Criterion) { - let mut group = c.benchmark_group("map-to-curve"); - - let pallas_input = &pallas::Base::one(); - group.bench_function("Pallas", |b| { - b.iter(|| pallas::MAP.map_to_curve(pallas_input)) - }); - - let vesta_input = &vesta::Base::one(); - group.bench_function("Vesta", |b| b.iter(|| vesta::MAP.map_to_curve(vesta_input))); -} - criterion_group!(benches, criterion_benchmark); criterion_main!(benches); From c17cd408f1c85fcad0ca51d48be606eaa802b47c Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 2 Feb 2021 17:37:00 -0700 Subject: [PATCH 15/28] Fix point doubling on isogenous curve and add test for isogeny of identity. --- src/pasta/curves.rs | 15 +++++---- src/pasta/pallas.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 8 deletions(-) diff --git a/src/pasta/curves.rs b/src/pasta/curves.rs index 57e5c2c..94fcc32 100644 --- a/src/pasta/curves.rs +++ b/src/pasta/curves.rs @@ -800,15 +800,14 @@ macro_rules! impl_projective_curve_specific { let yy = self.y.square(); let a = yy.square(); let zz = self.z.square(); - let s = (self.x + yy).square() - xx - a; - let s = s + s; - let m = xx + xx + xx + $name::curve_constant_a() * zz.square(); - let x3 = m.square() - (s + s); - let a = a + a; - let a = a + a; - let a = a + a; + let s = ((self.x + yy).square() - xx - a).double(); + let m = xx.double() + xx + $name::curve_constant_a() * zz.square(); + let x3 = m.square() - s.double(); + let a = a.double(); + let a = a.double(); + let a = a.double(); let y3 = m * (s - x3) - a; - let z3 = (self.x + self.y).square() - yy - zz; + let z3 = (self.y + self.z).square() - yy - zz; let tmp = $name { x: x3, diff --git a/src/pasta/pallas.rs b/src/pasta/pallas.rs index 4af4ae7..2bce353 100644 --- a/src/pasta/pallas.rs +++ b/src/pasta/pallas.rs @@ -14,6 +14,79 @@ pub type Point = Ep; /// A Pallas point in the affine coordinate space (or the point at infinity). pub type Affine = EpAffine; +#[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 = super::IsoEp::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 = + super::hashtocurve::iso_map::<_, Affine, super::IsoEpAffine>(&r, &Ep::ISOGENY_CONSTANTS); + let (x, y, z) = p.jacobian_coordinates(); + assert!( + format!("{:?}", x) == "0x318cc15f281662b3f26d0175cab97b924870c837879cac647e877be51a85e898" + ); + assert!( + format!("{:?}", y) == "0x1e91e2fa2a5a6a5bc86ff9564ae9336084470e7119dffcb85ae8c1383a3defd7" + ); + assert!( + format!("{:?}", z) == "0x1e049436efa754f5f189aec69c2c3a4a559eca6a12b45c3f2e4a769deeca6187" + ); +} + +#[test] +fn test_iso_map_identity() { + use crate::arithmetic::Curve; + + let r = super::IsoEp::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 r = (r * -Fq::one()) + r; + assert!(bool::from(r.is_on_curve())); + let p = + super::hashtocurve::iso_map::<_, Affine, super::IsoEpAffine>(&r, &Ep::ISOGENY_CONSTANTS); + assert!(bool::from(p.is_on_curve())); +} + #[test] fn test_map_to_curve_pallas() { use crate::arithmetic::Curve; @@ -31,4 +104,7 @@ fn test_map_to_curve_pallas() { assert!( format!("{:?}", z) == "0x1e049436efa754f5f189aec69c2c3a4a559eca6a12b45c3f2e4a769deeca6187" ); + assert!(bool::from(p.is_on_curve())); + let p = (p * -Fq::one()) + p; + assert!(bool::from(p.is_on_curve())); } From 9aa3327a0ae76de85e03b693585d0dd829d40511 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Thu, 4 Feb 2021 15:01:46 +0000 Subject: [PATCH 16/28] Fix clippy lints. Signed-off-by: Daira Hopwood --- src/pasta/fields/fp.rs | 2 +- src/pasta/fields/fq.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pasta/fields/fp.rs b/src/pasta/fields/fp.rs index c43fe2c..1a26a59 100644 --- a/src/pasta/fields/fp.rs +++ b/src/pasta/fields/fp.rs @@ -761,7 +761,7 @@ impl FieldExt for Fp { let rq = sqr(rp, 4) * r11; let rr = sqr(rq, 7) * r111; let rs = sqr(rr, 3) * r11; - rs.square() + rs.square() // rt } } diff --git a/src/pasta/fields/fq.rs b/src/pasta/fields/fq.rs index 544fcb6..81fcdfb 100644 --- a/src/pasta/fields/fq.rs +++ b/src/pasta/fields/fq.rs @@ -761,7 +761,7 @@ impl FieldExt for Fq { let sq = sqr(sp, 4) * s111; let sr = sqr(sq, 5) * s1011; let ss = sqr(sr, 3) * self; - sqr(ss, 4) + sqr(ss, 4) // st } } From 25ea5d07f7ba7e4eca0e3c908c70d7e90a1de4f4 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Thu, 4 Feb 2021 15:09:08 +0000 Subject: [PATCH 17/28] Fix error in doc comment. Signed-off-by: Daira Hopwood --- src/arithmetic/curves.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arithmetic/curves.rs b/src/arithmetic/curves.rs index 35dce17..837ac6b 100644 --- a/src/arithmetic/curves.rs +++ b/src/arithmetic/curves.rs @@ -92,7 +92,7 @@ pub trait Curve: /// let hasher = C::Projective::hash_to_curve("z.cash:example_pedersen_commitment"); /// let g = hasher(b"g"); /// let h = hasher(b"h"); - /// (g * x + h * r).to_affine() + /// (g * x + &(h * r)).to_affine() /// } /// ``` fn hash_to_curve(domain_prefix: &str) -> Box Self + 'static>; From 785ad5375ca2a90dd71dcb0208983692d1744c85 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Thu, 18 Feb 2021 23:39:54 +0000 Subject: [PATCH 18/28] Switch from XOF:SHAKE128 to XMD:BLAKE2b. Signed-off-by: Daira Hopwood --- src/pasta/curves.rs | 2 +- src/pasta/hashtocurve.rs | 56 +++++++++++++++++++++++++++++++--------- src/pasta/pallas.rs | 3 ++- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/pasta/curves.rs b/src/pasta/curves.rs index 94fcc32..0a18824 100644 --- a/src/pasta/curves.rs +++ b/src/pasta/curves.rs @@ -693,7 +693,7 @@ macro_rules! impl_projective_curve_specific { "{}-{}_{}_{}_RO_", domain_prefix, $name_affine::CURVE_ID, - "XOF:SHAKE128", + "XMD:BLAKE2b", "SSWU" ); diff --git a/src/pasta/hashtocurve.rs b/src/pasta/hashtocurve.rs index 2467e6f..76796fb 100644 --- a/src/pasta/hashtocurve.rs +++ b/src/pasta/hashtocurve.rs @@ -1,32 +1,64 @@ //! This module implements "simplified SWU" hashing to short Weierstrass curves //! with a = 0. +use std::convert::TryInto; use subtle::ConstantTimeEq; use crate::arithmetic::{Curve, CurveAffine, FieldExt}; /// 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}; + use blake2b_simd::{Params as Blake2bParams, State as Blake2bState}; 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 dst_prime: Vec = domain_separation_tag.into(); + dst_prime.extend_from_slice(&[domain_separation_tag.len() as u8]); - let mut xof = sha3::Shake128::default(); - xof.update(message); - xof.update(&(outlen as u32).to_be_bytes()); - xof.update([domain_separation_tag.len() as u8]); - xof.update(domain_separation_tag); + let personal = [0u8; 16]; + let empty_hasher = Blake2bParams::new() + .hash_length(CHUNKLEN) + .personal(&personal) + .to_state(); - for (big, buf) in xof - .finalize_boxed(outlen) - .chunks(CHUNKLEN) - .zip(buf.iter_mut()) - { + let xor = |left: &[u8; CHUNKLEN], right: &[u8; CHUNKLEN]| -> Vec { + left.iter() + .zip(right.iter()) + .map(|(&l, &r)| l ^ r) + .collect() + }; + + let finalize = |hasher: &Blake2bState| -> [u8; CHUNKLEN] { + hasher.finalize().as_bytes().try_into().unwrap() + }; + + let b_0 = finalize( + empty_hasher + .clone() + .update(&[0; CHUNKLEN]) + .update(message) + .update(&[0, 128, 0]) + .update(&dst_prime), + ); + let b_1 = finalize( + empty_hasher + .clone() + .update(&b_0) + .update(&[1]) + .update(&dst_prime), + ); + let b_2 = finalize( + empty_hasher + .clone() + .update(&xor(&b_0, &b_1)) + .update(&[2]) + .update(&dst_prime), + ); + + for (big, buf) in [b_1, b_2].iter().zip(buf.iter_mut()) { let mut little = [0u8; CHUNKLEN]; little.copy_from_slice(big); little.reverse(); diff --git a/src/pasta/pallas.rs b/src/pasta/pallas.rs index 2bce353..10750ef 100644 --- a/src/pasta/pallas.rs +++ b/src/pasta/pallas.rs @@ -19,7 +19,8 @@ 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"). + // with domain prefix "z.cash:test", Shake128, and input b"hello"). We don't + // implement Shake128 any more but that's fine. let r = super::IsoEp::new_jacobian( Base::from_raw([ 0xc37f111df5c4419e, From 6d8c899e160f936d8a0e690fc846db03ec61b0fe Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Thu, 18 Feb 2021 23:43:01 +0000 Subject: [PATCH 19/28] Rename map_to_curve to map_to_curve_simple_swu. Signed-off-by: Daira Hopwood --- src/pasta/curves.rs | 4 ++-- src/pasta/hashtocurve.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pasta/curves.rs b/src/pasta/curves.rs index 0a18824..e5816f9 100644 --- a/src/pasta/curves.rs +++ b/src/pasta/curves.rs @@ -700,13 +700,13 @@ macro_rules! impl_projective_curve_specific { Box::new(move |message| { let mut us = [Field::zero(); 2]; hashtocurve::hash_to_field(message, domain_separation_tag.as_bytes(), &mut us); - let q0 = hashtocurve::map_to_curve::<$base, $name_affine, $iso_affine>( + let q0 = hashtocurve::map_to_curve_simple_swu::<$base, $name_affine, $iso_affine>( &us[0], $name::THETA, $name::Z, $name::B_OVER_ZA, ); - let q1 = hashtocurve::map_to_curve::<$base, $name_affine, $iso_affine>( + let q1 = hashtocurve::map_to_curve_simple_swu::<$base, $name_affine, $iso_affine>( &us[1], $name::THETA, $name::Z, diff --git a/src/pasta/hashtocurve.rs b/src/pasta/hashtocurve.rs index 76796fb..fae19c9 100644 --- a/src/pasta/hashtocurve.rs +++ b/src/pasta/hashtocurve.rs @@ -94,7 +94,7 @@ pub fn iso_map, I: CurveAffine>( C::Projective::new_jacobian(xo, yo, zo).unwrap() } -pub fn map_to_curve, I: CurveAffine>( +pub fn map_to_curve_simple_swu, I: CurveAffine>( u: &F, theta: F, z: F, From 8b8dbbe2bbf79f1e098c00f127df1a6b1afeb723 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Thu, 18 Feb 2021 23:54:27 +0000 Subject: [PATCH 20/28] Refine type of buf in hash_to_field as suggested by @ebfull. Signed-off-by: Daira Hopwood --- src/pasta/hashtocurve.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pasta/hashtocurve.rs b/src/pasta/hashtocurve.rs index fae19c9..26758f6 100644 --- a/src/pasta/hashtocurve.rs +++ b/src/pasta/hashtocurve.rs @@ -7,7 +7,7 @@ use subtle::ConstantTimeEq; use crate::arithmetic::{Curve, CurveAffine, FieldExt}; /// 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]) { +pub fn hash_to_field(message: &[u8], domain_separation_tag: &[u8], buf: &mut [F; 2]) { use blake2b_simd::{Params as Blake2bParams, State as Blake2bState}; assert!(domain_separation_tag.len() < 256); From a14eccc13d3742e13ef97d178f954c4096ce03cc Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Sat, 20 Feb 2021 21:51:32 +0000 Subject: [PATCH 21/28] Remove unused hash support for Pasta Fp and Fq. Signed-off-by: Daira Hopwood --- src/pasta/curves.rs | 9 --------- src/pasta/fields/fp.rs | 2 +- src/pasta/fields/fq.rs | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/pasta/curves.rs b/src/pasta/curves.rs index e5816f9..966d04a 100644 --- a/src/pasta/curves.rs +++ b/src/pasta/curves.rs @@ -3,7 +3,6 @@ use core::cmp; use core::fmt::Debug; -use core::hash::{Hash, Hasher}; use core::ops::{Add, Mul, Neg, Sub}; use ff::Field; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; @@ -650,14 +649,6 @@ macro_rules! new_curve_impl { } } - impl Hash for $name_affine { - fn hash(&self, state: &mut H) { - self.x.hash(state); - self.y.hash(state); - bool::from(self.infinity).hash(state) - } - } - impl_binops_additive!($name, $name); impl_binops_additive!($name, $name_affine); impl_binops_additive_specify_output!($name_affine, $name_affine, $name); diff --git a/src/pasta/fields/fp.rs b/src/pasta/fields/fp.rs index 1a26a59..4a09c78 100644 --- a/src/pasta/fields/fp.rs +++ b/src/pasta/fields/fp.rs @@ -16,7 +16,7 @@ use crate::arithmetic::{adc, mac, sbb, FieldExt, Group, SqrtTables}; // The internal representation of this type is four 64-bit unsigned // integers in little-endian order. `Fp` values are always in // Montgomery form; i.e., Fp(a) = aR mod p, with R = 2^256. -#[derive(Clone, Copy, Eq, Hash)] +#[derive(Clone, Copy, Eq)] pub struct Fp(pub(crate) [u64; 4]); impl fmt::Debug for Fp { diff --git a/src/pasta/fields/fq.rs b/src/pasta/fields/fq.rs index 81fcdfb..f03cb79 100644 --- a/src/pasta/fields/fq.rs +++ b/src/pasta/fields/fq.rs @@ -16,7 +16,7 @@ use crate::arithmetic::{adc, mac, sbb, FieldExt, Group, SqrtTables}; // The internal representation of this type is four 64-bit unsigned // integers in little-endian order. `Fq` values are always in // Montgomery form; i.e., Fq(a) = aR mod q, with R = 2^256. -#[derive(Clone, Copy, Eq, Hash)] +#[derive(Clone, Copy, Eq)] pub struct Fq(pub(crate) [u64; 4]); impl fmt::Debug for Fq { From 642aad68a32d8c8d8ed05bedb67c4a3095bba9fe Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Sat, 20 Feb 2021 21:54:50 +0000 Subject: [PATCH 22/28] Revert comment changes that are no longer relevant, now that we don't expose the isogenous curves in the API --- src/arithmetic/curves.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arithmetic/curves.rs b/src/arithmetic/curves.rs index 837ac6b..7243f34 100644 --- a/src/arithmetic/curves.rs +++ b/src/arithmetic/curves.rs @@ -60,7 +60,7 @@ pub trait Curve: /// Obtains the additive identity. fn zero() -> Self; - /// Obtains the base point of the curve, if defined. + /// Obtains the base point of the curve. fn one() -> Self; /// Doubles this element. @@ -162,7 +162,7 @@ pub trait CurveAffine: /// Obtains the additive identity. fn zero() -> Self; - /// Obtains the base point of the curve, if defined. + /// Obtains the base point of the curve. fn one() -> Self; /// Returns whether or not this element is the identity. From 704a6c3637e5957b1b00fc29230a4049bf017ef5 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Sun, 21 Feb 2021 00:45:04 +0000 Subject: [PATCH 23/28] Remove unneeded sha3 dependency. Signed-off-by: Daira Hopwood --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 67b501d..7cd9d62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,6 @@ metrics = "0.14.2" num_cpus = "1.13" rand = "0.8" blake2b_simd = "0.5" -sha3 = "0.9.1" lazy_static = "1.4.0" static_assertions = "1.1.0" From 24def7ce02712904d4b04e4dc1860307f338f739 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Sun, 21 Feb 2021 21:00:50 +0000 Subject: [PATCH 24/28] Fix case where the input to map_to_curve_simple_swu is 0, and remove unneeded B_OVER_ZA constants. Signed-off-by: Daira Hopwood --- src/pasta/curves.rs | 20 -------------------- src/pasta/hashtocurve.rs | 13 +++---------- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/src/pasta/curves.rs b/src/pasta/curves.rs index 966d04a..b90e39d 100644 --- a/src/pasta/curves.rs +++ b/src/pasta/curves.rs @@ -695,13 +695,11 @@ macro_rules! impl_projective_curve_specific { &us[0], $name::THETA, $name::Z, - $name::B_OVER_ZA, ); let q1 = hashtocurve::map_to_curve_simple_swu::<$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())); @@ -988,15 +986,6 @@ impl Ep { 0x4000000000000000, ]); - /// `b * &((*z * a).invert().unwrap())` where a and b correspond with curve - /// constants for the isogenous curve - pub const B_OVER_ZA: Fp = Fp::from_raw([ - 0xaf333253bca63800, - 0xf6ca6e5ce0e2b674, - 0xe9585bf1a0c67160, - 0x2c150731d26bf03d, - ]); - /// `(F::ROOT_OF_UNITY.invert().unwrap() * z).sqrt().unwrap()` pub const THETA: Fp = Fp::from_raw([ 0xca330bcc09ac318e, @@ -1097,15 +1086,6 @@ impl Eq { 0x4000000000000000, ]); - /// `b * &((*z * a).invert().unwrap())` where a and b correspond with curve - /// constants for the isogenous curve - pub const B_OVER_ZA: Fq = Fq::from_raw([ - 0xb66e73e89c4736c2, - 0x6fa1dc53f442887a, - 0xcb59112c429e2216, - 0x252ca74e8e7b7846, - ]); - /// `(F::ROOT_OF_UNITY.invert().unwrap() * z).sqrt().unwrap()` pub const THETA: Fq = Fq::from_raw([ 0x632cae9872df1b5d, diff --git a/src/pasta/hashtocurve.rs b/src/pasta/hashtocurve.rs index 26758f6..db69f06 100644 --- a/src/pasta/hashtocurve.rs +++ b/src/pasta/hashtocurve.rs @@ -98,7 +98,6 @@ pub fn map_to_curve_simple_swu, I: CurveAf 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) @@ -127,17 +126,11 @@ pub fn map_to_curve_simple_swu, I: CurveAf 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 div = a * F::conditional_select(&-ta, &z, ta.ct_is_zero()); 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); + let num_gx1 = (num2_x1 + a * div2) * num_x1 + b * div3; // 5. x2 = Z * u^2 * x1 let num_x2 = z_u2 * num_x1; // same div @@ -145,7 +138,7 @@ pub fn map_to_curve_simple_swu, I: CurveAf // 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); + let (gx1_square, y1) = F::sqrt_ratio(&num_gx1, &div3); // This magic also comes from a generalization of [WB2019, section 4.2]. // From 7dc21f472752e7573826e0796c3f07a6f3432f71 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Sun, 21 Feb 2021 21:01:19 +0000 Subject: [PATCH 25/28] Repair test vectors and add tests for map_to_curve_simple_swu. Signed-off-by: Daira Hopwood --- src/pasta/pallas.rs | 62 ++++++++++++++++++++++++++++++++++++++++----- src/pasta/vesta.rs | 46 ++++++++++++++++++++++++++++++--- 2 files changed, 98 insertions(+), 10 deletions(-) diff --git a/src/pasta/pallas.rs b/src/pasta/pallas.rs index 10750ef..d38d056 100644 --- a/src/pasta/pallas.rs +++ b/src/pasta/pallas.rs @@ -54,6 +54,14 @@ fn test_iso_map() { assert!( format!("{:?}", z) == "0x1e049436efa754f5f189aec69c2c3a4a559eca6a12b45c3f2e4a769deeca6187" ); + + // check that iso_map([2] r) = [2] iso_map(r) + let r2 = r.double(); + assert!(bool::from(r2.is_on_curve())); + let p2 = + super::hashtocurve::iso_map::<_, Affine, super::IsoEpAffine>(&r2, &Ep::ISOGENY_CONSTANTS); + assert!(bool::from(p2.is_on_curve())); + assert!(bool::from(p2 == p.double())); } #[test] @@ -83,29 +91,71 @@ fn test_iso_map_identity() { .unwrap(); let r = (r * -Fq::one()) + r; assert!(bool::from(r.is_on_curve())); + assert!(bool::from(r.is_zero())); let p = super::hashtocurve::iso_map::<_, Affine, super::IsoEpAffine>(&r, &Ep::ISOGENY_CONSTANTS); assert!(bool::from(p.is_on_curve())); + assert!(bool::from(p.is_zero())); } #[test] -fn test_map_to_curve_pallas() { +fn test_map_to_curve_simple_swu() { use crate::arithmetic::Curve; + use crate::pasta::curves::{IsoEp, IsoEpAffine}; + use crate::pasta::hashtocurve::map_to_curve_simple_swu; - let hash = Point::hash_to_curve("z.cash:test"); - let p: Point = hash(b"hello"); + // The zero input is a special case. + let p: IsoEp = + map_to_curve_simple_swu::(&Fp::zero(), Ep::THETA, Ep::Z); let (x, y, z) = p.jacobian_coordinates(); println!("{:?}", p); assert!( - format!("{:?}", x) == "0x318cc15f281662b3f26d0175cab97b924870c837879cac647e877be51a85e898" + format!("{:?}", x) == "0x28c1a6a534f56c52e25295b339129a8af5f42525dea727f485ca3433519b096e" ); assert!( - format!("{:?}", y) == "0x1e91e2fa2a5a6a5bc86ff9564ae9336084470e7119dffcb85ae8c1383a3defd7" + format!("{:?}", y) == "0x3bfc658bee6653c63c7d7f0927083fd315d29c270207b7c7084fa1ee6ac5ae8d" ); assert!( - format!("{:?}", z) == "0x1e049436efa754f5f189aec69c2c3a4a559eca6a12b45c3f2e4a769deeca6187" + format!("{:?}", z) == "0x054b3ba10416dc104157b1318534a19d5d115472da7d746f8a5f250cd8cdef36" + ); + + let p: IsoEp = + map_to_curve_simple_swu::(&Fp::one(), Ep::THETA, Ep::Z); + let (x, y, z) = p.jacobian_coordinates(); + println!("{:?}", p); + assert!( + format!("{:?}", x) == "0x010cba5957e876534af5e967c026a1856d64b071068280837913b9a5a3561505" + ); + assert!( + format!("{:?}", y) == "0x062fc61f9cd3118e7d6e65a065ebf46a547514d6b08078e976fa6d515dcc9c81" + ); + assert!( + format!("{:?}", z) == "0x3f86cb8c311250c3101c4e523e7793605ccff5623de1753a7c75bc9a29a73688" + ); +} + +#[test] +fn test_hash_to_curve() { + use crate::arithmetic::Curve; + + // This test vector is chosen so that the first map_to_curve_simple_swu takes the gx1 square + // "branch" and the second takes the gx1 non-square "branch" (opposite to the Vesta test vector). + let hash = Point::hash_to_curve("z.cash:test"); + let p: Point = hash(b"world"); + let (x, y, z) = p.jacobian_coordinates(); + println!("{:?}", p); + assert!( + format!("{:?}", x) == "0x2ae2d9bde5a5b4bc1f1e7154f18a407ac826c9d7cd23c3b33efa0f237e99cd35" + ); + assert!( + format!("{:?}", y) == "0x3ca16b5bf2e6c41cdf781ead8ba61400becbc16430d026b65b707560b98f8b31" + ); + assert!( + format!("{:?}", z) == "0x2502d25cc3b1129d933af3ac34822111bfd070609fdebdfb778dd25cf40f9b82" ); assert!(bool::from(p.is_on_curve())); + let p = (p * -Fq::one()) + p; assert!(bool::from(p.is_on_curve())); + assert!(bool::from(p.is_zero())); } diff --git a/src/pasta/vesta.rs b/src/pasta/vesta.rs index a92956d..be387c2 100644 --- a/src/pasta/vesta.rs +++ b/src/pasta/vesta.rs @@ -15,20 +15,58 @@ pub type Point = Eq; pub type Affine = EqAffine; #[test] -fn test_map_to_curve_vesta() { +fn test_map_to_curve_simple_swu() { + use crate::arithmetic::Curve; + use crate::pasta::curves::{IsoEq, IsoEqAffine}; + use crate::pasta::hashtocurve::map_to_curve_simple_swu; + + // The zero input is a special case. + let p: IsoEq = + map_to_curve_simple_swu::(&Fq::zero(), Eq::THETA, Eq::Z); + let (x, y, z) = p.jacobian_coordinates(); + println!("{:?}", p); + assert!( + format!("{:?}", x) == "0x2ccc4c6ec2660e5644305bc52527d904d408f92407f599df8f158d50646a2e78" + ); + assert!( + format!("{:?}", y) == "0x29a34381321d13d72d50b6b462bb4ea6a9e47393fa28a47227bf35bc0ee7aa59" + ); + assert!( + format!("{:?}", z) == "0x0b851e9e579403a76df1100f556e1f226e5656bdf38f3bf8601d8a3a9a15890b" + ); + + let p: IsoEq = + map_to_curve_simple_swu::(&Fq::one(), Eq::THETA, Eq::Z); + let (x, y, z) = p.jacobian_coordinates(); + println!("{:?}", p); + assert!( + format!("{:?}", x) == "0x165f8b71841c5abc3d742ec13fb16f099d596b781e6f5c7d0b6682b1216a8258" + ); + assert!( + format!("{:?}", y) == "0x0dadef21de74ed7337a37dd74f126a92e4df73c3a704da501e36eaf59cf03120" + ); + assert!( + format!("{:?}", z) == "0x0a3d6f6c1af02bd9274cc0b80129759ce77edeef578d7de968d4a47d39026c82" + ); +} + +#[test] +fn test_hash_to_curve() { use crate::arithmetic::Curve; + // This test vector is chosen so that the first map_to_curve_simple_swu takes the gx1 non-square + // "branch" and the second takes the gx1 square "branch" (opposite to the Pallas test vector). let hash = Point::hash_to_curve("z.cash:test"); let p: Point = hash(b"hello"); let (x, y, z) = p.jacobian_coordinates(); println!("{:?}", p); assert!( - format!("{:?}", x) == "0x3984612258b3b43b4f6e046f7f796bbd35ffd8908804bcf47b9537d3ec7645c9" + format!("{:?}", x) == "0x24c3431db13111fcba2f214a0662ae48e675801988c5705877525750b65f7ad8" ); assert!( - format!("{:?}", y) == "0x2573c035293d745a288a65a7a37709ef99bcf31b77cfb3a1126a61e3adeebc4b" + format!("{:?}", y) == "0x0df21621bf38070d79193ec5959fc2bb09468e71c0190d0217b0984fc92282f3" ); assert!( - format!("{:?}", z) == "0x1cb99da94a634842b09a3ee1e5b462233e1fc23d0b357ec7fb0d1c409be30720" + format!("{:?}", z) == "0x3e95ef9cbe5a9978c0d82635b242cf773ecfbc764ae9b936aba64c43f67091c6" ); } From e408a351d5ab5c0399d6bf5e890a1a41a62b8545 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Sun, 21 Feb 2021 21:43:11 +0000 Subject: [PATCH 26/28] Remove a redundant bool::from. Signed-off-by: Daira Hopwood --- src/pasta/pallas.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pasta/pallas.rs b/src/pasta/pallas.rs index d38d056..06fd9c0 100644 --- a/src/pasta/pallas.rs +++ b/src/pasta/pallas.rs @@ -61,7 +61,7 @@ fn test_iso_map() { let p2 = super::hashtocurve::iso_map::<_, Affine, super::IsoEpAffine>(&r2, &Ep::ISOGENY_CONSTANTS); assert!(bool::from(p2.is_on_curve())); - assert!(bool::from(p2 == p.double())); + assert!(p2 == p.double()); } #[test] From 16e5f96f3f0ed108e8e42895d88d899d009b52b6 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Mon, 22 Feb 2021 16:02:38 +0000 Subject: [PATCH 27/28] Fix a clippy lint. Signed-off-by: Daira Hopwood --- src/pasta/hashtocurve.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pasta/hashtocurve.rs b/src/pasta/hashtocurve.rs index db69f06..dc1c165 100644 --- a/src/pasta/hashtocurve.rs +++ b/src/pasta/hashtocurve.rs @@ -50,9 +50,9 @@ pub fn hash_to_field(message: &[u8], domain_separation_tag: &[u8], .update(&[1]) .update(&dst_prime), ); + let mut empty_hasher = empty_hasher; let b_2 = finalize( empty_hasher - .clone() .update(&xor(&b_0, &b_1)) .update(&[2]) .update(&dst_prime), From e93de2c28501e25d098a7b599916b2700d4b2e54 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 22 Feb 2021 10:15:30 -0700 Subject: [PATCH 28/28] Avoid heap allocations within hash_to_curve. --- src/arithmetic/curves.rs | 2 +- src/pasta/curves.rs | 16 ++------ src/pasta/hashtocurve.rs | 81 +++++++++++++++++++++------------------- 3 files changed, 48 insertions(+), 51 deletions(-) diff --git a/src/arithmetic/curves.rs b/src/arithmetic/curves.rs index 7243f34..4a633d9 100644 --- a/src/arithmetic/curves.rs +++ b/src/arithmetic/curves.rs @@ -95,7 +95,7 @@ pub trait Curve: /// (g * x + &(h * r)).to_affine() /// } /// ``` - fn hash_to_curve(domain_prefix: &str) -> Box Self + 'static>; + fn hash_to_curve<'a>(domain_prefix: &'a str) -> Box Self + 'a>; /// Returns whether or not this element is on the curve; should /// always be true unless an "unchecked" API was used. diff --git a/src/pasta/curves.rs b/src/pasta/curves.rs index b90e39d..f22a65b 100644 --- a/src/pasta/curves.rs +++ b/src/pasta/curves.rs @@ -677,20 +677,12 @@ 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 hash_to_curve(domain_prefix: &str) -> Box Self + 'static> { + fn hash_to_curve<'a>(domain_prefix: &'a str) -> Box Self + 'a> { use super::hashtocurve; - let domain_separation_tag: String = format!( - "{}-{}_{}_{}_RO_", - domain_prefix, - $name_affine::CURVE_ID, - "XMD:BLAKE2b", - "SSWU" - ); - Box::new(move |message| { let mut us = [Field::zero(); 2]; - hashtocurve::hash_to_field(message, domain_separation_tag.as_bytes(), &mut us); + hashtocurve::hash_to_field($name_affine::CURVE_ID, domain_prefix, message, &mut us); let q0 = hashtocurve::map_to_curve_simple_swu::<$base, $name_affine, $iso_affine>( &us[0], $name::THETA, @@ -702,7 +694,7 @@ macro_rules! impl_projective_curve_specific { $name::Z, ); let r = q0 + &q1; - assert!(bool::from(r.is_on_curve())); + debug_assert!(bool::from(r.is_on_curve())); hashtocurve::iso_map::<$base, $name_affine, $iso_affine>( &r, &$name::ISOGENY_CONSTANTS, @@ -766,7 +758,7 @@ macro_rules! impl_projective_curve_specific { }; ($name:ident, $name_affine:ident, $iso_affine:ident, $base:ident, general) => { /// Unimplemented: hashing to this curve is not supported - fn hash_to_curve(_domain_prefix: &str) -> Box Self + 'static> { + fn hash_to_curve<'a>(_domain_prefix: &'a str) -> Box Self + 'a> { unimplemented!() } diff --git a/src/pasta/hashtocurve.rs b/src/pasta/hashtocurve.rs index dc1c165..2388c1e 100644 --- a/src/pasta/hashtocurve.rs +++ b/src/pasta/hashtocurve.rs @@ -1,66 +1,71 @@ //! This module implements "simplified SWU" hashing to short Weierstrass curves //! with a = 0. -use std::convert::TryInto; use subtle::ConstantTimeEq; use crate::arithmetic::{Curve, CurveAffine, FieldExt}; /// 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; 2]) { - use blake2b_simd::{Params as Blake2bParams, State as Blake2bState}; - assert!(domain_separation_tag.len() < 256); +pub fn hash_to_field( + curve_id: &str, + domain_prefix: &str, + message: &[u8], + buf: &mut [F; 2], +) { + assert!(domain_prefix.len() < 256); + assert!((22 + curve_id.len() + domain_prefix.len()) < 256); // Assume that the field size is 32 bytes and k is 256, where k is defined in // . const CHUNKLEN: usize = 64; - let mut dst_prime: Vec = domain_separation_tag.into(); - dst_prime.extend_from_slice(&[domain_separation_tag.len() as u8]); - let personal = [0u8; 16]; - let empty_hasher = Blake2bParams::new() + let empty_hasher = blake2b_simd::Params::new() .hash_length(CHUNKLEN) .personal(&personal) .to_state(); - let xor = |left: &[u8; CHUNKLEN], right: &[u8; CHUNKLEN]| -> Vec { - left.iter() - .zip(right.iter()) - .map(|(&l, &r)| l ^ r) - .collect() - }; + let b_0 = empty_hasher + .clone() + .update(&[0; CHUNKLEN]) + .update(message) + .update(&[0, 128, 0]) + .update(domain_prefix.as_bytes()) + .update(b"-") + .update(curve_id.as_bytes()) + .update(b"_XMD:BLAKE2b_SSWU_RO_") + .update(&[(22 + curve_id.len() + domain_prefix.len()) as u8]) + .finalize(); - let finalize = |hasher: &Blake2bState| -> [u8; CHUNKLEN] { - hasher.finalize().as_bytes().try_into().unwrap() - }; + let b_1 = empty_hasher + .clone() + .update(b_0.as_array()) + .update(&[1]) + .update(domain_prefix.as_bytes()) + .update(b"-") + .update(curve_id.as_bytes()) + .update(b"_XMD:BLAKE2b_SSWU_RO_") + .update(&[(22 + curve_id.len() + domain_prefix.len()) as u8]) + .finalize(); - let b_0 = finalize( + let b_2 = { + let mut empty_hasher = empty_hasher; + for (l, r) in b_0.as_array().iter().zip(b_1.as_array().iter()) { + empty_hasher.update(&[*l ^ *r]); + } empty_hasher - .clone() - .update(&[0; CHUNKLEN]) - .update(message) - .update(&[0, 128, 0]) - .update(&dst_prime), - ); - let b_1 = finalize( - empty_hasher - .clone() - .update(&b_0) - .update(&[1]) - .update(&dst_prime), - ); - let mut empty_hasher = empty_hasher; - let b_2 = finalize( - empty_hasher - .update(&xor(&b_0, &b_1)) .update(&[2]) - .update(&dst_prime), - ); + .update(domain_prefix.as_bytes()) + .update(b"-") + .update(curve_id.as_bytes()) + .update(b"_XMD:BLAKE2b_SSWU_RO_") + .update(&[(22 + curve_id.len() + domain_prefix.len()) as u8]) + .finalize() + }; for (big, buf) in [b_1, b_2].iter().zip(buf.iter_mut()) { let mut little = [0u8; CHUNKLEN]; - little.copy_from_slice(big); + little.copy_from_slice(big.as_array()); little.reverse(); *buf = F::from_bytes_wide(&little); }