# content
In our hackerspace community, we recently ran into some issues with end-to-end encrypted Matrix rooms: In a previously unencrypted public room with around 100 users across lots of different home servers, a room admin had enabled end-to-end encryption by accident.

Unfortunately, end-to-end encryption in Matrix can still be unreliable at times, and encryption (or rather decryption) errors can be caused by a wide range of issues by both sender and recipient. Therefore we decided that we wanted to return to an unencrypted room.

Please note that this blog post should not be interpreted as advocacy against end-to-end encryption; in fact, I'm a huge fan of end-to-end encryption. However, this specific case is about a room that is public anyway, and where E2EE was actively leading to issues.

How to Disable End-to-end Encryption in Matrix?

The short answer is: You don't!

Once E2EE has been enabled in a Matrix room, there is no way to disable it. The only choice is to create an entirely new and unencrypted room and tell your users to join that room instead.

The good news is that Matrix makes it fairly easy for your users to join the new room; if done properly, it's only a single click or tap in most clients.

If you're asking yourself "Matrix has a simple room upgrade API, why can't you just use that", the answer is simple: The room upgrade API causes the server to replicate all room state events to the new room. This includes the "enable encryption" event as well, and you'd be left with a new, but encrypted room.

How to Replace a Room?

The first thing you'll need is a user with admin permissions (power level 100) in the existing room, and an access token for that user. An access token can usually be obtained quite easily, e.g. in Element you can export the access token under Settings / Advanced settings / Dev Tools / Access Token when Developer mode is enabled. An access token issued by Synapse looks like the following, other homeservers may issue them in a different format:

syt_QcG6Rup_QEBF9Wji2mQzbyXoSgTw_9LJhae

The next thing we'll need is the internal room ID of your Matrix room. If your client does not reveal this, you can query it from the client-server API of your homeserver. This lookup does not yet require authentication:

$ curl 'https://matrix.example.org/_matrix/client/v3/directory/room/%23awesome-room:example.org' | jq
{
  "room_id" "!udc6osfy94WuSou9QG:example.org",
  "servers": [
    "example.org",
    "..."
  ]
}

(API specification)

Note that you have to URL-encode the # symbol in the room alias as %23 to prevent it from being treated as the URL fragment separator.

With these two pieces of data we can now retrieve the latest event in the current room. We need this event (or rather, its event ID) to link the room we will create in the next step back to the "end" of the old room to allow users to jump between the rooms as seamlessly as possible:

curl -H 'Authorization: Bearer syt_QcG6Rup_QEBF9Wji2mQzbyXoSgTw_9LJhae' 'https://matrix.example.org/_matrix/client/v3/rooms/!udc6osfy94WuSou9QG:example.org/messages?limit=1&dir=b' | jq
{
  "chunk": [
    {
      "type": "...",
      "content": {...},
      "room_id": "!udc6osfy94WuSou9QG:example.org",
      "sender": "@otheruser:matrix.org",
      "user_id": "@otheruser:matrix.org",
      "event_id": "$F3IubwKHQRXqp3rtDQGeI9liQAkHjPf1ac/B3l3haL8"
    }
  ],
  "start": "...",
  "end": "..."
}

(API specification)

The query parameters limit=1 and dir=b make sure that only the very latest event is returned.

Note that depending on the version of the current room, the event ID might look differently, e.g. in the first two versions of the room specification, there was a homeserver name attached. In the following, use the event ID exactly as it appears in the response above.

Now we have everything to create our new room. I have yet to see a Matrix client that allows you to create a room with predecessor info, so we have to do this via an API call as well:

$ curl \
  -X POST \
  -H 'Authorization: Bearer syt_QcG6Rup_QEBF9Wji2mQzbyXoSgTw_9LJhae' \
  -H 'Content-Type: application/json' \
  -d '{"creation_content":{"predecessor":{"room_id":"!udc6osfy94WuSou9QG:example.org","event_id":"$F3IubwKHQRXqp3rtDQGeI9liQAkHjPf1ac/B3l3haL8"}}}' \
  'https://matrix.example.org/_matrix/client/v3/createRoom' | jq
{
  "room_id": "!By6MbLtNqw5RDiNi6C:example.org"
}

(API specification)

You should now have a new, empty, unencrypted room with only yourself in it show up in your Matrix client(s).

At this point, you should take the time to set up this new room to match your old one, including things like:

The room name, topic and avatar

Joining policies and history visibility

Remove any public aliases from the old room and assign them to the new one.

Change the permissions in the old room so that regular users can't send new messages.

All of these can of course be done through the API, but all of these features are easily available in most Matrix clients, so there is simply no need to bother researching and discussing the associated API calls here.

Once you are done with the setup of the new room, there's one last step left to do: Send a "tombstone" event to the old room that renders the room read-only and instructs users to join the new room instead:

$ curl \
  -X PUT \
  -H 'Authorization: Bearer syt_QcG6Rup_QEBF9Wji2mQzbyXoSgTw_9LJhae' \
  -H 'Content-Type: application/json' \
  -d '{"replacement_room":"!By6MbLtNqw5RDiNi6C:example.org","body":"New room without E2EE"}' \
  'https://matrix.example.org/_matrix/client/v3/rooms/!udc6osfy94WuSou9QG:example.org/state/m.room.tombstone' | jq
{
  "event_id": "$wouAIBuVELQG57bMl28dfsBDCr94vcjzuQZ9SM1LXkb"
}

(API specification) (Event specification)

And that's it! Your old room is now archived and points to the new room, while the new room retains a link back to the old one so that members can still view older messages:

Figure 1: Visualization of the tombstone event in the old Matrix room in Element, with a link pointing the user to the new room.

Figure 2: Visualization of the creation event in the new Matrix room in Element, with a link pointing the user to the old room.

Final Thoughts

Using these few API calls, it is comparably easy to manually replace a Matrix room with another one without going through the room upgrade API. However, if you attempt to follow this approach, I have two final recommendations:

First of all, like enabling room encryption, sending a tombstone event to a room cannot be undone. Therefore, I recommend you create a new dedicated room for testing this migration before going for the real thing. Ideally, invite a few friends to the room and exchange a few messages before sending the tombstone event.

Finally, during testing this myself, I have noticed that not all clients show the message in the tombstone event. So ideally, before sending the tombstone, you might want to consider sending a regular message informing your users of the change, what they need to do, and if everything fails, have them re-join the new room via its public alias.

# contentMap.en
In our hackerspace community, we recently ran into some issues with end-to-end encrypted Matrix rooms: In a previously unencrypted public room with around 100 users across lots of different home servers, a room admin had enabled end-to-end encryption by accident.

Unfortunately, end-to-end encryption in Matrix can still be unreliable at times, and encryption (or rather decryption) errors can be caused by a wide range of issues by both sender and recipient. Therefore we decided that we wanted to return to an unencrypted room.

Please note that this blog post should not be interpreted as advocacy against end-to-end encryption; in fact, I'm a huge fan of end-to-end encryption. However, this specific case is about a room that is public anyway, and where E2EE was actively leading to issues.

How to Disable End-to-end Encryption in Matrix?

The short answer is: You don't!

Once E2EE has been enabled in a Matrix room, there is no way to disable it. The only choice is to create an entirely new and unencrypted room and tell your users to join that room instead.

The good news is that Matrix makes it fairly easy for your users to join the new room; if done properly, it's only a single click or tap in most clients.

If you're asking yourself "Matrix has a simple room upgrade API, why can't you just use that", the answer is simple: The room upgrade API causes the server to replicate all room state events to the new room. This includes the "enable encryption" event as well, and you'd be left with a new, but encrypted room.

How to Replace a Room?

The first thing you'll need is a user with admin permissions (power level 100) in the existing room, and an access token for that user. An access token can usually be obtained quite easily, e.g. in Element you can export the access token under Settings / Advanced settings / Dev Tools / Access Token when Developer mode is enabled. An access token issued by Synapse looks like the following, other homeservers may issue them in a different format:

syt_QcG6Rup_QEBF9Wji2mQzbyXoSgTw_9LJhae

The next thing we'll need is the internal room ID of your Matrix room. If your client does not reveal this, you can query it from the client-server API of your homeserver. This lookup does not yet require authentication:

$ curl 'https://matrix.example.org/_matrix/client/v3/directory/room/%23awesome-room:example.org' | jq
{
  "room_id" "!udc6osfy94WuSou9QG:example.org",
  "servers": [
    "example.org",
    "..."
  ]
}

(API specification)

Note that you have to URL-encode the # symbol in the room alias as %23 to prevent it from being treated as the URL fragment separator.

With these two pieces of data we can now retrieve the latest event in the current room. We need this event (or rather, its event ID) to link the room we will create in the next step back to the "end" of the old room to allow users to jump between the rooms as seamlessly as possible:

curl -H 'Authorization: Bearer syt_QcG6Rup_QEBF9Wji2mQzbyXoSgTw_9LJhae' 'https://matrix.example.org/_matrix/client/v3/rooms/!udc6osfy94WuSou9QG:example.org/messages?limit=1&dir=b' | jq
{
  "chunk": [
    {
      "type": "...",
      "content": {...},
      "room_id": "!udc6osfy94WuSou9QG:example.org",
      "sender": "@otheruser:matrix.org",
      "user_id": "@otheruser:matrix.org",
      "event_id": "$F3IubwKHQRXqp3rtDQGeI9liQAkHjPf1ac/B3l3haL8"
    }
  ],
  "start": "...",
  "end": "..."
}

(API specification)

The query parameters limit=1 and dir=b make sure that only the very latest event is returned.

Note that depending on the version of the current room, the event ID might look differently, e.g. in the first two versions of the room specification, there was a homeserver name attached. In the following, use the event ID exactly as it appears in the response above.

Now we have everything to create our new room. I have yet to see a Matrix client that allows you to create a room with predecessor info, so we have to do this via an API call as well:

$ curl \
  -X POST \
  -H 'Authorization: Bearer syt_QcG6Rup_QEBF9Wji2mQzbyXoSgTw_9LJhae' \
  -H 'Content-Type: application/json' \
  -d '{"creation_content":{"predecessor":{"room_id":"!udc6osfy94WuSou9QG:example.org","event_id":"$F3IubwKHQRXqp3rtDQGeI9liQAkHjPf1ac/B3l3haL8"}}}' \
  'https://matrix.example.org/_matrix/client/v3/createRoom' | jq
{
  "room_id": "!By6MbLtNqw5RDiNi6C:example.org"
}

(API specification)

You should now have a new, empty, unencrypted room with only yourself in it show up in your Matrix client(s).

At this point, you should take the time to set up this new room to match your old one, including things like:

The room name, topic and avatar

Joining policies and history visibility

Remove any public aliases from the old room and assign them to the new one.

Change the permissions in the old room so that regular users can't send new messages.

All of these can of course be done through the API, but all of these features are easily available in most Matrix clients, so there is simply no need to bother researching and discussing the associated API calls here.

Once you are done with the setup of the new room, there's one last step left to do: Send a "tombstone" event to the old room that renders the room read-only and instructs users to join the new room instead:

$ curl \
  -X PUT \
  -H 'Authorization: Bearer syt_QcG6Rup_QEBF9Wji2mQzbyXoSgTw_9LJhae' \
  -H 'Content-Type: application/json' \
  -d '{"replacement_room":"!By6MbLtNqw5RDiNi6C:example.org","body":"New room without E2EE"}' \
  'https://matrix.example.org/_matrix/client/v3/rooms/!udc6osfy94WuSou9QG:example.org/state/m.room.tombstone' | jq
{
  "event_id": "$wouAIBuVELQG57bMl28dfsBDCr94vcjzuQZ9SM1LXkb"
}

(API specification) (Event specification)

And that's it! Your old room is now archived and points to the new room, while the new room retains a link back to the old one so that members can still view older messages:

Figure 1: Visualization of the tombstone event in the old Matrix room in Element, with a link pointing the user to the new room.

Figure 2: Visualization of the creation event in the new Matrix room in Element, with a link pointing the user to the old room.

Final Thoughts

Using these few API calls, it is comparably easy to manually replace a Matrix room with another one without going through the room upgrade API. However, if you attempt to follow this approach, I have two final recommendations:

First of all, like enabling room encryption, sending a tombstone event to a room cannot be undone. Therefore, I recommend you create a new dedicated room for testing this migration before going for the real thing. Ideally, invite a few friends to the room and exchange a few messages before sending the tombstone event.

Finally, during testing this myself, I have noticed that not all clients show the message in the tombstone event. So ideally, before sending the tombstone, you might want to consider sending a regular message informing your users of the change, what they need to do, and if everything fails, have them re-join the new room via its public alias.