450 lines
18 KiB
Rust
450 lines
18 KiB
Rust
|
|
// M2 — TimeChain + State shakedown.
|
|||
|
|
// Subcommands:
|
|||
|
|
// vdf-forward [N] VDF forward N шагов, byte-exact match с manual SHA-256^N
|
|||
|
|
// next-d-boundaries Adaptive D: 7 binding test vectors из спеки
|
|||
|
|
// cba-branches cemented_bundle_aggregate: три ветви (W<2 / empty / non-empty)
|
|||
|
|
// state-root-compose state_root = SHA-256("mt-state-root" || node || cand || acct)
|
|||
|
|
// merkle-inclusion SparseMerkleTree: prove → verify (existence + absence)
|
|||
|
|
// all Прогнать все 5
|
|||
|
|
//
|
|||
|
|
// Exit code 0=PASS, 1=FAIL.
|
|||
|
|
|
|||
|
|
use std::env;
|
|||
|
|
use std::process::ExitCode;
|
|||
|
|
|
|||
|
|
use mt_codec::domain;
|
|||
|
|
use mt_crypto::{hash, sha256_raw, Hash32, PUBLIC_KEY_SIZE};
|
|||
|
|
use mt_examples::{hex_full, print_kv, print_note, print_section, print_subsection};
|
|||
|
|
use mt_genesis::genesis_params;
|
|||
|
|
use mt_merkle::{verify_proof, SparseMerkleTree};
|
|||
|
|
use mt_state::{
|
|||
|
|
compute_state_root, AccountId, AccountRecord, AccountTable, CandidatePool, CandidateRecord,
|
|||
|
|
NodeId, NodeRecord, NodeTable,
|
|||
|
|
};
|
|||
|
|
use mt_timechain::{cemented_bundle_aggregate, next_d, vdf_step};
|
|||
|
|
|
|||
|
|
// VDF forward через единственный vdf_step(prev, N) vs ручной N-кратный SHA-256.
|
|||
|
|
fn cmd_vdf_forward(steps: u64) -> bool {
|
|||
|
|
print_section(&format!(
|
|||
|
|
"VDF FORWARD — vdf_step(prev, {steps}) vs manual SHA-256^{steps}"
|
|||
|
|
));
|
|||
|
|
print_note(
|
|||
|
|
"spec, раздел \"TimeChain\": T_r = SHA-256^D(T_{r-1}). vdf_step(prev, D) применяет ровно D одиночных SHA-256.",
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
let prev: Hash32 = [0u8; 32];
|
|||
|
|
print_kv("T_r_0 (genesis seed)", hex_full(&prev));
|
|||
|
|
print_kv("D", format!("{steps}"));
|
|||
|
|
|
|||
|
|
print_subsection("1. vdf_step(prev, D) → T_r_D");
|
|||
|
|
let t_r_batch = vdf_step(&prev, steps);
|
|||
|
|
print_kv("T_r_D (batch)", hex_full(&t_r_batch));
|
|||
|
|
|
|||
|
|
print_subsection("2. Manual: prev → SHA-256 → SHA-256 → ... ровно D раз");
|
|||
|
|
let mut manual: Hash32 = prev;
|
|||
|
|
for _ in 0..steps {
|
|||
|
|
manual = sha256_raw(&manual);
|
|||
|
|
}
|
|||
|
|
print_kv("T_r_D (manual)", hex_full(&manual));
|
|||
|
|
|
|||
|
|
let equal = t_r_batch == manual;
|
|||
|
|
print_subsection("3. Byte-exact equality");
|
|||
|
|
print_kv("vdf_step == SHA-256^D", format!("{equal}"));
|
|||
|
|
|
|||
|
|
print_subsection("4. Determinism: повторный вызов vdf_step(prev, D) → тот же T_r_D");
|
|||
|
|
let t_r_again = vdf_step(&prev, steps);
|
|||
|
|
let det = t_r_batch == t_r_again;
|
|||
|
|
print_kv("vdf_step idempotent", format!("{det}"));
|
|||
|
|
|
|||
|
|
print_subsection("5. Preimage resistance: D=0 → identity; D=1 → один SHA-256");
|
|||
|
|
let d0 = vdf_step(&prev, 0);
|
|||
|
|
let d1 = vdf_step(&prev, 1);
|
|||
|
|
let single_sha = sha256_raw(&prev);
|
|||
|
|
let d0_id = d0 == prev;
|
|||
|
|
let d1_match = d1 == single_sha;
|
|||
|
|
print_kv("vdf_step(prev, 0) == prev", format!("{d0_id}"));
|
|||
|
|
print_kv("vdf_step(prev, 1) == SHA-256(prev)", format!("{d1_match}"));
|
|||
|
|
|
|||
|
|
let pass = equal && det && d0_id && d1_match;
|
|||
|
|
println!(
|
|||
|
|
"\n[result] VDF-FORWARD: {}",
|
|||
|
|
if pass { "PASS" } else { "FAIL" }
|
|||
|
|
);
|
|||
|
|
pass
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Adaptive D — 7 binding test vectors из спеки + sanity cases.
|
|||
|
|
fn cmd_next_d_boundaries() -> bool {
|
|||
|
|
print_section("ADAPTIVE D — next_d boundary cases");
|
|||
|
|
print_note(
|
|||
|
|
"spec, раздел \"Adaptive D\" (Integer form, [I-9]): high_permille=950, low_permille=850, rate=3/100. high/low — inclusive boundaries.",
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
let params = genesis_params();
|
|||
|
|
print_kv("high_permille", "950");
|
|||
|
|
print_kv("low_permille", "850");
|
|||
|
|
print_kv("rate", "3/100");
|
|||
|
|
|
|||
|
|
// (median_permille, expected_D_at_D_old=1000, comment)
|
|||
|
|
let cases: [(u32, u64, &str); 7] = [
|
|||
|
|
(1000, 1030, "100% — выше high, +3%"),
|
|||
|
|
(950, 1030, "950 = high_permille edge, inclusive → +3%"),
|
|||
|
|
(980, 1030, "980 — выше high, +3%"),
|
|||
|
|
(900, 1000, "dead zone middle — без изменения"),
|
|||
|
|
(
|
|||
|
|
851,
|
|||
|
|
1000,
|
|||
|
|
"851 — dead zone верхний край (>low), без изменения",
|
|||
|
|
),
|
|||
|
|
(850, 970, "850 = low_permille edge, inclusive → -3%"),
|
|||
|
|
(700, 970, "700 — глубоко ниже low, -3%"),
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
let mut all_match = true;
|
|||
|
|
for (median, expected, comment) in &cases {
|
|||
|
|
let actual = next_d(1000, *median, params);
|
|||
|
|
let match_ok = actual == *expected;
|
|||
|
|
print_kv(
|
|||
|
|
&format!("median={median:4} permille"),
|
|||
|
|
format!("D_new = {actual:5} (expected {expected}) — {comment}"),
|
|||
|
|
);
|
|||
|
|
if !match_ok {
|
|||
|
|
all_match = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print_subsection("Sanity: D_old=2_000_000_000, median=1000 → D_old × 103/100 без overflow");
|
|||
|
|
let big = next_d(2_000_000_000, 1000, params);
|
|||
|
|
let big_expected: u64 = 2_000_000_000u64 * 103 / 100;
|
|||
|
|
print_kv("D_new", format!("{big}"));
|
|||
|
|
print_kv("expected", format!("{big_expected}"));
|
|||
|
|
let big_ok = big == big_expected;
|
|||
|
|
|
|||
|
|
let pass = all_match && big_ok;
|
|||
|
|
println!(
|
|||
|
|
"\n[result] NEXT-D-BOUNDARIES: {}",
|
|||
|
|
if pass { "PASS" } else { "FAIL" }
|
|||
|
|
);
|
|||
|
|
pass
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// cemented_bundle_aggregate: три ветви.
|
|||
|
|
fn cmd_cba_branches() -> bool {
|
|||
|
|
print_section("CEMENTED BUNDLE AGGREGATE — три ветви ([I-8] anti-grinding binding)");
|
|||
|
|
print_note(
|
|||
|
|
"spec строки 2080-2089: W<2 → 0x00×32; |cemented|==0 → SHA-256(\"mt-bc-aggregate-empty\" || W_le8); иначе → SHA-256(\"mt-bc-aggregate\" || concat(sorted node_ids) || W_le8).",
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Ветвь 1: W < 2 → zeros.
|
|||
|
|
print_subsection("Branch 1: W=0, W=1 → 0x00 × 32");
|
|||
|
|
let cba_w0 = cemented_bundle_aggregate(0, &[]);
|
|||
|
|
let cba_w1 = cemented_bundle_aggregate(1, &[[0xAB; 32]]); // даже с node_ids — zeros (W<2)
|
|||
|
|
print_kv("cba(W=0, [])", hex_full(&cba_w0));
|
|||
|
|
print_kv("cba(W=1, [one_node])", hex_full(&cba_w1));
|
|||
|
|
let zeros: Hash32 = [0u8; 32];
|
|||
|
|
let b1_pass = cba_w0 == zeros && cba_w1 == zeros;
|
|||
|
|
print_kv("== 0x00 × 32", format!("{b1_pass}"));
|
|||
|
|
|
|||
|
|
// Ветвь 2: empty cemented set, W>=2.
|
|||
|
|
print_subsection("Branch 2: W=5, |cemented|==0 → SHA-256(\"mt-bc-aggregate-empty\" || W_le8)");
|
|||
|
|
let w: u64 = 5;
|
|||
|
|
let cba_empty = cemented_bundle_aggregate(w, &[]);
|
|||
|
|
let manual_empty: Hash32 = hash(domain::BC_AGGREGATE_EMPTY, &[&w.to_le_bytes()]);
|
|||
|
|
print_kv("cba(W=5, [])", hex_full(&cba_empty));
|
|||
|
|
print_kv("manual hash", hex_full(&manual_empty));
|
|||
|
|
let b2_pass = cba_empty == manual_empty;
|
|||
|
|
print_kv("byte-exact match", format!("{b2_pass}"));
|
|||
|
|
|
|||
|
|
// Ветвь 3: non-empty, W=10, два node_id.
|
|||
|
|
print_subsection(
|
|||
|
|
"Branch 3: W=10, два node_id (ascending sort) → SHA-256(\"mt-bc-aggregate\" || concat(sorted) || W_le8)",
|
|||
|
|
);
|
|||
|
|
let w3: u64 = 10;
|
|||
|
|
let id_a: NodeId = [0x11; 32];
|
|||
|
|
let id_b: NodeId = [0x22; 32];
|
|||
|
|
// Передаём в обратном порядке — функция должна сама отсортировать.
|
|||
|
|
let cba_pair_unsorted = cemented_bundle_aggregate(w3, &[id_b, id_a]);
|
|||
|
|
let cba_pair_sorted = cemented_bundle_aggregate(w3, &[id_a, id_b]);
|
|||
|
|
print_kv("cba(W=10, [B, A])", hex_full(&cba_pair_unsorted));
|
|||
|
|
print_kv("cba(W=10, [A, B])", hex_full(&cba_pair_sorted));
|
|||
|
|
let order_invariant = cba_pair_unsorted == cba_pair_sorted;
|
|||
|
|
print_kv(
|
|||
|
|
"order-invariant (sorted сама)",
|
|||
|
|
format!("{order_invariant}"),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Ручной расчёт.
|
|||
|
|
let mut concat: Vec<u8> = Vec::with_capacity(64);
|
|||
|
|
concat.extend_from_slice(&id_a);
|
|||
|
|
concat.extend_from_slice(&id_b);
|
|||
|
|
let manual_pair: Hash32 = hash(domain::BC_AGGREGATE, &[&concat, &w3.to_le_bytes()]);
|
|||
|
|
print_kv("manual hash", hex_full(&manual_pair));
|
|||
|
|
let manual_match = cba_pair_sorted == manual_pair;
|
|||
|
|
print_kv("byte-exact match", format!("{manual_match}"));
|
|||
|
|
|
|||
|
|
// Различие domain'ов: empty branch ≠ non-empty branch.
|
|||
|
|
print_subsection("Domain separation: empty branch ≠ non-empty branch для одного W");
|
|||
|
|
let cba_empty_w10 = cemented_bundle_aggregate(w3, &[]);
|
|||
|
|
let domain_distinct = cba_empty_w10 != cba_pair_sorted;
|
|||
|
|
print_kv("cba(10, []) ≠ cba(10, [A,B])", format!("{domain_distinct}"));
|
|||
|
|
|
|||
|
|
let pass = b1_pass && b2_pass && order_invariant && manual_match && domain_distinct;
|
|||
|
|
println!(
|
|||
|
|
"\n[result] CBA-BRANCHES: {}",
|
|||
|
|
if pass { "PASS" } else { "FAIL" }
|
|||
|
|
);
|
|||
|
|
pass
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Helpers: построить детерминированные тестовые записи.
|
|||
|
|
fn make_account(seed_byte: u8, account_id_byte: u8) -> AccountRecord {
|
|||
|
|
AccountRecord {
|
|||
|
|
account_id: [account_id_byte; 32],
|
|||
|
|
balance: 1_000_000_000_000_u128 + u128::from(seed_byte) * 1_000_000_u128,
|
|||
|
|
suite_id: 0x0001,
|
|||
|
|
is_node_operator: seed_byte == 1,
|
|||
|
|
frontier_hash: [seed_byte; 32],
|
|||
|
|
op_height: u32::from(seed_byte) * 7,
|
|||
|
|
account_chain_length: u32::from(seed_byte) * 11,
|
|||
|
|
account_chain_length_snapshot: u32::from(seed_byte) * 11,
|
|||
|
|
current_pubkey: [seed_byte; PUBLIC_KEY_SIZE],
|
|||
|
|
creation_window: u32::from(seed_byte) * 100,
|
|||
|
|
last_op_window: u32::from(seed_byte) * 100 + 5,
|
|||
|
|
last_activation_window: u32::from(seed_byte) * 100,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn make_node(seed_byte: u8, operator_id: AccountId) -> NodeRecord {
|
|||
|
|
NodeRecord {
|
|||
|
|
node_id: [seed_byte ^ 0x80; 32],
|
|||
|
|
node_pubkey: [seed_byte ^ 0x80; PUBLIC_KEY_SIZE],
|
|||
|
|
suite_id: 0x0001,
|
|||
|
|
operator_account_id: operator_id,
|
|||
|
|
start_window: u64::from(seed_byte) * 50,
|
|||
|
|
chain_length: u64::from(seed_byte) * 200,
|
|||
|
|
chain_length_snapshot: u64::from(seed_byte) * 200,
|
|||
|
|
chain_length_checkpoints: [u64::from(seed_byte) * 200; 6],
|
|||
|
|
last_confirmation_window: u64::from(seed_byte) * 200 + 10,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn make_candidate(seed_byte: u8, operator_id: AccountId) -> CandidateRecord {
|
|||
|
|
CandidateRecord {
|
|||
|
|
node_id: [seed_byte ^ 0xC0; 32],
|
|||
|
|
node_pubkey: [seed_byte ^ 0xC0; PUBLIC_KEY_SIZE],
|
|||
|
|
suite_id: 0x0001,
|
|||
|
|
operator_account_id: operator_id,
|
|||
|
|
proof_endpoint: [seed_byte; 32],
|
|||
|
|
w_start: u64::from(seed_byte) * 30,
|
|||
|
|
vdf_chain_length: u64::from(seed_byte) * 1000,
|
|||
|
|
registration_window: u64::from(seed_byte) * 30,
|
|||
|
|
expires: u64::from(seed_byte) * 30 + 60480,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn cmd_state_root_compose() -> bool {
|
|||
|
|
print_section("STATE ROOT COMPOSITION — 3 accounts + 2 nodes + 1 candidate");
|
|||
|
|
print_note(
|
|||
|
|
"spec строка 1269: state_root = SHA-256(\"mt-state-root\" || node_root || candidate_root || account_root). Order: node, candidate, account.",
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
let mut accounts = AccountTable::new();
|
|||
|
|
let acc1 = make_account(1, 0x01);
|
|||
|
|
let acc2 = make_account(2, 0x02);
|
|||
|
|
let acc3 = make_account(3, 0x03);
|
|||
|
|
accounts.insert(acc1.clone());
|
|||
|
|
accounts.insert(acc2.clone());
|
|||
|
|
accounts.insert(acc3.clone());
|
|||
|
|
|
|||
|
|
let mut nodes = NodeTable::new();
|
|||
|
|
let node1 = make_node(1, acc1.account_id);
|
|||
|
|
let node2 = make_node(2, acc2.account_id);
|
|||
|
|
nodes.insert(node1.clone());
|
|||
|
|
nodes.insert(node2.clone());
|
|||
|
|
|
|||
|
|
let mut candidates = CandidatePool::new();
|
|||
|
|
let cand1 = make_candidate(7, acc3.account_id);
|
|||
|
|
candidates.insert(cand1);
|
|||
|
|
|
|||
|
|
print_subsection("Sub-roots");
|
|||
|
|
let acct_root = accounts.root();
|
|||
|
|
let node_root = nodes.root();
|
|||
|
|
let cand_root = candidates.root();
|
|||
|
|
print_kv("account_root", hex_full(&acct_root));
|
|||
|
|
print_kv("node_root", hex_full(&node_root));
|
|||
|
|
print_kv("candidate_root", hex_full(&cand_root));
|
|||
|
|
|
|||
|
|
print_subsection("compute_state_root vs manual hash composition");
|
|||
|
|
let lib_root = compute_state_root(&node_root, &cand_root, &acct_root);
|
|||
|
|
let manual_root: Hash32 = hash(domain::STATE_ROOT, &[&node_root, &cand_root, &acct_root]);
|
|||
|
|
print_kv("compute_state_root", hex_full(&lib_root));
|
|||
|
|
print_kv("manual SHA-256", hex_full(&manual_root));
|
|||
|
|
let composition_ok = lib_root == manual_root;
|
|||
|
|
print_kv("byte-exact match", format!("{composition_ok}"));
|
|||
|
|
|
|||
|
|
print_subsection("Determinism: пересоздать таблицы заново → root байт-в-байт тот же");
|
|||
|
|
let mut accounts2 = AccountTable::new();
|
|||
|
|
accounts2.insert(make_account(3, 0x03));
|
|||
|
|
accounts2.insert(make_account(1, 0x01));
|
|||
|
|
accounts2.insert(make_account(2, 0x02));
|
|||
|
|
let acct_root2 = accounts2.root();
|
|||
|
|
let det_ok = acct_root == acct_root2;
|
|||
|
|
print_kv("insert-order invariance (BTreeMap)", format!("{det_ok}"));
|
|||
|
|
|
|||
|
|
print_subsection("Sub-root sensitivity: change one balance → state_root меняется");
|
|||
|
|
let mut accounts3 = AccountTable::new();
|
|||
|
|
let mut acc1_mut = acc1.clone();
|
|||
|
|
acc1_mut.balance += 1;
|
|||
|
|
accounts3.insert(acc1_mut);
|
|||
|
|
accounts3.insert(acc2.clone());
|
|||
|
|
accounts3.insert(acc3.clone());
|
|||
|
|
let mutated_root = compute_state_root(&node_root, &cand_root, &accounts3.root());
|
|||
|
|
let sensitive = mutated_root != lib_root;
|
|||
|
|
print_kv(
|
|||
|
|
"state_root изменился после balance += 1",
|
|||
|
|
format!("{sensitive}"),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
let pass = composition_ok && det_ok && sensitive;
|
|||
|
|
println!(
|
|||
|
|
"\n[result] STATE-ROOT-COMPOSE: {}",
|
|||
|
|
if pass { "PASS" } else { "FAIL" }
|
|||
|
|
);
|
|||
|
|
pass
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn cmd_merkle_inclusion() -> bool {
|
|||
|
|
print_section("MERKLE INCLUSION PROOF — sparse tree depth 256, prove + verify");
|
|||
|
|
print_note(
|
|||
|
|
"spec, раздел \"Sparse Merkle Tree\". InclusionProof = leaf_value + 256 siblings от bottom до top. verify_proof пересчитывает root.",
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
let mut tree = SparseMerkleTree::new();
|
|||
|
|
|
|||
|
|
// Один аккаунт + manual encode.
|
|||
|
|
let acc = make_account(7, 0x42);
|
|||
|
|
let mut serialized = Vec::with_capacity(2059);
|
|||
|
|
use mt_codec::CanonicalEncode;
|
|||
|
|
acc.encode(&mut serialized);
|
|||
|
|
print_kv("AccountRecord size", format!("{} bytes", serialized.len()));
|
|||
|
|
|
|||
|
|
tree.insert(acc.account_id, &serialized);
|
|||
|
|
let root = tree.root();
|
|||
|
|
print_kv("root (1 leaf)", hex_full(&root));
|
|||
|
|
|
|||
|
|
print_subsection("1. Prove existing key → verify_proof true");
|
|||
|
|
let proof = tree.prove(&acc.account_id, Some(&serialized));
|
|||
|
|
let exist_ok = verify_proof(&root, &proof);
|
|||
|
|
print_kv("verify_proof(root, proof)", format!("{exist_ok}"));
|
|||
|
|
|
|||
|
|
print_subsection("2. Tampered leaf → verify_proof false");
|
|||
|
|
let mut bad_serialized = serialized.clone();
|
|||
|
|
bad_serialized[0] ^= 0x01;
|
|||
|
|
let bad_proof = tree.prove(&acc.account_id, Some(&bad_serialized));
|
|||
|
|
let tampered_rejected = !verify_proof(&root, &bad_proof);
|
|||
|
|
print_kv(
|
|||
|
|
"verify(tampered_leaf) == false",
|
|||
|
|
format!("{tampered_rejected}"),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
print_subsection("3. Absence proof — несуществующий account_id");
|
|||
|
|
let absent_id: AccountId = [0xFE; 32];
|
|||
|
|
let absent_proof = tree.prove(&absent_id, None);
|
|||
|
|
let absent_ok = verify_proof(&root, &absent_proof);
|
|||
|
|
print_kv("verify_proof(absent)", format!("{absent_ok}"));
|
|||
|
|
|
|||
|
|
print_subsection("4. Cross-key fail: proof одного ключа не верифицируется против другого root");
|
|||
|
|
let mut tree2 = SparseMerkleTree::new();
|
|||
|
|
let acc_other = make_account(8, 0x55);
|
|||
|
|
let mut other_ser = Vec::with_capacity(2059);
|
|||
|
|
acc_other.encode(&mut other_ser);
|
|||
|
|
tree2.insert(acc_other.account_id, &other_ser);
|
|||
|
|
let other_root = tree2.root();
|
|||
|
|
let cross_rejected = !verify_proof(&other_root, &proof);
|
|||
|
|
print_kv(
|
|||
|
|
"verify(other_root, proof) == false",
|
|||
|
|
format!("{cross_rejected}"),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
let pass = exist_ok && tampered_rejected && absent_ok && cross_rejected;
|
|||
|
|
println!(
|
|||
|
|
"\n[result] MERKLE-INCLUSION: {}",
|
|||
|
|
if pass { "PASS" } else { "FAIL" }
|
|||
|
|
);
|
|||
|
|
pass
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn cmd_all() -> bool {
|
|||
|
|
print_section("M2 TIMECHAIN + STATE — FULL SHAKEDOWN");
|
|||
|
|
|
|||
|
|
let a = cmd_vdf_forward(1000);
|
|||
|
|
let b = cmd_next_d_boundaries();
|
|||
|
|
let c = cmd_cba_branches();
|
|||
|
|
let d = cmd_state_root_compose();
|
|||
|
|
let e = cmd_merkle_inclusion();
|
|||
|
|
|
|||
|
|
print_section("SUMMARY");
|
|||
|
|
print_kv("vdf-forward 1000", if a { "PASS" } else { "FAIL" });
|
|||
|
|
print_kv("next-d-boundaries", if b { "PASS" } else { "FAIL" });
|
|||
|
|
print_kv("cba-branches", if c { "PASS" } else { "FAIL" });
|
|||
|
|
print_kv("state-root-compose", if d { "PASS" } else { "FAIL" });
|
|||
|
|
print_kv("merkle-inclusion", if e { "PASS" } else { "FAIL" });
|
|||
|
|
|
|||
|
|
let pass = a && b && c && d && e;
|
|||
|
|
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!("M2 — TimeChain (VDF, next_d, CBA) + State (roots, merkle) shakedown");
|
|||
|
|
eprintln!();
|
|||
|
|
eprintln!("usage: m2_timechain_state <subcommand> [args]");
|
|||
|
|
eprintln!();
|
|||
|
|
eprintln!(" vdf-forward [N] VDF forward N шагов (default 1000) vs manual SHA-256^N");
|
|||
|
|
eprintln!(" next-d-boundaries Adaptive D — 7 binding test vectors из спеки");
|
|||
|
|
eprintln!(" cba-branches cemented_bundle_aggregate: три ветви");
|
|||
|
|
eprintln!(" state-root-compose state_root = SHA-256(\"mt-state-root\" || ...)");
|
|||
|
|
eprintln!(" merkle-inclusion SparseMerkleTree: prove → verify (existence + absence)");
|
|||
|
|
eprintln!(" all Прогнать все 5 подкоманд");
|
|||
|
|
eprintln!();
|
|||
|
|
eprintln!("Exit code 0=PASS, 1=FAIL.");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn main() -> ExitCode {
|
|||
|
|
let args: Vec<String> = env::args().collect();
|
|||
|
|
let sub = match args.get(1) {
|
|||
|
|
Some(s) => s.as_str(),
|
|||
|
|
None => {
|
|||
|
|
usage();
|
|||
|
|
return ExitCode::FAILURE;
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
let pass = match sub {
|
|||
|
|
"vdf-forward" => {
|
|||
|
|
let n: u64 = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(1000);
|
|||
|
|
cmd_vdf_forward(n)
|
|||
|
|
},
|
|||
|
|
"next-d-boundaries" => cmd_next_d_boundaries(),
|
|||
|
|
"cba-branches" => cmd_cba_branches(),
|
|||
|
|
"state-root-compose" => cmd_state_root_compose(),
|
|||
|
|
"merkle-inclusion" => cmd_merkle_inclusion(),
|
|||
|
|
"all" => cmd_all(),
|
|||
|
|
_ => {
|
|||
|
|
usage();
|
|||
|
|
return ExitCode::FAILURE;
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
bool_to_exit(pass)
|
|||
|
|
}
|