securedrop_protocol_minimal/primitives/
x25519.rs1use anyhow::Error;
2use libcrux_curve25519::ecdh;
3use libcrux_traits::kem::arrayref::Kem;
4use rand_core::{CryptoRng, RngCore};
5
6pub use libcrux_curve25519::{DK_LEN as SK_LEN, EK_LEN as PK_LEN};
7
8#[derive(Debug, Clone, Copy)]
10pub struct DHPublicKey([u8; PK_LEN]);
11
12impl DHPublicKey {
13 pub fn into_bytes(self) -> [u8; PK_LEN] {
14 self.0
15 }
16
17 pub fn from_bytes(bytes: [u8; PK_LEN]) -> Self {
18 Self(bytes)
19 }
20}
21
22#[derive(Debug, Clone)]
24pub struct DHPrivateKey([u8; SK_LEN]);
25
26impl DHPrivateKey {
27 pub fn into_bytes(self) -> [u8; SK_LEN] {
28 self.0
29 }
30
31 pub fn from_bytes(bytes: [u8; SK_LEN]) -> Self {
32 Self(bytes)
33 }
34}
35
36#[derive(Debug, Clone)]
38pub struct DHSharedSecret([u8; 32]);
39
40impl DHSharedSecret {
41 pub fn into_bytes(self) -> [u8; 32] {
42 self.0
43 }
44
45 pub fn from_bytes(bytes: [u8; 32]) -> Self {
46 Self(bytes)
47 }
48}
49
50pub fn deterministic_dh_keygen(randomness: [u8; 32]) -> Result<(DHPrivateKey, DHPublicKey), Error> {
53 let mut public_key = [0u8; PK_LEN];
54 let mut secret_key = [0u8; SK_LEN];
55
56 libcrux_curve25519::X25519::keygen(&mut public_key, &mut secret_key, &randomness)
57 .map_err(|_| anyhow::anyhow!("X25519 key generation failed"))?;
58
59 Ok((DHPrivateKey(secret_key), DHPublicKey(public_key)))
60}
61
62pub fn generate_dh_keypair<R: RngCore + CryptoRng>(
64 mut rng: R,
65) -> Result<(DHPrivateKey, DHPublicKey), Error> {
66 let mut randomness = [0u8; 32];
67 rng.fill_bytes(&mut randomness);
68
69 let mut public_key = [0u8; PK_LEN];
70 let mut secret_key = [0u8; SK_LEN];
71
72 libcrux_curve25519::X25519::keygen(&mut public_key, &mut secret_key, &randomness)
75 .map_err(|_| anyhow::anyhow!("X25519 key generation failed"))?;
76
77 typed(secret_key, public_key)
78}
79
80#[cfg_attr(hax, hax_lib::ensures(|result| result.is_ok()))]
83fn typed(sk: [u8; SK_LEN], pk: [u8; PK_LEN]) -> Result<(DHPrivateKey, DHPublicKey), Error> {
84 Ok((DHPrivateKey(sk), DHPublicKey(pk)))
85}
86
87pub fn generate_random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Result<[u8; 32], Error> {
89 let mut randomness = [0u8; 32];
90 rng.fill_bytes(&mut randomness);
91
92 let mut secret_key = [0u8; 32];
93 let mut _public_key = [0u8; 32]; libcrux_curve25519::X25519::keygen(&mut _public_key, &mut secret_key, &randomness)
98 .map_err(|_| anyhow::anyhow!("X25519 key generation failed"))?;
99
100 Ok(secret_key)
101}
102
103pub fn dh_public_key_from_scalar(scalar: [u8; 32]) -> DHPublicKey {
108 let mut public_key_bytes = [0u8; 32];
109 libcrux_curve25519::secret_to_public(&mut public_key_bytes, &scalar);
110 DHPublicKey::from_bytes(public_key_bytes)
111}
112
113pub fn dh_shared_secret(
115 public_key: &DHPublicKey,
116 private_scalar: [u8; 32],
117) -> Result<DHSharedSecret, Error> {
118 let mut shared_secret_bytes = [0u8; 32];
119 ecdh(&mut shared_secret_bytes, &public_key.0, &private_scalar)
120 .map_err(|_| anyhow::anyhow!("X25519 DH failed"))?;
121 Ok(DHSharedSecret(shared_secret_bytes))
122}
123
124#[cfg(test)]
125mod tests {
126 use crate::constants::LEN_DH_ITEM;
127
128 use super::*;
129 use proptest::prelude::*;
130 use rand_chacha::ChaCha20Rng;
131 use rand_core::SeedableRng;
132
133 fn get_rng() -> ChaCha20Rng {
135 let mut seed = [0u8; 32];
136 getrandom::fill(&mut seed).expect("OS random source failed");
137 ChaCha20Rng::from_seed(seed)
138 }
139
140 #[test]
141 fn test_deterministic_dh_keygen() {
142 proptest!(|(randomness in proptest::array::uniform32(any::<u8>()))| {
143 let (private_key, public_key) = deterministic_dh_keygen(randomness).unwrap();
144 });
145 }
146
147 #[test]
148 fn test_dh_shared_secret() {
149 let mut rng = get_rng();
150
151 let (sk1, pk1) = generate_dh_keypair(&mut rng).expect("need dh keygen");
152
153 let (sk2, pk2) = generate_dh_keypair(&mut rng).expect("need dh keygen");
154
155 let ss1 = dh_shared_secret(&pk1, sk2.into_bytes()).expect("need shared secret 1");
156 let ss2 = dh_shared_secret(&pk2, sk1.into_bytes()).expect("need shared secret 2");
157
158 assert_eq!(ss1.clone().into_bytes(), ss2.into_bytes());
159 assert_ne!(ss1.into_bytes(), [0u8; LEN_DH_ITEM])
160 }
161}