miden_crypto/ecdh/
x25519.rs1use alloc::vec::Vec;
16
17use hkdf::Hkdf;
18use rand::CryptoRng;
19use sha2::Sha256;
20use subtle::ConstantTimeEq;
21
22use crate::{
23 dsa::eddsa_25519_sha512::{KeyExchangeKey, PublicKey},
24 ecdh::KeyAgreementScheme,
25 utils::{
26 ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
27 zeroize::{Zeroize, ZeroizeOnDrop},
28 },
29};
30pub struct SharedSecret {
38 pub(crate) inner: x25519_dalek::SharedSecret,
39}
40impl SharedSecret {
41 pub(crate) fn new(inner: x25519_dalek::SharedSecret) -> SharedSecret {
42 Self { inner }
43 }
44
45 pub fn extract(&self, salt: Option<&[u8]>) -> Hkdf<Sha256> {
47 Hkdf::new(salt, self.inner.as_bytes())
48 }
49}
50
51impl Zeroize for SharedSecret {
52 fn zeroize(&mut self) {
65 let bytes = self.inner.as_bytes();
66 for byte in
67 unsafe { core::slice::from_raw_parts_mut(bytes.as_ptr() as *mut u8, bytes.len()) }
68 {
69 unsafe {
70 core::ptr::write_volatile(byte, 0u8);
71 }
72 }
73 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
74 }
75}
76
77impl ZeroizeOnDrop for SharedSecret {}
79
80impl AsRef<[u8]> for SharedSecret {
81 fn as_ref(&self) -> &[u8] {
82 self.inner.as_bytes()
83 }
84}
85
86pub struct EphemeralSecretKey {
94 inner: x25519_dalek::EphemeralSecret,
95}
96
97impl ZeroizeOnDrop for EphemeralSecretKey {}
98
99impl EphemeralSecretKey {
100 #[cfg(feature = "std")]
102 #[allow(clippy::new_without_default)]
103 pub fn new() -> Self {
104 let mut rng = rand::rng();
105
106 Self::with_rng(&mut rng)
107 }
108
109 pub fn with_rng<R: CryptoRng>(rng: &mut R) -> Self {
111 let sk = x25519_dalek::EphemeralSecret::random_from_rng(rng);
112 Self { inner: sk }
113 }
114
115 pub fn public_key(&self) -> EphemeralPublicKey {
117 EphemeralPublicKey {
118 inner: x25519_dalek::PublicKey::from(&self.inner),
119 }
120 }
121
122 pub fn diffie_hellman(self, pk_other: &PublicKey) -> SharedSecret {
125 let shared = self.inner.diffie_hellman(&pk_other.to_x25519());
126 SharedSecret::new(shared)
127 }
128}
129
130#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct EphemeralPublicKey {
136 pub(crate) inner: x25519_dalek::PublicKey,
137}
138
139impl Serializable for EphemeralPublicKey {
140 fn write_into<W: ByteWriter>(&self, target: &mut W) {
141 target.write_bytes(self.inner.as_bytes());
142 }
143}
144
145impl Deserializable for EphemeralPublicKey {
146 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
147 let bytes: [u8; 32] = source.read_array()?;
148 let mont = curve25519_dalek::montgomery::MontgomeryPoint(bytes);
152 let edwards = mont.to_edwards(0).ok_or_else(|| {
153 DeserializationError::InvalidValue("Invalid X25519 public key".into())
154 })?;
155 if edwards.is_small_order() {
156 return Err(DeserializationError::InvalidValue("Invalid X25519 public key".into()));
157 }
158
159 Ok(Self {
160 inner: x25519_dalek::PublicKey::from(bytes),
161 })
162 }
163}
164
165pub struct X25519;
169
170impl KeyAgreementScheme for X25519 {
171 type EphemeralSecretKey = EphemeralSecretKey;
172 type EphemeralPublicKey = EphemeralPublicKey;
173
174 type SecretKey = KeyExchangeKey;
175 type PublicKey = PublicKey;
176
177 type SharedSecret = SharedSecret;
178
179 fn generate_ephemeral_keypair<R: CryptoRng>(
180 rng: &mut R,
181 ) -> (Self::EphemeralSecretKey, Self::EphemeralPublicKey) {
182 let sk = EphemeralSecretKey::with_rng(rng);
183 let pk = sk.public_key();
184
185 (sk, pk)
186 }
187
188 fn exchange_ephemeral_static(
189 ephemeral_sk: Self::EphemeralSecretKey,
190 static_pk: &Self::PublicKey,
191 ) -> Result<Self::SharedSecret, super::KeyAgreementError> {
192 let shared = ephemeral_sk.diffie_hellman(static_pk);
193 if is_all_zero(shared.as_ref()) {
194 return Err(super::KeyAgreementError::InvalidSharedSecret);
195 }
196 Ok(shared)
197 }
198
199 fn exchange_static_ephemeral(
200 static_sk: &Self::SecretKey,
201 ephemeral_pk: &Self::EphemeralPublicKey,
202 ) -> Result<Self::SharedSecret, super::KeyAgreementError> {
203 let shared = static_sk.get_shared_secret(ephemeral_pk.clone());
204 if is_all_zero(shared.as_ref()) {
205 return Err(super::KeyAgreementError::InvalidSharedSecret);
206 }
207 Ok(shared)
208 }
209
210 fn extract_key_material(
211 shared_secret: &Self::SharedSecret,
212 length: usize,
213 info: &[u8],
214 ) -> Result<Vec<u8>, super::KeyAgreementError> {
215 super::extract_key_material(shared_secret.as_ref(), None, length, info)
216 }
217}
218
219fn is_all_zero(bytes: &[u8]) -> bool {
220 if bytes.is_empty() {
222 return false;
223 }
224 let acc = bytes.iter().fold(0u8, |acc, &byte| acc | byte);
225 acc.ct_eq(&0u8).into()
226}
227
228#[cfg(test)]
232mod tests {
233 use curve25519_dalek::{constants::EIGHT_TORSION, montgomery::MontgomeryPoint};
234
235 use super::*;
236 use crate::{
237 dsa::eddsa_25519_sha512::KeyExchangeKey, ecdh::KeyAgreementError,
238 rand::test_utils::seeded_rng, utils::Deserializable,
239 };
240
241 #[test]
242 fn key_agreement() {
243 let mut rng = seeded_rng([0u8; 32]);
244
245 let sk = KeyExchangeKey::with_rng(&mut rng);
247 let pk = sk.public_key();
248
249 let sk_e = EphemeralSecretKey::with_rng(&mut rng);
251 let pk_e = sk_e.public_key();
252
253 let shared_secret_key_1 = sk_e.diffie_hellman(&pk);
256
257 let shared_secret_key_2 = sk.get_shared_secret(pk_e);
261
262 assert_eq!(shared_secret_key_1.inner.to_bytes(), shared_secret_key_2.inner.to_bytes());
264 }
265
266 #[test]
267 fn ephemeral_public_key_rejects_small_order() {
268 let bytes = EIGHT_TORSION[1].to_montgomery().to_bytes();
269 let result = EphemeralPublicKey::read_from_bytes(&bytes);
270 assert!(result.is_err());
271 }
272
273 #[test]
274 fn ephemeral_public_key_rejects_twist_point() {
275 let bytes = find_twist_point_bytes();
276 let result = EphemeralPublicKey::read_from_bytes(&bytes);
277 assert!(result.is_err());
278 }
279
280 #[test]
281 fn exchange_static_ephemeral_rejects_zero_shared_secret() {
282 let mut rng = seeded_rng([0u8; 32]);
283 let static_sk = KeyExchangeKey::with_rng(&mut rng);
284
285 let low_order_bytes = EIGHT_TORSION[0].to_montgomery().to_bytes();
286 let low_order_pk = EphemeralPublicKey {
287 inner: x25519_dalek::PublicKey::from(low_order_bytes),
288 };
289
290 let result = X25519::exchange_static_ephemeral(&static_sk, &low_order_pk);
291 assert!(matches!(result, Err(KeyAgreementError::InvalidSharedSecret)));
292 }
293
294 #[test]
295 fn is_all_zero_accepts_arbitrary_lengths() {
296 assert!(!is_all_zero(&[]));
297 assert!(is_all_zero(&[0u8; 16]));
298 assert!(!is_all_zero(&[0u8, 1u8, 0u8, 0u8]));
299 }
300
301 fn find_twist_point_bytes() -> [u8; 32] {
302 let mut bytes = [0u8; 32];
303 for i in 0u16..=u16::MAX {
304 bytes[0] = (i & 0xff) as u8;
305 bytes[1] = (i >> 8) as u8;
306 if MontgomeryPoint(bytes).to_edwards(0).is_none() {
307 return bytes;
308 }
309 }
310 panic!("no twist point found in 16-bit search space");
311 }
312}