1use sha2::{Digest, Sha256};
2
3pub fn base58check_encode(bytes: &[u8]) -> String {
17 if bytes.len() == 0 {
18 return "".to_string();
19 }
20
21 let checksum = &calculate_checksum(bytes)[..4];
22 let bytes_with_checksum = [bytes, checksum].concat();
23 bs58::encode(bytes_with_checksum).into_string()
24}
25
26pub fn base58check_decode(input: &str) -> Option<Vec<u8>> {
40 let bytes_with_checksum = match bs58::decode(input).into_vec() {
41 Ok(bytes) => bytes,
42 Err(_) => return None,
43 };
44
45 if bytes_with_checksum.len() < 4 {
46 return None;
47 }
48
49 let (bytes, checksum) = bytes_with_checksum.split_at(bytes_with_checksum.len() - 4);
50 let expected_checksum = calculate_checksum(bytes);
51
52 if checksum != &expected_checksum[..4] {
53 return None;
54 }
55
56 Some(bytes.to_vec())
57}
58
59pub fn calculate_checksum(input: &[u8]) -> [u8; 4] {
73 let mut hasher = Sha256::new();
74 hasher.update(input);
75 let hash = hasher.finalize();
76 let hash256 = Sha256::digest(&hash);
77 hash256[..4]
78 .try_into()
79 .expect("Taking first 4 bytes of a SHA256 hash should never fail")
80}
81
82#[cfg(test)]
83mod base58_tests {
84 use super::*;
85
86 static VALID_STRING_DECODED_TO_ENCODED: &[(&str, &str)] = &[
88 ("", ""),
89 (" ", "Z"),
90 ("-", "n"),
91 ("0", "q"),
92 ("1", "r"),
93 ("-1", "4SU"),
94 ("11", "4k8"),
95 ("abc", "ZiCa"),
96 ("1234598760", "3mJr7AoUXx2Wqd"),
97 ("abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f"),
98 (
99 "00000000000000000000000000000000000000000000000000000000000000",
100 "3sN2THZeE9Eh9eYrwkvZqNstbHGvrxSAM7gXUXvyFQP8XvQLUqNCS27icwUeDT7ckHm4FUHM2mTVh1vbLmk7y",
101 ),
102 ];
103
104 static INVALID_STRINGS: &[&str] =
106 &["0", "O", "I", "l", "3mJr0", "O3yxU", "3sNI", "4kl8", "0OIl", "!@#$%^&*()-_=+~`"];
107
108 #[test]
109 fn test_base58_encoding_for_valid_strings() {
110 for (decoded, encoded) in VALID_STRING_DECODED_TO_ENCODED {
111 let bytes = decoded.as_bytes();
112 let result = bs58::encode(bytes).into_string();
113 assert_eq!(&result, *encoded);
114 }
115 }
116
117 #[test]
118 fn test_base58_decoding_for_valid_strings() {
119 for (decoded, encoded) in VALID_STRING_DECODED_TO_ENCODED {
120 let result = bs58::decode(encoded).into_vec().unwrap();
121 assert_eq!(result, Vec::from(*decoded));
122 }
123 }
124
125 #[test]
126 fn test_base58_decoding_for_invalid_strings() {
127 for invalid_string in INVALID_STRINGS {
128 let result = base58check_decode(invalid_string);
129 assert!(result.is_none());
130 }
131 }
132
133 #[test]
134 fn test_base58check_encoding() {
135 let input_data: Vec<u8> = vec![
136 6, 161, 159, 136, 34, 110, 33, 238, 14, 79, 14, 218, 133, 13, 109, 40, 194, 236, 153,
137 44, 61, 157, 254,
138 ];
139 let expected_output = "tz1Y3qqTg9HdrzZGbEjiCPmwuZ7fWVxpPtRw";
140 let actual_output = base58check_encode(&input_data);
141 assert_eq!(actual_output, expected_output);
142 }
143
144 #[test]
145 fn test_base58check_decoding() {
146 let input_string = "tz1Y3qqTg9HdrzZGbEjiCPmwuZ7fWVxpPtRw";
147 let expected_output_data: Vec<u8> = vec![
148 6, 161, 159, 136, 34, 110, 33, 238, 14, 79, 14, 218, 133, 13, 109, 40, 194, 236, 153,
149 44, 61, 157, 254,
150 ];
151 let actual_output = base58check_decode(input_string);
152 assert_eq!(actual_output, Some(expected_output_data));
153 }
154
155 #[test]
156 fn test_base58check_decoding_with_invalid_characters() {
157 assert!(base58check_decode("0oO1lL").is_none());
158 }
159
160 #[test]
161 fn test_base58check_decoding_with_invalid_checksum() {
162 assert!(base58check_decode("tz1Y3qqTg9HdrzZGbEjiCPmwuZ7fWVxpPtrW").is_none());
163 }
164}