diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-07 21:04:56 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-07 21:04:56 +0200 |
| commit | 3bd31db9d4fa2352f1095dcc630c77915d0774b8 (patch) | |
| tree | 5674ddabdf4037ee5b7c2d1bc682e941d7a7da01 /packages/meshbay-hub/src/meshbay_hub/static/locales | |
| parent | 7fa74d722108ca4338d14db4ecba53800df86fca (diff) | |
| download | meshbay-3bd31db9d4fa2352f1095dcc630c77915d0774b8.tar.gz | |
feat(chat): Tier 2 — a member verifies another member's device itself
Chat messages have been signed by the sending device since MNP 2.0, but a
reader had no way to know that the device belonged to the account the node
named: the signature proved *a device*, and `sender_id` was still the node's
word. This closes that for any account a client has already seen.
**What was blocking it was not effort — the evidence was not being kept.**
`_do_device_add` verified the countersignature that admits a second device and
stored only `added_by_pk`: *which* key approved, never the proof. And
`device_add_transcript` binds `nonce_node`, the approving connection's handshake
nonce, so even a stored signature was unverifiable by anyone who had not been on
that connection. `identities` gains `add_sig`, `add_nonce` and `add_ts`, added
before the migration's early return — which fires on every roster widened since
2026-08-18, i.e. all of them, so putting them inside it would have meant they
never arrived.
`group_roster_req`/`resp` relays, sealed under a new groupbox purpose and
answered to **any member of the group**, every live device of every active
member with the evidence that admitted it. The node decides nothing: it hands
over evidence and the client walks the chain from each account's root outwards
(`_verifyRoster`). That is deliberate — the node is the party the property holds
against, so it is not asked to assert trust.
Two holes the tests caught while this was being built:
- "no signature" was being treated as a trust root, so a node that writes the
roster could put any key in an account's row and have it laundered straight
into the verified set. A root is a device that names **no** countersigner.
- pinning only the verified subset at first sight raised "key changed" on
legitimate second devices whose countersignature predates this change. First
sight pins everything the node says, because that is what trust-on-first-use
means and an alarm that fires on normal events stops being read.
The property, and it must not be rounded up: **once a client has seen an
account, a node that later substitutes a key for it is detected. Nothing is
gained at first sight**, where there is nothing to compare against — the same
boundary `per-node-identity-v1.md` draws, unmoved.
The cost, stated because it is real: the roster is member-visible, so every
member learns how many devices the others hold and their public keys. It stays
inside the group, the hub is not involved, and it is scoped per group. A member
who cannot see the keys cannot check them.
User-visible surface: one notice, "this account is using a key you have not seen
before", in ten languages. Nothing else.
16 tests — 7 on the node (the evidence is stored, it verifies from the roster
alone, a fabricated device carries none, another group's members are not
disclosed), 9 running the shipped `_verifyRoster` under node against rosters
built by the shipped Python: a chain of three in any order, a signature by the
wrong key, one for another node, one for another account, and two fabricated
devices signing each other admitting nothing.
Tier 3 (operator-signed roster attestation) stays deferred, with nothing
depending on it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZZxYjz8YeWRz13xDi8LJr
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/locales')
10 files changed, 20 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js index 180c762..e5febde 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js @@ -659,6 +659,8 @@ export default { 'chat.unreadable_decrypt': 'Diese Nachricht konnte nicht entschlüsselt werden', 'chat.unreadable_envelope': 'Diese Nachricht kam unvollständig an', 'chat.unreadable_format': 'Diese Nachricht erfordert eine neuere Version von MeshBay', + 'chat.key_changed': 'Dieses Konto verwendet einen Schlüssel, den Sie noch nie gesehen haben', + 'chat.key_changed_hint': 'Der Node sagt, diese Nachricht komme von diesem Konto, aber dessen Geräteschlüssel ist keiner, den Sie akzeptiert haben, und nichts hat ihn signiert. Es kann ein anderswo hinzugefügtes Gerät sein — oder der Node, der die falsche Person nennt.', 'chat.encrypted_needs_newer': 'Diese Unterhaltung ist verschlüsselt und dieser Client kann sie nicht lesen — MeshBay aktualisieren', 'chat.encrypted_cannot_send': 'Diese Unterhaltung ist verschlüsselt und dieses Gerät kann noch nicht darin schreiben', 'group.leave': 'Gruppe verlassen', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js index 996d5b6..a171304 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js @@ -775,6 +775,8 @@ export default { 'chat.unreadable_decrypt': 'This message could not be decrypted', 'chat.unreadable_envelope': 'This message arrived incomplete', 'chat.unreadable_format': 'This message needs a newer version of MeshBay', + 'chat.key_changed': 'This account is using a key you have not seen before', + 'chat.key_changed_hint': 'The node says this message came from this account, but its device key is not one you have accepted before and nothing signed it into place. It may be a new device added elsewhere — or the node naming the wrong person.', 'chat.encrypted_needs_newer': 'This conversation is encrypted and this client cannot read it — update MeshBay', 'chat.encrypted_cannot_send': 'This conversation is encrypted and this device cannot post to it yet', 'group.leave': 'Leave group', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js index 10fac89..3b081cf 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js @@ -655,6 +655,8 @@ export default { 'chat.unreadable_decrypt': 'No se pudo descifrar este mensaje', 'chat.unreadable_envelope': 'Este mensaje llegó incompleto', 'chat.unreadable_format': 'Este mensaje necesita una versión más reciente de MeshBay', + 'chat.key_changed': 'Esta cuenta usa una clave que no has visto antes', + 'chat.key_changed_hint': 'El nodo dice que este mensaje viene de esta cuenta, pero su clave de dispositivo no es una que hayas aceptado y nada la firmó. Puede ser un dispositivo nuevo añadido en otro sitio — o el nodo nombrando a la persona equivocada.', 'chat.encrypted_needs_newer': 'Esta conversación está cifrada y este cliente no puede leerla: actualiza MeshBay', 'chat.encrypted_cannot_send': 'Esta conversación está cifrada y este dispositivo aún no puede escribir en ella', 'group.leave': 'Salir del grupo', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js index f470bf1..7ecd4a2 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js @@ -658,6 +658,8 @@ export default { 'chat.unreadable_decrypt': 'Ce message n\'a pas pu être déchiffré', 'chat.unreadable_envelope': 'Ce message est arrivé incomplet', 'chat.unreadable_format': 'Ce message nécessite une version plus récente de MeshBay', + 'chat.key_changed': 'Ce compte utilise une clé que vous n\'avez jamais vue', + 'chat.key_changed_hint': 'Le nœud affirme que ce message vient de ce compte, mais sa clé d\'appareil n\'est pas une de celles que vous avez acceptées et rien ne l\'a signée. Ce peut être un nouvel appareil ajouté ailleurs — ou le nœud qui nomme la mauvaise personne.', 'chat.encrypted_needs_newer': 'Cette conversation est chiffrée et ce client ne peut pas la lire — mettez MeshBay à jour', 'chat.encrypted_cannot_send': 'Cette conversation est chiffrée et cet appareil ne peut pas encore y écrire', 'group.leave': 'Quitter le groupe', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js index f971320..6ae92cc 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js @@ -657,6 +657,8 @@ export default { 'chat.unreadable_decrypt': 'Impossibile decifrare questo messaggio', 'chat.unreadable_envelope': 'Questo messaggio è arrivato incompleto', 'chat.unreadable_format': 'Questo messaggio richiede una versione più recente di MeshBay', + 'chat.key_changed': 'Questo account usa una chiave che non hai mai visto', + 'chat.key_changed_hint': 'Il nodo dice che questo messaggio viene da questo account, ma la sua chiave di dispositivo non è una che hai accettato e nulla l\'ha firmata. Può essere un nuovo dispositivo aggiunto altrove — o il nodo che nomina la persona sbagliata.', 'chat.encrypted_needs_newer': 'Questa conversazione è cifrata e questo client non può leggerla: aggiorna MeshBay', 'chat.encrypted_cannot_send': 'Questa conversazione è cifrata e questo dispositivo non può ancora scriverci', 'group.leave': 'Esci dal gruppo', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js index 0817608..e5a4a21 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js @@ -645,6 +645,8 @@ export default { 'chat.unreadable_decrypt': 'このメッセージを復号できませんでした', 'chat.unreadable_envelope': 'このメッセージは不完全な状態で届きました', 'chat.unreadable_format': 'このメッセージには新しいバージョンの MeshBay が必要です', + 'chat.key_changed': 'このアカウントは見覚えのない鍵を使っています', + 'chat.key_changed_hint': 'ノードはこのメッセージがこのアカウントからだと言っていますが、その端末鍵はあなたが受け入れたものではなく、署名もありません。別の場所で追加された新しい端末かもしれませんし、ノードが別人を名乗らせているのかもしれません。', 'chat.encrypted_needs_newer': 'この会話は暗号化されており、このクライアントでは読めません — MeshBay を更新してください', 'chat.encrypted_cannot_send': 'この会話は暗号化されており、この端末はまだ投稿できません', 'group.leave': 'グループを退出', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js index 25bf89d..7bee011 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js @@ -659,6 +659,8 @@ export default { 'chat.unreadable_decrypt': 'Dit bericht kon niet worden ontsleuteld', 'chat.unreadable_envelope': 'Dit bericht kwam onvolledig aan', 'chat.unreadable_format': 'Dit bericht vereist een nieuwere versie van MeshBay', + 'chat.key_changed': 'Dit account gebruikt een sleutel die u nog niet eerder zag', + 'chat.key_changed_hint': 'De node zegt dat dit bericht van dit account komt, maar de apparaatsleutel is er geen die u hebt geaccepteerd en niets heeft hem ondertekend. Het kan een elders toegevoegd apparaat zijn — of de node die de verkeerde persoon noemt.', 'chat.encrypted_needs_newer': 'Dit gesprek is versleuteld en deze client kan het niet lezen — werk MeshBay bij', 'chat.encrypted_cannot_send': 'Dit gesprek is versleuteld en dit apparaat kan er nog niet in schrijven', 'group.leave': 'Groep verlaten', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js index d63d4ae..ddcf9bc 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js @@ -677,6 +677,8 @@ export default { 'chat.unreadable_decrypt': 'Nie udało się odszyfrować tej wiadomości', 'chat.unreadable_envelope': 'Ta wiadomość dotarła niekompletna', 'chat.unreadable_format': 'Ta wiadomość wymaga nowszej wersji MeshBay', + 'chat.key_changed': 'To konto używa klucza, którego wcześniej nie widziałeś', + 'chat.key_changed_hint': 'Węzeł twierdzi, że ta wiadomość pochodzi z tego konta, ale jego klucz urządzenia nie jest jednym z zaakceptowanych i nic go nie podpisało. Może to być nowe urządzenie dodane gdzie indziej — albo węzeł wskazujący niewłaściwą osobę.', 'chat.encrypted_needs_newer': 'Ta rozmowa jest zaszyfrowana i ten klient nie może jej odczytać — zaktualizuj MeshBay', 'chat.encrypted_cannot_send': 'Ta rozmowa jest zaszyfrowana i to urządzenie nie może jeszcze w niej pisać', 'group.leave': 'Opuść grupę', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js index f23f237..ce31383 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js @@ -656,6 +656,8 @@ export default { 'chat.unreadable_decrypt': 'Não foi possível descriptografar esta mensagem', 'chat.unreadable_envelope': 'Esta mensagem chegou incompleta', 'chat.unreadable_format': 'Esta mensagem exige uma versão mais recente do MeshBay', + 'chat.key_changed': 'Esta conta está usando uma chave que você nunca viu', + 'chat.key_changed_hint': 'O nó diz que esta mensagem veio desta conta, mas a chave do dispositivo não é uma que você aceitou e nada a assinou. Pode ser um novo dispositivo adicionado em outro lugar — ou o nó apontando a pessoa errada.', 'chat.encrypted_needs_newer': 'Esta conversa é criptografada e este cliente não consegue lê-la — atualize o MeshBay', 'chat.encrypted_cannot_send': 'Esta conversa é criptografada e este dispositivo ainda não pode escrever nela', 'group.leave': 'Sair do grupo', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js index cd0332c..260d11c 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js @@ -632,6 +632,8 @@ export default { 'chat.unreadable_decrypt': '无法解密此消息', 'chat.unreadable_envelope': '此消息传输不完整', 'chat.unreadable_format': '此消息需要更新版本的 MeshBay', + 'chat.key_changed': '此账户使用了你未见过的密钥', + 'chat.key_changed_hint': '节点称这条消息来自该账户,但其设备密钥不是你接受过的,也没有任何签名为它背书。可能是在别处新增的设备——也可能是节点指认了错误的人。', 'chat.encrypted_needs_newer': '该对话已加密,此客户端无法读取 — 请更新 MeshBay', 'chat.encrypted_cannot_send': '该对话已加密,此设备尚无法在其中发言', 'group.leave': '退出群组', |