bee_block/address/
mod.rs

1// Copyright 2020-2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4mod alias;
5mod ed25519;
6mod nft;
7
8use alloc::{string::String, vec::Vec};
9
10use bech32::{self, FromBase32, ToBase32, Variant};
11use derive_more::From;
12use packable::PackableExt;
13
14pub use self::{alias::AliasAddress, ed25519::Ed25519Address, nft::NftAddress};
15use crate::{
16    output::{Output, OutputId},
17    semantic::{ConflictReason, ValidationContext},
18    signature::Signature,
19    unlock::Unlock,
20    Error,
21};
22
23/// A generic address supporting different address kinds.
24#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, packable::Packable)]
25#[cfg_attr(
26    feature = "serde",
27    derive(serde::Serialize, serde::Deserialize),
28    serde(tag = "type", content = "data")
29)]
30#[packable(tag_type = u8, with_error = Error::InvalidAddressKind)]
31#[packable(unpack_error = Error)]
32pub enum Address {
33    /// An Ed25519 address.
34    #[packable(tag = Ed25519Address::KIND)]
35    Ed25519(Ed25519Address),
36    /// An alias address.
37    #[packable(tag = AliasAddress::KIND)]
38    Alias(AliasAddress),
39    /// An NFT address.
40    #[packable(tag = NftAddress::KIND)]
41    Nft(NftAddress),
42}
43
44impl Address {
45    /// Returns the address kind of an [`Address`].
46    pub fn kind(&self) -> u8 {
47        match self {
48            Self::Ed25519(_) => Ed25519Address::KIND,
49            Self::Alias(_) => AliasAddress::KIND,
50            Self::Nft(_) => NftAddress::KIND,
51        }
52    }
53
54    /// Checks whether the address is an [`Ed25519Address`].
55    pub fn is_ed25519(&self) -> bool {
56        matches!(self, Self::Ed25519(_))
57    }
58
59    /// Checks whether the address is an [`AliasAddress`].
60    pub fn is_alias(&self) -> bool {
61        matches!(self, Self::Alias(_))
62    }
63
64    /// Checks whether the address is an [`NftAddress`].
65    pub fn is_nft(&self) -> bool {
66        matches!(self, Self::Nft(_))
67    }
68
69    /// Tries to create an [`Address`] from a bech32 encoded string.
70    pub fn try_from_bech32<T: AsRef<str>>(address: T) -> Result<(String, Self), Error> {
71        match bech32::decode(address.as_ref()) {
72            Ok((hrp, data, _)) => {
73                let bytes = Vec::<u8>::from_base32(&data).map_err(|_| Error::InvalidAddress)?;
74                Self::unpack_verified(bytes.as_slice(), &())
75                    .map_err(|_| Error::InvalidAddress)
76                    .map(|address| (hrp, address))
77            }
78            Err(_) => Err(Error::InvalidAddress),
79        }
80    }
81
82    /// Encodes this address to a bech32 string with the given Human Readable Part as prefix.
83    #[allow(clippy::wrong_self_convention)]
84    pub fn to_bech32<T: AsRef<str>>(&self, hrp: T) -> String {
85        // PANIC: encoding can't fail as `self` has already been validated and built.
86        bech32::encode(hrp.as_ref(), self.pack_to_vec().to_base32(), Variant::Bech32).unwrap()
87    }
88
89    ///
90    pub fn unlock(
91        &self,
92        unlock: &Unlock,
93        inputs: &[(OutputId, &Output)],
94        context: &mut ValidationContext,
95    ) -> Result<(), ConflictReason> {
96        match (self, unlock) {
97            (Address::Ed25519(ed25519_address), Unlock::Signature(unlock)) => {
98                if context.unlocked_addresses.contains(self) {
99                    return Err(ConflictReason::InvalidUnlock);
100                }
101
102                let Signature::Ed25519(signature) = unlock.signature();
103
104                if signature.is_valid(&context.essence_hash, ed25519_address).is_err() {
105                    return Err(ConflictReason::InvalidSignature);
106                }
107
108                context.unlocked_addresses.insert(*self);
109            }
110            (Address::Ed25519(_ed25519_address), Unlock::Reference(_unlock)) => {
111                // TODO actually check that it was unlocked by the same signature.
112                if !context.unlocked_addresses.contains(self) {
113                    return Err(ConflictReason::InvalidUnlock);
114                }
115            }
116            (Address::Alias(alias_address), Unlock::Alias(unlock)) => {
117                // PANIC: indexing is fine as it is already syntactically verified that indexes reference below.
118                if let (output_id, Output::Alias(alias_output)) = inputs[unlock.index() as usize] {
119                    if &alias_output.alias_id().or_from_output_id(output_id) != alias_address.alias_id() {
120                        return Err(ConflictReason::InvalidUnlock);
121                    }
122                    if !context.unlocked_addresses.contains(self) {
123                        return Err(ConflictReason::InvalidUnlock);
124                    }
125                } else {
126                    return Err(ConflictReason::InvalidUnlock);
127                }
128            }
129            (Address::Nft(nft_address), Unlock::Nft(unlock)) => {
130                // PANIC: indexing is fine as it is already syntactically verified that indexes reference below.
131                if let (output_id, Output::Nft(nft_output)) = inputs[unlock.index() as usize] {
132                    if &nft_output.nft_id().or_from_output_id(output_id) != nft_address.nft_id() {
133                        return Err(ConflictReason::InvalidUnlock);
134                    }
135                    if !context.unlocked_addresses.contains(self) {
136                        return Err(ConflictReason::InvalidUnlock);
137                    }
138                } else {
139                    return Err(ConflictReason::InvalidUnlock);
140                }
141            }
142            _ => return Err(ConflictReason::InvalidUnlock),
143        }
144
145        Ok(())
146    }
147}
148
149#[cfg(feature = "dto")]
150#[allow(missing_docs)]
151pub mod dto {
152    use serde::{Deserialize, Serialize, Serializer};
153    use serde_json::Value;
154
155    use super::*;
156    pub use super::{alias::dto::AliasAddressDto, ed25519::dto::Ed25519AddressDto, nft::dto::NftAddressDto};
157    use crate::error::dto::DtoError;
158
159    /// Describes all the different address types.
160    #[derive(Clone, Debug, Eq, PartialEq, From)]
161    pub enum AddressDto {
162        /// An Ed25519 address.
163        Ed25519(Ed25519AddressDto),
164        /// An alias address.
165        Alias(AliasAddressDto),
166        /// A NFT address.
167        Nft(NftAddressDto),
168    }
169
170    impl From<&Address> for AddressDto {
171        fn from(value: &Address) -> Self {
172            match value {
173                Address::Ed25519(a) => AddressDto::Ed25519(a.into()),
174                Address::Alias(a) => AddressDto::Alias(a.into()),
175                Address::Nft(a) => AddressDto::Nft(a.into()),
176            }
177        }
178    }
179
180    impl TryFrom<&AddressDto> for Address {
181        type Error = DtoError;
182
183        fn try_from(value: &AddressDto) -> Result<Self, Self::Error> {
184            match value {
185                AddressDto::Ed25519(a) => Ok(Address::Ed25519(a.try_into()?)),
186                AddressDto::Alias(a) => Ok(Address::Alias(a.try_into()?)),
187                AddressDto::Nft(a) => Ok(Address::Nft(a.try_into()?)),
188            }
189        }
190    }
191
192    impl<'de> Deserialize<'de> for AddressDto {
193        fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
194            let value = Value::deserialize(d)?;
195            Ok(
196                match value
197                    .get("type")
198                    .and_then(Value::as_u64)
199                    .ok_or_else(|| serde::de::Error::custom("invalid address type"))? as u8
200                {
201                    Ed25519Address::KIND => {
202                        AddressDto::Ed25519(Ed25519AddressDto::deserialize(value).map_err(|e| {
203                            serde::de::Error::custom(format!("cannot deserialize ed25519 address: {}", e))
204                        })?)
205                    }
206                    AliasAddress::KIND => {
207                        AddressDto::Alias(AliasAddressDto::deserialize(value).map_err(|e| {
208                            serde::de::Error::custom(format!("cannot deserialize alias address: {}", e))
209                        })?)
210                    }
211                    NftAddress::KIND => AddressDto::Nft(
212                        NftAddressDto::deserialize(value)
213                            .map_err(|e| serde::de::Error::custom(format!("cannot deserialize NFT address: {}", e)))?,
214                    ),
215                    _ => return Err(serde::de::Error::custom("invalid address type")),
216                },
217            )
218        }
219    }
220
221    impl Serialize for AddressDto {
222        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
223        where
224            S: Serializer,
225        {
226            #[derive(Serialize)]
227            #[serde(untagged)]
228            enum AddressDto_<'a> {
229                T1(&'a Ed25519AddressDto),
230                T2(&'a AliasAddressDto),
231                T3(&'a NftAddressDto),
232            }
233            #[derive(Serialize)]
234            struct TypedAddress<'a> {
235                #[serde(flatten)]
236                address: AddressDto_<'a>,
237            }
238            let address = match self {
239                AddressDto::Ed25519(o) => TypedAddress {
240                    address: AddressDto_::T1(o),
241                },
242                AddressDto::Alias(o) => TypedAddress {
243                    address: AddressDto_::T2(o),
244                },
245                AddressDto::Nft(o) => TypedAddress {
246                    address: AddressDto_::T3(o),
247                },
248            };
249            address.serialize(serializer)
250        }
251    }
252}