rustywallet_import/
detect.rs1use crate::error::{ImportError, Result};
4use crate::types::{ImportFormat, ImportResult};
5use crate::{import_wif, import_hex, import_mini_key, import_mnemonic, MnemonicImport};
6use rustywallet_hd::Network as HdNetwork;
7use rustywallet_keys::prelude::Network as KeysNetwork;
8
9fn convert_network(network: KeysNetwork) -> HdNetwork {
11 match network {
12 KeysNetwork::Mainnet => HdNetwork::Mainnet,
13 KeysNetwork::Testnet => HdNetwork::Testnet,
14 }
15}
16
17pub fn detect_format(input: &str) -> Option<ImportFormat> {
28 let input = input.trim();
29
30 if input.starts_with("6P") && input.len() == 58 {
32 return Some(ImportFormat::Bip38);
33 }
34
35 if (input.len() == 51 || input.len() == 52) &&
37 (input.starts_with('5') || input.starts_with('K') ||
38 input.starts_with('L') || input.starts_with('9') ||
39 input.starts_with('c')) {
40 return Some(ImportFormat::Wif);
41 }
42
43 let hex_input = input.strip_prefix("0x").or_else(|| input.strip_prefix("0X")).unwrap_or(input);
45 if hex_input.len() == 64 && hex_input.chars().all(|c| c.is_ascii_hexdigit()) {
46 return Some(ImportFormat::Hex);
47 }
48
49 if input.starts_with('S') && (input.len() == 22 || input.len() == 30) {
51 return Some(ImportFormat::MiniKey);
52 }
53
54 let words: Vec<&str> = input.split_whitespace().collect();
56 if [12, 15, 18, 21, 24].contains(&words.len()) {
57 return Some(ImportFormat::Mnemonic);
58 }
59
60 None
61}
62
63pub fn import_any(input: &str) -> Result<ImportResult> {
82 let input = input.trim();
83
84 let format = detect_format(input)
85 .ok_or_else(|| ImportError::InvalidFormat("Could not detect format".to_string()))?;
86
87 match format {
88 ImportFormat::Wif => {
89 let (key, network, compressed) = import_wif(input)?;
90 Ok(ImportResult::new(key, ImportFormat::Wif)
91 .with_network(convert_network(network))
92 .with_compressed(compressed))
93 }
94 ImportFormat::Hex => {
95 let key = import_hex(input)?;
96 Ok(ImportResult::new(key, ImportFormat::Hex)
97 .with_compressed(true))
98 }
99 ImportFormat::MiniKey => {
100 let key = import_mini_key(input)?;
101 Ok(ImportResult::new(key, ImportFormat::MiniKey)
102 .with_compressed(false)) }
104 ImportFormat::Mnemonic => {
105 let config = MnemonicImport::new(input);
106 import_mnemonic(config)
107 }
108 ImportFormat::Bip38 => {
109 Err(ImportError::InvalidFormat(
110 "BIP38 requires password - use import_bip38() directly".to_string()
111 ))
112 }
113 ImportFormat::ElectrumSeed => {
114 Err(ImportError::UnsupportedFormat(
115 "Electrum seed format not yet supported".to_string()
116 ))
117 }
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_detect_wif_uncompressed() {
127 let wif = "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ";
128 assert_eq!(detect_format(wif), Some(ImportFormat::Wif));
129 }
130
131 #[test]
132 fn test_detect_wif_compressed() {
133 let wif = "KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617";
134 assert_eq!(detect_format(wif), Some(ImportFormat::Wif));
135 }
136
137 #[test]
138 fn test_detect_hex() {
139 let hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
140 assert_eq!(detect_format(hex), Some(ImportFormat::Hex));
141 }
142
143 #[test]
144 fn test_detect_hex_with_prefix() {
145 let hex = "0x0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
146 assert_eq!(detect_format(hex), Some(ImportFormat::Hex));
147 }
148
149 #[test]
150 fn test_detect_bip38() {
151 let bip38 = "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg";
152 assert_eq!(detect_format(bip38), Some(ImportFormat::Bip38));
153 }
154
155 #[test]
156 fn test_detect_mini_key() {
157 let mini = "S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy";
158 assert_eq!(detect_format(mini), Some(ImportFormat::MiniKey));
159 }
160
161 #[test]
162 fn test_detect_mnemonic() {
163 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
164 assert_eq!(detect_format(mnemonic), Some(ImportFormat::Mnemonic));
165 }
166
167 #[test]
168 fn test_detect_unknown() {
169 let unknown = "not a valid format";
170 assert_eq!(detect_format(unknown), None);
171 }
172
173 #[test]
174 fn test_import_any_wif() {
175 let wif = "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ";
176 let result = import_any(wif).unwrap();
177 assert_eq!(result.format, ImportFormat::Wif);
178 assert!(!result.compressed);
179 }
180
181 #[test]
182 fn test_import_any_hex() {
183 let hex = "0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d";
184 let result = import_any(hex).unwrap();
185 assert_eq!(result.format, ImportFormat::Hex);
186 }
187
188 #[test]
189 fn test_import_any_mnemonic() {
190 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
191 let result = import_any(mnemonic).unwrap();
192 assert_eq!(result.format, ImportFormat::Mnemonic);
193 }
194
195 #[test]
196 fn test_import_any_bip38_requires_password() {
197 let bip38 = "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg";
198 let result = import_any(bip38);
199 assert!(matches!(result, Err(ImportError::InvalidFormat(_))));
200 }
201}