rustywallet_address/encoding/
base58.rs1use crate::error::AddressError;
4use sha2::{Digest, Sha256};
5
6pub struct Base58Check;
8
9impl Base58Check {
10 pub fn encode(version: u8, payload: &[u8]) -> String {
15 let mut data = Vec::with_capacity(1 + payload.len() + 4);
16 data.push(version);
17 data.extend_from_slice(payload);
18
19 let checksum = Self::checksum(&data);
21 data.extend_from_slice(&checksum);
22
23 bs58::encode(data).into_string()
24 }
25
26 pub fn decode(s: &str) -> Result<(u8, Vec<u8>), AddressError> {
30 let data = bs58::decode(s)
31 .into_vec()
32 .map_err(|e| AddressError::InvalidBase58(e.to_string()))?;
33
34 if data.len() < 5 {
35 return Err(AddressError::InvalidBase58("Data too short".to_string()));
36 }
37
38 let (payload_with_version, checksum) = data.split_at(data.len() - 4);
39 let expected_checksum = Self::checksum(payload_with_version);
40
41 if checksum != expected_checksum {
42 return Err(AddressError::ChecksumMismatch);
43 }
44
45 let version = payload_with_version[0];
46 let payload = payload_with_version[1..].to_vec();
47
48 Ok((version, payload))
49 }
50
51 fn checksum(data: &[u8]) -> [u8; 4] {
53 let hash1 = Sha256::digest(data);
54 let hash2 = Sha256::digest(hash1);
55 let mut checksum = [0u8; 4];
56 checksum.copy_from_slice(&hash2[..4]);
57 checksum
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn test_base58check_roundtrip() {
67 let payload = [0x01, 0x02, 0x03, 0x04, 0x05];
68 let encoded = Base58Check::encode(0x00, &payload);
69 let (version, decoded) = Base58Check::decode(&encoded).unwrap();
70 assert_eq!(version, 0x00);
71 assert_eq!(decoded, payload);
72 }
73
74 #[test]
75 fn test_base58check_invalid_checksum() {
76 let encoded = Base58Check::encode(0x00, &[0x01, 0x02, 0x03]);
77 let mut chars: Vec<char> = encoded.chars().collect();
79 if let Some(c) = chars.last_mut() {
80 *c = if *c == '1' { '2' } else { '1' };
81 }
82 let corrupted: String = chars.into_iter().collect();
83 assert!(Base58Check::decode(&corrupted).is_err());
84 }
85}