coins_bip32/
enc.rs

1use coins_core::hashes::{Digest, Hash256};
2use k256::ecdsa;
3use std::marker::PhantomData;
4
5use crate::{
6    primitives::{ChainCode, Hint, KeyFingerprint, XKeyInfo},
7    xkeys::{XPriv, XPub},
8    Bip32Error,
9};
10
11/// Decode a bytevector from a base58 check string
12pub fn decode_b58_check(s: &str) -> Result<Vec<u8>, Bip32Error> {
13    let data: Vec<u8> = bs58::decode(s).into_vec()?;
14    let idx = data.len() - 4;
15    let payload = &data[..idx];
16    let checksum = &data[idx..];
17
18    let digest = &Hash256::digest(payload);
19
20    let mut expected = [0u8; 4];
21    expected.copy_from_slice(&digest.as_slice()[..4]);
22    if expected != checksum {
23        Err(Bip32Error::BadB58Checksum)
24    } else {
25        Ok(payload.to_vec())
26    }
27}
28
29/// Encode a vec into a base58 check String
30pub fn encode_b58_check(v: &[u8]) -> String {
31    let digest = &Hash256::digest(v);
32
33    let mut checksum = [0u8; 4];
34    checksum.copy_from_slice(&digest.as_slice()[..4]);
35
36    let mut data = v.to_vec();
37    data.extend(checksum);
38
39    bs58::encode(data).into_string()
40}
41
42/// Contains network-specific serialization information
43pub trait NetworkParams {
44    /// The Bip32 privkey version bytes
45    const PRIV_VERSION: u32;
46    /// The Bip49 privkey version bytes
47    const BIP49_PRIV_VERSION: u32;
48    /// The Bip84 pubkey version bytes
49    const BIP84_PRIV_VERSION: u32;
50    /// The Bip32 pubkey version bytes
51    const PUB_VERSION: u32;
52    /// The Bip49 pubkey version bytes
53    const BIP49_PUB_VERSION: u32;
54    /// The Bip84 pubkey version bytes
55    const BIP84_PUB_VERSION: u32;
56}
57
58params!(
59    /// Mainnet encoding param
60    Main {
61        bip32: 0x0488_ADE4,
62        bip49: 0x049d_7878,
63        bip84: 0x04b2_430c,
64        bip32_pub: 0x0488_B21E,
65        bip49_pub: 0x049d_7cb2,
66        bip84_pub: 0x04b2_4746
67    }
68);
69
70params!(
71    /// Testnet encoding param
72    Test {
73        bip32: 0x0435_8394,
74        bip49: 0x044a_4e28,
75        bip84: 0x045f_18bc,
76        bip32_pub: 0x0435_87CF,
77        bip49_pub: 0x044a_5262,
78        bip84_pub: 0x045f_1cf6
79    }
80);
81
82/// Parameterizable Bitcoin encoder
83#[derive(Debug, Clone)]
84pub struct BitcoinEncoder<P: NetworkParams>(PhantomData<fn(P) -> P>);
85
86/// XKeyEncoder for Mainnet xkeys
87pub type MainnetEncoder = BitcoinEncoder<Main>;
88/// XKeyEncoder for Testnet xkeys
89pub type TestnetEncoder = BitcoinEncoder<Test>;
90
91/// Bip32/49/84 encoder
92pub trait XKeyEncoder {
93    #[doc(hidden)]
94    fn write_key_details<K, W>(writer: &mut W, key: &K) -> Result<usize, Bip32Error>
95    where
96        K: AsRef<XKeyInfo>,
97        W: std::io::Write,
98    {
99        let key = key.as_ref();
100        let mut written = writer.write(&[key.depth])?;
101        written += writer.write(&key.parent.0)?;
102        written += writer.write(&key.index.to_be_bytes())?;
103        written += writer.write(&key.chain_code.0)?;
104        Ok(written)
105    }
106
107    /// Serialize the xpub to `std::io::Write`
108    fn write_xpub<W, K>(writer: &mut W, key: &K) -> Result<usize, Bip32Error>
109    where
110        W: std::io::Write,
111        K: AsRef<XPub>;
112
113    /// Serialize the xpriv to `std::io::Write`
114    fn write_xpriv<W, K>(writer: &mut W, key: &K) -> Result<usize, Bip32Error>
115    where
116        W: std::io::Write,
117        K: AsRef<XPriv>;
118
119    #[doc(hidden)]
120    fn read_depth<R>(reader: &mut R) -> Result<u8, Bip32Error>
121    where
122        R: std::io::Read,
123    {
124        let mut buf = [0u8; 1];
125        reader.read_exact(&mut buf)?;
126        Ok(buf[0])
127    }
128
129    #[doc(hidden)]
130    fn read_parent<R>(reader: &mut R) -> Result<KeyFingerprint, Bip32Error>
131    where
132        R: std::io::Read,
133    {
134        let mut buf = [0u8; 4];
135        reader.read_exact(&mut buf)?;
136        Ok(buf.into())
137    }
138
139    #[doc(hidden)]
140    fn read_index<R>(reader: &mut R) -> Result<u32, Bip32Error>
141    where
142        R: std::io::Read,
143    {
144        let mut buf = [0u8; 4];
145        reader.read_exact(&mut buf)?;
146        Ok(u32::from_be_bytes(buf))
147    }
148
149    #[doc(hidden)]
150    fn read_chain_code<R>(reader: &mut R) -> Result<ChainCode, Bip32Error>
151    where
152        R: std::io::Read,
153    {
154        let mut buf = [0u8; 32];
155        reader.read_exact(&mut buf)?;
156        Ok(buf.into())
157    }
158
159    #[doc(hidden)]
160    fn read_xpriv_body<R>(reader: &mut R, hint: Hint) -> Result<XPriv, Bip32Error>
161    where
162        R: std::io::Read,
163    {
164        let depth = Self::read_depth(reader)?;
165        let parent = Self::read_parent(reader)?;
166        let index = Self::read_index(reader)?;
167        let chain_code = Self::read_chain_code(reader)?;
168
169        let mut buf = [0u8];
170        reader.read_exact(&mut buf)?;
171        if buf != [0] {
172            return Err(Bip32Error::BadPadding(buf[0]));
173        }
174
175        let mut buf = [0u8; 32];
176        reader.read_exact(&mut buf)?;
177        let key = ecdsa::SigningKey::from_bytes(&buf.into())?;
178
179        Ok(XPriv {
180            key,
181            xkey_info: XKeyInfo {
182                depth,
183                parent,
184                index,
185                chain_code,
186                hint,
187            },
188        })
189    }
190
191    #[doc(hidden)]
192    // Can be used for unhealthy but sometimes-desiable behavior. E.g. accepting an xpriv from any
193    // network.
194    fn read_xpriv_without_network<R>(reader: &mut R) -> Result<XPriv, Bip32Error>
195    where
196        R: std::io::Read,
197    {
198        let mut buf = [0u8; 4];
199        reader.read_exact(&mut buf)?;
200
201        Self::read_xpriv_body(reader, Hint::Legacy)
202    }
203
204    /// Attempt to instantiate an `XPriv` from a `std::io::Read`
205    ///
206    /// ```
207    /// use coins_bip32::{Bip32Error, xkeys::XPriv, enc::{XKeyEncoder, MainnetEncoder}};
208    /// # fn main() -> Result<(), Bip32Error> {
209    /// let xpriv_str = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi".to_owned();
210    ///
211    /// let xpriv: XPriv = MainnetEncoder::xpriv_from_base58(&xpriv_str)?;
212    /// # Ok(())
213    /// # }
214    /// ```
215    fn read_xpriv<R>(reader: &mut R) -> Result<XPriv, Bip32Error>
216    where
217        R: std::io::Read;
218
219    #[doc(hidden)]
220    fn read_xpub_body<R>(reader: &mut R, hint: Hint) -> Result<XPub, Bip32Error>
221    where
222        R: std::io::Read,
223    {
224        let depth = Self::read_depth(reader)?;
225        let parent = Self::read_parent(reader)?;
226        let index = Self::read_index(reader)?;
227        let chain_code = Self::read_chain_code(reader)?;
228
229        let mut buf = [0u8; 33];
230        reader.read_exact(&mut buf)?;
231        let key = ecdsa::VerifyingKey::from_sec1_bytes(&buf)?;
232
233        Ok(XPub {
234            key,
235            xkey_info: XKeyInfo {
236                depth,
237                parent,
238                index,
239                chain_code,
240                hint,
241            },
242        })
243    }
244
245    #[doc(hidden)]
246    // Can be used for unhealthy but sometimes-desiable behavior. E.g. accepting an xpub from any
247    // network.
248    fn read_xpub_without_network<R>(reader: &mut R) -> Result<XPub, Bip32Error>
249    where
250        R: std::io::Read,
251    {
252        let mut buf = [0u8; 4];
253        reader.read_exact(&mut buf)?;
254
255        Self::read_xpub_body(reader, Hint::Legacy)
256    }
257
258    /// Attempt to instantiate an `XPub` from a `std::io::Read`
259    ///
260    /// ```
261    /// use coins_bip32::{Bip32Error, xkeys::XPub, enc::{XKeyEncoder, MainnetEncoder}};
262    /// # fn main() -> Result<(), Bip32Error> {
263    /// let xpub_str = "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y".to_owned();
264    ///
265    /// let xpub: XPub = MainnetEncoder::xpub_from_base58(&xpub_str)?;
266    /// # Ok(())
267    /// # }
268    /// ```
269    fn read_xpub<R>(reader: &mut R) -> Result<XPub, Bip32Error>
270    where
271        R: std::io::Read;
272
273    /// Serialize an XPriv to base58
274    fn xpriv_to_base58<K>(k: &K) -> Result<String, Bip32Error>
275    where
276        K: AsRef<XPriv>,
277    {
278        let mut v: Vec<u8> = vec![];
279        Self::write_xpriv(&mut v, k)?;
280        Ok(encode_b58_check(&v))
281    }
282
283    /// Serialize an XPub to base58
284    fn xpub_to_base58<K>(k: &K) -> Result<String, Bip32Error>
285    where
286        K: AsRef<XPub>,
287    {
288        let mut v: Vec<u8> = vec![];
289        Self::write_xpub(&mut v, k)?;
290        Ok(encode_b58_check(&v))
291    }
292
293    /// Attempt to read an XPriv from a b58check string.
294    ///
295    /// ```
296    /// use coins_bip32::{Bip32Error, xkeys::XPriv, enc::{XKeyEncoder, MainnetEncoder}};
297    /// # fn main() -> Result<(), Bip32Error> {
298    /// let xpriv_str = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi".to_owned();
299    ///
300    /// let xpriv: XPriv = MainnetEncoder::xpriv_from_base58(&xpriv_str)?;
301    /// # Ok(())
302    /// # }
303    /// ```
304    fn xpriv_from_base58(s: &str) -> Result<XPriv, Bip32Error>
305where {
306        let data = decode_b58_check(s)?;
307        Self::read_xpriv(&mut &data[..])
308    }
309
310    /// Attempt to read an XPub from a b58check string
311    ///
312    /// ```
313    /// use coins_bip32::{Bip32Error, xkeys::XPub, enc::{XKeyEncoder, MainnetEncoder}};
314    /// # fn main() -> Result<(), Bip32Error> {
315    /// let xpub_str = "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y".to_owned();
316    ///
317    /// let xpub: XPub = MainnetEncoder::xpub_from_base58(&xpub_str)?;
318    /// # Ok(())
319    /// # }
320    /// ```
321    fn xpub_from_base58(s: &str) -> Result<XPub, Bip32Error>
322where {
323        let data = decode_b58_check(s)?;
324        Self::read_xpub(&mut &data[..])
325    }
326}
327
328impl<P: NetworkParams> XKeyEncoder for BitcoinEncoder<P> {
329    /// Serialize the xpub to `std::io::Write`
330    fn write_xpub<W, K>(writer: &mut W, key: &K) -> Result<usize, Bip32Error>
331    where
332        W: std::io::Write,
333        K: AsRef<XPub>,
334    {
335        let version = match key.as_ref().xkey_info.hint {
336            Hint::Legacy => P::PUB_VERSION,
337            Hint::Compatibility => P::BIP49_PUB_VERSION,
338            Hint::SegWit => P::BIP84_PUB_VERSION,
339        };
340        let mut written = writer.write(&version.to_be_bytes())?;
341        written += Self::write_key_details(writer, key.as_ref())?;
342        written += writer.write(key.as_ref().key.to_sec1_bytes().as_ref())?;
343        Ok(written)
344    }
345
346    /// Serialize the xpriv to `std::io::Write`
347    fn write_xpriv<W, K>(writer: &mut W, key: &K) -> Result<usize, Bip32Error>
348    where
349        W: std::io::Write,
350        K: AsRef<XPriv>,
351    {
352        let version = match key.as_ref().xkey_info.hint {
353            Hint::Legacy => P::PRIV_VERSION,
354            Hint::Compatibility => P::BIP49_PRIV_VERSION,
355            Hint::SegWit => P::BIP84_PRIV_VERSION,
356        };
357        let mut written = writer.write(&version.to_be_bytes())?;
358        written += Self::write_key_details(writer, key.as_ref())?;
359        written += writer.write(&[0])?;
360        written += writer.write(key.as_ref().key.to_bytes().as_ref())?;
361        Ok(written)
362    }
363
364    fn read_xpriv<R>(reader: &mut R) -> Result<XPriv, Bip32Error>
365    where
366        R: std::io::Read,
367    {
368        let mut buf = [0u8; 4];
369        reader.read_exact(&mut buf)?;
370        let version_bytes = u32::from_be_bytes(buf);
371
372        // Can't use associated constants in matches :()
373        let hint = if version_bytes == P::PRIV_VERSION {
374            Hint::Legacy
375        } else if version_bytes == P::BIP49_PRIV_VERSION {
376            Hint::Compatibility
377        } else if version_bytes == P::BIP84_PRIV_VERSION {
378            Hint::SegWit
379        } else {
380            return Err(Bip32Error::BadXPrivVersionBytes(buf));
381        };
382        Self::read_xpriv_body(reader, hint)
383    }
384
385    fn read_xpub<R>(reader: &mut R) -> Result<XPub, Bip32Error>
386    where
387        R: std::io::Read,
388    {
389        let mut buf = [0u8; 4];
390        reader.read_exact(&mut buf)?;
391        let version_bytes = u32::from_be_bytes(buf);
392
393        // Can't use associated constants in matches :()
394        let hint = if version_bytes == P::PUB_VERSION {
395            Hint::Legacy
396        } else if version_bytes == P::BIP49_PUB_VERSION {
397            Hint::Compatibility
398        } else if version_bytes == P::BIP84_PUB_VERSION {
399            Hint::SegWit
400        } else {
401            return Err(Bip32Error::BadXPrivVersionBytes(buf));
402        };
403        Self::read_xpub_body(reader, hint)
404    }
405}