//! Keypair persistence and encoding utilities. //! //! Reused by the `node` crate, `store_node`, and xtask tooling. use std::path::Path; use crate::crypto::Keypair; pub use swactor::transport::{hex_encode, hex_decode}; /// Base58-encode a byte slice (Bitcoin alphabet). pub fn base58_encode(bytes: &[u8]) -> String { bs58::encode(bytes).into_string() } /// Base58-decode a string into a 32-byte array. Returns `None` on invalid input. pub fn base58_decode(s: &str) -> Option<[u8; 32]> { let bytes = bs58::decode(s).into_vec().ok()?; bytes.try_into().ok() } /// Load a keypair from `path`, or generate a new one and persist it. /// /// The file format is JSON: /// ```json /// { /// "version": 1, /// "secret_key": "", /// "public_key": "", /// "created_at": "2025-01-01T00:00:00Z" /// } /// ``` pub fn load_or_generate_keypair(path: &Path) -> Keypair { if path.exists() { let data = std::fs::read_to_string(path).expect("failed to read key file"); let json: serde_json::Value = serde_json::from_str(&data).expect("invalid key file JSON"); let secret_hex = json .get("secret_key") .and_then(|v| v.as_str()) .expect("key file missing secret_key"); let secret_bytes = hex_decode(secret_hex).expect("invalid secret_key hex"); let secret: [u8; 32] = secret_bytes .try_into() .expect("secret_key must be 32 bytes"); Keypair::from_bytes(&secret) } else { let keypair = Keypair::generate(); let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs(); let json = serde_json::json!({ "version": 1, "secret_key": hex_encode(&keypair.secret_bytes()), "public_key": hex_encode(&keypair.node_id().0), "created_at": format_timestamp(now), }); if let Some(parent) = path.parent() { std::fs::create_dir_all(parent).expect("failed to create key file directory"); } std::fs::write(path, serde_json::to_string_pretty(&json).unwrap()) .expect("failed to write key file"); keypair } } /// Simple ISO-8601 UTC timestamp from epoch seconds. fn format_timestamp(secs: u64) -> String { let s = secs % 60; let m = (secs / 60) % 60; let h = (secs / 3600) % 24; let days = secs / 86400; let (y, mo, d) = days_to_ymd(days); format!("{y:04}-{mo:02}-{d:02}T{h:02}:{m:02}:{s:02}Z") } /// Convert days since epoch to (year, month, day). /// Algorithm from . fn days_to_ymd(mut days: u64) -> (u64, u64, u64) { days += 719468; let era = days / 146097; let doe = days - era * 146097; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; let y = yoe + era * 400; let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = doy - (153 * mp + 2) / 5 + 1; let m = if mp < 10 { mp + 3 } else { mp - 9 }; let y = if m <= 2 { y + 1 } else { y }; (y, m, d) }