use crate::{Config, Error};
const RADIX_58_10: u64 = 430_804_206_899_405_824;
#[inline(always)]
unsafe fn bignum_mul_add(
digits: &mut [u64],
count: &mut usize,
multiplier: u64,
addend: u64,
) -> bool {
let mut carry = addend as u128;
let mul = multiplier as u128;
let len = *count;
let mut i = 0;
while i + 1 < len {
let digit0 = *unsafe { digits.get_unchecked(i) };
let result0 = (digit0 as u128) * mul + carry;
*unsafe { digits.get_unchecked_mut(i) } = result0 as u64;
let carry0 = result0 >> 64;
let digit1 = *unsafe { digits.get_unchecked(i + 1) };
let result1 = (digit1 as u128) * mul + carry0;
*unsafe { digits.get_unchecked_mut(i + 1) } = result1 as u64;
carry = result1 >> 64;
i += 2;
}
if i < len {
let digit = *unsafe { digits.get_unchecked(i) };
let result = (digit as u128) * mul + carry;
*unsafe { digits.get_unchecked_mut(i) } = result as u64;
carry = result >> 64;
}
if carry > 0 {
if len >= digits.len() {
return false;
}
*unsafe { digits.get_unchecked_mut(len) } = carry as u64;
*count += 1;
}
true
}
#[inline(always)]
unsafe fn parse_chunk(config: &Config, src: &[u8]) -> Result<(u64, u64), Error> {
let mut value = 0u64;
let mut multiplier = 1u64;
let mut bad = 0u8;
for &byte in src {
let digit = *unsafe { config.decode_map.get_unchecked(byte as usize) };
bad |= digit;
value = value * 58 + (digit as u64);
multiplier *= 58;
}
if bad & 0x80 != 0 {
return Err(Error::InvalidCharacter);
}
Ok((value, multiplier))
}
#[inline(always)]
unsafe fn decode_payload(config: &Config, mut src: &[u8], dst: &mut [u8]) -> Result<usize, Error> {
let mut bignum_uninit = core::mem::MaybeUninit::<[u64; 160]>::uninit();
let bignum_ptr = bignum_uninit.as_mut_ptr() as *mut u64;
unsafe {
*bignum_ptr = 0;
}
let bignum = unsafe { &mut *bignum_uninit.as_mut_ptr() };
let mut count = 1;
while src.len() >= 10 {
let d0 = *unsafe {
config
.decode_map
.get_unchecked(*src.get_unchecked(0) as usize)
} as u64;
let d1 = *unsafe {
config
.decode_map
.get_unchecked(*src.get_unchecked(1) as usize)
} as u64;
let d2 = *unsafe {
config
.decode_map
.get_unchecked(*src.get_unchecked(2) as usize)
} as u64;
let d3 = *unsafe {
config
.decode_map
.get_unchecked(*src.get_unchecked(3) as usize)
} as u64;
let d4 = *unsafe {
config
.decode_map
.get_unchecked(*src.get_unchecked(4) as usize)
} as u64;
let d5 = *unsafe {
config
.decode_map
.get_unchecked(*src.get_unchecked(5) as usize)
} as u64;
let d6 = *unsafe {
config
.decode_map
.get_unchecked(*src.get_unchecked(6) as usize)
} as u64;
let d7 = *unsafe {
config
.decode_map
.get_unchecked(*src.get_unchecked(7) as usize)
} as u64;
let d8 = *unsafe {
config
.decode_map
.get_unchecked(*src.get_unchecked(8) as usize)
} as u64;
let d9 = *unsafe {
config
.decode_map
.get_unchecked(*src.get_unchecked(9) as usize)
} as u64;
let invalid = (d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 | d8 | d9) & 0x80;
if invalid != 0 {
return Err(Error::InvalidCharacter);
}
let v01 = d0 * 58 + d1;
let v23 = d2 * 58 + d3;
let v45 = d4 * 58 + d5;
let v67 = d6 * 58 + d7;
let v89 = d8 * 58 + d9;
let v03 = v01 * 3364 + v23;
let v47 = v45 * 3364 + v67;
let v07 = v03 * 11316496 + v47;
let chunk = v07 * 3364 + v89;
if !unsafe { bignum_mul_add(bignum, &mut count, RADIX_58_10, chunk) } {
return Err(Error::InputTooBig);
}
if count > 128 {
return Err(Error::InputTooBig);
}
src = &src[10..];
}
if !src.is_empty() {
let (chunk, multiplier) = unsafe { parse_chunk(config, src) }?;
if !unsafe { bignum_mul_add(bignum, &mut count, multiplier, chunk) } {
return Err(Error::InputTooBig);
}
}
if count > 128 {
return Err(Error::InputTooBig);
}
let mut out_idx = dst.len();
for i in 0..count {
let mut val = bignum[i];
for _ in 0..8 {
if out_idx == 0 {
if val > 0 || i + 1 < count {
return Err(Error::BufferTooSmall);
}
break;
}
out_idx -= 1;
*unsafe { dst.get_unchecked_mut(out_idx) } = val as u8;
val >>= 8;
}
}
while out_idx < dst.len() && *unsafe { dst.get_unchecked(out_idx) } == 0 {
out_idx += 1;
}
let length = dst.len() - out_idx;
if out_idx > 0 {
let ptr = dst.as_mut_ptr();
unsafe { core::ptr::copy(ptr.add(out_idx), ptr, length) };
} else if length > dst.len() {
return Err(Error::BufferTooSmall);
}
Ok(length)
}
#[inline(always)]
pub unsafe fn decode_slice_unsafe(
input: &[u8],
dst: &mut [u8],
config: &Config,
) -> Result<usize, Error> {
assert!(input.len() <= 2048, "Input too big! {}", input.len());
let zero_char = *unsafe { config.alphabet.get_unchecked(0) };
let mut leading_zeros = 0;
while leading_zeros < input.len() && *unsafe { input.get_unchecked(leading_zeros) } == zero_char
{
leading_zeros += 1;
}
if leading_zeros > 1024 {
return Err(Error::InputTooBig);
}
if leading_zeros > dst.len() {
return Err(Error::BufferTooSmall);
}
if leading_zeros > 0 {
unsafe { core::ptr::write_bytes(dst.as_mut_ptr(), 0, leading_zeros) };
}
let src = &input[leading_zeros..];
if src.is_empty() {
return Ok(leading_zeros);
}
let written_payload = unsafe { decode_payload(config, src, &mut dst[leading_zeros..]) }?;
let total_len = leading_zeros + written_payload;
if total_len > 1024 {
return Err(Error::InputTooBig);
}
Ok(total_len)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BITCOIN;
#[test]
fn test_bignum_mul_add_carry() {
let mut digits = [0u64; 10];
let mut count = 1;
unsafe { bignum_mul_add(&mut digits, &mut count, 58, 1) };
assert_eq!(count, 1);
assert_eq!(digits[0], 1);
digits[0] = u64::MAX;
count = 1;
unsafe { bignum_mul_add(&mut digits, &mut count, 2, 1) };
assert_eq!(count, 2);
assert_eq!(digits[0], u64::MAX);
assert_eq!(digits[1], 1);
}
#[test]
fn test_parse_chunk_invalid() {
let config = BITCOIN.config();
let res = unsafe { parse_chunk(config, b"10") }; assert!(res.is_err());
}
#[test]
fn test_decode_payload_buffer_too_small() {
let config = BITCOIN.config();
let mut dst = [0u8; 1];
let res = unsafe { decode_payload(config, b"222", &mut dst) };
assert_eq!(res.unwrap_err(), Error::BufferTooSmall);
}
}
#[cfg(all(test, miri))]
mod base58_miri_coverage {
use super::*;
use crate::BITCOIN;
use rand::{RngExt, rng};
fn random_bytes(len: usize) -> Vec<u8> {
let mut rng = rng();
(0..len).map(|_| rng.random()).collect()
}
#[test]
fn miri_base58_decode_fuzz() {
let lengths = [0, 1, 3, 10, 31, 32, 33, 63, 64, 65, 100, 512, 700];
for &len in &lengths {
let input_bytes = random_bytes(len);
let encoded_str = BITCOIN.encode(&input_bytes).unwrap();
let encoded_bytes = encoded_str.as_bytes();
let mut decoded_buf = vec![0u8; len];
let decoded_len =
unsafe { decode_slice_unsafe(encoded_bytes, &mut decoded_buf, BITCOIN.config()) }
.unwrap();
assert_eq!(decoded_len, len);
assert_eq!(&decoded_buf[..decoded_len], &input_bytes[..]);
}
}
#[test]
fn miri_base58_decode_leading_zeros() {
let input = "111222"; let mut out = [0u8; 10];
let len =
unsafe { decode_slice_unsafe(input.as_bytes(), &mut out, BITCOIN.config()) }.unwrap();
assert_eq!(len, 5);
assert_eq!(&out[..5], &[0x00, 0x00, 0x00, 0x0D, 0x5F]);
}
}