neo3/neo_crypto/
base58_helper.rs

1use sha2::{Digest, Sha256};
2
3/// Encodes a byte slice into a base58check string.
4///
5/// # Arguments
6///
7/// * `bytes` - A byte slice to be encoded.
8///
9/// # Example
10///
11/// ```
12/// use neo3::neo_crypto::base58check_encode;
13/// let bytes = [0x01, 0x02, 0x03];
14/// let encoded = base58check_encode(&bytes);
15/// ```
16pub 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
26/// Decodes a base58check string into a byte vector.
27///
28/// # Arguments
29///
30/// * `input` - A base58check string to be decoded.
31///
32/// # Example
33///
34/// ```
35/// use neo3::neo_crypto::base58check_decode;
36/// let input = "Abc123";
37/// let decoded = base58check_decode(input);
38/// ```
39pub 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
59/// Calculates the checksum of a byte slice.
60///
61/// # Arguments
62///
63/// * `input` - A byte slice to calculate the checksum for.
64///
65/// # Example
66///
67/// ```
68/// use neo3::neo_crypto::calculate_checksum;
69/// let bytes = [0x01, 0x02, 0x03];
70/// let checksum = calculate_checksum(&bytes);
71/// ```
72pub 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	// Define tuples of arbitrary strings that are mapped to valid Base58 encodings
87	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	// Define invalid strings
105	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}