Room Version 7
This room version builds on version 6 to introduce knocking as a possible join rule and membership state.
Client considerations
This is the first room version to support knocking completely. As such, users will not be able to knock on rooms which are not based off v7.
Though unchanged in this room version, clients which implement the redaction algorithm locally should refer to the redactions section below for a full overview.
Server implementation components
Room version 7 adds new authorization rules for events to support knocking. Room version 6 has details of other authorization rule changes, as do the versions v6 is based upon.
Authorization rules
[New in this version]
For checks performed upon m.room.member
events, a
new point for membership=knock
is added.
Events must be signed by the server denoted by the sender
key.
m.room.redaction
events are not explicitly part of the auth rules.
They are still subject to the minimum power level rules, but should always
fall into “10. Otherwise, allow”. Instead of being authorized at the time
of receipt, they are authorized at a later stage: see the
Redactions section below for more information.
The types of state events that affect authorization are:
m.room.create
m.room.member
m.room.join_rules
m.room.power_levels
m.room.third_party_invite
sender
’s power level can also refer to
the default power level for users in the room.
The rules are as follows:
- If type is
m.room.create
:- If it has any previous events, reject.
- If the domain of the
room_id
does not match the domain of thesender
, reject. - If
content.room_version
is present and is not a recognised version, reject. - If
content
has nocreator
field, reject. - Otherwise, allow.
- Reject if event has
auth_events
that:- have duplicate entries for a given
type
andstate_key
pair - have entries whose
type
andstate_key
don’t match those specified by the auth events selection algorithm described in the server specification.
- have duplicate entries for a given
- If event does not have a
m.room.create
in itsauth_events
, reject. - If type is
m.room.member
:- If no
state_key
key ormembership
key incontent
, reject. - If
membership
isjoin
:- If the only previous event is an
m.room.create
and thestate_key
is the creator, allow. - If the
sender
does not matchstate_key
, reject. - If the
sender
is banned, reject. - If the
join_rule
isinvite
then allow if membership state isinvite
orjoin
. - If the
join_rule
ispublic
, allow. - Otherwise, reject.
- If the only previous event is an
- If
membership
isinvite
:- If
content
hasthird_party_invite
key:- If target user is banned, reject.
- If
content.third_party_invite
does not have asigned
key, reject. - If
signed
does not havemxid
andtoken
keys, reject. - If
mxid
does not matchstate_key
, reject. - If there is no
m.room.third_party_invite
event in the current room state withstate_key
matchingtoken
, reject. - If
sender
does not matchsender
of them.room.third_party_invite
, reject. - If any signature in
signed
matches any public key in them.room.third_party_invite
event, allow. The public keys are incontent
ofm.room.third_party_invite
as:- A single public key in the
public_key
field. - A list of public keys in the
public_keys
field.
- A single public key in the
- Otherwise, reject.
- If the
sender
’s current membership state is notjoin
, reject. - If target user’s current membership state is
join
orban
, reject. - If the
sender
’s power level is greater than or equal to the invite level, allow. - Otherwise, reject.
- If
- If
membership
isleave
:- If the
sender
matchesstate_key
, allow if and only if that user’s current membership state isinvite
,join
, orknock
. - If the
sender
’s current membership state is notjoin
, reject. - If the target user’s current membership state is
ban
, and thesender
’s power level is less than the ban level, reject. - If the
sender
’s power level is greater than or equal to the kick level, and the target user’s power level is less than thesender
’s power level, allow. - Otherwise, reject.
- If the
- If
membership
isban
:- If the
sender
’s current membership state is notjoin
, reject. - If the
sender
’s power level is greater than or equal to the ban level, and the target user’s power level is less than thesender
’s power level, allow. - Otherwise, reject.
- If the
- If
membership
isknock
:- If the
join_rule
is anything other thanknock
, reject. - If
sender
does not matchstate_key
, reject. - If the
sender
’s current membership is notban
,invite
, orjoin
, allow. - Otherwise, reject.
- If the
- Otherwise, the membership is unknown. Reject.
- If no
- If the
sender
’s current membership state is notjoin
, reject. - If type is
m.room.third_party_invite
:- Allow if and only if
sender
’s current power level is greater than or equal to the invite level.
- Allow if and only if
- If the event type’s required power level is greater than the
sender
’s power level, reject. - If the event has a
state_key
that starts with an@
and does not match thesender
, reject. - If type is
m.room.power_levels
:- If
users
key incontent
is not a dictionary with keys that are valid user IDs with values that are integers (or a string that is an integer), reject. - If there is no previous
m.room.power_levels
event in the room, allow. - For the keys
users_default
,events_default
,state_default
,ban
,redact
,kick
,invite
check if they were added, changed or removed. For each found alteration:- If the current value is higher than the
sender
’s current power level, reject. - If the new value is higher than the
sender
’s current power level, reject.
- If the current value is higher than the
- For each entry being added, changed or removed in both the
events
,users
, andnotifications
keys:- If the current value is higher than the
sender
’s current power level, reject. - If the new value is higher than the
sender
’s current power level, reject.
- If the current value is higher than the
- For each entry being changed under the
users
key, other than thesender
’s own entry:- If the current value is equal to the
sender
’s current power level, reject.
- If the current value is equal to the
- Otherwise, allow.
- If
- Otherwise, allow.
Some consequences of these rules:
- Unless you are a member of the room, the only permitted operations (apart from the initial create/join) are: joining a public room; accepting or rejecting an invitation to a room.
- To unban somebody, you must have power level greater than or equal to both the kick and ban levels, and greater than the target user’s power level.
Unchanged from v6
The following sections have not been modified since v6, but are included for completeness.
Redactions
Upon receipt of a redaction event, the server must strip off any keys not in the following list:
event_id
type
room_id
sender
state_key
content
hashes
signatures
depth
prev_events
prev_state
auth_events
origin
origin_server_ts
membership
The content object must also be stripped of all keys, unless it is one of one of the following event types:
m.room.member
allows keymembership
.m.room.create
allows keycreator
.m.room.join_rules
allows keyjoin_rule
.m.room.power_levels
allows keysban
,events
,events_default
,kick
,redact
,state_default
,users
,users_default
.m.room.history_visibility
allows keyhistory_visibility
.
Handling redactions
In room versions 1 and 2, redactions were explicitly part of the authorization rules under Rule 11. As of room version 3, these conditions no longer exist as represented by this version’s authorization rules.
While redactions are always accepted by the authorization rules for events, they should not be sent to clients until both the redaction event and the event the redaction affects have been received, and can be validated. If both events are valid and have been seen by the server, then the server applies the redaction if one of the following conditions is met:
- The power level of the redaction event’s
sender
is greater than or equal to the redact level. - The domain of the redaction event’s
sender
matches that of the original event’ssender
.
If the server would apply a redaction, the redaction event is also sent to clients. Otherwise, the server simply waits for a valid partner event to arrive where it can then re-check the above.
Event IDs
The event ID is the reference
hash of
the event encoded using a variation of Unpadded
Base64 which replaces the 62nd and
63rd characters with -
and _
instead of using +
and /
. This
matches RFC4648’s definition of URL-safe
base64.
Event IDs are still prefixed with $
and might result in looking like
$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg
.
Event format
Events in rooms of this version have the following structure:
Persistent Data Unit
A persistent data unit (event) for room version 4 and beyond.
Persistent Data Unit
Name | Type | Description |
---|---|---|
auth_events |
[string] |
Required: Event IDs for the authorization events that would allow this event to be in the room. Must contain less than or equal to 10 events. Note that if the relevant auth event selection rules are used, this restriction should never be encountered. |
content |
object |
Required: The content of the event. |
depth |
integer |
Required: The maximum depth of the prev_events , plus one. Must be less than the
maximum value for an integer (2^63 - 1). If the room’s depth is already at
the limit, the depth must be set to the limit. |
hashes |
Event Hash |
Required: Content hashes of the PDU, following the algorithm specified in Signing Events. |
origin |
string |
Required: The server_name of the homeserver that created this event. |
origin_server_ts |
integer |
Required: Timestamp in milliseconds on origin homeserver when this event was created. |
prev_events |
[string] |
Required: Event IDs for the most recent events in the room that the homeserver was aware of when it made this event. Must contain less than or equal to 20 events. |
redacts |
string |
For redaction events, the ID of the event being redacted. |
room_id |
string |
Required: Room identifier. |
sender |
string |
Required: The ID of the user sending the event. |
signatures |
{ string: Server Signatures} |
Required: Signatures for the PDU, following the algorithm specified in Signing Events. |
state_key |
string |
If this key is present, the event is a state event, and it will replace previous events
with the same type and state_key in the room state. |
type |
string |
Required: Event type |
unsigned |
UnsignedData |
Additional data added by the origin server but not covered by the signatures . |
Name | Type | Description |
---|---|---|
sha256 |
string |
Required: The hash. |
Name | Type | Description |
---|---|---|
age |
integer |
The number of milliseconds that have passed since this message was sent. |
Examples
{
"auth_events": [
"$urlsafe_base64_encoded_eventid",
"$a-different-event-id"
],
"content": {
"key": "value"
},
"depth": 12,
"hashes": {
"sha256": "thishashcoversallfieldsincasethisisredacted"
},
"origin": "example.com",
"origin_server_ts": 1404838188000,
"prev_events": [
"$urlsafe_base64_encoded_eventid",
"$a-different-event-id"
],
"redacts": "$some-old_event",
"room_id": "!UcYsUzyxTGDxLBEvLy:example.org",
"sender": "@alice:example.com",
"signatures": {
"example.com": {
"ed25519:key_version:": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus"
}
},
"state_key": "my_key",
"type": "m.room.message",
"unsigned": {
"age": 4612,
"key": "value"
}
}
State resolution
The room state S′(E) after an event E is defined in terms of the room state S(E) before E, and depends on whether E is a state event or a message event:
- If E is a message event, then S′(E) = S(E).
- If E is a state event, then S′(E) is S(E), except that its
entry corresponding to E’s
event_type
andstate_key
is replaced by E’sevent_id
.
The room state S(E) before E is the resolution of the set of
states {S′(E1), S′(E2), …} consisting of
the states after each of E’s prev_event
s
{E1, E2, …}, where the resolution of a set of
states is given in the algorithm below.
Definitions
The state resolution algorithm for version 2 rooms uses the following definitions, given the set of room states {S1, S2, …}:
Power events
A power event is a state event with type m.room.power_levels
or
m.room.join_rules
, or a state event with type m.room.member
where
the membership
is leave
or ban
and the sender
does not match the
state_key
. The idea behind this is that power events are events that
might remove someone’s ability to do something in the room.
Unconflicted state map and conflicted state set
The unconflicted state map is the state where the value of each key
exists and is the same in each state Si. The conflicted
state set is the set of all other state events. Note that the
unconflicted state map only has one event per (event_type, state_key)
,
whereas the conflicted state set may have multiple events.
Auth difference The auth difference is calculated by first calculating the full auth chain for each state Si, that is the union of the auth chains for each event in Si, and then taking every event that doesn’t appear in every auth chain. If Ci is the full auth chain of Si, then the auth difference is ∪ Ci − ∩ Ci.
Full conflicted set The full conflicted set is the union of the conflicted state set and the auth difference.
Reverse topological power ordering The reverse topological power ordering of a set of events is the lexicographically smallest topological ordering based on the DAG formed by auth events. The reverse topological power ordering is ordered from earliest event to latest. For comparing two topological orderings to determine which is the lexicographically smallest, the following comparison relation on events is used: for events x and y, x < y if
- x’s sender has greater power level than y’s sender, when
looking at their respective
auth_event
s; or - the senders have the same power level, but x’s
origin_server_ts
is less than y’sorigin_server_ts
; or - the senders have the same power level and the events have the same
origin_server_ts
, but x’sevent_id
is less than y’sevent_id
.
The reverse topological power ordering can be found by sorting the events using Kahn’s algorithm for topological sorting, and at each step selecting, among all the candidate vertices, the smallest vertex using the above comparison relation.
Mainline ordering
Given an m.room.power_levels
event P, the mainline of P is the
list of events generated by starting with P and recursively taking the
m.room.power_levels
events from the auth_events
, ordered such that
P is last. Given another event e, the closest mainline event to
e is the first event encountered in the mainline when iteratively
descending through the m.room.power_levels
events in the auth_events
starting at e. If no mainline event is encountered when iteratively
descending through the m.room.power_levels
events, then the closest
mainline event to e can be considered to be a dummy event that is
before any other event in the mainline of P for the purposes of
condition 1 below.
The mainline ordering based on P of a set of events is the ordering, from smallest to largest, using the following comparison relation on events: for events x and y, x < y if
- the closest mainline event to x appears before the closest mainline event to y; or
- the closest mainline events are the same, but x’s
origin_server_ts
is less than y’sorigin_server_ts
; or - the closest mainline events are the same and the events have the
same
origin_server_ts
, but x’sevent_id
is less than y’sevent_id
.
Iterative auth checks
The iterative auth checks algorithm takes as input an initial room
state and a sorted list of state events, and constructs a new room state
by iterating through the event list and applying the state event to the
room state if the state event is allowed by the authorization
rules.
If the state event is not allowed by the authorization rules, then the
event is ignored. If a (event_type, state_key)
key that is required
for checking the authorization rules is not present in the state, then
the appropriate state event from the event’s auth_events
is used if
the auth event is not rejected.
Algorithm
The resolution of a set of states is obtained as follows:
- Take all power events and any events in their auth chains, recursively, that appear in the full conflicted set and order them by the reverse topological power ordering.
- Apply the iterative auth checks algorithm, starting from the unconflicted state map, to the list of events from the previous step to get a partially resolved state.
- Take all remaining events that weren’t picked in step 1 and order them by the mainline ordering based on the power level in the partially resolved state obtained in step 2.
- Apply the iterative auth checks algorithm on the partial resolved state and the list of events from the previous step.
- Update the result by replacing any event with the event with the same key from the unconflicted state map, if such an event exists, to get the final resolved state.
Rejected events
Events that have been rejected due to failing auth based on the state at the event (rather than based on their auth chain) are handled as usual by the algorithm, unless otherwise specified.
Note that no events rejected due to failure to auth against their auth chain should appear in the process, as they should not appear in state (the algorithm only uses events that appear in either the state sets or in the auth chain of the events in the state sets).
This helps ensure that different servers' view of state is more likely to converge, since rejection state of an event may be different. This can happen if a third server gives an incorrect version of the state when a server joins a room via it (either due to being faulty or malicious). Convergence of state is a desirable property as it ensures that all users in the room have a (mostly) consistent view of the state of the room. If the view of the state on different servers diverges it can lead to bifurcation of the room due to e.g. servers disagreeing on who is in the room.
Intuitively, using rejected events feels dangerous, however:
- Servers cannot arbitrarily make up state, since they still need to pass the auth checks based on the event’s auth chain (e.g. they can’t grant themselves power levels if they didn’t have them before).
- For a previously rejected event to pass auth there must be a set of state that allows said event. A malicious server could therefore produce a fork where it claims the state is that particular set of state, duplicate the rejected event to point to that fork, and send the event. The duplicated event would then pass the auth checks. Ignoring rejected events would therefore not eliminate any potential attack vectors.
Rejected auth events are deliberately excluded from use in the iterative auth checks, as auth events aren’t re-authed (although non-auth events are) during the iterative auth checks.
Canonical JSON
Servers MUST strictly enforce the JSON format specified in the
appendices. This translates to a
400 M_BAD_JSON
error on most endpoints, or discarding of events over
federation. For example, the Federation API’s /send
endpoint would
discard the event whereas the Client Server API’s /send/{eventType}
endpoint would return a M_BAD_JSON
error.
Signing key validity period
When validating event signatures, servers MUST enforce the
valid_until_ts
property from a key request is at least as large as the
origin_server_ts
for the event being validated. Servers missing a copy
of the signing key MUST try to obtain one via the GET
/_matrix/key/v2/server
or POST
/_matrix/key/v2/query
APIs. When using the /query
endpoint, servers MUST set the
minimum_valid_until_ts
property to prompt the notary server to attempt
to refresh the key if appropriate.
Servers MUST use the lesser of valid_until_ts
and 7 days into the
future when determining if a key is valid. This is to avoid a situation
where an attacker publishes a key which is valid for a significant
amount of time without a way for the homeserver owner to revoke it.