1use std::ops::Deref;
8
9use chacha20poly1305::{KeyInit, XChaCha20Poly1305, XNonce, aead::Aead};
10use ecdh_omr::{Blind, TakeTheHint};
11use hmac::Mac;
12use hmac::digest::{MacError, core_api::CoreWrapper};
13use ml_kem::EncodedSizeUser;
14use ml_kem::kem::{Decapsulate, Encapsulate};
15use rand_core::CryptoRngCore;
16use reach_signatures::{Sign, VerifyingKeys};
17use sha3::{Digest, Sha3_256, Sha3_512, Sha3_512Core};
18use zeroize::Zeroizing;
19
20use reach_aliases::{
21 BlindedPublicKey, Ed25519Signature, EnvelopeIdHint, EnvelopeIdHints, FnDsaSignature,
22 MlKemCiphertext, MlKemPublic, MlKemSecret, X25519Public, X25519Secret, XChaChaKey,
23};
24use reach_core::{
25 ParticipantPublicKeys, ProstDecode, ProstEncode, error,
26 memory::{Credentials, HintedEnvelopeId, Key, MessageVaultPassport},
27 wire::{CredentialVault, EnvelopeSeed, Salts, SealedEnvelopeId, SealedMessageVaultId},
28};
29use reach_signatures::{Digestible, Signatures, Verifier, verify_digest};
30
31use crate::{Ciphertext, Decryptable, Encryptable, Nonce, aliases::*, decrypt};
32
33pub trait ParticipantSecretKeys {
38 fn ec_secret_key(&self) -> &X25519Secret;
40 fn pq_secret_key(&self) -> &MlKemSecret;
42}
43
44pub trait PublicKeyEncrypted {
49 fn ec_public_key(&self) -> &X25519Public;
51 fn pq_ciphertext(&self) -> &MlKemCiphertext;
53}
54
55impl<T> Nonce for T
56where
57 T: PublicKeyEncrypted,
58{
59 fn nonce(&self) -> &XNonce {
60 XNonce::from_slice(&self.ec_public_key().as_bytes()[..24])
61 }
62}
63
64impl<T> Nonce for Vec<T>
65where
66 T: Ciphertext,
67{
68 fn nonce(&self) -> &XNonce {
69 XNonce::from_slice(
70 &self
71 .last()
72 .expect("Can't derive a nonce from an empty Vec")
73 .ciphertext()[..24],
74 )
75 }
76}
77
78pub trait PublicKeyEncryptedFromParts {
83 fn from_parts(
85 ec_public_key: X25519Public,
86 pq_ciphertext: MlKemCiphertext,
87 public_key_ciphertext: Vec<u8>,
88 ) -> Self;
89}
90
91macro_rules! public_key_encrypted {
92 ($type_name:ident, $ciphertext:ident) => {
93 public_key_encrypted!($type_name, ec_public_key, pq_ciphertext, $ciphertext);
94 };
95 (
96 $type_name:ident,
97 $ec_public_key:ident,
98 $pq_ciphertext:ident,
99 $ciphertext:ident
100 ) => {
101 impl PublicKeyEncrypted for $type_name {
102 fn ec_public_key(&self) -> &X25519Public {
103 &self.$ec_public_key
104 }
105
106 fn pq_ciphertext(&self) -> &MlKemCiphertext {
107 &self.$pq_ciphertext
108 }
109 }
110
111 impl Ciphertext for $type_name {
112 fn ciphertext(&self) -> &[u8] {
113 self.$ciphertext.as_slice()
114 }
115 }
116
117 impl PublicKeyEncryptedFromParts for $type_name {
118 fn from_parts(
119 $ec_public_key: X25519Public,
120 $pq_ciphertext: MlKemCiphertext,
121 $ciphertext: Vec<u8>,
122 ) -> Self {
123 Self {
124 $ec_public_key,
125 $pq_ciphertext,
126 $ciphertext,
127 }
128 }
129 }
130 };
131}
132
133public_key_encrypted!(CredentialVault, credentials_ciphertext);
134public_key_encrypted!(SealedEnvelopeId, envelope_id_ciphertext);
135public_key_encrypted!(SealedMessageVaultId, message_vault_id_ciphertext);
136
137pub trait AuthenticatingWrapperFromParts<W>
142where
143 W: Sized,
144{
145 fn from_parts(
147 inner: W,
148 ec_signature: Ed25519Signature,
149 pq_signature: FnDsaSignature,
150 verifying_keys_mac: Vec<u8>,
151 ) -> Self;
152}
153
154impl AuthenticatingWrapperFromParts<Key> for Credentials {
155 fn from_parts(
156 key: Key,
157 ec_signature: Ed25519Signature,
158 pq_signature: FnDsaSignature,
159 verifying_keys_mac: Vec<u8>,
160 ) -> Self {
161 Self {
162 key,
163 ec_signature,
164 pq_signature,
165 verifying_keys_mac,
166 }
167 }
168}
169
170pub fn blind_public_key<P>(participant: &P, csprng: &mut impl CryptoRngCore) -> BlindedPublicKey
175where
176 P: ParticipantPublicKeys,
177{
178 participant.ec_public_key().blind(csprng)
179}
180
181fn mac_shared_secret(
182 pq_shared_secret: &MlKemSharedSecret,
183 ec_shared_secret: &X25519SharedSecret,
184 ec_ciphertext: &X25519Public,
185 ec_public_key: &X25519Public,
186 salt: &[u8],
187) -> Zeroizing<[u8; 32]> {
188 let xwing_shared_secret = xwing_shared_secret(
189 pq_shared_secret,
190 ec_shared_secret,
191 ec_ciphertext,
192 ec_public_key,
193 );
194
195 salt_hash_digest(xwing_shared_secret.as_slice(), salt)
196}
197
198pub fn cipher_and_material_for(
203 ec_public_key: &X25519Public,
204 pq_public_key: &MlKemPublic,
205 salt: &[u8],
206 mac_salt: Option<&[u8]>,
207 csprng: &mut impl CryptoRngCore,
208) -> (
209 XChaCha20Poly1305,
210 Option<Zeroizing<[u8; 32]>>,
211 XNonce,
212 X25519Public,
213 MlKemCiphertext,
214) {
215 let ephemeral_ec_secret_key = X25519Ephemeral::random_from_rng(&mut *csprng);
216 let ephemeral_ec_public_key = X25519Public::from(&ephemeral_ec_secret_key);
217 let ec_shared_secret = ephemeral_ec_secret_key.diffie_hellman(ec_public_key);
218 let (pq_ciphertext, pq_shared_secret) = pq_public_key
219 .encapsulate(csprng)
220 .expect("Encapsulating ML-KEM keys is infallible");
221
222 let mac_shared_secret = mac_salt.map(|mac_salt| {
223 mac_shared_secret(
224 &pq_shared_secret,
225 &ec_shared_secret,
226 &ephemeral_ec_public_key,
227 ec_public_key,
228 mac_salt,
229 )
230 });
231
232 (
233 cipher_for(
234 &pq_shared_secret,
235 &ec_shared_secret,
236 &ephemeral_ec_public_key,
237 ec_public_key,
238 salt,
239 ),
240 mac_shared_secret,
241 XNonce::clone_from_slice(&ephemeral_ec_public_key.as_bytes()[..24]),
242 ephemeral_ec_public_key,
243 pq_ciphertext,
244 )
245}
246
247pub fn cipher_with_secrets(
253 ec_secret_key: &X25519Secret,
254 ec_public_key: &X25519Public,
255 pq_secret_key: &MlKemSecret,
256 pq_ciphertext: &MlKemCiphertext,
257 salt: &[u8],
258) -> XChaCha20Poly1305 {
259 let ec_shared_secret = ec_secret_key.diffie_hellman(ec_public_key);
260 let pq_shared_secret = pq_secret_key
261 .decapsulate(pq_ciphertext)
262 .expect("Decapsulation for ML-KEM is infallible");
263
264 cipher_for(
265 &pq_shared_secret,
266 &ec_shared_secret,
267 ec_public_key,
268 &X25519Public::from(ec_secret_key),
269 salt,
270 )
271}
272
273pub(crate) fn cipher_for(
274 pq_shared_secret: &MlKemSharedSecret,
275 ec_shared_secret: &X25519SharedSecret,
276 ec_ciphertext: &X25519Public,
277 ec_public_key: &X25519Public,
278 salt: &[u8],
279) -> XChaCha20Poly1305 {
280 let shared_secret = salt_hash_digest(
281 xwing_shared_secret(
282 pq_shared_secret,
283 ec_shared_secret,
284 ec_ciphertext,
285 ec_public_key,
286 )
287 .as_ref(),
288 salt,
289 );
290 let key = XChaChaKey::from_slice(shared_secret.as_ref());
291
292 XChaCha20Poly1305::new(key)
293}
294
295pub(crate) fn xwing_shared_secret(
296 pq_shared_secret: &MlKemSharedSecret,
297 ec_shared_secret: &X25519SharedSecret,
298 ec_ciphertext: &X25519Public,
299 ec_public_key: &X25519Public,
300) -> Zeroizing<[u8; 32]> {
301 let mut hasher = <Sha3_256 as Digest>::new();
302 hasher.update(br"\.//^\");
303 hasher.update(pq_shared_secret.as_slice());
304 hasher.update(ec_shared_secret.as_bytes());
305 hasher.update(ec_ciphertext.as_bytes());
306 hasher.update(ec_public_key.as_bytes());
307 Zeroizing::new(hasher.finalize().into())
308}
309
310pub(crate) fn salt_hash_digest(hash_digest: &[u8], salt: &[u8]) -> Zeroizing<[u8; 32]> {
311 let mut hasher = <Sha3_256 as Digest>::new();
312 hasher.update(hash_digest);
313 hasher.update(salt);
314 Zeroizing::new(hasher.finalize().into())
315}
316
317pub trait PublicKeyDecrypter<A>
322where
323 A: PublicKeyEncrypted + Ciphertext,
324 Self: ParticipantSecretKeys,
325{
326 fn decrypt<D>(&self, encrypted: &A, salts: &Salts) -> Result<D, error::CryptError>
330 where
331 D: ProstDecode + Decryptable<A>,
332 {
333 let cipher = cipher_with_secrets(
334 self.ec_secret_key(),
335 encrypted.ec_public_key(),
336 self.pq_secret_key(),
337 encrypted.pq_ciphertext(),
338 &salts.shared_secret,
339 );
340
341 decrypt(&cipher, encrypted.nonce(), encrypted.ciphertext())
342 }
343
344 fn mac_shared_secret(&self, encrypted: &A, salts: &Salts) -> Zeroizing<[u8; 32]> {
349 let ec_shared_secret = &self
350 .ec_secret_key()
351 .diffie_hellman(encrypted.ec_public_key());
352 let pq_shared_secret = &self
353 .pq_secret_key()
354 .decapsulate(encrypted.pq_ciphertext())
355 .expect("Decapsulation for ML-KEM is infallible");
356
357 mac_shared_secret(
358 pq_shared_secret,
359 ec_shared_secret,
360 encrypted.ec_public_key(),
361 &X25519Public::from(self.ec_secret_key()),
362 &salts.verifying_keys_mac,
363 )
364 }
365}
366
367pub trait VerifiableMac {
369 fn mac(&self, mac_shared_secret: &Zeroizing<[u8; 32]>) -> impl Mac;
371
372 fn verify_mac(
374 &self,
375 mac_shared_secret: &Zeroizing<[u8; 32]>,
376 tag: &[u8],
377 ) -> Result<(), MacError>;
378
379 fn finalized_mac(&self, mac_shared_secret: &Zeroizing<[u8; 32]>) -> Vec<u8> {
381 self.mac(mac_shared_secret).finalize().into_bytes().to_vec()
382 }
383}
384
385impl<T> VerifiableMac for T
386where
387 T: Verifier,
388{
389 fn mac(&self, mac_shared_secret: &Zeroizing<[u8; 32]>) -> impl Mac {
390 let mut mac = <HmacSha3_256 as Mac>::new_from_slice(mac_shared_secret.as_slice())
391 .expect("Hmac can take keys of any size.");
392 mac.update(self.ec_verifying_key().as_bytes());
393 mac.update(self.pq_verifying_key());
394
395 mac
396 }
397
398 fn verify_mac(
399 &self,
400 mac_shared_secret: &Zeroizing<[u8; 32]>,
401 tag: &[u8],
402 ) -> Result<(), MacError> {
403 self.mac(mac_shared_secret).verify_slice(tag)
404 }
405}
406
407pub fn binding_digest<P>(
419 ephemeral_ec_public_key: &X25519Public,
420 pq_ciphertext: &MlKemCiphertext,
421 hashable_bytes: Vec<Box<dyn AsRef<[u8]> + '_>>,
422 participant: &P,
423) -> CoreWrapper<Sha3_512Core>
424where
425 P: ParticipantPublicKeys,
426{
427 let mut hasher = <Sha3_512 as Digest>::new();
428 hasher.update(ephemeral_ec_public_key.as_bytes());
429 hasher.update(pq_ciphertext);
430 for bytes in hashable_bytes {
431 hasher.update(bytes.deref());
432 }
433 hasher.update(participant.ec_public_key().as_bytes());
434 hasher.update(participant.pq_public_key().as_bytes());
435
436 hasher
437}
438
439pub trait PublicKeyEncryptable<W> {
444 fn authenticated_encrypt<S, V, R, A>(
450 self,
451 signing_keys: &S,
452 verifier: &V,
453 recipient: &R,
454 salts: &Salts,
455 csprng: &mut impl CryptoRngCore,
456 ) -> Result<A, error::CryptError>
457 where
458 S: Sign + VerifyingKeys<V>,
459 V: Verifier,
460 A: PublicKeyEncryptedFromParts,
461 R: ParticipantPublicKeys,
462 W: AuthenticatingWrapperFromParts<Self> + Decryptable<A> + ProstEncode,
463 Self: Digestible + Sized,
464 {
465 let (cipher, mac_shared_secret, nonce, ec_public_key, pq_ciphertext) =
466 cipher_and_material_for(
467 recipient.ec_public_key(),
468 recipient.pq_public_key(),
469 &salts.shared_secret,
470 Some(&salts.verifying_keys_mac),
471 csprng,
472 );
473
474 let verifier_mac =
475 verifier.finalized_mac(&mac_shared_secret.expect("Infallible due to last call"));
476
477 let context = std::any::type_name::<Self>().as_bytes();
478 let digest = binding_digest(
479 &ec_public_key,
480 &pq_ciphertext,
481 self.hashable_bytes(),
482 recipient,
483 );
484 let (ec_signature, pq_signature) = signing_keys.sign_digest(context, digest, csprng);
485
486 let wrapper = W::from_parts(self, ec_signature, pq_signature, verifier_mac);
487
488 Ok(A::from_parts(
489 ec_public_key,
490 pq_ciphertext,
491 cipher.encrypt(&nonce, wrapper.encode_to_vec().as_slice())?,
492 ))
493 }
494}
495
496impl PublicKeyEncryptable<Credentials> for Key {}
497
498pub trait Authenticate<T>
504where
505 T: PublicKeyEncryptable<Self>,
506 Self: Sized,
507{
508 fn authenticate<E, R, V>(&self, encrypted: &E, recipient: &R, verifier: &V) -> bool
518 where
519 E: PublicKeyEncrypted,
520 R: ParticipantPublicKeys,
521 V: Verifier + ?Sized,
522 Self: Decryptable<E> + Signatures + Digestible,
523 {
524 let digest = binding_digest(
525 encrypted.ec_public_key(),
526 encrypted.pq_ciphertext(),
527 self.hashable_bytes(),
528 recipient,
529 );
530
531 let context = std::any::type_name::<T>().as_bytes();
532
533 verify_digest(
534 verifier,
535 context,
536 digest,
537 self.ec_signature(),
538 self.pq_signature(),
539 )
540 }
541}
542
543impl Authenticate<Key> for Credentials {}
544
545pub trait HintTaker {
547 fn take_the_hint(
549 &self,
550 envelope_id_hint: &EnvelopeIdHint,
551 salts: &Salts,
552 ) -> Result<HintedEnvelopeId, error::CryptError>
553 where
554 Self: ParticipantSecretKeys,
555 {
556 let decrypted_bytes = self
557 .ec_secret_key()
558 .take_the(envelope_id_hint, &salts.shared_secret)?;
559
560 Ok(HintedEnvelopeId::decode(decrypted_bytes)?)
561 }
562
563 fn take_all_the_hints(
565 &self,
566 envelope_id_hints: &EnvelopeIdHints,
567 salts: &Salts,
568 ) -> Result<Vec<HintedEnvelopeId>, error::DecodeError>
569 where
570 Self: ParticipantSecretKeys,
571 {
572 self.ec_secret_key()
573 .take_all_the(envelope_id_hints, &salts.shared_secret)
574 .iter()
575 .map(HintedEnvelopeId::decode)
576 .collect()
577 }
578}
579
580pub fn authenticated_encrypt_key<S, V>(
586 key: &Key,
587 signing_keys: &S,
588 verifier: &V,
589 recipients: &[impl ParticipantPublicKeys],
590 salts: &Salts,
591 csprng: &mut impl CryptoRngCore,
592) -> Result<Vec<CredentialVault>, error::CryptError>
593where
594 S: Sign + VerifyingKeys<V>,
595 V: Verifier,
596{
597 recipients
598 .iter()
599 .map(|p| key.authenticated_encrypt(signing_keys, verifier, p, salts, csprng))
600 .collect()
601}
602
603pub fn build_envelope_seed(
610 message_vault_passport: MessageVaultPassport,
611 participants: &[impl ParticipantPublicKeys],
612 key: &Key,
613 credential_vaults: Vec<CredentialVault>,
614 csprng: &mut impl CryptoRngCore,
615) -> Result<EnvelopeSeed, error::CryptError> {
616 let blinded_public_keys = participants
617 .iter()
618 .map(|p| blind_public_key(p, csprng))
619 .collect();
620
621 let (nonce, message_vault_passport_ciphertext) =
622 message_vault_passport.encrypt_owned(key, csprng)?;
623
624 Ok(EnvelopeSeed {
625 blinded_public_keys,
626 credential_vaults,
627 nonce,
628 message_vault_passport_ciphertext,
629 })
630}