Skip to main content

de_mls/mls_crypto/
types.rs

1//! MLS types and operation results.
2
3use openmls::key_packages::KeyPackage as MlsKeyPackage;
4
5use crate::mls_crypto::MlsError;
6
7/// Serialized key package for joining a conversation.
8///
9/// Carries the TLS-serialized key package and the owner's identity bytes.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct KeyPackageBytes {
12    bytes: Vec<u8>,
13    identity: Vec<u8>,
14}
15
16impl KeyPackageBytes {
17    pub fn new(bytes: Vec<u8>, identity: Vec<u8>) -> Self {
18        Self { bytes, identity }
19    }
20
21    /// Serialized key package bytes.
22    pub fn as_bytes(&self) -> &[u8] {
23        &self.bytes
24    }
25
26    /// Identity bytes of the key package's owner, extracted from the
27    /// MLS credential at construction time.
28    pub fn identity(&self) -> &[u8] {
29        &self.identity
30    }
31}
32
33/// Membership change as supplied to the steward's commit pipeline.
34///
35/// One of three "membership change" shapes used in the codebase. They
36/// describe the same underlying intent at different boundaries:
37///
38/// | Shape | Where | Carries |
39/// |-------|-------|---------|
40/// | [`crate::protos::de_mls::messages::v1::ConversationUpdateRequest`] | consensus wire | wire payload, also covers governance kinds (emergency / election) |
41/// | [`MlsCommitInput`] | input to [`super::MlsService::create_commit_candidate`] | Add carries the full key package; Remove carries the target identity bytes |
42/// | [`MlsProposalOutput`] | output of MLS staging / decryption | identity-only, plus `Other` for proposal kinds we don't construct |
43#[derive(Clone, Debug)]
44pub enum MlsCommitInput {
45    /// Add a new member using their key package.
46    Add(KeyPackageBytes),
47    /// Remove a member by their identity bytes.
48    Remove(Vec<u8>),
49}
50
51/// Membership change as observed in a single MLS proposal — extracted by
52/// the MLS service from incoming proposals (standalone or commit-bundled).
53///
54/// See [`MlsCommitInput`] for the corresponding input shape and the
55/// boundary table.
56#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
57pub enum MlsProposalOutput {
58    /// Add a member — identity is read from the key package credential.
59    Add(Vec<u8>),
60    /// Remove a member — identity of the removed member.
61    Remove(Vec<u8>),
62    /// Any other proposal type (update, reinit, etc.) — we do not
63    /// construct these ourselves; receipt is logged for diagnostics.
64    Other(String),
65}
66
67/// Coarse-grained kind of an MLS wire message.
68///
69/// Used for strict lane checks in the core batch pipeline before processing.
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub enum MlsMessageKind {
72    Application,
73    Proposal,
74    Commit,
75    Welcome,
76    Other,
77}
78
79/// Result of decrypting an inbound message.
80#[derive(Clone, Debug)]
81pub enum DecryptResult {
82    /// Application message decrypted successfully.
83    /// Contains `(message_bytes, sender_identity)`.
84    Application(Vec<u8>, Vec<u8>),
85    /// We were removed from the conversation.
86    /// Contains the authenticated sender identity.
87    Removed(Vec<u8>),
88    /// Proposal stored (no action needed).
89    /// Contains `(sender_identity, action)`.
90    ProposalStored(Vec<u8>, MlsProposalOutput),
91    /// Message ignored (wrong conversation/epoch).
92    Ignored,
93}
94
95/// Result of staging a remote candidate (proposal batch + commit).
96///
97/// Returned by `MlsService::stage_remote_commit`. The `Staged` variant
98/// carries the MLS-authenticated senders of every staged proposal and of
99/// the commit, plus the membership-change actions extracted from the
100/// commit. `Aborted` signals a benign rejection (stale epoch, wrong conversation,
101/// non-proposal in a proposal slot, non-commit in the commit slot) where
102/// no validated outcome can be produced — the caller must NOT treat it as
103/// a violation but should clean MLS state via `discard_staged_commit`.
104#[derive(Clone, Debug)]
105pub enum StagedCandidateResult {
106    /// Candidate staged successfully. All identities are MLS-authenticated.
107    Staged {
108        /// Identity bytes of the commit sender (MLS-authenticated).
109        commit_sender: Vec<u8>,
110        /// Per-proposal sender identity, in input order. Caller cross-checks
111        /// these against the commit sender to detect bundles signed by
112        /// non-committers.
113        proposal_senders: Vec<Vec<u8>>,
114        /// Whether this commit removes us from the conversation.
115        self_removed: bool,
116        /// Membership changes (Add/Remove) contained in the commit's proposals.
117        actions: Vec<MlsProposalOutput>,
118    },
119    /// Candidate was benign but not processable. Caller cleans MLS state.
120    Aborted,
121}
122
123/// Result of creating a commit candidate (not merged yet).
124#[derive(Clone, Debug)]
125pub struct CommitCandidate {
126    /// Serialized MLS proposal messages.
127    pub proposals: Vec<Vec<u8>>,
128    /// Serialized MLS commit message.
129    pub commit: Vec<u8>,
130    /// Optional welcome message for new members (if any adds).
131    pub welcome: Option<Vec<u8>>,
132}
133
134/// Parse a JSON-serialized key package and extract the identity.
135///
136/// Returns `(key_package_bytes, identity)` where:
137/// - `key_package_bytes` is the original JSON bytes (passed through)
138/// - `identity` is the identity bytes from the leaf credential
139pub fn key_package_bytes_from_json(json_bytes: Vec<u8>) -> Result<(Vec<u8>, Vec<u8>), MlsError> {
140    let kp: MlsKeyPackage =
141        serde_json::from_slice(&json_bytes).map_err(MlsError::KeyPackageJson)?;
142    let identity = kp.leaf_node().credential().serialized_content().to_vec();
143    Ok((json_bytes, identity))
144}