Skip to main content

matrix_sdk_crypto/types/signatures/
mod.rs

1/*
2Copyright 2022-2026 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
17mod signature;
18
19use std::collections::{BTreeMap, btree_map::IntoIter};
20
21use ruma::{DeviceKeyId, OwnedDeviceKeyId, OwnedUserId, UserId};
22use serde::{Deserialize, Deserializer, Serialize, Serializer};
23use vodozemac::Ed25519Signature;
24
25pub use self::signature::Signature;
26
27/// Represents a signature that could not be decoded.
28///
29/// This will currently only hold invalid Ed25519 signatures.
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct InvalidSignature {
32    /// The base64 encoded string that is claimed to contain a signature but
33    /// could not be decoded.
34    pub source: String,
35}
36
37/// Signatures for a signed object.
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct Signatures(
40    BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceKeyId, Result<Signature, InvalidSignature>>>,
41);
42
43impl Signatures {
44    /// Create a new, empty, signatures collection.
45    pub fn new() -> Self {
46        Signatures(Default::default())
47    }
48
49    /// Add the given signature from the given signer and the given key_id to
50    /// the collection.
51    pub fn add_signature(
52        &mut self,
53        signer: OwnedUserId,
54        key_id: OwnedDeviceKeyId,
55        signature: Ed25519Signature,
56    ) -> Option<Result<Signature, InvalidSignature>> {
57        self.0.entry(signer).or_default().insert(key_id, Ok(signature.into()))
58    }
59
60    /// Try to find an Ed25519 signature from the given signer with the given
61    /// key id.
62    pub fn get_signature(&self, signer: &UserId, key_id: &DeviceKeyId) -> Option<Ed25519Signature> {
63        self.get(signer)?.get(key_id)?.as_ref().ok()?.ed25519()
64    }
65
66    /// Get the map of signatures that belong to the given user.
67    pub fn get(
68        &self,
69        signer: &UserId,
70    ) -> Option<&BTreeMap<OwnedDeviceKeyId, Result<Signature, InvalidSignature>>> {
71        self.0.get(signer)
72    }
73
74    /// Remove all the signatures we currently hold.
75    pub fn clear(&mut self) {
76        self.0.clear()
77    }
78
79    /// Do we hold any signatures or is our collection completely empty.
80    pub fn is_empty(&self) -> bool {
81        self.0.is_empty()
82    }
83
84    /// How many signatures do we currently hold.
85    pub fn signature_count(&self) -> usize {
86        self.0.values().map(|u| u.len()).sum()
87    }
88}
89
90impl Default for Signatures {
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96impl IntoIterator for Signatures {
97    type Item = (OwnedUserId, BTreeMap<OwnedDeviceKeyId, Result<Signature, InvalidSignature>>);
98
99    type IntoIter =
100        IntoIter<OwnedUserId, BTreeMap<OwnedDeviceKeyId, Result<Signature, InvalidSignature>>>;
101
102    fn into_iter(self) -> Self::IntoIter {
103        self.0.into_iter()
104    }
105}
106
107impl<'de> Deserialize<'de> for Signatures {
108    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
109    where
110        D: Deserializer<'de>,
111    {
112        let map: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceKeyId, String>> =
113            Deserialize::deserialize(deserializer)?;
114
115        let map = map
116            .into_iter()
117            .map(|(user, signatures)| {
118                let signatures = signatures
119                    .into_iter()
120                    .map(|(key_id, s)| {
121                        let algorithm = key_id.algorithm();
122                        let signature = Signature::from_base64(algorithm, s);
123                        Ok((key_id, signature))
124                    })
125                    .collect::<Result<BTreeMap<_, _>, _>>()?;
126
127                Ok((user, signatures))
128            })
129            .collect::<Result<_, _>>()?;
130
131        Ok(Signatures(map))
132    }
133}
134
135impl Serialize for Signatures {
136    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
137    where
138        S: Serializer,
139    {
140        let signatures: BTreeMap<&OwnedUserId, BTreeMap<&OwnedDeviceKeyId, String>> = self
141            .0
142            .iter()
143            .map(|(u, m)| {
144                (
145                    u,
146                    m.iter()
147                        .map(|(d, s)| {
148                            (
149                                d,
150                                match s {
151                                    Ok(s) => s.to_base64(),
152                                    Err(i) => i.source.to_owned(),
153                                },
154                            )
155                        })
156                        .collect(),
157                )
158            })
159            .collect();
160
161        Serialize::serialize(&signatures, serializer)
162    }
163}
164
165#[cfg(test)]
166mod test {
167    use insta::{assert_json_snapshot, with_settings};
168    use ruma::{DeviceKeyAlgorithm, device_id, owned_user_id};
169
170    use super::*;
171
172    #[test]
173    fn snapshot_signatures() {
174        let signatures = Signatures(BTreeMap::from([
175            (
176                owned_user_id!("@alice:localhost"),
177                BTreeMap::from([
178                    (
179                        DeviceKeyId::from_parts(
180                            DeviceKeyAlgorithm::Ed25519,
181                            device_id!("ABCDEFGH"),
182                        ),
183                        Ok(Signature::from(Ed25519Signature::from_slice(&[0u8; 64]).unwrap())),
184                    ),
185                    (
186                        DeviceKeyId::from_parts(
187                            DeviceKeyAlgorithm::Curve25519,
188                            device_id!("IJKLMNOP"),
189                        ),
190                        Ok(Signature::from(Ed25519Signature::from_slice(&[1u8; 64]).unwrap())),
191                    ),
192                ]),
193            ),
194            (
195                owned_user_id!("@bob:localhost"),
196                BTreeMap::from([(
197                    DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, device_id!("ABCDEFGH")),
198                    Err(InvalidSignature { source: "SOME+B64+SOME+B64+SOME+B64+==".to_owned() }),
199                )]),
200            ),
201        ]));
202
203        with_settings!({sort_maps => true, prepend_module_to_snapshot => false}, {
204            assert_json_snapshot!(signatures)
205        });
206    }
207}