nucypher_core/
dkg.rs

1use alloc::boxed::Box;
2use alloc::string::String;
3use core::fmt;
4
5use chacha20poly1305::aead::{Aead, AeadCore, KeyInit, OsRng};
6use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
7use ferveo::api::{CiphertextHeader, FerveoVariant};
8use generic_array::typenum::Unsigned;
9use serde::{Deserialize, Serialize};
10use umbral_pre::serde_bytes; // TODO should this be in umbral?
11
12use crate::access_control::AccessControlPolicy;
13use crate::conditions::Context;
14use crate::dkg::session::{SessionSharedSecret, SessionStaticKey};
15use crate::versioning::{
16    messagepack_deserialize, messagepack_serialize, DeserializationError, ProtocolObject,
17    ProtocolObjectInner,
18};
19
20/// Errors during encryption.
21#[derive(Debug)]
22pub enum EncryptionError {
23    /// Given plaintext is too large for the backend to handle.
24    PlaintextTooLarge,
25}
26
27impl fmt::Display for EncryptionError {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            Self::PlaintextTooLarge => write!(f, "Plaintext is too large to encrypt"),
31        }
32    }
33}
34
35/// Errors during decryption.
36#[derive(Debug)]
37pub enum DecryptionError {
38    /// Ciphertext (which should be prepended by the nonce) is shorter than the nonce length.
39    CiphertextTooShort,
40    /// The ciphertext and the attached authentication data are inconsistent.
41    /// This can happen if:
42    /// - an incorrect key is used,
43    /// - the ciphertext is modified or cut short,
44    /// - an incorrect authentication data is provided on decryption.
45    AuthenticationFailed,
46    /// Unable to create object from decrypted ciphertext
47    DeserializationFailed(DeserializationError),
48}
49
50impl fmt::Display for DecryptionError {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            Self::CiphertextTooShort => write!(f, "The ciphertext must include the nonce"),
54            Self::AuthenticationFailed => write!(
55                f,
56                "Decryption of ciphertext failed: \
57                either someone tampered with the ciphertext or \
58                you are using an incorrect decryption key."
59            ),
60            Self::DeserializationFailed(err) => write!(f, "deserialization failed: {err}"),
61        }
62    }
63}
64
65type NonceSize = <ChaCha20Poly1305 as AeadCore>::NonceSize;
66
67fn encrypt_with_shared_secret(
68    shared_secret: &SessionSharedSecret,
69    plaintext: &[u8],
70) -> Result<Box<[u8]>, EncryptionError> {
71    let key = Key::from_slice(shared_secret.as_ref());
72    let cipher = ChaCha20Poly1305::new(key);
73    let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
74    let mut result = nonce.to_vec();
75    let ciphertext = cipher
76        .encrypt(&nonce, plaintext.as_ref())
77        .map_err(|_err| EncryptionError::PlaintextTooLarge)?;
78    result.extend(ciphertext);
79    Ok(result.into_boxed_slice())
80}
81
82fn decrypt_with_shared_secret(
83    shared_secret: &SessionSharedSecret,
84    ciphertext: &[u8],
85) -> Result<Box<[u8]>, DecryptionError> {
86    let nonce_size = <NonceSize as Unsigned>::to_usize();
87    let buf_size = ciphertext.len();
88    if buf_size < nonce_size {
89        return Err(DecryptionError::CiphertextTooShort);
90    }
91    let nonce = Nonce::from_slice(&ciphertext[..nonce_size]);
92    let encrypted_data = &ciphertext[nonce_size..];
93
94    let key = Key::from_slice(shared_secret.as_ref());
95    let cipher = ChaCha20Poly1305::new(key);
96    let plaintext = cipher
97        .decrypt(nonce, encrypted_data)
98        .map_err(|_err| DecryptionError::AuthenticationFailed)?;
99    Ok(plaintext.into_boxed_slice())
100}
101
102/// Module for session key objects.
103pub mod session {
104    use alloc::boxed::Box;
105    use alloc::string::String;
106    use core::fmt;
107
108    use generic_array::{
109        typenum::{Unsigned, U32},
110        GenericArray,
111    };
112    use rand::SeedableRng;
113    use rand_chacha::ChaCha20Rng;
114    use rand_core::{CryptoRng, OsRng, RngCore};
115    use serde::{Deserialize, Deserializer, Serialize, Serializer};
116    use umbral_pre::serde_bytes;
117    use x25519_dalek::{PublicKey, SharedSecret, StaticSecret};
118    use zeroize::ZeroizeOnDrop;
119
120    use crate::secret_box::{kdf, SecretBox};
121    use crate::versioning::{
122        messagepack_deserialize, messagepack_serialize, ProtocolObject, ProtocolObjectInner,
123    };
124
125    /// A Diffie-Hellman shared secret
126    #[derive(ZeroizeOnDrop)]
127    pub struct SessionSharedSecret {
128        derived_bytes: [u8; 32],
129    }
130
131    /// Implementation of Diffie-Hellman shared secret
132    impl SessionSharedSecret {
133        /// Create new shared secret from underlying library.
134        pub fn new(shared_secret: SharedSecret) -> Self {
135            let info = b"SESSION_SHARED_SECRET_DERIVATION/";
136            let derived_key = kdf::<U32>(shared_secret.as_bytes(), Some(info));
137            let derived_bytes = <[u8; 32]>::try_from(derived_key.as_secret().as_slice()).unwrap();
138            Self { derived_bytes }
139        }
140
141        /// View this shared secret as a byte array.
142        pub fn as_bytes(&self) -> &[u8; 32] {
143            &self.derived_bytes
144        }
145    }
146
147    impl AsRef<[u8]> for SessionSharedSecret {
148        /// View this shared secret as a byte array.
149        fn as_ref(&self) -> &[u8] {
150            self.as_bytes()
151        }
152    }
153
154    impl fmt::Display for SessionSharedSecret {
155        /// Format shared secret information.
156        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157            write!(f, "SessionSharedSecret...")
158        }
159    }
160
161    /// A session public key.
162    #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
163    pub struct SessionStaticKey(PublicKey);
164
165    /// Implementation of session static key
166    impl SessionStaticKey {
167        /// Convert this public key to a byte array.
168        pub fn to_bytes(&self) -> [u8; 32] {
169            self.0.to_bytes()
170        }
171    }
172
173    impl AsRef<[u8]> for SessionStaticKey {
174        /// View this public key as a byte array.
175        fn as_ref(&self) -> &[u8] {
176            self.0.as_bytes()
177        }
178    }
179
180    impl fmt::Display for SessionStaticKey {
181        /// Format public key information.
182        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183            write!(f, "SessionStaticKey: {}", hex::encode(&self.as_ref()[..8]))
184        }
185    }
186
187    impl Serialize for SessionStaticKey {
188        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
189        where
190            S: Serializer,
191        {
192            serde_bytes::as_hex::serialize(self.0.as_bytes(), serializer)
193        }
194    }
195
196    impl serde_bytes::TryFromBytes for SessionStaticKey {
197        type Error = core::array::TryFromSliceError;
198        fn try_from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
199            let array: [u8; 32] = bytes.try_into()?;
200            Ok(SessionStaticKey(PublicKey::from(array)))
201        }
202    }
203
204    impl<'a> Deserialize<'a> for SessionStaticKey {
205        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
206        where
207            D: Deserializer<'a>,
208        {
209            serde_bytes::as_hex::deserialize(deserializer)
210        }
211    }
212
213    impl<'a> ProtocolObjectInner<'a> for SessionStaticKey {
214        fn version() -> (u16, u16) {
215            (2, 0)
216        }
217
218        fn brand() -> [u8; 4] {
219            *b"TSSk"
220        }
221
222        fn unversioned_to_bytes(&self) -> Box<[u8]> {
223            messagepack_serialize(&self)
224        }
225
226        fn unversioned_from_bytes(
227            minor_version: u16,
228            bytes: &[u8],
229        ) -> Option<Result<Self, String>> {
230            if minor_version == 0 {
231                Some(messagepack_deserialize(bytes))
232            } else {
233                None
234            }
235        }
236    }
237
238    impl<'a> ProtocolObject<'a> for SessionStaticKey {}
239
240    /// A session secret key.
241    #[derive(ZeroizeOnDrop)]
242    pub struct SessionStaticSecret(pub(crate) StaticSecret);
243
244    impl SessionStaticSecret {
245        /// Perform diffie-hellman
246        pub fn derive_shared_secret(
247            &self,
248            their_public_key: &SessionStaticKey,
249        ) -> SessionSharedSecret {
250            let shared_secret = self.0.diffie_hellman(&their_public_key.0);
251            SessionSharedSecret::new(shared_secret)
252        }
253
254        /// Create secret key from RNG.
255        pub fn random_from_rng(csprng: &mut (impl RngCore + CryptoRng)) -> Self {
256            let secret_key = StaticSecret::random_from_rng(csprng);
257            Self(secret_key)
258        }
259
260        /// Create random secret key.
261        pub fn random() -> Self {
262            Self::random_from_rng(&mut OsRng)
263        }
264
265        /// Returns a public key corresponding to this secret key.
266        pub fn public_key(&self) -> SessionStaticKey {
267            let public_key = PublicKey::from(&self.0);
268            SessionStaticKey(public_key)
269        }
270    }
271
272    impl fmt::Display for SessionStaticSecret {
273        /// Format information above secret key.
274        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275            write!(f, "SessionStaticSecret:...")
276        }
277    }
278
279    // the size of the seed material for key derivation
280    type SessionSecretFactorySeedSize = U32;
281    // the size of the derived key
282    type SessionSecretFactoryDerivedKeySize = U32;
283    type SessionSecretFactorySeed = GenericArray<u8, SessionSecretFactorySeedSize>;
284
285    /// Error thrown when invalid random seed provided for creating key factory.
286    #[derive(Debug)]
287    pub struct InvalidSessionSecretFactorySeedLength;
288
289    impl fmt::Display for InvalidSessionSecretFactorySeedLength {
290        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291            write!(f, "Invalid seed length")
292        }
293    }
294
295    /// This class handles keyring material for session keys, by allowing deterministic
296    /// derivation of `SessionStaticSecret` objects based on labels.
297    #[derive(Clone, ZeroizeOnDrop, PartialEq)]
298    pub struct SessionSecretFactory(SecretBox<SessionSecretFactorySeed>);
299
300    impl SessionSecretFactory {
301        /// Creates a session secret factory using the given RNG.
302        pub fn random_with_rng(rng: &mut (impl CryptoRng + RngCore)) -> Self {
303            let mut bytes = SecretBox::new(SessionSecretFactorySeed::default());
304            rng.fill_bytes(bytes.as_mut_secret());
305            Self(bytes)
306        }
307
308        /// Creates a session secret factory using the default RNG.
309        pub fn random() -> Self {
310            Self::random_with_rng(&mut OsRng)
311        }
312
313        /// Returns the seed size required by
314        pub fn seed_size() -> usize {
315            SessionSecretFactorySeedSize::to_usize()
316        }
317
318        /// Creates a `SessionSecretFactory` using the given random bytes.
319        ///
320        /// **Warning:** make sure the given seed has been obtained
321        /// from a cryptographically secure source of randomness!
322        pub fn from_secure_randomness(
323            seed: &[u8],
324        ) -> Result<Self, InvalidSessionSecretFactorySeedLength> {
325            if seed.len() != Self::seed_size() {
326                return Err(InvalidSessionSecretFactorySeedLength);
327            }
328            Ok(Self(SecretBox::new(*SessionSecretFactorySeed::from_slice(
329                seed,
330            ))))
331        }
332
333        /// Creates a `SessionStaticSecret` deterministically from the given label.
334        pub fn make_key(&self, label: &[u8]) -> SessionStaticSecret {
335            let prefix = b"SESSION_KEY_DERIVATION/";
336            let info = [prefix, label].concat();
337            let seed = kdf::<SessionSecretFactoryDerivedKeySize>(self.0.as_secret(), Some(&info));
338            let mut rng =
339                ChaCha20Rng::from_seed(<[u8; 32]>::try_from(seed.as_secret().as_slice()).unwrap());
340            SessionStaticSecret::random_from_rng(&mut rng)
341        }
342    }
343
344    impl fmt::Display for SessionSecretFactory {
345        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346            write!(f, "SessionSecretFactory:...")
347        }
348    }
349}
350
351/// A request for an Ursula to derive a decryption share.
352#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
353pub struct ThresholdDecryptionRequest {
354    /// The ID of the ritual.
355    pub ritual_id: u32,
356    /// The ciphertext to generate a decryption share for.
357    pub ciphertext_header: CiphertextHeader,
358    /// The associated access control metadata.
359    pub acp: AccessControlPolicy,
360    /// A blob of bytes containing context required to evaluate conditions.
361    pub context: Option<Context>,
362    /// The ferveo variant to use for the decryption share derivation.
363    pub variant: FerveoVariant,
364}
365
366impl ThresholdDecryptionRequest {
367    /// Creates a new decryption request.
368    pub fn new(
369        ritual_id: u32,
370        ciphertext_header: &CiphertextHeader,
371        acp: &AccessControlPolicy,
372        context: Option<&Context>,
373        variant: FerveoVariant,
374    ) -> Self {
375        Self {
376            ritual_id,
377            ciphertext_header: ciphertext_header.clone(),
378            acp: acp.clone(),
379            context: context.cloned(),
380            variant,
381        }
382    }
383
384    /// Encrypts the decryption request.
385    pub fn encrypt(
386        &self,
387        shared_secret: &SessionSharedSecret,
388        requester_public_key: &SessionStaticKey,
389    ) -> EncryptedThresholdDecryptionRequest {
390        EncryptedThresholdDecryptionRequest::new(self, shared_secret, requester_public_key)
391    }
392}
393
394impl<'a> ProtocolObjectInner<'a> for ThresholdDecryptionRequest {
395    fn version() -> (u16, u16) {
396        (4, 0)
397    }
398
399    fn brand() -> [u8; 4] {
400        *b"ThRq"
401    }
402
403    fn unversioned_to_bytes(&self) -> Box<[u8]> {
404        messagepack_serialize(&self)
405    }
406
407    fn unversioned_from_bytes(minor_version: u16, bytes: &[u8]) -> Option<Result<Self, String>> {
408        if minor_version == 0 {
409            Some(messagepack_deserialize(bytes))
410        } else {
411            None
412        }
413    }
414}
415
416impl<'a> ProtocolObject<'a> for ThresholdDecryptionRequest {}
417
418/// An encrypted request for an Ursula to derive a decryption share.
419#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
420pub struct EncryptedThresholdDecryptionRequest {
421    /// ID of the ritual
422    pub ritual_id: u32,
423
424    /// Public key of requester
425    pub requester_public_key: SessionStaticKey,
426
427    #[serde(with = "serde_bytes::as_base64")]
428    /// Encrypted request
429    ciphertext: Box<[u8]>,
430}
431
432impl EncryptedThresholdDecryptionRequest {
433    fn new(
434        request: &ThresholdDecryptionRequest,
435        shared_secret: &SessionSharedSecret,
436        requester_public_key: &SessionStaticKey,
437    ) -> Self {
438        let ciphertext = encrypt_with_shared_secret(shared_secret, &request.to_bytes())
439            .expect("encryption failed - out of memory?");
440        Self {
441            ritual_id: request.ritual_id,
442            requester_public_key: *requester_public_key,
443            ciphertext,
444        }
445    }
446
447    /// Decrypts the decryption request
448    pub fn decrypt(
449        &self,
450        shared_secret: &SessionSharedSecret,
451    ) -> Result<ThresholdDecryptionRequest, DecryptionError> {
452        let decryption_request_bytes = decrypt_with_shared_secret(shared_secret, &self.ciphertext)?;
453        let decryption_request = ThresholdDecryptionRequest::from_bytes(&decryption_request_bytes)
454            .map_err(DecryptionError::DeserializationFailed)?;
455        Ok(decryption_request)
456    }
457}
458
459impl<'a> ProtocolObjectInner<'a> for EncryptedThresholdDecryptionRequest {
460    fn version() -> (u16, u16) {
461        (2, 0)
462    }
463
464    fn brand() -> [u8; 4] {
465        *b"ETRq"
466    }
467
468    fn unversioned_to_bytes(&self) -> Box<[u8]> {
469        messagepack_serialize(&self)
470    }
471
472    fn unversioned_from_bytes(minor_version: u16, bytes: &[u8]) -> Option<Result<Self, String>> {
473        if minor_version == 0 {
474            Some(messagepack_deserialize(bytes))
475        } else {
476            None
477        }
478    }
479}
480
481impl<'a> ProtocolObject<'a> for EncryptedThresholdDecryptionRequest {}
482
483/// A response from Ursula with a derived decryption share.
484#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
485pub struct ThresholdDecryptionResponse {
486    /// The ID of the ritual.
487    pub ritual_id: u32,
488
489    /// The decryption share to include in the response.
490    #[serde(with = "serde_bytes::as_base64")]
491    pub decryption_share: Box<[u8]>,
492}
493
494impl ThresholdDecryptionResponse {
495    /// Creates and a new decryption response.
496    pub fn new(ritual_id: u32, decryption_share: &[u8]) -> Self {
497        ThresholdDecryptionResponse {
498            ritual_id,
499            decryption_share: decryption_share.to_vec().into(),
500        }
501    }
502
503    /// Encrypts the decryption response.
504    pub fn encrypt(
505        &self,
506        shared_secret: &SessionSharedSecret,
507    ) -> EncryptedThresholdDecryptionResponse {
508        EncryptedThresholdDecryptionResponse::new(self, shared_secret)
509    }
510}
511
512impl<'a> ProtocolObjectInner<'a> for ThresholdDecryptionResponse {
513    fn version() -> (u16, u16) {
514        (2, 0)
515    }
516
517    fn brand() -> [u8; 4] {
518        *b"ThRs"
519    }
520
521    fn unversioned_to_bytes(&self) -> Box<[u8]> {
522        messagepack_serialize(&self)
523    }
524
525    fn unversioned_from_bytes(minor_version: u16, bytes: &[u8]) -> Option<Result<Self, String>> {
526        if minor_version == 0 {
527            Some(messagepack_deserialize(bytes))
528        } else {
529            None
530        }
531    }
532}
533
534impl<'a> ProtocolObject<'a> for ThresholdDecryptionResponse {}
535
536/// An encrypted response from Ursula with a derived decryption share.
537#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
538pub struct EncryptedThresholdDecryptionResponse {
539    /// The ID of the ritual.
540    pub ritual_id: u32,
541
542    #[serde(with = "serde_bytes::as_base64")]
543    ciphertext: Box<[u8]>,
544}
545
546impl EncryptedThresholdDecryptionResponse {
547    fn new(response: &ThresholdDecryptionResponse, shared_secret: &SessionSharedSecret) -> Self {
548        let ciphertext = encrypt_with_shared_secret(shared_secret, &response.to_bytes())
549            .expect("encryption failed - out of memory?");
550        Self {
551            ritual_id: response.ritual_id,
552            ciphertext,
553        }
554    }
555
556    /// Decrypts the decryption request
557    pub fn decrypt(
558        &self,
559        shared_secret: &SessionSharedSecret,
560    ) -> Result<ThresholdDecryptionResponse, DecryptionError> {
561        let decryption_response_bytes =
562            decrypt_with_shared_secret(shared_secret, &self.ciphertext)?;
563        let decryption_response =
564            ThresholdDecryptionResponse::from_bytes(&decryption_response_bytes)
565                .map_err(DecryptionError::DeserializationFailed)?;
566        Ok(decryption_response)
567    }
568}
569
570impl<'a> ProtocolObjectInner<'a> for EncryptedThresholdDecryptionResponse {
571    fn version() -> (u16, u16) {
572        (2, 0)
573    }
574
575    fn brand() -> [u8; 4] {
576        *b"ETRs"
577    }
578
579    fn unversioned_to_bytes(&self) -> Box<[u8]> {
580        messagepack_serialize(&self)
581    }
582
583    fn unversioned_from_bytes(minor_version: u16, bytes: &[u8]) -> Option<Result<Self, String>> {
584        if minor_version == 0 {
585            Some(messagepack_deserialize(bytes))
586        } else {
587            None
588        }
589    }
590}
591
592impl<'a> ProtocolObject<'a> for EncryptedThresholdDecryptionResponse {}
593
594#[cfg(test)]
595mod tests {
596    use ferveo::api::{encrypt as ferveo_encrypt, FerveoVariant, SecretBox};
597    use generic_array::typenum::Unsigned;
598    use rand_core::RngCore;
599
600    use crate::access_control::AccessControlPolicy;
601    use crate::conditions::{Conditions, Context};
602    use crate::dkg::session::SessionStaticSecret;
603    use crate::dkg::{
604        decrypt_with_shared_secret, encrypt_with_shared_secret, DecryptionError, NonceSize,
605    };
606    use crate::test_utils::util::random_dkg_pubkey;
607    use crate::versioning::{ProtocolObject, ProtocolObjectInner};
608    use crate::{
609        AuthenticatedData, EncryptedThresholdDecryptionRequest,
610        EncryptedThresholdDecryptionResponse, SessionSecretFactory, SessionStaticKey,
611        ThresholdDecryptionRequest, ThresholdDecryptionResponse,
612    };
613
614    #[test]
615    fn decryption_with_shared_secret() {
616        let service_secret = SessionStaticSecret::random();
617
618        let requester_secret = SessionStaticSecret::random();
619        let requester_public_key = requester_secret.public_key();
620
621        let service_shared_secret = service_secret.derive_shared_secret(&requester_public_key);
622
623        let ciphertext = b"1".to_vec().into_boxed_slice(); // length less than nonce size
624        let nonce_size = <NonceSize as Unsigned>::to_usize();
625        assert!(ciphertext.len() < nonce_size);
626
627        assert!(matches!(
628            decrypt_with_shared_secret(&service_shared_secret, &ciphertext).unwrap_err(),
629            DecryptionError::CiphertextTooShort
630        ));
631    }
632
633    #[test]
634    fn request_key_factory() {
635        let secret_factory = SessionSecretFactory::random();
636
637        // ensure that shared secret derived from factory can be used correctly
638        let label_1 = b"label_1".to_vec().into_boxed_slice();
639        let service_secret_key = secret_factory.make_key(label_1.as_ref());
640        let service_public_key = service_secret_key.public_key();
641
642        let label_2 = b"label_2".to_vec().into_boxed_slice();
643        let requester_secret_key = secret_factory.make_key(label_2.as_ref());
644        let requester_public_key = requester_secret_key.public_key();
645
646        let service_shared_secret = service_secret_key.derive_shared_secret(&requester_public_key);
647        let requester_shared_secret =
648            requester_secret_key.derive_shared_secret(&service_public_key);
649
650        let data_to_encrypt = b"The Tyranny of Merit".to_vec().into_boxed_slice();
651        let ciphertext =
652            encrypt_with_shared_secret(&requester_shared_secret, data_to_encrypt.as_ref()).unwrap();
653        let decrypted_data =
654            decrypt_with_shared_secret(&service_shared_secret, &ciphertext).unwrap();
655        assert_eq!(decrypted_data, data_to_encrypt);
656
657        // ensure same key can be generated by the same factory using the same seed
658        let same_requester_secret_key = secret_factory.make_key(label_2.as_ref());
659        let same_requester_public_key = same_requester_secret_key.public_key();
660        assert_eq!(requester_public_key, same_requester_public_key);
661
662        // ensure different key generated using same seed but using different factory
663        let other_secret_factory = SessionSecretFactory::random();
664        let not_same_requester_secret_key = other_secret_factory.make_key(label_2.as_ref());
665        let not_same_requester_public_key = not_same_requester_secret_key.public_key();
666        assert_ne!(requester_public_key, not_same_requester_public_key);
667
668        // ensure that two secret factories with the same seed generate the same keys
669        let mut secret_factory_seed = [0u8; 32];
670        rand::thread_rng().fill_bytes(&mut secret_factory_seed);
671        let seeded_secret_factory_1 =
672            SessionSecretFactory::from_secure_randomness(&secret_factory_seed).unwrap();
673        let seeded_secret_factory_2 =
674            SessionSecretFactory::from_secure_randomness(&secret_factory_seed).unwrap();
675
676        let key_label = b"seeded_factory_key_label".to_vec().into_boxed_slice();
677        let sk_1 = seeded_secret_factory_1.make_key(&key_label);
678        let pk_1 = sk_1.public_key();
679
680        let sk_2 = seeded_secret_factory_2.make_key(&key_label);
681        let pk_2 = sk_2.public_key();
682
683        assert_eq!(pk_1, pk_2);
684
685        // test secure randomness
686        let bytes = [0u8; 32];
687        let factory = SessionSecretFactory::from_secure_randomness(&bytes);
688        assert!(factory.is_ok());
689
690        let bytes = [0u8; 31];
691        let factory = SessionSecretFactory::from_secure_randomness(&bytes);
692        assert!(factory.is_err());
693    }
694
695    #[test]
696    fn session_static_key() {
697        let public_key_1: SessionStaticKey = SessionStaticSecret::random().public_key();
698        let public_key_2: SessionStaticKey = SessionStaticSecret::random().public_key();
699
700        let public_key_1_bytes = public_key_1.unversioned_to_bytes();
701        let public_key_2_bytes = public_key_2.unversioned_to_bytes();
702
703        // serialized public keys should always have the same length
704        assert_eq!(public_key_1_bytes.len(), public_key_2_bytes.len());
705
706        let deserialized_public_key_1 =
707            SessionStaticKey::unversioned_from_bytes(0, &public_key_1_bytes)
708                .unwrap()
709                .unwrap();
710        let deserialized_public_key_2 =
711            SessionStaticKey::unversioned_from_bytes(0, &public_key_2_bytes)
712                .unwrap()
713                .unwrap();
714
715        assert_eq!(public_key_1, deserialized_public_key_1);
716        assert_eq!(public_key_2, deserialized_public_key_2);
717    }
718
719    #[test]
720    fn threshold_decryption_request() {
721        for variant in [FerveoVariant::Simple, FerveoVariant::Precomputed] {
722            let ritual_id = 0;
723
724            let service_secret = SessionStaticSecret::random();
725
726            let requester_secret = SessionStaticSecret::random();
727            let requester_public_key = requester_secret.public_key();
728
729            let dkg_pk = random_dkg_pubkey();
730            let message = "The Tyranny of Merit".as_bytes().to_vec();
731            let aad = "my-add".as_bytes();
732            let ciphertext = ferveo_encrypt(SecretBox::new(message), aad, &dkg_pk).unwrap();
733
734            let auth_data = AuthenticatedData::new(&dkg_pk, &Conditions::new("abcd"));
735
736            let authorization = b"self_authorization";
737            let acp = AccessControlPolicy::new(&auth_data, authorization);
738
739            let ciphertext_header = ciphertext.header().unwrap();
740
741            let request = ThresholdDecryptionRequest::new(
742                ritual_id,
743                &ciphertext_header,
744                &acp,
745                Some(&Context::new("efgh")),
746                variant,
747            );
748
749            // requester encrypts request to send to service
750            let service_public_key = service_secret.public_key();
751            let requester_shared_secret =
752                requester_secret.derive_shared_secret(&service_public_key);
753            let encrypted_request =
754                request.encrypt(&requester_shared_secret, &requester_public_key);
755
756            // mimic serialization/deserialization over the wire
757            let encrypted_request_bytes = encrypted_request.to_bytes();
758            let encrypted_request_from_bytes =
759                EncryptedThresholdDecryptionRequest::from_bytes(&encrypted_request_bytes).unwrap();
760
761            assert_eq!(encrypted_request_from_bytes.ritual_id, ritual_id);
762            assert_eq!(
763                encrypted_request_from_bytes.requester_public_key,
764                requester_public_key
765            );
766
767            // service decrypts request
768            let service_shared_secret = service_secret
769                .derive_shared_secret(&encrypted_request_from_bytes.requester_public_key);
770            assert_eq!(
771                service_shared_secret.as_bytes(),
772                requester_shared_secret.as_bytes()
773            );
774            let decrypted_request = encrypted_request_from_bytes
775                .decrypt(&service_shared_secret)
776                .unwrap();
777            assert_eq!(decrypted_request, request);
778
779            // wrong shared key used
780            let random_secret_key = SessionStaticSecret::random();
781            let random_shared_secret =
782                random_secret_key.derive_shared_secret(&requester_public_key);
783            assert!(encrypted_request_from_bytes
784                .decrypt(&random_shared_secret)
785                .is_err());
786        }
787    }
788
789    #[test]
790    fn threshold_decryption_response() {
791        let ritual_id = 5;
792
793        let service_secret = SessionStaticSecret::random();
794        let requester_secret = SessionStaticSecret::random();
795
796        let decryption_share = b"The Tyranny of Merit";
797
798        let response = ThresholdDecryptionResponse::new(ritual_id, decryption_share);
799
800        // service encrypts response to send back
801        let requester_public_key = requester_secret.public_key();
802
803        let service_shared_secret = service_secret.derive_shared_secret(&requester_public_key);
804        let encrypted_response = response.encrypt(&service_shared_secret);
805        assert_eq!(encrypted_response.ritual_id, ritual_id);
806
807        // mimic serialization/deserialization over the wire
808        let encrypted_response_bytes = encrypted_response.to_bytes();
809        let encrypted_response_from_bytes =
810            EncryptedThresholdDecryptionResponse::from_bytes(&encrypted_response_bytes).unwrap();
811
812        // requester decrypts response
813        let service_public_key = service_secret.public_key();
814        let requester_shared_secret = requester_secret.derive_shared_secret(&service_public_key);
815        assert_eq!(
816            requester_shared_secret.as_bytes(),
817            service_shared_secret.as_bytes()
818        );
819        let decrypted_response = encrypted_response_from_bytes
820            .decrypt(&requester_shared_secret)
821            .unwrap();
822        assert_eq!(response, decrypted_response);
823        assert_eq!(response.ritual_id, ritual_id);
824        assert_eq!(
825            response.decryption_share,
826            decrypted_response.decryption_share
827        );
828
829        // wrong shared key used
830        let random_secret_key = SessionStaticSecret::random();
831        let random_shared_secret = random_secret_key.derive_shared_secret(&requester_public_key);
832        assert!(encrypted_response_from_bytes
833            .decrypt(&random_shared_secret)
834            .is_err());
835    }
836}