use bech32::primitives::hrp::Hrp;
use bech32::Fe32;
use bitcoin::{Script, ScriptBuf, WitnessProgram, WitnessVersion};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodedAddress {
pub hrp: String,
pub version: u8,
pub program: Vec<u8>,
pub script: ScriptBuf,
}
pub fn decode_address(address: &str) -> Option<DecodedAddress> {
let (hrp, version, program) = bech32::segwit::decode(address).ok()?;
let version = version.to_u8();
let wv = WitnessVersion::try_from(version).ok()?;
let wp = WitnessProgram::new(wv, &program).ok()?;
let script = ScriptBuf::new_witness_program(&wp);
Some(DecodedAddress {
hrp: hrp.to_lowercase(),
version,
program,
script,
})
}
pub fn address_to_script(address: &str) -> Option<ScriptBuf> {
decode_address(address).map(|d| d.script)
}
pub fn script_to_address(script: &Script, hrp: &str) -> Option<String> {
let version = script.witness_version()?;
let program = &script.as_bytes()[2..];
let hrp = Hrp::parse(hrp).ok()?;
let fe = Fe32::try_from(version.to_num()).ok()?;
bech32::segwit::encode(hrp, fe, program).ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_and_refusals() {
let addr = "tb1pvts4e2zcrujj9zey3kadyfgh2xs93v8va8ae9ldhukpxy2n3848qyqurhc";
let d = decode_address(addr).unwrap();
assert_eq!(d.hrp, "tb");
assert_eq!(d.version, 1);
assert_eq!(d.program.len(), 32);
assert_eq!(script_to_address(&d.script, "tb").as_deref(), Some(addr));
assert_eq!(script_to_address(&d.script, "ts").unwrap()[..4], *"ts1p");
let v0 = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
assert_eq!(
decode_address(v0).unwrap().script.to_hex_string(),
"0014751e76e8199196d454941c45d1b3a323f1433bd6"
);
assert_eq!(
script_to_address(&decode_address(v0).unwrap().script, "bc").as_deref(),
Some(v0)
);
assert!(decode_address("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh").is_none());
assert!(
decode_address("tb1PVTS4E2ZCRUJJ9ZEY3KADYFGH2XS93V8VA8AE9LDHUKPXY2N3848QYQURHC")
.is_none()
);
assert!(script_to_address(&ScriptBuf::from_hex("6a04deadbeef").unwrap(), "tb").is_none());
}
}