1use bech32::{FromBase32, ToBase32, Variant};
16
17#[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
41pub use blvm_consensus::types::Network;
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct BitcoinAddress {
47 pub network: Network,
49 pub witness_version: u8,
51 pub witness_program: Vec<u8>,
53}
54
55impl BitcoinAddress {
56 pub fn new(
58 network: Network,
59 witness_version: u8,
60 witness_program: Vec<u8>,
61 ) -> Result<Self, AddressError> {
62 if witness_version > 16 {
64 return Err(AddressError::InvalidWitnessVersion);
65 }
66
67 match witness_version {
69 0 => {
70 if witness_program.len() != 20 && witness_program.len() != 32 {
72 return Err(AddressError::InvalidWitnessLength);
73 }
74 }
75 1 => {
76 if witness_program.len() != 32 {
78 return Err(AddressError::InvalidWitnessLength);
79 }
80 }
81 _ => {
82 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 pub fn encode(&self) -> Result<String, AddressError> {
101 let hrp = self.network.hrp();
102
103 let program_base32 = witness_program_to_base32(&self.witness_program);
105
106 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 let encoded = if self.witness_version == 0 {
116 bech32::encode(hrp, &data, Variant::Bech32)
118 .map_err(|_| AddressError::InvalidEncoding)?
119 } else {
120 bech32::encode(hrp, &data, Variant::Bech32m)
122 .map_err(|_| AddressError::InvalidEncoding)?
123 };
124
125 Ok(encoded)
126 }
127
128 pub fn decode(encoded: &str) -> Result<Self, AddressError> {
130 let (hrp, data, variant) =
132 bech32::decode(encoded).map_err(|_| AddressError::InvalidEncoding)?;
133
134 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 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 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 match (witness_version, variant) {
162 (0, Variant::Bech32) => {
163 }
165 (1..=16, Variant::Bech32m) => {
166 }
168 _ => {
169 return Err(AddressError::UnsupportedVariant);
170 }
171 }
172
173 Ok(BitcoinAddress {
174 network,
175 witness_version,
176 witness_program,
177 })
178 }
179
180 pub fn is_taproot(&self) -> bool {
182 self.witness_version == 1 && self.witness_program.len() == 32
183 }
184
185 pub fn is_segwit(&self) -> bool {
187 self.witness_version == 0
188 }
189
190 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
201fn witness_program_to_base32(program: &[u8]) -> Vec<bech32::u5> {
203 program.to_base32()
205}
206
207fn 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 let program = vec![0x75; 20]; 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 let program = vec![0x75; 32]; 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 let program = vec![0x75; 32]; 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 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 let program = vec![0x75; 20]; 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}