Сердце формирования детерминированной цепочки находится в файле alert_chaining.c.
Вся магия состоит из трех небольших, но фундаментальных функций:
1. Вычисление хэша содержимого (Слепое шифрование)
Сервер вообще не расшифровывает сообщение. Он берет «сырые» зашифрованные байты и превращает их в 64-битный хэш содержимого:
/* Единая точка вычисления content_hash.
* Хэшируются только зашифрованные данные, поэтому сервер
* обеспечивает математическую честность, не имея ключей доступа. */
uint64_t alert_chain_compute_content(const Alert *a) {
XXH3_state_t state;
XXH3_64bits_reset(&state);
/* 1. Зашифрованное тело алерта */
XXH3_64bits_update(&state, a->text, a->text_len);
/* 2. Зашифрованный AES-ключ */
XXH3_64bits_update(&state, a->encrypted_key, a->encrypted_key_len);
/* 3. Вектор инициализации (IV) */
XXH3_64bits_update(&state, a->iv, a->iv_len);
/* 4. Аутентификационный тэг GCM (16 байт) */
XXH3_64bits_update(&state, a->tag, 16);
return XXH3_64bits_digest(&state);
}2. Математическое связывание звеньев (Криптографический замок)
Здесь текущий алерт намертво привязывается к своему порядковому ID, хэшу предыдущего алерта и своему содержимому:
/* Единая точка связывания звеньев цепи (Link Hash).
* Если изменить хотя бы 1 бит в ID, в предыдущем хэше или в теле алерта —
* на выходе получится совершенно другой curr_hash. */
uint64_t alert_chain_compute_link(uint64_t id, uint64_t prev_h, uint64_t cont_h) {
/* Формируем неразрывную тройку: ID + Хэш предка + Хэш содержимого */
uint64_t data[3] = { id, prev_h, cont_h };
/* Вычисляем сверхбыстрый 64-битный хэш XXH3 с нулевым сидом */
return XXH3_64bits_withSeed(data, sizeof(data), 0);
}3. Главный конвейер: Вставка и самоисцеление цепи (Re-chaining)
Вот этот участок кода разруливает сплит-брейны, делает BACKFILL и на лету пересчитывает будущее, если в середину цепи пришел пропущенный алерт:
/* Главная функция сборки цепи.
* Вызывается при добавлении любого алерта (от клиента или из репликации). */
void alert_chain_process_insertion(Recipient *rec, Alert *new_alert,
uint64_t remote_prev_hash,
uint64_t remote_curr_hash) {
(void)remote_prev_hash; /* Игнорируем сетевые подсказки: цепь строго детерминирована */
(void)remote_curr_hash;
/* ШАГ 1: Всегда локально считаем хэш содержимого нового алерта */
new_alert->content_hash = alert_chain_compute_content(new_alert);
/* ШАГ 2: Через бинарный поиск O(log N) находим точное хронологическое место алерта */
int pos = find_insert_position(rec, new_alert->id);
/* ШАГ 3: Привязываем новый алерт к предку */
if (pos == 0) {
/* Если это самый первый элемент в истории — у него нет предка (генезис) */
new_alert->prev_hash = 0;
} else {
/* Иначе жестко берем хэш стоящего перед ним алевра (pos - 1) */
new_alert->prev_hash = rec->alerts[pos - 1].curr_hash;
}
/* ШАГ 4: Запечатываем текущий алерт — вычисляем его собственный хэш */
new_alert->curr_hash = alert_chain_compute_link(
new_alert->id, new_alert->prev_hash, new_alert->content_hash);
/* ШАГ 5: МАШИНА ВРЕМЕНИ (Каскадный Re-chaining)
* Если алерт встал в середину массива (BACKFILL после сплит-брейна),
* то для всех последующих алертов родительский хэш изменился!
* Мы последовательно обновляем цепочку вправо до самого конца окна: */
uint64_t running_prev = new_alert->curr_hash;
for (int i = pos; i < rec->count; i++) {
Alert *cur = &rec->alerts[i];
/* Переназначаем предка для следующего алерта */
cur->prev_hash = running_prev;
/* Мгновенно пересчитываем его хэш с новым предком */
cur->curr_hash = alert_chain_compute_link(
cur->id, cur->prev_hash, cur->content_hash);
/* Двигаем волну обновления дальше в будущее */
running_prev = cur->curr_hash;
}
/* ШАГ 6: Фиксируем актуальную верхушку цепи для получателя */
if (rec->count > 0) {
rec->last_hash = rec->alerts[rec->count - 1].curr_hash;
} else {
rec->last_hash = new_alert->curr_hash;
}
}В чем красота этой реализации:
- Всего 3 строки на звено: Никаких тяжелых структур данных, указателей или связных списков — чистая прямая арифметика в плоском массиве памяти.
- Неуязвимость: Невозможно подменить сообщение в прошлом, не сломав
curr_hashу всех последующих элементов. - Мгновенная скорость: Хэш XXH3 считается со скоростью оперативной памяти (гигабайты в секунду), поэтому сдвиг и пересчет цепи на 1000 элементов занимают микросекунды, совершенно не нагружая CPU.