aboutsummaryrefslogtreecommitdiffstats
path: root/src/base58.rs
diff options
context:
space:
mode:
authormurilo ijanc2026-03-25 02:07:37 -0300
committermurilo ijanc2026-03-25 02:07:37 -0300
commit7aff2e1d279a4e442b32f49ca0a0eca065355787 (patch)
treebc987ece7eb78bb8375de1b20123ecd0f90472ba /src/base58.rs
downloadtesseras-paste-7aff2e1d279a4e442b32f49ca0a0eca065355787.tar.gz
Initial commit: tesseras-paste decentralized pastebin
DHT-backed encrypted pastebin with two binaries (tp/tpd), XChaCha20-Poly1305 encryption, content-addressed storage, and Unix socket + HTTP interfaces.
Diffstat (limited to 'src/base58.rs')
-rw-r--r--src/base58.rs153
1 files changed, 153 insertions, 0 deletions
diff --git a/src/base58.rs b/src/base58.rs
new file mode 100644
index 0000000..c412bc3
--- /dev/null
+++ b/src/base58.rs
@@ -0,0 +1,153 @@
+//! 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);
+ }
+}