crypto_addr/
wif.rs

1use base58::ToBase58;
2use bitcoin_hashes::sha256d::Hash as SHA256d;
3use bitcoin_hashes::Hash;
4use strum_macros::{Display, EnumString};
5
6use super::{AddressFormat, Error, Result};
7
8/// Format specification for formatting a private key as
9/// [WIF](https://en.bitcoin.it/wiki/Wallet_import_format).
10///
11/// Currently, this just specifies the blockchain network for which the WIF is intended to be used
12/// because for each blockchain network there is exactly one supported way to serialize the private
13/// key as a string.
14#[non_exhaustive]
15#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display)]
16pub enum WIFFormat {
17    Bitcoin = 0x80,
18    BitcoinTestnet = 0xEF,
19    Litecoin = 0xB0,
20    Dash = 0xCC,
21    Dogecoin = 0x9E,
22    Europecoin = 0xA8,
23    Peercoin = 0xB7,
24    Feathercoin = 0x8E,
25    Fujicoin = 0xA4,
26    VCash = 0xC7,
27    Stratis = 0xBF,
28    Clams = 0x85,
29    Blackcoin = 0x99,
30    Asiacoin = 0x97,
31    /// Format the private key directly as a hex string representing the raw private key bytes
32    Hex,
33}
34
35impl From<&AddressFormat> for WIFFormat {
36    /// Obtain a `WIFFormat` using the same blockchain network as an AddressFormat
37    fn from(opts: &AddressFormat) -> Self {
38        match opts {
39            AddressFormat::Bitcoin(_) => Self::Bitcoin,
40            AddressFormat::BitcoinTestnet => Self::BitcoinTestnet,
41            AddressFormat::BitcoinGold => Self::Bitcoin,
42            AddressFormat::BitcoinCash(_) => Self::Bitcoin,
43            AddressFormat::Litecoin => Self::Litecoin,
44            AddressFormat::LitecoinCash => Self::Litecoin,
45            AddressFormat::Dogecoin => Self::Dogecoin,
46            AddressFormat::Dash => Self::Dash,
47            AddressFormat::Europecoin => Self::Europecoin,
48            AddressFormat::Peercoin => Self::Peercoin,
49            AddressFormat::Syscoin => Self::Bitcoin,
50            AddressFormat::Feathercoin => Self::Feathercoin,
51            AddressFormat::Potcoin => Self::Peercoin,
52            AddressFormat::Namecoin => Self::Bitcoin,
53            AddressFormat::Fujicoin => Self::Fujicoin,
54            AddressFormat::Verge => Self::Dogecoin,
55            AddressFormat::Hempcoin => Self::Europecoin,
56            AddressFormat::VCash => Self::VCash,
57            AddressFormat::ZCash => Self::Bitcoin,
58            AddressFormat::Vertcoin => Self::Bitcoin,
59            AddressFormat::Ripple => Self::Hex,
60            AddressFormat::Stratis => Self::Stratis,
61            AddressFormat::Clams => Self::Clams,
62            AddressFormat::Blackcoin => Self::Blackcoin,
63            AddressFormat::ShadowCash => Self::Stratis,
64            AddressFormat::Beetlecoin => Self::Blackcoin,
65            AddressFormat::Asiacoin => Self::Asiacoin,
66            #[cfg(feature = "eth")]
67            AddressFormat::Ethereum => Self::Hex,
68        }
69    }
70}
71
72impl WIFFormat {
73    /// Encode private key bytes as a WIF string
74    pub(crate) fn encode(&self, private_key_bytes: impl AsRef<[u8]>) -> Result<String> {
75        if let Self::Hex = self {
76            return Ok(hex::encode(private_key_bytes));
77        }
78        // First check that the input slice is exactly 32 bytes long;
79        let private_key_slice = private_key_bytes.as_ref();
80        if private_key_slice.len() != 32 {
81            return Err(Error::InvalidLength {
82                received: private_key_slice.len(),
83                expected: 32,
84            });
85        }
86        // allocated on the stack, no biggie
87        let mut buf = [0; 38];
88        buf[0] = *self as u8;
89        buf[1..33].copy_from_slice(private_key_slice);
90        buf[33] = 1;
91
92        let checksum = SHA256d::hash(&buf[..34]);
93        buf[34..].copy_from_slice(&checksum[..4]);
94        Ok(buf.to_base58())
95    }
96}
97
98/// Encode a private key using WIF format
99///
100/// ### Usage
101/// WIFs are generated using [`WIF::wif`]
102/// ```
103/// use crypto_addr::{WIF, WIFFormat as WF};
104/// use hex_literal::hex;
105///
106/// const PRVKEY_BYTES: [u8; 32] =
107///     hex!("fdd662f90c0ad0e8c44fcbeb991365b1a66965265d3b1d1231e988150f3fb4bf");
108///
109/// assert_eq!(PRVKEY_BYTES.wif(WF::Bitcoin).as_deref(),  Ok("L5j8wWT18PsT4uUQPrQmHaG8dbsGTdC6PkEvyngK96QGd1FJXfVt"));
110/// assert_eq!(PRVKEY_BYTES.wif(WF::Litecoin).as_deref(), Ok("TBZQPFkBXmr3qk7GwVMdVvoWaTWaXiCzCx9BqbJri4aS8trGJjAj"));
111/// assert_eq!(PRVKEY_BYTES.wif(WF::Dogecoin).as_deref(), Ok("QX846MFzP1MaJRsSz8FZAo6k6dtqWBKuGzvqmR59sT1d4wJfoMbU"));
112/// assert_eq!(PRVKEY_BYTES.wif(WF::Dash).as_deref(),     Ok("XKo4PmqNS5Vu8EUnRcQdnoT9Yd8quRoLmKaqWK1WTTgN2ASdVNfh"));
113/// assert_eq!(PRVKEY_BYTES.wif(WF::Hex).as_deref(),      Ok("fdd662f90c0ad0e8c44fcbeb991365b1a66965265d3b1d1231e988150f3fb4bf"));
114pub trait WIF {
115    /// Encode private key bytes as a WIF using blockchain parameters given by `opts`
116    fn wif(&self, opts: WIFFormat) -> Result<String>;
117}
118
119
120/// The input bytes are interpreted as the raw private key in big-endian format.
121impl WIF for [u8] {
122    fn wif(&self, opts: WIFFormat) -> Result<String> {
123        opts.encode(self)
124    }
125}
126
127/// The input bytes are interpreted as the raw private key in big-endian format.
128impl WIF for crate::PrvkeyBytes {
129    fn wif(&self, opts: WIFFormat) -> Result<String> {
130        opts.encode(self)
131    }
132}
133
134#[cfg(feature = "k256")]
135impl WIF for k256::SecretKey {
136    fn wif(&self, opts: WIFFormat) -> Result<String> {
137        opts.encode(&self.to_bytes())
138    }
139}
140
141#[cfg(feature = "k256")]
142impl WIF for k256::ecdsa::SigningKey {
143    fn wif(&self, opts: WIFFormat) -> Result<String> {
144        opts.encode(&self.to_bytes())
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use crate::{BitcoinFormat, test_vectors::wif::*};
152
153    fn testfun(vectors: &str, opts: WIFFormat) {
154        for tc in vectors.lines().map(TestCase::from) {
155            assert_eq!(tc.prv.wif(opts).as_deref(), Ok(tc.wif));
156        }
157    }
158
159    #[test]
160    fn btc_wif() {
161        testfun(BTC_TEST_VECTORS, WIFFormat::Bitcoin)
162    }
163    #[test]
164    fn ltc_wif() {
165        testfun(LTC_TEST_VECTORS, WIFFormat::Litecoin)
166    }
167    #[test]
168    fn doge_wif() {
169        testfun(DOGE_TEST_VECTORS, WIFFormat::Dogecoin)
170    }
171    #[test]
172    fn dash_wif() {
173        testfun(DASH_TEST_VECTORS, WIFFormat::Dash)
174    }
175    #[test]
176    fn erc_wif() {
177        testfun(ERC_TEST_VECTORS, WIFFormat::Europecoin)
178    }
179
180    #[test]
181    fn from_addr_opts() {
182        assert_eq!(
183            WIFFormat::from(&AddressFormat::Bitcoin(BitcoinFormat::Legacy)),
184            WIFFormat::Bitcoin
185        );
186        assert_eq!(
187            WIFFormat::from(&AddressFormat::BitcoinCash(None)),
188            WIFFormat::Bitcoin
189        );
190        assert_eq!(
191            WIFFormat::from(&AddressFormat::BitcoinCash(Some("ligma".to_owned()))),
192            WIFFormat::Bitcoin
193        );
194        assert_eq!(
195            WIFFormat::from(&AddressFormat::Litecoin),
196            WIFFormat::Litecoin
197        );
198        assert_eq!(
199            WIFFormat::from(&AddressFormat::Dogecoin),
200            WIFFormat::Dogecoin
201        );
202        assert_eq!(WIFFormat::from(&AddressFormat::Dash), WIFFormat::Dash);
203        assert_eq!(
204            WIFFormat::from(&AddressFormat::Europecoin),
205            WIFFormat::Europecoin
206        );
207        assert_eq!(
208            WIFFormat::from(&AddressFormat::Peercoin),
209            WIFFormat::Peercoin
210        );
211    }
212}