diff --git a/curve25519-dalek/src/backend/serial/u64/field.rs b/curve25519-dalek/src/backend/serial/u64/field.rs index d16d2bd36..ada14204a 100644 --- a/curve25519-dalek/src/backend/serial/u64/field.rs +++ b/curve25519-dalek/src/backend/serial/u64/field.rs @@ -597,24 +597,4 @@ impl FieldElement51 { Choice::from(c_gt as u8) } - - /// Returns 1 if self is greater than the other and 0 otherwise - // strategy: check if b-a overflows. if it does not overflow, then a was larger - pub(crate) fn mpn_sub_n(&self, other: &Self) -> Choice { - let mut _ul = 0_u64; - let mut _vl = 0_u64; - let mut _rl = 0_u64; - - let mut cy = 0_u64; - for i in 0..5 { - _ul = self.0[i]; - _vl = other.0[i]; - - let (_sl, _cy1) = _ul.overflowing_sub(_vl); - let (_rl, _cy2) = _sl.overflowing_sub(cy); - cy = _cy1 as u64 | _cy2 as u64; - } - - Choice::from((cy != 0_u64) as u8) - } } diff --git a/curve25519-dalek/src/edwards.rs b/curve25519-dalek/src/edwards.rs index 5ad9229ac..520db55b6 100644 --- a/curve25519-dalek/src/edwards.rs +++ b/curve25519-dalek/src/edwards.rs @@ -588,7 +588,7 @@ impl EdwardsPoint { where D: Digest + Default, { - use crate::elligator2::Legacy; + use crate::elligator2::RFC9380; let mut hash = D::new(); hash.update(bytes); @@ -601,7 +601,7 @@ impl EdwardsPoint { // rfc9380 should always result in a valid point since no field elements // are invalid. so unwrap should be safe. #[allow(clippy::unwrap_used)] - let fe1 = MontgomeryPoint::from_representative::(&res).unwrap(); + let fe1 = MontgomeryPoint::from_representative::(&res).unwrap(); let E1_opt = fe1.to_edwards(sign_bit); E1_opt diff --git a/curve25519-dalek/src/elligator2.rs b/curve25519-dalek/src/elligator2.rs index 960287eac..95a2b0538 100644 --- a/curve25519-dalek/src/elligator2.rs +++ b/curve25519-dalek/src/elligator2.rs @@ -34,22 +34,38 @@ //! [`EdwardsPoint`] objects themselves. //! //! ```rust +//! # use hex::FromHex; //! use rand::RngCore; //! use curve25519_dalek::{MontgomeryPoint, EdwardsPoint, elligator2::{RFC9380, Randomized, MapToPointVariant}}; //! //! // Montgomery Points can be mapped to and from elligator representatives //! // using any algorithm variant. //! let tweak = rand::thread_rng().next_u32() as u8; -//! let mont_point = MontgomeryPoint::default(); // example point known to be representable +//! let mont_point = MontgomeryPoint::default(); // example private key point known to be representable //! let r = mont_point.to_representative::(tweak).unwrap(); +//! let pubkey = RFC9380::mul_base_clamped(mont_point.to_bytes()).to_montgomery(); //! -//! _ = MontgomeryPoint::from_representative::(&r).unwrap(); +//! let derived_pubkey = MontgomeryPoint::from_representative::(&r).unwrap(); +//! assert_eq!(pubkey, derived_pubkey); //! -//! // Edwards Points can be transformed as well. +//! // Points in byte format can be transformed +//! +//! // (example known representable point) +//! let mut point = <[u8;32]>::from_hex("00deadbeef00deadbeef00deadbeef00deadbeef00deadbeef00deadbeef0124").unwrap(); +//! let tweak = rand::thread_rng().next_u32() as u8; +//! let r = RFC9380::to_representative(&point, tweak).unwrap(); +//! let pubkey = RFC9380::mul_base_clamped(point).to_montgomery(); +//! +//! let derived_pubkey = MontgomeryPoint::from_representative::(&r).unwrap(); +//! assert_eq!(pubkey, derived_pubkey); +//! +//! // Edwards Points can be transformed as well. This example uses the Randomized variant. //! let edw_point = EdwardsPoint::default(); // example point known to be representable //! let r = edw_point.to_representative::(tweak).unwrap(); +//! let pubkey = Randomized::mul_base_clamped(edw_point.to_montgomery().to_bytes()); //! -//! _ = EdwardsPoint::from_representative::(&r).unwrap(); +//! let derived_pubkey = EdwardsPoint::from_representative::(&r).unwrap(); +//! assert_eq!(pubkey, derived_pubkey); //! ``` //! //! ### Generating Representable Points. @@ -131,8 +147,7 @@ use crate::EdwardsPoint; use cfg_if::cfg_if; use subtle::{ - Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, - CtOption, + Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, CtOption, }; /// bitmask for a single byte when clearing the high order two bits of a representative @@ -141,10 +156,6 @@ pub(crate) const MASK_UNSET_BYTE: u8 = 0x3f; pub(crate) const MASK_SET_BYTE: u8 = 0xc0; /// (p - 1) / 2 = 2^254 - 10 -pub(crate) const DIVIDE_MINUS_P_1_2_BYTES: [u8; 32] = [ - 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, -]; /// Common interface for the different ways to compute the elligator2 forward /// and reverse transformations. @@ -230,67 +241,6 @@ impl MapToPointVariant for Randomized { } } -#[cfg(feature = "digest")] -/// In general this mode should **NEVER** be used unless there is a very specific -/// reason to do so as it has multiple serious known flaws. -/// -/// Converts between a point on elliptic curve E (Curve25519) and an element of -/// the finite field F over which E is defined. Supports older implementations -/// with a common implementation bug (Signal, Kleshni-C). -/// -/// In contrast to the [`RFC9380`] variant, `Legacy` does NOT assume that input values are always -/// going to be the least-square-root representation of the field element. -/// This is divergent from the specifications for both elligator2 and RFC 9380, -/// however, some older implementations miss this detail. This allows us to be -/// compatible with those alternate implementations if necessary, since the -/// resulting point will be different for inputs with either of the -/// high-order two bits set. The kleshni C and Signal implementations are examples -/// of libraries that don't always use the least square root. -/// -// We return the LSR for to_representative values. This is here purely for testing -// compatability and ensuring that we understand the subtle differences that can -// influence proper implementation. -pub struct Legacy; - -#[cfg(feature = "digest")] -impl MapToPointVariant for Legacy { - fn from_representative(representative: &[u8; 32]) -> CtOption { - let representative = FieldElement::from_bytes(representative); - let (x, y) = map_fe_to_edwards(&representative); - let point = EdwardsPoint { - X: x, - Y: y, - Z: FieldElement::ONE, - T: &x * &y, - }; - CtOption::new(point, Choice::from(1)) - } - - // There is a bug in the kleshni implementation where it - // takes a sortcut when computng greater than for field elemements. - // For the purpose of making tests pass matching the bugged implementation - // I am adding the bug here intentionally. Legacy is not exposed and - // should not be exposed as it is obviously flawed in multiple ways. - // - // What we want is: - // If root - (p - 1) / 2 < 0, root := -root - // This is not equivalent to: - // if root > (p - 1)/2 root := -root - // - fn to_representative(point: &[u8; 32], _tweak: u8) -> CtOption<[u8; 32]> { - let pubkey = EdwardsPoint::mul_base_clamped(*point); - let v_in_sqrt = v_in_sqrt_pubkey_edwards(&pubkey); - - point_to_representative(&MontgomeryPoint(*point), v_in_sqrt.into()) - - // let divide_minus_p_1_2 = FieldElement::from_bytes(&DIVIDE_MINUS_P_1_2_BYTES); - // let did_negate = divide_minus_p_1_2.ct_gt(&b); - // let should_negate = Self::gt(&b, ÷_minus_p_1_2); - // FieldElement::conditional_negate(&mut b, did_negate ^ should_negate); - // CtOption::new(b.as_bytes(), c) - } -} - // =========================================================================== // Montgomery and Edwards Interfaces // =========================================================================== @@ -577,8 +527,6 @@ pub(crate) fn point_to_representative( point: &MontgomeryPoint, v_in_sqrt: bool, ) -> CtOption<[u8; 32]> { - let divide_minus_p_1_2 = FieldElement::from_bytes(&DIVIDE_MINUS_P_1_2_BYTES); - // a := point let a = &FieldElement::from_bytes(&point.0); let a_neg = -a; @@ -602,7 +550,8 @@ pub(crate) fn point_to_representative( // If root > (p - 1) / 2, root := -root // let negate = divide_minus_p_1_2.ct_gt(&b); - let negate = divide_minus_p_1_2.mpn_sub_n(&b); + // let negate = divide_minus_p_1_2.mpn_sub_n(&b); + let negate = b.is_negative(); FieldElement::conditional_negate(&mut b, negate); CtOption::new(b.as_bytes(), is_encodable) @@ -678,8 +627,6 @@ pub(crate) fn v_in_sqrt(key_input: &[u8; 32]) -> Choice { /// Determines if `V <= (p - 1)/2` for an EdwardsPoint (e.g an x25519 public key) /// and returns a [`Choice`] indicating the result. pub(crate) fn v_in_sqrt_pubkey_edwards(pubkey: &EdwardsPoint) -> Choice { - let divide_minus_p_1_2 = FieldElement::from_bytes(&DIVIDE_MINUS_P_1_2_BYTES); - // sqrtMinusAPlus2 is sqrt(-(486662+2)) let (_, sqrt_minus_a_plus_2) = FieldElement::sqrt_ratio_i( &(&MONTGOMERY_A_NEG - &(&FieldElement::ONE + &FieldElement::ONE)), @@ -697,8 +644,8 @@ pub(crate) fn v_in_sqrt_pubkey_edwards(pubkey: &EdwardsPoint) -> Choice { // is v <= (q-1)/2 ? // divide_minus_p_1_2.ct_gt(&v) - // v.is_negative() - divide_minus_p_1_2.mpn_sub_n(&v) + // divide_minus_p_1_2.mpn_sub_n(&v) + !v.is_negative() } // ============================================================================ @@ -800,8 +747,3 @@ mod randomness; #[cfg(test)] #[cfg(feature = "elligator2")] mod subgroup; - -#[cfg(test)] -#[cfg(feature = "elligator2")] -#[cfg(feature = "digest")] -mod legacy; diff --git a/curve25519-dalek/src/elligator2/compatibility.rs b/curve25519-dalek/src/elligator2/compatibility.rs index 8ba7a735b..21d0a3d09 100644 --- a/curve25519-dalek/src/elligator2/compatibility.rs +++ b/curve25519-dalek/src/elligator2/compatibility.rs @@ -1,11 +1,23 @@ use super::*; - use hex::FromHex; //////////////////////////////////////////////////////////// // Ntor tests // //////////////////////////////////////////////////////////// + +#[test] +#[cfg(feature = "elligator2")] +fn ident_is_representable() { + let m = MontgomeryPoint::default(); + let repres = m.to_representative::(0u8).expect("should succeed, I think"); + let pubkey = RFC9380::mul_base_clamped(m.to_bytes()).to_montgomery(); + + // let orig = MontgomeryPoint::from_representative::(&out).expect("should prob also work"); + let orig = MontgomeryPoint::map_to_point(&repres); + assert_eq!(&pubkey, &orig); +} + #[test] #[cfg(feature = "elligator2")] fn pubkey_from_repres() { diff --git a/curve25519-dalek/src/elligator2/legacy.rs b/curve25519-dalek/src/elligator2/legacy.rs deleted file mode 100644 index 7513e43d8..000000000 --- a/curve25519-dalek/src/elligator2/legacy.rs +++ /dev/null @@ -1,230 +0,0 @@ -use crate::{elligator2::*, MontgomeryPoint}; - -use hex::{self, FromHex}; - -#[test] -#[cfg(feature = "elligator2")] -fn repres_from_pubkey_kleshni() { - // testcases from kleshni - for (i, testcase) in encoding_testcases().iter().enumerate() { - // the testcases for kleshni have public key values encoded as montgomery - // points with high_y as the sign bit - let pubkey = <[u8; 32]>::from_hex(testcase.point).expect("failed to decode hex point"); - - let edw_point = MontgomeryPoint(pubkey) - .to_edwards(testcase.high_y as u8) - .expect("failed to convert point to edwards"); - let v_in_sqrt = v_in_sqrt_pubkey_edwards(&edw_point); - let repres = point_to_representative(&MontgomeryPoint(pubkey), v_in_sqrt.into()); - - if testcase.representative.is_some() { - let r = repres.expect("failed to get representative from point"); - - // make sure that we did in fact get a representative - assert_eq!( - testcase - .representative - .expect("checked, is some - this should never fail"), - hex::encode(r), - "[good case] kleshni ({i}) bad representative from privkey", - ); - - // make sure that the representative we got is the correct representative - let p = Legacy::from_representative(&r) - .expect("failed to get pubkey from valid representative"); - - // make sure that the public key derived from the representative matches - // the public key derived from the privatekey. - assert_eq!( - hex::encode(pubkey), - hex::encode(p.to_montgomery().to_bytes()), - "[good case] kleshni ({i}) pubkey from repres doesn't match pubkey from privkey" - ); - } else { - // We expect the provided private key NOT to map to a representative - assert!( - >::into(repres.is_none()), - "[good case] kleshni ({i}) expected none got repres {}", - hex::encode(repres.expect("this should not fail")) - ); - } - } -} - -#[test] -#[cfg(feature = "elligator2")] -fn pubkey_from_repres() { - // testcases from kleshni - for (i, testcase) in decoding_testcases().iter().enumerate() { - let repres = <[u8; 32]>::from_hex(testcase.representative) - .expect("failed to decode hex representative"); - - let point = MontgomeryPoint::map_to_point(&repres); - assert_eq!( - testcase.point, - hex::encode(point.to_bytes()), - "[good case] kleshni ({i}) bad representative from point" - ); - - let point_from_unbounded = MontgomeryPoint::from_representative::(&repres) - .expect("expected point, failed"); - assert_eq!( - testcase.non_lsr_point, - hex::encode(point_from_unbounded.to_bytes()), - "[good case] kleshni ({i}) bad representative from point" - ); - } -} - -const ENCODING_TESTS_COUNT: usize = 10; -struct EncodingTestCase { - /// sign value of the Montgomery point representation of the public key point - high_y: bool, - /// publkic key value, byte encoding of a Montgomery point - point: &'static str, - /// representative value associated with the provided point, if one exists. - representative: Option<&'static str>, -} - -/// In these testcases the `point` is the montgomery representation of the public -/// key value. We do not need the private key value to check these tests as we can -/// convert from public key to representative, and for some of the examples we may -/// not know what the associated private key would be as we manually swap the sign -/// ('high_y`) value for the public key point. -fn encoding_testcases() -> [EncodingTestCase; ENCODING_TESTS_COUNT] { - [ - // A not encodable point with both "high_y" values - EncodingTestCase { - point: "e6f66fdf6e230c603c5e6e59a254ea1476a13eb9511b9549846781e12e52230a", - high_y: false, - representative: None, - }, - EncodingTestCase { - point: "e6f66fdf6e230c603c5e6e59a254ea1476a13eb9511b9549846781e12e52230a", - high_y: true, - representative: None, - }, - // An encodable point with both "high_y" values - EncodingTestCase { - point: "33951964003c940878063ccfd0348af42150ca16d2646f2c5856e8338377d800", - high_y: false, - representative: Some( - "999b591b6697d074f266192277d554dec3c24c2ef6108101f63d94f7fff3a013", - ), - }, - EncodingTestCase { - point: "33951964003c940878063ccfd0348af42150ca16d2646f2c5856e8338377d800", - high_y: true, - representative: Some( - "bd3d2a7ed1c8a100a977f8d992e33aaa6f630d55089770ea469101d7fd73d13d", - ), - }, - // 0 with both "high_y" values - EncodingTestCase { - point: "0000000000000000000000000000000000000000000000000000000000000000", - high_y: false, - representative: Some( - "0000000000000000000000000000000000000000000000000000000000000000", - ), - }, - EncodingTestCase { - point: "0000000000000000000000000000000000000000000000000000000000000000", - high_y: true, - representative: Some( - "0000000000000000000000000000000000000000000000000000000000000000", - ), - }, - // A not encodable point with both "high_y" values - EncodingTestCase { - point: "10745497d35c6ede6ea6b330546a6fcbf15c903a7be28ae69b1ca14e0bf09b60", - high_y: false, - representative: Some( - "d660db8cf212d31ce8c6f7139e69b9ac47fd81c7c0bfcb93e364b2d424e24813", - ), - }, - EncodingTestCase { - point: "10745497d35c6ede6ea6b330546a6fcbf15c903a7be28ae69b1ca14e0bf09b60", - high_y: true, - representative: Some( - "489a2e0f6955e08f1ae6eb8dcdbc0f867a87a96a02d2dfd2aca21d8b536f0f1b", - ), - }, - // A not encodable point with both "high_y" values - EncodingTestCase { - point: "6d3187192afc3bcc05a497928816e3e2336dc539aa7fc296a9ee013f560db843", - high_y: false, - representative: Some( - "63d0d79e7f3c279cf4a0a5c3833fd85aa1f2c004c4e466f3a3844b3c2e06e410", - ), - }, - EncodingTestCase { - point: "6d3187192afc3bcc05a497928816e3e2336dc539aa7fc296a9ee013f560db843", - high_y: true, - representative: Some( - "0f03b41c86aeb49acf2f76b39cc90a55a0b140b7290f1c9e032591ddcb074537", - ), - }, - ] -} - -const DECODING_TESTS_COUNT: usize = 7; -struct DecodingTestCase { - representative: &'static str, - /// if we only allow least-square-root values as the representative and - /// clear the high order two bits (effectively) ensuring that the - /// representative value is less than `2^254 - 10`, this is the point - /// that we should receive. - point: &'static str, - /// if we allow unbounded values to be used directly as representatives, - /// not only least-square-root values, this is the point we should receive. - non_lsr_point: &'static str, -} - -fn decoding_testcases() -> [DecodingTestCase; DECODING_TESTS_COUNT] { - [ - // A small representative with false "high_y" property - DecodingTestCase { - representative: "e73507d38bae63992b3f57aac48c0abc14509589288457995a2b4ca3490aa207", - point: "1e8afffed6bf53fe271ad572473262ded8faec68e5e67ef45ebb82eeba52604f", - non_lsr_point: "1e8afffed6bf53fe271ad572473262ded8faec68e5e67ef45ebb82eeba52604f", - }, - // A small representative with true "high_y" property - DecodingTestCase { - representative: "95a16019041dbefed9832048ede11928d90365f24a38aa7aef1b97e23954101b", - point: "794f05ba3e3a72958022468c88981e0be5782be1e1145ce2c3c6fde16ded5363", - non_lsr_point: "794f05ba3e3a72958022468c88981e0be5782be1e1145ce2c3c6fde16ded5363", - }, - // The last representative returning true: (p - 1) / 2 - DecodingTestCase { - representative: "f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", - point: "9cdb525555555555555555555555555555555555555555555555555555555555", - non_lsr_point: "9cdb525555555555555555555555555555555555555555555555555555555555", - }, - // The first representative returning false: (p + 1) / 2 - DecodingTestCase { - representative: "f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", - point: "9cdb525555555555555555555555555555555555555555555555555555555555", - non_lsr_point: "9cdb525555555555555555555555555555555555555555555555555555555555", - }, - // 0 - DecodingTestCase { - representative: "0000000000000000000000000000000000000000000000000000000000000000", - point: "0000000000000000000000000000000000000000000000000000000000000000", - non_lsr_point: "0000000000000000000000000000000000000000000000000000000000000000", - }, - // These two tests are not least-square-root representations. - - // A large representative with false "high_y" property - DecodingTestCase { - representative: "179f24730ded2ce3173908ec61964653b8027e383f40346c1c9b4d2bdb1db76c", - point: "e6e5355e0482e952cc951f13db26316ab111ae9edb58c45428a984ce7042d349", - non_lsr_point: "10745497d35c6ede6ea6b330546a6fcbf15c903a7be28ae69b1ca14e0bf09b60", - }, - // A large representative with true "high_y" property - DecodingTestCase { - representative: "8a2f286180c3d8630b5f5a3c7cc027a55e0d3ffb3b1b990c5c7bb4c3d1f91b6f", - point: "27e222fec324b0293842a59a63b8201b0f97b1dd599ebcd478a896b7261aff3e", - non_lsr_point: "6d3187192afc3bcc05a497928816e3e2336dc539aa7fc296a9ee013f560db843", - }, - ] -}