Skip to main content

iota_types/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::block::{
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    /// Gets the address as an actual [`Ed25519Address`].
60    /// PANIC: do not call on a non-ed25519 address.
61    pub fn as_ed25519(&self) -> &Ed25519Address {
62        if let Self::Ed25519(address) = self {
63            address
64        } else {
65            panic!("as_ed25519 called on a non-ed25519 address");
66        }
67    }
68
69    /// Checks whether the address is an [`AliasAddress`].
70    pub fn is_alias(&self) -> bool {
71        matches!(self, Self::Alias(_))
72    }
73
74    /// Gets the address as an actual [`AliasAddress`].
75    /// PANIC: do not call on a non-alias address.
76    pub fn as_alias(&self) -> &AliasAddress {
77        if let Self::Alias(address) = self {
78            address
79        } else {
80            panic!("as_alias called on a non-alias address");
81        }
82    }
83
84    /// Checks whether the address is an [`NftAddress`].
85    pub fn is_nft(&self) -> bool {
86        matches!(self, Self::Nft(_))
87    }
88
89    /// Gets the address as an actual [`NftAddress`].
90    /// PANIC: do not call on a non-nft address.
91    pub fn as_nft(&self) -> &NftAddress {
92        if let Self::Nft(address) = self {
93            address
94        } else {
95            panic!("as_nft called on a non-nft address");
96        }
97    }
98
99    /// Tries to create an [`Address`] from a bech32 encoded string.
100    pub fn try_from_bech32<T: AsRef<str>>(address: T) -> Result<(String, Self), Error> {
101        match bech32::decode(address.as_ref()) {
102            Ok((hrp, data, _)) => {
103                let bytes = Vec::<u8>::from_base32(&data).map_err(|_| Error::InvalidAddress)?;
104                Self::unpack_verified(bytes.as_slice(), &())
105                    .map_err(|_| Error::InvalidAddress)
106                    .map(|address| (hrp, address))
107            }
108            Err(_) => Err(Error::InvalidAddress),
109        }
110    }
111
112    /// Encodes this address to a bech32 string with the given Human Readable Part as prefix.
113    pub fn to_bech32<T: AsRef<str>>(&self, hrp: T) -> String {
114        // PANIC: encoding can't fail as `self` has already been validated and built.
115        bech32::encode(hrp.as_ref(), self.pack_to_vec().to_base32(), Variant::Bech32).unwrap()
116    }
117
118    ///
119    pub fn unlock(
120        &self,
121        unlock: &Unlock,
122        inputs: &[(OutputId, &Output)],
123        context: &mut ValidationContext<'_>,
124    ) -> Result<(), ConflictReason> {
125        match (self, unlock) {
126            (Self::Ed25519(ed25519_address), Unlock::Signature(unlock)) => {
127                if context.unlocked_addresses.contains(self) {
128                    return Err(ConflictReason::InvalidUnlock);
129                }
130
131                let Signature::Ed25519(signature) = unlock.signature();
132
133                if signature.is_valid(&context.essence_hash, ed25519_address).is_err() {
134                    return Err(ConflictReason::InvalidSignature);
135                }
136
137                context.unlocked_addresses.insert(*self);
138            }
139            (Self::Ed25519(_ed25519_address), Unlock::Reference(_unlock)) => {
140                // TODO actually check that it was unlocked by the same signature.
141                if !context.unlocked_addresses.contains(self) {
142                    return Err(ConflictReason::InvalidUnlock);
143                }
144            }
145            (Self::Alias(alias_address), Unlock::Alias(unlock)) => {
146                // PANIC: indexing is fine as it is already syntactically verified that indexes reference below.
147                if let (output_id, Output::Alias(alias_output)) = inputs[unlock.index() as usize] {
148                    if &alias_output.alias_id_non_null(&output_id) != alias_address.alias_id() {
149                        return Err(ConflictReason::InvalidUnlock);
150                    }
151                    if !context.unlocked_addresses.contains(self) {
152                        return Err(ConflictReason::InvalidUnlock);
153                    }
154                } else {
155                    return Err(ConflictReason::InvalidUnlock);
156                }
157            }
158            (Self::Nft(nft_address), Unlock::Nft(unlock)) => {
159                // PANIC: indexing is fine as it is already syntactically verified that indexes reference below.
160                if let (output_id, Output::Nft(nft_output)) = inputs[unlock.index() as usize] {
161                    if &nft_output.nft_id_non_null(&output_id) != nft_address.nft_id() {
162                        return Err(ConflictReason::InvalidUnlock);
163                    }
164                    if !context.unlocked_addresses.contains(self) {
165                        return Err(ConflictReason::InvalidUnlock);
166                    }
167                } else {
168                    return Err(ConflictReason::InvalidUnlock);
169                }
170            }
171            _ => return Err(ConflictReason::InvalidUnlock),
172        }
173
174        Ok(())
175    }
176}
177
178#[cfg(feature = "dto")]
179#[allow(missing_docs)]
180pub mod dto {
181    use serde::{Deserialize, Serialize, Serializer};
182    use serde_json::Value;
183
184    use super::*;
185    pub use super::{alias::dto::AliasAddressDto, ed25519::dto::Ed25519AddressDto, nft::dto::NftAddressDto};
186    use crate::block::error::dto::DtoError;
187
188    /// Describes all the different address types.
189    #[derive(Clone, Debug, Eq, PartialEq, From)]
190    pub enum AddressDto {
191        /// An Ed25519 address.
192        Ed25519(Ed25519AddressDto),
193        /// An alias address.
194        Alias(AliasAddressDto),
195        /// A NFT address.
196        Nft(NftAddressDto),
197    }
198
199    impl From<&Address> for AddressDto {
200        fn from(value: &Address) -> Self {
201            match value {
202                Address::Ed25519(a) => Self::Ed25519(a.into()),
203                Address::Alias(a) => Self::Alias(a.into()),
204                Address::Nft(a) => Self::Nft(a.into()),
205            }
206        }
207    }
208
209    impl TryFrom<&AddressDto> for Address {
210        type Error = DtoError;
211
212        fn try_from(value: &AddressDto) -> Result<Self, Self::Error> {
213            match value {
214                AddressDto::Ed25519(a) => Ok(Self::Ed25519(a.try_into()?)),
215                AddressDto::Alias(a) => Ok(Self::Alias(a.try_into()?)),
216                AddressDto::Nft(a) => Ok(Self::Nft(a.try_into()?)),
217            }
218        }
219    }
220
221    impl<'de> Deserialize<'de> for AddressDto {
222        fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
223            let value = Value::deserialize(d)?;
224            Ok(
225                match value
226                    .get("type")
227                    .and_then(Value::as_u64)
228                    .ok_or_else(|| serde::de::Error::custom("invalid address type"))? as u8
229                {
230                    Ed25519Address::KIND => {
231                        Self::Ed25519(Ed25519AddressDto::deserialize(value).map_err(|e| {
232                            serde::de::Error::custom(format!("cannot deserialize ed25519 address: {e}"))
233                        })?)
234                    }
235                    AliasAddress::KIND => Self::Alias(
236                        AliasAddressDto::deserialize(value)
237                            .map_err(|e| serde::de::Error::custom(format!("cannot deserialize alias address: {e}")))?,
238                    ),
239                    NftAddress::KIND => Self::Nft(
240                        NftAddressDto::deserialize(value)
241                            .map_err(|e| serde::de::Error::custom(format!("cannot deserialize NFT address: {e}")))?,
242                    ),
243                    _ => return Err(serde::de::Error::custom("invalid address type")),
244                },
245            )
246        }
247    }
248
249    impl Serialize for AddressDto {
250        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
251        where
252            S: Serializer,
253        {
254            #[derive(Serialize)]
255            #[serde(untagged)]
256            enum AddressDto_<'a> {
257                T1(&'a Ed25519AddressDto),
258                T2(&'a AliasAddressDto),
259                T3(&'a NftAddressDto),
260            }
261            #[derive(Serialize)]
262            struct TypedAddress<'a> {
263                #[serde(flatten)]
264                address: AddressDto_<'a>,
265            }
266            let address = match self {
267                Self::Ed25519(o) => TypedAddress {
268                    address: AddressDto_::T1(o),
269                },
270                Self::Alias(o) => TypedAddress {
271                    address: AddressDto_::T2(o),
272                },
273                Self::Nft(o) => TypedAddress {
274                    address: AddressDto_::T3(o),
275                },
276            };
277            address.serialize(serializer)
278        }
279    }
280}