mod alias;
mod ed25519;
mod nft;
use alloc::{string::String, vec::Vec};
use bech32::{self, FromBase32, ToBase32, Variant};
use derive_more::From;
use packable::PackableExt;
pub use self::{alias::AliasAddress, ed25519::Ed25519Address, nft::NftAddress};
use crate::block::{
output::{Output, OutputId},
semantic::{ConflictReason, ValidationContext},
signature::Signature,
unlock::Unlock,
Error,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, packable::Packable)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "data")
)]
#[packable(tag_type = u8, with_error = Error::InvalidAddressKind)]
#[packable(unpack_error = Error)]
pub enum Address {
#[packable(tag = Ed25519Address::KIND)]
Ed25519(Ed25519Address),
#[packable(tag = AliasAddress::KIND)]
Alias(AliasAddress),
#[packable(tag = NftAddress::KIND)]
Nft(NftAddress),
}
impl Address {
pub fn kind(&self) -> u8 {
match self {
Self::Ed25519(_) => Ed25519Address::KIND,
Self::Alias(_) => AliasAddress::KIND,
Self::Nft(_) => NftAddress::KIND,
}
}
pub fn is_ed25519(&self) -> bool {
matches!(self, Self::Ed25519(_))
}
pub fn as_ed25519(&self) -> &Ed25519Address {
if let Address::Ed25519(address) = self {
address
} else {
panic!("as_ed25519 called on a non-ed25519 address");
}
}
pub fn is_alias(&self) -> bool {
matches!(self, Self::Alias(_))
}
pub fn as_alias(&self) -> &AliasAddress {
if let Address::Alias(address) = self {
address
} else {
panic!("as_alias called on a non-alias address");
}
}
pub fn is_nft(&self) -> bool {
matches!(self, Self::Nft(_))
}
pub fn as_nft(&self) -> &NftAddress {
if let Address::Nft(address) = self {
address
} else {
panic!("as_nft called on a non-nft address");
}
}
pub fn try_from_bech32<T: AsRef<str>>(address: T) -> Result<(String, Self), Error> {
match bech32::decode(address.as_ref()) {
Ok((hrp, data, _)) => {
let bytes = Vec::<u8>::from_base32(&data).map_err(|_| Error::InvalidAddress)?;
Self::unpack_verified(bytes.as_slice(), &())
.map_err(|_| Error::InvalidAddress)
.map(|address| (hrp, address))
}
Err(_) => Err(Error::InvalidAddress),
}
}
#[allow(clippy::wrong_self_convention)]
pub fn to_bech32<T: AsRef<str>>(&self, hrp: T) -> String {
bech32::encode(hrp.as_ref(), self.pack_to_vec().to_base32(), Variant::Bech32).unwrap()
}
pub fn unlock(
&self,
unlock: &Unlock,
inputs: &[(OutputId, &Output)],
context: &mut ValidationContext,
) -> Result<(), ConflictReason> {
match (self, unlock) {
(Address::Ed25519(ed25519_address), Unlock::Signature(unlock)) => {
if context.unlocked_addresses.contains(self) {
return Err(ConflictReason::InvalidUnlock);
}
let Signature::Ed25519(signature) = unlock.signature();
if signature.is_valid(&context.essence_hash, ed25519_address).is_err() {
return Err(ConflictReason::InvalidSignature);
}
context.unlocked_addresses.insert(*self);
}
(Address::Ed25519(_ed25519_address), Unlock::Reference(_unlock)) => {
if !context.unlocked_addresses.contains(self) {
return Err(ConflictReason::InvalidUnlock);
}
}
(Address::Alias(alias_address), Unlock::Alias(unlock)) => {
if let (output_id, Output::Alias(alias_output)) = inputs[unlock.index() as usize] {
if &alias_output.alias_id_non_null(&output_id) != alias_address.alias_id() {
return Err(ConflictReason::InvalidUnlock);
}
if !context.unlocked_addresses.contains(self) {
return Err(ConflictReason::InvalidUnlock);
}
} else {
return Err(ConflictReason::InvalidUnlock);
}
}
(Address::Nft(nft_address), Unlock::Nft(unlock)) => {
if let (output_id, Output::Nft(nft_output)) = inputs[unlock.index() as usize] {
if &nft_output.nft_id_non_null(&output_id) != nft_address.nft_id() {
return Err(ConflictReason::InvalidUnlock);
}
if !context.unlocked_addresses.contains(self) {
return Err(ConflictReason::InvalidUnlock);
}
} else {
return Err(ConflictReason::InvalidUnlock);
}
}
_ => return Err(ConflictReason::InvalidUnlock),
}
Ok(())
}
}
#[cfg(feature = "dto")]
#[allow(missing_docs)]
pub mod dto {
use serde::{Deserialize, Serialize, Serializer};
use serde_json::Value;
use super::*;
pub use super::{alias::dto::AliasAddressDto, ed25519::dto::Ed25519AddressDto, nft::dto::NftAddressDto};
use crate::block::error::dto::DtoError;
#[derive(Clone, Debug, Eq, PartialEq, From)]
pub enum AddressDto {
Ed25519(Ed25519AddressDto),
Alias(AliasAddressDto),
Nft(NftAddressDto),
}
impl From<&Address> for AddressDto {
fn from(value: &Address) -> Self {
match value {
Address::Ed25519(a) => AddressDto::Ed25519(a.into()),
Address::Alias(a) => AddressDto::Alias(a.into()),
Address::Nft(a) => AddressDto::Nft(a.into()),
}
}
}
impl TryFrom<&AddressDto> for Address {
type Error = DtoError;
fn try_from(value: &AddressDto) -> Result<Self, Self::Error> {
match value {
AddressDto::Ed25519(a) => Ok(Address::Ed25519(a.try_into()?)),
AddressDto::Alias(a) => Ok(Address::Alias(a.try_into()?)),
AddressDto::Nft(a) => Ok(Address::Nft(a.try_into()?)),
}
}
}
impl<'de> Deserialize<'de> for AddressDto {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let value = Value::deserialize(d)?;
Ok(
match value
.get("type")
.and_then(Value::as_u64)
.ok_or_else(|| serde::de::Error::custom("invalid address type"))? as u8
{
Ed25519Address::KIND => {
AddressDto::Ed25519(Ed25519AddressDto::deserialize(value).map_err(|e| {
serde::de::Error::custom(format!("cannot deserialize ed25519 address: {e}"))
})?)
}
AliasAddress::KIND => AddressDto::Alias(
AliasAddressDto::deserialize(value)
.map_err(|e| serde::de::Error::custom(format!("cannot deserialize alias address: {e}")))?,
),
NftAddress::KIND => AddressDto::Nft(
NftAddressDto::deserialize(value)
.map_err(|e| serde::de::Error::custom(format!("cannot deserialize NFT address: {e}")))?,
),
_ => return Err(serde::de::Error::custom("invalid address type")),
},
)
}
}
impl Serialize for AddressDto {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
#[serde(untagged)]
enum AddressDto_<'a> {
T1(&'a Ed25519AddressDto),
T2(&'a AliasAddressDto),
T3(&'a NftAddressDto),
}
#[derive(Serialize)]
struct TypedAddress<'a> {
#[serde(flatten)]
address: AddressDto_<'a>,
}
let address = match self {
AddressDto::Ed25519(o) => TypedAddress {
address: AddressDto_::T1(o),
},
AddressDto::Alias(o) => TypedAddress {
address: AddressDto_::T2(o),
},
AddressDto::Nft(o) => TypedAddress {
address: AddressDto_::T3(o),
},
};
address.serialize(serializer)
}
}
}