bsv_primitives/base58/
mod.rs1use crate::PrimitivesError;
9use crate::hash::sha256d;
10
11const _ALPHABET: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
15
16pub fn encode(data: &[u8]) -> String {
27 bs58::encode(data).with_alphabet(bs58::Alphabet::BITCOIN).into_string()
28}
29
30pub fn decode(s: &str) -> Result<Vec<u8>, PrimitivesError> {
40 bs58::decode(s)
41 .with_alphabet(bs58::Alphabet::BITCOIN)
42 .into_vec()
43 .map_err(|e| PrimitivesError::InvalidBase58(e.to_string()))
44}
45
46pub fn check_encode(data: &[u8]) -> String {
57 let checksum = sha256d(data);
58 let mut payload = data.to_vec();
59 payload.extend_from_slice(&checksum[..4]);
60 encode(&payload)
61}
62
63pub fn check_decode(s: &str) -> Result<Vec<u8>, PrimitivesError> {
74 let decoded = decode(s)?;
75 if decoded.len() < 4 {
76 return Err(PrimitivesError::InvalidBase58(
77 "data too short for checksum".to_string()
78 ));
79 }
80 let (payload, checksum) = decoded.split_at(decoded.len() - 4);
81 let expected = sha256d(payload);
82 if checksum != &expected[..4] {
83 return Err(PrimitivesError::ChecksumMismatch);
84 }
85 Ok(payload.to_vec())
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
95 fn test_base58_empty_string() {
96 let input = hex::decode("").unwrap();
97 assert_eq!(encode(&input), "");
98 let decoded = decode("").unwrap();
99 assert_eq!(decoded, input);
100 }
101
102 #[test]
103 fn test_base58_single_zero_byte() {
104 let input = hex::decode("00").unwrap();
105 assert_eq!(encode(&input), "1");
106 let decoded = decode("1").unwrap();
107 assert_eq!(decoded, input);
108 }
109
110 #[test]
111 fn test_base58_decoded_address() {
112 let input = hex::decode("00010966776006953D5567439E5E39F86A0D273BEED61967F6").unwrap();
113 let encoded = encode(&input);
114 assert_eq!(encoded, "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM");
115 let decoded = decode("16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM").unwrap();
116 assert_eq!(decoded, input);
117 }
118
119 #[test]
120 fn test_base58_decoded_hash() {
121 let input = hex::decode("0123456789ABCDEF").unwrap();
122 let encoded = encode(&input);
123 assert_eq!(encoded, "C3CPq7c8PY");
124 let decoded = decode("C3CPq7c8PY").unwrap();
125 assert_eq!(decoded, input);
126 }
127
128 #[test]
129 fn test_base58_leading_zeros() {
130 let input = hex::decode("000000287FB4CD").unwrap();
131 let encoded = encode(&input);
132 assert_eq!(encoded, "111233QC4");
133 let decoded = decode("111233QC4").unwrap();
134 assert_eq!(decoded, input);
135 }
136
137 #[test]
140 fn test_base58_decode_invalid_character() {
141 assert!(decode("invalid!@#$%").is_err());
142 }
143
144 #[test]
145 fn test_base58_decode_mixed_valid_invalid() {
146 assert!(decode("1234!@#$%").is_err());
147 }
148
149 #[test]
152 fn test_base58_encode_nil_input() {
153 assert_eq!(encode(&[]), "");
154 }
155
156 #[test]
157 fn test_base58_encode_all_zeros() {
158 assert_eq!(encode(&[0, 0, 0, 0]), "1111");
159 }
160
161 #[test]
162 fn test_base58_encode_large_number() {
163 assert_eq!(encode(&[255, 255, 255, 255]), "7YXq9G");
164 }
165
166 #[test]
169 fn test_base58_check_roundtrip() {
170 let payload = hex::decode("00f54a5851e9372b87810a8e60cdd2e7cfd80b6e31").unwrap();
171 let encoded = check_encode(&payload);
172 let decoded = check_decode(&encoded).unwrap();
173 assert_eq!(decoded, payload);
174 }
175
176 #[test]
177 fn test_base58_check_bad_checksum() {
178 let payload = vec![0x80, 0x01, 0x02, 0x03];
180 let mut encoded = check_encode(&payload);
181 let last = encoded.pop().unwrap();
182 let replacement = if last == '1' { '2' } else { '1' };
183 encoded.push(replacement);
184 assert!(check_decode(&encoded).is_err());
185 }
186}