Repository: ruma/ruma
Pull request #193 (2020-08-10 14:09:59)

Reorganize encryption related code
Some types used for encryption related tasks need to be used across more internal crates than expected, so a few have been moved and renamed for clarity.

* Rename the `key_algorithms` module in `ruma-identifiers-validation` to `crypto_algorithms`
* move `ruma_events::Algorithm` to `ruma-identifiers-validation` and rename it `EventEncryptionAlgorithm`
* move `DeviceKeys` from `ruma-client-api` to `ruma-common`


alexjg: I think this is a great issue to raise. It's already helped me as I was slightly confused on the status of the project vis a vis spec standardisation and async/await stuff so it may be worth clarifying that directly in the 'status' section on the project readme?

One of the reasons  I'm interested in this project is that I'm interested in running many independent matrix servers from within a Kubernetes cluster. Multi-tenant basically. I think that's covered in the goals you've mentioned above but I wonder if other people are also interested in this and whether there's any need to explicitly call it out?
exul: **Ruma homeserver and Ruma libraries**
"What Ruma is" mentions the libraries, whereas "Product focus" describes that developing the homeserver is the focus of the project. Which on one hand I understand, because the libraries were developed as part of the homeserver, on the other hand, I think way more projects would benefit from the libraries. I use the libraries myself and the clean code and the nice APIs are really helpful.
I also think that the libraries (especially the client-server ones) can adapt spec changes faster than the homeserver as a whole.
My main question here is: Will the library development be driven by the homeserver development or will the be developt more independently from the homeserver.
From what I've seen in the past, they were developed pretty independently and I like that.

**small and large scale deployments**
Supporting large scale deployments usually comes with the trade-off of complexity. Is the idea to support two architectures (the way dendrite does it) or have one that fits all deployments at the cost of setup complexity. Which boils down to complexity in the code vs. complexity in the setup.

**developers**
Not sure if this belongs here, but I've seen quite a few great open source projects getting abandoned. I think the only way to mitigate this is to have more developers on board. Are there specific plans to address this?
jimmycuadra: @alexjg Thanks for the feedback. My plan is to create dedicated pages on the website that present these goals and a status page to give a more high level overview of the status of the project.

In terms of your intended use case, that is absolutely a project goal. It's one of my personal use cases as well. I think you're right that a goals document should include a bit more information about what the supported use cases are.
jimmycuadra: @exul Great points. It's probably best to have "product focus" split into multiple sections for the applications and the libraries. It's confusing as written that Ruma "is" three different things but "focuses" on homeserver-specific things. For your main question, I think the answer is "separate," in the sense that we want the libraries to be driven by the use cases of the Rust/Matrix community in addition to the needs of the homeserver. In other words, we'll never close a pull request on a library because the change is not specifically needed by the homeserver. That doesn't mean that integration in the homeserver won't be taken into account. But overall the libraries should have a broader raison d'être than "implementation details of the homeserver that happen to be separate crates."

Re: scale of deployment, my current thinking is that we follow the same approach as Dendrite where there is an all-in-one binary for small deployments and multiple binaries for different parts of the system for large deployments. Some of this is already explained on the website's project pages (specifically the "[homeserver comes in two flavors](https://www.ruma.io/projects/)" part) but it makes sense for it to be explained in the goals document too.

Re: sustainability of development, this is a problem with open source software in general. I don't have a magic solution, but some things that may help:

1. Make the project itself useful to lots of people. The bigger the the pool of users, the bigger the pool of potential contributors.
2. Provide guides aimed at developers explaining the architecture of the project, as well as good contribution guidelines.
3. Make major contributors to the project more visible to both show our appreciation for their work, and to let people just discovering the project that it's more than the work of just one dude. This may inspire confidence that the project is more sustainable.
exul: Regarding sustainability: You already did a great job by adding frequent contributes to the Ruma organization in a very early stage. Mentioning in the README that contributions from less experienced developers are welcome might encourage them to contribute. Maybe we can even offer a mentor program for some issue (kind of the same way that rust does it).
Another possibility would be to have "office hours" (I shamelessly stole the idea from kops: https://github.com/kubernetes/kops/#office-hours) where new contributors can get help not only via chat but on a call.
All this requires additional time that is "non-development" time, so I think it makes sense to discuss up-front how much the existing community can and wants to invest.
erlend-sh: Being a Rust equivalent to the other Matrix homeservers is already of great value, but in order to see adoption outside of the Rust community it'd be beneficial for Ruma to target some specific use cases that aren't currently being addressed by the Matrix ecosystem at large.

My main gripe with Matrix is the lack of a [straight forward registration flow](https://github.com/matrix-org/matrix-doc/issues/578#issuecomment-265150801). Matrix as-is could easily be providing a better service than the barely-maintained Gitter.im, but there seems to be no interest in supporting "social logins" through the likes of GitHub and GitLab.

The ability to use other applications as a parent userbase is also lacking. users.rust-lang.org could easily be set up so that anyone who signs up for an account there also gets an account on the official Rust Rocket.Chat or Mattermost chat hub (if such a thing existed), but such a setup is far from easy with Matrix.

Thus my wishlist for special homeserver features in Ruma would be as follows:

- Some equivalent of [OmniAuth](https://github.com/omniauth) built into Ruma
- Ruma as the child of a SSO parent - [Example in Discourse](https://meta.discourse.org/t/official-single-sign-on-for-discourse-sso/13045)
- Ruma as the parent of a SSO child - [Example in Discourse](https://meta.discourse.org/t/using-discourse-as-a-sso-provider/32974)
msrd0: I completely agree with @erlend-sh that it must be far easier to use matrix for users outside the it section. Usually, when I ask them to install matrix, they download the app and go like _Hey, why do I need to choose a user id and all that kind of stuff, I just want to enter my phone number and have people find me using that_. That said, I know that there is support for 3rd party logins like email or phone numbers, but it is far from useful by now.

If ruma could support signing up using nothing other than a phone number to get started and allowed to set a username later ruma could add some great value and possible users.
nikhiljha: - Developer Onboarding

I really like the idea of having a set time each week/month where newer contributors can get help in a call/chat. Starting with a new codebase often leads to a lot of "stupid" questions relating to how things work, and having those resolved quickly would definitely help people who want to contribute but don't know where to start. Yes, it is "non-development" time, but I think it would be just as productive as actually programming something. 

- Alternate Signon Methods

The good news is that even with the regular identity server, first you verify your email/phone number and then you bind it to an address. This means that you can make the signup flow go almost exactly like Telegram/WhatsApp (enter phone number, enter verification code, enter name, optional fields to enter username and password).

What Ruma would have to add to make it actually work is a way to authenticate users based on a one time code sent to another session (Telegram) or an SMS to the phone number (WhatsApp). If this is done, I would really like it to be a plugin of some sort. Synapse is super slow and resource-hungry, and I really don't want Ruma to also end up like that.

An easier thing to implement would be oAuth (as erlend-sh mentioned). Having a configuration option that lets you list OAuth providers would be really nice and would allow Ruma clients to be just as easy to use as most popular chat applications.
jonbayer: sooo, async/await is settled. or did this project kinda lose steam while waiting. 
jimmycuadra: async/await is not the only thing we're waiting for. It's a foundational language feature that is necessary for the development of the library ecosystem around it. There's still a lot that needs to happen. Even async/await itself was released as an "MVP" of the feature, with noticeable omissions, like the ability to use `async fn` in trait methods.

Patch:
diff --git a/ruma-client-api/src/r0/keys.rs b/ruma-client-api/src/r0/keys.rs
index 288d2ed9..18047000 100644
--- a/ruma-client-api/src/r0/keys.rs
+++ b/ruma-client-api/src/r0/keys.rs
@@ -2,8 +2,7 @@
 
 use std::{collections::BTreeMap, fmt::Debug};
 
-use ruma_events::Algorithm;
-use ruma_identifiers::{DeviceId, DeviceKeyId, UserId};
+use ruma_identifiers::{DeviceKeyId, UserId};
 use serde::{Deserialize, Serialize};
 
 pub mod claim_keys;
@@ -16,37 +15,6 @@ pub mod upload_signatures;
 #[cfg(feature = "unstable-pre-spec")]
 pub mod upload_signing_keys;
 
-/// Identity keys for a device.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct DeviceKeys {
-    /// The ID of the user the device belongs to. Must match the user ID used when logging in.
-    pub user_id: UserId,
-
-    /// The ID of the device these keys belong to. Must match the device ID used when logging in.
-    pub device_id: Box<DeviceId>,
-
-    /// The encryption algorithms supported by this device.
-    pub algorithms: Vec<Algorithm>,
-
-    /// Public identity keys.
-    pub keys: BTreeMap<DeviceKeyId, String>,
-
-    /// Signatures for the device key object.
-    pub signatures: BTreeMap<UserId, BTreeMap<DeviceKeyId, String>>,
-
-    /// Additional data added to the device key information by intermediate servers, and
-    /// not covered by the signatures.
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub unsigned: Option<UnsignedDeviceInfo>,
-}
-
-/// Additional data added to device key information by intermediate servers.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct UnsignedDeviceInfo {
-    /// The display name which the user set on the device.
-    pub device_display_name: Option<String>,
-}
-
 /// A key for the SignedCurve25519 algorithm
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct SignedKey {
diff --git a/ruma-client-api/src/r0/keys/get_keys.rs b/ruma-client-api/src/r0/keys/get_keys.rs
index c93b69b7..029fe290 100644
--- a/ruma-client-api/src/r0/keys/get_keys.rs
+++ b/ruma-client-api/src/r0/keys/get_keys.rs
@@ -3,12 +3,12 @@
 use std::{collections::BTreeMap, time::Duration};
 
 use ruma_api::ruma_api;
+use ruma_common::encryption::DeviceKeys;
 use ruma_identifiers::{DeviceId, UserId};
 use serde_json::Value as JsonValue;
 
 #[cfg(feature = "unstable-pre-spec")]
 use super::CrossSigningKey;
-use super::DeviceKeys;
 
 ruma_api! {
     metadata: {
diff --git a/ruma-client-api/src/r0/keys/upload_keys.rs b/ruma-client-api/src/r0/keys/upload_keys.rs
index 43684808..61457177 100644
--- a/ruma-client-api/src/r0/keys/upload_keys.rs
+++ b/ruma-client-api/src/r0/keys/upload_keys.rs
@@ -4,9 +4,10 @@ use std::collections::BTreeMap;
 
 use js_int::UInt;
 use ruma_api::ruma_api;
+use ruma_common::encryption::DeviceKeys;
 use ruma_identifiers::{DeviceKeyAlgorithm, DeviceKeyId};
 
-use super::{DeviceKeys, OneTimeKey};
+use super::OneTimeKey;
 
 ruma_api! {
     metadata: {
diff --git a/ruma-common/src/encryption.rs b/ruma-common/src/encryption.rs
new file mode 100644
index 00000000..9be2010a
--- /dev/null
+++ b/ruma-common/src/encryption.rs
@@ -0,0 +1,39 @@
+//! Common types for [encryption] related tasks.
+//!
+//! [encryption](https://matrix.org/docs/spec/client_server/r0.6.1#id62)
+
+use std::collections::BTreeMap;
+
+use ruma_identifiers::{DeviceId, DeviceKeyId, EventEncryptionAlgorithm, UserId};
+use serde::{Deserialize, Serialize};
+
+/// Identity keys for a device.
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct DeviceKeys {
+    /// The ID of the user the device belongs to. Must match the user ID used when logging in.
+    pub user_id: UserId,
+
+    /// The ID of the device these keys belong to. Must match the device ID used when logging in.
+    pub device_id: Box<DeviceId>,
+
+    /// The encryption algorithms supported by this device.
+    pub algorithms: Vec<EventEncryptionAlgorithm>,
+
+    /// Public identity keys.
+    pub keys: BTreeMap<DeviceKeyId, String>,
+
+    /// Signatures for the device key object.
+    pub signatures: BTreeMap<UserId, BTreeMap<DeviceKeyId, String>>,
+
+    /// Additional data added to the device key information by intermediate servers, and
+    /// not covered by the signatures.
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub unsigned: Option<UnsignedDeviceInfo>,
+}
+
+/// Additional data added to device key information by intermediate servers.
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct UnsignedDeviceInfo {
+    /// The display name which the user set on the device.
+    pub device_display_name: Option<String>,
+}
diff --git a/ruma-common/src/lib.rs b/ruma-common/src/lib.rs
index 3f738d0a..a4faa72f 100644
--- a/ruma-common/src/lib.rs
+++ b/ruma-common/src/lib.rs
@@ -2,6 +2,7 @@
 
 #![warn(missing_docs, missing_debug_implementations)]
 
+pub mod encryption;
 pub mod presence;
 pub mod push;
 mod raw;
diff --git a/ruma-events/src/algorithm.rs b/ruma-events/src/algorithm.rs
deleted file mode 100644
index 389ee0e0..00000000
--- a/ruma-events/src/algorithm.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-use std::fmt::{Display, Formatter, Result as FmtResult};
-
-use serde::{Deserialize, Serialize};
-
-/// An encryption algorithm to be used to encrypt messages sent to a room.
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-#[non_exhaustive]
-#[serde(from = "String", into = "String")]
-pub enum Algorithm {
-    /// Olm version 1 using Curve25519, AES-256, and SHA-256.
-    OlmV1Curve25519AesSha2,
-
-    /// Megolm version 1 using AES-256 and SHA-256.
-    MegolmV1AesSha2,
-
-    /// Any algorithm that is not part of the specification.
-    Custom(String),
-}
-
-impl Display for Algorithm {
-    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
-        let algorithm_str = match *self {
-            Algorithm::OlmV1Curve25519AesSha2 => "m.olm.v1.curve25519-aes-sha2",
-            Algorithm::MegolmV1AesSha2 => "m.megolm.v1.aes-sha2",
-            Algorithm::Custom(ref algorithm) => algorithm,
-        };
-
-        write!(f, "{}", algorithm_str)
-    }
-}
-
-impl<T> From<T> for Algorithm
-where
-    T: Into<String> + AsRef<str>,
-{
-    fn from(s: T) -> Algorithm {
-        match s.as_ref() {
-            "m.olm.v1.curve25519-aes-sha2" => Algorithm::OlmV1Curve25519AesSha2,
-            "m.megolm.v1.aes-sha2" => Algorithm::MegolmV1AesSha2,
-            _ => Algorithm::Custom(s.into()),
-        }
-    }
-}
-
-impl From<Algorithm> for String {
-    fn from(algorithm: Algorithm) -> String {
-        algorithm.to_string()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use ruma_serde::test::serde_json_eq;
-    use serde_json::json;
-
-    use super::Algorithm;
-
-    #[test]
-    fn serialize_and_deserialize_from_display_form() {
-        serde_json_eq(Algorithm::MegolmV1AesSha2, json!("m.megolm.v1.aes-sha2"));
-        serde_json_eq(Algorithm::OlmV1Curve25519AesSha2, json!("m.olm.v1.curve25519-aes-sha2"));
-        serde_json_eq(Algorithm::Custom("io.ruma.test".into()), json!("io.ruma.test"));
-    }
-}
diff --git a/ruma-events/src/forwarded_room_key.rs b/ruma-events/src/forwarded_room_key.rs
index c8edd0b8..20aa3cef 100644
--- a/ruma-events/src/forwarded_room_key.rs
+++ b/ruma-events/src/forwarded_room_key.rs
@@ -1,10 +1,9 @@
 //! Types for the *m.forwarded_room_key* event.
 
 use ruma_events_macros::BasicEventContent;
-use ruma_identifiers::RoomId;
+use ruma_identifiers::{EventEncryptionAlgorithm, RoomId};
 use serde::{Deserialize, Serialize};
 
-use super::Algorithm;
 use crate::BasicEvent;
 
 /// This event type is used to forward keys for end-to-end encryption.
@@ -17,7 +16,7 @@ pub type ForwardedRoomKeyEvent = BasicEvent<ForwardedRoomKeyEventContent>;
 #[ruma_event(type = "m.forwarded_room_key")]
 pub struct ForwardedRoomKeyEventContent {
     /// The encryption algorithm the key in this event is to be used with.
-    pub algorithm: Algorithm,
+    pub algorithm: EventEncryptionAlgorithm,
 
     /// The room where the key is used.
     pub room_id: RoomId,
diff --git a/ruma-events/src/lib.rs b/ruma-events/src/lib.rs
index 446b1798..5935b830 100644
--- a/ruma-events/src/lib.rs
+++ b/ruma-events/src/lib.rs
@@ -121,7 +121,7 @@ use std::fmt::Debug;
 
 use js_int::Int;
 use ruma_common::Raw;
-use ruma_identifiers::RoomId;
+use ruma_identifiers::{EventEncryptionAlgorithm, RoomId};
 use serde::{
     de::{self, IgnoredAny},
     Deserialize, Serialize,
@@ -130,7 +130,6 @@ use serde_json::value::RawValue as RawJsonValue;
 
 use self::room::redaction::{RedactionEvent, SyncRedactionEvent};
 
-mod algorithm;
 mod enums;
 mod error;
 mod event_kinds;
@@ -160,7 +159,6 @@ pub mod tag;
 pub mod typing;
 
 pub use self::{
-    algorithm::Algorithm,
     enums::{
         AnyBasicEvent, AnyBasicEventContent, AnyEphemeralRoomEvent, AnyEphemeralRoomEventContent,
         AnyEvent, AnyMessageEvent, AnyMessageEventContent, AnyPossiblyRedactedMessageEvent,
diff --git a/ruma-events/src/room/encryption.rs b/ruma-events/src/room/encryption.rs
index 2de42ecc..4cba8d6c 100644
--- a/ruma-events/src/room/encryption.rs
+++ b/ruma-events/src/room/encryption.rs
@@ -4,7 +4,7 @@ use js_int::UInt;
 use ruma_events_macros::StateEventContent;
 use serde::{Deserialize, Serialize};
 
-use crate::{Algorithm, StateEvent};
+use crate::{EventEncryptionAlgorithm, StateEvent};
 
 /// Defines how messages sent in this room should be encrypted.
 pub type EncryptionEvent = StateEvent<EncryptionEventContent>;
@@ -17,7 +17,7 @@ pub struct EncryptionEventContent {
     /// The encryption algorithm to be used to encrypt messages sent in this room.
     ///
     /// Must be `m.megolm.v1.aes-sha2`.
-    pub algorithm: Algorithm,
+    pub algorithm: EventEncryptionAlgorithm,
 
     /// How long the session should be used before changing it.
     ///
@@ -34,7 +34,7 @@ pub struct EncryptionEventContent {
 
 impl EncryptionEventContent {
     /// Creates a new `EncryptionEventContent` with the given algorithm.
-    pub fn new(algorithm: Algorithm) -> Self {
+    pub fn new(algorithm: EventEncryptionAlgorithm) -> Self {
         Self { algorithm, rotation_period_ms: None, rotation_period_msgs: None }
     }
 }
diff --git a/ruma-events/src/room_key.rs b/ruma-events/src/room_key.rs
index 786959ec..894b78c5 100644
--- a/ruma-events/src/room_key.rs
+++ b/ruma-events/src/room_key.rs
@@ -1,10 +1,9 @@
 //! Types for the *m.room_key* event.
 
 use ruma_events_macros::BasicEventContent;
-use ruma_identifiers::RoomId;
+use ruma_identifiers::{EventEncryptionAlgorithm, RoomId};
 use serde::{Deserialize, Serialize};
 
-use super::Algorithm;
 use crate::BasicEvent;
 
 /// This event type is used to exchange keys for end-to-end encryption.
@@ -19,7 +18,7 @@ pub struct RoomKeyEventContent {
     /// The encryption algorithm the key in this event is to be used with.
     ///
     /// Must be `m.megolm.v1.aes-sha2`.
-    pub algorithm: Algorithm,
+    pub algorithm: EventEncryptionAlgorithm,
 
     /// The room where the key is used.
     pub room_id: RoomId,
@@ -33,17 +32,17 @@ pub struct RoomKeyEventContent {
 
 #[cfg(test)]
 mod tests {
-    use ruma_identifiers::room_id;
+    use ruma_identifiers::{room_id, EventEncryptionAlgorithm};
     use serde_json::{json, to_value as to_json_value};
 
     use super::RoomKeyEventContent;
-    use crate::{Algorithm, BasicEvent};
+    use crate::BasicEvent;
 
     #[test]
     fn serialization() {
         let ev = BasicEvent {
             content: RoomKeyEventContent {
-                algorithm: Algorithm::MegolmV1AesSha2,
+                algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
                 room_id: room_id!("!testroomid:example.org"),
                 session_id: "SessId".into(),
                 session_key: "SessKey".into(),
diff --git a/ruma-events/src/room_key_request.rs b/ruma-events/src/room_key_request.rs
index 1d748ff0..eccafb2c 100644
--- a/ruma-events/src/room_key_request.rs
+++ b/ruma-events/src/room_key_request.rs
@@ -1,11 +1,10 @@
 //! Types for the *m.room_key_request* event.
 
 use ruma_events_macros::BasicEventContent;
-use ruma_identifiers::{DeviceId, RoomId};
+use ruma_identifiers::{DeviceId, EventEncryptionAlgorithm, RoomId};
 use serde::{Deserialize, Serialize};
 use strum::{Display, EnumString};
 
-use super::Algorithm;
 use crate::BasicEvent;
 
 /// This event type is used to request keys for end-to-end encryption.
@@ -54,7 +53,7 @@ pub enum Action {
 #[derive(Clone, Debug, Deserialize, Serialize)]
 pub struct RequestedKeyInfo {
     /// The encryption algorithm the requested key in this event is to be used with.
-    pub algorithm: Algorithm,
+    pub algorithm: EventEncryptionAlgorithm,
 
     /// The room where the key is used.
     pub room_id: RoomId,
diff --git a/ruma-identifiers-validation/Cargo.toml b/ruma-identifiers-validation/Cargo.toml
index 37cb07fa..3c90880e 100644
--- a/ruma-identifiers-validation/Cargo.toml
+++ b/ruma-identifiers-validation/Cargo.toml
@@ -16,5 +16,7 @@ edition = "2018"
 default = ["serde"]
 
 [dependencies]
+ruma-serde = { version = "0.2.3", path = "../ruma-serde" }
 serde = { version = "1.0.114", optional = true, features = ["derive"] }
+serde_json = "1.0.57"
 strum = { version = "0.19.2", features = ["derive"] }
diff --git a/ruma-identifiers-validation/src/key_algorithms.rs b/ruma-identifiers-validation/src/crypto_algorithms.rs
similarity index 50%
rename from ruma-identifiers-validation/src/key_algorithms.rs
rename to ruma-identifiers-validation/src/crypto_algorithms.rs
index 5d4c95d1..00da190c 100644
--- a/ruma-identifiers-validation/src/key_algorithms.rs
+++ b/ruma-identifiers-validation/src/crypto_algorithms.rs
@@ -1,6 +1,7 @@
 //! Key algorithms used in Matrix spec.
 
 use std::convert::TryFrom;
+use std::fmt::{Display, Formatter, Result as FmtResult};
 
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
@@ -65,9 +66,62 @@ impl TryFrom<String> for ServerKeyAlgorithm {
     }
 }
 
+/// An encryption algorithm to be used to encrypt messages sent to a room.
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(
+    feature = "serde",
+    derive(Deserialize, Serialize),
+    serde(from = "String", into = "String")
+)]
+#[non_exhaustive]
+pub enum EventEncryptionAlgorithm {
+    /// Olm version 1 using Curve25519, AES-256, and SHA-256.
+    OlmV1Curve25519AesSha2,
+
+    /// Megolm version 1 using AES-256 and SHA-256.
+    MegolmV1AesSha2,
+
+    /// Any algorithm that is not part of the specification.
+    Custom(String),
+}
+
+impl Display for EventEncryptionAlgorithm {
+    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+        let algorithm_str = match *self {
+            EventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => "m.olm.v1.curve25519-aes-sha2",
+            EventEncryptionAlgorithm::MegolmV1AesSha2 => "m.megolm.v1.aes-sha2",
+            EventEncryptionAlgorithm::Custom(ref algorithm) => algorithm,
+        };
+
+        write!(f, "{}", algorithm_str)
+    }
+}
+
+impl<T> From<T> for EventEncryptionAlgorithm
+where
+    T: Into<String> + AsRef<str>,
+{
+    fn from(s: T) -> EventEncryptionAlgorithm {
+        match s.as_ref() {
+            "m.olm.v1.curve25519-aes-sha2" => EventEncryptionAlgorithm::OlmV1Curve25519AesSha2,
+            "m.megolm.v1.aes-sha2" => EventEncryptionAlgorithm::MegolmV1AesSha2,
+            _ => EventEncryptionAlgorithm::Custom(s.into()),
+        }
+    }
+}
+
+impl From<EventEncryptionAlgorithm> for String {
+    fn from(algorithm: EventEncryptionAlgorithm) -> String {
+        algorithm.to_string()
+    }
+}
+
 #[cfg(test)]
 mod tests {
-    use super::{DeviceKeyAlgorithm, ServerKeyAlgorithm};
+    use ruma_serde::test::serde_json_eq;
+    use serde_json::json;
+
+    use super::{DeviceKeyAlgorithm, EventEncryptionAlgorithm, ServerKeyAlgorithm};
 
     #[test]
     fn parse_device_key_algorithm() {
@@ -80,4 +134,17 @@ mod tests {
     fn parse_server_key_algorithm() {
         assert_eq!("ed25519".parse(), Ok(ServerKeyAlgorithm::Ed25519));
     }
+
+    #[test]
+    fn event_encryption_algorithm_serde() {
+        serde_json_eq(EventEncryptionAlgorithm::MegolmV1AesSha2, json!("m.megolm.v1.aes-sha2"));
+        serde_json_eq(
+            EventEncryptionAlgorithm::OlmV1Curve25519AesSha2,
+            json!("m.olm.v1.curve25519-aes-sha2"),
+        );
+        serde_json_eq(
+            EventEncryptionAlgorithm::Custom("io.ruma.test".into()),
+            json!("io.ruma.test"),
+        );
+    }
 }
diff --git a/ruma-identifiers-validation/src/device_key_id.rs b/ruma-identifiers-validation/src/device_key_id.rs
index 596651c4..a94c733c 100644
--- a/ruma-identifiers-validation/src/device_key_id.rs
+++ b/ruma-identifiers-validation/src/device_key_id.rs
@@ -1,6 +1,6 @@
 use std::{num::NonZeroU8, str::FromStr};
 
-use crate::{key_algorithms::DeviceKeyAlgorithm, Error};
+use crate::{crypto_algorithms::DeviceKeyAlgorithm, Error};
 
 pub fn validate(s: &str) -> Result<NonZeroU8, Error> {
     let colon_idx = NonZeroU8::new(s.find(':').ok_or(Error::MissingDeviceKeyDelimiter)? as u8)
diff --git a/ruma-identifiers-validation/src/lib.rs b/ruma-identifiers-validation/src/lib.rs
index 85785373..48a40a27 100644
--- a/ruma-identifiers-validation/src/lib.rs
+++ b/ruma-identifiers-validation/src/lib.rs
@@ -1,7 +1,7 @@
+pub mod crypto_algorithms;
 pub mod device_key_id;
 pub mod error;
 pub mod event_id;
-pub mod key_algorithms;
 pub mod room_alias_id;
 pub mod room_id;
 pub mod room_id_or_alias_id;
diff --git a/ruma-identifiers-validation/src/server_key_id.rs b/ruma-identifiers-validation/src/server_key_id.rs
index b3982acd..05a92ff3 100644
--- a/ruma-identifiers-validation/src/server_key_id.rs
+++ b/ruma-identifiers-validation/src/server_key_id.rs
@@ -1,6 +1,6 @@
 use std::{num::NonZeroU8, str::FromStr};
 
-use crate::{key_algorithms::ServerKeyAlgorithm, Error};
+use crate::{crypto_algorithms::ServerKeyAlgorithm, Error};
 
 pub fn validate(s: &str) -> Result<NonZeroU8, Error> {
     let colon_idx = NonZeroU8::new(s.find(':').ok_or(Error::MissingServerKeyDelimiter)? as u8)
diff --git a/ruma-identifiers/src/device_key_id.rs b/ruma-identifiers/src/device_key_id.rs
index c9a81f6c..89f37033 100644
--- a/ruma-identifiers/src/device_key_id.rs
+++ b/ruma-identifiers/src/device_key_id.rs
@@ -2,7 +2,7 @@
 
 use std::{convert::TryInto, num::NonZeroU8, str::FromStr};
 
-use ruma_identifiers_validation::{key_algorithms::DeviceKeyAlgorithm, Error};
+use ruma_identifiers_validation::{crypto_algorithms::DeviceKeyAlgorithm, Error};
 
 use crate::DeviceId;
 
@@ -56,7 +56,7 @@ common_impls!(DeviceKeyId, try_from, "Device key ID with algorithm and device ID
 mod test {
     use std::convert::TryFrom;
 
-    use ruma_identifiers_validation::{key_algorithms::DeviceKeyAlgorithm, Error};
+    use ruma_identifiers_validation::{crypto_algorithms::DeviceKeyAlgorithm, Error};
     #[cfg(feature = "serde")]
     use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
 
diff --git a/ruma-identifiers/src/lib.rs b/ruma-identifiers/src/lib.rs
index 5a9d5bb7..09242c78 100644
--- a/ruma-identifiers/src/lib.rs
+++ b/ruma-identifiers/src/lib.rs
@@ -22,8 +22,8 @@ pub use crate::{
 };
 #[doc(inline)]
 pub use ruma_identifiers_validation::{
+    crypto_algorithms::{DeviceKeyAlgorithm, EventEncryptionAlgorithm, ServerKeyAlgorithm},
     error::Error,
-    key_algorithms::{DeviceKeyAlgorithm, ServerKeyAlgorithm},
 };
 
 #[macro_use]
diff --git a/ruma-identifiers/src/server_key_id.rs b/ruma-identifiers/src/server_key_id.rs
index 0d4ad725..ce7caf4a 100644
--- a/ruma-identifiers/src/server_key_id.rs
+++ b/ruma-identifiers/src/server_key_id.rs
@@ -2,7 +2,7 @@
 
 use std::{convert::TryInto, num::NonZeroU8, str::FromStr};
 
-use ruma_identifiers_validation::{key_algorithms::ServerKeyAlgorithm, Error};
+use ruma_identifiers_validation::{crypto_algorithms::ServerKeyAlgorithm, Error};
 
 /// Key identifiers used for homeserver signing keys.
 #[derive(Clone, Debug)]
@@ -59,7 +59,7 @@ mod tests {
     use crate::{Error, ServerKeyId};
 
     #[cfg(feature = "serde")]
-    use ruma_identifiers_validation::key_algorithms::ServerKeyAlgorithm;
+    use ruma_identifiers_validation::crypto_algorithms::ServerKeyAlgorithm;
 
     #[cfg(feature = "serde")]
     #[test]


Test patch:
diff --git a/ruma-events/tests/to_device.rs b/ruma-events/tests/to_device.rs
index 4a3ca6fa..f3df7153 100644
--- a/ruma-events/tests/to_device.rs
+++ b/ruma-events/tests/to_device.rs
@@ -1,7 +1,5 @@
-use ruma_events::{
-    room_key::RoomKeyEventContent, Algorithm, AnyToDeviceEventContent, ToDeviceEvent,
-};
-use ruma_identifiers::{room_id, user_id};
+use ruma_events::{room_key::RoomKeyEventContent, AnyToDeviceEventContent, ToDeviceEvent};
+use ruma_identifiers::{room_id, user_id, EventEncryptionAlgorithm};
 use serde_json::{json, to_value as to_json_value};
 
 #[test]
@@ -9,7 +7,7 @@ fn serialization() {
     let ev = ToDeviceEvent {
         sender: user_id!("@example:example.org"),
         content: AnyToDeviceEventContent::RoomKey(RoomKeyEventContent {
-            algorithm: Algorithm::MegolmV1AesSha2,
+            algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
             room_id: room_id!("!testroomid:example.org"),
             session_id: "SessId".into(),
             session_key: "SessKey".into(),