stacks_core/crypto/
wif.rs

1//! WIF parsing and construction of Stacks private keys.
2
3use 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
12/// Contains validated WIF bytes. It assumess compression is always used.
13pub struct WIF([u8; WIF_LENGTH]);
14
15impl WIF {
16	/// Constructs a WIF from a network and private key
17	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	/// Attempts to parse a WIF from a byte slice
32	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	/// Returns the network
61	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	/// Returns the private key
70	pub fn private_key(&self) -> StacksResult<PrivateKey> {
71		Ok(PrivateKey::from_slice(&self.0[1..33])?)
72	}
73}
74
75/// WIF length consists of:
76///
77/// 1. [WIFPrefix] byte
78/// 2. Private key bytes (32 bytes)
79/// 3. Compression byte
80/// 4. Checksum bytes ( 4 bytes)
81pub const WIF_LENGTH: usize = 38;
82
83/// WIF network prefix byte
84#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, EnumIter, FromRepr)]
85#[repr(u8)]
86pub enum WIFPrefix {
87	/// Mainnet
88	Mainnet = 128,
89	/// Testnet
90	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}