Skip to main content

blvm_protocol/
address.rs

1//! BIP350/351: Bech32m Address Encoding
2//!
3//! Implements Bech32m encoding for Taproot (P2TR) addresses and Bech32 encoding
4//! for SegWit (P2WPKH/P2WSH) addresses.
5//!
6//! BIP173: Bech32 encoding (SegWit addresses) - bc1...
7//! BIP350: Bech32m encoding (Taproot addresses) - bc1p...
8//! BIP351: Version 1 witness encoding for Taproot
9//!
10//! Specifications:
11//! - https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
12//! - https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki
13//! - https://github.com/bitcoin/bips/blob/master/bip-0351.mediawiki
14
15use bech32::{FromBase32, ToBase32, Variant};
16
17/// Bitcoin address encoding error
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum AddressError {
20    InvalidWitnessVersion,
21    InvalidWitnessLength,
22    InvalidEncoding,
23    UnsupportedVariant,
24    InvalidHRP,
25}
26
27impl std::fmt::Display for AddressError {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            AddressError::InvalidWitnessVersion => write!(f, "Invalid witness version"),
31            AddressError::InvalidWitnessLength => write!(f, "Invalid witness data length"),
32            AddressError::InvalidEncoding => write!(f, "Invalid address encoding"),
33            AddressError::UnsupportedVariant => write!(f, "Unsupported address variant"),
34            AddressError::InvalidHRP => write!(f, "Invalid human-readable part"),
35        }
36    }
37}
38
39impl std::error::Error for AddressError {}
40
41// Re-export Network from blvm-consensus to avoid duplication
42pub use blvm_consensus::types::Network;
43
44/// Encoded Bitcoin address
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct BitcoinAddress {
47    /// Network identifier
48    pub network: Network,
49    /// Witness version (0 for SegWit, 1 for Taproot)
50    pub witness_version: u8,
51    /// Witness program (20 bytes for P2WPKH, 32 bytes for P2WSH/P2TR)
52    pub witness_program: Vec<u8>,
53}
54
55impl BitcoinAddress {
56    /// Create a new Bech32/Bech32m address
57    pub fn new(
58        network: Network,
59        witness_version: u8,
60        witness_program: Vec<u8>,
61    ) -> Result<Self, AddressError> {
62        // Validate witness version
63        if witness_version > 16 {
64            return Err(AddressError::InvalidWitnessVersion);
65        }
66
67        // Validate witness program length
68        match witness_version {
69            0 => {
70                // SegWit v0: P2WPKH (20 bytes) or P2WSH (32 bytes)
71                if witness_program.len() != 20 && witness_program.len() != 32 {
72                    return Err(AddressError::InvalidWitnessLength);
73                }
74            }
75            1 => {
76                // Taproot v1: P2TR (32 bytes)
77                if witness_program.len() != 32 {
78                    return Err(AddressError::InvalidWitnessLength);
79                }
80            }
81            _ => {
82                // Future witness versions: 2-16 bytes (as per BIP342)
83                if witness_program.len() < 2 || witness_program.len() > 40 {
84                    return Err(AddressError::InvalidWitnessLength);
85                }
86            }
87        }
88
89        Ok(BitcoinAddress {
90            network,
91            witness_version,
92            witness_program,
93        })
94    }
95
96    /// Encode address to Bech32 (for SegWit v0) or Bech32m (for Taproot v1+)
97    ///
98    /// BIP173: Witness version 0 uses Bech32
99    /// BIP350: Witness version 1+ uses Bech32m
100    pub fn encode(&self) -> Result<String, AddressError> {
101        let hrp = self.network.hrp();
102
103        // Convert witness program to base32 (u5)
104        let program_base32 = witness_program_to_base32(&self.witness_program);
105
106        // Combine witness version and program as u5 values
107        // Witness version needs to be converted to u5 (it's 0-16, fits in u5)
108        let mut data = vec![
109            bech32::u5::try_from_u8(self.witness_version)
110                .map_err(|_| AddressError::InvalidWitnessVersion)?,
111        ];
112        data.extend_from_slice(&program_base32);
113
114        // Encode using appropriate variant
115        let encoded = if self.witness_version == 0 {
116            // BIP173: Bech32 for version 0
117            bech32::encode(hrp, &data, Variant::Bech32)
118                .map_err(|_| AddressError::InvalidEncoding)?
119        } else {
120            // BIP350: Bech32m for version 1+
121            bech32::encode(hrp, &data, Variant::Bech32m)
122                .map_err(|_| AddressError::InvalidEncoding)?
123        };
124
125        Ok(encoded)
126    }
127
128    /// Decode Bech32 or Bech32m address
129    pub fn decode(encoded: &str) -> Result<Self, AddressError> {
130        // Try Bech32m first (Taproot), then Bech32 (SegWit)
131        let (hrp, data, variant) =
132            bech32::decode(encoded).map_err(|_| AddressError::InvalidEncoding)?;
133
134        // Determine network from HRP
135        let network = match hrp.as_str() {
136            "bc" => Network::Mainnet,
137            "tb" => Network::Testnet,
138            "bcrt" => Network::Regtest,
139            _ => return Err(AddressError::InvalidHRP),
140        };
141
142        if data.is_empty() {
143            return Err(AddressError::InvalidEncoding);
144        }
145
146        // First u5 value is witness version
147        let witness_version_u5 = data[0];
148        let witness_version = witness_version_u5.to_u8();
149        if witness_version > 16 {
150            return Err(AddressError::InvalidWitnessVersion);
151        }
152
153        // Remaining u5 values are witness program (base32 encoded)
154        let program_base32 = &data[1..];
155        let witness_program = base32_to_witness_program(program_base32)?;
156        if witness_version == 0 && witness_program.len() != 20 && witness_program.len() != 32 {
157            return Err(AddressError::InvalidWitnessLength);
158        }
159
160        // Validate variant matches witness version
161        match (witness_version, variant) {
162            (0, Variant::Bech32) => {
163                // Correct: SegWit v0 uses Bech32
164            }
165            (1..=16, Variant::Bech32m) => {
166                // Correct: Taproot v1+ uses Bech32m
167            }
168            _ => {
169                return Err(AddressError::UnsupportedVariant);
170            }
171        }
172
173        Ok(BitcoinAddress {
174            network,
175            witness_version,
176            witness_program,
177        })
178    }
179
180    /// Check if address is a Taproot address (P2TR)
181    pub fn is_taproot(&self) -> bool {
182        self.witness_version == 1 && self.witness_program.len() == 32
183    }
184
185    /// Check if address is a SegWit address (P2WPKH or P2WSH)
186    pub fn is_segwit(&self) -> bool {
187        self.witness_version == 0
188    }
189
190    /// Get address type as string
191    pub fn address_type(&self) -> &'static str {
192        match (self.witness_version, self.witness_program.len()) {
193            (0, 20) => "P2WPKH",
194            (0, 32) => "P2WSH",
195            (1, 32) => "P2TR",
196            _ => "Unknown",
197        }
198    }
199}
200
201/// Convert witness program bytes to base32 (u5)
202fn witness_program_to_base32(program: &[u8]) -> Vec<bech32::u5> {
203    // Convert bytes to base32 (returns Vec<u5> directly)
204    program.to_base32()
205}
206
207/// Convert base32 (u5) to witness program bytes
208fn base32_to_witness_program(data: &[bech32::u5]) -> Result<Vec<u8>, AddressError> {
209    Vec::<u8>::from_base32(data).map_err(|_| AddressError::InvalidEncoding)
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn test_encode_segwit_p2wpkh() {
218        // Example P2WPKH address
219        let program = vec![0x75; 20]; // 20 bytes
220        let addr = BitcoinAddress::new(Network::Mainnet, 0, program).unwrap();
221        let encoded = addr.encode().unwrap();
222
223        assert!(encoded.starts_with("bc1"));
224        assert_eq!(addr.witness_version, 0);
225        assert_eq!(addr.witness_program.len(), 20);
226    }
227
228    #[test]
229    fn test_encode_segwit_p2wsh() {
230        // Example P2WSH address
231        let program = vec![0x75; 32]; // 32 bytes
232        let addr = BitcoinAddress::new(Network::Mainnet, 0, program).unwrap();
233        let encoded = addr.encode().unwrap();
234
235        assert!(encoded.starts_with("bc1"));
236        assert_eq!(addr.witness_version, 0);
237        assert_eq!(addr.witness_program.len(), 32);
238    }
239
240    #[test]
241    fn test_encode_taproot_p2tr() {
242        // Example Taproot address (P2TR)
243        let program = vec![0x75; 32]; // 32 bytes
244        let addr = BitcoinAddress::new(Network::Mainnet, 1, program).unwrap();
245        let encoded = addr.encode().unwrap();
246
247        assert!(encoded.starts_with("bc1p"));
248        assert!(addr.is_taproot());
249        assert_eq!(addr.witness_version, 1);
250        assert_eq!(addr.witness_program.len(), 32);
251    }
252
253    #[test]
254    fn test_decode_segwit() {
255        // This is a simplified test - actual addresses would need real test vectors
256        // For now, we test the structure
257        let program = vec![0x75; 20];
258        let addr = BitcoinAddress::new(Network::Mainnet, 0, program.clone()).unwrap();
259        let encoded = addr.encode().unwrap();
260
261        let decoded = BitcoinAddress::decode(&encoded).unwrap();
262        assert_eq!(decoded.witness_version, 0);
263        assert_eq!(decoded.witness_program, program);
264    }
265
266    #[test]
267    fn test_decode_taproot() {
268        let program = vec![0x75; 32];
269        let addr = BitcoinAddress::new(Network::Mainnet, 1, program.clone()).unwrap();
270        let encoded = addr.encode().unwrap();
271
272        let decoded = BitcoinAddress::decode(&encoded).unwrap();
273        assert!(decoded.is_taproot());
274        assert_eq!(decoded.witness_version, 1);
275        assert_eq!(decoded.witness_program, program);
276    }
277
278    #[test]
279    fn test_invalid_witness_version() {
280        let program = vec![0x75; 20];
281        let result = BitcoinAddress::new(Network::Mainnet, 17, program);
282        assert!(result.is_err());
283    }
284
285    #[test]
286    fn test_invalid_witness_length_taproot() {
287        // Taproot must be 32 bytes
288        let program = vec![0x75; 20]; // Wrong length
289        let result = BitcoinAddress::new(Network::Mainnet, 1, program);
290        assert!(result.is_err());
291    }
292
293    #[test]
294    fn test_network_hrp() {
295        assert_eq!(Network::Mainnet.hrp(), "bc");
296        assert_eq!(Network::Testnet.hrp(), "tb");
297        assert_eq!(Network::Regtest.hrp(), "bcrt");
298    }
299
300    #[test]
301    fn test_address_types() {
302        let p2wpkh = BitcoinAddress::new(Network::Mainnet, 0, vec![0; 20]).unwrap();
303        assert_eq!(p2wpkh.address_type(), "P2WPKH");
304        assert!(p2wpkh.is_segwit());
305
306        let p2wsh = BitcoinAddress::new(Network::Mainnet, 0, vec![0; 32]).unwrap();
307        assert_eq!(p2wsh.address_type(), "P2WSH");
308        assert!(p2wsh.is_segwit());
309
310        let p2tr = BitcoinAddress::new(Network::Mainnet, 1, vec![0; 32]).unwrap();
311        assert_eq!(p2tr.address_type(), "P2TR");
312        assert!(p2tr.is_taproot());
313    }
314}