securedrop_protocol_minimal/primitives/
dh_akem.rs1use crate::primitives::provider::hpke_rs::{HpkePrivateKey, HpkePublicKey};
2use crate::primitives::provider::kem::{PrivateKey, PublicKey};
3use rand_core::{CryptoRng, RngCore};
4
5pub const DH_AKEM_PUBLIC_KEY_LEN: usize = crate::primitives::provider::curve25519::PK_LEN;
6pub(crate) const DH_AKEM_PRIVATE_KEY_LEN: usize = crate::primitives::provider::curve25519::SK_LEN;
7pub(crate) const DH_AKEM_SECRET_LEN: usize = crate::primitives::provider::curve25519::LEN_DH_SHARE;
8pub(crate) const DH_AKEM_ENCAPS_SECRET_LEN: usize =
9 crate::primitives::provider::curve25519::LEN_DH_SHARE;
10
11#[derive(Debug, Clone)]
13pub(crate) struct DhAkemPublicKey([u8; DH_AKEM_PUBLIC_KEY_LEN]);
14
15#[derive(Debug, Clone)]
17pub(crate) struct DhAkemPrivateKey([u8; DH_AKEM_PRIVATE_KEY_LEN]);
18
19#[derive(Debug, Clone)]
21pub(crate) struct DhAkemSecret([u8; DH_AKEM_SECRET_LEN]);
22
23impl DhAkemPublicKey {
24 pub(crate) fn as_bytes(&self) -> &[u8; DH_AKEM_PUBLIC_KEY_LEN] {
25 &self.0
26 }
27
28 pub(crate) fn from_bytes(bytes: [u8; DH_AKEM_PUBLIC_KEY_LEN]) -> Self {
29 Self(bytes)
30 }
31}
32
33impl DhAkemPrivateKey {
34 pub(crate) fn as_bytes(&self) -> &[u8; DH_AKEM_PRIVATE_KEY_LEN] {
35 &self.0
36 }
37
38 pub(crate) fn from_bytes(bytes: [u8; DH_AKEM_PRIVATE_KEY_LEN]) -> Self {
39 Self(bytes)
40 }
41}
42
43impl DhAkemSecret {
44 pub(crate) fn as_bytes(&self) -> &[u8; DH_AKEM_SECRET_LEN] {
45 &self.0
46 }
47
48 pub(crate) fn from_bytes(bytes: [u8; DH_AKEM_SECRET_LEN]) -> Self {
49 Self(bytes)
50 }
51}
52
53fn clamp(scalar: &mut [u8; 32]) {
55 scalar[0] &= 248u8;
57 scalar[31] &= 127u8;
59 scalar[31] |= 64u8;
61}
62
63pub(crate) fn deterministic_keygen(
66 randomness: [u8; 32],
67) -> Result<(DhAkemPrivateKey, DhAkemPublicKey), anyhow::Error> {
68 use crate::primitives::provider::kem::{Algorithm, key_gen_derand};
69
70 let mut clamped_randomness = randomness.clone();
72 clamp(&mut clamped_randomness);
73
74 let (sk, pk) = key_gen_derand(Algorithm::X25519, &clamped_randomness)
75 .map_err(|e| anyhow::anyhow!("DH-AKEM deterministic key generation failed: {:?}", e))?;
76
77 typed(sk, pk)
78}
79
80impl From<DhAkemPrivateKey> for HpkePrivateKey {
81 fn from(sk: DhAkemPrivateKey) -> Self {
82 HpkePrivateKey::from(sk.0.to_vec())
83 }
84}
85
86impl From<DhAkemPublicKey> for HpkePublicKey {
87 fn from(pk: DhAkemPublicKey) -> Self {
88 HpkePublicKey::from(pk.0.to_vec())
89 }
90}
91
92#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
93fn typed(
95 sk: PrivateKey,
96 pk: PublicKey,
97) -> Result<(DhAkemPrivateKey, DhAkemPublicKey), anyhow::Error> {
98 let private_key_bytes = sk.encode();
100 let public_key_bytes = pk.encode();
101
102 if private_key_bytes.len() != DH_AKEM_PRIVATE_KEY_LEN
104 || public_key_bytes.len() != DH_AKEM_PUBLIC_KEY_LEN
105 {
106 return Err(anyhow::anyhow!(
117 "Unexpected DH-AKEM key sizes: private={}, public={}",
118 private_key_bytes.len(),
119 public_key_bytes.len()
120 ));
121 }
122
123 let private_key = DhAkemPrivateKey::from_bytes(
124 private_key_bytes
125 .try_into()
126 .map_err(|_| anyhow::anyhow!("Failed to convert private key bytes"))?,
127 );
128 let public_key = DhAkemPublicKey::from_bytes(
129 public_key_bytes
130 .try_into()
131 .map_err(|_| anyhow::anyhow!("Failed to convert public key bytes"))?,
132 );
133
134 Ok((private_key, public_key))
135}
136
137pub(crate) fn generate_dh_akem_keypair<R: RngCore + CryptoRng>(
139 rng: &mut R,
140) -> Result<(DhAkemPrivateKey, DhAkemPublicKey), anyhow::Error> {
141 use crate::primitives::provider::kem::{Algorithm, key_gen};
142
143 let (sk, pk) = key_gen(Algorithm::X25519, rng)
145 .map_err(|e| anyhow::anyhow!("DH-AKEM key generation failed: {:?}", e))?;
146 typed(sk, pk)
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use proptest::prelude::*;
153 use rand_chacha::ChaCha20Rng;
154 use rand_core::SeedableRng;
155
156 #[test]
157 fn test_dh_akem_key_generation() {
158 let mut rng = ChaCha20Rng::seed_from_u64(42);
159
160 let (private_key, public_key) =
161 generate_dh_akem_keypair(&mut rng).expect("Should generate DH-AKEM keypair");
162
163 assert_eq!(private_key.as_bytes().len(), DH_AKEM_PRIVATE_KEY_LEN);
165 assert_eq!(public_key.as_bytes().len(), DH_AKEM_PUBLIC_KEY_LEN);
166
167 assert_ne!(private_key.as_bytes(), public_key.as_bytes());
169 }
170
171 #[test]
172 fn test_dh_akem_key_serialization() {
173 let mut rng = ChaCha20Rng::seed_from_u64(42);
174
175 let (private_key, public_key) =
176 generate_dh_akem_keypair(&mut rng).expect("Should generate DH-AKEM keypair");
177
178 let private_bytes = *private_key.as_bytes();
180 let public_bytes = *public_key.as_bytes();
181
182 let reconstructed_private = DhAkemPrivateKey::from_bytes(private_bytes);
183 let reconstructed_public = DhAkemPublicKey::from_bytes(public_bytes);
184
185 assert_eq!(private_key.as_bytes(), reconstructed_private.as_bytes());
186 assert_eq!(public_key.as_bytes(), reconstructed_public.as_bytes());
187 }
188
189 #[test]
190 fn test_deterministic_keygen() {
191 proptest!(|(randomness in proptest::array::uniform32(any::<u8>()).prop_filter("exclude zero", |arr| arr != &[0u8; 32]))| {
192 let (private_key, public_key) = deterministic_keygen(randomness.try_into().unwrap()).unwrap();
193 });
194 }
195}