montana/Монтана-Протокол/Код/crates/mt-codec/src/lib.rs

369 lines
11 KiB
Rust

#![no_std]
extern crate alloc;
use alloc::vec::Vec;
// spec, раздел "Consensus encoding layer"
pub trait CanonicalEncode {
fn encode(&self, buf: &mut Vec<u8>);
}
#[inline]
pub fn write_u8(buf: &mut Vec<u8>, v: u8) {
buf.push(v);
}
#[inline]
pub fn write_u16(buf: &mut Vec<u8>, v: u16) {
buf.extend_from_slice(&v.to_le_bytes());
}
#[inline]
pub fn write_u32(buf: &mut Vec<u8>, v: u32) {
buf.extend_from_slice(&v.to_le_bytes());
}
#[inline]
pub fn write_u64(buf: &mut Vec<u8>, v: u64) {
buf.extend_from_slice(&v.to_le_bytes());
}
#[inline]
pub fn write_u128(buf: &mut Vec<u8>, v: u128) {
buf.extend_from_slice(&v.to_le_bytes());
}
#[inline]
pub fn write_bytes(buf: &mut Vec<u8>, 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<u8>) {
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]);
}
}