Skip to main content

codec_multibase/
base58btc.rs

1// SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use bs58::decode::Error as Bs58DecodeError;
6use thiserror::Error;
7use zeroize::Zeroizing;
8
9/// Maximum accepted base58btc input length.
10///
11/// The underlying base58 conversion is not linear in input size. This cap keeps
12/// untrusted byte and text inputs bounded while leaving headroom above the
13/// largest currently supported multikey encodings.
14pub const MAX_BASE58BTC_INPUT_LEN: usize = 8 * 1024;
15
16/// Error returned when base58btc decoding fails.
17#[derive(Debug, Error)]
18#[non_exhaustive]
19pub enum Base58Error {
20    /// The output buffer was too small to hold the decoded bytes.
21    #[error("base58btc output buffer too small")]
22    BufferTooSmall,
23    /// Decoding failed for a reason not covered by the other variants.
24    #[error("base58btc decode failed")]
25    DecodeFailed,
26    /// The input contained a character outside the base58btc alphabet.
27    #[error("invalid base58btc character")]
28    InvalidCharacter,
29    /// The input contained a non-ASCII character.
30    #[error("non-ascii base58btc character")]
31    NonAsciiCharacter,
32    /// The input exceeded the accepted base58btc text length.
33    #[error("base58btc input too large")]
34    InputTooLarge,
35}
36
37impl From<Bs58DecodeError> for Base58Error {
38    fn from(value: Bs58DecodeError) -> Self {
39        match value {
40            Bs58DecodeError::BufferTooSmall => Self::BufferTooSmall,
41            Bs58DecodeError::InvalidCharacter { .. } => Self::InvalidCharacter,
42            Bs58DecodeError::NonAsciiCharacter { .. } => Self::NonAsciiCharacter,
43            _ => Self::DecodeFailed,
44        }
45    }
46}
47
48/// Encodes bytes as a base58btc string.
49pub fn base58btc_encode(bytes: &[u8]) -> Result<String, Base58Error> {
50    if bytes.len() > MAX_BASE58BTC_INPUT_LEN {
51        return Err(Base58Error::InputTooLarge);
52    }
53    let mut output = Zeroizing::new(Vec::new());
54    bs58::encode(bytes)
55        .onto(&mut *output)
56        .map_err(|_| Base58Error::BufferTooSmall)?;
57    match String::from_utf8(core::mem::take(&mut *output)) {
58        Ok(encoded) => Ok(encoded),
59        Err(error) => {
60            let _bytes = Zeroizing::new(error.into_bytes());
61            Err(Base58Error::DecodeFailed)
62        }
63    }
64}
65
66/// Decodes a base58btc string into bytes.
67///
68/// Fails closed: returns an error on any invalid or non-ASCII character.
69pub fn base58btc_decode(s: &str) -> Result<Vec<u8>, Base58Error> {
70    if s.len() > MAX_BASE58BTC_INPUT_LEN {
71        return Err(Base58Error::InputTooLarge);
72    }
73    bs58::decode(s).into_vec().map_err(Base58Error::from)
74}