#![no_std] extern crate alloc; use alloc::vec::Vec; // spec, раздел "Consensus encoding layer" pub trait CanonicalEncode { fn encode(&self, buf: &mut Vec); } #[inline] pub fn write_u8(buf: &mut Vec, v: u8) { buf.push(v); } #[inline] pub fn write_u16(buf: &mut Vec, v: u16) { buf.extend_from_slice(&v.to_le_bytes()); } #[inline] pub fn write_u32(buf: &mut Vec, v: u32) { buf.extend_from_slice(&v.to_le_bytes()); } #[inline] pub fn write_u64(buf: &mut Vec, v: u64) { buf.extend_from_slice(&v.to_le_bytes()); } #[inline] pub fn write_u128(buf: &mut Vec, v: u128) { buf.extend_from_slice(&v.to_le_bytes()); } #[inline] pub fn write_bytes(buf: &mut Vec, v: &[u8]) { buf.extend_from_slice(v); } // spec, раздел "Domain separators registry" pub mod domain { // Class domains (Правило R2 — identifier(obj)) pub const OP: &[u8] = b"mt-op"; pub const NODEREG: &[u8] = b"mt-nodereg"; pub const PROPOSAL: &[u8] = b"mt-proposal"; pub const BUNDLE: &[u8] = b"mt-bundle"; pub const VDF_REVEAL: &[u8] = b"mt-vdf-reveal"; // Account/Node derivation pub const ACCOUNT: &[u8] = b"mt-account"; pub const CANDIDATE_VDF_INIT: &[u8] = b"mt-candidate-vdf-init"; pub const MERKLE_LEAF: &[u8] = b"mt-merkle-leaf"; pub const MERKLE_NODE: &[u8] = b"mt-merkle-node"; pub const STATE_ROOT: &[u8] = b"mt-state-root"; pub const TIMECHAIN: &[u8] = b"mt-timechain"; pub const LOTTERY: &[u8] = b"mt-lottery"; pub const BC_AGGREGATE: &[u8] = b"mt-bc-aggregate"; pub const BC_AGGREGATE_EMPTY: &[u8] = b"mt-bc-aggregate-empty"; pub const SELECTION: &[u8] = b"mt-selection"; pub const NODEREG_SORT: &[u8] = b"mt-nodereg-sort"; pub const CONFIRMATION: &[u8] = b"mt-confirmation"; pub const APP: &[u8] = b"mt-app"; pub const NODE: &[u8] = b"mt-node"; pub const GENESIS: &[u8] = b"mt-genesis"; pub const SEED: &[u8] = b"mt-seed"; pub const ACCOUNT_KEY: &[u8] = b"mt-account-key"; pub const NODE_KEY: &[u8] = b"mt-node-key"; // spec v30.x: `mt-account-lottery` domain удалён (account lottery не существует). pub const CONTENT_CHUNK: &[u8] = b"mt-content-chunk"; pub const CONTENT_MANIFEST: &[u8] = b"mt-content-manifest"; pub const PROFILE: &[u8] = b"mt-profile"; pub const ENCRYPTION_KEY: &[u8] = b"mt-encryption-key"; pub const APP_ENCRYPTION_KEY: &[u8] = b"mt-app-encryption-key"; pub const PREKEYS: &[u8] = b"mt-prekeys"; // libp2p Ed25519 transport identity per M8 cross-machine networking. // Derived из master_seed для recoverable identities (Mode A); в Mode B // ephemeral Ed25519 секрет хранится напрямую в identity.bin. pub const LIBP2P_TRANSPORT_KEY: &[u8] = b"mt-libp2p-transport-key"; pub const TUNNEL_ONLINE: &[u8] = b"mt-tunnel-online"; pub const TUNNEL_MESH: &[u8] = b"mt-tunnel-mesh"; pub const BOOTSTRAP_POW: &[u8] = b"mt-bootstrap-pow"; pub const RECOVERY_FINGERPRINT: &[u8] = b"mt-recovery-fingerprint"; } #[cfg(test)] mod tests { use super::*; use alloc::vec; #[test] fn write_u8_single_byte() { let mut buf = Vec::new(); write_u8(&mut buf, 0xAB); assert_eq!(buf, vec![0xAB]); } #[test] fn write_u16_little_endian() { let mut buf = Vec::new(); write_u16(&mut buf, 0x1234); assert_eq!(buf, vec![0x34, 0x12]); } #[test] fn write_u32_little_endian() { let mut buf = Vec::new(); write_u32(&mut buf, 0xDEADBEEF); assert_eq!(buf, vec![0xEF, 0xBE, 0xAD, 0xDE]); } #[test] fn write_u64_one() { let mut buf = Vec::new(); write_u64(&mut buf, 1); assert_eq!(buf, vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); } #[test] fn write_u64_max() { let mut buf = Vec::new(); write_u64(&mut buf, u64::MAX); assert_eq!(buf, vec![0xFF; 8]); } #[test] fn write_u128_emission_moneta() { // spec: EMISSION_moneta = 13_000_000_000 let mut buf = Vec::new(); write_u128(&mut buf, 13_000_000_000u128); let expected = 13_000_000_000u128.to_le_bytes(); assert_eq!(buf.as_slice(), &expected); assert_eq!(buf.len(), 16); } #[test] fn write_bytes_raw_no_prefix() { let mut buf = Vec::new(); write_bytes(&mut buf, &[0xAA, 0xBB, 0xCC]); assert_eq!(buf, vec![0xAA, 0xBB, 0xCC]); } #[test] fn write_bytes_empty() { let mut buf = Vec::new(); write_bytes(&mut buf, &[]); assert!(buf.is_empty()); } #[test] fn multiple_writes_concat() { let mut buf = Vec::new(); write_u8(&mut buf, 0x01); write_u16(&mut buf, 0x0302); write_u32(&mut buf, 0x07060504); assert_eq!(buf, vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]); } #[test] fn domain_lottery_ascii() { // spec: "mt-lottery" → 10 bytes 0x6D 0x74 0x2D 0x6C 0x6F 0x74 0x74 0x65 0x72 0x79 assert_eq!( domain::LOTTERY, &[0x6D, 0x74, 0x2D, 0x6C, 0x6F, 0x74, 0x74, 0x65, 0x72, 0x79] ); } #[test] fn domain_account_ascii() { assert_eq!(domain::ACCOUNT, b"mt-account"); assert_eq!(domain::ACCOUNT.len(), 10); } #[test] fn domain_class_domains_ascii() { assert_eq!(domain::OP, b"mt-op"); assert_eq!(domain::NODEREG, b"mt-nodereg"); assert_eq!(domain::PROPOSAL, b"mt-proposal"); assert_eq!(domain::BUNDLE, b"mt-bundle"); assert_eq!(domain::VDF_REVEAL, b"mt-vdf-reveal"); } #[test] fn domain_node_ascii() { assert_eq!(domain::NODE, b"mt-node"); } #[test] fn domain_state_root_ascii() { assert_eq!(domain::STATE_ROOT, b"mt-state-root"); } #[test] fn domain_merkle_leaf_ascii() { assert_eq!(domain::MERKLE_LEAF, b"mt-merkle-leaf"); } #[test] fn domain_merkle_node_ascii() { assert_eq!(domain::MERKLE_NODE, b"mt-merkle-node"); } #[test] fn domain_timechain_ascii() { assert_eq!(domain::TIMECHAIN, b"mt-timechain"); } #[test] fn domain_selection_ascii() { assert_eq!(domain::SELECTION, b"mt-selection"); } #[test] fn domain_bc_aggregate_ascii() { assert_eq!(domain::BC_AGGREGATE, b"mt-bc-aggregate"); assert_eq!(domain::BC_AGGREGATE_EMPTY, b"mt-bc-aggregate-empty"); } #[test] fn domain_nodereg_sort_ascii() { assert_eq!(domain::NODEREG_SORT, b"mt-nodereg-sort"); } #[test] fn domain_candidate_vdf_init_ascii() { assert_eq!(domain::CANDIDATE_VDF_INIT, b"mt-candidate-vdf-init"); } #[test] fn domain_tunnel_online_ascii() { assert_eq!(domain::TUNNEL_ONLINE, b"mt-tunnel-online"); } #[test] fn domain_tunnel_mesh_ascii() { assert_eq!(domain::TUNNEL_MESH, b"mt-tunnel-mesh"); } #[test] fn domain_tunnel_prefix_free() { assert!(!domain::TUNNEL_MESH.starts_with(domain::TUNNEL_ONLINE)); assert!(!domain::TUNNEL_ONLINE.starts_with(domain::TUNNEL_MESH)); } #[test] fn domain_bootstrap_pow_ascii() { assert_eq!(domain::BOOTSTRAP_POW, b"mt-bootstrap-pow"); } #[test] fn domain_recovery_fingerprint_ascii() { assert_eq!(domain::RECOVERY_FINGERPRINT, b"mt-recovery-fingerprint"); } #[test] fn all_domains_start_with_mt_dash() { let all: [&[u8]; 33] = [ domain::OP, domain::NODEREG, domain::PROPOSAL, domain::BUNDLE, domain::VDF_REVEAL, domain::ACCOUNT, domain::CANDIDATE_VDF_INIT, domain::MERKLE_LEAF, domain::MERKLE_NODE, domain::STATE_ROOT, domain::TIMECHAIN, domain::LOTTERY, domain::BC_AGGREGATE, domain::BC_AGGREGATE_EMPTY, domain::SELECTION, domain::NODEREG_SORT, domain::CONFIRMATION, domain::APP, domain::NODE, domain::GENESIS, domain::SEED, domain::ACCOUNT_KEY, domain::NODE_KEY, domain::CONTENT_CHUNK, domain::CONTENT_MANIFEST, domain::PROFILE, domain::ENCRYPTION_KEY, domain::APP_ENCRYPTION_KEY, domain::PREKEYS, domain::TUNNEL_ONLINE, domain::TUNNEL_MESH, domain::BOOTSTRAP_POW, domain::RECOVERY_FINGERPRINT, ]; for d in all { assert!( d.starts_with(b"mt-"), "domain does not start with mt-: {d:?}" ); } // 33 = 31 previous + 2 (TUNNEL_ONLINE/TUNNEL_MESH split per Pass 14 prefix-free) + 1 (RECOVERY_FINGERPRINT for m1_mnemonic two-device validation) - 1 (TUNNEL replaced by 2) assert_eq!(all.len(), 33); } // Property-style test: write → read roundtrip for 1000 deterministic u64 values. // Uses xorshift PRNG to avoid external crate dependency. #[test] fn roundtrip_u64_property() { let mut state: u64 = 0x9E3779B97F4A7C15; // SplitMix64 golden gamma for _ in 0..1000 { state ^= state << 13; state ^= state >> 7; state ^= state << 17; let original = state; let mut buf = Vec::new(); write_u64(&mut buf, original); assert_eq!(buf.len(), 8); let mut arr = [0u8; 8]; arr.copy_from_slice(&buf); let roundtripped = u64::from_le_bytes(arr); assert_eq!(roundtripped, original); } } #[test] fn roundtrip_u128_property() { let mut lo: u64 = 0xDEADBEEFCAFEBABE; let mut hi: u64 = 0x1234567890ABCDEF; for _ in 0..1000 { lo ^= lo << 13; lo ^= lo >> 7; lo ^= lo << 17; hi ^= hi << 11; hi ^= hi >> 5; hi ^= hi << 23; let original = ((hi as u128) << 64) | (lo as u128); let mut buf = Vec::new(); write_u128(&mut buf, original); assert_eq!(buf.len(), 16); let mut arr = [0u8; 16]; arr.copy_from_slice(&buf); let roundtripped = u128::from_le_bytes(arr); assert_eq!(roundtripped, original); } } #[test] fn canonical_encode_trait_works() { struct Pair { a: u32, b: u16, } impl CanonicalEncode for Pair { fn encode(&self, buf: &mut Vec) { write_u32(buf, self.a); write_u16(buf, self.b); } } let p = Pair { a: 0x01020304, b: 0x0506, }; let mut buf = Vec::new(); p.encode(&mut buf); assert_eq!(buf, vec![0x04, 0x03, 0x02, 0x01, 0x06, 0x05]); } }