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