From b1a4a8c2c3455a06866cedf877d20806299ff237 Mon Sep 17 00:00:00 2001 From: Michael Lodder Date: Wed, 1 Nov 2023 10:49:58 -0600 Subject: [PATCH] add hash_to_curve method for edwards curves Signed-off-by: Michael Lodder --- curve25519-dalek/Cargo.toml | 5 +- .../src/backend/serial/fiat_u32/field.rs | 8 ++ .../src/backend/serial/fiat_u64/field.rs | 10 ++ .../src/backend/serial/u32/field.rs | 8 ++ .../src/backend/serial/u64/field.rs | 10 ++ curve25519-dalek/src/edwards.rs | 107 ++++++++++++++++++ 6 files changed, 146 insertions(+), 2 deletions(-) diff --git a/curve25519-dalek/Cargo.toml b/curve25519-dalek/Cargo.toml index b61579552..3eb83d795 100644 --- a/curve25519-dalek/Cargo.toml +++ b/curve25519-dalek/Cargo.toml @@ -48,6 +48,7 @@ required-features = ["alloc", "rand_core"] [dependencies] cfg-if = "1" +elliptic-curve = { version = "0.13.5", features = ["hash2curve"], optional = true } ff = { version = "0.13", default-features = false, optional = true } group = { version = "0.13", default-features = false, optional = true } rand_core = { version = "0.6.4", default-features = false, optional = true } @@ -63,11 +64,11 @@ cpufeatures = "0.2.6" fiat-crypto = { version = "0.2.1", default-features = false } [features] -default = ["alloc", "precomputed-tables", "zeroize"] +default = ["alloc", "group", "precomputed-tables", "zeroize"] alloc = ["zeroize?/alloc"] precomputed-tables = [] legacy_compatibility = [] -group = ["dep:group", "rand_core"] +group = ["dep:group", "dep:elliptic-curve", "rand_core"] group-bits = ["group", "ff/bits"] [target.'cfg(all(not(curve25519_dalek_backend = "fiat"), not(curve25519_dalek_backend = "serial"), target_arch = "x86_64"))'.dependencies] diff --git a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs index 94e1f6d36..fc19badbd 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs @@ -193,6 +193,14 @@ impl FieldElement2625 { FieldElement2625(fiat_25519_tight_field_element(limbs)) } + /// Elligator2 constant for Edwards Curve + pub const EDWARDS_ELL_A: FieldElement2625 = + FieldElement2625::from_limbs([486662, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + /// Elligator2 constant for Edwards Curve + pub const EDWARDS_MINUS_ELL_A: FieldElement2625 = FieldElement2625::from_limbs([ + 0xfff892e7, 0x7ffff, 0xffffffff, 0x7ffff, 0xffffffff, 0x7ffff, 0xffffffff, 0x7ffff, + 0xffffffff, 0x7ffff, + ]); /// The scalar \\( 0 \\). pub const ZERO: FieldElement2625 = FieldElement2625::from_limbs([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); /// The scalar \\( 1 \\). diff --git a/curve25519-dalek/src/backend/serial/fiat_u64/field.rs b/curve25519-dalek/src/backend/serial/fiat_u64/field.rs index c871b55c2..3b11b7fbc 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u64/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u64/field.rs @@ -172,6 +172,16 @@ impl FieldElement51 { FieldElement51(fiat_25519_tight_field_element(limbs)) } + /// Elligator2 constant for Edwards Curve + pub const EDWARDS_ELL_A: FieldElement51 = FieldElement51::from_limbs([486662, 0, 0, 0, 0]); + /// Elligator2 constant for Edwards Curve + pub const EDWARDS_MINUS_ELL_A: FieldElement51 = FieldElement51::from_limbs([ + 0x7fffffff892e7, + 0x7ffffffffffff, + 0x7ffffffffffff, + 0x7ffffffffffff, + 0x7ffffffffffff, + ]); /// The scalar \\( 0 \\). pub const ZERO: FieldElement51 = FieldElement51::from_limbs([0, 0, 0, 0, 0]); /// The scalar \\( 1 \\). diff --git a/curve25519-dalek/src/backend/serial/u32/field.rs b/curve25519-dalek/src/backend/serial/u32/field.rs index 4e0b2133b..6929bccef 100644 --- a/curve25519-dalek/src/backend/serial/u32/field.rs +++ b/curve25519-dalek/src/backend/serial/u32/field.rs @@ -288,6 +288,14 @@ impl FieldElement2625 { FieldElement2625(limbs) } + /// Elligator2 constant for Edwards Curve + pub const EDWARDS_ELL_A: FieldElement2625 = + FieldElement2625::from_limbs([486662, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + /// Elligator2 constant for Edwards Curve + pub const EDWARDS_MINUS_ELL_A: FieldElement2625 = FieldElement2625::from_limbs([ + 0xfff892e7, 0x7ffff, 0xffffffff, 0x7ffff, 0xffffffff, 0x7ffff, 0xffffffff, 0x7ffff, + 0xffffffff, 0x7ffff, + ]); /// The scalar \\( 0 \\). pub const ZERO: FieldElement2625 = FieldElement2625::from_limbs([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); /// The scalar \\( 1 \\). diff --git a/curve25519-dalek/src/backend/serial/u64/field.rs b/curve25519-dalek/src/backend/serial/u64/field.rs index 9659effa1..3b35e570e 100644 --- a/curve25519-dalek/src/backend/serial/u64/field.rs +++ b/curve25519-dalek/src/backend/serial/u64/field.rs @@ -259,6 +259,16 @@ impl FieldElement51 { FieldElement51(limbs) } + /// Elligator2 constant for Edwards Curve + pub const EDWARDS_ELL_A: FieldElement51 = FieldElement51::from_limbs([486662, 0, 0, 0, 0]); + /// Elligator2 constant for Edwards Curve + pub const EDWARDS_MINUS_ELL_A: FieldElement51 = FieldElement51::from_limbs([ + 0x7fffffff892e7, + 0x7ffffffffffff, + 0x7ffffffffffff, + 0x7ffffffffffff, + 0x7ffffffffffff, + ]); /// The scalar \\( 0 \\). pub const ZERO: FieldElement51 = FieldElement51::from_limbs([0, 0, 0, 0, 0]); /// The scalar \\( 1 \\). diff --git a/curve25519-dalek/src/edwards.rs b/curve25519-dalek/src/edwards.rs index accf22776..57a634401 100644 --- a/curve25519-dalek/src/edwards.rs +++ b/curve25519-dalek/src/edwards.rs @@ -604,6 +604,92 @@ impl EdwardsPoint { .expect("Montgomery conversion to Edwards point in Elligator failed") .mul_by_cofactor() } + + #[cfg(feature = "group")] + /// Maps the input bytes to the curve. This implements the spec for + /// [`hash_to_curve`](https://datatracker.ietf.org/doc/rfc9380/) according to sections + /// 8.5 and J.5 + pub fn hash_to_curve(msg: &[u8], dst: &[u8]) -> Self + where + X: for<'a> elliptic_curve::hash2curve::ExpandMsg<'a>, + { + use elliptic_curve::{ + bigint::{ArrayEncoding, Encoding, NonZero, U384}, + hash2curve::Expander, + }; + + let dst = [dst]; + let mut random_bytes = [0u8; 96]; + let mut expander = + X::expand_message(&[msg], &dst, random_bytes.len()).expect("expand_message failed"); + expander.fill_bytes(&mut random_bytes); + + let p = NonZero::new(U384::from_be_hex("000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed")).expect("NonZero::new failed"); + let u0 = U384::from_be_bytes( + <[u8; 48]>::try_from(&random_bytes[..48]).expect("try_from failed"), + ) % p; + let u1 = U384::from_be_bytes( + <[u8; 48]>::try_from(&random_bytes[48..]).expect("try_from failed"), + ) % p; + + let mut arr = [0u8; 32]; + arr.copy_from_slice(&u0.to_le_byte_array()[..32]); + let u0 = FieldElement::from_bytes(&arr); + arr.copy_from_slice(&u1.to_le_byte_array()[..32]); + let u1 = FieldElement::from_bytes(&arr); + + let q0 = map_to_edwards(u0); + let q1 = map_to_edwards(u1); + let p = q0 + q1; + p.mul_by_cofactor() + } +} + +fn map_to_edwards(e: FieldElement) -> EdwardsPoint { + let (u, v) = elligator_encode(e); + let (x, y) = montgomery_to_edwards(u, v); + affine_to_edwards(x, y) +} + +fn elligator_encode(e: FieldElement) -> (FieldElement, FieldElement) { + let mut t1 = &(&FieldElement::ONE + &FieldElement::ONE) * &e.square(); // 2u^2 + let e1 = t1.ct_eq(&FieldElement::MINUS_ONE); + t1.conditional_assign(&FieldElement::ZERO, e1); // if 2u^2 == -1, t1 = 0 + let x1 = &(&t1 + &FieldElement::ONE).invert() * &FieldElement::EDWARDS_MINUS_ELL_A; // -A / t1 + 1 + let min_x1 = -(&x1); + + let gx1 = &(&(&(&x1 + &FieldElement::EDWARDS_ELL_A) * &x1) + &FieldElement::ONE) * &x1; // x1 * (x1 * (x1 + A) + 1) + let x2 = &min_x1 - &FieldElement::EDWARDS_ELL_A; // -x1 - A + let gx2 = &t1 * &gx1; + let (is_square, root1) = FieldElement::sqrt_ratio_i(&gx1, &FieldElement::ONE); + let neg_root1 = -(&root1); + let (_, root2) = FieldElement::sqrt_ratio_i(&gx2, &FieldElement::ONE); + + let x = FieldElement::conditional_select(&x2, &x1, is_square); + let y = FieldElement::conditional_select(&root2, &neg_root1, is_square); + (x, y) +} + +fn montgomery_to_edwards(u: FieldElement, v: FieldElement) -> (FieldElement, FieldElement) { + let inv_sqr_d = FieldElement::from_bytes(&[ + 6, 126, 69, 255, 170, 4, 110, 204, 130, 26, 125, 75, 209, 211, 161, 197, 126, 79, 252, 3, + 220, 8, 123, 210, 187, 6, 160, 96, 244, 237, 38, 15, + ]); + let x = &(&v.invert() * &u) * &inv_sqr_d; + let u1 = &u - &FieldElement::ONE; + let u2 = &u + &FieldElement::ONE; + let y = &u1 * &u2.invert(); + (x, y) +} + +fn affine_to_edwards(x: FieldElement, y: FieldElement) -> EdwardsPoint { + let t = &x * &y; + EdwardsPoint { + X: x, + Y: y, + Z: FieldElement::ONE, + T: t, + } } // ------------------------------------------------------------------------ @@ -2267,4 +2353,25 @@ mod test { assert_eq!(point.compress().to_bytes(), output[..]); } } + + #[cfg(feature = "group")] + #[test] + fn hash_to_curve() { + const DST: &[u8] = b"QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_RO_"; + let msgs: [(&[u8], &str); 5] = [ + (b"", "09a6c8561a0b22bef63124c588ce4c62ea83a3c899763af26d795302e115dc21"), + (b"abc", "9a8395b88338f22e435bbd301183e7f20a5f9de643f11882fb237f88268a5531"), + (b"abcdef0123456789", "53060a3d140e7fbcda641ed3cf42c88a75411e648a1add71217f70ea8ec561a6"), + (b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", "2eca15e355fcfa39d2982f67ddb0eea138e2994f5956ed37b7f72eea5e89d2f7"), + (b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "6dc2fc04f266c5c27f236a80b14f92ccd051ef1ff027f26a07f8c0f327d8f995"), + ]; + for (input, expected_hex) in msgs { + let pt = EdwardsPoint::hash_to_curve::< + elliptic_curve::hash2curve::ExpandMsgXmd, + >(input, DST); + let mut expected_bytes = hex::decode(expected_hex).unwrap(); + expected_bytes.reverse(); + assert_eq!(expected_bytes, pt.to_bytes()); + } + } }