1#[cfg(feature = "alloc")]
4use alloc::{
5 string::{String, ToString},
6 vec::Vec,
7};
8
9use bitcoin::{PrivateKey, bip32::Xpriv, key::CompressedPublicKey};
10use core::marker::PhantomData;
11use kobe::Wallet;
12use zeroize::Zeroizing;
13
14use crate::address::create_address;
15use crate::{AddressType, DerivationPath, Error, Network};
16
17#[derive(Debug)]
37pub struct Deriver<'a> {
38 master_key: Xpriv,
40 network: Network,
42 _wallet: PhantomData<&'a Wallet>,
44}
45
46#[derive(Debug, Clone)]
48pub struct DerivedAddress {
49 pub path: DerivationPath,
51 pub private_key_hex: Zeroizing<String>,
53 pub private_key_wif: Zeroizing<String>,
55 pub public_key_hex: String,
57 pub address: String,
59 pub address_type: AddressType,
61}
62
63impl<'a> Deriver<'a> {
64 #[inline]
70 pub fn new(wallet: &'a Wallet, network: Network) -> Result<Self, Error> {
71 let master_key = Xpriv::new_master(network.to_bitcoin_network(), wallet.seed())?;
72
73 Ok(Self {
74 master_key,
75 network,
76 _wallet: PhantomData,
77 })
78 }
79
80 #[inline]
92 pub fn derive(&self, index: u32) -> Result<DerivedAddress, Error> {
93 self.derive_with(AddressType::P2wpkh, index)
94 }
95
96 #[inline]
113 pub fn derive_with(
114 &self,
115 address_type: AddressType,
116 index: u32,
117 ) -> Result<DerivedAddress, Error> {
118 let path = DerivationPath::bip_standard(address_type, self.network, 0, false, index);
119 self.derive_path(&path, address_type)
120 }
121
122 #[inline]
133 pub fn derive_many(&self, start: u32, count: u32) -> Result<Vec<DerivedAddress>, Error> {
134 self.derive_many_with(AddressType::P2wpkh, start, count)
135 }
136
137 pub fn derive_many_with(
149 &self,
150 address_type: AddressType,
151 start: u32,
152 count: u32,
153 ) -> Result<Vec<DerivedAddress>, Error> {
154 (start..start + count)
155 .map(|index| self.derive_with(address_type, index))
156 .collect()
157 }
158
159 pub fn derive_path(
178 &self,
179 path: &DerivationPath,
180 address_type: AddressType,
181 ) -> Result<DerivedAddress, Error> {
182 let secp = bitcoin::secp256k1::Secp256k1::new();
183 let derived = self.master_key.derive_priv(&secp, path.inner())?;
184
185 let private_key = PrivateKey::new(derived.private_key, self.network.to_bitcoin_network());
186 let public_key = CompressedPublicKey::from_private_key(&secp, &private_key)
187 .expect("valid private key always produces valid public key");
188
189 let address = create_address(&public_key, self.network, address_type);
190
191 let private_key_bytes = derived.private_key.secret_bytes();
193
194 Ok(DerivedAddress {
195 path: path.clone(),
196 private_key_hex: Zeroizing::new(hex::encode(private_key_bytes)),
197 private_key_wif: Zeroizing::new(private_key.to_wif()),
198 public_key_hex: public_key.to_string(),
199 address: address.to_string(),
200 address_type,
201 })
202 }
203
204 #[must_use]
206 pub const fn network(&self) -> Network {
207 self.network
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 const TEST_MNEMONIC: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
216
217 fn test_wallet() -> Wallet {
218 Wallet::from_mnemonic(TEST_MNEMONIC, None).unwrap()
219 }
220
221 #[test]
222 fn test_derive_default() {
223 let wallet = test_wallet();
224 let deriver = Deriver::new(&wallet, Network::Mainnet).unwrap();
225 let addr = deriver.derive(0).unwrap();
226
227 assert!(addr.address.starts_with("bc1q"));
229 assert_eq!(addr.path.to_string(), "m/84'/0'/0'/0/0");
230 }
231
232 #[test]
233 fn test_derive_with_p2wpkh() {
234 let wallet = test_wallet();
235 let deriver = Deriver::new(&wallet, Network::Mainnet).unwrap();
236 let addr = deriver.derive_with(AddressType::P2wpkh, 0).unwrap();
237
238 assert!(addr.address.starts_with("bc1q"));
239 assert_eq!(addr.path.to_string(), "m/84'/0'/0'/0/0");
240 }
241
242 #[test]
243 fn test_derive_with_p2pkh() {
244 let wallet = test_wallet();
245 let deriver = Deriver::new(&wallet, Network::Mainnet).unwrap();
246 let addr = deriver.derive_with(AddressType::P2pkh, 0).unwrap();
247
248 assert!(addr.address.starts_with('1'));
249 assert_eq!(addr.path.to_string(), "m/44'/0'/0'/0/0");
250 }
251
252 #[test]
253 fn test_derive_with_p2sh() {
254 let wallet = test_wallet();
255 let deriver = Deriver::new(&wallet, Network::Mainnet).unwrap();
256 let addr = deriver.derive_with(AddressType::P2shP2wpkh, 0).unwrap();
257
258 assert!(addr.address.starts_with('3'));
259 assert_eq!(addr.path.to_string(), "m/49'/0'/0'/0/0");
260 }
261
262 #[test]
263 fn test_derive_with_p2tr() {
264 let wallet = test_wallet();
265 let deriver = Deriver::new(&wallet, Network::Mainnet).unwrap();
266 let addr = deriver.derive_with(AddressType::P2tr, 0).unwrap();
267
268 assert!(addr.address.starts_with("bc1p"));
269 assert_eq!(addr.path.to_string(), "m/86'/0'/0'/0/0");
270 }
271
272 #[test]
273 fn test_derive_testnet() {
274 let wallet = test_wallet();
275 let deriver = Deriver::new(&wallet, Network::Testnet).unwrap();
276 let addr = deriver.derive(0).unwrap();
277
278 assert!(addr.address.starts_with("tb1q"));
279 assert_eq!(addr.path.to_string(), "m/84'/1'/0'/0/0");
280 }
281
282 #[test]
283 fn test_derive_many() {
284 let wallet = test_wallet();
285 let deriver = Deriver::new(&wallet, Network::Mainnet).unwrap();
286 let addrs = deriver.derive_many(0, 5).unwrap();
287
288 assert_eq!(addrs.len(), 5);
289
290 let mut seen = Vec::new();
292 for addr in &addrs {
293 assert!(!seen.contains(&addr.address));
294 seen.push(addr.address.clone());
295 }
296 assert_eq!(seen.len(), 5);
297 }
298
299 #[test]
300 fn test_derive_many_with() {
301 let wallet = test_wallet();
302 let deriver = Deriver::new(&wallet, Network::Mainnet).unwrap();
303 let addrs = deriver.derive_many_with(AddressType::P2pkh, 0, 3).unwrap();
304
305 assert_eq!(addrs.len(), 3);
306 for addr in &addrs {
307 assert!(addr.address.starts_with('1'));
308 }
309 }
310
311 #[test]
312 fn test_passphrase_changes_addresses() {
313 let wallet1 = Wallet::from_mnemonic(TEST_MNEMONIC, None).unwrap();
314 let wallet2 = Wallet::from_mnemonic(TEST_MNEMONIC, Some("password")).unwrap();
315
316 let deriver1 = Deriver::new(&wallet1, Network::Mainnet).unwrap();
317 let deriver2 = Deriver::new(&wallet2, Network::Mainnet).unwrap();
318
319 let addr1 = deriver1.derive(0).unwrap();
320 let addr2 = deriver2.derive(0).unwrap();
321
322 assert_ne!(addr1.address, addr2.address);
324 }
325}