use std::str::FromStr;
use elements::AddressParams;
use crate::server::{Error, Network};
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Address {
Bitcoin(bitcoin::Address),
Elements(elements::Address),
}
impl Address {
pub fn from_str(s: &str, network: Network) -> Result<Self, Error> {
Ok(match network {
Network::Liquid => liquid_address(s, &AddressParams::LIQUID)?,
Network::LiquidTestnet => liquid_address(s, &AddressParams::LIQUID_TESTNET)?,
Network::ElementsRegtest => liquid_address(s, &AddressParams::ELEMENTS)?,
Network::Bitcoin => bitcoin_address(s, bitcoin::Network::Bitcoin)?,
Network::BitcoinTestnet => bitcoin_address(s, bitcoin::Network::Testnet)?,
Network::BitcoinRegtest => bitcoin_address(s, bitcoin::Network::Regtest)?,
Network::BitcoinSignet => bitcoin_address(s, bitcoin::Network::Signet)?,
})
}
pub(crate) fn script_pubkey(&self) -> elements::Script {
match self {
Address::Bitcoin(addr) => addr.script_pubkey().to_bytes().into(),
Address::Elements(addr) => addr.script_pubkey().clone(),
}
}
pub fn elements(&self) -> Option<&elements::Address> {
match self {
Address::Bitcoin(_) => None,
Address::Elements(addr) => Some(addr),
}
}
pub fn ensure_not_blinded(&self) -> Result<(), Error> {
match self {
Address::Bitcoin(_) => Ok(()),
Address::Elements(addr) => {
if addr.is_blinded() {
Err(Error::AddressCannotBeBlinded)
} else {
Ok(())
}
}
}
}
pub fn to_unconfidential(&self) -> Option<Address> {
self.elements()
.map(|addr| Address::Elements(addr.to_unconfidential()))
}
}
impl std::fmt::Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Address::Bitcoin(addr) => addr.fmt(f),
Address::Elements(addr) => addr.fmt(f),
}
}
}
fn bitcoin_address(s: &str, network: bitcoin::Network) -> Result<Address, Error> {
let addr = bitcoin::Address::from_str(s).map_err(|e| Error::String(format!("{e:?}")))?;
let addr = addr
.require_network(network)
.map_err(|e| Error::String(format!("{e:?}")))?;
Ok(Address::Bitcoin(addr))
}
fn liquid_address(s: &str, params: &'static AddressParams) -> Result<Address, Error> {
let addr = elements::Address::from_str(s).map_err(|e| Error::String(format!("{e:?}")))?;
if addr.params != params {
return Err(Error::WrongNetwork);
}
Ok(Address::Elements(addr))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bitcoin_mainnet_addresses() {
let addr =
Address::from_str("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", Network::Bitcoin).unwrap();
assert!(matches!(addr, Address::Bitcoin(_)));
assert_eq!(addr.to_string(), "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2");
let addr =
Address::from_str("3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy", Network::Bitcoin).unwrap();
assert!(matches!(addr, Address::Bitcoin(_)));
assert_eq!(addr.to_string(), "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy");
let addr = Address::from_str(
"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
Network::Bitcoin,
)
.unwrap();
assert!(matches!(addr, Address::Bitcoin(_)));
assert_eq!(
addr.to_string(),
"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"
);
let addr = Address::from_str(
"bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3",
Network::Bitcoin,
)
.unwrap();
assert!(matches!(addr, Address::Bitcoin(_)));
assert_eq!(
addr.to_string(),
"bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3"
);
}
#[test]
fn test_bitcoin_testnet_addresses() {
let addr = Address::from_str(
"mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn",
Network::BitcoinTestnet,
)
.unwrap();
assert!(matches!(addr, Address::Bitcoin(_)));
assert_eq!(addr.to_string(), "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn");
let addr = Address::from_str(
"2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc",
Network::BitcoinTestnet,
)
.unwrap();
assert!(matches!(addr, Address::Bitcoin(_)));
assert_eq!(addr.to_string(), "2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc");
let addr = Address::from_str(
"tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx",
Network::BitcoinTestnet,
)
.unwrap();
assert!(matches!(addr, Address::Bitcoin(_)));
assert_eq!(
addr.to_string(),
"tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx"
);
}
#[test]
fn test_bitcoin_regtest_addresses() {
let addr = Address::from_str(
"bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080",
Network::BitcoinRegtest,
)
.unwrap();
assert!(matches!(addr, Address::Bitcoin(_)));
assert_eq!(
addr.to_string(),
"bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080"
);
}
#[test]
fn test_bitcoin_signet_addresses() {
let addr = Address::from_str(
"tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx",
Network::BitcoinSignet,
)
.unwrap();
assert!(matches!(addr, Address::Bitcoin(_)));
assert_eq!(
addr.to_string(),
"tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx"
);
}
#[test]
fn test_liquid_mainnet_addresses() {
let addr = Address::from_str(
"ex1qq6krj23yx9s4xjeas453huxx8azrk942qrxsvh",
Network::Liquid,
)
.unwrap();
assert!(matches!(addr, Address::Elements(_)));
assert_eq!(
addr.to_string(),
"ex1qq6krj23yx9s4xjeas453huxx8azrk942qrxsvh"
);
}
#[test]
fn test_liquid_testnet_addresses() {
let addr = Address::from_str(
"tex1qv0s62sz6xnxf9d4qkvsnwqs5pz9k9q8dpp0q2h",
Network::LiquidTestnet,
)
.unwrap();
assert!(matches!(addr, Address::Elements(_)));
assert_eq!(
addr.to_string(),
"tex1qv0s62sz6xnxf9d4qkvsnwqs5pz9k9q8dpp0q2h"
);
}
#[test]
fn test_elements_regtest_addresses() {
let result = Address::from_str("invalid", Network::ElementsRegtest);
assert!(matches!(result, Err(Error::String(_))));
}
#[test]
fn test_wrong_network_errors() {
let result = Address::from_str(
"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2",
Network::BitcoinTestnet,
);
assert!(matches!(result, Err(Error::String(_))));
let result = Address::from_str("mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn", Network::Bitcoin);
assert!(matches!(result, Err(Error::String(_))));
let result = Address::from_str(
"ex1qq6krj23yx9s4xjeas453huxx8azrk942qrxsvh",
Network::LiquidTestnet,
);
assert_eq!(result, Err(Error::WrongNetwork));
let result = Address::from_str(
"tex1qv0s62sz6xnxf9d4qkvsnwqs5pz9k9q8dpp0q2h",
Network::Liquid,
);
assert_eq!(result, Err(Error::WrongNetwork));
}
#[test]
fn test_invalid_addresses() {
let result = Address::from_str("invalid_address", Network::Bitcoin);
assert!(matches!(result, Err(Error::String(_))));
let result = Address::from_str("invalid_address", Network::Liquid);
assert!(matches!(result, Err(Error::String(_))));
let result = Address::from_str("", Network::Bitcoin);
assert!(matches!(result, Err(Error::String(_))));
let result = Address::from_str("abc", Network::Bitcoin);
assert!(matches!(result, Err(Error::String(_))));
}
#[test]
fn test_blinded_address_error() {
let blinded_addr = "lq1qqgyxa469eaugae2sz3q8qzaqy0v57ecuekzyngfac5nw4z87yqskc5tp2wtueqq6am0x062zewkrl9lr0cqwvw0j9633xqe2e";
let result = Address::from_str(blinded_addr, Network::Liquid).unwrap();
assert_eq!(
result.ensure_not_blinded(),
Err(Error::AddressCannotBeBlinded)
);
}
#[test]
fn test_mixed_network_types() {
let result = Address::from_str("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", Network::Liquid);
assert!(matches!(result, Err(Error::String(_))));
let result = Address::from_str(
"ex1qq6krj23yx9s4xjeas453huxx8azrk942qrxsvh",
Network::Bitcoin,
);
assert!(matches!(result, Err(Error::String(_))));
}
#[test]
fn test_script_pubkey_generation() {
let addr =
Address::from_str("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", Network::Bitcoin).unwrap();
let script = addr.script_pubkey();
assert!(!script.as_bytes().is_empty());
let addr = Address::from_str(
"ex1qq6krj23yx9s4xjeas453huxx8azrk942qrxsvh",
Network::Liquid,
)
.unwrap();
let script = addr.script_pubkey();
assert!(!script.as_bytes().is_empty());
}
}