docs-matrix-spec/locales/zh-Hans/appendices.md
2025-04-20 16:13:37 +08:00

36 KiB
Raw Permalink Blame History

title weight type
附录 70 docs

{{< boxes/warning >}} 本页面的翻译未经核对,可能存在翻译质量不佳、错翻、漏翻等情况。您可以在 Forgejo 存储库 打开 Issue、提交 Pull Request 或邮件联系我们提出改进建议和参与翻译与核对。 {{< /boxes/warning >}}

无补位的 Base64

无补位 Base64 指的是 RFC 4648 中定义的“标准”Base64 编码,但不包含等号 "=" 补位。具体来说RFC 4648 要求编码数据长度需为 4 的整数倍时,使用 = 字符进行补位,而无补位 Base64 则省略此补位。

供参考RFC 4648 中 Base64 的编码字母表如下:

Value Encoding  Value Encoding  Value Encoding  Value Encoding
    0 A            17 R            34 i            51 z
    1 B            18 S            35 j            52 0
    2 C            19 T            36 k            53 1
    3 D            20 U            37 l            54 2
    4 E            21 V            38 m            55 3
    5 F            22 W            39 n            56 4
    6 G            23 X            40 o            57 5
    7 H            24 Y            41 p            58 6
    8 I            25 Z            42 q            59 7
    9 J            26 a            43 r            60 8
   10 K            27 b            44 s            61 9
   11 L            28 c            45 t            62 +
   12 M            29 d            46 u            63 /
   13 N            30 e            47 v
   14 O            31 f            48 w
   15 P            32 g            49 x
   16 Q            33 h            50 y

使用无补位 Base64 编码的字符串示例:

UNPADDED_BASE64("") = ""
UNPADDED_BASE64("f") = "Zg"
UNPADDED_BASE64("fo") = "Zm8"
UNPADDED_BASE64("foo") = "Zm9v"
UNPADDED_BASE64("foob") = "Zm9vYg"
UNPADDED_BASE64("fooba") = "Zm9vYmE"
UNPADDED_BASE64("foobar") = "Zm9vYmFy"

在解码 Base64 时,建议各实现尽可能接受含有或不含有补位字符的输入,以最大程度增强互操作性。

二进制数据

在一些情况下,需要封装二进制数据,例如公钥或签名。鉴于 JSON 无法安全地表示原始二进制数据,所有二进制值应按上述无补位 Base64 字符串编码并在 JSON 中表示。

当 Matrix 规范中提到“opaque byte”或“opaque Base64”值时指的是在 Base64 解码之后的对应值应视作不可解释的二进制数据,而不是其编码表示。

无论何时,客户端或服务端实现都可以检测 Base64 编码值的正确性,并直接拒绝非法编码的值。但这并非强制要求,可视为实现细节。

针对未来协议转换(如不用 JSON 的场景),如无需 Base64 编码即可安全表示二进制值,则 Base64 可完全省略。

JSON 签名

Matrix 规范中多个位置要求对 JSON 对象进行加密签名。这要求将 JSON 编码为二进制字符串。不幸的是,同样的 JSON 结构可以通过更改空白符或调整键顺序产生不同的字节表示。

因此,对对象签名需使用 规范化 JSON 编码为字节序列,并计算该序列的签名,然后将签名添加到原始 JSON 对象。

规范化 JSON

为确保所有实现都使用相同方式编码 JSON我们定义“规范化 JSON”。本定义与规格外对“Canonical JSON”的其它使用不同。

对于一个值,规范化编码指以 UTF-8 最短编码、字典键按 Unicode 码点字典序排序的 JSON 字符串。JSON 中的数字须为 [-(2**53)+1, (2**53)-1] 范围内的整数,不允许指数形式或小数,不允许出现负零 -0

我们选用 UTF-8 作为编码方式,因为所有平台均可用且网络上的 JSON 多为 UTF-8 编码。采用键排序以保证顺序一致。整数范围限定为可用 IEEE 双精度浮点精确表示的范围,因许多 JSON 库以此存储数字。

{{% boxes/warning %}} 房间版本 1、2、3、4 和 5 中的事件可能并不完全符合上述限制。服务器应尽可能能够处理被这些限制判为无效的 JSON。

需特别注意的是,整数可能不在上述指定范围。 {{% /boxes/warning %}}

{{% boxes/note %}} 此编码不允许浮点型。 {{% /boxes/note %}}

import json

def canonical_json(value):
    return json.dumps(
        value,
        # 将 ASCII 之外的码点编码为 UTF-8而不是 \u 转义
        ensure_ascii=False,
        # 移除多余空白。
        separators=(',',':'),
        # 字典键进行排序。
        sort_keys=True,
        # 结果 Unicode 编码成 UTF-8 字节。
    ).encode("UTF-8")

语法

参照 http://tools.ietf.org/html/rfc7159 的语法,移除无关紧要的空白、分数、小数及冗余字符转义。

value     = false / null / true / object / array / number / string
false     = %x66.61.6C.73.65
null      = %x6E.75.6C.6C
true      = %x74.72.75.65
object    = %x7B [ member *( %x2C member ) ] %x7D
member    = string %x3A value
array     = %x5B [ value *( %x2C value ) ] %x5D
number    = [ %x2D ] int
int       = %x30 / ( %x31-39 *digit )
digit     = %x30-39
string    = %x22 *char %x22
char      = unescaped / %x5C escaped
unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
escaped   = %x22 ; "    引号  U+0022
          / %x5C ; \    反斜杠 U+005C
          / %x62 ; b    退格符  U+0008
          / %x66 ; f    换页符  U+000C
          / %x6E ; n    换行    U+000A
          / %x72 ; r    回车    U+000D
          / %x74 ; t    制表符  U+0009
          / %x75.30.30.30 (%x30-37 / %x62 / %x65-66) ; u000X
          / %x75.30.30.31 (%x30-39 / %x61-66)        ; u001X

示例

为帮助开发兼容实现,以下测试值可用于验证规范化转换代码。

给定如下 JSON 对象:

{}

应输出如下规范化 JSON

{}

给定如下 JSON 对象:

{
    "one": 1,
    "two": "Two"
}

应输出如下规范化 JSON

{"one":1,"two":"Two"}

给定如下 JSON 对象:

{
    "b": "2",
    "a": "1"
}

应输出如下规范化 JSON

{"a":"1","b":"2"}

给定如下 JSON 对象:

{"b":"2","a":"1"}

应输出如下规范化 JSON

{"a":"1","b":"2"}

给定如下 JSON 对象:

{
    "auth": {
        "success": true,
        "mxid": "@john.doe:example.com",
        "profile": {
            "display_name": "John Doe",
            "three_pids": [
                {
                    "medium": "email",
                    "address": "john.doe@example.org"
                },
                {
                    "medium": "msisdn",
                    "address": "123456789"
                }
            ]
        }
    }
}

应输出如下规范化 JSON

{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}

给定如下 JSON 对象:

{
    "a": "日本語"
}

应输出如下规范化 JSON

{"a":"日本語"}

给定如下 JSON 对象:

{
    "本": 2,
    "日": 1
}

应输出如下规范化 JSON

{"日":1,"本":2}

给定如下 JSON 对象:

{
    "a": "\u65E5"
}

应输出如下规范化 JSON

{"a":"日"}

给定如下 JSON 对象:

{
    "a": null
}

应输出如下规范化 JSON

{"a":null}

给定如下 JSON 对象:

{
    "a": -0,
    "b": 1e10
}

应输出如下规范化 JSON

{"a":0,"b":10000000000}

签名细节

对 JSON 进行签名时,需先移除 signaturesunsigned 相关的键,然后采用上述规范化编码方式编码对象。对获得的字节数据进行签名,并将签名结果采用无补位 Base64编码。得到的 base64 签名应按签名密钥标识符添加至 signatures 中,其下为签名实体的名称,共同返回原始 JSON 对象,并同时恢复 unsigned 字段。

签名密钥标识符签名算法密钥标识符拼接而成。签名算法标识用于签名的算法,目前支持的值为 ed25519,参见 NACL (http://nacl.cr.yp.to/)。密钥标识符用于区分同一实体使用的不同签名密钥。

unsignedsignatures 字段不受签名覆盖。因此,中间实体可添加未签名数据(比如时间戳)或额外签名。

{
   "name": "example.org",
   "signing_keys": {
     "ed25519:1": "XSl0kuyvrXNj6A+7/tkrB9sxSbRi08Of5uRhxOqZtEQ"
   },
   "unsigned": {
      "age_ts": 922834800000
   },
   "signatures": {
      "example.org": {
         "ed25519:1": "s76RUgajp8w172am0zQb/iPTHsRnb4SkrzGoeCOSFfcBY2V/1c8QfrmdXHpvnc2jK5BD1WiJIxiMW95fMjK7Bw"
      }
   }
}
def sign_json(json_object, signing_key, signing_name):
    signatures = json_object.pop("signatures", {})
    unsigned = json_object.pop("unsigned", None)

    signed = signing_key.sign(encode_canonical_json(json_object))
    signature_base64 = encode_base64(signed.signature)

    key_id = "%s:%s" % (signing_key.alg, signing_key.version)
    signatures.setdefault(signing_name, {})[key_id] = signature_base64

    json_object["signatures"] = signatures
    if unsigned is not None:
        json_object["unsigned"] = unsigned

    return json_object

验证签名

要检查某实体是否已对 JSON 对象签名,具体步骤如下:

  1. 检查对象的 signatures 成员下是否有该实体的条目。如缺失则验证失败。
  2. 从该条目中移除本实现不理解算法的签名密钥标识符。如剩下的签名密钥标识符为空则验证失败。
  3. 从本地缓存或可信密钥服务器查询剩余签名密钥标识符校验密钥。找不到则验证失败。
  4. 解码 base64 编码的签名字节。失败则验证失败。
  5. 移除对象中的 signaturesunsigned 成员。
  6. 使用规范化 JSON方式编码剩余 JSON。
  7. 校验密钥对签名字节和编码对象进行校验。如失败则验证失败,否则成功。

标识符语法

部分标识符特定于某些房间版本,详见 房间版本规范

通用命名空间标识符语法

{{% added-in v="1.2" %}}

本规范定义了一些标识符采用通用命名空间标识符语法。该语法适用于非面向用户的标识符,并为实现创建新标识符提供统一机制。

语法定义如下:

  • 标识符长度不少于 1 个字符且不超过 255 个字符。
  • 标识符必须以 [a-z] 开头,全由 [a-z][0-9]-_. 构成。
  • m. 开头的标识符为 Matrix 官方保留,不得使用。
  • 规范未描述的标识符应遵循 Java 包命名约定进行命名空间区分,通常采用反向域名格式,如 com.example.identifier

{{% boxes/note %}} 标识符可继承本规范的语法。例如,“该标识符使用通用命名空间标识符语法,但无强制命名空间要求”——这意味着 m. 依然保留,但实现可以不使用反向 DNS 格式命名自定义标识符。 {{% /boxes/note %}}

{{% boxes/rationale %}} ASCII 字符不会因同形异义或编码差异影响标识符用途。此外,全部小写可避免大小写带来的敏感性问题。 {{% /boxes/rationale %}}

服务器名

一个主服务器通过服务器名唯一标识。此值在多个标识符中被引用,详见下文。

服务器名表示其他主服务器可通信的该服务器的地址。下述语法包括所有有效服务器名:

server_name = hostname [ ":" port ]

port        = 1*5DIGIT

hostname    = IPv4address / "[" IPv6address "]" / dns-name

IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT

IPv6address = 2*45IPv6char

IPv6char    = DIGIT / %x41-46 / %x61-66 / ":" / "."
                  ; 0-9, A-F, a-f, :, .

dns-name    = 1*255dns-char

dns-char    = DIGIT / ALPHA / "-" / "."

——换句话说,服务器名为主机名,后可带可选的数字端口。主机名可以是点分十进制 IPv4 地址、方括号包裹的 IPv6 地址,或 DNS 名称。

IPv4 字面量须为 0-255 的四段十进制数字,用 . 分隔。IPv6 字面量参见 RFC3513, section 2.2

用于 Matrix 的 DNS 名称应遵守互联网主机名的通用约束:以 . 分隔的标签,每个标签为字母数字或连字符。

有效服务器名示例:

  • matrix.org
  • matrix.org:8888
  • 1.2.3.4IPv4 字面量)
  • 1.2.3.4:1234IPv4 字面量含端口)
  • [1234:5678::abcd]IPv6 字面量)
  • [1234:5678::abcd]:5678IPv6 字面量含端口)

{{% boxes/note %}} 此语法基于互联网主机名标准 RFC1123, section 2.1,扩展了对 IPv6 字面量的支持。 {{% /boxes/note %}}

服务器名必须区分大小写:例如,@user:matrix.org@user:MATRIX.ORG 表示不同用户。

关于服务器名选择,建议如下:

  • 服务器名总长度不应超过 230 个字符。
  • 服务器名不应包含大写字母。

通用标识符格式

Matrix 协议为多个实体(如用户、事件、房间)分配唯一标识符,采用通用格式:

&string

其中 & 为前缀符号sigilstring 为标识符内容。

前缀符号如下:

  • @:用户 ID
  • !:房间 ID
  • $:事件 ID
  • #:房间别名

用户 ID、房间 ID、房间别名及部分事件 ID 格式如下:

&localpart:domain

其中 domain 为创建该标识符的主服务器 服务器名localpart 由该主服务器分配。

标识符的具体格式与类型相关。例如,事件 ID 有时可以包含 domain,详见下方的 事件 ID 章节。

用户标识符

{{% changed-in v="1.8" %}}

Matrix 系统内用户通过用户 ID 唯一标识。用户 ID 归属于分配账户的主服务器,格式如下:

@localpart:domain

用户 ID 的 localpart 为该用户的不透明标识符。不得为空,仅可包含 a-z0-9._=-/+ 字符。

domain 为创建账户的主服务器 服务器名

用户 ID 总长(含 @ 和域名)不得超过 255 字节。

合法用户 ID 完整语法如下:

user_id = "@" user_id_localpart ":" server_name
user_id_localpart = 1*user_id_char
user_id_char = DIGIT
             / %x61-7A                   ; a-z
             / "-" / "." / "=" / "_" / "/" / "+"

{{% boxes/rationale %}} 在定义用户 ID 可用字符时,我们综合考虑了若干要素。

首先,排除了 US-ASCII 基本字符集外的字符。用户 ID 主要用于协议层级标识,作为人类可读名称属于第二层。在一些情况下用户 ID 也用于区分具有相似显示名的用户。支持全 Unicode 字符集将大大增加人工区分难度。选择有限字符集意味着即使不熟悉拉丁字母的用户,也能辨别相似用户 ID。

我们禁止大写字符,是因为不希望出现仅以大小写区分的两个用户 ID例如可用 @USER:matrix.org 替代 @user:matrix.org。但在部分场景(如 m.room.member 事件的 state_key)中用户 ID 必须区分大小写。禁止大写字符并要求主服务器在为新用户生成 ID 时统一小写,是避免 @USER:matrix.org@user:matrix.org 产生歧义的简便做法。

我们还限制了可用标点符号,以减少特殊字符冲突。例如,部分 API如过滤器 API"*" 作为通配符,所以不能作为合法用户 ID 字符。

长度限制则源自事件中 sender 关键字的长度上限。由于用户 ID 出现在用户发送的每个事件中,为防止其长度大幅超过实际内容而设限。 {{% /boxes/rationale %}}

Matrix 用户 ID 有时被非正式地称为 MXID。

历史用户 ID

本规范早期版本允许更广泛的字符作为用户 ID localpart。目前仍有活跃用户 ID 不符合现行字符集要求,也有部分房间历史事件的 sender 不合规。为了兼容这些房间,客户端和服务器必须能够接收 localpart 为除 :NULU+0000以外的任意合法非代理 Unicode 码点(包括其它控制字符及空字符串)的用户 ID。

localpart 含 U+0021~U+007E 以外的字符,或为空,用户 ID 被视为不合规。对于当前房间版本,服务器在联邦传递事件时仍需接受这些历史用户 ID不应在事件上下文外将其转发给客户端(如设备列表更新等数据应被丢弃)。

未来房间版本可能直接阻止使用历史字符集的用户参与。历史字符集已废弃

跨字符集映射

某些情况下需将更广泛字符集映射到受限的用户 ID localpart 字符集。例如主服务器接受 /register 注册时根据用户名创建用户 ID或桥接其他协议的用户 ID。

具体映射方式实现可自定。由于用户 ID 对外为不透明字符串,唯一要求是实现能保持映射一致。建议算法如下:

  1. 以 UTF-8 编码字符字符串。
  2. A-Z 字节转为小写。
    • 若桥接需区分大小写用户,先加 _ 转义大写再转换。如 A 编码为 _a,真实 _ 则写为 __
  3. 其余不在允许字符集的字节及 =,以十六进制值、前缀 = 形式编码。例如 # 写为 =23á 写为 =c3=a1

{{% boxes/rationale %}} 建议映射试图最大化简单 ASCII 标识符的人类可读性(区别于 base32同时能编码所有字符(区别于 punycode不支持 ASCII 标点编码)。 {{% /boxes/rationale %}}

房间 ID

房间有唯一的房间 ID。格式如下

!opaque_id:domain

domain 为创建房间的主服务器 服务器名 仅用于名称空间防止冲突,并不一定代表房间现仍由该服务器维护。

房间 ID 区分大小写。不建议为人类可读,客户端应完全以不可解释字符串处理之。

房间 ID 的 localpart(即 opaque_id)可包含除 :NULU+0000外的任意合法非代理 Unicode 码点(包括控制字符),但建议生成时仅用 ASCII 字母与数字 (AZ, az, 09)。

房间 ID 总长(含 ! 和域名)不得超过 255 字节。

房间别名

房间可有零个或多个别名。格式如下:

#room_alias:domain

domain 为创建别名的主服务器 服务器名,其他服务器可通过该主服务器解析别名。

别名的 localpart 可包含除 :NUL 的任意合法非代理 Unicode 码点。

房间别名总长(含 # 和域名)不得超过 255 字节。

事件 ID

事件有唯一事件 ID。格式如下

$opaque_id

但具体格式依 房间版本规范 而异。早期房间版本事件 ID 包含 domain 组件,较新版本则省略,使用 base64 编码散列。

除房间版本要求外,事件 ID (含 $ 和 domain如有总长不得超过 255 字节。

事件 ID 区分大小写。不建议为人类可读,客户端应完全视为不透明字符串处理。

URI

Matrix 内资源有两种主要引用方式matrix.to 及 matrix: URI。当前规范均认定上述两者为有效的实体/资源引用方式。

房间、用户及别名均可通过 URI 表示。可用于特定上下文引用对象如在消息中提及用户或提供房间历史的永久链接permalink

Matrix URI 方案

{{% added-in v="1.2" %}}

Matrix URI 格式定义如下([] 表存在可选部分,{} 表变量):

matrix:[//{authority}/]{type}/{id without sigil}[/{type}/{id without sigil}...][?{query}][#{fragment}]

作为模式,可表示为:

MatrixURI = "matrix:" hier-part [ "?" query ] [ "#" fragment ]
hier-part = [ "//" authority "/" ] path
path = entity-descriptor ["/" entity-descriptor]
entity-descriptor = nonid-segment / type-qualifier id-without-sigil
nonid-segment = segment-nz ; 见 RFC 3986
type-qualifier = segment-nz "/" ; 见 RFC 3986
id-without-sigil = string ; 详见上方 Matrix 标识符规范
query = query-element *( "&" query-item )
query-item = action / routing / custom-query-item
action = "action=" ( "join" / "chat" )
routing = "via=” authority
custom-query-item = custom-item-name "=" custom-item-value
custom-item-name = 1*unreserved ; 反向 DNS 名
custom-item-value =

此格式遵循 RFC 3986,以确保与现有工具最大兼容。方案名(matrix)已被 IANA 注册:点此查看

目前,authorityfragment 未由本规范使用仅作预留。Matrix 并无可合理填充 authority 的中央权威。schema 中的 nonid-segment 也为未来预留。

type 指明实体类型,id without sigil 即去除开头符号的标识符。目前用法如下:

  • r:房间别名
  • u:用户
  • roomid:房间 ID区别于房间别名
  • e:事件(位于房间 ID roomid 之后),跟在房间别名 r 后的 e 已不推荐使用。

{{% boxes/note %}} 在此 URI 格式开发过程中曾使用过 userroomevent 等类型,现在必须转为 ureroomid 没有变化。 {{% /boxes/note %}}

{{% boxes/note %}} {{% changed-in v="1.11" %}} 在通过房间别名(r)而非房间 IDroomid)引用房间内事件 ID 的方式现已弃用。未发现实际用例,以及房间别名本身可变,故不再支持。 {{% /boxes/note %}}

id without sigil 即实体标识符去掉 sigil。例如 !room:example.org 变为 room:example.org! 为房间 ID sigilsigil 定义见 通用标识符格式

query 可选,用于提示客户端 URI 意图。当前规范如下:

  • action——用于指示客户端具体动作。无 action 则表明 URI 只标识资源,无建议操作(如可跳转到对象信息页)。
    • action=join——表明客户端应尝试加入 URI 所示房间,仅适用于房间相关 URI。若用户未加入房间客户端应先询问用户。
    • action=chat——表明客户端应尝试发起/打开与 URI 指定用户的 DM私聊仅适用于用户相关 URI。如支持 Canonical DM应重用现有 DM。若未跳转到已有 DM客户端宜先询问用户。
  • via——指定解析或操作资源时可尝试的服务器authority 语法)。如 下文 所述,用于房间 ID 路由推荐,也适合用于非公开联邦的标识符解析。更完整方案见 MSC3020

自定义参数可用 通用命名空间标识符格式,并按规定编码(如百分比编码和转义 &)。与规范参数冲突时,客户端应优先标准项。为兼容不同客户端,建议自定义参数保持一致;建议广泛有用的参数提案纳入规范。

常见 URI 示例:

  • 指向 #somewhere:example.org 的链接:matrix:r/somewhere:example.org
  • 指向 !somewhere:example.org 的链接:matrix:roomid/somewhere:example.org?via=elsewhere.ca
  • 指向房间 !somewhere:example.org 内事件 $event 的链接:matrix:roomid/somewhere:example.org/e/event?via=elsewhere.ca
  • @alice:example.org 私聊链接:matrix:u/alice:example.org?action=chat

推荐客户端实现算法见 原始 MSC 文档

matrix.to 导航

{{% boxes/note %}} matrix.to 是早于 matrix: URI 方案的命名空间 URI。不要将其视为可公开解析 Web 服务,仅做规范说明。 {{% /boxes/note %}}

matrix.to URI 格式如下,基于 RFC 3986

https://matrix.to/#/<identifier>/<extra parameter>?<additional arguments>

其中 <identifier> 可为房间 ID、房间别名或用户 ID。仅在引用事件 ID 的永久链接中使用 <extra parameter>。matrix.to URI 需以 https://matrix.to/#/ 和标识符开头。

<additional arguments> 及前导问号可选,详见下文。

客户端不应用后台回退至 Web 服务器而应在客户端内解析。例如点击房间别名 URI 时弹出参与房间的界面。

URI 组成部分应按 RFC 3986 百分号编码。

matrix.to URI 示例:

  • 指向 #somewhere:example.org 的链接:https://matrix.to/#/%23somewhere%3Aexample.org
  • 指向 !somewhere:example.org 的链接:https://matrix.to/#/!somewhere%3Aexample.org?via=elsewhere.ca
  • 指向房间 !somewhere:example.org 内事件 $event 的链接:https://matrix.to/#/!somewhere%3Aexample.org/%24event%3Aexample.org?via=elsewhere.ca
  • 指向 @alice:example.org 的链接:https://matrix.to/#/%40alice%3Aexample.org

{{% boxes/note %}} {{% changed-in v="1.11" %}} 通过房间别名指代房间内事件 ID 的方式现已弃用。未发现实际用例,且房间别名非唯一、可变,不应继续支持。 {{% /boxes/note %}}

{{% boxes/note %}} 历史上,客户端生成的 URI 未总是充分编码。客户端应尽力兼容,例:未编码的房间别名应尽量支持。 {{% /boxes/note %}}

{{% boxes/note %}} 解码 matrix.to URI 可能出现多余斜杠,这与部分 房间版本 有关。生成 URI 时推荐编码斜杠。 {{% /boxes/note %}}

{{% boxes/note %}} 旧版规范曾提及用“群组”组织房间,但未正式引入规范内容,现已被 Spaces 取代。历史 matrix.to URI 指向群组形式如 https://matrix.to/#/%2Bexample%3Aexample.org+ sigil 可编码也可不编码)。 {{% /boxes/note %}}

路由

房间 ID 本身不可路由,无可靠域名接收请求。引入 via 参数后可部分缓解,但不可彻底解决。客户端应尽力选择好路由目标,但也应注意相关 问题 #1579

若房间或其永久链接未用房间别名,建议 URI 查询参数含至少一个 via 指定服务器。可多次添加 via 指定多个服务器。

via 参数内容建议用于 客户端服务器 /join/{roomIdOrAlias} API

生成房间链接和永久链接时,应选能长期存在的高概率服务器。具体选择方法为实现细节,当前建议选取 3 个唯一服务器,规则如下:

  • 第一个服务器为房间内最高权限且等级不低于 50 的用户所在服务器若无则选成员最多的服务器。高权限用户通常100稳定性高更能长期留在房间。
  • 第二个服务器为按规模排序的下一个或成员最多的服务器。因用户多的服务器更可能长期参与,不易被移除。
  • 第三个服务器为下一个成员最多的服务器。
  • 被服务器 ACL 阻止的服务器不得被选择。
  • IP 地址不得作服务器应更倾向使用域名。IP 不可迁移,风险高。
  • 三个服务器应互不重复。实际不足三台则只指定现有数量。例如仅 2 用户的房间最多提供 2 个 via

不透明标识符

规范定义某些标识符采用不透明标识符语法。本语法适用于无需解析或解释的非用户可见标识符,仅要求全局唯一。

语法定义:

  • 标识符仅可含 [0-9][A-Z][a-z]-._~
  • 若无特殊说明,长度不少于 1 字符且不超过 255 字符。

{{% boxes/note %}} 字符集与 RFC 3986 的 unreserved 字符一致。 {{% /boxes/note %}}

密钥表示约定

有时需在用户界面展示私有加密密钥。

此时,密钥 以如下字符串格式呈现:

  1. 创建字节数组,先为 0x8B0x01 两字节,再接原始密钥字节。
  2. 将上述所有字节(含前两字节)异或,得出校验字节,并将该字节附加至数组尾。
  3. 用 base58 编码全部字节,字母表为 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
  4. 每 4 个字符插入一个空格。

读取密钥时,客户端应忽略所有空白,并按 1-4 步逆序解析。

{{% boxes/note %}} base58 字母表与比特币地址一致。 {{% /boxes/note %}}

3PID 类型

第三方标识符3PID是指在其它命名空间中与某人关联的标识符。由 medium(标识命名空间)与 address(该空间内的字符串)元组组成。address 必须是标识符的规范格式,即在可有多种表示的情况下,只能选用其中一种。

例如,电邮的 medium 为 'email'address 为 email 地址,如 bob@example.com。域名解析不区分大小写,因此 bob@Example.com 的 3PID 也应写为 bob@example.com(小写 'e'),而非 bob@Example.com

本规范下定义命名空间如下,后续版本可扩展更多。

电子邮件

Medium: email

表示电子邮件地址,address 格式为 user@domain 且域名全部小写,不能含姓名、尖括号或 mailto: 前缀。

除将电邮域名部分转为小写外,实现还应遵循 Unicode 标准第五章“无大小写匹配” 算法进行大小写归一。例如 Strauß@Example.com 处理时应视作 strauss@example.com

PSTN 电话号码

Medium: msisdn

表示公用电话网电话号码。address 以 MSISDNE.164 编号规则)格式表示的电话号码。注意 MSISDN 不带 '+' 前缀。

Glob 风格匹配

某些场景下需要用 glob通配符匹配字符串。Matrix 的 glob 匹配遵循:

  • * 匹配零个或多个字符。
  • ? 匹配恰好一个字符。

点分属性路径

通过“点”连接属性名能直观表达事件属性路径,如 content.body 表示事件的 contentbody 属性。

如属性名自身包含点,为避免歧义,点与反斜杠应以反斜杠转义。例如,content.m\.relates_to 表示 content 下名为 m.relates_to 的属性。类似地,content.m\\foo 表示名为 m\foo 的属性。

其它转义序列原样保留,例如 \x 按字面解释为反斜杠加 x。建议实现不做冗余转义因其它转义序列将来也可能赋予特殊含义。

安全威胁模型

拒绝服务

攻击者可能尝试阻止消息送达或发送,从而:

  • 破坏竞争对手的服务或市场推广
  • 审查讨论或某参与者
  • 实施一般性破坏

威胁:资源耗尽

攻击者可能导致受害服务器消耗殆尽某项资源(如开放 TCP 连接、CPU、内存、磁盘

威胁:不可恢复一致性冲突

攻击者可发送消息导致集群产生无法恢复的“脑裂”状态,致使受害服务器无法得出房间一致状态。

威胁:篡改历史

攻击者可能诱使受害者接受无效消息,被其他服务器拒绝,并因此波及依赖其消息的后续消息。

威胁:阻断网络流量

攻击者可试图隔离受害服务器与部分或全部房间服务器间流量。

威胁:高流量消息攻击

攻击者可向目标房间发送高流量消息,致其难以使用。

威胁:无授权用户封禁

攻击者可能未经授权封禁房间用户。

冒充

攻击者可能尝试伪造受害者身份发送消息,以:

  • 从事非法活动冒充受害者
  • 窃取受害者权限

威胁:篡改消息内容

攻击者可能修改受害者已发消息内容。

威胁:伪造消息 "origin" 字段

攻击者可能发送新消息,冒充受害者并伪造 "origin" 字段。

垃圾信息Spam

攻击者可尝试向受害者批量发送消息,以:

  • 寻找诈骗对象
  • 推销不需要的商品

威胁:非请求消息

攻击者可向不愿接收者发送消息。

威胁:辱骂消息

攻击者可向受害者发送辱骂或威胁消息。

窃听

攻击者可尝试获取并非发往自身的受害者消息内容或元数据,以:

  • 获取敏感个人信息或商业信息
  • 利用信息冒充受害者(如重置密码邮件)
  • 了解受害者的交流对象与时间

威胁:传输泄露

攻击者可能在服务器间传输中泄露消息内容或元数据。

威胁:泄露给房间外服务器

攻击者可能诱使房间内服务器向未获授权的攻击者服务器发送消息。

威胁:泄露给房间内服务器

攻击者占有房间内服务器后可暴露该房间消息及元数据。

加密测试向量

为帮助开发兼容实现,以下测试值可用于验证加密事件签名代码。

签名密钥

以下所有测试向量均使用下列 base64 编码字符串解码得出的 32 字节值,作为生成 ed25519 签名密钥的种子:

SIGNING_KEY_SEED = decode_base64(
    "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA1"
)

均采用以下服务器名及密钥 ID

SERVER_NAME = "domain"

KEY_ID = "ed25519:1"

JSON 签名

给定空 JSON 对象:

{}

签名算法应输出:

{
    "signatures": {
        "domain": {
            "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
        }
    }
}

给定有内容的 JSON 对象:

{
    "one": 1,
    "two": "Two"
}

签名算法应输出:

{
    "one": 1,
    "signatures": {
        "domain": {
            "ed25519:1": "KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"
        }
    },
    "two": "Two"
}

事件签名

极简事件如下:

{
    "room_id": "!x:domain",
    "sender": "@a:domain",
    "origin": "domain",
    "origin_server_ts": 1000000,
    "signatures": {},
    "hashes": {},
    "type": "X",
    "content": {},
    "prev_events": [],
    "auth_events": [],
    "depth": 3,
    "unsigned": {
        "age_ts": 1000000
    }
}

签名算法应输出:

{
    "auth_events": [],
    "content": {},
    "depth": 3,
    "hashes": {
        "sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos"
    },
    "origin": "domain",
    "origin_server_ts": 1000000,
    "prev_events": [],
    "room_id": "!x:domain",
    "sender": "@a:domain",
    "signatures": {
        "domain": {
            "ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg"
        }
    },
    "type": "X",
    "unsigned": {
        "age_ts": 1000000
    }
}

带有可撤回内容的事件如下:

{
    "content": {
        "body": "Here is the message content"
    },
    "event_id": "$0:domain",
    "origin": "domain",
    "origin_server_ts": 1000000,
    "type": "m.room.message",
    "room_id": "!r:domain",
    "sender": "@u:domain",
    "signatures": {},
    "unsigned": {
        "age_ts": 1000000
    }
}

签名算法应输出:

{
    "content": {
        "body": "Here is the message content"
    },
    "event_id": "$0:domain",
    "hashes": {
        "sha256": "onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g"
    },
    "origin": "domain",
    "origin_server_ts": 1000000,
    "type": "m.room.message",
    "room_id": "!r:domain",
    "sender": "@u:domain",
    "signatures": {
        "domain": {
            "ed25519:1": "Wm+VzmOUOz08Ds+0NTWb1d4CZrVsJSikkeRxh6aCcUwu6pNC78FunoD7KNWzqFn241eYHYMGCA5McEiVPdhzBA"
        }
    },
    "unsigned": {
        "age_ts": 1000000
    }
}

Matrix API 约定

本节主要用于指导 Matrix 新 API 的设计人员,统一 API 行为规则,以保持协议一致性,提升开发体验。

HTTP 接口和 JSON 属性命名

HTTP 传输的 API 接口名称一律使用下划线分隔。例如 /delete_devices

API 中 JSON 对象的键名也遵循该命名约定。

{{% boxes/note %}} 历史上有部分例外,如 /createRoom。这些不一致的问题未来版本可能会统一。 {{% /boxes/note %}}

分页

支持多页结果的 REST API 端点应符合如下约定。

  • 若有更多结果,接口返回名为 next_batch 的属性,其值为字符串 token可在后续接口请求中用以获取下一页。

    若无更多结果,则 省略 next_batch 属性。

  • 接口接受名为 from 的查询参数,客户端应赋以前次返回的 next_batch 值。

  • 若接口支持双向分页(如 /messages,可向前/后遍历时间线),则应返回 prev_batch 属性,可指向上一页。

    避免用额外的“方向”参数。next_batchprev_batch 的 token 应已包含区分页向的信息。