matrix_sdk_crypto/types/
room_history.rs

1/*
2Copyright 2025 The Matrix.org Foundation C.I.C.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17//! Types for sharing encrypted room history, per [MSC4268].
18//!
19//! [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
20
21use std::fmt::Debug;
22
23use ruma::{DeviceKeyAlgorithm, OwnedRoomId};
24use serde::{Deserialize, Serialize};
25use vodozemac::{megolm::ExportedSessionKey, Curve25519PublicKey};
26
27use crate::{
28    olm::ExportedRoomKey,
29    types::{
30        deserialize_curve_key, events::room_key_withheld::RoomKeyWithheldContent,
31        serialize_curve_key, EventEncryptionAlgorithm, SigningKeys,
32    },
33};
34#[cfg(doc)]
35use crate::{olm::InboundGroupSession, types::events::room_key::RoomKeyContent};
36
37/// A bundle of historic room keys, for sharing encrypted room history, per
38/// [MSC4268].
39///
40/// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
41#[derive(Deserialize, Serialize, Debug, Default)]
42pub struct RoomKeyBundle {
43    /// Keys that we are sharing with the recipient.
44    pub room_keys: Vec<HistoricRoomKey>,
45
46    /// Keys that we are *not* sharing with the recipient.
47    pub withheld: Vec<RoomKeyWithheldContent>,
48}
49
50impl RoomKeyBundle {
51    /// Returns true if there is nothing of value in this bundle.
52    pub fn is_empty(&self) -> bool {
53        self.room_keys.is_empty() && self.withheld.is_empty()
54    }
55}
56
57/// An [`InboundGroupSession`] for sharing as part of a [`RoomKeyBundle`].
58///
59/// Note: unlike a room key received via an `m.room_key` message (i.e., a
60/// [`RoomKeyContent`]), we have no direct proof that the original sender
61/// actually created this session; rather, we have to take the word of
62/// whoever sent us this key bundle.
63#[derive(Deserialize, Serialize)]
64pub struct HistoricRoomKey {
65    /// The encryption algorithm that the session uses.
66    pub algorithm: EventEncryptionAlgorithm,
67
68    /// The room where the session is used.
69    pub room_id: OwnedRoomId,
70
71    /// The Curve25519 key of the device which initiated the session originally,
72    /// according to the device that sent us this key.
73    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
74    pub sender_key: Curve25519PublicKey,
75
76    /// The ID of the session that the key is for.
77    pub session_id: String,
78
79    /// The key for the session.
80    pub session_key: ExportedSessionKey,
81
82    /// The Ed25519 key of the device which initiated the session originally,
83    /// according to the device that sent us this key.
84    #[serde(default)]
85    pub sender_claimed_keys: SigningKeys<DeviceKeyAlgorithm>,
86}
87
88impl Debug for HistoricRoomKey {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        f.debug_struct("SharedRoomKey")
91            .field("algorithm", &self.algorithm)
92            .field("room_id", &self.room_id)
93            .field("sender_key", &self.sender_key)
94            .field("session_id", &self.session_id)
95            .field("sender_claimed_keys", &self.sender_claimed_keys)
96            .finish_non_exhaustive()
97    }
98}
99
100impl From<ExportedRoomKey> for HistoricRoomKey {
101    fn from(exported_room_key: ExportedRoomKey) -> Self {
102        let ExportedRoomKey {
103            algorithm,
104            room_id,
105            sender_key,
106            session_id,
107            session_key,
108            sender_claimed_keys,
109            shared_history: _,
110            forwarding_curve25519_key_chain: _,
111        } = exported_room_key;
112        HistoricRoomKey {
113            algorithm,
114            room_id,
115            sender_key,
116            session_id,
117            session_key,
118            sender_claimed_keys,
119        }
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use insta::assert_debug_snapshot;
126    use ruma::{owned_room_id, DeviceKeyAlgorithm};
127    use vodozemac::{
128        megolm::ExportedSessionKey, Curve25519PublicKey, Curve25519SecretKey, Ed25519SecretKey,
129    };
130
131    use crate::types::{room_history::HistoricRoomKey, EventEncryptionAlgorithm};
132
133    #[test]
134    fn test_historic_room_key_debug() {
135        let key = HistoricRoomKey {
136            algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
137            room_id: owned_room_id!("!room:id"),
138            sender_key: Curve25519PublicKey::from(&Curve25519SecretKey::from_slice(b"abcdabcdabcdabcdabcdabcdabcdabcd")),
139            session_id: "id1234".to_owned(),
140            session_key: ExportedSessionKey::from_base64("AQAAAAC2XHVzsMBKs4QCRElJ92CJKyGtknCSC8HY7cQ7UYwndMKLQAejXLh5UA0l6s736mgctcUMNvELScUWrObdflrHo+vth/gWreXOaCnaSxmyjjKErQwyIYTkUfqbHy40RJfEesLwnN23on9XAkch/iy8R2+Jz7B8zfG01f2Ow2SxPQFnAndcO1ZSD2GmXgedy6n4B20MWI1jGP2wiexOWbFS").unwrap(),
141            sender_claimed_keys: vec![(DeviceKeyAlgorithm::Ed25519, Ed25519SecretKey::from_slice(b"abcdabcdabcdabcdabcdabcdabcdabcd").public_key().into())].into_iter().collect(),
142        };
143
144        assert_debug_snapshot!(key);
145    }
146}