221 KiB
Montana — Спецификация протокола
Версия: 26.13.13 (2026-04-12)
Определение
Montana — цифровой стандарт времени как реляционная структура. Сеть независимых VDF-осцилляторов, конституирующих каноническую последовательность событий через последовательное хэширование и консенсус между узлами. Montana производит каноническую структуру отношений между хэш-событиями, индексированных window_index. Каждое канонически зарегистрированное окно = 13 Ɉ (TimeCoin).
Montana Time — реляционная структура, конституируемая последовательным хэшированием в рамках VDF и канонической упорядоченностью, устанавливаемой консенсусом между узлами. Внутри этой структуры время в протоколе существует как последовательность канонических событий.
Montana — самодостаточная система отсчёта: каноническая последовательность событий, которую внешние системы могут наблюдать и использовать как reference frame для своих нужд.
Основная функция — каноническая временная координата (window_index). Вторичная — передача ценности.
Консенсус: Proof of Time (PoT) — четыре цепочки. TimeChain: глобальная каноническая цепь (D последовательных SHA-256 = одно окно). NodeChain: персональная цепочка узла (доказательство присутствия в каждом окне). AccountChain: счётчик окон активности аккаунта. AccountTable: состояние счёта. Влияние узла = длина его NodeChain. Протокол и есть структура отношений между событиями, оцифрованная и криптографически верифицируемая.
Genesis: symbolic window 0. Перевод window_index в любые внешние time scales является задачей клиентского слоя.
Генезис-фраза: «Кто контролирует прошлое, контролирует будущее. Кто контролирует настоящее, контролирует прошлое.» — Оруэлл, 1984
Эволюция протокола: открытые предложения (MIPs — Montana Improvement Proposals) публикуются в Content Layer как рекомендации, реализации выпускают новые версии, операторы узлов выбирают какую версию запускать. 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 консенсуса
4. Эволюция без on-chain governance
Проблема. On-chain governance — голосования, советы, формальные процедуры изменения правил внутри протокола — вводит subjective компоненты в consensus state и создаёт постоянную атакуемую поверхность. Любая структура которая голосует становится мишенью: компрометация моделей, подкуп участников, юрисдикционное давление. Чем формальнее governance, тем чётче определена цель атаки.
Решение. Эволюция через открытые предложения. Изменения протокола публикуются как MIPs (Montana Improvement Proposals) в Content Layer. Реализации (узловое ПО) выпускают новые версии с реализованными MIPs. Операторы узлов сами выбирают какую версию запускать. Fork resolution полностью детерминирован: при расхождении правил сеть разделяется на цепочки, каждая со своим chain_length, и узлы следуют за той цепочкой которая длиннее по их собственным правилам валидации.
Свойства.
- Consensus state не содержит ни одного subjective поля связанного с governance — нет attack surface
- Последнее слово реально у узлов: оператор каждого узла самостоятельно решает что запускать, без посредников
- MIPs существуют как тексты в Content Layer (anchor + контент), любой может опубликовать, любой может верифицировать авторство и timestamp
- Экспертные советы (AI Council, Core Council) допустимы как advisory — публикуют рекомендации, обзоры, анализ безопасности; их подписи не имеют binding эффекта на consensus
- Захват advisory совета не даёт контроля над протоколом — он даёт только возможность опубликовать рекомендацию, которую узлы могут проигнорировать
- История эволюции навсегда в Content Layer через anchor — каждый MIP и каждое обсуждение timestamped в TimeChain
Следствие: цифровой reference frame времени без человека-посредника
Четыре решённые проблемы порождают уникальную возможность. Любой документ, событие, состояние может быть записано в Montana с математически доказуемой привязкой к канонической позиции в последовательности событий (window_index). Anchor — 32 байта, навсегда. Ни одна существующая система не предоставляет координату времени, которая одновременно децентрализована, неплутократична, привязана к детерминированной денежной политике, свободна от on-chain governance и не зависит от внешних физических эталонов. 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] Открытость финансового слоя. Балансы, суммы переводов, отправители, получатели — публичны. Приватность данных приложения — через Anchor (хэш в сети, контент у владельца зашифрованным).
[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 → 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] Минимальная криптографическая поверхность. Каждый новый примитив требует обоснования закрытием конкретного механизма. Дублирование функциональности через два разных примитива запрещено.
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, NodeChain), адреса, 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, NodeChain endpoints, подписи
Псевдонимность на уровне account_id. Финансовая приватность — задача приложений: микшеры, payment channels, off-chain settlements.
Двойная трата
Каждый аккаунт имеет одну цепочку. Две операции с одним prev_hash = equivocation.
Без конфликта: операция → узлы валидируют → публикуют confirmation → quorum → cemented (необратимо, ~0.3 сек). Баланс обновляется при settle (apply at window close).
При конфликте (equivocation):
- Узел получает операцию X с prev_hash = H. Узел уже видел операцию Y с prev_hash = H, Y ≠ X. Форк обнаружен. Обе операции помечаются как equivocated.
- Если одна операция уже cemented (quorum до обнаружения конфликта) — cemented необратимо. Вторая отклоняется.
- Если ни одна не cemented — узлы продолжают собирать confirmations для обеих. Если одна набирает quorum → cemented, вторая отклоняется.
- Если через 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, окно регистрации (первое окно NodeChain)
chain_length 8B <- u64, число окон с cemented BundledConfirmation узла; инкрементируется в apply at window close
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 после 13 000 окон VDF
W_start 8B <- u64, окно начала VDF (заявлено кандидатом)
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 хэшей.
Структура 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 — глобальные часы (ход времени). NodeChain — машинное присутствие узла (непрерывное VDF). 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 в каждом окне. Якорится в TimeChain каждое окно:
S_{i,s,0} = SHA-256(S_{i,s-1,m} || T_s || node_id_i)
S_{i,s,j+1} = SHA-256(S_{i,s,j}) для j = 0..m-1
Три компонента seed: предыдущий endpoint (непрерывность цепочки), значение TimeChain (протокольное время), node_id (идентичность). m последовательных хэшей за окно — одно звено NodeChain.
Инициализация: для первого окна нового узла предыдущий endpoint отсутствует. NodeChain init привязан к каноническим данным proposal окна selection event, в котором кандидат выбран:
S_{i,0,0} = SHA-256("mt-nodechain-init" || state_root || timechain_value || node_id_i)
Где state_root и timechain_value из proposal окна selection event. Канонические, верифицируемые.
state_root и timechain_value из proposal header окна selection event. Оба канонические. Предвычисление VDF невозможно — timechain_value неизвестен до закрытия окна. Grinding surface = ноль. Верифицируем любым узлом.
NodeChain зависит от TimeChain. TimeChain не зависит от NodeChain.
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:
| Свойство | NodeChain | AccountChain |
|---|---|---|
| Источник | node_pubkey | account_pubkey |
| Идентификатор | node_id | account_id |
| Тип присутствия | машинное | человеческое |
| Ритм | непрерывный (каждое окно VDF) | дискретный (окно с операцией) |
| Длина | chain_length (окна) | account_chain_length (окна) |
| Единица длины | окно τ₁ | окно τ₁ |
| Накопление | автоматически с каждым окном | через активность пользователя |
| Якорь во времени | timechain_value каждое окно | timechain_value окна с операцией |
| Защита от подделки | VDF необратим | подпись FN-DSA-512 |
| Linking | endpoint предыдущего звена | prev_hash предыдущей операции |
| Защита от Sybil | 13 000 окон VDF + selection event + adaptive VDF | накопление окон требует работы в каждом окне |
Узел доказывает присутствие непрерывной работой машины в каждом окне. Аккаунт доказывает присутствие активным использованием сети — каждая операция фиксирует одно окно человеческого бытия на временной шкале Montana. Оба механизма математически верифицируемы, оба производят запись на одной шкале времени.
AccountChain зависит от TimeChain напрямую — каждая операция привязана к timechain_value момента финализации. 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 за последние 6τ₂ (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.
Разделение весов:
- Лотерея (эмиссия):
lottery_weight = chain_length_snapshot + seniority_bonus. Недавняя работа (snapshot) доминирует, longevity даёт bounded bonus. - Quorum (безопасность): абсолютный
chain_length. Старожилы доминируют в финализации.
Если weighted_ticket_node < target — узел кандидат и публикует VDF_Reveal:
VDF_Reveal:
node_id 32B
window_index 4B <- индекс τ₁
endpoint 32B <- S_{i,s,m}
start_window 4B <- окно начала NodeChain (для верификации)
signature 666B <- FN-DSA-512, подписано node_pubkey
Итого: ~738B
Любой активный узел может стать кандидатом лотереи — lottery_weight основан на недавней работе (snapshot 6τ₂), старожилы получают 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)
)
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: dependency rule ограничивает аккаунт одной операцией за окно — один лотерейный билет. timechain_value(W) известен только после закрытия окна — endpoint непредсказуем заранее.
Определение winner-а (Lookback Leadership)
Winner окна W-1 определяется при cementing proposal окна W. Proposer окна W = winner окна W-2 (канонически известен из cemented state).
Механика:
- Окно W-1 завершается: confirmers публикуют BundledConfirmation_{W-1} (операции окна W-1 + VDF_Reveals окна W-2), кандидаты публикуют VDF_Reveal_{W-1}, аккаунты публикуют операции.
proposer_W = winner_{W-2}(канонически определён из proposal_{W-1}).- Окно W начинается. Confirmers получают VDF_Reveals_{W-1} через P2P и включают их в BundledConfirmation_W наряду с операциями окна W. VDF_Reveal идентифицируется по
window_index = W-1. - VDF_Reveal_{W-1} cemented когда confirmers с суммарным chain_length ≥ 67% active_chain_length включили его в свои BundledConfirmation_W. Cement status каноничен — каждый узел отслеживает его независимо по P2P bundles.
- 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) - Из included_reveals_{W-1} извлекаются все node endpoints. Из included_bundles_{W-1} извлекаются все cemented account operations.
winner_{W-1} = argmin(weighted_ticket)среди всех кандидатов (cemented VDF_Reveal nodes + аккаунты).- 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
- Сеть валидирует 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 корректен? (независимый пересчёт)
- Если 67% active_chain_length подписывают proposal_W → proposal cemented. Winner_{W-1} получает 13 Ɉ. Winner_{W-1} становится proposer_{W+1}.
- Если < 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
- Подпись FN-DSA-512 соответствует node_pubkey из Node Table
- window_index = текущий τ₁
- node_id существует в Node Table
- weighted_ticket < target
- endpoint верифицируем: пересчёт NodeChain VDF от предыдущего endpoint
Валидация участия аккаунта
- account_id существует в Account Table
- account_chain_length_snapshot > 0
- Аккаунт имеет cemented операцию в окне W
- operation_for_lottery определена детерминированно (dependency rule: одна операция за окно)
- 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" |
| 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) + независимая верифицируемость состояния.
- Proposer (proposer_node_id) публикует подписанный proposal header + control_set
- Каждый узел проверяет
window_index == prev_proposal.window_index + 1,protocol_version >= prev_proposal.protocol_versionиprotocol_version <= local_max_supported_version - Каждый узел независимо вычисляет ожидаемый control_set по формуле и сравнивает с proposer's
- Каждый узел применяет control_set + TimeCoin детерминированно в порядке (cemented_window asc, op_hash lex asc)
- Каждый узел сравнивает вычисленный state_root с заявленным в proposal
- Совпадает — proposal принят
- Не совпадает — 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 (NodeChain или 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 детерминирован — каждый узел вычисляет его независимо. NodeChain для окна N+1 стартует сразу после закрытия окна N, используя собственный endpoint текущего окна и новое значение TimeChain. 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 <- текущий NodeChain endpoint (доказывает chain_length)
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 верифицируем: пересчёт m хэшей от предыдущего известного endpoint данного узла. node.chain_length хранится в Node Table и инкрементируется в apply_proposal шаг 3.5 для каждого узла с cemented BundledConfirmation в окне W. Endpoint BundledConfirmation верифицирует вычисление VDF за соответствующее окно.
Объект финализирован (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} сетью.
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_start >= W_p - 2 × τ₂_windows,
вычислить effective_vdf_length(W_start) из canonical state на окно W_start
(candidate_pressure = pending_candidates / active_nodes,
if > 0.01: 13000 × pressure × 100, else: 13000),
верифицировать proof_endpoint: пересчёт VDF от
SHA-256("mt-candidate-vdf-init" || timechain_value(W_start) || node_id)
через effective_vdf_length(W_start) окон,
создать запись в Candidate Pool:
node_id, node_pubkey, suite_id, operator_account_id,
proof_endpoint, W_start,
registration_window = W_p,
expires = W_p + 3 × τ₂_windows.
Шаг 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(timechain_value(current_window) || 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.
Шаг 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. Обратных зависимостей нет.
С ростом TPS сети дополнительные ядра подключаются для верификации операций. Минимум для валидатора: 3 логических ядра (TimeChain + NodeChain + Account). Один узел = 3 ядра. 50 ядер = 16 узлов. Верификация операций аккаунтов полностью параллелизуется — цепочки аккаунтов независимы.
Вход и регистрация
Два уровня входа в сеть. Узлы участвуют в консенсусе — открытый вход через 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 = ∅
Genesis NodeChain init для bootstrap-узла:
nodechain_init_0 = SHA-256("mt-nodechain-genesis" || node_id_0)
Первое звено NodeChain в окне 0: S_{0,0,0} = nodechain_init_0.
Дальнейшие звенья по формуле S_{0,s+1,0} = SHA-256(S_{0,s,m} || T_{s+1} || node_id_0).
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)
m₀ (8B) начальное значение m NodeChain VDF (1 000 000 000 = D₀/13)
τ₂_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 — закреплён в коде каждой реализации.
Первое окно τ₁ после генезиса — 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 первых двух окон нет.
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, считает NodeChain VDF 13 000 окон от init:
candidate_vdf_init = SHA-256("mt-candidate-vdf-init" || timechain_value(W_start) || 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 после 13 000 окон VDF
W_start 8B <- окно начала VDF
signature 666B
Итого: ~1 638 B
NodeRegistration — ControlObject. При cementing → запись в Candidate Pool. Кандидат ожидает selection event.
Валидация NodeRegistration:
- Подпись FN-DSA-512 валидна для node_pubkey
- node_id = SHA-256("mt-node" || node_pubkey) уникален (нет в Node Table и Candidate Pool)
- operator_account_id существует в Account Table и
is_node_operator == 0 - W_start >= current_window - 2 × τ₂_windows
- proof_endpoint верифицируем: пересчёт VDF от
SHA-256("mt-candidate-vdf-init" || timechain_value(W_start) || node_id)черезeffective_vdf_length(W_start)окон (adaptive VDF)
Верификация: effective_vdf_length(W_start) сегментов VDF проверяются параллельно. На C ядрах: ~(effective_vdf_length/C) × t_segment.
Шаг 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
NodeChain init: SHA-256("mt-nodechain-init" || state_root || timechain_value || node_id) из proposal окна selection event. Оператор-аккаунт получает is_node_operator = 1. Запись удаляется из Candidate Pool.
Expiry. Кандидатура истекает через 3τ₂ (39 000 окон). Запись удаляется из Candidate Pool автоматически.
Sybil-защита (четыре уровня):
-
VDF-барьер: 13 000 окон последовательного хэширования (при нормальной нагрузке), 3 ядра CPU. Физическая работа.
-
Adaptive VDF: стоимость кандидатуры пропорциональна давлению на сеть. Сеть видит давление мгновенно (pending_candidates в canonical state). Рост защиты — мгновенный, снятие — медленное (через expiry 3τ₂).
candidate_pressure = pending_candidates / active_nodes
if candidate_pressure > 0.01:
effective_vdf_length = 13000 × candidate_pressure × 100
else:
effective_vdf_length = 13000
| Ситуация | pending | active | pressure | effective_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 окон |
effective_vdf_length фиксируется для кандидата в момент W_start. Валидатор проверяет proof_endpoint через effective_vdf_length(W_start), вычисленный из canonical state на окно W_start. Изменение давления после W_start не влияет на уже считающихся кандидатов.
Self-correcting: чем сильнее давление → тем длиннее VDF → тем дороже каждый Sybil → давление падает. При снижении давления → pending уменьшается через expiry (3τ₂) → effective_vdf нормализуется → легитимный вход восстанавливается.
Timing window (known limitation). Атакующий может начать VDF при низком давлении (effective_vdf = 13 000) и подать NodeRegistration после завершения, когда давление уже высокое. Его proof валиден для effective_vdf(W_start). Первая волна кандидатов, начатых при низком давлении, проходит со стандартным VDF. Вторая волна (начатая при высоком давлении) заблокирована adaptive VDF. Impact ограничен: (a) selection rate limit пропускает max active/130 за event; (b) candidate_expiry = 3τ₂ ограничивает окно подачи; (c) weighted mechanisms (chain_length = 1 при входе) ограничивают влияние вошедших Sybil-узлов на quorum и лотерею.
-
Selection rate limit: max(1, active_nodes/130) за 369 окон. Массовый вход ограничен. Минимум 1 кандидат всегда проходит.
-
Weighted механизмы: chain_length определяет вес в quorum (безопасность). lottery_weight (snapshot 6τ₂ + 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, NodeChain, лотерея, 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 продвигается на
mхэшей с якорем в текущем T_s -
Операции аккаунтов подтверждаются непрерывно через confirmations (cement), применяются батчем в конце окна (settle)
-
control_set: все cemented ControlObjects из окон (previous_proposal.window, current_window] (каноничен)
-
Confirmers (~100) публикуют BundledConfirmation (операции текущего окна + VDF_Reveals предыдущего окна)
-
Кандидаты (~12) публикуют VDF_Reveal с NodeChain endpoint для лотереи; 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 и m через participation-ratio feedback (см. ниже)
- Snapshot account_chain_length: для каждого аккаунта
account_chain_length_snapshot = account_chain_length. Snapshot используется лотереей аккаунтов в течение следующего τ₂. Детерминированно для всех узлов - Pruning Account Table: удаление пустых аккаунтов без активности 4τ₂ (52 000 окон) с обновлением Merkle путей
- Pruning Node Table: для каждого узла N где
(current_window - N.last_confirmation_window) > 8 × τ₂_windows:- Если
N.operator_account_idсуществует в Account Table — установитьAccount Table[N.operator_account_id].is_node_operator = 0(operator-аккаунт освобождается, может участвовать в лотерее аккаунтов) - Удалить запись N из Node Table
- Пересчитать node_root
- Если
- Supply audit (sanity check): Σ balance(account) для всех аккаунтов = 13_000_000_000 × (window_index + 1) nɈ
- Криптографическая амнезия: подписанные 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 не трогаем)
m_new = m_old × (D_new / D_old)
Семантика.
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.
- Естественное следование 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⁹)
m₀ = 1 000 000 000 (10⁹, D₀ / 13 = NodeChain VDF за окно)
participation_dead_zone_low = 0.85
participation_dead_zone_high = 0.95
d_adjustment_rate = 0.03 (±3% за τ₂)
Параметры D₀ и m₀ фиксируются в 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 — персональная цепочка узла. VDF-цепочка конкретного node_id, якорится в TimeChain каждое окно. Доказывает непрерывную работу узла.
Account — состояние счёта. Операции финализируются непрерывно через подтверждения (67% active_chain_length). ControlObjects включаются в proposal (каноничен).
Зависимости односторонние: TimeChain → NodeChain → AccountChain → AccountTable. Отказ в AccountTable не останавливает часы. Отказ конкретного узла в NodeChain не заражает общий ритм.
Лотерея
Лотерея объединяет два класса участников: узлы (через NodeChain) и аккаунты (через 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 nɈ)
- Proposal формирует узел-кандидат с минимальным weighted_ticket в этом окне (proposer_node)
- Если в окне нет узлов-кандидатов — proposer выбирается из всех узлов с lowest weighted_ticket (fallback)
- Proposer не получает дополнительной награды — это его обязанность как ближайшего узла
Финальность proposal — подпись proposer_node_id на proposal header. Верификация — независимый пересчёт state_root.
Верификация
Proposer публикует: {proposer_node_id, NodeChain endpoint, proposal}.
Верификация NodeChain за одно окно: пересчёт m хэшей. Параллелизация по сегментам — время верификации обратно пропорционально числу ядер.
Верификация 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 окон) + 3 ядра на узел + 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 mɈ = 1 000 000 μɈ = 1 000 000 000 nɈ
Одно окно τ₁ регистрирует одну единицу Montana Time = 13 Ɉ. Число 13 — фиксированная константа протокола эмиссии за окно.
Точность: 9 знаков после запятой. Все расчёты эмиссии в nɈ (целочисленная арифметика, без плавающей точки).
Issuance schedule
Одно окно Montana Time порождает 13 Ɉ. С первого окна и навсегда.
| Параметр | Значение |
|---|---|
| Genesis | window_index = 0 |
| TIME_RECORD | 13 000 000 000 nɈ (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 применён — балансы в таблице обновлены, тело операции сети больше не нужно.
Три уровня участников
Узел (валидатор) — десктоп или сервер, 24/7, минимум 3 ядра (1 узел = 3 ядра, 50 ядер = 16 узлов):
Хранит:
Account Table (account_id, balance, frontier_hash, pubkey)
+ persistent sparse Merkle tree (account_root обновляется при settle)
Node Table (node_id, pubkey, start_window, chain_length)
+ persistent sparse Merkle tree (node_root обновляется при settle)
Candidate Pool (node_id, pubkey, operator, proof_endpoint, W_start, expires)
+ persistent sparse Merkle tree (candidate_root обновляется при settle)
Proposals (навсегда)
Blob Buffer (ephemeral) (TTL = τ₂, для кратковременных сообщений)
Persistent Blob Storage (TTL = 0, контент app_id на которые узел подписан)
Genesis Content (книга Montana, mandatory replication)
Persistent Blob Index ((app_id, data_hash) → blob, с флагом is_manifest)
Делает:
TimeChain VDF (1 ядро, 24/7)
NodeChain VDF (1 ядро, 24/7)
Валидация операций (1+ ядро)
P2P gossip (операции, confirmations, reveals, proposals)
Почтовый ящик (хранит сообщения для своего владельца пока тот офлайн)
Content replication (DHT provide, gossip announce, serving ContentRequest/ChunkRequest)
Chunk verification (SHA-256 + Merkle reconstruction для полученных чанков)
Кошелёк (клиент) — телефон, онлайн когда используется:
Хранит:
Свои ключи (seed → keypairs, включая encryption key для application layer)
Свои контакты (адресная книга: имя → mt-адрес, с локальным override)
Локальная история (своя цепочка операций для UX)
Сообщения (локальная история переписки, messenger session states)
Timestamp proofs (Anchor + BundledConfirmations + proposal headers, локально)
Подписки (app_id каналов, книги, профилей на которые подписан)
Реплики контента (persistent blobs подписанных app_id по желанию)
Делает:
Отправка/получение переводов
Мессенджер (P2P напрямую через libp2p)
Discovery (через application layer)
Запрос pubkey и proposals у узлов сети
Доверенный узел — узел друга, семьи, сообщества:
Делает:
Всё что узел + хранит Blob Buffer для привязанных аккаунтов
Владелец аккаунта привязывает свой 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 |
Потеря данных клиента
Потеря устройства: баланс в Account Table цел и публичен, seed восстанавливает ключи, доступ к аккаунту полностью восстанавливается. Локальная история переводов и сообщений утрачена — но баланс читается из Account Table напрямую. Если есть доверенный узел — зашифрованные сообщения можно восстановить.
Fast Sync (новый узел)
- Цепочка proposals от генезиса — проверка TimeChain-цепочки и подписей proposer-узлов (мегабайты)
- Snapshot трёх таблиц (Account Table + Node Table + Candidate Pool) от пиров на момент окна W (произвольное недавнее окно)
- 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. - 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
- Mandatory genesis content replication. Загрузить книгу Montana через Content Layer:
- ContentRequest(genesis_content_app_id, genesis_content_data_hash) → manifest
- Для каждого чанка: ChunkRequest + верификация SHA-256
- Пересчёт Merkle root манифеста → сравнение с genesis_content_data_hash
- Без успешной загрузки genesis content Fast Sync считается неполным
- Узел синхронизирован и готов к участию
Snapshot привязан к конкретному proposal (settled state после apply at window close). Catch-up дистанция определяется свежестью snapshot — обычно несколько окон.
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 объединяет два класса участников: узлы (NodeChain) и аккаунты (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
- каждая содержит endpoint NodeChain confirmer на момент окна W
- суммарный 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: пересчитать NodeChain endpoint от start_window до окна W,
подтвердить заявленный chain_length
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 язык. Не нужен газ. Не нужны комиссии.
Phone Discovery и Messenger
Phone discovery и messenger — полностью на Application Layer, не в protocol core. Protocol не знает о телефонах, именах, сообщениях. Application Layer реализует эти функции через Content Layer (см. ниже) и Interop Standards.
Протокол предоставляет:
- Identity: account_id через OpenAccount
- Timestamping: Anchor с произвольным data_hash
- Storage: Blob Buffer для хранения произвольных байт (persistent и ephemeral режимы)
- Transport: libp2p gossip и Content Request Protocol
Всё остальное — phone discovery, encryption, messaging protocols, profiles — реализуется на уровне приложения. Стандарты совместимости фиксированы в разделе Application Layer Interop Standards.
Content Layer
Content Layer предоставляет механизм хранения и репликации произвольных данных между узлами с привязкой к Anchor. Узлы подписанные на app_id хранят контент этого app_id. Новый узел или узел восстанавливающийся после offline скачивает недостающие блобы у пиров, верифицирует целостность через хэши из Anchor. Целевая задача — децентрализованное облако данных где каждый подписчик является хранителем, а факт существования контента зафиксирован навсегда через Anchor в proposal chain.
Persistent Blob
Blob Buffer получает второй режим хранения:
- TTL = τ₂ (ephemeral) — кратковременные сообщения, удаляются через τ₂
- TTL = 0 (persistent) — контент привязанный к Anchor, хранится бессрочно пока узел подписан на соответствующий app_id
Persistent blob индексируется парой (app_id, data_hash) → blob_bytes. Блоб может содержать manifest чанкованного контента (флаг is_manifest = true) или один чанк/целый файл. Размер одного blob ограничен chunk_size. Удаление persistent blob — решение оператора узла через явную отписку от app_id, не автоматически по таймеру.
Chunking Standard
Большие файлы разбиваются на чанки фиксированного размера:
chunk_size = 256 KB (фиксировано протоколом)
chunk formaт: chunk_index (4B, u32) || chunk_data (≤262 144 bytes)
chunk_hash = SHA-256("mt-content-chunk" || chunk_data)
Manifest содержит метаданные файла и упорядоченный список chunk_hashes:
Manifest {
version: u16 (currently 1)
file_name: string (UTF-8, length-prefixed, max 256 bytes)
file_size: u64
mime_type: string (UTF-8, length-prefixed, max 64 bytes)
chunk_count: u32
chunk_hashes: [32B × chunk_count]
}
Merkle tree строится поверх chunk_hashes. Корень дерева:
data_hash = SHA-256("mt-content-manifest" || canonical_serialization(Manifest))
Этот data_hash записывается в Anchor. Маленький файл (< chunk_size) — один чанк, manifest с chunk_count = 1.
Manifest сохраняется как первый persistent blob по (app_id, data_hash) с флагом is_manifest = true. Индекс узла хранит эту связь, позволяя быстро находить manifest для любого Anchor.
Content Request Protocol
P2P сообщения libp2p для обмена контентом между узлами:
ContentRequest:
app_id 32B
data_hash 32B (data_hash из Anchor — manifest root)
ContentResponse:
status 1B (0 = ok, 1 = not_found, 2 = error)
payload variable (serialized Manifest если status = 0)
ChunkRequest:
data_hash 32B (data_hash манифеста)
chunk_index 4B
ChunkResponse:
status 1B
chunk_data variable (до chunk_size байт)
Процесс верификации при получении:
- Получив Manifest: десериализовать, проверить каноническая форма, пересчитать
data_hash = SHA-256("mt-content-manifest" || serialization), сравнить с запрошенным - Получив чанк: пересчитать
chunk_hash = SHA-256("mt-content-chunk" || chunk_data), сравнить с соответствующим элементомchunk_hashesв manifest - После сбора всех чанков: пересчитать Merkle tree из chunk_hashes, сравнить корень с data_hash из Anchor в proposal
- Любое несовпадение — отклонить ответ пира, запросить у другого пира, пометить нечестного пира в local blacklist транспорта
Content Discovery
Два параллельных механизма поиска провайдеров контента:
DHT provide/lookup (Kademlia):
- Узел хранящий контент app_id публикует запись "я провайдер для app_id X" в Kademlia DHT
- Запрашивающий узел делает lookup по app_id, получает список провайдеров
- Подключается к провайдерам, запрашивает контент через ContentRequest/ChunkRequest
- Стандартный libp2p content routing
Gossip announce:
- При установлении соединения с новым пиром узел в handshake объявляет список своих app_id (Bloom filter если список большой)
- Пир запоминает привязку пир → app_id
- При локальном ContentRequest по app_id которого нет — пересылает запрос пирам объявившим этот app_id
Оба механизма работают параллельно. Узел использует любой рабочий путь. Content Discovery — локальная network state, не входит в consensus.
Genesis Content
Книга Montana — первый и обязательный контент сети, зафиксированный в Genesis Decree:
genesis_content_app_id = SHA-256("mt-app" || "montana")
genesis_content_data_hash = <хэш манифеста книги Montana v1.0, хардкодировано в Genesis Decree>
Mandatory replication. Каждый узел Montana обязан хранить текущую версию книги Montana как persistent blob. Это часть протокольного определения "узел Montana", не optional подписка. При Fast Sync новый узел загружает genesis content вместе с initial state; без успешной загрузки sync считается неполным.
Обновление книги. Автор публикует новый Anchor в genesis_content_app_id с новым data_hash. Узлы получают новую версию через Content Request Protocol, верифицируют через Merkle reconstruction, заменяют локальную копию. Старые версии остаются доступными через исторические Anchor в proposals навсегда — версии книги историчны и неудаляемы. Обновление значения genesis_content_data_hash в protocol_params возможно только через software upgrade узла (новая версия Rust core с обновлённой константой), как любое изменение Genesis Decree.
Application Layer Interop Standards
Этот раздел фиксирует минимальные стандарты для совместимости между приложениями Montana. Приложения следующие этим стандартам могут взаимодействовать между собой — обмениваться профилями, сообщениями, контентом. Приложения использующие другие форматы работают в изоляции.
Это нормативный раздел: форматы и формулы в нём обязательны для interop-совместимых приложений.
Canonical app_id registry
Фиксированные app_id для стандартных функций приложений:
| Функция | Формула | Назначение |
|---|---|---|
| genesis content | SHA-256("mt-app" || "montana") |
Книга Montana и её обновления |
| profile | SHA-256("mt-app" || "profile") |
Публичные профили пользователей |
| encryption keys | SHA-256("mt-app" || "encryption-keys") |
Discovery encryption pubkeys |
| messenger prekeys | SHA-256("mt-app" || "messenger-prekeys") |
Pre-keys bundles для Double Ratchet |
| phone discovery | SHA-256("mt-app" || "phone-discovery") |
Public mode phone → account lookup |
Пользовательские каналы используют формулу SHA-256("mt-app" || channel_name) где channel_name — произвольная строка выбранная создателем канала. Уникальность каналов обеспечивается через уникальность имени; коллизии разрешаются по первому cemented Anchor в данном app_id.
ProfileBlob format
Канонический формат публичного профиля пользователя:
ProfileBlob {
version u16 (currently 1)
display_name string (UTF-8, length-prefixed, max 64 bytes, may be empty)
avatar_hash 32B (ref to image blob by data_hash, или 0x00..00)
bio string (UTF-8, length-prefixed, max 256 bytes, may be empty)
updated_at u64 (unix timestamp публикации)
}
Сериализация: little-endian, length-prefix для строк (u16 length + bytes).
Публикация профиля:
- Serialize ProfileBlob канонически
data_hash = SHA-256("mt-profile" || serialized)store_blob(app_id_profile, data_hash, serialized)publish_anchor(app_id_profile, data_hash)
Lookup профиля другого пользователя:
- Запросить через Anchor history: все Anchor с
app_id = profileиsender = target_account_id - Отсортировать по времени (окно финализации Anchor), взять новейший
fetch_blob(app_id_profile, latest_data_hash)- Deserialize
Profile опционален — пользователь может не публиковать профиль. Приложение должно поддерживать локальные override имён независимо от публичного профиля (пользователь может видеть контакт как "Мама" локально, даже если публичный профиль контакта другой).
Published encryption_pubkey format
Формат блока публикации encryption key пользователя для приёма зашифрованного контента:
EncryptionKeyBlob {
version u16 (currently 1)
algorithm u16 (1 = ML-KEM-768)
encryption_pubkey variable (1184B для ML-KEM-768)
published_at u64 (unix timestamp)
}
Публикация:
- Serialize EncryptionKeyBlob
data_hash = SHA-256("mt-encryption-key" || serialized)store_blob(app_id_encryption_keys, data_hash, serialized)publish_anchor(app_id_encryption_keys, data_hash)
Lookup encryption key получателя:
- Запросить Anchor history: все Anchor с
app_id = encryption-keysиsender = target_account_id - Взять новейший (последняя ротация ключа)
fetch_blob(app_id_encryption_keys, latest_data_hash)- Deserialize, извлечь encryption_pubkey
Key rotation. Публикация нового Anchor с новой EncryptionKeyBlob. Старые ключи остаются в proposal history навсегда — старые ciphertexts расшифровываются если владелец сохранил старый seckey.
Messenger pre-keys bundle format
Для инициализации Double Ratchet PQ session с offline получателем. Пользователь публикует pre-keys bundle заранее; отправитель использует его для первого сообщения без ответа получателя.
PreKeyBundle {
version u16 (currently 1)
identity_key variable (ML-KEM-768 identity pubkey, 1184B)
signed_prekey variable (ML-KEM-768 signed pre-key, 1184B)
prekey_signature 666B (FN-DSA-512 подпись signed_prekey identity key)
one_time_prekeys [variable] (массив ML-KEM-768 pubkeys, одноразовые)
published_at u64
}
Публикация:
- Serialize PreKeyBundle
data_hash = SHA-256("mt-prekeys" || serialized)store_blob(app_id_messenger_prekeys, data_hash, serialized)publish_anchor(app_id_messenger_prekeys, data_hash)
Refresh. При исчерпании one_time_prekeys (каждое pre-key используется одним отправителем и удаляется) публикуется новый bundle. Получатель должен мониторить использование pre-keys и публиковать fresh bundle заранее.
Phone discovery (public mode)
Опциональная функция для приложений поддерживающих поиск по номеру телефона.
Формула: phone_hash = SHA-256("mt-phone-public" || phone_e164)
Где phone_e164 — номер телефона в формате E.164 (например +79991234567).
Публикация в public mode:
- Пользователь явно включил public phone discovery в приложении
- Приложение вычисляет phone_hash
data_hash = phone_hash- Persistent blob содержит
account_idвладельца (32B) store_blob(app_id_phone_discovery, data_hash, account_id)publish_anchor(app_id_phone_discovery, data_hash)
Lookup:
- Приложение для каждого контакта из адресной книги вычисляет phone_hash
fetch_blob(app_id_phone_discovery, phone_hash)→ account_id или not_found- Если найден — контакт в сети Montana
Privacy warning. Public mode подвержен rainbow table attack. Атакующий со списком phone numbers может вычислить phone_hashes и искать совпадения в сети. Это эквивалентно модели WhatsApp. Пользователь выбирает режим осознанно.
Private mode (рекомендованный по умолчанию). Пользователь не публикует phone_hash. Контакты добавляются только через QR-код, ссылку или прямой обмен account_id. Максимальная приватность, ручной ввод контактов.
Recommended crypto primitives для Application Layer
Эти примитивы не входят в protocol core (core остаётся с SHA-256 + FN-DSA-512). Они рекомендованы для Application Layer encryption и messaging чтобы обеспечить совместимость между приложениями. Приложения использующие другие примитивы работают в изоляции.
| Примитив | Стандарт | Применение |
|---|---|---|
| ML-KEM-768 | FIPS 203, NIST PQC | Key encapsulation для encrypted messaging и file encryption |
| ChaCha20-Poly1305 | RFC 8439 | Symmetric AEAD encryption содержимого |
| HKDF-SHA-256 | RFC 5869 | Derivation ключей из KEM shared secret |
Все три примитива постквантово-безопасны или симметричны (ChaCha20-Poly1305 ослабляется Grover до 128-bit, приемлемо). Production-ready реализации доступны для всех major языков.
Application-level key derivation из seed. Encryption keypair (ML-KEM-768) выводится из того же seed что и account и node keypairs, через отдельный domain separator mt-encryption-key (см. раздел Деривация ключей). Восстановление seed из мнемоники восстанавливает все три keypair одной операцией.
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. Пересчитать NodeChain endpoint confirmer от start_window до окна W
c. Подтвердить заявленный chain_length
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, 5: ~O(1) хэш-операций (константное число CPU-циклов). Шаг 3b: пересчёт NodeChain VDF confirmer — m × (W − start_window) хэшей, параллелизуется по сегментам. Шаг 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 и NodeChain 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, NodeChain, адреса, Merkle-деревья |
| FN-DSA-512 | NIST PQC selection финал (июль 2022), forthcoming FIPS 206, reference implementation production-ready | Подписи операций аккаунтов и proposals узлов |
Consensus encoding layer
Консенсусно-критическая поверхность: каноническая сериализация, Merkle layout и domain separation. Разная сериализация одного объекта = разный хэш = форк. Требования:
- Fixed binary encoding для каждого консенсусного объекта
- Length-prefix кодирование полей, фиксированный endianness (little-endian)
- Domain separation для всех хэшей:
| Домен | Контекст |
|---|---|
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-nodechain-init |
NodeChain init seed |
mt-confirmation |
Хэширование async confirmations |
mt-app |
Деривация app_id для Application Layer |
mt-node |
Деривация node_id |
mt-genesis |
Деривация frontier_hash genesis-аккаунтов |
mt-nodechain-genesis |
Деривация nodechain_init для genesis-узлов |
mt-seed |
Salt для PBKDF2 деривации seed из мнемоники |
mt-account-key |
Деривация keypair аккаунта из seed |
mt-node-key |
Деривация keypair узла из seed |
mt-account-lottery |
Endpoint AccountChain для лотереи |
mt-content-chunk |
Хэширование чанков контента в Content Layer |
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 |
- Альтернативные сериализации запрещены
- Test vectors для каждого консенсусного объекта
- Cross-implementation conformance tests перед запуском mainnet
Protocol layer
Собственная реализация поверх криптографического ядра:
| Компонент | Назначение |
|---|---|
| Merkle-деревья | State Root (из SHA-256 вызовов) |
| VDF scheduling | Управление TimeChain и NodeChain цепочками |
| 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))
Сервер проверяет:
- Подпись валидна для заявленного client_pubkey
- Window slot = текущий ИЛИ предыдущий (окно = 2 window_index)
- Уровень доступа — сервер проверяет 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, NodeChain, state machine работают поверх любого транспорта без изменений.
Peer Selection
Открытый вход с VDF-барьером делает sybil-узлы дорогими: каждый sybil = 13 000 окон VDF + 3 ядра CPU + 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 входящих. При переполнении — вытеснение:
- Защитить 4 с наименьшим пингом
- Защитить 4 с последними полезными сообщениями (любое валидное Montana-сообщение которое узел ещё не видел)
- Защитить до 8 из разных подсетей (по одному от каждой)
- Защитить 4 с последними proposals
- Из оставшихся — вытеснить из крупнейшей подсетевой группы
Якоря
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 не затронут.
Эволюция протокола
Изменения правил протокола существуют вне 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, как социальный и инженерный процесс над Content Layer и репозиториями реализаций.
Жизненный цикл изменения
1. PROPOSAL
Любой участник публикует MIP (Montana Improvement Proposal)
как persistent blob в Content Layer:
app_id = SHA-256("mt-app" || "mips")
data_hash = H(текст MIP)
anchor = операция Anchor в AccountChain автора
Авторство и timestamp доказуемы через подпись Anchor и
timechain_value cemented окна. История эволюции навсегда
в Content Layer + 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 структуры — публикующие рекомендации, обзоры, анализ безопасности через Content Layer. Их подписи не имеют binding эффекта на consensus, их составы не хранятся в state, их голоса не считаются в state transitions.
Примеры advisory структур (опциональны, не часть протокола):
- AI Council — модели разных компаний публикуют технические обзоры MIPs
- Core Council — публичные эксперты публикуют анализ безопасности и социальную координацию
Захват advisory совета не даёт контроля над протоколом — он даёт только возможность опубликовать рекомендацию, которую операторы узлов могут проигнорировать. Это устраняет attack surface governance: нет binding голосования = нет цели для компрометации.
Advisory councils организуются вне протокола (репозитории, форумы, каналы Content Layer). Протокол не знает об их существовании и не выделяет им никаких прав.
Параметрическая адаптация
Параметры 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₀ (NodeChain VDF за окно) | 10⁹ (D₀ / 13) | Пропорция 1:13 от TimeChain. NodeChain доказывает присутствие в каждом окне, объём работы = 1/13 от TimeChain |
| τ₂ (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 |
| Ядра на узел | 3 | TimeChain + NodeChain + Account validation |
Инженерные константы
| Константа | Значение | Обоснование |
|---|---|---|
| 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 | скользящее окно 6τ₂ (78 000 окон) | Количество cemented BundledConfirmation за последние 6τ₂. Основа lottery_weight. Новый узел через 6τ₂ выходит на равный 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 | effective_vdf = 13000 × pressure × 100. При 100% давлении VDF в 100 раз длиннее. Self-correcting: стоимость атаки растёт пропорционально давлению |
Архитектура
ТЕЛЕФОН / ДЕСКТОП УЗЕЛ (десктоп / сервер, 24/7)
┌────────────────────────┐ ┌──────────────────────────────────────┐
│ Кошелёк │ │ │
│ FN-DSA-512 keypair │ │ TimeChain │
│ локальная UX-история │ │ T_r = SHA-256^D(T_{r-1}) │
│ операций │ │ каноническая последовательность, │
│ │ │ источник случайности │
│ AccountChain │ │ │ │
│ (счётчик окон │ │ ▼ │
│ активности) │ │ NodeChain (per node) │
│ │ │ S_{i,w} = SHA-256(S_{i,w-1} || T_w │
└──────────┬─────────────┘ │ || node_id) │
│ операции │ доказательство присутствия │
│ (type|prev_hash| │ chain_length = окна с BundledConf. │
│ 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.
Отказ узла не заражает каноническую последовательность.