Skip to main content

cardano_serialization_lib/
utils.rs

1use cbor_event::{
2    self,
3    de::Deserializer,
4    se::{Serialize, Serializer},
5};
6use hex::FromHex;
7use serde_json;
8use std::fmt::Display;
9use std::{
10    collections::HashMap,
11    io::{BufRead, Seek, Write},
12};
13
14use super::*;
15use crate::error::{DeserializeError, DeserializeFailure};
16use schemars::JsonSchema;
17
18pub fn to_bytes<T: cbor_event::se::Serialize>(data_item: &T) -> Vec<u8> {
19    let mut buf = Serializer::new_vec();
20    data_item.serialize(&mut buf).unwrap();
21    buf.finalize()
22}
23
24pub fn from_bytes<T: Deserialize>(data: &Vec<u8>) -> Result<T, DeserializeError> {
25    let mut raw = Deserializer::from(std::io::Cursor::new(data));
26    T::deserialize(&mut raw)
27}
28
29#[wasm_bindgen]
30#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, JsonSchema)]
31pub struct TransactionUnspentOutput {
32    pub(crate) input: TransactionInput,
33    pub(crate) output: TransactionOutput,
34}
35
36impl_to_from!(TransactionUnspentOutput);
37
38#[wasm_bindgen]
39impl TransactionUnspentOutput {
40    pub fn new(input: &TransactionInput, output: &TransactionOutput) -> TransactionUnspentOutput {
41        Self {
42            input: input.clone(),
43            output: output.clone(),
44        }
45    }
46
47    pub fn input(&self) -> TransactionInput {
48        self.input.clone()
49    }
50
51    pub fn output(&self) -> TransactionOutput {
52        self.output.clone()
53    }
54}
55
56impl cbor_event::se::Serialize for TransactionUnspentOutput {
57    fn serialize<'se, W: Write>(
58        &self,
59        serializer: &'se mut Serializer<W>,
60    ) -> cbor_event::Result<&'se mut Serializer<W>> {
61        serializer.write_array(cbor_event::Len::Len(2))?;
62        self.input.serialize(serializer)?;
63        self.output.serialize(serializer)
64    }
65}
66
67impl Deserialize for TransactionUnspentOutput {
68    fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
69        (|| -> Result<_, DeserializeError> {
70            match raw.cbor_type()? {
71                cbor_event::Type::Array => {
72                    let len = raw.array()?;
73                    let input = (|| -> Result<_, DeserializeError> {
74                        Ok(TransactionInput::deserialize(raw)?)
75                    })()
76                    .map_err(|e| e.annotate("input"))?;
77                    let output = (|| -> Result<_, DeserializeError> {
78                        Ok(TransactionOutput::deserialize(raw)?)
79                    })()
80                    .map_err(|e| e.annotate("output"))?;
81                    let ret = Ok(Self { input, output });
82                    match len {
83                        cbor_event::Len::Len(n) => match n {
84                            2 =>
85                            /* it's ok */
86                            {
87                                ()
88                            }
89                            n => {
90                                return Err(
91                                    DeserializeFailure::DefiniteLenMismatch(n, Some(2)).into()
92                                );
93                            }
94                        },
95                        cbor_event::Len::Indefinite => match raw.special()? {
96                            CBORSpecial::Break =>
97                            /* it's ok */
98                            {
99                                ()
100                            }
101                            _ => return Err(DeserializeFailure::EndingBreakMissing.into()),
102                        },
103                    }
104                    ret
105                }
106                _ => Err(DeserializeFailure::NoVariantMatched.into()),
107            }
108        })()
109        .map_err(|e| e.annotate("TransactionUnspentOutput"))
110    }
111}
112
113#[wasm_bindgen]
114#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, JsonSchema)]
115pub struct TransactionUnspentOutputs(pub(crate) Vec<TransactionUnspentOutput>);
116
117to_from_json!(TransactionUnspentOutputs);
118
119#[wasm_bindgen]
120impl TransactionUnspentOutputs {
121    pub fn new() -> Self {
122        Self(Vec::new())
123    }
124
125    pub fn len(&self) -> usize {
126        self.0.len()
127    }
128
129    pub fn get(&self, index: usize) -> TransactionUnspentOutput {
130        self.0[index].clone()
131    }
132
133    pub fn add(&mut self, elem: &TransactionUnspentOutput) {
134        self.0.push(elem.clone());
135    }
136}
137
138impl_vec_wrapper!(TransactionUnspentOutputs, TransactionUnspentOutput);
139
140#[wasm_bindgen]
141#[derive(
142    Clone,
143    Debug,
144    Default,
145    /*Hash,*/ Ord,
146    serde::Serialize,
147    serde::Deserialize,
148    JsonSchema,
149)]
150pub struct Value {
151    pub(crate) coin: Coin,
152    pub(crate) multiasset: Option<MultiAsset>,
153}
154
155impl_to_from!(Value);
156
157#[wasm_bindgen]
158impl Value {
159    pub fn new(coin: &Coin) -> Value {
160        Self::from(coin.clone())
161    }
162
163    pub fn new_from_assets(multiasset: &MultiAsset) -> Value {
164        Value::from(multiasset.clone())
165    }
166
167    pub fn new_with_assets(coin: &Coin, multiasset: &MultiAsset) -> Value {
168        Self::from(multiasset.clone()).with_coin(*coin)
169    }
170
171    pub fn zero() -> Value {
172        Self::default()
173    }
174
175    pub fn is_zero(&self) -> bool {
176        self.coin.is_zero() && self.multiasset.as_ref().is_none_or(MultiAsset::is_zero)
177    }
178
179    pub fn coin(&self) -> Coin {
180        self.coin
181    }
182
183    pub fn set_coin(&mut self, coin: &Coin) {
184        self.coin = coin.clone();
185    }
186
187    pub fn multiasset(&self) -> Option<MultiAsset> {
188        self.multiasset.clone()
189    }
190
191    pub fn set_multiasset(&mut self, multiasset: &MultiAsset) {
192        self.multiasset = Some(multiasset.clone());
193    }
194
195    pub fn checked_add(&self, rhs: &Value) -> Result<Value, JsError> {
196        <Self as num::CheckedAdd>::checked_add(self, rhs)
197            .ok_or_else(|| JsError::from_str("overflow"))
198    }
199
200    pub fn checked_sub(&self, rhs_value: &Value) -> Result<Value, JsError> {
201        <Self as num::CheckedSub>::checked_sub(self, rhs_value)
202            .ok_or_else(|| JsError::from_str("underflow"))
203    }
204
205    pub fn clamped_sub(&self, rhs_value: &Value) -> Value {
206        <Self as num_traits::SaturatingSub>::saturating_sub(&self, rhs_value)
207    }
208
209    /// note: values are only partially comparable
210    pub fn compare(&self, rhs_value: &Value) -> Option<i8> {
211        match self.partial_cmp(&rhs_value) {
212            None => None,
213            Some(std::cmp::Ordering::Equal) => Some(0),
214            Some(std::cmp::Ordering::Less) => Some(-1),
215            Some(std::cmp::Ordering::Greater) => Some(1),
216        }
217    }
218}
219
220impl Value {
221    pub fn lovelace(amount: impl Into<Coin>) -> Self {
222        Self::from(amount.into())
223    }
224
225    pub fn with_asset(
226        self,
227        policy: PolicyID,
228        name: AssetName,
229        amount: BigNum,
230    ) -> Self {
231        let multiasset = self.multiasset.unwrap_or_default().with_asset(policy, name, amount);
232        let multiasset = (!multiasset.is_zero()).then_some(multiasset);
233        Self { multiasset, ..self }
234    }
235
236    pub fn with_assets(self, policy: PolicyID, assets: Assets) -> Self {
237        let multiasset = self.multiasset.unwrap_or_default().with_assets(policy, assets);
238        let multiasset = (!multiasset.is_zero()).then_some(multiasset);
239        Self { multiasset, ..self }
240    }
241
242    pub fn with_multiasset(self, multiasset: MultiAsset) -> Self {
243        let multiasset = (!multiasset.is_zero()).then_some(multiasset);
244        Self { multiasset, ..self }
245    }
246
247    pub fn with_coin(self, coin: Coin) -> Self {
248        Self { coin, ..self }
249    }
250}
251
252impl From<MultiAsset> for Value {
253   fn from(ma: MultiAsset) -> Self {
254       Self { coin: Coin::zero(), multiasset: (ma.len() > 0).then_some(ma) }
255
256   }
257}
258
259impl From<Coin> for Value {
260    fn from(coin: Coin) -> Self {
261       Self { coin, multiasset: None }
262
263   }
264}
265
266impl PartialEq for Value {
267    fn eq(&self, other: &Self) -> bool {
268        let self_ma = self.multiasset.as_ref().map(|ma| ma.reduce_empty_to_none()).flatten();
269        let other_ma = other.multiasset.as_ref().map(|ma| ma.reduce_empty_to_none()).flatten();
270        self.coin == other.coin && self_ma == other_ma
271    }
272}
273
274impl Eq for Value {}
275
276impl PartialOrd for Value {
277    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
278        use std::cmp::Ordering::*;
279
280        fn compare_assets(
281            lhs: &Option<MultiAsset>,
282            rhs: &Option<MultiAsset>,
283        ) -> Option<std::cmp::Ordering> {
284            match (lhs, rhs) {
285                (None, None) => Some(Equal),
286                (None, Some(rhs_assets)) => MultiAsset::new().partial_cmp(&rhs_assets),
287                (Some(lhs_assets), None) => lhs_assets.partial_cmp(&MultiAsset::new()),
288                (Some(lhs_assets), Some(rhs_assets)) => lhs_assets.partial_cmp(&rhs_assets),
289            }
290        }
291
292        compare_assets(&self.multiasset(), &other.multiasset()).and_then(|assets_match| {
293            let coin_cmp = self.coin.cmp(&other.coin);
294
295            match (coin_cmp, assets_match) {
296                (coin_order, Equal) => Some(coin_order),
297                (Equal, Less) => Some(Less),
298                (Less, Less) => Some(Less),
299                (Equal, Greater) => Some(Greater),
300                (Greater, Greater) => Some(Greater),
301                (_, _) => None,
302            }
303        })
304    }
305}
306
307impl std::ops::Add for Value {
308    type Output = Value;
309
310    fn add(self, rhs: Self) -> Self::Output {
311        <Self as num_traits::CheckedAdd>::checked_add(&self, &rhs).expect("Value overflow")
312    }
313}
314
315impl std::ops::Add<Coin> for Value {
316    type Output = Value;
317
318    fn add(self, rhs: Coin) -> Self::Output {
319        let coin = num::CheckedAdd::checked_add(&self.coin, &rhs).expect("Value overflow");
320        Value { coin, ..self }
321    }
322}
323
324impl std::ops::Add<MultiAsset> for Value {
325    type Output = Value;
326
327    fn add(self, rhs: MultiAsset) -> Self::Output {
328        self + Value::from(rhs)
329    }
330}
331
332impl std::ops::Sub for Value {
333    type Output = Value;
334
335    fn sub(self, rhs: Self) -> Self::Output {
336        <Self as num_traits::CheckedSub>::checked_sub(&self, &rhs).expect("Value underflow")
337    }
338}
339
340impl std::ops::Sub<Coin> for Value {
341    type Output = Value;
342
343    fn sub(self, rhs: Coin) -> Self::Output {
344        let coin = num::CheckedSub::checked_sub(&self.coin, &rhs).expect("Value underflow");
345        Value { coin, ..self }
346    }
347}
348
349impl std::ops::Sub<MultiAsset> for Value {
350    type Output = Value;
351
352    fn sub(self, rhs: MultiAsset) -> Self::Output {
353        self - Value::from(rhs)
354    }
355}
356
357impl num_traits::CheckedAdd for Value {
358    fn checked_add(&self, other: &Self) -> Option<Self> {
359        let coin = num::CheckedAdd::checked_add(&self.coin, &other.coin)?;
360        let multiasset = match (&self.multiasset, &other.multiasset) {
361            (Some(first), Some(second)) => Some(num::CheckedAdd::checked_add(first, second)?),
362            (Some(first), None) => Some(first.clone()),
363            (None, Some(second)) => Some(second.clone()),
364            (None, None) => None,
365        }
366        .filter(|ma| !ma.is_zero());
367        Some(Self { coin, multiasset })
368    }
369}
370
371impl num_traits::CheckedSub for Value {
372    fn checked_sub(&self, other: &Self) -> Option<Self> {
373        let coin = num::CheckedSub::checked_sub(&self.coin, &other.coin)?;
374        let multiasset = match (&self.multiasset, &other.multiasset) {
375            (Some(first), Some(second)) => Some(num::CheckedSub::checked_sub(first, second)?),
376            (Some(first), None) => Some(first.clone()),
377            (None, Some(second)) if !second.is_zero() => return None,
378            (None, Some(_zero)) => None,
379            (None, None) => None,
380        }
381        .filter(|ma| !ma.is_zero());
382        Some(Self { coin, multiasset })
383    }
384}
385
386impl num_traits::SaturatingSub for Value {
387    fn saturating_sub(&self, v: &Self) -> Self {
388        let coin = self.coin.saturating_sub(&v.coin);
389        let multiasset = match (&self.multiasset, &v.multiasset) {
390            (Some(first), Some(second)) => Some(first.saturating_sub(second)),
391            (Some(first), None) => Some(first.clone()),
392            (None, _) => None,
393        }
394        .filter(|ma| !ma.is_zero());
395        Self { coin, multiasset }
396    }
397}
398
399impl num_traits::SaturatingAdd for Value {
400    fn saturating_add(&self, v: &Self) -> Self {
401        let coin = self.coin.saturating_add(&v.coin);
402        let multiasset = match (&self.multiasset, &v.multiasset) {
403            (Some(first), Some(second)) => Some(first.saturating_add(second)),
404            (Some(first), None) => Some(first.clone()),
405            (None, Some(second)) => Some(second.clone()),
406            (None, None) => None,
407        }
408        .filter(|ma| !ma.is_zero());
409        Self { coin, multiasset }
410    }
411}
412
413impl cbor_event::se::Serialize for Value {
414    fn serialize<'se, W: Write>(
415        &self,
416        serializer: &'se mut Serializer<W>,
417    ) -> cbor_event::Result<&'se mut Serializer<W>> {
418        let multiasset = self.multiasset
419            .as_ref()
420            .map(|ma| ma.reduce_empty_to_none())
421            .flatten();
422
423        if let Some(multiasset) = multiasset {
424            serializer.write_array(cbor_event::Len::Len(2))?;
425            self.coin.serialize(serializer)?;
426            multiasset.serialize(serializer)?;
427        } else {
428            self.coin.serialize(serializer)?;
429        }
430
431        Ok(serializer)
432    }
433}
434
435impl Deserialize for Value {
436    fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
437        (|| -> Result<_, DeserializeError> {
438            match raw.cbor_type()? {
439                cbor_event::Type::UnsignedInteger => Ok(Value::new(&Coin::deserialize(raw)?)),
440                cbor_event::Type::Array => {
441                    let len = raw.array()?;
442                    let coin =
443                        (|| -> Result<_, DeserializeError> { Ok(Coin::deserialize(raw)?) })()
444                            .map_err(|e| e.annotate("coin"))?;
445                    let multiasset =
446                        (|| -> Result<_, DeserializeError> { Ok(MultiAsset::deserialize(raw)?) })()
447                            .map_err(|e| e.annotate("multiasset"))?;
448                    let ret = Ok(Self {
449                        coin,
450                        multiasset: Some(multiasset),
451                    });
452                    match len {
453                        cbor_event::Len::Len(n) => match n {
454                            2 =>
455                            /* it's ok */
456                            {
457                                ()
458                            }
459                            n => {
460                                return Err(
461                                    DeserializeFailure::DefiniteLenMismatch(n, Some(2)).into()
462                                );
463                            }
464                        },
465                        cbor_event::Len::Indefinite => match raw.special()? {
466                            CBORSpecial::Break =>
467                            /* it's ok */
468                            {
469                                ()
470                            }
471                            _ => return Err(DeserializeFailure::EndingBreakMissing.into()),
472                        },
473                    }
474                    ret
475                }
476                _ => Err(DeserializeFailure::NoVariantMatched.into()),
477            }
478        })()
479        .map_err(|e| e.annotate("Value"))
480    }
481}
482
483pub(crate) const BOUNDED_BYTES_CHUNK_SIZE: usize = 64;
484
485pub(crate) fn write_bounded_bytes<'se, W: Write>(
486    serializer: &'se mut Serializer<W>,
487    bytes: &[u8],
488) -> cbor_event::Result<&'se mut Serializer<W>> {
489    if bytes.len() <= BOUNDED_BYTES_CHUNK_SIZE {
490        serializer.write_bytes(bytes)
491    } else {
492        // to get around not having access from outside the library we just write the raw CBOR indefinite byte string code here
493        serializer.write_raw_bytes(&[0x5f])?;
494        for chunk in bytes.chunks(BOUNDED_BYTES_CHUNK_SIZE) {
495            serializer.write_bytes(chunk)?;
496        }
497        serializer.write_special(CBORSpecial::Break)
498    }
499}
500
501pub(crate) fn read_bounded_bytes<R: BufRead + Seek>(
502    raw: &mut Deserializer<R>,
503) -> Result<Vec<u8>, DeserializeError> {
504    use std::io::Read;
505    let t = raw.cbor_type()?;
506    if t != CBORType::Bytes {
507        return Err(cbor_event::Error::Expected(CBORType::Bytes, t).into());
508    }
509    let (len, len_sz) = raw.cbor_len()?;
510    match len {
511        cbor_event::Len::Len(_) => {
512            let bytes = raw.bytes()?;
513            if bytes.len() > BOUNDED_BYTES_CHUNK_SIZE {
514                return Err(DeserializeFailure::OutOfRange {
515                    min: 0,
516                    max: BOUNDED_BYTES_CHUNK_SIZE,
517                    found: bytes.len(),
518                }
519                .into());
520            }
521            Ok(bytes)
522        }
523        cbor_event::Len::Indefinite => {
524            // this is CBOR indefinite encoding, but we must check that each chunk
525            // is at most 64 big so we can't just use cbor_event's implementation
526            // and check after the fact.
527            // This is a slightly adopted version of what I made internally in cbor_event
528            // but with the extra checks and not having access to non-pub methods.
529            let mut bytes = Vec::new();
530            raw.advance(1 + len_sz)?;
531            // TODO: also change this + check at end of loop to the following after we update cbor_event
532            //while raw.cbor_type()? != CBORType::Special || !raw.special_break()? {
533            while raw.cbor_type()? != CBORType::Special {
534                let chunk_t = raw.cbor_type()?;
535                if chunk_t != CBORType::Bytes {
536                    return Err(cbor_event::Error::Expected(CBORType::Bytes, chunk_t).into());
537                }
538                let (chunk_len, chunk_len_sz) = raw.cbor_len()?;
539                match chunk_len {
540                    // TODO: use this error instead once that PR is merged into cbor_event
541                    //cbor_event::Len::Indefinite => return Err(cbor_event::Error::InvalidIndefiniteString.into()),
542                    cbor_event::Len::Indefinite => {
543                        return Err(cbor_event::Error::CustomError(String::from(
544                            "Illegal CBOR: Indefinite string found inside indefinite string",
545                        ))
546                        .into());
547                    }
548                    cbor_event::Len::Len(len) => {
549                        if len as usize > BOUNDED_BYTES_CHUNK_SIZE {
550                            return Err(DeserializeFailure::OutOfRange {
551                                min: 0,
552                                max: BOUNDED_BYTES_CHUNK_SIZE,
553                                found: len as usize,
554                            }
555                            .into());
556                        }
557                        raw.advance(1 + chunk_len_sz)?;
558                        raw.as_mut_ref()
559                            .by_ref()
560                            .take(len)
561                            .read_to_end(&mut bytes)
562                            .map_err(|e| cbor_event::Error::IoError(e))?;
563                    }
564                }
565            }
566            if raw.special()? != CBORSpecial::Break {
567                return Err(DeserializeFailure::EndingBreakMissing.into());
568            }
569            Ok(bytes)
570        }
571    }
572}
573
574
575pub struct CBORReadLen {
576    deser_len: cbor_event::Len,
577    read: u64,
578}
579
580impl CBORReadLen {
581    pub fn new(len: cbor_event::Len) -> Self {
582        Self {
583            deser_len: len,
584            read: 0,
585        }
586    }
587
588    // Marks {n} values as being read, and if we go past the available definite length
589    // given by the CBOR, we return an error.
590    pub fn read_elems(&mut self, count: usize) -> Result<(), DeserializeFailure> {
591        match self.deser_len {
592            cbor_event::Len::Len(n) => {
593                self.read += count as u64;
594                if self.read > n {
595                    Err(DeserializeFailure::DefiniteLenMismatch(n, None))
596                } else {
597                    Ok(())
598                }
599            }
600            cbor_event::Len::Indefinite => Ok(()),
601        }
602    }
603
604    pub fn finish(&self) -> Result<(), DeserializeFailure> {
605        match self.deser_len {
606            cbor_event::Len::Len(n) => {
607                if self.read == n {
608                    Ok(())
609                } else {
610                    Err(DeserializeFailure::DefiniteLenMismatch(n, Some(self.read)))
611                }
612            }
613            cbor_event::Len::Indefinite => Ok(()),
614        }
615    }
616}
617
618#[wasm_bindgen]
619pub fn make_daedalus_bootstrap_witness(
620    tx_body_hash: &TransactionHash,
621    addr: &ByronAddress,
622    key: &LegacyDaedalusPrivateKey,
623) -> BootstrapWitness {
624    let chain_code = key.chaincode();
625
626    let pubkey = Bip32PublicKey::from_bytes(&key.0.to_public().as_ref()).unwrap();
627    let vkey = Vkey::new(&pubkey.to_raw_key());
628    let signature =
629        Ed25519Signature::from_bytes(key.0.sign(&tx_body_hash.to_bytes()).as_ref().to_vec())
630            .unwrap();
631
632    BootstrapWitness::new(&vkey, &signature, chain_code, addr.attributes())
633}
634
635#[wasm_bindgen]
636pub fn make_icarus_bootstrap_witness(
637    tx_body_hash: &TransactionHash,
638    addr: &ByronAddress,
639    key: &Bip32PrivateKey,
640) -> BootstrapWitness {
641    let chain_code = key.chaincode();
642
643    let raw_key = key.to_raw_key();
644    let vkey = Vkey::new(&raw_key.to_public());
645    let signature = raw_key.sign(&tx_body_hash.to_bytes());
646
647    BootstrapWitness::new(&vkey, &signature, chain_code, addr.attributes())
648}
649
650#[wasm_bindgen]
651pub fn make_vkey_witness(tx_body_hash: &TransactionHash, sk: &PrivateKey) -> Vkeywitness {
652    let sig = sk.sign(tx_body_hash.0.as_ref());
653    Vkeywitness::new(&Vkey::new(&sk.to_public()), &sig)
654}
655
656#[wasm_bindgen]
657pub fn hash_auxiliary_data(auxiliary_data: &AuxiliaryData) -> AuxiliaryDataHash {
658    AuxiliaryDataHash::from(blake2b256(&auxiliary_data.to_bytes()))
659}
660
661#[wasm_bindgen]
662pub fn hash_plutus_data(plutus_data: &PlutusData) -> DataHash {
663    DataHash::from(blake2b256(&plutus_data.to_bytes()))
664}
665
666#[wasm_bindgen]
667pub fn hash_script_data(
668    redeemers: &Redeemers,
669    cost_models: &Costmdls,
670    datums: Option<PlutusList>,
671) -> ScriptDataHash {
672    let mut buf = Vec::new();
673    if redeemers.len() == 0 && datums.is_some() {
674        /*
675        ; Finally, note that in the case that a transaction includes datums but does not
676        ; include any redeemers, the script data format becomes (in hex):
677        ; [ A0 | datums | A0 ]
678        ; corresponding to a CBOR empty map and an empty map (our apologies).
679        ; Before Conway first structure was an empty list, but it was changed to empty map since Conway.
680        */
681        buf.push(0xA0);
682        if let Some(d) = &datums {
683            buf.extend(d.to_set_bytes());
684        }
685        buf.push(0xA0);
686    } else {
687        /*
688        ; script data format:
689        ; [ redeemers | datums | language views ]
690        ; The redeemers are exactly the data present in the transaction witness set.
691        ; Similarly for the datums, if present. If no datums are provided, the middle
692        ; field is an empty string.
693        */
694        buf.extend(redeemers.to_bytes());
695        if let Some(d) = &datums {
696            buf.extend(d.to_set_bytes());
697        }
698        buf.extend(cost_models.language_views_encoding());
699    }
700    ScriptDataHash::from(blake2b256(&buf))
701}
702
703// wasm-bindgen can't accept Option without clearing memory, so we avoid exposing this in WASM
704pub fn internal_get_implicit_input(
705    withdrawals: &Option<Withdrawals>,
706    certs: &Option<Certificates>,
707    pool_deposit: &BigNum, // // protocol parameter
708    key_deposit: &BigNum,  // protocol parameter
709) -> Result<Value, JsError> {
710    let withdrawal_sum = match &withdrawals {
711        None => BigNum::zero(),
712        Some(x) => {
713            x.0.values()
714                .try_fold(BigNum::zero(), |acc, ref withdrawal_amt| {
715                    acc.checked_add(&withdrawal_amt)
716                })?
717        }
718    };
719    let certificate_refund = match &certs {
720        None => BigNum::zero(),
721        Some(certs) => certs
722            .certs
723            .iter()
724            .try_fold(BigNum::zero(), |acc, ref cert| match &cert.0 {
725                CertificateEnum::StakeDeregistration(cert) => {
726                    if let Some(coin) = cert.coin {
727                        acc.checked_add(&coin)
728                    } else {
729                        acc.checked_add(&key_deposit)
730                    }
731                }
732                CertificateEnum::PoolRetirement(_) => acc.checked_add(&pool_deposit),
733                CertificateEnum::DRepDeregistration(cert) => acc.checked_add(&cert.coin),
734                _ => Ok(acc),
735            })?,
736    };
737
738    Ok(Value::new(
739        &withdrawal_sum.checked_add(&certificate_refund)?,
740    ))
741}
742
743pub fn internal_get_deposit(
744    certs: &Option<Certificates>,
745    pool_deposit: &BigNum, // // protocol parameter
746    key_deposit: &BigNum,  // protocol parameter
747) -> Result<Coin, JsError> {
748    let certificate_deposit = match &certs {
749        None => BigNum::zero(),
750        Some(certs) => certs
751            .certs
752            .iter()
753            .try_fold(BigNum::zero(), |acc, ref cert| match &cert.0 {
754                CertificateEnum::PoolRegistration(_) => acc.checked_add(&pool_deposit),
755                CertificateEnum::StakeRegistration(cert) => {
756                    if let Some(coin) = cert.coin {
757                        acc.checked_add(&coin)
758                    } else {
759                        acc.checked_add(&key_deposit)
760                    }
761                }
762                CertificateEnum::DRepRegistration(cert) => acc.checked_add(&cert.coin),
763                CertificateEnum::StakeRegistrationAndDelegation(cert) => {
764                    acc.checked_add(&cert.coin)
765                }
766                CertificateEnum::VoteRegistrationAndDelegation(cert) => acc.checked_add(&cert.coin),
767                CertificateEnum::StakeVoteRegistrationAndDelegation(cert) => {
768                    acc.checked_add(&cert.coin)
769                }
770                _ => Ok(acc),
771            })?,
772    };
773    Ok(certificate_deposit)
774}
775
776#[wasm_bindgen]
777pub fn get_implicit_input(
778    txbody: &TransactionBody,
779    pool_deposit: &BigNum, // // protocol parameter
780    key_deposit: &BigNum,  // protocol parameter
781) -> Result<Value, JsError> {
782    internal_get_implicit_input(
783        &txbody.withdrawals,
784        &txbody.certs,
785        &pool_deposit,
786        &key_deposit,
787    )
788}
789
790#[wasm_bindgen]
791pub fn get_deposit(
792    txbody: &TransactionBody,
793    pool_deposit: &BigNum, // // protocol parameter
794    key_deposit: &BigNum,  // protocol parameter
795) -> Result<Coin, JsError> {
796    internal_get_deposit(&txbody.certs, &pool_deposit, &key_deposit)
797}
798
799#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
800pub struct MinOutputAdaCalculator {
801    output: TransactionOutput,
802    data_cost: DataCost,
803}
804
805impl MinOutputAdaCalculator {
806    pub fn new(output: &TransactionOutput, data_cost: &DataCost) -> Self {
807        Self {
808            output: output.clone(),
809            data_cost: data_cost.clone(),
810        }
811    }
812
813    pub fn new_empty(data_cost: &DataCost) -> Result<MinOutputAdaCalculator, JsError> {
814        Ok(Self {
815            output: MinOutputAdaCalculator::create_fake_output()?,
816            data_cost: data_cost.clone(),
817        })
818    }
819
820    pub fn set_address(&mut self, address: &Address) {
821        self.output.address = address.clone();
822    }
823
824    pub fn set_plutus_data(&mut self, data: &PlutusData) {
825        self.output.plutus_data = Some(DataOption::Data(data.clone()));
826    }
827
828    pub fn set_data_hash(&mut self, data_hash: &DataHash) {
829        self.output.plutus_data = Some(DataOption::DataHash(data_hash.clone()));
830    }
831
832    pub fn set_amount(&mut self, amount: &Value) {
833        self.output.amount = amount.clone();
834    }
835
836    pub fn set_script_ref(&mut self, script_ref: &ScriptRef) {
837        self.output.script_ref = Some(script_ref.clone());
838    }
839
840    pub fn calculate_ada(&self) -> Result<BigNum, JsError> {
841        let mut output: TransactionOutput = self.output.clone();
842        for _ in 0..3 {
843            let required_coin = Self::calc_required_coin(&output, &self.data_cost)?;
844            if output.amount.coin.less_than(&required_coin) {
845                output.amount.coin = required_coin.clone();
846            } else {
847                return Ok(required_coin);
848            }
849        }
850        output.amount.coin = BigNum(u64::MAX);
851        Ok(Self::calc_required_coin(&output, &self.data_cost)?)
852    }
853
854    fn create_fake_output() -> Result<TransactionOutput, JsError> {
855        let fake_base_address: Address = Address::from_bech32("addr_test1qpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5ewvxwdrt70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qum8x5w")?;
856        let fake_value: Value = Value::new(&BigNum(1000000));
857        Ok(TransactionOutput::new(&fake_base_address, &fake_value))
858    }
859
860    pub fn calc_size_cost(data_cost: &DataCost, size: usize) -> Result<Coin, JsError> {
861        //according to https://hydra.iohk.io/build/15339994/download/1/babbage-changes.pdf
862        //See on the page 9 getValue txout
863        BigNum(size as u64)
864            .checked_add(&BigNum(160))?
865            .checked_mul(&data_cost.coins_per_byte())
866    }
867
868    pub fn calc_required_coin(
869        output: &TransactionOutput,
870        data_cost: &DataCost,
871    ) -> Result<Coin, JsError> {
872        //according to https://hydra.iohk.io/build/15339994/download/1/babbage-changes.pdf
873        //See on the page 9 getValue txout
874        Self::calc_size_cost(data_cost, output.to_bytes().len())
875    }
876}
877
878///returns minimal amount of ada for the output for case when the amount is included to the output
879#[wasm_bindgen]
880pub fn min_ada_for_output(
881    output: &TransactionOutput,
882    data_cost: &DataCost,
883) -> Result<BigNum, JsError> {
884    MinOutputAdaCalculator::new(output, data_cost).calculate_ada()
885}
886
887/// Used to choosed the schema for a script JSON string
888#[wasm_bindgen]
889pub enum ScriptSchema {
890    Wallet,
891    Node,
892}
893
894/// Receives a script JSON string
895/// and returns a NativeScript.
896/// Cardano Wallet and Node styles are supported.
897///
898/// * wallet: https://github.com/input-output-hk/cardano-wallet/blob/master/specifications/api/swagger.yaml
899/// * node: https://github.com/input-output-hk/cardano-node/blob/master/doc/reference/simple-scripts.md
900///
901/// self_xpub is expected to be a Bip32PublicKey as hex-encoded bytes
902#[wasm_bindgen]
903pub fn encode_json_str_to_native_script(
904    json: &str,
905    self_xpub: &str,
906    schema: ScriptSchema,
907) -> Result<NativeScript, JsError> {
908    let value: serde_json::Value =
909        serde_json::from_str(&json).map_err(|e| JsError::from_str(&e.to_string()))?;
910
911    let native_script = match schema {
912        ScriptSchema::Wallet => encode_wallet_value_to_native_script(value, self_xpub)?,
913        ScriptSchema::Node => todo!(),
914    };
915
916    Ok(native_script)
917}
918
919fn encode_wallet_value_to_native_script(
920    value: serde_json::Value,
921    self_xpub: &str,
922) -> Result<NativeScript, JsError> {
923    match value {
924        serde_json::Value::Object(map)
925            if map.contains_key("cosigners") && map.contains_key("template") =>
926        {
927            let mut cosigners = HashMap::new();
928
929            if let serde_json::Value::Object(cosigner_map) = map.get("cosigners").unwrap() {
930                for (key, value) in cosigner_map.iter() {
931                    if let serde_json::Value::String(xpub) = value {
932                        if xpub == "self" {
933                            cosigners.insert(key.to_owned(), self_xpub.to_owned());
934                        } else {
935                            cosigners.insert(key.to_owned(), xpub.to_owned());
936                        }
937                    } else {
938                        return Err(JsError::from_str("cosigner value must be a string"));
939                    }
940                }
941            } else {
942                return Err(JsError::from_str("cosigners must be a map"));
943            }
944
945            let template = map.get("template").unwrap();
946
947            let template_native_script = encode_template_to_native_script(template, &cosigners)?;
948
949            Ok(template_native_script)
950        }
951        _ => Err(JsError::from_str(
952            "top level must be an object. cosigners and template keys are required",
953        )),
954    }
955}
956
957fn encode_template_to_native_script(
958    template: &serde_json::Value,
959    cosigners: &HashMap<String, String>,
960) -> Result<NativeScript, JsError> {
961    match template {
962        serde_json::Value::String(cosigner) => {
963            if let Some(xpub) = cosigners.get(cosigner) {
964                let bytes = Vec::from_hex(xpub).map_err(|e| JsError::from_str(&e.to_string()))?;
965
966                let public_key = Bip32PublicKey::from_bytes(&bytes)?;
967
968                Ok(NativeScript::new_script_pubkey(&ScriptPubkey::new(
969                    &public_key.to_raw_key().hash(),
970                )))
971            } else {
972                Err(JsError::from_str(&format!(
973                    "cosigner {} not found",
974                    cosigner
975                )))
976            }
977        }
978        serde_json::Value::Object(map) if map.contains_key("all") => {
979            let mut all = NativeScripts::new();
980
981            if let serde_json::Value::Array(array) = map.get("all").unwrap() {
982                for val in array {
983                    all.add(&encode_template_to_native_script(val, cosigners)?);
984                }
985            } else {
986                return Err(JsError::from_str("all must be an array"));
987            }
988
989            Ok(NativeScript::new_script_all(&ScriptAll::new(&all)))
990        }
991        serde_json::Value::Object(map) if map.contains_key("any") => {
992            let mut any = NativeScripts::new();
993
994            if let serde_json::Value::Array(array) = map.get("any").unwrap() {
995                for val in array {
996                    any.add(&encode_template_to_native_script(val, cosigners)?);
997                }
998            } else {
999                return Err(JsError::from_str("any must be an array"));
1000            }
1001
1002            Ok(NativeScript::new_script_any(&ScriptAny::new(&any)))
1003        }
1004        serde_json::Value::Object(map) if map.contains_key("some") => {
1005            if let serde_json::Value::Object(some) = map.get("some").unwrap() {
1006                if some.contains_key("at_least") && some.contains_key("from") {
1007                    let n = if let serde_json::Value::Number(at_least) =
1008                        some.get("at_least").unwrap()
1009                    {
1010                        if let Some(n) = at_least.as_u64() {
1011                            n as u32
1012                        } else {
1013                            return Err(JsError::from_str("at_least must be an integer"));
1014                        }
1015                    } else {
1016                        return Err(JsError::from_str("at_least must be an integer"));
1017                    };
1018
1019                    let mut from_scripts = NativeScripts::new();
1020
1021                    if let serde_json::Value::Array(array) = some.get("from").unwrap() {
1022                        for val in array {
1023                            from_scripts.add(&encode_template_to_native_script(val, cosigners)?);
1024                        }
1025                    } else {
1026                        return Err(JsError::from_str("from must be an array"));
1027                    }
1028
1029                    Ok(NativeScript::new_script_n_of_k(&ScriptNOfK::new(
1030                        n,
1031                        &from_scripts,
1032                    )))
1033                } else {
1034                    Err(JsError::from_str("some must contain at_least and from"))
1035                }
1036            } else {
1037                Err(JsError::from_str("some must be an object"))
1038            }
1039        }
1040        serde_json::Value::Object(map) if map.contains_key("active_from") => {
1041            if let serde_json::Value::Number(active_from) = map.get("active_from").unwrap() {
1042                if let Some(n) = active_from.as_u64() {
1043                    let slot: SlotBigNum = n.into();
1044
1045                    let time_lock_start = TimelockStart::new_timelockstart(&slot);
1046
1047                    Ok(NativeScript::new_timelock_start(&time_lock_start))
1048                } else {
1049                    Err(JsError::from_str(
1050                        "active_from slot must be an integer greater than or equal to 0",
1051                    ))
1052                }
1053            } else {
1054                Err(JsError::from_str("active_from slot must be a number"))
1055            }
1056        }
1057        serde_json::Value::Object(map) if map.contains_key("active_until") => {
1058            if let serde_json::Value::Number(active_until) = map.get("active_until").unwrap() {
1059                if let Some(n) = active_until.as_u64() {
1060                    let slot: SlotBigNum = n.into();
1061
1062                    let time_lock_expiry = TimelockExpiry::new_timelockexpiry(&slot);
1063
1064                    Ok(NativeScript::new_timelock_expiry(&time_lock_expiry))
1065                } else {
1066                    Err(JsError::from_str(
1067                        "active_until slot must be an integer greater than or equal to 0",
1068                    ))
1069                }
1070            } else {
1071                Err(JsError::from_str("active_until slot must be a number"))
1072            }
1073        }
1074        _ => Err(JsError::from_str("invalid template format")),
1075    }
1076}
1077
1078pub(crate) fn opt64<T>(o: &Option<T>) -> u64 {
1079    o.is_some() as u64
1080}
1081
1082pub(crate) fn opt64_non_empty<T: NoneOrEmpty>(o: &Option<T>) -> u64 {
1083    (!o.is_none_or_empty()) as u64
1084}
1085
1086pub struct ValueShortage {
1087    pub(crate) ada_shortage: Option<(Coin, Coin, Coin)>,
1088    pub(crate) asset_shortage: Vec<(PolicyID, AssetName, Coin, Coin)>,
1089}
1090
1091impl Display for ValueShortage {
1092    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1093        write!(f, "shortage: {{")?;
1094        if let Some((input_data, out_data, fee)) = self.ada_shortage {
1095            writeln!(
1096                f,
1097                "ada in inputs: {}, ada in outputs: {}, fee {}",
1098                input_data, out_data, fee
1099            )?;
1100            writeln!(f, "NOTE! \"ada in inputs\" must be >= (\"ada in outputs\" + fee) before adding change")?;
1101            writeln!(
1102                f,
1103                "and  \"ada in inputs\" must be == (\"ada in outputs\" + fee) after adding change"
1104            )?;
1105        }
1106        for (policy_id, asset_name, asset_shortage, asset_available) in &self.asset_shortage {
1107            write!(
1108                f,
1109                "policy id: \"{}\", asset name: \"{}\" ",
1110                policy_id, asset_name
1111            )?;
1112            writeln!(
1113                f,
1114                "coins in inputs: {}, coins in outputs: {}",
1115                asset_shortage, asset_available
1116            )?;
1117        }
1118        write!(f, " }}")
1119    }
1120}
1121
1122pub(crate) fn get_input_shortage(
1123    all_inputs_value: &Value,
1124    all_outputs_value: &Value,
1125    fee: &Coin,
1126) -> Result<Option<ValueShortage>, JsError> {
1127    let mut shortage = ValueShortage {
1128        ada_shortage: None,
1129        asset_shortage: Vec::new(),
1130    };
1131    if all_inputs_value.coin < all_outputs_value.coin.checked_add(fee)? {
1132        shortage.ada_shortage = Some((
1133            all_inputs_value.coin.clone(),
1134            all_outputs_value.coin.clone(),
1135            fee.clone(),
1136        ));
1137    }
1138
1139    if let Some(policies) = &all_outputs_value.multiasset {
1140        for (policy_id, assets) in &policies.0 {
1141            for (asset_name, coins) in &assets.0 {
1142                let inputs_coins = match &all_inputs_value.multiasset {
1143                    Some(multiasset) => multiasset.get_asset(policy_id, asset_name),
1144                    None => Coin::zero(),
1145                };
1146
1147                if inputs_coins < *coins {
1148                    shortage.asset_shortage.push((
1149                        policy_id.clone(),
1150                        asset_name.clone(),
1151                        inputs_coins,
1152                        coins.clone(),
1153                    ));
1154                }
1155            }
1156        }
1157    }
1158
1159    if shortage.ada_shortage.is_some() || shortage.asset_shortage.len() > 0 {
1160        Ok(Some(shortage))
1161    } else {
1162        Ok(None)
1163    }
1164}
1165
1166#[wasm_bindgen]
1167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1168pub enum TransactionSetsState {
1169    AllSetsHaveTag = 0,
1170    AllSetsHaveNoTag = 1,
1171    MixedSets = 2,
1172}
1173
1174/// Returns the state of the transaction sets.
1175/// If all sets have a tag, it returns AllSetsHaveTag.
1176/// If all sets have no tag, it returns AllSetsHaveNoTag.
1177/// If there is a mix of tagged and untagged sets, it returns MixedSets.
1178/// This function is useful for checking if a transaction might be signed by a hardware wallet.
1179/// And for checking which parameter should be used in a hardware wallet api.
1180/// WARNING this function will be deleted after all tags for set types will be mandatory. Approx after next hf
1181#[wasm_bindgen]
1182pub fn has_transaction_set_tag(tx_bytes: Vec<u8>) -> Result<TransactionSetsState, JsError> {
1183    let tx = Transaction::from_bytes(tx_bytes)?;
1184    has_transaction_set_tag_internal(&tx.body, Some(&tx.witness_set))
1185}
1186
1187pub(crate) fn has_transaction_set_tag_internal(body: &TransactionBody, witnesses_set: Option<&TransactionWitnessSet>) -> Result<TransactionSetsState, JsError> {
1188    let body_tag = has_transaction_body_set_tag(&body)?;
1189    let witness_tag = witnesses_set.map(has_transaction_witnesses_set_tag).flatten();
1190
1191    match (body_tag, witness_tag) {
1192        (TransactionSetsState::AllSetsHaveTag, Some(TransactionSetsState::AllSetsHaveTag)) => Ok(TransactionSetsState::AllSetsHaveTag),
1193        (TransactionSetsState::AllSetsHaveNoTag, Some(TransactionSetsState::AllSetsHaveNoTag)) => Ok(TransactionSetsState::AllSetsHaveNoTag),
1194        (TransactionSetsState::AllSetsHaveTag, None) => Ok(TransactionSetsState::AllSetsHaveTag),
1195        (TransactionSetsState::AllSetsHaveNoTag, None) => Ok(TransactionSetsState::AllSetsHaveNoTag),
1196        _ => Ok(TransactionSetsState::MixedSets),
1197    }
1198}
1199
1200pub(crate) fn has_transaction_body_set_tag(body: &TransactionBody) -> Result<TransactionSetsState, JsError> {
1201    let mut has_tag = false;
1202    let mut has_no_tag  = false;
1203
1204    match body.inputs.get_set_type() {
1205        CborSetType::Tagged => has_tag = true,
1206        CborSetType::Untagged => has_no_tag = true,
1207    }
1208    body.reference_inputs.as_ref().map(|ref_inputs| {
1209        match ref_inputs.get_set_type() {
1210            CborSetType::Tagged => has_tag = true,
1211            CborSetType::Untagged => has_no_tag = true,
1212        }
1213    });
1214    body.required_signers.as_ref().map(|required_signers| {
1215        match required_signers.get_set_type() {
1216            CborSetType::Tagged => has_tag = true,
1217            CborSetType::Untagged => has_no_tag = true,
1218        }
1219    });
1220    body.voting_proposals.as_ref().map(|voting_proposals| {
1221        match voting_proposals.get_set_type() {
1222            CborSetType::Tagged => has_tag = true,
1223            CborSetType::Untagged => has_no_tag = true,
1224        }
1225    });
1226    body.collateral.as_ref().map(|collateral_inputs| {
1227        match collateral_inputs.get_set_type() {
1228            CborSetType::Tagged => has_tag = true,
1229            CborSetType::Untagged => has_no_tag = true,
1230        }
1231    });
1232    body.certs.as_ref().map(|certs| {
1233        match certs.get_set_type() {
1234            CborSetType::Tagged => has_tag = true,
1235            CborSetType::Untagged => has_no_tag = true,
1236        }
1237    });
1238
1239    body.certs.as_ref().map(|certs| {
1240        for cert in certs {
1241            match &cert.0 {
1242                CertificateEnum::PoolRegistration(pool_reg) => {
1243                    match pool_reg.pool_params.pool_owners.get_set_type() {
1244                        CborSetType::Tagged => has_tag = true,
1245                        CborSetType::Untagged => has_no_tag = true,
1246                    }
1247                }
1248                _ => {}
1249            }
1250        }
1251    });
1252
1253    body.voting_proposals.as_ref().map(|voting_proposals| {
1254        for proposal in voting_proposals {
1255            match &proposal.governance_action.0 {
1256                GovernanceActionEnum::UpdateCommitteeAction(upd_action) => {
1257                    match upd_action.members_to_remove.get_set_type() {
1258                        CborSetType::Tagged => has_tag = true,
1259                        CborSetType::Untagged => has_no_tag = true,
1260                    }
1261                }
1262                _ => {}
1263            }
1264        }
1265    });
1266
1267    match (has_tag, has_no_tag) {
1268        (true, true) => Ok(TransactionSetsState::MixedSets),
1269        (true, false) => Ok(TransactionSetsState::AllSetsHaveTag),
1270        (false, true) => Ok(TransactionSetsState::AllSetsHaveNoTag),
1271        (false, false) => Err(JsError::from_str("Transaction has invalid state")),
1272    }
1273}
1274
1275pub(crate) fn has_transaction_witnesses_set_tag(witness_set: &TransactionWitnessSet) -> Option<TransactionSetsState> {
1276    let mut has_tag = false;
1277    let mut has_no_tag  = false;
1278
1279    witness_set.bootstraps.as_ref().map(|bs| {
1280        match bs.get_set_type() {
1281            CborSetType::Tagged => has_tag = true,
1282            CborSetType::Untagged => has_no_tag = true,
1283        }
1284    });
1285    witness_set.vkeys.as_ref().map(|vkeys| {
1286        match vkeys.get_set_type() {
1287            CborSetType::Tagged => has_tag = true,
1288            CborSetType::Untagged => has_no_tag = true,
1289        }
1290    });
1291    witness_set.plutus_data.as_ref().map(|plutus_data| {
1292        match plutus_data.get_set_type() {
1293            Some(CborSetType::Tagged) => has_tag = true,
1294            Some(CborSetType::Untagged) => has_no_tag = true,
1295            None => has_tag = true,
1296        }
1297    });
1298    witness_set.native_scripts.as_ref().map(|native_scripts| {
1299        match native_scripts.get_set_type() {
1300            Some(CborSetType::Tagged) => has_tag = true,
1301            Some(CborSetType::Untagged) => has_no_tag = true,
1302            None => has_tag = true,
1303        }
1304    });
1305    witness_set.plutus_scripts.as_ref().map(|plutus_scripts| {
1306        match plutus_scripts.get_set_type(&Language::new_plutus_v1()) {
1307            Some(CborSetType::Tagged) => has_tag = true,
1308            Some(CborSetType::Untagged) => has_no_tag = true,
1309            None => has_tag = true,
1310        }
1311        match plutus_scripts.get_set_type(&Language::new_plutus_v2()) {
1312            Some(CborSetType::Tagged) => has_tag = true,
1313            Some(CborSetType::Untagged) => has_no_tag = true,
1314            None => has_tag = true,
1315        }
1316        match plutus_scripts.get_set_type(&Language::new_plutus_v3()) {
1317            Some(CborSetType::Tagged) => has_tag = true,
1318            Some(CborSetType::Untagged) => has_no_tag = true,
1319            None => has_tag = true,
1320        }
1321    });
1322
1323    match (has_tag, has_no_tag) {
1324        (true, true) => Some(TransactionSetsState::MixedSets),
1325        (true, false) => Some(TransactionSetsState::AllSetsHaveTag),
1326        (false, true) => Some(TransactionSetsState::AllSetsHaveNoTag),
1327        (false, false) => None,
1328    }
1329}