diff --git a/assets/scss/_styles_project.scss b/assets/scss/_styles_project.scss index 772b84120..9739f239e 100644 --- a/assets/scss/_styles_project.scss +++ b/assets/scss/_styles_project.scss @@ -502,6 +502,13 @@ Make padding symmetrical (this selector is used in the default styles to apply p } } +/* Adjust the width of math to match normal paragraphs */ +@include media-breakpoint-up(lg) { + .katex-display { + max-width: 80%; + } +} + /* Adjust default styles for info banner */ .pageinfo-primary { @include media-breakpoint-up(lg) { diff --git a/changelogs/internal/newsfragments/2226.clarification b/changelogs/internal/newsfragments/2226.clarification new file mode 100644 index 000000000..687588185 --- /dev/null +++ b/changelogs/internal/newsfragments/2226.clarification @@ -0,0 +1 @@ +Inline Olm & Megolm specifications. diff --git a/config/_default/hugo.toml b/config/_default/hugo.toml index 400643b24..5bf2f8c6e 100644 --- a/config/_default/hugo.toml +++ b/config/_default/hugo.toml @@ -43,6 +43,12 @@ description = "Home of the Matrix specification for decentralised communication" [markup.goldmark.renderer] # Enables us to render raw HTML unsafe = true + [markup.goldmark.extensions] + [markup.goldmark.extensions.passthrough] + enable = true + [markup.goldmark.extensions.passthrough.delimiters] + block = [['\[', '\]']] + inline = [['\(', '\)']] [markup.highlight] # See a complete list of available styles at https://xyproto.github.io/splash/docs/all.html # If the style is changed, remember to regenerate the CSS with: @@ -121,7 +127,7 @@ sidebar_menu_compact = true [[server.headers]] for = '/**' [server.headers.values] - Content-Security-Policy = "default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' data:; connect-src 'self'; font-src 'self' data:; media-src 'self'; child-src 'self'; form-action 'self'; object-src 'self'" + Content-Security-Policy = "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; img-src 'self' data:; connect-src 'self'; font-src 'self' data:; media-src 'self'; child-src 'self'; form-action 'self'; object-src 'self'" X-XSS-Protection = "1; mode=block" X-Content-Type-Options = "nosniff" # Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload" diff --git a/content/_index.md b/content/_index.md index 849409b5b..4c52fe473 100644 --- a/content/_index.md +++ b/content/_index.md @@ -151,7 +151,7 @@ request. How data flows between clients: -``` +```nohighlight { Matrix client A } { Matrix client B } ^ | ^ | | events | Client-Server API | events | diff --git a/content/appendices.md b/content/appendices.md index 923f9315a..52bb79fb5 100644 --- a/content/appendices.md +++ b/content/appendices.md @@ -749,13 +749,13 @@ history (a permalink). The Matrix URI scheme is defined as follows (`[]` enclose optional parts, `{}` enclose variables): -``` +```nohighlight matrix:[//{authority}/]{type}/{id without sigil}[/{type}/{id without sigil}...][?{query}][#{fragment}] ``` As a schema, this can be represented as: -``` +```nohighlight MatrixURI = "matrix:" hier-part [ "?" query ] [ "#" fragment ] hier-part = [ "//" authority "/" ] path path = entity-descriptor ["/" entity-descriptor] @@ -865,7 +865,7 @@ below for more details. A matrix.to URI has the following format, based upon the specification defined in [RFC 3986](https://tools.ietf.org/html/rfc3986): -``` +```nohighlight https://matrix.to/#//? ``` diff --git a/content/application-service-api.md b/content/application-service-api.md index 46c3efca1..48f1c5332 100644 --- a/content/application-service-api.md +++ b/content/application-service-api.md @@ -178,13 +178,13 @@ The application service API provides a transaction API for sending a list of events. Each list of events includes a transaction ID, which works as follows: -``` +```nohighlight Typical HS ---> AS : Homeserver sends events with transaction ID T. <--- : Application Service sends back 200 OK. ``` -``` +```nohighlight AS ACK Lost HS ---> AS : Homeserver sends events with transaction ID T. <-/- : AS 200 OK is lost. @@ -258,7 +258,7 @@ have been omitted for brevity): **Typical** -``` +```nohighlight AS ---> HS : /_matrix/client/v1/appservice/{appserviceId}/ping {"transaction_id": "meow"} HS ---> AS : /_matrix/app/v1/ping {"transaction_id": "meow"} HS <--- AS : 200 OK {} @@ -267,7 +267,7 @@ AS <--- HS : 200 OK {"duration_ms": 123} **Incorrect `hs_token`** -``` +```nohighlight AS ---> HS : /_matrix/client/v1/appservice/{appserviceId}/ping {"transaction_id": "meow"} HS ---> AS : /_matrix/app/v1/ping {"transaction_id": "meow"} HS <--- AS : 403 Forbidden {"errcode": "M_FORBIDDEN"} @@ -276,7 +276,7 @@ AS <--- HS : 502 Bad Gateway {"errcode": "M_BAD_STATUS", "status": 403, "body": **Can't connect to appservice** -``` +```nohighlight AS ---> HS : /_matrix/client/v1/appservice/{appserviceId}/ping {"transaction_id": "meow"} HS -/-> AS : /_matrix/app/v1/ping {"transaction_id": "meow"} AS <--- HS : 502 Bad Gateway {"errcode": "M_CONNECTION_FAILED"} diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index 69757086c..ad04f22cc 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -683,7 +683,7 @@ request parameter. A client should first make a request with no `auth` parameter. The homeserver returns an HTTP 401 response, with a JSON body, as follows: -``` +```nohighlight HTTP/1.1 401 Unauthorized Content-Type: application/json ``` @@ -729,7 +729,7 @@ given. It also contains other keys dependent on the auth type being attempted. For example, if the client is attempting to complete auth type `example.type.foo`, it might submit something like this: -``` +```nohighlight POST /_matrix/client/v3/endpoint HTTP/1.1 Content-Type: application/json ``` @@ -752,7 +752,7 @@ along with the same object as when no authentication was attempted, with the addition of the `completed` key which is an array of auth types the client has completed successfully: -``` +```nohighlight HTTP/1.1 401 Unauthorized Content-Type: application/json ``` @@ -786,7 +786,7 @@ but the client may make a second attempt, it returns the same HTTP status 401 response as above, with the addition of the standard `errcode` and `error` fields describing the error. For example: -``` +```nohighlight HTTP/1.1 401 Unauthorized Content-Type: application/json ``` @@ -816,7 +816,7 @@ Content-Type: application/json If the request fails for a reason other than authentication, the server returns an error message in the standard format. For example: -``` +```nohighlight HTTP/1.1 400 Bad request Content-Type: application/json ``` @@ -855,7 +855,7 @@ must still give a 401 response to requests with no auth data. At a high level, the requests made for an API call completing an auth flow with three stages will resemble the following diagram: -``` +```nohighlight _______________________ | Stage 0 | | No auth | @@ -913,7 +913,7 @@ This specification defines the following auth types: To use this authentication type, clients should submit an auth dict as follows: -``` +```nohighlight { "type": "m.login.password", "identifier": { @@ -1163,7 +1163,7 @@ user during registration, if applicable. 1. A client might submit a registration request as follows: - ``` + ```nohighlight POST /_matrix/client/v3/register ``` ```json @@ -1176,7 +1176,7 @@ user during registration, if applicable. 2. The server requires the user to accept some terms of service before registration, so returns the following response: - ``` + ```nohighlight HTTP/1.1 401 Unauthorized Content-Type: application/json ``` @@ -1211,7 +1211,7 @@ user during registration, if applicable. 4. The client repeats the registration request, confirming that the user has accepted the documents: - ``` + ```nohighlight POST /_matrix/client/v3/register ``` ```json @@ -1226,7 +1226,7 @@ user during registration, if applicable. ``` 5. All authentication steps have now completed, so the request is successful: - ``` + ```nohighlight HTTP/1.1 200 OK Content-Type: application/json ``` @@ -1647,7 +1647,7 @@ This authorization request URL must be opened in the user's browser: Sample authorization request, with extra whitespaces for readability: -``` +```nohighlight https://account.example.com/oauth2/auth? client_id = s6BhdRkqt3 & response_type = code & @@ -1680,7 +1680,7 @@ used in the authorization request. A successful authorization will have a `code` value, for example: -``` +```nohighlight https://app.example.com/oauth2-callback#state=ewubooN9weezeewah9fol4oothohroh3&code=iuB7Eiz9heengah1joh2ioy9ahChuP6R ``` @@ -1692,7 +1692,7 @@ A failed authorization will have the following values: For example: -``` +```nohighlight https://app.example.com/oauth2-callback#state=ewubooN9weezeewah9fol4oothohroh3&error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request.&error_uri=https%3A%2F%2Ferrors.example.com%2F ``` @@ -1717,7 +1717,7 @@ type, the expiration time, and the refresh token. Sample token request: -``` +```nohighlight POST /oauth2/token HTTP/1.1 Host: account.example.com Content-Type: application/x-www-form-urlencoded @@ -2040,7 +2040,7 @@ When generating a new `device_id`, the client SHOULD generate a random string with enough entropy. It SHOULD only use characters from the unreserved character list defined by [RFC 3986 section 2.3](https://datatracker.ietf.org/doc/html/rfc3986#section-2.3): -``` +```nohighlight unreserved = a-z / A-Z / 0-9 / "-" / "." / "_" / "~" ``` @@ -2053,7 +2053,7 @@ In any case it MUST only use characters allowed by the OAuth 2.0 scope definition in [RFC 6749 section 3.3](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3), which is defined as the following ASCII ranges: -``` +```nohighlight %x21 / %x23-5B / %x5D-7E ``` @@ -2195,7 +2195,7 @@ The body of the request includes the following parameters, encoded as For example, revoking using the access token: -``` +```nohighlight POST /oauth2/revoke HTTP/1.1 Host: auth.example.com Content-Type: application/x-www-form-urlencoded @@ -2240,7 +2240,7 @@ set to `true` on all but the following Client-Server APIs: Servers MAY additionally include details of why the lock was applied in the `error` field. -``` +```nohighlight HTTP/1.1 401 Unauthorized Content-Type: application/json ``` @@ -2320,7 +2320,7 @@ When a client attempts to perform an action while suspended, the server MUST respond with a `403 Forbidden` error response with `M_USER_SUSPENDED` as the error code, as shown below: -``` +```nohighlight HTTP/1.1 403 Forbidden Content-Type: application/json ``` @@ -2928,7 +2928,7 @@ For example, a `/sync` request might return a range of four events `E2`, `E3`, `E4` and `E5` within a given room, omitting two prior events `E0` and `E1`. This can be visualised as follows: -``` +```nohighlight [E0]->[E1]->[E2]->[E3]->[E4]->[E5] ^ ^ | | @@ -2946,7 +2946,7 @@ deprecated `/events` API) support long-polling in this way. Continuing the example above, an incremental sync might report a single new event `E6`. The response can be visualised as: -``` +```nohighlight [E0]->[E1]->[E2]->[E3]->[E4]->[E5]->[E6] ^ ^ | | @@ -2970,7 +2970,7 @@ the `since` parameter. The server knows about four new events, `E7`, `E8`, the server sends a `limited` response containing `E8`, `E9` and `E10`but omitting `E7`. This forms a gap, which we can see in the visualisation: -``` +```nohighlight | gap | | <-> | [E0]->[E1]->[E2]->[E3]->[E4]->[E5]->[E6]->[E7]->[E8]->[E9]->[E10] @@ -3065,29 +3065,29 @@ to another. Valid requests look like: -``` +```nohighlight PUT /rooms/!roomid:domain/state/m.example.event { "key" : "without a state key" } ``` -``` +```nohighlight PUT /rooms/!roomid:domain/state/m.another.example.event/foo { "key" : "with 'foo' as the state key" } ``` In contrast, these requests are invalid: -``` +```nohighlight POST /rooms/!roomid:domain/state/m.example.event/ { "key" : "cannot use POST here" } ``` -``` +```nohighlight PUT /rooms/!roomid:domain/state/m.another.example.event/foo/11 { "key" : "txnIds are not supported" } ``` Care should be taken to avoid setting the wrong `state key`: -``` +```nohighlight PUT /rooms/!roomid:domain/state/m.another.example.event/11 { "key" : "with '11' as the state key, but was probably intended to be a txnId" } ``` @@ -3095,7 +3095,7 @@ PUT /rooms/!roomid:domain/state/m.another.example.event/11 The `state_key` is often used to store state about individual users, by using the user ID as the `state_key` value. For example: -``` +```nohighlight PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Aexample.org { "animal" : "cat", "reason": "fluffy" } ``` @@ -3103,7 +3103,7 @@ PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Aexample.org In some cases, there may be no need for a `state_key`, so it can be omitted: -``` +```nohighlight PUT /rooms/!roomid:domain/state/m.room.bgd.color { "color": "red", "hex": "#ff0000" } ``` diff --git a/content/client-server-api/modules/content_repo.md b/content/client-server-api/modules/content_repo.md index 80898d3a4..39fe33ecc 100644 --- a/content/client-server-api/modules/content_repo.md +++ b/content/client-server-api/modules/content_repo.md @@ -33,7 +33,7 @@ specification. Content locations are represented as Matrix Content (`mxc://`) URIs. They look like: -``` +```nohighlight mxc:/// : The name of the homeserver where this content originated, e.g. matrix.org diff --git a/content/client-server-api/modules/end_to_end_encryption.md b/content/client-server-api/modules/end_to_end_encryption.md index c06178d70..3154f384f 100644 --- a/content/client-server-api/modules/end_to_end_encryption.md +++ b/content/client-server-api/modules/end_to_end_encryption.md @@ -18,7 +18,7 @@ exchange fingerprints between users to build a web of trust. device. This may include long-term identity keys, and/or one-time keys. -``` +```nohighlight +----------+ +--------------+ | Bob's HS | | Bob's Device | +----------+ +--------------+ @@ -29,7 +29,7 @@ keys. 2) Alice requests Bob's public identity keys and supported algorithms. -``` +```nohighlight +----------------+ +------------+ +----------+ | Alice's Device | | Alice's HS | | Bob's HS | +----------------+ +------------+ +----------+ @@ -40,7 +40,7 @@ keys. 3) Alice selects an algorithm and claims any one-time keys needed. -``` +```nohighlight +----------------+ +------------+ +----------+ | Alice's Device | | Alice's HS | | Bob's HS | +----------------+ +------------+ +----------+ @@ -491,7 +491,7 @@ this example, Bob's device sends the `m.key.verification.start`, Alice's device could also send that message. As well, the order of the `m.key.verification.done` messages could be reversed. -``` +```nohighlight +---------------+ +---------------+ +-------------+ +-------------+ | AliceDevice1 | | AliceDevice2 | | BobDevice1 | | BobDevice2 | +---------------+ +---------------+ +-------------+ +-------------+ @@ -695,7 +695,7 @@ The process between Alice and Bob verifying each other would be: The wire protocol looks like the following between Alice and Bob's devices: -``` +```nohighlight +-------------+ +-----------+ | AliceDevice | | BobDevice | +-------------+ +-----------+ @@ -969,7 +969,7 @@ she can trust Bob's device if: The following diagram illustrates how keys are signed: -``` +```nohighlight +------------------+ .................. +----------------+ | +--------------+ | .................. : | +------------+ | | | v v v : : v v v | | @@ -1000,7 +1000,7 @@ the user who created them. The following diagram illustrates Alice's view, hiding the keys and signatures that she cannot see: -``` +```nohighlight +------------------+ +----------------+ +----------------+ | +--------------+ | | | | +------------+ | | | v v | v v v | | @@ -1218,7 +1218,7 @@ The binary segment MUST be of the following form: For example, if Alice displays a QR code encoding the following binary data: -``` +```nohighlight "MATRIX" |ver|mode| len | event ID 4D 41 54 52 49 58 02 00 00 2D 21 41 42 43 44 ... | user's cross-signing key | other user's cross-signing key | shared secret diff --git a/content/client-server-api/modules/push.md b/content/client-server-api/modules/push.md index 1b7ecc4dc..eb2252ba7 100644 --- a/content/client-server-api/modules/push.md +++ b/content/client-server-api/modules/push.md @@ -1,7 +1,7 @@ ### Push Notifications -``` +```nohighlight +--------------------+ +-------------------+ Matrix HTTP | | | | Notification Protocol | App Developer | | Device Vendor | diff --git a/content/client-server-api/modules/receipts.md b/content/client-server-api/modules/receipts.md index f38e8611c..ac0a232c7 100644 --- a/content/client-server-api/modules/receipts.md +++ b/content/client-server-api/modules/receipts.md @@ -214,7 +214,7 @@ before delivering them to clients. Some receipts are sent across federation as EDUs with type `m.receipt`. The format of the EDUs are: -``` +```nohighlight { : { : { diff --git a/content/client-server-api/modules/secrets.md b/content/client-server-api/modules/secrets.md index fb997992a..d1cde4099 100644 --- a/content/client-server-api/modules/secrets.md +++ b/content/client-server-api/modules/secrets.md @@ -157,7 +157,7 @@ Some secret is encrypted using keys with ID `key_id_1` and `key_id_2`: `org.example.some.secret`: -``` +```nohighlight { "encrypted": { "key_id_1": { @@ -177,7 +177,7 @@ and the key descriptions for the keys would be: `m.secret_storage.key.key_id_1`: -``` +```nohighlight { "name": "Some key", "algorithm": "m.secret_storage.v1.aes-hmac-sha2", @@ -187,7 +187,7 @@ and the key descriptions for the keys would be: `m.secret_storage.key.key_id_2`: -``` +```nohighlight { "name": "Some other key", "algorithm": "m.secret_storage.v1.aes-hmac-sha2", @@ -199,7 +199,7 @@ If `key_id_1` is the default key, then we also have: `m.secret_storage.default_key`: -``` +```nohighlight { "key": "key_id_1" } @@ -294,7 +294,7 @@ in the `iterations` parameter. Example: -``` +```nohighlight { "passphrase": { "algorithm": "m.pbkdf2", diff --git a/content/client-server-api/modules/spaces.md b/content/client-server-api/modules/spaces.md index 7de414597..d8c88c693 100644 --- a/content/client-server-api/modules/spaces.md +++ b/content/client-server-api/modules/spaces.md @@ -58,7 +58,7 @@ parent to the room. The `state_key` for the event is the child room's ID. For example, to achieve the following: -``` +```nohighlight #space:example.org #general:example.org (!abcdefg:example.org) !private:example.org diff --git a/content/client-server-api/modules/sso_login.md b/content/client-server-api/modules/sso_login.md index 8b228356a..58ce61dac 100644 --- a/content/client-server-api/modules/sso_login.md +++ b/content/client-server-api/modules/sso_login.md @@ -67,7 +67,7 @@ opening an embedded web view. These steps are illustrated as follows: -``` +```nohighlight Matrix Client Matrix Homeserver Auth Server | | | |-------------(0) GET /login----------->| | diff --git a/content/client-server-api/modules/third_party_invites.md b/content/client-server-api/modules/third_party_invites.md index 2fc8b82e9..f46ef703a 100644 --- a/content/client-server-api/modules/third_party_invites.md +++ b/content/client-server-api/modules/third_party_invites.md @@ -44,7 +44,7 @@ If the lookup yields a result for a Matrix User ID then the normal [invite process](/server-server-api/#inviting-to-a-room) can be initiated. This process ends up looking like this: -``` +```nohighlight +---------+ +-------------+ +-----------------+ | Client | | Homeserver | | IdentityServer | +---------+ +-------------+ +-----------------+ @@ -74,7 +74,7 @@ the invite on the identity server with a call to and emit a valid [`m.room.third_party_invite`](#mroomthird_party_invite) event to the room. This process ends up looking like this: -``` +```nohighlight +---------+ +-------------+ +-----------------+ | Client | | Homeserver | | IdentityServer | +---------+ +-------------+ +-----------------+ @@ -133,7 +133,7 @@ and an identity server IS, the full sequence for a third-party invite would look like the following. This diagram assumes H1 and H2 are residents of the room while H3 is attempting to join. -``` +```nohighlight +-------+ +-----------------+ +-----+ +-----+ +-----+ +-----+ | UserA | | ThirdPartyUser | | H1 | | H2 | | H3 | | IS | +-------+ +-----------------+ +-----+ +-----+ +-----+ +-----+ diff --git a/content/client-server-api/modules/voip_events.md b/content/client-server-api/modules/voip_events.md index 1c0bfd28c..ace616c24 100644 --- a/content/client-server-api/modules/voip_events.md +++ b/content/client-server-api/modules/voip_events.md @@ -129,7 +129,7 @@ or not there have been any changes to the Matrix spec. A call is set up with message events exchanged as follows: -``` +```nohighlight Caller Callee [Place Call] m.call.invite -----------> @@ -144,7 +144,7 @@ A call is set up with message events exchanged as follows: Or a rejected call: -``` +```nohighlight Caller Callee m.call.invite ------------> m.call.candidate ---------> diff --git a/content/olm-megolm/_index.md b/content/olm-megolm/_index.md new file mode 100644 index 000000000..56a128b07 --- /dev/null +++ b/content/olm-megolm/_index.md @@ -0,0 +1,711 @@ +--- +title: "Olm & Megolm" +weight: 61 +type: docs +--- + +## Olm: A Cryptographic Ratchet + +An implementation of the double cryptographic ratchet described by +https://whispersystems.org/docs/specifications/doubleratchet/. + +### Notation + +This document uses \(\parallel\) to represent string concatenation. When +\(\parallel\) appears on the right hand side of an \(=\) it means that +the inputs are concatenated. When \(\parallel\) appears on the left hand +side of an \(=\) it means that the output is split. + +When this document uses \(\operatorname{ECDH}\left(K_A,K_B\right)\) it means +that each party computes a Diffie-Hellman agreement using their private key +and the remote party's public key. +So party \(A\) computes \(\operatorname{ECDH}\left(K_B^{public},K_A^{private}\right)\) +and party \(B\) computes \(\operatorname{ECDH}\left(K_A^{public},K_B^{private}\right)\). + +Where this document uses \(\operatorname{HKDF}\left(salt,IKM,info,L\right)\) it +refers to the [HMAC-based key derivation function][] with a salt value of +\(salt\), input key material of \(IKM\), context string \(info\), +and output keying material length of \(L\) bytes. + +### The Olm Algorithm + +#### Initial setup + +The setup takes four [Curve25519][] inputs: Identity keys for Alice and Bob, +\(I_A\) and \(I_B\), and one-time keys for Alice and Bob, +\(E_A\) and \(E_B\). A shared secret, \(S\), is generated using +[Triple Diffie-Hellman][]. The initial 256 bit root key, \(R_0\), and 256 +bit chain key, \(C_{0,0}\), are derived from the shared secret using an +HMAC-based Key Derivation Function using [SHA-256][] as the hash function +([HKDF-SHA-256][]) with default salt and ``"OLM_ROOT"`` as the info. + +\[ +\begin{aligned} + S&=\operatorname{ECDH}\left(I_A,E_B\right)\;\parallel\; + \operatorname{ECDH}\left(E_A,I_B\right)\;\parallel\; + \operatorname{ECDH}\left(E_A,E_B\right)\\ + R_0\;\parallel\;C_{0,0}&= + \operatorname{HKDF}\left(0,S,\text{``OLM\_ROOT"},64\right) +\end{aligned} +\] + +#### Advancing the root key + +Advancing a root key takes the previous root key, \(R_{i-1}\), and two +Curve25519 inputs: the previous ratchet key, \(T_{i-1}\), and the current +ratchet key \(T_i\). The even ratchet keys are generated by Alice. +The odd ratchet keys are generated by Bob. A shared secret is generated +using Diffie-Hellman on the ratchet keys. The next root key, \(R_i\), and +chain key, \(C_{i,0}\), are derived from the shared secret using +[HKDF-SHA-256][] using \(R_{i-1}\) as the salt and ``"OLM_RATCHET"`` as the +info. + +\[ +\begin{aligned} + R_i\;\parallel\;C_{i,0}&= + \operatorname{HKDF}\left( + R_{i-1}, + \operatorname{ECDH}\left(T_{i-1},T_i\right), + \text{``OLM\_RATCHET"}, + 64 + \right) +\end{aligned} +\] + +#### Advancing the chain key + +Advancing a chain key takes the previous chain key, \(C_{i,j-1}\). The next +chain key, \(C_{i,j}\), is the [HMAC-SHA-256][] of ``"\x02"`` using the +previous chain key as the key. + +\[ +\begin{aligned} + C_{i,j}&=\operatorname{HMAC}\left(C_{i,j-1},\text{``\char`\\x02"}\right) +\end{aligned} +\] + +#### Creating a message key + +Creating a message key takes the current chain key, \(C_{i,j}\). The +message key, \(M_{i,j}\), is the [HMAC-SHA-256][] of ``"\x01"`` using the +current chain key as the key. The message keys where \(i\) is even are used +by Alice to encrypt messages. The message keys where \(i\) is odd are used +by Bob to encrypt messages. + +\[ +\begin{aligned} + M_{i,j}&=\operatorname{HMAC}\left(C_{i,j},\text{``\char`\\x01"}\right) +\end{aligned} +\] + +### The Olm Protocol + +#### Creating an outbound session + +Bob publishes the public parts of his identity key, \(I_B\), and some +single-use one-time keys \(E_B\). + +Alice downloads Bob's identity key, \(I_B\), and a one-time key, +\(E_B\). She generates a new single-use key, \(E_A\), and computes a +root key, \(R_0\), and a chain key \(C_{0,0}\). She also generates a +new ratchet key \(T_0\). + +#### Sending the first pre-key messages + +Alice computes a message key, \(M_{0,j}\), and a new chain key, +\(C_{0,j+1}\), using the current chain key. She replaces the current chain +key with the new one. + +Alice encrypts her plain-text with the message key, \(M_{0,j}\), using an +authenticated encryption scheme (see below) to get a cipher-text, +\(X_{0,j}\). + +She then sends the following to Bob: + * The public part of her identity key, \(I_A\) + * The public part of her single-use key, \(E_A\) + * The public part of Bob's single-use key, \(E_B\) + * The current chain index, \(j\) + * The public part of her ratchet key, \(T_0\) + * The cipher-text, \(X_{0,j}\) + +Alice will continue to send pre-key messages until she receives a message from +Bob. + +#### Creating an inbound session from a pre-key message + +Bob receives a pre-key message as above. + +Bob looks up the private part of his single-use key, \(E_B\). He can now +compute the root key, \(R_0\), and the chain key, \(C_{0,0}\), from +\(I_A\), \(E_A\), \(I_B\), and \(E_B\). + +Bob then advances the chain key \(j\) times, to compute the chain key used +by the message, \(C_{0,j}\). He now creates the +message key, \(M_{0,j}\), and attempts to decrypt the cipher-text, +\(X_{0,j}\). If the cipher-text's authentication is correct then Bob can +discard the private part of his single-use one-time key, \(E_B\). + +Bob stores Alice's initial ratchet key, \(T_0\), until he wants to +send a message. + +#### Sending normal messages + +Once a message has been received from the other side, a session is considered +established, and a more compact form is used. + +To send a message, the user checks if they have a sender chain key, +\(C_{i,j}\). Alice uses chain keys where \(i\) is even. Bob uses chain +keys where \(i\) is odd. If the chain key doesn't exist then a new ratchet +key \(T_i\) is generated and a new root key \(R_i\) and chain key +\(C_{i,0}\) are computed using \(R_{i-1}\), \(T_{i-1}\) and +\(T_i\). + +A message key, +\(M_{i,j}\) is computed from the current chain key, \(C_{i,j}\), and +the chain key is replaced with the next chain key, \(C_{i,j+1}\). The +plain-text is encrypted with \(M_{i,j}\), using an authenticated encryption +scheme (see below) to get a cipher-text, \(X_{i,j}\). + +The user then sends the following to the recipient: + * The current chain index, \(j\) + * The public part of the current ratchet key, \(T_i\) + * The cipher-text, \(X_{i,j}\) + +#### Receiving messages + +The user receives a message as above with the sender's current chain index, \(j\), +the sender's ratchet key, \(T_i\), and the cipher-text, \(X_{i,j}\). + +The user checks if they have a receiver chain with the correct +\(i\) by comparing the ratchet key, \(T_i\). If the chain doesn't exist +then they compute a new root key, \(R_i\), and a new receiver chain, with +chain key \(C_{i,0}\), using \(R_{i-1}\), \(T_{i-1}\) and +\(T_i\). + +If the \(j\) of the message is less than +the current chain index on the receiver then the message may only be decrypted +if the receiver has stored a copy of the message key \(M_{i,j}\). Otherwise +the receiver computes the chain key, \(C_{i,j}\). The receiver computes the +message key, \(M_{i,j}\), from the chain key and attempts to decrypt the +cipher-text, \(X_{i,j}\). + +If the decryption succeeds the receiver updates the chain key for \(T_i\) +with \(C_{i,j+1}\) and stores the message keys that were skipped in the +process so that they can decode out of order messages. If the receiver created +a new receiver chain then they discard their current sender chain so that +they will create a new chain when they next send a message. + +### The Olm Message Format + +Olm uses two types of messages. The underlying transport protocol must provide +a means for recipients to distinguish between them. + +#### Normal Messages + +Olm messages start with a one byte version followed by a variable length +payload followed by a fixed length message authentication code. + +```nohighlight + +--------------+------------------------------------+-----------+ + | Version Byte | Payload Bytes | MAC Bytes | + +--------------+------------------------------------+-----------+ +``` + +The version byte is ``"\x03"``. + +The payload consists of key-value pairs where the keys are integers and the +values are integers and strings. The keys are encoded as a variable length +integer tag where the 3 lowest bits indicates the type of the value: +0 for integers, 2 for strings. If the value is an integer then the tag is +followed by the value encoded as a variable length integer. If the value is +a string then the tag is followed by the length of the string encoded as +a variable length integer followed by the string itself. + +Olm uses a variable length encoding for integers. Each integer is encoded as a +sequence of bytes with the high bit set followed by a byte with the high bit +clear. The seven low bits of each byte store the bits of the integer. The least +significant bits are stored in the first byte. + +**Name**|**Tag**|**Type**|**Meaning** +:-----:|:-----:|:-----:|:-----: +Ratchet-Key|0x0A|String|The public part of the ratchet key, Ti, of the message +Chain-Index|0x10|Integer|The chain index, j, of the message +Cipher-Text|0x22|String|The cipher-text, Xi, j, of the message + +The length of the MAC is determined by the authenticated encryption algorithm +being used. (Olm version 1 uses [HMAC-SHA-256][], truncated to 8 bytes). The +MAC protects all of the bytes preceding the MAC. + +#### Pre-Key Messages + +Olm pre-key messages start with a one byte version followed by a variable +length payload. + +```nohighlight + +--------------+------------------------------------+ + | Version Byte | Payload Bytes | + +--------------+------------------------------------+ +``` + +The version byte is ``"\x03"``. + +The payload uses the same key-value format as for normal messages. + +**Name**|**Tag**|**Type**|**Meaning** +:-----:|:-----:|:-----:|:-----: +One-Time-Key|0x0A|String|The public part of Bob's single-use key, Eb. +Base-Key|0x12|String|The public part of Alice's single-use key, Ea. +Identity-Key|0x1A|String|The public part of Alice's identity key, Ia. +Message|0x22|String|An embedded Olm message with its own version and MAC. + +### Olm Authenticated Encryption + +#### Version 1 + +Version 1 of Olm uses [AES-256][] in [CBC][] mode with [PKCS#7][] padding for +encryption and [HMAC-SHA-256][] (truncated to 64 bits) for authentication. The +256 bit AES key, 256 bit HMAC key, and 128 bit AES IV are derived from the +message key using [HKDF-SHA-256][] using the default salt and an info of +``"OLM_KEYS"``. + +\[ +\begin{aligned} + AES\_KEY_{i,j}\;\parallel\;HMAC\_KEY_{i,j}\;\parallel\;AES\_IV_{i,j} + &= \operatorname{HKDF}\left(0,M_{i,j},\text{``OLM\_KEYS"},80\right) +\end{aligned} +\] + +The plain-text is encrypted with AES-256, using the key \(AES\_KEY_{i,j}\) +and the IV \(AES\_IV_{i,j}\) to give the cipher-text, \(X_{i,j}\). + +Then the entire message (including the Version Byte and all Payload Bytes) are +passed through [HMAC-SHA-256][]. The first 8 bytes of the MAC are appended to the message. + +### Message authentication concerns + +To avoid unknown key-share attacks, the application must include identifying +data for the sending and receiving user in the plain-text of (at least) the +pre-key messages. Such data could be a user ID, a telephone number; +alternatively it could be the public part of a keypair which the relevant user +has proven ownership of. + +#### Example attacks + +1. Alice publishes her public [Curve25519][] identity key, \(I_A\). Eve + publishes the same identity key, claiming it as her own. Bob downloads + Eve's keys, and associates \(I_A\) with Eve. Alice sends a message to + Bob; Eve intercepts it before forwarding it to Bob. Bob believes the + message came from Eve rather than Alice. + + This is prevented if Alice includes her user ID in the plain-text of the + pre-key message, so that Bob can see that the message was sent by Alice + originally. + +2. Bob publishes his public [Curve25519][] identity key, \(I_B\). Eve + publishes the same identity key, claiming it as her own. Alice downloads + Eve's keys, and associates \(I_B\) with Eve. Alice sends a message to + Eve; Eve cannot decrypt it, but forwards it to Bob. Bob believes the + Alice sent the message to him, whereas Alice intended it to go to Eve. + + This is prevented by Alice including the user ID of the intended recpient + (Eve) in the plain-text of the pre-key message. Bob can now tell that the + message was meant for Eve rather than him. + +### IPR + +The Olm specification (this document) is hereby placed in the public domain. + +### Feedback + +Can be sent to olm at matrix.org. + +### Acknowledgements + +The ratchet that Olm implements was designed by Trevor Perrin and Moxie +Marlinspike - details at https://whispersystems.org/docs/specifications/doubleratchet/. Olm is +an entirely new implementation written by the Matrix.org team. + +[Curve25519]: http://cr.yp.to/ecdh.html +[Triple Diffie-Hellman]: https://whispersystems.org/blog/simplifying-otr-deniability/ +[HMAC-based key derivation function]: https://tools.ietf.org/html/rfc5869 +[HKDF-SHA-256]: https://tools.ietf.org/html/rfc5869 +[HMAC-SHA-256]: https://tools.ietf.org/html/rfc2104 +[SHA-256]: https://tools.ietf.org/html/rfc6234 +[AES-256]: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf +[CBC]: http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf +[PKCS#7]: https://tools.ietf.org/html/rfc2315 + +## Megolm group ratchet + +An AES-based cryptographic ratchet intended for group communications. + +### Background + +The Megolm ratchet is intended for encrypted messaging applications where there +may be a large number of recipients of each message, thus precluding the use of +peer-to-peer encryption systems such as [Olm][]. + +It also allows a recipient to decrypt received messages multiple times. For +instance, in client/server applications, a copy of the ciphertext can be stored +on the (untrusted) server, while the client need only store the session keys. + +### Overview + +Each participant in a conversation uses their own outbound session for +encrypting messages. A session consists of a ratchet and an [Ed25519][] keypair. + +Secrecy is provided by the ratchet, which can be wound forwards but not +backwards, and is used to derive a distinct message key for each message. + +Authenticity is provided via Ed25519 signatures. + +The value of the ratchet, and the public part of the Ed25519 key, are shared +with other participants in the conversation via secure peer-to-peer +channels. Provided that peer-to-peer channel provides authenticity of the +messages to the participants and deniability of the messages to third parties, +the Megolm session will inherit those properties. + +### The Megolm ratchet algorithm + +The Megolm ratchet \(R_i\) consists of four parts, \(R_{i,j}\) for +\(j \in {0,1,2,3}\). The length of each part depends on the hash function +in use (256 bits for this version of Megolm). + +The ratchet is initialised with cryptographically-secure random data, and +advanced as follows: + +\[ +\begin{aligned} +R_{i,0} &= + \begin{cases} + H_0\left(R_{2^{24}(n-1),0}\right) &\text{if }\exists n | i = 2^{24}n\\ + R_{i-1,0} &\text{otherwise} + \end{cases}\\ +R_{i,1} &= + \begin{cases} + H_1\left(R_{2^{24}(n-1),0}\right) &\text{if }\exists n | i = 2^{24}n\\ + H_1\left(R_{2^{16}(m-1),1}\right) &\text{if }\exists m | i = 2^{16}m\\ + R_{i-1,1} &\text{otherwise} + \end{cases}\\ +R_{i,2} &= + \begin{cases} + H_2\left(R_{2^{24}(n-1),0}\right) &\text{if }\exists n | i = 2^{24}n\\ + H_2\left(R_{2^{16}(m-1),1}\right) &\text{if }\exists m | i = 2^{16}m\\ + H_2\left(R_{2^8(p-1),2}\right) &\text{if }\exists p | i = 2^8p\\ + R_{i-1,2} &\text{otherwise} + \end{cases}\\ +R_{i,3} &= + \begin{cases} + H_3\left(R_{2^{24}(n-1),0}\right) &\text{if }\exists n | i = 2^{24}n\\ + H_3\left(R_{2^{16}(m-1),1}\right) &\text{if }\exists m | i = 2^{16}m\\ + H_3\left(R_{2^8(p-1),2}\right) &\text{if }\exists p | i = 2^8p\\ + H_3\left(R_{i-1,3}\right) &\text{otherwise} + \end{cases} +\end{aligned} +\] + +where \(H_0\), \(H_1\), \(H_2\), and \(H_3\) are different hash +functions. In summary: every \(2^8\) iterations, \(R_{i,3}\) is +reseeded from \(R_{i,2}\). Every \(2^{16}\) iterations, \(R_{i,2}\) +and \(R_{i,3}\) are reseeded from \(R_{i,1}\). Every \(2^{24}\) +iterations, \(R_{i,1}\), \(R_{i,2}\) and \(R_{i,3}\) are reseeded +from \(R_{i,0}\). + +The complete ratchet value, \(R_{i}\), is hashed to generate the keys used +to encrypt each message. This scheme allows the ratchet to be advanced an +arbitrary amount forwards while needing at most 1020 hash computations. A +client can decrypt chat history onwards from the earliest value of the ratchet +it is aware of, but cannot decrypt history from before that point without +reversing the hash function. + +This allows a participant to share its ability to decrypt chat history with +another from a point in the conversation onwards by giving a copy of the +ratchet at that point in the conversation. + + +### The Megolm protocol + +#### Session setup + +Each participant in a conversation generates their own Megolm session. A +session consists of three parts: + +* a 32 bit counter, \(i\). +* an [Ed25519][] keypair, \(K\). +* a ratchet, \(R_i\), which consists of four 256-bit values, + \(R_{i,j}\) for \(j \in {0,1,2,3}\). + +The counter \(i\) is initialised to \(0\). A new Ed25519 keypair is +generated for \(K\). The ratchet is simply initialised with 1024 bits of +cryptographically-secure random data. + +A single participant may use multiple sessions over the lifetime of a +conversation. The public part of \(K\) is used as an identifier to +discriminate between sessions. + +#### Sharing session data + +To allow other participants in the conversation to decrypt messages, the +session data is formatted as described in [Session-sharing format](#session-sharing-format). It is then +shared with other participants in the conversation via a secure peer-to-peer +channel (such as that provided by [Olm][]). + +When the session data is received from other participants, the recipient first +checks that the signature matches the public key. They then store their own +copy of the counter, ratchet, and public key. + +#### Message encryption + +This version of Megolm uses [AES-256][] in [CBC][] mode with [PKCS#7][] padding and +[HMAC-SHA-256][] (truncated to 64 bits). The 256 bit AES key, 256 bit HMAC key, +and 128 bit AES IV are derived from the megolm ratchet \(R_i\): + +\[ +\begin{aligned} + \mathit{AES\_KEY}_{i}\;\parallel\;\mathit{HMAC\_KEY}_{i}\;\parallel\;\mathit{AES\_IV}_{i} + &= \operatorname{HKDF}\left(0,\,R_{i},\text{"MEGOLM\_KEYS"},\,80\right) \\ +\end{aligned} +\] + +where \(\parallel\) represents string splitting, and +\(\operatorname{HKDF}\left(\mathit{salt},\,\mathit{IKM},\,\mathit{info},\,L\right)\) +refers to the [HMAC-based key +derivation function][] using using [SHA-256][] as the hash function +([HKDF-SHA-256][]) with a salt value of \(\mathit{salt}\), input key material of +\(\mathit{IKM}\), context string \(\mathit{info}\), and output keying material length of +\(L\) bytes. + +The plain-text is encrypted with AES-256, using the key \(\mathit{AES\_KEY}_{i}\) +and the IV \(\mathit{AES\_IV}_{i}\) to give the cipher-text, \(X_{i}\). + +The ratchet index \(i\), and the cipher-text \(X_{i}\), are then packed +into a message as described in [Message format](#message-format). Then the entire message +(including the version bytes and all payload bytes) are passed through +HMAC-SHA-256. The first 8 bytes of the MAC are appended to the message. + +Finally, the authenticated message is signed using the Ed25519 keypair; the 64 +byte signature is appended to the message. + +The complete signed message, together with the public part of \(K\) (acting +as a session identifier), can then be sent over an insecure channel. The +message can then be authenticated and decrypted only by recipients who have +received the session data. + +#### Advancing the ratchet + +After each message is encrypted, the ratchet is advanced. This is done as +described in [The Megolm ratchet algorithm](#the-megolm-ratchet-algorithm), using the following definitions: + +\[ +\begin{aligned} + H_0(A) &\equiv \operatorname{HMAC}(A,\text{``\char`\\x00"}) \\ + H_1(A) &\equiv \operatorname{HMAC}(A,\text{``\char`\\x01"}) \\ + H_2(A) &\equiv \operatorname{HMAC}(A,\text{``\char`\\x02"}) \\ + H_3(A) &\equiv \operatorname{HMAC}(A,\text{``\char`\\x03"}) \\ +\end{aligned} +\] + +where \(\operatorname{HMAC}(A, T)\) is the HMAC-SHA-256 of ``T``, using ``A`` as the +key. + +For outbound sessions, the updated ratchet and counter are stored in the +session. + +In order to maintain the ability to decrypt conversation history, inbound +sessions should store a copy of their earliest known ratchet value (unless they +explicitly want to drop the ability to decrypt that history - see [Partial +Forward Secrecy](#partial-forward-secrecy)). They may also choose to cache calculated ratchet values, +but the decision of which ratchet states to cache is left to the application. + +### Data exchange formats + +#### Session sharing format + +This format is used for the initial sharing of a Megolm session with other +group participants who need to be able to read messages encrypted by this +session. + +The session sharing format is as follows: + +```nohighlight ++---+----+--------+--------+--------+--------+------+-----------+ +| V | i | R(i,0) | R(i,1) | R(i,2) | R(i,3) | Kpub | Signature | ++---+----+--------+--------+--------+--------+------+-----------+ +0 1 5 37 69 101 133 165 229 bytes +``` + +The version byte, ``V``, is ``"\x02"``. + +This is followed by the ratchet index, \(i\), which is encoded as a +big-endian 32-bit integer; the ratchet values \(R_{i,j}\); and the public +part of the Ed25519 keypair \(K\). + +The data is then signed using the Ed25519 keypair, and the 64-byte signature is +appended. + +#### Session export format + +Once the session is initially shared with the group participants, each +participant needs to retain a copy of the session if they want to maintain +their ability to decrypt messages encrypted with that session. + +For forward-secrecy purposes, a participant may choose to store a ratcheted +version of the session. But since the ratchet index is covered by the +signature, this would invalidate the signature. So we define a similar format, +called the *session export format*, which is identical to the [session sharing +format](#session-sharing-format) except for dropping the signature. + +The Megolm session export format is thus as follows: + +```nohighlight ++---+----+--------+--------+--------+--------+------+ +| V | i | R(i,0) | R(i,1) | R(i,2) | R(i,3) | Kpub | ++---+----+--------+--------+--------+--------+------+ +0 1 5 37 69 101 133 165 bytes +``` + +The version byte, ``V``, is ``"\x01"``. + +This is followed by the ratchet index, \(i\), which is encoded as a +big-endian 32-bit integer; the ratchet values \(R_{i,j}\); and the public +part of the Ed25519 keypair \(K\). + +#### Message format + +Megolm messages consist of a one byte version, followed by a variable length +payload, a fixed length message authentication code, and a fixed length +signature. + +```nohighlight ++---+------------------------------------+-----------+------------------+ +| V | Payload Bytes | MAC Bytes | Signature Bytes | ++---+------------------------------------+-----------+------------------+ +0 1 N N+8 N+72 bytes +``` + +The version byte, ``V``, is ``"\x03"``. + +The payload uses a format based on the [Protocol Buffers encoding][]. It +consists of the following key-value pairs: + +**Name**|**Tag**|**Type**|**Meaning** +:-----:|:-----:|:-----:|:-----: +Message-Index|0x08|Integer|The index of the ratchet, i +Cipher-Text|0x12|String|The cipher-text, Xi, of the message + +Within the payload, integers are encoded using a variable length encoding. Each +integer is encoded as a sequence of bytes with the high bit set followed by a +byte with the high bit clear. The seven low bits of each byte store the bits of +the integer. The least significant bits are stored in the first byte. + +Strings are encoded as a variable-length integer followed by the string itself. + +Each key-value pair is encoded as a variable-length integer giving the tag, +followed by a string or variable-length integer giving the value. + +The payload is followed by the MAC. The length of the MAC is determined by the +authenticated encryption algorithm being used (8 bytes in this version of the +protocol). The MAC protects all of the bytes preceding the MAC. + +The length of the signature is determined by the signing algorithm being used +(64 bytes in this version of the protocol). The signature covers all of the +bytes preceding the signature. + +### Limitations + +#### Message Replays + +A message can be decrypted successfully multiple times. This means that an +attacker can re-send a copy of an old message, and the recipient will treat it +as a new message. + +To mitigate this it is recommended that applications track the ratchet indices +they have received and that they reject messages with a ratchet index that +they have already decrypted. + +#### Lack of Transcript Consistency + +In a group conversation, there is no guarantee that all recipients have +received the same messages. For example, if Alice is in a conversation with Bob +and Charlie, she could send different messages to Bob and Charlie, or could +send some messages to Bob but not Charlie, or vice versa. + +Solving this is, in general, a hard problem, particularly in a protocol which +does not guarantee in-order message delivery. For now it remains the subject of +future research. + +#### Lack of Backward Secrecy + +[Backward secrecy](https://intensecrypto.org/public/lec_08_hash_functions_part2.html#sec-forward-and-backward-secrecy) +(also called 'future secrecy' or 'post-compromise security') is the property +that if current private keys are compromised, an attacker cannot decrypt +future messages in a given session. In other words, when looking +**backwards** in time at a compromise which has already happened, **current** +messages are still secret. + +By itself, Megolm does not possess this property: once the key to a Megolm +session is compromised, the attacker can decrypt any message that was +encrypted using a key derived from the compromised or subsequent ratchet +values. + +In order to mitigate this, the application should ensure that Megolm sessions +are not used indefinitely. Instead it should periodically start a new session, +with new keys shared over a secure channel. + + + +#### Partial Forward Secrecy + +[Forward secrecy](https://intensecrypto.org/public/lec_08_hash_functions_part2.html#sec-forward-and-backward-secrecy) +(also called 'perfect forward secrecy') is the property that if the current +private keys are compromised, an attacker cannot decrypt *past* messages in +a given session. In other words, when looking **forwards** in time towards a +potential future compromise, **current** messages will be secret. + +In Megolm, each recipient maintains a record of the ratchet value which allows +them to decrypt any messages sent in the session after the corresponding point +in the conversation. If this value is compromised, an attacker can similarly +decrypt past messages which were encrypted by a key derived from the +compromised or subsequent ratchet values. This gives 'partial' forward +secrecy. + +To mitigate this issue, the application should offer the user the option to +discard historical conversations, by winding forward any stored ratchet values, +or discarding sessions altogether. + +#### Dependency on secure channel for key exchange + +The design of the Megolm ratchet relies on the availability of a secure +peer-to-peer channel for the exchange of session keys. Any vulnerabilities in +the underlying channel are likely to be amplified when applied to Megolm +session setup. + +For example, if the peer-to-peer channel is vulnerable to an unknown key-share +attack, the entire Megolm session become similarly vulnerable. For example: +Alice starts a group chat with Eve, and shares the session keys with Eve. Eve +uses the unknown key-share attack to forward the session keys to Bob, who +believes Alice is starting the session with him. Eve then forwards messages +from the Megolm session to Bob, who again believes they are coming from +Alice. Provided the peer-to-peer channel is not vulnerable to this attack, Bob +will realise that the key-sharing message was forwarded by Eve, and can treat +the Megolm session as a forgery. + +A second example: if the peer-to-peer channel is vulnerable to a replay +attack, this can be extended to entire Megolm sessions. + +### License + +The Megolm specification (this document) is licensed under the Apache License, +Version 2.0 http://www.apache.org/licenses/LICENSE-2.0. + +[Ed25519]: http://ed25519.cr.yp.to/ +[HMAC-based key derivation function]: https://tools.ietf.org/html/rfc5869 +[HKDF-SHA-256]: https://tools.ietf.org/html/rfc5869 +[HMAC-SHA-256]: https://tools.ietf.org/html/rfc2104 +[SHA-256]: https://tools.ietf.org/html/rfc6234 +[AES-256]: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf +[CBC]: http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf +[PKCS#7]: https://tools.ietf.org/html/rfc2315 +[Olm]: https://gitlab.matrix.org/matrix-org/olm/blob/master/docs/olm.md +[Protocol Buffers encoding]: https://developers.google.com/protocol-buffers/docs/encoding diff --git a/content/proposals.md b/content/proposals.md index eb7fd3da0..8abf79d19 100644 --- a/content/proposals.md +++ b/content/proposals.md @@ -1,6 +1,6 @@ --- title: "Spec Change Proposals" -weight: 60 +weight: 62 type: docs --- @@ -281,7 +281,7 @@ corresponding labels for each stage on the [matrix-spec-proposals](https://github.com/matrix-org/matrix-spec-proposals) pull request trackers. -``` +```nohighlight + + Proposals | Spec PRs | Additional States +-------+ | +------+ | +---------------+ diff --git a/content/push-gateway-api.md b/content/push-gateway-api.md index 5fb36d7cf..161cf24f7 100644 --- a/content/push-gateway-api.md +++ b/content/push-gateway-api.md @@ -14,7 +14,7 @@ A client's homeserver forwards information about received events to the push gateway. The gateway then submits a push notification to the push notification provider (e.g. APNS, GCM). -``` +```nohighlight +--------------------+ +-------------------+ Matrix HTTP | | | | Notification Protocol | App Developer | | Device Vendor | diff --git a/content/rooms/v10.md b/content/rooms/v10.md index 7af19873d..470dc73a0 100644 --- a/content/rooms/v10.md +++ b/content/rooms/v10.md @@ -18,7 +18,7 @@ refined in [room version 9](/rooms/v9)). Clients should render the new join rule accordingly for such rooms. For example: -``` +```nohighlight This room is: [ ] Public [x] Private diff --git a/content/server-server-api.md b/content/server-server-api.md index 3e4f87f3a..ca1cdd2e7 100644 --- a/content/server-server-api.md +++ b/content/server-server-api.md @@ -289,7 +289,7 @@ and any query parameters if present, but should not include the leading Step 1 sign JSON: -``` +```nohighlight { "method": "POST", "uri": "/target", @@ -822,7 +822,7 @@ ResidentServer->JoiningServer: send_join response JoiningServer->Client: join response --> -``` +```nohighlight +---------+ +---------------+ +-----------------+ +-----------------+ | Client | | JoiningServer | | DirectoryServer | | ResidentServer | +---------+ +---------------+ +-----------------+ +-----------------+ diff --git a/layouts/_markup/render-passthrough.html b/layouts/_markup/render-passthrough.html new file mode 100644 index 000000000..ff03f3e58 --- /dev/null +++ b/layouts/_markup/render-passthrough.html @@ -0,0 +1,9 @@ +{{- $opts := dict "output" "htmlAndMathml" "displayMode" (eq .Type "block") }} +{{- with try (transform.ToMath .Inner $opts) }} + {{- with .Err }} + {{- errorf "Unable to render mathematical markup to HTML using the transform.ToMath function. The KaTeX display engine threw the following error: %s: see %s." . $.Position }} + {{- else }} + {{- .Value }} + {{- $.Page.Store.Set "hasMath" true }} + {{- end }} +{{- end -}} diff --git a/layouts/docs/baseof.html b/layouts/docs/baseof.html index ef748ab73..a353ce2b4 100644 --- a/layouts/docs/baseof.html +++ b/layouts/docs/baseof.html @@ -12,6 +12,10 @@ class="no-js"> {{ partial "head.html" . }} + {{ if .Page.Store.Get "hasMath" }} + + + {{ end }}
diff --git a/static/css/fonts/KaTeX_AMS-Regular.woff2 b/static/css/fonts/KaTeX_AMS-Regular.woff2 new file mode 100644 index 000000000..0acaaff03 Binary files /dev/null and b/static/css/fonts/KaTeX_AMS-Regular.woff2 differ diff --git a/static/css/fonts/KaTeX_Caligraphic-Bold.woff2 b/static/css/fonts/KaTeX_Caligraphic-Bold.woff2 new file mode 100644 index 000000000..f390922ec Binary files /dev/null and b/static/css/fonts/KaTeX_Caligraphic-Bold.woff2 differ diff --git a/static/css/fonts/KaTeX_Caligraphic-Regular.woff2 b/static/css/fonts/KaTeX_Caligraphic-Regular.woff2 new file mode 100644 index 000000000..75344a1f9 Binary files /dev/null and b/static/css/fonts/KaTeX_Caligraphic-Regular.woff2 differ diff --git a/static/css/fonts/KaTeX_Fraktur-Bold.woff2 b/static/css/fonts/KaTeX_Fraktur-Bold.woff2 new file mode 100644 index 000000000..395f28bea Binary files /dev/null and b/static/css/fonts/KaTeX_Fraktur-Bold.woff2 differ diff --git a/static/css/fonts/KaTeX_Fraktur-Regular.woff2 b/static/css/fonts/KaTeX_Fraktur-Regular.woff2 new file mode 100644 index 000000000..735f6948d Binary files /dev/null and b/static/css/fonts/KaTeX_Fraktur-Regular.woff2 differ diff --git a/static/css/fonts/KaTeX_Main-Bold.woff2 b/static/css/fonts/KaTeX_Main-Bold.woff2 new file mode 100644 index 000000000..ab2ad21da Binary files /dev/null and b/static/css/fonts/KaTeX_Main-Bold.woff2 differ diff --git a/static/css/fonts/KaTeX_Main-BoldItalic.woff2 b/static/css/fonts/KaTeX_Main-BoldItalic.woff2 new file mode 100644 index 000000000..5931794de Binary files /dev/null and b/static/css/fonts/KaTeX_Main-BoldItalic.woff2 differ diff --git a/static/css/fonts/KaTeX_Main-Italic.woff2 b/static/css/fonts/KaTeX_Main-Italic.woff2 new file mode 100644 index 000000000..b50920e13 Binary files /dev/null and b/static/css/fonts/KaTeX_Main-Italic.woff2 differ diff --git a/static/css/fonts/KaTeX_Main-Regular.woff2 b/static/css/fonts/KaTeX_Main-Regular.woff2 new file mode 100644 index 000000000..eb24a7ba2 Binary files /dev/null and b/static/css/fonts/KaTeX_Main-Regular.woff2 differ diff --git a/static/css/fonts/KaTeX_Math-BoldItalic.woff2 b/static/css/fonts/KaTeX_Math-BoldItalic.woff2 new file mode 100644 index 000000000..29657023a Binary files /dev/null and b/static/css/fonts/KaTeX_Math-BoldItalic.woff2 differ diff --git a/static/css/fonts/KaTeX_Math-Italic.woff2 b/static/css/fonts/KaTeX_Math-Italic.woff2 new file mode 100644 index 000000000..215c143fd Binary files /dev/null and b/static/css/fonts/KaTeX_Math-Italic.woff2 differ diff --git a/static/css/fonts/KaTeX_SansSerif-Bold.woff2 b/static/css/fonts/KaTeX_SansSerif-Bold.woff2 new file mode 100644 index 000000000..cfaa3bda5 Binary files /dev/null and b/static/css/fonts/KaTeX_SansSerif-Bold.woff2 differ diff --git a/static/css/fonts/KaTeX_SansSerif-Italic.woff2 b/static/css/fonts/KaTeX_SansSerif-Italic.woff2 new file mode 100644 index 000000000..349c06dc6 Binary files /dev/null and b/static/css/fonts/KaTeX_SansSerif-Italic.woff2 differ diff --git a/static/css/fonts/KaTeX_SansSerif-Regular.woff2 b/static/css/fonts/KaTeX_SansSerif-Regular.woff2 new file mode 100644 index 000000000..a90eea85f Binary files /dev/null and b/static/css/fonts/KaTeX_SansSerif-Regular.woff2 differ diff --git a/static/css/fonts/KaTeX_Script-Regular.woff2 b/static/css/fonts/KaTeX_Script-Regular.woff2 new file mode 100644 index 000000000..b3048fc11 Binary files /dev/null and b/static/css/fonts/KaTeX_Script-Regular.woff2 differ diff --git a/static/css/fonts/KaTeX_Size1-Regular.woff2 b/static/css/fonts/KaTeX_Size1-Regular.woff2 new file mode 100644 index 000000000..c5a8462fb Binary files /dev/null and b/static/css/fonts/KaTeX_Size1-Regular.woff2 differ diff --git a/static/css/fonts/KaTeX_Size2-Regular.woff2 b/static/css/fonts/KaTeX_Size2-Regular.woff2 new file mode 100644 index 000000000..e1bccfe24 Binary files /dev/null and b/static/css/fonts/KaTeX_Size2-Regular.woff2 differ diff --git a/static/css/fonts/KaTeX_Size3-Regular.woff2 b/static/css/fonts/KaTeX_Size3-Regular.woff2 new file mode 100644 index 000000000..249a28662 Binary files /dev/null and b/static/css/fonts/KaTeX_Size3-Regular.woff2 differ diff --git a/static/css/fonts/KaTeX_Size4-Regular.woff2 b/static/css/fonts/KaTeX_Size4-Regular.woff2 new file mode 100644 index 000000000..680c13085 Binary files /dev/null and b/static/css/fonts/KaTeX_Size4-Regular.woff2 differ diff --git a/static/css/fonts/KaTeX_Typewriter-Regular.woff2 b/static/css/fonts/KaTeX_Typewriter-Regular.woff2 new file mode 100644 index 000000000..771f1af70 Binary files /dev/null and b/static/css/fonts/KaTeX_Typewriter-Regular.woff2 differ diff --git a/static/css/katex.min.css b/static/css/katex.min.css new file mode 100644 index 000000000..33e523630 --- /dev/null +++ b/static/css/katex.min.css @@ -0,0 +1 @@ +@font-face{font-family:KaTeX_AMS;font-style:normal;font-weight:400;src:url(fonts/KaTeX_AMS-Regular.woff2) format("woff2"),url(fonts/KaTeX_AMS-Regular.woff) format("woff"),url(fonts/KaTeX_AMS-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Caligraphic-Bold.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Bold.woff) format("woff"),url(fonts/KaTeX_Caligraphic-Bold.ttf) format("truetype")}@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Caligraphic-Regular.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Regular.woff) format("woff"),url(fonts/KaTeX_Caligraphic-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Fraktur-Bold.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Bold.woff) format("woff"),url(fonts/KaTeX_Fraktur-Bold.ttf) format("truetype")}@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Fraktur-Regular.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Regular.woff) format("woff"),url(fonts/KaTeX_Fraktur-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Main-Bold.woff2) format("woff2"),url(fonts/KaTeX_Main-Bold.woff) format("woff"),url(fonts/KaTeX_Main-Bold.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:700;src:url(fonts/KaTeX_Main-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Main-BoldItalic.woff) format("woff"),url(fonts/KaTeX_Main-BoldItalic.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:400;src:url(fonts/KaTeX_Main-Italic.woff2) format("woff2"),url(fonts/KaTeX_Main-Italic.woff) format("woff"),url(fonts/KaTeX_Main-Italic.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Main-Regular.woff2) format("woff2"),url(fonts/KaTeX_Main-Regular.woff) format("woff"),url(fonts/KaTeX_Main-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:700;src:url(fonts/KaTeX_Math-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Math-BoldItalic.woff) format("woff"),url(fonts/KaTeX_Math-BoldItalic.ttf) format("truetype")}@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:400;src:url(fonts/KaTeX_Math-Italic.woff2) format("woff2"),url(fonts/KaTeX_Math-Italic.woff) format("woff"),url(fonts/KaTeX_Math-Italic.ttf) format("truetype")}@font-face{font-family:"KaTeX_SansSerif";font-style:normal;font-weight:700;src:url(fonts/KaTeX_SansSerif-Bold.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Bold.woff) format("woff"),url(fonts/KaTeX_SansSerif-Bold.ttf) format("truetype")}@font-face{font-family:"KaTeX_SansSerif";font-style:italic;font-weight:400;src:url(fonts/KaTeX_SansSerif-Italic.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Italic.woff) format("woff"),url(fonts/KaTeX_SansSerif-Italic.ttf) format("truetype")}@font-face{font-family:"KaTeX_SansSerif";font-style:normal;font-weight:400;src:url(fonts/KaTeX_SansSerif-Regular.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Regular.woff) format("woff"),url(fonts/KaTeX_SansSerif-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Script;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Script-Regular.woff2) format("woff2"),url(fonts/KaTeX_Script-Regular.woff) format("woff"),url(fonts/KaTeX_Script-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size1;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size1-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size1-Regular.woff) format("woff"),url(fonts/KaTeX_Size1-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size2;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size2-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size2-Regular.woff) format("woff"),url(fonts/KaTeX_Size2-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size3;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size3-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size3-Regular.woff) format("woff"),url(fonts/KaTeX_Size3-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size4;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size4-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size4-Regular.woff) format("woff"),url(fonts/KaTeX_Size4-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Typewriter;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Typewriter-Regular.woff2) format("woff2"),url(fonts/KaTeX_Typewriter-Regular.woff) format("woff"),url(fonts/KaTeX_Typewriter-Regular.ttf) format("truetype")}.katex{font:normal 1.21em KaTeX_Main,Times New Roman,serif;line-height:1.2;text-indent:0;text-rendering:auto}.katex *{-ms-high-contrast-adjust:none!important;border-color:currentColor}.katex .katex-version:after{content:"0.16.23"}.katex .katex-mathml{clip:rect(1px,1px,1px,1px);border:0;height:1px;overflow:hidden;padding:0;position:absolute;width:1px}.katex .katex-html>.newline{display:block}.katex .base{position:relative;white-space:nowrap;width:-webkit-min-content;width:-moz-min-content;width:min-content}.katex .base,.katex .strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathnormal{font-family:KaTeX_Math;font-style:italic}.katex .mathit{font-family:KaTeX_Main;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-style:italic;font-weight:700}.katex .amsrm,.katex .mathbb,.katex .textbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak,.katex .textfrak{font-family:KaTeX_Fraktur}.katex .mathboldfrak,.katex .textboldfrak{font-family:KaTeX_Fraktur;font-weight:700}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr,.katex .textscr{font-family:KaTeX_Script}.katex .mathsf,.katex .textsf{font-family:KaTeX_SansSerif}.katex .mathboldsf,.katex .textboldsf{font-family:KaTeX_SansSerif;font-weight:700}.katex .mathitsf,.katex .mathsfit,.katex .textitsf{font-family:KaTeX_SansSerif;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{border-collapse:collapse;display:inline-table;table-layout:fixed}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;position:relative;vertical-align:bottom}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;font-size:1px;min-width:2px;vertical-align:bottom;width:2px}.katex .vbox{align-items:baseline;display:inline-flex;flex-direction:column}.katex .hbox{width:100%}.katex .hbox,.katex .thinbox{display:inline-flex;flex-direction:row}.katex .thinbox{max-width:0;width:0}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline,.katex .hline,.katex .mfrac .frac-line,.katex .overline .overline-line,.katex .rule,.katex .underline .underline-line{min-height:1px}.katex .mspace{display:inline-block}.katex .clap,.katex .llap,.katex .rlap{position:relative;width:0}.katex .clap>.inner,.katex .llap>.inner,.katex .rlap>.inner{position:absolute}.katex .clap>.fix,.katex .llap>.fix,.katex .rlap>.fix{display:inline-block}.katex .llap>.inner{right:0}.katex .clap>.inner,.katex .rlap>.inner{left:0}.katex .clap>.inner>span{margin-left:-50%;margin-right:50%}.katex .rule{border:0 solid;display:inline-block;position:relative}.katex .hline,.katex .overline .overline-line,.katex .underline .underline-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline{border-bottom-style:dashed;display:inline-block;width:100%}.katex .sqrt>.root{margin-left:.2777777778em;margin-right:-.5555555556em}.katex .fontsize-ensurer.reset-size1.size1,.katex .sizing.reset-size1.size1{font-size:1em}.katex .fontsize-ensurer.reset-size1.size2,.katex .sizing.reset-size1.size2{font-size:1.2em}.katex .fontsize-ensurer.reset-size1.size3,.katex .sizing.reset-size1.size3{font-size:1.4em}.katex .fontsize-ensurer.reset-size1.size4,.katex .sizing.reset-size1.size4{font-size:1.6em}.katex .fontsize-ensurer.reset-size1.size5,.katex .sizing.reset-size1.size5{font-size:1.8em}.katex .fontsize-ensurer.reset-size1.size6,.katex .sizing.reset-size1.size6{font-size:2em}.katex .fontsize-ensurer.reset-size1.size7,.katex .sizing.reset-size1.size7{font-size:2.4em}.katex .fontsize-ensurer.reset-size1.size8,.katex .sizing.reset-size1.size8{font-size:2.88em}.katex .fontsize-ensurer.reset-size1.size9,.katex .sizing.reset-size1.size9{font-size:3.456em}.katex .fontsize-ensurer.reset-size1.size10,.katex .sizing.reset-size1.size10{font-size:4.148em}.katex .fontsize-ensurer.reset-size1.size11,.katex .sizing.reset-size1.size11{font-size:4.976em}.katex .fontsize-ensurer.reset-size2.size1,.katex .sizing.reset-size2.size1{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size2.size2,.katex .sizing.reset-size2.size2{font-size:1em}.katex .fontsize-ensurer.reset-size2.size3,.katex .sizing.reset-size2.size3{font-size:1.1666666667em}.katex .fontsize-ensurer.reset-size2.size4,.katex .sizing.reset-size2.size4{font-size:1.3333333333em}.katex .fontsize-ensurer.reset-size2.size5,.katex .sizing.reset-size2.size5{font-size:1.5em}.katex .fontsize-ensurer.reset-size2.size6,.katex .sizing.reset-size2.size6{font-size:1.6666666667em}.katex .fontsize-ensurer.reset-size2.size7,.katex .sizing.reset-size2.size7{font-size:2em}.katex .fontsize-ensurer.reset-size2.size8,.katex .sizing.reset-size2.size8{font-size:2.4em}.katex .fontsize-ensurer.reset-size2.size9,.katex .sizing.reset-size2.size9{font-size:2.88em}.katex .fontsize-ensurer.reset-size2.size10,.katex .sizing.reset-size2.size10{font-size:3.4566666667em}.katex .fontsize-ensurer.reset-size2.size11,.katex .sizing.reset-size2.size11{font-size:4.1466666667em}.katex .fontsize-ensurer.reset-size3.size1,.katex .sizing.reset-size3.size1{font-size:.7142857143em}.katex .fontsize-ensurer.reset-size3.size2,.katex .sizing.reset-size3.size2{font-size:.8571428571em}.katex .fontsize-ensurer.reset-size3.size3,.katex .sizing.reset-size3.size3{font-size:1em}.katex .fontsize-ensurer.reset-size3.size4,.katex .sizing.reset-size3.size4{font-size:1.1428571429em}.katex .fontsize-ensurer.reset-size3.size5,.katex .sizing.reset-size3.size5{font-size:1.2857142857em}.katex .fontsize-ensurer.reset-size3.size6,.katex .sizing.reset-size3.size6{font-size:1.4285714286em}.katex .fontsize-ensurer.reset-size3.size7,.katex .sizing.reset-size3.size7{font-size:1.7142857143em}.katex .fontsize-ensurer.reset-size3.size8,.katex .sizing.reset-size3.size8{font-size:2.0571428571em}.katex .fontsize-ensurer.reset-size3.size9,.katex .sizing.reset-size3.size9{font-size:2.4685714286em}.katex .fontsize-ensurer.reset-size3.size10,.katex .sizing.reset-size3.size10{font-size:2.9628571429em}.katex .fontsize-ensurer.reset-size3.size11,.katex .sizing.reset-size3.size11{font-size:3.5542857143em}.katex .fontsize-ensurer.reset-size4.size1,.katex .sizing.reset-size4.size1{font-size:.625em}.katex .fontsize-ensurer.reset-size4.size2,.katex .sizing.reset-size4.size2{font-size:.75em}.katex .fontsize-ensurer.reset-size4.size3,.katex .sizing.reset-size4.size3{font-size:.875em}.katex .fontsize-ensurer.reset-size4.size4,.katex .sizing.reset-size4.size4{font-size:1em}.katex .fontsize-ensurer.reset-size4.size5,.katex .sizing.reset-size4.size5{font-size:1.125em}.katex .fontsize-ensurer.reset-size4.size6,.katex .sizing.reset-size4.size6{font-size:1.25em}.katex .fontsize-ensurer.reset-size4.size7,.katex .sizing.reset-size4.size7{font-size:1.5em}.katex .fontsize-ensurer.reset-size4.size8,.katex .sizing.reset-size4.size8{font-size:1.8em}.katex .fontsize-ensurer.reset-size4.size9,.katex .sizing.reset-size4.size9{font-size:2.16em}.katex .fontsize-ensurer.reset-size4.size10,.katex .sizing.reset-size4.size10{font-size:2.5925em}.katex .fontsize-ensurer.reset-size4.size11,.katex .sizing.reset-size4.size11{font-size:3.11em}.katex .fontsize-ensurer.reset-size5.size1,.katex .sizing.reset-size5.size1{font-size:.5555555556em}.katex .fontsize-ensurer.reset-size5.size2,.katex .sizing.reset-size5.size2{font-size:.6666666667em}.katex .fontsize-ensurer.reset-size5.size3,.katex .sizing.reset-size5.size3{font-size:.7777777778em}.katex .fontsize-ensurer.reset-size5.size4,.katex .sizing.reset-size5.size4{font-size:.8888888889em}.katex .fontsize-ensurer.reset-size5.size5,.katex .sizing.reset-size5.size5{font-size:1em}.katex .fontsize-ensurer.reset-size5.size6,.katex .sizing.reset-size5.size6{font-size:1.1111111111em}.katex .fontsize-ensurer.reset-size5.size7,.katex .sizing.reset-size5.size7{font-size:1.3333333333em}.katex .fontsize-ensurer.reset-size5.size8,.katex .sizing.reset-size5.size8{font-size:1.6em}.katex .fontsize-ensurer.reset-size5.size9,.katex .sizing.reset-size5.size9{font-size:1.92em}.katex .fontsize-ensurer.reset-size5.size10,.katex .sizing.reset-size5.size10{font-size:2.3044444444em}.katex .fontsize-ensurer.reset-size5.size11,.katex .sizing.reset-size5.size11{font-size:2.7644444444em}.katex .fontsize-ensurer.reset-size6.size1,.katex .sizing.reset-size6.size1{font-size:.5em}.katex .fontsize-ensurer.reset-size6.size2,.katex .sizing.reset-size6.size2{font-size:.6em}.katex .fontsize-ensurer.reset-size6.size3,.katex .sizing.reset-size6.size3{font-size:.7em}.katex .fontsize-ensurer.reset-size6.size4,.katex .sizing.reset-size6.size4{font-size:.8em}.katex .fontsize-ensurer.reset-size6.size5,.katex .sizing.reset-size6.size5{font-size:.9em}.katex .fontsize-ensurer.reset-size6.size6,.katex .sizing.reset-size6.size6{font-size:1em}.katex .fontsize-ensurer.reset-size6.size7,.katex .sizing.reset-size6.size7{font-size:1.2em}.katex .fontsize-ensurer.reset-size6.size8,.katex .sizing.reset-size6.size8{font-size:1.44em}.katex .fontsize-ensurer.reset-size6.size9,.katex .sizing.reset-size6.size9{font-size:1.728em}.katex .fontsize-ensurer.reset-size6.size10,.katex .sizing.reset-size6.size10{font-size:2.074em}.katex .fontsize-ensurer.reset-size6.size11,.katex .sizing.reset-size6.size11{font-size:2.488em}.katex .fontsize-ensurer.reset-size7.size1,.katex .sizing.reset-size7.size1{font-size:.4166666667em}.katex .fontsize-ensurer.reset-size7.size2,.katex .sizing.reset-size7.size2{font-size:.5em}.katex .fontsize-ensurer.reset-size7.size3,.katex .sizing.reset-size7.size3{font-size:.5833333333em}.katex .fontsize-ensurer.reset-size7.size4,.katex .sizing.reset-size7.size4{font-size:.6666666667em}.katex .fontsize-ensurer.reset-size7.size5,.katex .sizing.reset-size7.size5{font-size:.75em}.katex .fontsize-ensurer.reset-size7.size6,.katex .sizing.reset-size7.size6{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size7.size7,.katex .sizing.reset-size7.size7{font-size:1em}.katex .fontsize-ensurer.reset-size7.size8,.katex .sizing.reset-size7.size8{font-size:1.2em}.katex .fontsize-ensurer.reset-size7.size9,.katex .sizing.reset-size7.size9{font-size:1.44em}.katex .fontsize-ensurer.reset-size7.size10,.katex .sizing.reset-size7.size10{font-size:1.7283333333em}.katex .fontsize-ensurer.reset-size7.size11,.katex .sizing.reset-size7.size11{font-size:2.0733333333em}.katex .fontsize-ensurer.reset-size8.size1,.katex .sizing.reset-size8.size1{font-size:.3472222222em}.katex .fontsize-ensurer.reset-size8.size2,.katex .sizing.reset-size8.size2{font-size:.4166666667em}.katex .fontsize-ensurer.reset-size8.size3,.katex .sizing.reset-size8.size3{font-size:.4861111111em}.katex .fontsize-ensurer.reset-size8.size4,.katex .sizing.reset-size8.size4{font-size:.5555555556em}.katex .fontsize-ensurer.reset-size8.size5,.katex .sizing.reset-size8.size5{font-size:.625em}.katex .fontsize-ensurer.reset-size8.size6,.katex .sizing.reset-size8.size6{font-size:.6944444444em}.katex .fontsize-ensurer.reset-size8.size7,.katex .sizing.reset-size8.size7{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size8.size8,.katex .sizing.reset-size8.size8{font-size:1em}.katex .fontsize-ensurer.reset-size8.size9,.katex .sizing.reset-size8.size9{font-size:1.2em}.katex .fontsize-ensurer.reset-size8.size10,.katex .sizing.reset-size8.size10{font-size:1.4402777778em}.katex .fontsize-ensurer.reset-size8.size11,.katex .sizing.reset-size8.size11{font-size:1.7277777778em}.katex .fontsize-ensurer.reset-size9.size1,.katex .sizing.reset-size9.size1{font-size:.2893518519em}.katex .fontsize-ensurer.reset-size9.size2,.katex .sizing.reset-size9.size2{font-size:.3472222222em}.katex .fontsize-ensurer.reset-size9.size3,.katex .sizing.reset-size9.size3{font-size:.4050925926em}.katex .fontsize-ensurer.reset-size9.size4,.katex .sizing.reset-size9.size4{font-size:.462962963em}.katex .fontsize-ensurer.reset-size9.size5,.katex .sizing.reset-size9.size5{font-size:.5208333333em}.katex .fontsize-ensurer.reset-size9.size6,.katex .sizing.reset-size9.size6{font-size:.5787037037em}.katex .fontsize-ensurer.reset-size9.size7,.katex .sizing.reset-size9.size7{font-size:.6944444444em}.katex .fontsize-ensurer.reset-size9.size8,.katex .sizing.reset-size9.size8{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size9.size9,.katex .sizing.reset-size9.size9{font-size:1em}.katex .fontsize-ensurer.reset-size9.size10,.katex .sizing.reset-size9.size10{font-size:1.2002314815em}.katex .fontsize-ensurer.reset-size9.size11,.katex .sizing.reset-size9.size11{font-size:1.4398148148em}.katex .fontsize-ensurer.reset-size10.size1,.katex .sizing.reset-size10.size1{font-size:.2410800386em}.katex .fontsize-ensurer.reset-size10.size2,.katex .sizing.reset-size10.size2{font-size:.2892960463em}.katex .fontsize-ensurer.reset-size10.size3,.katex .sizing.reset-size10.size3{font-size:.337512054em}.katex .fontsize-ensurer.reset-size10.size4,.katex .sizing.reset-size10.size4{font-size:.3857280617em}.katex .fontsize-ensurer.reset-size10.size5,.katex .sizing.reset-size10.size5{font-size:.4339440694em}.katex .fontsize-ensurer.reset-size10.size6,.katex .sizing.reset-size10.size6{font-size:.4821600771em}.katex .fontsize-ensurer.reset-size10.size7,.katex .sizing.reset-size10.size7{font-size:.5785920926em}.katex .fontsize-ensurer.reset-size10.size8,.katex .sizing.reset-size10.size8{font-size:.6943105111em}.katex .fontsize-ensurer.reset-size10.size9,.katex .sizing.reset-size10.size9{font-size:.8331726133em}.katex .fontsize-ensurer.reset-size10.size10,.katex .sizing.reset-size10.size10{font-size:1em}.katex .fontsize-ensurer.reset-size10.size11,.katex .sizing.reset-size10.size11{font-size:1.1996142719em}.katex .fontsize-ensurer.reset-size11.size1,.katex .sizing.reset-size11.size1{font-size:.2009646302em}.katex .fontsize-ensurer.reset-size11.size2,.katex .sizing.reset-size11.size2{font-size:.2411575563em}.katex .fontsize-ensurer.reset-size11.size3,.katex .sizing.reset-size11.size3{font-size:.2813504823em}.katex .fontsize-ensurer.reset-size11.size4,.katex .sizing.reset-size11.size4{font-size:.3215434084em}.katex .fontsize-ensurer.reset-size11.size5,.katex .sizing.reset-size11.size5{font-size:.3617363344em}.katex .fontsize-ensurer.reset-size11.size6,.katex .sizing.reset-size11.size6{font-size:.4019292605em}.katex .fontsize-ensurer.reset-size11.size7,.katex .sizing.reset-size11.size7{font-size:.4823151125em}.katex .fontsize-ensurer.reset-size11.size8,.katex .sizing.reset-size11.size8{font-size:.578778135em}.katex .fontsize-ensurer.reset-size11.size9,.katex .sizing.reset-size11.size9{font-size:.6945337621em}.katex .fontsize-ensurer.reset-size11.size10,.katex .sizing.reset-size11.size10{font-size:.8336012862em}.katex .fontsize-ensurer.reset-size11.size11,.katex .sizing.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .accent>.vlist-t,.katex .op-limits>.vlist-t{text-align:center}.katex .accent .accent-body{position:relative}.katex .accent .accent-body:not(.accent-full){width:0}.katex .overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;min-width:1px}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{fill:currentColor;stroke:currentColor;fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:block;height:inherit;position:absolute;width:100%}.katex svg path{stroke:none}.katex img{border-style:none;max-height:none;max-width:none;min-height:0;min-width:0}.katex .stretchy{display:block;overflow:hidden;position:relative;width:100%}.katex .stretchy:after,.katex .stretchy:before{content:""}.katex .hide-tail{overflow:hidden;position:relative;width:100%}.katex .halfarrow-left{left:0;overflow:hidden;position:absolute;width:50.2%}.katex .halfarrow-right{overflow:hidden;position:absolute;right:0;width:50.2%}.katex .brace-left{left:0;overflow:hidden;position:absolute;width:25.1%}.katex .brace-center{left:25%;overflow:hidden;position:absolute;width:50%}.katex .brace-right{overflow:hidden;position:absolute;right:0;width:25.1%}.katex .x-arrow-pad{padding:0 .5em}.katex .cd-arrow-pad{padding:0 .55556em 0 .27778em}.katex .mover,.katex .munder,.katex .x-arrow{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox,.katex .fcolorbox{border:.04em solid;box-sizing:border-box}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap{margin-left:-.2em;margin-right:-.2em}.katex .sout{border-bottom-style:solid;border-bottom-width:.08em}.katex .angl{border-right:.049em solid;border-top:.049em solid;box-sizing:border-box;margin-right:.03889em}.katex .anglpad{padding:0 .03889em}.katex .eqn-num:before{content:"(" counter(katexEqnNo) ")";counter-increment:katexEqnNo}.katex .mml-eqn-num:before{content:"(" counter(mmlEqnNo) ")";counter-increment:mmlEqnNo}.katex .mtr-glue{width:50%}.katex .cd-vert-arrow{display:inline-block;position:relative}.katex .cd-label-left{display:inline-block;position:absolute;right:calc(50% + .3em);text-align:left}.katex .cd-label-right{display:inline-block;left:calc(50% + .3em);position:absolute;text-align:right}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:block;text-align:center;white-space:nowrap}.katex-display>.katex>.katex-html{display:block;position:relative}.katex-display>.katex>.katex-html>.tag{position:absolute;right:0}.katex-display.leqno>.katex>.katex-html>.tag{left:0;right:auto}.katex-display.fleqn>.katex{padding-left:2em;text-align:left}body{counter-reset:katexEqnNo mmlEqnNo}