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![bech32::u5::try_from_u8(self.witness_version)
109 .map_err(|_| AddressError::InvalidWitnessVersion)?];
110 data.extend_from_slice(&program_base32);
111
112 let encoded = if self.witness_version == 0 {
114 bech32::encode(hrp, &data, Variant::Bech32)
116 .map_err(|_| AddressError::InvalidEncoding)?
117 } else {
118 bech32::encode(hrp, &data, Variant::Bech32m)
120 .map_err(|_| AddressError::InvalidEncoding)?
121 };
122
123 Ok(encoded)
124 }
125
126 pub fn decode(encoded: &str) -> Result<Self, AddressError> {
128 let (hrp, data, variant) =
130 bech32::decode(encoded).map_err(|_| AddressError::InvalidEncoding)?;
131
132 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 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 let program_base32 = &data[1..];
153 let witness_program = base32_to_witness_program(program_base32)?;
154
155 match (witness_version, variant) {
157 (0, Variant::Bech32) => {
158 }
160 (1..=16, Variant::Bech32m) => {
161 }
163 _ => {
164 return Err(AddressError::UnsupportedVariant);
165 }
166 }
167
168 Ok(BitcoinAddress {
169 network,
170 witness_version,
171 witness_program,
172 })
173 }
174
175 pub fn is_taproot(&self) -> bool {
177 self.witness_version == 1 && self.witness_program.len() == 32
178 }
179
180 pub fn is_segwit(&self) -> bool {
182 self.witness_version == 0
183 }
184
185 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
196fn witness_program_to_base32(program: &[u8]) -> Vec<bech32::u5> {
198 program.to_base32()
200}
201
202fn 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 let program = vec![0x75; 20]; 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 let program = vec![0x75; 32]; 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 let program = vec![0x75; 32]; 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 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 let program = vec![0x75; 20]; 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}