1use crate::{Error, Result};
20use parking_lot::RwLock;
21use sp_core::crypto::{Ss58AddressFormat, Ss58Codec};
22use sp_core::{ed25519, sr25519, Pair as PairTrait};
23use std::collections::HashMap;
24use std::sync::Arc;
25use tracing::{debug, info};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
29pub enum KeyPairType {
30 #[default]
32 Sr25519,
33 Ed25519,
35}
36
37#[derive(Clone)]
74pub struct Wallet {
75 key_type: KeyPairType,
77 sr25519_pair: Option<sr25519::Pair>,
79 ed25519_pair: Option<ed25519::Pair>,
81 ss58_format: Ss58AddressFormat,
83}
84
85impl Wallet {
86 pub fn new_random() -> Self {
88 Self::new_random_with_type(KeyPairType::Sr25519)
89 }
90
91 pub fn new_random_with_type(key_type: KeyPairType) -> Self {
93 info!("Creating new random {:?} wallet", key_type);
94
95 match key_type {
96 KeyPairType::Sr25519 => {
97 let (pair, _seed) = sr25519::Pair::generate();
98 Self {
99 key_type,
100 sr25519_pair: Some(pair),
101 ed25519_pair: None,
102 ss58_format: Ss58AddressFormat::custom(42), }
104 }
105 KeyPairType::Ed25519 => {
106 let (pair, _seed) = ed25519::Pair::generate();
107 Self {
108 key_type,
109 sr25519_pair: None,
110 ed25519_pair: Some(pair),
111 ss58_format: Ss58AddressFormat::custom(42),
112 }
113 }
114 }
115 }
116
117 pub fn from_mnemonic(mnemonic: &str, key_type: KeyPairType) -> Result<Self> {
119 Self::from_mnemonic_with_path(mnemonic, None, key_type)
120 }
121
122 pub fn from_mnemonic_with_path(
124 mnemonic: &str,
125 path: Option<&str>,
126 key_type: KeyPairType,
127 ) -> Result<Self> {
128 info!("Creating wallet from mnemonic with {:?} keys", key_type);
129
130 let _ = bip39::Mnemonic::parse(mnemonic)
132 .map_err(|e| Error::Wallet(format!("Invalid mnemonic: {}", e)))?;
133
134 let full_path = if let Some(p) = path {
136 format!("{}//{}", mnemonic, p)
137 } else {
138 mnemonic.to_string()
139 };
140
141 match key_type {
142 KeyPairType::Sr25519 => {
143 let pair = sr25519::Pair::from_string(&full_path, None)
144 .map_err(|e| Error::Wallet(format!("Failed to derive key: {:?}", e)))?;
145
146 Ok(Self {
147 key_type,
148 sr25519_pair: Some(pair),
149 ed25519_pair: None,
150 ss58_format: Ss58AddressFormat::custom(42),
151 })
152 }
153 KeyPairType::Ed25519 => {
154 let pair = ed25519::Pair::from_string(&full_path, None)
155 .map_err(|e| Error::Wallet(format!("Failed to derive key: {:?}", e)))?;
156
157 Ok(Self {
158 key_type,
159 sr25519_pair: None,
160 ed25519_pair: Some(pair),
161 ss58_format: Ss58AddressFormat::custom(42),
162 })
163 }
164 }
165 }
166
167 pub fn from_seed(seed: &[u8], key_type: KeyPairType) -> Result<Self> {
169 info!("Creating wallet from seed with {:?} keys", key_type);
170
171 if seed.len() != 32 {
172 return Err(Error::Wallet("Seed must be 32 bytes".to_string()));
173 }
174
175 let mut seed_array = [0u8; 32];
176 seed_array.copy_from_slice(seed);
177
178 match key_type {
179 KeyPairType::Sr25519 => {
180 let pair = sr25519::Pair::from_seed(&seed_array);
181 Ok(Self {
182 key_type,
183 sr25519_pair: Some(pair),
184 ed25519_pair: None,
185 ss58_format: Ss58AddressFormat::custom(42),
186 })
187 }
188 KeyPairType::Ed25519 => {
189 let pair = ed25519::Pair::from_seed(&seed_array);
190 Ok(Self {
191 key_type,
192 sr25519_pair: None,
193 ed25519_pair: Some(pair),
194 ss58_format: Ss58AddressFormat::custom(42),
195 })
196 }
197 }
198 }
199
200 pub fn generate_mnemonic() -> String {
202 use bip39::{Language, Mnemonic};
203 use rand::RngCore;
204
205 let mut entropy = [0u8; 32];
206 rand::rng().fill_bytes(&mut entropy);
207
208 Mnemonic::from_entropy_in(Language::English, &entropy)
209 .expect("Failed to generate mnemonic")
210 .to_string()
211 }
212
213 pub fn with_ss58_format(mut self, format: u16) -> Self {
215 self.ss58_format = Ss58AddressFormat::custom(format);
216 self
217 }
218
219 pub fn public_key(&self) -> Vec<u8> {
221 match self.key_type {
222 KeyPairType::Sr25519 => self.sr25519_pair.as_ref().unwrap().public().0.to_vec(),
223 KeyPairType::Ed25519 => self.ed25519_pair.as_ref().unwrap().public().0.to_vec(),
224 }
225 }
226
227 pub fn address(&self) -> String {
229 match self.key_type {
230 KeyPairType::Sr25519 => {
231 let public = self.sr25519_pair.as_ref().unwrap().public();
232 public.to_ss58check_with_version(self.ss58_format)
233 }
234 KeyPairType::Ed25519 => {
235 let public = self.ed25519_pair.as_ref().unwrap().public();
236 public.to_ss58check_with_version(self.ss58_format)
237 }
238 }
239 }
240
241 pub fn key_type(&self) -> KeyPairType {
243 self.key_type
244 }
245
246 pub fn sign(&self, message: &[u8]) -> Vec<u8> {
248 match self.key_type {
249 KeyPairType::Sr25519 => {
250 let pair = self.sr25519_pair.as_ref().unwrap();
251 pair.sign(message).0.to_vec()
252 }
253 KeyPairType::Ed25519 => {
254 let pair = self.ed25519_pair.as_ref().unwrap();
255 pair.sign(message).0.to_vec()
256 }
257 }
258 }
259
260 pub fn verify(&self, message: &[u8], signature: &[u8]) -> bool {
262 match self.key_type {
263 KeyPairType::Sr25519 => {
264 if signature.len() != 64 {
265 return false;
266 }
267 let mut sig_array = [0u8; 64];
268 sig_array.copy_from_slice(signature);
269 let sig = sr25519::Signature::from_raw(sig_array);
270 let public = self.sr25519_pair.as_ref().unwrap().public();
271 sr25519::Pair::verify(&sig, message, &public)
272 }
273 KeyPairType::Ed25519 => {
274 if signature.len() != 64 {
275 return false;
276 }
277 let mut sig_array = [0u8; 64];
278 sig_array.copy_from_slice(signature);
279 let sig = ed25519::Signature::from_raw(sig_array);
280 let public = self.ed25519_pair.as_ref().unwrap().public();
281 ed25519::Pair::verify(&sig, message, &public)
282 }
283 }
284 }
285
286 pub fn seed(&self) -> Option<[u8; 32]> {
289 match self.key_type {
290 KeyPairType::Sr25519 => {
291 None
293 }
294 KeyPairType::Ed25519 => {
295 None
297 }
298 }
299 }
300
301 pub fn sr25519_pair(&self) -> Option<&sr25519::Pair> {
303 self.sr25519_pair.as_ref()
304 }
305
306 pub fn ed25519_pair(&self) -> Option<&ed25519::Pair> {
308 self.ed25519_pair.as_ref()
309 }
310}
311
312impl std::fmt::Debug for Wallet {
313 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
314 f.debug_struct("Wallet")
315 .field("key_type", &self.key_type)
316 .field("address", &self.address())
317 .field("ss58_format", &self.ss58_format)
318 .finish()
319 }
320}
321
322impl Drop for Wallet {
323 fn drop(&mut self) {
324 debug!(
327 "Dropping wallet of type {:?} at address {}",
328 self.key_type,
329 self.address()
330 );
331 }
332}
333
334pub struct WalletManager {
336 wallets: Arc<RwLock<HashMap<String, Wallet>>>,
337 default_key_type: KeyPairType,
338}
339
340impl WalletManager {
341 pub fn new() -> Self {
343 Self {
344 wallets: Arc::new(RwLock::new(HashMap::new())),
345 default_key_type: KeyPairType::Sr25519,
346 }
347 }
348
349 pub fn with_key_type(key_type: KeyPairType) -> Self {
351 Self {
352 wallets: Arc::new(RwLock::new(HashMap::new())),
353 default_key_type: key_type,
354 }
355 }
356
357 pub fn create_wallet(&self, name: impl Into<String>) -> Wallet {
359 let wallet = Wallet::new_random_with_type(self.default_key_type);
360 let name = name.into();
361
362 debug!("Creating wallet '{}' at address {}", name, wallet.address());
363
364 self.wallets.write().insert(name.clone(), wallet.clone());
365 wallet
366 }
367
368 pub fn add_wallet(&self, name: impl Into<String>, wallet: Wallet) {
370 let name = name.into();
371 debug!("Adding wallet '{}' at address {}", name, wallet.address());
372 self.wallets.write().insert(name, wallet);
373 }
374
375 pub fn get_wallet(&self, name: &str) -> Option<Wallet> {
377 self.wallets.read().get(name).cloned()
378 }
379
380 pub fn remove_wallet(&self, name: &str) -> Option<Wallet> {
382 debug!("Removing wallet '{}'", name);
383 self.wallets.write().remove(name)
384 }
385
386 pub fn list_wallets(&self) -> Vec<String> {
388 self.wallets.read().keys().cloned().collect()
389 }
390
391 pub fn wallet_count(&self) -> usize {
393 self.wallets.read().len()
394 }
395
396 pub fn clear(&self) {
398 debug!("Clearing all wallets");
399 self.wallets.write().clear();
400 }
401}
402
403impl Default for WalletManager {
404 fn default() -> Self {
405 Self::new()
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412
413 #[test]
414 fn test_create_random_wallet() {
415 let wallet = Wallet::new_random();
416 assert_eq!(wallet.key_type(), KeyPairType::Sr25519);
417 assert!(!wallet.address().is_empty());
418 assert!(!wallet.public_key().is_empty());
419 }
420
421 #[test]
422 fn test_create_wallet_types() {
423 let sr25519_wallet = Wallet::new_random_with_type(KeyPairType::Sr25519);
424 assert_eq!(sr25519_wallet.key_type(), KeyPairType::Sr25519);
425
426 let ed25519_wallet = Wallet::new_random_with_type(KeyPairType::Ed25519);
427 assert_eq!(ed25519_wallet.key_type(), KeyPairType::Ed25519);
428 }
429
430 #[test]
431 fn test_sign_and_verify() {
432 let wallet = Wallet::new_random();
433 let message = b"Hello, Substrate!";
434
435 let signature = wallet.sign(message);
436 assert_eq!(signature.len(), 64);
437
438 assert!(wallet.verify(message, &signature));
439 assert!(!wallet.verify(b"Different message", &signature));
440 }
441
442 #[test]
443 fn test_generate_mnemonic() {
444 let mnemonic = Wallet::generate_mnemonic();
445 assert!(!mnemonic.is_empty());
446
447 let wallet = Wallet::from_mnemonic(&mnemonic, KeyPairType::Sr25519);
449 assert!(wallet.is_ok());
450 }
451
452 #[test]
453 fn test_wallet_from_mnemonic() {
454 let mnemonic = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
455
456 let wallet1 = Wallet::from_mnemonic(mnemonic, KeyPairType::Sr25519).unwrap();
457 let wallet2 = Wallet::from_mnemonic(mnemonic, KeyPairType::Sr25519).unwrap();
458
459 assert_eq!(wallet1.address(), wallet2.address());
461 }
462
463 #[test]
464 fn test_wallet_with_ss58_format() {
465 let wallet = Wallet::new_random().with_ss58_format(0); let address = wallet.address();
467
468 assert!(address.starts_with('1'));
470 }
471
472 #[test]
473 fn test_wallet_from_seed() {
474 let seed = [42u8; 32];
475 let wallet1 = Wallet::from_seed(&seed, KeyPairType::Sr25519).unwrap();
476 let wallet2 = Wallet::from_seed(&seed, KeyPairType::Sr25519).unwrap();
477
478 assert_eq!(wallet1.address(), wallet2.address());
480 }
481
482 #[test]
483 fn test_wallet_manager() {
484 let manager = WalletManager::new();
485
486 let wallet1 = manager.create_wallet("wallet1");
487 assert_eq!(manager.wallet_count(), 1);
488
489 let wallet2 = Wallet::new_random();
490 manager.add_wallet("wallet2", wallet2.clone());
491 assert_eq!(manager.wallet_count(), 2);
492
493 let retrieved = manager.get_wallet("wallet1").unwrap();
494 assert_eq!(retrieved.address(), wallet1.address());
495
496 let names = manager.list_wallets();
497 assert_eq!(names.len(), 2);
498 assert!(names.contains(&"wallet1".to_string()));
499 assert!(names.contains(&"wallet2".to_string()));
500
501 manager.remove_wallet("wallet1");
502 assert_eq!(manager.wallet_count(), 1);
503
504 manager.clear();
505 assert_eq!(manager.wallet_count(), 0);
506 }
507
508 #[test]
509 fn test_different_key_types_produce_different_addresses() {
510 let seed = [42u8; 32];
511 let sr25519_wallet = Wallet::from_seed(&seed, KeyPairType::Sr25519).unwrap();
512 let ed25519_wallet = Wallet::from_seed(&seed, KeyPairType::Ed25519).unwrap();
513
514 assert_ne!(sr25519_wallet.address(), ed25519_wallet.address());
517 }
518}