1#[cfg(feature = "alloc")]
4use alloc::{
5 string::{String, ToString},
6 vec::Vec,
7};
8use core::marker::PhantomData;
9
10use bitcoin::{PrivateKey, bip32::Xpriv, key::CompressedPublicKey};
11use kobe::Wallet;
12use zeroize::Zeroizing;
13
14use crate::address::create_address;
15use crate::{AddressType, DerivationPath, Error, Network};
16
17#[derive(Debug)]
22pub struct Deriver<'a> {
23 master_key: Xpriv,
25 network: Network,
27 _wallet: PhantomData<&'a Wallet>,
29}
30
31#[derive(Debug, Clone)]
33pub struct DerivedAddress {
34 pub path: DerivationPath,
36 pub private_key_hex: Zeroizing<String>,
38 pub private_key_wif: Zeroizing<String>,
40 pub public_key_hex: String,
42 pub address: String,
44 pub address_type: AddressType,
46}
47
48impl<'a> Deriver<'a> {
49 #[inline]
55 pub fn new(wallet: &'a Wallet, network: Network) -> Result<Self, Error> {
56 let master_key = Xpriv::new_master(network.to_bitcoin_network(), wallet.seed())?;
57
58 Ok(Self {
59 master_key,
60 network,
61 _wallet: PhantomData,
62 })
63 }
64
65 #[inline]
77 pub fn derive(&self, index: u32) -> Result<DerivedAddress, Error> {
78 self.derive_with(AddressType::P2wpkh, index)
79 }
80
81 #[inline]
98 pub fn derive_with(
99 &self,
100 address_type: AddressType,
101 index: u32,
102 ) -> Result<DerivedAddress, Error> {
103 let path = DerivationPath::bip_standard(address_type, self.network, 0, false, index);
104 self.derive_path(&path, address_type)
105 }
106
107 #[inline]
118 pub fn derive_many(&self, start: u32, count: u32) -> Result<Vec<DerivedAddress>, Error> {
119 self.derive_many_with(AddressType::P2wpkh, start, count)
120 }
121
122 pub fn derive_many_with(
134 &self,
135 address_type: AddressType,
136 start: u32,
137 count: u32,
138 ) -> Result<Vec<DerivedAddress>, Error> {
139 (start..start + count)
140 .map(|index| self.derive_with(address_type, index))
141 .collect()
142 }
143
144 pub fn derive_path(
163 &self,
164 path: &DerivationPath,
165 address_type: AddressType,
166 ) -> Result<DerivedAddress, Error> {
167 let secp = bitcoin::secp256k1::Secp256k1::new();
168 let derived = self.master_key.derive_priv(&secp, path.inner())?;
169
170 let private_key = PrivateKey::new(derived.private_key, self.network.to_bitcoin_network());
171 let public_key = CompressedPublicKey::from_private_key(&secp, &private_key)
172 .expect("valid private key always produces valid public key");
173
174 let address = create_address(&public_key, self.network, address_type);
175
176 let private_key_bytes = derived.private_key.secret_bytes();
178
179 Ok(DerivedAddress {
180 path: path.clone(),
181 private_key_hex: Zeroizing::new(hex::encode(private_key_bytes)),
182 private_key_wif: Zeroizing::new(private_key.to_wif()),
183 public_key_hex: public_key.to_string(),
184 address: address.to_string(),
185 address_type,
186 })
187 }
188
189 #[must_use]
191 pub const fn network(&self) -> Network {
192 self.network
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 const TEST_MNEMONIC: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
201
202 fn test_wallet() -> Wallet {
203 Wallet::from_mnemonic(TEST_MNEMONIC, None).unwrap()
204 }
205
206 #[test]
207 fn test_derive_default() {
208 let wallet = test_wallet();
209 let deriver = Deriver::new(&wallet, Network::Mainnet).unwrap();
210 let addr = deriver.derive(0).unwrap();
211
212 assert!(addr.address.starts_with("bc1q"));
214 assert_eq!(addr.path.to_string(), "m/84'/0'/0'/0/0");
215 }
216
217 #[test]
218 fn test_derive_with_p2wpkh() {
219 let wallet = test_wallet();
220 let deriver = Deriver::new(&wallet, Network::Mainnet).unwrap();
221 let addr = deriver.derive_with(AddressType::P2wpkh, 0).unwrap();
222
223 assert!(addr.address.starts_with("bc1q"));
224 assert_eq!(addr.path.to_string(), "m/84'/0'/0'/0/0");
225 }
226
227 #[test]
228 fn test_derive_with_p2pkh() {
229 let wallet = test_wallet();
230 let deriver = Deriver::new(&wallet, Network::Mainnet).unwrap();
231 let addr = deriver.derive_with(AddressType::P2pkh, 0).unwrap();
232
233 assert!(addr.address.starts_with('1'));
234 assert_eq!(addr.path.to_string(), "m/44'/0'/0'/0/0");
235 }
236
237 #[test]
238 fn test_derive_with_p2sh() {
239 let wallet = test_wallet();
240 let deriver = Deriver::new(&wallet, Network::Mainnet).unwrap();
241 let addr = deriver.derive_with(AddressType::P2shP2wpkh, 0).unwrap();
242
243 assert!(addr.address.starts_with('3'));
244 assert_eq!(addr.path.to_string(), "m/49'/0'/0'/0/0");
245 }
246
247 #[test]
248 fn test_derive_with_p2tr() {
249 let wallet = test_wallet();
250 let deriver = Deriver::new(&wallet, Network::Mainnet).unwrap();
251 let addr = deriver.derive_with(AddressType::P2tr, 0).unwrap();
252
253 assert!(addr.address.starts_with("bc1p"));
254 assert_eq!(addr.path.to_string(), "m/86'/0'/0'/0/0");
255 }
256
257 #[test]
258 fn test_derive_testnet() {
259 let wallet = test_wallet();
260 let deriver = Deriver::new(&wallet, Network::Testnet).unwrap();
261 let addr = deriver.derive(0).unwrap();
262
263 assert!(addr.address.starts_with("tb1q"));
264 assert_eq!(addr.path.to_string(), "m/84'/1'/0'/0/0");
265 }
266
267 #[test]
268 fn test_derive_many() {
269 let wallet = test_wallet();
270 let deriver = Deriver::new(&wallet, Network::Mainnet).unwrap();
271 let addrs = deriver.derive_many(0, 5).unwrap();
272
273 assert_eq!(addrs.len(), 5);
274
275 let mut seen = Vec::new();
277 for addr in &addrs {
278 assert!(!seen.contains(&addr.address));
279 seen.push(addr.address.clone());
280 }
281 assert_eq!(seen.len(), 5);
282 }
283
284 #[test]
285 fn test_derive_many_with() {
286 let wallet = test_wallet();
287 let deriver = Deriver::new(&wallet, Network::Mainnet).unwrap();
288 let addrs = deriver.derive_many_with(AddressType::P2pkh, 0, 3).unwrap();
289
290 assert_eq!(addrs.len(), 3);
291 for addr in &addrs {
292 assert!(addr.address.starts_with('1'));
293 }
294 }
295
296 #[test]
297 fn test_passphrase_changes_addresses() {
298 let wallet1 = Wallet::from_mnemonic(TEST_MNEMONIC, None).unwrap();
299 let wallet2 = Wallet::from_mnemonic(TEST_MNEMONIC, Some("password")).unwrap();
300
301 let deriver1 = Deriver::new(&wallet1, Network::Mainnet).unwrap();
302 let deriver2 = Deriver::new(&wallet2, Network::Mainnet).unwrap();
303
304 let addr1 = deriver1.derive(0).unwrap();
305 let addr2 = deriver2.derive(0).unwrap();
306
307 assert_ne!(addr1.address, addr2.address);
309 }
310}