Skip to main content

ruma_signatures/
hash.rs

1use base64::{Engine, alphabet};
2use ruma_common::{
3    CanonicalJsonObject,
4    canonical_json::redact,
5    room_version_rules::{EventIdFormatVersion, RoomVersionRules},
6    serde::{Base64, base64::Standard},
7};
8use sha2::{Digest, Sha256};
9
10use crate::{JsonError, verify::to_canonical_json_string_with_fields_to_remove};
11
12/// The [maximum size allowed] for a PDU.
13///
14/// [maximum size allowed]: https://spec.matrix.org/v1.18/client-server-api/#size-limits
15const MAX_PDU_BYTES: usize = 65_535;
16
17/// The fields to remove from a JSON object when creating a content hash of an event.
18static CONTENT_HASH_FIELDS_TO_REMOVE: &[&str] = &["hashes", "signatures", "unsigned"];
19
20/// The fields to remove from a JSON object when creating a reference hash of an event.
21static REFERENCE_HASH_FIELDS_TO_REMOVE: &[&str] = &["signatures", "unsigned"];
22
23/// Computes the [content hash] of the given event.
24///
25/// The content hash of an event covers the complete event including the unredacted contents. It is
26/// used during federation and is described in the Matrix server-server specification.
27///
28/// # Parameters
29///
30/// * `object`: A JSON object to generate a content hash for.
31///
32/// # Errors
33///
34/// Returns an error if the event is too large.
35///
36/// [content hash]: https://spec.matrix.org/v1.18/server-server-api/#calculating-the-content-hash-for-an-event
37pub fn content_hash(object: &CanonicalJsonObject) -> Result<Base64<Standard, [u8; 32]>, JsonError> {
38    let json =
39        to_canonical_json_string_with_fields_to_remove(object, CONTENT_HASH_FIELDS_TO_REMOVE)?;
40
41    if json.len() > MAX_PDU_BYTES {
42        return Err(JsonError::PduTooLarge);
43    }
44
45    let hash = Sha256::digest(json.as_bytes());
46
47    Ok(Base64::new(hash.into()))
48}
49
50/// Computes the [reference hash] of the given event.
51///
52/// The reference hash of an event covers the essential fields of an event, including content
53/// hashes.
54///
55/// Returns the hash as a base64-encoded string, without padding. The correct character set is used
56/// depending on the room version:
57///
58/// * For room versions 1 and 2, the standard character set is used for sending the reference hash
59///   of the `auth_events` and `prev_events`.
60/// * For room version 3, the standard character set is used for using the reference hash as the
61///   event ID.
62/// * For newer versions, the URL-safe character set is used for using the reference hash as the
63///   event ID.
64///
65/// # Parameters
66///
67/// * `object`: A JSON object to generate a reference hash for.
68/// * `rules`: The rules of the version of the current room.
69///
70/// # Errors
71///
72/// Returns an error if the event is too large or redaction fails.
73///
74/// [reference hash]: https://spec.matrix.org/v1.18/server-server-api#calculating-the-reference-hash-for-an-event
75pub fn reference_hash(
76    object: &CanonicalJsonObject,
77    rules: &RoomVersionRules,
78) -> Result<String, JsonError> {
79    let redacted_value = redact(object.clone(), &rules.redaction, None)?;
80
81    let json = to_canonical_json_string_with_fields_to_remove(
82        &redacted_value,
83        REFERENCE_HASH_FIELDS_TO_REMOVE,
84    )?;
85
86    if json.len() > MAX_PDU_BYTES {
87        return Err(JsonError::PduTooLarge);
88    }
89
90    let hash = Sha256::digest(json.as_bytes());
91
92    let base64_alphabet = match rules.event_id_format {
93        EventIdFormatVersion::V1 | EventIdFormatVersion::V2 => alphabet::STANDARD,
94        // Room versions higher than version 3 are URL-safe base64 encoded
95        _ => alphabet::URL_SAFE,
96    };
97    let base64_engine = base64::engine::GeneralPurpose::new(
98        &base64_alphabet,
99        base64::engine::general_purpose::NO_PAD,
100    );
101
102    Ok(base64_engine.encode(hash))
103}