fix: broken markdown refs auto-resolved

This commit is contained in:
efir369999 2026-05-06 01:09:28 +03:00
parent 954fa029ce
commit 4c1a731184

View File

@ -39,7 +39,7 @@
- *код ↔ исполнение* — прогон полного набора тестов в один поток (`.cargo/config.toml [build] jobs = 1` + `RUST_TEST_THREADS = 1`); - *код ↔ исполнение* — прогон полного набора тестов в один поток (`.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). - *код ↔ внешние стандарты* — 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](Code/.cargo/config.toml)`. Из соображений охраны машины автора (см. `feedback_single_core_tests.md`) все сборки и тесты выполнялись в одном процессе и одном потоке — это требование зашито в `[.cargo/config.toml](../Code/.cargo/config.toml)`.
Динамическую инспекцию production deployment на серверах Frankfurt/Moscow я **не** выполнял: согласно `MEMORY.md → SeaFare Montana`, оба сервера обслуживают SeaFare и Cascade-VPN, не реализацию TimeChain. Production deployment montana-node ещё не существует. Динамическую инспекцию production deployment на серверах Frankfurt/Moscow я **не** выполнял: согласно `MEMORY.md → SeaFare Montana`, оба сервера обслуживают SeaFare и Cascade-VPN, не реализацию TimeChain. Production deployment montana-node ещё не существует.
@ -55,12 +55,12 @@ zeroize = =1.8.1
getrandom = =0.2.15 getrandom = =0.2.15
``` ```
Внешние FFI-зависимости только в `mt-crypto-native` ([crates/mt-crypto-native/Cargo.toml](Code/crates/mt-crypto-native/Cargo.toml)): Внешние FFI-зависимости только в `mt-crypto-native` ([crates/mt-crypto-native/Cargo.toml](../Code/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`; - `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`; - `libc = =0.2.169` — POSIX `mlock`/`munlock`/`signal`;
- `serde`, `serde_json` — в крейте только для парсинга NIST ACVP fixtures (изолированы от production пути). - `serde`, `serde_json` — в крейте только для парсинга NIST ACVP fixtures (изолированы от production пути).
**Toolchain:** Rust stable, минимум 1.70, зафиксирован в [rust-toolchain.toml](Code/rust-toolchain.toml). Профиль release: `lto = "fat"`, `codegen-units = 1`, `panic = "abort"`, `overflow-checks = true` ([Cargo.toml:17-22](Code/Cargo.toml)) — корректные параметры для криптографически-критичного бинарника. **Toolchain:** Rust stable, минимум 1.70, зафиксирован в [rust-toolchain.toml](../Code/rust-toolchain.toml). Профиль release: `lto = "fat"`, `codegen-units = 1`, `panic = "abort"`, `overflow-checks = true` (Cargo.toml:17-22) — корректные параметры для криптографически-критичного бинарника.
`cargo audit`: 41 dependency, **0 уязвимостей, 0 предупреждений**. Запущено мной 2026-04-28, advisory DB загружена 2026-04-28T15:17:23Z. `cargo audit`: 41 dependency, **0 уязвимостей, 0 предупреждений**. Запущено мной 2026-04-28, advisory DB загружена 2026-04-28T15:17:23Z.
@ -72,64 +72,64 @@ getrandom = =0.2.15
| Компонент | Файл | Lines | Назначение | | Компонент | Файл | Lines | Назначение |
|-----------|------|-------|------------| |-----------|------|-------|------------|
| `mt-codec` | [crates/mt-codec/src/lib.rs](Code/crates/mt-codec/src/lib.rs) | 351 | трейт CanonicalEncode + регистр domain-разделителей (32 штуки) | | `mt-codec` | [crates/mt-codec/src/lib.rs](../Code/crates/mt-codec/src/lib.rs) | 351 | трейт CanonicalEncode + регистр domain-разделителей (32 штуки) |
| `mt-crypto` (Rust shim) | [crates/mt-crypto/src/lib.rs](Code/crates/mt-crypto/src/lib.rs) | 662 | публичный API (`PublicKey`, `SecretKey`, `Signature`, `Mlkem*`, sign/verify, hash), heap+mlock+zeroize secret hygiene | | `mt-crypto` (Rust shim) | [crates/mt-crypto/src/lib.rs](../Code/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](Code/crates/mt-crypto-native/src/lib.rs) | 49 | extern "C" объявления, константы размеров | | `mt-crypto-native` (FFI Rust) | [crates/mt-crypto-native/src/lib.rs](../Code/crates/mt-crypto-native/src/lib.rs) | 49 | extern "C" объявления, константы размеров |
| `mt-crypto-native` (C wrapper) | [crates/mt-crypto-native/csrc/mt_crypto.c](Code/crates/mt-crypto-native/csrc/mt_crypto.c) | 457 | тонкая обёртка над OpenSSL EVP API (KeyGen, Sign, Verify, self_test) | | `mt-crypto-native` (C wrapper) | [crates/mt-crypto-native/csrc/mt_crypto.c](../Code/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](Code/crates/mt-crypto-native/csrc/mt_crypto.h) | 67 | C API + 13 status codes | | `mt-crypto-native` (C header) | [crates/mt-crypto-native/csrc/mt_crypto.h](../Code/crates/mt-crypto-native/csrc/mt_crypto.h) | 67 | C API + 13 status codes |
| `mt-mnemonic` | [crates/mt-mnemonic/src/](Code/crates/mt-mnemonic/src/) | 996 | 24-слов мнемоника, PBKDF2-HMAC-SHA-256 (iter = 2²⁰), HKDF-Expand per-role вывод, привязанный 2048-словарь | | `mt-mnemonic` | crates/mt-mnemonic/src/ | 996 | 24-слов мнемоника, PBKDF2-HMAC-SHA-256 (iter = 2²⁰), HKDF-Expand per-role вывод, привязанный 2048-словарь |
### 4.2 Сильные стороны ### 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`): **Чистая трёхслойная архитектура 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](Code/crates/mt-crypto-native/csrc/mt_crypto.c); - 4 блока обращений к FFI: контракт размеров явно описан, OpenSSL EVP convention `(void*)seed/sk/pk` — backwards compat cast без mutation, описано в [csrc/mt_crypto.c:61-67](../Code/crates/mt-crypto-native/csrc/mt_crypto.c);
- 4 блока `mlock`/`munlock` для secret pages: best-effort с graceful fallback на encrypted swap (FileVault/LUKS) при `RLIMIT_MEMLOCK exceeded`. - 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](Code/crates/mt-crypto-native/csrc/mt_crypto.c)): **Secret hygiene на исключительно высоком уровне** ([mt-crypto/src/lib.rs:130-204, 331-403](../Code/crates/mt-crypto-native/csrc/mt_crypto.c)):
- `SecretKey(Box<[u8; SECRET_KEY_SIZE]>)` — heap-allocation, `!Clone`, `!Copy`, `!PartialEq` (исключение timing leak через `==`); - `SecretKey(Box<[u8; SECRET_KEY_SIZE]>)` — heap-allocation, `!Clone`, `!Copy`, `!PartialEq` (исключение timing leak через `==`);
- `Drop` имплементация: `zeroize` + `munlock`; - `Drop` имплементация: `zeroize` + `munlock`;
- `from_array(mut bytes)` принимает массив by-value, копирует в heap через `alloc_locked_secret_box`, **зануляет** stack-копию через `bytes.zeroize()` ([line 139](Code/crates/mt-crypto/src/lib.rs)); - `from_array(mut bytes)` принимает массив by-value, копирует в heap через `alloc_locked_secret_box`, **зануляет** stack-копию через `bytes.zeroize()` ([line 139](../Code/crates/mt-crypto/src/lib.rs));
- FFI `keypair_from_seed` пишет напрямую в locked heap-страницу (никаких stack temporary buffers с secret bytes); - FFI `keypair_from_seed` пишет напрямую в locked heap-страницу (никаких stack temporary buffers с secret bytes);
- При ошибке FFI `sk_box.zeroize()` вызывается явно перед drop ([line 248](Code/crates/mt-crypto/src/lib.rs)). - При ошибке FFI `sk_box.zeroize()` вызывается явно перед drop ([line 248](../Code/crates/mt-crypto/src/lib.rs)).
**Аналогичная защита для `MlkemSecretKey`** (ML-KEM-768, 2400 байт). **Аналогичная защита для `MlkemSecretKey`** (ML-KEM-768, 2400 байт).
**13/13 security invariants Pass 17** ([crates/mt-crypto/tests/security_invariants.rs](Code/crates/mt-crypto/tests/security_invariants.rs)): trait-based проверки `!Clone`, `!PartialEq`, heap-allocation, наличие Drop, отсутствие `println!`/`log::*` на secret bytes — выполняются в CI как regression-detection. **13/13 security invariants Pass 17** ([crates/mt-crypto/tests/security_invariants.rs](../Code/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](Code/crates/mt-crypto-native/csrc/mt_crypto.c)): **FIPS-conformance в C-обёртке** ([csrc/mt_crypto.c](../Code/crates/mt-crypto-native/csrc/mt_crypto.c)):
- ML-DSA-65 KeyGen: `OSSL_PKEY_PARAM_ML_DSA_SEED` ([line 113](Code/crates/mt-crypto-native/csrc/mt_crypto.c)) — FIPS 204 §3.1 ξ ∈ B³² (32 байта); - ML-DSA-65 KeyGen: `OSSL_PKEY_PARAM_ML_DSA_SEED` ([line 113](../Code/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](Code/crates/mt-crypto-native/csrc/mt_crypto.c)) — FIPS 203 §6.1 d ‖ z (64 байта); - ML-KEM-768 KeyGen: `OSSL_PKEY_PARAM_ML_KEM_SEED` ([line 130](../Code/crates/mt-crypto-native/csrc/mt_crypto.c)) — FIPS 203 §6.1 d ‖ z (64 байта);
- Sign: `OSSL_SIGNATURE_PARAM_DETERMINISTIC = 1` ([line 222](Code/crates/mt-crypto-native/csrc/mt_crypto.c)) — FIPS 204 Algorithm 2 deterministic вариант (критично для консенсус-определённости [I-3]); - Sign: `OSSL_SIGNATURE_PARAM_DETERMINISTIC = 1` ([line 222](../Code/crates/mt-crypto-native/csrc/mt_crypto.c)) — FIPS 204 Algorithm 2 deterministic вариант (критично для консенсус-определённости [I-3]);
- Sign with context: `ctx_len > 255` отвергается ([line 279](Code/crates/mt-crypto-native/csrc/mt_crypto.c)) — FIPS 204 ограничение; - Sign with context: `ctx_len > 255` отвергается ([line 279](../Code/crates/mt-crypto-native/csrc/mt_crypto.c)) — FIPS 204 ограничение;
- Все cleanup через `goto cleanup` с обязательными `EVP_PKEY_free` + `EVP_PKEY_CTX_free` + `EVP_MD_CTX_free` (ни одной утечки). - Все cleanup через `goto cleanup` с обязательными `EVP_PKEY_free` + `EVP_PKEY_CTX_free` + `EVP_MD_CTX_free` (ни одной утечки).
**Покрытие RFC и FIPS векторами:** **Покрытие RFC и FIPS векторами:**
- HMAC ([crates/mt-mnemonic/src/hmac.rs](Code/crates/mt-mnemonic/src/hmac.rs)): RFC 4231 cases 1, 2, 4, 6 (включая длинный ключ > BLOCK_SIZE); - HMAC ([crates/mt-mnemonic/src/hmac.rs](../Code/crates/mt-mnemonic/src/hmac.rs)): RFC 4231 cases 1, 2, 4, 6 (включая длинный ключ > BLOCK_SIZE);
- HKDF-Expand ([crates/mt-mnemonic/src/hkdf.rs](Code/crates/mt-mnemonic/src/hkdf.rs)): RFC 5869 §A.1, §A.2, §A.3; - HKDF-Expand ([crates/mt-mnemonic/src/hkdf.rs](../Code/crates/mt-mnemonic/src/hkdf.rs)): RFC 5869 §A.1, §A.2, §A.3;
- PBKDF2 ([crates/mt-mnemonic/src/pbkdf2.rs](Code/crates/mt-mnemonic/src/pbkdf2.rs)): RFC 7914 §11 vector 1 (c = 1) + vector 2 (c = 80 000) + CryptoJS (c = 4 096); - PBKDF2 ([crates/mt-mnemonic/src/pbkdf2.rs](../Code/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](Code/crates/mt-crypto/src/lib.rs)): FIPS 180-4 §B.1 «abc»; - SHA-256 ([crates/mt-crypto/src/lib.rs:516-523](../Code/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/](Code/crates/mt-crypto-native/tests/fixtures/nist_acvp/)) — 4 файла: - ML-DSA-65 + ML-KEM-768: NIST ACVP-Server fixtures (crates/mt-crypto-native/tests/fixtures/nist_acvp/) — 4 файла:
- `ml_dsa_65_keygen.json`, `ml_kem_768_keygen.json`, - `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_all15.json` (15 SigGen cases с context bytes 0…255),
- `ml_dsa_65_siggen_det_external_pure_empty_ctx.json` (empty ctx case); - `ml_dsa_65_siggen_det_external_pure_empty_ctx.json` (empty ctx case);
- `mt_self_test()` ([csrc/mt_crypto.c:387-457](Code/crates/mt-crypto-native/csrc/mt_crypto.c)): determinism, sign/verify roundtrip, sign-determinism cross-check, bit-flip rejection, ML-KEM determinism. - `mt_self_test()` ([csrc/mt_crypto.c:387-457](../Code/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](Code/crates/mt-crypto/src/lib.rs)): функция `hash(domain, parts)` всегда вставляет NUL-байт между доменом и payload — закрывает finding P1 (prefix-collision protection) предыдущего внешнего аудита. **Привязка hash-композиции к domain-разделителям** ([mt-crypto/src/lib.rs:85-93](../Code/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](Code/crates/mt-mnemonic/src/wordlist.rs)): **Wordlist binding fingerprint** ([crates/mt-mnemonic/src/wordlist.rs:14-31](../Code/crates/mt-mnemonic/src/wordlist.rs)):
- `WORDLIST_FINGERPRINT = SHA-256(WORDLIST_RAW)` зашит как константа; - `WORDLIST_FINGERPRINT = SHA-256(WORDLIST_RAW)` зашит как константа;
- `init_wordlist()` ассертит совпадение, лексикографическую сортировку и ровно 2048 строк — не проходит инициализация при corruption встроенного wordlist; - `init_wordlist()` ассертит совпадение, лексикографическую сортировку и ровно 2048 строк — не проходит инициализация при corruption встроенного wordlist;
- `pub fn word_index(word) -> Option<u16>` через `binary_search` (детерминирована). - `pub fn word_index(word) -> Option<u16>` через `binary_search` (детерминирована).
**5 KAT-векторов в [crates/mt-mnemonic/tests/keygen_vectors.rs](Code/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. **5 KAT-векторов в [crates/mt-mnemonic/tests/keygen_vectors.rs](../Code/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 Слабые стороны ### 4.3 Слабые стороны
| ID | Severity | Описание | Локация | | 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](Code/crates/mt-crypto-native/src/lib.rs) | | 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](../Code/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](Code/crates/mt-crypto/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](../Code/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](Code/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](../Code/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. | (отсутствие теста) | | 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 ### 4.4 Рекомендации по M1
@ -150,36 +150,36 @@ getrandom = =0.2.15
| Компонент | Файл | Lines | Назначение | | Компонент | Файл | Lines | Назначение |
|-----------|------|-------|------------| |-----------|------|-------|------------|
| `mt-merkle` | [crates/mt-merkle/src/lib.rs](Code/crates/mt-merkle/src/lib.rs) | 770 | Sparse Merkle Tree глубины 256, кэш `empty_internal[0..256]`, `prove`/`verify_proof` | | `mt-merkle` | [crates/mt-merkle/src/lib.rs](../Code/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](Code/crates/mt-genesis/src/lib.rs) | 354 | `ProtocolParams` SSOT (4094 байт), Genesis Decree, `is_genesis_bootstrap_finalized` | | `mt-genesis` | [crates/mt-genesis/src/lib.rs](../Code/crates/mt-genesis/src/lib.rs) | 354 | `ProtocolParams` SSOT (4094 байт), Genesis Decree, `is_genesis_bootstrap_finalized` |
| `mt-state` | [crates/mt-state/src/lib.rs](Code/crates/mt-state/src/lib.rs) | 648 | AccountTable (2059 байт записи) + NodeTable (2098) + CandidatePool (2082), `compute_state_root` | | `mt-state` | [crates/mt-state/src/lib.rs](../Code/crates/mt-state/src/lib.rs) | 648 | AccountTable (2059 байт записи) + NodeTable (2098) + CandidatePool (2082), `compute_state_root` |
| `mt-timechain` | [crates/mt-timechain/src/lib.rs](Code/crates/mt-timechain/src/lib.rs) | 348 | `vdf_step`/`vdf_verify`, `next_d` (Adaptive D feedback), `cemented_bundle_aggregate` (canonical [I-8] binding) | | `mt-timechain` | [crates/mt-timechain/src/lib.rs](../Code/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 Сильные стороны ### 5.2 Сильные стороны
**Sparse Merkle Tree (SMT) реализация** ([mt-merkle/src/lib.rs](Code/crates/mt-merkle/src/lib.rs)): **Sparse Merkle Tree (SMT) реализация** ([mt-merkle/src/lib.rs](../Code/crates/mt-merkle/src/lib.rs)):
- `BTreeMap<[u8; 32], Hash32>` для leaves (детерминированная итерация); - `BTreeMap<[u8; 32], Hash32>` для leaves (детерминированная итерация);
- `OnceLock<[Hash32; 257]>` кэш `empty_internal` (вычисляется один раз); - `OnceLock<[Hash32; 257]>` кэш `empty_internal` (вычисляется один раз);
- `compute_subtree_root`**итеративная** реализация через explicit work-stack ([lines 82-138](Code/crates/mt-merkle/src/lib.rs)) — закрывает stack-overflow угрозу для embedded targets с малым stack; - `compute_subtree_root`**итеративная** реализация через explicit work-stack ([lines 82-138](../Code/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; - `Cell<Option<Hash32>>` invalidate-on-mutate caching root (О(1) при последовательных read'ах) — закрывает M2-5 perf finding;
- **Корректная idempotency**: `insert` того же leaf не invalidate'ит cache ([lines 187-193](Code/crates/mt-merkle/src/lib.rs)); - **Корректная idempotency**: `insert` того же leaf не invalidate'ит cache ([lines 187-193](../Code/crates/mt-merkle/src/lib.rs));
- `try_empty_internal` для untrusted input vs `empty_internal` (с явным assertion для programmer error) — defensive engineering. - `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. - 4 reject test'а для tampered proof: mutated sibling, mutated leaf_value, wrong root, wrong absence claim.
**ProtocolParams SSOT** ([mt-genesis/src/lib.rs](Code/crates/mt-genesis/src/lib.rs)): **ProtocolParams SSOT** ([mt-genesis/src/lib.rs](../Code/crates/mt-genesis/src/lib.rs)):
- `OnceLock` singleton для immutability; - `OnceLock` singleton для immutability;
- 22 поля с явным byte-layout, `PARAMS_ENCODED_SIZE = 4094` сверяется тестом; - 22 поля с явным byte-layout, `PARAMS_ENCODED_SIZE = 4094` сверяется тестом;
- `is_genesis_bootstrap_finalized()` + явный `#[ignore]` тест `bootstrap_keypairs_finalized` фиксируют статус «pre-Genesis-ceremony»; - `is_genesis_bootstrap_finalized()` + явный `#[ignore]` тест `bootstrap_keypairs_finalized` фиксируют статус «pre-Genesis-ceremony»;
- 16 mutation-detection тестов проверяют что любое изменение поля меняет `compute_genesis_state_hash`. - 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](Code/crates/mt-state/src/lib.rs)). `is_active(node, W, τ₂)` через `saturating_sub` ([line 295](Code/crates/mt-state/src/lib.rs)) — защита от underflow в genesis окне. **Состояние (`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](../Code/crates/mt-state/src/lib.rs)). `is_active(node, W, τ₂)` через `saturating_sub` ([line 295](../Code/crates/mt-state/src/lib.rs)) — защита от underflow в genesis окне.
**TimeChain VDF + Adaptive D + cemented_bundle_aggregate** ([mt-timechain/src/lib.rs](Code/crates/mt-timechain/src/lib.rs)): **TimeChain VDF + Adaptive D + cemented_bundle_aggregate** ([mt-timechain/src/lib.rs](../Code/crates/mt-timechain/src/lib.rs)):
- `vdf_step(prev, d) = SHA-256^d(prev)`, `d=0` → identity (no-op); - `vdf_step(prev, d) = SHA-256^d(prev)`, `d=0` → identity (no-op);
- `vdf_verify` — re-computes byte-exact; - `vdf_verify` — re-computes byte-exact;
- `next_d` — integer-permille арифметика per [I-9]: `>= 950 permille → ×103/100`, `<= 850 → ×97/100`, иначе dead-zone; - `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](Code/crates/mt-timechain/src/lib.rs)) — корректный engineering halt при non-attacker triggered overflow (median_ratio derived канонически из cemented set, ≈ 1.5M лет до достижения горизонта); - `checked_mul` overflow-detection через panic с descriptive message ([lines 49-58, 62-69](../Code/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](Code/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 функции). - **`cemented_bundle_aggregate`** ([lines 90-111](../Code/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`. - 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 крейтам. **0 unsafe, 0 panic в production paths, 0 HashMap, 0 f32/f64, 0 SystemTime, 0 RNG в lib коде.** Проверено через wide-scan grep по всем M2 крейтам.
@ -188,9 +188,9 @@ getrandom = =0.2.15
| ID | Severity | Описание | Локация | | 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](Code/crates/mt-state/src/lib.rs) | | 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](../Code/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](Code/crates/mt-genesis/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](../Code/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](Code/crates/mt-merkle/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](../Code/crates/mt-merkle/src/lib.rs) |
### 5.4 Рекомендации по M2 ### 5.4 Рекомендации по M2
@ -207,7 +207,7 @@ Solid M2 layer с очень чистой реализацией SMT и корр
### 6.1 Предмет аудита ### 6.1 Предмет аудита
`mt-account` ([crates/mt-account/src/lib.rs](Code/crates/mt-account/src/lib.rs)) — 2 556 строк. Включает 4 user-операции: `mt-account` ([crates/mt-account/src/lib.rs](../Code/crates/mt-account/src/lib.rs)) — 2 556 строк. Включает 4 user-операции:
- Transfer (0x02), ChangeKey (0x03), Anchor (0x04), TransferActivation (0x0A); - Transfer (0x02), ChangeKey (0x03), Anchor (0x04), TransferActivation (0x0A);
- `validate_*` + `apply_*` пары; - `validate_*` + `apply_*` пары;
- `op_hash` через `mt-op` domain separator; - `op_hash` через `mt-op` domain separator;
@ -221,22 +221,22 @@ Solid M2 layer с очень чистой реализацией SMT и корр
**Полное покрытие validate→apply разделения**: **Полное покрытие validate→apply разделения**:
- Каждый opcode имеет `validate_*` с явным набором проверок (`InvalidPrevHash`, `DuplicateAccount`, `AccountNotFound`, `ReceiverNotActive`, `ReceiverAlreadyExists`, `InvalidBinding`, `InvalidSignature`, `InsufficientBalance`, `SelfTransfer`, `ZeroAmount`, `UnsupportedSuite`, `ActivationCooldownNotElapsed`); - Каждый opcode имеет `validate_*` с явным набором проверок (`InvalidPrevHash`, `DuplicateAccount`, `AccountNotFound`, `ReceiverNotActive`, `ReceiverAlreadyExists`, `InvalidBinding`, `InvalidSignature`, `InsufficientBalance`, `SelfTransfer`, `ZeroAmount`, `UnsupportedSuite`, `ActivationCooldownNotElapsed`);
- `validate_transfer_activation` ([lines 244-286](Code/crates/mt-account/src/lib.rs)) проверяет binding `receiver == derive_account_id(suite_id, receiver_pubkey)` и cooldown `[I-15]` (1 активация на sender за τ₂); - `validate_transfer_activation` ([lines 244-286](../Code/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](Code/crates/mt-account/src/lib.rs)); - `validate_change_key` явно требует подпись **старым** ключом ([lines 288-303](../Code/crates/mt-account/src/lib.rs));
- `ValidationContext` обязательная обёртка `(current_window, tau2_windows)` — caller не забудет передать context. - `ValidationContext` обязательная обёртка `(current_window, tau2_windows)` — caller не забудет передать context.
**Все `panic!` в `apply_*` defense-in-depth**: **Все `panic!` в `apply_*` defense-in-depth**:
5 вызовов `panic!` ([lines 360, 378, 386, 392, 428, 438, 442, 475, 479, 496, 500, 580, 599, 632](Code/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-сообщениями. Соответствует политике `Code/CLAUDE.md → Code style → No unwrap/expect в lib коде, кроме случаев protocol violation с явным комментарием почему invariant не может быть нарушен`. 5 вызовов `panic!` ([lines 360, 378, 386, 392, 428, 438, 442, 475, 479, 496, 500, 580, 599, 632](../Code/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-сообщениями. Соответствует политике `Code/CLAUDE.md → Code style → No unwrap/expect в lib коде, кроме случаев protocol violation с явным комментарием почему invariant не может быть нарушен`.
**`window_w_to_u32` cast helper** ([lines 358-365](Code/crates/mt-account/src/lib.rs)) — каст `u64 → u32` с явным описанием horizon (~8000 лет на τ₁ = 60 сек) и descriptive panic при достижении. AccountRecord использует `u32` для `window`-полей как encoded-size optimization; consensus-types — `u64`. **`window_w_to_u32` cast helper** ([lines 358-365](../Code/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](Code/crates/mt-account/src/lib.rs)): **`apply_emission`** ([lines 561-588](../Code/crates/mt-account/src/lib.rs)):
- Genesis: window_w == 0 → return (нет W-1); - Genesis: window_w == 0 → return (нет W-1);
- Lookup `node.operator_account_id` через NodeTable + assert existence (protocol invariant); - Lookup `node.operator_account_id` через NodeTable + assert existence (protocol invariant);
- `checked_add` на operator.balance с descriptive panic при overflow на u128::MAX (≈ 10³⁸ nɈ — практически недостижимо); - `checked_add` на operator.balance с descriptive panic при overflow на u128::MAX (≈ 10³⁸ nɈ — практически недостижимо);
- Зачислённая сумма = `params.emission_moneta` (SSOT через `mt-genesis`). - Зачислённая сумма = `params.emission_moneta` (SSOT через `mt-genesis`).
**`build_genesis_state`** ([lines 656-701](Code/crates/mt-account/src/lib.rs)): **`build_genesis_state`** ([lines 656-701](../Code/crates/mt-account/src/lib.rs)):
- 1 bootstrap account (is_node_operator=true, balance=0, frontier = SHA-256(GENESIS, account_id)); - 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); - 1 bootstrap node (chain_length=1 для invariant DS-2 weighted_ticket);
- empty Candidate Pool; - empty Candidate Pool;
@ -248,8 +248,8 @@ Solid M2 layer с очень чистой реализацией SMT и корр
| ID | Severity | Описание | Локация | | 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](Code/crates/mt-account/src/lib.rs) | | 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](../Code/crates/mt-account/src/lib.rs) |
| INFO-M3-2 | INFO | Чистое разделение `settle_window` отдельно от `apply_proposal` ([line 543](Code/crates/mt-account/src/lib.rs)) хорошо документировано (orchestration ordering invariant виден caller-у), но требует дисциплины caller-а — нет typestate enforcement. Documented в module-level comment + `Code/CLAUDE.md → [C-7]`. | [crates/mt-account/src/lib.rs:711-720](Code/crates/mt-account/src/lib.rs) | | INFO-M3-2 | INFO | Чистое разделение `settle_window` отдельно от `apply_proposal` ([line 543](../Code/crates/mt-account/src/lib.rs)) хорошо документировано (orchestration ordering invariant виден caller-у), но требует дисциплины caller-а — нет typestate enforcement. Documented в module-level comment + `Code/CLAUDE.md → [C-7]`. | [crates/mt-account/src/lib.rs:711-720](../Code/crates/mt-account/src/lib.rs) |
### 6.4 Рекомендации по M3 ### 6.4 Рекомендации по M3
@ -268,34 +268,34 @@ Excellent apply-layer. Полное покрытие validate→apply, корр
| Компонент | Файл | Lines | Назначение | | Компонент | Файл | Lines | Назначение |
|-----------|------|-------|------------| |-----------|------|-------|------------|
| `mt-lottery` | [crates/mt-lottery/src/lib.rs](Code/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-lottery` | [crates/mt-lottery/src/lib.rs](../Code/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](Code/crates/mt-consensus/src/lib.rs) | 1 089 | ProposalHeader (3 722 байта, 17 полей), `validate_header`, `canonical_proposer`, `validate_winner`, `compute_control_set` | | `mt-consensus` | [crates/mt-consensus/src/lib.rs](../Code/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](Code/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` | | `mt-entry` | [crates/mt-entry/src/lib.rs](../Code/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 Сильные стороны ### 7.2 Сильные стороны
**`log2_q64` Q64.64 fixed-point integer log** ([crates/mt-lottery/src/lib.rs:282-355](Code/crates/mt-lottery/src/lib.rs)): **`log2_q64` Q64.64 fixed-point integer log** ([crates/mt-lottery/src/lib.rs:282-355](../Code/crates/mt-lottery/src/lib.rs)):
- degree-3 Remez minimax polynomial с binding coefficients B0..B3 (hardcoded); - degree-3 Remez minimax polynomial с binding coefficients B0..B3 (hardcoded);
- максимальная ошибка 2⁻¹⁰·⁶² (per spec); - максимальная ошибка 2⁻¹⁰·⁶² (per spec);
- `endpoint == 0` → saturate to `u128::MAX` (handle SHA collision tail event); - `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; - **M4-LOW-3 closure**: `unwrap_or([0; 16])` вместо `expect()` — absolute panic-free guarantee на impossible path;
- `saturating_sub` ([line 354](Code/crates/mt-lottery/src/lib.rs)) на minimax overshoot — log от числа ≥ 1 не может быть отрицательным; - `saturating_sub` ([line 354](../Code/crates/mt-lottery/src/lib.rs)) на minimax overshoot — log от числа ≥ 1 не может быть отрицательным;
- `LN2_Q64 = 0xB17217F7D1CF79AB` ≈ ln(2) × 2⁶⁴ — корректное значение (≈ 12 786 308 645 202 655 659). - `LN2_Q64 = 0xB17217F7D1CF79AB` ≈ ln(2) × 2⁶⁴ — корректное значение (≈ 12 786 308 645 202 655 659).
**`quorum`** ([line 476-479](Code/crates/mt-lottery/src/lib.rs)): integer form `(67 × X + 99) / 100` через `saturating_mul/add` (M4-LOW-5 closure) — defense-in-depth от u64 overflow. **`quorum`** ([line 476-479](../Code/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](Code/crates/mt-lottery/src/lib.rs)): canonical tie-break `(ticket asc, class asc, id lex asc)` — устраняет недетерминизм при tie вероятностью 2⁻¹²⁸. **`determine_winner`** ([line 434-448](../Code/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](Code/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. **`validate_header`** ([crates/mt-consensus/src/lib.rs:108-151](../Code/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](Code/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). **`canonical_proposer`** ([crates/mt-consensus/src/lib.rs:185-203](../Code/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](Code/crates/mt-entry/src/lib.rs)): incremental sort by `nr_sort_key` + canonical apply порядок; pending_count инкрементится после каждого insert для корректности `required_vdf_length` adaptive formula. **`apply_noderegistrations_batch`** ([crates/mt-entry/src/lib.rs:376-417](../Code/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): **Все 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`. - `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](Code/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. **M4-1 closure** ([crates/mt-lottery/src/lib.rs:35-46, 117-129](../Code/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-кодах (отмечено явно). **Все 0 unsafe, 0 panic в lib коде, 0 HashMap, 0 f32/f64, 0 SystemTime.** Контролируемые `expect()` присутствуют только в test-кодах (отмечено явно).
@ -303,11 +303,11 @@ Excellent apply-layer. Полное покрытие validate→apply, корр
| ID | Severity | Описание | Локация | | 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](Code/crates/mt-lottery/src/lib.rs) | | 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](../Code/crates/mt-lottery/src/lib.rs) |
| L-M4-2 | LOW | `validate_winner` ([crates/mt-consensus/src/lib.rs:374-395](Code/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](Code/crates/mt-consensus/src/lib.rs) | | L-M4-2 | LOW | `validate_winner` ([crates/mt-consensus/src/lib.rs:374-395](../Code/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](../Code/crates/mt-consensus/src/lib.rs) |
| L-M4-3 | LOW | `mt-entry::apply_selection_event` ([line 260-303](Code/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](Code/crates/mt-entry/src/lib.rs) | | L-M4-3 | LOW | `mt-entry::apply_selection_event` ([line 260-303](../Code/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](../Code/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](Code/crates/mt-consensus/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](../Code/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](Code/crates/mt-lottery/src/lib.rs)). `seniority_bonus = chain_length / 69`. 69 — magic number; обоснование в спецификации не прочитано (нулевое доверие к доке). Тестами зафиксировано — детерминированно. | [crates/mt-lottery/src/lib.rs:249-251](Code/crates/mt-lottery/src/lib.rs) | | INFO-M4-5 | INFO | `lottery_weight = chain_length_snapshot + seniority_bonus` ([crates/mt-lottery/src/lib.rs:257-259](../Code/crates/mt-lottery/src/lib.rs)). `seniority_bonus = chain_length / 69`. 69 — magic number; обоснование в спецификации не прочитано (нулевое доверие к доке). Тестами зафиксировано — детерминированно. | [crates/mt-lottery/src/lib.rs:249-251](../Code/crates/mt-lottery/src/lib.rs) |
### 7.4 Рекомендации по M4 ### 7.4 Рекомендации по M4
@ -325,7 +325,7 @@ Excellent apply-layer. Полное покрытие validate→apply, корр
### 8.1 Предмет аудита ### 8.1 Предмет аудита
`mt-store` ([crates/mt-store/src/lib.rs](Code/crates/mt-store/src/lib.rs)) — 955 строк. Pure `std::fs` (без RocksDB/sled — minimum deps). Покрывает: `mt-store` ([crates/mt-store/src/lib.rs](../Code/crates/mt-store/src/lib.rs)) — 955 строк. Pure `std::fs` (без RocksDB/sled — minimum deps). Покрывает:
- `FsStore::open` + cleanup orphan `.tmp` файлов; - `FsStore::open` + cleanup orphan `.tmp` файлов;
- save/load AccountTable / NodeTable / CandidatePool через canonical_encode/decode round-trip; - save/load AccountTable / NodeTable / CandidatePool через canonical_encode/decode round-trip;
- Proposal archive (`proposals/{window:020}.bin`); - Proposal archive (`proposals/{window:020}.bin`);
@ -334,24 +334,24 @@ Excellent apply-layer. Полное покрытие validate→apply, корр
### 8.2 Сильные стороны ### 8.2 Сильные стороны
**Atomic write pattern** ([lines 95-114](Code/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. **Atomic write pattern** ([lines 95-114](../Code/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](Code/crates/mt-store/src/lib.rs)) — closure M5-LOW-8 finding. Защита от накопления tmp-файлов после crashed write_atomic (process killed между fs::write tmp и fs::rename). **Cleanup orphan tmp на open()** ([lines 49-71](../Code/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](Code/crates/mt-store/src/lib.rs)) — все decode_X функции отвергают неверный длиной payload до парсинга полей. **Strict CorruptedLength check ДО decode** ([lines 162-167, 201-206, 239-244, 364-370](../Code/crates/mt-store/src/lib.rs)) — все decode_X функции отвергают неверный длиной payload до парсинга полей.
**Crash recovery через `verify_consistency`** ([lines 472-482](Code/crates/mt-store/src/lib.rs)): при reopen проверяет что proposal с window = meta.last_cemented существует в archive. Mismatch (crash между archive и meta write) → error, не silent skip. **Crash recovery через `verify_consistency`** ([lines 472-482](../Code/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](Code/crates/mt-store/src/lib.rs)): сортированный возврат удалённых window indices. Не блокирует read для proposals ≥ threshold. **Pruning через `prune_proposals_before(threshold)`** ([lines 490-515](../Code/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](Code/crates/mt-store/src/lib.rs)) внутри `#[cfg(test)] mod tests` для `rand_suffix()` tempdir naming — НЕ в consensus path. **0 unsafe, 0 panic в production lib коде, 0 HashMap, 0 f32/f64.** SystemTime usage ([line 533](../Code/crates/mt-store/src/lib.rs)) внутри `#[cfg(test)] mod tests` для `rand_suffix()` tempdir naming — НЕ в consensus path.
### 8.3 Слабые стороны ### 8.3 Слабые стороны
| ID | Severity | Описание | Локация | | 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](Code/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](Code/crates/mt-store/src/lib.rs) | | 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](../Code/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](../Code/crates/mt-store/src/lib.rs) |
| INFO-M5-2 | INFO | `cleanup_orphan_tmp` — best-effort: при ошибке `read_dir` либо `fs::remove_file` просто skip ([lines 54-71](Code/crates/mt-store/src/lib.rs)). Tmp-накопление при много раз crashed open's возможно, но не security-critical. | [crates/mt-store/src/lib.rs:54-71](Code/crates/mt-store/src/lib.rs) | | INFO-M5-2 | INFO | `cleanup_orphan_tmp` — best-effort: при ошибке `read_dir` либо `fs::remove_file` просто skip ([lines 54-71](../Code/crates/mt-store/src/lib.rs)). Tmp-накопление при много раз crashed open's возможно, но не security-critical. | [crates/mt-store/src/lib.rs:54-71](../Code/crates/mt-store/src/lib.rs) |
### 8.4 Рекомендации по M5 ### 8.4 Рекомендации по M5
@ -367,7 +367,7 @@ Excellent apply-layer. Полное покрытие validate→apply, корр
### 9.1 Предмет ### 9.1 Предмет
`crates/montana-node/` — singleton-mode реализация полного цикла окна. 9 файлов, ~2 190 строк. Главный файл — [crates/montana-node/src/commands/start.rs](Code/crates/montana-node/src/commands/start.rs) (594 строки). `crates/montana-node/` — singleton-mode реализация полного цикла окна. 9 файлов, ~2 190 строк. Главный файл — [crates/montana-node/src/commands/start.rs](../Code/crates/montana-node/src/commands/start.rs) (594 строки).
### 9.2 Состояние закрытия SPEC_DEVIATIONS DEV-001…DEV-011 ### 9.2 Состояние закрытия SPEC_DEVIATIONS DEV-001…DEV-011
@ -375,15 +375,15 @@ Excellent apply-layer. Полное покрытие validate→apply, корр
| DEV | Заявленное отклонение | Состояние в текущем коде | | 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](Code/crates/montana-node/src/commands/start.rs)) | | 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](../Code/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](Code/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](../Code/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](Code/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](../Code/crates/montana-node/src/commands/start.rs)) |
| DEV-004 | BundledConfirmation никогда не формируется | **закрыт**: формируется и подписывается явно ([start.rs:256-268](Code/crates/montana-node/src/commands/start.rs)) | | DEV-004 | BundledConfirmation никогда не формируется | **закрыт**: формируется и подписывается явно ([start.rs:256-268](../Code/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](Code/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](../Code/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](Code/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](../Code/crates/montana-node/src/commands/start.rs)) |
| DEV-007 | `next_d` не вызывается на τ₂ boundary | **закрыт**: вызывается в конце окна с τ₂ boundary check ([start.rs:393-406](Code/crates/montana-node/src/commands/start.rs)) — **с ограничением: см. ниже M-NODE-1** | | DEV-007 | `next_d` не вызывается на τ₂ boundary | **закрыт**: вызывается в конце окна с τ₂ boundary check ([start.rs:393-406](../Code/crates/montana-node/src/commands/start.rs)) — **с ограничением: см. ниже M-NODE-1** |
| DEV-008 | selection_event с zeros в advance.rs | **закрыт**: файл `advance.rs` физически удалён (`ls commands/` подтверждает) | | DEV-008 | selection_event с zeros в advance.rs | **закрыт**: файл `advance.rs` физически удалён (`ls commands/` подтверждает) |
| DEV-009 | apply_proposal целиком обойдён | **закрыт**: `apply_proposal` вызывается через canonical pipeline ([start.rs:326-332](Code/crates/montana-node/src/commands/start.rs)) | | DEV-009 | apply_proposal целиком обойдён | **закрыт**: `apply_proposal` вызывается через canonical pipeline ([start.rs:326-332](../Code/crates/montana-node/src/commands/start.rs)) |
| DEV-010 | genesis bootstrap auto-detected | **acknowledged** — реализована автодетекция через `NodeLifecycle::is_bootstrap_node` сравнение pubkey байт-в-байт | | DEV-010 | genesis bootstrap auto-detected | **acknowledged** — реализована автодетекция через `NodeLifecycle::is_bootstrap_node` сравнение pubkey байт-в-байт |
| DEV-011 | hardware calibration initial D | **acknowledged** как permanent feature для genesis узла | | DEV-011 | hardware calibration initial D | **acknowledged** как permanent feature для genesis узла |
@ -391,22 +391,22 @@ Excellent apply-layer. Полное покрытие validate→apply, корр
### 9.3 Сильные стороны ### 9.3 Сильные стороны
- **Auto-detection genesis vs candidate** ([start.rs:72-92](Code/crates/montana-node/src/commands/start.rs)) корректно ветвит phase=Active immediately для bootstrap либо phase=CandidateVdf с `target_chain_length = τ₂_windows`. - **Auto-detection genesis vs candidate** ([start.rs:72-92](../Code/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. - **Polностью canonical pipeline** с реальными `validate_*` + `apply_*` через mt-account / mt-consensus / mt-lottery / mt-entry / mt-store.
- **State root self-verify** ([start.rs:342-352](Code/crates/montana-node/src/commands/start.rs)) — corruption диска / памяти detected immediately, panic с descriptive message. - **State root self-verify** ([start.rs:342-352](../Code/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](Code/crates/montana-node/src/commands/start.rs)). - **Graceful shutdown** — SIGINT/SIGTERM handler через `libc::signal` + `AtomicBool`; signal-safe (только atomic store), POSIX async-signal-safe ([start.rs:31-33, 501-513](../Code/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. - **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 Слабые стороны ### 9.4 Слабые стороны
| ID | Severity | Описание | Локация | | ID | Severity | Описание | Локация |
|----|----------|----------|---------| |----|----------|----------|---------|
| M-NODE-1 | MEDIUM | Hardcoded `median_permille = 1000u32` для `next_d` ([start.rs:394](Code/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](Code/crates/montana-node/src/commands/start.rs) | | M-NODE-1 | MEDIUM | Hardcoded `median_permille = 1000u32` для `next_d` ([start.rs:394](../Code/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](../Code/crates/montana-node/src/commands/start.rs) |
| L-NODE-1 | LOW | `IDENTITY_MAGIC = b"mt-local"` ([identity.rs:17](Code/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](Code/crates/montana-node/src/identity.rs) | | L-NODE-1 | LOW | `IDENTITY_MAGIC = b"mt-local"` ([identity.rs:17](../Code/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](../Code/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](Code/crates/montana-node/src/commands/start.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](../Code/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](Code/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](Code/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](../Code/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](../Code/crates/montana-node/src/commands/start.rs) |
| L-NODE-4 | LOW | `control_root = [0u8; 32]` ([line 290](Code/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](Code/crates/montana-node/src/commands/start.rs) | | L-NODE-4 | LOW | `control_root = [0u8; 32]` ([line 290](../Code/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](../Code/crates/montana-node/src/commands/start.rs) |
| INFO-NODE-5 | INFO | `target: u128::MAX` ([line 312](Code/crates/montana-node/src/commands/start.rs)) — placeholder. Singleton = 1 node = winner всегда побеждает (нет конкурентов). Real lottery semantics активируются при ≥ 2 узлах. | [crates/montana-node/src/commands/start.rs:312](Code/crates/montana-node/src/commands/start.rs) | | INFO-NODE-5 | INFO | `target: u128::MAX` ([line 312](../Code/crates/montana-node/src/commands/start.rs)) — placeholder. Singleton = 1 node = winner всегда побеждает (нет конкурентов). Real lottery semantics активируются при ≥ 2 узлах. | [crates/montana-node/src/commands/start.rs:312](../Code/crates/montana-node/src/commands/start.rs) |
### 9.5 Рекомендации по узлу ### 9.5 Рекомендации по узлу
@ -429,16 +429,16 @@ Singleton-mode работает корректно через canonical pipeline
| Константа | Spec значение | Code значение | Файл | | Константа | Spec значение | Code значение | Файл |
|-----------|---------------|---------------|------| |-----------|---------------|---------------|------|
| `HASH_SIZE` | 32 (SHA-256) | 32 | [mt-crypto/src/lib.rs:71](Code/crates/mt-crypto/src/lib.rs) | | `HASH_SIZE` | 32 (SHA-256) | 32 | [mt-crypto/src/lib.rs:71](../Code/crates/mt-crypto/src/lib.rs) |
| `PUBLIC_KEY_SIZE` | 1952 (FIPS 204 ML-DSA-65 level 3) | 1952 | [mt-crypto/src/lib.rs:73](Code/crates/mt-crypto/src/lib.rs) | | `PUBLIC_KEY_SIZE` | 1952 (FIPS 204 ML-DSA-65 level 3) | 1952 | [mt-crypto/src/lib.rs:73](../Code/crates/mt-crypto/src/lib.rs) |
| `SECRET_KEY_SIZE` | 4032 | 4032 | [mt-crypto/src/lib.rs:74](Code/crates/mt-crypto/src/lib.rs) | | `SECRET_KEY_SIZE` | 4032 | 4032 | [mt-crypto/src/lib.rs:74](../Code/crates/mt-crypto/src/lib.rs) |
| `SIGNATURE_SIZE` | 3309 | 3309 | [mt-crypto/src/lib.rs:75](Code/crates/mt-crypto/src/lib.rs) | | `SIGNATURE_SIZE` | 3309 | 3309 | [mt-crypto/src/lib.rs:75](../Code/crates/mt-crypto/src/lib.rs) |
| `KEYPAIR_SEED_SIZE` | 32 (FIPS 204 §3.1 ξ ∈ B³²) | 32 | [mt-crypto/src/lib.rs:77](Code/crates/mt-crypto/src/lib.rs) | | `KEYPAIR_SEED_SIZE` | 32 (FIPS 204 §3.1 ξ ∈ B³²) | 32 | [mt-crypto/src/lib.rs:77](../Code/crates/mt-crypto/src/lib.rs) |
| `MLKEM_PUBLIC_KEY_SIZE` | 1184 (FIPS 203 ML-KEM-768) | 1184 | [mt-crypto/src/lib.rs:79](Code/crates/mt-crypto/src/lib.rs) | | `MLKEM_PUBLIC_KEY_SIZE` | 1184 (FIPS 203 ML-KEM-768) | 1184 | [mt-crypto/src/lib.rs:79](../Code/crates/mt-crypto/src/lib.rs) |
| `MLKEM_SECRET_KEY_SIZE` | 2400 | 2400 | [mt-crypto/src/lib.rs:80](Code/crates/mt-crypto/src/lib.rs) | | `MLKEM_SECRET_KEY_SIZE` | 2400 | 2400 | [mt-crypto/src/lib.rs:80](../Code/crates/mt-crypto/src/lib.rs) |
| `MLKEM_SEED_SIZE` | 64 (FIPS 203 §6.1 d ‖ z) | 64 | [mt-crypto/src/lib.rs:81](Code/crates/mt-crypto/src/lib.rs) | | `MLKEM_SEED_SIZE` | 64 (FIPS 203 §6.1 d ‖ z) | 64 | [mt-crypto/src/lib.rs:81](../Code/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](Code/crates/mt-crypto-native/src/lib.rs)) — обоснованный duplicate для C FFI binding boundary, mild [C-1] violation. Все остальные крейты (`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](../Code/crates/mt-crypto-native/src/lib.rs)) — обоснованный duplicate для C FFI binding boundary, mild [C-1] violation.
### 10.2 Размеры структур (one source per crate) ### 10.2 Размеры структур (one source per crate)
@ -470,17 +470,17 @@ Singleton-mode работает корректно через canonical pipeline
| Параметр | Code значение | Файл | | Параметр | Code значение | Файл |
|----------|---------------|------| |----------|---------------|------|
| `d0` | 325_000_000 | [mt-genesis/src/lib.rs:82](Code/crates/mt-genesis/src/lib.rs) | | `d0` | 325_000_000 | [mt-genesis/src/lib.rs:82](../Code/crates/mt-genesis/src/lib.rs) |
| `tau2_windows` | 20_160 | [mt-genesis/src/lib.rs:84](Code/crates/mt-genesis/src/lib.rs) | | `tau2_windows` | 20_160 | [mt-genesis/src/lib.rs:84](../Code/crates/mt-genesis/src/lib.rs) |
| `emission_moneta` | 13_000_000_000 (13 GɈ baseline) | [mt-genesis/src/lib.rs:85](Code/crates/mt-genesis/src/lib.rs) | | `emission_moneta` | 13_000_000_000 (13 GɈ baseline) | [mt-genesis/src/lib.rs:85](../Code/crates/mt-genesis/src/lib.rs) |
| `confirmation_quorum` | 67/100 | [mt-genesis/src/lib.rs:87-88](Code/crates/mt-genesis/src/lib.rs) | | `confirmation_quorum` | 67/100 | [mt-genesis/src/lib.rs:87-88](../Code/crates/mt-genesis/src/lib.rs) |
| `participation_dead_zone_low/high` | 85/95 (permille × 10) | [mt-genesis/src/lib.rs:89-90](Code/crates/mt-genesis/src/lib.rs) | | `participation_dead_zone_low/high` | 85/95 (permille × 10) | [mt-genesis/src/lib.rs:89-90](../Code/crates/mt-genesis/src/lib.rs) |
| `d_adjustment_rate` | 3/100 | [mt-genesis/src/lib.rs:91-92](Code/crates/mt-genesis/src/lib.rs) | | `d_adjustment_rate` | 3/100 | [mt-genesis/src/lib.rs:91-92](../Code/crates/mt-genesis/src/lib.rs) |
| `vdf_entry_windows` | 20_160 = τ₂ | [mt-genesis/src/lib.rs:93](Code/crates/mt-genesis/src/lib.rs) | | `vdf_entry_windows` | 20_160 = τ₂ | [mt-genesis/src/lib.rs:93](../Code/crates/mt-genesis/src/lib.rs) |
| `selection_interval` | 336 (τ₂/336 = 60 раз/сутки) | [mt-genesis/src/lib.rs:94](Code/crates/mt-genesis/src/lib.rs) | | `selection_interval` | 336 (τ₂/336 = 60 раз/сутки) | [mt-genesis/src/lib.rs:94](../Code/crates/mt-genesis/src/lib.rs) |
| `admission_divisor` | 130 (M4-LOW-7 closure: ранее hardcoded const, теперь в ProtocolParams) | [mt-genesis/src/lib.rs:95](Code/crates/mt-genesis/src/lib.rs) | | `admission_divisor` | 130 (M4-LOW-7 closure: ранее hardcoded const, теперь в ProtocolParams) | [mt-genesis/src/lib.rs:95](../Code/crates/mt-genesis/src/lib.rs) |
| `candidate_expiry_windows` | 60_480 = 3τ₂ | [mt-genesis/src/lib.rs:96](Code/crates/mt-genesis/src/lib.rs) | | `candidate_expiry_windows` | 60_480 = 3τ₂ | [mt-genesis/src/lib.rs:96](../Code/crates/mt-genesis/src/lib.rs) |
| `pruning_idle_windows` | 80_640 = 4τ₂ | [mt-genesis/src/lib.rs:99](Code/crates/mt-genesis/src/lib.rs) | | `pruning_idle_windows` | 80_640 = 4τ₂ | [mt-genesis/src/lib.rs:99](../Code/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. 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.
@ -514,9 +514,9 @@ VERSION.md заявляет spec target = `Montana v35.3.2 (2026-04-28)`. AUDIT.
## 12. Архитектурные наблюдения сверх M0…M5 scope ## 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). 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](Code/Cargo.toml)) — корректно для consensus-критичного бинарника. Никакого unwinding с partially-modified state. 2. **`panic = "abort"` в release профиле** (Cargo.toml:21) — корректно для consensus-критичного бинарника. Никакого unwinding с partially-modified state.
3. **`overflow-checks = true` в обоих dev и release профилях** ([Cargo.toml:22, 25](Code/Cargo.toml)) — предотвращает silent wrap. Все consensus-арифметика дополнительно использует `checked_*` либо `saturating_*` per [I-9]. 3. **`overflow-checks = true` в обоих dev и release профилях** (Cargo.toml:22, 25) — предотвращает 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](Code/docker/release-build.dockerfile)) — CI gate `reproducible_release` в .github/workflows/ci.yml. 4. **Reproducible release builds** через Docker container с pinned base image (`debian:bookworm-slim@sha256:40b107342c492725bc7aacbe93a49945445191ae364184a6d24fedb28172f6f7`) ([docker/release-build.dockerfile](../Code/docker/release-build.dockerfile)) — CI gate `reproducible_release` в .github/workflows/ci.yml.
--- ---