//! Bitcoin-style Base58 encoding/decoding. //! //! No external dependencies. Uses the standard Bitcoin //! alphabet (no 0, O, I, l to avoid ambiguity). const ALPHABET: &[u8; 58] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; /// Decode table: ASCII byte → base58 value (255 = invalid). const DECODE: [u8; 128] = { let mut table = [255u8; 128]; let mut i = 0; while i < 58 { table[ALPHABET[i] as usize] = i as u8; i += 1; } table }; /// Encode bytes to Base58 string. pub fn encode(input: &[u8]) -> String { if input.is_empty() { return String::new(); } // Count leading zeros let leading_zeros = input.iter().take_while(|&&b| b == 0).count(); // Convert to base58 via repeated division let mut digits: Vec = Vec::with_capacity(input.len() * 2); for &byte in input { let mut carry = byte as u32; for d in &mut digits { carry += (*d as u32) << 8; *d = (carry % 58) as u8; carry /= 58; } while carry > 0 { digits.push((carry % 58) as u8); carry /= 58; } } let mut result = String::with_capacity(leading_zeros + digits.len()); for _ in 0..leading_zeros { result.push('1'); } for &d in digits.iter().rev() { result.push(ALPHABET[d as usize] as char); } result } /// Decode a Base58 string to bytes. pub fn decode(input: &str) -> Option> { if input.is_empty() { return Some(Vec::new()); } // Count leading '1's (representing zero bytes) let leading_ones = input.chars().take_while(|&c| c == '1').count(); // Convert from base58 via repeated multiplication let mut bytes: Vec = Vec::with_capacity(input.len()); for c in input.chars() { let c = c as usize; if c >= 128 { return None; } let val = DECODE[c]; if val == 255 { return None; } let mut carry = val as u32; for b in &mut bytes { carry += (*b as u32) * 58; *b = (carry & 0xFF) as u8; carry >>= 8; } while carry > 0 { bytes.push((carry & 0xFF) as u8); carry >>= 8; } } let mut result = vec![0; leading_ones]; result.extend(bytes.iter().rev()); Some(result) } #[cfg(test)] mod tests { use super::*; #[test] fn encode_empty() { assert_eq!(encode(b""), ""); } #[test] fn encode_hello() { assert_eq!(encode(b"Hello World"), "JxF12TrwUP45BMd"); } #[test] fn roundtrip() { let data = b"tesseras-paste test data"; let encoded = encode(data); let decoded = decode(&encoded).unwrap(); assert_eq!(decoded, data); } #[test] fn roundtrip_binary() { let data: Vec = (0..=255).collect(); let encoded = encode(&data); let decoded = decode(&encoded).unwrap(); assert_eq!(decoded, data); } #[test] fn leading_zeros() { let data = [0, 0, 0, 1, 2, 3]; let encoded = encode(&data); assert!(encoded.starts_with("111")); let decoded = decode(&encoded).unwrap(); assert_eq!(decoded, data); } #[test] fn decode_invalid_char() { assert!(decode("invalid0char").is_none()); } #[test] fn decode_empty() { assert_eq!(decode("").unwrap(), Vec::::new()); } #[test] fn known_vector() { // SHA-256 of "test" in base58 let hash: [u8; 32] = { use tesseras_dht::sha2::{Digest, Sha256}; let mut h = Sha256::new(); h.update(b"test"); h.finalize().into() }; let encoded = encode(&hash); let decoded = decode(&encoded).unwrap(); assert_eq!(decoded, hash); } }