# Roadmap — Montana Reference Implementation **Spec target:** см. `VERSION.md` (single source of truth) **Implementation version:** 0.0.0 (pre-release, в разработке) --- ## Scope **В scope:** protocol core согласно спеке Montana (версия — `VERSION.md`) — криптография, TimeChain, NodeChain, AccountChain, Account Table, consensus (Proof of Time), P2P transport, Fast Sync, node binary. **Вне scope (отдельный workspace в будущем):** Application Layer по спеке `Montana App v2.4.2.md` — Juno agent, messaging, файловое хранилище, профили, клиентская LLM runtime. --- ## Принципы разработки 1. **Одна функция за раз.** Explain → Write + tests → Test → Commit. Никаких «напишу весь модуль сразу». 2. **Success criteria блок до кода.** Для каждой consensus-critical функции — spec quote + контракт + чек-лист критериев (см. `CLAUDE.md` → Verifiable success criteria). 3. **Автокоммит.** Любое изменение в `Протокол/Код/` автоматически завершается `git commit`. См. `CLAUDE.md` → Git discipline. 4. **Четыре обязательные команды зелёные перед каждым коммитом:** - `cargo fmt --all -- --check` - `cargo clippy --all-targets -- -D warnings` - `cargo test --all` - `cargo build --all --release` 5. **Byte-for-byte детерминизм.** Custom canonical encoding, explicit little-endian, BTreeMap вместо HashMap в consensus, no floats, no system clock. См. `CLAUDE.md` → Byte-for-byte determinism. 6. **Ссылки на спеку в коде.** Каждое consensus-critical решение помечается комментарием `// spec, раздел "<название>"` **без версии**. Источник истины версии — `VERSION.md`. При spec bump `VERSION.md` обновляется, spec-комментарии в коде не трогаются, если раздел не переименован. 7. **Demo откладываются до готовности протокола.** До M4 (consensus core) отдельные `examples/demo.rs` в пакетах не пишем — юнит-тестов достаточно для sanity check. После M4 создаём `examples/` в корне workspace с end-to-end сценариями (OpenAccount → Transfer → BundledConfirmation → apply_proposal), когда есть реальный flow между пакетами. --- ## Статусы crates - **TODO** — не начато, ожидает своей очереди - **In progress** — начата работа, не завершено - **Written** — код написан, тесты есть, ожидает review - **Committed** — зафиксировано в git, закрыто - **Blocked** — заблокирован gap в спеке, внешней зависимостью или другим crate --- ## 15 Crates — статус и зависимости | # | Crate | Milestone | Зависит от | Раздел спеки | Статус | |---|-------|-----------|------------|--------------|--------| | 1 | `mt-codec` | M1 | — | Consensus encoding layer | ✅ Committed (90464a8) | | 2 | `mt-crypto` | M1 | sha2, pqcrypto-falcon | Криптография + Primitive layer | ✅ Committed (df55372) | | 3 | `mt-merkle` | M1 | mt-codec, mt-crypto | Sparse Merkle Tree | ✅ Committed (f242956) | | 4 | `mt-genesis` | M1 | mt-codec, mt-crypto | Genesis Decree + protocol params | ✅ Committed (45c1e84) | | 5 | `mt-state` | M2 | mt-codec, mt-crypto, mt-merkle | Состояние сети (Account/Node/Candidate Tables + roots) | ✅ Committed (ab99e23) | | 6 | `mt-timechain` | M2 | mt-codec, mt-crypto, mt-genesis, mt-state | TimeChain VDF + Adaptive D + cemented_bundle_aggregate | ✅ Committed (76ed8da) | | 7 | `mt-account` | M3 | mt-codec, mt-crypto, mt-state, mt-genesis, mt-timechain | AccountChain operations + apply_proposal | ✅ Committed (Phase A..F closed, 102 tests) | | 8 | `mt-lottery` | M4 | mt-crypto, mt-state | Node/Account lottery + BundledConfirmation | ✅ Closed (6 phases, 96 тестов) | | 9 | `mt-consensus` | M4 | mt-state, mt-lottery, mt-timechain | Proposal + Lookback Leadership + fallback | ✅ Closed (5 phases, 60 тестов) | | 10 | `mt-entry` | M4 | mt-state, mt-timechain | NodeRegistration + selection event + adaptive VDF | ✅ Closed (5 phases, 39 тестов) | | 11 | `mt-store` | M5 | mt-state, mt-consensus | Filesystem persistence | ✅ Closed (5 phases, 24 теста) | | 12 | `mt-net` + `mt-net-transport` | M6 | mt-consensus, libp2p | libp2p + IBT + Wire format + Dandelion++ + Mesh + S&F | ✅ Closed (Phase A-G + C.0-C.4, 124 tests, in-process e2e; cross-machine pairing defer M8) | | 13 | `mt-sync` | M7 | mt-state, mt-net | Fast Sync | ⏳ TODO | | 14 | `montana-node` | M8 | все | Бинарь + CLI | ✅ Closed (byte-exact rewrite через canonical apply_proposal, commit fb204ef; DEV-001..DEV-009 closed; DEV-010 acknowledged genesis bootstrap) | | 15 | `mt-conformance` | M9 | все | Test vectors + conformance suite | ✅ READY initial (envelope A1-A3 + IBT B1 + PoW F1/F2); expansion в работе (12 TBD-A markers) | --- ## Milestones ### M0 — Workspace skeleton ✅ - [x] Cargo workspace, toolchain pin, rustfmt/clippy configs - [x] VERSION.md, README.md, ROADMAP.md - [x] Git repository в `Протокол/Код/`, parent gitignore настроен - [x] 4 обязательные команды зелёные - [i] mt-version stub crate (удалён 2026-04-17 как нарушение SSOT [C-1]) **Результат:** база для всей реализации. Commit `e2457ad`. ### M1 — Foundational layer ✅ ЗАКРЫТ Четыре параллельных crate: fundamentals криптографии и сериализации. - [x] `mt-codec` — canonical encoding (u8/u16/u32/u64/u128 LE, fixed bytes, 30 domain separators). 26 тестов. Commit `90464a8`. - [x] `mt-crypto` — SHA-256 + FN-DSA-512 обёртки (Hash32, PublicKey 897B, SecretKey 1281B, Signature 666B, SuiteId). 18 тестов. Commit `df55372`. - [x] `mt-merkle` — sparse Merkle tree глубины 256, InclusionProof + verify_proof, empty_internal через OnceLock. 25 тестов. Commit `f242956`. - [x] `mt-genesis` — ProtocolParams (25 полей, 2017 байт canonical), Genesis State Hash formula, OnceLock singleton. 19 тестов + 1 ignored (bootstrap keypairs TBD). Commit `45c1e84`. **Итог M1:** 4 пакета (mt-codec, mt-crypto, mt-merkle, mt-genesis), 88 тестов зелёных, ~1680 строк кода. Foundational layer закрыт. Найдена ambiguity в спеке: Genesis State Hash формула без domain separator — единственный такой случай, flagged для spec author review. ### M2 — State & time ✅ ЗАКРЫТ - [x] `mt-state` — Account Table, Node Table, Candidate Pool записи (1000/1043/1027 B) + три sparse Merkle tree + state_root композиция + derive_account_id/derive_node_id + is_active predicate. 32 теста. Commit `ab99e23`. - [x] `mt-timechain` — vdf_step/verify (T_r = SHA-256^D), next_d (Adaptive D ±3%), cemented_bundle_aggregate (unpredictable-offline binding, 3 ветви). 23 теста. Commit `76ed8da`. **Итог M2:** 2 пакета, 55 тестов зелёных, ~1000 строк кода. State types + canonical time + anti-grinding binding готовы. Материализация Genesis state (создание bootstrap AccountTable/NodeTable записей) — отложена в M3, зависит от apply_proposal для консистентности. ### M3 — AccountChain ✅ ЗАКРЫТ Один пакет `mt-account`, 6 phases закрыты. 102 теста, ~1850 строк кода. | Phase | Scope | Commit | |-------|-------|--------| | A | operation types + canonical encoding + op_hash (identifier via signed_scope после v29.7.0) | `0efd92d` → refactor `1af1fff` | | B | validation (OpError + 5 функций) | `cf95eaf` | | C | apply individual operations | `8fcca20` | | D | emission (reward/bonus/bootstrap_cumulative/supply) | `36503dc` | | E | apply_proposal partial (steps 2/3.5/3.6/4, stubs 1/3a/3b) | `5ab2a45` | | F | Genesis state materialization | `30400eb` | Критерий закрытия выполнен: два аккаунта могут обменяться Transfer, balance updated, state_root детерминирован (проверено тестами `apply_transfer_sum_delta_balance_is_zero`, `apply_proposal_state_root_deterministic`). ### M4 — Consensus core 🔄 Next Три пакета параллельно — сердце консенсуса. Зависимости между ними: ``` mt-lottery (идентификация winner) ──┐ ├─→ mt-consensus (proposal сборка) mt-entry (node admission) ─────────┘ ``` #### `mt-lottery` ⏳ TODO Раздел спеки: «VDF Reveal и лотерея» (~строка 754+), «Confirmer threshold», «cemented_bundle_aggregate» (уже в mt-timechain). - **Phase A. BundledConfirmation тип + layout** ✅ (24 теста) - `struct BundledConfirmation { node_id, endpoint, window_index, op_hashes[], reveal_hashes[], signature }` — counts встроены через `Vec::len()`, encoded как u16 LE префиксы - CanonicalEncode + encode_signed_scope (SSI R1), bundle_hash = identifier (SSI R2, class "mt-bundle") - `BundleError` (6 вариантов): UnknownNode, UnsupportedSuite, OpsOutOfOrder, RevealsOutOfOrder, WrongEndpoint, InvalidSignature - validate: strict ascending op_hashes/reveal_hashes, endpoint == expected_T_r (caller), signature verify by NodeTable[node_id].node_pubkey - **Phase B. VDF_Reveal тип + layout** ✅ (15 тестов) - `struct VdfReveal { node_id, window_index, endpoint, signature }` — REVEAL_SIZE = 734 B - CanonicalEncode + encode_signed_scope (SSI R1), reveal_hash = identifier (SSI R2, class "mt-vdf-reveal") - `compute_endpoint(t_r, cba_w_minus_2, node_id, window_index)` = SHA-256("mt-lottery" || T_r(W) || cba(W-2) || node_id || window_index LE) - `RevealError` (5 вариантов): UnknownNode, UnsupportedSuite, WrongWindow, WrongEndpoint, InvalidSignature - validate_reveal: спек-правила 1, 2, 3, 5 («Валидация VDF_Reveal», строки 1020-1026). Правило 4 (weighted_ticket < target) — в Phase C - **Phase C. Node lottery: weighted_ticket_node** ✅ (24 теста) - `seniority_bonus(chain_length, snapshot) = min(chain_length/69, snapshot)` — u64 unsigned - `lottery_weight = snapshot + seniority_bonus` — u64 unsigned, DS-2 floor ≥ 1 - `log2_q64(endpoint)` — bit-scan leading_zeros + нормализация мантиссы в [2^127, 2^128) + **degree-3 Remez minimax polynomial** `log2(1+y) ≈ B0 + y·(B1 - y·(B2_abs - y·B3))` (halved form, unsigned u64 coefficients; max error 2^-10.62) - `ln_q64(endpoint) = log2_q64 × LN2_Q64 >> 64`, `LN2_Q64 = 0xB17217F7D1CF79AB` - `weighted_ticket_node = ln_q64(endpoint) / (lottery_weight as u128)` — u128 integer div toward zero - Monotonicity + determinism + boundary tests + 5 binding test vectors TV1-TV5 from spec - [I-9] compliance **closed** (binding coefficients B0..B3 + 14 binding test vectors total: 5 ln_q64 + 5 weighted_ticket_node + 4 weighted_ticket_account) - **Phase D. Account lottery: weighted_ticket_account** ✅ (13 тестов) - `compute_account_endpoint(account_id, op_hash, t_r, cba_w_minus_2)` = SHA-256("mt-account-lottery" || ...) - `weighted_ticket_account = ln_q64(endpoint) / (account_chain_length_snapshot as u128)` — u128 совместим с node ticket для argmin - `AccountLotteryError` (2 варианта): OperatorExcluded, ZeroSnapshot - `validate_account_participation(is_node_operator, snapshot)` — spec правила 2, 3 из «Валидация участия аккаунта» - **Phase E. Winner determination** ✅ (12 тестов) - `Candidate { ticket: u128, class: u8, id: [u8;32] }` + `Winner` - `determine_winner(candidates)` — argmin by (ticket asc, class asc, id lex asc) canonical rule - `sorted_candidates_for_fallback` — для fallback cascade - Tie-breaking ambiguity в спеке (probability ~ 2^-128) закрыт canonical rule в коде - **Phase F. Quorum calculation** ✅ (8 тестов) - `quorum(active_chain_length) = (67 × X + 99) / 100` — u64 unsigned, [I-9] compliant (spec vectors passed) - `is_cemented(cemented_sum, active)` = `cemented_sum ≥ quorum` #### `mt-consensus` ⏳ TODO Раздел спеки: «Закрытие окна (Lookback Leadership Finalization)», «Proposal header», «Canonical acceptance», «fallback cascade». - **Phase A. Proposal header тип + layout** ✅ (22 теста) - `ProposalHeader` struct, 18 полей, PROPOSAL_HEADER_SIZE = 1080 B (target 16B u128, winner_class ∈ {1,2} validated) - encode_signed_scope (R1) + proposal_hash (R2, class "mt-proposal") - `HeaderError`: UnknownProposer, UnsupportedSuite, InvalidSignature, WindowNotMonotone, ProtocolVersionDecreased, ProtocolVersionUnsupported, FallbackDepthZero - validate_header: invariants window monotone, protocol_version monotone + ≤ local_max, fallback_depth ≥ 1, signature R1 - **Phase B. Lookback Leadership — proposer_W = winner_{W-2}** ✅ (11 тестов) - `canonical_proposer(current_window, bootstrap, sorted_candidates_W-2)` — первый node в sorted list - `fallback_proposer(...)` — Nth node для fallback cascade - Genesis bootstrap (W<2) + extended bootstrap (нет nodes в candidates) - Account winner case — proposer = ближайший node (spec строка 1315) - **Phase C. control_set формула** ✅ (10 тестов) - `ControlObjectRef { op_hash, cemented_window }` - `compute_control_set(all_cemented, prev_window, W)` — filter + sort (window asc, op_hash lex asc) - `validate_control_set` — равенство проверяется byte-exact - **Phase D. Canonical acceptance validation** ✅ (13 тестов) - `AcceptanceError`: ProposerNotCanonical, InsufficientBundles, IncludedRevealsMismatch, WrongWinner - `validate_proposer_is_canonical(header, bootstrap, sorted_W-2)` — сверка с fallback_proposer(depth) - `validate_bundles_threshold` — делегирует mt_lottery::is_cemented - `validate_included_reveals` — byte-exact equality reveal_hashes - `validate_winner` — argmin через mt_lottery::determine_winner - state_root verification — делегирован в mt-account::apply_proposal - **Phase E. Finalization flow** ✅ (4 теста) - `FinalizationStatus { Cemented, Rejected }` + `finalization_status(sigs_sum, active)` - `leader_penalty_excluded_node(header)` → NodeId для exclusion из lottery текущего окна #### `mt-entry` ✅ CLOSED (5 phases, 39 тестов) Раздел спеки: «Вход и регистрация», «apply_proposal Шаг 1/3a/3b», «Adaptive VDF». - **Phase A. NodeRegistration** ✅ (11 тестов) - `struct NodeRegistration`, NODE_REGISTRATION_SIZE = 1646 B - encode_signed_scope + nodereg_hash (R2 "mt-nodereg") - NodeRegError: UnsupportedSuite, InvalidSignature, NodeIdAlreadyIn{NodeTable,CandidatePool}, OperatorAccount{NotFound,AlreadyNode}, WStartOutOfRange, VdfChainTooShort - validate_noderegistration (structural checks 1-3) - **Phase B. candidate_vdf_init + Candidate Pool** ✅ (4 теста) - `candidate_vdf_init(t_r, cba, node_id)` = SHA-256("mt-candidate-vdf-init" || ...) - `compute_expiry_window` = registration + 3τ₂ - `apply_candidate_expiry(pool, window)` — шаг 3a - **Phase C. Selection event** ✅ (9 тестов) - `selection_slots(active_nodes) = max(1, active/130)` — 1% cap - `selection_sort_key(t_r, cba, node_id)` = SHA-256("mt-selection" || ...) - `is_selection_window(W)` — каждые 336 окон - `rank_candidates_for_selection` + `apply_selection_event` — шаг 3b (chain_length=1 активация) - **Phase D. Adaptive VDF** ✅ (6 тестов) - `required_vdf_length(pending, active, τ₂)` — integer form через permille (per [I-9]) - pressure_permille > 10 (1%) → τ₂ × pressure_permille / 10; иначе τ₂ - **Phase E. apply_proposal orchestration** ✅ (5 тестов) - `nr_sort_key(t_r, cba, node_pubkey)` per spec строки 1838-1843 - `apply_noderegistrations_batch` — incremental apply с pending growth - Caller (mt-node / integration test) orchestrates: shag 1 = batch, 3a = expiry, 3b = selection, затем mt-account::apply_proposal для steps 2/3.5/3.6/4 **Критерий закрытия M4:** 2 узла могут запустить локальную сеть in-memory, проходить окна, рассчитывать winner, cementing proposals. Полный state transition apply_proposal замкнут. ### M5 — Persistence ⏳ Next after M4 Раздел спеки: «Fast Sync», «Хранение». Цель: локальный узел перезапускается с восстановлением state с диска; crash-consistent между apply_proposal commit-ами. #### `mt-store` ✅ CLOSED (5 phases, 24 теста) **Реализация:** filesystem + fixed-size records (не RocksDB/sled — pure std::fs, минимум deps, максимум простоты для Manual Validation Gate). - **Phase A. FsStore** ✅ - `FsStore::open(path)` — создаёт root + proposals/ subdirectory - `StoreError`: Io, CorruptedLength, ParseFailed, NotFound - **Phase B. Table persistence** ✅ - `save/load_account_table` — canonical_encode concat → accounts.bin, fixed-size parse - `save/load_node_table` — nodes.bin - `save/load_candidate_pool` — candidates.bin - Decode функции — inverse CanonicalEncode, byte-exact (root byte-equal round-trip verified) - **Phase C. Proposal archive** ✅ - `archive_proposal(header)` → `proposals/{window:020}.bin` - `get_proposal_by_window(W)` → Option, byte-exact decode - Test 100 proposals random access passes - **Phase D. Crash recovery** ✅ - `meta.last_cemented.bin` — u64 LE, last committed window - `verify_consistency()` — meta указывает на archived proposal? - Test: meta=100 без archive → NotFound finding - **Phase E. Pruning** ✅ - `prune_proposals_before(threshold)` — delete files window < threshold - AccountTable/NodeTable/CandidatePool untouched - Test prune 9/20 proposals, current state preserved **Критерий закрытия M5 passes:** 1. `cargo test -p mt-store` = 24/24 ✓ 2. `full_restart_cycle_state_preserved` integration test — open → populate → save → close → reopen → load → roots byte-equal ✓ 3. `verify_consistency_detects_missing_proposal` fault flagging ✓ Hands-on example `examples/m5_persist.rs` — будет delivered в Validation Gate. --- ## Локальный shakedown — Manual Validation Gate (между M5 и M6) ⏳ **Смысл:** до написания сетевого слоя (M6 libp2p, Dandelion++, IBT) автор вручную на своей машине прогоняет **каждую шестерёнку протокола** с hands-on example binaries. Архитектор (я) объясняет output, критик (я в другой роли) задаёт adversarial вопросы на каждом шаге. Цель — убедиться что протокол работает вживую, не только в unit tests. ### Режим прохождения — **incremental, scenario-by-scenario** **Зафиксировано автором 2026-04-20.** Gate проходится пошагово, по одному сценарию за сессию: 1. Architect пишет `examples/mN_name.rs` binary **только для текущего сценария** 2. Автор запускает команды на своей машине, копирует output 3. Architect разбирает output построчно, сверяет с expected 4. Критик задаёт adversarial checklist 5. Автор либо подтверждает, либо флагует finding 6. Всё OK → статус сценария `✅ passed {дата}` в tracker-е ниже 7. Finding → fix → повторный прогон → retry до passed **Ценность incremental над batch:** ошибки ловятся в момент написания соответствующего binary; не накапливаются до середины Gate. Каждая шестерёнка exercised отдельно. **Scenario crate:** все binaries живут в `crates/mt-examples/` — отдельный crate с dependencies на все необходимые modules, каждый scenario — отдельный `examples/mN_*.rs` (cargo example). Создаётся в первой Gate сессии. ### Audit findings (M1-F audit closure — все 7 closed) **M1-F audit критика реализации (2026-04-26).** 7 findings surface-нуты, **все 7 закрыты конструкцией**. Status: READY FOR EXTERNAL AUDIT (M1 foundational layer scope). Audit package: [AUDIT.md](AUDIT.md) + [docs/audit-checklist.md](docs/audit-checklist.md). | Finding | Закрытие | Commit | |---------|----------|--------| | F-1: нет `Drop+zeroize` для `SecretKey`/`MlkemSecretKey` | ✅ workspace dep `zeroize=1.8.1` + Drop impls | `3333738` | | F-2: panic в lib коде на FFI ошибку | ✅ `sign`/`keypair_from_seed` → `Result<_, CryptoError>` | `e1164ad` | | F-3: KAT-baselines self-derived без NIST FIPS oracle | ✅ NIST ACVP differential testing — 51/51 byte-exact | `6b7ff30` | | F-4: `cfg!(target_os)` неверно для cross-compile | ✅ `CARGO_CFG_TARGET_OS` env var | `9f2ba93` | | F-5: `keypair()` слабая энтропия в публичном API | ✅ `#[cfg(any(test, feature = "testing"))]` гейт | `e1164ad` | | F-6: один error code маскирует разные fail paths | ✅ 6 новых раздельных error codes (7-12) | `71896f6` | | F-7: `SecretKey::from_array` без validation | ✅ implicit closure через F-2 (invalid SK → Err при первом sign) | `e1164ad` | **F-3 closure detail (NIST ACVP differential testing):** Sparse clone https://github.com/usnistgov/ACVP-Server (Apache-2.0, public domain test vectors из NIST CAVP). Extracted ML-DSA-65 + ML-KEM-768 KeyGen + SigGen deterministic test cases в `crates/mt-crypto-native/tests/fixtures/nist_acvp/` (~500 KB JSON, 3 files). Integration test `crates/mt-crypto-native/tests/nist_acvp_kat.rs` runs differential testing. **51/51 NIST KAT byte-exact PASS:** - ML-DSA-65 KeyGen 25/25 (FIPS 204 Algorithm 1 deterministic seed → pubkey/secretkey) - ML-KEM-768 KeyGen 25/25 (FIPS 203 Algorithm 16 (d, z) → ek/dk) - ML-DSA-65 SigGen deterministic external pure empty-context 1/1 (FIPS 204 Algorithm 2 — Montana usage pattern) **Conformance proof:** OpenSSL 3.5.5 LTS backend через mt-crypto-native FFI производит выходы байт-в-байт идентичные NIST FIPS 204/203 reference. Cross-implementation conformance доказана на public NIST oracle. **Reproduction:** ``` cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo test -p mt-crypto-native --test nist_acvp_kat -- --nocapture ``` **Known deferred (lesser scope, явно документированы в [AUDIT.md](AUDIT.md) §3):** - ML-DSA-65 SigGen с non-empty context — current API не принимает context parameter (Montana usage pattern не использует FIPS context); расширение FFI signature когда понадобится - ML-DSA-65 SigVer NIST KAT direct — косвенно подтверждено через round-trip + NIST sign byte-exact - ML-KEM-768 Encapsulate/Decapsulate NIST KAT — M1-F scope только KeyGen; encapDecap нужен в M6+ application layer Эти limitations задокументированы с closure path и не препятствуют external audit M1 foundational layer. ### Status tracker | Scenario | Binary | Status | Дата | |----------|--------|--------|------| | 0. User onboarding (24-word mnemonic → identity) | `mt-examples/examples/m1_mnemonic.rs` | ✅ passed (positive end-to-end: 6/6 PASS — seeds, keypair (terminal), recovery-fingerprint, mnemonic, vectors (6 binding), roundtrip; adversarial negative-cases — unit tests `mt-mnemonic` отдельно, не покрыто example subcommands) | 2026-04-30 | | 1. M1 Crypto shakedown | `mt-examples/examples/m1_crypto.rs` | ✅ passed (5/5 PASS — keypair-deterministic, keypair-random, sign, hash, merkle-empty; finding в Subsection 4 cmd_sign: ожидание `sig1 != sig2` противоречило spec строке 5286 «deterministic ML-DSA-65, RND = 0x00 × 32» — assertion инвертирована, title переписан на «Determinism…») | 2026-04-30 | | 2. M2 TimeChain + State | `mt-examples/examples/m2_timechain_state.rs` | ✅ passed (5/5 PASS — vdf-forward 1000, next-d-boundaries, cba-branches, state-root-compose, merkle-inclusion; binary написан 2026-04-30 коммитом `7e333e6`, прогнан автором на iMac) | 2026-04-30 | | 3. M3 Account operations | `mt-examples/examples/m3_account.rs` | ⏳ TODO | — | | 4. M4 Full consensus cycle | `mt-examples/examples/m4_local_net.rs` | ⏳ TODO | — | | 5. M5 Persistence + restart | `mt-examples/examples/m5_persist.rs` | ⏳ TODO | — | **M6 unblock criterion:** все 6 строк status = `✅ passed`. ### Starter instructions — начало следующей сессии Стандартная последовательность для новой сессии после этой точки: 1. **Архитектор реализации погружается в роль.** Прочитать `Код/CLAUDE.md` (v1.4.0+) построчно. Показать критерии работы. Проверить `VERSION.md` — актуальный `Spec target` (на 2026-04-21 = Montana v29.13.0). 2. **Sanity check build state:** ``` cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo fmt --all -- --check && cargo clippy --all-targets -- -D warnings && cargo test --all 2>&1 | grep -E "test result.*passed|FAILED" | tail -5 ``` Ожидание: fmt/clippy clean, 550+ tests passed, 0 failed. 3. **Выбрать следующий сценарий Validation Gate** (incremental, один за сессию). Первый pending — сценарий 0 «User onboarding». Последовательность: 0 → 1 → 2 → 3 → 4 → 5. 4. **Прогон сценария:** - Автор copy-paste команды из соответствующего блока «Сценарий N» ниже. - Автор запускает на своей машине, копирует output в чат. - Архитектор разбирает построчно, сверяет с acceptance criteria. - Критик (роль `Код/CRITIC.md` v1.3.0+) применяет adversarial checklist + min 2 perspectives с отдельными выводами per perspective. - Всё OK → сценарий `✅ passed {дата}` в status tracker. - Finding → architect предлагает fix → автор apply `делай` → retry. 5. **M6 разблокируется** когда все 6 сценариев passed. До этого — manual validation приоритет выше любых новых features. ### Policy - Binary пишет в stdout структурированные events (hashes, state roots, winners, errors). Автор читает output глазами. - Формат сценария ниже: binary → команды → expected output → architect gate → critic checklist. - M6 не начинается пока ВСЕ 5 сценариев не passed с подписью архитектора и критика. ### Формат каждого сценария ``` Сценарий N: {название} Фаза покрытия: {M1/M2/...} Prerequisite: {предыдущие сценарии} Binary: examples/mN_name.rs Команды (one-line shell, абсолютные пути): cd "..." && cargo run --release --example mN_name -- {args} Expected output pattern: {literal prefix / regex} Architect gate: {acceptance criterion} Critic checklist: {5-10 adversarial вопросов} ``` ### Сценарий 0 — User onboarding (24-word mnemonic → derived identity) Binary: `examples/m1_mnemonic.rs` Демонстрирует **production user flow**: как реальный пользователь создаёт identity в Montana — от 256-битной entropy через 24-словную мнемонику к master_seed и per-role keypair seeds для трёх ролей (account, node, app-encryption). Что проверяем: - **Canonical wordlist** — 2048 слов, binding SHA-256 fingerprint `2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda` матчит - **M-1 Algorithm** (mnemonic_to_master_seed) — PBKDF2-HMAC-SHA-256, salt=`"mt-seed"`, iter=2²⁰ → 64-байтовый master_seed - **24-слов mnemonic encoding** — entropy (256 bit) + 8-bit checksum = 264 bit → 24 × 11 bit = 24 слова (последнее включает checksum) - **Per-role HKDF-Expand** — master_seed + info (`"mt-account-key"` / `"mt-node-key"` / `"mt-app-encryption-key"`) → 48B/48B/64B seeds - **Byte-exact binding vectors** — 6 штук в спеке (3 entropy → master_seed + 3 per-role derivation) Команды (actual subcommands): ```bash cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_mnemonic -- vectors cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_mnemonic -- mnemonic "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art" cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_mnemonic -- keypair 0000000000000000000000000000000000000000000000000000000000000000 cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_mnemonic -- roundtrip 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_mnemonic -- all ``` Expected output markers: - `vectors`: «6 BINDING TEST VECTORS» секция, все с `byte-exact match true`; wordlist fingerprint verified - `mnemonic "<24 words>"`: displayed 24 слова из canonical wordlist, master_seed 64 bytes displayed, 3 derived seeds (48B/48B/64B) displayed - `keypair `: entropy hex → mnemonic 24 words → master_seed → 3 roles seeds - `roundtrip`: entropy → mnemonic → master_seed → mnemonic (roundtrip) → **same master_seed** idempotency proof - `all`: запускает все 4 subcommands подряд; finale `[result] ALL M1 MNEMONIC: PASS` Architect gate: - 6 binding test vectors byte-exact match true - 24 слова actual visible в output (не просто count) - Wordlist fingerprint matches binding value - Roundtrip idempotency verified - Три per-role seeds distinct для same master_seed (HKDF domain separation works) Critic adversarial checklist (применять с min 2 perspectives per CRITIC.md v1.3.0): **Perspective — Cryptographer:** - PBKDF2 iter = 2²⁰ actual (не меньший для performance)? - HKDF info domain separation между three roles gives different seeds? - Wordlist fingerprint tampering detected (mutate one word bit → verify fingerprint fails)? **Perspective — Pen-tester:** - Invalid mnemonic (word not in list, wrong checksum byte) → rejected с ясной ошибкой? - Partial mnemonic (23 or 25 words) → rejected? - mnemonic с valid words но invalid checksum (изменить последнее слово) → rejected? - Unicode / whitespace normalization — два visually-identical inputs producing same master_seed OR rejected? **Perspective — Production operator:** - Error messages actionable (user знает как исправить)? - Timing для 2²⁰ iterations acceptable (должно быть 0.5-2 секунды)? - SK bytes **не** утекают в stdout (default behavior — redacted, M1_DUMP_SK=1 для opt-in, см. m1_crypto P2 external closure)? ### Сценарий 1 — M1 Crypto shakedown Binary: `examples/m1_crypto.rs` (backfill — пишем первым в Validation Gate) Что проверяем: - SHA-256 FIPS 180-4 test vector: `hash("abc")` = `ba7816bf...` byte-exact - FN-DSA-512 keypair + sign message + verify → OK; mutate signature byte → verify fail - Merkle `empty_internal(256)` — 257 × 32B precomputed, derivation trace уровней 0-2 - Domain separation для всех R2 class domains (`mt-op`, `mt-nodereg`, `mt-proposal`, `mt-bundle`, ...) — разные hashes Команды (subcommands actual binary; подкоманда `all` прогоняет всё подряд): ```bash cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_crypto -- hash cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_crypto -- sign "test" cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_crypto -- keypair 3 cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_crypto -- merkle-empty cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_crypto -- all ``` Expected output markers (байт-exact проверяется mt_crypto::hash против FIPS 180-4): - `hash`: секция «1. FIPS 180-4 §B.1 vector» строка `byte-exact match true`; секция «2. Empty-parts collapse» `equal true`; секция «3. Part concatenation» `equal true`; секция «4. Domain separation» — различные 32-байтовые хэши для ≥ 10 classes - `sign`: секция «SIGN + VERIFY» — `signature 666 bytes`, `verify(mutated signature) false`, 4 adversarial теста все pass - `keypair N`: N × (pubkey 897B, secret 1281B), `uniqueness N distinct pubkeys` - `merkle-empty`: `TREE_DEPTH 256`, derivation trace уровни 0..2, finale `empty_internal(256) = ` Architect gate: `byte-exact match true` в FIPS vector секции; unique hashes для каждого R2 domain; empty_internal(256) deterministic; sign verify mutated → false. Critic checklist: - Запустить на Intel Mac + ARM Mac + Linux x86 — все hashes совпадают? (должны) - Re-run 2 раза `sign "test"` — σ отличаются между запусками? (должно — FN-DSA-512 randomized sampler non-deterministic) - Оба σ верифицируются? (должны — stability через signed_scope, Правило R2) - `hash(b"mt-op", &[b""])` vs `hash(b"mt-op", &[])` — одинаковы? (должны — empty-parts collapse proof в output) - `hash(b"mt-op", &[b"aa", b"bb"])` vs `hash(b"mt-op", &[b"aabb"])` — одинаковы? (должны — concat proof в output) - Unique domain separation — все R2 classes производят разные hashes для того же input? (должны — см. секция 4) ### Сценарий 2 — M2 TimeChain + State shakedown Binary: `examples/m2_timechain_state.rs` Что проверяем: - VDF forward: 1000 steps от genesis T_r_0, сверить T_r_1000 ручным пересчётом `SHA-256^1000` - `next_d` boundary cases: median_permille 850, 851, 949, 950, 1000 — верно увеличивает/оставляет/уменьшает - `cemented_bundle_aggregate`: window < 2 → zeros, empty set → `SHA-256("mt-bc-aggregate-empty" || W)`, non-empty → sorted node_ids aggregate - State root composition: build AccountTable с 3 accounts + NodeTable 2 nodes + CandidatePool 1 → recompute state_root через `SHA-256("mt-state-root" || ...)` independently - Merkle inclusion proof: один account → extract proof → verify against root Команды: ```bash cd "..." && cargo run --release --example m2_timechain_state -- vdf-forward 1000 cd "..." && cargo run --release --example m2_timechain_state -- next-d-boundaries cd "..." && cargo run --release --example m2_timechain_state -- cba-branches cd "..." && cargo run --release --example m2_timechain_state -- state-root-compose cd "..." && cargo run --release --example m2_timechain_state -- merkle-inclusion ``` Architect gate: все outputs byte-exact совпадают с hand-computed reference. Critic checklist: - VDF backward — impossible? (должен — preimage resistance SHA-256) - next_d at median=850 exact — decreases or unchanged? (per spec: ≤ low → decrease) - cemented_bundle_aggregate с одинаковыми node_ids, разным порядком — same output? (должен — sorted по node_id) - state_root с reordering accounts in BTreeMap — stable? (должен — BTreeMap sorted by key) - Merkle proof на несуществующий key — absence proof returns leaf_value=empty, verify_proof returns true? (должен) ### Сценарий 3 — M3 Account operations Binary: `examples/m3_account.rs` Что проверяем: - Scenario: создать bootstrap → accounts A, B (через TransferActivation от sponsor) → Transfer A→B → apply_proposal → state - Observable: balance A decreased, B increased; Σ delta_balance = 0; frontier_hash updated; account_chain_length inc - Emission: manually apply `reward_moneta(params) = EMISSION_moneta` (const 13 Ɉ) для нескольких окон; supply растёт линейно - Genesis state materialization: build_genesis_state → check bootstrap account + node with chain_length=1 - DS-2 edge: attempt apply_proposal с lottery_weight = 0 (chain_length_snapshot=0) → winner_node panics? returns err? Команды: ```bash cd "..." && cargo run --release --example m3_account -- transfer-scenario cd "..." && cargo run --release --example m3_account -- emission-schedule 0 10000 20000 60000 cd "..." && cargo run --release --example m3_account -- genesis-state cd "..." && cargo run --release --example m3_account -- ds2-edge ``` Architect gate: Σ balance invariant после transfer; emission matches closed-form; genesis_state_root deterministic. Critic checklist: - Transfer A→B с amount > balance A — отклоняется? (InsufficientBalance) - Transfer A→A — отклоняется? (SelfTransfer) - Двойной OpenAccount с тем же pubkey → DuplicateAccount? - Invalid prev_hash (не совпадает с frontier) → InvalidPrevHash? - Signature от другого keypair → InvalidSignature? - apply_proposal с одинаковым winner дважды (replay) → emission двойная? (нет — каждое W разный) ### Сценарий 4 — M4 Full consensus cycle (ключевой) Binary: `examples/m4_local_net.rs --nodes 2 --windows 10` Что проверяем: in-memory 2+ synthetic узла проходят 10 окон с полным protocol cycle: - Каждое окно: узлы синтетически подписывают BCs окна W-1 (из testdata) → proposer_{W} = winner_{W-2} → собирает included_bundles + included_reveals → argmin winner → apply_proposal → cementing via quorum - Printout per window: winner_id, winner_class (Node/Account), reward_moneta, state_root, active_chain_length, quorum - После 10 окон: supply ledger (Σ rewards) == EMISSION_moneta × 10 — consistency Команды: ```bash cd "..." && cargo run --release --example m4_local_net -- --nodes 2 --windows 10 cd "..." && cargo run --release --example m4_local_net -- --nodes 2 --windows 10 --seed 42 cd "..." && cargo run --release --example m4_local_net -- --nodes 5 --windows 100 --verbose ``` Architect gate: - Σ reward == EMISSION_moneta × N_windows (supply invariant) - Все apply_proposal succeeded (no protocol violations) - state_root по окнам — деterministic для given --seed - Два запуска с --seed 42 → byte-exact same trace Critic checklist: - Если один узел не подписывает BC → остаётся в active set? (да, пока < 2τ₂) - Если winner = offline узел → fallback на second_min? (proposer может timeout) - Fallback cascade: reject proposal → second_min → rejected → third_min? (работает до available candidates) - 67% quorum точно — что если ровно 67%? (`(67X+99)/100` дает ceiling — включительно проходит) - Dependency rule: две операции от одного sender в одном окне — вторая отклоняется? - argmin tie-breaking: два кандидата с одинаковым weighted_ticket — как определяется winner? (сравнение by node_id lex asc после ticket equality) ### Сценарий 5 — M5 Persistence + restart Binary: `examples/m5_persist.rs` Что проверяем: - Run M4 scenario → close → reopen → state_root byte-exact совпадает - Crash посреди apply_proposal (SIGKILL или std::process::exit) → reopen → state consistent (либо applied, либо not) - Prune 50 oldest proposals → current state untouched, recent proposals accessible - Concurrent read while writing (RocksDB snapshot): reader видит consistent view Команды: ```bash cd "..." && cargo run --release --example m5_persist -- restart-cycle 20 cd "..." && cargo run --release --example m5_persist -- crash-midcommit cd "..." && cargo run --release --example m5_persist -- prune 50 ``` Architect gate: state_root after restart == state_root before close; crash recovery replays partial commit cleanly; pruned proposals NotFound, current state intact. Critic checklist: - Corrupt RocksDB файл (e.g., truncate) → open returns error, не panic? - Два процесса одновременно open одну базу → второй fails (RocksDB lock)? - Disk full во время commit → WriteBatch atomic — либо all, либо nothing? - Restart через 1 год simulated time — replay всех 13 млн окон работает за разумное время? --- ### Проведение Validation Gate — incremental workflow На каждую сессию — **один сценарий**: 1. Автор открывает новую сессию репликой «начинаем Gate Scenario N» (или «пиши» если уже понятно). 2. Architect пишет `crates/mt-examples/examples/mN_name.rs` (новый binary для текущего сценария). 3. Architect даёт команды одной строкой + expected output pattern. 4. Автор запускает на своей машине, копирует output в чат. 5. Architect читает output построчно, сверяет с expected. 6. Критик задаёт adversarial checklist questions (5-10 вопросов из scenario-блока). 7. Автор: «всё работает как ожидал» либо флагует подозрительное. 8. Всё OK → architect обновляет status tracker выше: строка сценария → `✅ passed {дата}`, commit в ROADMAP. 9. Finding → fix (в binary или в core crate) → повторный прогон → retry. **Next session trigger:** автор открывает новую сессию после passed предыдущего сценария. **Критерий закрытия Validation Gate:** все 5 строк tracker-а = `✅ passed`. Только после этого — M6. ### Starter — с чего начать новую сессию Первая Gate сессия (Scenario 1): 1. Автор: «начинаем Scenario 1 M1 Crypto» (или просто «пиши» после тега ROADMAP контекста) 2. Architect: создаёт `crates/mt-examples/` crate (Cargo.toml + пустой `src/lib.rs` или dummy), добавляет workspace members entry 3. Architect: пишет `crates/mt-examples/examples/m1_crypto.rs` с 4 subcommands (fips-abc, falcon-roundtrip, merkle-empty, domain-separation) 4. Architect: даёт команды автору 5. Manual run + разбор + критик → passed → next session (Scenario 2) --- ## Manual Validation Matrix M1-M5 (production-grade per-element) Дополняет «Локальный shakedown» сценарии 0-5 (выше). Сценарии — happy-path scenario-by-scenario; матрица — **каждый винтик** на уровнях кода / протокола / пользователя / UX. Цель — production-grade проверка всех элементов перед external audit + перед запуском M6 сетевого слоя. **Формат каждого пункта:** ``` [ ] Имя | file:line | spec ref | reproduce | expected | pass criterion ``` **Принципы:** - M6+ network исключён (не реализован) - Сценарии 0-5 дают smoke-pass; матрица детализирует — каждое test vector / invariant / API surface - Pass criterion бинарный: byte-exact match / numerical equality / observable behavior — без «вроде работает» - Зависимости: пункт может зависеть от предыдущих (например проверка transfer требует init готовый) --- ### Уровень 1 — Code primitives (M1 Foundational) #### M1.1 mt-codec — canonical encoding + domain registry | # | Элемент | file:line | Reproduce | Expected | Pass | |---|---------|-----------|-----------|----------|------| | 1.1.1 | `write_u8/u16/u32/u64/u128` LE | crates/mt-codec/src/lib.rs | `cargo test -p mt-codec` | LE byte order, no platform dependence | byte-exact match unit-tests | | 1.1.2 | `write_bytes(buf, &[u8])` | mt-codec/src/lib.rs | round-trip property test | encode → decode = identity | property test passes ≥1000 cases | | 1.1.3 | `CanonicalEncode` trait impls для всех consensus types | mt-codec/src/lib.rs + per-crate impl | `grep "impl CanonicalEncode"` workspace-wide | каждый consensus type implements | NodeRegistration, ProposalHeader, BundledConfirmation, VdfReveal, AccountRecord, NodeRecord, CandidateRecord — все present | | 1.1.4 | Domain registry 32 константы prefix-free | mt-codec::domain | `grep "pub const " crates/mt-codec/src/lib.rs \| grep -i domain` | список 32 domains, ни один не префикс другого | prefix-free check: convention `mt-{component}[-{subcomponent}]` соблюдена для всех | | 1.1.5 | `hash(domain, parts)` self-delimiting | mt-crypto::hash | manual reproduce: `python3 -c "import hashlib; print(hashlib.sha256(b'mt-proposal' + bytes([0]) + payload).hexdigest())"` vs Rust output | byte-exact SHA-256 match | byte-exact match для test inputs | | 1.1.6 | `sha256_raw(bytes)` без domain | mt-crypto:sha256_raw | FIPS 180-4 §B.1 vector "abc" → ba7816bf… | byte-exact "abc" → standard digest | unit-test passes | #### M1.2 mt-crypto — PQ crypto + secret types | # | Элемент | file:line | Reproduce | Expected | Pass | |---|---------|-----------|-----------|----------|------| | 1.2.1 | `keypair_from_seed(seed)` ML-DSA-65 deterministic | mt-crypto/src/lib.rs | `cargo test -p mt-crypto-native --test nist_acvp_kat` | NIST FIPS 204 KAT byte-exact | 25/25 KeyGen vectors PASS | | 1.2.2 | `sign(sk, msg)` deterministic ML-DSA-65 (FIPS 204 Algorithm 2) | mt-crypto::sign | NIST ACVP SigGen 15 cases tgId=3 | byte-exact deterministic signature | 15/15 SigGen vectors PASS | | 1.2.3 | `verify(pk, msg, sig)` constant-time | mt-crypto::verify | sign+verify roundtrip + tamper test | accept good, reject mutated | unit-test PASS, no panic | | 1.2.4 | `keypair_from_seed_mlkem` ML-KEM-768 | mt-crypto-native NIST KAT | `cargo test -p mt-crypto-native --test nist_acvp_kat` | FIPS 203 KAT byte-exact | 25/25 KeyGen vectors PASS | | 1.2.5 | `SecretKey` heap+mlock | mt-crypto/src/lib.rs:130 | `cargo test -p mt-crypto --test security_invariants secret_key_is_heap_allocated` | size_of::() = ptr (8B), не 4032 | invariant test PASS | | 1.2.6 | `SecretKey: !Clone` | mt-crypto::SecretKey | trait check test | compile-error на try_clone() | compile-time enforce | | 1.2.7 | `SecretKey: !PartialEq` (timing-leak protection) | mt-crypto::SecretKey | security_invariants test | no `==` operator | compile-time check PASS | | 1.2.8 | `Drop+zeroize` для SecretKey/MlkemSecretKey | mt-crypto/src/lib.rs Drop impl | drop scope test | bytes zeroed после drop | unit-test verify post-drop memory | | 1.2.9 | `mlock`/`munlock` 7 unsafe blocks с SAFETY | mt-crypto/src/lib.rs:164,192,235,276,293,364,383 | grep `// SAFETY:` | каждый unsafe имеет comment | 7/7 SAFETY comments present | | 1.2.10 | No println on secret bytes | grep | `rg "println" crates/mt-crypto/src` | нет matches на SK fields | grep clean | #### M1.3 mt-crypto-native — FFI к OpenSSL EVP | # | Элемент | file:line | Reproduce | Expected | Pass | |---|---------|-----------|-----------|----------|------| | 1.3.1 | `mt_keypair_from_seed_mldsa` C wrapper | csrc/mt_crypto.c | NIST KAT differential | OpenSSL EVP_PKEY_KEYGEN с `OSSL_PKEY_PARAM_ML_DSA_SEED` | byte-exact NIST FIPS 204 | | 1.3.2 | `mt_sign_mldsa` deterministic | csrc/mt_crypto.c | ACVP SigGen с `OSSL_SIGNATURE_PARAM_DETERMINISTIC=1` | FIPS 204 Algorithm 2 deterministic | 15/15 PASS | | 1.3.3 | `mt_sign_mldsa_ctx` (FIPS context support) | csrc/mt_crypto.c | empty ctx == ctx-equivalence | один test case | 1/1 PASS | | 1.3.4 | OpenSSL 3.5.5 LTS pinned | Cargo.toml | `cargo tree \| grep openssl-src` | =300.5.5+3.5.5 exact pin | exact version match | | 1.3.5 | Reproducible builds через build.rs | crates/mt-crypto-native/build.rs | `docker build -f docker/release-build.dockerfile` two passes | byte-identical binaries | sha256 binary 1 == sha256 binary 2 | #### M1.4 mt-mnemonic — 24-word recovery flow | # | Элемент | file:line | Reproduce | Expected | Pass | |---|---------|-----------|-----------|----------|------| | 1.4.1 | `entropy_to_mnemonic([u8; 32])` | mt-mnemonic/src/mnemonic.rs | KAT vectors test | byte-exact 24 words from BIP-39 wordlist | 5/5 KAT PASS | | 1.4.2 | `mnemonic_to_master_seed("...")` PBKDF2 iter=2²⁰ | mt-mnemonic/src/pbkdf2.rs | RFC 7914 §11 / RFC 6070 vectors | byte-exact 64B master_seed | RFC vectors PASS | | 1.4.3 | `mldsa_seed_for_role(seed, domain)` HKDF | mt-mnemonic/src/hkdf.rs | RFC 5869 §A.1 | byte-exact 32B per-role seed | RFC vectors PASS | | 1.4.4 | `mlkem_seed_for_role(seed, domain)` 64B output | mt-mnemonic/src/hkdf.rs | KAT test | byte-exact 64B (d ‖ z FIPS 203 §6.1) | unit-test PASS | | 1.4.5 | HMAC-SHA-256 RFC 4231 | mt-mnemonic/src/hmac.rs | RFC 4231 cases 1-7 | byte-exact MAC | 7/7 PASS | | 1.4.6 | Wordlist binding SHA-256 | mt-mnemonic/src/wordlist.rs | hash binding test | sha256(wordlist file) match expected | hash match | | 1.4.7 | Whitespace-tolerant parsing (F-12 closure) | mt-mnemonic/src/mnemonic.rs:120 | `init --mnemonic " word1 word2 ..."` | accepts irregular whitespace | regression test PASS | | 1.4.8 | End-to-end recovery determinism | mt-mnemonic/tests/e2e_recovery.rs | `cargo test -p mt-mnemonic --test e2e_recovery` | mnemonic → identity → mnemonic = identity | 1/1 PASS | --- ### Уровень 2 — State foundation (M2) #### M2.1 mt-merkle — Sparse Merkle Tree depth 256 | # | Элемент | file:line | Reproduce | Expected | Pass | |---|---------|-----------|-----------|----------|------| | 2.1.1 | `leaf_hash(serialized)` domain-separated | mt-merkle/src/lib.rs:13 | `python3 -c "import hashlib; print(hashlib.sha256(b'mt-merkle-leaf' + bytes([0]) + bytes).hexdigest())"` | byte-exact SHA-256 | external oracle match | | 2.1.2 | `internal_hash(left, right)` order-sensitive | mt-merkle/src/lib.rs | swap left↔right test | hash(L,R) ≠ hash(R,L) | order-sensitivity PASS | | 2.1.3 | `empty_internal(k)` precomputed cache levels 0-256 | mt-merkle/src/lib.rs OnceLock | manual shasum levels 0,1,2,3 | recursive `H(empty_internal(k-1), empty_internal(k-1))` | 4/4 manual computations match | | 2.1.4 | `SparseMerkleTree::insert(key, value)` | mt-merkle/src/lib.rs | insertion-order independence test | вставка в разном порядке → одинаковый root | order-independence PASS | | 2.1.5 | `SparseMerkleTree::root()` BTreeMap canonical | mt-merkle::SparseMerkleTree | determinism_invariants tests | byte-exact root для разного insertion order | 10/10 invariants PASS | | 2.1.6 | `verify_proof(root, proof)` inclusion | mt-merkle::verify_proof | construct proof, verify | true для valid, false для tampered | 2/2 cases PASS | | 2.1.7 | `verify_proof` absence (empty leaf) | mt-merkle::verify_proof | proof for non-existent key | true (absence verified) | absence proof PASS | | 2.1.8 | TREE_DEPTH = 256 | mt-merkle::TREE_DEPTH | const check | == 256 | static assertion | | 2.1.9 | InclusionProof MAX_LEAF_VALUE_SIZE = 4096 (P10-1) | mt-merkle/src/lib.rs:285 | encode test с oversized leaf | debug_assert panic | bound enforced | | 2.1.10 | InclusionProof MAX_SIBLINGS = TREE_DEPTH | mt-merkle::MAX_SIBLINGS | const | == 256 | static | #### M2.2 mt-genesis — ProtocolParams SSOT | # | Элемент | file:line | Reproduce | Expected | Pass | |---|---------|-----------|-----------|----------|------| | 2.2.1 | `genesis_params()` singleton OnceLock | mt-genesis/src/lib.rs | повторные вызовы → same Arc ptr | стабильный pointer | unit-test PASS | | 2.2.2 | `ProtocolParams` encoded size 4094B | mt-genesis::PARAMS_ENCODED_SIZE | `assert_eq!(p.encode().len(), 4094)` | byte-exact 4094B | static | | 2.2.3 | `emission_moneta = 13 × 10⁹ nɈ` | mt-genesis::ProtocolParams | const check | == 13_000_000_000 | LE bytes [0x00, 0xc8, 0x4d, 0x21, 3, 0, 0, 0] | | 2.2.4 | `d0 = 325_000_000` | mt-genesis::ProtocolParams | runtime calibration on M-class Mac | окно ≈ 60s wall-clock | empirical 60-65s | | 2.2.5 | `tau2_windows = 20160` | mt-genesis::ProtocolParams | const | == 20160 (~14 days) | static | | 2.2.6 | `selection_interval` = 336 | mt-genesis::ProtocolParams | const | == 336 (1/60 of τ₂) | static | | 2.2.7 | `bootstrap_node_pubkey` placeholder | mt-genesis::ProtocolParams | byte check | == [0u8; 1952] (pre-ceremony) | static; post-ceremony updated | | 2.2.8 | `genesis_app_id()` SHA-256 domain-separated | mt-genesis/src/lib.rs | manual recompute | byte-exact hash | unit-test PASS | | 2.2.9 | `compute_genesis_state_hash()` determinism | mt-genesis | повторные вызовы | byte-exact same hash | determinism_invariants 7/7 | | 2.2.10 | `is_genesis_bootstrap_finalized(params)` | mt-genesis | predicate test | placeholder zeros → false | static check | #### M2.3 mt-state — AccountTable / NodeTable / CandidatePool | # | Элемент | file:line | Reproduce | Expected | Pass | |---|---------|-----------|-----------|----------|------| | 2.3.1 | `AccountRecord` 2059B byte-exact | mt-state/src/lib.rs:25 | `assert_eq!(record.encode().len(), ACCOUNT_RECORD_SIZE)` | == 2059 | static | | 2.3.2 | `NodeRecord` 2098B byte-exact | mt-state/src/lib.rs:59 | encode size | == 2098 | static | | 2.3.3 | `CandidateRecord` 2082B byte-exact | mt-state/src/lib.rs | encode size | == 2082 | static | | 2.3.4 | `derive_account_id(suite_id, pk)` SHA-256 domain | mt-state::derive_account_id | manual recompute via Python | byte-exact | external oracle PASS | | 2.3.5 | `derive_node_id(pk)` distinct from account_id | mt-state::derive_node_id | derive both for same pk | account_id ≠ node_id | cross-distinctness PASS | | 2.3.6 | `AccountTable::insert/get` BTreeMap | mt-state::AccountTable | order-independence test | root hash same regardless insertion order | invariant PASS | | 2.3.7 | `NodeTable::insert/contains` | mt-state::NodeTable | duplicate check | second insert returns false / errors | unit-test PASS | | 2.3.8 | `CandidatePool::insert/remove/iter` | mt-state::CandidatePool | TTL-based pruning | apply_candidate_expiry удаляет expired | scenario test | | 2.3.9 | `compute_state_root(node, candidate, account)` | mt-state::compute_state_root | manual SHA-256 composition | byte-exact tree-of-three | external oracle PASS | | 2.3.10 | `is_active(node, current_window)` predicate | mt-state::is_active | boundary at 2×τ₂ inclusive test | true within window, false outside | boundary tests PASS | #### M2.4 mt-timechain — VDF + adaptive D + cba | # | Элемент | file:line | Reproduce | Expected | Pass | |---|---------|-----------|-----------|----------|------| | 2.4.1 | `vdf_step(prev, d)` SHA-256^d associative | mt-timechain/src/lib.rs:13 | composition test: `vdf_step(vdf_step(x, a), b) == vdf_step(x, a+b)` | associativity holds | invariant PASS | | 2.4.2 | `vdf_step(prev, 0) == prev` identity | mt-timechain | zero-iteration check | output == input | unit-test PASS | | 2.4.3 | `vdf_verify(prev, expected, d)` | mt-timechain::vdf_verify | sign correct + tamper | accept correct, reject wrong claim | 2/2 cases PASS | | 2.4.4 | `next_d(current, median_permille, params)` adaptive | mt-timechain/src/lib.rs:40-89 | dead-zone (median == 1000): no change; high (>1030): +3%; low (<970): -3% | integer permille arithmetic | 3/3 boundary tests PASS | | 2.4.5 | `next_d` overflow protection | mt-timechain/src/lib.rs:51,65 | extreme D close to u64::MAX | panic с descriptive message | overflow caught (~25_000 epochs) | | 2.4.6 | `cemented_bundle_aggregate(W, &[])` empty marker | mt-timechain::cba | window=0,1 → genesis zeros | byte-exact `[0u8; 32]` | static | | 2.4.7 | `cemented_bundle_aggregate(W, [node_ids])` canonical sort | mt-timechain | input order independence test | byte-exact для разного порядка | [I-8] invariant PASS | | 2.4.8 | `cemented_bundle_aggregate` distinct empty vs non-empty | mt-timechain | empty `[]` vs `[node_id]` | разные hashes | distinctness PASS | | 2.4.9 | TimeChain VDF на genesis узле тикает per окно | montana-node start | `montana-node start --max-windows 2 && status` | T_r изменился, current_window инкрементировано на 2 | observable PASS | --- ### Уровень 3 — apply_proposal (M3 mt-account) | # | Элемент | file:line | Reproduce | Expected | Pass | |---|---------|-----------|-----------|----------|------| | 3.1 | Transfer 0x02 layout (TRANSFER_SIZE) | mt-account/src/lib.rs | encode size check | byte-exact spec | static const | | 3.2 | `validate_transfer` signature + balance + chain_length | mt-account::validate_transfer | tests/transfer_*.rs | reject bad sig / insufficient balance / non-monotone chain | reject all 3 cases | | 3.3 | `apply_transfer` atomic: balance debited, credited, frontier_hash bound | mt-account::apply_transfer | unit-test | sender.balance -= amount, receiver.balance += amount, frontier_hash = op_hash | invariant PASS | | 3.4 | ChangeKey 0x03 layout | mt-account::CHANGE_KEY_SIZE | encode size | byte-exact spec | static const | | 3.5 | `validate_change_key` signature под старым ключом | mt-account::validate_change_key | tamper test | new sig under old key required | unit-test PASS | | 3.6 | `apply_change_key` rotates current_pubkey | mt-account::apply_change_key | before/after | account.current_pubkey == new_pk, suite_id updated | unit-test PASS | | 3.7 | Anchor 0x04 layout | mt-account::ANCHOR_SIZE | encode size | byte-exact spec | static const | | 3.8 | `validate_anchor` app_id + data_hash binding | mt-account::validate_anchor | tests | data_hash 32B fixed, app_id present | unit-test PASS | | 3.9 | `apply_anchor` AccountChain entry, не меняет balance | mt-account::apply_anchor | balance check | balance unchanged, chain_length += 1 | unit-test PASS | | 3.10 | TransferActivation 0x0A sponsor pattern | mt-account::TRANSFER_ACTIVATION_SIZE | encode size | byte-exact spec | static const | | 3.11 | `apply_transfer_activation` создаёт новый AccountRecord | mt-account::apply_transfer_activation | before/after AccountTable | new account inserted, balance=amount, is_node_operator=false | unit-test PASS | | 3.12 | `op_hash(operation)` SHA-256("mt-op" \|\| signed_scope) | mt-account/src/lib.rs | manual recompute | byte-exact | external oracle PASS | | 3.13 | `settle_window(cemented_ops, W)` сортировка по op_hash | mt-account::settle_window | unsorted input | output sorted ascending op_hash | invariant PASS | | 3.14 | `apply_proposal` Step 2 (emission), 3.5 (chain_length++), 3.6 (checkpoint), 4 (state_root) | mt-account::apply_proposal | per-window in start.rs | balance += emission_moneta, node.chain_length += 1, state_root updated | observable balance growth | | 3.15 | `apply_emission(winner_account, params)` const 13 Ɉ | mt-account::apply_emission | один tick | account.balance += 13_000_000_000 nɈ | observable PASS | | 3.16 | `supply_moneta(W, params) = emission × (W+1)` closed-form | mt-account::supply_moneta | различные W | linear formula | unit-test PASS | | 3.17 | `build_genesis_state(params)` bootstrap | mt-account::build_genesis_state | call + encode | byte-exact deterministic state | invariant PASS | | 3.18 | `genesis_state_root(params)` byte-exact | mt-account::genesis_state_root | manual recompute | == compute_state_root результат | external oracle PASS | | 3.19 | Conservation: Σ delta_balance == 0 per Transfer/Activation | mt-account::apply_* | property test | sum unchanged | invariant PASS | | 3.20 | checked_sub on balance underflow → panic с protocol invariant breach | mt-account::apply_transfer | underflow case | panic с descriptive message | controlled halt | --- ### Уровень 4 — Consensus mechanics (M4) #### M4.1 mt-lottery — BundledConfirmation + VdfReveal + lottery | # | Элемент | file:line | Reproduce | Expected | Pass | |---|---------|-----------|-----------|----------|------| | 4.1.1 | `BundledConfirmation::encode_signed_scope` byte-exact | mt-lottery/src/lib.rs:34 | size check | fixed overhead 76B + var hashes | static | | 4.1.2 | `bundle_hash(bc)` SHA-256("mt-bundle" \|\| scope) | mt-lottery/src/lib.rs:69 | external oracle | byte-exact match | external PASS | | 4.1.3 | `validate_bundle` rules a-d | mt-lottery/src/lib.rs:102 | per-rule rejection tests | UnknownNode/UnsupportedSuite/WrongEndpoint/OpsOutOfOrder/RevealsOutOfOrder/InvalidSignature | 6/6 reject paths PASS | | 4.1.4 | `validate_bundle` u16 cap (M4-1 closure) | mt-lottery/src/lib.rs:120 | op_hashes.len() > u16::MAX → TooManyOps | reject before signature verify | functional test PASS | | 4.1.5 | op_hashes ascending strictly | mt-lottery::is_strictly_ascending | unsorted input → reject | OpsOutOfOrder error | unit-test PASS | | 4.1.6 | `VdfReveal` layout 32+8+32+SIG bytes | mt-lottery::REVEAL_SIZE | size check | == REVEAL_SIZE const | static | | 4.1.7 | `reveal_hash(reveal)` SHA-256("mt-vdf-reveal" \|\| scope) | mt-lottery::reveal_hash | external oracle | byte-exact | external PASS | | 4.1.8 | `validate_reveal` (5 rules) | mt-lottery::validate_reveal | per-rule reject | similar to bundle | unit-test PASS | | 4.1.9 | `compute_endpoint(T_r, cba(W-2), my_node, W)` lottery formula с [I-8] binding | mt-lottery::compute_endpoint | manual SHA-256 composition | byte-exact, [I-8] satisfied | external oracle PASS | | 4.1.10 | `log2_q64(x_q64)` Q64.64 polynomial | mt-lottery/src/lib.rs (B0..B3 coefficients) | reference Python implementation | max error 2^-10.62 | precision match | | 4.1.11 | `ln_q64(x_q64)` derived from log2_q64 | mt-lottery::ln_q64 | reference test | byte-exact match | unit-test PASS | | 4.1.12 | `weighted_ticket_node(endpoint, w, snapshot)` u128 integer division | mt-lottery::weighted_ticket_node | various w | deterministic ticket | determinism_invariants 34/34 | | 4.1.13 | `lottery_weight(chain_length, snapshot)` integer | mt-lottery::lottery_weight | boundary chain_length | monotone non-decreasing | unit-test PASS | | 4.1.14 | `seniority_bonus` integer formula | mt-lottery::seniority_bonus | various inputs | spec-exact | unit-test PASS | | 4.1.15 | `determine_winner` argmin (ticket asc, class asc, id lex asc) | mt-lottery::determine_winner | tie cases | canonical ordering rule | tie-breaker tests PASS | | 4.1.16 | `quorum(active_chain_length)` 67% ceiling `(67×X+99)/100` | mt-lottery::quorum | various X | integer ceiling formula | spec-binding PASS | | 4.1.17 | `is_cemented(c, active)` predicate | mt-lottery::is_cemented | boundary cases | true if c >= quorum(active) | boundary tests PASS | #### M4.2 mt-consensus — ProposalHeader R1/R2 | # | Элемент | file:line | Reproduce | Expected | Pass | |---|---------|-----------|-----------|----------|------| | 4.2.1 | `ProposalHeader` 3722B fixed (17 полей) | mt-consensus/src/lib.rs:33 | encode size | == PROPOSAL_HEADER_SIZE = 3722 | static | | 4.2.2 | `encode_signed_scope` 413B (без подписи) | mt-consensus/src/lib.rs | encode_signed_scope().len() | == 413 | static computation | | 4.2.3 | `proposal_hash(header)` SHA-256("mt-proposal" \|\| scope) | mt-consensus::proposal_hash | external oracle | byte-exact | external PASS | | 4.2.4 | `validate_header` rules a-f | mt-consensus::validate_header | per-rule tests | fallback_depth ≥ 1, window_index = prev+1, protocol_version monotone, proposer registered, suite Mldsa65, sig verify | 6/6 reject paths PASS | | 4.2.5 | `target` поле u128 LE 16B (P5 closure) | mt-consensus::ProposalHeader | encode field | u128 16 bytes LE | static | | 4.2.6 | `canonical_proposer(W)` Lookback Leadership | mt-consensus::canonical_proposer | proposer_W = winner_{W-2} | spec-exact | unit-test PASS | | 4.2.7 | `fallback_proposer(depth)` cascade | mt-consensus::fallback_proposer | depth 1, 2, 3 | byzantine-safe ordering | unit-test PASS | | 4.2.8 | `compute_control_set(headers)` filter + sort | mt-consensus::compute_control_set | unordered input | sorted by (window asc, op_hash lex asc) | invariant PASS | | 4.2.9 | `validate_proposer_is_canonical` | mt-consensus | mismatched proposer | reject | unit-test PASS | | 4.2.10 | `finalization_status(header)` Cemented/Rejected | mt-consensus::finalization_status | various confirmer counts | per is_cemented | unit-test PASS | #### M4.3 mt-entry — NodeRegistration + Selection | # | Элемент | file:line | Reproduce | Expected | Pass | |---|---------|-----------|-----------|----------|------| | 4.3.1 | `NodeRegistration` 5344B fixed | mt-entry/src/lib.rs | encode size | == NODE_REGISTRATION_SIZE | static | | 4.3.2 | `nodereg_hash` SHA-256("mt-nodereg" \|\| scope) | mt-entry::nodereg_hash | external oracle | byte-exact | external PASS | | 4.3.3 | `validate_noderegistration` 3 rules | mt-entry::validate_noderegistration | per-rule reject | suite supported, sig verify, node_id unique, operator account exists+!is_node_operator | 4/4 reject paths PASS | | 4.3.4 | `candidate_vdf_init([T_r, cba(W-2), node_id])` [I-8] | mt-entry::candidate_vdf_init | external oracle | byte-exact composition | external PASS | | 4.3.5 | `compute_expiry_window(W)` 3τ₂ | mt-entry::compute_expiry_window | various W | == W + 3 × 20160 | static formula | | 4.3.6 | `apply_candidate_expiry(pool, W)` removes expired | mt-entry::apply_candidate_expiry | per-window call | pool.len() decreases | observable PASS | | 4.3.7 | `selection_slots` 1% admission cap (ADMISSION_DIVISOR=130) | mt-entry::selection_slots | active_chain_length 13000 → 100 slots | per-formula | spec-binding PASS | | 4.3.8 | `selection_sort_key` SHA-256("mt-selection" \|\| ...) | mt-entry::selection_sort_key | external oracle | byte-exact | external PASS | | 4.3.9 | `rank_candidates_for_selection` canonical sort | mt-entry::rank_candidates_for_selection | unsorted input | sorted by sort_key | invariant PASS | | 4.3.10 | `apply_selection_event(pool, nodes, accounts, W, params)` | mt-entry::apply_selection_event | candidates ready | inserted into NodeTable, removed from pool, operator account flagged | observable PASS | | 4.3.11 | `apply_noderegistrations_batch` incremental sort+apply | mt-entry::apply_noderegistrations_batch | per-window | applied count == 1 if vdf_chain_length ≥ τ₂ | observable PASS | | 4.3.12 | `required_vdf_length(pending, active, τ₂)` integer permille | mt-entry::required_vdf_length | pending=0,active=0 → tau2_windows | spec-binding | unit-test PASS | --- ### Уровень 5 — Persistence (M5 mt-store) | # | Элемент | file:line | Reproduce | Expected | Pass | |---|---------|-----------|-----------|----------|------| | 5.1 | `FsStore::open(data_dir)` создаёт proposals/ | mt-store/src/lib.rs | удалить proposals/, открыть store | proposals/ создан | observable PASS | | 5.2 | `cleanup_orphan_tmp` (M5-LOW-8 closure) | mt-store/src/lib.rs:67 | placement orphan .tmp file | удалён при open | regression test PASS | | 5.3 | `save_account_table` + `load_account_table` round-trip | mt-store/src/lib.rs:271-281 | save → load | byte-exact decode | round-trip PASS | | 5.4 | `save_node_table` + `load_node_table` round-trip | mt-store/src/lib.rs:301-311 | save → load | byte-exact decode | round-trip PASS | | 5.5 | `save_candidate_pool` + `load_candidate_pool` round-trip | mt-store/src/lib.rs:331-341 | save → load | byte-exact decode | round-trip PASS | | 5.6 | `archive_proposal(header)` создаёт `proposals/{W:020}.bin` | mt-store::archive_proposal | per-window | файл 3722B на диске | `ls data/proposals/` | | 5.7 | `get_proposal_by_window(W)` byte-exact decode | mt-store::get_proposal_by_window | archived header | recovered identical | round-trip PASS | | 5.8 | `save_meta_last_cemented(W)` u64 LE | mt-store::save_meta_last_cemented | per-window | meta_last_cemented.bin = 8B u64 LE | hexdump check | | 5.9 | `verify_consistency()` crash recovery | mt-store::verify_consistency | удалить proposals/ файл, оставить meta_last_cemented | StoreError::NotFound | error path PASS | | 5.10 | `prune_proposals_before(window)` | mt-store::prune_proposals_before | call с threshold | удаляет файлы window < threshold | filesystem state | | 5.11 | Atomic rename pattern (tempfile → rename) | mt-store/src/lib.rs:87-114 | crash mid-write simulation | partial files либо clean state | atomicity PASS | | 5.12 | `decode_X` size validation перед read | mt-store/src/lib.rs | corrupted file (wrong size) | StoreError::CorruptedLength | error path PASS | --- ### Уровень 6 — Integration layer (montana-node CLI) #### N1. Identity management commands | # | Команда | Reproduce | Expected | Pass | |---|---------|-----------|----------|------| | 6.1 | `montana-node init` (random) | `./montana-node init` | 24 words displayed in 4×6 grid + identity.bin (15627B) | 24-word grid + file size byte-exact | | 6.2 | `montana-node init --mnemonic "..."` | recovery from words | byte-exact same identity as original | identity match | | 6.3 | `montana-node init --entropy ` | deterministic from hex | byte-exact same on re-run | determinism PASS | | 6.4 | `montana-node init --force` overwrite | second init без --force отклонён, с --force OK | IdentityFileExists error без --force | error path PASS | | 6.5 | `montana-node inspect` (default) | без --reveal-master-seed | fingerprint only, no master_seed | observable PASS | | 6.6 | `montana-node inspect --reveal-master-seed` | флаг present | master_seed shown (64 hex) | observable PASS | | 6.7 | Recovery determinism cross-machine | machine 1 init → mnemonic; machine 2 init --mnemonic | identical account_id, node_id | byte-exact match | #### N2. Node lifecycle commands | # | Команда | Reproduce | Expected | Pass | |---|---------|-----------|----------|------| | 6.8 | `montana-node start --max-windows 2` | finite run | 2 windows processed, balance += 26 Ɉ, exit 0 | observable PASS | | 6.9 | `montana-node start --d-test-override 1000000` | TEST-ONLY override | warning printed, fast windows | observable PASS | | 6.10 | `montana-node status` | AccountTable + NodeTable + CandidatePool dump | Σ balance, supply, phase, current_window correct | observable PASS | | 6.11 | `montana-node time` | window timing info | current_window, ближайший selection (W % 336==0), эпоха τ₂ | static check | | 6.12 | `montana-node help` / `--help` / `-h` | help text | full usage + flag descriptions | text match | | 6.13 | Exit codes | invalid args → exit 2; ok → exit 0; runtime err → exit 1 | per-spec | exit code check | | 6.14 | LocalNodeError variants Display | trigger каждую ошибку | typed error message в stderr | manual cases | #### N3. Phase transitions auto-detection | # | Сценарий | file:line | Reproduce | Expected | Pass | |---|----------|-----------|-----------|----------|------| | 6.15 | Genesis bootstrap (placeholder zeros) | node_lifecycle.rs:70 is_bootstrap_node | `cargo run -p montana-node start --max-windows 1` (свежий узел) | phase = Active immediately, NodeTable содержит self | observable PASS | | 6.16 | Candidate path (post-ceremony, mismatch pubkey) | node_lifecycle.rs:78 | finalized bootstrap_node_pubkey != identity.node_pk | phase = Bootstrap → CandidateVdf на первом окне | DEV-010 logic PASS (не тестируется до ceremony) | | 6.17 | DEV-010 status | docs/SPEC_DEVIATIONS.md:172 | grep Status | acknowledged auto-detection | static | #### N4. State migration | # | Сценарий | file:line | Reproduce | Expected | Pass | |---|----------|-----------|-----------|----------|------| | 6.18 | current_window.bin v0 → v1 auto-upgrade | clock.rs:31-58 | вручную создать 8B legacy file → start узел → check size | next save → 16B v1 формат | round-trip PASS | | 6.19 | identity.bin magic+version validation | identity.rs:226-231 | tamper magic byte → load_identity | InvalidMagic error | error path PASS | | 6.20 | timechain.bin magic+version | timechain_state.rs:47-52 | tamper magic | InvalidMagic | error path PASS | | 6.21 | node_state.bin magic+version | node_lifecycle.rs:82-87 | tamper magic | InvalidMagic | error path PASS | --- ### Уровень 7 — Operator surface (UX/UI) #### O1. launchd integration (macOS) | # | Элемент | Reproduce | Expected | Pass | |---|---------|-----------|----------|------| | 7.1 | plist Label = "org.montana.node" | `cat ~/Library/LaunchAgents/org.montana.node.plist \| grep Label` | production reverse-domain (no `dev.*`/`local.*`) | static check | | 7.2 | RunAtLoad=true | plist | автозапуск при логине | login test | | 7.3 | KeepAlive Crashed=true | plist | restart on crash | `kill -9 `, узел restart-ит через ThrottleInterval=10 | restart observable | | 7.4 | StandardOutPath/StandardErrorPath | plist | logs в `data/logs/` | `tail -F data/logs/montana.log` | observable | | 7.5 | SIGTERM graceful shutdown | start.rs:31-33 + 545-546 | `launchctl unload -w plist` | узел сохраняет state, exit clean | clean shutdown | | 7.6 | ThrottleInterval=10 rate limit | plist | crash несколько раз | минимум 10s между restart | observable timing | #### O2. .command файлы | # | Файл | Reproduce | Expected | Pass | |---|------|-----------|----------|------| | 7.7 | `1. Запуск и логи узла.command` | дабл-клик в Finder | Terminal открывается, status + tail -F | observable open Terminal | | 7.8 | `2. Остановить узел.command` | дабл-клик | launchctl unload, "ГОТОВО" message | узел остановлен | | 7.9 | Relative `$DIR` paths (не hardcoded) | grep "$HOME/Applications" в .command | пусто | path-portable PASS | #### O3. Install scripts | # | Скрипт | Reproduce | Expected | Pass | |---|--------|-----------|----------|------| | 7.10 | `scripts/install-local-mac.sh` | `bash scripts/install-local-mac.sh` (свежая система) | build + install + identity init + launchd setup | full e2e install | | 7.11 | `scripts/install-mac.sh` | `curl ... \| bash` | git clone + build + install | full e2e install | | 7.12 | `scripts/install-vps.sh` (Linux) | `bash scripts/install-vps.sh` Ubuntu/Debian | systemd setup + start | systemctl status active | | 7.13 | `INSTALL_DIR` env var override | `INSTALL_DIR=/custom/path bash scripts/install-local-mac.sh` | install в custom location | observable PASS | | 7.14 | `dist/macOS/install.command` | дабл-клик | full install + open Finder | GUI flow PASS | #### O4. State files inventory | # | Файл | Path | Expected size/format | Pass | |---|------|------|---------------------|------| | 7.15 | `identity.bin` | `data/identity.bin` | 15627B fixed, magic="mt-local", version=1 | size + magic byte-exact | | 7.16 | `accounts.bin` | `data/accounts.bin` | N × 2059B (растёт с TransferActivation) | size кратен 2059 | | 7.17 | `nodes.bin` | `data/nodes.bin` | N × 2098B | size кратен 2098 | | 7.18 | `candidates.bin` | `data/candidates.bin` | N × 2082B | size кратен 2082 | | 7.19 | `meta/current_window.bin` | `data/meta/current_window.bin` | 16B v1 (либо 8B v0 legacy) | size 16 либо 8 | | 7.20 | `meta/timechain.bin` | `data/meta/timechain.bin` | magic "mttc" + version + T_r + D + last_window | hexdump magic | | 7.21 | `meta/node_state.bin` | `data/meta/node_state.bin` | magic "mtns" + version + lifecycle fields | hexdump magic | | 7.22 | `meta_last_cemented.bin` | `data/meta_last_cemented.bin` | 8B u64 LE (либо absent если pre-genesis) | hexdump | | 7.23 | `proposals/{window:020}.bin` | `data/proposals/00000000000000000001.bin` | каждый файл 3722B | size byte-exact | | 7.24 | `logs/montana.log` + `montana.err.log` | `data/logs/` | log files (launchd redirect) | files present | #### O5. Crash recovery | # | Сценарий | Reproduce | Expected | Pass | |---|----------|-----------|----------|------| | 7.25 | Power loss mid-write (atomic rename) | start узел, `pkill -9 montana-node` во время save_progress | partial .tmp файл либо clean state, не corrupted bin | atomicity PASS | | 7.26 | SIGKILL → reload state | `kill -9 ` → restart | continues с last cemented window (state preserved) | observable continuity | | 7.27 | Corrupted file detection | tamper data/accounts.bin (truncate) | StoreError::CorruptedLength на load | error path PASS | | 7.28 | meta_last_cemented vs proposals/ inconsistency | удалить proposals/.bin, оставить meta_last_cemented | StoreError::NotFound, не silent skip | verify_consistency PASS | | 7.29 | Re-open после crash valid state | crash → restart | узел продолжает с того окна, balance preserved | observable PASS | --- ### Уровень 8 — Conformance & test vectors #### C1. NIST KAT (cross-implementation conformance) | # | Vector | Source | Reproduce | Pass | |---|--------|--------|-----------|------| | 8.1 | ML-DSA-65 KeyGen 25 cases | NIST ACVP-Server `ML-DSA-keyGen-FIPS204` | `cargo test -p mt-crypto-native --test nist_acvp_kat` | 25/25 byte-exact | | 8.2 | ML-DSA-65 SigGen 15 cases (1 empty + 14 ctx) | NIST ACVP-Server `ML-DSA-sigGen-FIPS204` tgId=3 | same test | 15/15 byte-exact | | 8.3 | ML-KEM-768 KeyGen 25 cases | NIST ACVP-Server `ML-KEM-keyGen-FIPS203` | same test | 25/25 byte-exact | | 8.4 | mt_sign_mldsa ≡ mt_sign_mldsa_ctx (empty ctx) | own correctness check | same test | 1/1 PASS | #### C2. RFC test vectors | # | Vector | Source | Pass | |---|--------|--------|------| | 8.5 | SHA-256 "abc" → ba7816bf...15ad | FIPS 180-4 §B.1 | unit-test PASS | | 8.6 | HMAC-SHA-256 cases 1-7 | RFC 4231 | 7/7 PASS | | 8.7 | HKDF-Expand vectors | RFC 5869 §A.1 | PASS | | 8.8 | PBKDF2 vectors | RFC 7914 §11 / RFC 6070 | PASS | #### C3. Determinism invariants (191 automated total) | # | Crate | Test count | Reproduce | Pass | |---|-------|------------|-----------|------| | 8.9 | mt-merkle | 10 | `cargo test -p mt-merkle --test determinism_invariants` | 10/10 | | 8.10 | mt-genesis | 7 | `cargo test -p mt-genesis --test determinism_invariants` | 7/7 | | 8.11 | mt-state | (per file) | `cargo test -p mt-state --test determinism_invariants` | all PASS | | 8.12 | mt-timechain | 19 | `cargo test -p mt-timechain --test determinism_invariants` | 19/19 | | 8.13 | mt-account | 29 | `cargo test -p mt-account` | 29/29 | | 8.14 | mt-lottery | 34 | `cargo test -p mt-lottery` | 34/34 | | 8.15 | mt-consensus | 27 | `cargo test -p mt-consensus` | 27/27 | | 8.16 | mt-entry | 24 | `cargo test -p mt-entry` | 24/24 | | 8.17 | mt-store | 17 | `cargo test -p mt-store` | 17/17 | #### C4. Security invariants (13 automated) | # | Invariant | Reproduce | Pass | |---|-----------|-----------|------| | 8.18 | SecretKey: !Clone | `cargo test -p mt-crypto --test security_invariants secret_key_is_not_clone` | compile-PASS | | 8.19 | SecretKey: !PartialEq | same test | compile-PASS | | 8.20 | SecretKey heap-allocated | same test | size_of == ptr | | 8.21 | SecretKey impl Drop | same test | trait check | | 8.22 | MlkemSecretKey same 4 invariants | same test | 4/4 PASS | | 8.23 | FFI fills SK с non-zero bytes | same test | bytes != [0; N] | | 8.24 | No println/log on SK in lib code | same test | grep clean | | 8.25 | PublicKey/Signature: Clone (sanity) | same test | trait check | --- ### Уровень 9 — DEV-001..DEV-011 status verification | # | DEV-N | Spec ref | Status verify | Code location | Pass | |---|-------|----------|---------------|---------------|------| | 9.1 | DEV-001..DEV-009 — закрыты через canonical apply_proposal pipeline rewrite | docs/SPEC_DEVIATIONS.md:1-170 | `grep "Status: закрыто" docs/SPEC_DEVIATIONS.md \| wc -l` | 9 closed entries | static | | 9.2 | DEV-010 — genesis bootstrap auto-detection | docs/SPEC_DEVIATIONS.md:172 | `is_bootstrap_node` logic в node_lifecycle.rs:70-79 | runtime check via `montana-node status` | observable | | 9.3 | DEV-011 — hardware calibration initial D | docs/SPEC_DEVIATIONS.md:N | runtime D adjustment per τ₂ | post-Genesis ceremony auto-adjust | static acknowledgment | | 9.4 | Pre-commit hook DEV count enforcement | scripts/pre-commit.sh Gate 3 | edit DEV in code without doc → reject | enforced commit gate | static | --- ### Прохождение matrix **Этапы:** 1. **Фаза A — automated baseline** (1-2 часа) - `cargo test --all` — все 191 determinism + 13 security + NIST KAT + RFC vectors - Pass criterion: 0 failures - Result: матрица пунктов 8.x = automated PASS 2. **Фаза B — operator surface manual** (1 рабочий день) - Свежая установка через `scripts/install-local-mac.sh` - Прохождение пунктов 6.x (CLI commands), 7.x (operator surface) - Verify state files размеры/magic - Verify launchd integration (start/stop/restart on crash) - Verify install scripts на VPS (Linux) + macOS 3. **Фаза C — protocol per-element manual** (3-5 рабочих дней) - Каждый пункт 1.x (M1 primitives), 2.x (M2 state), 3.x (apply_proposal), 4.x (consensus) - External oracle cross-checks (Python SHA-256 для hash compositions) - Boundary tests (overflow, underflow, edge cases) - Per-DEV deviation status confirmation 4. **Фаза D — recovery & crash** (0.5 рабочего дня) - Сценарии 7.25-7.29 — atomic rename, SIGKILL recovery, corrupted file detection **Pass criterion для всей matrix:** 100% пунктов с PASS либо явный finding с DEV-NNN reference в docs/SPEC_DEVIATIONS.md. **Output:** заполненная matrix (markdown table со столбцом "Status: PASS/FAIL/N/A") в отдельном документе `docs/manual-validation-results-YYYY-MM-DD.md`. Это deliverable перед external audit / перед запуском M6 network layer. **Tooling:** - Automated: `cargo test --all` для пунктов 8.x - External oracle: Python 3 + hashlib для SHA-256 cross-checks - Diff: `diff -u expected actual` для byte-exact verification - Hexdump: `hexdump -C file | head` для file format verification --- ### Связь с существующим Manual Validation Gate «Локальный shakedown» сценарии 0-5 (выше) дают smoke-pass — happy-path запуск каждого слоя. Matrix M1-M5 — детализация: каждый scenario разворачивается в 20-50 проверок per-element. Сценарии 0-5 → entry-level demonstration. Matrix → production-grade audit-ready evidence. --- ### M6 — Network ✅ ЗАКРЫТ (in-process e2e); cross-machine pairing defer M8 - [x] `mt-net` (~2700 LOC, no_std) — wire format envelope + 12 structured payloads + IBT online/mesh proofs + Bootstrap PoW + Uniform Framing + peer selection (4-level diversity + LRU) + Dandelion++ stem-fluff + NAT traversal + Mesh Transport + Store-and-Forward. **110 unit + integration tests PASS.** - [x] `mt-net-transport` (~470 LOC, std + libp2p) — libp2p TCP+TLS 1.3+Noise+Yamux upgrade chain + MontanaCodec для request-response + MontanaBehaviour + IBT classify_proof. **14 tests PASS включая 3 e2e two-node communication.** Phases закрытия: - Phase A wire envelope + payloads (commits `9de287b`, `bc694a5`) - Phase B IBT + Bootstrap PoW (commit `26e76c9`) - Phase D Uniform Framing (commit `cce189f`) - Phase E peer selection + diversity + LRU (commit `f34726f`) - Phase F Dandelion + NAT (commit `7d44687`) - Phase G Mesh + Store-and-Forward (commit `60466c1`) - **Critic-fix bundle P-C1..P-C8** (commit `93b9cdc`) — 8 code findings closed (domain SSOT, prefix-free rename, fuzz harnesses, try_new constructors, forward-compat, O(1) verify, unwrap/expect refactor) - Phase C.0 mt-net-transport skeleton + libp2p [C-5] capability checklist 8/8 PASS (commit `ea6608e`) - Phase C.1 MontanaCodec (commit `11f3b80`) - Phase C.2 MontanaBehaviour wrapper (commit `ba00051`) - Phase C.3 e2e two-node handshake — Manual Validation Gate scenario 6 PASS (commit `9a15f49`) - Phase C.4 e2e proposal exchange + 512 KiB boundary — scenario 7 PASS (commit `04f8d29`) **Критерий закрытия M6:** 2 узла на разных machines обмениваются proposals через network — closed in-process (e2e tests), cross-machine pairing defer to M8 montana-node binary distribution. ### M7 — Fast Sync - [ ] `mt-sync` — snapshot делiver + verify через Merkle root сравнение, catch-up от snapshot до current window, genesis content replication. **Критерий закрытия M7:** новый узел синхронизируется от 0 до текущего state за минуты, не часы. ### M8 — Node binary - [ ] `mt-node` — CLI, config, wire всё вместе, logging, metrics, graceful shutdown. **Критерий закрытия M8:** один бинарь `mt-node` запускает узел с конфигом, участвует в сети, зарабатывает Монтану (Ɉ) в лотерее. ### M9 — Conformance suite ✅ READY (initial); expansion in progress - [x] `mt-conformance` (~150 LOC) — публичный test vectors набор для cross-implementation byte-exact verification. Initial vectors: envelope A1/A2/A3 + IBT B1 (after P-C2 rename) + Bootstrap PoW F1/F2 target derivation. **2 unit tests PASS.** - [ ] Expansion: 12 TBD-A markers (consensus objects 0x01..0x22 + app-layer 0x60/0x61/0x64) defer until app-layer payload format finalization. **Критерий закрытия M9:** вторая реализация (Swift iOS либо Go) проходит все vectors байт-в-байт. iOS Phase 2.1 — port done (`MontanaTests/MTConformanceVectors.swift` mirror), pending Xcode `xcodebuild test` verification. ### M10 — Spec compliance cleanup ✅ ЗАКРЫТ Полное приведение кода в соответствие спеке после серии breaking changes (winner_class removal, TransferActivation opcode, single-path node lottery, ACCOUNT_RECORD_SIZE refactor). Закрыто 7 commits, все 4 обязательные проверки зелёные, 593+ тестов passed. ### M11 — CloseAccount финализация 🔄 Текущий - [ ] `CloseAccount` opcode `0x0B` — полная реализация после spec-финализации payload формата: struct + Инварианты CloseAccount, apply_proposal dispatch (вычитание баланса в supply, удаление AccountRecord, освобождение operator-binding если is_node_operator), binding test vectors. **Критерий закрытия M11:** payload format CloseAccount специфицирован в спеке, реализация byte-exact, все 4 обязательные проверки зелёные. ### M12 — Pure conservation monetary policy compliance ✅ ЗАКРЫТ (superseded by M13) Pre-mainnet миграция к pure geometric pin 41/40, без bootstrap-надбавки, без сжигания, без opcodes прикладного слоя (никнеймы, сервисные кредиты). **Superseded M13 const emission cleanup** — geometric step-up baseline (41/40) удалён в пользу const `EMISSION_moneta = 13 Ɉ` per окно. Историческая запись остаётся для context. ### M13 — Const emission cleanup ✅ ЗАКРЫТ Pre-mainnet упрощение монетарной политики: pin 41/40 + carry-recurrence заменён на единственную константу `EMISSION_moneta = 13 × 10⁹ nɈ` per окно. `reward_moneta(W) = EMISSION_moneta` (const, навсегда), `supply_moneta(W) = EMISSION_moneta × (W + 1)` closed-form. Удалено: - `MonetaryState` struct + 24B persistent state + `apply_step` + 3 controlled overflow panics - `monetary_epoch_tick` apply_proposal Step 2.5 - `r_baseline_at_epoch` reconstruction helper - `save_monetary_state`/`load_monetary_state` mt-store API + `monetary.bin` file - ProtocolParams поля `monetary_epoch_windows`, `inflation_num`, `inflation_den` (−24B encoded, 4118 → 4094) - `m33_emission` example - Constitutional declaration spec section (Freigeld/Gesell/Frederick/Keynes/Friedman/Schmitt-Grohé/Bordo/Onken/Wörgl academic apparatus) - Equilibrium analysis для (41/40)^e + Binding test vectors с эпохами Замена: - ProtocolParams: единственное поле `emission_moneta: u128 = 13_000_000_000` - `reward_moneta(params: &ProtocolParams) -> u128` — однострочник `params.emission_moneta` - `supply_moneta(W, params)` — closed-form `emission × (W + 1)` - `compute_state_root(node_root, candidate_root, account_root)` — без MonetaryState arg - `apply_proposal(account_table, node_table, candidate_pool, input, params)` — без monetary mut arg, без Step 2.5 Constitutional break: новый Genesis State Hash (ProtocolParams layout + state_root composition меняются) — pre-mainnet нормально. **Критерий закрытия M13:** spec + code grep на pattern set (`MonetaryState|r_baseline|carry_current|inflation_num|inflation_den|monetary_epoch|R_GENESIS|r_genesis_moneta|MONETARY_STATE|geometric step|41/40|2\.5%|Freigeld|Gesell|Frederick`) даёт 0 hits в спеке и в коде. ### M1-E — Миграция подписи FN-DSA-512 → ML-DSA-65 ✅ ЗАКРЫТ Pre-mainnet breaking wire-format change: переход на NIST FIPS 204 финализированный стандарт. Закрывает [I-1] (PQ-secure) более mainstream путём (NIST level 3, formally finalized) и устраняет dependency на необфициальный pqcrypto-falcon binding wrapper. **Все 11 шагов Phase E plan executed:** - E.1: проверка test_vectors.rs (vectors уже обновлены под L=32 для ML-DSA, L=64 для ML-KEM) - E.2: 5 KAT vectors сгенерированы через keygen_vectors.rs reference implementation - E.3: спека v31.0.0 обновлена — Derivation Vectors 1+2 hex заменён на реальные значения; новый раздел «Binding KAT vectors для KeyGen → terminal observable output» с 5 KAT (SHA-256 fingerprints для byte-exact cross-impl conformance) - E.4: `mt-crypto::self_test()` обновлён — KAT 1 byte-exact conformance check вместо placeholder; добавлены `EXPECTED_KAT_1_PK_SHA256` / `EXPECTED_KAT_1_SK_SHA256` константы - E.5: integration test `e2e_recovery.rs` — 3 теста (terminal_observable_byte_exact, distinct_entropies_distinct_terminals, account_node_keys_differ_same_master) **Phase F (examples):** - F.1: m1_mnemonic refactor — cmd_keypair → cmd_seeds, новый cmd_keypair (terminal output), новый cmd_recovery_fingerprint; mt-codec domain registry +1 (mt-recovery-fingerprint) - F.2: m1_crypto refactor — cmd_keypair → cmd_keypair_random + новый cmd_keypair_deterministic (default `keypair`) - F.3: verify через release binaries — все subcommands PASS **Phase G (closure):** - G.1: VERSION.md updated, M1-E entry added - G.2: ROADMAP.md M1-E milestone closure - G.3: 4 обязательные проверки green (fmt/clippy/test/build --release) - G.4: 3 commits — Phase E `8be11f3`, Phase F `11f7232`, Phase G (этот) **Manual validation готов к авторскому прогону** (Validation Gate Scenario 0 и 1 ready): - `m1_mnemonic recovery-fingerprint` — single 64-char hex для two-device validation - `m1_mnemonic keypair` — terminal observable IDs (account_id, node_id) + 6 byte-exact key parts - `m1_crypto keypair` — deterministic recovery primitive sanity - `m1_mnemonic vectors` — 6/6 binding vectors PASS **Acknowledgement:** `ml-dsa 0.1.0-rc.8` — RustCrypto pure-Rust, **NOT formally audited**; migration target `libcrux-ml-dsa` когда formally verified version stable. Любая смена implementation library обязана сохранять byte-identity с KAT fingerprints спеки (cross-impl conformance gate). --- ## Dependency graph ``` sha2, ml-dsa, ml-kem │ ▼ ┌───────────────────────────────────────┐ │ │ mt-codec mt-crypto │ │ └─────────────┬─────────────────────────┘ │ ┌────────────────┴────────────────┐ ▼ ▼ mt-merkle mt-genesis │ │ └──────────────┬──────────────────┘ ▼ mt-state ◄────── mt-timechain │ │ └────┬─────────────┘ ▼ mt-account │ ┌───────────────────┼───────────────────┐ ▼ ▼ ▼ mt-lottery mt-consensus mt-entry │ │ │ └───────────────────┼───────────────────┘ ▼ mt-store │ ▼ mt-net │ ▼ mt-sync │ ▼ mt-node │ ▼ mt-conformance ``` Каждая стрелка — `[dependencies]` в `Cargo.toml`. Никаких циклов (Rust их запрещает на уровне компилятора). --- ## История обновлений | Дата | Изменение | Commit | |------|-----------|--------| | 2026-04-21 | ROADMAP actualization: R1 stale v29.8.x refs fix + R2 Сценарий 0 User onboarding (24-word mnemonic flow через m1_mnemonic) добавлен в Validation Gate + R3 history entries 2026-04-21 + R4 Starter instructions block для новой сессии. M6 unblock criterion: 6/6 сценариев passed. | (этот) | | 2026-04-21 | CRITIC.md v1.2.0 → v1.3.0: 3 новых прохода (Source→Sink Flow, Independent Oracle / Differential Check, Misuse-Resistance API Audit) + reframe mt-examples как operator-facing security surface + hard enforcement Multi-perspective rotation через obligatory per-perspective conclusions block. Catalyst — external critic v1.2.0 поднял 6 blind spots. | `5d41d7c` | | 2026-04-21 | feat: domain separation structural fix (NUL separator) + external critic findings closure. mt_crypto::hash() теперь SHA-256(domain ‖ 0x00 ‖ parts) — self-delimiting guarantee против 8 prefix-collision pairs в registry. Новая sha256_raw() для FIPS/HMAC/raw. Spec v29.12.0 → v29.13.0. Closes 3 external findings (P1 domain sep, P2 SK leak, P3 label) + 2 mine (P4 empty_internal binding, P5 stale RECOVERY). | `d762cec` | | 2026-04-21 | CRITIC.md v1.1.0 → v1.2.0: 7 новых проходов (timing/side-channel, concurrency, version compat, deps source audit, resource exhaustion беyond DoS, test quality, deployment) + Multi-perspective rotation + Anti-recency bias check + known blind spots (M-1..M-5 documented). | `bcecc65` | | 2026-04-21 | CRITIC.md v1.0.0 → v1.1.0: 4 новых прохода (primitive byte-level audit, registry integrity, output/observable surface, bottom-up reading discipline) + поведение при external critic finding. Reactive response на domain separation bug surface-ит ранее. | `74d2142` | | 2026-04-21 | doc: sync ROADMAP сценария 1 subcommands с actual m1_crypto binary interface (hash/sign/keypair/merkle-empty/all, не fips-abc/falcon-roundtrip/domain-separation) + m1_mnemonic stale spec refs cleanup. | `4126006` | | 2026-04-21 | feat: [I-9] closure path A — poly3 minimax заменяет linear log2_q64. mt-lottery Phase C prototype linear (2^-3.5 error) → Remez degree-3 minimax (2^-10.62, theoretical optimum). Binding coefficients B0..B3 halved form unsigned u64, unsigned Horner с intermediate invariants proofs. 14 binding test vectors (5 ln_q64 + 5 weighted_ticket_node + 4 weighted_ticket_account) + 5 target_next vectors в спеке. Spec v29.10.1 → v29.12.0. 4 conformance pending formulas closed. | `7b76dc9` | | 2026-04-21 | feat: validate_header winner_class ∈ {1,2} check — закрывает P2 external critic реализации (panic в apply_emission от malformed byte в подписанном proposal → liveness attack). HeaderError::InvalidWinnerClass + 2 тест-vectors. Spec v29.10.1 added invariant `winner_class ∈ {1,2}` в Proposal header. | `589be89` | | 2026-04-21 | refactor: [C-1] SSOT — WINNER_CLASS_NODE/ACCOUNT в mt-state (единый источник), pub use re-exports в mt-account/mt-lottery. Закрывает P1 external critic реализации (duplicate constants). | `5df072d` | | 2026-04-21 | Spec bump v29.9.0 → v29.10.1: mt-mnemonic closes [I-9] conformance (6 binding test vectors в спеке для M-1 Algorithm + per-role HKDF derivation). | `a35af3f` | | 2026-04-20 | Validation Gate режим зафиксирован: incremental scenario-by-scenario (один сценарий за сессию), status tracker, starter instructions для новой сессии. M6 unblock = 5/5 passed. | (предыдущий) | | 2026-04-20 | M4 + M5 CLOSED: mt-consensus (60 тестов, 5 phases — header + Lookback + control_set + Canonical acceptance + Finalization) + mt-entry (39 тестов, 5 phases — NodeRegistration + Candidate Pool + Selection event + Adaptive VDF + batch apply) + mt-store (24 теста, 5 phases — FsStore + table persist + proposal archive + crash recovery + pruning). Workspace 506 тестов. Next: Manual Validation Gate (5 сценариев). | (предыдущий) | | 2026-04-20 | Spec+role fix: target 8B → 16B u128 (header aligned с P5 P[I-9] integer form) + fallback_depth bound [1,255] specified + role 4.13.1 → 4.13.2 Gate 0.5 шаг (d.2) field name coverage. Proposal header [I-9] audit: 17 полей прогнаны — 1 finding (target, fixed), остальные clean. Critic findings P12-P15 закрыты. Ничто: `9113850`. | 9113850 | | 2026-04-19 | ROADMAP: expanded M5 до phases A-E + новая секция «Локальный shakedown — Manual Validation Gate» (5 сценариев между M5 и M6) + policy hands-on example per milestone | 2f317b8 | | 2026-04-19 | M4 Phase C: mt-lottery Node lottery — `seniority_bonus`, `lottery_weight`, `log2_q64` (bit-scan + Phase C prototype linear approx), `ln_q64`, `weighted_ticket_node` (24 теста). [I-9] compliant, polynomial deferred (closed 2026-04-21 path A) | 4cb42bc | | 2026-04-19 | Post-audit fix: critic findings P6-P9 (duplicate lottery formulas без integer form в spec) + P8 (methodology failure — пропуск Шага 8). Ничто commits: `21e1547` (spec duplicate fix), `7be68f5` (role 4.13.0 → 4.13.1 + усилен Gate 0.5 обязательным pre-edit duplicate scan). | de68778 | | 2026-04-19 | C4 re-audit M2-M3 по [I-9]: `next_d` соответствует spec (code использовал permille, spec Q32.32 → aligned на permille); emission family (bonus/reward/bootstrap_cumulative/supply) полностью [I-9] compliant без изменений. Spec P4 patched in-place в Ничто; code comment добавлен. | 2c14587 | | 2026-04-19 | Spec v29.7.1 → v29.8.0: [I-9] Bit-exact deterministic arithmetic инвариант + integer forms для P2/P3/P4/P5 (role+spec atomic в Ничто `1f3f3f9`); Phase C unblocked; fix ROADMAP ×/+ ошибка | 94c7002 | | 2026-04-19 | M4 Phase B: mt-lottery VdfReveal тип + encode/signed_scope/reveal_hash/compute_endpoint/validate_reveal (15 тестов) | 5395581 | | 2026-04-24 | **M10 ЗАКРЫТ** — spec compliance cleanup v30.7.0 → v30.9.0. 11 phases закрыты (Phase 7 CloseAccount отложен в M11). 7 commits: 0b55d69 (ROADMAP), 7f19876 (AccountRecord +53B + winner_class cascade), 500fb9f (account lottery cleanup), 2ed18ab (TransferActivation + cooldown), 67868d9 (Nickname/Auction tables + ProtocolParams extension), 533c457 (price_at binding vectors), и этот closure commit. 593+ тестов зелёные, 4 проверки passed. | (этот commit) | | 2026-04-23 | M10 Phase 0: VERSION.md bump v30.7.0 → v30.9.0 + ROADMAP M10 milestone block + 13 phases plan + 21 drift points audit | 0b55d69 | | 2026-04-19 | M4 Phase A: mt-lottery BundledConfirmation тип + encode/signed_scope/bundle_hash/validate (24 теста) | 444e399 | | 2026-04-19 | Spec v29.7.0 → v29.7.1: editorial cleanup (S-1 дубль mt-node, S-2 genesis_candidate_root, F-1 Genesis State Hash domain) + code fix mt-genesis | 6058f3b | | 2026-04-19 | Роль v1.3.0 → v1.4.0: [C-2] Spec Flow Pre-verification глобальный инвариант | 0197044 | | 2026-04-19 | **M3 ЗАКРЫТ** — mt-account 6 phases, 102 теста, ~1850 строк. ROADMAP: M4 детально, M5 crude. | 30400eb | | 2026-04-19 | M3 Phase F: Genesis state materialization (12 тестов) | 30400eb | | 2026-04-19 | M3 Phase E: apply_proposal partial steps 2/3.5/3.6/4 (15 тестов) | 5ab2a45 | | 2026-04-19 | M3 Phase D: emission reward/bonus/supply (17 тестов) | 36503dc | | 2026-04-19 | M3 Phase C: apply individual ops (15 тестов) | 8fcca20 | | 2026-04-18 | M3 Phase B: validation OpError + 5 функций (23 теста) | cf95eaf | | 2026-04-18 | Spec v29.6.9 → v29.7.0: signature architecture (SSI rules R1..R4), breaking `cemented_bundle_aggregate` → node_ids only | 918cdf2 | | 2026-04-17 | ROADMAP: M3 phase-детализация + M4 crude phases (правило v1.3.0) | e92f0b4 | | 2026-04-17 | Роль v1.2.0 → v1.3.0: правило детализации ROADMAP | 0893fd4 | | 2026-04-17 | M3 Phase A: mt-account types + encoding + op_hash (18 тестов) | 0efd92d | | 2026-04-17 | **M2 ЗАКРЫТ**: mt-timechain (23 теста), итог M2 — 2 пакета, 55 тестов | 76ed8da | | 2026-04-17 | M2: mt-state закрыт (32 теста) | ab99e23 | | 2026-04-17 | Spec v29.6.8 → v29.6.9 (противоречия #2, #3 устранены) | 2c148b5 | | 2026-04-17 | mt-version crate удалён (нарушение SSOT [C-1]) | 8bd008e | | 2026-04-17 | [C-1] SSOT — глобальный инвариант кода в роли | 2a9309d | | 2026-04-17 | Spec v29.6.7 → v29.6.8 (противоречие #1: selection_interval) | ff390f2 | | 2026-04-17 | **M1 ЗАКРЫТ**: mt-genesis (19+1 тест), итог M1 — 4 пакета, 88 тестов | 45c1e84 | | 2026-04-17 | M1: mt-merkle закрыт (25 тестов) | f242956 | | 2026-04-17 | Roadmap: demos отложены до M4+ | 7c9219e | | 2026-04-17 | Создан ROADMAP.md | 65e34ec | | 2026-04-17 | M1: mt-crypto закрыт (18 тестов) | df55372 | | 2026-04-17 | M1: mt-codec закрыт (26 тестов) | 90464a8 | | 2026-04-17 | Роль: post-commit test block обязателен | 8d42073 | | 2026-04-17 | Роль: language policy — max Russian | c06c56b | | 2026-04-17 | Роль: terminal commands one-line | a18acc6 | | 2026-04-17 | Роль: auto-commit policy | cd2f719 | | 2026-04-17 | M0: workspace skeleton | e2457ad |