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
157        // Validate variant matches witness version
158        match (witness_version, variant) {
159            (0, Variant::Bech32) => {
160                // Correct: SegWit v0 uses Bech32
161            }
162            (1..=16, Variant::Bech32m) => {
163                // Correct: Taproot v1+ uses Bech32m
164            }
165            _ => {
166                return Err(AddressError::UnsupportedVariant);
167            }
168        }
169
170        Ok(BitcoinAddress {
171            network,
172            witness_version,
173            witness_program,
174        })
175    }
176
177    /// Check if address is a Taproot address (P2TR)
178    pub fn is_taproot(&self) -> bool {
179        self.witness_version == 1 && self.witness_program.len() == 32
180    }
181
182    /// Check if address is a SegWit address (P2WPKH or P2WSH)
183    pub fn is_segwit(&self) -> bool {
184        self.witness_version == 0
185    }
186
187    /// Get address type as string
188    pub fn address_type(&self) -> &'static str {
189        match (self.witness_version, self.witness_program.len()) {
190            (0, 20) => "P2WPKH",
191            (0, 32) => "P2WSH",
192            (1, 32) => "P2TR",
193            _ => "Unknown",
194        }
195    }
196}
197
198/// Convert witness program bytes to base32 (u5)
199fn witness_program_to_base32(program: &[u8]) -> Vec<bech32::u5> {
200    // Convert bytes to base32 (returns Vec<u5> directly)
201    program.to_base32()
202}
203
204/// Convert base32 (u5) to witness program bytes
205fn base32_to_witness_program(data: &[bech32::u5]) -> Result<Vec<u8>, AddressError> {
206    Vec::<u8>::from_base32(data).map_err(|_| AddressError::InvalidEncoding)
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn test_encode_segwit_p2wpkh() {
215        // Example P2WPKH address
216        let program = vec![0x75; 20]; // 20 bytes
217        let addr = BitcoinAddress::new(Network::Mainnet, 0, program).unwrap();
218        let encoded = addr.encode().unwrap();
219
220        assert!(encoded.starts_with("bc1"));
221        assert_eq!(addr.witness_version, 0);
222        assert_eq!(addr.witness_program.len(), 20);
223    }
224
225    #[test]
226    fn test_encode_segwit_p2wsh() {
227        // Example P2WSH address
228        let program = vec![0x75; 32]; // 32 bytes
229        let addr = BitcoinAddress::new(Network::Mainnet, 0, program).unwrap();
230        let encoded = addr.encode().unwrap();
231
232        assert!(encoded.starts_with("bc1"));
233        assert_eq!(addr.witness_version, 0);
234        assert_eq!(addr.witness_program.len(), 32);
235    }
236
237    #[test]
238    fn test_encode_taproot_p2tr() {
239        // Example Taproot address (P2TR)
240        let program = vec![0x75; 32]; // 32 bytes
241        let addr = BitcoinAddress::new(Network::Mainnet, 1, program).unwrap();
242        let encoded = addr.encode().unwrap();
243
244        assert!(encoded.starts_with("bc1p"));
245        assert!(addr.is_taproot());
246        assert_eq!(addr.witness_version, 1);
247        assert_eq!(addr.witness_program.len(), 32);
248    }
249
250    #[test]
251    fn test_decode_segwit() {
252        // This is a simplified test - actual addresses would need real test vectors
253        // For now, we test the structure
254        let program = vec![0x75; 20];
255        let addr = BitcoinAddress::new(Network::Mainnet, 0, program.clone()).unwrap();
256        let encoded = addr.encode().unwrap();
257
258        let decoded = BitcoinAddress::decode(&encoded).unwrap();
259        assert_eq!(decoded.witness_version, 0);
260        assert_eq!(decoded.witness_program, program);
261    }
262
263    #[test]
264    fn test_decode_taproot() {
265        let program = vec![0x75; 32];
266        let addr = BitcoinAddress::new(Network::Mainnet, 1, program.clone()).unwrap();
267        let encoded = addr.encode().unwrap();
268
269        let decoded = BitcoinAddress::decode(&encoded).unwrap();
270        assert!(decoded.is_taproot());
271        assert_eq!(decoded.witness_version, 1);
272        assert_eq!(decoded.witness_program, program);
273    }
274
275    #[test]
276    fn test_invalid_witness_version() {
277        let program = vec![0x75; 20];
278        let result = BitcoinAddress::new(Network::Mainnet, 17, program);
279        assert!(result.is_err());
280    }
281
282    #[test]
283    fn test_invalid_witness_length_taproot() {
284        // Taproot must be 32 bytes
285        let program = vec![0x75; 20]; // Wrong length
286        let result = BitcoinAddress::new(Network::Mainnet, 1, program);
287        assert!(result.is_err());
288    }
289
290    #[test]
291    fn test_network_hrp() {
292        assert_eq!(Network::Mainnet.hrp(), "bc");
293        assert_eq!(Network::Testnet.hrp(), "tb");
294        assert_eq!(Network::Regtest.hrp(), "bcrt");
295    }
296
297    #[test]
298    fn test_address_types() {
299        let p2wpkh = BitcoinAddress::new(Network::Mainnet, 0, vec![0; 20]).unwrap();
300        assert_eq!(p2wpkh.address_type(), "P2WPKH");
301        assert!(p2wpkh.is_segwit());
302
303        let p2wsh = BitcoinAddress::new(Network::Mainnet, 0, vec![0; 32]).unwrap();
304        assert_eq!(p2wsh.address_type(), "P2WSH");
305        assert!(p2wsh.is_segwit());
306
307        let p2tr = BitcoinAddress::new(Network::Mainnet, 1, vec![0; 32]).unwrap();
308        assert_eq!(p2tr.address_type(), "P2TR");
309        assert!(p2tr.is_taproot());
310    }
311}