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

639 lines
68 KiB
Markdown
Raw Permalink 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.

# Внешний аудит протокола 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³⁸ практически недостижимо);
- Зачислённая сумма = `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_*` для большей очевидности validateapply ordering для будущих контрибьюторов.
### 6.5 Оценка M3: **9.0 / 10**
Excellent apply-layer. Полное покрытие validateapply, корректные 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>`.