foundation_urtypes/registry/
coininfo.rs

1// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. <hello@foundationdevices.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use minicbor::{
5    data::Tag, data::Type, decode::Error, encode::Write, Decode, Decoder, Encode, Encoder,
6};
7
8/// Metadata for the type and use of a cryptocurrency.
9#[doc(alias("crypto-coininfo"))]
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct CoinInfo {
12    /// Coin type.
13    pub coin_type: CoinType,
14    /// Network identifier.
15    ///
16    /// `mainnet` is the general for all currencies.
17    ///
18    /// All others are coin-specific value.
19    pub network: u64,
20}
21
22impl CoinInfo {
23    /// Tag for embedding [`CoinInfo`] in other types.
24    pub const TAG: Tag = Tag::new(40305);
25
26    /// Universal value for unique network.
27    pub const NETWORK_MAINNET: u64 = 0;
28
29    /// Bitcoin testnet network.
30    pub const NETWORK_BTC_TESTNET: u64 = 1;
31
32    /// Bitcoin mainnet.
33    pub const BTC_MAINNET: Self = Self {
34        coin_type: CoinType::BTC,
35        network: Self::NETWORK_MAINNET,
36    };
37
38    /// Construct a new [`CoinInfo`].
39    pub const fn new(coin_type: CoinType, network: u64) -> Self {
40        Self { coin_type, network }
41    }
42
43    pub fn is_default(&self) -> bool {
44        self.coin_type == CoinType::BTC && self.network == Self::NETWORK_MAINNET
45    }
46}
47
48impl<'b, C> Decode<'b, C> for CoinInfo {
49    fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result<Self, Error> {
50        let mut coin_type = None;
51        let mut network = None;
52
53        let mut len = d.map()?;
54        loop {
55            match len {
56                Some(0) => break,
57                Some(n) => len = Some(n - 1),
58                None => {
59                    if d.datatype()? == Type::Break {
60                        break;
61                    }
62                }
63            }
64
65            match d.u32()? {
66                1 => coin_type = Some(CoinType::decode(d, ctx)?),
67                2 => network = Some(d.u64()?),
68                _ => return Err(Error::message("unknown map entry")),
69            }
70        }
71
72        Ok(Self {
73            coin_type: coin_type.unwrap_or(CoinType::BTC),
74            network: network.unwrap_or(0),
75        })
76    }
77}
78
79impl<C> Encode<C> for CoinInfo {
80    fn encode<W: Write>(
81        &self,
82        e: &mut Encoder<W>,
83        ctx: &mut C,
84    ) -> Result<(), minicbor::encode::Error<W::Error>> {
85        let is_not_default_coin_type = self.coin_type != CoinType::BTC;
86        let is_not_default_network = self.network != 0;
87        let len = is_not_default_coin_type as u64 + is_not_default_network as u64;
88
89        e.map(len)?;
90
91        if is_not_default_coin_type {
92            e.u8(1)?;
93            self.coin_type.encode(e, ctx)?;
94        }
95
96        if is_not_default_network {
97            e.u8(2)?.u64(self.network)?;
98        }
99
100        Ok(())
101    }
102}
103
104/// A coin type.
105///
106/// Values are defined in [SLIP-44].
107///
108/// [SLIP-44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md
109#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
110pub struct CoinType(pub(crate) u32);
111
112impl CoinType {
113    pub const BTC: Self = CoinType(0x00);
114
115    pub fn new(value: u32) -> Self {
116        Self(value)
117    }
118
119    /// Get the value of the coin type.
120    pub fn get(self) -> u32 {
121        self.0
122    }
123}
124
125impl<'b, C> Decode<'b, C> for CoinType {
126    fn decode(d: &mut Decoder<'b>, _ctx: &mut C) -> Result<Self, Error> {
127        let n = d.u32()?;
128        if n >= 1 << 31 {
129            return Err(Error::message("coin type out of range"));
130        }
131
132        Ok(CoinType(n))
133    }
134}
135
136impl<C> Encode<C> for CoinType {
137    fn encode<W: Write>(
138        &self,
139        e: &mut Encoder<W>,
140        _ctx: &mut C,
141    ) -> Result<(), minicbor::encode::Error<W::Error>> {
142        e.u32(self.get())?;
143        Ok(())
144    }
145}
146
147impl From<u32> for CoinType {
148    fn from(n: u32) -> Self {
149        CoinType(n)
150    }
151}
152
153impl From<CoinType> for u32 {
154    fn from(coin_type: CoinType) -> Self {
155        coin_type.get()
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    // Basic test. No independent test vectors available.
164    #[test]
165    fn test_crypto_coininfo_roundtrip() {
166        let crypto_coininfo = CoinInfo::BTC_MAINNET;
167        let cbor = minicbor::to_vec(&crypto_coininfo).unwrap();
168        let decoded = minicbor::decode(&cbor).unwrap();
169        assert_eq!(crypto_coininfo, decoded);
170    }
171
172    // Basic test. No independent test vectors available.
173    #[test]
174    fn test_coin_type_roundtrip() {
175        let coin_type = CoinType::BTC;
176        let cbor = minicbor::to_vec(coin_type).unwrap();
177        assert_eq!(cbor, &[0x00]);
178        let decoded = minicbor::decode(&cbor).unwrap();
179        assert_eq!(coin_type, decoded);
180    }
181}