montana/Монтана-Протокол/Код/crates/mt-merkle/tests/determinism_invariants.rs

127 lines
3.7 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Automated determinism invariants для mt-merkle.
// M2 audit prep — proactive regression detection per [I-3] determinism +
// [C-1] SSOT (empty_internal binding с mt-codec/mt-crypto).
//
// Sparse Merkle Tree consensus-critical = ANY non-determinism = consensus fork.
use mt_crypto::Hash32;
use mt_merkle::{empty_internal, internal_hash, leaf_hash, SparseMerkleTree};
use std::mem::size_of_val;
#[test]
fn empty_internal_deterministic_across_invocations() {
// Same level → same hash, byte-exact, идемпотентно.
for level in 0..16 {
let a = empty_internal(level);
let b = empty_internal(level);
assert_eq!(a, b, "empty_internal({}) non-deterministic", level);
}
}
#[test]
fn empty_internal_different_levels_different_hashes() {
// Domain separation между уровнями — каждый level имеет уникальный hash.
let mut prev = empty_internal(0);
for level in 1..16 {
let h = empty_internal(level);
assert_ne!(prev, h, "empty_internal({}) collides с предыдущим", level);
prev = h;
}
}
#[test]
fn leaf_hash_deterministic() {
let data = b"montana-test-leaf";
let a = leaf_hash(data);
let b = leaf_hash(data);
assert_eq!(a, b);
let c = leaf_hash(b"different-data");
assert_ne!(a, c);
}
#[test]
fn internal_hash_deterministic() {
let left = [0x11u8; 32];
let right = [0x22u8; 32];
let a = internal_hash(&left, &right);
let b = internal_hash(&left, &right);
assert_eq!(a, b);
}
#[test]
fn internal_hash_left_right_asymmetric() {
// internal_hash(L, R) != internal_hash(R, L) — order-sensitive
// (защита от swap atak на Merkle proofs).
let l = [0x11u8; 32];
let r = [0x22u8; 32];
let lr = internal_hash(&l, &r);
let rl = internal_hash(&r, &l);
assert_ne!(lr, rl, "internal_hash должен быть order-sensitive");
}
#[test]
fn smt_empty_root_deterministic() {
// Пустой tree — root известен и детерминирован между instances.
let t1: SparseMerkleTree = SparseMerkleTree::new();
let t2: SparseMerkleTree = SparseMerkleTree::new();
assert_eq!(
t1.root(),
t2.root(),
"SMT::new() должен давать deterministic empty root"
);
}
#[test]
fn smt_insert_deterministic_across_orders() {
// Insertion order не должен влиять на root (BTreeMap внутри = canonical
// sort by key) — критично для consensus determinism.
let mut t1 = SparseMerkleTree::new();
let mut t2 = SparseMerkleTree::new();
let entries: Vec<([u8; 32], Vec<u8>)> = vec![
([0x01u8; 32], b"alice".to_vec()),
([0x02u8; 32], b"bob".to_vec()),
([0x03u8; 32], b"charlie".to_vec()),
];
// Forward order
for (k, v) in &entries {
t1.insert(*k, v);
}
// Reverse order
for (k, v) in entries.iter().rev() {
t2.insert(*k, v);
}
assert_eq!(
t1.root(),
t2.root(),
"Insertion order не должен влиять на SMT root — BTreeMap canonical sort"
);
}
#[test]
fn smt_root_changes_on_insert() {
let mut t = SparseMerkleTree::new();
let empty_root = t.root();
t.insert([0x42u8; 32], b"value");
let after_insert = t.root();
assert_ne!(empty_root, after_insert, "Root must change on insert");
}
// ---------- Static type invariants ----------
#[test]
fn hash32_is_32_bytes() {
let h: Hash32 = [0u8; 32];
assert_eq!(size_of_val(&h), 32);
}
#[test]
fn key_type_is_32_bytes() {
// SMT key = 32 bytes (account_id, node_id, etc.) — фиксированный размер
// для consensus determinism.
let k: [u8; 32] = [0u8; 32];
assert_eq!(size_of_val(&k), 32);
}