Merge rust-bitcoin/rust-bitcoin#6440: base58: Introduce `decode_check_to_array` for alloc-less decoding
What changed, and why it matters
This commit is a routine feature addition to the rust-bitcoin base58 crate. It adds a new no-allocator function to decode short base58-check strings into fixed-size byte arrays, and renames the existing error type while keeping a deprecated alias for backward compatibility. The changes are mostly refactoring and API expansion; there is no direct evidence of a security vulnerability being fixed.
No immediate security action required. Reviewers may want to verify that the new `decode_check_to_array` length checks and checksum logic match the existing `decode_check` behavior, and that the deprecated `base58::Error` alias remains source-compatible for downstream users.
Security signals we found
No security-relevant bug fix is described in the commit message or diff.
New decoding path uses a fixed 128-byte scratch buffer (`ArrayVec`) and rejects oversized inputs.
Checksum verification and invalid-character handling are preserved from the existing alloc implementation.
The change removes several `#[cfg(feature = "alloc")]` guards on error types, making error types available in no-alloc builds.
No unsafe code, no cryptographic changes, no secret-handling changes observed.
Evidence from the diff
The merge introduces decode_check_to_array<const N: usize>() for decoding base58check payloads up to 128 characters into fixed-size arrays without requiring the alloc feature. It splits the old decode() logic into a private build_base256() helper that writes into a generic Buffer, and adds DecodeCheckError, DecodeCheckArrayError, and related error types. Existing public error type base58::Error is renamed to DecodeCheckError with a deprecated type alias. Callers in addresses, crypto, and key_expression are updated to use the new name. Tests cover round-trips, length mismatches, invalid characters, bad checksums, and the 128-character input limit.
Changed components
base58/src/lib.rsbase58/src/error.rsaddresses/src/error.rscrypto/src/key.rskey_expression/src/bip32.rsInspect captured patch +330 / −77
### addresses/src/error.rs
@@ -249,7 +249,7 @@ impl std::error::Error for ParseBech32Error {
#[non_exhaustive]
pub enum Base58Error {
/// Parse legacy Base58 error.
- ParseBase58(base58::Error),
+ ParseBase58(base58::DecodeCheckError),
/// Legacy address is too long.
LegacyAddressTooLong(LegacyAddressTooLongError),
/// Invalid base58 payload data length for legacy address.
@@ -286,8 +286,8 @@ impl std::error::Error for Base58Error {
}
}
-impl From<base58::Error> for Base58Error {
- fn from(e: base58::Error) -> Self { Self::ParseBase58(e) }
+impl From<base58::DecodeCheckError> for Base58Error {
+ fn from(e: base58::DecodeCheckError) -> Self { Self::ParseBase58(e) }
}
impl From<LegacyAddressTooLongError> for Base58Error {
### base58/src/error.rs
@@ -5,17 +5,25 @@
use core::convert::Infallible;
use core::fmt;
-#[cfg(feature = "alloc")]
use internals::write_err;
/// An error occurred during base58 decoding (with checksum).
#[cfg(feature = "alloc")]
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct Error(pub(super) ErrorInner);
+#[deprecated(since = "TBD", note = "use DecodeCheckError instead")]
+pub type Error = DecodeCheckError;
#[cfg(feature = "alloc")]
+/// An error occurred during base58 decoding (with checksum).
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct DecodeCheckError(pub(super) DecodeCheckErrorInner);
+
+#[cfg(not(feature = "alloc"))]
+/// An error occurred during base58 decoding (with checksum).
#[derive(Debug, Clone, PartialEq, Eq)]
-pub(super) enum ErrorInner {
+pub(crate) struct DecodeCheckError(pub(super) DecodeCheckErrorInner);
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(super) enum DecodeCheckErrorInner {
/// Invalid character while decoding.
Decode(InvalidCharacterError),
/// Checksum was not correct.
@@ -24,42 +32,39 @@ pub(super) enum ErrorInner {
TooShort(TooShortError),
}
-#[cfg(feature = "alloc")]
-impl Error {
+impl DecodeCheckError {
/// Returns the invalid base58 character, if encountered.
pub fn invalid_character(&self) -> Option<u8> {
match self.0 {
- ErrorInner::Decode(ref e) => Some(e.invalid_character()),
+ DecodeCheckErrorInner::Decode(ref e) => Some(e.invalid_character()),
_ => None,
}
}
/// Returns the incorrect checksum along with the expected checksum, if encountered.
pub fn incorrect_checksum(&self) -> Option<(u32, u32)> {
match self.0 {
- ErrorInner::IncorrectChecksum(ref e) => Some((e.incorrect, e.expected)),
+ DecodeCheckErrorInner::IncorrectChecksum(ref e) => Some((e.incorrect, e.expected)),
_ => None,
}
}
/// Returns the invalid base58 string length (require at least 4 bytes for checksum), if encountered.
pub fn invalid_length(&self) -> Option<usize> {
match self.0 {
- ErrorInner::TooShort(ref e) => Some(e.length),
+ DecodeCheckErrorInner::TooShort(ref e) => Some(e.length),
_ => None,
}
}
}
-#[cfg(feature = "alloc")]
-impl From<Infallible> for Error {
+impl From<Infallible> for DecodeCheckError {
fn from(never: Infallible) -> Self { match never {} }
}
-#[cfg(feature = "alloc")]
-impl fmt::Display for Error {
+impl fmt::Display for DecodeCheckError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use ErrorInner::{Decode, IncorrectChecksum, TooShort};
+ use DecodeCheckErrorInner::{Decode, IncorrectChecksum, TooShort};
match self.0 {
Decode(ref e) => write_err!(f, "decode"; e),
@@ -70,9 +75,9 @@ impl fmt::Display for Error {
}
#[cfg(feature = "std")]
-impl std::error::Error for Error {
+impl std::error::Error for DecodeCheckError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- use ErrorInner::{Decode, IncorrectChecksum, TooShort};
+ use DecodeCheckErrorInner::{Decode, IncorrectChecksum, TooShort};
match self.0 {
Decode(ref e) => Some(e),
@@ -82,22 +87,18 @@ impl std::error::Error for Error {
}
}
-#[cfg(feature = "alloc")]
-impl From<InvalidCharacterError> for Error {
- fn from(e: InvalidCharacterError) -> Self { Self(ErrorInner::Decode(e)) }
+impl From<InvalidCharacterError> for DecodeCheckError {
+ fn from(e: InvalidCharacterError) -> Self { Self(DecodeCheckErrorInner::Decode(e)) }
}
-#[cfg(feature = "alloc")]
-impl From<IncorrectChecksumError> for Error {
- fn from(e: IncorrectChecksumError) -> Self { Self(ErrorInner::IncorrectChecksum(e)) }
+impl From<IncorrectChecksumError> for DecodeCheckError {
+ fn from(e: IncorrectChecksumError) -> Self { Self(DecodeCheckErrorInner::IncorrectChecksum(e)) }
}
-#[cfg(feature = "alloc")]
-impl From<TooShortError> for Error {
- fn from(e: TooShortError) -> Self { Self(ErrorInner::TooShort(e)) }
+impl From<TooShortError> for DecodeCheckError {
+ fn from(e: TooShortError) -> Self { Self(DecodeCheckErrorInner::TooShort(e)) }
}
-#[cfg(feature = "alloc")]
/// Checksum was not correct.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct IncorrectChecksumError {
@@ -107,12 +108,10 @@ pub(super) struct IncorrectChecksumError {
pub(super) expected: u32,
}
-#[cfg(feature = "alloc")]
impl From<Infallible> for IncorrectChecksumError {
fn from(never: Infallible) -> Self { match never {} }
}
-#[cfg(feature = "alloc")]
impl fmt::Display for IncorrectChecksumError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
@@ -131,20 +130,17 @@ impl std::error::Error for IncorrectChecksumError {
}
}
-#[cfg(feature = "alloc")]
/// The decoded base58 data was too short (require at least 4 bytes for checksum).
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct TooShortError {
/// The length of the decoded data.
pub(super) length: usize,
}
-#[cfg(feature = "alloc")]
impl From<Infallible> for TooShortError {
fn from(never: Infallible) -> Self { match never {} }
}
-#[cfg(feature = "alloc")]
impl fmt::Display for TooShortError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
@@ -202,32 +198,131 @@ impl std::error::Error for InputTooLongError {
}
}
+/// Error returned when decoding base58check data into a fixed-size array.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct DecodeCheckArrayError(pub(super) DecodeCheckArrayErrorInner);
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(super) enum DecodeCheckArrayErrorInner {
+ /// Decoding the base58check string failed (invalid character, bad checksum or too short).
+ Decode(DecodeCheckError),
+ /// The decoded payload length did not match the requested array length.
+ UnexpectedLength(UnexpectedLengthError),
+}
+
+impl DecodeCheckArrayError {
+ /// Returns the invalid base58 character, if encountered.
+ pub fn invalid_character(&self) -> Option<u8> {
+ match self.0 {
+ DecodeCheckArrayErrorInner::Decode(ref e) => e.invalid_character(),
+ DecodeCheckArrayErrorInner::UnexpectedLength(_) => None,
+ }
+ }
+
+ /// Returns the incorrect checksum along with the expected checksum, if encountered.
+ pub fn incorrect_checksum(&self) -> Option<(u32, u32)> {
+ match self.0 {
+ DecodeCheckArrayErrorInner::Decode(ref e) => e.incorrect_checksum(),
+ DecodeCheckArrayErrorInner::UnexpectedLength(_) => None,
+ }
+ }
+
+ /// Returns the invalid base58 string length (require at least 4 bytes for checksum), if encountered.
+ pub fn invalid_length(&self) -> Option<usize> {
+ match self.0 {
+ DecodeCheckArrayErrorInner::Decode(ref e) => e.invalid_length(),
+ DecodeCheckArrayErrorInner::UnexpectedLength(_) => None,
+ }
+ }
+}
+
+impl From<Infallible> for DecodeCheckArrayError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+impl fmt::Display for DecodeCheckArrayError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use DecodeCheckArrayErrorInner::{Decode, UnexpectedLength};
+
+ match self.0 {
+ Decode(ref e) => write_err!(f, "decode"; e),
+ UnexpectedLength(ref e) => write_err!(f, "unexpected length"; e),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for DecodeCheckArrayError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ use DecodeCheckArrayErrorInner::{Decode, UnexpectedLength};
+
+ match self.0 {
+ Decode(ref e) => Some(e),
+ UnexpectedLength(ref e) => Some(e),
+ }
+ }
+}
+
+/// The decoded base58check payload length did not match the requested array length.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(super) struct UnexpectedLengthError {
+ /// The requested (expected) array length.
+ pub(super) expected: usize,
+ /// The decoded payload length.
+ ///
+ /// When the input string exceeds the maximum supported length, this is an
+ /// approximation of the decoded length derived from the input string length.
+ pub(super) actual: usize,
+}
+
+impl From<Infallible> for UnexpectedLengthError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+impl fmt::Display for UnexpectedLengthError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "decoded base58check payload was {} bytes, expected {}",
+ self.actual, self.expected
+ )
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for UnexpectedLengthError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ let Self { expected: _, actual: _ } = self;
+ None
+ }
+}
+
/// Found an invalid ASCII byte while decoding base58 string.
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InvalidCharacterError(pub(super) InvalidCharacterErrorInner);
-#[cfg(feature = "alloc")]
+#[cfg(not(feature = "alloc"))]
+/// Found an invalid ASCII byte while decoding base58 string.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) struct InvalidCharacterError(pub(super) InvalidCharacterErrorInner);
+
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct InvalidCharacterErrorInner {
pub(super) invalid: u8,
}
-#[cfg(feature = "alloc")]
impl InvalidCharacterError {
- #[cfg(feature = "alloc")]
pub(super) fn new(invalid: u8) -> Self { Self(InvalidCharacterErrorInner { invalid }) }
/// Returns the invalid base58 character.
pub fn invalid_character(&self) -> u8 { self.0.invalid }
}
-#[cfg(feature = "alloc")]
impl From<Infallible> for InvalidCharacterError {
fn from(never: Infallible) -> Self { match never {} }
}
-#[cfg(feature = "alloc")]
impl fmt::Display for InvalidCharacterError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid base58 character {:#x}", self.0.invalid)
@@ -241,3 +336,9 @@ impl std::error::Error for InvalidCharacterError {
None
}
}
+
+/// An error that can occur from the `build_base256` function.
+pub(super) enum Base256Error<T> {
+ Buffer(T),
+ InvalidChar(InvalidCharacterError),
+}
### base58/src/lib.rs
@@ -40,27 +40,26 @@ use core::fmt;
pub use std::{string::String, vec::Vec};
use hashes::sha256d;
-#[cfg(feature = "alloc")]
use internals::array::ArrayExt;
use internals::array_vec::ArrayVec;
#[allow(unused)] // MSRV polyfill
-#[cfg(feature = "alloc")]
use internals::slice::SliceExt;
+use crate::error::{
+ Base256Error, DecodeCheckArrayErrorInner, IncorrectChecksumError, TooShortError,
+ UnexpectedLengthError,
+};
#[cfg(not(feature = "alloc"))]
-use crate::error::InputTooLongErrorInner;
-#[cfg(feature = "alloc")]
-use crate::error::{IncorrectChecksumError, TooShortError};
+use crate::error::{DecodeCheckError, InputTooLongErrorInner, InvalidCharacterError};
#[rustfmt::skip] // Keep public re-exports separate.
#[cfg(feature = "alloc")]
#[doc(no_inline)]
-pub use self::error::{Error, InvalidCharacterError};
+pub use self::error::{DecodeCheckError, InvalidCharacterError};
#[doc(no_inline)]
-pub use self::error::InputTooLongError;
+pub use self::error::{DecodeCheckArrayError, InputTooLongError};
#[rustfmt::skip]
-#[cfg(feature = "alloc")]
static BASE58_DIGITS: [Option<u8>; 128] = [
None, None, None, None, None, None, None, None, // 0-7
None, None, None, None, None, None, None, None, // 8-15
@@ -80,47 +79,59 @@ static BASE58_DIGITS: [Option<u8>; 128] = [
Some(55), Some(56), Some(57), None, None, None, None, None, // 120-127
];
-/// Decodes a base58-encoded string into a byte vector.
+/// Builds the little-endian base-256 representation of base58 `data` into `scratch`.
+///
+/// The padding zero bytes are not decoded, so the big-endian decoded value is directly read
+/// back to front.
///
/// # Errors
///
/// Returns an error if the input contains an invalid base58 character (not in the base58 alphabet).
-#[allow(clippy::missing_panics_doc)] // Internal assertion, not user-controllable.
-#[cfg(feature = "alloc")]
-pub fn decode(data: &str) -> Result<Vec<u8>, InvalidCharacterError> {
- // 11/15 is just over log_256(58)
- let mut scratch = Vec::with_capacity(1 + data.len() * 11 / 15);
+fn build_base256<T: Buffer>(data: &str, scratch: &mut T) -> Result<(), Base256Error<T::Err>> {
// Build in base 256
for d58 in data.bytes() {
// Compute "X = X * 58 + next_digit" in base 256
if usize::from(d58) >= BASE58_DIGITS.len() {
- return Err(InvalidCharacterError::new(d58));
+ return Err(Base256Error::InvalidChar(InvalidCharacterError::new(d58)));
}
let mut carry = match BASE58_DIGITS[usize::from(d58)] {
Some(d58) => u32::from(d58),
None => {
- return Err(InvalidCharacterError::new(d58));
+ return Err(Base256Error::InvalidChar(InvalidCharacterError::new(d58)));
}
};
- if scratch.is_empty() {
- for _ in 0..scratch.capacity() {
- scratch.push(carry as u8);
- carry /= 256;
- }
- } else {
- for d256 in &mut scratch {
- carry += u32::from(*d256) * 58;
- *d256 = carry as u8; // cast loses data intentionally
- carry /= 256;
- }
+ for d256 in scratch.slice_mut() {
+ carry += u32::from(*d256) * 58;
+ *d256 = carry as u8; // cast loses data intentionally
+ carry /= 256;
+ }
+ while carry > 0 {
+ // This function (build_base256) is only ever called with a Vec (infallible) or an ArrayVec with a pre-checked size.
+ scratch.try_push(carry as u8).map_err(Base256Error::Buffer)?; // cast loses data intentionally
+ carry /= 256;
}
- assert_eq!(carry, 0);
}
+ Ok(())
+}
+
+/// Decodes a base58-encoded string into a byte vector.
+///
+/// # Errors
+///
+/// Returns an error if the input contains an invalid base58 character (not in the base58 alphabet).
+#[cfg(feature = "alloc")]
+pub fn decode(data: &str) -> Result<Vec<u8>, InvalidCharacterError> {
+ // 11/15 is just over log_256(58)
+ let mut scratch = Vec::with_capacity(1 + data.len() * 11 / 15);
+ build_base256(data, &mut scratch).map_err(|e| match e {
+ Base256Error::Buffer(_) => unreachable!("Vec cannot fail try_push"),
+ Base256Error::InvalidChar(err) => err,
+ })?;
// Copy leading zeroes directly
let mut ret: Vec<u8> = data.bytes().take_while(|&x| x == BASE58_CHARS[0]).map(|_| 0).collect();
// Copy rest of string
- ret.extend(scratch.into_iter().rev().skip_while(|&x| x == 0));
+ ret.extend(scratch.into_iter().rev());
Ok(ret)
}
@@ -132,7 +143,7 @@ pub fn decode(data: &str) -> Result<Vec<u8>, InvalidCharacterError> {
/// * The decoded data is less than 4 bytes (too short for checksum verification).
/// * The checksum does not match the expected value.
#[cfg(feature = "alloc")]
-pub fn decode_check(data: &str) -> Result<Vec<u8>, Error> {
+pub fn decode_check(data: &str) -> Result<Vec<u8>, DecodeCheckError> {
let mut ret: Vec<u8> = decode(data)?;
let (remaining, &data_check) =
ret.split_last_chunk::<4>().ok_or(TooShortError { length: ret.len() })?;
@@ -150,6 +161,76 @@ pub fn decode_check(data: &str) -> Result<Vec<u8>, Error> {
Ok(ret)
}
+/// Decodes a base58check-encoded string into a fixed-size array, verifying the checksum.
+///
+/// This does not require `alloc`, but it only works for inputs up to 128 characters long. `N` is
+/// the expected length of the decoded payload (excluding the 4 byte checksum). Decoding will fail if
+/// the payload is any other length.
+///
+/// # Errors
+///
+/// * The input contains an invalid base58 character.
+/// * The decoded data is less than 4 bytes (too short for checksum verification).
+/// * The checksum does not match the expected value.
+/// * The input is longer than 128 characters.
+/// * The decoded payload length is not exactly `N` bytes.
+#[allow(clippy::missing_panics_doc)] // payload length is checked before cast unwrap
+pub fn decode_check_to_array<const N: usize>(data: &str) -> Result<[u8; N], DecodeCheckArrayError> {
+ // 11/15 is just over log_256(58), so the decoded length never exceeds the input length.
+ let mut scratch = ArrayVec::<u8, SHORT_OPT_BUFFER_LEN>::new();
+ build_base256(data, &mut scratch)
+ .map_err(|e| match e {
+ // Too long to decode within the fixed buffer. Report an approximate decoded length.
+ Base256Error::Buffer(_) =>
+ DecodeCheckArrayErrorInner::UnexpectedLength(UnexpectedLengthError {
+ expected: N,
+ actual: data.len() * 11 / 15,
+ }),
+ Base256Error::InvalidChar(err) =>
+ DecodeCheckArrayErrorInner::Decode(DecodeCheckError::from(err)),
+ })
+ .map_err(DecodeCheckArrayError)?;
+
+ let leading_zeros = data.bytes().take_while(|&x| x == BASE58_CHARS[0]).count();
+ let decoded_len = leading_zeros + scratch.len();
+
+ let mut decoded = [0u8; SHORT_OPT_BUFFER_LEN];
+ scratch.as_mut_slice().reverse();
+
+ // Copy the scratch into a subslice, erroring if out of range.
+ let write_slice = decoded
+ .get_mut(leading_zeros..decoded_len)
+ .ok_or(UnexpectedLengthError { expected: N, actual: data.len() * 11 / 15 })
+ .map_err(DecodeCheckArrayErrorInner::UnexpectedLength)
+ .map_err(DecodeCheckArrayError)?;
+ write_slice.copy_from_slice(&scratch);
+ let decoded = &decoded[..decoded_len];
+
+ let (payload, &data_check) = decoded.split_last_chunk::<4>().ok_or_else(|| {
+ DecodeCheckArrayError(DecodeCheckArrayErrorInner::Decode(DecodeCheckError::from(
+ TooShortError { length: decoded_len },
+ )))
+ })?;
+
+ if payload.len() != N {
+ return Err(DecodeCheckArrayError(DecodeCheckArrayErrorInner::UnexpectedLength(
+ UnexpectedLengthError { expected: N, actual: payload.len() },
+ )));
+ }
+
+ let hash_check = *sha256d::Hash::hash(payload).as_byte_array().sub_array::<0, 4>();
+ let expected = u32::from_le_bytes(hash_check);
+ let actual = u32::from_le_bytes(data_check);
+
+ if actual != expected {
+ return Err(DecodeCheckArrayError(DecodeCheckArrayErrorInner::Decode(
+ DecodeCheckError::from(IncorrectChecksumError { incorrect: actual, expected }),
+ )));
+ }
+
+ Ok(payload.try_into().expect("payload length checked to equal N"))
+}
+
const SHORT_OPT_BUFFER_LEN: usize = 128;
/// A base58check-encoded string (data followed by a 4 byte `SHA256d` checksum, base58-encoded).
@@ -339,15 +420,16 @@ fn encode_to_buffer<I: Iterator<Item = u8>, T: Buffer>(data: I, buf: &mut T) ->
}
#[cfg(test)]
-#[cfg(feature = "alloc")]
mod tests {
+ #[cfg(feature = "alloc")]
use alloc::vec;
use hex::hex;
use super::*;
#[test]
+ #[cfg(feature = "alloc")]
fn base58_encode() {
// Basics
assert_eq!(Base58CkString::encode_unbounded(&[13, 36][..]).as_str(), "7YY3x3vS");
@@ -380,6 +462,7 @@ mod tests {
}
#[test]
+ #[cfg(feature = "alloc")]
fn base58_decode() {
// Basics
assert_eq!(decode("1").ok(), Some(vec![0u8]));
@@ -401,6 +484,75 @@ mod tests {
}
#[test]
+ fn decode_check_to_array_roundtrip() {
+ let addr = hex!("00f8917303bfa8ef24f292e8fa1419b20460ba064d");
+ let encoded = Base58CkString::encode(&addr).unwrap();
+
+ let decoded = decode_check_to_array::<21>(encoded.as_str()).unwrap();
+ assert_eq!(decoded, addr);
+ #[cfg(feature = "alloc")]
+ assert_eq!(decoded.as_slice(), decode_check(encoded.as_str()).unwrap().as_slice());
+ }
+
+ #[test]
+ fn decode_check_to_array_errors() {
+ use crate::error::DecodeCheckArrayErrorInner;
+
+ const STRING_LEN: usize = SHORT_OPT_BUFFER_LEN + 1;
+ const APPROX_LEN: usize = STRING_LEN * 11 / 15;
+
+ let encoded = "1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH"; // 21 byte payload
+
+ let err = decode_check_to_array::<20>(encoded).unwrap_err();
+ assert_eq!(
+ err,
+ DecodeCheckArrayError(DecodeCheckArrayErrorInner::UnexpectedLength(
+ crate::error::UnexpectedLengthError { expected: 20, actual: 21 }
+ ))
+ );
+
+ assert!(matches!(
+ decode_check_to_array::<21>("¢").unwrap_err(),
+ DecodeCheckArrayError(DecodeCheckArrayErrorInner::Decode(_))
+ ));
+
+ assert!(matches!(
+ decode_check_to_array::<21>("1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHG").unwrap_err(),
+ DecodeCheckArrayError(DecodeCheckArrayErrorInner::Decode(_))
+ ));
+
+ let long = "1".repeat(STRING_LEN);
+ assert!(matches!(
+ decode_check_to_array::<21>(&long).unwrap_err(),
+ DecodeCheckArrayError(DecodeCheckArrayErrorInner::UnexpectedLength(
+ crate::UnexpectedLengthError { expected: 21, actual: APPROX_LEN }
+ ))
+ ));
+ }
+
+ #[test]
+ fn decode_check_to_array_at_input_length_limit() {
+ let encoded = "22UzJUbV3TnAhvzqfW411nkMuSfpgxfYfuuCyNPtrA9EQTViEdsmiBAqEyGP4EGFHb1c7XKWFmjWj9uzBdg8kpCVXAaWVGQmovSTnFjSjEEa9sAZqKUYrvnvgVtPVTuj";
+ let want = [0xFFu8; 89];
+ assert_eq!(encoded.len(), SHORT_OPT_BUFFER_LEN);
+ assert_eq!(decode_check_to_array::<89>(encoded).unwrap(), want);
+
+ let encoded = "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111DH4Svg";
+ let mut want = [0u8; 123];
+ want[122] = 0x01;
+ assert_eq!(encoded.len(), SHORT_OPT_BUFFER_LEN);
+ assert_eq!(decode_check_to_array::<123>(encoded).unwrap(), want);
+ }
+
+ #[test]
+ fn decode_check_to_array_leading_zeros() {
+ let data = [0u8, 0, 1, 2, 3];
+ let encoded = Base58CkString::encode(&data).unwrap();
+ assert_eq!(decode_check_to_array::<5>(encoded.as_str()).unwrap(), data);
+ }
+
+ #[test]
+ #[cfg(feature = "alloc")]
fn base58_roundtrip() {
let s = "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs";
let v: Vec<u8> = decode_check(s).unwrap();
### crypto/src/key.rs
@@ -1635,7 +1635,7 @@ pub mod error {
#[cfg(feature = "alloc")]
pub enum FromWifError {
/// A base58 decoding error.
- Base58(base58::Error),
+ Base58(base58::DecodeCheckError),
/// Base58 decoded data was an invalid length.
InvalidBase58PayloadLength(InvalidBase58PayloadLengthError),
/// Base58 decoded data contained an invalid address version byte.
### key_expression/src/bip32.rs
@@ -1282,7 +1282,7 @@ pub mod error {
#[non_exhaustive]
pub enum ParseXprivError {
/// Base58 encoding error.
- Base58(base58::Error),
+ Base58(base58::DecodeCheckError),
/// Base58 decoded data was an invalid length.
InvalidBase58PayloadLength(InvalidBase58PayloadLengthError),
/// Binary xpriv decode error.
@@ -1314,16 +1314,16 @@ pub mod error {
}
}
- impl From<base58::Error> for ParseXprivError {
- fn from(e: base58::Error) -> Self { Self::Base58(e) }
+ impl From<base58::DecodeCheckError> for ParseXprivError {
+ fn from(e: base58::DecodeCheckError) -> Self { Self::Base58(e) }
}
/// Error parsing a base58check BIP-0032 xpub string.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseXpubError {
/// Base58 encoding error.
- Base58(base58::Error),
+ Base58(base58::DecodeCheckError),
/// Base58 decoded data was an invalid length.
InvalidBase58PayloadLength(InvalidBase58PayloadLengthError),
/// Binary xpub decode error.
@@ -1355,8 +1355,8 @@ pub mod error {
}
}
- impl From<base58::Error> for ParseXpubError {
- fn from(e: base58::Error) -> Self { Self::Base58(e) }
+ impl From<base58::DecodeCheckError> for ParseXpubError {
+ fn from(e: base58::DecodeCheckError) -> Self { Self::Base58(e) }
}
/// Attempted to derive a child of depth 256 or higher.Why this scored 21/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.