p2panda_encryption/message_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 "message
4//! encryption" scheme with strong forward secrecy and post-compromise security.
5//!
6//! ## Protocol
7//!
8//! DCGKA generates a sequence of update secrets for each group member, which are used as input to
9//! a ratchet to encrypt/decrypt application messages sent by that member. Only group members learn
10//! these update secrets, and fresh secrets are generated every time a user is added or removed, or
11//! a PCS update is requested. The DCGKA protocol ensures that all users observe the same sequence
12//! of update secrets for each group member, regardless of the order in which concurrent messages
13//! are received.
14//!
15//! ```text
16//! ┌────────────────────────────────────────────────┐
17//! │ "Outer" Ratchet │
18//! Alice ├────────────────────────────────────────────────┤
19//! │ │ │
20//! │ │ Previous Chain Secret │
21//! │ │ for Bob │
22//! Delivered │ │
23//! via 2SM │ │ │
24//! │ │ │ │
25//! │ │ │ │ ┌─────┐
26//! ▼ │ │ ◄───────────┼───│"Ack"│
27//! ┌────┐ │ │ │ └─────┘
28//! │Seed├───────│──► HKDF │ │
29//! └────┘ │ │ Bob │ │
30//! │ │ ┌─────────────┐ │ │
31//! │ ├───► │Member Secret├───────┼─► HKDF ─────┼──► Update Secret
32//! │ │ └─────────────┘ │ │ │
33//! │ │ ▼ │ │
34//! │ │ Charlie HKDF │ │
35//! │ │ ┌─────────────┐ │ │ │
36//! │ ├───► │Member Secret├─... │ │ ▼
37//! │ │ └─────────────┘ │ │ ┌───────────────┐
38//! │ │ │ │ │Message Ratchet│
39//! │ │ ... │ │ └───────────────┘
40//! │ │ ┌─────────────┐ │ │
41//! │ └───► │Member Secret├─... │ │
42//! │ └─────────────┘ │ │
43//! │ │ │
44//! │ ▼ │
45//! │ New Chain Secret │
46//! │ │ │
47//! │ │ │
48//! │ ▼ ... │
49//! │ │
50//! └────────────────────────────────────────────────┘
51//! ```
52//!
53//! To initiate a PCS update, a user generates a fresh random value called a seed secret, and sends
54//! it to each other group member via a two-party secure channel, like in Sender Keys. On receiving
55//! a seed secret, a group member deterministically derives from it an update secret for the
56//! sender's ratchet, and also an update secret for its own ratchet. Moreover, the recipient
57//! broadcasts an unencrypted acknowledgment to the group indicating that it has applied the
58//! update. Every recipient of the acknowledgment then updates not only the ratchet for the sender
59//! of the original update, but also the ratchet for the sender of the acknowledgment. Thus, after
60//! one seed secret has been disseminated via n - 1 two-party messages, and confirmed via n - 1
61//! broadcast acknowledgments, each group member has derived an update secret from it and updated
62//! their ratchet.
63//!
64//! ## Credits
65//!
66//! The implementation follows the DCGKA protocol specified in the paper: "Key Agreement for
67//! Decentralized Secure Group Messaging with Strong Security Guarantees" by Matthew Weidner,
68//! Martin Kleppmann, Daniel Hugenroth, Alastair R. Beresford (2020).
69//!
70//! <https://eprint.iacr.org/2020/1281.pdf>
71//!
72//! Some adjustments have been made to the version in the paper:
73//!
74//! * Renamed `process` to `process_remote`.
75//! * Added `rng` as an argument to most methods.
76//! * `seq` is taken care of _outside_ of this implementation. Methods return control messages
77//! which need to be manually assigned a "seq", that is a vector clock, hash, seq_num or similar.
78//! * After calling a group operation "create", "add", "remove" or "update" the user needs to process
79//! the output themselves by calling `process_local`. This allows a user of the API to correctly
80//! craft a `seq` for their control messages (see point above).
81//! * Instead of sending the history of control messages in "welcome" messages we send the
82//! "processed" and potentially garbage-collected CRDT state of DGM. This also allows
83//! implementations where control messages are encrypted as well.
84//! * We're not recording the "add" control message to the history before sending a "welcome"
85//! message after adding a member, the receiver of the "welcome" message needs to add themselves.
86use std::collections::{HashMap, HashSet};
87use std::fmt::Display;
88use std::marker::PhantomData;
89
90use serde::{Deserialize, Serialize};
91use thiserror::Error;
92
93use crate::crypto::hkdf::{HkdfError, hkdf};
94use crate::crypto::{Rng, RngError, Secret};
95use crate::key_bundle::OneTimeKeyBundle;
96use crate::traits::{
97 AckedGroupMembership, IdentityHandle, IdentityManager, IdentityRegistry, OperationId,
98 PreKeyManager, PreKeyRegistry,
99};
100use crate::two_party::{TwoParty, TwoPartyError, TwoPartyMessage, TwoPartyState};
101
102/// 256-bit secret "outer" chain- and update key.
103const RATCHET_KEY_SIZE: usize = 32;
104
105/// A decentralized continuous group key agreement protocol (DCGKA) for p2panda's "message
106/// encryption" scheme with strong forward secrecy and post-compromise security.
107pub struct Dcgka<ID, OP, PKI, DGM, KMG> {
108 _marker: PhantomData<(ID, OP, PKI, DGM, KMG)>,
109}
110
111/// Serializable state of DCGKA (for persistence).
112#[derive(Debug, Serialize, Deserialize)]
113#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
114pub struct DcgkaState<ID, OP, PKI, DGM, KMG>
115where
116 ID: IdentityHandle,
117 OP: OperationId,
118 PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, OneTimeKeyBundle>,
119 DGM: AckedGroupMembership<ID, OP>,
120 KMG: IdentityManager<KMG::State> + PreKeyManager,
121{
122 /// Public Key Infrastructure (PKI). From here we retrieve the identity keys and one-time
123 /// prekey bundles for each member to do 2SM.
124 pub(crate) pki: PKI::State,
125
126 /// Our own key manager state holding the secret parts for our own identity keys and published
127 /// one-time prekey bundles so we can do 2SM.
128 pub(crate) my_keys: KMG::State,
129
130 /// Our id which is used as a unique handle inside this group.
131 pub(crate) my_id: ID,
132
133 /// Randomly generated seed we keep temporarily around when creating or updating a group or
134 /// removing a member.
135 pub(crate) next_seed: Option<NextSeed>,
136
137 /// Handlers for each member to manage the "Two-Party Secure Messaging" (2SM) key-agreement
138 /// protocol as specified in the paper.
139 pub(crate) two_party: HashMap<ID, TwoPartyState<OneTimeKeyBundle>>, // "2sm" in paper
140
141 /// Member secrets are "temporary" secrets we derive after receiving a new seed or adding
142 /// someone. We keep them around until we've received an acknowledgment of that member.
143 ///
144 /// We only store the member secrets, and not the seed secret, so that if the user's private
145 /// state is compromised, the adversary obtains only those member secrets that have not yet
146 /// been used.
147 ///
148 /// The first parameter in the key tuple is the "sender" or "original creator" of the update
149 /// secret. They generated the secret during the given "sequence" (second parameter) for a
150 /// "member" (third parameter).
151 pub(crate) member_secrets: HashMap<(ID, OP, ID), ChainSecret>, // key: "(sender, seq, ID)" in paper
152
153 /// Chain secrets for the "outer" key-agreement ratchet.
154 ///
155 /// Secrets for the "inner" message ratchet are returned to the user as part of the
156 /// "sender_update_secret" and "me_update_secret" fields when invoking a group membership
157 /// operation or processing a control message.
158 pub(crate) ratchet: HashMap<ID, ChainSecret>,
159
160 /// Decentralised group membership (DGM) state.
161 pub(crate) dgm: DGM::State,
162}
163
164impl<ID, OP, PKI, DGM, KMG> Dcgka<ID, OP, PKI, DGM, KMG>
165where
166 ID: IdentityHandle,
167 OP: OperationId,
168 PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, OneTimeKeyBundle>,
169 DGM: AckedGroupMembership<ID, OP>,
170 KMG: IdentityManager<KMG::State> + PreKeyManager,
171{
172 /// Returns new DCGKA state with our own identity and key managers.
173 ///
174 /// Use this when creating a new group or before accepting an invitation to an existing one.
175 pub fn init(
176 my_id: ID,
177 my_keys: KMG::State,
178 pki: PKI::State,
179 dgm: DGM::State,
180 ) -> DcgkaState<ID, OP, PKI, DGM, KMG> {
181 DcgkaState {
182 pki,
183 my_id,
184 my_keys,
185 next_seed: None,
186 two_party: HashMap::new(),
187 member_secrets: HashMap::new(),
188 ratchet: HashMap::new(),
189 dgm,
190 }
191 }
192
193 /// Handler for when a "remote" control message is received from the network.
194 ///
195 /// It takes the user ID of the message sender, a control message, and a direct message (or
196 /// none if there is no associated direct message).
197 ///
198 /// Control messages are expected to be authenticated and causally ordered.
199 pub fn process_remote(
200 y: DcgkaState<ID, OP, PKI, DGM, KMG>,
201 input: ProcessInput<ID, OP, DGM>,
202 rng: &Rng,
203 ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
204 let ProcessInput {
205 sender,
206 seq,
207 direct_message,
208 control_message,
209 } = input;
210 assert_ne!(sender, y.my_id, "do not process own control messages");
211 let (y_i, output) = match control_message {
212 ControlMessage::Create { initial_members } => {
213 Self::process_create(y, sender, seq, initial_members, direct_message, rng)?
214 }
215 ControlMessage::Ack {
216 ack_sender,
217 ack_seq,
218 } => Self::process_ack(y, sender, (&ack_sender, ack_seq), direct_message)?,
219 ControlMessage::Update => Self::process_update(y, sender, seq, direct_message, rng)?,
220 ControlMessage::Remove { removed } => {
221 Self::process_remove(y, sender, seq, &removed, direct_message, rng)?
222 }
223 ControlMessage::Add { added } => {
224 Self::process_add(y, sender, seq, added, direct_message, rng)?
225 }
226 ControlMessage::AddAck {
227 ack_sender,
228 ack_seq,
229 } => Self::process_add_ack(y, sender, (&ack_sender, ack_seq), direct_message)?,
230 };
231 Ok((y_i, output))
232 }
233
234 /// Handler which is _always_ called _after_ every local group membership operation
235 /// ("create", "update", "remove" or "add") which was applied by us.
236 ///
237 /// Invoking a membership operation always returns a "control message" which needs to be
238 /// processed by the application _before_ we can process it locally (and thus finally updating
239 /// our local state). This is because some applications have more complex requirements around
240 /// wrapping the control message around their own message type which will be published on the
241 /// network (for example an append-only log entry with signature, vector clocks, etc.) and we
242 /// can't "guess" the resulting operation id.
243 ///
244 /// Calling this method will _never_ yield another control- or direct message.
245 pub fn process_local(
246 y: DcgkaState<ID, OP, PKI, DGM, KMG>,
247 seq: OP,
248 input: OperationOutput<ID, OP, DGM>,
249 rng: &Rng,
250 ) -> DcgkaOperationResult<ID, OP, PKI, DGM, KMG> {
251 let my_id = y.my_id;
252 let (y_i, output) = match input.control_message {
253 ControlMessage::Create {
254 ref initial_members,
255 } => Self::process_create(y, my_id, seq, initial_members.clone(), None, rng)?,
256 ControlMessage::Update => Self::process_update(y, my_id, seq, None, rng)?,
257 ControlMessage::Remove { removed } => {
258 Self::process_remove(y, my_id, seq, &removed, None, rng)?
259 }
260 ControlMessage::Add { added } => Self::process_add(y, my_id, seq, added, None, rng)?,
261 _ => panic!(
262 "only call process_local after local create, update, remove or add operations"
263 ),
264 };
265
266 // Processing our local group operations should never yield control or direct messages.
267 assert!(output.control_message.is_none());
268 assert!(output.direct_messages.is_empty());
269
270 Ok((
271 y_i,
272 OperationOutput {
273 control_message: input.control_message,
274 direct_messages: input.direct_messages,
275 me_update_secret: Some(output.sender_update_secret.unwrap()),
276 },
277 ))
278 }
279
280 /// Takes a set of users IDs (including us) and creates a new group with those members.
281 ///
282 /// Note that every member ID needs to be unique for this group.
283 ///
284 /// A group is created in three steps: 1. one user calls "create" and broadcasts a control
285 /// message of type "create" (plus direct messages) to the initial members; 2. each member
286 /// processes that message and broadcasts an "ack" control message; 3. each member processes
287 /// the ack from each other member.
288 pub fn create(
289 y: DcgkaState<ID, OP, PKI, DGM, KMG>,
290 initial_members: Vec<ID>,
291 rng: &Rng,
292 ) -> DcgkaOperationResult<ID, OP, PKI, DGM, KMG> {
293 // De-duplicate members.
294 let mut initial_members: Vec<ID> =
295 initial_members.into_iter().fold(Vec::new(), |mut acc, id| {
296 if !acc.contains(&id) {
297 acc.push(id);
298 }
299 acc
300 });
301
302 // Add ourselves if the user hasn't done it yet.
303 if !initial_members.contains(&y.my_id) {
304 initial_members.push(y.my_id);
305 }
306
307 // The "create" function constructs the "create" control message.
308 let control_message = ControlMessage::Create {
309 initial_members: initial_members.clone(),
310 };
311
312 // Generate the set of direct messages to send.
313 let (y_ii, direct_messages) = Self::generate_seed(y, &initial_members, rng)?;
314
315 // It then calls process_create to process the control message for this user (as if it had
316 // received the message) before returning.
317 //
318 // process_create returns a tuple after updating the state including an update secret I; we
319 // use these and ignore the rest.
320 Ok((
321 y_ii,
322 OperationOutput {
323 control_message,
324 direct_messages,
325 me_update_secret: None,
326 },
327 ))
328 }
329
330 /// Called by group members when they receive the "create" message.
331 fn process_create(
332 mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
333 sender: ID,
334 seq: OP,
335 initial_members: Vec<ID>,
336 direct_message: Option<DirectMessage<ID, OP, DGM>>,
337 rng: &Rng,
338 ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
339 y.dgm =
340 DGM::create(y.my_id, &initial_members).map_err(|err| DcgkaError::DgmOperation(err))?;
341 Self::process_seed(y, &sender, seq, direct_message, rng)
342 }
343
344 /// Called by group members when they receive the "ack" message.
345 ///
346 /// In this function, `ackID` and `ackOP` are the user id and id of the acknowledged message.
347 fn process_ack(
348 mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
349 sender: ID,
350 ack: (&ID, OP),
351 direct_message: Option<DirectMessage<ID, OP, DGM>>,
352 ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
353 // If the acknowledged message was a group membership operation, we record the
354 // acknowledgment. We do this because the member_view function needs to know which
355 // operations have been acknowledged by which user.
356 //
357 // Acking the message will fail if it's an ack of the user's own removal. Thus we will
358 // refuse to process messages from a user that depend on their own removal.
359 if DGM::is_add(&y.dgm, ack.1) && DGM::is_remove(&y.dgm, ack.1) && sender != y.my_id {
360 // This condition will fail for acks of the creation and of updates.
361 y.dgm = DGM::ack(y.dgm, sender, ack.1).map_err(|err| DcgkaError::DgmOperation(err))?;
362 }
363
364 // Read from γ.memberSecret the appropriate member secret that was previously derived from
365 // the seed secret in the message being acknowledged. The member secret is then deleted for
366 // forward secrecy.
367 let member_secret = y.member_secrets.remove(&(*ack.0, ack.1, sender)); // FS
368
369 let (y_i, sender_member_secret) = match (member_secret, direct_message) {
370 (None, None) => return Ok((y, ProcessOutput::default())),
371 (Some(member_secret), _) => (y, member_secret),
372 (
373 None,
374 Some(DirectMessage {
375 recipient,
376 content: DirectMessageContent::Forward { ciphertext },
377 ..
378 }),
379 ) => {
380 if recipient != y.my_id {
381 // Direct message was not meant for us.
382 return Ok((y, ProcessOutput::default()));
383 }
384
385 // The recipient of such a message handles concurrent adds here, where the
386 // forwarded member secret is decrypted and then used to update the ratchet for the
387 // "ack" sender. Note that this forwarding behavior does not violate forward
388 // secrecy: an application message can still only be decrypted by those users who
389 // were group members at the time of sending.
390 let (y_i, plaintext) = Self::decrypt_from(y, &sender, ciphertext)?;
391 (y_i, ChainSecret::try_from_bytes(&plaintext)?)
392 }
393 (None, Some(direct_message)) => {
394 return Err(DcgkaError::UnexpectedDirectMessageType(
395 DirectMessageType::Forward,
396 direct_message.message_type(),
397 ));
398 }
399 };
400
401 // We update the ratchet for the sender of the "ack" and return the resulting update
402 // secret.
403 let (y_ii, sender_update_secret) =
404 Self::update_ratchet(y_i, &sender, sender_member_secret)?;
405
406 Ok((
407 y_ii,
408 ProcessOutput {
409 control_message: None,
410 direct_messages: Vec::new(),
411 sender_update_secret: Some(sender_update_secret),
412 me_update_secret: None,
413 },
414 ))
415 }
416
417 /// Generate a new seed for the group. This "refreshes" the group's entropy and should be
418 /// called frequently if no other group operations took place for a while.
419 pub fn update(
420 y: DcgkaState<ID, OP, PKI, DGM, KMG>,
421 rng: &Rng,
422 ) -> DcgkaOperationResult<ID, OP, PKI, DGM, KMG> {
423 let control_message = ControlMessage::Update;
424
425 let recipient_ids: Vec<ID> = Self::member_view(&y, &y.my_id)?
426 .into_iter()
427 .filter(|member| member != &y.my_id)
428 .collect();
429
430 let (y_i, direct_messages) = Self::generate_seed(y, &recipient_ids, rng)?;
431
432 Ok((
433 y_i,
434 OperationOutput {
435 control_message,
436 direct_messages,
437 me_update_secret: None,
438 },
439 ))
440 }
441
442 /// Called by group members when they receive the "update" control message.
443 fn process_update(
444 y: DcgkaState<ID, OP, PKI, DGM, KMG>,
445 sender: ID,
446 seq: OP,
447 direct_message: Option<DirectMessage<ID, OP, DGM>>,
448 rng: &Rng,
449 ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
450 Self::process_seed(y, &sender, seq, direct_message, rng)
451 }
452
453 /// Remove a member from the group.
454 ///
455 /// This generates a new seed for the remaining members for post-compromise security (PCS).
456 pub fn remove(
457 y: DcgkaState<ID, OP, PKI, DGM, KMG>,
458 removed: ID,
459 rng: &Rng,
460 ) -> DcgkaOperationResult<ID, OP, PKI, DGM, KMG> {
461 let control_message = ControlMessage::Remove { removed };
462
463 let recipient_ids: Vec<ID> = Self::member_view(&y, &y.my_id)?
464 .into_iter()
465 .filter(|member| member != &y.my_id && member != &removed)
466 .collect();
467
468 let (y_i, direct_messages) = Self::generate_seed(y, &recipient_ids, rng)?;
469
470 Ok((
471 y_i,
472 OperationOutput {
473 control_message,
474 direct_messages,
475 me_update_secret: None,
476 },
477 ))
478 }
479
480 /// Called by group members when they receive the "remove" control message.
481 fn process_remove(
482 mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
483 sender: ID,
484 seq: OP,
485 removed: &ID,
486 direct_message: Option<DirectMessage<ID, OP, DGM>>,
487 rng: &Rng,
488 ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
489 y.dgm = DGM::remove(y.dgm, sender, removed, seq)
490 .map_err(|err| DcgkaError::DgmOperation(err))?;
491 Self::process_seed(y, &sender, seq, direct_message, rng)
492 }
493
494 /// Adds a new group member.
495 ///
496 /// The added group member will receive a direct "welcome" message, every member will process
497 /// an "add" control message.
498 pub fn add(
499 y: DcgkaState<ID, OP, PKI, DGM, KMG>,
500 added: ID,
501 rng: &Rng,
502 ) -> DcgkaOperationResult<ID, OP, PKI, DGM, KMG> {
503 // Construct a control message of type "add" to broadcast to the group
504 let control_message = ControlMessage::Add { added };
505
506 // Construct a welcome message that is sent to the new member as a direct message.
507 //
508 // The welcome message contains the current KDF ratchet state of the sender, encrypted
509 // using 2SM, and the history of group membership operations to date (necessary so that the
510 // new member can evaluate the DGM function).
511 let (y_i, ciphertext) = {
512 let chain_secret_bytes = y
513 .ratchet
514 .get(&y.my_id)
515 .ok_or(DcgkaError::MissingRatchetSecret)?
516 .as_bytes()
517 .to_vec();
518 Self::encrypt_to(y, &added, &chain_secret_bytes, rng)?
519 };
520 let direct_message = DirectMessage {
521 recipient: added,
522 content: DirectMessageContent::Welcome {
523 ciphertext,
524 history: {
525 // Send current DGM state to added user. The benefit of doing it like that is
526 // that we can encrypt group operations as well, otherwise we would need to ask
527 // the added user to process all previous (unencrypted) group operations
528 // before.
529 y_i.dgm.clone()
530 },
531 },
532 };
533
534 Ok((
535 y_i,
536 OperationOutput {
537 control_message,
538 direct_messages: vec![direct_message],
539 me_update_secret: None,
540 },
541 ))
542 }
543
544 /// Called by both the sender and each recipient of an "add" control message, including the new
545 /// group member.
546 ///
547 /// ## Concurrency
548 ///
549 /// Another scenario that needs to be handled is when two users are concurrently added to the
550 /// group. For example, in a group consisting initially of {A, B}, say A adds C to the group,
551 /// while concurrently B adds D. User C first processes its own addition and welcome message,
552 /// and then processes B's addition of D. However, since C was not a group member at the time B
553 /// sent its "add" message, C does not yet have B's ratchet state, so C cannot derive an update
554 /// secret for B's "add" message.
555 ///
556 /// When B finds out about the fact that A has added C, B sends C its ratchet state as usual,
557 /// so C can initialize its copy of B's ratchet as before. Similarly, when D finds out about
558 /// the fact that A has added C, D sends its ratchet state to C along with the "add-ack"
559 /// message. The existing logic therefore handles the concurrent additions: after all acks have
560 /// been delivered, C and D have both initialized their copies of all four ratchets, and so
561 /// they are able to decrypt application messages that any group member sent after processing
562 /// their addition.
563 fn process_add(
564 mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
565 sender: ID,
566 seq: OP,
567 added: ID,
568 direct_message: Option<DirectMessage<ID, OP, DGM>>,
569 rng: &Rng,
570 ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
571 // Local user is the new group member being added. Call "process_welcome" instead and
572 // return early.
573 if added == y.my_id {
574 let Some(DirectMessage {
575 recipient,
576 content:
577 DirectMessageContent::Welcome {
578 ciphertext,
579 history,
580 },
581 ..
582 }) = direct_message
583 else {
584 return match direct_message {
585 Some(direct_message) => Err(DcgkaError::UnexpectedDirectMessageType(
586 DirectMessageType::Welcome,
587 direct_message.message_type(),
588 )),
589 None => Err(DcgkaError::MissingDirectMessage(DirectMessageType::Welcome)),
590 };
591 };
592
593 if recipient != y.my_id {
594 return Err(DcgkaError::NotOurDirectMessage(y.my_id, recipient));
595 }
596
597 return Self::process_welcome(y, sender, seq, history, ciphertext);
598 }
599
600 // Otherwise extend γ.history with the add operation.
601 y.dgm = DGM::add(y.dgm, sender, added, seq).map_err(|err| DcgkaError::DgmOperation(err))?;
602
603 // Determine whether the local user was already a group member at the time the "add"
604 // message was sent. This is true in the common case, but may be false if multiple users
605 // were added concurrently.
606 let is_concurrent = !Self::member_view(&y, &sender)?
607 .iter()
608 .any(|member| member == &y.my_id);
609
610 let (y_ii, sender_update_secret) = if is_concurrent {
611 (y, None)
612 } else {
613 // We twice update the ratchet for the sender of the "add" message. In both calls to
614 // update_ratchet, rather than using a random seed secret, the ratchet input is a
615 // constant string ("welcome" and "add" respectively). It is sufficient to use
616 // constants here because all existing group members are allowed to know the next
617 // update secrets following the add operation.
618
619 // 1. The value returned by the first ratchet update is stored in γ.memberSecret as the
620 // added user's first member secret;
621 let (mut y_i, sender_member_secret) =
622 Self::update_ratchet(y, &sender, ChainSecret::from_welcome())?;
623 y_i.member_secrets.insert(
624 (sender, seq, added),
625 ChainSecret::from(sender_member_secret),
626 );
627 // 2. The result of the second ratchet update becomes Isender, the update secret for
628 // the sender of the "add".
629 let (y_ii, sender_update_secret) =
630 Self::update_ratchet(y_i, &sender, ChainSecret::from_add())?;
631 (y_ii, Some(sender_update_secret))
632 };
633
634 // If the local user is the sender, we return that update secret.
635 if sender == y_ii.my_id {
636 return Ok((
637 y_ii,
638 ProcessOutput {
639 control_message: None,
640 direct_messages: Vec::new(),
641 sender_update_secret,
642 me_update_secret: None,
643 },
644 ));
645 }
646
647 // Otherwise, we need to acknowledge the "add" message, so we construct a control message
648 // of type "add-ack" to broadcast (note that add has its own acknowledgment type, whereas
649 // create, update and remove all use "ack").
650 let control = ControlMessage::AddAck {
651 ack_sender: sender,
652 ack_seq: seq,
653 };
654
655 // We then use 2SM to encrypt our current ratchet state to send as a direct message to the
656 // added user, so that they can decrypt subsequent messages we send.
657 let (y_iii, ciphertext) = {
658 let chain_secret_bytes = y_ii
659 .ratchet
660 .get(&y_ii.my_id)
661 .ok_or(DcgkaError::MissingRatchetSecret)?
662 .as_bytes()
663 .to_vec();
664 Self::encrypt_to(y_ii, &added, &chain_secret_bytes, rng)?
665 };
666 let forward = DirectMessage {
667 recipient: added,
668 content: DirectMessageContent::Forward { ciphertext },
669 };
670
671 // Finally, we call process_add_ack to compute the local user's update secret Ime, and
672 // return it with Isender.
673 let (y_iv, output) = {
674 let my_id = y_iii.my_id;
675 Self::process_add_ack(y_iii, my_id, (&sender, seq), None)?
676 };
677 let me_update_secret = output.sender_update_secret;
678
679 Ok((
680 y_iv,
681 ProcessOutput {
682 control_message: Some(control),
683 direct_messages: vec![forward],
684 sender_update_secret,
685 me_update_secret,
686 },
687 ))
688 }
689
690 /// Called by both the sender and each recipient of an "add-ack" message, including the new
691 /// group member.
692 fn process_add_ack(
693 mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
694 sender: ID,
695 ack: (&ID, OP),
696 direct_message: Option<DirectMessage<ID, OP, DGM>>,
697 ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
698 // Add the acknowledgment to γ.history, like in process_ack.
699 y.dgm = DGM::ack(y.dgm, sender, ack.1).map_err(|err| DcgkaError::DgmOperation(err))?;
700
701 // If the current user is the new group member, the "add_ack" message is accompanied by the
702 // direct message that we constructed in process_add; this direct message dmsg contains the
703 // encrypted ratchet state of the sender of the "add_ack", so we decrypt it.
704 let y_i = if let Some(direct_message) = direct_message {
705 if let DirectMessage {
706 recipient,
707 content: DirectMessageContent::Forward { ciphertext },
708 ..
709 } = direct_message
710 {
711 if recipient != y.my_id {
712 return Err(DcgkaError::NotOurDirectMessage(y.my_id, recipient));
713 }
714
715 let (mut y_i, plaintext) = Self::decrypt_from(y, &sender, ciphertext)?;
716 let chain_secret = ChainSecret::try_from_bytes(&plaintext)?;
717 y_i.ratchet.insert(sender, chain_secret);
718 y_i
719 } else {
720 return Err(DcgkaError::UnexpectedDirectMessageType(
721 DirectMessageType::Forward,
722 direct_message.message_type(),
723 ));
724 }
725 } else {
726 y
727 };
728
729 // Check if the local user was already a group member at the time the "add_ack" was sent
730 // (which may not be the case when there are concurrent additions).
731 let is_concurrent = !Self::member_view(&y_i, &sender)?
732 .iter()
733 .any(|member| member == &y_i.my_id);
734
735 if !is_concurrent {
736 // If so, we compute a new update secret I for the sender of the "add_ack" by calling
737 // update_ratchet with the constant string "add". In the case of the new member, the
738 // ratchet state was just previously initialized. This ratchet update allows all group
739 // members, including the new one, to derive each member's update secret for the add
740 // operation, but it prevents the new group member from obtaining any update secret
741 // from before they were added.
742 let (y_ii, sender_update_secret) =
743 Self::update_ratchet(y_i, &sender, ChainSecret::from_add())?;
744 return Ok((
745 y_ii,
746 ProcessOutput {
747 control_message: None,
748 direct_messages: Vec::new(),
749 sender_update_secret: Some(sender_update_secret),
750 me_update_secret: None,
751 },
752 ));
753 }
754
755 Ok((y_i, ProcessOutput::default()))
756 }
757
758 /// Second function called by a newly added group member (the first is the call to init that
759 /// sets up their state).
760 fn process_welcome(
761 mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
762 sender: ID,
763 seq: OP,
764 history: DGM::State,
765 ciphertext: TwoPartyMessage,
766 ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
767 // Adding user's copy of γ.history sent in their welcome message, which is used to
768 // initialize the added user's history.
769 //
770 // Note that we might receive multiple welcome messages when peers added us concurrently. A
771 // DGM implementation might want to account for this.
772 y.dgm = DGM::from_welcome(y.dgm, history).map_err(|err| DcgkaError::DgmOperation(err))?;
773
774 // Add ourselves.
775 y.dgm =
776 DGM::add(y.dgm, sender, y.my_id, seq).map_err(|err| DcgkaError::DgmOperation(err))?;
777
778 // Ciphertext of the adding user's ratchet state, which we decrypt.
779 let y_i = {
780 let (mut y_i, plaintext) = Self::decrypt_from(y, &sender, ciphertext)?;
781 let chain_secret = ChainSecret::try_from_bytes(&plaintext)?;
782 y_i.ratchet.insert(sender, chain_secret);
783 y_i
784 };
785
786 // After γ.ratchet[sender] is initialized, we can call update_ratchet twice with the
787 // constant strings "welcome" and "add": exactly the same ratchet operations as every other
788 // group member performs in process_add.
789 //
790 // As before, the result of the first update_ratchet call becomes the first member secret
791 // for the added user, and the second returns Isender, the update secret for the sender of
792 // the add operation.
793 let y_ii = {
794 let (mut y_ii, member_secret) =
795 Self::update_ratchet(y_i, &sender, ChainSecret::from_welcome())?;
796 y_ii.member_secrets
797 .insert((sender, seq, y_ii.my_id), ChainSecret::from(member_secret));
798 y_ii
799 };
800
801 let (y_iii, sender_update_secret) =
802 Self::update_ratchet(y_ii, &sender, ChainSecret::from_add())?;
803
804 // Finally, the new group member constructs an "ack" control message (not "add_ack") to
805 // broadcast and calls process_ack to compute their first update secret Ime.
806 let control = ControlMessage::Ack {
807 ack_sender: sender,
808 ack_seq: seq,
809 };
810
811 // process_ack works as described previously, reading from γ.memberSecret the member secret
812 // we just generated, and passing it to update_ratchet.
813 //
814 // The previous ratchet state for the new member is the empty string ε, as set up by init,
815 // so this step initializes the new member's ratchet. Every other group member, on
816 // receiving the new member's "ack", will initialize their copy of the new member's ratchet
817 // in the same way. By the end of process_welcome, the new group member has obtained update
818 // secrets for themselves and the user who added them. They then use those secrets to
819 // initialize the ratchets for application messages, allowing them to send messages and
820 // decrypt messages from the user who added them. The ratchets for other group members are
821 // initialized by process_add_ack.
822 let (y_iv, output) = {
823 let my_id = y_iii.my_id;
824 Self::process_ack(y_iii, my_id, (&sender, seq), None)?
825 };
826 let me_update_secret = output.sender_update_secret;
827
828 Ok((
829 y_iv,
830 ProcessOutput {
831 control_message: Some(control),
832 direct_messages: Vec::new(),
833 sender_update_secret: Some(sender_update_secret),
834 me_update_secret: Some(
835 me_update_secret.expect("sender update secret from process_ack"),
836 ),
837 },
838 ))
839 }
840
841 /// Generates a seed secret using a secure source of random bits, then calls `encrypt_to` to
842 /// encrypt it for each other group member using the 2SM protocol. It returns the updated
843 /// protocol state and the set of direct messages to send.
844 fn generate_seed(
845 mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
846 recipients: &[ID],
847 rng: &Rng,
848 ) -> GenerateSeedResult<ID, OP, PKI, DGM, KMG> {
849 let mut direct_messages: Vec<DirectMessage<ID, OP, DGM>> =
850 Vec::with_capacity(recipients.len());
851
852 // Generate next seed.
853 let next_seed_bytes = rng.random_array()?;
854 y.next_seed = Some(NextSeed::from_bytes(next_seed_bytes));
855
856 let y_i = {
857 let mut y_loop = y;
858 for recipient in recipients {
859 // Skip ourselves.
860 if recipient == &y_loop.my_id {
861 continue;
862 }
863
864 // Encrypt to every recipient.
865 let (y_next, ciphertext) =
866 Self::encrypt_to(y_loop, recipient, &next_seed_bytes, rng)?;
867 y_loop = y_next;
868
869 direct_messages.push(DirectMessage {
870 recipient: *recipient,
871 content: DirectMessageContent::TwoParty { ciphertext },
872 });
873 }
874 y_loop
875 };
876
877 Ok((y_i, direct_messages))
878 }
879
880 /// Handle the next seed for the group, either generated locally by us or received as an
881 /// encrypted message by another member.
882 ///
883 /// We use the seed to derive independent member secrets for each group member by combining
884 /// the seed secret and each user ID using HKDF.
885 fn process_seed(
886 mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
887 sender: &ID,
888 seq: OP,
889 direct_message: Option<DirectMessage<ID, OP, DGM>>,
890 rng: &Rng,
891 ) -> DcgkaProcessResult<ID, OP, PKI, DGM, KMG> {
892 // Determine the set of users who were group members at the time the control message was
893 // sent, and hence the set of recipients of the message.
894 let recipients: Vec<ID> = Self::member_view(&y, sender)?
895 .into_iter()
896 .filter(|member| member != sender)
897 .collect();
898
899 // Attempt to obtain the seed secret.
900 let (mut y_i, next_seed) = if sender == &y.my_id {
901 // 1. If the control message was sent by the local user, the last call to generate_seed
902 // placed the seed secret in γ.nextSeed, so we read that variable and then delete
903 // its contents.
904 let next_seed = y.next_seed.take().expect("seed was generated before"); // FS
905 (y, next_seed)
906 } else if recipients.iter().any(|member| member == &y.my_id) {
907 // 2. If the control message was sent by another user, and the local user is one of its
908 // recipients, we use decrypt_from to decrypt the direct message containing the seed
909 // secret.
910 let Some(DirectMessage {
911 recipient,
912 content: DirectMessageContent::TwoParty { ciphertext },
913 ..
914 }) = direct_message
915 else {
916 return match direct_message {
917 Some(direct_message) => Err(DcgkaError::UnexpectedDirectMessageType(
918 DirectMessageType::TwoParty,
919 direct_message.message_type(),
920 )),
921 None => Err(DcgkaError::MissingDirectMessage(
922 DirectMessageType::TwoParty,
923 )),
924 };
925 };
926
927 if recipient != y.my_id {
928 return Err(DcgkaError::NotOurDirectMessage(y.my_id, recipient));
929 }
930
931 let (y_i, plaintext) = Self::decrypt_from(y, sender, ciphertext)?;
932 (y_i, NextSeed::try_from_bytes(&plaintext)?)
933 } else {
934 // 3. Otherwise we return an "ack" message without deriving an update secret. Case 3
935 // may occur when a group member is added concurrently to other messages.
936 let control = ControlMessage::Ack {
937 ack_sender: *sender,
938 ack_seq: seq,
939 };
940 return Ok((
941 y,
942 ProcessOutput {
943 control_message: Some(control),
944 direct_messages: Vec::new(),
945 sender_update_secret: None,
946 me_update_secret: None,
947 },
948 ));
949 };
950
951 // Derive independent member secrets for each group member from the seed secret by
952 // combining the seed secret and each user ID using HKDF. The secret for the sender of the
953 // message is stored in senderSecret, and those for the other group members are stored in
954 // γ.memberSecret; the latter are used when we receive acknowledgments from those users.
955 for recipient in &recipients {
956 let recipient_identity_key = PKI::identity_key(&y_i.pki, recipient)
957 .map_err(|err| DcgkaError::IdentityRegistry(err))?
958 .ok_or(DcgkaError::MissingIdentityKey(*recipient))?;
959 let recipient_member_secret: [u8; RATCHET_KEY_SIZE] = hkdf(
960 b"update",
961 &{
962 let mut ikm = Vec::with_capacity(RATCHET_KEY_SIZE * 2);
963 ikm.extend_from_slice(next_seed.as_bytes());
964 ikm.extend_from_slice(recipient_identity_key.as_bytes());
965 ikm
966 },
967 None,
968 )?;
969 y_i.member_secrets.insert(
970 (*sender, seq, *recipient),
971 ChainSecret::from_bytes(recipient_member_secret),
972 );
973 }
974
975 // The sender's member secret is used immediately to update their KDF ratchet and compute
976 // their update secret Isender, using update_ratchet.
977 let (y_ii, sender_update_secret) = {
978 let sender_identity_key = PKI::identity_key(&y_i.pki, sender)
979 .map_err(|err| DcgkaError::IdentityRegistry(err))?
980 .ok_or(DcgkaError::MissingIdentityKey(*sender))?;
981 let sender_member_secret: [u8; RATCHET_KEY_SIZE] = hkdf(
982 b"update",
983 &{
984 let mut ikm = Vec::with_capacity(RATCHET_KEY_SIZE * 2);
985 ikm.extend_from_slice(next_seed.as_bytes());
986 ikm.extend_from_slice(sender_identity_key.as_bytes());
987 ikm
988 },
989 None,
990 )?;
991 Self::update_ratchet(y_i, sender, ChainSecret::from_bytes(sender_member_secret))?
992 };
993
994 // We only store the member secrets, and not the seed secret, so that if the user's private
995 // state is compromised, the adversary obtains only those member secrets that have not yet
996 // been used.
997 drop(next_seed); // FS for "next_seed".
998
999 // If the local user is the sender of the control message, we are now finished and return
1000 // the update secret.
1001 if sender == &y_ii.my_id {
1002 return Ok((
1003 y_ii,
1004 ProcessOutput {
1005 control_message: None,
1006 direct_messages: Vec::new(),
1007 sender_update_secret: Some(sender_update_secret),
1008 me_update_secret: None,
1009 },
1010 ));
1011 }
1012
1013 // If we received the seed secret from another user, we construct an "ack" control message
1014 // to broadcast, including the sender ID and sequence number of the message we are
1015 // acknowledging.
1016 let control = ControlMessage::Ack {
1017 ack_sender: *sender,
1018 ack_seq: seq,
1019 };
1020
1021 // Care is required when an add operation occurs concurrently with an update, remove, or
1022 // another add operation. We want all intended recipients to learn every update secret,
1023 // since otherwise some users would not be able to decrypt some messages, despite being a
1024 // group member.
1025 //
1026 // For example, consider a group with members {A, B, C}, and say A performs an update while
1027 // concurrently C adds D to the group. When A distributes a new seed secret through
1028 // 2SM-encrypted direct messages, D will not be a recipient of one of those direct
1029 // messages, since A did not know about D's addition at the time of sending. D cannot
1030 // derive any of the member secrets for this update. When B updates its KDF ratchet using
1031 // A's seed secret, it will compute an update secret that D does not know, and D will not
1032 // be able to decrypt B's subsequent application messages.
1033 //
1034 // In this example, B may receive the add and the update in either order. If B processes
1035 // A's update first, the seed secret from A is already incorporated into B's ratchet state
1036 // at the time of adding D; since B sends this ratchet state to D along with its "add-ack"
1037 // message, no further action is needed. On the other hand, if B processes the addition of
1038 // D first, then when B subsequently processes A's update, B must take the member secret it
1039 // derives from A's seed secret and forward it to D, so that D can compute B's update
1040 // secret for A's update.
1041 //
1042 // Recall that first we set recipients to be the set of group members at the time the
1043 // update/remove was sent, except for the sender. We then compute the current set of
1044 // members according to the local node. The set difference thus computes the set of users
1045 // whose additions have been processed by the local user, but who were not yet known to
1046 // the sender of the update.
1047 //
1048 // If there are any such users, we construct a direct message to each of them. One of the
1049 // member secrets we computed before is the member secret for the local user. We
1050 // 2SM-encrypt that member secret for each of the users who need it. This set forward is
1051 // sent as direct messages along with the "ack".
1052 let (y_iii, forward_messages) = {
1053 let members: Vec<ID> = Self::member_view(&y_ii, &y_ii.my_id)?
1054 .into_iter()
1055 .filter(|member| member != sender && !recipients.contains(member))
1056 .collect();
1057 let mut forward_messages = Vec::with_capacity(members.len());
1058
1059 let mut y_loop = y_ii;
1060 for member in members {
1061 let member_secret_bytes = y_loop
1062 .member_secrets
1063 .get(&(*sender, seq, y_loop.my_id))
1064 .ok_or(DcgkaError::MissingMemberSecret(*sender, seq))?
1065 .as_bytes()
1066 .to_vec();
1067 let (y_next, ciphertext) =
1068 Self::encrypt_to(y_loop, &member, &member_secret_bytes, rng)?;
1069 y_loop = y_next;
1070 forward_messages.push(DirectMessage {
1071 recipient: member,
1072 content: DirectMessageContent::Forward { ciphertext },
1073 });
1074 }
1075
1076 (y_loop, forward_messages)
1077 };
1078
1079 // Compute an update secret Ime for the local user.
1080 let (y_iv, output) = {
1081 let my_id = y_iii.my_id;
1082 Self::process_ack(y_iii, my_id, (sender, seq), None)?
1083 };
1084 let me_update_secret = output.sender_update_secret;
1085
1086 Ok((
1087 y_iv,
1088 ProcessOutput {
1089 control_message: Some(control),
1090 direct_messages: forward_messages,
1091 sender_update_secret: Some(sender_update_secret),
1092 me_update_secret,
1093 },
1094 ))
1095 }
1096
1097 /// Uses 2SM to encrypt a direct message for another group member. The first time a message is
1098 /// encrypted to a particular recipient ID, the 2SM protocol state is initialized and stored in
1099 /// γ.2sm[ID]. We then use 2SM-Send to encrypt the message, and store the updated protocol
1100 /// state in γ.
1101 fn encrypt_to(
1102 mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
1103 recipient: &ID,
1104 plaintext: &[u8],
1105 rng: &Rng,
1106 ) -> DcgkaResult<ID, OP, PKI, DGM, KMG, TwoPartyMessage> {
1107 let y_2sm = match y.two_party.remove(recipient) {
1108 Some(y_2sm) => y_2sm,
1109 None => {
1110 let (pki_i, prekey_bundle) = PKI::key_bundle(y.pki, recipient)
1111 .map_err(|err| DcgkaError::PreKeyRegistry(err))?;
1112 y.pki = pki_i;
1113 let prekey_bundle = prekey_bundle.ok_or(DcgkaError::MissingPreKeys(*recipient))?;
1114 TwoParty::<KMG, OneTimeKeyBundle>::init(prekey_bundle)
1115 }
1116 };
1117 let (y_2sm_i, ciphertext) =
1118 TwoParty::<KMG, OneTimeKeyBundle>::send(y_2sm, &y.my_keys, plaintext, rng)?;
1119 y.two_party.insert(*recipient, y_2sm_i);
1120 Ok((y, ciphertext))
1121 }
1122
1123 /// Is the reverse of encrypt_to. It similarly initializes the protocol state on first use, and
1124 /// then uses 2SM-Receive to decrypt the ciphertext, with the protocol state stored in
1125 /// γ.2sm[ID].
1126 fn decrypt_from(
1127 mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
1128 sender: &ID,
1129 ciphertext: TwoPartyMessage,
1130 ) -> DcgkaResult<ID, OP, PKI, DGM, KMG, Vec<u8>> {
1131 let y_2sm = match y.two_party.remove(sender) {
1132 Some(y_2sm) => y_2sm,
1133 None => {
1134 let (pki_i, prekey_bundle) = PKI::key_bundle(y.pki, sender)
1135 .map_err(|err| DcgkaError::PreKeyRegistry(err))?;
1136 y.pki = pki_i;
1137 let prekey_bundle = prekey_bundle.ok_or(DcgkaError::MissingPreKeys(*sender))?;
1138 TwoParty::<KMG, OneTimeKeyBundle>::init(prekey_bundle)
1139 }
1140 };
1141 let (y_2sm_i, y_my_keys_i, plaintext) =
1142 TwoParty::<KMG, OneTimeKeyBundle>::receive(y_2sm, y.my_keys, ciphertext)?;
1143 y.my_keys = y_my_keys_i;
1144 y.two_party.insert(*sender, y_2sm_i);
1145 Ok((y, plaintext))
1146 }
1147
1148 /// Generates the next update secret for group member ID. It implements the outer KDF of the
1149 /// ratchet. The ratchet state is stored in γ.ratchet[ID]; we use an HMAC-based key derivation
1150 /// function HKDF to combine the ratchet state with an input, producing an update secret and a
1151 /// new ratchet state.
1152 fn update_ratchet(
1153 mut y: DcgkaState<ID, OP, PKI, DGM, KMG>,
1154 member: &ID,
1155 member_secret: ChainSecret,
1156 ) -> DcgkaResult<ID, OP, PKI, DGM, KMG, UpdateSecret> {
1157 let identity_key = PKI::identity_key(&y.pki, member)
1158 .map_err(|err| DcgkaError::IdentityRegistry(err))?
1159 .ok_or(DcgkaError::MissingIdentityKey(*member))?;
1160 // Mix in the previous "outer" ratchet chain secret if it exists.
1161 let previous_outer_ratchet_key = y.ratchet.get(member);
1162 let update_secret: [u8; RATCHET_KEY_SIZE] = hkdf(
1163 b"update",
1164 &{
1165 let mut ikm = Vec::with_capacity(RATCHET_KEY_SIZE * 3);
1166 if let Some(previous_outer_ratchet_key) = previous_outer_ratchet_key {
1167 ikm.extend_from_slice(previous_outer_ratchet_key.as_bytes());
1168 }
1169 ikm.extend_from_slice(member_secret.as_bytes());
1170 ikm.extend_from_slice(identity_key.as_bytes());
1171 ikm
1172 },
1173 None,
1174 )?;
1175 let next_outer_ratchet_key: [u8; RATCHET_KEY_SIZE] = hkdf(
1176 b"chain",
1177 &{
1178 let mut ikm = Vec::with_capacity(RATCHET_KEY_SIZE * 3);
1179 if let Some(previous_outer_ratchet_key) = previous_outer_ratchet_key {
1180 ikm.extend_from_slice(previous_outer_ratchet_key.as_bytes());
1181 }
1182 ikm.extend_from_slice(member_secret.as_bytes());
1183 ikm.extend_from_slice(identity_key.as_bytes());
1184 ikm
1185 },
1186 None,
1187 )?;
1188 drop(member_secret); // FS for `member_secret`
1189 y.ratchet
1190 .insert(*member, ChainSecret::from_bytes(next_outer_ratchet_key));
1191 Ok((y, UpdateSecret::from_bytes(update_secret)))
1192 }
1193
1194 /// Computes the set of group members at the time of the most recent control message sent by
1195 /// user ID. It works by filtering the set of group membership operations to contain only those
1196 /// seen by ID, and then invoking the Decentralised Group Membership function DGM to compute
1197 /// the group membership.
1198 pub fn member_view(
1199 y: &DcgkaState<ID, OP, PKI, DGM, KMG>,
1200 viewer: &ID,
1201 ) -> Result<HashSet<ID>, DcgkaError<ID, OP, PKI, DGM, KMG>> {
1202 let members =
1203 DGM::members_view(&y.dgm, viewer).map_err(|err| DcgkaError::MembersView(err))?;
1204 Ok(members)
1205 }
1206}
1207
1208pub type GenerateSeedResult<ID, OP, PKI, DGM, KMG> = Result<
1209 (
1210 DcgkaState<ID, OP, PKI, DGM, KMG>,
1211 Vec<DirectMessage<ID, OP, DGM>>,
1212 ),
1213 DcgkaError<ID, OP, PKI, DGM, KMG>,
1214>;
1215
1216pub type DcgkaResult<ID, OP, PKI, DGM, KMG, T> =
1217 Result<(DcgkaState<ID, OP, PKI, DGM, KMG>, T), DcgkaError<ID, OP, PKI, DGM, KMG>>;
1218
1219pub type DcgkaProcessResult<ID, OP, PKI, DGM, KMG> =
1220 DcgkaResult<ID, OP, PKI, DGM, KMG, ProcessOutput<ID, OP, DGM>>;
1221
1222pub type DcgkaOperationResult<ID, OP, PKI, DGM, KMG> =
1223 DcgkaResult<ID, OP, PKI, DGM, KMG, OperationOutput<ID, OP, DGM>>;
1224
1225/// Message that should be broadcast to the group.
1226///
1227/// The control message must be distributed to the other group members through Authenticated Causal
1228/// Broadcast, calling the process function on the recipient when they are delivered.
1229#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1230pub enum ControlMessage<ID, OP> {
1231 Create { initial_members: Vec<ID> },
1232 Ack { ack_sender: ID, ack_seq: OP },
1233 Update,
1234 Remove { removed: ID },
1235 Add { added: ID },
1236 AddAck { ack_sender: ID, ack_seq: OP },
1237}
1238
1239impl<ID, OP> Display for ControlMessage<ID, OP> {
1240 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1241 write!(
1242 f,
1243 "{}",
1244 match self {
1245 ControlMessage::Create { .. } => "create",
1246 ControlMessage::Ack { .. } => "ack",
1247 ControlMessage::Update => "update",
1248 ControlMessage::Remove { .. } => "remove",
1249 ControlMessage::Add { .. } => "add",
1250 ControlMessage::AddAck { .. } => "add_ack",
1251 }
1252 )
1253 }
1254}
1255
1256/// Arguments required to process a group operation received from another member.
1257#[derive(Clone, Debug)]
1258pub struct ProcessInput<ID, OP, DGM>
1259where
1260 DGM: AckedGroupMembership<ID, OP>,
1261{
1262 /// Sequence number, which consecutively numbers successive control messages from the same
1263 /// sender.
1264 pub seq: OP,
1265
1266 /// Author of this message.
1267 pub sender: ID,
1268
1269 /// Message received from this author.
1270 pub control_message: ControlMessage<ID, OP>,
1271
1272 /// Optional direct message for us.
1273 ///
1274 /// Applications need to filter the direct message for the correct recipient before passing it
1275 /// as an input. There can always only be max. 1 direct message per recipient.
1276 pub direct_message: Option<DirectMessage<ID, OP, DGM>>,
1277}
1278
1279/// Calling "process" returns a 4-tuple `(control, dmsgs, Is, Ir)`, where `Is` is an update secret
1280/// for the sender of the message being processed, `Ir` is an update secret for the recipient.
1281#[derive(Debug)]
1282pub struct ProcessOutput<ID, OP, DGM>
1283where
1284 DGM: AckedGroupMembership<ID, OP>,
1285{
1286 /// Control message that should be broadcast to the group.
1287 pub control_message: Option<ControlMessage<ID, OP>>,
1288
1289 /// Direct messages to be sent to specific members.
1290 pub direct_messages: Vec<DirectMessage<ID, OP, DGM>>,
1291
1292 /// Our next update key for the message ratchet for incoming messages from the sender.
1293 pub sender_update_secret: Option<UpdateSecret>,
1294
1295 /// Our next update key for the message ratchet for outgoing messages to the group.
1296 pub me_update_secret: Option<UpdateSecret>,
1297}
1298
1299impl<ID, OP, DGM> Default for ProcessOutput<ID, OP, DGM>
1300where
1301 DGM: AckedGroupMembership<ID, OP>,
1302{
1303 fn default() -> Self {
1304 Self {
1305 control_message: None,
1306 direct_messages: Vec::new(),
1307 sender_update_secret: None,
1308 me_update_secret: None,
1309 }
1310 }
1311}
1312
1313/// Calling "create", "add", "remove" and "update" return a tuple of three variables (`control`,
1314/// `dmsgs` and `I`) after changing the state for the current user.
1315///
1316/// `control` is a control message that should be broadcast to the group, `dmsgs` is a set of `(u,
1317/// m)` pairs where `m` is a direct message that should be sent to user `u`, and `I` is a new
1318/// update secret for the current user.
1319pub struct OperationOutput<ID, OP, DGM>
1320where
1321 DGM: AckedGroupMembership<ID, OP>,
1322{
1323 /// Control message that should be broadcast to the group.
1324 pub control_message: ControlMessage<ID, OP>,
1325
1326 /// Set of messages directly to be sent to specific users.
1327 pub direct_messages: Vec<DirectMessage<ID, OP, DGM>>,
1328
1329 /// Our next update key for the message ratchet for outgoing messages to the group.
1330 pub me_update_secret: Option<UpdateSecret>,
1331}
1332
1333/// Direct message that should be sent to a single member.
1334///
1335/// The direct message must be distributed to the other group members through Authenticated Causal
1336/// Broadcast, calling the process function on the recipient when they are delivered.
1337///
1338/// If direct messages are sent along with a control message, we assume that the direct message for
1339/// the appropriate recipient is delivered in the same call to process. Our algorithm never sends a
1340/// direct message without an associated broadcast control message.
1341#[derive(Clone, Debug, Serialize, Deserialize)]
1342pub struct DirectMessage<ID, OP, DGM>
1343where
1344 DGM: AckedGroupMembership<ID, OP>,
1345{
1346 pub recipient: ID,
1347 pub content: DirectMessageContent<ID, OP, DGM>,
1348}
1349
1350#[derive(Clone, Debug, Serialize, Deserialize)]
1351pub enum DirectMessageContent<ID, OP, DGM>
1352where
1353 DGM: AckedGroupMembership<ID, OP>,
1354{
1355 Welcome {
1356 ciphertext: TwoPartyMessage,
1357 history: DGM::State,
1358 },
1359 TwoParty {
1360 ciphertext: TwoPartyMessage,
1361 },
1362 Forward {
1363 ciphertext: TwoPartyMessage,
1364 },
1365}
1366
1367#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1368pub enum DirectMessageType {
1369 Welcome,
1370 TwoParty,
1371 Forward,
1372}
1373
1374impl Display for DirectMessageType {
1375 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1376 write!(
1377 f,
1378 "{}",
1379 match self {
1380 DirectMessageType::Welcome => "welcome",
1381 DirectMessageType::TwoParty => "2sm",
1382 DirectMessageType::Forward => "forward",
1383 }
1384 )
1385 }
1386}
1387
1388impl<ID, OP, DGM> DirectMessage<ID, OP, DGM>
1389where
1390 DGM: AckedGroupMembership<ID, OP>,
1391{
1392 pub fn message_type(&self) -> DirectMessageType {
1393 match self.content {
1394 DirectMessageContent::Welcome { .. } => DirectMessageType::Welcome,
1395 DirectMessageContent::TwoParty { .. } => DirectMessageType::TwoParty,
1396 DirectMessageContent::Forward { .. } => DirectMessageType::Forward,
1397 }
1398 }
1399}
1400
1401/// Randomly generated seed we keep temporarily around when creating or updating a group or
1402/// removing a member.
1403#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1404#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
1405pub struct NextSeed(Secret<RATCHET_KEY_SIZE>);
1406
1407impl NextSeed {
1408 pub fn from_bytes(bytes: [u8; RATCHET_KEY_SIZE]) -> Self {
1409 Self(Secret::from_bytes(bytes))
1410 }
1411
1412 pub fn try_from_bytes<ID, OP, PKI, DGM, KMG>(
1413 bytes: &[u8],
1414 ) -> Result<Self, DcgkaError<ID, OP, PKI, DGM, KMG>>
1415 where
1416 PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, OneTimeKeyBundle>,
1417 DGM: AckedGroupMembership<ID, OP>,
1418 KMG: PreKeyManager,
1419 {
1420 let bytes: [u8; RATCHET_KEY_SIZE] =
1421 bytes.try_into().map_err(|_| DcgkaError::InvalidKeySize)?;
1422 Ok(Self::from_bytes(bytes))
1423 }
1424
1425 pub(crate) fn as_bytes(&self) -> &[u8; RATCHET_KEY_SIZE] {
1426 self.0.as_bytes()
1427 }
1428}
1429
1430/// Chain secret for the "outer" key-agreement ratchet.
1431#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1432#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
1433pub struct ChainSecret(Secret<RATCHET_KEY_SIZE>);
1434
1435impl ChainSecret {
1436 pub fn from_welcome() -> Self {
1437 // BLAKE3 hash of the byte-representation of the utf8 string "welcome".
1438 Self::from_bytes([
1439 168, 234, 241, 118, 147, 12, 137, 47, 48, 26, 61, 243, 183, 11, 158, 143, 99, 219, 142,
1440 131, 41, 18, 245, 167, 132, 195, 241, 26, 89, 106, 154, 134,
1441 ])
1442 }
1443
1444 pub fn from_add() -> Self {
1445 // BLAKE3 hash of the byte-representation of the utf8 string "add".
1446 Self::from_bytes([
1447 58, 3, 204, 193, 45, 117, 68, 208, 41, 238, 11, 13, 169, 250, 180, 215, 22, 4, 43, 226,
1448 179, 34, 182, 188, 85, 49, 221, 39, 150, 98, 220, 156,
1449 ])
1450 }
1451
1452 pub fn from_bytes(bytes: [u8; RATCHET_KEY_SIZE]) -> Self {
1453 Self(Secret::from_bytes(bytes))
1454 }
1455
1456 pub fn try_from_bytes<ID, OP, PKI, DGM, KEY>(
1457 bytes: &[u8],
1458 ) -> Result<Self, DcgkaError<ID, OP, PKI, DGM, KEY>>
1459 where
1460 PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, OneTimeKeyBundle>,
1461 DGM: AckedGroupMembership<ID, OP>,
1462 KEY: PreKeyManager,
1463 {
1464 let bytes: [u8; RATCHET_KEY_SIZE] =
1465 bytes.try_into().map_err(|_| DcgkaError::InvalidKeySize)?;
1466 Ok(Self::from_bytes(bytes))
1467 }
1468
1469 pub(crate) fn as_bytes(&self) -> &[u8; RATCHET_KEY_SIZE] {
1470 self.0.as_bytes()
1471 }
1472}
1473
1474impl From<UpdateSecret> for ChainSecret {
1475 fn from(value: UpdateSecret) -> Self {
1476 Self(value.0)
1477 }
1478}
1479
1480/// Chain secret for the "inner" message ratchet.
1481#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1482#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
1483pub struct UpdateSecret(Secret<RATCHET_KEY_SIZE>);
1484
1485impl UpdateSecret {
1486 pub fn from_bytes(bytes: [u8; RATCHET_KEY_SIZE]) -> Self {
1487 Self(Secret::from_bytes(bytes))
1488 }
1489
1490 pub fn try_from_bytes<ID, OP, PKI, DGM, KMG>(
1491 bytes: &[u8],
1492 ) -> Result<Self, DcgkaError<ID, OP, PKI, DGM, KMG>>
1493 where
1494 PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, OneTimeKeyBundle>,
1495 DGM: AckedGroupMembership<ID, OP>,
1496 KMG: PreKeyManager,
1497 {
1498 let bytes: [u8; RATCHET_KEY_SIZE] =
1499 bytes.try_into().map_err(|_| DcgkaError::InvalidKeySize)?;
1500 Ok(Self::from_bytes(bytes))
1501 }
1502
1503 pub(crate) fn as_bytes(&self) -> &[u8; RATCHET_KEY_SIZE] {
1504 self.0.as_bytes()
1505 }
1506}
1507
1508impl From<UpdateSecret> for Secret<RATCHET_KEY_SIZE> {
1509 fn from(update_secret: UpdateSecret) -> Self {
1510 update_secret.0
1511 }
1512}
1513
1514#[derive(Debug, Error)]
1515pub enum DcgkaError<ID, OP, PKI, DGM, KMG>
1516where
1517 PKI: IdentityRegistry<ID, PKI::State> + PreKeyRegistry<ID, OneTimeKeyBundle>,
1518 DGM: AckedGroupMembership<ID, OP>,
1519 KMG: PreKeyManager,
1520{
1521 #[error("the given key does not match the required 32 byte length")]
1522 InvalidKeySize,
1523
1524 #[error("expected ratchet secret but couldn't find anything")]
1525 MissingRatchetSecret,
1526
1527 #[error("expected message secret for {0} at seq {1} but couldn't find anything")]
1528 MissingMemberSecret(ID, OP),
1529
1530 #[error("expected direct message of type \"{0}\" but got nothing instead")]
1531 MissingDirectMessage(DirectMessageType),
1532
1533 #[error("expected direct message of type \"{0}\" but got message of type \"{1}\" instead")]
1534 UnexpectedDirectMessageType(DirectMessageType, DirectMessageType),
1535
1536 #[error("direct message recipient mismatch, expected recipient: {1}, actual recipient: {0}")]
1537 NotOurDirectMessage(ID, ID),
1538
1539 #[error("computing members view from dgm failed: {0}")]
1540 MembersView(DGM::Error),
1541
1542 #[error("dgm operation failed: {0}")]
1543 DgmOperation(DGM::Error),
1544
1545 #[error("failed retrieving bundle from pre key registry: {0}")]
1546 PreKeyRegistry(<PKI as PreKeyRegistry<ID, OneTimeKeyBundle>>::Error),
1547
1548 #[error("failed retrieving identity from registry: {0}")]
1549 IdentityRegistry(<PKI as IdentityRegistry<ID, PKI::State>>::Error),
1550
1551 #[error("missing key bundle for member {0}")]
1552 MissingPreKeys(ID),
1553
1554 #[error("missing identity key for member {0}")]
1555 MissingIdentityKey(ID),
1556
1557 #[error(transparent)]
1558 Rng(#[from] RngError),
1559
1560 #[error(transparent)]
1561 KeyManager(KMG::Error),
1562
1563 #[error(transparent)]
1564 TwoParty(#[from] TwoPartyError),
1565
1566 #[error(transparent)]
1567 Hdkf(#[from] HkdfError),
1568}