Skip to main content

charms_data/
lib.rs

1use anyhow::{Result, anyhow, ensure};
2use ark_std::{
3    cmp::Ordering,
4    collections::BTreeMap,
5    format,
6    str::FromStr,
7    string::{String, ToString},
8    vec::Vec,
9};
10use ciborium::Value;
11use core::{convert::TryInto, fmt};
12use serde::{
13    Deserialize, Deserializer, Serialize, Serializer, de,
14    de::{DeserializeOwned, SeqAccess, Visitor},
15    ser::SerializeTuple,
16};
17use serde_with::{Bytes, IfIsHumanReadable, hex::Hex, serde_as};
18pub mod util;
19
20/// Macro to check a condition and return false (early) if it does not hold.
21/// This is useful for checking pre-requisite conditions in predicate-type functions.
22/// Inspired by the `ensure!` macro from the `anyhow` crate.
23/// The function must return a boolean.
24/// Example:
25/// ```rust
26/// use charms_data::check;
27///
28/// fn b_is_multiple_of_a(a: u32, b: u32) -> bool {
29///     check!(a <= b && a != 0);    // returns false early if `a` is greater than `b` or `a` is zero
30///     match b % a {
31///         0 => true,
32///         _ => false,
33///     }
34/// }
35#[macro_export]
36macro_rules! check {
37    ($condition:expr) => {
38        if !$condition {
39            eprintln!("condition does not hold: {}", stringify!($condition));
40            return false;
41        }
42    };
43}
44
45/// Represents a transaction involving Charms.
46/// A Charms transaction sits on top of a Bitcoin transaction. Therefore, it transforms a set of
47/// input UTXOs into a set of output UTXOs.
48/// A Charms transaction may also reference other valid UTXOs that are not being spent or created.
49#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
50pub struct Transaction {
51    /// Input UTXOs.
52    pub ins: Vec<(UtxoId, Charms)>,
53    /// Reference UTXOs.
54    pub refs: Vec<(UtxoId, Charms)>,
55    /// Output charms.
56    pub outs: Vec<Charms>,
57    /// Amounts of native coin in inputs
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub coin_ins: Option<Vec<NativeOutput>>,
60    /// Amounts of native coin in outputs
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub coin_outs: Option<Vec<NativeOutput>>,
63    /// Previous transactions (creating outputs spent by this transaction) by transaction ID.
64    pub prev_txs: BTreeMap<TxId, Data>,
65    /// All apps used in this transaction with their public inputs.
66    pub app_public_inputs: BTreeMap<App, Data>,
67}
68
69#[serde_as]
70#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
71pub struct NativeOutput {
72    pub amount: u64,
73    #[serde_as(as = "IfIsHumanReadable<Hex>")]
74    pub dest: Vec<u8>,
75    #[serde(skip_serializing_if = "Option::is_none", default)]
76    pub content: Option<Data>,
77}
78
79/// Charms are tokens, NFTs or instances of arbitrary app state.
80/// This type alias represents a collection of charms.
81/// Structurally it is a map of `app -> data`.
82pub type Charms = BTreeMap<App, Data>;
83
84/// ID of a UTXO (Unspent Transaction Output) in the underlying ledger system (e.g. Bitcoin).
85/// A UTXO ID is a pair of `(transaction ID, index of the output)`.
86#[cfg_attr(any(test, feature = "test"), derive(test_strategy::Arbitrary))]
87#[derive(Clone, Default, Eq, Ord, PartialEq, PartialOrd)]
88pub struct UtxoId(pub TxId, pub u32);
89
90impl UtxoId {
91    /// Convert to a byte array (of 36 bytes).
92    pub fn to_bytes(&self) -> [u8; 36] {
93        let mut bytes = [0u8; 36];
94        bytes[..32].copy_from_slice(&self.0.0); // Copy TxId
95        bytes[32..].copy_from_slice(&self.1.to_le_bytes()); // Copy index as little-endian
96        bytes
97    }
98
99    /// Create `UtxoId` from a byte array (of 36 bytes).
100    pub fn from_bytes(bytes: [u8; 36]) -> Self {
101        let mut txid_bytes = [0u8; 32];
102        txid_bytes.copy_from_slice(&bytes[..32]);
103        let index = u32::from_le_bytes(bytes[32..].try_into().expect("exactly 4 bytes expected"));
104        UtxoId(TxId(txid_bytes), index)
105    }
106
107    fn to_string_internal(&self) -> String {
108        format!("{}:{}", self.0.to_string(), self.1)
109    }
110}
111
112impl FromStr for UtxoId {
113    type Err = anyhow::Error;
114
115    /// Try to create `UtxoId` from a string in the format `txid_hex:index`.
116    /// Example:
117    /// ```
118    /// use std::str::FromStr;
119    /// use charms_data::UtxoId;
120    /// let utxo_id = UtxoId::from_str("92077a14998b31367efeec5203a00f1080facdb270cbf055f09b66ae0a273c7d:3").unwrap();
121    /// ```
122    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
123        let parts: Vec<&str> = s.split(':').collect();
124        if parts.len() != 2 {
125            return Err(anyhow!("expected format: txid_hex:index"));
126        }
127
128        let txid = TxId::from_str(parts[0])?;
129
130        let index = parts[1]
131            .parse::<u32>()
132            .map_err(|e| anyhow!("invalid index: {}", e))?;
133
134        Ok(UtxoId(txid, index))
135    }
136}
137
138impl fmt::Display for UtxoId {
139    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
140        self.to_string_internal().fmt(f)
141    }
142}
143
144impl fmt::Debug for UtxoId {
145    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146        write!(f, "UtxoId({})", self.to_string_internal())
147    }
148}
149
150impl Serialize for UtxoId {
151    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
152    where
153        S: Serializer,
154    {
155        if serializer.is_human_readable() {
156            serializer.serialize_str(&self.to_string())
157        } else {
158            serializer.serialize_bytes(self.to_bytes().as_ref())
159        }
160    }
161}
162
163impl<'de> Deserialize<'de> for UtxoId {
164    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
165    where
166        D: Deserializer<'de>,
167    {
168        struct UtxoIdVisitor;
169
170        impl<'de> Visitor<'de> for UtxoIdVisitor {
171            type Value = UtxoId;
172
173            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
174                formatter.write_str("a string in format 'txid_hex:index' or a tuple (TxId, u32)")
175            }
176
177            // Handle human-readable format ("txid_hex:index")
178            fn visit_str<E>(self, value: &str) -> Result<UtxoId, E>
179            where
180                E: de::Error,
181            {
182                UtxoId::from_str(value).map_err(E::custom)
183            }
184
185            // Handle non-human-readable byte format [u8; 36]
186            fn visit_bytes<E>(self, v: &[u8]) -> core::result::Result<Self::Value, E>
187            where
188                E: de::Error,
189            {
190                Ok(UtxoId::from_bytes(v.try_into().map_err(|e| {
191                    E::custom(format!("invalid utxo_id bytes: {}", e))
192                })?))
193            }
194        }
195
196        if deserializer.is_human_readable() {
197            deserializer.deserialize_str(UtxoIdVisitor)
198        } else {
199            deserializer.deserialize_bytes(UtxoIdVisitor)
200        }
201    }
202}
203
204/// App represents an application that can be used to create, transform or destroy charms (tokens,
205/// NFTs and other instances of app data).
206///
207/// An app is identified by a single character `tag`, a 32-byte `identity` and a 32-byte `vk`
208/// (verification key).
209/// The `tag` is a single character that represents the type of the app, with two special values:
210/// - `TOKEN` (tag `t`) for tokens,
211/// - `NFT` (tag `n`) for NFTs.
212///
213/// Other values of `tag` are perfectly legal. The above ones are special: tokens and NFTs can be
214/// transferred without providing the app's implementation (RISC-V binary).
215///
216/// The `vk` is a 32-byte byte string (hash) that is used to verify proofs that the app's contract
217/// is satisfied (against the certain transaction, additional public input and private input).
218///
219/// The `identity` is a 32-byte byte string (hash) that uniquely identifies the app among other apps
220/// implemented using the same code.
221#[cfg_attr(any(test, feature = "test"), derive(proptest_derive::Arbitrary))]
222#[derive(Clone, Default, Eq, Ord, PartialEq, PartialOrd)]
223pub struct App {
224    pub tag: char,
225    pub identity: B32,
226    pub vk: B32,
227}
228
229impl FromStr for App {
230    type Err = anyhow::Error;
231
232    fn from_str(value: &str) -> Result<Self, Self::Err> {
233        // Split the string at '/'
234        let mut parts = value.split('/').collect::<Vec<&str>>();
235        let mut parts = parts.as_mut_slice();
236        ensure!(parts.len() >= 3);
237        if parts[0].is_empty() && parts[1].is_empty() {
238            parts = &mut parts[1..];
239            parts[0] = "/";
240        }
241        ensure!(
242            parts.len() == 3,
243            "expected format: tag_char/identity_hex/vk_hex"
244        );
245
246        let tag = char::from_str(parts[0]).map_err(|e| anyhow!(e))?;
247
248        let identity = B32::from_str(parts[1]).map_err(|e| anyhow!(e))?;
249
250        let vk = B32::from_str(parts[2]).map_err(|e| anyhow!(e))?;
251
252        Ok(App { tag, identity, vk })
253    }
254}
255
256impl fmt::Display for App {
257    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
258        write!(f, "{}/{}/{}", self.tag, self.identity, self.vk)
259    }
260}
261
262impl fmt::Debug for App {
263    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
264        write!(f, "App({}/{}/{})", self.tag, self.identity, self.vk)
265    }
266}
267
268impl Serialize for App {
269    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
270    where
271        S: Serializer,
272    {
273        if serializer.is_human_readable() {
274            serializer.serialize_str(&self.to_string())
275        } else {
276            let mut s = serializer.serialize_tuple(3)?;
277            s.serialize_element(&self.tag)?;
278            s.serialize_element(&self.identity)?;
279            s.serialize_element(&self.vk)?;
280            s.end()
281        }
282    }
283}
284
285impl<'de> Deserialize<'de> for App {
286    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
287    where
288        D: Deserializer<'de>,
289    {
290        struct AppVisitor;
291
292        impl<'de> Visitor<'de> for AppVisitor {
293            type Value = App;
294
295            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
296                formatter.write_str("a string in format 'tag_char/identity_hex/vk_hex' or a struct with tag, identity and vk fields")
297            }
298
299            // Handle human-readable format ("tag_char/identity_hex/vk_hex")
300            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
301            where
302                E: de::Error,
303            {
304                App::from_str(value).map_err(E::custom)
305            }
306
307            fn visit_seq<A>(self, mut seq: A) -> core::result::Result<Self::Value, A::Error>
308            where
309                A: SeqAccess<'de>,
310            {
311                let tag = seq
312                    .next_element()?
313                    .ok_or_else(|| de::Error::missing_field("tag"))?;
314                let identity = seq
315                    .next_element()?
316                    .ok_or_else(|| de::Error::missing_field("identity"))?;
317                let vk = seq
318                    .next_element()?
319                    .ok_or_else(|| de::Error::missing_field("vk"))?;
320
321                Ok(App { tag, identity, vk })
322            }
323        }
324
325        if deserializer.is_human_readable() {
326            deserializer.deserialize_str(AppVisitor)
327        } else {
328            deserializer.deserialize_tuple(3, AppVisitor)
329        }
330    }
331}
332
333/// ID (hash) of a transaction in the underlying ledger (Bitcoin).
334#[cfg_attr(any(test, feature = "test"), derive(proptest_derive::Arbitrary))]
335#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
336pub struct TxId(pub [u8; 32]);
337
338impl TxId {
339    fn to_string_internal(&self) -> String {
340        let mut txid = self.0;
341        txid.reverse();
342        hex::encode(&txid)
343    }
344}
345
346impl FromStr for TxId {
347    type Err = anyhow::Error;
348
349    /// Try to create `TxId` from a string of 64 hex characters.
350    /// Note that string representation of transaction IDs in Bitcoin is reversed, and so is ours
351    /// (for compatibility).
352    ///
353    /// Example:
354    /// ```
355    /// use std::str::FromStr;
356    /// use charms_data::TxId;
357    /// let tx_id = TxId::from_str("92077a14998b31367efeec5203a00f1080facdb270cbf055f09b66ae0a273c7d").unwrap();
358    /// ```
359    fn from_str(s: &str) -> Result<Self> {
360        ensure!(s.len() == 64, "expected 64 hex characters");
361        let bytes = hex::decode(s).map_err(|e| anyhow!("invalid txid hex: {}", e))?;
362        let mut txid: [u8; 32] = bytes.try_into().expect("exactly 32 bytes expected");
363        txid.reverse();
364        Ok(TxId(txid))
365    }
366}
367
368impl fmt::Display for TxId {
369    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
370        self.to_string_internal().fmt(f)
371    }
372}
373
374impl fmt::Debug for TxId {
375    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
376        write!(f, "TxId({})", self.to_string_internal())
377    }
378}
379
380impl Serialize for TxId {
381    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
382    where
383        S: Serializer,
384    {
385        if serializer.is_human_readable() {
386            serializer.serialize_str(&self.to_string())
387        } else {
388            serializer.serialize_bytes(&self.0)
389        }
390    }
391}
392
393impl<'de> Deserialize<'de> for TxId {
394    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
395    where
396        D: Deserializer<'de>,
397    {
398        struct TxIdVisitor;
399
400        impl<'de> Visitor<'de> for TxIdVisitor {
401            type Value = TxId;
402
403            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
404                formatter.write_str("a string of 64 hex characters or a byte array of 32 bytes")
405            }
406
407            // Handle human-readable format ("txid_hex")
408            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
409            where
410                E: de::Error,
411            {
412                TxId::from_str(value).map_err(E::custom)
413            }
414
415            // Handle non-human-readable byte format [u8; 32]
416            fn visit_bytes<E>(self, v: &[u8]) -> core::result::Result<Self::Value, E>
417            where
418                E: de::Error,
419            {
420                Ok(TxId(v.try_into().map_err(|e| {
421                    E::custom(format!("invalid txid bytes: {}", e))
422                })?))
423            }
424        }
425
426        if deserializer.is_human_readable() {
427            deserializer.deserialize_str(TxIdVisitor)
428        } else {
429            deserializer.deserialize_bytes(TxIdVisitor)
430        }
431    }
432}
433
434/// 32-byte byte string (e.g. a hash, like SHA256).
435#[cfg_attr(any(test, feature = "test"), derive(proptest_derive::Arbitrary))]
436#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
437pub struct B32(pub [u8; 32]);
438
439impl FromStr for B32 {
440    type Err = anyhow::Error;
441
442    fn from_str(s: &str) -> Result<Self, Self::Err> {
443        ensure!(s.len() == 64, "expected 64 hex characters");
444        let bytes = hex::decode(s).map_err(|e| anyhow!("invalid hex: {}", e))?;
445        let hash: [u8; 32] = bytes.try_into().expect("exactly 32 bytes expected");
446        Ok(B32(hash))
447    }
448}
449
450impl AsRef<[u8]> for B32 {
451    fn as_ref(&self) -> &[u8] {
452        &self.0
453    }
454}
455
456impl fmt::Display for B32 {
457    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
458        hex::encode(&self.0).fmt(f)
459    }
460}
461
462impl fmt::Debug for B32 {
463    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
464        write!(f, "Bytes32({})", hex::encode(&self.0))
465    }
466}
467
468impl Serialize for B32 {
469    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
470    where
471        S: Serializer,
472    {
473        if serializer.is_human_readable() {
474            serializer.serialize_str(&self.to_string())
475        } else {
476            let mut seq = serializer.serialize_tuple(32)?;
477            for &byte in &self.0 {
478                seq.serialize_element(&byte)?;
479            }
480            seq.end()
481        }
482    }
483}
484
485impl<'de> Deserialize<'de> for B32 {
486    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
487    where
488        D: Deserializer<'de>,
489    {
490        struct B32Visitor;
491
492        impl<'de> Visitor<'de> for B32Visitor {
493            type Value = B32;
494
495            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
496                formatter.write_str("a string of 64 hex characters or a sequence of 32 bytes")
497            }
498
499            // Handle human-readable format ("hex")
500            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
501            where
502                E: de::Error,
503            {
504                B32::from_str(value).map_err(E::custom)
505            }
506
507            // Handle non-human-readable byte format [u8; 32]
508            fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
509            where
510                A: SeqAccess<'de>,
511            {
512                let mut bytes = [0u8; 32];
513                for i in 0..32 {
514                    bytes[i] = seq
515                        .next_element()?
516                        .ok_or_else(|| serde::de::Error::invalid_length(i, &"32 elements"))?;
517                }
518
519                // Check if there are extra elements
520                if seq.next_element::<u8>()?.is_some() {
521                    return Err(serde::de::Error::invalid_length(33, &"exactly 32 elements"));
522                }
523
524                Ok(B32(bytes))
525            }
526        }
527
528        if deserializer.is_human_readable() {
529            deserializer.deserialize_str(B32Visitor)
530        } else {
531            deserializer.deserialize_tuple(32, B32Visitor)
532        }
533    }
534}
535
536/// Represents a data value that is guaranteed to be serialized/deserialized to/from CBOR.
537#[derive(Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
538pub struct Data(Value);
539
540impl Eq for Data {}
541
542impl Ord for Data {
543    fn cmp(&self, other: &Self) -> Ordering {
544        self.0
545            .partial_cmp(&other.0)
546            .expect("Value comparison should have succeeded") // PANIC: will panic if CBOR Value comparison returns None (NaN or incomparable). We expect this to never happen (famous last words).
547    }
548}
549
550impl Data {
551    /// Create an empty data value.
552    pub fn empty() -> Self {
553        Self(Value::Null)
554    }
555
556    /// Check if the data value is empty.
557    pub fn is_empty(&self) -> bool {
558        self.0.is_null()
559    }
560
561    /// Try to cast to a value of a deserializable type (implementing
562    /// `serde::de::DeserializeOwned`).
563    pub fn value<T: DeserializeOwned>(&self) -> Result<T> {
564        self.0
565            .deserialized()
566            .map_err(|e| anyhow!("deserialization error: {}", e))
567    }
568
569    /// Serialize to bytes.
570    pub fn bytes(&self) -> Vec<u8> {
571        util::write(&self).expect("serialization is expected to succeed")
572    }
573
574    pub fn try_from_bytes(bytes: &[u8]) -> anyhow::Result<Self> {
575        Ok(Self(util::read(bytes)?))
576    }
577}
578
579impl<T> From<&T> for Data
580where
581    T: Serialize,
582{
583    fn from(value: &T) -> Self {
584        Self(Value::serialized(value).expect("casting to a CBOR Value is expected to succeed"))
585    }
586}
587
588impl Default for Data {
589    fn default() -> Self {
590        Self::empty()
591    }
592}
593
594impl fmt::Debug for Data {
595    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
596        write!(f, "Data({})", format!("{:?}", &self.0))
597    }
598}
599
600/// Special `App.tag` value for fungible tokens. See [`App`] for more details.
601pub const TOKEN: char = 't';
602/// Special `App.tag` value for non-fungible tokens (NFTs). See [`App`] for more details.
603pub const NFT: char = 'n';
604/// Special `App.tag` value for scrolls: Bitcoin outputs with such charms can only be created at
605/// Scrolls addresses. It works as any other non-token app for Cardano.
606pub const SCROLL: char = 's';
607
608/// Check if the transaction is a simple transfer of assets specified by `app`.
609pub fn is_simple_transfer(app: &App, tx: &Transaction) -> bool {
610    match app.tag {
611        TOKEN => token_amounts_balanced(app, tx),
612        NFT => nft_state_preserved(app, tx),
613        _ => false,
614    }
615}
616
617/// Check if the provided app's token amounts are balanced in the transaction. This means that the
618/// sum of the token amounts in the `tx` inputs is equal to the sum of the token amounts in the `tx`
619/// outputs.
620pub fn token_amounts_balanced(app: &App, tx: &Transaction) -> bool {
621    match (
622        sum_token_amount(app, tx.ins.iter().map(|(_, v)| v)),
623        sum_token_amount(app, tx.outs.iter()),
624    ) {
625        (Ok(amount_in), Ok(amount_out)) => amount_in == amount_out,
626        (..) => false,
627    }
628}
629
630/// Check if the NFT states are preserved in the transaction. This means that the NFTs (created by
631/// the provided `app`) in the `tx` inputs are the same as the NFTs in the `tx` outputs.
632pub fn nft_state_preserved(app: &App, tx: &Transaction) -> bool {
633    let nft_states_in = app_state_multiset(app, tx.ins.iter().map(|(_, v)| v));
634    let nft_states_out = app_state_multiset(app, tx.outs.iter());
635
636    nft_states_in == nft_states_out
637}
638
639/// Deprecated. Use [charm_values] instead.
640#[deprecated(since = "0.7.0", note = "use `charm_values` instead")]
641pub fn app_datas<'a>(
642    app: &'a App,
643    strings_of_charms: impl Iterator<Item = &'a Charms>,
644) -> impl Iterator<Item = &'a Data> {
645    charm_values(app, strings_of_charms)
646}
647
648/// Iterate over all charm values of a given app (charm key) in the given outputs
649/// (strings of charms).
650pub fn charm_values<'a>(
651    app: &'a App,
652    strings_of_charms: impl Iterator<Item = &'a Charms>,
653) -> impl Iterator<Item = &'a Data> {
654    strings_of_charms.filter_map(|charms| charms.get(app))
655}
656
657fn app_state_multiset<'a>(
658    app: &App,
659    strings_of_charms: impl Iterator<Item = &'a Charms>,
660) -> BTreeMap<&'a Data, usize> {
661    strings_of_charms
662        .filter_map(|charms| charms.get(app))
663        .fold(BTreeMap::new(), |mut r, s| {
664            match r.get_mut(s) {
665                Some(count) => *count += 1,
666                None => {
667                    r.insert(s, 1);
668                }
669            }
670            r
671        })
672}
673
674/// Sum the token amounts in the provided `strings_of_charms`.
675pub fn sum_token_amount<'a>(
676    app: &App,
677    strings_of_charms: impl Iterator<Item = &'a Charms>,
678) -> Result<u64> {
679    ensure!(app.tag == TOKEN);
680    strings_of_charms.fold(Ok(0u64), |amount, charms| match charms.get(app) {
681        Some(state) => Ok(amount? + state.value::<u64>()?),
682        None => amount,
683    })
684}
685
686#[cfg(test)]
687mod tests {
688    use super::*;
689    use ciborium::Value;
690    use proptest::prelude::*;
691    use test_strategy::proptest;
692
693    #[proptest]
694    fn doesnt_crash(s: String) {
695        let _ = TxId::from_str(&s);
696    }
697
698    #[proptest]
699    fn txid_roundtrip(txid: TxId) {
700        let s = txid.to_string();
701        let txid2 = TxId::from_str(&s).unwrap();
702        prop_assert_eq!(txid, txid2);
703    }
704
705    #[proptest]
706    fn vk_serde_roundtrip(vk: B32) {
707        let bytes = util::write(&vk).unwrap();
708        let vk2 = util::read(bytes.as_slice()).unwrap();
709        prop_assert_eq!(vk, vk2);
710    }
711
712    #[proptest]
713    fn vk_serde_json_roundtrip(vk: App) {
714        let json_str = serde_json::to_string(&vk).unwrap();
715        // dbg!(&json_str);
716        let vk2 = serde_json::from_str(&json_str).unwrap();
717        prop_assert_eq!(vk, vk2);
718    }
719
720    #[proptest]
721    fn vk_serde_yaml_roundtrip(vk: App) {
722        let yaml_str = serde_yaml::to_string(&vk).unwrap();
723        // dbg!(&yaml_str);
724        let vk2 = serde_yaml::from_str(&yaml_str).unwrap();
725        prop_assert_eq!(vk, vk2);
726    }
727
728    #[proptest]
729    fn app_serde_roundtrip(app: App) {
730        let bytes = util::write(&app).unwrap();
731        let app2 = util::read(bytes.as_slice()).unwrap();
732        prop_assert_eq!(app, app2);
733    }
734
735    #[proptest]
736    fn utxo_id_serde_roundtrip(utxo_id: UtxoId) {
737        let bytes = util::write(&utxo_id).unwrap();
738        let utxo_id2 = util::read(bytes.as_slice()).unwrap();
739        prop_assert_eq!(utxo_id, utxo_id2);
740    }
741
742    #[proptest]
743    fn tx_id_serde_roundtrip(tx_id: TxId) {
744        let bytes = util::write(&tx_id).unwrap();
745        let tx_id2 = util::read(bytes.as_slice()).unwrap();
746        prop_assert_eq!(tx_id, tx_id2);
747    }
748
749    #[test]
750    fn minimal_txid() {
751        let tx_id_bytes: [u8; 32] = [&[1u8], [0u8; 31].as_ref()].concat().try_into().unwrap();
752        let tx_id = TxId(tx_id_bytes);
753        let tx_id_str = tx_id.to_string();
754        let tx_id_str_expected = "0000000000000000000000000000000000000000000000000000000000000001";
755        assert_eq!(tx_id_str, tx_id_str_expected);
756    }
757
758    #[test]
759    fn data_dbg() {
760        let v = 42u64;
761        let data: Data = Data::from(&v);
762        assert_eq!(format!("{:?}", data), format!("Data({:?})", Value::from(v)));
763
764        let data = Data::empty();
765        assert_eq!(format!("{:?}", data), "Data(Null)");
766
767        let vec1: Vec<u64> = vec![];
768        let data: Data = Data::from(&vec1);
769        assert_eq!(format!("{:?}", data), "Data(Array([]))");
770    }
771
772    #[test]
773    fn data_bytes() {
774        let v = ("42u64", 42u64);
775        let data = Data::from(&v);
776        let value = Value::serialized(&v).expect("serialization should have succeeded");
777
778        let buf = util::write(&value).expect("serialization should have succeeded");
779
780        assert_eq!(data.bytes(), buf);
781    }
782
783    #[test]
784    fn dummy() {}
785}
786
787#[derive(Clone, Debug, Serialize, Deserialize)]
788pub struct AppInput {
789    pub app_binaries: BTreeMap<B32, Vec<u8>>,
790    pub app_private_inputs: BTreeMap<App, Data>,
791    /// Signatures of Wasm binary hashes for versioned app modules, keyed by app `vk` (which is
792    /// the SHA256 of the signing public key).
793    #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
794    pub app_signatures: BTreeMap<B32, AppSignature>,
795}
796
797/// Identifies a specific version of an app's Wasm binary, signed by the app's owning key.
798///
799/// For a versioned app, the app's `vk` is the SHA256 of a public key. The corresponding
800/// [`AppSignature`] proves that the holder of that key has authorized this particular Wasm
801/// binary as the given `version` of the app.
802#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
803pub struct VersionedApp {
804    /// Version number, as exposed by the `__app_version` export of the Wasm binary.
805    pub version: u32,
806    /// SHA256 hash of the Wasm binary that implements this version of the app.
807    pub wasm_hash: B32,
808}
809
810/// Signature on a Wasm binary hash, together with the x-only public key it was produced with.
811///
812/// The signature scheme is BIP-340 Schnorr over secp256k1. `public_key` is the 32-byte x-only
813/// verifying key (BIP-340); `signature` is the 64-byte Schnorr signature over the SHA256 hash
814/// of the Wasm binary (i.e. over the corresponding [`VersionedApp::wasm_hash`]). Both fields
815/// are typed as fixed-size byte arrays so that bad lengths are rejected at the
816/// (de)serialization boundary rather than deep inside signature verification.
817#[serde_as]
818#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
819pub struct AppSignature {
820    /// BIP-340 x-only public key (32 bytes).
821    pub public_key: B32,
822    /// BIP-340 Schnorr signature over the Wasm binary's SHA256 hash (64 bytes).
823    #[serde_as(as = "IfIsHumanReadable<Hex, Bytes>")]
824    pub signature: [u8; 64],
825}