1mod 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
22pub const UNLOCK_COUNT_MAX: u16 = INPUT_COUNT_MAX; pub const UNLOCK_COUNT_RANGE: RangeInclusive<u16> = INPUT_COUNT_RANGE; pub const UNLOCK_INDEX_MAX: u16 = INPUT_INDEX_MAX; pub const UNLOCK_INDEX_RANGE: RangeInclusive<u16> = INPUT_INDEX_RANGE; pub(crate) type UnlockIndex = BoundedU16<{ *UNLOCK_INDEX_RANGE.start() }, { *UNLOCK_INDEX_RANGE.end() }>;
32
33#[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 #[packable(tag = SignatureUnlock::KIND)]
45 Signature(SignatureUnlock),
46 #[packable(tag = ReferenceUnlock::KIND)]
48 Reference(ReferenceUnlock),
49 #[packable(tag = AliasUnlock::KIND)]
51 Alias(AliasUnlock),
52 #[packable(tag = NftUnlock::KIND)]
54 Nft(NftUnlock),
55}
56
57impl Unlock {
58 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#[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 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 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 #[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}