1use bip32::{DerivationPath, XPrv};
10use bip39::{Language, Mnemonic};
11use bitcoin::secp256k1::{PublicKey, Secp256k1, XOnlyPublicKey};
12use bitcoin::{Address, Network, key::CompressedPublicKey};
13use serde::{Deserialize, Serialize};
14use sha2::{Digest, Sha256};
15use std::error::Error as StdError;
16use std::{fmt, str::FromStr};
17use zeroize::Zeroize;
18
19const DEFAULT_NETWORK: Network = Network::Bitcoin;
20const DEFAULT_DERIVATION_PATH: &str = "m/84'/0'/0'/0/0";
21const DEFAULT_BIP39_PASSPHRASE: &str = "";
22
23#[derive(Debug, Serialize, Deserialize)]
25pub struct GetAddressResponse {
26 pub address: String,
28 pub path: String,
30 pub public_key: String,
32}
33
34#[derive(Debug, Clone, Copy)]
36pub enum WordCount {
37 Words12 = 12,
39 Words15 = 15,
41 Words18 = 18,
43 Words21 = 21,
45 Words24 = 24,
47}
48
49impl WordCount {
50 pub fn value(&self) -> usize {
52 *self as usize
53 }
54}
55
56#[derive(Debug)]
58pub enum DerivationError {
59 InvalidDerivationPath(String),
61 InvalidNetworkType(String),
63 InvalidPurposeField(String),
65 InvalidXOnlyPubkey(String),
67 Bip39Error(bip39::Error),
69 Bip32Error(bip32::Error),
71 BitcoinError(String),
73 SecpError(bitcoin::secp256k1::Error),
75 ParseError(std::num::ParseIntError),
77 GenericError(String),
79}
80
81impl fmt::Display for DerivationError {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 DerivationError::InvalidDerivationPath(msg) => {
85 write!(f, "Invalid derivation path: {}", msg)
86 }
87 DerivationError::InvalidNetworkType(msg) => write!(f, "Invalid network type: {}", msg),
88 DerivationError::InvalidPurposeField(msg) => {
89 write!(f, "Invalid purpose field: {}", msg)
90 }
91 DerivationError::InvalidXOnlyPubkey(msg) => {
92 write!(f, "Invalid X-only public key: {}", msg)
93 }
94 DerivationError::Bip39Error(e) => write!(f, "BIP39 error: {}", e),
95 DerivationError::Bip32Error(e) => write!(f, "BIP32 error: {}", e),
96 DerivationError::BitcoinError(e) => write!(f, "Bitcoin error: {}", e),
97 DerivationError::SecpError(e) => write!(f, "Secp256k1 error: {}", e),
98 DerivationError::ParseError(e) => write!(f, "Parse error: {}", e),
99 DerivationError::GenericError(msg) => write!(f, "Error: {}", msg),
100 }
101 }
102}
103
104impl StdError for DerivationError {}
105
106impl From<bip39::Error> for DerivationError {
108 fn from(err: bip39::Error) -> Self {
109 DerivationError::Bip39Error(err)
110 }
111}
112
113impl From<bip32::Error> for DerivationError {
114 fn from(err: bip32::Error) -> Self {
115 DerivationError::Bip32Error(err)
116 }
117}
118
119impl From<bitcoin::secp256k1::Error> for DerivationError {
120 fn from(err: bitcoin::secp256k1::Error) -> Self {
121 DerivationError::SecpError(err)
122 }
123}
124
125impl From<std::num::ParseIntError> for DerivationError {
126 fn from(err: std::num::ParseIntError) -> Self {
127 DerivationError::ParseError(err)
128 }
129}
130
131struct SecureSeed {
133 seed: Vec<u8>,
134}
135
136impl SecureSeed {
137 pub fn new(seed: Vec<u8>) -> Self {
139 Self { seed }
140 }
141}
142
143impl Zeroize for SecureSeed {
144 fn zeroize(&mut self) {
145 self.seed.zeroize();
146 }
147}
148
149impl Drop for SecureSeed {
150 fn drop(&mut self) {
151 self.zeroize();
152 }
153}
154
155struct SecureMnemonic {
157 mnemonic: Mnemonic,
158 phrase: String,
160}
161
162impl SecureMnemonic {
163 pub fn new(mnemonic: Mnemonic) -> Self {
165 let phrase = mnemonic.to_string();
166 Self { mnemonic, phrase }
167 }
168
169 pub fn to_seed(&self, passphrase: &str) -> Vec<u8> {
171 self.mnemonic.to_seed(passphrase).to_vec()
172 }
173}
174
175impl Zeroize for SecureMnemonic {
176 fn zeroize(&mut self) {
177 self.phrase.zeroize();
179 }
182}
183
184impl Drop for SecureMnemonic {
185 fn drop(&mut self) {
186 self.zeroize();
187 }
188}
189
190struct SecurePrivateKey {
192 key: bitcoin::secp256k1::SecretKey,
193}
194
195impl SecurePrivateKey {
196 pub fn new(key: bitcoin::secp256k1::SecretKey) -> Self {
198 Self { key }
199 }
200
201 pub fn key(&self) -> &bitcoin::secp256k1::SecretKey {
203 &self.key
204 }
205}
206
207impl Drop for SecurePrivateKey {
208 fn drop(&mut self) {
209 let _ = bitcoin::secp256k1::SecretKey::from_slice(&[0u8; 32]);
211 }
212}
213
214struct SecureWifKey {
216 key: bitcoin::PrivateKey,
217}
218
219impl SecureWifKey {
220 pub fn new(key: bitcoin::PrivateKey) -> Self {
222 Self { key }
223 }
224
225 pub fn to_wif(&self) -> String {
227 self.key.to_wif()
228 }
229}
230
231impl Drop for SecureWifKey {
232 fn drop(&mut self) {
233 if let Ok(zeroed) = bitcoin::secp256k1::SecretKey::from_slice(&[0u8; 32]) {
235 let _ = std::mem::replace(&mut self.key.inner, zeroed);
237 }
238 }
239}
240
241pub fn generate_mnemonic(
253 word_count: Option<WordCount>,
254 language: Option<Language>,
255) -> Result<String, DerivationError> {
256 let word_count = word_count.unwrap_or(WordCount::Words12);
257 let lang = language.unwrap_or(Language::English);
258
259 let mnemonic = Mnemonic::generate_in(lang, word_count.value())?;
261 let phrase = mnemonic.to_string();
262
263 Ok(phrase)
264}
265
266fn validate_purpose_field(purpose: &str) -> Result<(), DerivationError> {
277 match purpose {
278 "44'" | "49'" | "84'" | "86'" => Ok(()),
279 _ => Err(DerivationError::InvalidPurposeField(format!(
280 "Unsupported purpose field: {}. Expected one of: 44', 49', 84', 86'",
281 purpose
282 ))),
283 }
284}
285
286pub fn derive_bitcoin_address(
307 mnemonic_phrase: &str,
308 derivation_path_str: Option<&str>,
309 network: Option<Network>,
310 bip39_passphrase: Option<&str>,
311) -> Result<GetAddressResponse, DerivationError> {
312 let network = network.unwrap_or(DEFAULT_NETWORK);
313 let bip39_passphrase = bip39_passphrase.unwrap_or(DEFAULT_BIP39_PASSPHRASE);
314 let derivation_path_str = derivation_path_str.unwrap_or(DEFAULT_DERIVATION_PATH);
315
316 let path_parts: Vec<&str> = derivation_path_str.split('/').collect();
318 if path_parts.len() != 6 || path_parts[0] != "m" {
319 return Err(DerivationError::InvalidDerivationPath(
320 "Invalid derivation path format. Expected format example: m/84'/0'/0'/0/0".to_string(),
321 ));
322 }
323
324 let purpose = path_parts.get(1).ok_or_else(|| {
326 DerivationError::InvalidDerivationPath(
327 "Missing purpose field in derivation path".to_string(),
328 )
329 })?;
330
331 validate_purpose_field(purpose)?;
333
334 let second_number = path_parts[2].trim_end_matches('\'').parse::<u32>()?;
336 match network {
337 Network::Bitcoin => {
338 if second_number != 0 {
339 return Err(DerivationError::InvalidDerivationPath(format!(
340 "Invalid Coin number in the derivation path for {}. Expected 0.",
341 network
342 )));
343 }
344 }
345 Network::Testnet | Network::Regtest | Network::Signet => {
346 if second_number != 1 {
347 return Err(DerivationError::InvalidDerivationPath(format!(
348 "Invalid Coin number in the derivation path for {}. Expected 1.",
349 network
350 )));
351 }
352 }
353 _ => {
355 return Err(DerivationError::InvalidNetworkType(format!(
356 "Unsupported network type: {}",
357 network
358 )));
359 }
360 }
361
362 let mnemonic = match Mnemonic::parse_in(Language::English, mnemonic_phrase) {
364 Ok(m) => SecureMnemonic::new(m),
365 Err(e) => return Err(DerivationError::Bip39Error(e)),
366 };
367
368 let mut seed_bytes = mnemonic.to_seed(bip39_passphrase);
370 let secure_seed = SecureSeed::new(seed_bytes.clone());
371
372 let derivation_path = match derivation_path_str.parse::<DerivationPath>() {
374 Ok(path) => path,
375 Err(e) => {
376 seed_bytes.zeroize();
377 return Err(DerivationError::Bip32Error(e));
378 }
379 };
380
381 let xprv = match XPrv::derive_from_path(&secure_seed.seed, &derivation_path) {
383 Ok(key) => key,
384 Err(e) => {
385 seed_bytes.zeroize();
386 return Err(DerivationError::Bip32Error(e));
387 }
388 };
389
390 let secp = Secp256k1::new();
392 let secret_key = match bitcoin::secp256k1::SecretKey::from_slice(&xprv.private_key().to_bytes())
393 {
394 Ok(key) => SecurePrivateKey::new(key),
395 Err(e) => {
396 seed_bytes.zeroize();
397 return Err(DerivationError::SecpError(e));
398 }
399 };
400
401 let public_key = PublicKey::from_secret_key(&secp, secret_key.key());
403
404 seed_bytes.zeroize();
406
407 let compressed_public_key = match CompressedPublicKey::from_slice(&public_key.serialize()) {
409 Ok(key) => key,
410 Err(e) => {
411 return Err(DerivationError::BitcoinError(format!(
412 "Failed to create compressed public key: {}",
413 e
414 )));
415 }
416 };
417
418 let address = match *purpose {
420 "44'" => Address::p2pkh(&compressed_public_key, network),
421 "49'" => Address::p2shwpkh(&compressed_public_key, network),
422 "84'" => Address::p2wpkh(&compressed_public_key, network),
423 "86'" => {
424 let x_only_pubkey = match XOnlyPublicKey::from_slice(&public_key.serialize()[1..]) {
426 Ok(key) => key,
427 Err(e) => {
428 return Err(DerivationError::InvalidXOnlyPubkey(format!(
429 "Failed to create XOnlyPublicKey: {}",
430 e
431 )));
432 }
433 };
434 let merkle_root = None; Address::p2tr(&secp, x_only_pubkey, merkle_root, network)
436 }
437 _ => {
438 return Err(DerivationError::InvalidPurposeField(format!(
439 "Unsupported purpose field: {}",
440 purpose
441 )));
442 }
443 };
444
445 let address_string = address.to_string();
446 let public_key_string = public_key.to_string();
447
448 Ok(GetAddressResponse {
449 address: address_string,
450 path: derivation_path_str.to_string(),
451 public_key: public_key_string,
452 })
453}
454
455#[derive(Debug, Serialize, Deserialize)]
457pub struct GetAddressesResponse {
458 pub addresses: Vec<GetAddressResponse>,
460}
461
462pub fn derive_bitcoin_addresses(
482 mnemonic_phrase: &str,
483 derivation_path_str: Option<&str>,
484 network: Option<Network>,
485 bip39_passphrase: Option<&str>,
486 is_change: Option<bool>,
487 start_index: Option<u32>,
488 count: Option<u32>,
489) -> Result<GetAddressesResponse, DerivationError> {
490 let network = network.unwrap_or(DEFAULT_NETWORK);
491 let bip39_passphrase = bip39_passphrase.unwrap_or(DEFAULT_BIP39_PASSPHRASE);
492 let path = derivation_path_str.unwrap_or("m/84'/0'/0'");
493 let is_change = is_change.unwrap_or(false);
494 let start_index = start_index.unwrap_or(0);
495 let count = count.unwrap_or(1);
496
497 let mut addresses = Vec::with_capacity(count as usize);
499
500 let path_parts: Vec<&str> = path.split('/').collect();
502
503 let base_path = if path_parts.len() >= 4 && path_parts[0] == "m" {
505 path_parts[..4].join("/")
507 } else {
508 return Err(DerivationError::InvalidDerivationPath(
509 "Invalid derivation path format. Expected format that includes at least: m/purpose'/coin'/account'".to_string()
510 ));
511 };
512
513 let purpose = path_parts.get(1).ok_or_else(|| {
515 DerivationError::InvalidDerivationPath(
516 "Missing purpose field in derivation path".to_string(),
517 )
518 })?;
519
520 validate_purpose_field(purpose)?;
522
523 let second_number = path_parts[2].trim_end_matches('\'').parse::<u32>()?;
525 match network {
526 Network::Bitcoin => {
527 if second_number != 0 {
528 return Err(DerivationError::InvalidDerivationPath(format!(
529 "Invalid Coin number in the derivation path for {}. Expected 0.",
530 network
531 )));
532 }
533 }
534 Network::Testnet | Network::Regtest | Network::Signet => {
535 if second_number != 1 {
536 return Err(DerivationError::InvalidDerivationPath(format!(
537 "Invalid Coin number in the derivation path for {}. Expected 1.",
538 network
539 )));
540 }
541 }
542 _ => {
544 return Err(DerivationError::InvalidNetworkType(format!(
545 "Unsupported network type: {}",
546 network
547 )));
548 }
549 }
550
551 let change_component = if is_change { "1" } else { "0" };
553
554 for i in start_index..(start_index + count) {
556 let full_path = format!("{}/{}/{}", base_path, change_component, i);
558
559 match derive_bitcoin_address(
561 mnemonic_phrase,
562 Some(&full_path),
563 Some(network),
564 Some(bip39_passphrase),
565 ) {
566 Ok(address) => addresses.push(address),
567 Err(e) => return Err(e),
568 }
569 }
570
571 Ok(GetAddressesResponse { addresses })
572}
573
574pub fn calculate_script_hash(
589 address: &str,
590 network: Option<Network>,
591) -> Result<String, DerivationError> {
592 let network = network.unwrap_or(DEFAULT_NETWORK);
593
594 let parsed_addr = match Address::from_str(address) {
596 Ok(addr) => addr,
597 Err(e) => {
598 return Err(DerivationError::BitcoinError(format!(
599 "Failed to parse address: {}",
600 e
601 )));
602 }
603 };
604
605 let addr = match parsed_addr.require_network(network) {
606 Ok(address) => address,
607 Err(e) => {
608 return Err(DerivationError::BitcoinError(format!(
609 "Network mismatch: {}",
610 e
611 )));
612 }
613 };
614
615 let script = addr.script_pubkey();
617 let script_bytes = script.as_bytes();
618
619 let hash = Sha256::digest(script_bytes);
621
622 let mut reversed_hash = hash.to_vec();
624 reversed_hash.reverse();
625
626 let script_hash_hex = hex::encode(reversed_hash);
628
629 Ok(script_hash_hex)
630}
631
632pub fn derive_private_key(
648 mnemonic_phrase: &str,
649 derivation_path_str: Option<&str>,
650 network: Option<Network>,
651 bip39_passphrase: Option<&str>,
652) -> Result<String, DerivationError> {
653 let network = network.unwrap_or(DEFAULT_NETWORK);
654 let bip39_passphrase = bip39_passphrase.unwrap_or(DEFAULT_BIP39_PASSPHRASE);
655 let derivation_path_str = derivation_path_str.unwrap_or(DEFAULT_DERIVATION_PATH);
656
657 let mnemonic = match Mnemonic::parse_in(Language::English, mnemonic_phrase) {
659 Ok(m) => SecureMnemonic::new(m),
660 Err(e) => return Err(DerivationError::Bip39Error(e)),
661 };
662
663 let mut seed_bytes = mnemonic.to_seed(bip39_passphrase);
665 let secure_seed = SecureSeed::new(seed_bytes.clone());
666
667 let derivation_path = match derivation_path_str.parse::<DerivationPath>() {
669 Ok(path) => path,
670 Err(e) => {
671 seed_bytes.zeroize();
672 return Err(DerivationError::Bip32Error(e));
673 }
674 };
675
676 let xprv = match XPrv::derive_from_path(&secure_seed.seed, &derivation_path) {
678 Ok(key) => key,
679 Err(e) => {
680 seed_bytes.zeroize();
681 return Err(DerivationError::Bip32Error(e));
682 }
683 };
684
685 let secret_key = match bitcoin::secp256k1::SecretKey::from_slice(&xprv.private_key().to_bytes())
687 {
688 Ok(key) => key,
689 Err(e) => {
690 seed_bytes.zeroize();
691 return Err(DerivationError::SecpError(e));
692 }
693 };
694
695 seed_bytes.zeroize();
697
698 let private_key_wif = SecureWifKey::new(bitcoin::PrivateKey {
700 compressed: true,
701 network: network.into(),
702 inner: secret_key,
703 });
704
705 let private_key_string = private_key_wif.to_wif();
707
708 Ok(private_key_string)
709}
710
711pub fn validate_mnemonic(mnemonic_phrase: &str) -> Result<(), DerivationError> {
734 Mnemonic::from_str(mnemonic_phrase)
735 .map(|_| ())
736 .map_err(|e| DerivationError::Bip39Error(e))
737}
738
739pub fn is_valid_bip39_word(word: &str, language: Option<Language>) -> bool {
756 let lang = language.unwrap_or(Language::English);
757 lang.word_list()
758 .iter()
759 .any(|w| w.to_lowercase() == word.to_lowercase())
760}
761
762pub fn get_bip39_suggestions(
781 partial_word: &str,
782 limit: usize,
783 language: Option<Language>,
784) -> Vec<String> {
785 let lang = language.unwrap_or(Language::English);
786 let lowercased = partial_word.to_lowercase();
787
788 let mut suggestions: Vec<String> = lang
789 .word_list()
790 .iter()
791 .filter(|word| word.starts_with(&lowercased))
792 .map(|w| w.to_string())
793 .collect();
794
795 suggestions.sort();
796 suggestions.truncate(limit);
797 suggestions
798}
799
800pub fn get_bip39_wordlist(language: Option<Language>) -> Vec<String> {
816 let lang = language.unwrap_or(Language::English);
817 lang.word_list()
818 .iter()
819 .map(|w| w.to_string())
820 .collect()
821}
822
823pub fn mnemonic_to_entropy(mnemonic_phrase: &str) -> Result<Vec<u8>, DerivationError> {
845 let mnemonic = Mnemonic::from_str(mnemonic_phrase)?;
846 Ok(mnemonic.to_entropy().to_vec())
847}
848
849pub fn entropy_to_mnemonic(
867 entropy: &[u8],
868 language: Option<Language>,
869) -> Result<String, DerivationError> {
870 let lang = language.unwrap_or(Language::English);
871 let mnemonic = Mnemonic::from_entropy_in(lang, entropy)?;
872 Ok(mnemonic.to_string())
873}
874
875pub fn mnemonic_to_seed(
898 mnemonic_phrase: &str,
899 passphrase: Option<&str>,
900) -> Result<Vec<u8>, DerivationError> {
901 let mnemonic = Mnemonic::from_str(mnemonic_phrase)?;
902 let passphrase = passphrase.unwrap_or("");
903 Ok(mnemonic.to_seed(passphrase).to_vec())
904}
905
906#[cfg(test)]
907mod tests {
908 use super::*;
909 use std::option::Option;
910
911 #[test]
912 fn test_generate_mnemonic() {
913 let mnemonic12: String = generate_mnemonic(
915 Option::from(WordCount::Words12),
916 Option::from(Language::English),
917 )
918 .unwrap();
919 println!("Generated 12-word mnemonic: {}", mnemonic12);
920 assert_eq!(mnemonic12.split_whitespace().count(), 12);
921
922 let mnemonic15: String = generate_mnemonic(
924 Option::from(WordCount::Words15),
925 Option::from(Language::English),
926 )
927 .unwrap();
928 println!("Generated 15-word mnemonic: {}", mnemonic15);
929 assert_eq!(mnemonic15.split_whitespace().count(), 15);
930
931 let mnemonic18: String = generate_mnemonic(
933 Option::from(WordCount::Words18),
934 Option::from(Language::English),
935 )
936 .unwrap();
937 println!("Generated 18-word mnemonic: {}", mnemonic18);
938 assert_eq!(mnemonic18.split_whitespace().count(), 18);
939
940 let mnemonic21: String = generate_mnemonic(
942 Option::from(WordCount::Words21),
943 Option::from(Language::English),
944 )
945 .unwrap();
946 println!("Generated 21-word mnemonic: {}", mnemonic21);
947 assert_eq!(mnemonic21.split_whitespace().count(), 21);
948
949 let mnemonic24: String = generate_mnemonic(
951 Option::from(WordCount::Words24),
952 Option::from(Language::English),
953 )
954 .unwrap();
955 println!("Generated 24-word mnemonic: {}", mnemonic24);
956 assert_eq!(mnemonic24.split_whitespace().count(), 24);
957 }
958
959 #[test]
960 fn test_derive_address_p2pkh() {
961 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
963 let path = "m/44'/0'/0'/0/0";
964 let address = derive_bitcoin_address(
965 mnemonic,
966 Option::from(path),
967 Option::from(Network::Bitcoin),
968 None,
969 )
970 .unwrap();
971 println!("P2PKH address: {}", address.address);
972 println!("P2PKH pubkey: {}", address.public_key);
973 assert_eq!(address.address, "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA");
974 assert_eq!(address.path, path);
975 }
976
977 #[test]
978 fn test_derive_address_p2shwpkh() {
979 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
981 let path = "m/49'/0'/0'/0/0";
982 let address = derive_bitcoin_address(
983 mnemonic,
984 Option::from(path),
985 Option::from(Network::Bitcoin),
986 None,
987 )
988 .unwrap();
989 println!("P2SH-WPKH address: {}", address.address);
990 println!("P2SH-WPKH pubkey: {}", address.public_key);
991 assert_eq!(address.address, "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf");
992 assert_eq!(address.path, path);
993 }
994
995 #[test]
996 fn test_derive_address_p2wpkh() {
997 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
999 let path = "m/84'/0'/0'/0/0";
1000 let address = derive_bitcoin_address(
1001 mnemonic,
1002 Option::from(path),
1003 Option::from(Network::Bitcoin),
1004 None,
1005 )
1006 .unwrap();
1007 println!("P2WPKH address: {}", address.address);
1008 println!("P2WPKH pubkey: {}", address.public_key);
1009 assert_eq!(
1010 address.address,
1011 "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"
1012 );
1013 assert_eq!(address.path, path);
1014 }
1015
1016 #[test]
1017 fn test_derive_address_p2tr() {
1018 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
1020 let path = "m/86'/0'/0'/0/0";
1021 let address = derive_bitcoin_address(
1022 mnemonic,
1023 Option::from(path),
1024 Option::from(Network::Bitcoin),
1025 None,
1026 )
1027 .unwrap();
1028 println!("P2TR address: {}", address.address);
1029 println!("P2TR pubkey: {}", address.public_key);
1030 assert!(address.address.starts_with("bc1p"));
1032 assert_eq!(address.path, path);
1033 }
1034
1035 #[test]
1036 fn test_calculate_script_hash() {
1037 let script_hash = calculate_script_hash(
1039 "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA",
1040 Option::from(Network::Bitcoin),
1041 )
1042 .unwrap();
1043 println!("P2PKH script hash: {}", script_hash);
1044 assert_eq!(
1045 script_hash,
1046 "1e8750b8a4c0912d8b84f7eb53472cbdcb57f9e0cde263b2e51ecbe30853cd68"
1047 );
1048
1049 let script_hash = calculate_script_hash(
1051 "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu",
1052 Option::from(Network::Bitcoin),
1053 )
1054 .unwrap();
1055 println!("P2WPKH script hash: {}", script_hash);
1056 assert_eq!(script_hash.len(), 64); }
1059
1060 #[test]
1061 fn test_derive_private_key() {
1062 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
1064 let path = "m/84'/0'/0'/0/0";
1065 let private_key = derive_private_key(
1066 mnemonic,
1067 Option::from(path),
1068 Option::from(Network::Bitcoin),
1069 None,
1070 )
1071 .unwrap();
1072 assert_eq!(
1073 private_key,
1074 "KyZpNDKnfs94vbrwhJneDi77V6jF64PWPF8x5cdJb8ifgg2DUc9d"
1075 );
1076 }
1077
1078 #[test]
1079 fn test_derive_bitcoin_addresses() {
1080 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
1082 let base_path = "m/84'/0'/0'";
1083 let result = derive_bitcoin_addresses(
1084 mnemonic,
1085 Option::from(base_path),
1086 Option::from(Network::Bitcoin),
1087 None,
1088 None,
1089 None,
1090 Option::from(3),
1091 )
1092 .unwrap();
1093
1094 assert_eq!(result.addresses.len(), 3);
1096
1097 assert_eq!(result.addresses[0].path, "m/84'/0'/0'/0/0");
1099 assert_eq!(result.addresses[1].path, "m/84'/0'/0'/0/1");
1100 assert_eq!(result.addresses[2].path, "m/84'/0'/0'/0/2");
1101
1102 assert_eq!(
1104 result.addresses[0].address,
1105 "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"
1106 );
1107
1108 let full_path = "m/84'/0'/0'/0/5"; let full_path_result = derive_bitcoin_addresses(
1111 mnemonic,
1112 Option::from(full_path),
1113 Option::from(Network::Bitcoin),
1114 None,
1115 None,
1116 Option::from(10),
1117 Option::from(2),
1118 )
1119 .unwrap();
1120
1121 assert_eq!(full_path_result.addresses[0].path, "m/84'/0'/0'/0/10");
1123 assert_eq!(full_path_result.addresses[1].path, "m/84'/0'/0'/0/11");
1124
1125 let full_path_change = derive_bitcoin_addresses(
1127 mnemonic,
1128 Option::from(full_path),
1129 Option::from(Network::Bitcoin),
1130 None,
1131 Option::from(true),
1132 None,
1133 Option::from(2),
1134 )
1135 .unwrap();
1136
1137 assert_eq!(full_path_change.addresses[0].path, "m/84'/0'/0'/1/0");
1139 assert_eq!(full_path_change.addresses[1].path, "m/84'/0'/0'/1/1");
1140
1141 let full_change_path = "m/84'/0'/0'/1/0";
1143 let change_override_result = derive_bitcoin_addresses(
1144 mnemonic,
1145 Option::from(full_change_path),
1146 Option::from(Network::Bitcoin),
1147 None,
1148 Option::from(false),
1149 None,
1150 Option::from(2),
1151 )
1152 .unwrap();
1153
1154 assert_eq!(change_override_result.addresses[0].path, "m/84'/0'/0'/0/0");
1156 assert_eq!(change_override_result.addresses[1].path, "m/84'/0'/0'/0/1");
1157 }
1158
1159 #[test]
1160 fn test_validate_mnemonic() {
1161 let valid_mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
1162 assert!(validate_mnemonic(valid_mnemonic).is_ok());
1163
1164 let invalid_mnemonic = "invalid word sequence that is not valid";
1165 assert!(validate_mnemonic(invalid_mnemonic).is_err());
1166 }
1167
1168 #[test]
1169 fn test_derive_addresses_with_varying_mnemonic_lengths() {
1170 let mnemonic12 = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
1172 let result12 = derive_bitcoin_address(
1173 mnemonic12,
1174 Some("m/84'/0'/0'/0/0"),
1175 Some(Network::Bitcoin),
1176 None,
1177 );
1178 assert!(result12.is_ok());
1179 assert_eq!(result12.unwrap().address, "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu");
1180
1181 let mnemonic15 = generate_mnemonic(Some(WordCount::Words15), None).unwrap();
1183 assert_eq!(mnemonic15.split_whitespace().count(), 15);
1184 let result15 = derive_bitcoin_address(
1185 &mnemonic15,
1186 Some("m/84'/0'/0'/0/0"),
1187 Some(Network::Bitcoin),
1188 None,
1189 );
1190 assert!(result15.is_ok());
1191 assert!(result15.unwrap().address.starts_with("bc1"));
1192
1193 let mnemonic18 = generate_mnemonic(Some(WordCount::Words18), None).unwrap();
1195 assert_eq!(mnemonic18.split_whitespace().count(), 18);
1196 let result18 = derive_bitcoin_address(
1197 &mnemonic18,
1198 Some("m/84'/0'/0'/0/0"),
1199 Some(Network::Bitcoin),
1200 None,
1201 );
1202 assert!(result18.is_ok());
1203 assert!(result18.unwrap().address.starts_with("bc1"));
1204
1205 let mnemonic21 = generate_mnemonic(Some(WordCount::Words21), None).unwrap();
1207 assert_eq!(mnemonic21.split_whitespace().count(), 21);
1208 let result21 = derive_bitcoin_address(
1209 &mnemonic21,
1210 Some("m/84'/0'/0'/0/0"),
1211 Some(Network::Bitcoin),
1212 None,
1213 );
1214 assert!(result21.is_ok());
1215 assert!(result21.unwrap().address.starts_with("bc1"));
1216
1217 let mnemonic24 = generate_mnemonic(Some(WordCount::Words24), None).unwrap();
1219 assert_eq!(mnemonic24.split_whitespace().count(), 24);
1220 let result24 = derive_bitcoin_address(
1221 &mnemonic24,
1222 Some("m/84'/0'/0'/0/0"),
1223 Some(Network::Bitcoin),
1224 None,
1225 );
1226 assert!(result24.is_ok());
1227 assert!(result24.unwrap().address.starts_with("bc1"));
1228 }
1229
1230 #[test]
1231 fn test_is_valid_bip39_word() {
1232 assert!(is_valid_bip39_word("abandon", None));
1233 assert!(is_valid_bip39_word("ABANDON", None)); assert!(!is_valid_bip39_word("notaword", None));
1235 }
1236
1237 #[test]
1238 fn test_get_bip39_suggestions() {
1239 let suggestions = get_bip39_suggestions("ab", 5, None);
1240 assert!(!suggestions.is_empty());
1241 assert!(suggestions.contains(&"abandon".to_string()));
1242 assert!(suggestions.contains(&"ability".to_string()));
1243 assert!(suggestions.len() <= 5);
1244
1245 let mut sorted = suggestions.clone();
1247 sorted.sort();
1248 assert_eq!(suggestions, sorted);
1249 }
1250
1251 #[test]
1252 fn test_get_bip39_wordlist() {
1253 let wordlist = get_bip39_wordlist(None);
1254 assert_eq!(wordlist.len(), 2048);
1255 assert!(wordlist.contains(&"abandon".to_string()));
1256 assert!(wordlist.contains(&"zoo".to_string()));
1257 }
1258
1259 #[test]
1260 fn test_mnemonic_entropy_conversion() {
1261 let mnemonic12 = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
1263 let entropy12 = mnemonic_to_entropy(mnemonic12).unwrap();
1264 assert_eq!(entropy12.len(), 16); let recovered_mnemonic12 = entropy_to_mnemonic(&entropy12, None).unwrap();
1266 assert_eq!(mnemonic12, recovered_mnemonic12);
1267
1268 let generated15 = generate_mnemonic(Some(WordCount::Words15), None).unwrap();
1270 let entropy15 = mnemonic_to_entropy(&generated15).unwrap();
1271 assert_eq!(entropy15.len(), 20); let recovered_mnemonic15 = entropy_to_mnemonic(&entropy15, None).unwrap();
1273 assert_eq!(generated15, recovered_mnemonic15);
1274
1275 let generated18 = generate_mnemonic(Some(WordCount::Words18), None).unwrap();
1277 let entropy18 = mnemonic_to_entropy(&generated18).unwrap();
1278 assert_eq!(entropy18.len(), 24); let recovered_mnemonic18 = entropy_to_mnemonic(&entropy18, None).unwrap();
1280 assert_eq!(generated18, recovered_mnemonic18);
1281
1282 let generated21 = generate_mnemonic(Some(WordCount::Words21), None).unwrap();
1284 let entropy21 = mnemonic_to_entropy(&generated21).unwrap();
1285 assert_eq!(entropy21.len(), 28); let recovered_mnemonic21 = entropy_to_mnemonic(&entropy21, None).unwrap();
1287 assert_eq!(generated21, recovered_mnemonic21);
1288
1289 let generated24 = generate_mnemonic(Some(WordCount::Words24), None).unwrap();
1291 let entropy24 = mnemonic_to_entropy(&generated24).unwrap();
1292 assert_eq!(entropy24.len(), 32); let recovered_mnemonic24 = entropy_to_mnemonic(&entropy24, None).unwrap();
1294 assert_eq!(generated24, recovered_mnemonic24);
1295 }
1296
1297 #[test]
1298 fn test_mnemonic_to_seed() {
1299 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
1300
1301 let seed1 = mnemonic_to_seed(mnemonic, None).unwrap();
1303 assert_eq!(seed1.len(), 64);
1304 let expected_seed = hex::decode("5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4").unwrap();
1305 assert_eq!(seed1, expected_seed);
1306
1307 let seed2 = mnemonic_to_seed(mnemonic, Some("passphrase")).unwrap();
1309 assert_eq!(seed2.len(), 64);
1310
1311 assert_ne!(seed1, seed2);
1313 }
1314}