25 KiB
Pre-audit self-attestation checklist — M1+M2+M3+M4+M5+M6+M9 layers
Заполняется архитектором перед каждым external audit engagement. Все пункты должны быть [x] либо иметь явное обоснование почему [ ].
A. Conformance proofs
- NIST FIPS 204 ML-DSA-65 KeyGen byte-exact vs ACVP-Server published vectors (25 cases)
- NIST FIPS 203 ML-KEM-768 KeyGen byte-exact vs ACVP-Server published vectors (25 cases)
- NIST FIPS 204 ML-DSA-65 SigGen deterministic byte-exact для empty context (1 case)
- 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
- Layer 1 Rust shim 662 строк (
crates/mt-crypto/src/lib.rs), все 7unsafeblocks с// 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). - Layer 2 own C wrapper 457 строк (
mt_crypto.c) + 67 строк (mt_crypto.h), focused EVP API wrapping,-Wall -Wextra -Wpedantic -Werror - Layer 3 vendored OpenSSL 3.5.5 LTS через
openssl-src = "=300.5.5+3.5.5"byte-pinned - 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. - No
serdeauto-derive в consensus-critical types (customCanonicalEncodetrait)
C. Memory safety + secret hygiene (Pass 17 enforcement)
- Drop+zeroize для
SecretKey(4032B) иMlkemSecretKey(2400B) - Heap-allocated через Box — secret bytes живут в heap, не stack inline (verified
secret_key_is_heap_allocatedtest:size_of::<SecretKey>() == 8pointer) - mlock applied на heap pages для secret bytes (best-effort через
alloc_locked_secret_box); fallback assumption: encrypted swap (FileVault macOS / LUKS Linux) - Stack hygiene при FFI —
keypair_from_seed*пишет напрямую в heap-locked Box; никаких stack temporary buffers с secret bytes (verified в crates/mt-crypto/src/lib.rs — функцииkeypair_from_seedиkeypair_from_seed_mlkem, search by name) - munlock в Drop перед heap dealloc (best-effort, errno ignored)
- No
Clone/Copyderive на secret types (compile-time enforced via security_invariants test) - No
PartialEq/Eqна secret types (предотвращает timing leak через ==) - No secret bytes в logs / stdout / stderr (file-content scan через
no_println_or_log_on_secret_bytes_in_lib_codetest) mt-examples/m1_crypto.rs::print_skgated через env var (M1_DUMP_SK=1opt-in; по умолчанию SK bytes redacted; механизм:dump_sk_enabled()функция в m1_crypto.rs проверяется вprint_skстрока 76)- FFI buffer sizes match contract (PUBLIC_KEY_SIZE / SECRET_KEY_SIZE / SIGNATURE_SIZE constants used consistently)
- 13 security invariants automated в crates/mt-crypto/tests/security_invariants.rs — regression detection
- 6 Security Cards заполнены в docs/security-cards.md — Pass 17 mandatory enforcement
D. Error surface
sign/keypair_from_seed/keypair_from_seed_mlkemвозвращаютResult<_, CryptoError>(не panic)- CryptoError enum с 11 variants (Display + std::error::Error impls)
- No
unwrap()/expect()в lib коде кроме явного internal invariant с комментарием - No silent error swallowing (
.ok(),let _ = ...)
E. Determinism (consensus path)
- FIPS 204 Algorithm 2 deterministic Sign через
OSSL_SIGNATURE_PARAM_DETERMINISTIC=1 - No
f32/f64в crypto path (consensus determinism per Montana [I-3] + [I-9]) - No
HashMap/HashSetiteration order dependency - No
SystemTime::now/Instant::nowв consensus path (только в test/tool helperkeypair()который gated#[cfg(any(test, feature = "testing"))])
F. Misuse resistance
SecretKey::from_array(arbitrary_bytes)при последующемsign()возвращаетErr(CryptoError::InvalidSecretKey), не panic (F-7 closure через F-2 Result API)keypair()(weak entropy test helper) не доступен в production binary (cfg-gate)- Public type fields private (no struct literal construction обходящая validation)
- No
Defaultimpl для types requiring real crypto material
G. Build & reproducibility
Cargo.lockcommitted- Точные версии всех dependencies (
=X.Y.Z) rust-toolchain.tomlpinned- Docker reproducible build с pinned base image digest
- CI gate
reproducible_releaseпроверяет byte-identity между двумя независимыми runs - Cross-compile correctness через
CARGO_CFG_TARGET_OSenv var
H. Dependencies
- Crypto deps все production-grade (OpenSSL 3.5.5 LTS, sha2 0.10.9, no pre-1.0 в consensus path)
- No "USE AT YOUR OWN RISK" libraries в production paths
cargo auditclean (verified 2026-04-26: 0 vulnerabilities, 0 warnings, 39 dependencies scanned). Prerequisite:cargo install cargo-audit --locked(один раз). Verify:cd "<repo-root>" && cargo auditcargo tree -p mt-crypto | grep -iE "ml-dsa|ml-kem|hybrid-array"→ 0 hits (RustCrypto pre-1.0 deps удалены полностью per M1-F migration)- License compatibility — все deps MIT / Apache-2.0 / BSD / ISC
I. Documentation
AUDIT.mdв корне репозитория с audit chain + threat model + reproduction commands- Threat model explicit: in scope / out of scope / known limitations с closure paths
- Spec references в коде через
// spec, раздел "<name>"без версии (single source of truth —VERSION.md) - Manual Validation Gate scenarios documented в
ROADMAP.md - Architect + critic roles (CLAUDE.md, CRITIC.md) в репозитории — peer-reviewable methodology
J. Open findings
- Zero open audit findings в M1 foundational layer (per AUDIT.md §5)
- All 7 M1-F audit findings (F-1..F-7) закрыты конструкцией
- All 5 audit-package findings (F-A1..F-A5) закрыты конструкцией
- All 4 M0+M1+M2 critic findings (F-1..F-4 from
b4a00b1audit) закрыты: 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 ниже - 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 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 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 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 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. Закрытие требует:
- Run cargo-mutants
- Analyze surviving mutations
- 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;
атакующий создаёт
Naccounts с 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 "<repo-root>" && cargo test -p mt-crypto-native --test nist_acvp_kat -- --nocapture
Internal correctness baselines:
cd "<repo-root>" && cargo test -p mt-crypto-native -p mt-crypto -p mt-mnemonic
Recovery flow end-to-end:
cd "<repo-root>" && cargo test -p mt-mnemonic --test e2e_recovery -- --nocapture
M2 Determinism invariants (mt-merkle / mt-genesis / mt-state / mt-timechain):
cd "<repo-root>" && 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 "<repo-root>" && 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 "<repo-root>" && 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 "<repo-root>" && 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 <name>.tmp после save, atomic overwrite).
M5 Pseudo-fuzz harness (mt-store wire decoders):
cd "<repo-root>" && 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 "<repo-root>" && python3 scripts/oracle_python_sha256.py
cd "<repo-root>" && 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 "<repo-root>" && 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), который выполняет тот же двойной build с byte-identity assertion. Auditor может проверить green CI runs за период как evidence.
Build sanity:
cd "<repo-root>" && cargo fmt --all -- --check && cargo clippy --all-targets -- -D warnings && cargo build --all --release
H. M6 Network layer (mt-net + mt-net-transport)
- 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)
- mt-net 110 unit + integration tests pass —
cargo test -p mt-net --features testing - 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) - Manual Validation Gate scenario 6 PASS — two-node handshake e2e (commit
9a15f49);tests/e2e_two_node_handshake.rs::two_node_request_response_ping_pong - 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} - [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
- Backpressure rules B1-B6 enforced — max_protocol_payload_bytes (1 MiB) + max_sf_ciphertext_bytes (64 KiB) reject до allocation per spec
- 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
- 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)
- mt-conformance crate created — public binding test vectors для cross-implementation byte-exact verification
- 2 unit tests pass —
envelope_vectors_byte_exact+pow_target_byte_exact - Initial vectors covered — envelope A1/A2/A3 + IBT B1 (after P-C2 rename) + Bootstrap PoW F1/F2 target derivation
- iOS port мirrored —
iOS/Apps/Montana/MontanaTests/MTConformanceVectors.swiftbyte-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).