montana/Монтана-Протокол/Код/crates/mt-examples/examples/m1_crypto.rs

672 lines
25 KiB
Rust
Raw Normal View History

use std::env;
use std::process::ExitCode;
use std::time::Instant;
use mt_codec::domain;
use mt_crypto::{
hash, keypair, keypair_from_seed, sha256_raw, sign, verify, Hash32, PublicKey, SecretKey,
Signature, KEYPAIR_SEED_SIZE, PUBLIC_KEY_SIZE, SECRET_KEY_SIZE, SIGNATURE_SIZE,
};
use mt_examples::{
hex_full, print_field, print_kv, print_note, print_section, print_subsection, print_warn,
xxd_dump,
};
use mt_merkle::{empty_internal, internal_hash, leaf_hash, TREE_DEPTH};
// Debug fingerprint через domain-separated hash. Label «fingerprint (debug)»
// явно указывает что это NOT raw SHA-256 — используется mt-fingerprint-debug
// domain для избежания confusion с consensus hashes.
fn fingerprint(bytes: &[u8]) -> String {
let h: Hash32 = hash(b"mt-fingerprint-debug", &[bytes]);
let short: Vec<u8> = h[..8].to_vec();
hex_full(&short)
}
fn debug_hash(bytes: &[u8]) -> String {
let h: Hash32 = hash(b"mt-fingerprint-debug", &[bytes]);
hex_full(&h)
}
// Raw SHA-256 без domain separation — для FIPS 180-4 conformance checks
// и для сравнения с standalone `shasum -a 256` output.
fn raw_sha256(bytes: &[u8]) -> String {
hex_full(&sha256_raw(bytes))
}
// Dump SK только если явно включён через env var M1_DUMP_SK=1.
// По умолчанию — redacted fingerprint only. Prevents accidental leak через
// cargo run --release --example m1_crypto > shared.log.
fn dump_sk_enabled() -> bool {
std::env::var("M1_DUMP_SK").ok().as_deref() == Some("1")
}
// cmd_keypair_deterministic заменяет старый print_recovery_disclosure (soft note):
// реальный test of deterministic KeyGen — два прогона keypair_from_seed(seed) с
// тем же seed → byte-exact equality, с разным seed → различные. Это actual
// демонстрация recovery flow на уровне primitive (не только note про mnemonic crate).
fn print_pk(pk: &PublicKey) {
print_subsection("PUBLIC KEY — передаётся в сеть, попадает в AccountTable/NodeTable");
print_kv("size", format!("{PUBLIC_KEY_SIZE} bytes"));
print_kv(
"fingerprint (8B, domain=mt-fingerprint-debug)",
fingerprint(pk.as_bytes()),
);
print_kv(
"debug_hash (domain=mt-fingerprint-debug)",
debug_hash(pk.as_bytes()),
);
print_kv("sha256_raw (no domain)", raw_sha256(pk.as_bytes()));
println!(" hex_full:");
println!(" {}", hex_full(pk.as_bytes()));
println!(" xxd dump:");
for line in xxd_dump(pk.as_bytes()).lines() {
println!(" {line}");
}
}
fn print_sk(sk: &SecretKey) {
print_subsection("SECRET KEY — НЕ покидает узел, хранится только локально");
print_kv("size", format!("{SECRET_KEY_SIZE} bytes"));
print_kv(
"fingerprint (8B, domain=mt-fingerprint-debug)",
fingerprint(sk.as_bytes()),
);
if !dump_sk_enabled() {
print_warn(
"SK bytes REDACTED — set M1_DUMP_SK=1 to show (dev-only, unsafe для shared output)",
);
return;
}
print_kv(
"debug_hash (domain=mt-fingerprint-debug)",
debug_hash(sk.as_bytes()),
);
print_kv("sha256_raw (no domain)", raw_sha256(sk.as_bytes()));
println!(" hex_full:");
println!(" {}", hex_full(sk.as_bytes()));
println!(" xxd dump:");
for line in xxd_dump(sk.as_bytes()).lines() {
println!(" {line}");
}
print_warn("SK отображён только потому что M1_DUMP_SK=1 установлен");
print_warn("В production SK никогда не логируется, не сериализуется в открытом виде");
}
fn print_sig(sig: &Signature) {
print_subsection("SIGNATURE — ML-DSA-65 deterministic");
print_kv("size", format!("{SIGNATURE_SIZE} bytes"));
print_kv(
"fingerprint (8B, domain=mt-fingerprint-debug)",
fingerprint(sig.as_bytes()),
);
print_kv(
"debug_hash (domain=mt-fingerprint-debug)",
debug_hash(sig.as_bytes()),
);
print_kv("sha256_raw (no domain)", raw_sha256(sig.as_bytes()));
println!(" hex_full:");
println!(" {}", hex_full(sig.as_bytes()));
println!(" xxd dump:");
for line in xxd_dump(sig.as_bytes()).lines() {
println!(" {line}");
}
}
fn cmd_keypair_random(n: usize) -> bool {
print_section(&format!(
"KEYPAIR RANDOM — ML-DSA-65 (OS rng entropy) × {n} (sanity check primitive)"
));
print_subsection("Алгоритм");
print_kv(
"scheme",
"ML-DSA-65 (NIST FIPS 204 finalized 2024-08, lattice-based, [I-1] compliant)",
);
print_kv(
"library",
"OpenSSL 3.5.5 LTS via own thin C FFI wrapper (mt-crypto-native)",
);
print_kv(
"entropy source",
"time+pid+stack-addr → SHA-256 (test/tool only; production через keypair_from_seed из HKDF)",
);
print_kv("pk size", format!("{PUBLIC_KEY_SIZE} bytes"));
print_kv("sk size", format!("{SECRET_KEY_SIZE} bytes"));
print_kv("sig size", format!("{SIGNATURE_SIZE} bytes"));
let mut pks: Vec<PublicKey> = Vec::with_capacity(n);
let mut sks: Vec<Vec<u8>> = Vec::with_capacity(n);
let mut all_verify_ok = true;
for i in 0..n {
print_section(&format!("KEYPAIR #{} / {}", i + 1, n));
let t0 = Instant::now();
let (pk, sk) = keypair();
let elapsed = t0.elapsed();
print_kv(
"generation time",
format!("{:.2}ms", elapsed.as_secs_f64() * 1000.0),
);
print_pk(&pk);
print_sk(&sk);
let msg = format!("keypair#{i} self-test");
let sig = sign(&sk, msg.as_bytes()).expect("sign self-test");
let ok = verify(&pk, msg.as_bytes(), &sig);
print_subsection("Self-test sign+verify");
print_kv("test message", format!("{:?} ({} bytes)", msg, msg.len()));
print_kv("verify(pk,msg,sig)", format!("{ok}"));
if !ok {
all_verify_ok = false;
}
pks.push(pk);
sks.push(sk.as_bytes().to_vec());
}
print_section("UNIQUENESS INVARIANT");
let mut pks_distinct = true;
let mut sks_distinct = true;
for i in 0..pks.len() {
for j in (i + 1)..pks.len() {
if pks[i].as_bytes() == pks[j].as_bytes() {
pks_distinct = false;
print_warn(&format!("pk[{i}] == pk[{j}] !!!"));
}
if sks[i] == sks[j] {
sks_distinct = false;
print_warn(&format!("sk[{i}] == sk[{j}] !!!"));
}
}
}
print_kv("all_public_keys_distinct", format!("{pks_distinct}"));
print_kv("all_secret_keys_distinct", format!("{sks_distinct}"));
print_section("RECOVERY MECHANISM POINTER");
print_note("Production identity flow — через m1_mnemonic (deterministic from 24 слов)");
print_note("Эта команда — RANDOM keypair sanity check ML-DSA-65 primitive (test/dev only)");
print_note(
"Для deterministic recovery test: subcommand `keypair` (без --random) либо `m1_mnemonic`",
);
let pass = all_verify_ok && pks_distinct && sks_distinct;
println!(
"\n[result] KEYPAIR-RANDOM: {}",
if pass { "PASS" } else { "FAIL" }
);
pass
}
fn cmd_keypair_deterministic() -> bool {
print_section("KEYPAIR DETERMINISTIC — ML-DSA-65.KeyGen(seed) byte-exact recovery test");
print_subsection("Алгоритм");
print_kv(
"scheme",
"ML-DSA-65 (NIST FIPS 204 finalized 2024-08, FIPS 204 §3.1 ξ ∈ B32)",
);
print_kv(
"seed source",
"fixed test value [0x42; 32] (production: HKDF-Expand из master_seed)",
);
print_kv("pk size", format!("{PUBLIC_KEY_SIZE} bytes"));
print_kv("sk size", format!("{SECRET_KEY_SIZE} bytes"));
let seed = [0x42u8; KEYPAIR_SEED_SIZE];
print_subsection("DERIVATION TRACE — два независимых прогона keypair_from_seed(seed)");
print_kv("seed hex", hex_full(&seed));
let (pk_a, sk_a) = keypair_from_seed(&seed).expect("keygen run A");
let (pk_b, sk_b) = keypair_from_seed(&seed).expect("keygen run B");
let pk_match = pk_a.as_bytes() == pk_b.as_bytes();
let sk_match = sk_a.as_bytes() == sk_b.as_bytes();
print_kv(
"Run A pk fingerprint (8B)",
hex_full(&sha256_raw(pk_a.as_bytes())[..8]),
);
print_kv(
"Run B pk fingerprint (8B)",
hex_full(&sha256_raw(pk_b.as_bytes())[..8]),
);
print_kv(
"Run A sk fingerprint (8B)",
hex_full(&sha256_raw(sk_a.as_bytes())[..8]),
);
print_kv(
"Run B sk fingerprint (8B)",
hex_full(&sha256_raw(sk_b.as_bytes())[..8]),
);
print_subsection("BYTE-EXACT EQUALITY ASSERTIONS");
print_kv("pk_a == pk_b", if pk_match { "OK ✓" } else { "FAIL ✗" });
print_kv("sk_a == sk_b", if sk_match { "OK ✓" } else { "FAIL ✗" });
print_subsection("DIFFERENT SEED → DIFFERENT KEYPAIR (sanity)");
let seed2 = [0x43u8; KEYPAIR_SEED_SIZE];
let (pk_c, _) = keypair_from_seed(&seed2).expect("keygen seed2");
let pk_differs = pk_a.as_bytes() != pk_c.as_bytes();
print_kv("seed2 hex", hex_full(&seed2));
print_kv(
"pk_a != pk_c (different seed)",
if pk_differs { "OK ✓" } else { "FAIL ✗" },
);
print_subsection("FINGERPRINTS — для cross-impl byte-exact verification");
print_kv("pk sha256", hex_full(&sha256_raw(pk_a.as_bytes())));
print_kv("sk sha256", hex_full(&sha256_raw(sk_a.as_bytes())));
print_section("RECOVERY MECHANISM");
print_note(
"Эта команда — actual byte-exact test, не описание. Тот же seed → тот же keypair всегда.",
);
print_note(
"В production seed выводится из 24-слов мнемоники через mt-mnemonic (HKDF-Expand role-keyed).",
);
print_note("Полный recovery flow до terminal IDs — m1_mnemonic recovery-fingerprint");
let pass = pk_match && sk_match && pk_differs;
println!(
"\n[result] KEYPAIR-DETERMINISTIC: {}",
if pass { "PASS" } else { "FAIL" }
);
pass
}
fn cmd_sign(msg: &str) -> bool {
print_section(&format!(
"SIGN + VERIFY — ML-DSA-65 deterministic signature on message {msg:?}"
));
print_subsection("Message preparation");
let msg_bytes = msg.as_bytes();
print_kv("len", format!("{} bytes", msg_bytes.len()));
print_kv("ascii", msg.to_string());
print_kv("hex", hex_full(msg_bytes));
let (pk, sk) = keypair();
print_pk(&pk);
print_sk(&sk);
print_subsection("Sign call trace");
print_kv(
"function",
format!("mt_crypto::sign(sk, msg) → Signature[{SIGNATURE_SIZE}B]"),
);
print_kv(
"internal",
"OpenSSL EVP_DigestSign + OSSL_SIGNATURE_PARAM_DETERMINISTIC=1 (FIPS 204 Algorithm 2, deterministic variant)",
);
let t0 = Instant::now();
let sig = sign(&sk, msg_bytes).expect("sign happy path");
let sign_ms = t0.elapsed().as_secs_f64() * 1000.0;
print_kv("sign latency", format!("{sign_ms:.2}ms"));
print_sig(&sig);
print_subsection("Verify call trace (happy path)");
let t1 = Instant::now();
let ok = verify(&pk, msg_bytes, &sig);
let verify_ms = t1.elapsed().as_secs_f64() * 1000.0;
print_kv("verify latency", format!("{verify_ms:.2}ms"));
print_kv("result", format!("{ok}"));
print_section("ADVERSARIAL TESTS");
print_subsection("1. Mutation at 4 positions must all reject");
let positions = [0usize, 166, 333, 665];
let mut mut_all_rejected = true;
for &pos in &positions {
let mut bad = *sig.as_bytes();
bad[pos] ^= 0xFF;
let bad_sig = Signature::from_array(bad);
let rejected = !verify(&pk, msg_bytes, &bad_sig);
print_kv(
&format!("mutate byte {pos}"),
format!(
"sig[{pos}]=0x{:02x} → 0x{:02x}, verify={}",
sig.as_bytes()[pos],
bad[pos],
if rejected {
"REJECTED ✓"
} else {
"ACCEPTED ✗"
}
),
);
if !rejected {
mut_all_rejected = false;
}
}
print_subsection("2. Mutated message must be rejected");
let mut bad_msg = msg_bytes.to_vec();
if !bad_msg.is_empty() {
bad_msg[0] ^= 0x01;
} else {
bad_msg.push(0x01);
}
let msg_rejected = !verify(&pk, &bad_msg, &sig);
print_kv("orig msg hex", hex_full(msg_bytes));
print_kv("bad msg hex", hex_full(&bad_msg));
print_kv(
"verify(pk, bad_msg, sig)",
if msg_rejected {
"REJECTED ✓"
} else {
"ACCEPTED ✗"
},
);
print_subsection("3. Cross-key: verify with wrong pubkey must fail");
let (other_pk, _) = keypair();
let cross_rejected = !verify(&other_pk, msg_bytes, &sig);
print_kv("other_pk fingerprint", fingerprint(other_pk.as_bytes()));
print_kv(
"verify(other_pk, msg, sig)",
if cross_rejected {
"REJECTED ✓"
} else {
"ACCEPTED ✗"
},
);
print_subsection(
"4. Determinism: same (sk, msg) twice → identical σ (FIPS 204 §3.7, RND = 0x00 × 32)",
);
let sig2 = sign(&sk, msg_bytes).expect("sign sample 2");
let identical = sig.as_bytes() == sig2.as_bytes();
let sig2_verifies = verify(&pk, msg_bytes, &sig2);
print_kv("sig1 fingerprint", fingerprint(sig.as_bytes()));
print_kv("sig2 fingerprint", fingerprint(sig2.as_bytes()));
print_kv("sig1 == sig2", format!("{identical}"));
print_kv("verify(pk, msg, sig2)", format!("{sig2_verifies}"));
let pass =
ok && mut_all_rejected && msg_rejected && cross_rejected && identical && sig2_verifies;
println!("\n[result] SIGN: {}", if pass { "PASS" } else { "FAIL" });
pass
}
fn cmd_hash() -> bool {
print_section("HASH — SHA-256 (FIPS 180-4) + domain-separated composition");
print_subsection("1. FIPS 180-4 §B.1 vector: SHA-256(\"abc\")");
let input = b"abc";
print_kv("input ascii", "abc");
print_kv("input hex", hex_full(input));
print_kv("input len", format!("{} bytes", input.len()));
let got = sha256_raw(input);
let expected_hex = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
print_kv("mt_crypto::sha256_raw(b\"abc\")", hex_full(&got));
print_kv("FIPS 180-4 expected", expected_hex.to_string());
let fips_ok = hex_full(&got) == expected_hex;
print_kv("byte-exact match", format!("{fips_ok}"));
print_note("NOTE: mt_crypto::hash(domain, parts) использует NUL separator и self-delimiting");
print_note("formula SHA-256(domain || 0x00 || parts) — не raw SHA-256. Для FIPS compatibility");
print_note("используется отдельная функция sha256_raw() без domain separation.");
print_subsection("2. Empty-parts collapse: hash(d,[]) == hash(d,[b\"\"])");
let a = hash(domain::OP, &[]);
let b = hash(domain::OP, &[&[] as &[u8]]);
print_kv("hash(OP, [])", hex_full(&a));
print_kv("hash(OP, [b\"\"])", hex_full(&b));
let collapse_ok = a == b;
print_kv("equal", format!("{collapse_ok}"));
print_subsection("3. Part concatenation: hash(d,[a,b,c]) == hash(d,[a||b||c])");
let h_parts = hash(domain::OP, &[b"aa", b"bb", b"cc"]);
let h_concat = hash(domain::OP, &[b"aabbcc"]);
print_kv("hash(OP, [aa,bb,cc])", hex_full(&h_parts));
print_kv("hash(OP, [aabbcc])", hex_full(&h_concat));
let concat_ok = h_parts == h_concat;
print_kv("equal", format!("{concat_ok}"));
print_subsection("4. Domain separation — все R2 class domains c input=x");
let x: &[u8] = b"x";
let class_domains: &[(&str, &[u8])] = &[
("mt-op", domain::OP),
("mt-nodereg", domain::NODEREG),
("mt-proposal", domain::PROPOSAL),
("mt-bundle", domain::BUNDLE),
("mt-vdf-reveal", domain::VDF_REVEAL),
];
let mut outputs: Vec<Hash32> = Vec::new();
for (name, d) in class_domains {
let h = hash(d, &[x]);
print_kv(&format!("{name:<16} input=x"), hex_full(&h));
outputs.push(h);
}
let mut class_distinct = true;
for i in 0..outputs.len() {
for j in (i + 1)..outputs.len() {
if outputs[i] == outputs[j] {
class_distinct = false;
print_warn(&format!(
"collision: {} == {}",
class_domains[i].0, class_domains[j].0
));
}
}
}
print_kv("all_class_domains_distinct", format!("{class_distinct}"));
print_subsection("5. Domain separator registry — полный список 32 штук");
let all_domains: &[(&str, &[u8])] = &[
("mt-op", domain::OP),
("mt-nodereg", domain::NODEREG),
("mt-proposal", domain::PROPOSAL),
("mt-bundle", domain::BUNDLE),
("mt-vdf-reveal", domain::VDF_REVEAL),
("mt-account", domain::ACCOUNT),
("mt-candidate-vdf-init", domain::CANDIDATE_VDF_INIT),
("mt-merkle-leaf", domain::MERKLE_LEAF),
("mt-merkle-node", domain::MERKLE_NODE),
("mt-state-root", domain::STATE_ROOT),
("mt-timechain", domain::TIMECHAIN),
("mt-lottery", domain::LOTTERY),
("mt-bc-aggregate", domain::BC_AGGREGATE),
("mt-bc-aggregate-empty", domain::BC_AGGREGATE_EMPTY),
("mt-selection", domain::SELECTION),
("mt-nodereg-sort", domain::NODEREG_SORT),
("mt-confirmation", domain::CONFIRMATION),
("mt-app", domain::APP),
("mt-node", domain::NODE),
("mt-genesis", domain::GENESIS),
("mt-seed", domain::SEED),
("mt-account-key", domain::ACCOUNT_KEY),
("mt-node-key", domain::NODE_KEY),
("mt-content-chunk", domain::CONTENT_CHUNK),
("mt-content-manifest", domain::CONTENT_MANIFEST),
("mt-profile", domain::PROFILE),
("mt-encryption-key", domain::ENCRYPTION_KEY),
("mt-app-encryption-key", domain::APP_ENCRYPTION_KEY),
("mt-prekeys", domain::PREKEYS),
("mt-tunnel-online", domain::TUNNEL_ONLINE),
("mt-tunnel-mesh", domain::TUNNEL_MESH),
("mt-bootstrap-pow", domain::BOOTSTRAP_POW),
("mt-recovery-fingerprint", domain::RECOVERY_FINGERPRINT),
];
print_kv("registry size", format!("{}", all_domains.len()));
for (name, bytes) in all_domains {
print_field(
name,
&format!("{} bytes, hex={}", bytes.len(), hex_full(bytes)),
);
}
let pass = fips_ok && collapse_ok && concat_ok && class_distinct;
println!("\n[result] HASH: {}", if pass { "PASS" } else { "FAIL" });
pass
}
fn cmd_merkle() -> bool {
print_section("MERKLE — Sparse Merkle Tree depth=256, empty_internal table");
print_subsection("Определения");
print_kv("TREE_DEPTH", format!("{TREE_DEPTH}"));
print_kv("EMPTY_LEAF", hex_full(&empty_internal(0)));
print_kv(
"leaf_hash(x)",
"= SHA-256(\"mt-merkle-leaf\" || x)".to_string(),
);
print_kv(
"internal_hash(l,r)",
"= SHA-256(\"mt-merkle-node\" || l || r)".to_string(),
);
print_kv(
"empty_internal(k)",
"= level 0: [0;32]; level k≥1: internal_hash(empty_internal(k-1), empty_internal(k-1))"
.to_string(),
);
print_subsection("Derivation trace — первые 3 уровня");
let lvl0 = empty_internal(0);
let lvl1 = empty_internal(1);
let lvl2 = empty_internal(2);
println!(" level 0 = EMPTY_LEAF");
println!(" = {}", hex_full(&lvl0));
println!();
println!(" level 1 = internal_hash(level_0, level_0)");
println!(" = SHA-256(\"mt-merkle-node\" || level_0 || level_0)");
println!(" input domain (14B): {}", hex_full(domain::MERKLE_NODE));
println!(
" input data (64B): {}",
hex_full(&[&lvl0[..], &lvl0[..]].concat())
);
println!(" output (32B): {}", hex_full(&lvl1));
println!();
println!(" level 2 = internal_hash(level_1, level_1)");
println!(" input domain (14B): {}", hex_full(domain::MERKLE_NODE));
println!(
" input data (64B): {}",
hex_full(&[&lvl1[..], &lvl1[..]].concat())
);
println!(" output (32B): {}", hex_full(&lvl2));
print_subsection("Полная таблица empty_internal(0..=256)");
for k in 0..=TREE_DEPTH {
let v = empty_internal(k);
println!(" level {k:>3}: {}", hex_full(&v));
}
print_subsection("Invariants");
let lvl256 = empty_internal(TREE_DEPTH);
let level0_zero = lvl0 == [0u8; 32];
let mut monotonic_all_differ = true;
for k in 0..TREE_DEPTH {
if empty_internal(k) == empty_internal(k + 1) {
monotonic_all_differ = false;
print_warn(&format!("level {k} == level {}", k + 1));
}
}
let determinism = empty_internal(TREE_DEPTH) == lvl256;
print_kv("level_0_is_all_zero", format!("{level0_zero}"));
print_kv("all_consecutive_differ", format!("{monotonic_all_differ}"));
print_kv("determinism (re-query equal)", format!("{determinism}"));
print_subsection("Sanity: leaf_hash(\"hello\") определён и отличается от EMPTY_LEAF");
let leaf = leaf_hash(b"hello");
let internal = internal_hash(&lvl0, &lvl0);
print_kv("leaf_hash(b\"hello\")", hex_full(&leaf));
print_kv("internal_hash(zeros,zeros)", hex_full(&internal));
print_kv(
"leaf_hash == internal_hash(z,z)",
format!("{}", leaf == internal),
);
print_kv(
"leaf_hash == empty_internal(1)",
format!("{}", leaf == lvl1),
);
let leaf_vs_empty = leaf != lvl0;
print_kv("leaf_hash != EMPTY_LEAF", format!("{leaf_vs_empty}"));
let pass = level0_zero && monotonic_all_differ && determinism && leaf_vs_empty;
println!("\n[result] MERKLE: {}", if pass { "PASS" } else { "FAIL" });
pass
}
fn cmd_all() -> bool {
print_section("M1 CRYPTO — FULL USER JOURNEY");
print_note(
"Прогоняется полный путь: deterministic keypair test + 3 random keypairs + sign + hash + merkle",
);
let a = cmd_keypair_deterministic();
let b = cmd_keypair_random(3);
let c = cmd_sign("Montana M1 crypto shakedown — production journey");
let d = cmd_hash();
let e = cmd_merkle();
print_section("SUMMARY");
print_kv("keypair-deterministic", if a { "PASS" } else { "FAIL" });
print_kv("keypair-random", if b { "PASS" } else { "FAIL" });
print_kv("sign", if c { "PASS" } else { "FAIL" });
print_kv("hash", if d { "PASS" } else { "FAIL" });
print_kv("merkle-empty", 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!("M1 CRYPTO — ML-DSA-65 + SHA-256 + Sparse Merkle shakedown binary");
eprintln!();
eprintln!("usage: m1_crypto <subcommand> [args]");
eprintln!();
eprintln!(" keypair Deterministic keypair test (recovery sanity, default)");
eprintln!(" keypair-random [N] RANDOM N keypairs (default 1) + uniqueness check");
eprintln!(" sign [MSG] Keypair → sign(MSG) → verify + 4 adversarial теста");
eprintln!(" hash FIPS 180-4 vector + domain separation + registry полный");
eprintln!(" merkle-empty Sparse Merkle: все 257 levels + derivation trace");
eprintln!(" all Прогнать все подкоманды подряд (user journey)");
eprintln!();
eprintln!("Всё печатает байты в hex + xxd dump. 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 {
"keypair" => cmd_keypair_deterministic(),
"keypair-random" => {
let n: usize = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(1);
cmd_keypair_random(n)
},
"sign" => {
let default_msg = String::from("Montana M1 crypto shakedown");
let msg = args.get(2).unwrap_or(&default_msg);
cmd_sign(msg)
},
"hash" => cmd_hash(),
"merkle-empty" => cmd_merkle(),
"all" => cmd_all(),
_ => {
usage();
return ExitCode::FAILURE;
},
};
bool_to_exit(pass)
}