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#[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 Hex,
33}
34
35impl From<&AddressFormat> for WIFFormat {
36 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 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 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 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
98pub trait WIF {
115 fn wif(&self, opts: WIFFormat) -> Result<String>;
117}
118
119
120impl WIF for [u8] {
122 fn wif(&self, opts: WIFFormat) -> Result<String> {
123 opts.encode(self)
124 }
125}
126
127impl 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}