diff --git a/src/hash_to_curve.rs b/src/hash_to_curve.rs index b27b7ab7..b4d19b86 100644 --- a/src/hash_to_curve.rs +++ b/src/hash_to_curve.rs @@ -5,7 +5,10 @@ use pasta_curves::arithmetic::CurveExt; use static_assertions::const_assert; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; -use crate::ff_ext::Legendre; +use crate::{ + ff_ext::Legendre, + secp256k1::{iso_map_secp256k1, IsoSecp256k1, Secp256k1}, +}; /// Hashes over a message and writes the output to all of `buf`. /// Modified from https://github.com/zcash/pasta_curves/blob/7e3fc6a4919f6462a32b79dd226cb2587b7961eb/src/hashtocurve.rs#L11. @@ -87,7 +90,7 @@ fn hash_to_field>( // Implementation of #[allow(clippy::too_many_arguments)] -pub(crate) fn simple_svdw_map_to_curve(u: C::Base, z: C::Base) -> C +pub(crate) fn sswu_map_to_curve(u: C::Base, z: C::Base) -> C where C: CurveExt, { @@ -151,8 +154,9 @@ where C::new_jacobian(x, y, one).unwrap() } +// Implementation of #[allow(clippy::type_complexity)] -pub(crate) fn simple_svdw_hash_to_curve<'a, C>( +pub(crate) fn sswu_hash_to_curve<'a, C>( curve_id: &'static str, domain_prefix: &'a str, z: C::Base, @@ -165,7 +169,7 @@ where let mut us = [C::Base::ZERO; 2]; hash_to_field("SSWU", curve_id, domain_prefix, message, &mut us); - let [q0, q1]: [C; 2] = us.map(|u| simple_svdw_map_to_curve(u, z)); + let [q0, q1]: [C; 2] = us.map(|u| sswu_map_to_curve::(u, z)); let r = q0 + &q1; debug_assert!(bool::from(r.is_on_curve())); @@ -173,6 +177,22 @@ where }) } +// Implementation of +#[allow(clippy::type_complexity)] +pub(crate) fn sswu_hash_to_curve_secp256k1<'a>( + _curve_id: &'static str, + domain_prefix: &'a str, +) -> Box Secp256k1 + 'a> { + Box::new(move |message| { + let rp = IsoSecp256k1::hash_to_curve(domain_prefix)(message); + + let r = iso_map_secp256k1(rp); + + debug_assert!(bool::from(r.is_on_curve())); + r + }) +} + #[allow(clippy::too_many_arguments)] pub(crate) fn svdw_map_to_curve( u: C::Base, diff --git a/src/lib.rs b/src/lib.rs index 82613ca9..b28ed89e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,8 @@ pub mod secp256k1; pub mod secp256r1; pub mod secq256k1; +pub mod utils; + #[macro_use] mod derive; diff --git a/src/secp256k1/curve.rs b/src/secp256k1/curve.rs index 4bd0f26a..3390138c 100644 --- a/src/secp256k1/curve.rs +++ b/src/secp256k1/curve.rs @@ -1,7 +1,7 @@ use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::{prime::PrimeCurveAffine, Curve, Group as _, GroupEncoding}; -use crate::hash_to_curve::svdw_hash_to_curve; +use crate::hash_to_curve::{sswu_hash_to_curve, sswu_hash_to_curve_secp256k1}; use crate::secp256k1::Fp; use crate::secp256k1::Fq; use crate::{Coordinates, CurveAffine, CurveExt}; @@ -12,6 +12,12 @@ use core::ops::{Add, Mul, Neg, Sub}; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; +use crate::{ + impl_add_binop_specify_output, impl_binops_additive, impl_binops_additive_specify_output, + impl_binops_multiplicative, impl_binops_multiplicative_mixed, impl_sub_binop_specify_output, + new_curve_impl, +}; + #[cfg(feature = "derive_serde")] use serde::{Deserialize, Serialize}; @@ -48,12 +54,6 @@ const SECP_GENERATOR_Y: Fp = Fp::from_raw([ const SECP_A: Fp = Fp::from_raw([0, 0, 0, 0]); const SECP_B: Fp = Fp::from_raw([7, 0, 0, 0]); -use crate::{ - impl_add_binop_specify_output, impl_binops_additive, impl_binops_additive_specify_output, - impl_binops_multiplicative, impl_binops_multiplicative_mixed, impl_sub_binop_specify_output, - new_curve_impl, -}; - new_curve_impl!( (pub), Secp256k1, @@ -65,92 +65,298 @@ new_curve_impl!( SECP_A, SECP_B, "secp256k1", - |curve_id, domain_prefix| svdw_hash_to_curve(curve_id, domain_prefix, Secp256k1::SVDW_Z), + |curve_id, domain_prefix| sswu_hash_to_curve_secp256k1(curve_id, domain_prefix), ); impl Secp256k1 { - const SVDW_Z: Fp = Fp::ONE; + // Z = -11 (reference: ) + // 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc24 + #[allow(dead_code)] + const SSWU_Z: Fp = Fp::from_raw([ + 0xfffffffefffffc24, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + ]); } -#[test] -fn test_curve() { - crate::tests::curve::curve_tests::(); -} +// Simplified SWU for AB == 0 +// +// E': y'^2 = x'^3 + A' * x' + B', where +// A': 0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533 +// B': 1771 +// (reference: ) +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]); -#[test] -fn test_hash_to_curve() { - crate::tests::curve::hash_to_curve_test::(); +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, +]); + +impl group::cofactor::CofactorGroup for IsoSecp256k1 { + type Subgroup = IsoSecp256k1; + + fn clear_cofactor(&self) -> Self { + *self + } + + fn into_subgroup(self) -> CtOption { + CtOption::new(self, 1.into()) + } + + fn is_torsion_free(&self) -> Choice { + 1.into() + } } -#[test] -fn test_serialization() { - crate::tests::curve::random_serialization_test::(); - #[cfg(feature = "derive_serde")] - crate::tests::curve::random_serde_test::(); +new_curve_impl!( + (pub(crate)), + IsoSecp256k1, + IsoSecp256k1Affine, + true, + Fp, + Fq, + (ISO_SECP_GENERATOR_X, ISO_SECP_GENERATOR_Y), + ISO_SECP_A, + ISO_SECP_B, + "secp256k1", + |curve_id, domain_prefix| sswu_hash_to_curve(curve_id, domain_prefix, IsoSecp256k1::SSWU_Z), +); + +impl IsoSecp256k1 { + // Z = -11 (reference: ) + // 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc24 + // NOTE: This `Z` is the `SSWU_Z` of `Secp256k1` curve. + const SSWU_Z: Fp = Fp::from_raw([ + 0xfffffffefffffc24, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + ]); } -#[test] -fn test_endo_consistency() { - let g = Secp256k1::generator(); - assert_eq!(g * Fq::ZETA, g.endo()); +/// 3-Isogeny Map for Secp256k1 +/// Reference: +pub(crate) fn iso_map_secp256k1(rp: IsoSecp256k1) -> Secp256k1 { + // constants for secp256k1 iso_map computation + const K: [[Fp; 4]; 5] = [ + [Fp::ZERO; 4], + [ + Fp::from_raw([ + 0x8e38e38daaaaa8c7, + 0x38e38e38e38e38e3, + 0xe38e38e38e38e38e, + 0x8e38e38e38e38e38, + ]), + Fp::from_raw([ + 0xdfff1044f17c6581, + 0xd595d2fc0bf63b92, + 0xb9f315cea7fd44c5, + 0x7d3d4c80bc321d5, + ]), + Fp::from_raw([ + 0x4ecbd0b53d9dd262, + 0xe4506144037c4031, + 0xe2a413deca25caec, + 0x534c328d23f234e6, + ]), + Fp::from_raw([ + 0x8e38e38daaaaa88c, + 0x38e38e38e38e38e3, + 0xe38e38e38e38e38e, + 0x8e38e38e38e38e38, + ]), + ], + [ + Fp::from_raw([ + 0x9fe6b745781eb49b, + 0x86cd409542f8487d, + 0x9ca34ccbb7b640dd, + 0xd35771193d94918a, + ]), + Fp::from_raw([ + 0xc52a56612a8c6d14, + 0x06d36b641f5e41bb, + 0xf7c4b2d51b542254, + 0xedadc6f64383dc1d, + ]), + Fp::ZERO, + Fp::ZERO, + ], + [ + Fp::from_raw([ + 0xa12f684b8e38e23c, + 0x2f684bda12f684bd, + 0x684bda12f684bda1, + 0x4bda12f684bda12f, + ]), + Fp::from_raw([ + 0xdffc90fc201d71a3, + 0x647ab046d686da6f, + 0xa9d0a54b12a0a6d5, + 0xc75e0c32d5cb7c0f, + ]), + Fp::from_raw([ + 0xa765e85a9ecee931, + 0x722830a201be2018, + 0x715209ef6512e576, + 0x29a6194691f91a73, + ]), + Fp::from_raw([ + 0x84bda12f38e38d84, + 0xbda12f684bda12f6, + 0xa12f684bda12f684, + 0x2f684bda12f684bd, + ]), + ], + [ + Fp::from_raw([ + 0xfffffffefffff93b, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + ]), + Fp::from_raw([ + 0xdfb425d2685c2573, + 0x9467c1bfc8e8d978, + 0xd5e9e6632722c298, + 0x7a06534bb8bdb49f, + ]), + Fp::from_raw([ + 0xa7bf8192bfd2a76f, + 0x0a3d21162f0d6299, + 0xf3a70c3fa8fe337e, + 0x6484aa716545ca2c, + ]), + Fp::ZERO, + ], + ]; + + // Convert to affine coordinates: + let coords = ::AffineExt::coordinates(&rp.into()).unwrap(); + let x = coords.x(); + let y = coords.y(); + + // iso_map logic + let x_squared = x.square(); + let x_cubed = x * x_squared; + + let x_num = K[1][3] * x_cubed + K[1][2] * x_squared + K[1][1] * x + K[1][0]; + let x_den = x_squared + K[2][1] * x + K[2][0]; + + let y_num = K[3][3] * x_cubed + K[3][2] * x_squared + K[3][1] * x + K[3][0]; + let y_den = x_cubed + K[4][2] * x_squared + K[4][1] * x + K[4][0]; + + // exceptional case MUST return identity + // reference: + if x_den.is_zero().into() || y_den.is_zero().into() { + return Secp256k1::identity(); + } + + let x = x_num * x_den.invert().unwrap(); + let y = y * (y_num * y_den.invert().unwrap()); + + Secp256k1::new_jacobian(x, y, Fp::ONE).unwrap() } -#[test] -fn ecdsa_example() { - use crate::group::Curve; - use crate::CurveAffine; - use ff::FromUniformBytes; - use rand_core::OsRng; - - fn mod_n(x: Fp) -> Fq { - let mut x_repr = [0u8; 32]; - x_repr.copy_from_slice(x.to_repr().as_ref()); - let mut x_bytes = [0u8; 64]; - x_bytes[..32].copy_from_slice(&x_repr[..]); - Fq::from_uniform_bytes(&x_bytes) +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_curve() { + crate::tests::curve::curve_tests::(); } - let g = Secp256k1::generator(); + #[test] + fn test_hash_to_curve() { + crate::tests::curve::hash_to_curve_test::(); + } + + #[test] + fn test_serialization() { + crate::tests::curve::random_serialization_test::(); + #[cfg(feature = "derive_serde")] + crate::tests::curve::random_serde_test::(); + } + + #[test] + fn test_endo_consistency() { + let g = Secp256k1::generator(); + assert_eq!(g * Fq::ZETA, g.endo()); + } + + #[test] + fn ecdsa_example() { + use crate::group::Curve; + use crate::CurveAffine; + use ff::FromUniformBytes; + use rand_core::OsRng; + + fn mod_n(x: Fp) -> Fq { + let mut x_repr = [0u8; 32]; + x_repr.copy_from_slice(x.to_repr().as_ref()); + let mut x_bytes = [0u8; 64]; + x_bytes[..32].copy_from_slice(&x_repr[..]); + Fq::from_uniform_bytes(&x_bytes) + } + + let g = Secp256k1::generator(); - for _ in 0..1000 { - // Generate a key pair - let sk = Fq::random(OsRng); - let pk = (g * sk).to_affine(); + for _ in 0..1000 { + // Generate a key pair + let sk = Fq::random(OsRng); + let pk = (g * sk).to_affine(); - // Generate a valid signature - // Suppose `m_hash` is the message hash - let msg_hash = Fq::random(OsRng); + // Generate a valid signature + // Suppose `m_hash` is the message hash + let msg_hash = Fq::random(OsRng); - let (r, s) = { - // Draw arandomness - let k = Fq::random(OsRng); - let k_inv = k.invert().unwrap(); + let (r, s) = { + // Draw arandomness + let k = Fq::random(OsRng); + let k_inv = k.invert().unwrap(); - // Calculate `r` - let r_point = (g * k).to_affine().coordinates().unwrap(); - let x = r_point.x(); - let r = mod_n(*x); + // Calculate `r` + let r_point = (g * k).to_affine().coordinates().unwrap(); + let x = r_point.x(); + let r = mod_n(*x); - // Calculate `s` - let s = k_inv * (msg_hash + (r * sk)); + // Calculate `s` + let s = k_inv * (msg_hash + (r * sk)); - (r, s) - }; + (r, s) + }; - { - // Verify - let s_inv = s.invert().unwrap(); - let u_1 = msg_hash * s_inv; - let u_2 = r * s_inv; + { + // Verify + let s_inv = s.invert().unwrap(); + let u_1 = msg_hash * s_inv; + let u_2 = r * s_inv; - let v_1 = g * u_1; - let v_2 = pk * u_2; + let v_1 = g * u_1; + let v_2 = pk * u_2; - let r_point = (v_1 + v_2).to_affine().coordinates().unwrap(); - let x_candidate = r_point.x(); - let r_candidate = mod_n(*x_candidate); + let r_point = (v_1 + v_2).to_affine().coordinates().unwrap(); + let x_candidate = r_point.x(); + let r_candidate = mod_n(*x_candidate); - assert_eq!(r, r_candidate); + assert_eq!(r, r_candidate); + } } } } diff --git a/src/secp256r1/curve.rs b/src/secp256r1/curve.rs index 7e6e24ef..e592d82b 100644 --- a/src/secp256r1/curve.rs +++ b/src/secp256r1/curve.rs @@ -1,7 +1,7 @@ use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::{prime::PrimeCurveAffine, Curve, Group as _, GroupEncoding}; -use crate::hash_to_curve::simple_svdw_hash_to_curve; +use crate::hash_to_curve::sswu_hash_to_curve; use crate::secp256r1::Fp; use crate::secp256r1::Fq; use crate::{Coordinates, CurveAffine, CurveExt}; @@ -76,7 +76,7 @@ new_curve_impl!( SECP_A, SECP_B, "secp256r1", - |curve_id, domain_prefix| simple_svdw_hash_to_curve(curve_id, domain_prefix, Secp256r1::SSVDW_Z), + |curve_id, domain_prefix| sswu_hash_to_curve(curve_id, domain_prefix, Secp256r1::SSVDW_Z), ); impl Secp256r1 { diff --git a/src/tests/curve.rs b/src/tests/curve.rs index 95bece49..32c61aa3 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -3,7 +3,7 @@ use crate::ff::Field; use crate::ff_ext::Legendre; use crate::group::prime::PrimeCurveAffine; -use crate::tests::fe_from_str; +use crate::utils::fe_from_str; use crate::{group::GroupEncoding, serde::SerdeObject}; use crate::{hash_to_curve, CurveAffine, CurveExt}; use rand_core::{OsRng, RngCore}; diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 5c6cd819..f773c8d7 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,17 +1,2 @@ -use ff::PrimeField; -use num_bigint::BigUint; -use num_traits::Num; -use std::borrow::Cow; - pub mod curve; pub mod field; - -pub(crate) fn fe_from_str(string: impl AsRef) -> F { - let string = string.as_ref(); - let oct = if let Some(hex) = string.strip_prefix("0x") { - Cow::Owned(BigUint::from_str_radix(hex, 16).unwrap().to_string()) - } else { - Cow::Borrowed(string) - }; - F::from_str_vartime(&oct).unwrap() -} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..3b595672 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,14 @@ +use ff::PrimeField; +use num_bigint::BigUint; +use num_traits::Num; +use std::borrow::Cow; + +pub(crate) fn fe_from_str(string: impl AsRef) -> F { + let string = string.as_ref(); + let oct = if let Some(hex) = string.strip_prefix("0x") { + Cow::Owned(BigUint::from_str_radix(hex, 16).unwrap().to_string()) + } else { + Cow::Borrowed(string) + }; + F::from_str_vartime(&oct).unwrap() +}