# Pre-audit self-attestation checklist — M1+M2+M3+M4+M5+M6+M9 layers Заполняется архитектором перед каждым external audit engagement. Все пункты должны быть `[x]` либо иметь явное обоснование почему `[ ]`. --- ## A. Conformance proofs - [x] **NIST FIPS 204 ML-DSA-65 KeyGen byte-exact** vs ACVP-Server published vectors (25 cases) - [x] **NIST FIPS 203 ML-KEM-768 KeyGen byte-exact** vs ACVP-Server published vectors (25 cases) - [x] **NIST FIPS 204 ML-DSA-65 SigGen deterministic byte-exact** для empty context (1 case) - [x] **NIST FIPS 180-4 SHA-256** vector "abc" → ba7816bf...15ad - [ ] **NIST FIPS 204 ML-DSA-65 SigVer** (deferred — косвенно подтверждено через round-trip; direct NIST KAT не добавлен в M1-F) - [ ] **NIST FIPS 203 ML-KEM-768 Encapsulate/Decapsulate** (deferred — Montana M1-F scope только KeyGen, encapDecap нужен в M6+) ## B. Code surface - [x] **Layer 1 Rust shim** **662 строк** (`crates/mt-crypto/src/lib.rs`), все **7** `unsafe` blocks с `// SAFETY:` комментариями: 4 FFI sites (`fn keypair_from_seed`, `fn sign`, `fn verify`, `fn keypair_from_seed_mlkem`) + 3 mlock/munlock (`impl Drop for SecretKey`, `fn alloc_locked_secret_box`, `impl Drop for MlkemSecretKey`). Точные строки сверять через `grep -n "unsafe " crates/mt-crypto/src/lib.rs` (line refs не фиксируем — drift при growth). - [x] **Layer 2 own C wrapper** **457 строк** (`mt_crypto.c`) + **67 строк** (`mt_crypto.h`), focused EVP API wrapping, `-Wall -Wextra -Wpedantic -Werror` - [x] **Layer 3 vendored OpenSSL 3.5.5 LTS** через `openssl-src = "=300.5.5+3.5.5"` byte-pinned - [x] Total own audit surface (Layer 1 + Layer 2) **1280 строк** (662 Rust shim + 49 FFI bindings + 457 C wrapper + 67 C header + 45 build script) — small enough для thorough review. Точные числа: `wc -l crates/mt-crypto/src/lib.rs crates/mt-crypto-native/src/lib.rs crates/mt-crypto-native/csrc/mt_crypto.{c,h} crates/mt-crypto-native/build.rs`. - [x] No `serde` auto-derive в consensus-critical types (custom `CanonicalEncode` trait) ## C. Memory safety + secret hygiene (Pass 17 enforcement) - [x] **Drop+zeroize** для `SecretKey` (4032B) и `MlkemSecretKey` (2400B) - [x] **Heap-allocated через Box** — secret bytes живут в heap, не stack inline (verified `secret_key_is_heap_allocated` test: `size_of::() == 8` pointer) - [x] **mlock applied** на heap pages для secret bytes (best-effort через `alloc_locked_secret_box`); fallback assumption: encrypted swap (FileVault macOS / LUKS Linux) - [x] **Stack hygiene при FFI** — `keypair_from_seed*` пишет напрямую в heap-locked Box; никаких stack temporary buffers с secret bytes (verified в [crates/mt-crypto/src/lib.rs](../crates/mt-crypto/src/lib.rs) — функции `keypair_from_seed` и `keypair_from_seed_mlkem`, search by name) - [x] **munlock в Drop** перед heap dealloc (best-effort, errno ignored) - [x] No `Clone`/`Copy` derive на secret types (compile-time enforced via security_invariants test) - [x] No `PartialEq`/`Eq` на secret types (предотвращает timing leak через ==) - [x] No secret bytes в logs / stdout / stderr (file-content scan через `no_println_or_log_on_secret_bytes_in_lib_code` test) - [x] `mt-examples/m1_crypto.rs::print_sk` gated через env var (`M1_DUMP_SK=1` opt-in; по умолчанию SK bytes redacted; механизм: `dump_sk_enabled()` функция в [m1_crypto.rs](../crates/mt-examples/examples/m1_crypto.rs#L39-L41) проверяется в `print_sk` строка 76) - [x] FFI buffer sizes match contract (PUBLIC_KEY_SIZE / SECRET_KEY_SIZE / SIGNATURE_SIZE constants used consistently) - [x] **13 security invariants automated** в [crates/mt-crypto/tests/security_invariants.rs](../crates/mt-crypto/tests/security_invariants.rs) — regression detection - [x] **6 Security Cards заполнены** в [docs/security-cards.md](security-cards.md) — Pass 17 mandatory enforcement ## D. Error surface - [x] **`sign` / `keypair_from_seed` / `keypair_from_seed_mlkem`** возвращают `Result<_, CryptoError>` (не panic) - [x] **CryptoError enum** с 11 variants (Display + std::error::Error impls) - [x] No `unwrap()` / `expect()` в lib коде кроме явного internal invariant с комментарием - [x] No silent error swallowing (`.ok()`, `let _ = ...`) ## E. Determinism (consensus path) - [x] **FIPS 204 Algorithm 2 deterministic Sign** через `OSSL_SIGNATURE_PARAM_DETERMINISTIC=1` - [x] No `f32`/`f64` в crypto path (consensus determinism per Montana [I-3] + [I-9]) - [x] No `HashMap`/`HashSet` iteration order dependency - [x] No `SystemTime::now`/`Instant::now` в consensus path (только в test/tool helper `keypair()` который gated `#[cfg(any(test, feature = "testing"))]`) ## F. Misuse resistance - [x] **`SecretKey::from_array(arbitrary_bytes)`** при последующем `sign()` возвращает `Err(CryptoError::InvalidSecretKey)`, не panic (F-7 closure через F-2 Result API) - [x] `keypair()` (weak entropy test helper) **не доступен** в production binary (cfg-gate) - [x] Public type fields private (no struct literal construction обходящая validation) - [x] No `Default` impl для types requiring real crypto material ## G. Build & reproducibility - [x] **`Cargo.lock`** committed - [x] **Точные версии всех dependencies** (`=X.Y.Z`) - [x] **`rust-toolchain.toml`** pinned - [x] **Docker reproducible build** с pinned base image digest - [x] **CI gate `reproducible_release`** проверяет byte-identity между двумя независимыми runs - [x] **Cross-compile correctness** через `CARGO_CFG_TARGET_OS` env var ## H. Dependencies - [x] **Crypto deps** все production-grade (OpenSSL 3.5.5 LTS, sha2 0.10.9, no pre-1.0 в consensus path) - [x] **No "USE AT YOUR OWN RISK" libraries** в production paths - [x] **`cargo audit`** clean (verified 2026-04-26: 0 vulnerabilities, 0 warnings, 39 dependencies scanned). Prerequisite: `cargo install cargo-audit --locked` (один раз). Verify: `cd "" && cargo audit` - [x] **`cargo tree -p mt-crypto | grep -iE "ml-dsa|ml-kem|hybrid-array"`** → 0 hits (RustCrypto pre-1.0 deps удалены полностью per M1-F migration) - [x] **License compatibility** — все deps MIT / Apache-2.0 / BSD / ISC ## I. Documentation - [x] **`AUDIT.md`** в корне репозитория с audit chain + threat model + reproduction commands - [x] **Threat model** explicit: in scope / out of scope / known limitations с closure paths - [x] **Spec references** в коде через `// spec, раздел ""` без версии (single source of truth — `VERSION.md`) - [x] **Manual Validation Gate scenarios** documented в `ROADMAP.md` - [x] **Architect + critic roles** ([CLAUDE.md](../CLAUDE.md), [CRITIC.md](../CRITIC.md)) в репозитории — peer-reviewable methodology ## J. Open findings - [x] **Zero open audit findings** в M1 foundational layer (per AUDIT.md §5) - [x] All 7 M1-F audit findings (F-1..F-7) закрыты конструкцией - [x] All 5 audit-package findings (F-A1..F-A5) закрыты конструкцией - [x] All 4 M0+M1+M2 critic findings (F-1..F-4 from `b4a00b1` audit) закрыты: F-1 (mt-recovery-fingerprint domain spec drift) → spec patch v33.1.2 → v33.1.3; F-2 (VERSION.md stale Implementation field) → updated to M0..M5 closed; F-3 (false positive снят критиком в самом audit); F-4 (controlled halts documentation) → этот раздел K ниже - [x] Manual Validation Gate scenarios 0/1 status: ✅ Ready (зеленая под external audit) ## K. Controlled halts (documented panic sites) Список всех `panic!`/`assert!` в lib коде Montana с обоснованием. Все они — **controlled halts при protocol-invariant violation**, НЕ attacker-triggered, НЕ silent failures. Auditor должен verify что каждый panic site: - (a) reachable только при invariant violation от trusted source (Genesis params, frozen const) - (b) imeет explicit comment с обоснованием - (c) не открыт для attacker-controlled input | Site | Файл:строка | Trigger | Обоснование | |------|-------------|---------|-------------| | `apply_transfer*` balance underflow | [crates/mt-account/src/lib.rs](../crates/mt-account/src/lib.rs) `fn apply_transfer{,_activation}` | `sender.balance.checked_sub(amount)` returns None | Protocol invariant breach: `validate_transfer*` гарантирует `balance >= amount` ДО apply. Halt = caller вызвал apply без validate (programmer error либо memory corruption). Не attacker-triggered: validate-then-apply pattern enforced. | | `apply_transfer*` receiver/operator balance overflow | [crates/mt-account/src/lib.rs](../crates/mt-account/src/lib.rs) `fn apply_transfer/apply_emission` | `balance.checked_add(amount)` returns None at u128::MAX | Encoded arithmetic horizon — суммарный баланс per-account достиг u128::MAX (~3.4×10³⁸ nɈ). Не достижим под const emission `EMISSION_moneta = 13 × 10⁹ nɈ` per окно. Documented halt. | | `apply_*` op_height/account_chain_length overflow | [crates/mt-account/src/lib.rs](../crates/mt-account/src/lib.rs) `fn apply_*` | `u32` field counters достигли u32::MAX (~4.29 млрд operations per account) | Encoded arithmetic horizon: 4.29 млрд operations per account, не достижим в реалистичный срок. Documented halt. | | `window_w_to_u32` cast overflow | [crates/mt-account/src/lib.rs](../crates/mt-account/src/lib.rs) `fn window_w_to_u32` | `window_w: u64 > u32::MAX` при cast в AccountRecord field | AccountRecord использует u32 для window-полей (encoded size optimization 4B vs 8B). Horizon = ~4.29 млрд окон ≈ 8000 лет at 60 sec/window. Documented halt. | **Все sites:** - ✅ Имеют explicit panic message с обоснованием - ✅ Reachable только при achieved arithmetic horizon либо protocol invariant breach - ✅ Не attacker-triggered - ✅ Halt = correct behavior per spec — protocol upgrade required либо validate-then-apply violated (programmer error) `mt-crypto` panics gated через `assert_eq!(r, MT_OK, ...)` уже converted в `Result<_, CryptoError>` в M1-F closure (commit `e1164ad`) — там panic-в-lib больше нет. `mt-examples` test helpers могут panic через `.expect("...")` — это test scaffolding, вне production audit scope (per critic role Scope §«НЕ входит в scope: mt-examples test helpers»). Production binary вызовы из mt-examples (m1_crypto demo) panicи через `.expect("HKDF-derived seed cannot fail KeyGen")` — internal invariant, не attacker-triggered. ## L0. Test strength augmentations (Pass 22) **Mutation testing (recommended, не block для audit):** ``` cargo install cargo-mutants --locked cargo mutants --package mt-lottery --package mt-consensus --package mt-entry --package mt-account --package mt-store ``` Mutation testing вводит синтетические `mutations` (изменения арифметики, boundary conditions, removed function calls) в production code и проверяет есть ли test что catches каждый mutation. **Surviving mutations** = weak tests (могут passing на broken code). **Текущий статус:** не run автоматически в CI. Recommended pre-mainnet benchmark: ≥80% mutation kill rate для consensus path (M3-M4 крейты). **Применимость к external audit:** auditor может запросить mutation report как evidence test strength. Закрытие требует: 1. Run cargo-mutants 2. Analyze surviving mutations 3. Add test cases или strengthen assertions Closure cost ~1-2 рабочих дня (run + analyze + augment tests). Не блокер для current external audit engagement — это test quality improvement metric, не correctness gap. ## L. M3 Storage Cards (per persistent state table) Per родительский `Протокол/CLAUDE.md` Storage Card invariant: каждая persistent state table обязана иметь Storage Card до статуса «closed». Закрывает Gate 14 ([I-14] state lifecycle) для apply_proposal layer. ### AccountTable Storage Card ``` Таблица: AccountTable (mt_state::AccountTable) Operation создающая запись: TransferActivation (opcode 0x0A) Платит creation cost: sender (existing account, sponsor pattern) Размер записи (bytes): 2059 (ACCOUNT_RECORD_SIZE) Secondary resources per record: SMT leaf hash 32B + потенциальный merkle path Cost per record: амount > 0 (sender выбирает) — нет fixed creation fee, sender отправляет любую amount получателю который при этом получает AccountRecord Cost barrier для anti-spam: НЕ через денежный барьер (нарушило бы [I-15] time-based scarcity); защита через cooldown 1 TransferActivation per sender per τ₂ (см. validate_transfer_activation spec rule (e) [I-15] cooldown enforcement) Lifecycle condition: нет explicit removal в M3 scope (CloseAccount opcode 0x0B — M11 milestone, pending spec finalization payload format) Lifecycle threshold: N/A в текущей версии [I-14] путь: 3 (rate-limit через [I-15] time scarcity); barriers через time, не money Existing pruning consistent: yes (нет pruning, по design до M11) [I-14] compliance status: pending M11 — closure path в ROADMAP §M11 «CloseAccount финализация». Rate-limit через cooldown сейчас limits attacker account creation rate; explicit deletion ждёт spec finalization opcode 0x0B. Conservation invariant (per-op): Σ delta_balance == 0 для Transfer/ TransferActivation (sender -= amount, receiver += amount, atomic) Storage growth invariant per τ₂: ≤ active_chain_length / τ₂ × max_accounts_ per_τ₂ (rate-limited через [I-15]) Storage cap: нет explicit hard cap — relies on [I-15] time-based + future M11 deletion ``` **Sabotage budget analysis:** атакующий $1M / $100k / $10k без profit motive, максимизирующий state bytes: - Stake-protected: TransferActivation требует sender с balance > 0 + cooldown; атакующий создаёт `N` accounts с initial balance, ждёт τ₂ окон, повторяет активацию каждого отдельным sponsor. Real cost = TC required для bootstrapping N senders × cooldown overhead. - Per-τ₂ limit: 1 activation per existing account → max N accounts на attacker = `existing_accounts × (windows / τ₂)`. Линейный, не exponential growth. - Для bloat 1 GB AccountTable: 1 GB / 2059 B ≈ 487K accounts. При 1 activation per account per τ₂ = 487K окон ≈ 6.7 days (при τ₂ = 20160 × 60sec). Эта оценка предполагает что attacker уже владеет ≈ 487K sender accounts — что само по себе требует bootstrapping. - M11 CloseAccount позволит deletion, что smaller surface для accumulated bloat но не предотвратит short-term spike. [I-15] time-based + cooldown — текущий primary mitigation. M11 — secondary explicit cleanup path после spec finalization. ### NodeTable + CandidatePool Storage Cards Доступ через mt-state types (NODE_RECORD_SIZE = 2098, CANDIDATE_RECORD_SIZE = 2082). Lifecycle / cost analysis для этих таблиц — domain mt-entry (M4 audit scope, отдельный milestone). --- ## Reproduction one-liners для аудитора **NIST KAT cross-implementation conformance proof:** ``` cd "" && cargo test -p mt-crypto-native --test nist_acvp_kat -- --nocapture ``` **Internal correctness baselines:** ``` cd "" && cargo test -p mt-crypto-native -p mt-crypto -p mt-mnemonic ``` **Recovery flow end-to-end:** ``` cd "" && cargo test -p mt-mnemonic --test e2e_recovery -- --nocapture ``` **M2 Determinism invariants (mt-merkle / mt-genesis / mt-state / mt-timechain):** ``` cd "" && cargo test -p mt-merkle -p mt-genesis -p mt-state -p mt-timechain --test determinism_invariants -- --nocapture ``` Ожидание: SMT root determinism, Genesis singleton stability, state table BTreeMap canonical sort, VDF + cemented_bundle_aggregate per [I-8]. **M3 Determinism invariants (mt-account):** ``` cd "" && cargo test -p mt-account --test determinism_invariants -- --nocapture ``` Ожидание: Transfer/ChangeKey/Anchor/TransferActivation encoded sizes, op_hash determinism (R2 invariant), apply_* determinism, validate rejection patterns, settle_window order independence, genesis state determinism, reward/supply consistency, apply_proposal determinism, controlled panic on protocol breach (checked arithmetic). **M4 Determinism invariants (mt-lottery / mt-consensus / mt-entry):** ``` cd "" && cargo test -p mt-lottery -p mt-consensus -p mt-entry --test determinism_invariants -- --nocapture ``` Ожидание: 32 + 27 + 24 = **83 PASS** — bundle_hash/reveal_hash R2 stability, compute_endpoint [I-8] binding, log2_q64 / ln_q64 / weighted_ticket_node monotonicity, determine_winner argmin canonical (M4-1 closure: TooManyOps validation barrier), proposal_hash R2, canonical_proposer / fallback_proposer Lookback Leadership cascade, compute_control_set canonical sort, validate_* acceptance, finalization_status, NodeRegistration R2, candidate_vdf_init [I-8], selection_slots / selection_sort_key, required_vdf_length Adaptive VDF, distinct domain separators between three sort_key compositions. **M5 Determinism invariants (mt-store):** ``` cd "" && cargo test -p mt-store --test determinism_invariants -- --nocapture ``` Ожидание: AccountTable / NodeTable / CandidatePool save/load roundtrip (root byte-equal), CorruptedLength detection, crash recovery (meta + verify_consistency), prune_proposals, byte-exact equality для identical input, BTreeMap canonical sort persistence-stable, full state cycle (open → populate → save → close → reopen → load), R5 atomic rename verification (no `.tmp` после save, atomic overwrite). **M5 Pseudo-fuzz harness (mt-store wire decoders):** ``` cd "" && cargo test -p mt-store --test fuzz_decoders -- --nocapture ``` Ожидание: 5 PASS — `decode_account_record` / `decode_node_record` / `decode_candidate_record` / `decode_proposal_header` / `load_meta_last_cemented` прогоняются на 7500+ pseudo-random byte arrays различных длин (deterministic Xorshift64). Invariant: never panic, always возвращает Result; valid length → Ok, mismatch → StoreError::CorruptedLength. Pseudo-fuzz используется вместо libfuzzer-sys / cargo-fuzz из-за nightly toolchain dependency (workspace pinned на stable). При появлении nightly target — заменить на coverage-guided fuzzing в crates/mt-store/fuzz/ fuzz_targets/. **M4 External SHA-256 oracle (Pass 25 Independent Oracle):** ``` cd "" && python3 scripts/oracle_python_sha256.py cd "" && cargo test -p mt-lottery --test external_oracle -p mt-entry --test external_oracle ``` Ожидание (Python): 4 hardcoded hex digests + distinct domains PASS + input sensitivity PASS. Ожидание (Rust tests): 4 PASS — `compute_endpoint`, `candidate_vdf_init`, `selection_sort_key`, `nr_sort_key` byte-exact match Python `hashlib.sha256` output. Cross-impl conformance verified — независимая reference не от Rust SHA-256 (sha2 crate) → защита от drift между Rust impl и spec formula. **Reproducible release build verification:** *Prerequisites:* Docker ≥ 20.10 installed and running, bash (для process substitution), ~30 минут wall-clock для двух clean builds, ~5 GB free disk. ``` cd "" && docker build --no-cache --file docker/release-build.dockerfile --tag mt-audit-1 . && docker build --no-cache --file docker/release-build.dockerfile --tag mt-audit-2 . && diff <(docker run --rm mt-audit-1 sha256sum /usr/local/bin/*) <(docker run --rm mt-audit-2 sha256sum /usr/local/bin/*) ``` *Альтернатива без Docker prerequisite:* верифицировать через CI history — каждый push в `main` запускает CI job `reproducible_release` (см. [.github/workflows/ci.yml](../.github/workflows/ci.yml)), который выполняет тот же двойной build с byte-identity assertion. Auditor может проверить green CI runs за период как evidence. **Build sanity:** ``` cd "" && cargo fmt --all -- --check && cargo clippy --all-targets -- -D warnings && cargo build --all --release ``` --- --- ## H. M6 Network layer (mt-net + mt-net-transport) - [x] **mt-net wire format byte-exact** — ProtocolMessage envelope (14 B header + payload), 18 message types in registry, IBT online + mesh proof, Bootstrap PoW, Uniform Framing (1024 B fixed), 12 structured payloads (FastSync*, PeerList*, BatchLookup*, RangeSubscribe*, Bye) - [x] **mt-net 110 unit + integration tests pass** — `cargo test -p mt-net --features testing` - [x] **mt-net-transport libp2p TCP+TLS 1.3+Noise+Yamux upgrade chain** — verified through `cargo test -p mt-net-transport --features testing` (14 tests pass) - [x] **Manual Validation Gate scenario 6 PASS** — two-node handshake e2e (commit `9a15f49`); `tests/e2e_two_node_handshake.rs::two_node_request_response_ping_pong` - [x] **Manual Validation Gate scenario 7 PASS** — proposal exchange e2e + 512 KiB boundary (commit `04f8d29`); `tests/e2e_proposal_exchange.rs::{proposal_envelope_round_trip, large_payload_near_max_limit}` - [x] **[C-5] libp2p capability checklist 8/8 PASS** — TCP+TLS 1.3+Noise+Yamux+Swarm primitives, async tokio, rustls + snow constant-time, Linux+macOS+Windows, IPFS+Filecoin+Polkadot 5+ years production, MIT/Apache 2.0 - [x] **Backpressure rules B1-B6 enforced** — max_protocol_payload_bytes (1 MiB) + max_sf_ciphertext_bytes (64 KiB) reject до allocation per spec - [x] **Critic-fix bundle P-C1..P-C8 closed** — domain registry SSOT в mt-codec, prefix-free rename mt-tunnel→mt-tunnel-online, 5 fuzz harnesses scaffolded, try_new constructors, Bye forward-compat, ibt_mesh_verify O(1) path, no unwrap/expect в lib code - [x] **5 fuzz harnesses scaffolded** в `crates/mt-net/fuzz/fuzz_targets/` — fuzz_decode_envelope/frame/mesh_frame/sf_envelope/payloads (запуск через `cargo +nightly fuzz run`) ## I. M9 Conformance suite (mt-conformance) - [x] **mt-conformance crate created** — public binding test vectors для cross-implementation byte-exact verification - [x] **2 unit tests pass** — `envelope_vectors_byte_exact` + `pow_target_byte_exact` - [x] **Initial vectors covered** — envelope A1/A2/A3 + IBT B1 (after P-C2 rename) + Bootstrap PoW F1/F2 target derivation - [x] **iOS port мirrored** — `iOS/Apps/Montana/MontanaTests/MTConformanceVectors.swift` byte-exact mirror Rust crate - [ ] **Expansion** (12 TBD-A markers) — defer until app-layer payload format finalization (BatchLookupRequest/Response, RangeSubscribeResponse query/result/blob entry types) ## Sign-off | Role | Name | Date | Status | |------|------|------|--------| | Architect | (architect role per CLAUDE.md v1.15.0) | 2026-05-02 | ✅ Self-attested ready (M6 + M9 added) | | Critic | (critic role per CRITIC.md v1.7.0) | 2026-05-02 | ✅ All findings closed (8 P-C1..P-C8 + 5 P-S1..P-S5 critic-fix bundle) | | Author | (project owner) | — | Ожидает решения об audit firm engagement | | External auditor | (TBD) | — | Pending engagement | --- **Status:** READY FOR EXTERNAL AUDIT (M1 + M2 + M3 + M4 + M5 + M6 + M9 layers scope, 16 крейтов, ~14640 LOC, 255+ invariants + 14 e2e network tests, 53/53 findings closed: 40 prior + 13 P-C1..P-C8 + P-S1..P-S5 critic-fix bundle; spec v35.23.0 — M6 transport closure + Genesis Decree network params restructure).