crate::ix!();
pub fn decode_base58(input: &str, vch_ret: &mut Vec<u8>, max_ret_len: usize) -> bool {
use nom::character::is_space;
let input = input.trim();
let mut chars = input.chars().peekable();
let mut zeroes = 0;
while matches!(chars.peek(), Some('1')) {
zeroes += 1;
chars.next();
if zeroes > max_ret_len {
return false;
}
}
let base58_map = MAP_BASE58;
let mut b256: Vec<u8> = Vec::new();
while let Some(c) = chars.next() {
if is_space(c as u8) {
return false;
}
let carry = {
let idx = c as usize;
if idx >= 256 || base58_map[idx] == -1 {
return false; }
base58_map[idx] as u32
};
let mut carry = carry;
for val in b256.iter_mut().rev() {
let tmp = (*val as u32) * 58 + carry;
*val = (tmp & 0xFF) as u8;
carry = tmp >> 8;
}
while carry > 0 {
b256.insert(0, (carry & 0xFF) as u8);
carry >>= 8;
}
}
let length = b256.len();
if zeroes + length > max_ret_len {
return false;
}
vch_ret.clear();
vch_ret.resize(zeroes, 0);
vch_ret.extend_from_slice(&b256);
true
}
pub unsafe fn decode_base58_raw(
mut psz: *const u8,
vch: &mut Vec<u8>,
max_ret_len: i32
) -> bool {
while *psz != 0 && nom::character::is_space(*psz ){
psz = psz.add(1);
}
let mut zeroes: i32 = 0;
let mut length: i32 = 0;
while char::from(*psz) == '1' {
zeroes += 1;
if zeroes > max_ret_len {
return false;
}
psz = psz.add(1);
}
let size: usize = libc::strlen(psz as *const i8) * 733 / 1000 + 1;
let mut b256: Vec<u8> = vec![0; size];
const_assert!(MAP_BASE58.len() == 256);
while *psz != 0 && !nom::character::is_space(*psz){
let mut carry: i32 = MAP_BASE58[*psz as usize] as i32;
if carry == -1 {
return false;
}
let mut i: i32 = 0;
for val in b256.iter_mut().rev() {
if carry == 0 && i >= length {
break;
}
carry += (58 * (*val)) as i32;
*val = (carry % 256).try_into().unwrap();
carry /= 256;
i += 1;
}
assert!(carry == 0);
length = i;
if length + zeroes > max_ret_len {
return false
}
psz = psz.add(1);
}
while nom::character::is_space(*psz){
psz = psz.add(1);
}
if *psz != 0 {
return false
}
let mut it = b256.iter();
let offset: usize = (size - length as usize).try_into().unwrap();
it.advance_by(offset);
vch.reserve(zeroes as usize + it.len());
vch[0..zeroes as usize].fill(0);
let mut idx = size - length as usize;
while idx < b256.len() && b256[idx] == 0 {
idx += 1;
}
vch.clear();
vch.resize(zeroes as usize, 0);
vch.extend_from_slice(&b256[idx..]);
return true
}
#[cfg(test)]
mod decode_spec {
use super::*;
fn hex_to_bytes(src: &str) -> Vec<u8> {
fn nibble(b: u8) -> u8 {
match b {
b'0'..=b'9' => b - b'0',
b'a'..=b'f' => b - b'a' + 10,
b'A'..=b'F' => b - b'A' + 10,
_ => panic!("non‑hex digit {b:?}"),
}
}
assert!(src.len() % 2 == 0, "hex string must be even length");
src.as_bytes()
.chunks_exact(2)
.map(|pair| (nibble(pair[0]) << 4) | nibble(pair[1]))
.collect()
}
const GOOD_VECTORS: &[(&str, &str)] = &[
("", ""),
("61", "2g"),
("626262", "a3gV"),
("000000", "111"),
];
#[traced_test]
fn decode_reference_vectors() {
for (idx, (hex, b58)) in GOOD_VECTORS.iter().enumerate() {
let mut out = Vec::new();
trace!(idx, ?hex, ?b58, "decoding reference vector");
assert!(
decode_base58(b58, &mut out, 256),
"vector #{idx} failed to decode"
);
assert_eq!(out, hex_to_bytes(hex), "vector #{idx} payload mismatch");
}
}
#[traced_test]
fn reject_forbidden_characters() {
for bad in ["bad0", "OOPS", "Ill", "l0"] {
let mut sink = Vec::new();
info!(?bad, "attempting to decode forbidden input");
assert!(
!decode_base58(bad, &mut sink, 100),
"input ‘{bad}’ unexpectedly decoded"
);
}
}
#[traced_test]
fn random_roundtrip_property() {
let mut seed = 0xDEC0_FFEEu64;
for case in 0..256 {
seed = seed.wrapping_mul(0xA5A5_9665_0000_0001).rotate_left(7);
let len = (seed & 0x3F) as usize; let mut bytes = Vec::with_capacity(len);
for i in 0..len {
bytes.push(((seed >> (i % 8)) & 0xFF) as u8);
}
let encoded = crate::encode::encode_base58(&bytes);
let mut decoded = Vec::<u8>::new();
assert!(
decode_base58(&encoded, &mut decoded, (len as i32 + 4).try_into().unwrap()),
"case {case}: failed to decode"
);
assert_eq!(decoded, bytes, "case {case}: round‑trip mismatch");
}
}
}