stacks_core/
wallet.rs

1//! Exposes tools to create and manage Stacks credentials.
2
3use std::str::FromStr;
4
5use bdk::{
6	bitcoin::{
7		secp256k1::Secp256k1,
8		util::bip32::{DerivationPath, ExtendedPrivKey},
9		Address as BitcoinAddress, AddressType as BitcoinAddressType,
10		Network as BitcoinNetwork,
11	},
12	keys::bip39::Mnemonic,
13};
14use rand::random;
15use serde::{Deserialize, Serialize};
16
17use crate::{
18	address::{AddressVersion, StacksAddress},
19	crypto::{wif::WIF, PrivateKey, PublicKey},
20	Network, StacksError, StacksResult,
21};
22
23/// Computes Stacks derivation paths
24pub fn stacks_derivation_path(index: u32) -> StacksResult<DerivationPath> {
25	Ok(DerivationPath::from_str(&format!(
26		"m/44'/5757'/0'/0/{}",
27		index
28	))?)
29}
30
31/// Computes Bitcoin derivation paths
32pub fn bitcoin_derivation_path(
33	network: BitcoinNetwork,
34	kind: BitcoinAddressType,
35	index: u32,
36) -> StacksResult<DerivationPath> {
37	let mut path = "m/".to_string();
38
39	match kind {
40		BitcoinAddressType::P2pkh => path.push_str("44'/"),
41		BitcoinAddressType::P2wpkh => path.push_str("84'/"),
42		BitcoinAddressType::P2tr => path.push_str("86'/"),
43		_ => {
44			return Err(StacksError::InvalidArguments(
45				"Invalid Bitcoin addres type",
46			))
47		}
48	};
49
50	match network {
51		BitcoinNetwork::Bitcoin => path.push_str("0'/"),
52		_ => path.push_str("1'/"),
53	}
54
55	path.push_str(&format!("{}'/0/0", index));
56
57	Ok(DerivationPath::from_str(&path)?)
58}
59
60/// Derives a key from a master key and a derivation path
61pub fn derive_key(
62	master_key: ExtendedPrivKey,
63	path: DerivationPath,
64) -> ExtendedPrivKey {
65	master_key.derive_priv(&Secp256k1::new(), &path).unwrap()
66}
67
68/// Wallet of credentials
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct Wallet {
71	master_key: ExtendedPrivKey,
72	mnemonic: Mnemonic,
73}
74
75impl Wallet {
76	/// Creates a wallet from the network, mnemonic, and optional passphrase
77	pub fn new(mnemonic: impl AsRef<str>) -> StacksResult<Self> {
78		let mnemonic = Mnemonic::from_str(mnemonic.as_ref())?;
79
80		// Bitcoin network is irrelevant for extended private keys
81		let master_key = ExtendedPrivKey::new_master(
82			BitcoinNetwork::Bitcoin,
83			&mnemonic.to_seed(""),
84		)?;
85
86		Ok(Self {
87			master_key,
88			mnemonic,
89		})
90	}
91
92	/// Creates a random wallet
93	pub fn random() -> StacksResult<Self> {
94		let entropy: [u8; 32] = random();
95		let mnemonic = Mnemonic::from_entropy(&entropy)?;
96
97		Self::new(mnemonic.to_string())
98	}
99
100	/// Returns the mnemonic of the wallet
101	pub fn mnemonic(&self) -> Mnemonic {
102		self.mnemonic.clone()
103	}
104
105	/// Returns the master key of the wallet
106	pub fn master_key(&self) -> PrivateKey {
107		self.master_key.private_key
108	}
109
110	/// Returns the WIF of the wallet
111	pub fn wif(&self, network: Network) -> WIF {
112		WIF::new(network, self.master_key())
113	}
114
115	/// Returns the credentials at the given index
116	pub fn credentials(
117		&self,
118		network: Network,
119		index: u32,
120	) -> StacksResult<Credentials> {
121		Credentials::new(network, self.master_key, index)
122	}
123
124	/// Returns the Bitcoin credentials at the given index
125	pub fn bitcoin_credentials(
126		&self,
127		network: BitcoinNetwork,
128		index: u32,
129	) -> StacksResult<BitcoinCredentials> {
130		BitcoinCredentials::new(network, self.master_key, index)
131	}
132}
133
134/// Credentials that can be used to sign transactions
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct Credentials {
137	network: Network,
138	private_key: PrivateKey,
139}
140
141impl Credentials {
142	/// Creates credentials from the network and private key
143	pub fn new(
144		network: Network,
145		master_key: ExtendedPrivKey,
146		index: u32,
147	) -> StacksResult<Self> {
148		let private_key =
149			derive_key(master_key, stacks_derivation_path(index)?)
150				.to_priv()
151				.inner;
152
153		Ok(Self {
154			network,
155			private_key,
156		})
157	}
158
159	/// Returns the Stacks network
160	pub fn network(&self) -> Network {
161		self.network
162	}
163
164	/// Returns the private key
165	pub fn private_key(&self) -> PrivateKey {
166		self.private_key
167	}
168
169	/// Returns the public key
170	pub fn public_key(&self) -> PublicKey {
171		self.private_key.public_key(&Secp256k1::new())
172	}
173
174	/// Returns the Stacks P2PKH address
175	pub fn address(&self) -> StacksAddress {
176		let version = match self.network {
177			Network::Mainnet => AddressVersion::MainnetSingleSig,
178			Network::Testnet => AddressVersion::TestnetSingleSig,
179		};
180
181		StacksAddress::p2pkh(version, &self.public_key())
182	}
183
184	/// Returns the WIF
185	pub fn wif(&self) -> WIF {
186		WIF::new(self.network(), self.private_key())
187	}
188}
189
190/// Bitcoin Credentials that can be used to sign transactions
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct BitcoinCredentials {
193	network: BitcoinNetwork,
194	private_key_p2pkh: PrivateKey,
195	private_key_p2wpkh: PrivateKey,
196	private_key_p2tr: PrivateKey,
197}
198
199impl BitcoinCredentials {
200	/// Creates Bitcoin credentials from the Bitcoin network and private key
201	pub fn new(
202		network: BitcoinNetwork,
203		master_key: ExtendedPrivKey,
204		index: u32,
205	) -> StacksResult<Self> {
206		let private_key_p2pkh = derive_key(
207			master_key,
208			bitcoin_derivation_path(network, BitcoinAddressType::P2pkh, index)?,
209		)
210		.to_priv()
211		.inner;
212
213		let private_key_p2wpkh = derive_key(
214			master_key,
215			bitcoin_derivation_path(
216				network,
217				BitcoinAddressType::P2wpkh,
218				index,
219			)?,
220		)
221		.to_priv()
222		.inner;
223
224		let private_key_p2tr = derive_key(
225			master_key,
226			bitcoin_derivation_path(network, BitcoinAddressType::P2tr, index)?,
227		)
228		.to_priv()
229		.inner;
230
231		Ok(Self {
232			network,
233			private_key_p2pkh,
234			private_key_p2wpkh,
235			private_key_p2tr,
236		})
237	}
238
239	/// Returns the Bitcoin network
240	pub fn network(&self) -> BitcoinNetwork {
241		self.network
242	}
243
244	/// Returns the Bitcoin P2PKH private key
245	pub fn private_key_p2pkh(&self) -> PrivateKey {
246		self.private_key_p2pkh
247	}
248
249	/// Returns the Bitcoin P22PKH private key
250	pub fn private_key_p2wpkh(&self) -> PrivateKey {
251		self.private_key_p2wpkh
252	}
253
254	/// Returns the Bitcoin P2TR private key
255	pub fn private_key_p2tr(&self) -> PrivateKey {
256		self.private_key_p2tr
257	}
258
259	/// Returns the Bitcoin P2PKH public key
260	pub fn public_key_p2pkh(&self) -> PublicKey {
261		self.private_key_p2pkh.public_key(&Secp256k1::new())
262	}
263
264	/// Returns the Bitcoin P2WPKH public key
265	pub fn public_key_p2wpkh(&self) -> PublicKey {
266		self.private_key_p2wpkh.public_key(&Secp256k1::new())
267	}
268
269	/// Returns the Bitcoin P2TR public key
270	pub fn public_key_p2tr(&self) -> PublicKey {
271		self.private_key_p2tr.public_key(&Secp256k1::new())
272	}
273
274	/// Returns the Bitcoin P2PKH address
275	pub fn address_p2pkh(&self) -> BitcoinAddress {
276		BitcoinAddress::p2pkh(
277			&bdk::bitcoin::PublicKey::new(self.public_key_p2pkh()),
278			self.network(),
279		)
280	}
281
282	/// Returns the Bitcoin P2WPKH address
283	pub fn address_p2wpkh(&self) -> BitcoinAddress {
284		BitcoinAddress::p2wpkh(
285			&bdk::bitcoin::PublicKey::new(self.public_key_p2wpkh()),
286			self.network(),
287		)
288		.unwrap()
289	}
290
291	/// Returns the Bitcoin P2TR address
292	pub fn address_p2tr(&self) -> BitcoinAddress {
293		BitcoinAddress::p2tr(
294			&Secp256k1::new(),
295			self.public_key_p2tr().x_only_public_key().0,
296			None,
297			self.network(),
298		)
299	}
300
301	/// Returns the WIF for P2PKH
302	pub fn wif_p2pkh(&self) -> WIF {
303		WIF::new(self.network().into(), self.private_key_p2pkh())
304	}
305
306	/// Returns the WIF for P2WPKH
307	pub fn wif_p2wpkh(&self) -> WIF {
308		WIF::new(self.network().into(), self.private_key_p2wpkh())
309	}
310
311	/// Returns the WIF for P2TR
312	pub fn wif_p2tr(&self) -> WIF {
313		WIF::new(self.network().into(), self.private_key_p2tr())
314	}
315}