diff --git a/curve25519-dalek/benches/dalek_benchmarks.rs b/curve25519-dalek/benches/dalek_benchmarks.rs index 47386b2..27cc6bf 100644 --- a/curve25519-dalek/benches/dalek_benchmarks.rs +++ b/curve25519-dalek/benches/dalek_benchmarks.rs @@ -74,6 +74,21 @@ mod edwards_benches { }); } + #[cfg(feature = "digest")] + fn encode_to_curve(c: &mut BenchmarkGroup) { + let mut rng = rng(); + + let mut msg = [0u8; 32]; + let mut domain_sep = [0u8; 32]; + rng.fill_bytes(&mut msg); + rng.fill_bytes(&mut domain_sep); + + c.bench_function( + "Elligator2 encode to curve (SHA-512, input size 32 bytes)", + |b| b.iter(|| EdwardsPoint::encode_to_curve::(&[&msg], &[&domain_sep])), + ); + } + #[cfg(feature = "digest")] fn hash_to_curve(c: &mut BenchmarkGroup) { let mut rng = rng(); @@ -100,6 +115,7 @@ mod edwards_benches { consttime_fixed_base_scalar_mul(&mut g); consttime_variable_base_scalar_mul(&mut g); vartime_double_base_scalar_mul(&mut g); + encode_to_curve(&mut g); hash_to_curve(&mut g); } } diff --git a/curve25519-dalek/src/backend/serial/u32/constants.rs b/curve25519-dalek/src/backend/serial/u32/constants.rs index 191bb13..ff6e4ec 100644 --- a/curve25519-dalek/src/backend/serial/u32/constants.rs +++ b/curve25519-dalek/src/backend/serial/u32/constants.rs @@ -78,12 +78,14 @@ pub(crate) const SQRT_M1: FieldElement2625 = FieldElement2625::from_limbs([ pub(crate) const APLUS2_OVER_FOUR: FieldElement2625 = FieldElement2625::from_limbs([121666, 0, 0, 0, 0, 0, 0, 0, 0, 0]); +#[cfg(feature = "digest")] /// `MONTGOMERY_A` is equal to 486662, which is a constant of the curve equation /// for Curve25519 in its Montgomery form. (This is used internally within the /// Elligator map.) pub(crate) const MONTGOMERY_A: FieldElement2625 = FieldElement2625::from_limbs([486662, 0, 0, 0, 0, 0, 0, 0, 0, 0]); +#[cfg(feature = "digest")] /// `MONTGOMERY_A_NEG` is equal to -486662. (This is used internally within the /// Elligator map.) pub(crate) const MONTGOMERY_A_NEG: FieldElement2625 = FieldElement2625::from_limbs([ diff --git a/curve25519-dalek/src/backend/serial/u64/constants.rs b/curve25519-dalek/src/backend/serial/u64/constants.rs index 22e0ccc..cd27b8f 100644 --- a/curve25519-dalek/src/backend/serial/u64/constants.rs +++ b/curve25519-dalek/src/backend/serial/u64/constants.rs @@ -108,11 +108,13 @@ pub(crate) const SQRT_M1: FieldElement51 = FieldElement51::from_limbs([ pub(crate) const APLUS2_OVER_FOUR: FieldElement51 = FieldElement51::from_limbs([121666, 0, 0, 0, 0]); +#[cfg(feature = "digest")] /// `MONTGOMERY_A` is equal to 486662, which is a constant of the curve equation /// for Curve25519 in its Montgomery form. (This is used internally within the /// Elligator map.) pub(crate) const MONTGOMERY_A: FieldElement51 = FieldElement51::from_limbs([486662, 0, 0, 0, 0]); +#[cfg(feature = "digest")] /// `MONTGOMERY_A_NEG` is equal to -486662. (This is used internally within the /// Elligator map.) pub(crate) const MONTGOMERY_A_NEG: FieldElement51 = FieldElement51::from_limbs([ diff --git a/curve25519-dalek/src/edwards.rs b/curve25519-dalek/src/edwards.rs index 1e94447..f7d2e69 100644 --- a/curve25519-dalek/src/edwards.rs +++ b/curve25519-dalek/src/edwards.rs @@ -635,12 +635,90 @@ impl EdwardsPoint { } #[cfg(feature = "digest")] - /// Perform hashing to curve, with explicit hash function and domain separator, `domain_sep`, - /// using the suite `edwards25519_XMD:SHA-512_ELL2_NU_`. The input is the concatenation of the - /// elements of `bytes`. Likewise for the domain separator with `domain_sep`. At least one - /// element of `domain_sep`, MUST be nonempty, and the concatenation MUST NOT exceed + // The function `map_to_curve` calculates an [EdwardsPoint] from a [FieldElement]. + fn map_to_curve(fe: FieldElement) -> EdwardsPoint { + let c1 = ED25519_SQRTAM2; + + // 1. (xMn, xMd, yMn, yMd) = map_to_curve_elligator2_curve25519(u) + let (xMn, xMd, yMn, yMd) = crate::montgomery::elligator_encode(&fe); + // 2. xn = xMn * yMd + let xn = &xMn * &yMd; + // 3. xn = xn * c1 + let xn = &xn * &c1; + // 4. xd = xMd * yMn + let xd = &xMd * &yMn; + // 5. yn = xMn - xMd + let yn = &xMn - &xMd; + // 6. yd = xMn + xMd + let yd = &xMn + &xMd; + // 7. tv1 = xd * yd + let tv1 = &xd * &yd; + // 8. e = tv1 == 0 + let e = tv1.ct_eq(&FieldElement::ZERO); + // 9. xn = CMOV(xn, 0, e) + let xn = FieldElement::conditional_select(&xn, &FieldElement::ZERO, e); + // 10. xd = CMOV(xd, 1, e) + let xd = FieldElement::conditional_select(&xd, &FieldElement::ONE, e); + // 11. yn = CMOV(yn, 1, e) + let yn = FieldElement::conditional_select(&yn, &FieldElement::ONE, e); + // 12. yd = CMOV(yd, 1, e) + let yd = FieldElement::conditional_select(&yd, &FieldElement::ONE, e); + // 13. return (xn, xd, yn, yd) + + EdwardsPoint { + X: &xn * &yd, + Y: &xd * &yn, + Z: &xd * &yd, + T: &xn * &yn, + } + } + + #[cfg(feature = "digest")] + /// Perform encode to curve per RFC 9380, with explicit hash function and domain separator + /// `domain_sep`, using the Twisted Edwards Elligator 2 method. The input is the concatenation + /// of the elements of `bytes`. Likewise for the domain separator with `domain_sep`. At least + /// one element of `domain_sep`, MUST be nonempty, and the concatenation MUST NOT exceed 255 + /// bytes. + /// + /// The specification names SHA-512 as an example of a secure hash to use with this function, + /// but you may use any 512-bit hash within reason (see the + /// [`spec`](https://www.rfc-editor.org/rfc/rfc9380.html#section-5.2) for details). + /// + /// # Warning + /// `encode_to_curve` is a nonuniform encoding from byte strings to points in `G`. That is, + /// the distribution of its output is not uniformly random in `G`: the set of possible outputs + /// of encode_to_curve is only a fraction of the points in `G`, and some points in this set + /// are more likely to be output than others. + /// + /// If your application needs the distribution of the output to be statistically close to + /// uniform in `G`, use [Self::hash_to_curve] instead. + /// + /// # Panics + /// Panics if `domain_sep.collect().len() == 0` or `> 255` + pub fn encode_to_curve(bytes: &[&[u8]], domain_sep: &[&[u8]]) -> EdwardsPoint + where + D: BlockSizeUser + Default + FixedOutput + HashMarker, + D::BlockSize: IsGreater, + { + // For reference see + // https://www.rfc-editor.org/rfc/rfc9380.html#name-elligator-2-method-2 + + let fe = FieldElement::hash_to_field::(bytes, domain_sep); + let Q = Self::map_to_curve(fe[0]); + Q.mul_by_cofactor() + } + + #[cfg(feature = "digest")] + /// Perform a hash to curve per RFC 9380, with explicit hash function and domain separator + /// `domain_sep`, using the Twisted Edwards Elligator 2 method. The input is the concatenation + /// of the elements of `bytes`. Likewise for the domain separator with `domain_sep`. At least + /// one element of `domain_sep`, MUST be nonempty, and the concatenation MUST NOT exceed /// 255 bytes. /// + /// The specification names SHA-512 as an example of a secure hash to use with this function, + /// but you may use any 512-bit hash within reason (see the + /// [`spec`](https://www.rfc-editor.org/rfc/rfc9380.html#section-5.2) for details). + /// /// # Panics /// Panics if `domain_sep.collect().len() == 0` or `> 255` pub fn hash_to_curve(bytes: &[&[u8]], domain_sep: &[&[u8]]) -> EdwardsPoint @@ -651,34 +729,12 @@ impl EdwardsPoint { // For reference see // https://www.rfc-editor.org/rfc/rfc9380.html#name-elligator-2-method-2 - let fe = FieldElement::hash_to_field::(bytes, domain_sep); - let (M1, is_sq) = crate::montgomery::elligator_encode(&fe); + let fe = FieldElement::hash_to_field::(bytes, domain_sep); + let Q0 = Self::map_to_curve(fe[0]); + let Q1 = Self::map_to_curve(fe[1]); - // The `to_edwards` conversion we're performing takes as input the sign of the Edwards - // `y` coordinate. However, the specification uses `is_sq` to determine the sign of the - // Montgomery `v` coordinate. Our approach reconciles this mismatch as follows: - // - // * We arbitrarily fix the sign of the Edwards `y` coordinate (we choose 0). - // * Using the Montgomery `u` coordinate and the Edwards `X` coordinate, we recover `v`. - // * We verify that the sign of `v` matches the expected one, i.e., `is_sq == mont_v.is_negative()`. - // * If it does not match, we conditionally negate to correct the sign. - // - // Note: This logic aligns with the RFC draft specification: - // https://www.rfc-editor.org/rfc/rfc9380.html#name-elligator-2-method-2 - // followed by the mapping - // https://www.rfc-editor.org/rfc/rfc9380.html#name-mappings-for-twisted-edward - // The only difference is that our `elligator_encode` returns only the Montgomery `u` coordinate, - // so we apply this workaround to reconstruct and validate the sign. - - let mut E1_opt = M1 - .to_edwards(0) - .expect("Montgomery conversion to Edwards point in Elligator failed"); - - // Now we recover v, to ensure that we got the sign right. - let mont_v = - &(&ED25519_SQRTAM2 * &FieldElement::from_bytes(&M1.to_bytes())) * &E1_opt.X.invert(); - E1_opt.X.conditional_negate(is_sq ^ mont_v.is_negative()); - E1_opt.mul_by_cofactor() + let R = Q0 + Q1; + R.mul_by_cofactor() } /// Return an `EdwardsPoint` chosen uniformly at random using a user-provided RNG. @@ -2387,10 +2443,10 @@ mod test { } // Hash-to-curve test vectors from - // https://www.rfc-editor.org/rfc/rfc9380.html#name-edwards25519_xmdsha-512_ell2 + // https://www.rfc-editor.org/rfc/rfc9380.html#appendix-J.5.2 // These are of the form (input_msg, output_x, output_y) #[cfg(all(feature = "alloc", feature = "digest"))] - const RFC_HASH_TO_CURVE_KAT: &[(&[u8], &str, &str)] = &[ + const RFC_ENCODE_TO_CURVE_KAT: &[(&[u8], &str, &str)] = &[ ( b"", "1ff2b70ecf862799e11b7ae744e3489aa058ce805dd323a936375a84695e76da", @@ -2424,32 +2480,88 @@ mod test { ) ]; + #[cfg(all(feature = "alloc", feature = "digest"))] + fn hex_str_to_fe(hex_str: &str) -> FieldElement { + let mut bytes = hex::decode(hex_str).unwrap().to_vec(); + bytes.reverse(); + FieldElement::from_bytes(&bytes.try_into().unwrap()) + } + + #[test] + #[cfg(all(feature = "alloc", feature = "digest"))] + fn elligator_encode_to_curve_test_vectors() { + let dst = b"QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_NU_"; + for (index, vector) in RFC_ENCODE_TO_CURVE_KAT.iter().enumerate() { + let input = vector.0; + + let expected_output = { + let x = hex_str_to_fe(vector.1); + let y = hex_str_to_fe(vector.2); + AffinePoint { x, y }.to_edwards() + }; + + let computed = EdwardsPoint::encode_to_curve::(&[&input], &[dst]); + assert_eq!(computed, expected_output, "Failed in test {}", index); + } + } + + // Hash-to-curve test vectors from + // https://www.rfc-editor.org/rfc/rfc9380.html#appendix-J.5.1 + // These are of the form (input_msg, output_x, output_y) + #[cfg(all(feature = "alloc", feature = "digest"))] + const RFC_HASH_TO_CURVE_KAT: &[(&[u8], &str, &str)] = &[ + ( + b"", + "3c3da6925a3c3c268448dcabb47ccde5439559d9599646a8260e47b1e4822fc6", + "09a6c8561a0b22bef63124c588ce4c62ea83a3c899763af26d795302e115dc21", + ), + + ( + b"abc", + "608040b42285cc0d72cbb3985c6b04c935370c7361f4b7fbdb1ae7f8c1a8ecad", + "1a8395b88338f22e435bbd301183e7f20a5f9de643f11882fb237f88268a5531", + ), + + ( + b"abcdef0123456789", + "6d7fabf47a2dc03fe7d47f7dddd21082c5fb8f86743cd020f3fb147d57161472", + "53060a3d140e7fbcda641ed3cf42c88a75411e648a1add71217f70ea8ec561a6", + ), + ( + b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\ + qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + "5fb0b92acedd16f3bcb0ef83f5c7b7a9466b5f1e0d8d217421878ea3686f8524", + "2eca15e355fcfa39d2982f67ddb0eea138e2994f5956ed37b7f72eea5e89d2f7", + ), + ( + b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "0efcfde5898a839b00997fbe40d2ebe950bc81181afbd5cd6b9618aa336c1e8c", + "6dc2fc04f266c5c27f236a80b14f92ccd051ef1ff027f26a07f8c0f327d8f995" + ) + ]; + #[test] #[cfg(all(feature = "alloc", feature = "digest"))] fn elligator_hash_to_curve_test_vectors() { - let dst = b"QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_NU_"; + let dst = b"QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_RO_"; for (index, vector) in RFC_HASH_TO_CURVE_KAT.iter().enumerate() { let input = vector.0; let expected_output = { - let mut x_bytes = hex::decode(vector.1).unwrap(); - x_bytes.reverse(); - let x = FieldElement::from_bytes(&x_bytes.try_into().unwrap()); + let x = hex_str_to_fe(vector.1); + let y = hex_str_to_fe(vector.2); - let mut y_bytes = hex::decode(vector.2).unwrap(); - y_bytes.reverse(); - let y = FieldElement::from_bytes(&y_bytes.try_into().unwrap()); - - EdwardsPoint { - X: x, - Y: y, - Z: FieldElement::ONE, - T: &x * &y, - } + AffinePoint { x, y }.to_edwards() }; let computed = EdwardsPoint::hash_to_curve::(&[&input], &[dst]); - assert_eq!(computed, expected_output, "Failed in test {}", index); + + assert_eq!(expected_output, computed, "Failed in test {}", index); } } } diff --git a/curve25519-dalek/src/field.rs b/curve25519-dalek/src/field.rs index db2d9aa..a25a737 100644 --- a/curve25519-dalek/src/field.rs +++ b/curve25519-dalek/src/field.rs @@ -266,7 +266,7 @@ impl FieldElement { /// Raise this field element to the power (p-5)/8 = 2^252 -3. #[rustfmt::skip] // keep alignment of explanatory comments #[allow(clippy::let_and_return)] - fn pow_p58(&self) -> FieldElement { + pub(crate) fn pow_p58(&self) -> FieldElement { // The bits of (p-5)/8 are 101111.....11. // // nonzero bits of exponent @@ -354,62 +354,139 @@ impl FieldElement { } #[cfg(feature = "digest")] - /// Perform hashing to a [`FieldElement`], per the - /// [`hash_to_curve`](https://www.rfc-editor.org/rfc/rfc9380.html#section-5.2) specification. - /// Uses the suite `edwards25519_XMD:SHA-512_ELL2_NU_`. The input is the concatenation of the - /// elements of `bytes`. Likewise for the domain separator with `domain_sep`. At least one - /// element of `domain_sep`, MUST be nonempty, and the concatenation MUST NOT exceed 255 bytes. + /// Hashes the given message and domain separator to produce `COUNT` [`FieldElement`]s, per RFC + /// 9380. The hash's input is the concatenation of the elements of `msg`. Likewise for the + /// domain separator with `domain_sep`. At least one element of `domain_sep`, MUST be nonempty, + /// its concatenation MUST NOT exceed 255 bytes, and `COUNT` MUST be 1 or 2. + /// + /// The specification names SHA-512 as an example of a secure hash to use with this function, + /// but you may use any 512-bit hash within reason (see the + /// [`spec`](https://www.rfc-editor.org/rfc/rfc9380.html#section-5.2) for details). /// /// # Panics - /// Panics if `domain_sep.collect().len() == 0` or `> 255` - pub fn hash_to_field(bytes: &[&[u8]], domain_sep: &[&[u8]]) -> Self + /// Panics if `domain_sep.collect().len() == 0` or `> 255`. Also panics if `COUNT > 2` or + /// `COUNT == 0`. + pub fn hash_to_field( + msg: &[&[u8]], + domain_sep: &[&[u8]], + ) -> [Self; COUNT] where D: BlockSizeUser + Default + FixedOutput + HashMarker, D::BlockSize: IsGreater, { - let l_i_b_str = 48u16.to_be_bytes(); - let z_pad = Array::::default(); + // We only use `hash_to_field` for Elligator2, which uses 0 < COUNT <= 2 + assert!(COUNT == 1 || COUNT == 2); - let mut hasher = D::new().chain_update(z_pad); + // §5.2, we generate count * m * L = COUNT * 1 * (256 + 128)/8 bytes + let len_in_bytes = COUNT * 48; + // Buffer should hold however many digests we need to produce len_in_bytes bytes. + // len_in_bytes is at most 2*48=96, and the digest is 64B. So that's 2 digests, or 128B + let mut buf = [0u8; 128]; + // We can call this without worrying about panics: len_in_bytes is at most 96, and buf is + // bigger than it. Further, if `domain_sep` is too large, that's also a panic condition of + // this function so it's fine + let uniform_bytes = expand_msg_xmd::(msg, domain_sep, &mut buf, len_in_bytes); - for slice in bytes { - hasher = hasher.chain_update(slice); + let mut result = [FieldElement::ONE; COUNT]; + for i in 0..COUNT { + let mut bytes_wide = [0u8; 64]; + bytes_wide[..48].copy_from_slice(&uniform_bytes[48 * i..48 * (i + 1)]); + bytes_wide[..48].reverse(); + + result[i] = FieldElement::from_bytes_wide(&bytes_wide); } - hasher = hasher.chain_update(l_i_b_str).chain_update([0u8]); - - let mut domain_sep_len = 0usize; - for slice in domain_sep { - hasher = hasher.chain_update(slice); - domain_sep_len += slice.len(); - } - - let domain_sep_len = u8::try_from(domain_sep_len) - .expect("Unexpected overflow from domain separator's size."); - assert_ne!( - domain_sep_len, 0, - "Domain separator MUST have nonzero length." - ); - - let b_0 = hasher.chain_update([domain_sep_len]).finalize(); - - let mut hasher = D::new().chain_update(b_0.as_slice()).chain_update([1u8]); - - for slice in domain_sep { - hasher = hasher.chain_update(slice) - } - - let b_1 = hasher.chain_update([domain_sep_len]).finalize(); - - // §5.2, we only generate count * m * L = 1 * 1 * (256 + 128)/8 = 48 bytes - let mut bytes_wide = [0u8; 64]; - bytes_wide[..48].copy_from_slice(&b_1.as_slice()[..48]); - bytes_wide[..48].reverse(); - - FieldElement::from_bytes_wide(&bytes_wide) + result } } +/// Hashes the concatenation of the elements of `msg` with domain separator equal to the +/// concatenation of `domain_sep`. The output is an `outlen`-length slice into `buf`. Follows +/// https://www.rfc-editor.org/rfc/rfc9380.html#section-5.3.1 +/// +/// # Panics +/// Panics if the domain separator is empty, if the total length of the domain separator is more +/// than 255 bytes, if `outlen` is more than `255 * D::output_size()`, if `outlen is more than +/// 65535, or if `dst.len() < outlen`. +#[cfg(feature = "digest")] +fn expand_msg_xmd<'a, D>( + msg: &[&[u8]], + domain_sep: &[&[u8]], + buf: &'a mut [u8], + outlen: usize, +) -> &'a [u8] +where + D: BlockSizeUser + Default + FixedOutput + HashMarker, +{ + use core::iter::once; + + // The notation we use in this function is the same as in the spec + let len_in_bytes = u16::try_from(outlen).expect("outlen must not exceed 65535"); + + assert!(buf.len() >= outlen); + let ell = u8::try_from((len_in_bytes as usize).div_ceil(D::output_size())) + .expect("output length cannot exceed 255 times digest size"); + let domain_sep_len = u8::try_from(domain_sep.iter().map(|c| c.len()).sum::()) + .expect("unexpected overflow from domain separator's size."); + assert_ne!( + domain_sep_len, 0, + "domain separator MUST have nonzero length." + ); + + let domain_sep_len_slice = &[domain_sep_len][..]; + let dst_prime = domain_sep.iter().copied().chain(once(domain_sep_len_slice)); + let z_pad = Array::::default(); + let l_i_b_str = len_in_bytes.to_be_bytes(); + + // Collect the components of msg_prime + let msg_prime = once(z_pad.as_slice()) + .chain(msg.iter().copied()) + .chain(once(l_i_b_str.as_slice())) + .chain(once(&[0u8][..])) + .chain(dst_prime.clone()); + // Hash all of msg_prime + let b_0 = msg_prime + .fold(D::new(), |h, slice| h.chain_update(slice)) + .finalize(); + + // Collect the input components for the b_1 hash + let b_1_input = once(b_0.as_slice()) + .chain(once(&[1u8][..])) + .chain(dst_prime.clone()); + let b_1 = b_1_input + .fold(D::new(), |h, slice| h.chain_update(slice)) + .finalize(); + // Write b_1 to the output buffer + buf[..D::output_size()].copy_from_slice(&b_1); + + for i in 2..=ell { + // Get the last digest we produced + let i_ = i as usize; + let last_b = &buf[(i_ - 2) * D::output_size()..(i_ - 1) * D::output_size()]; + // XOR it with b_0 + let mut xor_bs = b_0.clone(); + xor_bs + .as_mut_slice() + .iter_mut() + .zip(last_b) + .for_each(|(l, r)| *l ^= *r); + + // Hash (b_0 ^ b_{i-1}) || i || dst_prime + let i_slice = &[i][..]; + let b_i_input = once(xor_bs.as_slice()) + .chain(once(i_slice)) + .chain(dst_prime.clone()); + let b_i = b_i_input + .fold(D::new(), |h, slice| h.chain_update(slice)) + .finalize(); + + // Write b_i to the output buffer + buf[(i_ - 1) * D::output_size()..i_ * D::output_size()].copy_from_slice(&b_i); + } + + &buf[..outlen] +} + #[cfg(test)] mod test { use crate::field::*; @@ -721,8 +798,15 @@ mod test { } } + #[cfg(feature = "digest")] + fn fe_from_test_vector(expected_hex: &str) -> FieldElement { + let mut expected_hash = hex::decode(expected_hex).unwrap(); + expected_hash.reverse(); + FieldElement::from_bytes(&expected_hash.try_into().unwrap()) + } + /// Hash to field test vectors from - /// https://www.rfc-editor.org/rfc/rfc9380.html#name-edwards25519_xmdsha-512_ell2 + /// https://www.rfc-editor.org/rfc/rfc9380.html#appendix-J.5.2 /// These are of the form (input_msg, output_field_elem) #[cfg(feature = "digest")] const RFC_HASH_TO_FIELD_KAT: &[(&[u8], &str)] = &[ @@ -761,14 +845,67 @@ mod test { let dst = "QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_NU_"; for (msg, expected_hash_hex) in RFC_HASH_TO_FIELD_KAT { - let fe = FieldElement::hash_to_field::(&[msg], &[dst.as_bytes()]); - let expected_fe = { - let mut expected_hash = hex::decode(expected_hash_hex).unwrap(); - expected_hash.reverse(); - FieldElement::from_bytes(&expected_hash.try_into().unwrap()) - }; + let fe = FieldElement::hash_to_field::(&[msg], &[dst.as_bytes()])[0]; + let expected_fe = fe_from_test_vector(expected_hash_hex); assert_eq!(fe, expected_fe); } } + + /// Hash to field test vectors from + /// https://www.rfc-editor.org/rfc/rfc9380.html#appendix-J.5.1 + /// These are of the form (input_msg, output_field_elem, output_field_elem) + #[cfg(feature = "digest")] + const RFC_HASH_TO_FIELD_KAT_2: &[(&[u8], &str, &str)] = &[ + ( + b"", + "03fef4813c8cb5f98c6eef88fae174e6e7d5380de2b007799ac7ee712d203f3a", + "780bdddd137290c8f589dc687795aafae35f6b674668d92bf92ae793e6a60c75" + ), + ( + b"abc", + "5081955c4141e4e7d02ec0e36becffaa1934df4d7a270f70679c78f9bd57c227", + "005bdc17a9b378b6272573a31b04361f21c371b256252ae5463119aa0b925b76" + ), + ( + b"abcdef0123456789", + "285ebaa3be701b79871bcb6e225ecc9b0b32dff2d60424b4c50642636a78d5b3", + "2e253e6a0ef658fedb8e4bd6a62d1544fd6547922acb3598ec6b369760b81b31" + ), + ( + b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\ + qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + "4fedd25431c41f2a606952e2945ef5e3ac905a42cf64b8b4d4a83c533bf321af", + "02f20716a5801b843987097a8276b6d869295b2e11253751ca72c109d37485a9" + ), + ( + b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "6e34e04a5106e9bd59f64aba49601bf09d23b27f7b594e56d5de06df4a4ea33b", + "1c1c2cb59fc053f44b86c5d5eb8c1954b64976d0302d3729ff66e84068f5fd96" + ) + ]; + + #[test] + #[cfg(feature = "digest")] + fn hash_to_field_2() { + use sha2::Sha512; + let dst = "QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_RO_"; + + for (msg, expected_hash_hex_1, expected_hash_hex_2) in + crate::field::test::RFC_HASH_TO_FIELD_KAT_2 + { + let fe = FieldElement::hash_to_field::(&[msg], &[dst.as_bytes()]); + + let expected_fe_1 = fe_from_test_vector(expected_hash_hex_1); + assert_eq!(fe[0], expected_fe_1); + + let expected_fe_2 = fe_from_test_vector(expected_hash_hex_2); + assert_eq!(fe[1], expected_fe_2); + } + } } diff --git a/curve25519-dalek/src/montgomery.rs b/curve25519-dalek/src/montgomery.rs index 8f6fa4a..46a6b12 100644 --- a/curve25519-dalek/src/montgomery.rs +++ b/curve25519-dalek/src/montgomery.rs @@ -54,7 +54,9 @@ use core::{ ops::{Mul, MulAssign}, }; -use crate::constants::{APLUS2_OVER_FOUR, MONTGOMERY_A, MONTGOMERY_A_NEG}; +use crate::constants::APLUS2_OVER_FOUR; +#[cfg(feature = "digest")] +use crate::constants::{MONTGOMERY_A, MONTGOMERY_A_NEG, SQRT_M1}; use crate::edwards::{CompressedEdwardsY, EdwardsPoint}; use crate::field::FieldElement; use crate::scalar::{Scalar, clamp_integer}; @@ -62,12 +64,26 @@ use crate::scalar::{Scalar, clamp_integer}; use crate::traits::Identity; use subtle::Choice; +use subtle::ConditionallySelectable; use subtle::ConstantTimeEq; -use subtle::{ConditionallyNegatable, ConditionallySelectable}; #[cfg(feature = "zeroize")] use zeroize::Zeroize; +// We need the const 2^((p+3)/8) for elligator_encode. These defs are checked in tests::consts() +#[cfg(all(curve25519_dalek_bits = "32", feature = "digest"))] +const FE_C2: FieldElement = FieldElement::from_limbs([ + 34513073, 25610706, 9377949, 3500415, 12389472, 33281959, 41962654, 31548777, 326685, 11406482, +]); +#[cfg(all(curve25519_dalek_bits = "64", feature = "digest"))] +const FE_C2: FieldElement = FieldElement::from_limbs([ + 1718705420411057, + 234908883556509, + 2233514472574048, + 2117202627021982, + 765476049583133, +]); + /// Holds the \\(u\\)-coordinate of a point on the Montgomery form of /// Curve25519 or its twist. #[derive(Copy, Clone, Debug, Default)] @@ -252,33 +268,98 @@ impl MontgomeryPoint { } } -/// Perform the Elligator2 mapping to a Montgomery point. Returns a Montgomery point and a `Choice` -/// determining whether eps is a square. This is required by the standard to determine the -/// sign of the v coordinate. +#[cfg(feature = "digest")] +/// Perform the Elligator2 mapping to a tuple `(xn, xd, yn, yd)` such that +/// `(xn / xd, yn / yd)` is a point on curve25519. /// /// See -// -#[allow(unused)] -pub(crate) fn elligator_encode(r_0: &FieldElement) -> (MontgomeryPoint, Choice) { +pub(crate) fn elligator_encode( + u: &FieldElement, +) -> (FieldElement, FieldElement, FieldElement, FieldElement) { + // We follow https://www.rfc-editor.org/rfc/rfc9380.html#appendix-G.2.1 + + use core::ops::Neg; let one = FieldElement::ONE; - let d_1 = &one + &r_0.square2(); /* 2r^2 */ + let c2 = FE_C2; - let d = &MONTGOMERY_A_NEG * &(d_1.invert()); /* A/(1+2r^2) */ + // 1. tv1 = u^2 + // 2. tv1 = 2 * tv1 + let tv1 = u.square2(); + // 3. xd = tv1 + 1 + let xd = &one + &tv1; + // 4. x1n = -J + let x1n = MONTGOMERY_A_NEG; + // 5. tv2 = xd^2 + let tv2 = xd.square(); + // 6. gxd = tv2 * xd + let gxd = &tv2 * &xd; + // 7. gx1 = J * tv1 + let gx1 = &MONTGOMERY_A * &tv1; + // 8. gx1 = gx1 * x1n + let gx1 = &gx1 * &x1n; + // 9. gx1 = gx1 + tv2 + let gx1 = &gx1 + &tv2; + // 10. gx1 = gx1 * x1n + let gx1 = &gx1 * &x1n; + // 11. tv3 = gxd^2 + let tv3 = gxd.square(); + // 12. tv2 = tv3^2 + let tv2 = tv3.square(); + // 13. tv3 = tv3 * gxd + let tv3 = &tv3 * &gxd; + // 14. tv3 = tv3 * gx1 + let tv3 = &tv3 * &gx1; + // 15. tv2 = tv2 * tv3 + let tv2 = &tv2 * &tv3; + // 16. y11 = tv2^c4 + let y11 = tv2.pow_p58(); + // 17. y11 = y11 * tv3 + let y11 = &y11 * &tv3; + // 18. y12 = y11 * c3 + let y12 = &y11 * &SQRT_M1; + // 19. tv2 = y11^2 + let tv2 = y11.square(); + // 20. tv2 = tv2 * gxd + let tv2 = &tv2 * &gxd; + // 21. e1 = tv2 == gx1 + let e1 = tv2.ct_eq(&gx1); + // 22. y1 = CMOV(y12, y11, e1) + let y1 = FieldElement::conditional_select(&y12, &y11, e1); + // 23. x2n = x1n * tv1 + let x2n = &x1n * &tv1; + // 24. y21 = y11 * u + let y21 = &y11 * u; + // 25. y21 = y21 * c2 + let y21 = &y21 * &c2; + // 26. y22 = y21 * c3 + let y22 = &y21 * &SQRT_M1; + // 27. gx2 = gx1 * tv1 + let gx2 = &gx1 * &tv1; + // 28. tv2 = y21^2 + let tv2 = y21.square(); + // 29. tv2 = tv2 * gxd + let tv2 = &tv2 * &gxd; + // 30. e2 = tv2 == gx2 + let e2 = tv2.ct_eq(&gx2); + // 31. y2 = CMOV(y22, y21, e2) + let y2 = FieldElement::conditional_select(&y22, &y21, e2); + // 32. tv2 = y1^2 + let tv2 = y1.square(); + // 33. tv2 = tv2 * gxd + let tv2 = &tv2 * &gxd; + // 34. e3 = tv2 == gx1 + let e3 = tv2.ct_eq(&gx1); + // 35. xn = CMOV(x2n, x1n, e3) + let xn = FieldElement::conditional_select(&x2n, &x1n, e3); + // 36. y = CMOV(y2, y1, e3) + let y = FieldElement::conditional_select(&y2, &y1, e3); + // 37. e4 = sgn0(y) == 1 + let e4 = y.is_negative(); + // 38. y = CMOV(y, -y, e3 XOR e4) + let y = FieldElement::conditional_select(&y, &y.neg(), e3 ^ e4); + // 39. return (xn, xd, y, 1) - let d_sq = &d.square(); - let au = &MONTGOMERY_A * &d; - - let inner = &(d_sq + &au) + &one; - let eps = &d * &inner; /* eps = d^3 + Ad^2 + d */ - - let (eps_is_sq, _eps) = FieldElement::sqrt_ratio_i(&eps, &one); - - let zero = FieldElement::ZERO; - let Atemp = FieldElement::conditional_select(&MONTGOMERY_A, &zero, eps_is_sq); /* 0, or A if nonsquare*/ - let mut u = &d + &Atemp; /* d, or d+A if nonsquare */ - u.conditional_negate(!eps_is_sq); /* d, or -d-A if nonsquare */ - - (MontgomeryPoint(u.to_bytes()), eps_is_sq) + (xn, xd, y, one) } /// A `ProjectivePoint` holds a point on the projective line @@ -434,9 +515,6 @@ mod test { use super::*; use crate::constants; - #[cfg(feature = "alloc")] - use alloc::vec::Vec; - use rand_core::{CryptoRng, RngCore, TryRngCore}; #[test] @@ -639,6 +717,7 @@ mod test { } #[cfg(feature = "alloc")] + #[cfg(feature = "digest")] const ELLIGATOR_CORRECT_OUTPUT: [u8; 32] = [ 0x5f, 0x35, 0x20, 0x00, 0x1c, 0x6c, 0x99, 0x36, 0xa3, 0x12, 0x06, 0xaf, 0xe7, 0xc7, 0xac, 0x22, 0x4e, 0x88, 0x61, 0x61, 0x9b, 0xf9, 0x88, 0x72, 0x44, 0x49, 0x15, 0x89, 0x9d, 0x95, @@ -647,20 +726,37 @@ mod test { #[test] #[cfg(feature = "alloc")] + #[cfg(feature = "digest")] fn montgomery_elligator_correct() { + use alloc::vec::Vec; let bytes: Vec = (0u8..32u8).collect(); let bits_in: [u8; 32] = (&bytes[..]).try_into().expect("Range invariant broken"); let fe = FieldElement::from_bytes(&bits_in); - let (eg, _) = elligator_encode(&fe); - assert_eq!(eg.to_bytes(), ELLIGATOR_CORRECT_OUTPUT); + let (un, ud, ..) = elligator_encode(&fe); + let u = &un * &ud.invert(); + assert_eq!(u.to_bytes(), ELLIGATOR_CORRECT_OUTPUT); } #[test] + #[cfg(feature = "digest")] fn montgomery_elligator_zero_zero() { let zero = [0u8; 32]; let fe = FieldElement::from_bytes(&zero); - let (eg, _) = elligator_encode(&fe); - assert_eq!(eg.to_bytes(), zero); + let (un, ud, ..) = elligator_encode(&fe); + let u = &un * &ud.invert(); + assert_eq!(u.to_bytes(), zero); + } + + // Check that FE_C2 is correctly defined + #[test] + #[cfg(feature = "digest")] + fn c2() { + let one = FieldElement::ONE; + let two = &one + &one; + // c2 = 2^((p+3)/8) = 2^((p-5)/8 + 8/8) = 2*2^((p-5)/8) + let c2 = &two * &(two.pow_p58()); + + assert_eq!(c2, FE_C2); } }