diff --git a/Cargo.toml b/Cargo.toml index 0ad9658..7cd9d62 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 diff --git a/benches/hashtocurve.rs b/benches/hashtocurve.rs new file mode 100644 index 0000000..cd3e987 --- /dev/null +++ b/benches/hashtocurve.rs @@ -0,0 +1,23 @@ +//! Benchmarks for hashing to the Pasta curves. + +use criterion::{criterion_group, criterion_main, Criterion}; + +use halo2::arithmetic::Curve; +use halo2::pasta::{pallas, vesta}; + +fn criterion_benchmark(c: &mut Criterion) { + bench_hash_to_curve(c); +} + +fn bench_hash_to_curve(c: &mut Criterion) { + let mut group = c.benchmark_group("hash-to-curve"); + + 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::Point::hash_to_curve("z.cash:test"); + group.bench_function("Vesta", |b| b.iter(|| hash_vesta(b"benchmark"))); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/src/arithmetic.rs b/src/arithmetic.rs index dcb4189..51a70e8 100644 --- a/src/arithmetic.rs +++ b/src/arithmetic.rs @@ -2,7 +2,7 @@ //! field and polynomial arithmetic. use crossbeam_utils::thread; -use ff::Field; +pub use ff::Field; mod curves; mod fields; diff --git a/src/arithmetic/curves.rs b/src/arithmetic/curves.rs index 50bc2d9..4a633d9 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 @@ -76,6 +76,27 @@ 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); + + /// Requests a hasher that accepts messages and returns near-uniformly + /// 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<'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. fn is_on_curve(&self) -> Choice; @@ -84,8 +105,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,6 +156,9 @@ 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; @@ -182,6 +213,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/pasta.rs b/src/pasta.rs index c851450..924ec13 100644 --- a/src/pasta.rs +++ b/src/pasta.rs @@ -6,6 +6,7 @@ mod macros; mod curves; mod fields; +mod hashtocurve; pub mod pallas; pub mod vesta; diff --git a/src/pasta/curves.rs b/src/pasta/curves.rs index 23d0fcb..f22a65b 100644 --- a/src/pasta/curves.rs +++ b/src/pasta/curves.rs @@ -11,26 +11,30 @@ 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) => { + (($($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, } 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) } } /// 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, @@ -51,6 +55,8 @@ macro_rules! new_curve_impl { type Scalar = $scalar; type Base = $base; + impl_projective_curve_specific!($name, $name_affine, $iso_affine, $base, $curve_type); + fn zero() -> Self { Self { x: $base::zero(), @@ -59,19 +65,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 +85,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 +146,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 +477,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 +489,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 +595,10 @@ macro_rules! new_curve_impl { } } + fn a() -> Self::Base { + $name::curve_constant_a() + } + fn b() -> Self::Base { $name::curve_constant_b() } @@ -712,5 +675,414 @@ 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, $name_affine:ident, $iso_affine:ident, $base:ident, special_a0_b5) => { + fn hash_to_curve<'a>(domain_prefix: &'a str) -> Box Self + 'a> { + use super::hashtocurve; + + Box::new(move |message| { + let mut us = [Field::zero(); 2]; + 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, + $name::Z, + ); + let q1 = hashtocurve::map_to_curve_simple_swu::<$base, $name_affine, $iso_affine>( + &us[1], + $name::THETA, + $name::Z, + ); + let r = q0 + &q1; + debug_assert!(bool::from(r.is_on_curve())); + hashtocurve::iso_map::<$base, $name_affine, $iso_affine>( + &r, + &$name::ISOGENY_CONSTANTS, + ) + }) + } + + 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, $name_affine:ident, $iso_affine:ident, $base:ident, general) => { + /// Unimplemented: hashing to this curve is not supported + fn hash_to_curve<'a>(_domain_prefix: &'a str) -> Box Self + 'a> { + unimplemented!() + } + + /// 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).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.y + self.z).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!( + (pub), + Ep, + EpAffine, + IsoEpAffine, + Fp, + Fq, + b"halo2_____pallas", + "pallas", + [0, 0, 0, 0], + [5, 0, 0, 0], + special_a0_b5 +); +new_curve_impl!( + (pub), + Eq, + EqAffine, + IsoEqAffine, + Fq, + Fp, + b"halo2______vesta", + "vesta", + [0, 0, 0, 0], + [5, 0, 0, 0], + special_a0_b5 +); +new_curve_impl!( + (pub(crate)), + IsoEp, + IsoEpAffine, + EpAffine, + Fp, + Fq, + b"halo2_iso_pallas", + "iso-pallas", + [ + 0x92bb4b0b657a014b, + 0xb74134581a27a59f, + 0x49be2d7258370742, + 0x18354a2eb0ea8c9c, + ], + [1265, 0, 0, 0], + general +); +new_curve_impl!( + (pub(crate)), + IsoEq, + IsoEqAffine, + EqAffine, + Fq, + Fp, + b"halo2__iso_vesta", + "iso-vesta", + [ + 0xc515ad7242eaa6b1, + 0x9673928c7d01b212, + 0x81639c4d96f78773, + 0x267f9b2ee592271a, + ], + [1265, 0, 0, 0], + general +); + +impl Ep { + /// 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, + ]); + + /// `(F::ROOT_OF_UNITY.invert().unwrap() * z).sqrt().unwrap()` + pub const THETA: Fp = Fp::from_raw([ + 0xca330bcc09ac318e, + 0x51f64fc4dc888857, + 0x4647aef782d5cdc8, + 0x0f7bdb65814179b4, + ]); +} + +impl Eq { + /// 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, + ]); + + /// `(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/fields/fp.rs b/src/pasta/fields/fp.rs index 737789c..4a09c78 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 56e73c7..f03cb79 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 } } diff --git a/src/pasta/hashtocurve.rs b/src/pasta/hashtocurve.rs new file mode 100644 index 0000000..2388c1e --- /dev/null +++ b/src/pasta/hashtocurve.rs @@ -0,0 +1,175 @@ +//! This module implements "simplified SWU" hashing to short Weierstrass curves +//! with a = 0. + +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( + 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 personal = [0u8; 16]; + let empty_hasher = blake2b_simd::Params::new() + .hash_length(CHUNKLEN) + .personal(&personal) + .to_state(); + + 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 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_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 + .update(&[2]) + .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.as_array()); + 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_simple_swu, I: CurveAffine>( + u: &F, + theta: F, + z: 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 * F::conditional_select(&-ta, &z, ta.ct_is_zero()); + let num2_x1 = num_x1.square(); + let div2 = div.square(); + let div3 = div2 * div; + 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 + + // 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, &div3); + + // 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 78b2f64..06fd9c0 100644 --- a/src/pasta/pallas.rs +++ b/src/pasta/pallas.rs @@ -1,13 +1,161 @@ -//! The Pallas elliptic curve group. +//! The Pallas and iso-Pallas elliptic curve groups. + +use super::{Ep, EpAffine, Fp, Fq}; + +/// 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; +#[test] +fn test_iso_map() { + use crate::arithmetic::Curve; -/// The scalar field of the Pallas group. -pub type Scalar = super::Fq; + // 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"). We don't + // implement Shake128 any more but that's fine. + 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" + ); + + // 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!(p2 == p.double()); +} + +#[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())); + 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_simple_swu() { + use crate::arithmetic::Curve; + use crate::pasta::curves::{IsoEp, IsoEpAffine}; + use crate::pasta::hashtocurve::map_to_curve_simple_swu; + + // 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) == "0x28c1a6a534f56c52e25295b339129a8af5f42525dea727f485ca3433519b096e" + ); + assert!( + format!("{:?}", y) == "0x3bfc658bee6653c63c7d7f0927083fd315d29c270207b7c7084fa1ee6ac5ae8d" + ); + assert!( + 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 c20d9b6..be387c2 100644 --- a/src/pasta/vesta.rs +++ b/src/pasta/vesta.rs @@ -1,13 +1,72 @@ -//! The Vesta elliptic curve group. +//! The Vesta and iso-Vesta elliptic curve groups. + +use super::{Eq, EqAffine, Fp, Fq}; + +/// 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; +#[test] +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 scalar field of the Vesta group. -pub type Scalar = super::Fp; + // 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) == "0x24c3431db13111fcba2f214a0662ae48e675801988c5705877525750b65f7ad8" + ); + assert!( + format!("{:?}", y) == "0x0df21621bf38070d79193ec5959fc2bb09468e71c0190d0217b0984fc92282f3" + ); + assert!( + format!("{:?}", z) == "0x3e95ef9cbe5a9978c0d82635b242cf773ecfbc764ae9b936aba64c43f67091c6" + ); +}