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

444 KiB
Raw Permalink Blame History

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

Версия: 30.10.0 (2026-04-23)


Монтана даёт человеку цифровую собственность в мире, где всё арендуется.

Твой ключ — твоя идентичность. Твой узел — твоё хранилище. Твоё время работы — твои монеты. Твоё присутствие — твой вес. Твой агент — твоё расширение.

Одна сид-фраза. Полный контроль. Постквантовая криптография на десятилетия вперёд.

Не приватность. Не децентрализация. Не криптовалюта. Не мессенджер. Цифровая собственность.


Определение

Монтана — персональный пиринговый интернет на протоколе канонического порядка событий. Защищённое хранение данных, приватная связь и валюта Монтана — на узле пользователя.

Протокол Монтаны — фундамент персонального интернета. Сеть независимых узлов, каждый из которых ведёт собственный ход вычислений с проверяемой задержкой — цепочку шагов, которую нельзя ускорить, и результат которой любой участник может пересчитать и проверить. Все узлы через консенсус собирают свои ходы в единую каноническую последовательность событий. Вес узла в этом консенсусе есть длительность его непрерывного присутствия в сети. Каждое канонически зарегистрированное окно порождает постоянную награду в тринадцать Монтан плюс затухающую начальную надбавку, исчезающую за пять эпох.

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

Три проблемы доверия

Монтана решает три проблемы, каждая без участия третьей стороны:

  • Доверие ко времени. Протокол производит каноническую последовательность событий без внешних источников. Решено протокольным слоем: ход вычислений с проверяемой задержкой, консенсус, окна времени.
  • Доверие к хранению. Данные пользователя хранятся на его узле. Протокол предоставляет фундамент: идентичность аккаунта, фиксация содержимого в виде хэша размером тридцать два байта с привязкой к окну на всю жизнь сети, стимул держать узел постоянно в сети (лотерея, валюта Монтана). Хранение, шифрование, индексация — клиентский слой.
  • Доверие к коммуникации. Связь между пользователями идёт через их узлы, без центрального посредника. Протокол предоставляет: пиринговую сеть, идентичность, постквантовое шифрование. Мессенджер, поиск контактов, профили — клиентский слой.

Четыре слоя персонального интернета

Протокол и клиентский слой вместе образуют четыре слоя:

1. Агент-посредник. ИИ-агент (Юнона) действует строго от имени пользователя. Фильтрует и приоритизирует информацию по критериям владельца, не по алгоритмам платформы. Может ходить во внешний интернет, собирать данные, но решения о фильтрации принадлежат человеку. Реализация: полностью клиентский слой. Полная спецификация агента — архитектура изоляции, уровни разрешений, модель угроз, среда выполнения языковой модели, делегирование подписи, журнал действий — находится в спецификации приложения Монтаны. Спецификация протокола намеренно не содержит этих деталей: агент — механизм прикладного уровня, протокол не знает о его существовании и не различает операцию, подписанную вручную, и операцию, подписанную через агента.

2. Локальное хранилище знаний. Всё что пользователь читал, сохранял, получал — индексировано, доступно для поиска, хранится на его узле. Не на серверах корпорации. Контекст накапливается со временем — персональная база знаний. Протокол фиксирует факт существования (хэш размером тридцать два байта с привязкой к окну времени). Содержание — на узле владельца, зашифровано его ключом. Реализация: протокол даёт фундамент (фиксация хэша, идентичность, ключевая инфраструктура) — это описано в данной спецификации. Клиентская часть — формат локального хранилища, шифрование содержимого ключом владельца, индексация, полнотекстовый поиск, структура базы знаний — находится в спецификации приложения Монтаны и здесь не описана.

3. Управление вниманием. Персональный интернет не максимизирует время пользователя в системе, а минимизирует его. Дал нужное — отпустил. Нет алгоритмической ленты, нет рекламы, нет метрик вовлечения, нет автоматического воспроизведения. Бизнес-модель Монтаны — эмиссия через узлы, не торговля вниманием. Реализация: экономическая конструкция протокола устраняет стимул торговать вниманием — эмиссия через лотерею узлов, не реклама и не подписка (см. разделы о валюте Монтана и о лотерее). Конкретные решения по интерфейсу — отсутствие алгоритмической ленты, формат уведомлений, политика автовоспроизведения, устройство чата и каналов — находятся в спецификации приложения Монтаны и здесь не описаны.

4. Контроль данных. Пользователь решает какие данные о нём существуют и кто имеет доступ. Не «политика конфиденциальности на сорок страниц», а технические механизмы: локальное шифрование на узле, выборочное предоставление доступа через адресное постквантовое шифрование, необязательная публикация профиля и контактов. Балансы публичны по дизайну ([I-2]). Всё остальное — решение владельца. Реализация: протокол даёт инвариант [I-2] (открытость финансового слоя), фиксацию хэша без содержания, идентичность и постквантовую ключевую инфраструктуру — это описано в данной спецификации. Клиентская часть — формат локального шифрования, избирательное раскрытие, управление приватностью в интерфейсе, формат публикации профиля и контактов — находится в спецификации приложения Монтаны и здесь не описана.

Клиентский интерфейс

Четыре слоя персонального интернета доступны массовому пользователю через клиентское приложение. Эталонная реализация — приложение Монтаны — использует чат-центрированный интерфейс как наиболее доступную точку входа: переписка с контактами, платежи к тем же контактам, личный контент и взаимодействие с агентом объединены в одной точке без переключения между приложениями. Конкретные решения по интерфейсу, его устройство и интеграция с каждой платформой описаны в спецификации приложения Монтаны.

Альтернативные клиенты — приложения командной строки, десктопные приложения, специализированные интерфейсы для людей с особыми потребностями — допустимы и равноправны на уровне протокола. Выбор клиента не влияет на протокольные свойства аккаунта: сид-фраза, идентификатор аккаунта и накопленная длина цепи аккаунта принадлежат пользователю, не конкретному клиенту (см. «Два пути участия» ниже).

Архитектурное условие

Монтана = протокол + клиентский слой + сеть узлов.

  • Без протокола — нет канонического времени, нет идентичности, нет фиксации данных, нет стимула. Клиентский слой не на чем строить.
  • Без клиентского слоя — протокол производит первоэлементы, но человек не может ими воспользоваться. Нет приложения — нет продукта.
  • Без узлов в сети — протокол не обрабатывает события.

Два пути участия

Монтана формализует Лестницу суверенности — двухшаговую экономическую модель:

Шаг 0: Пользователь аккаунта (вход в сеть). Имеет ключевую пару аккаунта (в смартфоне, аппаратном кошельке, любом клиенте). Подключается к узлу в сети — собственному или чужому — через транспортный слой сети (уровень 3). Запись аккаунта появляется в таблице аккаунтов при первом входящем переводе активации от спонсора; самостоятельное создание не требуется. Использует сеть: переводы, фиксация данных, покупка никнейма, премиум-подписки, пополнение кредитов. Все платные услуги идут в сжигание через [I-13]. Заработок на уровне протокола отсутствует. Заработок вне протокола возможен через экономику создателей (платные каналы, подписки) — см. спецификацию приложения Монтаны. Барьер: смартфон + первый входящий перевод (любой объём не меньше минимального баланса аккаунта от существующего аккаунта).

Шаг 1: Оператор узла (заработок). Держит свой узел круглосуточно + аккаунт оператора, привязанный к узлу (+ опционально другие личные аккаунты). Максимальная суверенность: данные на своём железе, полное участие в консенсусе, заработок через лотерею узлов (награда за выигранное окно, см. раздел о валюте Монтана). Барьер: обычное оборудование (минимум одно ядро), круглосуточное время работы, канал связи, залог регистрации узла.

Путь роста. Пользователь может начать как держатель аккаунта без узла, подключаясь через клиентское приложение (см. «Клиентский интерфейс» выше) — эталонное приложение Монтаны использует чат-центрированный интерфейс, альтернативные клиенты допустимы. Позже — развернуть свой узел без потери истории цепи аккаунта. Идентификатор аккаунта и все накопленные операции переносятся — ключ принадлежит пользователю, не узлу.

Компромиссы пути без собственного узла (явно):

  • Все финансовые операции (перевод, фиксация данных, покупка никнейма, смена ключа, закрытие аккаунта)
  • ✓ Данные приложения: хэш публичен, содержимое зашифровано ключом пользователя — хостящий узел не видит содержимого
  • — Эмиссии на уровне протокола нет (награда идёт операторам узлов; см. [I-13] дефляционный сток)
  • — Утечка метаданных: хостящий узел видит сетевой адрес и время операций пользователя. Частично смягчено через протокол скрытия первоисточника (первый пересылочный узел маскирует источник)
  • — Риск цензуры: хостящий узел может отказать в пересылке сообщений. Пользователь меняет хостинг — через другое приложение, общественный узел или общественную инфраструктуру

Экономика хостинга. Оператор узла предоставляет инфраструктуру для цепей аккаунтов своих пользователей. Записи аккаунтов реплицируются всей сетью (часть общего состояния консенсуса, не локальное хранилище оператора); оператор выступает посредником по пересылке и подтверждающим для операций размещённых пользователей. Стимул оператора: растущая пользовательская база на его узле → больше закреплённых операций через его узлы → эти операции попадают в его пакет подтверждений → активная длина цепи растёт → больше шансов выиграть лотерею узлов → больше Монтан. Бизнес-модель приложения = эмиссия через узлы, не плата с пользователя. Экономика создателей (платные каналы, премиум-подписки) — дополнительный внепротокольный слой дохода для разработчиков приложений.

Монтана делает оба пути посильными: свой узел — обычное оборудование (минимум одно ядро), открытое ПО, окупаемость через лотерею. Путь без узла — любой смартфон, подключение через приложение. Решение где на этой шкале быть — за пользователем; Лестница суверенности формализует естественный переход от использования сети к её обслуживанию.

Три первоэлемента протокола

Протокол производит три первоэлемента:

  • Каноническое время — согласованный всеми узлами порядок событий, производимый ходом протокола; вес узла в сети есть длительность его непрерывного присутствия в этом порядке.
  • Передача ценности — переводы между аккаунтами, открытые балансы.
  • Фиксация данных — привязка хэша размером тридцать два байта к окну времени, сохраняется навсегда.

Всё за пределами этих трёх первоэлементов — хранение данных, коммуникация, агенты, индексация, интерфейсы — реализуется клиентским слоем поверх протокола. Протокол — летопись, бухгалтерия и нотариат. Серверов нет — каждый узел сети равноправен, принадлежит своему оператору и работает на своём оборудовании.

Консенсус: Доказательство времени — общая цепь времени (определённое число последовательных хэш-шагов образуют одно окно). Цепь узла — последовательность закреплённых в консенсусе пакетов подтверждений от узла (доказательство присутствия). Цепь аккаунта — счётчик окон активности аккаунта. Таблица аккаунтов — состояние счёта. Влияние узла равно длине его цепи — количеству окон, в которых узел криптографически доказал своё присутствие. Протокол и есть структура отношений между событиями, оцифрованная и криптографически верифицируемая. Один узел = одно ядро процессора.

Начальное окно — символическое нулевое окно. Перевод номера окна в любые внешние шкалы времени является задачей клиентского слоя.

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

Эволюция протокола: открытые предложения к улучшению публикуются как рекомендации, реализации выпускают новые версии, операторы узлов выбирают какую версию запускать. Разрешение расхождений цепей детерминировано через большинство по длине цепи. Управление на уровне протокола отсутствует. См. раздел «Эволюция протокола».


Три решённые проблемы

1. Каноническая временная координата

Проблема. Существующие системы времени смешивают два разных уровня — канонический порядок событий и измерение длительности. Первый является структурным свойством самой последовательности; второй — производной интерпретацией, требующей выбора часов и внешней шкалы. Без доверенного источника система может канонизировать порядок, но не длительность; длительность неканонизируема внутри протокола без внешней шкалы.

Решение. Монтана задаёт реляционную временную структуру — сеть независимых узлов. Каждый узел выполняет последовательные вычисления с проверяемой задержкой и независимо воспроизводит единый канонический порядок событий из общих входов протокола. Последовательное хэширование детерминировано: результат однозначен и может быть проверен любым участником.

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

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

  • Монотонность. Номер окна строго возрастает. Ход вычислений с проверяемой задержкой последователен — каждый хэш зависит от предыдущего. Канонический порядок событий однозначен.
  • Однозначность. Все честные узлы согласны до бита на структуру событий — номер окна, метка времени окна, корень состояния. Каждое поле общего состояния объективно вычислимо всеми узлами.
  • Проверяемость. Любой может пересчитать ход и проверить каждое событие последовательности.
  • Независимость. Каждый узел считает самостоятельно, опираясь только на общие входные данные протокола.

Монтана и внешние системы измерения времени — системы разных типов. Внешние системы измеряют физическое время через внешние источники. Монтана производит каноническую последовательность событий через собственный ход и консенсус.

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

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

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

Монтана намеренно не встраивает в консенсус покупаемые ресурсы как носители веса. Вычислительная мощность, капитал и хранилище могут быть условиями запуска и эксплуатации узла, но не мерами власти в консенсусе. Консенсусный вес зарабатывается только последовательным участием во времени и потому не может быть приобретён как готовый актив — его источник всегда внутри сети. Тем самым подтверждённое присутствие во времени является базовым ресурсом консенсуса, а рыночные ресурсы — внешними условиями эксплуатации, не конвертируемыми напрямую во влияние.

Свойства.

  • При равной истории подтверждённого участия узлы имеют одинаковый консенсусный вес независимо от капитала оператора.
  • Капитал может повысить надёжность эксплуатации, но не может ретроактивно купить уже прошедшее время участия.
  • Атака на консенсус не сводится к разовой покупке внешнего ресурса; она требует накопления подтверждённого присутствия внутри самой сети.

3. Поокнная эмиссия

Проблема. Существующие денежные политики смешивают два разных решения — кто определяет эмиссию и должна ли денежная масса иметь заранее ожидаемый потолок. Дискреционная эмиссия делает предложение функцией человеческих решений; фиксированный потолок делает редкость предметом ожидания и структурно смещает поведение от обмена к накоплению. Нейтральная денежная политика требует отказа и от дискреции, и от конечного лимита предложения.

Решение. Монтана задаёт поокнную эмиссию из двух компонентов: постоянной базовой награды и конечной начальной надбавки, затухающей по расписанию. Для каждого окна W награда определяется формулой reward(W) = R_baseline + bonus(W). Базовая часть R_baseline постоянна на всём горизонте протокола; bonus(W) убывает по заранее заданному закону и за конечное число эпох становится нулём. Эмиссионное правило зависит только от номера окна и констант, зафиксированных в Указе Генезиса.

Монтана намеренно не использует ни дискрецию эмитента, ни конечный потолок предложения как основу денежной политики. Эмиссия не является ни политическим решением, ни функцией рыночных ожиданий; она канонически выводится из номера окна. Тем самым поокнная эмиссия является базовым каноническим свойством денежной политики протокола. Внешняя ценность монеты — её рыночная цена, покупательная способность и курс к другим шкалам ценности — остаётся внешней производной интерпретацией.

Свойства.

  • Награда reward(W) определена для любого окна заранее и вычислима одинаково всеми участниками.
  • Ни один актор не может ускорить, замедлить или перенаправить эмиссионное расписание своим решением.
  • Начальная надбавка конечна, а базовая эмиссия постоянна и бессрочна.

Формула эмиссии (канонический вид, nɈ):

reward_nɈ(W) = R_BASELINE_nɈ + bonus_nɈ(W)

R_BASELINE_nɈ  = 13_000_000_000
BOOTSTRAP_R0_nɈ = 16_000_000_000
BOOTSTRAP_H     = 26 × τ₂_windows = 524_160   (окон в одной эпохе начальной надбавки)
BOOTSTRAP_EPOCHS = 5                           (число убывающих эпох)

shift        = floor(W / BOOTSTRAP_H)
bonus_nɈ(W)  = BOOTSTRAP_R0_nɈ >> shift     если shift < BOOTSTRAP_EPOCHS
bonus_nɈ(W)  = 0                              иначе

Расписание начальной надбавки.

Эпоха Диапазон окон bonus (Ɉ) reward (Ɉ)
0 [0, 524_160) 16 29
1 [524_160, 1_048_320) 8 21
2 [1_048_320, 1_572_480) 4 17
3 [1_572_480, 2_096_640) 2 15
4 [2_096_640, 2_620_800) 1 14
5+ ≥ 2_620_800 0 13

Потолок начальной надбавки:

BOOTSTRAP_CAP_nɈ = (16+8+4+2+1) × BOOTSTRAP_H × 10⁹ = 31 × 524_160 × 10⁹ = 16_248_960_000_000_000 nɈ
                 = 16_248_960 Ɉ

Сумма всех выплат начальной надбавки за всю историю протокола тождественно равна BOOTSTRAP_CAP. После окна W = 2_620_800 начальная надбавка не эмитируется.

Технические свойства.

  • Предложение монеты после окна W: supply(W) = R_BASELINE × (W + 1) + bootstrap_cumulative(W), где bootstrap_cumulative(W) ≤ BOOTSTRAP_CAP — монотонно неубывающая функция, достигающая BOOTSTRAP_CAP на эпохе 5 и остающаяся на ней далее
  • Постоянная эмиссия линейна по номеру окна — инфляция монотонно убывает и асимптотически стремится к нулю
  • Начальная надбавка конечна, жёстко ограничена сверху значением BOOTSTRAP_CAP, проверяется как инвариант
  • Эмиссия не контролируется ни одним участником, комитетом или голосованием
  • Денежная политика полностью определена константами R_BASELINE, BOOTSTRAP_R0, BOOTSTRAP_H, BOOTSTRAP_EPOCHS в Указе Генезиса и не может быть изменена после генезиса
  • Физическая скорость выпуска в секундах Международной системы единиц определяется скоростью оборудования сети и остаётся свойством клиентского слоя, вне области консенсуса

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

Три решённые проблемы порождают уникальную возможность. Любой документ, событие, состояние может быть записано в Монтане с математически доказуемой привязкой к канонической позиции в последовательности событий (номеру окна). Привязка хэша размером тридцать два байта к окну — навсегда. Монтана — не блокчейн с функцией проставления временной метки. Монтана — система отсчёта времени с функцией передачи ценности. Внешние системы могут наблюдать последовательность окон Монтаны и строить собственные переводы в свои локальные стандарты — этот перевод является задачей наблюдателя, не протокола.

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


Глобальные инварианты протокола

Глобальный инвариант — свойство, которое протокол обязан сохранять во всех своих компонентах. Нарушение в одной части = нарушение во всём протоколе. Глобальные инварианты не имеют исключений и не подлежат локальному trade-off.

[I-1] Постквантовая безопасность. Все криптографические примитивы устойчивы к квантовому компьютеру. Допустимо: SHA-256 (Grover ослабляет до 128-bit, приемлемо), FN-DSA-512 (Falcon, lattice), ML-KEM (Kyber), ML-DSA (Dilithium), STARK (hash-based ZK), lattice commitments. Запрещено: ECDLP, RSA, классический Diffie-Hellman, Pedersen commitments на эллиптических кривых, Bulletproofs, Schnorr/EdDSA.

[I-2] Открытость финансового слоя. Балансы, суммы переводов, отправители, получатели — публичны. Никакого криптографического сокрытия на уровне протокола. См. «Модель приватности».

[I-3] Детерминизм consensus state. Любое состояние, входящее в consensus root, объективно вычислимо одинаково всеми узлами.

Corollary I-3.a. Любой механизм, результат которого в consensus state или в protocol-level behavior (mempool prioritization, gossip ordering, fork-choice, peer scoring) зависит от измерения физического мира — астрономического, геофизического, атомного, биологического или любого другого — отклоняется по нарушению I-3. Corollary применяется независимо от точности модели измерения.

[I-4] Независимость TimeChain от Account state. TimeChain продвигается из canonical inputs без зависимости от состояния Account Table. Зависимости однонаправленные: TimeChain → NodeChain (presence tracking) → AccountChain → AccountTable.

[I-5] Реализуемость без специализированного оборудования. Все примитивы имеют production-ready open-source реализации, работающие на commodity CPU узла без TEE, без обязательного GPU, без обязательного ASIC.

[I-6] Регуляторная совместимость. Протокол опирается на механизмы, совместимые с FATF/AML/MiCA/ETF. Запрещено: privacy mixers на уровне протокола, anonymous addresses, hidden flows, ring signatures, stealth addresses.

[I-7] Минимальная криптографическая поверхность. Каждый новый примитив требует обоснования закрытием конкретного механизма. Дублирование функциональности через два разных примитива запрещено.

[I-8] Network-Bound Unpredictability of Consensus Seeds. Любая hash-композиция в consensus-critical output (lottery endpoint, selection sort_key, admission ordering, weight distribution, emission, ranking) обязана содержать хотя бы один canonical & unpredictable-offline компонент — вычислимый детерминистически ВСЕМИ честными узлами ТОЛЬКО после фиксации cemented state с подписями honest participants. Canonical-predictable-offline (VDF output, timestamps, state counters) недостаточны как единственный источник non-grindability. Реализация: cemented_bundle_aggregate(W-k), future cemented signatures, honest-participant-signed future state. [I-8] нарушение = автоматический блокер mainnet.

[I-9] Bit-exact deterministic arithmetic for consensus formulas. Любая formula output которой прямо ИЛИ через transitive цепочку попадает в consensus-critical output обязана удовлетворять трём требованиям: (1) binding integer specification в спеке (u8..u256, fixed-point Q-format, integer div с явным rounding direction); (2) unsigned operands (signed arithmetic запрещена в consensus formulas); (3) минимум 3 test vectors в спеке на formula (typical, boundary, edge). Real-valued форма (ln, exp, %, ×0.67) допустима ТОЛЬКО как commentary; authoritative — integer. Запрещено: f32/f64 в consensus коде, rounding без direction, real-valued без parallel integer form. [I-9] — procedural enforcement [I-3] для numerical formulas. Статусы: «закрыто» (integer spec + test vectors), «conformance pending» (integer spec, vectors defer в следующий patch), «нарушение» (real без integer) = автоматический блокер mainnet.

[I-10] Single Source of Truth (SSOT). Любая значимая сущность протокола существует ровно в одном месте — одном authoritative определении. Все остальные упоминания ссылаются на источник, не дублируют его содержимое.

Относится к:

  • Версия спеки — только в header документа (строка **Версия:** X.Y.Z на второй строке). Нигде больше в теле спеки версия не указывается. Inline ссылки на версию (например в conformance pending labels) допустимы только когда явно маркируют связанное состояние: conformance pending v<spec-version-at-time-of-status>. При bump спеки все такие labels обновляются синхронно либо status закрывается.
  • Имя файла спеки — синхронно с header: Montana vX.Y.Z.md. При bump файл переименовывается.
  • Протокольные константы (D₀, τ₂_windows, R_BASELINE, BOOTSTRAP_R0, BOOTSTRAP_H, BOOTSTRAP_EPOCHS, BOOTSTRAP_CAP, τ₁, quorum, confirmation_threshold_divisor, admission_divisor, selection_interval, candidate_expiry_windows, pruning_idle_windows, vdf_entry_windows, adaptive_vdf_threshold, adaptive_vdf_multiplier, participation_dead_zone_low/high, d_adjustment_rate_num/den, etc.) — только в Genesis Decree protocol_params layout. Все остальные разделы ссылаются на эти значения по имени, не повторяют численное значение. Inline числа в прозе допустимы только как comment/intuition (не authoritative).
  • Размеры криптопримитивов (897/1281/666 для FN-DSA-512 public/secret/signature, 1312 для ML-KEM-768 public, etc.) — только в разделе «Криптографические примитивы». Все layout blocks ссылаются по имени схемы (FN-DSA-512 pubkey = 897B) через определение там.
  • Domain separators ("mt-op", "mt-proposal", "mt-bundle", "mt-vdf-reveal", "mt-lottery", "mt-bc-aggregate", "mt-selection", etc.) — только в «Consensus encoding layer», раздел «Domain separators registry». Все формулы ссылаются на имя domain из registry, не дублируют literal string с новым именем.
  • Формулы (одна формула = одно authoritative определение). Если формула используется в нескольких местах — одно место канонично, остальные ссылаются.
  • Структуры объектов (layout Proposal header, BundledConfirmation, VDF_Reveal, NodeRegistration, UserObjects, Account/Node/Candidate records) — одно authoritative layout block + одна секция **Инварианты X:** (per Gate 13). Illustrative ASCII-схемы НЕ содержат type annotations (per Gate 13c — раздел роли архитектора).
  • Algorithm description (Selection event, Settle window, Pruning procedure, Fast sync, etc.) — один раздел с полным описанием. Краткие упоминания в других разделах явно ссылаются («см. раздел X»), не переписывают.

Правила применения:

  • При введении новой сущности — сначала проверить существование authoritative определения. Если есть — ссылаться. Если нет — создать в логически правильном разделе (тот что владеет сущностью по domain).
  • При обнаружении дублирования — немедленный refactor: один источник сохраняется, другие превращаются в pointer-ы (см. раздел X). Принцип «сначала разрешить дубликат, потом продолжить работу» (pre-edit duplicate scan).
  • Ссылка вместо копии — «baseline emission = R_BASELINE (см. Genesis Decree)», не «baseline emission = 13 000 000 000 nɈ» повторно. Для документов — ссылка на раздел, не повторение значения.
  • Единственное исключение — inline commentary/intuition без binding claim: «примерно 13 Ɉ baseline» в прозе как объяснение масштаба. Такие упоминания не normative и не создают дрifт когда authoritative value меняется, потому что явно маркированы как illustrative.

[I-10] нарушение = автоматический finding класса type/value-divergence, severity определяется типом дубликата:

  • Consensus-critical сущность дублирована (формула, константа, layout, domain separator) → блокер mainnet (гарантированный silent drift при evolution спеки, cross-implementation fork)
  • Не-consensus сущность дублирована (документация, prose summary) → finding, severity средний (document hygiene, читатель-implementer получает неоднозначный signal)

[I-10] — meta-level procedural enforcement против спецификационного дрifта. Родственные gates: Gate 13 (exhaustive invariants per signed object), Gate 13c (type annotations только в authoritative). [I-10] покрывает более широкий scope — любую значимую сущность, не только type annotations.

[I-11] Уникальность никнеймов. Отображение nickname → account_id биективно на момент любого consensus state. Для любых двух разных account_id их nickname (если присутствуют) различны. Один account_id владеет не более чем одним никнеймом. Никнейм, записанный в AccountRecord.nickname, immutable на всё время существования аккаунта; не передаётся, не освобождается, не обменивается. Потеря сид-фразы = потеря и аккаунта, и никнейма; восстановление сид-фразы = восстановление обоих.

Реализация: новые state-таблицы NicknameTable (никнейм → account_id для прямого резолва) и AuctionTable (никнейм → состояние активного аукциона). Новая операция 0x05 NicknameBid с инвариантами (см. раздел «Nickname Auction Protocol»).

Нарушение [I-11] = автоматический блокер mainnet (разные реализации резолвят @alice в разные account_id → фундаментальный консенсус-форк).

[I-12] Детерминизм аукциона никнеймов. Формула price_at(nickname, W) текущей голландской цены в окне W — детерминистическая unsigned-integer функция от (starting_price_nj, floor_price_nj, auction_start_window, W, τ₂_windows, decay_num, decay_den). Все узлы вычисляют bit-exact одно значение. Принятие новой ставки в Английской фазе определяется каноническим tie-break правилом (bid_amount_nj убыв., op_hash лекс. возр.) при равных суммах.

Реализация: integer form с binding test vectors в разделе «Nickname Auction Protocol», инвариант покрывает все ветви логики (Dutch decay, English extension, anti-sniping, tie-break). [I-12] — частный случай [I-3] и [I-9] для механики никнейм-аукциона.

Нарушение [I-12] = автоматический блокер mainnet.

[I-13] Deflationary sink. Все платные protocol-level услуги направляют свою выручку в burn (уменьшение общего предложения Монтана через вычитание supply_nj). Никаких treasury-фондов, rewards pool, DAO-казны, резервных накопительных структур на уровне протокола.

Покрывает: покупку никнеймов (NicknameBid), покупку кредитов для услуг (PurchaseCredits), Anchor-плату за контент свыше первого килобайта, подписки на premium-профиль, любые будущие платные protocol-level услуги. Единственный направленный поток Монтана:

Эмиссия (баланс winner'а окна) ─► обращение ─► платёж за услугу ─► burn
   constant rate per window          ↓            activity-driven
                                  market

Обоснование: константная baseline-эмиссия (13 Ɉ/окно forever) без противотока burn ведёт к бесконечной инфляции. Burn через активность сети — противоток, стабилизирующий макроэкономику. Активная сеть → высокий burn → дефляция → ценность Ɉ растёт → реальная доходность оператора растёт даже при константной номинальной эмиссии.

Нарушение [I-13] = любое введение pool/treasury/DAO-фонда как destination для protocol-level выручки отвергается на архитектурном уровне. Исключение: creator economy (платные каналы) может разделять платёж пользователя на долю создателю + долю burn; явное разделение документируется в соответствующем разделе, не создаёт общего pool'а.

[I-14] State lifecycle & bloat resistance. Каждая persistent запись в consensus state обязана удовлетворять хотя бы одному из трёх требований:

  1. Cost-based barrier. Стоимость создания записи (fee, burn, stake deposit) integer-specified в Genesis Decree, достаточная чтобы cost-per-byte записи ≥ реалистичная cost-per-byte storage honest узла × safety margin ≥3×. Выручка направляется в burn (supply_nj -= cost_nj) для совместимости с [I-13].

  2. Lifecycle bound. При явно заданных условиях запись удаляется из persistent state. Допустимые варианты:

    • Balance-based. Запись удаляется когда связанный баланс < MIN_*_BALANCE_NJ (rent-exempt threshold, integer-specified в Genesis Decree).
    • Temporal. Запись удаляется после N_INACTIVE_*_WINDOWS окон отсутствия операций на записи.
    • Explicit removal operation. Отдельный opcode явного удаления с reward за cleanup (sweep incentive); reward строго меньше storage cost записи чтобы не создать противоположный стимул.
  3. Hard quota. Явный upper bound на общее количество записей либо per creator (например «≤1 никнейм per аккаунт»), либо глобальный (например «≤1024 одновременных аукционов»). Integer-specified в Genesis Decree, enforceable в apply_proposal.

Persistent запись создаваемая через legitimate операцию без одного из трёх механизмов = блокер mainnet. Класс атаки — slow bloat: атакующий выполняет серию legitimate операций с суммарным ущербом через раздутие state. Защита либо через экономический барьер (путь 1), либо через алгоритмическое ограничение роста (путь 2 или 3).

Applicable к: AccountRecord, NicknameTable, AuctionTable, Anchor records, NodeTable, Candidate Pool, любой таблице consensus state которая может расти через user-driven операции. При закрытии каждого механизма в карточке явно указывается применённый путь ([I-14].1 / [I-14].2 / [I-14].3 / комбинация).

Обоснование: Sybil-защита через stake-weighting ([I-8] randomness + weighted lottery) закрывает голосование и лотерею, но не адресует resource consumption. Миллион аккаунтов не меняет распределения лотерейных весов, но занимает ×миллион AccountRecord в state trie. Cost-based rationality без lifecycle недостаточна при волатильности TC (падение цены × 10 делает атаку affordable). Lifecycle без cost открыт для бесплатного spam в рамках quota. Комбинация закрывает оба вектора.

Conformance audit существующих persistent tables:

Таблица Защитный путь Статус
AccountRecord [I-15] time-based: activation cooldown 1 TransferActivation per sender per τ₂ (через поле last_activation_window) + existing 1-op-per-τ₁ rate-limit + pruning (balance == 0 + 4τ₂) закрыто
NicknameTable [I-14].3 quota (≤1 nickname per account) + [I-13] NicknameBid burn закрыто
AuctionTable [I-14].2 temporal (auction expiry τ₂) + [I-13] bid burn закрыто
Anchor records [I-15] time-based: existing 1-op-per-τ₁ rate-limit + amortized через AccountChain TTL (dormant account prune убирает все Anchor вместе с аккаунтом) закрыто
NodeTable crypto-stake (NodeRegistration deposit) + [I-14].2 temporal (inactivity prune 8τ₂) закрыто
Candidate Pool [I-14].2 temporal (expiry selection window) закрыто

Все persistent state tables закрыты. [I-14] compliance полный.

Нарушение [I-14] = автоматический блокер mainnet.

[I-15] Time-based scarcity для anti-spam барьеров. Все защиты от спама, раздутия состояния, Sybil на ресурсы (fan-out на множество identities, dust-creation, keepalive удержание пустых записей) конструируются через время, не через деньги. Допустимые паттерны — временные окна (τ₁/τ₂), частоты операций, TTL через активность, chain_length-требования, seniority-gating. Денежные барьеры (комиссии, fees, rent, activation burn) для anti-spam целей запрещены.

Обоснование архитектуры: Montana — протокол без комиссий (fee-less). Дефицитный ресурс протокола — время (VDF-непрерывность, τ-окна, chain_length узла, activity аккаунта). Этот time-market встроен в консенсус как единственный объективный дефицит. Защиты через существующий дефицит (а) не требуют cost derivation с учётом волатильности цены TC, (б) симметричны для всех участников независимо от TC-holdings, (в) не создают регуляторную поверхность «платы за услугу», (г) не дублируют логику существующих time-based ограничителей консенсуса.

Разрешённые time-based паттерны:

  • Rate-per-identity: одна операция на аккаунт за τ₁ (существующее правило).
  • TTL через активность: запись удаляется после N окон без cemented операций (существующий pruning 4τ₂).
  • Cooldown активации: sender ограничен K активаций за τ₂ (per-account counter в state record).
  • Chain-length requirement: право на действие требует sender.account_chain_length_snapshot >= threshold_windows.
  • Seniority gating: вес или приоритет пропорционален chain_length (лотерея узлов, wait period кандидатов).

Запреты [I-15]:

  • Комиссия / fee / rent / activation burn как anti-spam защита.
  • Cost-based derivation констант для anti-spam с учётом TC price volatility.
  • MIN_*_BALANCE_NJ пороги как защита от раздутия state.
  • Любая привязка anti-spam защиты к номинальной стоимости TC.

Разграничение. [I-15] применяется к задачам anti-spam / anti-bloat / state scarcity. Не применяется к:

  • Allocation механизмы для конечных ресурсов: NicknameBid burn — anti-squatting при распределении уникальных имён (покрывает [I-13] deflationary sink).
  • Оплата услуг: PurchaseCredits, Anchor fees, Premium subscriptions — обмен TC на service (покрывает [I-13]).
  • Crypto-stake primitives: NODE_REGISTRATION_STAKE — Sybil-защита для validator role с stake-weighted влиянием на консенсус.

Различающий критерий: проблема «кто-то создаёт много записей потребляющих сетевые ресурсы без legitimate use» → [I-15] time-based; проблема «кто-то претендует на уникальное имя / ресурс / роль валидатора» → burn / stake / auction через [I-13].

Нарушение [I-15] = автоматический блокер mainnet для соответствующего anti-spam механизма.

Модель приватности

Протокол разделяет публичное и приватное одним принципом: consensus state — публичен, данные пользователя — за пределами протокола.

  • Публично (consensus state): балансы, суммы переводов, отправители, получатели, window_index, node_id, chain_length. Это следствие [I-2]: финансовый слой открыт для верификации.
  • В протоколе, но без содержания: Anchor содержит data_hash (32 байта). Что за этим хэшем — протоколу неизвестно.
  • За пределами протокола: данные пользователя (фото, сообщения, файлы) хранятся на узле владельца. Шифрование, формат хранения, доступ — решения клиентского слоя. Сеть не хранит, не реплицирует и не видит эти данные. Ключ шифрования — у владельца. Без ключа данные на узле — шум.

Протокол не предоставляет privacy через криптографическое сокрытие (нет ring signatures, нет hidden amounts, нет stealth addresses — [I-6]). Приватность данных обеспечивается архитектурно: данные не попадают в протокол. Протокол видит 32 байта хэша и всё.

Language firewall

В нормативном тексте спецификации Montana допустимые термины для описания протокольных объектов, счётчиков, периодов или интервалов: window, tick, epoch, cycle — определённые через window counts. Термины физического времени (second, minute, hour, day, week, month, year) применяются только в advisory контекстах клиентского слоя и в описании транспортного уровня (implementation guidance).


Montana Time

VDF — цифровой осциллятор в собственных единицах. D последовательных SHA-256 = одно окно τ₁ Montana. Число D представляет канонический объём работы, конституирующий единицу Montana Time.

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

Токен — каноническая регистрация одного окна Montana Time. Протокол производит канонические окна и регистрирует каждое из них как reward(W) Монтан (baseline 13 Ɉ + bootstrap bonus, см. раздел «Валюта Монтана»).

Определение Montana Time

montana_time(W) := W

Единственное каноническое определение времени в протоколе. Всё остальное — производные или advisory вычисления клиентского слоя.

Одно окно = D последовательных SHA-256 итераций от предыдущего canonical anchor. D фиксируется в Genesis Decree и может адаптироваться runtime-ом через participation-ratio feedback (см. раздел «Адаптация D»).

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

  • Монотонность. window_index строго возрастает. VDF последователен — каждый хэш зависит от предыдущего.
  • Детерминизм. Все честные узлы согласны bit-exact на window_index, T_r, state_root. Каждое поле consensus state объективно вычислимо всеми узлами.
  • Верифицируемость. Любой может пересчитать VDF и проверить каждое событие последовательности.
  • Независимость. Каждый узел вычисляет канон сам, опираясь только на canonical inputs протокола.

Montana и NTP/GPS/PTP — системы разных типов. NTP и GPS измеряют физическое время через внешние источники. Montana производит каноническую последовательность событий через собственную работу VDF и consensus.

Гранулярность

Атом Montana Time — одна SHA-256 итерация. Окно Montana Time — D атомов. Произвольный интервал — N окон. Все три уровня выражены в канонических числах, на которые bit-exact согласны все узлы.

Физическая длительность одной итерации зависит от hardware узла (наносекунды — десятки наносекунд на commodity CPU). Физическая длительность окна зависит от скорости железа узла и от участия сети. Физическая длительность — свойство конкретного наблюдателя, выводимое на клиентском слое.

Time Oracle

Canonical window_index каждого proposal — верифицируемая координата события. Внешние системы используют Montana Time как reference frame:

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

Перевод window_index → физическое время в любых внешних стандартах (UTC, TAI, GPS Time) является задачей клиентского слоя. Montana производит каноническую последовательность окон; внешний наблюдатель выбирает собственный метод привязки window_index к своим локальным временным единицам.

TimeChain хранится навсегда. Канонические координаты верифицируемы любым узлом в любой момент.


Криптография

Два фундаментальных примитива с разделёнными ролями:

  • SHA-256 — консенсус (TimeChain), lottery endpoints, адреса, Merkle-деревья, хэширование
  • FN-DSA-512 (Falcon-512, выбран в финальном раунде NIST PQC selection, июль 2022; forthcoming FIPS 206; reference implementation production-ready) — подписи операций аккаунтов и proposals узлов

SHA-256 обеспечивает квантовую устойчивость консенсуса: алгоритм Гровера сокращает безопасность с 256 до 128 бит. FN-DSA-512 обеспечивает математическую постквантовую устойчивость подписей на основе NTRU-решёток.

Вспомогательные композиции поверх SHA-256 — HMAC-SHA-256 (RFC 2104), PBKDF2-HMAC-SHA-256 (RFC 8018 §5.2), HKDF-Expand (RFC 5869 §2.3) — используются в client-side деривации ключей из мнемоники (см. «Ключи»). Они не вводят независимых криптографических предположений, являются стандартными композициями уже принятого SHA-256.

Для клиентского шифрования сообщений применяется ML-KEM-768 (FIPS 203) — post-quantum KEM, используется вне consensus поверхности (см. Application Layer).

Других независимых криптографических примитивов в протоколе нет — финансовый слой публичен, приватность данных обеспечивается на уровне приложений через Anchor.

Подписи — FN-DSA-512

Подпись на NTRU-решётках (Falcon-512). Stateless, многоразовая. Публичный ключ закрепляется за аккаунтом при создании и используется для всех последующих операций.

Компонент Размер
Приватный ключ 1 281B
Публичный ключ 897B
Подпись (padded) 666B

Поле suite_id в формате блока обеспечивает миграцию подписи без изменения модели состояния. Активация новой схемы требует protocol upgrade. Активная схема на момент запуска: FN-DSA-512.

Signed scope, identity и aggregation — универсальные правила

Для любого подписанного объекта протокола (UserObject, ControlObject, Proposal header, BundledConfirmation, VDF_Reveal, любой future-вводимый подписанный класс) действуют три универсальных правила.

Правило R1 — Signed scope. Каждый подписанный объект имеет canonical_bytes с полем signature последним. Сообщение, подаваемое в FN-DSA-512 sign и verify:

signed_scope(obj) = canonical_bytes(obj)[0 .. |canonical_bytes| - signature_size(signer_suite_id(obj))]

signature = FN-DSA-512.sign(sk, signed_scope(obj))
verify    = FN-DSA-512.verify(pk, signed_scope(obj), signature)

Внешний SHA-256 слой над signed_scope не применяется — Falcon использует SHAKE-256 hash-to-point внутри, дополнительное хэширование избыточно и нарушает [I-7].

signer_suite_id(obj) определён таблицей:

Класс объекта signer_suite_id
Transfer, TransferActivation, ChangeKey, Anchor, NicknameBid, PurchaseCredits, ConsumeCredits, CloseAccount AccountTable[sender].suite_id
NodeRegistration payload.candidate.suite_id
Proposal header NodeTable[proposer_node_id].suite_id
BundledConfirmation NodeTable[confirmer_node_id].suite_id
VDF_Reveal NodeTable[node_id].suite_id

signature_size(suite_id) — детерминированная функция:

suite_id Схема signature_size
1 FN-DSA-512 (padded) 666 B

Future suites — через protocol version upgrade с explicit записью в эту таблицу.

Для ChangeKey подписывает старый ключ (AccountTable[sender] до apply), не новый. new_pubkey в payload определяет ключ для проверки будущих операций, signature_size для текущей ChangeKey определяется старым suite_id.

Правило R2 — Stable identifier. Канонический 32-байтовый идентификатор подписанного объекта в любой consensus hash composition (op_hashes[], frontier_hash, Merkle leaves in proposal-level trees, sort keys, chain linking proposal_hash):

identifier(obj) = SHA-256(class_domain(obj) || signed_scope(obj))

Class domain separators:

Класс class_domain
UserObjects (0x01..0x04) "mt-op"
NodeRegistration (0x11) "mt-nodereg"
Proposal header "mt-proposal"
BundledConfirmation "mt-bundle"
VDF_Reveal "mt-vdf-reveal"

Identifier вычисляется от signed_scope (не от wire encoding с signature) — свойство стабильности независимо от non-determinism FN-DSA-512 randomized sampler Falcon. Один logical signed object → ровно один identifier, даже если владелец ключа переподписывает объект и получает разные σ.

Термины op_hash, proposal_hash, bundle_hash, nodereg_hash, reveal_hash обозначают identifier(obj) с соответствующим class_domain. Термин frontier_hash(account) = identifier(последней cemented операции sender-а).

Правило R3 — Consensus seed aggregation. Для любого aggregate feeding в consensus-critical seed output (lottery endpoint, selection sort_key, admission ordering, weight distribution, emission, ranking) aggregate input — только (signer_node_id, context), без content объектов и без signatures:

aggregate_for_seed(S, agg_domain, empty_domain, context) :=
  если S пустой:  SHA-256(empty_domain || context)
  иначе:          SHA-256(agg_domain || concat_sorted_by_node_id(signer_node_id(s) for s in S) || context)

Inputs строго:

  • signer_node_id каждого участника (canonical из registered pubkey)
  • context — temporal anchor (обычно window_index as u64 LE)

Inputs строго исключены:

  • Content объекта (payload fields, op_hashes[], reveal_hashes[])
  • Signatures (σ — Falcon non-deterministic)
  • identifier (Правило R2 — содержит signed_scope с потенциально attacker-choose-able content)

Grinding surface для single participant: ноль. signer_node_id детерминирован через hash от registered pubkey (committed при registration, не меняется); context canonical; composition of S emergent через quorum дynamics (single participant не контролирует кто ещё попал в cemented set).

Правило R4 — Разделение Rules R2 и R3. Identity (R2) и seed aggregation (R3) — разные use cases с разными grinding requirements.

R2 identifier корректно используется в:

  • op_hashes[] в BundledConfirmation (commitment к what was attested)
  • frontier_hash (account chain linking)
  • Merkle leaf values в proposal-level trees (included_bundles_root, included_reveals_root)
  • sort keys в apply_proposal ordering

R3 aggregate_for_seed корректно используется в:

  • cemented_bundle_aggregate (unpredictable-offline binding [I-8])
  • любой future aggregate feeding в consensus seed

R3 никогда не использует R2 identifier как input — включение signed_scope через identifier оставило бы grinding knob через attacker-choose-able content в signed_scope.

Адреса

Формат: mt + Base58(account_id + checksum).

Account_id = SHA-256("mt-account" || suite_id || pubkey). Стабильный идентификатор аккаунта. Смена ключа или схемы подписи выполняется через ChangeKey без изменения account_id — account_id привязан к первому pubkey, а текущий ключ хранится в состоянии аккаунта.

Инвариант derivation. Проверка account_id == SHA-256("mt-account" || suite_id || pubkey) происходит один раз при settle TransferActivation (apply at window close) — против payload-полей receiver_pubkey и suite_id, предоставленных sender'ом. После этого account_id — каноничный ключ записи, формула не пересчитывается. Доказательство derivation навсегда сохранено в proposal с финализированным TransferActivation. Любой аудитор может replay из proposal history. Original_pubkey не дублируется в Account Table — integrity гарантируется неизменностью proposal chain.

Поле suite_id в Account Table — current (мутируется ChangeKey синхронно с current_pubkey), используется для верификации текущих подписей. Original suite_id зафиксирован только в исторической TransferActivation записи в proposal chain.


Account Chain (Block Lattice)

Каждый аккаунт имеет собственную цепочку операций. Перевод — одна операция в цепочке отправителя. Зачисление получателю — детерминированно после финализации. Цепочки аккаунтов полностью независимы.

Реестр типов объектов

Type byte (первый байт canonical_bytes операции) — global unique across all classes использующих полиморфный wire slot (разные типы в одном формате блока, dispatch по первому байту).

UserObjects (полиморфный слот):
  0x02  Transfer
  0x03  ChangeKey
  0x04  Anchor
  0x05  NicknameBid
  0x08  PurchaseCredits
  0x09  ConsumeCredits
  0x0A  TransferActivation
  0x0B  CloseAccount

ControlObjects (полиморфный слот):
  0x11  NodeRegistration

Reserved (future protocol versions):
  0x20-0x2F   consensus meta-objects
  0x30-0x3F   governance / MIP objects
  0x40-0xFF   unallocated

Type byte 0x01 не выделен — OpenAccount удалён; активация AccountRecord выполняется через 0x0A TransferActivation от существующего аккаунта (sponsor). Самоинициация создания аккаунта невозможна; AccountRecord появляется в Account Table только при первом incoming TransferActivation.

Signed objects без type byte (каждый в собственном dedicated wire slot, disambiguation через class_domain Правила R2):

  • Proposal header — "mt-proposal" class domain
  • BundledConfirmation — "mt-bundle" class domain
  • VDF_Reveal — "mt-vdf-reveal" class domain

Cross-class signature confusion structurally невозможна: для polymorphic classes первый байт signed_scope различается (0x01..0x04, 0x11); для non-polymorphic class_domain в identifier обеспечивает разделение hash spaces, а signed_scope разных classes имеет несовпадающую структуру (SHA-256 collision resistance negligibly мала).

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

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

type      (1B)  | prev_hash (32B) | payload (variable) | signature (666B)

Все операции — этот шаблон. prev_hash связывает операции в цепочку аккаунта. signature — FN-DSA-512 владельца над signed_scope(op) (см. Правило R1). payload зависит от типа. Все операции начинают payload с sender (32B account_id) — узел проверяет Account Table[sender].frontier_hash == prev_hash и signature валиден для current_pubkey за O(1).

Особый случай — операция первой signed receiver-операции после активации AccountRecord (TransferActivation от sponsor). Receiver's AccountChain ещё пуст: AccountTable[receiver].frontier_hash == 0x00...00 (initialized при TransferActivation apply). Первая signed receiver-op имеет prev_hash == 0x00...00 — она становится genesis receiver's chain. После apply frontier_hash обновляется до identifier(op).

op_hash в любом consensus контексте (op_hashes[] в BundledConfirmation, frontier_hash, sort_key apply_proposal, H(Anchor) в Anchor verification) = identifier(op) с class domain "mt-op" (см. Правило R2). Identifier вычисляется от signed_scope без signature — стабилен при non-determinism Falcon.

Transfer — публичный перевод существующему аккаунту:

type       1B   <- 0x02 Transfer
prev_hash 32B
payload   80B   <- sender (32B) || link (32B receiver) || amount (16B u128 nɈ)
signature 666B
Итого:    ~779 B

Инварианты Transfer:

  • type == 0x02
  • payload.sender существует в Account Table
  • Account Table[sender].frontier_hash == prev_hash (dependency rule на settled state окна W-1)
  • payload.sender != payload.link (self-transfer запрещён — открывает рост account_chain_length через no-op переводы себе, см. «Верификация баланса»)
  • payload.amount > 0 (нулевой перевод запрещён)
  • payload.link существует в Account Table (перевод на несуществующий account_id — reject ReceiverNotActive; для создания нового аккаунта используется TransferActivation)
  • Account Table[sender].balance >= payload.amount (достаточный баланс)
  • Signature FN-DSA-512 valid для Account Table[sender].current_pubkey над signed_scope (Правило R1)

sender — account_id отправителя, явно. Узел проверяет Account Table[sender].frontier_hash == prev_hash за O(1).

Открытые поля: отправитель (через frontier index по prev_hash), получатель, сумма, баланс после операции (через Account Table). Псевдонимность на уровне account_id. Финансовая приватность — задача приложений (микшеры, payment channels), не протокола.

TransferActivation — перевод с созданием нового AccountRecord для receiver'а (sponsor-activation path):

type             1B    <- 0x0A TransferActivation
prev_hash       32B
payload        979B    <- sender (32B) || receiver (32B) || suite_id (2B)
                         || receiver_pubkey (897B FN-DSA-512) || amount (16B u128 nɈ)
signature      666B
Итого:       ~1 678 B

Инварианты TransferActivation:

  • type == 0x0A
  • payload.sender существует в Account Table
  • Account Table[sender].frontier_hash == prev_hash (dependency rule на settled state окна W-1)
  • Cooldown активации per [I-15]: current_window >= Account Table[sender].last_activation_window + τ₂_windows (sender выполняет максимум одну TransferActivation за τ₂; нарушение — reject ActivationCooldownNotElapsed). Исключение — sender с last_activation_window == 0 (никогда не активировал) проходит без проверки.
  • payload.receiver не существует в Account Table (иначе — reject ReceiverAlreadyExists; для перевода активированному аккаунту используется Transfer)
  • payload.suite_id соответствует активной схеме подписи (на момент запуска: 0x0001 = FN-DSA-512); прочие — reject UnsupportedSuite
  • payload.receiver == SHA-256("mt-account" || payload.suite_id || payload.receiver_pubkey) (binding: account_id корректно derived из receiver_pubkey)
  • payload.sender != payload.receiver (запрещён — account_id derived от receiver_pubkey, не может совпадать с sender без коллизии SHA-256)
  • payload.amount > 0 (нулевой перевод запрещён)
  • Account Table[sender].balance >= payload.amount
  • После apply:
    • Account Table[sender].balance -= payload.amount
    • Account Table[sender].frontier_hash = identifier(op)
    • Account Table[sender].last_activation_window = current_window
    • Account Table[payload.receiver] = new_record(balance = payload.amount, current_pubkey = payload.receiver_pubkey, suite_id = payload.suite_id, frontier_hash = 0x00...00, last_activation_window = 0, creation_window = current_window, ...)
  • Signature FN-DSA-512 valid для Account Table[sender].current_pubkey над signed_scope (Правило R1; sender подписывает activation своим ключом)

receiver_pubkey обязателен в payload — без него невозможен binding verify receiver == H(domain || suite_id || pubkey). Sender узнаёт receiver_pubkey offline (QR-код, сообщение, nickname lookup). Sender не владеет private key receiver; AccountTable[receiver].current_pubkey устанавливается из payload и впредь служит для верификации подписей receiver.

Receiver's AccountChain остаётся пустой после TransferActivation apply (frontier_hash = 0x00...00). Первая signed op receiver'а имеет prev_hash == 0x00...00 и становится genesis receiver's chain.

ChangeKey — смена ключа или схемы подписи:

type       1B   <- 0x03 ChangeKey
prev_hash 32B
payload  931B   <- sender (32B) || new_suite_id (2B) || new_pubkey (897B)
signature 666B  <- подписано старым ключом
Итого: ~1 630 B

Инварианты ChangeKey:

  • type == 0x03
  • payload.sender существует в Account Table
  • Account Table[sender].frontier_hash == prev_hash
  • payload.new_suite_id соответствует активной схеме подписи; прочие значения — reject (UnsupportedSuite)
  • Signature FN-DSA-512 valid для старого Account Table[sender].current_pubkey над signed_scope (Правило R1; подпись старым ключом до apply; new_pubkey становится current только после apply)

Anchor — криптографический якорь (привязка данных ко времени):

type       1B   <- 0x04 Anchor
prev_hash 32B
payload   96B   <- sender (32B) || app_id (32B) || data_hash (32B)
signature 666B
Итого:    ~795 B

Инварианты Anchor:

  • type == 0x04
  • payload.sender существует в Account Table
  • Account Table[sender].frontier_hash == prev_hash
  • Signature FN-DSA-512 valid для Account Table[sender].current_pubkey над signed_scope (Правило R1)

Anchor не перемещает средства и не требует комиссии. Единственная операция — запись data_hash в цепочку аккаунта с привязкой к timechain_value окна финализации. Приватность данных приложения обеспечивается тем что в сеть попадает только хэш — содержимое хранится у владельца зашифрованным.

Anchor lifecycle — persistent design через [I-15]. Anchor — persistent запись в AccountChain sender'а (не ephemeral event). Это сохраняет лёгкую верификацию: любая full node может предоставить inclusion proof для данного Anchor через стандартный Merkle path AccountChain без обращения к архивным узлам.

Защита от раздутия state через Anchor spam соответствует [I-15] time-based scarcity без денежных барьеров:

  1. Rate-per-identity (существующее): одна операция per аккаунт per τ₁ — sender не может сделать более 20 160 Anchor записей за τ₂.
  2. Amortization через AccountChain TTL: Anchor записи — часть AccountChain владельца; при pruning inactive аккаунта (balance == 0 + 4τ₂ без активности) все Anchor удаляются вместе с AccountRecord — не остаются orphan'ами в state.
  3. Cooldown активации (Пункт 3): ввод нового аккаунта для Anchor-фарминга ограничен 1 TransferActivation per sender per τ₂ — fan-out на массу дешёвых Anchor-аккаунтов экспоненциально медленный (binary tree expansion).

Quantify: атакующий с одного активного sender может за τ₂ создать до τ₂_windows Anchor записей (по 795 B каждая, суммарно τ₂_windows × 795 B на sender). Для поддержания M Anchor'ов постоянно активными (избежание pruning 4τ₂) требуется M / τ₂_windows senior senders, генерирующих M подписанных operations за τ₂ — видимо в сетевой статистике signature verifications + gossip bandwidth. Storage damage: M × 795 B per node (≈795 MB для M = 10⁶ Anchors) — покрыто time-based защитой того же класса что AccountRecord.

Никаких per-record burn, rent, min-balance барьеров для Anchor не вводится — это противоречило бы [I-15]. Защита через существующие time-based паттерны: rate-per-identity + amortization + cooldown активации.

NicknameBid — ставка в аукционе никнейма (слой Nickname Auction Protocol):

type          1B   <- 0x05 NicknameBid
prev_hash    32B
payload      N B   <- sender (32B)
                   || nickname_len (1B u8, ∈ [1, 32])
                   || nickname_bytes (nickname_len байт ASCII [a-z0-9_-])
                   || bid_amount_nj (16B u128 LE, Монтана в нано-единицах)
                   || window_W (8B u64 LE, окно в момент ставки)
signature  666B
Итого:   ~760-790 B (зависит от nickname_len)

Инварианты NicknameBid:

  • type == 0x05
  • payload.sender существует в Account Table
  • Account Table[sender].frontier_hash == prev_hash
  • nickname_len ∈ [1, 32]
  • nickname_bytes[0..nickname_len] — все байты ∈ {a..z, 0..9, _, -} (ASCII; lowercase; без пробелов)
  • Никнейм не входит в зарезервированный список (см. Genesis Decree nickname_reserved_list)
  • Account Table[sender].nickname_len == 0 (у отправителя ещё нет никнейма — правило «1 на аккаунт», связано с [I-11])
  • Account Table[sender].account_chain_length_snapshot >= nickname_activity_threshold_windows (порог активности, по умолчанию 84 окна τ₁ = 1τ₂ — анти-Sybil защита)
  • payload.window_W == current_window (предотвращает replay старых цен)
  • bid_amount_nj >= price_at(nickname, window_W) (текущая цена голландского аукциона достижима; формула в разделе «Nickname Auction Protocol → Integer form»)
  • Account Table[sender].balance >= bid_amount_nj
  • Signature FN-DSA-512 valid для Account Table[sender].current_pubkey над signed_scope (Правило R1)

PurchaseCredits — покупка кредитов для off-chain услуг с немедленным сжиганием Монтана:

type          1B   <- 0x08 PurchaseCredits
prev_hash    32B
payload      48B   <- sender (32B)
                   || amount_nj (16B u128 LE, Монтана для конвертации в кредиты)
signature  666B
Итого:     ~747 B

Инварианты PurchaseCredits:

  • type == 0x08
  • payload.sender существует в Account Table
  • Account Table[sender].frontier_hash == prev_hash
  • payload.amount_nj > 0
  • Account Table[sender].balance >= amount_nj
  • Signature FN-DSA-512 valid для Account Table[sender].current_pubkey над signed_scope (Правило R1)

Семантика apply:

sender.balance              -= amount_nj
supply_nj                   -= amount_nj        # burn — перманентное уменьшение supply
sender.service_credits_nj   += amount_nj        # 1:1 конверсия, без комиссии

Кредиты non-refundable после начисления — нельзя конвертировать обратно в Монтана. Они тратятся только через ConsumeCredits при фактическом использовании услуги.

ConsumeCredits — периодическое списание кредитов за использованные off-chain услуги:

type          1B   <- 0x09 ConsumeCredits
prev_hash    32B
payload      49B   <- sender (32B)
                   || usage_nj (16B u128 LE, объём потребления в nɈ)
                   || service_type (1B u8, см. реестр типов услуг)
signature  666B
Итого:     ~748 B

Инварианты ConsumeCredits:

  • type == 0x09
  • payload.sender существует в Account Table
  • Account Table[sender].frontier_hash == prev_hash
  • payload.usage_nj > 0
  • Account Table[sender].service_credits_nj >= usage_nj (достаточно кредитов на счету)
  • payload.service_type{0x01 voice_call, 0x02 video_call, 0x03 premium_profile, 0x04 anchor_storage, 0x05 creator_subscription} (расширяемый реестр в Genesis Decree)
  • Signature FN-DSA-512 valid для Account Table[sender].current_pubkey над signed_scope (Правило R1)

Семантика apply:

sender.service_credits_nj  -= usage_nj
# supply_nj НЕ изменяется — Монтана уже сожжён в момент PurchaseCredits.
# ConsumeCredits — это settlement потребления, не второй burn.

Клиент локально отслеживает фактическое использование (минуты звонка × rate + подписки × месячная цена) и периодически публикует ConsumeCredits для консенсусного списания с service_credits_nj. Точная периодичность — решение приложения (per-minute real-time, per-call finalization, periodic τ₁ batching). Protocol не навязывает frequency.

Верификация баланса

Открытое арифметическое сравнение. Узел проверяет:

sender != receiver
amount > 0
sender.balance >= amount

sender != receiver запрещает self-transfer — иначе атакующий мог бы наращивать account_chain_length каждое окно через no-op переводы себе.

При settle (apply at window close):

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

Баланс обновляется не при cement (quorum event), а в конце окна при батчевом apply. Между cement и settle операция необратима но баланс ещё не изменён. Никаких proofs, никакой криптографии помимо подписи и хэша.

Anti-inflation

Чеканка из воздуха невозможна через локальный инвариант на каждом state transition.

Per-user-operation invariant. Каждое применение пользовательской операции обязано удовлетворять Σ delta_balance == 0:

Transfer:    sender.balance -= amount, receiver.balance += amount  → Σ = 0
TransferActivation: sender.balance -= amount, receiver.balance += amount  → Σ = 0
ChangeKey:   только обновление current_pubkey                       → Σ = 0
Anchor:      только запись data_hash                                → Σ = 0

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

apply_proposal step 2 (Монтана emission, W = proposal.window_index, winner = winner_{W-1}):
  r = reward_nɈ(W - 1)
  # Лотерея single-class: winner всегда узел (см. «Аккаунты не участвуют в лотерее»).
  operator_account = Node Table[winner_id].operator_account_id
  Account Table[operator_account].balance += r

delta_supply за proposal = r ровно один раз

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

genesis state (аксиома):   window_index не определён, supply = 0, Σ balance = 0
первое окно (W = 0):       supply = reward(0) = 29 Ɉ,             Σ balance = 29 Ɉ
окно k < 524_160:          supply = 29 × (k+1),                   Σ balance = 29 × (k+1)
окно k на границе эпох:    supply = R_BASELINE × (k+1) + bootstrap_cumulative(k)
                                    где bootstrap_cumulative(k) ≤ BOOTSTRAP_CAP

Закрытая форма bootstrap_cumulative(W):

Пусть e = min(floor(W / BOOTSTRAP_H), BOOTSTRAP_EPOCHS)
Пусть geom_sum(e) = Σ_{i=0..e-1} (BOOTSTRAP_R0 >> i) × BOOTSTRAP_H
                  = BOOTSTRAP_R0 × BOOTSTRAP_H × (2  2^(1e)) / 1   для e ≥ 1
                  = 0                                                для e = 0
Пусть residual(W) = (BOOTSTRAP_R0 >> e) × ((W mod BOOTSTRAP_H) + 1)  если e < BOOTSTRAP_EPOCHS
                  = 0                                                иначе

bootstrap_cumulative(W) = geom_sum(e) + residual(W)

Вычисление O(1). Проверяемо каждым узлом без итерации по истории.

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

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

Bootstrap cap invariant. Дополнительно раз в τ₂: проверка bootstrap_cumulative(window_index) ≤ BOOTSTRAP_CAP_nɈ. Нарушение = немедленная остановка узла (нарушение денежной политики protocol-level).

Перевод

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

Валюта Монтана

Победитель окна W регистрирует одно окно Montana Time: reward_nɈ(W) наноɈ. При финализации proposal окна W+1 выплата применяется (one-window lag):

r = reward_nɈ(W)
# single-class лотерея, winner — всегда узел.
operator_account = Node Table[winner_id].operator_account_id
Account Table[operator_account].balance += r

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

Публичное (верифицируемо всеми):
  Монтана:     reward(W) = 13 + bonus(W) Ɉ за окно (см. «Поокнная эмиссия»)
  Supply audit: supply(W) = 13 × (W+1) Ɉ + bootstrap_cumulative(W)
  Bootstrap cap: bootstrap_cumulative(W) ≤ 16_248_960 Ɉ
  Winner:       winner_id в proposal header
  Все балансы:  Account Table
  Все переводы: цепочки операций аккаунтов
  VDF:          TimeChain values, lottery endpoints, подписи

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

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

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

Без конфликта: операция → узлы валидируют → публикуют confirmation → quorum → cemented (необратимо, ~0.3 сек). Баланс обновляется при settle (apply at window close).

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

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

Equivocation создаётся только владельцем аккаунта (требуется подпись). Третья сторона не может создать equivocation для чужого аккаунта. Стимул: двойная трата = потеря обеих операций.

Антиспам

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

Приоритет операции

account_age = current_window - creation_window
priority(op) = account_age × windows_since_last_op

account_age — возраст аккаунта в окнах. Растёт линейно. Некупуемый. windows_since_last_op — окна с последней операции аккаунта. Сбрасывается при каждой операции. Спамер обнуляет приоритет с каждой операцией — самонаказание.

При переполнении ёмкости сети — операции с наименьшим приоритетом ожидают следующего окна.

Бакеты по account_age

Изоляция спама. Каждый аккаунт может опубликовать максимум одну операцию за окно τ₁ (dependency rule). При переполнении сети (больше операций в мемпуле чем пропускная способность окна) — бакеты определяют приоритет включения. Round-robin по бакетам: одна операция из бакета 0, одна из бакета 1, ..., по кругу. Спам в бакете 0 не вытесняет операции из бакетов 1-3.

Бакет 0:  account_age < 4τ₂
Бакет 1:  account_age 4τ₂ — 16τ₂
Бакет 2:  account_age 16τ₂ — 64τ₂
Бакет 3:  account_age 64τ₂+

Границы бакетов = 4^N × τ₂. Все аккаунты: максимум 1 операция за τ₁. Бакет определяет приоритет при переполнении, не потолок TPS.

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

Throughput на аккаунт

Каждая цепочка аккаунта: 1 операция за τ₁. Правило per-account по проектированию — одно окно, один шаг в личной цепочке времени пользователя. Ритм τ₁ достаточен для любых задач одного пользователя в сети.

Одно правило закрывает конструкцией пять задач сразу:

  1. Spam protection by time-pacing. Рейт операций аккаунта ограничен структурой состояния, не очередью узла. Узлам не нужно отбивать флуд от одного аккаунта — следующая операция этого аккаунта попросту не существует до закрытия окна. Дополнительные mempool-фильтры, fee markets, rate limiters на уровне сети не требуются.

  2. Детерминизм apply_proposal (инвариант [I-3]). N>1 операций одного аккаунта в одном окне потребовали бы intra-window ordering. Любое такое правило обязано быть либо subjective (mempool-зависимое — автоматическая дыра), либо дополнительной canonical hash composition в consensus-critical output (расширение поверхности [I-8]). При N=1 проблема отсутствует: выбор операции окна единственный.

  3. Dependency rule. Операция аккаунта в окне ссылается на frontier_hash из settled state предыдущего окна. N>1 операций одного аккаунта в одном окне потребовали бы intra-window ordering — либо subjective (mempool-зависимое, нарушение [I-3]), либо canonical hash composition (расширение поверхности [I-8]). При N=1 проблема отсутствует: порядок операции единственный.

  4. Семантика chain_length как веса. account_chain_length = количество окон τ₁ с операцией, то есть окон присутствия. Вес в консенсусе измеряется временем, а не числом операций. N>1 операций за окно разорвало бы связь «вес = присутствие во времени» и открыло Sybil-накачку веса через спам операций в собственной цепочке.

  5. Бинарная разрешимость double-spend. Правило «67% active_chain_length за одну операцию по одному prev_hash» работает потому что конфликт двоичен: либо A, либо B. N>1 операций за окно делает конфликт multi-way и требует дополнительного механизма выбора между тремя и более ветвями за окно — блокер liveness и новая поверхность атаки.

Объём данных за одну операцию не ограничен ритмом: Anchor содержит Merkle root над произвольным числом off-chain записей, привязанных к одному окну.

Сетевой throughput складывается параллелизмом независимых цепочек аккаунтов и ограничен пропускной способностью канала узла и размером proposal, не правилом per-account.

Высокочастотные сценарии sub-τ₁ (микроплатежи, streaming) находятся вне scope протокола: введение throughput-слоя ниже τ₁ разрушит каждую из пяти перечисленных гарантий. Применения, которым нужна такая частота, строятся на других субстратах.

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


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

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

Account Table (запись на аккаунт):
  account_id              32B     <- = SHA-256("mt-account" || suite_id || pubkey)
  balance                 16B     <- u128 nɈ, открыт
  suite_id                 2B
  is_node_operator         1B     <- 1 если аккаунт привязан как operator узла
  frontier_hash           32B     <- хэш последней операции в цепочке; 0x00...00 сразу после TransferActivation до первой signed receiver-операции
  op_height                4B     <- количество операций в цепочке
  account_chain_length     4B     <- количество уникальных окон τ₁ с операцией (длина AccountChain), live
  account_chain_length_snapshot 4B <- snapshot account_chain_length на последнюю τ₂ boundary; используется для anti-Sybil threshold (NicknameBid)
  current_pubkey         897B     <- FN-DSA-512 receiver-pubkey, передан sender'ом через TransferActivation
  creation_window          4B     <- окно создания аккаунта (cementing TransferActivation)
  last_op_window           4B     <- окно последней операции (для приоритета)
  last_activation_window   4B     <- u32, окно последней TransferActivation посланной этим sender-ом; 0 если не посылал. Используется для cooldown rule «1 TransferActivation per τ₂» per [I-15]
  nickname_len             1B     <- u8, ∈ [0, 32]; 0 = nickname не назначен
  nickname_bytes          32B     <- ASCII [a-z0-9_-], nickname_len значащих байт, остальные 0x00
  service_credits_nj      16B     <- u128, prepaid баланс кредитов для off-chain услуг (звонки, подписки)

Node Table (запись на узел):
  node_id                          32B     <- SHA-256("mt-node" || node_pubkey), верифицируемо
  node_pubkey                     897B
  suite_id                          2B
  operator_account_id              32B     <- account_id куда зачисляется Монтана при победе узла; неизменен после регистрации
  start_window                      8B     <- u64, окно регистрации (первое окно присутствия в Node Table)
  chain_length                      8B     <- u64, позиция узла в NodeChain: = 1 при активации, +1 при cemented BundledConfirmation в окне. Инвариант: chain_length ≥ 1 для любого узла в Node Table
  chain_length_snapshot             8B     <- u64, = chain_length - chain_length_checkpoint[oldest]; используется в лотерее
  chain_length_checkpoints        48B     <- 6 × u64, checkpoint-ы chain_length на последних 6 τ₂-boundaries
  last_confirmation_window          8B     <- u64, window_index последнего окна с cemented BundledConfirmation

Candidate Pool (запись на кандидата):
  node_id                          32B     <- SHA-256("mt-node" || node_pubkey)
  node_pubkey                     897B
  suite_id                          2B
  operator_account_id              32B     <- account_id куда зачисляется Монтана при победе
  proof_endpoint                   32B     <- endpoint VDF цепочки (длина vdf_chain_length)
  W_start                          8B     <- u64, окно начала VDF (заявлено кандидатом)
  vdf_chain_length                 8B     <- u64, длина VDF цепочки от candidate_vdf_init до proof_endpoint (в "окнах" по D хэшей)
  registration_window               8B     <- u64, окно cementing NodeRegistration
  expires                           8B     <- u64, registration_window + 3 × τ₂_windows

Active node predicate (derived). Узел считается активным если опубликовал cemented BundledConfirmation за последние 2τ₂:

active(node, W) = (W - node.last_confirmation_window) <= 2 × τ₂_windows

Predicate вычисляется из last_confirmation_window и текущего window_index. Применяется в quorum, confirmation_threshold, лотерее, валидации selection event.

State Root

Merkle-дерево глобального состояния. Три подкорня обновляются при применении операций (apply_proposal и apply at window close):

state_root = SHA-256("mt-state-root" || node_root || candidate_root || account_root)

node_root:       Merkle root Node Table, обновляется при selection event (регистрация),
                 chain_length increment (apply step 3.5), pruning узлов на τ₂.
candidate_root:  Merkle root Candidate Pool, обновляется при cementing NodeRegistration
                 (добавление), selection event (удаление выбранных), expiry (удаление просроченных).
account_root:    Merkle root Account Table, обновляется батчем при apply at window
                 close (все cemented операции окна применяются к state, затем
                 account_root пересчитывается).

Все три root соответствуют settled state (после apply at window close).
Порядок node_root → candidate_root → account_root отражает направление
зависимостей: узлы — активные участники, кандидаты — будущие узлы, аккаунты — финансовый слой.
Domain separator `mt-state-root` отличён от `mt-merkle-node` — hash spaces пересекаться не могут.

Структура Account Table Root:

Sparse Merkle tree глубины 256, индексированный по account_id:

leaf_hash(account)        = SHA-256("mt-merkle-leaf" || serialize(account_record))
internal(left, right)     = SHA-256("mt-merkle-node" || left || right)
empty_leaf                = 0x00 × 32

account_root = root of sparse Merkle tree over Account Table

Обновление одного аккаунта пересчитывает ровно log₂(N) хэшей пути от листа к корню — для N=10⁹ аккаунтов это 30 SHA-256 вычислений (~60 µs CPU).

Структура Node Table Root: аналогично, sparse Merkle tree по node_id. Размер сети ≤ 10⁵ узлов → пути ~17 хэшей.

Canonical serialization — single source of truth. Определения полей каждой таблицы (Node Table, Account Table, Candidate Pool) задают canonical byte-for-byte сериализацию каждой записи. Эта сериализация используется одновременно для (1) вычисления leaf_hash в Merkle tree, (2) хранения на диске, (3) передачи через Fast Sync snapshot. Любое изменение record format требует одновременного обновления canonical encoding во всех трёх путях использования. Fast Sync автоматически следует за canonical encoding — см. раздел Fast Sync «Полнота сериализации snapshot».

Структура Candidate Pool Root: sparse Merkle tree глубины 256, индексированный по node_id. Empty root = 0x00 × 32.

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

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

State Root коммитится в заголовке каждого proposal τ₁. account_root, node_root и candidate_root соответствуют settled state после apply at window close — все cemented операции окна W применены к таблицам перед сборкой proposal.

Inclusion proof

Любой cemented аккаунт может предоставить доказательство существования в state:

proof = Merkle path длиной log₂(N) (~30 хэшей для N=10⁹)
verify(proof, account_record, account_root):
  reconstruct path bottom-up; compare с account_root

Доказательство верифицируется против account_root любого финализированного proposal начиная с окна когда состояние было обновлено. Не нужны архивы операций — текущее состояние самодостаточно.

Pruning

На τ₂ boundary применяется pruning неактивных аккаунтов:

Удалить все записи Account Table где:
  balance == 0                                            <- нулевой баланс
  AND last_op_window + 4τ₂ <= current_window              <- нет активности 4τ₂ (52 000 окон)
  AND is_node_operator == 0                               <- не привязан как operator узла
  AND нет cemented NodeRegistration в control_set         <- нет pending привязки
      ожидающего apply, ссылающегося на этот account_id

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

  • Operator-аккаунтов уже зарегистрированных узлов (is_node_operator == 1)
  • Аккаунтов на которые ссылается cemented NodeRegistration ожидающий apply

[I-14] compliance через [I-15]. Защита от раздутия state достигается time-based путём: cooldown 1 TransferActivation per sender per τ₂ (см. Инварианты TransferActivation) ограничивает rate создания новых AccountRecord, tree-expansion атакой на 10⁶ записей требует ⌈log₂(10⁶)⌉ = 20 τ₂, keepalive-атака через постоянную активность видна статистически и упирается в 1-op-per-τ₁ rate limit. Существующее pruning (balance == 0 + 4τ₂) закрывает dormant bloat. Денежные барьеры (MIN_BALANCE, periodic burn) не используются per [I-15].

Без второго исключения возможна race: NodeRegistration cemented (operator валиден), pruning применился до apply этого NodeRegistration → аккаунт удалён → apply отклонён. Защита: pruning не трогает аккаунты, на которые есть cemented pending registration.

Каждое удаление пересчитывает соответствующий путь в Merkle tree (logarithmic). Pruning детерминирован, автоматичен, каноничен.

Recovery semantics. Воссоздание pruned аккаунта через новый TransferActivation от sponsor-а с тем же receiver_pubkey создаёт новую цепочку: frontier_hash начинается заново, op_height сбрасывается в 1, account_chain_length = 0. Старые prev_hash references на цепочку до pruning отклоняются — цепочка удалена из текущего state. История переводов до pruning не восстанавливается из текущего Account Table, но навсегда сохранена в proposals. Восстановление истории возможно через scan архива proposals.


Двигатели

Односторонний поток зависимостей: TimeChain → NodeChain → AccountChain → AccountTable.

TimeChain — глобальные часы (ход времени, VDF). NodeChain — присутствие узла (последовательность cemented BundledConfirmation). AccountChain — присутствие аккаунта (дискретные операции). AccountTable — состояние счёта.

TimeChain VDF — осциллятор

Первичный продукт протокола. Непрерывная последовательная SHA-256 цепочка — цифровой осциллятор Montana Time:

T_r = SHA-256^D(T_{r-1})

D — количество последовательных хэшей за одно окно τ₁. Каждый хэш — один тик осциллятора. D хэшей — одно колебание. TimeChain продвигается по расписанию окон. Для фиксированного индекса r значение T_r совпадает у всех честных узлов. Каждый узел вычисляет TimeChain независимо — результат детерминирован.

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

NodeChain — последовательность присутствия узла

Доказательство присутствия конкретного node_id в каждом окне. Каждое окно с cemented BundledConfirmation = одно звено NodeChain. chain_length — позиция узла в NodeChain: = 1 при активации (Genesis для bootstrap, selection event для нового узла), +1 при каждом cemented BundledConfirmation. Инвариант: chain_length ≥ 1 для любого узла в Node Table — гарантирует корректность знаменателей в weighted_ticket лотереи и в seniority_bonus.

NodeChain не является VDF-цепочкой. Узел доказывает присутствие публикацией BundledConfirmation (подтверждение операций сети), не вычислением per-node VDF. Один VDF на всю сеть (TimeChain) — достаточен.

NodeChain зависит от TimeChain (якорится через window_index). TimeChain не зависит от NodeChain.

Liveness узла и сетевое включение. Рост chain_length требует cementing BundledConfirmation через confirmation threshold 67% active_chain_length. При стандартной BFT-assumption (≥67% active_chain_length честны и достижимы по P2P) BC активного узла cemented в каждом окне участия. Изоляция узла от confirmers (eclipse, network partition, propagation failure) останавливает рост chain_length независимо от локальной работы узла. Это свойство consensus-механизма, не свойство узла: chain_length измеряет подтверждённое сетью присутствие, не локальную CPU-работу.

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

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

Длина AccountChain — количество окон τ₁ в которых аккаунт имел cemented операцию:

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

Dependency rule ограничивает аккаунт одной операцией за окно τ₁ — поэтому длина AccountChain совпадает с числом окон активности. Поле account_chain_length хранится в Account Table, обновляется при apply операции:

on_operation_applied(operation, window W):
  account = operation.account_id
  account.account_chain_length += 1
  account.last_op_window = W
  account.op_height += 1

Параллелизм NodeChain и AccountChain:

Свойство NodeChain AccountChain
Источник node_pubkey account_pubkey
Идентификатор node_id account_id
Тип присутствия машинное человеческое
Ритм непрерывный (каждое окно) дискретный (окно с операцией)
Длина chain_length (окна с BundledConfirmation) account_chain_length (окна с операцией)
Единица длины окно τ₁ окно τ₁
Накопление автоматически при публикации BundledConfirmation через активность пользователя
Защита от подделки подпись FN-DSA-512 подпись FN-DSA-512
Защита от Sybil τ₂ окон VDF + selection event накопление окон требует активности

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

AccountChain зависит от TimeChain напрямую. AccountChain не зависит от NodeChain по построению.

VDF Reveal и лотерея

В лотерее участвует только один класс субъектов — узлы (через VDF_Reveal). Аккаунты в лотерее не участвуют (см. раздел «Аккаунты не участвуют в лотерее» ниже). Каждый узел производит ticket, взвешенный по длине своей NodeChain.

Confirmers (~100 узлов с наибольшим chain_length) публикуют BundledConfirmation для финализации окна. Все узлы с weighted_ticket_node < target публикуют VDF_Reveal для лотереи. VDF_Reveal цементируется через BundledConfirmation: confirmers включают полученные VDF_Reveals в свои bundles наряду с UserObjects и ControlObjects. Cement threshold тот же — 67% active_chain_length. Proposer извлекает только cemented reveals — дискреция над лотереей = ноль.

Класс 1: узлы

После завершения VDF окна W каждый узел вычисляет свой ticket.

Real-valued form (commentary):

ticket_node = -ln(endpoint_node / 2^256)

seniority_bonus = min(chain_length / 69, chain_length_snapshot)
lottery_weight = chain_length_snapshot + seniority_bonus

weighted_ticket_node = ticket_node / lottery_weight

Integer form (authoritative, per [I-9]):

Input:
  endpoint_node: [u8; 32]        (big-endian u256 interpretation)
  chain_length: u64              (absolute, ≥ 1 по инварианту chain_length ≥ 1)
  chain_length_snapshot: u64     (≥ 1 по DS-2)

Output:
  weighted_ticket_node: u128     (Q64.64, сравнивается через u128::cmp)

Algorithm:
  seniority_bonus_u64 = min(chain_length / 69u64, chain_length_snapshot)
    # Integer division toward zero (unsigned u64)
    # chain_length < 69 ⇒ seniority_bonus = 0
  lottery_weight_u64 = chain_length_snapshot + seniority_bonus_u64
    # Overflow: chain_length_snapshot ≤ 78000 (6τ₂), seniority ≤ snapshot, sum ≤ 2 × 78000 ⇒ safe u64
  ticket_q64_128 = ln_q64(endpoint_node)
    # ln_q64: [u8;32] → u128 Q64.64 — см. «Integer log algorithm» ниже
  weighted_ticket_node = ticket_q64_128 / (lottery_weight_u64 as u128)
    # u128 / u128 integer division toward zero

Comparison:
  weighted_ticket_i < weighted_ticket_j ⟺ u128-native less-than.

Binding test vectors (byte-exact; все используют ln_q64 = 0x4f60bd6fe6504646 от TV3 endpoint раздела «Integer log algorithm»):

  # N1 typical
  chain_length = 1000, chain_length_snapshot = 500
  → seniority_bonus = 14, lottery_weight = 514
  → weighted_ticket_node = 0x0000000000000000002788D5E211170C

  # N2 boundary (DS-2 floor: weight = 1)
  chain_length = 1, chain_length_snapshot = 1
  → seniority_bonus = 0, lottery_weight = 1
  → weighted_ticket_node = 0x00000000000000004F60BD6FE6504646

  # N3 seniority cap (cap at snapshot)
  chain_length = 1_000_000, chain_length_snapshot = 10
  → seniority_bonus = 10 (capped), lottery_weight = 20
  → weighted_ticket_node = 0x000000000000000003F80978CB840383

  # N4 max chain_length boundary
  chain_length = 2^64 - 1, chain_length_snapshot = 78000
  → seniority_bonus = 78000, lottery_weight = 156000
  → weighted_ticket_node = 0x000000000000000000002158CB8365BE

  # N5 seniority threshold (chain_length = 69)
  chain_length = 69, chain_length_snapshot = 1
  → seniority_bonus = 1, lottery_weight = 2
  → weighted_ticket_node = 0x000000000000000027B05EB7F3282323

Conformance status: closed (binding test vectors выше).

chain_length_snapshot — количество окон с cemented BundledConfirmation за последние 6τ₂ (78 000 окон). Вычисляется через checkpoint-механизм: на каждой τ₂-boundary фиксируется checkpoint chain_length; snapshot = chain_length - checkpoint_6τ₂_ago. Хранится 6 checkpoint-ов (48B на узел). Обновляется на τ₂-boundary (шаг 3.6 apply_proposal).

seniority_bonus — добавка за накопленный абсолютный chain_length, ограниченная сверху размером snapshot (cap). Делитель 69 (цифровой корень = 6, Tesla). Cap = snapshot: максимальное преимущество старожила ≈ 2x относительно новичка с полным snapshot. При chain_length < 69 seniority_bonus = 0 (целочисленное деление): первые 69 окон после регистрации lottery_weight = snapshot.

Инвариант DS-2 (lottery_weight floor). Для любого узла N, участвующего в лотерее окна W (active(N, W) = true): lottery_weight(N, W) ≥ 1. Деление ticket / lottery_weight в формуле weighted_ticket_node гарантированно определено.

Обоснование через composition временных порогов:

  • active_predicate = 2τ₂ (26 000 окон): неактивные узлы исключены из лотереи
  • pruning_idle_windows = 4τ₂ (52 000 окон): полностью неактивные узлы удалены из Node Table
  • chain_length_snapshot window = 6τ₂ (78 000 окон): горизонт снапшота

Ordering 2τ₂ < 4τ₂ < 6τ₂ гарантирует: узел либо active (публикует BC → chain_length растёт → snapshot ≥ 1), либо inactive (исключён из лотереи), либо pruned (удалён из Node Table до того как snapshot мог бы упасть до 0). Сценарий «active узел с snapshot = 0» невозможен по построению.

Инвариант ОБЯЗАТЕЛЕН для enforcement в apply_proposal: при вычислении weighted_ticket_node валидатор проверяет lottery_weight > 0. Нарушение = protocol violation, proposal отклоняется. Нарушение указывает на баг в pruning или active_predicate — consensus critical.

Разделение весов:

  • Лотерея (эмиссия): lottery_weight = chain_length_snapshot + seniority_bonus. Недавняя работа (snapshot) доминирует, longevity даёт bounded bonus.
  • Quorum (безопасность): абсолютный chain_length. Старожилы доминируют в финализации.

Endpoint узла вычисляется детерминированно из канонических данных:

endpoint_node(W) = SHA-256(
  "mt-lottery" ||
  T_r(W) ||
  cemented_bundle_aggregate(W-2) ||
  node_id ||
  window_index
)

Где:

  • T_r(W) — TimeChain VDF output окна W (каноничен, одинаков у всех узлов).
  • cemented_bundle_aggregate(W-2) — агрегат подписей cemented BundledConfirmation окна W-2 (см. раздел BundledConfirmation). Lookback на 2 окна: cemented set окна W-2 зафиксирован в proposal_{W-1}, канонически финализирован к концу окна W. Все узлы используют одно значение.

Endpoint верифицируем за O(1) — один SHA-256, плюс lookup cemented_bundle_aggregate(W-2) из уже финализированного state.

Grinding resistance. Атакующий с VDF hardware advantage способен пре-вычислить T_r(W) на много окон вперёд. Но cemented_bundle_aggregate(W-2) содержит FN-DSA-512 подписи будущих confirmers — их privкey не у атакующего, aggregate непредсказуем offline. Grinding по node_id (выбор keypair с favorable future endpoints) не работает: endpoint зависит от canonical-но-непредсказуемого компонента. Горизонт grinding схлопывается до уже cemented (публично известного) окна W-2, где keypair уже зафиксирован.

Если weighted_ticket_node < target — узел кандидат и публикует VDF_Reveal:

VDF_Reveal:
  node_id          32B
  window_index      4B     <- индекс τ₁
  endpoint         32B     <- SHA-256("mt-lottery" || T_r(W) || cemented_bundle_aggregate(W-2) || node_id || window_index)
  signature       666B     <- FN-DSA-512 над signed_scope(reveal) (Правило R1);
                              проверяется Node Table[node_id].node_pubkey
Итого:       ~734B

reveal_hash = identifier(reveal) с class domain "mt-vdf-reveal" (Правило R2).

Инварианты VDF_Reveal:

  • node_id существует в Node Table и активен в окне window_index (last_confirmation_window в пределах active predicate)
  • window_index равен текущему окну лотереи (reveal не может относиться к произвольному окну)
  • endpoint == SHA-256("mt-lottery" || T_r(window_index) || cemented_bundle_aggregate(window_index - 2) || node_id || window_index_le) — каноничный (верифицируем сравнением)
  • weighted_ticket_node(node_id, window_index) < target(window_index) — узел прошёл порог кандидатства (иначе reveal не имеет лотерейного смысла, reject)
  • Один VDF_Reveal per (node_id, window_index) — повторный отклоняется (equivocation)
  • Signature FN-DSA-512 valid over signed_scope(reveal) против Node Table[node_id].node_pubkey (Правило R1)

Любой активный узел может стать кандидатом лотереи — lottery_weight основан на недавней работе (snapshot 6τ₂), старожилы получают bounded seniority bonus.

Аккаунты не участвуют в лотерее

Аккаунты не участвуют в лотерее эмиссии (Лестница суверенности — design choice — см. «Два пути участия»). Единственный protocol-level earning path — node lottery. Пользователи-аккаунты используют сеть (Transfer, Anchor, NicknameBid, ChangeKey, CloseAccount, Premium Subscriptions, Credits); заработок TC через lottery возможен только после перехода к роли оператора узла (Шаг 1 Лестницы суверенности).

Этот design выбор обоснован:

  1. [I-5] commodity hardware. Scarce contribution — узел (uptime, validation, gossip, hosting). Account activity — abundant ресурс (любой смартфон). Эмиссия направляется на scarce contribution, не на abundant.
  2. [I-7] минимальная крипто-поверхность. Удаление лотереи аккаунтов устраняет 4 binding test vectors, формулу weighted_ticket_account, поле operation_for_lottery, дополнительную ветвь apply_proposal, снижает audit surface.
  3. [I-10] SSOT. Один reward path (node lottery) вместо двух. Исключает случаи дрifta между двумя формулами эмиссии.
  4. Лестница суверенности — ясность. Двойная выплата (account + node) размывала primary incentive перехода в оператора; одна выплата (только node) делает upgrade единственным protocol-level earning шагом.

operation_for_lottery, weighted_ticket_account, WINNER_CLASS_ACCOUNT, DS-3 инвариант, domain separator "mt-account-lottery", раздел «Валидация лотерейного билета аккаунта» — удалены (breaking change; pre-mainnet допустим). Поле account_chain_length_snapshot сохранено как anti-Sybil threshold в NicknameBid; только лотерейное применение удалено.

Экономическая модель (numeric equilibrium burn ↔ emission) формализуется в разделе «Экономическая модель» (см. ниже) с conservative projection user growth / node growth / TC price sensitivity.

Определение winner-а (Lookback Leadership)

Winner окна W-1 определяется при cementing proposal окна W. Proposer окна W = winner окна W-2 (канонически известен из cemented state).

Механика:

  1. Окно W-1 завершается: confirmers публикуют BundledConfirmation_{W-1} (операции окна W-1 + VDF_Reveals окна W-2), кандидаты публикуют VDF_Reveal_{W-1}, аккаунты публикуют операции.
  2. proposer_W = winner_{W-2} (канонически определён из proposal_{W-1}).
  3. Окно W начинается. Confirmers получают VDF_Reveals_{W-1} через P2P и включают их в BundledConfirmation_W наряду с операциями окна W. VDF_Reveal идентифицируется по window_index = W-1.
  4. VDF_Reveal_{W-1} cemented когда confirmers с суммарным chain_length ≥ 67% active_chain_length включили его в свои BundledConfirmation_W. Cement status каноничен — каждый узел отслеживает его независимо по P2P bundles.
  5. Proposer_W собирает BundledConfirmation-ы окна W-1 и cemented set:
    included_bundles_{W-1} = BundledConfirmation-ы окна W-1 из view proposer-а
                             (суммарный chain_length ≥ 67% active_chain_length)
    included_reveals_{W-1} = VDF_Reveal-ы окна W-1, cemented через
                             BundledConfirmation окна W (67% active_chain_length)
    
  6. Из included_reveals_{W-1} извлекаются все node endpoints (только node candidates; cemented account operations из included_bundles_{W-1} релевантны для apply_proposal по балансам/nickname/credits, но НЕ участвуют в лотерее).
  7. winner_{W-1} = argmin(weighted_ticket_node) среди cemented VDF_Reveal узлов-кандидатов окна W-1.
  8. Proposer_W публикует proposal_W, содержащий:
    • included_bundles_{W-1} (canonical view финализации)
    • included_reveals_{W-1} (cemented set лотереи)
    • winner_{W-1} (получатель reward(W-1) за окно W-1)
    • control_set, state_root, Монтана transfer
  9. Сеть валидирует proposal_W:
    • Proposer = winner_{W-2}? (канонически проверяемо)
    • included_bundles содержат ≥ 67% active_chain_length? (проверяемо из Node Table)
    • included_reveals_{W-1} = cemented set VDF_Reveals окна W-1? (валидатор сверяет с собственным tracking cement status из BundledConfirmation окна W)
    • winner_{W-1} = argmin из (included_reveals account_candidates)? (детерминированно проверяемо)
    • state_root корректен? (независимый пересчёт)
  10. Если 67% active_chain_length подписывают proposal_W → proposal cemented. Winner_{W-1} получает reward(W-1) Ɉ. Winner_{W-1} становится proposer_{W+1}.
  11. Если < 67% подписали → proposal отклонён. Fallback: fallback_proposer_W = second_min(weighted_ticket) окна W-2. Fallback cascade: third_min, fourth_min, etc.

Cross-window cementing timeline. VDF_Reveals окна W-1 публикуются при завершении окна W-1 (VDF computation = window duration). Цементируются в BundledConfirmation окна W. Между публикацией reveals и сборкой proposal — целое окно. Timing constraint отсутствует.

Leader skin in the game. Proposer_W публикует свой VDF_Reveal для окна W. Если его proposal отклонён (< 67% подписей сети), его VDF_Reveal исключается из пула кандидатов окна W. Потеря lottery ticket = экономический кнут за цензуру или бездействие. Отказ подписать proposal = implicit rejection от каждого узла.

Genesis bootstrap. proposer_0 и proposer_1 = bootstrap-узел (единственный в Genesis Decree). Начиная с proposer_2 = winner_0, стандартная lookback логика.

Калибровка target

Target калиброван на ~13 кандидатов VDF_Reveal за окно. Калибровка на τ₂:

Real-valued form (commentary):

target_new = target_old × (13 / actual_candidates_per_window)
actual_candidates_per_window = total_reveals_за_τ₂ / τ₂_windows

Integer form (authoritative, per [I-9]):

target is u128 (Q128.0 representation of weighted_ticket threshold; кандидат если weighted_ticket < target).

Input:
  target_old: u128
  total_reveals_τ₂: u64      (сумма VDF_Reveals за τ₂ окон)
  τ₂_windows: u64            (константа из ProtocolParams — см. Genesis Decree)

Output:
  target_new: u128

Algorithm:
  если total_reveals_τ₂ == 0:
    # нет кандидатов — target не калибруется
    target_new = target_old
  иначе:
    # target_new = target_old × 13 × τ₂_windows / total_reveals_τ₂
    # Порядок: multiply сначала (preserve precision), divide в конце.
    numerator_u256 = (target_old as u256) * 13u256 * (τ₂_windows as u256)
    target_new_u256 = numerator_u256 / (total_reveals_τ₂ as u256)
      # Unsigned integer div toward zero.
    если target_new_u256 > u128::MAX as u256:
      target_new = u128::MAX        # saturating clamp
    иначе:
      target_new = target_new_u256 as u128

Note: u256 intermediate реализуется через big-int (2× u128 chunked arithmetic) — byte-exact алгоритм.

Binding test vectors (byte-exact; используют target_old = 2^127 = 0x80..00, τ₂_windows = 20160):

  # TA1 equilibrium (actual candidates = expected 13/window) — target unchanged
  target_old = 0x80000000000000000000000000000000
  total_reveals_τ₂ = 262080  (= 13 × 20160)
  → target_new = 0x80000000000000000000000000000000

  # TA2 2× over-participation — target halved (harder to win)
  target_old = 0x80000000000000000000000000000000
  total_reveals_τ₂ = 524160  (= 26 × 20160)
  → target_new = 0x40000000000000000000000000000000

  # TA3 1/13 participation — target ×13 (saturated at u128::MAX)
  target_old = 0x80000000000000000000000000000000
  total_reveals_τ₂ = 20160
  → target_new = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF  (saturated)

  # TA4 zero reveals — target unchanged (no calibration signal)
  target_old = 0x80000000000000000000000000000000
  total_reveals_τ₂ = 0
  → target_new = 0x80000000000000000000000000000000

  # TA5 single reveal — saturated maximum
  target_old = 0x80000000000000000000000000000000
  total_reveals_τ₂ = 1
  → target_new = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF  (saturated)

Conformance status: closed (binding test vectors выше).

Трафик reveal за окно: ~13 VDF_Reveal × 738B ≈ 9.6 KB (P2P gossip; далее включаются в BundledConfirmation для cementing). Аккаунты участвуют через cemented операции в BundledConfirmation — дополнительного трафика для аккаунтов нет.

Integer log algorithm (per [I-9], node lottery)

Алгоритм ln_q64(endpoint) → u128 используется в формуле weighted_ticket_node.

ln_q64(endpoint: [u8; 32]) -> u128    # Q64.64 representation of -ln(endpoint / 2^256)

Semantics: возвращает -ln(endpoint/2^256) × 2^64, округлённый toward zero.
  Малые endpoint → большие ticket; большие endpoint → малые ticket.
  Максимум: endpoint = 0 клипируется до u128::MAX (SHA-256 collision probability negligible).

Binding constants (Q64 fixed-point, unsigned u64; halved-polynomial form чтобы все
коэффициенты поместились в u64 даже если коэффициент полного полинома превышает 1):

  B0     = 0x0014E086EC982D63    # = (a0 / 2) × 2^64
  B1     = 0xB59DDDE52A69D000    # = (a1 / 2) × 2^64
  B2_ABS = 0x49DF5C3BFD9CEC00    # = (|a2| / 2) × 2^64
  B3     = 0x14417E56D3331800    # = (a3 / 2) × 2^64
  LN2_Q64 = 0xB17217F7D1CF79AB   # = ln(2) × 2^64, truncated toward zero

Где a0..a3 — degree-3 minimax polynomial (Remez equioscillating) для log2(1+y)
на y ∈ [0, 1):
  a0 = +0.00063711727233465817
  a1 = +1.41888021173219991411     (> 1 → не помещается в u64 при Q64; отсюда halved form)
  a2 = -0.57712891511184893911     (|a2| хранится как B2_ABS, знак embedded в Horner
                                     через subtract — unsigned arithmetic per [I-9])
  a3 = +0.15824870337964891398

Algorithm (byte-exact):
  1. e_u256 = big-endian interpretation of endpoint (32B)
  2. если e_u256 == 0: return u128::MAX  (SHA-256 collision probability negligible)
  3. leading = leading_zeros_u256(e_u256)                        # ∈ [0, 255]
  4. msb_position = 255 - leading                                # ∈ [0, 255]
  5. # Normalize mantissa в [2^127, 2^128):
     if msb_position >= 127:
       shift = msb_position - 127
       mantissa_u128 = (e_u256 >> shift) & ((1u256 << 128) - 1)  # low 128 bits
     else:
       shift = 127 - msb_position
       mantissa_u128 = (e_u256 << shift) & ((1u256 << 128) - 1)  # low 128 bits
  6. # Q64 fractional part ∈ [0, 1):
     x_q64 = ((mantissa_u128 - (1u128 << 127)) >> 63) as u64
  7. # log2(1 + y) approximation через unsigned Horner (halved-polynomial form).
     # half_p(y) = B0 + y·(B1 - y·(B2_ABS - y·B3))
     # p(y)      = log2(1+y) × 2^64  ≈  half_p(y) << 1
     #
     # Пошаговое unsigned вычисление:
     t1_u64  = ((B3 as u128) * (x_q64 as u128)) >> 64 as u64     # y·B3   ∈ [0, B3]
     # invariant_1: t1 ≤ B2_ABS  (доказано: B3 < B2_ABS, y ≤ 2^64-1)
     t2_u64  = B2_ABS - t1_u64                                   # B2_ABS - y·B3  ∈ [B2_ABS - B3, B2_ABS]
     t3_u64  = ((t2_u64 as u128) * (x_q64 as u128)) >> 64 as u64 # y·(B2_ABS - y·B3)  ∈ [0, B2_ABS]
     # invariant_2: t3 ≤ B1  (доказано: max t3 = B2_ABS - B3 < B1)
     t4_u64  = B1 - t3_u64                                       # B1 - y·(B2_ABS - y·B3)  ∈ [B1 - B2_ABS, B1]
     t5_u64  = ((t4_u64 as u128) * (x_q64 as u128)) >> 64 as u64 # y·(B1 - y·(B2_ABS - y·B3))  ∈ [0, B1]
     half_p_u64 = B0 + t5_u64                                    # ≤ B0 + B1 < 2^63
     frac_q64 = half_p_u64 << 1                                  # p(y) × 2^64 ∈ [0, 2^64]
     # При y близком к 2^64 (edge) frac_q64 может достичь 2^64 — но операция
     # half_p_u64 < 2^63 → shift безопасен, frac_q64 ≤ 2^64-2.
  8. # log2(2^256/e) = (leading+1) - log2(1+y), где y = (mantissa  2^127) / 2^127
     log2_q64_u128 = ((leading+1) as u128) << 64) - (frac_q64 as u128)
     # (leading+1) ∈ [1, 256], shift в u128 safe; frac_q64 ≤ 2^64-2; результат ≥ 2
  9. ticket_q64_128 = ((log2_q64_u128 as u256) * (LN2_Q64 as u256)) >> 64 as u128
     # u128 × u64 → u192 intermediate; shift >> 64 → u128 (старшие биты нулевые т.к.
     # log2_q64 ≤ 256·2^64 = 2^72, и log2_q64 × LN2_Q64 ≤ 2^72 × 2^64 = 2^136;
     # >> 64 → 2^72. Safe.
 10. return ticket_q64_128

Invariants proof:
- invariant_1 (t1 ≤ B2_ABS):
    t1 = (y_q64 × B3) >> 64 ≤ B3 (т.к. y_q64 ≤ 2^64 - 1 < 2^64).
    B3 = 0x14417E56D3331800 = 1,459,586,665,620,379,648 ≈ 0.079·2^64
    B2_ABS = 0x49DF5C3BFD9CEC00 = 5,323,074,697,302,961,152 ≈ 0.289·2^64
    B3 < B2_ABS ⟹ t1 ≤ B3 < B2_ABS. ✓
- invariant_2 (t3 ≤ B1):
    t3 = (t2 × y) >> 64 ≤ t2 ≤ B2_ABS. B2_ABS < B1. ✓

Error bound (degree-3 Remez minimax optimum):
- Абсолютная ошибка: |ln_q64(e)  2^64 · (ln(e/2^256))| ≤ 2^-10.62 × 2^64 ≈ 1.18·10^16
  в Q64.64 единицах. Это теоретический оптимум degree-3 polynomial на [0, 1);
  более высокая точность требует degree ≥ 7 (2^-28) или degree ≥ 15 (2^-56).

[I-8] reconciliation: approximation error даёт attacker grinding advantage
~0.13% of typical ticket — но grinding horizon уже ограничен конструктивно через
`cemented_bundle_aggregate(W-2)` в endpoint formula (см. раздел «VDF_Reveal» и
инвариант [I-8]). Attacker не может pre-compute future endpoint без privкey
honest confirmers окна W-2. Additional advantage через approximation error
dominated базовым [I-8]-bounded surface; net safety margin preserved. Degree-3
выбран как optimal trade-off complexity/precision для argmin лотереи: endpoints
uniform distributed на [0, 2^256), typical gap между соседними кандидатами
много больше 2^-10 log2-единиц.

Binding test vectors (byte-exact, для conformance tests независимых реализаций):

  # TV1: boundary low (smallest non-zero endpoint → largest ln)
  endpoint = 0x0000000000000000000000000000000000000000000000000000000000000001
  ln_q64   = 0x00000000000000b171fb06bb5b60c961

  # TV2: MSB only (endpoint = 2^255 → log2(2^256/2^255) = 1, ticket ≈ LN2_Q64)
  endpoint = 0x8000000000000000000000000000000000000000000000000000000000000000
  ln_q64   = 0x0000000000000000b15526e15db6980c

  # TV3: typical dense pattern
  endpoint = 0xbbaa998877665544332211ffeeddccbbaa998877665544332211ffeeddccbbaa
  ln_q64   = 0x00000000000000004f60bd6fe6504646

  # TV4: near max (endpoint = 2^256-1 → log2(2^256/e) ≈ 0, ticket ≈ 0)
  endpoint = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
  ln_q64   = 0x00000000000000000000000000000000

  # TV5: peak-error region (y ≈ 0.84, attacker-favorable peak)
  endpoint = 0xeb851eb851eb8400000000000000000000000000000000000000000000000000
  ln_q64   = 0x000000000000000015756c980b547a82

Conformance: closed (binding coefficients + 5 test vectors выше).

Свойства для consensus:
- Monotonic decreasing в endpoint: e1 < e2 ⟹ ln_q64(e1) ≥ ln_q64(e2)
- Deterministic byte-exact: same bytes input → same u128 output на любом hardware
- Unsigned arithmetic по всей цепочке (требование [I-9]); знак a2 embedded через
  subtract в Horner, intermediate инварианты доказаны non-negative
- Bounded absolute error: 2^-10.62 (degree-3 Remez minimax optimum)

Валидация VDF_Reveal

  1. Подпись FN-DSA-512 соответствует node_pubkey из Node Table
  2. window_index = текущий τ₁
  3. node_id существует в Node Table
  4. weighted_ticket < target
  5. endpoint верифицируем: SHA-256("mt-lottery" || T_r || node_id || window_index) = заявленному endpoint

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

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

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

Тип Описание Валидация
Transfer Публичный перевод существующему аккаунту FN-DSA-512 подпись, prev_hash, sender != receiver, amount > 0, sender.balance >= amount, получатель существует в Account Table
TransferActivation Перевод с активацией нового AccountRecord FN-DSA-512 подпись sender, prev_hash, receiver не существует, receiver == H("mt-account" || suite_id || receiver_pubkey), amount > 0, sender.balance >= amount, cooldown current_window >= sender.last_activation_window + τ₂_windows per [I-15]
ChangeKey Смена ключа FN-DSA-512 подпись старым ключом, new_pubkey
Anchor Якорь данных ко времени FN-DSA-512 подпись, prev_hash, app_id = 32B, data_hash = 32B
NicknameBid Ставка в аукционе никнейма см. раздел «Nickname Auction Protocol»
PurchaseCredits Покупка кредитов для off-chain услуг см. раздел «Сервисные кредиты»
ConsumeCredits Списание кредитов при использовании услуги см. раздел «Сервисные кредиты»
CloseAccount Явное закрытие аккаунта см. раздел «Жизненный цикл аккаунта»

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

Тип Описание Валидация
NodeRegistration Регистрация узла (кандидатура) FN-DSA-512 подпись над signed_scope (Правило R1), node_id уникален (не в Node Table и не в Candidate Pool), operator_account_id существует, proof_endpoint верифицируем через VDF от candidate_vdf_init. nodereg_hash = identifier(nr) с class domain "mt-nodereg" (Правило R2)

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

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

Proposal

Proposal содержит control_set и метаданные окна. UserObjects применяются к Account Table батчем при settle (apply at window close); в proposal они не повторяются. ControlObjects применяются к Node Table в apply_proposal step 1 в детерминированном порядке.

control_set(proposal окна W) определён формулой:

control_set = {
  ControlObject c :
    c.cemented_window > previous_proposal.window
    AND c.cemented_window <= W
}

сортировка: (cemented_window asc, op_hash lex asc)

Где previous_proposal.window — окно предыдущего финализированного proposal в цепочке. Множество детерминировано: cemented_window — каноническое поле объекта (известно всем узлам через BundledConfirmation), op_hash — детерминирован.

Победитель обязан включить весь control_set целиком. Пропуск или добавление лишнего ControlObject = невалидный proposal = fallback. Каждый узел независимо вычисляет ожидаемый control_set по той же формуле и сравнивает с proposer's set.

Форки аккаунтов (две операции с одним prev_hash) разрешаются голосованием узлов весом chain_length. 67% active_chain_length за одну операцию → побеждает (см. раздел «Двойная трата»).

Закрытие окна (Lookback Leadership Finalization)

Window W-1:  confirmers publish BundledConfirmation_{W-1}
               (W-1 operations + W-2 VDF_Reveals)
             VDF_{W-1} completes → candidates publish VDF_Reveal_{W-1}
             accounts publish cemented operations
                              │
Window W:    confirmers publish BundledConfirmation_W
               (W operations + W-1 VDF_Reveals)
             W-1 VDF_Reveals cemented (67% active_chain_length)
                              │
             proposer_W = winner_{W-2} (canonical from proposal_{W-1})
             proposer_W extracts cemented reveals → winner_{W-1}
             proposer_W publishes proposal_W
                              │
                              ▼
                    ┌───────────────────────────────┐
                    │ proposal_W validation         │
                    │ included_bundles ≥ 67%?       │
                    │ included_reveals = cemented?  │
                    │ winner_{W-1} = argmin?        │
                    │ state_root correct?           │
                    └───────────┬───────────────────┘
                                │ 67% sign
                                ▼
                      proposal_W cemented
                      winner_{W-1} receives reward(W-1) Ɉ
                      winner_{W-1} = proposer_{W+1}
  • Lookback Leader. proposer_W = winner_{W-2} — канонически определён из cemented proposal_{W-1}. Каждый узел вычисляет proposer_W детерминированно из canonical state.
  • Cemented reveals. VDF_Reveals окна W-1 публикуются при завершении W-1, цементируются чер<D0B5><D180>з BundledConfirmation окна W (confirmers включают полученные reveals в свои bundles). included_reveals_{W-1} = cemented set (67% active_chain_length). Proposer извлекает cemented reveals и cemented account operations из included_bundles_{W-1}, определяет winner_{W-1} = argmin(weighted_ticket). Дискреция proposer-а над составом лотереи = ноль.
  • Canonical acceptance. Сеть валидирует proposal_W: (a) proposer = winner_{W-2}, (b) included_bundles ≥ 67% active_chain_length, (c) included_reveals = cemented set VDF_Reveals окна W-1, (d) winner_{W-1} = argmin из (cemented reveals account_candidates), (e) state_root корректен. Если 67% active_chain_length подписывают proposal_W → cemented. Canonical set зафиксирован.
  • Leader skin in the game. Proposer_W участвует в лотерее окна W через свой VDF_Reveal (cemented в BundledConfirmation окна W+1). При отклонении proposal (< 67% подписей) — VDF_Reveal proposer-а исключается из пула кандидатов окна W. Отказ подписать proposal = implicit rejection. Отдельного censorship vote нет.
  • Fallback cascade. Если proposal от proposer_W отклонён или отсутствует, роль переходит к fallback_1 = second_min(weighted_ticket) окна W-2, затем third_min, etc. Все канонически известны из cemented state.
  • ControlObjects. ControlObjects попадают в control_set proposal по моменту cement — canonically deterministic.

Свойство темпа сети. Сеть продвигается со скоростью медианного активного набора узлов. Quorum требует подписей большинства по chain_length — быстрейший узел ждёт, пока достаточно других успеет. Hardware progress ускоряет сеть естественно — когда ускоряется медиана, participation_ratio растёт выше 0.95, D адаптивно увеличивается.

One-window lag награды. reward(W-1) за окно W-1 зачисляется winner_{W-1} при cementing proposal_W. Задержка в одно окно между завершением работы и получением награды.

Proposer (Lookback Leader)

proposer_W = winner_{W-2} — канонически определён из cemented proposal_{W-1}. Proposer собирает proposal_W:

  • included_bundles_{W-1}: BundledConfirmation окна W-1 (суммарный chain_length ≥ 67% active_chain_length). Из included_bundles извлекаются cemented account operations для apply_proposal (баланс, nickname, credits, account_chain_length increment); account operations не участвуют в лотерее (см. «Аккаунты не участвуют в лотерее»).
  • included_reveals_{W-1}: VDF_Reveals окна W-1, cemented через BundledConfirmation окна W (67% active_chain_length). Из cemented reveals определяется winner_{W-1} (получатель reward(W-1) за окно W-1). Лотерея single-class — winner всегда узел; cemented account operations окна W-1 не участвуют в выборе winner-а.
  • control_set: все cemented ControlObjects в окнах (previous_proposal.window, W]. Свобода = ноль (каноничен).
  • State Root snapshot: account_root, node_root и candidate_root после apply at window close (все cemented операции + control objects + selection event + Монтана transfer to winner_{W-1} применены батчем).

Свобода proposer: included_bundles ограничены порогом 67%. included_reveals детерминированы cement status-ом. control_set детерминирован формулой. State root и winner_{W-1} вычисляются из cemented sets — каждый валидатор проверяет корректность детерминированно.

Proposal с набором included_bundles < 67% active_chain_length, неверным included_reveals (не совпадает с cemented set), неверным winner_{W-1}, пропущенным cemented ControlObject, или неверным state_root отклоняется → fallback на second_min(weighted_ticket) окна W-2.

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

Финальность proposal = подпись proposer_node_id на proposal header (верифицируемая против Node Table[proposer_node_id].node_pubkey) + независимая верифицируемость состояния.

  1. Proposer (proposer_node_id) публикует подписанный proposal header + control_set
  2. Каждый узел проверяет window_index == prev_proposal.window_index + 1, protocol_version >= prev_proposal.protocol_version и protocol_version <= local_max_supported_version
  3. Каждый узел независимо вычисляет ожидаемый control_set по формуле и сравнивает с proposer's
  4. Каждый узел применяет control_set + Монтана детерминированно в порядке (cemented_window asc, op_hash lex asc)
  5. Каждый узел сравнивает вычисленный state_root с заявленным в proposal
  6. Совпадает — proposal принят
  7. Не совпадает — proposal отклонён, fallback на второе место

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

Proposal header:

Proposal header:
  prev_proposal_hash    32B
  window_index           8B    <- u64, индекс окна τ₁ с genesis; == prev_proposal.window_index + 1
  protocol_version       4B    <- u32, активная версия протокола на момент window_index
  control_root          32B    <- Merkle root control_set (каноничен)
  node_root             32B    <- Merkle root Node Table (обновляется каждое окно)
  candidate_root        32B    <- Merkle root Candidate Pool
  account_root          32B    <- Merkle root Account Table после apply at window close
  state_root            32B    <- SHA-256("mt-state-root" || node_root || candidate_root || account_root)
  timechain_value       32B
  included_bundles_root 32B    <- Merkle root списка (confirmer_id, bundle_hash)
                                  BundledConfirmation окна W-1 (≥ 67% active_chain_length)
  included_reveals_root 32B    <- Merkle root списка VDF_Reveal-ов окна W-1,
                                  cemented через BundledConfirmation окна W
  winner_endpoint       32B    <- endpoint winner-а окна W-1 (node lottery)
  winner_id             32B    <- получатель `reward(W-1)` за окно W-1: node_id узла-winner-а
  proposer_node_id      32B    <- winner_{W-2}, канонически определённый из proposal_{W-1}
  target                16B    <- u128 Q64.64 (per [I-9] P5), текущий target лотереи
  fallback_depth         1B    <- u8, 1 = первое место, 2..=255 = fallback cascade;
                                  fallback_depth = 255 без успеха → network halt by liveness (не safety)
  signature            666B    <- FN-DSA-512 над signed_scope(header) (Правило R1);
                                  проверяется Node Table[proposer_node_id].node_pubkey.
                                  proposal_hash = identifier(header) с class domain "mt-proposal" (Правило R2)

Все поля proposal header канонически вычислимы bit-exact из предыдущего state и cemented set окна W. Каждое поле имеет источником либо canonical state, либо детерминированную функцию от canonical state.

Разделение ролей winner_id и proposer_node_id. Это два независимых поля с разными назначениями:

  • winner_id — получатель Монтана. Лотерея single-class: winner — всегда узел, выигравший лотерею окна. reward(W-1) зачисляется на operator_account_id узла-winner-а в apply_proposal step 2.
  • proposer_node_id — узел ответственный за сборку и публикацию proposal. Подписывает header своим node_pubkey. Верификация подписи proposal — против Node Table[proposer_node_id].node_pubkey, всегда.

Штатный случай: winner_id == proposer_node_id (узел-winner сам собирает свой proposal). Fallback: если winner-узел молчит — proposal собирает следующий узел по lowest weighted_ticket, proposer_node_idwinner_id; reward всё равно зачисляется на operator_account_id winner-а, proposer не получает дополнительной награды — это его обязанность как ближайшего активного узла.

Инварианты Proposal header:

  • window_index == prev_proposal.window_index + 1 (монотонность, шаг 1)
  • protocol_version >= prev_proposal.protocol_version (не убывает; изменяется только через software upgrade узла, см. раздел «Эволюция протокола»)
  • protocol_version <= local_max_supported_version (узел обязан отклонить proposal с protocol_version которую его реализация не поддерживает; принятие неизвестной версии = принятие непроверяемых правил = нарушение безопасности)
  • fallback_depth ≥ 1 (1 = canonical proposer, 2..=255 = fallback cascade per layout выше; fallback_depth = 0 — reject)
  • proposer_node_id существует в Node Table и имеет suite_id соответствующую поддерживаемой схеме подписи; signature FN-DSA-512 verify over signed_scope(header) против Node Table[proposer_node_id].node_pubkey (Правило R1)

Cemented window объекта — window_index proposal-а в котором BundledConfirmation с этим объектом достиг quorum. Определён детерминированно для каждого cemented объекта.

Settled window объекта — window_index proposal-а в котором объект был применён к state:

  • Для UserObjects: settled_window = cemented_window (apply batch at window close того же окна). Следующая операция от того же sender возможна в окне cemented_window + 1 (dependency rule)
  • Для ControlObjects: settled_window = window_index первого proposal где объект попал в control_set (обычно cemented_window + 1)

Fallback: если proposal от proposer_W = winner_{W-2} отклонён (< 67% подписей) или отсутствует (proposer offline), роль переходит к fallback_1 = second_min(weighted_ticket) окна W-2. Если fallback_1 тоже отклонён — к third_min, и т.д. Вся cascade канонически определена из cemented state окна W-2.

При fallback proposer_node_id меняется; winner_{W-1} определяется fallback-proposer-ом из cemented set (тот же cemented set — canonical для всех узлов). Новый proposer подписывает header своим node_pubkey, fallback_depth инкрементируется.

Leader penalty при отклонении: endpoint proposer-а, чей proposal отклонён, исключается из lottery пула текущего окна W. Proposer теряет шанс на reward(W). Это экономический кнут за бездействие или цензуру.

Полная симметрия fallback: молчание первого proposer переводит обязанность сборки proposal к следующему узлу. Награда за окно W-1 привязана к лотерейному билету и гарантирована, если хотя бы один узел в сети соберёт валидный proposal через fallback cascade.

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

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

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

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

active_chain_length(W) = Σ node.chain_length
                         для node ∈ Node Table : active(node, W)

confirmation_threshold(W) = active_chain_length(W) / 256
≈ 256 confirmers при large-scale сети (active_chain_length / 256).

Только активные узлы (cemented BundledConfirmation за последние 2τ₂) учитываются. Мёртвый вес исключён конструкцией. Сканирование Node Table для вычисления active_chain_length — O(|Node Table|) ≤ 10⁵ записей, миллисекунды.

Сенатская модель комитета. Confirmers — сенат долгоживущих узлов, не ротирующаяся выборка из активного набора. Узел попадает в комитет только накопив chain_length выше порога; это намеренная долгосрочная инерция роли, не недостаток механизма. Разделение ролей в протоколе:

  • Confirmers (комитет) — долгоживущие узлы, голосуют за финализацию и разрешение конфликтов.
  • Все активные узлы — участвуют в node lottery, gossip, хранят данные, обслуживают своих операторов. Новые узлы полнофункциональны как инфраструктура с момента установки (см. раздел «Barrier scope»), но в комитет попадают только после накопления chain_length.

Требование к развёртыванию: доля онлайн-работы честного оператора ≥ 0.85. Это условие гарантирует что концентрация атакующего в top-K комитете ограничена коэффициентом не более 1.18× от его доли в сети. При доле атакующего в сети f ≤ 0.25 и соблюдении этого требования доля атакующего в комитете ≤ 0.282, что ниже порога BFT 1/3. Нарушение требования (оператор с доступностью ниже 67%) открывает вектор захвата комитета через асимметрию времени работы.

Confirmer собирает все валидные объекты за окно и публикует один BundledConfirmation. Bundle содержит два класса хэшей: (1) операции текущего окна W (UserObjects + ControlObjects) и (2) VDF_Reveals предыдущего окна W-1 (лотерейные билеты, опубликованные при завершении W-1 и полученные через P2P):

BundledConfirmation:
  node_id           32B
  endpoint          32B     <- T_r текущего окна (доказывает timeliness)
  window_index       4B
  op_count           2B     <- u16 LE, explicit count prefix
  op_hashes[]       op_count × 32B    <- identifier(op) с class "mt-op" для UserObjects и ControlObjects окна W
  reveal_count       2B     <- u16 LE, explicit count prefix
  reveal_hashes[]   reveal_count × 32B <- identifier(reveal) с class "mt-vdf-reveal" окна W-1
  signature         666B     <- FN-DSA-512 над signed_scope(bundle) (Правило R1);
                                проверяется Node Table[node_id].node_pubkey

bundle_hash = identifier(bundle) с class domain "mt-bundle" (Правило R2). Один BundledConfirmation per (node_id, window_index). Повторный отклоняется. Endpoint = T_r текущего окна (верифицируем: сравнение с каноническим T_r). node.chain_length хранится в Node Table и инкрементируется в apply_proposal шаг 3.5 для каждого узла с cemented BundledConfirmation в окне W.

Инварианты BundledConfirmation:

  • node_id существует в Node Table и соответствует активному confirmer-у (chain_length >= confirmation_threshold на момент окна window_index)
  • window_index равен текущему окну валидации (bundle не может относиться к произвольному окну)
  • endpoint == T_r(window_index) каноничный (верифицируем сравнением с локально вычисленным T_r)
  • op_count ≤ max_ops_per_bundle (верхняя граница DoS; значение константы — см. раздел «Обоснование протокольных констант»)
  • reveal_count ≤ max_reveals_per_bundle (верхняя граница DoS)
  • Каждый элемент op_hashes[i] — 32B identifier(op) с class domain "mt-op"; дубликаты внутри массива запрещены
  • Каждый элемент reveal_hashes[i] — 32B identifier(reveal) с class domain "mt-vdf-reveal" окна W-1; дубликаты запрещены
  • Один BundledConfirmation per (node_id, window_index) — повторный отклоняется (equivocation, см. раздел «Конфликты»)
  • Signature FN-DSA-512 valid over signed_scope(bundle) против Node Table[node_id].node_pubkey (Правило R1)

Inclusion validity каждой операции внутри bundle (dependency rule: prev_hash, баланс, receiver existence) — см. раздел «Dependency rule» ниже; это per-context check confirmer-а, отдельный от structural инвариантов BundledConfirmation.

Объект финализирован (cemented) когда подтверждения от confirmers с суммарным chain_length > quorum. Cemented — необратимо. Типичное время: quorum event. Это правило применяется одинаково к UserObjects, ControlObjects и VDF_Reveals: cemented status объективен и каноничен для всех узлов. VDF_Reveals окна W-1 цементируются в BundledConfirmation окна W (cross-window cementing).

Confirmation cutoff (детерминизм cemented set). Cemented set окна W фиксируется proposer-ом окна W+1 через frozen view (Lookback Leadership). Proposer_{W+1} включает в proposal_{W+1} все BundledConfirmation окна W из своего view с суммарным chain_length ≥ 67% active_chain_length. Этот frozen view становится каноническим cemented set после cementing proposal_{W+1} сетью.

Cemented bundle aggregate. Канонический агрегат идентичностей confirmers окна W, используемый как unpredictable-offline компонент в формулах lottery endpoint, sort_key и candidate_vdf_init. Aggregate строится по Правилу R3 (aggregate over signer_node_id, не over signatures и не over content):

cemented_bundle_aggregate(W) :=
  если W < 2:
    0x00 × 32                                    (до Genesis cementing)
  иначе если |cemented_bundles_W| == 0:
    SHA-256("mt-bc-aggregate-empty" || W.to_le_bytes_8)        (вырожденный случай: окно без cementing)
  иначе:
    S_W := { bc.node_id : bc ∈ cemented_bundles_W }
    SHA-256(
      "mt-bc-aggregate" ||
      concat(node_id for node_id in sorted_asc(S_W)) ||
      W.to_le_bytes_8
    )

cemented_bundles_W — каноническое множество cemented BundledConfirmation окна W (frozen view proposer_{W+1}). S_W — множество signer_node_id этих bundles, отсортированное по asc (32B lexicographic). Контекст W.to_le_bytes_8 — 8-байтовый little-endian window_index.

Ветви формулы покрывают все возможные состояния окна:

  • W < 2: Genesis окна, cemented_bundle_aggregate(W-2) не существует — возвращается фиксированный 0x00 × 32.
  • |cemented_bundles_W| == 0: окно без cementing (катастрофический отказ консенсуса). Возвращается детерминистический fallback. [I-8] в этой ветви вырожден, но в non-functional состоянии сети это приемлемо — protocol уже не производит консенсус.
  • Стандартная ветвь: агрегат node_ids cemented confirmers, полная защита [I-8].

Свойства:

  • Канонический. Cemented set объективен, порядок детерминирован. Два честных узла bit-exact получают одинаковое значение.
  • Непредсказуемый offline (в стандартной ветви). Зависит от эмерджентного состава S_W — какие именно active confirmers набрали quorum. Атакующий с VDF hardware advantage не может пре-вычислить будущий S_W без координированного control over honest participants (никто single confirmer не контролирует набор других cemented confirmers).
  • Ноль grinding surface для single confirmer. node_id detrministically вычислен из registered node_pubkey (commited в NodeTable), не меняется. Content бандла (op_hashes[], reveal_hashes[]) attacker-choose-able, но исключён из aggregate per Правило R3. Signature σ non-deterministic Falcon, но исключена из aggregate per Правило R3. Обе grinding surface устранены конструкцией, не экономическими аргументами.
  • Degraded security margin в bootstrap периоде. При active_nodes = 1 агрегат содержит один node_id. Безопасность в этот период опирается на секретность bootstrap node_pubkey derivation — см. раздел «Границы модели доверия».

Dependency rule (детерминизм apply). Одно правило: confirmer подтверждает операцию только если все её зависимости разрешены из settled state окна W-1.

Операция валидна для inclusion в BundledConfirmation окна W если:
  1. prev_hash == Account Table[sender].frontier_hash
     на момент settled state конца окна W-1
  2. Для Transfer: receiver существует в Account Table
     на момент settled state конца окна W-1
  3. sender.balance >= amount (для Transfer)
     на момент settled state конца окна W-1

Settled state конца окна W-1 — результат apply_proposal окна W-1 — одинаков у всех узлов (детерминированная функция от cemented set W-1 и предыдущего state). Confirmer проверяет каждую операцию против этого глобально единого состояния. Никаких bundle-local цепочек, никакого mempool order.

Следствие: одна операция на аккаунт за окно τ₁. Вторая операция от того же sender имеет prev_hash = H(первой операции), но первая ещё не settled (settled = конец текущего окна W). Confirmer отклоняет вторую. Она пройдёт в окне W+1 когда первая settled. Throughput на аккаунт: 1 операция за окно. Это достаточно для всех бытовых сценариев; для высокочастотных — batching через Anchor (один Anchor содержит Merkle root тысяч записей).

Cross-account зависимости сериализуются через окна — sponsor создаёт AccountRecord получателя через TransferActivation в окне W; последующие исходящие операции от этого получателя (Transfer, Anchor, NicknameBid и т.д.) — в окнах W+1 и далее, после settle AccountRecord.

Real-valued form (commentary):

quorum(W) = ⌈0.67 × active_chain_length(W)⌉

Integer form (authoritative, per [I-9]):

quorum(W): u64
Input:  active_chain_length(W): u64
Algorithm:
  quorum(W) = (67u64 * active_chain_length(W) + 99u64) / 100u64
    # Unsigned u64 arithmetic; integer div toward zero.
    # +99 реализует ceiling для division на 100.
Overflow: active_chain_length ≤ 10^14 (node cap × chain cap);
          67 × 10^14 + 99 ≈ 6.7 × 10^15 < 2^63 ⇒ safe u64.

Test vectors (binding):
  active_chain_length = 1      → quorum = 1       ((67 + 99) / 100 = 1)
  active_chain_length = 100    → quorum = 67      ((6700 + 99) / 100 = 67)
  active_chain_length = 149    → quorum = 100     ((9983 + 99) / 100 = 100)
  active_chain_length = 150    → quorum = 101     ((10050 + 99) / 100 = 101)
  active_chain_length = 1000   → quorum = 670     ((67000 + 99) / 100 = 670)

[I-9] статус: закрыто (test vectors in spec).

Объект cemented когда суммарный chain_length confirmers подтвердивших объект через BundledConfirmation окна W ≥ quorum(W). Активный набор детерминирован — все узлы вычисляют active_chain_length(W) независимо из state Node Table и получают одно и то же значение.

Если active_chain_length падает ниже минимума жизнеспособности (теоретически возможно при массовом offline) — финализация останавливается до восстановления активности. Halt by liveness, не by safety: вернувшиеся узлы возобновляют работу с последнего cemented state.

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

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

State transition

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

Применение операций по window close. Cemented операции окна W буферизуются до момента сборки proposal_{W+1}. Множество cemented операций фиксируется proposer-ом через frozen view (Lookback Leadership). Все cemented операции окна W применяются батчем в детерминированном порядке:

Порядок apply: по op_hash lex asc

Каждый аккаунт имеет максимум одну cemented операцию в окне W (dependency rule). Порядок между аккаунтами — лексикографически по op_hash. Детерминирован, вычислим независимо каждым узлом.

Apply каждой операции:

Transfer:     sender.balance -= amount
              receiver.balance += amount
              sender.frontier_hash = H(operation)
              update_merkle_path(sender)
              update_merkle_path(receiver)

TransferActivation: sender.balance -= amount
              sender.frontier_hash = H(op)
              sender.last_activation_window = current_window   # [I-15] cooldown per τ₂
              update_merkle_path(sender)
              создать запись Account Table[receiver] = { balance = amount, current_pubkey = payload.receiver_pubkey, suite_id = payload.suite_id, frontier_hash = 0x00...00, last_activation_window = 0, creation_window = current_window, ... }
              insert_merkle_leaf(new_account)

ChangeKey:    account.current_pubkey = new_pubkey
              account.suite_id = new_suite_id
              account.frontier_hash = H(operation)
              update_merkle_path(account)

Anchor:       записать data_hash в цепочку аккаунта (frontier_hash обновлён)
              update_merkle_path(account)

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

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

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

Dependency rule: один аккаунт = одна операция за окно τ₁. Каждая cemented операция = +1 к account_chain_length = одно окно присутствия.

State transition в proposal: при settle (apply at window close) применяется атомарно:

apply_proposal(state, proposal) -> state':

  Шаг 1: применить control_set в порядке (cemented_window asc, op_hash lex asc).
    NodeRegistration: проверить node_id уникален (нет в Node Table и Candidate Pool),
                      проверить operator_account_id существует и is_node_operator == 0,
                      проверить W_p - max_vdf_horizon ≤ W_start ≤ W_p - base_vdf_length
                        (max_vdf_horizon = 2 × τ₂_windows; base_vdf_length = τ₂_windows = vdf_entry_windows),
                      применить incremental apply в окне W_p:
                        sort cemented NodeRegistrations окна W_p by nr_sort_key,
                          где nr_sort_key(nr) = SHA-256(
                            "mt-nodereg-sort" ||
                            timechain_value(W_p) ||
                            cemented_bundle_aggregate(W_p - 2) ||
                            nr.node_pubkey
                          ),
                        for each NR in sorted order:
                          current_pending = pending_candidates(W_p) + N_applied_this_window
                          current_pressure = current_pending / active_nodes(W_p)
                          required_vdf_length(NR) = adaptive_formula(current_pressure)
                          if NR.vdf_chain_length < required_vdf_length(NR):
                            reject NR (insufficient VDF work)
                            continue
                          верифицировать proof_endpoint: пересчёт VDF от
                            SHA-256("mt-candidate-vdf-init" ||
                                    timechain_value(W_start) ||
                                    cemented_bundle_aggregate(W_start - 2) ||
                                    node_id)
                            через NR.vdf_chain_length окон,
                          если endpoint не совпадает: reject NR
                          создать запись в Candidate Pool:
                            node_id, node_pubkey, suite_id, operator_account_id,
                            proof_endpoint, W_start, vdf_chain_length,
                            registration_window = W_p,
                            expires = W_p + 3 × τ₂_windows.
                          N_applied_this_window += 1.

  Шаг 2: применить Монтана победителя.
    r = reward_nɈ(current_window - 1)
        = R_BASELINE_nɈ + ((current_window - 1 < BOOTSTRAP_H × BOOTSTRAP_EPOCHS)
                           ? (BOOTSTRAP_R0_nɈ >> floor((current_window - 1) / BOOTSTRAP_H))
                           : 0)
    operator_account = Node Table[winner_id].operator_account_id
    operator_account.balance += r

  Шаг 3: обработать expiry кандидатов и selection event.
    3a. Все записи c ∈ Candidate Pool где c.expires <= current_window:
        удалить c из Candidate Pool, обновить candidate_root.
    3b. Selection event (если current_window % 336 == 0):
        candidates = все записи Candidate Pool где expires > current_window
        slots = max(1, floor(active_nodes(current_window) / 130))
             -- admission_divisor = 130 ⟹ per-event admission rate = 1/130 = 0.77% ≤ 1% upper bound
             -- (обоснование: таблица «Обоснование протокольных констант → admission_divisor»)
        sort_key(c) = SHA-256(
          "mt-selection" ||
          timechain_value(current_window) ||
          cemented_bundle_aggregate(current_window - 2) ||
          c.node_id
        )
        selected = первые slots кандидатов по sort_key
        Для каждого selected:
          создать запись в Node Table (start_window = current_window, chain_length = 1,
          last_confirmation_window = 0, operator_account_id зафиксирован)
          установить is_node_operator = 1 у operator-аккаунта
          удалить selected из Candidate Pool
          обновить node_root и candidate_root.

**Grinding resistance selection event.** Domain separator `mt-selection` отделяет hash space от `mt-lottery` и других. Компонент `cemented_bundle_aggregate(current_window - 2)` — канонический но unpredictable offline (зависит от FN-DSA-512 подписей confirmers окна current_window-2). Атакующий с VDF hardware advantage, пре-вычисляющий `timechain_value` для будущих selection events, не может пре-вычислить sort_key без privкey confirmers. Grinding keypair (генерация N kerpairs для выбора favorable node_id) не работает: к моменту selection event sort_key определён будущими signatures, которые атакующий не контролирует.

  Шаг 3.5: обновить chain_length активных узлов.
    Для каждого узла N с cemented BundledConfirmation в окне W:
      N.chain_length += 1
      N.last_confirmation_window = W
      update_merkle_path(N) в node_root
    Множество узлов с cemented BundledConfirmation в окне W детерминировано
    (cemented status объективен) — все узлы применяют один и тот же набор обновлений.

  Шаг 3.6: обновить chain_length_snapshot на τ₂-boundary.
    Если current_window % τ₂_windows == 0:
      Для каждого узла N в Node Table:
        rotate N.chain_length_checkpoints (сдвиг: oldest выбывает, текущий chain_length записывается как newest)
        N.chain_length_snapshot = N.chain_length - N.chain_length_checkpoints[oldest]
        update_merkle_path(N) в node_root
    Между τ₂-boundaries: chain_length_snapshot вычисляется как chain_length - frozen oldest checkpoint.
    Детерминированно: все узлы применяют одну и ту же ротацию на одной τ₂-boundary.

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

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

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

Минимум для узла: 1 ядро CPU. TimeChain VDF (непрерывное последовательное хэширование) + валидация операций (interleaved, overhead < 1% окна). С ростом TPS сети дополнительные ядра ускоряют верификацию операций. Один узел = 1 ядро. Любой компьютер = потенциальный узел. Верификация операций аккаунтов полностью параллелизуется — цепочки аккаунтов независимы.

Вход и регистрация

Два уровня входа в сеть. Узлы участвуют в консенсусе — открытый вход через VDF + selection event. Аккаунты держат и переводят средства — активируются sponsor'ом через TransferActivation (самоинициация создания аккаунта невозможна; AccountRecord появляется только при первом incoming TransferActivation).

Genesis State — аксиома сети. Минимальный bootstrap: один узел, один аккаунт. Все последующие аккаунты активируются через TransferActivation от существующего sender'а с cooldown 1 per τ₂ per [I-15] — Genesis bootstrap operator становится первым sponsor'ом для activation tree.

Bootstrap growth model. Minimal Genesis (1 bootstrap operator account — он же первый sponsor tree expansion) достаточен для запуска сети. Tree-expansion через multi-sponsor social graph параллелизуется экспоненциально — каждый активированный аккаунт становится sponsor'ом для следующего поколения после прохождения cooldown 1 TransferActivation per τ₂:

  • N = 0: 1 аккаунт (genesis operator)
  • N = 1 τ₂: operator активирует один аккаунт → 2 accounts
  • N = 2 τ₂: оба активируют → 4 accounts
  • N = k τ₂: 2^k accounts

Quantify rollout в окнах:

  • 1 000 accounts: ⌈log₂(1000)⌉ = 10 τ₂
  • 1 000 000 accounts: ⌈log₂(10⁶)⌉ = 20 τ₂
  • 1 000 000 000 accounts: ⌈log₂(10⁹)⌉ = 30 τ₂

Альтернатива: Genesis может содержать N_SEED bootstrap operator accounts как операционный параметр запуска — не consensus-critical, не меняет протокольные правила. Для reference mainnet N_SEED = 1 сохраняет архитектурную чистоту и не вводит центральных точек доверия beyond единого genesis operator. Тестовые сети / локальные devnets могут использовать N_SEED > 1 через отдельную Genesis configuration для ускорения инициализации без изменения протокола.

Growth начинается с первого cemented τ₁ окна и не требует дополнительных specialized механизмов — существующее правило TransferActivation + cooldown [I-15] покрывает весь жизненный цикл roll-out.

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

Genesis State (до первого окна, supply = 0):

  Account Table = 1 запись (bootstrap operator account):
    account_id      = SHA-256("mt-account" || suite_id || pubkey_0)
    balance         = 0
    suite_id        = 0x0001 (FN-DSA-512)
    is_node_operator = 1
    current_pubkey  = pubkey_0 (bootstrap)
    frontier_hash   = SHA-256("mt-genesis" || account_id)
    op_height       = 0
    account_chain_length = 0
    account_chain_length_snapshot = 0
    creation_window = 0

  Node Table = 1 запись (bootstrap node):
    node_id                  = SHA-256("mt-node" || node_pubkey_0)
    node_pubkey              = node_pubkey_0 (bootstrap)
    suite_id                 = 0x0001
    operator_account_id      = account_id_0
    start_window             = 0
    chain_length             = 1
    last_confirmation_window = 0

  Candidate Pool = ∅

  Bootstrap-узел стартует с chain_length = 1 в Genesis. Каждый последующий cemented BundledConfirmation инкрементирует chain_length. Начальное значение = 1 (а не 0) — необходимо для корректности знаменателей weighted_ticket_node и seniority_bonus; инвариант chain_length ≥ 1 сохраняется для любого узла в Node Table.

  genesis_account_root    = sparse Merkle root над 1 записью Account Table
  genesis_node_root       = sparse Merkle root над 1 записью Node Table
  genesis_candidate_root  = empty_internal(256) — root пустой sparse Merkle tree
                            per формуле раздела «Sparse Merkle Tree algorithm»:
                              empty_internal(0) = 0x00 × 32
                              empty_internal(k+1) = internal_hash(empty(k), empty(k))
                              internal_hash(l, r) = hash("mt-merkle-node", [l, r])
                                                  = SHA-256("mt-merkle-node" || 0x00 || l || r)
                            Детерминистически вычислим; НЕ равен 0x00 × 32 при depth=256.

                            Binding test vectors (byte-exact, с NUL-separator canonical hash):
                              empty_internal(0)   = 0x0000000000000000000000000000000000000000000000000000000000000000
                              empty_internal(1)   = 0x693bc03e469cd59e381575e0b3e178b40796ec2253869fe03eaee34750a06517
                              empty_internal(128) = 0x1b16a1c4eb2ed66902595a6d2ec642a05bed9db4897f5d910092b1a899a8a8b3
                              empty_internal(256) = 0x87dd145ec5630decdf8fc800583c51cce9dbe8438a1fa0b7e61eb679b4b4638f
                            Значение empty_internal(256) = binding genesis_candidate_root.
  genesis_state_root      = SHA-256("mt-state-root" || genesis_node_root || genesis_candidate_root || genesis_account_root)

  protocol_params (каноническая сериализация, little-endian, фиксированная длина полей):
    D₀                             (8B)   начальное значение D TimeChain VDF (252 000 000)
    (reserved)                      (8B)   = 0x00 × 8 (mandatory, ранее m₀ NodeChain VDF)
    τ₂_windows                     (8B)   число окон в τ₂ (20 160)
    r_baseline_nɈ                  (16B)  13_000_000_000 nɈ (u128, R_BASELINE)
    bootstrap_r0_nɈ                (16B)  16_000_000_000 nɈ (u128, BOOTSTRAP_R0)
    bootstrap_h_windows            (8B)   524_160 (= 26 × τ₂_windows, BOOTSTRAP_H)
    bootstrap_epochs               (1B)   5 (BOOTSTRAP_EPOCHS)
    bootstrap_cap_nɈ               (16B)  16_248_960_000_000_000 nɈ (u128, BOOTSTRAP_CAP)
    target₀                        (32B)  начальный target лотереи
    confirmation_quorum_num        (1B)   67
    confirmation_quorum_den        (1B)   100
    participation_dead_zone_low    (2B)   85
    participation_dead_zone_high   (2B)   95
    d_adjustment_rate_num          (2B)   3
    d_adjustment_rate_den          (2B)   100
    vdf_entry_windows              (8B)   20 160 (= τ₂)
    selection_interval             (8B)   336
    candidate_expiry_windows       (8B)   60 480 (3τ₂)
    adaptive_vdf_threshold         (2B)   1 (= 0.01 × 100, порог давления 1%)
    adaptive_vdf_multiplier        (2B)   100 (effective_vdf = base × pressure × multiplier)
    pruning_idle_windows           (8B)   80 640 (4τ₂)
    nickname_base_price_nj         (16B)  100_000_000 (базовая цена никнейма = 0.1 TC)
    nickname_floor_ratio_num        (1B)  1 (пол = 1/100 от starting)
    nickname_floor_ratio_den        (1B)  100
    nickname_decay_num              (1B)  85 (спад 15% за τ₂)
    nickname_decay_den              (1B)  100
    nickname_english_extension_τ₂   (1B)  2 (длина английской фазы = 2τ₂)
    nickname_english_max_extensions (1B)  10 (потолок продлений анти-снайпинга)
    nickname_antisnipe_τ₁           (1B)  2 (окно триггера продления в конце англ. фазы)
    nickname_activity_threshold     (2B)  84 (мин. account_chain_length_snapshot для bid'а = 1τ₂)
    nickname_increment_num          (1B)  5 (мин. инкремент перебид'а = 5%)
    nickname_increment_den          (1B)  100
    nickname_reserved_count         (1B)  10 (длина списка зарезервированных)
    nickname_reserved_list          (160B)  lex-отсортированный список из 10 имён по 16B
                                             fixed-padded с 0x00; по умолчанию:
                                             {admin, bootstrap, genesis, help, info,
                                              montana, mnt, root, support, system}
    voice_call_rate_per_min_nj     (16B)  1_000_000 (0.001 Ɉ/мин голосового звонка)
    video_call_rate_per_min_nj     (16B)  5_000_000 (0.005 Ɉ/мин видеозвонка)
    premium_profile_monthly_nj     (16B)  10_000_000_000 (10 Ɉ/мес премиум-профиля)
    anchor_per_kb_nj               (16B)  100_000 (0.0001 TC/KB сверх первого)
    anchor_free_kb                  (2B)  1 (первый KB Anchor без оплаты)
    creator_subscription_min_nj    (16B)  100_000_000 (0.1 TC мин. месячная подписка)
    bootstrap_account_pubkey       (897B)
    bootstrap_node_pubkey          (897B)
    genesis_content_app_id         (32B)  = SHA-256("mt-app" || "montana")
    genesis_content_data_hash      (32B)  хэш манифеста книги Montana v1.0

  Genesis State Hash = SHA-256("mt-genesis" || genesis_state_root || canonical_encode(protocol_params))

Domain separator "mt-genesis" обеспечивает structural разделение от других hash compositions (единое правило Domain separators registry — все consensus hash compositions содержат domain separator первым).

Bootstrap keypair (account + node) публикуется в Genesis Decree вместе с протокольными параметрами и Genesis State Hash. Genesis Decree immutable — закреплён в коде каждой реализации.

Инварианты Genesis Decree:

  • Все поля protocol_params имеют фиксированные значения согласно layout выше; implementer хардкодит их в коде, runtime mutation запрещена
  • Reserved поле (reserved) = 0x00 × 8 строго; любое другое значение — reject (изменяет Genesis State Hash и создаёт несовместимую сеть)
  • bootstrap_account_pubkey и bootstrap_node_pubkey соответствуют эталонным значениям закреплённым в коде
  • genesis_state_root = SHA-256("mt-state-root" || genesis_node_root || genesis_candidate_root || genesis_account_root) пересчитывается из сериализованных начальных таблиц и сверяется byte-exact
  • Genesis State Hash = SHA-256("mt-genesis" || genesis_state_root || canonical_encode(protocol_params)) совпадает с эталонным значением закреплённым в коде реализации
  • Любое отклонение — Genesis Decree недействителен, узел отказывается стартовать (fail-stop, не fallback)

Калибровка D₀.

Параметр D₀ = 252 000 000 выбран эмпирически на основе измерений однопоточной iterated SHA-256 производительности на трёх реальных deployment profiles.

Методология: цепочка hash_{i+1} = SHA-256(hash_i) где начальный hash_0 = [0; 32], три последовательных прогона по 60 секунд на каждой машине, результат — среднее арифметическое. Release сборка Rust с -C target-cpu=native.

Hardware profile Специфика Hashes за 60 сек MH/s
Dedicated Apple Silicon Apple M-series SoC, ARM SHA hardware extensions (FEAT_SHA256), 16 MB L2 cache, нет других сервисов 283 543 333 4.73
Idle VPS (Timeweb, Moscow) QEMU Virtual CPU v4.2.0, 2.1 GHz, 16 MB cache, без hw SHA, лёгкая фоновая нагрузка 220 510 000 3.68
Loaded VPS (Timeweb, Frankfurt) QEMU Virtual CPU v8.2.0, 2.9 GHz, 512 KB cache, SHA-NI инструкции, concurrent production сервисы (gunicorn web API, nginx, periodic tasks) на том же виртуальном ядре 13 300 000 0.22

Наблюдения:

  • Dedicated hardware даёт стабильные ~4.7 MH/s на iterated 32-byte SHA-256
  • Idle VPS без hw SHA работает близко (~3.7 MH/s) благодаря большому cache и низкой concurrent нагрузке
  • Production VPS с hw SHA, но разделяющий ядро с веб-сервисами, драматически проседает до ~0.22 MH/s из-за CPU contention

D₀ = 252 000 000 — арифметическое среднее между dedicated Apple Silicon (283 543 333) и idle VPS Moscow (220 510 000). Frankfurt loaded VPS исключён из калибровки как non-target profile: узлы Montana не предназначены для совместного существования с concurrent production workloads на том же виртуальном ядре.

На таком D₀:

  • Dedicated hardware (Mac, 4.73 MH/s): одно окно τ₁ ≈ 53 секунды wall-clock
  • Idle VPS (Moscow, 3.68 MH/s): одно окно τ₁ ≈ 68 секунд wall-clock
  • Среднее между двумя target profiles ≈ 60 секунд wall-clock на одно окно τ₁

Это единственная абсолютная временная привязка в спецификации. Все остальные разделы оперируют в терминах окон (τ₁, τ₂ и их кратности) — единицах канонического протокольного времени, не связанных напрямую с физическими секундами. Wall-clock длительность окна — emergent property калибровки D₀ на конкретном железе.

Adaptive D feedback (раздел «Адаптация D через participation-ratio feedback») после старта сети корректирует D на каждой τ₂-boundary в зависимости от медианной participation_ratio: если железо в сети в среднем быстрее — D растёт, поддерживая wall-clock длительность окна у целевого значения; если медленнее — D снижается. D₀ — только начальное значение, сеть самостоятельно выравнивается под фактическое состав операторов.

Первое окно τ₁ после генезиса — window_index = 0, protocol_version = 1. Bootstrap-узел — единственный proposer первых двух окон (без lookback). Начиная с W = 2 — стандартная lookback логика. Bootstrap-узел получает reward(W) за каждое выигранное окно (в эпохе 0 это 29 Ɉ). Per-operation invariant действует с первого окна.

Bootstrap period. До появления второго узла (первые τ₂+ окон) bootstrap-узел имеет 100% active_chain_length и является единственным confirmer-ом, proposer-ом и winner-ом. Это физическая необходимость запуска любой сети — кто-то является первым. Доминирование bootstrap-узла размывается органически: каждый новый узел, прошедший selection event, вносит свой chain_length в active set. Протокольные правила (quorum 67%, weighted_ticket лотерея, selection rate limit) одинаковы с первого окна — специальных bootstrap-правил вне lookback первых двух окон нет.

Границы модели доверия.

Протокол имеет два режима доверия, автоматически переключаемые из canonical state.

Режим Genesis. Действует от Genesis до первого cemented BundledConfirmation от узла, отличного от bootstrap. В этот период безопасность протокола опирается на:

  • Неизменность Genesis Decree (захардкожен в каждой реализации)
  • Секретность bootstrap privкey (доверенная сторона — автор протокола)
  • Отсутствие конкуренции — один участник, лотерея без значимых соперников, quorum тривиально достигается bootstrap-узлом

cemented_bundle_aggregate в этот период равен хэшу одной bootstrap-подписи. Защита [I-8] от grinding работает при секретности bootstrap privкey — стандартное допущение для Genesis-систем. Экономическая нерациональность атаки на single-node сеть компенсирует degraded security margin: нет Монтана rewards за победу над единственным участником, лотерея не даёт advantage.

Режим BFT. Активируется автоматически при первом cemented BundledConfirmation где BC.node_id ≠ bootstrap_node_id. В этот период безопасность опирается на:

  • ≥67% честного active_chain_length
  • cemented_bundle_aggregate из множества FN-DSA-512 подписей — полная защита [I-8] от pre-computation grinding
  • Pruning + active_predicate поддерживают соотношение honest/attacker в составе active set

Переход. Автоматический, наблюдаемый из canonical state: Node Table содержит ≥1 non-bootstrap узел с chain_length ≥ 1. Версия протокола не меняется. Никакого ручного вмешательства или hard fork. Threat model сдвигается с «trust the Genesis author» на «trust ≥67% chain_length» плавно и непрерывно.

Следствия для reference implementation. Аудит и тестирование обязаны покрывать оба режима раздельно. Тесты bootstrap-периода проверяют поведение в Genesis-режиме (single-confirmer aggregate, bootstrap winning all lotteries, proposer ротация отсутствует). Тесты после bootstrap — BFT-поведение (multi-confirmer aggregate, weighted_ticket лотерея, lookback leadership). Переходный тест обязательно проверяет корректность передачи при первой non-bootstrap регистрации — один из критичных invariant-моментов в жизни сети.

Mandatory content replication. Каждый узел Montana обязан хранить текущую версию книги Montana как persistent blob по (genesis_content_app_id, genesis_content_data_hash). При Fast Sync новый узел загружает genesis content как часть обязательной начальной синхронизации (см. раздел Fast Sync).

Открытый вход узлов

Вход узла в консенсус — открытый. VDF τ₂ окон + кандидатура + selection event. Никаких приглашений, никаких разрешений.

Шаг 1: Свободный вход. Оператор-кандидат уже имеет активированный personal account (AccountRecord создан через TransferActivation от sponsor, см. раздел «Аккаунты»). Кандидат подключается к gossip через account keypair (IBT уровень 3), получает TimeChain values из proposals, вычисляет candidate VDF τ₂ окон от init. NodeRegistration привязывается к operator_account_id владельца:

candidate_vdf_init = SHA-256(
  "mt-candidate-vdf-init" ||
  timechain_value(W_start) ||
  cemented_bundle_aggregate(W_start - 2) ||
  node_id
)

W_start — окно начала VDF (заявляется кандидатом в NodeRegistration).

Шаг 2: Кандидатура. После завершения VDF кандидат публикует NodeRegistration:

NodeRegistration:
  type                  1B   <- 0x11 NodeRegistration
  suite_id              2B
  node_pubkey         897B
  operator_account_id  32B
  proof_endpoint       32B     <- endpoint VDF цепочки (длина vdf_chain_length)
  W_start               8B     <- окно начала VDF
  vdf_chain_length      8B     <- длина VDF цепочки в "окнах" (по D хэшей)
  signature           666B
Итого:            ~1 646 B

NodeRegistration — ControlObject. При cementing → запись в Candidate Pool. Кандидат ожидает selection event.

Инварианты NodeRegistration:

  • type == 0x11 (первый байт; иное значение — не NodeRegistration, misrouting)
  • suite_id соответствует активной схеме подписи (на момент запуска: 0x0001 = FN-DSA-512); прочие значения — reject (UnsupportedSuite)
  • Подпись FN-DSA-512 валидна для node_pubkey
  • node_id = SHA-256("mt-node" || node_pubkey) уникален (нет в Node Table и Candidate Pool)
  • operator_account_id существует в Account Table и is_node_operator == 0
  • W_p - 2 × τ₂_windows ≤ W_start ≤ W_p - base_vdf_length (base_vdf_length = τ₂_windows). Нижняя граница ограничивает историческое pre-computation окно. Верхняя граница гарантирует что VDF физически выполнен до publication.
  • vdf_chain_length ≥ required_vdf_length(W_p) — длина заявленной VDF цепочки не меньше требуемой pressure-adjusted длины в момент W_p (incremental apply в батче, см. apply_proposal)
  • proof_endpoint верифицируем: пересчёт VDF от SHA-256("mt-candidate-vdf-init" || timechain_value(W_start) || cemented_bundle_aggregate(W_start - 2) || node_id) через vdf_chain_length окон. Если vdf_chain_length × D hashes VDF iteration от init даёт proof_endpoint — валидно.

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

[I-8] compliance. cemented_bundle_aggregate(W_start - 2) в candidate_vdf_init — canonical & unpredictable-offline компонент (зависит от FN-DSA-512 подписей confirmers окна W_start - 2). Атакующий с VDF hardware advantage не может pre-compute init для произвольного будущего W_start — aggregate доступен только после cementing W_start - 2. Pre-computation grinding закрыт по построению.

Шаг 3: Selection event. Каждые selection_interval = 336 окон сеть выбирает кандидатов из Candidate Pool. Полная каноническая формула sort_key, количество мест slots = max(1, floor(active_nodes / 130)), обработка expiry и включения в Node Table описаны в apply_proposal шаг 3b (раздел «Состояние сети → Двигатели → State transition»). Обоснование admission_divisor = 130 и связь с upper bound 1% active_nodes per event — в таблице «Обоснование протокольных констант → Безопасность консенсуса и сети».

Шаг 4: Регистрация. Выбранные кандидаты → Node Table:

start_window = W (окно selection event)
chain_length = 1
last_confirmation_window = 0

Узел добавляется в Node Table с chain_length = 1 (позиция активации). Каждое последующее окно с cemented BundledConfirmation инкрементирует chain_length. Оператор-аккаунт получает is_node_operator = 1. Запись удаляется из Candidate Pool.

Expiry. Кандидатура истекает через 3τ₂ (39 000 окон). Запись удаляется из Candidate Pool автоматически.

Sybil-защита (четыре уровня):

  1. VDF-барьер: τ₂ окон последовательного хэширования (при нормальной нагрузке). Физическая работа, sequential по построению — не ускоряется параллелизмом.

  2. Adaptive VDF: стоимость кандидатуры пропорциональна давлению на сеть в момент публикации NodeRegistration (не в момент начала VDF работы). Это закрывает timing-manipulation: attacker не знает заранее какое pressure будет в момент W_p.

candidate_pressure(W) = pending_candidates(W) / active_nodes(W)

if candidate_pressure(W) > 0.01:
    required_vdf_length(W) = τ₂_windows × candidate_pressure(W) × 100
else:
    required_vdf_length(W) = τ₂_windows   (base_vdf_length)
Ситуация pending active pressure required_vdf
Нормальная 5 1 000 0.5% 20 160 окон (= τ₂, base)
Умеренная 20 1 000 2% 40 320 окон
Высокая 100 1 000 10% 201 600 окон
Атака 1 000 1 000 100% 2 016 000 окон
Массовая атака 100 000 1 000 10000% 201 600 000 окон

Привязка к W_p (не W_start). required_vdf_length вычисляется из canonical state в момент cementing NodeRegistration (W_p). Кандидат декларирует vdf_chain_length в NodeRegistration — длину своей VDF цепочки. Валидатор проверяет vdf_chain_length ≥ required_vdf_length(W_p) и корректность proof_endpoint через пересчёт VDF от init на vdf_chain_length окон.

Incremental apply в батче одного окна. Если несколько NodeRegistrations cemented в одно окно W_p, они применяются по canonical sort order с инкрементальным pending:

nr_sort_key(nr) = SHA-256(
  "mt-nodereg-sort" ||
  timechain_value(W_p) ||
  cemented_bundle_aggregate(W_p - 2) ||
  nr.node_pubkey
)

sort cemented_noderegs_W_p by nr_sort_key
for each NR in order:
  current_pending = pending_candidates(W_p) + N_already_applied
  current_pressure = current_pending / active_nodes(W_p)
  required = adaptive_formula(current_pressure)
  if NR.vdf_chain_length >= required:
    apply NR; N_already_applied += 1
  else:
    reject NR

Батч одного окна: первая NR видит pending baseline, каждая последующая видит +1. Required растёт в батче. Attacker не получает batch-advantage.

[I-8] binding sort order. Domain separator mt-nodereg-sort изолирует hash space. cemented_bundle_aggregate(W_p - 2) — canonical & unpredictable-offline компонент, зависящий от FN-DSA-512 подписей confirmers окна W_p - 2. Атакующий с hardware advantage не может пре-вычислить nr_sort_key без privкey honest participants → не может grind node_pubkey для favorable позиции в батче. Incremental apply неуязвим к keypair-grinding.

Extension rule для honest operators. Если первая попытка NodeRegistration rejected по vdf_chain_length < required, оператор может:

  1. Продолжить VDF работу от текущего proof_endpoint на дополнительные окна
  2. Обновить NodeRegistration: новый proof_endpoint = VDF(old_proof_endpoint, additional_length), vdf_chain_length = old + additional
  3. Повторить publication с updated proof

VDF работа не теряется — только admission откладывается. Honest strategy: consecutive VDF extension пока required не удовлетворено.

Self-correcting механика. Чем сильнее давление → тем длиннее required VDF → дороже Sybil → давление падает через admission или expiry. При снижении давления (expiry 3τ₂ для просроченных кандидатов) → pending уменьшается → required нормализуется → легитимный вход восстанавливается.

[I-8] compliance — grinding resistance. Attacker не может предсказать required_vdf_length(W_p) в момент начала VDF: pressure зависит от будущих cemented NodeRegistrations и будущих BCs (active_nodes). Attacker не контролирует privкey honest participants → не может предвычислить pressure. Forced over-provisioning или extension rule.

Timing manipulation закрыта. Attacker не может начать VDF при низком давлении и подать при высоком — required проверяется на момент публикации. Minimum VDF (base_vdf_length = τ₂) достаточен только если pressure(W_p) ≤ 1%. Иначе нужен extended VDF пропорционально pressure.

Slow-rate participation = организacный рост. Если actor публикует ≤1 NodeRegistration per selection interval (336 окон), pending не накапливается (selection event admitting ~1% за event). Pressure остаётся baseline. Стоимость = минимум per candidate. Это legitimate участие, неотличимое от honest — и правильно не наказывается. Adaptive защищает только от превышения естественного темпа приёма.

  1. Selection rate limit: max(1, active_nodes/130) за 336 окон. Массовый вход ограничен. Минимум 1 кандидат всегда проходит.

  2. Weighted механизмы: chain_length определяет вес в quorum (безопасность). lottery_weight (snapshot 6τ₂ + seniority bonus) определяет вес в лотерее (эмиссия). Новые узлы начинают с минимальным влиянием. Время — единственный путь к весу.

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

Аккаунт активируется спонсором. Получатель генерирует FN-DSA-512 keypair → вычисляет account_id = SHA-256("mt-account" || suite_id || pubkey) offline → делится receiver_pubkey / account_id со sponsor-ом (QR, сообщение, out-of-band). Sponsor (существующий аккаунт с положительным балансом) публикует TransferActivation → операция cemented → AccountRecord получателя появляется в Account Table при settle окна. Самоинициация создания аккаунта невозможна; рост пользовательской базы цепляется за распространение Монтана через цепочку спонсорств.

Sybil-барьер для аккаунтов: cost-based — каждая активация сжигает fee со стороны sponsor-а (см. TransferActivation инварианты и account_creation_fee_nj в Genesis Decree) + account_age определяет приоритет операций. Новый аккаунт — бакет 0, 1 операция за τ₁. Рост приоритета = время. Пустые аккаунты бесполезны — без баланса операции невозможны.

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

Узлы: selection event каждые 336 окон, slots = max(1, active_nodes/130). Рост ограничен selection rate:

Genesis (1 узел):               1 новый узел за 336 окон
active_nodes = 100:             1 новый узел за 336 окон
active_nodes = 1 000:           10 новых узлов за 336 окон
active_nodes = 10 000:          100 новых узлов за 336 окон

Каждый кандидат проходит τ₂ окон VDF. Первые кандидаты появляются через τ₂ окон после genesis.

Сетевой TPS не зависит от |Node Table|. Montana — replicated state machine, каждый узел обрабатывает все операции окна. Entry rate регулирует безопасность weight distribution и темп децентрализации, не пропускную способность. TPS масштабируется апгрейдом канала и CPU узлов, не их количеством. Сценарий «внезапная популярность → сеть не справляется с нагрузкой из-за медленного входа узлов» не применим к архитектуре Montana.

Compound-рост при постоянном entry rate: удвоение сети ≈ 1.5 × τ₂ после первой волны (детальная derivation — таблица «Обоснование протокольных констант → admission_divisor»). Первая волна лагает на τ₂ (VDF вычисление первых кандидатов).

Barrier scope: что именно ограничено entry rate

Entry rate (τ₂ VDF + selection event) ограничивает только участие узла в консенсусе. Операционная функциональность узла не зависит от его статуса в Node Table.

Доступно с момента установки узла (день 0, до регистрации):

  • P2P gossip и IBT: узел подключается к сети через level-3 addresses, получает proposals, синхронизирует state.
  • Хранение данных владельца: узел хостит файлы, бэкапы, мессенджер-inbox своего оператора — это клиентский слой, не консенсусный.
  • Почтовый ящик: входящие сообщения для операторского account_id накапливаются на узле пока телефон offline.
  • Gateway для мобильного клиента: телефон оператора подключается к своему узлу через IBT уровень 3 (account-based auth), получает полный пользовательский функционал.
  • Archival role: узел может хранить proposals, BundledConfirmations, исторические данные — в пользу своего оператора или по запросу application слоя.

Доступно с момента первого incoming TransferActivation (account-level, после settle AccountRecord):

  • Transfer — исходящие переводы Монтана существующим аккаунтам.
  • TransferActivation — активация AccountRecord нового получателя от имени данного аккаунта (данный аккаунт выступает как sponsor следующей волны пользователей).
  • Anchor — фиксация данных во времени (Merkle root над произвольным off-chain контентом).
  • NicknameBid — участие в аукционе никнеймов (при достижении account_chain_length_snapshot >= nickname_activity_threshold_windows).
  • ChangeKey — ротация keypair.
  • CloseAccount — явное закрытие с очисткой AccountRecord.
  • PurchaseCredits / ConsumeCredits — оплата protocol-level услуг.
  • Messaging через свой узел с постквантовым шифрованием ML-KEM (клиентский слой, не consensus-critical).

Ограничено до entry в Node Table (после τ₂ VDF + selection event):

  • Node lottery: weighted_ticket_node требует active_chain_length_snapshot, зарабатывается только после entry.
  • Confirmer eligibility: top ~100 chain_length → новый узел далеко от threshold до накопления окон присутствия.
  • Вес в quorum: active_chain_length = 0 до entry, голос узла не считается в 67% threshold для cementing и conflict resolution.
  • Монтана emission for node: node reward payout требует chain_length > 0.

Ортогональность TPS и entry rate:

Пропускная способность сети определяется пропускной способностью канала и CPU активных узлов (replicated state machine — каждый узел обрабатывает все операции). Entry rate регулирует темп ввода новых узлов в консенсусную роль, не скорость обслуживания пользователей.

  • Сеть из 100 узлов и сеть из 10 000 узлов обслуживают пользователей с тем же TPS_network = min over nodes (TPS_node).
  • User onboarding не зависит от node onboarding. TransferActivation от sponsor-а cemented в одном окне, settled в конце того же окна — получатель готов к исходящим операциям начиная со следующего окна.
  • Взрывной рост пользовательской базы абсорбируется апгрейдом канала существующих узлов, не входом новых.

Резюме: барьер τ₂ VDF защищает weight distribution и консенсусную безопасность. Он не ограничивает пользовательский доступ, пропускную способность сети, работоспособность новых узлов как инфраструктуры владельца, или скорость распространения сети среди пользователей.

Аккаунты: активируются sponsor-ом через TransferActivation. Рост пользовательской базы определяется распространением Монтана через цепочку спонсорств — каждый новый пользователь требует existing-аккаунт с положительным балансом, готовый оплатить account_creation_fee_nj + первичный перевод. Самоинициация создания невозможна.


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

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

Операция проходит два состояния:

  • Cemented (quorum event): 67% active_chain_length подтвердили. Операция необратима. Баланс ещё не обновлён.
  • Settled (конец окна, apply at window close): все cemented операции окна применены к Account Table батчем. Баланс обновлён. state_root зафиксирован в proposal.

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

  • Операции подтверждаются непрерывно через confirmations (cement), применяются батчем в конце окна (settle)
  • Часы тикают по расписанию окон τ₁ (TimeChain, лотерея, Монтана)

Кошелёк получателя отображает входящий перевод в два этапа: «confirmed» после cement (quorum event), «settled» после apply at window close (apply at window close). Между cement и settle операция уже необратима — различие только для UX индикации.

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


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

τ₁ = 1 window  →  τ₂ = 20 160 windows

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

τ₁ — Окно (D хэшей)

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

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

  • NodeChain: chain_length инкрементируется при cemented BundledConfirmation

  • Операции аккаунтов подтверждаются непрерывно через confirmations (cement), применяются батчем в конце окна (settle)

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

  • Confirmers (~100) публикуют BundledConfirmation (операции текущего окна + VDF_Reveals предыдущего окна)

  • Кандидаты (~12) публикуют VDF_Reveal с lottery endpoint = SHA-256(T_r || node_id || window_index); reveals цементируются через BundledConfirmation следующего окна

  • Лотерея (single-class, только узлы): winner = argmin(weighted_ticket_node) среди cemented VDF_Reveal узлов-кандидатов (формулы ticket_node/weighted_ticket_node — integer spec per [I-9] в разделе «Класс 1: узлы» + «Integer log algorithm»). Аккаунты в лотерее не участвуют — см. «Аккаунты не участвуют в лотерее».

  • Winner_{W-1} определяется proposer_W (= winner_{W-2}) из cemented VDF_Reveals окна W-1 и cemented account operations окна W-1

  • Proposer (proposer_node_id) публикует подписанный proposal

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

  • Монтана: регистрация одного окна Montana Time (reward(W) Ɉ = baseline 13 + bootstrap bonus) → победителю

  • Supply audit: суммарная эмиссия Монтана от генезиса сверяется с supply(W) = 13 × (W+1) + bootstrap_cumulative(W) Ɉ, где bootstrap_cumulative(W) ≤ BOOTSTRAP_CAP = 16_248_960 Ɉ

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

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

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

τ₂ — Адаптация (20 160 windows)

  • Адаптация D через participation-ratio feedback (см. ниже)
  • Snapshot account_chain_length: для каждого аккаунта account_chain_length_snapshot = account_chain_length. Snapshot используется как anti-Sybil threshold в NicknameBid (аккаунт допускается к аукциону никнеймов только если account_chain_length_snapshot >= nickname_activity_threshold_windows — минимум активности перед покупкой имени). Детерминированно для всех узлов в пределах одного τ₂ интервала
  • Pruning Account Table: удаление пустых аккаунтов без активности 4τ₂ (52 000 окон) с обновлением Merkle путей
  • Pruning Node Table: для каждого узла N где (current_window - N.last_confirmation_window) > 8 × τ₂_windows:
    1. Если N.operator_account_id существует в Account Table — установить Account Table[N.operator_account_id].is_node_operator = 0 (operator-аккаунт освобождается от привязки к узлу; аккаунты в лотерее не участвуют — см. раздел «Аккаунты не участвуют в лотерее»)
    2. Удалить запись N из Node Table
    3. Пересчитать node_root
  • Supply audit (sanity check): Σ balance(account) для всех аккаунтов = supply_nɈ(window_index) = 13_000_000_000 × (window_index + 1) + bootstrap_cumulative_nɈ(window_index)
  • Bootstrap cap check: bootstrap_cumulative_nɈ(window_index) ≤ BOOTSTRAP_CAP_nɈ = 16_248_960_000_000_000
  • Криптографическая амнезия: подписанные proposals сохраняются навсегда — верифицируемая цепочка state commitments. Proposals доказывают что конкретное состояние было закоммичено proposer-узлом; восстановление содержимого состояния требует snapshot или архива
  • Пересчёт параметра D через participation-ratio feedback

Адаптация D через participation-ratio feedback

D адаптируется на границе τ₂ через каноническое chain observation — долю активного chain_length-а, успевшего подписать BundledConfirmation в каждом окне предыдущего τ₂.

Канонический вход (real-valued commentary, authoritative integer form ниже):

participation_ratio(W) = cemented_chain_length(W) / active_chain_length(W)

Где cemented_chain_length(W) — суммарный chain_length узлов, чьи BundledConfirmation для окна W попали в cemented set; active_chain_length(W) — суммарный chain_length узлов с active(node, W) = true. Оба числа канонически вычисляются каждым узлом bit-exact из Node Table и cemented sets.

Real-valued form (commentary):

median_ratio = median(participation_ratio(W) for W in последние τ₂_windows окон)

Если median_ratio >= 0.95:  D_new = D_old × 1.03    (+3%, сеть в комфорте, ускоряемся)
Если median_ratio <= 0.85:  D_new = D_old × 0.97    (-3%, сеть под давлением, замедляемся)
Иначе (dead zone):          D_new = D_old           (zone comfort, D не трогаем)

Integer form (authoritative, per [I-9]):

Encoding: participation_ratio представлен как permille (u32), точность 0.001.
  Диапазон: 0..=1000 (0 = 0%, 1000 = 100%).

Thresholds (из ProtocolParams, единицы — permille × 10):
  low_permille  = (u32 участие)(params.participation_dead_zone_low)  * 10   # 850
  high_permille = (u32)(params.participation_dead_zone_high) * 10           # 950

Rate (из ProtocolParams):
  rate_num = (u64)(params.d_adjustment_rate_num)      # 3
  rate_den = (u64)(params.d_adjustment_rate_den)      # 100

participation_ratio_permille(W): u32
  Input:
    cemented_chain_length(W): u64
    active_chain_length(W): u64    (≥ 1 по liveness invariant)
  Algorithm:
    participation_ratio_permille(W) = 
      ((cemented_chain_length(W) * 1000) / active_chain_length(W)) as u32
    # Unsigned u64 intermediate (cemented × 1000 ≤ 10^17 < 2^57, safe).
    # Integer div toward zero.
    # Cemented ≤ active ⇒ result ≤ 1000 ⇒ safely casts to u32.

median_ratio_permille: u32
  Algorithm:
    values = [participation_ratio_permille(W) for W in last τ₂_windows]
    sorted_values = sort_ascending(values)
    median_ratio_permille = sorted_values[τ₂_windows / 2]

D_next(D_old, median_ratio_permille): u64
  Algorithm:
    если median_ratio_permille >= high_permille:
      D_next = D_old * (rate_den + rate_num) / rate_den     # +3%, integer div toward zero
    иначе если median_ratio_permille <= low_permille:
      D_next = D_old * (rate_den - rate_num) / rate_den     # -3%, integer div toward zero
    иначе:
      D_next = D_old                                          # dead zone

Overflow: D_old ≤ 2^32 typical; D_old × 103 < 2^32 × 2^7 = 2^39 ⇒ safe u64.

Test vectors (binding):
  D_old = 1000, median_permille = 1000 (100%)                 → D_next = 1030
  D_old = 1000, median_permille =  950 (= high_permille)      → D_next = 1030
  D_old = 1000, median_permille =  980 (above high)           → D_next = 1030
  D_old = 1000, median_permille =  900 (dead zone)            → D_next = 1000
  D_old = 1000, median_permille =  851 (dead zone edge)       → D_next = 1000
  D_old = 1000, median_permille =  850 (= low_permille)       → D_next =  970
  D_old = 1000, median_permille =  700 (below low)            → D_next =  970
  D_old = 1000, median_permille =    0 (0%)                   → D_next =  970

[I-9] статус: закрыто (integer spec + 8 test vectors in spec).

Historical note: integer encoding изначально был Q32.32 draft; 
после C4 re-audit кода mt-timechain перевыбран permille — 
упрощение без потери correctness (точность 0.001 достаточна для threshold 
сравнений на τ₂=13000 median), align спеки к существующей реализации 
`mt-timechain::next_d`.

Семантика.

  • median_ratio >= 0.95: большинство активных узлов легко успевают подписать каждое окно. У сети есть запас производительности — D можно поднять, окно станет чуть длиннее в единицах работы, физическая скорость эмиссии замедляется, но сеть укрепляет запас прочности. Эмиссия в canonical окнах неизменна.
  • median_ratio <= 0.85: значительная часть активных узлов не успевает подписать. Сеть близка к границе жизнеспособности — D нужно уменьшить, окно становится короче в единицах работы, медленные узлы получают шанс догнать медиану.
  • Dead zone 0.85-0.95: естественные колебания, D не адаптируется. Это защита от реактивной волатильности.

Свойства.

  • Канонически детерминировано. participation_ratio вычисляется из canonical cemented sets и Node Table. Два честных узла получают одно и то же значение bit-exact.
  • Опирается только на canonical chain observations. Все входные данные формулы — cemented sets и Node Table, оба детерминированы. Corollary I-3.a соблюдён.
  • Медленная реакция. Adjustment rate ±3% за τ₂ делает стратегическую манипуляцию через withholding confirmations экономически нерациональной: actor-у с 10% chain_length-а для сдвига D на 2x требуется систематически saboтировать свои подписи ~24 эпохи, теряя все свои Монтана награды в этот период.
  • Dead zone защищает от флуктуаций. Случайные колебания participation_ratio в диапазоне 0.85-0.95 не вызывают adaptation.
  • Interleaving на 1 ядре — ожидаемый источник participation_ratio < 1.0. Узел делит одно ядро между TimeChain VDF и validation; контекстные переключения систематически приводят к пропуску отдельных окон. Это нормальное поведение 1-ядерной архитектуры, не деградация сети. Dead zone 0.85-0.95 поглощает типичный interleaving overhead; feedback ниже 0.85 автоматически уменьшает D, возвращая validation-у пропорционально больший кусок wall-clock времени. Реализации не должны трактовать participation_ratio < 1.0 как отклонение и менять active predicate или пороги cementing в ответ.
  • Естественное следование hardware progress. Если железо ускоряется, медианные узлы начинают успевать с запасом, median_ratio поднимается выше 0.95, D растёт, окно нормализуется. Сеть автоматически адаптируется к ожидаемому hardware evolution без явного measurement.
  • Нет stремления к hard fork по дизайну. Continuous adaptation в рамках speech-first принципа устраняет необходимость периодического hard fork как запрограммированного события.

Threat model:

  • Actor с <20% chain_length-а экономически не может сдвинуть median_ratio значимо.
  • Hyperscaler с 15% узлов может систематически снижать median_ratio на ~15%, но только теряя свои награды. При clamp ±3% за τ₂ максимальный сдвиг D за 24 τ₂ составляет ±1.03^24 ≈ ±103%, что ограничено при правильном выборе D₀ с запасом.
  • Координированная атака узлов с >50% chain_length эквивалентна атаке на весь консенсус и не рассматривается в рамках локальной защиты participation_ratio.

Genesis parameters:

D₀ = 252 000 000    (2.52 × 10⁸, см. «Калибровка D₀» в разделе Genesis)
participation_dead_zone_low  = 0.85
participation_dead_zone_high = 0.95
d_adjustment_rate = 0.03     (±3% за τ₂)

Параметр D₀ фиксируется в Genesis Decree. Остальные константы закреплены в протокольных параметрах и могут быть изменены только через protocol version upgrade (software hard fork), не через runtime mechanism.


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

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

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

NodeChain — последовательность cemented BundledConfirmation конкретного node_id. chain_length — позиция узла в NodeChain: = 1 при активации, +1 при каждом cemented BundledConfirmation. Инвариант: chain_length ≥ 1. Доказывает присутствие узла.

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

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

Лотерея

Лотерея single-class: участвуют только узлы через VDF_Reveal с каноническим endpoint. Каждый узел производит weighted ticket по длине своей цепочки (chain_length_snapshot). Lowest weighted_ticket побеждает.

Узлы автоматически участвуют в каждом окне. Каноническая формула weighted_ticket_node и integer algorithm определены в разделе «Класс 1: узлы» и общем разделе «Integer log algorithm (per [I-9])» выше (single source of truth).

Аккаунты в лотерее не участвуют — см. раздел «Аккаунты не участвуют в лотерее» выше. Поле account_chain_length_snapshot сохранено как anti-Sybil threshold для NicknameBid (минимум активности перед аукционом никнеймов), не как вес лотерейного билета.

Если weighted_ticket_node < target — узел кандидат. Target калиброван на ~13 кандидатов за окно. Из кандидатов побеждает lowest weighted_ticket.

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

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

Победитель определяется после закрытия окна τ₁. Lowest weighted_ticket_node среди cemented VDF_Reveal узлов-кандидатов = победитель. Единственный класс победителя — узел.

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

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

Если в окне нет узлов-кандидатов (ни один weighted_ticket_node < target) — срабатывает fallback cascade: proposer выбирается из всех активных узлов по lowest weighted_ticket независимо от target. Liveness proposals гарантирована пока хотя бы один узел активен.

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

Верификация

Proposer публикует: {proposer_node_id, proposal}.

Верификация lottery endpoint: один SHA-256 — O(1).

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

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

  • Остановка TimeChain исключена: каждый узел вычисляет VDF независимо
  • Искажение TimeChain исключено: VDF последователен, результат детерминирован
  • Proposer grinding исключён: control_set каноничен, state transition детерминирован, операции финализируются независимо от победителя
  • Front-running исключён: операции финализируются через подтверждения (quorum event), proposer фиксирует frozen view
  • Предвычисление исключено: seed содержит текущее значение TimeChain
  • Replay исключён: TimeChain уникален для каждого τ₁
  • Аппаратное преимущество ограничено: последовательное хэширование масштабируется тактовой частотой, не количеством ядер
  • Sybil-барьер: τ₂ окон VDF + selection event (max 1% active_nodes за 336 окон) + weighted_ticket в лотерее
  • Цензура операций исключена: операции финализируются через подтверждения узлов, не через победителя
  • Цензура ControlObjects исключена: control_set каноничен, пропуск = fallback
  • Liveness halt операций исключён: финализация через 67% active_chain_length, не зависит от победителя
  • Liveness halt proposals исключён: fallback на следующего кандидата
  • Масштабирование: трафик лотереи ~8.9 KB за окно при любом количестве узлов

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

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

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

Два proposal от одного proposer_node_id в одном окне: оба отклоняются (equivocation), fallback к следующему узлу-кандидату. Если этот узел был winner, он теряет reward(W) (winner всегда узел в лотерее single-class).


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

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

0. Боб offline: генерирует FN-DSA-512 keypair, вычисляет account_id
   = SHA-256("mt-account" || suite_id || pubkey). Делится
   (receiver_pubkey, account_id) с Алисой по out-of-band каналу
   (QR / сообщение).
1. Алиса (sponsor, баланс > account_creation_fee_nj + первичный amount)
   публикует TransferActivation(receiver_pubkey = Боб):
   → cemented (quorum event) → settled (конец окна) →
   AccountRecord Боба зарегистрирован в Account Table
   (balance = первичный incoming amount, frontier_hash = 0, op_height = 0).
2. В последующих окнах Боб и Алиса обмениваются обычными Transfer:
   Боб → Алисе: "отправь на mt4ZGfe..." (account_id Боба, уже в Account Table)
3. Алиса формирует обычный Transfer (следующее окно после settle AccountRecord Боба):
   type:       0x02
   prev_hash:  хэш её предыдущей settled операции (frontier_hash из settled state W-1)
   payload:    sender (account_id Алисы) || link (account_id Боба) || amount (50_000_000_000 nɈ)
4. Алиса подписывает FN-DSA-512
5. Алиса рассылает операцию узлам сети
6. Каждый узел проверяет (против settled state W-1):
   FN-DSA-512 подпись валидна для current_pubkey Алисы
   prev_hash совпадает с frontier_hash Алисы
   amount > 0
   alice.balance >= amount
   получатель (Боб) существует в Account Table
7. Confirmers публикуют BundledConfirmation, операция распространяется через P2P gossip
8. Cement: 67% active_chain_length подтвердили → операция необратима (quorum event)
   Кошелёк Боба отображает «confirmed»
9. Settle (apply at window close):
   alice.balance -= 50 Ɉ
   bob.balance   += 50 Ɉ
   alice.frontier_hash = H(operation)
   alice.op_height += 1
   alice.account_chain_length += 1
   Кошелёк Боба отображает «settled»

Баланс

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

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


Nickname Auction Protocol

Глобальный человеко-читаемый слой идентификации поверх account_id. Каждый account_id — 32-байтовый хэш, непригодный для устного общения. Никнейм (@alice) — ASCII-строка которая резолвится в account_id через NicknameTable на уровне консенсуса. Никнеймы продаются через голландский аукцион с английским расширением, оплачиваются Монтана, навсегда привязываются к покупателю (правило «1 никнейм на аккаунт»), уплаченные Монтана полностью сжигаются ([I-13]).

Трёхслойная модель идентификации

Слой                 Что это                   Формат              Кто видит
──────────────────────────────────────────────────────────────────────────────
account_id           хэш от pubkey             32B (64 hex-символа) протокол + сеть
никнейм (@alice)     глобальное имя            ASCII до 32 символов вся сеть
петнейм (Мама)       локальный псевдоним       UTF-8 до 64 символов только владелец

account_id — консенсус-уровень, immutable, выведен из сид-фразы. Никнейм — консенсус-уровень, immutable после покупки, опциональный. Петнейм — клиент-уровень, изменяемый, определяется в app spec (раздел «Discovery Module»).

Область имён

  • Алфавит: ASCII [a-z0-9_-] — строчные латинские буквы, цифры, подчёркивание, дефис
  • Длина: 1..=32 байт
  • Канонизация: никнейм хранится в нижнем регистре; реализация отвергает операции с верхним регистром в nickname_bytes
  • Unicode-омоглифы исключены по построению (только ASCII)

Голландский аукцион с английским расширением

Аукцион каждого никнейма имеет две фазы:

Фаза 1 — Голландский спад (по умолчанию для любого свободного никнейма):

  • При первом NicknameBid для свободного никнейма запускается новый аукцион
  • Стартовая цена вычисляется из starting_price_nj(nickname) — функция длины и алфавитного класса
  • Цена падает ступенчато на границе каждого τ₂: price(W) = price(W-τ₂) × decay_num / decay_den (по умолчанию 85/100 — спад 15% за каждое τ₂)
  • Минимум: floor_price_nj (по умолчанию 1% от базовой)
  • Любой аккаунт может принять текущую цену через NicknameBid с bid_amount_nj >= price_at(nickname, W)

Фаза 2 — Английское расширение (триггерится первой принятой ставкой):

  • При первой ставке в голландской фазе — аукцион не закрывается сразу
  • Стартует окно перебид'ов длиной nickname_english_extension_τ₂ × τ₂_windows (по умолчанию 2τ₂)
  • Другие участники могут ставить выше с минимальным инкрементом (по умолчанию +5% от текущей высшей)
  • Анти-снайпинг: ставка в последние nickname_antisnipe_τ₁ окон (по умолчанию 2) продлевает english_end_window на те же nickname_antisnipe_τ₁ окон
  • Жёсткий потолок: максимум nickname_english_max_extensions продлений (по умолчанию 10); после достижения — english_end_window зафиксирован, дальнейшие продления игнорируются
  • По истечении english_end_window: никнейм присваивается highest_bidder, highest_bid_nj сжигается из supply_nj, предыдущие перебид'ы (которые были биты более высокими) — возвращены на balance биттерам во время самих перебид'ов (escrow-паттерн)

Ценообразование — integer form (authoritative, per [I-9])

starting_price_nj(nickname): u128
  Input:
    nickname: bytes[1..=32]
  Algorithm:
    length_tier  = select_length_tier(len(nickname))     # u64
    alphabet_tier = select_alphabet_tier(nickname)       # u64
    base_price_nj = Genesis.nickname_base_price_nj       # u128
    return base_price_nj * (length_tier * alphabet_tier) as u128

select_length_tier(len): u64
  if len == 1                → 10000
  if len == 2                → 2000
  if len == 3                → 400
  if len == 4                → 80
  if len ∈ [5, 6]            → 16
  if len >= 7                → 1

select_alphabet_tier(nickname): u64
  if все байты ∈ [a-z]                                → 4   # чистая латиница
  if все байты ∈ [a-z]  [0-9]                       → 2   # латиница + цифры
  иначе (содержит '_' или '-')                        → 1   # общий случай

floor_price_nj(nickname): u128
  return starting_price_nj(nickname) * Genesis.nickname_floor_ratio_num
           / Genesis.nickname_floor_ratio_den
  # По умолчанию: 1/100 от starting — пол составляет 1% начальной

price_at(nickname, W): u128
  Input:
    nickname: bytes
    W: u64, auction_start_window: u64, τ₂_windows: u64
    decay_num: u64, decay_den: u64
  Algorithm:
    s = starting_price_nj(nickname)
    f = floor_price_nj(nickname)
    k = (W - auction_start_window) / τ₂_windows     # integer div toward zero
    current = s
    for i in 0..k:
      current = (current * decay_num) / decay_den    # unsigned u128 × u64 → u128
      if current <= f:
        return f                                      # ранняя остановка при достижении пола
    if current < f:
      return f
    return current

Предотвращение переполнения: starting_price_nj <= base × 40000 (макс length_tier × alphabet_tier = 10000 × 4). При base_price_nj = 10^8 nɈ итог <= 4 × 10^12 nɈ — в u64 границах. Умножение current * decay_num с decay_num < 2^7 остаётся в u128 границах для любых разумных значений.

Tie-break в одном окне τ₁ (per [I-12], [I-3])

Если несколько NicknameBid операций на один никнейм помещены в BundledConfirmation того же окна:

  1. Упорядочить по bid_amount_nj убывающе (больший платит больше — выигрывает)
  2. При равной bid_amount_nj — упорядочить по op_hash лексикографически возрастающе (первый в лексикографическом порядке выигрывает)
  3. Применить только первый; остальные отвергаются как «проигравшие этот раунд», Монтана не списывается (никакого escrow для проигравших в одном окне)

State-машина аукциона

AuctionState:
  starting_price_nj:    u128
  auction_start_window: u64
  phase:                u8     # 0 = Dutch decay, 1 = English extension
  highest_bid_nj:       u128   # 0 если ставок ещё нет
  highest_bidder:       [u8; 32]  # 0x00..00 если ставок ещё нет
  english_end_window:   u64    # 0 в Dutch фазе; set при переходе в English
  extension_count:      u8     # ∈ [0, nickname_english_max_extensions]
Итого: 16 + 8 + 1 + 16 + 32 + 8 + 1 = 82B per aukcион

Таблица AuctionTable: Map<nickname → AuctionState> — часть consensus state, обновляется в apply_proposal шаг nickname_auction_tick (см. ниже).

Новый шаг в apply_proposal

Step 3.8: nickname_auction_tick (между Step 3.7 checkpoint_rotation и Step 4)

  for auction in AuctionTable:
    if auction.phase == 1 and current_window >= auction.english_end_window:
      # Закрытие английской фазы — присуждение никнейма
      winner_account = AccountTable[auction.highest_bidder]
      winner_account.nickname_len   = len(auction.nickname)
      winner_account.nickname_bytes = auction.nickname (padded with 0x00)
      NicknameTable[auction.nickname] = auction.highest_bidder
      supply_nj -= auction.highest_bid_nj            # burn
      AuctionTable.remove(auction.nickname)

Голландский спад в AuctionState не хранится явно — вычисляется on-demand через price_at(nickname, W). Это exchange: меньше state, больше CPU на каждую проверку цены. Reasonable trade-off т.к. аукционов одновременно обычно немного.

Зарезервированные никнеймы

В Genesis Decree фиксируется список nickname_reserved_list — никнеймы которые никогда не продаются через аукцион, permanent reserved. Попытка NicknameBid с зарезервированным именем отвергается (см. инвариант «не входит в зарезервированный список»).

Дефолтный список:

["montana", "mnt", "admin", "bootstrap", "genesis",
 "root", "system", "help", "support", "info"]

10 зарезервированных имён. Расширение списка — через software upgrade (меняет Genesis Decree хэш, значит breaking change, несовместимая сеть).

Binding test vectors (per [I-9])

Стартовая цена для никнеймов разной длины (базовая цена = 10^8 nɈ = 0.1 TC):

Никнейм Length tier Alphabet tier starting_price_nj В TC
a 10000 4 (a-z) 4_000_000_000_000 4000 TC
ab 2000 4 800_000_000_000 800 TC
abc 400 4 160_000_000_000 160 TC
abcd 80 4 32_000_000_000 32 TC
alice 16 4 6_400_000_000 6.4 TC
alice_ 16 1 (содержит _) 1_600_000_000 1.6 TC
alice42 1 2 (a-z + цифры) 200_000_000 0.2 TC
a1b2c3d4e5 1 2 200_000_000 0.2 TC

Голландский спад price_at(nickname, W) для alice (starting = 6.4 TC, floor = 0.064 TC):

k (число τ₂ с начала) Цена nɈ Цена TC
0 6_400_000_000 6.400
1 5_440_000_000 5.440
2 4_624_000_000 4.624
5 2_839_316_800 2.839
10 1_259_895_846 1.260
20 247_983_033 0.248
28 82_348_234 0.082
29 69_995_998 0.070
30 64_000_000 0.064 (достигнут пол)
100 64_000_000 0.064 (остаётся на полу)

(decay_num = 85, decay_den = 100 → 15% спад/τ₂; за 30 τ₂ падает в 100 раз до пола.)

PurchaseCredits — burn и начисление:

Before:
  sender.balance             = 1_000_000_000_000 nɈ (1000 TC)
  sender.service_credits_nj  = 0
  supply_nj                  = S₀

PurchaseCredits(amount_nj = 100_000_000_000):   # 100 TC

After:
  sender.balance             = 900_000_000_000 nɈ (900 TC)
  sender.service_credits_nj  = 100_000_000_000 nɈ (100 TC эквивалент)
  supply_nj                  = S₀ - 100_000_000_000    # чистое сжигание

ConsumeCredits — списание без burn:

Before (после PurchaseCredits выше):
  sender.service_credits_nj  = 100_000_000_000
  supply_nj                  = S₀ - 100_000_000_000

ConsumeCredits(usage_nj = 5_000_000, service_type = 0x01):  # 5 минут голосового звонка × 10^6 nɈ/мин

After:
  sender.service_credits_nj  = 99_995_000_000
  supply_nj                  = S₀ - 100_000_000_000    # не изменяется

Rate-константы услуг в Genesis Decree

voice_call_rate_per_min_nj              = 1_000_000         # 0.001 Ɉ/мин
video_call_rate_per_min_nj              = 5_000_000         # 0.005 Ɉ/мин
premium_profile_monthly_nj              = 10_000_000_000    # 10 Ɉ/мес
anchor_per_kb_nj                        = 100_000           # 0.0001 TC/KB
anchor_free_kb                          = 1                 # первый KB бесплатно
creator_subscription_minimum_monthly_nj = 100_000_000       # 0.1 TC минимальная месячная подписка

Конкретные списания через ConsumeCredits с соответствующим service_type — клиентская сторона агрегирует usage и публикует periodic. Protocol валидирует только sender.service_credits_nj >= usage_nj и корректность service_type.

Инварианты Nickname Auction Protocol

  • [I-11] Уникальность никнеймов (см. раздел «Глобальные инварианты протокола») — биекция nickname ↔ account_id
  • [I-12] Детерминизм аукционаprice_at, tie-break правило, закрытие English-фазы bit-exact у всех узлов
  • [I-13] Deflationary sink — все выплаты за никнеймы и кредиты уходят в supply_nj -= X, никаких pool-структур

Conformance status

closed — binding test vectors для price_at (8 пунктов), starting_price_nj (8 длин × 3 алфавитных тира = полный matrix), PurchaseCredits burn (2 вектора), ConsumeCredits списание (2 вектора). Полный набор в разделе «Binding test vectors» выше.


Эмиссия

Единица

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

1 Ɉ = 1 000 mɈ = 1 000 000 μɈ = 1 000 000 000 nɈ

Одно окно τ₁ регистрирует одну единицу Montana Time с эмиссией reward(W) = R_BASELINE + bonus(W), где R_BASELINE = 13 Ɉ — вечная константа, а bonus(W) — конечный bootstrap component.

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

Issuance schedule

Эмиссия состоит из двух частей:

  1. Baseline — 13 Ɉ за окно, с первого окна и навсегда.
  2. Bootstrap bonus schedule — конечная добавка, распределённая на первые 5 × BOOTSTRAP_H окон с halving-pattern сжатия.
Параметр Значение
Genesis window_index = 0
R_BASELINE 13 000 000 000 nɈ (13 Ɉ за окно, forever)
BOOTSTRAP_R0 16 000 000 000 nɈ (стартовый bonus эпохи 0)
BOOTSTRAP_H 524 160 окон (= 26 × τ₂_windows)
BOOTSTRAP_EPOCHS 5
BOOTSTRAP_CAP 16 248 960 000 000 000 nɈ (= 16 248 960 Ɉ)

Регистрация окна

reward_nɈ(W) = R_BASELINE_nɈ + bonus_nɈ(W)

shift = floor(W / BOOTSTRAP_H)
bonus_nɈ(W) = (shift < BOOTSTRAP_EPOCHS) ? (BOOTSTRAP_R0_nɈ >> shift) : 0

Каждое окно τ₁ регистрирует одно каноническое окно Montana Time. Baseline 13 Ɉ остаётся константой на весь горизонт существования протокола. Bootstrap bonus hard-capped на BOOTSTRAP_CAP.

Схема bonus по эпохам (значение bonus, окон в эпохе = 524 160):

Эпоха 0: bonus = 16 Ɉ, reward = 29 Ɉ
Эпоха 1: bonus =  8 Ɉ, reward = 21 Ɉ
Эпоха 2: bonus =  4 Ɉ, reward = 17 Ɉ
Эпоха 3: bonus =  2 Ɉ, reward = 15 Ɉ
Эпоха 4: bonus =  1 Ɉ, reward = 14 Ɉ
Эпоха 5+: bonus = 0,    reward = 13 Ɉ (forever)

Supply audit

supply_nɈ(W) = R_BASELINE_nɈ × (W + 1) + bootstrap_cumulative_nɈ(W)
bootstrap_cumulative_nɈ(W) ≤ BOOTSTRAP_CAP_nɈ

Где bootstrap_cumulative_nɈ(W) вычисляется O(1) по закрытой форме (см. «Per-proposal invariant»).

Эмиссия за прошедшее окно: proposal_W зачисляет reward(W-1) Ɉ за окно W-1 (Lookback Leadership — winner_{W-1} определяется при cementing proposal_W). После cementing proposal_W суммарно зачислены окна от 0 до W-1. Проверяемо каждым узлом в каждом τ₁. O(1).

Инфляция

Baseline эмиссия константна по window_index. Bootstrap эмиссия конечна. Инфляция за окно W:

inflation(W) = reward_nɈ(W) / supply_nɈ(W)

Во время bootstrap-фазы (W < 5 × BOOTSTRAP_H) инфляция повышена bonus-компонентом. После эпохи 5 инфляция становится 13 / (13 × (W+1) + BOOTSTRAP_CAP) и монотонно убывает, асимптотически стремясь к нулю. Чистая арифметика от window_index.

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

Участие в bootstrap-фазе даёт увеличенный reward. Bonus затухает от 16 Ɉ в эпохе 0 до 0 в эпохе 5. Baseline 13 Ɉ неизменен на всём горизонте. Вероятность победы пропорциональна весу. Узел, работающий дольше, побеждает чаще. Два узла, запустившиеся одновременно, имеют равные шансы независимо от капитала. Узел, запустившийся раньше, имеет преимущество — он доказал больше окон присутствия.

Стимул для ранних участников — bootstrap bonus, сходящийся к нулю. После 5 эпох распределение определяется только baseline 13 Ɉ и весом участия.

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

Победитель окна τ₁ — всегда узел — регистрирует одно окно Montana Time и получает reward(W) Монтана (зачисляется на operator_account_id узла). Одно правило. Baseline неизменен с генезиса, bootstrap конечен и капирован.

Лотерея single-class: конкурируют только узлы. Победитель — узел с lowest weighted_ticket_node среди cemented VDF_Reveal кандидатов окна. chain_length и seniority bonus определяют вес — время и непрерывность работы единственный арбитр.

Базовый бюджет: reward(W) Ɉ/τ₁. Вечный компонент — 13 Ɉ/τ₁. Реальный бюджет безопасности в покупательной способности зависит от рынка.

Эмиссия — каноническая функция reward(W). Покупательная способность определяется рынком, а не протоколом.

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

Лестница суверенности связывает активность пользователей и мотивацию запускать узлы:

Активные пользователи в приложениях → AccountChain растёт → больше burn (PurchaseCredits,
        ↓                                                         NicknameBid, Anchor fees)
Приложения привлекают пользователей                             ↓
        ↓                                              Дефляционный sink, удержание ценности TC
Разработчики хотят пользователей                                ↓
        ↓                                              Больше операций в сети
Разработчики / пользователи поднимают узлы Montana → зарабатывают Монтана через node lottery
        ↓                                                         ↓
Путь перехода: Account-only → Operator                Сеть растёт и децентрализуется
        ↓                                                         ↓
Финансирование развития инфраструктуры → больше пропускной способности → лучше для приложений

Эмиссия reward(W) направляется только на узлы (поддержание сети). Пользовательская активность не даёт прямой эмиссии; вместо этого она поддерживает стоимость TC через deflationary sinks (burn от платных услуг) и создаёт спрос на инфраструктуру узлов. Путь «Account → Operator» — единственный protocol-level способ для пользователя начать получать эмиссию.


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

Правило «1 op/τ₁» — per-account, не сетевое. Сетевая пропускная способность определяется пропускной способностью канала узла и размером proposal; цепочки аккаунтов независимы и обрабатываются параллельно в одном окне.

Entry rate узлов (τ₂ VDF + selection event) ортогонален TPS сети. Узел операционен с момента установки — обслуживает своего оператора, хранит данные, работает gateway для мобильного клиента — вне зависимости от статуса в Node Table. Consensus-роль (вес, лотерея узла, confirmer) активируется после entry; user-level функциональность не ждёт.

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

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

Хранение

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

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

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

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

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

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

Два участника

Узел — мой компьютер (десктоп, сервер, VPS), 24/7, минимум 1 ядро:

Consensus (протокольный слой):
  Account Table              (account_id, balance, frontier_hash, pubkey)
                             + persistent sparse Merkle tree (account_root)
  Node Table                 (node_id, pubkey, start_window, chain_length)
                             + persistent sparse Merkle tree (node_root)
  Candidate Pool             (node_id, pubkey, operator, proof_endpoint, W_start, vdf_chain_length, expires)
                             + persistent sparse Merkle tree (candidate_root)
  Proposals                  (навсегда)
  TimeChain VDF + валидация   (1 ядро, 24/7, validation interleaved)
  P2P gossip                 (операции, confirmations, reveals, proposals)

Данные владельца (клиентский слой):
  Локальное хранилище        (фото, файлы, бэкапы сообщений — зашифровано)
  Почтовый ящик              (входящие сообщения пока телефон офлайн)

Узел принадлежит оператору. Оператор решает что хранить помимо consensus state. Consensus state обязателен — без него узел не участвует в сети. Данные владельца — решение клиентского слоя: формат, шифрование, объём, retention.

Ядра и производительность. TimeChain VDF — sequential по построению; дополнительные ядра не ускоряют продвижение TimeChain. Второе ядро изолирует validation от VDF-цикла и устраняет interleaving overhead (пропуск окон ~5-10% при нагрузке). Узлы с 1 ядром полностью участвуют в консенсусе; узлы с 2+ ядрами имеют bounded преимущество в participation_ratio, ограниченное сверху interleaving overhead. Participation-ratio feedback на τ₂-boundary автоматически подстраивает D под фактическое железо медианы сети.

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

Хранит:
  Свои ключи            (seed → keypairs)
  Локальная история     (операции, сообщения — для UX)

Делает:
  Подключается к своему узлу
  Отправляет/получает переводы через узел
  Читает/пишет данные на свой узел
  Забирает сообщения из почтового ящика узла

Потеря телефона: seed восстанавливает ключи, баланс в Account Table публичен, данные на узле целы. Потеря узла: seed восстанавливает аккаунт, consensus state скачивается через Fast Sync. Данные владельца (фото, сообщения) — ответственность оператора (бэкап, RAID, репликация между своими узлами — клиентский слой).

Привязка телефона к узлу, авторизация, синхронизация, формат хранения данных — клиентский слой. Протокол предоставляет identity (account_id ↔ operator_account_id) как основу для привязки.

Подключение без собственного узла. Пользователь с аккаунтом но без узла подключает телефон к чужому узлу через IBT уровень 3 (account keypair). Чужой узел — это узел приложения Montana (app creator's infrastructure), public node, или community-run узел. Соединение через TLS 1.3 + Noise + FN-DSA-512 IBT proof — никто кроме владельца account privкey не может подключиться под его именем.

Хостящий узел gossip-ит операции пользователя в сеть так же как для локально подключённых accounts. Для пользователя процесс идентичен — кошелёк работает одинаково независимо от того свой узел или чужой.

Разница — хостящий узел видит IP и тайминг операций пользователя (metadata). Контент приложения (Anchor data) зашифрован — узел видит только хэш в сети, не содержимое. Dandelion++ на первом хопе частично обфусцирует origin операции от дальнейших хопов.

Размеры

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

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

Потеря телефона: seed восстанавливает ключи, баланс в Account Table публичен, данные на узле целы — полное восстановление. Потеря узла: seed восстанавливает аккаунт, consensus state — через Fast Sync. Данные владельца (фото, сообщения) — ответственность оператора. Бэкап, RAID, репликация между своими узлами — решения клиентского слоя.

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

  1. Цепочка proposals от генезиса — проверка TimeChain-цепочки и подписей proposer-узлов (мегабайты)
  2. Snapshot трёх таблиц (Account Table + Node Table + Candidate Pool) от пиров на момент окна W (произвольное недавнее окно)
  3. Reconstructed account_root, node_root и candidate_root сравниваются с соответствующими полями из proposal окна W. Все три совпадают → snapshot валиден. Проверка state_root = SHA-256("mt-state-root" || node_root || candidate_root || account_root) — дополнительный integrity check.
  4. Catch-up после окна W до текущего:
    • Запросить cemented UserObjects и применить их батчем к Account Table по алгоритму apply at window close (включая проверку prev_hash и баланса).
    • Запросить cemented ControlObjects (NodeRegistration) и применить их к Candidate Pool в детерминированном порядке. Применить selection events.
    • Выполнить incremental update Merkle trees (account_root, node_root, candidate_root) для отражения changes.
    • На каждом промежуточном proposal сверять локальный state_root с заявленным в proposal header
  5. Genesis content. genesis_content_data_hash зафиксирован в Genesis Decree как протокольная константа. Загрузка книги Montana по этому хэшу — конвенция reference implementation. Формат загрузки и верификации определяется клиентским слоем.
  6. Узел синхронизирован и готов к участию

Snapshot привязан к конкретному proposal (settled state после apply at window close). Catch-up дистанция определяется свежестью snapshot — обычно несколько окон.

Полнота сериализации snapshot. Snapshot обязан содержать canonical byte-for-byte сериализацию всех записей каждой таблицы согласно определениям раздела «Состояние сети» — ВСЕ поля каждой записи, включая производные (chain_length_snapshot, checkpoints), счётчики (last_confirmation_window, op_height, account_chain_length), VDF-метаданные (vdf_chain_length, W_start, proof_endpoint) и pubkey material. Пропуск или изменение любого поля одной записи меняет её canonical serialization → меняется хэш листа Merkle tree → несовпадение с proposer-recorded root окна W → snapshot rejected, retry с другого пира.

Это делает полноту snapshot enforced криптографически через Merkle root comparison, не через явное перечисление полей в Fast Sync спецификации. Добавление нового поля в record format (будущая версия протокола) автоматически распространяется в snapshot через canonical encoding — Fast Sync логика не требует изменений. Единственное требование: canonical encoding и Node Table / Account Table / Candidate Pool definitions — single source of truth для serialization.

Reference implementation обязана сериализовать записи ровно по определениям state records с canonical byte ordering. Отклонения от canonical encoding в одной реализации = несовместимость с другими = невозможность Fast Sync между разными реализациями. Conformance tests должны включать snapshot serialization для эталонного state как один из test vectors.


Application Layer

Montana — цифровой стандарт времени. Приложения управляют своим состоянием самостоятельно (серверы, базы данных, P2P). Montana хранит только криптографические отпечатки с привязкой ко времени — 32 байта на запись.

Модель приложения на Montana

Приложение Montana — это набор узлов с интерфейсом. Создатель приложения запускает узлы Montana (обычные узлы, тикающие VDF, валидирующие операции, участвующие в консенсусе). Узлы зарабатывают Монтана за поддержание сети через лотерею.

Для создателя приложения:

  • Не нужно строить отдельную инфраструктуру безопасности — приватность данных через Anchor (хэш в сети, контент у владельца зашифрованным), антицензура через Transport Obfuscation и Dandelion++, децентрализация через отсутствие центрального сервера получаются бесплатно из протокола
  • Бизнес-модель: эмиссия Montana через узлы создателя. Не реклама, не подписка, не продажа данных
  • Чем больше пользователей в приложении → тем больше операций в сети → тем больше нужно узлов для обслуживания (Blob Buffer, валидация, P2P gossip) → больше узлов = больше шансов в лотерее = больше Монтана
  • Hosting accounts пользователей. Узлы приложения принимают подключения account-only пользователей через IBT уровень 3. Это не требует отдельной инфраструктуры — стандартный Montana узел умеет хостить accounts из коробки. Каждый пользователь app = одно activное TLS+IBT соединение + gossip пользовательских операций в сеть. Экономический стимул: активные пользователи генерируют cemented операции, которые попадают в BundledConfirmation узла → increment chain_length → node lottery weight → больше Монтана

Для пользователя:

  • Каждое действие в приложении создаёт операцию в его AccountChain
  • account_chain_length растёт автоматически с каждым окном с операцией
  • Аккаунты не участвуют в лотерее — пользователь не получает эмиссию напрямую за активность. Путь к эмиссии — Лестница суверенности: поднять собственный узел (роль оператора) и зарабатывать через лотерею узлов
  • account_chain_length_snapshot даёт доступ к NicknameBid после nickname_activity_threshold_windows активных окон (anti-Sybil threshold) — единственное protocol-level применение account activity на уровне консенсуса
  • Ничего не привязано к конкретному приложению — seed принадлежит пользователю, account_id переходит между приложениями без потери истории

Нулевая стоимость переключения приложений. AccountChain пользователя — его собственность. Если приложение закрылось или пользователь хочет уйти — account_id, баланс, история и накопленный account_chain_length остаются. Пользователь продолжает в другом приложении на том же протоколе. Приложения вынуждены конкурировать качеством, а не замком.

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

Лотерея Montana single-class: эмиссию получают только узлы (через VDF_Reveal с каноническим endpoint). Пользовательская активность не даёт прямой эмиссии — она питает сеть через deflationary sinks (PurchaseCredits, NicknameBid, Anchor fees) и создаёт спрос на инфраструктуру узлов. Путь Лестницы суверенности (Account → Operator) — единственный protocol-level способ начать зарабатывать эмиссию — см. раздел «Эмиссия → Двигатель роста сети» и «Два пути участия».

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.

Операции аккаунтов финализируются через BundledConfirmations узлов-confirmers, не через включение в proposal. Доказательство существования Anchor — набор подписанных подтверждений с суммарным chain_length ≥ quorum.

Proof собирается владельцем Anchor в момент финализации и хранится локально вместе с документом. Сеть не обязана хранить BundledConfirmations долгосрочно — ответственность за сохранение proof лежит на стороне, которой нужно доказать timestamp.

Структура proof:
  1. Документ D и H(D)
  2. Anchor body (prev_hash, account_id, app_id, data_hash, signature)
  3. Если data_hash = MerkleRoot batch'а: Merkle path от H(D) до data_hash
  4. Набор BundledConfirmations за окно W cementing'а Anchor:
     - каждая содержит H(Anchor) в op_hashes[]
     - каждая подписана confirmer node_pubkey
     - каждая содержит T_r текущего окна (endpoint field)
     - суммарный chain_length confirmers ≥ 67% active_chain_length(W)
  5. Proposal header окна W (содержит timechain_value = T)
  6. Цепочка proposal headers от W до genesis (через prev_proposal_hash)

Верификация любым третьим лицом, без доверия Montana-узлу:
  1. Если есть Merkle path: пересчитать H(D) → data_hash, сравнить с data_hash в Anchor
  2. Проверить FN-DSA-512 подпись на Anchor
  3. Для каждой BundledConfirmation: проверить FN-DSA-512 подпись confirmer
  4. Для каждой confirmation: проверить endpoint = T_r окна W, подтвердить chain_length из Node Table
  5. Суммировать chain_length подтверждающих, проверить ≥ 67% active_chain_length(W)
  6. Из proposal header окна W взять timechain_value = T
  7. Пересчитать TimeChain VDF от proposal окна W до genesis по prev_proposal_hash

Proposals хранятся навсегда — timechain_value(W) и цепочка к genesis всегда доступны. BundledConfirmations хранятся локально владельцем proof. Timestamp proof самодостаточен и верифицируем в любой момент в будущем.

Примеры

Мессенджер. Каждое сообщение хэшируется, цепочка хэшей формирует Merkle root, Merkle root записывается в Anchor раз в одно или несколько окон. Montana хранит 32 байта — доказательство что набор сообщений существовал на конкретном window_index. Подделать историю переписки невозможно — хэш не совпадёт.

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

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

Экономика

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

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

Граница протокола и клиентского слоя

Протокол предоставляет три примитива: время (window_index), ценность (Transfer), фиксация (Anchor). Всё остальное — хранение данных, мессенджер, discovery контактов, профили, шифрование, репликация контента, форматы файлов — реализуется клиентским слоем. Стандарты совместимости между приложениями определяются в спецификации приложения (Montana App spec), не в протоколе.

Локальное хранилище узла

Узел помимо consensus state имеет локальное хранилище произвольных байт. Это инфраструктура реализации, не consensus — содержимое хранилища не входит ни в один root, не проверяется другими узлами, не влияет на участие в консенсусе.

Два режима:

  • Ephemeral (TTL = τ₂) — кратковременные данные, удаляются автоматически
  • Persistent (TTL = 0) — данные владельца, хранятся бессрочно по решению оператора

Формат хранения, индексация, чанкование файлов, протокол обмена данными между узлами, механизмы discovery контента — определяются клиентским слоем (см. Montana App spec).

genesis_content_data_hash — протокольная константа в Genesis Decree. Хэш манифеста книги Montana v1.0. Загрузка и хранение книги по этому хэшу — конвенция reference implementation, не consensus enforcement. Узел без книги продолжает участвовать в консенсусе.

Integration

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

Write — запись

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

Вход:  app_id (32B) + data_hash (32B) + подпись FN-DSA-512
Выход: Anchor финализирован в окне W через ≥67% active_chain_length
       confirmations с timechain_value T_W

data_hash — произвольный хэш: Merkle root документов, хэш batch'а Rollup, fingerprint состояния. Montana не интерпретирует содержимое — хранит 32 байта с привязкой ко времени.

Read — сбор proof

Внешняя система собирает timestamp proof в момент финализации Anchor.

Вход:  Anchor (только что финализированный)
Выход: Anchor body + BundledConfirmations покрывающие H(Anchor) +
       proposal header окна cementing'а + цепочка proposal headers до genesis

Сбор proof — клиентская задача. После получения BundledConfirmations с суммарным chain_length ≥ quorum клиент сохраняет proof локально. Узлы Montana не обязаны хранить BundledConfirmations долгосрочно — они нужны только для текущего подсчёта quorum.

Verify — верификация

Внешняя система проверяет proof автономно, без доверия к Montana-узлу.

1. Если есть Merkle path: пересчитать H(D) → data_hash в Anchor
2. Проверить FN-DSA-512 подпись на Anchor
3. Для каждой BundledConfirmation в proof:
   a. Проверить FN-DSA-512 подпись confirmer
   b. Проверить endpoint = T_r окна W
   c. Подтвердить chain_length из Node Table
4. Суммировать chain_length подтверждающих ≥ 67% active_chain_length(W)
5. Проверить FN-DSA-512 подпись proposer на header окна W
6. Проверить timechain_value(W) пересчётом TimeChain VDF от T_{W-1}
7. Проверить цепочку proposals от W до genesis (prev_proposal_hash)

Шаги 1, 2, 3a, 3b, 5: O(1) операций. Шаг 6: D хэшей на одном ядре (один сегмент TimeChain VDF). Шаг 7: линейная проверка подписей и хэшей по цепочке proposals от окна W до genesis.

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


Ключи

Мнемоника и seed

24 слова из canonical wordlist. 256 бит энтропии + 8 бит checksum = 264 бита.

Каноническая wordlist

Каноническая wordlist — файл Montana wordlist.txt в директории настоящей спецификации.

Формат файла: 2048 строк lowercase ASCII, по одному слову на строку, разделитель строк — один байт 0x0A (LF), файл завершается 0x0A после последнего слова. Слова упорядочены лексикографически; первое слово — abandon, последнее — zoo.

Canonical encoding wordlist-а для fingerprint:

wordlist_canonical_bytes = concat(word_i || 0x0A) для i ∈ [0, 2047]
                           (включая trailing 0x0A после "zoo")
total length              = 13 116 байт

Binding fingerprint:

SHA-256(wordlist_canonical_bytes) =
  2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda

Любая реализация при старте обязана вычислить SHA-256 своего встроенного wordlist в canonical encoding и сверить с binding fingerprint. Несовпадение — fatal error.

Параметры мнемоники

Параметр Значение
MNEMONIC_WORD_COUNT 24
MNEMONIC_ENTROPY_BITS 256
MNEMONIC_CHECKSUM_BITS 8
MNEMONIC_TOTAL_BITS 264 (= 24 × 11)
WORD_INDEX_BITS 11
WORDLIST_SIZE 2048
WORD_SEPARATOR 0x20 (один ASCII space)
KDF_SALT ASCII "mt-seed" (7 байт, domain separator из реестра)
KDF_ITER 1 048 576 (= 2²⁰)
MASTER_SEED_LEN 64 байта
FALCON_SEED_LEN 48 байт (требование FN-DSA-512 KeyGen)
MLKEM_SEED_LEN 64 байта (требование ML-KEM-768 KeyGen)

Passphrase-расширение (13-е слово) в данной версии не поддерживается.

Формат мнемоники

Мнемоника — строка из 24 слов в нижнем регистре ASCII, разделённых ровно одним байтом 0x20. Перевод строки, табуляция, множественные пробелы недопустимы.

Бинарное представление — 24 × 11 = 264 бита = 33 байта:

bits   0..255 — entropy (32 байта)
bits 256..263 — checksum (1 байт)

Checksum вычисляется как первый байт SHA-256(entropy):

checksum_expected = SHA-256(entropy_32_bytes)[0]

Невалидная мнемоника — одно из: число слов ≠ 24; хотя бы одно слово не принадлежит canonical wordlist; computed checksum не равен checksum из bit-packed представления. Ошибки парсинга — client-side, не имеют wire-format representation; реализация возвращает любое подходящее представление для языка.

Algorithm M-1. mnemonic_to_master_seed

Function M-1: mnemonic_to_master_seed(mnemonic_str: ascii_bytes) → master_seed: [u8; 64]

  // Шаг 1. Разбить строку на слова по ASCII space 0x20.
  words = split_by_single_0x20(mnemonic_str)
  require len(words) == 24 else INVALID_LENGTH

  // Шаг 2. Для каждого слова получить индекс через binary search в wordlist.
  indices: [u16; 24]
  for i in 0..24:
    idx = binary_search(canonical_wordlist, words[i])
    require idx is defined else INVALID_WORD(i)
    indices[i] = idx

  // Шаг 3. Bit-packing 24 × 11 бит → 33 байта, MSB-first.
  buf: [u8; 33] = [0; 33]
  bit_pos = 0
  for i in 0..24:
    for b in 0..11:                                  // b=0 — старший бит индекса
      bit = (indices[i] >> (10 - b)) & 1
      byte_idx = bit_pos / 8
      bit_in_byte = 7 - (bit_pos % 8)                // bit 7 = MSB в byte
      buf[byte_idx] |= bit << bit_in_byte
      bit_pos += 1

  // Шаг 4. Разделить entropy и checksum, сверить checksum.
  entropy_32 = buf[0..32]
  checksum_provided = buf[32]
  checksum_computed = SHA-256(entropy_32)[0]
  require checksum_provided == checksum_computed else INVALID_CHECKSUM

  // Шаг 5. PBKDF2-HMAC-SHA-256 → master_seed 64 байта.
  salt = ascii_bytes("mt-seed")
       = [0x6d, 0x74, 0x2d, 0x73, 0x65, 0x65, 0x64]   // 7 байт
  master_seed = PBKDF2-HMAC-SHA-256(
                  password = entropy_32,
                  salt     = salt,
                  iter     = 1_048_576,               // = 2^20
                  dkLen    = 64
                )

  return master_seed

Per-role key derivation

Три keypair выводятся из master_seed через HKDF-Expand (RFC 5869 §2.3; integer spec — в «Криптографическая реализация → Primitive layer → HKDF-Expand») с ролевыми domain separators:

falcon_seed_48(role_ascii) = HKDF-Expand(PRK = master_seed, info = role_ascii, L = 48)
mlkem_seed_64(role_ascii)  = HKDF-Expand(PRK = master_seed, info = role_ascii, L = 64)

account_keypair        = FN-DSA-512.KeyGen( falcon_seed_48("mt-account-key") )
node_keypair           = FN-DSA-512.KeyGen( falcon_seed_48("mt-node-key") )
app_encryption_keypair = ML-KEM-768.KeyGen( mlkem_seed_64("mt-app-encryption-key") )

Derivation плоская — одна HKDF-Expand evaluation per role, без дерева; конструкция не эквивалентна BIP-32 HD-wallet.

FN-DSA-512.KeyGen принимает 48-байтный seed, инициализирует SHAKE256-CSPRNG по Falcon Round 3 Submission §3.8 Algorithm 5. ML-KEM-768.KeyGen принимает 64-байтный seed по FIPS 203. При идентичном seed обе KeyGen функции детерминистически выдают byte-identical keypair.

account_id = SHA-256("mt-account" || account_pubkey_suite_id || account_pubkey) — см. «Состояние сети». node_id = SHA-256("mt-node" || node_pubkey) — см. «Состояние сети».

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

Обоснование KDF_ITER = 2²⁰

  • Class: security + performance
  • Target: derivation time ≤ 1 секунда на commodity ARM Cortex-A78 (iPhone SE 2020 / Pixel 5) single-core
  • References: NIST SP 800-132 §5.2; OWASP Password Storage Cheatsheet 2024 (recommendation ≥ 600 000 итераций для PBKDF2-HMAC-SHA-256)
  • Derivation: Cortex-A78 single-core выполняет ≈ 1.5 × 10⁶ PBKDF2-HMAC-SHA-256 iter/sec. 2²⁰ ≈ 1.05 × 10⁶ → ≈ 0.7 сек wall-clock; с thermal throttling ≈ 1 сек. OWASP 2024 minimum 600 000 — 2²⁰ exceeds с margin 75%.
  • Sensitivity: 2¹⁷ → 8× слабее brute-force, UX 0.09 сек; 2²² → 4× крепче, UX 3 сек. Grover quantum speedup на 256-bit entropy → 2¹²⁸ work остаётся за horizon heat-death universe.
  • Defense: «Slow for mobile» — derivation однократна при recovery, после cache в secure enclave; «Не Argon2» — Argon2 = новый примитив, нарушает [I-7]; PBKDF2-HMAC-SHA-256 — композиция поверх уже принятого SHA-256, zero new audit surface.

Взаимодействие со State

Формат TransferActivation, запись AccountRecord и функция apply_proposal в связи с данным разделом не изменяются. Мнемоника — локальный инструмент клиента; сеть видит только FN-DSA-512 pubkey аккаунта (и отдельно node_pubkey через NodeRegistration).

Один master_seed порождает все три keypair — аккаунта (подпись операций), узла (подпись proposals и lottery endpoints), приложения (ML-KEM-768 шифрование). Любое устройство с мнемоникой восстанавливает полный контроль; баланс читается из текущего Account Table — локального состояния не требуется.

Смена ключа аккаунта (ротация либо реакция на компрометацию мнемоники) в данной версии не поддерживается; компрометация мнемоники закрывается переводом баланса на новый аккаунт до момента утраты.

[I-9] статус

Integer specification Algorithm M-1, PBKDF2-HMAC-SHA-256, HMAC-SHA-256, HKDF-Expand — ✓ (см. «Криптографическая реализация → Primitive layer»).

Unsigned operands — ✓ (entropy, salt, iter, dkLen, все промежуточные значения unsigned).

Test vectors — ✓ закрыто (6 binding vectors, подсекция «Test vectors» ниже).

FN-DSA-512.KeyGen и ML-KEM-768.KeyGen наследуют conformance от соответствующих NIST submissions и FIPS финализаций.

Test vectors (binding)

Все значения byte-exact, получены прогоном reference implementation mt-mnemonic (crates/mt-mnemonic в Протокол/Код/). Любая независимая реализация обязана воспроизводить идентичные hex-значения.

M-1 Vector 1 — minimum entropy:

entropy      = [0x00; 32]
checksum     = SHA-256([0x00; 32])[0] = 0x66
mnemonic     = "abandon abandon abandon abandon abandon abandon abandon abandon
                abandon abandon abandon abandon abandon abandon abandon abandon
                abandon abandon abandon abandon abandon abandon abandon art"
master_seed  = 38a1421ac3ce191fbdc46b1cca266a9d72d22320fb38bda6a3df90a1ead664a7
               8951703197be882ace38e0f557a492a8e9ff5e3c02290a8eecf5939468708edb

M-1 Vector 2 — maximum entropy:

entropy      = [0xFF; 32]
checksum     = SHA-256([0xFF; 32])[0] = 0xAF
mnemonic     = "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo
                zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote"
master_seed  = a5925c51583447a0abe43b65dbc591f3780a91c7d44c6b333975a211096039f3
               d1d0ca9e125aa4e756f0a35b0006378ac69450e8254e32f16409a350f3ca9104

M-1 Vector 3 — middle case from deterministic seed:

entropy      = SHA-256(ASCII "Montana test vector 3")
             = 279d5f5e441b81b5a551c50421a2559e971563608a6f2f646f7c6a1fe12ca88f
mnemonic     = "chest turtle stuff market retreat suspect next december
                aerobic artist nice diamond image random lion evil
                control casino tenant stage wrap north peasant upper"
master_seed  = da13e259eb58c79a650c312efe79d2ef42861ad114206ec48cb4b1eb5dcf0c22
               75b074ef8b02fbc2123032090ff004d7cc546d2bbf34c4e10ec3c6fb092f9a47

Per-role derivation vectors — используют master_seed из M-1 Vector 1:

Derivation Vector 1 — account keypair seed (FN-DSA-512, 48 байт):

falcon_seed_48 = HKDF-Expand(master_seed_v1, info="mt-account-key", L=48)
               = 08ce5c19768c679fda24c0d3360e57ce03d00c94c175e59f50e9c77894c20818
                 74b0015ca2cbcb66cf1a3f5d3dbd4171

Derivation Vector 2 — node keypair seed (FN-DSA-512, 48 байт):

falcon_seed_48 = HKDF-Expand(master_seed_v1, info="mt-node-key", L=48)
               = efe527d96de2cb82b3ee2e8ad24b4aca71014e37896b0c025a376335ad456acc
                 4f024a74da8045f1f2f39f9adb34b004

Derivation Vector 3 — ML-KEM-768 encryption keypair seed (64 байта):

mlkem_seed_64 = HKDF-Expand(master_seed_v1, info="mt-app-encryption-key", L=64)
              = 3eb9bcd201a1d5e671c9d23a929589a26ceb53338cd0684b5d77314a14601b03
                9f3e2ae7e5e0be8acd47b4b928c3e73b5d875b9fc7089b22bc1d59e9dc31077e

Все 6 векторов зафиксированы в crates/mt-mnemonic/tests/test_vectors.rs как byte-exact integration tests. Закрытие [I-9] conformance для Algorithm M-1 и per-role derivation.


Криптографическая реализация

Primitive layer

Собственная реализация криптографических примитивов запрещена. Только audited библиотеки с constant-time гарантиями и опубликованными test vectors.

Примитив Стандарт Роль
SHA-256 FIPS 180-4 TimeChain, lottery endpoints, адреса, Merkle-деревья
FN-DSA-512 NIST PQC selection финал (июль 2022), forthcoming FIPS 206, reference implementation production-ready Подписи операций аккаунтов и proposals узлов
HMAC-SHA-256 RFC 2104 Внутренний примитив PBKDF2 и HKDF (композиция поверх SHA-256)
PBKDF2-HMAC-SHA-256 RFC 8018 §5.2 KDF деривации master_seed из мнемоники (Algorithm M-1)
HKDF-Expand (поверх HMAC-SHA-256) RFC 5869 §2.3 Per-role key derivation ключей из master_seed
ML-KEM-768 FIPS 203 Шифрование сообщений на клиентском уровне (Application Layer)

HMAC-SHA-256 — integer спецификация

HMAC-SHA-256(key: bytes, message: bytes) → bytes[32]:
  B = 64                                              // SHA-256 block size в байтах
  if len(key) > B:
    key = SHA-256(key)                                // 32 байта
  if len(key) < B:
    key = key || [0x00] * (B - len(key))              // pad нулями до 64 байт
  ipad = [0x36] * 64
  opad = [0x5C] * 64
  key_ipad = key XOR ipad                             // byte-wise XOR
  key_opad = key XOR opad
  inner = SHA-256(key_ipad || message)
  outer = SHA-256(key_opad || inner)
  return outer

Ссылка: RFC 2104. SHA-256 следует FIPS 180-4.

PBKDF2-HMAC-SHA-256 — integer спецификация

PBKDF2(password: bytes, salt: bytes, iter: u32, dkLen: usize) → bytes[dkLen]:
  hLen = 32                                           // SHA-256 output length
  l = (dkLen + hLen - 1) / hLen                       // ceiling; для dkLen=64 → l=2
  DK = [] (empty byte sequence)
  for i in 1..=l:
    U_1 = HMAC-SHA-256(password, salt || u32_be(i))   // u32_be(i) = 4 байта big-endian
    T_i = U_1
    U_prev = U_1
    for k in 2..=iter:
      U_k = HMAC-SHA-256(password, U_prev)
      T_i = T_i XOR U_k                               // byte-wise XOR, длина 32 байта
      U_prev = U_k
    append T_i to DK                                  // DK растёт блоками по 32 байта
  return DK[0..dkLen]                                 // обрезать до dkLen байт

Ссылка: RFC 8018 §5.2.

HKDF-Expand — integer спецификация

HKDF-Expand(PRK: bytes[≥32], info: bytes, L: usize) → OKM: bytes[L]:
  hLen = 32                                           // HMAC-SHA-256 output length
  require L ≤ 255 × hLen                              // HKDF limit (L ≤ 8160)
  n = (L + hLen - 1) / hLen                           // ceiling
  T_0 = empty byte sequence
  OKM = [] (empty byte sequence)
  for i in 1..=n:
    T_i = HMAC-SHA-256(PRK, T_{i-1} || info || u8(i))
    append T_i to OKM
  return OKM[0..L]                                    // обрезать до L байт

Ссылка: RFC 5869 §2.3 (только Expand-step; Extract-step не используется — master_seed из PBKDF2 уже является high-entropy uniform ключевым материалом).

Consensus encoding layer

Консенсусно-критическая поверхность: каноническая сериализация, Merkle layout и domain separation. Разная сериализация одного объекта = разный хэш = форк. Эта секция нормативно определяет byte-for-byte marshalling algorithm для всех консенсусных объектов.

Primitive types.

Type Size Encoding
u8 1B raw byte
u16 2B little-endian
u32 4B little-endian
u64 8B little-endian
u128 16B little-endian
bytes[N] N байт raw bytes (нет length prefix — N известно из типа)

Все integer-поля используют little-endian byte ordering. Знак отсутствует (все counters unsigned).

Fixed-length byte arrays (account_id, node_id, hash, pubkey, signature): сериализация = raw bytes, длина детерминирована определением типа (32B для id/hash, 897B для FN-DSA-512 pubkey, 666B для FN-DSA-512 signature). Нет length prefix и нет разделителей.

Struct serialization. Поля структуры сериализуются в declared order из определения «Состояние сети». Каждое поле кодируется по своему типу. Байты конкатенируются без padding и без разделителей. Результат = total bytes = сумма size всех полей.

Пример Account Table record (полный layout):

serialize(account) :=
  account_id                      (32B)
  balance                         (16B, u128 little-endian)
  suite_id                         (2B, u16 little-endian)
  is_node_operator                 (1B, u8)
  frontier_hash                   (32B)
  op_height                        (4B, u32 little-endian)
  account_chain_length             (4B, u32 little-endian)
  account_chain_length_snapshot    (4B, u32 little-endian)
  current_pubkey                 (897B)
  creation_window                  (4B, u32 little-endian)
  last_op_window                   (4B, u32 little-endian)
  nickname_len                     (1B, u8, ∈ [0, 32])
  nickname_bytes                  (32B, ASCII [a-z0-9_-]; первые nickname_len байт значащие, остальные 0x00)
  service_credits_nj              (16B, u128 little-endian)
= 1053 bytes (deterministic, fixed size; включая nickname-слой и anti-spam cooldown для TransferActivation)

Variable-length arrays. Consensus-critical массивы кодируются как count_field + elements_concatenated. Count field присутствует в struct definition как отдельное поле (например, op_count 2B в BundledConfirmation). Если count явно не указан в struct — prefix = u16 little-endian.

Canonical ordering consensus-critical массивов.

Детерминизм требует фиксированного порядка элементов:

Array Canonical sort key Обоснование
op_hashes[] в BundledConfirmation ascending lexicographic по hash 32B comparison byte-for-byte
reveal_hashes[] в BundledConfirmation ascending lexicographic по hash 32B comparison byte-for-byte
cemented_bundles_W (для aggregate) ascending по node_id детерминированный порядок подписей
Candidates в selection event sort_key(c) ascending формула раздела Selection
NodeRegistrations в incremental apply W_p nr_sort_key(nr) ascending формула раздела Adaptive VDF

Lexicographic byte comparison: старший байт (index 0) важнее младшего. Массивы одинаковой длины.

Domain separator encoding.

Доменные разделители ("mt-account", "mt-lottery", etc.) сериализуются как raw ASCII bytes без null terminator, без length prefix. Длина разделителя фиксирована его литералом.

Пример: "mt-lottery" → 10 bytes: 0x6D 0x74 0x2D 0x6C 0x6F 0x74 0x74 0x65 0x72 0x79.

Hash composition: SHA-256("mt-lottery" || T_r || ...) означает SHA-256 applied to concatenation: 10 байт разделителя + 32 байта T_r + ... Разделитель всегда в начале hash input.

Sparse Merkle Tree algorithm.

Глубина дерева: 256 бит (индекс = 32-байтовый ключ, биты от наименьшего значимого (LSB) до старшего).

Операция Формула
leaf_hash(record) SHA-256("mt-merkle-leaf" || serialize(record))
internal_hash(left, right) SHA-256("mt-merkle-node" || left || right)
empty_leaf 0x00 × 32
empty_internal(level) precomputed: empty(0) = empty_leaf; empty(k+1) = internal_hash(empty(k), empty(k))

Precomputed массив empty_internal[0..256] — 257 × 32B = ~8 KB, вычисляется один раз и кэшируется.

Update path при изменении записи с ключом key:

1. new_value := leaf_hash(new_record)
2. current_bits := key
3. for L = 0 to 255:
     bit := (current_bits >> L) & 1
     sibling := текущий sibling на уровне L (из tree или empty_internal(L))
     if bit == 0:
       new_value := internal_hash(new_value, sibling)
     else:
       new_value := internal_hash(sibling, new_value)
4. new_root := new_value

Сложность: O(256) worst-case, O(log N) для sparse tree с caching непустых веток. Для N = 10⁹ записей эффективная глубина ~30 уровней.

Direction convention: bit = 0 означает позиция «слева», bit = 1 — «справа». Фиксировано для детерминизма.

Inclusion proof format:

MerkleProof:
  key                32B    <- индекс листа
  leaf_value            ?    <- serialize(record) или пустой массив (proof of absence)
  leaf_length           4B   <- u32 little-endian размер leaf_value (0 для absence)
  sibling_bitmap       32B   <- 256 бит: bit[i] = 1 если sibling на уровне i non-empty
  sibling_count         2B   <- u16 little-endian, число non-empty siblings
  siblings[]             ?   <- sibling_count × 32B, siblings в порядке возрастания уровня

Верификация: reconstruct root iteratively используя key биты + leaf_value + siblings (с учётом bitmap для empty levels). Сравнить с known root.

Endianness bitmap. Bit[0] = наименее значимый бит первого байта sibling_bitmap (little-endian bit order внутри байта). Level L → bitmap byte (L >> 3), bit offset (L & 7).

Обязательные требования.

  • Fixed binary encoding для каждого консенсусного объекта
  • Little-endian для всех integer типов
  • Domain separation для всех hash compositions
  • Canonical ordering массивов где порядок влияет на hash
  • Альтернативные сериализации запрещены
  • Test vectors для каждого консенсусного объекта (генерируются reference implementation)
  • Cross-implementation conformance tests перед запуском mainnet

Bijective canonical invariant. Для каждого consensus-критического объекта canonical_encode — bijective функция: одно logical value → ровно одно valid byte representation. Гарантируется конструктивно через:

  • Fixed integer endianness: все u16/u32/u64/u128 encoded LE
  • Fixed field order: порядок полей в encoding = порядок declaration в struct definition
  • Variable-length arrays: explicit count: uN LE prefix (N явно указан в struct layout) + элементы sorted по canonical key before encoding
  • Fixed-size arrays: без length prefix (размер implicit из типа)
  • Ноль optional полей (каждое поле всегда присутствует)
  • Ноль alternative representations (нет variable padding, normalized vs non-normalized forms)

Нарушение bijective = consensus-critical bug: две реализации producing разные canonical_bytes для одной logical value → разные signed_scope → signature одной не верифицируется для другой → consensus split. Invariant проверяется per class в conformance suite через round-trip test vectors: encode(decode(bytes)) == bytes и decode(encode(value)) == value для всех valid inputs.

Domain-separated hash primitive (self-delimiting):

Канонический hash primitive для всех consensus-critical composition:

hash(domain: bytes, parts: list[bytes]) := SHA-256(domain || 0x00 || parts[0] || parts[1] || ...)

NUL byte separator между domain и parts обеспечивает structural self-delimiting: ни один ASCII domain name не содержит байт 0x00, поэтому byte 0x00 unambiguously отделяет domain от parts. Реализация prefix-free относительно registry — для любых domain1, domain2 и любых attacker-controlled parts1, parts2:

hash(domain1, parts1) == hash(domain2, parts2)
  ⟹ (domain1 == domain2) ∧ (concat(parts1) == concat(parts2))

Это гарантирует невозможность cross-domain preimage collision даже если registry содержит prefix-related domains (mt-accountmt-account-key, mt-nodemt-nodereg, mt-appmt-app-encryption-key, etc.) — NUL separator делает preimage bytes различными независимо от name prefixes.

Spec shorthand convention. В тексте формулы пишутся в сокращённой форме SHA-256("mt-op" || scope) для читаемости — это always означает canonical hash("mt-op", [scope]) = SHA-256("mt-op" || 0x00 || scope). Внедрение NUL separator — implementation detail canonical hash primitive, не optional parameter.

Контекст: ранее hash primitive определялся как raw concatenation SHA-256(domain || parts...) без separator. Внешний critic audit выявил 8 prefix-collision pairs в registry (mt-nodereg ⊂ mt-nodereg-sort, mt-account ⊂ mt-account-key/lottery, mt-node ⊂ mt-nodereg/-key, mt-bc-aggregate ⊂ mt-bc-aggregate-empty, mt-app ⊂ mt-app-encryption-key) enabling cross-domain preimage collision при attacker-controlled parts. NUL separator — structural fix через unambiguous framing, не patch ad-hoc renaming (которое оставляет class of vulnerability открытым для future registry additions).

Binding test vectors (domain-separated hash):

DS1 — empty parts, short domain

hash("mt-op", []) preimage = "mt-op" || 0x00 = 6d742d6f7000 output = e96b8d4adaee5cce25dca37bbec2b3d1f9d8dd5e74aee90ad39eb8c8dc7bf41e

DS2 — prefix-collision test: mt-node vs mt-node-key

hash("mt-node", []) preimage = "mt-node" || 0x00 = 6d742d6e6f646500 output = 04dfa5a7f0aae0b29a7e1e3df85a41cd1f1e9f5e3c8bf70e6e32fe61a43a1c42 hash("mt-node-key", []) preimage = "mt-node-key" || 0x00 = 6d742d6e6f64652d6b657900 output = <distinct от DS2 выше> Verification: DS2_node ≠ DS2_nodekey (NUL separator гарантирует)

DS3 — collision-critical parts: hash("mt-app", ["-encryption-key"]) vs hash("mt-app-encryption-key", [])

Ранее (без separator): BOTH preimage = "mt-app-encryption-key" → collision

Текущая реализация (с separator):

hash("mt-app", ["-encryption-key"]) preimage = "mt-app" || 0x00 || "-encryption-key" = 6d742d61707000 || 2d656e6372797074696f6e2d6b6579 hash("mt-app-encryption-key", []) preimage = "mt-app-encryption-key" || 0x00 = 6d742d6170702d656e6372797074696f6e2d6b657900 Verification: DS3_split ≠ DS3_direct (NUL position differs)

(Точные output bytes DS1-DS3 — см. conformance test vectors в reference implementation mt-crypto crate; значения генерируются через cargo test -p mt-crypto domain_separation_binding.)


Domain separators registry:

Домен Контекст
mt-op Class domain для identifier(op) операций аккаунтов (UserObjects 0x01..0x04) — Правило R2
mt-nodereg Class domain для identifier(nr) NodeRegistration (0x11) — Правило R2
mt-proposal Class domain для identifier(header) Proposal header (заменил mt-header) — Правило R2
mt-bundle Class domain для identifier(bundle) BundledConfirmation — Правило R2
mt-vdf-reveal Class domain для identifier(reveal) VDF_Reveal — Правило R2
mt-account Деривация account_id = SHA-256("mt-account" || suite_id || pubkey)
mt-node Деривация node_id = SHA-256("mt-node" || node_pubkey)
mt-candidate-vdf-init VDF init seed для кандидата на вход в сеть
mt-merkle-leaf Листья Merkle-деревьев
mt-merkle-node Внутренние узлы Merkle-деревьев
mt-state-root Композиция state_root из node_root, candidate_root и account_root
mt-timechain TimeChain VDF seed
mt-lottery Lottery endpoint seed (SHA-256(T_r || cemented_bundle_aggregate(W-2) || node_id || window_index))
mt-bc-aggregate Aggregate_for_seed domain для cemented_bundle_aggregate (non-empty) — Правило R3, aggregate over node_ids
mt-bc-aggregate-empty Fallback для вырожденного случая cemented_bundle_aggregate (|cemented_bundles_W| == 0): SHA-256("mt-bc-aggregate-empty" || W.to_le_bytes_8)
mt-selection Sort key для selection event (SHA-256("mt-selection" || timechain_value || cemented_bundle_aggregate(W-2) || node_id))
mt-nodereg-sort Sort key для incremental apply NodeRegistrations в окне W_p (SHA-256("mt-nodereg-sort" || timechain_value(W_p) || cemented_bundle_aggregate(W_p-2) || node_pubkey))
mt-confirmation Хэширование async confirmations
mt-app Деривация app_id для Application Layer
mt-genesis Деривация frontier_hash genesis-аккаунтов
mt-nodechain-genesis (deprecated, ранее NodeChain VDF genesis init)
mt-seed Salt (7 байт) для PBKDF2-HMAC-SHA-256 в Algorithm M-1 «Ключи → Мнемоника и seed»
mt-account-key info для HKDF-Expand при per-role derivation account keypair из master_seed
mt-node-key info для HKDF-Expand при per-role derivation node keypair из master_seed
mt-content-chunk Хэширование чанков контента (клиентский слой)
mt-content-manifest Хэширование манифеста чанкованного контента (клиентский слой)
mt-profile Хэширование ProfileBlob в Application Layer
mt-encryption-key Хэширование EncryptionKeyBlob в Application Layer
mt-app-encryption-key info для HKDF-Expand при per-role derivation ML-KEM-768 encryption keypair из master_seed (Application Layer)
mt-prekeys Хэширование PreKeyBundle в Application Layer
mt-tunnel IBT proof подпись при входе на узел (internet transport)
mt-tunnel-mesh IBT proof подпись при входе на peer через mesh transport (отличный domain separator предотвращает cross-context replay online proof в mesh)
mt-bootstrap-pow Proof-of-work при подключении к bootstrap
mt-mesh-frame-mac HMAC-SHA-256 key derivation для MAC поля MeshFrame (integrity против mesh-level tampering)
mt-mesh-ack Подпись rate-limit acknowledgement от relay к sender (см. Store-and-Forward Semantics)
mt-mesh-session Derivation mesh_session_id из peer pubkey + session_nonce

Protocol layer

Собственная реализация поверх криптографического ядра:

Компонент Назначение
Merkle-деревья State Root (из SHA-256 вызовов)
VDF scheduling Управление TimeChain VDF
State machine Account Table, Node Table, state transitions
P2P gossip Распространение операций, confirmations и proposals

Инфраструктура

Библиотека Назначение
RocksDB Хранение Account Table и операций
libp2p P2P транспорт

Production: Rust.


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

Все временные параметры сетевого уровня (frame rate, padding window, feeler interval, Dandelion timers) — implementation guidance для локального сетевого стека узла. Они оперируют на локальных часах узла и находятся вне scope consensus state.

Transport Obfuscation

Монтана — персональная сеть. Каждый узел — персональный сервер участника. Транспортный слой построен из этого определения: персональный сервер отвечает только участникам, персональный мессенджер скрывает тайминг сообщений, персональный = доступный обычному человеку.

Шифрование

Все P2P-соединения инкапсулированы в TLS 1.3 на порт 443. Noise framework (встроен в libp2p) для аутентификации по публичному ключу узла внутри TLS. Содержимое трафика недоступно наблюдателю.

Identity-Bound Tunnel (IBT)

Персональный сервер отвечает только участникам сети. После TLS handshake клиент отправляет proof аутентификации. Узлы (зарегистрированные и приглашённые) подписывают node keypair. Аккаунты (клиенты) подписывают account keypair.

proof = FN-DSA-512_sign(client_privkey,
          "mt-tunnel" || server_node_id || floor(current_window_index / 2))

Сервер проверяет:

  1. Подпись валидна для заявленного client_pubkey
  2. Window slot = текущий ИЛИ предыдущий (окно = 2 window_index)
  3. Уровень доступа — сервер проверяет client_pubkey по трём таблицам последовательно, первое совпадение определяет уровень:
    • node_id = SHA-256("mt-node" || client_pubkey) в Node Table → полный gossip (клиент подключился node keypair)
    • node_id с node_pubkey = client_pubkey в Candidate Pool → read-only gossip: получает proposals (кандидат подключился node keypair)
    • account_id = SHA-256("mt-account" || suite_id || client_pubkey) в Account Table → подключение к доверенному узлу (клиент подключился account keypair)
    • Ни одно не найдено → отказ

Условия 1-2 выполнены + уровень 3 определён → Noise handshake → Montana P2P с соответствующим уровнем доступа. Любое не выполнено → TLS alert bad_certificate, close. Стандартное поведение сервера с обязательной аутентификацией клиента — таких серверов в интернете миллионы (корпоративные порталы, API, банковские системы).

Replay protection: server_node_id привязывает proof к конкретному получателю. Window slot ограничивает replay window до 2 окон.

Bootstrap exception: genesis bootstrap nodes хардкодированы как (IP, node_id, pubkey) × 12. Bootstrap принимает proof от любого валидного FN-DSA-512 ключа (Account Table не проверяется). Для защиты от connection flood клиент прилагает proof-of-work:

SHA-256("mt-bootstrap-pow" || proof || nonce) < target

target подбирается чтобы стоимость ≈ 100ms CPU. PoW требуется только при подключении к bootstrap, не к обычным peers.

Mesh transport IBT extension.

Mesh transport (см. подраздел «Mesh Transport» ниже) работает при отсутствии fresh window_index — устройство может быть offline часы или дни до следующей синхронизации с internet-сетью. IBT proof в mesh контексте использует cached window_index — значение последнего известного окна с любого предыдущего online-соединения.

Формула для mesh transport:

mesh_proof = FN-DSA-512_sign(
    client_privkey,
    "mt-tunnel-mesh"
    || peer_node_id
    || floor(cached_window_index / 2)
    || mesh_session_nonce)

где:
  cached_window_index    u32    — последнее известное окно
                                  от любого предыдущего online
                                  handshake или gossiped proposal
  mesh_session_nonce     32B    — генерируется инициатором
                                  handshake из CSPRNG, передаётся
                                  в plain части mesh advertisement

Acceptable staleness bound. Peer принимает cached_window_index в диапазоне [peer.known_window_index - 7 × τ₁, peer.known_window_index]. Свыше 7 × τ₁ cached значение признаётся слишком старым — peer отклоняет mesh IBT handshake и требует свежее значение через любой доступный канал до продолжения.

Session nonce tracking. Peer хранит used_nonces[sender_pubkey] — set of mesh_session_nonce значений использованных данным sender в пределах acceptable staleness window. При приёме proof с mesh_session_nonce ∈ used_nonces[sender_pubkey] → reject (replay). Set pruning: записи старше 7 × τ₁ удаляются (nonce reuse после expiry acceptable, cached_window_index уже невалиден).

Domain separator обязательно mt-tunnel-mesh, не mt-tunnel. Отдельный separator критичен — иначе атакующий перехвативший online IBT proof (window slot = 2 × τ₁ replay) мог бы использовать его в mesh контексте где staleness window расширен до 7 × τ₁. Cross-context replay блокируется на уровне domain separation.

Replay surface analysis.

  • Online IBT (separator mt-tunnel): replay window 2 × τ₁ — узкий, acceptable.
  • Mesh IBT (separator mt-tunnel-mesh): replay window расширен до 7 × τ₁, но replay блокируется per-nonce tracking.
  • Cross-context: domain separation делает proof для одного context невалидным в другом.

Verification procedure для mesh peer.

  1. Parse advertisement, извлечь sender_pubkey, mesh_session_nonce, proof.
  2. Проверить signature с sender_pubkey.
  3. Извлечь cached_window_index из proof message reconstruction (peer знает sender_pubkey, peer_node_id известен локально, пробует range [known_window_index - 7 × τ₁, known_window_index] пока не найдёт совпадающее значение; при отсутствии совпадения — reject).
  4. Проверить mesh_session_nonce ∉ used_nonces[sender_pubkey].
  5. Всё ok → accept, добавить mesh_session_nonce в used_nonces, начать mesh сессию.
  6. Любая проверка не прошла → silent reject (no error message, чтобы не давать feedback злоумышленнику).

Uniform Framing

Все Montana-сообщения внутри IBT-соединения фрагментируются на фреймы фиксированного размера:

frame_size = 1024 bytes

frame format:
  flags     1B    (0x01 = data, 0x02 = padding, 0x04 = continuation)
  length    2B    (полезная нагрузка, ≤1021B)
  payload   1021B (данные или random padding до frame_size)

Персональный мессенджер скрывает тайминг: между узлами идёт постоянный поток фреймов. Реальные Montana-сообщения замещают padding-фреймы, не добавляются к ним. Наблюдатель внутри сети не может отличить перевод от доказательства времени от тишины — всё одинаковые зашифрованные фреймы.

Параметры:

  • Baseline frame rate: 1 frame/сек на исходящих соединениях. Входящие — фреймы при наличии данных
  • Maximum burst: ≤ 8 frames подряд без паузы ≥ 10ms
  • Minimum padding ratio: ≥ 20% фреймов в скользящем 60-секундном окне на исходящих

Персональный = доступный: 13 исходящих × 1 frame/сек × 1024 bytes = 13 KB/сек ≈ 33 GB/мес. Приемлемо для домашнего сервера.

Transport Randomness

Все рандомизированные решения транспортного уровня (stem routing, frame scheduling, nonce generation) используют CSPRNG из OS entropy pool. Детерминированный PRNG от node state запрещён для transport-layer randomness.

Transport obfuscation ортогонален консенсусу. TimeChain, state machine работают поверх любого транспорта без изменений.

Peer Selection

Открытый вход с VDF-барьером делает sybil-узлы дорогими: каждый sybil = τ₂ окон VDF (sequential SHA-256, не ускоряется параллелизмом) + selection event. Peer selection использует diversity constraints из протокольных данных (start_window) и сетевых (/16, ASN).

P2P gossip — только зарегистрированные и приглашённые узлы (уровни 1-2 IBT, см. Transport Obfuscation → Identity-Bound Tunnel). Аккаунты (уровень 3 IBT) взаимодействуют через свой доверенный узел.

Исходящие соединения

13 исходящих, все полные. Uniform framing скрывает типы сообщений — отдельные relay-only соединения не нужны.

Выбор: случайный 50/50 из таблиц «новые» и «проверенные». Бакетирование с секретным ключом узла. Без preference по chain_length — выбор равномерный.

Четыре уровня diversity

Каждый исходящий проверяется по всем четырём constraints:

Сетевые:
  /16  — не более 1 исходящего на /16 подсеть (IPv4) или /48 (IPv6)
  ASN  — не более 2 исходящих на автономную систему

Протокольные:
  start_window — не более 2 исходящих к узлам с start_window в одном τ₂

Сетевые constraints: /16 и ASN diversity. Протокольный constraint start_window канонически доступен из Node Table.

Следствие: кластер sybil зарегистрированных в один τ₂ → максимум 2 из 13 слотов. Eclipse требует узлы в 7+ разных AS в 7+ разных /16 с регистрацией в 7+ разных τ₂.

ASN-карта загружается при запуске. Без карты — fallback на /16.

Адресный менеджер

Две таблицы:

  • Новые — адреса полученные через peer exchange и DHT. Узел ещё не подключался
  • Проверенные — адреса к которым узел успешно подключался через IBT

Бакетирование: bucket = Hash(secret_key, source_group, addr_group) % N. Детерминированно с секретным ключом — атакующий не может предсказать в какой бакет попадёт его адрес.

Входящие соединения

До 32 входящих. При переполнении — вытеснение:

  1. Защитить 4 с наименьшим пингом
  2. Защитить 4 с последними полезными сообщениями (любое валидное Montana-сообщение которое узел ещё не видел)
  3. Защитить до 8 из разных подсетей (по одному от каждой)
  4. Защитить 4 с последними proposals
  5. Из оставшихся — вытеснить из крупнейшей подсетевой группы

Якоря

2 исходящих с наибольшим uptime соединения сохраняются каждые τ₂. При перезапуске после аварии или обновления — подключиться к якорям первым до случайного выбора из таблиц.

Feeler

Каждые 10 минут: подключиться к случайному адресу из «новых», выполнить IBT handshake (все три уровня проверки). Успех на любом уровне → перенести в «проверенные» с пометкой уровня (node / invited / account). Неуспех → пометить или удалить.

Ротация

По поведению: если peer не передал ни одного нового proposal за τ₂ — заменить. Peer с долей невалидных сообщений выше 50% в скользящем τ₁-окне — отключить с запретом переподключения на τ₂. Peer который relay-ит честно — полезен сети, остаётся.

PeerRecord

Формат записи о пире при peer exchange:

PeerRecord:
  ip            16B   (IPv4-mapped IPv6)
  port           2B   (u16)
  node_id       32B
  node_pubkey  897B   (FN-DSA-512)

Без node_id и node_pubkey клиент не может вычислить IBT proof для подключения. Peer exchange: не более 100 PeerRecord за сообщение. Не более 1 peer exchange сообщения в минуту от каждого peer.

Censorship-Resistant Discovery

Генезис: 12 hardcoded bootstrap nodes (IP, node_id, pubkey). Если все 12 IP заблокированы на уровне страны — новый узел не может войти в сеть. Пять независимых каналов обнаружения. Достаточно одного из пяти.

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

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

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

4. Encrypted Client Hello (ECH). Bootstrap через CDN с поддержкой ECH. SNI зашифрован — наблюдатель видит IP CDN, но не целевой домен. Эффективен в юрисдикциях без активной блокировки ECH extension. В юрисдикциях блокирующих ECH (Китай с 2023, Россия с 2024) — канал неработоспособен. Для таких юрисдикций — каналы 1-3, 5.

5. Mesh peer exchange. При полном отсутствии доступа к internet (государственный shutdown, отключение межзоновой связности, локальная изоляция) узел обнаруживает локальных peers через mesh transport (Bluetooth LE advertisement, Wi-Fi Direct service discovery). Peer exchange работает на уровне mesh frame с типом frame_type = 0 (discovery) — см. подраздел «Mesh Transport» и «Store-and-Forward Semantics». Физический радиус обнаружения — десятки метров; mesh multi-hop forwarding расширяет эффективный радиус до сотен метров и километров при достаточной плотности устройств. Когда хотя бы одно устройство в mesh-сети получает доступ к internet — вся цепочка синхронизируется через него как через единый шлюз.

Избыточность = устойчивость. Пять каналов независимы по physical-layer доставке (IP internet для 1-4, radio mesh для 5). Блокировка internet-канала на уровне государства не затрагивает канал 5 — отключить mesh требует подавления Bluetooth/Wi-Fi на каждом устройстве физически, что практически нереализуемо.

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

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

Две фазы:

Stem (стебель):
  Операция проходит по цепочке случайных узлов (в среднем 2-3 hop).
  Каждый узел видит только предыдущий hop, не автора.
  На каждом hop с вероятностью p = 0.4 переход в fluff.
  E[stem_length] = 1/p = 2.5 hops.
  P(stem ≤ 1) = 40%, P(stem ≤ 3) = 78%.

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

Stem routing. Стебель использует только исходящие соединения — входящие не участвуют. Каждые 693 окна узел выбирает 2 из 13 исходящих как стебельных (stem epoch). Все стебельные операции в эпохе направляются через одного из этих 2 (выбор по hash(msg)).

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

Объект Режим Причина
UserObject (Transfer, TransferActivation, Anchor, NicknameBid, PurchaseCredits, ConsumeCredits, ChangeKey, CloseAccount) Stem → fluff Скрыть IP отправителя
ControlObject (NodeRegistration) Stem → fluff Скрыть IP регистрирующегося кандидата
VDF Reveal Прямой gossip (без stem) node_id публичен в reveal, анонимность невозможна; IP скрыт Transport Obfuscation (TLS 1.3 на порт 443)
Confirmation Stem → fluff Скрыть какой узел подтвердил первым

Свойства:

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

Реализация:

stem_peers = random_sample(outbound, 2)    // каждые 693 окна

on_receive_stem(msg, from_peer):
  if random() < 0.4:
    gossip_broadcast(msg)                  // fluff
  else:
    next = stem_peers[hash(msg) % 2]      // детерминированный выбор из 2
    send_stem(msg, next)                   // продолжить stem
  start_timer(msg, 30s)                   // страховка на каждом hop

on_timer_expired(msg):
  if msg не обнаружен в gossip:
    gossip_broadcast(msg)                  // принудительный fluff

Каждый stem-узел страхует следующий. Таймер 30 секунд на каждом hop независимо. Если следующий hop уронил сообщение — текущий hop обнаруживает отсутствие операции в gossip и делает fluff сам. Максимальная задержка = 30 секунд (один hop), не кумулятивная.

Dandelion++ не требует внешней инфраструктуры. Каждый Montana-узел уже является relay — gossip существует, stem добавляет 2-3 hop перед ним. Latency overhead: миллисекунды.

NAT Traversal

Персональная сеть работает когда каждый может войти. Большинство домашних пользователей за NAT — невидимы для входящих соединений. Без NAT traversal персональный интернет = серверный клуб.

Три механизма, каждый следующий — если предыдущий не сработал:

1. AutoNAT (определение). Узел спрашивает outbound peers: «видишь ли мой IP:port напрямую?» Если да — NAT нет. Если нет — узел знает свой NAT-статус.

2. DCUtR (пробивка). Два NAT-узла координируются через третий узел с публичным IP. Оба отправляют исходящие пакеты — роутеры открывают «дырки» для ответов. После координации — прямое соединение. Успех: 60-70% случаев (TCP). Carrier-grade NAT (мобильные операторы): ~30%.

3. Circuit Relay v2 (транзит). Если пробивка не удалась — трафик идёт через outbound peer с публичным IP. Relay — не отдельный механизм и не выделенный сервер. Relay-соединение = обычное исходящее соединение, подчиняющееся тем же правилам: uniform framing, diversity constraints, ротация по поведению. Содержимое зашифровано конец-в-конец (Noise) — relay видит IP участников но не содержимое. Metadata распределён по 13 outbound peers из разных /16 и ASN — ни один relay не видит полный граф.

Relay — не fallback а гарантия подключения при любом типе NAT. Пробивка — оптимизация для снижения нагрузки на relay.

Лимиты relay: до 32 одновременных relay-соединений на узел, bandwidth per relay ≤ baseline frame rate (1 KB/сек). 32 × 1 KB/сек = 32 KB/сек ≈ 82 GB/мес — приемлемо для домашнего узла с публичным IP.

Обязанность. Узлы с публичным IP поддерживают relay — персональная сеть работает когда каждый может войти. Reference implementation включает relay при обнаружении публичного IP. Feeler-подключения проверяют поддержку relay у peers; узлы без relay помечаются no-relay в адресном менеджере. NAT-узлы предпочитают peers поддерживающие relay при выборе исходящих.

Все три механизма — стандарт libp2p (AutoNAT, DCUtR, Circuit Relay v2). Ноль новых протокольных примитивов.

Mesh Transport

Internet не всегда доступен. Государственные shutdown (Иран 2019 — неделя, Беларусь 2020 — дни, Мьянма 2021 — месяцы), локальные сбои, изолированные зоны. Montana поддерживает работу в таких условиях через mesh transport поверх Bluetooth Low Energy и Wi-Fi Direct — устройства обнаруживают друг друга в физическом радиусе и пересылают encrypted Montana-сообщения hop-by-hop. Mesh не замещает internet transport, а дополняет: при возвращении связности сеть автоматически конвергирует через mesh-internet шлюз.

Mesh transport ортогонален консенсусу так же как internet transport (раздел выше) — state machine работает поверх любого доставочного канала без изменений.

MeshFrame wire format

Все mesh-сообщения фрагментируются на фреймы фиксированного формата:

MeshFrame:
  mesh_protocol_version   u16    — версия mesh wire format
                                   (0x0001 для v1)
  frame_type              u8     — 0=discovery, 1=data,
                                   2=ack, 3=forward
  ttl                     u8     — max 16 при создании,
                                   monotonic decrement
                                   на каждом forwarding hop;
                                   ttl=0 → frame дропается
  hop_count               u8     — 0 при создании,
                                   monotonic increment
                                   на каждом forwarding hop
  sender_ref              32B    — mesh_session_id инициатора
                                   (не прямой node_id для
                                    privacy на mesh слое)
  recipient_hint          32B    — encrypted routing hint
                                   либо broadcast marker
                                   (0xFF × 32 = broadcast)
  payload_length          u16    — длина payload в байтах,
                                   ≤ 256
  payload                 variable — encrypted blob,
                                     ≤ 256B для fit в один
                                     BLE GATT notification
                                     без fragmentation
  mac                     16B    — HMAC-SHA-256 truncated,
                                   key derived через
                                   HKDF от shared session
                                   secret с domain separator
                                   "mt-mesh-frame-mac"

Итого: 64 + 256 + 16 = 336B максимум
       (64 байта header + 256 байт payload + 16 байт MAC)

Инварианты MeshFrame:

  • mesh_protocol_version ∈ {0x0001} для v1; иные значения reject
  • frame_type ∈ {0, 1, 2, 3}; иное → drop
  • ttl ∈ [0, 16]; при создании фрейма sender устанавливает ≤ 16; при каждом forward ttl := ttl - 1; если ttl = 0 и frame требует forwarding — drop
  • hop_count ∈ [0, 16]; при создании = 0; при каждом forward hop_count := hop_count + 1; если hop_count > 16 → drop (защита от malformed increment)
  • sender_ref = 32 байта = mesh_session_id отправителя (см. derivation ниже), не прямой node_id
  • recipient_hint = 32 байта; значение 0xFF × 32 обозначает broadcast, иное — encrypted routing hint; получатель проверяет соответствие self через локальное state
  • payload_length ≤ 256; строгое неравенство иначе → drop
  • payload длина точно payload_length байт; encrypted blob (шифрование выполнено на уровне session, mesh transport layer видит только ciphertext)
  • mac = 16 байт, HMAC-SHA-256(session_mac_key, header_bytes || payload) truncated до первых 16 байт; session_mac_key = HKDF-SHA-256(session_shared_secret, salt=empty, info="mt-mesh-frame-mac", length=32); mismatch MAC → drop + increment soft-blacklist counter для sender_ref
  • Signature verify rule: MeshFrame не подписывается FN-DSA-512 напрямую (MAC достаточен для integrity между двумя peer в установленной session); identity-level authentication выполняется один раз при mesh IBT handshake, subsequent frames authenticated через session MAC
  • Cross-field consistency: hop_count + ttl ≤ 16 в любом состоянии (initial: hop_count=0, ttl≤16; при каждом forward ttl := ttl - 1, hop_count := hop_count + 1, сумма инвариантна); нарушение hop_count + ttl > 16 — malformed frame, drop + increment soft-blacklist counter для peer из которого пришла frame

mesh_session_id derivation. Для каждой mesh сессии (между парой peers после mesh IBT handshake) выводится:

mesh_session_id = HKDF-SHA-256(
    ikm    = shared_secret_from_noise_handshake,
    salt   = mesh_session_nonce_initiator || mesh_session_nonce_responder,
    info   = "mt-mesh-session",
    length = 32
)

mesh_session_id используется в поле sender_ref вместо прямого node_id — mesh transport на уровне wire format не раскрывает identity отправителя случайному слушателю в радиусе. Identity раскрывается только peer с которым установлена сессия (они знают mesh_session_id).

Валидация MeshFrame.

  1. mesh_protocol_version совпадает с ожидаемой версией peer. Mismatch → drop, no forward.
  2. frame_type ∈ {0, 1, 2, 3}. Иное → drop.
  3. ttl ∈ [0, 16]. Если ttl=0 и frame пришёл для forwarding — drop.
  4. hop_count ≤ 16. Иное → drop (защита от malformed increment).
  5. payload_length ≤ 256. Иное → drop.
  6. mac verify через HMAC-SHA-256 с session key. Mismatch → drop, increment soft-blacklist counter sender_ref.
  7. Для frame_type = 3 (forward) — применить правила Store-and-Forward Semantics (ниже).

Mesh framing profile

Internet transport uniform framing (подраздел «Uniform Framing») не применяется к mesh transport. Mesh имеет независимый framing profile:

Internet (существующий):
  frame_size            = 1024 bytes
  baseline_rate         = 1 frame/сек
  контекст              = TLS 1.3 over IP

Mesh (v1):
  frame_size            = 256 bytes (fit в BLE MTU типично
                          без application-level fragmentation
                          в большинстве стеков iOS/Android)
  baseline_rate         = 1 frame/10 сек (baseline advertisement
                          + occasional data, battery-sustainable)
  burst_mode_rate       = 1 frame/сек (активируется ТОЛЬКО при
                          активной mesh chat session, не continuous)
  burst_mode_duration   = ≤ 120 сек после последнего data frame,
                          затем возврат к baseline_rate
  fragmentation         = sequence numbers для сообщений > 256B,
                          reassembly на получателе через seq_id
                          в payload header уровня application

Обоснование параметров:

  • 256B — BLE MTU реально варьируется 23-512B, большинство современных iOS/Android поддерживают ≥ 247B, 256B выбран как compromise fit-without-fragmentation на mainstream устройствах
  • 1 frame/10 сек baseline — continuous Bluetooth scanning при более частом ритме съедает 30-50% батареи смартфона за несколько часов; 1/10s профиль extends battery usability до рабочего дня
  • burst до 1/сек — активная переписка требует reasonable responsiveness; активация по событию «активный chat session» ограничивает всплеск энергопотребления ко времени реального использования

Fragmentation

Сообщения превышающие 256B fragmented на уровне application перед enqueue в mesh:

ApplicationPayload (до fragmentation):
  fragment_count     u16   — общее число фрагментов
  fragment_index     u16   — index текущего фрагмента (0-based)
  message_id         32B   — unique id сообщения,
                             shared across всех фрагментов
  data               variable — часть encrypted payload

Получатель собирает фрагменты по message_id, порядок восстанавливается через fragment_index. Timeout reassembly: 60 сек от первого полученного фрагмента — если не все собраны, partial drop. Fragment_index ≤ 255 (max 256 фрагментов × 256B payload = 64KB верхняя граница одного application message; большие объёмы — через Content Layer chunking на blob уровне).

Mesh discovery flow

  1. Устройство в mesh-активном режиме периодически (baseline_rate = 1 frame/10 сек) бродкастит frame_type = 0 (discovery) с sender_ref = mesh_session_id_self_generated и payload = short advertisement: protocol version, capability flags, optional trust hint.
  2. Другие устройства в радиусе принимают broadcast, извлекают advertisement.
  3. Если принимающее устройство считает инициатора потенциально интересным (известный контакт в адресной книге; broadcast addressed to broadcast marker и устройство в broadcast-listening mode; любое другое правило application) — оно инициирует mesh IBT handshake (см. «Identity-Bound Tunnel» выше, формула mesh_proof).
  4. После успешного handshake — session установлена, оба peer добавляют mesh_session_id в active sessions.
  5. Обмен данных происходит через frame_type = 1 (data).

Battery management

Reference implementation рекомендуется:

  • Scheduled Bluetooth scan: 1 раз в 10 секунд при baseline, чаще при burst
  • Wi-Fi Direct используется только для high-throughput сессий (передача больших файлов), не continuous
  • iOS background mode constraints: полный mesh transport работает только в foreground; в background доступно ограниченное Core Bluetooth BGTaskScheduler сканирование
  • Android: BLE advertisement и scanning в background — стандарт платформы, требует declared foreground service notification для compliance

Store-and-Forward Semantics

Mesh transport inherently async: получатель сообщения может быть вне радиуса в момент отправки. Store-and-forward semantics описывают как промежуточные устройства буферизуют и пересылают сообщения к их конечному получателю.

Buffer model

Каждое устройство в mesh-активном режиме поддерживает локальный buffer:

MeshBuffer (локальный state устройства):
  entries: map<frame_hash, BufferEntry>

BufferEntry:
  frame              MeshFrame
  received_at        timestamp (local, monotonic)
  ttl_remaining      u8        (decremented каждый forwarding hop)
  sender_ref         32B       (из frame, для per-sender quota)
  forwarded_to       set<peer_id>  (peers которым уже переслано,
                                    защита от петель)

frame_hash = SHA-256(MeshFrame serialized) — ключ для идемпотентного recept.

Buffer policies

Capacity limits (по умолчанию, настраиваемо в реализации):

  • Max buffer size per device: 1024 frames (≈ 336 KB)
  • Max retention per frame: 24 часа (TTL expiry на buffer entry, независимо от ttl в frame который decremented per hop)
  • Max frames per sender_ref in buffer: 10 concurrent

Priority queue (при enqueue):

  1. Own sent frames (frames originated by this device) — highest priority
  2. Frames addressed to known contacts (locally stored) — high priority
  3. Frames addressed to unknown recipients (broadcast или unknown recipient_hint) — low priority

Drop policy при overflow:

  • Первое при переполнении — drop low-priority oldest
  • При исчерпании low-priority — drop high-priority oldest (не own)
  • Own frames не дропаются до expiry

Per-sender quota

Защита от flood DOS (вектор M1 из adversarial review):

Rate limits per sender_ref:
  max_frames_per_minute  = 10
  max_frames_in_buffer   = 10 concurrently

При превышении:
  - Новые frames от этого sender_ref дропаются
  - Sender_ref получает signed rate-limit ack с отказом
  - Soft-blacklist local: exponential backoff, первое
    нарушение — 60 сек ignore, второе — 120 сек, и т.д.
    до 3600 сек максимум

Signed rate-limit acks

Relay подписывает acknowledgement для каждой принятой (и forwarded или сохранённой) frame:

MeshAck:
  acked_frame_hash    32B   — SHA-256 frame которая acked
  relay_node_id       32B
  status              u8    (0=accepted, 1=buffered,
                             2=forwarded, 3=rejected_quota,
                             4=rejected_expired)
  timestamp_relay     u64   — local monotonic ms
  signature           666B  — FN-DSA-512_sign(
                                relay_privkey,
                                "mt-mesh-ack"
                                || acked_frame_hash
                                || relay_node_id
                                || status
                                || timestamp_relay)

Инварианты MeshAck:

  • acked_frame_hash = SHA-256 over canonical serialization MeshFrame к которому относится ack; receiver ack'а проверяет что хэш соответствует реально отправленной frame
  • relay_node_id = SHA-256("mt-node" || relay_pubkey); receiver должен знать relay_pubkey для проверки подписи
  • status ∈ {0, 1, 2, 3, 4}; значение вне диапазона → reject ack как malformed
  • timestamp_relay — u64 monotonic ms, используется только для ordering на стороне получателя; не consensus-critical (не детерминирован по определению — local monotonic), поэтому не участвует в state transitions
  • signature = 666 байт FN-DSA-512, валидация через relay_pubkey; подписываемое сообщение канонически сериализовано в порядке перечисления полей (acked_frame_hash || relay_node_id || status || timestamp_relay)
  • Signature verify rule: FN-DSA-512.verify(relay_pubkey, domain_separator || canonical_payload, signature) = valid; иначе drop ack
  • Ack не применяется к state transitions — это чисто local signal для sender rate adjustment, вне scope consensus

Sender использует ack для:

  • Confirmation что frame принята (status ∈ {0, 1, 2})
  • Detection перегрузки (status=3 → flood suppression, уменьшить rate)
  • Detection expired frames (status=4 → frame outdated, не повторять)

Отсутствие ack в 30 секунд после отправки → sender предполагает relay недоступен, пробует другой peer.

Forwarding algorithm

on_receive(frame, from_peer):
  frame_hash = SHA-256(frame)
  if frame_hash in buffer:
    return  # дубликат, already processed

  if not validate_frame(frame):
    drop; increment soft-blacklist counter from_peer
    return

  if frame.sender_ref in soft_blacklist:
    drop silently
    return

  if buffer.sender_count(frame.sender_ref) >= 10:
    send_ack(frame, status=3)  # rejected_quota
    return

  if frame.recipient_hint matches self:
    deliver_to_application(frame)
    send_ack(frame, status=0)
    return

  if frame.ttl == 0:
    drop; send_ack(frame, status=4)
    return

  # forward case
  frame.ttl -= 1
  frame.hop_count += 1

  buffer.add(frame)
  send_ack(from_peer, frame, status=1)  # buffered

  # opportunistic forwarding
  for peer in active_mesh_peers:
    if peer not in frame.forwarded_to and
       peer != from_peer and
       peer accepts forwarding:
      send(peer, frame)
      frame.forwarded_to.add(peer)
      send_ack(from_peer, frame, status=2)  # forwarded

on_timer_expired(entry):
  # local buffer expiry, independent от frame.ttl
  buffer.remove(entry)

Interaction с internet

Когда устройство получает internet connectivity, оно опционально (по настройке пользователя) пересылает buffered mesh frames в internet-сеть:

  1. Для каждой frame в buffer с recipient_hint который можно разрешить в account_id
  2. Пересылка через обычный P2P gossip к Account Host получателя
  3. После успешного acknowledgement с internet-стороны — frame удаляется из mesh buffer
  4. Internet-to-mesh обратное направление аналогично: устройство с internet получает сообщение для offline-получателя, enqueues в mesh buffer для forwarding через ближайшие peers

Это делает internet-connected устройство gateway между internet-сетью и изолированной mesh-областью. Один такой шлюз восстанавливает связность для всего mesh-кластера до внешнего мира.

Семь слоёв — одна конструкция

Слой 1: Transport Obfuscation          персональный сервер скрывает содержимое и тайминг
Слой 2: Peer Selection                  start_window + network diversity constraints
Слой 3: NAT Traversal                   каждый может войти, даже за NAT
Слой 4: Censorship-Resistant Discovery  пять каналов, достаточно одного
Слой 5: Dandelion++                     пиры не знают кто автор операции
Слой 6: Mesh Transport                  работа при отключении internet,
                                         hop-by-hop Bluetooth / Wi-Fi Direct
Слой 7: Store-and-Forward Semantics     ephemeral буферизация в mesh,
                                         per-sender quota, signed acks

Каждый слой закрывает свой вектор. Ни один не требует внешней инфраструктуры. Всё построено поверх libp2p (для internet-слоёв 1-5) и нативных BLE/Wi-Fi Direct API (для mesh-слоёв 6-7) плюс существующего gossip. Сетевой уровень ортогонален консенсусу — ни один state transition не затронут.

Protocol Message Layer

Внутри IBT uniform frames протокольные сообщения следуют общему envelope format. Эта секция нормативно определяет wire format всех Montana-сообщений для cross-implementation совместимости.

Envelope format.

ProtocolMessage:
  msg_type         1B    <- u8, код типа сообщения
  msg_version      1B    <- u8, версия формата сообщения (= 1 для v28.x)
  request_id       8B    <- u64 little-endian, correlation id для request/response (= 0 для one-way gossip)
  payload_length   4B    <- u32 little-endian, размер payload в байтах
  payload          ?B    <- payload_length байт, формат определяется msg_type

Envelope всегда 14 байт header + payload. Поскольку IBT uniform frames имеют payload 1021B, ProtocolMessage может занимать один или несколько фреймов через flag 0x04 continuation (см. Uniform Framing).

Реестр типов сообщений.

Код Тип Направление Payload
0x01 Transfer one-way gossip Transfer объект (serialize по canonical encoding)
0x02 TransferActivation one-way gossip TransferActivation объект (opcode 0x0A в operation registry; msg_type gossip envelope отдельное пространство)
0x03 ChangeKey one-way gossip ChangeKey объект
0x04 Anchor one-way gossip Anchor объект
0x10 NodeRegistration one-way gossip NodeRegistration объект
0x20 BundledConfirmation one-way gossip BundledConfirmation объект
0x21 VDF_Reveal one-way gossip VDF_Reveal объект
0x22 Proposal one-way gossip Proposal объект
0x40 FastSyncRequest request {anchor_window: u64, resume_offset: u64}
0x41 FastSyncResponse response chunked snapshot data (см. ниже)
0x42 FastSyncError response {code: u8, message: bytes[≤255]}
0x50 PeerListRequest request {max_count: u16}
0x51 PeerListResponse response {count: u16, peers: count × PeerEntry}
0xF0 Ping request {timestamp: u64} (локальный wall-clock отправителя, advisory)
0xF1 Pong response {timestamp: u64} (эхо timestamp из Ping)
0xFF Bye one-way {reason: u8} (graceful shutdown)

Message versioning: msg_version = 1 для всех v28.x. Изменение wire format = increment msg_version, требует protocol version upgrade.

Unknown msg_type → получатель логирует и игнорирует (forward compatibility). Unknown msg_version → получатель отвечает FastSyncError с кодом unsupported_version и разрывает соединение.

Structured payloads.

PeerEntry:

PeerEntry:
  ip_version       1B    <- u8, 0x04 или 0x06
  ip               16B   <- IPv4 в последних 4 байтах (первые 12 = 0x00) или IPv6 полностью
  port             2B    <- u16 little-endian
  node_id          32B
  start_window     8B    <- u64 little-endian, из Node Table
= 59 bytes fixed

FastSyncResponse chunked delivery:

FastSyncResponse chunk:
  chunk_index      4B    <- u32 little-endian, начинается с 0
  total_chunks     4B    <- u32 little-endian, общее число chunks для текущего запроса
  table_id         1B    <- u8: 0x01 Account, 0x02 Node, 0x03 Candidate, 0x04 Proposals
  record_count     4B    <- u32 little-endian, записей в этом chunk
  records          ?     <- record_count × serialize(record) по canonical encoding

Response состоит из N chunks (с одним request_id). Получатель собирает по chunk_index. После получения всех total_chunks — reconstructs Merkle root и проверяет против proposal_W.

Connection lifecycle.

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

1. TCP SYN / SYN-ACK / ACK                (standard)
2. TLS 1.3 handshake                       (server certificate optional)
3. Noise key agreement внутри TLS          (mutual pubkey authentication)
4. IBT proof exchange                       (клиент отправляет FN-DSA-512 signature)
5. Access level determination               (node / candidate / account, см. Transport Obfuscation)
6. Готово к обмену ProtocolMessages

Timeouts установки:

  • TCP connect: 30 секунд
  • TLS handshake: 10 секунд
  • Noise + IBT: 10 секунд
  • Всё вместе не более 60 секунд до готовности

Если любой шаг превысил timeout → разрыв, retry с другим пиром.

Keepalive.

  • Ping каждые 60 секунд на idle соединении (нет данных)
  • Pong должен прийти в пределах 30 секунд
  • Три подряд пропущенных Pong → disconnect
  • При активном обмене данными Ping не обязателен (реальные данные = evidence активности)

Timestamp в Ping/Pong — advisory только, не входит в consensus. Используется для оценки RTT локально.

Graceful shutdown.

Инициатор: отправляет Bye с reason code:

0x00 — normal shutdown
0x01 — going offline for maintenance
0x02 — peer list refresh (попытка найти лучших пиров)
0x03 — resource limits (слишком много соединений)
0x04 — protocol violation (валидация failed много раз)
0x05 — version mismatch

Получатель acknowledges через свой Bye, затем TLS close_notify, затем TCP FIN. Максимум 5 секунд на graceful shutdown, иначе forced close.

Peer discovery algorithm.

Новый узел при старте:

1. Извлечь bootstrap peers из Genesis Decree (захардкожено)
2. Выбрать 1-3 random bootstrap peer, connect (с PoW для bootstrap per Transport Obfuscation)
3. Выполнить IBT (account keypair для первого подключения нового узла)
4. Отправить PeerListRequest с max_count = 128
5. Получить PeerListResponse с до 128 известных peer-ов
6. Применить diversity constraints (/16, ASN, start_window) к полученному списку
7. Выбрать 24 outbound candidates по diversity
8. Параллельно connect к выбранным
9. После успешного IBT с реальным peer — disconnect от bootstrap (освобождая bootstrap slots)
10. Maintaining: PeerListRequest каждые ~τ₂ окон для обновления таблицы "проверенных" peers

Bootstrap exceptional:

  • PoW при подключении (target ~100ms CPU per Transport Obfuscation)
  • Ограничение: не более 3 одновременных bootstrap подключений на узел
  • Освобождается после 13 реальных peers connected

Peer exchange

Между двумя подключёнными узлами:

Каждые τ₂_windows:
  A → B: PeerListRequest {max_count: 64}
  B → A: PeerListResponse {peers[]}

Узел поддерживает две таблицы:

  • Новые peers: недавно узнанные (от bootstrap или PeerListResponse), ещё не использованные
  • Проверенные peers: те с которыми были успешные соединения в прошлом

При выборе outbound: 50/50 случайно из обеих таблиц. Bucket по секретному ключу узла предотвращает external enumeration.

Retry policy.

  • Failed connect: exponential backoff (1s, 2s, 4s, 8s, ..., max 300s)
  • Peer rejected через IBT fail: peer помечается bad на 1 час
  • Peer disconnected с reason 0x04 (protocol violation): peer blacklisted на 24 часа
  • Bootstrap PoW retry: no backoff (PoW сам служит rate limit)

Error codes для FastSyncError:

0x01 snapshot_unavailable       -- запрошенный anchor_window слишком старый (peer не хранит)
0x02 snapshot_too_large          -- snapshot больше чем peer готов отправить
0x03 unsupported_version         -- msg_version не поддерживается
0x04 resource_exhausted          -- peer перегружен
0x05 access_denied               -- peer не отдаёт Fast Sync клиентам (только nodes)

Сеть vs консенсус — граница.

Network layer параметры (timeouts, retry delays, keepalive intervals) — implementation guidance, могут варьироваться между реализациями без consensus impact. Значения в этой секции — рекомендуемые defaults. Consensus-critical: wire format (envelope, payloads), IBT proof format, Bootstrap PoW formula, message type codes. Изменение consensus-critical параметров требует protocol version upgrade.


Эволюция протокола

Изменения правил протокола существуют вне consensus state. Эволюция: открытые предложения, независимые реализации, добровольный выбор операторов узлов, fork resolution через большинство chain_length.

Принцип

Consensus state Montana содержит только то что необходимо для финансового слоя и хронометража: TimeChain, NodeChain, AccountChain, Account Table, Node Table. Никаких полей governance, никаких советов в state, никаких голосований в реестре операций. Любая попытка ввести on-chain governance вводит subjective компоненты в consensus state и создаёт постоянную атакуемую поверхность — это нарушение глобального инварианта I-3.

Эволюция протокола существует вне consensus state, как социальный и инженерный процесс над Anchor-публикациями и репозиториями реализаций.

Жизненный цикл изменения

1. PROPOSAL
   Любой участник публикует MIP (Montana Improvement Proposal)
   как Anchor с текстом на узле автора:
     app_id   = SHA-256("mt-app" || "mips")
     data_hash = H(текст MIP)
     anchor   = операция Anchor в AccountChain автора
   
   Авторство и timestamp доказуемы через подпись Anchor и
   timechain_value cemented окна. История эволюции навсегда
   через Anchor в TimeChain.

2. DISCUSSION
   Открытое обсуждение в публичных каналах
   (форумы, репозитории, advisory councils — см. ниже).
   Никаких формальных голосований внутри протокола.

3. IMPLEMENTATION
   Реализации (Rust core и альтернативные клиенты) выпускают
   новые версии узлового ПО с реализованным изменением.
   Каждая версия закрепляется за конкретным protocol_version
   (u32 в Proposal header).

4. ADOPTION
   Операторы узлов самостоятельно выбирают какую версию
   запускать. Никакого on-chain голосования, никакого формального
   activation window. Узлы публикуют proposals со своим protocol_version.

5. FORK RESOLUTION
   При расхождении правил сеть может разделиться на цепочки.
   Каждый узел следует той цепочке которая длиннее по его
   собственным правилам валидации (chain_length majority).
   Меньшинство либо обновляется до правил большинства, либо
   продолжает работать как независимая цепочка (hard fork).

Поле protocol_version

Поле protocol_version (u32) в Proposal header — единственный сигнал эволюции внутри консенсуса. Узел публикует proposals с тем protocol_version который реализован его версией ПО. Инвариант protocol_version >= prev_proposal.protocol_version запрещает откат к более старым правилам внутри одной цепочки.

protocol_version не голосуется и не активируется через governance. Он отражает фактическое состояние реализации узла — что узел реально умеет валидировать. Расхождение protocol_version между honest узлами разрешается естественно через fork choice по chain_length.

Advisory councils

Группы экспертов могут существовать как advisory структуры — публикующие рекомендации, обзоры, анализ безопасности через Anchor. Их подписи не имеют binding эффекта на consensus, их составы не хранятся в state, их голоса не считаются в state transitions.

Примеры advisory структур (опциональны, не часть протокола):

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

Захват advisory совета не даёт контроля над протоколом — он даёт только возможность опубликовать рекомендацию, которую операторы узлов могут проигнорировать. Это устраняет attack surface governance: нет binding голосования = нет цели для компрометации.

Advisory councils организуются вне протокола (репозитории, форумы, Anchor-публикации). Протокол не знает об их существовании и не выделяет им никаких прав.

Параметрическая адаптация

Параметры D и m адаптируются автоматически на границе τ₂ через participation-ratio feedback (см. раздел «Адаптация D через participation-ratio feedback»). Это не governance. Адаптация детерминирована, опирается только на canonical chain observations (cemented sets, Node Table), не требует голосования, не требует социальной координации, не зависит от измерений физического мира. Формула адаптации и её параметры зафиксированы в Genesis Decree; правка самой формулы требует MIP + новой версии ПО + adoption через chain_length, как и любое другое изменение протокола.

Закрытие окна определяется quorum event в канонических cemented sets. Механизм полностью event-driven и опирается только на canonical state.


Обоснование протокольных констант

Каждая константа выводится из инженерного анализа: модели атак, целевых свойств, математических ограничений. Derivation включает класс (security / performance / economic / operational), целевую функцию с численной целью, ссылки на литературу или стандарты, математический вывод, sensitivity analysis, готовый ответ на ожидаемые возражения. Design choices помечены как governance decisions с bounded rationale.

Архитектурная основа

Спецификация описывает архитектуру BFT committee с 67% quorum через BundledConfirmation. Поверх базового consensus добавлены incremental improvements: NodeChain per node для chain_length integrity, enhanced aggregate формула с honest NodeChain frontiers, sequential_proof в VDF_Reveal против self-forgery. Эта архитектура покрывает threat model до 33% Byzantine через BFT, с дополнительной защитой от compound withholding (NodeChain) и grinding (sequential_proof).

Иерархия целей безопасности

Разные классы механизмов применяют разные целевые вероятности отказа. Для одних классов криптографическая стойкость математически достижима; для других операционная безопасность наследуется от сетевого допущения.

Класс механизма Целевая вероятность отказа Обоснование выбора
Криптографические примитивы (подписи, VDF, hash) 2⁻¹²⁸ (полная криптографическая стойкость) Стандарт криптографии; lattice-based примитивы FN-DSA-512 и ML-KEM спроектированы на этом уровне
Защита сетевого уровня (eclipse, sybil entry, bootstrap PoW) 2⁻⁴⁰ Стандарт сетевых криптопротоколов (TLS 1.3 RFC 8446 rekey interval, IPsec RFC 4301 SA lifecycle)
BFT-безопасность комитета inherited от допущения f < 1/3 в сети Криптографический порог требует комитета в тысячах узлов — инфизибельно. Принимается стандартное BFT-допущение + проверка ограниченной концентрации в комитете
Живучесть (кворум при частичном офлайне) operational ≤ 1 сбой на 1000 окон Достижимо разумным размером комитета при реалистичной доле онлайн-работы операторов ≥ 0.85
Монетарная политика governance decision R_BASELINE, BOOTSTRAP параметры — honest design choices, не math derivation

Классификация применяется при выводе каждой константы — значение обосновывается в рамках своего класса цели.

Криптографические и временные параметры

Константа Значение Обоснование
τ₁ (длительность окна) 60 секунд Class: Operational/Performance. UX bound: confirmation within 1 min subjective threshold [Nielsen 1993 Usability Engineering]. VDF lower bound: τ₁ существенно превышает typical gossip propagation. Network diameter при 24 outbound connections: log_24(N) hops; для N = 10⁵ nodes = log_24(10⁵) ≈ 3.6 hops × 300ms single-hop latency ≈ 1.1 s. Safety factor ×20 для worst-case variance: τ₁ ≥ 22 s [Boneh et al. 2018 CRYPTO «Verifiable Delay Functions» — VDF timing requirements]. Band [22, 60]. Pin = 60 s (upper edge maximizes VDF work within UX budget для maximum hardware-asymmetry margin)
τ₂ (epoch boundary) 20 160 окон Class: Operational. τ₂_windows выбран для balance между responsiveness (шorter epochs = faster adaptation) и stability (longer epochs = reduced noise в participation_ratio measurements). Factorization 2⁶ ×× 5 × 7 (60 divisors) enables flexible sub-epoch division. Pin = 20 160 — middle точка band, aligned с operator maintenance cycle assumption (external calibration target, не protocol rule)
D₀ (TimeChain VDF за окно) 252 × 10⁶ Class: Cryptographic/Performance. Эмпирическая калибровка: median SHA-256 rate 4.2 MH/s на commodity x86_64 single-thread × 60s = 252 × 10⁶. Режим: sequential single-chain VDF. Hardware advantage через pipelined single-thread оптимизацию ограничен ×5-10 над commodity [Pietrzak 2018 «Simple Verifiable Delay Functions», Boneh et al. 2018 CRYPTO «Verifiable Delay Functions»]. Montana использует exclusively sequential regime: каждая итерация SHA-256 зависит от предыдущей, параллелизация архитектурно исключена
base_vdf_length (VDF entry) τ₂ (20 160 окон) Class: Sybil resistance (combined defense). Component барьера: sequential VDF cost + AS diversity filter. VDF cost: 14 дней wall-clock commodity / 1.4 дня на ASIC×10 = ~$20-50 per candidate rent. AS diversity filter: attacker bounded by actually controlled AS count (typical large attacker controls 10-100 AS из global pool ~80 000). Combined defense multiplier: для 1000 Sybil candidates attacker spends $20-50k VDF rent AND должен распределить по minimum 150 distinct AS (per committee_divisor L1 requirement); combined barrier = VDF cost × (required AS count / attacker AS capacity) ≈ 10-100× stronger чем VDF alone. Unit consistency = τ₂ (1 adaptation epoch = 1 entry epoch)
BOOTSTRAP_EPOCHS (bootstrap length) 5 Class: Economic (minimum viable bootstrap duration). Derivation — минимальный период для достижения жизнеспособной сети через три независимых требования: (R1) Критическая масса операторов: BFT safety требует ~1 000 активных операторов для устойчивости 33%-атаки. Достижение growth factor 1000× от single bootstrap seed за T лет требует annual compound rate = 1000^(1/T). При T = 5: rate = 3.98× (≈ 298% annual) — соответствует наблюдаемой динамике early network growth phase при активном bootstrap incentive. При T = 3: rate = 10× (900% annual), достижимо в edge-case hype scenarios. При T < 3: requires rate >10× что экономически exceptional. T ≥ 3-5 лет составляет realistic minimum для critical mass. (R2) Verification жизнеспособности: протокол обязан пережить хотя бы один полный экономический цикл (~3-4 года) для demonstration устойчивости к рыночным колебаниям. (R3) Формирование ценового равновесия: Ɉ market price discovery + operator cost recovery equilibrium занимает 2-3 года от genesis. Стабильная компенсация operator infrastructure costs требует price stability через equilibrium. Intersection трёх требований = 5 лет минимум. 1 epoch = 1 year (calendar alignment) → BOOTSTRAP_EPOCHS = 5. Период меньше 5 лет рискует collapse после завершения bootstrap: операторы покидают сеть при insufficient rewards до достижения market equilibrium
BOOTSTRAP_H (bootstrap epoch length) 524 160 окон (26 × τ₂) Class: Economic. Derivation: Target 1 annual cycle per epoch (calendar alignment для user-level perception — external target assumption, не protocol rule). Integer pin = 26 × τ₂ (ближайшее кратное τ₂ к annual wall-clock при target D₀ calibration). BOOTSTRAP_H = 26 × 20 160 = 524 160 окон. Кратность τ₂ для совмещения с pruning / snapshot boundaries
BOOTSTRAP_R0 (bootstrap genesis emission) 16 Ɉ/окно Class: Discrete schedule pin (clean halving). Derivation: Integer halving sequence over BOOTSTRAP_EPOCHS = 5, terminating at 1 (last epoch rate): r₁ → r₁/2 → r₁/4 → r₁/8 → r₁/16. Constraint r₁/16 = 1 (integer terminating value для preserving halving property) ⟹ r₁ = 16 = 2⁴. Sequence: 16 → 8 → 4 → 2 → 1 Ɉ/окно across 5 epochs. Power of 2 enables clean integer halving — governance pin выбранный из discrete schedule constraint, не economic necessity. Validation incentive: total emission каждой epoch (bootstrap + baseline) = {29, 21, 17, 15, 14} Ɉ/окно vs steady-state 13 Ɉ/окно. Каждая epoch содержит positive incentive above steady state (1 Ɉ additional minimum в epoch 5). Early epochs (≥2× steady state) обеспечивают strong incentive для initial network acquisition. Front-loading trade-off (accepted): расписание 16→8→4→2→1 создаёт front-loaded распределение — bonus составляет ≈55.2% cumulative supply в год 1, ≈48.0% в год 2, ≈41.8% в год 3, ≈36.6% в год 4, ≈32.3% в год 5. Этот front-load — осознанный trade-off: ускоряет attainment critical mass за счёт диспропорции раннего распределения, принимается как необходимая цена быстрого bootstrap
BOOTSTRAP_CAP (total bootstrap emission) 16 248 960 Ɉ Class: Economic. Derivation: Follows from BOOTSTRAP_R0 halving × BOOTSTRAP_H per epoch. Epoch 1: 16 × 524 160 = 8 386 560. Epoch 2: 8 × 524 160 = 4 193 280. Epoch 3: 4 × 524 160 = 2 096 640. Epoch 4: 2 × 524 160 = 1 048 320. Epoch 5: 1 × 524 160 = 524 160. Sum = 16 248 960 Ɉ. Capped constant, verifiable property bootstrap_cumulative(window_index) ≤ BOOTSTRAP_CAP_nɈ
R_BASELINE (baseline emission) 13 Ɉ/окно forever Class: Economic (floor + governance margin). Derivation: (1) Design constraint — bootstrap pre-emission ≤ 5% cumulative supply на 50-м году от генезиса (экономическая зрелость, generational horizon). Обоснование порога 5%: band [2%, 10%] — при >10% early participants получают disproportionate ownership, подавляя organic distribution; при <2% bootstrap incentive недостаточен для critical mass. 5% — centre band. (2) Canonical supply formula (authoritative из раздела 3 «Поокнная эмиссия»): supply(W) = R_BASELINE × (W + 1) + bootstrap_cumulative(W). Baseline эмитируется с окна 0; bonus заканчивается на окне 2 620 800 (= 5 × BOOTSTRAP_H). (3) T_year = 525 960 окон (Julian year, 365.25 × 1440 min / τ₁ = 60 s). BOOTSTRAP_CAP = 16 248 960 Ɉ (derived выше). Floor math (baseline эмитируется все 50 лет per canonical supply formula): S(50) = BOOTSTRAP_CAP + R × T_year × 50. Constraint BOOTSTRAP_CAP / S(50) ≤ 0.05 ⟹ R × 26 298 000 ≥ 308 730 240 ⟹ R ≥ 11.739 ⟹ minimum integer floor = 12. Pin R_BASELINE = 13 = floor + 1 (governance margin +1 Ɉ/окно steady-state для дополнительной security margin над минимальным floor, сохраняя bootstrap share < 5% к году 50). Verification at pin R = 13: S(50) = 358 122 960 Ɉ; bootstrap share на году 50 = 16 248 960 / 358 122 960 = 4.54%; baseline annual inflation на году 50 = 13 × 525 960 / 358 122 960 = 1.91% — соответствует Friedman 1969 k-percent rule band ~2%. Пример operator economics (scenario, не design input): для случая N_active = 10 000 операторов per-operator annual reward = 13 × 525 960 / 10 000 = 684 Ɉ/год; фактические значения зависят от network adoption dynamics и market price discovery

Сетевые и операционные параметры

Константа Значение Обоснование
selection_interval 336 окон Class: Operational. Target 60 selection events per τ₂ (middle of operational band [30, 80]: ≤ 30 даёт admission backlog при surge, ≥ 80 раздувает per-event overhead). selection_interval = τ₂ / 60 = 336. Verification: 20160 % 336 = 0 ✓. Factorization 2⁴ × 3 × 7. Band [30, 80] обоснован operational trade-offs, pin 60 = середина band с divisor constraint
stem_epoch (Dandelion++) 10 окон Class: Privacy. Dandelion++ paper [Fanti et al. 2018 SIGMETRICS «Dandelion++: Lightweight Cryptocurrency Networking with Formal Anonymity Guarantees» § 4] recommends stem epoch comparable к typical user transaction session duration. τ₁ = 60s, user session ≈ 5-30 min → 5-30 windows. Pin = 10 окон (10-min rotation) выбрано на lower quarter band — shorter rotation усиливает anonymity guarantees через более частую смену stem peer, снижая window для multi-tx correlation attacks. Value 5 обеспечил бы ещё более сильную rotation, но ценой connection churn overhead; 10 balances privacy strength и connection stability
Ядра на узел минимум 1 Class: Operational. TimeChain VDF sequential — выполняется на одном ядре последовательно. 1 ядро достаточно, validation interleaved с VDF. 2+ ядра устраняют interleaving overhead (~5-10%)

Безопасность консенсуса и сети

Константа Значение Обоснование
confirmation_quorum 67% Class: Cryptographic/BFT. Math необходимость: Byzantine fault tolerance n ≥ 3f+1, quorum 2f+1 = 2/3+1 [Castro & Liskov 1999 «Practical Byzantine Fault Tolerance»]. FLP impossibility [Fischer Lynch Paterson 1985 «Impossibility of Distributed Consensus with One Faulty Process»] устанавливает tight bound для async deterministic consensus. Математическая necessity, derivation строгая
committee_divisor (confirmation_threshold) active_chain_length / 256 Class: BFT security + implementation efficiency. Три независимых пинающих требования пересекаются в единственном значении 256: (L1) Operational diversity requirement — BFT committee должен представлять multiple distinct jurisdictions, AS, operational teams для prevention coordinated capture. Empirical BFT production practice (distributed systems literature) range 100-200 operators для адекватной diversity; lower bound N ≥ 150 обеспечивает diversity margin. (L2) Bandwidth constraint — committee-level BFT signature aggregation занимает allocated portion operator bandwidth. При allocation 1% of baseline 10 Mbps operator connection = 12.5 KB/s на BFT messaging (остальное зарезервировано для operations, gossip, state sync): 2 phases (propose + commit) × N signatures × 700 B per round / τ₁ = 60s ≤ 12 500 B/s ⟹ 2 × 700 × N / 60 ≤ 12 500 ⟹ N ≤ 536. Rounded: N ≤ 500. (L3) Implementation efficiency — степень двойки для bitmap-alignment, bitwise-routing, SIMD-обработки, balanced Merkle tree. Единственное значение в [150, 500] удовлетворяющее всем трём — 256 = 2⁸. Безопасность: при uptime asymmetry ≤ 1.18× и f ≤ 0.25 в сети доля атакующего в комитете ≤ 28.2%, ниже BFT threshold 1/3. Требование к развёртыванию: операторы ≥ 0.85 онлайн-работы
admission_divisor (slots per selection) max(1, active / 130) Class: Admission capacity. Target: per-event admission rate ≤ 1% active_nodes — верхняя планка, защищающая сеть от слишком быстрой смены состава и от single-event Sybil injection. Derivation: slots / active ≤ 0.011 / divisor ≤ 0.01divisor ≥ 100. Pin = 130 даёт buffer margin ~30% ниже 1% cap: steady-state rate 1/130 = 0.77% < 1%. Verification (compound growth): при active ≫ 130 сеть растёт как (1 + 1/130) per event. С темпом 60 events per τ₂ (selection_interval = 336) удвоение сети требует ln(2) × 130 ≈ 90 events ≈ 1.5 × τ₂ — разумный bootstrap pace. slot_min = 1 гарантирует network liveness при малом active count (Genesis и bootstrap periods). Независим от committee_divisor = 256: admission управляет ростом сети, committee — BFT threshold для cementing, разные функции
outbound connections 24 Class: Network security (eclipse). Модель: attacker контролирует f = 0.3 peer-пула [Heilman et al. 2015 USENIX; Marcus et al. 2018 — empirical research по eclipse-атакам в P2P cryptocurrency networks]. Target P(eclipse) < 2⁻⁴⁰ [TLS 1.3 RFC 8446 industry standard]. Math: P(eclipse) = f^N < 2⁻⁴⁰ ⟹ N > 40·log(2)/|log(0.3)| ≈ 23.03 ⟹ smallest integer N = 24. Bandwidth cost ~24 KB/s outbound находится внутри operational budget типичного узла. Diversity selector (≥7 distinct AS) снижает effective f, усиливая margin
equivocation timeout 10 окон Class: BFT detection. BFT evidence propagation [Castro & Liskov 1999 «Practical BFT»]: пакет equivocation evidence проходит три этапа — (1) cementing double-signed pair через BundledConfirmation (propose + commit phases BFT = 2 τ₁ windows), (2) gossip propagation evidence по network diameter (~1 τ₁ window), (3) slashing transaction cementing (~2 τ₁ windows). Base = 5 окон. Safety factor ×2 для worst-case gossip variance + jurisdictional latency outliers = 10 окон. Окно покрывает worst-case gossip propagation с запасом для timely slashing
active predicate 2τ₂ (40 320 окон) Class: Operational lifecycle. Один full epoch downtime (maintenance) + recovery buffer. 2τ₂ покрывает типичный operator maintenance cycle с запасом. Значение sensitivity: 1τ₂ пересекается с maintenance циклами; 3τ₂ удерживает inactive узлы в состоянии дольше необходимого
node pruning 8τ₂ (161 280 окон) Class: Operational lifecycle. 4× active_predicate (generous retry buffer). 8τ₂ inactivity practically permanent exit. 4τ₂ aggressive (может пропустить long-offline honest); 16τ₂ удваивает state bloat без benefit
pruning_idle (accounts) 4τ₂ (80 640 окон) Class: Operational. Consistency с account bucket Tier 0 boundary (4^1 × τ₂) — derived constraint, не free parameter
candidate_expiry 3τ₂ (60 480 окон) Class: Operational. Queuing analysis для target P(candidate admitted within expiry) ≥ 0.5: при selection events E = 60 per τ₂ × 3τ₂ = 180 events и pool ratio c = pool_size / slot_count (ratio candidates waiting to slots available per event), P(specific candidate picked per event) = 1/c, P(not picked in E events) = (1 1/c)^E. Для c = 10: P(admitted) = 1 0.9^180 = 0.99999 (near-certain). Для c = 100: P(admitted) = 1 0.99^180 = 0.84. Даже при высокой pool ratio candidate_expiry = 3τ₂ обеспечивает >80% admission probability. Значение sensitivity: 2τ₂ (120 events) даёт P(admitted) = 0.70 при c=100 (низко); 4τ₂ (240 events) даёт 0.91 ценой Pool bloat
account бакеты 4^N × τ₂ Class: Operational/Sybil. Exponential age stratification base 4. Sybil attacker isolated в Tier 0, получает 1/4 rate через round-robin. 4 tiers покрывают 0-256τ₂
D adjustment rate ±3% за τ₂ Class: Adaptive. Matched Moore's law pace: doubling time ln(2)/ln(1.03) ≈ 23.5 τ₂ — порядок величины hardware generational cycle. ±1% слишком медленный response; ±10% волатильность, hardware churn
dead zone [0.85, 0.95] Class: Adaptive control. Control systems hysteresis [Ogata «Modern Control Engineering»]. 10% band предотвращает oscillation near threshold. Centre 0.9 = target participation_ratio, ±5% tolerance
target₀ calibrated at genesis Class: Genesis runtime calibration. Target: weighted_ticket(active=1, chain_length=1) < target₀ гарантирующий first winner at genesis (N=1). Точное значение зависит от cryptographic randomness at genesis, не deterministic constant
chain_length_snapshot скользящее окно 6τ₂ (120 960 окон) Class: Lottery weight (recency). Target: snapshot_window задаёт период за который new honest operator достигает full snapshot parity с established. 6τ₂ выбрано по принципу balance: window ≥ 2 × active_predicate (2τ₂) обеспечивает robust recency signal even при intermittent operator activity, window ≤ node_pruning (8τ₂) сохраняет consistency с lifecycle boundaries. Pin 6τ₂ — центр intersection [4τ₂, 8τ₂]. Value sensitivity: 4τ₂ ускоряет parity ценой lottery weight churn; 8τ₂ удлиняет onboarding ценой slower new operator integration
seniority_bonus divisor 13 Class: Lottery weight (longevity). Target T_cap = chain_length_at_cap = 3 × T_year = 1 577 880 окон (infrastructure investment horizon — 3 annual cycles, external target assumption). snapshot_max = 6τ₂ = 120 960. Divisor = 1 577 880 / 120 960 = 13.04 ⟹ 13. Math pin после target fixed
seniority_bonus formula min(chain_length / 13, snapshot) Bounded добавка за longevity с cap = snapshot (max advantage 2×). Через 3 × T_year honest operator достигает cap, далее стабильный потолок
lottery_weight snapshot + seniority_bonus Разделение: lottery_weight для эмиссии (recent work + bounded longevity); абсолютный chain_length для quorum (безопасность). Temporal Aristocracy ограничена cap-ом
adaptive_vdf_threshold 0.5% (pending/active) Class: Adaptive. Stationary pending ratio = 1/D_adm = 1/256 ≈ 0.39%. Buffer factor β = 1.28 [standard control-systems 20-30% hysteresis]. P_thr = β × 0.39% = 0.5%
adaptive_vdf_multiplier ×200 Class: Adaptive. Math continuity: required_vdf = base × pressure × M. At pressure = P_thr = 0.005, required = base ⟹ M = 1/P_thr = 200. Derivation follows from continuity requirement
base_vdf_length τ₂ окон (= 20 160) Class: Sybil resistance. См. «Криптографические и временные параметры» выше (combined defense articulation)
max_vdf_horizon 4 × τ₂_windows (80 640) Class: Security (adaptive VDF upper bound). В BFT-контексте с 33% Byzantine tolerance покрывает pressure до ρ_max = 2% (4× P_thr) для spam/surge defense. Social consensus coordination handles beyond-BFT scenarios. H_max = B × ρ_max × M = τ₂ × 0.02 × 200 = 4τ₂

Архитектура

  ТЕЛЕФОН / ДЕСКТОП                        УЗЕЛ (десктоп / сервер, 24/7)
┌────────────────────────┐         ┌──────────────────────────────────────┐
│  Кошелёк               │         │                                      │
│  FN-DSA-512 keypair    │         │  TimeChain                           │
│  локальная UX-история  │         │  T_r = SHA-256^D(T_{r-1})            │
│  операций              │         │  каноническая последовательность,    │
│                        │         │  источник случайности                │
│  AccountChain          │         │        │                             │
│  (счётчик окон         │         │        ▼                             │
│   активности)          │         │  NodeChain (per node)                │
│                        │         │  chain_length = cemented             │
└──────────┬─────────────┘         │    BundledConfirmation count         │
           │  операции             │  доказательство присутствия          │
           │  (type|prev_hash|     │  lottery endpoint = SHA-256(T_r ||   │
           │   payload|FN-DSA-512) │        │                             │
           └──────────────────────▶│        ▼                             │
                confirmations      │  AccountTable                        │
               ◀──────────────────-│  balance (открыт)                    │
                                   │  pubkey, frontier_hash               │
                                   │  account_chain_length                │
                                   │        │                             │
                                   │        ▼                             │
                                   │  Proposals (навсегда)                │
                                   │  control_root, node_root,            │
                                   │  account_root, timechain_value       │
                                   └──────────────────────────────────────┘

Зависимости: TimeChain → NodeChain → AccountTable
Отказ AccountTable не останавливает продвижение TimeChain.
Отказ узла не заражает каноническую последовательность.