use minicbor::{
data::Tag, data::Type, decode::Error, encode::Write, Decode, Decoder, Encode, Encoder,
};
use crate::registry::CoinInfo;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Address<'a> {
pub info: Option<CoinInfo>,
pub kind: Option<AddressKind>,
pub data: &'a [u8],
}
impl<'a> Address<'a> {
pub const TAG: Tag = Tag::new(307);
}
#[cfg(feature = "bitcoin")]
fn data_from_payload(payload: &bitcoin::address::Payload) -> Result<&[u8], InterpretAddressError> {
use bitcoin::address::Payload;
match payload {
Payload::PubkeyHash(ref pkh) => Ok(pkh.as_ref()),
Payload::ScriptHash(ref sh) => Ok(sh.as_ref()),
Payload::WitnessProgram(ref wp) => Ok(wp.program().as_bytes()),
_ => Err(InterpretAddressError::UnsupportedPayload),
}
}
#[cfg(feature = "bitcoin")]
impl<'a> TryFrom<&'a bitcoin::Address<bitcoin::address::NetworkUnchecked>> for Address<'a> {
type Error = InterpretAddressError;
fn try_from(
address: &'a bitcoin::Address<bitcoin::address::NetworkUnchecked>,
) -> Result<Self, Self::Error> {
let kind = AddressKind::try_from(address.payload()).ok();
let data = data_from_payload(address.payload())?;
Ok(Self {
info: None,
kind,
data,
})
}
}
#[cfg(feature = "bitcoin")]
impl<'a> TryFrom<&'a bitcoin::Address<bitcoin::address::NetworkChecked>> for Address<'a> {
type Error = InterpretAddressError;
fn try_from(address: &'a bitcoin::Address) -> Result<Self, Self::Error> {
use crate::registry::CoinType;
use bitcoin::Network;
let network = match address.network() {
Network::Bitcoin => CoinInfo::NETWORK_MAINNET,
Network::Testnet => CoinInfo::NETWORK_BTC_TESTNET,
_ => return Err(InterpretAddressError::UnsupportedNetwork),
};
let info = CoinInfo::new(CoinType::BTC, network);
let kind = AddressKind::try_from(address.payload()).ok();
let data = data_from_payload(address.payload())?;
Ok(Self {
info: Some(info),
kind,
data,
})
}
}
#[cfg(feature = "bitcoin")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InterpretAddressError {
UnsupportedNetwork,
UnsupportedPayload,
}
impl<'b, C> Decode<'b, C> for Address<'b> {
fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result<Self, Error> {
let mut info = None;
let mut address_type = None;
let mut data = None;
let mut len = d.map()?;
loop {
match len {
Some(0) => break,
Some(n) => len = Some(n - 1),
None => {
if d.datatype()? == Type::Break {
break;
}
}
}
match d.u32()? {
1 => {
if CoinInfo::TAG != d.tag()? {
return Err(Error::message("crypto-coin-info tag is invalid"));
}
info = Some(CoinInfo::decode(d, ctx)?);
}
2 => address_type = Some(AddressKind::decode(d, ctx)?),
3 => data = Some(d.bytes()?),
_ => return Err(Error::message("unknown map entry")),
}
}
Ok(Self {
info,
kind: address_type,
data: data.ok_or_else(|| Error::message("data is missing"))?,
})
}
}
impl<'a, C> Encode<C> for Address<'a> {
fn encode<W: Write>(
&self,
e: &mut Encoder<W>,
ctx: &mut C,
) -> Result<(), minicbor::encode::Error<W::Error>> {
let include_info = match self.info {
Some(ref i) => !i.is_default(),
None => false,
};
let len = include_info as u64 + self.kind.is_some() as u64 + 1;
e.map(len)?;
if include_info {
let info = self.info.as_ref().unwrap();
e.u8(1)?.tag(CoinInfo::TAG)?;
info.encode(e, ctx)?;
}
if let Some(ref address_type) = self.kind {
e.u8(2)?;
address_type.encode(e, ctx)?;
}
e.u8(3)?.bytes(self.data)?;
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AddressKind {
P2PKH,
P2SH,
P2WPKH,
}
impl TryFrom<u8> for AddressKind {
type Error = InvalidAddressType;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
0 => AddressKind::P2PKH,
1 => AddressKind::P2SH,
2 => AddressKind::P2WPKH,
_ => {
return Err(InvalidAddressType {
invalid_type: value,
})
}
})
}
}
#[derive(Debug)]
pub struct InvalidAddressType {
pub invalid_type: u8,
}
impl From<AddressKind> for u8 {
fn from(value: AddressKind) -> Self {
match value {
AddressKind::P2PKH => 0,
AddressKind::P2SH => 1,
AddressKind::P2WPKH => 2,
}
}
}
#[cfg(feature = "bitcoin")]
impl TryFrom<&bitcoin::address::Payload> for AddressKind {
type Error = UnknownAddressType;
fn try_from(value: &bitcoin::address::Payload) -> Result<Self, Self::Error> {
use bitcoin::{address::Payload, blockdata::script::witness_version::WitnessVersion};
let kind = match value {
Payload::PubkeyHash(_) => AddressKind::P2PKH,
Payload::ScriptHash(_) => AddressKind::P2SH,
Payload::WitnessProgram(wp) => match wp.version() {
WitnessVersion::V0 if wp.program().as_bytes().len() == 20 => AddressKind::P2WPKH,
_ => return Err(UnknownAddressType),
},
_ => return Err(UnknownAddressType),
};
Ok(kind)
}
}
#[cfg(feature = "bitcoin")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnknownAddressType;
impl<'b, C> Decode<'b, C> for AddressKind {
fn decode(d: &mut Decoder<'b>, _ctx: &mut C) -> Result<Self, Error> {
AddressKind::try_from(d.u8()?).map_err(|_| Error::message("invalid address type"))
}
}
impl<C> Encode<C> for AddressKind {
fn encode<W: Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut C,
) -> Result<(), minicbor::encode::Error<W::Error>> {
e.u8((*self).into())?;
Ok(())
}
}