Skip to main content

securedrop_protocol_minimal/primitives/
x25519.rs

1use crate::primitives::provider::{self, curve25519::ecdh};
2use anyhow::Error;
3use rand_core::{CryptoRng, RngCore};
4
5pub const DH_PUBLIC_KEY_LEN: usize = crate::primitives::provider::curve25519::PK_LEN;
6pub(crate) const DH_PRIVATE_KEY_LEN: usize = crate::primitives::provider::curve25519::SK_LEN;
7pub(crate) const DH_SHARED_SECRET_LEN: usize =
8    crate::primitives::provider::curve25519::LEN_DH_SHARE;
9
10/// An X25519 public key.
11#[derive(Debug, Clone, Copy)]
12pub struct DHPublicKey([u8; DH_PUBLIC_KEY_LEN]);
13
14impl DHPublicKey {
15    pub fn into_bytes(self) -> [u8; DH_PUBLIC_KEY_LEN] {
16        self.0
17    }
18
19    pub fn from_bytes(bytes: [u8; DH_PUBLIC_KEY_LEN]) -> Self {
20        Self(bytes)
21    }
22}
23
24/// An X25519 private key.
25#[derive(Debug, Clone)]
26pub struct DHPrivateKey([u8; DH_PRIVATE_KEY_LEN]);
27
28impl DHPrivateKey {
29    pub fn into_bytes(self) -> [u8; DH_PRIVATE_KEY_LEN] {
30        self.0
31    }
32
33    pub fn from_bytes(bytes: [u8; DH_PRIVATE_KEY_LEN]) -> Self {
34        Self(bytes)
35    }
36}
37
38/// An X25519 shared secret.
39#[derive(Debug, Clone)]
40pub struct DHSharedSecret([u8; 32]);
41
42impl DHSharedSecret {
43    pub fn into_bytes(self) -> [u8; 32] {
44        self.0
45    }
46
47    pub fn from_bytes(bytes: [u8; 32]) -> Self {
48        Self(bytes)
49    }
50}
51
52/// Generate DH keypair from external randomness
53/// FOR TEST PURPOSES ONLY
54pub fn deterministic_dh_keygen(randomness: [u8; 32]) -> Result<(DHPrivateKey, DHPublicKey), Error> {
55    let mut public_key = [0u8; DH_PUBLIC_KEY_LEN];
56    let mut secret_key = [0u8; DH_PRIVATE_KEY_LEN];
57
58    provider::curve25519::x25519_keygen(&mut public_key, &mut secret_key, &randomness)
59        .map_err(|_| anyhow::anyhow!("X25519 key generation failed"))?;
60
61    Ok((DHPrivateKey(secret_key), DHPublicKey(public_key)))
62}
63
64/// Generate a new DH key pair using X25519
65pub fn generate_dh_keypair<R: RngCore + CryptoRng>(
66    mut rng: R,
67) -> Result<(DHPrivateKey, DHPublicKey), Error> {
68    let mut randomness = [0u8; 32];
69    rng.fill_bytes(&mut randomness);
70
71    let mut public_key = [0u8; DH_PUBLIC_KEY_LEN];
72    let mut secret_key = [0u8; DH_PRIVATE_KEY_LEN];
73
74    // Generate the key pair using X25519 from libcrux
75    // Parameters: ek (public key), dk (secret key), rand (randomness)
76    provider::curve25519::x25519_keygen(&mut public_key, &mut secret_key, &randomness)
77        .map_err(|_| anyhow::anyhow!("X25519 key generation failed"))?;
78
79    typed(secret_key, public_key)
80}
81
82// Fixed-sized arrays are enforced by at compile-time, so type-checking
83// implies...
84#[cfg_attr(hax, hax_lib::ensures(|result| result.is_ok()))]
85fn typed(
86    sk: [u8; DH_PRIVATE_KEY_LEN],
87    pk: [u8; DH_PUBLIC_KEY_LEN],
88) -> Result<(DHPrivateKey, DHPublicKey), Error> {
89    Ok((DHPrivateKey(sk), DHPublicKey(pk)))
90}
91
92/// Generate a random scalar for DH operations using X25519
93pub fn generate_random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Result<[u8; 32], Error> {
94    let mut randomness = [0u8; 32];
95    rng.fill_bytes(&mut randomness);
96
97    let mut secret_key = [0u8; 32];
98    let mut _public_key = [0u8; 32]; // We don't need the public key here
99
100    // Generate the key pair using X25519 from libcrux
101    // Parameters: ek (public key), dk (secret key), rand (randomness)
102    provider::curve25519::x25519_keygen(&mut _public_key, &mut secret_key, &randomness)
103        .map_err(|_| anyhow::anyhow!("X25519 key generation failed"))?;
104
105    Ok(secret_key)
106}
107
108/// Convert a scalar to a DH public key using the X25519 standard generator base point
109///
110/// libcrux_curve25519::secret_to_public uses the standard X25519 base point G = 9
111/// (defined as [9, 0, 0, 0, ...] in the HACL implementation, see `g25519` in their code)
112pub fn dh_public_key_from_scalar(scalar: [u8; 32]) -> DHPublicKey {
113    let mut public_key_bytes = [0u8; 32];
114    provider::curve25519::secret_to_public(&mut public_key_bytes, &scalar);
115    DHPublicKey::from_bytes(public_key_bytes)
116}
117
118/// Compute DH shared secret
119pub fn dh_shared_secret(
120    public_key: &DHPublicKey,
121    private_scalar: [u8; 32],
122) -> Result<DHSharedSecret, Error> {
123    let mut shared_secret_bytes = [0u8; 32];
124    ecdh(&mut shared_secret_bytes, &public_key.0, &private_scalar)
125        .map_err(|_| anyhow::anyhow!("X25519 DH failed"))?;
126    Ok(DHSharedSecret(shared_secret_bytes))
127}
128
129#[cfg(test)]
130mod tests {
131    use crate::primitives::provider::curve25519::LEN_DH_SHARE;
132
133    use super::*;
134    use proptest::prelude::*;
135    use rand_chacha::ChaCha20Rng;
136    use rand_core::SeedableRng;
137
138    // Toy purposes
139    fn get_rng() -> ChaCha20Rng {
140        let mut seed = [0u8; 32];
141        getrandom::fill(&mut seed).expect("OS random source failed");
142        ChaCha20Rng::from_seed(seed)
143    }
144
145    #[test]
146    fn test_deterministic_dh_keygen() {
147        proptest!(|(randomness in proptest::array::uniform32(any::<u8>()))| {
148            let (private_key, public_key) = deterministic_dh_keygen(randomness).unwrap();
149        });
150    }
151
152    #[test]
153    fn test_dh_shared_secret() {
154        let mut rng = get_rng();
155
156        let (sk1, pk1) = generate_dh_keypair(&mut rng).expect("need dh keygen");
157
158        let (sk2, pk2) = generate_dh_keypair(&mut rng).expect("need dh keygen");
159
160        let ss1 = dh_shared_secret(&pk1, sk2.into_bytes()).expect("need shared secret 1");
161        let ss2 = dh_shared_secret(&pk2, sk1.into_bytes()).expect("need shared secret 2");
162
163        assert_eq!(ss1.clone().into_bytes(), ss2.into_bytes());
164        assert_ne!(ss1.into_bytes(), [0u8; LEN_DH_SHARE])
165    }
166}