p2panda_encryption/data_scheme/
dcgka.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! A decentralized continuous group key agreement protocol (DCGKA) for p2panda's "data encryption"
4//! scheme with forward secrecy and post-compromise security.
5//!
6//! This uses the 2SM (Two-Party Secure Messaging) key agreement scheme internally with strong
7//! forward secrecy guarantees.
8use std::collections::{HashMap, HashSet};
9use std::fmt::{Debug, Display};
10use std::marker::PhantomData;
11
12use serde::{Deserialize, Serialize};
13use thiserror::Error;
14
15use crate::crypto::Rng;
16use crate::data_scheme::{GroupSecret, GroupSecretError, SecretBundle, SecretBundleState};
17use crate::key_bundle::LongTermKeyBundle;
18use crate::traits::{
19    GroupMembership, IdentityHandle, IdentityManager, IdentityRegistry, OperationId, PreKeyManager,
20    PreKeyRegistry,
21};
22use crate::two_party::{TwoParty, TwoPartyError, TwoPartyMessage, TwoPartyState};
23
24/// A decentralized continuous group key agreement protocol (DCGKA) for p2panda's "data encryption"
25/// scheme with forward secrecy and post-compromise security.
26pub struct Dcgka<ID, OP, PKI, DGM, KMG> {
27    _marker: PhantomData<(ID, OP, PKI, DGM, KMG)>,
28}
29
30/// Serializable state of "data encryption" DCGKA (for persistence).
31#[derive(Debug, Serialize, Deserialize)]
32#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
33pub struct DcgkaState<ID, OP, PKI, DGM, KMG>
34where
35    ID: IdentityHandle,
36    OP: OperationId,
37    PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, LongTermKeyBundle>,
38    DGM: GroupMembership<ID, OP>,
39    KMG: IdentityManager<KMG::State> + PreKeyManager,
40{
41    /// Public Key Infrastructure (PKI). From here we retrieve the identity keys and long-term
42    /// pre-key bundles for each member to do 2SM.
43    pub pki: PKI::State,
44
45    /// Our own key manager state holding the secret parts for our own identity keys and published
46    /// long-term pre-key bundles so we can do 2SM.
47    pub my_keys: KMG::State,
48
49    /// Our id which is used as a unique handle inside this group.
50    pub my_id: ID,
51
52    /// Handlers for each member to manage the "Two-Party Secure Messaging" (2SM) key-agreement
53    /// protocol as specified in the paper.
54    pub two_party: HashMap<ID, TwoPartyState<LongTermKeyBundle>>,
55
56    /// Decentralised group membership (DGM) state.
57    pub dgm: DGM::State,
58}
59
60impl<ID, OP, PKI, DGM, KMG> Dcgka<ID, OP, PKI, DGM, KMG>
61where
62    ID: IdentityHandle,
63    OP: OperationId,
64    PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, LongTermKeyBundle>,
65    DGM: GroupMembership<ID, OP>,
66    KMG: IdentityManager<KMG::State> + PreKeyManager,
67{
68    /// Returns new DCGKA state with our own identity and key managers.
69    ///
70    /// Use this when creating a new group or before accepting an invitation to an existing one.
71    pub fn init(
72        my_id: ID,
73        my_keys: KMG::State,
74        pki: PKI::State,
75        dgm: DGM::State,
76    ) -> DcgkaState<ID, OP, PKI, DGM, KMG> {
77        DcgkaState {
78            pki,
79            my_id,
80            my_keys,
81            two_party: HashMap::new(),
82            dgm,
83        }
84    }
85
86    /// Handler for when a "remote" control message is received from the network or when we need to
87    /// process our "local" operation after calling "create", "update", "add" or "remove".
88    ///
89    /// It takes the user ID of the message sender, a control message, and a direct message (or
90    /// none if there is no associated direct message).
91    ///
92    /// Control messages are expected to be authenticated and causally ordered.
93    pub fn process(
94        y: DcgkaState<ID, OP, PKI, DGM, KMG>,
95        input: ProcessInput<ID, OP, DGM>,
96    ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
97        let ProcessInput {
98            sender,
99            control_message,
100            direct_message,
101            seq,
102        } = input;
103        let (y_i, output) = match control_message {
104            ControlMessage::Create { initial_members } => {
105                Self::process_create(y, &sender, initial_members, direct_message)?
106            }
107            ControlMessage::Update => Self::process_update(y, &sender, direct_message)?,
108            ControlMessage::Remove { removed } => {
109                Self::process_remove(y, sender, seq, &removed, direct_message)?
110            }
111            ControlMessage::Add { added } => {
112                Self::process_add(y, sender, seq, added, direct_message)?
113            }
114        };
115        Ok((y_i, output))
116    }
117
118    /// Takes a set of users IDs (including us), an initial group secret and creates a new group with those members who will learn about this secret.
119    ///
120    /// Note that every member ID needs to be unique for this group.
121    pub fn create(
122        y: DcgkaState<ID, OP, PKI, DGM, KMG>,
123        initial_members: Vec<ID>,
124        group_secret: &GroupSecret,
125        rng: &Rng,
126    ) -> DcgkaOperationResult<ID, OP, PKI, DGM, KMG> {
127        // De-duplicate members.
128        let mut initial_members: Vec<ID> =
129            initial_members.into_iter().fold(Vec::new(), |mut acc, id| {
130                if !acc.contains(&id) {
131                    acc.push(id);
132                }
133                acc
134            });
135
136        // Add ourselves if the user hasn't done it yet.
137        if !initial_members.contains(&y.my_id) {
138            initial_members.push(y.my_id);
139        }
140
141        // The "create" function constructs the "create" control message.
142        let control_message = ControlMessage::Create {
143            initial_members: initial_members.clone(),
144        };
145
146        // Generate the set of direct messages to send.
147        let (y_ii, direct_messages) =
148            Self::send_group_secret(y, &initial_members, group_secret, rng)?;
149
150        Ok((
151            y_ii,
152            OperationOutput {
153                control_message,
154                direct_messages,
155            },
156        ))
157    }
158
159    /// Called by group members when they receive the "create" message.
160    fn process_create(
161        mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
162        sender: &ID,
163        initial_members: Vec<ID>,
164        direct_message: Option<DirectMessage<ID, OP, DGM>>,
165    ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
166        y.dgm =
167            DGM::create(y.my_id, &initial_members).map_err(|err| DcgkaError::DgmOperation(err))?;
168        Self::process_secret(y, sender, direct_message)
169    }
170
171    /// Establishes a new secret for the group.
172    pub fn update(
173        y: DcgkaState<ID, OP, PKI, DGM, KMG>,
174        group_secret: &GroupSecret,
175        rng: &Rng,
176    ) -> DcgkaOperationResult<ID, OP, PKI, DGM, KMG> {
177        let control_message = ControlMessage::Update;
178
179        let recipient_ids: Vec<ID> = Self::members(&y)?
180            .into_iter()
181            .filter(|member| member != &y.my_id)
182            .collect();
183
184        let (y_i, direct_messages) = Self::send_group_secret(y, &recipient_ids, group_secret, rng)?;
185
186        Ok((
187            y_i,
188            OperationOutput {
189                control_message,
190                direct_messages,
191            },
192        ))
193    }
194
195    /// Called by group members when they receive the "update" control message.
196    fn process_update(
197        y: DcgkaState<ID, OP, PKI, DGM, KMG>,
198        sender: &ID,
199        direct_message: Option<DirectMessage<ID, OP, DGM>>,
200    ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
201        Self::process_secret(y, sender, direct_message)
202    }
203
204    /// Remove a member from the group.
205    ///
206    /// This takes a new secret as an argument for the remaining members for post-compromise security (PCS).
207    pub fn remove(
208        y: DcgkaState<ID, OP, PKI, DGM, KMG>,
209        removed: ID,
210        group_secret: &GroupSecret,
211        rng: &Rng,
212    ) -> DcgkaOperationResult<ID, OP, PKI, DGM, KMG> {
213        let control_message = ControlMessage::Remove { removed };
214
215        let recipient_ids: Vec<ID> = Self::members(&y)?
216            .into_iter()
217            .filter(|member| member != &y.my_id && member != &removed)
218            .collect();
219
220        let (y_i, direct_messages) = Self::send_group_secret(y, &recipient_ids, group_secret, rng)?;
221
222        Ok((
223            y_i,
224            OperationOutput {
225                control_message,
226                direct_messages,
227            },
228        ))
229    }
230
231    /// Called by group members when they receive the "remove" control message.
232    fn process_remove(
233        mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
234        sender: ID,
235        seq: OP,
236        removed: &ID,
237        direct_message: Option<DirectMessage<ID, OP, DGM>>,
238    ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
239        y.dgm = DGM::remove(y.dgm, sender, removed, seq)
240            .map_err(|err| DcgkaError::DgmOperation(err))?;
241        Self::process_secret(y, &sender, direct_message)
242    }
243
244    /// Adds a new group member.
245    ///
246    /// The added group member will receive a direct "welcome" message containing all previously
247    /// used secrets of the group in form of a [`SecretBundle`]. Every member will process
248    /// an "add" control message.
249    pub fn add(
250        y: DcgkaState<ID, OP, PKI, DGM, KMG>,
251        added: ID,
252        bundle: &SecretBundleState,
253        rng: &Rng,
254    ) -> DcgkaOperationResult<ID, OP, PKI, DGM, KMG> {
255        // Construct a control message of type "add" to broadcast to the group.
256        let control_message = ControlMessage::Add { added };
257
258        // Construct a welcome message that is sent to the new member as a direct message.
259        let (y_i, ciphertext) = {
260            let bundle_bytes = bundle.to_bytes()?;
261            Self::encrypt_to(y, &added, &bundle_bytes, rng)?
262        };
263        let direct_message = DirectMessage {
264            recipient: added,
265            content: DirectMessageContent::Welcome {
266                ciphertext,
267                history: y_i.dgm.clone(),
268            },
269        };
270
271        Ok((
272            y_i,
273            OperationOutput {
274                control_message,
275                direct_messages: vec![direct_message],
276            },
277        ))
278    }
279
280    /// Called by both the sender and each recipient of an "add" control message, including the new
281    /// group member.
282    fn process_add(
283        mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
284        sender: ID,
285        seq: OP,
286        added: ID,
287        direct_message: Option<DirectMessage<ID, OP, DGM>>,
288    ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
289        y.dgm = DGM::add(y.dgm, sender, added, seq).map_err(|err| DcgkaError::DgmOperation(err))?;
290
291        if added == y.my_id {
292            let Some(DirectMessage {
293                recipient,
294                content:
295                    DirectMessageContent::Welcome {
296                        ciphertext,
297                        history,
298                    },
299                ..
300            }) = direct_message
301            else {
302                return match direct_message {
303                    Some(direct_message) => Err(DcgkaError::UnexpectedDirectMessageType(
304                        DirectMessageType::Welcome,
305                        direct_message.message_type(),
306                    )),
307                    None => Err(DcgkaError::MissingDirectMessage(DirectMessageType::Welcome)),
308                };
309            };
310
311            if recipient != y.my_id {
312                return Err(DcgkaError::NotOurDirectMessage(y.my_id, recipient));
313            }
314
315            return Self::process_welcome(y, sender, ciphertext, history);
316        }
317
318        Ok((y, GroupSecretOutput::None))
319    }
320
321    /// Second function called by a newly added group member (the first is the call to init that
322    /// sets up their state).
323    fn process_welcome(
324        mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
325        sender: ID,
326        ciphertext: TwoPartyMessage,
327        history: DGM::State,
328    ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
329        y.dgm = DGM::from_welcome(y.my_id, history).map_err(|err| DcgkaError::DgmOperation(err))?;
330
331        let (y_i, bundle) = {
332            let (y_i, plaintext) = Self::decrypt_from(y, &sender, ciphertext)?;
333            let bundle = SecretBundle::try_from_bytes(&plaintext)?;
334            (y_i, bundle)
335        };
336
337        Ok((y_i, GroupSecretOutput::Bundle(bundle)))
338    }
339
340    /// Takes a group secret, then calls `encrypt_to` to
341    /// encrypt it for each other group member using the 2SM protocol. It returns the updated
342    /// protocol state and the set of direct messages to send.
343    fn send_group_secret(
344        y: DcgkaState<ID, OP, PKI, DGM, KMG>,
345        recipients: &[ID],
346        group_secret: &GroupSecret,
347        rng: &Rng,
348    ) -> SendSecretResult<ID, OP, PKI, DGM, KMG> {
349        let mut direct_messages: Vec<DirectMessage<ID, OP, DGM>> =
350            Vec::with_capacity(recipients.len());
351
352        let y_i = {
353            let mut y_loop = y;
354            for recipient in recipients {
355                // Skip ourselves.
356                if recipient == &y_loop.my_id {
357                    continue;
358                }
359
360                // Encrypt to every recipient.
361                let (y_next, ciphertext) =
362                    Self::encrypt_to(y_loop, recipient, &group_secret.to_bytes()?, rng)?;
363                y_loop = y_next;
364
365                direct_messages.push(DirectMessage {
366                    recipient: *recipient,
367                    content: DirectMessageContent::TwoParty { ciphertext },
368                });
369            }
370            y_loop
371        };
372
373        Ok((y_i, direct_messages))
374    }
375
376    /// Handles a new secret for the group, received as an
377    /// encrypted, direct message from another member.
378    fn process_secret(
379        y: DcgkaState<ID, OP, PKI, DGM, KMG>,
380        sender: &ID,
381        direct_message: Option<DirectMessage<ID, OP, DGM>>,
382    ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
383        let Some(direct_message) = direct_message else {
384            return Ok((y, GroupSecretOutput::None));
385        };
386
387        let DirectMessage {
388            recipient,
389            content: DirectMessageContent::TwoParty { ciphertext },
390            ..
391        } = direct_message
392        else {
393            return Err(DcgkaError::UnexpectedDirectMessageType(
394                DirectMessageType::TwoParty,
395                direct_message.message_type(),
396            ));
397        };
398
399        if recipient != y.my_id {
400            return Err(DcgkaError::NotOurDirectMessage(y.my_id, recipient));
401        }
402
403        let (y_i, plaintext) = Self::decrypt_from(y, sender, ciphertext)?;
404        let group_secret = GroupSecret::try_from_bytes(&plaintext)?;
405
406        Ok((y_i, GroupSecretOutput::Secret(group_secret)))
407    }
408
409    /// Uses 2SM to encrypt a direct message for another group member. The first time a message is
410    /// encrypted to a particular recipient ID, the 2SM protocol state is initialised and stored in
411    /// γ.2sm[ID]. We then use 2SM-Send to encrypt the message, and store the updated protocol
412    /// state in γ.
413    fn encrypt_to(
414        mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
415        recipient: &ID,
416        plaintext: &[u8],
417        rng: &Rng,
418    ) -> DcgkaResult<ID, OP, PKI, DGM, KMG, TwoPartyMessage> {
419        let y_2sm = match y.two_party.remove(recipient) {
420            Some(y_2sm) => y_2sm,
421            None => {
422                let (pki_i, prekey_bundle) = PKI::key_bundle(y.pki, recipient)
423                    .map_err(|err| DcgkaError::PreKeyRegistry(err))?;
424                y.pki = pki_i;
425                let prekey_bundle = prekey_bundle.ok_or(DcgkaError::MissingPreKeys(*recipient))?;
426                TwoParty::<KMG, LongTermKeyBundle>::init(prekey_bundle)
427            }
428        };
429        let (y_2sm_i, ciphertext) =
430            TwoParty::<KMG, LongTermKeyBundle>::send(y_2sm, &y.my_keys, plaintext, rng)?;
431        y.two_party.insert(*recipient, y_2sm_i);
432        Ok((y, ciphertext))
433    }
434
435    /// Is the reverse of encrypt_to. It similarly initialises the protocol state on first use, and
436    /// then uses 2SM-Receive to decrypt the ciphertext, with the protocol state stored in
437    /// γ.2sm[ID].
438    fn decrypt_from(
439        mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
440        sender: &ID,
441        ciphertext: TwoPartyMessage,
442    ) -> DcgkaResult<ID, OP, PKI, DGM, KMG, Vec<u8>> {
443        let y_2sm = match y.two_party.remove(sender) {
444            Some(y_2sm) => y_2sm,
445            None => {
446                let (pki_i, prekey_bundle) = PKI::key_bundle(y.pki, sender)
447                    .map_err(|err| DcgkaError::PreKeyRegistry(err))?;
448                y.pki = pki_i;
449                let prekey_bundle = prekey_bundle.ok_or(DcgkaError::MissingPreKeys(*sender))?;
450                TwoParty::<KMG, LongTermKeyBundle>::init(prekey_bundle)
451            }
452        };
453        let (y_2sm_i, y_my_keys_i, plaintext) =
454            TwoParty::<KMG, LongTermKeyBundle>::receive(y_2sm, y.my_keys, ciphertext)?;
455        y.my_keys = y_my_keys_i;
456        y.two_party.insert(*sender, y_2sm_i);
457        Ok((y, plaintext))
458    }
459
460    /// Returns the set of group members at the current time.
461    pub fn members(
462        y: &DcgkaState<ID, OP, PKI, DGM, KMG>,
463    ) -> Result<HashSet<ID>, DcgkaError<ID, OP, PKI, DGM, KMG>> {
464        let members = DGM::members(&y.dgm).map_err(|err| DcgkaError::GroupMembership(err))?;
465        Ok(members)
466    }
467}
468
469pub type SendSecretResult<ID, OP, PKI, DGM, KMG> = Result<
470    (
471        DcgkaState<ID, OP, PKI, DGM, KMG>,
472        Vec<DirectMessage<ID, OP, DGM>>,
473    ),
474    DcgkaError<ID, OP, PKI, DGM, KMG>,
475>;
476
477pub type DcgkaResult<ID, OP, PKI, DGM, KMG, T> =
478    Result<(DcgkaState<ID, OP, PKI, DGM, KMG>, T), DcgkaError<ID, OP, PKI, DGM, KMG>>;
479
480pub type DcgkaProcessResult<ID, OP, PKI, DGM, KMG> =
481    DcgkaResult<ID, OP, PKI, DGM, KMG, GroupSecretOutput>;
482
483pub type DcgkaOperationResult<ID, OP, PKI, DGM, KMG> =
484    DcgkaResult<ID, OP, PKI, DGM, KMG, OperationOutput<ID, OP, DGM>>;
485
486/// Message that should be broadcast to the group.
487///
488/// The control message must be distributed to the other group members through Authenticated Causal
489/// Broadcast, calling the process function on the recipient when they are delivered.
490#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
491pub enum ControlMessage<ID> {
492    Create { initial_members: Vec<ID> },
493    Update,
494    Remove { removed: ID },
495    Add { added: ID },
496}
497
498impl<ID> Display for ControlMessage<ID> {
499    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
500        write!(
501            f,
502            "{}",
503            match self {
504                ControlMessage::Create { .. } => "create",
505                ControlMessage::Update => "update",
506                ControlMessage::Remove { .. } => "remove",
507                ControlMessage::Add { .. } => "add",
508            }
509        )
510    }
511}
512
513/// Arguments required to process a group operation received from another member.
514#[derive(Clone, Debug)]
515pub struct ProcessInput<ID, OP, DGM>
516where
517    DGM: GroupMembership<ID, OP>,
518{
519    /// Sequence number, which consecutively numbers successive control messages from the same
520    /// sender.
521    pub seq: OP,
522
523    /// Author of this message.
524    pub sender: ID,
525
526    /// Message received from this author.
527    pub control_message: ControlMessage<ID>,
528
529    /// Optional direct message for us.
530    ///
531    /// Applications need to filter the direct message for the correct recipient before passing it
532    /// as an input. There can always only be max. 1 direct message per recipient.
533    pub direct_message: Option<DirectMessage<ID, OP, DGM>>,
534}
535
536/// Secret encryption keys we've learned about after processing a member's control message.
537#[derive(Debug, PartialEq, Eq)]
538pub enum GroupSecretOutput {
539    None,
540    Secret(GroupSecret),
541    Bundle(SecretBundleState),
542}
543
544/// Calling "create", "add", "remove" and "update" returns a control message that should be
545/// broadcast to the group and a set of direct messages that should be sent to the regarding
546/// members.
547#[derive(Debug)]
548pub struct OperationOutput<ID, OP, DGM>
549where
550    DGM: GroupMembership<ID, OP>,
551{
552    /// Control message that should be broadcast to the group.
553    pub control_message: ControlMessage<ID>,
554
555    /// Set of messages directly to be sent to specific users.
556    pub direct_messages: Vec<DirectMessage<ID, OP, DGM>>,
557}
558
559/// Direct message that should be sent to a single member.
560///
561/// The direct message must be distributed to the other group members through Authenticated Causal
562/// Broadcast, calling the process function on the recipient when they are delivered.
563///
564/// If direct messages are sent along with a control message, we assume that the direct message for
565/// the appropriate recipient is delivered in the same call to process. Our algorithm never sends a
566/// direct message without an associated broadcast control message.
567#[derive(Clone, Debug, Serialize, Deserialize)]
568pub struct DirectMessage<ID, OP, DGM>
569where
570    DGM: GroupMembership<ID, OP>,
571{
572    pub recipient: ID,
573    pub content: DirectMessageContent<ID, OP, DGM>,
574}
575
576#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
577pub enum DirectMessageType {
578    Welcome,
579    TwoParty,
580}
581
582impl Display for DirectMessageType {
583    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
584        write!(
585            f,
586            "{}",
587            match self {
588                DirectMessageType::Welcome => "welcome",
589                DirectMessageType::TwoParty => "2sm",
590            }
591        )
592    }
593}
594
595impl<ID, OP, DGM> DirectMessage<ID, OP, DGM>
596where
597    DGM: GroupMembership<ID, OP>,
598{
599    pub fn message_type(&self) -> DirectMessageType {
600        match self.content {
601            DirectMessageContent::Welcome { .. } => DirectMessageType::Welcome,
602            DirectMessageContent::TwoParty { .. } => DirectMessageType::TwoParty,
603        }
604    }
605}
606
607#[derive(Clone, Debug, Serialize, Deserialize)]
608pub enum DirectMessageContent<ID, OP, DGM>
609where
610    DGM: GroupMembership<ID, OP>,
611{
612    Welcome {
613        ciphertext: TwoPartyMessage,
614        history: DGM::State,
615    },
616    TwoParty {
617        ciphertext: TwoPartyMessage,
618    },
619}
620
621#[derive(Debug, Error)]
622pub enum DcgkaError<ID, OP, PKI, DGM, KMG>
623where
624    PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, LongTermKeyBundle>,
625    DGM: GroupMembership<ID, OP>,
626    KMG: PreKeyManager,
627{
628    #[error("expected direct message of type \"{0}\" but got nothing instead")]
629    MissingDirectMessage(DirectMessageType),
630
631    #[error("expected direct message of type \"{0}\" but got message of type \"{1}\" instead")]
632    UnexpectedDirectMessageType(DirectMessageType, DirectMessageType),
633
634    #[error("direct message recipient mismatch, expected recipient: {1}, actual recipient: {0}")]
635    NotOurDirectMessage(ID, ID),
636
637    #[error("computing members view from dgm failed: {0}")]
638    GroupMembership(DGM::Error),
639
640    #[error("dgm operation failed: {0}")]
641    DgmOperation(DGM::Error),
642
643    #[error("failed retrieving bundle from pre key registry: {0}")]
644    PreKeyRegistry(<PKI as PreKeyRegistry<ID, LongTermKeyBundle>>::Error),
645
646    #[error("failed retrieving identity from registry: {0}")]
647    IdentityRegistry(<PKI as IdentityRegistry<ID, PKI::State>>::Error),
648
649    #[error("missing key bundle for member {0}")]
650    MissingPreKeys(ID),
651
652    #[error(transparent)]
653    GroupSecret(#[from] GroupSecretError),
654
655    #[error(transparent)]
656    KeyManager(KMG::Error),
657
658    #[error(transparent)]
659    TwoParty(#[from] TwoPartyError),
660}