use std::fmt;
use std::str::FromStr;
use crate::primitives::ec::PublicKey;
use crate::primitives::encoding::{from_base58_check, to_base58_check};
use crate::primitives::hash::hash160;
use crate::{Error, Result};
const MAINNET_PREFIX: u8 = 0x00;
const TESTNET_PREFIX: u8 = 0x6f;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Address {
pub_key_hash: [u8; 20],
prefix: u8,
}
impl Address {
pub fn new_from_string(address: &str) -> Result<Self> {
let (version, payload) = from_base58_check(address)?;
if version.len() != 1 {
return Err(Error::InvalidAddress(format!(
"invalid address version length for '{}'",
address
)));
}
let prefix = version[0];
match prefix {
MAINNET_PREFIX | TESTNET_PREFIX => {}
_ => {
return Err(Error::UnsupportedAddress(address.to_string()));
}
}
if payload.len() != 20 {
return Err(Error::InvalidAddressLength(address.to_string()));
}
let mut pub_key_hash = [0u8; 20];
pub_key_hash.copy_from_slice(&payload);
Ok(Self {
pub_key_hash,
prefix,
})
}
pub fn new_from_public_key_hash(hash: &[u8], mainnet: bool) -> Result<Self> {
if hash.len() != 20 {
return Err(Error::InvalidDataLength {
expected: 20,
actual: hash.len(),
});
}
let mut pub_key_hash = [0u8; 20];
pub_key_hash.copy_from_slice(hash);
let prefix = if mainnet {
MAINNET_PREFIX
} else {
TESTNET_PREFIX
};
Ok(Self {
pub_key_hash,
prefix,
})
}
pub fn new_from_public_key(public_key: &PublicKey, mainnet: bool) -> Result<Self> {
let h = hash160(&public_key.to_compressed());
Self::new_from_public_key_hash(&h, mainnet)
}
pub fn public_key_hash(&self) -> &[u8] {
&self.pub_key_hash
}
pub fn prefix(&self) -> u8 {
self.prefix
}
pub fn is_mainnet(&self) -> bool {
self.prefix == MAINNET_PREFIX
}
pub fn is_valid_address(address: &str) -> bool {
Self::new_from_string(address).is_ok()
}
}
impl fmt::Display for Address {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", to_base58_check(&self.pub_key_hash, &[self.prefix]))
}
}
impl FromStr for Address {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::new_from_string(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::ec::PrivateKey;
use crate::primitives::encoding::{from_hex, to_hex};
const TEST_PUBLIC_KEY_HEX: &str =
"026cf33373a9f3f6c676b75b543180703df225f7f8edbffedc417718a8ad4e89ce";
const TEST_PUBLIC_KEY_HASH: &str = "00ac6144c4db7b5790f343cf0477a65fb8a02eb7";
#[test]
fn test_new_from_string_mainnet() {
let address_str = "1E7ucTTWRTahCyViPhxSMor2pj4VGQdFMr";
let addr = Address::new_from_string(address_str).unwrap();
assert_eq!(
to_hex(addr.public_key_hash()),
"8fe80c75c9560e8b56ed64ea3c26e18d2c52211b"
);
assert_eq!(addr.to_string(), address_str);
assert!(addr.is_mainnet());
}
#[test]
fn test_new_from_string_testnet() {
let address_str = "mtdruWYVEV1wz5yL7GvpBj4MgifCB7yhPd";
let addr = Address::new_from_string(address_str).unwrap();
assert_eq!(
to_hex(addr.public_key_hash()),
"8fe80c75c9560e8b56ed64ea3c26e18d2c52211b"
);
assert_eq!(addr.to_string(), address_str);
assert!(!addr.is_mainnet());
}
#[test]
fn test_new_from_string_short_address() {
let result = Address::new_from_string("ADD8E55");
assert!(result.is_err());
}
#[test]
fn test_new_from_string_unsupported_address() {
let result = Address::new_from_string("27BvY7rFguYQvEL872Y7Fo77Y3EBApC2EK");
assert!(result.is_err());
}
#[test]
fn test_new_from_public_key_hash_mainnet() {
let hash = from_hex(TEST_PUBLIC_KEY_HASH).unwrap();
let addr = Address::new_from_public_key_hash(&hash, true).unwrap();
assert_eq!(to_hex(addr.public_key_hash()), TEST_PUBLIC_KEY_HASH);
assert_eq!(addr.to_string(), "114ZWApV4EEU8frr7zygqQcB1V2BodGZuS");
}
#[test]
fn test_new_from_public_key_hash_testnet() {
let hash = from_hex(TEST_PUBLIC_KEY_HASH).unwrap();
let addr = Address::new_from_public_key_hash(&hash, false).unwrap();
assert_eq!(to_hex(addr.public_key_hash()), TEST_PUBLIC_KEY_HASH);
assert_eq!(addr.to_string(), "mfaWoDuTsFfiunLTqZx4fKpVsUctiDV9jk");
}
#[test]
fn test_new_from_public_key_hash_invalid_length() {
let result = Address::new_from_public_key_hash(&[0u8; 19], true);
assert!(result.is_err());
let result = Address::new_from_public_key_hash(&[0u8; 21], true);
assert!(result.is_err());
}
#[test]
fn test_new_from_public_key_mainnet() {
let pubkey = PublicKey::from_hex(TEST_PUBLIC_KEY_HEX).unwrap();
let addr = Address::new_from_public_key(&pubkey, true).unwrap();
assert_eq!(to_hex(addr.public_key_hash()), TEST_PUBLIC_KEY_HASH);
assert_eq!(addr.to_string(), "114ZWApV4EEU8frr7zygqQcB1V2BodGZuS");
}
#[test]
fn test_new_from_public_key_testnet() {
let pubkey = PublicKey::from_hex(TEST_PUBLIC_KEY_HEX).unwrap();
let addr = Address::new_from_public_key(&pubkey, false).unwrap();
assert_eq!(to_hex(addr.public_key_hash()), TEST_PUBLIC_KEY_HASH);
assert_eq!(addr.to_string(), "mfaWoDuTsFfiunLTqZx4fKpVsUctiDV9jk");
}
#[test]
fn test_roundtrip_mainnet() {
let address_str = "114ZWApV4EEU8frr7zygqQcB1V2BodGZuS";
let addr = Address::new_from_string(address_str).unwrap();
assert_eq!(addr.to_string(), address_str);
}
#[test]
fn test_roundtrip_testnet() {
let address_str = "mfaWoDuTsFfiunLTqZx4fKpVsUctiDV9jk";
let addr = Address::new_from_string(address_str).unwrap();
assert_eq!(addr.to_string(), address_str);
}
#[test]
fn test_from_str_trait() {
let addr: Address = "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH".parse().unwrap();
assert!(addr.is_mainnet());
}
#[test]
fn test_display_trait() {
let addr = Address::new_from_string("1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH").unwrap();
let displayed = format!("{}", addr);
assert_eq!(displayed, "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
}
#[test]
fn test_is_valid_address() {
assert!(Address::is_valid_address(
"1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"
));
assert!(Address::is_valid_address(
"114ZWApV4EEU8frr7zygqQcB1V2BodGZuS"
));
assert!(Address::is_valid_address(
"mfaWoDuTsFfiunLTqZx4fKpVsUctiDV9jk"
));
assert!(!Address::is_valid_address("invalid"));
assert!(!Address::is_valid_address(""));
}
#[test]
fn test_invalid_checksum() {
let result = Address::new_from_string("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN3");
assert!(result.is_err());
}
#[test]
fn test_generator_point_address() {
let pubkey = PublicKey::from_hex(
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
)
.unwrap();
let addr = Address::new_from_public_key(&pubkey, true).unwrap();
assert_eq!(addr.to_string(), "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
}
#[test]
fn test_private_key_address_consistency() {
let private_key = PrivateKey::from_hex(
"0000000000000000000000000000000000000000000000000000000000000001",
)
.unwrap();
let pubkey = private_key.public_key();
let addr_from_address = Address::new_from_public_key(&pubkey, true).unwrap();
let addr_from_pubkey = pubkey.to_address();
assert_eq!(addr_from_address.to_string(), addr_from_pubkey);
}
#[test]
fn test_locking_script_to_address() {
use crate::script::template::ScriptTemplate;
use crate::script::templates::P2PKH;
let private_key = PrivateKey::from_hex(
"0000000000000000000000000000000000000000000000000000000000000001",
)
.unwrap();
let pubkey = private_key.public_key();
let pubkey_hash = pubkey.hash160();
let locking = P2PKH::new().lock(&pubkey_hash).unwrap();
let addr = locking.to_address();
assert!(addr.is_some());
let addr = addr.unwrap();
assert_eq!(addr.to_string(), pubkey.to_address());
}
#[test]
fn test_locking_script_to_address_non_p2pkh() {
use crate::script::LockingScript;
let op_return = LockingScript::from_asm("OP_RETURN").unwrap();
assert!(op_return.to_address().is_none());
let p2sh =
LockingScript::from_hex("a914000000000000000000000000000000000000000087").unwrap();
assert!(p2sh.to_address().is_none());
let empty = LockingScript::new();
assert!(empty.to_address().is_none());
}
}