bee_block/unlock/
mod.rs

1// Copyright 2020-2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4mod alias;
5mod nft;
6mod reference;
7mod signature;
8
9use alloc::vec::Vec;
10use core::ops::RangeInclusive;
11
12use derive_more::{Deref, From};
13use hashbrown::HashSet;
14use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable};
15
16pub use self::{alias::AliasUnlock, nft::NftUnlock, reference::ReferenceUnlock, signature::SignatureUnlock};
17use crate::{
18    input::{INPUT_COUNT_MAX, INPUT_COUNT_RANGE, INPUT_INDEX_MAX, INPUT_INDEX_RANGE},
19    Error,
20};
21
22/// The maximum number of unlocks of a transaction.
23pub const UNLOCK_COUNT_MAX: u16 = INPUT_COUNT_MAX; // 128
24/// The range of valid numbers of unlocks of a transaction.
25pub const UNLOCK_COUNT_RANGE: RangeInclusive<u16> = INPUT_COUNT_RANGE; // [1..128]
26/// The maximum index of unlocks of a transaction.
27pub const UNLOCK_INDEX_MAX: u16 = INPUT_INDEX_MAX; // 127
28/// The range of valid indices of unlocks of a transaction.
29pub const UNLOCK_INDEX_RANGE: RangeInclusive<u16> = INPUT_INDEX_RANGE; // [0..127]
30
31pub(crate) type UnlockIndex = BoundedU16<{ *UNLOCK_INDEX_RANGE.start() }, { *UNLOCK_INDEX_RANGE.end() }>;
32
33/// Defines the mechanism by which a transaction input is authorized to be consumed.
34#[derive(Clone, Debug, Eq, PartialEq, Hash, From, Packable)]
35#[cfg_attr(
36    feature = "serde",
37    derive(serde::Serialize, serde::Deserialize),
38    serde(tag = "type", content = "data")
39)]
40#[packable(unpack_error = Error)]
41#[packable(tag_type = u8, with_error = Error::InvalidUnlockKind)]
42pub enum Unlock {
43    /// A signature unlock.
44    #[packable(tag = SignatureUnlock::KIND)]
45    Signature(SignatureUnlock),
46    /// A reference unlock.
47    #[packable(tag = ReferenceUnlock::KIND)]
48    Reference(ReferenceUnlock),
49    /// An alias unlock.
50    #[packable(tag = AliasUnlock::KIND)]
51    Alias(AliasUnlock),
52    /// An NFT unlock.
53    #[packable(tag = NftUnlock::KIND)]
54    Nft(NftUnlock),
55}
56
57impl Unlock {
58    /// Returns the unlock kind of an [`Unlock`].
59    pub fn kind(&self) -> u8 {
60        match self {
61            Self::Signature(_) => SignatureUnlock::KIND,
62            Self::Reference(_) => ReferenceUnlock::KIND,
63            Self::Alias(_) => AliasUnlock::KIND,
64            Self::Nft(_) => NftUnlock::KIND,
65        }
66    }
67}
68
69pub(crate) type UnlockCount = BoundedU16<{ *UNLOCK_COUNT_RANGE.start() }, { *UNLOCK_COUNT_RANGE.end() }>;
70
71/// A collection of unlocks.
72#[derive(Clone, Debug, Eq, PartialEq, Deref, Packable)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
74#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidUnlockCount(p.into())))]
75pub struct Unlocks(#[packable(verify_with = verify_unlocks)] BoxedSlicePrefix<Unlock, UnlockCount>);
76
77impl Unlocks {
78    /// Creates a new [`Unlocks`].
79    pub fn new(unlocks: Vec<Unlock>) -> Result<Self, Error> {
80        let unlocks: BoxedSlicePrefix<Unlock, UnlockCount> = unlocks
81            .into_boxed_slice()
82            .try_into()
83            .map_err(Error::InvalidUnlockCount)?;
84
85        verify_unlocks::<true>(&unlocks, &())?;
86
87        Ok(Self(unlocks))
88    }
89
90    /// Gets an [`Unlock`] from an [`Unlocks`].
91    /// Returns the referenced unlock if the requested unlock was a reference.
92    pub fn get(&self, index: usize) -> Option<&Unlock> {
93        match self.0.get(index) {
94            Some(Unlock::Reference(reference)) => self.0.get(reference.index() as usize),
95            Some(unlock) => Some(unlock),
96            None => None,
97        }
98    }
99}
100
101fn verify_unlocks<const VERIFY: bool>(unlocks: &[Unlock], _: &()) -> Result<(), Error> {
102    if VERIFY {
103        let mut seen_signatures = HashSet::new();
104
105        for (index, unlock) in (0u16..).zip(unlocks.iter()) {
106            match unlock {
107                Unlock::Signature(signature) => {
108                    if !seen_signatures.insert(signature) {
109                        return Err(Error::DuplicateSignatureUnlock(index));
110                    }
111                }
112                Unlock::Reference(reference) => {
113                    if index == 0
114                        || reference.index() >= index
115                        || !matches!(unlocks[reference.index() as usize], Unlock::Signature(_))
116                    {
117                        return Err(Error::InvalidUnlockReference(index));
118                    }
119                }
120                Unlock::Alias(alias) => {
121                    if index == 0 || alias.index() >= index {
122                        return Err(Error::InvalidUnlockAlias(index));
123                    }
124                }
125                Unlock::Nft(nft) => {
126                    if index == 0 || nft.index() >= index {
127                        return Err(Error::InvalidUnlockNft(index));
128                    }
129                }
130            }
131        }
132    }
133
134    Ok(())
135}
136
137#[cfg(feature = "dto")]
138#[allow(missing_docs)]
139pub mod dto {
140    use serde::{Deserialize, Serialize, Serializer};
141    use serde_json::Value;
142
143    use super::*;
144    pub use super::{
145        alias::dto::AliasUnlockDto, nft::dto::NftUnlockDto, reference::dto::ReferenceUnlockDto,
146        signature::dto::SignatureUnlockDto,
147    };
148    use crate::{
149        error::dto::DtoError,
150        signature::{
151            dto::{Ed25519SignatureDto, SignatureDto},
152            Ed25519Signature, Signature,
153        },
154    };
155
156    /// Describes all the different unlock types.
157    #[derive(Clone, Debug, Eq, PartialEq, From)]
158    pub enum UnlockDto {
159        Signature(SignatureUnlockDto),
160        Reference(ReferenceUnlockDto),
161        Alias(AliasUnlockDto),
162        Nft(NftUnlockDto),
163    }
164
165    impl From<&Unlock> for UnlockDto {
166        fn from(value: &Unlock) -> Self {
167            match value {
168                Unlock::Signature(signature) => match signature.signature() {
169                    Signature::Ed25519(ed) => UnlockDto::Signature(SignatureUnlockDto {
170                        kind: SignatureUnlock::KIND,
171                        signature: SignatureDto::Ed25519(Ed25519SignatureDto {
172                            kind: Ed25519Signature::KIND,
173                            public_key: prefix_hex::encode(ed.public_key()),
174                            signature: prefix_hex::encode(ed.signature()),
175                        }),
176                    }),
177                },
178                Unlock::Reference(r) => UnlockDto::Reference(ReferenceUnlockDto {
179                    kind: ReferenceUnlock::KIND,
180                    index: r.index(),
181                }),
182                Unlock::Alias(a) => UnlockDto::Alias(AliasUnlockDto {
183                    kind: AliasUnlock::KIND,
184                    index: a.index(),
185                }),
186                Unlock::Nft(n) => UnlockDto::Nft(NftUnlockDto {
187                    kind: NftUnlock::KIND,
188                    index: n.index(),
189                }),
190            }
191        }
192    }
193
194    impl TryFrom<&UnlockDto> for Unlock {
195        type Error = DtoError;
196
197        fn try_from(value: &UnlockDto) -> Result<Self, Self::Error> {
198            match value {
199                UnlockDto::Signature(s) => match &s.signature {
200                    SignatureDto::Ed25519(ed) => {
201                        let public_key =
202                            prefix_hex::decode(&ed.public_key).map_err(|_| DtoError::InvalidField("publicKey"))?;
203                        let signature =
204                            prefix_hex::decode(&ed.signature).map_err(|_| DtoError::InvalidField("signature"))?;
205                        Ok(Unlock::Signature(SignatureUnlock::from(Signature::Ed25519(
206                            Ed25519Signature::new(public_key, signature),
207                        ))))
208                    }
209                },
210                UnlockDto::Reference(r) => Ok(Unlock::Reference(ReferenceUnlock::new(r.index)?)),
211                UnlockDto::Alias(a) => Ok(Unlock::Alias(AliasUnlock::new(a.index)?)),
212                UnlockDto::Nft(n) => Ok(Unlock::Nft(NftUnlock::new(n.index)?)),
213            }
214        }
215    }
216
217    impl<'de> Deserialize<'de> for UnlockDto {
218        fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
219            let value = Value::deserialize(d)?;
220            Ok(
221                match value
222                    .get("type")
223                    .and_then(Value::as_u64)
224                    .ok_or_else(|| serde::de::Error::custom("invalid unlock type"))? as u8
225                {
226                    SignatureUnlock::KIND => {
227                        UnlockDto::Signature(SignatureUnlockDto::deserialize(value).map_err(|e| {
228                            serde::de::Error::custom(format!("cannot deserialize signature unlock: {}", e))
229                        })?)
230                    }
231                    ReferenceUnlock::KIND => {
232                        UnlockDto::Reference(ReferenceUnlockDto::deserialize(value).map_err(|e| {
233                            serde::de::Error::custom(format!("cannot deserialize reference unlock: {}", e))
234                        })?)
235                    }
236                    AliasUnlock::KIND => UnlockDto::Alias(
237                        AliasUnlockDto::deserialize(value)
238                            .map_err(|e| serde::de::Error::custom(format!("cannot deserialize alias unlock: {}", e)))?,
239                    ),
240                    NftUnlock::KIND => UnlockDto::Nft(
241                        NftUnlockDto::deserialize(value)
242                            .map_err(|e| serde::de::Error::custom(format!("cannot deserialize NFT unlock: {}", e)))?,
243                    ),
244                    _ => return Err(serde::de::Error::custom("invalid unlock type")),
245                },
246            )
247        }
248    }
249
250    impl Serialize for UnlockDto {
251        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
252        where
253            S: Serializer,
254        {
255            #[derive(Serialize)]
256            #[serde(untagged)]
257            enum UnlockDto_<'a> {
258                T1(&'a SignatureUnlockDto),
259                T2(&'a ReferenceUnlockDto),
260                T3(&'a AliasUnlockDto),
261                T4(&'a NftUnlockDto),
262            }
263            #[derive(Serialize)]
264            struct TypedUnlock<'a> {
265                #[serde(flatten)]
266                unlock: UnlockDto_<'a>,
267            }
268            let unlock = match self {
269                UnlockDto::Signature(o) => TypedUnlock {
270                    unlock: UnlockDto_::T1(o),
271                },
272                UnlockDto::Reference(o) => TypedUnlock {
273                    unlock: UnlockDto_::T2(o),
274                },
275                UnlockDto::Alias(o) => TypedUnlock {
276                    unlock: UnlockDto_::T3(o),
277                },
278                UnlockDto::Nft(o) => TypedUnlock {
279                    unlock: UnlockDto_::T4(o),
280                },
281            };
282            unlock.serialize(serializer)
283        }
284    }
285}