# Montana — Спецификация протокола **Версия:** 23.0.0 (2026-04-09 UTC) ## Определение Montana — цифровой стандарт времени. Сеть независимых VDF-осцилляторов, поддерживающих единую верифицируемую временную шкалу с криптографическим доказательством каждого момента. Каждая зарегистрированная секунда = 1 TimeCoin (Ɉ). NTP говорит «сейчас 14:32» — и ты доверяешь серверу. Montana говорит «сейчас 14:32, вот криптографическое доказательство, и вот что произошло в этот момент» — и ты проверяешь сам. Основная функция — хронометраж. Вторичная — передача ценности. Консенсус: **Proof of Time (PoT)** — четыре цепочки. TimeChain: глобальные часы (D последовательных SHA-256 = одно окно). NodeChain: персональная цепочка узла (доказательство присутствия при каждом тике). AccountChain: счётчик окон активности аккаунта. AccountTable: состояние счёта. Влияние узла = длина его NodeChain. Протокол не использует время для консенсуса — протокол и есть ход времени, оцифрованный и криптографически верифицируемый. Генезис: 09.01.2026 00:00:00 UTC. Генезис-фраза: `«Кто контролирует прошлое, контролирует будущее. Кто контролирует настоящее, контролирует прошлое.» — Оруэлл, 1984` Протокол не имеет on-chain governance. Эволюция протокола проходит через Bitcoin-style процесс: открытые предложения (MIPs — Montana Improvement Proposals) публикуются в Content Layer как рекомендации, реализации выпускают новые версии, операторы узлов выбирают какую версию запускать. Fork resolution детерминирован через chain_length большинство. См. раздел «Эволюция протокола». --- ## Четыре решённые проблемы ### 1. Децентрализованный хронометраж **Проблема.** Существующие системы измерения времени (NTP, GPS, PTP) зависят от доверенной инфраструктуры. Компрометация сервера NTP или отключение спутника GPS нарушает временную шкалу для всех зависимых систем. **Решение.** Децентрализованные часы — сеть независимых VDF-осцилляторов, в которой каждый узел вычисляет ход времени автономно через последовательное SHA-256 хэширование. Результат детерминирован и верифицируем любым участником без доверия к третьей стороне. **Свойства.** Montana Time обладает четырьмя свойствами одновременно: | Свойство | Определение | NTP | GPS | PTP | Montana | |----------|------------|-----|-----|-----|---------| | Монотонность | Время не идёт назад | нет | да | да | да | | Консистентность | Все честные узлы согласны на одну шкалу | слабая | да | да | да | | Верифицируемость | Любой может доказать прохождение интервала | нет | нет | нет | да | | Независимость | Отсутствие серверов, спутников, доверенной инфраструктуры | нет | нет | нет | да | Ни одна существующая система измерения времени не обеспечивает все четыре свойства. ### 2. Неплутократический консенсус **Проблема.** В Proof of Work влияние пропорционально вычислительному бюджету. В Proof of Stake — капиталу. В обоих случаях безопасность сети является функцией концентрации ресурсов, приобретаемых на рынке. **Решение.** Proof of Time — механизм консенсуса, в котором влияние узла определяется исключительно длительностью его непрерывного присутствия в сети, измеренной в подписанных временных окнах. Вес узла = длина его NodeChain (количество окон, в которых узел криптографически доказал своё присутствие). **Свойства.** - Время — единственный ресурс, который нельзя приобрести, передать, делегировать или сконцентрировать - Два участника, запустившие узлы одновременно, имеют равный вес независимо от капитала - Стоимость атаки на консенсус выражается не в валюте, а во времени, и растёт линейно с возрастом сети ### 3. Хронометрическая эмиссия **Проблема.** Денежная политика фиатных валют определяется решениями комитетов и непредсказуема. Денежная политика Bitcoin предсказуема, но дефляционна — фиксированный потолок supply создаёт ожидание роста цены и подавляет использование как средства обмена. **Решение.** Хронометрическая эмиссия — денежная политика, в которой скорость создания новых единиц привязана к физической константе — секунде — и неизменна на всём горизонте существования протокола. Одна секунда протокольного времени порождает одну монету. **Свойства.** - Supply в момент T = количество секунд, прошедших с генезиса - Годовая инфляция монотонно убывает и асимптотически стремится к нулю как следствие арифметики, не изменения правил - Эмиссия не контролируется ни одним участником, комитетом или голосованием - Денежная политика полностью определена единственной константой и не может быть изменена после генезиса ### 4. Эволюция без on-chain governance **Проблема.** On-chain governance — голосования, советы, формальные процедуры изменения правил внутри протокола — вводит subjective компоненты в consensus state и создаёт постоянную атакуемую поверхность. Любая структура которая голосует становится мишенью: компрометация моделей, подкуп участников, юрисдикционное давление. Чем формальнее governance, тем чётче определена цель атаки. **Решение.** Bitcoin-style эволюция. Протокол не имеет on-chain governance вообще. Изменения протокола публикуются как открытые предложения (MIPs — Montana Improvement Proposals) в Content Layer. Реализации (узловое ПО) выпускают новые версии с реализованными MIPs. Операторы узлов сами выбирают какую версию запускать. Fork resolution полностью детерминирован: при расхождении правил сеть разделяется на цепочки, каждая со своим chain_length, и узлы следуют за той цепочкой которая длиннее по их собственным правилам валидации. Никаких голосований внутри консенсуса. Никаких советов с правом подписи изменений. Никаких полей governance в state. **Свойства.** - Consensus state не содержит ни одного subjective поля связанного с governance — нет attack surface - Последнее слово реально у узлов: оператор каждого узла самостоятельно решает что запускать, без посредников - MIPs существуют как тексты в Content Layer (anchor + контент), любой может опубликовать, любой может верифицировать авторство и timestamp - Экспертные советы (AI Council, Core Council) допустимы как **advisory** — публикуют рекомендации, обзоры, анализ безопасности; их подписи не имеют binding эффекта на consensus - Захват advisory совета не даёт контроля над протоколом — он даёт только возможность опубликовать рекомендацию, которую узлы могут проигнорировать - История эволюции навсегда в Content Layer через anchor — каждый MIP и каждое обсуждение timestamped в TimeChain ### Следствие: цифровой стандарт времени без человека-посредника Четыре решённые проблемы порождают уникальную возможность. Любой документ, событие, состояние может быть записано в Montana с математически доказуемой привязкой к моменту верифицируемого времени. Anchor — 32 байта, навсегда. Ни одна существующая система не предоставляет timestamp, который одновременно децентрализован, неплутократичен, привязан к детерминированной денежной политике и свободен от on-chain governance. Montana — не блокчейн с функцией timestamping. Montana — цифровой стандарт времени с функцией передачи ценности. Bitcoin убрал доверие к деньгам — Montana следует тому же принципу для всех слоёв. Ни один человек не может в одиночку изменить протокол. Ни одна группа разработчиков. Ни одна корпорация. Ни один совет. Изменения существуют только как открытые предложения и реализации, которые операторы узлов выбирают запускать или нет. --- ## Montana Time VDF — цифровой аналог физического осциллятора. 9 192 631 770 колебаний цезия-133 = одна секунда SI. D последовательных SHA-256 = одно окно τ₁ Montana. Калибровка D каждые τ₂ — синхронизация цифрового осциллятора с физическим временем. TimeChain — глобальные цифровые часы, поддерживаемые сетью узлов. Каждый узел тикает независимо через последовательное хэширование. Результат детерминирован — одни входные данные дают одну временную шкалу. Токен — не награда за работу. Токен — тик часов, записанный в цепочку. Протокол не генерирует монеты — протокол регистрирует прошедшие секунды. Запись называется монетой. ### Четыре свойства | Свойство | NTP | GPS | PTP | Montana | |----------|-----|-----|-----|---------| | Монотонность | нет | да | да | да | | Консистентность | слабая | да | да | да | | Верифицируемость | нет | нет | нет | да | | Независимость | нет | нет | нет | да | **Монотонность.** Время никогда не идёт назад. VDF последователен — каждый хэш зависит от предыдущего. **Консистентность.** Все честные узлы согласны на одну временную шкалу. TimeChain детерминирован. **Верифицируемость.** Любой может пересчитать VDF и доказать что заявленное время прошло. **Независимость.** Каждый узел тикает сам. Нет серверов, спутников, доверенной инфраструктуры. Montana — не эталон точности. Montana — эталон независимости. ### Точность Гранулярность осциллятора: одно SHA-256 хэширование (зависит от аппаратуры: наносекунды — десятки наносекунд). Дрифт между калибровками (τ₂): единицы секунд за 14 дней. Калибровка D возвращает окно к целевым 60 секундам. Протокол самокорректируется. ### Time Oracle TimeChain value в каждом proposal — верифицируемая временная метка. Внешние системы используют Montana Time: - **Timestamping.** H(document) привязанный к TimeChain value = криптографическое доказательство существования в момент T. - **Ordering.** Два события привязанные к разным TimeChain values имеют доказуемый порядок. - **Anchoring.** Внешний протокол якорится в Montana Time для независимой верификации порядка событий. ### Протокольная дата ``` protocol_date(window_index) = genesis_timestamp + (window_index + 1) × 60 ``` Genesis: 09.01.2026 00:00:00 UTC (Unix: 1 736 380 800). Proposal `window_index = 525 599` = ровно один год после генезиса. Калибровка D корректирует дрифт — протокольная дата отклоняется от UTC на единицы секунд за τ₂. Формула точна для любого window_index. TimeChain хранится навсегда. Временные метки верифицируемы любым узлом в любой момент. --- ## Криптография Два примитива с разделёнными ролями: - **SHA-256** — консенсус (TimeChain, NodeChain), адреса, Merkle-деревья, хэширование - **FN-DSA-512** (Falcon-512, выбран в финальном раунде NIST PQC selection, июль 2022; forthcoming FIPS 206; reference implementation production-ready) — подписи операций аккаунтов и proposals узлов SHA-256 обеспечивает квантовую устойчивость консенсуса: алгоритм Гровера сокращает безопасность с 256 до 128 бит. FN-DSA-512 обеспечивает математическую постквантовую устойчивость подписей на основе NTRU-решёток. Других криптографических примитивов в протоколе нет — финансовый слой публичен, приватность данных обеспечивается на уровне приложений через Anchor. ### Подписи — FN-DSA-512 Подпись на NTRU-решётках (Falcon-512). Stateless, многоразовая. Публичный ключ закрепляется за аккаунтом при создании и используется для всех последующих операций. | Компонент | Размер | |-----------|--------| | Приватный ключ | 1 281B | | Публичный ключ | 897B | | Подпись (padded) | 666B | Поле suite_id в формате блока обеспечивает миграцию подписи без изменения модели состояния. Активация новой схемы требует protocol upgrade. Активная схема на момент запуска: FN-DSA-512. ### Адреса Формат: `mt` + Base58(account_id + checksum). Account_id = SHA-256("mt-account" || suite_id || pubkey). Стабильный идентификатор аккаунта. Смена ключа или схемы подписи выполняется через ChangeKey без изменения account_id — account_id привязан к первому pubkey, а текущий ключ хранится в состоянии аккаунта. **Инвариант derivation.** Проверка `account_id == SHA-256("mt-account" || suite_id || pubkey)` происходит **один раз** при финализации OpenAccount. После этого account_id — каноничный ключ записи, формула не пересчитывается. Доказательство derivation навсегда сохранено в proposal с финализированным OpenAccount. Любой аудитор может replay из proposal history. Original_pubkey не дублируется в Account Table — integrity гарантируется неизменностью proposal chain. Поле `suite_id` в Account Table — **current** (мутируется ChangeKey синхронно с current_pubkey), используется для верификации текущих подписей. Original suite_id зафиксирован только в исторической OpenAccount записи в proposal chain. --- ## Account Chain (Block Lattice) Каждый аккаунт имеет собственную цепочку операций. Перевод — одна операция в цепочке отправителя. Зачисление получателю — детерминированно после финализации. Цепочки аккаунтов полностью независимы. ### Реестр типов объектов ``` UserObjects: 0x01 OpenAccount 0x02 Transfer 0x03 ChangeKey 0x04 Anchor ControlObjects: 0x10 NodeInvitation 0x11 NodeRegistration 0x20 MIP ``` ### Типы операций **Универсальная форма операции:** ``` type (1B) | prev_hash (32B) | payload (variable) | signature (666B) ``` Все операции — этот шаблон. `prev_hash` связывает операции в цепочку аккаунта. `signature` — FN-DSA-512 владельца. `payload` зависит от типа. Все non-OpenAccount операции начинают payload с `sender (32B account_id)` — узел проверяет `Account Table[sender].frontier_hash == prev_hash` и `signature валиден для current_pubkey` за O(1). **OpenAccount** — создание аккаунта (один раз). Единственная операция где `prev_hash = 0x00...00`: ``` type 1B <- 0x01 OpenAccount prev_hash 32B <- 0x00...00 payload 899B <- suite_id (2B) || pubkey (897B FN-DSA-512) signature 666B Итого: ~1 598 B ``` `account_id = SHA-256("mt-account" || suite_id || pubkey)` — детерминирован, не хранится в payload. **Transfer** — публичный перевод: ``` type 1B <- 0x02 Transfer prev_hash 32B payload 80B <- sender (32B) || link (32B receiver) || amount (16B u128 nɈ) signature 666B Итого: ~779 B ``` `sender` — account_id отправителя, явно. Узел проверяет `Account Table[sender].frontier_hash == prev_hash` за O(1). Открытые поля: отправитель (через frontier index по prev_hash), получатель, сумма, баланс после операции (через Account Table). Псевдонимность на уровне account_id, как Bitcoin. Финансовая приватность — задача приложений (микшеры, payment channels), не протокола. **ChangeKey** — смена ключа или схемы подписи: ``` type 1B <- 0x03 ChangeKey prev_hash 32B payload 931B <- sender (32B) || new_suite_id (2B) || new_pubkey (897B) signature 666B <- подписано старым ключом Итого: ~1 630 B ``` **Anchor** — криптографический якорь (привязка данных ко времени): ``` type 1B <- 0x04 Anchor prev_hash 32B payload 96B <- sender (32B) || app_id (32B) || data_hash (32B) signature 666B Итого: ~795 B ``` Anchor не перемещает средства и не требует комиссии. Единственная операция — запись data_hash в цепочку аккаунта с привязкой к timechain_value окна финализации. Приватность данных приложения обеспечивается тем что в сеть попадает только хэш — содержимое хранится у владельца зашифрованным. ### Верификация баланса Открытое арифметическое сравнение. Узел проверяет: ``` sender != receiver amount > 0 sender.balance >= amount ``` `sender != receiver` запрещает self-transfer — иначе атакующий мог бы наращивать account_chain_length каждое окно через no-op переводы себе. После финализации: ``` sender.balance -= amount receiver.balance += amount ``` Никаких proofs, никакой криптографии помимо подписи и хэша. ### Anti-inflation Чеканка из воздуха невозможна через локальный инвариант на каждом state transition. **Per-user-operation invariant.** Каждое применение пользовательской операции обязано удовлетворять `Σ delta_balance == 0`: ``` Transfer: sender.balance -= amount, receiver.balance += amount → Σ = 0 OpenAccount: новый аккаунт с balance = 0 → Σ = 0 ChangeKey: только обновление current_pubkey → Σ = 0 Anchor: только запись data_hash → Σ = 0 ``` **Per-proposal invariant.** Каждый финализированный proposal окна τ₁ обязан удовлетворять `delta_supply == +60 Ɉ`: ``` apply_proposal step 2 (TimeCoin emission): если winner_class = Node: operator_account.balance += 60_000_000_000 nɈ если winner_class = Account: winner_account.balance += 60_000_000_000 nɈ delta_supply за proposal = +60_000_000_000 nɈ ровно один раз ``` O(1) проверка на каждое state transition. Глобальный инвариант `Σ balance == 60 Ɉ × (window_index + 1)` истинен по индукции от genesis при условии что каждый переход поддерживает per-operation invariant. ``` genesis state (аксиома): window_index не определён, supply = 0, Σ balance = 0 первое окно: window_index = 0, supply = 60 Ɉ, Σ balance = 60 Ɉ окно k: window_index = k, supply = 60 × (k+1) Ɉ, Σ balance = 60 × (k+1) Ɉ ``` Никаких откатов cemented операций не требуется — каждое cemented локально валидно по конструкции. **τ₂ sanity check.** Дополнительная проверка раз в τ₂: пересчёт `Σ balance` по всей Account Table и сравнение с `60 Ɉ × (window_index + 1)`. Не load-bearing для финализации — служит для обнаружения багов реализации. Расхождение = немедленная остановка узла, дамп state для расследования. ### Перевод Перевод на несуществующий account_id — отклоняется. Получатель обязан существовать в Account Table до получения перевода. ### TimeCoin Победитель τ₁ записывает прошедшее время: 60 Ɉ (60 зарегистрированных секунд). При финализации proposal окна: ``` если winner_class = Node: operator_account.balance += 60_000_000_000 nɈ если winner_class = Account: winner_account.balance += 60_000_000_000 nɈ ``` Атомарное обновление баланса. Узел получает награду через привязанный operator_account (зафиксирован при NodeRegistration). Никаких отдельных coinbase-структур, никаких отдельных таблиц эмиссии. Зачисление есть состояние Account Table. ``` Публичное (верифицируемо всеми): TimeCoin: 60 Ɉ за окно (константа) Supply audit: supply(window_index) = 60_000_000_000 × (window_index + 1) nɈ Winner: winner_id в proposal header Все балансы: Account Table Все переводы: цепочки операций аккаунтов VDF: TimeChain values, NodeChain endpoints, подписи ``` Псевдонимность на уровне account_id (как Bitcoin). Финансовая приватность — задача приложений: микшеры, payment channels, off-chain settlements. Не задача протокола. ### Двойная трата Каждый аккаунт имеет одну цепочку. Две операции с одним prev_hash = equivocation. **Без конфликта:** операция → узлы валидируют → публикуют confirmation → quorum → финализирована. Cemented — необратимо. **При конфликте (equivocation):** 1. Узел получает операцию X с prev_hash = H. Узел уже видел операцию Y с prev_hash = H, Y ≠ X. Форк обнаружен. Обе операции помечаются как equivocated. 2. Если одна операция уже cemented (quorum до обнаружения конфликта) — cemented необратимо. Вторая отклоняется. 3. Если ни одна не cemented — узлы продолжают собирать confirmations для обеих. Если одна набирает quorum → cemented, вторая отклоняется. 4. Если через 10 окон ни одна не набрала quorum → обе отклоняются окончательно. Аккаунт продолжает с последней cemented операции. Владелец отправляет новую операцию. Equivocation создаётся только владельцем аккаунта (требуется подпись). Третья сторона не может создать equivocation для чужого аккаунта. Стимул: двойная трата = потеря обеих операций. ### Антиспам Ноль комиссий — антиспам через время. Право на операцию = доказанное время существования аккаунта. #### Приоритет операции ``` account_age = current_window - creation_window priority(op) = account_age × windows_since_last_op ``` `account_age` — возраст аккаунта в окнах. Растёт линейно. Некупуемый. `windows_since_last_op` — окна с последней операции аккаунта. Сбрасывается при каждой операции. Спамер обнуляет приоритет с каждой операцией — самонаказание. При переполнении ёмкости сети — операции с наименьшим приоритетом ожидают следующего окна. #### Бакеты по account_age Изоляция спама. Round-robin по бакетам при формировании набора операций: одна операция из бакета 0, одна из бакета 1, ..., по кругу. Спам в бакете 0 не вытесняет операции из бакетов 1-3. ``` Бакет 0: account_age < 4τ₂ 1 операция за τ₁ Бакет 1: account_age 4τ₂ — 16τ₂ 4 операции за τ₁ Бакет 2: account_age 16τ₂ — 64τ₂ 16 операций за τ₁ Бакет 3: account_age 64τ₂+ 64 операции за τ₁ ``` Границы бакетов = 4^N × τ₂. Квота = 4^N операций за τ₁. Одна формула. Новый аккаунт — бакет 0 с момента создания. 1 операция в минуту. Вход без ожидания: получил перевод → сразу можешь отправить. #### Квота Жёсткий потолок операций аккаунта за одно окно τ₁. Превышение квоты → операция невалидна → не ретранслируется. Не приоритизация — запрет. Спамер с 1000 новых аккаунтов: 1000 операций за τ₁ в бакете 0. Бакет 0 получает 1/4 от round-robin. Изолирован. Аккаунты в бакетах 1-3 не замечают. --- ## Состояние сети Глобальное состояние = Account Table + Node Table. ``` Account Table (запись на аккаунт): account_id 32B <- = SHA-256("mt-account" || suite_id || pubkey) balance 16B <- u128 nɈ, открыт suite_id 2B is_node_operator 1B <- 1 если аккаунт привязан как operator узла; исключён из лотереи аккаунтов frontier_hash 32B <- хэш последней операции в цепочке op_height 4B <- количество операций в цепочке account_chain_length 4B <- количество уникальных окон τ₁ с операцией (длина AccountChain), live account_chain_length_snapshot 4B <- snapshot account_chain_length на последнюю τ₂ boundary, используется лотереей current_pubkey 897B <- FN-DSA-512 creation_window 4B <- окно создания аккаунта (OpenAccount) last_op_window 4B <- окно последней операции (для приоритета) human_core_flag 1B <- 1 если член Core Council, 0 иначе ai_council_flag 1B <- 1 если член AI Council, 0 иначе council_role 1B <- 0=обычный, 1=член, 2=Председатель ai_reputation_score 4B <- signed int, репутация (только используется для AI) last_mip_vote_window 4B <- окно последнего MIP-голоса active_mip_id 32B <- хэш активного MIP, 0x00 если нет Constraint: human_core_flag + ai_council_flag <= 1 (mutex ролей) Node Table (запись на узел): node_id 32B <- SHA-256("mt-node" || node_pubkey), верифицируемо node_pubkey 897B suite_id 2B operator_account_id 32B <- account_id куда зачисляется TimeCoin при победе узла; неизменен после регистрации start_window 4B <- окно регистрации (первое окно NodeChain) pending_invite 32B <- node_id приглашённого узла (0x00..00 если нет) invite_window 4B <- окно финализации NodeInvitation (0 если нет) invite_expires 4B <- invite_window + 21 160 (0 если нет) invites_used_epoch 2B <- количество NodeInvitation финализированных в текущей τ₂-эпохе; сбрасывается в 0 на каждой τ₂ boundary ``` ### State Root Merkle-дерево глобального состояния. Оба подкорня обновляются инкрементально на каждое state transition: ``` state_root = SHA-256("mt-state-root" || node_root || account_root) node_root: Merkle root Node Table, обновляется при каждом изменении Node Table (apply control_set, expiry, registration) account_root: Merkle root Account Table, обновляется при каждом cemented state transition аккаунта (Transfer, OpenAccount, ChangeKey, Anchor) Оба root всегда соответствуют live state — никаких frozen snapshots. Порядок node_root → account_root отражает направление зависимостей TimeChain → NodeChain → AccountChain → AccountTable. Domain separator `mt-state-root` отличён от `mt-merkle-node` (используемого для внутренних узлов Merkle tree) — 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 хэшей. Каждый узел в Node Table — участник сети. Узел существует в таблице = участвует. Все sort keys фиксированной длины. Побайтовое лексикографическое сравнение. Две реализации с одинаковыми данными строят одинаковое дерево и получают одинаковый State Root. State Root коммитится в заголовке каждого финализированного proposal τ₁. `account_root` и `node_root` всегда live — соответствуют состоянию Account Table и Node Table на момент сборки proposal. #### Inclusion proof Любой cemented аккаунт может предоставить доказательство существования в state: ``` proof = Merkle path длиной log₂(N) (~30 хэшей для N=10⁹) verify(proof, account_record, account_root): reconstruct path bottom-up; compare с account_root ``` Доказательство верифицируется против `account_root` любого финализированного proposal начиная с окна когда состояние было обновлено. Не нужны архивы операций — текущее состояние самодостаточно. #### Pruning На τ₂ boundary применяется pruning неактивных аккаунтов: ``` Удалить все записи Account Table где: balance == 0 <- нулевой баланс AND last_op_window + 4τ₂ <= current_window <- нет активности 4τ₂ (56 дней) AND is_node_operator == 0 <- не привязан как operator узла AND нет cemented NodeRegistration в control_set <- нет pending привязки ожидающего apply, ссылающегося на этот account_id ``` Пустой аккаунт без активности 56 дней — удаляется, кроме: - Operator-аккаунтов уже зарегистрированных узлов (`is_node_operator == 1`) - Аккаунтов на которые ссылается cemented NodeRegistration ожидающий apply Без второго исключения возможна race: NodeRegistration cemented (operator валиден), pruning применился до apply этого NodeRegistration → аккаунт удалён → apply отклонён. Защита: pruning не трогает аккаунты, на которые есть cemented pending registration. Каждое удаление пересчитывает соответствующий путь в Merkle tree (logarithmic). Pruning детерминирован, автоматичен, каноничен. **Recovery semantics.** Воссоздание pruned аккаунта через новый OpenAccount с тем же ключом создаёт **новую цепочку**: frontier_hash начинается заново, op_height сбрасывается в 1, account_chain_length = 0. Старые prev_hash references на цепочку до pruning отклоняются — цепочка удалена из текущего state. История переводов до pruning не восстанавливается из текущего Account Table, но навсегда сохранена в proposals. Восстановление истории возможно через scan архива proposals. --- ## Двигатели Четыре цепочки с односторонним потоком зависимостей: TimeChain → NodeChain → AccountChain → AccountTable. TimeChain — глобальные часы (ход времени). NodeChain — машинное присутствие узла (непрерывное VDF). AccountChain — человеческое присутствие аккаунта (дискретные операции). AccountTable — состояние счёта. ### TimeChain VDF — осциллятор Первичный продукт протокола. Непрерывная последовательная SHA-256 цепочка — цифровой осциллятор Montana Time: ``` T_r = SHA-256^D(T_{r-1}) ``` D — количество последовательных хэшей за одно окно τ₁. Каждый хэш — один тик осциллятора. D хэшей — одно колебание. TimeChain продвигается по расписанию окон. Для фиксированного индекса r значение T_r совпадает у всех честных узлов. Каждый узел вычисляет TimeChain независимо — результат детерминирован. TimeChain не зависит от состояния, транзакций и поведения отдельных узлов. Даже при отказе всего Account слоя часы продолжают тикать. ### NodeChain — персональная цепочка узла Криптографическое доказательство присутствия конкретного node_id при каждом тике часов. Якорится в TimeChain каждое окно: ``` S_{i,s,0} = SHA-256(S_{i,s-1,m} || T_s || node_id_i) S_{i,s,j+1} = SHA-256(S_{i,s,j}) для j = 0..m-1 ``` Три компонента seed: предыдущий endpoint (непрерывность цепочки), значение TimeChain (протокольное время), node_id (идентичность). m последовательных хэшей за окно — одно звено NodeChain. Инициализация: для первого окна нового узла предыдущий endpoint отсутствует. NodeChain init привязан к каноническим данным proposal в котором NodeInvitation финализирован: ``` S_{i,0,0} = SHA-256("mt-nodechain-init" || control_root || timechain_value || node_id_i) ``` control_root и timechain_value из proposal header окна финализации Invitation. Оба канонические (не зависят от субъективного user_set). Предвычисление VDF невозможно — timechain_value неизвестен до закрытия окна. Grinding surface = ноль. Верифицируем любым узлом. NodeChain зависит от TimeChain. TimeChain не зависит от NodeChain. ### AccountChain — персональная цепочка аккаунта Криптографическое доказательство присутствия конкретного account_id в дискретных моментах. Каждое звено — финализированная операция аккаунта (Transfer, OpenAccount, ChangeKey, Anchor, MIPProposal, MIPVote). Linking через `prev_hash` (хэш предыдущей операции в цепочке аккаунта). Якорится в TimeChain через timechain_value момента финализации каждой операции. Длина AccountChain — количество уникальных окон τ₁ в которых аккаунт имел хотя бы одну финализированную операцию: ``` account_chain_length(account, W) = | { w : w <= W, аккаунт имел >=1 cemented операцию в окне w } | ``` Длина растёт максимум на 1 за окно τ₁. Множественные операции в одном окне дают +1 (один окно — одно приращение). Поле `account_chain_length` хранится в Account Table, обновляется при финализации операции: ``` on_operation_finalized(operation, window W): account = operation.account_id if W != account.last_op_window: account.account_chain_length += 1 account.last_op_window = W account.op_height += 1 ``` **Параллелизм с NodeChain:** | Свойство | NodeChain | AccountChain | |----------|-----------|--------------| | Источник | node_pubkey | account_pubkey | | Идентификатор | node_id | account_id | | Тип присутствия | машинное | человеческое | | Ритм | непрерывный (каждое окно VDF) | дискретный (окно с операцией) | | Длина | chain_length (окна) | account_chain_length (окна) | | Единица длины | окно τ₁ | окно τ₁ | | Накопление | автоматически с каждым окном | через активность пользователя | | Якорь во времени | timechain_value каждое окно | timechain_value окна с операцией | | Защита от подделки | VDF необратим | подпись FN-DSA-512 | | Linking | endpoint предыдущего звена | prev_hash предыдущей операции | | Защита от Sybil | 14 дней VDF + invite slot | накопление окон требует реального времени | Узел доказывает присутствие непрерывной работой машины в каждом окне. Аккаунт доказывает присутствие активным использованием сети — каждая операция фиксирует одно окно человеческого бытия на временной шкале Montana. Оба механизма математически верифицируемы, оба производят запись на одной шкале времени. AccountChain зависит от TimeChain напрямую — каждая операция привязана к timechain_value момента финализации. AccountChain не зависит от NodeChain по построению — цепочка аккаунта существует независимо от того какой узел победил в окне финализации. ### VDF Reveal и лотерея В лотерее участвуют два класса субъектов: **узлы** (через NodeChain) и **аккаунты** (через AccountChain). Каждый класс производит ticket взвешенный по длине своей цепочки. Lowest weighted_ticket из объединённого множества кандидатов выигрывает 60 Ɉ. #### Класс 1: узлы После закрытия окна τ₁ каждый узел вычисляет свой ticket: ``` ticket_node = -ln(endpoint_node / 2^256) weighted_ticket_node = ticket_node / chain_length ``` Если weighted_ticket_node < target — узел кандидат и публикует VDF_Reveal: ``` VDF_Reveal: node_id 32B window_index 4B <- индекс τ₁ endpoint 32B <- S_{i,s,m} start_window 4B <- окно начала NodeChain (для верификации) signature 666B <- FN-DSA-512, подписано node_pubkey Итого: ~738B ``` #### Класс 2: аккаунты Аккаунт автоматически становится кандидатом если у него есть финализированная операция в окне W. Никакой отдельный reveal не публикуется — операция уже cemented в сети, endpoint вычисляется детерминированно любым узлом. ``` operation_for_lottery(account, W) = первая cemented операция аккаунта в окне W по (cemented_window, op_hash) lex order endpoint_account(W) = SHA-256( "mt-account-lottery" || account_id || hash(operation_for_lottery) || timechain_value(W) ) account_length_at_lottery = account.account_chain_length_snapshot ticket_account = -ln(endpoint_account / 2^256) weighted_ticket_account = ticket_account / account_length_at_lottery ``` `account_chain_length_snapshot` обновляется на каждой τ₂ boundary копией текущего `account_chain_length`. Между τ₂ boundaries snapshot frozen — все узлы используют одно значение, лотерея детерминирована. Live `account_chain_length` продолжает расти от cemented операций, но не используется лотереей до следующего τ₂. Если weighted_ticket_account < target — аккаунт кандидат. Аккаунт без операции в окне W не участвует в лотерее этого окна. **Исключение operator-аккаунтов.** Аккаунт с `is_node_operator = 1` исключён из лотереи аккаунтов даже если у него есть операция в окне. Узел получает вес через NodeChain (через свой node_id); operator_account только хранит TimeCoin. Двойной счёт исключён конструкцией. Оператор узла, желающий участвовать в лотерее аккаунтов, использует отдельный персональный аккаунт без NodeRegistration привязки. **Защита от grinding:** operation_for_lottery — первая cemented операция в окне (детерминированно по cemented_window primary, op_hash secondary lex). Множественные операции в одном окне дают только один лотерейный билет. Атакующий не может выбрать «лучшую» операцию через множественные публикации. timechain_value(W) известен только после закрытия окна — endpoint не предсказуем заранее. #### Победитель окна ``` candidates = node_candidates ∪ account_candidates winner = argmin(weighted_ticket(c) for c in candidates) winner получает TIME_RECORD = 60 Ɉ ``` **Если победил узел:** он формирует proposal как обычно (control_set, state_root, подпись node_pubkey). **Если победил аккаунт:** он получает 60 Ɉ TimeCoin, но proposal формирует **узел-кандидат с минимальным weighted_ticket в этом окне**. Этот узел становится proposer без дополнительной награды — это его обязанность как следующего кандидата. Proposal содержит winner_id = account_id победившего аккаунта. **Если в окне нет узлов-кандидатов и есть аккаунты-кандидаты:** победитель выбирается среди аккаунтов. Proposal формирует ближайший узел по weighted_ticket (fallback к узлу с lowest ticket даже если не кандидат), без дополнительной награды. #### Калибровка target Target калиброван на ~12 кандидатов за окно (включая обоих классов). Калибровка на τ₂: ``` target_new = target_old × (12 / actual_candidates_per_window) actual_candidates_per_window = total_candidates_за_τ₂ / 20 160 ``` Трафик reveal за окно: ~12 узловых reveals × 738B ≈ 8.9 KB. Аккаунты не публикуют отдельные reveals — их операции уже в gossip. #### Валидация reveal узла 1. Подпись FN-DSA-512 соответствует node_pubkey из Node Table 2. window_index = только что закрытый τ₁ 3. node_id существует в Node Table 4. weighted_ticket < target 5. endpoint верифицируем: пересчёт NodeChain VDF от предыдущего endpoint #### Валидация участия аккаунта 1. account_id существует в Account Table 2. account_chain_length_snapshot > 0 3. У аккаунта есть хотя бы одна cemented операция в окне W 4. operation_for_lottery определена детерминированно 5. weighted_ticket_account < target ### Account — содержимое блока Приём, верификация объектов и формирование набора. Два класса объектов: **UserObjects** — пользовательские операции: | Тип | Описание | Валидация | |-----|----------|-----------| | Transfer | Публичный перевод | FN-DSA-512 подпись, prev_hash, sender != receiver, amount > 0, sender.balance >= amount, получатель существует | | OpenAccount | Создание аккаунта | FN-DSA-512 подпись, prev_hash = 0, account_id = SHA-256("mt-account" || pubkey) не существует в Account Table | | ChangeKey | Смена ключа | FN-DSA-512 подпись старым ключом, new_pubkey | | Anchor | Якорь данных ко времени | FN-DSA-512 подпись, prev_hash, app_id = 32B, data_hash = 32B | **ControlObjects** — объекты управляющие составом сети: | Тип | Описание | Валидация | |-----|----------|-----------| | NodeInvitation | Приглашение нового узла | FN-DSA-512 подпись пригласившего, pending_invite = 0 | | NodeRegistration | Регистрация узла | FN-DSA-512 подпись, node_id уникален, proof_endpoint верифицируем через VDF, приглашение существует | Каждый узел валидирует объекты обоих классов локально при получении. Валидные объекты ретранслируются по P2P. Все объекты — UserObjects и ControlObjects — финализируются (cemented) одинаково: через 67% online chain_length подтверждения в BundledConfirmation. Cemented status объективен и одинаков для всех узлов. Дискреция победителя над включением ControlObjects = ноль. #### Proposal Proposal содержит **control_set** и метаданные окна. UserObjects применяются к Account Table потокова при cemented; в proposal они не повторяются. ControlObjects применяются к Node Table в apply_proposal step 1 в детерминированном порядке. **control_set(proposal окна W)** определён формулой: ``` control_set = { ControlObject c : c.cemented_window > previous_proposal.window AND c.cemented_window <= W } сортировка: (cemented_window asc, op_hash lex asc) ``` Где `previous_proposal.window` — окно предыдущего финализированного proposal в цепочке. Множество детерминировано: cemented_window — каноническое поле объекта (известно всем узлам через BundledConfirmation), op_hash — детерминирован. Победитель **обязан** включить весь control_set целиком. Пропуск или добавление лишнего ControlObject = невалидный proposal = fallback. Каждый узел независимо вычисляет ожидаемый control_set по той же формуле и сравнивает с proposer's set. Форки аккаунтов (две операции с одним prev_hash) разрешаются голосованием узлов весом chain_length. 67% online chain_length за одну операцию → побеждает (см. раздел «Двойная трата»). #### Дедлайн в окне ``` |-------- τ₁ (60 сек) --------|------- R (12 сек) -------| ^ ^ ^ T_close confirmation reveal_cutoff cutoff (T+R/2) + apply trigger + proposal collect ``` - **T_close** — момент закрытия окна (детерминирован TimeChain advance к T_{W+1}). - **confirmation_cutoff** = T_close + R/2. BundledConfirmation окна W, опубликованные позже, игнорируются. Cemented set окна W становится детерминированным. - **reveal_cutoff** = T_close + R. VDF_Reveal принимается до этого момента. После reveal_cutoff: батч apply всех cemented операций окна W, обновление Account Table и Node Table, сборка proposal с актуальными account_root и node_root. - ControlObjects не имеют отдельного cutoff — они попадают в control_set следующего proposal по моменту cement. R, F — калибруются в τ₂ (см. раздел «Калибровка R, F»). Генезис: R₀ = 12s, F₀ = 12s. После reveal_cutoff: определяется winner лотереи и proposer_node_id. Если winner_class = Node, proposer_node_id == winner_id, и этот узел собирает proposal. Если winner_class = Account, proposer_node_id — ближайший узел-кандидат, он собирает proposal от имени аккаунта-winner. #### Proposer Победитель собирает proposal: - **control_set**: все cemented ControlObjects в окнах (previous_proposal.window, W] (формула выше). Свобода = ноль. - **State Root snapshot**: live account_root и node_root на момент сборки Свобода proposer: ноль. control_set детерминирован формулой. State Root всегда live — каждый узел независимо обновляет Merkle tree при каждом cement, результат идентичен. Proposal с пропущенным cemented ControlObject, добавленным non-cemented ControlObject, неверным порядком или неверным state_root отклоняется, переход ко второму месту. #### Финальность proposal Финальность proposal = подпись proposer_node_id на proposal header (верифицируемая против Node Table[proposer_node_id].node_pubkey) + независимая верифицируемость состояния. 1. Победитель публикует подписанный proposal header + control_set 2. Каждый узел проверяет `window_index == prev_proposal.window_index + 1` и `protocol_version >= prev_proposal.protocol_version` 3. Каждый узел проверяет wall_clock в bounds [expected_window_time(window_index) - 12, expected_window_time(window_index) + 12] 4. Каждый узел независимо вычисляет ожидаемый control_set по формуле и сравнивает с proposer's 5. Каждый узел применяет control_set + TimeCoin детерминированно в порядке (cemented_window asc, op_hash lex asc) 6. Каждый узел сравнивает вычисленный state_root с заявленным в proposal 7. Совпадает — proposal принят 8. Не совпадает — proposal отклонён, fallback на второе место Финальность операций аккаунтов — отдельный процесс через подтверждения (67% online 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 (обновляется каждое окно) account_root 32B <- live Merkle root Account Table на момент сборки proposal state_root 32B <- SHA-256("mt-state-root" || node_root || account_root) timechain_value 32B winner_class 1B <- 1=Node, 2=Account winner_endpoint 32B <- NodeChain endpoint winner-а (если winner_class=1) иначе endpoint_account вычисляемый winner_id 32B <- получатель TimeCoin: node_id (winner_class=1) или account_id (winner_class=2) proposer_node_id 32B <- узел собравший и подписавший proposal; если winner_class=1, proposer_node_id == winner_id; если winner_class=2, proposer_node_id = node-кандидат с минимальным weighted_ticket (или fallback) target 8B <- текущий target лотереи wall_clock 8B <- секунды с генезиса (локальное измерение proposer), валиден в [expected_window_time(window_index) - 12, expected_window_time(window_index) + 12] fallback_depth 1B <- 1 = первое место, 2+ = fallback signature 666B <- FN-DSA-512, подпись header Node Table[proposer_node_id].node_pubkey ``` **Разделение ролей winner_id и proposer_node_id.** Это два независимых поля с разными назначениями: - `winner_id` — получатель TimeCoin. Аккаунт или узел, выигравший лотерею окна. Используется только в apply_proposal step 2 для зачисления 60 Ɉ. - `proposer_node_id` — узел ответственный за сборку и публикацию proposal. Подписывает header своим node_pubkey. Верификация подписи proposal — против `Node Table[proposer_node_id].node_pubkey`, всегда. Когда `winner_class = Account`, winner — это аккаунт без node_pubkey, физически не способный подписать proposal. Подписывает всегда узел-proposer (ближайший по weighted_ticket). TimeCoin при этом получает winner (аккаунт), proposer не получает дополнительной награды. **Инварианты Proposal header:** - `window_index == prev_proposal.window_index + 1` (монотонность, шаг 1) - `protocol_version >= prev_proposal.protocol_version` (не убывает; изменяется только через активацию MIP) - `wall_clock` в bounds [expected_window_time(window_index) − 12, expected_window_time(window_index) + 12] - `wall_clock > prev_proposal.wall_clock` (строгая монотонность) **Cemented window** объекта — `window_index` proposal-а в котором BundledConfirmation с этим объектом достиг quorum. Определён детерминированно для каждого cemented объекта. **Settled window** объекта — `window_index` proposal-а в котором объект был применён к state: - Для UserObjects: `settled_window = cemented_window` (apply batch at window close того же окна) - Для ControlObjects: `settled_window` = window_index первого proposal где объект попал в control_set (обычно `cemented_window + 1`) Fallback: если proposal от назначенного proposer_node_id не получен в пределах timeout (F секунд после reveal_cutoff) или отклонён — proposal формирует следующий узел-кандидат по weighted_ticket. При fallback меняется только **proposer_node_id**; `winner_id` и распределение TimeCoin сохраняются (60 Ɉ всё равно уходит изначальному winner). Новый proposer_node_id подписывает header своим node_pubkey, `fallback_depth` инкрементируется. Молчание первого proposer не лишает winner награды — только переводит обязанность сборки proposal к следующему узлу. Если winner_class = Node и сам winner молчит, он теряет TimeCoin за это окно (winner_id становится node_id нового proposer автоматически; proposer и winner при fallback совпадают только если новый proposer также был node-кандидатом с высоким рангом). F калибруется в τ₂ (см. раздел «Калибровка R, F»). **Симметрия fallback между winner classes:** - `winner_class = Node`, первый proposer (== winner) молчит → новый proposer = следующий узел-кандидат; новый узел становится winner_id, первоначальный winner теряет TimeCoin - `winner_class = Account`, первый proposer (ближайший узел) молчит → новый proposer = следующий узел-кандидат; winner_id остаётся account_id (аккаунт-winner не зависит от молчания proposer — его награда гарантирована если хотя бы один узел в сети соберёт валидный proposal) Это обеспечивает что выигрыш аккаунта в лотерее не может быть отобран задержкой узла-proposer. Пока любой узел публикует валидный proposal с корректным winner_id, аккаунт получает свои 60 Ɉ. #### Непрерывность VDF VDF следующего окна вычисляется непрерывно, не ожидая завершения финализации предыдущего. TimeChain для окна N+1 детерминирован — каждый узел вычисляет его независимо. NodeChain для окна N+1 стартует сразу после закрытия окна N, используя собственный endpoint текущего окна и новое значение TimeChain. Reveal phase и финализация происходят параллельно с началом VDF следующего окна. #### Confirmations (финализация операций и control objects) Confirmers — узлы с chain_length >= confirmation_threshold. Подтверждают **все** валидные объекты окна (UserObjects + ControlObjects) от имени сети. ``` confirmation_threshold = total_chain_length / 100 Пересчитывается на τ₂ boundary из канонических данных Node Table. ~100 confirmers при любом размере сети. ``` Confirmer собирает все валидные объекты за окно (без разделения на классы) и публикует один BundledConfirmation: ``` BundledConfirmation: node_id 32B endpoint 32B <- текущий NodeChain endpoint (доказывает chain_length) window_index 4B op_count 2B op_hashes[] op_count × 32B <- хэши UserObjects и ControlObjects вместе signature 666B ``` Один BundledConfirmation per (node_id, window_index). Повторный отклоняется. Endpoint верифицируем: пересчёт m хэшей от предыдущего известного endpoint данного узла. chain_length(node, W) = |{ w : w ≤ W, узел опубликовал валидный BundledConfirmation в окне w }|. Endpoint BundledConfirmation верифицирует вычисление VDF за соответствующее окно. Объект финализирован (cemented) когда подтверждения от confirmers с суммарным chain_length > quorum. Cemented — необратимо. Типичное время: ~0.3 секунды. Это правило применяется одинаково к UserObjects и ControlObjects: cemented status объективен и каноничен для всех узлов. **Confirmation cutoff (детерминизм cemented set).** Confirmer обязан опубликовать BundledConfirmation окна W до момента `T_W_close + R/2` (половина reveal окна). BundledConfirmation окна W, полученные после этого момента, **игнорируются** при вычислении cemented set окна W. Cutoff делает множество cemented operations окна W объективным и одинаковым у всех узлов: любой узел собирает confirmations за окно W до T_W_close + R/2 → независимо вычисляет один и тот же cemented set. **Dependency rule (детерминизм apply).** Confirmer не подтверждает операцию, зависящую от не-cemented state, с двумя исключениями для chain через bundle: - **Cross-account dependencies:** Transfer на receiver R валиден только если R существует в Account Table на cemented state до окна W. Аккаунт-получатель должен быть cemented в W-1 или раньше. Confirmer не включает в bundle Transfer на R если R был создан в окне W (создание и получение через окно). - **Same-account chain:** операция с prev_hash = H валидна если H == frontier_hash sender по cemented state до окна W, **либо** H = H(предыдущей операции того же sender уже включённой в текущий bundle confirmer-а в окне W). Это позволяет цепочку операций одного аккаунта в одном окне (до квоты бакета): первая операция привязана к cemented frontier, последующие — к предыдущим в bundle. Cross-account зависимости сериализуются через окна — создание аккаунта в окне W, получение перевода в окне W+1. Same-account операции допускаются в одном окне через bundle chain. Apply at window close следует тому же порядку (внутри аккаунта по op_height) и никогда не отвергает cemented операцию. ``` quorum = max(67% × online_chain_length, 50% × total_chain_length) online_chain_length = сумма chain_length confirmers, опубликовавших BundledConfirmation за последние 10 окон total_chain_length = Σ chain_length(node, current_window) для всех узлов в Node Table ``` 67% online — нормальный режим. 50% total — абсолютный минимум (детерминирован, вычисляется из канонических данных Node Table). При online < 50% total — операции не финализируются (сеть приостановлена by design). Трафик confirmations: ~100 bundles × ~4 KB ≈ 400 KB за окно. Стабильно при любом масштабе. Узлы-наблюдатели (chain_length < threshold) получают bundles, верифицируют endpoint и подписи, подсчитывают quorum, применяют cemented операции. Не публикуют confirmations. #### State transition Два параллельных процесса обновления состояния: **Применение операций по window close.** Cemented операции окна W буферизуются до confirmation cutoff (T_W_close + R/2). После cutoff множество cemented операций детерминировано. На момент T_W_close + R (момент сборки proposal) все cemented операции окна W применяются батчем в детерминированном порядке: ``` 1. Группировать cemented operations окна W по sender (account_id) 2. Между аккаунтами: порядок групп по min(op_hash) внутри группы, lex asc 3. Внутри одного аккаунта: операции применяются по chain prev_hash (op_height возрастает, естественная цепочка) ``` Внутри аккаунта порядок задан натуральной chain — каждая следующая операция имеет prev_hash = H(предыдущей). Это допускает множественные операции одного аккаунта в одном окне (до квоты бакета). Между аккаунтами порядок lex по min op_hash группы — детерминирован. Apply каждой операции: ``` Transfer: sender.balance -= amount receiver.balance += amount sender.frontier_hash = H(operation) update_merkle_path(sender) update_merkle_path(receiver) OpenAccount: создать запись в Account Table (balance = 0, pubkey, frontier_hash = H(op)) insert_merkle_leaf(new_account) ChangeKey: account.current_pubkey = new_pubkey account.suite_id = new_suite_id account.frontier_hash = H(operation) update_merkle_path(account) Anchor: записать data_hash в цепочку аккаунта (frontier_hash обновлён) update_merkle_path(account) После каждой операции: account_root = current root. ``` **При apply каждой операции** обновляется AccountChain length signer-аккаунта (подписавшего операцию): ``` on_operation_applied(operation, window W): signer = operation.sender # account_id из payload if W != signer.last_op_window: signer.account_chain_length += 1 ← новое окно присутствия signer.last_op_window = W signer.op_height += 1 # Получатель Transfer не получает обновления chain_length — # пассивное получение не считается активностью. ``` Множественные операции одного аккаунта в одном окне дают только +1 к account_chain_length. Длина измеряется в уникальных окнах, не в количестве операций. **State transition в proposal:** при финализации proposal применяется атомарно: ``` apply_proposal(state, proposal) -> state': Шаг 1: применить control_set в порядке (cemented_window asc, op_hash lex asc). NodeInvitation: записать pending_invite, invite_window и invite_expires в Node Table пригласившего. NodeRegistration: проверить приглашение, создать запись в Node Table. Очистить pending_invite пригласившего. Шаг 2: применить TimeCoin победителя. Если winner_class = 1 (Node): operator_account = Node Table[winner_id].operator_account_id operator_account.balance += 60_000_000_000 nɈ Если winner_class = 2 (Account): Account Table[winner_id].balance += 60_000_000_000 nɈ Proposer (proposer_node_id) формирует proposal без награды. Шаг 3: обработать expiry. Приглашения узлов: все записи Node Table где invite_expires <= current_window и invite_expires > 0 -> очистить pending_invite, invite_window и invite_expires. Шаг 4: node_root и account_root уже отражают все cemented изменения (incremental Merkle update произошёл при каждом state transition). state_root = SHA-256("mt-state-root" || node_root || account_root). ``` Порядок детерминирован. Каждый узел применяет одни и те же шаги и получает один и тот же state_root. AccountTable зависит от TimeChain, NodeChain и AccountChain. Обратных зависимостей нет. С ростом TPS сети дополнительные ядра подключаются для верификации операций. Минимум для валидатора: 3 логических ядра (TimeChain + NodeChain + Account). Один узел = 3 ядра. 50 ядер = 16 узлов. Верификация операций аккаунтов полностью параллелизуется — цепочки аккаунтов независимы. ### Приглашение и регистрация Два уровня входа в сеть. Узлы участвуют в консенсусе — приглашение + 14 дней VDF. Аккаунты держат и переводят средства — создаются явно через OpenAccount, без приглашений. Генезис: 12 узлов в разных локациях (hardcoded, аналог bootstrap nodes в Bitcoin). **Genesis State — аксиома сети.** Не результат операций, не финализация. Начальное состояние, существующее до того как любая операция возможна: ``` Genesis State (до первого окна, supply = 0): Account Table = 12 хардкодированных записей: account_id = SHA-256("mt-account" || suite_id || pubkey_i) balance = 0 suite_id = 0x0001 (FN-DSA-512) is_node_operator = 1 current_pubkey = pubkey_i (founder i) frontier_hash = SHA-256("mt-genesis" || account_id) op_height = 0 account_chain_length = 0 account_chain_length_snapshot = 0 creation_window = 0 (остальные поля = 0) Node Table = 12 хардкодированных записей: node_id = SHA-256("mt-node" || node_pubkey_i) node_pubkey = node_pubkey_i (founder i) suite_id = 0x0001 operator_account_id = account_id_i (i-я запись Account Table) start_window = 0 (остальные поля = 0) Genesis NodeChain init для каждого genesis-узла: nodechain_init_i = SHA-256("mt-nodechain-genesis" || node_id_i) Первое звено NodeChain в окне 0: S_{i,0,0} = nodechain_init_i. Дальнейшие звенья по обычной формуле S_{i,s+1,0} = SHA-256(S_{i,s,m} || T_{s+1} || node_id_i). genesis_account_root = sparse Merkle root над 12 записями Account Table genesis_node_root = sparse Merkle root над 12 записями Node Table genesis_state_root = SHA-256("mt-state-root" || genesis_node_root || genesis_account_root) protocol_params (каноническая сериализация, little-endian, фиксированная длина полей): D₀ (8B) начальная сложность TimeChain VDF m₀ (8B) начальное число хэшей NodeChain за окно R₀ (8B) начальный reveal timeout, секунды F₀ (8B) начальный fallback timeout, секунды τ₁_seconds (8B) длительность окна (60) τ₂_windows (8B) число окон в τ₂ (20 160) wall_clock_tolerance (8B) допустимое отклонение wall_clock от expected (12) genesis_time_unix (8B) 1 736 380 800 (09.01.2026 00:00:00 UTC) timecoin_per_window (16B) 60_000_000_000 nɈ (u128) target₀ (32B) начальный target лотереи confirmation_quorum_num (1B) 67 confirmation_quorum_den (1B) 100 invite_expiry_windows (8B) 21 160 pruning_idle_windows (8B) 80 640 (4τ₂) founders_account_pubkeys (12 × 897B = 10 764 B) founders_node_pubkeys (12 × 897B = 10 764 B) genesis_content_app_id (32B) = SHA-256("mt-app" || "montana") genesis_content_data_hash (32B) хэш манифеста книги Montana v1.0 (хардкодировано) Genesis State Hash = SHA-256(genesis_state_root || protocol_params) ``` 12 пар (pubkey, node_pubkey) основателей публикуются в Genesis Decree вместе с протокольными параметрами и финальным Genesis State Hash. Genesis Decree immutable — не может быть изменён через MIP. `genesis_content_data_hash` может быть обновлён только через MIP при публикации новой версии книги Montana. Первое окно τ₁ после генезиса — window_index = 0, protocol_version = 1. Один из 12 узлов выигрывает лотерею → его operator_account.balance += 60_000_000_000 nɈ → supply = 60 Ɉ. Per-operation invariant начинает действовать с этого момента. **Mandatory content replication.** Каждый узел Montana обязан хранить текущую версию книги Montana как persistent blob по (genesis_content_app_id, genesis_content_data_hash). При Fast Sync новый узел загружает genesis content как часть обязательной начальной синхронизации (см. раздел Fast Sync). #### Приглашение узла (NodeInvitation) Вход узла в консенсус. Приглашение + 14 дней VDF + регистрация. Квота приглашений определяется chain_length бакетом пригласившего: ``` Бакет 0: chain_length 1-4 τ₂ 1 приглашение за τ₂ Бакет 1: chain_length 4-16 τ₂ 4 приглашения за τ₂ Бакет 2: chain_length 16-64 τ₂ 16 приглашений за τ₂ Бакет 3: chain_length 64+ τ₂ 64 приглашения за τ₂ ``` Границы бакетов = 4^N × τ₂. Квота = 4^N приглашений за τ₂. Та же формула что и для антиспам аккаунтов. ``` NodeInvitation: type 1B <- 0x10 NodeInvitation inviter_node_id 32B invited_pubkey 897B <- публичный ключ приглашённого узла signature 666B <- подписано inviter node_pubkey Итого: ~1 596B ``` NodeInvitation — ControlObject. Не содержит start_window — определяется при финализации. Валидация: 1. Подпись валидна для inviter node_pubkey из Node Table 2. inviter существует в Node Table 3. inviter invites_used_epoch < квота бакета по chain_length 4. invited node_id = SHA-256("mt-node" || invited_pubkey) не существует в Node Table **Жизненный цикл NodeInvitation:** 1. Опубликована в окне W₀ 2. Cemented в окне W_c через 67% online chain_length confirmations (~0.3 сек) 3. Включена в control_set proposal окна W_p ≥ W_c (первый proposal после cement) 4. Применена в apply_proposal step 1 окна W_p При apply (proposal P окна W_p): - inviter pending_invite = invited node_id - inviter invite_window = W_p (окно apply, не окно cement) - inviter invite_expires = W_p + 21 160 (20 160 окон + 1 000 окон запас) #### Привязка NodeChain к моменту приглашения Первое звено NodeChain приглашённого узла привязано к каноническим полям proposal в котором NodeInvitation финализирован: ``` nodechain_init = SHA-256("mt-nodechain-init" || control_root || timechain_value || node_id) ``` control_root и timechain_value — канонические поля из proposal header окна финализации. Не зависят от субъективного user_set победителя. Предвычисление VDF невозможно: timechain_value неизвестен до закрытия окна. Приглашённый узел узнаёт control_root и timechain_value только увидев финализированный proposal → вычисляет nodechain_init → начинает NodeChain с окна W+1. #### Регистрация узла Приглашённый узел после финализации NodeInvitation: 1. Наблюдает proposal с NodeInvitation → получает control_root и timechain_value 2. Вычисляет nodechain_init = SHA-256("mt-nodechain-init" || control_root || timechain_value || node_id) 3. Непрерывно строит NodeChain: 20 160 окон подряд (от W+1 до W+20 160), каждое звено якорится в соответствующий TimeChain 4. Через ~14 дней получает proof_endpoint = S_{i,20159,m} 5. Публикует NodeRegistration ``` NodeRegistration: type 1B <- 0x11 NodeRegistration suite_id 2B node_pubkey 897B <- FN-DSA-512 ключ узла inviter_node_id 32B <- кто пригласил operator_account_id 32B <- account_id оператора, должен существовать в Account Table proof_endpoint 32B <- S_{i,20159,m} (endpoint после 20 160 окон VDF) signature 666B <- подписано node_pubkey Итого: ~1 662 B ``` NodeRegistration — ControlObject. Валидация NodeRegistration: 1. Подпись FN-DSA-512 валидна для node_pubkey 2. node_id уникален (не существует в Node Table) 3. inviter_node_id существует в Node Table, pending_invite = node_id 4. invite_window + 20 160 < текущее окно (VDF завершён) 5. operator_account_id существует в Account Table и `is_node_operator == 0` (ещё не привязан к другому узлу) 6. Восстановить control_root и timechain_value из proposal окна invite_window 7. Вычислить nodechain_init = SHA-256("mt-nodechain-init" || control_root || timechain_value || node_id) из proposal окна invite_window 8. proof_endpoint верифицируем: пересчёт VDF от nodechain_init через 20 160 окон с якорением в TimeChain значения от invite_window+1 Верификация: 20 160 сегментов VDF проверяются параллельно. На C ядрах: ~(20 160/C) × t_segment. **Жизненный цикл NodeRegistration:** 1. Опубликована в окне W₀ (после 20 160 окон VDF от приглашения) 2. Cemented в окне W_c через 67% online chain_length confirmations 3. Включена в control_set proposal окна W_p ≥ W_c 4. Применена в apply_proposal step 1 окна W_p При apply: если `Account Table[operator_account_id].is_node_operator == 1` на момент применения — отклонить (другая NodeRegistration с тем же operator_account была применена раньше в порядке (cemented_window, op_hash)). Иначе: создать запись в Node Table (start_window = W_p, operator_account_id зафиксирован), установить `is_node_operator = 1` у operator-аккаунта, очистить pending_invite, invite_window и invite_expires у пригласившего. #### Истечение приглашения узла Если NodeRegistration не финализирован до invite_expires (invite_window + 21 160) — приглашённый не завершил VDF. При обработке state transition: pending_invite, invite_window, invite_expires пригласившего очищаются автоматически. Узел может приглашать снова. #### Создание аккаунта Аккаунт не требует приглашений. Пользователь генерирует FN-DSA-512 keypair → вычисляет account_id = SHA-256("mt-account" || suite_id || pubkey) → публикует OpenAccount → запись появляется в Account Table при финализации. Адрес существует математически до публикации, но операции к нему отклоняются до OpenAccount. Sybil-барьер для аккаунтов: account_age (возраст аккаунта) определяет квоту операций. Новый аккаунт — бакет 0, максимум 1 операция за τ₁. Рост квоты = время. Пустые аккаунты бесполезны — без баланса ничего не делают. #### Скорость роста сети Узлы: квота приглашений определяется chain_length бакетом (4^N за τ₂). Рост ограничен зрелостью сети: ``` Генезис (12 узлов, бакет 0): 12 × 1 = 12 приглашений за τ₂ После 4τ₂ (бакет 1): 12 × 4 = 48 приглашений за τ₂ После 16τ₂ (бакет 2): 12 × 16 = 192 приглашения за τ₂ После 64τ₂ (бакет 3): 12 × 64 = 768 приглашений за τ₂ ``` Со временем барьер приглашений размывается: старые узлы приглашают больше, сеть растёт быстрее. Каждый приглашённый узел проходит 14 дней VDF независимо от квоты пригласившего. Аккаунты: без ограничений. Любой владелец TimeCoin может создать аккаунт любому, переведя средства на новый адрес. Рост пользовательской базы не ограничен протоколом. --- ## Потоковая модель Операции аккаунтов текут непрерывно. Узел получает операцию → проверяет подпись FN-DSA-512 и баланс → передаёт в P2P gossip. Confirmers (~100 узлов с наибольшим chain_length) собирают операции за окно и публикуют BundledConfirmation. Операция финализируется при получении подтверждений от узлов с суммарным chain_length > 67% online chain_length. Типичное время: ~0.3 секунды. Cemented — необратимо. Два параллельных процесса: - **Операции** финализируются непрерывно через подтверждения (не ждут окна) - **Часы** тикают по расписанию окон τ₁ (TimeChain, NodeChain, лотерея, TimeCoin) Кошелёк получателя отображает входящий перевод после финализации (~0.3 секунды). Одно состояние: «финализирован». Цепочки аккаунтов полностью независимы. Операции разных аккаунтов обрабатываются параллельно без конфликтов. --- ## Временные слои (τ) ``` τ₁ (60с) → τ₂ (20 160 × τ₁ ≈ 14 дней) ``` Одно окно — τ₁. Всё остальное — производные. ### τ₁ — Окно (60 секунд) Единственная единица протокольного времени. Регистрация времени и эмиссия. - TimeChain продвигается на D хэшей - NodeChain продвигается на m хэшей с якорем в текущем T_s - Операции аккаунтов финализируются непрерывно через подтверждения (параллельно с окном) - control_set: все cemented ControlObjects из окон (previous_proposal.window, current_window] (каноничен) - Кандидаты (~12) раскрывают NodeChain endpoint (reveal phase, R = 12 секунд) - Лотерея: `ticket_i = -ln(endpoint_i / 2^256)`, победитель = lowest ticket среди кандидатов - Победитель публикует подписанный proposal - Финальность proposal: подпись proposer_node_id на proposal header. Каждый валидатор применяет control_set + TimeCoin детерминированно и проверяет state_root - TimeCoin: запись прошедшего времени (60 Ɉ = 60 секунд) → победителю - Supply audit: суммарная эмиссия TimeCoin от генезиса сверяется с supply(window_index) из issuance schedule - Разрешение форков: приоритет ветки с наибольшим суммарным TimeChain-доказательством TimeChain safety: компрометация значения TimeChain требует нарушения свойства последовательности SHA-256 VDF. TimeChain liveness: задержка продвижения TimeChain невозможна — TimeChain вычисляется каждым узлом независимо. ### τ₂ — Адаптация (20 160 × τ₁ ≈ 14 дней) - Калибровка D и m (см. ниже) - Калибровка R, F (см. ниже) - Snapshot account_chain_length: для каждого аккаунта `account_chain_length_snapshot = account_chain_length`. Snapshot используется лотереей аккаунтов в течение следующего τ₂. Детерминированно для всех узлов - Pruning: удаление пустых аккаунтов без активности 4τ₂ (56 дней) с обновлением Merkle путей - Supply audit (sanity check): Σ balance(account) для всех аккаунтов = 60_000_000_000 × (window_index + 1) nɈ - Криптографическая амнезия: подписанные proposals сохраняются навсегда — верифицируемая цепочка state commitments. Proposals доказывают что конкретное состояние было закоммичено proposer-узлом; восстановление содержимого состояния требует snapshot или архива - Пересчёт параметров размера окна τ₁ #### Калибровка D и m Каждый proposal содержит `wall_clock` — секунды с генезиса по локальным часам proposer_node_id. Поле зажато детерминированными bounds: ``` expected_window_time(window_index) = genesis_time_unix + (window_index + 1) × τ₁_seconds = 1 736 380 800 + (window_index + 1) × 60 (genesis 09.01.2026 00:00:00 UTC) wall_clock_tolerance = 12 секунд (фиксировано в Genesis Decree) Валидация wall_clock при получении proposal: expected = expected_window_time(proposal.window_index) expected - 12 <= wall_clock <= expected + 12 (зажат в ±12 сек от ожидаемого) wall_clock > prev_proposal.wall_clock (монотонность) ``` Любой proposal с wall_clock вне bounds — отклоняется. Каждый узел независимо вычисляет expected_window_time из канонической формулы (window_index известен из proposal header) → объективная проверка без mempool/network состояния. Proposer имеет ~24 секунды свободы — недостаточно чтобы сдвинуть медиану 20 160 значений за τ₂ значимо. Медиана 20 160 wall_clocks за τ₂ — распределённый эталон скорости сети. Атака на медиану ограничена: при доле побед p максимальный сдвиг ~p × 12 сек, что меньше нормальной hardware drift volatility. Формула пересчёта D на границе τ₂: ``` intervals[i] = proposal[i].wall_clock - proposal[i-1].wall_clock для i = 1..20 159 actual_interval = median(intervals) target_interval = 60 D_new = D_old × target_interval / actual_interval D_new = clamp(D_new, D_old × 0.5, D_old × 1.5) ``` Формула точная — D корректируется ровно пропорционально отклонению. Страховочный clamp ±50% защищает от чёрного лебедя (массовый выход узлов, аппаратная революция). При нормальной работе отклонение медианы от 60 секунд — единицы секунд, коррекция D — единицы процентов. Медиана > 60 → окна медленнее цели → D слишком велик → уменьшить. Медиана < 60 → окна быстрее → D увеличить. m калибруется пропорционально: `m_new = m_old × (D_new / D_old)`. Генезис: D₀ и m₀ калибруются при запуске для целевых 60 секунд. Абсолютный якорь: 09.01.2026 00:00:00 UTC. Медиана 20 159 интервалов — для сдвига необходим контроль >50% proposals (>50% веса сети). #### Калибровка R, F Входные данные — из proposals за τ₂ (канонические, детерминированно вычисляются всеми узлами): ``` fallback_ratio = count(fallback_depth > 1) / 20160 ``` Формула пересчёта: ``` R_new = clamp(R_old × fallback_ratio / fallback_target, R_old × 0.8, R_old × 1.2) R_new = clamp(R_new, R_min, R_max) F_new = clamp(F_old × fallback_ratio / fallback_target, F_old × 0.8, F_old × 1.2) F_new = clamp(F_new, F_min, F_max) ``` Протокольные константы: ``` fallback_target = 5% R_min = 4s, R_max = 30s F_min = 4s, F_max = 30s ``` Генезис: R₀ = 12s, F₀ = 12s. --- ## Консенсус — Proof of Time (PoT) ### Четыре цепочки **TimeChain** — глобальные часы. Чистая VDF-цепочка `T_r = SHA-256^D(T_{r-1})`. Первичный продукт протокола. Источник времени и случайности. Продвигается по расписанию окон. **NodeChain** — персональная цепочка узла. VDF-цепочка конкретного node_id, якорится в TimeChain каждое окно. Доказывает непрерывную работу узла. **Account** — состояние счёта. Операции финализируются непрерывно через подтверждения (67% online chain_length). ControlObjects включаются в proposal (каноничен). Зависимости односторонние: TimeChain → NodeChain → AccountChain → AccountTable. Отказ в AccountTable не останавливает часы. Отказ конкретного узла в NodeChain не заражает общий ритм. ### Лотерея Лотерея объединяет два класса участников: узлы (через NodeChain) и аккаунты (через AccountChain). Каждый класс производит weighted ticket по длине своей цепочки. Lowest weighted_ticket из объединённого множества побеждает. **Узлы** автоматически участвуют в каждом окне: ``` ticket_node = -ln(endpoint_node / 2^256) weighted_ticket_node = ticket_node / chain_length ``` **Аккаунты** участвуют в окне с финализированной операцией: ``` ticket_account = -ln(endpoint_account / 2^256) weighted_ticket_account = ticket_account / account_chain_length ``` `account_chain_length_snapshot` обновляется на τ₂ boundary, frozen до следующей τ₂ boundary. Лотерея использует snapshot — детерминированно для всех узлов. Если weighted_ticket < target — субъект кандидат. Target калиброван на ~12 кандидатов за окно (включая оба класса). Из кандидатов побеждает lowest weighted_ticket. **Стимул узла:** каждое окно с опубликованным BundledConfirmation увеличивает chain_length → увеличивает шанс победы. Пропущенное окно — это окно не входит в chain_length. Узел остаётся в Node Table и продолжает участвовать. **Стимул аккаунта:** каждое окно с операцией увеличивает account_chain_length → реальный (хоть и редкий) шанс выиграть 60 Ɉ за активность в Montana. ### Победитель τ₁ Победитель определяется после закрытия окна τ₁. Lowest weighted_ticket из всех кандидатов (узлов и аккаунтов) = победитель. **Если победил узел:** - Записывает TimeChain value - Operator account узла получает 60 Ɉ TimeCoin - Коммитит State Root - Формирует proposal (control_set + State Root + TimeCoin), подписывает node_pubkey **Если победил аккаунт:** - Аккаунт получает 60 Ɉ TimeCoin (winner_account.balance += 60_000_000_000 nɈ) - Proposal формирует **узел-кандидат с минимальным weighted_ticket в этом окне** (proposer_node) - Если в окне нет узлов-кандидатов — proposer выбирается из всех узлов с lowest weighted_ticket (fallback) - Proposer не получает дополнительной награды — это его обязанность как ближайшего узла Финальность proposal — подпись proposer_node_id на proposal header. Верификация — независимый пересчёт state_root. ### Верификация Победитель публикует: `{node_id, NodeChain endpoint, proposal}`. Верификация NodeChain за одно окно: пересчёт m хэшей. Параллелизация по сегментам — время верификации обратно пропорционально числу ядер. Верификация proposal: независимое применение control_set + TimeCoin и сравнение state_root. ### Устойчивость - **Остановка часов** исключена: каждый узел тикает независимо - **Искажение часов** исключено: VDF последователен, результат детерминирован - **Proposer grinding** исключён: control_set каноничен, state transition детерминирован, операции финализируются независимо от победителя - **Front-running** исключён: операции финализируются через подтверждения (~0.3s), не через proposal proposer-а - **Предвычисление** исключено: seed содержит текущее значение TimeChain - **Replay** исключён: TimeChain уникален для каждого τ₁ - **Аппаратное преимущество** ограничено: последовательное хэширование масштабируется тактовой частотой, не количеством ядер - **Sybil-барьер**: приглашение (квота по chain_length бакету, 4^N за τ₂) + 14 дней VDF + 3 ядра на узел + weighted_ticket в лотерее - **Цензура операций** исключена: операции финализируются через подтверждения узлов, не через победителя - **Цензура ControlObjects** исключена: control_set каноничен, пропуск = fallback - **Liveness halt операций** исключён: финализация через 67% online chain_length, не зависит от победителя - **Liveness halt proposals** исключён: fallback на следующего кандидата - **Масштабирование**: трафик лотереи ~8.9 KB за окно при любом количестве узлов ### Разрешение конфликтов **Двойная операция аккаунта** (две операции с одним prev_hash): equivocation. Cemented до обнаружения — необратимо, вторая отклоняется. Не cemented — ожидание quorum 10 окон, затем обе отклоняются. См. раздел «Двойная трата». **Невалидный proposal**: валидаторы отклоняют, fallback на следующего кандидата. Победитель теряет TimeCoin за это окно. **Два proposal от одного proposer_node_id в одном окне**: оба отклоняются (equivocation), fallback к следующему узлу-кандидату. Если этот узел был winner (winner_class=1), он теряет TimeCoin. --- ## Адреса и переводы ### Полный флоу перевода ``` 1. Боб: OpenAccount → account_id зарегистрирован в Account Table (balance = 0) 2. Боб → Алисе: "отправь на mt4ZGfe..." (account_id Боба) 3. Алиса формирует Transfer в своей цепочке: type: 0x02 prev_hash: хэш её предыдущей операции payload: sender (account_id Алисы) || link (account_id Боба) || amount (50_000_000_000 nɈ) 4. Алиса подписывает FN-DSA-512 5. Алиса рассылает операцию узлам сети 6. Каждый узел проверяет: FN-DSA-512 подпись валидна для current_pubkey Алисы prev_hash совпадает с frontier_hash Алисы amount > 0 alice.balance >= amount получатель (Боб) существует в Account Table 7. Узлы публикуют confirmations, операция распространяется через P2P gossip 8. 67% online chain_length подтвердили → финализирована (~0.3 секунды) alice.balance -= 50 Ɉ bob.balance += 50 Ɉ alice.frontier_hash = H(operation) alice.op_height += 1 ``` ### Баланс Баланс аккаунта — открытое число `u128 nɈ` в Account Table. Обновляется при финализации арифметически: исходящий Transfer вычитает amount, входящий зачисляет. Видим всем узлам и через любого верификатора цепочки. Бэкап = seed (для деривации приватного ключа FN-DSA-512). Восстановление кошелька: ключ выводится из seed, баланс читается из текущего Account Table — никакого локального состояния не требуется. --- ## Эмиссия ### Единица Монета: **TimeCoin** (тикер: $TimeCoin, символ: Ɉ). 1 секунда = 1 TimeCoin = 1 Ɉ = 1 000 mɈ = 1 000 000 μɈ = 1 000 000 000 nɈ 60 секунд = 1 минута = 60 TimeCoin 3 600 секунд = 1 час = 3 600 TimeCoin 86 400 секунд = 1 день = 86 400 TimeCoin Одно окно τ₁ регистрирует 60 прошедших секунд = 60 TimeCoin. Точность: 9 знаков после запятой (наносекунда). Все расчёты эмиссии в nɈ (целочисленная арифметика, без плавающей точки). ### Issuance schedule Одна секунда протокольного времени порождает одну монету. С первого блока и навсегда. | Параметр | Значение | |----------|----------| | Генезис | 09.01.2026 00:00:00 UTC | | TIME_RECORD | 60 000 000 000 nɈ (60 Ɉ) | ### Регистрация времени ``` time_record(window_index) = 60_000_000_000 nɈ ``` Каждое окно τ₁ регистрирует 60 прошедших секунд = 60 Ɉ. Без халвингов, без фаз, без исключений. Одна константа на весь горизонт существования протокола. ### Supply audit ``` supply(window_index) = 60_000_000_000 × (window_index + 1) nɈ ``` Одно умножение. Проверяемо каждым узлом в каждом τ₁. O(1). ### Инфляция Supply растёт линейно. Инфляция снижается асимптотически к нулю — константная эмиссия делится на растущий supply: ``` Год 1: 100% Год 2: 50% Год 5: 20% Год 10: 10% Год 50: 2% Год 100: 1% Год 1000: 0.1% ``` ### Раннее участие Эмиссия постоянна: 60 TimeCoin за каждое окно, с первого блока и навсегда. Вероятность победы пропорциональна весу. Узел, работающий дольше, побеждает чаще. Два узла запустившиеся одновременно имеют равные шансы независимо от капитала. Узел запустившийся раньше имеет преимущество — он доказал больше времени. Стимул для ранних участников встроен в арифметику: не бонусы, не множители — просто больший вес. ### Распределение Победитель окна τ₁ — узел или аккаунт — записывает прошедшее время и получает 60 Ɉ TimeCoin в своей цепочке. Одно правило. Неизменно с генезиса. Узлы и аккаунты конкурируют в единой лотерее. Узлы доминируют статистически из-за непрерывного присутствия — chain_length растёт каждое окно, weighted_ticket систематически ниже. Аккаунты получают долю эмиссии пропорционально своей активности — account_chain_length растёт с каждым окном с операцией. Время — единственный арбитр. Базовый бюджет: 60 Ɉ/τ₁ (запись 60 секунд). Реальный бюджет безопасности в покупательной способности зависит от рынка. 1 TimeCoin = 1 секунда описывает скорость хода часов. Не ценовой peg, не гарантия покупательной способности. #### Двигатель роста сети Участие аккаунтов в лотерее создаёт flywheel роста сети: ``` Активные пользователи в приложениях → AccountChain растёт → шансы в лотерее ↓ ↓ Приложения привлекают пользователей Иногда выигрывают TimeCoin ↓ ↓ Разработчики хотят пользователей Дополнительная мотивация активности ↓ ↓ Разработчики запускают узлы Montana Больше операций в сети ↓ ↓ Узлы зарабатывают TimeCoin Сеть растёт и децентрализуется ↓ ↓ Финансирование развития инфраструктуры → больше пропускной способности → лучше для приложений ``` Эмиссия 60 Ɉ за окно одна и та же, но финансирует обе стороны одновременно: узлы (поддержание сети) и активные пользователи (использование сети). Они не конкурируют — они взаимно усиливают друг друга. Это циркулярная экономика которой нет в Bitcoin. ### Reference Time Ratio Reference Time Ratio (RTR) — эталонный коэффициент клиентского слоя, выражающий один BTC в единицах времени Montana через отношение эмиссионных правил Bitcoin и Montana. RTR — не рыночная цена, не обменный курс и не рекомендация для торговли. RTR измеряет геометрию эмиссии, а не спрос, ликвидность или полезность актива. #### Область действия RTR существует только на клиентском слое. - Не входит в state machine - Не записывается в proposal header - Не используется в консенсусе - Не требует oracle, биржи или внешнего API Узлы Montana выполняют консенсус без обращения к RTR. #### Формула Пусть n — индекс halving-эпохи Bitcoin (n = 0 для периода 2009-2012). ``` rtr_btc(n) = 12 × 2^n [Ɉ / BTC] ``` Вывод: ``` block_interval_btc = 600 секунд block_reward_btc(n) = 50 / 2^n BTC rtr_btc(n) = block_interval_btc / block_reward_btc(n) = 600 / (50 / 2^n) = 12 × 2^n ``` Поскольку 1 Ɉ = 1 секунда протокольного времени Montana, коэффициент напрямую выражается в Montana-секундах на 1 BTC. #### Интерпретация RTR отвечает на вопрос: сколько единиц времени Montana соответствует одному BTC в текущей subsidy-эпохе Bitcoin. Это не оценка рыночной стоимости BTC. Это протокольное отношение двух эмиссионных кривых. Bitcoin уменьшает выпуск геометрически через halving. Montana добавляет время линейно: 60 Ɉ за окно. Из-за этого rtr_btc(n) удваивается на каждом halving Bitcoin. #### Halving-эпохи | n | Период (ориентир) | Block reward | RTR (Ɉ/BTC) | |---|-------------------|--------------|-------------| | 0 | 2009 — 2012 | 50 BTC | 12 | | 1 | 2012 — 2016 | 25 BTC | 24 | | 2 | 2016 — 2020 | 12.5 BTC | 48 | | 3 | 2020 — 2024 | 6.25 BTC | 96 | | 4 | 2024 — 2028 | 3.125 BTC | 192 | | 5 | 2028 — 2032 | 1.5625 BTC | 384 | | 6 | 2032 — 2036 | 0.78125 BTC | 768 | | 7 | 2036 — 2040 | 0.390625 BTC | 1 536 | | 8 | 2040 — 2044 | 0.1953125 BTC | 3 072 | Границы эпох определяются Bitcoin block height: каждые 210 000 блоков. Даты — ориентировочные. #### Свойства - **Детерминизм.** Любой клиент получает одно и то же значение из Bitcoin block height. - **Независимость от рынка.** Биржи, order book и внешние цены не участвуют. - **Независимость от политики.** Значение не меняется голосованием Montana. - **Историческая верифицируемость.** RTR для любой эпохи Bitcoin вычислим ретроспективно. - **Простота.** Одна формула, без oracle и без внешней инфраструктуры. #### Ограничения RTR не измеряет: - рыночную цену BTC - справедливую стоимость BTC - ликвидность - волатильность - комиссионный рынок Bitcoin - спрос на Montana или Bitcoin RTR корректно интерпретировать как reference time ratio, а не как price. #### Использование RTR может использоваться кошельками и приложениями для: - отображения BTC в шкале времени Montana - исторического сравнения halving-эпох - UI-индикатора эмиссионного отношения Bitcoin и Montana - образовательного слоя, объясняющего различие двух эмиссионных кривых --- ## Пропускная способность Размер Transfer: ~779 B (открытый перевод, FN-DSA-512 подпись). | Канал узла | TPS | |-----------|-----| | 10 Mbps | ~1 600 | | 100 Mbps | ~16 000 | | 1 Gbps | ~160 000 | --- ## Хранение ### Состояния операции (UX) Операция проходит два различимых состояния: ``` publish ──→ cement (~0.3 сек) ──→ settle (≤ 60 сек) "confirmed" "settled" ``` - **Cemented (~0.3 сек):** 67% online chain_length подтвердили операцию через BundledConfirmation. Операция необратима и гарантированно будет применена в конце окна. Wallet показывает «confirmed». - **Settled (≤ 60 сек, в конце окна):** все cemented операции окна применены батчем к Account Table в детерминированном порядке. account_root зафиксирован в proposal. Wallet показывает «settled». Между cement и settle операция уже необратима — настройка двух UI-состояний нужна только для индикации завершённости state transition. Зависимые операции (Transfer на только что созданный аккаунт) сериализуются по окнам через confirmer dependency rule, поэтому cemented операция гарантированно settle-ится. ### Модель: глобальное состояние + локальная история Узлы хранят глобальное состояние (Account Table, Node Table, proposals). Тела операций аккаунтов хранятся у владельцев. После финализации state transition применён — балансы в таблице обновлены, тело операции сети больше не нужно. ### Три уровня участников **Узел (валидатор)** — десктоп или сервер, 24/7, минимум 3 ядра (1 узел = 3 ядра, 50 ядер = 16 узлов): ``` Хранит: Account Table (account_id, balance, frontier_hash, pubkey) + persistent sparse Merkle tree (account_root всегда live) Node Table (node_id, pubkey, start_window, invites) + persistent sparse Merkle tree (node_root всегда live) Proposals (навсегда) Blob Buffer (ephemeral) (TTL = τ₂, для кратковременных сообщений) Persistent Blob Storage (TTL = 0, контент app_id на которые узел подписан) Genesis Content (книга Montana, mandatory replication) Persistent Blob Index ((app_id, data_hash) → blob, с флагом is_manifest) Делает: TimeChain VDF (1 ядро, 24/7) NodeChain VDF (1 ядро, 24/7) Валидация операций (1+ ядро) P2P gossip (операции, confirmations, reveals, proposals) Почтовый ящик (хранит сообщения для своего владельца пока тот офлайн) Content replication (DHT provide, gossip announce, serving ContentRequest/ChunkRequest) Chunk verification (SHA-256 + Merkle reconstruction для полученных чанков) ``` **Кошелёк (клиент)** — телефон, онлайн когда используется: ``` Хранит: Свои ключи (seed → keypairs, включая encryption key для application layer) Свои контакты (адресная книга: имя → mt-адрес, с локальным override) Локальная история (своя цепочка операций для UX) Сообщения (локальная история переписки, messenger session states) Timestamp proofs (Anchor + BundledConfirmations + proposal headers, локально) Подписки (app_id каналов, книги, профилей на которые подписан) Реплики контента (persistent blobs подписанных app_id по желанию) Делает: Отправка/получение переводов Мессенджер (P2P напрямую через libp2p) Discovery (через application layer) Запрос pubkey и proposals у узлов сети ``` **Доверенный узел** — узел друга, семьи, сообщества: ``` Делает: Всё что узел + хранит Blob Buffer для привязанных аккаунтов Владелец аккаунта привязывает свой account_id к доверенному узлу Узел хранит зашифрованные сообщения (содержимое скрыто) Владелец забирает сообщения когда появляется онлайн ``` ### Размеры | Участник | Данные | Размер | |----------|--------|--------| | Узел (1M аккаунтов) | Account Table + Node Table + Proposals | ~2 GB | | Узел (10M аккаунтов) | Account Table + Node Table + Proposals | ~11 GB | | Узел (100M аккаунтов) | Account Table + Node Table + Proposals | ~101 GB | | Кошелёк (обычный) | 100 операций/год + контакты + сообщения | ~1 MB | | Кошелёк (активный) | 10 000 операций/год | ~16 MB | | Корпорация | 1M Anchor/год | ~0.8 GB | ### Потеря данных клиента Потеря устройства: баланс в Account Table цел и публичен, seed восстанавливает ключи, доступ к аккаунту полностью восстанавливается. Локальная история переводов и сообщений утрачена — но баланс читается из Account Table напрямую. Если есть доверенный узел — зашифрованные сообщения можно восстановить. ### Fast Sync (новый узел) 1. Цепочка proposals от генезиса — проверка TimeChain-цепочки и подписей proposer-узлов (мегабайты) 2. Snapshot Account Table + Node Table от пиров на момент окна W (произвольное недавнее окно) 3. Reconstructed account_root и node_root сравниваются с account_root и node_root из proposal окна W. Совпадает — snapshot валиден. 4. Catch-up cemented операций после окна W до текущего: - Запросить cemented операции от пиров - Для каждой: проверить подпись FN-DSA-512, sender.frontier_hash == prev_hash, balance >= amount (для Transfer) - Применить incremental update Merkle tree - На каждом промежуточном proposal сверять локальный account_root с заявленным в proposal header 5. **Mandatory genesis content replication.** Загрузить книгу Montana через Content Layer: - ContentRequest(genesis_content_app_id, genesis_content_data_hash) → manifest - Для каждого чанка: ChunkRequest + верификация SHA-256 - Пересчёт Merkle root манифеста → сравнение с genesis_content_data_hash - Без успешной загрузки genesis content Fast Sync считается неполным 6. Узел синхронизирован и готов к участию Поскольку account_root всегда live, snapshot можно делать с любого окна — нет окон ожидания τ₂ boundary. Catch-up дистанция определяется свежестью snapshot, обычно минуты. --- ## Application Layer Montana — цифровой стандарт времени. Приложения управляют своим состоянием самостоятельно (серверы, базы данных, P2P). Montana хранит только криптографические отпечатки с привязкой ко времени — 32 байта на запись. ### Модель приложения на Montana Приложение Montana — это набор узлов с интерфейсом. Создатель приложения запускает узлы Montana (обычные узлы, тикающие VDF, валидирующие операции, участвующие в консенсусе). Узлы зарабатывают TimeCoin за поддержание сети через лотерею. **Для создателя приложения:** - Не нужно строить отдельную инфраструктуру безопасности — приватность данных через Anchor (хэш в сети, контент у владельца зашифрованным), антицензура через Transport Obfuscation и Dandelion++, децентрализация через отсутствие центрального сервера получаются бесплатно из протокола - Бизнес-модель: эмиссия Montana через узлы создателя. Не реклама, не подписка, не продажа данных - Чем больше пользователей в приложении → тем больше операций в сети → тем больше нужно узлов для обслуживания (Blob Buffer, валидация, P2P gossip) → больше узлов = больше шансов в лотерее = больше TimeCoin **Для пользователя:** - Каждое действие в приложении создаёт операцию в его AccountChain - account_chain_length растёт автоматически с каждым окном с операцией - Пользователь автоматически участвует в лотерее в каждом окне с операцией — без заявок, без стейкинга, без понимания криптографии - Шанс победы зависит от account_chain_length — длинная активная цепочка даёт реальные шансы выиграть 60 Ɉ - Ничего не привязано к конкретному приложению — seed принадлежит пользователю, account_id переходит между приложениями без потери истории **Нулевая стоимость переключения приложений.** AccountChain пользователя — его собственность. Если приложение закрылось или пользователь хочет уйти — account_id, баланс, история и накопленный account_chain_length остаются. Пользователь продолжает в другом приложении на том же протоколе. Приложения вынуждены конкурировать качеством, а не замком. ### Двигатель роста сети через AccountChain Лотерея Montana объединяет два класса участников: узлы (NodeChain) и аккаунты (AccountChain). Узлы доминируют статистически из-за непрерывного присутствия. Аккаунты получают долю эмиссии через активность пользователей. Эта механика создаёт самоподдерживающийся цикл роста сети — см. раздел "Эмиссия → Двигатель роста сети". ### Anchor Одна операция, данные навсегда привязаны к timechain_value конкретного окна. ``` Anchor: prev_hash 32B account_id 32B app_id 32B <- SHA-256("mt-app" || app_name) data_hash 32B <- Merkle root, H(document), произвольный хэш signature 666B Итого: ~796B ``` app_id — детерминированный идентификатор пространства имён. Вычисляется из имени приложения, регистрация не требуется. Позволяет фильтровать, индексировать, строить лёгкие клиенты для конкретного приложения. ### Timestamp Proof Стандартный формат доказательства: документ D существовал не позже момента T. В модели v20.2.0 операции аккаунтов финализируются через BundledConfirmations узлов-confirmers, не через включение в proposal. Доказательство существования Anchor — набор подписанных подтверждений с суммарным chain_length ≥ quorum. Proof собирается владельцем Anchor в момент финализации и хранится локально вместе с документом. Сеть не обязана хранить BundledConfirmations долгосрочно — ответственность за сохранение proof лежит на стороне, которой нужно доказать timestamp. ``` Структура proof: 1. Документ D и H(D) 2. Anchor body (prev_hash, account_id, app_id, data_hash, signature) 3. Если data_hash = MerkleRoot batch'а: Merkle path от H(D) до data_hash 4. Набор BundledConfirmations за окно W cementing'а Anchor: - каждая содержит H(Anchor) в op_hashes[] - каждая подписана confirmer node_pubkey - каждая содержит endpoint NodeChain confirmer на момент окна W - суммарный chain_length confirmers ≥ 67% online_chain_length(W) 5. Proposal header окна W (содержит timechain_value = T) 6. Цепочка proposal headers от W до genesis (через prev_proposal_hash) Верификация любым третьим лицом, без доверия Montana-узлу: 1. Если есть Merkle path: пересчитать H(D) → data_hash, сравнить с data_hash в Anchor 2. Проверить FN-DSA-512 подпись на Anchor 3. Для каждой BundledConfirmation: проверить FN-DSA-512 подпись confirmer 4. Для каждой confirmation: пересчитать NodeChain endpoint от start_window до окна W, подтвердить заявленный chain_length 5. Суммировать chain_length подтверждающих, проверить ≥ 67% online_chain_length(W) 6. Из proposal header окна W взять timechain_value = T 7. Пересчитать TimeChain VDF от proposal окна W до genesis по prev_proposal_hash ``` Proposals хранятся навсегда — timechain_value(W) и цепочка к genesis всегда доступны. BundledConfirmations хранятся локально владельцем proof. Timestamp proof самодостаточен и верифицируем в любой момент в будущем. ### Примеры **Мессенджер.** Каждое сообщение хэшируется, цепочка хэшей формирует Merkle root, Merkle root записывается в Anchor раз в минуту или час. Montana хранит 32 байта — доказательство что набор сообщений существовал в конкретный момент. Подделать историю переписки невозможно — хэш не совпадёт. **Архив документов.** Компания ежедневно записывает Merkle root документов. Через 10 лет регулятор спрашивает «существовал ли документ X на дату Y». Компания предоставляет документ, Merkle proof и ссылку на proposal. Верификация математическая. **Социальная сеть.** Каждый пост привязан к Montana Time через Anchor. Порядок публикаций доказуем. Редактирование не скрывает оригинал — хэш оригинала уже в цепочке. ### Экономика Anchor бесплатен. Тысячи приложений записывающих якоря — утилитарное использование Montana Time. Спрос на токен привязан к утилитарной функции: перевод ценности и запись времени, не спекуляция. Не нужны смарт-контракты. Не нужен Turing-complete язык. Не нужен газ. Не нужны комиссии. ### Phone Discovery и Messenger Phone discovery и messenger — полностью на Application Layer, не в protocol core. Protocol не знает о телефонах, именах, сообщениях. Application Layer реализует эти функции через Content Layer (см. ниже) и Interop Standards. Протокол предоставляет: - **Identity:** account_id через OpenAccount - **Timestamping:** Anchor с произвольным data_hash - **Storage:** Blob Buffer для хранения произвольных байт (persistent и ephemeral режимы) - **Transport:** libp2p gossip и Content Request Protocol Всё остальное — phone discovery, encryption, messaging protocols, profiles — реализуется на уровне приложения. Стандарты совместимости фиксированы в разделе Application Layer Interop Standards. ### Content Layer Content Layer предоставляет механизм хранения и репликации произвольных данных между узлами с привязкой к Anchor. Узлы подписанные на app_id хранят контент этого app_id. Новый узел или узел восстанавливающийся после offline скачивает недостающие блобы у пиров, верифицирует целостность через хэши из Anchor. Целевая задача — децентрализованное облако данных где каждый подписчик является хранителем, а факт существования контента зафиксирован навсегда через Anchor в proposal chain. #### Persistent Blob Blob Buffer получает второй режим хранения: - **TTL = τ₂** (ephemeral) — кратковременные сообщения, удаляются через τ₂ - **TTL = 0** (persistent) — контент привязанный к Anchor, хранится бессрочно пока узел подписан на соответствующий app_id Persistent blob индексируется парой `(app_id, data_hash) → blob_bytes`. Блоб может содержать manifest чанкованного контента (флаг `is_manifest = true`) или один чанк/целый файл. Размер одного blob ограничен chunk_size. Удаление persistent blob — решение оператора узла через явную отписку от app_id, не автоматически по таймеру. #### Chunking Standard Большие файлы разбиваются на чанки фиксированного размера: ``` chunk_size = 256 KB (фиксировано протоколом) chunk formaт: chunk_index (4B, u32) || chunk_data (≤262 144 bytes) chunk_hash = SHA-256("mt-content-chunk" || chunk_data) ``` Manifest содержит метаданные файла и упорядоченный список chunk_hashes: ``` Manifest { version: u16 (currently 1) file_name: string (UTF-8, length-prefixed, max 256 bytes) file_size: u64 mime_type: string (UTF-8, length-prefixed, max 64 bytes) chunk_count: u32 chunk_hashes: [32B × chunk_count] } ``` Merkle tree строится поверх chunk_hashes. Корень дерева: ``` data_hash = SHA-256("mt-content-manifest" || canonical_serialization(Manifest)) ``` Этот `data_hash` записывается в Anchor. Маленький файл (< chunk_size) — один чанк, manifest с `chunk_count = 1`. Manifest сохраняется как первый persistent blob по `(app_id, data_hash)` с флагом `is_manifest = true`. Индекс узла хранит эту связь, позволяя быстро находить manifest для любого Anchor. #### Content Request Protocol P2P сообщения libp2p для обмена контентом между узлами: ``` ContentRequest: app_id 32B data_hash 32B (data_hash из Anchor — manifest root) ContentResponse: status 1B (0 = ok, 1 = not_found, 2 = error) payload variable (serialized Manifest если status = 0) ChunkRequest: data_hash 32B (data_hash манифеста) chunk_index 4B ChunkResponse: status 1B chunk_data variable (до chunk_size байт) ``` **Процесс верификации при получении:** 1. Получив Manifest: десериализовать, проверить каноническая форма, пересчитать `data_hash = SHA-256("mt-content-manifest" || serialization)`, сравнить с запрошенным 2. Получив чанк: пересчитать `chunk_hash = SHA-256("mt-content-chunk" || chunk_data)`, сравнить с соответствующим элементом `chunk_hashes` в manifest 3. После сбора всех чанков: пересчитать Merkle tree из chunk_hashes, сравнить корень с data_hash из Anchor в proposal 4. Любое несовпадение — отклонить ответ пира, запросить у другого пира, пометить нечестного пира в local blacklist транспорта #### Content Discovery Два параллельных механизма поиска провайдеров контента: **DHT provide/lookup (Kademlia):** - Узел хранящий контент app_id публикует запись "я провайдер для app_id X" в Kademlia DHT - Запрашивающий узел делает lookup по app_id, получает список провайдеров - Подключается к провайдерам, запрашивает контент через ContentRequest/ChunkRequest - Стандартный libp2p content routing **Gossip announce:** - При установлении соединения с новым пиром узел в handshake объявляет список своих app_id (Bloom filter если список большой) - Пир запоминает привязку пир → app_id - При локальном ContentRequest по app_id которого нет — пересылает запрос пирам объявившим этот app_id Оба механизма работают параллельно. Узел использует любой рабочий путь. Content Discovery — локальная network state, не входит в consensus. #### Genesis Content Книга Montana — первый и обязательный контент сети, зафиксированный в Genesis Decree: ``` genesis_content_app_id = SHA-256("mt-app" || "montana") genesis_content_data_hash = <хэш манифеста книги Montana v1.0, хардкодировано в Genesis Decree> ``` **Mandatory replication.** Каждый узел Montana обязан хранить текущую версию книги Montana как persistent blob. Это часть протокольного определения "узел Montana", не optional подписка. При Fast Sync новый узел загружает genesis content вместе с initial state; без успешной загрузки sync считается неполным. **Обновление книги.** Автор публикует новый Anchor в `genesis_content_app_id` с новым `data_hash`. Узлы получают новую версию через Content Request Protocol, верифицируют через Merkle reconstruction, заменяют локальную копию. Старые версии остаются доступными через исторические Anchor в proposals навсегда — версии книги историчны и неудаляемы. Обновление значения `genesis_content_data_hash` в protocol_params возможно только через MIP (параметрический governance). ### Application Layer Interop Standards Этот раздел фиксирует минимальные стандарты для совместимости между приложениями Montana. Приложения следующие этим стандартам могут взаимодействовать между собой — обмениваться профилями, сообщениями, контентом. Приложения использующие другие форматы работают в изоляции. Это нормативный раздел: форматы и формулы в нём обязательны для interop-совместимых приложений. #### Canonical app_id registry Фиксированные app_id для стандартных функций приложений: | Функция | Формула | Назначение | |---|---|---| | genesis content | `SHA-256("mt-app" \|\| "montana")` | Книга Montana и её обновления | | profile | `SHA-256("mt-app" \|\| "profile")` | Публичные профили пользователей | | encryption keys | `SHA-256("mt-app" \|\| "encryption-keys")` | Discovery encryption pubkeys | | messenger prekeys | `SHA-256("mt-app" \|\| "messenger-prekeys")` | Pre-keys bundles для Double Ratchet | | phone discovery | `SHA-256("mt-app" \|\| "phone-discovery")` | Public mode phone → account lookup | Пользовательские каналы используют формулу `SHA-256("mt-app" || channel_name)` где `channel_name` — произвольная строка выбранная создателем канала. Уникальность каналов обеспечивается через уникальность имени; коллизии разрешаются по первому cemented Anchor в данном app_id. #### ProfileBlob format Канонический формат публичного профиля пользователя: ``` ProfileBlob { version u16 (currently 1) display_name string (UTF-8, length-prefixed, max 64 bytes, may be empty) avatar_hash 32B (ref to image blob by data_hash, или 0x00..00) bio string (UTF-8, length-prefixed, max 256 bytes, may be empty) updated_at u64 (unix timestamp публикации) } ``` Сериализация: little-endian, length-prefix для строк (u16 length + bytes). **Публикация профиля:** 1. Serialize ProfileBlob канонически 2. `data_hash = SHA-256("mt-profile" || serialized)` 3. `store_blob(app_id_profile, data_hash, serialized)` 4. `publish_anchor(app_id_profile, data_hash)` **Lookup профиля другого пользователя:** 1. Запросить через Anchor history: все Anchor с `app_id = profile` и `sender = target_account_id` 2. Отсортировать по времени (окно финализации Anchor), взять новейший 3. `fetch_blob(app_id_profile, latest_data_hash)` 4. Deserialize Profile опционален — пользователь может не публиковать профиль. Приложение должно поддерживать локальные override имён независимо от публичного профиля (пользователь может видеть контакт как "Мама" локально, даже если публичный профиль контакта другой). #### Published encryption_pubkey format Формат блока публикации encryption key пользователя для приёма зашифрованного контента: ``` EncryptionKeyBlob { version u16 (currently 1) algorithm u16 (1 = ML-KEM-768) encryption_pubkey variable (1184B для ML-KEM-768) published_at u64 (unix timestamp) } ``` **Публикация:** 1. Serialize EncryptionKeyBlob 2. `data_hash = SHA-256("mt-encryption-key" || serialized)` 3. `store_blob(app_id_encryption_keys, data_hash, serialized)` 4. `publish_anchor(app_id_encryption_keys, data_hash)` **Lookup encryption key получателя:** 1. Запросить Anchor history: все Anchor с `app_id = encryption-keys` и `sender = target_account_id` 2. Взять новейший (последняя ротация ключа) 3. `fetch_blob(app_id_encryption_keys, latest_data_hash)` 4. Deserialize, извлечь encryption_pubkey **Key rotation.** Публикация нового Anchor с новой EncryptionKeyBlob. Старые ключи остаются в proposal history навсегда — старые ciphertexts расшифровываются если владелец сохранил старый seckey. #### Messenger pre-keys bundle format Для инициализации Double Ratchet PQ session с offline получателем. Пользователь публикует pre-keys bundle заранее; отправитель использует его для первого сообщения без ответа получателя. ``` PreKeyBundle { version u16 (currently 1) identity_key variable (ML-KEM-768 identity pubkey, 1184B) signed_prekey variable (ML-KEM-768 signed pre-key, 1184B) prekey_signature 666B (FN-DSA-512 подпись signed_prekey identity key) one_time_prekeys [variable] (массив ML-KEM-768 pubkeys, одноразовые) published_at u64 } ``` **Публикация:** 1. Serialize PreKeyBundle 2. `data_hash = SHA-256("mt-prekeys" || serialized)` 3. `store_blob(app_id_messenger_prekeys, data_hash, serialized)` 4. `publish_anchor(app_id_messenger_prekeys, data_hash)` **Refresh.** При исчерпании one_time_prekeys (каждое pre-key используется одним отправителем и удаляется) публикуется новый bundle. Получатель должен мониторить использование pre-keys и публиковать fresh bundle заранее. #### Phone discovery (public mode) Опциональная функция для приложений поддерживающих поиск по номеру телефона. **Формула:** `phone_hash = SHA-256("mt-phone-public" || phone_e164)` Где `phone_e164` — номер телефона в формате E.164 (например `+79991234567`). **Публикация в public mode:** 1. Пользователь явно включил public phone discovery в приложении 2. Приложение вычисляет phone_hash 3. `data_hash = phone_hash` 4. Persistent blob содержит `account_id` владельца (32B) 5. `store_blob(app_id_phone_discovery, data_hash, account_id)` 6. `publish_anchor(app_id_phone_discovery, data_hash)` **Lookup:** 1. Приложение для каждого контакта из адресной книги вычисляет phone_hash 2. `fetch_blob(app_id_phone_discovery, phone_hash)` → account_id или not_found 3. Если найден — контакт в сети Montana **Privacy warning.** Public mode подвержен rainbow table attack. Атакующий со списком phone numbers может вычислить phone_hashes и искать совпадения в сети. Это эквивалентно модели WhatsApp. Пользователь выбирает режим осознанно. **Private mode** (рекомендованный по умолчанию). Пользователь не публикует phone_hash. Контакты добавляются только через QR-код, ссылку или прямой обмен account_id. Максимальная приватность, ручной ввод контактов. #### Recommended crypto primitives для Application Layer Эти примитивы **не входят** в protocol core (core остаётся с SHA-256 + FN-DSA-512). Они рекомендованы для Application Layer encryption и messaging чтобы обеспечить совместимость между приложениями. Приложения использующие другие примитивы работают в изоляции. | Примитив | Стандарт | Применение | |---|---|---| | ML-KEM-768 | FIPS 203, NIST PQC | Key encapsulation для encrypted messaging и file encryption | | ChaCha20-Poly1305 | RFC 8439 | Symmetric AEAD encryption содержимого | | HKDF-SHA-256 | RFC 5869 | Derivation ключей из KEM shared secret | Все три примитива постквантово-безопасны или симметричны (ChaCha20-Poly1305 ослабляется Grover до 128-bit, приемлемо). Production-ready реализации доступны для всех major языков. **Application-level key derivation из seed.** Encryption keypair (ML-KEM-768) выводится из того же seed что и account и node keypairs, через отдельный domain separator `mt-encryption-key` (см. раздел Деривация ключей). Восстановление seed из мнемоники восстанавливает все три keypair одной операцией. ### Integration Три операции для подключения внешних систем к Montana. #### Write — запись Внешняя система формирует Anchor и отправляет в P2P-сеть. ``` Вход: app_id (32B) + data_hash (32B) + подпись FN-DSA-512 Выход: Anchor финализирован в окне W через ≥67% online_chain_length confirmations с timechain_value T_W ``` data_hash — произвольный хэш: Merkle root документов, хэш batch'а Rollup, fingerprint состояния. Montana не интерпретирует содержимое — хранит 32 байта с привязкой ко времени. #### Read — сбор proof Внешняя система собирает timestamp proof в момент финализации Anchor. ``` Вход: Anchor (только что финализированный) Выход: Anchor body + BundledConfirmations покрывающие H(Anchor) + proposal header окна cementing'а + цепочка proposal headers до genesis ``` Сбор proof — клиентская задача. После получения BundledConfirmations с суммарным chain_length ≥ quorum клиент сохраняет proof локально. Узлы Montana не обязаны хранить BundledConfirmations долгосрочно — они нужны только для текущего подсчёта quorum. #### Verify — верификация Внешняя система проверяет proof автономно, без доверия к Montana-узлу. ``` 1. Если есть Merkle path: пересчитать H(D) → data_hash в Anchor 2. Проверить FN-DSA-512 подпись на Anchor 3. Для каждой BundledConfirmation в proof: a. Проверить FN-DSA-512 подпись confirmer b. Пересчитать NodeChain endpoint confirmer от start_window до окна W c. Подтвердить заявленный chain_length 4. Суммировать chain_length подтверждающих ≥ 67% online_chain_length(W) 5. Проверить FN-DSA-512 подпись proposer на header окна W 6. Проверить timechain_value(W) пересчётом TimeChain VDF от T_{W-1} 7. Проверить цепочку proposals от W до genesis (prev_proposal_hash) ``` Шаги 1, 2, 3a, 5: миллисекунды. Шаг 3b: пересчёт NodeChain VDF confirmer — m × (W − start_window) хэшей, параллелизуется по сегментам. Шаг 6: ~60 секунд на одном ядре (один сегмент TimeChain VDF). Шаг 7: линейная проверка подписей и хэшей по цепочке proposals от окна W до genesis. Полная верификация от генезиса: H сегментов TimeChain VDF, каждый независим. На C ядрах: ~(H/C) × 60 секунд. TimeChain хранит все промежуточные T_r в proposals — параллелизация полная. --- ## Ключи ### Мнемоника и seed 24 слова из словаря BIP-39 (2 048 английских слов). 256 бит энтропии + 8 бит checksum. ``` mnemonic: 24 слова BIP-39 seed: PBKDF2-SHA512(mnemonic, "mt-seed", 2048 итераций) ``` ### Деривация ``` seed ├── Аккаунт: FN-DSA-512 keypair (HMAC-SHA256(seed || "mt-account-key")) │ → account_id = SHA-256("mt-account" || account_pubkey) └── Узел: FN-DSA-512 keypair (HMAC-SHA256(seed || "mt-node-key")) → node_id = SHA-256("mt-node" || node_pubkey) ``` Один seed порождает два FN-DSA-512 keypair. Аккаунт — подпись операций пользователя. Узел — подпись proposals и NodeChain endpoints. Это полный набор секретных материалов protocol core. account_id и node_id выводятся из публичных ключей, верифицируемы без знания seed. **Application-level keys.** Приложения могут выводить дополнительные ключи из того же seed через свои domain separators (не protocol-critical). Рекомендованный стандарт Application Layer определяет encryption keypair через `HMAC-SHA256(seed || "mt-app-encryption-key")` для ML-KEM-768. Этот ключ не входит в protocol core — его знание не нужно узлам консенсуса. Приложения использующие этот стандарт получают совместимое восстановление: один seed → все ключи (account, node, encryption). Следствие: любое устройство с seed получает полный доступ к аккаунту. Баланс читается из текущего Account Table — никакого локального состояния не требуется. Бэкап = 24 слова, восстановление мгновенное. --- ## Криптографическая реализация ### Primitive layer Собственная реализация криптографических примитивов запрещена. Только audited библиотеки с constant-time гарантиями и опубликованными test vectors. | Примитив | Стандарт | Роль | |----------|----------|------| | SHA-256 | FIPS 180-4 | TimeChain, NodeChain, адреса, Merkle-деревья | | FN-DSA-512 | NIST PQC selection финал (июль 2022), forthcoming FIPS 206, reference implementation production-ready | Подписи операций аккаунтов и proposals узлов | ### Consensus encoding layer Консенсусно-критическая поверхность: каноническая сериализация, Merkle layout и domain separation. Разная сериализация одного объекта = разный хэш = форк. Требования: - Fixed binary encoding для каждого консенсусного объекта - Length-prefix кодирование полей, фиксированный endianness (little-endian) - Domain separation для всех хэшей: | Домен | Контекст | |-------|----------| | `mt-op` | Хэширование операций аккаунтов | | `mt-header` | Хэширование proposal headers | | `mt-account` | Деривация account_id | | `mt-invitation` | Хэширование приглашений | | `mt-merkle-leaf` | Листья Merkle-деревьев | | `mt-merkle-node` | Внутренние узлы Merkle-деревьев | | `mt-state-root` | Композиция state_root из node_root и account_root | | `mt-timechain` | TimeChain VDF seed | | `mt-nodechain-init` | NodeChain init seed | | `mt-confirmation` | Хэширование async confirmations | | `mt-app` | Деривация app_id для Application Layer | | `mt-node` | Деривация node_id | | `mt-genesis` | Деривация frontier_hash genesis-аккаунтов | | `mt-nodechain-genesis` | Деривация nodechain_init для genesis-узлов | | `mt-seed` | Salt для PBKDF2 деривации seed из мнемоники | | `mt-account-key` | Деривация keypair аккаунта из seed | | `mt-node-key` | Деривация keypair узла из seed | | `mt-mip` | Деривация mip_id из (account_id, op_height) | | `mt-mip-vote` | Хэширование MIPVote | | `mt-section` | Деривация target_section из section_path | | `mt-diff` | Хэширование DiffEntry для канонического порядка | | `mt-mip-order` | Псевдослучайный порядок применения MIP в τ₂ периоде | | `mt-account-lottery` | Endpoint AccountChain для лотереи | | `mt-content-chunk` | Хэширование чанков контента в Content Layer | | `mt-content-manifest` | Хэширование манифеста чанкованного контента | | `mt-profile` | Хэширование ProfileBlob в Application Layer | | `mt-encryption-key` | Хэширование EncryptionKeyBlob в Application Layer | | `mt-app-encryption-key` | Деривация encryption keypair из seed в Application Layer | | `mt-prekeys` | Хэширование PreKeyBundle в Application Layer | | `mt-phone-public` | Хэширование phone_hash для public mode phone discovery | - Альтернативные сериализации запрещены - Test vectors для каждого консенсусного объекта - Cross-implementation conformance tests перед запуском mainnet ### Protocol layer Собственная реализация поверх криптографического ядра: | Компонент | Назначение | |-----------|------------| | Merkle-деревья | State Root (из SHA-256 вызовов) | | VDF scheduling | Управление TimeChain и NodeChain цепочками | | State machine | Account Table, Node Table, state transitions | | P2P gossip | Распространение операций, confirmations и proposals | ### Инфраструктура | Библиотека | Назначение | |------------|------------| | RocksDB | Хранение Account Table и операций | | libp2p | P2P транспорт | Production: Rust. --- ## Сетевой уровень ### Transport Obfuscation P2P-трафик Montana неотличим от обычного HTTPS для ISP-уровня наблюдателя. Каждый узел обслуживает реальный HTTPS на порту 443 (статическая страница, API-заглушка). SNI = валидный домен узла. Montana-соединение устанавливается после TLS handshake по Noise protocol ID внутри TLS. Внешний наблюдатель видит обычный веб-сервер. **Требования:** 1. Все P2P-соединения инкапсулированы в TLS 1.3 на порт 443 2. Каждый узел обслуживает реальный HTTPS на том же порту (валидный SNI, HTTP-ответы) 3. Noise framework (встроен в libp2p) для шифрования внутри TLS 4. Padding: каждый пакет дополняется до фиксированного размера из набора {1 KB, 2 KB, 4 KB}. Ближайший больший размер 5. Timing jitter: интервал между пакетами рандомизирован в пределах ±20% от базового **Что скрывается:** | Наблюдатель | Видит | Не видит | |-------------|-------|----------| | ISP / DPI | TLS 1.3 соединение на порт 443, валидный SNI, HTTP-ответы | Протокол, содержимое, тип сообщения | | Сетевой аналитик | Поток пакетов фиксированного размера | Паттерн VDF reveal / confirmation / proposal | | Государственный фаервол | HTTPS-трафик на валидный домен | Факт участия в Montana | **Реализация:** ``` libp2p transport stack: TCP → TLS 1.3 (порт 443, SNI = домен узла) → Noise → Montana protocol HTTPS fallback: Соединение без Noise protocol ID → обычный HTTP-ответ (статическая страница) Соединение с Noise protocol ID → Montana P2P Padding: msg_padded = msg || random_bytes(next_bucket_size - len(msg)) bucket_sizes = [1024, 2048, 4096] Timing jitter: send_delay = base_interval × (1 + uniform(-0.2, 0.2)) ``` Блокировка Montana = блокировка конкретного домена (ручная работа по каждому узлу). Transport obfuscation ортогонален консенсусу. TimeChain, NodeChain, state machine работают поверх любого транспорта без изменений. ### Censorship-Resistant Discovery Генезис: 12 hardcoded bootstrap nodes. Если все 12 IP заблокированы на уровне страны — новый узел не может войти в сеть. Четыре независимых канала обнаружения. Достаточно одного из четырёх. **1. Encrypted Client Hello (ECH).** Bootstrap через CDN с поддержкой ECH (Cloudflare, стандарт IETF TLS 1.3). SNI зашифрован — наблюдатель видит IP CDN, но не видит целевой домен. Montana bootstrap неотличим от обращения к любому другому сайту за тем же CDN. ECH — стандартизированная защита приватности, поддержан CDN, браузерами и серверами. Блокировка ECH = блокировка стандарта TLS = блокировка современного интернета. **2. Peer exchange.** Каждый узел хранит и передаёт список активных пиров новичкам. Достаточно знать IP одного узла — друг, QR-код, мессенджер. Один живой контакт = вход в сеть. **3. DHT.** Kademlia DHT поверх libp2p. Узлы находят друг друга без центральной точки. Идентификаторы рандомизированы — DHT не раскрывает node_id до установления Montana-соединения. **4. Bridge nodes.** Узлы за пределами цензурируемой юрисдикции, опубликованные через внеполосные каналы (социальные сети, мессенджеры, печатные QR-коды). IP bridge node неизвестен фаерволу до использования. Избыточность = устойчивость. Четыре канала независимы. Блокировка одного не влияет на остальные. ### Dandelion++ (анонимность отправителя) P2P gossip Montana ретранслирует операции через все узлы. Без защиты первый пир знает IP отправителя. Dandelion++ (Fanti et al. 2018) устраняет связь IP → операция модификацией существующего gossip. **Две фазы:** ``` Stem (стебель): Операция проходит по цепочке через 2-3 случайных узла. Каждый узел видит только предыдущий hop, не автора. На каждом hop с вероятностью p = 0.1 переход в fluff. Fluff (пух): Последний stem-узел запускает обычный gossip. Для всей сети операция «появилась» из случайной точки. ``` **Применение по типу объекта:** | Объект | Режим | Причина | |--------|-------|---------| | UserObject (Transfer, Anchor, OpenAccount, ChangeKey) | Stem → fluff | Скрыть IP отправителя | | ControlObject (NodeInvitation, NodeRegistration) | Stem → fluff | Скрыть IP пригласившего/регистрирующегося | | VDF Reveal | Прямой gossip (без stem) | node_id публичен в reveal, анонимность невозможна | | Confirmation | Stem → fluff | Скрыть какой узел подтвердил первым | VDF Reveal — единственное исключение. Reveal содержит node_id по определению. Связь IP → node_id для внешнего наблюдателя закрыта слоем Transport Obfuscation (TLS 1.3 на порт 443). **Свойства:** | Угроза | Защита | |--------|--------| | Пир видит IP отправителя | Stem: пир видит только предыдущий hop | | Глобальный наблюдатель (ISP) | TLS 1.3 + timing jitter (Transport Obfuscation) | | Анализ графа gossip | Операция входит в gossip из случайной точки | | Контроль k узлов | Деанонимизация требует контроля O(√n) узлов | **Реализация:** ``` stem_peer = random_choice(connected_peers) on_receive_stem(msg, from_peer): if random() < 0.1: gossip_broadcast(msg) // fluff else: next_peer = random_choice(connected_peers, exclude=from_peer) send_stem(msg, next_peer) // продолжить stem start_timer(msg, 30s) // страховка на каждом hop on_timer_expired(msg): if msg не обнаружен в gossip: gossip_broadcast(msg) // принудительный fluff ``` Каждый stem-узел страхует следующий. Таймер 30 секунд на каждом hop независимо. Если следующий hop уронил сообщение — текущий hop обнаруживает отсутствие операции в gossip и делает fluff сам. Максимальная задержка = 30 секунд (один hop), не кумулятивная. Dandelion++ не требует внешней инфраструктуры. Каждый Montana-узел уже является relay — gossip существует, stem добавляет 2-3 hop перед ним. Latency overhead: миллисекунды. ### Три слоя — одна конструкция ``` Слой 1: Transport Obfuscation ISP не знает что ты в Montana Слой 2: Censorship-Resistant Discovery фаервол не может отрезать от сети Слой 3: Dandelion++ пиры не знают кто автор операции ``` Каждый слой закрывает свой вектор. Ни один не требует внешней инфраструктуры. Всё построено поверх libp2p и существующего gossip. Сетевой уровень ортогонален консенсусу — ни один state transition не затронут. --- ## Governance — Triple Consent Эволюция протокола Montana проходит через систему трёх независимых советов. Изменения принимаются когда минимум 2 из 3 советов проголосовали ЗА с собственным кворумом. Это убирает посредников между пользователями сети и кодом протокола: любое изменение проходит через формальное голосование с криптографическими подписями, никаких закрытых обсуждений и непрозрачных merge решений. ### Принцип: три источника легитимности ``` AI Council — техническая компетенция (модели разных компаний) Core Council — человеческая ответственность (публичные люди) Node Council — экономическое присутствие (узлы с накопленным временем) ``` Ни один совет в одиночку не может изменить протокол. Захват одного недостаточен. Полный захват требует одновременной компрометации двух из трёх разнородных групп. ### AI Council Состав: 13 моделей включая Председателя (Genesis = 13, hard floor = 10, max = 13). ``` Кворум: > 67% от полного состава snapshot молчание не входит в yes_count Председатель: argmax(ai_reputation_score) среди членов без преимущества в голосе (тай-брейкер не нужен) Репутация: +1 за подтверждённое утверждение −1 за галлюцинацию 0 за inconclusive (вопрос уже закрыт или не требует изменений) ``` Идентификация члена: account_id с `ai_council_flag = 1`. ### Core Council Состав: 13 человек включая Председателя (Genesis = 13, hard floor = 10, max = 13). ``` Кворум: > 67% от полного состава snapshot Председатель: назначается через Triple Consent (не по репутации) отвечает за социальную координацию обновлений узлов после принятия MIP ``` Идентификация члена: account_id с `human_core_flag = 1`. Список членов канонический в Account Table. ### Node Council Состав: все узлы в Node Table. ``` Вес голоса: chain_length каждого узла Кворум: > 67% от Σ chain_length всех узлов Голос: MIPVote с подписью node_pubkey ``` Голосование Node Council — агрегация Anchor-операций. Каждый узел публикует подпись за/против как Anchor с подписью node_pubkey. Сумма вычисляется клиентами детерминированно. Это не меняет консенсус — опирается на то что узлы владеют account_id и могут подписывать Anchor. ### Структуры объектов ``` MIPProposal: prev_hash 32B account_id 32B mip_id 32B H("mt-mip" || account_id || op_height) diff_count 2B uint16, ∈ [1, 16] diff_entries ... массив DiffEntry, лексикографически по H("mt-diff" || serialized) rationale_hash 32B H(off-chain текста обоснования) signature 666B FN-DSA-512 DiffEntry: op_type 1B ∈ {1, 2, 3, 4} target_section 32B H("mt-section" || canonical_path) payload_size 2B uint16 payload ... typed bytes по op_type MIPVote: prev_hash 32B voter_id 32B account_id (AI/Core) или node_id (Node) voter_class 1B 1=AI, 2=Core, 3=Node (вычисляется из voter_id) mip_id 32B vote_value 1B 1=YES, 2=NO signature 666B FN-DSA-512 ``` Сериализация подчиняется общим правилам Montana (раздел "Consensus encoding layer"): fixed binary encoding, length-prefix, little-endian, domain separation. Test vectors обязательны перед mainnet. ### Четыре типа op_type ``` op_type = 1 ADD_OBJECT Добавление нового объекта (только UserObject) op_type = 2 CHANGE_FORMAT Изменение формата существующего объекта op_type = 3 CHANGE_RULE Изменение правила в apply_proposal/calibration op_type = 4 ADD_DOMAIN Новый domain separator ``` Все четыре типа требуют 2 из 3 Советов. Никакого SOFT/HARD различия — все MIP проходят один путь. ### Алгоритм классификации ``` classify(MIPProposal) -> {VALID, INVALID}: if MIPProposal.diff_count == 0 or > 16: return INVALID for entry in MIPProposal.diff_entries: if entry.op_type ∉ {1, 2, 3, 4}: return INVALID if not validate_canonical_serialization(entry): return INVALID if not validate_payload_constraints(entry): return INVALID return VALID ``` INVALID MIP отклоняется на этапе приёма (как невалидная подпись). Не попадает в голосование. ### Constraints для op_type 1 (ADD_OBJECT) ``` Payload: object_type_id 1B из canonical registry object_class 1B 1=UserObject (РАЗРЕШЕНО) 2=ControlObject (ЗАПРЕЩЕНО → INVALID) field_count 1B fields ... validation_rule_count 1B validation_rules ... массив RulePredicate RulePredicate: predicate_id 1B из CANONICAL_PREDICATES target_field_index 1B индекс поля этого объекта (не другого state) parameters ... Constraints: object_class == 1 validation_rule_count >= 1 каждый predicate_id ∈ CANONICAL_PREDICATES каждый target_field_index указывает на поле этого объекта ``` ControlObject через MIP запрещён полностью. Все ControlObject определены в Genesis (NodeInvitation, NodeRegistration). Любой новый ControlObject требует новой версии Montana. ### Constraints для op_type 2 (CHANGE_FORMAT) ``` Payload: target_object_type_id 1B объект который меняется change_kind 1B тип изменения change_data ... детали зависят от change_kind change_kind ∈ { 1: add_field, 2: remove_field, 3: change_field_type, 4: rename_field, 5: remove_object_entirely, 6: swap_council_member, 7: add_council_member, 8: remove_council_member } Constraints: target_section ∈ MUTABLE_SECTIONS Для remove_council_member: |Совет| - 1 >= 10 (hard floor) Иначе INVALID Для add_council_member: |Совет| + 1 <= 13 (max) Иначе INVALID ``` ControlObject секции (`node_invitation_format`, `node_registration_format`, `vdf_reveal_format`, `confirmation_format`, `proposal_header`, `node_table`) запрещены для CHANGE_FORMAT. ### Constraints для op_type 3 (CHANGE_RULE) ``` Payload: target_function_id 2B из canonical function registry new_value_or_predicate ... Constraints: target_function_id ∈ MUTABLE_FUNCTIONS (whitelist) IMMUTABLE_FUNCTIONS → INVALID ``` ### Constraints для op_type 4 (ADD_DOMAIN) ``` Payload: domain_string ASCII, "mt-" prefix, [a-z0-9-], length 4..32 Constraints: не конфликтует с существующими domain separators ``` ### Whitelists ``` MUTABLE_SECTIONS (CHANGE_FORMAT разрешён): transfer_format open_account_format change_key_format anchor_format account_table IMMUTABLE_SECTIONS (CHANGE_FORMAT запрещён): node_invitation_format node_registration_format vdf_reveal_format confirmation_format proposal_header node_table MUTABLE_FUNCTIONS (CHANGE_RULE разрешён): calibrate_d, calibrate_m, calibrate_r, calibrate_f target_calibration_lottery tau_mip_voting, tau_mip_activation, vote_cutoff_buffer council_quorum_threshold, node_council_threshold time_record_value bucket_age_thresholds, bucket_quotas pruning_inactivity_threshold IMMUTABLE_FUNCTIONS (CHANGE_RULE запрещён): validate_node_invitation, validate_node_registration, validate_vdf_reveal validate_confirmation, validate_transfer, validate_open_account validate_change_key, validate_anchor apply_proposal, apply_user_object, apply_control_object compute_state_root, compute_account_root, compute_node_root compute_control_root fork_choice_rule, finality_quorum, equivocation_detection signature_scheme, hash_function canonical_predicates_registry CANONICAL_PREDICATES (для validation_rules в ADD_OBJECT): signature_check prev_hash_check equivocation_check range_check size_check balance_check supply_invariant_check format_check domain_separator_check ``` Никаких state_read, temporal queries, cross-account queries в CANONICAL_PREDICATES. Validation rules могут работать только на полях самого объекта. ### Жизненный цикл MIP ``` 1. INITIATE account_id публикует MIPProposal как UserObject в своей цепочке. classify() → VALID или REJECTED при приёме. active_mip_id записывается в Account Table. Лимит: один account_id = максимум один активный MIP. MIP не блокирует слот приглашения узлов и не блокирует сообщения — определяется по тому же уровню квоты операций. 2. CEMENT MIPProposal распространяется по P2P, валидируется узлами, получает confirmations. Cemented когда > 67% online_chain_length подтвердили. mip_proposed_window = окно cemented MIPProposal (не окно signing). 3. SNAPSHOT (на τ₂ boundary) snapshot_window = last_tau2_boundary(mip_proposed_window) где last_tau2_boundary(W) = max τ₂_boundary <= W AI_snapshot = {account_id : ai_council_flag = 1 на snapshot_window} Core_snapshot = {account_id : human_core_flag = 1 на snapshot_window} total_chain_length_snapshot = Σ chain_length всех узлов на snapshot_window Snapshot привязан к τ₂ boundary для детерминированности голосования и состава Council. 4. VOTE voting_window = [mip_proposed_window, mip_proposed_window + 4τ₂ - 100] Голос = MIPVote (UserObject) с подписью голосующего. Голос валиден если cemented_window голоса <= mip_proposed_window + 4τ₂ - 100. Buffer 100 окон до подсчёта обеспечивает гарантированную propagation cemented голосов до всех узлов. Equivocation protection: один (voter_id, mip_id) = один голос. Два голоса от одного voter_id с разными vote_value = equivocation, обе отклоняются (по правилу спеки "Двойная трата"). 5. COUNT counting_window = mip_proposed_window + 4τ₂ ai_yes_count = Σ cemented MIPVote от AI_snapshot с vote_value=YES core_yes_count = Σ cemented MIPVote от Core_snapshot с vote_value=YES node_yes_weight = Σ chain_length(node) для узлов с cemented vote_value=YES (chain_length считается на snapshot_window) ai_passed = (ai_yes_count > 0.67 × |AI_snapshot|) AND |AI_snapshot| >= 10 core_passed = (core_yes_count > 0.67 × |Core_snapshot|) AND |Core_snapshot| >= 10 node_passed = (node_yes_weight > 0.67 × total_chain_length_snapshot) councils_passed = (ai_passed ? 1 : 0) + (core_passed ? 1 : 0) + (node_passed ? 1 : 0) ACCEPTED = (councils_passed >= 2) Если councils_passed < 2 → CLOSED, active_mip_id освобождается. 6. ACTIVATE (на τ₂ boundary) activation_target = counting_window + 4τ₂ activation_tau2_boundary = next τ₂_boundary >= activation_target Узлы имеют 4τ₂ для обновления софта. Председатель Core координирует социально. На activation_tau2_boundary применить все ACCEPTED MIP того периода: pending = collect_accepted_mips(activation_tau2_boundary) for mip in pending: mip.sort_key = H("mt-mip-order" || timechain_value(activation_tau2_boundary) || mip.mip_id) sort pending by sort_key (lex byte order) for mip in sorted: try: state = apply_mip(state, mip) except: skip mip (отклоняется на этапе применения) Псевдослучайный порядок через VDF исключает грайндинг — атакующий не может предсказать порядок до момента активации. ``` ### Genesis Council Decree При запуске сети Montana первоначальный состав AI Council и Core Council определяется Genesis Council Decree. Это однократное решение, записанное в первый блок сети. ``` Genesis: каждый Совет = 13 (12 советников + 1 Председатель) AI Council: 13 моделей разных компаний Core Council: 13 публичных людей Состав публикуется отдельно при запуске сети Hard floor: 10 (enforced на pre-classify через атомарные операции) Maximum: 13 После Genesis все изменения через Triple Consent. Genesis Decree immutable — не может быть изменён через MIP. ``` ### Освобождение неактивных слотов Освобождение через MIP, не автоматически. Если член Совета молчит — другие два Совета принимают MIP на его исключение через стандартный 2 из 3 механизм. Hard floor 10 защищает от опускания состава ниже жизнеспособного минимума. ### Soft fork vs Hard fork В этой версии Montana различение SOFT/HARD удалено. Все MIP проходят через единый процесс с порогом 67% для каждого Совета. Это консервативный выбор — каждое изменение требует широкого согласия. Каскадные атаки через цепочки тихих изменений невозможны потому что каждый MIP проходит публичное голосование Triple Consent. ### Архитектурный trade-off: социальная инерция узлов Triple Consent основано на 2 из 3 Советов с порогом 67%. При координированной компрометации AI Council + Core Council (требует подкупа моделей разных компаний и публичных людей одновременно), MIP может быть принят без согласия Совета Узлов. В этом случае защита сети возлагается на активный отказ операторов узлов обновляться. Узлы, не обновившие софт к activation_tau2_boundary, продолжают работать на старой версии. Сеть может разделиться на оригинальную (старая версия) и обновлённую (новая версия). Каноничность определяется большинством chain_length каждой стороны. Это сознательный архитектурный выбор: - Технические эксперты (AI + Core) могут предлагать изменения - Сеть узлов имеет последнее слово через экономическое голосование ногами - Симметрично с Bitcoin hard forks (BCH откололся от BTC аналогично) Это ограничение — НЕ дыра. Это явная декларация что Triple Consent не криптографическая гарантия консервативности — он структурная гарантия множественности голосов с экономическим финальным фильтром. Mitigation на уровне Совета Ядра: Председатель Core отвечает за прозрачную социальную координацию обновлений. Любой принятый MIP публично объявляется через каналы Core. Операторы узлов получают описание изменения и время на анализ. --- ## Архитектура ``` ТЕЛЕФОН / ДЕСКТОП УЗЕЛ (десктоп / сервер, 24/7) ┌────────────────────────┐ ┌──────────────────────────────────────┐ │ Кошелёк │ │ │ │ FN-DSA-512 keypair │ │ TimeChain │ │ локальная UX-история │ │ T_r = SHA-256^D(T_{r-1}) │ │ операций │ │ глобальные часы, источник │ │ │ │ случайности │ │ AccountChain │ │ │ │ │ (счётчик окон │ │ ▼ │ │ активности) │ │ NodeChain (per node) │ │ │ │ S_{i,w} = SHA-256(S_{i,w-1} || T_w │ └──────────┬─────────────┘ │ || node_id) │ │ операции │ доказательство присутствия │ │ (type|prev_hash| │ chain_length = окна с BundledConf. │ │ payload|FN-DSA-512) │ │ │ └──────────────────────▶│ ▼ │ confirmations │ AccountTable │ ◀──────────────────-│ balance: u64 (открыт) │ │ pubkey, frontier_hash │ │ account_chain_length │ │ │ │ │ ▼ │ │ Proposals (навсегда) │ │ control_root, node_root, │ │ account_root, timechain_value │ └──────────────────────────────────────┘ Зависимости: TimeChain → NodeChain → AccountTable Отказ AccountTable не останавливает часы. Отказ узла не заражает TimeChain. ```