c32address 0.1.0

Crockford Base32Check encoding for Stacks addresses
Documentation
// Copyright (c) 2025 New Internet Labs Limited
// SPDX-License-Identifier: MIT
#![doc = include_str!("../README.md")]
use thiserror::Error;
use log::{debug, error, info};

/// The prefix character used for all Stacks addresses
pub const STACKS_ADDRESS_PREFIX: char = 'S';

#[derive(Debug, Error)]
pub enum AddressError {
    #[error("Invalid hash160 length: expected 20 bytes, got {0}")]
    InvalidLength(usize),
    #[error("Invalid address format: {0}")]
    InvalidFormat(String),
    #[error("Invalid version: {0}")]
    InvalidVersion(u8),
    #[error("Base58 decode error: {0}")]
    Base58Error(String),
}

/// Encode a Stacks address from version byte and hash160
pub fn encode_address(version: u8, hash160: &[u8]) -> Result<String, AddressError> {
    debug!("Encoding address with version {} and hash160 length {}", version, hash160.len());
    
    // Validate version
    if version >= 32 {
        error!("Invalid version {}: must be less than 32", version);
        return Err(AddressError::InvalidVersion(version));
    }

    // Validate hash160 length
    if hash160.len() != 20 {
        error!("Invalid hash160 length {}: must be 20 bytes", hash160.len());
        return Err(AddressError::InvalidLength(hash160.len()));
    }

    let c32string_result = c32::encode_check(hash160, version);

    // Prepend 'S'
    match c32string_result {    
        Ok(c32string) => {
            let address = format!("{}{}", STACKS_ADDRESS_PREFIX, c32string);
            info!("Successfully encoded address: {}", address);
            Ok(address)
        },
        Err(e) => {
            error!("Failed to encode c32check string: {}", e);
            Err(AddressError::InvalidFormat(e.to_string()))
        }
    }
}

/// Decode a Stacks address into version byte and hash160
pub fn decode_address(address: &str) -> Result<(u8, Vec<u8>), AddressError> {
    debug!("Decoding address: {}", address);
    
    // Basic format validation
    if address.len() < 5 {
        error!("Address too short: length {}", address.len());
        return Err(AddressError::InvalidFormat(
            "Address too short".to_string()
        ));
    }

    if !address.starts_with(STACKS_ADDRESS_PREFIX) {
        error!("Invalid address prefix: must start with '{}'", STACKS_ADDRESS_PREFIX);
        return Err(AddressError::InvalidFormat(
            format!("Address must start with '{}'", STACKS_ADDRESS_PREFIX)
        ));
    }

    match c32::decode_check(&address[1..]) {
        Ok((version, hash)) => {
            info!("Successfully decoded address {} to version {} and hash160", address, version);
            Ok((version, hash))
        },
        Err(e) => {
            error!("Failed to decode c32check string: {}", e);
            Err(AddressError::InvalidFormat(e.to_string()))
        }
    }
}