stacks_core/crypto/
wif.rs1use bdk::bitcoin::util::base58;
4use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
5
6use super::Hashing;
7use crate::{
8 crypto::{sha256::DoubleSha256Hasher, PrivateKey},
9 Network, StacksError, StacksResult,
10};
11
12pub struct WIF([u8; WIF_LENGTH]);
14
15impl WIF {
16 pub fn new(network: Network, private_key: PrivateKey) -> Self {
18 let mut bytes = [0u8; WIF_LENGTH];
19 bytes[0] = WIFPrefix::from(network) as u8;
20 bytes[1..33].copy_from_slice(&private_key.secret_bytes());
21 bytes[33] = 0x01;
22
23 let bytes_to_hash = bytes[..34].to_vec();
24 bytes[34..].copy_from_slice(
25 &DoubleSha256Hasher::new(bytes_to_hash).as_bytes()[..4],
26 );
27
28 Self(bytes)
29 }
30
31 pub fn from_bytes(bytes: impl AsRef<[u8]>) -> StacksResult<Self> {
33 let bytes: [u8; WIF_LENGTH] = bytes.as_ref().try_into()?;
34
35 let wif = Self(bytes);
36 wif.validate()?;
37
38 Ok(wif)
39 }
40
41 fn validate(&self) -> StacksResult<()> {
42 let valid_network_byte =
43 WIFPrefix::iter().any(|prefix| prefix as u8 == self.0[0]);
44 let valid_private_key = PrivateKey::from_slice(&self.0[1..33]).is_ok();
45 let valid_compression_byte = self.0[33] == 0x01;
46 let valid_checksum = DoubleSha256Hasher::new(&self.0[..34]).as_ref()
47 [..4] == self.0[34..];
48
49 if valid_network_byte
50 && valid_private_key
51 && valid_compression_byte
52 && valid_checksum
53 {
54 Ok(())
55 } else {
56 Err(StacksError::InvalidData("WIF is invalid"))
57 }
58 }
59
60 pub fn network(&self) -> StacksResult<Network> {
62 match WIFPrefix::from_repr(self.0[0]) {
63 Some(WIFPrefix::Mainnet) => Ok(Network::Mainnet),
64 Some(WIFPrefix::Testnet) => Ok(Network::Testnet),
65 _ => Err(StacksError::InvalidData("Unknown network byte")),
66 }
67 }
68
69 pub fn private_key(&self) -> StacksResult<PrivateKey> {
71 Ok(PrivateKey::from_slice(&self.0[1..33])?)
72 }
73}
74
75pub const WIF_LENGTH: usize = 38;
82
83#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, EnumIter, FromRepr)]
85#[repr(u8)]
86pub enum WIFPrefix {
87 Mainnet = 128,
89 Testnet = 239,
91}
92
93impl From<Network> for WIFPrefix {
94 fn from(value: Network) -> Self {
95 match value {
96 Network::Mainnet => Self::Mainnet,
97 Network::Testnet => Self::Testnet,
98 }
99 }
100}
101
102impl TryFrom<String> for WIF {
103 type Error = StacksError;
104
105 fn try_from(value: String) -> Result<Self, Self::Error> {
106 let wif = Self::from_bytes(base58::from(&value)?)?;
107 wif.validate()?;
108
109 Ok(wif)
110 }
111}
112
113impl ToString for WIF {
114 fn to_string(&self) -> String {
115 base58::encode_slice(&self.0)
116 }
117}
118
119#[cfg(test)]
120mod tests {
121
122 use bdk::bitcoin::secp256k1::Secp256k1;
123 use rand::thread_rng;
124
125 use super::*;
126
127 #[test]
128 fn wif() {
129 let pk = Secp256k1::new().generate_keypair(&mut thread_rng()).0;
130
131 for network in Network::iter() {
132 let wif = WIF::new(network, pk);
133
134 assert_eq!(wif.network().unwrap(), network);
135 assert_eq!(wif.private_key().unwrap(), pk);
136
137 let bitcoin_pk =
138 bdk::bitcoin::PrivateKey::from_wif(&wif.to_string()).unwrap();
139
140 assert_eq!(pk.secret_bytes().as_slice(), &bitcoin_pk.to_bytes());
141 assert_eq!(wif.to_string(), bitcoin_pk.to_wif());
142 assert_eq!(bitcoin_pk.network, network.into());
143 }
144 }
145}