dup_crypto/bases/
b58.rs

1//  Copyright (C) 2020 Éloïs SANCHEZ.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as
5// published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16//! Provide base58 convertion tools
17
18use crate::bases::BaseConversionError;
19
20/// Convert to base58 string
21pub trait ToBase58 {
22    /// Convert to base58 string
23    fn to_base58(&self) -> String;
24}
25
26/// Create an array of 32 bytes from a Base58 string.
27pub fn str_base58_to_32bytes(base58_data: &str) -> Result<([u8; 32], u8), BaseConversionError> {
28    let mut source = base58_data;
29    let mut count_leading_1 = 0;
30    while !source.is_empty() && &source[0..1] == "1" {
31        source = &source[1..];
32        count_leading_1 += 1;
33    }
34
35    let mut u8_array = [0; 32];
36    match bs58::decode(source).into(&mut u8_array) {
37        Ok(written_len) => {
38            if written_len == 32 {
39                Ok((u8_array, count_leading_1))
40            } else {
41                let delta = 32 - written_len;
42                for i in (0..written_len).rev() {
43                    u8_array[i + delta] = u8_array[i];
44                }
45                #[allow(clippy::needless_range_loop)]
46                for i in 0..delta {
47                    u8_array[i] = 0;
48                }
49                Ok((u8_array, count_leading_1))
50            }
51        }
52        Err(bs58::decode::Error::InvalidCharacter { character, index }) => {
53            Err(BaseConversionError::InvalidCharacter {
54                character,
55                offset: index,
56            })
57        }
58        Err(bs58::decode::Error::BufferTooSmall) => str_base58_to_32bytes_vec(base58_data),
59        _ => Err(BaseConversionError::UnknownError),
60    }
61}
62
63// Create an array of 32 bytes from a Base58 string (use heap allocation)
64fn str_base58_to_32bytes_vec(base58_data: &str) -> Result<([u8; 32], u8), BaseConversionError> {
65    let mut source = base58_data;
66    let mut count_leading_1 = 0;
67    while !source.is_empty() && &source[0..1] == "1" {
68        source = &source[1..];
69        count_leading_1 += 1;
70    }
71
72    let mut u8_array = [0; 32];
73    match bs58::decode(source).into_vec() {
74        Ok(bytes) => {
75            let len = std::cmp::min(bytes.len(), 32);
76            u8_array[(32 - len)..].copy_from_slice(&bytes[..len]);
77            Ok((u8_array, count_leading_1))
78        }
79        Err(bs58::decode::Error::InvalidCharacter { character, index }) => {
80            Err(BaseConversionError::InvalidCharacter {
81                character,
82                offset: index,
83            })
84        }
85        Err(bs58::decode::Error::BufferTooSmall) => {
86            Err(BaseConversionError::InvalidBaseConverterLength)
87        }
88        _ => Err(BaseConversionError::UnknownError),
89    }
90}
91
92/// Create a Base58 string from a slice of bytes.
93pub fn bytes_to_str_base58(bytes: &[u8], count_leading_1: u8) -> String {
94    let mut str_base58 = String::new();
95    let mut remaining_leading_1 = count_leading_1;
96    while remaining_leading_1 > 0 {
97        remaining_leading_1 -= 1;
98        str_base58.push('1');
99    }
100    if count_leading_1 >= 32 {
101        return str_base58;
102    }
103
104    let bytes_len = bytes.len();
105    let mut i = 0;
106    while i < bytes_len && bytes[i] == 0 {
107        i += 1;
108    }
109    str_base58.push_str(&bs58::encode(&bytes[i..]).into_string());
110    str_base58
111}
112
113#[cfg(test)]
114mod tests {
115
116    use super::*;
117
118    #[test]
119    fn test_base_58_str_with_only_1() -> Result<(), BaseConversionError> {
120        let base58str = "11111111111111111111111111111111111111111111";
121
122        let (bytes, count_leading_1) = str_base58_to_32bytes(base58str)?;
123
124        assert_eq!(count_leading_1, 44);
125
126        println!("{:?}", bytes);
127
128        assert_eq!(base58str, &bytes_to_str_base58(&bytes[..], count_leading_1),);
129
130        Ok(())
131    }
132
133    #[test]
134    fn test_base_58_str_with_leading_1() -> Result<(), BaseConversionError> {
135        let base58str = "13fn6X3XWVgshHTgS8beZMo9XiyScx6MB6yPsBB5ZBia";
136
137        let (bytes, count_leading_1) = str_base58_to_32bytes(base58str)?;
138
139        println!("{:?}", bytes);
140
141        assert_eq!(base58str, &bytes_to_str_base58(&bytes[..], count_leading_1),);
142
143        Ok(())
144    }
145
146    #[test]
147    fn test_other_base_58_str_with_leading_1() -> Result<(), BaseConversionError> {
148        let base58str = "1V27SH9TiVEDs8TWFPydpRKxhvZari7wjGwQnPxMnkr";
149
150        let (bytes, count_leading_1) = str_base58_to_32bytes(base58str)?;
151
152        println!("{:?}", bytes);
153
154        assert_eq!(base58str, &bytes_to_str_base58(&bytes[..], count_leading_1),);
155
156        Ok(())
157    }
158
159    #[test]
160    fn test_third_base_58_str_with_leading_1() -> Result<(), BaseConversionError> {
161        let base58str = "1XoFs76G4yidvVY3FZBwYyLXTMjabryhFD8mNQPkQKHk";
162
163        let (bytes, count_leading_1) = str_base58_to_32bytes(base58str)?;
164
165        println!("{:?}", bytes);
166
167        assert_eq!(base58str, &bytes_to_str_base58(&bytes[..], count_leading_1),);
168
169        Ok(())
170    }
171
172    #[test]
173    fn test_base_58_str_with_43_char() -> Result<(), BaseConversionError> {
174        let base58str = "2nV7Dv4nhTJ9dZUvRJpL34vFP9b2BkDjKWv9iBW2JaR";
175
176        let (bytes, count_leading_1) = str_base58_to_32bytes(base58str)?;
177
178        println!("{}", count_leading_1);
179        println!("{:?}", bytes);
180
181        assert_eq!(base58str, &bytes_to_str_base58(&bytes[..], count_leading_1),);
182
183        Ok(())
184    }
185
186    #[test]
187    fn test_invalid_pubkey_of_33_bytes() -> Result<(), BaseConversionError> {
188        str_base58_to_32bytes("jUPLL2BgY2QpheWEY3R13edV2Y4tvQMCXjJVM8PGDvyd")?;
189        Ok(())
190    }
191}
192
193/*/// Create an array of 64bytes from a Base58 string.
194pub fn str_base58_to_64bytes(base58_data: &str) -> Result<[u8; 64], BaseConvertionError> {
195    match base58_data.from_base58() {
196        Ok(result) => {
197            if result.len() == 64 {
198                let mut u8_array = [0; 64];
199
200                u8_array[..64].clone_from_slice(&result[..64]);
201
202                Ok(u8_array)
203            } else {
204                Err(BaseConvertionError::InvalidLength {
205                    expected: 64,
206                    found: result.len(),
207                })
208            }
209        }
210        Err(FromBase58Error::InvalidBase58Character(character, offset)) => {
211            Err(BaseConvertionError::InvalidCharacter { character, offset })
212        }
213        Err(FromBase58Error::InvalidBase58Length) => {
214            Err(BaseConvertionError::InvalidBaseConverterLength)
215        }
216    }
217}*/