use std::{fmt, io};
use crate::{
parameters::NetworkKind,
serialization::{SerializationError, ZcashDeserialize, ZcashSerialize},
transparent::{opcodes::OpCode, Script},
};
#[cfg(test)]
use proptest::prelude::*;
#[derive(
Clone, Eq, PartialEq, Hash, serde_with::SerializeDisplay, serde_with::DeserializeFromStr,
)]
pub enum Address {
PayToScriptHash {
network_kind: NetworkKind,
script_hash: [u8; 20],
},
PayToPublicKeyHash {
network_kind: NetworkKind,
pub_key_hash: [u8; 20],
},
}
impl fmt::Debug for Address {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut debug_struct = f.debug_struct("TransparentAddress");
match self {
Address::PayToScriptHash {
network_kind,
script_hash,
} => debug_struct
.field("network_kind", network_kind)
.field("script_hash", &hex::encode(script_hash))
.finish(),
Address::PayToPublicKeyHash {
network_kind,
pub_key_hash,
} => debug_struct
.field("network_kind", network_kind)
.field("pub_key_hash", &hex::encode(pub_key_hash))
.finish(),
}
}
}
impl fmt::Display for Address {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut bytes = io::Cursor::new(Vec::new());
let _ = self.zcash_serialize(&mut bytes);
f.write_str(&bs58::encode(bytes.get_ref()).with_check().into_string())
}
}
impl std::str::FromStr for Address {
type Err = SerializationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let result = &bs58::decode(s).with_check(None).into_vec();
match result {
Ok(bytes) => Self::zcash_deserialize(&bytes[..]),
Err(_) => Err(SerializationError::Parse("t-addr decoding error")),
}
}
}
impl ZcashSerialize for Address {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
match self {
Address::PayToScriptHash {
network_kind,
script_hash,
} => {
writer.write_all(&network_kind.b58_script_address_prefix())?;
writer.write_all(script_hash)?
}
Address::PayToPublicKeyHash {
network_kind,
pub_key_hash,
} => {
writer.write_all(&network_kind.b58_pubkey_address_prefix())?;
writer.write_all(pub_key_hash)?
}
}
Ok(())
}
}
impl ZcashDeserialize for Address {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let mut version_bytes = [0; 2];
reader.read_exact(&mut version_bytes)?;
let mut hash_bytes = [0; 20];
reader.read_exact(&mut hash_bytes)?;
match version_bytes {
zcash_primitives::constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX => {
Ok(Address::PayToScriptHash {
network_kind: NetworkKind::Mainnet,
script_hash: hash_bytes,
})
}
zcash_primitives::constants::testnet::B58_SCRIPT_ADDRESS_PREFIX => {
Ok(Address::PayToScriptHash {
network_kind: NetworkKind::Testnet,
script_hash: hash_bytes,
})
}
zcash_primitives::constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX => {
Ok(Address::PayToPublicKeyHash {
network_kind: NetworkKind::Mainnet,
pub_key_hash: hash_bytes,
})
}
zcash_primitives::constants::testnet::B58_PUBKEY_ADDRESS_PREFIX => {
Ok(Address::PayToPublicKeyHash {
network_kind: NetworkKind::Testnet,
pub_key_hash: hash_bytes,
})
}
_ => Err(SerializationError::Parse("bad t-addr version/type")),
}
}
}
impl Address {
pub fn from_pub_key_hash(network_kind: NetworkKind, pub_key_hash: [u8; 20]) -> Self {
Self::PayToPublicKeyHash {
network_kind,
pub_key_hash,
}
}
pub fn from_script_hash(network_kind: NetworkKind, script_hash: [u8; 20]) -> Self {
Self::PayToScriptHash {
network_kind,
script_hash,
}
}
pub fn network_kind(&self) -> NetworkKind {
match self {
Address::PayToScriptHash { network_kind, .. } => *network_kind,
Address::PayToPublicKeyHash { network_kind, .. } => *network_kind,
}
}
pub fn is_script_hash(&self) -> bool {
matches!(self, Address::PayToScriptHash { .. })
}
pub fn hash_bytes(&self) -> [u8; 20] {
match *self {
Address::PayToScriptHash { script_hash, .. } => script_hash,
Address::PayToPublicKeyHash { pub_key_hash, .. } => pub_key_hash,
}
}
pub fn create_script_from_address(&self) -> Script {
let mut script_bytes = Vec::new();
match self {
Address::PayToScriptHash { .. } => {
script_bytes.push(OpCode::Hash160 as u8);
script_bytes.push(OpCode::Push20Bytes as u8);
script_bytes.extend(self.hash_bytes());
script_bytes.push(OpCode::Equal as u8);
}
Address::PayToPublicKeyHash { .. } => {
script_bytes.push(OpCode::Dup as u8);
script_bytes.push(OpCode::Hash160 as u8);
script_bytes.push(OpCode::Push20Bytes as u8);
script_bytes.extend(self.hash_bytes());
script_bytes.push(OpCode::EqualVerify as u8);
script_bytes.push(OpCode::CheckSig as u8);
}
};
Script::new(&script_bytes)
}
}
#[cfg(test)]
mod tests {
use ripemd::{Digest, Ripemd160};
use secp256k1::PublicKey;
use sha2::Sha256;
use super::*;
trait ToAddressWithNetwork {
fn to_address(&self, network: NetworkKind) -> Address;
}
impl ToAddressWithNetwork for Script {
fn to_address(&self, network_kind: NetworkKind) -> Address {
Address::PayToScriptHash {
network_kind,
script_hash: Address::hash_payload(self.as_raw_bytes()),
}
}
}
impl ToAddressWithNetwork for PublicKey {
fn to_address(&self, network_kind: NetworkKind) -> Address {
Address::PayToPublicKeyHash {
network_kind,
pub_key_hash: Address::hash_payload(&self.serialize()[..]),
}
}
}
impl Address {
#[allow(dead_code)]
fn hash_payload(bytes: &[u8]) -> [u8; 20] {
let sha_hash = Sha256::digest(bytes);
let ripe_hash = Ripemd160::digest(sha_hash);
let mut payload = [0u8; 20];
payload[..].copy_from_slice(&ripe_hash[..]);
payload
}
}
#[test]
fn pubkey_mainnet() {
let _init_guard = zebra_test::init();
let pub_key = PublicKey::from_slice(&[
3, 23, 183, 225, 206, 31, 159, 148, 195, 42, 67, 115, 146, 41, 248, 140, 11, 3, 51, 41,
111, 180, 110, 143, 114, 134, 88, 73, 198, 174, 52, 184, 78,
])
.expect("A PublicKey from slice");
let t_addr = pub_key.to_address(NetworkKind::Mainnet);
assert_eq!(format!("{t_addr}"), "t1bmMa1wJDFdbc2TiURQP5BbBz6jHjUBuHq");
}
#[test]
fn pubkey_testnet() {
let _init_guard = zebra_test::init();
let pub_key = PublicKey::from_slice(&[
3, 23, 183, 225, 206, 31, 159, 148, 195, 42, 67, 115, 146, 41, 248, 140, 11, 3, 51, 41,
111, 180, 110, 143, 114, 134, 88, 73, 198, 174, 52, 184, 78,
])
.expect("A PublicKey from slice");
let t_addr = pub_key.to_address(NetworkKind::Testnet);
assert_eq!(format!("{t_addr}"), "tmTc6trRhbv96kGfA99i7vrFwb5p7BVFwc3");
}
#[test]
fn empty_script_mainnet() {
let _init_guard = zebra_test::init();
let script = Script::new(&[0u8; 20]);
let t_addr = script.to_address(NetworkKind::Mainnet);
assert_eq!(format!("{t_addr}"), "t3Y5pHwfgHbS6pDjj1HLuMFxhFFip1fcJ6g");
}
#[test]
fn empty_script_testnet() {
let _init_guard = zebra_test::init();
let script = Script::new(&[0; 20]);
let t_addr = script.to_address(NetworkKind::Testnet);
assert_eq!(format!("{t_addr}"), "t2L51LcmpA43UMvKTw2Lwtt9LMjwyqU2V1P");
}
#[test]
fn from_string() {
let _init_guard = zebra_test::init();
let t_addr: Address = "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd".parse().unwrap();
assert_eq!(format!("{t_addr}"), "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd");
}
#[test]
fn debug() {
let _init_guard = zebra_test::init();
let t_addr: Address = "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd".parse().unwrap();
assert_eq!(
format!("{t_addr:?}"),
"TransparentAddress { network_kind: Mainnet, script_hash: \"7d46a730d31f97b1930d3368a967c309bd4d136a\" }"
);
}
}
#[cfg(test)]
proptest! {
#[test]
fn transparent_address_roundtrip(taddr in any::<Address>()) {
let _init_guard = zebra_test::init();
let mut data = Vec::new();
taddr.zcash_serialize(&mut data).expect("t-addr should serialize");
let taddr2 = Address::zcash_deserialize(&data[..]).expect("randomized t-addr should deserialize");
prop_assert_eq![taddr, taddr2];
}
}