// M3 — Account operations shakedown. // Subcommands: // genesis-state build_genesis_state(params): bootstrap acc + node + chain_length=1 // open-account TransferActivation: sponsor → new account // transfer-scenario sponsor → A → B → Transfer A→B 50% balance // change-key ChangeKey: rotate pk; old pk → InvalidSignature на следующей op // anchor Anchor: chain payload hashes // emission-schedule [W1 W2 ...] reward + supply ledger // validation-rejects 6 OpError cases байт-в-байт REJECTED // all Прогнать все 7 // // Exit code 0=PASS, 1=FAIL. use std::env; use std::process::ExitCode; use mt_account::{ apply, build_genesis_state, genesis_state_root, op_hash, reward_moneta, supply_moneta, validate_anchor, validate_change_key, validate_transfer, validate_transfer_activation, Anchor, ChangeKey, GenesisState, OpError, Operation, Transfer, TransferActivation, }; use mt_crypto::{keypair_from_seed, sign, PublicKey, SecretKey, KEYPAIR_SEED_SIZE}; use mt_examples::{hex_full, print_kv, print_note, print_section, print_subsection}; use mt_genesis::genesis_params; use mt_state::{derive_account_id, AccountId, AccountRecord, AccountTable}; const SUITE_ID: u16 = 0x0001; // ---- helpers ---- // Deterministic keypair из seed-byte (повторяемо между прогонами). fn kp(seed_byte: u8) -> (PublicKey, SecretKey) { let mut seed = [0u8; KEYPAIR_SEED_SIZE]; seed[0] = seed_byte; seed[31] = seed_byte ^ 0xAA; // entropy keypair_from_seed(&seed).expect("keypair_from_seed") } // Краткий fingerprint pubkey (8 байт) для отображения. fn pk8(pk: &PublicKey) -> String { hex_full(&pk.as_bytes()[..8]) } // Создать sponsor account с явным балансом — synthetic setup без build_genesis_state. fn synthetic_sponsor(state: &mut AccountTable, pk: &PublicKey, balance: u128) -> AccountId { let account_id = derive_account_id(SUITE_ID, pk.as_bytes()); let frontier = mt_crypto::hash(mt_codec::domain::GENESIS, &[&account_id]); state.insert(AccountRecord { account_id, balance, suite_id: SUITE_ID, is_node_operator: false, frontier_hash: frontier, op_height: 0, account_chain_length: 0, account_chain_length_snapshot: 0, current_pubkey: *pk.as_bytes(), creation_window: 0, last_op_window: 0, last_activation_window: 0, }); account_id } // ---- builders для операций ---- fn mk_transfer_activation( sponsor_id: AccountId, sponsor_sk: &SecretKey, receiver_pk: &PublicKey, amount: u128, prev_hash: mt_crypto::Hash32, ) -> TransferActivation { let receiver_id = derive_account_id(SUITE_ID, receiver_pk.as_bytes()); let mut op = TransferActivation { prev_hash, sender: sponsor_id, receiver: receiver_id, suite_id: SUITE_ID, receiver_pubkey: receiver_pk.clone(), amount, signature: mt_crypto::Signature::from_array([0u8; mt_crypto::SIGNATURE_SIZE]), }; let mut scope = Vec::new(); op.encode_signed_scope(&mut scope); op.signature = sign(sponsor_sk, &scope).expect("sign TransferActivation"); op } fn mk_transfer( sender_id: AccountId, sender_sk: &SecretKey, link_id: AccountId, amount: u128, prev_hash: mt_crypto::Hash32, ) -> Transfer { let mut op = Transfer { prev_hash, sender: sender_id, link: link_id, amount, signature: mt_crypto::Signature::from_array([0u8; mt_crypto::SIGNATURE_SIZE]), }; let mut scope = Vec::new(); op.encode_signed_scope(&mut scope); op.signature = sign(sender_sk, &scope).expect("sign Transfer"); op } fn mk_change_key( sender_id: AccountId, old_sk: &SecretKey, new_pk: &PublicKey, prev_hash: mt_crypto::Hash32, ) -> ChangeKey { let mut op = ChangeKey { prev_hash, sender: sender_id, new_suite_id: SUITE_ID, new_pubkey: new_pk.clone(), signature: mt_crypto::Signature::from_array([0u8; mt_crypto::SIGNATURE_SIZE]), }; let mut scope = Vec::new(); op.encode_signed_scope(&mut scope); op.signature = sign(old_sk, &scope).expect("sign ChangeKey"); op } fn mk_anchor( sender_id: AccountId, sk: &SecretKey, app_id: [u8; 32], data_hash: [u8; 32], prev_hash: mt_crypto::Hash32, ) -> Anchor { let mut op = Anchor { prev_hash, sender: sender_id, app_id, data_hash, signature: mt_crypto::Signature::from_array([0u8; mt_crypto::SIGNATURE_SIZE]), }; let mut scope = Vec::new(); op.encode_signed_scope(&mut scope); op.signature = sign(sk, &scope).expect("sign Anchor"); op } // ---- subcommands ---- fn cmd_genesis_state() -> bool { print_section("GENESIS STATE — build_genesis_state(params) sanity"); print_note( "spec, раздел \"Genesis Decree\": bootstrap acc + bootstrap node creates initial state. node.chain_length = 1 (invariant ≥ 1).", ); let params = genesis_params(); let GenesisState { account_table, node_table, candidate_pool, } = build_genesis_state(params); print_subsection("Tables population"); let acc_count = account_table.len(); let node_count = node_table.len(); let cand_count = candidate_pool.len(); print_kv("AccountTable rows", format!("{acc_count}")); print_kv("NodeTable rows", format!("{node_count}")); print_kv("CandidatePool rows", format!("{cand_count}")); print_subsection("Bootstrap account"); let bootstrap_acct_id = derive_account_id( mt_account::GENESIS_SUITE_ID, ¶ms.bootstrap_account_pubkey, ); let acct = account_table .get(&bootstrap_acct_id) .expect("bootstrap account in table"); print_kv("account_id", hex_full(&acct.account_id)); print_kv("balance", format!("{}", acct.balance)); print_kv("is_node_operator", format!("{}", acct.is_node_operator)); print_kv( "account_chain_length", format!("{}", acct.account_chain_length), ); print_subsection("Bootstrap node"); let node_id_expected = mt_state::derive_node_id(¶ms.bootstrap_node_pubkey); let node = node_table.get(&node_id_expected).expect("bootstrap node"); print_kv("node_id", hex_full(&node.node_id)); print_kv("chain_length", format!("{}", node.chain_length)); print_kv("operator_account_id", hex_full(&node.operator_account_id)); print_subsection("genesis_state_root determinism"); let genesis_state = build_genesis_state(params); let r1 = genesis_state_root(&genesis_state); let r2 = genesis_state_root(&build_genesis_state(params)); print_kv("root run #1", hex_full(&r1)); print_kv("root run #2", hex_full(&r2)); let det = r1 == r2; print_kv("byte-exact determinism", format!("{det}")); let pass = acc_count == 1 && node_count == 1 && cand_count == 0 && acct.is_node_operator && node.chain_length == 1 && node.operator_account_id == acct.account_id && det; println!( "\n[result] GENESIS-STATE: {}", if pass { "PASS" } else { "FAIL" } ); pass } fn cmd_open_account() -> bool { print_section("OPEN ACCOUNT — TransferActivation от sponsor создаёт receiver"); print_note( "spec, разделы \"TransferActivation\" + \"Account Chain (Block Lattice)\": sender balance -= amount, receiver = новый AccountRecord с balance = amount, frontier_hash receiver = 0x00 ... (receiver chain genesis), sender frontier_hash = op_hash.", ); let mut state = AccountTable::new(); let (sponsor_pk, sponsor_sk) = kp(0x01); let (alice_pk, _alice_sk) = kp(0x02); let sponsor_id = synthetic_sponsor(&mut state, &sponsor_pk, 1_000_000); let sponsor_frontier = state.get(&sponsor_id).unwrap().frontier_hash; print_subsection("Pre-state"); print_kv("sponsor balance", "1_000_000"); print_kv("alice balance", "—"); print_kv("sponsor frontier", hex_full(&sponsor_frontier)); let op = mk_transfer_activation( sponsor_id, &sponsor_sk, &alice_pk, 100_000, sponsor_frontier, ); let opc = Operation::TransferActivation(op.clone()); print_subsection("Operation"); print_kv("amount", format!("{}", op.amount)); print_kv("receiver_pk(8B)", pk8(&alice_pk)); let h = op_hash(&opc); print_kv("op_hash", hex_full(&h)); let h2 = op_hash(&opc); print_kv("op_hash recompute", hex_full(&h2)); print_kv( "op_hash deterministic (R2: identifier(op) = SHA-256(\"mt-op\" || signed_scope))", format!("{}", h == h2), ); print_subsection("Validate"); let r = validate_transfer_activation(&op, &state, /*W*/ 1000, /*τ₂*/ 20160); print_kv("validate", format!("{r:?}")); if r.is_err() { println!("\n[result] OPEN-ACCOUNT: FAIL"); return false; } apply(&opc, &mut state, /*W*/ 1000); print_subsection("Post-state"); let sponsor_after = state.get(&sponsor_id).unwrap(); let alice_id = derive_account_id(SUITE_ID, alice_pk.as_bytes()); let alice_after = state.get(&alice_id).expect("alice created"); print_kv("sponsor balance", format!("{}", sponsor_after.balance)); print_kv("alice balance", format!("{}", alice_after.balance)); print_kv( "sponsor account_chain_length", format!("{}", sponsor_after.account_chain_length), ); print_kv( "alice account_chain_length", format!("{}", alice_after.account_chain_length), ); print_kv( "sponsor frontier (new)", hex_full(&sponsor_after.frontier_hash), ); print_kv( "alice frontier (genesis)", hex_full(&alice_after.frontier_hash), ); let pass = sponsor_after.balance == 900_000 && alice_after.balance == 100_000 && sponsor_after.account_chain_length == 1 && alice_after.account_chain_length == 0 && alice_after.last_activation_window == 0 && sponsor_after.last_activation_window == 1000; println!( "\n[result] OPEN-ACCOUNT: {}", if pass { "PASS" } else { "FAIL" } ); pass } fn cmd_transfer_scenario() -> bool { print_section("TRANSFER SCENARIO — sponsor → A → B → Transfer A→B 50%"); print_note( "Полная цепочка: 2× TransferActivation (создаём A, B), затем Transfer A→B. Σ delta_balance = 0 после каждого шага. spec, разделы \"TransferActivation\" + \"Transfer\".", ); let mut state = AccountTable::new(); let (sponsor_pk, sponsor_sk) = kp(0x10); let (alice_pk, alice_sk) = kp(0x11); let (bob_pk, _bob_sk) = kp(0x12); let sponsor_id = synthetic_sponsor(&mut state, &sponsor_pk, 1_000_000); // Шаг 1: sponsor → alice (создание). print_subsection("Step 1: sponsor → alice (TransferActivation 200_000), W=100"); let f1 = state.get(&sponsor_id).unwrap().frontier_hash; let op1 = mk_transfer_activation(sponsor_id, &sponsor_sk, &alice_pk, 200_000, f1); validate_transfer_activation(&op1, &state, 100, 20160).expect("step 1 validate"); apply(&Operation::TransferActivation(op1.clone()), &mut state, 100); let alice_id = derive_account_id(SUITE_ID, alice_pk.as_bytes()); print_kv( "sponsor balance", format!("{}", state.get(&sponsor_id).unwrap().balance), ); print_kv( "alice balance", format!("{}", state.get(&alice_id).unwrap().balance), ); // Шаг 2: sponsor → bob (создание). Cooldown τ₂ — поэтому W=100+20160=20260. print_subsection( "Step 2: sponsor → bob (TransferActivation 100_000), W=20260 (после cooldown τ₂)", ); let f2 = state.get(&sponsor_id).unwrap().frontier_hash; let op2 = mk_transfer_activation(sponsor_id, &sponsor_sk, &bob_pk, 100_000, f2); validate_transfer_activation(&op2, &state, 20260, 20160).expect("step 2 validate"); apply( &Operation::TransferActivation(op2.clone()), &mut state, 20260, ); let bob_id = derive_account_id(SUITE_ID, bob_pk.as_bytes()); print_kv( "sponsor balance", format!("{}", state.get(&sponsor_id).unwrap().balance), ); print_kv( "bob balance", format!("{}", state.get(&bob_id).unwrap().balance), ); // Шаг 3: Alice → Bob, 50% balance Alice = 100_000. print_subsection("Step 3: Alice → Bob (Transfer 100_000)"); let alice_frontier = state.get(&alice_id).unwrap().frontier_hash; let alice_balance_before = state.get(&alice_id).unwrap().balance; let bob_balance_before = state.get(&bob_id).unwrap().balance; let op3 = mk_transfer(alice_id, &alice_sk, bob_id, 100_000, alice_frontier); let validate_r = validate_transfer(&op3, &state); print_kv("validate Transfer", format!("{validate_r:?}")); apply(&Operation::Transfer(op3.clone()), &mut state, 20300); let alice_after = state.get(&alice_id).unwrap(); let bob_after = state.get(&bob_id).unwrap(); print_kv("alice balance", format!("{}", alice_after.balance)); print_kv("bob balance", format!("{}", bob_after.balance)); print_kv( "alice account_chain_length", format!("{}", alice_after.account_chain_length), ); print_kv("alice frontier_hash", hex_full(&alice_after.frontier_hash)); print_subsection("Conservation: Σ delta_balance == 0 для Transfer step"); let alice_delta: i128 = alice_after.balance as i128 - alice_balance_before as i128; let bob_delta: i128 = bob_after.balance as i128 - bob_balance_before as i128; let total_delta = alice_delta + bob_delta; print_kv("alice delta", format!("{alice_delta}")); print_kv("bob delta", format!("{bob_delta}")); print_kv("total delta (Σ)", format!("{total_delta}")); let pass = validate_r.is_ok() && alice_after.balance == 100_000 && bob_after.balance == 200_000 && alice_after.account_chain_length == 1 && bob_after.account_chain_length == 0 && alice_delta == -100_000 && bob_delta == 100_000 && total_delta == 0; println!( "\n[result] TRANSFER-SCENARIO: {}", if pass { "PASS" } else { "FAIL" } ); pass } fn cmd_change_key() -> bool { print_section("CHANGE KEY — rotate pubkey, signature transition"); print_note( "spec, раздел \"ChangeKey\": op подписан СТАРЫМ ключом (current_pubkey в state до apply). После apply: state.current_pubkey = new_pubkey. Следующая op подписывается новым ключом.", ); let mut state = AccountTable::new(); let (sponsor_pk, sponsor_sk_old) = kp(0x20); let (new_pk, sponsor_sk_new) = kp(0x21); let acc_id = synthetic_sponsor(&mut state, &sponsor_pk, 500_000); let f0 = state.get(&acc_id).unwrap().frontier_hash; print_subsection("Pre-state"); print_kv("current_pubkey (old, 8B)", pk8(&sponsor_pk)); print_subsection("ChangeKey signed by OLD sk"); let op_change = mk_change_key(acc_id, &sponsor_sk_old, &new_pk, f0); let r1 = validate_change_key(&op_change, &state); print_kv("validate (old sk → ok)", format!("{r1:?}")); apply(&Operation::ChangeKey(op_change.clone()), &mut state, 500); let after_change = state.get(&acc_id).unwrap(); print_kv( "current_pubkey now (8B)", pk8(&PublicKey::from_array(after_change.current_pubkey)), ); let pk_rotated = after_change.current_pubkey == *new_pk.as_bytes(); print_subsection("Anchor подписан НОВЫМ sk → должен валидироваться"); let f1 = after_change.frontier_hash; let op_anchor_new = mk_anchor(acc_id, &sponsor_sk_new, [0xAB; 32], [0xCD; 32], f1); let r2 = validate_anchor(&op_anchor_new, &state); print_kv("validate (new sk)", format!("{r2:?}")); print_subsection("Anchor подписан СТАРЫМ sk → должен быть отвергнут"); let op_anchor_old = mk_anchor(acc_id, &sponsor_sk_old, [0xAB; 32], [0xCD; 32], f1); let r3 = validate_anchor(&op_anchor_old, &state); print_kv("validate (old sk)", format!("{r3:?}")); let old_rejected = matches!(r3, Err(OpError::InvalidSignature)); let pass = r1.is_ok() && pk_rotated && r2.is_ok() && old_rejected; println!( "\n[result] CHANGE-KEY: {}", if pass { "PASS" } else { "FAIL" } ); pass } fn cmd_anchor() -> bool { print_section("ANCHOR — chain payload hashes"); print_note( "spec, раздел \"Anchor\": user opcode фиксирует hash off-chain контента в chain. Multiple anchors образуют chain через frontier_hash линковку.", ); let mut state = AccountTable::new(); let (pk, sk) = kp(0x30); let acc_id = synthetic_sponsor(&mut state, &pk, 100_000); let mut prev = state.get(&acc_id).unwrap().frontier_hash; let mut applied_count = 0u32; for i in 1u8..=3 { print_subsection(&format!("Anchor #{i}")); let op = mk_anchor(acc_id, &sk, [i; 32], [i ^ 0xAA; 32], prev); let r = validate_anchor(&op, &state); print_kv("validate", format!("{r:?}")); if r.is_err() { break; } apply( &Operation::Anchor(op.clone()), &mut state, u64::from(i) * 10, ); let after = state.get(&acc_id).unwrap(); print_kv("frontier_hash", hex_full(&after.frontier_hash)); print_kv( "account_chain_length", format!("{}", after.account_chain_length), ); prev = after.frontier_hash; applied_count += 1; } let final_state = state.get(&acc_id).unwrap(); let pass = applied_count == 3 && final_state.account_chain_length == 3 && final_state.balance == 100_000; println!("\n[result] ANCHOR: {}", if pass { "PASS" } else { "FAIL" }); pass } fn cmd_emission_schedule(windows: &[u64]) -> bool { print_section("EMISSION SCHEDULE — reward + supply ledger"); print_note( "spec, раздел \"Эмиссия\": reward_moneta = EMISSION_moneta = 13 Ɉ const per window. supply_moneta(W) = (W+1) × EMISSION_moneta.", ); let params = genesis_params(); let reward = reward_moneta(params); print_kv("EMISSION_moneta (per window)", format!("{reward} nɈ")); let mut all_match = true; print_subsection("Schedule"); for &w in windows { let supply = supply_moneta(w, params); let expected = u128::from(w + 1) * reward; let ok = supply == expected; print_kv( &format!("W = {w:8}"), format!( "supply = {supply:20} nɈ (expected {expected}) {}", if ok { "✓" } else { "✗" } ), ); if !ok { all_match = false; } } print_subsection("Linearity sanity"); let s_a = supply_moneta(1000, params); let s_b = supply_moneta(2000, params); let diff = s_b - s_a; let expected_diff = u128::from(1000u64) * reward; print_kv("supply(2000) - supply(1000)", format!("{diff} nɈ")); print_kv("expected (1000 × reward)", format!("{expected_diff}")); let lin_ok = diff == expected_diff; let pass = all_match && lin_ok && reward > 0; println!( "\n[result] EMISSION-SCHEDULE: {}", if pass { "PASS" } else { "FAIL" } ); pass } fn cmd_validation_rejects() -> bool { print_section("VALIDATION REJECTS — 10 adversarial cases"); print_note( "Десять OpError cases должны быть REJECTED detection правилами validate_*. spec, разделы \"Account Chain (Block Lattice) → Верификация\" + \"Anti-spam\".", ); let mut state = AccountTable::new(); let (alice_pk, alice_sk) = kp(0x40); let (bob_pk, bob_sk) = kp(0x41); let (charlie_pk, _charlie_sk) = kp(0x42); let (dave_pk, _dave_sk) = kp(0x43); let alice_id = synthetic_sponsor(&mut state, &alice_pk, 500_000); let bob_id = synthetic_sponsor(&mut state, &bob_pk, 100_000); let alice_frontier = state.get(&alice_id).unwrap().frontier_hash; let mut all_correctly_rejected = true; fn report(label: &str, expected: &str, actual: &Result<(), OpError>, matches: bool) { print_kv("validate", format!("{actual:?}")); print_kv("ожидание", format!("{expected} — правило: {label}")); print_kv( "verdict", if matches { "REJECTED ✓" } else { "ACCEPTED ✗" }, ); } // 1. InsufficientBalance. print_subsection("1. InsufficientBalance — Transfer amount > sender balance"); let op = mk_transfer(alice_id, &alice_sk, bob_id, 1_000_000, alice_frontier); let r = validate_transfer(&op, &state); let m = matches!(r, Err(OpError::InsufficientBalance)); report( "amount=1_000_000 > balance=500_000", "InsufficientBalance", &r, m, ); if !m { all_correctly_rejected = false; } // 2. SelfTransfer. print_subsection("2. SelfTransfer — sender == link"); let op = mk_transfer(alice_id, &alice_sk, alice_id, 100, alice_frontier); let r = validate_transfer(&op, &state); let m = matches!(r, Err(OpError::SelfTransfer)); report("Alice → Alice", "SelfTransfer", &r, m); if !m { all_correctly_rejected = false; } // 3. ZeroAmount. print_subsection("3. ZeroAmount — Transfer amount = 0"); let op = mk_transfer(alice_id, &alice_sk, bob_id, 0, alice_frontier); let r = validate_transfer(&op, &state); let m = matches!(r, Err(OpError::ZeroAmount)); report("amount=0", "ZeroAmount", &r, m); if !m { all_correctly_rejected = false; } // 4. InvalidPrevHash. print_subsection("4. InvalidPrevHash — prev_hash != sender.frontier_hash"); let op = mk_transfer(alice_id, &alice_sk, bob_id, 100, [0xFF; 32]); let r = validate_transfer(&op, &state); let m = matches!(r, Err(OpError::InvalidPrevHash)); report("stale prev_hash 0xFF×32", "InvalidPrevHash", &r, m); if !m { all_correctly_rejected = false; } // 5. InvalidSignature. print_subsection("5. InvalidSignature — подпись чужим sk"); let op = mk_transfer(alice_id, &bob_sk, bob_id, 100, alice_frontier); let r = validate_transfer(&op, &state); let m = matches!(r, Err(OpError::InvalidSignature)); report("Alice's op подписан Bob's sk", "InvalidSignature", &r, m); if !m { all_correctly_rejected = false; } // 6. ReceiverAlreadyExists. print_subsection("6. ReceiverAlreadyExists — TransferActivation для уже-existing receiver"); let op = mk_transfer_activation(alice_id, &alice_sk, &bob_pk, 100, alice_frontier); let r = validate_transfer_activation(&op, &state, 5000, 20160); let m = matches!(r, Err(OpError::ReceiverAlreadyExists)); report("Bob уже в AccountTable", "ReceiverAlreadyExists", &r, m); if !m { all_correctly_rejected = false; } // 7. ActivationCooldownNotElapsed. print_subsection("7. ActivationCooldownNotElapsed — второй TransferActivation до τ₂"); let mut state_cd = state.clone(); let f0 = state_cd.get(&alice_id).unwrap().frontier_hash; let op_first = mk_transfer_activation(alice_id, &alice_sk, &charlie_pk, 100, f0); apply(&Operation::TransferActivation(op_first), &mut state_cd, 100); let f1 = state_cd.get(&alice_id).unwrap().frontier_hash; let op_second = mk_transfer_activation(alice_id, &alice_sk, &dave_pk, 100, f1); let r = validate_transfer_activation(&op_second, &state_cd, 200, 20160); let m = matches!(r, Err(OpError::ActivationCooldownNotElapsed)); report( "apply at W=100, validate at W=200 < 100+τ₂", "ActivationCooldownNotElapsed", &r, m, ); if !m { all_correctly_rejected = false; } // 8. UnsupportedSuite. print_subsection("8. UnsupportedSuite — TransferActivation с suite_id = 0xFFFF"); let receiver_id_bad = derive_account_id(0xFFFF, dave_pk.as_bytes()); let mut op = TransferActivation { prev_hash: alice_frontier, sender: alice_id, receiver: receiver_id_bad, suite_id: 0xFFFF, receiver_pubkey: dave_pk.clone(), amount: 100, signature: mt_crypto::Signature::from_array([0u8; mt_crypto::SIGNATURE_SIZE]), }; let mut scope = Vec::new(); op.encode_signed_scope(&mut scope); op.signature = sign(&alice_sk, &scope).expect("sign 0xFFFF suite"); let r = validate_transfer_activation(&op, &state, 5000, 20160); let m = matches!(r, Err(OpError::UnsupportedSuite)); report( "suite_id = 0xFFFF не зарегистрирован", "UnsupportedSuite", &r, m, ); if !m { all_correctly_rejected = false; } // 9. AccountNotFound. print_subsection("9. AccountNotFound — Transfer от account_id не в AccountTable"); let phantom_id: AccountId = [0xDE; 32]; let op = mk_transfer(phantom_id, &alice_sk, bob_id, 100, alice_frontier); let r = validate_transfer(&op, &state); let m = matches!(r, Err(OpError::AccountNotFound)); report("phantom sender 0xDE×32", "AccountNotFound", &r, m); if !m { all_correctly_rejected = false; } // 10. InvalidBinding. print_subsection("10. InvalidBinding — receiver_id не derived из (suite_id, receiver_pubkey)"); let mut op = TransferActivation { prev_hash: alice_frontier, sender: alice_id, receiver: [0x00; 32], suite_id: SUITE_ID, receiver_pubkey: dave_pk.clone(), amount: 100, signature: mt_crypto::Signature::from_array([0u8; mt_crypto::SIGNATURE_SIZE]), }; let mut scope = Vec::new(); op.encode_signed_scope(&mut scope); op.signature = sign(&alice_sk, &scope).expect("sign bad binding"); let r = validate_transfer_activation(&op, &state, 5000, 20160); let m = matches!(r, Err(OpError::InvalidBinding)); report( "receiver = 0x00×32 ≠ derive_account_id(suite, pk)", "InvalidBinding", &r, m, ); if !m { all_correctly_rejected = false; } // Sanity positive. print_subsection("Sanity positive — legitimate TransferActivation Alice → Charlie"); let op = mk_transfer_activation(alice_id, &alice_sk, &charlie_pk, 100, alice_frontier); let r = validate_transfer_activation(&op, &state, 5000, 20160); print_kv("validate", format!("{r:?}")); let positive_ok = r.is_ok(); print_kv( "verdict", if positive_ok { "ACCEPTED ✓" } else { "REJECTED ✗" }, ); let pass = all_correctly_rejected && positive_ok; println!( "\n[result] VALIDATION-REJECTS: {}", if pass { "PASS" } else { "FAIL" } ); pass } fn cmd_all() -> bool { print_section("M3 ACCOUNT — FULL SHAKEDOWN"); let a = cmd_genesis_state(); let b = cmd_open_account(); let c = cmd_transfer_scenario(); let d = cmd_change_key(); let e = cmd_anchor(); let f = cmd_emission_schedule(&[0, 100, 1000, 20160, 1_000_000]); let g = cmd_validation_rejects(); print_section("SUMMARY"); print_kv("genesis-state", if a { "PASS" } else { "FAIL" }); print_kv("open-account", if b { "PASS" } else { "FAIL" }); print_kv("transfer-scenario", if c { "PASS" } else { "FAIL" }); print_kv("change-key", if d { "PASS" } else { "FAIL" }); print_kv("anchor", if e { "PASS" } else { "FAIL" }); print_kv("emission-schedule", if f { "PASS" } else { "FAIL" }); print_kv("validation-rejects", if g { "PASS" } else { "FAIL" }); let pass = a && b && c && d && e && f && g; println!( "\n[result] ALL SCENARIOS: {}", if pass { "PASS" } else { "FAIL" } ); pass } fn bool_to_exit(pass: bool) -> ExitCode { if pass { ExitCode::SUCCESS } else { ExitCode::FAILURE } } fn usage() { eprintln!("M3 — Account operations shakedown"); eprintln!(); eprintln!("usage: m3_account [args]"); eprintln!(); eprintln!(" genesis-state build_genesis_state(params): bootstrap acc + node"); eprintln!(" open-account TransferActivation: sponsor → new account"); eprintln!(" transfer-scenario sponsor → A → B → Transfer A→B"); eprintln!(" change-key ChangeKey: pk rotation + signature transition"); eprintln!(" anchor Anchor: chain payload hashes"); eprintln!(" emission-schedule [W ...] reward + supply ledger"); eprintln!(" validation-rejects 6 OpError adversarial cases"); eprintln!(" all Прогнать все 7"); eprintln!(); eprintln!("Exit code 0=PASS, 1=FAIL."); } fn main() -> ExitCode { let args: Vec = env::args().collect(); let sub = match args.get(1) { Some(s) => s.as_str(), None => { usage(); return ExitCode::FAILURE; }, }; let pass = match sub { "genesis-state" => cmd_genesis_state(), "open-account" => cmd_open_account(), "transfer-scenario" => cmd_transfer_scenario(), "change-key" => cmd_change_key(), "anchor" => cmd_anchor(), "emission-schedule" => { let ws: Vec = args[2..].iter().filter_map(|s| s.parse().ok()).collect(); let default = vec![0u64, 100, 1000, 20160, 1_000_000]; let use_args: &[u64] = if ws.is_empty() { &default } else { &ws }; cmd_emission_schedule(use_args) }, "validation-rejects" => cmd_validation_rejects(), "all" => cmd_all(), _ => { usage(); return ExitCode::FAILURE; }, }; bool_to_exit(pass) }