Skip to content

Commit e16ce60

Browse files
basenc: implement --base58 encoding option (#8751)
* 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. * basenc: fix clippy warnings and spelling issues Fix explicit iteration clippy warnings. Add Base58 alphabet to spell-checker ignore list to resolve cspell errors.
1 parent c0cdd90 commit e16ce60

File tree

6 files changed

+177
-15
lines changed

6 files changed

+177
-15
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: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ 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,
16-
Z85Wrapper,
15+
BASE2LSBF, BASE2MSBF, Base58Wrapper, Base64SimdWrapper, EncodingWrapper, Format,
16+
SupportsFastDecodeAndEncode, Z85Wrapper,
1717
for_base_common::{BASE32, BASE32HEX, BASE64URL, HEXUPPER_PERMISSIVE},
1818
};
1919
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
@@ -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: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
// spell-checker:ignore (encodings) lsbf msbf
77
// spell-checker:ignore unpadded
8+
// spell-checker:ignore ABCDEFGHJKLMNPQRSTUVWXY Zabcdefghijkmnopqrstuvwxyz
89

910
use crate::error::{UResult, USimpleError};
1011
use base64_simd;
@@ -105,6 +106,7 @@ pub enum Format {
105106
Base2Lsbf,
106107
Base2Msbf,
107108
Z85,
109+
Base58,
108110
}
109111

110112
pub const BASE2LSBF: Encoding = new_encoding! {
@@ -119,6 +121,8 @@ pub const BASE2MSBF: Encoding = new_encoding! {
119121

120122
pub struct Z85Wrapper {}
121123

124+
pub struct Base58Wrapper {}
125+
122126
pub struct EncodingWrapper {
123127
pub alphabet: &'static [u8],
124128
pub encoding: Encoding,
@@ -181,6 +185,142 @@ pub trait SupportsFastDecodeAndEncode {
181185
fn valid_decoding_multiple(&self) -> usize;
182186
}
183187

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

tests/by-util/test_basenc.rs

Lines changed: 31 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!")
201+
.succeeds()
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")
201210
.succeeds()
202-
.stdout_only("nm=QNz.92jz/PV8\n");
211+
.stdout_only("Hello, World!");
203212
}
204213

205214
#[test]
@@ -238,6 +247,15 @@ 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(&["--base64", "--base32", "--base16", "--z85", "--base58"])
254+
.pipe_in("Hello!")
255+
.succeeds()
256+
.stdout_only("d3yC1LKr\n");
257+
}
258+
241259
#[test]
242260
fn test_base32_decode_repeated() {
243261
new_ucmd!()

0 commit comments

Comments
 (0)