1409 lines
96 KiB
Markdown
1409 lines
96 KiB
Markdown
|
|
# Montana — Спецификация протокола
|
|||
|
|
|
|||
|
|
**Версия:** 16.1.0 (2026-04-03)
|
|||
|
|
|
|||
|
|
## Определение
|
|||
|
|
|
|||
|
|
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 MSK.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Три решённые проблемы
|
|||
|
|
|
|||
|
|
### 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 с математически доказуемой привязкой к моменту верифицируемого времени. AnchorBlock — 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 MSK (Unix: 1736373600). 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)
|
|||
|
|
|
|||
|
|
Каждый аккаунт имеет собственную цепочку блоков. Перевод — один блок в цепочке отправителя. Зачисление получателю — детерминированно после финализации proposal. Цепочки аккаунтов полностью независимы.
|
|||
|
|
|
|||
|
|
### Типы блоков
|
|||
|
|
|
|||
|
|
**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: адрес существует математически, в блокчейне появляется при первом зачислении.
|
|||
|
|
|
|||
|
|
**StateBlock** — приватный перевод:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
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 на баланс после операции
|
|||
|
|
fee 8B <- комиссия (открытая, аудируемая)
|
|||
|
|
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 + fee
|
|||
|
|
Итого: ~1 704B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Скрыто: отправитель, получатель, сумма, баланс. Открыто: комиссия. Комиссия — часть публичного слоя, аудируемая как эмиссия. Победитель получает сумму открытых комиссий — верифицируемо каждым узлом. Balance proof доказывает что prev_commitment == balance_commitment + amount_commitment + fee (открытая сумма) без раскрытия скрытых значений.
|
|||
|
|
|
|||
|
|
**ChangeKey** — смена ключа или схемы подписи:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
prev_hash 32B
|
|||
|
|
account_id 32B
|
|||
|
|
new_suite_id 2B
|
|||
|
|
new_pubkey 897B <- новый публичный ключ
|
|||
|
|
signature 666B <- подписано старым ключом
|
|||
|
|
Итого: ~1 629B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**AnchorBlock** — криптографический якорь (привязка данных ко времени):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
prev_hash 32B <- хэш предыдущего блока в цепочке аккаунта
|
|||
|
|
account_id 32B
|
|||
|
|
app_id 32B <- SHA-256("mt-app" || app_name), пространство имён приложения
|
|||
|
|
data_hash 32B <- хэш произвольных данных (Merkle root, H(document), ...)
|
|||
|
|
balance_commitment 33B <- Pedersen commitment на баланс после комиссии
|
|||
|
|
signature 666B <- FN-DSA-512
|
|||
|
|
range_proof 336B <- Bulletproof: balance >= 0, fee >= min_fee
|
|||
|
|
Итого: ~1 163B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
AnchorBlock не перемещает средства. Единственная операция — запись data_hash в цепочку аккаунта с привязкой к timechain_value окна финализации. Комиссия верифицируется через balance_proof: разница между предыдущим и новым balance_commitment >= min_fee.
|
|||
|
|
|
|||
|
|
### Верификация баланса
|
|||
|
|
|
|||
|
|
Балансы и суммы скрыты через Pedersen commitments. Верификация без знания значений:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
balance_proof: prev_balance_commitment == new_balance_commitment + amount_commitment + fee_commitment
|
|||
|
|
range_proof: new_balance >= 0, amount > 0, fee >= min_fee
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Каждый узел проверяет Bulletproofs: commitments балансируются, все значения неотрицательны, комиссия не ниже min_fee. Значения не раскрываются. Без 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.
|
|||
|
|
|
|||
|
|
### Комиссия
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
base_fee_nɈ(W) = base_fee_nɈ(W-1) + base_fee_nɈ(W-1) × (utilization_bps - 5000) / 40000
|
|||
|
|
utilization_bps = user_set_size × 10000 / max_block_size
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Адаптивная формула на основе заполненности окна. Целевая заполненность: 50%. Входные данные каноничны — из proposals. Детерминирована, одинакова у всех узлов.
|
|||
|
|
|
|||
|
|
Генезис: base_fee(0) = 1 000 nɈ (1 μɈ). Пол: 1 nɈ. Потолок: 1 000 000 000 nɈ (1 Ɉ).
|
|||
|
|
|
|||
|
|
При пустой сети: base_fee падает до пола (1 nɈ). При нагрузке > 50%: base_fee растёт экспоненциально (~удвоение каждые 20 окон при 100% заполненности). Спам становится экспоненциально дороже с каждым окном.
|
|||
|
|
|
|||
|
|
Победитель окна τ₁ получает сумму всех комиссий блоков в окне. Сумма комиссий верифицируема через commitment arithmetic.
|
|||
|
|
|
|||
|
|
### Перевод
|
|||
|
|
|
|||
|
|
Перевод на несуществующий account_id создаёт аккаунт получателя автоматически при финализации. Запись в Account Table: account_id, balance = link_amount, pubkey из OpenAccount (если есть) или из первого входящего перевода.
|
|||
|
|
|
|||
|
|
### TimeCoin (публичный слой)
|
|||
|
|
|
|||
|
|
Единственный публичный объект в системе переводов. Победитель τ₁ записывает прошедшее время: 60 Ɉ (60 зарегистрированных секунд) + сумма комиссий всех блоков окна.
|
|||
|
|
|
|||
|
|
TimeCoin **открыт**: сумма эмиссии, winner_node_id, height — публичные поля proposal header. Это чеканка новых монет — она должна быть верифицируема каждым узлом без криптографических proofs.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Публичное (верифицируемо всеми):
|
|||
|
|
TimeCoin: 60 Ɉ за окно (константа)
|
|||
|
|
Комиссии: fee в каждом блоке (открытое поле, 8B)
|
|||
|
|
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). Чеканка открыта — нельзя напечатать из воздуха. Комиссии открыты — сумма верифицируема простым сложением. Переводы закрыты — нельзя узнать кто кому сколько. Два слоя: публичная эмиссия и комиссии + приватные транзакции.
|
|||
|
|
|
|||
|
|
### Двойная трата
|
|||
|
|
|
|||
|
|
Каждый аккаунт имеет одну цепочку. Два блока с одним prev_hash = форк. Форк обнаруживается мгновенно. Разрешается победителем τ₁: побеждает блок полученный раньше (меньшая позиция j в NodeChain победителя). Второй блок отбрасывается. Позиция верифицируема через VDF — гриндинг невозможен.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Состояние сети
|
|||
|
|
|
|||
|
|
Глобальное состояние = Account Table + Node Table.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Account Table (запись на аккаунт):
|
|||
|
|
account_id 32B
|
|||
|
|
balance_commitment 33B <- Pedersen commitment на баланс (значение скрыто)
|
|||
|
|
frontier_hash 32B <- хэш последнего блока в цепочке
|
|||
|
|
block_height 4B
|
|||
|
|
suite_id 2B
|
|||
|
|
current_pubkey 897B
|
|||
|
|
phone_hash 32B <- SHA-256("mt-phone" || phone), 0x00 если не привязан
|
|||
|
|
|
|||
|
|
Node Table (запись на узел):
|
|||
|
|
node_id 32B <- SHA-256("mt-node" || node_pubkey), верифицируемо
|
|||
|
|
node_pubkey 897B
|
|||
|
|
suite_id 2B
|
|||
|
|
chain_length 4B <- суммарное количество подписанных τ₁, weight = chain_length
|
|||
|
|
pending_invite 32B <- node_id приглашённого узла (0x00..00 если нет)
|
|||
|
|
invite_window 4B <- окно финализации NodeInvitation (0 если нет)
|
|||
|
|
invite_expires 4B <- invite_window + 21 160 (0 если нет)
|
|||
|
|
status 1B <- active | suspended
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### State Root
|
|||
|
|
|
|||
|
|
Merkle-дерево глобального состояния. Два подкорня, каждый — Merkle root своей таблицы с явным порядком листьев:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
State Root = SHA-256("mt-merkle-node" || account_root || node_root)
|
|||
|
|
|
|||
|
|
Account Table Root: листья по account_id (лексикографически)
|
|||
|
|
Node Table Root: листья по node_id (лексикографически)
|
|||
|
|
|
|||
|
|
active_set_root = Merkle root подмножества Node Table где status = active
|
|||
|
|
и chain_length > 0.
|
|||
|
|
Детерминировано из Global State. Узлы с weight = 0 не входят в active set.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Все sort keys фиксированной длины. Побайтовое лексикографическое сравнение. Две реализации с одинаковыми данными строят одинаковое дерево и получают одинаковый State Root.
|
|||
|
|
|
|||
|
|
State Root коммитится в заголовке каждого финализированного proposal τ₁.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Двигатели
|
|||
|
|
|
|||
|
|
Три цепочки с односторонним потоком зависимостей: 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 (публичный слой)
|
|||
|
|
|
|||
|
|
После закрытия окна τ₁ каждый узел публикует подписанное reveal-сообщение (VDF endpoint становится известен только после завершения VDF за окно). VDF Reveal — публичный: node_id, endpoint, подпись открыты. Это консенсус — должен быть верифицируем всеми:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
VDF_Reveal:
|
|||
|
|
node_id 32B
|
|||
|
|
window_index 4B <- индекс τ₁
|
|||
|
|
endpoint 32B <- S_{i,s,m}
|
|||
|
|
signature 666B <- FN-DSA-512, подписано node_pubkey
|
|||
|
|
Итого: ~734B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Reveal распространяется по P2P. Валидация при получении:
|
|||
|
|
|
|||
|
|
1. Подпись FN-DSA-512 соответствует node_pubkey из Node Table
|
|||
|
|
2. window_index = только что закрытый τ₁
|
|||
|
|
3. node_id существует в Node Table, status = active
|
|||
|
|
4. endpoint верифицируем: пересчёт NodeChain VDF от предыдущего endpoint (или от nodechain_init для первого окна узла)
|
|||
|
|
|
|||
|
|
Два reveal с одинаковыми (node_id, window_index) и одинаковым endpoint = дедупликация. Два reveal с одинаковыми (node_id, window_index) и разным endpoint = reveal_conflict (equivocation).
|
|||
|
|
|
|||
|
|
active_vdf_list = все валидные VDF_Reveal для текущего window_index, полученные до reveal_cutoff. Порядок: node_id лексикографически. Список каноничен. Свобода победителя над списком: ноль. Пропуск валидного reveal = невалидный proposal = fallback.
|
|||
|
|
|
|||
|
|
late_vdf_list = все валидные VDF_Reveal для предыдущего window_index (N-1), не вошедшие в active_vdf_list окна N-1, полученные до reveal_cutoff текущего окна. Порядок: node_id лексикографически. Каноничен. Одно окно grace — reveal для окна N-2 и старше отвергается.
|
|||
|
|
|
|||
|
|
chain_length += 1 за reveal в любом из двух списков. Время доказано — время учтено. Лотерея использует только active_vdf_list. TimeCoin начисляется только за active_vdf_list. Стимул раскрывать вовремя: опоздавший узел сохраняет chain_length, но теряет TimeCoin за это окно.
|
|||
|
|
|
|||
|
|
### Account — содержимое блока
|
|||
|
|
|
|||
|
|
Приём, верификация объектов и формирование набора. Два класса объектов:
|
|||
|
|
|
|||
|
|
**UserObjects** — пользовательские операции:
|
|||
|
|
|
|||
|
|
| Тип | Описание | Валидация |
|
|||
|
|
|-----|----------|-----------|
|
|||
|
|
| StateBlock | Приватный перевод | FN-DSA-512 подпись, Bulletproofs валидны, prev_hash. Если получатель не существует — создаётся автоматически |
|
|||
|
|
| OpenAccount | Публикация ключа | FN-DSA-512 подпись, account_id существует в Account Table (создан входящим переводом) |
|
|||
|
|
| ChangeKey | Смена ключа | FN-DSA-512 подпись старым ключом, new_pubkey |
|
|||
|
|
| AnchorBlock | Якорь данных ко времени | FN-DSA-512 подпись, Bulletproofs валидны, prev_hash, app_id = 32B, data_hash = 32B |
|
|||
|
|
| PhoneLink | Привязка телефона | FN-DSA-512 подпись, phone_hash = 32B, Bulletproofs валидны |
|
|||
|
|
|
|||
|
|
**ControlObjects** — объекты управляющие составом сети:
|
|||
|
|
|
|||
|
|
| Тип | Описание | Валидация |
|
|||
|
|
|-----|----------|-----------|
|
|||
|
|
| NodeInvitation | Приглашение нового узла | FN-DSA-512 подпись пригласившего, pending_invite = 0, status = active |
|
|||
|
|
| NodeRegistration | Регистрация узла | FN-DSA-512 подпись, node_id уникален, proof_endpoint верифицируем через VDF, приглашение существует |
|
|||
|
|
| EquivocationProof | Доказательство equivocation | Два конфликтующих подписанных сообщения от одного node_id (proposal_conflict или reveal_conflict) |
|
|||
|
|
|
|||
|
|
Каждый узел валидирует объекты обоих классов локально при получении. Валидные объекты ретранслируются по P2P. ControlObjects дополнительно ретранслируются каждое окно до финализации или expiry.
|
|||
|
|
|
|||
|
|
#### Два набора в proposal
|
|||
|
|
|
|||
|
|
Proposal содержит два набора с разными правилами:
|
|||
|
|
|
|||
|
|
**user_set** = все валидные UserObjects из мемпула победителя до block_cutoff. Определяется мемпулом победителя. UserObjects не вошедшие в proposal переносятся в следующее окно. Пропуск UserObject не является основанием для отклонения proposal.
|
|||
|
|
|
|||
|
|
**control_set** = все валидные ControlObjects полученные по P2P до control_cutoff, не финализированные ранее и не истёкшие. Каноничен — все ControlObjects включены ровно один раз. Пропуск или добавление лишнего ControlObject = невалидный proposal = fallback.
|
|||
|
|
|
|||
|
|
Порядок внутри control_set: H(object) лексикографически (каноничен, не зависит от победителя).
|
|||
|
|
|
|||
|
|
Порядок внутри user_set: позиция получения в NodeChain победителя. Каждый узел помечает момент получения UserObject как позицию j в своём NodeChain (промежуточный хэш S_{i,s,j}). Победитель сортирует user_set по позиции j — хронологический порядок, верифицируемый через пересчёт NodeChain. Точность: единицы микросекунд (одна позиция = 60/m секунд).
|
|||
|
|
|
|||
|
|
Форки аккаунтов (два блока с одним prev_hash) разрешаются правилом: побеждает блок, полученный победителем раньше (меньшая позиция j в NodeChain). Верифицируемо — позиция является промежуточным хэшем VDF.
|
|||
|
|
|
|||
|
|
#### Два дедлайна в окне
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
|-------- τ₁ (60 сек) --------|--- R (12 сек) ---|
|
|||
|
|
^ ^
|
|||
|
|
block_cutoff reveal_cutoff = control_cutoff
|
|||
|
|
(C сек до закрытия) (R сек после закрытия)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **block_cutoff** = C секунд до закрытия окна. UserObjects после block_cutoff переносятся в следующее окно. C достаточен для P2P-пропагации последних блоков. C = ⌈R / 2⌉.
|
|||
|
|
- **reveal_cutoff** = control_cutoff = R секунд после закрытия окна. VDF_Reveal и ControlObjects принимаются до этого момента.
|
|||
|
|
|
|||
|
|
R, F, C — калибруются в τ₂ (см. раздел «Калибровка R, F, C»). Генезис: R₀ = 12s, F₀ = 12s, C₀ = 6s.
|
|||
|
|
|
|||
|
|
После reveal_cutoff: определяется победитель лотереи, победитель собирает proposal.
|
|||
|
|
|
|||
|
|
#### Proposer
|
|||
|
|
|
|||
|
|
Победитель собирает proposal из двух наборов:
|
|||
|
|
- **control_set**: все ControlObjects до control_cutoff (каноничен, свобода = ноль)
|
|||
|
|
- **user_set**: все UserObjects из своего мемпула до block_cutoff
|
|||
|
|
|
|||
|
|
Свобода proposer над обработкой: ноль (порядок control_set = H(object), порядок user_set = позиция в NodeChain, state transition = детерминирован). Свобода над control_set: ноль (каноничен). Свобода над user_set: ограничена мемпулом (задержка, не раскол). Свобода над порядком user_set: ноль (определяется позицией получения в NodeChain, верифицируемо).
|
|||
|
|
|
|||
|
|
Proposal с невалидным объектом, пропущенным ControlObject или неверным state_root отклоняется, переход ко второму месту.
|
|||
|
|
|
|||
|
|
#### Финальность proposal
|
|||
|
|
|
|||
|
|
Финальность = подпись победителя на proposal header + независимая верифицируемость.
|
|||
|
|
|
|||
|
|
1. Победитель публикует подписанный proposal header + control_set + user_set + active_vdf_list + late_vdf_list
|
|||
|
|
2. Каждый узел проверяет каждый блок из списка по правилам валидации
|
|||
|
|
3. Каждый узел применяет state transition детерминированно
|
|||
|
|
4. Каждый узел сравнивает вычисленный state_root с заявленным в proposal
|
|||
|
|
5. Совпадает — proposal принят, state обновлён
|
|||
|
|
6. Не совпадает — proposal отклонён, fallback на второе место
|
|||
|
|
|
|||
|
|
Proposal header:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Proposal header:
|
|||
|
|
prev_proposal_hash 32B
|
|||
|
|
prev_state_root 32B <- State Root до применения блоков
|
|||
|
|
control_root 32B <- Merkle root control_set (каноничен)
|
|||
|
|
user_root 32B <- Merkle root user_set
|
|||
|
|
vdf_list_root 32B <- Merkle root active_vdf_list (каноничен)
|
|||
|
|
late_vdf_root 32B <- Merkle root late_vdf_list (каноничен, 0x00 если пуст)
|
|||
|
|
new_state_root 32B <- State Root после применения всех блоков
|
|||
|
|
active_set_root 32B
|
|||
|
|
timechain_value 32B
|
|||
|
|
timecoin_hash 32B
|
|||
|
|
winner_node_id 32B
|
|||
|
|
wall_clock 8B <- секунды с генезиса (локальное измерение победителя)
|
|||
|
|
fallback_depth 1B <- 1 = первое место, 2+ = fallback (каноничен)
|
|||
|
|
signature 666B <- FN-DSA-512, подписано node_pubkey победителя
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Fallback: если proposal победителя не получен в пределах timeout (F секунд после reveal_cutoff) или отклонён — proposal формирует второе место (следующий min ticket). Каскад до третьего места и далее с тем же timeout. Молчание не наказывается — победитель теряет только TimeCoin за это окно. F калибруется в τ₂ (см. раздел «Калибровка R, F, C»).
|
|||
|
|
|
|||
|
|
#### Непрерывность VDF
|
|||
|
|
|
|||
|
|
VDF следующего окна вычисляется непрерывно, не ожидая завершения финализации предыдущего. TimeChain для окна N+1 детерминирован — каждый узел вычисляет его независимо. NodeChain для окна N+1 стартует сразу после закрытия окна N, используя собственный endpoint текущего окна и новое значение TimeChain. Reveal phase и финализация происходят параллельно с началом VDF следующего окна.
|
|||
|
|
|
|||
|
|
#### Async confirmations (для light clients)
|
|||
|
|
|
|||
|
|
После принятия proposal узлы публикуют confirmation:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Confirmation:
|
|||
|
|
node_id 32B
|
|||
|
|
proposal_hash 32B
|
|||
|
|
signature 666B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Confirmations не участвуют в консенсусе. Они предоставляют light clients аудиторский след: независимые узлы подтвердили корректность state_root. Light client взвешивает confirmations по chain_length из prev_state_root (состояние на начало окна, зафиксировано в proposal header). Не по количеству node_id. Порог доверия определяется light client.
|
|||
|
|
|
|||
|
|
#### State transition
|
|||
|
|
|
|||
|
|
При финализации proposal state transition применяется атомарно в фиксированном порядке:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
apply_proposal(state, proposal) -> state':
|
|||
|
|
|
|||
|
|
Шаг 1: применить control_set в порядке H(object) лексикографически.
|
|||
|
|
NodeInvitation: записать pending_invite, invite_window и invite_expires в Node Table пригласившего.
|
|||
|
|
NodeRegistration: проверить приглашение, создать запись в Node Table
|
|||
|
|
(chain_length=1, status=active). Очистить pending_invite пригласившего.
|
|||
|
|
EquivocationProof: применить санкцию к node_id в Node Table
|
|||
|
|
(proposal_conflict или reveal_conflict: status=suspended, chain_length=0).
|
|||
|
|
|
|||
|
|
Шаг 2: применить user_set в порядке позиции получения в NodeChain победителя.
|
|||
|
|
StateBlock: обновить balance_commitment отправителя и получателя.
|
|||
|
|
Если получатель не существует — создать запись в Account Table.
|
|||
|
|
OpenAccount: записать pubkey в Account Table (аккаунт уже создан входящим переводом).
|
|||
|
|
ChangeKey: обновить pubkey и suite_id в Account Table.
|
|||
|
|
AnchorBlock: обновить balance_commitment отправителя (комиссия).
|
|||
|
|
PhoneLink: записать phone_hash в Account Table, обновить balance_commitment (комиссия).
|
|||
|
|
|
|||
|
|
Шаг 3: применить TimeCoin победителя.
|
|||
|
|
|
|||
|
|
Шаг 4: обновить chain_length.
|
|||
|
|
Для каждого node_id из active_vdf_list в proposal:
|
|||
|
|
chain_length += 1 в Node Table.
|
|||
|
|
Для каждого node_id из late_vdf_list в proposal:
|
|||
|
|
chain_length += 1 в Node Table.
|
|||
|
|
|
|||
|
|
Шаг 5: обработать expiry.
|
|||
|
|
Приглашения узлов: все записи Node Table где invite_expires <= current_window
|
|||
|
|
и invite_expires > 0 -> очистить pending_invite, invite_window и invite_expires.
|
|||
|
|
|
|||
|
|
Шаг 6: вычислить new_state_root.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Порядок детерминирован. Control_set первым, user_set вторым, TimeCoin третьим, chain_length четвёртым, expiry последним. Каждый узел применяет одну и ту же последовательность шагов к одним и тем же наборам и получает один и тот же new_state_root.
|
|||
|
|
|
|||
|
|
Account зависит от TimeChain и NodeChain. Обратных зависимостей нет.
|
|||
|
|
|
|||
|
|
С ростом TPS сети дополнительные ядра подключаются для верификации блоков. Минимум для валидатора: 3 логических ядра (TimeChain + NodeChain + Account). Верификация блоков аккаунтов полностью параллелизуется — цепочки аккаунтов независимы.
|
|||
|
|
|
|||
|
|
### Приглашение и регистрация
|
|||
|
|
|
|||
|
|
Два уровня входа в сеть. Узлы участвуют в консенсусе — жёсткий вход (приглашение + 14 дней VDF). Аккаунты держат и переводят средства — облегчённый вход (приглашение, немедленная активация).
|
|||
|
|
|
|||
|
|
Генезис: 12 узлов в разных локациях (hardcoded, аналог bootstrap nodes в Bitcoin).
|
|||
|
|
|
|||
|
|
#### Приглашение узла (NodeInvitation)
|
|||
|
|
|
|||
|
|
Вход узла в консенсус. Приглашение + 14 дней VDF + регистрация. Одновременно одно приглашение узла на пригласившего.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
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 status = active
|
|||
|
|
3. inviter pending_invite = 0x00..00 (нет активного приглашения узла)
|
|||
|
|
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 (chain_length = 1, status = active). Очистить 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-барьер: каждый спам-аккаунт стоит отправителю min_fee за перевод. Пустые аккаунты бесполезны — без баланса ничего не делают.
|
|||
|
|
|
|||
|
|
#### Скорость роста сети
|
|||
|
|
|
|||
|
|
Узлы: каждый узел производит максимум одно приглашение узла за ~14 дней. Рост ограничен текущим размером сети:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Генезис: 12 узлов
|
|||
|
|
14 дней: 24
|
|||
|
|
28 дней: 48
|
|||
|
|
1 год: до 12 × 2^26 (~800M, теоретический максимум)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Аккаунты: без ограничений. Любой владелец TimeCoin может создать аккаунт любому, переведя средства на новый адрес. Рост пользовательской базы не ограничен протоколом.
|
|||
|
|
|
|||
|
|
Аппаратный бюджет сверх количества приглашений узлов бесполезен. 1000 узлов = максимум 1000 новых узлов за 14 дней, независимо от количества ядер.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Потоковая модель
|
|||
|
|
|
|||
|
|
Блоки аккаунтов текут непрерывно. Узел получает блок → проверяет подпись FN-DSA-512 и баланс → помечает позицию j в своём NodeChain → передаёт в P2P gossip. Время подтверждения определяется скоростью P2P-распространения (~2-5 секунд).
|
|||
|
|
|
|||
|
|
Позиция j — промежуточный хэш S_{i,s,j} NodeChain в момент получения блока. Криптографическое доказательство момента получения с точностью до микросекунд. Верифицируемо через пересчёт NodeChain.
|
|||
|
|
|
|||
|
|
Блок включается в канонический набор при включении победителем лотереи в proposal τ₁. Порядок блоков в proposal — хронологический по позиции в NodeChain победителя. Окна являются точками финализации, а не условиями приёма.
|
|||
|
|
|
|||
|
|
Кошелёк получателя отображает входящий перевод сразу после P2P-валидации (~2-5 секунд) как «подтверждён, ожидает финализации». После включения в proposal τ₁ — «финализирован». Подтверждение — безопасность подписи. Финализация — каноническое состояние.
|
|||
|
|
|
|||
|
|
Цепочки аккаунтов полностью независимы. Блоки разных аккаунтов обрабатываются параллельно без конфликтов.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Временные слои (τ)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
τ₁ (60с) → τ₂ (20 160 × τ₁ ≈ 14 дней)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Одно окно — τ₁. Всё остальное — производные.
|
|||
|
|
|
|||
|
|
### τ₁ — Окно (60 секунд)
|
|||
|
|
|
|||
|
|
Единственная единица протокольного времени. Слот, финализация и регистрация времени — одно и то же.
|
|||
|
|
|
|||
|
|
- TimeChain продвигается на D хэшей
|
|||
|
|
- NodeChain продвигается на m хэшей с якорем в текущем T_s
|
|||
|
|
- Узлы валидируют и ретранслируют блоки аккаунтов по P2P
|
|||
|
|
- Active set (состав и веса участников) определяется на момент reveal_cutoff. VDF не зависит от active set — вычисляется непрерывно. К reveal_cutoff state transition предыдущего окна применён — active set актуален
|
|||
|
|
- control_set: все валидные ControlObjects до control_cutoff (каноничен)
|
|||
|
|
- user_set: все валидные UserObjects из мемпула победителя до block_cutoff, порядок по позиции получения в NodeChain
|
|||
|
|
- Узлы раскрывают NodeChain endpoint (reveal phase, R = 12 секунд)
|
|||
|
|
- Лотерея: `ticket_i = -ln(S_{i,s,m} / 2^256) / weight_i`, победитель = min(ticket_i) среди допущенных
|
|||
|
|
- Победитель публикует подписанный proposal
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Proposal header:
|
|||
|
|
prev_proposal_hash 32B
|
|||
|
|
prev_state_root 32B
|
|||
|
|
control_root 32B
|
|||
|
|
user_root 32B
|
|||
|
|
vdf_list_root 32B
|
|||
|
|
late_vdf_root 32B
|
|||
|
|
new_state_root 32B
|
|||
|
|
active_set_root 32B
|
|||
|
|
timechain_value 32B
|
|||
|
|
timecoin_hash 32B
|
|||
|
|
winner_node_id 32B
|
|||
|
|
wall_clock 8B
|
|||
|
|
fallback_depth 1B
|
|||
|
|
signature 666B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- Финальность: подпись победителя на proposal header. Каждый валидатор применяет блоки детерминированно и проверяет new_state_root
|
|||
|
|
- При финализации: state transition по фиксированному порядку (см. раздел «State transition» в Account)
|
|||
|
|
- 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, C (см. ниже)
|
|||
|
|
- State Root коммитится в каждом τ₁ (в proposal header). State Root покрывает весь Global State: Account Table, Node Table. τ₂ фиксирует канонический State Root для Fast Sync
|
|||
|
|
- Криптографическая амнезия: подписанные proposals сохраняются навсегда — верифицируемая цепочка state commitments. Тела блоков аккаунтов, подписи FN-DSA-512 и данные execution objects удаляются после 8 × τ₂ (112 дней). Proposals доказывают что конкретное состояние было закоммичено победителем; восстановление содержимого состояния требует snapshot или архива. Equivocation proofs по объектам старше 8 × τ₂ не принимаются — все споры разрешаются внутри окна
|
|||
|
|
- Пересчёт параметров размера окна τ₁
|
|||
|
|
|
|||
|
|
#### Калибровка 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 × actual_interval / target_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 MSK. Медиана 20 159 интервалов — для сдвига необходим контроль >50% proposals (>50% веса сети).
|
|||
|
|
|
|||
|
|
#### Калибровка R, F, C
|
|||
|
|
|
|||
|
|
Входные данные — из proposals за τ₂ (канонические, детерминированно вычисляются всеми узлами):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
late_ratio = Σ|late_vdf_list| / (Σ|active_vdf_list| + Σ|late_vdf_list|)
|
|||
|
|
fallback_ratio = count(fallback_depth > 1) / 20160
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Формула пересчёта:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
R_new = clamp(R_old × late_ratio / late_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)
|
|||
|
|
|
|||
|
|
C = ⌈R / 2⌉
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Протокольные константы:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
late_target = 5%
|
|||
|
|
fallback_target = 5%
|
|||
|
|
R_min = 4s, R_max = 30s
|
|||
|
|
F_min = 4s, F_max = 30s
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Генезис: R₀ = 12s, F₀ = 12s, C₀ = 6s. Шаг: ±20% за τ₂. C — производная от R, отдельной калибровки не требует.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Консенсус — Proof of Time (PoT)
|
|||
|
|
|
|||
|
|
### Три цепочки
|
|||
|
|
|
|||
|
|
**TimeChain** — глобальные часы. Чистая VDF-цепочка `T_r = SHA-256^D(T_{r-1})`. Первичный продукт протокола. Источник времени и случайности. Продвигается по расписанию окон.
|
|||
|
|
|
|||
|
|
**NodeChain** — персональная цепочка узла. VDF-цепочка конкретного node_id, якорится в TimeChain каждое окно. Доказывает присутствие при каждом тике часов. Раскрытие endpoint при закрытии τ₁ = +1 звено = +1 к весу.
|
|||
|
|
|
|||
|
|
**Account** — состояние счёта. Два набора: control_set (каноничен, все ControlObjects) + user_set (из мемпула победителя). Порядок и обработка детерминированы.
|
|||
|
|
|
|||
|
|
Зависимости односторонние: TimeChain → NodeChain → Account. Отказ в Account не останавливает часы. Отказ конкретного узла в NodeChain не заражает общий ритм.
|
|||
|
|
|
|||
|
|
### Стаж и вес
|
|||
|
|
|
|||
|
|
#### Определение
|
|||
|
|
|
|||
|
|
Вес узла — суммарное количество подписанных τ₁:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
weight_i = chain_length_i
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Вес — единственная мера влияния узла в протоколе. Определяется только количеством подписанных окон.
|
|||
|
|
|
|||
|
|
#### Как растёт
|
|||
|
|
|
|||
|
|
Подписал окно — плюс один. Не подписал — ничего не произошло. Equivocation — chain_length = 0.
|
|||
|
|
|
|||
|
|
Узел раскрывает VDF_Reveal после закрытия τ₁ (см. раздел «VDF Reveal»). active_vdf_list каноничен: все валидные reveal до reveal_cutoff, порядок по node_id. Свобода победителя: ноль. State transition: chain_length += 1 для каждого node_id из active_vdf_list и late_vdf_list. Пропуск окна не наказывается — узел просто не получает +1. Late reveal (опоздавший на одно окно) сохраняет chain_length, но не участвует в лотерее и не получает TimeCoin.
|
|||
|
|
|
|||
|
|
|
|||
|
|
#### На что влияет вес
|
|||
|
|
|
|||
|
|
Вес определяет две вещи:
|
|||
|
|
|
|||
|
|
**1. Лотерея.** Вероятность победы в τ₁ строго пропорциональна весу:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
ticket_i = -ln(S_{i,s,m} / 2^256) / weight_i
|
|||
|
|
winner = min(ticket_i)
|
|||
|
|
P(node_i) = weight_i / Σ weight(all_nodes)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Доказательство: S_{i,s,m} / 2^256 приближает U[0,1]. -ln(U) ~ Exp(1). -ln(U)/w ~ Exp(w). Минимум независимых Exp(w_i): P(i wins) = w_i / Σw_j. Точно пропорционально при любых весах.
|
|||
|
|
|
|||
|
|
Узел с weight = 0 не участвует в лотерее.
|
|||
|
|
|
|||
|
|
**2. Допуск.** weight = 0 означает: узел участвует в сети (валидация, ретрансляция), но не участвует в лотерее и не может быть победителем τ₁.
|
|||
|
|
|
|||
|
|
### Победитель τ₁
|
|||
|
|
|
|||
|
|
Победитель определяется после закрытия окна τ₁. Каждый узел раскрывает свой NodeChain endpoint. Минимальный ticket среди допущенных — победитель.
|
|||
|
|
|
|||
|
|
Победитель публикует подписанный proposal: control_set (каноничен) + user_set (из мемпула) + TimeCoin + active_vdf_list (каноничен) + late_vdf_list (каноничен). Порядок и обработка детерминированы. Валидация: control_set полон, все объекты валидны, state_root корректен. UserObjects не вошедшие в proposal переносятся в следующее окно.
|
|||
|
|
|
|||
|
|
Финальность — подпись победителя на proposal header. Верификация — независимый пересчёт state_root.
|
|||
|
|
|
|||
|
|
### Верификация
|
|||
|
|
|
|||
|
|
Победитель публикует: `{node_id, NodeChain endpoint, proposal}`.
|
|||
|
|
|
|||
|
|
Верификация NodeChain за одно окно: пересчёт m хэшей. Параллелизация по сегментам — время верификации обратно пропорционально числу ядер.
|
|||
|
|
|
|||
|
|
Верификация proposal: независимое применение блоков из canonical set и сравнение state_root.
|
|||
|
|
|
|||
|
|
### Устойчивость
|
|||
|
|
|
|||
|
|
- **Остановка часов** исключена: каждый узел тикает независимо. Остановить протокол = остановить все VDF-осцилляторы одновременно
|
|||
|
|
- **Искажение часов** исключено: VDF последователен, результат детерминирован. Нельзя ускорить, замедлить или подделать
|
|||
|
|
- **Proposer grinding** исключён: порядок control_set = H(object) лексикографически, порядок user_set = позиция в NodeChain (верифицируемо), state transition детерминирован, свобода над обработкой = ноль
|
|||
|
|
- **Front-running** исключён: порядок user_set определяется позицией получения в NodeChain победителя, победитель неизвестен до reveal — атакующий не знает чей NodeChain определит порядок
|
|||
|
|
- **Committee grinding** исключён: TimeChain не зависит от состояния и транзакций, seed лотереи строится из TimeChain
|
|||
|
|
- **Node_id гриндинг** исключён: TimeChain неизвестен при регистрации
|
|||
|
|
- **Предвычисление** исключено: seed содержит текущее значение TimeChain
|
|||
|
|
- **Replay** исключён: TimeChain уникален для каждого τ₁
|
|||
|
|
- **Аппаратное преимущество** ограничено: последовательное хэширование масштабируется тактовой частотой и IPC, а не количеством ядер или бюджетом
|
|||
|
|
- **Sybil-барьер**: вход по приглашению (1 инвайт на узел, 1 одновременно) + регистрация = 20 160 окон VDF (~14 дней) + NodeChain (физическое ядро) + линейный рост веса. Скорость роста Sybil ограничена размером его текущей сети, а не бюджетом
|
|||
|
|
- **Цензура UserObjects** = задержка, не раскол. Пропущенный блок переносится в следующее окно. В account chain prev_hash резервирует место
|
|||
|
|
- **Цензура ControlObjects** исключена: control_set каноничен, пропуск = невалидный proposal = fallback
|
|||
|
|
- **Liveness halt** исключён: нет порогового голосования, финальность определяется одним победителем с fallback на следующий ticket
|
|||
|
|
- **Fallback cascade**: молчащий победитель теряет TimeCoin за это окно. Санкции без подписанного доказательства не применяются
|
|||
|
|
|
|||
|
|
### Разрешение конфликтов и санкции
|
|||
|
|
|
|||
|
|
Два класса нарушений. Пользовательские конфликты разрешаются протокольными правилами без санкций. Валидаторский equivocation — через аннулирование конфликтующих сообщений и санкции.
|
|||
|
|
|
|||
|
|
#### Пользовательские конфликты
|
|||
|
|
|
|||
|
|
**Двойной блок аккаунта** (два блока с одним prev_hash): побеждает блок полученный победителем раньше (меньшая позиция в NodeChain). Верифицируемо через VDF. Без санкции.
|
|||
|
|
|
|||
|
|
**Невалидный proposal**: валидаторы отклоняют, переход ко второму месту. Без санкции (потерянный TimeCoin — достаточное наказание).
|
|||
|
|
|
|||
|
|
#### Валидаторские нарушения
|
|||
|
|
|
|||
|
|
Два типа нарушений. Оба доказуемы криптографически — подписанные конфликтующие сообщения. Санкции возникают только из подписанного доказательства, не из отсутствия сообщения.
|
|||
|
|
|
|||
|
|
**Proposal conflict** — победитель публикует два разных proposal для одного τ₁ (одинаковые (node_id, window_index), разный proposal_hash):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Сразу: status = suspended, chain_length = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Reveal conflict** — узел публикует два разных VDF_Reveal для одного τ₁ (одинаковые (node_id, window_index), разный endpoint):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Сразу: status = suspended, chain_length = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Молчание победителя (proposal не опубликован) не является нарушением. Победитель теряет TimeCoin за это окно. chain_length не затрагивается (VDF_Reveal доказывает присутствие). Fallback на следующий ticket без санкций.
|
|||
|
|
|
|||
|
|
Санкции вступают в силу с следующего τ₁. Active set определяется на момент reveal_cutoff — к этому моменту state transition предыдущего окна применён.
|
|||
|
|
|
|||
|
|
#### Equivocation proof
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
EquivocationProof:
|
|||
|
|
type 1B <- proposal_conflict | reveal_conflict
|
|||
|
|
node_id 32B
|
|||
|
|
evidence_a <- первое подписанное сообщение
|
|||
|
|
evidence_b <- второе конфликтующее подписанное сообщение
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Публикует любой узел обнаруживший конфликт. Верификация: оба сообщения подписаны одним node_id с одинаковым window_index и разным содержимым. Подписи FN-DSA-512 криптографически верифицируемы.
|
|||
|
|
|
|||
|
|
Proof включается в канонический набор τ₁. При финализации state transition применяет санкцию к node_id.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Адреса и переводы
|
|||
|
|
|
|||
|
|
### Полный флоу перевода
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. Боб: кошелёк генерирует keypair -> account_id (постоянный адрес, существует математически)
|
|||
|
|
2. Боб -> Алисе: "отправь на mt4ZGfe..." (account_id)
|
|||
|
|
3. Алиса формирует StateBlock в своей цепочке:
|
|||
|
|
prev_hash: хэш её предыдущего блока
|
|||
|
|
link: account_id Боба
|
|||
|
|
link_amount: 50 Ɉ
|
|||
|
|
balance: 49.999 Ɉ (100 - 50 - 0.001 fee)
|
|||
|
|
4. Алиса подписывает FN-DSA-512
|
|||
|
|
5. Алиса рассылает блок узлам сети
|
|||
|
|
6. Каждый узел проверяет:
|
|||
|
|
FN-DSA-512 подпись валидна для pubkey Алисы
|
|||
|
|
prev_hash совпадает с frontier Алисы
|
|||
|
|
fee = 100 - 49.999 - 50 = 0.001 Ɉ >= min_fee
|
|||
|
|
balance >= 0
|
|||
|
|
link_amount > 0
|
|||
|
|
7. Блок распространяется через P2P gossip (~2-5 секунд)
|
|||
|
|
Кошелёк Боба: «подтверждён, ожидает финализации»
|
|||
|
|
8. Победитель лотереи включает блок в proposal τ₁
|
|||
|
|
9. При финализации proposal:
|
|||
|
|
Баланс Алисы: 50 Ɉ (из StateBlock)
|
|||
|
|
Если Боб новый — создать запись в Account Table (balance = 50 Ɉ)
|
|||
|
|
Если Боб существует — баланс увеличен на 50 Ɉ (детерминированно)
|
|||
|
|
Кошелёк Боба: «финализирован»
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Баланс
|
|||
|
|
|
|||
|
|
Баланс аккаунта — одно число в таблице аккаунтов. Обновляется при финализации: исходящие переводы (из StateBlock отправителя) и входящие зачисления (детерминированно по финализированным блокам).
|
|||
|
|
|
|||
|
|
Бэкап = 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 MSK |
|
|||
|
|
| 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%
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Bootstrap
|
|||
|
|
|
|||
|
|
Ранние участники записывают непропорционально большую долю времени через вероятность лотереи. При 10 узлах каждый побеждает примерно раз в 10 минут. При 100 000 — примерно раз в 70 дней. Bootstrap встроен в математику лотереи, а не в расписание часов.
|
|||
|
|
|
|||
|
|
### Распределение
|
|||
|
|
|
|||
|
|
Победитель τ₁ записывает прошедшее время и получает все комиссии окна через TimeCoin в своей цепочке. Одно правило. Неизменно с генезиса.
|
|||
|
|
|
|||
|
|
Базовый бюджет: 60 Ɉ/τ₁ (запись 60 секунд) + комиссии. Реальный бюджет безопасности в покупательной способности зависит от рынка.
|
|||
|
|
|
|||
|
|
1 TimeCoin = 1 секунда описывает скорость хода часов. Не ценовой peg, не гарантия покупательной способности.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Пропускная способность
|
|||
|
|
|
|||
|
|
Размер StateBlock: ~779B.
|
|||
|
|
|
|||
|
|
| Канал узла | 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:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Хранит:
|
|||
|
|
Account Table (commitments, frontier_hash, pubkey, phone_hash)
|
|||
|
|
Node Table (node_id, pubkey, chain_length, status)
|
|||
|
|
Proposals (навсегда)
|
|||
|
|
Blob Buffer (зашифрованные сообщения для владельца, TTL = τ₂)
|
|||
|
|
|
|||
|
|
Делает:
|
|||
|
|
TimeChain VDF (1 ядро, 24/7)
|
|||
|
|
NodeChain VDF (1 ядро, 24/7)
|
|||
|
|
Валидация блоков (1+ ядро)
|
|||
|
|
P2P gossip (блоки, 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 AnchorBlocks/год | ~1.1 GB |
|
|||
|
|
|
|||
|
|
### Потеря данных клиента
|
|||
|
|
|
|||
|
|
Потеря устройства: balance_commitment в Account Table цел, seed восстанавливает ключи, баланс доступен. История переводов и сообщений утрачена — как потерять выписку, не деньги. Если есть доверенный узел — зашифрованные сообщения можно восстановить.
|
|||
|
|
|
|||
|
|
### Fast Sync (новый узел)
|
|||
|
|
|
|||
|
|
1. Цепочка proposals от генезиса — проверка TimeChain-цепочки и подписей победителей (мегабайты)
|
|||
|
|
2. State Root из последнего τ₂ (покрывает весь Global State)
|
|||
|
|
3. Global State snapshot от пиров: каноническая сериализация всех листьев Merkle-дерева состояния (Account Table + Node Table). Верификация: пересчёт Merkle root из полученных листьев, сравнение с State Root из proposal τ₂. `MerkleRoot(snapshot_leaves) == state_root_from_proposal`
|
|||
|
|
4. Узел синхронизирован и готов к участию
|
|||
|
|
|
|||
|
|
Proposals доказывают цепочку state commitments. Snapshot восстанавливает содержимое состояния через пересчёт Merkle root. Тела блоков не нужны — состояние самодостаточно.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Application Layer
|
|||
|
|
|
|||
|
|
Montana — цифровой стандарт времени. Приложения управляют своим состоянием самостоятельно (серверы, базы данных, P2P). Montana хранит только криптографические отпечатки с привязкой ко времени — 32 байта на запись.
|
|||
|
|
|
|||
|
|
### AnchorBlock
|
|||
|
|
|
|||
|
|
Один блок, одна комиссия, данные навсегда привязаны к timechain_value конкретного окна.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
AnchorBlock:
|
|||
|
|
prev_hash 32B
|
|||
|
|
account_id 32B
|
|||
|
|
app_id 32B <- SHA-256("mt-app" || app_name)
|
|||
|
|
data_hash 32B <- Merkle root, H(document), произвольный хэш
|
|||
|
|
balance_commitment 33B <- Pedersen commitment (баланс скрыт)
|
|||
|
|
signature 666B
|
|||
|
|
range_proof 336B <- Bulletproof
|
|||
|
|
Итого: ~1 163B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
app_id — детерминированный идентификатор пространства имён. Вычисляется из имени приложения, регистрация не требуется. Позволяет фильтровать, индексировать, строить лёгкие клиенты для конкретного приложения.
|
|||
|
|
|
|||
|
|
### Timestamp Proof
|
|||
|
|
|
|||
|
|
Стандартный формат доказательства: документ D существовал до момента T.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Доказательство:
|
|||
|
|
1. H(D) <- хэш документа
|
|||
|
|
2. AnchorBlock с data_hash <- содержит MerkleRoot включающий H(D)
|
|||
|
|
3. Merkle proof: H(D) → data_hash <- H(D) является листом дерева
|
|||
|
|
4. Proposal header окна <- содержит AnchorBlock, timechain_value = T
|
|||
|
|
5. VDF-цепочка от генезиса <- доказывает T
|
|||
|
|
|
|||
|
|
Верификация:
|
|||
|
|
1. Пересчитать Merkle proof: H(D) → data_hash
|
|||
|
|
2. Убедиться что AnchorBlock включён в proposal (user_root)
|
|||
|
|
3. Убедиться что proposal содержит валидный timechain_value
|
|||
|
|
4. Любой участник с доступом к цепочке proposals верифицирует
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Proposals хранятся навсегда. Timestamp proof верифицируем в любой момент.
|
|||
|
|
|
|||
|
|
### Примеры
|
|||
|
|
|
|||
|
|
**Мессенджер.** Каждое сообщение хэшируется, цепочка хэшей формирует Merkle root, Merkle root записывается в AnchorBlock раз в минуту или час. Montana хранит 32 байта — доказательство что набор сообщений существовал в конкретный момент. Подделать историю переписки невозможно — хэш не совпадёт.
|
|||
|
|
|
|||
|
|
**Архив документов.** Компания ежедневно записывает Merkle root документов. Через 10 лет регулятор спрашивает «существовал ли документ X на дату Y». Компания предоставляет документ, Merkle proof и ссылку на proposal. Верификация математическая.
|
|||
|
|
|
|||
|
|
**Социальная сеть.** Каждый пост привязан к Montana Time через AnchorBlock. Порядок публикаций доказуем. Редактирование не скрывает оригинал — хэш оригинала уже в цепочке.
|
|||
|
|
|
|||
|
|
### Экономика
|
|||
|
|
|
|||
|
|
Каждый AnchorBlock платит комиссию. Тысячи приложений записывающих якоря — тысячи комиссий каждое окно. Спрос на токен привязан к утилитарной функции: запись времени, не спекуляция.
|
|||
|
|
|
|||
|
|
Не нужны смарт-контракты. Не нужен 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)
|
|||
|
|
balance_commitment 33B
|
|||
|
|
signature 666B
|
|||
|
|
range_proof 336B
|
|||
|
|
Итого: ~1 131B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Алиса открывает адресную книгу → вычисляет 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:** AnchorBlock с хэшем переписки → доказуемый момент
|
|||
|
|
|
|||
|
|
**Что Montana НЕ делает:**
|
|||
|
|
|
|||
|
|
- Не хранит сообщения
|
|||
|
|
- Не маршрутизирует
|
|||
|
|
- Не буферизирует
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Отправка:
|
|||
|
|
1. Алиса знает phone_hash Боба → account_id → pubkey (локально)
|
|||
|
|
2. Шифрует сообщение pubkey Боба
|
|||
|
|
3. Отправляет прямо устройству Боба через P2P (libp2p)
|
|||
|
|
4. Опционально: AnchorBlock с 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 — запись
|
|||
|
|
|
|||
|
|
Внешняя система формирует AnchorBlock и отправляет в P2P-сеть.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Вход: app_id (32B) + data_hash (32B) + подпись FN-DSA-512
|
|||
|
|
Выход: AnchorBlock включён в 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
|
|||
|
|
├── Аккаунт: FN-DSA-512 keypair → account_id = SHA-256("mt-account" || suite_id || account_pubkey)
|
|||
|
|
└── Узел: FN-DSA-512 keypair → node_id = SHA-256("mt-node" || node_pubkey)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Один seed порождает два FN-DSA-512 keypair: для аккаунта (подпись блоков) и для узла (подпись proposals и NodeChain endpoints). account_id и node_id выводятся из публичных ключей, верифицируемы без знания seed. Бэкап = seed.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Криптографическая реализация
|
|||
|
|
|
|||
|
|
### 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-block` | Хэширование блоков аккаунтов |
|
|||
|
|
| `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 |
|
|||
|
|
|
|||
|
|
- Альтернативные сериализации запрещены
|
|||
|
|
- Test vectors для каждого консенсусного объекта
|
|||
|
|
- Cross-implementation conformance tests перед запуском mainnet
|
|||
|
|
|
|||
|
|
### Protocol layer
|
|||
|
|
|
|||
|
|
Собственная реализация поверх криптографического ядра:
|
|||
|
|
|
|||
|
|
| Компонент | Назначение |
|
|||
|
|
|-----------|------------|
|
|||
|
|
| Merkle-деревья | State Root, blocks_root (из SHA-256 вызовов) |
|
|||
|
|
| VDF scheduling | Управление TimeChain и NodeChain цепочками |
|
|||
|
|
| State machine | Account Table, Node Table, state transitions |
|
|||
|
|
| P2P gossip | Распространение блоков и proposals |
|
|||
|
|
|
|||
|
|
### Инфраструктура
|
|||
|
|
|
|||
|
|
| Библиотека | Назначение |
|
|||
|
|
|------------|------------|
|
|||
|
|
| RocksDB | Хранение Account Table и блоков |
|
|||
|
|
| libp2p | P2P транспорт |
|
|||
|
|
|
|||
|
|
Production: Rust.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Архитектура
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────┐
|
|||
|
|
│ Wallet │
|
|||
|
|
│ Кошелёк, баланс, переводы │
|
|||
|
|
│ FN-DSA-512 keypair │
|
|||
|
|
└──────────────┬──────────────────┘
|
|||
|
|
│
|
|||
|
|
┌──────────────┴──────────────────┐
|
|||
|
|
│ Montana │
|
|||
|
|
│ Децентрализованные часы │
|
|||
|
|
│ │
|
|||
|
|
│ TimeChain ──→ NodeChain ──→ Account
|
|||
|
|
│ (часы) (присутствие) (состояние)
|
|||
|
|
│ │
|
|||
|
|
│ Account Chain (Block Lattice) │
|
|||
|
|
│ Account Table, Proposals │
|
|||
|
|
│ SHA-256, FN-DSA-512 │
|
|||
|
|
└─────────────────────────────────┘
|
|||
|
|
```
|