foundation_urtypes/registry/
address.rs

1// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. <hello@foundationdevices.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4//! # `crypto-address`
5//!
6//! See [BCR-2020-009](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-009-address.md).
7
8use minicbor::{
9    data::Tag, data::Type, decode::Error, encode::Write, Decode, Decoder, Encode, Encoder,
10};
11
12use crate::registry::CoinInfo;
13
14/// A cryptocurrency address.
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub struct Address<'a> {
17    /// Coin information.
18    pub info: Option<CoinInfo>,
19    /// Address type if applicable.
20    pub kind: Option<AddressKind>,
21    /// The address data.
22    pub data: &'a [u8],
23}
24
25impl<'a> Address<'a> {
26    /// The CBOR tag used when [`Address`] is embedded in other CBOR
27    /// types.
28    pub const TAG: Tag = Tag::new(307);
29}
30
31#[cfg(feature = "bitcoin")]
32fn data_from_payload(payload: &bitcoin::address::Payload) -> Result<&[u8], InterpretAddressError> {
33    use bitcoin::address::Payload;
34
35    match payload {
36        Payload::PubkeyHash(ref pkh) => Ok(pkh.as_ref()),
37        Payload::ScriptHash(ref sh) => Ok(sh.as_ref()),
38        Payload::WitnessProgram(ref wp) => Ok(wp.program().as_bytes()),
39        _ => Err(InterpretAddressError::UnsupportedPayload),
40    }
41}
42
43#[cfg(feature = "bitcoin")]
44impl<'a> TryFrom<&'a bitcoin::Address<bitcoin::address::NetworkUnchecked>> for Address<'a> {
45    type Error = InterpretAddressError;
46
47    fn try_from(
48        address: &'a bitcoin::Address<bitcoin::address::NetworkUnchecked>,
49    ) -> Result<Self, Self::Error> {
50        let kind = AddressKind::try_from(address.payload()).ok();
51        let data = data_from_payload(address.payload())?;
52
53        Ok(Self {
54            info: None,
55            kind,
56            data,
57        })
58    }
59}
60
61#[cfg(feature = "bitcoin")]
62impl<'a> TryFrom<&'a bitcoin::Address<bitcoin::address::NetworkChecked>> for Address<'a> {
63    type Error = InterpretAddressError;
64
65    fn try_from(address: &'a bitcoin::Address) -> Result<Self, Self::Error> {
66        use crate::registry::CoinType;
67        use bitcoin::Network;
68
69        let network = match address.network() {
70            Network::Bitcoin => CoinInfo::NETWORK_MAINNET,
71            Network::Testnet => CoinInfo::NETWORK_BTC_TESTNET,
72            _ => return Err(InterpretAddressError::UnsupportedNetwork),
73        };
74        let info = CoinInfo::new(CoinType::BTC, network);
75        let kind = AddressKind::try_from(address.payload()).ok();
76        let data = data_from_payload(address.payload())?;
77
78        Ok(Self {
79            info: Some(info),
80            kind,
81            data,
82        })
83    }
84}
85
86#[cfg(feature = "bitcoin")]
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub enum InterpretAddressError {
89    UnsupportedNetwork,
90    UnsupportedPayload,
91}
92
93impl<'b, C> Decode<'b, C> for Address<'b> {
94    fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result<Self, Error> {
95        let mut info = None;
96        let mut address_type = None;
97        let mut data = None;
98
99        let mut len = d.map()?;
100        loop {
101            match len {
102                Some(0) => break,
103                Some(n) => len = Some(n - 1),
104                None => {
105                    if d.datatype()? == Type::Break {
106                        break;
107                    }
108                }
109            }
110
111            match d.u32()? {
112                1 => {
113                    if CoinInfo::TAG != d.tag()? {
114                        return Err(Error::message("crypto-coin-info tag is invalid"));
115                    }
116
117                    info = Some(CoinInfo::decode(d, ctx)?);
118                }
119                2 => address_type = Some(AddressKind::decode(d, ctx)?),
120                3 => data = Some(d.bytes()?),
121                _ => return Err(Error::message("unknown map entry")),
122            }
123        }
124
125        Ok(Self {
126            info,
127            kind: address_type,
128            data: data.ok_or_else(|| Error::message("data is missing"))?,
129        })
130    }
131}
132
133impl<'a, C> Encode<C> for Address<'a> {
134    fn encode<W: Write>(
135        &self,
136        e: &mut Encoder<W>,
137        ctx: &mut C,
138    ) -> Result<(), minicbor::encode::Error<W::Error>> {
139        let include_info = match self.info {
140            Some(ref i) => !i.is_default(),
141            None => false,
142        };
143
144        let len = include_info as u64 + self.kind.is_some() as u64 + 1;
145        e.map(len)?;
146
147        if include_info {
148            let info = self.info.as_ref().unwrap();
149            e.u8(1)?.tag(CoinInfo::TAG)?;
150            info.encode(e, ctx)?;
151        }
152
153        if let Some(ref address_type) = self.kind {
154            e.u8(2)?;
155            address_type.encode(e, ctx)?;
156        }
157
158        e.u8(3)?.bytes(self.data)?;
159
160        Ok(())
161    }
162}
163
164/// Bitcoin (and similar cryptocurrencies) address type.
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
166pub enum AddressKind {
167    /// Pay to Public Key Hash.
168    P2PKH,
169    /// Pay to Script Hash.
170    P2SH,
171    /// Pay to Witness Public Key Hash.
172    P2WPKH,
173}
174
175impl TryFrom<u8> for AddressKind {
176    type Error = InvalidAddressType;
177
178    fn try_from(value: u8) -> Result<Self, Self::Error> {
179        Ok(match value {
180            0 => AddressKind::P2PKH,
181            1 => AddressKind::P2SH,
182            2 => AddressKind::P2WPKH,
183            _ => {
184                return Err(InvalidAddressType {
185                    invalid_type: value,
186                })
187            }
188        })
189    }
190}
191
192/// Error that can happen during conversion from an unsigned integer to an
193/// [`AddressKind`].
194#[derive(Debug)]
195pub struct InvalidAddressType {
196    /// The invalid type.
197    pub invalid_type: u8,
198}
199
200impl From<AddressKind> for u8 {
201    fn from(value: AddressKind) -> Self {
202        match value {
203            AddressKind::P2PKH => 0,
204            AddressKind::P2SH => 1,
205            AddressKind::P2WPKH => 2,
206        }
207    }
208}
209
210#[cfg(feature = "bitcoin")]
211impl TryFrom<&bitcoin::address::Payload> for AddressKind {
212    type Error = UnknownAddressType;
213
214    fn try_from(value: &bitcoin::address::Payload) -> Result<Self, Self::Error> {
215        use bitcoin::{address::Payload, blockdata::script::witness_version::WitnessVersion};
216
217        let kind = match value {
218            Payload::PubkeyHash(_) => AddressKind::P2PKH,
219            Payload::ScriptHash(_) => AddressKind::P2SH,
220            Payload::WitnessProgram(wp) => match wp.version() {
221                WitnessVersion::V0 if wp.program().as_bytes().len() == 20 => AddressKind::P2WPKH,
222                _ => return Err(UnknownAddressType),
223            },
224            _ => return Err(UnknownAddressType),
225        };
226
227        Ok(kind)
228    }
229}
230
231#[cfg(feature = "bitcoin")]
232#[derive(Debug, Clone, PartialEq, Eq)]
233pub struct UnknownAddressType;
234
235impl<'b, C> Decode<'b, C> for AddressKind {
236    fn decode(d: &mut Decoder<'b>, _ctx: &mut C) -> Result<Self, Error> {
237        AddressKind::try_from(d.u8()?).map_err(|_| Error::message("invalid address type"))
238    }
239}
240
241impl<C> Encode<C> for AddressKind {
242    fn encode<W: Write>(
243        &self,
244        e: &mut Encoder<W>,
245        _ctx: &mut C,
246    ) -> Result<(), minicbor::encode::Error<W::Error>> {
247        e.u8((*self).into())?;
248        Ok(())
249    }
250}