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

17 KiB
Raw Blame History

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.