2697 lines
221 KiB
Markdown
2697 lines
221 KiB
Markdown
|
|
# Montana — Спецификация протокола
|
|||
|
|
|
|||
|
|
**Версия:** 26.13.13 (2026-04-12)
|
|||
|
|
|
|||
|
|
## Определение
|
|||
|
|
|
|||
|
|
Montana — цифровой стандарт времени как реляционная структура. Сеть независимых VDF-осцилляторов, конституирующих каноническую последовательность событий через последовательное хэширование и консенсус между узлами. Montana производит каноническую структуру отношений между хэш-событиями, индексированных window_index. Каждое канонически зарегистрированное окно = 13 Ɉ (TimeCoin).
|
|||
|
|
|
|||
|
|
**Montana Time** — реляционная структура, конституируемая последовательным хэшированием в рамках VDF и канонической упорядоченностью, устанавливаемой консенсусом между узлами. Внутри этой структуры время в протоколе существует как последовательность канонических событий.
|
|||
|
|
|
|||
|
|
Montana — самодостаточная система отсчёта: каноническая последовательность событий, которую внешние системы могут наблюдать и использовать как reference frame для своих нужд.
|
|||
|
|
|
|||
|
|
Основная функция — каноническая временная координата (window_index). Вторичная — передача ценности.
|
|||
|
|
|
|||
|
|
Консенсус: **Proof of Time (PoT)** — четыре цепочки. TimeChain: глобальная каноническая цепь (D последовательных SHA-256 = одно окно). NodeChain: персональная цепочка узла (доказательство присутствия в каждом окне). AccountChain: счётчик окон активности аккаунта. AccountTable: состояние счёта. Влияние узла = длина его NodeChain. Протокол **и есть** структура отношений между событиями, оцифрованная и криптографически верифицируемая.
|
|||
|
|
|
|||
|
|
Genesis: symbolic window 0. Перевод window_index в любые внешние time scales является задачей клиентского слоя.
|
|||
|
|
|
|||
|
|
Генезис-фраза: `«Кто контролирует прошлое, контролирует будущее. Кто контролирует настоящее, контролирует прошлое.» — Оруэлл, 1984`
|
|||
|
|
|
|||
|
|
Эволюция протокола: открытые предложения (MIPs — Montana Improvement Proposals) публикуются в Content Layer как рекомендации, реализации выпускают новые версии, операторы узлов выбирают какую версию запускать. Fork resolution детерминирован через chain_length большинство. On-chain governance отсутствует. См. раздел «Эволюция протокола».
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Четыре решённые проблемы
|
|||
|
|
|
|||
|
|
### 1. Каноническая временная координата
|
|||
|
|
|
|||
|
|
**Проблема.** Существующие системы измерения времени (NTP, GPS, PTP) измеряют физическое время через доверенную инфраструктуру. Компрометация сервера NTP или отключение спутника GPS нарушает временную шкалу для всех зависимых систем. Использование таких систем в консенсусе протокола создаёт subjective input в consensus state.
|
|||
|
|
|
|||
|
|
**Решение.** Реляционная временная структура — сеть независимых VDF-осцилляторов, производящая каноническую последовательность событий через собственную работу. Каждый узел вычисляет цепочку событий автономно через последовательное SHA-256 хэширование. Результат детерминирован и верифицируем любым участником из canonical inputs.
|
|||
|
|
|
|||
|
|
**Свойства.** Montana Time обладает четырьмя свойствами:
|
|||
|
|
|
|||
|
|
- **Монотонность.** window_index строго возрастает. VDF последователен — каждый хэш зависит от предыдущего. Канонический порядок событий однозначен.
|
|||
|
|
- **Детерминизм.** Все честные узлы согласны bit-exact на структуру событий — window_index, T_r, state_root. Каждое поле consensus state объективно вычислимо всеми узлами.
|
|||
|
|
- **Верифицируемость.** Любой может пересчитать VDF и проверить каждое событие последовательности.
|
|||
|
|
- **Независимость.** Каждый узел считает самостоятельно, опираясь только на canonical inputs протокола.
|
|||
|
|
|
|||
|
|
Montana и NTP/GPS/PTP — системы разных типов. NTP и GPS измеряют физическое время через внешние источники. Montana производит каноническую последовательность событий через собственную работу VDF и consensus.
|
|||
|
|
|
|||
|
|
### 2. Неплутократический консенсус
|
|||
|
|
|
|||
|
|
**Проблема.** В существующих консенсусных механизмах влияние пропорционально вычислительному бюджету или капиталу. Безопасность сети является функцией концентрации ресурсов, приобретаемых на рынке.
|
|||
|
|
|
|||
|
|
**Решение.** Proof of Time — механизм консенсуса, в котором влияние узла определяется исключительно длительностью его непрерывного присутствия в сети, измеренной в подписанных временных окнах. Вес узла = длина его NodeChain (количество окон, в которых узел криптографически доказал своё присутствие).
|
|||
|
|
|
|||
|
|
**Свойства.**
|
|||
|
|
|
|||
|
|
- Время — единственный ресурс, который нельзя приобрести, передать, делегировать или сконцентрировать
|
|||
|
|
- Два участника, запустившие узлы одновременно, имеют равный вес независимо от капитала
|
|||
|
|
- Стоимость атаки на консенсус выражается не в валюте, а во времени, и растёт линейно с возрастом сети
|
|||
|
|
|
|||
|
|
### 3. Window-based эмиссия
|
|||
|
|
|
|||
|
|
**Проблема.** Денежная политика фиатных валют определяется решениями комитетов и непредсказуема. Дефляционные модели с фиксированным потолком supply создают ожидание роста цены и подавляют использование как средства обмена.
|
|||
|
|
|
|||
|
|
**Решение.** Window-based эмиссия — денежная политика, в которой количество новых единиц за одно каноническое окно фиксировано и неизменно на всём горизонте существования протокола. Одно окно Montana Time порождает 13 единиц TimeCoin.
|
|||
|
|
|
|||
|
|
**Свойства.**
|
|||
|
|
|
|||
|
|
- Supply после окна W = `13 × (W + 1)` Ɉ
|
|||
|
|
- Эмиссия линейна по window_index — инфляция монотонно убывает и асимптотически стремится к нулю
|
|||
|
|
- Эмиссия не контролируется ни одним участником, комитетом или голосованием
|
|||
|
|
- Денежная политика полностью определена единственной константой (13 Ɉ за окно) и не может быть изменена после генезиса
|
|||
|
|
- Физическая скорость выпуска в SI-секундах определяется скоростью hardware сети и остаётся свойством клиентского слоя, вне scope консенсуса
|
|||
|
|
|
|||
|
|
### 4. Эволюция без on-chain governance
|
|||
|
|
|
|||
|
|
**Проблема.** On-chain governance — голосования, советы, формальные процедуры изменения правил внутри протокола — вводит subjective компоненты в consensus state и создаёт постоянную атакуемую поверхность. Любая структура которая голосует становится мишенью: компрометация моделей, подкуп участников, юрисдикционное давление. Чем формальнее governance, тем чётче определена цель атаки.
|
|||
|
|
|
|||
|
|
**Решение.** Эволюция через открытые предложения. Изменения протокола публикуются как MIPs (Montana Improvement Proposals) в Content Layer. Реализации (узловое ПО) выпускают новые версии с реализованными MIPs. Операторы узлов сами выбирают какую версию запускать. Fork resolution полностью детерминирован: при расхождении правил сеть разделяется на цепочки, каждая со своим chain_length, и узлы следуют за той цепочкой которая длиннее по их собственным правилам валидации.
|
|||
|
|
|
|||
|
|
**Свойства.**
|
|||
|
|
|
|||
|
|
- Consensus state не содержит ни одного subjective поля связанного с governance — нет attack surface
|
|||
|
|
- Последнее слово реально у узлов: оператор каждого узла самостоятельно решает что запускать, без посредников
|
|||
|
|
- MIPs существуют как тексты в Content Layer (anchor + контент), любой может опубликовать, любой может верифицировать авторство и timestamp
|
|||
|
|
- Экспертные советы (AI Council, Core Council) допустимы как **advisory** — публикуют рекомендации, обзоры, анализ безопасности; их подписи не имеют binding эффекта на consensus
|
|||
|
|
- Захват advisory совета не даёт контроля над протоколом — он даёт только возможность опубликовать рекомендацию, которую узлы могут проигнорировать
|
|||
|
|
- История эволюции навсегда в Content Layer через anchor — каждый MIP и каждое обсуждение timestamped в TimeChain
|
|||
|
|
|
|||
|
|
### Следствие: цифровой reference frame времени без человека-посредника
|
|||
|
|
|
|||
|
|
Четыре решённые проблемы порождают уникальную возможность. Любой документ, событие, состояние может быть записано в Montana с математически доказуемой привязкой к канонической позиции в последовательности событий (window_index). Anchor — 32 байта, навсегда. Ни одна существующая система не предоставляет координату времени, которая одновременно децентрализована, неплутократична, привязана к детерминированной денежной политике, свободна от on-chain governance и не зависит от внешних физических эталонов. Montana — не блокчейн с функцией timestamping. Montana — reference frame времени с функцией передачи ценности. Внешние системы могут наблюдать последовательность окон Montana и строить собственные переводы в свои локальные стандарты — этот перевод является задачей наблюдателя, не протокола.
|
|||
|
|
|
|||
|
|
Ни один человек, группа разработчиков, корпорация или совет не контролирует протокол. Изменения существуют только как открытые предложения и реализации, которые операторы узлов выбирают запускать.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Глобальные инварианты протокола
|
|||
|
|
|
|||
|
|
Глобальный инвариант — свойство, которое протокол обязан сохранять во всех своих компонентах. Нарушение в одной части = нарушение во всём протоколе. Глобальные инварианты не имеют исключений и не подлежат локальному trade-off.
|
|||
|
|
|
|||
|
|
**[I-1] Постквантовая безопасность.** Все криптографические примитивы устойчивы к квантовому компьютеру. Допустимо: SHA-256 (Grover ослабляет до 128-bit, приемлемо), FN-DSA-512 (Falcon, lattice), ML-KEM (Kyber), ML-DSA (Dilithium), STARK (hash-based ZK), lattice commitments. Запрещено: ECDLP, RSA, классический Diffie-Hellman, Pedersen commitments на эллиптических кривых, Bulletproofs, Schnorr/EdDSA.
|
|||
|
|
|
|||
|
|
**[I-2] Открытость финансового слоя.** Балансы, суммы переводов, отправители, получатели — публичны. Приватность данных приложения — через Anchor (хэш в сети, контент у владельца зашифрованным).
|
|||
|
|
|
|||
|
|
**[I-3] Детерминизм consensus state.** Любое состояние, входящее в consensus root, объективно вычислимо одинаково всеми узлами.
|
|||
|
|
|
|||
|
|
**Corollary I-3.a.** Любой механизм, результат которого в consensus state или в protocol-level behavior (mempool prioritization, gossip ordering, fork-choice, peer scoring) зависит от измерения физического мира — астрономического, геофизического, атомного, биологического или любого другого — отклоняется по нарушению I-3. Corollary применяется независимо от точности модели измерения.
|
|||
|
|
|
|||
|
|
**[I-4] Независимость TimeChain от Account state.** TimeChain продвигается из canonical inputs без зависимости от состояния Account Table. Зависимости однонаправленные: TimeChain → NodeChain → AccountChain → AccountTable.
|
|||
|
|
|
|||
|
|
**[I-5] Реализуемость без специализированного оборудования.** Все примитивы имеют production-ready open-source реализации, работающие на commodity CPU узла без TEE, без обязательного GPU, без обязательного ASIC.
|
|||
|
|
|
|||
|
|
**[I-6] Регуляторная совместимость.** Протокол опирается на механизмы, совместимые с FATF/AML/MiCA/ETF. Запрещено: privacy mixers на уровне протокола, anonymous addresses, hidden flows, ring signatures, stealth addresses.
|
|||
|
|
|
|||
|
|
**[I-7] Минимальная криптографическая поверхность.** Каждый новый примитив требует обоснования закрытием конкретного механизма. Дублирование функциональности через два разных примитива запрещено.
|
|||
|
|
|
|||
|
|
### Language firewall
|
|||
|
|
|
|||
|
|
В нормативном тексте спецификации Montana допустимые термины для описания протокольных объектов, счётчиков, периодов или интервалов: `window`, `tick`, `epoch`, `cycle` — определённые через window counts. Термины физического времени (`second`, `minute`, `hour`, `day`, `week`, `month`, `year`) применяются только в advisory контекстах клиентского слоя и в описании транспортного уровня (implementation guidance).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Montana Time
|
|||
|
|
|
|||
|
|
VDF — цифровой осциллятор в собственных единицах. `D` последовательных SHA-256 = одно окно τ₁ Montana. Число D представляет канонический объём работы, конституирующий единицу Montana Time.
|
|||
|
|
|
|||
|
|
TimeChain — глобальная каноническая цепь, поддерживаемая сетью узлов. Каждый узел вычисляет её независимо через последовательное хэширование. Результат детерминирован bit-exact — одни входные данные дают одну каноническую последовательность.
|
|||
|
|
|
|||
|
|
Токен — каноническая регистрация одного окна Montana Time. Протокол производит канонические окна и регистрирует каждое из них как 13 Ɉ.
|
|||
|
|
|
|||
|
|
### Определение Montana Time
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
montana_time(W) := W
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Единственное каноническое определение времени в протоколе. Всё остальное — производные или advisory вычисления клиентского слоя.
|
|||
|
|
|
|||
|
|
Одно окно = `D` последовательных SHA-256 итераций от предыдущего canonical anchor. D фиксируется в Genesis Decree и может адаптироваться runtime-ом через participation-ratio feedback (см. раздел «Адаптация D»).
|
|||
|
|
|
|||
|
|
### Четыре свойства
|
|||
|
|
|
|||
|
|
- **Монотонность.** `window_index` строго возрастает. VDF последователен — каждый хэш зависит от предыдущего.
|
|||
|
|
- **Детерминизм.** Все честные узлы согласны bit-exact на window_index, T_r, state_root. Каждое поле consensus state объективно вычислимо всеми узлами.
|
|||
|
|
- **Верифицируемость.** Любой может пересчитать VDF и проверить каждое событие последовательности.
|
|||
|
|
- **Независимость.** Каждый узел вычисляет канон сам, опираясь только на canonical inputs протокола.
|
|||
|
|
|
|||
|
|
Montana и NTP/GPS/PTP — системы разных типов. NTP и GPS измеряют физическое время через внешние источники. Montana производит каноническую последовательность событий через собственную работу VDF и consensus.
|
|||
|
|
|
|||
|
|
### Гранулярность
|
|||
|
|
|
|||
|
|
Атом Montana Time — одна SHA-256 итерация. Окно Montana Time — `D` атомов. Произвольный интервал — `N` окон. Все три уровня выражены в канонических числах, на которые bit-exact согласны все узлы.
|
|||
|
|
|
|||
|
|
Физическая длительность одной итерации зависит от hardware узла (наносекунды — десятки наносекунд на commodity CPU). Физическая длительность окна зависит от скорости железа узла и от участия сети. Физическая длительность — свойство конкретного наблюдателя, выводимое на клиентском слое.
|
|||
|
|
|
|||
|
|
### Time Oracle
|
|||
|
|
|
|||
|
|
Canonical `window_index` каждого proposal — верифицируемая координата события. Внешние системы используют Montana Time как reference frame:
|
|||
|
|
|
|||
|
|
- **Timestamping.** H(document) привязанный к window_index = криптографическое доказательство существования в позиции W канонической последовательности.
|
|||
|
|
- **Ordering.** Два события, привязанные к разным window_index, имеют доказуемый канонический порядок.
|
|||
|
|
- **Anchoring.** Внешний протокол якорится в Montana Time для независимой верификации порядка событий.
|
|||
|
|
|
|||
|
|
Перевод `window_index → физическое время` в любых внешних стандартах (UTC, TAI, GPS Time) является задачей клиентского слоя. Montana производит каноническую последовательность окон; внешний наблюдатель выбирает собственный метод привязки window_index к своим локальным временным единицам.
|
|||
|
|
|
|||
|
|
TimeChain хранится навсегда. Канонические координаты верифицируемы любым узлом в любой момент.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Криптография
|
|||
|
|
|
|||
|
|
Два примитива с разделёнными ролями:
|
|||
|
|
|
|||
|
|
- **SHA-256** — консенсус (TimeChain, NodeChain), адреса, Merkle-деревья, хэширование
|
|||
|
|
- **FN-DSA-512** (Falcon-512, выбран в финальном раунде NIST PQC selection, июль 2022; forthcoming FIPS 206; reference implementation production-ready) — подписи операций аккаунтов и proposals узлов
|
|||
|
|
|
|||
|
|
SHA-256 обеспечивает квантовую устойчивость консенсуса: алгоритм Гровера сокращает безопасность с 256 до 128 бит. FN-DSA-512 обеспечивает математическую постквантовую устойчивость подписей на основе NTRU-решёток. Других криптографических примитивов в протоколе нет — финансовый слой публичен, приватность данных обеспечивается на уровне приложений через Anchor.
|
|||
|
|
|
|||
|
|
### Подписи — FN-DSA-512
|
|||
|
|
|
|||
|
|
Подпись на NTRU-решётках (Falcon-512). Stateless, многоразовая. Публичный ключ закрепляется за аккаунтом при создании и используется для всех последующих операций.
|
|||
|
|
|
|||
|
|
| Компонент | Размер |
|
|||
|
|
|-----------|--------|
|
|||
|
|
| Приватный ключ | 1 281B |
|
|||
|
|
| Публичный ключ | 897B |
|
|||
|
|
| Подпись (padded) | 666B |
|
|||
|
|
|
|||
|
|
Поле suite_id в формате блока обеспечивает миграцию подписи без изменения модели состояния. Активация новой схемы требует protocol upgrade. Активная схема на момент запуска: FN-DSA-512.
|
|||
|
|
|
|||
|
|
### Адреса
|
|||
|
|
|
|||
|
|
Формат: `mt` + Base58(account_id + checksum).
|
|||
|
|
|
|||
|
|
Account_id = SHA-256("mt-account" || suite_id || pubkey). Стабильный идентификатор аккаунта. Смена ключа или схемы подписи выполняется через ChangeKey без изменения account_id — account_id привязан к первому pubkey, а текущий ключ хранится в состоянии аккаунта.
|
|||
|
|
|
|||
|
|
**Инвариант derivation.** Проверка `account_id == SHA-256("mt-account" || suite_id || pubkey)` происходит **один раз** при settle OpenAccount (apply at window close). После этого account_id — каноничный ключ записи, формула не пересчитывается. Доказательство derivation навсегда сохранено в proposal с финализированным OpenAccount. Любой аудитор может replay из proposal history. Original_pubkey не дублируется в Account Table — integrity гарантируется неизменностью proposal chain.
|
|||
|
|
|
|||
|
|
Поле `suite_id` в Account Table — **current** (мутируется ChangeKey синхронно с current_pubkey), используется для верификации текущих подписей. Original suite_id зафиксирован только в исторической OpenAccount записи в proposal chain.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Account Chain (Block Lattice)
|
|||
|
|
|
|||
|
|
Каждый аккаунт имеет собственную цепочку операций. Перевод — одна операция в цепочке отправителя. Зачисление получателю — детерминированно после финализации. Цепочки аккаунтов полностью независимы.
|
|||
|
|
|
|||
|
|
### Реестр типов объектов
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
UserObjects:
|
|||
|
|
0x01 OpenAccount
|
|||
|
|
0x02 Transfer
|
|||
|
|
0x03 ChangeKey
|
|||
|
|
0x04 Anchor
|
|||
|
|
|
|||
|
|
ControlObjects:
|
|||
|
|
0x11 NodeRegistration
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Типы операций
|
|||
|
|
|
|||
|
|
**Универсальная форма операции:**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
type (1B) | prev_hash (32B) | payload (variable) | signature (666B)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Все операции — этот шаблон. `prev_hash` связывает операции в цепочку аккаунта. `signature` — FN-DSA-512 владельца. `payload` зависит от типа. Все non-OpenAccount операции начинают payload с `sender (32B account_id)` — узел проверяет `Account Table[sender].frontier_hash == prev_hash` и `signature валиден для current_pubkey` за O(1).
|
|||
|
|
|
|||
|
|
**OpenAccount** — создание аккаунта (один раз). Единственная операция где `prev_hash = 0x00...00`:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
type 1B <- 0x01 OpenAccount
|
|||
|
|
prev_hash 32B <- 0x00...00
|
|||
|
|
payload 899B <- suite_id (2B) || pubkey (897B FN-DSA-512)
|
|||
|
|
signature 666B
|
|||
|
|
Итого: ~1 598 B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`account_id = SHA-256("mt-account" || suite_id || pubkey)` — детерминирован, не хранится в payload.
|
|||
|
|
|
|||
|
|
**Transfer** — публичный перевод:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
type 1B <- 0x02 Transfer
|
|||
|
|
prev_hash 32B
|
|||
|
|
payload 80B <- sender (32B) || link (32B receiver) || amount (16B u128 nɈ)
|
|||
|
|
signature 666B
|
|||
|
|
Итого: ~779 B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`sender` — account_id отправителя, явно. Узел проверяет `Account Table[sender].frontier_hash == prev_hash` за O(1).
|
|||
|
|
|
|||
|
|
Открытые поля: отправитель (через frontier index по prev_hash), получатель, сумма, баланс после операции (через Account Table). Псевдонимность на уровне account_id. Финансовая приватность — задача приложений (микшеры, payment channels), не протокола.
|
|||
|
|
|
|||
|
|
**ChangeKey** — смена ключа или схемы подписи:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
type 1B <- 0x03 ChangeKey
|
|||
|
|
prev_hash 32B
|
|||
|
|
payload 931B <- sender (32B) || new_suite_id (2B) || new_pubkey (897B)
|
|||
|
|
signature 666B <- подписано старым ключом
|
|||
|
|
Итого: ~1 630 B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Anchor** — криптографический якорь (привязка данных ко времени):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
type 1B <- 0x04 Anchor
|
|||
|
|
prev_hash 32B
|
|||
|
|
payload 96B <- sender (32B) || app_id (32B) || data_hash (32B)
|
|||
|
|
signature 666B
|
|||
|
|
Итого: ~795 B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Anchor не перемещает средства и не требует комиссии. Единственная операция — запись data_hash в цепочку аккаунта с привязкой к timechain_value окна финализации. Приватность данных приложения обеспечивается тем что в сеть попадает только хэш — содержимое хранится у владельца зашифрованным.
|
|||
|
|
|
|||
|
|
### Верификация баланса
|
|||
|
|
|
|||
|
|
Открытое арифметическое сравнение. Узел проверяет:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
sender != receiver
|
|||
|
|
amount > 0
|
|||
|
|
sender.balance >= amount
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`sender != receiver` запрещает self-transfer — иначе атакующий мог бы наращивать account_chain_length каждое окно через no-op переводы себе.
|
|||
|
|
|
|||
|
|
При settle (apply at window close):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
sender.balance -= amount
|
|||
|
|
receiver.balance += amount
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Баланс обновляется не при cement (quorum event), а в конце окна при батчевом apply. Между cement и settle операция необратима но баланс ещё не изменён. Никаких proofs, никакой криптографии помимо подписи и хэша.
|
|||
|
|
|
|||
|
|
### Anti-inflation
|
|||
|
|
|
|||
|
|
Чеканка из воздуха невозможна через локальный инвариант на каждом state transition.
|
|||
|
|
|
|||
|
|
**Per-user-operation invariant.** Каждое применение пользовательской операции обязано удовлетворять `Σ delta_balance == 0`:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Transfer: sender.balance -= amount, receiver.balance += amount → Σ = 0
|
|||
|
|
OpenAccount: новый аккаунт с balance = 0 → Σ = 0
|
|||
|
|
ChangeKey: только обновление current_pubkey → Σ = 0
|
|||
|
|
Anchor: только запись data_hash → Σ = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Per-proposal invariant.** Каждый финализированный proposal окна τ₁ обязан удовлетворять `delta_supply == +13 Ɉ`:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
apply_proposal step 2 (TimeCoin emission):
|
|||
|
|
если winner_class = Node: operator_account.balance += 13_000_000_000 nɈ
|
|||
|
|
если winner_class = Account: winner_account.balance += 13_000_000_000 nɈ
|
|||
|
|
|
|||
|
|
delta_supply за proposal = +13_000_000_000 nɈ ровно один раз
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
O(1) проверка на каждое state transition. Глобальный инвариант `Σ balance == 13 Ɉ × (window_index + 1)` истинен по индукции от genesis при условии что каждый переход поддерживает per-operation invariant.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
genesis state (аксиома): window_index не определён, supply = 0, Σ balance = 0
|
|||
|
|
первое окно: window_index = 0, supply = 13 Ɉ, Σ balance = 13 Ɉ
|
|||
|
|
окно k: window_index = k, supply = 13 × (k+1) Ɉ, Σ balance = 13 × (k+1) Ɉ
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Никаких откатов cemented операций не требуется — каждое cemented локально валидно по конструкции.
|
|||
|
|
|
|||
|
|
**τ₂ sanity check.** Дополнительная проверка раз в τ₂: пересчёт `Σ balance` по всей Account Table и сравнение с `13 Ɉ × (window_index + 1)`. Не load-bearing для финализации — служит для обнаружения багов реализации. Расхождение = немедленная остановка узла, дамп state для расследования.
|
|||
|
|
|
|||
|
|
### Перевод
|
|||
|
|
|
|||
|
|
Перевод на несуществующий account_id — отклоняется. Получатель обязан существовать в Account Table до получения перевода.
|
|||
|
|
|
|||
|
|
### TimeCoin
|
|||
|
|
|
|||
|
|
Победитель τ₁ регистрирует одно окно Montana Time: 13 Ɉ. При финализации proposal окна:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
если winner_class = Node: operator_account.balance += 13_000_000_000 nɈ
|
|||
|
|
если winner_class = Account: winner_account.balance += 13_000_000_000 nɈ
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Атомарное обновление баланса. Узел получает награду через привязанный operator_account (зафиксирован при NodeRegistration). Никаких отдельных coinbase-структур, никаких отдельных таблиц эмиссии. Зачисление есть состояние Account Table.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Публичное (верифицируемо всеми):
|
|||
|
|
TimeCoin: 13 Ɉ за окно (константа)
|
|||
|
|
Supply audit: supply(window_index) = 13_000_000_000 × (window_index + 1) nɈ
|
|||
|
|
Winner: winner_id в proposal header
|
|||
|
|
Все балансы: Account Table
|
|||
|
|
Все переводы: цепочки операций аккаунтов
|
|||
|
|
VDF: TimeChain values, NodeChain endpoints, подписи
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Псевдонимность на уровне account_id. Финансовая приватность — задача приложений: микшеры, payment channels, off-chain settlements.
|
|||
|
|
|
|||
|
|
### Двойная трата
|
|||
|
|
|
|||
|
|
Каждый аккаунт имеет одну цепочку. Две операции с одним prev_hash = equivocation.
|
|||
|
|
|
|||
|
|
**Без конфликта:** операция → узлы валидируют → публикуют confirmation → quorum → cemented (необратимо, ~0.3 сек). Баланс обновляется при settle (apply at window close).
|
|||
|
|
|
|||
|
|
**При конфликте (equivocation):**
|
|||
|
|
|
|||
|
|
1. Узел получает операцию X с prev_hash = H. Узел уже видел операцию Y с prev_hash = H, Y ≠ X. Форк обнаружен. Обе операции помечаются как equivocated.
|
|||
|
|
2. Если одна операция уже cemented (quorum до обнаружения конфликта) — cemented необратимо. Вторая отклоняется.
|
|||
|
|
3. Если ни одна не cemented — узлы продолжают собирать confirmations для обеих. Если одна набирает quorum → cemented, вторая отклоняется.
|
|||
|
|
4. Если через 13 окон ни одна не набрала quorum → обе отклоняются окончательно. Аккаунт продолжает с последней cemented операции. Владелец отправляет новую операцию.
|
|||
|
|
|
|||
|
|
Equivocation создаётся только владельцем аккаунта (требуется подпись). Третья сторона не может создать equivocation для чужого аккаунта. Стимул: двойная трата = потеря обеих операций.
|
|||
|
|
|
|||
|
|
### Антиспам
|
|||
|
|
|
|||
|
|
Ноль комиссий — антиспам через время. Право на операцию = доказанное время существования аккаунта.
|
|||
|
|
|
|||
|
|
#### Приоритет операции
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
account_age = current_window - creation_window
|
|||
|
|
priority(op) = account_age × windows_since_last_op
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`account_age` — возраст аккаунта в окнах. Растёт линейно. Некупуемый. `windows_since_last_op` — окна с последней операции аккаунта. Сбрасывается при каждой операции. Спамер обнуляет приоритет с каждой операцией — самонаказание.
|
|||
|
|
|
|||
|
|
При переполнении ёмкости сети — операции с наименьшим приоритетом ожидают следующего окна.
|
|||
|
|
|
|||
|
|
#### Бакеты по account_age
|
|||
|
|
|
|||
|
|
Изоляция спама. Каждый аккаунт может опубликовать максимум одну операцию за окно τ₁ (dependency rule). При переполнении сети (больше операций в мемпуле чем пропускная способность окна) — бакеты определяют **приоритет включения**. Round-robin по бакетам: одна операция из бакета 0, одна из бакета 1, ..., по кругу. Спам в бакете 0 не вытесняет операции из бакетов 1-3.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Бакет 0: account_age < 4τ₂
|
|||
|
|
Бакет 1: account_age 4τ₂ — 16τ₂
|
|||
|
|
Бакет 2: account_age 16τ₂ — 64τ₂
|
|||
|
|
Бакет 3: account_age 64τ₂+
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Границы бакетов = 4^N × τ₂. Все аккаунты: максимум 1 операция за τ₁. Бакет определяет приоритет при переполнении, не потолок TPS.
|
|||
|
|
|
|||
|
|
Новый аккаунт — бакет 0 с момента создания. 1 операция за τ₁. Вход без ожидания: получил перевод → сразу можешь отправить.
|
|||
|
|
|
|||
|
|
#### Throughput на аккаунт
|
|||
|
|
|
|||
|
|
1 операция за τ₁ (одно окно). Один Anchor содержит Merkle root от произвольного количества записей — throughput данных ограничен только размером Anchor. Для высокочастотных переводов — payment channels или application-level batching.
|
|||
|
|
|
|||
|
|
Спамер с 1000 новых аккаунтов: 1000 операций за τ₁ в бакете 0. Бакет 0 получает 1/4 от round-robin. Изолирован. Аккаунты в бакетах 1-3 не замечают.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Состояние сети
|
|||
|
|
|
|||
|
|
Глобальное состояние = Account Table + Node Table + Candidate Pool.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Account Table (запись на аккаунт):
|
|||
|
|
account_id 32B <- = SHA-256("mt-account" || suite_id || pubkey)
|
|||
|
|
balance 16B <- u128 nɈ, открыт
|
|||
|
|
suite_id 2B
|
|||
|
|
is_node_operator 1B <- 1 если аккаунт привязан как operator узла; исключён из лотереи аккаунтов
|
|||
|
|
frontier_hash 32B <- хэш последней операции в цепочке
|
|||
|
|
op_height 4B <- количество операций в цепочке
|
|||
|
|
account_chain_length 4B <- количество уникальных окон τ₁ с операцией (длина AccountChain), live
|
|||
|
|
account_chain_length_snapshot 4B <- snapshot account_chain_length на последнюю τ₂ boundary, используется лотереей
|
|||
|
|
current_pubkey 897B <- FN-DSA-512
|
|||
|
|
creation_window 4B <- окно создания аккаунта (OpenAccount)
|
|||
|
|
last_op_window 4B <- окно последней операции (для приоритета)
|
|||
|
|
|
|||
|
|
Node Table (запись на узел):
|
|||
|
|
node_id 32B <- SHA-256("mt-node" || node_pubkey), верифицируемо
|
|||
|
|
node_pubkey 897B
|
|||
|
|
suite_id 2B
|
|||
|
|
operator_account_id 32B <- account_id куда зачисляется TimeCoin при победе узла; неизменен после регистрации
|
|||
|
|
start_window 8B <- u64, окно регистрации (первое окно NodeChain)
|
|||
|
|
chain_length 8B <- u64, число окон с cemented BundledConfirmation узла; инкрементируется в apply at window close
|
|||
|
|
chain_length_snapshot 8B <- u64, = chain_length - chain_length_checkpoint[oldest]; используется в лотерее
|
|||
|
|
chain_length_checkpoints 48B <- 6 × u64, checkpoint-ы chain_length на последних 6 τ₂-boundaries
|
|||
|
|
last_confirmation_window 8B <- u64, window_index последнего окна с cemented BundledConfirmation
|
|||
|
|
|
|||
|
|
Candidate Pool (запись на кандидата):
|
|||
|
|
node_id 32B <- SHA-256("mt-node" || node_pubkey)
|
|||
|
|
node_pubkey 897B
|
|||
|
|
suite_id 2B
|
|||
|
|
operator_account_id 32B <- account_id куда зачисляется TimeCoin при победе
|
|||
|
|
proof_endpoint 32B <- endpoint после 13 000 окон VDF
|
|||
|
|
W_start 8B <- u64, окно начала VDF (заявлено кандидатом)
|
|||
|
|
registration_window 8B <- u64, окно cementing NodeRegistration
|
|||
|
|
expires 8B <- u64, registration_window + 3 × τ₂_windows
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Active node predicate (derived).** Узел считается активным если опубликовал cemented BundledConfirmation за последние 2τ₂:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
active(node, W) = (W - node.last_confirmation_window) <= 2 × τ₂_windows
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Predicate вычисляется из `last_confirmation_window` и текущего `window_index`. Применяется в quorum, confirmation_threshold, лотерее, валидации selection event.
|
|||
|
|
|
|||
|
|
### State Root
|
|||
|
|
|
|||
|
|
Merkle-дерево глобального состояния. Три подкорня обновляются при применении операций (apply_proposal и apply at window close):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
state_root = SHA-256("mt-state-root" || node_root || candidate_root || account_root)
|
|||
|
|
|
|||
|
|
node_root: Merkle root Node Table, обновляется при selection event (регистрация),
|
|||
|
|
chain_length increment (apply step 3.5), pruning узлов на τ₂.
|
|||
|
|
candidate_root: Merkle root Candidate Pool, обновляется при cementing NodeRegistration
|
|||
|
|
(добавление), selection event (удаление выбранных), expiry (удаление просроченных).
|
|||
|
|
account_root: Merkle root Account Table, обновляется батчем при apply at window
|
|||
|
|
close (все cemented операции окна применяются к state, затем
|
|||
|
|
account_root пересчитывается).
|
|||
|
|
|
|||
|
|
Все три root соответствуют settled state (после apply at window close).
|
|||
|
|
Порядок node_root → candidate_root → account_root отражает направление
|
|||
|
|
зависимостей: узлы — активные участники, кандидаты — будущие узлы, аккаунты — финансовый слой.
|
|||
|
|
Domain separator `mt-state-root` отличён от `mt-merkle-node` — hash spaces пересекаться не могут.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Структура Account Table Root:**
|
|||
|
|
|
|||
|
|
Sparse Merkle tree глубины 256, индексированный по `account_id`:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
leaf_hash(account) = SHA-256("mt-merkle-leaf" || serialize(account_record))
|
|||
|
|
internal(left, right) = SHA-256("mt-merkle-node" || left || right)
|
|||
|
|
empty_leaf = 0x00 × 32
|
|||
|
|
|
|||
|
|
account_root = root of sparse Merkle tree over Account Table
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Обновление одного аккаунта пересчитывает ровно `log₂(N)` хэшей пути от листа к корню — для N=10⁹ аккаунтов это 30 SHA-256 вычислений (~60 µs CPU).
|
|||
|
|
|
|||
|
|
**Структура Node Table Root:** аналогично, sparse Merkle tree по `node_id`. Размер сети ≤ 10⁵ узлов → пути ~17 хэшей.
|
|||
|
|
|
|||
|
|
**Структура Candidate Pool Root:** sparse Merkle tree глубины 256, индексированный по `node_id`. Empty root = `0x00 × 32`.
|
|||
|
|
|
|||
|
|
Каждый узел в Node Table — участник сети. Узел существует в таблице = участвует.
|
|||
|
|
|
|||
|
|
Все sort keys фиксированной длины. Побайтовое лексикографическое сравнение. Две реализации с одинаковыми данными строят одинаковое дерево и получают одинаковый State Root.
|
|||
|
|
|
|||
|
|
State Root коммитится в заголовке каждого proposal τ₁. `account_root`, `node_root` и `candidate_root` соответствуют settled state после apply at window close — все cemented операции окна W применены к таблицам перед сборкой proposal.
|
|||
|
|
|
|||
|
|
#### Inclusion proof
|
|||
|
|
|
|||
|
|
Любой cemented аккаунт может предоставить доказательство существования в state:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
proof = Merkle path длиной log₂(N) (~30 хэшей для N=10⁹)
|
|||
|
|
verify(proof, account_record, account_root):
|
|||
|
|
reconstruct path bottom-up; compare с account_root
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Доказательство верифицируется против `account_root` любого финализированного proposal начиная с окна когда состояние было обновлено. Не нужны архивы операций — текущее состояние самодостаточно.
|
|||
|
|
|
|||
|
|
#### Pruning
|
|||
|
|
|
|||
|
|
На τ₂ boundary применяется pruning неактивных аккаунтов:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Удалить все записи Account Table где:
|
|||
|
|
balance == 0 <- нулевой баланс
|
|||
|
|
AND last_op_window + 4τ₂ <= current_window <- нет активности 4τ₂ (52 000 окон)
|
|||
|
|
AND is_node_operator == 0 <- не привязан как operator узла
|
|||
|
|
AND нет cemented NodeRegistration в control_set <- нет pending привязки
|
|||
|
|
ожидающего apply, ссылающегося на этот account_id
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Пустой аккаунт без активности 4τ₂ — удаляется, кроме:
|
|||
|
|
- Operator-аккаунтов уже зарегистрированных узлов (`is_node_operator == 1`)
|
|||
|
|
- Аккаунтов на которые ссылается cemented NodeRegistration ожидающий apply
|
|||
|
|
|
|||
|
|
Без второго исключения возможна race: NodeRegistration cemented (operator валиден), pruning применился до apply этого NodeRegistration → аккаунт удалён → apply отклонён. Защита: pruning не трогает аккаунты, на которые есть cemented pending registration.
|
|||
|
|
|
|||
|
|
Каждое удаление пересчитывает соответствующий путь в Merkle tree (logarithmic). Pruning детерминирован, автоматичен, каноничен.
|
|||
|
|
|
|||
|
|
**Recovery semantics.** Воссоздание pruned аккаунта через новый OpenAccount с тем же ключом создаёт **новую цепочку**: frontier_hash начинается заново, op_height сбрасывается в 1, account_chain_length = 0. Старые prev_hash references на цепочку до pruning отклоняются — цепочка удалена из текущего state. История переводов до pruning не восстанавливается из текущего Account Table, но навсегда сохранена в proposals. Восстановление истории возможно через scan архива proposals.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Двигатели
|
|||
|
|
|
|||
|
|
Четыре цепочки с односторонним потоком зависимостей: TimeChain → NodeChain → AccountChain → AccountTable.
|
|||
|
|
|
|||
|
|
TimeChain — глобальные часы (ход времени). NodeChain — машинное присутствие узла (непрерывное VDF). AccountChain — человеческое присутствие аккаунта (дискретные операции). AccountTable — состояние счёта.
|
|||
|
|
|
|||
|
|
### TimeChain VDF — осциллятор
|
|||
|
|
|
|||
|
|
Первичный продукт протокола. Непрерывная последовательная SHA-256 цепочка — цифровой осциллятор Montana Time:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
T_r = SHA-256^D(T_{r-1})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
D — количество последовательных хэшей за одно окно τ₁. Каждый хэш — один тик осциллятора. D хэшей — одно колебание. TimeChain продвигается по расписанию окон. Для фиксированного индекса r значение T_r совпадает у всех честных узлов. Каждый узел вычисляет TimeChain независимо — результат детерминирован.
|
|||
|
|
|
|||
|
|
TimeChain не зависит от состояния, транзакций и поведения отдельных узлов. Даже при отказе всего Account слоя часы продолжают тикать.
|
|||
|
|
|
|||
|
|
### NodeChain — персональная цепочка узла
|
|||
|
|
|
|||
|
|
Криптографическое доказательство присутствия конкретного node_id в каждом окне. Якорится в TimeChain каждое окно:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
S_{i,s,0} = SHA-256(S_{i,s-1,m} || T_s || node_id_i)
|
|||
|
|
S_{i,s,j+1} = SHA-256(S_{i,s,j}) для j = 0..m-1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Три компонента seed: предыдущий endpoint (непрерывность цепочки), значение TimeChain (протокольное время), node_id (идентичность). m последовательных хэшей за окно — одно звено NodeChain.
|
|||
|
|
|
|||
|
|
Инициализация: для первого окна нового узла предыдущий endpoint отсутствует. NodeChain init привязан к каноническим данным proposal окна selection event, в котором кандидат выбран:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
S_{i,0,0} = SHA-256("mt-nodechain-init" || state_root || timechain_value || node_id_i)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Где `state_root` и `timechain_value` из proposal окна selection event. Канонические, верифицируемые.
|
|||
|
|
|
|||
|
|
state_root и timechain_value из proposal header окна selection event. Оба канонические. Предвычисление VDF невозможно — timechain_value неизвестен до закрытия окна. Grinding surface = ноль. Верифицируем любым узлом.
|
|||
|
|
|
|||
|
|
NodeChain зависит от TimeChain. TimeChain не зависит от NodeChain.
|
|||
|
|
|
|||
|
|
### AccountChain — персональная цепочка аккаунта
|
|||
|
|
|
|||
|
|
Криптографическое доказательство присутствия конкретного account_id в дискретных моментах. Каждое звено — финализированная операция аккаунта (Transfer, OpenAccount, ChangeKey, Anchor). Linking через `prev_hash` (хэш предыдущей операции в цепочке аккаунта). Якорится в TimeChain через timechain_value момента финализации каждой операции.
|
|||
|
|
|
|||
|
|
Длина AccountChain — количество окон τ₁ в которых аккаунт имел cemented операцию:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
account_chain_length(account, W) = | { w : w <= W, аккаунт имел cemented операцию в окне w } |
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Dependency rule ограничивает аккаунт одной операцией за окно τ₁ — поэтому длина AccountChain совпадает с числом окон активности. Поле `account_chain_length` хранится в Account Table, обновляется при apply операции:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
on_operation_applied(operation, window W):
|
|||
|
|
account = operation.account_id
|
|||
|
|
account.account_chain_length += 1
|
|||
|
|
account.last_op_window = W
|
|||
|
|
account.op_height += 1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Параллелизм с NodeChain:**
|
|||
|
|
|
|||
|
|
| Свойство | NodeChain | AccountChain |
|
|||
|
|
|----------|-----------|--------------|
|
|||
|
|
| Источник | node_pubkey | account_pubkey |
|
|||
|
|
| Идентификатор | node_id | account_id |
|
|||
|
|
| Тип присутствия | машинное | человеческое |
|
|||
|
|
| Ритм | непрерывный (каждое окно VDF) | дискретный (окно с операцией) |
|
|||
|
|
| Длина | chain_length (окна) | account_chain_length (окна) |
|
|||
|
|
| Единица длины | окно τ₁ | окно τ₁ |
|
|||
|
|
| Накопление | автоматически с каждым окном | через активность пользователя |
|
|||
|
|
| Якорь во времени | timechain_value каждое окно | timechain_value окна с операцией |
|
|||
|
|
| Защита от подделки | VDF необратим | подпись FN-DSA-512 |
|
|||
|
|
| Linking | endpoint предыдущего звена | prev_hash предыдущей операции |
|
|||
|
|
| Защита от Sybil | 13 000 окон VDF + selection event + adaptive VDF | накопление окон требует работы в каждом окне |
|
|||
|
|
|
|||
|
|
Узел доказывает присутствие непрерывной работой машины в каждом окне. Аккаунт доказывает присутствие активным использованием сети — каждая операция фиксирует одно окно человеческого бытия на временной шкале Montana. Оба механизма математически верифицируемы, оба производят запись на одной шкале времени.
|
|||
|
|
|
|||
|
|
AccountChain зависит от TimeChain напрямую — каждая операция привязана к timechain_value момента финализации. AccountChain не зависит от NodeChain по построению — цепочка аккаунта существует независимо от того какой узел победил в окне финализации.
|
|||
|
|
|
|||
|
|
### VDF Reveal и лотерея
|
|||
|
|
|
|||
|
|
В лотерее участвуют два класса субъектов: **узлы** (через VDF_Reveal) и **аккаунты** (через cemented операции). Каждый класс производит ticket, взвешенный по длине своей цепочки.
|
|||
|
|
|
|||
|
|
Confirmers (~100 узлов с наибольшим chain_length) публикуют BundledConfirmation для финализации окна. Все узлы с weighted_ticket < target публикуют VDF_Reveal для лотереи. VDF_Reveal цементируется через BundledConfirmation: confirmers включают полученные VDF_Reveals в свои bundles наряду с UserObjects и ControlObjects. Cement threshold тот же — 67% active_chain_length. Proposer извлекает только cemented reveals — дискреция над лотереей = ноль.
|
|||
|
|
|
|||
|
|
#### Класс 1: узлы
|
|||
|
|
|
|||
|
|
После завершения VDF окна W каждый узел вычисляет свой ticket:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
ticket_node = -ln(endpoint_node / 2^256)
|
|||
|
|
|
|||
|
|
seniority_bonus = min(chain_length / 69, chain_length_snapshot)
|
|||
|
|
lottery_weight = chain_length_snapshot + seniority_bonus
|
|||
|
|
|
|||
|
|
weighted_ticket_node = ticket_node / lottery_weight
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`chain_length_snapshot` — количество окон с cemented BundledConfirmation за последние 6τ₂ (78 000 окон). Вычисляется через checkpoint-механизм: на каждой τ₂-boundary фиксируется checkpoint chain_length; snapshot = chain_length - checkpoint_6τ₂_ago. Хранится 6 checkpoint-ов (48B на узел). Обновляется на τ₂-boundary (шаг 3.6 apply_proposal).
|
|||
|
|
|
|||
|
|
`seniority_bonus` — добавка за накопленный абсолютный chain_length, ограниченная сверху размером snapshot (cap). Делитель 69 (цифровой корень = 6, Tesla). Cap = snapshot: максимальное преимущество старожила ≈ 2x относительно новичка с полным snapshot. При chain_length < 69 seniority_bonus = 0 (целочисленное деление): первые 69 окон после регистрации lottery_weight = snapshot.
|
|||
|
|
|
|||
|
|
Разделение весов:
|
|||
|
|
- **Лотерея (эмиссия):** `lottery_weight = chain_length_snapshot + seniority_bonus`. Недавняя работа (snapshot) доминирует, longevity даёт bounded bonus.
|
|||
|
|
- **Quorum (безопасность):** абсолютный `chain_length`. Старожилы доминируют в финализации.
|
|||
|
|
|
|||
|
|
Если weighted_ticket_node < target — узел кандидат и публикует VDF_Reveal:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
VDF_Reveal:
|
|||
|
|
node_id 32B
|
|||
|
|
window_index 4B <- индекс τ₁
|
|||
|
|
endpoint 32B <- S_{i,s,m}
|
|||
|
|
start_window 4B <- окно начала NodeChain (для верификации)
|
|||
|
|
signature 666B <- FN-DSA-512, подписано node_pubkey
|
|||
|
|
Итого: ~738B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Любой активный узел может стать кандидатом лотереи — lottery_weight основан на недавней работе (snapshot 6τ₂), старожилы получают bounded seniority bonus.
|
|||
|
|
|
|||
|
|
#### Класс 2: аккаунты
|
|||
|
|
|
|||
|
|
Аккаунт автоматически становится кандидатом если у него есть cemented операция в окне W. Endpoint вычисляется детерминированно:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
operation_for_lottery(account, W) = единственная cemented операция аккаунта в окне W
|
|||
|
|
(dependency rule: максимум одна)
|
|||
|
|
|
|||
|
|
endpoint_account(W) = SHA-256(
|
|||
|
|
"mt-account-lottery" ||
|
|||
|
|
account_id ||
|
|||
|
|
hash(operation_for_lottery) ||
|
|||
|
|
timechain_value(W)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
account_length_at_lottery = account.account_chain_length_snapshot
|
|||
|
|
|
|||
|
|
ticket_account = -ln(endpoint_account / 2^256)
|
|||
|
|
weighted_ticket_account = ticket_account / account_length_at_lottery
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`account_chain_length_snapshot` обновляется на каждой τ₂ boundary копией текущего `account_chain_length`. Между τ₂ boundaries snapshot frozen — все узлы используют одно значение, лотерея детерминирована.
|
|||
|
|
|
|||
|
|
Аккаунт без операции в окне W не участвует в лотерее этого окна.
|
|||
|
|
|
|||
|
|
**Исключение operator-аккаунтов.** Аккаунт с `is_node_operator = 1` исключён из лотереи аккаунтов. Узел получает вес через NodeChain; operator_account только хранит TimeCoin. Двойной счёт исключён конструкцией. Оператор узла, желающий участвовать в лотерее аккаунтов, использует отдельный персональный аккаунт.
|
|||
|
|
|
|||
|
|
**Защита от grinding:** dependency rule ограничивает аккаунт одной операцией за окно — один лотерейный билет. timechain_value(W) известен только после закрытия окна — endpoint непредсказуем заранее.
|
|||
|
|
|
|||
|
|
#### Определение winner-а (Lookback Leadership)
|
|||
|
|
|
|||
|
|
Winner окна W-1 определяется при cementing proposal окна W. Proposer окна W = winner окна W-2 (канонически известен из cemented state).
|
|||
|
|
|
|||
|
|
**Механика:**
|
|||
|
|
|
|||
|
|
1. Окно W-1 завершается: confirmers публикуют BundledConfirmation_{W-1} (операции окна W-1 + VDF_Reveals окна W-2), кандидаты публикуют VDF_Reveal_{W-1}, аккаунты публикуют операции.
|
|||
|
|
2. `proposer_W = winner_{W-2}` (канонически определён из proposal_{W-1}).
|
|||
|
|
3. Окно W начинается. Confirmers получают VDF_Reveals_{W-1} через P2P и включают их в BundledConfirmation_W наряду с операциями окна W. VDF_Reveal идентифицируется по `window_index = W-1`.
|
|||
|
|
4. VDF_Reveal_{W-1} cemented когда confirmers с суммарным chain_length ≥ 67% active_chain_length включили его в свои BundledConfirmation_W. Cement status каноничен — каждый узел отслеживает его независимо по P2P bundles.
|
|||
|
|
5. Proposer_W собирает BundledConfirmation-ы окна W-1 и cemented set:
|
|||
|
|
```
|
|||
|
|
included_bundles_{W-1} = BundledConfirmation-ы окна W-1 из view proposer-а
|
|||
|
|
(суммарный chain_length ≥ 67% active_chain_length)
|
|||
|
|
included_reveals_{W-1} = VDF_Reveal-ы окна W-1, cemented через
|
|||
|
|
BundledConfirmation окна W (67% active_chain_length)
|
|||
|
|
```
|
|||
|
|
6. Из included_reveals_{W-1} извлекаются все node endpoints. Из included_bundles_{W-1} извлекаются все cemented account operations.
|
|||
|
|
7. `winner_{W-1} = argmin(weighted_ticket)` среди всех кандидатов (cemented VDF_Reveal nodes + аккаунты).
|
|||
|
|
8. Proposer_W публикует proposal_W, содержащий:
|
|||
|
|
- `included_bundles_{W-1}` (canonical view финализации)
|
|||
|
|
- `included_reveals_{W-1}` (cemented set лотереи)
|
|||
|
|
- `winner_{W-1}` (получатель 13 Ɉ за окно W-1)
|
|||
|
|
- control_set, state_root, TimeCoin transfer
|
|||
|
|
9. Сеть валидирует proposal_W:
|
|||
|
|
- Proposer = winner_{W-2}? (канонически проверяемо)
|
|||
|
|
- included_bundles содержат ≥ 67% active_chain_length? (проверяемо из Node Table)
|
|||
|
|
- included_reveals_{W-1} = cemented set VDF_Reveals окна W-1? (валидатор сверяет с собственным tracking cement status из BundledConfirmation окна W)
|
|||
|
|
- winner_{W-1} = argmin из (included_reveals ∪ account_candidates)? (детерминированно проверяемо)
|
|||
|
|
- state_root корректен? (независимый пересчёт)
|
|||
|
|
10. Если 67% active_chain_length подписывают proposal_W → proposal cemented. Winner_{W-1} получает 13 Ɉ. Winner_{W-1} становится proposer_{W+1}.
|
|||
|
|
11. Если < 67% подписали → proposal отклонён. Fallback: `fallback_proposer_W = second_min(weighted_ticket)` окна W-2. Fallback cascade: third_min, fourth_min, etc.
|
|||
|
|
|
|||
|
|
**Cross-window cementing timeline.** VDF_Reveals окна W-1 публикуются при завершении окна W-1 (VDF computation = window duration). Цементируются в BundledConfirmation окна W. Между публикацией reveals и сборкой proposal — целое окно. Timing constraint отсутствует.
|
|||
|
|
|
|||
|
|
**Leader skin in the game.** Proposer_W публикует свой VDF_Reveal для окна W. Если его proposal отклонён (< 67% подписей сети), его VDF_Reveal исключается из пула кандидатов окна W. Потеря lottery ticket = экономический кнут за цензуру или бездействие. Отказ подписать proposal = implicit rejection от каждого узла.
|
|||
|
|
|
|||
|
|
**Genesis bootstrap.** proposer_0 и proposer_1 = bootstrap-узел (единственный в Genesis Decree). Начиная с proposer_2 = winner_0, стандартная lookback логика.
|
|||
|
|
|
|||
|
|
#### Калибровка target
|
|||
|
|
|
|||
|
|
Target калиброван на ~13 кандидатов VDF_Reveal за окно. Калибровка на τ₂:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
target_new = target_old × (13 / actual_candidates_per_window)
|
|||
|
|
actual_candidates_per_window = total_reveals_за_τ₂ / 13 000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Трафик reveal за окно: ~13 VDF_Reveal × 738B ≈ 9.6 KB (P2P gossip; далее включаются в BundledConfirmation для cementing). Аккаунты участвуют через cemented операции в BundledConfirmation — дополнительного трафика для аккаунтов нет.
|
|||
|
|
|
|||
|
|
#### Валидация VDF_Reveal
|
|||
|
|
|
|||
|
|
1. Подпись FN-DSA-512 соответствует node_pubkey из Node Table
|
|||
|
|
2. window_index = текущий τ₁
|
|||
|
|
3. node_id существует в Node Table
|
|||
|
|
4. weighted_ticket < target
|
|||
|
|
5. endpoint верифицируем: пересчёт NodeChain VDF от предыдущего endpoint
|
|||
|
|
|
|||
|
|
#### Валидация участия аккаунта
|
|||
|
|
|
|||
|
|
1. account_id существует в Account Table
|
|||
|
|
2. account_chain_length_snapshot > 0
|
|||
|
|
3. Аккаунт имеет cemented операцию в окне W
|
|||
|
|
4. operation_for_lottery определена детерминированно (dependency rule: одна операция за окно)
|
|||
|
|
5. weighted_ticket_account < target
|
|||
|
|
|
|||
|
|
### Account — содержимое блока
|
|||
|
|
|
|||
|
|
Приём, верификация объектов и формирование набора. Два класса объектов:
|
|||
|
|
|
|||
|
|
**UserObjects** — пользовательские операции:
|
|||
|
|
|
|||
|
|
| Тип | Описание | Валидация |
|
|||
|
|
|-----|----------|-----------|
|
|||
|
|
| Transfer | Публичный перевод | FN-DSA-512 подпись, prev_hash, sender != receiver, amount > 0, sender.balance >= amount, получатель существует |
|
|||
|
|
| OpenAccount | Создание аккаунта | FN-DSA-512 подпись, prev_hash = 0, account_id = SHA-256("mt-account" || pubkey) не существует в Account Table |
|
|||
|
|
| ChangeKey | Смена ключа | FN-DSA-512 подпись старым ключом, new_pubkey |
|
|||
|
|
| Anchor | Якорь данных ко времени | FN-DSA-512 подпись, prev_hash, app_id = 32B, data_hash = 32B |
|
|||
|
|
|
|||
|
|
**ControlObjects** — объекты управляющие составом сети:
|
|||
|
|
|
|||
|
|
| Тип | Описание | Валидация |
|
|||
|
|
|-----|----------|-----------|
|
|||
|
|
| NodeRegistration | Регистрация узла (кандидатура) | FN-DSA-512 подпись, node_id уникален (не в Node Table и не в Candidate Pool), operator_account_id существует, proof_endpoint верифицируем через VDF от candidate_vdf_init |
|
|||
|
|
|
|||
|
|
Каждый узел валидирует объекты обоих классов локально при получении. Валидные объекты ретранслируются по P2P.
|
|||
|
|
|
|||
|
|
Все объекты — UserObjects, ControlObjects и VDF_Reveals — финализируются (cemented) одинаково: через 67% active_chain_length подтверждения в BundledConfirmation. Cemented status объективен и одинаков для всех узлов. Дискреция победителя над включением ControlObjects и VDF_Reveals = ноль.
|
|||
|
|
|
|||
|
|
#### Proposal
|
|||
|
|
|
|||
|
|
Proposal содержит **control_set** и метаданные окна. UserObjects применяются к Account Table батчем при settle (apply at window close); в proposal они не повторяются. ControlObjects применяются к Node Table в apply_proposal step 1 в детерминированном порядке.
|
|||
|
|
|
|||
|
|
**control_set(proposal окна W)** определён формулой:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
control_set = {
|
|||
|
|
ControlObject c :
|
|||
|
|
c.cemented_window > previous_proposal.window
|
|||
|
|
AND c.cemented_window <= W
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
сортировка: (cemented_window asc, op_hash lex asc)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Где `previous_proposal.window` — окно предыдущего финализированного proposal в цепочке. Множество детерминировано: cemented_window — каноническое поле объекта (известно всем узлам через BundledConfirmation), op_hash — детерминирован.
|
|||
|
|
|
|||
|
|
Победитель **обязан** включить весь control_set целиком. Пропуск или добавление лишнего ControlObject = невалидный proposal = fallback. Каждый узел независимо вычисляет ожидаемый control_set по той же формуле и сравнивает с proposer's set.
|
|||
|
|
|
|||
|
|
Форки аккаунтов (две операции с одним prev_hash) разрешаются голосованием узлов весом chain_length. 67% active_chain_length за одну операцию → побеждает (см. раздел «Двойная трата»).
|
|||
|
|
|
|||
|
|
#### Закрытие окна (Lookback Leadership Finalization)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Window W-1: confirmers publish BundledConfirmation_{W-1}
|
|||
|
|
(W-1 operations + W-2 VDF_Reveals)
|
|||
|
|
VDF_{W-1} completes → candidates publish VDF_Reveal_{W-1}
|
|||
|
|
accounts publish cemented operations
|
|||
|
|
│
|
|||
|
|
Window W: confirmers publish BundledConfirmation_W
|
|||
|
|
(W operations + W-1 VDF_Reveals)
|
|||
|
|
W-1 VDF_Reveals cemented (67% active_chain_length)
|
|||
|
|
│
|
|||
|
|
proposer_W = winner_{W-2} (canonical from proposal_{W-1})
|
|||
|
|
proposer_W extracts cemented reveals → winner_{W-1}
|
|||
|
|
proposer_W publishes proposal_W
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
┌───────────────────────────────┐
|
|||
|
|
│ proposal_W validation │
|
|||
|
|
│ included_bundles ≥ 67%? │
|
|||
|
|
│ included_reveals = cemented? │
|
|||
|
|
│ winner_{W-1} = argmin? │
|
|||
|
|
│ state_root correct? │
|
|||
|
|
└───────────┬───────────────────┘
|
|||
|
|
│ 67% sign
|
|||
|
|
▼
|
|||
|
|
proposal_W cemented
|
|||
|
|
winner_{W-1} receives 13 Ɉ
|
|||
|
|
winner_{W-1} = proposer_{W+1}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **Lookback Leader.** `proposer_W = winner_{W-2}` — канонически определён из cemented proposal_{W-1}. Каждый узел вычисляет proposer_W детерминированно из canonical state.
|
|||
|
|
- **Cemented reveals.** VDF_Reveals окна W-1 публикуются при завершении W-1, цементируются чер<D0B5><D180>з BundledConfirmation окна W (confirmers включают полученные reveals в свои bundles). `included_reveals_{W-1}` = cemented set (67% active_chain_length). Proposer извлекает cemented reveals и cemented account operations из included_bundles_{W-1}, определяет `winner_{W-1} = argmin(weighted_ticket)`. Дискреция proposer-а над составом лотереи = ноль.
|
|||
|
|
- **Canonical acceptance.** Сеть валидирует proposal_W: (a) proposer = winner_{W-2}, (b) included_bundles ≥ 67% active_chain_length, (c) included_reveals = cemented set VDF_Reveals окна W-1, (d) winner_{W-1} = argmin из (cemented reveals ∪ account_candidates), (e) state_root корректен. Если 67% active_chain_length подписывают proposal_W → cemented. Canonical set зафиксирован.
|
|||
|
|
- **Leader skin in the game.** Proposer_W участвует в лотерее окна W через свой VDF_Reveal (cemented в BundledConfirmation окна W+1). При отклонении proposal (< 67% подписей) — VDF_Reveal proposer-а исключается из пула кандидатов окна W. Отказ подписать proposal = implicit rejection. Отдельного censorship vote нет.
|
|||
|
|
- **Fallback cascade.** Если proposal от proposer_W отклонён или отсутствует, роль переходит к `fallback_1 = second_min(weighted_ticket)` окна W-2, затем third_min, etc. Все канонически известны из cemented state.
|
|||
|
|
- **ControlObjects.** ControlObjects попадают в control_set proposal по моменту cement — canonically deterministic.
|
|||
|
|
|
|||
|
|
**Свойство темпа сети.** Сеть продвигается со скоростью медианного активного набора узлов. Quorum требует подписей большинства по chain_length — быстрейший узел ждёт, пока достаточно других успеет. Hardware progress ускоряет сеть естественно — когда ускоряется медиана, participation_ratio растёт выше 0.95, D адаптивно увеличивается.
|
|||
|
|
|
|||
|
|
**One-window lag награды.** 13 Ɉ за окно W-1 зачисляются winner_{W-1} при cementing proposal_W. Задержка в одно окно между завершением работы и получением награды.
|
|||
|
|
|
|||
|
|
#### Proposer (Lookback Leader)
|
|||
|
|
|
|||
|
|
`proposer_W = winner_{W-2}` — канонически определён из cemented proposal_{W-1}. Proposer собирает proposal_W:
|
|||
|
|
|
|||
|
|
- **included_bundles_{W-1}**: BundledConfirmation окна W-1 (суммарный chain_length ≥ 67% active_chain_length). Из included_bundles извлекаются cemented account operations для лотереи.
|
|||
|
|
- **included_reveals_{W-1}**: VDF_Reveals окна W-1, cemented через BundledConfirmation окна W (67% active_chain_length). Из cemented reveals + cemented account operations определяется `winner_{W-1}` (получатель 13 Ɉ за окно W-1).
|
|||
|
|
- **control_set**: все cemented ControlObjects в окнах (previous_proposal.window, W]. Свобода = ноль (каноничен).
|
|||
|
|
- **State Root snapshot**: account_root, node_root и candidate_root после apply at window close (все cemented операции + control objects + selection event + TimeCoin transfer to winner_{W-1} применены батчем).
|
|||
|
|
|
|||
|
|
Свобода proposer: included_bundles ограничены порогом 67%. included_reveals детерминированы cement status-ом. control_set детерминирован формулой. State root и winner_{W-1} вычисляются из cemented sets — каждый валидатор проверяет корректность детерминированно.
|
|||
|
|
|
|||
|
|
Proposal с набором included_bundles < 67% active_chain_length, неверным included_reveals (не совпадает с cemented set), неверным winner_{W-1}, пропущенным cemented ControlObject, или неверным state_root отклоняется → fallback на second_min(weighted_ticket) окна W-2.
|
|||
|
|
|
|||
|
|
#### Финальность proposal
|
|||
|
|
|
|||
|
|
Финальность proposal = подпись proposer_node_id на proposal header (верифицируемая против Node Table[proposer_node_id].node_pubkey) + независимая верифицируемость состояния.
|
|||
|
|
|
|||
|
|
1. Proposer (proposer_node_id) публикует подписанный proposal header + control_set
|
|||
|
|
2. Каждый узел проверяет `window_index == prev_proposal.window_index + 1`, `protocol_version >= prev_proposal.protocol_version` и `protocol_version <= local_max_supported_version`
|
|||
|
|
3. Каждый узел независимо вычисляет ожидаемый control_set по формуле и сравнивает с proposer's
|
|||
|
|
4. Каждый узел применяет control_set + TimeCoin детерминированно в порядке (cemented_window asc, op_hash lex asc)
|
|||
|
|
5. Каждый узел сравнивает вычисленный state_root с заявленным в proposal
|
|||
|
|
6. Совпадает — proposal принят
|
|||
|
|
7. Не совпадает — proposal отклонён, fallback на второе место
|
|||
|
|
|
|||
|
|
Финальность операций аккаунтов — отдельный процесс через подтверждения (67% active_chain_length), не через proposal.
|
|||
|
|
|
|||
|
|
Proposal header:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Proposal header:
|
|||
|
|
prev_proposal_hash 32B
|
|||
|
|
window_index 8B <- u64, индекс окна τ₁ с genesis; == prev_proposal.window_index + 1
|
|||
|
|
protocol_version 4B <- u32, активная версия протокола на момент window_index
|
|||
|
|
control_root 32B <- Merkle root control_set (каноничен)
|
|||
|
|
node_root 32B <- Merkle root Node Table (обновляется каждое окно)
|
|||
|
|
candidate_root 32B <- Merkle root Candidate Pool
|
|||
|
|
account_root 32B <- Merkle root Account Table после apply at window close
|
|||
|
|
state_root 32B <- SHA-256("mt-state-root" || node_root || candidate_root || account_root)
|
|||
|
|
timechain_value 32B
|
|||
|
|
included_bundles_root 32B <- Merkle root списка (confirmer_id, bundle_hash)
|
|||
|
|
BundledConfirmation окна W-1 (≥ 67% active_chain_length)
|
|||
|
|
included_reveals_root 32B <- Merkle root списка VDF_Reveal-ов окна W-1,
|
|||
|
|
cemented через BundledConfirmation окна W
|
|||
|
|
winner_class 1B <- 1=Node, 2=Account (winner окна W-1)
|
|||
|
|
winner_endpoint 32B <- endpoint winner-а окна W-1 (NodeChain или account lottery)
|
|||
|
|
winner_id 32B <- получатель TimeCoin за окно W-1: node_id (winner_class=1)
|
|||
|
|
или account_id (winner_class=2)
|
|||
|
|
proposer_node_id 32B <- winner_{W-2}, канонически определённый из proposal_{W-1}
|
|||
|
|
target 8B <- текущий target лотереи
|
|||
|
|
fallback_depth 1B <- 1 = первое место, 2+ = fallback
|
|||
|
|
signature 666B <- FN-DSA-512, подпись header Node Table[proposer_node_id].node_pubkey
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Все поля proposal header канонически вычислимы bit-exact из предыдущего state и cemented set окна W. Каждое поле имеет источником либо canonical state, либо детерминированную функцию от canonical state.
|
|||
|
|
|
|||
|
|
**Разделение ролей winner_id и proposer_node_id.** Это два независимых поля с разными назначениями:
|
|||
|
|
|
|||
|
|
- `winner_id` — получатель TimeCoin. Аккаунт или узел, выигравший лотерею окна. Используется только в apply_proposal step 2 для зачисления 13 Ɉ.
|
|||
|
|
- `proposer_node_id` — узел ответственный за сборку и публикацию proposal. Подписывает header своим node_pubkey. Верификация подписи proposal — против `Node Table[proposer_node_id].node_pubkey`, всегда.
|
|||
|
|
|
|||
|
|
Когда `winner_class = Account`, winner — это аккаунт без node_pubkey, физически не способный подписать proposal. Подписывает всегда узел-proposer (ближайший по weighted_ticket). TimeCoin при этом получает winner (аккаунт), proposer не получает дополнительной награды.
|
|||
|
|
|
|||
|
|
**Инварианты Proposal header:**
|
|||
|
|
|
|||
|
|
- `window_index == prev_proposal.window_index + 1` (монотонность, шаг 1)
|
|||
|
|
- `protocol_version >= prev_proposal.protocol_version` (не убывает; изменяется только через software upgrade узла, см. раздел «Эволюция протокола»)
|
|||
|
|
- `protocol_version <= local_max_supported_version` (узел **обязан отклонить** proposal с protocol_version которую его реализация не поддерживает; принятие неизвестной версии = принятие непроверяемых правил = нарушение безопасности)
|
|||
|
|
|
|||
|
|
**Cemented window** объекта — `window_index` proposal-а в котором BundledConfirmation с этим объектом достиг quorum. Определён детерминированно для каждого cemented объекта.
|
|||
|
|
|
|||
|
|
**Settled window** объекта — `window_index` proposal-а в котором объект был применён к state:
|
|||
|
|
- Для UserObjects: `settled_window = cemented_window` (apply batch at window close того же окна). Следующая операция от того же sender возможна в окне `cemented_window + 1` (dependency rule)
|
|||
|
|
- Для ControlObjects: `settled_window` = window_index первого proposal где объект попал в control_set (обычно `cemented_window + 1`)
|
|||
|
|
|
|||
|
|
Fallback: если proposal от `proposer_W = winner_{W-2}` отклонён (< 67% подписей) или отсутствует (proposer offline), роль переходит к `fallback_1 = second_min(weighted_ticket)` окна W-2. Если fallback_1 тоже отклонён — к third_min, и т.д. Вся cascade канонически определена из cemented state окна W-2.
|
|||
|
|
|
|||
|
|
При fallback `proposer_node_id` меняется; `winner_{W-1}` определяется fallback-proposer-ом из cemented set (тот же cemented set — canonical для всех узлов). Новый proposer подписывает header своим node_pubkey, `fallback_depth` инкрементируется.
|
|||
|
|
|
|||
|
|
**Leader penalty при отклонении:** endpoint proposer-а, чей proposal отклонён, исключается из lottery пула текущего окна W. Proposer теряет шанс на 13 Ɉ. Это экономический кнут за бездействие или цензуру.
|
|||
|
|
|
|||
|
|
**Полная симметрия fallback:** молчание первого proposer переводит обязанность сборки proposal к следующему узлу. Награда за окно W-1 привязана к лотерейному билету и гарантирована, если хотя бы один узел в сети соберёт валидный proposal через fallback cascade.
|
|||
|
|
|
|||
|
|
#### Непрерывность VDF
|
|||
|
|
|
|||
|
|
VDF следующего окна вычисляется непрерывно, не ожидая завершения финализации предыдущего. TimeChain для окна N+1 детерминирован — каждый узел вычисляет его независимо. NodeChain для окна N+1 стартует сразу после закрытия окна N, используя собственный endpoint текущего окна и новое значение TimeChain. Reveal phase и финализация происходят параллельно с началом VDF следующего окна.
|
|||
|
|
|
|||
|
|
#### Confirmations (финализация операций и control objects)
|
|||
|
|
|
|||
|
|
Confirmers — узлы с chain_length >= confirmation_threshold. Подтверждают **все** валидные объекты окна (UserObjects + ControlObjects) от имени сети.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
active_chain_length(W) = Σ node.chain_length
|
|||
|
|
для node ∈ Node Table : active(node, W)
|
|||
|
|
|
|||
|
|
confirmation_threshold(W) = active_chain_length(W) / 130
|
|||
|
|
~130 confirmers при large-scale сети (active_chain_length / 130).
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Только активные узлы (cemented BundledConfirmation за последние 2τ₂) учитываются. Мёртвый вес исключён конструкцией. Сканирование Node Table для вычисления `active_chain_length` — O(|Node Table|) ≤ 10⁵ записей, миллисекунды.
|
|||
|
|
|
|||
|
|
Confirmer собирает все валидные объекты за окно и публикует один BundledConfirmation. Bundle содержит два класса хэшей: (1) операции текущего окна W (UserObjects + ControlObjects) и (2) VDF_Reveals предыдущего окна W-1 (лотерейные билеты, опубликованные при завершении W-1 и полученные через P2P):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
BundledConfirmation:
|
|||
|
|
node_id 32B
|
|||
|
|
endpoint 32B <- текущий NodeChain endpoint (доказывает chain_length)
|
|||
|
|
window_index 4B
|
|||
|
|
op_count 2B
|
|||
|
|
op_hashes[] op_count × 32B <- хэши UserObjects и ControlObjects окна W
|
|||
|
|
reveal_count 2B
|
|||
|
|
reveal_hashes[] reveal_count × 32B <- хэши VDF_Reveals окна W-1
|
|||
|
|
signature 666B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Один BundledConfirmation per (node_id, window_index). Повторный отклоняется. Endpoint верифицируем: пересчёт m хэшей от предыдущего известного endpoint данного узла. `node.chain_length` хранится в Node Table и инкрементируется в `apply_proposal` шаг 3.5 для каждого узла с cemented BundledConfirmation в окне W. Endpoint BundledConfirmation верифицирует вычисление VDF за соответствующее окно.
|
|||
|
|
|
|||
|
|
Объект финализирован (cemented) когда подтверждения от confirmers с суммарным chain_length > quorum. Cemented — необратимо. Типичное время: quorum event. Это правило применяется одинаково к UserObjects, ControlObjects и VDF_Reveals: cemented status объективен и каноничен для всех узлов. VDF_Reveals окна W-1 цементируются в BundledConfirmation окна W (cross-window cementing).
|
|||
|
|
|
|||
|
|
**Confirmation cutoff (детерминизм cemented set).** Cemented set окна W фиксируется proposer-ом окна W+1 через frozen view (Lookback Leadership). Proposer_{W+1} включает в proposal_{W+1} все BundledConfirmation окна W из своего view с суммарным chain_length ≥ 67% active_chain_length. Этот frozen view становится каноническим cemented set после cementing proposal_{W+1} сетью.
|
|||
|
|
|
|||
|
|
**Dependency rule (детерминизм apply).** Одно правило: confirmer подтверждает операцию только если все её зависимости разрешены из settled state окна W-1.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Операция валидна для inclusion в BundledConfirmation окна W если:
|
|||
|
|
1. prev_hash == Account Table[sender].frontier_hash
|
|||
|
|
на момент settled state конца окна W-1
|
|||
|
|
2. Для Transfer: receiver существует в Account Table
|
|||
|
|
на момент settled state конца окна W-1
|
|||
|
|
3. sender.balance >= amount (для Transfer)
|
|||
|
|
на момент settled state конца окна W-1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Settled state конца окна W-1 — результат apply_proposal окна W-1 — одинаков у всех узлов (детерминированная функция от cemented set W-1 и предыдущего state). Confirmer проверяет каждую операцию против этого глобально единого состояния. Никаких bundle-local цепочек, никакого mempool order.
|
|||
|
|
|
|||
|
|
**Следствие: одна операция на аккаунт за окно τ₁.** Вторая операция от того же sender имеет prev_hash = H(первой операции), но первая ещё не settled (settled = конец текущего окна W). Confirmer отклоняет вторую. Она пройдёт в окне W+1 когда первая settled. Throughput на аккаунт: 1 операция за окно. Это достаточно для всех бытовых сценариев; для высокочастотных — batching через Anchor (один Anchor содержит Merkle root тысяч записей).
|
|||
|
|
|
|||
|
|
Cross-account зависимости сериализуются через окна — создание аккаунта OpenAccount в окне W, получение перевода в окне W+1.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
quorum(W) = ⌈0.67 × active_chain_length(W)⌉
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Объект cemented когда суммарный chain_length confirmers подтвердивших объект через BundledConfirmation окна W ≥ quorum(W). Активный набор детерминирован — все узлы вычисляют `active_chain_length(W)` независимо из state Node Table и получают одно и то же значение.
|
|||
|
|
|
|||
|
|
Если active_chain_length падает ниже минимума жизнеспособности (теоретически возможно при массовом offline) — финализация останавливается до восстановления активности. Halt by liveness, не by safety: вернувшиеся узлы возобновляют работу с последнего cemented state.
|
|||
|
|
|
|||
|
|
Трафик confirmations: ~100 bundles × ~4 KB ≈ 400 KB за окно. Стабильно при любом масштабе.
|
|||
|
|
|
|||
|
|
Узлы-наблюдатели (chain_length < threshold) получают bundles, верифицируют endpoint и подписи, подсчитывают quorum, применяют cemented операции. Не публикуют confirmations.
|
|||
|
|
|
|||
|
|
#### State transition
|
|||
|
|
|
|||
|
|
Два параллельных процесса обновления состояния:
|
|||
|
|
|
|||
|
|
**Применение операций по window close.** Cemented операции окна W буферизуются до момента сборки proposal_{W+1}. Множество cemented операций фиксируется proposer-ом через frozen view (Lookback Leadership). Все cemented операции окна W применяются батчем в детерминированном порядке:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Порядок apply: по op_hash lex asc
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Каждый аккаунт имеет максимум одну cemented операцию в окне W (dependency rule). Порядок между аккаунтами — лексикографически по op_hash. Детерминирован, вычислим независимо каждым узлом.
|
|||
|
|
|
|||
|
|
Apply каждой операции:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Transfer: sender.balance -= amount
|
|||
|
|
receiver.balance += amount
|
|||
|
|
sender.frontier_hash = H(operation)
|
|||
|
|
update_merkle_path(sender)
|
|||
|
|
update_merkle_path(receiver)
|
|||
|
|
|
|||
|
|
OpenAccount: создать запись в Account Table (balance = 0, pubkey, frontier_hash = H(op))
|
|||
|
|
insert_merkle_leaf(new_account)
|
|||
|
|
|
|||
|
|
ChangeKey: account.current_pubkey = new_pubkey
|
|||
|
|
account.suite_id = new_suite_id
|
|||
|
|
account.frontier_hash = H(operation)
|
|||
|
|
update_merkle_path(account)
|
|||
|
|
|
|||
|
|
Anchor: записать data_hash в цепочку аккаунта (frontier_hash обновлён)
|
|||
|
|
update_merkle_path(account)
|
|||
|
|
|
|||
|
|
После каждой операции: account_root = current root.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**При apply каждой операции** обновляется AccountChain length signer-аккаунта (подписавшего операцию):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
on_operation_applied(operation, window W):
|
|||
|
|
signer = operation.sender # account_id из payload
|
|||
|
|
signer.account_chain_length += 1
|
|||
|
|
signer.last_op_window = W
|
|||
|
|
signer.op_height += 1
|
|||
|
|
# Получатель Transfer не получает обновления chain_length —
|
|||
|
|
# пассивное получение не считается активностью.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Dependency rule: один аккаунт = одна операция за окно τ₁. Каждая cemented операция = +1 к account_chain_length = одно окно присутствия.
|
|||
|
|
|
|||
|
|
**State transition в proposal:** при settle (apply at window close) применяется атомарно:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
apply_proposal(state, proposal) -> state':
|
|||
|
|
|
|||
|
|
Шаг 1: применить control_set в порядке (cemented_window asc, op_hash lex asc).
|
|||
|
|
NodeRegistration: проверить node_id уникален (нет в Node Table и Candidate Pool),
|
|||
|
|
проверить operator_account_id существует и is_node_operator == 0,
|
|||
|
|
проверить W_start >= W_p - 2 × τ₂_windows,
|
|||
|
|
вычислить effective_vdf_length(W_start) из canonical state на окно W_start
|
|||
|
|
(candidate_pressure = pending_candidates / active_nodes,
|
|||
|
|
if > 0.01: 13000 × pressure × 100, else: 13000),
|
|||
|
|
верифицировать proof_endpoint: пересчёт VDF от
|
|||
|
|
SHA-256("mt-candidate-vdf-init" || timechain_value(W_start) || node_id)
|
|||
|
|
через effective_vdf_length(W_start) окон,
|
|||
|
|
создать запись в Candidate Pool:
|
|||
|
|
node_id, node_pubkey, suite_id, operator_account_id,
|
|||
|
|
proof_endpoint, W_start,
|
|||
|
|
registration_window = W_p,
|
|||
|
|
expires = W_p + 3 × τ₂_windows.
|
|||
|
|
|
|||
|
|
Шаг 2: применить TimeCoin победителя.
|
|||
|
|
Если winner_class = 1 (Node): operator_account = Node Table[winner_id].operator_account_id
|
|||
|
|
operator_account.balance += 13_000_000_000 nɈ
|
|||
|
|
Если winner_class = 2 (Account): Account Table[winner_id].balance += 13_000_000_000 nɈ
|
|||
|
|
Proposer (proposer_node_id) формирует proposal без награды.
|
|||
|
|
|
|||
|
|
Шаг 3: обработать expiry кандидатов и selection event.
|
|||
|
|
3a. Все записи c ∈ Candidate Pool где c.expires <= current_window:
|
|||
|
|
удалить c из Candidate Pool, обновить candidate_root.
|
|||
|
|
3b. Selection event (если current_window % 369 == 0):
|
|||
|
|
candidates = все записи Candidate Pool где expires > current_window
|
|||
|
|
slots = max(1, floor(active_nodes(current_window) / 130))
|
|||
|
|
sort_key(c) = SHA-256(timechain_value(current_window) || c.node_id)
|
|||
|
|
selected = первые slots кандидатов по sort_key
|
|||
|
|
Для каждого selected:
|
|||
|
|
создать запись в Node Table (start_window = current_window, chain_length = 1,
|
|||
|
|
last_confirmation_window = 0, operator_account_id зафиксирован)
|
|||
|
|
установить is_node_operator = 1 у operator-аккаунта
|
|||
|
|
удалить selected из Candidate Pool
|
|||
|
|
обновить node_root и candidate_root.
|
|||
|
|
|
|||
|
|
Шаг 3.5: обновить chain_length активных узлов.
|
|||
|
|
Для каждого узла N с cemented BundledConfirmation в окне W:
|
|||
|
|
N.chain_length += 1
|
|||
|
|
N.last_confirmation_window = W
|
|||
|
|
update_merkle_path(N) в node_root
|
|||
|
|
Множество узлов с cemented BundledConfirmation в окне W детерминировано
|
|||
|
|
(cemented status объективен) — все узлы применяют один и тот же набор обновлений.
|
|||
|
|
|
|||
|
|
Шаг 3.6: обновить chain_length_snapshot на τ₂-boundary.
|
|||
|
|
Если current_window % τ₂_windows == 0:
|
|||
|
|
Для каждого узла N в Node Table:
|
|||
|
|
rotate N.chain_length_checkpoints (сдвиг: oldest выбывает, текущий chain_length записывается как newest)
|
|||
|
|
N.chain_length_snapshot = N.chain_length - N.chain_length_checkpoints[oldest]
|
|||
|
|
update_merkle_path(N) в node_root
|
|||
|
|
Между τ₂-boundaries: chain_length_snapshot вычисляется как chain_length - frozen oldest checkpoint.
|
|||
|
|
Детерминированно: все узлы применяют одну и ту же ротацию на одной τ₂-boundary.
|
|||
|
|
|
|||
|
|
Шаг 4: node_root, candidate_root и account_root уже отражают все cemented изменения
|
|||
|
|
(incremental Merkle update произошёл при каждом state transition).
|
|||
|
|
state_root = SHA-256("mt-state-root" || node_root || candidate_root || account_root).
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Порядок детерминирован. Каждый узел применяет одни и те же шаги и получает один и тот же state_root.
|
|||
|
|
|
|||
|
|
AccountTable зависит от TimeChain, NodeChain и AccountChain. Обратных зависимостей нет.
|
|||
|
|
|
|||
|
|
С ростом TPS сети дополнительные ядра подключаются для верификации операций. Минимум для валидатора: 3 логических ядра (TimeChain + NodeChain + Account). Один узел = 3 ядра. 50 ядер = 16 узлов. Верификация операций аккаунтов полностью параллелизуется — цепочки аккаунтов независимы.
|
|||
|
|
|
|||
|
|
### Вход и регистрация
|
|||
|
|
|
|||
|
|
Два уровня входа в сеть. Узлы участвуют в консенсусе — открытый вход через VDF + selection event. Аккаунты держат и переводят средства — создаются явно через OpenAccount.
|
|||
|
|
|
|||
|
|
**Genesis State — аксиома сети.** Минимальный bootstrap: один узел, один аккаунт. Начальное состояние, существующее до того как любая операция возможна:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Genesis State (до первого окна, supply = 0):
|
|||
|
|
|
|||
|
|
Account Table = 1 запись (bootstrap operator account):
|
|||
|
|
account_id = SHA-256("mt-account" || suite_id || pubkey_0)
|
|||
|
|
balance = 0
|
|||
|
|
suite_id = 0x0001 (FN-DSA-512)
|
|||
|
|
is_node_operator = 1
|
|||
|
|
current_pubkey = pubkey_0 (bootstrap)
|
|||
|
|
frontier_hash = SHA-256("mt-genesis" || account_id)
|
|||
|
|
op_height = 0
|
|||
|
|
account_chain_length = 0
|
|||
|
|
account_chain_length_snapshot = 0
|
|||
|
|
creation_window = 0
|
|||
|
|
|
|||
|
|
Node Table = 1 запись (bootstrap node):
|
|||
|
|
node_id = SHA-256("mt-node" || node_pubkey_0)
|
|||
|
|
node_pubkey = node_pubkey_0 (bootstrap)
|
|||
|
|
suite_id = 0x0001
|
|||
|
|
operator_account_id = account_id_0
|
|||
|
|
start_window = 0
|
|||
|
|
chain_length = 1
|
|||
|
|
last_confirmation_window = 0
|
|||
|
|
|
|||
|
|
Candidate Pool = ∅
|
|||
|
|
|
|||
|
|
Genesis NodeChain init для bootstrap-узла:
|
|||
|
|
nodechain_init_0 = SHA-256("mt-nodechain-genesis" || node_id_0)
|
|||
|
|
Первое звено NodeChain в окне 0: S_{0,0,0} = nodechain_init_0.
|
|||
|
|
Дальнейшие звенья по формуле S_{0,s+1,0} = SHA-256(S_{0,s,m} || T_{s+1} || node_id_0).
|
|||
|
|
|
|||
|
|
genesis_account_root = sparse Merkle root над 1 записью Account Table
|
|||
|
|
genesis_node_root = sparse Merkle root над 1 записью Node Table
|
|||
|
|
genesis_candidate_root = 0x00 × 32 (пустая sparse Merkle tree)
|
|||
|
|
genesis_state_root = SHA-256("mt-state-root" || genesis_node_root || genesis_candidate_root || genesis_account_root)
|
|||
|
|
|
|||
|
|
protocol_params (каноническая сериализация, little-endian, фиксированная длина полей):
|
|||
|
|
D₀ (8B) начальное значение D TimeChain VDF (13 000 000 000)
|
|||
|
|
m₀ (8B) начальное значение m NodeChain VDF (1 000 000 000 = D₀/13)
|
|||
|
|
τ₂_windows (8B) число окон в τ₂ (13 000)
|
|||
|
|
timecoin_per_window (16B) 13_000_000_000 nɈ (u128)
|
|||
|
|
target₀ (32B) начальный target лотереи
|
|||
|
|
confirmation_quorum_num (1B) 67
|
|||
|
|
confirmation_quorum_den (1B) 100
|
|||
|
|
participation_dead_zone_low (2B) 85
|
|||
|
|
participation_dead_zone_high (2B) 95
|
|||
|
|
d_adjustment_rate_num (2B) 3
|
|||
|
|
d_adjustment_rate_den (2B) 100
|
|||
|
|
vdf_entry_windows (8B) 13 000
|
|||
|
|
selection_interval (8B) 369
|
|||
|
|
candidate_expiry_windows (8B) 39 000 (3τ₂)
|
|||
|
|
adaptive_vdf_threshold (2B) 1 (= 0.01 × 100, порог давления 1%)
|
|||
|
|
adaptive_vdf_multiplier (2B) 100 (effective_vdf = base × pressure × multiplier)
|
|||
|
|
pruning_idle_windows (8B) 52 000 (4τ₂)
|
|||
|
|
bootstrap_account_pubkey (897B)
|
|||
|
|
bootstrap_node_pubkey (897B)
|
|||
|
|
genesis_content_app_id (32B) = SHA-256("mt-app" || "montana")
|
|||
|
|
genesis_content_data_hash (32B) хэш манифеста книги Montana v1.0
|
|||
|
|
|
|||
|
|
Genesis State Hash = SHA-256(genesis_state_root || protocol_params)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Bootstrap keypair (account + node) публикуется в Genesis Decree вместе с протокольными параметрами и Genesis State Hash. Genesis Decree immutable — закреплён в коде каждой реализации.
|
|||
|
|
|
|||
|
|
Первое окно τ₁ после генезиса — window_index = 0, protocol_version = 1. Bootstrap-узел — единственный proposer первых двух окон (без lookback). Начиная с W = 2 — стандартная lookback логика. Bootstrap-узел получает 13 Ɉ за каждое выигранное окно. Per-operation invariant действует с первого окна.
|
|||
|
|
|
|||
|
|
**Bootstrap period.** До появления второго узла (первые 13 000+ окон) bootstrap-узел имеет 100% active_chain_length и является единственным confirmer-ом, proposer-ом и winner-ом. Это физическая необходимость запуска любой сети — кто-то является первым. Доминирование bootstrap-узла размывается органически: каждый новый узел, прошедший selection event, вносит свой chain_length в active set. Протокольные правила (quorum 67%, weighted_ticket лотерея, selection rate limit) одинаковы с первого окна — специальных bootstrap-правил вне lookback первых двух окон нет.
|
|||
|
|
|
|||
|
|
**Mandatory content replication.** Каждый узел Montana обязан хранить текущую версию книги Montana как persistent blob по (genesis_content_app_id, genesis_content_data_hash). При Fast Sync новый узел загружает genesis content как часть обязательной начальной синхронизации (см. раздел Fast Sync).
|
|||
|
|
|
|||
|
|
#### Открытый вход узлов
|
|||
|
|
|
|||
|
|
Вход узла в консенсус — открытый. VDF 13 000 окон + кандидатура + selection event. Никаких приглашений, никаких разрешений.
|
|||
|
|
|
|||
|
|
**Шаг 1: Свободный вход.** Кандидат создаёт аккаунт (OpenAccount), подключается к gossip через account keypair (IBT уровень 3), получает TimeChain values из proposals, считает NodeChain VDF 13 000 окон от init:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
candidate_vdf_init = SHA-256("mt-candidate-vdf-init" || timechain_value(W_start) || node_id)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
W_start — окно начала VDF (заявляется кандидатом в NodeRegistration).
|
|||
|
|
|
|||
|
|
**Шаг 2: Кандидатура.** После завершения VDF кандидат публикует NodeRegistration:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
NodeRegistration:
|
|||
|
|
type 1B <- 0x11 NodeRegistration
|
|||
|
|
suite_id 2B
|
|||
|
|
node_pubkey 897B
|
|||
|
|
operator_account_id 32B
|
|||
|
|
proof_endpoint 32B <- endpoint после 13 000 окон VDF
|
|||
|
|
W_start 8B <- окно начала VDF
|
|||
|
|
signature 666B
|
|||
|
|
Итого: ~1 638 B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
NodeRegistration — ControlObject. При cementing → запись в Candidate Pool. Кандидат ожидает selection event.
|
|||
|
|
|
|||
|
|
Валидация NodeRegistration:
|
|||
|
|
|
|||
|
|
1. Подпись FN-DSA-512 валидна для node_pubkey
|
|||
|
|
2. node_id = SHA-256("mt-node" || node_pubkey) уникален (нет в Node Table и Candidate Pool)
|
|||
|
|
3. operator_account_id существует в Account Table и `is_node_operator == 0`
|
|||
|
|
4. W_start >= current_window - 2 × τ₂_windows
|
|||
|
|
5. proof_endpoint верифицируем: пересчёт VDF от `SHA-256("mt-candidate-vdf-init" || timechain_value(W_start) || node_id)` через `effective_vdf_length(W_start)` окон (adaptive VDF)
|
|||
|
|
|
|||
|
|
Верификация: `effective_vdf_length(W_start)` сегментов VDF проверяются параллельно. На C ядрах: ~(effective_vdf_length/C) × t_segment.
|
|||
|
|
|
|||
|
|
**Шаг 3: Selection event.** Каждые 369 окон сеть выбирает кандидатов из Candidate Pool:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
selection_windows = { W : W % 369 == 0 }
|
|||
|
|
slots(W) = max(1, floor(active_nodes(W) / 130))
|
|||
|
|
candidates(W) = все записи Candidate Pool где expires > W
|
|||
|
|
sort_key(candidate) = SHA-256(timechain_value(W) || candidate.node_id)
|
|||
|
|
selected(W) = первые slots(W) кандидатов по sort_key
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Детерминированно, верифицируемо. Каждый узел вычисляет один и тот же результат. timechain_value(W) неизвестен до окна W — grinding node_id бесполезен.
|
|||
|
|
|
|||
|
|
**Шаг 4: Регистрация.** Выбранные кандидаты → Node Table:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
start_window = W (окно selection event)
|
|||
|
|
chain_length = 1
|
|||
|
|
last_confirmation_window = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
NodeChain init: `SHA-256("mt-nodechain-init" || state_root || timechain_value || node_id)` из proposal окна selection event. Оператор-аккаунт получает `is_node_operator = 1`. Запись удаляется из Candidate Pool.
|
|||
|
|
|
|||
|
|
**Expiry.** Кандидатура истекает через 3τ₂ (39 000 окон). Запись удаляется из Candidate Pool автоматически.
|
|||
|
|
|
|||
|
|
**Sybil-защита (четыре уровня):**
|
|||
|
|
|
|||
|
|
1. **VDF-барьер:** 13 000 окон последовательного хэширования (при нормальной нагрузке), 3 ядра CPU. Физическая работа.
|
|||
|
|
|
|||
|
|
2. **Adaptive VDF:** стоимость кандидатуры пропорциональна давлению на сеть. Сеть видит давление мгновенно (pending_candidates в canonical state). Рост защиты — мгновенный, снятие — медленное (через expiry 3τ₂).
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
candidate_pressure = pending_candidates / active_nodes
|
|||
|
|
|
|||
|
|
if candidate_pressure > 0.01:
|
|||
|
|
effective_vdf_length = 13000 × candidate_pressure × 100
|
|||
|
|
else:
|
|||
|
|
effective_vdf_length = 13000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| Ситуация | pending | active | pressure | effective_vdf |
|
|||
|
|
|----------|---------|--------|----------|---------------|
|
|||
|
|
| Нормальная | 5 | 1 000 | 0.5% | 13 000 окон |
|
|||
|
|
| Умеренная | 20 | 1 000 | 2% | 26 000 окон |
|
|||
|
|
| Высокая | 100 | 1 000 | 10% | 130 000 окон |
|
|||
|
|
| Атака | 1 000 | 1 000 | 100% | 1 300 000 окон |
|
|||
|
|
| Массовая атака | 100 000 | 1 000 | 10000% | 130 000 000 окон |
|
|||
|
|
|
|||
|
|
`effective_vdf_length` фиксируется для кандидата в момент W_start. Валидатор проверяет proof_endpoint через `effective_vdf_length(W_start)`, вычисленный из canonical state на окно W_start. Изменение давления после W_start не влияет на уже считающихся кандидатов.
|
|||
|
|
|
|||
|
|
Self-correcting: чем сильнее давление → тем длиннее VDF → тем дороже каждый Sybil → давление падает. При снижении давления → pending уменьшается через expiry (3τ₂) → effective_vdf нормализуется → легитимный вход восстанавливается.
|
|||
|
|
|
|||
|
|
**Timing window (known limitation).** Атакующий может начать VDF при низком давлении (effective_vdf = 13 000) и подать NodeRegistration после завершения, когда давление уже высокое. Его proof валиден для effective_vdf(W_start). Первая волна кандидатов, начатых при низком давлении, проходит со стандартным VDF. Вторая волна (начатая при высоком давлении) заблокирована adaptive VDF. Impact ограничен: (a) selection rate limit пропускает max active/130 за event; (b) candidate_expiry = 3τ₂ ограничивает окно подачи; (c) weighted mechanisms (chain_length = 1 при входе) ограничивают влияние вошедших Sybil-узлов на quorum и лотерею.
|
|||
|
|
|
|||
|
|
3. **Selection rate limit:** max(1, active_nodes/130) за 369 окон. Массовый вход ограничен. Минимум 1 кандидат всегда проходит.
|
|||
|
|
|
|||
|
|
4. **Weighted механизмы:** chain_length определяет вес в quorum (безопасность). lottery_weight (snapshot 6τ₂ + seniority bonus) определяет вес в лотерее (эмиссия). Новые узлы начинают с минимальным влиянием. Время — единственный путь к весу.
|
|||
|
|
|
|||
|
|
#### Создание аккаунта
|
|||
|
|
|
|||
|
|
Аккаунт создаётся свободно. Пользователь генерирует FN-DSA-512 keypair → вычисляет account_id = SHA-256("mt-account" || suite_id || pubkey) → публикует OpenAccount → операция cemented → запись появляется в Account Table при settle.
|
|||
|
|
|
|||
|
|
Sybil-барьер для аккаунтов: account_age определяет приоритет операций. Новый аккаунт — бакет 0, 1 операция за τ₁. Рост приоритета = время. Пустые аккаунты бесполезны — без баланса операции невозможны.
|
|||
|
|
|
|||
|
|
#### Скорость роста сети
|
|||
|
|
|
|||
|
|
Узлы: selection event каждые 369 окон, slots = max(1, active_nodes/130). Рост ограничен selection rate:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Genesis (1 узел): 1 новый узел за 369 окон
|
|||
|
|
active_nodes = 100: 1 новый узел за 369 окон
|
|||
|
|
active_nodes = 1 000: 10 новых узлов за 369 окон
|
|||
|
|
active_nodes = 10 000: 100 новых узлов за 369 окон
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Каждый кандидат проходит 13 000 окон VDF. Первые кандидаты появляются через 13 000 окон после genesis.
|
|||
|
|
|
|||
|
|
Аккаунты: создаются свободно через OpenAccount. Рост пользовательской базы определяется распространением TimeCoin.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Потоковая модель
|
|||
|
|
|
|||
|
|
Операции аккаунтов текут непрерывно. Узел получает операцию → проверяет подпись FN-DSA-512 и баланс (против settled state W-1) → передаёт в P2P gossip. Confirmers (~100 узлов с наибольшим chain_length) собирают операции за окно и публикуют BundledConfirmation.
|
|||
|
|
|
|||
|
|
Операция проходит два состояния:
|
|||
|
|
- **Cemented** (quorum event): 67% active_chain_length подтвердили. Операция необратима. Баланс ещё не обновлён.
|
|||
|
|
- **Settled** (конец окна, apply at window close): все cemented операции окна применены к Account Table батчем. Баланс обновлён. state_root зафиксирован в proposal.
|
|||
|
|
|
|||
|
|
Два параллельных процесса:
|
|||
|
|
- **Операции** подтверждаются непрерывно через confirmations (cement), применяются батчем в конце окна (settle)
|
|||
|
|
- **Часы** тикают по расписанию окон τ₁ (TimeChain, NodeChain, лотерея, TimeCoin)
|
|||
|
|
|
|||
|
|
Кошелёк получателя отображает входящий перевод в два этапа: «confirmed» после cement (quorum event), «settled» после apply at window close (apply at window close). Между cement и settle операция уже необратима — различие только для UX индикации.
|
|||
|
|
|
|||
|
|
Цепочки аккаунтов полностью независимы. Операции разных аккаунтов обрабатываются параллельно без конфликтов.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Временные слои (τ)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
τ₁ = 1 window → τ₂ = 13 000 windows
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Одно окно — τ₁. Всё остальное — производные в window counts.
|
|||
|
|
|
|||
|
|
### τ₁ — Окно (D хэшей)
|
|||
|
|
|
|||
|
|
Единственная единица канонического времени протокола. Регистрация одного окна Montana Time и эмиссия.
|
|||
|
|
|
|||
|
|
- TimeChain продвигается на `D` хэшей
|
|||
|
|
- NodeChain продвигается на `m` хэшей с якорем в текущем T_s
|
|||
|
|
- Операции аккаунтов подтверждаются непрерывно через confirmations (cement), применяются батчем в конце окна (settle)
|
|||
|
|
- control_set: все cemented ControlObjects из окон (previous_proposal.window, current_window] (каноничен)
|
|||
|
|
- Confirmers (~100) публикуют BundledConfirmation (операции текущего окна + VDF_Reveals предыдущего окна)
|
|||
|
|
- Кандидаты (~12) публикуют VDF_Reveal с NodeChain endpoint для лотереи; reveals цементируются через BundledConfirmation следующего окна
|
|||
|
|
- Лотерея: `ticket_i = -ln(endpoint_i / 2^256)`, winner = argmin(weighted_ticket) среди cemented VDF_Reveal nodes + cemented account operations
|
|||
|
|
- Winner_{W-1} определяется proposer_W (= winner_{W-2}) из cemented VDF_Reveals окна W-1 и cemented account operations окна W-1
|
|||
|
|
- Proposer (proposer_node_id) публикует подписанный proposal
|
|||
|
|
|
|||
|
|
- Финальность proposal: подпись proposer_node_id на proposal header. Каждый валидатор применяет control_set + TimeCoin детерминированно и проверяет state_root
|
|||
|
|
- TimeCoin: регистрация одного окна Montana Time (13 Ɉ) → победителю
|
|||
|
|
- Supply audit: суммарная эмиссия TimeCoin от генезиса сверяется с `supply(window_index) = 13 × (window_index + 1) Ɉ`
|
|||
|
|
- Разрешение форков: приоритет ветки с наибольшим суммарным TimeChain-доказательством
|
|||
|
|
|
|||
|
|
TimeChain safety: компрометация значения TimeChain требует нарушения свойства последовательности SHA-256 VDF.
|
|||
|
|
|
|||
|
|
TimeChain liveness: задержка продвижения TimeChain невозможна — TimeChain вычисляется каждым узлом независимо.
|
|||
|
|
|
|||
|
|
### τ₂ — Адаптация (13 000 windows)
|
|||
|
|
|
|||
|
|
- Адаптация D и m через participation-ratio feedback (см. ниже)
|
|||
|
|
- Snapshot account_chain_length: для каждого аккаунта `account_chain_length_snapshot = account_chain_length`. Snapshot используется лотереей аккаунтов в течение следующего τ₂. Детерминированно для всех узлов
|
|||
|
|
- Pruning Account Table: удаление пустых аккаунтов без активности 4τ₂ (52 000 окон) с обновлением Merkle путей
|
|||
|
|
- Pruning Node Table: для каждого узла N где `(current_window - N.last_confirmation_window) > 8 × τ₂_windows`:
|
|||
|
|
1. Если `N.operator_account_id` существует в Account Table — установить `Account Table[N.operator_account_id].is_node_operator = 0` (operator-аккаунт освобождается, может участвовать в лотерее аккаунтов)
|
|||
|
|
2. Удалить запись N из Node Table
|
|||
|
|
3. Пересчитать node_root
|
|||
|
|
- Supply audit (sanity check): Σ balance(account) для всех аккаунтов = 13_000_000_000 × (window_index + 1) nɈ
|
|||
|
|
- Криптографическая амнезия: подписанные proposals сохраняются навсегда — верифицируемая цепочка state commitments. Proposals доказывают что конкретное состояние было закоммичено proposer-узлом; восстановление содержимого состояния требует snapshot или архива
|
|||
|
|
- Пересчёт параметра D через participation-ratio feedback
|
|||
|
|
|
|||
|
|
#### Адаптация D через participation-ratio feedback
|
|||
|
|
|
|||
|
|
D адаптируется на границе τ₂ через каноническое chain observation — долю активного chain_length-а, успевшего подписать BundledConfirmation в каждом окне предыдущего τ₂.
|
|||
|
|
|
|||
|
|
**Канонический вход:**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
participation_ratio(W) = cemented_chain_length(W) / active_chain_length(W)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Где `cemented_chain_length(W)` — суммарный chain_length узлов, чьи BundledConfirmation для окна W попали в cemented set; `active_chain_length(W)` — суммарный chain_length узлов с `active(node, W) = true`. Оба числа канонически вычисляются каждым узлом bit-exact из Node Table и cemented sets.
|
|||
|
|
|
|||
|
|
**Формула адаптации на τ₂ boundary:**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
median_ratio = median(participation_ratio(W) for W in последние 13 000 окон)
|
|||
|
|
|
|||
|
|
Если median_ratio >= 0.95: D_new = D_old × 1.03 (+3%, сеть в комфорте, ускоряемся)
|
|||
|
|
Если median_ratio <= 0.85: D_new = D_old × 0.97 (-3%, сеть под давлением, замедляемся)
|
|||
|
|
Иначе (dead zone): D_new = D_old (zone comfort, D не трогаем)
|
|||
|
|
|
|||
|
|
m_new = m_old × (D_new / D_old)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Семантика.**
|
|||
|
|
|
|||
|
|
- `median_ratio >= 0.95`: большинство активных узлов легко успевают подписать каждое окно. У сети есть запас производительности — D можно поднять, окно станет чуть длиннее в единицах работы, TimeCoin эмиссия замедляется в физическом времени, но сеть укрепляет запас прочности.
|
|||
|
|
- `median_ratio <= 0.85`: значительная часть активных узлов не успевает подписать. Сеть близка к границе жизнеспособности — D нужно уменьшить, окно становится короче в единицах работы, медленные узлы получают шанс догнать медиану.
|
|||
|
|
- Dead zone 0.85-0.95: естественные колебания, D не адаптируется. Это защита от реактивной волатильности.
|
|||
|
|
|
|||
|
|
**Свойства.**
|
|||
|
|
|
|||
|
|
- **Канонически детерминировано.** participation_ratio вычисляется из canonical cemented sets и Node Table. Два честных узла получают одно и то же значение bit-exact.
|
|||
|
|
- **Опирается только на canonical chain observations.** Все входные данные формулы — cemented sets и Node Table, оба детерминированы. Corollary I-3.a соблюдён.
|
|||
|
|
- **Медленная реакция.** Adjustment rate ±3% за τ₂ делает стратегическую манипуляцию через withholding confirmations экономически нерациональной: actor-у с 10% chain_length-а для сдвига D на 2x требуется систематически saboтировать свои подписи ~24 эпохи, теряя все свои TimeCoin награды в этот период.
|
|||
|
|
- **Dead zone защищает от флуктуаций.** Случайные колебания participation_ratio в диапазоне 0.85-0.95 не вызывают adaptation.
|
|||
|
|
- **Естественное следование hardware progress.** Если железо ускоряется, медианные узлы начинают успевать с запасом, median_ratio поднимается выше 0.95, D растёт, окно нормализуется. Сеть автоматически адаптируется к ожидаемому hardware evolution без явного measurement.
|
|||
|
|
- **Нет stремления к hard fork по дизайну.** Continuous adaptation в рамках speech-first принципа устраняет необходимость периодического hard fork как запрограммированного события.
|
|||
|
|
|
|||
|
|
**Threat model:**
|
|||
|
|
|
|||
|
|
- Actor с <20% chain_length-а экономически не может сдвинуть median_ratio значимо.
|
|||
|
|
- Hyperscaler с 15% узлов может систематически снижать median_ratio на ~15%, но только теряя свои награды. При clamp ±3% за τ₂ максимальный сдвиг D за 24 τ₂ составляет ±1.03^24 ≈ ±103%, что ограничено при правильном выборе `D₀` с запасом.
|
|||
|
|
- Координированная атака узлов с >50% chain_length эквивалентна атаке на весь консенсус и не рассматривается в рамках локальной защиты participation_ratio.
|
|||
|
|
|
|||
|
|
**Genesis parameters:**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
D₀ = 13 000 000 000 (13 × 10⁹)
|
|||
|
|
m₀ = 1 000 000 000 (10⁹, D₀ / 13 = NodeChain VDF за окно)
|
|||
|
|
participation_dead_zone_low = 0.85
|
|||
|
|
participation_dead_zone_high = 0.95
|
|||
|
|
d_adjustment_rate = 0.03 (±3% за τ₂)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Параметры D₀ и m₀ фиксируются в Genesis Decree. Остальные константы закреплены в протокольных параметрах и могут быть изменены только через protocol version upgrade (software hard fork), не через runtime mechanism.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Консенсус — Proof of Time (PoT)
|
|||
|
|
|
|||
|
|
### Четыре цепочки
|
|||
|
|
|
|||
|
|
**TimeChain** — глобальные часы. Чистая VDF-цепочка `T_r = SHA-256^D(T_{r-1})`. Первичный продукт протокола. Источник времени и случайности. Продвигается по расписанию окон.
|
|||
|
|
|
|||
|
|
**NodeChain** — персональная цепочка узла. VDF-цепочка конкретного node_id, якорится в TimeChain каждое окно. Доказывает непрерывную работу узла.
|
|||
|
|
|
|||
|
|
**Account** — состояние счёта. Операции финализируются непрерывно через подтверждения (67% active_chain_length). ControlObjects включаются в proposal (каноничен).
|
|||
|
|
|
|||
|
|
Зависимости односторонние: TimeChain → NodeChain → AccountChain → AccountTable. Отказ в AccountTable не останавливает часы. Отказ конкретного узла в NodeChain не заражает общий ритм.
|
|||
|
|
|
|||
|
|
### Лотерея
|
|||
|
|
|
|||
|
|
Лотерея объединяет два класса участников: узлы (через NodeChain) и аккаунты (через AccountChain). Каждый класс производит weighted ticket по длине своей цепочки. Lowest weighted_ticket из объединённого множества побеждает.
|
|||
|
|
|
|||
|
|
**Узлы** автоматически участвуют в каждом окне:
|
|||
|
|
```
|
|||
|
|
ticket_node = -ln(endpoint_node / 2^256)
|
|||
|
|
lottery_weight = chain_length_snapshot + min(chain_length / 69, chain_length_snapshot)
|
|||
|
|
weighted_ticket_node = ticket_node / lottery_weight
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Аккаунты** участвуют в окне с финализированной операцией:
|
|||
|
|
```
|
|||
|
|
ticket_account = -ln(endpoint_account / 2^256)
|
|||
|
|
weighted_ticket_account = ticket_account / account_chain_length
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`account_chain_length_snapshot` обновляется на τ₂ boundary, frozen до следующей τ₂ boundary. Лотерея использует snapshot — детерминированно для всех узлов.
|
|||
|
|
|
|||
|
|
Если weighted_ticket < target — субъект кандидат. Target калиброван на ~13 кандидатов за окно (включая оба класса). Из кандидатов побеждает lowest weighted_ticket.
|
|||
|
|
|
|||
|
|
**Стимул узла:** каждое окно с опубликованным BundledConfirmation увеличивает chain_length → увеличивает шанс победы. Пропущенное окно — это окно не входит в chain_length. Узел остаётся в Node Table и продолжает участвовать.
|
|||
|
|
|
|||
|
|
**Стимул аккаунта:** каждое окно с операцией увеличивает account_chain_length → реальный (хоть и редкий) шанс выиграть 13 Ɉ за активность в Montana.
|
|||
|
|
|
|||
|
|
### Победитель τ₁
|
|||
|
|
|
|||
|
|
Победитель определяется после закрытия окна τ₁. Lowest weighted_ticket из всех кандидатов (узлов и аккаунтов) = победитель.
|
|||
|
|
|
|||
|
|
**Если победил узел:**
|
|||
|
|
- Записывает TimeChain value
|
|||
|
|
- Operator account узла получает 13 Ɉ TimeCoin
|
|||
|
|
- Коммитит State Root
|
|||
|
|
- Формирует proposal (control_set + State Root + TimeCoin), подписывает node_pubkey
|
|||
|
|
|
|||
|
|
**Если победил аккаунт:**
|
|||
|
|
- Аккаунт получает 13 Ɉ TimeCoin (winner_account.balance += 13_000_000_000 nɈ)
|
|||
|
|
- Proposal формирует **узел-кандидат с минимальным weighted_ticket в этом окне** (proposer_node)
|
|||
|
|
- Если в окне нет узлов-кандидатов — proposer выбирается из всех узлов с lowest weighted_ticket (fallback)
|
|||
|
|
- Proposer не получает дополнительной награды — это его обязанность как ближайшего узла
|
|||
|
|
|
|||
|
|
Финальность proposal — подпись proposer_node_id на proposal header. Верификация — независимый пересчёт state_root.
|
|||
|
|
|
|||
|
|
### Верификация
|
|||
|
|
|
|||
|
|
Proposer публикует: `{proposer_node_id, NodeChain endpoint, proposal}`.
|
|||
|
|
|
|||
|
|
Верификация NodeChain за одно окно: пересчёт m хэшей. Параллелизация по сегментам — время верификации обратно пропорционально числу ядер.
|
|||
|
|
|
|||
|
|
Верификация proposal: независимое применение control_set + TimeCoin и сравнение state_root.
|
|||
|
|
|
|||
|
|
### Устойчивость
|
|||
|
|
|
|||
|
|
- **Остановка TimeChain** исключена: каждый узел вычисляет VDF независимо
|
|||
|
|
- **Искажение TimeChain** исключено: VDF последователен, результат детерминирован
|
|||
|
|
- **Proposer grinding** исключён: control_set каноничен, state transition детерминирован, операции финализируются независимо от победителя
|
|||
|
|
- **Front-running** исключён: операции финализируются через подтверждения (quorum event), proposer фиксирует frozen view
|
|||
|
|
- **Предвычисление** исключено: seed содержит текущее значение TimeChain
|
|||
|
|
- **Replay** исключён: TimeChain уникален для каждого τ₁
|
|||
|
|
- **Аппаратное преимущество** ограничено: последовательное хэширование масштабируется тактовой частотой, не количеством ядер
|
|||
|
|
- **Sybil-барьер**: 13 000 окон VDF + selection event (max 1% active_nodes за 369 окон) + 3 ядра на узел + weighted_ticket в лотерее
|
|||
|
|
- **Цензура операций** исключена: операции финализируются через подтверждения узлов, не через победителя
|
|||
|
|
- **Цензура ControlObjects** исключена: control_set каноничен, пропуск = fallback
|
|||
|
|
- **Liveness halt операций** исключён: финализация через 67% active_chain_length, не зависит от победителя
|
|||
|
|
- **Liveness halt proposals** исключён: fallback на следующего кандидата
|
|||
|
|
- **Масштабирование**: трафик лотереи ~8.9 KB за окно при любом количестве узлов
|
|||
|
|
|
|||
|
|
### Разрешение конфликтов
|
|||
|
|
|
|||
|
|
**Двойная операция аккаунта** (две операции с одним prev_hash): equivocation. Cemented до обнаружения — необратимо, вторая отклоняется. Не cemented — ожидание quorum 13 окон, затем обе отклоняются. См. раздел «Двойная трата».
|
|||
|
|
|
|||
|
|
**Невалидный proposal**: валидаторы отклоняют, fallback на следующего кандидата. Победитель теряет TimeCoin за это окно.
|
|||
|
|
|
|||
|
|
**Два proposal от одного proposer_node_id в одном окне**: оба отклоняются (equivocation), fallback к следующему узлу-кандидату. Если этот узел был winner (winner_class=1), он теряет TimeCoin.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Адреса и переводы
|
|||
|
|
|
|||
|
|
### Полный флоу перевода
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. Боб: OpenAccount → cemented (quorum event) → settled (конец окна) →
|
|||
|
|
account_id зарегистрирован в Account Table (balance = 0)
|
|||
|
|
2. Боб → Алисе: "отправь на mt4ZGfe..." (account_id Боба)
|
|||
|
|
3. Алиса формирует Transfer (в следующем окне после settle OpenAccount Боба):
|
|||
|
|
type: 0x02
|
|||
|
|
prev_hash: хэш её предыдущей settled операции (frontier_hash из settled state W-1)
|
|||
|
|
payload: sender (account_id Алисы) || link (account_id Боба) || amount (50_000_000_000 nɈ)
|
|||
|
|
4. Алиса подписывает FN-DSA-512
|
|||
|
|
5. Алиса рассылает операцию узлам сети
|
|||
|
|
6. Каждый узел проверяет (против settled state W-1):
|
|||
|
|
FN-DSA-512 подпись валидна для current_pubkey Алисы
|
|||
|
|
prev_hash совпадает с frontier_hash Алисы
|
|||
|
|
amount > 0
|
|||
|
|
alice.balance >= amount
|
|||
|
|
получатель (Боб) существует в Account Table
|
|||
|
|
7. Confirmers публикуют BundledConfirmation, операция распространяется через P2P gossip
|
|||
|
|
8. Cement: 67% active_chain_length подтвердили → операция необратима (quorum event)
|
|||
|
|
Кошелёк Боба отображает «confirmed»
|
|||
|
|
9. Settle (apply at window close):
|
|||
|
|
alice.balance -= 50 Ɉ
|
|||
|
|
bob.balance += 50 Ɉ
|
|||
|
|
alice.frontier_hash = H(operation)
|
|||
|
|
alice.op_height += 1
|
|||
|
|
alice.account_chain_length += 1
|
|||
|
|
Кошелёк Боба отображает «settled»
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Баланс
|
|||
|
|
|
|||
|
|
Баланс аккаунта — открытое число `u128 nɈ` в Account Table. Обновляется при settle (apply at window close): исходящий Transfer вычитает amount, входящий зачисляет. Видим всем узлам и через любого верификатора цепочки.
|
|||
|
|
|
|||
|
|
Бэкап = seed (для деривации приватного ключа FN-DSA-512). Восстановление кошелька: ключ выводится из seed, баланс читается из текущего Account Table — никакого локального состояния не требуется.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Эмиссия
|
|||
|
|
|
|||
|
|
### Единица
|
|||
|
|
|
|||
|
|
Монета: **TimeCoin** (тикер: $TimeCoin, символ: Ɉ).
|
|||
|
|
|
|||
|
|
1 Ɉ = 1 000 mɈ = 1 000 000 μɈ = 1 000 000 000 nɈ
|
|||
|
|
|
|||
|
|
Одно окно τ₁ регистрирует одну единицу Montana Time = 13 Ɉ. Число 13 — фиксированная константа протокола эмиссии за окно.
|
|||
|
|
|
|||
|
|
Точность: 9 знаков после запятой. Все расчёты эмиссии в nɈ (целочисленная арифметика, без плавающей точки).
|
|||
|
|
|
|||
|
|
### Issuance schedule
|
|||
|
|
|
|||
|
|
Одно окно Montana Time порождает 13 Ɉ. С первого окна и навсегда.
|
|||
|
|
|
|||
|
|
| Параметр | Значение |
|
|||
|
|
|----------|----------|
|
|||
|
|
| Genesis | window_index = 0 |
|
|||
|
|
| TIME_RECORD | 13 000 000 000 nɈ (13 Ɉ за окно) |
|
|||
|
|
|
|||
|
|
### Регистрация окна
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
time_record(window_index) = 13_000_000_000 nɈ
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Каждое окно τ₁ регистрирует одно каноническое окно Montana Time = 13 Ɉ. Без халвингов, без фаз, без исключений. Одна константа на весь горизонт существования протокола.
|
|||
|
|
|
|||
|
|
### Supply audit
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
supply(window_index) = 13_000_000_000 × (window_index + 1) nɈ
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Эмиссия за прошедшее окно: proposal_W зачисляет 13 Ɉ за окно W-1 (Lookback Leadership — winner_{W-1} определяется при cementing proposal_W). После cementing proposal_W суммарно зачислено окна от 0 до W-1, supply = 13 × (W + 1) Ɉ. Одно умножение. Проверяемо каждым узлом в каждом τ₁. O(1).
|
|||
|
|
|
|||
|
|
### Инфляция
|
|||
|
|
|
|||
|
|
Supply растёт линейно по window_index. Инфляция за окно W:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
inflation(W) = 1 / W
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Монотонно убывает, асимптотически стремится к нулю. Чистая арифметика от window_index.
|
|||
|
|
|
|||
|
|
### Раннее участие
|
|||
|
|
|
|||
|
|
Эмиссия постоянна: 13 Ɉ за каждое окно, с первого окна и навсегда. Вероятность победы пропорциональна весу. Узел, работающий дольше, побеждает чаще. Два узла, запустившиеся одновременно, имеют равные шансы независимо от капитала. Узел, запустившийся раньше, имеет преимущество — он доказал больше окон присутствия.
|
|||
|
|
|
|||
|
|
Стимул для ранних участников встроен в арифметику: не бонусы, не множители — просто больший вес.
|
|||
|
|
|
|||
|
|
### Распределение
|
|||
|
|
|
|||
|
|
Победитель окна τ₁ — узел или аккаунт — регистрирует одно окно Montana Time и получает 13 Ɉ TimeCoin в своей цепочке. Одно правило. Неизменно с генезиса.
|
|||
|
|
|
|||
|
|
Узлы и аккаунты конкурируют в единой лотерее. Узлы доминируют статистически из-за непрерывного присутствия — chain_length растёт каждое окно, weighted_ticket систематически ниже. Аккаунты получают долю эмиссии пропорционально своей активности — account_chain_length растёт с каждым окном с операцией. Время — единственный арбитр.
|
|||
|
|
|
|||
|
|
Базовый бюджет: 13 Ɉ/τ₁ (регистрация одного окна Montana Time). Реальный бюджет безопасности в покупательной способности зависит от рынка.
|
|||
|
|
|
|||
|
|
13 Ɉ за окно — каноническая константа эмиссии Montana Time. Покупательная способность определяется рынком, а не протоколом.
|
|||
|
|
|
|||
|
|
#### Двигатель роста сети
|
|||
|
|
|
|||
|
|
Участие аккаунтов в лотерее создаёт flywheel роста сети:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Активные пользователи в приложениях → AccountChain растёт → шансы в лотерее
|
|||
|
|
↓ ↓
|
|||
|
|
Приложения привлекают пользователей Иногда выигрывают TimeCoin
|
|||
|
|
↓ ↓
|
|||
|
|
Разработчики хотят пользователей Дополнительная мотивация активности
|
|||
|
|
↓ ↓
|
|||
|
|
Разработчики запускают узлы Montana Больше операций в сети
|
|||
|
|
↓ ↓
|
|||
|
|
Узлы зарабатывают TimeCoin Сеть растёт и децентрализуется
|
|||
|
|
↓ ↓
|
|||
|
|
Финансирование развития инфраструктуры → больше пропускной способности → лучше для приложений
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Эмиссия 13 Ɉ за окно одна и та же, но финансирует обе стороны одновременно: узлы (поддержание сети) и активные пользователи (использование сети). Они взаимно усиливают друг друга — циркулярная экономика.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Пропускная способность
|
|||
|
|
|
|||
|
|
Размер Transfer: ~779 B (открытый перевод, FN-DSA-512 подпись).
|
|||
|
|
|
|||
|
|
| Канал узла | TPS |
|
|||
|
|
|-----------|-----|
|
|||
|
|
| 10 Mbps | ~1 600 |
|
|||
|
|
| 100 Mbps | ~16 000 |
|
|||
|
|
| 1 Gbps | ~160 000 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Хранение
|
|||
|
|
|
|||
|
|
### Состояния операции (UX)
|
|||
|
|
|
|||
|
|
Операция проходит два различимых состояния:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
publish ──→ cement (quorum event) ──→ settle (apply at window close)
|
|||
|
|
"confirmed" "settled"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **Cemented (quorum event):** 67% active_chain_length подтвердили операцию через BundledConfirmation. Операция необратима и гарантированно будет применена в конце окна. Wallet показывает «confirmed».
|
|||
|
|
- **Settled (apply at window close, в конце окна):** все cemented операции окна применены батчем к Account Table в детерминированном порядке. account_root зафиксирован в proposal. Wallet показывает «settled».
|
|||
|
|
|
|||
|
|
Между cement и settle операция уже необратима — настройка двух UI-состояний нужна только для индикации завершённости state transition. Зависимые операции (Transfer на только что созданный аккаунт) сериализуются по окнам через confirmer dependency rule, поэтому cemented операция гарантированно settle-ится.
|
|||
|
|
|
|||
|
|
### Модель: глобальное состояние + локальная история
|
|||
|
|
|
|||
|
|
Узлы хранят глобальное состояние (Account Table, Node Table, Candidate Pool, proposals). Тела операций аккаунтов хранятся у владельцев. После settle (apply at window close) state transition применён — балансы в таблице обновлены, тело операции сети больше не нужно.
|
|||
|
|
|
|||
|
|
### Три уровня участников
|
|||
|
|
|
|||
|
|
**Узел (валидатор)** — десктоп или сервер, 24/7, минимум 3 ядра (1 узел = 3 ядра, 50 ядер = 16 узлов):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Хранит:
|
|||
|
|
Account Table (account_id, balance, frontier_hash, pubkey)
|
|||
|
|
+ persistent sparse Merkle tree (account_root обновляется при settle)
|
|||
|
|
Node Table (node_id, pubkey, start_window, chain_length)
|
|||
|
|
+ persistent sparse Merkle tree (node_root обновляется при settle)
|
|||
|
|
Candidate Pool (node_id, pubkey, operator, proof_endpoint, W_start, expires)
|
|||
|
|
+ persistent sparse Merkle tree (candidate_root обновляется при settle)
|
|||
|
|
Proposals (навсегда)
|
|||
|
|
Blob Buffer (ephemeral) (TTL = τ₂, для кратковременных сообщений)
|
|||
|
|
Persistent Blob Storage (TTL = 0, контент app_id на которые узел подписан)
|
|||
|
|
Genesis Content (книга Montana, mandatory replication)
|
|||
|
|
Persistent Blob Index ((app_id, data_hash) → blob, с флагом is_manifest)
|
|||
|
|
|
|||
|
|
Делает:
|
|||
|
|
TimeChain VDF (1 ядро, 24/7)
|
|||
|
|
NodeChain VDF (1 ядро, 24/7)
|
|||
|
|
Валидация операций (1+ ядро)
|
|||
|
|
P2P gossip (операции, confirmations, reveals, proposals)
|
|||
|
|
Почтовый ящик (хранит сообщения для своего владельца пока тот офлайн)
|
|||
|
|
Content replication (DHT provide, gossip announce, serving ContentRequest/ChunkRequest)
|
|||
|
|
Chunk verification (SHA-256 + Merkle reconstruction для полученных чанков)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Кошелёк (клиент)** — телефон, онлайн когда используется:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Хранит:
|
|||
|
|
Свои ключи (seed → keypairs, включая encryption key для application layer)
|
|||
|
|
Свои контакты (адресная книга: имя → mt-адрес, с локальным override)
|
|||
|
|
Локальная история (своя цепочка операций для UX)
|
|||
|
|
Сообщения (локальная история переписки, messenger session states)
|
|||
|
|
Timestamp proofs (Anchor + BundledConfirmations + proposal headers, локально)
|
|||
|
|
Подписки (app_id каналов, книги, профилей на которые подписан)
|
|||
|
|
Реплики контента (persistent blobs подписанных app_id по желанию)
|
|||
|
|
|
|||
|
|
Делает:
|
|||
|
|
Отправка/получение переводов
|
|||
|
|
Мессенджер (P2P напрямую через libp2p)
|
|||
|
|
Discovery (через application layer)
|
|||
|
|
Запрос pubkey и proposals у узлов сети
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Доверенный узел** — узел друга, семьи, сообщества:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Делает:
|
|||
|
|
Всё что узел + хранит Blob Buffer для привязанных аккаунтов
|
|||
|
|
Владелец аккаунта привязывает свой account_id к доверенному узлу
|
|||
|
|
Узел хранит зашифрованные сообщения (содержимое скрыто)
|
|||
|
|
Владелец забирает сообщения когда появляется онлайн
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Размеры
|
|||
|
|
|
|||
|
|
| Участник | Данные | Размер |
|
|||
|
|
|----------|--------|--------|
|
|||
|
|
| Узел (1M аккаунтов) | Account Table + Node Table + Candidate Pool + Proposals | ~2 GB |
|
|||
|
|
| Узел (10M аккаунтов) | Account Table + Node Table + Candidate Pool + Proposals | ~11 GB |
|
|||
|
|
| Узел (100M аккаунтов) | Account Table + Node Table + Candidate Pool + Proposals | ~101 GB |
|
|||
|
|
| Кошелёк (обычный) | ~100 операций за 26 τ₂ + контакты + сообщения | ~1 MB |
|
|||
|
|
| Кошелёк (активный) | ~10 000 операций за 26 τ₂ | ~16 MB |
|
|||
|
|
| Корпорация | ~1M Anchor за 26 τ₂ | ~0.8 GB |
|
|||
|
|
|
|||
|
|
### Потеря данных клиента
|
|||
|
|
|
|||
|
|
Потеря устройства: баланс в Account Table цел и публичен, seed восстанавливает ключи, доступ к аккаунту полностью восстанавливается. Локальная история переводов и сообщений утрачена — но баланс читается из Account Table напрямую. Если есть доверенный узел — зашифрованные сообщения можно восстановить.
|
|||
|
|
|
|||
|
|
### Fast Sync (новый узел)
|
|||
|
|
|
|||
|
|
1. Цепочка proposals от генезиса — проверка TimeChain-цепочки и подписей proposer-узлов (мегабайты)
|
|||
|
|
2. Snapshot трёх таблиц (Account Table + Node Table + Candidate Pool) от пиров на момент окна W (произвольное недавнее окно)
|
|||
|
|
3. Reconstructed `account_root`, `node_root` и `candidate_root` сравниваются с соответствующими полями из proposal окна W. Все три совпадают → snapshot валиден. Проверка `state_root = SHA-256("mt-state-root" || node_root || candidate_root || account_root)` — дополнительный integrity check.
|
|||
|
|
4. Catch-up после окна W до текущего:
|
|||
|
|
- Запросить cemented UserObjects и применить их батчем к Account Table по алгоритму apply at window close (включая проверку prev_hash и баланса).
|
|||
|
|
- Запросить cemented ControlObjects (NodeRegistration) и применить их к Candidate Pool в детерминированном порядке. Применить selection events.
|
|||
|
|
- Выполнить incremental update Merkle trees (account_root, node_root, candidate_root) для отражения changes.
|
|||
|
|
- На каждом промежуточном proposal сверять локальный state_root с заявленным в proposal header
|
|||
|
|
5. **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. Узел синхронизирован и готов к участию
|
|||
|
|
|
|||
|
|
Snapshot привязан к конкретному proposal (settled state после apply at window close). Catch-up дистанция определяется свежестью snapshot — обычно несколько окон.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Application Layer
|
|||
|
|
|
|||
|
|
Montana — цифровой стандарт времени. Приложения управляют своим состоянием самостоятельно (серверы, базы данных, P2P). Montana хранит только криптографические отпечатки с привязкой ко времени — 32 байта на запись.
|
|||
|
|
|
|||
|
|
### Модель приложения на Montana
|
|||
|
|
|
|||
|
|
Приложение Montana — это набор узлов с интерфейсом. Создатель приложения запускает узлы Montana (обычные узлы, тикающие VDF, валидирующие операции, участвующие в консенсусе). Узлы зарабатывают TimeCoin за поддержание сети через лотерею.
|
|||
|
|
|
|||
|
|
**Для создателя приложения:**
|
|||
|
|
|
|||
|
|
- Не нужно строить отдельную инфраструктуру безопасности — приватность данных через Anchor (хэш в сети, контент у владельца зашифрованным), антицензура через Transport Obfuscation и Dandelion++, децентрализация через отсутствие центрального сервера получаются бесплатно из протокола
|
|||
|
|
- Бизнес-модель: эмиссия Montana через узлы создателя. Не реклама, не подписка, не продажа данных
|
|||
|
|
- Чем больше пользователей в приложении → тем больше операций в сети → тем больше нужно узлов для обслуживания (Blob Buffer, валидация, P2P gossip) → больше узлов = больше шансов в лотерее = больше TimeCoin
|
|||
|
|
|
|||
|
|
**Для пользователя:**
|
|||
|
|
|
|||
|
|
- Каждое действие в приложении создаёт операцию в его AccountChain
|
|||
|
|
- account_chain_length растёт автоматически с каждым окном с операцией
|
|||
|
|
- Пользователь автоматически участвует в лотерее в каждом окне с операцией — без заявок, без стейкинга, без понимания криптографии
|
|||
|
|
- Шанс победы зависит от account_chain_length — длинная активная цепочка даёт реальные шансы выиграть 13 Ɉ
|
|||
|
|
- Ничего не привязано к конкретному приложению — seed принадлежит пользователю, account_id переходит между приложениями без потери истории
|
|||
|
|
|
|||
|
|
**Нулевая стоимость переключения приложений.** AccountChain пользователя — его собственность. Если приложение закрылось или пользователь хочет уйти — account_id, баланс, история и накопленный account_chain_length остаются. Пользователь продолжает в другом приложении на том же протоколе. Приложения вынуждены конкурировать качеством, а не замком.
|
|||
|
|
|
|||
|
|
### Двигатель роста сети через AccountChain
|
|||
|
|
|
|||
|
|
Лотерея Montana объединяет два класса участников: узлы (NodeChain) и аккаунты (AccountChain). Узлы доминируют статистически из-за непрерывного присутствия. Аккаунты получают долю эмиссии через активность пользователей. Эта механика создаёт самоподдерживающийся цикл роста сети — см. раздел "Эмиссия → Двигатель роста сети".
|
|||
|
|
|
|||
|
|
|
|||
|
|
### Anchor
|
|||
|
|
|
|||
|
|
Одна операция, данные навсегда привязаны к timechain_value конкретного окна.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Anchor:
|
|||
|
|
prev_hash 32B
|
|||
|
|
account_id 32B
|
|||
|
|
app_id 32B <- SHA-256("mt-app" || app_name)
|
|||
|
|
data_hash 32B <- Merkle root, H(document), произвольный хэш
|
|||
|
|
signature 666B
|
|||
|
|
Итого: ~796B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
app_id — детерминированный идентификатор пространства имён. Вычисляется из имени приложения, регистрация не требуется. Позволяет фильтровать, индексировать, строить лёгкие клиенты для конкретного приложения.
|
|||
|
|
|
|||
|
|
### Timestamp Proof
|
|||
|
|
|
|||
|
|
Стандартный формат доказательства: документ D существовал не позже момента T.
|
|||
|
|
|
|||
|
|
В модели v20.2.0 операции аккаунтов финализируются через BundledConfirmations узлов-confirmers, не через включение в proposal. Доказательство существования Anchor — набор подписанных подтверждений с суммарным chain_length ≥ quorum.
|
|||
|
|
|
|||
|
|
Proof собирается владельцем Anchor в момент финализации и хранится локально вместе с документом. Сеть не обязана хранить BundledConfirmations долгосрочно — ответственность за сохранение proof лежит на стороне, которой нужно доказать timestamp.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Структура proof:
|
|||
|
|
1. Документ D и H(D)
|
|||
|
|
2. Anchor body (prev_hash, account_id, app_id, data_hash, signature)
|
|||
|
|
3. Если data_hash = MerkleRoot batch'а: Merkle path от H(D) до data_hash
|
|||
|
|
4. Набор BundledConfirmations за окно W cementing'а Anchor:
|
|||
|
|
- каждая содержит H(Anchor) в op_hashes[]
|
|||
|
|
- каждая подписана confirmer node_pubkey
|
|||
|
|
- каждая содержит endpoint NodeChain confirmer на момент окна W
|
|||
|
|
- суммарный chain_length confirmers ≥ 67% active_chain_length(W)
|
|||
|
|
5. Proposal header окна W (содержит timechain_value = T)
|
|||
|
|
6. Цепочка proposal headers от W до genesis (через prev_proposal_hash)
|
|||
|
|
|
|||
|
|
Верификация любым третьим лицом, без доверия Montana-узлу:
|
|||
|
|
1. Если есть Merkle path: пересчитать H(D) → data_hash, сравнить с data_hash в Anchor
|
|||
|
|
2. Проверить FN-DSA-512 подпись на Anchor
|
|||
|
|
3. Для каждой BundledConfirmation: проверить FN-DSA-512 подпись confirmer
|
|||
|
|
4. Для каждой confirmation: пересчитать NodeChain endpoint от start_window до окна W,
|
|||
|
|
подтвердить заявленный chain_length
|
|||
|
|
5. Суммировать chain_length подтверждающих, проверить ≥ 67% active_chain_length(W)
|
|||
|
|
6. Из proposal header окна W взять timechain_value = T
|
|||
|
|
7. Пересчитать TimeChain VDF от proposal окна W до genesis по prev_proposal_hash
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Proposals хранятся навсегда — timechain_value(W) и цепочка к genesis всегда доступны. BundledConfirmations хранятся локально владельцем proof. Timestamp proof самодостаточен и верифицируем в любой момент в будущем.
|
|||
|
|
|
|||
|
|
### Примеры
|
|||
|
|
|
|||
|
|
**Мессенджер.** Каждое сообщение хэшируется, цепочка хэшей формирует Merkle root, Merkle root записывается в Anchor раз в одно или несколько окон. Montana хранит 32 байта — доказательство что набор сообщений существовал на конкретном window_index. Подделать историю переписки невозможно — хэш не совпадёт.
|
|||
|
|
|
|||
|
|
**Архив документов.** Компания ежедневно записывает Merkle root документов. Через 10 лет регулятор спрашивает «существовал ли документ X на дату Y». Компания предоставляет документ, Merkle proof и ссылку на proposal. Верификация математическая.
|
|||
|
|
|
|||
|
|
**Социальная сеть.** Каждый пост привязан к Montana Time через Anchor. Порядок публикаций доказуем. Редактирование не скрывает оригинал — хэш оригинала уже в цепочке.
|
|||
|
|
|
|||
|
|
### Экономика
|
|||
|
|
|
|||
|
|
Anchor бесплатен. Тысячи приложений записывающих якоря — утилитарное использование Montana Time. Спрос на токен привязан к утилитарной функции: перевод ценности и запись времени, не спекуляция.
|
|||
|
|
|
|||
|
|
Не нужны смарт-контракты. Не нужен Turing-complete язык. Не нужен газ. Не нужны комиссии.
|
|||
|
|
|
|||
|
|
### Phone Discovery и Messenger
|
|||
|
|
|
|||
|
|
Phone discovery и messenger — полностью на Application Layer, не в protocol core. Protocol не знает о телефонах, именах, сообщениях. Application Layer реализует эти функции через Content Layer (см. ниже) и Interop Standards.
|
|||
|
|
|
|||
|
|
Протокол предоставляет:
|
|||
|
|
- **Identity:** account_id через OpenAccount
|
|||
|
|
- **Timestamping:** Anchor с произвольным data_hash
|
|||
|
|
- **Storage:** Blob Buffer для хранения произвольных байт (persistent и ephemeral режимы)
|
|||
|
|
- **Transport:** libp2p gossip и Content Request Protocol
|
|||
|
|
|
|||
|
|
Всё остальное — phone discovery, encryption, messaging protocols, profiles — реализуется на уровне приложения. Стандарты совместимости фиксированы в разделе Application Layer Interop Standards.
|
|||
|
|
|
|||
|
|
### Content Layer
|
|||
|
|
|
|||
|
|
Content Layer предоставляет механизм хранения и репликации произвольных данных между узлами с привязкой к Anchor. Узлы подписанные на app_id хранят контент этого app_id. Новый узел или узел восстанавливающийся после offline скачивает недостающие блобы у пиров, верифицирует целостность через хэши из Anchor. Целевая задача — децентрализованное облако данных где каждый подписчик является хранителем, а факт существования контента зафиксирован навсегда через Anchor в proposal chain.
|
|||
|
|
|
|||
|
|
#### Persistent Blob
|
|||
|
|
|
|||
|
|
Blob Buffer получает второй режим хранения:
|
|||
|
|
|
|||
|
|
- **TTL = τ₂** (ephemeral) — кратковременные сообщения, удаляются через τ₂
|
|||
|
|
- **TTL = 0** (persistent) — контент привязанный к Anchor, хранится бессрочно пока узел подписан на соответствующий app_id
|
|||
|
|
|
|||
|
|
Persistent blob индексируется парой `(app_id, data_hash) → blob_bytes`. Блоб может содержать manifest чанкованного контента (флаг `is_manifest = true`) или один чанк/целый файл. Размер одного blob ограничен chunk_size. Удаление persistent blob — решение оператора узла через явную отписку от app_id, не автоматически по таймеру.
|
|||
|
|
|
|||
|
|
#### Chunking Standard
|
|||
|
|
|
|||
|
|
Большие файлы разбиваются на чанки фиксированного размера:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
chunk_size = 256 KB (фиксировано протоколом)
|
|||
|
|
|
|||
|
|
chunk formaт: chunk_index (4B, u32) || chunk_data (≤262 144 bytes)
|
|||
|
|
chunk_hash = SHA-256("mt-content-chunk" || chunk_data)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Manifest содержит метаданные файла и упорядоченный список chunk_hashes:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Manifest {
|
|||
|
|
version: u16 (currently 1)
|
|||
|
|
file_name: string (UTF-8, length-prefixed, max 256 bytes)
|
|||
|
|
file_size: u64
|
|||
|
|
mime_type: string (UTF-8, length-prefixed, max 64 bytes)
|
|||
|
|
chunk_count: u32
|
|||
|
|
chunk_hashes: [32B × chunk_count]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Merkle tree строится поверх chunk_hashes. Корень дерева:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
data_hash = SHA-256("mt-content-manifest" || canonical_serialization(Manifest))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Этот `data_hash` записывается в Anchor. Маленький файл (< chunk_size) — один чанк, manifest с `chunk_count = 1`.
|
|||
|
|
|
|||
|
|
Manifest сохраняется как первый persistent blob по `(app_id, data_hash)` с флагом `is_manifest = true`. Индекс узла хранит эту связь, позволяя быстро находить manifest для любого Anchor.
|
|||
|
|
|
|||
|
|
#### Content Request Protocol
|
|||
|
|
|
|||
|
|
P2P сообщения libp2p для обмена контентом между узлами:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
ContentRequest:
|
|||
|
|
app_id 32B
|
|||
|
|
data_hash 32B (data_hash из Anchor — manifest root)
|
|||
|
|
|
|||
|
|
ContentResponse:
|
|||
|
|
status 1B (0 = ok, 1 = not_found, 2 = error)
|
|||
|
|
payload variable (serialized Manifest если status = 0)
|
|||
|
|
|
|||
|
|
ChunkRequest:
|
|||
|
|
data_hash 32B (data_hash манифеста)
|
|||
|
|
chunk_index 4B
|
|||
|
|
|
|||
|
|
ChunkResponse:
|
|||
|
|
status 1B
|
|||
|
|
chunk_data variable (до chunk_size байт)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Процесс верификации при получении:**
|
|||
|
|
|
|||
|
|
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 возможно только через software upgrade узла (новая версия Rust core с обновлённой константой), как любое изменение Genesis Decree.
|
|||
|
|
|
|||
|
|
### Application Layer Interop Standards
|
|||
|
|
|
|||
|
|
Этот раздел фиксирует минимальные стандарты для совместимости между приложениями Montana. Приложения следующие этим стандартам могут взаимодействовать между собой — обмениваться профилями, сообщениями, контентом. Приложения использующие другие форматы работают в изоляции.
|
|||
|
|
|
|||
|
|
Это нормативный раздел: форматы и формулы в нём обязательны для interop-совместимых приложений.
|
|||
|
|
|
|||
|
|
#### Canonical app_id registry
|
|||
|
|
|
|||
|
|
Фиксированные app_id для стандартных функций приложений:
|
|||
|
|
|
|||
|
|
| Функция | Формула | Назначение |
|
|||
|
|
|---|---|---|
|
|||
|
|
| genesis content | `SHA-256("mt-app" \|\| "montana")` | Книга Montana и её обновления |
|
|||
|
|
| profile | `SHA-256("mt-app" \|\| "profile")` | Публичные профили пользователей |
|
|||
|
|
| encryption keys | `SHA-256("mt-app" \|\| "encryption-keys")` | Discovery encryption pubkeys |
|
|||
|
|
| messenger prekeys | `SHA-256("mt-app" \|\| "messenger-prekeys")` | Pre-keys bundles для Double Ratchet |
|
|||
|
|
| phone discovery | `SHA-256("mt-app" \|\| "phone-discovery")` | Public mode phone → account lookup |
|
|||
|
|
|
|||
|
|
Пользовательские каналы используют формулу `SHA-256("mt-app" || channel_name)` где `channel_name` — произвольная строка выбранная создателем канала. Уникальность каналов обеспечивается через уникальность имени; коллизии разрешаются по первому cemented Anchor в данном app_id.
|
|||
|
|
|
|||
|
|
#### ProfileBlob format
|
|||
|
|
|
|||
|
|
Канонический формат публичного профиля пользователя:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
ProfileBlob {
|
|||
|
|
version u16 (currently 1)
|
|||
|
|
display_name string (UTF-8, length-prefixed, max 64 bytes, may be empty)
|
|||
|
|
avatar_hash 32B (ref to image blob by data_hash, или 0x00..00)
|
|||
|
|
bio string (UTF-8, length-prefixed, max 256 bytes, may be empty)
|
|||
|
|
updated_at u64 (unix timestamp публикации)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Сериализация: little-endian, length-prefix для строк (u16 length + bytes).
|
|||
|
|
|
|||
|
|
**Публикация профиля:**
|
|||
|
|
|
|||
|
|
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% active_chain_length
|
|||
|
|
confirmations с timechain_value T_W
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
data_hash — произвольный хэш: Merkle root документов, хэш batch'а Rollup, fingerprint состояния. Montana не интерпретирует содержимое — хранит 32 байта с привязкой ко времени.
|
|||
|
|
|
|||
|
|
#### Read — сбор proof
|
|||
|
|
|
|||
|
|
Внешняя система собирает timestamp proof в момент финализации Anchor.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Вход: Anchor (только что финализированный)
|
|||
|
|
Выход: Anchor body + BundledConfirmations покрывающие H(Anchor) +
|
|||
|
|
proposal header окна cementing'а + цепочка proposal headers до genesis
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Сбор proof — клиентская задача. После получения BundledConfirmations с суммарным chain_length ≥ quorum клиент сохраняет proof локально. Узлы Montana не обязаны хранить BundledConfirmations долгосрочно — они нужны только для текущего подсчёта quorum.
|
|||
|
|
|
|||
|
|
#### Verify — верификация
|
|||
|
|
|
|||
|
|
Внешняя система проверяет proof автономно, без доверия к Montana-узлу.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. Если есть Merkle path: пересчитать H(D) → data_hash в Anchor
|
|||
|
|
2. Проверить FN-DSA-512 подпись на Anchor
|
|||
|
|
3. Для каждой BundledConfirmation в proof:
|
|||
|
|
a. Проверить FN-DSA-512 подпись confirmer
|
|||
|
|
b. Пересчитать NodeChain endpoint confirmer от start_window до окна W
|
|||
|
|
c. Подтвердить заявленный chain_length
|
|||
|
|
4. Суммировать chain_length подтверждающих ≥ 67% active_chain_length(W)
|
|||
|
|
5. Проверить FN-DSA-512 подпись proposer на header окна W
|
|||
|
|
6. Проверить timechain_value(W) пересчётом TimeChain VDF от T_{W-1}
|
|||
|
|
7. Проверить цепочку proposals от W до genesis (prev_proposal_hash)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Шаги 1, 2, 3a, 5: ~O(1) хэш-операций (константное число CPU-циклов). Шаг 3b: пересчёт NodeChain VDF confirmer — `m × (W − start_window)` хэшей, параллелизуется по сегментам. Шаг 6: `D` хэшей на одном ядре (один сегмент TimeChain VDF). Шаг 7: линейная проверка подписей и хэшей по цепочке proposals от окна W до genesis.
|
|||
|
|
|
|||
|
|
Полная верификация от генезиса: H сегментов TimeChain VDF, каждый независим. На C ядрах: ~(H/C) × D хэшей. TimeChain хранит все промежуточные T_r в proposals — параллелизация полная.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Ключи
|
|||
|
|
|
|||
|
|
### Мнемоника и seed
|
|||
|
|
|
|||
|
|
24 слова из словаря мнемоники (2 048 английских слов). 256 бит энтропии + 8 бит checksum.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
mnemonic: 24 слова
|
|||
|
|
seed: PBKDF2-SHA512(mnemonic, "mt-seed", 2048 итераций)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Деривация
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
seed
|
|||
|
|
├── Аккаунт: FN-DSA-512 keypair (HMAC-SHA256(seed || "mt-account-key"))
|
|||
|
|
│ → account_id = SHA-256("mt-account" || account_pubkey)
|
|||
|
|
└── Узел: FN-DSA-512 keypair (HMAC-SHA256(seed || "mt-node-key"))
|
|||
|
|
→ node_id = SHA-256("mt-node" || node_pubkey)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Один seed порождает два FN-DSA-512 keypair. Аккаунт — подпись операций пользователя. Узел — подпись proposals и NodeChain endpoints. Это полный набор секретных материалов protocol core.
|
|||
|
|
|
|||
|
|
account_id и node_id выводятся из публичных ключей, верифицируемы без знания seed.
|
|||
|
|
|
|||
|
|
**Application-level keys.** Приложения могут выводить дополнительные ключи из того же seed через свои domain separators (не protocol-critical). Рекомендованный стандарт Application Layer определяет encryption keypair через `HMAC-SHA256(seed || "mt-app-encryption-key")` для ML-KEM-768. Этот ключ не входит в protocol core — его знание не нужно узлам консенсуса. Приложения использующие этот стандарт получают совместимое восстановление: один seed → все ключи (account, node, encryption).
|
|||
|
|
|
|||
|
|
Следствие: любое устройство с seed получает полный доступ к аккаунту. Баланс читается из текущего Account Table — никакого локального состояния не требуется. Бэкап = 24 слова, восстановление мгновенное.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Криптографическая реализация
|
|||
|
|
|
|||
|
|
### Primitive layer
|
|||
|
|
|
|||
|
|
Собственная реализация криптографических примитивов запрещена. Только audited библиотеки с constant-time гарантиями и опубликованными test vectors.
|
|||
|
|
|
|||
|
|
| Примитив | Стандарт | Роль |
|
|||
|
|
|----------|----------|------|
|
|||
|
|
| SHA-256 | FIPS 180-4 | TimeChain, NodeChain, адреса, Merkle-деревья |
|
|||
|
|
| FN-DSA-512 | NIST PQC selection финал (июль 2022), forthcoming FIPS 206, reference implementation production-ready | Подписи операций аккаунтов и proposals узлов |
|
|||
|
|
|
|||
|
|
### Consensus encoding layer
|
|||
|
|
|
|||
|
|
Консенсусно-критическая поверхность: каноническая сериализация, Merkle layout и domain separation. Разная сериализация одного объекта = разный хэш = форк. Требования:
|
|||
|
|
|
|||
|
|
- Fixed binary encoding для каждого консенсусного объекта
|
|||
|
|
- Length-prefix кодирование полей, фиксированный endianness (little-endian)
|
|||
|
|
- Domain separation для всех хэшей:
|
|||
|
|
|
|||
|
|
| Домен | Контекст |
|
|||
|
|
|-------|----------|
|
|||
|
|
| `mt-op` | Хэширование операций аккаунтов |
|
|||
|
|
| `mt-header` | Хэширование proposal headers |
|
|||
|
|
| `mt-account` | Деривация account_id |
|
|||
|
|
| `mt-candidate-vdf-init` | VDF init seed для кандидата на вход в сеть |
|
|||
|
|
| `mt-merkle-leaf` | Листья Merkle-деревьев |
|
|||
|
|
| `mt-merkle-node` | Внутренние узлы Merkle-деревьев |
|
|||
|
|
| `mt-state-root` | Композиция state_root из node_root, candidate_root и account_root |
|
|||
|
|
| `mt-timechain` | TimeChain VDF seed |
|
|||
|
|
| `mt-nodechain-init` | NodeChain init seed |
|
|||
|
|
| `mt-confirmation` | Хэширование async confirmations |
|
|||
|
|
| `mt-app` | Деривация app_id для Application Layer |
|
|||
|
|
| `mt-node` | Деривация node_id |
|
|||
|
|
| `mt-genesis` | Деривация frontier_hash genesis-аккаунтов |
|
|||
|
|
| `mt-nodechain-genesis` | Деривация nodechain_init для genesis-узлов |
|
|||
|
|
| `mt-seed` | Salt для PBKDF2 деривации seed из мнемоники |
|
|||
|
|
| `mt-account-key` | Деривация keypair аккаунта из seed |
|
|||
|
|
| `mt-node-key` | Деривация keypair узла из seed |
|
|||
|
|
| `mt-account-lottery` | Endpoint AccountChain для лотереи |
|
|||
|
|
| `mt-content-chunk` | Хэширование чанков контента в Content Layer |
|
|||
|
|
| `mt-content-manifest` | Хэширование манифеста чанкованного контента |
|
|||
|
|
| `mt-profile` | Хэширование ProfileBlob в Application Layer |
|
|||
|
|
| `mt-encryption-key` | Хэширование EncryptionKeyBlob в Application Layer |
|
|||
|
|
| `mt-app-encryption-key` | Деривация encryption keypair из seed в Application Layer |
|
|||
|
|
| `mt-prekeys` | Хэширование PreKeyBundle в Application Layer |
|
|||
|
|
| `mt-phone-public` | Хэширование phone_hash для public mode phone discovery |
|
|||
|
|
| `mt-tunnel` | IBT proof подпись при входе на узел |
|
|||
|
|
| `mt-bootstrap-pow` | Proof-of-work при подключении к bootstrap |
|
|||
|
|
|
|||
|
|
- Альтернативные сериализации запрещены
|
|||
|
|
- Test vectors для каждого консенсусного объекта
|
|||
|
|
- Cross-implementation conformance tests перед запуском mainnet
|
|||
|
|
|
|||
|
|
### Protocol layer
|
|||
|
|
|
|||
|
|
Собственная реализация поверх криптографического ядра:
|
|||
|
|
|
|||
|
|
| Компонент | Назначение |
|
|||
|
|
|-----------|------------|
|
|||
|
|
| Merkle-деревья | State Root (из SHA-256 вызовов) |
|
|||
|
|
| VDF scheduling | Управление TimeChain и NodeChain цепочками |
|
|||
|
|
| State machine | Account Table, Node Table, state transitions |
|
|||
|
|
| P2P gossip | Распространение операций, confirmations и proposals |
|
|||
|
|
|
|||
|
|
### Инфраструктура
|
|||
|
|
|
|||
|
|
| Библиотека | Назначение |
|
|||
|
|
|------------|------------|
|
|||
|
|
| RocksDB | Хранение Account Table и операций |
|
|||
|
|
| libp2p | P2P транспорт |
|
|||
|
|
|
|||
|
|
Production: Rust.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Сетевой уровень
|
|||
|
|
|
|||
|
|
Все временные параметры сетевого уровня (frame rate, padding window, feeler interval, Dandelion timers) — implementation guidance для локального сетевого стека узла. Они оперируют на локальных часах узла и находятся вне scope consensus state.
|
|||
|
|
|
|||
|
|
### Transport Obfuscation
|
|||
|
|
|
|||
|
|
Монтана — персональная сеть. Каждый узел — персональный сервер участника. Транспортный слой построен из этого определения: персональный сервер отвечает только участникам, персональный мессенджер скрывает тайминг сообщений, персональный = доступный обычному человеку.
|
|||
|
|
|
|||
|
|
#### Шифрование
|
|||
|
|
|
|||
|
|
Все P2P-соединения инкапсулированы в TLS 1.3 на порт 443. Noise framework (встроен в libp2p) для аутентификации по публичному ключу узла внутри TLS. Содержимое трафика недоступно наблюдателю.
|
|||
|
|
|
|||
|
|
#### Identity-Bound Tunnel (IBT)
|
|||
|
|
|
|||
|
|
Персональный сервер отвечает только участникам сети. После TLS handshake клиент отправляет proof аутентификации. Узлы (зарегистрированные и приглашённые) подписывают node keypair. Аккаунты (клиенты) подписывают account keypair.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
proof = FN-DSA-512_sign(client_privkey,
|
|||
|
|
"mt-tunnel" || server_node_id || floor(current_window_index / 2))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Сервер проверяет:
|
|||
|
|
|
|||
|
|
1. Подпись валидна для заявленного client_pubkey
|
|||
|
|
2. Window slot = текущий ИЛИ предыдущий (окно = 2 window_index)
|
|||
|
|
3. Уровень доступа — сервер проверяет client_pubkey по трём таблицам последовательно, первое совпадение определяет уровень:
|
|||
|
|
- `node_id = SHA-256("mt-node" || client_pubkey)` в Node Table → **полный gossip** (клиент подключился node keypair)
|
|||
|
|
- `node_id` с `node_pubkey = client_pubkey` в Candidate Pool → **read-only gossip**: получает proposals (кандидат подключился node keypair)
|
|||
|
|
- `account_id = SHA-256("mt-account" || suite_id || client_pubkey)` в Account Table → **подключение к доверенному узлу** (клиент подключился account keypair)
|
|||
|
|
- Ни одно не найдено → отказ
|
|||
|
|
|
|||
|
|
Условия 1-2 выполнены + уровень 3 определён → Noise handshake → Montana P2P с соответствующим уровнем доступа.
|
|||
|
|
Любое не выполнено → TLS alert `bad_certificate`, close. Стандартное поведение сервера с обязательной аутентификацией клиента — таких серверов в интернете миллионы (корпоративные порталы, API, банковские системы).
|
|||
|
|
|
|||
|
|
Replay protection: server_node_id привязывает proof к конкретному получателю. Window slot ограничивает replay window до 2 окон.
|
|||
|
|
|
|||
|
|
Bootstrap exception: genesis bootstrap nodes хардкодированы как `(IP, node_id, pubkey) × 12`. Bootstrap принимает proof от любого валидного FN-DSA-512 ключа (Account Table не проверяется). Для защиты от connection flood клиент прилагает proof-of-work:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
SHA-256("mt-bootstrap-pow" || proof || nonce) < target
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`target` подбирается чтобы стоимость ≈ 100ms CPU. PoW требуется только при подключении к bootstrap, не к обычным peers.
|
|||
|
|
|
|||
|
|
#### Uniform Framing
|
|||
|
|
|
|||
|
|
Все Montana-сообщения внутри IBT-соединения фрагментируются на фреймы фиксированного размера:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
frame_size = 1024 bytes
|
|||
|
|
|
|||
|
|
frame format:
|
|||
|
|
flags 1B (0x01 = data, 0x02 = padding, 0x04 = continuation)
|
|||
|
|
length 2B (полезная нагрузка, ≤1021B)
|
|||
|
|
payload 1021B (данные или random padding до frame_size)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Персональный мессенджер скрывает тайминг: между узлами идёт постоянный поток фреймов. Реальные Montana-сообщения замещают padding-фреймы, не добавляются к ним. Наблюдатель внутри сети не может отличить перевод от доказательства времени от тишины — всё одинаковые зашифрованные фреймы.
|
|||
|
|
|
|||
|
|
Параметры:
|
|||
|
|
|
|||
|
|
- Baseline frame rate: 1 frame/сек на исходящих соединениях. Входящие — фреймы при наличии данных
|
|||
|
|
- Maximum burst: ≤ 8 frames подряд без паузы ≥ 10ms
|
|||
|
|
- Minimum padding ratio: ≥ 20% фреймов в скользящем 60-секундном окне на исходящих
|
|||
|
|
|
|||
|
|
Персональный = доступный: 13 исходящих × 1 frame/сек × 1024 bytes = 13 KB/сек ≈ 33 GB/мес. Приемлемо для домашнего сервера.
|
|||
|
|
|
|||
|
|
#### Transport Randomness
|
|||
|
|
|
|||
|
|
Все рандомизированные решения транспортного уровня (stem routing, frame scheduling, nonce generation) используют CSPRNG из OS entropy pool. Детерминированный PRNG от node state запрещён для transport-layer randomness.
|
|||
|
|
|
|||
|
|
Transport obfuscation ортогонален консенсусу. TimeChain, NodeChain, state machine работают поверх любого транспорта без изменений.
|
|||
|
|
|
|||
|
|
### Peer Selection
|
|||
|
|
|
|||
|
|
Открытый вход с VDF-барьером делает sybil-узлы дорогими: каждый sybil = 13 000 окон VDF + 3 ядра CPU + selection event. Peer selection использует diversity constraints из протокольных данных (start_window) и сетевых (/16, ASN).
|
|||
|
|
|
|||
|
|
P2P gossip — только зарегистрированные и приглашённые узлы (уровни 1-2 IBT, см. Transport Obfuscation → Identity-Bound Tunnel). Аккаунты (уровень 3 IBT) взаимодействуют через свой доверенный узел.
|
|||
|
|
|
|||
|
|
#### Исходящие соединения
|
|||
|
|
|
|||
|
|
13 исходящих, все полные. Uniform framing скрывает типы сообщений — отдельные relay-only соединения не нужны.
|
|||
|
|
|
|||
|
|
Выбор: случайный 50/50 из таблиц «новые» и «проверенные». Бакетирование с секретным ключом узла. Без preference по chain_length — выбор равномерный.
|
|||
|
|
|
|||
|
|
#### Четыре уровня diversity
|
|||
|
|
|
|||
|
|
Каждый исходящий проверяется по всем четырём constraints:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Сетевые:
|
|||
|
|
/16 — не более 1 исходящего на /16 подсеть (IPv4) или /48 (IPv6)
|
|||
|
|
ASN — не более 2 исходящих на автономную систему
|
|||
|
|
|
|||
|
|
Протокольные:
|
|||
|
|
start_window — не более 2 исходящих к узлам с start_window в одном τ₂
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Сетевые constraints: /16 и ASN diversity. Протокольный constraint start_window канонически доступен из Node Table.
|
|||
|
|
|
|||
|
|
Следствие: кластер sybil зарегистрированных в один τ₂ → максимум 2 из 13 слотов. Eclipse требует узлы в 7+ разных AS в 7+ разных /16 с регистрацией в 7+ разных τ₂.
|
|||
|
|
|
|||
|
|
ASN-карта загружается при запуске. Без карты — fallback на /16.
|
|||
|
|
|
|||
|
|
#### Адресный менеджер
|
|||
|
|
|
|||
|
|
Две таблицы:
|
|||
|
|
|
|||
|
|
- **Новые** — адреса полученные через peer exchange и DHT. Узел ещё не подключался
|
|||
|
|
- **Проверенные** — адреса к которым узел успешно подключался через IBT
|
|||
|
|
|
|||
|
|
Бакетирование: `bucket = Hash(secret_key, source_group, addr_group) % N`. Детерминированно с секретным ключом — атакующий не может предсказать в какой бакет попадёт его адрес.
|
|||
|
|
|
|||
|
|
#### Входящие соединения
|
|||
|
|
|
|||
|
|
До 32 входящих. При переполнении — вытеснение:
|
|||
|
|
|
|||
|
|
1. Защитить 4 с наименьшим пингом
|
|||
|
|
2. Защитить 4 с последними полезными сообщениями (любое валидное Montana-сообщение которое узел ещё не видел)
|
|||
|
|
3. Защитить до 8 из разных подсетей (по одному от каждой)
|
|||
|
|
4. Защитить 4 с последними proposals
|
|||
|
|
5. Из оставшихся — вытеснить из крупнейшей подсетевой группы
|
|||
|
|
|
|||
|
|
#### Якоря
|
|||
|
|
|
|||
|
|
2 исходящих с наибольшим uptime соединения сохраняются каждые τ₂. При перезапуске после аварии или обновления — подключиться к якорям первым до случайного выбора из таблиц.
|
|||
|
|
|
|||
|
|
#### Feeler
|
|||
|
|
|
|||
|
|
Каждые 10 минут: подключиться к случайному адресу из «новых», выполнить IBT handshake (все три уровня проверки). Успех на любом уровне → перенести в «проверенные» с пометкой уровня (node / invited / account). Неуспех → пометить или удалить.
|
|||
|
|
|
|||
|
|
#### Ротация
|
|||
|
|
|
|||
|
|
По поведению: если peer не передал ни одного нового proposal за τ₂ — заменить. Peer с долей невалидных сообщений выше 50% в скользящем τ₁-окне — отключить с запретом переподключения на τ₂. Peer который relay-ит честно — полезен сети, остаётся.
|
|||
|
|
|
|||
|
|
#### PeerRecord
|
|||
|
|
|
|||
|
|
Формат записи о пире при peer exchange:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
PeerRecord:
|
|||
|
|
ip 16B (IPv4-mapped IPv6)
|
|||
|
|
port 2B (u16)
|
|||
|
|
node_id 32B
|
|||
|
|
node_pubkey 897B (FN-DSA-512)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Без node_id и node_pubkey клиент не может вычислить IBT proof для подключения. Peer exchange: не более 100 PeerRecord за сообщение. Не более 1 peer exchange сообщения в минуту от каждого peer.
|
|||
|
|
|
|||
|
|
### Censorship-Resistant Discovery
|
|||
|
|
|
|||
|
|
Генезис: 12 hardcoded bootstrap nodes `(IP, node_id, pubkey)`. Если все 12 IP заблокированы на уровне страны — новый узел не может войти в сеть. Четыре независимых канала обнаружения. Достаточно одного из четырёх.
|
|||
|
|
|
|||
|
|
**1. Peer exchange.** Каждый узел хранит и передаёт список активных пиров новичкам. Достаточно знать IP одного узла — друг, QR-код, мессенджер. Один живой контакт = вход в сеть.
|
|||
|
|
|
|||
|
|
**2. DHT.** Kademlia DHT поверх libp2p. Узлы находят друг друга без центральной точки. Идентификаторы рандомизированы — DHT не раскрывает node_id до установления Montana-соединения.
|
|||
|
|
|
|||
|
|
**3. Bridge nodes.** Узлы за пределами цензурируемой юрисдикции, опубликованные через внеполосные каналы (социальные сети, мессенджеры, печатные QR-коды). IP bridge node неизвестен фаерволу до использования.
|
|||
|
|
|
|||
|
|
**4. Encrypted Client Hello (ECH).** Bootstrap через CDN с поддержкой ECH. SNI зашифрован — наблюдатель видит IP CDN, но не целевой домен. Эффективен в юрисдикциях без активной блокировки ECH extension. В юрисдикциях блокирующих ECH (Китай с 2023, Россия с 2024) — канал неработоспособен. Для таких юрисдикций — каналы 1-3.
|
|||
|
|
|
|||
|
|
Избыточность = устойчивость. Четыре канала независимы. Блокировка одного не влияет на остальные.
|
|||
|
|
|
|||
|
|
### Dandelion++ (анонимность отправителя)
|
|||
|
|
|
|||
|
|
P2P gossip Montana ретранслирует операции через все узлы. Без защиты первый пир знает IP отправителя. Dandelion++ (Fanti et al. 2018) устраняет связь IP → операция модификацией существующего gossip.
|
|||
|
|
|
|||
|
|
**Две фазы:**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Stem (стебель):
|
|||
|
|
Операция проходит по цепочке случайных узлов (в среднем 2-3 hop).
|
|||
|
|
Каждый узел видит только предыдущий hop, не автора.
|
|||
|
|
На каждом hop с вероятностью p = 0.4 переход в fluff.
|
|||
|
|
E[stem_length] = 1/p = 2.5 hops.
|
|||
|
|
P(stem ≤ 1) = 40%, P(stem ≤ 3) = 78%.
|
|||
|
|
|
|||
|
|
Fluff (пух):
|
|||
|
|
Последний stem-узел запускает обычный gossip.
|
|||
|
|
Для всей сети операция «появилась» из случайной точки.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Stem routing.** Стебель использует только исходящие соединения — входящие не участвуют. Каждые 693 окна узел выбирает 2 из 13 исходящих как стебельных (stem epoch). Все стебельные операции в эпохе направляются через одного из этих 2 (выбор по hash(msg)).
|
|||
|
|
|
|||
|
|
**Применение по типу объекта:**
|
|||
|
|
|
|||
|
|
| Объект | Режим | Причина |
|
|||
|
|
|--------|-------|---------|
|
|||
|
|
| UserObject (Transfer, Anchor, OpenAccount, ChangeKey) | Stem → fluff | Скрыть IP отправителя |
|
|||
|
|
| ControlObject (NodeRegistration) | Stem → fluff | Скрыть IP регистрирующегося кандидата |
|
|||
|
|
| VDF Reveal | Прямой gossip (без stem) | node_id публичен в reveal, анонимность невозможна; IP скрыт Transport Obfuscation (TLS 1.3 на порт 443) |
|
|||
|
|
| Confirmation | Stem → fluff | Скрыть какой узел подтвердил первым |
|
|||
|
|
|
|||
|
|
**Свойства:**
|
|||
|
|
|
|||
|
|
| Угроза | Защита |
|
|||
|
|
|--------|--------|
|
|||
|
|
| Пир видит IP отправителя | Stem: пир видит только предыдущий hop |
|
|||
|
|
| Глобальный наблюдатель (ISP) | TLS 1.3 + uniform framing (Transport Obfuscation) |
|
|||
|
|
| Анализ графа gossip | Операция входит в gossip из случайной точки |
|
|||
|
|
| Контроль k узлов | Деанонимизация требует контроля O(√n) узлов |
|
|||
|
|
|
|||
|
|
**Реализация:**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
stem_peers = random_sample(outbound, 2) // каждые 693 окна
|
|||
|
|
|
|||
|
|
on_receive_stem(msg, from_peer):
|
|||
|
|
if random() < 0.4:
|
|||
|
|
gossip_broadcast(msg) // fluff
|
|||
|
|
else:
|
|||
|
|
next = stem_peers[hash(msg) % 2] // детерминированный выбор из 2
|
|||
|
|
send_stem(msg, next) // продолжить stem
|
|||
|
|
start_timer(msg, 30s) // страховка на каждом hop
|
|||
|
|
|
|||
|
|
on_timer_expired(msg):
|
|||
|
|
if msg не обнаружен в gossip:
|
|||
|
|
gossip_broadcast(msg) // принудительный fluff
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Каждый stem-узел страхует следующий. Таймер 30 секунд на каждом hop независимо. Если следующий hop уронил сообщение — текущий hop обнаруживает отсутствие операции в gossip и делает fluff сам. Максимальная задержка = 30 секунд (один hop), не кумулятивная.
|
|||
|
|
|
|||
|
|
Dandelion++ не требует внешней инфраструктуры. Каждый Montana-узел уже является relay — gossip существует, stem добавляет 2-3 hop перед ним. Latency overhead: миллисекунды.
|
|||
|
|
|
|||
|
|
### NAT Traversal
|
|||
|
|
|
|||
|
|
Персональная сеть работает когда каждый может войти. Большинство домашних пользователей за NAT — невидимы для входящих соединений. Без NAT traversal персональный интернет = серверный клуб.
|
|||
|
|
|
|||
|
|
Три механизма, каждый следующий — если предыдущий не сработал:
|
|||
|
|
|
|||
|
|
**1. AutoNAT (определение).** Узел спрашивает outbound peers: «видишь ли мой IP:port напрямую?» Если да — NAT нет. Если нет — узел знает свой NAT-статус.
|
|||
|
|
|
|||
|
|
**2. DCUtR (пробивка).** Два NAT-узла координируются через третий узел с публичным IP. Оба отправляют исходящие пакеты — роутеры открывают «дырки» для ответов. После координации — прямое соединение. Успех: 60-70% случаев (TCP). Carrier-grade NAT (мобильные операторы): ~30%.
|
|||
|
|
|
|||
|
|
**3. Circuit Relay v2 (транзит).** Если пробивка не удалась — трафик идёт через outbound peer с публичным IP. Relay — не отдельный механизм и не выделенный сервер. Relay-соединение = обычное исходящее соединение, подчиняющееся тем же правилам: uniform framing, diversity constraints, ротация по поведению. Содержимое зашифровано конец-в-конец (Noise) — relay видит IP участников но не содержимое. Metadata распределён по 13 outbound peers из разных /16 и ASN — ни один relay не видит полный граф.
|
|||
|
|
|
|||
|
|
Relay — не fallback а гарантия подключения при любом типе NAT. Пробивка — оптимизация для снижения нагрузки на relay.
|
|||
|
|
|
|||
|
|
**Лимиты relay:** до 32 одновременных relay-соединений на узел, bandwidth per relay ≤ baseline frame rate (1 KB/сек). 32 × 1 KB/сек = 32 KB/сек ≈ 82 GB/мес — приемлемо для домашнего узла с публичным IP.
|
|||
|
|
|
|||
|
|
**Обязанность.** Узлы с публичным IP поддерживают relay — персональная сеть работает когда каждый может войти. Reference implementation включает relay при обнаружении публичного IP. Feeler-подключения проверяют поддержку relay у peers; узлы без relay помечаются `no-relay` в адресном менеджере. NAT-узлы предпочитают peers поддерживающие relay при выборе исходящих.
|
|||
|
|
|
|||
|
|
Все три механизма — стандарт libp2p (AutoNAT, DCUtR, Circuit Relay v2). Ноль новых протокольных примитивов.
|
|||
|
|
|
|||
|
|
### Пять слоёв — одна конструкция
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Слой 1: Transport Obfuscation персональный сервер скрывает содержимое и тайминг
|
|||
|
|
Слой 2: Peer Selection start_window + network diversity constraints
|
|||
|
|
Слой 3: NAT Traversal каждый может войти, даже за NAT
|
|||
|
|
Слой 4: Censorship-Resistant Discovery четыре канала, достаточно одного
|
|||
|
|
Слой 5: Dandelion++ пиры не знают кто автор операции
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Каждый слой закрывает свой вектор. Ни один не требует внешней инфраструктуры. Всё построено поверх libp2p и существующего gossip. Сетевой уровень ортогонален консенсусу — ни один state transition не затронут.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Эволюция протокола
|
|||
|
|
|
|||
|
|
Изменения правил протокола существуют вне consensus state. Эволюция: открытые предложения, независимые реализации, добровольный выбор операторов узлов, fork resolution через большинство chain_length.
|
|||
|
|
|
|||
|
|
### Принцип
|
|||
|
|
|
|||
|
|
Consensus state Montana содержит только то что необходимо для финансового слоя и хронометража: TimeChain, NodeChain, AccountChain, Account Table, Node Table. Никаких полей governance, никаких советов в state, никаких голосований в реестре операций. Любая попытка ввести on-chain governance вводит subjective компоненты в consensus state и создаёт постоянную атакуемую поверхность — это нарушение глобального инварианта I-3.
|
|||
|
|
|
|||
|
|
Эволюция протокола существует **вне** consensus state, как социальный и инженерный процесс над Content Layer и репозиториями реализаций.
|
|||
|
|
|
|||
|
|
### Жизненный цикл изменения
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. PROPOSAL
|
|||
|
|
Любой участник публикует MIP (Montana Improvement Proposal)
|
|||
|
|
как persistent blob в Content Layer:
|
|||
|
|
app_id = SHA-256("mt-app" || "mips")
|
|||
|
|
data_hash = H(текст MIP)
|
|||
|
|
anchor = операция Anchor в AccountChain автора
|
|||
|
|
|
|||
|
|
Авторство и timestamp доказуемы через подпись Anchor и
|
|||
|
|
timechain_value cemented окна. История эволюции навсегда
|
|||
|
|
в Content Layer + TimeChain.
|
|||
|
|
|
|||
|
|
2. DISCUSSION
|
|||
|
|
Открытое обсуждение в публичных каналах
|
|||
|
|
(форумы, репозитории, advisory councils — см. ниже).
|
|||
|
|
Никаких формальных голосований внутри протокола.
|
|||
|
|
|
|||
|
|
3. IMPLEMENTATION
|
|||
|
|
Реализации (Rust core и альтернативные клиенты) выпускают
|
|||
|
|
новые версии узлового ПО с реализованным изменением.
|
|||
|
|
Каждая версия закрепляется за конкретным protocol_version
|
|||
|
|
(u32 в Proposal header).
|
|||
|
|
|
|||
|
|
4. ADOPTION
|
|||
|
|
Операторы узлов самостоятельно выбирают какую версию
|
|||
|
|
запускать. Никакого on-chain голосования, никакого формального
|
|||
|
|
activation window. Узлы публикуют proposals со своим protocol_version.
|
|||
|
|
|
|||
|
|
5. FORK RESOLUTION
|
|||
|
|
При расхождении правил сеть может разделиться на цепочки.
|
|||
|
|
Каждый узел следует той цепочке которая длиннее по его
|
|||
|
|
собственным правилам валидации (chain_length majority).
|
|||
|
|
Меньшинство либо обновляется до правил большинства, либо
|
|||
|
|
продолжает работать как независимая цепочка (hard fork).
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Поле protocol_version
|
|||
|
|
|
|||
|
|
Поле `protocol_version` (u32) в Proposal header — единственный сигнал эволюции внутри консенсуса. Узел публикует proposals с тем `protocol_version` который реализован его версией ПО. Инвариант `protocol_version >= prev_proposal.protocol_version` запрещает откат к более старым правилам внутри одной цепочки.
|
|||
|
|
|
|||
|
|
`protocol_version` не голосуется и не активируется через governance. Он отражает фактическое состояние реализации узла — что узел реально умеет валидировать. Расхождение `protocol_version` между honest узлами разрешается естественно через fork choice по chain_length.
|
|||
|
|
|
|||
|
|
### Advisory councils
|
|||
|
|
|
|||
|
|
Группы экспертов могут существовать как **advisory** структуры — публикующие рекомендации, обзоры, анализ безопасности через Content Layer. Их подписи не имеют binding эффекта на consensus, их составы не хранятся в state, их голоса не считаются в state transitions.
|
|||
|
|
|
|||
|
|
Примеры advisory структур (опциональны, не часть протокола):
|
|||
|
|
|
|||
|
|
- **AI Council** — модели разных компаний публикуют технические обзоры MIPs
|
|||
|
|
- **Core Council** — публичные эксперты публикуют анализ безопасности и социальную координацию
|
|||
|
|
|
|||
|
|
Захват advisory совета не даёт контроля над протоколом — он даёт только возможность опубликовать рекомендацию, которую операторы узлов могут проигнорировать. Это устраняет attack surface governance: нет binding голосования = нет цели для компрометации.
|
|||
|
|
|
|||
|
|
Advisory councils организуются вне протокола (репозитории, форумы, каналы Content Layer). Протокол не знает об их существовании и не выделяет им никаких прав.
|
|||
|
|
|
|||
|
|
### Параметрическая адаптация
|
|||
|
|
|
|||
|
|
Параметры `D` и `m` адаптируются автоматически на границе τ₂ через participation-ratio feedback (см. раздел «Адаптация D через participation-ratio feedback»). Это **не** governance. Адаптация детерминирована, опирается только на canonical chain observations (cemented sets, Node Table), не требует голосования, не требует социальной координации, не зависит от измерений физического мира. Формула адаптации и её параметры зафиксированы в Genesis Decree; правка самой формулы требует MIP + новой версии ПО + adoption через chain_length, как и любое другое изменение протокола.
|
|||
|
|
|
|||
|
|
Закрытие окна определяется quorum event в канонических cemented sets. Механизм полностью event-driven и опирается только на canonical state.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Обоснование протокольных констант
|
|||
|
|
|
|||
|
|
Числовая система Montana построена на оси 13 (Змееносец — число вне 12-ричной системы) и последовательности 3-6-9 (числа вне бинарного цикла 1-2-4-8-7-5). Каждая константа имеет символическое и инженерное обоснование.
|
|||
|
|
|
|||
|
|
### Числовая ось 13
|
|||
|
|
|
|||
|
|
| Константа | Значение | Обоснование |
|
|||
|
|
|-----------|----------|-------------|
|
|||
|
|
| D₀ (TimeChain VDF за окно) | 13 × 10⁹ | 13 = Змееносец. Масштаб 10⁹ обеспечивает VDF >> network RTT на commodity CPU (~100 MH/s → окно физически достаточно для gossip propagation) |
|
|||
|
|
| m₀ (NodeChain VDF за окно) | 10⁹ (D₀ / 13) | Пропорция 1:13 от TimeChain. NodeChain доказывает присутствие в каждом окне, объём работы = 1/13 от TimeChain |
|
|||
|
|
| τ₂ (epoch boundary) | 13 000 окон | 13 × 10³. Одна эпоха = одна единица VDF entry. Все процессы длительного действия (pruning, adaptation, snapshot) привязаны к τ₂ |
|
|||
|
|
| VDF entry (стоимость входа) | 13 000 окон | = τ₂. Кандидат работает ровно одну эпоху до входа. Совпадение τ₂ и VDF entry — feature: единица адаптации = единица входа |
|
|||
|
|
| timecoin_per_window | 13 Ɉ | 13 на третьем масштабе: D = 13×10⁹, VDF entry = 13×10³, emission = 13×10⁰ |
|
|||
|
|
|
|||
|
|
### Последовательность 3-6-9
|
|||
|
|
|
|||
|
|
| Константа | Значение | Обоснование |
|
|||
|
|
|-----------|----------|-------------|
|
|||
|
|
| selection_interval | 369 окон | 3-6-9 как единое число. Цифровой корень = 9. ~35 selection events за τ₂ — плавный вход новых узлов |
|
|||
|
|
| stem_epoch (Dandelion++) | 693 окна | Цифровой корень = 9. ~1.9 selection intervals — ротация стебельных peers чаще, чем selection events, для diversity |
|
|||
|
|
| Ядра на узел | 3 | TimeChain + NodeChain + Account validation |
|
|||
|
|
|
|||
|
|
### Инженерные константы
|
|||
|
|
|
|||
|
|
| Константа | Значение | Обоснование |
|
|||
|
|
|-----------|----------|-------------|
|
|||
|
|
| confirmation_quorum | 67% | Стандартный BFT 2/3+1. 40 лет distributed systems research (PBFT, Tendermint, HotStuff) |
|
|||
|
|
| confirmation_threshold | active_chain_length / 130 | 130 = 13 × 10. Числовая ось. ~130 confirmers при large-scale сети |
|
|||
|
|
| outbound connections | 13 | Числовая ось. Больше diversity и relay capacity, чем при 8. Eclipse требует 7+ разных AS/subnet/τ₂ |
|
|||
|
|
| equivocation timeout | 13 окон | Числовая ось. Окно разрешения equivocation до отклонения обеих операций |
|
|||
|
|
| active predicate | 2τ₂ (26 000 окон) | Узел мог пропустить одну полную эпоху (downtime, перезагрузка) и вернуться. 1τ₂ — слишком агрессивно. 3τ₂ — мёртвые узлы висят слишком долго |
|
|||
|
|
| node pruning | 8τ₂ (104 000 окон) | 4× active threshold. Узел неактивен 8 эпох = гарантированно мёртв. Запас позволяет длительный offline без потери записи |
|
|||
|
|
| pruning_idle (accounts) | 4τ₂ (52 000 окон) | = граница первого бакета аккаунтов (4^1 × τ₂). Consistency с бакетной системой |
|
|||
|
|
| candidate_expiry | 3τ₂ (39 000 окон) | ~105 selection events. Достаточно для fair chance при конкуренции. 2τ₂ = 70 events (вероятность пропуска выше). 4τ₂ = раздувание Candidate Pool |
|
|||
|
|
| account бакеты | 4^N × τ₂ | Экспоненциальная шкала. Sybil-атакующий изолирован в бакете 0. Органический рост через 4× расширения на каждом уровне |
|
|||
|
|
| slots per selection | max(1, active/130) | ~0.77% от сети за event. 130 = 13 × 10. Consistency с confirmation threshold (active_chain_length / 130) |
|
|||
|
|
| D adjustment rate | ±3% за τ₂ | Медленная реакция: 24 эпохи для удвоения D. Быстрее — withholding атака дешевеет. Медленнее — инертность при hardware shift |
|
|||
|
|
| dead zone | 0.85 — 0.95 | Ниже 0.85 = 15%+ узлов под давлением. Выше 0.95 = комфорт. Между = нормальные флуктуации, D стабилен |
|
|||
|
|
| target₀ | calibrated at genesis | Калибруется на ~13 VDF_Reveal кандидатов за окно. При genesis (1 узел): target₀ устанавливается так, чтобы единственный узел гарантированно проходил threshold (weighted_ticket < target₀ для chain_length = 1). Уточняется при testnet |
|
|||
|
|
| chain_length_snapshot | скользящее окно 6τ₂ (78 000 окон) | Количество cemented BundledConfirmation за последние 6τ₂. Основа lottery_weight. Новый узел через 6τ₂ выходит на равный snapshot со старожилом. Цифровой корень 6 — Tesla |
|
|||
|
|
| seniority_bonus | min(chain_length / 69, snapshot) | Bounded добавка за longevity. Делитель 69 (цифровой корень 6, Tesla). Cap = snapshot: максимальное преимущество старожила ≈ 2x. Через ~5 лет bonus достигает cap. Далее — стабильный потолок |
|
|||
|
|
| lottery_weight | snapshot + seniority_bonus | Разделение: lottery_weight для эмиссии (недавняя работа + bounded longevity), абсолютный chain_length для quorum (безопасность). Temporal Aristocracy ограничена cap-ом. Новые участники мотивированы на любой стадии сети |
|
|||
|
|
| adaptive_vdf_threshold | 1% (pending/active) | Совпадает с selection rate (~0.77% за event). Ниже порога — стандартный VDF. Выше — давление аномальное, защита активируется |
|
|||
|
|
| adaptive_vdf_multiplier | ×100 | effective_vdf = 13000 × pressure × 100. При 100% давлении VDF в 100 раз длиннее. Self-correcting: стоимость атаки растёт пропорционально давлению |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Архитектура
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
ТЕЛЕФОН / ДЕСКТОП УЗЕЛ (десктоп / сервер, 24/7)
|
|||
|
|
┌────────────────────────┐ ┌──────────────────────────────────────┐
|
|||
|
|
│ Кошелёк │ │ │
|
|||
|
|
│ FN-DSA-512 keypair │ │ TimeChain │
|
|||
|
|
│ локальная UX-история │ │ T_r = SHA-256^D(T_{r-1}) │
|
|||
|
|
│ операций │ │ каноническая последовательность, │
|
|||
|
|
│ │ │ источник случайности │
|
|||
|
|
│ AccountChain │ │ │ │
|
|||
|
|
│ (счётчик окон │ │ ▼ │
|
|||
|
|
│ активности) │ │ NodeChain (per node) │
|
|||
|
|
│ │ │ S_{i,w} = SHA-256(S_{i,w-1} || T_w │
|
|||
|
|
└──────────┬─────────────┘ │ || node_id) │
|
|||
|
|
│ операции │ доказательство присутствия │
|
|||
|
|
│ (type|prev_hash| │ chain_length = окна с BundledConf. │
|
|||
|
|
│ payload|FN-DSA-512) │ │ │
|
|||
|
|
└──────────────────────▶│ ▼ │
|
|||
|
|
confirmations │ AccountTable │
|
|||
|
|
◀──────────────────-│ balance: u64 (открыт) │
|
|||
|
|
│ pubkey, frontier_hash │
|
|||
|
|
│ account_chain_length │
|
|||
|
|
│ │ │
|
|||
|
|
│ ▼ │
|
|||
|
|
│ Proposals (навсегда) │
|
|||
|
|
│ control_root, node_root, │
|
|||
|
|
│ account_root, timechain_value │
|
|||
|
|
└──────────────────────────────────────┘
|
|||
|
|
|
|||
|
|
Зависимости: TimeChain → NodeChain → AccountTable
|
|||
|
|
Отказ AccountTable не останавливает продвижение TimeChain.
|
|||
|
|
Отказ узла не заражает каноническую последовательность.
|
|||
|
|
```
|