p2panda_encryption/data_scheme/
group.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! API to manage groups using the "Data Encryption" scheme and process remote control- and
4//! application messages.
5#![allow(clippy::type_complexity)]
6use std::collections::{HashSet, VecDeque};
7use std::marker::PhantomData;
8
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11
12use crate::crypto::xchacha20::{XAeadError, XAeadNonce};
13use crate::crypto::{Rng, RngError};
14use crate::data_scheme::data::{decrypt_data, encrypt_data};
15use crate::data_scheme::dcgka::{
16    ControlMessage, Dcgka, DcgkaError, DcgkaState, DirectMessage, GroupSecretOutput,
17    OperationOutput, ProcessInput,
18};
19use crate::data_scheme::group_secret::{
20    GroupSecret, GroupSecretError, GroupSecretId, SecretBundle, SecretBundleState,
21};
22use crate::key_bundle::LongTermKeyBundle;
23use crate::traits::{
24    GroupMembership, GroupMessage, GroupMessageContent, IdentityHandle, IdentityManager,
25    IdentityRegistry, OperationId, Ordering, PreKeyManager, PreKeyRegistry,
26};
27
28/// API to manage groups using the "Data Encryption" scheme and process remote control messages.
29pub struct EncryptionGroup<ID, OP, PKI, DGM, KMG, ORD> {
30    _marker: PhantomData<(ID, OP, PKI, DGM, KMG, ORD)>,
31}
32
33/// Group state for "data encryption" scheme. Serializable for persistence.
34#[derive(Debug, Serialize, Deserialize)]
35#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
36pub struct GroupState<ID, OP, PKI, DGM, KMG, ORD>
37where
38    ID: IdentityHandle,
39    OP: OperationId,
40    PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, LongTermKeyBundle>,
41    PKI::State: Clone,
42    DGM: GroupMembership<ID, OP>,
43    KMG: IdentityManager<KMG::State> + PreKeyManager,
44    KMG::State: Clone,
45    ORD: Ordering<ID, OP, DGM>,
46{
47    pub my_id: ID,
48    pub dcgka: DcgkaState<ID, OP, PKI, DGM, KMG>,
49    pub orderer: ORD::State,
50    pub secrets: SecretBundleState,
51    pub is_welcomed: bool,
52}
53
54impl<ID, OP, PKI, DGM, KMG, ORD> EncryptionGroup<ID, OP, PKI, DGM, KMG, ORD>
55where
56    ID: IdentityHandle,
57    OP: OperationId,
58    PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, LongTermKeyBundle>,
59    PKI::State: Clone,
60    DGM: GroupMembership<ID, OP>,
61    KMG: IdentityManager<KMG::State> + PreKeyManager,
62    KMG::State: Clone,
63    ORD: Ordering<ID, OP, DGM>,
64{
65    /// Returns initial state for group.
66    ///
67    /// This needs to be called before creating or being added to a group.
68    #[allow(unused)]
69    pub fn init(
70        my_id: ID,
71        my_keys: KMG::State,
72        pki: PKI::State,
73        dgm: DGM::State,
74        orderer: ORD::State,
75    ) -> GroupState<ID, OP, PKI, DGM, KMG, ORD> {
76        GroupState {
77            my_id,
78            dcgka: Dcgka::init(my_id, my_keys, pki, dgm),
79            orderer,
80            secrets: SecretBundle::init(),
81            is_welcomed: false,
82        }
83    }
84
85    /// Creates new group with initial set of members.
86    pub fn create(
87        mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
88        initial_members: Vec<ID>,
89        rng: &Rng,
90    ) -> GroupResult<ORD::Message, ID, OP, PKI, DGM, KMG, ORD> {
91        if y.is_welcomed {
92            return Err(GroupError::GroupAlreadyEstablished);
93        }
94
95        // Generate new group secret.
96        let group_secret = SecretBundle::generate(&y.secrets, rng)?;
97
98        // Create new group with initial members.
99        let (y_dcgka_i, pre) = Dcgka::create(y.dcgka, initial_members, &group_secret, rng)?;
100        y.dcgka = y_dcgka_i;
101
102        let (mut y_i, message) = Self::process_local(y, pre, Some(group_secret))?;
103
104        // Set our own "create" as the "welcome" message.
105        let y_orderer_i = ORD::set_welcome(y_i.orderer, &message).map_err(GroupError::Orderer)?;
106        y_i.orderer = y_orderer_i;
107        y_i.is_welcomed = true;
108
109        Ok((y_i, message))
110    }
111
112    /// Adds new member to group.
113    pub fn add(
114        mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
115        added: ID,
116        rng: &Rng,
117    ) -> GroupResult<ORD::Message, ID, OP, PKI, DGM, KMG, ORD> {
118        if !y.is_welcomed {
119            return Err(GroupError::GroupNotYetEstablished);
120        }
121
122        if y.my_id == added {
123            return Err(GroupError::NotAddOurselves);
124        }
125
126        // Add a new member to the group.
127        let (y_dcgka_i, pre) = Dcgka::add(y.dcgka, added, &y.secrets, rng)?;
128        y.dcgka = y_dcgka_i;
129
130        Self::process_local(y, pre, None)
131    }
132
133    /// Removes member from group. It is possible to remove ourselves.
134    pub fn remove(
135        mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
136        removed: ID,
137        rng: &Rng,
138    ) -> GroupResult<ORD::Message, ID, OP, PKI, DGM, KMG, ORD> {
139        if !y.is_welcomed {
140            return Err(GroupError::GroupNotYetEstablished);
141        }
142
143        // Generate new group secret.
144        let group_secret = SecretBundle::generate(&y.secrets, rng)?;
145
146        // Remove a member from the group.
147        let (y_dcgka_i, pre) = Dcgka::remove(y.dcgka, removed, &group_secret, rng)?;
148        y.dcgka = y_dcgka_i;
149
150        Self::process_local(y, pre, Some(group_secret))
151    }
152
153    /// Updates group by providing all current members with new group secret.
154    pub fn update(
155        mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
156        rng: &Rng,
157    ) -> GroupResult<ORD::Message, ID, OP, PKI, DGM, KMG, ORD> {
158        if !y.is_welcomed {
159            return Err(GroupError::GroupNotYetEstablished);
160        }
161
162        // Generate new group secret.
163        let group_secret = SecretBundle::generate(&y.secrets, rng)?;
164
165        // Update the group by generating a new seed.
166        let (y_dcgka_i, pre) = Dcgka::update(y.dcgka, &group_secret, rng)?;
167        y.dcgka = y_dcgka_i;
168
169        Self::process_local(y, pre, Some(group_secret))
170    }
171
172    /// Handler for incoming, remote messages.
173    ///
174    /// This yields a list of "outputs" which can be either control messages which need to be
175    /// broadcast to all members in the group or decrypted application payloads.
176    ///
177    /// If we got removed after processing a control message we will receive an "removed" output
178    /// signal.
179    pub fn receive(
180        mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
181        message: &ORD::Message,
182    ) -> GroupResult<Vec<GroupOutput<ID, OP, DGM, ORD>>, ID, OP, PKI, DGM, KMG, ORD> {
183        let message_content = message.content();
184        let mut is_create_or_welcome = false;
185
186        // Accept "create" control messages if we haven't established our state yet and if we are
187        // part of the initial members set.
188        if let GroupMessageContent::Control(ControlMessage::Create {
189            ref initial_members,
190        }) = message_content
191        {
192            if y.is_welcomed {
193                return Err(GroupError::GroupAlreadyEstablished);
194            }
195
196            if initial_members.contains(&y.my_id) {
197                is_create_or_welcome = true;
198            }
199        }
200
201        // Accept "add" control messages if we are being added by it.
202        if let GroupMessageContent::Control(ControlMessage::Add { added }) = message_content
203            && !y.is_welcomed
204            && added == y.my_id
205        {
206            is_create_or_welcome = true;
207        }
208
209        let y_orderer_i = ORD::queue(y.orderer, message).map_err(GroupError::Orderer)?;
210        y.orderer = y_orderer_i;
211
212        if !y.is_welcomed && !is_create_or_welcome {
213            // We're receiving control- and application messages for this group but we haven't
214            // joined yet. We keep these messages for later. We don't know yet when we will join
215            // the group and which of these messages we can process afterwards.
216            return Ok((y, vec![]));
217        }
218
219        if !y.is_welcomed && is_create_or_welcome {
220            // We've received a "create" or "add" (welcome) message for us and can join the group
221            // now.
222            let y_orderer_i = ORD::set_welcome(y.orderer, message).map_err(GroupError::Orderer)?;
223            y.orderer = y_orderer_i;
224        }
225
226        let mut results = Vec::new();
227        let mut y_loop = y;
228
229        let mut control_messages = VecDeque::new();
230        let mut application_messages = VecDeque::new();
231
232        // Check if there's any correctly ordered messages ready to-be processed.
233        loop {
234            let (y_orderer_next, result) =
235                ORD::next_ready_message(y_loop.orderer).map_err(GroupError::Orderer)?;
236            y_loop.orderer = y_orderer_next;
237
238            let Some(message) = result else {
239                break;
240            };
241
242            match message.content() {
243                GroupMessageContent::Control(_) => {
244                    control_messages.push_back(message);
245                }
246                GroupMessageContent::Application { .. } => {
247                    application_messages.push_back(message);
248                }
249            }
250        }
251
252        // Process all control messages first.
253        while let Some(message) = control_messages.pop_front() {
254            let (y_next, result) = Self::process_ready(y_loop, &message)?;
255            y_loop = y_next;
256            if let Some(message) = result {
257                results.push(message);
258            }
259        }
260
261        // .. then process all application messages.
262        while let Some(message) = application_messages.pop_front() {
263            let (y_next, result) = Self::process_ready(y_loop, &message)?;
264            y_loop = y_next;
265            if let Some(message) = result {
266                results.push(message);
267            }
268        }
269
270        Ok((y_loop, results))
271    }
272
273    /// Encrypts application payload towards the current group.
274    ///
275    /// The returned message can then be broadcast to all members in the group. The underlying
276    /// protocol makes sure that all members will be able to decrypt this message.
277    pub fn send(
278        mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
279        plaintext: &[u8],
280        rng: &Rng,
281    ) -> GroupResult<ORD::Message, ID, OP, PKI, DGM, KMG, ORD> {
282        if !y.is_welcomed {
283            return Err(GroupError::GroupNotYetEstablished);
284        }
285
286        let Some(group_secret) = y.secrets.latest() else {
287            return Err(GroupError::NoGroupSecretAvailable);
288        };
289
290        // Encrypt application data.
291        let secret_id = group_secret.id();
292        let (nonce, ciphertext) = Self::encrypt(group_secret, plaintext, rng)?;
293
294        // Determine parameters for to-be-published application message.
295        let (y_orderer_i, message) =
296            ORD::next_application_message(y.orderer, secret_id, nonce, ciphertext)
297                .map_err(GroupError::Orderer)?;
298        y.orderer = y_orderer_i;
299
300        Ok((y, message))
301    }
302
303    /// Returns a list of all current members in this group from our perspective.
304    pub fn members(
305        y: &GroupState<ID, OP, PKI, DGM, KMG, ORD>,
306    ) -> Result<HashSet<ID>, GroupError<ID, OP, PKI, DGM, KMG, ORD>> {
307        let members = Dcgka::members(&y.dcgka)?;
308        Ok(members)
309    }
310
311    /// Applications can remove group secrets for forward secrecy based on their own logic.
312    ///
313    /// Make sure that the ordering implementation and higher-level application logic accounts for
314    /// error cases where past secrets might not exist anymore.
315    #[allow(unused)]
316    pub fn update_secrets<F>(
317        mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
318        update_fn: F,
319    ) -> GroupState<ID, OP, PKI, DGM, KMG, ORD>
320    where
321        F: FnOnce(SecretBundleState) -> SecretBundleState,
322    {
323        y.secrets = update_fn(y.secrets);
324        y
325    }
326
327    /// Processes our own locally created control messages.
328    fn process_local(
329        mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
330        output: OperationOutput<ID, OP, DGM>,
331        group_secret: Option<GroupSecret>,
332    ) -> GroupResult<ORD::Message, ID, OP, PKI, DGM, KMG, ORD> {
333        // Determine parameters for to-be-published control message.
334        let (y_orderer_i, message) =
335            ORD::next_control_message(y.orderer, &output.control_message, &output.direct_messages)
336                .map_err(GroupError::Orderer)?;
337        y.orderer = y_orderer_i;
338
339        // Process control message locally to update our state.
340        let (y_dcgka_i, _) = Dcgka::process(
341            y.dcgka,
342            ProcessInput {
343                seq: message.id(),
344                sender: message.sender(),
345                control_message: output.control_message,
346                direct_message: None,
347            },
348        )?;
349        y.dcgka = y_dcgka_i;
350
351        // Add new generated secret to bundle when given.
352        if let Some(group_secret) = group_secret {
353            y.secrets = SecretBundle::insert(y.secrets, group_secret);
354        }
355
356        Ok((y, message))
357    }
358
359    /// Processes remote messages which have been marked as "ready" by the orderer.
360    fn process_ready(
361        y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
362        message: &ORD::Message,
363    ) -> GroupResult<Option<GroupOutput<ID, OP, DGM, ORD>>, ID, OP, PKI, DGM, KMG, ORD> {
364        match message.content() {
365            GroupMessageContent::Control(control_message) => {
366                let direct_message = message
367                    .direct_messages()
368                    .into_iter()
369                    .find(|dm| dm.recipient == y.my_id);
370
371                let (mut y_i, output) = Self::process_remote(
372                    y,
373                    message.id(),
374                    message.sender(),
375                    control_message,
376                    direct_message,
377                )?;
378
379                // Check if processing this message added us to the group.
380                let we_are_members = Self::members(&y_i)?.contains(&y_i.my_id);
381                if !y_i.is_welcomed && we_are_members {
382                    y_i.is_welcomed = true;
383                }
384
385                // Check if processing this message removed us from the group.
386                let is_removed = y_i.is_welcomed && !we_are_members;
387                if is_removed {
388                    Ok((y_i, Some(GroupOutput::Removed)))
389                } else {
390                    Ok((y_i, output.map(|msg| GroupOutput::Control(msg))))
391                }
392            }
393            GroupMessageContent::Application {
394                group_secret_id,
395                ciphertext,
396                nonce,
397            } => {
398                let (y_i, plaintext) = Self::decrypt(y, nonce, group_secret_id, ciphertext)?;
399                Ok((y_i, Some(GroupOutput::Application { plaintext })))
400            }
401        }
402    }
403
404    /// Internal method to process remote control message.
405    fn process_remote(
406        mut y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
407        seq: OP,
408        sender: ID,
409        control_message: ControlMessage<ID>,
410        direct_message: Option<DirectMessage<ID, OP, DGM>>,
411    ) -> GroupResult<Option<ORD::Message>, ID, OP, PKI, DGM, KMG, ORD> {
412        let (y_dcgka_i, output) = Dcgka::process(
413            y.dcgka,
414            ProcessInput {
415                seq,
416                sender,
417                control_message,
418                direct_message,
419            },
420        )?;
421        y.dcgka = y_dcgka_i;
422
423        // Add newly learned group secrets to our bundle.
424        y.secrets = match output {
425            GroupSecretOutput::Secret(group_secret) => {
426                SecretBundle::insert(y.secrets, group_secret)
427            }
428            GroupSecretOutput::Bundle(secret_bundle_state) => {
429                SecretBundle::extend(y.secrets, secret_bundle_state)
430            }
431            GroupSecretOutput::None => y.secrets,
432        };
433
434        // Processing remote control messages never results in new messages.
435        Ok((y, None))
436    }
437
438    /// Encrypt message by using the latest known group secret and a random nonce.
439    fn encrypt(
440        group_secret: &GroupSecret,
441        plaintext: &[u8],
442        rng: &Rng,
443    ) -> Result<(XAeadNonce, Vec<u8>), GroupError<ID, OP, PKI, DGM, KMG, ORD>> {
444        let nonce: XAeadNonce = rng.random_array()?;
445        let ciphertext = encrypt_data(plaintext, group_secret, nonce)?;
446        Ok((nonce, ciphertext))
447    }
448
449    /// Decrypt message by using a group secret.
450    fn decrypt(
451        y: GroupState<ID, OP, PKI, DGM, KMG, ORD>,
452        nonce: XAeadNonce,
453        group_secret_id: GroupSecretId,
454        ciphertext: Vec<u8>,
455    ) -> GroupResult<Vec<u8>, ID, OP, PKI, DGM, KMG, ORD> {
456        let Some(group_secret) = y.secrets.get(&group_secret_id) else {
457            return Err(GroupError::UnknownGroupSecret(hex::encode(group_secret_id)));
458        };
459        let plaintext = decrypt_data(&ciphertext, group_secret, nonce)?;
460        Ok((y, plaintext))
461    }
462}
463
464pub type GroupResult<T, ID, OP, PKI, DGM, KMG, ORD> =
465    Result<(GroupState<ID, OP, PKI, DGM, KMG, ORD>, T), GroupError<ID, OP, PKI, DGM, KMG, ORD>>;
466
467/// Result from processing a remote message or calling a local group operation.
468#[derive(Clone, Debug, PartialEq, Eq)]
469pub enum GroupOutput<ID, OP, DGM, ORD>
470where
471    DGM: GroupMembership<ID, OP>,
472    ORD: Ordering<ID, OP, DGM>,
473{
474    /// Control message for group encryption which should be broadcast to all members of the group.
475    Control(ORD::Message),
476
477    /// Decrypted payload of message.
478    Application { plaintext: Vec<u8> },
479
480    /// Signal that we've been removed from the group.
481    Removed,
482}
483
484#[derive(Debug, Error)]
485pub enum GroupError<ID, OP, PKI, DGM, KMG, ORD>
486where
487    PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, LongTermKeyBundle>,
488    DGM: GroupMembership<ID, OP>,
489    KMG: PreKeyManager,
490    ORD: Ordering<ID, OP, DGM>,
491{
492    #[error(transparent)]
493    Rng(#[from] RngError),
494
495    #[error(transparent)]
496    Dcgka(#[from] DcgkaError<ID, OP, PKI, DGM, KMG>),
497
498    #[error(transparent)]
499    Orderer(ORD::Error),
500
501    #[error(transparent)]
502    XAead(#[from] XAeadError),
503
504    #[error(transparent)]
505    GroupSecret(#[from] GroupSecretError),
506
507    #[error("creating or joining a group is not possible, state is already established")]
508    GroupAlreadyEstablished,
509
510    #[error("state is not ready yet, group needs to be created or joined first")]
511    GroupNotYetEstablished,
512
513    #[error("can not add ourselves to the group")]
514    NotAddOurselves,
515
516    #[error("we do not have created or learned about any group secrets yet")]
517    NoGroupSecretAvailable,
518
519    #[error("tried to decrypt message with an unknown group secret: {0}")]
520    UnknownGroupSecret(String),
521}
522
523#[cfg(test)]
524mod tests {
525    use crate::crypto::Rng;
526    use crate::data_scheme::group::GroupOutput;
527    use crate::data_scheme::test_utils::network::init_group_state;
528    use crate::traits::{GroupMembership, Ordering};
529
530    use super::{EncryptionGroup, GroupError};
531
532    pub fn assert_payload<ID, OP, DGM, ORD>(
533        messages: &[GroupOutput<ID, OP, DGM, ORD>],
534        expected_payload: &[u8],
535    ) where
536        DGM: GroupMembership<ID, OP>,
537        ORD: Ordering<ID, OP, DGM>,
538    {
539        let message = messages.first().expect("expected at least one message");
540        if let GroupOutput::Application { plaintext } = message {
541            assert_eq!(
542                plaintext, expected_payload,
543                "expected payload does not match"
544            );
545        } else {
546            panic!("expected application message");
547        }
548    }
549
550    #[test]
551    fn post_compromise_security() {
552        let rng = Rng::from_seed([1; 32]);
553
554        let alice = 0;
555        let bob = 1;
556        let charlie = 2;
557
558        let [y_alice, y_bob, y_charlie] = init_group_state([alice, bob, charlie], &rng);
559
560        // Alice creates a group with Bob and Charlie.
561        let (y_alice, alice_message_0) =
562            EncryptionGroup::create(y_alice, vec![alice, bob, charlie], &rng).unwrap();
563        let (y_bob, _) = EncryptionGroup::receive(y_bob, &alice_message_0).unwrap();
564        let (y_charlie, _) = EncryptionGroup::receive(y_charlie, &alice_message_0).unwrap();
565
566        // Alice encrypts data for Bob and Charlie.
567        let (y_alice, alice_message_1) = EncryptionGroup::send(y_alice, b"Da Da Da", &rng).unwrap();
568
569        // Both Bob and Charlie can decrypt the payload.
570        let (y_bob, bob_output) = EncryptionGroup::receive(y_bob, &alice_message_1).unwrap();
571        assert_payload(&bob_output, b"Da Da Da");
572        let (y_charlie, charlie_output) =
573            EncryptionGroup::receive(y_charlie, &alice_message_1).unwrap();
574        assert_payload(&charlie_output, b"Da Da Da");
575
576        // Bob removes Charlie.
577        let (y_bob, bob_message_0) = EncryptionGroup::remove(y_bob, charlie, &rng).unwrap();
578        let (y_alice, alice_output) = EncryptionGroup::receive(y_alice, &bob_message_0).unwrap();
579        assert!(alice_output.is_empty());
580
581        // Alice and Bob should have both the same "latest" secret.
582        assert_eq!(
583            y_alice.secrets.latest().unwrap().id(),
584            y_bob.secrets.latest().unwrap().id()
585        );
586
587        // Charlie receives the signal that they got removed by this message.
588        let (y_charlie, charlie_output) =
589            EncryptionGroup::receive(y_charlie, &bob_message_0).unwrap();
590        let GroupOutput::Removed = charlie_output.first().unwrap() else {
591            panic!("expected removed output");
592        };
593
594        // Alice encrypts data for Bob.
595        let (_y_alice, alice_message_2) =
596            EncryptionGroup::send(y_alice, b"Ich lieb dich nicht / Du liebst mich nicht", &rng)
597                .unwrap();
598
599        // Bob can still decrypt this message from Alice.
600        let (_y_bob, bob_output) = EncryptionGroup::receive(y_bob, &alice_message_2).unwrap();
601        assert_payload(&bob_output, b"Ich lieb dich nicht / Du liebst mich nicht");
602
603        // Charlie can not decrypt the latest message anymore.
604        assert!(matches!(
605            EncryptionGroup::receive(y_charlie, &alice_message_2),
606            Err(GroupError::UnknownGroupSecret(_))
607        ));
608    }
609}