tesseras-paste 0.1.0

Decentralized pastebin built on tesseras-dht
//! 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<u8> = 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<Vec<u8>> {
    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<u8> = 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<u8> = (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::<u8>::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);
    }
}