parity-zcash/keys/src/address.rs

158 lines
4.2 KiB
Rust

//! `AddressHash` with network identifier and format type
//!
//! A Bitcoin address, or simply address, is an identifier of 26-35 alphanumeric characters, beginning with the number 1
//! or 3, that represents a possible destination for a bitcoin payment.
//!
//! https://en.bitcoin.it/wiki/Address
use base58::{FromBase58, ToBase58};
use network::Network;
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
use zebra_crypto::checksum;
use {AddressHash, DisplayLayout, Error};
/// There are two transparent address formats currently in use.
/// https://bitcoin.org/en/developer-reference#address-conversion
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Type {
/// Pay to PubKey Hash
/// Common P2PKH which begin with the number 1, eg: 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2.
/// https://bitcoin.org/en/glossary/p2pkh-address
P2PKH,
/// Pay to Script Hash
/// Newer P2SH type starting with the number 3, eg: 3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy.
/// https://bitcoin.org/en/glossary/p2sh-address
P2SH,
}
/// `AddressHash` with network identifier and format type
#[derive(Debug, PartialEq, Clone)]
pub struct Address {
/// The type of the address.
pub kind: Type,
/// The network of the address.
pub network: Network,
/// Public key hash.
pub hash: AddressHash,
}
pub struct AddressDisplayLayout([u8; 26]);
impl Deref for AddressDisplayLayout {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DisplayLayout for Address {
type Target = AddressDisplayLayout;
fn layout(&self) -> Self::Target {
let mut result = [0u8; 26];
result[..2].copy_from_slice(&match (self.network, self.kind) {
(Network::Mainnet, Type::P2PKH) => [0x1C, 0xB8],
(Network::Testnet, Type::P2PKH) => [0x1D, 0x25],
(Network::Mainnet, Type::P2SH) => [0x1C, 0xBD],
(Network::Testnet, Type::P2SH) => [0x1C, 0xBA],
});
result[2..22].copy_from_slice(&*self.hash);
let cs = checksum(&result[0..22]);
result[22..].copy_from_slice(&*cs);
AddressDisplayLayout(result)
}
fn from_layout(data: &[u8]) -> Result<Self, Error>
where
Self: Sized,
{
if data.len() != 26 {
return Err(Error::InvalidAddress);
}
let cs = checksum(&data[..22]);
if &data[22..] != &*cs {
return Err(Error::InvalidChecksum);
}
let (network, kind) = match (data[0], data[1]) {
(0x1C, 0xB8) => (Network::Mainnet, Type::P2PKH),
(0x1C, 0xBD) => (Network::Mainnet, Type::P2SH),
(0x1D, 0x25) => (Network::Testnet, Type::P2PKH),
(0x1C, 0xBA) => (Network::Testnet, Type::P2SH),
_ => return Err(Error::InvalidAddress),
};
let mut hash = AddressHash::default();
hash.copy_from_slice(&data[2..22]);
let address = Address {
kind: kind,
network: network,
hash: hash,
};
Ok(address)
}
}
impl fmt::Display for Address {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.layout().to_base58().fmt(f)
}
}
impl FromStr for Address {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error>
where
Self: Sized,
{
let hex = try!(s.from_base58().map_err(|_| Error::InvalidAddress));
Address::from_layout(&hex)
}
}
impl From<&'static str> for Address {
fn from(s: &'static str) -> Self {
s.parse().unwrap()
}
}
#[cfg(test)]
mod tests {
use super::{Address, Type};
use network::Network;
#[test]
fn test_address_to_string() {
let address = Address {
kind: Type::P2PKH,
network: Network::Mainnet,
hash: "ff197b14e502ab41f3bc8ccb48c4abac9eab35bc".into(),
};
assert_eq!(
"t1h8SqgtM3QM5e2M8EzhhT1yL2PXXtA6oqe".to_owned(),
address.to_string()
);
}
#[test]
fn test_address_from_str() {
let address = Address {
kind: Type::P2PKH,
network: Network::Mainnet,
hash: "ff197b14e502ab41f3bc8ccb48c4abac9eab35bc".into(),
};
assert_eq!(address, "t1h8SqgtM3QM5e2M8EzhhT1yL2PXXtA6oqe".into());
}
}