Add an implementation of simplified SWU hash-to-curve.

Signed-off-by: Daira Hopwood <daira@jacaranda.org>
This commit is contained in:
Daira Hopwood 2021-01-13 13:24:47 +00:00
parent c9b606212e
commit fa3afc29bb
11 changed files with 941 additions and 98 deletions

View file

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

47
benches/hashtocurve.rs Normal file
View file

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

View file

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

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
@ -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<Self>;
}
/// 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;
}

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

@ -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<F: FieldExt, I: CurveAffine<Base = F>, C: CurveAffine<Base = F>> {
/// The MAP_ID of this method as specified in
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.html#name-suite-id-naming-conventions-2>.
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<F> + 'static,
) -> Box<dyn Fn(&[u8]) -> 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<F> + 'static,
) -> Box<dyn Fn(&[u8]) -> 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<F: FieldExt> {
/// The HASH_NAME of this message hasher as specified in
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.html#name-suite-id-naming-conventions-2>.
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<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<F: FieldExt> {
marker: PhantomData<F>,
}
impl<F: FieldExt> MessageHasher<F> for Shake128<F> {
fn hash_name(&self) -> &str {
"XOF:SHAKE128"
}
fn hash_to_field(&self, message: &[u8], domain_separation_tag: &[u8], count: usize) -> Vec<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
// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.html#name-security-considerations-3>.
const CHUNKLEN: usize = 64;
let outlen = count * CHUNKLEN;
let mut outlen_enc = vec![];
outlen_enc.write_u32::<BigEndian>(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<F: FieldExt> {
marker: PhantomData<F>,
}
impl<F: FieldExt> MessageHasher<F> for Blake2bXof<F> {
fn hash_name(&self) -> &str {
"XOF:BLAKE2b"
}
#[allow(unused_variables)]
fn hash_to_field(&self, message: &[u8], domain_separation_tag: &[u8], count: usize) -> Vec<F> {
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<Base = F>,
C: CurveAffine<Base = F>,
> {
/// `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<C>,
marker_iso: PhantomData<I>,
}
impl<F: FieldExt, I: CurveAffine<Base = F>, C: CurveAffine<Base = F>>
SimplifiedSWUWithDegree3Isogeny<F, I, C>
{
/// 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<F: FieldExt, I: CurveAffine<Base = F>, C: CurveAffine<Base = F>> HashToCurve<F, I, C>
for SimplifiedSWUWithDegree3Isogeny<F, I, C>
{
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)
}
}

View file

@ -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<Self> {
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<H: Hasher>(&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
);

View file

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

View file

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

View file

@ -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<Base, IsoAffine, Affine> = {
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"
);
}

View file

@ -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<Base, IsoAffine, Affine> = {
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"
);
}