Skip to content

Commit

Permalink
Remove generated iso curve (#160)
Browse files Browse the repository at this point in the history
* remove iso curve generation

stay projective in the end of sswu mapping

* apply suggestions

bring back removed comments

* remove unused imports
  • Loading branch information
kilic authored Jun 25, 2024
1 parent 63d44ee commit ec576f8
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 123 deletions.
52 changes: 35 additions & 17 deletions src/hash_to_curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@ use ff::{Field, FromUniformBytes, PrimeField};
use pasta_curves::arithmetic::CurveExt;
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};

pub enum Method {
SSWU,
pub enum Method<C: CurveExt> {
SSWU(Iso<C>),
SVDW,
}

/// Map the homogeneous coordinates of a point from the isogenous curve to a point in the original curve.
#[allow(clippy::type_complexity)]
pub struct Iso<C: CurveExt> {
pub(crate) a: C::Base,
pub(crate) b: C::Base,
pub(crate) map: Box<dyn Fn(C::Base, C::Base, C::Base) -> C>,
}

pub struct Suite<C: CurveExt, D: Digest + BlockSizeUser, const L: usize> {
domain: Vec<u8>,
map_to_curve: Box<dyn Fn(C::Base) -> C>,
Expand Down Expand Up @@ -92,14 +100,20 @@ impl<C: CurveExt, D: Digest + BlockSizeUser, const L: usize> Suite<C, D, L>
where
C::Base: Legendre + FromUniformBytes<L>,
{
pub(crate) fn new(domain: &[u8], z: C::Base, method: Method) -> Self {
pub(crate) fn new(domain: &[u8], z: C::Base, method: Method<C>) -> Self {
// Check for the target bits of security `k`. Currently, the target security is 128 bits.
// See: <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#section-5.1>
// L = ceil((ceil(log2(p)) + k) / 8)
assert!((C::Base::NUM_BITS as usize + 128) / 8 <= L);

let map_to_curve: Box<dyn Fn(C::Base) -> C> = match method {
Method::SSWU => Box::new(move |u| sswu_map_to_curve::<C>(u, z)),
Method::SSWU(iso) => {
let Iso { a, b, map } = iso;
Box::new(move |u| {
let (x, y, z) = sswu_map_to_curve::<C>(u, z, a, b);
map(x, y, z)
})
}

Method::SVDW => {
let [c1, c2, c3, c4] = svdw_precomputed_constants::<C>(z);
Box::new(move |u| svdw_map_to_curve::<C>(u, c1, c2, c3, c4, z))
Expand Down Expand Up @@ -164,7 +178,12 @@ pub(crate) fn svdw_precomputed_constants<C: CurveExt>(z: C::Base) -> [C::Base; 4

// Implementation of <https://datatracker.ietf.org/doc/html/rfc9380#name-simplified-swu-method>
#[allow(clippy::too_many_arguments)]
pub(crate) fn sswu_map_to_curve<C>(u: C::Base, z: C::Base) -> C
pub(crate) fn sswu_map_to_curve<C>(
u: C::Base,
z: C::Base,
a: C::Base,
b: C::Base,
) -> (C::Base, C::Base, C::Base)
where
C: CurveExt,
{
Expand Down Expand Up @@ -205,11 +224,6 @@ where
)
}

let zero = C::Base::ZERO;
let one = C::Base::ONE;
let a = C::a();
let b = C::b();

//1. tv1 = u^2
let tv1 = u.square();
//2. tv1 = Z * tv1
Expand All @@ -219,11 +233,11 @@ where
//4. tv2 = tv2 + tv1
let tv2 = tv2 + tv1;
//5. tv3 = tv2 + 1
let tv3 = tv2 + one;
let tv3 = tv2 + C::Base::ONE;
//6. tv3 = B * tv3
let tv3 = b * tv3;
//7. tv4 = CMOV(Z, -tv2, tv2 != 0) # tv4 = z if tv2 is 0 else tv4 = -tv2
let tv2_is_not_zero = !tv2.ct_eq(&zero);
let tv2_is_not_zero = !tv2.ct_eq(&C::Base::ZERO);
let tv4 = C::Base::conditional_select(&z, &-tv2, tv2_is_not_zero);
//8. tv4 = A * tv4
let tv4 = a * tv4;
Expand Down Expand Up @@ -259,10 +273,14 @@ where
let e1 = u.is_odd().ct_eq(&y.is_odd());
//24. y = CMOV(-y, y, e1) # Select correct sign of y
let y = C::Base::conditional_select(&-y, &y, e1);
//25. x = x / tv4
let x = x * tv4.invert().unwrap();
//26. return (x, y)
C::new_jacobian(x, y, one).unwrap()

// In the original algorithm:
// 25. x = x / tv4
// 26. return (x, y)
//
// we omit instruction 25. and return the result in homogeneous coordinates (instead of the previous jacobi).

(x, y * tv4, tv4)
}

#[allow(clippy::too_many_arguments)]
Expand Down
131 changes: 34 additions & 97 deletions src/secp256k1/curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use core::cmp;
use core::fmt::Debug;
use core::iter::Sum;
use core::ops::{Add, Mul, Neg, Sub};
use group::cofactor::CofactorGroup;
use rand::RngCore;
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};

Expand Down Expand Up @@ -65,110 +64,52 @@ new_curve_impl!(
SECP_A,
SECP_B,
"secp256k1",
|domain_prefix| hash_to_curve(domain_prefix),
|domain_prefix| hash_to_curve(domain_prefix, hash_to_curve_suite(b"secp256k1_XMD:SHA-256_SSWU_RO_")),
);

impl Secp256k1 {
fn hash_to_curve_suite(domain: &[u8]) -> crate::hash_to_curve::Suite<Secp256k1, sha2::Sha256, 48> {
// Z = -11 (reference: <https://www.rfc-editor.org/rfc/rfc9380.html#name-suites-for-secp256k1>)
// 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc24
#[allow(dead_code)]
const SSWU_Z: Fp = Fp::from_raw([
0xfffffffefffffc24,
0xffffffffffffffff,
0xffffffffffffffff,
0xffffffffffffffff,
]);
}

#[allow(clippy::type_complexity)]
pub(crate) fn hash_to_curve<'a>(domain_prefix: &'a str) -> Box<dyn Fn(&[u8]) -> Secp256k1 + 'a> {
Box::new(move |message| {
let r0 = IsoSecp256k1::hash_to_curve(domain_prefix)(message);
let r1 = iso_map_secp256k1(r0);
r1.clear_cofactor()
})
}

// Simplified SWU for AB == 0 <https://www.rfc-editor.org/rfc/rfc9380.html#name-simplified-swu-for-ab-0>
//
// E': y'^2 = x'^3 + A' * x' + B', where
// A': 0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533
// B': 1771
// (reference: <https://www.rfc-editor.org/rfc/rfc9380.html#name-suites-for-secp256k1>)
pub const ISO_SECP_A: Fp = Fp::from_raw([
0x405447c01a444533,
0xe953d363cb6f0e5d,
0xa08a5558f0f5d272,
0x3f8731abdd661adc,
]);
pub const ISO_SECP_B: Fp = Fp::from_raw([1771, 0, 0, 0]);
// E': y'^2 = x'^3 + A' * x' + B', where
// A': 0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533
// B': 1771
// (reference: <https://www.rfc-editor.org/rfc/rfc9380.html#name-suites-for-secp256k1>)
pub const ISO_SECP_A: Fp = Fp::from_raw([
0x405447c01a444533,
0xe953d363cb6f0e5d,
0xa08a5558f0f5d272,
0x3f8731abdd661adc,
]);

const ISO_SECP_GENERATOR_X: Fp = Fp::from_raw([
0xD11D739D05A9F7A8,
0x00E448E38AF94593,
0x2287B72788F0933A,
0xC49B6C192E36AB1A,
]);
const ISO_SECP_GENERATOR_Y: Fp = Fp::from_raw([
0x10836BBAD9E12F4F,
0xC054381C214E65D4,
0x6DF11CC434B9FAC0,
0x9A9322D799106965,
]);
pub const ISO_SECP_B: Fp = Fp::from_raw([1771, 0, 0, 0]);

impl group::cofactor::CofactorGroup for IsoSecp256k1 {
type Subgroup = IsoSecp256k1;
let iso_map = crate::hash_to_curve::Iso {
a: ISO_SECP_A,
b: ISO_SECP_B,
map: Box::new(iso_map),
};

fn clear_cofactor(&self) -> Self {
*self
}

fn into_subgroup(self) -> CtOption<Self::Subgroup> {
CtOption::new(self, 1.into())
}

fn is_torsion_free(&self) -> Choice {
1.into()
}
crate::hash_to_curve::Suite::new(domain, SSWU_Z, crate::hash_to_curve::Method::SSWU(iso_map))
}

new_curve_impl!(
(pub(crate)),
IsoSecp256k1,
IsoSecp256k1Affine,
Fp,
Fq,
(ISO_SECP_GENERATOR_X, ISO_SECP_GENERATOR_Y),
ISO_SECP_A,
ISO_SECP_B,
"secp256k1",
|domain_prefix| crate::hash_to_curve::hash_to_curve(domain_prefix, IsoSecp256k1::default_hash_to_curve_suite()),
);

impl IsoSecp256k1 {
// Z = -11 (reference: <https://www.rfc-editor.org/rfc/rfc9380.html#name-suites-for-secp256k1>)
// 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc24
// NOTE: This `Z` is the `SSWU_Z` of `Secp256k1` curve.
const SSWU_Z: Fp = Fp::from_raw([
0xfffffffefffffc24,
0xffffffffffffffff,
0xffffffffffffffff,
0xffffffffffffffff,
]);

fn default_hash_to_curve_suite() -> crate::hash_to_curve::Suite<IsoSecp256k1, sha2::Sha256, 48>
{
crate::hash_to_curve::Suite::<IsoSecp256k1, sha2::Sha256, 48>::new(
b"secp256k1_XMD:SHA-256_SSWU_RO_",
Self::SSWU_Z,
crate::hash_to_curve::Method::SSWU,
)
}
#[allow(clippy::type_complexity)]
pub(crate) fn hash_to_curve<'a>(
domain_prefix: &'a str,
suite: crate::hash_to_curve::Suite<Secp256k1, sha2::Sha256, 48>,
) -> Box<dyn Fn(&[u8]) -> Secp256k1 + 'a> {
Box::new(move |message| suite.hash_to_curve(domain_prefix, message))
}

/// 3-Isogeny Map for Secp256k1
/// Reference: <https://www.rfc-editor.org/rfc/rfc9380.html#name-3-isogeny-map-for-secp256k1>
pub(crate) fn iso_map_secp256k1(rp: IsoSecp256k1) -> Secp256k1 {
pub(crate) fn iso_map(x: Fp, y: Fp, z: Fp) -> Secp256k1 {
// constants for secp256k1 iso_map computation
const K: [[Fp; 4]; 5] = [
[Fp::ZERO; 4],
Expand Down Expand Up @@ -263,26 +204,22 @@ pub(crate) fn iso_map_secp256k1(rp: IsoSecp256k1) -> Secp256k1 {
],
];

let (x, y, z) = rp.jacobian_coordinates();

let z2 = z.square();
let z3 = z2 * z;
let z4 = z2.square();
let z6 = z3.square();

// iso_map logic (avoid inversion)
// iso_map logic (avoid inversion) in projective coordinates
// reference: <https://github.com/zcash/pasta_curves/blob/main/src/hashtocurve.rs#L80-L106>
let x_num = ((K[1][3] * x + K[1][2] * z2) * x + K[1][1] * z4) * x + K[1][0] * z6;
let x_den = (z2 * x + K[2][1] * z4) * x + K[2][0] * z6;
let x_num = ((K[1][3] * x + K[1][2] * z) * x + K[1][1] * z2) * x + K[1][0] * z3;
let x_den = (z * x + K[2][1] * z2) * x + K[2][0] * z3;

let y_num = (((K[3][3] * x + K[3][2] * z2) * x + K[3][1] * z4) * x + K[3][0] * z6) * y;
let y_den = (((x + K[4][2] * z2) * x + K[4][1] * z4) * x + K[4][0] * z6) * z3;
let y_num = (((K[3][3] * x + K[3][2] * z) * x + K[3][1] * z2) * x + K[3][0] * z3) * y;
let y_den = (((x + K[4][2] * z) * x + K[4][1] * z2) * x + K[4][0] * z3) * z;

let z = x_den * y_den;
let x = x_num * y_den * z;
let y = y_num * x_den * z.square();
let x = x_num * y_den;
let y = y_num * x_den;

Secp256k1::new_jacobian(x, y, z).unwrap()
Secp256k1 { x, y, z }
}

#[cfg(test)]
Expand Down
26 changes: 17 additions & 9 deletions src/secp256r1/curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ new_curve_impl!(
SECP_A,
SECP_B,
"secp256r1",
|domain_prefix| crate::hash_to_curve::hash_to_curve(domain_prefix, Secp256r1::default_hash_to_curve_suite()),
|domain_prefix| hash_to_curve(domain_prefix, hash_to_curve_suite(b"P256_XMD:SHA-256_SSWU_RO_")),
);

impl Secp256r1 {
fn hash_to_curve_suite(domain: &[u8]) -> crate::hash_to_curve::Suite<Secp256r1, sha2::Sha256, 48> {
// Optimal Z with: <https://datatracker.ietf.org/doc/html/rfc9380#sswu-z-code>
// 0xffffffff00000001000000000000000000000000fffffffffffffffffffffff5
// Z = -10 (reference: <https://www.rfc-editor.org/rfc/rfc9380.html#section-8.2>)
Expand All @@ -89,13 +89,21 @@ impl Secp256r1 {
0xffffffff00000001,
]);

fn default_hash_to_curve_suite() -> crate::hash_to_curve::Suite<Secp256r1, sha2::Sha256, 48> {
crate::hash_to_curve::Suite::<Secp256r1, sha2::Sha256, 48>::new(
b"P256_XMD:SHA-256_SSWU_RO_",
Self::SSWU_Z,
crate::hash_to_curve::Method::SSWU,
)
}
let iso_map = crate::hash_to_curve::Iso {
a: Secp256r1::a(),
b: Secp256r1::b(),
map: Box::new(move |x, y, z| Secp256r1 { x, y, z }),
};

crate::hash_to_curve::Suite::new(domain, SSWU_Z, crate::hash_to_curve::Method::SSWU(iso_map))
}

#[allow(clippy::type_complexity)]
pub(crate) fn hash_to_curve<'a>(
domain_prefix: &'a str,
suite: crate::hash_to_curve::Suite<Secp256r1, sha2::Sha256, 48>,
) -> Box<dyn Fn(&[u8]) -> Secp256r1 + 'a> {
Box::new(move |message| suite.hash_to_curve(domain_prefix, message))
}

#[cfg(test)]
Expand Down

0 comments on commit ec576f8

Please sign in to comment.