diff --git a/Cargo.lock b/Cargo.lock index 586a43f..3c2838a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,6 +280,12 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +[[package]] +name = "fiat-crypto" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6ab97095615857b6ad00a8330fff0e443f1def9fd357cef82d0ca0677b616b" + [[package]] name = "fnv" version = "1.0.7" @@ -465,6 +471,7 @@ dependencies = [ "curve25519-dalek", "digest", "displaydoc", + "fiat-crypto", "generic-array", "hex", "hkdf", @@ -476,6 +483,7 @@ dependencies = [ "scrypt", "serde_json", "sha2", + "subtle", "thiserror", "x25519-dalek", "zeroize", diff --git a/Cargo.toml b/Cargo.toml index 66e49b9..2e9be34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,12 +20,14 @@ u32_backend = ["curve25519-dalek/u32_backend", "x25519-dalek/u32_backend"] curve25519-dalek = { version = "3.0.0", default-features = false, features = ["std"] } digest = "0.9.0" displaydoc = "0.1.7" +fiat-crypto = { version = "0.1.5"} generic-array = "0.14.4" hkdf = "0.9.0" hmac = "0.9.0" rand_core = "0.5.1" scrypt = { version = "0.4.1", optional = true } sha2 = "0.9.1" +subtle = { version = "^2.2.1", default-features = false } thiserror = "1.0.20" x25519-dalek = { version = "1.0.1", default-features = false, features = ["std"] } zeroize = "1.1" diff --git a/src/elligator/field.rs b/src/elligator/field.rs new file mode 100644 index 0000000..1c070e7 --- /dev/null +++ b/src/elligator/field.rs @@ -0,0 +1,353 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. +#![allow(clippy::let_and_return)] + +//! Field arithmetic modulo \\(p = 2\^{255} - 19\\), using \\(64\\)-bit +//! limbs with \\(128\\)-bit products. + +use core::fmt::Debug; +use core::ops::Neg; +use core::ops::{Add, AddAssign}; +use core::ops::{Mul, MulAssign}; + +use subtle::Choice; +use subtle::ConditionallyNegatable; +use subtle::ConditionallySelectable; +use subtle::ConstantTimeEq; + +use zeroize::Zeroize; + +use fiat_crypto::curve25519_64::*; + +/// A `FieldElement51` represents an element of the field +/// \\( \mathbb Z / (2\^{255} - 19)\\). +/// +/// In the 64-bit implementation, a `FieldElement` is represented in +/// radix \\(2\^{51}\\) as five `u64`s; the coefficients are allowed to +/// grow up to \\(2\^{54}\\) between reductions modulo \\(p\\). +/// +/// # Note +/// +/// The `curve25519_dalek::field` module provides a type alias +/// `curve25519_dalek::field::FieldElement` to either `FieldElement51` +/// or `FieldElement2625`. +/// +/// The backend-specific type `FieldElement51` should not be used +/// outside of the `curve25519_dalek::field` module. +#[derive(Copy, Clone)] +pub struct FieldElement51(pub(crate) [u64; 5]); + +impl Debug for FieldElement51 { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "FieldElement51({:?})", &self.0[..]) + } +} + +impl Zeroize for FieldElement51 { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +impl ConstantTimeEq for FieldElement51 { + /// Test equality between two `FieldElement`s. Since the + /// internal representation is not canonical, the field elements + /// are normalized to wire format before comparison. + fn ct_eq(&self, other: &FieldElement51) -> Choice { + self.to_bytes().ct_eq(&other.to_bytes()) + } +} + +impl<'b> AddAssign<&'b FieldElement51> for FieldElement51 { + fn add_assign(&mut self, _rhs: &'b FieldElement51) { + let input = self.0; + fiat_25519_add(&mut self.0, &input, &_rhs.0); + let input = self.0; + fiat_25519_carry(&mut self.0, &input); + } +} + +impl<'a, 'b> Add<&'b FieldElement51> for &'a FieldElement51 { + type Output = FieldElement51; + fn add(self, _rhs: &'b FieldElement51) -> FieldElement51 { + let mut output = *self; + fiat_25519_add(&mut output.0, &self.0, &_rhs.0); + let input = output.0; + fiat_25519_carry(&mut output.0, &input); + output + } +} + +impl<'b> MulAssign<&'b FieldElement51> for FieldElement51 { + fn mul_assign(&mut self, _rhs: &'b FieldElement51) { + let input = self.0; + fiat_25519_carry_mul(&mut self.0, &input, &_rhs.0); + } +} + +impl<'a, 'b> Mul<&'b FieldElement51> for &'a FieldElement51 { + type Output = FieldElement51; + fn mul(self, _rhs: &'b FieldElement51) -> FieldElement51 { + let mut output = *self; + fiat_25519_carry_mul(&mut output.0, &self.0, &_rhs.0); + output + } +} + +impl<'a> Neg for &'a FieldElement51 { + type Output = FieldElement51; + fn neg(self) -> FieldElement51 { + let mut output = *self; + fiat_25519_opp(&mut output.0, &self.0); + let input = output.0; + fiat_25519_carry(&mut output.0, &input); + output + } +} + +impl ConditionallySelectable for FieldElement51 { + fn conditional_select( + a: &FieldElement51, + b: &FieldElement51, + choice: Choice, + ) -> FieldElement51 { + let mut output = [0u64; 5]; + fiat_25519_selectznz(&mut output, choice.unwrap_u8() as fiat_25519_u1, &a.0, &b.0); + FieldElement51(output) + } + + fn conditional_swap(a: &mut FieldElement51, b: &mut FieldElement51, choice: Choice) { + u64::conditional_swap(&mut a.0[0], &mut b.0[0], choice); + u64::conditional_swap(&mut a.0[1], &mut b.0[1], choice); + u64::conditional_swap(&mut a.0[2], &mut b.0[2], choice); + u64::conditional_swap(&mut a.0[3], &mut b.0[3], choice); + u64::conditional_swap(&mut a.0[4], &mut b.0[4], choice); + } + + fn conditional_assign(&mut self, _rhs: &FieldElement51, choice: Choice) { + let mut output = [0u64; 5]; + let choicebit = choice.unwrap_u8() as fiat_25519_u1; + fiat_25519_cmovznz_u64(&mut output[0], choicebit, self.0[0], _rhs.0[0]); + fiat_25519_cmovznz_u64(&mut output[1], choicebit, self.0[1], _rhs.0[1]); + fiat_25519_cmovznz_u64(&mut output[2], choicebit, self.0[2], _rhs.0[2]); + fiat_25519_cmovznz_u64(&mut output[3], choicebit, self.0[3], _rhs.0[3]); + fiat_25519_cmovznz_u64(&mut output[4], choicebit, self.0[4], _rhs.0[4]); + *self = FieldElement51(output); + } +} + +impl FieldElement51 { + /// Construct zero. + pub fn zero() -> FieldElement51 { + FieldElement51([0, 0, 0, 0, 0]) + } + + /// Construct one. + pub fn one() -> FieldElement51 { + FieldElement51([1, 0, 0, 0, 0]) + } + + pub fn is_negative(&self) -> Choice { + let bytes = self.to_bytes(); + (bytes[0] & 1).into() + } + + /// Raise this field element to the power (p-5)/8 = 2^252 -3. + fn pow_p58(&self) -> FieldElement51 { + // The bits of (p-5)/8 are 101111.....11. + // + // nonzero bits of exponent + let (t19, _) = self.pow22501(); // 249..0 + let t20 = t19.pow2k(2); // 251..2 + let t21 = self * &t20; // 251..2,0 + + t21 + } + + /// Given a nonzero field element, compute its inverse. + /// + /// The inverse is computed as self^(p-2), since + /// x^(p-2)x = x^(p-1) = 1 (mod p). + /// + /// This function returns zero on input zero. + pub fn invert(&self) -> FieldElement51 { + // The bits of p-2 = 2^255 -19 -2 are 11010111111...11. + // + // nonzero bits of exponent + let (t19, t3) = self.pow22501(); // t19: 249..0 ; t3: 3,1,0 + let t20 = t19.pow2k(5); // 254..5 + let t21 = &t20 * &t3; // 254..5,3,1,0 + + t21 + } + + /// Compute (self^(2^250-1), self^11), used as a helper function + /// within invert() and pow22523(). + fn pow22501(&self) -> (FieldElement51, FieldElement51) { + // Instead of managing which temporary variables are used + // for what, we define as many as we need and leave stack + // allocation to the compiler + // + // Each temporary variable t_i is of the form (self)^e_i. + // Squaring t_i corresponds to multiplying e_i by 2, + // so the pow2k function shifts e_i left by k places. + // Multiplying t_i and t_j corresponds to adding e_i + e_j. + // + // Temporary t_i Nonzero bits of e_i + // + let t0 = self.square(); // 1 e_0 = 2^1 + let t1 = t0.square().square(); // 3 e_1 = 2^3 + let t2 = self * &t1; // 3,0 e_2 = 2^3 + 2^0 + let t3 = &t0 * &t2; // 3,1,0 + let t4 = t3.square(); // 4,2,1 + let t5 = &t2 * &t4; // 4,3,2,1,0 + let t6 = t5.pow2k(5); // 9,8,7,6,5 + let t7 = &t6 * &t5; // 9,8,7,6,5,4,3,2,1,0 + let t8 = t7.pow2k(10); // 19..10 + let t9 = &t8 * &t7; // 19..0 + let t10 = t9.pow2k(20); // 39..20 + let t11 = &t10 * &t9; // 39..0 + let t12 = t11.pow2k(10); // 49..10 + let t13 = &t12 * &t7; // 49..0 + let t14 = t13.pow2k(50); // 99..50 + let t15 = &t14 * &t13; // 99..0 + let t16 = t15.pow2k(100); // 199..100 + let t17 = &t16 * &t15; // 199..0 + let t18 = t17.pow2k(50); // 249..50 + let t19 = &t18 * &t13; // 249..0 + + (t19, t3) + } + + /// Load a `FieldElement51` from the low 255 bits of a 256-bit + /// input. + /// + /// # Warning + /// + /// This function does not check that the input used the canonical + /// representative. It masks the high bit, but it will happily + /// decode 2^255 - 18 to 1. Applications that require a canonical + /// encoding of every field element should decode, re-encode to + /// the canonical encoding, and check that the input was + /// canonical. + /// + pub fn from_bytes(bytes: &[u8; 32]) -> FieldElement51 { + let mut temp = [0u8; 32]; + temp.copy_from_slice(bytes); + temp[31] &= 127u8; + let mut output = [0u64; 5]; + fiat_25519_from_bytes(&mut output, &temp); + FieldElement51(output) + } + + /// Serialize this `FieldElement51` to a 32-byte array. The + /// encoding is canonical. + pub fn to_bytes(&self) -> [u8; 32] { + let mut bytes = [0u8; 32]; + fiat_25519_to_bytes(&mut bytes, &self.0); + bytes + } + + /// Given `k > 0`, return `self^(2^k)`. + pub fn pow2k(&self, mut k: u32) -> FieldElement51 { + let mut output = *self; + loop { + let input = output.0; + fiat_25519_carry_square(&mut output.0, &input); + k -= 1; + if k == 0 { + return output; + } + } + } + + /// Given `FieldElements` `u` and `v`, compute either `sqrt(u/v)` + /// or `sqrt(i*u/v)` in constant time. + /// + /// This function always returns the nonnegative square root. + /// + /// # Return + /// + /// - `(Choice(1), +sqrt(u/v)) ` if `v` is nonzero and `u/v` is square; + /// - `(Choice(1), zero) ` if `u` is zero; + /// - `(Choice(0), zero) ` if `v` is zero and `u` is nonzero; + /// - `(Choice(0), +sqrt(i*u/v))` if `u/v` is nonsquare (so `i*u/v` is square). + /// + pub fn sqrt_ratio_i(u: &FieldElement51, v: &FieldElement51) -> (Choice, FieldElement51) { + // Using the same trick as in ed25519 decoding, we merge the + // inversion, the square root, and the square test as follows. + // + // To compute sqrt(α), we can compute β = α^((p+3)/8). + // Then β^2 = ±α, so multiplying β by sqrt(-1) if necessary + // gives sqrt(α). + // + // To compute 1/sqrt(α), we observe that + // 1/β = α^(p-1 - (p+3)/8) = α^((7p-11)/8) + // = α^3 * (α^7)^((p-5)/8). + // + // We can therefore compute sqrt(u/v) = sqrt(u)/sqrt(v) + // by first computing + // r = u^((p+3)/8) v^(p-1-(p+3)/8) + // = u u^((p-5)/8) v^3 (v^7)^((p-5)/8) + // = (uv^3) (uv^7)^((p-5)/8). + // + // If v is nonzero and u/v is square, then r^2 = ±u/v, + // so vr^2 = ±u. + // If vr^2 = u, then sqrt(u/v) = r. + // If vr^2 = -u, then sqrt(u/v) = r*sqrt(-1). + // + // If v is zero, r is also zero. + + let v3 = &v.square() * v; + let v7 = &v3.square() * v; + let mut r = &(u * &v3) * &(u * &v7).pow_p58(); + let check = v * &r.square(); + + let i = &SQRT_M1; + + let correct_sign_sqrt = check.ct_eq(u); + let flipped_sign_sqrt = check.ct_eq(&(-u)); + let flipped_sign_sqrt_i = check.ct_eq(&(&(-u) * i)); + + let r_prime = &SQRT_M1 * &r; + r.conditional_assign(&r_prime, flipped_sign_sqrt | flipped_sign_sqrt_i); + + // Choose the nonnegative square root. + let r_is_negative = r.is_negative(); + r.conditional_negate(r_is_negative); + + let was_nonzero_square = correct_sign_sqrt | flipped_sign_sqrt; + + (was_nonzero_square, r) + } + + /// Returns the square of this field element. + pub fn square(&self) -> FieldElement51 { + let mut output = *self; + fiat_25519_carry_square(&mut output.0, &self.0); + output + } + + /// Returns 2 times the square of this field element. + pub fn square2(&self) -> FieldElement51 { + let mut output = *self; + let mut temp = *self; + // Void vs return type, measure cost of copying self + fiat_25519_carry_square(&mut temp.0, &self.0); + fiat_25519_add(&mut output.0, &temp.0, &temp.0); + let input = output.0; + fiat_25519_carry(&mut output.0, &input); + output + } +} + +/// Precomputed value of one of the square roots of -1 (mod p) +pub(crate) const SQRT_M1: FieldElement51 = FieldElement51([ + 1718705420411056, + 234908883556509, + 2233514472574048, + 2117202627021982, + 765476049583133, +]); diff --git a/src/elligator/mod.rs b/src/elligator/mod.rs new file mode 100644 index 0000000..76dc438 --- /dev/null +++ b/src/elligator/mod.rs @@ -0,0 +1,116 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. +#![allow(non_snake_case)] + +mod field; + +use curve25519_dalek::{edwards::EdwardsPoint, montgomery::MontgomeryPoint, scalar::Scalar}; +use field::FieldElement51; +use sha2::Digest; +use subtle::{ConditionallyNegatable, ConditionallySelectable}; + +const MONT_A: FieldElement51 = FieldElement51([486662, 0, 0, 0, 0]); + +fn elligator_signal(r_0: &FieldElement51) -> MontgomeryPoint { + let minus_a = -&MONT_A; /* A = 486662 */ + let one = FieldElement51::one(); + let d_1 = &one + &r_0.square2(); /* 2r^2 */ + + let d = &minus_a * &(d_1.invert()); /* A/(1+2r^2) */ + + let d_sq = &d.square(); + let au = &MONT_A * &d; + + let inner = &(d_sq + &au) + &one; + let eps = &d * &inner; /* eps = d^3 + Ad^2 + d */ + + let (eps_is_sq, _eps) = FieldElement51::sqrt_ratio_i(&eps, &one); + + let zero = FieldElement51::zero(); + let Atemp = FieldElement51::conditional_select(&MONT_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()) +} + +fn hash_to_point(bytes: &[u8]) -> EdwardsPoint { + let mut hash = sha2::Sha512::new(); + hash.update(bytes); + let h = hash.finalize(); + let mut res = [0u8; 32]; + res.copy_from_slice(&h[..32]); + + let sign_bit = (res[31] & 0x80) >> 7; + + let fe = FieldElement51::from_bytes(&res); + + let M1 = elligator_signal(&fe); + let E1_opt = M1.to_edwards(sign_bit); + + E1_opt + .expect("Montgomery conversion to Edwards point in Elligator failed") + .mul_by_cofactor() +} + +#[cfg(test)] +mod tests { + use super::*; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Signal tests from // + // https://github.com/signalapp/libsignal-protocol-c/blob/master/src/curve25519/ed25519/tests/internal_fast_tests.c#L222-L282 // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + 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, + 0xf4, 0x6e, + ]; + + #[test] + fn elligator_correct() { + let bytes: Vec = (0u8..32u8).collect(); + let mut bits_in = [0u8; 32]; + bits_in.copy_from_slice(&bytes); + let fe = FieldElement51::from_bytes(&bits_in); + let eg = elligator_signal(&fe); + assert_eq!(eg.to_bytes(), ELLIGATOR_CORRECT_OUTPUT); + } + + #[test] + fn elligator_zero_zero() { + let zero = [0u8; 32]; + let fe = FieldElement51::from_bytes(&zero); + let eg = elligator_signal(&fe); + assert_eq!(eg.to_bytes(), zero); + } + + const HASHTOPOINT_CORRECT_OUTPUT1: [u8; 32] = [ + 0xce, 0x89, 0x9f, 0xb2, 0x8f, 0xf7, 0x20, 0x91, 0x5e, 0x14, 0xf5, 0xb7, 0x99, 0x08, 0xab, + 0x17, 0xaa, 0x2e, 0xe2, 0x45, 0xb4, 0xfc, 0x2b, 0xf6, 0x06, 0x36, 0x29, 0x40, 0xed, 0x7d, + 0xe7, 0xed, + ]; + + const HASHTOPOINT_CORRECT_OUTPUT2: [u8; 32] = [ + 0xa0, 0x35, 0xbb, 0xa9, 0x4d, 0x30, 0x55, 0x33, 0x0d, 0xce, 0xc2, 0x7f, 0x83, 0xde, 0x79, + 0xd0, 0x89, 0x67, 0x72, 0x4c, 0x07, 0x8d, 0x68, 0x9d, 0x61, 0x52, 0x1d, 0xf9, 0x2c, 0x5c, + 0xba, 0x77, + ]; + + #[test] + fn test_hash_to_point_1() { + let bits: Vec = (0u8..32u8).collect(); + let hashed = hash_to_point(&bits); + assert_eq!(hashed.compress().to_bytes(), HASHTOPOINT_CORRECT_OUTPUT1); + } + + #[test] + fn test_hash_to_point_2() { + let bits: Vec = (0u8..32u8).map(|u| u + 1).collect(); + let hashed = hash_to_point(&bits); + assert_eq!(hashed.compress().to_bytes(), HASHTOPOINT_CORRECT_OUTPUT2); + } +} diff --git a/src/lib.rs b/src/lib.rs index 9bd0657..1560775 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -413,7 +413,9 @@ pub mod ciphersuite; mod envelope; mod hash; +mod elligator; pub mod group; + pub mod map_to_curve; pub mod key_exchange;