Skip to content

Commit d536436

Browse files
basenc: implement --base58 encoding option
Add support for Base58 encoding to basenc as per GNU coreutils 9.8. Base58 uses the alphabet '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' which excludes visually ambiguous characters (0, O, I, l). Resolves issue #8744.
1 parent 32eef06 commit d536436

File tree

6 files changed

+179
-14
lines changed

6 files changed

+179
-14
lines changed

src/uu/base32/locales/en-US.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ basenc-help-base2msbf = bit string with most significant bit (msb) first
4242
basenc-help-z85 = ascii85-like encoding;
4343
when encoding, input length must be a multiple of 4;
4444
when decoding, input length must be a multiple of 5
45+
basenc-help-base58 = visually unambiguous base58 encoding
4546
4647
# Error messages
4748
basenc-error-missing-encoding-type = missing encoding type

src/uu/base32/locales/fr-FR.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ basenc-help-base2msbf = chaîne de bits avec le bit de poids fort (msb) en premi
3737
basenc-help-z85 = encodage de type ascii85 ;
3838
lors de l'encodage, la longueur d'entrée doit être un multiple de 4 ;
3939
lors du décodage, la longueur d'entrée doit être un multiple de 5
40+
basenc-help-base58 = encodage base58 visuellement non ambigu
4041
4142
# Messages d'erreur
4243
basenc-error-missing-encoding-type = type d'encodage manquant

src/uu/base32/src/base_common.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use std::io::{self, ErrorKind, Read, Seek};
1212
use std::path::{Path, PathBuf};
1313
use uucore::display::Quotable;
1414
use uucore::encoding::{
15-
BASE2LSBF, BASE2MSBF, Base64SimdWrapper, EncodingWrapper, Format, SupportsFastDecodeAndEncode,
15+
BASE2LSBF, BASE2MSBF, Base64SimdWrapper, Base58Wrapper, EncodingWrapper, Format, SupportsFastDecodeAndEncode,
1616
Z85Wrapper,
1717
for_base_common::{BASE32, BASE32HEX, BASE64URL, HEXUPPER_PERMISSIVE},
1818
};
@@ -285,6 +285,7 @@ pub fn get_supports_fast_decode_and_encode(
285285
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=_-",
286286
)),
287287
Format::Z85 => Box::from(Z85Wrapper {}),
288+
Format::Base58 => Box::from(Base58Wrapper {}),
288289
}
289290
}
290291

src/uu/basenc/src/basenc.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ fn get_encodings() -> Vec<(&'static str, Format, String)> {
3939
translate!("basenc-help-base2msbf"),
4040
),
4141
("z85", Format::Z85, translate!("basenc-help-z85")),
42+
("base58", Format::Base58, translate!("basenc-help-base58")),
4243
]
4344
}
4445

src/uucore/src/lib/features/encoding.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ pub enum Format {
105105
Base2Lsbf,
106106
Base2Msbf,
107107
Z85,
108+
Base58,
108109
}
109110

110111
pub const BASE2LSBF: Encoding = new_encoding! {
@@ -119,6 +120,8 @@ pub const BASE2MSBF: Encoding = new_encoding! {
119120

120121
pub struct Z85Wrapper {}
121122

123+
pub struct Base58Wrapper {}
124+
122125
pub struct EncodingWrapper {
123126
pub alphabet: &'static [u8],
124127
pub encoding: Encoding,
@@ -181,6 +184,140 @@ pub trait SupportsFastDecodeAndEncode {
181184
fn valid_decoding_multiple(&self) -> usize;
182185
}
183186

187+
impl SupportsFastDecodeAndEncode for Base58Wrapper {
188+
fn alphabet(&self) -> &'static [u8] {
189+
// Base58 alphabet
190+
b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
191+
}
192+
193+
fn decode_into_vec(&self, input: &[u8], output: &mut Vec<u8>) -> UResult<()> {
194+
if input.is_empty() {
195+
return Ok(());
196+
}
197+
198+
// Count leading zeros (will become leading 1s in base58)
199+
let leading_ones = input.iter().take_while(|&&b| b == b'1').count();
200+
201+
// Skip leading 1s for conversion
202+
let input_trimmed = &input[leading_ones..];
203+
if input_trimmed.is_empty() {
204+
output.resize(output.len() + leading_ones, 0);
205+
return Ok(());
206+
}
207+
208+
// Convert base58 to big integer
209+
let mut num: Vec<u32> = vec![0];
210+
let alphabet = self.alphabet();
211+
212+
for &byte in input_trimmed {
213+
// Find position in alphabet
214+
let digit = alphabet.iter().position(|&b| b == byte)
215+
.ok_or_else(|| USimpleError::new(1, "error: invalid input".to_owned()))?;
216+
217+
// Multiply by 58 and add digit
218+
let mut carry = digit as u32;
219+
for n in num.iter_mut() {
220+
let tmp = (*n as u64) * 58 + carry as u64;
221+
*n = tmp as u32;
222+
carry = (tmp >> 32) as u32;
223+
}
224+
if carry > 0 {
225+
num.push(carry);
226+
}
227+
}
228+
229+
// Convert to bytes (little endian, then reverse)
230+
let mut result = Vec::new();
231+
for &n in num.iter() {
232+
result.extend_from_slice(&n.to_le_bytes());
233+
}
234+
235+
// Remove trailing zeros and reverse to get big endian
236+
while result.last() == Some(&0) && result.len() > 1 {
237+
result.pop();
238+
}
239+
result.reverse();
240+
241+
// Add leading zeros for leading 1s in input
242+
let mut final_result = vec![0; leading_ones];
243+
final_result.extend_from_slice(&result);
244+
245+
output.extend_from_slice(&final_result);
246+
Ok(())
247+
}
248+
249+
fn encode_to_vec_deque(&self, input: &[u8], output: &mut VecDeque<u8>) -> UResult<()> {
250+
if input.is_empty() {
251+
return Ok(());
252+
}
253+
254+
// Count leading zeros
255+
let leading_zeros = input.iter().take_while(|&&b| b == 0).count();
256+
257+
// Skip leading zeros
258+
let input_trimmed = &input[leading_zeros..];
259+
if input_trimmed.is_empty() {
260+
for _ in 0..leading_zeros {
261+
output.push_back(b'1');
262+
}
263+
return Ok(());
264+
}
265+
266+
// Convert bytes to big integer
267+
let mut num: Vec<u32> = Vec::new();
268+
for &byte in input_trimmed {
269+
let mut carry = byte as u32;
270+
for n in num.iter_mut() {
271+
let tmp = (*n as u64) * 256 + carry as u64;
272+
*n = tmp as u32;
273+
carry = (tmp >> 32) as u32;
274+
}
275+
if carry > 0 {
276+
num.push(carry);
277+
}
278+
}
279+
280+
// Convert to base58
281+
let mut result = Vec::new();
282+
let alphabet = self.alphabet();
283+
284+
while !num.is_empty() && num.iter().any(|&n| n != 0) {
285+
let mut carry = 0u64;
286+
for n in num.iter_mut().rev() {
287+
let tmp = carry * (1u64 << 32) + *n as u64;
288+
*n = (tmp / 58) as u32;
289+
carry = tmp % 58;
290+
}
291+
result.push(alphabet[carry as usize]);
292+
293+
// Remove leading zeros
294+
while num.last() == Some(&0) && num.len() > 1 {
295+
num.pop();
296+
}
297+
}
298+
299+
// Add leading 1s for leading zeros in input
300+
for _ in 0..leading_zeros {
301+
output.push_back(b'1');
302+
}
303+
304+
// Add result (reversed because we built it backwards)
305+
for byte in result.into_iter().rev() {
306+
output.push_back(byte);
307+
}
308+
309+
Ok(())
310+
}
311+
312+
fn unpadded_multiple(&self) -> usize {
313+
1 // Base58 doesn't use padding
314+
}
315+
316+
fn valid_decoding_multiple(&self) -> usize {
317+
1 // Any length is valid for Base58
318+
}
319+
}
320+
184321
impl SupportsFastDecodeAndEncode for Z85Wrapper {
185322
fn alphabet(&self) -> &'static [u8] {
186323
// Z85 alphabet

tests/by-util/test_basenc.rs

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -185,21 +185,30 @@ fn test_base2lsbf_decode() {
185185
}
186186

187187
#[test]
188-
fn test_choose_last_encoding_z85() {
188+
fn test_z85_decode() {
189189
new_ucmd!()
190-
.args(&[
191-
"--base2lsbf",
192-
"--base2msbf",
193-
"--base16",
194-
"--base32hex",
195-
"--base64url",
196-
"--base32",
197-
"--base64",
198-
"--z85",
199-
])
200-
.pipe_in("Hello, World")
190+
.args(&["--z85", "-d"])
191+
.pipe_in("nm=QNz.92jz/PV8")
192+
.succeeds()
193+
.stdout_only("Hello, World");
194+
}
195+
196+
#[test]
197+
fn test_base58() {
198+
new_ucmd!()
199+
.arg("--base58")
200+
.pipe_in("Hello, World!")
201201
.succeeds()
202-
.stdout_only("nm=QNz.92jz/PV8\n");
202+
.stdout_only("72k1xXWG59fYdzSNoA\n");
203+
}
204+
205+
#[test]
206+
fn test_base58_decode() {
207+
new_ucmd!()
208+
.args(&["--base58", "-d"])
209+
.pipe_in("72k1xXWG59fYdzSNoA")
210+
.succeeds()
211+
.stdout_only("Hello, World!");
203212
}
204213

205214
#[test]
@@ -238,6 +247,21 @@ fn test_choose_last_encoding_base2lsbf() {
238247
.stdout_only("00110110110011100100011001100110\n");
239248
}
240249

250+
#[test]
251+
fn test_choose_last_encoding_base58() {
252+
new_ucmd!()
253+
.args(&[
254+
"--base64",
255+
"--base32",
256+
"--base16",
257+
"--z85",
258+
"--base58",
259+
])
260+
.pipe_in("Hello!")
261+
.succeeds()
262+
.stdout_only("d3yC1LKr\n");
263+
}
264+
241265
#[test]
242266
fn test_base32_decode_repeated() {
243267
new_ucmd!()

0 commit comments

Comments
 (0)