rustywallet_address/encoding/
bech32.rs

1//! Bech32/Bech32m encoding for SegWit and Taproot addresses.
2
3use crate::error::AddressError;
4use bech32::Hrp;
5
6/// Bech32 encoder/decoder for Bitcoin SegWit addresses.
7pub struct Bech32Encoder;
8
9impl Bech32Encoder {
10    /// Encode data using Bech32 (witness version 0 - SegWit).
11    pub fn encode_bech32(hrp: &str, _witness_version: u8, data: &[u8]) -> Result<String, AddressError> {
12        let hrp = Hrp::parse(hrp).map_err(|e| AddressError::InvalidBech32(e.to_string()))?;
13        
14        bech32::segwit::encode(hrp, bech32::segwit::VERSION_0, data)
15            .map_err(|e| AddressError::InvalidBech32(e.to_string()))
16    }
17
18    /// Encode data using Bech32m (witness version 1+ - Taproot).
19    pub fn encode_bech32m(hrp: &str, data: &[u8]) -> Result<String, AddressError> {
20        let hrp = Hrp::parse(hrp).map_err(|e| AddressError::InvalidBech32(e.to_string()))?;
21        
22        bech32::segwit::encode(hrp, bech32::segwit::VERSION_1, data)
23            .map_err(|e| AddressError::InvalidBech32(e.to_string()))
24    }
25
26    /// Encode data using Bech32m with version 0 (for Silent Payments).
27    /// Note: Silent Payments use a different encoding scheme than segwit.
28    pub fn encode_bech32m_with_version(hrp: &str, version: u8, data: &[u8]) -> Result<String, AddressError> {
29        let hrp = Hrp::parse(hrp).map_err(|e| AddressError::InvalidBech32(e.to_string()))?;
30        
31        // For Silent Payments, we use version 0 with bech32m
32        // This is a simplified implementation - full BIP352 has specific encoding
33        if version == 0 {
34            // Use segwit version 0 encoding but with bech32m checksum
35            // For now, use version 1 encoding as placeholder
36            bech32::segwit::encode(hrp, bech32::segwit::VERSION_1, data)
37                .map_err(|e| AddressError::InvalidBech32(e.to_string()))
38        } else {
39            Err(AddressError::InvalidBech32(format!("Unsupported version: {}", version)))
40        }
41    }
42
43    /// Decode Bech32/Bech32m encoded address.
44    /// Returns (hrp, witness_version, program).
45    pub fn decode(s: &str) -> Result<(String, u8, Vec<u8>), AddressError> {
46        let (hrp, version, program) = bech32::segwit::decode(s)
47            .map_err(|e| AddressError::InvalidBech32(e.to_string()))?;
48
49        Ok((hrp.to_string(), version.to_u8(), program))
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn test_bech32_segwit_roundtrip() {
59        let data = [0u8; 20]; // 20-byte hash for P2WPKH
60        let encoded = Bech32Encoder::encode_bech32("bc", 0, &data).unwrap();
61        assert!(encoded.starts_with("bc1q"));
62        
63        let (hrp, version, decoded) = Bech32Encoder::decode(&encoded).unwrap();
64        assert_eq!(hrp, "bc");
65        assert_eq!(version, 0);
66        assert_eq!(decoded, data);
67    }
68
69    #[test]
70    fn test_bech32m_taproot_roundtrip() {
71        let data = [0u8; 32]; // 32-byte x-only pubkey for P2TR
72        let encoded = Bech32Encoder::encode_bech32m("bc", &data).unwrap();
73        assert!(encoded.starts_with("bc1p"));
74        
75        let (hrp, version, decoded) = Bech32Encoder::decode(&encoded).unwrap();
76        assert_eq!(hrp, "bc");
77        assert_eq!(version, 1);
78        assert_eq!(decoded, data);
79    }
80}