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![bech32::u5::try_from_u8(self.witness_version)
109            .map_err(|_| AddressError::InvalidWitnessVersion)?];
110        data.extend_from_slice(&program_base32);
111
112        // Encode using appropriate variant
113        let encoded = if self.witness_version == 0 {
114            // BIP173: Bech32 for version 0
115            bech32::encode(hrp, &data, Variant::Bech32)
116                .map_err(|_| AddressError::InvalidEncoding)?
117        } else {
118            // BIP350: Bech32m for version 1+
119            bech32::encode(hrp, &data, Variant::Bech32m)
120                .map_err(|_| AddressError::InvalidEncoding)?
121        };
122
123        Ok(encoded)
124    }
125
126    /// Decode Bech32 or Bech32m address
127    pub fn decode(encoded: &str) -> Result<Self, AddressError> {
128        // Try Bech32m first (Taproot), then Bech32 (SegWit)
129        let (hrp, data, variant) =
130            bech32::decode(encoded).map_err(|_| AddressError::InvalidEncoding)?;
131
132        // Determine network from HRP
133        let network = match hrp.as_str() {
134            "bc" => Network::Mainnet,
135            "tb" => Network::Testnet,
136            "bcrt" => Network::Regtest,
137            _ => return Err(AddressError::InvalidHRP),
138        };
139
140        if data.is_empty() {
141            return Err(AddressError::InvalidEncoding);
142        }
143
144        // First u5 value is witness version
145        let witness_version_u5 = data[0];
146        let witness_version = witness_version_u5.to_u8();
147        if witness_version > 16 {
148            return Err(AddressError::InvalidWitnessVersion);
149        }
150
151        // Remaining u5 values are witness program (base32 encoded)
152        let program_base32 = &data[1..];
153        let witness_program = base32_to_witness_program(program_base32)?;
154
155        // Validate variant matches witness version
156        match (witness_version, variant) {
157            (0, Variant::Bech32) => {
158                // Correct: SegWit v0 uses Bech32
159            }
160            (1..=16, Variant::Bech32m) => {
161                // Correct: Taproot v1+ uses Bech32m
162            }
163            _ => {
164                return Err(AddressError::UnsupportedVariant);
165            }
166        }
167
168        Ok(BitcoinAddress {
169            network,
170            witness_version,
171            witness_program,
172        })
173    }
174
175    /// Check if address is a Taproot address (P2TR)
176    pub fn is_taproot(&self) -> bool {
177        self.witness_version == 1 && self.witness_program.len() == 32
178    }
179
180    /// Check if address is a SegWit address (P2WPKH or P2WSH)
181    pub fn is_segwit(&self) -> bool {
182        self.witness_version == 0
183    }
184
185    /// Get address type as string
186    pub fn address_type(&self) -> &'static str {
187        match (self.witness_version, self.witness_program.len()) {
188            (0, 20) => "P2WPKH",
189            (0, 32) => "P2WSH",
190            (1, 32) => "P2TR",
191            _ => "Unknown",
192        }
193    }
194}
195
196/// Convert witness program bytes to base32 (u5)
197fn witness_program_to_base32(program: &[u8]) -> Vec<bech32::u5> {
198    // Convert bytes to base32 (returns Vec<u5> directly)
199    program.to_base32()
200}
201
202/// Convert base32 (u5) to witness program bytes
203fn base32_to_witness_program(data: &[bech32::u5]) -> Result<Vec<u8>, AddressError> {
204    Vec::<u8>::from_base32(data).map_err(|_| AddressError::InvalidEncoding)
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn test_encode_segwit_p2wpkh() {
213        // Example P2WPKH address
214        let program = vec![0x75; 20]; // 20 bytes
215        let addr = BitcoinAddress::new(Network::Mainnet, 0, program).unwrap();
216        let encoded = addr.encode().unwrap();
217
218        assert!(encoded.starts_with("bc1"));
219        assert_eq!(addr.witness_version, 0);
220        assert_eq!(addr.witness_program.len(), 20);
221    }
222
223    #[test]
224    fn test_encode_segwit_p2wsh() {
225        // Example P2WSH address
226        let program = vec![0x75; 32]; // 32 bytes
227        let addr = BitcoinAddress::new(Network::Mainnet, 0, program).unwrap();
228        let encoded = addr.encode().unwrap();
229
230        assert!(encoded.starts_with("bc1"));
231        assert_eq!(addr.witness_version, 0);
232        assert_eq!(addr.witness_program.len(), 32);
233    }
234
235    #[test]
236    fn test_encode_taproot_p2tr() {
237        // Example Taproot address (P2TR)
238        let program = vec![0x75; 32]; // 32 bytes
239        let addr = BitcoinAddress::new(Network::Mainnet, 1, program).unwrap();
240        let encoded = addr.encode().unwrap();
241
242        assert!(encoded.starts_with("bc1p"));
243        assert!(addr.is_taproot());
244        assert_eq!(addr.witness_version, 1);
245        assert_eq!(addr.witness_program.len(), 32);
246    }
247
248    #[test]
249    fn test_decode_segwit() {
250        // This is a simplified test - actual addresses would need real test vectors
251        // For now, we test the structure
252        let program = vec![0x75; 20];
253        let addr = BitcoinAddress::new(Network::Mainnet, 0, program.clone()).unwrap();
254        let encoded = addr.encode().unwrap();
255
256        let decoded = BitcoinAddress::decode(&encoded).unwrap();
257        assert_eq!(decoded.witness_version, 0);
258        assert_eq!(decoded.witness_program, program);
259    }
260
261    #[test]
262    fn test_decode_taproot() {
263        let program = vec![0x75; 32];
264        let addr = BitcoinAddress::new(Network::Mainnet, 1, program.clone()).unwrap();
265        let encoded = addr.encode().unwrap();
266
267        let decoded = BitcoinAddress::decode(&encoded).unwrap();
268        assert!(decoded.is_taproot());
269        assert_eq!(decoded.witness_version, 1);
270        assert_eq!(decoded.witness_program, program);
271    }
272
273    #[test]
274    fn test_invalid_witness_version() {
275        let program = vec![0x75; 20];
276        let result = BitcoinAddress::new(Network::Mainnet, 17, program);
277        assert!(result.is_err());
278    }
279
280    #[test]
281    fn test_invalid_witness_length_taproot() {
282        // Taproot must be 32 bytes
283        let program = vec![0x75; 20]; // Wrong length
284        let result = BitcoinAddress::new(Network::Mainnet, 1, program);
285        assert!(result.is_err());
286    }
287
288    #[test]
289    fn test_network_hrp() {
290        assert_eq!(Network::Mainnet.hrp(), "bc");
291        assert_eq!(Network::Testnet.hrp(), "tb");
292        assert_eq!(Network::Regtest.hrp(), "bcrt");
293    }
294
295    #[test]
296    fn test_address_types() {
297        let p2wpkh = BitcoinAddress::new(Network::Mainnet, 0, vec![0; 20]).unwrap();
298        assert_eq!(p2wpkh.address_type(), "P2WPKH");
299        assert!(p2wpkh.is_segwit());
300
301        let p2wsh = BitcoinAddress::new(Network::Mainnet, 0, vec![0; 32]).unwrap();
302        assert_eq!(p2wsh.address_type(), "P2WSH");
303        assert!(p2wsh.is_segwit());
304
305        let p2tr = BitcoinAddress::new(Network::Mainnet, 1, vec![0; 32]).unwrap();
306        assert_eq!(p2tr.address_type(), "P2TR");
307        assert!(p2tr.is_taproot());
308    }
309}