From 4c1a7311847e03ef7530937365a8f1b2c566ec54 Mon Sep 17 00:00:00 2001 From: efir369999 Date: Wed, 6 May 2026 01:09:28 +0300 Subject: [PATCH] fix: broken markdown refs auto-resolved --- .../claude-opus-4-7-1m_2026-04-28_T2023.md | 242 +++++++++--------- 1 file changed, 121 insertions(+), 121 deletions(-) diff --git a/Montana-Protocol/External-Audit/claude-opus-4-7-1m_2026-04-28_T2023.md b/Montana-Protocol/External-Audit/claude-opus-4-7-1m_2026-04-28_T2023.md index c7bd194..47a26cc 100644 --- a/Montana-Protocol/External-Audit/claude-opus-4-7-1m_2026-04-28_T2023.md +++ b/Montana-Protocol/External-Audit/claude-opus-4-7-1m_2026-04-28_T2023.md @@ -39,7 +39,7 @@ - *код ↔ исполнение* — прогон полного набора тестов в один поток (`.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](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 ещё не существует. @@ -55,12 +55,12 @@ zeroize = =1.8.1 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`; - `libc = =0.2.169` — POSIX `mlock`/`munlock`/`signal`; - `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. @@ -72,64 +72,64 @@ getrandom = =0.2.15 | Компонент | Файл | Lines | Назначение | |-----------|------|-------|------------| -| `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-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 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-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-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 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/ | 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](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`. -**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 через `==`); - `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 `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 байт). -**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)): -- 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 байта); -- 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 ограничение; +**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-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 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` (ни одной утечки). **Покрытие 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); -- 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); -- 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 файла: +- 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; +- 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»; +- 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_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](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)` зашит как константа; - `init_wordlist()` ассертит совпадение, лексикографическую сортировку и ровно 2048 строк — не проходит инициализация при corruption встроенного wordlist; - `pub fn word_index(word) -> Option` через `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 Слабые стороны | 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-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) | +| 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) | +| 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. | (отсутствие теста) | ### 4.4 Рекомендации по M1 @@ -150,36 +150,36 @@ getrandom = =0.2.15 | Компонент | Файл | 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-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-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-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-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) | ### 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 (детерминированная итерация); - `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>` 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. - 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; - 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](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_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](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 функции). +- `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 функции). - 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 крейтам. @@ -188,9 +188,9 @@ getrandom = =0.2.15 | 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-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) | +| 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) | +| 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 @@ -207,7 +207,7 @@ Solid M2 layer с очень чистой реализацией SMT и корр ### 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); - `validate_*` + `apply_*` пары; - `op_hash` через `mt-op` domain separator; @@ -221,22 +221,22 @@ Solid M2 layer с очень чистой реализацией SMT и корр **Полное покрытие validate→apply разделения**: - Каждый 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_change_key` явно требует подпись **старым** ключом ([lines 288-303](Code/crates/mt-account/src/lib.rs)); +- `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)); - `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](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); - Lookup `node.operator_account_id` через NodeTable + assert existence (protocol invariant); - `checked_add` на operator.balance с descriptive panic при overflow на u128::MAX (≈ 10³⁸ nɈ — практически недостижимо); - Зачислённая сумма = `params.emission_moneta` (SSOT через `mt-genesis`). -**`build_genesis_state`** ([lines 656-701](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 node (chain_length=1 для invariant DS-2 weighted_ticket); - empty Candidate Pool; @@ -248,8 +248,8 @@ Solid M2 layer с очень чистой реализацией SMT и корр | 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) | -| 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) | +| 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) | ### 6.4 Рекомендации по M3 @@ -268,34 +268,34 @@ Excellent apply-layer. Полное покрытие validate→apply, корр | Компонент | Файл | 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-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-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-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 Сильные стороны -**`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); - максимальная ошибка 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](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). -**`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): - `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-кодах (отмечено явно). @@ -303,11 +303,11 @@ Excellent apply-layer. Полное покрытие validate→apply, корр | 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) | -| 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) | -| 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) | +| 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-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-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 @@ -325,7 +325,7 @@ Excellent apply-layer. Полное покрытие validate→apply, корр ### 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` файлов; - save/load AccountTable / NodeTable / CandidatePool через canonical_encode/decode round-trip; - Proposal archive (`proposals/{window:020}.bin`); @@ -334,24 +334,24 @@ Excellent apply-layer. Полное покрытие validate→apply, корр ### 8.2 Сильные стороны -**Atomic write pattern** ([lines 95-114](Code/crates/mt-store/src/lib.rs)): `write_atomic` пишет в `.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` пишет в `.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 Слабые стороны | 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) | -| 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) | +| 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) | ### 8.4 Рекомендации по M5 @@ -367,7 +367,7 @@ Excellent apply-layer. Полное покрытие validate→apply, корр ### 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 @@ -375,15 +375,15 @@ Excellent apply-layer. Полное покрытие validate→apply, корр | 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-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-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-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-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-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-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-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-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-011 | hardware calibration initial D | **acknowledged** как permanent feature для genesis узла | @@ -391,22 +391,22 @@ Excellent apply-layer. Полное покрытие validate→apply, корр ### 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. -- **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)). +- **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)). - **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](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-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-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) | +| 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-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-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) | ### 9.5 Рекомендации по узлу @@ -429,16 +429,16 @@ Singleton-mode работает корректно через canonical pipeline | Константа | Spec значение | Code значение | Файл | |-----------|---------------|---------------|------| -| `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) | -| `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) | -| `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_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) | +| `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) | +| `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) | +| `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_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) | -Все остальные крейты (`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) @@ -470,17 +470,17 @@ Singleton-mode работает корректно через canonical pipeline | Параметр | Code значение | Файл | |----------|---------------|------| -| `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) | -| `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) | -| `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) | -| `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) | -| `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) | -| `pruning_idle_windows` | 80_640 = 4τ₂ | [mt-genesis/src/lib.rs:99](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) | +| `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) | +| `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) | +| `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) | +| `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) | +| `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. @@ -514,9 +514,9 @@ VERSION.md заявляет spec target = `Montana v35.3.2 (2026-04-28)`. AUDIT. ## 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](Code/Cargo.toml)) — корректно для 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]. -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. +2. **`panic = "abort"` в release профиле** (Cargo.toml:21) — корректно для consensus-критичного бинарника. Никакого unwinding с partially-modified state. +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. ---