-
Notifications
You must be signed in to change notification settings - Fork 417
[WIP] MSC1228: Removing MXIDs from events #1228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: old_master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,327 @@ | ||||||
| # MSC1228: Removing MXIDs from events | ||||||
|
|
||||||
| ## Background | ||||||
|
|
||||||
| We would like to be able to break the association between a user's ID (such as | ||||||
| `@richvdh:sw1v.org`) and their activity in a room. | ||||||
|
|
||||||
| The stretch goal is to also remove the association with server names, since for | ||||||
| many users, they are the only user on a server and it is reasonable to be able | ||||||
| to ask for the removal of any history of `sw1v.org`'s involvement with a room. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At now each homeserver have own unique namespace via domain suffix, this prevent from duplicates in federation for user and room id's. But if we replace them to random keys without domain suffix - how we can prevent making duplicated keys by some homeservers (not only collisions, but also manually created dupilcates on some "evil" homeservers)? Homeserver can't check for duplicates in all federated servers before create key, and after creating keys - it will be too late for change it.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need to sign the create event with the key, so you can only use a given public key as a room ID if you have the private key; in essence, you're asking for someone to break curve25519. The curve25519 keyspace is 2^256, so for a 50% chance of a collison somewhere within the entire space of matrix rooms, we'd need about 10^38 rooms (see https://en.wikipedia.org/wiki/Birthday_attack). That's a huge number.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for clarifying! |
||||||
|
|
||||||
| The general idea presented here is to use a pseudomym in many places where we | ||||||
| currently use user IDs. The current `@user:server` then becomes a user alias; | ||||||
| the mapping between alias and the psuedonumous ID is public but can be removed | ||||||
| in the future. | ||||||
|
Comment on lines
+12
to
+15
This comment was marked as resolved.
Sorry, something went wrong. |
||||||
|
|
||||||
| User IDs currently appear in the following places in a room: | ||||||
|
|
||||||
| * `sender` of each event | ||||||
| * `state_key` of `m.room.member` events | ||||||
| * `users` list in `m.room.power_levels` events | ||||||
| * `creatorUserId` in the content of `m.widget` | ||||||
|
|
||||||
| Server names appear in the following places: | ||||||
|
|
||||||
| * `origin` of each event | ||||||
| * keys in the `signatures` dict of each event | ||||||
| * Room IDs | ||||||
| * Room Aliases | ||||||
| * `state_keys` in `m.room.aliases` events | ||||||
| * `matrix.to` permalinks | ||||||
|
|
||||||
| ## Proposal | ||||||
|
|
||||||
| [This is v3 of this proposal, which in summary is: do proposal v1, but also | ||||||
| introduce a requirement for a global `user_key` at the same time, which in | ||||||
| future will replace mxids as the user's One True Identity. v1 and v2 are | ||||||
| available for reference at | ||||||
| https://docs.google.com/document/d/1ni4LnC_vafX4h4K4sYNpmccS7QeHEFpAcYcbLS-J21Q#heading=h.y1krynr6itl4.] | ||||||
|
|
||||||
| * Each user (currently identified by an mxid) will also have a `user_key`. In | ||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @martindale suggests: Public keys should not be revealed until the first message is sent; use a hashed value instead.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem with a hash is that it requires a separate mechanism to distribute the actual key; and I'm not sure how we would control access to that key. If I want to start a conversation with a given user, I need their public user_key: what is to say that I should or should not be permitted it? |
||||||
| time, this will replace the mxid as your One True Identity; however for now | ||||||
| they will live in parallel. | ||||||
|
|
||||||
| * A `user_key` is represented like `~1:dV3hr3yE9SxhsWEGBJdTho777S8ompkJTh`, | ||||||
| where `1:` is a version (to allow other systems to be used in future) and | ||||||
| the rest is an (unpadded urlsafe-base64ed) ed25519 public key. | ||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @martindale suggests: ed25519 doesn't support hierarchical keys; recommend secp256k1 instead
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's worth noting that we use ed25519 heavily elsewhere in the protocol. Are the advantages of secp256k1 worth the overhead of introducing a separate set of crypto primitives?
uhoreg marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
| * Homeservers are responsible for making up keys for their users. | ||||||
uhoreg marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
| * For now, each homeserver maintains a one-to-one mapping between | ||||||
| `user_key` and mxid for each of their users. In future, we will look to | ||||||
| break this link to allow portability of accounts. | ||||||
|
Comment on lines
+51
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A side note: I imagine that older versions of the CS API, for example, would show the mxid in the fields they're in right now to avoid breaking older clients that rely on parsing an ID (to get just the name or server) for whatever reason. |
||||||
|
|
||||||
| * Room IDs also become ed25519 public keys. | ||||||
uhoreg marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
| * They look like: `!Sr_Vj3FIqyQ2WjJ9fWpUXRdz6fX4oFAjKrDmu198PnI`. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do user keys get to have versions, yet user room IDs and user room keys do not? (Sorry if this doesn't make sense, this is pretty much my first interaction with the spec and I came from one of the blog posts) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. User room IDs and keys are specific to that room, hence they are versioned by the room version. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The root key of the user is not, hence that needs to be versioned separately. |
||||||
|
|
||||||
| * The server which creates the room is responsible for creating the keypair. | ||||||
|
|
||||||
| * The `m.room.create` event is signed with the room id to stop people making | ||||||
| new rooms which look like old ones. After this point, the private key is | ||||||
| never needed again. <sup id="a1">[1](#f1)</sup> | ||||||
|
|
||||||
| * Define a `user_room_key`, which is yet another ed25519 public key. | ||||||
uhoreg marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
| * It looks like `^Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw`. | ||||||
|
|
||||||
| * Homeservers are responsible for making up user keys for their users. They | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| can (and should) use a different key in each room for each user. | ||||||
|
|
||||||
| * This `user_room_key` is used where we currently use an mxid in the DAG: | ||||||
| `sender`, `m.room.member`, `m.room.power_levels`, | ||||||
| `creatorUserId`. `creator` is removed as per | ||||||
| [MSC2175](./2175-remove-creator-field.md). | ||||||
|
|
||||||
| * Events are **signed by the `user_room_key` of the sender instead of the | ||||||
| server's key**. | ||||||
|
|
||||||
| * If a user leaves and rejoins a room, they should use the same | ||||||
| `user_room_key` (unless a server admin has manually removed the old | ||||||
| mapping). This makes ban evasion harder. (It's up to server owners to | ||||||
| ensure this rule is followed - servers which don't respect it and allow a | ||||||
| serial abuser to evade bans by issuing different `user_room_keys` are likely | ||||||
| to suffer whole-server bans.) | ||||||
|
|
||||||
| * Invite and join events include: | ||||||
|
|
||||||
| * `mxid_mapping`: field which gives the user's `@user:server` mxid and which | ||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in time, the intention is to replace the mxid with simply a server name. |
||||||
| must be signed by the server in question, and the signature must be | ||||||
| verified before the mapping is considered valid. | ||||||
|
|
||||||
| * `user_mapping`: contains the `user_key` giving your True Identity, and | ||||||
| signed by that key. The signature must be verified by receiving | ||||||
| homeservers for it to be considered a valid invite/join event for a vNext | ||||||
| room. | ||||||
|
|
||||||
| ### Examples | ||||||
|
|
||||||
| `m.room.create`: signed by both the room key and the `user_room_key` of the | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we haven't defined "room key" at this point - might be better to call it the room ID's key? |
||||||
| sender: | ||||||
|
|
||||||
| ```json | ||||||
| { | ||||||
| "type": "m.room.create", | ||||||
| "state_key": "", | ||||||
| "room_id": "!Sr_Vj3FIqyQ2WjJ9fWpUXRdz6fX4oFAjKrDmu198PnI", | ||||||
| "event_id": "$Riw5upWofaD4MNGM7bbZIj3bf+Th3fW/tklH4+6+VOg", | ||||||
| "sender": "^Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw", | ||||||
| "content": {}, | ||||||
| "origin_server_ts": 1459891964497, | ||||||
| "prev_events": [], | ||||||
| "prev_state": [], | ||||||
| "auth_events": [], | ||||||
| "signatures": { | ||||||
| "!Sr_Vj3FIqyQ2WjJ9fWpUXRdz6fX4oFAjKrDmu198PnI": "<...>", | ||||||
| "^Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw": "<...>" | ||||||
| }, | ||||||
| "hashes":{"sha256":"3ASU57dV3hr3yE9SxhsWEGBJdTho777S8ompkJTh/Uo"} | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| `m.room.member`, showing `mxid_mapping` and `user_mapping`: | ||||||
|
|
||||||
| ```json | ||||||
| { | ||||||
| "type": "m.room.member", | ||||||
| "state_key": "^Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw", | ||||||
| "room_id": "!Sr_Vj3FIqyQ2WjJ9fWpUXRdz6fX4oFAjKrDmu198PnI", | ||||||
| "event_id": "$k21EhS3j8lhwqTi5NMTUH04oFyvR/1ujBGSWbW27aDs", | ||||||
| "sender": "^Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw", | ||||||
| "content": { | ||||||
| "membership": "join", | ||||||
| "avatar_url": "...", | ||||||
| "displayname": "...", | ||||||
| "mxid_mapping": { | ||||||
| "user_room_key": "^Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw", | ||||||
| "user_id": "@richvdh:matrix.org", | ||||||
| "signatures": { | ||||||
| "matrix.org": { "ed25519:a_zrXW": "<...>" } | ||||||
| } | ||||||
| }, | ||||||
| "user_mapping": { | ||||||
| "user_key": "~1:dV3hr3yE9SxhsWEGBJdTho777S8ompkJTh", | ||||||
| "user_room_key": "^Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw", | ||||||
| "signatures": { | ||||||
| "~1:dV3hr3yE9SxhsWEGBJdTho777S8ompkJTh": "<...>" | ||||||
| } | ||||||
| } | ||||||
| }, | ||||||
| "origin_server_ts": 1489597048772, | ||||||
| "prev_events": [ | ||||||
| "$Riw5upWofaD4MNGM7bbZIj3bf+Th3fW/tklH4+6+VOg" | ||||||
| ], | ||||||
| "prev_state": [], | ||||||
| "auth_events": [ | ||||||
| "$Riw5upWofaD4MNGM7bbZIj3bf+Th3fW/tklH4+6+VOg" | ||||||
| ], | ||||||
| "signatures": { | ||||||
| "^Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw": "<...>" | ||||||
| }, | ||||||
| "hashes":{"sha256":"KLx4Alfa0QzOihSmUMZ1WQj5QdWnbMHwmqKxmYO8hJE"} | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ### Handling the mxid mapping | ||||||
|
|
||||||
| When a server joins a room, it will be presented with a bunch of | ||||||
| `m.room.member` events which may claim mappings onto mxids. There are three | ||||||
| reasons we need to know whether the mapping is valid: | ||||||
|
|
||||||
| * For clients, to help track users between rooms and to correlate to presence | ||||||
| * To authorise other servers to do backfill requests, etc. | ||||||
| * For outgoing messages, knowing which servers to send to. | ||||||
|
|
||||||
| None of these are _particularly_ urgent (they all degrade fairly gracefully in | ||||||
| the case that a mapping is missed). A lot of the slowness in joining rooms | ||||||
| currently comes from having to pull server keys so that event signatures can be | ||||||
| verified during the join process, so it would be nice to be able to consider | ||||||
| the join complete as soon as the signature on the event is verified, and verify | ||||||
| the `mxid_mappings` in the background. | ||||||
|
|
||||||
| #### Implementation | ||||||
|
|
||||||
| As a homeserver, we make an attempt to verify the sender before sending events | ||||||
| to our clients. | ||||||
|
|
||||||
| * When a new invite/join event turns up for a _room you are already in_ (either | ||||||
| via federation push, or because we pulled it via `/event/xxx` due to missing | ||||||
| `auth_events`/`prev_events`): | ||||||
|
|
||||||
| We make an attempt to verify its `mxid_mapping` before persisting it into | ||||||
| our db. If the sig is wrong, we reject the event at that point. | ||||||
|
|
||||||
| If we can't get the key (with a shortish timeout), we handle it as normal | ||||||
| (and schedule a retry for later). | ||||||
|
|
||||||
| Typically we only care about the most recent<sup id="a2">[2](#f2)</sup> | ||||||
| `mxid_mapping` for each `user_room_key`; when we see another we can cancel any | ||||||
| pending verification of any previous mapping<sup id="a3">[3](#f3)</sup>. Any | ||||||
| previously-verified mapping should remain in place until another mapping | ||||||
| becomes available. | ||||||
|
|
||||||
| * When we _backfill_: | ||||||
|
|
||||||
| We do the same thing, although in many cases we'll already have active | ||||||
| mappings for the users in question, so we can ignore any received that | ||||||
| way. By only honouring the most recent mapping, we gain the correct | ||||||
| semantics for account portability: authorisation for backfill depends on the | ||||||
| current location of the user, rather than wherever they were in the past. | ||||||
|
|
||||||
| * When we join a _new room_: | ||||||
|
|
||||||
| For now we do the same thing (ie, make an attempt to verify the mxid | ||||||
| mappings in the join events we receive, and time them out quickly). In | ||||||
| future we might optimise this so the the mappings are verified lazily. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @richvdh how would this work in practice? are you suggesting that you reject events from the DB in retrospect having lazily verified the mapping, and send these to events as redactions of some kind? |
||||||
|
|
||||||
| This should mean that in the majority of cases, we'll have a verified mxid by | ||||||
| the time we send the event to a client. | ||||||
|
|
||||||
| For each `user_room_key`, we therefore have: | ||||||
|
|
||||||
| * zero or one verified mxid mappings. | ||||||
| * zero or one incomplete mxid mappings. | ||||||
|
|
||||||
| We extend the CS API to include a `verified_sender_mxid` field on any events | ||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ara4n says: Whilst i think we should have this present by default to aid stupid clients, should we provide the option to filter it out for clients which know they are smart enough to learn mappings from the async mapping bit of the sync response instead?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. my reply: maybe? I'm not sure the added complexity is worthwhile just to remove a field from some JSON which you could otherwise ignore. |
||||||
| sent to a client where we know the user's current **verified** mxid. | ||||||
|
|
||||||
| * This is included in the interests of helping simple clients do the right | ||||||
| thing most of the time - but it is annoying and dangerous because it will | ||||||
| **sometimes** be missing. Still, we don't want to either (a) hold up all | ||||||
| traffic in the room while we wait for a verification which may never | ||||||
| succeed; (b) hold back some events while we do a verification for a sender; | ||||||
| (c) require that all clients always have to wait for an asynchronous | ||||||
| verification and match them up. | ||||||
|
|
||||||
| We also add a **new** field to the `/sync` response which tells clients about | ||||||
| mxid mappings as they are resolved. | ||||||
|
|
||||||
| Question: should we remove unverified mxid mappings from join events before | ||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some debate over this at https://docs.google.com/document/d/1ni4LnC_vafX4h4K4sYNpmccS7QeHEFpAcYcbLS-J21Q/edit?disco=AAAAB3zzaso, but everybody apart from me seemed to think we should, and on re-reading, I think I agree. |
||||||
| serving them to the clients, to stop client developers relying on it and | ||||||
| breaking everything? | ||||||
|
|
||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We may need to give details on how to use the user keys to find user devices and device keys in order to do e2e. |
||||||
| ### Sending invites | ||||||
|
|
||||||
| We have a bootstrapping problem for invites in that, until a user joins a room, | ||||||
| we don't know their `user_room_key`. | ||||||
|
|
||||||
| Also: invite events are supposed to be signed by the invitee, so that other | ||||||
| members of the room can be sure that they have actually received a copy of the | ||||||
| event. | ||||||
|
|
||||||
| The current invite dance is: | ||||||
|
|
||||||
| * inviting server builds a complete invite event, and signs it | ||||||
| * inviting server sends a copy to invited server, along with some (unsigned) | ||||||
| state about the room: name, avatar, inviting user's join event | ||||||
| * invited server checks that request came from server of inviting user | ||||||
| * invited server also signs the event, and tells the user about it | ||||||
| * inviting server adds the (double-signed) event to the DAG and sends it to | ||||||
| the rest of the federation (including the invited server, if it was already | ||||||
| in the room) | ||||||
|
|
||||||
| We could change this to: | ||||||
|
|
||||||
| * inviting server builds a partial invite event | ||||||
| * inviting server PUTs to `/_matrix/federation/v3/invite/<event_id>/<target_mxid>` on invited server | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Excuse me asking but it is not completely clear to me how the inviting server knows which is the invited server? Either we invite someone by knowing his/her Global lookup systems (like DHT) are usually used to look up the location of a cryptographically identified resource in distributed networks (or get a negative response of non-existence after a timeout) but as far as I know we don't have that. (I feel a slight similarity to the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My understanding is that we are still going to have mxids of the form |
||||||
| * invited server checks that request came from server of inviting user | ||||||
| * invited server adds: | ||||||
| * `user_room_key` in state_key | ||||||
| * mxid attestation | ||||||
| * signature | ||||||
| * invited server tells the user about it, and returns the completed event to inviting server | ||||||
| * inviting server adds its signature to the complete event | ||||||
| * inviting server adds the (double-signed) event to the DAG and sends it to | ||||||
| the rest of the federation (including the invited server, if it was already | ||||||
| in the room) | ||||||
|
|
||||||
| ## Problems | ||||||
|
|
||||||
| How to handle the state keys in `room_aliases` events? | ||||||
|
|
||||||
| How to handle server names in `matrix.to` permalinks? | ||||||
|
|
||||||
| What if somebody does a join with an inappropriate avatar/displayname? If we | ||||||
| redact their join, we'll redact their identity assertion too :/ | ||||||
|
|
||||||
| ## Other things that might fall out nicely | ||||||
|
|
||||||
| * Fixes broken backfill due to changed signing keys (matrix-org/synapse#3121) | ||||||
| * Case sensitive mxid comparison problems? | ||||||
| * Opens a path to killing off perspectives in favour of just asking servers | ||||||
| what their keys are (via TLS, with trust coming from X.509 certificates). | ||||||
| * Helping with the domain reuse problem | ||||||
| * Nicer way of validating redactions by comparing the `user_room_key` of the | ||||||
| message and the redaction which addresses the suboptimal solution introduced | ||||||
| by [MSC1659](./1659-event-id-as-hashes.md) | ||||||
| * β¦ | ||||||
|
|
||||||
| ## Stuff we might want to avoid designing out | ||||||
|
|
||||||
| * Ability to migrate users between servers by changing their mapping assertions | ||||||
|
|
||||||
| * Ability to support alternative identity mapping assertions rather than being | ||||||
| strictly mxid->user_key (e.g. 3pid->user-key too). This could help | ||||||
| decentralised identity mappings in general in future, and possibly unify 3pid | ||||||
| invites with normal invites? It could also support discovering servers by | ||||||
| key rather than DNS (e.g. via DHT), which would be useful for p2p in future. | ||||||
|
|
||||||
| ## Key management | ||||||
|
|
||||||
| If a private user key gets lost, they can just start using a new one and | ||||||
| announce a new mapping; however this may require an update to the | ||||||
| `power_levels` to give rights to the new user. | ||||||
|
|
||||||
| If a private user key is compromised, then again we start using a new one and | ||||||
| announce a new mapping, so that new events from the old key wouldn't look like | ||||||
| they came from that user. Again it may need `power_level`s updates to remove | ||||||
| power from the old `user_key`, but I think that is fair: you give away the keys | ||||||
| to your privileged account, you have to expect some cleanup. Ideally we would | ||||||
| have a way of revoking the old key properly, but this can be deferred for now. | ||||||
|
|
||||||
| <a id="f1"/>[1] although we might think about letting its use confer some sort of founder semantics.[β©](#a1) | ||||||
|
|
||||||
| <a id="f2"/>[2] or more accurately, the one in the current room state.[β©](#a2) | ||||||
|
|
||||||
| <a id="f3"/>[3] if a user/server spams out mappings so quickly that none of them ever complete, that is their own loss.[β©](#a3) | ||||||
Uh oh!
There was an error while loading. Please reload this page.