p2panda_encryption/two_party/
x3dh.rs1use serde::{Deserialize, Serialize};
14use thiserror::Error;
15
16use crate::crypto::aead::{AeadError, AeadNonce, aead_decrypt, aead_encrypt};
17use crate::crypto::hkdf::{HkdfError, hkdf};
18use crate::crypto::x25519::{PublicKey, SecretKey, X25519Error};
19use crate::crypto::{Rng, RngError};
20use crate::key_bundle::{KeyBundleError, OneTimePreKeyId, PreKeyId};
21use crate::traits::KeyBundle;
22
23const KDF_INFO: &[u8; 7] = b"p2panda";
25
26#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
29pub struct X3dhCiphertext {
30 pub identity_key: PublicKey,
32
33 pub onetime_prekey_id: Option<OneTimePreKeyId>,
36
37 pub prekey_id: PreKeyId,
39
40 pub ciphertext: Vec<u8>,
42
43 pub ephemeral_key: PublicKey,
45}
46
47pub fn x3dh_encrypt<KB: KeyBundle>(
49 plaintext: &[u8],
50 our_identity_secret: &SecretKey,
51 their_prekey_bundle: &KB,
52 rng: &Rng,
53) -> Result<X3dhCiphertext, X3dhError> {
54 their_prekey_bundle.verify()?;
56
57 let our_identity_key = our_identity_secret.verifying_key()?;
58
59 let our_ephemeral_secret = SecretKey::from_bytes(rng.random_array()?);
60 let our_ephemeral_key = our_ephemeral_secret.verifying_key()?;
61
62 let mut ikm = Vec::with_capacity({
63 if their_prekey_bundle.onetime_prekey().is_none() {
64 32 * 4
65 } else {
66 32 * 5
67 }
68 });
69
70 ikm.extend_from_slice(&[0xFFu8; 32]); ikm.extend_from_slice(
74 &our_identity_secret.calculate_agreement(their_prekey_bundle.signed_prekey())?,
75 );
76
77 ikm.extend_from_slice(
79 &our_ephemeral_secret.calculate_agreement(their_prekey_bundle.identity_key())?,
80 );
81
82 ikm.extend_from_slice(
84 &our_ephemeral_secret.calculate_agreement(their_prekey_bundle.signed_prekey())?,
85 );
86
87 if let Some(onetime_prekey) = their_prekey_bundle.onetime_prekey() {
89 ikm.extend_from_slice(&our_ephemeral_secret.calculate_agreement(onetime_prekey)?);
90 }
91
92 let sk: [u8; 32] = {
94 let salt = vec![0_u8; 32];
95 hkdf(&salt, &ikm, Some(KDF_INFO))?
96 };
97
98 drop(our_ephemeral_secret);
99 drop(ikm);
100
101 let ad = {
103 let mut buf = Vec::new();
104 buf.extend_from_slice(our_identity_key.as_bytes());
105 buf.extend_from_slice(their_prekey_bundle.identity_key().as_bytes());
106 buf
107 };
108
109 let nonce: AeadNonce = hkdf(b"", &sk, None)?;
110 let ciphertext = aead_encrypt(&sk, plaintext, nonce, Some(&ad))?;
111
112 Ok(X3dhCiphertext {
113 ciphertext,
114 ephemeral_key: our_ephemeral_key,
115 identity_key: our_identity_key,
116 prekey_id: *their_prekey_bundle.signed_prekey(),
117 onetime_prekey_id: their_prekey_bundle.onetime_prekey_id(),
118 })
119}
120
121pub fn x3dh_decrypt(
127 their_ciphertext: &X3dhCiphertext,
128 our_identity_secret: &SecretKey,
129 our_prekey_secret: &SecretKey,
130 our_onetime_secret: Option<&SecretKey>,
131) -> Result<Vec<u8>, X3dhError> {
132 let our_identity_key = our_identity_secret.verifying_key()?;
133
134 let mut ikm = Vec::with_capacity(if our_onetime_secret.is_none() {
135 32 * 4
136 } else {
137 32 * 5
138 });
139
140 ikm.extend_from_slice(&[0xFFu8; 32]); ikm.extend_from_slice(&our_prekey_secret.calculate_agreement(&their_ciphertext.identity_key)?);
144
145 ikm.extend_from_slice(
147 &our_identity_secret.calculate_agreement(&their_ciphertext.ephemeral_key)?,
148 );
149
150 ikm.extend_from_slice(&our_prekey_secret.calculate_agreement(&their_ciphertext.ephemeral_key)?);
152
153 if let Some(our_onetime_secret) = our_onetime_secret {
155 ikm.extend_from_slice(
156 &our_onetime_secret.calculate_agreement(&their_ciphertext.ephemeral_key)?,
157 );
158 }
159
160 let sk: [u8; 32] = {
162 let salt = vec![0_u8; 32];
163 hkdf(&salt, &ikm, Some(KDF_INFO))?
164 };
165
166 drop(ikm);
167
168 let ad = {
170 let mut buf = Vec::new();
171 buf.extend_from_slice(their_ciphertext.identity_key.as_bytes());
172 buf.extend_from_slice(our_identity_key.as_bytes());
173 buf
174 };
175
176 let nonce: AeadNonce = hkdf(b"", &sk, None)?;
177 let plaintext = aead_decrypt(&sk, &their_ciphertext.ciphertext, nonce, Some(&ad))?;
178
179 Ok(plaintext)
180}
181
182#[derive(Debug, Error)]
183pub enum X3dhError {
184 #[error(transparent)]
185 Rng(#[from] RngError),
186
187 #[error(transparent)]
188 Aead(#[from] AeadError),
189
190 #[error(transparent)]
191 Hkdf(#[from] HkdfError),
192
193 #[error(transparent)]
194 X25519(#[from] X25519Error),
195
196 #[error(transparent)]
197 KeyBundle(#[from] KeyBundleError),
198}
199
200#[cfg(test)]
201mod tests {
202 use crate::crypto::Rng;
203 use crate::crypto::x25519::SecretKey;
204 use crate::key_bundle::{Lifetime, LongTermKeyBundle, OneTimeKeyBundle, OneTimePreKey, PreKey};
205
206 use super::{x3dh_decrypt, x3dh_encrypt};
207
208 #[test]
209 fn encrypt_decrypt() {
210 let rng = Rng::from_seed([1; 32]);
211
212 let bob_identity_secret = SecretKey::from_bytes(rng.random_array().unwrap());
213
214 let bob_prekey_secret = SecretKey::from_bytes(rng.random_array().unwrap());
215 let bob_signed_prekey = PreKey::new(
216 bob_prekey_secret.verifying_key().unwrap(),
217 Lifetime::default(),
218 );
219
220 let bob_onetime_secret = SecretKey::from_bytes(rng.random_array().unwrap());
221 let bob_onetime_prekey = OneTimePreKey::new(bob_onetime_secret.verifying_key().unwrap(), 2);
222
223 let bob_prekey_signature = bob_signed_prekey.sign(&bob_identity_secret, &rng).unwrap();
224
225 let bob_prekey_bundle = OneTimeKeyBundle::new(
226 bob_identity_secret.verifying_key().unwrap(),
227 bob_signed_prekey,
228 bob_prekey_signature,
229 Some(bob_onetime_prekey),
230 );
231
232 let alice_identity_secret = SecretKey::from_bytes(rng.random_array().unwrap());
233
234 let ciphertext = x3dh_encrypt(
235 b"Hello, Panda!",
236 &alice_identity_secret,
237 &bob_prekey_bundle,
238 &rng,
239 )
240 .unwrap();
241
242 let plaintext = x3dh_decrypt(
243 &ciphertext,
244 &bob_identity_secret,
245 &bob_prekey_secret,
246 Some(&bob_onetime_secret),
247 )
248 .unwrap();
249
250 assert_eq!(b"Hello, Panda!", plaintext.as_slice());
251 }
252
253 #[test]
254 fn longterm_key_bundle() {
255 let rng = Rng::from_seed([1; 32]);
256
257 let bob_identity_secret = SecretKey::from_bytes(rng.random_array().unwrap());
258
259 let bob_prekey_secret = SecretKey::from_bytes(rng.random_array().unwrap());
260 let bob_signed_prekey = PreKey::new(
261 bob_prekey_secret.verifying_key().unwrap(),
262 Lifetime::default(),
263 );
264
265 let bob_prekey_signature = bob_signed_prekey.sign(&bob_identity_secret, &rng).unwrap();
266
267 let bob_prekey_bundle = LongTermKeyBundle::new(
268 bob_identity_secret.verifying_key().unwrap(),
269 bob_signed_prekey,
270 bob_prekey_signature,
271 );
272
273 let alice_identity_secret = SecretKey::from_bytes(rng.random_array().unwrap());
274
275 let ciphertext = x3dh_encrypt(
276 b"Hello, Panda!",
277 &alice_identity_secret,
278 &bob_prekey_bundle,
279 &rng,
280 )
281 .unwrap();
282
283 let plaintext =
284 x3dh_decrypt(&ciphertext, &bob_identity_secret, &bob_prekey_secret, None).unwrap();
285
286 assert_eq!(b"Hello, Panda!", plaintext.as_slice());
287 }
288}