montana/Montana-Protocol/Spec-Patch-VpnHeartbeat.md
2026-05-21 03:44:38 +03:00

348 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Spec Patch: opcode `0x06 VpnHeartbeat`
**Целевая версия спеки:** Montana Protocol v35.25.1 → **v35.26.0**
**Дата проекта патча:** 2026-05-19
**Применимый раздел:** §11 «Account operations», §13 «apply_proposal»
**Зависимые инварианты:** [I-1] PQ-secure, [I-3] Determinism, [I-9] Bit-exact arithmetic, [I-13] Deflationary sink, [I-14] State lifecycle
**Связанные роли:** `Protocol/CLAUDE.md` v4.30.0+ (архитектор), `Protocol/CRITIC.md` v3.13.0+ (критик)
---
## Намерение патча
Закрытие глобальной слабости в текущей реализации Montana App + VPN-balance coordinator:
1. **Heartbeat начисление Ɉ происходит вне консенсуса.** Текущий Rust coordinator на узле Moscow ведёт `state.json` авторитарно, не реплицируется через TimeChain, не cemented. F-4 из аудит-пакета `Android/Внешний-аудит/07-Известные-ограничения.md`.
2. **Sybil-resistance работает только через Ed25519 TOFU pinning.** Это нарушает [I-1] PQ-secure (Shor на curve25519), хотя сейчас acceptable как Phase 2 stopgap. CF-Phase2-1.
3. **Migration к полному консенсусу требует нового opcode и operation type.**
Этот патч описывает opcode `0x06 VpnHeartbeat` — нормативно. После принятия:
- `mt-account` реализует `apply_vpn_heartbeat`
- `montana-node` принимает в mempool, cement через BundledConfirmation
- AccountRecord.balance обновляется через canonical apply pipeline
- Координатор Moscow становится **indexer** для быстрого чтения, не source-of-truth
---
## §1. Type byte registry — расширение
Текущее распределение (Protocol v35.25.0 §11.1):
| Byte | Operation | Класс |
|------|-----------|-------|
| `0x01` | _reserved (anchor-fee sponsor)_ | — |
| `0x02` | Transfer | value |
| `0x03` | ChangeKey | power |
| `0x04` | Anchor | value |
| `0x05` | NicknameBid | value (allocation) |
После v35.26.0:
| Byte | Operation | Класс |
|------|-----------|-------|
| `0x01` | _reserved_ | — |
| `0x02` | Transfer | value |
| `0x03` | ChangeKey | power |
| `0x04` | Anchor | value |
| `0x05` | NicknameBid | value (allocation) |
| **`0x06`** | **`VpnHeartbeat`** | **value (operator-credit)** |
---
## §2. Operation layout
```
operation VpnHeartbeat ::= {
type_byte: u8 = 0x06
sender_account_id: AccountId (20B) // адрес владельца кошелька-кредитуемого
window_index: u64 (8B LE) // окно консенсуса в котором heartbeat валиден
exit_node_id: NodeId (32B) // public key валидатора-exit (узел VPN-каскада)
duration_ms: u32 (4B LE) // intervalo миллисекунд с предыдущего heartbeat
operator_signature: Signature (666B) // FN-DSA-512 подпись sender-а над canonical_preimage
}
```
**Размер:** `1 + 20 + 8 + 32 + 4 + 666 = 731 bytes`.
### §2.1 Canonical preimage для подписи
```
domain_separator = ASCII("mt-vpn-heartbeat-v1") (19B)
preimage = domain_separator
|| sender_account_id (20B)
|| window_index_le8 (8B)
|| exit_node_id (32B)
|| duration_ms_le4 (4B)
preimage_length = 19 + 20 + 8 + 32 + 4 = 83 bytes
```
`operator_signature = FN-DSA-512.Sign(sender_secret_key, preimage)`.
### §2.2 Инварианты VpnHeartbeat
1. **VH-1 type byte:** `type_byte == 0x06`.
2. **VH-2 sender exists:** `AccountTable[sender_account_id]` существует и `account_chain_length ≥ 1`.
3. **VH-3 window bounds:** `current_window 1 ≤ window_index ≤ current_window` (heartbeat принимается только в текущем или предыдущем окне).
4. **VH-4 exit-node validity:** `NodeTable[exit_node_id]` существует и имеет `is_active = true` и `node_role ∈ {exit_node, dual_role}`.
5. **VH-5 signature verify:** `FN-DSA-512.Verify(AccountTable[sender_account_id].suite_pubkey, preimage, operator_signature) == true`.
6. **VH-6 duration upper bound:** `duration_ms ≤ MAX_HEARTBEAT_DURATION_MS = 30 000` ms (защита от backdate flooding).
7. **VH-7 duration lower bound:** `duration_ms ≥ MIN_HEARTBEAT_DURATION_MS = 4 000` ms (защита от heartbeat spam внутри окна).
8. **VH-8 monotonic nonce:** `duration_ms` накопляется через `AccountTable[sender].vpn_credited_seconds_in_window[window_index]` — heartbeat принимается только если суммарная сумма за окно ≤ `WINDOW_DURATION_MS = τ₁`.
9. **VH-9 exit-node cooldown:** `NodeTable[exit_node_id].last_heartbeat_processed_window ≥ window_index 2` (защита от использования давно-offline exit-узлов).
---
## §3. apply_vpn_heartbeat
### §3.1 Шаги применения
```
apply_vpn_heartbeat(state, op):
// Шаг 1. Валидация инвариантов VH-1..VH-9.
require all VH-1..VH-9 pass
// Шаг 2. Вычисление credit.
rate_nj_per_ms = RATE_NJ_PER_MILLISECOND = 1 // 0.001 Ɉ/sec = 1 nɈ/ms
credit_nj = op.duration_ms × rate_nj_per_ms // integer arithmetic per [I-9]
// Шаг 3. State mutation:
state.account_table[op.sender_account_id].balance_nj += credit_nj
state.account_table[op.sender_account_id].vpn_credited_seconds_x1000_in_window[op.window_index] += op.duration_ms
state.node_table[op.exit_node_id].vpn_traffic_served_seconds += op.duration_ms / 1000
// Шаг 4. Emission supply update:
state.supply_nj += credit_nj // эмиссия Ɉ за VPN-работу
// Шаг 5. Chain length:
state.account_table[op.sender_account_id].account_chain_length += 1
state.account_table[op.sender_account_id].frontier_hash = H_canon(op)
```
### §3.2 Возможные результаты
- **OK:** state mutated as above.
- **Err::AccountNotFound:** инвариант VH-2 нарушен.
- **Err::SignatureInvalid:** VH-5.
- **Err::WindowOutOfBounds:** VH-3.
- **Err::ExitNodeOffline:** VH-4 / VH-9.
- **Err::DurationOutOfBounds:** VH-6 / VH-7.
- **Err::WindowQuotaExceeded:** VH-8.
---
## §4. Эмиссия Ɉ за VPN — экономический анализ
### §4.1 Параметры
| Параметр | Значение | Обоснование |
|---|---|---|
| `RATE_NJ_PER_MILLISECOND` | `1 nɈ/ms` (= `0.001 Ɉ/sec`) | Соответствует текущему MVP-coordinator. Закрепляется как initial baseline. |
| `MIN_HEARTBEAT_DURATION_MS` | `4 000 ms` | Rate-limit per identity per heartbeat (см. Pass 18 critic). |
| `MAX_HEARTBEAT_DURATION_MS` | `30 000 ms` | Защита от backdate flooding. |
| `WINDOW_DURATION_MS` | `τ₁ = 30 000 ms` (один TimeChain window) | Используется как cap quota per window. |
| `MAX_HEARTBEATS_PER_WINDOW_PER_SENDER` | `WINDOW_DURATION_MS / MIN_HEARTBEAT_DURATION_MS = 7` | Pre-mainnet, может быть скорректировано. |
### §4.2 Maximum emission rate per validator
Допущения: один honest sender накапливает `WINDOW_DURATION_MS = 30 000 ms = 30 sec` credit per τ₁.
```
emission_per_sender_per_τ₁ = 30 000 nɈ = 0.03 Ɉ
emission_per_sender_per_day = 0.03 × (86400/30) = 86.4 Ɉ/day
emission_per_sender_per_year = 31536 Ɉ/year (theoretical maximum)
```
### §4.3 Sybil-attack defense
Sybil-attacker создаёт N fake accounts. Каждый требует:
- Открытие AccountRecord через первый Transfer (закрытие через [I-14] cost barrier — отдельный механизм)
- FN-DSA-512 signature на каждый heartbeat (computational cost не критичный, hardware-asymmetry attack)
- Активный VPN session на exit-узле (network bandwidth cost > VPN credit при честных rate)
**Деривация:** atacker экономика выгодна только если `revenue_per_account_per_day > cost_of_VPN_bandwidth_per_account_per_day`. Учитывая что VPN-bandwidth сам стоит более чем 0.001 Ɉ/sec (текущий TC price + cloud bandwidth pricing), attack экономически нерентабельна.
### §4.4 Соответствие [I-13] Deflationary sink
VpnHeartbeat — **value operation**, не burn. Эмиссия Ɉ за работу validator-а покрывается **существующими** механизмами sink ([I-13]):
- NicknameBid burn
- Anchor fee burn
- ChangeKey re-issuance cost
Баланс между emission (VpnHeartbeat) и burn (NicknameBid + Anchor) — open параметр для economic equilibrium (см. Pass 22 critic — equilibrium analysis).
---
## §5. AccountRecord — расширение поля
```
AccountRecord ::= {
... // existing fields per v35.25
vpn_credited_seconds_x1000: u64 // суммарно за всю жизнь аккаунта
vpn_credited_seconds_x1000_in_window: u32 // per current window (reset per τ₁)
vpn_last_window_index: u64 // последнее окно где аккаунт активен
}
```
**Дополнительно 24 байта на AccountRecord.**
### §5.1 NodeRecord — расширение поля
```
NodeRecord ::= {
...
vpn_traffic_served_seconds: u64 // суммарно за всю жизнь узла
}
```
**Дополнительно 8 байт на NodeRecord.**
### §5.2 [I-14] State lifecycle compliance
`vpn_credited_seconds_x1000_in_window` сбрасывается на каждой границе τ₁. Это **не persistent growth** — поле bounded, переиспользуется. ✅
`vpn_traffic_served_seconds` растёт линейно с активностью узла. Для exit-узлов это **expected behavior** (per [I-14] путь cost-based: NODE_REGISTRATION_STAKE покрывает storage cost для NodeRecord). ✅
---
## §6. Test vectors (binding для conformance)
### §6.1 Vector V6-1: канонический preimage
```
sender_account_id = 2f8714b236118011647ec51d0ca6ad40d286bec7 (20B)
window_index = 1779120000 (u64 LE)
exit_node_id = b17dd919772d4268a7249b866b92d12b... (32B placeholder)
duration_ms = 5000 (u32 LE)
preimage_hex = 6d742d76706e2d68656172746265 6174 2d 76 31 [domain]
| 2f8714b236118011647ec51d0ca6 ad40 d2 86 bec7 [sender]
| 00ed2bc564010000 [window le8]
| b17dd919772d4268a7249b866b92 d12b ... [exit_id]
| 88130000 [duration le4]
preimage_size = 83 bytes
```
(Конкретный hex значения вычисляются при первом deploy implementation — это placeholder.)
### §6.2 Vector V6-2: balance delta integer arithmetic
```
duration_ms = 5000 ms
rate_nj_per_ms = 1
credit_nj = 5000
Pre-state: account.balance_nj = 0
Post-state: account.balance_nj = 5000
```
### §6.3 Vector V6-3: rejected — duration слишком большая
```
duration_ms = 35000
Expected: Err::DurationOutOfBounds (VH-6 violation, 35000 > MAX = 30000)
```
---
## §7. Migration path Phase 2 → Phase 3
### §7.1 Координатор Moscow остаётся
После принятия opcode `0x06` координатор `mt-vpn-balance.service` продолжает работать как **indexer**:
- Принимает heartbeat от Android клиента через legacy REST API (Ed25519 signed).
- Транслирует в `VpnHeartbeat` opcode → sends в локальный montana-node mempool.
- Опционально: возвращает клиенту transaction hash как proof что operation попала в mempool.
### §7.2 Android client изменения
После принятия opcode:
- BIP39 seed → FN-DSA-512 keypair (вместо Ed25519). Требует Falcon JNI bridge.
- Heartbeat body расширяется: добавляется `signature` (FN-DSA-512, 666B) и поля `window_index`, `exit_node_id`, `duration_ms`.
- Координатор Moscow становится транспортом, не authority.
### §7.3 Backwards-compatible переходный период
В переходный период:
- Координатор принимает **обе** формы heartbeat: legacy (Ed25519) и новую (FN-DSA-512).
- Legacy heartbeats не cemented в TimeChain — only credited через координатор.
- Pre-mainnet принцип: при запуске mainnet legacy heartbeats **прекращают** работать, требуется update приложения.
---
## §8. Adversarial gates check (Gate 0..15)
### Gate 0 — Global invariants
- [I-1] PQ-secure: ✅ FN-DSA-512 (Falcon, lattice-based, NIST PQ winner)
- [I-2] Public financial layer: ✅ balance + duration_ms public
- [I-3] Determinism: ✅ integer arithmetic only, no floats
- [I-4] TimeChain independence: ✅ VpnHeartbeat зависит от TimeChain (window_index), не наоборот
- [I-5] Commodity hardware: ✅ FN-DSA-512 на ARM64 ~5-10ms sign, приемлемо
- [I-6] Regulatory compat: ✅ public balance, no privacy mixer
- [I-7] Minimal crypto surface: ✅ переиспользует FN-DSA-512 уже в спеке
- [I-8] Network-bound unpredictability: **N/A** (VpnHeartbeat не использует seed/lottery)
- [I-9] Bit-exact arithmetic: ✅ integer-only, test vectors §6
- [I-13] Deflationary sink: ✅ эмиссия покрывается existing burn механизмами
- [I-14] State lifecycle: ✅ window-window поле сбрасывается, persistent поля cost-bounded
### Gate 1 — Control plane separation
`VpnHeartbeat` — value operation (двигает balance, эмиссию). Не power. ✅
### Gate 2 — Temporal anchor audit
`window_index` ограничен `current_window ± 1` (VH-3). Нет precompute attack. ✅
### Gate 9 — Expiry math
Heartbeat не имеет expiry. Quota window-based (VH-8). ✅
### Gate 10 — Hardware asymmetry
VpnHeartbeat не использует canonical seed → grinding-attack не применим. ✅
### Gate 13 — Invariant enumeration
§2.2 содержит exhaustive list VH-1..VH-9. ✅
### Gate 14 — State lifecycle
§5.2 — все persistent поля либо bounded, либо cost-protected. ✅
### Gate 15 — Post-edit completeness
При принятии патча требуется:
- Удалить упоминания координатора Moscow как authoritative в Android `Внешний-аудит/05-Состояние-и-хранилище.md`
- Обновить mt-account реализацию: добавить apply_vpn_heartbeat
- Обновить mt-conformance test vectors
- Bump VERSION.md и Code/VERSION.md
---
## §9. Roadmap implementation
| Этап | Длительность | Описание |
|------|--------------|----------|
| **M-VPN-2 Phase A** | 1 week | Spec patch finalized + critic 22-pass audit |
| **M-VPN-2 Phase B** | 2 weeks | `mt-account::apply_vpn_heartbeat` + 50+ unit tests |
| **M-VPN-2 Phase C** | 1 week | `montana-node` accepts opcode в mempool + cemented |
| **M-VPN-2 Phase D** | 1 week | `mt-conformance` test vectors из §6 |
| **M-VPN-3 Phase A** | 1 week | Android pqcrypto-falcon JNI bridge research + prototype |
| **M-VPN-3 Phase B** | 2 weeks | Production Android FN-DSA-512 integration |
| **M-VPN-3 Phase C** | 1 week | Coordinator Moscow translates legacy → opcode |
| **M-VPN-4** | 1 week | Migration cutover (legacy heartbeats deprecated) |
**Total:** ~9 weeks. Зависит от availability `pqcrypto-falcon` Android bindings.
---
## §10. Status
**Spec patch:** draft pending critic review.
**Implementation:** **TODO** (M-VPN-2/3 milestones not started).
**Acknowledged dependency:** Falcon-512 Android JNI is non-trivial — alternative migration path = SLH-DSA (SPHINCS+) если pqcrypto-falcon не пригоден.
После принятия патча архитектором + критиком — bump спеки v35.25.0 → v35.26.0 с заменой §11.1 type byte registry и добавлением §11.2-VpnHeartbeat.