charms_data/
lib.rs

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