Этап 3: TOFU и комнаты — ключ комнаты, атомарный rekey, участники
Сервер: комнаты, состав и завёрнутые ключи; смена состава и rekey одним атомарным запросом с проверкой keys[].to против итогового состава; обрезка до двух последних ключей; передача владения по joined_at; события room каждому со своим ключом и room_left удалённым; членство и keyId в проверках сообщения; миграция 002. Клиент: TOFU на всех путях, по которым публичный ключ доходит до клиента; предупреждение о смене ключа с блокировкой отправки и повторной расшифровкой сохранённого raw; заворачивание и разворачивание ключей комнаты, включая себе — тем же кодом, без ветвления; расшифровка любым известным keyId; экраны участников, создание комнаты, карточка контакта с двумя отпечатками. ADR-037: roomId генерирует клиент. crypto.md вплетает roomId в заворачивание, а оно происходит до запроса — создатель обязан привязать ключ к идентификатору, которого по прежнему протоколу ещё не существовало. ADR-039: завёрнутый ключ принимается только от участника. Иначе сервер подставляет ключ, завёрнутый посторонним аккаунтом, TOFU молчит — ник незнакомый, первый ключ запоминается молча, — и комната уезжает на ключ сервера. Одно подменённое поле в ответе, без сговора и подмены кода. ADR-041: долг по rekey — состояние комнаты, а не свойство события. Владелец, офлайн в момент выхода участника, не узнавал о долге никогда, и комната навсегда оставалась на ключе, который вышедший знает. ADR-038, 040, 042, 043, 044: тексты экранов комнат, снятие pending при возврате прежнего ключа, порядок ключей, форма раньше прав, экран покинутой комнаты. Приёмка на боевом: комната на троих, добавленный четвёртый читает только новое, вышедший после rekey новых не получает и писать не может, keys_mismatch, key_exists, not_owner, owner, room_conflict, чужой X-Device. Подмена public_key в базе даёт у собеседника предупреждение и блокирует отправку — проверено в Chrome. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DbCjVfTFq4ZFG8juD45YJ
This commit is contained in:
+91
-1
@@ -393,7 +393,20 @@ input[type="password"] {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* имя комнаты бывает длиной в 64 символа (ADR-018): в шапке оно
|
||||
обрезается, как и в строке сайдбара, — иначе страница едет вбок */
|
||||
|
||||
.head .chat-title,
|
||||
.head .title {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.back {
|
||||
flex: none;
|
||||
min-height: 32px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--edge);
|
||||
@@ -444,12 +457,73 @@ input[type="password"] {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.fp-label {
|
||||
margin: 12px 0 4px;
|
||||
font-size: 11px;
|
||||
color: var(--stone);
|
||||
}
|
||||
|
||||
.block--first > .fp-label:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.fp-hint {
|
||||
margin: 10px 0 0;
|
||||
font-size: 11px;
|
||||
color: var(--mute);
|
||||
}
|
||||
|
||||
/* участники комнаты — docs/ui.md, «Участники» */
|
||||
|
||||
.members {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.member {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 28px;
|
||||
font-size: 13px;
|
||||
color: var(--text2);
|
||||
}
|
||||
|
||||
.member__name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.member .tag {
|
||||
color: var(--stone);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.member .link {
|
||||
margin-left: auto;
|
||||
color: var(--mute);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* строка ввода как отдельная форма: «новый чат» и «добавить участника» */
|
||||
|
||||
.form--row + .form--row {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.form--row .button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* две кнопки подряд не слипаются в двойную рамку */
|
||||
|
||||
.block > .button + .button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* чат: шапка, лента, ввод — docs/identity/screens.html */
|
||||
|
||||
.chat-title {
|
||||
@@ -663,6 +737,13 @@ input[type="password"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ввод заблокирован предупреждением о ключе: кнопка «>» тоже гаснет */
|
||||
|
||||
.input__send[disabled] {
|
||||
color: var(--stone);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.counter,
|
||||
.enter {
|
||||
flex: none;
|
||||
@@ -675,15 +756,24 @@ input[type="password"] {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
/* полоса над вводом: нет соединения — stone, отказ отправки — mark */
|
||||
/* полоса над вводом: нет соединения — stone, отказ отправки и смена
|
||||
ключа — mark. У полосы про ключ есть кнопка, поэтому строка — flex */
|
||||
|
||||
.bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0 8px;
|
||||
margin: 0 0 10px;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
color: var(--stone);
|
||||
}
|
||||
|
||||
.bar[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bar--mark {
|
||||
color: var(--mark);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user