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