Skip to main content

mx_core/
bech32_utils.rs

1//! Bech32 address encoding and decoding utilities.
2
3use crate::error::CoreError;
4use prost::bytes::Bytes;
5
6/// Decodes a bech32-encoded address into raw bytes.
7///
8/// # Arguments
9/// * `hrp_addr` - A bech32-encoded address string (e.g., "erd1...")
10///
11/// # Returns
12/// The raw 32-byte public key, or an error if decoding fails
13///
14/// # Errors
15/// Returns an error if:
16/// - The address is not valid bech32
17/// - The decoded data is not exactly 32 bytes
18pub fn decode_bech32(hrp_addr: &str) -> Result<Bytes, CoreError> {
19    let (_hrp, raw) =
20        bech32::decode(hrp_addr).map_err(|e| CoreError::InvalidBech32(e.to_string()))?;
21
22    if raw.len() != 32 {
23        return Err(CoreError::InvalidAddressLength(raw.len()));
24    }
25
26    Ok(Bytes::from(raw))
27}
28
29/// Decodes an optional bech32 address, returning empty Bytes if None or empty.
30///
31/// # Arguments
32/// * `addr` - Optional bech32-encoded address string
33///
34/// # Returns
35/// The raw bytes, or empty Bytes if input is None/empty
36pub fn decode_optional_bech32(addr: Option<&str>) -> Result<Bytes, CoreError> {
37    match addr {
38        Some(a) if !a.trim().is_empty() => decode_bech32(a),
39        _ => Ok(Bytes::new()),
40    }
41}
42
43/// Encodes raw bytes as a bech32 address with the given HRP.
44///
45/// # Arguments
46/// * `hrp` - Human-readable prefix (e.g., "erd" for `MultiversX` mainnet)
47/// * `data` - Raw bytes to encode (typically 32-byte public key)
48///
49/// # Returns
50/// The bech32-encoded address string
51///
52/// # Errors
53/// Returns an error if encoding fails
54pub fn encode_bech32(hrp: &str, data: &[u8]) -> Result<String, CoreError> {
55    bech32::encode::<bech32::Bech32>(
56        bech32::Hrp::parse(hrp).map_err(|e| CoreError::InvalidHrp(e.to_string()))?,
57        data,
58    )
59    .map_err(|e| CoreError::Bech32Encode(e.to_string()))
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_decode_bech32() {
68        let addr = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th";
69        let result = decode_bech32(addr);
70        assert!(result.is_ok());
71        assert_eq!(result.unwrap().len(), 32);
72    }
73
74    #[test]
75    fn test_decode_bech32_invalid() {
76        let result = decode_bech32("invalid");
77        assert!(result.is_err());
78    }
79
80    #[test]
81    fn test_encode_decode_roundtrip() {
82        let original = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th";
83        let decoded = decode_bech32(original).unwrap();
84        let encoded = encode_bech32("erd", &decoded).unwrap();
85        assert_eq!(original, encoded);
86    }
87
88    #[test]
89    fn test_decode_optional_bech32_none() {
90        let result = decode_optional_bech32(None);
91        assert!(result.is_ok());
92        assert!(result.unwrap().is_empty());
93    }
94
95    #[test]
96    fn test_decode_optional_bech32_empty() {
97        let result = decode_optional_bech32(Some(""));
98        assert!(result.is_ok());
99        assert!(result.unwrap().is_empty());
100    }
101}