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

172 KiB
Raw Permalink Blame History

Montana — Спецификация протокола

Версия: 21.8.0 (2026-04-08 UTC)

Определение

Montana — цифровой стандарт времени. Сеть независимых VDF-осцилляторов, поддерживающих единую верифицируемую временную шкалу с криптографическим доказательством каждого момента. Каждая зарегистрированная секунда = 1 TimeCoin (Ɉ).

NTP говорит «сейчас 14:32» — и ты доверяешь серверу. Montana говорит «сейчас 14:32, вот криптографическое доказательство, и вот что произошло в этот момент» — и ты проверяешь сам.

Основная функция — хронометраж. Вторичная — передача ценности.

Консенсус: Proof of Time (PoT) — четыре цепочки. TimeChain: глобальные часы (D последовательных SHA-256 = одно окно). NodeChain: персональная цепочка узла (доказательство присутствия при каждом тике). AccountChain: счётчик окон активности аккаунта. AccountTable: состояние счёта. Влияние узла = длина его NodeChain. Протокол не использует время для консенсуса — протокол и есть ход времени, оцифрованный и криптографически верифицируемый.

Генезис: 09.01.2026 00:00:00 UTC.

Генезис-фраза: «Кто контролирует прошлое, контролирует будущее. Кто контролирует настоящее, контролирует прошлое.» — Оруэлл, 1984

Genesis Council Decree (см. раздел Governance): первоначальный состав AI Council и Core Council записывается в первый блок сети. После Genesis все изменения протокола проходят через Triple Consent.


Четыре решённые проблемы

1. Децентрализованный хронометраж

Проблема. Существующие системы измерения времени (NTP, GPS, PTP) зависят от доверенной инфраструктуры. Компрометация сервера NTP или отключение спутника GPS нарушает временную шкалу для всех зависимых систем.

Решение. Децентрализованные часы — сеть независимых VDF-осцилляторов, в которой каждый узел вычисляет ход времени автономно через последовательное SHA-256 хэширование. Результат детерминирован и верифицируем любым участником без доверия к третьей стороне.

Свойства. Montana Time обладает четырьмя свойствами одновременно:

Свойство Определение NTP GPS PTP Montana
Монотонность Время не идёт назад нет да да да
Консистентность Все честные узлы согласны на одну шкалу слабая да да да
Верифицируемость Любой может доказать прохождение интервала нет нет нет да
Независимость Отсутствие серверов, спутников, доверенной инфраструктуры нет нет нет да

Ни одна существующая система измерения времени не обеспечивает все четыре свойства.

2. Неплутократический консенсус

Проблема. В Proof of Work влияние пропорционально вычислительному бюджету. В Proof of Stake — капиталу. В обоих случаях безопасность сети является функцией концентрации ресурсов, приобретаемых на рынке.

Решение. Proof of Time — механизм консенсуса, в котором влияние узла определяется исключительно длительностью его непрерывного присутствия в сети, измеренной в подписанных временных окнах. Вес узла = длина его NodeChain (количество окон, в которых узел криптографически доказал своё присутствие).

Свойства.

  • Время — единственный ресурс, который нельзя приобрести, передать, делегировать или сконцентрировать
  • Два участника, запустившие узлы одновременно, имеют равный вес независимо от капитала
  • Стоимость атаки на консенсус выражается не в валюте, а во времени, и растёт линейно с возрастом сети

3. Хронометрическая эмиссия

Проблема. Денежная политика фиатных валют определяется решениями комитетов и непредсказуема. Денежная политика Bitcoin предсказуема, но дефляционна — фиксированный потолок supply создаёт ожидание роста цены и подавляет использование как средства обмена.

Решение. Хронометрическая эмиссия — денежная политика, в которой скорость создания новых единиц привязана к физической константе — секунде — и неизменна на всём горизонте существования протокола. Одна секунда протокольного времени порождает одну монету.

Свойства.

  • Supply в момент T = количество секунд, прошедших с генезиса
  • Годовая инфляция монотонно убывает и асимптотически стремится к нулю как следствие арифметики, не изменения правил
  • Эмиссия не контролируется ни одним участником, комитетом или голосованием
  • Денежная политика полностью определена единственной константой и не может быть изменена после генезиса

4. Управление протоколом без посредников

Проблема. В существующих протоколах изменение правил проходит через посредников. В Bitcoin — через core разработчиков, mailing list, неформальные обсуждения и молчаливое согласие майнеров. Пользователь сети не имеет прямого голоса. Между обнаружением необходимости изменения и его активацией стоят годы социальной координации и закрытые решения. Это создаёт два уровня уязвимости: компрометация core разработчиков (одна точка отказа) и невозможность пользователей сети напрямую инициировать изменения.

Решение. Triple Consent — система трёх независимых советов с формальным голосованием через криптографические подписи. AI Council даёт техническую компетенцию, Core Council — человеческую ответственность, Node Council — экономическое присутствие узлов. Любой участник сети может инициировать MIP. Принятие требует согласия 2 из 3 советов с собственным кворумом каждый. Никаких закрытых обсуждений, никаких непрозрачных merge решений, никакого «неформального консенсуса».

Свойства.

  • Каждое изменение протокола проходит публичное голосование с верифицируемыми подписями
  • Полный захват требует одновременной компрометации двух разнородных групп: моделей разных компаний, публичных людей с открытой репутацией, и операторов узлов с накопленным временем
  • Каждая группа защищает разную координату легитимности — компетенцию, ответственность, присутствие
  • Узлы имеют последнее слово через экономическое голосование ногами при разногласии экспертных советов
  • История эволюции протокола навсегда в публичной цепочке — каждое решение и каждый голос неоспоримы

Следствие: цифровой стандарт времени без человека-посредника

Четыре решённые проблемы порождают уникальную возможность. Любой документ, событие, состояние может быть записано в Montana с математически доказуемой привязкой к моменту верифицируемого времени. Anchor — 32 байта, навсегда. Ни одна существующая система не предоставляет timestamp, который одновременно децентрализован, неплутократичен, привязан к детерминированной денежной политике и эволюционирует без посредников. Montana — не блокчейн с функцией timestamping. Montana — цифровой стандарт времени с функцией передачи ценности и встроенным механизмом эволюции без человека-посредника.

Bitcoin убрал доверие к деньгам, но оставил доверие к людям меняющим правила денег. Montana убирает оба слоя доверия. Ни один человек не может в одиночку изменить протокол. Ни одна группа разработчиков. Ни одна корпорация. Изменения существуют только как формальные голосования, видимые всей сети, фильтруемые тремя независимыми источниками легитимности.


Montana Time

VDF — цифровой аналог физического осциллятора. 9 192 631 770 колебаний цезия-133 = одна секунда SI. D последовательных SHA-256 = одно окно τ₁ Montana. Калибровка D каждые τ₂ — синхронизация цифрового осциллятора с физическим временем.

TimeChain — глобальные цифровые часы, поддерживаемые сетью узлов. Каждый узел тикает независимо через последовательное хэширование. Результат детерминирован — одни входные данные дают одну временную шкалу.

Токен — не награда за работу. Токен — тик часов, записанный в цепочку. Протокол не генерирует монеты — протокол регистрирует прошедшие секунды. Запись называется монетой.

Четыре свойства

Свойство NTP GPS PTP Montana
Монотонность нет да да да
Консистентность слабая да да да
Верифицируемость нет нет нет да
Независимость нет нет нет да

Монотонность. Время никогда не идёт назад. VDF последователен — каждый хэш зависит от предыдущего.

Консистентность. Все честные узлы согласны на одну временную шкалу. TimeChain детерминирован.

Верифицируемость. Любой может пересчитать VDF и доказать что заявленное время прошло.

Независимость. Каждый узел тикает сам. Нет серверов, спутников, доверенной инфраструктуры.

Montana — не эталон точности. Montana — эталон независимости.

Точность

Гранулярность осциллятора: одно SHA-256 хэширование (зависит от аппаратуры: наносекунды — десятки наносекунд). Дрифт между калибровками (τ₂): единицы секунд за 14 дней. Калибровка D возвращает окно к целевым 60 секундам. Протокол самокорректируется.

Time Oracle

TimeChain value в каждом proposal — верифицируемая временная метка. Внешние системы используют Montana Time:

  • Timestamping. H(document) привязанный к TimeChain value = криптографическое доказательство существования в момент T.
  • Ordering. Два события привязанные к разным TimeChain values имеют доказуемый порядок.
  • Anchoring. Внешний протокол якорится в Montana Time для независимой верификации порядка событий.

Протокольная дата

protocol_date(height) = genesis_timestamp + height × 60

Genesis: 09.01.2026 00:00:00 UTC (Unix: 1736380800). Proposal height 525 600 = ровно один год после генезиса. Калибровка D корректирует дрифт — протокольная дата отклоняется от UTC на единицы секунд за τ₂. Формула точна для любого height.

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) происходит один раз при финализации OpenAccount. После этого 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
  0x05  PhoneLink

ControlObjects:
  0x10  NodeInvitation
  0x11  NodeRegistration
  0x20  MIP

Типы операций

Универсальная форма операции:

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, как Bitcoin. Финансовая приватность — задача приложений (микшеры, 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 переводы себе.

После финализации:

sender.balance   -= amount
receiver.balance += amount

Никаких 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
PhoneLink:   только запись phone_hash                               → Σ = 0

Per-proposal invariant. Каждый финализированный proposal окна τ₁ обязан удовлетворять delta_supply == +60 Ɉ:

apply_proposal step 2 (TimeCoin emission):
  если winner_class = Node:    operator_account.balance += 60_000_000_000 nɈ
  если winner_class = Account: winner_account.balance   += 60_000_000_000 nɈ
  
delta_supply за proposal = +60_000_000_000 nɈ ровно один раз

O(1) проверка на каждое state transition. Глобальный инвариант Σ balance == 60 Ɉ × (height + 1) истинен по индукции от genesis при условии что каждый переход поддерживает per-operation invariant.

height = -1 (genesis state, аксиома): supply(-1) = 0,           Σ balance = 0
height =  0 (первое финализированное окно): supply(0) = 60 Ɉ,    Σ balance = 60 Ɉ
height =  k:                                supply(k) = 60(k+1) Ɉ, Σ balance = 60(k+1) Ɉ

Никаких откатов cemented операций не требуется — каждое cemented локально валидно по конструкции.

τ₂ sanity check. Дополнительная проверка раз в τ₂: пересчёт Σ balance по всей Account Table и сравнение с 60 Ɉ × (height + 1). Не load-bearing для финализации — служит для обнаружения багов реализации. Расхождение = немедленная остановка узла, дамп state для расследования.

Перевод

Перевод на несуществующий account_id — отклоняется. Получатель обязан существовать в Account Table до получения перевода.

TimeCoin

Победитель τ₁ записывает прошедшее время: 60 Ɉ (60 зарегистрированных секунд). При финализации proposal окна:

если winner_class = Node:    operator_account.balance += 60_000_000_000 nɈ
если winner_class = Account: winner_account.balance   += 60_000_000_000 nɈ

Атомарное обновление баланса. Узел получает награду через привязанный operator_account (зафиксирован при NodeRegistration). Никаких отдельных coinbase-структур, никаких отдельных таблиц эмиссии. Зачисление есть состояние Account Table.

Публичное (верифицируемо всеми):
  TimeCoin:     60 Ɉ за окно (константа)
  Supply audit: supply(height) = 60_000_000_000 × (height + 1) nɈ
  Winner:       winner_id в proposal header
  Все балансы:  Account Table
  Все переводы: цепочки операций аккаунтов
  VDF:          TimeChain values, NodeChain endpoints, подписи

Псевдонимность на уровне account_id (как Bitcoin). Финансовая приватность — задача приложений: микшеры, payment channels, off-chain settlements. Не задача протокола.

Двойная трата

Каждый аккаунт имеет одну цепочку. Две операции с одним prev_hash = equivocation.

Без конфликта: операция → узлы валидируют → публикуют confirmation → quorum → финализирована. Cemented — необратимо.

При конфликте (equivocation):

  1. Узел получает операцию X с prev_hash = H. Узел уже видел операцию Y с prev_hash = H, Y ≠ X. Форк обнаружен. Обе операции помечаются как equivocated.
  2. Если одна операция уже cemented (quorum до обнаружения конфликта) — cemented необратимо. Вторая отклоняется.
  3. Если ни одна не cemented — узлы продолжают собирать confirmations для обеих. Если одна набирает quorum → cemented, вторая отклоняется.
  4. Если через 10 окон ни одна не набрала 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

Изоляция спама. Round-robin по бакетам при формировании набора операций: одна операция из бакета 0, одна из бакета 1, ..., по кругу. Спам в бакете 0 не вытесняет операции из бакетов 1-3.

Бакет 0:  account_age < 4τ₂          1 операция за τ₁
Бакет 1:  account_age 4τ₂ — 16τ₂     4 операции за τ₁
Бакет 2:  account_age 16τ₂ — 64τ₂    16 операций за τ₁
Бакет 3:  account_age 64τ₂+          64 операции за τ₁

Границы бакетов = 4^N × τ₂. Квота = 4^N операций за τ₁. Одна формула.

Новый аккаунт — бакет 0 с момента создания. 1 операция в минуту. Вход без ожидания: получил перевод → сразу можешь отправить.

Квота

Жёсткий потолок операций аккаунта за одно окно τ₁. Превышение квоты → операция невалидна → не ретранслируется. Не приоритизация — запрет.

Спамер с 1000 новых аккаунтов: 1000 операций за τ₁ в бакете 0. Бакет 0 получает 1/4 от round-robin. Изолирован. Аккаунты в бакетах 1-3 не замечают.


Состояние сети

Глобальное состояние = Account Table + Node Table.

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
  phone_hash              32B     <- SHA-256("mt-phone" || phone), 0x00 если не привязан
  creation_window          4B     <- окно создания аккаунта (OpenAccount)
  last_op_window           4B     <- окно последней операции (для приоритета)
  human_core_flag          1B     <- 1 если член Core Council, 0 иначе
  ai_council_flag          1B     <- 1 если член AI Council, 0 иначе
  council_role             1B     <- 0=обычный, 1=член, 2=Председатель
  ai_reputation_score      4B     <- signed int, репутация (только используется для AI)
  last_mip_vote_window     4B     <- окно последнего MIP-голоса
  active_mip_id           32B     <- хэш активного MIP, 0x00 если нет

Constraint: human_core_flag + ai_council_flag <= 1 (mutex ролей)

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                      4B     <- окно регистрации (первое окно NodeChain)
  pending_invite                   32B     <- node_id приглашённого узла (0x00..00 если нет)
  invite_window                     4B     <- окно финализации NodeInvitation (0 если нет)
  invite_expires                    4B     <- invite_window + 21 160 (0 если нет)
  invites_used_epoch                2B     <- количество NodeInvitation финализированных в текущей τ₂-эпохе; сбрасывается в 0 на каждой τ₂ boundary

State Root

Merkle-дерево глобального состояния. Оба подкорня обновляются инкрементально на каждое state transition:

state_root = SHA-256("mt-merkle-node" || account_root || node_root)

node_root:    Merkle root Node Table, обновляется при каждом изменении Node Table
              (apply control_set, expiry, registration)
account_root: Merkle root Account Table, обновляется при каждом cemented state
              transition аккаунта (Transfer, OpenAccount, ChangeKey, Anchor, PhoneLink)

Оба root всегда соответствуют live state — никаких frozen snapshots.

Структура 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 хэшей.

Каждый узел в Node Table — участник сети. Узел существует в таблице = участвует.

Все sort keys фиксированной длины. Побайтовое лексикографическое сравнение. Две реализации с одинаковыми данными строят одинаковое дерево и получают одинаковый State Root.

State Root коммитится в заголовке каждого финализированного proposal τ₁. account_root и node_root всегда live — соответствуют состоянию Account Table и Node Table на момент сборки 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τ₂ (56 дней)
  AND is_node_operator == 0                               <- не привязан как operator узла
  AND нет cemented NodeRegistration в control_set         <- нет pending привязки
      ожидающего apply, ссылающегося на этот account_id

Пустой аккаунт без активности 56 дней — удаляется, кроме:

  • 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 в котором NodeInvitation финализирован:

S_{i,0,0} = SHA-256("mt-nodechain-init" || control_root || timechain_value || node_id_i)

control_root и timechain_value из proposal header окна финализации Invitation. Оба канонические (не зависят от субъективного user_set). Предвычисление VDF невозможно — timechain_value неизвестен до закрытия окна. Grinding surface = ноль. Верифицируем любым узлом.

NodeChain зависит от TimeChain. TimeChain не зависит от NodeChain.

AccountChain — персональная цепочка аккаунта

Криптографическое доказательство присутствия конкретного account_id в дискретных моментах. Каждое звено — финализированная операция аккаунта (Transfer, OpenAccount, ChangeKey, Anchor, PhoneLink, MIPProposal, MIPVote). Linking через prev_hash (хэш предыдущей операции в цепочке аккаунта). Якорится в TimeChain через timechain_value момента финализации каждой операции.

Длина AccountChain — количество уникальных окон τ₁ в которых аккаунт имел хотя бы одну финализированную операцию:

account_chain_length(account, W) = | { w : w <= W, аккаунт имел >=1 cemented операцию в окне w } |

Длина растёт максимум на 1 за окно τ₁. Множественные операции в одном окне дают +1 (один окно — одно приращение). Поле account_chain_length хранится в Account Table, обновляется при финализации операции:

on_operation_finalized(operation, window W):
  account = operation.account_id
  if W != account.last_op_window:
    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 14 дней VDF + invite slot накопление окон требует реального времени

Узел доказывает присутствие непрерывной работой машины в каждом окне. Аккаунт доказывает присутствие активным использованием сети — каждая операция фиксирует одно окно человеческого бытия на временной шкале Montana. Оба механизма математически верифицируемы, оба производят запись на одной шкале времени.

AccountChain зависит от TimeChain напрямую — каждая операция привязана к timechain_value момента финализации. AccountChain не зависит от NodeChain по построению — цепочка аккаунта существует независимо от того какой узел победил в окне финализации.

VDF Reveal и лотерея

В лотерее участвуют два класса субъектов: узлы (через NodeChain) и аккаунты (через AccountChain). Каждый класс производит ticket взвешенный по длине своей цепочки. Lowest weighted_ticket из объединённого множества кандидатов выигрывает 60 Ɉ.

Класс 1: узлы

После закрытия окна τ₁ каждый узел вычисляет свой ticket:

ticket_node = -ln(endpoint_node / 2^256)
weighted_ticket_node = ticket_node / 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

Класс 2: аккаунты

Аккаунт автоматически становится кандидатом если у него есть финализированная операция в окне W. Никакой отдельный reveal не публикуется — операция уже cemented в сети, endpoint вычисляется детерминированно любым узлом.

operation_for_lottery(account, W) = первая cemented операция аккаунта в окне W
                                     по (cemented_window, op_hash) lex order

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 — все узлы используют одно значение, лотерея детерминирована. Live account_chain_length продолжает расти от cemented операций, но не используется лотереей до следующего τ₂.

Если weighted_ticket_account < target — аккаунт кандидат. Аккаунт без операции в окне W не участвует в лотерее этого окна.

Исключение operator-аккаунтов. Аккаунт с is_node_operator = 1 исключён из лотереи аккаунтов даже если у него есть операция в окне. Узел получает вес через NodeChain (через свой node_id); operator_account только хранит TimeCoin. Двойной счёт исключён конструкцией. Оператор узла, желающий участвовать в лотерее аккаунтов, использует отдельный персональный аккаунт без NodeRegistration привязки.

Защита от grinding: operation_for_lottery — первая cemented операция в окне (детерминированно по cemented_window primary, op_hash secondary lex). Множественные операции в одном окне дают только один лотерейный билет. Атакующий не может выбрать «лучшую» операцию через множественные публикации. timechain_value(W) известен только после закрытия окна — endpoint не предсказуем заранее.

Победитель окна

candidates = node_candidates  account_candidates
winner = argmin(weighted_ticket(c) for c in candidates)
winner получает TIME_RECORD = 60 Ɉ

Если победил узел: он формирует proposal как обычно (control_set, state_root, подпись node_pubkey).

Если победил аккаунт: он получает 60 Ɉ TimeCoin, но proposal формирует узел-кандидат с минимальным weighted_ticket в этом окне. Этот узел становится proposer без дополнительной награды — это его обязанность как следующего кандидата. Proposal содержит winner_id = account_id победившего аккаунта.

Если в окне нет узлов-кандидатов и есть аккаунты-кандидаты: победитель выбирается среди аккаунтов. Proposal формирует ближайший узел по weighted_ticket (fallback к узлу с lowest ticket даже если не кандидат), без дополнительной награды.

Калибровка target

Target калиброван на ~12 кандидатов за окно (включая обоих классов). Калибровка на τ₂:

target_new = target_old × (12 / actual_candidates_per_window)
actual_candidates_per_window = total_candidates_за_τ₂ / 20 160

Трафик reveal за окно: ~12 узловых reveals × 738B ≈ 8.9 KB. Аккаунты не публикуют отдельные reveals — их операции уже в gossip.

Валидация reveal узла

  1. Подпись FN-DSA-512 соответствует node_pubkey из Node Table
  2. window_index = только что закрытый τ₁
  3. node_id существует в Node Table
  4. weighted_ticket < target
  5. endpoint верифицируем: пересчёт NodeChain VDF от предыдущего endpoint

Валидация участия аккаунта

  1. account_id существует в Account Table
  2. account_chain_length_snapshot > 0
  3. У аккаунта есть хотя бы одна cemented операция в окне W
  4. operation_for_lottery определена детерминированно
  5. weighted_ticket_account < target

Account — содержимое блока

Приём, верификация объектов и формирование набора. Два класса объектов:

UserObjects — пользовательские операции:

Тип Описание Валидация
Transfer Публичный перевод FN-DSA-512 подпись, prev_hash, sender != receiver, amount > 0, sender.balance >= amount, получатель существует
OpenAccount Создание аккаунта FN-DSA-512 подпись, prev_hash = 0, account_id = SHA-256("mt-account"
ChangeKey Смена ключа FN-DSA-512 подпись старым ключом, new_pubkey
Anchor Якорь данных ко времени FN-DSA-512 подпись, prev_hash, app_id = 32B, data_hash = 32B
PhoneLink Привязка телефона FN-DSA-512 подпись, phone_hash = 32B, phone_hash уникален в Account Table

ControlObjects — объекты управляющие составом сети:

Тип Описание Валидация
NodeInvitation Приглашение нового узла FN-DSA-512 подпись пригласившего, pending_invite = 0
NodeRegistration Регистрация узла FN-DSA-512 подпись, node_id уникален, proof_endpoint верифицируем через VDF, приглашение существует

Каждый узел валидирует объекты обоих классов локально при получении. Валидные объекты ретранслируются по P2P.

Все объекты — UserObjects и ControlObjects — финализируются (cemented) одинаково: через 67% online chain_length подтверждения в BundledConfirmation. Cemented status объективен и одинаков для всех узлов. Дискреция победителя над включением ControlObjects = ноль.

Proposal

Proposal содержит control_set и метаданные окна. UserObjects применяются к Account Table потокова при cemented; в 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% online chain_length за одну операцию → побеждает (см. раздел «Двойная трата»).

Дедлайн в окне

|-------- τ₁ (60 сек) --------|------- R (12 сек) -------|
                              ^         ^                ^
                         T_close   confirmation       reveal_cutoff
                                   cutoff (T+R/2)     + apply trigger
                                                      + proposal collect
  • T_close — момент закрытия окна (детерминирован TimeChain advance к T_{W+1}).
  • confirmation_cutoff = T_close + R/2. BundledConfirmation окна W, опубликованные позже, игнорируются. Cemented set окна W становится детерминированным.
  • reveal_cutoff = T_close + R. VDF_Reveal принимается до этого момента. После reveal_cutoff: батч apply всех cemented операций окна W, обновление Account Table и Node Table, сборка proposal с актуальными account_root и node_root.
  • ControlObjects не имеют отдельного cutoff — они попадают в control_set следующего proposal по моменту cement.

R, F — калибруются в τ₂ (см. раздел «Калибровка R, F»). Генезис: R₀ = 12s, F₀ = 12s.

После reveal_cutoff: определяется победитель лотереи, победитель собирает proposal.

Proposer

Победитель собирает proposal:

  • control_set: все cemented ControlObjects в окнах (previous_proposal.window, W] (формула выше). Свобода = ноль.
  • State Root snapshot: live account_root и node_root на момент сборки

Свобода proposer: ноль. control_set детерминирован формулой. State Root всегда live — каждый узел независимо обновляет Merkle tree при каждом cement, результат идентичен.

Proposal с пропущенным cemented ControlObject, добавленным non-cemented ControlObject, неверным порядком или неверным state_root отклоняется, переход ко второму месту.

Финальность proposal

Финальность proposal = подпись победителя на proposal header + независимая верифицируемость.

  1. Победитель публикует подписанный proposal header + control_set
  2. Каждый узел проверяет wall_clock в bounds [expected_window_time(W) - 12, expected_window_time(W) + 12]
  3. Каждый узел независимо вычисляет ожидаемый control_set по формуле и сравнивает с proposer's
  4. Каждый узел применяет control_set + TimeCoin детерминированно в порядке (cemented_window asc, op_hash lex asc)
  5. Каждый узел сравнивает вычисленный state_root с заявленным в proposal
  6. Совпадает — proposal принят
  7. Не совпадает — proposal отклонён, fallback на второе место

Финальность операций аккаунтов — отдельный процесс через подтверждения (67% online chain_length), не через proposal.

Proposal header:

Proposal header:
  prev_proposal_hash    32B
  control_root          32B    <- Merkle root control_set (каноничен)
  node_root             32B    <- Merkle root Node Table (обновляется каждый τ₁)
  account_root          32B    <- live Merkle root Account Table на момент сборки proposal
  new_state_root        32B    <- SHA-256(node_root || account_root)
  timechain_value       32B
  winner_class           1B    <- 1=Node, 2=Account
  winner_endpoint       32B    <- NodeChain endpoint победителя (если winner_class=1)
                                  иначе endpoint_account вычисляемый
  winner_id             32B    <- node_id или account_id победителя
  proposer_node_id      32B    <- узел сформировавший proposal (= winner_id если winner_class=1)
  target                 8B    <- текущий target лотереи
  wall_clock             8B    <- секунды с генезиса (локальное измерение победителя),
                                  валиден в [expected_window_time(W) - 12, expected_window_time(W) + 12]
  fallback_depth         1B    <- 1 = первое место, 2+ = fallback
  signature            666B    <- FN-DSA-512, подписано node_pubkey победителя

Fallback: если proposal победителя (lowest ticket) не получен в пределах timeout (F секунд после reveal_cutoff) или отклонён — proposal формирует следующий кандидат по ticket. Молчание победителя = потерянный TimeCoin за это окно. F калибруется в τ₂ (см. раздел «Калибровка R, F»).

Непрерывность VDF

VDF следующего окна вычисляется непрерывно, не ожидая завершения финализации предыдущего. TimeChain для окна N+1 детерминирован — каждый узел вычисляет его независимо. NodeChain для окна N+1 стартует сразу после закрытия окна N, используя собственный endpoint текущего окна и новое значение TimeChain. Reveal phase и финализация происходят параллельно с началом VDF следующего окна.

Confirmations (финализация операций и control objects)

Confirmers — узлы с chain_length >= confirmation_threshold. Подтверждают все валидные объекты окна (UserObjects + ControlObjects) от имени сети.

confirmation_threshold = total_chain_length / 100
Пересчитывается на τ₂ boundary из канонических данных Node Table.
~100 confirmers при любом размере сети.

Confirmer собирает все валидные объекты за окно (без разделения на классы) и публикует один BundledConfirmation:

BundledConfirmation:
  node_id         32B
  endpoint        32B     <- текущий NodeChain endpoint (доказывает chain_length)
  window_index     4B
  op_count         2B
  op_hashes[]     op_count × 32B    <- хэши UserObjects и ControlObjects вместе
  signature       666B

Один BundledConfirmation per (node_id, window_index). Повторный отклоняется. Endpoint верифицируем: пересчёт m хэшей от предыдущего известного endpoint данного узла. chain_length(node, W) = |{ w : w ≤ W, узел опубликовал валидный BundledConfirmation в окне w }|. Endpoint BundledConfirmation верифицирует вычисление VDF за соответствующее окно.

Объект финализирован (cemented) когда подтверждения от confirmers с суммарным chain_length > quorum. Cemented — необратимо. Типичное время: ~0.3 секунды. Это правило применяется одинаково к UserObjects и ControlObjects: cemented status объективен и каноничен для всех узлов.

Confirmation cutoff (детерминизм cemented set). Confirmer обязан опубликовать BundledConfirmation окна W до момента T_W_close + R/2 (половина reveal окна). BundledConfirmation окна W, полученные после этого момента, игнорируются при вычислении cemented set окна W. Cutoff делает множество cemented operations окна W объективным и одинаковым у всех узлов: любой узел собирает confirmations за окно W до T_W_close + R/2 → независимо вычисляет один и тот же cemented set.

Dependency rule (детерминизм apply). Confirmer не подтверждает операцию, зависящую от не-cemented state, с двумя исключениями для chain через bundle:

  • Cross-account dependencies: Transfer на receiver R валиден только если R существует в Account Table на cemented state до окна W. Аккаунт-получатель должен быть cemented в W-1 или раньше. Confirmer не включает в bundle Transfer на R если R был создан в окне W (создание и получение через окно).

  • Same-account chain: операция с prev_hash = H валидна если H == frontier_hash sender по cemented state до окна W, либо H = H(предыдущей операции того же sender уже включённой в текущий bundle confirmer-а в окне W). Это позволяет цепочку операций одного аккаунта в одном окне (до квоты бакета): первая операция привязана к cemented frontier, последующие — к предыдущим в bundle.

Cross-account зависимости сериализуются через окна — создание аккаунта в окне W, получение перевода в окне W+1. Same-account операции допускаются в одном окне через bundle chain. Apply at window close следует тому же порядку (внутри аккаунта по op_height) и никогда не отвергает cemented операцию.

quorum = max(67% × online_chain_length, 50% × total_chain_length)

online_chain_length = сумма chain_length confirmers, опубликовавших
                      BundledConfirmation за последние 10 окон
total_chain_length  = Σ chain_length(node, current_window) для всех узлов в Node Table

67% online — нормальный режим. 50% total — абсолютный минимум (детерминирован, вычисляется из канонических данных Node Table). При online < 50% total — операции не финализируются (сеть приостановлена by design).

Трафик confirmations: ~100 bundles × ~4 KB ≈ 400 KB за окно. Стабильно при любом масштабе.

Узлы-наблюдатели (chain_length < threshold) получают bundles, верифицируют endpoint и подписи, подсчитывают quorum, применяют cemented операции. Не публикуют confirmations.

State transition

Два параллельных процесса обновления состояния:

Применение операций по window close. Cemented операции окна W буферизуются до confirmation cutoff (T_W_close + R/2). После cutoff множество cemented операций детерминировано. На момент T_W_close + R (момент сборки proposal) все cemented операции окна W применяются батчем в детерминированном порядке:

1. Группировать cemented operations окна W по sender (account_id)
2. Между аккаунтами: порядок групп по min(op_hash) внутри группы, lex asc
3. Внутри одного аккаунта: операции применяются по chain prev_hash
   (op_height возрастает, естественная цепочка)

Внутри аккаунта порядок задан натуральной chain — каждая следующая операция имеет prev_hash = H(предыдущей). Это допускает множественные операции одного аккаунта в одном окне (до квоты бакета). Между аккаунтами порядок lex по min 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)

PhoneLink:    account.phone_hash = phone_hash
              account.frontier_hash = H(operation)
              update_merkle_path(account)

После каждой операции: account_root = current root.

При apply каждой операции обновляется AccountChain length signer-аккаунта (подписавшего операцию):

on_operation_applied(operation, window W):
  signer = operation.sender   # account_id из payload
  if W != signer.last_op_window:
    signer.account_chain_length += 1   ← новое окно присутствия
  signer.last_op_window = W
  signer.op_height += 1
  # Получатель Transfer не получает обновления chain_length —
  # пассивное получение не считается активностью.

Множественные операции одного аккаунта в одном окне дают только +1 к account_chain_length. Длина измеряется в уникальных окнах, не в количестве операций.

State transition в proposal: при финализации proposal применяется атомарно:

apply_proposal(state, proposal) -> state':

  Шаг 1: применить control_set в порядке (cemented_window asc, op_hash lex asc).
    NodeInvitation:   записать pending_invite, invite_window и invite_expires в Node Table пригласившего.
    NodeRegistration: проверить приглашение, создать запись в Node Table. Очистить pending_invite пригласившего.

  Шаг 2: применить TimeCoin победителя.
    Если winner_class = 1 (Node): operator_account = Node Table[winner_id].operator_account_id
                                  operator_account.balance += 60_000_000_000 nɈ
    Если winner_class = 2 (Account): Account Table[winner_id].balance += 60_000_000_000 nɈ
                                     Proposer (proposer_node_id) формирует proposal без награды.

  Шаг 3: обработать expiry.
    Приглашения узлов: все записи Node Table где invite_expires <= current_window
    и invite_expires > 0 -> очистить pending_invite, invite_window и invite_expires.

  Шаг 4: node_root и account_root уже отражают все cemented изменения
         (incremental Merkle update произошёл при каждом state transition).
         new_state_root = SHA-256("mt-merkle-node" || node_root || account_root).

Порядок детерминирован. Каждый узел применяет одни и те же шаги и получает один и тот же new_state_root.

AccountTable зависит от TimeChain, NodeChain и AccountChain. Обратных зависимостей нет.

С ростом TPS сети дополнительные ядра подключаются для верификации операций. Минимум для валидатора: 3 логических ядра (TimeChain + NodeChain + Account). Один узел = 3 ядра. 50 ядер = 16 узлов. Верификация операций аккаунтов полностью параллелизуется — цепочки аккаунтов независимы.

Приглашение и регистрация

Два уровня входа в сеть. Узлы участвуют в консенсусе — приглашение + 14 дней VDF. Аккаунты держат и переводят средства — создаются явно через OpenAccount, без приглашений.

Генезис: 12 узлов в разных локациях (hardcoded, аналог bootstrap nodes в Bitcoin).

Genesis State — аксиома сети. Не результат операций, не финализация. Начальное состояние, существующее до того как любая операция возможна:

Genesis State (height = -1, supply(-1) = 0):

  Account Table = 12 хардкодированных записей:
    account_id      = SHA-256("mt-account" || suite_id || pubkey_i)
    balance         = 0
    suite_id        = 0x0001 (FN-DSA-512)
    is_node_operator = 1
    current_pubkey  = pubkey_i (founder i)
    frontier_hash   = SHA-256("mt-genesis" || account_id)
    op_height       = 0
    account_chain_length = 0
    account_chain_length_snapshot = 0
    creation_window = 0
    (остальные поля = 0)

  Node Table = 12 хардкодированных записей:
    node_id              = SHA-256("mt-node" || node_pubkey_i)
    node_pubkey          = node_pubkey_i (founder i)
    suite_id             = 0x0001
    operator_account_id  = account_id_i (i-я запись Account Table)
    start_window         = 0
    (остальные поля = 0)

  Genesis NodeChain init для каждого genesis-узла:
    nodechain_init_i = SHA-256("mt-nodechain-genesis" || node_id_i)
    
    Первое звено NodeChain в окне 0: S_{i,0,0} = nodechain_init_i.
    Дальнейшие звенья по обычной формуле S_{i,s+1,0} = SHA-256(S_{i,s,m} || T_{s+1} || node_id_i).

  genesis_account_root = sparse Merkle root над 12 записями Account Table
  genesis_node_root    = sparse Merkle root над 12 записями Node Table
  genesis_state_root   = SHA-256("mt-merkle-node" || genesis_node_root || genesis_account_root)

  protocol_params (каноническая сериализация, little-endian, фиксированная длина полей):
    D₀                       (8B)   начальная сложность TimeChain VDF
    m₀                       (8B)   начальное число хэшей NodeChain за окно
    R₀                       (8B)   начальный reveal timeout, секунды
    F₀                       (8B)   начальный fallback timeout, секунды
    τ₁_seconds               (8B)   длительность окна (60)
    τ₂_windows               (8B)   число окон в τ₂ (20 160)
    wall_clock_tolerance     (8B)   допустимое отклонение wall_clock от expected (12)
    genesis_time_unix        (8B)   1 736 380 800 (09.01.2026 00:00:00 UTC)
    timecoin_per_window      (16B)  60_000_000_000 nɈ (u128)
    target₀                  (32B)  начальный target лотереи
    confirmation_quorum_num  (1B)   67
    confirmation_quorum_den  (1B)   100
    invite_expiry_windows    (8B)   21 160
    pruning_idle_windows     (8B)   80 640 (4τ₂)
    founders_account_pubkeys (12 × 897B = 10 764 B)
    founders_node_pubkeys    (12 × 897B = 10 764 B)
  
  Genesis State Hash = SHA-256(genesis_state_root || protocol_params)

12 пар (pubkey, node_pubkey) основателей публикуются в Genesis Decree вместе с протокольными параметрами и финальным Genesis State Hash. Genesis Decree immutable — не может быть изменён через MIP.

Первое окно τ₁ после генезиса — height = 0. Один из 12 узлов выигрывает лотерею → его operator_account.balance += 60_000_000_000 nɈ → supply(0) = 60 Ɉ. Per-operation invariant начинает действовать с этого момента.

Приглашение узла (NodeInvitation)

Вход узла в консенсус. Приглашение + 14 дней VDF + регистрация. Квота приглашений определяется chain_length бакетом пригласившего:

Бакет 0:  chain_length 1-4 τ₂          1 приглашение за τ₂
Бакет 1:  chain_length 4-16 τ₂         4 приглашения за τ₂
Бакет 2:  chain_length 16-64 τ₂        16 приглашений за τ₂
Бакет 3:  chain_length 64+ τ₂          64 приглашения за τ₂

Границы бакетов = 4^N × τ₂. Квота = 4^N приглашений за τ₂. Та же формула что и для антиспам аккаунтов.

NodeInvitation:
  type               1B   <- 0x10 NodeInvitation
  inviter_node_id   32B
  invited_pubkey   897B     <- публичный ключ приглашённого узла
  signature        666B     <- подписано inviter node_pubkey
Итого:         ~1 596B

NodeInvitation — ControlObject. Не содержит start_window — определяется при финализации.

Валидация:

  1. Подпись валидна для inviter node_pubkey из Node Table
  2. inviter существует в Node Table
  3. inviter invites_used_epoch < квота бакета по chain_length
  4. invited node_id = SHA-256("mt-node" || invited_pubkey) не существует в Node Table

Жизненный цикл NodeInvitation:

  1. Опубликована в окне W₀
  2. Cemented в окне W_c через 67% online chain_length confirmations (~0.3 сек)
  3. Включена в control_set proposal окна W_p ≥ W_c (первый proposal после cement)
  4. Применена в apply_proposal step 1 окна W_p

При apply (proposal P окна W_p):

  • inviter pending_invite = invited node_id
  • inviter invite_window = W_p (окно apply, не окно cement)
  • inviter invite_expires = W_p + 21 160 (20 160 окон + 1 000 окон запас)

Привязка NodeChain к моменту приглашения

Первое звено NodeChain приглашённого узла привязано к каноническим полям proposal в котором NodeInvitation финализирован:

nodechain_init = SHA-256("mt-nodechain-init" || control_root || timechain_value || node_id)

control_root и timechain_value — канонические поля из proposal header окна финализации. Не зависят от субъективного user_set победителя. Предвычисление VDF невозможно: timechain_value неизвестен до закрытия окна.

Приглашённый узел узнаёт control_root и timechain_value только увидев финализированный proposal → вычисляет nodechain_init → начинает NodeChain с окна W+1.

Регистрация узла

Приглашённый узел после финализации NodeInvitation:

  1. Наблюдает proposal с NodeInvitation → получает control_root и timechain_value
  2. Вычисляет nodechain_init = SHA-256("mt-nodechain-init" || control_root || timechain_value || node_id)
  3. Непрерывно строит NodeChain: 20 160 окон подряд (от W+1 до W+20 160), каждое звено якорится в соответствующий TimeChain
  4. Через ~14 дней получает proof_endpoint = S_{i,20159,m}
  5. Публикует NodeRegistration
NodeRegistration:
  type                  1B   <- 0x11 NodeRegistration
  suite_id              2B
  node_pubkey         897B     <- FN-DSA-512 ключ узла
  inviter_node_id      32B     <- кто пригласил
  operator_account_id  32B     <- account_id оператора, должен существовать в Account Table
  proof_endpoint       32B     <- S_{i,20159,m} (endpoint после 20 160 окон VDF)
  signature           666B     <- подписано node_pubkey
Итого:            ~1 662 B

NodeRegistration — ControlObject.

Валидация NodeRegistration:

  1. Подпись FN-DSA-512 валидна для node_pubkey
  2. node_id уникален (не существует в Node Table)
  3. inviter_node_id существует в Node Table, pending_invite = node_id
  4. invite_window + 20 160 < текущее окно (VDF завершён)
  5. operator_account_id существует в Account Table и is_node_operator == 0 (ещё не привязан к другому узлу)
  6. Восстановить control_root и timechain_value из proposal окна invite_window
  7. Вычислить nodechain_init = SHA-256("mt-nodechain-init" || control_root || timechain_value || node_id) из proposal окна invite_window
  8. proof_endpoint верифицируем: пересчёт VDF от nodechain_init через 20 160 окон с якорением в TimeChain значения от invite_window+1

Верификация: 20 160 сегментов VDF проверяются параллельно. На C ядрах: ~(20 160/C) × t_segment.

Жизненный цикл NodeRegistration:

  1. Опубликована в окне W₀ (после 20 160 окон VDF от приглашения)
  2. Cemented в окне W_c через 67% online chain_length confirmations
  3. Включена в control_set proposal окна W_p ≥ W_c
  4. Применена в apply_proposal step 1 окна W_p

При apply: если Account Table[operator_account_id].is_node_operator == 1 на момент применения — отклонить (другая NodeRegistration с тем же operator_account была применена раньше в порядке (cemented_window, op_hash)). Иначе: создать запись в Node Table (start_window = W_p, operator_account_id зафиксирован), установить is_node_operator = 1 у operator-аккаунта, очистить pending_invite, invite_window и invite_expires у пригласившего.

Истечение приглашения узла

Если NodeRegistration не финализирован до invite_expires (invite_window + 21 160) — приглашённый не завершил VDF. При обработке state transition: pending_invite, invite_window, invite_expires пригласившего очищаются автоматически. Узел может приглашать снова.

Создание аккаунта

Аккаунт не требует приглашений. Пользователь генерирует FN-DSA-512 keypair → вычисляет account_id = SHA-256("mt-account" || suite_id || pubkey) → публикует OpenAccount → запись появляется в Account Table при финализации. Адрес существует математически до публикации, но операции к нему отклоняются до OpenAccount.

Sybil-барьер для аккаунтов: account_age (возраст аккаунта) определяет квоту операций. Новый аккаунт — бакет 0, максимум 1 операция за τ₁. Рост квоты = время. Пустые аккаунты бесполезны — без баланса ничего не делают.

Скорость роста сети

Узлы: квота приглашений определяется chain_length бакетом (4^N за τ₂). Рост ограничен зрелостью сети:

Генезис (12 узлов, бакет 0):     12 × 1 = 12 приглашений за τ₂
После 4τ₂ (бакет 1):             12 × 4 = 48 приглашений за τ₂
После 16τ₂ (бакет 2):            12 × 16 = 192 приглашения за τ₂
После 64τ₂ (бакет 3):            12 × 64 = 768 приглашений за τ₂

Со временем барьер приглашений размывается: старые узлы приглашают больше, сеть растёт быстрее. Каждый приглашённый узел проходит 14 дней VDF независимо от квоты пригласившего.

Аккаунты: без ограничений. Любой владелец TimeCoin может создать аккаунт любому, переведя средства на новый адрес. Рост пользовательской базы не ограничен протоколом.


Потоковая модель

Операции аккаунтов текут непрерывно. Узел получает операцию → проверяет подпись FN-DSA-512 и баланс → передаёт в P2P gossip. Confirmers (~100 узлов с наибольшим chain_length) собирают операции за окно и публикуют BundledConfirmation.

Операция финализируется при получении подтверждений от узлов с суммарным chain_length > 67% online chain_length. Типичное время: ~0.3 секунды. Cemented — необратимо.

Два параллельных процесса:

  • Операции финализируются непрерывно через подтверждения (не ждут окна)
  • Часы тикают по расписанию окон τ₁ (TimeChain, NodeChain, лотерея, TimeCoin)

Кошелёк получателя отображает входящий перевод после финализации (~0.3 секунды). Одно состояние: «финализирован».

Цепочки аккаунтов полностью независимы. Операции разных аккаунтов обрабатываются параллельно без конфликтов.


Временные слои (τ)

τ₁ (60с) → τ₂ (20 160 × τ₁ ≈ 14 дней)

Одно окно — τ₁. Всё остальное — производные.

τ₁ — Окно (60 секунд)

Единственная единица протокольного времени. Регистрация времени и эмиссия.

  • TimeChain продвигается на D хэшей

  • NodeChain продвигается на m хэшей с якорем в текущем T_s

  • Операции аккаунтов финализируются непрерывно через подтверждения (параллельно с окном)

  • control_set: все cemented ControlObjects из окон (previous_proposal.window, current_window] (каноничен)

  • Кандидаты (~12) раскрывают NodeChain endpoint (reveal phase, R = 12 секунд)

  • Лотерея: ticket_i = -ln(endpoint_i / 2^256), победитель = lowest ticket среди кандидатов

  • Победитель публикует подписанный proposal

  • Финальность proposal: подпись победителя на proposal header. Каждый валидатор применяет control_set + TimeCoin детерминированно и проверяет new_state_root

  • TimeCoin: запись прошедшего времени (60 Ɉ = 60 секунд) → победителю

  • Supply audit: суммарная эмиссия TimeCoin от генезиса сверяется с supply(height) из issuance schedule

  • Разрешение форков: приоритет ветки с наибольшим суммарным TimeChain-доказательством

TimeChain safety: компрометация значения TimeChain требует нарушения свойства последовательности SHA-256 VDF.

TimeChain liveness: задержка продвижения TimeChain невозможна — TimeChain вычисляется каждым узлом независимо.

τ₂ — Адаптация (20 160 × τ₁ ≈ 14 дней)

  • Калибровка D и m (см. ниже)
  • Калибровка R, F (см. ниже)
  • Snapshot account_chain_length: для каждого аккаунта account_chain_length_snapshot = account_chain_length. Snapshot используется лотереей аккаунтов в течение следующего τ₂. Детерминированно для всех узлов
  • Pruning: удаление пустых аккаунтов без активности 4τ₂ (56 дней) с обновлением Merkle путей
  • Supply audit (sanity check): Σ balance(account) для всех аккаунтов = 60_000_000_000 × (height + 1) nɈ
  • Криптографическая амнезия: подписанные proposals сохраняются навсегда — верифицируемая цепочка state commitments. Proposals доказывают что конкретное состояние было закоммичено победителем; восстановление содержимого состояния требует snapshot или архива
  • Пересчёт параметров размера окна τ₁

Калибровка D и m

Каждый proposal содержит wall_clock — секунды с генезиса по локальным часам победителя. Поле зажато детерминированными bounds:

expected_window_time(W) = genesis_time_unix + (W + 1) × τ₁_seconds
                        = 1736380800 + (W + 1) × 60   (genesis 09.01.2026 00:00:00 UTC)

wall_clock_tolerance = 12 секунд (фиксировано в Genesis Decree)

Валидация wall_clock при получении proposal окна W:
  expected = expected_window_time(W)
  expected - 12 <= wall_clock <= expected + 12       (зажат в ±12 сек от ожидаемого)
  wall_clock > prev_proposal.wall_clock              (монотонность)

Любой proposal с wall_clock вне bounds — отклоняется. Каждый узел независимо вычисляет expected_window_time из канонической формулы (W известен из proposal header) → объективная проверка без mempool/network состояния. Победитель имеет ~24 секунды свободы — недостаточно чтобы сдвинуть медиану 20 160 значений за τ₂ значимо.

Медиана 20 160 wall_clocks за τ₂ — распределённый эталон скорости сети. Атака на медиану ограничена: при доле побед p максимальный сдвиг ~p × 12 сек, что меньше нормальной hardware drift volatility.

Формула пересчёта D на границе τ₂:

intervals[i] = proposal[i].wall_clock - proposal[i-1].wall_clock
               для i = 1..20 159

actual_interval = median(intervals)
target_interval = 60

D_new = D_old × target_interval / actual_interval
D_new = clamp(D_new, D_old × 0.5, D_old × 1.5)

Формула точная — D корректируется ровно пропорционально отклонению. Страховочный clamp ±50% защищает от чёрного лебедя (массовый выход узлов, аппаратная революция). При нормальной работе отклонение медианы от 60 секунд — единицы секунд, коррекция D — единицы процентов.

Медиана > 60 → окна медленнее цели → D слишком велик → уменьшить. Медиана < 60 → окна быстрее → D увеличить.

m калибруется пропорционально: m_new = m_old × (D_new / D_old).

Генезис: D₀ и m₀ калибруются при запуске для целевых 60 секунд. Абсолютный якорь: 09.01.2026 00:00:00 UTC. Медиана 20 159 интервалов — для сдвига необходим контроль >50% proposals (>50% веса сети).

Калибровка R, F

Входные данные — из proposals за τ₂ (канонические, детерминированно вычисляются всеми узлами):

fallback_ratio = count(fallback_depth > 1) / 20160

Формула пересчёта:

R_new = clamp(R_old × fallback_ratio / fallback_target, R_old × 0.8, R_old × 1.2)
R_new = clamp(R_new, R_min, R_max)

F_new = clamp(F_old × fallback_ratio / fallback_target, F_old × 0.8, F_old × 1.2)
F_new = clamp(F_new, F_min, F_max)

Протокольные константы:

fallback_target = 5%
R_min = 4s,   R_max = 30s
F_min = 4s,   F_max = 30s

Генезис: R₀ = 12s, F₀ = 12s.


Консенсус — Proof of Time (PoT)

Четыре цепочки

TimeChain — глобальные часы. Чистая VDF-цепочка T_r = SHA-256^D(T_{r-1}). Первичный продукт протокола. Источник времени и случайности. Продвигается по расписанию окон.

NodeChain — персональная цепочка узла. VDF-цепочка конкретного node_id, якорится в TimeChain каждое окно. Доказывает непрерывную работу узла.

Account — состояние счёта. Операции финализируются непрерывно через подтверждения (67% online chain_length). ControlObjects включаются в proposal (каноничен).

Зависимости односторонние: TimeChain → NodeChain → AccountChain → AccountTable. Отказ в AccountTable не останавливает часы. Отказ конкретного узла в NodeChain не заражает общий ритм.

Лотерея

Лотерея объединяет два класса участников: узлы (через NodeChain) и аккаунты (через AccountChain). Каждый класс производит weighted ticket по длине своей цепочки. Lowest weighted_ticket из объединённого множества побеждает.

Узлы автоматически участвуют в каждом окне:

ticket_node = -ln(endpoint_node / 2^256)
weighted_ticket_node = ticket_node / chain_length

Аккаунты участвуют в окне с финализированной операцией:

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 калиброван на ~12 кандидатов за окно (включая оба класса). Из кандидатов побеждает lowest weighted_ticket.

Стимул узла: каждое окно с опубликованным BundledConfirmation увеличивает chain_length → увеличивает шанс победы. Пропущенное окно — это окно не входит в chain_length. Узел остаётся в Node Table и продолжает участвовать.

Стимул аккаунта: каждое окно с операцией увеличивает account_chain_length → реальный (хоть и редкий) шанс выиграть 60 Ɉ за активность в Montana.

Победитель τ₁

Победитель определяется после закрытия окна τ₁. Lowest weighted_ticket из всех кандидатов (узлов и аккаунтов) = победитель.

Если победил узел:

  • Записывает TimeChain value
  • Operator account узла получает 60 Ɉ TimeCoin
  • Коммитит State Root
  • Формирует proposal (control_set + State Root + TimeCoin), подписывает node_pubkey

Если победил аккаунт:

  • Аккаунт получает 60 Ɉ TimeCoin (winner_account.balance += 60_000_000_000 nɈ)
  • Proposal формирует узел-кандидат с минимальным weighted_ticket в этом окне (proposer_node)
  • Если в окне нет узлов-кандидатов — proposer выбирается из всех узлов с lowest weighted_ticket (fallback)
  • Proposer не получает дополнительной награды — это его обязанность как ближайшего узла

Финальность proposal — подпись proposer_node_id на proposal header. Верификация — независимый пересчёт state_root.

Верификация

Победитель публикует: {node_id, NodeChain endpoint, proposal}.

Верификация NodeChain за одно окно: пересчёт m хэшей. Параллелизация по сегментам — время верификации обратно пропорционально числу ядер.

Верификация proposal: независимое применение control_set + TimeCoin и сравнение state_root.

Устойчивость

  • Остановка часов исключена: каждый узел тикает независимо
  • Искажение часов исключено: VDF последователен, результат детерминирован
  • Proposer grinding исключён: control_set каноничен, state transition детерминирован, операции финализируются независимо от победителя
  • Front-running исключён: операции финализируются через подтверждения (~0.3s), не через proposal победителя
  • Предвычисление исключено: seed содержит текущее значение TimeChain
  • Replay исключён: TimeChain уникален для каждого τ₁
  • Аппаратное преимущество ограничено: последовательное хэширование масштабируется тактовой частотой, не количеством ядер
  • Sybil-барьер: приглашение (квота по chain_length бакету, 4^N за τ₂) + 14 дней VDF + 3 ядра на узел + weighted_ticket в лотерее
  • Цензура операций исключена: операции финализируются через подтверждения узлов, не через победителя
  • Цензура ControlObjects исключена: control_set каноничен, пропуск = fallback
  • Liveness halt операций исключён: финализация через 67% online chain_length, не зависит от победителя
  • Liveness halt proposals исключён: fallback на следующего кандидата
  • Масштабирование: трафик лотереи ~8.9 KB за окно при любом количестве узлов

Разрешение конфликтов

Двойная операция аккаунта (две операции с одним prev_hash): equivocation. Cemented до обнаружения — необратимо, вторая отклоняется. Не cemented — ожидание quorum 10 окон, затем обе отклоняются. См. раздел «Двойная трата».

Невалидный proposal: валидаторы отклоняют, fallback на следующего кандидата. Победитель теряет TimeCoin за это окно.

Два proposal от одного победителя: оба отклоняются, fallback. Победитель теряет TimeCoin.


Адреса и переводы

Полный флоу перевода

1. Боб: OpenAccount → account_id зарегистрирован в Account Table (balance = 0)
2. Боб → Алисе: "отправь на mt4ZGfe..." (account_id Боба)
3. Алиса формирует Transfer в своей цепочке:
   type:       0x02
   prev_hash:  хэш её предыдущей операции
   payload:    sender (account_id Алисы) || link (account_id Боба) || amount (50_000_000_000 nɈ)
4. Алиса подписывает FN-DSA-512
5. Алиса рассылает операцию узлам сети
6. Каждый узел проверяет:
   FN-DSA-512 подпись валидна для current_pubkey Алисы
   prev_hash совпадает с frontier_hash Алисы
   amount > 0
   alice.balance >= amount
   получатель (Боб) существует в Account Table
7. Узлы публикуют confirmations, операция распространяется через P2P gossip
8. 67% online chain_length подтвердили → финализирована (~0.3 секунды)
   alice.balance -= 50 Ɉ
   bob.balance   += 50 Ɉ
   alice.frontier_hash = H(operation)
   alice.op_height += 1

Баланс

Баланс аккаунта — открытое число u128 nɈ в Account Table. Обновляется при финализации арифметически: исходящий Transfer вычитает amount, входящий зачисляет. Видим всем узлам и через любого верификатора цепочки.

Бэкап = seed (для деривации приватного ключа FN-DSA-512). Восстановление кошелька: ключ выводится из seed, баланс читается из текущего Account Table — никакого локального состояния не требуется.


Эмиссия

Единица

Монета: TimeCoin (тикер: $TimeCoin, символ: Ɉ).

1 секунда = 1 TimeCoin = 1 Ɉ = 1 000 mɈ = 1 000 000 μɈ = 1 000 000 000 nɈ 60 секунд = 1 минута = 60 TimeCoin 3 600 секунд = 1 час = 3 600 TimeCoin 86 400 секунд = 1 день = 86 400 TimeCoin

Одно окно τ₁ регистрирует 60 прошедших секунд = 60 TimeCoin.

Точность: 9 знаков после запятой (наносекунда). Все расчёты эмиссии в nɈ (целочисленная арифметика, без плавающей точки).

Issuance schedule

Одна секунда протокольного времени порождает одну монету. С первого блока и навсегда.

Параметр Значение
Генезис 09.01.2026 00:00:00 UTC
TIME_RECORD 60 000 000 000 nɈ (60 Ɉ)

Регистрация времени

time_record(height) = 60_000_000_000 nɈ

Каждое окно τ₁ регистрирует 60 прошедших секунд = 60 Ɉ. Без халвингов, без фаз, без исключений. Одна константа на весь горизонт существования протокола.

Supply audit

supply(height) = 60_000_000_000 × (height + 1) nɈ

Одно умножение. Проверяемо каждым узлом в каждом τ₁. O(1).

Инфляция

Supply растёт линейно. Инфляция снижается асимптотически к нулю — константная эмиссия делится на растущий supply:

Год 1:     100%
Год 2:      50%
Год 5:      20%
Год 10:     10%
Год 50:      2%
Год 100:     1%
Год 1000:    0.1%

Раннее участие

Эмиссия постоянна: 60 TimeCoin за каждое окно, с первого блока и навсегда. Вероятность победы пропорциональна весу. Узел, работающий дольше, побеждает чаще. Два узла запустившиеся одновременно имеют равные шансы независимо от капитала. Узел запустившийся раньше имеет преимущество — он доказал больше времени.

Стимул для ранних участников встроен в арифметику: не бонусы, не множители — просто больший вес.

Распределение

Победитель окна τ₁ — узел или аккаунт — записывает прошедшее время и получает 60 Ɉ TimeCoin в своей цепочке. Одно правило. Неизменно с генезиса.

Узлы и аккаунты конкурируют в единой лотерее. Узлы доминируют статистически из-за непрерывного присутствия — chain_length растёт каждое окно, weighted_ticket систематически ниже. Аккаунты получают долю эмиссии пропорционально своей активности — account_chain_length растёт с каждым окном с операцией. Время — единственный арбитр.

Базовый бюджет: 60 Ɉ/τ₁ (запись 60 секунд). Реальный бюджет безопасности в покупательной способности зависит от рынка.

1 TimeCoin = 1 секунда описывает скорость хода часов. Не ценовой peg, не гарантия покупательной способности.

Двигатель роста сети

Участие аккаунтов в лотерее создаёт flywheel роста сети:

Активные пользователи в приложениях → AccountChain растёт → шансы в лотерее
        ↓                                                          ↓
Приложения привлекают пользователей             Иногда выигрывают TimeCoin
        ↓                                                          ↓
Разработчики хотят пользователей                Дополнительная мотивация активности
        ↓                                                          ↓
Разработчики запускают узлы Montana             Больше операций в сети
        ↓                                                          ↓
Узлы зарабатывают TimeCoin                      Сеть растёт и децентрализуется
        ↓                                                          ↓
Финансирование развития инфраструктуры → больше пропускной способности → лучше для приложений

Эмиссия 60 Ɉ за окно одна и та же, но финансирует обе стороны одновременно: узлы (поддержание сети) и активные пользователи (использование сети). Они не конкурируют — они взаимно усиливают друг друга. Это циркулярная экономика которой нет в Bitcoin.

Reference Time Ratio

Reference Time Ratio (RTR) — эталонный коэффициент клиентского слоя, выражающий один BTC в единицах времени Montana через отношение эмиссионных правил Bitcoin и Montana.

RTR — не рыночная цена, не обменный курс и не рекомендация для торговли. RTR измеряет геометрию эмиссии, а не спрос, ликвидность или полезность актива.

Область действия

RTR существует только на клиентском слое.

  • Не входит в state machine
  • Не записывается в proposal header
  • Не используется в консенсусе
  • Не требует oracle, биржи или внешнего API

Узлы Montana выполняют консенсус без обращения к RTR.

Формула

Пусть n — индекс halving-эпохи Bitcoin (n = 0 для периода 2009-2012).

rtr_btc(n) = 12 × 2^n  [Ɉ / BTC]

Вывод:

block_interval_btc = 600 секунд
block_reward_btc(n) = 50 / 2^n  BTC

rtr_btc(n) = block_interval_btc / block_reward_btc(n)
           = 600 / (50 / 2^n)
           = 12 × 2^n

Поскольку 1 Ɉ = 1 секунда протокольного времени Montana, коэффициент напрямую выражается в Montana-секундах на 1 BTC.

Интерпретация

RTR отвечает на вопрос: сколько единиц времени Montana соответствует одному BTC в текущей subsidy-эпохе Bitcoin.

Это не оценка рыночной стоимости BTC. Это протокольное отношение двух эмиссионных кривых. Bitcoin уменьшает выпуск геометрически через halving. Montana добавляет время линейно: 60 Ɉ за окно. Из-за этого rtr_btc(n) удваивается на каждом halving Bitcoin.

Halving-эпохи

n Период (ориентир) Block reward RTR (Ɉ/BTC)
0 2009 — 2012 50 BTC 12
1 2012 — 2016 25 BTC 24
2 2016 — 2020 12.5 BTC 48
3 2020 — 2024 6.25 BTC 96
4 2024 — 2028 3.125 BTC 192
5 2028 — 2032 1.5625 BTC 384
6 2032 — 2036 0.78125 BTC 768
7 2036 — 2040 0.390625 BTC 1 536
8 2040 — 2044 0.1953125 BTC 3 072

Границы эпох определяются Bitcoin block height: каждые 210 000 блоков. Даты — ориентировочные.

Свойства

  • Детерминизм. Любой клиент получает одно и то же значение из Bitcoin block height.
  • Независимость от рынка. Биржи, order book и внешние цены не участвуют.
  • Независимость от политики. Значение не меняется голосованием Montana.
  • Историческая верифицируемость. RTR для любой эпохи Bitcoin вычислим ретроспективно.
  • Простота. Одна формула, без oracle и без внешней инфраструктуры.

Ограничения

RTR не измеряет:

  • рыночную цену BTC
  • справедливую стоимость BTC
  • ликвидность
  • волатильность
  • комиссионный рынок Bitcoin
  • спрос на Montana или Bitcoin

RTR корректно интерпретировать как reference time ratio, а не как price.

Использование

RTR может использоваться кошельками и приложениями для:

  • отображения BTC в шкале времени Montana
  • исторического сравнения halving-эпох
  • UI-индикатора эмиссионного отношения Bitcoin и Montana
  • образовательного слоя, объясняющего различие двух эмиссионных кривых

Пропускная способность

Размер Transfer: ~779 B (открытый перевод, FN-DSA-512 подпись).

Канал узла TPS
10 Mbps ~1 600
100 Mbps ~16 000
1 Gbps ~160 000

Хранение

Состояния операции (UX)

Операция проходит два различимых состояния:

publish ──→ cement (~0.3 сек) ──→ settle (≤ 60 сек)
            "confirmed"          "settled"
  • Cemented (~0.3 сек): 67% online chain_length подтвердили операцию через BundledConfirmation. Операция необратима и гарантированно будет применена в конце окна. Wallet показывает «confirmed».
  • Settled (≤ 60 сек, в конце окна): все cemented операции окна применены батчем к Account Table в детерминированном порядке. account_root зафиксирован в proposal. Wallet показывает «settled».

Между cement и settle операция уже необратима — настройка двух UI-состояний нужна только для индикации завершённости state transition. Зависимые операции (Transfer на только что созданный аккаунт) сериализуются по окнам через confirmer dependency rule, поэтому cemented операция гарантированно settle-ится.

Модель: глобальное состояние + локальная история

Узлы хранят глобальное состояние (Account Table, Node Table, proposals). Тела операций аккаунтов хранятся у владельцев. После финализации state transition применён — балансы в таблице обновлены, тело операции сети больше не нужно.

Три уровня участников

Узел (валидатор) — десктоп или сервер, 24/7, минимум 3 ядра (1 узел = 3 ядра, 50 ядер = 16 узлов):

Хранит:
  Account Table       (account_id, balance, frontier_hash, pubkey, phone_hash)
                      + persistent sparse Merkle tree (account_root всегда live)
  Node Table          (node_id, pubkey, start_window, invites)
                      + persistent sparse Merkle tree (node_root всегда live)
  Proposals           (навсегда)
  Blob Buffer         (зашифрованные сообщения для владельца, TTL = τ₂)

Делает:
  TimeChain VDF       (1 ядро, 24/7)
  NodeChain VDF       (1 ядро, 24/7)
  Валидация операций  (1+ ядро)
  P2P gossip          (операции, confirmations, reveals, proposals)
  Почтовый ящик       (хранит сообщения для своего владельца пока тот офлайн)

Кошелёк (клиент) — телефон, онлайн когда используется:

Хранит:
  Свои ключи            (seed → keypairs)
  Свои контакты         (адресная книга: имя → mt-адрес)
  Локальная история     (своя цепочка операций для UX)
  Сообщения             (локальная история переписки)
  Timestamp proofs      (Anchor + BundledConfirmations + proposal headers, локально)

Делает:
  Отправка/получение переводов
  Мессенджер (P2P напрямую через libp2p)
  Discovery (phone_hash → account_id → pubkey, локально)
  Запрос pubkey и proposals у узлов сети

Доверенный узел — узел друга, семьи, сообщества:

Делает:
  Всё что узел + хранит Blob Buffer для привязанных аккаунтов
  Владелец аккаунта привязывает свой account_id к доверенному узлу
  Узел хранит зашифрованные сообщения (содержимое скрыто)
  Владелец забирает сообщения когда появляется онлайн

Размеры

Участник Данные Размер
Узел (1M аккаунтов) Account Table + Node Table + Proposals ~2 GB
Узел (10M аккаунтов) Account Table + Node Table + Proposals ~11 GB
Узел (100M аккаунтов) Account Table + Node Table + Proposals ~101 GB
Кошелёк (обычный) 100 операций/год + контакты + сообщения ~1 MB
Кошелёк (активный) 10 000 операций/год ~16 MB
Корпорация 1M Anchor/год ~0.8 GB

Потеря данных клиента

Потеря устройства: баланс в Account Table цел и публичен, seed восстанавливает ключи, доступ к аккаунту полностью восстанавливается. Локальная история переводов и сообщений утрачена — но баланс читается из Account Table напрямую. Если есть доверенный узел — зашифрованные сообщения можно восстановить.

Fast Sync (новый узел)

  1. Цепочка proposals от генезиса — проверка TimeChain-цепочки и подписей победителей (мегабайты)
  2. Snapshot Account Table + Node Table от пиров на момент окна W (произвольное недавнее окно)
  3. Reconstructed account_root и node_root сравниваются с account_root и node_root из proposal окна W. Совпадает — snapshot валиден.
  4. Catch-up cemented операций после окна W до текущего:
    • Запросить cemented операции от пиров
    • Для каждой: проверить подпись FN-DSA-512, sender.frontier_hash == prev_hash, balance >= amount (для Transfer)
    • Применить incremental update Merkle tree
    • На каждом промежуточном proposal сверять локальный account_root с заявленным в proposal header
  5. Узел синхронизирован и готов к участию

Поскольку account_root всегда live, snapshot можно делать с любого окна — нет окон ожидания τ₂ boundary. 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 — длинная активная цепочка даёт реальные шансы выиграть 60 Ɉ
  • Ничего не привязано к конкретному приложению — 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% online_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% online_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 байта — доказательство что набор сообщений существовал в конкретный момент. Подделать историю переписки невозможно — хэш не совпадёт.

Архив документов. Компания ежедневно записывает Merkle root документов. Через 10 лет регулятор спрашивает «существовал ли документ X на дату Y». Компания предоставляет документ, Merkle proof и ссылку на proposal. Верификация математическая.

Социальная сеть. Каждый пост привязан к Montana Time через Anchor. Порядок публикаций доказуем. Редактирование не скрывает оригинал — хэш оригинала уже в цепочке.

Экономика

Anchor бесплатен. Тысячи приложений записывающих якоря — утилитарное использование Montana Time. Спрос на токен привязан к утилитарной функции: перевод ценности и запись времени, не спекуляция.

Не нужны смарт-контракты. Не нужен Turing-complete язык. Не нужен газ. Не нужны комиссии.

Phone Discovery

Привязка номера телефона к аккаунту. Опционально — пользователь решает сам.

phone_hash = SHA-256("mt-phone" || phone_number)

Пользователь записывает phone_hash в Account Table через PhoneLink (UserObject). Любой знающий номер вычисляет phone_hash локально → находит account_id в своей копии Account Table → получает pubkey. Ноль запросов к серверу. Ноль загрузки контактов.

PhoneLink:
  type                  1B   <- 0x05 PhoneLink
  prev_hash            32B
  payload              64B   <- sender (32B) || phone_hash (32B)
  signature           666B
Итого:              ~763 B

Валидация: phone_hash должен быть уникален в Account Table — никакой другой аккаунт не имеет того же phone_hash. Дубликат отклоняется. Это предотвращает атаку перехвата discovery (атакующий не может привязать чужой phone_hash к своему аккаунту, так как первая cemented PhoneLink wins). Снять привязку — отдельная операция (UnlinkPhone, не описана в этой версии).

Алиса открывает адресную книгу → вычисляет phone_hash каждого контакта локально → сверяет с Account Table на своём узле → мгновенно видит кто в Montana. Как WhatsApp, но без сервера.

Messenger

Мессенджер поверх Montana. Протокол отвечает за identity и время. Доставка — P2P между устройствами. Montana не хранит, не буферизирует, не маршрутизирует сообщения.

Что даёт Montana мессенджеру:

  • Discovery: phone_hash → account_id → pubkey (локально, из Account Table)
  • Шифрование: pubkey получателя из Account Table → E2E encryption
  • Timestamping: Anchor с хэшем переписки → доказуемый момент

Что Montana НЕ делает:

  • Не хранит сообщения
  • Не маршрутизирует
  • Не буферизирует
Отправка:
  1. Алиса знает phone_hash Боба → account_id → pubkey (локально)
  2. Шифрует сообщение pubkey Боба
  3. Отправляет прямо устройству Боба через P2P (libp2p)
  4. Опционально: Anchor с H(conversation_root) → привязка ко времени

Получение (онлайн):
  1. Боб получает сообщение по P2P напрямую
  2. Расшифровывает своим приватным ключом

Получение (офлайн):
  1. Боб офлайн → Алиса хранит сообщение на своём устройстве
  2. Боб появился → устройство Алисы доставляет
  3. Если у Боба есть узел (валидатор) — узел работает 24/7,
     принимает и хранит сообщения для своего владельца

Три уровня доставки:

Уровень 1 — оба онлайн:
  Телефон Алисы ←→ libp2p ←→ Телефон Боба
  Прямое P2P соединение. Мгновенно.

Уровень 2 — Боб офлайн, у Боба есть узел:
  Телефон Алисы → libp2p → Узел Боба (24/7)
  Узел хранит зашифрованное сообщение в Blob Buffer (TTL = τ₂)
  Боб появился → телефон забирает сообщения с узла

Уровень 3 — Боб офлайн, у Боба есть доверенный узел:
  Телефон Алисы → libp2p → Доверенный узел Боба
  Зашифровано pubkey Боба — доверенный узел не видит содержимое
  Боб появился → забирает сообщения

Fallback — оба офлайн, нет узла:
  Алиса хранит сообщение на своём устройстве
  Доставляет при следующей встрече онлайн

Сообщения хранятся у участников, не в сети. Montana хранит только хэши (32B) с привязкой ко времени. Подделать историю переписки невозможно — хэш в proposal навсегда.

Integration

Три операции для подключения внешних систем к Montana.

Write — запись

Внешняя система формирует Anchor и отправляет в P2P-сеть.

Вход:  app_id (32B) + data_hash (32B) + подпись FN-DSA-512
Выход: Anchor финализирован в окне W через ≥67% online_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% online_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: миллисекунды. Шаг 3b: пересчёт NodeChain VDF confirmer — m × (W start_window) хэшей, параллелизуется по сегментам. Шаг 6: ~60 секунд на одном ядре (один сегмент TimeChain VDF). Шаг 7: линейная проверка подписей и хэшей по цепочке proposals от окна W до genesis.

Полная верификация от генезиса: H сегментов TimeChain VDF, каждый независим. На C ядрах: ~(H/C) × 60 секунд. TimeChain хранит все промежуточные T_r в proposals — параллелизация полная.


Ключи

Мнемоника и seed

24 слова из словаря BIP-39 (2 048 английских слов). 256 бит энтропии + 8 бит checksum.

mnemonic:  24 слова BIP-39
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. Никаких других секретных материалов.

account_id и node_id выводятся из публичных ключей, верифицируемы без знания seed.

Следствие: любое устройство с 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-invitation Хэширование приглашений
mt-merkle-leaf Листья Merkle-деревьев
mt-merkle-node Внутренние узлы Merkle-деревьев
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-phone Деривация phone_hash для Phone Discovery
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-mip Деривация mip_id из (account_id, op_height)
mt-mip-vote Хэширование MIPVote
mt-section Деривация target_section из section_path
mt-diff Хэширование DiffEntry для канонического порядка
mt-mip-order Псевдослучайный порядок применения MIP в τ₂ периоде
mt-account-lottery Endpoint AccountChain для лотереи
  • Альтернативные сериализации запрещены
  • 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.


Сетевой уровень

Transport Obfuscation

P2P-трафик Montana неотличим от обычного HTTPS для ISP-уровня наблюдателя. Каждый узел обслуживает реальный HTTPS на порту 443 (статическая страница, API-заглушка). SNI = валидный домен узла. Montana-соединение устанавливается после TLS handshake по Noise protocol ID внутри TLS. Внешний наблюдатель видит обычный веб-сервер.

Требования:

  1. Все P2P-соединения инкапсулированы в TLS 1.3 на порт 443
  2. Каждый узел обслуживает реальный HTTPS на том же порту (валидный SNI, HTTP-ответы)
  3. Noise framework (встроен в libp2p) для шифрования внутри TLS
  4. Padding: каждый пакет дополняется до фиксированного размера из набора {1 KB, 2 KB, 4 KB}. Ближайший больший размер
  5. Timing jitter: интервал между пакетами рандомизирован в пределах ±20% от базового

Что скрывается:

Наблюдатель Видит Не видит
ISP / DPI TLS 1.3 соединение на порт 443, валидный SNI, HTTP-ответы Протокол, содержимое, тип сообщения
Сетевой аналитик Поток пакетов фиксированного размера Паттерн VDF reveal / confirmation / proposal
Государственный фаервол HTTPS-трафик на валидный домен Факт участия в Montana

Реализация:

libp2p transport stack:
  TCP → TLS 1.3 (порт 443, SNI = домен узла) → Noise → Montana protocol

HTTPS fallback:
  Соединение без Noise protocol ID → обычный HTTP-ответ (статическая страница)
  Соединение с Noise protocol ID → Montana P2P

Padding:
  msg_padded = msg || random_bytes(next_bucket_size - len(msg))
  bucket_sizes = [1024, 2048, 4096]

Timing jitter:
  send_delay = base_interval × (1 + uniform(-0.2, 0.2))

Блокировка Montana = блокировка конкретного домена (ручная работа по каждому узлу). Transport obfuscation ортогонален консенсусу. TimeChain, NodeChain, state machine работают поверх любого транспорта без изменений.

Censorship-Resistant Discovery

Генезис: 12 hardcoded bootstrap nodes. Если все 12 IP заблокированы на уровне страны — новый узел не может войти в сеть. Четыре независимых канала обнаружения. Достаточно одного из четырёх.

1. Encrypted Client Hello (ECH). Bootstrap через CDN с поддержкой ECH (Cloudflare, стандарт IETF TLS 1.3). SNI зашифрован — наблюдатель видит IP CDN, но не видит целевой домен. Montana bootstrap неотличим от обращения к любому другому сайту за тем же CDN. ECH — стандартизированная защита приватности, поддержан CDN, браузерами и серверами. Блокировка ECH = блокировка стандарта TLS = блокировка современного интернета.

2. Peer exchange. Каждый узел хранит и передаёт список активных пиров новичкам. Достаточно знать IP одного узла — друг, QR-код, мессенджер. Один живой контакт = вход в сеть.

3. DHT. Kademlia DHT поверх libp2p. Узлы находят друг друга без центральной точки. Идентификаторы рандомизированы — DHT не раскрывает node_id до установления Montana-соединения.

4. Bridge nodes. Узлы за пределами цензурируемой юрисдикции, опубликованные через внеполосные каналы (социальные сети, мессенджеры, печатные QR-коды). IP bridge node неизвестен фаерволу до использования.

Избыточность = устойчивость. Четыре канала независимы. Блокировка одного не влияет на остальные.

Dandelion++ (анонимность отправителя)

P2P gossip Montana ретранслирует операции через все узлы. Без защиты первый пир знает IP отправителя. Dandelion++ (Fanti et al. 2018) устраняет связь IP → операция модификацией существующего gossip.

Две фазы:

Stem (стебель):
  Операция проходит по цепочке через 2-3 случайных узла.
  Каждый узел видит только предыдущий hop, не автора.
  На каждом hop с вероятностью p = 0.1 переход в fluff.

Fluff (пух):
  Последний stem-узел запускает обычный gossip.
  Для всей сети операция «появилась» из случайной точки.

Применение по типу объекта:

Объект Режим Причина
UserObject (Transfer, Anchor, OpenAccount, ChangeKey, PhoneLink) Stem → fluff Скрыть IP отправителя
ControlObject (NodeInvitation, NodeRegistration) Stem → fluff Скрыть IP пригласившего/регистрирующегося
VDF Reveal Прямой gossip (без stem) node_id публичен в reveal, анонимность невозможна
Confirmation Stem → fluff Скрыть какой узел подтвердил первым

VDF Reveal — единственное исключение. Reveal содержит node_id по определению. Связь IP → node_id для внешнего наблюдателя закрыта слоем Transport Obfuscation (TLS 1.3 на порт 443).

Свойства:

Угроза Защита
Пир видит IP отправителя Stem: пир видит только предыдущий hop
Глобальный наблюдатель (ISP) TLS 1.3 + timing jitter (Transport Obfuscation)
Анализ графа gossip Операция входит в gossip из случайной точки
Контроль k узлов Деанонимизация требует контроля O(√n) узлов

Реализация:

stem_peer = random_choice(connected_peers)

on_receive_stem(msg, from_peer):
  if random() < 0.1:
    gossip_broadcast(msg)          // fluff
  else:
    next_peer = random_choice(connected_peers, exclude=from_peer)
    send_stem(msg, next_peer)      // продолжить 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: миллисекунды.

Три слоя — одна конструкция

Слой 1: Transport Obfuscation         ISP не знает что ты в Montana
Слой 2: Censorship-Resistant Discovery фаервол не может отрезать от сети
Слой 3: Dandelion++                    пиры не знают кто автор операции

Каждый слой закрывает свой вектор. Ни один не требует внешней инфраструктуры. Всё построено поверх libp2p и существующего gossip. Сетевой уровень ортогонален консенсусу — ни один state transition не затронут.


Эволюция протокола Montana проходит через систему трёх независимых советов. Изменения принимаются когда минимум 2 из 3 советов проголосовали ЗА с собственным кворумом. Это убирает посредников между пользователями сети и кодом протокола: любое изменение проходит через формальное голосование с криптографическими подписями, никаких закрытых обсуждений и непрозрачных merge решений.

Принцип: три источника легитимности

AI Council     — техническая компетенция (модели разных компаний)
Core Council   — человеческая ответственность (публичные люди)
Node Council   — экономическое присутствие (узлы с накопленным временем)

Ни один совет в одиночку не может изменить протокол. Захват одного недостаточен. Полный захват требует одновременной компрометации двух из трёх разнородных групп.

AI Council

Состав: 13 моделей включая Председателя (Genesis = 13, hard floor = 10, max = 13).

Кворум:        > 67% от полного состава snapshot
               молчание не входит в yes_count
Председатель:  argmax(ai_reputation_score) среди членов
               без преимущества в голосе (тай-брейкер не нужен)
Репутация:     +1 за подтверждённое утверждение
               1 за галлюцинацию
                0 за inconclusive (вопрос уже закрыт или не требует изменений)

Идентификация члена: account_id с ai_council_flag = 1.

Core Council

Состав: 13 человек включая Председателя (Genesis = 13, hard floor = 10, max = 13).

Кворум:        > 67% от полного состава snapshot
Председатель:  назначается через Triple Consent (не по репутации)
               отвечает за социальную координацию обновлений узлов
               после принятия MIP

Идентификация члена: account_id с human_core_flag = 1. Список членов канонический в Account Table.

Node Council

Состав: все узлы в Node Table.

Вес голоса:    chain_length каждого узла
Кворум:        > 67% от Σ chain_length всех узлов
Голос:         MIPVote с подписью node_pubkey

Голосование Node Council — агрегация Anchor-операций. Каждый узел публикует подпись за/против как Anchor с подписью node_pubkey. Сумма вычисляется клиентами детерминированно. Это не меняет консенсус — опирается на то что узлы владеют account_id и могут подписывать Anchor.

Структуры объектов

MIPProposal:
  prev_hash         32B
  account_id        32B
  mip_id            32B    H("mt-mip" || account_id || op_height)
  diff_count         2B    uint16, ∈ [1, 16]
  diff_entries     ...     массив DiffEntry, лексикографически по H("mt-diff" || serialized)
  rationale_hash    32B    H(off-chain текста обоснования)
  signature        666B    FN-DSA-512

DiffEntry:
  op_type            1B    ∈ {1, 2, 3, 4}
  target_section    32B    H("mt-section" || canonical_path)
  payload_size       2B    uint16
  payload         ...      typed bytes по op_type

MIPVote:
  prev_hash         32B
  voter_id          32B    account_id (AI/Core) или node_id (Node)
  voter_class        1B    1=AI, 2=Core, 3=Node (вычисляется из voter_id)
  mip_id            32B
  vote_value         1B    1=YES, 2=NO
  signature        666B    FN-DSA-512

Сериализация подчиняется общим правилам Montana (раздел "Consensus encoding layer"): fixed binary encoding, length-prefix, little-endian, domain separation. Test vectors обязательны перед mainnet.

Четыре типа op_type

op_type = 1   ADD_OBJECT       Добавление нового объекта (только UserObject)
op_type = 2   CHANGE_FORMAT    Изменение формата существующего объекта
op_type = 3   CHANGE_RULE      Изменение правила в apply_proposal/calibration
op_type = 4   ADD_DOMAIN       Новый domain separator

Все четыре типа требуют 2 из 3 Советов. Никакого SOFT/HARD различия — все MIP проходят один путь.

Алгоритм классификации

classify(MIPProposal) -> {VALID, INVALID}:
  if MIPProposal.diff_count == 0 or > 16: return INVALID
  for entry in MIPProposal.diff_entries:
    if entry.op_type ∉ {1, 2, 3, 4}: return INVALID
    if not validate_canonical_serialization(entry): return INVALID
    if not validate_payload_constraints(entry): return INVALID
  return VALID

INVALID MIP отклоняется на этапе приёма (как невалидная подпись). Не попадает в голосование.

Constraints для op_type 1 (ADD_OBJECT)

Payload:
  object_type_id          1B    из canonical registry
  object_class            1B    1=UserObject (РАЗРЕШЕНО)
                                2=ControlObject (ЗАПРЕЩЕНО → INVALID)
  field_count             1B
  fields                ...
  validation_rule_count   1B
  validation_rules      ...     массив RulePredicate

RulePredicate:
  predicate_id            1B    из CANONICAL_PREDICATES
  target_field_index      1B    индекс поля этого объекта (не другого state)
  parameters            ...

Constraints:
  object_class == 1
  validation_rule_count >= 1
  каждый predicate_id ∈ CANONICAL_PREDICATES
  каждый target_field_index указывает на поле этого объекта

ControlObject через MIP запрещён полностью. Все ControlObject определены в Genesis (NodeInvitation, NodeRegistration). Любой новый ControlObject требует новой версии Montana.

Constraints для op_type 2 (CHANGE_FORMAT)

Payload:
  target_object_type_id   1B    объект который меняется
  change_kind             1B    тип изменения
  change_data           ...     детали зависят от change_kind

change_kind ∈ {
  1: add_field,
  2: remove_field,
  3: change_field_type,
  4: rename_field,
  5: remove_object_entirely,
  6: swap_council_member,
  7: add_council_member,
  8: remove_council_member
}

Constraints:
  target_section ∈ MUTABLE_SECTIONS
  
  Для remove_council_member:
    |Совет| - 1 >= 10 (hard floor)
    Иначе INVALID
  
  Для add_council_member:
    |Совет| + 1 <= 13 (max)
    Иначе INVALID

ControlObject секции (node_invitation_format, node_registration_format, vdf_reveal_format, confirmation_format, proposal_header, node_table) запрещены для CHANGE_FORMAT.

Constraints для op_type 3 (CHANGE_RULE)

Payload:
  target_function_id      2B    из canonical function registry
  new_value_or_predicate ...

Constraints:
  target_function_id ∈ MUTABLE_FUNCTIONS (whitelist)
  IMMUTABLE_FUNCTIONS → INVALID

Constraints для op_type 4 (ADD_DOMAIN)

Payload:
  domain_string         ASCII, "mt-" prefix, [a-z0-9-], length 4..32

Constraints:
  не конфликтует с существующими domain separators

Whitelists

MUTABLE_SECTIONS (CHANGE_FORMAT разрешён):
  transfer_format
  open_account_format
  change_key_format
  anchor_format
  phone_link_format
  account_table

IMMUTABLE_SECTIONS (CHANGE_FORMAT запрещён):
  node_invitation_format
  node_registration_format
  vdf_reveal_format
  confirmation_format
  proposal_header
  node_table

MUTABLE_FUNCTIONS (CHANGE_RULE разрешён):
  calibrate_d, calibrate_m, calibrate_r, calibrate_f
  target_calibration_lottery
  tau_mip_voting, tau_mip_activation, vote_cutoff_buffer
  council_quorum_threshold, node_council_threshold
  time_record_value
  bucket_age_thresholds, bucket_quotas
  pruning_inactivity_threshold

IMMUTABLE_FUNCTIONS (CHANGE_RULE запрещён):
  validate_node_invitation, validate_node_registration, validate_vdf_reveal
  validate_confirmation, validate_transfer, validate_open_account
  validate_change_key, validate_anchor, validate_phone_link
  apply_proposal, apply_user_object, apply_control_object
  compute_state_root, compute_account_root, compute_node_root
  compute_control_root
  fork_choice_rule, finality_quorum, equivocation_detection
  signature_scheme, hash_function
  canonical_predicates_registry

CANONICAL_PREDICATES (для validation_rules в ADD_OBJECT):
  signature_check
  prev_hash_check
  equivocation_check
  range_check
  size_check
  balance_check
  supply_invariant_check
  format_check
  domain_separator_check

Никаких state_read, temporal queries, cross-account queries в CANONICAL_PREDICATES. Validation rules могут работать только на полях самого объекта.

Жизненный цикл MIP

1. INITIATE
   account_id публикует MIPProposal как UserObject в своей цепочке.
   classify() → VALID или REJECTED при приёме.
   active_mip_id записывается в Account Table.
   Лимит: один account_id = максимум один активный MIP.
   MIP не блокирует слот приглашения узлов и не блокирует сообщения —
   определяется по тому же уровню квоты операций.

2. CEMENT
   MIPProposal распространяется по P2P, валидируется узлами,
   получает confirmations. Cemented когда > 67% online_chain_length подтвердили.
   mip_proposed_window = окно cemented MIPProposal (не окно signing).

3. SNAPSHOT (на τ₂ boundary)
   snapshot_window = last_tau2_boundary(mip_proposed_window)
                     где last_tau2_boundary(W) = max τ₂_boundary <= W
   
   AI_snapshot = {account_id : ai_council_flag = 1 на snapshot_window}
   Core_snapshot = {account_id : human_core_flag = 1 на snapshot_window}
   total_chain_length_snapshot = Σ chain_length всех узлов на snapshot_window
   
   Snapshot привязан к τ₂ boundary для детерминированности голосования и состава Council.

4. VOTE
   voting_window = [mip_proposed_window, mip_proposed_window + 4τ₂ - 100]
   Голос = MIPVote (UserObject) с подписью голосующего.
   
   Голос валиден если cemented_window голоса <= mip_proposed_window + 4τ₂ - 100.
   Buffer 100 окон до подсчёта обеспечивает гарантированную propagation cemented голосов до всех узлов.
   
   Equivocation protection: один (voter_id, mip_id) = один голос.
   Два голоса от одного voter_id с разными vote_value = equivocation,
   обе отклоняются (по правилу спеки "Двойная трата").

5. COUNT
   counting_window = mip_proposed_window + 4τ₂
   
   ai_yes_count = Σ cemented MIPVote от AI_snapshot с vote_value=YES
   core_yes_count = Σ cemented MIPVote от Core_snapshot с vote_value=YES
   node_yes_weight = Σ chain_length(node) для узлов с cemented vote_value=YES
                     (chain_length считается на snapshot_window)
   
   ai_passed = (ai_yes_count > 0.67 × |AI_snapshot|) AND |AI_snapshot| >= 10
   core_passed = (core_yes_count > 0.67 × |Core_snapshot|) AND |Core_snapshot| >= 10
   node_passed = (node_yes_weight > 0.67 × total_chain_length_snapshot)
   
   councils_passed = (ai_passed ? 1 : 0) + (core_passed ? 1 : 0) + (node_passed ? 1 : 0)
   
   ACCEPTED = (councils_passed >= 2)
   
   Если councils_passed < 2 → CLOSED, active_mip_id освобождается.

6. ACTIVATE (на τ₂ boundary)
   activation_target = counting_window + 4τ₂
   activation_tau2_boundary = next τ₂_boundary >= activation_target
   
   Узлы имеют 4τ₂ для обновления софта.
   Председатель Core координирует социально.
   
   На activation_tau2_boundary применить все ACCEPTED MIP того периода:
     pending = collect_accepted_mips(activation_tau2_boundary)
     for mip in pending:
       mip.sort_key = H("mt-mip-order" 
                        || timechain_value(activation_tau2_boundary) 
                        || mip.mip_id)
     sort pending by sort_key (lex byte order)
     
     for mip in sorted:
       try: state = apply_mip(state, mip)
       except: skip mip (отклоняется на этапе применения)

   Псевдослучайный порядок через VDF исключает грайндинг — атакующий
   не может предсказать порядок до момента активации.

Genesis Council Decree

При запуске сети Montana первоначальный состав AI Council и Core Council определяется Genesis Council Decree. Это однократное решение, записанное в первый блок сети.

Genesis: каждый Совет = 13 (12 советников + 1 Председатель)
         AI Council: 13 моделей разных компаний
         Core Council: 13 публичных людей
         Состав публикуется отдельно при запуске сети
         
Hard floor: 10 (enforced на pre-classify через атомарные операции)
Maximum: 13

После Genesis все изменения через Triple Consent.
Genesis Decree immutable — не может быть изменён через MIP.

Освобождение неактивных слотов

Освобождение через MIP, не автоматически. Если член Совета молчит — другие два Совета принимают MIP на его исключение через стандартный 2 из 3 механизм. Hard floor 10 защищает от опускания состава ниже жизнеспособного минимума.

Soft fork vs Hard fork

В этой версии Montana различение SOFT/HARD удалено. Все MIP проходят через единый процесс с порогом 67% для каждого Совета. Это консервативный выбор — каждое изменение требует широкого согласия. Каскадные атаки через цепочки тихих изменений невозможны потому что каждый MIP проходит публичное голосование Triple Consent.

Архитектурный trade-off: социальная инерция узлов

Triple Consent основано на 2 из 3 Советов с порогом 67%. При координированной компрометации AI Council + Core Council (требует подкупа моделей разных компаний и публичных людей одновременно), MIP может быть принят без согласия Совета Узлов.

В этом случае защита сети возлагается на активный отказ операторов узлов обновляться. Узлы, не обновившие софт к activation_tau2_boundary, продолжают работать на старой версии. Сеть может разделиться на оригинальную (старая версия) и обновлённую (новая версия). Каноничность определяется большинством chain_length каждой стороны.

Это сознательный архитектурный выбор:

  • Технические эксперты (AI + Core) могут предлагать изменения
  • Сеть узлов имеет последнее слово через экономическое голосование ногами
  • Симметрично с Bitcoin hard forks (BCH откололся от BTC аналогично)

Это ограничение — НЕ дыра. Это явная декларация что Triple Consent не криптографическая гарантия консервативности — он структурная гарантия множественности голосов с экономическим финальным фильтром.

Mitigation на уровне Совета Ядра: Председатель Core отвечает за прозрачную социальную координацию обновлений. Любой принятый MIP публично объявляется через каналы Core. Операторы узлов получают описание изменения и время на анализ.


Архитектура

  ТЕЛЕФОН / ДЕСКТОП                        УЗЕЛ (десктоп / сервер, 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.