diff --git a/Cargo.lock b/Cargo.lock index 26cfdde..1881d32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,8 @@ dependencies = [ "assert_matches", "base58", "criterion", + "num-bigint", + "num-traits", "rust-base58", "sha2", ] diff --git a/Cargo.toml b/Cargo.toml index 01289bb..e56df5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,12 @@ std = ["alloc"] alloc = [] check = ["sha2"] cb58 = ["sha2"] +bigint = ["num-bigint", "num-traits"] [dependencies] sha2 = { version = "0.10", optional = true, default-features = false } +num-bigint = { version = "0.4.3", optional = true, default-features = false } +num-traits = { version = "0.2.15", optional = true, default-features = false } [dev_dependencies] criterion = "0.3" diff --git a/src/decode.rs b/src/decode.rs index d3a509e..b31709c 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -5,6 +5,9 @@ use core::fmt; #[cfg(feature = "alloc")] use alloc::vec::Vec; +#[cfg(feature = "bigint")] +use num_bigint::BigUint; + use crate::Check; #[cfg(any(feature = "check", feature = "cb58"))] use crate::CHECKSUM_LEN; @@ -287,22 +290,27 @@ impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> { } } +fn alpha_decode(index: usize, input_char: u8, alpha: &Alphabet) -> Result { + if input_char > 127 { + return Err(Error::NonAsciiCharacter { index }); + }; + let val = alpha.decode[input_char as usize]; + if val >= 58 { + return Err(Error::InvalidCharacter { + character: input_char as char, + index, + }); + } + return Ok(val); +} + +#[cfg(not(feature = "bigint"))] fn decode_into(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Result { let mut index = 0; let zero = alpha.encode[0]; for (i, c) in input.iter().enumerate() { - if *c > 127 { - return Err(Error::NonAsciiCharacter { index: i }); - } - - let mut val = alpha.decode[*c as usize] as usize; - if val == 0xFF { - return Err(Error::InvalidCharacter { - character: *c as char, - index: i, - }); - } + let mut val = alpha_decode(i, *c, alpha)? as usize; for byte in &mut output[..index] { val += (*byte as usize) * 58; @@ -328,6 +336,100 @@ fn decode_into(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Result Result { + let mut index = 0; + let zero = alpha.encode[0]; + + for (_, _) in input.iter().enumerate().take_while(|(_, c)| **c == zero) { + let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?; + *byte = 0; + index += 1; + } + + let index_0 = index; + let input_len = input.len() - index_0; + + if input_len > 0 && input_len <= 10 { + let mut output_uint = 0u64; + for (i, c) in input.iter().enumerate().skip(index_0) { + let val = alpha_decode(i, *c, alpha)? as u64; + output_uint = 58 * output_uint + val; + } + while output_uint > 0 { + let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?; + *byte = output_uint as u8; + index += 1; + output_uint >>= 8 + } + output[index_0..index].reverse(); + } else if input_len <= 21 { + let mut output_uint = 0u128; + for (i, c) in input.iter().enumerate().skip(index_0) { + let val = alpha_decode(i, *c, alpha)? as u128; + output_uint = 58 * output_uint + val; + } + while output_uint > 0 { + let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?; + *byte = output_uint as u8; + index += 1; + output_uint >>= 8 + } + output[index_0..index].reverse(); + } else if input_len <= 43 { + let mut output_uints = [0u64; 4]; + let mut ll_index = 0; + for (i, c) in input.iter().enumerate().skip(index_0) { + let mut val = alpha_decode(i, *c, alpha)? as u128; + + for ll in &mut output_uints[..ll_index] { + val += *ll as u128 * 58; + *ll = val as u64; + val >>= 64; + } + while val > 0 { + let ll = output_uints + .get_mut(ll_index) + .expect("Base58 input under 43 chars fit into [u64;4]"); + *ll = val as u64; + ll_index += 1; + val >>= 64 + } + } + output_uints.reverse(); + let mut leading_0 = true; + for ll in output_uints { + for be_byte in ll.to_be_bytes() { + if leading_0 && be_byte == 0 { + continue; + } else { + leading_0 = false; + } + let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?; + *byte = be_byte; + index += 1; + } + } + } else { + let mut clean_input: Vec = Vec::with_capacity(input_len); + for (i, c) in input.iter().enumerate().skip(index_0) { + let val = alpha_decode(i, *c, alpha)?; + clean_input.push(val); + } + let b58_biguint = BigUint::from_radix_be(&clean_input, 58).unwrap(); + let decoded_vec: Vec = b58_biguint.to_bytes_be(); + for c in decoded_vec.iter() { + if index >= output.len() { + break; + } + let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?; + *byte = *c; + index += 1; + } + } + Ok(index) +} + #[cfg(feature = "check")] fn decode_check_into( input: &[u8], diff --git a/src/encode.rs b/src/encode.rs index 32f59f3..1fa93f4 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -5,6 +5,11 @@ use core::fmt; #[cfg(feature = "alloc")] use alloc::{string::String, vec::Vec}; +#[cfg(feature = "bigint")] +use num_bigint::BigUint; +#[cfg(feature = "bigint")] +use num_traits::Zero; + use crate::Check; #[cfg(any(feature = "check", feature = "cb58"))] use crate::CHECKSUM_LEN; @@ -361,6 +366,7 @@ fn max_encoded_len(len: usize) -> usize { len + (len + 1) / 2 } +#[cfg(not(feature = "bigint"))] fn encode_into<'a, I>(input: I, output: &mut [u8], alpha: &Alphabet) -> Result where I: Clone + IntoIterator, @@ -399,6 +405,39 @@ where Ok(index) } +#[cfg(feature = "bigint")] +fn encode_into<'a, I>(input: I, output: &mut [u8], alpha: &Alphabet) -> Result +where + I: Clone + IntoIterator, +{ + let mut index = 0; + + let vector: Vec = input.into_iter().cloned().collect(); + let big_uint = BigUint::from_radix_be(&vector, 256).unwrap(); + let mut big_58 = Vec::with_capacity(vector.len() * 2); + for _ in vector.iter().take_while(|v| **v == 0) { + if index == output.len() { + return Err(Error::BufferTooSmall); + } + output[index] = 0; + index += 1; + } + + if !big_uint.is_zero() { + big_58.append(&mut big_uint.to_radix_be(58)) + } + + for val in &big_58 { + if index == output.len() { + return Err(Error::BufferTooSmall); + } + output[index] = alpha.encode[*val as usize]; + index += 1; + } + + Ok(index) +} + #[cfg(feature = "check")] fn encode_check_into( input: &[u8], diff --git a/src/lib.rs b/src/lib.rs index aabc097..13cfce6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,7 @@ //! `alloc` | implied by `std` | Support encoding/decoding to [`Vec`](alloc::vec::Vec) and [`String`](alloc::string::String) as appropriate //! `check` | **off**-by-default | Integrated support for [Base58Check][] //! `cb58` | **off**-by-default | Integrated support for [CB58][] +//! `bigint`| **off**-by-default | Integrated support for [BigUint][] //! //! [Base58Check]: https://en.bitcoin.it/wiki/Base58Check_encoding //! [CB58]: https://support.avax.network/en/articles/4587395-what-is-cb58