Merge pull request #136 from daira/hash-to-curve

Hash to curve
This commit is contained in:
ebfull 2021-02-22 10:24:36 -07:00 committed by GitHub
commit 627d729836
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 915 additions and 99 deletions

View File

@ -30,6 +30,10 @@ criterion = "0.3"
name = "arithmetic"
harness = false
[[bench]]
name = "hashtocurve"
harness = false
[[bench]]
name = "plonk"
harness = false

23
benches/hashtocurve.rs Normal file
View File

@ -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);

View File

@ -2,7 +2,7 @@
//! field and polynomial arithmetic.
use crossbeam_utils::thread;
use ff::Field;
pub use ff::Field;
mod curves;
mod fields;

View File

@ -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<C: CurveAffine>(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<dyn Fn(&[u8]) -> 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<Self>;
}
/// 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;
}

View File

@ -315,7 +315,7 @@ impl<F: FieldExt> SqrtTables<F> {
(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;

View File

@ -6,6 +6,7 @@ mod macros;
mod curves;
mod fields;
mod hashtocurve;
pub mod pallas;
pub mod vesta;

View File

@ -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<Self> {
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<dyn Fn(&[u8]) -> 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<dyn Fn(&[u8]) -> 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,
]);
}

View File

@ -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
}
}

View File

@ -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
}
}

175
src/pasta/hashtocurve.rs Normal file
View File

@ -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<F: FieldExt>(
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
// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.html#name-security-considerations-3>.
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<F: FieldExt, C: CurveAffine<Base = F>, I: CurveAffine<Base = F>>(
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<F: FieldExt, C: CurveAffine<Base = F>, I: CurveAffine<Base = F>>(
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()
}

View File

@ -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, EpAffine, IsoEpAffine>(&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, EpAffine, IsoEpAffine>(&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()));
}

View File

@ -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, EqAffine, IsoEqAffine>(&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, EqAffine, IsoEqAffine>(&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"
);
}