Skip to main content

fedimint_core/encoding/
btc.rs

1use std::io::{Error, Write};
2use std::str::FromStr;
3
4use bitcoin::address::NetworkUnchecked;
5use bitcoin::hashes::Hash as BitcoinHash;
6use hex::{FromHex, ToHex};
7use lightning::ln::msgs;
8use lightning::util::ser::{BigSize, Readable, Writeable};
9use miniscript::{Descriptor, MiniscriptKey};
10use serde::{Deserialize, Serialize};
11
12use crate::encoding::{Decodable, DecodeError, Encodable};
13use crate::get_network_for_address;
14use crate::module::registry::ModuleDecoderRegistry;
15
16/// Keeps a rust-bitcoin reader failure distinguishable as [`DecodeError::Io`].
17fn from_bitcoin_encode_error(error: bitcoin::consensus::encode::Error) -> DecodeError {
18    match error {
19        bitcoin::consensus::encode::Error::Io(io) => DecodeError::Io(io.into()),
20        other => DecodeError::from_err(other),
21    }
22}
23
24/// Keeps a PSBT reader failure distinguishable as [`DecodeError::Io`].
25fn from_psbt_error(error: bitcoin::psbt::Error) -> DecodeError {
26    match error {
27        bitcoin::psbt::Error::Io(io) => DecodeError::Io(io.into()),
28        bitcoin::psbt::Error::ConsensusEncoding(inner) => from_bitcoin_encode_error(inner),
29        other => DecodeError::from_err(other),
30    }
31}
32
33macro_rules! impl_encode_decode_bridge {
34    ($btc_type:ty) => {
35        impl crate::encoding::Encodable for $btc_type {
36            fn consensus_encode<W: std::io::Write>(
37                &self,
38                writer: &mut W,
39            ) -> Result<(), std::io::Error> {
40                bitcoin::consensus::Encodable::consensus_encode(
41                    self,
42                    &mut std::io::BufWriter::new(writer),
43                )?;
44                Ok(())
45            }
46        }
47
48        impl crate::encoding::Decodable for $btc_type {
49            fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
50                d: &mut D,
51                _modules: &$crate::module::registry::ModuleDecoderRegistry,
52            ) -> Result<Self, crate::encoding::DecodeError> {
53                bitcoin::consensus::Decodable::consensus_decode_from_finite_reader(
54                    &mut SimpleBitcoinRead(d),
55                )
56                .map_err(from_bitcoin_encode_error)
57            }
58        }
59    };
60}
61
62impl_encode_decode_bridge!(bitcoin::block::Header);
63impl_encode_decode_bridge!(bitcoin::BlockHash);
64impl_encode_decode_bridge!(bitcoin::OutPoint);
65impl_encode_decode_bridge!(bitcoin::TxOut);
66impl_encode_decode_bridge!(bitcoin::ScriptBuf);
67impl_encode_decode_bridge!(bitcoin::Transaction);
68impl_encode_decode_bridge!(bitcoin::merkle_tree::PartialMerkleTree);
69
70impl crate::encoding::Encodable for bitcoin::psbt::Psbt {
71    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
72        self.serialize_to_writer(&mut BitoinIoWriteAdapter::from(writer))?;
73        Ok(())
74    }
75}
76
77impl crate::encoding::Decodable for bitcoin::psbt::Psbt {
78    fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
79        d: &mut D,
80        _modules: &ModuleDecoderRegistry,
81    ) -> Result<Self, crate::encoding::DecodeError> {
82        Self::deserialize_from_reader(&mut BufBitcoinReader::new(d)).map_err(from_psbt_error)
83    }
84}
85
86impl crate::encoding::Encodable for bitcoin::Txid {
87    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
88        bitcoin::consensus::Encodable::consensus_encode(
89            self,
90            &mut std::io::BufWriter::new(writer),
91        )?;
92        Ok(())
93    }
94
95    fn consensus_encode_to_hex(&self) -> String {
96        let mut bytes = self.consensus_encode_to_vec();
97
98        // Just Bitcoin things: transaction hashes are encoded reverse
99        bytes.reverse();
100
101        // TODO: remove double-allocation
102        bytes.encode_hex()
103    }
104}
105
106impl crate::encoding::Decodable for bitcoin::Txid {
107    fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
108        d: &mut D,
109        _modules: &::fedimint_core::module::registry::ModuleDecoderRegistry,
110    ) -> Result<Self, crate::encoding::DecodeError> {
111        bitcoin::consensus::Decodable::consensus_decode_from_finite_reader(&mut SimpleBitcoinRead(
112            d,
113        ))
114        .map_err(from_bitcoin_encode_error)
115    }
116
117    fn consensus_decode_hex(
118        hex: &str,
119        modules: &ModuleDecoderRegistry,
120    ) -> Result<Self, DecodeError> {
121        let mut bytes = Vec::<u8>::from_hex(hex).map_err(DecodeError::from_err)?;
122
123        // Just Bitcoin things: transaction hashes are encoded reverse
124        bytes.reverse();
125
126        Decodable::consensus_decode_whole(&bytes, modules)
127    }
128}
129
130impl<K> Encodable for Descriptor<K>
131where
132    K: MiniscriptKey,
133{
134    fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
135        let descriptor_str = self.to_string();
136        descriptor_str.consensus_encode(writer)
137    }
138}
139
140impl<K> Decodable for Descriptor<K>
141where
142    Self: FromStr,
143    <Self as FromStr>::Err: ToString + std::error::Error + Send + Sync + 'static,
144    K: MiniscriptKey,
145{
146    fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
147        d: &mut D,
148        modules: &ModuleDecoderRegistry,
149    ) -> Result<Self, DecodeError> {
150        let descriptor_str = String::consensus_decode_partial_from_finite_reader(d, modules)?;
151        Self::from_str(&descriptor_str).map_err(DecodeError::from_err)
152    }
153}
154
155/// Wrapper around `bitcoin::Network` that encodes and decodes the network as a
156/// little-endian u32. This is here for backwards compatibility and is used by
157/// the LNv1 and WalletV1 modules.
158#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
159pub struct NetworkLegacyEncodingWrapper(pub bitcoin::Network);
160
161impl std::fmt::Display for NetworkLegacyEncodingWrapper {
162    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
163        write!(f, "{}", self.0)
164    }
165}
166
167impl Encodable for NetworkLegacyEncodingWrapper {
168    fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
169        u32::from_le_bytes(self.0.magic().to_bytes()).consensus_encode(writer)
170    }
171}
172
173impl Decodable for NetworkLegacyEncodingWrapper {
174    fn consensus_decode_partial<D: std::io::Read>(
175        d: &mut D,
176        modules: &ModuleDecoderRegistry,
177    ) -> Result<Self, DecodeError> {
178        let num = u32::consensus_decode_partial(d, modules)?;
179        let magic = bitcoin::p2p::Magic::from_bytes(num.to_le_bytes());
180        let network = bitcoin::Network::from_magic(magic)
181            .ok_or_else(|| DecodeError::custom(format!("Unknown network magic: {magic:x}")))?;
182        Ok(Self(network))
183    }
184}
185impl Encodable for bitcoin::Network {
186    fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
187        self.magic().to_bytes().consensus_encode(writer)
188    }
189}
190
191impl Decodable for bitcoin::Network {
192    fn consensus_decode_partial<D: std::io::Read>(
193        d: &mut D,
194        modules: &ModuleDecoderRegistry,
195    ) -> Result<Self, DecodeError> {
196        Self::from_magic(bitcoin::p2p::Magic::from_bytes(
197            Decodable::consensus_decode_partial(d, modules)?,
198        ))
199        .ok_or_else(|| DecodeError::from_str("Unknown network magic"))
200    }
201}
202
203impl Encodable for bitcoin::Amount {
204    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
205        self.to_sat().consensus_encode(writer)
206    }
207}
208
209impl Decodable for bitcoin::Amount {
210    fn consensus_decode_partial<D: std::io::Read>(
211        d: &mut D,
212        modules: &ModuleDecoderRegistry,
213    ) -> Result<Self, DecodeError> {
214        Ok(Self::from_sat(u64::consensus_decode_partial(d, modules)?))
215    }
216}
217
218impl Encodable for bitcoin::Address<NetworkUnchecked> {
219    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
220        NetworkLegacyEncodingWrapper(get_network_for_address(self)).consensus_encode(writer)?;
221        self.clone()
222            // We need an `Address<NetworkChecked>` in order to get the script pubkey.
223            // Calling `assume_checked` is generally a bad idea, but it's safe here where we're
224            // encoding the address because addresses are always decoded as unchecked.
225            .assume_checked()
226            .script_pubkey()
227            .consensus_encode(writer)?;
228        Ok(())
229    }
230}
231
232impl Decodable for bitcoin::Address<NetworkUnchecked> {
233    fn consensus_decode_partial<D: std::io::Read>(
234        mut d: &mut D,
235        modules: &ModuleDecoderRegistry,
236    ) -> Result<Self, DecodeError> {
237        let network = NetworkLegacyEncodingWrapper::consensus_decode_partial(&mut d, modules)?.0;
238        let script_pk = bitcoin::ScriptBuf::consensus_decode_partial(&mut d, modules)?;
239
240        let address =
241            bitcoin::Address::from_script(&script_pk, network).map_err(DecodeError::from_err)?;
242
243        Ok(address.into_unchecked())
244    }
245}
246
247impl Encodable for bitcoin::hashes::sha256::Hash {
248    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
249        self.to_byte_array().consensus_encode(writer)
250    }
251}
252
253impl Decodable for bitcoin::hashes::sha256::Hash {
254    fn consensus_decode_partial<D: std::io::Read>(
255        d: &mut D,
256        modules: &ModuleDecoderRegistry,
257    ) -> Result<Self, DecodeError> {
258        Ok(Self::from_byte_array(Decodable::consensus_decode_partial(
259            d, modules,
260        )?))
261    }
262}
263
264impl Encodable for bitcoin::hashes::hash160::Hash {
265    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
266        self.to_byte_array().consensus_encode(writer)
267    }
268}
269
270impl Decodable for bitcoin::hashes::hash160::Hash {
271    fn consensus_decode_partial<D: std::io::Read>(
272        d: &mut D,
273        modules: &ModuleDecoderRegistry,
274    ) -> Result<Self, DecodeError> {
275        Ok(Self::from_byte_array(Decodable::consensus_decode_partial(
276            d, modules,
277        )?))
278    }
279}
280
281impl Encodable for lightning_invoice::Bolt11Invoice {
282    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
283        self.to_string().consensus_encode(writer)
284    }
285}
286
287impl Decodable for lightning_invoice::Bolt11Invoice {
288    fn consensus_decode_partial<D: std::io::Read>(
289        d: &mut D,
290        modules: &ModuleDecoderRegistry,
291    ) -> Result<Self, DecodeError> {
292        String::consensus_decode_partial(d, modules)?
293            .parse::<Self>()
294            .map_err(DecodeError::from_err)
295    }
296}
297
298impl Encodable for lightning_invoice::RoutingFees {
299    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
300        self.base_msat.consensus_encode(writer)?;
301        self.proportional_millionths.consensus_encode(writer)?;
302        Ok(())
303    }
304}
305
306impl Decodable for lightning_invoice::RoutingFees {
307    fn consensus_decode_partial<D: std::io::Read>(
308        d: &mut D,
309        modules: &ModuleDecoderRegistry,
310    ) -> Result<Self, DecodeError> {
311        let base_msat = Decodable::consensus_decode_partial(d, modules)?;
312        let proportional_millionths = Decodable::consensus_decode_partial(d, modules)?;
313        Ok(Self {
314            base_msat,
315            proportional_millionths,
316        })
317    }
318}
319
320impl Encodable for BigSize {
321    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
322        let mut writer = BitoinIoWriteAdapter::from(writer);
323        self.write(&mut writer)?;
324        Ok(())
325    }
326}
327
328impl Decodable for BigSize {
329    fn consensus_decode_partial<R: std::io::Read>(
330        r: &mut R,
331        _modules: &ModuleDecoderRegistry,
332    ) -> Result<Self, DecodeError> {
333        Self::read(&mut SimpleBitcoinRead(r)).map_err(|error| match error {
334            msgs::DecodeError::ShortRead => {
335                DecodeError::Io(std::io::ErrorKind::UnexpectedEof.into())
336            }
337            msgs::DecodeError::Io(kind) => DecodeError::Io(bitcoin_io::Error::from(kind).into()),
338            other => DecodeError::custom(format!("BigSize decoding error: {other:?}")),
339        })
340    }
341}
342
343// Simple decoder implementing `bitcoin_io::Read` for `std::io::Read`.
344// This is needed because `bitcoin::consensus::Decodable` requires a
345// `bitcoin_io::Read`.
346struct SimpleBitcoinRead<R: std::io::Read>(R);
347
348impl<R: std::io::Read> bitcoin_io::Read for SimpleBitcoinRead<R> {
349    fn read(&mut self, buf: &mut [u8]) -> bitcoin_io::Result<usize> {
350        self.0.read(buf).map_err(bitcoin_io::Error::from)
351    }
352}
353
354/// Wrap buffering support for implementations of Read.
355/// A reader which keeps an internal buffer to avoid hitting the underlying
356/// stream directly for every read.
357///
358/// In order to avoid reading bytes past the first object, and those bytes then
359/// ending up getting dropped, this BufBitcoinReader operates in
360/// one-byte-increments.
361///
362/// This code is vendored from the `lightning` crate:
363/// <https://github.com/lightningdevkit/rust-lightning/blob/5718baaed947fcaa9c60d80cdf309040c0c68489/lightning/src/util/ser.rs#L72-L138>
364struct BufBitcoinReader<'a, R: std::io::Read> {
365    inner: &'a mut R,
366    buf: [u8; 1],
367    is_consumed: bool,
368}
369
370impl<'a, R: std::io::Read> BufBitcoinReader<'a, R> {
371    /// Creates a [`BufBitcoinReader`] which will read from the given `inner`.
372    fn new(inner: &'a mut R) -> Self {
373        BufBitcoinReader {
374            inner,
375            buf: [0; 1],
376            is_consumed: true,
377        }
378    }
379}
380
381impl<R: std::io::Read> bitcoin_io::Read for BufBitcoinReader<'_, R> {
382    #[inline]
383    fn read(&mut self, output: &mut [u8]) -> bitcoin_io::Result<usize> {
384        if output.is_empty() {
385            return Ok(0);
386        }
387        #[allow(clippy::useless_let_if_seq)]
388        let mut offset = 0;
389        if !self.is_consumed {
390            output[0] = self.buf[0];
391            self.is_consumed = true;
392            offset = 1;
393        }
394        Ok(self
395            .inner
396            .read(&mut output[offset..])
397            .map(|len| len + offset)?)
398    }
399}
400
401impl<R: std::io::Read> bitcoin_io::BufRead for BufBitcoinReader<'_, R> {
402    #[inline]
403    fn fill_buf(&mut self) -> bitcoin_io::Result<&[u8]> {
404        debug_assert!(false, "rust-bitcoin doesn't actually use this");
405        if self.is_consumed {
406            let count = self.inner.read(&mut self.buf[..])?;
407            debug_assert!(count <= 1, "read gave us a garbage length");
408
409            // upon hitting EOF, assume the byte is already consumed
410            self.is_consumed = count == 0;
411        }
412
413        if self.is_consumed {
414            Ok(&[])
415        } else {
416            Ok(&self.buf[..])
417        }
418    }
419
420    #[inline]
421    fn consume(&mut self, amount: usize) {
422        debug_assert!(false, "rust-bitcoin doesn't actually use this");
423        if amount >= 1 {
424            debug_assert_eq!(amount, 1, "Can only consume one byte");
425            debug_assert!(!self.is_consumed, "Cannot consume more than had been read");
426            self.is_consumed = true;
427        }
428    }
429}
430
431/// A writer counting number of bytes written to it
432///
433/// Copy&pasted from <https://github.com/SOF3/count-write> which
434/// uses Apache license (and it's a trivial amount of code, repeating
435/// on stack overflow).
436pub struct BitoinIoWriteAdapter<W> {
437    inner: W,
438}
439
440impl<W> From<W> for BitoinIoWriteAdapter<W> {
441    fn from(inner: W) -> Self {
442        Self { inner }
443    }
444}
445
446impl<W: Write> bitcoin_io::Write for BitoinIoWriteAdapter<W> {
447    fn write(&mut self, buf: &[u8]) -> bitcoin_io::Result<usize> {
448        let written = self.inner.write(buf)?;
449        Ok(written)
450    }
451
452    fn flush(&mut self) -> bitcoin_io::Result<()> {
453        self.inner.flush().map_err(bitcoin_io::Error::from)
454    }
455}
456
457#[cfg(test)]
458mod tests {
459    use std::str::FromStr;
460
461    use bitcoin::hashes::Hash as BitcoinHash;
462    use hex::FromHex;
463
464    use crate::ModuleDecoderRegistry;
465    use crate::db::DatabaseValue;
466    use crate::encoding::btc::NetworkLegacyEncodingWrapper;
467    use crate::encoding::tests::{test_roundtrip, test_roundtrip_expected};
468    use crate::encoding::{Decodable, DecodeError, Encodable};
469
470    #[test_log::test]
471    fn block_hash_roundtrip() {
472        let blockhash = bitcoin::BlockHash::from_str(
473            "0000000000000000000065bda8f8a88f2e1e00d9a6887a43d640e52a4c7660f2",
474        )
475        .unwrap();
476        test_roundtrip_expected(
477            &blockhash,
478            &[
479                242, 96, 118, 76, 42, 229, 64, 214, 67, 122, 136, 166, 217, 0, 30, 46, 143, 168,
480                248, 168, 189, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
481            ],
482        );
483    }
484
485    #[test_log::test]
486    fn tx_roundtrip() {
487        let transaction: Vec<u8> = FromHex::from_hex(
488            "02000000000101d35b66c54cf6c09b81a8d94cd5d179719cd7595c258449452a9305ab9b12df250200000000fdffffff020cd50a0000000000160014ae5d450b71c04218e6e81c86fcc225882d7b7caae695b22100000000160014f60834ef165253c571b11ce9fa74e46692fc5ec10248304502210092062c609f4c8dc74cd7d4596ecedc1093140d90b3fd94b4bdd9ad3e102ce3bc02206bb5a6afc68d583d77d5d9bcfb6252a364d11a307f3418be1af9f47f7b1b3d780121026e5628506ecd33242e5ceb5fdafe4d3066b5c0f159b3c05a621ef65f177ea28600000000"
489        ).unwrap();
490        let transaction =
491            bitcoin::Transaction::from_bytes(&transaction, &ModuleDecoderRegistry::default())
492                .unwrap();
493        test_roundtrip_expected(
494            &transaction,
495            &[
496                2, 0, 0, 0, 0, 1, 1, 211, 91, 102, 197, 76, 246, 192, 155, 129, 168, 217, 76, 213,
497                209, 121, 113, 156, 215, 89, 92, 37, 132, 73, 69, 42, 147, 5, 171, 155, 18, 223,
498                37, 2, 0, 0, 0, 0, 253, 255, 255, 255, 2, 12, 213, 10, 0, 0, 0, 0, 0, 22, 0, 20,
499                174, 93, 69, 11, 113, 192, 66, 24, 230, 232, 28, 134, 252, 194, 37, 136, 45, 123,
500                124, 170, 230, 149, 178, 33, 0, 0, 0, 0, 22, 0, 20, 246, 8, 52, 239, 22, 82, 83,
501                197, 113, 177, 28, 233, 250, 116, 228, 102, 146, 252, 94, 193, 2, 72, 48, 69, 2,
502                33, 0, 146, 6, 44, 96, 159, 76, 141, 199, 76, 215, 212, 89, 110, 206, 220, 16, 147,
503                20, 13, 144, 179, 253, 148, 180, 189, 217, 173, 62, 16, 44, 227, 188, 2, 32, 107,
504                181, 166, 175, 198, 141, 88, 61, 119, 213, 217, 188, 251, 98, 82, 163, 100, 209,
505                26, 48, 127, 52, 24, 190, 26, 249, 244, 127, 123, 27, 61, 120, 1, 33, 2, 110, 86,
506                40, 80, 110, 205, 51, 36, 46, 92, 235, 95, 218, 254, 77, 48, 102, 181, 192, 241,
507                89, 179, 192, 90, 98, 30, 246, 95, 23, 126, 162, 134, 0, 0, 0, 0,
508            ],
509        );
510    }
511
512    #[test_log::test]
513    fn txid_roundtrip() {
514        let txid = bitcoin::Txid::from_str(
515            "51f7ed2f23e58cc6e139e715e9ce304a1e858416edc9079dd7b74fa8d2efc09a",
516        )
517        .unwrap();
518        test_roundtrip_expected(
519            &txid,
520            &[
521                154, 192, 239, 210, 168, 79, 183, 215, 157, 7, 201, 237, 22, 132, 133, 30, 74, 48,
522                206, 233, 21, 231, 57, 225, 198, 140, 229, 35, 47, 237, 247, 81,
523            ],
524        );
525    }
526
527    #[test_log::test]
528    fn network_roundtrip() {
529        let networks: [(bitcoin::Network, [u8; 5], [u8; 4]); 5] = [
530            (
531                bitcoin::Network::Bitcoin,
532                [0xFE, 0xD9, 0xB4, 0xBE, 0xF9],
533                [0xF9, 0xBE, 0xB4, 0xD9],
534            ),
535            (
536                bitcoin::Network::Testnet,
537                [0xFE, 0x07, 0x09, 0x11, 0x0B],
538                [0x0B, 0x11, 0x09, 0x07],
539            ),
540            (
541                bitcoin::Network::Testnet4,
542                [0xFE, 0x28, 0x3F, 0x16, 0x1C],
543                [0x1C, 0x16, 0x3F, 0x28],
544            ),
545            (
546                bitcoin::Network::Signet,
547                [0xFE, 0x40, 0xCF, 0x03, 0x0A],
548                [0x0A, 0x03, 0xCF, 0x40],
549            ),
550            (
551                bitcoin::Network::Regtest,
552                [0xFE, 0xDA, 0xB5, 0xBF, 0xFA],
553                [0xFA, 0xBF, 0xB5, 0xDA],
554            ),
555        ];
556
557        for (network, magic_legacy_bytes, magic_bytes) in networks {
558            let network_legacy_encoded =
559                NetworkLegacyEncodingWrapper(network).consensus_encode_to_vec();
560
561            let network_encoded = network.consensus_encode_to_vec();
562
563            let network_legacy_decoded = NetworkLegacyEncodingWrapper::consensus_decode_whole(
564                &network_legacy_encoded,
565                &ModuleDecoderRegistry::default(),
566            )
567            .unwrap()
568            .0;
569
570            let network_decoded = bitcoin::Network::consensus_decode_whole(
571                &network_encoded,
572                &ModuleDecoderRegistry::default(),
573            )
574            .unwrap();
575
576            assert_eq!(magic_legacy_bytes, *network_legacy_encoded);
577            assert_eq!(magic_bytes, *network_encoded);
578            assert_eq!(network, network_legacy_decoded);
579            assert_eq!(network, network_decoded);
580        }
581    }
582
583    #[test_log::test]
584    fn address_roundtrip() {
585        let addresses = [
586            "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5",
587            "mxMYaq5yWinZ9AKjCDcBEbiEwPJD9n2uLU",
588            "1FK8o7mUxyd6QWJAUw7J4vW7eRxuyjj6Ne",
589            "3JSrSU7z7R1Yhh26pt1zzRjQz44qjcrXwb",
590            "tb1qunn0thpt8uk3yk2938ypjccn3urxprt78z9ccq",
591            "2MvUMRv2DRHZi3VshkP7RMEU84mVTfR9xjq",
592        ];
593
594        for address_str in addresses {
595            let address =
596                bitcoin::Address::from_str(address_str).expect("All tested addresses are valid");
597            let encoding = address.consensus_encode_to_vec();
598            let parsed_address = bitcoin::Address::consensus_decode_whole(
599                &encoding,
600                &ModuleDecoderRegistry::default(),
601            )
602            .expect("Decoding address failed");
603
604            assert_eq!(address, parsed_address);
605        }
606    }
607
608    #[test_log::test]
609    fn sha256_roundtrip() {
610        test_roundtrip_expected(
611            &bitcoin::hashes::sha256::Hash::hash(b"Hello world!"),
612            &[
613                192, 83, 94, 75, 226, 183, 159, 253, 147, 41, 19, 5, 67, 107, 248, 137, 49, 78, 74,
614                63, 174, 192, 94, 207, 252, 187, 125, 243, 26, 217, 229, 26,
615            ],
616        );
617    }
618
619    #[test_log::test]
620    fn bolt11_invoice_roundtrip() {
621        let invoice_str = "lnbc100p1psj9jhxdqud3jxktt5w46x7unfv9kz6mn0v3jsnp4q0d3p2sfluzdx45tqcs\
622			h2pu5qc7lgq0xs578ngs6s0s68ua4h7cvspp5q6rmq35js88zp5dvwrv9m459tnk2zunwj5jalqtyxqulh0l\
623			5gflssp5nf55ny5gcrfl30xuhzj3nphgj27rstekmr9fw3ny5989s300gyus9qyysgqcqpcrzjqw2sxwe993\
624			h5pcm4dxzpvttgza8zhkqxpgffcrf5v25nwpr3cmfg7z54kuqq8rgqqqqqqqq2qqqqq9qq9qrzjqd0ylaqcl\
625			j9424x9m8h2vcukcgnm6s56xfgu3j78zyqzhgs4hlpzvznlugqq9vsqqqqqqqlgqqqqqeqq9qrzjqwldmj9d\
626			ha74df76zhx6l9we0vjdquygcdt3kssupehe64g6yyp5yz5rhuqqwccqqyqqqqlgqqqqjcqq9qrzjqf9e58a\
627			guqr0rcun0ajlvmzq3ek63cw2w282gv3z5uupmuwvgjtq2z55qsqqg6qqqyqqqrtnqqqzq3cqygrzjqvphms\
628			ywntrrhqjcraumvc4y6r8v4z5v593trte429v4hredj7ms5z52usqq9ngqqqqqqqlgqqqqqqgq9qrzjq2v0v\
629			p62g49p7569ev48cmulecsxe59lvaw3wlxm7r982zxa9zzj7z5l0cqqxusqqyqqqqlgqqqqqzsqygarl9fh3\
630			8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
631			j5r6drg6k6zcqj0fcwg";
632        let invoice = invoice_str
633            .parse::<lightning_invoice::Bolt11Invoice>()
634            .unwrap();
635        test_roundtrip(&invoice);
636    }
637
638    #[test_log::test]
639    fn truncated_outpoint_is_an_io_error() {
640        let outpoint = bitcoin::OutPoint {
641            txid: bitcoin::Txid::from_str(
642                "51f7ed2f23e58cc6e139e715e9ce304a1e858416edc9079dd7b74fa8d2efc09a",
643            )
644            .unwrap(),
645            vout: 0,
646        };
647        let mut encoded = outpoint.consensus_encode_to_vec();
648        encoded.truncate(encoded.len() - 1);
649
650        let err =
651            bitcoin::OutPoint::consensus_decode_whole(&encoded, &ModuleDecoderRegistry::default())
652                .expect_err("35 of 36 bytes are not a full outpoint");
653        assert!(matches!(err, DecodeError::Io(_)), "{err:?}");
654    }
655
656    #[test_log::test]
657    fn empty_psbt_input_is_an_io_error() {
658        // The PSBT magic is read through rust-bitcoin's consensus decoder, so a short
659        // read arrives wrapped in `ConsensusEncoding` rather than as a
660        // top-level `Io`.
661        let err =
662            bitcoin::psbt::Psbt::consensus_decode_whole(&[], &ModuleDecoderRegistry::default())
663                .expect_err("empty input is not a psbt");
664        assert!(matches!(err, DecodeError::Io(_)), "{err:?}");
665    }
666}