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
157 match (witness_version, variant) {
159 (0, Variant::Bech32) => {
160 }
162 (1..=16, Variant::Bech32m) => {
163 }
165 _ => {
166 return Err(AddressError::UnsupportedVariant);
167 }
168 }
169
170 Ok(BitcoinAddress {
171 network,
172 witness_version,
173 witness_program,
174 })
175 }
176
177 pub fn is_taproot(&self) -> bool {
179 self.witness_version == 1 && self.witness_program.len() == 32
180 }
181
182 pub fn is_segwit(&self) -> bool {
184 self.witness_version == 0
185 }
186
187 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
198fn witness_program_to_base32(program: &[u8]) -> Vec<bech32::u5> {
200 program.to_base32()
202}
203
204fn 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 let program = vec![0x75; 20]; 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 let program = vec![0x75; 32]; 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 let program = vec![0x75; 32]; 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 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 let program = vec![0x75; 20]; 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}