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 .as_slice()
126 .try_into()
127 .map_err(|_| anyhow::anyhow!("Failed to convert private key bytes"))?,
128 );
129 let public_key = DhAkemPublicKey::from_bytes(
130 public_key_bytes
131 .as_slice()
132 .try_into()
133 .map_err(|_| anyhow::anyhow!("Failed to convert public key bytes"))?,
134 );
135
136 Ok((private_key, public_key))
137}
138
139pub(crate) fn generate_dh_akem_keypair<R: RngCore + CryptoRng>(
141 rng: &mut R,
142) -> Result<(DhAkemPrivateKey, DhAkemPublicKey), anyhow::Error> {
143 use crate::primitives::provider::kem::{Algorithm, key_gen};
144
145 let (sk, pk) = key_gen(Algorithm::X25519, rng)
147 .map_err(|e| anyhow::anyhow!("DH-AKEM key generation failed: {:?}", e))?;
148 typed(sk, pk)
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use proptest::prelude::*;
155 use rand_chacha::ChaCha20Rng;
156 use rand_core::SeedableRng;
157
158 #[test]
159 fn test_dh_akem_key_generation() {
160 let mut rng = ChaCha20Rng::seed_from_u64(42);
161
162 let (private_key, public_key) =
163 generate_dh_akem_keypair(&mut rng).expect("Should generate DH-AKEM keypair");
164
165 assert_eq!(private_key.as_bytes().len(), DH_AKEM_PRIVATE_KEY_LEN);
167 assert_eq!(public_key.as_bytes().len(), DH_AKEM_PUBLIC_KEY_LEN);
168
169 assert_ne!(private_key.as_bytes(), public_key.as_bytes());
171 }
172
173 #[test]
174 fn test_dh_akem_key_serialization() {
175 let mut rng = ChaCha20Rng::seed_from_u64(42);
176
177 let (private_key, public_key) =
178 generate_dh_akem_keypair(&mut rng).expect("Should generate DH-AKEM keypair");
179
180 let private_bytes = *private_key.as_bytes();
182 let public_bytes = *public_key.as_bytes();
183
184 let reconstructed_private = DhAkemPrivateKey::from_bytes(private_bytes);
185 let reconstructed_public = DhAkemPublicKey::from_bytes(public_bytes);
186
187 assert_eq!(private_key.as_bytes(), reconstructed_private.as_bytes());
188 assert_eq!(public_key.as_bytes(), reconstructed_public.as_bytes());
189 }
190
191 #[test]
192 fn test_deterministic_keygen() {
193 proptest!(|(randomness in proptest::array::uniform32(any::<u8>()).prop_filter("exclude zero", |arr| arr != &[0u8; 32]))| {
194 let (private_key, public_key) = deterministic_keygen(randomness.try_into().unwrap()).unwrap();
195 });
196 }
197}