montana/Монтана-Протокол/Архив/Montana v28.4.0.md

2887 lines
248 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Montana — Спецификация протокола
**Версия:** 28.4.0 (2026-04-14)
## Определение
Montana — персональный P2P интернет на протоколе времени. Защищённое хранение данных, приватная связь и монеты времени — на узле пользователя.
Протокол Montana — фундамент персонального интернета. Сеть независимых VDF-осцилляторов, производящая каноническую последовательность событий через последовательное хэширование и консенсус между узлами. Каждое канонически зарегистрированное окно = 13 Ɉ (TimeCoin).
**Montana Time** — реляционная структура, конституируемая последовательным хэшированием в рамках VDF и канонической упорядоченностью, устанавливаемой консенсусом между узлами. Внутри этой структуры время в протоколе существует как последовательность канонических событий. Montana — самодостаточная система отсчёта: каноническая последовательность событий, которую внешние системы могут наблюдать и использовать как reference frame для своих нужд.
### Три проблемы доверия
Montana решает три проблемы, каждая без участия третьей стороны:
- **Доверие ко времени.** Протокол производит каноническую последовательность событий без внешних источников. Решено протокольным слоем: VDF, consensus, window_index.
- **Доверие к хранению.** Данные пользователя хранятся на его узле. Протокол предоставляет фундамент: identity (account_id), фиксация (Anchor — 32 байта хэш навсегда), стимул держать узел online (лотерея, TimeCoin). Хранение, шифрование, индексация — клиентский слой.
- **Доверие к коммуникации.** Связь между пользователями идёт через их узлы, без центрального посредника. Протокол предоставляет: P2P сеть, identity, постквантовое шифрование (ML-KEM). Мессенджер, discovery, профили — клиентский слой.
### Четыре слоя персонального интернета
Протокол и клиентский слой вместе образуют четыре слоя:
**1. Агент-посредник.** ИИ-агент (Juno) действует строго от имени пользователя. Фильтрует и приоритизирует информацию по критериям владельца, не по алгоритмам платформы. Может ходить во внешний интернет, собирать данные, но решения о фильтрации принадлежат человеку. *Реализация: клиентский слой (app spec).*
**2. Локальное хранилище знаний.** Всё что пользователь читал, сохранял, получал — индексировано, доступно для поиска, хранится на его узле. Не на серверах корпорации. Контекст накапливается со временем — персональная база знаний. Протокол фиксирует факт существования (Anchor = 32B хэш). Содержание — на узле владельца, зашифровано его ключом. *Реализация: протокол (Anchor, identity) + клиентский слой (хранение, индексация, поиск).*
**3. Управление вниманием.** Персональный интернет не максимизирует время пользователя в системе, а минимизирует его. Дал нужное — отпустил. Нет алгоритмической ленты, нет рекламы, нет engagement metrics, нет autoplay. Бизнес-модель Montana — эмиссия через узлы, не торговля вниманием. *Реализация: клиентский слой (app spec) + экономическая конструкция протокола (лотерея, TimeCoin).*
**4. Контроль данных.** Пользователь решает какие данные о нём существуют и кто имеет доступ. Не «политика конфиденциальности на 40 страниц», а технические механизмы: локальное шифрование на узле, выборочное предоставление доступа через адресное шифрование (ML-KEM), optional публикация профиля и контактов. Балансы публичны по дизайну ([I-2]). Всё остальное — решение владельца. *Реализация: протокол (I-2, модель приватности, Anchor = хэш без содержания) + клиентский слой (шифрование, selective sharing).*
### Архитектурное условие
Персональный интернет = протокол + свой узел + клиентский слой. Без любого из трёх — не работает.
- **Без протокола** — нет канонического времени, нет identity, нет Anchor, нет стимула. Клиентский слой не на чем строить.
- **Без своего узла** — данные на чужом узле. Это чужой iCloud в другой форме. Кошелёк и мессенджер работают, но обещание «мои данные под моим контролем» не выполняется.
- **Без клиентского слоя** — протокол производит примитивы, но человек не может ими воспользоваться. Нет приложения — нет продукта.
Montana делает личную инфраструктуру посильной: commodity hardware (минимум 1 ядро), окупаемость через лотерею (TimeCoin), открытое ПО. Но решение поставить узел — за человеком.
### Три примитива протокола
Протокол производит три примитива:
- **Каноническое время** — window_index, детерминированная последовательность событий через VDF
- **Передача ценности** — Transfer между аккаунтами, балансы в Account Table
- **Фиксация данных** — Anchor, 32 байта data_hash с привязкой к window_index, навсегда
Всё за пределами этих трёх примитивов — хранение данных, коммуникация, агенты, индексация, интерфейсы — реализуется клиентским слоем поверх протокола. Протокол — часы, бухгалтерия и нотариат. Серверов нет — каждый узел сети равноправен, принадлежит своему оператору и работает на своём оборудовании.
Консенсус: **Proof of Time (PoT)** — TimeChain: глобальная каноническая цепь (D последовательных SHA-256 = одно окно). NodeChain: последовательность cemented BundledConfirmation узла (доказательство присутствия). AccountChain: счётчик окон активности аккаунта. AccountTable: состояние счёта. Влияние узла = длина его NodeChain (chain_length = число окон с cemented BundledConfirmation). Протокол **и есть** структура отношений между событиями, оцифрованная и криптографически верифицируемая. Один узел = одно ядро CPU.
Genesis: symbolic window 0. Перевод window_index в любые внешние time scales является задачей клиентского слоя.
Генезис-фраза: `«Кто контролирует прошлое, контролирует будущее. Кто контролирует настоящее, контролирует прошлое.» — Оруэлл, 1984`
Эволюция протокола: открытые предложения (MIPs — Montana Improvement Proposals) публикуются как рекомендации, реализации выпускают новые версии, операторы узлов выбирают какую версию запускать. Fork resolution детерминирован через chain_length большинство. On-chain governance отсутствует. См. раздел «Эволюция протокола».
---
## Три решённые проблемы
### 1. Каноническая временная координата
**Проблема.** Существующие системы измерения времени (NTP, GPS, PTP) измеряют физическое время через доверенную инфраструктуру. Компрометация сервера NTP или отключение спутника GPS нарушает временную шкалу для всех зависимых систем. Использование таких систем в консенсусе протокола создаёт subjective input в consensus state.
**Решение.** Реляционная временная структура — сеть независимых VDF-осцилляторов, производящая каноническую последовательность событий через собственную работу. Каждый узел вычисляет цепочку событий автономно через последовательное SHA-256 хэширование. Результат детерминирован и верифицируем любым участником из canonical inputs.
**Свойства.** Montana Time обладает четырьмя свойствами:
- **Монотонность.** window_index строго возрастает. VDF последователен — каждый хэш зависит от предыдущего. Канонический порядок событий однозначен.
- **Детерминизм.** Все честные узлы согласны bit-exact на структуру событий — window_index, T_r, state_root. Каждое поле consensus state объективно вычислимо всеми узлами.
- **Верифицируемость.** Любой может пересчитать VDF и проверить каждое событие последовательности.
- **Независимость.** Каждый узел считает самостоятельно, опираясь только на canonical inputs протокола.
Montana и NTP/GPS/PTP — системы разных типов. NTP и GPS измеряют физическое время через внешние источники. Montana производит каноническую последовательность событий через собственную работу VDF и consensus.
### 2. Неплутократический консенсус
**Проблема.** В существующих консенсусных механизмах влияние пропорционально вычислительному бюджету или капиталу. Безопасность сети является функцией концентрации ресурсов, приобретаемых на рынке.
**Решение.** Proof of Time — механизм консенсуса, в котором влияние узла определяется исключительно длительностью его непрерывного присутствия в сети, измеренной в подписанных временных окнах. Вес узла = длина его NodeChain (количество окон, в которых узел криптографически доказал своё присутствие).
**Свойства.**
- Время — единственный ресурс, который нельзя приобрести, передать, делегировать или сконцентрировать
- Два участника, запустившие узлы одновременно, имеют равный вес независимо от капитала
- Стоимость атаки на консенсус выражается не в валюте, а во времени, и растёт линейно с возрастом сети
### 3. Window-based эмиссия
**Проблема.** Денежная политика фиатных валют определяется решениями комитетов и непредсказуема. Дефляционные модели с фиксированным потолком supply создают ожидание роста цены и подавляют использование как средства обмена.
**Решение.** Window-based эмиссия — денежная политика, в которой количество новых единиц за одно каноническое окно фиксировано и неизменно на всём горизонте существования протокола. Одно окно Montana Time порождает 13 единиц TimeCoin.
**Свойства.**
- Supply после окна W = `13 × (W + 1)` Ɉ
- Эмиссия линейна по window_index — инфляция монотонно убывает и асимптотически стремится к нулю
- Эмиссия не контролируется ни одним участником, комитетом или голосованием
- Денежная политика полностью определена единственной константой (13 Ɉ за окно) и не может быть изменена после генезиса
- Физическая скорость выпуска в SI-секундах определяется скоростью hardware сети и остаётся свойством клиентского слоя, вне scope консенсуса
### Следствие: цифровой reference frame времени без человека-посредника
Три решённые проблемы порождают уникальную возможность. Любой документ, событие, состояние может быть записано в Montana с математически доказуемой привязкой к канонической позиции в последовательности событий (window_index). Anchor — 32 байта, навсегда. Montana — не блокчейн с функцией timestamping. Montana — reference frame времени с функцией передачи ценности. Внешние системы могут наблюдать последовательность окон Montana и строить собственные переводы в свои локальные стандарты — этот перевод является задачей наблюдателя, не протокола.
Ни один человек, группа разработчиков, корпорация или совет не контролирует протокол. Изменения существуют только как открытые предложения и реализации, которые операторы узлов выбирают запускать.
---
## Глобальные инварианты протокола
Глобальный инвариант — свойство, которое протокол обязан сохранять во всех своих компонентах. Нарушение в одной части = нарушение во всём протоколе. Глобальные инварианты не имеют исключений и не подлежат локальному trade-off.
**[I-1] Постквантовая безопасность.** Все криптографические примитивы устойчивы к квантовому компьютеру. Допустимо: SHA-256 (Grover ослабляет до 128-bit, приемлемо), FN-DSA-512 (Falcon, lattice), ML-KEM (Kyber), ML-DSA (Dilithium), STARK (hash-based ZK), lattice commitments. Запрещено: ECDLP, RSA, классический Diffie-Hellman, Pedersen commitments на эллиптических кривых, Bulletproofs, Schnorr/EdDSA.
**[I-2] Открытость финансового слоя.** Балансы, суммы переводов, отправители, получатели — публичны. Никакого криптографического сокрытия на уровне протокола. См. «Модель приватности».
**[I-3] Детерминизм consensus state.** Любое состояние, входящее в consensus root, объективно вычислимо одинаково всеми узлами.
**Corollary I-3.a.** Любой механизм, результат которого в consensus state или в protocol-level behavior (mempool prioritization, gossip ordering, fork-choice, peer scoring) зависит от измерения физического мира — астрономического, геофизического, атомного, биологического или любого другого — отклоняется по нарушению I-3. Corollary применяется независимо от точности модели измерения.
**[I-4] Независимость TimeChain от Account state.** TimeChain продвигается из canonical inputs без зависимости от состояния Account Table. Зависимости однонаправленные: TimeChain → NodeChain (presence tracking) → AccountChain → AccountTable.
**[I-5] Реализуемость без специализированного оборудования.** Все примитивы имеют production-ready open-source реализации, работающие на commodity CPU узла без TEE, без обязательного GPU, без обязательного ASIC.
**[I-6] Регуляторная совместимость.** Протокол опирается на механизмы, совместимые с FATF/AML/MiCA/ETF. Запрещено: privacy mixers на уровне протокола, anonymous addresses, hidden flows, ring signatures, stealth addresses.
**[I-7] Минимальная криптографическая поверхность.** Каждый новый примитив требует обоснования закрытием конкретного механизма. Дублирование функциональности через два разных примитива запрещено.
### Модель приватности
Протокол разделяет публичное и приватное одним принципом: **consensus state — публичен, данные пользователя — за пределами протокола**.
- **Публично (consensus state):** балансы, суммы переводов, отправители, получатели, window_index, node_id, chain_length. Это следствие [I-2]: финансовый слой открыт для верификации.
- **В протоколе, но без содержания:** Anchor содержит data_hash (32 байта). Что за этим хэшем — протоколу неизвестно.
- **За пределами протокола:** данные пользователя (фото, сообщения, файлы) хранятся на узле владельца. Шифрование, формат хранения, доступ — решения клиентского слоя. Сеть не хранит, не реплицирует и не видит эти данные. Ключ шифрования — у владельца. Без ключа данные на узле — шум.
Протокол не предоставляет privacy через криптографическое сокрытие (нет ring signatures, нет hidden amounts, нет stealth addresses — [I-6]). Приватность данных обеспечивается архитектурно: данные не попадают в протокол. Протокол видит 32 байта хэша и всё.
### Language firewall
В нормативном тексте спецификации Montana допустимые термины для описания протокольных объектов, счётчиков, периодов или интервалов: `window`, `tick`, `epoch`, `cycle` — определённые через window counts. Термины физического времени (`second`, `minute`, `hour`, `day`, `week`, `month`, `year`) применяются только в advisory контекстах клиентского слоя и в описании транспортного уровня (implementation guidance).
---
## Montana Time
VDF — цифровой осциллятор в собственных единицах. `D` последовательных SHA-256 = одно окно τ₁ Montana. Число D представляет канонический объём работы, конституирующий единицу Montana Time.
TimeChain — глобальная каноническая цепь, поддерживаемая сетью узлов. Каждый узел вычисляет её независимо через последовательное хэширование. Результат детерминирован bit-exact — одни входные данные дают одну каноническую последовательность.
Токен — каноническая регистрация одного окна Montana Time. Протокол производит канонические окна и регистрирует каждое из них как 13 Ɉ.
### Определение Montana Time
```
montana_time(W) := W
```
Единственное каноническое определение времени в протоколе. Всё остальное — производные или advisory вычисления клиентского слоя.
Одно окно = `D` последовательных SHA-256 итераций от предыдущего canonical anchor. D фиксируется в Genesis Decree и может адаптироваться runtime-ом через participation-ratio feedback (см. раздел «Адаптация D»).
### Четыре свойства
- **Монотонность.** `window_index` строго возрастает. VDF последователен — каждый хэш зависит от предыдущего.
- **Детерминизм.** Все честные узлы согласны bit-exact на window_index, T_r, state_root. Каждое поле consensus state объективно вычислимо всеми узлами.
- **Верифицируемость.** Любой может пересчитать VDF и проверить каждое событие последовательности.
- **Независимость.** Каждый узел вычисляет канон сам, опираясь только на canonical inputs протокола.
Montana и NTP/GPS/PTP — системы разных типов. NTP и GPS измеряют физическое время через внешние источники. Montana производит каноническую последовательность событий через собственную работу VDF и consensus.
### Гранулярность
Атом Montana Time — одна SHA-256 итерация. Окно Montana Time — `D` атомов. Произвольный интервал — `N` окон. Все три уровня выражены в канонических числах, на которые bit-exact согласны все узлы.
Физическая длительность одной итерации зависит от hardware узла (наносекунды — десятки наносекунд на commodity CPU). Физическая длительность окна зависит от скорости железа узла и от участия сети. Физическая длительность — свойство конкретного наблюдателя, выводимое на клиентском слое.
### Time Oracle
Canonical `window_index` каждого proposal — верифицируемая координата события. Внешние системы используют Montana Time как reference frame:
- **Timestamping.** H(document) привязанный к window_index = криптографическое доказательство существования в позиции W канонической последовательности.
- **Ordering.** Два события, привязанные к разным window_index, имеют доказуемый канонический порядок.
- **Anchoring.** Внешний протокол якорится в Montana Time для независимой верификации порядка событий.
Перевод `window_index → физическое время` в любых внешних стандартах (UTC, TAI, GPS Time) является задачей клиентского слоя. Montana производит каноническую последовательность окон; внешний наблюдатель выбирает собственный метод привязки window_index к своим локальным временным единицам.
TimeChain хранится навсегда. Канонические координаты верифицируемы любым узлом в любой момент.
---
## Криптография
Два примитива с разделёнными ролями:
- **SHA-256** — консенсус (TimeChain), lottery endpoints, адреса, Merkle-деревья, хэширование
- **FN-DSA-512** (Falcon-512, выбран в финальном раунде NIST PQC selection, июль 2022; forthcoming FIPS 206; reference implementation production-ready) — подписи операций аккаунтов и proposals узлов
SHA-256 обеспечивает квантовую устойчивость консенсуса: алгоритм Гровера сокращает безопасность с 256 до 128 бит. FN-DSA-512 обеспечивает математическую постквантовую устойчивость подписей на основе NTRU-решёток. Других криптографических примитивов в протоколе нет — финансовый слой публичен, приватность данных обеспечивается на уровне приложений через Anchor.
### Подписи — FN-DSA-512
Подпись на NTRU-решётках (Falcon-512). Stateless, многоразовая. Публичный ключ закрепляется за аккаунтом при создании и используется для всех последующих операций.
| Компонент | Размер |
|-----------|--------|
| Приватный ключ | 1 281B |
| Публичный ключ | 897B |
| Подпись (padded) | 666B |
Поле suite_id в формате блока обеспечивает миграцию подписи без изменения модели состояния. Активация новой схемы требует protocol upgrade. Активная схема на момент запуска: FN-DSA-512.
### Адреса
Формат: `mt` + Base58(account_id + checksum).
Account_id = SHA-256("mt-account" || suite_id || pubkey). Стабильный идентификатор аккаунта. Смена ключа или схемы подписи выполняется через ChangeKey без изменения account_id — account_id привязан к первому pubkey, а текущий ключ хранится в состоянии аккаунта.
**Инвариант derivation.** Проверка `account_id == SHA-256("mt-account" || suite_id || pubkey)` происходит **один раз** при settle OpenAccount (apply at window close). После этого account_id — каноничный ключ записи, формула не пересчитывается. Доказательство derivation навсегда сохранено в proposal с финализированным OpenAccount. Любой аудитор может replay из proposal history. Original_pubkey не дублируется в Account Table — integrity гарантируется неизменностью proposal chain.
Поле `suite_id` в Account Table — **current** (мутируется ChangeKey синхронно с current_pubkey), используется для верификации текущих подписей. Original suite_id зафиксирован только в исторической OpenAccount записи в proposal chain.
---
## Account Chain (Block Lattice)
Каждый аккаунт имеет собственную цепочку операций. Перевод — одна операция в цепочке отправителя. Зачисление получателю — детерминированно после финализации. Цепочки аккаунтов полностью независимы.
### Реестр типов объектов
```
UserObjects:
0x01 OpenAccount
0x02 Transfer
0x03 ChangeKey
0x04 Anchor
ControlObjects:
0x11 NodeRegistration
```
### Типы операций
**Универсальная форма операции:**
```
type (1B) | prev_hash (32B) | payload (variable) | signature (666B)
```
Все операции — этот шаблон. `prev_hash` связывает операции в цепочку аккаунта. `signature` — FN-DSA-512 владельца. `payload` зависит от типа. Все non-OpenAccount операции начинают payload с `sender (32B account_id)` — узел проверяет `Account Table[sender].frontier_hash == prev_hash` и `signature валиден для current_pubkey` за O(1).
**OpenAccount** — создание аккаунта (один раз). Единственная операция где `prev_hash = 0x00...00`:
```
type 1B <- 0x01 OpenAccount
prev_hash 32B <- 0x00...00
payload 899B <- suite_id (2B) || pubkey (897B FN-DSA-512)
signature 666B
Итого: ~1 598 B
```
`account_id = SHA-256("mt-account" || suite_id || pubkey)` — детерминирован, не хранится в payload.
**Transfer** — публичный перевод:
```
type 1B <- 0x02 Transfer
prev_hash 32B
payload 80B <- sender (32B) || link (32B receiver) || amount (16B u128 nɈ)
signature 666B
Итого: ~779 B
```
`sender` — account_id отправителя, явно. Узел проверяет `Account Table[sender].frontier_hash == prev_hash` за O(1).
Открытые поля: отправитель (через frontier index по prev_hash), получатель, сумма, баланс после операции (через Account Table). Псевдонимность на уровне account_id. Финансовая приватность — задача приложений (микшеры, payment channels), не протокола.
**ChangeKey** — смена ключа или схемы подписи:
```
type 1B <- 0x03 ChangeKey
prev_hash 32B
payload 931B <- sender (32B) || new_suite_id (2B) || new_pubkey (897B)
signature 666B <- подписано старым ключом
Итого: ~1 630 B
```
**Anchor** — криптографический якорь (привязка данных ко времени):
```
type 1B <- 0x04 Anchor
prev_hash 32B
payload 96B <- sender (32B) || app_id (32B) || data_hash (32B)
signature 666B
Итого: ~795 B
```
Anchor не перемещает средства и не требует комиссии. Единственная операция — запись data_hash в цепочку аккаунта с привязкой к timechain_value окна финализации. Приватность данных приложения обеспечивается тем что в сеть попадает только хэш — содержимое хранится у владельца зашифрованным.
### Верификация баланса
Открытое арифметическое сравнение. Узел проверяет:
```
sender != receiver
amount > 0
sender.balance >= amount
```
`sender != receiver` запрещает self-transfer — иначе атакующий мог бы наращивать account_chain_length каждое окно через no-op переводы себе.
При settle (apply at window close):
```
sender.balance -= amount
receiver.balance += amount
```
Баланс обновляется не при cement (quorum event), а в конце окна при батчевом apply. Между cement и settle операция необратима но баланс ещё не изменён. Никаких proofs, никакой криптографии помимо подписи и хэша.
### Anti-inflation
Чеканка из воздуха невозможна через локальный инвариант на каждом state transition.
**Per-user-operation invariant.** Каждое применение пользовательской операции обязано удовлетворять `Σ delta_balance == 0`:
```
Transfer: sender.balance -= amount, receiver.balance += amount → Σ = 0
OpenAccount: новый аккаунт с balance = 0 → Σ = 0
ChangeKey: только обновление current_pubkey → Σ = 0
Anchor: только запись data_hash → Σ = 0
```
**Per-proposal invariant.** Каждый финализированный proposal окна τ₁ обязан удовлетворять `delta_supply == +13 Ɉ`:
```
apply_proposal step 2 (TimeCoin emission):
если winner_class = Node: operator_account.balance += 13_000_000_000 nɈ
если winner_class = Account: winner_account.balance += 13_000_000_000 nɈ
delta_supply за proposal = +13_000_000_000 nɈ ровно один раз
```
O(1) проверка на каждое state transition. Глобальный инвариант `Σ balance == 13 Ɉ × (window_index + 1)` истинен по индукции от genesis при условии что каждый переход поддерживает per-operation invariant.
```
genesis state (аксиома): window_index не определён, supply = 0, Σ balance = 0
первое окно: window_index = 0, supply = 13 Ɉ, Σ balance = 13 Ɉ
окно k: window_index = k, supply = 13 × (k+1) Ɉ, Σ balance = 13 × (k+1) Ɉ
```
Никаких откатов cemented операций не требуется — каждое cemented локально валидно по конструкции.
**τ₂ sanity check.** Дополнительная проверка раз в τ₂: пересчёт `Σ balance` по всей Account Table и сравнение с `13 Ɉ × (window_index + 1)`. Не load-bearing для финализации — служит для обнаружения багов реализации. Расхождение = немедленная остановка узла, дамп state для расследования.
### Перевод
Перевод на несуществующий account_id — отклоняется. Получатель обязан существовать в Account Table до получения перевода.
### TimeCoin
Победитель τ₁ регистрирует одно окно Montana Time: 13 Ɉ. При финализации proposal окна:
```
если winner_class = Node: operator_account.balance += 13_000_000_000 nɈ
если winner_class = Account: winner_account.balance += 13_000_000_000 nɈ
```
Атомарное обновление баланса. Узел получает награду через привязанный operator_account (зафиксирован при NodeRegistration). Никаких отдельных coinbase-структур, никаких отдельных таблиц эмиссии. Зачисление есть состояние Account Table.
```
Публичное (верифицируемо всеми):
TimeCoin: 13 Ɉ за окно (константа)
Supply audit: supply(window_index) = 13_000_000_000 × (window_index + 1) nɈ
Winner: winner_id в proposal header
Все балансы: Account Table
Все переводы: цепочки операций аккаунтов
VDF: TimeChain values, lottery endpoints, подписи
```
Псевдонимность на уровне account_id. Финансовая приватность — задача приложений: микшеры, payment channels, off-chain settlements.
### Двойная трата
Каждый аккаунт имеет одну цепочку. Две операции с одним prev_hash = equivocation.
**Без конфликта:** операция → узлы валидируют → публикуют confirmation → quorum → cemented (необратимо, ~0.3 сек). Баланс обновляется при settle (apply at window close).
**При конфликте (equivocation):**
1. Узел получает операцию X с prev_hash = H. Узел уже видел операцию Y с prev_hash = H, Y ≠ X. Форк обнаружен. Обе операции помечаются как equivocated.
2. Если одна операция уже cemented (quorum до обнаружения конфликта) — cemented необратимо. Вторая отклоняется.
3. Если ни одна не cemented — узлы продолжают собирать confirmations для обеих. Если одна набирает quorum → cemented, вторая отклоняется.
4. Если через 13 окон ни одна не набрала quorum → обе отклоняются окончательно. Аккаунт продолжает с последней cemented операции. Владелец отправляет новую операцию.
Equivocation создаётся только владельцем аккаунта (требуется подпись). Третья сторона не может создать equivocation для чужого аккаунта. Стимул: двойная трата = потеря обеих операций.
### Антиспам
Ноль комиссий — антиспам через время. Право на операцию = доказанное время существования аккаунта.
#### Приоритет операции
```
account_age = current_window - creation_window
priority(op) = account_age × windows_since_last_op
```
`account_age` — возраст аккаунта в окнах. Растёт линейно. Некупуемый. `windows_since_last_op` — окна с последней операции аккаунта. Сбрасывается при каждой операции. Спамер обнуляет приоритет с каждой операцией — самонаказание.
При переполнении ёмкости сети — операции с наименьшим приоритетом ожидают следующего окна.
#### Бакеты по account_age
Изоляция спама. Каждый аккаунт может опубликовать максимум одну операцию за окно τ₁ (dependency rule). При переполнении сети (больше операций в мемпуле чем пропускная способность окна) — бакеты определяют **приоритет включения**. Round-robin по бакетам: одна операция из бакета 0, одна из бакета 1, ..., по кругу. Спам в бакете 0 не вытесняет операции из бакетов 1-3.
```
Бакет 0: account_age < 4τ₂
Бакет 1: account_age 4τ₂ — 16τ₂
Бакет 2: account_age 16τ₂ — 64τ₂
Бакет 3: account_age 64τ₂+
```
Границы бакетов = 4^N × τ₂. Все аккаунты: максимум 1 операция за τ₁. Бакет определяет приоритет при переполнении, не потолок TPS.
Новый аккаунт — бакет 0 с момента создания. 1 операция за τ₁. Вход без ожидания: получил перевод → сразу можешь отправить.
#### Throughput на аккаунт
1 операция за τ₁ (одно окно). Один Anchor содержит Merkle root от произвольного количества записей — throughput данных ограничен только размером Anchor. Для высокочастотных переводов — payment channels или application-level batching.
Спамер с 1000 новых аккаунтов: 1000 операций за τ₁ в бакете 0. Бакет 0 получает 1/4 от round-robin. Изолирован. Аккаунты в бакетах 1-3 не замечают.
---
## Состояние сети
Глобальное состояние = Account Table + Node Table + Candidate Pool.
```
Account Table (запись на аккаунт):
account_id 32B <- = SHA-256("mt-account" || suite_id || pubkey)
balance 16B <- u128 nɈ, открыт
suite_id 2B
is_node_operator 1B <- 1 если аккаунт привязан как operator узла; исключён из лотереи аккаунтов
frontier_hash 32B <- хэш последней операции в цепочке
op_height 4B <- количество операций в цепочке
account_chain_length 4B <- количество уникальных окон τ₁ с операцией (длина AccountChain), live
account_chain_length_snapshot 4B <- snapshot account_chain_length на последнюю τ₂ boundary, используется лотереей
current_pubkey 897B <- FN-DSA-512
creation_window 4B <- окно создания аккаунта (OpenAccount)
last_op_window 4B <- окно последней операции (для приоритета)
Node Table (запись на узел):
node_id 32B <- SHA-256("mt-node" || node_pubkey), верифицируемо
node_pubkey 897B
suite_id 2B
operator_account_id 32B <- account_id куда зачисляется TimeCoin при победе узла; неизменен после регистрации
start_window 8B <- u64, окно регистрации (первое окно присутствия в Node Table)
chain_length 8B <- u64, позиция узла в NodeChain: = 1 при активации, +1 при cemented BundledConfirmation в окне. Инвариант: chain_length ≥ 1 для любого узла в Node Table
chain_length_snapshot 8B <- u64, = chain_length - chain_length_checkpoint[oldest]; используется в лотерее
chain_length_checkpoints 48B <- 6 × u64, checkpoint-ы chain_length на последних 6 τ₂-boundaries
last_confirmation_window 8B <- u64, window_index последнего окна с cemented BundledConfirmation
Candidate Pool (запись на кандидата):
node_id 32B <- SHA-256("mt-node" || node_pubkey)
node_pubkey 897B
suite_id 2B
operator_account_id 32B <- account_id куда зачисляется TimeCoin при победе
proof_endpoint 32B <- endpoint VDF цепочки (длина vdf_chain_length)
W_start 8B <- u64, окно начала VDF (заявлено кандидатом)
vdf_chain_length 8B <- u64, длина VDF цепочки от candidate_vdf_init до proof_endpoint (в "окнах" по D хэшей)
registration_window 8B <- u64, окно cementing NodeRegistration
expires 8B <- u64, registration_window + 3 × τ₂_windows
```
**Active node predicate (derived).** Узел считается активным если опубликовал cemented BundledConfirmation за последние 2τ₂:
```
active(node, W) = (W - node.last_confirmation_window) <= 2 × τ₂_windows
```
Predicate вычисляется из `last_confirmation_window` и текущего `window_index`. Применяется в quorum, confirmation_threshold, лотерее, валидации selection event.
### State Root
Merkle-дерево глобального состояния. Три подкорня обновляются при применении операций (apply_proposal и apply at window close):
```
state_root = SHA-256("mt-state-root" || node_root || candidate_root || account_root)
node_root: Merkle root Node Table, обновляется при selection event (регистрация),
chain_length increment (apply step 3.5), pruning узлов на τ₂.
candidate_root: Merkle root Candidate Pool, обновляется при cementing NodeRegistration
(добавление), selection event (удаление выбранных), expiry (удаление просроченных).
account_root: Merkle root Account Table, обновляется батчем при apply at window
close (все cemented операции окна применяются к state, затем
account_root пересчитывается).
Все три root соответствуют settled state (после apply at window close).
Порядок node_root → candidate_root → account_root отражает направление
зависимостей: узлы — активные участники, кандидаты — будущие узлы, аккаунты — финансовый слой.
Domain separator `mt-state-root` отличён от `mt-merkle-node` — hash spaces пересекаться не могут.
```
**Структура Account Table Root:**
Sparse Merkle tree глубины 256, индексированный по `account_id`:
```
leaf_hash(account) = SHA-256("mt-merkle-leaf" || serialize(account_record))
internal(left, right) = SHA-256("mt-merkle-node" || left || right)
empty_leaf = 0x00 × 32
account_root = root of sparse Merkle tree over Account Table
```
Обновление одного аккаунта пересчитывает ровно `log₂(N)` хэшей пути от листа к корню — для N=10⁹ аккаунтов это 30 SHA-256 вычислений (~60 µs CPU).
**Структура Node Table Root:** аналогично, sparse Merkle tree по `node_id`. Размер сети ≤ 10⁵ узлов → пути ~17 хэшей.
**Canonical serialization — single source of truth.** Определения полей каждой таблицы (Node Table, Account Table, Candidate Pool) задают canonical byte-for-byte сериализацию каждой записи. Эта сериализация используется одновременно для (1) вычисления leaf_hash в Merkle tree, (2) хранения на диске, (3) передачи через Fast Sync snapshot. Любое изменение record format требует одновременного обновления canonical encoding во всех трёх путях использования. Fast Sync автоматически следует за canonical encoding — см. раздел Fast Sync «Полнота сериализации snapshot».
**Структура Candidate Pool Root:** sparse Merkle tree глубины 256, индексированный по `node_id`. Empty root = `0x00 × 32`.
Каждый узел в Node Table — участник сети. Узел существует в таблице = участвует.
Все sort keys фиксированной длины. Побайтовое лексикографическое сравнение. Две реализации с одинаковыми данными строят одинаковое дерево и получают одинаковый State Root.
State Root коммитится в заголовке каждого proposal τ₁. `account_root`, `node_root` и `candidate_root` соответствуют settled state после apply at window close — все cemented операции окна W применены к таблицам перед сборкой proposal.
#### Inclusion proof
Любой cemented аккаунт может предоставить доказательство существования в state:
```
proof = Merkle path длиной log₂(N) (~30 хэшей для N=10⁹)
verify(proof, account_record, account_root):
reconstruct path bottom-up; compare с account_root
```
Доказательство верифицируется против `account_root` любого финализированного proposal начиная с окна когда состояние было обновлено. Не нужны архивы операций — текущее состояние самодостаточно.
#### Pruning
На τ₂ boundary применяется pruning неактивных аккаунтов:
```
Удалить все записи Account Table где:
balance == 0 <- нулевой баланс
AND last_op_window + 4τ₂ <= current_window <- нет активности 4τ₂ (52 000 окон)
AND is_node_operator == 0 <- не привязан как operator узла
AND нет cemented NodeRegistration в control_set <- нет pending привязки
ожидающего apply, ссылающегося на этот account_id
```
Пустой аккаунт без активности 4τ₂ — удаляется, кроме:
- Operator-аккаунтов уже зарегистрированных узлов (`is_node_operator == 1`)
- Аккаунтов на которые ссылается cemented NodeRegistration ожидающий apply
Без второго исключения возможна race: NodeRegistration cemented (operator валиден), pruning применился до apply этого NodeRegistration → аккаунт удалён → apply отклонён. Защита: pruning не трогает аккаунты, на которые есть cemented pending registration.
Каждое удаление пересчитывает соответствующий путь в Merkle tree (logarithmic). Pruning детерминирован, автоматичен, каноничен.
**Recovery semantics.** Воссоздание pruned аккаунта через новый OpenAccount с тем же ключом создаёт **новую цепочку**: frontier_hash начинается заново, op_height сбрасывается в 1, account_chain_length = 0. Старые prev_hash references на цепочку до pruning отклоняются — цепочка удалена из текущего state. История переводов до pruning не восстанавливается из текущего Account Table, но навсегда сохранена в proposals. Восстановление истории возможно через scan архива proposals.
---
## Двигатели
Односторонний поток зависимостей: TimeChain → NodeChain → AccountChain → AccountTable.
TimeChain — глобальные часы (ход времени, VDF). NodeChain — присутствие узла (последовательность cemented BundledConfirmation). AccountChain — присутствие аккаунта (дискретные операции). AccountTable — состояние счёта.
### TimeChain VDF — осциллятор
Первичный продукт протокола. Непрерывная последовательная SHA-256 цепочка — цифровой осциллятор Montana Time:
```
T_r = SHA-256^D(T_{r-1})
```
D — количество последовательных хэшей за одно окно τ₁. Каждый хэш — один тик осциллятора. D хэшей — одно колебание. TimeChain продвигается по расписанию окон. Для фиксированного индекса r значение T_r совпадает у всех честных узлов. Каждый узел вычисляет TimeChain независимо — результат детерминирован.
TimeChain не зависит от состояния, транзакций и поведения отдельных узлов. Даже при отказе всего Account слоя часы продолжают тикать.
### NodeChain — последовательность присутствия узла
Доказательство присутствия конкретного node_id в каждом окне. Каждое окно с cemented BundledConfirmation = одно звено NodeChain. chain_length — позиция узла в NodeChain: = 1 при активации (Genesis для bootstrap, selection event для нового узла), +1 при каждом cemented BundledConfirmation. Инвариант: chain_length ≥ 1 для любого узла в Node Table — гарантирует корректность знаменателей в weighted_ticket лотереи и в seniority_bonus.
NodeChain не является VDF-цепочкой. Узел доказывает присутствие публикацией BundledConfirmation (подтверждение операций сети), не вычислением per-node VDF. Один VDF на всю сеть (TimeChain) — достаточен.
NodeChain зависит от TimeChain (якорится через window_index). TimeChain не зависит от NodeChain.
**Liveness узла и сетевое включение.** Рост chain_length требует cementing BundledConfirmation через confirmation threshold 67% active_chain_length. При стандартной BFT-assumption (≥67% active_chain_length честны и достижимы по P2P) BC активного узла cemented в каждом окне участия. Изоляция узла от confirmers (eclipse, network partition, propagation failure) останавливает рост chain_length независимо от локальной работы узла. Это свойство consensus-механизма, не свойство узла: chain_length измеряет подтверждённое сетью присутствие, не локальную CPU-работу.
### AccountChain — персональная цепочка аккаунта
Криптографическое доказательство присутствия конкретного account_id в дискретных моментах. Каждое звено — финализированная операция аккаунта (Transfer, OpenAccount, ChangeKey, Anchor). Linking через `prev_hash` (хэш предыдущей операции в цепочке аккаунта). Якорится в TimeChain через timechain_value момента финализации каждой операции.
Длина AccountChain — количество окон τ₁ в которых аккаунт имел cemented операцию:
```
account_chain_length(account, W) = | { w : w <= W, аккаунт имел cemented операцию в окне w } |
```
Dependency rule ограничивает аккаунт одной операцией за окно τ₁ — поэтому длина AccountChain совпадает с числом окон активности. Поле `account_chain_length` хранится в Account Table, обновляется при apply операции:
```
on_operation_applied(operation, window W):
account = operation.account_id
account.account_chain_length += 1
account.last_op_window = W
account.op_height += 1
```
**Параллелизм NodeChain и AccountChain:**
| Свойство | NodeChain | AccountChain |
|----------|-----------|--------------|
| Источник | node_pubkey | account_pubkey |
| Идентификатор | node_id | account_id |
| Тип присутствия | машинное | человеческое |
| Ритм | непрерывный (каждое окно) | дискретный (окно с операцией) |
| Длина | chain_length (окна с BundledConfirmation) | account_chain_length (окна с операцией) |
| Единица длины | окно τ₁ | окно τ₁ |
| Накопление | автоматически при публикации BundledConfirmation | через активность пользователя |
| Защита от подделки | подпись FN-DSA-512 | подпись FN-DSA-512 |
| Защита от Sybil | 13 000 окон + selection event | накопление окон требует активности |
Узел доказывает присутствие публикацией BundledConfirmation в каждом окне. Аккаунт — операцией. Оба механизма верифицируемы, оба производят запись на одной шкале времени.
AccountChain зависит от TimeChain напрямую. AccountChain не зависит от NodeChain по построению.
### VDF Reveal и лотерея
В лотерее участвуют два класса субъектов: **узлы** (через VDF_Reveal) и **аккаунты** (через cemented операции). Каждый класс производит ticket, взвешенный по длине своей цепочки.
Confirmers (~100 узлов с наибольшим chain_length) публикуют BundledConfirmation для финализации окна. Все узлы с weighted_ticket < target публикуют VDF_Reveal для лотереи. VDF_Reveal цементируется через BundledConfirmation: confirmers включают полученные VDF_Reveals в свои bundles наряду с UserObjects и ControlObjects. Cement threshold тот же 67% active_chain_length. Proposer извлекает только cemented reveals дискреция над лотереей = ноль.
#### Класс 1: узлы
После завершения VDF окна W каждый узел вычисляет свой ticket:
```
ticket_node = -ln(endpoint_node / 2^256)
seniority_bonus = min(chain_length / 69, chain_length_snapshot)
lottery_weight = chain_length_snapshot + seniority_bonus
weighted_ticket_node = ticket_node / lottery_weight
```
`chain_length_snapshot` количество окон с cemented BundledConfirmation за последние (78 000 окон). Вычисляется через checkpoint-механизм: на каждой τ-boundary фиксируется checkpoint chain_length; snapshot = chain_length - checkpoint_6τ_ago. Хранится 6 checkpoint-ов (48B на узел). Обновляется на τ-boundary (шаг 3.6 apply_proposal).
`seniority_bonus` добавка за накопленный абсолютный chain_length, ограниченная сверху размером snapshot (cap). Делитель 69 (цифровой корень = 6, Tesla). Cap = snapshot: максимальное преимущество старожила 2x относительно новичка с полным snapshot. При chain_length < 69 seniority_bonus = 0 (целочисленное деление): первые 69 окон после регистрации lottery_weight = snapshot.
**Инвариант DS-2 (lottery_weight floor).** Для любого узла N, участвующего в лотерее окна W (active(N, W) = true): `lottery_weight(N, W) ≥ 1`. Деление `ticket / lottery_weight` в формуле weighted_ticket_node гарантированно определено.
Обоснование через composition временных порогов:
- `active_predicate = 2τ₂` (26 000 окон): неактивные узлы исключены из лотереи
- `pruning_idle_windows = 4τ₂` (52 000 окон): полностью неактивные узлы удалены из Node Table
- `chain_length_snapshot window = 6τ₂` (78 000 окон): горизонт снапшота
Ordering `2τ₂ < 4τ₂ < 6τ₂` гарантирует: узел либо active (публикует BC chain_length растёт snapshot 1), либо inactive (исключён из лотереи), либо pruned (удалён из Node Table до того как snapshot мог бы упасть до 0). Сценарий «active узел с snapshot = невозможен по построению.
Инвариант ОБЯЗАТЕЛЕН для enforcement в apply_proposal: при вычислении weighted_ticket_node валидатор проверяет `lottery_weight > 0`. Нарушение = protocol violation, proposal отклоняется. Нарушение указывает на баг в pruning или active_predicate consensus critical.
Разделение весов:
- **Лотерея (эмиссия):** `lottery_weight = chain_length_snapshot + seniority_bonus`. Недавняя работа (snapshot) доминирует, longevity даёт bounded bonus.
- **Quorum (безопасность):** абсолютный `chain_length`. Старожилы доминируют в финализации.
Endpoint узла вычисляется детерминированно из канонических данных:
```
endpoint_node(W) = SHA-256(
"mt-lottery" ||
T_r(W) ||
cemented_bundle_aggregate(W-2) ||
node_id ||
window_index
)
```
Где:
- `T_r(W)` TimeChain VDF output окна W (каноничен, одинаков у всех узлов).
- `cemented_bundle_aggregate(W-2)` агрегат подписей cemented BundledConfirmation окна W-2 (см. раздел BundledConfirmation). Lookback на 2 окна: cemented set окна W-2 зафиксирован в proposal_{W-1}, канонически финализирован к концу окна W. Все узлы используют одно значение.
Endpoint верифицируем за O(1) один SHA-256, плюс lookup `cemented_bundle_aggregate(W-2)` из уже финализированного state.
**Grinding resistance.** Атакующий с VDF hardware advantage способен пре-вычислить `T_r(W)` на много окон вперёд. Но `cemented_bundle_aggregate(W-2)` содержит FN-DSA-512 подписи будущих confirmers их privкey не у атакующего, aggregate непредсказуем offline. Grinding по node_id (выбор keypair с favorable future endpoints) не работает: endpoint зависит от canonical-но-непредсказуемого компонента. Горизонт grinding схлопывается до уже cemented (публично известного) окна W-2, где keypair уже зафиксирован.
Если weighted_ticket_node < target узел кандидат и публикует VDF_Reveal:
```
VDF_Reveal:
node_id 32B
window_index 4B <- индекс τ₁
endpoint 32B <- SHA-256("mt-lottery" || T_r(W) || cemented_bundle_aggregate(W-2) || node_id || window_index)
signature 666B <- FN-DSA-512, подписано node_pubkey
Итого: ~734B
```
Любой активный узел может стать кандидатом лотереи lottery_weight основан на недавней работе (snapshot ₂), старожилы получают bounded seniority bonus.
#### Класс 2: аккаунты
Аккаунт автоматически становится кандидатом если у него есть cemented операция в окне W. Endpoint вычисляется детерминированно:
```
operation_for_lottery(account, W) = единственная cemented операция аккаунта в окне W
(dependency rule: максимум одна)
endpoint_account(W) = SHA-256(
"mt-account-lottery" ||
account_id ||
hash(operation_for_lottery) ||
timechain_value(W) ||
cemented_bundle_aggregate(W-2)
)
account_length_at_lottery = account.account_chain_length_snapshot
ticket_account = -ln(endpoint_account / 2^256)
weighted_ticket_account = ticket_account / account_length_at_lottery
```
`account_chain_length_snapshot` обновляется на каждой τ boundary копией текущего `account_chain_length`. Между τ boundaries snapshot frozen все узлы используют одно значение, лотерея детерминирована.
Аккаунт без операции в окне W не участвует в лотерее этого окна.
**Исключение operator-аккаунтов.** Аккаунт с `is_node_operator = 1` исключён из лотереи аккаунтов. Узел получает вес через NodeChain; operator_account только хранит TimeCoin. Двойной счёт исключён конструкцией. Оператор узла, желающий участвовать в лотерее аккаунтов, использует отдельный персональный аккаунт.
**Защита от grinding:** три слоя. (1) dependency rule ограничивает аккаунт одной операцией за окно один лотерейный билет. (2) `timechain_value(W)` известен только после закрытия окна endpoint не вычислим до публикации операции в рамках окна. (3) `cemented_bundle_aggregate(W-2)` содержит FN-DSA-512 подписи confirmers непредсказуем offline атакующим с VDF hardware advantage, блокирует пре-вычисление endpoint через lookahead и grinding содержимого operation.
#### Определение winner-а (Lookback Leadership)
Winner окна W-1 определяется при cementing proposal окна W. Proposer окна W = winner окна W-2 (канонически известен из cemented state).
**Механика:**
1. Окно W-1 завершается: confirmers публикуют BundledConfirmation_{W-1} (операции окна W-1 + VDF_Reveals окна W-2), кандидаты публикуют VDF_Reveal_{W-1}, аккаунты публикуют операции.
2. `proposer_W = winner_{W-2}` (канонически определён из proposal_{W-1}).
3. Окно W начинается. Confirmers получают VDF_Reveals_{W-1} через P2P и включают их в BundledConfirmation_W наряду с операциями окна W. VDF_Reveal идентифицируется по `window_index = W-1`.
4. VDF_Reveal_{W-1} cemented когда confirmers с суммарным chain_length 67% active_chain_length включили его в свои BundledConfirmation_W. Cement status каноничен каждый узел отслеживает его независимо по P2P bundles.
5. Proposer_W собирает BundledConfirmation-ы окна W-1 и cemented set:
```
included_bundles_{W-1} = BundledConfirmation-ы окна W-1 из view proposer-а
(суммарный chain_length 67% active_chain_length)
included_reveals_{W-1} = VDF_Reveal-ы окна W-1, cemented через
BundledConfirmation окна W (67% active_chain_length)
```
6. Из included_reveals_{W-1} извлекаются все node endpoints. Из included_bundles_{W-1} извлекаются все cemented account operations.
7. `winner_{W-1} = argmin(weighted_ticket)` среди всех кандидатов (cemented VDF_Reveal nodes + аккаунты).
8. Proposer_W публикует proposal_W, содержащий:
- `included_bundles_{W-1}` (canonical view финализации)
- `included_reveals_{W-1}` (cemented set лотереи)
- `winner_{W-1}` (получатель 13 Ɉ за окно W-1)
- control_set, state_root, TimeCoin transfer
9. Сеть валидирует proposal_W:
- Proposer = winner_{W-2}? (канонически проверяемо)
- included_bundles содержат 67% active_chain_length? (проверяемо из Node Table)
- included_reveals_{W-1} = cemented set VDF_Reveals окна W-1? (валидатор сверяет с собственным tracking cement status из BundledConfirmation окна W)
- winner_{W-1} = argmin из (included_reveals account_candidates)? (детерминированно проверяемо)
- state_root корректен? (независимый пересчёт)
10. Если 67% active_chain_length подписывают proposal_W proposal cemented. Winner_{W-1} получает 13 Ɉ. Winner_{W-1} становится proposer_{W+1}.
11. Если < 67% подписали proposal отклонён. Fallback: `fallback_proposer_W = second_min(weighted_ticket)` окна W-2. Fallback cascade: third_min, fourth_min, etc.
**Cross-window cementing timeline.** VDF_Reveals окна W-1 публикуются при завершении окна W-1 (VDF computation = window duration). Цементируются в BundledConfirmation окна W. Между публикацией reveals и сборкой proposal целое окно. Timing constraint отсутствует.
**Leader skin in the game.** Proposer_W публикует свой VDF_Reveal для окна W. Если его proposal отклонён (< 67% подписей сети), его VDF_Reveal исключается из пула кандидатов окна W. Потеря lottery ticket = экономический кнут за цензуру или бездействие. Отказ подписать proposal = implicit rejection от каждого узла.
**Genesis bootstrap.** proposer_0 и proposer_1 = bootstrap-узел (единственный в Genesis Decree). Начиная с proposer_2 = winner_0, стандартная lookback логика.
#### Калибровка target
Target калиброван на ~13 кандидатов VDF_Reveal за окно. Калибровка на τ:
```
target_new = target_old × (13 / actual_candidates_per_window)
actual_candidates_per_window = total_reveals_за_τ₂ / 13 000
```
Трафик reveal за окно: ~13 VDF_Reveal × 738B 9.6 KB (P2P gossip; далее включаются в BundledConfirmation для cementing). Аккаунты участвуют через cemented операции в BundledConfirmation дополнительного трафика для аккаунтов нет.
#### Валидация VDF_Reveal
1. Подпись FN-DSA-512 соответствует node_pubkey из Node Table
2. window_index = текущий τ
3. node_id существует в Node Table
4. weighted_ticket < target
5. endpoint верифицируем: SHA-256("mt-lottery" || T_r || node_id || window_index) = заявленному endpoint
#### Валидация участия аккаунта
1. account_id существует в Account Table
2. account_chain_length_snapshot > 0
3. Аккаунт имеет cemented операцию в окне W
4. operation_for_lottery определена детерминированно (dependency rule: одна операция за окно)
5. weighted_ticket_account < target
### Account — содержимое блока
Приём, верификация объектов и формирование набора. Два класса объектов:
**UserObjects** пользовательские операции:
| Тип | Описание | Валидация |
|-----|----------|-----------|
| Transfer | Публичный перевод | FN-DSA-512 подпись, prev_hash, sender != receiver, amount > 0, sender.balance >= amount, получатель существует |
| OpenAccount | Создание аккаунта | FN-DSA-512 подпись, prev_hash = 0, account_id = SHA-256("mt-account" || pubkey) не существует в Account Table |
| ChangeKey | Смена ключа | FN-DSA-512 подпись старым ключом, new_pubkey |
| Anchor | Якорь данных ко времени | FN-DSA-512 подпись, prev_hash, app_id = 32B, data_hash = 32B |
**ControlObjects** — объекты управляющие составом сети:
| Тип | Описание | Валидация |
|-----|----------|-----------|
| NodeRegistration | Регистрация узла (кандидатура) | FN-DSA-512 подпись, node_id уникален (не в Node Table и не в Candidate Pool), operator_account_id существует, proof_endpoint верифицируем через VDF от candidate_vdf_init |
Каждый узел валидирует объекты обоих классов локально при получении. Валидные объекты ретранслируются по P2P.
Все объекты — UserObjects, ControlObjects и VDF_Reveals — финализируются (cemented) одинаково: через 67% active_chain_length подтверждения в BundledConfirmation. Cemented status объективен и одинаков для всех узлов. Дискреция победителя над включением ControlObjects и VDF_Reveals = ноль.
#### Proposal
Proposal содержит **control_set** и метаданные окна. UserObjects применяются к Account Table батчем при settle (apply at window close); в proposal они не повторяются. ControlObjects применяются к Node Table в apply_proposal step 1 в детерминированном порядке.
**control_set(proposal окна W)** определён формулой:
```
control_set = {
ControlObject c :
c.cemented_window > previous_proposal.window
AND c.cemented_window <= W
}
сортировка: (cemented_window asc, op_hash lex asc)
```
Где `previous_proposal.window` — окно предыдущего финализированного proposal в цепочке. Множество детерминировано: cemented_window — каноническое поле объекта (известно всем узлам через BundledConfirmation), op_hash — детерминирован.
Победитель **обязан** включить весь control_set целиком. Пропуск или добавление лишнего ControlObject = невалидный proposal = fallback. Каждый узел независимо вычисляет ожидаемый control_set по той же формуле и сравнивает с proposer's set.
Форки аккаунтов (две операции с одним prev_hash) разрешаются голосованием узлов весом chain_length. 67% active_chain_length за одну операцию → побеждает (см. раздел «Двойная трата»).
#### Закрытие окна (Lookback Leadership Finalization)
```
Window W-1: confirmers publish BundledConfirmation_{W-1}
(W-1 operations + W-2 VDF_Reveals)
VDF_{W-1} completes → candidates publish VDF_Reveal_{W-1}
accounts publish cemented operations
Window W: confirmers publish BundledConfirmation_W
(W operations + W-1 VDF_Reveals)
W-1 VDF_Reveals cemented (67% active_chain_length)
proposer_W = winner_{W-2} (canonical from proposal_{W-1})
proposer_W extracts cemented reveals → winner_{W-1}
proposer_W publishes proposal_W
┌───────────────────────────────┐
│ proposal_W validation │
│ included_bundles ≥ 67%? │
│ included_reveals = cemented? │
│ winner_{W-1} = argmin? │
│ state_root correct? │
└───────────┬───────────────────┘
│ 67% sign
proposal_W cemented
winner_{W-1} receives 13 Ɉ
winner_{W-1} = proposer_{W+1}
```
- **Lookback Leader.** `proposer_W = winner_{W-2}` — канонически определён из cemented proposal_{W-1}. Каждый узел вычисляет proposer_W детерминированно из canonical state.
- **Cemented reveals.** VDF_Reveals окна W-1 публикуются при завершении W-1, цементируются чер<D0B5><D180>з BundledConfirmation окна W (confirmers включают полученные reveals в свои bundles). `included_reveals_{W-1}` = cemented set (67% active_chain_length). Proposer извлекает cemented reveals и cemented account operations из included_bundles_{W-1}, определяет `winner_{W-1} = argmin(weighted_ticket)`. Дискреция proposer-а над составом лотереи = ноль.
- **Canonical acceptance.** Сеть валидирует proposal_W: (a) proposer = winner_{W-2}, (b) included_bundles ≥ 67% active_chain_length, (c) included_reveals = cemented set VDF_Reveals окна W-1, (d) winner_{W-1} = argmin из (cemented reveals account_candidates), (e) state_root корректен. Если 67% active_chain_length подписывают proposal_W → cemented. Canonical set зафиксирован.
- **Leader skin in the game.** Proposer_W участвует в лотерее окна W через свой VDF_Reveal (cemented в BundledConfirmation окна W+1). При отклонении proposal (< 67% подписей) VDF_Reveal proposer-а исключается из пула кандидатов окна W. Отказ подписать proposal = implicit rejection. Отдельного censorship vote нет.
- **Fallback cascade.** Если proposal от proposer_W отклонён или отсутствует, роль переходит к `fallback_1 = second_min(weighted_ticket)` окна W-2, затем third_min, etc. Все канонически известны из cemented state.
- **ControlObjects.** ControlObjects попадают в control_set proposal по моменту cement canonically deterministic.
**Свойство темпа сети.** Сеть продвигается со скоростью медианного активного набора узлов. Quorum требует подписей большинства по chain_length быстрейший узел ждёт, пока достаточно других успеет. Hardware progress ускоряет сеть естественно когда ускоряется медиана, participation_ratio растёт выше 0.95, D адаптивно увеличивается.
**One-window lag награды.** 13 Ɉ за окно W-1 зачисляются winner_{W-1} при cementing proposal_W. Задержка в одно окно между завершением работы и получением награды.
#### Proposer (Lookback Leader)
`proposer_W = winner_{W-2}` канонически определён из cemented proposal_{W-1}. Proposer собирает proposal_W:
- **included_bundles_{W-1}**: BundledConfirmation окна W-1 (суммарный chain_length 67% active_chain_length). Из included_bundles извлекаются cemented account operations для лотереи.
- **included_reveals_{W-1}**: VDF_Reveals окна W-1, cemented через BundledConfirmation окна W (67% active_chain_length). Из cemented reveals + cemented account operations определяется `winner_{W-1}` (получатель 13 Ɉ за окно W-1).
- **control_set**: все cemented ControlObjects в окнах (previous_proposal.window, W]. Свобода = ноль (каноничен).
- **State Root snapshot**: account_root, node_root и candidate_root после apply at window close (все cemented операции + control objects + selection event + TimeCoin transfer to winner_{W-1} применены батчем).
Свобода proposer: included_bundles ограничены порогом 67%. included_reveals детерминированы cement status-ом. control_set детерминирован формулой. State root и winner_{W-1} вычисляются из cemented sets каждый валидатор проверяет корректность детерминированно.
Proposal с набором included_bundles < 67% active_chain_length, неверным included_reveals (не совпадает с cemented set), неверным winner_{W-1}, пропущенным cemented ControlObject, или неверным state_root отклоняется fallback на second_min(weighted_ticket) окна W-2.
#### Финальность proposal
Финальность proposal = подпись proposer_node_id на proposal header (верифицируемая против Node Table[proposer_node_id].node_pubkey) + независимая верифицируемость состояния.
1. Proposer (proposer_node_id) публикует подписанный proposal header + control_set
2. Каждый узел проверяет `window_index == prev_proposal.window_index + 1`, `protocol_version >= prev_proposal.protocol_version` и `protocol_version <= local_max_supported_version`
3. Каждый узел независимо вычисляет ожидаемый control_set по формуле и сравнивает с proposer's
4. Каждый узел применяет control_set + TimeCoin детерминированно в порядке (cemented_window asc, op_hash lex asc)
5. Каждый узел сравнивает вычисленный state_root с заявленным в proposal
6. Совпадает proposal принят
7. Не совпадает proposal отклонён, fallback на второе место
Финальность операций аккаунтов отдельный процесс через подтверждения (67% active_chain_length), не через proposal.
Proposal header:
```
Proposal header:
prev_proposal_hash 32B
window_index 8B <- u64, индекс окна τ₁ с genesis; == prev_proposal.window_index + 1
protocol_version 4B <- u32, активная версия протокола на момент window_index
control_root 32B <- Merkle root control_set (каноничен)
node_root 32B <- Merkle root Node Table (обновляется каждое окно)
candidate_root 32B <- Merkle root Candidate Pool
account_root 32B <- Merkle root Account Table после apply at window close
state_root 32B <- SHA-256("mt-state-root" || node_root || candidate_root || account_root)
timechain_value 32B
included_bundles_root 32B <- Merkle root списка (confirmer_id, bundle_hash)
BundledConfirmation окна W-1 (≥ 67% active_chain_length)
included_reveals_root 32B <- Merkle root списка VDF_Reveal-ов окна W-1,
cemented через BundledConfirmation окна W
winner_class 1B <- 1=Node, 2=Account (winner окна W-1)
winner_endpoint 32B <- endpoint winner-а окна W-1 (node lottery или account lottery)
winner_id 32B <- получатель TimeCoin за окно W-1: node_id (winner_class=1)
или account_id (winner_class=2)
proposer_node_id 32B <- winner_{W-2}, канонически определённый из proposal_{W-1}
target 8B <- текущий target лотереи
fallback_depth 1B <- 1 = первое место, 2+ = fallback
signature 666B <- FN-DSA-512, подпись header Node Table[proposer_node_id].node_pubkey
```
Все поля proposal header канонически вычислимы bit-exact из предыдущего state и cemented set окна W. Каждое поле имеет источником либо canonical state, либо детерминированную функцию от canonical state.
**Разделение ролей winner_id и proposer_node_id.** Это два независимых поля с разными назначениями:
- `winner_id` получатель TimeCoin. Аккаунт или узел, выигравший лотерею окна. Используется только в apply_proposal step 2 для зачисления 13 Ɉ.
- `proposer_node_id` узел ответственный за сборку и публикацию proposal. Подписывает header своим node_pubkey. Верификация подписи proposal против `Node Table[proposer_node_id].node_pubkey`, всегда.
Когда `winner_class = Account`, winner это аккаунт без node_pubkey, физически не способный подписать proposal. Подписывает всегда узел-proposer (ближайший по weighted_ticket). TimeCoin при этом получает winner (аккаунт), proposer не получает дополнительной награды.
**Инварианты Proposal header:**
- `window_index == prev_proposal.window_index + 1` (монотонность, шаг 1)
- `protocol_version >= prev_proposal.protocol_version` (не убывает; изменяется только через software upgrade узла, см. раздел «Эволюция протокола»)
- `protocol_version <= local_max_supported_version` (узел **обязан отклонить** proposal с protocol_version которую его реализация не поддерживает; принятие неизвестной версии = принятие непроверяемых правил = нарушение безопасности)
**Cemented window** объекта `window_index` proposal-а в котором BundledConfirmation с этим объектом достиг quorum. Определён детерминированно для каждого cemented объекта.
**Settled window** объекта `window_index` proposal-а в котором объект был применён к state:
- Для UserObjects: `settled_window = cemented_window` (apply batch at window close того же окна). Следующая операция от того же sender возможна в окне `cemented_window + 1` (dependency rule)
- Для ControlObjects: `settled_window` = window_index первого proposal где объект попал в control_set (обычно `cemented_window + 1`)
Fallback: если proposal от `proposer_W = winner_{W-2}` отклонён (< 67% подписей) или отсутствует (proposer offline), роль переходит к `fallback_1 = second_min(weighted_ticket)` окна W-2. Если fallback_1 тоже отклонён к third_min, и т.д. Вся cascade канонически определена из cemented state окна W-2.
При fallback `proposer_node_id` меняется; `winner_{W-1}` определяется fallback-proposer-ом из cemented set (тот же cemented set canonical для всех узлов). Новый proposer подписывает header своим node_pubkey, `fallback_depth` инкрементируется.
**Leader penalty при отклонении:** endpoint proposer-а, чей proposal отклонён, исключается из lottery пула текущего окна W. Proposer теряет шанс на 13 Ɉ. Это экономический кнут за бездействие или цензуру.
**Полная симметрия fallback:** молчание первого proposer переводит обязанность сборки proposal к следующему узлу. Награда за окно W-1 привязана к лотерейному билету и гарантирована, если хотя бы один узел в сети соберёт валидный proposal через fallback cascade.
#### Непрерывность VDF
VDF следующего окна вычисляется непрерывно, не ожидая завершения финализации предыдущего. TimeChain для окна N+1 детерминирован каждый узел вычисляет его независимо. Reveal phase и финализация происходят параллельно с началом VDF следующего окна.
#### Confirmations (финализация операций и control objects)
Confirmers узлы с chain_length >= confirmation_threshold. Подтверждают **все** валидные объекты окна (UserObjects + ControlObjects) от имени сети.
```
active_chain_length(W) = Σ node.chain_length
для node ∈ Node Table : active(node, W)
confirmation_threshold(W) = active_chain_length(W) / 130
~130 confirmers при large-scale сети (active_chain_length / 130).
```
Только активные узлы (cemented BundledConfirmation за последние 2τ₂) учитываются. Мёртвый вес исключён конструкцией. Сканирование Node Table для вычисления `active_chain_length` — O(|Node Table|) ≤ 10⁵ записей, миллисекунды.
Confirmer собирает все валидные объекты за окно и публикует один BundledConfirmation. Bundle содержит два класса хэшей: (1) операции текущего окна W (UserObjects + ControlObjects) и (2) VDF_Reveals предыдущего окна W-1 (лотерейные билеты, опубликованные при завершении W-1 и полученные через P2P):
```
BundledConfirmation:
node_id 32B
endpoint 32B <- T_r текущего окна (доказывает timeliness)
window_index 4B
op_count 2B
op_hashes[] op_count × 32B <- хэши UserObjects и ControlObjects окна W
reveal_count 2B
reveal_hashes[] reveal_count × 32B <- хэши VDF_Reveals окна W-1
signature 666B
```
Один BundledConfirmation per (node_id, window_index). Повторный отклоняется. Endpoint = T_r текущего окна (верифицируем: сравнение с каноническим T_r). `node.chain_length` хранится в Node Table и инкрементируется в `apply_proposal` шаг 3.5 для каждого узла с cemented BundledConfirmation в окне W.
Объект финализирован (cemented) когда подтверждения от confirmers с суммарным chain_length > quorum. Cemented — необратимо. Типичное время: quorum event. Это правило применяется одинаково к UserObjects, ControlObjects и VDF_Reveals: cemented status объективен и каноничен для всех узлов. VDF_Reveals окна W-1 цементируются в BundledConfirmation окна W (cross-window cementing).
**Confirmation cutoff (детерминизм cemented set).** Cemented set окна W фиксируется proposer-ом окна W+1 через frozen view (Lookback Leadership). Proposer_{W+1} включает в proposal_{W+1} все BundledConfirmation окна W из своего view с суммарным chain_length ≥ 67% active_chain_length. Этот frozen view становится каноническим cemented set после cementing proposal_{W+1} сетью.
**Cemented bundle aggregate.** Канонический агрегат подписей confirmers окна W, используемый как unpredictable-offline компонент в формулах lottery endpoint, sort_key и candidate_vdf_init:
```
cemented_bundle_aggregate(W) :=
если W < 2:
0x00 × 32 (до Genesis cementing)
иначе если |cemented_bundles_W| == 0:
SHA-256("mt-bc-aggregate-empty" || W) (вырожденный случай: окно без cementing)
иначе:
SHA-256(
"mt-bc-aggregate" ||
concat(bc.signature for bc in sorted(cemented_bundles_W, by node_id))
)
```
`cemented_bundles_W` — каноническое множество cemented BundledConfirmation окна W (frozen view proposer_{W+1}). Сортировка по node_id обеспечивает детерминированный порядок.
Ветви формулы покрывают все возможные состояния окна:
- **W < 2:** Genesis окна, cemented_bundle_aggregate(W-2) не существует возвращается фиксированный 0x00 × 32.
- **|cemented_bundles_W| == 0:** окно без cementing (катастрофический отказ консенсуса). Возвращается детерминистический fallback. [I-8] в этой ветви вырожден, но в non-functional состоянии сети это приемлемо protocol уже не производит консенсус.
- **Стандартная ветвь:** агрегат подписей, полная защита [I-8].
Свойства:
- **Канонический.** Cemented set объективен, порядок детерминирован. Два честных узла bit-exact получают одинаковое значение.
- **Непредсказуемый offline (в стандартной ветви).** Зависит от FN-DSA-512 подписей конфирмеров требует их приватных ключей. Атакующий с VDF hardware advantage не может пре-вычислить будущие `cemented_bundle_aggregate` без privкey большинства активных узлов. Это закрывает endpoint-grinding через keypair/operation-grinding с lookahead-advantage.
- **Degraded security margin в bootstrap периоде.** При `active_nodes = 1` агрегат считается из одной bootstrap-подписи. Безопасность в этот период опирается на секретность bootstrap privкey см. раздел «Границы модели доверия».
**Dependency rule (детерминизм apply).** Одно правило: confirmer подтверждает операцию только если все её зависимости разрешены из settled state окна W-1.
```
Операция валидна для inclusion в BundledConfirmation окна W если:
1. prev_hash == Account Table[sender].frontier_hash
на момент settled state конца окна W-1
2. Для Transfer: receiver существует в Account Table
на момент settled state конца окна W-1
3. sender.balance >= amount (для Transfer)
на момент settled state конца окна W-1
```
Settled state конца окна W-1 результат apply_proposal окна W-1 одинаков у всех узлов (детерминированная функция от cemented set W-1 и предыдущего state). Confirmer проверяет каждую операцию против этого глобально единого состояния. Никаких bundle-local цепочек, никакого mempool order.
**Следствие: одна операция на аккаунт за окно τ₁.** Вторая операция от того же sender имеет prev_hash = H(первой операции), но первая ещё не settled (settled = конец текущего окна W). Confirmer отклоняет вторую. Она пройдёт в окне W+1 когда первая settled. Throughput на аккаунт: 1 операция за окно. Это достаточно для всех бытовых сценариев; для высокочастотных batching через Anchor (один Anchor содержит Merkle root тысяч записей).
Cross-account зависимости сериализуются через окна создание аккаунта OpenAccount в окне W, получение перевода в окне W+1.
```
quorum(W) = ⌈0.67 × active_chain_length(W)⌉
```
Объект cemented когда суммарный chain_length confirmers подтвердивших объект через BundledConfirmation окна W quorum(W). Активный набор детерминирован все узлы вычисляют `active_chain_length(W)` независимо из state Node Table и получают одно и то же значение.
Если active_chain_length падает ниже минимума жизнеспособности (теоретически возможно при массовом offline) финализация останавливается до восстановления активности. Halt by liveness, не by safety: вернувшиеся узлы возобновляют работу с последнего cemented state.
Трафик confirmations: ~100 bundles × ~4 KB 400 KB за окно. Стабильно при любом масштабе.
Узлы-наблюдатели (chain_length < threshold) получают bundles, верифицируют endpoint и подписи, подсчитывают quorum, применяют cemented операции. Не публикуют confirmations.
#### State transition
Два параллельных процесса обновления состояния:
**Применение операций по window close.** Cemented операции окна W буферизуются до момента сборки proposal_{W+1}. Множество cemented операций фиксируется proposer-ом через frozen view (Lookback Leadership). Все cemented операции окна W применяются батчем в детерминированном порядке:
```
Порядок apply: по op_hash lex asc
```
Каждый аккаунт имеет максимум одну cemented операцию в окне W (dependency rule). Порядок между аккаунтами лексикографически по op_hash. Детерминирован, вычислим независимо каждым узлом.
Apply каждой операции:
```
Transfer: sender.balance -= amount
receiver.balance += amount
sender.frontier_hash = H(operation)
update_merkle_path(sender)
update_merkle_path(receiver)
OpenAccount: создать запись в Account Table (balance = 0, pubkey, frontier_hash = H(op))
insert_merkle_leaf(new_account)
ChangeKey: account.current_pubkey = new_pubkey
account.suite_id = new_suite_id
account.frontier_hash = H(operation)
update_merkle_path(account)
Anchor: записать data_hash в цепочку аккаунта (frontier_hash обновлён)
update_merkle_path(account)
После каждой операции: account_root = current root.
```
**При apply каждой операции** обновляется AccountChain length signer-аккаунта (подписавшего операцию):
```
on_operation_applied(operation, window W):
signer = operation.sender # account_id из payload
signer.account_chain_length += 1
signer.last_op_window = W
signer.op_height += 1
# Получатель Transfer не получает обновления chain_length —
# пассивное получение не считается активностью.
```
Dependency rule: один аккаунт = одна операция за окно τ₁. Каждая cemented операция = +1 к account_chain_length = одно окно присутствия.
**State transition в proposal:** при settle (apply at window close) применяется атомарно:
```
apply_proposal(state, proposal) -> state':
Шаг 1: применить control_set в порядке (cemented_window asc, op_hash lex asc).
NodeRegistration: проверить node_id уникален (нет в Node Table и Candidate Pool),
проверить operator_account_id существует и is_node_operator == 0,
проверить W_p - max_vdf_horizon ≤ W_start ≤ W_p - base_vdf_length
(max_vdf_horizon = 2 × τ₂_windows; base_vdf_length = 13000),
применить incremental apply в окне W_p:
sort cemented NodeRegistrations окна W_p by nr_sort_key,
где nr_sort_key(nr) = SHA-256(
"mt-nodereg-sort" ||
timechain_value(W_p) ||
cemented_bundle_aggregate(W_p - 2) ||
nr.node_pubkey
),
for each NR in sorted order:
current_pending = pending_candidates(W_p) + N_applied_this_window
current_pressure = current_pending / active_nodes(W_p)
required_vdf_length(NR) = adaptive_formula(current_pressure)
if NR.vdf_chain_length < required_vdf_length(NR):
reject NR (insufficient VDF work)
continue
верифицировать proof_endpoint: пересчёт VDF от
SHA-256("mt-candidate-vdf-init" ||
timechain_value(W_start) ||
cemented_bundle_aggregate(W_start - 2) ||
node_id)
через NR.vdf_chain_length окон,
если endpoint не совпадает: reject NR
создать запись в Candidate Pool:
node_id, node_pubkey, suite_id, operator_account_id,
proof_endpoint, W_start, vdf_chain_length,
registration_window = W_p,
expires = W_p + 3 × τ₂_windows.
N_applied_this_window += 1.
Шаг 2: применить TimeCoin победителя.
Если winner_class = 1 (Node): operator_account = Node Table[winner_id].operator_account_id
operator_account.balance += 13_000_000_000 nɈ
Если winner_class = 2 (Account): Account Table[winner_id].balance += 13_000_000_000 nɈ
Proposer (proposer_node_id) формирует proposal без награды.
Шаг 3: обработать expiry кандидатов и selection event.
3a. Все записи c ∈ Candidate Pool где c.expires <= current_window:
удалить c из Candidate Pool, обновить candidate_root.
3b. Selection event (если current_window % 369 == 0):
candidates = все записи Candidate Pool где expires > current_window
slots = max(1, floor(active_nodes(current_window) / 130))
sort_key(c) = SHA-256(
"mt-selection" ||
timechain_value(current_window) ||
cemented_bundle_aggregate(current_window - 2) ||
c.node_id
)
selected = первые slots кандидатов по sort_key
Для каждого selected:
создать запись в Node Table (start_window = current_window, chain_length = 1,
last_confirmation_window = 0, operator_account_id зафиксирован)
установить is_node_operator = 1 у operator-аккаунта
удалить selected из Candidate Pool
обновить node_root и candidate_root.
**Grinding resistance selection event.** Domain separator `mt-selection` отделяет hash space от `mt-lottery` и других. Компонент `cemented_bundle_aggregate(current_window - 2)` — канонический но unpredictable offline (зависит от FN-DSA-512 подписей confirmers окна current_window-2). Атакующий с VDF hardware advantage, пре-вычисляющий `timechain_value` для будущих selection events, не может пре-вычислить sort_key без privкey confirmers. Grinding keypair (генерация N kerpairs для выбора favorable node_id) не работает: к моменту selection event sort_key определён будущими signatures, которые атакующий не контролирует.
Шаг 3.5: обновить chain_length активных узлов.
Для каждого узла N с cemented BundledConfirmation в окне W:
N.chain_length += 1
N.last_confirmation_window = W
update_merkle_path(N) в node_root
Множество узлов с cemented BundledConfirmation в окне W детерминировано
(cemented status объективен) — все узлы применяют один и тот же набор обновлений.
Шаг 3.6: обновить chain_length_snapshot на τ₂-boundary.
Если current_window % τ₂_windows == 0:
Для каждого узла N в Node Table:
rotate N.chain_length_checkpoints (сдвиг: oldest выбывает, текущий chain_length записывается как newest)
N.chain_length_snapshot = N.chain_length - N.chain_length_checkpoints[oldest]
update_merkle_path(N) в node_root
Между τ₂-boundaries: chain_length_snapshot вычисляется как chain_length - frozen oldest checkpoint.
Детерминированно: все узлы применяют одну и ту же ротацию на одной τ₂-boundary.
Шаг 4: node_root, candidate_root и account_root уже отражают все cemented изменения
(incremental Merkle update произошёл при каждом state transition).
state_root = SHA-256("mt-state-root" || node_root || candidate_root || account_root).
```
Порядок детерминирован. Каждый узел применяет одни и те же шаги и получает один и тот же state_root.
AccountTable зависит от TimeChain, NodeChain и AccountChain. Обратных зависимостей нет.
Минимум для узла: **1 ядро CPU**. TimeChain VDF (непрерывное последовательное хэширование) + валидация операций (interleaved, overhead < 1% окна). С ростом TPS сети дополнительные ядра ускоряют верификацию операций. Один узел = 1 ядро. Любой компьютер = потенциальный узел. Верификация операций аккаунтов полностью параллелизуется цепочки аккаунтов независимы.
### Вход и регистрация
Два уровня входа в сеть. Узлы участвуют в консенсусе открытый вход через VDF + selection event. Аккаунты держат и переводят средства создаются явно через OpenAccount.
**Genesis State — аксиома сети.** Минимальный bootstrap: один узел, один аккаунт. Начальное состояние, существующее до того как любая операция возможна:
```
Genesis State (до первого окна, supply = 0):
Account Table = 1 запись (bootstrap operator account):
account_id = SHA-256("mt-account" || suite_id || pubkey_0)
balance = 0
suite_id = 0x0001 (FN-DSA-512)
is_node_operator = 1
current_pubkey = pubkey_0 (bootstrap)
frontier_hash = SHA-256("mt-genesis" || account_id)
op_height = 0
account_chain_length = 0
account_chain_length_snapshot = 0
creation_window = 0
Node Table = 1 запись (bootstrap node):
node_id = SHA-256("mt-node" || node_pubkey_0)
node_pubkey = node_pubkey_0 (bootstrap)
suite_id = 0x0001
operator_account_id = account_id_0
start_window = 0
chain_length = 1
last_confirmation_window = 0
Candidate Pool = ∅
Bootstrap-узел стартует с chain_length = 1 в Genesis. Каждый последующий cemented BundledConfirmation инкрементирует chain_length. Начальное значение = 1 (а не 0) — необходимо для корректности знаменателей weighted_ticket_node и seniority_bonus; инвариант chain_length ≥ 1 сохраняется для любого узла в Node Table.
genesis_account_root = sparse Merkle root над 1 записью Account Table
genesis_node_root = sparse Merkle root над 1 записью Node Table
genesis_candidate_root = 0x00 × 32 (пустая sparse Merkle tree)
genesis_state_root = SHA-256("mt-state-root" || genesis_node_root || genesis_candidate_root || genesis_account_root)
protocol_params (каноническая сериализация, little-endian, фиксированная длина полей):
D₀ (8B) начальное значение D TimeChain VDF (13 000 000 000)
(reserved) (8B) = 0x00 × 8 (mandatory, ранее m₀ NodeChain VDF)
τ₂_windows (8B) число окон в τ₂ (13 000)
timecoin_per_window (16B) 13_000_000_000 nɈ (u128)
target₀ (32B) начальный target лотереи
confirmation_quorum_num (1B) 67
confirmation_quorum_den (1B) 100
participation_dead_zone_low (2B) 85
participation_dead_zone_high (2B) 95
d_adjustment_rate_num (2B) 3
d_adjustment_rate_den (2B) 100
vdf_entry_windows (8B) 13 000
selection_interval (8B) 369
candidate_expiry_windows (8B) 39 000 (3τ₂)
adaptive_vdf_threshold (2B) 1 (= 0.01 × 100, порог давления 1%)
adaptive_vdf_multiplier (2B) 100 (effective_vdf = base × pressure × multiplier)
pruning_idle_windows (8B) 52 000 (4τ₂)
bootstrap_account_pubkey (897B)
bootstrap_node_pubkey (897B)
genesis_content_app_id (32B) = SHA-256("mt-app" || "montana")
genesis_content_data_hash (32B) хэш манифеста книги Montana v1.0
Genesis State Hash = SHA-256(genesis_state_root || protocol_params)
```
Bootstrap keypair (account + node) публикуется в Genesis Decree вместе с протокольными параметрами и Genesis State Hash. Genesis Decree immutable закреплён в коде каждой реализации.
**Валидация Genesis Decree.** Каждая реализация при старте вычисляет Genesis State Hash из сериализованных protocol_params и сверяет с эталонным значением, закреплённым в коде. Любое отклонение включая ненулевые байты в reserved поле делает Genesis Decree недействительным, узел отказывается стартовать. Reserved поле = 0x00 × 8 строго; любое другое значение изменяет Genesis State Hash и создаёт несовместимую сеть.
Первое окно τ после генезиса window_index = 0, protocol_version = 1. Bootstrap-узел единственный proposer первых двух окон (без lookback). Начиная с W = 2 стандартная lookback логика. Bootstrap-узел получает 13 Ɉ за каждое выигранное окно. Per-operation invariant действует с первого окна.
**Bootstrap period.** До появления второго узла (первые 13 000+ окон) bootstrap-узел имеет 100% active_chain_length и является единственным confirmer-ом, proposer-ом и winner-ом. Это физическая необходимость запуска любой сети кто-то является первым. Доминирование bootstrap-узла размывается органически: каждый новый узел, прошедший selection event, вносит свой chain_length в active set. Протокольные правила (quorum 67%, weighted_ticket лотерея, selection rate limit) одинаковы с первого окна специальных bootstrap-правил вне lookback первых двух окон нет.
**Границы модели доверия.**
Протокол имеет два режима доверия, автоматически переключаемые из canonical state.
**Режим Genesis.** Действует от Genesis до первого cemented BundledConfirmation от узла, отличного от bootstrap. В этот период безопасность протокола опирается на:
- Неизменность Genesis Decree (захардкожен в каждой реализации)
- Секретность bootstrap privкey (доверенная сторона автор протокола)
- Отсутствие конкуренции один участник, лотерея без значимых соперников, quorum тривиально достигается bootstrap-узлом
`cemented_bundle_aggregate` в этот период равен хэшу одной bootstrap-подписи. Защита [I-8] от grinding работает при секретности bootstrap privкey стандартное допущение для Genesis-систем. Экономическая нерациональность атаки на single-node сеть компенсирует degraded security margin: нет TimeCoin rewards за победу над единственным участником, лотерея не даёт advantage.
**Режим BFT.** Активируется автоматически при первом cemented BundledConfirmation где `BC.node_id ≠ bootstrap_node_id`. В этот период безопасность опирается на:
- 67% честного active_chain_length
- `cemented_bundle_aggregate` из множества FN-DSA-512 подписей полная защита [I-8] от pre-computation grinding
- Pruning + active_predicate поддерживают соотношение honest/attacker в составе active set
**Переход.** Автоматический, наблюдаемый из canonical state: Node Table содержит 1 non-bootstrap узел с chain_length 1. Версия протокола не меняется. Никакого ручного вмешательства или hard fork. Threat model сдвигается с «trust the Genesis author» на «trust 67% chain_length» плавно и непрерывно.
**Следствия для reference implementation.** Аудит и тестирование обязаны покрывать оба режима раздельно. Тесты bootstrap-периода проверяют поведение в Genesis-режиме (single-confirmer aggregate, bootstrap winning all lotteries, proposer ротация отсутствует). Тесты после bootstrap BFT-поведение (multi-confirmer aggregate, weighted_ticket лотерея, lookback leadership). Переходный тест обязательно проверяет корректность передачи при первой non-bootstrap регистрации один из критичных invariant-моментов в жизни сети.
**Mandatory content replication.** Каждый узел Montana обязан хранить текущую версию книги Montana как persistent blob по (genesis_content_app_id, genesis_content_data_hash). При Fast Sync новый узел загружает genesis content как часть обязательной начальной синхронизации (см. раздел Fast Sync).
#### Открытый вход узлов
Вход узла в консенсус открытый. VDF 13 000 окон + кандидатура + selection event. Никаких приглашений, никаких разрешений.
**Шаг 1: Свободный вход.** Кандидат создаёт аккаунт (OpenAccount), подключается к gossip через account keypair (IBT уровень 3), получает TimeChain values из proposals, вычисляет candidate VDF 13 000 окон от init:
```
candidate_vdf_init = SHA-256(
"mt-candidate-vdf-init" ||
timechain_value(W_start) ||
cemented_bundle_aggregate(W_start - 2) ||
node_id
)
```
W_start окно начала VDF (заявляется кандидатом в NodeRegistration).
**Шаг 2: Кандидатура.** После завершения VDF кандидат публикует NodeRegistration:
```
NodeRegistration:
type 1B <- 0x11 NodeRegistration
suite_id 2B
node_pubkey 897B
operator_account_id 32B
proof_endpoint 32B <- endpoint VDF цепочки (длина vdf_chain_length)
W_start 8B <- окно начала VDF
vdf_chain_length 8B <- длина VDF цепочки в "окнах" (по D хэшей)
signature 666B
Итого: ~1 646 B
```
NodeRegistration ControlObject. При cementing запись в Candidate Pool. Кандидат ожидает selection event.
Валидация NodeRegistration:
1. Подпись FN-DSA-512 валидна для node_pubkey
2. node_id = SHA-256("mt-node" || node_pubkey) уникален (нет в Node Table и Candidate Pool)
3. operator_account_id существует в Account Table и `is_node_operator == 0`
4. `W_p - 2 × τ₂_windows ≤ W_start ≤ W_p - base_vdf_length` (base_vdf_length = 13000). Нижняя граница ограничивает историческое pre-computation окно. Верхняя граница гарантирует что VDF физически выполнен до publication.
5. `vdf_chain_length ≥ required_vdf_length(W_p)` длина заявленной VDF цепочки не меньше требуемой pressure-adjusted длины в момент W_p (incremental apply в батче, см. apply_proposal)
6. proof_endpoint верифицируем: пересчёт VDF от `SHA-256("mt-candidate-vdf-init" || timechain_value(W_start) || cemented_bundle_aggregate(W_start - 2) || node_id)` через `vdf_chain_length` окон. Если `vdf_chain_length × D hashes` VDF iteration от init даёт proof_endpoint валидно.
Верификация: `vdf_chain_length` сегментов VDF проверяются параллельно. На C ядрах: ~(vdf_chain_length/C) × t_segment.
**[I-8] compliance.** `cemented_bundle_aggregate(W_start - 2)` в candidate_vdf_init canonical & unpredictable-offline компонент (зависит от FN-DSA-512 подписей confirmers окна W_start - 2). Атакующий с VDF hardware advantage не может pre-compute init для произвольного будущего W_start aggregate доступен только после cementing W_start - 2. Pre-computation grinding закрыт по построению.
**Шаг 3: Selection event.** Каждые 369 окон сеть выбирает кандидатов из Candidate Pool:
```
selection_windows = { W : W % 369 == 0 }
slots(W) = max(1, floor(active_nodes(W) / 130))
candidates(W) = все записи Candidate Pool где expires > W
sort_key(candidate) = SHA-256(timechain_value(W) || candidate.node_id)
selected(W) = первые slots(W) кандидатов по sort_key
```
Детерминированно, верифицируемо. Каждый узел вычисляет один и тот же результат. timechain_value(W) неизвестен до окна W grinding node_id бесполезен.
**Шаг 4: Регистрация.** Выбранные кандидаты Node Table:
```
start_window = W (окно selection event)
chain_length = 1
last_confirmation_window = 0
```
Узел добавляется в Node Table с chain_length = 1 (позиция активации). Каждое последующее окно с cemented BundledConfirmation инкрементирует chain_length. Оператор-аккаунт получает `is_node_operator = 1`. Запись удаляется из Candidate Pool.
**Expiry.** Кандидатура истекает через (39 000 окон). Запись удаляется из Candidate Pool автоматически.
**Sybil-защита (четыре уровня):**
1. **VDF-барьер:** 13 000 окон последовательного хэширования (при нормальной нагрузке). Физическая работа, sequential по построению не ускоряется параллелизмом.
2. **Adaptive VDF:** стоимость кандидатуры пропорциональна давлению на сеть в момент **публикации** NodeRegistration (не в момент начала VDF работы). Это закрывает timing-manipulation: attacker не знает заранее какое pressure будет в момент W_p.
```
candidate_pressure(W) = pending_candidates(W) / active_nodes(W)
if candidate_pressure(W) > 0.01:
required_vdf_length(W) = 13000 × candidate_pressure(W) × 100
else:
required_vdf_length(W) = 13000 (base_vdf_length)
```
| Ситуация | pending | active | pressure | required_vdf |
|----------|---------|--------|----------|--------------|
| Нормальная | 5 | 1 000 | 0.5% | 13 000 окон |
| Умеренная | 20 | 1 000 | 2% | 26 000 окон |
| Высокая | 100 | 1 000 | 10% | 130 000 окон |
| Атака | 1 000 | 1 000 | 100% | 1 300 000 окон |
| Массовая атака | 100 000 | 1 000 | 10000% | 130 000 000 окон |
**Привязка к W_p (не W_start).** `required_vdf_length` вычисляется из canonical state **в момент cementing NodeRegistration (W_p)**. Кандидат декларирует `vdf_chain_length` в NodeRegistration длину своей VDF цепочки. Валидатор проверяет `vdf_chain_length ≥ required_vdf_length(W_p)` и корректность proof_endpoint через пересчёт VDF от init на `vdf_chain_length` окон.
**Incremental apply в батче одного окна.** Если несколько NodeRegistrations cemented в одно окно W_p, они применяются по canonical sort order с инкрементальным pending:
```
nr_sort_key(nr) = SHA-256(
"mt-nodereg-sort" ||
timechain_value(W_p) ||
cemented_bundle_aggregate(W_p - 2) ||
nr.node_pubkey
)
sort cemented_noderegs_W_p by nr_sort_key
for each NR in order:
current_pending = pending_candidates(W_p) + N_already_applied
current_pressure = current_pending / active_nodes(W_p)
required = adaptive_formula(current_pressure)
if NR.vdf_chain_length >= required:
apply NR; N_already_applied += 1
else:
reject NR
```
Батч одного окна: первая NR видит pending baseline, каждая последующая видит +1. Required растёт в батче. Attacker не получает batch-advantage.
**[I-8] binding sort order.** Domain separator `mt-nodereg-sort` изолирует hash space. `cemented_bundle_aggregate(W_p - 2)` canonical & unpredictable-offline компонент, зависящий от FN-DSA-512 подписей confirmers окна W_p - 2. Атакующий с hardware advantage не может пре-вычислить `nr_sort_key` без privкey honest participants не может grind `node_pubkey` для favorable позиции в батче. Incremental apply неуязвим к keypair-grinding.
**Extension rule для honest operators.** Если первая попытка NodeRegistration rejected по `vdf_chain_length < required`, оператор может:
1. Продолжить VDF работу от текущего proof_endpoint на дополнительные окна
2. Обновить NodeRegistration: новый proof_endpoint = VDF(old_proof_endpoint, additional_length), vdf_chain_length = old + additional
3. Повторить publication с updated proof
VDF работа не теряется только admission откладывается. Honest strategy: consecutive VDF extension пока required не удовлетворено.
**Self-correcting механика.** Чем сильнее давление тем длиннее required VDF дороже Sybil давление падает через admission или expiry. При снижении давления (expiry для просроченных кандидатов) pending уменьшается required нормализуется легитимный вход восстанавливается.
**[I-8] compliance grinding resistance.** Attacker не может предсказать `required_vdf_length(W_p)` в момент начала VDF: pressure зависит от будущих cemented NodeRegistrations и будущих BCs (active_nodes). Attacker не контролирует privкey honest participants не может предвычислить pressure. Forced over-provisioning или extension rule.
**Timing manipulation закрыта.** Attacker не может начать VDF при низком давлении и подать при высоком required проверяется на момент публикации. Minimum VDF (13000) достаточен только если pressure(W_p) 1%. Иначе нужен extended VDF пропорционально pressure.
**Slow-rate participation = организacный рост.** Если actor публикует 1 NodeRegistration per selection interval (369 окон), pending не накапливается (selection event admitting ~1% за event). Pressure остаётся baseline. Стоимость = минимум per candidate. Это **legitimate участие**, неотличимое от honest и правильно не наказывается. Adaptive защищает только от превышения естественного темпа приёма.
3. **Selection rate limit:** max(1, active_nodes/130) за 369 окон. Массовый вход ограничен. Минимум 1 кандидат всегда проходит.
4. **Weighted механизмы:** chain_length определяет вес в quorum (безопасность). lottery_weight (snapshot + seniority bonus) определяет вес в лотерее (эмиссия). Новые узлы начинают с минимальным влиянием. Время единственный путь к весу.
#### Создание аккаунта
Аккаунт создаётся свободно. Пользователь генерирует FN-DSA-512 keypair вычисляет account_id = SHA-256("mt-account" || suite_id || pubkey) публикует OpenAccount операция cemented запись появляется в Account Table при settle.
Sybil-барьер для аккаунтов: account_age определяет приоритет операций. Новый аккаунт бакет 0, 1 операция за τ₁. Рост приоритета = время. Пустые аккаунты бесполезны без баланса операции невозможны.
#### Скорость роста сети
Узлы: selection event каждые 369 окон, slots = max(1, active_nodes/130). Рост ограничен selection rate:
```
Genesis (1 узел): 1 новый узел за 369 окон
active_nodes = 100: 1 новый узел за 369 окон
active_nodes = 1 000: 10 новых узлов за 369 окон
active_nodes = 10 000: 100 новых узлов за 369 окон
```
Каждый кандидат проходит 13 000 окон VDF. Первые кандидаты появляются через 13 000 окон после genesis.
Аккаунты: создаются свободно через OpenAccount. Рост пользовательской базы определяется распространением TimeCoin.
---
## Потоковая модель
Операции аккаунтов текут непрерывно. Узел получает операцию проверяет подпись FN-DSA-512 и баланс (против settled state W-1) передаёт в P2P gossip. Confirmers (~100 узлов с наибольшим chain_length) собирают операции за окно и публикуют BundledConfirmation.
Операция проходит два состояния:
- **Cemented** (quorum event): 67% active_chain_length подтвердили. Операция необратима. Баланс ещё не обновлён.
- **Settled** (конец окна, apply at window close): все cemented операции окна применены к Account Table батчем. Баланс обновлён. state_root зафиксирован в proposal.
Два параллельных процесса:
- **Операции** подтверждаются непрерывно через confirmations (cement), применяются батчем в конце окна (settle)
- **Часы** тикают по расписанию окон τ (TimeChain, лотерея, TimeCoin)
Кошелёк получателя отображает входящий перевод в два этапа: «confirmed» после cement (quorum event), «settled» после apply at window close (apply at window close). Между cement и settle операция уже необратима различие только для UX индикации.
Цепочки аккаунтов полностью независимы. Операции разных аккаунтов обрабатываются параллельно без конфликтов.
---
## Временные слои (τ)
```
τ₁ = 1 window → τ₂ = 13 000 windows
```
Одно окно τ₁. Всё остальное производные в window counts.
### τ₁ — Окно (D хэшей)
Единственная единица канонического времени протокола. Регистрация одного окна Montana Time и эмиссия.
- TimeChain продвигается на `D` хэшей
- NodeChain: chain_length инкрементируется при cemented BundledConfirmation
- Операции аккаунтов подтверждаются непрерывно через confirmations (cement), применяются батчем в конце окна (settle)
- control_set: все cemented ControlObjects из окон (previous_proposal.window, current_window] (каноничен)
- Confirmers (~100) публикуют BundledConfirmation (операции текущего окна + VDF_Reveals предыдущего окна)
- Кандидаты (~12) публикуют VDF_Reveal с lottery endpoint = SHA-256(T_r || node_id || window_index); reveals цементируются через BundledConfirmation следующего окна
- Лотерея: `ticket_i = -ln(endpoint_i / 2^256)`, winner = argmin(weighted_ticket) среди cemented VDF_Reveal nodes + cemented account operations
- Winner_{W-1} определяется proposer_W (= winner_{W-2}) из cemented VDF_Reveals окна W-1 и cemented account operations окна W-1
- Proposer (proposer_node_id) публикует подписанный proposal
- Финальность proposal: подпись proposer_node_id на proposal header. Каждый валидатор применяет control_set + TimeCoin детерминированно и проверяет state_root
- TimeCoin: регистрация одного окна Montana Time (13 Ɉ) победителю
- Supply audit: суммарная эмиссия TimeCoin от генезиса сверяется с `supply(window_index) = 13 × (window_index + 1) Ɉ`
- Разрешение форков: приоритет ветки с наибольшим суммарным TimeChain-доказательством
TimeChain safety: компрометация значения TimeChain требует нарушения свойства последовательности SHA-256 VDF.
TimeChain liveness: задержка продвижения TimeChain невозможна TimeChain вычисляется каждым узлом независимо.
### τ₂ — Адаптация (13 000 windows)
- Адаптация D через participation-ratio feedback (см. ниже)
- Snapshot account_chain_length: для каждого аккаунта `account_chain_length_snapshot = account_chain_length`. Snapshot используется лотереей аккаунтов в течение следующего τ₂. Детерминированно для всех узлов
- Pruning Account Table: удаление пустых аккаунтов без активности (52 000 окон) с обновлением Merkle путей
- Pruning Node Table: для каждого узла N где `(current_window - N.last_confirmation_window) > 8 × τ₂_windows`:
1. Если `N.operator_account_id` существует в Account Table установить `Account Table[N.operator_account_id].is_node_operator = 0` (operator-аккаунт освобождается, может участвовать в лотерее аккаунтов)
2. Удалить запись N из Node Table
3. Пересчитать node_root
- Supply audit (sanity check): Σ balance(account) для всех аккаунтов = 13_000_000_000 × (window_index + 1)
- Криптографическая амнезия: подписанные proposals сохраняются навсегда верифицируемая цепочка state commitments. Proposals доказывают что конкретное состояние было закоммичено proposer-узлом; восстановление содержимого состояния требует snapshot или архива
- Пересчёт параметра D через participation-ratio feedback
#### Адаптация D через participation-ratio feedback
D адаптируется на границе τ через каноническое chain observation долю активного chain_length-а, успевшего подписать BundledConfirmation в каждом окне предыдущего τ₂.
**Канонический вход:**
```
participation_ratio(W) = cemented_chain_length(W) / active_chain_length(W)
```
Где `cemented_chain_length(W)` суммарный chain_length узлов, чьи BundledConfirmation для окна W попали в cemented set; `active_chain_length(W)` суммарный chain_length узлов с `active(node, W) = true`. Оба числа канонически вычисляются каждым узлом bit-exact из Node Table и cemented sets.
**Формула адаптации на τ₂ boundary:**
```
median_ratio = median(participation_ratio(W) for W in последние 13 000 окон)
Если median_ratio >= 0.95: D_new = D_old × 1.03 (+3%, сеть в комфорте, ускоряемся)
Если median_ratio <= 0.85: D_new = D_old × 0.97 (-3%, сеть под давлением, замедляемся)
Иначе (dead zone): D_new = D_old (zone comfort, D не трогаем)
```
**Семантика.**
- `median_ratio >= 0.95`: большинство активных узлов легко успевают подписать каждое окно. У сети есть запас производительности D можно поднять, окно станет чуть длиннее в единицах работы, TimeCoin эмиссия замедляется в физическом времени, но сеть укрепляет запас прочности.
- `median_ratio <= 0.85`: значительная часть активных узлов не успевает подписать. Сеть близка к границе жизнеспособности D нужно уменьшить, окно становится короче в единицах работы, медленные узлы получают шанс догнать медиану.
- Dead zone 0.85-0.95: естественные колебания, D не адаптируется. Это защита от реактивной волатильности.
**Свойства.**
- **Канонически детерминировано.** participation_ratio вычисляется из canonical cemented sets и Node Table. Два честных узла получают одно и то же значение bit-exact.
- **Опирается только на canonical chain observations.** Все входные данные формулы cemented sets и Node Table, оба детерминированы. Corollary I-3.a соблюдён.
- **Медленная реакция.** Adjustment rate ±3% за τ делает стратегическую манипуляцию через withholding confirmations экономически нерациональной: actor-у с 10% chain_length-а для сдвига D на 2x требуется систематически saboтировать свои подписи ~24 эпохи, теряя все свои TimeCoin награды в этот период.
- **Dead zone защищает от флуктуаций.** Случайные колебания participation_ratio в диапазоне 0.85-0.95 не вызывают adaptation.
- **Interleaving на 1 ядре ожидаемый источник participation_ratio < 1.0.** Узел делит одно ядро между TimeChain VDF и validation; контекстные переключения систематически приводят к пропуску отдельных окон. Это нормальное поведение 1-ядерной архитектуры, не деградация сети. Dead zone 0.85-0.95 поглощает типичный interleaving overhead; feedback ниже 0.85 автоматически уменьшает D, возвращая validation-у пропорционально больший кусок wall-clock времени. Реализации не должны трактовать participation_ratio < 1.0 как отклонение и менять active predicate или пороги cementing в ответ.
- **Естественное следование hardware progress.** Если железо ускоряется, медианные узлы начинают успевать с запасом, median_ratio поднимается выше 0.95, D растёт, окно нормализуется. Сеть автоматически адаптируется к ожидаемому hardware evolution без явного measurement.
- **Нет stремления к hard fork по дизайну.** Continuous adaptation в рамках speech-first принципа устраняет необходимость периодического hard fork как запрограммированного события.
**Threat model:**
- Actor с <20% chain_length-а экономически не может сдвинуть median_ratio значимо.
- Hyperscaler с 15% узлов может систематически снижать median_ratio на ~15%, но только теряя свои награды. При clamp ±3% за τ максимальный сдвиг D за 24 τ составляет ±1.03^24 ±103%, что ограничено при правильном выборе `D₀` с запасом.
- Координированная атака узлов с >50% chain_length эквивалентна атаке на весь консенсус и не рассматривается в рамках локальной защиты participation_ratio.
**Genesis parameters:**
```
D₀ = 13 000 000 000 (13 × 10⁹)
participation_dead_zone_low = 0.85
participation_dead_zone_high = 0.95
d_adjustment_rate = 0.03 (±3% за τ₂)
```
Параметр D₀ фиксируется в Genesis Decree. Остальные константы закреплены в протокольных параметрах и могут быть изменены только через protocol version upgrade (software hard fork), не через runtime mechanism.
---
## Консенсус — Proof of Time (PoT)
### Четыре цепочки
**TimeChain** — глобальные часы. Чистая VDF-цепочка `T_r = SHA-256^D(T_{r-1})`. Первичный продукт протокола. Источник времени и случайности. Продвигается по расписанию окон.
**NodeChain** — последовательность cemented BundledConfirmation конкретного node_id. chain_length — позиция узла в NodeChain: = 1 при активации, +1 при каждом cemented BundledConfirmation. Инвариант: chain_length ≥ 1. Доказывает присутствие узла.
**Account** — состояние счёта. Операции финализируются непрерывно через подтверждения (67% active_chain_length). ControlObjects включаются в proposal (каноничен).
Зависимости односторонние: TimeChain → NodeChain → AccountChain → AccountTable. Отказ в AccountTable не останавливает часы. Отказ конкретного узла в NodeChain не заражает общий ритм.
### Лотерея
Лотерея объединяет два класса участников: узлы (через VDF_Reveal с каноническим endpoint) и аккаунты (через AccountChain). Каждый класс производит weighted ticket по длине своей цепочки. Lowest weighted_ticket из объединённого множества побеждает.
**Узлы** автоматически участвуют в каждом окне:
```
ticket_node = -ln(endpoint_node / 2^256)
lottery_weight = chain_length_snapshot + min(chain_length / 69, chain_length_snapshot)
weighted_ticket_node = ticket_node / lottery_weight
```
**Аккаунты** участвуют в окне с финализированной операцией:
```
ticket_account = -ln(endpoint_account / 2^256)
weighted_ticket_account = ticket_account / account_chain_length
```
`account_chain_length_snapshot` обновляется на τ₂ boundary, frozen до следующей τ₂ boundary. Лотерея использует snapshot — детерминированно для всех узлов.
Если weighted_ticket < target субъект кандидат. Target калиброван на ~13 кандидатов за окно (включая оба класса). Из кандидатов побеждает lowest weighted_ticket.
**Стимул узла:** каждое окно с опубликованным BundledConfirmation увеличивает chain_length увеличивает шанс победы. Пропущенное окно это окно не входит в chain_length. Узел остаётся в Node Table и продолжает участвовать.
**Стимул аккаунта:** каждое окно с операцией увеличивает account_chain_length реальный (хоть и редкий) шанс выиграть 13 Ɉ за активность в Montana.
### Победитель τ₁
Победитель определяется после закрытия окна τ₁. Lowest weighted_ticket из всех кандидатов (узлов и аккаунтов) = победитель.
**Если победил узел:**
- Записывает TimeChain value
- Operator account узла получает 13 Ɉ TimeCoin
- Коммитит State Root
- Формирует proposal (control_set + State Root + TimeCoin), подписывает node_pubkey
**Если победил аккаунт:**
- Аккаунт получает 13 Ɉ TimeCoin (winner_account.balance += 13_000_000_000 )
- Proposal формирует **узел-кандидат с минимальным weighted_ticket в этом окне** (proposer_node)
- Если в окне нет узлов-кандидатов proposer выбирается из всех узлов с lowest weighted_ticket (fallback)
- Proposer не получает дополнительной награды это его обязанность как ближайшего узла
Финальность proposal подпись proposer_node_id на proposal header. Верификация независимый пересчёт state_root.
### Верификация
Proposer публикует: `{proposer_node_id, proposal}`.
Верификация lottery endpoint: один SHA-256 O(1).
Верификация proposal: независимое применение control_set + TimeCoin и сравнение state_root.
### Устойчивость
- **Остановка TimeChain** исключена: каждый узел вычисляет VDF независимо
- **Искажение TimeChain** исключено: VDF последователен, результат детерминирован
- **Proposer grinding** исключён: control_set каноничен, state transition детерминирован, операции финализируются независимо от победителя
- **Front-running** исключён: операции финализируются через подтверждения (quorum event), proposer фиксирует frozen view
- **Предвычисление** исключено: seed содержит текущее значение TimeChain
- **Replay** исключён: TimeChain уникален для каждого τ
- **Аппаратное преимущество** ограничено: последовательное хэширование масштабируется тактовой частотой, не количеством ядер
- **Sybil-барьер**: 13 000 окон VDF + selection event (max 1% active_nodes за 369 окон) + weighted_ticket в лотерее
- **Цензура операций** исключена: операции финализируются через подтверждения узлов, не через победителя
- **Цензура ControlObjects** исключена: control_set каноничен, пропуск = fallback
- **Liveness halt операций** исключён: финализация через 67% active_chain_length, не зависит от победителя
- **Liveness halt proposals** исключён: fallback на следующего кандидата
- **Масштабирование**: трафик лотереи ~8.9 KB за окно при любом количестве узлов
### Разрешение конфликтов
**Двойная операция аккаунта** (две операции с одним prev_hash): equivocation. Cemented до обнаружения необратимо, вторая отклоняется. Не cemented ожидание quorum 13 окон, затем обе отклоняются. См. раздел «Двойная трата».
**Невалидный proposal**: валидаторы отклоняют, fallback на следующего кандидата. Победитель теряет TimeCoin за это окно.
**Два proposal от одного proposer_node_id в одном окне**: оба отклоняются (equivocation), fallback к следующему узлу-кандидату. Если этот узел был winner (winner_class=1), он теряет TimeCoin.
---
## Адреса и переводы
### Полный флоу перевода
```
1. Боб: OpenAccount → cemented (quorum event) → settled (конец окна) →
account_id зарегистрирован в Account Table (balance = 0)
2. Боб → Алисе: "отправь на mt4ZGfe..." (account_id Боба)
3. Алиса формирует Transfer (в следующем окне после settle OpenAccount Боба):
type: 0x02
prev_hash: хэш её предыдущей settled операции (frontier_hash из settled state W-1)
payload: sender (account_id Алисы) || link (account_id Боба) || amount (50_000_000_000 nɈ)
4. Алиса подписывает FN-DSA-512
5. Алиса рассылает операцию узлам сети
6. Каждый узел проверяет (против settled state W-1):
FN-DSA-512 подпись валидна для current_pubkey Алисы
prev_hash совпадает с frontier_hash Алисы
amount > 0
alice.balance >= amount
получатель (Боб) существует в Account Table
7. Confirmers публикуют BundledConfirmation, операция распространяется через P2P gossip
8. Cement: 67% active_chain_length подтвердили → операция необратима (quorum event)
Кошелёк Боба отображает «confirmed»
9. Settle (apply at window close):
alice.balance -= 50 Ɉ
bob.balance += 50 Ɉ
alice.frontier_hash = H(operation)
alice.op_height += 1
alice.account_chain_length += 1
Кошелёк Боба отображает «settled»
```
### Баланс
Баланс аккаунта открытое число `u128 nɈ` в Account Table. Обновляется при settle (apply at window close): исходящий Transfer вычитает amount, входящий зачисляет. Видим всем узлам и через любого верификатора цепочки.
Бэкап = seed (для деривации приватного ключа FN-DSA-512). Восстановление кошелька: ключ выводится из seed, баланс читается из текущего Account Table никакого локального состояния не требуется.
---
## Эмиссия
### Единица
Монета: **TimeCoin** (тикер: $TimeCoin, символ: Ɉ).
1 Ɉ = 1 000 = 1 000 000 μɈ = 1 000 000 000
Одно окно τ регистрирует одну единицу Montana Time = 13 Ɉ. Число 13 фиксированная константа протокола эмиссии за окно.
Точность: 9 знаков после запятой. Все расчёты эмиссии в (целочисленная арифметика, без плавающей точки).
### Issuance schedule
Одно окно Montana Time порождает 13 Ɉ. С первого окна и навсегда.
| Параметр | Значение |
|----------|----------|
| Genesis | window_index = 0 |
| TIME_RECORD | 13 000 000 000 (13 Ɉ за окно) |
### Регистрация окна
```
time_record(window_index) = 13_000_000_000 nɈ
```
Каждое окно τ регистрирует одно каноническое окно Montana Time = 13 Ɉ. Без халвингов, без фаз, без исключений. Одна константа на весь горизонт существования протокола.
### Supply audit
```
supply(window_index) = 13_000_000_000 × (window_index + 1) nɈ
```
Эмиссия за прошедшее окно: proposal_W зачисляет 13 Ɉ за окно W-1 (Lookback Leadership winner_{W-1} определяется при cementing proposal_W). После cementing proposal_W суммарно зачислено окна от 0 до W-1, supply = 13 × (W + 1) Ɉ. Одно умножение. Проверяемо каждым узлом в каждом τ₁. O(1).
### Инфляция
Supply растёт линейно по window_index. Инфляция за окно W:
```
inflation(W) = 1 / W
```
Монотонно убывает, асимптотически стремится к нулю. Чистая арифметика от window_index.
### Раннее участие
Эмиссия постоянна: 13 Ɉ за каждое окно, с первого окна и навсегда. Вероятность победы пропорциональна весу. Узел, работающий дольше, побеждает чаще. Два узла, запустившиеся одновременно, имеют равные шансы независимо от капитала. Узел, запустившийся раньше, имеет преимущество он доказал больше окон присутствия.
Стимул для ранних участников встроен в арифметику: не бонусы, не множители просто больший вес.
### Распределение
Победитель окна τ узел или аккаунт регистрирует одно окно Montana Time и получает 13 Ɉ TimeCoin в своей цепочке. Одно правило. Неизменно с генезиса.
Узлы и аккаунты конкурируют в единой лотерее. Узлы доминируют статистически из-за непрерывного присутствия chain_length растёт каждое окно, weighted_ticket систематически ниже. Аккаунты получают долю эмиссии пропорционально своей активности account_chain_length растёт с каждым окном с операцией. Время единственный арбитр.
Базовый бюджет: 13 Ɉ/τ (регистрация одного окна Montana Time). Реальный бюджет безопасности в покупательной способности зависит от рынка.
13 Ɉ за окно каноническая константа эмиссии Montana Time. Покупательная способность определяется рынком, а не протоколом.
#### Двигатель роста сети
Участие аккаунтов в лотерее создаёт flywheel роста сети:
```
Активные пользователи в приложениях → AccountChain растёт → шансы в лотерее
↓ ↓
Приложения привлекают пользователей Иногда выигрывают TimeCoin
↓ ↓
Разработчики хотят пользователей Дополнительная мотивация активности
↓ ↓
Разработчики запускают узлы Montana Больше операций в сети
↓ ↓
Узлы зарабатывают TimeCoin Сеть растёт и децентрализуется
↓ ↓
Финансирование развития инфраструктуры → больше пропускной способности → лучше для приложений
```
Эмиссия 13 Ɉ за окно одна и та же, но финансирует обе стороны одновременно: узлы (поддержание сети) и активные пользователи (использование сети). Они взаимно усиливают друг друга циркулярная экономика.
---
## Пропускная способность
Размер Transfer: ~779 B (открытый перевод, FN-DSA-512 подпись).
| Канал узла | TPS |
|-----------|-----|
| 10 Mbps | ~1 600 |
| 100 Mbps | ~16 000 |
| 1 Gbps | ~160 000 |
---
## Хранение
### Состояния операции (UX)
Операция проходит два различимых состояния:
```
publish ──→ cement (quorum event) ──→ settle (apply at window close)
"confirmed" "settled"
```
- **Cemented (quorum event):** 67% active_chain_length подтвердили операцию через BundledConfirmation. Операция необратима и гарантированно будет применена в конце окна. Wallet показывает «confirmed».
- **Settled (apply at window close, в конце окна):** все cemented операции окна применены батчем к Account Table в детерминированном порядке. account_root зафиксирован в proposal. Wallet показывает «settled».
Между cement и settle операция уже необратима настройка двух UI-состояний нужна только для индикации завершённости state transition. Зависимые операции (Transfer на только что созданный аккаунт) сериализуются по окнам через confirmer dependency rule, поэтому cemented операция гарантированно settle-ится.
### Модель: глобальное состояние + локальная история
Узлы хранят глобальное состояние (Account Table, Node Table, Candidate Pool, proposals). Тела операций аккаунтов хранятся у владельцев. После settle (apply at window close) state transition применён балансы в таблице обновлены, тело операции сети больше не нужно.
### Два участника
**Узел** мой компьютер (десктоп, сервер, VPS), 24/7, минимум 1 ядро:
```
Consensus (протокольный слой):
Account Table (account_id, balance, frontier_hash, pubkey)
+ persistent sparse Merkle tree (account_root)
Node Table (node_id, pubkey, start_window, chain_length)
+ persistent sparse Merkle tree (node_root)
Candidate Pool (node_id, pubkey, operator, proof_endpoint, W_start, vdf_chain_length, expires)
+ persistent sparse Merkle tree (candidate_root)
Proposals (навсегда)
TimeChain VDF + валидация (1 ядро, 24/7, validation interleaved)
P2P gossip (операции, confirmations, reveals, proposals)
Данные владельца (клиентский слой):
Локальное хранилище (фото, файлы, бэкапы сообщений — зашифровано)
Почтовый ящик (входящие сообщения пока телефон офлайн)
```
Узел принадлежит оператору. Оператор решает что хранить помимо consensus state. Consensus state обязателен без него узел не участвует в сети. Данные владельца решение клиентского слоя: формат, шифрование, объём, retention.
**Ядра и производительность.** TimeChain VDF sequential по построению; дополнительные ядра не ускоряют продвижение TimeChain. Второе ядро изолирует validation от VDF-цикла и устраняет interleaving overhead (пропуск окон ~5-10% при нагрузке). Узлы с 1 ядром полностью участвуют в консенсусе; узлы с 2+ ядрами имеют bounded преимущество в participation_ratio, ограниченное сверху interleaving overhead. Participation-ratio feedback на τ-boundary автоматически подстраивает D под фактическое железо медианы сети.
**Телефон (кошелёк)** клиент моего узла, онлайн когда используется:
```
Хранит:
Свои ключи (seed → keypairs)
Локальная история (операции, сообщения — для UX)
Делает:
Подключается к своему узлу
Отправляет/получает переводы через узел
Читает/пишет данные на свой узел
Забирает сообщения из почтового ящика узла
```
Потеря телефона: seed восстанавливает ключи, баланс в Account Table публичен, данные на узле целы. Потеря узла: seed восстанавливает аккаунт, consensus state скачивается через Fast Sync. Данные владельца (фото, сообщения) ответственность оператора (бэкап, RAID, репликация между своими узлами клиентский слой).
Привязка телефона к узлу, авторизация, синхронизация, формат хранения данных клиентский слой. Протокол предоставляет identity (account_id operator_account_id) как основу для привязки.
### Размеры
| Участник | Данные | Размер |
|----------|--------|--------|
| Узел (1M аккаунтов) | Account Table + Node Table + Candidate Pool + Proposals | ~2 GB |
| Узел (10M аккаунтов) | Account Table + Node Table + Candidate Pool + Proposals | ~11 GB |
| Узел (100M аккаунтов) | Account Table + Node Table + Candidate Pool + Proposals | ~101 GB |
| Кошелёк (обычный) | ~100 операций за 26 τ + контакты + сообщения | ~1 MB |
| Кошелёк (активный) | ~10 000 операций за 26 τ | ~16 MB |
| Корпорация | ~1M Anchor за 26 τ | ~0.8 GB |
### Потеря данных клиента
Потеря телефона: seed восстанавливает ключи, баланс в Account Table публичен, данные на узле целы полное восстановление. Потеря узла: seed восстанавливает аккаунт, consensus state через Fast Sync. Данные владельца (фото, сообщения) ответственность оператора. Бэкап, RAID, репликация между своими узлами решения клиентского слоя.
### Fast Sync (новый узел)
1. Цепочка proposals от генезиса проверка TimeChain-цепочки и подписей proposer-узлов (мегабайты)
2. Snapshot трёх таблиц (Account Table + Node Table + Candidate Pool) от пиров на момент окна W (произвольное недавнее окно)
3. Reconstructed `account_root`, `node_root` и `candidate_root` сравниваются с соответствующими полями из proposal окна W. Все три совпадают snapshot валиден. Проверка `state_root = SHA-256("mt-state-root" || node_root || candidate_root || account_root)` дополнительный integrity check.
4. Catch-up после окна W до текущего:
- Запросить cemented UserObjects и применить их батчем к Account Table по алгоритму apply at window close (включая проверку prev_hash и баланса).
- Запросить cemented ControlObjects (NodeRegistration) и применить их к Candidate Pool в детерминированном порядке. Применить selection events.
- Выполнить incremental update Merkle trees (account_root, node_root, candidate_root) для отражения changes.
- На каждом промежуточном proposal сверять локальный state_root с заявленным в proposal header
5. **Genesis content.** `genesis_content_data_hash` зафиксирован в Genesis Decree как протокольная константа. Загрузка книги Montana по этому хэшу конвенция reference implementation. Формат загрузки и верификации определяется клиентским слоем.
6. Узел синхронизирован и готов к участию
Snapshot привязан к конкретному proposal (settled state после apply at window close). Catch-up дистанция определяется свежестью snapshot обычно несколько окон.
**Полнота сериализации snapshot.** Snapshot обязан содержать canonical byte-for-byte сериализацию всех записей каждой таблицы согласно определениям раздела «Состояние сети» ВСЕ поля каждой записи, включая производные (chain_length_snapshot, checkpoints), счётчики (last_confirmation_window, op_height, account_chain_length), VDF-метаданные (vdf_chain_length, W_start, proof_endpoint) и pubkey material. Пропуск или изменение любого поля одной записи меняет её canonical serialization меняется хэш листа Merkle tree несовпадение с proposer-recorded root окна W snapshot rejected, retry с другого пира.
Это делает полноту snapshot enforced криптографически через Merkle root comparison, не через явное перечисление полей в Fast Sync спецификации. Добавление нового поля в record format (будущая версия протокола) автоматически распространяется в snapshot через canonical encoding Fast Sync логика не требует изменений. Единственное требование: canonical encoding и Node Table / Account Table / Candidate Pool definitions single source of truth для serialization.
Reference implementation обязана сериализовать записи ровно по определениям state records с canonical byte ordering. Отклонения от canonical encoding в одной реализации = несовместимость с другими = невозможность Fast Sync между разными реализациями. Conformance tests должны включать snapshot serialization для эталонного state как один из test vectors.
---
## Application Layer
Montana цифровой стандарт времени. Приложения управляют своим состоянием самостоятельно (серверы, базы данных, P2P). Montana хранит только криптографические отпечатки с привязкой ко времени 32 байта на запись.
### Модель приложения на Montana
Приложение Montana это набор узлов с интерфейсом. Создатель приложения запускает узлы Montana (обычные узлы, тикающие VDF, валидирующие операции, участвующие в консенсусе). Узлы зарабатывают TimeCoin за поддержание сети через лотерею.
**Для создателя приложения:**
- Не нужно строить отдельную инфраструктуру безопасности приватность данных через Anchor (хэш в сети, контент у владельца зашифрованным), антицензура через Transport Obfuscation и Dandelion++, децентрализация через отсутствие центрального сервера получаются бесплатно из протокола
- Бизнес-модель: эмиссия Montana через узлы создателя. Не реклама, не подписка, не продажа данных
- Чем больше пользователей в приложении тем больше операций в сети тем больше нужно узлов для обслуживания (Blob Buffer, валидация, P2P gossip) больше узлов = больше шансов в лотерее = больше TimeCoin
**Для пользователя:**
- Каждое действие в приложении создаёт операцию в его AccountChain
- account_chain_length растёт автоматически с каждым окном с операцией
- Пользователь автоматически участвует в лотерее в каждом окне с операцией без заявок, без стейкинга, без понимания криптографии
- Шанс победы зависит от account_chain_length длинная активная цепочка даёт реальные шансы выиграть 13 Ɉ
- Ничего не привязано к конкретному приложению seed принадлежит пользователю, account_id переходит между приложениями без потери истории
**Нулевая стоимость переключения приложений.** AccountChain пользователя его собственность. Если приложение закрылось или пользователь хочет уйти account_id, баланс, история и накопленный account_chain_length остаются. Пользователь продолжает в другом приложении на том же протоколе. Приложения вынуждены конкурировать качеством, а не замком.
### Двигатель роста сети через AccountChain
Лотерея Montana объединяет два класса участников: узлы (через VDF_Reveal с каноническим endpoint) и аккаунты (через AccountChain). Узлы доминируют статистически из-за непрерывного присутствия. Аккаунты получают долю эмиссии через активность пользователей. Эта механика создаёт самоподдерживающийся цикл роста сети см. раздел "Эмиссия Двигатель роста сети".
### Anchor
Одна операция, данные навсегда привязаны к timechain_value конкретного окна.
```
Anchor:
prev_hash 32B
account_id 32B
app_id 32B <- SHA-256("mt-app" || app_name)
data_hash 32B <- Merkle root, H(document), произвольный хэш
signature 666B
Итого: ~796B
```
app_id детерминированный идентификатор пространства имён. Вычисляется из имени приложения, регистрация не требуется. Позволяет фильтровать, индексировать, строить лёгкие клиенты для конкретного приложения.
### Timestamp Proof
Стандартный формат доказательства: документ D существовал не позже момента T.
В модели v20.2.0 операции аккаунтов финализируются через BundledConfirmations узлов-confirmers, не через включение в proposal. Доказательство существования Anchor набор подписанных подтверждений с суммарным chain_length quorum.
Proof собирается владельцем Anchor в момент финализации и хранится локально вместе с документом. Сеть не обязана хранить BundledConfirmations долгосрочно ответственность за сохранение proof лежит на стороне, которой нужно доказать timestamp.
```
Структура proof:
1. Документ D и H(D)
2. Anchor body (prev_hash, account_id, app_id, data_hash, signature)
3. Если data_hash = MerkleRoot batch'а: Merkle path от H(D) до data_hash
4. Набор BundledConfirmations за окно W cementing'а Anchor:
- каждая содержит H(Anchor) в op_hashes[]
- каждая подписана confirmer node_pubkey
- каждая содержит T_r текущего окна (endpoint field)
- суммарный chain_length confirmers ≥ 67% active_chain_length(W)
5. Proposal header окна W (содержит timechain_value = T)
6. Цепочка proposal headers от W до genesis (через prev_proposal_hash)
Верификация любым третьим лицом, без доверия Montana-узлу:
1. Если есть Merkle path: пересчитать H(D) → data_hash, сравнить с data_hash в Anchor
2. Проверить FN-DSA-512 подпись на Anchor
3. Для каждой BundledConfirmation: проверить FN-DSA-512 подпись confirmer
4. Для каждой confirmation: проверить endpoint = T_r окна W, подтвердить chain_length из Node Table
5. Суммировать chain_length подтверждающих, проверить ≥ 67% active_chain_length(W)
6. Из proposal header окна W взять timechain_value = T
7. Пересчитать TimeChain VDF от proposal окна W до genesis по prev_proposal_hash
```
Proposals хранятся навсегда timechain_value(W) и цепочка к genesis всегда доступны. BundledConfirmations хранятся локально владельцем proof. Timestamp proof самодостаточен и верифицируем в любой момент в будущем.
### Примеры
**Мессенджер.** Каждое сообщение хэшируется, цепочка хэшей формирует Merkle root, Merkle root записывается в Anchor раз в одно или несколько окон. Montana хранит 32 байта доказательство что набор сообщений существовал на конкретном window_index. Подделать историю переписки невозможно хэш не совпадёт.
**Архив документов.** Компания ежедневно записывает Merkle root документов. Через 10 лет регулятор спрашивает «существовал ли документ X на дату Y». Компания предоставляет документ, Merkle proof и ссылку на proposal. Верификация математическая.
**Социальная сеть.** Каждый пост привязан к Montana Time через Anchor. Порядок публикаций доказуем. Редактирование не скрывает оригинал хэш оригинала уже в цепочке.
### Экономика
Anchor бесплатен. Тысячи приложений записывающих якоря утилитарное использование Montana Time. Спрос на токен привязан к утилитарной функции: перевод ценности и запись времени, не спекуляция.
Не нужны смарт-контракты. Не нужен Turing-complete язык. Не нужен газ. Не нужны комиссии.
### Граница протокола и клиентского слоя
Протокол предоставляет три примитива: время (window_index), ценность (Transfer), фиксация (Anchor). Всё остальное хранение данных, мессенджер, discovery контактов, профили, шифрование, репликация контента, форматы файлов реализуется клиентским слоем. Стандарты совместимости между приложениями определяются в спецификации приложения (Montana App spec), не в протоколе.
### Локальное хранилище узла
Узел помимо consensus state имеет локальное хранилище произвольных байт. Это инфраструктура реализации, не consensus содержимое хранилища не входит ни в один root, не проверяется другими узлами, не влияет на участие в консенсусе.
Два режима:
- **Ephemeral** (TTL = τ₂) кратковременные данные, удаляются автоматически
- **Persistent** (TTL = 0) данные владельца, хранятся бессрочно по решению оператора
Формат хранения, индексация, чанкование файлов, протокол обмена данными между узлами, механизмы discovery контента определяются клиентским слоем (см. Montana App spec).
**genesis_content_data_hash** протокольная константа в Genesis Decree. Хэш манифеста книги Montana v1.0. Загрузка и хранение книги по этому хэшу конвенция reference implementation, не consensus enforcement. Узел без книги продолжает участвовать в консенсусе.
### Integration
Три операции для подключения внешних систем к Montana.
#### Write — запись
Внешняя система формирует Anchor и отправляет в P2P-сеть.
```
Вход: app_id (32B) + data_hash (32B) + подпись FN-DSA-512
Выход: Anchor финализирован в окне W через ≥67% active_chain_length
confirmations с timechain_value T_W
```
data_hash произвольный хэш: Merkle root документов, хэш batch'а Rollup, fingerprint состояния. Montana не интерпретирует содержимое хранит 32 байта с привязкой ко времени.
#### Read — сбор proof
Внешняя система собирает timestamp proof в момент финализации Anchor.
```
Вход: Anchor (только что финализированный)
Выход: Anchor body + BundledConfirmations покрывающие H(Anchor) +
proposal header окна cementing'а + цепочка proposal headers до genesis
```
Сбор proof клиентская задача. После получения BundledConfirmations с суммарным chain_length quorum клиент сохраняет proof локально. Узлы Montana не обязаны хранить BundledConfirmations долгосрочно они нужны только для текущего подсчёта quorum.
#### Verify — верификация
Внешняя система проверяет proof автономно, без доверия к Montana-узлу.
```
1. Если есть Merkle path: пересчитать H(D) → data_hash в Anchor
2. Проверить FN-DSA-512 подпись на Anchor
3. Для каждой BundledConfirmation в proof:
a. Проверить FN-DSA-512 подпись confirmer
b. Проверить endpoint = T_r окна W
c. Подтвердить chain_length из Node Table
4. Суммировать chain_length подтверждающих ≥ 67% active_chain_length(W)
5. Проверить FN-DSA-512 подпись proposer на header окна W
6. Проверить timechain_value(W) пересчётом TimeChain VDF от T_{W-1}
7. Проверить цепочку proposals от W до genesis (prev_proposal_hash)
```
Шаги 1, 2, 3a, 3b, 5: O(1) операций. Шаг 6: `D` хэшей на одном ядре (один сегмент TimeChain VDF). Шаг 7: линейная проверка подписей и хэшей по цепочке proposals от окна W до genesis.
Полная верификация от генезиса: H сегментов TimeChain VDF, каждый независим. На C ядрах: ~(H/C) × D хэшей. TimeChain хранит все промежуточные T_r в proposals параллелизация полная.
---
## Ключи
### Мнемоника и seed
24 слова из словаря мнемоники (2 048 английских слов). 256 бит энтропии + 8 бит checksum.
```
mnemonic: 24 слова
seed: PBKDF2-SHA512(mnemonic, "mt-seed", 2048 итераций)
```
### Деривация
```
seed
├── Аккаунт: FN-DSA-512 keypair (HMAC-SHA256(seed || "mt-account-key"))
│ → account_id = SHA-256("mt-account" || account_pubkey)
└── Узел: FN-DSA-512 keypair (HMAC-SHA256(seed || "mt-node-key"))
→ node_id = SHA-256("mt-node" || node_pubkey)
```
Один seed порождает два FN-DSA-512 keypair. Аккаунт подпись операций пользователя. Узел подпись proposals и lottery endpoints. Это полный набор секретных материалов protocol core.
account_id и node_id выводятся из публичных ключей, верифицируемы без знания seed.
**Application-level keys.** Приложения могут выводить дополнительные ключи из того же seed через свои domain separators (не protocol-critical). Рекомендованный стандарт Application Layer определяет encryption keypair через `HMAC-SHA256(seed || "mt-app-encryption-key")` для ML-KEM-768. Этот ключ не входит в protocol core его знание не нужно узлам консенсуса. Приложения использующие этот стандарт получают совместимое восстановление: один seed все ключи (account, node, encryption).
Следствие: любое устройство с seed получает полный доступ к аккаунту. Баланс читается из текущего Account Table никакого локального состояния не требуется. Бэкап = 24 слова, восстановление мгновенное.
---
## Криптографическая реализация
### Primitive layer
Собственная реализация криптографических примитивов запрещена. Только audited библиотеки с constant-time гарантиями и опубликованными test vectors.
| Примитив | Стандарт | Роль |
|----------|----------|------|
| SHA-256 | FIPS 180-4 | TimeChain, lottery endpoints, адреса, Merkle-деревья |
| FN-DSA-512 | NIST PQC selection финал (июль 2022), forthcoming FIPS 206, reference implementation production-ready | Подписи операций аккаунтов и proposals узлов |
### Consensus encoding layer
Консенсусно-критическая поверхность: каноническая сериализация, Merkle layout и domain separation. Разная сериализация одного объекта = разный хэш = форк. Эта секция нормативно определяет byte-for-byte marshalling algorithm для всех консенсусных объектов.
**Primitive types.**
| Type | Size | Encoding |
|------|------|----------|
| u8 | 1B | raw byte |
| u16 | 2B | little-endian |
| u32 | 4B | little-endian |
| u64 | 8B | little-endian |
| u128 | 16B | little-endian |
| bytes[N] | N байт | raw bytes (нет length prefix N известно из типа) |
Все integer-поля используют little-endian byte ordering. Знак отсутствует (все counters unsigned).
**Fixed-length byte arrays** (account_id, node_id, hash, pubkey, signature): сериализация = raw bytes, длина детерминирована определением типа (32B для id/hash, 897B для FN-DSA-512 pubkey, 666B для FN-DSA-512 signature). Нет length prefix и нет разделителей.
**Struct serialization.** Поля структуры сериализуются в declared order из определения «Состояние сети». Каждое поле кодируется по своему типу. Байты конкатенируются без padding и без разделителей. Результат = total bytes = сумма size всех полей.
Пример Account Table record (полный layout):
```
serialize(account) :=
account_id (32B)
balance (16B, u128 little-endian)
suite_id (2B, u16 little-endian)
is_node_operator (1B, u8)
frontier_hash (32B)
op_height (4B, u32 little-endian)
account_chain_length (4B, u32 little-endian)
account_chain_length_snapshot (4B, u32 little-endian)
current_pubkey (897B)
creation_window (4B, u32 little-endian)
last_op_window (4B, u32 little-endian)
= 1000 bytes (deterministic, fixed size)
```
**Variable-length arrays.** Consensus-critical массивы кодируются как `count_field + elements_concatenated`. Count field присутствует в struct definition как отдельное поле (например, `op_count 2B` в BundledConfirmation). Если count явно не указан в struct prefix = u16 little-endian.
**Canonical ordering consensus-critical массивов.**
Детерминизм требует фиксированного порядка элементов:
| Array | Canonical sort key | Обоснование |
|-------|-------------------|-------------|
| `op_hashes[]` в BundledConfirmation | ascending lexicographic по hash | 32B comparison byte-for-byte |
| `reveal_hashes[]` в BundledConfirmation | ascending lexicographic по hash | 32B comparison byte-for-byte |
| cemented_bundles_W (для aggregate) | ascending по node_id | детерминированный порядок подписей |
| Candidates в selection event | sort_key(c) ascending | формула раздела Selection |
| NodeRegistrations в incremental apply W_p | nr_sort_key(nr) ascending | формула раздела Adaptive VDF |
Lexicographic byte comparison: старший байт (index 0) важнее младшего. Массивы одинаковой длины.
**Domain separator encoding.**
Доменные разделители (`"mt-account"`, `"mt-lottery"`, etc.) сериализуются как **raw ASCII bytes без null terminator, без length prefix**. Длина разделителя фиксирована его литералом.
Пример: `"mt-lottery"` 10 bytes: `0x6D 0x74 0x2D 0x6C 0x6F 0x74 0x74 0x65 0x72 0x79`.
Hash composition: `SHA-256("mt-lottery" || T_r || ...)` означает SHA-256 applied to concatenation: 10 байт разделителя + 32 байта T_r + ... Разделитель всегда в начале hash input.
**Sparse Merkle Tree algorithm.**
Глубина дерева: 256 бит (индекс = 32-байтовый ключ, биты от наименьшего значимого (LSB) до старшего).
| Операция | Формула |
|----------|---------|
| leaf_hash(record) | SHA-256("mt-merkle-leaf" \|\| serialize(record)) |
| internal_hash(left, right) | SHA-256("mt-merkle-node" \|\| left \|\| right) |
| empty_leaf | 0x00 × 32 |
| empty_internal(level) | precomputed: empty(0) = empty_leaf; empty(k+1) = internal_hash(empty(k), empty(k)) |
Precomputed массив `empty_internal[0..256]` 257 × 32B = ~8 KB, вычисляется один раз и кэшируется.
**Update path при изменении записи с ключом `key`:**
```
1. new_value := leaf_hash(new_record)
2. current_bits := key
3. for L = 0 to 255:
bit := (current_bits >> L) & 1
sibling := текущий sibling на уровне L (из tree или empty_internal(L))
if bit == 0:
new_value := internal_hash(new_value, sibling)
else:
new_value := internal_hash(sibling, new_value)
4. new_root := new_value
```
Сложность: O(256) worst-case, O(log N) для sparse tree с caching непустых веток. Для N = 10⁹ записей эффективная глубина ~30 уровней.
Direction convention: bit = 0 означает позиция «слева», bit = 1 «справа». Фиксировано для детерминизма.
**Inclusion proof format:**
```
MerkleProof:
key 32B <- индекс листа
leaf_value ? <- serialize(record) или пустой массив (proof of absence)
leaf_length 4B <- u32 little-endian размер leaf_value (0 для absence)
sibling_bitmap 32B <- 256 бит: bit[i] = 1 если sibling на уровне i non-empty
sibling_count 2B <- u16 little-endian, число non-empty siblings
siblings[] ? <- sibling_count × 32B, siblings в порядке возрастания уровня
```
Верификация: reconstruct root iteratively используя `key` биты + `leaf_value` + siblings (с учётом bitmap для empty levels). Сравнить с known root.
**Endianness bitmap.** Bit[0] = наименее значимый бит первого байта sibling_bitmap (little-endian bit order внутри байта). Level L bitmap byte (L >> 3), bit offset (L & 7).
**Обязательные требования.**
- Fixed binary encoding для каждого консенсусного объекта
- Little-endian для всех integer типов
- Domain separation для всех hash compositions
- Canonical ordering массивов где порядок влияет на hash
- Альтернативные сериализации запрещены
- Test vectors для каждого консенсусного объекта (генерируются reference implementation)
- Cross-implementation conformance tests перед запуском mainnet
**Domain separators registry:**
| Домен | Контекст |
|-------|----------|
| `mt-op` | Хэширование операций аккаунтов |
| `mt-header` | Хэширование proposal headers |
| `mt-account` | Деривация account_id |
| `mt-candidate-vdf-init` | VDF init seed для кандидата на вход в сеть |
| `mt-merkle-leaf` | Листья Merkle-деревьев |
| `mt-merkle-node` | Внутренние узлы Merkle-деревьев |
| `mt-state-root` | Композиция state_root из node_root, candidate_root и account_root |
| `mt-timechain` | TimeChain VDF seed |
| `mt-lottery` | Lottery endpoint seed (SHA-256(T_r \|\| cemented_bundle_aggregate(W-2) \|\| node_id \|\| window_index)) |
| `mt-bc-aggregate` | Агрегат подписей cemented BundledConfirmation окна — компонент endpoint, unpredictable offline |
| `mt-bc-aggregate-empty` | Fallback для вырожденного случая cemented_bundle_aggregate (\|cemented_bundles_W\| == 0): SHA-256("mt-bc-aggregate-empty" \|\| W) |
| `mt-selection` | Sort key для selection event (SHA-256("mt-selection" \|\| timechain_value \|\| cemented_bundle_aggregate(W-2) \|\| node_id)) |
| `mt-nodereg-sort` | Sort key для incremental apply NodeRegistrations в окне W_p (SHA-256("mt-nodereg-sort" \|\| timechain_value(W_p) \|\| cemented_bundle_aggregate(W_p-2) \|\| node_pubkey)) |
| `mt-confirmation` | Хэширование async confirmations |
| `mt-app` | Деривация app_id для Application Layer |
| `mt-node` | Деривация node_id |
| `mt-genesis` | Деривация frontier_hash genesis-аккаунтов |
| `mt-nodechain-genesis` | (deprecated v28.0.0, ранее NodeChain VDF genesis init) |
| `mt-seed` | Salt для PBKDF2 деривации seed из мнемоники |
| `mt-account-key` | Деривация keypair аккаунта из seed |
| `mt-node-key` | Деривация keypair узла из seed |
| `mt-account-lottery` | Endpoint AccountChain для лотереи |
| `mt-content-chunk` | Хэширование чанков контента (клиентский слой) |
| `mt-content-manifest` | Хэширование манифеста чанкованного контента (клиентский слой) |
| `mt-profile` | Хэширование ProfileBlob в Application Layer |
| `mt-encryption-key` | Хэширование EncryptionKeyBlob в Application Layer |
| `mt-app-encryption-key` | Деривация encryption keypair из seed в Application Layer |
| `mt-prekeys` | Хэширование PreKeyBundle в Application Layer |
| `mt-phone-public` | Хэширование phone_hash для public mode phone discovery |
| `mt-tunnel` | IBT proof подпись при входе на узел |
| `mt-bootstrap-pow` | Proof-of-work при подключении к bootstrap |
### Protocol layer
Собственная реализация поверх криптографического ядра:
| Компонент | Назначение |
|-----------|------------|
| Merkle-деревья | State Root (из SHA-256 вызовов) |
| VDF scheduling | Управление TimeChain VDF |
| State machine | Account Table, Node Table, state transitions |
| P2P gossip | Распространение операций, confirmations и proposals |
### Инфраструктура
| Библиотека | Назначение |
|------------|------------|
| RocksDB | Хранение Account Table и операций |
| libp2p | P2P транспорт |
Production: Rust.
---
## Сетевой уровень
Все временные параметры сетевого уровня (frame rate, padding window, feeler interval, Dandelion timers) — implementation guidance для локального сетевого стека узла. Они оперируют на локальных часах узла и находятся вне scope consensus state.
### Transport Obfuscation
Монтана — персональная сеть. Каждый узел — персональный сервер участника. Транспортный слой построен из этого определения: персональный сервер отвечает только участникам, персональный мессенджер скрывает тайминг сообщений, персональный = доступный обычному человеку.
#### Шифрование
Все P2P-соединения инкапсулированы в TLS 1.3 на порт 443. Noise framework (встроен в libp2p) для аутентификации по публичному ключу узла внутри TLS. Содержимое трафика недоступно наблюдателю.
#### Identity-Bound Tunnel (IBT)
Персональный сервер отвечает только участникам сети. После TLS handshake клиент отправляет proof аутентификации. Узлы (зарегистрированные и приглашённые) подписывают node keypair. Аккаунты (клиенты) подписывают account keypair.
```
proof = FN-DSA-512_sign(client_privkey,
"mt-tunnel" || server_node_id || floor(current_window_index / 2))
```
Сервер проверяет:
1. Подпись валидна для заявленного client_pubkey
2. Window slot = текущий ИЛИ предыдущий (окно = 2 window_index)
3. Уровень доступа — сервер проверяет client_pubkey по трём таблицам последовательно, первое совпадение определяет уровень:
- `node_id = SHA-256("mt-node" || client_pubkey)` в Node Table → **полный gossip** (клиент подключился node keypair)
- `node_id` с `node_pubkey = client_pubkey` в Candidate Pool → **read-only gossip**: получает proposals (кандидат подключился node keypair)
- `account_id = SHA-256("mt-account" || suite_id || client_pubkey)` в Account Table → **подключение к доверенному узлу** (клиент подключился account keypair)
- Ни одно не найдено → отказ
Условия 1-2 выполнены + уровень 3 определён → Noise handshake → Montana P2P с соответствующим уровнем доступа.
Любое не выполнено → TLS alert `bad_certificate`, close. Стандартное поведение сервера с обязательной аутентификацией клиента — таких серверов в интернете миллионы (корпоративные порталы, API, банковские системы).
Replay protection: server_node_id привязывает proof к конкретному получателю. Window slot ограничивает replay window до 2 окон.
Bootstrap exception: genesis bootstrap nodes хардкодированы как `(IP, node_id, pubkey) × 12`. Bootstrap принимает proof от любого валидного FN-DSA-512 ключа (Account Table не проверяется). Для защиты от connection flood клиент прилагает proof-of-work:
```
SHA-256("mt-bootstrap-pow" || proof || nonce) < target
```
`target` подбирается чтобы стоимость ≈ 100ms CPU. PoW требуется только при подключении к bootstrap, не к обычным peers.
#### Uniform Framing
Все Montana-сообщения внутри IBT-соединения фрагментируются на фреймы фиксированного размера:
```
frame_size = 1024 bytes
frame format:
flags 1B (0x01 = data, 0x02 = padding, 0x04 = continuation)
length 2B (полезная нагрузка, ≤1021B)
payload 1021B (данные или random padding до frame_size)
```
Персональный мессенджер скрывает тайминг: между узлами идёт постоянный поток фреймов. Реальные Montana-сообщения замещают padding-фреймы, не добавляются к ним. Наблюдатель внутри сети не может отличить перевод от доказательства времени от тишины — всё одинаковые зашифрованные фреймы.
Параметры:
- Baseline frame rate: 1 frame/сек на исходящих соединениях. Входящие — фреймы при наличии данных
- Maximum burst: ≤ 8 frames подряд без паузы ≥ 10ms
- Minimum padding ratio: ≥ 20% фреймов в скользящем 60-секундном окне на исходящих
Персональный = доступный: 13 исходящих × 1 frame/сек × 1024 bytes = 13 KB/сек ≈ 33 GB/мес. Приемлемо для домашнего сервера.
#### Transport Randomness
Все рандомизированные решения транспортного уровня (stem routing, frame scheduling, nonce generation) используют CSPRNG из OS entropy pool. Детерминированный PRNG от node state запрещён для transport-layer randomness.
Transport obfuscation ортогонален консенсусу. TimeChain, state machine работают поверх любого транспорта без изменений.
### Peer Selection
Открытый вход с VDF-барьером делает sybil-узлы дорогими: каждый sybil = 13 000 окон VDF (sequential SHA-256, не ускоряется параллелизмом) + selection event. Peer selection использует diversity constraints из протокольных данных (start_window) и сетевых (/16, ASN).
P2P gossip — только зарегистрированные и приглашённые узлы (уровни 1-2 IBT, см. Transport Obfuscation → Identity-Bound Tunnel). Аккаунты (уровень 3 IBT) взаимодействуют через свой доверенный узел.
#### Исходящие соединения
13 исходящих, все полные. Uniform framing скрывает типы сообщений — отдельные relay-only соединения не нужны.
Выбор: случайный 50/50 из таблиц «новые» и «проверенные». Бакетирование с секретным ключом узла. Без preference по chain_length — выбор равномерный.
#### Четыре уровня diversity
Каждый исходящий проверяется по всем четырём constraints:
```
Сетевые:
/16 — не более 1 исходящего на /16 подсеть (IPv4) или /48 (IPv6)
ASN — не более 2 исходящих на автономную систему
Протокольные:
start_window — не более 2 исходящих к узлам с start_window в одном τ₂
```
Сетевые constraints: /16 и ASN diversity. Протокольный constraint start_window канонически доступен из Node Table.
Следствие: кластер sybil зарегистрированных в один τ₂ → максимум 2 из 13 слотов. Eclipse требует узлы в 7+ разных AS в 7+ разных /16 с регистрацией в 7+ разных τ₂.
ASN-карта загружается при запуске. Без карты — fallback на /16.
#### Адресный менеджер
Две таблицы:
- **Новые** — адреса полученные через peer exchange и DHT. Узел ещё не подключался
- **Проверенные** — адреса к которым узел успешно подключался через IBT
Бакетирование: `bucket = Hash(secret_key, source_group, addr_group) % N`. Детерминированно с секретным ключом — атакующий не может предсказать в какой бакет попадёт его адрес.
#### Входящие соединения
До 32 входящих. При переполнении — вытеснение:
1. Защитить 4 с наименьшим пингом
2. Защитить 4 с последними полезными сообщениями (любое валидное Montana-сообщение которое узел ещё не видел)
3. Защитить до 8 из разных подсетей (по одному от каждой)
4. Защитить 4 с последними proposals
5. Из оставшихся — вытеснить из крупнейшей подсетевой группы
#### Якоря
2 исходящих с наибольшим uptime соединения сохраняются каждые τ₂. При перезапуске после аварии или обновления — подключиться к якорям первым до случайного выбора из таблиц.
#### Feeler
Каждые 10 минут: подключиться к случайному адресу из «новых», выполнить IBT handshake (все три уровня проверки). Успех на любом уровне → перенести в «проверенные» с пометкой уровня (node / invited / account). Неуспех → пометить или удалить.
#### Ротация
По поведению: если peer не передал ни одного нового proposal за τ₂ — заменить. Peer с долей невалидных сообщений выше 50% в скользящем τ₁-окне — отключить с запретом переподключения на τ₂. Peer который relay-ит честно — полезен сети, остаётся.
#### PeerRecord
Формат записи о пире при peer exchange:
```
PeerRecord:
ip 16B (IPv4-mapped IPv6)
port 2B (u16)
node_id 32B
node_pubkey 897B (FN-DSA-512)
```
Без node_id и node_pubkey клиент не может вычислить IBT proof для подключения. Peer exchange: не более 100 PeerRecord за сообщение. Не более 1 peer exchange сообщения в минуту от каждого peer.
### Censorship-Resistant Discovery
Генезис: 12 hardcoded bootstrap nodes `(IP, node_id, pubkey)`. Если все 12 IP заблокированы на уровне страны — новый узел не может войти в сеть. Четыре независимых канала обнаружения. Достаточно одного из четырёх.
**1. Peer exchange.** Каждый узел хранит и передаёт список активных пиров новичкам. Достаточно знать IP одного узла — друг, QR-код, мессенджер. Один живой контакт = вход в сеть.
**2. DHT.** Kademlia DHT поверх libp2p. Узлы находят друг друга без центральной точки. Идентификаторы рандомизированы — DHT не раскрывает node_id до установления Montana-соединения.
**3. Bridge nodes.** Узлы за пределами цензурируемой юрисдикции, опубликованные через внеполосные каналы (социальные сети, мессенджеры, печатные QR-коды). IP bridge node неизвестен фаерволу до использования.
**4. Encrypted Client Hello (ECH).** Bootstrap через CDN с поддержкой ECH. SNI зашифрован — наблюдатель видит IP CDN, но не целевой домен. Эффективен в юрисдикциях без активной блокировки ECH extension. В юрисдикциях блокирующих ECH (Китай с 2023, Россия с 2024) — канал неработоспособен. Для таких юрисдикций — каналы 1-3.
Избыточность = устойчивость. Четыре канала независимы. Блокировка одного не влияет на остальные.
### Dandelion++ (анонимность отправителя)
P2P gossip Montana ретранслирует операции через все узлы. Без защиты первый пир знает IP отправителя. Dandelion++ (Fanti et al. 2018) устраняет связь IP → операция модификацией существующего gossip.
**Две фазы:**
```
Stem (стебель):
Операция проходит по цепочке случайных узлов (в среднем 2-3 hop).
Каждый узел видит только предыдущий hop, не автора.
На каждом hop с вероятностью p = 0.4 переход в fluff.
E[stem_length] = 1/p = 2.5 hops.
P(stem ≤ 1) = 40%, P(stem ≤ 3) = 78%.
Fluff (пух):
Последний stem-узел запускает обычный gossip.
Для всей сети операция «появилась» из случайной точки.
```
**Stem routing.** Стебель использует только исходящие соединения — входящие не участвуют. Каждые 693 окна узел выбирает 2 из 13 исходящих как стебельных (stem epoch). Все стебельные операции в эпохе направляются через одного из этих 2 (выбор по hash(msg)).
**Применение по типу объекта:**
| Объект | Режим | Причина |
|--------|-------|---------|
| UserObject (Transfer, Anchor, OpenAccount, ChangeKey) | Stem → fluff | Скрыть IP отправителя |
| ControlObject (NodeRegistration) | Stem → fluff | Скрыть IP регистрирующегося кандидата |
| VDF Reveal | Прямой gossip (без stem) | node_id публичен в reveal, анонимность невозможна; IP скрыт Transport Obfuscation (TLS 1.3 на порт 443) |
| Confirmation | Stem → fluff | Скрыть какой узел подтвердил первым |
**Свойства:**
| Угроза | Защита |
|--------|--------|
| Пир видит IP отправителя | Stem: пир видит только предыдущий hop |
| Глобальный наблюдатель (ISP) | TLS 1.3 + uniform framing (Transport Obfuscation) |
| Анализ графа gossip | Операция входит в gossip из случайной точки |
| Контроль k узлов | Деанонимизация требует контроля O(√n) узлов |
**Реализация:**
```
stem_peers = random_sample(outbound, 2) // каждые 693 окна
on_receive_stem(msg, from_peer):
if random() < 0.4:
gossip_broadcast(msg) // fluff
else:
next = stem_peers[hash(msg) % 2] // детерминированный выбор из 2
send_stem(msg, next) // продолжить stem
start_timer(msg, 30s) // страховка на каждом hop
on_timer_expired(msg):
if msg не обнаружен в gossip:
gossip_broadcast(msg) // принудительный fluff
```
Каждый stem-узел страхует следующий. Таймер 30 секунд на каждом hop независимо. Если следующий hop уронил сообщение — текущий hop обнаруживает отсутствие операции в gossip и делает fluff сам. Максимальная задержка = 30 секунд (один hop), не кумулятивная.
Dandelion++ не требует внешней инфраструктуры. Каждый Montana-узел уже является relay — gossip существует, stem добавляет 2-3 hop перед ним. Latency overhead: миллисекунды.
### NAT Traversal
Персональная сеть работает когда каждый может войти. Большинство домашних пользователей за NAT — невидимы для входящих соединений. Без NAT traversal персональный интернет = серверный клуб.
Три механизма, каждый следующий — если предыдущий не сработал:
**1. AutoNAT (определение).** Узел спрашивает outbound peers: «видишь ли мой IP:port напрямую?» Если да — NAT нет. Если нет — узел знает свой NAT-статус.
**2. DCUtR (пробивка).** Два NAT-узла координируются через третий узел с публичным IP. Оба отправляют исходящие пакеты — роутеры открывают «дырки» для ответов. После координации — прямое соединение. Успех: 60-70% случаев (TCP). Carrier-grade NAT (мобильные операторы): ~30%.
**3. Circuit Relay v2 (транзит).** Если пробивка не удалась — трафик идёт через outbound peer с публичным IP. Relay — не отдельный механизм и не выделенный сервер. Relay-соединение = обычное исходящее соединение, подчиняющееся тем же правилам: uniform framing, diversity constraints, ротация по поведению. Содержимое зашифровано конец-в-конец (Noise) — relay видит IP участников но не содержимое. Metadata распределён по 13 outbound peers из разных /16 и ASN — ни один relay не видит полный граф.
Relay — не fallback а гарантия подключения при любом типе NAT. Пробивка — оптимизация для снижения нагрузки на relay.
**Лимиты relay:** до 32 одновременных relay-соединений на узел, bandwidth per relay ≤ baseline frame rate (1 KB/сек). 32 × 1 KB/сек = 32 KB/сек ≈ 82 GB/мес — приемлемо для домашнего узла с публичным IP.
**Обязанность.** Узлы с публичным IP поддерживают relay — персональная сеть работает когда каждый может войти. Reference implementation включает relay при обнаружении публичного IP. Feeler-подключения проверяют поддержку relay у peers; узлы без relay помечаются `no-relay` в адресном менеджере. NAT-узлы предпочитают peers поддерживающие relay при выборе исходящих.
Все три механизма — стандарт libp2p (AutoNAT, DCUtR, Circuit Relay v2). Ноль новых протокольных примитивов.
### Пять слоёв — одна конструкция
```
Слой 1: Transport Obfuscation персональный сервер скрывает содержимое и тайминг
Слой 2: Peer Selection start_window + network diversity constraints
Слой 3: NAT Traversal каждый может войти, даже за NAT
Слой 4: Censorship-Resistant Discovery четыре канала, достаточно одного
Слой 5: Dandelion++ пиры не знают кто автор операции
```
Каждый слой закрывает свой вектор. Ни один не требует внешней инфраструктуры. Всё построено поверх libp2p и существующего gossip. Сетевой уровень ортогонален консенсусу — ни один state transition не затронут.
### Protocol Message Layer
Внутри IBT uniform frames протокольные сообщения следуют общему envelope format. Эта секция нормативно определяет wire format всех Montana-сообщений для cross-implementation совместимости.
**Envelope format.**
```
ProtocolMessage:
msg_type 1B <- u8, код типа сообщения
msg_version 1B <- u8, версия формата сообщения (= 1 для v28.x)
request_id 8B <- u64 little-endian, correlation id для request/response (= 0 для one-way gossip)
payload_length 4B <- u32 little-endian, размер payload в байтах
payload ?B <- payload_length байт, формат определяется msg_type
```
Envelope всегда 14 байт header + payload. Поскольку IBT uniform frames имеют payload 1021B, ProtocolMessage может занимать один или несколько фреймов через flag `0x04 continuation` (см. Uniform Framing).
**Реестр типов сообщений.**
| Код | Тип | Направление | Payload |
|-----|-----|-------------|---------|
| 0x01 | Transfer | one-way gossip | Transfer объект (serialize по canonical encoding) |
| 0x02 | OpenAccount | one-way gossip | OpenAccount объект |
| 0x03 | ChangeKey | one-way gossip | ChangeKey объект |
| 0x04 | Anchor | one-way gossip | Anchor объект |
| 0x10 | NodeRegistration | one-way gossip | NodeRegistration объект |
| 0x20 | BundledConfirmation | one-way gossip | BundledConfirmation объект |
| 0x21 | VDF_Reveal | one-way gossip | VDF_Reveal объект |
| 0x22 | Proposal | one-way gossip | Proposal объект |
| 0x40 | FastSyncRequest | request | `{anchor_window: u64, resume_offset: u64}` |
| 0x41 | FastSyncResponse | response | chunked snapshot data (см. ниже) |
| 0x42 | FastSyncError | response | `{code: u8, message: bytes[≤255]}` |
| 0x50 | PeerListRequest | request | `{max_count: u16}` |
| 0x51 | PeerListResponse | response | `{count: u16, peers: count × PeerEntry}` |
| 0xF0 | Ping | request | `{timestamp: u64}` (локальный wall-clock отправителя, advisory) |
| 0xF1 | Pong | response | `{timestamp: u64}` (эхо timestamp из Ping) |
| 0xFF | Bye | one-way | `{reason: u8}` (graceful shutdown) |
Message versioning: `msg_version = 1` для всех v28.x. Изменение wire format = increment msg_version, требует protocol version upgrade.
Unknown msg_type → получатель логирует и игнорирует (forward compatibility). Unknown msg_version → получатель отвечает FastSyncError с кодом `unsupported_version` и разрывает соединение.
**Structured payloads.**
`PeerEntry`:
```
PeerEntry:
ip_version 1B <- u8, 0x04 или 0x06
ip 16B <- IPv4 в последних 4 байтах (первые 12 = 0x00) или IPv6 полностью
port 2B <- u16 little-endian
node_id 32B
start_window 8B <- u64 little-endian, из Node Table
= 59 bytes fixed
```
`FastSyncResponse` chunked delivery:
```
FastSyncResponse chunk:
chunk_index 4B <- u32 little-endian, начинается с 0
total_chunks 4B <- u32 little-endian, общее число chunks для текущего запроса
table_id 1B <- u8: 0x01 Account, 0x02 Node, 0x03 Candidate, 0x04 Proposals
record_count 4B <- u32 little-endian, записей в этом chunk
records ? <- record_count × serialize(record) по canonical encoding
```
Response состоит из N chunks (с одним request_id). Получатель собирает по chunk_index. После получения всех total_chunks — reconstructs Merkle root и проверяет против proposal_W.
**Connection lifecycle.**
Порядок установки соединения:
```
1. TCP SYN / SYN-ACK / ACK (standard)
2. TLS 1.3 handshake (server certificate optional)
3. Noise key agreement внутри TLS (mutual pubkey authentication)
4. IBT proof exchange (клиент отправляет FN-DSA-512 signature)
5. Access level determination (node / candidate / account, см. Transport Obfuscation)
6. Готово к обмену ProtocolMessages
```
Timeouts установки:
- TCP connect: 30 секунд
- TLS handshake: 10 секунд
- Noise + IBT: 10 секунд
- Всё вместе не более 60 секунд до готовности
Если любой шаг превысил timeout → разрыв, retry с другим пиром.
**Keepalive.**
- Ping каждые 60 секунд на idle соединении (нет данных)
- Pong должен прийти в пределах 30 секунд
- Три подряд пропущенных Pong → disconnect
- При активном обмене данными Ping не обязателен (реальные данные = evidence активности)
Timestamp в Ping/Pong — advisory только, не входит в consensus. Используется для оценки RTT локально.
**Graceful shutdown.**
Инициатор: отправляет Bye с reason code:
```
0x00 — normal shutdown
0x01 — going offline for maintenance
0x02 — peer list refresh (попытка найти лучших пиров)
0x03 — resource limits (слишком много соединений)
0x04 — protocol violation (валидация failed много раз)
0x05 — version mismatch
```
Получатель acknowledges через свой Bye, затем TLS close_notify, затем TCP FIN. Максимум 5 секунд на graceful shutdown, иначе forced close.
**Peer discovery algorithm.**
Новый узел при старте:
```
1. Извлечь bootstrap peers из Genesis Decree (захардкожено)
2. Выбрать 1-3 random bootstrap peer, connect (с PoW для bootstrap per Transport Obfuscation)
3. Выполнить IBT (account keypair для первого подключения нового узла)
4. Отправить PeerListRequest с max_count = 128
5. Получить PeerListResponse с до 128 известных peer-ов
6. Применить diversity constraints (/16, ASN, start_window) к полученному списку
7. Выбрать 13 outbound candidates по diversity
8. Параллельно connect к выбранным
9. После успешного IBT с реальным peer — disconnect от bootstrap (освобождая bootstrap slots)
10. Maintaining: PeerListRequest каждые ~τ₂ окон для обновления таблицы "проверенных" peers
```
Bootstrap exceptional:
- PoW при подключении (target ~100ms CPU per Transport Obfuscation)
- Ограничение: не более 3 одновременных bootstrap подключений на узел
- Освобождается после 13 реальных peers connected
**Peer exchange**
Между двумя подключёнными узлами:
```
Каждые τ₂_windows (~9 дней wall-clock):
A → B: PeerListRequest {max_count: 64}
B → A: PeerListResponse {peers[]}
```
Узел поддерживает две таблицы:
- **Новые** peers: недавно узнанные (от bootstrap или PeerListResponse), ещё не использованные
- **Проверенные** peers: те с которыми были успешные соединения в прошлом
При выборе outbound: 50/50 случайно из обеих таблиц. Bucket по секретному ключу узла предотвращает external enumeration.
**Retry policy.**
- Failed connect: exponential backoff (1s, 2s, 4s, 8s, ..., max 300s)
- Peer rejected через IBT fail: peer помечается bad на 1 час
- Peer disconnected с reason 0x04 (protocol violation): peer blacklisted на 24 часа
- Bootstrap PoW retry: no backoff (PoW сам служит rate limit)
**Error codes для FastSyncError:**
```
0x01 snapshot_unavailable -- запрошенный anchor_window слишком старый (peer не хранит)
0x02 snapshot_too_large -- snapshot больше чем peer готов отправить
0x03 unsupported_version -- msg_version не поддерживается
0x04 resource_exhausted -- peer перегружен
0x05 access_denied -- peer не отдаёт Fast Sync клиентам (только nodes)
```
**Сеть vs консенсус — граница.**
Network layer параметры (timeouts, retry delays, keepalive intervals) — implementation guidance, могут варьироваться между реализациями без consensus impact. Значения в этой секции — рекомендуемые defaults. Consensus-critical: wire format (envelope, payloads), IBT proof format, Bootstrap PoW formula, message type codes. Изменение consensus-critical параметров требует protocol version upgrade.
---
## Эволюция протокола
Изменения правил протокола существуют вне consensus state. Эволюция: открытые предложения, независимые реализации, добровольный выбор операторов узлов, fork resolution через большинство chain_length.
### Принцип
Consensus state Montana содержит только то что необходимо для финансового слоя и хронометража: TimeChain, NodeChain, AccountChain, Account Table, Node Table. Никаких полей governance, никаких советов в state, никаких голосований в реестре операций. Любая попытка ввести on-chain governance вводит subjective компоненты в consensus state и создаёт постоянную атакуемую поверхность — это нарушение глобального инварианта I-3.
Эволюция протокола существует **вне** consensus state, как социальный и инженерный процесс над Anchor-публикациями и репозиториями реализаций.
### Жизненный цикл изменения
```
1. PROPOSAL
Любой участник публикует MIP (Montana Improvement Proposal)
как Anchor с текстом на узле автора:
app_id = SHA-256("mt-app" || "mips")
data_hash = H(текст MIP)
anchor = операция Anchor в AccountChain автора
Авторство и timestamp доказуемы через подпись Anchor и
timechain_value cemented окна. История эволюции навсегда
через Anchor в TimeChain.
2. DISCUSSION
Открытое обсуждение в публичных каналах
(форумы, репозитории, advisory councils — см. ниже).
Никаких формальных голосований внутри протокола.
3. IMPLEMENTATION
Реализации (Rust core и альтернативные клиенты) выпускают
новые версии узлового ПО с реализованным изменением.
Каждая версия закрепляется за конкретным protocol_version
(u32 в Proposal header).
4. ADOPTION
Операторы узлов самостоятельно выбирают какую версию
запускать. Никакого on-chain голосования, никакого формального
activation window. Узлы публикуют proposals со своим protocol_version.
5. FORK RESOLUTION
При расхождении правил сеть может разделиться на цепочки.
Каждый узел следует той цепочке которая длиннее по его
собственным правилам валидации (chain_length majority).
Меньшинство либо обновляется до правил большинства, либо
продолжает работать как независимая цепочка (hard fork).
```
### Поле protocol_version
Поле `protocol_version` (u32) в Proposal header — единственный сигнал эволюции внутри консенсуса. Узел публикует proposals с тем `protocol_version` который реализован его версией ПО. Инвариант `protocol_version >= prev_proposal.protocol_version` запрещает откат к более старым правилам внутри одной цепочки.
`protocol_version` не голосуется и не активируется через governance. Он отражает фактическое состояние реализации узла — что узел реально умеет валидировать. Расхождение `protocol_version` между honest узлами разрешается естественно через fork choice по chain_length.
### Advisory councils
Группы экспертов могут существовать как **advisory** структуры — публикующие рекомендации, обзоры, анализ безопасности через Anchor. Их подписи не имеют binding эффекта на consensus, их составы не хранятся в state, их голоса не считаются в state transitions.
Примеры advisory структур (опциональны, не часть протокола):
- **AI Council** — модели разных компаний публикуют технические обзоры MIPs
- **Core Council** — публичные эксперты публикуют анализ безопасности и социальную координацию
Захват advisory совета не даёт контроля над протоколом — он даёт только возможность опубликовать рекомендацию, которую операторы узлов могут проигнорировать. Это устраняет attack surface governance: нет binding голосования = нет цели для компрометации.
Advisory councils организуются вне протокола (репозитории, форумы, Anchor-публикации). Протокол не знает об их существовании и не выделяет им никаких прав.
### Параметрическая адаптация
Параметры `D` и `m` адаптируются автоматически на границе τ₂ через participation-ratio feedback (см. раздел «Адаптация D через participation-ratio feedback»). Это **не** governance. Адаптация детерминирована, опирается только на canonical chain observations (cemented sets, Node Table), не требует голосования, не требует социальной координации, не зависит от измерений физического мира. Формула адаптации и её параметры зафиксированы в Genesis Decree; правка самой формулы требует MIP + новой версии ПО + adoption через chain_length, как и любое другое изменение протокола.
Закрытие окна определяется quorum event в канонических cemented sets. Механизм полностью event-driven и опирается только на canonical state.
---
## Обоснование протокольных констант
Числовая система Montana построена на оси 13 (Змееносец — число вне 12-ричной системы) и последовательности 3-6-9 (числа вне бинарного цикла 1-2-4-8-7-5). Каждая константа имеет символическое и инженерное обоснование.
### Числовая ось 13
| Константа | Значение | Обоснование |
|-----------|----------|-------------|
| D₀ (TimeChain VDF за окно) | 13 × 10⁹ | 13 = Змееносец. Масштаб 10⁹ обеспечивает VDF >> network RTT на commodity CPU (~100 MH/s → окно физически достаточно для gossip propagation) |
| (m₀ — removed) | — | NodeChain VDF удалён в v28.0.0. Присутствие доказывается через BundledConfirmation, endpoint через SHA-256(T_r \|\| node_id \|\| window_index) |
| τ₂ (epoch boundary) | 13 000 окон | 13 × 10³. Одна эпоха = одна единица VDF entry. Все процессы длительного действия (pruning, adaptation, snapshot) привязаны к τ₂ |
| VDF entry (стоимость входа) | 13 000 окон | = τ₂. Кандидат работает ровно одну эпоху до входа. Совпадение τ₂ и VDF entry — feature: единица адаптации = единица входа |
| timecoin_per_window | 13 Ɉ | 13 на третьем масштабе: D = 13×10⁹, VDF entry = 13×10³, emission = 13×10⁰ |
### Последовательность 3-6-9
| Константа | Значение | Обоснование |
|-----------|----------|-------------|
| selection_interval | 369 окон | 3-6-9 как единое число. Цифровой корень = 9. ~35 selection events за τ₂ — плавный вход новых узлов |
| stem_epoch (Dandelion++) | 693 окна | Цифровой корень = 9. ~1.9 selection intervals — ротация стебельных peers чаще, чем selection events, для diversity |
| Ядра на узел | минимум 1 | TimeChain VDF sequential — не ускоряется параллелизмом. 1 ядро достаточно, validation interleaved с VDF. 2+ ядра устраняют interleaving overhead (~5-10%), дают bounded преимущество в participation_ratio |
### Инженерные константы
| Константа | Значение | Обоснование |
|-----------|----------|-------------|
| confirmation_quorum | 67% | Стандартный BFT 2/3+1. 40 лет distributed systems research (PBFT, Tendermint, HotStuff) |
| confirmation_threshold | active_chain_length / 130 | 130 = 13 × 10. Числовая ось. ~130 confirmers при large-scale сети |
| outbound connections | 13 | Числовая ось. Больше diversity и relay capacity, чем при 8. Eclipse требует 7+ разных AS/subnet/τ₂ |
| equivocation timeout | 13 окон | Числовая ось. Окно разрешения equivocation до отклонения обеих операций |
| active predicate | 2τ₂ (26 000 окон) | Узел мог пропустить одну полную эпоху (downtime, перезагрузка) и вернуться. 1τ₂ — слишком агрессивно. 3τ₂ — мёртвые узлы висят слишком долго |
| node pruning | 8τ₂ (104 000 окон) | 4× active threshold. Узел неактивен 8 эпох = гарантированно мёртв. Запас позволяет длительный offline без потери записи |
| pruning_idle (accounts) | 4τ₂ (52 000 окон) | = граница первого бакета аккаунтов (4^1 × τ₂). Consistency с бакетной системой |
| candidate_expiry | 3τ₂ (39 000 окон) | ~105 selection events. Достаточно для fair chance при конкуренции. 2τ₂ = 70 events (вероятность пропуска выше). 4τ₂ = раздувание Candidate Pool |
| account бакеты | 4^N × τ₂ | Экспоненциальная шкала. Sybil-атакующий изолирован в бакете 0. Органический рост через 4× расширения на каждом уровне |
| slots per selection | max(1, active/130) | ~0.77% от сети за event. 130 = 13 × 10. Consistency с confirmation threshold (active_chain_length / 130) |
| D adjustment rate | ±3% за τ₂ | Медленная реакция: 24 эпохи для удвоения D. Быстрее — withholding атака дешевеет. Медленнее — инертность при hardware shift |
| dead zone | 0.85 — 0.95 | Ниже 0.85 = 15%+ узлов под давлением. Выше 0.95 = комфорт. Между = нормальные флуктуации, D стабилен |
| target₀ | calibrated at genesis | Калибруется на ~13 VDF_Reveal кандидатов за окно. При genesis (1 узел): target₀ устанавливается так, чтобы единственный узел гарантированно проходил threshold (weighted_ticket < target для chain_length = 1). Уточняется при testnet |
| chain_length_snapshot | скользящее окно (78 000 окон) | Количество cemented BundledConfirmation за последние ₂. Основа lottery_weight. Новый узел через выходит на равный snapshot со старожилом. Цифровой корень 6 Tesla |
| seniority_bonus | min(chain_length / 69, snapshot) | Bounded добавка за longevity. Делитель 69 (цифровой корень 6, Tesla). Cap = snapshot: максимальное преимущество старожила 2x. Через ~5 лет bonus достигает cap. Далее стабильный потолок |
| lottery_weight | snapshot + seniority_bonus | Разделение: lottery_weight для эмиссии (недавняя работа + bounded longevity), абсолютный chain_length для quorum (безопасность). Temporal Aristocracy ограничена cap-ом. Новые участники мотивированы на любой стадии сети |
| adaptive_vdf_threshold | 1% (pending/active) | Совпадает с selection rate (~0.77% за event). Ниже порога базовая длина VDF. Выше давление аномальное, защита активируется |
| adaptive_vdf_multiplier | ×100 | required_vdf = base_vdf × pressure × multiplier. Вычисляется на момент W_p (cementing NodeRegistration). Self-correcting: стоимость растёт пропорционально pressure |
| base_vdf_length | 13000 окон | Минимальная длина VDF цепочки per candidate (= Genesis параметр vdf_entry_windows) |
| max_vdf_horizon | 2 × τ_windows | Верхняя граница `W_p - W_start` ограничивает историческое pre-computation окно |
---
## Архитектура
```
ТЕЛЕФОН / ДЕСКТОП УЗЕЛ (десктоп / сервер, 24/7)
┌────────────────────────┐ ┌──────────────────────────────────────┐
│ Кошелёк │ │ │
│ FN-DSA-512 keypair │ │ TimeChain │
│ локальная UX-история │ │ T_r = SHA-256^D(T_{r-1}) │
│ операций │ │ каноническая последовательность, │
│ │ │ источник случайности │
│ AccountChain │ │ │ │
│ (счётчик окон │ │ ▼ │
│ активности) │ │ NodeChain (per node) │
│ │ │ chain_length = cemented │
└──────────┬─────────────┘ │ BundledConfirmation count │
│ операции │ доказательство присутствия │
│ (type|prev_hash| │ lottery endpoint = SHA-256(T_r || │
│ payload|FN-DSA-512) │ │ │
└──────────────────────▶│ ▼ │
confirmations │ AccountTable │
◀──────────────────-│ balance: u64 (открыт) │
│ pubkey, frontier_hash │
│ account_chain_length │
│ │ │
│ ▼ │
│ Proposals (навсегда) │
│ control_root, node_root, │
│ account_root, timechain_value │
└──────────────────────────────────────┘
Зависимости: TimeChain → NodeChain → AccountTable
Отказ AccountTable не останавливает продвижение TimeChain.
Отказ узла не заражает каноническую последовательность.
```