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
33fn consensus_encode_with_buffer<T: bitcoin::consensus::Encodable, W: std::io::Write>(
34    value: &T,
35    writer: &mut W,
36) -> Result<(), std::io::Error> {
37    let mut buffered_writer = std::io::BufWriter::new(writer);
38    bitcoin::consensus::Encodable::consensus_encode(value, &mut buffered_writer)?;
39    buffered_writer
40        .into_inner()
41        .map_err(std::io::IntoInnerError::into_error)?;
42    Ok(())
43}
44
45macro_rules! impl_encode_decode_bridge {
46    ($btc_type:ty) => {
47        impl crate::encoding::Encodable for $btc_type {
48            fn consensus_encode<W: std::io::Write>(
49                &self,
50                writer: &mut W,
51            ) -> Result<(), std::io::Error> {
52                consensus_encode_with_buffer(self, writer)
53            }
54        }
55
56        impl crate::encoding::Decodable for $btc_type {
57            fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
58                d: &mut D,
59                _modules: &$crate::module::registry::ModuleDecoderRegistry,
60            ) -> Result<Self, crate::encoding::DecodeError> {
61                bitcoin::consensus::Decodable::consensus_decode_from_finite_reader(
62                    &mut SimpleBitcoinRead(d),
63                )
64                .map_err(from_bitcoin_encode_error)
65            }
66        }
67    };
68}
69
70impl_encode_decode_bridge!(bitcoin::block::Header);
71impl_encode_decode_bridge!(bitcoin::BlockHash);
72impl_encode_decode_bridge!(bitcoin::OutPoint);
73impl_encode_decode_bridge!(bitcoin::TxOut);
74impl_encode_decode_bridge!(bitcoin::ScriptBuf);
75impl_encode_decode_bridge!(bitcoin::Transaction);
76impl_encode_decode_bridge!(bitcoin::merkle_tree::PartialMerkleTree);
77
78impl crate::encoding::Encodable for bitcoin::psbt::Psbt {
79    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
80        self.serialize_to_writer(&mut BitoinIoWriteAdapter::from(writer))?;
81        Ok(())
82    }
83}
84
85impl crate::encoding::Decodable for bitcoin::psbt::Psbt {
86    fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
87        d: &mut D,
88        _modules: &ModuleDecoderRegistry,
89    ) -> Result<Self, crate::encoding::DecodeError> {
90        Self::deserialize_from_reader(&mut BufBitcoinReader::new(d)).map_err(from_psbt_error)
91    }
92}
93
94impl crate::encoding::Encodable for bitcoin::Txid {
95    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
96        consensus_encode_with_buffer(self, writer)
97    }
98
99    fn consensus_encode_to_hex(&self) -> String {
100        let mut bytes = self.consensus_encode_to_vec();
101
102        // Just Bitcoin things: transaction hashes are encoded reverse
103        bytes.reverse();
104
105        // TODO: remove double-allocation
106        bytes.encode_hex()
107    }
108}
109
110impl crate::encoding::Decodable for bitcoin::Txid {
111    fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
112        d: &mut D,
113        _modules: &::fedimint_core::module::registry::ModuleDecoderRegistry,
114    ) -> Result<Self, crate::encoding::DecodeError> {
115        bitcoin::consensus::Decodable::consensus_decode_from_finite_reader(&mut SimpleBitcoinRead(
116            d,
117        ))
118        .map_err(from_bitcoin_encode_error)
119    }
120
121    fn consensus_decode_hex(
122        hex: &str,
123        modules: &ModuleDecoderRegistry,
124    ) -> Result<Self, DecodeError> {
125        let mut bytes = Vec::<u8>::from_hex(hex).map_err(DecodeError::from_err)?;
126
127        // Just Bitcoin things: transaction hashes are encoded reverse
128        bytes.reverse();
129
130        Decodable::consensus_decode_whole(&bytes, modules)
131    }
132}
133
134impl<K> Encodable for Descriptor<K>
135where
136    K: MiniscriptKey,
137{
138    fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
139        let descriptor_str = self.to_string();
140        descriptor_str.consensus_encode(writer)
141    }
142}
143
144impl<K> Decodable for Descriptor<K>
145where
146    Self: FromStr,
147    <Self as FromStr>::Err: ToString + std::error::Error + Send + Sync + 'static,
148    K: MiniscriptKey,
149{
150    fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
151        d: &mut D,
152        modules: &ModuleDecoderRegistry,
153    ) -> Result<Self, DecodeError> {
154        let descriptor_str = String::consensus_decode_partial_from_finite_reader(d, modules)?;
155        Self::from_str(&descriptor_str).map_err(DecodeError::from_err)
156    }
157}
158
159/// Wrapper around `bitcoin::Network` that encodes and decodes the network as a
160/// little-endian u32. This is here for backwards compatibility and is used by
161/// the LNv1 and WalletV1 modules.
162#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
163pub struct NetworkLegacyEncodingWrapper(pub bitcoin::Network);
164
165impl std::fmt::Display for NetworkLegacyEncodingWrapper {
166    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
167        write!(f, "{}", self.0)
168    }
169}
170
171impl Encodable for NetworkLegacyEncodingWrapper {
172    fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
173        u32::from_le_bytes(self.0.magic().to_bytes()).consensus_encode(writer)
174    }
175}
176
177impl Decodable for NetworkLegacyEncodingWrapper {
178    fn consensus_decode_partial<D: std::io::Read>(
179        d: &mut D,
180        modules: &ModuleDecoderRegistry,
181    ) -> Result<Self, DecodeError> {
182        let num = u32::consensus_decode_partial(d, modules)?;
183        let magic = bitcoin::p2p::Magic::from_bytes(num.to_le_bytes());
184        let network = bitcoin::Network::from_magic(magic)
185            .ok_or_else(|| DecodeError::custom(format!("Unknown network magic: {magic:x}")))?;
186        Ok(Self(network))
187    }
188}
189impl Encodable for bitcoin::Network {
190    fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
191        self.magic().to_bytes().consensus_encode(writer)
192    }
193}
194
195impl Decodable for bitcoin::Network {
196    fn consensus_decode_partial<D: std::io::Read>(
197        d: &mut D,
198        modules: &ModuleDecoderRegistry,
199    ) -> Result<Self, DecodeError> {
200        Self::from_magic(bitcoin::p2p::Magic::from_bytes(
201            Decodable::consensus_decode_partial(d, modules)?,
202        ))
203        .ok_or_else(|| DecodeError::from_str("Unknown network magic"))
204    }
205}
206
207impl Encodable for bitcoin::Amount {
208    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
209        self.to_sat().consensus_encode(writer)
210    }
211}
212
213impl Decodable for bitcoin::Amount {
214    fn consensus_decode_partial<D: std::io::Read>(
215        d: &mut D,
216        modules: &ModuleDecoderRegistry,
217    ) -> Result<Self, DecodeError> {
218        Ok(Self::from_sat(u64::consensus_decode_partial(d, modules)?))
219    }
220}
221
222impl Encodable for bitcoin::Address<NetworkUnchecked> {
223    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
224        NetworkLegacyEncodingWrapper(get_network_for_address(self)).consensus_encode(writer)?;
225        self.clone()
226            // We need an `Address<NetworkChecked>` in order to get the script pubkey.
227            // Calling `assume_checked` is generally a bad idea, but it's safe here where we're
228            // encoding the address because addresses are always decoded as unchecked.
229            .assume_checked()
230            .script_pubkey()
231            .consensus_encode(writer)?;
232        Ok(())
233    }
234}
235
236impl Decodable for bitcoin::Address<NetworkUnchecked> {
237    fn consensus_decode_partial<D: std::io::Read>(
238        mut d: &mut D,
239        modules: &ModuleDecoderRegistry,
240    ) -> Result<Self, DecodeError> {
241        let network = NetworkLegacyEncodingWrapper::consensus_decode_partial(&mut d, modules)?.0;
242        let script_pk = bitcoin::ScriptBuf::consensus_decode_partial(&mut d, modules)?;
243
244        let address =
245            bitcoin::Address::from_script(&script_pk, network).map_err(DecodeError::from_err)?;
246
247        Ok(address.into_unchecked())
248    }
249}
250
251impl Encodable for bitcoin::hashes::sha256::Hash {
252    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
253        self.to_byte_array().consensus_encode(writer)
254    }
255}
256
257impl Decodable for bitcoin::hashes::sha256::Hash {
258    fn consensus_decode_partial<D: std::io::Read>(
259        d: &mut D,
260        modules: &ModuleDecoderRegistry,
261    ) -> Result<Self, DecodeError> {
262        Ok(Self::from_byte_array(Decodable::consensus_decode_partial(
263            d, modules,
264        )?))
265    }
266}
267
268impl Encodable for bitcoin::hashes::hash160::Hash {
269    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
270        self.to_byte_array().consensus_encode(writer)
271    }
272}
273
274impl Decodable for bitcoin::hashes::hash160::Hash {
275    fn consensus_decode_partial<D: std::io::Read>(
276        d: &mut D,
277        modules: &ModuleDecoderRegistry,
278    ) -> Result<Self, DecodeError> {
279        Ok(Self::from_byte_array(Decodable::consensus_decode_partial(
280            d, modules,
281        )?))
282    }
283}
284
285impl Encodable for lightning_invoice::Bolt11Invoice {
286    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
287        self.to_string().consensus_encode(writer)
288    }
289}
290
291impl Decodable for lightning_invoice::Bolt11Invoice {
292    fn consensus_decode_partial<D: std::io::Read>(
293        d: &mut D,
294        modules: &ModuleDecoderRegistry,
295    ) -> Result<Self, DecodeError> {
296        String::consensus_decode_partial(d, modules)?
297            .parse::<Self>()
298            .map_err(DecodeError::from_err)
299    }
300}
301
302impl Encodable for lightning_invoice::RoutingFees {
303    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
304        self.base_msat.consensus_encode(writer)?;
305        self.proportional_millionths.consensus_encode(writer)?;
306        Ok(())
307    }
308}
309
310impl Decodable for lightning_invoice::RoutingFees {
311    fn consensus_decode_partial<D: std::io::Read>(
312        d: &mut D,
313        modules: &ModuleDecoderRegistry,
314    ) -> Result<Self, DecodeError> {
315        let base_msat = Decodable::consensus_decode_partial(d, modules)?;
316        let proportional_millionths = Decodable::consensus_decode_partial(d, modules)?;
317        Ok(Self {
318            base_msat,
319            proportional_millionths,
320        })
321    }
322}
323
324impl Encodable for BigSize {
325    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
326        let mut writer = BitoinIoWriteAdapter::from(writer);
327        self.write(&mut writer)?;
328        Ok(())
329    }
330}
331
332impl Decodable for BigSize {
333    fn consensus_decode_partial<R: std::io::Read>(
334        r: &mut R,
335        _modules: &ModuleDecoderRegistry,
336    ) -> Result<Self, DecodeError> {
337        Self::read(&mut SimpleBitcoinRead(r)).map_err(|error| match error {
338            msgs::DecodeError::ShortRead => {
339                DecodeError::Io(std::io::ErrorKind::UnexpectedEof.into())
340            }
341            msgs::DecodeError::Io(kind) => DecodeError::Io(bitcoin_io::Error::from(kind).into()),
342            other => DecodeError::custom(format!("BigSize decoding error: {other:?}")),
343        })
344    }
345}
346
347// Simple decoder implementing `bitcoin_io::Read` for `std::io::Read`.
348// This is needed because `bitcoin::consensus::Decodable` requires a
349// `bitcoin_io::Read`.
350struct SimpleBitcoinRead<R: std::io::Read>(R);
351
352impl<R: std::io::Read> bitcoin_io::Read for SimpleBitcoinRead<R> {
353    fn read(&mut self, buf: &mut [u8]) -> bitcoin_io::Result<usize> {
354        self.0.read(buf).map_err(bitcoin_io::Error::from)
355    }
356}
357
358/// Wrap buffering support for implementations of Read.
359/// A reader which keeps an internal buffer to avoid hitting the underlying
360/// stream directly for every read.
361///
362/// In order to avoid reading bytes past the first object, and those bytes then
363/// ending up getting dropped, this BufBitcoinReader operates in
364/// one-byte-increments.
365///
366/// This code is vendored from the `lightning` crate:
367/// <https://github.com/lightningdevkit/rust-lightning/blob/5718baaed947fcaa9c60d80cdf309040c0c68489/lightning/src/util/ser.rs#L72-L138>
368struct BufBitcoinReader<'a, R: std::io::Read> {
369    inner: &'a mut R,
370    buf: [u8; 1],
371    is_consumed: bool,
372}
373
374impl<'a, R: std::io::Read> BufBitcoinReader<'a, R> {
375    /// Creates a [`BufBitcoinReader`] which will read from the given `inner`.
376    fn new(inner: &'a mut R) -> Self {
377        BufBitcoinReader {
378            inner,
379            buf: [0; 1],
380            is_consumed: true,
381        }
382    }
383}
384
385impl<R: std::io::Read> bitcoin_io::Read for BufBitcoinReader<'_, R> {
386    #[inline]
387    fn read(&mut self, output: &mut [u8]) -> bitcoin_io::Result<usize> {
388        if output.is_empty() {
389            return Ok(0);
390        }
391        #[allow(clippy::useless_let_if_seq)]
392        let mut offset = 0;
393        if !self.is_consumed {
394            output[0] = self.buf[0];
395            self.is_consumed = true;
396            offset = 1;
397        }
398        Ok(self
399            .inner
400            .read(&mut output[offset..])
401            .map(|len| len + offset)?)
402    }
403}
404
405impl<R: std::io::Read> bitcoin_io::BufRead for BufBitcoinReader<'_, R> {
406    #[inline]
407    fn fill_buf(&mut self) -> bitcoin_io::Result<&[u8]> {
408        debug_assert!(false, "rust-bitcoin doesn't actually use this");
409        if self.is_consumed {
410            let count = self.inner.read(&mut self.buf[..])?;
411            debug_assert!(count <= 1, "read gave us a garbage length");
412
413            // upon hitting EOF, assume the byte is already consumed
414            self.is_consumed = count == 0;
415        }
416
417        if self.is_consumed {
418            Ok(&[])
419        } else {
420            Ok(&self.buf[..])
421        }
422    }
423
424    #[inline]
425    fn consume(&mut self, amount: usize) {
426        debug_assert!(false, "rust-bitcoin doesn't actually use this");
427        if amount >= 1 {
428            debug_assert_eq!(amount, 1, "Can only consume one byte");
429            debug_assert!(!self.is_consumed, "Cannot consume more than had been read");
430            self.is_consumed = true;
431        }
432    }
433}
434
435/// A writer counting number of bytes written to it
436///
437/// Copy&pasted from <https://github.com/SOF3/count-write> which
438/// uses Apache license (and it's a trivial amount of code, repeating
439/// on stack overflow).
440pub struct BitoinIoWriteAdapter<W> {
441    inner: W,
442}
443
444impl<W> From<W> for BitoinIoWriteAdapter<W> {
445    fn from(inner: W) -> Self {
446        Self { inner }
447    }
448}
449
450impl<W: Write> bitcoin_io::Write for BitoinIoWriteAdapter<W> {
451    fn write(&mut self, buf: &[u8]) -> bitcoin_io::Result<usize> {
452        let written = self.inner.write(buf)?;
453        Ok(written)
454    }
455
456    fn flush(&mut self) -> bitcoin_io::Result<()> {
457        self.inner.flush().map_err(bitcoin_io::Error::from)
458    }
459}
460
461#[cfg(test)]
462mod tests;