17 KiB
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:
-
Heartbeat начисление Ɉ происходит вне консенсуса. Текущий Rust coordinator на узле Moscow ведёт
state.jsonавторитарно, не реплицируется через TimeChain, не cemented. F-4 из аудит-пакетаAndroid/Внешний-аудит/07-Известные-ограничения.md. -
Sybil-resistance работает только через Ed25519 TOFU pinning. Это нарушает [I-1] PQ-secure (Shor на curve25519), хотя сейчас acceptable как Phase 2 stopgap. CF-Phase2-1.
-
Migration к полному консенсусу требует нового opcode и operation type.
Этот патч описывает opcode 0x06 VpnHeartbeat — нормативно. После принятия:
mt-accountреализуетapply_vpn_heartbeatmontana-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
- VH-1 type byte:
type_byte == 0x06. - VH-2 sender exists:
AccountTable[sender_account_id]существует иaccount_chain_length ≥ 1. - VH-3 window bounds:
current_window − 1 ≤ window_index ≤ current_window(heartbeat принимается только в текущем или предыдущем окне). - VH-4 exit-node validity:
NodeTable[exit_node_id]существует и имеетis_active = trueиnode_role ∈ {exit_node, dual_role}. - VH-5 signature verify:
FN-DSA-512.Verify(AccountTable[sender_account_id].suite_pubkey, preimage, operator_signature) == true. - VH-6 duration upper bound:
duration_ms ≤ MAX_HEARTBEAT_DURATION_MS = 30 000ms (защита от backdate flooding). - VH-7 duration lower bound:
duration_ms ≥ MIN_HEARTBEAT_DURATION_MS = 4 000ms (защита от heartbeat spam внутри окна). - VH-8 monotonic nonce:
duration_msнакопляется черезAccountTable[sender].vpn_credited_seconds_in_window[window_index]— heartbeat принимается только если суммарная сумма за окно ≤WINDOW_DURATION_MS = τ₁. - 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).
- Транслирует в
VpnHeartbeatopcode → 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.