montana/Монтана-Протокол/Внешний аудит/claude-opus-4-7-1m_2026-04-28_T2023.md

639 lines
68 KiB
Markdown
Raw Normal View History

# Внешний аудит протокола Montana — слои M0…M5
**Аудитор:** Claude Opus 4.7 (1M context) — `claude-opus-4-7[1m]`
**Дата сессии:** 2026-04-28, начало 20:23 локального времени
**Доверие:** нулевое к любым `.md` документам в `Протокол/` (включая `AUDIT.md`, `VERSION.md`, `ROADMAP.md`, `SPEC_DEVIATIONS.md`); полное к исходному коду в `Протокол/Код/montana-node` и его рабочей области, плюс к выводу собственных сборок и тестов.
**Корень рабочей области:** `/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код`
---
## 1. Сводная оценка
| Слой | Crate(ы) | LOC | Тесты прогнаны | Findings (HIGH/MED/LOW/INFO) | Оценка |
|------|----------|-----|----------------|------------------------------|--------|
| **M1** криптография + восстановление | mt-codec, mt-crypto, mt-crypto-native (Rust+C), mt-mnemonic | ~2 050 + 524 C | PASS — см. §11 | 0/0/2/2 | **9.0/10** |
| **M2** основа состояния | mt-merkle, mt-genesis, mt-state, mt-timechain | ~1 820 | PASS (60 определённость) | 0/0/2/1 | **9.0/10** |
| **M3** apply_proposal | mt-account | ~2 560 | PASS (89 модульных + 35 определённость) | 0/0/1/1 | **9.0/10** |
| **M4** консенсус | mt-lottery, mt-consensus, mt-entry | ~3 860 | PASS | 0/1/3/2 | **8.5/10** |
| **M5** персистентность | mt-store | ~960 | PASS | 0/0/1/1 | **9.0/10** |
| **узел** интеграция (M5+) | montana-node | ~2 190 | сборка PASS (запуск не делал — singleton process с VDF τ₁ ≥ 60 с) | 0/1/2/1 | **8.0/10** |
| **общий** SSOT, deps, build | весь workspace | — | `cargo audit` чисто, `cargo check` чисто | 0/1/1/0 | **9.0/10** |
**ИТОГ — уровень безопасности: 8.7 / 10.**
Аудит подтверждает: реализация M1…M5 готова к внешнему security-аудиту специализированной фирмой по консенсусным протоколам с post-quantum криптографией. Открытых блокеров кода — нет. Открытые задачи относятся к (а) deployment-ceremony перед mainnet (Genesis bootstrap pubkeys), (б) интеграционному слою montana-node при переходе из singleton в multi-node (часть полей `ProposalHeader` сейчас placeholder), (в) расхождениям между AUDIT.md и реальным состоянием кода (документация AUDIT.md устарела).
---
## 2. Метод и принцип нулевого доверия
Каждое утверждение ниже подкреплено либо ссылкой на конкретный файл и строку из исходного кода, либо логом запущенной мной команды. Я НЕ принимал на веру:
- содержимое `AUDIT.md` (использовал как сырой target scope);
- статусы в `SPEC_DEVIATIONS.md` (проверял реальное состояние кода);
- собственную память о протоколе из предыдущих сессий;
- утверждения в комментариях кода о соответствии спеке (сверял с другими местами в коде, c byte-exact testами, с RFC и FIPS векторами).
**Тройная сверка:**
- *спека ↔ код* — численные константы, размеры структур, имена доменных разделителей, формулы;
- *код ↔ исполнение* — прогон полного набора тестов в один поток (`.cargo/config.toml [build] jobs = 1` + `RUST_TEST_THREADS = 1`);
- *код ↔ внешние стандарты* — RFC 4231 (HMAC), RFC 5869 (HKDF), RFC 7914 (PBKDF2), FIPS 180-4 (SHA-256), FIPS 203 (ML-KEM-768), FIPS 204 (ML-DSA-65 ξ ∈ B³², deterministic Sign Algorithm 2).
Из соображений охраны машины автора (см. `feedback_single_core_tests.md`) все сборки и тесты выполнялись в одном процессе и одном потоке — это требование зашито в `[.cargo/config.toml](Код/.cargo/config.toml)`.
Динамическую инспекцию production deployment на серверах Frankfurt/Moscow я **не** выполнял: согласно `MEMORY.md → SeaFare Montana`, оба сервера обслуживают SeaFare и Cascade-VPN, не реализацию TimeChain. Production deployment montana-node ещё не существует.
---
## 3. Структура проверенной кодовой базы
15 крейтов, единая рабочая область Cargo. Ключевые зависимости пинованы точно через `[workspace.dependencies]` и `=X.Y.Z`:
```toml
sha2 = =0.10.9
zeroize = =1.8.1
getrandom = =0.2.15
```
Внешние FFI-зависимости только в `mt-crypto-native` ([crates/mt-crypto-native/Cargo.toml](Код/crates/mt-crypto-native/Cargo.toml)):
- `openssl-src = =300.5.5+3.5.5` — FIPS 140-3 валидированный OpenSSL 3.5.5 LTS, vendored как исходник, собирается через `cc::Build`;
- `libc = =0.2.169` — POSIX `mlock`/`munlock`/`signal`;
- `serde`, `serde_json` — в крейте только для парсинга NIST ACVP fixtures (изолированы от production пути).
**Toolchain:** Rust stable, минимум 1.70, зафиксирован в [rust-toolchain.toml](Код/rust-toolchain.toml). Профиль release: `lto = "fat"`, `codegen-units = 1`, `panic = "abort"`, `overflow-checks = true` ([Cargo.toml:17-22](Код/Cargo.toml)) — корректные параметры для криптографически-критичного бинарника.
`cargo audit`: 41 dependency, **0 уязвимостей, 0 предупреждений**. Запущено мной 2026-04-28, advisory DB загружена 2026-04-28T15:17:23Z.
---
## 4. Фаза M1 — криптография и восстановление identity
### 4.1 Предмет аудита
| Компонент | Файл | Lines | Назначение |
|-----------|------|-------|------------|
| `mt-codec` | [crates/mt-codec/src/lib.rs](Код/crates/mt-codec/src/lib.rs) | 351 | трейт CanonicalEncode + регистр domain-разделителей (32 штуки) |
| `mt-crypto` (Rust shim) | [crates/mt-crypto/src/lib.rs](Код/crates/mt-crypto/src/lib.rs) | 662 | публичный API (`PublicKey`, `SecretKey`, `Signature`, `Mlkem*`, sign/verify, hash), heap+mlock+zeroize secret hygiene |
| `mt-crypto-native` (FFI Rust) | [crates/mt-crypto-native/src/lib.rs](Код/crates/mt-crypto-native/src/lib.rs) | 49 | extern "C" объявления, константы размеров |
| `mt-crypto-native` (C wrapper) | [crates/mt-crypto-native/csrc/mt_crypto.c](Код/crates/mt-crypto-native/csrc/mt_crypto.c) | 457 | тонкая обёртка над OpenSSL EVP API (KeyGen, Sign, Verify, self_test) |
| `mt-crypto-native` (C header) | [crates/mt-crypto-native/csrc/mt_crypto.h](Код/crates/mt-crypto-native/csrc/mt_crypto.h) | 67 | C API + 13 status codes |
| `mt-mnemonic` | [crates/mt-mnemonic/src/](Код/crates/mt-mnemonic/src/) | 996 | 24-слов мнемоника, PBKDF2-HMAC-SHA-256 (iter = 2²⁰), HKDF-Expand per-role вывод, привязанный 2048-словарь |
### 4.2 Сильные стороны
**Чистая трёхслойная архитектура hybrid Rust + C** соответствует production-pattern (Solana, Bitcoin Core, Tendermint). Все восемь блоков `unsafe` имеют корректные комментарии `// SAFETY:` (lines 164, 192, 235, 276, 293, 364, 383 в `mt-crypto/src/lib.rs`, line 503 в `montana-node/src/commands/start.rs`):
- 4 блока обращений к FFI: контракт размеров явно описан, OpenSSL EVP convention `(void*)seed/sk/pk` — backwards compat cast без mutation, описано в [csrc/mt_crypto.c:61-67](Код/crates/mt-crypto-native/csrc/mt_crypto.c);
- 4 блока `mlock`/`munlock` для secret pages: best-effort с graceful fallback на encrypted swap (FileVault/LUKS) при `RLIMIT_MEMLOCK exceeded`.
**Secret hygiene на исключительно высоком уровне** ([mt-crypto/src/lib.rs:130-204, 331-403](Код/crates/mt-crypto-native/csrc/mt_crypto.c)):
- `SecretKey(Box<[u8; SECRET_KEY_SIZE]>)` — heap-allocation, `!Clone`, `!Copy`, `!PartialEq` (исключение timing leak через `==`);
- `Drop` имплементация: `zeroize` + `munlock`;
- `from_array(mut bytes)` принимает массив by-value, копирует в heap через `alloc_locked_secret_box`, **зануляет** stack-копию через `bytes.zeroize()` ([line 139](Код/crates/mt-crypto/src/lib.rs));
- FFI `keypair_from_seed` пишет напрямую в locked heap-страницу (никаких stack temporary buffers с secret bytes);
- При ошибке FFI `sk_box.zeroize()` вызывается явно перед drop ([line 248](Код/crates/mt-crypto/src/lib.rs)).
**Аналогичная защита для `MlkemSecretKey`** (ML-KEM-768, 2400 байт).
**13/13 security invariants Pass 17** ([crates/mt-crypto/tests/security_invariants.rs](Код/crates/mt-crypto/tests/security_invariants.rs)): trait-based проверки `!Clone`, `!PartialEq`, heap-allocation, наличие Drop, отсутствие `println!`/`log::*` на secret bytes — выполняются в CI как regression-detection.
**FIPS-conformance в C-обёртке** ([csrc/mt_crypto.c](Код/crates/mt-crypto-native/csrc/mt_crypto.c)):
- ML-DSA-65 KeyGen: `OSSL_PKEY_PARAM_ML_DSA_SEED` ([line 113](Код/crates/mt-crypto-native/csrc/mt_crypto.c)) — FIPS 204 §3.1 ξ ∈ B³² (32 байта);
- ML-KEM-768 KeyGen: `OSSL_PKEY_PARAM_ML_KEM_SEED` ([line 130](Код/crates/mt-crypto-native/csrc/mt_crypto.c)) — FIPS 203 §6.1 d ‖ z (64 байта);
- Sign: `OSSL_SIGNATURE_PARAM_DETERMINISTIC = 1` ([line 222](Код/crates/mt-crypto-native/csrc/mt_crypto.c)) — FIPS 204 Algorithm 2 deterministic вариант (критично для консенсус-определённости [I-3]);
- Sign with context: `ctx_len > 255` отвергается ([line 279](Код/crates/mt-crypto-native/csrc/mt_crypto.c)) — FIPS 204 ограничение;
- Все cleanup через `goto cleanup` с обязательными `EVP_PKEY_free` + `EVP_PKEY_CTX_free` + `EVP_MD_CTX_free` (ни одной утечки).
**Покрытие RFC и FIPS векторами:**
- HMAC ([crates/mt-mnemonic/src/hmac.rs](Код/crates/mt-mnemonic/src/hmac.rs)): RFC 4231 cases 1, 2, 4, 6 (включая длинный ключ > BLOCK_SIZE);
- HKDF-Expand ([crates/mt-mnemonic/src/hkdf.rs](Код/crates/mt-mnemonic/src/hkdf.rs)): RFC 5869 §A.1, §A.2, §A.3;
- PBKDF2 ([crates/mt-mnemonic/src/pbkdf2.rs](Код/crates/mt-mnemonic/src/pbkdf2.rs)): RFC 7914 §11 vector 1 (c = 1) + vector 2 (c = 80 000) + CryptoJS (c = 4 096);
- SHA-256 ([crates/mt-crypto/src/lib.rs:516-523](Код/crates/mt-crypto/src/lib.rs)): FIPS 180-4 §B.1 «abc»;
- ML-DSA-65 + ML-KEM-768: NIST ACVP-Server fixtures ([crates/mt-crypto-native/tests/fixtures/nist_acvp/](Код/crates/mt-crypto-native/tests/fixtures/nist_acvp/)) — 4 файла:
- `ml_dsa_65_keygen.json`, `ml_kem_768_keygen.json`,
- `ml_dsa_65_siggen_det_external_pure_all15.json` (15 SigGen cases с context bytes 0…255),
- `ml_dsa_65_siggen_det_external_pure_empty_ctx.json` (empty ctx case);
- `mt_self_test()` ([csrc/mt_crypto.c:387-457](Код/crates/mt-crypto-native/csrc/mt_crypto.c)): determinism, sign/verify roundtrip, sign-determinism cross-check, bit-flip rejection, ML-KEM determinism.
**Привязка hash-композиции к domain-разделителям** ([mt-crypto/src/lib.rs:85-93](Код/crates/mt-crypto/src/lib.rs)): функция `hash(domain, parts)` всегда вставляет NUL-байт между доменом и payload — закрывает finding P1 (prefix-collision protection) предыдущего внешнего аудита.
**Wordlist binding fingerprint** ([crates/mt-mnemonic/src/wordlist.rs:14-31](Код/crates/mt-mnemonic/src/wordlist.rs)):
- `WORDLIST_FINGERPRINT = SHA-256(WORDLIST_RAW)` зашит как константа;
- `init_wordlist()` ассертит совпадение, лексикографическую сортировку и ровно 2048 строк — не проходит инициализация при corruption встроенного wordlist;
- `pub fn word_index(word) -> Option<u16>` через `binary_search` (детерминирована).
**5 KAT-векторов в [crates/mt-mnemonic/tests/keygen_vectors.rs](Код/crates/mt-mnemonic/tests/keygen_vectors.rs)** покрывают полный цикл `entropy → mnemonic → master_seed → per-role HKDF → keypair` для ML-DSA-65 (3 случая) + ML-KEM-768 (2 случая). Это закрывает [C-4] End-to-End Observable Closure.
### 4.3 Слабые стороны
| ID | Severity | Описание | Локация |
|----|----------|----------|---------|
| L-M1-1 | LOW | `mt-crypto-native::MLDSA65_SIGNATURE_SIZE = 3309` — дубликат `mt-crypto::SIGNATURE_SIZE`; обоснован для FFI boundary, но по строгому [C-1] SSOT может разъехаться при обновлении. | [crates/mt-crypto-native/src/lib.rs:7](Код/crates/mt-crypto-native/src/lib.rs) |
| L-M1-2 | LOW | `keypair()` тест-only функция использует `getrandom::getrandom().expect()``expect()` оправдан (OS CSPRNG fallback недоступности — environmental failure, не protocol invariant). Тем не менее, в `lib.rs` lib-кода это формально `.expect()`. Закрыт `cfg(any(test, feature = "testing"))`. | [crates/mt-crypto/src/lib.rs:270](Код/crates/mt-crypto/src/lib.rs) |
| INFO-M1-3 | INFO | TODO для `mt-telemetry` интеграции (F-5 closure pending) — runtime warning при `mlock` failure. Текущая реализация silently fallback-ит на non-locked Box. Не security gap (encrypted swap покрывает) — но видимость для оператора отложена. | [crates/mt-crypto/src/lib.rs:183-189](Код/crates/mt-crypto/src/lib.rs) |
| INFO-M1-4 | INFO | M1-крейт `keypair_from_seed_mlkem` не покрыт собственными NIST KAT для Encapsulate/Decapsulate (только KeyGen). Fixtures для encapDecap есть в репо, но тест не интегрирован. Не блокер: ML-KEM Encapsulate/Decapsulate понадобится в M6+ application layer. | (отсутствие теста) |
### 4.4 Рекомендации по M1
1. **(LOW)** перевести `mt-crypto-native::MLDSA65_SIGNATURE_SIZE` на `pub use mt_crypto::SIGNATURE_SIZE` либо явно задокументировать что это reverse-dependency (мне видится первое предпочтительнее);
2. **(INFO)** добавить ML-KEM-768 Encapsulate/Decapsulate KAT при разработке M6+ messaging слоя;
3. **(INFO)** ускорить `mt-telemetry` интеграцию для observability `mlock` failure.
### 4.5 Оценка M1: **9.0 / 10**
Это исключительно сильный криптографический слой. Hybrid Rust+C через own thin FFI wrapper — правильный архитектурный выбор по [C-6], учитывая отсутствие production-grade pure-Rust имплементаций ML-DSA / ML-KEM в 2026-Q2. Production audit firms (NCC Group, Trail of Bits, Cure53) могут принимать этот код в работу немедленно.
---
## 5. Фаза M2 — основа состояния (state foundation)
### 5.1 Предмет аудита
| Компонент | Файл | Lines | Назначение |
|-----------|------|-------|------------|
| `mt-merkle` | [crates/mt-merkle/src/lib.rs](Код/crates/mt-merkle/src/lib.rs) | 770 | Sparse Merkle Tree глубины 256, кэш `empty_internal[0..256]`, `prove`/`verify_proof` |
| `mt-genesis` | [crates/mt-genesis/src/lib.rs](Код/crates/mt-genesis/src/lib.rs) | 354 | `ProtocolParams` SSOT (4094 байт), Genesis Decree, `is_genesis_bootstrap_finalized` |
| `mt-state` | [crates/mt-state/src/lib.rs](Код/crates/mt-state/src/lib.rs) | 648 | AccountTable (2059 байт записи) + NodeTable (2098) + CandidatePool (2082), `compute_state_root` |
| `mt-timechain` | [crates/mt-timechain/src/lib.rs](Код/crates/mt-timechain/src/lib.rs) | 348 | `vdf_step`/`vdf_verify`, `next_d` (Adaptive D feedback), `cemented_bundle_aggregate` (canonical [I-8] binding) |
### 5.2 Сильные стороны
**Sparse Merkle Tree (SMT) реализация** ([mt-merkle/src/lib.rs](Код/crates/mt-merkle/src/lib.rs)):
- `BTreeMap<[u8; 32], Hash32>` для leaves (детерминированная итерация);
- `OnceLock<[Hash32; 257]>` кэш `empty_internal` (вычисляется один раз);
- `compute_subtree_root`**итеративная** реализация через explicit work-stack ([lines 82-138](Код/crates/mt-merkle/src/lib.rs)) — закрывает stack-overflow угрозу для embedded targets с малым stack;
- `Cell<Option<Hash32>>` invalidate-on-mutate caching root (О(1) при последовательных read'ах) — закрывает M2-5 perf finding;
- **Корректная idempotency**: `insert` того же leaf не invalidate'ит cache ([lines 187-193](Код/crates/mt-merkle/src/lib.rs));
- `try_empty_internal` для untrusted input vs `empty_internal` (с явным assertion для programmer error) — defensive engineering.
- 4 reject test'а для tampered proof: mutated sibling, mutated leaf_value, wrong root, wrong absence claim.
**ProtocolParams SSOT** ([mt-genesis/src/lib.rs](Код/crates/mt-genesis/src/lib.rs)):
- `OnceLock` singleton для immutability;
- 22 поля с явным byte-layout, `PARAMS_ENCODED_SIZE = 4094` сверяется тестом;
- `is_genesis_bootstrap_finalized()` + явный `#[ignore]` тест `bootstrap_keypairs_finalized` фиксируют статус «pre-Genesis-ceremony»;
- 16 mutation-detection тестов проверяют что любое изменение поля меняет `compute_genesis_state_hash`.
**Состояние (`mt-state`)**: три таблицы (AccountTable, NodeTable, CandidatePool) с одинаковым pattern: `BTreeMap` + `SparseMerkleTree`. `compute_state_root` через SHA-256 с domain separator `mt-state-root` ([crates/mt-state/src/lib.rs:272-281](Код/crates/mt-state/src/lib.rs)). `is_active(node, W, τ₂)` через `saturating_sub` ([line 295](Код/crates/mt-state/src/lib.rs)) — защита от underflow в genesis окне.
**TimeChain VDF + Adaptive D + cemented_bundle_aggregate** ([mt-timechain/src/lib.rs](Код/crates/mt-timechain/src/lib.rs)):
- `vdf_step(prev, d) = SHA-256^d(prev)`, `d=0` → identity (no-op);
- `vdf_verify` — re-computes byte-exact;
- `next_d` — integer-permille арифметика per [I-9]: `>= 950 permille → ×103/100`, `<= 850 → ×97/100`, иначе dead-zone;
- `checked_mul` overflow-detection через panic с descriptive message ([lines 49-58, 62-69](Код/crates/mt-timechain/src/lib.rs)) — корректный engineering halt при non-attacker triggered overflow (median_ratio derived канонически из cemented set, ≈ 1.5M лет до достижения горизонта);
- **`cemented_bundle_aggregate`** ([lines 90-111](Код/crates/mt-timechain/src/lib.rs)): три ветви (W < 2 0×32 genesis, |cemented| = 0 `mt-bc-aggregate-empty` domain, иначе `mt-bc-aggregate` с canonical sorted node_ids + window). **Signature и op_hashes ИСКЛЮЧЕНЫ из input** закрытие grinding surface через σ конструктивно (нет в type signature функции).
- 19+ тестов покрывают все ветви + grinding resistance: `aggregate_order_independent`, `aggregate_independent_of_signature_type`.
**0 unsafe, 0 panic в production paths, 0 HashMap, 0 f32/f64, 0 SystemTime, 0 RNG в lib коде.** Проверено через wide-scan grep по всем M2 крейтам.
### 5.3 Слабые стороны
| ID | Severity | Описание | Локация |
|----|----------|----------|---------|
| L-M2-1 | LOW | Комментарии в `mt-state` устарели после миграции на ML-DSA-65 (1952-байт pubkey vs прежний Falcon-512 666 байт). Lines 56 и 92 говорят `1043 bytes fixed` / `1027 bytes fixed`, а реальные `NODE_RECORD_SIZE = 2098` / `CANDIDATE_RECORD_SIZE = 2082`. Тесты проверяют actual size — код корректен, документация в комментариях устарела. | [crates/mt-state/src/lib.rs:56,92](Код/crates/mt-state/src/lib.rs) |
| L-M2-2 | LOW | Genesis bootstrap pubkeys / target_zero / genesis_content_data_hash хранят placeholder zeros до Genesis ceremony. **Не блокер аудита кода** (layout / encoding / SSOT корректны), но блокер mainnet deployment. Документировано через `is_genesis_bootstrap_finalized` + `#[ignore]` тест. | [crates/mt-genesis/src/lib.rs:100-103](Код/crates/mt-genesis/src/lib.rs) |
| INFO-M2-3 | INFO | `compute_subtree_root::Combine` использует `expect()` (lines 122-126 в `mt-merkle`) — но это invariant breach в вычислительной структуре, не reachable от внешнего входа. Acceptable. | [crates/mt-merkle/src/lib.rs:122-126](Код/crates/mt-merkle/src/lib.rs) |
### 5.4 Рекомендации по M2
1. **(LOW)** обновить комментарии `mt-state/src/lib.rs:56,92` на корректные размеры (`2098 bytes fixed`, `2082 bytes fixed`) либо удалить — тесты `node_record_encoded_size` / `candidate_record_encoded_size` уже фиксируют корректные значения;
2. **(LOW)** оформить план Genesis ceremony в отдельный milestone (как заявлено в AUDIT.md, но без конкретного разработанного процесса).
### 5.5 Оценка M2: **9.0 / 10**
Solid M2 layer с очень чистой реализацией SMT и корректным [I-8] cemented_bundle_aggregate. Doc-drift в `mt-state` — единственная подозрительность.
---
## 6. Фаза M3 — apply_proposal layer
### 6.1 Предмет аудита
`mt-account` ([crates/mt-account/src/lib.rs](Код/crates/mt-account/src/lib.rs)) — 2 556 строк. Включает 4 user-операции:
- Transfer (0x02), ChangeKey (0x03), Anchor (0x04), TransferActivation (0x0A);
- `validate_*` + `apply_*` пары;
- `op_hash` через `mt-op` domain separator;
- `settle_window` сортирует cemented ops по `op_hash` lex asc;
- `apply_proposal` orchestrates Steps 2 (emission), 3.5 (chain_length++), 3.6 (checkpoint rotation), 4 (state_root); Steps 1 / 3a / 3b делегированы M4 mt-entry;
- `apply_emission` зачисляет константу `EMISSION_moneta = 13 × 10⁹ nɈ` operator-у winner-узла;
- `reward_moneta(params) = params.emission_moneta`, `supply_moneta(W) = E × (W+1)` closed-form;
- `build_genesis_state` для bootstrap.
### 6.2 Сильные стороны
**Полное покрытие validate→apply разделения**:
- Каждый opcode имеет `validate_*` с явным набором проверок (`InvalidPrevHash`, `DuplicateAccount`, `AccountNotFound`, `ReceiverNotActive`, `ReceiverAlreadyExists`, `InvalidBinding`, `InvalidSignature`, `InsufficientBalance`, `SelfTransfer`, `ZeroAmount`, `UnsupportedSuite`, `ActivationCooldownNotElapsed`);
- `validate_transfer_activation` ([lines 244-286](Код/crates/mt-account/src/lib.rs)) проверяет binding `receiver == derive_account_id(suite_id, receiver_pubkey)` и cooldown `[I-15]` (1 активация на sender за τ₂);
- `validate_change_key` явно требует подпись **старым** ключом ([lines 288-303](Код/crates/mt-account/src/lib.rs));
- `ValidationContext` обязательная обёртка `(current_window, tau2_windows)` — caller не забудет передать context.
**Все `panic!` в `apply_*` defense-in-depth**:
5 вызовов `panic!` ([lines 360, 378, 386, 392, 428, 438, 442, 475, 479, 496, 500, 580, 599, 632](Код/crates/mt-account/src/lib.rs)) обёрнуты `checked_sub/add().unwrap_or_else(|| panic!("protocol invariant breach: ..."))` с описательными сообщениями. Они НЕ reachable от подписанного external input если caller вызывает `validate_*` first (заявлено в комментарии lines 346-348). Это appropriate engineering halt при implementation bug — `expect()` стиль, но с лучшими error-сообщениями. Соответствует политике `Код/CLAUDE.md → Code style → No unwrap/expect в lib коде, кроме случаев protocol violation с явным комментарием почему invariant не может быть нарушен`.
**`window_w_to_u32` cast helper** ([lines 358-365](Код/crates/mt-account/src/lib.rs)) — каст `u64 → u32` с явным описанием horizon (~8000 лет на τ₁ = 60 сек) и descriptive panic при достижении. AccountRecord использует `u32` для `window`-полей как encoded-size optimization; consensus-types — `u64`.
**`apply_emission`** ([lines 561-588](Код/crates/mt-account/src/lib.rs)):
- Genesis: window_w == 0 → return (нет W-1);
- Lookup `node.operator_account_id` через NodeTable + assert existence (protocol invariant);
- `checked_add` на operator.balance с descriptive panic при overflow на u128::MAX (≈ 10³⁸ nɈ — практически недостижимо);
- Зачислённая сумма = `params.emission_moneta` (SSOT через `mt-genesis`).
**`build_genesis_state`** ([lines 656-701](Код/crates/mt-account/src/lib.rs)):
- 1 bootstrap account (is_node_operator=true, balance=0, frontier = SHA-256(GENESIS, account_id));
- 1 bootstrap node (chain_length=1 для invariant DS-2 weighted_ticket);
- empty Candidate Pool;
- `genesis_state_root = compute_state_root(node_root, candidate_root, account_root)`.
**89 модульных тестов + 35 определённости** в `crates/mt-account/tests/determinism_invariants.rs`. Прогон PASS exit 0.
### 6.3 Слабые стороны
| ID | Severity | Описание | Локация |
|----|----------|----------|---------|
| L-M3-1 | LOW | `op_height: u32` overflow horizon — формально 4.29 млрд операций одного аккаунта. На 1B пользователей это 4 op/user в среднем; при горячих аккаунтах (узлы-операторы, exchange-аккаунты) horizon может быть достижим в multi-decade scale. Текущий panic корректен (encoded arithmetic horizon), но может потребовать `u64` миграцию ранее заявленного «8000 лет» горизонта. | [crates/mt-account/src/lib.rs:386,392,475,479,496,500](Код/crates/mt-account/src/lib.rs) |
| INFO-M3-2 | INFO | Чистое разделение `settle_window` отдельно от `apply_proposal` ([line 543](Код/crates/mt-account/src/lib.rs)) хорошо документировано (orchestration ordering invariant виден caller-у), но требует дисциплины caller-а — нет typestate enforcement. Documented в module-level comment + `Код/CLAUDE.md → [C-7]`. | [crates/mt-account/src/lib.rs:711-720](Код/crates/mt-account/src/lib.rs) |
### 6.4 Рекомендации по M3
1. **(INFO)** добавить «Sensitivity at high transaction rate» расчёт `op_height` overflow horizon в Storage Card по [I-14] методологии. Если horizon достигается раньше ~50 лет на горячем аккаунте — рассмотреть upgrade path в M6+;
2. **(INFO)** добавить debug_assert либо комментарий `// PRECONDITION: validate_* called` в начале каждого `apply_*` для большей очевидности validate→apply ordering для будущих контрибьюторов.
### 6.5 Оценка M3: **9.0 / 10**
Excellent apply-layer. Полное покрытие validate→apply, корректные checked-arithmetic защиты, settle_window отделён от apply_proposal с явным orchestration contract.
---
## 7. Фаза M4 — consensus mechanics
### 7.1 Предмет аудита
| Компонент | Файл | Lines | Назначение |
|-----------|------|-------|------------|
| `mt-lottery` | [crates/mt-lottery/src/lib.rs](Код/crates/mt-lottery/src/lib.rs) | 1 720 | BundledConfirmation R1/R2, VdfReveal R1/R2, `compute_endpoint`, `log2_q64` Q64.64, `weighted_ticket_node`, `quorum`, `determine_winner` argmin |
| `mt-consensus` | [crates/mt-consensus/src/lib.rs](Код/crates/mt-consensus/src/lib.rs) | 1 089 | ProposalHeader (3 722 байта, 17 полей), `validate_header`, `canonical_proposer`, `validate_winner`, `compute_control_set` |
| `mt-entry` | [crates/mt-entry/src/lib.rs](Код/crates/mt-entry/src/lib.rs) | 1 054 | NodeRegistration (5 344 байта, 0x11), `validate_noderegistration`, `candidate_vdf_init` ([I-8]), `selection_slots`, `apply_selection_event`, `apply_noderegistrations_batch` |
### 7.2 Сильные стороны
**`log2_q64` Q64.64 fixed-point integer log** ([crates/mt-lottery/src/lib.rs:282-355](Код/crates/mt-lottery/src/lib.rs)):
- degree-3 Remez minimax polynomial с binding coefficients B0..B3 (hardcoded);
- максимальная ошибка 2⁻¹⁰·⁶² (per spec);
- `endpoint == 0` → saturate to `u128::MAX` (handle SHA collision tail event);
- **M4-LOW-3 closure**: `unwrap_or([0; 16])` вместо `expect()` — absolute panic-free guarantee на impossible path;
- `saturating_sub` ([line 354](Код/crates/mt-lottery/src/lib.rs)) на minimax overshoot — log от числа ≥ 1 не может быть отрицательным;
- `LN2_Q64 = 0xB17217F7D1CF79AB` ≈ ln(2) × 2⁶⁴ — корректное значение (≈ 12 786 308 645 202 655 659).
**`quorum`** ([line 476-479](Код/crates/mt-lottery/src/lib.rs)): integer form `(67 × X + 99) / 100` через `saturating_mul/add` (M4-LOW-5 closure) — defense-in-depth от u64 overflow.
**`determine_winner`** ([line 434-448](Код/crates/mt-lottery/src/lib.rs)): canonical tie-break `(ticket asc, class asc, id lex asc)` — устраняет недетерминизм при tie вероятностью 2⁻¹²⁸.
**`validate_header`** ([crates/mt-consensus/src/lib.rs:108-151](Код/crates/mt-consensus/src/lib.rs)) — 6 структурных проверок: `fallback_depth ≥ 1`, `window_index = prev + 1` (через `checked_add` M4-LOW-4 closure), `protocol_version` monotone + ≤ local_max, proposer registered + Mldsa65, signature verify.
**`canonical_proposer`** ([crates/mt-consensus/src/lib.rs:185-203](Код/crates/mt-consensus/src/lib.rs)) — Lookback Leadership с явным **degraded-mode failsafe** документированным в комментарии M4-INFO-10: при empty W-2 cemented set → bootstrap_node_id (defense-in-depth liveness, не steady-state design).
**`apply_noderegistrations_batch`** ([crates/mt-entry/src/lib.rs:376-417](Код/crates/mt-entry/src/lib.rs)): incremental sort by `nr_sort_key` + canonical apply порядок; pending_count инкрементится после каждого insert для корректности `required_vdf_length` adaptive formula.
**Все consensus-critical hash-композиции через `domain::*` импорт** (не literal byte strings):
- `bundle_hash`, `reveal_hash`, `compute_endpoint`, `proposal_hash`, `nodereg_hash`, `candidate_vdf_init`, `selection_sort_key`, `nr_sort_key`.
**M4-1 closure** ([crates/mt-lottery/src/lib.rs:35-46, 117-129](Код/crates/mt-lottery/src/lib.rs)): explicit caps на `op_hashes.len() / reveal_hashes.len() <= u16::MAX` ДО encode (write_u16 cast) + debug_assert defense-in-depth.
**Все 0 unsafe, 0 panic в lib коде, 0 HashMap, 0 f32/f64, 0 SystemTime.** Контролируемые `expect()` присутствуют только в test-кодах (отмечено явно).
### 7.3 Слабые стороны
| ID | Severity | Описание | Локация |
|----|----------|----------|---------|
| M-M4-1 | MEDIUM | `BundleError::TooManyOps`/`TooManyReveals` cap = 65 535 (u16). На 1B пользователей при ~1000 узлах одно окно может содержать > 100K cemented op_hashes per node bundle. **Эскалация требует spec-patch на u16→u32 length prefix**. Документировано в комментарии line 122-124 как «SCALE NOTE (M6+ scaling concern)». **Это известный scale-limit, не сейчас-актуальный block.** | [crates/mt-lottery/src/lib.rs:122-129](Код/crates/mt-lottery/src/lib.rs) |
| L-M4-2 | LOW | `validate_winner` ([crates/mt-consensus/src/lib.rs:374-395](Код/crates/mt-consensus/src/lib.rs)) строго отвергает любой winner_id если cemented set W-1 пуст. Caller responsibility skip для genesis okon (M4-MED-2 documented). Это **не баг**, но усложняет caller контракт — нет typestate enforcement. | [crates/mt-consensus/src/lib.rs:374-395](Код/crates/mt-consensus/src/lib.rs) |
| L-M4-3 | LOW | `mt-entry::apply_selection_event` ([line 260-303](Код/crates/mt-entry/src/lib.rs)) — устанавливает `chain_length = 1` для нового узла (per spec invariant DS-2). Если caller вызывает функцию повторно для одного и того же селекшна (например через replay), `node_table.insert(node_record)` перезапишет существующий с chain_length, который мог быть выше. Защита: `pool.remove(&cand.node_id)` гарантирует что при втором вызове `selected` = empty (нет в Candidate Pool). Семантически корректно. | [crates/mt-entry/src/lib.rs:269-302](Код/crates/mt-entry/src/lib.rs) |
| INFO-M4-4 | INFO | `compute_control_set` (mt-consensus) включает в filter `c.cemented_window > previous_proposal_window AND ≤ current_window` — hard inclusive границы. Корректно для steady-state, но при reorg либо resync может потребовать дополнительных проверок (M6+ network-layer concern). | [crates/mt-consensus/src/lib.rs:248-267](Код/crates/mt-consensus/src/lib.rs) |
| INFO-M4-5 | INFO | `lottery_weight = chain_length_snapshot + seniority_bonus` ([crates/mt-lottery/src/lib.rs:257-259](Код/crates/mt-lottery/src/lib.rs)). `seniority_bonus = chain_length / 69`. 69 — magic number; обоснование в спецификации не прочитано (нулевое доверие к доке). Тестами зафиксировано — детерминированно. | [crates/mt-lottery/src/lib.rs:249-251](Код/crates/mt-lottery/src/lib.rs) |
### 7.4 Рекомендации по M4
1. **(MEDIUM)** запланировать M6+ spec-patch на u16 → u32 length prefix для bundle op_hashes/reveal_hashes; для текущего scope — добавить telemetry alert при `op_hashes.len() > 32 768` (early-warning перед достижением u16 cap);
2. **(LOW)** рассмотреть введение typestate wrapper `ValidatedHeader<H, W>` для force-обеспечения validate→apply ordering на уровне типов вместо документации;
3. **(INFO)** проверить документацию обоснования magic number 69 в seniority_bonus при будущей spec sweep.
### 7.5 Оценка M4: **8.5 / 10**
Очень сильный consensus слой. `log2_q64` integer log с Remez minimax — впечатляющая инженерия. M4-1 / M4-LOW-3 / M4-LOW-4 / M4-LOW-5 closures свидетельствуют что предыдущие audits были тщательны и closure качественное. Снижение на 0.5 за scale-concern u16 cap (известный, документированный, отложенный на M6+).
---
## 8. Фаза M5 — persistence
### 8.1 Предмет аудита
`mt-store` ([crates/mt-store/src/lib.rs](Код/crates/mt-store/src/lib.rs)) — 955 строк. Pure `std::fs` (без RocksDB/sled — minimum deps). Покрывает:
- `FsStore::open` + cleanup orphan `.tmp` файлов;
- save/load AccountTable / NodeTable / CandidatePool через canonical_encode/decode round-trip;
- Proposal archive (`proposals/{window:020}.bin`);
- Crash recovery (`meta_last_cemented.bin`, `verify_consistency`);
- Pruning (`prune_proposals_before(threshold)`).
### 8.2 Сильные стороны
**Atomic write pattern** ([lines 95-114](Код/crates/mt-store/src/lib.rs)): `write_atomic` пишет в `<name>.tmp` затем `fs::rename(tmp, final)`. POSIX rename(2) atomic per single filesystem — observers видят либо old либо new content, никогда partial.
**Cleanup orphan tmp на open()** ([lines 49-71](Код/crates/mt-store/src/lib.rs)) — closure M5-LOW-8 finding. Защита от накопления tmp-файлов после crashed write_atomic (process killed между fs::write tmp и fs::rename).
**Strict CorruptedLength check ДО decode** ([lines 162-167, 201-206, 239-244, 364-370](Код/crates/mt-store/src/lib.rs)) — все decode_X функции отвергают неверный длиной payload до парсинга полей.
**Crash recovery через `verify_consistency`** ([lines 472-482](Код/crates/mt-store/src/lib.rs)): при reopen проверяет что proposal с window = meta.last_cemented существует в archive. Mismatch (crash между archive и meta write) → error, не silent skip.
**Pruning через `prune_proposals_before(threshold)`** ([lines 490-515](Код/crates/mt-store/src/lib.rs)): сортированный возврат удалённых window indices. Не блокирует read для proposals ≥ threshold.
**0 unsafe, 0 panic в production lib коде, 0 HashMap, 0 f32/f64.** SystemTime usage ([line 533](Код/crates/mt-store/src/lib.rs)) внутри `#[cfg(test)] mod tests` для `rand_suffix()` tempdir naming — НЕ в consensus path.
### 8.3 Слабые стороны
| ID | Severity | Описание | Локация |
|----|----------|----------|---------|
| L-M5-1 | LOW | `write_atomic` использует `fs::rename` для atomicity, но не вызывает `fsync`. Под power-loss POSIX rename atomic only after fsync **of the directory** (некоторые файловые системы могут потерять файл если rename committed но fsync не выполнен). Документировано в [line 92-93](Код/crates/mt-store/src/lib.rs) как «дополнительно использует fsync (в M6 operator layer); rename atomicity достаточна для filesystem-level consistency». **Это правильно для сейчас**, но при включении production оператор должен добавить fsync layer. | [crates/mt-store/src/lib.rs:87-114](Код/crates/mt-store/src/lib.rs) |
| INFO-M5-2 | INFO | `cleanup_orphan_tmp` — best-effort: при ошибке `read_dir` либо `fs::remove_file` просто skip ([lines 54-71](Код/crates/mt-store/src/lib.rs)). Tmp-накопление при много раз crashed open's возможно, но не security-critical. | [crates/mt-store/src/lib.rs:54-71](Код/crates/mt-store/src/lib.rs) |
### 8.4 Рекомендации по M5
1. **(LOW)** при переходе к production deployment добавить `fsync` для критичных commit-points (proposal archive + meta_last_cemented). Описано в комментарии line 92-94 как M6 operator layer задача — корректное deferral.
### 8.5 Оценка M5: **9.0 / 10**
Чистый minimal persistence layer. Atomic rename + cleanup tmp + crash recovery — правильный набор для filesystem-only хранения без heavyweight DB.
---
## 9. Интеграция узла — `montana-node`
### 9.1 Предмет
`crates/montana-node/` — singleton-mode реализация полного цикла окна. 9 файлов, ~2 190 строк. Главный файл — [crates/montana-node/src/commands/start.rs](Код/crates/montana-node/src/commands/start.rs) (594 строки).
### 9.2 Состояние закрытия SPEC_DEVIATIONS DEV-001…DEV-011
`docs/SPEC_DEVIATIONS.md` декларирует 11 отклонений со статусом «закрыто (rewrite через canonical apply_proposal pipeline, commit pending)». **Я проверил реальный код** — rewrite **состоялся**:
| DEV | Заявленное отклонение | Состояние в текущем коде |
|-----|----------------------|---------------------------|
| DEV-001 | NodeRegistration с `vdf_chain_length=0`, обход `apply_noderegistrations_batch` через ручной `CandidatePool::insert` | **закрыт**: phase CandidateVdf реально тикает VDF до `target_chain_length = τ₂_windows`, формирует валидный NodeRegistration, вызывает `validate_noderegistration` + `apply_noderegistrations_batch` через canonical pipeline ([start.rs:158-228](Код/crates/montana-node/src/commands/start.rs)) |
| DEV-002 | `proof_endpoint = candidate_vdf_init(zeros, zeros, node_id)` — placeholder timechain_value и cba | **закрыт**: реальные `timechain.t_r` и `cemented_bundle_aggregate(w_start - 2, &[])` ([start.rs:171-174](Код/crates/montana-node/src/commands/start.rs)) |
| DEV-003 | Лотерея отсутствует — winner = `state.nodes.iter().next()` | **закрыт**: формирование `VdfReveal` + `validate_reveal` + `BundledConfirmation` + `validate_bundle` + `weighted_ticket_node` через canonical API ([start.rs:232-286](Код/crates/montana-node/src/commands/start.rs)) |
| DEV-004 | BundledConfirmation никогда не формируется | **закрыт**: формируется и подписывается явно ([start.rs:256-268](Код/crates/montana-node/src/commands/start.rs)) |
| DEV-005 | ProposalHeader не формируется, Step 4 apply_proposal обойдён | **закрыт**: `ProposalHeader` со всеми полями, `apply_proposal` вызывается, `archive_proposal` + `save_meta_last_cemented` ([start.rs:297-359](Код/crates/montana-node/src/commands/start.rs)) |
| DEV-006 | state_root не cross-check между proposer и validator | **закрыт**: после `apply_proposal` recompute через `compute_state_root` и byte-exact compare; mismatch → panic ([start.rs:342-352](Код/crates/montana-node/src/commands/start.rs)) |
| DEV-007 | `next_d` не вызывается на τ₂ boundary | **закрыт**: вызывается в конце окна с τ₂ boundary check ([start.rs:393-406](Код/crates/montana-node/src/commands/start.rs)) — **с ограничением: см. ниже M-NODE-1** |
| DEV-008 | selection_event с zeros в advance.rs | **закрыт**: файл `advance.rs` физически удалён (`ls commands/` подтверждает) |
| DEV-009 | apply_proposal целиком обойдён | **закрыт**: `apply_proposal` вызывается через canonical pipeline ([start.rs:326-332](Код/crates/montana-node/src/commands/start.rs)) |
| DEV-010 | genesis bootstrap auto-detected | **acknowledged** — реализована автодетекция через `NodeLifecycle::is_bootstrap_node` сравнение pubkey байт-в-байт |
| DEV-011 | hardware calibration initial D | **acknowledged** как permanent feature для genesis узла |
**Итог: реальный код полностью использует canonical pipeline.** SPEC_DEVIATIONS.md status «commit pending» устарел — commits выполнены (см. git log: `546a866 montana-node: auto-detect genesis vs candidate`, `0921db6 [C-12] Production-grade naming`, `db01de6 rename mt-local-node → montana-node`).
### 9.3 Сильные стороны
- **Auto-detection genesis vs candidate** ([start.rs:72-92](Код/crates/montana-node/src/commands/start.rs)) корректно ветвит phase=Active immediately для bootstrap либо phase=CandidateVdf с `target_chain_length = τ₂_windows`.
- **Polностью canonical pipeline** с реальными `validate_*` + `apply_*` через mt-account / mt-consensus / mt-lottery / mt-entry / mt-store.
- **State root self-verify** ([start.rs:342-352](Код/crates/montana-node/src/commands/start.rs)) — corruption диска / памяти detected immediately, panic с descriptive message.
- **Graceful shutdown** — SIGINT/SIGTERM handler через `libc::signal` + `AtomicBool`; signal-safe (только atomic store), POSIX async-signal-safe ([start.rs:31-33, 501-513](Код/crates/montana-node/src/commands/start.rs)).
- **Production-grade naming** per [C-12]: `montana-node` crate, `org.montana.node` launchd label, `Montana/node/` path. Никаких маркеров `local/dev/test/temp/sim` в production identifiers.
### 9.4 Слабые стороны
| ID | Severity | Описание | Локация |
|----|----------|----------|---------|
| M-NODE-1 | MEDIUM | Hardcoded `median_permille = 1000u32` для `next_d` ([start.rs:394](Код/crates/montana-node/src/commands/start.rs)). Singleton всегда даёт 100% participation, поэтому D всегда увеличивается на +3% каждые τ₂ окна. Для multi-node M6+ потребуется реальная aggregation через cemented `BundledConfirmation` всех узлов. **Документировано как singleton-mode упрощение, не block.** | [crates/montana-node/src/commands/start.rs:393-406](Код/crates/montana-node/src/commands/start.rs) |
| L-NODE-1 | LOW | `IDENTITY_MAGIC = b"mt-local"` ([identity.rs:17](Код/crates/montana-node/src/identity.rs)) — file format magic для identity-файла. Имя `mt-local` создаёт naming clash с domain registry pattern `mt-*`. Это НЕ domain separator (используется только для file-format detection при load), но визуально может быть перепутан. | [crates/montana-node/src/identity.rs:17](Код/crates/montana-node/src/identity.rs) |
| L-NODE-2 | LOW | Double-sign pattern в start.rs: header подписывается на line 318-319 (с pre_state_root), затем replay-подписывается на line 339-340 (с post_state_root). Не security risk, но wasted ML-DSA-65 deterministic Sign call (≈ 5-50 ms на signature на commodity CPU). Можно оптимизировать — apply_proposal вычисляется ДО первой подписи, тогда header вооще ne нужно перевычислять. | [crates/montana-node/src/commands/start.rs:318-340](Код/crates/montana-node/src/commands/start.rs) |
| L-NODE-3 | LOW | `included_bundles_root = single_leaf_root(&bc_h)` где `single_leaf_root(leaf) = *leaf` ([line 515-517](Код/crates/montana-node/src/commands/start.rs)) — для одиночного включённого bundle возвращает hash без Merkle структуры. Семантически = root одного листа, но если spec требует full Merkle wrap (`leaf_hash(bc_h)`) — это может расходиться при interop. Singleton-mode workaround. | [crates/montana-node/src/commands/start.rs:288-289, 515-517](Код/crates/montana-node/src/commands/start.rs) |
| L-NODE-4 | LOW | `control_root = [0u8; 32]` ([line 290](Код/crates/montana-node/src/commands/start.rs)) — placeholder. В singleton нет control objects (нет других узлов регистрации); `[0; 32]` = empty Merkle tree marker. Корректно для пустого set, но проверить spec соответствие при появлении ControlObjects (M6+). | [crates/montana-node/src/commands/start.rs:290](Код/crates/montana-node/src/commands/start.rs) |
| INFO-NODE-5 | INFO | `target: u128::MAX` ([line 312](Код/crates/montana-node/src/commands/start.rs)) — placeholder. Singleton = 1 node = winner всегда побеждает (нет конкурентов). Real lottery semantics активируются при ≥ 2 узлах. | [crates/montana-node/src/commands/start.rs:312](Код/crates/montana-node/src/commands/start.rs) |
### 9.5 Рекомендации по узлу
1. **(MEDIUM)** при переходе к multi-node M6+ заменить hardcoded `median_permille = 1000u32` на реальную aggregation через `participation_history: Vec<u32>` per τ₂ window;
2. **(LOW)** оптимизировать double-sign pattern: вычислить `apply_proposal` first, затем сразу подписать header с post_state_root (одна signature вместо двух);
3. **(LOW)** переименовать `IDENTITY_MAGIC = b"mt-local"` на `b"montana1"` либо `b"mtid001"` для устранения naming clash потенциала;
4. **(INFO)** обновить SPEC_DEVIATIONS.md DEV-001…DEV-009 со статуса «commit pending» на «closed (commit <sha>)».
### 9.6 Оценка узла: **8.0 / 10**
Singleton-mode работает корректно через canonical pipeline. Снижение на 1.0 за placeholder поля `ProposalHeader` (control_root, target, single_leaf_root, median_permille) — известные artefacts singleton, требующие proper закрытия при переходе к multi-node M6+.
---
## 10. SSOT cross-check
Я проверил соответствие spec ↔ code для всех значимых SSOT-ограниченных сущностей.
### 10.1 Размеры криптопримитивов (one source: `mt-crypto/src/lib.rs:71-81`)
| Константа | Spec значение | Code значение | Файл |
|-----------|---------------|---------------|------|
| `HASH_SIZE` | 32 (SHA-256) | 32 | [mt-crypto/src/lib.rs:71](Код/crates/mt-crypto/src/lib.rs) |
| `PUBLIC_KEY_SIZE` | 1952 (FIPS 204 ML-DSA-65 level 3) | 1952 | [mt-crypto/src/lib.rs:73](Код/crates/mt-crypto/src/lib.rs) |
| `SECRET_KEY_SIZE` | 4032 | 4032 | [mt-crypto/src/lib.rs:74](Код/crates/mt-crypto/src/lib.rs) |
| `SIGNATURE_SIZE` | 3309 | 3309 | [mt-crypto/src/lib.rs:75](Код/crates/mt-crypto/src/lib.rs) |
| `KEYPAIR_SEED_SIZE` | 32 (FIPS 204 §3.1 ξ ∈ B³²) | 32 | [mt-crypto/src/lib.rs:77](Код/crates/mt-crypto/src/lib.rs) |
| `MLKEM_PUBLIC_KEY_SIZE` | 1184 (FIPS 203 ML-KEM-768) | 1184 | [mt-crypto/src/lib.rs:79](Код/crates/mt-crypto/src/lib.rs) |
| `MLKEM_SECRET_KEY_SIZE` | 2400 | 2400 | [mt-crypto/src/lib.rs:80](Код/crates/mt-crypto/src/lib.rs) |
| `MLKEM_SEED_SIZE` | 64 (FIPS 203 §6.1 d ‖ z) | 64 | [mt-crypto/src/lib.rs:81](Код/crates/mt-crypto/src/lib.rs) |
Все остальные крейты (`mt-state`, `mt-account`, `mt-genesis`, `mt-mnemonic`, `mt-entry`, `montana-node`) импортируют через `use mt_crypto::*` без переобъявления. **Единственное дублирование**: `mt-crypto-native::MLDSA65_SIGNATURE_SIZE = 3309` ([crates/mt-crypto-native/src/lib.rs:7](Код/crates/mt-crypto-native/src/lib.rs)) — обоснованный duplicate для C FFI binding boundary, mild [C-1] violation.
### 10.2 Размеры структур (one source per crate)
| Константа | Code значение | Source crate |
|-----------|---------------|--------------|
| `ACCOUNT_RECORD_SIZE` | 2059 | mt-state |
| `NODE_RECORD_SIZE` | 2098 | mt-state |
| `CANDIDATE_RECORD_SIZE` | 2082 | mt-state |
| `PROPOSAL_HEADER_SIZE` | 3722 | mt-consensus |
| `PARAMS_ENCODED_SIZE` | 4094 | mt-genesis |
| `NODE_REGISTRATION_SIZE` | 5344 | mt-entry |
| `TRANSFER_SIZE` | 3422 | mt-account (computed: `1 + 32 + 32 + 32 + 16 + SIGNATURE_SIZE`) |
| `CHANGE_KEY_SIZE` | 5328 | mt-account (`1 + 32 + 32 + 2 + PUBLIC_KEY_SIZE + SIGNATURE_SIZE`) |
| `ANCHOR_SIZE` | 3438 | mt-account |
| `TRANSFER_ACTIVATION_SIZE` | 5376 | mt-account |
| `BUNDLE_FIXED_OVERHEAD` | computed | mt-lottery |
| `REVEAL_SIZE` | computed | mt-lottery |
| `TREE_DEPTH` | 256 | mt-merkle |
Все структуры консумятся через `use` из owning crate, тестами `*_record_encoded_size` сверяются с actual encode output.
### 10.3 Domain registry (one source: `mt-codec/src/lib.rs:44-81`)
**32 domain separator** в `mod domain` — соответствует AUDIT.md заявлению. Префикс `mt-` для всех. Все consensus-critical hash-композиции в production коде используют `domain::*` импорт. Я проверил литеральные `b"mt-..."` подстроки за пределами `mt-codec`:
- **Все вне-mt-codec literal `b"mt-..."`** находятся в `#[cfg(test)] mod tests` блоках для проверки expected hash значений (legitimate test setup);
- **Один acceptable исключение**: `IDENTITY_MAGIC = b"mt-local"` в `montana-node/identity.rs:17` — file format magic, не domain separator (см. L-NODE-1 finding).
### 10.4 Протокольные параметры (one source: `mt-genesis::ProtocolParams`)
| Параметр | Code значение | Файл |
|----------|---------------|------|
| `d0` | 325_000_000 | [mt-genesis/src/lib.rs:82](Код/crates/mt-genesis/src/lib.rs) |
| `tau2_windows` | 20_160 | [mt-genesis/src/lib.rs:84](Код/crates/mt-genesis/src/lib.rs) |
| `emission_moneta` | 13_000_000_000 (13 GɈ baseline) | [mt-genesis/src/lib.rs:85](Код/crates/mt-genesis/src/lib.rs) |
| `confirmation_quorum` | 67/100 | [mt-genesis/src/lib.rs:87-88](Код/crates/mt-genesis/src/lib.rs) |
| `participation_dead_zone_low/high` | 85/95 (permille × 10) | [mt-genesis/src/lib.rs:89-90](Код/crates/mt-genesis/src/lib.rs) |
| `d_adjustment_rate` | 3/100 | [mt-genesis/src/lib.rs:91-92](Код/crates/mt-genesis/src/lib.rs) |
| `vdf_entry_windows` | 20_160 = τ₂ | [mt-genesis/src/lib.rs:93](Код/crates/mt-genesis/src/lib.rs) |
| `selection_interval` | 336 (τ₂/336 = 60 раз/сутки) | [mt-genesis/src/lib.rs:94](Код/crates/mt-genesis/src/lib.rs) |
| `admission_divisor` | 130 (M4-LOW-7 closure: ранее hardcoded const, теперь в ProtocolParams) | [mt-genesis/src/lib.rs:95](Код/crates/mt-genesis/src/lib.rs) |
| `candidate_expiry_windows` | 60_480 = 3τ₂ | [mt-genesis/src/lib.rs:96](Код/crates/mt-genesis/src/lib.rs) |
| `pruning_idle_windows` | 80_640 = 4τ₂ | [mt-genesis/src/lib.rs:99](Код/crates/mt-genesis/src/lib.rs) |
VERSION.md заявляет spec target = `Montana v35.3.2 (2026-04-28)`. AUDIT.md ссылается на `Montana v34.0.0 (2026-04-27)`**AUDIT.md устарел на три минорных bump'а** (v34.0.0 → v35.2.0 → v35.3.0 → v35.3.1 → v35.3.2 за один день после AUDIT.md). Это **не код-блокер**, но AUDIT.md документ требует обновления для consistency.
---
## 11. Динамические тесты (single-thread, single-process)
Все тесты прогнаны мной с настройками `[build] jobs = 1` + `RUST_TEST_THREADS = 1` (зашиты в `.cargo/config.toml` для предотвращения перегрева MacBook на PBKDF2 c=2²⁰).
| Тестовый набор | Команда | Статус | Время |
|----------------|---------|--------|-------|
| `cargo check --workspace --all-targets` | базовая проверка компиляции | **PASS** (exit 0) | ~2 мин |
| `cargo test -p mt-codec -p mt-merkle -p mt-genesis -p mt-state -p mt-timechain` | M2 + codec | **PASS** (exit 0) | ~1 мин |
| `cargo test -p mt-account -p mt-store` | M3 + M5 | **PASS** (exit 0) | ~2 мин |
| `cargo test -p mt-lottery -p mt-consensus -p mt-entry` | M4 | **PASS** (exit 0) | ~3 мин |
| `cargo test -p mt-crypto -p mt-crypto-native -p mt-mnemonic` | M1 (включает PBKDF2 c=80 000 + NIST ACVP KAT + RFC vectors + e2e recovery) | **PASS** (exit 0) | ~16 мин (включая компиляцию vendored OpenSSL 3.5.5) |
| `cargo audit` | known CVE на 41 deps | **CLEAN** (0 vulnerabilities, 0 warnings) | ~30 с |
**Финальный M1 результат (зафиксирован после завершения отчёта):**
- `e2e_recovery.rs` — 3/3 PASS, включая `e2e_recovery_terminal_observable_byte_exact` ([C-4] End-to-End Observable Closure compliance);
- `keygen_vectors.rs` — 7/7 PASS: 5 KAT-векторов (KAT 1: ML-DSA-65 seed=zero, KAT 2: seed=0xFF, KAT 3: ML-DSA-65 account from master_v1, KAT 4: ML-DSA-65 node from master_v1, KAT 5: ML-KEM-768 app encryption) + 2 determinism cross-checks;
- `test_vectors.rs` — 6/6 PASS: m1_vector_1/2/3 (entropy → mnemonic → master_seed) + derivation_vector_1/2/3 (master_seed → per-role HKDF);
- `regression_baselines.rs` (mt-crypto-native) — PASS;
- `nist_acvp_kat.rs` (mt-crypto-native) — PASS на 50+ NIST ACVP-Server fixtures (ML-DSA-65 KeyGen 25 + ML-DSA-65 SigGen 15 + empty ctx + ML-KEM-768 KeyGen);
- `security_invariants.rs` (mt-crypto) — 13/13 PASS (Pass 17 secret hygiene properties).
**Замечание:** ни один из прогнанных тестов **не failed** ни в одном крейте M1+M2+M3+M4+M5.
---
## 12. Архитектурные наблюдения сверх M0…M5 scope
1. **Hybrid Rust+C через own thin FFI wrapper** — правильный архитектурный выбор по [C-6]. OpenSSL 3.5.5 LTS — FIPS 140-3 валидированный, поддержка до апреля 2030, vendored byte-pinned. Audit chain shallow: Layer 1 (Rust shim 49 строк), Layer 2 (own C wrapper 524 строки .c+.h), Layer 3 (OpenSSL — vendor responsibility).
2. **`panic = "abort"` в release профиле** ([Cargo.toml:21](Код/Cargo.toml)) — корректно для consensus-критичного бинарника. Никакого unwinding с partially-modified state.
3. **`overflow-checks = true` в обоих dev и release профилях** ([Cargo.toml:22, 25](Код/Cargo.toml)) — предотвращает silent wrap. Все consensus-арифметика дополнительно использует `checked_*` либо `saturating_*` per [I-9].
4. **Reproducible release builds** через Docker container с pinned base image (`debian:bookworm-slim@sha256:40b107342c492725bc7aacbe93a49945445191ae364184a6d24fedb28172f6f7`) ([docker/release-build.dockerfile](Код/docker/release-build.dockerfile)) — CI gate `reproducible_release` в .github/workflows/ci.yml.
---
## 13. Сводный список findings по приоритету
### Блокеры mainnet deployment (НЕ блокеры аудита кода)
- **B-1**: Genesis ceremony pending — `bootstrap_account_pubkey`, `bootstrap_node_pubkey`, `target_zero`, `genesis_content_data_hash` всё ещё placeholder `[0u8; N]`. Programmatic detection через `is_genesis_bootstrap_finalized()`. Требует multi-party ceremony либо single trusted party — design decision автора. (**знал, документировано в AUDIT.md и SPEC_DEVIATIONS DEV-010**)
### Medium
- **M-M4-1** (LOW→MEDIUM при scaling): `BundleError::TooManyOps`/`TooManyReveals` cap = 65 535 (u16 length prefix). На 1B пользователях с 1000 узлами — 100K+ op_hashes/окно. M6+ spec-patch на u32 length prefix.
- **M-NODE-1**: hardcoded `median_permille = 1000u32` для singleton; replace на real aggregation в M6+.
### Low
- **L-M1-1**: `mt-crypto-native::MLDSA65_SIGNATURE_SIZE = 3309` дубликат — sub-FFI boundary [C-1] mild violation.
- **L-M1-2**: `keypair()` — gated `cfg(any(test, feature = "testing"))`, `expect()` на getrandom acceptable.
- **L-M2-1**: устаревшие комментарии `mt-state/src/lib.rs:56,92` (1043B/1027B vs реальные 2098B/2082B). **Update doc comment.**
- **L-M2-2**: Genesis bootstrap pubkeys placeholder (= B-1).
- **L-M3-1**: `op_height: u32` overflow horizon на горячих аккаунтах в multi-decade. Acceptable now; future migration path.
- **L-M4-2**: `validate_winner` strict reject empty cemented W-1 — caller responsibility skip для genesis. **Не баг**, но без typestate enforcement.
- **L-M4-3**: `apply_selection_event` perf-аспект replay — корректен через `pool.remove`.
- **L-M5-1**: `write_atomic` без `fsync` — отложено на M6 operator layer.
- **L-NODE-1**: `IDENTITY_MAGIC = b"mt-local"` naming clash с domain registry pattern.
- **L-NODE-2**: double-sign pattern в start.rs — wasted compute.
- **L-NODE-3**: `single_leaf_root(leaf) = *leaf` для одиночного bundle/reveal в singleton.
- **L-NODE-4**: `control_root = [0u8; 32]` placeholder для пустого ControlObject set.
### Info
- **INFO-M1-3**: `mt-telemetry` integration TODO для observability `mlock` failure.
- **INFO-M1-4**: ML-KEM Encapsulate/Decapsulate KAT не интегрирован (fixtures в репо есть).
- **INFO-M2-3**: `compute_subtree_root::Combine` `expect()` — invariant breach unreachable.
- **INFO-M3-2**: settle_window отделён от apply_proposal через documentation, не typestate.
- **INFO-M4-4**: `compute_control_set` filter inclusive границы — M6+ network-layer concern для reorg/resync.
- **INFO-M4-5**: magic number 69 в `seniority_bonus = chain_length / 69` — обоснование в спецификации (не проверено в этом аудите по принципу нулевого доверия к доке).
- **INFO-M5-2**: `cleanup_orphan_tmp` best-effort — tmp накопление при многократных crashed open's possible.
- **INFO-NODE-5**: `target: u128::MAX` placeholder в singleton mode.
### Документация / housekeeping
- **DOC-1**: `AUDIT.md` ссылается на spec target v34.0.0; актуальный target по `VERSION.md` = v35.3.2. Обновить.
- **DOC-2**: `SPEC_DEVIATIONS.md` DEV-001…DEV-009 — статус «commit pending» устарел; реальные commits сделаны (`546a866`, `0921db6`, `db01de6` и др.). Обновить на «закрыто (commit <sha>)».
---
## 14. Сильные стороны проекта в целом
1. **Полная type-level дисциплина**: 0 unsafe без SAFETY, 0 panic в production paths без protocol-invariant обоснования, 0 HashMap/HashSet, 0 f32/f64, 0 SystemTime/Instant в consensus path, 0 thread_rng / OsRng в lib коде.
2. **Domain separation первого класса**: 32 SSOT registry в `mt-codec`, NUL-byte separator `domain ‖ 0x00 ‖ parts...` (P1 closure).
3. **Secret hygiene**: heap+mlock+zeroize+`!Clone`+`!PartialEq` — не worse чем libsodium / boringssl.
4. **Integer arithmetic per [I-9]**: ВСЕ consensus formulas в integer Q-format с binding test vectors. `log2_q64` — degree-3 Remez minimax с hardcoded coefficients.
5. **`[I-8]` Network-Bound Unpredictability**: `cemented_bundle_aggregate` корректно исключает signatures и op_hashes из input; canonical sort по `node_id` обеспечивает order-independence.
6. **RFC + FIPS conformance**: HMAC RFC 4231 (4 cases), HKDF RFC 5869 (3 cases), PBKDF2 RFC 7914 (2 cases) + CryptoJS, SHA-256 FIPS 180-4, ML-DSA-65 / ML-KEM-768 NIST ACVP (50+ KAT cases).
7. **Reproducible builds** через pinned Docker image + CI gate.
8. **`cargo audit` clean**: 41 deps, 0 vulnerabilities, 0 warnings.
9. **Доказательная зрелость через предыдущие audits**: 4 successive incremental audits + critic closures (M1-F: 7+5+4 closure, M2: 17 closure, M3: 10 closure, M4: 10 closure, M5: 1 closure) — pattern «caught + closed» без оставшихся открытых.
10. **Производственная дисциплина**: production-grade naming везде ([C-12]), zero deferred policy в audit-ready состоянии ([C-6]), `panic="abort"` + `overflow-checks=true`.
---
## 15. Итоговая оценка: **8.7 / 10**
Аудит подтверждает: M1…M5 готовы к engagement производственной security firm (NCC Group, Trail of Bits, Quarkslab, Cure53). Открытых блокеров кода — нет. Block-листы только относятся к (а) Genesis ceremony перед mainnet, (б) singleton-mode placeholder полям при переходе к multi-node M6+, (в) обновлению AUDIT.md / SPEC_DEVIATIONS.md документации.
**Распределение оценки:**
- M1: 9.0/10 — excellent crypto layer
- M2: 9.0/10 — solid foundation
- M3: 9.0/10 — clean apply layer
- M4: 8.5/10 — minor scale concern (u16 length prefix)
- M5: 9.0/10 — minimal correct persistence
- montana-node integration: 8.0/10 — singleton placeholders для M6+
- общее (SSOT, deps, build): 9.0/10
**Top-3 рекомендации (приоритет порядка):**
1. **(MEDIUM)** обновить AUDIT.md spec target v34.0.0 → v35.3.2 + SPEC_DEVIATIONS.md DEV-001…DEV-009 на «закрыто (commit <sha>)» — устранить documentation drift.
2. **(LOW)** обновить устаревшие комментарии `mt-state/src/lib.rs:56,92` на актуальные размеры структур (2098B / 2082B).
3. **(MEDIUM)** при переходе к multi-node M6+ заменить hardcoded `median_permille = 1000u32` на real participation aggregation; запланировать spec-patch на u32 length prefix для bundle hashes.
---
## Приложение A — методология восстановления отчёта
Если потребуется воспроизвести этот аудит через другого аудитора, ключевые команды (`cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код"`):
```bash
cargo check --workspace --all-targets
cargo test -p mt-codec -p mt-merkle -p mt-genesis -p mt-state -p mt-timechain
cargo test -p mt-account -p mt-store
cargo test -p mt-lottery -p mt-consensus -p mt-entry
cargo test -p mt-crypto -p mt-crypto-native -p mt-mnemonic
cargo audit --json
```
Все настройки single-thread / single-process уже в `.cargo/config.toml`, override не требуется.
Список финальных проверок:
1. Существуют ли любые `unsafe { ... }` без `// SAFETY:` комментария:
```bash
grep -rB1 'unsafe {' crates/*/src/*.rs | grep -v 'SAFETY' | grep 'unsafe {' | grep -v test
```
2. Любые `unwrap()` / `expect()` / `panic!` в lib коде вне `#[cfg(test)]`:
```bash
grep -rn 'unwrap()\|expect(\|panic!' crates/*/src/*.rs | grep -v '#\[cfg(test)\]' | grep -v 'protocol invariant'
```
3. HashMap/HashSet в lib path:
```bash
grep -rn 'HashMap\|HashSet' crates/*/src/*.rs | grep -v test | grep -v BTreeMap
```
4. Domain registry size:
```bash
grep -cE '^\s*pub const [A-Z_]+: &\[u8\]' crates/mt-codec/src/lib.rs
```
---
**Конец отчёта.** Все файлы в `Протокол/` помимо кода рассматривались как сырой target scope, не как источник истины. Все findings сопровождены конкретными ссылками на `crates/<crate>/src/<file>:<line>`.