Remove `SimplifiedSWUWithDegree3Isogeny` structure because state is no longer necessary.

This commit is contained in:
Sean Bowe 2021-02-02 11:53:30 -07:00 committed by Daira Hopwood
parent 83e2656c3e
commit 783e602e85
5 changed files with 156 additions and 358 deletions

View File

@ -12,7 +12,6 @@ pub mod vesta;
pub use curves::*;
pub use fields::*;
use hashtocurve::*;
#[test]
fn test_endo_consistency() {

View File

@ -687,16 +687,7 @@ macro_rules! new_curve_impl {
macro_rules! impl_projective_curve_specific {
($name:ident, $name_affine:ident, $iso_affine:ident, $base:ident, special_a0_b5) => {
fn hasher(domain_prefix: &str) -> Box<dyn Fn(&[u8]) -> Self + 'static> {
use super::hashtocurve::SimplifiedSWUWithDegree3Isogeny;
let swu: SimplifiedSWUWithDegree3Isogeny<$base, $name_affine, $iso_affine> =
SimplifiedSWUWithDegree3Isogeny::new(
$name::Z,
$name::ISOGENY_CONSTANTS,
$name::MINUS_B_OVER_A,
$name::B_OVER_ZA,
$name::THETA,
);
use super::hashtocurve;
let domain_separation_tag: String = format!(
"{}-{}_{}_{}_RO_",
@ -708,12 +699,25 @@ macro_rules! impl_projective_curve_specific {
Box::new(move |message| {
let mut us = [Field::zero(); 2];
SimplifiedSWUWithDegree3Isogeny::<$base, $name_affine, $iso_affine>::hash_to_field(
message,
domain_separation_tag.as_bytes(),
&mut us,
hashtocurve::hash_to_field(message, domain_separation_tag.as_bytes(), &mut us);
let q0 = hashtocurve::map_to_curve::<$base, $name_affine, $iso_affine>(
&us[0],
$name::THETA,
$name::Z,
$name::B_OVER_ZA,
);
swu.field_elements_to_curve(&us[0], &us[1])
let q1 = hashtocurve::map_to_curve::<$base, $name_affine, $iso_affine>(
&us[1],
$name::THETA,
$name::Z,
$name::B_OVER_ZA,
);
let r = q0 + &q1;
assert!(bool::from(r.is_on_curve()));
hashtocurve::iso_map::<$base, $name_affine, $iso_affine>(
&r,
&$name::ISOGENY_CONSTANTS,
)
})
}

View File

@ -2,260 +2,147 @@
//! with a = 0.
use byteorder::{BigEndian, WriteBytesExt};
use core::fmt::Debug;
use core::marker::PhantomData;
use subtle::ConstantTimeEq;
use crate::arithmetic::{Curve, CurveAffine, Field, FieldExt};
use crate::arithmetic::{Curve, CurveAffine, FieldExt};
/// Implementation of the "simplified SWU" hashing to short Weierstrass curves
/// with a = 0. Internally uses SHAKE128.
#[derive(Debug)]
pub struct SimplifiedSWUWithDegree3Isogeny<
F: FieldExt,
C: CurveAffine<Base = F>,
I: CurveAffine<Base = F>,
> {
/// `Z` parameter (ξ in [WB2019](https://eprint.iacr.org/2019/403)).
pub z: F,
/// Hashes over a message and writes the output to all of `buf`.
pub fn hash_to_field<F: FieldExt>(message: &[u8], domain_separation_tag: &[u8], buf: &mut [F]) {
use sha3::digest::{ExtendableOutput, Update};
assert!(domain_separation_tag.len() < 256);
/// Precomputed -b/a for the isogenous curve.
pub minus_b_over_a: F,
// Assume that the field size is 32 bytes and k is 256, where k is defined in
// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.html#name-security-considerations-3>.
const CHUNKLEN: usize = 64;
/// Precomputed b/Za for the isogenous curve.
pub b_over_za: F,
let outlen = buf.len() * CHUNKLEN;
let mut outlen_enc = vec![];
outlen_enc.write_u32::<BigEndian>(outlen as u32).unwrap();
/// Precomputed sqrt(Z / ROOT_OF_UNITY).
pub theta: F,
let mut xof = sha3::Shake128::default();
xof.update(message);
xof.update(outlen_enc);
xof.update([domain_separation_tag.len() as u8]);
xof.update(domain_separation_tag);
/// Constants for the isogeny.
pub isogeny_constants: [F; 13],
_marker_c: PhantomData<C>,
_marker_i: PhantomData<I>,
}
impl<F: FieldExt, C: CurveAffine<Base = F>, I: CurveAffine<Base = F>>
SimplifiedSWUWithDegree3Isogeny<F, C, I>
{
/// Create a SimplifiedSWUWithDegree3Isogeny method for the given parameters.
///
/// # Panics
/// Panics if z is square.
pub fn new(
z: F,
isogeny_constants: [F; 13],
minus_b_over_a: F,
b_over_za: F,
theta: F,
) -> Self {
SimplifiedSWUWithDegree3Isogeny {
z: z,
minus_b_over_a,
b_over_za,
theta,
isogeny_constants: isogeny_constants,
_marker_c: PhantomData,
_marker_i: PhantomData,
}
}
/// The full hash from an input message to a curve point.
///
/// `domain_prefix` should identify the application protocol, usage
/// within that protocol, and version, e.g. "z.cash:Orchard-V1".
/// Other fields required to conform to [IRTF-CFRG-Hash-to-Curve]
/// will be added automatically. There may be a length limitation on
/// `domain_prefix`.
///
/// For example, the resulting full domain separation tag for the
/// Pallas curve using `Shake128` and the simplified SWU map might be
/// b"z.cash:Orchard-V1-pallas_XOF:SHAKE128_SSWU_RO_".
pub fn hash_to_curve(&self, domain_prefix: &str) -> Box<dyn Fn(&[u8]) -> C::Projective + '_> {
let domain_separation_tag: String = format!(
"{}-{}_{}_{}_RO_",
domain_prefix,
C::CURVE_ID,
"XOF:SHAKE128",
"SSWU"
);
Box::new(move |message| {
let mut us = [Field::zero(); 2];
Self::hash_to_field(message, domain_separation_tag.as_bytes(), &mut us);
self.field_elements_to_curve(&us[0], &us[1])
})
}
/// A non-uniform hash from an input message to a curve point.
/// This is *not* suitable for applications requiring a random oracle.
/// Use `hash_to_curve` instead unless you are really sure that a
/// non-uniform map is sufficient.
///
/// `domain_prefix` is as described for `hash_to_curve`.
///
/// For example, the resulting full domain separation tag for the
/// Pallas curve using `Shake128` and the simplified SWU map might be
/// b"z.cash:Orchard-V1-pallas_XOF:SHAKE128_SSWU_NU_".
pub fn encode_to_curve(&self, domain_prefix: &str) -> Box<dyn Fn(&[u8]) -> C::Projective + '_> {
let domain_separation_tag: String = format!(
"{}-{}_{}_{}_NU_",
domain_prefix,
C::CURVE_ID,
"XOF:SHAKE128",
"SSWU"
);
Box::new(move |message| {
let mut us = [Field::zero(); 1];
Self::hash_to_field(message, domain_separation_tag.as_bytes(), &mut us);
let r = self.map_to_curve(&us[0]);
self.iso_map(&r)
})
}
/// Hashes over a message and writes the output to all of `buf`.
pub fn hash_to_field(message: &[u8], domain_separation_tag: &[u8], buf: &mut [F]) {
use sha3::digest::{ExtendableOutput, Update};
assert!(domain_separation_tag.len() < 256);
// Assume that the field size is 32 bytes and k is 256, where k is defined in
// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.html#name-security-considerations-3>.
const CHUNKLEN: usize = 64;
let outlen = buf.len() * 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);
for (big, buf) in xof
.finalize_boxed(outlen)
.chunks(CHUNKLEN)
.zip(buf.iter_mut())
{
let mut little = [0u8; CHUNKLEN];
little.copy_from_slice(big);
little.reverse();
*buf = F::from_bytes_wide(&little);
}
}
/// Maps a field element to the isogenous curve.
pub fn map_to_curve(&self, u: &F) -> I::Projective {
// 1. tv1 = inv0(Z^2 * u^4 + Z * u^2)
// 2. x1 = (-B / A) * (1 + tv1)
// 3. If tv1 == 0, set x1 = B / (Z * A)
// 4. gx1 = x1^3 + A * x1 + B
//
// We use the "Avoiding inversions" optimization in [WB2019, section 4.2]
// (not to be confused with section 4.3):
//
// here [WB2019]
// ------- ---------------------------------
// Z ξ
// u t
// Z * u^2 ξ * t^2 (called u, confusingly)
// x1 X_0(t)
// x2 X_1(t)
// gx1 g(X_0(t))
// gx2 g(X_1(t))
//
// Using the "here" names:
// x1 = num_x1/div = [B*(Z^2 * u^4 + Z * u^2 + 1)] / [-A*(Z^2 * u^4 + Z * u^2]
// gx1 = num_gx1/div_gx1 = [num_x1^3 + A * num_x1 * div^2 + B * div^3] / div^3
let a = I::a();
let b = I::b();
let z_u2 = self.z * u.square();
let ta = z_u2.square() + z_u2;
let num_x1 = b * (ta + F::one());
let div = -a * ta;
let num2_x1 = num_x1.square();
let div2 = div.square();
let div3 = div2 * div;
let ta_is_zero = ta.ct_is_zero();
let num_gx1 = F::conditional_select(
&((num2_x1 + a * div2) * num_x1 + b * div3),
&self.b_over_za,
ta_is_zero,
);
let div_gx1 = F::conditional_select(&div3, &F::one(), ta_is_zero);
// 5. x2 = Z * u^2 * x1
let num_x2 = z_u2 * num_x1; // same div
// 6. gx2 = x2^3 + A * x2 + B [optimized out; see below]
// 7. If is_square(gx1), set x = x1 and y = sqrt(gx1)
// 8. Else set x = x2 and y = sqrt(gx2)
let (gx1_square, y1) = F::sqrt_ratio(&num_gx1, &div_gx1);
// This magic also comes from a generalization of [WB2019, section 4.2].
//
// The Sarkar square root algorithm with input s gives us a square root of
// h * s for free when s is not square, where h is a fixed nonsquare.
// In our implementation, h = ROOT_OF_UNITY.
// We know that Z / h is a square since both Z and h are
// nonsquares. Precompute theta as a square root of Z / ROOT_OF_UNITY.
//
// We have gx2 = g(Z * u^2 * x1) = Z^3 * u^6 * gx1
// = (Z * u^3)^2 * (Z/h * h * gx1)
// = (Z * theta * u^3)^2 * (h * gx1)
//
// When gx1 is not square, y1 is a square root of h * gx1, and so Z * theta * u^3 * y1
// is a square root of gx2. Note that we don't actually need to compute gx2.
let y2 = self.theta * z_u2 * u * y1;
let num_x = F::conditional_select(&num_x2, &num_x1, gx1_square);
let y = F::conditional_select(&y2, &y1, gx1_square);
// 9. If sgn0(u) != sgn0(y), set y = -y
let y = F::conditional_select(
&(-y),
&y,
(u.get_lower_32() % 2).ct_eq(&(y.get_lower_32() % 2)),
);
I::Projective::new_jacobian(num_x * div, y * div3, div).unwrap()
}
/// Implements a degree 3 isogeny map.
pub fn iso_map(&self, p: &I::Projective) -> C::Projective {
// The input and output are in Jacobian coordinates, using the method
// in "Avoiding inversions" [WB2019, section 4.3].
let iso = self.isogeny_constants;
let (x, y, z) = p.jacobian_coordinates();
let z2 = z.square();
let z3 = z2 * z;
let z4 = z2.square();
let z6 = z3.square();
let num_x = ((iso[0] * x + iso[1] * z2) * x + iso[2] * z4) * x + iso[3] * z6;
let div_x = (z2 * x + iso[4] * z4) * x + iso[5] * z6;
let num_y = (((iso[6] * x + iso[7] * z2) * x + iso[8] * z4) * x + iso[9] * z6) * y;
let div_y = (((x + iso[10] * z2) * x + iso[11] * z4) * x + iso[12] * z6) * z3;
let zo = div_x * div_y;
let xo = num_x * div_y * zo;
let yo = num_y * div_x * zo.square();
C::Projective::new_jacobian(xo, yo, zo).unwrap()
}
/// Map two field elements to a curve point.
pub fn field_elements_to_curve(&self, u0: &C::Base, u1: &C::Base) -> C::Projective {
let q0 = self.map_to_curve(u0);
let q1 = self.map_to_curve(u1);
let r: I::Projective = q0 + &q1;
assert!(bool::from(r.is_on_curve()));
// here is where we would scale by the cofactor if we supported nonprime-order curves
self.iso_map(&r)
for (big, buf) in xof
.finalize_boxed(outlen)
.chunks(CHUNKLEN)
.zip(buf.iter_mut())
{
let mut little = [0u8; CHUNKLEN];
little.copy_from_slice(big);
little.reverse();
*buf = F::from_bytes_wide(&little);
}
}
/// Implements a degree 3 isogeny map.
pub fn iso_map<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<F: FieldExt, C: CurveAffine<Base = F>, I: CurveAffine<Base = F>>(
u: &F,
theta: F,
z: F,
b_over_za: F,
) -> I::Projective {
// 1. tv1 = inv0(Z^2 * u^4 + Z * u^2)
// 2. x1 = (-B / A) * (1 + tv1)
// 3. If tv1 == 0, set x1 = B / (Z * A)
// 4. gx1 = x1^3 + A * x1 + B
//
// We use the "Avoiding inversions" optimization in [WB2019, section 4.2]
// (not to be confused with section 4.3):
//
// here [WB2019]
// ------- ---------------------------------
// Z ξ
// u t
// Z * u^2 ξ * t^2 (called u, confusingly)
// x1 X_0(t)
// x2 X_1(t)
// gx1 g(X_0(t))
// gx2 g(X_1(t))
//
// Using the "here" names:
// x1 = num_x1/div = [B*(Z^2 * u^4 + Z * u^2 + 1)] / [-A*(Z^2 * u^4 + Z * u^2]
// gx1 = num_gx1/div_gx1 = [num_x1^3 + A * num_x1 * div^2 + B * div^3] / div^3
let a = I::a();
let b = I::b();
let z_u2 = z * u.square();
let ta = z_u2.square() + z_u2;
let num_x1 = b * (ta + F::one());
let div = -a * ta;
let num2_x1 = num_x1.square();
let div2 = div.square();
let div3 = div2 * div;
let ta_is_zero = ta.ct_is_zero();
let num_gx1 = F::conditional_select(
&((num2_x1 + a * div2) * num_x1 + b * div3),
&b_over_za,
ta_is_zero,
);
let div_gx1 = F::conditional_select(&div3, &F::one(), ta_is_zero);
// 5. x2 = Z * u^2 * x1
let num_x2 = z_u2 * num_x1; // same div
// 6. gx2 = x2^3 + A * x2 + B [optimized out; see below]
// 7. If is_square(gx1), set x = x1 and y = sqrt(gx1)
// 8. Else set x = x2 and y = sqrt(gx2)
let (gx1_square, y1) = F::sqrt_ratio(&num_gx1, &div_gx1);
// This magic also comes from a generalization of [WB2019, section 4.2].
//
// The Sarkar square root algorithm with input s gives us a square root of
// h * s for free when s is not square, where h is a fixed nonsquare.
// In our implementation, h = ROOT_OF_UNITY.
// We know that Z / h is a square since both Z and h are
// nonsquares. Precompute theta as a square root of Z / ROOT_OF_UNITY.
//
// We have gx2 = g(Z * u^2 * x1) = Z^3 * u^6 * gx1
// = (Z * u^3)^2 * (Z/h * h * gx1)
// = (Z * theta * u^3)^2 * (h * gx1)
//
// When gx1 is not square, y1 is a square root of h * gx1, and so Z * theta * u^3 * y1
// is a square root of gx2. Note that we don't actually need to compute gx2.
let y2 = theta * z_u2 * u * y1;
let num_x = F::conditional_select(&num_x2, &num_x1, gx1_square);
let y = F::conditional_select(&y2, &y1, gx1_square);
// 9. If sgn0(u) != sgn0(y), set y = -y
let y = F::conditional_select(
&(-y),
&y,
(u.get_lower_32() % 2).ct_eq(&(y.get_lower_32() % 2)),
);
I::Projective::new_jacobian(num_x * div, y * div3, div).unwrap()
}

View File

@ -1,8 +1,5 @@
//! The Pallas and iso-Pallas elliptic curve groups.
use lazy_static::lazy_static;
use super::SimplifiedSWUWithDegree3Isogeny;
use super::{Ep, EpAffine, Fp, Fq, IsoEp, IsoEpAffine};
/// The base field of the Pallas and iso-Pallas curves.
@ -23,72 +20,9 @@ pub type IsoPoint = IsoEp;
/// A iso-Pallas point in the affine coordinate space (or the point at infinity).
pub type IsoAffine = IsoEpAffine;
lazy_static! {
/// The iso-Pallas -> Pallas degree 3 isogeny map.
pub static ref MAP: SimplifiedSWUWithDegree3Isogeny<Base, Affine, IsoAffine> = {
SimplifiedSWUWithDegree3Isogeny::new(
Point::Z,
Point::ISOGENY_CONSTANTS,
Point::MINUS_B_OVER_A,
Point::B_OVER_ZA,
Point::THETA
)
};
}
#[test]
fn test_iso_map() {
use crate::arithmetic::Curve;
// This is a regression test (it's the same input to iso_map as for hash_to_curve
// with domain prefix "z.cash:test", Shake128, and input b"hello").
let r = IsoPoint::new_jacobian(
Base::from_raw([
0xc37f111df5c4419e,
0x593c053e5e2337ad,
0x9c6cfc47bce1aba6,
0x0a881e4d556945aa,
]),
Base::from_raw([
0xf234e04434502b47,
0x6979f7f2b0acf188,
0xa62eec46f662cb4e,
0x035e5c8a06d5cfb4,
]),
Base::from_raw([
0x11ab791d4fb6f6b4,
0x575baa717958ef1f,
0x6ac4e343558dcbf3,
0x3af37975b0933125,
]),
)
.unwrap();
let p = MAP.iso_map(&r);
let (x, y, z) = p.jacobian_coordinates();
assert!(
format!("{:?}", x) == "0x318cc15f281662b3f26d0175cab97b924870c837879cac647e877be51a85e898"
);
assert!(
format!("{:?}", y) == "0x1e91e2fa2a5a6a5bc86ff9564ae9336084470e7119dffcb85ae8c1383a3defd7"
);
assert!(
format!("{:?}", z) == "0x1e049436efa754f5f189aec69c2c3a4a559eca6a12b45c3f2e4a769deeca6187"
);
}
#[test]
fn test_map_to_curve_pallas() {
use crate::arithmetic::{Curve, CurveAffine, FieldExt};
use std::collections::HashSet;
assert!(MAP.minus_b_over_a * IsoAffine::a() == -IsoAffine::b());
assert!(MAP.b_over_za * MAP.z * IsoAffine::a() == IsoAffine::b());
assert!(MAP.theta.square() * Base::ROOT_OF_UNITY == MAP.z);
let set: HashSet<_> = (0..10000)
.map(|i| MAP.map_to_curve(&Base::from(i)).to_affine())
.collect();
assert!(set.len() == 10000);
use crate::arithmetic::Curve;
let hash = Point::hasher("z.cash:test");
let p: Point = hash(b"hello");

View File

@ -1,8 +1,5 @@
//! The Vesta and iso-Vesta elliptic curve groups.
use lazy_static::lazy_static;
use super::SimplifiedSWUWithDegree3Isogeny;
use super::{Eq, EqAffine, Fp, Fq, IsoEq, IsoEqAffine};
/// The base field of the Vesta and iso-Vesta curves.
@ -23,32 +20,9 @@ pub type IsoPoint = IsoEq;
/// A iso-Vesta point in the affine coordinate space (or the point at infinity).
pub type IsoAffine = IsoEqAffine;
lazy_static! {
/// The iso-Vesta -> Vesta degree 3 isogeny map.
pub static ref MAP: SimplifiedSWUWithDegree3Isogeny<Base, Affine, IsoAffine> = {
SimplifiedSWUWithDegree3Isogeny::new(
Point::Z,
Point::ISOGENY_CONSTANTS,
Point::MINUS_B_OVER_A,
Point::B_OVER_ZA,
Point::THETA
)
};
}
#[test]
fn test_map_to_curve_vesta() {
use crate::arithmetic::{Curve, CurveAffine, FieldExt};
use std::collections::HashSet;
assert!(MAP.minus_b_over_a * IsoAffine::a() == -IsoAffine::b());
assert!(MAP.b_over_za * MAP.z * IsoAffine::a() == IsoAffine::b());
assert!(MAP.theta.square() * Base::ROOT_OF_UNITY == MAP.z);
let set: HashSet<_> = (0..10000)
.map(|i| MAP.map_to_curve(&Base::from(i)).to_affine())
.collect();
assert!(set.len() == 10000);
use crate::arithmetic::Curve;
let hash = Point::hasher("z.cash:test");
let p: Point = hash(b"hello");