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