**Не доверяю** ни одному `.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-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-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.
Боковой эффект: 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 правильный
```rust
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.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:
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:
Если `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:
// но 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
// Без контекста окна/τ₂ — 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
Если 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
**Описание.** `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 |
| 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)
3.**M3-A-1** — `apply_chain_length_increment` использовать `checked_add` (consistency)
4.**M3-A-2** — `apply_checkpoint_rotation` использовать `checked_sub` (defense-in-depth)
5.**M3-A-3** — Resolve spec ambiguity в genesis_candidate_root (spec patch либо code change)
### LOW
6.**F-5** — runtime telemetry warning при `mlock` failure (не только doc-level closure)
7.**M3-A-5** — документировать operator_account_id existing invariant в module comment
8.**M3-A-7** — typestate pattern для settle_window vs apply_proposal ordering
9.**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 (детерминизм)
**Главные слабости:** дисциплина 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