bc_components/encapsulation/
encapsulation_public_key.rs

1use anyhow::{Result, bail};
2use dcbor::prelude::*;
3
4use crate::{
5    EncapsulationCiphertext, EncapsulationScheme, Encrypter, MLKEMPublicKey,
6    PrivateKeyBase, SymmetricKey, X25519PublicKey, tags,
7};
8
9/// A public key used for key encapsulation mechanisms (KEM).
10///
11/// `EncapsulationPublicKey` is an enum representing different types of public
12/// keys that can be used for key encapsulation, including:
13///
14/// - X25519: Curve25519-based key exchange
15/// - ML-KEM: Module Lattice-based Key Encapsulation Mechanism at various
16///   security levels
17///
18/// These public keys are used to encrypt (encapsulate) shared secrets that can
19/// only be decrypted (decapsulated) by the corresponding private key holder.
20#[derive(Debug, Clone, PartialEq, Eq, Hash)]
21pub enum EncapsulationPublicKey {
22    /// An X25519 public key
23    X25519(X25519PublicKey),
24    /// An ML-KEM public key (post-quantum)
25    MLKEM(MLKEMPublicKey),
26}
27
28impl EncapsulationPublicKey {
29    /// Returns the encapsulation scheme associated with this public key.
30    ///
31    /// # Returns
32    ///
33    /// The encapsulation scheme (X25519, MLKEM512, MLKEM768, or MLKEM1024)
34    /// that corresponds to this public key.
35    ///
36    /// # Example
37    ///
38    /// ```
39    /// use bc_components::{EncapsulationScheme, X25519PrivateKey};
40    ///
41    /// // Generate a keypair
42    /// let private_key = X25519PrivateKey::new();
43    /// let public_key = private_key.public_key();
44    ///
45    /// // Convert to encapsulation public key
46    /// let encapsulation_public_key =
47    ///     bc_components::EncapsulationPublicKey::X25519(public_key);
48    ///
49    /// // Check the scheme
50    /// assert_eq!(
51    ///     encapsulation_public_key.encapsulation_scheme(),
52    ///     EncapsulationScheme::X25519
53    /// );
54    /// ```
55    pub fn encapsulation_scheme(&self) -> EncapsulationScheme {
56        match self {
57            Self::X25519(_) => EncapsulationScheme::X25519,
58            Self::MLKEM(pk) => match pk.level() {
59                crate::MLKEM::MLKEM512 => EncapsulationScheme::MLKEM512,
60                crate::MLKEM::MLKEM768 => EncapsulationScheme::MLKEM768,
61                crate::MLKEM::MLKEM1024 => EncapsulationScheme::MLKEM1024,
62            },
63        }
64    }
65
66    /// Encapsulates a new shared secret using this public key.
67    ///
68    /// This method performs the encapsulation operation for key exchange. It
69    /// generates a new shared secret and encapsulates it using this public
70    /// key.
71    ///
72    /// The encapsulation process differs based on the key type:
73    /// - For X25519: Generates an ephemeral private/public key pair, derives a
74    ///   shared secret using Diffie-Hellman, and returns the shared secret
75    ///   along with the ephemeral public key
76    /// - For ML-KEM: Uses the KEM encapsulation algorithm to generate and
77    ///   encapsulate a random shared secret
78    ///
79    /// # Returns
80    ///
81    /// A tuple containing:
82    /// - The generated shared secret as a `SymmetricKey`
83    /// - The encapsulation ciphertext that can be sent to the private key
84    ///   holder
85    ///
86    /// # Example
87    ///
88    /// ```
89    /// use bc_components::EncapsulationScheme;
90    ///
91    /// // Generate a key pair using the default scheme (X25519)
92    /// let (private_key, public_key) = EncapsulationScheme::default().keypair();
93    ///
94    /// // Encapsulate a new shared secret
95    /// let (shared_secret, ciphertext) =
96    ///     public_key.encapsulate_new_shared_secret();
97    ///
98    /// // The private key holder can recover the same shared secret
99    /// let recovered_secret =
100    ///     private_key.decapsulate_shared_secret(&ciphertext).unwrap();
101    /// assert_eq!(shared_secret, recovered_secret);
102    /// ```
103    pub fn encapsulate_new_shared_secret(
104        &self,
105    ) -> (SymmetricKey, EncapsulationCiphertext) {
106        match self {
107            EncapsulationPublicKey::X25519(public_key) => {
108                let emphemeral_sender = PrivateKeyBase::new();
109                let ephemeral_private_key =
110                    emphemeral_sender.x25519_private_key();
111                let ephemeral_public_key = ephemeral_private_key.public_key();
112                let shared_key =
113                    ephemeral_private_key.shared_key_with(public_key);
114                (
115                    shared_key,
116                    EncapsulationCiphertext::X25519(ephemeral_public_key),
117                )
118            }
119            EncapsulationPublicKey::MLKEM(public_key) => {
120                let (shared_key, ciphertext) =
121                    public_key.encapsulate_new_shared_secret();
122                (shared_key, EncapsulationCiphertext::MLKEM(ciphertext))
123            }
124        }
125    }
126}
127
128/// Implementation of the `Encrypter` trait for `EncapsulationPublicKey`.
129///
130/// This allows `EncapsulationPublicKey` to be used with the generic encryption
131/// interface defined by the `Encrypter` trait.
132impl Encrypter for EncapsulationPublicKey {
133    fn encapsulation_public_key(&self) -> EncapsulationPublicKey {
134        self.clone()
135    }
136
137    fn encapsulate_new_shared_secret(
138        &self,
139    ) -> (SymmetricKey, EncapsulationCiphertext) {
140        self.encapsulate_new_shared_secret()
141    }
142}
143
144/// Conversion from `EncapsulationPublicKey` to CBOR for serialization.
145impl From<EncapsulationPublicKey> for CBOR {
146    fn from(public_key: EncapsulationPublicKey) -> Self {
147        match public_key {
148            EncapsulationPublicKey::X25519(public_key) => public_key.into(),
149            EncapsulationPublicKey::MLKEM(public_key) => public_key.into(),
150        }
151    }
152}
153
154/// Conversion from CBOR to `EncapsulationPublicKey` for deserialization.
155impl TryFrom<CBOR> for EncapsulationPublicKey {
156    type Error = anyhow::Error;
157
158    fn try_from(cbor: CBOR) -> Result<Self> {
159        match cbor.as_case() {
160            CBORCase::Tagged(tag, _) => match tag.value() {
161                tags::TAG_X25519_PUBLIC_KEY => {
162                    Ok(EncapsulationPublicKey::X25519(
163                        X25519PublicKey::try_from(cbor)?,
164                    ))
165                }
166                tags::TAG_MLKEM_PUBLIC_KEY => {
167                    Ok(EncapsulationPublicKey::MLKEM(MLKEMPublicKey::try_from(
168                        cbor,
169                    )?))
170                }
171                _ => bail!("Invalid encapsulation public key"),
172            },
173            _ => bail!("Invalid encapsulation public key"),
174        }
175    }
176}