369 lines
11 KiB
Rust
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]);
|
|
}
|
|
}
|