libcrux/
ecdh.rs

1//! # ECDH
2//!
3//! Depending on the platform and available features the most efficient implementation
4//! is chosen.
5//!
6//! ## x25519
7//! For x25519 the portable HACL implementation is used unless running on an x64
8//! CPU with BMI2 and ADX support. In this case the libjade implementation is
9//! used.
10//!
11//! ## P256
12//! For P256 the portable HACL implementation is used.
13
14use crate::hacl;
15
16#[derive(Debug, PartialEq, Eq)]
17pub enum LowLevelError {
18    Jasmin(String),
19    Hacl(hacl::Error),
20}
21
22#[derive(Debug, PartialEq, Eq)]
23pub enum Error {
24    InvalidPoint,
25    InvalidScalar,
26    UnknownAlgorithm,
27    KeyGenError,
28    Custom(String),
29    Wrap(LowLevelError),
30}
31
32impl From<hacl::p256::Error> for Error {
33    fn from(value: hacl::p256::Error) -> Self {
34        Error::Wrap(LowLevelError::Hacl(hacl::Error::P256(value)))
35    }
36}
37
38/// ECDH algorithm.
39#[derive(Debug, PartialEq, Eq, Clone, Copy)]
40pub enum Algorithm {
41    X25519,
42    X448,
43    P256,
44    P384,
45    P521,
46}
47
48pub(crate) mod x25519 {
49    use rand::{CryptoRng, Rng};
50
51    use super::Error;
52
53    pub struct PrivateKey(pub [u8; 32]);
54    pub struct PublicKey(pub [u8; 32]);
55
56    impl From<&[u8; 32]> for PublicKey {
57        fn from(value: &[u8; 32]) -> Self {
58            Self(value.clone())
59        }
60    }
61
62    impl TryFrom<&[u8]> for PublicKey {
63        type Error = Error;
64
65        fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
66            Ok(Self(value.try_into().map_err(|_| Error::InvalidPoint)?))
67        }
68    }
69
70    impl TryFrom<crate::kem::PublicKey> for PublicKey {
71        type Error = Error;
72
73        fn try_from(value: crate::kem::PublicKey) -> Result<Self, Self::Error> {
74            if let crate::kem::PublicKey::X25519(k) = value {
75                Ok(k)
76            } else {
77                Err(Error::InvalidPoint)
78            }
79        }
80    }
81
82    impl From<&[u8; 32]> for PrivateKey {
83        fn from(value: &[u8; 32]) -> Self {
84            Self(value.clone())
85        }
86    }
87
88    impl TryFrom<&[u8]> for PrivateKey {
89        type Error = Error;
90
91        fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
92            Ok(Self(value.try_into().map_err(|_| Error::InvalidScalar)?))
93        }
94    }
95
96    impl TryFrom<crate::kem::PrivateKey> for PrivateKey {
97        type Error = Error;
98
99        fn try_from(value: crate::kem::PrivateKey) -> Result<Self, Self::Error> {
100            if let crate::kem::PrivateKey::X25519(k) = value {
101                Ok(k)
102            } else {
103                Err(Error::InvalidScalar)
104            }
105        }
106    }
107
108    impl AsRef<[u8]> for PrivateKey {
109        fn as_ref(&self) -> &[u8] {
110            &self.0
111        }
112    }
113
114    impl AsRef<[u8]> for PublicKey {
115        fn as_ref(&self) -> &[u8] {
116            &self.0
117        }
118    }
119
120    impl AsRef<[u8; 32]> for PrivateKey {
121        fn as_ref(&self) -> &[u8; 32] {
122            &self.0
123        }
124    }
125
126    impl AsRef<[u8; 32]> for PublicKey {
127        fn as_ref(&self) -> &[u8; 32] {
128            &self.0
129        }
130    }
131
132    #[cfg(all(bmi2, adx, target_arch = "x86_64"))]
133    pub(crate) fn derive(p: &PublicKey, s: &PrivateKey) -> Result<PublicKey, Error> {
134        use crate::hacl::curve25519;
135        use libcrux_platform::x25519_support;
136        // On x64 we use vale if available or hacl as fallback.
137        // Jasmin exists but is not verified yet.
138
139        if x25519_support() {
140            curve25519::vale::ecdh(s, p)
141                .map_err(|e| Error::Custom(format!("HACL Error {:?}", e)))
142                .map(|p| PublicKey(p))
143            // XXX: not verified yet
144            // crate::jasmin::x25519::mulx::derive(s, p)
145            //     .map_err(|e| Error::Custom(format!("Libjade Error {:?}", e)))
146        } else {
147            curve25519::ecdh(s, p)
148                .map_err(|e| Error::Custom(format!("HACL Error {:?}", e)))
149                .map(|p| PublicKey(p))
150            // XXX: not verified yet
151            // crate::jasmin::x25519::derive(s, p)
152            //     .map_err(|e| Error::Custom(format!("Libjade Error {:?}", e)))
153        }
154    }
155
156    #[cfg(any(
157        all(target_arch = "x86_64", any(not(bmi2), not(adx))),
158        target_arch = "x86"
159    ))]
160    pub(crate) fn derive(p: &PublicKey, s: &PrivateKey) -> Result<PublicKey, Error> {
161        use crate::hacl::curve25519;
162        // On x64 we use vale if available or hacl as fallback.
163        // Jasmin exists but is not verified yet.
164
165        curve25519::ecdh(s, p)
166            .map_err(|e| Error::Custom(format!("HACL Error {:?}", e)))
167            .map(|p| PublicKey(p))
168        // XXX: not verified yet
169        // crate::jasmin::x25519::derive(s, p)
170        //     .map_err(|e| Error::Custom(format!("Libjade Error {:?}", e)))
171    }
172
173    #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
174    pub(crate) fn derive(p: &PublicKey, s: &PrivateKey) -> Result<PublicKey, Error> {
175        // On any other platform we use the portable HACL implementation.
176        use crate::hacl::curve25519;
177
178        curve25519::ecdh(s, p)
179            .map_err(|e| Error::Custom(format!("HACL Error {:?}", e)))
180            .map(|p| PublicKey(p))
181    }
182
183    // XXX: libjade's secret to public is broken on Windows (overflows the stack).
184    // #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
185    // pub(super) fn secret_to_public(p: &PrivateKey) -> Result<[u8; 32], Error> {
186    //     crate::jasmin::x25519::secret_to_public(s).map_err(|e| Error::Custom(format!("Libjade Error {:?}", e)))
187    // }
188
189    // #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
190    pub(crate) fn secret_to_public(s: &PrivateKey) -> Result<PublicKey, Error> {
191        // On any other platform we use the portable HACL implementation.
192        use crate::hacl::curve25519;
193
194        Ok(PublicKey(curve25519::secret_to_public(s)))
195    }
196
197    /// Generate a new x25519 secret.
198    pub fn generate_secret(rng: &mut (impl CryptoRng + Rng)) -> Result<PrivateKey, Error> {
199        const LIMIT: usize = 100;
200        for _ in 0..LIMIT {
201            let mut out = [0u8; 32];
202            rng.try_fill_bytes(&mut out)
203                .map_err(|_| Error::KeyGenError)?;
204
205            // We don't want a 0 key.
206            if out.iter().all(|&b| b == 0) {
207                continue;
208            }
209
210            // We clamp the key already to make sure it can't be misused.
211            out[0] = out[0] & 248u8;
212            out[31] = out[31] & 127u8;
213            out[31] = out[31] | 64u8;
214
215            return Ok(PrivateKey(out));
216        }
217
218        Err(Error::KeyGenError)
219    }
220
221    /// Generate a new P256 key pair
222    pub fn key_gen(rng: &mut (impl CryptoRng + Rng)) -> Result<(PrivateKey, PublicKey), Error> {
223        let sk = generate_secret(rng)?;
224        let pk = secret_to_public(&sk)?;
225        Ok((sk, pk))
226    }
227}
228
229pub use x25519::generate_secret as x25519_generate_secret;
230pub use x25519::key_gen as x25519_key_gen;
231
232pub(crate) mod p256 {
233    use rand::{CryptoRng, Rng};
234
235    // P256 we only have in HACL
236    use crate::hacl::p256;
237
238    use super::Error;
239
240    pub struct PrivateKey(pub [u8; 32]);
241    pub struct PublicKey(pub [u8; 64]);
242
243    impl From<&[u8; 64]> for PublicKey {
244        fn from(value: &[u8; 64]) -> Self {
245            Self(value.clone())
246        }
247    }
248
249    impl TryFrom<&[u8]> for PublicKey {
250        type Error = Error;
251
252        fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
253            Ok(Self(value.try_into().map_err(|_| Error::InvalidPoint)?))
254        }
255    }
256
257    impl From<&[u8; 32]> for PrivateKey {
258        fn from(value: &[u8; 32]) -> Self {
259            Self(value.clone())
260        }
261    }
262
263    impl TryFrom<&[u8]> for PrivateKey {
264        type Error = Error;
265
266        fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
267            Ok(Self(value.try_into().map_err(|_| Error::InvalidScalar)?))
268        }
269    }
270
271    impl AsRef<[u8]> for PrivateKey {
272        fn as_ref(&self) -> &[u8] {
273            &self.0
274        }
275    }
276
277    impl AsRef<[u8]> for PublicKey {
278        fn as_ref(&self) -> &[u8] {
279            &self.0
280        }
281    }
282
283    impl AsRef<[u8; 32]> for PrivateKey {
284        fn as_ref(&self) -> &[u8; 32] {
285            &self.0
286        }
287    }
288
289    impl AsRef<[u8; 64]> for PublicKey {
290        fn as_ref(&self) -> &[u8; 64] {
291            &self.0
292        }
293    }
294
295    pub(super) fn derive(p: &PublicKey, s: &PrivateKey) -> Result<PublicKey, Error> {
296        // We assume that the private key has been validated.
297        p256::ecdh(s, p)
298            .map_err(|e| Error::Custom(format!("HACL Error {:?}", e)))
299            .map(|p| PublicKey(p))
300    }
301
302    pub(super) fn secret_to_public(s: &PrivateKey) -> Result<PublicKey, Error> {
303        p256::validate_scalar(s).map_err(|e| Error::Custom(format!("HACL Error {:?}", e)))?;
304        p256::secret_to_public(s)
305            .map_err(|e| Error::Custom(format!("HACL Error {:?}", e)))
306            .map(|p| PublicKey(p))
307    }
308
309    pub fn validate_scalar(s: &PrivateKey) -> Result<(), Error> {
310        p256::validate_scalar(s).map_err(|e| e.into())
311    }
312
313    #[allow(unused)]
314    pub fn validate_point(p: &PublicKey) -> Result<(), Error> {
315        p256::validate_point(p).map_err(|e| e.into())
316    }
317
318    pub(crate) fn prepare_public_key(public_key: &[u8]) -> Result<PublicKey, Error> {
319        if public_key.is_empty() {
320            return Err(Error::InvalidPoint);
321        }
322
323        // Parse the public key.
324        let pk = if let Ok(pk) = p256::uncompressed_to_coordinates(public_key) {
325            pk
326        } else {
327            // Might be uncompressed
328            if public_key.len() == 33 {
329                p256::compressed_to_coordinates(public_key).map_err(|_| Error::InvalidPoint)?
330            } else {
331                // Might be a simple concatenation
332                public_key.try_into().map_err(|_| Error::InvalidPoint)?
333            }
334        };
335        let pk = PublicKey(pk);
336
337        p256::validate_point(&pk)
338            .map(|()| pk)
339            .map_err(|_| Error::InvalidPoint)
340    }
341
342    /// Generate a new p256 secret (scalar)
343    pub fn generate_secret(rng: &mut (impl CryptoRng + Rng)) -> Result<PrivateKey, Error> {
344        const LIMIT: usize = 100;
345        for _ in 0..LIMIT {
346            let mut out = [0u8; 32];
347            rng.try_fill_bytes(&mut out)
348                .map_err(|_| Error::KeyGenError)?;
349
350            let out = PrivateKey(out);
351            if validate_scalar(&out).is_ok() {
352                return Ok(out);
353            }
354        }
355        Err(Error::KeyGenError)
356    }
357
358    /// Generate a new P256 key pair
359    pub fn key_gen(rng: &mut (impl CryptoRng + Rng)) -> Result<(PrivateKey, PublicKey), Error> {
360        let sk = generate_secret(rng)?;
361        let pk = secret_to_public(&sk)?;
362        Ok((sk, pk))
363    }
364}
365
366pub use p256::generate_secret as p256_generate_secret;
367pub use p256::key_gen as p256_key_gen;
368pub use p256::validate_scalar as p256_validate_scalar;
369
370/// Derive the ECDH shared secret.
371/// Returns `Ok(point * scalar)` on the provided curve [`Algorithm`] or an error.
372pub fn derive(
373    alg: Algorithm,
374    point: impl AsRef<[u8]>,
375    scalar: impl AsRef<[u8]>,
376) -> Result<Vec<u8>, Error> {
377    match alg {
378        Algorithm::X25519 => {
379            x25519::derive(&point.as_ref().try_into()?, &scalar.as_ref().try_into()?)
380                .map(|r| r.0.into())
381        }
382        Algorithm::P256 => {
383            let point = p256::prepare_public_key(point.as_ref())?;
384            let scalar = hacl::p256::validate_scalar_slice(scalar.as_ref())
385                .map_err(|_| Error::InvalidScalar)?;
386
387            p256::derive(&point, &scalar).map(|r| r.0.into())
388        }
389        _ => Err(Error::UnknownAlgorithm),
390    }
391}
392
393pub(crate) fn p256_derive(
394    point: &p256::PublicKey,
395    scalar: &p256::PrivateKey,
396) -> Result<p256::PublicKey, Error> {
397    p256::validate_point(point)?;
398    p256::validate_scalar(scalar)?;
399
400    p256::derive(&point, &scalar)
401}
402
403/// Derive the public key for the provided secret key `scalar`.
404pub fn secret_to_public(alg: Algorithm, scalar: impl AsRef<[u8]>) -> Result<Vec<u8>, Error> {
405    match alg {
406        Algorithm::X25519 => {
407            x25519::secret_to_public(&scalar.as_ref().try_into()?).map(|r| r.0.into())
408        }
409        Algorithm::P256 => p256::secret_to_public(&scalar.as_ref().try_into()?).map(|r| r.0.into()),
410        _ => Err(Error::UnknownAlgorithm),
411    }
412}
413
414/// Validate a secret key.
415pub fn validate_scalar(alg: Algorithm, s: impl AsRef<[u8]>) -> Result<(), Error> {
416    match alg {
417        Algorithm::X25519 => {
418            if s.as_ref().iter().all(|&b| b == 0) {
419                Err(Error::InvalidScalar)
420            } else {
421                Ok(())
422            }
423        }
424        Algorithm::P256 => p256::validate_scalar(&s.as_ref().try_into()?),
425        _ => Err(Error::UnknownAlgorithm),
426    }
427}
428
429use rand::{CryptoRng, Rng};
430
431/// Generate a new private key scalar.
432///
433/// The function returns the new scalar or an [`Error::KeyGenError`] if it was unable to
434/// generate a new key. If this happens, the provided `rng` is probably faulty.
435pub fn generate_secret(alg: Algorithm, rng: &mut (impl CryptoRng + Rng)) -> Result<Vec<u8>, Error> {
436    match alg {
437        Algorithm::X25519 => x25519::generate_secret(rng).map(|k| k.0.to_vec()),
438        Algorithm::P256 => p256::generate_secret(rng).map(|k| k.0.to_vec()),
439        _ => Err(Error::UnknownAlgorithm),
440    }
441}
442
443/// Generate a fresh key pair.
444///
445/// The function returns the (secret key, public key) tuple, or an [`Error`].
446pub fn key_gen(
447    alg: Algorithm,
448    rng: &mut (impl CryptoRng + Rng),
449) -> Result<(Vec<u8>, Vec<u8>), Error> {
450    let sk = generate_secret(alg, rng)?;
451    let pk = secret_to_public(alg, &sk)?;
452    Ok((sk, pk))
453}