1use super::error::PrimitivesError;
7use super::hash::hash256;
8
9const BASE58_ALPHABET: &[u8; 58] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
11
12pub fn to_hex(bytes: &[u8]) -> String {
14 bytes.iter().map(|b| format!("{b:02x}")).collect()
15}
16
17pub fn from_hex(hex: &str) -> Result<Vec<u8>, PrimitivesError> {
19 if !hex.len().is_multiple_of(2) {
20 return Err(PrimitivesError::InvalidHex(
21 "odd length hex string".to_string(),
22 ));
23 }
24 (0..hex.len())
25 .step_by(2)
26 .map(|i| {
27 u8::from_str_radix(&hex[i..i + 2], 16).map_err(|e| {
28 PrimitivesError::InvalidHex(format!("invalid hex char at position {i}: {e}"))
29 })
30 })
31 .collect()
32}
33
34pub fn base58_encode(data: &[u8]) -> String {
39 let leading_zeros = data.iter().take_while(|&&b| b == 0).count();
41
42 let mut result: Vec<u8> = Vec::new();
45
46 for &byte in data.iter() {
47 let mut carry = byte as u32;
48 for digit in result.iter_mut() {
49 let x = (*digit as u32) * 256 + carry;
50 *digit = (x % 58) as u8;
51 carry = x / 58;
52 }
53 while carry > 0 {
54 result.push((carry % 58) as u8);
55 carry /= 58;
56 }
57 }
58
59 let mut s = String::with_capacity(leading_zeros + result.len());
61
62 for _ in 0..leading_zeros {
64 s.push('1');
65 }
66
67 for &digit in result.iter().rev() {
69 s.push(BASE58_ALPHABET[digit as usize] as char);
70 }
71
72 s
73}
74
75pub fn base58_decode(s: &str) -> Result<Vec<u8>, PrimitivesError> {
79 if s.is_empty() {
80 return Err(PrimitivesError::InvalidFormat(
81 "empty base58 string".to_string(),
82 ));
83 }
84
85 let mut alphabet_map = [255u8; 128];
87 for (i, &ch) in BASE58_ALPHABET.iter().enumerate() {
88 alphabet_map[ch as usize] = i as u8;
89 }
90
91 let leading_ones = s.chars().take_while(|&c| c == '1').count();
93
94 let size = ((s.len() - leading_ones) as f64 * (58.0_f64.ln() / 256.0_f64.ln()) + 1.0) as usize;
96 let mut result = vec![0u8; size];
97
98 for ch in s.chars() {
99 let ch_val = ch as usize;
100 if ch_val >= 128 || alphabet_map[ch_val] == 255 {
101 return Err(PrimitivesError::InvalidFormat(format!(
102 "invalid base58 character: {ch}"
103 )));
104 }
105 let mut carry = alphabet_map[ch_val] as u32;
106 for byte in result.iter_mut() {
107 let x = (*byte as u32) * 58 + carry;
108 *byte = (x & 0xff) as u8;
109 carry = x >> 8;
110 }
111 }
112
113 result.reverse();
115 let skip = result.iter().take_while(|&&b| b == 0).count();
116 let result = &result[skip..];
117
118 let mut output = vec![0u8; leading_ones];
120 output.extend_from_slice(result);
121
122 Ok(output)
123}
124
125pub fn base58_check_encode(payload: &[u8], prefix: &[u8]) -> String {
130 let mut data = Vec::with_capacity(prefix.len() + payload.len() + 4);
131 data.extend_from_slice(prefix);
132 data.extend_from_slice(payload);
133
134 let checksum = hash256(&data);
135 data.extend_from_slice(&checksum[..4]);
136
137 base58_encode(&data)
138}
139
140pub fn base58_check_decode(
145 s: &str,
146 prefix_length: usize,
147) -> Result<(Vec<u8>, Vec<u8>), PrimitivesError> {
148 let bin = base58_decode(s)?;
149
150 if bin.len() < prefix_length + 4 {
151 return Err(PrimitivesError::InvalidFormat(
152 "base58check data too short".to_string(),
153 ));
154 }
155
156 let prefix = bin[..prefix_length].to_vec();
157 let payload = bin[prefix_length..bin.len() - 4].to_vec();
158 let checksum = &bin[bin.len() - 4..];
159
160 let mut hash_input = Vec::with_capacity(prefix.len() + payload.len());
162 hash_input.extend_from_slice(&prefix);
163 hash_input.extend_from_slice(&payload);
164 let expected_checksum = hash256(&hash_input);
165
166 if checksum != &expected_checksum[..4] {
167 return Err(PrimitivesError::ChecksumMismatch);
168 }
169
170 Ok((prefix, payload))
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
182 fn test_hex_encode_decode_roundtrip() {
183 let data = vec![0x00, 0x01, 0xff, 0xab, 0xcd];
184 let hex = to_hex(&data);
185 assert_eq!(hex, "0001ffabcd");
186 let decoded = from_hex(&hex).unwrap();
187 assert_eq!(decoded, data);
188 }
189
190 #[test]
191 fn test_hex_empty() {
192 assert_eq!(to_hex(&[]), "");
193 assert_eq!(from_hex("").unwrap(), Vec::<u8>::new());
194 }
195
196 #[test]
197 fn test_hex_odd_length() {
198 assert!(from_hex("abc").is_err());
199 }
200
201 #[test]
202 fn test_hex_invalid_char() {
203 assert!(from_hex("gg").is_err());
204 }
205
206 #[test]
211 fn test_base58_encode_known_vector() {
212 let data = b"Hello World";
214 let encoded = base58_encode(data);
215 assert_eq!(encoded, "JxF12TrwUP45BMd");
216 }
217
218 #[test]
219 fn test_base58_decode_known_vector() {
220 let decoded = base58_decode("JxF12TrwUP45BMd").unwrap();
221 assert_eq!(decoded, b"Hello World");
222 }
223
224 #[test]
225 fn test_base58_roundtrip() {
226 let test_cases: Vec<&[u8]> =
227 vec![b"", &[0], &[0, 0, 0], &[0, 0, 0, 1], b"test", &[0xff; 32]];
228 for data in test_cases.iter().skip(1) {
230 let encoded = base58_encode(data);
231 let decoded = base58_decode(&encoded).unwrap();
232 assert_eq!(&decoded, data, "Base58 roundtrip failed for {data:?}");
233 }
234 }
235
236 #[test]
237 fn test_base58_leading_zeros() {
238 let data = vec![0, 0, 0, 1];
240 let encoded = base58_encode(&data);
241 assert!(
242 encoded.starts_with("111"),
243 "Expected 3 leading '1's for 3 leading zero bytes, got: {encoded}"
244 );
245 let decoded = base58_decode(&encoded).unwrap();
246 assert_eq!(decoded, data);
247 }
248
249 #[test]
250 fn test_base58_invalid_char() {
251 assert!(base58_decode("0abc").is_err());
253 assert!(base58_decode("Oabc").is_err());
254 assert!(base58_decode("Iabc").is_err());
255 assert!(base58_decode("labc").is_err());
256 }
257
258 #[test]
263 fn test_base58_check_encode_decode_roundtrip() {
264 let payload = vec![0x01, 0x02, 0x03, 0x04];
265 let prefix = vec![0x00];
266
267 let encoded = base58_check_encode(&payload, &prefix);
268 let (dec_prefix, dec_payload) = base58_check_decode(&encoded, 1).unwrap();
269
270 assert_eq!(dec_prefix, prefix);
271 assert_eq!(dec_payload, payload);
272 }
273
274 #[test]
275 fn test_base58_check_bad_checksum() {
276 let payload = vec![0x01, 0x02, 0x03, 0x04];
277 let prefix = vec![0x00];
278
279 let encoded = base58_check_encode(&payload, &prefix);
280
281 let mut chars: Vec<char> = encoded.chars().collect();
283 let last = chars.len() - 1;
284 chars[last] = if chars[last] == '1' { '2' } else { '1' };
285 let tampered: String = chars.into_iter().collect();
286
287 assert!(
288 base58_check_decode(&tampered, 1).is_err(),
289 "Should fail with tampered checksum"
290 );
291 }
292
293 #[test]
294 fn test_base58_check_wif_known_vector() {
295 let wif = "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn";
299 let result = base58_check_decode(wif, 1);
300 assert!(result.is_ok(), "Known WIF should decode successfully");
301 let (prefix, payload) = result.unwrap();
302 assert_eq!(prefix, vec![0x80]);
303 assert_eq!(payload.len(), 33);
305 assert_eq!(payload[..31], vec![0u8; 31]);
307 assert_eq!(payload[31], 1);
308 assert_eq!(payload[32], 1);
310 }
311
312 #[test]
313 fn test_base58_check_encode_wif() {
314 let mut key_data = vec![0u8; 32];
316 key_data[31] = 1;
317 key_data.push(0x01); let prefix = vec![0x80];
319
320 let wif = base58_check_encode(&key_data, &prefix);
321 assert_eq!(wif, "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn");
322 }
323}