fedimint_core/encoding/
btc.rs

1use std::io::{Error, Write};
2use std::str::FromStr;
3
4use anyhow::format_err;
5use bitcoin::address::NetworkUnchecked;
6use bitcoin::hashes::Hash as BitcoinHash;
7use hex::{FromHex, ToHex};
8use miniscript::{Descriptor, MiniscriptKey};
9use serde::{Deserialize, Serialize};
10
11use super::{BufBitcoinReader, CountWrite, SimpleBitcoinRead};
12use crate::encoding::{Decodable, DecodeError, Encodable};
13use crate::get_network_for_address;
14use crate::module::registry::ModuleDecoderRegistry;
15
16macro_rules! impl_encode_decode_bridge {
17    ($btc_type:ty) => {
18        impl crate::encoding::Encodable for $btc_type {
19            fn consensus_encode<W: std::io::Write>(
20                &self,
21                writer: &mut W,
22            ) -> Result<usize, std::io::Error> {
23                Ok(bitcoin::consensus::Encodable::consensus_encode(
24                    self,
25                    &mut std::io::BufWriter::new(writer),
26                )?)
27            }
28        }
29
30        impl crate::encoding::Decodable for $btc_type {
31            fn consensus_decode_from_finite_reader<D: std::io::Read>(
32                d: &mut D,
33                _modules: &$crate::module::registry::ModuleDecoderRegistry,
34            ) -> Result<Self, crate::encoding::DecodeError> {
35                bitcoin::consensus::Decodable::consensus_decode_from_finite_reader(
36                    &mut SimpleBitcoinRead(d),
37                )
38                .map_err(crate::encoding::DecodeError::from_err)
39            }
40        }
41    };
42}
43
44impl_encode_decode_bridge!(bitcoin::block::Header);
45impl_encode_decode_bridge!(bitcoin::BlockHash);
46impl_encode_decode_bridge!(bitcoin::OutPoint);
47impl_encode_decode_bridge!(bitcoin::ScriptBuf);
48impl_encode_decode_bridge!(bitcoin::Transaction);
49impl_encode_decode_bridge!(bitcoin::merkle_tree::PartialMerkleTree);
50
51impl crate::encoding::Encodable for bitcoin::psbt::Psbt {
52    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
53        Ok(self.serialize_to_writer(&mut CountWrite::from(writer))?)
54    }
55}
56
57impl crate::encoding::Decodable for bitcoin::psbt::Psbt {
58    fn consensus_decode_from_finite_reader<D: std::io::Read>(
59        d: &mut D,
60        _modules: &ModuleDecoderRegistry,
61    ) -> Result<Self, crate::encoding::DecodeError> {
62        Self::deserialize_from_reader(&mut BufBitcoinReader::new(d))
63            .map_err(crate::encoding::DecodeError::from_err)
64    }
65}
66
67impl crate::encoding::Encodable for bitcoin::Txid {
68    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
69        Ok(bitcoin::consensus::Encodable::consensus_encode(
70            self,
71            &mut std::io::BufWriter::new(writer),
72        )?)
73    }
74
75    fn consensus_encode_to_hex(&self) -> String {
76        let mut bytes = vec![];
77        self.consensus_encode(&mut bytes)
78            .expect("encoding to bytes can't fail for io reasons");
79
80        // Just Bitcoin things: transaction hashes are encoded reverse
81        bytes.reverse();
82
83        // TODO: remove double-allocation
84        bytes.encode_hex()
85    }
86}
87
88impl crate::encoding::Decodable for bitcoin::Txid {
89    fn consensus_decode_from_finite_reader<D: std::io::Read>(
90        d: &mut D,
91        _modules: &::fedimint_core::module::registry::ModuleDecoderRegistry,
92    ) -> Result<Self, crate::encoding::DecodeError> {
93        bitcoin::consensus::Decodable::consensus_decode_from_finite_reader(&mut SimpleBitcoinRead(
94            d,
95        ))
96        .map_err(crate::encoding::DecodeError::from_err)
97    }
98
99    fn consensus_decode_hex(
100        hex: &str,
101        modules: &ModuleDecoderRegistry,
102    ) -> Result<Self, DecodeError> {
103        let mut bytes = Vec::<u8>::from_hex(hex)
104            .map_err(anyhow::Error::from)
105            .map_err(DecodeError::new_custom)?;
106
107        // Just Bitcoin things: transaction hashes are encoded reverse
108        bytes.reverse();
109
110        let mut reader = std::io::Cursor::new(bytes);
111        Decodable::consensus_decode(&mut reader, modules)
112    }
113}
114
115impl<K> Encodable for Descriptor<K>
116where
117    K: MiniscriptKey,
118{
119    fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<usize, Error> {
120        let descriptor_str = self.to_string();
121        descriptor_str.consensus_encode(writer)
122    }
123}
124
125impl<K> Decodable for Descriptor<K>
126where
127    Self: FromStr,
128    <Self as FromStr>::Err: ToString + std::error::Error + Send + Sync + 'static,
129    K: MiniscriptKey,
130{
131    fn consensus_decode_from_finite_reader<D: std::io::Read>(
132        d: &mut D,
133        modules: &ModuleDecoderRegistry,
134    ) -> Result<Self, DecodeError> {
135        let descriptor_str = String::consensus_decode_from_finite_reader(d, modules)?;
136        Self::from_str(&descriptor_str).map_err(DecodeError::from_err)
137    }
138}
139
140impl Encodable for bitcoin::Network {
141    fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<usize, Error> {
142        u32::from_le_bytes(self.magic().to_bytes()).consensus_encode(writer)
143    }
144}
145
146impl Decodable for bitcoin::Network {
147    fn consensus_decode<D: std::io::Read>(
148        d: &mut D,
149        modules: &ModuleDecoderRegistry,
150    ) -> Result<Self, DecodeError> {
151        let num = u32::consensus_decode(d, modules)?;
152        let magic = bitcoin::p2p::Magic::from_bytes(num.to_le_bytes());
153        Self::from_magic(magic).ok_or_else(|| {
154            DecodeError::new_custom(format_err!("Unknown network magic: {:x}", magic))
155        })
156    }
157}
158
159#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
160pub struct NetworkSaneEncodingWrapper(pub bitcoin::Network);
161
162impl Encodable for NetworkSaneEncodingWrapper {
163    fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<usize, Error> {
164        self.0.magic().to_bytes().consensus_encode(writer)
165    }
166}
167
168impl Decodable for NetworkSaneEncodingWrapper {
169    fn consensus_decode<D: std::io::Read>(
170        d: &mut D,
171        modules: &ModuleDecoderRegistry,
172    ) -> Result<Self, DecodeError> {
173        Ok(Self(
174            bitcoin::Network::from_magic(bitcoin::p2p::Magic::from_bytes(
175                Decodable::consensus_decode(d, modules)?,
176            ))
177            .ok_or_else(|| DecodeError::new_custom(format_err!("Unknown network magic")))?,
178        ))
179    }
180}
181
182impl Encodable for bitcoin::Amount {
183    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
184        self.to_sat().consensus_encode(writer)
185    }
186}
187
188impl Decodable for bitcoin::Amount {
189    fn consensus_decode<D: std::io::Read>(
190        d: &mut D,
191        modules: &ModuleDecoderRegistry,
192    ) -> Result<Self, DecodeError> {
193        Ok(Self::from_sat(u64::consensus_decode(d, modules)?))
194    }
195}
196
197impl Encodable for bitcoin::Address<NetworkUnchecked> {
198    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, Error> {
199        let mut len = 0;
200        len += get_network_for_address(self.as_unchecked()).consensus_encode(writer)?;
201        len += self
202            .clone()
203            .assume_checked()
204            .script_pubkey()
205            .consensus_encode(writer)?;
206        Ok(len)
207    }
208}
209
210impl Decodable for bitcoin::Address<NetworkUnchecked> {
211    fn consensus_decode<D: std::io::Read>(
212        mut d: &mut D,
213        modules: &ModuleDecoderRegistry,
214    ) -> Result<Self, DecodeError> {
215        let network = bitcoin::Network::consensus_decode(&mut d, modules)?;
216        let script_pk = bitcoin::ScriptBuf::consensus_decode(&mut d, modules)?;
217
218        let address = bitcoin::Address::from_script(&script_pk, network)
219            .map_err(|e| DecodeError::new_custom(e.into()))?;
220
221        Ok(address.as_unchecked().clone())
222    }
223}
224
225impl Encodable for bitcoin::hashes::sha256::Hash {
226    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, Error> {
227        self.to_byte_array().consensus_encode(writer)
228    }
229}
230
231impl Decodable for bitcoin::hashes::sha256::Hash {
232    fn consensus_decode<D: std::io::Read>(
233        d: &mut D,
234        modules: &ModuleDecoderRegistry,
235    ) -> Result<Self, DecodeError> {
236        Ok(Self::from_byte_array(Decodable::consensus_decode(
237            d, modules,
238        )?))
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use std::io::Cursor;
245    use std::str::FromStr;
246
247    use bitcoin::hashes::Hash as BitcoinHash;
248
249    use crate::encoding::btc::NetworkSaneEncodingWrapper;
250    use crate::encoding::tests::test_roundtrip_expected;
251    use crate::encoding::{Decodable, Encodable};
252    use crate::ModuleDecoderRegistry;
253
254    #[test_log::test]
255    fn network_roundtrip() {
256        let networks: [(bitcoin::Network, [u8; 5], [u8; 4]); 5] = [
257            (
258                bitcoin::Network::Bitcoin,
259                [0xFE, 0xD9, 0xB4, 0xBE, 0xF9],
260                [0xF9, 0xBE, 0xB4, 0xD9],
261            ),
262            (
263                bitcoin::Network::Testnet,
264                [0xFE, 0x07, 0x09, 0x11, 0x0B],
265                [0x0B, 0x11, 0x09, 0x07],
266            ),
267            (
268                bitcoin::Network::Testnet4,
269                [0xFE, 0x28, 0x3F, 0x16, 0x1C],
270                [0x1C, 0x16, 0x3F, 0x28],
271            ),
272            (
273                bitcoin::Network::Signet,
274                [0xFE, 0x40, 0xCF, 0x03, 0x0A],
275                [0x0A, 0x03, 0xCF, 0x40],
276            ),
277            (
278                bitcoin::Network::Regtest,
279                [0xFE, 0xDA, 0xB5, 0xBF, 0xFA],
280                [0xFA, 0xBF, 0xB5, 0xDA],
281            ),
282        ];
283
284        for (network, magic_bytes, magic_sane_bytes) in networks {
285            let mut network_encoded = Vec::new();
286            network.consensus_encode(&mut network_encoded).unwrap();
287
288            let mut network_sane_encoded = Vec::new();
289            NetworkSaneEncodingWrapper(network)
290                .consensus_encode(&mut network_sane_encoded)
291                .unwrap();
292
293            let network_decoded = bitcoin::Network::consensus_decode(
294                &mut Cursor::new(network_encoded.clone()),
295                &ModuleDecoderRegistry::default(),
296            )
297            .unwrap();
298
299            let network_sane_decoded = NetworkSaneEncodingWrapper::consensus_decode(
300                &mut Cursor::new(network_sane_encoded.clone()),
301                &ModuleDecoderRegistry::default(),
302            )
303            .unwrap();
304
305            assert_eq!(magic_bytes, *network_encoded);
306            assert_eq!(magic_sane_bytes, *network_sane_encoded);
307            assert_eq!(network, network_decoded);
308            assert_eq!(network, network_sane_decoded.0);
309        }
310    }
311
312    #[test_log::test]
313    fn address_roundtrip() {
314        let addresses = [
315            "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5",
316            "mxMYaq5yWinZ9AKjCDcBEbiEwPJD9n2uLU",
317            "1FK8o7mUxyd6QWJAUw7J4vW7eRxuyjj6Ne",
318            "3JSrSU7z7R1Yhh26pt1zzRjQz44qjcrXwb",
319            "tb1qunn0thpt8uk3yk2938ypjccn3urxprt78z9ccq",
320            "2MvUMRv2DRHZi3VshkP7RMEU84mVTfR9xjq",
321        ];
322
323        for address_str in addresses {
324            let address =
325                bitcoin::Address::from_str(address_str).expect("All tested addresses are valid");
326            let mut encoding = vec![];
327            address
328                .consensus_encode(&mut encoding)
329                .expect("Encoding to vec can't fail");
330            let mut cursor = Cursor::new(encoding);
331            let parsed_address =
332                bitcoin::Address::consensus_decode(&mut cursor, &ModuleDecoderRegistry::default())
333                    .expect("Decoding address failed");
334
335            assert_eq!(address, parsed_address);
336        }
337    }
338
339    #[test_log::test]
340    fn sha256_roundtrip() {
341        test_roundtrip_expected(
342            &bitcoin::hashes::sha256::Hash::hash(b"Hello world!"),
343            &[
344                192, 83, 94, 75, 226, 183, 159, 253, 147, 41, 19, 5, 67, 107, 248, 137, 49, 78, 74,
345                63, 174, 192, 94, 207, 252, 187, 125, 243, 26, 217, 229, 26,
346            ],
347        );
348    }
349}