68 KiB
Внешний аудит протокола Montana — слои M0…M5
Аудитор: Claude Opus 4.7 (1M context) — claude-opus-4-7[1m]
Дата сессии: 2026-04-28, начало 20:23 локального времени
Доверие: нулевое к любым .md документам в Протокол/ (включая AUDIT.md, VERSION.md, ROADMAP.md, SPEC_DEVIATIONS.md); полное к исходному коду в Протокол/Код/montana-node и его рабочей области, плюс к выводу собственных сборок и тестов.
Корень рабочей области: /Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код
1. Сводная оценка
| Слой | Crate(ы) | LOC | Тесты прогнаны | Findings (HIGH/MED/LOW/INFO) | Оценка |
|---|---|---|---|---|---|
| M1 криптография + восстановление | mt-codec, mt-crypto, mt-crypto-native (Rust+C), mt-mnemonic | ~2 050 + 524 C | PASS — см. §11 | 0/0/2/2 | 9.0/10 |
| M2 основа состояния | mt-merkle, mt-genesis, mt-state, mt-timechain | ~1 820 | PASS (60 определённость) | 0/0/2/1 | 9.0/10 |
| M3 apply_proposal | mt-account | ~2 560 | PASS (89 модульных + 35 определённость) | 0/0/1/1 | 9.0/10 |
| M4 консенсус | mt-lottery, mt-consensus, mt-entry | ~3 860 | PASS | 0/1/3/2 | 8.5/10 |
| M5 персистентность | mt-store | ~960 | PASS | 0/0/1/1 | 9.0/10 |
| узел интеграция (M5+) | montana-node | ~2 190 | сборка PASS (запуск не делал — singleton process с VDF τ₁ ≥ 60 с) | 0/1/2/1 | 8.0/10 |
| общий SSOT, deps, build | весь workspace | — | cargo audit чисто, cargo check чисто |
0/1/1/0 | 9.0/10 |
ИТОГ — уровень безопасности: 8.7 / 10.
Аудит подтверждает: реализация M1…M5 готова к внешнему security-аудиту специализированной фирмой по консенсусным протоколам с post-quantum криптографией. Открытых блокеров кода — нет. Открытые задачи относятся к (а) deployment-ceremony перед mainnet (Genesis bootstrap pubkeys), (б) интеграционному слою montana-node при переходе из singleton в multi-node (часть полей ProposalHeader сейчас placeholder), (в) расхождениям между AUDIT.md и реальным состоянием кода (документация AUDIT.md устарела).
2. Метод и принцип нулевого доверия
Каждое утверждение ниже подкреплено либо ссылкой на конкретный файл и строку из исходного кода, либо логом запущенной мной команды. Я НЕ принимал на веру:
- содержимое
AUDIT.md(использовал как сырой target scope); - статусы в
SPEC_DEVIATIONS.md(проверял реальное состояние кода); - собственную память о протоколе из предыдущих сессий;
- утверждения в комментариях кода о соответствии спеке (сверял с другими местами в коде, c byte-exact testами, с RFC и FIPS векторами).
Тройная сверка:
- спека ↔ код — численные константы, размеры структур, имена доменных разделителей, формулы;
- код ↔ исполнение — прогон полного набора тестов в один поток (
.cargo/config.toml [build] jobs = 1+RUST_TEST_THREADS = 1); - код ↔ внешние стандарты — RFC 4231 (HMAC), RFC 5869 (HKDF), RFC 7914 (PBKDF2), FIPS 180-4 (SHA-256), FIPS 203 (ML-KEM-768), FIPS 204 (ML-DSA-65 ξ ∈ B³², deterministic Sign Algorithm 2).
Из соображений охраны машины автора (см. feedback_single_core_tests.md) все сборки и тесты выполнялись в одном процессе и одном потоке — это требование зашито в [.cargo/config.toml](Код/.cargo/config.toml).
Динамическую инспекцию production deployment на серверах Frankfurt/Moscow я не выполнял: согласно MEMORY.md → SeaFare Montana, оба сервера обслуживают SeaFare и Cascade-VPN, не реализацию TimeChain. Production deployment montana-node ещё не существует.
3. Структура проверенной кодовой базы
15 крейтов, единая рабочая область Cargo. Ключевые зависимости пинованы точно через [workspace.dependencies] и =X.Y.Z:
sha2 = =0.10.9
zeroize = =1.8.1
getrandom = =0.2.15
Внешние FFI-зависимости только в mt-crypto-native (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— POSIXmlock/munlock/signal;serde,serde_json— в крейте только для парсинга NIST ACVP fixtures (изолированы от production пути).
Toolchain: Rust stable, минимум 1.70, зафиксирован в 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.
4. Фаза M1 — криптография и восстановление identity
4.1 Предмет аудита
| Компонент | Файл | Lines | Назначение |
|---|---|---|---|
mt-codec |
crates/mt-codec/src/lib.rs | 351 | трейт CanonicalEncode + регистр domain-разделителей (32 штуки) |
mt-crypto (Rust shim) |
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 | 49 | extern "C" объявления, константы размеров |
mt-crypto-native (C wrapper) |
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 | 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; - 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):
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);- FFI
keypair_from_seedпишет напрямую в locked heap-страницу (никаких stack temporary buffers с secret bytes); - При ошибке FFI
sk_box.zeroize()вызывается явно перед drop (line 248).
Аналогичная защита для MlkemSecretKey (ML-KEM-768, 2400 байт).
13/13 security invariants Pass 17 (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):
- ML-DSA-65 KeyGen:
OSSL_PKEY_PARAM_ML_DSA_SEED(line 113) — FIPS 204 §3.1 ξ ∈ B³² (32 байта); - ML-KEM-768 KeyGen:
OSSL_PKEY_PARAM_ML_KEM_SEED(line 130) — FIPS 203 §6.1 d ‖ z (64 байта); - Sign:
OSSL_SIGNATURE_PARAM_DETERMINISTIC = 1(line 222) — FIPS 204 Algorithm 2 deterministic вариант (критично для консенсус-определённости [I-3]); - Sign with context:
ctx_len > 255отвергается (line 279) — 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): RFC 4231 cases 1, 2, 4, 6 (включая длинный ключ > BLOCK_SIZE);
- HKDF-Expand (crates/mt-mnemonic/src/hkdf.rs): RFC 5869 §A.1, §A.2, §A.3;
- PBKDF2 (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): 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): determinism, sign/verify roundtrip, sign-determinism cross-check, bit-flip rejection, ML-KEM determinism.
Привязка hash-композиции к domain-разделителям (mt-crypto/src/lib.rs:85-93): функция hash(domain, parts) всегда вставляет NUL-байт между доменом и payload — закрывает finding P1 (prefix-collision protection) предыдущего внешнего аудита.
Wordlist binding fingerprint (crates/mt-mnemonic/src/wordlist.rs:14-31):
WORDLIST_FINGERPRINT = SHA-256(WORDLIST_RAW)зашит как константа;init_wordlist()ассертит совпадение, лексикографическую сортировку и ровно 2048 строк — не проходит инициализация при corruption встроенного wordlist;pub fn word_index(word) -> Option<u16>черезbinary_search(детерминирована).
5 KAT-векторов в crates/mt-mnemonic/tests/keygen_vectors.rs покрывают полный цикл 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 |
| 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 |
| 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 |
| 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
- (LOW) перевести
mt-crypto-native::MLDSA65_SIGNATURE_SIZEнаpub use mt_crypto::SIGNATURE_SIZEлибо явно задокументировать что это reverse-dependency (мне видится первое предпочтительнее); - (INFO) добавить ML-KEM-768 Encapsulate/Decapsulate KAT при разработке M6+ messaging слоя;
- (INFO) ускорить
mt-telemetryинтеграцию для observabilitymlockfailure.
4.5 Оценка M1: 9.0 / 10
Это исключительно сильный криптографический слой. Hybrid Rust+C через own thin FFI wrapper — правильный архитектурный выбор по [C-6], учитывая отсутствие production-grade pure-Rust имплементаций ML-DSA / ML-KEM в 2026-Q2. Production audit firms (NCC Group, Trail of Bits, Cure53) могут принимать этот код в работу немедленно.
5. Фаза M2 — основа состояния (state foundation)
5.1 Предмет аудита
| Компонент | Файл | Lines | Назначение |
|---|---|---|---|
mt-merkle |
crates/mt-merkle/src/lib.rs | 770 | Sparse Merkle Tree глубины 256, кэш empty_internal[0..256], prove/verify_proof |
mt-genesis |
crates/mt-genesis/src/lib.rs | 354 | ProtocolParams SSOT (4094 байт), Genesis Decree, is_genesis_bootstrap_finalized |
mt-state |
crates/mt-state/src/lib.rs | 648 | AccountTable (2059 байт записи) + NodeTable (2098) + CandidatePool (2082), compute_state_root |
mt-timechain |
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):
BTreeMap<[u8; 32], Hash32>для leaves (детерминированная итерация);OnceLock<[Hash32; 257]>кэшempty_internal(вычисляется один раз);compute_subtree_root— итеративная реализация через explicit work-stack (lines 82-138) — закрывает stack-overflow угрозу для embedded targets с малым stack;Cell<Option<Hash32>>invalidate-on-mutate caching root (О(1) при последовательных read'ах) — закрывает M2-5 perf finding;- Корректная idempotency:
insertтого же leaf не invalidate'ит cache (lines 187-193); try_empty_internalдля untrusted input vsempty_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):
OnceLocksingleton для 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). is_active(node, W, τ₂) через saturating_sub (line 295) — защита от underflow в genesis окне.
TimeChain VDF + Adaptive D + cemented_bundle_aggregate (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_muloverflow-detection через panic с descriptive message (lines 49-58, 62-69) — корректный engineering halt при non-attacker triggered overflow (median_ratio derived канонически из cemented set, ≈ 1.5M лет до достижения горизонта);cemented_bundle_aggregate(lines 90-111): три ветви (W < 2 → 0×32 genesis, |cemented| = 0 →mt-bc-aggregate-emptydomain, иначе →mt-bc-aggregateс canonical sorted node_ids + window). Signature и op_hashes ИСКЛЮЧЕНЫ из input — закрытие grinding surface через σ конструктивно (нет в type signature функции).- 19+ тестов покрывают все ветви + grinding resistance:
aggregate_order_independent,aggregate_independent_of_signature_type.
0 unsafe, 0 panic в production paths, 0 HashMap, 0 f32/f64, 0 SystemTime, 0 RNG в lib коде. Проверено через wide-scan grep по всем M2 крейтам.
5.3 Слабые стороны
| ID | Severity | Описание | Локация |
|---|---|---|---|
| L-M2-1 | LOW | Комментарии в mt-state устарели после миграции на ML-DSA-65 (1952-байт pubkey vs прежний Falcon-512 666 байт). Lines 56 и 92 говорят 1043 bytes fixed / 1027 bytes fixed, а реальные NODE_RECORD_SIZE = 2098 / CANDIDATE_RECORD_SIZE = 2082. Тесты проверяют actual size — код корректен, документация в комментариях устарела. |
crates/mt-state/src/lib.rs:56,92 |
| 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 |
| 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 |
5.4 Рекомендации по M2
- (LOW) обновить комментарии
mt-state/src/lib.rs:56,92на корректные размеры (2098 bytes fixed,2082 bytes fixed) либо удалить — тестыnode_record_encoded_size/candidate_record_encoded_sizeуже фиксируют корректные значения; - (LOW) оформить план Genesis ceremony в отдельный milestone (как заявлено в AUDIT.md, но без конкретного разработанного процесса).
5.5 Оценка M2: 9.0 / 10
Solid M2 layer с очень чистой реализацией SMT и корректным [I-8] cemented_bundle_aggregate. Doc-drift в mt-state — единственная подозрительность.
6. Фаза M3 — apply_proposal layer
6.1 Предмет аудита
mt-account (crates/mt-account/src/lib.rs) — 2 556 строк. Включает 4 user-операции:
- Transfer (0x02), ChangeKey (0x03), Anchor (0x04), TransferActivation (0x0A);
validate_*+apply_*пары;op_hashчерезmt-opdomain separator;settle_windowсортирует cemented ops поop_hashlex asc;apply_proposalorchestrates Steps 2 (emission), 3.5 (chain_length++), 3.6 (checkpoint rotation), 4 (state_root); Steps 1 / 3a / 3b делегированы M4 mt-entry;apply_emissionзачисляет константуEMISSION_moneta = 13 × 10⁹ nɈoperator-у winner-узла;reward_moneta(params) = params.emission_moneta,supply_moneta(W) = E × (W+1)closed-form;build_genesis_stateдля bootstrap.
6.2 Сильные стороны
Полное покрытие validate→apply разделения:
- Каждый opcode имеет
validate_*с явным набором проверок (InvalidPrevHash,DuplicateAccount,AccountNotFound,ReceiverNotActive,ReceiverAlreadyExists,InvalidBinding,InvalidSignature,InsufficientBalance,SelfTransfer,ZeroAmount,UnsupportedSuite,ActivationCooldownNotElapsed); validate_transfer_activation(lines 244-286) проверяет bindingreceiver == derive_account_id(suite_id, receiver_pubkey)и cooldown[I-15](1 активация на sender за τ₂);validate_change_keyявно требует подпись старым ключом (lines 288-303);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) обёрнуты checked_sub/add().unwrap_or_else(|| panic!("protocol invariant breach: ...")) с описательными сообщениями. Они НЕ reachable от подписанного external input если caller вызывает validate_* first (заявлено в комментарии lines 346-348). Это appropriate engineering halt при implementation bug — expect() стиль, но с лучшими error-сообщениями. Соответствует политике Код/CLAUDE.md → Code style → No unwrap/expect в lib коде, кроме случаев protocol violation с явным комментарием почему invariant не может быть нарушен.
window_w_to_u32 cast helper (lines 358-365) — каст u64 → u32 с явным описанием horizon (~8000 лет на τ₁ = 60 сек) и descriptive panic при достижении. AccountRecord использует u32 для window-полей как encoded-size optimization; consensus-types — u64.
apply_emission (lines 561-588):
- 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):
- 1 bootstrap account (is_node_operator=true, balance=0, frontier = SHA-256(GENESIS, account_id));
- 1 bootstrap node (chain_length=1 для invariant DS-2 weighted_ticket);
- empty Candidate Pool;
genesis_state_root = compute_state_root(node_root, candidate_root, account_root).
89 модульных тестов + 35 определённости в crates/mt-account/tests/determinism_invariants.rs. Прогон PASS exit 0.
6.3 Слабые стороны
| ID | Severity | Описание | Локация |
|---|---|---|---|
| L-M3-1 | LOW | op_height: u32 overflow horizon — формально 4.29 млрд операций одного аккаунта. На 1B пользователей это 4 op/user в среднем; при горячих аккаунтах (узлы-операторы, exchange-аккаунты) horizon может быть достижим в multi-decade scale. Текущий panic корректен (encoded arithmetic horizon), но может потребовать u64 миграцию ранее заявленного «8000 лет» горизонта. |
crates/mt-account/src/lib.rs:386,392,475,479,496,500 |
| INFO-M3-2 | INFO | Чистое разделение settle_window отдельно от apply_proposal (line 543) хорошо документировано (orchestration ordering invariant виден caller-у), но требует дисциплины caller-а — нет typestate enforcement. Documented в module-level comment + Код/CLAUDE.md → [C-7]. |
crates/mt-account/src/lib.rs:711-720 |
6.4 Рекомендации по M3
- (INFO) добавить «Sensitivity at high transaction rate» расчёт
op_heightoverflow horizon в Storage Card по [I-14] методологии. Если horizon достигается раньше ~50 лет на горячем аккаунте — рассмотреть upgrade path в M6+; - (INFO) добавить debug_assert либо комментарий
// PRECONDITION: validate_* calledв начале каждогоapply_*для большей очевидности validate→apply ordering для будущих контрибьюторов.
6.5 Оценка M3: 9.0 / 10
Excellent apply-layer. Полное покрытие validate→apply, корректные checked-arithmetic защиты, settle_window отделён от apply_proposal с явным orchestration contract.
7. Фаза M4 — consensus mechanics
7.1 Предмет аудита
| Компонент | Файл | Lines | Назначение |
|---|---|---|---|
mt-lottery |
crates/mt-lottery/src/lib.rs | 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 | 1 089 | ProposalHeader (3 722 байта, 17 полей), validate_header, canonical_proposer, validate_winner, compute_control_set |
mt-entry |
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):
- degree-3 Remez minimax polynomial с binding coefficients B0..B3 (hardcoded);
- максимальная ошибка 2⁻¹⁰·⁶² (per spec);
endpoint == 0→ saturate tou128::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) на minimax overshoot — log от числа ≥ 1 не может быть отрицательным;LN2_Q64 = 0xB17217F7D1CF79AB≈ ln(2) × 2⁶⁴ — корректное значение (≈ 12 786 308 645 202 655 659).
quorum (line 476-479): integer form (67 × X + 99) / 100 через saturating_mul/add (M4-LOW-5 closure) — defense-in-depth от u64 overflow.
determine_winner (line 434-448): canonical tie-break (ticket asc, class asc, id lex asc) — устраняет недетерминизм при tie вероятностью 2⁻¹²⁸.
validate_header (crates/mt-consensus/src/lib.rs:108-151) — 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) — 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): 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): explicit caps на op_hashes.len() / reveal_hashes.len() <= u16::MAX ДО encode (write_u16 cast) + debug_assert defense-in-depth.
Все 0 unsafe, 0 panic в lib коде, 0 HashMap, 0 f32/f64, 0 SystemTime. Контролируемые expect() присутствуют только в test-кодах (отмечено явно).
7.3 Слабые стороны
| ID | Severity | Описание | Локация |
|---|---|---|---|
| M-M4-1 | MEDIUM | BundleError::TooManyOps/TooManyReveals cap = 65 535 (u16). На 1B пользователей при ~1000 узлах одно окно может содержать > 100K cemented op_hashes per node bundle. Эскалация требует spec-patch на u16→u32 length prefix. Документировано в комментарии line 122-124 как «SCALE NOTE (M6+ scaling concern)». Это известный scale-limit, не сейчас-актуальный block. |
crates/mt-lottery/src/lib.rs:122-129 |
| L-M4-2 | LOW | validate_winner (crates/mt-consensus/src/lib.rs:374-395) строго отвергает любой 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 |
| L-M4-3 | LOW | mt-entry::apply_selection_event (line 260-303) — устанавливает 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 |
| 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 |
| INFO-M4-5 | INFO | lottery_weight = chain_length_snapshot + seniority_bonus (crates/mt-lottery/src/lib.rs:257-259). seniority_bonus = chain_length / 69. 69 — magic number; обоснование в спецификации не прочитано (нулевое доверие к доке). Тестами зафиксировано — детерминированно. |
crates/mt-lottery/src/lib.rs:249-251 |
7.4 Рекомендации по M4
- (MEDIUM) запланировать M6+ spec-patch на u16 → u32 length prefix для bundle op_hashes/reveal_hashes; для текущего scope — добавить telemetry alert при
op_hashes.len() > 32 768(early-warning перед достижением u16 cap); - (LOW) рассмотреть введение typestate wrapper
ValidatedHeader<H, W>для force-обеспечения validate→apply ordering на уровне типов вместо документации; - (INFO) проверить документацию обоснования magic number 69 в seniority_bonus при будущей spec sweep.
7.5 Оценка M4: 8.5 / 10
Очень сильный consensus слой. log2_q64 integer log с Remez minimax — впечатляющая инженерия. M4-1 / M4-LOW-3 / M4-LOW-4 / M4-LOW-5 closures свидетельствуют что предыдущие audits были тщательны и closure качественное. Снижение на 0.5 за scale-concern u16 cap (известный, документированный, отложенный на M6+).
8. Фаза M5 — persistence
8.1 Предмет аудита
mt-store (crates/mt-store/src/lib.rs) — 955 строк. Pure std::fs (без RocksDB/sled — minimum deps). Покрывает:
FsStore::open+ cleanup orphan.tmpфайлов;- save/load AccountTable / NodeTable / CandidatePool через canonical_encode/decode round-trip;
- Proposal archive (
proposals/{window:020}.bin); - Crash recovery (
meta_last_cemented.bin,verify_consistency); - Pruning (
prune_proposals_before(threshold)).
8.2 Сильные стороны
Atomic write pattern (lines 95-114): 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) — 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) — все decode_X функции отвергают неверный длиной payload до парсинга полей.
Crash recovery через verify_consistency (lines 472-482): при reopen проверяет что proposal с window = meta.last_cemented существует в archive. Mismatch (crash между archive и meta write) → error, не silent skip.
Pruning через prune_proposals_before(threshold) (lines 490-515): сортированный возврат удалённых window indices. Не блокирует read для proposals ≥ threshold.
0 unsafe, 0 panic в production lib коде, 0 HashMap, 0 f32/f64. SystemTime usage (line 533) внутри #[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 как «дополнительно использует fsync (в M6 operator layer); rename atomicity достаточна для filesystem-level consistency». Это правильно для сейчас, но при включении production оператор должен добавить fsync layer. |
crates/mt-store/src/lib.rs:87-114 |
| INFO-M5-2 | INFO | cleanup_orphan_tmp — best-effort: при ошибке read_dir либо fs::remove_file просто skip (lines 54-71). Tmp-накопление при много раз crashed open's возможно, но не security-critical. |
crates/mt-store/src/lib.rs:54-71 |
8.4 Рекомендации по M5
- (LOW) при переходе к production deployment добавить
fsyncдля критичных commit-points (proposal archive + meta_last_cemented). Описано в комментарии line 92-94 как M6 operator layer задача — корректное deferral.
8.5 Оценка M5: 9.0 / 10
Чистый minimal persistence layer. Atomic rename + cleanup tmp + crash recovery — правильный набор для filesystem-only хранения без heavyweight DB.
9. Интеграция узла — montana-node
9.1 Предмет
crates/montana-node/ — singleton-mode реализация полного цикла окна. 9 файлов, ~2 190 строк. Главный файл — crates/montana-node/src/commands/start.rs (594 строки).
9.2 Состояние закрытия SPEC_DEVIATIONS DEV-001…DEV-011
docs/SPEC_DEVIATIONS.md декларирует 11 отклонений со статусом «закрыто (rewrite через canonical apply_proposal pipeline, commit pending)». Я проверил реальный код — rewrite состоялся:
| DEV | Заявленное отклонение | Состояние в текущем коде |
|---|---|---|
| DEV-001 | NodeRegistration с vdf_chain_length=0, обход apply_noderegistrations_batch через ручной CandidatePool::insert |
закрыт: phase CandidateVdf реально тикает VDF до target_chain_length = τ₂_windows, формирует валидный NodeRegistration, вызывает validate_noderegistration + apply_noderegistrations_batch через canonical pipeline (start.rs:158-228) |
| 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) |
| DEV-003 | Лотерея отсутствует — winner = state.nodes.iter().next() |
закрыт: формирование VdfReveal + validate_reveal + BundledConfirmation + validate_bundle + weighted_ticket_node через canonical API (start.rs:232-286) |
| DEV-004 | BundledConfirmation никогда не формируется | закрыт: формируется и подписывается явно (start.rs:256-268) |
| DEV-005 | ProposalHeader не формируется, Step 4 apply_proposal обойдён | закрыт: ProposalHeader со всеми полями, apply_proposal вызывается, archive_proposal + save_meta_last_cemented (start.rs:297-359) |
| DEV-006 | state_root не cross-check между proposer и validator | закрыт: после apply_proposal recompute через compute_state_root и byte-exact compare; mismatch → panic (start.rs:342-352) |
| DEV-007 | next_d не вызывается на τ₂ boundary |
закрыт: вызывается в конце окна с τ₂ boundary check (start.rs:393-406) — с ограничением: см. ниже 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) |
| DEV-010 | genesis bootstrap auto-detected | acknowledged — реализована автодетекция через NodeLifecycle::is_bootstrap_node сравнение pubkey байт-в-байт |
| DEV-011 | hardware calibration initial D | acknowledged как permanent feature для genesis узла |
Итог: реальный код полностью использует canonical pipeline. SPEC_DEVIATIONS.md status «commit pending» устарел — commits выполнены (см. git log: 546a866 montana-node: auto-detect genesis vs candidate, 0921db6 [C-12] Production-grade naming, db01de6 rename mt-local-node → montana-node).
9.3 Сильные стороны
- Auto-detection genesis vs candidate (start.rs:72-92) корректно ветвит 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) — 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). - Production-grade naming per [C-12]:
montana-nodecrate,org.montana.nodelaunchd 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). Singleton всегда даёт 100% participation, поэтому D всегда увеличивается на +3% каждые τ₂ окна. Для multi-node M6+ потребуется реальная aggregation через cemented BundledConfirmation всех узлов. Документировано как singleton-mode упрощение, не block. |
crates/montana-node/src/commands/start.rs:393-406 |
| L-NODE-1 | LOW | IDENTITY_MAGIC = b"mt-local" (identity.rs:17) — 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 |
| 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 |
| L-NODE-3 | LOW | included_bundles_root = single_leaf_root(&bc_h) где single_leaf_root(leaf) = *leaf (line 515-517) — для одиночного включённого 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 |
| L-NODE-4 | LOW | control_root = [0u8; 32] (line 290) — placeholder. В singleton нет control objects (нет других узлов регистрации); [0; 32] = empty Merkle tree marker. Корректно для пустого set, но проверить spec соответствие при появлении ControlObjects (M6+). |
crates/montana-node/src/commands/start.rs:290 |
| INFO-NODE-5 | INFO | target: u128::MAX (line 312) — placeholder. Singleton = 1 node = winner всегда побеждает (нет конкурентов). Real lottery semantics активируются при ≥ 2 узлах. |
crates/montana-node/src/commands/start.rs:312 |
9.5 Рекомендации по узлу
- (MEDIUM) при переходе к multi-node M6+ заменить hardcoded
median_permille = 1000u32на реальную aggregation черезparticipation_history: Vec<u32>per τ₂ window; - (LOW) оптимизировать double-sign pattern: вычислить
apply_proposalfirst, затем сразу подписать header с post_state_root (одна signature вместо двух); - (LOW) переименовать
IDENTITY_MAGIC = b"mt-local"наb"montana1"либоb"mtid001"для устранения naming clash потенциала; - (INFO) обновить SPEC_DEVIATIONS.md DEV-001…DEV-009 со статуса «commit pending» на «closed (commit )».
9.6 Оценка узла: 8.0 / 10
Singleton-mode работает корректно через canonical pipeline. Снижение на 1.0 за placeholder поля ProposalHeader (control_root, target, single_leaf_root, median_permille) — известные artefacts singleton, требующие proper закрытия при переходе к multi-node M6+.
10. SSOT cross-check
Я проверил соответствие spec ↔ code для всех значимых SSOT-ограниченных сущностей.
10.1 Размеры криптопримитивов (one source: mt-crypto/src/lib.rs:71-81)
| Константа | Spec значение | Code значение | Файл |
|---|---|---|---|
HASH_SIZE |
32 (SHA-256) | 32 | mt-crypto/src/lib.rs:71 |
PUBLIC_KEY_SIZE |
1952 (FIPS 204 ML-DSA-65 level 3) | 1952 | mt-crypto/src/lib.rs:73 |
SECRET_KEY_SIZE |
4032 | 4032 | mt-crypto/src/lib.rs:74 |
SIGNATURE_SIZE |
3309 | 3309 | mt-crypto/src/lib.rs:75 |
KEYPAIR_SEED_SIZE |
32 (FIPS 204 §3.1 ξ ∈ B³²) | 32 | mt-crypto/src/lib.rs:77 |
MLKEM_PUBLIC_KEY_SIZE |
1184 (FIPS 203 ML-KEM-768) | 1184 | mt-crypto/src/lib.rs:79 |
MLKEM_SECRET_KEY_SIZE |
2400 | 2400 | mt-crypto/src/lib.rs:80 |
MLKEM_SEED_SIZE |
64 (FIPS 203 §6.1 d ‖ z) | 64 | mt-crypto/src/lib.rs:81 |
Все остальные крейты (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) — обоснованный duplicate для C FFI binding boundary, mild [C-1] violation.
10.2 Размеры структур (one source per crate)
| Константа | Code значение | Source crate |
|---|---|---|
ACCOUNT_RECORD_SIZE |
2059 | mt-state |
NODE_RECORD_SIZE |
2098 | mt-state |
CANDIDATE_RECORD_SIZE |
2082 | mt-state |
PROPOSAL_HEADER_SIZE |
3722 | mt-consensus |
PARAMS_ENCODED_SIZE |
4094 | mt-genesis |
NODE_REGISTRATION_SIZE |
5344 | mt-entry |
TRANSFER_SIZE |
3422 | mt-account (computed: 1 + 32 + 32 + 32 + 16 + SIGNATURE_SIZE) |
CHANGE_KEY_SIZE |
5328 | mt-account (1 + 32 + 32 + 2 + PUBLIC_KEY_SIZE + SIGNATURE_SIZE) |
ANCHOR_SIZE |
3438 | mt-account |
TRANSFER_ACTIVATION_SIZE |
5376 | mt-account |
BUNDLE_FIXED_OVERHEAD |
computed | mt-lottery |
REVEAL_SIZE |
computed | mt-lottery |
TREE_DEPTH |
256 | mt-merkle |
Все структуры консумятся через use из owning crate, тестами *_record_encoded_size сверяются с actual encode output.
10.3 Domain registry (one source: mt-codec/src/lib.rs:44-81)
32 domain separator в mod domain — соответствует AUDIT.md заявлению. Префикс mt- для всех. Все consensus-critical hash-композиции в production коде используют domain::* импорт. Я проверил литеральные b"mt-..." подстроки за пределами mt-codec:
- Все вне-mt-codec literal
b"mt-..."находятся в#[cfg(test)] mod testsблоках для проверки expected hash значений (legitimate test setup); - Один acceptable исключение:
IDENTITY_MAGIC = b"mt-local"вmontana-node/identity.rs:17— file format magic, не domain separator (см. L-NODE-1 finding).
10.4 Протокольные параметры (one source: mt-genesis::ProtocolParams)
| Параметр | Code значение | Файл |
|---|---|---|
d0 |
325_000_000 | mt-genesis/src/lib.rs:82 |
tau2_windows |
20_160 | mt-genesis/src/lib.rs:84 |
emission_moneta |
13_000_000_000 (13 GɈ baseline) | mt-genesis/src/lib.rs:85 |
confirmation_quorum |
67/100 | mt-genesis/src/lib.rs:87-88 |
participation_dead_zone_low/high |
85/95 (permille × 10) | mt-genesis/src/lib.rs:89-90 |
d_adjustment_rate |
3/100 | mt-genesis/src/lib.rs:91-92 |
vdf_entry_windows |
20_160 = τ₂ | mt-genesis/src/lib.rs:93 |
selection_interval |
336 (τ₂/336 = 60 раз/сутки) | mt-genesis/src/lib.rs:94 |
admission_divisor |
130 (M4-LOW-7 closure: ранее hardcoded const, теперь в ProtocolParams) | mt-genesis/src/lib.rs:95 |
candidate_expiry_windows |
60_480 = 3τ₂ | mt-genesis/src/lib.rs:96 |
pruning_idle_windows |
80_640 = 4τ₂ | mt-genesis/src/lib.rs:99 |
VERSION.md заявляет spec target = Montana v35.3.2 (2026-04-28). AUDIT.md ссылается на Montana v34.0.0 (2026-04-27) — AUDIT.md устарел на три минорных bump'а (v34.0.0 → v35.2.0 → v35.3.0 → v35.3.1 → v35.3.2 за один день после AUDIT.md). Это не код-блокер, но AUDIT.md документ требует обновления для consistency.
11. Динамические тесты (single-thread, single-process)
Все тесты прогнаны мной с настройками [build] jobs = 1 + RUST_TEST_THREADS = 1 (зашиты в .cargo/config.toml для предотвращения перегрева MacBook на PBKDF2 c=2²⁰).
| Тестовый набор | Команда | Статус | Время |
|---|---|---|---|
cargo check --workspace --all-targets |
базовая проверка компиляции | PASS (exit 0) | ~2 мин |
cargo test -p mt-codec -p mt-merkle -p mt-genesis -p mt-state -p mt-timechain |
M2 + codec | PASS (exit 0) | ~1 мин |
cargo test -p mt-account -p mt-store |
M3 + M5 | PASS (exit 0) | ~2 мин |
cargo test -p mt-lottery -p mt-consensus -p mt-entry |
M4 | PASS (exit 0) | ~3 мин |
cargo test -p mt-crypto -p mt-crypto-native -p mt-mnemonic |
M1 (включает PBKDF2 c=80 000 + NIST ACVP KAT + RFC vectors + e2e recovery) | PASS (exit 0) | ~16 мин (включая компиляцию vendored OpenSSL 3.5.5) |
cargo audit |
known CVE на 41 deps | CLEAN (0 vulnerabilities, 0 warnings) | ~30 с |
Финальный M1 результат (зафиксирован после завершения отчёта):
e2e_recovery.rs— 3/3 PASS, включаяe2e_recovery_terminal_observable_byte_exact([C-4] End-to-End Observable Closure compliance);keygen_vectors.rs— 7/7 PASS: 5 KAT-векторов (KAT 1: ML-DSA-65 seed=zero, KAT 2: seed=0xFF, KAT 3: ML-DSA-65 account from master_v1, KAT 4: ML-DSA-65 node from master_v1, KAT 5: ML-KEM-768 app encryption) + 2 determinism cross-checks;test_vectors.rs— 6/6 PASS: m1_vector_1/2/3 (entropy → mnemonic → master_seed) + derivation_vector_1/2/3 (master_seed → per-role HKDF);regression_baselines.rs(mt-crypto-native) — PASS;nist_acvp_kat.rs(mt-crypto-native) — PASS на 50+ NIST ACVP-Server fixtures (ML-DSA-65 KeyGen 25 + ML-DSA-65 SigGen 15 + empty ctx + ML-KEM-768 KeyGen);security_invariants.rs(mt-crypto) — 13/13 PASS (Pass 17 secret hygiene properties).
Замечание: ни один из прогнанных тестов не failed ни в одном крейте M1+M2+M3+M4+M5.
12. Архитектурные наблюдения сверх M0…M5 scope
- 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).
panic = "abort"в release профиле (Cargo.toml:21) — корректно для consensus-критичного бинарника. Никакого unwinding с partially-modified state.overflow-checks = trueв обоих dev и release профилях (Cargo.toml:22, 25) — предотвращает silent wrap. Все consensus-арифметика дополнительно используетchecked_*либоsaturating_*per [I-9].- Reproducible release builds через Docker container с pinned base image (
debian:bookworm-slim@sha256:40b107342c492725bc7aacbe93a49945445191ae364184a6d24fedb28172f6f7) (docker/release-build.dockerfile) — CI gatereproducible_releaseв .github/workflows/ci.yml.
13. Сводный список findings по приоритету
Блокеры mainnet deployment (НЕ блокеры аудита кода)
- B-1: Genesis ceremony pending —
bootstrap_account_pubkey,bootstrap_node_pubkey,target_zero,genesis_content_data_hashвсё ещё placeholder[0u8; N]. Programmatic detection черезis_genesis_bootstrap_finalized(). Требует multi-party ceremony либо single trusted party — design decision автора. (знал, документировано в AUDIT.md и SPEC_DEVIATIONS DEV-010)
Medium
- M-M4-1 (LOW→MEDIUM при scaling):
BundleError::TooManyOps/TooManyRevealscap = 65 535 (u16 length prefix). На 1B пользователях с 1000 узлами — 100K+ op_hashes/окно. M6+ spec-patch на u32 length prefix. - M-NODE-1: hardcoded
median_permille = 1000u32для singleton; replace на real aggregation в M6+.
Low
- L-M1-1:
mt-crypto-native::MLDSA65_SIGNATURE_SIZE = 3309дубликат — sub-FFI boundary [C-1] mild violation. - L-M1-2:
keypair()— gatedcfg(any(test, feature = "testing")),expect()на getrandom acceptable. - L-M2-1: устаревшие комментарии
mt-state/src/lib.rs:56,92(1043B/1027B vs реальные 2098B/2082B). Update doc comment. - L-M2-2: Genesis bootstrap pubkeys placeholder (= B-1).
- L-M3-1:
op_height: u32overflow horizon на горячих аккаунтах в multi-decade. Acceptable now; future migration path. - L-M4-2:
validate_winnerstrict reject empty cemented W-1 — caller responsibility skip для genesis. Не баг, но без typestate enforcement. - L-M4-3:
apply_selection_eventperf-аспект replay — корректен черезpool.remove. - L-M5-1:
write_atomicбезfsync— отложено на M6 operator layer. - L-NODE-1:
IDENTITY_MAGIC = b"mt-local"naming clash с domain registry pattern. - L-NODE-2: double-sign pattern в start.rs — wasted compute.
- L-NODE-3:
single_leaf_root(leaf) = *leafдля одиночного bundle/reveal в singleton. - L-NODE-4:
control_root = [0u8; 32]placeholder для пустого ControlObject set.
Info
- INFO-M1-3:
mt-telemetryintegration TODO для observabilitymlockfailure. - INFO-M1-4: ML-KEM Encapsulate/Decapsulate KAT не интегрирован (fixtures в репо есть).
- INFO-M2-3:
compute_subtree_root::Combineexpect()— invariant breach unreachable. - INFO-M3-2: settle_window отделён от apply_proposal через documentation, не typestate.
- INFO-M4-4:
compute_control_setfilter inclusive границы — M6+ network-layer concern для reorg/resync. - INFO-M4-5: magic number 69 в
seniority_bonus = chain_length / 69— обоснование в спецификации (не проверено в этом аудите по принципу нулевого доверия к доке). - INFO-M5-2:
cleanup_orphan_tmpbest-effort — tmp накопление при многократных crashed open's possible. - INFO-NODE-5:
target: u128::MAXplaceholder в singleton mode.
Документация / housekeeping
- DOC-1:
AUDIT.mdссылается на spec target v34.0.0; актуальный target поVERSION.md= v35.3.2. Обновить. - DOC-2:
SPEC_DEVIATIONS.mdDEV-001…DEV-009 — статус «commit pending» устарел; реальные commits сделаны (546a866,0921db6,db01de6и др.). Обновить на «закрыто (commit )».
14. Сильные стороны проекта в целом
- Полная type-level дисциплина: 0 unsafe без SAFETY, 0 panic в production paths без protocol-invariant обоснования, 0 HashMap/HashSet, 0 f32/f64, 0 SystemTime/Instant в consensus path, 0 thread_rng / OsRng в lib коде.
- Domain separation первого класса: 32 SSOT registry в
mt-codec, NUL-byte separatordomain ‖ 0x00 ‖ parts...(P1 closure). - Secret hygiene: heap+mlock+zeroize+
!Clone+!PartialEq— не worse чем libsodium / boringssl. - Integer arithmetic per [I-9]: ВСЕ consensus formulas в integer Q-format с binding test vectors.
log2_q64— degree-3 Remez minimax с hardcoded coefficients. [I-8]Network-Bound Unpredictability:cemented_bundle_aggregateкорректно исключает signatures и op_hashes из input; canonical sort поnode_idобеспечивает order-independence.- RFC + FIPS conformance: HMAC RFC 4231 (4 cases), HKDF RFC 5869 (3 cases), PBKDF2 RFC 7914 (2 cases) + CryptoJS, SHA-256 FIPS 180-4, ML-DSA-65 / ML-KEM-768 NIST ACVP (50+ KAT cases).
- Reproducible builds через pinned Docker image + CI gate.
cargo auditclean: 41 deps, 0 vulnerabilities, 0 warnings.- Доказательная зрелость через предыдущие audits: 4 successive incremental audits + critic closures (M1-F: 7+5+4 closure, M2: 17 closure, M3: 10 closure, M4: 10 closure, M5: 1 closure) — pattern «caught + closed» без оставшихся открытых.
- Производственная дисциплина: production-grade naming везде ([C-12]), zero deferred policy в audit-ready состоянии ([C-6]),
panic="abort"+overflow-checks=true.
15. Итоговая оценка: 8.7 / 10
Аудит подтверждает: M1…M5 готовы к engagement производственной security firm (NCC Group, Trail of Bits, Quarkslab, Cure53). Открытых блокеров кода — нет. Block-листы только относятся к (а) Genesis ceremony перед mainnet, (б) singleton-mode placeholder полям при переходе к multi-node M6+, (в) обновлению AUDIT.md / SPEC_DEVIATIONS.md документации.
Распределение оценки:
- M1: 9.0/10 — excellent crypto layer
- M2: 9.0/10 — solid foundation
- M3: 9.0/10 — clean apply layer
- M4: 8.5/10 — minor scale concern (u16 length prefix)
- M5: 9.0/10 — minimal correct persistence
- montana-node integration: 8.0/10 — singleton placeholders для M6+
- общее (SSOT, deps, build): 9.0/10
Top-3 рекомендации (приоритет порядка):
- (MEDIUM) обновить AUDIT.md spec target v34.0.0 → v35.3.2 + SPEC_DEVIATIONS.md DEV-001…DEV-009 на «закрыто (commit )» — устранить documentation drift.
- (LOW) обновить устаревшие комментарии
mt-state/src/lib.rs:56,92на актуальные размеры структур (2098B / 2082B). - (MEDIUM) при переходе к multi-node M6+ заменить hardcoded
median_permille = 1000u32на real participation aggregation; запланировать spec-patch на u32 length prefix для bundle hashes.
Приложение A — методология восстановления отчёта
Если потребуется воспроизвести этот аудит через другого аудитора, ключевые команды (cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код"):
cargo check --workspace --all-targets
cargo test -p mt-codec -p mt-merkle -p mt-genesis -p mt-state -p mt-timechain
cargo test -p mt-account -p mt-store
cargo test -p mt-lottery -p mt-consensus -p mt-entry
cargo test -p mt-crypto -p mt-crypto-native -p mt-mnemonic
cargo audit --json
Все настройки single-thread / single-process уже в .cargo/config.toml, override не требуется.
Список финальных проверок:
- Существуют ли любые
unsafe { ... }без// SAFETY:комментария:grep -rB1 'unsafe {' crates/*/src/*.rs | grep -v 'SAFETY' | grep 'unsafe {' | grep -v test - Любые
unwrap()/expect()/panic!в lib коде вне#[cfg(test)]:grep -rn 'unwrap()\|expect(\|panic!' crates/*/src/*.rs | grep -v '#\[cfg(test)\]' | grep -v 'protocol invariant' - HashMap/HashSet в lib path:
grep -rn 'HashMap\|HashSet' crates/*/src/*.rs | grep -v test | grep -v BTreeMap - Domain registry size:
grep -cE '^\s*pub const [A-Z_]+: &\[u8\]' crates/mt-codec/src/lib.rs
Конец отчёта. Все файлы в Протокол/ помимо кода рассматривались как сырой target scope, не как источник истины. Все findings сопровождены конкретными ссылками на crates/<crate>/src/<file>:<line>.