Skip to content

Commit 7d6e443

Browse files
committed
feat: Port SignatureListItem to Rust
1 parent 3e6bf10 commit 7d6e443

5 files changed

Lines changed: 116 additions & 17 deletions

File tree

rust/src/e2e_keys/mod.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* This file is licensed under the Affero General Public License (AGPL) version 3.
3+
*
4+
* Copyright (C) 2026 Element Creations Ltd
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
*
11+
* See the GNU Affero General Public License for more details:
12+
* <https://www.gnu.org/licenses/agpl-3.0.html>.
13+
*
14+
* Originally licensed under the Apache License, Version 2.0:
15+
* <http://www.apache.org/licenses/LICENSE-2.0>.
16+
*
17+
* [This file includes modifications made by Element Creations Ltd]
18+
*/
19+
20+
use pyo3::{
21+
pyclass, pymethods,
22+
types::{PyAnyMethods, PyModule, PyModuleMethods},
23+
Bound, Py, PyAny, PyResult, Python,
24+
};
25+
26+
pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
27+
let child_module = PyModule::new(py, "e2e_keys")?;
28+
child_module.add_class::<SignatureListItem>()?;
29+
30+
m.add_submodule(&child_module)?;
31+
32+
py.import("sys")?
33+
.getattr("modules")?
34+
.set_item("synapse.synapse_rust.e2e_keys", child_module)?;
35+
36+
Ok(())
37+
}
38+
39+
/// A pending cross-signing signature.
40+
#[derive(Debug)]
41+
#[pyclass(frozen)]
42+
pub struct SignatureListItem {
43+
/// Full key ID of the signing key, e.g. `"ed25519:ABCDEF"`.
44+
#[pyo3(get)]
45+
pub signing_key_id: String,
46+
47+
/// User whose key was signed.
48+
#[pyo3(get)]
49+
pub target_user_id: String,
50+
51+
/// Device ID (or master-key ID) that the signature targets.
52+
#[pyo3(get)]
53+
pub target_device_id: String,
54+
55+
/// Raw signature value.
56+
#[pyo3(get)]
57+
pub signature: Py<PyAny>,
58+
}
59+
60+
#[pymethods]
61+
impl SignatureListItem {
62+
#[new]
63+
fn py_new(
64+
signing_key_id: String,
65+
target_user_id: String,
66+
target_device_id: String,
67+
signature: Py<PyAny>,
68+
) -> Self {
69+
Self {
70+
signing_key_id,
71+
target_user_id,
72+
target_device_id,
73+
signature,
74+
}
75+
}
76+
77+
fn __repr__(&self) -> String {
78+
format!(
79+
"SignatureListItem(signing_key_id={:?}, target_user_id={:?}, target_device_id={:?})",
80+
self.signing_key_id, self.target_user_id, self.target_device_id,
81+
)
82+
}
83+
}

rust/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use pyo3_log::ResetHandle;
77
pub mod acl;
88
pub mod canonical_json;
99
pub mod duration;
10+
pub mod e2e_keys;
1011
pub mod errors;
1112
pub mod events;
1213
pub mod http;
@@ -64,6 +65,7 @@ fn synapse_rust(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
6465

6566
acl::register_module(py, m)?;
6667
push::register_module(py, m)?;
68+
e2e_keys::register_module(py, m)?;
6769
events::register_module(py, m)?;
6870
http_client::register_module(py, m)?;
6971
rendezvous::register_module(py, m)?;

synapse/handlers/e2e_keys.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import logging
2323
from typing import TYPE_CHECKING, Iterable, Mapping
2424

25-
import attr
2625
from canonicaljson import encode_canonical_json
2726
from signedjson.key import VerifyKey, decode_verify_key_bytes
2827
from signedjson.sign import SignatureVerifyException, verify_signed_json
@@ -35,6 +34,7 @@
3534
from synapse.handlers.device import DeviceWriterHandler
3635
from synapse.logging.context import make_deferred_yieldable, run_in_background
3736
from synapse.logging.opentracing import log_kv, set_tag, tag_args, trace
37+
from synapse.synapse_rust.e2e_keys import SignatureListItem
3838
from synapse.types import (
3939
JsonDict,
4040
JsonMapping,
@@ -1132,7 +1132,7 @@ async def upload_signatures_for_device_keys(
11321132

11331133
async def _process_self_signatures(
11341134
self, user_id: str, signatures: JsonDict
1135-
) -> tuple[list["SignatureListItem"], dict[str, dict[str, dict]]]:
1135+
) -> tuple[list[SignatureListItem], dict[str, dict[str, dict]]]:
11361136
"""Process uploaded signatures of the user's own keys.
11371137
11381138
Signatures of the user's own keys from this API come in two forms:
@@ -1150,7 +1150,7 @@ async def _process_self_signatures(
11501150
Raises:
11511151
SynapseError: if the input is malformed
11521152
"""
1153-
signature_list: list["SignatureListItem"] = []
1153+
signature_list: list[SignatureListItem] = []
11541154
failures: dict[str, dict[str, JsonDict]] = {}
11551155
if not signatures:
11561156
return signature_list, failures
@@ -1252,7 +1252,7 @@ def _check_master_key_signature(
12521252
signed_master_key: JsonDict,
12531253
stored_master_key: JsonMapping,
12541254
devices: dict[str, dict[str, JsonDict]],
1255-
) -> list["SignatureListItem"]:
1255+
) -> list[SignatureListItem]:
12561256
"""Check signatures of a user's master key made by their devices.
12571257
12581258
Args:
@@ -1296,7 +1296,7 @@ def _check_master_key_signature(
12961296

12971297
async def _process_other_signatures(
12981298
self, user_id: str, signatures: dict[str, dict]
1299-
) -> tuple[list["SignatureListItem"], dict[str, dict[str, dict]]]:
1299+
) -> tuple[list[SignatureListItem], dict[str, dict[str, dict]]]:
13001300
"""Process uploaded signatures of other users' keys. These will be the
13011301
target user's master keys, signed by the uploading user's user-signing
13021302
key.
@@ -1312,7 +1312,7 @@ async def _process_other_signatures(
13121312
Raises:
13131313
SynapseError: if the input is malformed
13141314
"""
1315-
signature_list: list["SignatureListItem"] = []
1315+
signature_list: list[SignatureListItem] = []
13161316
failures: dict[str, dict[str, JsonDict]] = {}
13171317
if not signatures:
13181318
return signature_list, failures
@@ -1747,16 +1747,6 @@ def _one_time_keys_match(old_key_json: str, new_key: JsonDict) -> bool:
17471747
return old_key == new_key_copy
17481748

17491749

1750-
@attr.s(slots=True, auto_attribs=True)
1751-
class SignatureListItem:
1752-
"""An item in the signature list as used by upload_signatures_for_device_keys."""
1753-
1754-
signing_key_id: str
1755-
target_user_id: str
1756-
target_device_id: str
1757-
signature: JsonDict
1758-
1759-
17601750
class SigningKeyEduUpdater:
17611751
"""Handles incoming signing key updates from federation and updates the DB"""
17621752

synapse/storage/databases/main/end_to_end_keys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@
6161
from synapse.util.json import json_decoder, json_encoder
6262

6363
if TYPE_CHECKING:
64-
from synapse.handlers.e2e_keys import SignatureListItem
6564
from synapse.server import HomeServer
65+
from synapse.synapse_rust.e2e_keys import SignatureListItem
6666

6767

6868
@attr.s(slots=True, auto_attribs=True)

synapse/synapse_rust/e2e_keys.pyi

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import Any
2+
3+
class SignatureListItem:
4+
"""A pending cross-signing signature."""
5+
6+
signing_key_id: str
7+
""" Full key ID of the signing key, e.g. `"ed25519:ABCDEF"`."""
8+
9+
target_user_id: str
10+
"""User whose key was signed."""
11+
12+
target_device_id: str
13+
"""Device ID (or master-key ID) that the signature targets."""
14+
15+
signature: Any
16+
"""Raw signature value."""
17+
18+
def __init__(
19+
self,
20+
signing_key_id: str,
21+
target_user_id: str,
22+
target_device_id: str,
23+
signature: Any,
24+
) -> None: ...

0 commit comments

Comments
 (0)