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