Skip to main content

openmls/key_packages/
mod.rs

1//! # Key Packages
2//!
3//! Key packages are pre-published public keys that carry information about a
4//! user, allowing for the asynchronous addition of clients to an MLS group.
5//!
6//! A key package object specifies:
7//!
8//! - A **protocol version** and ciphersuite that the client supports
9//! - A **public key** that others can use for key agreement
10//! - A **credential** authenticating the client's application-layer identity
11//! - A list of **extensions** for the key package (see
12//!   [Extensions](`mod@crate::extensions`) for details)
13//!
14//! Key packages are meant to be used only once and SHOULD NOT be reused,
15//! except as a last resort—i.e., when no other key package is available.
16//! Clients MAY generate and publish multiple key packages to support multiple
17//! ciphersuites.
18//!
19//! The HPKE init key MUST be a public key for the asymmetric encryption scheme
20//! defined by the ciphersuite. It MUST be unique among key packages created by
21//! the client. The entire structure is signed using the client's signature key.
22//! A key package object with an invalid signature field is considered malformed.
23//!
24//! ## Creating key package bundles
25//!
26//! Key package bundles are key packages that include their private key. A key
27//! package bundle can be created as follows:
28//!
29//! ```
30//! use openmls::{prelude::{*, tls_codec::*}};
31//! use openmls_rust_crypto::OpenMlsRustCrypto;
32//! use openmls_basic_credential::SignatureKeyPair;
33//!
34//! let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
35//! let provider = OpenMlsRustCrypto::default();
36//!
37//! let credential = BasicCredential::new("identity".into());
38//! let signer =
39//!     SignatureKeyPair::new(ciphersuite.signature_algorithm())
40//!         .expect("Error generating a signature key pair.");
41//! let credential_with_key = CredentialWithKey {
42//!     credential: credential.into(),
43//!     signature_key: signer.public().into(),
44//! };
45//! let key_package = KeyPackage::builder()
46//!     .build(
47//!         ciphersuite,
48//!         &provider,
49//!         &signer,
50//!         credential_with_key,
51//!     )
52//!     .unwrap();
53//! ```
54//!
55//! See [`KeyPackage`] for more details and other ways to create key packages.
56//!
57//! ## Loading key packages
58//!
59//! When getting key packages from another user the serialized bytes are parsed
60//! as follows;
61//!
62//! ```
63//! use openmls::prelude::{*, tls_codec::*};
64//! use openmls::test_utils::hex_to_bytes;
65//! use openmls_rust_crypto::OpenMlsRustCrypto;
66//!
67//! let provider = OpenMlsRustCrypto::default();
68//! let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
69//!
70//! let key_package_bytes = hex_to_bytes(
71//!         "0001000120D4F26FCA6EF6B1CA2FDD8DCAA501730FB003323AD8C781490B94782771\
72//!         B22216208E9BF17CE632EC753A9BFC624F275AA745ACD7316A5CF18B8E39CE71A80EE\
73//!         137205639176E415B378BA54B9E7C678FFAA860676CEFEDFA0DD3FF692F20AC7E2632\
74//!         0001086964656E74697479020001060001000200030200010C0001000200030004000\
75//!         50007020001010000000064A1986E00000000776DA97E004040A71F1B7A5F78D15C3F\
76//!         B215D811BADB0BBBD78B582D42E5C3672085699DCA5F90DAA57BB74A3A973789E7006\
77//!         887FCE85F0E64C19C1F26C28B5752B3C3312FF3040040407B5A96167512061E78414F\
78//!         E3F29B89FF2A954CB8E0A6E976EA039E0A1A0AB91B80664BDDC62BC8CBE64BC9242C4\
79//!         CDC33F56A10E425A384AED029C23E1D467C0E");
80//!
81//! let key_package_in = KeyPackageIn::tls_deserialize(&mut key_package_bytes.as_slice())
82//!     .expect("Could not deserialize KeyPackage");
83//!
84//! let key_package = key_package_in
85//!     .validate(provider.crypto(), ProtocolVersion::Mls10)
86//!     .expect("Invalid KeyPackage");
87//! ```
88//!
89//! See [`KeyPackage`] for more details on how to use key packages.
90
91use crate::{
92    ciphersuite::{
93        hash_ref::{make_key_package_ref, KeyPackageRef},
94        signable::*,
95        *,
96    },
97    credentials::*,
98    error::LibraryError,
99    extensions::{Extension, ExtensionType, Extensions, LastResortExtension},
100    storage::OpenMlsProvider,
101    treesync::{
102        node::{
103            encryption_keys::{EncryptionKeyPair, EncryptionPrivateKey},
104            leaf_node::{Capabilities, LeafNodeSource, NewLeafNodeParams, TreeInfoTbs},
105        },
106        LeafNode,
107    },
108    versions::ProtocolVersion,
109};
110use openmls_traits::{
111    crypto::OpenMlsCrypto, signatures::Signer, storage::StorageProvider, types::Ciphersuite,
112};
113use serde::{Deserialize, Serialize};
114use tls_codec::{
115    Serialize as TlsSerializeTrait, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize,
116};
117
118// Private
119use errors::*;
120
121// Public
122pub mod errors;
123pub mod key_package_in;
124
125mod lifetime;
126#[cfg(feature = "virtual-clients-draft")]
127mod vc;
128
129// Tests
130#[cfg(test)]
131pub(crate) mod tests;
132
133// Public types
134pub use key_package_in::KeyPackageIn;
135pub use lifetime::Lifetime;
136#[cfg(feature = "virtual-clients-draft")]
137pub use vc::{VcKeyPackageBatch, VcKeyPackageBatchBuilder};
138
139/// The unsigned payload of a key package.
140/// Any modification must happen on this unsigned struct. Use `sign` to get a
141/// signed key package.
142///
143/// ```text
144/// struct {
145///     ProtocolVersion version;
146///     CipherSuite cipher_suite;
147///     HPKEPublicKey init_key;
148///     LeafNode leaf_node;
149///     Extension extensions<V>;
150/// } KeyPackageTBS;
151/// ```
152#[derive(Debug, Clone, PartialEq, TlsSize, TlsSerialize, Serialize, Deserialize)]
153struct KeyPackageTbs {
154    protocol_version: ProtocolVersion,
155    ciphersuite: Ciphersuite,
156    init_key: InitKey,
157    leaf_node: LeafNode,
158    extensions: Extensions<KeyPackage>,
159}
160
161impl Signable for KeyPackageTbs {
162    type SignedOutput = KeyPackage;
163
164    fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
165        self.tls_serialize_detached()
166    }
167
168    fn label(&self) -> &str {
169        SIGNATURE_KEY_PACKAGE_LABEL
170    }
171}
172
173impl From<KeyPackage> for KeyPackageTbs {
174    fn from(kp: KeyPackage) -> Self {
175        kp.payload
176    }
177}
178
179/// The key package struct.
180#[derive(Debug, Clone, Serialize, Deserialize, TlsSize)]
181pub struct KeyPackage {
182    payload: KeyPackageTbs,
183    signature: Signature,
184    #[serde(skip)]
185    #[tls_codec(skip)]
186    serialized_payload: Option<Vec<u8>>,
187}
188
189impl TlsSerializeTrait for KeyPackage {
190    fn tls_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, tls_codec::Error> {
191        let mut written = 0;
192        if let Some(ref bytes) = self.serialized_payload {
193            written += writer.write(bytes)?;
194        } else {
195            written += self.payload.tls_serialize(writer)?;
196        }
197        written += self.signature.tls_serialize(writer)?;
198        Ok(written)
199    }
200}
201
202impl PartialEq for KeyPackage {
203    fn eq(&self, other: &Self) -> bool {
204        // We ignore the signature in the comparison. The same key package
205        // may have different, valid signatures.
206        self.payload == other.payload
207    }
208}
209
210impl SignedStruct<KeyPackageTbs> for KeyPackage {
211    fn from_payload(
212        payload: KeyPackageTbs,
213        signature: Signature,
214        serialized_payload: Vec<u8>,
215    ) -> Self {
216        Self {
217            payload,
218            signature,
219            serialized_payload: Some(serialized_payload),
220        }
221    }
222}
223
224const SIGNATURE_KEY_PACKAGE_LABEL: &str = "KeyPackageTBS";
225
226/// The leaf-node-specific parameters used when creating a [`KeyPackage`].
227pub(crate) struct KeyPackageLeafNodeParams {
228    pub(crate) lifetime: Lifetime,
229    pub(crate) capabilities: Capabilities,
230    pub(crate) extensions: Extensions<LeafNode>,
231}
232
233/// Helper struct containing the results of building a new [`KeyPackage`].
234pub(crate) struct KeyPackageCreationResult {
235    pub key_package: KeyPackage,
236    pub encryption_keypair: EncryptionKeyPair,
237    pub init_private_key: HpkePrivateKey,
238}
239
240/// Init key for HPKE.
241#[derive(
242    Debug,
243    Clone,
244    PartialEq,
245    TlsSize,
246    TlsSerialize,
247    Serialize,
248    Deserialize,
249    TlsDeserialize,
250    TlsDeserializeBytes,
251)]
252pub struct InitKey {
253    key: HpkePublicKey,
254}
255
256impl InitKey {
257    /// Return the internal [`HpkePublicKey`].
258    pub fn key(&self) -> &HpkePublicKey {
259        &self.key
260    }
261
262    /// Return the internal [`HpkePublicKey`] as a slice.
263    pub fn as_slice(&self) -> &[u8] {
264        self.key.as_slice()
265    }
266}
267
268impl From<Vec<u8>> for InitKey {
269    fn from(key: Vec<u8>) -> Self {
270        Self {
271            key: HpkePublicKey::from(key),
272        }
273    }
274}
275
276impl From<HpkePublicKey> for InitKey {
277    fn from(key: HpkePublicKey) -> Self {
278        Self { key }
279    }
280}
281
282// Public `KeyPackage` functions.
283impl KeyPackage {
284    /// Create a key package builder.
285    ///
286    /// This is provided for convenience. You can also use [`KeyPackageBuilder::new`].
287    pub fn builder() -> KeyPackageBuilder {
288        KeyPackageBuilder::new()
289    }
290
291    /// Create a new key package for the given `ciphersuite` and `identity`.
292    pub(crate) fn create(
293        ciphersuite: Ciphersuite,
294        provider: &impl OpenMlsProvider,
295        signer: &impl Signer,
296        credential_with_key: CredentialWithKey,
297        extensions: Extensions<KeyPackage>,
298        leaf_node_params: KeyPackageLeafNodeParams,
299    ) -> Result<KeyPackageCreationResult, KeyPackageNewError> {
300        if ciphersuite.signature_algorithm() != signer.signature_scheme() {
301            return Err(KeyPackageNewError::CiphersuiteSignatureSchemeMismatch);
302        }
303
304        provider
305            .crypto()
306            .supports(ciphersuite)
307            .map_err(|_| KeyPackageNewError::UnsupportedCiphersuite(ciphersuite))?;
308
309        // Create a new HPKE key pair
310        let ikm = Secret::random(ciphersuite, provider.rand())
311            .map_err(LibraryError::unexpected_crypto_error)?;
312        let init_key = provider
313            .crypto()
314            .derive_hpke_keypair(ciphersuite.hpke_config(), ikm.as_slice())
315            .map_err(|e| {
316                KeyPackageNewError::LibraryError(LibraryError::unexpected_crypto_error(e))
317            })?;
318        let (key_package, encryption_keypair) = Self::new_from_keys(
319            ciphersuite,
320            provider,
321            signer,
322            credential_with_key,
323            extensions,
324            leaf_node_params,
325            init_key.public.into(),
326        )?;
327
328        Ok(KeyPackageCreationResult {
329            key_package,
330            encryption_keypair,
331            init_private_key: init_key.private,
332        })
333    }
334
335    /// Create a new key package for the given `ciphersuite` and `identity`.
336    ///
337    /// The HPKE init key must have been generated before and the private part
338    /// has to be stored in the key store.
339    ///
340    /// This function returns the new [`KeyPackage`] as well as the
341    /// encryption key ([`HpkeKeyPair`]) of the leaf node.
342    ///
343    /// The caller is responsible for storing the new values.
344    fn new_from_keys(
345        ciphersuite: Ciphersuite,
346        provider: &impl OpenMlsProvider,
347        signer: &impl Signer,
348        credential_with_key: CredentialWithKey,
349        extensions: Extensions<KeyPackage>,
350        leaf_node_params: KeyPackageLeafNodeParams,
351        init_key: InitKey,
352    ) -> Result<(Self, EncryptionKeyPair), KeyPackageNewError> {
353        // We don't need the private key here. It's stored in the key store for
354        // use later when creating a group with this key package.
355
356        let KeyPackageLeafNodeParams {
357            lifetime,
358            capabilities,
359            extensions: leaf_node_extensions,
360        } = leaf_node_params;
361
362        let new_leaf_node_params = NewLeafNodeParams {
363            ciphersuite,
364            credential_with_key,
365            leaf_node_source: LeafNodeSource::KeyPackage(lifetime),
366            capabilities,
367            extensions: leaf_node_extensions,
368            tree_info_tbs: TreeInfoTbs::KeyPackage,
369        };
370
371        let (leaf_node, encryption_key_pair) =
372            LeafNode::new(provider, signer, new_leaf_node_params)?;
373
374        let key_package_tbs = KeyPackageTbs {
375            protocol_version: ProtocolVersion::default(),
376            ciphersuite,
377            init_key,
378            leaf_node,
379            extensions,
380        };
381
382        let key_package = key_package_tbs.sign(signer)?;
383
384        Ok((key_package, encryption_key_pair))
385    }
386
387    /// Create a new KeyPackage from caller-provided init and encryption keys.
388    ///
389    /// Mirrors [`KeyPackage::new_from_keys`] but takes a caller-provided
390    /// encryption key pair for the leaf instead of generating a fresh one.
391    /// Used by the virtual-clients KeyPackage build path, where both keys are
392    /// derived from a per-operation secret so a sibling can reproduce them.
393    #[cfg(feature = "virtual-clients-draft")]
394    fn new_from_vc_keys(
395        ciphersuite: Ciphersuite,
396        signer: &impl Signer,
397        credential_with_key: CredentialWithKey,
398        extensions: Extensions<KeyPackage>,
399        leaf_node_params: KeyPackageLeafNodeParams,
400        init_key: InitKey,
401        encryption_key_pair: EncryptionKeyPair,
402    ) -> Result<(Self, EncryptionKeyPair), KeyPackageNewError> {
403        let KeyPackageLeafNodeParams {
404            lifetime,
405            capabilities,
406            extensions: leaf_node_extensions,
407        } = leaf_node_params;
408
409        let new_leaf_node_params = NewLeafNodeParams {
410            ciphersuite,
411            credential_with_key,
412            leaf_node_source: LeafNodeSource::KeyPackage(lifetime),
413            capabilities,
414            extensions: leaf_node_extensions,
415            tree_info_tbs: TreeInfoTbs::KeyPackage,
416        };
417
418        let (leaf_node, encryption_key_pair) = LeafNode::new_with_encryption_key_pair(
419            signer,
420            new_leaf_node_params,
421            encryption_key_pair,
422        )?;
423
424        let key_package_tbs = KeyPackageTbs {
425            protocol_version: ProtocolVersion::default(),
426            ciphersuite,
427            init_key,
428            leaf_node,
429            extensions,
430        };
431
432        let key_package = key_package_tbs.sign(signer)?;
433
434        Ok((key_package, encryption_key_pair))
435    }
436
437    /// Get a reference to the extensions of this key package.
438    pub fn extensions(&self) -> &Extensions<KeyPackage> {
439        &self.payload.extensions
440    }
441
442    /// Check whether the this key package supports all the required extensions
443    /// in the provided list.
444    pub fn check_extension_support(
445        &self,
446        required_extensions: &[ExtensionType],
447    ) -> Result<(), KeyPackageExtensionSupportError> {
448        for required_extension in required_extensions.iter() {
449            if !self.extensions().contains(*required_extension) {
450                return Err(KeyPackageExtensionSupportError::UnsupportedExtension);
451            }
452        }
453
454        Ok(())
455    }
456
457    /// Compute the [`KeyPackageRef`] of this [`KeyPackage`].
458    /// The [`KeyPackageRef`] is used to identify a new member that should get
459    /// added to a group.
460    pub fn hash_ref(&self, crypto: &impl OpenMlsCrypto) -> Result<KeyPackageRef, LibraryError> {
461        make_key_package_ref(
462            &self
463                .tls_serialize_detached()
464                .map_err(LibraryError::missing_bound_check)?,
465            self.payload.ciphersuite,
466            crypto,
467        )
468        .map_err(LibraryError::unexpected_crypto_error)
469    }
470
471    /// Get the [`Ciphersuite`].
472    pub fn ciphersuite(&self) -> Ciphersuite {
473        self.payload.ciphersuite
474    }
475
476    /// Get the [`LeafNode`] reference.
477    pub fn leaf_node(&self) -> &LeafNode {
478        &self.payload.leaf_node
479    }
480
481    /// Get the public HPKE init key of this key package.
482    pub fn hpke_init_key(&self) -> &InitKey {
483        &self.payload.init_key
484    }
485
486    /// Check if this KeyPackage is a last resort key package.
487    pub fn last_resort(&self) -> bool {
488        self.payload.extensions.contains(ExtensionType::LastResort)
489    }
490
491    /// Get the lifetime of the KeyPackage
492    pub fn life_time(&self) -> &Lifetime {
493        // Leaf nodes contain a lifetime if an only if they are inside a KeyPackage. Since we are
494        // in a KeyPackage, this can never be None and unwrap is safe.
495        // TODO: get rid of the unwrap, see https://github.com/openmls/openmls/issues/1663.
496        self.payload.leaf_node.life_time().unwrap()
497    }
498}
499
500/// Crate visible `KeyPackage` functions.
501impl KeyPackage {
502    /// Get the `ProtocolVersion`.
503    pub(crate) fn protocol_version(&self) -> ProtocolVersion {
504        self.payload.protocol_version
505    }
506}
507
508/// Builder that helps creating (and configuring) a [`KeyPackage`].
509#[derive(Default, Debug, Clone, Serialize, Deserialize)]
510pub struct KeyPackageBuilder {
511    key_package_lifetime: Option<Lifetime>,
512    key_package_extensions: Option<Extensions<KeyPackage>>,
513    leaf_node_capabilities: Option<Capabilities>,
514    leaf_node_extensions: Option<Extensions<LeafNode>>,
515    last_resort: bool,
516}
517
518impl KeyPackageBuilder {
519    /// Create a key package builder.
520    pub fn new() -> Self {
521        Self {
522            key_package_lifetime: None,
523            key_package_extensions: None,
524            leaf_node_capabilities: None,
525            leaf_node_extensions: None,
526            last_resort: false,
527        }
528    }
529
530    /// Set the key package lifetime.
531    pub fn key_package_lifetime(mut self, lifetime: Lifetime) -> Self {
532        self.key_package_lifetime.replace(lifetime);
533        self
534    }
535
536    /// Set the key package extensions.
537    pub fn key_package_extensions(mut self, extensions: Extensions<KeyPackage>) -> Self {
538        self.key_package_extensions.replace(extensions);
539        self
540    }
541
542    /// Mark the key package as a last-resort key package via a [`LastResortExtension`].
543    pub fn mark_as_last_resort(mut self) -> Self {
544        self.last_resort = true;
545        self
546    }
547
548    /// Set the leaf node capabilities.
549    pub fn leaf_node_capabilities(mut self, capabilities: Capabilities) -> Self {
550        self.leaf_node_capabilities.replace(capabilities);
551        self
552    }
553
554    /// Set the leaf node extensions.
555    ///
556    /// Returns an error if one or more of the extensions is invalid in leaf nodes.
557    pub fn leaf_node_extensions(mut self, extensions: Extensions<LeafNode>) -> Self {
558        self.leaf_node_extensions.replace(extensions);
559        self
560    }
561
562    /// Ensure that a last-resort extension is present in the key package if the
563    /// `last_resort` flag is set.
564    fn ensure_last_resort(&mut self) {
565        if self.last_resort {
566            let last_resort_extension = Extension::LastResort(LastResortExtension::default());
567            if let Some(extensions) = self.key_package_extensions.as_mut() {
568                extensions
569                    .add_or_replace(last_resort_extension)
570                    .expect("LastResort extensions are allowed in key packages");
571            } else {
572                self.key_package_extensions = Some(
573                    Extensions::single(last_resort_extension)
574                        .expect("LastResort extensions are allowed in key packages"),
575                );
576            }
577        }
578    }
579
580    #[cfg(test)]
581    pub(crate) fn build_without_storage(
582        mut self,
583        ciphersuite: Ciphersuite,
584        provider: &impl OpenMlsProvider,
585        signer: &impl Signer,
586        credential_with_key: CredentialWithKey,
587    ) -> Result<KeyPackageCreationResult, KeyPackageNewError> {
588        self.ensure_last_resort();
589        let leaf_node_params = KeyPackageLeafNodeParams {
590            lifetime: self.key_package_lifetime.unwrap_or_default(),
591            capabilities: self.leaf_node_capabilities.unwrap_or_default(),
592            extensions: self.leaf_node_extensions.unwrap_or_default(),
593        };
594        KeyPackage::create(
595            ciphersuite,
596            provider,
597            signer,
598            credential_with_key,
599            self.key_package_extensions.unwrap_or_default(),
600            leaf_node_params,
601        )
602    }
603
604    /// Finalize and build the key package.
605    pub fn build(
606        mut self,
607        ciphersuite: Ciphersuite,
608        provider: &impl OpenMlsProvider,
609        signer: &impl Signer,
610        credential_with_key: CredentialWithKey,
611    ) -> Result<KeyPackageBundle, KeyPackageNewError> {
612        self.ensure_last_resort();
613
614        let leaf_node_params = KeyPackageLeafNodeParams {
615            lifetime: self.key_package_lifetime.unwrap_or_default(),
616            capabilities: self.leaf_node_capabilities.unwrap_or_default(),
617            extensions: self.leaf_node_extensions.unwrap_or_default(),
618        };
619        let KeyPackageCreationResult {
620            key_package,
621            encryption_keypair,
622            init_private_key,
623        } = KeyPackage::create(
624            ciphersuite,
625            provider,
626            signer,
627            credential_with_key,
628            self.key_package_extensions.unwrap_or_default(),
629            leaf_node_params,
630        )?;
631
632        // Store the key package in the key store with the hash reference as id
633        // for retrieval when parsing welcome messages.
634        let full_kp = KeyPackageBundle {
635            key_package,
636            private_init_key: init_private_key,
637            private_encryption_key: encryption_keypair.private_key().clone(),
638        };
639        provider
640            .storage()
641            .write_key_package(&full_kp.key_package.hash_ref(provider.crypto())?, &full_kp)
642            .map_err(|_| KeyPackageNewError::StorageError)?;
643
644        Ok(full_kp)
645    }
646
647    /// Build a batch of virtual-client KeyPackages a sibling can reproduce.
648    ///
649    /// Allocates a single generation of the `key_package` operation ratchet for
650    /// the emulation epoch identified by `epoch_id`. For each
651    /// `key_package_index` in `0..count` it derives a per-KeyPackage seed
652    /// secret from that one operation secret and derives the KeyPackage's init
653    /// key and leaf encryption key from the seed. Each leaf carries an
654    /// encrypted `DerivationInfo` under
655    /// [`VC_COMPONENT_ID`](crate::components::vc_derivation_info::VC_COMPONENT_ID)
656    /// so a sibling can recover the emulation leaf index, generation, and
657    /// index.
658    ///
659    /// The operation secret and the seeds are derived under the emulation
660    /// epoch's ciphersuite (the operation tree's ciphersuite). The init and
661    /// leaf-encryption keys are derived from each seed under the KeyPackage's
662    /// own `ciphersuite`.
663    ///
664    /// Each leaf must declare `AppDataDictionary` support and list
665    /// [`VC_COMPONENT_ID`](crate::components::vc_derivation_info::VC_COMPONENT_ID)
666    /// in its `AppComponents` entry, otherwise this returns
667    /// [`VirtualClientsError::AppDataDictionaryNotSupported`](crate::components::vc_derivation_info::VirtualClientsError::AppDataDictionaryNotSupported)
668    /// or
669    /// [`VirtualClientsError::VcComponentNotListed`](crate::components::vc_derivation_info::VirtualClientsError::VcComponentNotListed).
670    ///
671    /// Returns a [`VcKeyPackageBatch`] holding the batch's `generation` and one
672    /// `(KeyPackageBundle, KeyPackageInfo)` per KeyPackage. Each bundle is also
673    /// written to storage so the creating client can process its own Welcomes,
674    /// and each
675    /// [`KeyPackageInfo`](crate::components::vc_derivation_info::KeyPackageInfo)
676    /// carries the index the client hands to its sibling.
677    ///
678    /// Returns [`KeyPackageNewError::EmptyBatch`] when `count` is 0, before
679    /// loading any state or consuming a generation.
680    #[cfg(feature = "virtual-clients-draft")]
681    pub fn build_vc_batch(
682        self,
683        ciphersuite: Ciphersuite,
684        provider: &impl OpenMlsProvider,
685        signer: &impl Signer,
686        credential_with_key: CredentialWithKey,
687        epoch_id: crate::components::vc_derivation_info::EpochId,
688        count: usize,
689    ) -> Result<VcKeyPackageBatch, KeyPackageNewError> {
690        // Reject an unsupported ciphersuite and an empty batch before loading
691        // state or consuming a generation.
692        provider
693            .crypto()
694            .supports(ciphersuite)
695            .map_err(|_| KeyPackageNewError::UnsupportedCiphersuite(ciphersuite))?;
696
697        if count == 0 {
698            return Err(KeyPackageNewError::EmptyBatch);
699        }
700        let mut builder = VcKeyPackageBatchBuilder::with_capacity(provider, epoch_id, count)?;
701        for _ in 0..count {
702            builder.add_key_package(
703                self.clone(),
704                ciphersuite,
705                provider.crypto(),
706                signer,
707                credential_with_key.clone(),
708            )?;
709        }
710        builder.finalize(provider)
711    }
712}
713
714/// A [`KeyPackageBundle`] contains a [`KeyPackage`] and the init and encryption
715/// private key.
716///
717/// This is stored to ensure the private key is handled together with the key
718/// package.
719#[derive(Debug, Clone, Serialize, Deserialize)]
720pub struct KeyPackageBundle {
721    pub(crate) key_package: KeyPackage,
722    pub(crate) private_init_key: HpkePrivateKey,
723    pub(crate) private_encryption_key: EncryptionPrivateKey,
724}
725
726// Public `KeyPackageBundle` functions.
727impl KeyPackageBundle {
728    /// Get a reference to the public part of this bundle, i.e. the [`KeyPackage`].
729    pub fn key_package(&self) -> &KeyPackage {
730        &self.key_package
731    }
732
733    /// Extract the key package from the bundle.
734    pub fn into_key_package(self) -> KeyPackage {
735        self.key_package
736    }
737
738    /// Get a reference to the private init key.
739    pub fn init_private_key(&self) -> &HpkePrivateKey {
740        &self.private_init_key
741    }
742
743    /// Get the encryption key pair.
744    pub(crate) fn encryption_key_pair(&self) -> EncryptionKeyPair {
745        EncryptionKeyPair::from((
746            self.key_package.leaf_node().encryption_key().clone(),
747            self.private_encryption_key.clone(),
748        ))
749    }
750}
751
752#[cfg(any(test, feature = "test-utils"))]
753impl KeyPackageBundle {
754    /// Generate a new key package bundle with the private key.
755    pub fn new(
756        key_package: KeyPackage,
757        private_init_key: HpkePrivateKey,
758        private_encryption_key: EncryptionPrivateKey,
759    ) -> Self {
760        Self {
761            key_package,
762            private_init_key,
763            private_encryption_key,
764        }
765    }
766
767    /// Get a reference to the private encryption key.
768    pub fn encryption_private_key(&self) -> &HpkePrivateKey {
769        self.private_encryption_key.key()
770    }
771}
772
773#[cfg(test)]
774impl KeyPackageBundle {
775    pub(crate) fn generate(
776        provider: &impl OpenMlsProvider,
777        signer: &impl Signer,
778        ciphersuite: Ciphersuite,
779        credential_with_key: CredentialWithKey,
780    ) -> Self {
781        KeyPackage::builder()
782            .build(ciphersuite, provider, signer, credential_with_key)
783            .unwrap()
784    }
785}