1use crate::{Error, Result};
11use parking_lot::RwLock;
12use sp_core::crypto::{Ss58AddressFormat, Ss58Codec};
13use sp_core::{ed25519, sr25519, Pair as PairTrait};
14use std::collections::HashMap;
15use std::sync::Arc;
16use tracing::{debug, info};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
20pub enum KeyPairType {
21 #[default]
23 Sr25519,
24 Ed25519,
26}
27
28#[derive(Clone)]
35pub struct Wallet {
36 key_type: KeyPairType,
38 sr25519_pair: Option<sr25519::Pair>,
40 ed25519_pair: Option<ed25519::Pair>,
42 ss58_format: Ss58AddressFormat,
44}
45
46impl Wallet {
47 pub fn new_random() -> Self {
49 Self::new_random_with_type(KeyPairType::Sr25519)
50 }
51
52 pub fn new_random_with_type(key_type: KeyPairType) -> Self {
54 info!("Creating new random {:?} wallet", key_type);
55
56 match key_type {
57 KeyPairType::Sr25519 => {
58 let (pair, _seed) = sr25519::Pair::generate();
59 Self {
60 key_type,
61 sr25519_pair: Some(pair),
62 ed25519_pair: None,
63 ss58_format: Ss58AddressFormat::custom(42), }
65 }
66 KeyPairType::Ed25519 => {
67 let (pair, _seed) = ed25519::Pair::generate();
68 Self {
69 key_type,
70 sr25519_pair: None,
71 ed25519_pair: Some(pair),
72 ss58_format: Ss58AddressFormat::custom(42),
73 }
74 }
75 }
76 }
77
78 #[allow(clippy::result_large_err)]
80 pub fn from_mnemonic(mnemonic: &str, key_type: KeyPairType) -> Result<Self> {
81 Self::from_mnemonic_with_path(mnemonic, None, key_type)
82 }
83
84 #[allow(clippy::result_large_err)]
86 pub fn from_mnemonic_with_path(
87 mnemonic: &str,
88 path: Option<&str>,
89 key_type: KeyPairType,
90 ) -> Result<Self> {
91 info!("Creating wallet from mnemonic with {:?} keys", key_type);
92
93 let _ = bip39::Mnemonic::parse(mnemonic)
95 .map_err(|e| Error::Wallet(format!("Invalid mnemonic: {}", e)))?;
96
97 let full_path = if let Some(p) = path {
99 format!("{}//{}", mnemonic, p)
100 } else {
101 mnemonic.to_string()
102 };
103
104 match key_type {
105 KeyPairType::Sr25519 => {
106 let pair = sr25519::Pair::from_string(&full_path, None)
107 .map_err(|e| Error::Wallet(format!("Failed to derive key: {:?}", e)))?;
108
109 Ok(Self {
110 key_type,
111 sr25519_pair: Some(pair),
112 ed25519_pair: None,
113 ss58_format: Ss58AddressFormat::custom(42),
114 })
115 }
116 KeyPairType::Ed25519 => {
117 let pair = ed25519::Pair::from_string(&full_path, None)
118 .map_err(|e| Error::Wallet(format!("Failed to derive key: {:?}", e)))?;
119
120 Ok(Self {
121 key_type,
122 sr25519_pair: None,
123 ed25519_pair: Some(pair),
124 ss58_format: Ss58AddressFormat::custom(42),
125 })
126 }
127 }
128 }
129
130 #[allow(clippy::result_large_err)]
132 pub fn from_seed(seed: &[u8], key_type: KeyPairType) -> Result<Self> {
133 info!("Creating wallet from seed with {:?} keys", key_type);
134
135 if seed.len() != 32 {
136 return Err(Error::Wallet("Seed must be 32 bytes".to_string()));
137 }
138
139 let mut seed_array = [0u8; 32];
140 seed_array.copy_from_slice(seed);
141
142 match key_type {
143 KeyPairType::Sr25519 => {
144 let pair = sr25519::Pair::from_seed(&seed_array);
145 Ok(Self {
146 key_type,
147 sr25519_pair: Some(pair),
148 ed25519_pair: None,
149 ss58_format: Ss58AddressFormat::custom(42),
150 })
151 }
152 KeyPairType::Ed25519 => {
153 let pair = ed25519::Pair::from_seed(&seed_array);
154 Ok(Self {
155 key_type,
156 sr25519_pair: None,
157 ed25519_pair: Some(pair),
158 ss58_format: Ss58AddressFormat::custom(42),
159 })
160 }
161 }
162 }
163
164 pub fn generate_mnemonic() -> String {
166 use bip39::{Language, Mnemonic};
167 use rand::RngCore;
168
169 let mut entropy = [0u8; 32];
170 rand::rng().fill_bytes(&mut entropy);
171
172 Mnemonic::from_entropy_in(Language::English, &entropy)
173 .expect("Failed to generate mnemonic")
174 .to_string()
175 }
176
177 pub fn with_ss58_format(mut self, format: u16) -> Self {
179 self.ss58_format = Ss58AddressFormat::custom(format);
180 self
181 }
182
183 pub fn public_key(&self) -> Vec<u8> {
185 match self.key_type {
186 KeyPairType::Sr25519 => self.sr25519_pair.as_ref().unwrap().public().0.to_vec(),
187 KeyPairType::Ed25519 => self.ed25519_pair.as_ref().unwrap().public().0.to_vec(),
188 }
189 }
190
191 pub fn address(&self) -> String {
193 match self.key_type {
194 KeyPairType::Sr25519 => {
195 let public = self.sr25519_pair.as_ref().unwrap().public();
196 public.to_ss58check_with_version(self.ss58_format)
197 }
198 KeyPairType::Ed25519 => {
199 let public = self.ed25519_pair.as_ref().unwrap().public();
200 public.to_ss58check_with_version(self.ss58_format)
201 }
202 }
203 }
204
205 pub fn key_type(&self) -> KeyPairType {
207 self.key_type
208 }
209
210 pub fn sign(&self, message: &[u8]) -> Vec<u8> {
212 match self.key_type {
213 KeyPairType::Sr25519 => {
214 let pair = self.sr25519_pair.as_ref().unwrap();
215 pair.sign(message).0.to_vec()
216 }
217 KeyPairType::Ed25519 => {
218 let pair = self.ed25519_pair.as_ref().unwrap();
219 pair.sign(message).0.to_vec()
220 }
221 }
222 }
223
224 pub fn verify(&self, message: &[u8], signature: &[u8]) -> bool {
226 match self.key_type {
227 KeyPairType::Sr25519 => {
228 if signature.len() != 64 {
229 return false;
230 }
231 let mut sig_array = [0u8; 64];
232 sig_array.copy_from_slice(signature);
233 let sig = sr25519::Signature::from_raw(sig_array);
234 let public = self.sr25519_pair.as_ref().unwrap().public();
235 sr25519::Pair::verify(&sig, message, &public)
236 }
237 KeyPairType::Ed25519 => {
238 if signature.len() != 64 {
239 return false;
240 }
241 let mut sig_array = [0u8; 64];
242 sig_array.copy_from_slice(signature);
243 let sig = ed25519::Signature::from_raw(sig_array);
244 let public = self.ed25519_pair.as_ref().unwrap().public();
245 ed25519::Pair::verify(&sig, message, &public)
246 }
247 }
248 }
249
250 pub fn seed(&self) -> Option<[u8; 32]> {
253 match self.key_type {
254 KeyPairType::Sr25519 => {
255 None
257 }
258 KeyPairType::Ed25519 => {
259 None
261 }
262 }
263 }
264
265 pub fn sr25519_pair(&self) -> Option<&sr25519::Pair> {
267 self.sr25519_pair.as_ref()
268 }
269
270 pub fn ed25519_pair(&self) -> Option<&ed25519::Pair> {
272 self.ed25519_pair.as_ref()
273 }
274}
275
276impl std::fmt::Debug for Wallet {
277 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278 f.debug_struct("Wallet")
279 .field("key_type", &self.key_type)
280 .field("address", &self.address())
281 .field("ss58_format", &self.ss58_format)
282 .finish()
283 }
284}
285
286pub struct WalletManager {
288 wallets: Arc<RwLock<HashMap<String, Wallet>>>,
289 default_key_type: KeyPairType,
290}
291
292impl WalletManager {
293 pub fn new() -> Self {
295 Self {
296 wallets: Arc::new(RwLock::new(HashMap::new())),
297 default_key_type: KeyPairType::Sr25519,
298 }
299 }
300
301 pub fn with_key_type(key_type: KeyPairType) -> Self {
303 Self {
304 wallets: Arc::new(RwLock::new(HashMap::new())),
305 default_key_type: key_type,
306 }
307 }
308
309 pub fn create_wallet(&self, name: impl Into<String>) -> Wallet {
311 let wallet = Wallet::new_random_with_type(self.default_key_type);
312 let name = name.into();
313
314 debug!("Creating wallet '{}' at address {}", name, wallet.address());
315
316 self.wallets.write().insert(name.clone(), wallet.clone());
317 wallet
318 }
319
320 pub fn add_wallet(&self, name: impl Into<String>, wallet: Wallet) {
322 let name = name.into();
323 debug!("Adding wallet '{}' at address {}", name, wallet.address());
324 self.wallets.write().insert(name, wallet);
325 }
326
327 pub fn get_wallet(&self, name: &str) -> Option<Wallet> {
329 self.wallets.read().get(name).cloned()
330 }
331
332 pub fn remove_wallet(&self, name: &str) -> Option<Wallet> {
334 debug!("Removing wallet '{}'", name);
335 self.wallets.write().remove(name)
336 }
337
338 pub fn list_wallets(&self) -> Vec<String> {
340 self.wallets.read().keys().cloned().collect()
341 }
342
343 pub fn wallet_count(&self) -> usize {
345 self.wallets.read().len()
346 }
347
348 pub fn clear(&self) {
350 debug!("Clearing all wallets");
351 self.wallets.write().clear();
352 }
353}
354
355impl Default for WalletManager {
356 fn default() -> Self {
357 Self::new()
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use super::*;
364
365 #[test]
366 fn test_create_random_wallet() {
367 let wallet = Wallet::new_random();
368 assert_eq!(wallet.key_type(), KeyPairType::Sr25519);
369 assert!(!wallet.address().is_empty());
370 assert!(!wallet.public_key().is_empty());
371 }
372
373 #[test]
374 fn test_create_wallet_types() {
375 let sr25519_wallet = Wallet::new_random_with_type(KeyPairType::Sr25519);
376 assert_eq!(sr25519_wallet.key_type(), KeyPairType::Sr25519);
377
378 let ed25519_wallet = Wallet::new_random_with_type(KeyPairType::Ed25519);
379 assert_eq!(ed25519_wallet.key_type(), KeyPairType::Ed25519);
380 }
381
382 #[test]
383 fn test_sign_and_verify() {
384 let wallet = Wallet::new_random();
385 let message = b"Hello, Substrate!";
386
387 let signature = wallet.sign(message);
388 assert_eq!(signature.len(), 64);
389
390 assert!(wallet.verify(message, &signature));
391 assert!(!wallet.verify(b"Different message", &signature));
392 }
393
394 #[test]
395 fn test_generate_mnemonic() {
396 let mnemonic = Wallet::generate_mnemonic();
397 assert!(!mnemonic.is_empty());
398
399 let wallet = Wallet::from_mnemonic(&mnemonic, KeyPairType::Sr25519);
401 assert!(wallet.is_ok());
402 }
403
404 #[test]
405 fn test_wallet_from_mnemonic() {
406 let mnemonic = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
407
408 let wallet1 = Wallet::from_mnemonic(mnemonic, KeyPairType::Sr25519).unwrap();
409 let wallet2 = Wallet::from_mnemonic(mnemonic, KeyPairType::Sr25519).unwrap();
410
411 assert_eq!(wallet1.address(), wallet2.address());
413 }
414
415 #[test]
416 fn test_wallet_with_ss58_format() {
417 let wallet = Wallet::new_random().with_ss58_format(0); let address = wallet.address();
419
420 assert!(address.starts_with('1'));
422 }
423
424 #[test]
425 fn test_wallet_from_seed() {
426 let seed = [42u8; 32];
427 let wallet1 = Wallet::from_seed(&seed, KeyPairType::Sr25519).unwrap();
428 let wallet2 = Wallet::from_seed(&seed, KeyPairType::Sr25519).unwrap();
429
430 assert_eq!(wallet1.address(), wallet2.address());
432 }
433
434 #[test]
435 fn test_wallet_manager() {
436 let manager = WalletManager::new();
437
438 let wallet1 = manager.create_wallet("wallet1");
439 assert_eq!(manager.wallet_count(), 1);
440
441 let wallet2 = Wallet::new_random();
442 manager.add_wallet("wallet2", wallet2.clone());
443 assert_eq!(manager.wallet_count(), 2);
444
445 let retrieved = manager.get_wallet("wallet1").unwrap();
446 assert_eq!(retrieved.address(), wallet1.address());
447
448 let names = manager.list_wallets();
449 assert_eq!(names.len(), 2);
450 assert!(names.contains(&"wallet1".to_string()));
451 assert!(names.contains(&"wallet2".to_string()));
452
453 manager.remove_wallet("wallet1");
454 assert_eq!(manager.wallet_count(), 1);
455
456 manager.clear();
457 assert_eq!(manager.wallet_count(), 0);
458 }
459
460 #[test]
461 fn test_different_key_types_produce_different_addresses() {
462 let seed = [42u8; 32];
463 let sr25519_wallet = Wallet::from_seed(&seed, KeyPairType::Sr25519).unwrap();
464 let ed25519_wallet = Wallet::from_seed(&seed, KeyPairType::Ed25519).unwrap();
465
466 assert_ne!(sr25519_wallet.address(), ed25519_wallet.address());
469 }
470}