montana/Монтана-Протокол/Внешний аудит/claude-opus-4-7_2026-04-27_T121239.md

37 KiB
Raw Permalink Blame History

Внешний аудит кода Montana — отчёт #3 (incremental, M3 + закрытия M1/M2)

Аудитор: Claude Opus 4.7 (1M context), модель claude-opus-4-7[1m] Дата проведения: 2026-04-27, T12:12:39 — T13:00:00 (примерно) Локация: /Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код/ Тип аудита: incremental — только новые части + verification закрытий из предыдущих отчётов

Предыдущие отчёты:


1. Scope этого аудита

Новый scope (этот отчёт)

Артефакт Файл Строк Назначение
M3 — mt-account crates/mt-account/src/lib.rs 2574 apply_proposal layer: 4 user opcodes (Transfer/ChangeKey/Anchor/TransferActivation), validate/apply, op_hash, settle_window, apply_emission, monetary_epoch_tick, build_genesis_state
M3 determinism tests crates/mt-account/tests/determinism_invariants.rs 530 35 invariants — type code stability, op_hash determinism, R2 invariant, apply determinism, settle order-independence, genesis_state_root with monetary
M1 обновления crates/mt-crypto/src/lib.rs 568 → 648 F-4 closure (3 mlock SAFETY), F-6 closure (getrandom вместо SystemTime+PID)
M1 FIPS context crates/mt-crypto-native/csrc/mt_crypto.c 375 → 443 F-8 closure: новая функция mt_sign_mldsa_ctx для NIST CAVP non-empty context cases
M1 FFI binding crates/mt-crypto-native/src/lib.rs 40 → 49 Декларация mt_sign_mldsa_ctx
M1 C header crates/mt-crypto-native/csrc/mt_crypto.h 56 → 67 Declaration + 13 status codes
NIST fixtures (новый) tests/fixtures/nist_acvp/ml_dsa_65_siggen_det_external_pure_all15.json 354 KB F-8 closure: 15 SigGen cases

Total новый M3 audit surface: 3104 строки (mt-account src + tests).

Не повторяю (покрыто первыми двумя отчётами)

  • M1 mt-mnemonic, mt-codec base
  • M2 mt-merkle, mt-genesis, mt-state, mt-timechain
  • mt-crypto core architecture (только diff)
  • 51 NIST KAT base cases

2. Методология (та же, ноль доверия к документации)

Доверяю только: исходному коду, публичным NIST FIPS / RFC стандартам, NIST CAVP repository (github.com/usnistgov/ACVP-Server), RustSec Advisory DB.

Не доверяю ни одному .md файлу в репо (включая обновлённый AUDIT.md, security-cards.md, audit-checklist.md, спеку Montana v33.1.3).

Single thread / single process: соблюдено через .cargo/config.toml.


3. Закрытия findings из предыдущих отчётов — verification

AUDIT.md заявляет (commit 6ff26b3) что 14/19 findings из моего первого отчёта закрыты + ряд M2 findings из второго отчёта. Я независимо проверил каждое заявление.

Из первого отчёта (M1)

ID Описание Статус заявленный Verified фактически
F-1 line counts mt-crypto: 568 → 643 (не упоминается) НЕ закрыт. AUDIT.md теперь заявляет 648. Фактически 648 . Cleared accidentally — line counts updated на 648 в текущем AUDIT.md и фактическом коде совпадают.
F-2 cargo fmt --check FAILS closed Закрыт. Прогон cargo fmt --all -- --check → exit 0 (verified).
F-3 Stale "RustCrypto pure-Rust" в m1_crypto.rs closed Не verified в этом аудите (out of scope), доверять заявлению.
F-4 3 unsafe blocks без // SAFETY: closed Закрыт. Lines 165 (Drop SK munlock), 186 (alloc_locked_secret_box mlock), 352 (Drop MlkemSK munlock) — все имеют формальный // SAFETY: теперь, проверил построчно.
F-5 mlock без runtime warning closed ⚠️ Частично закрыт. SAFETY-комментарий расширен (lines 186-194 объясняет failure modes). Runtime warning не добавлен — failure всё ещё silent (let _ = libc::mlock(...)). Documentation-level closure, не runtime closure.
F-6 test-only keypair() weak entropy closed Закрыт. Line 261: getrandom::getrandom(&mut seed).expect(...). Замена SystemTime + PID + stack address на OS CSPRNG (Linux getrandom, macOS SecRandomCopyBytes, Windows BCryptGenRandom).
F-7 PBKDF2/HKDF/HMAC intermediate buffers без zeroize closed Не verified в этом аудите (out of scope, mt-mnemonic).
F-8 SigGen NIST KAT 1/15 cases только closed Закрыт. Новый fixture ml_dsa_65_siggen_det_external_pure_all15.json (354 KB) + новая функция mt_sign_mldsa_ctx для non-empty context. 15/15 PASS independently verified (см. §6 cross-check).
F-9 rename kat_independent → regression_baselines closed Не verified directly (file was renamed per AUDIT.md заявление; doc reference в AUDIT.md теперь указывает regression_baselines.rs).
F-12 split_whitespace вместо split(' ') closed Не verified directly (out of scope).
F-13 "13 error codes" semantic ambiguity (не закрыт) AUDIT.md теперь заявляет "13 status codes (1 success + 12 errors)" — semantic clarification .
F-14, F-15 constant-time, fuzzing deferred Acknowledged как открытые, не closed (понятно).
F-18 cc parallel feature closed Не verified directly.
F-19 const-cast в SAFETY closed Не verified directly.

Итог по первому отчёту: 4/19 verified закрыты конструкцией в этом аудите (F-2, F-4, F-6, F-8), 1/19 частично закрыто (F-5), остальные claimed closed но не verified в этой incremental сессии.

Из второго отчёта (M2)

ID Описание Статус заявленный Verified фактически
M2-1 Genesis bootstrap pubkeys placeholder flagged как known limitation ⚠️ Не закрыт, но re-classified. AUDIT.md §3 Known limitations 0 теперь явно документирует это как "блокер mainnet, не блокер аудита кода". Программный check mt_genesis::is_genesis_bootstrap_finalized упомянут (но не verified в этом аудите). Acceptable framing.
M2-2 Stale comment pin 30/29 в mt-state (не упомянут) ⚠️ Не verified в этом отчёте, AUDIT.md не упоминает закрытие.
M2-3 Binding test vectors на pin 41/40 (не явно упомянут) Закрыт. Line 1912-1918 mt-account: тест r_baseline_at_epoch_one_is_first_step явно проверяет pin 41/40: 13e9 × 41 / 40 = 13_325_000_000. Production constants теперь покрыты binding vectors.
M2-13 MonetaryState не в compute_state_root closed Закрыт. compute_state_root(node_root, candidate_root, account_root, monetary) — новая сигнатура с параметром monetary. Test genesis_state_root_includes_monetary_per_m213 (line 293-304 mt-account) проверяет: разные MonetaryState → разные state_root. Семантика fork detection восстановлена.

Итог по второму отчёту: 2 verified закрыты (M2-3, M2-13), 1 re-classified (M2-1), остальные не verified в incremental режиме.

Новые closures которые сделаны в M3 без явного finding (proactive)

  • M3-1 (window_w u32→u64)window_w_to_u32 cast с descriptive panic; verified test apply_panics_on_window_w_above_u32_max
  • M3-3 (checked arithmetic в balance updates)checked_sub/checked_add с descriptive panic в всех apply_*; verified test apply_transfer_panics_on_unsanitized_underflow

4. Сильные стороны M3 (mt-account)

4.1. Pure Rust, ноль unsafe blocks

$ grep -n "unsafe " crates/mt-account/src/lib.rs
(0 hits)

Весь M3 layer написан на чистом Rust без unsafe — упрощает audit surface.

4.2. SSI Правило R2 — op_hash без signature

op_hash(op) = SHA-256("mt-op" || 0x00 || signed_scope(op)) — signature исключена из hash input. Это:

  • Anti-grinding: signature scheme может быть randomized или deterministic — op identity не зависит
  • Stable identifier: один и тот же logical op даёт один и тот же hash независимо от подписи
  • Test op_hash_stable_under_signature_mutation (line 1015-1031) — explicit test что mutation signature не меняет op_hash

4.3. Validate-before-apply pattern строго соблюдён

Каждая apply_* функция начинается с:

let mut sender = state
    .get(&op.sender)
    .expect("protocol invariant: validate_transfer ensures sender exists")
    .clone();

apply_* assumes validated input. Это design pattern с явным expect() контрактом — caller обязан pre-validate. AUDIT.md документирует 7 expect() в audit-checklist §K.

4.4. Checked arithmetic во всех balance/counter updates

sender.balance = sender.balance.checked_sub(op.amount).unwrap_or_else(|| {
    panic!("apply_transfer: balance underflow — protocol invariant breach \
            (validate_transfer должен был отвергнуть op с balance={} < amount={})",
            sender.balance, op.amount)
});

Все integer операции:

  • checked_sub для balance декремента (sender)
  • checked_add для balance инкремента (receiver, operator reward)
  • checked_add для op_height и account_chain_length

Descriptive panic message с context — fail-fast при protocol invariant violation, не silent wrap.

4.5. Anti-spam через time-based scarcity ([I-15] compliance)

TransferActivation cooldown:

if sender.last_activation_window != 0
    && current_window < sender.last_activation_window.saturating_add(tau2_windows)
{
    return Err(OpError::ActivationCooldownNotElapsed);
}

1 активация на sender за τ₂ = 20_160 окон ≈ 14 дней. Time-based, не money-based — соответствует [I-15] разделу спеки.

4.6. ChangeKey подписан старым ключом, не новым

validate_change_key явно проверяет signature через sender.current_pubkey (старый ключ), не через new_pubkey:

if !verify_signed_scope(&scope, &op.signature, &sender.current_pubkey) {
    return Err(OpError::InvalidSignature);
}

Test validate_change_key_rejects_signature_by_new_key_not_old (line 1459-1482) — adversarial test что попытка подписать новым ключом отвергается.

4.7. TransferActivation receiver binding — anti-Sybil

validate_transfer_activation проверяет:

let derived = derive_account_id(op.suite_id, op.receiver_pubkey.as_bytes());
if derived != op.receiver {
    return Err(OpError::InvalidBinding);
}

receiver = SHA-256("mt-account" || suite_id || pubkey) — нельзя создать аккаунт с произвольным ID. Pubkey commit-нут в receiver через хеш.

4.8. settle_window сортирует по op_hash lex asc — детерминизм

indexed.sort_by_key(|(h, _)| *h);
for (_, op) in indexed {
    apply(op, state, window_w);
}

Test settle_window_sorts_by_op_hash_lex_asc (line 2020-2047) — порядок входа не влияет на root. Защита от non-determinism через input order.

4.9. apply_proposal orchestration explicit ordering

apply_emission(... window_w, winner_id, monetary, params); // Step 2: reward W-1
monetary_epoch_tick(monetary, window_w, params);           // Step 2.5: monetary boundary
apply_chain_length_increment(node_table, confirmers, w);   // Step 3.5
apply_checkpoint_rotation(node_table, w, params);          // Step 3.6
compute_state_root(node_root, candidate_root, account_root, monetary)  // Step 4

Боковой эффект: emission до monetary_epoch_tick — чтобы reward(W-1) использовал MonetaryState активный для W-1, а не уже сдвинутый.

4.10. settle_window отделён от apply_proposal — design choice

Comment lines 750-757 явно объясняет: settle (cemented user ops) выполняется caller'ом ДО apply_proposal, не внутри. Это делает orchestration ordering visible callerу.

4.11. monetary_epoch_tick boundary правильный

if e_current > e_prev && e_current > 0 {
    monetary.apply_step(...)
}

e_current > 0 явно — эпоха 0 использует R_GENESIS без step. Первый step при переходе в эпоху 1.

4.12. checkpoint rotation на τ₂-границе

Six-element ring buffer chain_length_checkpoints[0..6]:

  • На каждом τ₂-boundary: shift left, новый = current chain_length
  • snapshot = chain_length - oldest_after_rotation = накопление за 6 τ₂ окон ≈ 84 дня

Это implements weighted_ticket fairness через 6-window history.

4.13. Genesis State design clean

  • 1 bootstrap account: is_node_operator = true, balance = 0
  • 1 bootstrap node: chain_length = 1 (spec invariant ≥ 1), chain_length_checkpoints = [0; 6]
  • Empty Candidate Pool
  • frontier_hash = SHA-256("mt-genesis" || account_id) — domain-separated

4.14. NIST KAT расширен с 1 до 15 cases (F-8 closure)

Я независимо скачал NIST source (https://github.com/usnistgov/ACVP-Server/master/.../ML-DSA-sigGen-FIPS204/internalProjection.json) и сравнил byte-exact:

Group NIST source Local fixture Match
ML-DSA-65 KeyGen tgId=2 25 cases 25 cases 25/25 byte-exact
ML-KEM-768 KeyGen 25 cases 25 cases 25/25 byte-exact
ML-DSA-65 SigGen tgId=3 (det+ext+pure) 15 cases 15 cases 15/15 byte-exact

Canonical SHA-256 локального ML-DSA-65 SigGen fixture = NIST tgId=3 SHA-256: 31b2d50f2c735e1a7d6eb35b8b8d8e71fb0f1e1f42076131b7a9cfe9fd6d67f8

Это подтверждает что AUDIT.md заявление "66 differential test cases vs NIST CAVP" — независимо verified.

4.15. Тестовое покрытие исключительно высокое

  • 93 unit tests в mt-account/src/lib.rs — все error paths покрыты, dispatcher tests, encoded sizes, field orders, op_hash invariants, validate (per opcode), apply (per opcode), genesis, monetary, supply, apply_proposal orchestration
  • 35 determinism invariants в tests/ — encoded sizes, type code stability, op_hash R2, apply determinism, settle order-independence, genesis_state_root includes monetary, M3-1/M3-3 closures verified

4.16. Cargo audit чистый

vulns: 0, deps: 40, warnings: 0

40 deps (один новый transitive: getrandom для F-6 closure).

4.17. cargo fmt + clippy чистые

cargo fmt --all -- --check    → EXIT 0  (F-2 closure)
cargo clippy --all-targets -- -D warnings → EXIT 0  (clean)

5. Findings — новые (только M3 specific)

Findings нумерованы с префиксом M3-A- для отделения от первого/второго отчётов.

M3-A-1 [LOW] — Inconsistent checked arithmetic в apply_chain_length_increment

Описание. В apply_chain_length_increment (lines 619-628 mt-account/src/lib.rs):

let mut node = existing.clone();
node.chain_length += 1;  // ← raw `+=`, без checked_add
node.last_confirmation_window = window_w;

apply_transfer, apply_change_key, apply_anchor, apply_emission — все используют checked_add с descriptive panic. Только apply_chain_length_increment использует raw +=.

Воздействие. chain_length is u64. Overflow horizon = 2^64 = 1.84×10^19. На 60 sec/window это ~3.5×10^11 лет. Practically безопасно. Но pattern asymmetry с другими apply_* — discipline gap.

Рекомендация. Применить checked_add для consistency:

node.chain_length = node.chain_length.checked_add(1)
    .unwrap_or_else(|| panic!("apply_chain_length_increment: chain_length overflow at u64::MAX"));

M3-A-2 [LOW] — chain_length - chain_length_checkpoints[0] без checked_sub

Описание. В apply_checkpoint_rotation (line 644):

rotated.chain_length_snapshot = rotated.chain_length - rotated.chain_length_checkpoints[0];

Если chain_length_checkpoints[0] > rotated.chain_length (theoretically violation invariant), silent u64 underflow → wrap. chain_length_snapshot становится огромным числом близким к u64::MAX → может неправильно работать в lottery weighting.

Воздействие. Currently relies на invariant что checkpoints всегда ≤ chain_length, который maintained через rotation logic. Но silent failure если invariant violated через external bug или corrupted state.

Рекомендация. Использовать checked_sub с panic при violation:

rotated.chain_length_snapshot = rotated.chain_length
    .checked_sub(rotated.chain_length_checkpoints[0])
    .unwrap_or_else(|| panic!("apply_checkpoint_rotation: invariant breach — checkpoint > chain_length"));

M3-A-3 [INFO] — Spec ambiguity: genesis_candidate_root

Описание. Test comment в коде (line 2526-2528):

// NB: spec (v29.7.0, строка 1500) упоминает "genesis_candidate_root = 0x00 × 32"
// но sparse Merkle root пустого дерева = empty_internal(TREE_DEPTH), не 0x00.
// mt-merkle возвращает empty_internal(256). Это spec ambiguity — flagged.

Воздействие. Код использует empty_internal(256), не 0x00 × 32. Если spec требует 0x00 × 32 — divergence между spec и code. Test обходит проблему проверяя только determinism (fresh.root() == g.root()), не abs value.

Рекомендация. Resolve spec ambiguity либо через spec patch (явно указать что genesis_candidate_root = empty_internal(256)), либо через code change (вернуть 0x00 × 32 если spec правильный). Без resolution — silent divergence риск при independent re-implementation.

M3-A-4 [LOW] — validate(op) для TransferActivation bypass cooldown check

Описание. Generic dispatcher validate(op, state) (lines 315-325):

pub fn validate(op: &Operation, state: &AccountTable) -> Result<(), OpError> {
    match op {
        ...
        Operation::TransferActivation(inner) => {
            // Без контекста окна/τ₂ — bypass cooldown check; caller использует validate_transfer_activation.
            validate_transfer_activation(inner, state, 0, 0)
        },
    }
}

Передача current_window=0, tau2=0 означает: cooldown check никогда не fail, потому что 0 < 0 + 0 = false.

Воздействие.

  • Caller обязан использовать validate_transfer_activation(op, state, current_window, tau2_windows) напрямую с production константами для TransferActivation
  • Если caller ошибочно использует общий validate(op, state) — TransferActivation cooldown не проверяется → spam vulnerability через множественные TransferActivation в одно окно
  • Comment предупреждает, но silent fallback

Рекомендация. Один из:

  1. Удалить Operation::TransferActivation из generic validate(op) (заставить compile-time error при попытке dispatch без context)
  2. Изменить signature validate(op, state, ...) принимая Option<(window, tau2)> — caller обязан передать
  3. Изменить validate_transfer_activation(op, state, 0, 0) на возврат конкретной ошибки вроде OpError::ContextRequired который заставляет caller вызвать правильную функцию

M3-A-5 [INFO] — apply_emission зависит от operator_account_id existing в AccountTable

Описание. Line 605:

let mut operator = account_table
    .get(&operator_id)
    .expect("protocol invariant: operator account exists")
    .clone();

Если node имеет operator_account_id = ID который не в AccountTable → panic.

Воздействие.

  • Genesis bootstrap имеет operator = bootstrap_account_id (existed via build_genesis_state) — OK
  • При future NodeRegistration в M4 — caller должен гарантировать что operator_account_id создан в AccountTable до registration node
  • Если node добавлен с operator не в AccountTable (corrupted state) → panic in apply_emission на следующем emission cycle

Рекомендация. Документировать invariant в module-level comment либо добавить debug_assert в NodeTable insert (но это уже M4 territory).

M3-A-6 [INFO] — chain_length_checkpoints[6] semantics now documented (closes M2-8)

Описание. В второго отчёте M2-8 я писал что chain_length_checkpoints: [u64; 6] без объяснения. В M3 это clearer через apply_checkpoint_rotation:

  • 6-element ring buffer
  • На каждой τ₂-границе: shift left, newest = current chain_length
  • snapshot = chain_length - oldest_after_rotation = накопление за 6 τ₂ окон

Это implements weighted_ticket fairness через 6-window history (~84 дня at 14 days per τ₂).

Severity: INFO (не finding, closes M2-8 from second report).

M3-A-7 [INFO] — settle_window vs apply_proposal ordering — invariant by comment

Описание. Caller обязан вызывать:

  1. settle_window(account_table, cemented_ops, window_w) ДО
  2. apply_proposal(account_table, node_table, candidate_pool, monetary, input, params)

Документировано comment lines 750-757. Compile-time enforcement отсутствует — нет type-level гарантии что settle вызвано до apply_proposal.

Воздействие. Если caller перепутает order — silent fork (cemented ops apply'ятся после emission, balance changes не видны для reward calc).

Рекомендация. Пакет API в struct с typestate pattern:

let pre_emission = SettledState::new(account_table, cemented_ops, window_w);
let root = pre_emission.apply_proposal(node_table, candidate_pool, monetary, input, params);

Без typestate — просто документированный invariant, как сейчас.

M3-A-8 [INFO] — genesis_state_root signature change cascade

Описание. compute_state_root теперь принимает 4-й параметр monetary (M2-13 closure). Это breaking change для любого caller который использовал старую 3-параметровую signature.

Воздействие. В рамках текущего workspace cascade applied (verified через cargo build --all clean). Для внешних users (не существуют yet до публикации API) — breaking change. Pre-mainnet acceptable.

Severity: INFO (acceptable per Pre-mainnet принцип, не bug).


6. Тесты прогон (single thread / single process)

Cargo audit

vulns: 0, deps: 40, warnings: 0

Cargo fmt --all -- --check

EXIT = 0  ✅ (F-2 закрыт)

Cargo clippy --all-targets -- -D warnings

EXIT = 0  ✅ clean

NIST ACVP KAT (66 cases)

running 4 tests
test nist_acvp_ml_dsa_65_keygen_byte_exact ... ok       (25/25)
test nist_acvp_ml_dsa_65_siggen_deterministic_external_pure_all15 ... ok    (15/15: 1 empty + 14 non-empty ctx)
test nist_acvp_ml_kem_768_keygen_byte_exact ... ok      (25/25)
test siggen_empty_ctx_equivalence ... ok                (mt_sign_mldsa ≡ mt_sign_mldsa_ctx empty)

test result: ok. 4 passed; 0 failed

NIST source download + byte-exact comparison

File Local SHA-256 canonical NIST source SHA-256 canonical Match
ML-DSA-65 KeyGen (25 cases) 2cbfd5571eabd9... 2cbfd5571eabd9...
ML-DSA-65 SigGen (15 cases) 31b2d50f2c735e1a... 31b2d50f2c735e1a...
ML-KEM-768 KeyGen (25 cases) (verified second отчёт) (verified second отчёт)

M3 mt-account tests

test result: ok. 93 passed; 0 failed; 0 ignored   (unit tests)
test result: ok. 35 passed; 0 failed; 0 ignored   (determinism invariants)
test result: ok. 0 passed                          (doc tests)

Total M3: 128 tests PASS, 0 failed.

Combined test status (3 audits)

Layer Tests passed NIST KAT
M1 (audit #1) 118 51 (now 66)
M2 (audit #2) 165 (использует M1 KAT)
M3 (audit #3) 128 (использует M1 KAT)
Combined audited 411 tests + 66 NIST KAT

7. Spec ↔ Code byte-exact alignment table verification

AUDIT.md table (§2 Conformance Proofs, lines 164-182) заявляет 17 alignments. Independent verification:

AUDIT.md заявление Verified ✓/✗ Источник
ML-DSA-65 pubkey 1952B mt-crypto::PUBLIC_KEY_SIZE = 1952
ML-DSA-65 secretkey 4032B mt-crypto::SECRET_KEY_SIZE = 4032
ML-DSA-65 signature 3309B mt-crypto::SIGNATURE_SIZE = 3309
ML-DSA-65 seed 32B mt-crypto::KEYPAIR_SEED_SIZE = 32
ML-KEM-768 ek 1184B mt-crypto::MLKEM_PUBLIC_KEY_SIZE = 1184
ML-KEM-768 dk 2400B mt-crypto::MLKEM_SECRET_KEY_SIZE = 2400
ML-KEM-768 seed 64B mt-crypto::MLKEM_SEED_SIZE = 64
AccountRecord 2059B mt-state::ACCOUNT_RECORD_SIZE = 2059
NodeRecord 2098B mt-state::NODE_RECORD_SIZE = 2098
CandidateRecord 2082B mt-state::CANDIDATE_RECORD_SIZE = 2082
ProposalHeader 3722B ⚠️ Out of scope mt-consensus / mt-entry — M4-M5
ProtocolParams 4110B mt-genesis::PARAMS_ENCODED_SIZE = 4110
Sparse Merkle Tree depth 256 mt-merkle::TREE_DEPTH = 256
Inflation pin 41/40 = 2.5% mt-genesis: inflation_num=41, inflation_den=40 (verified test asymptotic_ppm = 25_000)
R_GENESIS 13 Ɉ mt-genesis: r_genesis_moneta=13_000_000_000
monetary_epoch_windows 524_160 mt-genesis: monetary_epoch_windows = 524_160
Domain registry 32 domains mt-codec const list (verified second отчёт). AUDIT.md в прошлом отчёте говорил 33; теперь 32 — синхронизировано

Итог: 16/17 verified, 1 out of scope (M4-M5).

(Из второго отчёта finding "33 vs 32" исправлен — AUDIT.md теперь правильно говорит 32.)


8. Известные ограничения этого аудита

  1. F-3, F-7, F-9, F-12, F-18, F-19 из первого отчёта — заявлены closed, не verified в этом incremental прогоне (нужен полный re-audit M1 для подтверждения)
  2. M2-2 — stale comment pin 30/29 в mt-state — не verified в этом отчёте
  3. AUDIT.md audit history line 335 не упоминает мой второй отчёт (M2 scope, 17 findings) — gap в documentation accountability
  4. ProposalHeader 3722B не verified (M4-M5 scope)
  5. Side-channel, formal verification, fuzzing, audit firm signature — те же ограничения первых двух отчётов

9. Recommendations по приоритетам

HIGH (закрыть до production audit firm)

  1. AUDIT.md history gap — добавить строку в Audit History про второй мой external audit (M2 layer, 17 findings, 2026-04-26 T232707)
  2. M3-A-4 — TransferActivation cooldown bypass через generic validate(op) — изменить signature чтобы caller был обязан передать context

MEDIUM (закрыть до v1.0 release)

  1. M3-A-1apply_chain_length_increment использовать checked_add (consistency)
  2. M3-A-2apply_checkpoint_rotation использовать checked_sub (defense-in-depth)
  3. M3-A-3 — Resolve spec ambiguity в genesis_candidate_root (spec patch либо code change)

LOW

  1. F-5 — runtime telemetry warning при mlock failure (не только doc-level closure)
  2. M3-A-5 — документировать operator_account_id existing invariant в module comment
  3. M3-A-7 — typestate pattern для settle_window vs apply_proposal ordering
  4. M2-2 — обновить stale comment pin 30/29 в mt-state на актуальный 41/40

10. Итоговая оценка уровня безопасности

Для M3 layer: 8.5 / 10

(использую half-step потому что качество code заметно выше предыдущих layer, но не достигает 9 без закрытия HIGH findings)

За что ставлю 8.5 (положительное):

  1. Pure Rust, 0 unsafe blocks в M3
  2. SSI Правило R2 (op_hash без signature) implemented + tested adversarially
  3. Validate-before-apply pattern строго соблюдён
  4. Checked arithmetic во всех balance/counter updates с descriptive panic
  5. Anti-spam через time-based scarcity ([I-15] compliance)
  6. ChangeKey signed by old key (правильная семантика)
  7. TransferActivation receiver binding (anti-Sybil через derive_account_id)
  8. settle_window canonical sort by op_hash (детерминизм)
  9. apply_proposal orchestration ordering correct (emission → tick)
  10. Genesis state design clean
  11. MonetaryState теперь в state_root (M2-13 closure)
  12. Production pin 41/40 binding test vectors (M2-3 closure)
  13. NIST KAT расширен 1→15 SigGen cases + 25+25 KeyGen + equivalence test (66 total)
  14. 15/15 SigGen byte-exact match с NIST source (independently downloaded + verified)
  15. 128 tests PASS (93 unit + 35 determinism), 0 failed
  16. cargo audit / clippy / fmt all clean
  17. F-2, F-4, F-6, F-8 из первого отчёта закрыты конструкцией

За что снимаю 1.5 (отрицательное):

  1. M3-A-4 TransferActivation cooldown bypass через generic validate(op) — silent spam vulnerability при misuse
  2. M3-A-1, M3-A-2 inconsistent checked arithmetic — discipline gap
  3. M3-A-3 spec ambiguity flagged в коде но не resolved
  4. F-5 runtime warning при mlock failure не добавлен (только doc-level)
  5. AUDIT.md audit history gap (мой второй отчёт не упомянут)
  6. Genesis bootstrap pubkeys placeholder (re-classified как known limitation, но всё ещё блокер mainnet)
  7. Те же глобальные ограничения первых двух отчётов: side-channel hardware testing нет, formal verification нет, audit firm signature нет

Чтобы поднять до 9: закрыть M3-A-4 (HIGH) + sync AUDIT.md history + M3-A-1/M3-A-2 (consistency)

Чтобы поднять до 10: + audit firm signature + formal verification + side-channel hardware testing + Genesis ceremony complete

Заключение по M3

M3 layer (apply_proposal, account operations, emission) демонстрирует высокий уровень design discipline:

  • Чистый Rust без FFI рисков
  • Explicit ordering invariants (validate → apply → settle → orchestrate)
  • Anti-grinding через op_hash R2
  • Anti-Sybil через receiver binding
  • Anti-spam через time-based cooldown
  • Comprehensive testing (128 tests, 35 invariants)
  • Production constants теперь в binding tests

Главные слабости: дисциплина checked arithmetic не везде consistent (apply_chain_length_increment, apply_checkpoint_rotation), один real cooldown-bypass risk (M3-A-4), и documentation drift (AUDIT.md history skip второго отчёта).

Прогресс между отчётами очевиден: многие findings первого аудита (F-2, F-4, F-6, F-8) закрыты конструкцией; M2-3 и M2-13 из второго отчёта закрыты. Это показывает active maintenance и готовность отвечать на external audit feedback.

Я подтверждаю заявление AUDIT.md "M1 + M2 + M3 layers — READY FOR EXTERNAL AUDIT" в части code quality с двумя оговорками:

  1. M3-A-4 (HIGH) должен быть закрыт перед audit firm engagement
  2. AUDIT.md audit history должен upd подтвердить второй мой external audit для accountability

11. Метаданные воспроизведения

Команды для проверки:

cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo audit --json | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'vulns: {d[\"vulnerabilities\"][\"count\"]}')"
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo fmt --all -- --check
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo clippy --all-targets -- -D warnings
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo test -p mt-account
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo test -p mt-crypto-native --test nist_acvp_kat -- --nocapture
cd /tmp && curl -sL "https://raw.githubusercontent.com/usnistgov/ACVP-Server/master/gen-val/json-files/ML-DSA-sigGen-FIPS204/internalProjection.json" -o nist_mldsa_siggen.json

Среда выполнения:

  • Платформа: Darwin 24.6.0 (macOS), ARM64 (Apple Silicon)
  • rustc 1.92.0, cargo 1.92.0 (Homebrew)
  • .cargo/config.toml: jobs=1, RUST_TEST_THREADS=1 (соблюдено)

Серверы: montana-moscow / montana-frankfurt доступны но не использованы — incremental audit surface локально достаточен.


12. Cross-reference на предыдущие аудиты

Этот отчёт дополняет, не заменяет первые два:

Cumulative findings: 44

  • Closed конструкцией: ~16 (verified независимо)
  • Re-classified как known limitation: 1 (M2-1 Genesis bootstrap)
  • Open: ~27 (большинство LOW/INFO documentation drift)

Аудитор: Claude Opus 4.7 (1M context) Подпись модели: claude-opus-4-7[1m] Дата создания отчёта: 2026-04-27 Идентификатор аудита: claude-opus-4-7_2026-04-27_T121239 Тип: Incremental external audit (M3 scope + verification закрытий M1/M2)