mirror of
https://github.com/uutils/coreutils.git
synced 2025-12-23 08:47:37 +00:00
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.
This commit is contained in:
parent
c0cdd904b8
commit
e16ce60a38
6 changed files with 177 additions and 15 deletions
|
|
@ -42,6 +42,7 @@ basenc-help-base2msbf = bit string with most significant bit (msb) first
|
|||
basenc-help-z85 = ascii85-like encoding;
|
||||
when encoding, input length must be a multiple of 4;
|
||||
when decoding, input length must be a multiple of 5
|
||||
basenc-help-base58 = visually unambiguous base58 encoding
|
||||
|
||||
# Error messages
|
||||
basenc-error-missing-encoding-type = missing encoding type
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ basenc-help-base2msbf = chaîne de bits avec le bit de poids fort (msb) en premi
|
|||
basenc-help-z85 = encodage de type ascii85 ;
|
||||
lors de l'encodage, la longueur d'entrée doit être un multiple de 4 ;
|
||||
lors du décodage, la longueur d'entrée doit être un multiple de 5
|
||||
basenc-help-base58 = encodage base58 visuellement non ambigu
|
||||
|
||||
# Messages d'erreur
|
||||
basenc-error-missing-encoding-type = type d'encodage manquant
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ use std::io::{self, ErrorKind, Read, Seek};
|
|||
use std::path::{Path, PathBuf};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::encoding::{
|
||||
BASE2LSBF, BASE2MSBF, Base64SimdWrapper, EncodingWrapper, Format, SupportsFastDecodeAndEncode,
|
||||
Z85Wrapper,
|
||||
BASE2LSBF, BASE2MSBF, Base58Wrapper, Base64SimdWrapper, EncodingWrapper, Format,
|
||||
SupportsFastDecodeAndEncode, Z85Wrapper,
|
||||
for_base_common::{BASE32, BASE32HEX, BASE64URL, HEXUPPER_PERMISSIVE},
|
||||
};
|
||||
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
|
||||
|
|
@ -285,6 +285,7 @@ pub fn get_supports_fast_decode_and_encode(
|
|||
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=_-",
|
||||
)),
|
||||
Format::Z85 => Box::from(Z85Wrapper {}),
|
||||
Format::Base58 => Box::from(Base58Wrapper {}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ fn get_encodings() -> Vec<(&'static str, Format, String)> {
|
|||
translate!("basenc-help-base2msbf"),
|
||||
),
|
||||
("z85", Format::Z85, translate!("basenc-help-z85")),
|
||||
("base58", Format::Base58, translate!("basenc-help-base58")),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
// spell-checker:ignore (encodings) lsbf msbf
|
||||
// spell-checker:ignore unpadded
|
||||
// spell-checker:ignore ABCDEFGHJKLMNPQRSTUVWXY Zabcdefghijkmnopqrstuvwxyz
|
||||
|
||||
use crate::error::{UResult, USimpleError};
|
||||
use base64_simd;
|
||||
|
|
@ -105,6 +106,7 @@ pub enum Format {
|
|||
Base2Lsbf,
|
||||
Base2Msbf,
|
||||
Z85,
|
||||
Base58,
|
||||
}
|
||||
|
||||
pub const BASE2LSBF: Encoding = new_encoding! {
|
||||
|
|
@ -119,6 +121,8 @@ pub const BASE2MSBF: Encoding = new_encoding! {
|
|||
|
||||
pub struct Z85Wrapper {}
|
||||
|
||||
pub struct Base58Wrapper {}
|
||||
|
||||
pub struct EncodingWrapper {
|
||||
pub alphabet: &'static [u8],
|
||||
pub encoding: Encoding,
|
||||
|
|
@ -181,6 +185,142 @@ pub trait SupportsFastDecodeAndEncode {
|
|||
fn valid_decoding_multiple(&self) -> usize;
|
||||
}
|
||||
|
||||
impl SupportsFastDecodeAndEncode for Base58Wrapper {
|
||||
fn alphabet(&self) -> &'static [u8] {
|
||||
// Base58 alphabet
|
||||
b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
}
|
||||
|
||||
fn decode_into_vec(&self, input: &[u8], output: &mut Vec<u8>) -> UResult<()> {
|
||||
if input.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Count leading zeros (will become leading 1s in base58)
|
||||
let leading_ones = input.iter().take_while(|&&b| b == b'1').count();
|
||||
|
||||
// Skip leading 1s for conversion
|
||||
let input_trimmed = &input[leading_ones..];
|
||||
if input_trimmed.is_empty() {
|
||||
output.resize(output.len() + leading_ones, 0);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Convert base58 to big integer
|
||||
let mut num: Vec<u32> = vec![0];
|
||||
let alphabet = self.alphabet();
|
||||
|
||||
for &byte in input_trimmed {
|
||||
// Find position in alphabet
|
||||
let digit = alphabet
|
||||
.iter()
|
||||
.position(|&b| b == byte)
|
||||
.ok_or_else(|| USimpleError::new(1, "error: invalid input".to_owned()))?;
|
||||
|
||||
// Multiply by 58 and add digit
|
||||
let mut carry = digit as u32;
|
||||
for n in &mut num {
|
||||
let tmp = (*n as u64) * 58 + carry as u64;
|
||||
*n = tmp as u32;
|
||||
carry = (tmp >> 32) as u32;
|
||||
}
|
||||
if carry > 0 {
|
||||
num.push(carry);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to bytes (little endian, then reverse)
|
||||
let mut result = Vec::new();
|
||||
for &n in &num {
|
||||
result.extend_from_slice(&n.to_le_bytes());
|
||||
}
|
||||
|
||||
// Remove trailing zeros and reverse to get big endian
|
||||
while result.last() == Some(&0) && result.len() > 1 {
|
||||
result.pop();
|
||||
}
|
||||
result.reverse();
|
||||
|
||||
// Add leading zeros for leading 1s in input
|
||||
let mut final_result = vec![0; leading_ones];
|
||||
final_result.extend_from_slice(&result);
|
||||
|
||||
output.extend_from_slice(&final_result);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encode_to_vec_deque(&self, input: &[u8], output: &mut VecDeque<u8>) -> UResult<()> {
|
||||
if input.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Count leading zeros
|
||||
let leading_zeros = input.iter().take_while(|&&b| b == 0).count();
|
||||
|
||||
// Skip leading zeros
|
||||
let input_trimmed = &input[leading_zeros..];
|
||||
if input_trimmed.is_empty() {
|
||||
for _ in 0..leading_zeros {
|
||||
output.push_back(b'1');
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Convert bytes to big integer
|
||||
let mut num: Vec<u32> = Vec::new();
|
||||
for &byte in input_trimmed {
|
||||
let mut carry = byte as u32;
|
||||
for n in &mut num {
|
||||
let tmp = (*n as u64) * 256 + carry as u64;
|
||||
*n = tmp as u32;
|
||||
carry = (tmp >> 32) as u32;
|
||||
}
|
||||
if carry > 0 {
|
||||
num.push(carry);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to base58
|
||||
let mut result = Vec::new();
|
||||
let alphabet = self.alphabet();
|
||||
|
||||
while !num.is_empty() && num.iter().any(|&n| n != 0) {
|
||||
let mut carry = 0u64;
|
||||
for n in num.iter_mut().rev() {
|
||||
let tmp = carry * (1u64 << 32) + *n as u64;
|
||||
*n = (tmp / 58) as u32;
|
||||
carry = tmp % 58;
|
||||
}
|
||||
result.push(alphabet[carry as usize]);
|
||||
|
||||
// Remove leading zeros
|
||||
while num.last() == Some(&0) && num.len() > 1 {
|
||||
num.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// Add leading 1s for leading zeros in input
|
||||
for _ in 0..leading_zeros {
|
||||
output.push_back(b'1');
|
||||
}
|
||||
|
||||
// Add result (reversed because we built it backwards)
|
||||
for byte in result.into_iter().rev() {
|
||||
output.push_back(byte);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unpadded_multiple(&self) -> usize {
|
||||
1 // Base58 doesn't use padding
|
||||
}
|
||||
|
||||
fn valid_decoding_multiple(&self) -> usize {
|
||||
1 // Any length is valid for Base58
|
||||
}
|
||||
}
|
||||
|
||||
impl SupportsFastDecodeAndEncode for Z85Wrapper {
|
||||
fn alphabet(&self) -> &'static [u8] {
|
||||
// Z85 alphabet
|
||||
|
|
|
|||
|
|
@ -185,21 +185,30 @@ fn test_base2lsbf_decode() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_choose_last_encoding_z85() {
|
||||
fn test_z85_decode() {
|
||||
new_ucmd!()
|
||||
.args(&[
|
||||
"--base2lsbf",
|
||||
"--base2msbf",
|
||||
"--base16",
|
||||
"--base32hex",
|
||||
"--base64url",
|
||||
"--base32",
|
||||
"--base64",
|
||||
"--z85",
|
||||
])
|
||||
.pipe_in("Hello, World")
|
||||
.args(&["--z85", "-d"])
|
||||
.pipe_in("nm=QNz.92jz/PV8")
|
||||
.succeeds()
|
||||
.stdout_only("nm=QNz.92jz/PV8\n");
|
||||
.stdout_only("Hello, World");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_base58() {
|
||||
new_ucmd!()
|
||||
.arg("--base58")
|
||||
.pipe_in("Hello, World!")
|
||||
.succeeds()
|
||||
.stdout_only("72k1xXWG59fYdzSNoA\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_base58_decode() {
|
||||
new_ucmd!()
|
||||
.args(&["--base58", "-d"])
|
||||
.pipe_in("72k1xXWG59fYdzSNoA")
|
||||
.succeeds()
|
||||
.stdout_only("Hello, World!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -238,6 +247,15 @@ fn test_choose_last_encoding_base2lsbf() {
|
|||
.stdout_only("00110110110011100100011001100110\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_choose_last_encoding_base58() {
|
||||
new_ucmd!()
|
||||
.args(&["--base64", "--base32", "--base16", "--z85", "--base58"])
|
||||
.pipe_in("Hello!")
|
||||
.succeeds()
|
||||
.stdout_only("d3yC1LKr\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_base32_decode_repeated() {
|
||||
new_ucmd!()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue