1579 lines
104 KiB
Markdown
1579 lines
104 KiB
Markdown
# Montana — Спецификация протокола
|
||
|
||
**Версия:** 19.4.0 (2026-04-06 UTC)
|
||
|
||
## Определение
|
||
|
||
Montana — цифровой стандарт времени. Сеть независимых VDF-осцилляторов, поддерживающих единую верифицируемую временную шкалу с криптографическим доказательством каждого момента. Каждая зарегистрированная секунда = 1 TimeCoin (Ɉ).
|
||
|
||
NTP говорит «сейчас 14:32» — и ты доверяешь серверу. Montana говорит «сейчас 14:32, вот криптографическое доказательство, и вот что произошло в этот момент» — и ты проверяешь сам.
|
||
|
||
Основная функция — хронометраж. Вторичная — передача ценности.
|
||
|
||
Консенсус: **Proof of Time (PoT)** — три цепочки. TimeChain: глобальные часы (D последовательных SHA-256 = одно окно). NodeChain: персональная цепочка узла (доказательство присутствия при каждом тике). Account: состояние счёта. Влияние узла = длина его NodeChain. Протокол не использует время для консенсуса — протокол и есть ход времени, оцифрованный и криптографически верифицируемый.
|
||
|
||
Генезис: 09.01.2026 00:00:00 UTC.
|
||
|
||
Генезис-фраза: `«Кто контролирует прошлое, контролирует будущее. Кто контролирует настоящее, контролирует прошлое.» — Оруэлл, 1984`
|
||
|
||
---
|
||
|
||
## Три решённые проблемы
|
||
|
||
### 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 = количество секунд, прошедших с генезиса
|
||
- Годовая инфляция монотонно убывает и асимптотически стремится к нулю как следствие арифметики, не изменения правил
|
||
- Эмиссия не контролируется ни одним участником, комитетом или голосованием
|
||
- Денежная политика полностью определена единственной константой и не может быть изменена после генезиса
|
||
|
||
### Следствие: цифровой стандарт времени
|
||
|
||
Три решённые проблемы порождают уникальную возможность. Любой документ, событие, состояние может быть записано в Montana с математически доказуемой привязкой к моменту верифицируемого времени. Anchor — 32 байта, навсегда. Ни одна существующая система не предоставляет timestamp, который одновременно децентрализован, неплутократичен и привязан к детерминированной денежной политике. Montana — не блокчейн с функцией timestamping. 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** (selected NIST candidate, forthcoming FIPS 206) — подписи операций аккаунтов
|
||
- **Pedersen commitments** (Ristretto group) — скрытие балансов и сумм
|
||
- **Bulletproofs** (Bünz et al. 2018) — range proofs и balance proofs без trusted setup
|
||
|
||
SHA-256 обеспечивает квантовую устойчивость консенсуса: алгоритм Гровера сокращает безопасность с 256 до 128 бит. FN-DSA-512 обеспечивает математическую постквантовую устойчивость подписей на основе NTRU-решёток. Pedersen commitments и Bulletproofs обеспечивают приватность транзакций — балансы, суммы и получатели скрыты, корректность верифицируема без раскрытия значений.
|
||
|
||
### Подписи — 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, а текущий ключ хранится в состоянии аккаунта.
|
||
|
||
---
|
||
|
||
## Account Chain (Block Lattice)
|
||
|
||
Каждый аккаунт имеет собственную цепочку операций. Перевод — одна операция в цепочке отправителя. Зачисление получателю — детерминированно после финализации. Цепочки аккаунтов полностью независимы.
|
||
|
||
### Типы операций
|
||
|
||
**OpenAccount** — создание аккаунта (один раз):
|
||
|
||
```
|
||
type 1B
|
||
suite_id 2B
|
||
account_id 32B
|
||
pubkey 897B <- FN-DSA-512, публикуется единожды
|
||
signature 666B
|
||
Итого: ~1 598B
|
||
```
|
||
|
||
Аккаунт создаётся автоматически при первом входящем переводе. Отправитель указывает pubkey получателя в поле link, при финализации в Account Table создаётся запись с balance = link_amount. Приглашений не требуется. Как Bitcoin: адрес существует математически, в блокчейне появляется при первом зачислении.
|
||
|
||
**Transfer** — приватный перевод:
|
||
|
||
```
|
||
prev_hash 32B <- хэш предыдущей операции в цепочке аккаунта
|
||
sender_commitment 33B <- Pedersen commitment на account_id отправителя
|
||
link_commitment 33B <- Pedersen commitment на account_id получателя
|
||
amount_commitment 33B <- Pedersen commitment на сумму перевода
|
||
balance_commitment 33B <- Pedersen commitment на баланс после операции
|
||
ephemeral_pubkey 33B <- одноразовый ключ для stealth address получателя
|
||
encrypted_data 96B <- зашифровано для получателя: сумма, sender, blinding factors
|
||
flags 1B
|
||
signature 666B <- FN-DSA-512
|
||
range_proof 672B <- Bulletproof: amount >= 0, balance >= 0
|
||
balance_proof 64B <- proof: prev_commitment == balance + amount
|
||
Итого: ~1 696B
|
||
```
|
||
|
||
Скрыто: отправитель, получатель, сумма, баланс. Balance proof доказывает что prev_commitment == balance_commitment + amount_commitment без раскрытия скрытых значений.
|
||
|
||
**ChangeKey** — смена ключа или схемы подписи:
|
||
|
||
```
|
||
prev_hash 32B
|
||
account_id 32B
|
||
new_suite_id 2B
|
||
new_pubkey 897B <- новый публичный ключ
|
||
signature 666B <- подписано старым ключом
|
||
Итого: ~1 629B
|
||
```
|
||
|
||
**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 <- FN-DSA-512
|
||
Итого: ~796B
|
||
```
|
||
|
||
Anchor не перемещает средства и не требует комиссии. Единственная операция — запись data_hash в цепочку аккаунта с привязкой к timechain_value окна финализации.
|
||
|
||
### Верификация баланса
|
||
|
||
Балансы и суммы скрыты через Pedersen commitments. Верификация без знания значений:
|
||
|
||
```
|
||
balance_proof: prev_balance_commitment == new_balance_commitment + amount_commitment
|
||
range_proof: new_balance >= 0, amount > 0
|
||
```
|
||
|
||
Каждый узел проверяет Bulletproofs: commitments балансируются, все значения неотрицательны. Значения не раскрываются. Без trusted setup.
|
||
|
||
### Anti-inflation
|
||
|
||
Чеканка монет из воздуха невозможна. Два механизма:
|
||
|
||
**1. Commitment arithmetic.** Pedersen commitments гомоморфны: `C(a) + C(b) = C(a+b)`. Сумма всех balance_commitments в системе = commitment на общий supply. Каждый узел верифицирует: сумма всех commitments == commitment на supply(height). Если кто-то создал монеты — сумма не сходится.
|
||
|
||
**2. Supply audit.** `supply(height) = 60_000_000_000 × (height + 1) nɈ`. TimeCoin — единственный источник новых монет. TimeCoin публичен (в proposal header). Supply детерминирован по height. Расхождение = невалидный state.
|
||
|
||
### Перевод
|
||
|
||
Перевод на несуществующий account_id создаёт аккаунт получателя автоматически при финализации. Запись в Account Table: account_id, balance = link_amount, pubkey из OpenAccount (если есть) или из первого входящего перевода.
|
||
|
||
### TimeCoin (публичный слой)
|
||
|
||
Единственный публичный объект в системе переводов. Победитель τ₁ записывает прошедшее время: 60 Ɉ (60 зарегистрированных секунд).
|
||
|
||
TimeCoin **открыт**: сумма эмиссии, winner_node_id, height — публичные поля proposal header. Это чеканка новых монет — она должна быть верифицируема каждым узлом без криптографических proofs.
|
||
|
||
```
|
||
Публичное (верифицируемо всеми):
|
||
TimeCoin: 60 Ɉ за окно (константа)
|
||
Supply audit: supply(height) = 60_000_000_000 × (height + 1) nɈ
|
||
Winner: winner_node_id в proposal header
|
||
VDF: TimeChain values, NodeChain endpoints, подписи
|
||
|
||
Приватное (только участники):
|
||
Кто отправил перевод (sender_commitment)
|
||
Кому отправлен (link_commitment)
|
||
Сколько (amount_commitment)
|
||
Какой баланс (balance_commitment)
|
||
```
|
||
|
||
Supply audit: сумма всех TimeCoin от генезиса = supply(height). Чеканка открыта — нельзя напечатать из воздуха. Переводы закрыты — нельзя узнать кто кому сколько. Два слоя: публичная эмиссия + приватные опер<D0B5><D180>ции.
|
||
|
||
### Двойная трата
|
||
|
||
Каждый аккаунт имеет одну цепочку. Две операции с одним 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
|
||
balance_commitment 33B <- Pedersen commitment на баланс (значение скрыто)
|
||
frontier_hash 32B <- хэш последней операции в цепочке
|
||
op_height 4B <- количество операций в цепочке
|
||
suite_id 2B
|
||
current_pubkey 897B
|
||
phone_hash 32B <- SHA-256("mt-phone" || phone), 0x00 если не привязан
|
||
creation_window 4B <- окно создания аккаунта (первый входящий перевод)
|
||
last_op_window 4B <- окно последней операции (для приоритета)
|
||
|
||
Node Table (запись на узел):
|
||
node_id 32B <- SHA-256("mt-node" || node_pubkey), верифицируемо
|
||
node_pubkey 897B
|
||
suite_id 2B
|
||
start_window 4B <- окно регистрации (первое окно NodeChain)
|
||
pending_invite 32B <- node_id приглашённого узла (0x00..00 если нет)
|
||
invite_window 4B <- окно финализации NodeInvitation (0 если нет)
|
||
invite_expires 4B <- invite_window + 21 160 (0 если нет)
|
||
|
||
```
|
||
|
||
### State Root
|
||
|
||
Merkle-дерево глобального состояния. Два подкорня с разным ритмом обновления:
|
||
|
||
```
|
||
state_root = SHA-256("mt-merkle-node" || account_root || node_root)
|
||
|
||
node_root: Merkle root Node Table, обновляется каждый τ₁ (control_set + expiry)
|
||
account_root: Merkle root Account Table, обновляется на границе τ₂ (checkpoint)
|
||
|
||
Account Table Root: листья по account_id (лексикографически)
|
||
Node Table Root: листья по node_id (лексикографически)
|
||
|
||
Каждый узел в Node Table — участник сети. Узел существует в таблице = участвует.
|
||
```
|
||
|
||
Все sort keys фиксированной длины. Побайтовое лексикографическое сравнение. Две реализации с одинаковыми данными строят одинаковое дерево и получают одинаковый State Root.
|
||
|
||
State Root коммитится в заголовке каждого финализированного proposal τ₁. Между τ₂ — `node_root` пересчитывается каждый τ₁, `account_root` frozen (одинаков во всех proposals τ₂-периода).
|
||
|
||
#### τ₂ Account Root Checkpoint
|
||
|
||
На границе τ₂ (первый proposal нового τ₂-периода) `account_root` пересчитывается:
|
||
|
||
```
|
||
account_root_cutoff = τ₂_boundary_window - 100
|
||
account_root = MerkleRoot(Account Table по состоянию на account_root_cutoff)
|
||
```
|
||
|
||
Cutoff = 100 окон до boundary (~100 минут). За 100 минут любая операция гарантированно cemented у всех узлов. Все узлы вычисляют одинаковый account_root из одинакового набора cemented операций. Детерминирован.
|
||
|
||
#### Pruning
|
||
|
||
На τ₂ boundary вместе с пересчётом account_root:
|
||
|
||
```
|
||
Удалить все записи Account Table где:
|
||
balance_commitment == C(0) <- нулевой баланс (каноничное значение)
|
||
AND last_op_window + 4τ₂ <= current_window <- нет активности 4τ₂ (56 дней)
|
||
```
|
||
|
||
Пустой аккаунт без активности 56 дней — удаляется. Воссоздание: новый входящий Transfer → аккаунт создаётся заново. При нулевом балансе balance_commitment устанавливается в каноничное C(0) с r=0. Pruning детерминирован, автоматичен, каноничен.
|
||
|
||
---
|
||
|
||
## Двигатели
|
||
|
||
Три цепочки с односторонним потоком зависимостей: TimeChain → NodeChain → Account.
|
||
|
||
### 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.
|
||
|
||
### VDF Reveal и лотерея
|
||
|
||
После закрытия окна τ₁ каждый узел вычисляет свой ticket:
|
||
|
||
```
|
||
ticket_i = -ln(endpoint_i / 2^256)
|
||
weighted_ticket_i = ticket_i / chain_length_i
|
||
```
|
||
|
||
Вес в лотерее пропорционален chain_length. Узел с большим chain_length имеет пропорционально больший шанс победы. Если ticket < target — узел является кандидатом и публикует reveal. Из кандидатов побеждает lowest weighted_ticket.
|
||
|
||
```
|
||
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
|
||
```
|
||
|
||
Target калиброван на ~12 кандидатов за окно. Из кандидатов побеждает lowest weighted_ticket. Калибровка target на τ₂:
|
||
|
||
```
|
||
target_new = target_old × (12 / actual_candidates_per_window)
|
||
actual_candidates_per_window = total_candidates_за_τ₂ / 20 160
|
||
```
|
||
|
||
Reveal публикуется только кандидатами. Трафик за окно: ~12 reveals × 738B ≈ 8.9 KB. При любом количестве узлов.
|
||
|
||
Валидация reveal при получении:
|
||
|
||
1. Подпись FN-DSA-512 соответствует node_pubkey из Node Table
|
||
2. window_index = только что закрытый τ₁
|
||
3. node_id существует в Node Table
|
||
4. ticket < target
|
||
5. endpoint верифицируем: пересчёт NodeChain VDF от предыдущего endpoint
|
||
|
||
### Account — содержимое блока
|
||
|
||
Приём, верификация объектов и формирование набора. Два класса объектов:
|
||
|
||
**UserObjects** — пользовательские операции:
|
||
|
||
| Тип | Описание | Валидация |
|
||
|-----|----------|-----------|
|
||
| Transfer | Приватный перевод | FN-DSA-512 подпись, Bulletproofs валидны, prev_hash. Если получатель не существует — создаётся автоматически |
|
||
| OpenAccount | Публикация ключа | FN-DSA-512 подпись, account_id существует в 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 |
|
||
|
||
**ControlObjects** — объекты управляющие составом сети:
|
||
|
||
| Тип | Описание | Валидация |
|
||
|-----|----------|-----------|
|
||
| NodeInvitation | Приглашение нового узла | FN-DSA-512 подпись пригласившего, pending_invite = 0 |
|
||
| NodeRegistration | Регистрация узла | FN-DSA-512 подпись, node_id уникален, proof_endpoint верифицируем через VDF, приглашение существует |
|
||
|
||
Каждый узел валидирует объекты обоих классов локально при получении. Валидные объекты ретранслируются по P2P.
|
||
|
||
UserObjects финализируются непрерывно через подтверждения (67% online chain_length). ControlObjects включаются в proposal победителем τ₁ (каноничен).
|
||
|
||
#### Proposal
|
||
|
||
Proposal содержит только **control_set** и метаданные окна. UserObjects финализируются непрерывно через подтверждения, не через proposal.
|
||
|
||
**control_set** = все валидные ControlObjects полученные по P2P до control_cutoff, не финализированные ранее и не истёкшие. Каноничен — все ControlObjects включены ровно один раз. Пропуск или добавление лишнего ControlObject = невалидный proposal = fallback.
|
||
|
||
Порядок внутри control_set: H(object) лексикографически (каноничен, не зависит от победителя).
|
||
|
||
Форки аккаунтов (две операции с одним prev_hash) разрешаются голосованием узлов весом chain_length. 67% online chain_length за одну операцию → побеждает (см. раздел «Двойная трата»).
|
||
|
||
#### Дедлайн в окне
|
||
|
||
```
|
||
|-------- τ₁ (60 сек) --------|--- R (12 сек) ---|
|
||
^
|
||
reveal_cutoff = control_cutoff
|
||
(R сек после закрытия)
|
||
```
|
||
|
||
- **reveal_cutoff** = control_cutoff = R секунд после закрытия окна. VDF_Reveal и ControlObjects принимаются до этого момента.
|
||
|
||
R, F — калибруются в τ₂ (см. раздел «Калибровка R, F»). Генезис: R₀ = 12s, F₀ = 12s.
|
||
|
||
После reveal_cutoff: определяется победитель лотереи, победитель собирает proposal.
|
||
|
||
#### Proposer
|
||
|
||
Победитель собирает proposal:
|
||
- **control_set**: все ControlObjects до control_cutoff (каноничен, свобода = ноль)
|
||
- **State Root snapshot**: состояние после применения всех финализированных операций за окно
|
||
|
||
Свобода proposer: ноль. Control_set каноничен. State Root детерминирован — каждый узел применяет одни и те же финализированные операции и получает один и тот же результат.
|
||
|
||
Proposal с невалидным ControlObject, пропущенным ControlObject или неверным state_root отклоняется, переход ко второму месту.
|
||
|
||
#### Финальность proposal
|
||
|
||
Финальность proposal = подпись победителя на proposal header + независимая верифицируемость.
|
||
|
||
1. Победитель публикует подписанный proposal header + control_set
|
||
2. Каждый узел проверяет ControlObjects по правилам валидации
|
||
3. Каждый узел применяет control_set + TimeCoin детерминированно
|
||
4. Каждый узел сравнивает вычисленный state_root с заявленным в proposal
|
||
5. Совпадает — proposal принят
|
||
6. Не совпадает — 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 <- Merkle root Account Table (frozen между τ₂, обновляется на τ₂ boundary)
|
||
new_state_root 32B <- SHA-256(node_root || account_root)
|
||
timechain_value 32B
|
||
winner_endpoint 32B <- NodeChain endpoint победителя
|
||
timecoin_hash 32B
|
||
winner_node_id 32B
|
||
target 8B <- текущий target лотереи
|
||
wall_clock 8B <- секунды с генезиса (локальное измерение победителя)
|
||
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 (финализация операций)
|
||
|
||
Confirmers — узлы с chain_length >= confirmation_threshold. Подтверждают операции от имени сети.
|
||
|
||
```
|
||
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
|
||
signature 666B
|
||
```
|
||
|
||
Один BundledConfirmation per (node_id, window_index). Повторный отклоняется. Endpoint верифицируем: пересчёт m хэшей от предыдущего известного endpoint данного узла. chain_length = количество реально вычисленных VDF-звеньев, доказанное endpoint.
|
||
|
||
Операция финализирована когда подтверждения от confirmers с суммарным chain_length > quorum. Cemented — необратимо. Типичное время: ~0.3 секунды.
|
||
|
||
```
|
||
quorum = max(67% × online_chain_length, 50% × total_chain_length)
|
||
|
||
online_chain_length = сумма chain_length confirmers, опубликовавших
|
||
BundledConfirmation за последние 10 окон
|
||
total_chain_length = Σ(current_window - start_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
|
||
|
||
Два параллельных процесса обновления состояния:
|
||
|
||
**Непрерывная финализация операций:** при достижении 67% online chain_length операция применяется к Account Table немедленно:
|
||
|
||
```
|
||
Transfer: обновить balance_commitment отправителя и получателя.
|
||
Если получатель не существует — создать запись в Account Table.
|
||
OpenAccount: записать pubkey в Account Table.
|
||
ChangeKey: обновить pubkey и suite_id в Account Table.
|
||
Anchor: записать data_hash в цепочку аккаунта.
|
||
PhoneLink: записать phone_hash в Account Table.
|
||
```
|
||
|
||
**State transition в proposal:** при финализации proposal применяется атомарно:
|
||
|
||
```
|
||
apply_proposal(state, proposal) -> state':
|
||
|
||
Шаг 1: применить control_set в порядке H(object) лексикографически.
|
||
NodeInvitation: записать pending_invite, invite_window и invite_expires в Node Table пригласившего.
|
||
NodeRegistration: проверить приглашение, создать запись в Node Table. Очистить pending_invite пригласившего.
|
||
|
||
Шаг 2: применить TimeCoin победителя.
|
||
|
||
Шаг 3: обработать expiry.
|
||
Приглашения узлов: все записи Node Table где invite_expires <= current_window
|
||
и invite_expires > 0 -> очистить pending_invite, invite_window и invite_expires.
|
||
|
||
Шаг 4: вычислить node_root (после control_set + TimeCoin + expiry).
|
||
account_root = frozen (из предыдущего τ₂ checkpoint, или пересчитан если τ₂ boundary).
|
||
new_state_root = SHA-256(node_root || account_root).
|
||
```
|
||
|
||
Порядок детерминирован. Каждый узел применяет одни и те же шаги и получает один и тот же new_state_root.
|
||
|
||
Account зависит от TimeChain и NodeChain. Обратных зависимостей нет.
|
||
|
||
С ростом TPS сети дополнительные ядра подключаются для верификации операций. Минимум для валидатора: 3 логических ядра (TimeChain + NodeChain + Account). Один узел = 3 ядра. 50 ядер = 16 узлов. Верификация операций аккаунтов полностью параллелизуется — цепочки аккаунтов независимы.
|
||
|
||
### Приглашение и регистрация
|
||
|
||
Два уровня входа в сеть. Узлы участвуют в консенсусе — приглашение + 14 дней VDF. Аккаунты держат и переводят средства — создаются автоматически при первом входящем переводе.
|
||
|
||
Генезис: 12 узлов в разных локациях (hardcoded, аналог bootstrap nodes в Bitcoin).
|
||
|
||
#### Приглашение узла (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:
|
||
inviter_node_id 32B
|
||
invited_pubkey 897B <- публичный ключ приглашённого узла
|
||
signature 666B <- подписано inviter node_pubkey
|
||
Итого: ~1 595B
|
||
```
|
||
|
||
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
|
||
|
||
При финализации в proposal P окна W:
|
||
- inviter pending_invite = invited node_id
|
||
- inviter invite_window = W
|
||
- inviter invite_expires = W + 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
|
||
suite_id 2B
|
||
node_pubkey 897B <- FN-DSA-512 ключ узла
|
||
inviter_node_id 32B <- кто пригласил
|
||
proof_endpoint 32B <- S_{i,20159,m} (endpoint после 20 160 окон VDF)
|
||
signature 666B <- подписано node_pubkey
|
||
Итого: ~1 630B
|
||
```
|
||
|
||
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. Восстановить control_root и timechain_value из proposal окна invite_window
|
||
6. Вычислить nodechain_init = SHA-256("mt-nodechain-init" || control_root || timechain_value || node_id) из proposal окна invite_window
|
||
7. proof_endpoint верифицируем: пересчёт VDF от nodechain_init через 20 160 окон с якорением в TimeChain значения от invite_window+1
|
||
|
||
Верификация: 20 160 сегментов VDF проверяются параллельно. На C ядрах: ~(20 160/C) × t_segment.
|
||
|
||
При финализации: создать запись в Node Table (start_window = текущее окно). Очистить 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) → получает адрес. Аккаунт появляется в Account Table при первом входящем переводе. Как Bitcoin: адрес существует математически, в блокчейне — при первом зачислении.
|
||
|
||
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: все валидные ControlObjects до control_cutoff (каноничен)
|
||
- Кандидаты (~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 (см. ниже)
|
||
- Account Root checkpoint: пересчёт account_root в первом proposal нового τ₂. Cutoff = boundary - 100 окон. Детерминирован
|
||
- Pruning: удаление пустых аккаунтов без активности 4τ₂ (56 дней)
|
||
- Supply audit: сумма всех balance_commitments == commitment на supply(height)
|
||
- Криптографическая амнезия: подписанные proposals сохраняются навсегда — верифицируемая цепочка state commitments. Proposals доказывают что конкретное состояние было закоммичено победителем; восстановление содержимого состояния требует snapshot или архива
|
||
- Пересчёт параметров размера окна τ₁
|
||
|
||
#### Калибровка D и m
|
||
|
||
Каждый proposal содержит `wall_clock` — секунды с генезиса по локальным часам победителя. Одно измерение субъективно. Медиана 20 160 измерений за τ₂ — распределённый эталон.
|
||
|
||
Валидация wall_clock при получении proposal:
|
||
|
||
```
|
||
wall_clock > prev_proposal.wall_clock (монотонность)
|
||
wall_clock < prev_proposal.wall_clock + 2 × 60 (не более 2× target вперёд)
|
||
```
|
||
|
||
Формула пересчёта 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 → Account. Отказ в Account не останавливает часы. Отказ конкретного узла в NodeChain не заражает общий ритм.
|
||
|
||
### Лотерея
|
||
|
||
Шанс победы пропорционален chain_length. После закрытия окна τ₁ каждый узел вычисляет ticket из своего NodeChain endpoint:
|
||
|
||
```
|
||
ticket_i = -ln(endpoint_i / 2^256)
|
||
weighted_ticket_i = ticket_i / chain_length_i
|
||
```
|
||
|
||
Если ticket < target — узел является кандидатом. Target калиброван на ~12 кандидатов за окно. Из кандидатов побеждает lowest weighted_ticket.
|
||
|
||
Стимул работать непрерывно: каждое окно онлайн увеличивает chain_length → увеличивает шанс победы → увеличивает доход. Каждое окно офлайн — потерянный chain_length навсегда.
|
||
|
||
### Победитель τ₁
|
||
|
||
Победитель определяется после закрытия окна τ₁. Кандидаты (~12 из всей сети) публикуют reveal. Lowest weighted_ticket = победитель.
|
||
|
||
Победитель:
|
||
- Записывает TimeChain value
|
||
- Получает 60 Ɉ TimeCoin (единственный доход)
|
||
- Коммитит State Root (snapshot финализированных операций за окно)
|
||
- Включает ControlObjects в proposal (каноничен)
|
||
|
||
Победитель публикует подписанный proposal: control_set + State Root snapshot + TimeCoin. Валидация: control_set полон, все объекты валидны, state_root корректен. Финальность proposal — подпись победителя на 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. Боб: кошелёк генерирует keypair -> account_id (постоянный адрес, существует математически)
|
||
2. Боб -> Алисе: "отправь на mt4ZGfe..." (account_id)
|
||
3. Алиса формирует Transfer в своей цепочке:
|
||
prev_hash: хэш её предыдущей операции
|
||
link: account_id Боба
|
||
link_amount: 50 Ɉ
|
||
balance: 50 Ɉ (100 - 50)
|
||
4. Алиса подписывает FN-DSA-512
|
||
5. Алиса рассылает операцию узлам сети
|
||
6. Каждый узел проверяет:
|
||
FN-DSA-512 подпись валидна для pubkey Алисы
|
||
prev_hash совпадает с frontier Алисы
|
||
balance >= 0
|
||
link_amount > 0
|
||
7. Узлы публикуют confirmations, операция распространяется через P2P gossip
|
||
8. 67% online chain_length подтвердили → финализирована (~0.3 секунды)
|
||
Баланс Алисы: 50 Ɉ
|
||
Если Боб новый — создать запись в Account Table (balance = 50 Ɉ)
|
||
Если Боб существует — баланс увеличен на 50 Ɉ (детерминированно)
|
||
Кошелёк Боба: «финализирован»
|
||
```
|
||
|
||
### Баланс
|
||
|
||
Баланс аккаунта — одно число в таблице аккаунтов. Обновляется при финализации операции: исходящие переводы (из Transfer отправителя) и входящие зачисления (детерминированно по финализированным операциям).
|
||
|
||
Бэкап = seed (для деривации приватного ключа FN-DSA-512).
|
||
|
||
---
|
||
|
||
## Эмиссия
|
||
|
||
### Единица
|
||
|
||
Монета: **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 в своей цепочке. Одно правило. Неизменно с генезиса.
|
||
|
||
Базовый бюджет: 60 Ɉ/τ₁ (запись 60 секунд). Реальный бюджет безопасности в покупательной способности зависит от рынка.
|
||
|
||
1 TimeCoin = 1 секунда описывает скорость хода часов. Не ценовой peg, не гарантия покупательной способности.
|
||
|
||
---
|
||
|
||
## Пропускная способность
|
||
|
||
Размер Transfer: ~1 696B (приватный перевод с Bulletproofs).
|
||
|
||
| Канал узла | TPS |
|
||
|-----------|-----|
|
||
| 10 Mbps | ~1 620 |
|
||
| 100 Mbps | ~16 200 |
|
||
| 1 Gbps | ~162 000 |
|
||
|
||
### Адаптивный размер окна
|
||
|
||
Пересчёт в τ₂:
|
||
|
||
- Заполненность > 80% → увеличение размера окна
|
||
- Заполненность < 20% → уменьшение размера окна
|
||
- Шаг: ±20% за τ₂
|
||
- Диапазон: 1 MB — 100 MB
|
||
|
||
---
|
||
|
||
## Хранение
|
||
|
||
### Модель: глобальное состояние + локальная история
|
||
|
||
Узлы хранят глобальное состояние (Account Table, Node Table, proposals). Тела операций аккаунтов хранятся у владельцев. После финализации state transition применён — commitment в таблице обновлён, тело операции сети больше не нужно.
|
||
|
||
### Три уровня участников
|
||
|
||
**Узел (валидатор)** — десктоп или сервер, 24/7, минимум 3 ядра (1 узел = 3 ядра, 50 ядер = 16 узлов):
|
||
|
||
```
|
||
Хранит:
|
||
Account Table (commitments, frontier_hash, pubkey, phone_hash)
|
||
Node Table (node_id, pubkey, start_window, invites)
|
||
Proposals (навсегда)
|
||
Blob Buffer (зашифрованные сообщения для владельца, TTL = τ₂)
|
||
|
||
Делает:
|
||
TimeChain VDF (1 ядро, 24/7)
|
||
NodeChain VDF (1 ядро, 24/7)
|
||
Валидация операций (1+ ядро)
|
||
P2P gossip (операции, confirmations, reveals, proposals)
|
||
Почтовый ящик (хранит сообщения для своего владельца пока тот офлайн)
|
||
```
|
||
|
||
**Кошелёк (клиент)** — телефон, онлайн когда используется:
|
||
|
||
```
|
||
Хранит:
|
||
Своя цепочка операций (история переводов)
|
||
Свои ключи (seed → keypairs)
|
||
Свои контакты (адресная книга: имя → mt-адрес)
|
||
Blinding factors (для расшифровки commitments)
|
||
Сообщения (локальная история переписки)
|
||
|
||
Делает:
|
||
Отправка/получение переводов
|
||
Мессенджер (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 |
|
||
|
||
### Потеря данных клиента
|
||
|
||
Потеря устройства: balance_commitment в Account Table цел, seed восстанавливает ключи, баланс доступен. История переводов и сообщений утрачена — как потерять выписку, не деньги. Если есть доверенный узел — зашифрованные сообщения можно восстановить.
|
||
|
||
### Fast Sync (новый узел)
|
||
|
||
1. Цепочка proposals от генезиса — проверка TimeChain-цепочки и подписей победителей (мегабайты)
|
||
2. State Root из последнего τ₂ boundary proposal (account_root + node_root)
|
||
3. Global State snapshot от пиров: каноническая сериализация всех листьев Merkle-дерева состояния (Account Table + Node Table). Верификация: пересчёт Merkle root из полученных листьев, сравнение с account_root и node_root из proposal
|
||
4. Catch-up cemented операций после τ₂ checkpoint:
|
||
- Запросить cemented операции от пиров
|
||
- Для каждой: проверить подпись FN-DSA-512 + Bulletproofs + prev_hash
|
||
- Запросить confirmations, проверить quorum (chain_length > quorum)
|
||
- Применить к локальному Account Table
|
||
5. Узел синхронизирован и готов к участию
|
||
|
||
Proposals доказывают цепочку state commitments. Checkpoint восстанавливает каноничное состояние. Catch-up добирает cemented операции после checkpoint через самоверифицируемые операции + confirmations. Тела операций не нужны сети — состояние самодостаточно.
|
||
|
||
---
|
||
|
||
## Application Layer
|
||
|
||
Montana — цифровой стандарт времени. Приложения управляют своим состоянием самостоятельно (серверы, базы данных, P2P). Montana хранит только криптографические отпечатки с привязкой ко времени — 32 байта на запись.
|
||
|
||
### 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.
|
||
|
||
```
|
||
Доказательство:
|
||
1. H(D) <- хэш документа
|
||
2. Anchor с data_hash <- содержит MerkleRoot включающий H(D)
|
||
3. Merkle proof: H(D) → data_hash <- H(D) является листом дерева
|
||
4. Proposal header окна <- содержит Anchor, timechain_value = T
|
||
5. VDF-цепочка от генезиса <- доказывает T
|
||
|
||
Верификация:
|
||
1. Пересчитать Merkle proof: H(D) → data_hash
|
||
2. Убедиться что Anchor включён в proposal (user_root)
|
||
3. Убедиться что proposal содержит валидный timechain_value
|
||
4. Любой участник с доступом к цепочке proposals верифицирует
|
||
```
|
||
|
||
Proposals хранятся навсегда. 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:
|
||
prev_hash 32B
|
||
account_id 32B
|
||
phone_hash 32B <- SHA-256("mt-phone" || phone_number)
|
||
signature 666B
|
||
Итого: ~762B
|
||
```
|
||
|
||
Алиса открывает адресную книгу → вычисляет 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 включён в proposal окна W с timechain_value T_W
|
||
```
|
||
|
||
data_hash — произвольный хэш: Merkle root документов, хэш batch'а Rollup, fingerprint состояния. Montana не интерпретирует содержимое — хранит 32 байта с привязкой ко времени.
|
||
|
||
#### Read — чтение
|
||
|
||
Внешняя система запрашивает доказательство по height или data_hash.
|
||
|
||
```
|
||
Вход: proposal height или data_hash + app_id
|
||
Выход: proposal header + Merkle path от data_hash до user_root
|
||
```
|
||
|
||
Proposal header содержит timechain_value = доказуемый момент. Merkle path доказывает что data_hash включён в этот proposal.
|
||
|
||
#### Verify — верификация
|
||
|
||
Внешняя система проверяет proof автономно, без доверия к Montana-узлу.
|
||
|
||
```
|
||
1. Пересчитать Merkle path: data_hash → user_root
|
||
2. Сравнить user_root с полем в proposal header
|
||
3. Проверить подпись победителя на proposal header
|
||
4. Проверить timechain_value: пересчёт D хэшей от T_{W-1} до T_W
|
||
5. Проверить цепочку proposals: prev_proposal_hash связывает окна до генезиса
|
||
```
|
||
|
||
Шаги 1–3: миллисекунды. Шаг 4: ~60 секунд на одном ядре (один сегмент VDF). Шаг 5: проверка подписей и хэшей — линейна по количеству proposals, параллелизуема.
|
||
|
||
Полная верификация от генезиса: H сегментов 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" || suite_id || account_pubkey)
|
||
├── Узел: FN-DSA-512 keypair (HMAC-SHA256(seed || "mt-node-key"))
|
||
│ → node_id = SHA-256("mt-node" || node_pubkey)
|
||
└── Blinding: blinding_seed = HMAC-SHA256(seed || "mt-blinding")
|
||
r(op) = HMAC-SHA256(blinding_seed || op_height) mod ℓ
|
||
```
|
||
|
||
Один seed порождает два FN-DSA-512 keypair и blinding_seed. Аккаунт — подпись операций. Узел — подпись proposals и NodeChain endpoints. Blinding — детерминированные blinding factors для Pedersen commitments.
|
||
|
||
account_id и node_id выводятся из публичных ключей, верифицируемы без знания seed. blinding_seed — локальный секрет, не публикуется.
|
||
|
||
Blinding factor для каждой операции: `r(op) = HMAC-SHA256(blinding_seed || op_height) mod ℓ`, где ℓ — порядок Ristretto group. op_height — порядковый номер операции в цепочке аккаунта (каноничен, хранится в Account Table). HMAC-SHA256 — PRF, выход неотличим от случайного без знания blinding_seed. Reduction mod ℓ обеспечивает попадание в допустимый диапазон скаляров группы. Hiding property Pedersen commitment сохранена.
|
||
|
||
Следствие: любое устройство с seed пересчитывает все blinding factors для всех операций. Синхронизация между устройствами не требуется. Бэкап = 24 слова.
|
||
|
||
---
|
||
|
||
## Криптографическая реализация
|
||
|
||
### Primitive layer
|
||
|
||
Собственная реализация криптографических примитивов запрещена. Только audited библиотеки с constant-time гарантиями и опубликованными test vectors.
|
||
|
||
| Примитив | Стандарт | Роль |
|
||
|----------|----------|------|
|
||
| SHA-256 | FIPS 180-4 | TimeChain, NodeChain, адреса, Merkle-деревья |
|
||
| FN-DSA-512 | Selected NIST candidate, forthcoming FIPS 206 | Подписи блоков аккаунтов и proposals |
|
||
| Pedersen commitments | Ristretto group | Скрытие балансов и сумм |
|
||
| Bulletproofs | Bünz et al. 2018 | Range proofs, balance proofs. Без trusted setup |
|
||
|
||
### 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-equivocation` | Хэширование equivocation proofs |
|
||
| `mt-confirmation` | Хэширование async confirmations |
|
||
| `mt-app` | Деривация app_id для Application Layer |
|
||
| `mt-node` | Деривация node_id |
|
||
| `mt-phone` | Деривация phone_hash для Phone Discovery |
|
||
| `mt-blinding` | Деривация blinding_seed для Pedersen commitments |
|
||
| `mt-seed` | Salt для PBKDF2 деривации seed из мнемоники |
|
||
| `mt-account-key` | Деривация keypair аккаунта из seed |
|
||
| `mt-node-key` | Деривация keypair узла из seed |
|
||
|
||
- Альтернативные сериализации запрещены
|
||
- 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 не затронут.
|
||
|
||
---
|
||
|
||
## Архитектура
|
||
|
||
```
|
||
┌─────────────────────────────────┐
|
||
│ Wallet │
|
||
│ Кошелёк, баланс, переводы │
|
||
│ FN-DSA-512 keypair │
|
||
└──────────────┬──────────────────┘
|
||
│
|
||
┌──────────────┴──────────────────┐
|
||
│ Montana │
|
||
│ Децентрализованные часы │
|
||
│ │
|
||
│ TimeChain ──→ NodeChain ──→ Account
|
||
│ (часы) (присутствие) (состояние)
|
||
│ │
|
||
│ Account Chain (Block Lattice) │
|
||
│ Account Table, Proposals │
|
||
│ SHA-256, FN-DSA-512 │
|
||
└─────────────────────────────────┘
|
||
```
|