Skip to main content

sol_safekey/
lib.rs

1//! # Sol SafeKey
2//!
3//! A powerful Solana key management library with military-grade encryption.
4//!
5//! ## Features
6//!
7//! - **Simple Encryption**: Password-based encryption for Solana private keys
8//! - **Triple-Factor Authentication**: Hardware fingerprint + master password + security question
9//! - **2FA Support**: TOTP-based two-factor authentication
10//! - **Cross-Platform**: Works on macOS, Linux, and Windows
11//!
12//! ## Quick Start
13//!
14//! ```rust
15//! use sol_safekey::{KeyManager, EncryptionResult};
16//!
17//! // Generate a new Solana keypair
18//! let keypair = KeyManager::generate_keypair();
19//!
20//! // Encrypt with password
21//! let encrypted = KeyManager::encrypt_with_password(
22//!     &keypair.to_base58_string(),
23//!     "my_strong_password"
24//! ).unwrap();
25//!
26//! // Decrypt with password
27//! let decrypted = KeyManager::decrypt_with_password(
28//!     &encrypted,
29//!     "my_strong_password"
30//! ).unwrap();
31//! ```
32
33use base64::engine::general_purpose;
34use base64::Engine;
35use ring::digest;
36
37// Re-export modules for advanced usage (conditional compilation)
38#[cfg(feature = "2fa")]
39pub mod totp;
40
41#[cfg(feature = "2fa")]
42pub mod secure_totp;
43
44#[cfg(feature = "2fa")]
45pub mod hardware_fingerprint;
46
47#[cfg(feature = "2fa")]
48pub mod security_question;
49
50// Interactive menu module - needed for bot integration
51pub mod interactive;
52
53// Bot helper module for easy bot integration (no CLI dependency)
54pub mod bot_helper;
55
56// Solana operations interactive menu
57pub mod operations;
58
59// Solana utilities for token operations
60#[cfg(any(feature = "solana-ops", feature = "sol-trade-sdk"))]
61pub mod solana_utils;
62
63// Re-export commonly used types
64pub use solana_sdk::signature::{Keypair, Signer};
65pub use solana_sdk::pubkey::Pubkey;
66
67// ============================================================================
68// Core Encryption/Decryption Functions
69// ============================================================================
70
71/// Simple XOR encryption/decryption using a 32-byte key
72fn xor_encrypt_decrypt(data: &[u8], key: &[u8; 32]) -> Vec<u8> {
73    let mut result = Vec::with_capacity(data.len());
74
75    // Generate keystream from the key
76    let mut keystream = Vec::new();
77    let mut i: u32 = 0;
78    while keystream.len() < data.len() {
79        let mut ctx = digest::Context::new(&digest::SHA256);
80        ctx.update(key);
81        ctx.update(&i.to_le_bytes());
82        let hash = ctx.finish();
83        keystream.extend_from_slice(hash.as_ref());
84        i += 1;
85    }
86
87    // XOR operation
88    for (i, &byte) in data.iter().enumerate() {
89        result.push(byte ^ keystream[i % keystream.len()]);
90    }
91
92    result
93}
94
95/// Encrypt a string with a 32-byte encryption key
96///
97/// Returns base64-encoded encrypted data
98pub fn encrypt_key(secret_key: &str, encryption_key: &[u8; 32]) -> Result<String, String> {
99    let data = secret_key.as_bytes();
100    let encrypted = xor_encrypt_decrypt(data, encryption_key);
101    Ok(general_purpose::STANDARD.encode(encrypted))
102}
103
104/// Decrypt a base64-encoded encrypted string with a 32-byte encryption key
105///
106/// Returns the original plaintext string
107pub fn decrypt_key(encrypted_data: &str, encryption_key: &[u8; 32]) -> Result<String, String> {
108    let ciphertext = general_purpose::STANDARD.decode(encrypted_data)
109        .map_err(|_| "Invalid encrypted data format".to_string())?;
110
111    let decrypted = xor_encrypt_decrypt(&ciphertext, encryption_key);
112
113    String::from_utf8(decrypted)
114        .map_err(|_| "Invalid UTF-8 data in decrypted content".to_string())
115}
116
117/// Minimum password length for encryption/decryption
118pub const MIN_PASSWORD_LENGTH: usize = 10;
119
120/// Maximum password length for encryption/decryption
121pub const MAX_PASSWORD_LENGTH: usize = 20;
122
123/// Fixed salt for password hashing
124/// This prevents rainbow table attacks and ensures consistent key derivation
125const PASSWORD_SALT: &[u8] = b"sol-safekey-v1-salt-2025";
126
127/// Generate a 16-byte encryption key from a password using SHA-256
128///
129/// This function:
130/// 1. Combines password with fixed salt
131/// 2. Hashes using SHA-256 (produces 32 bytes)
132/// 3. Takes the first 16 bytes of the hash as the encryption key
133///
134/// Password requirements:
135/// - Minimum length: 10 characters
136/// - Maximum length: 20 characters
137pub fn generate_encryption_key_simple(password: &str) -> [u8; 32] {
138    // Combine password with fixed salt
139    let mut salted_password = password.as_bytes().to_vec();
140    salted_password.extend_from_slice(PASSWORD_SALT);
141
142    // Hash the salted password using SHA-256
143    let hash = digest::digest(&digest::SHA256, &salted_password);
144
145    // Take the first 16 bytes of the hash
146    let mut key = [0u8; 32];
147    key[0..16].copy_from_slice(&hash.as_ref()[0..16]);
148
149    // Fill the remaining 16 bytes by repeating the first 16 bytes
150    // This ensures we have a 32-byte key for compatibility
151    key[16..32].copy_from_slice(&hash.as_ref()[0..16]);
152
153    key
154}
155
156// ============================================================================
157// High-Level Key Management API (简单集成用)
158// ============================================================================
159
160/// Result type for encryption operations
161pub type EncryptionResult<T> = Result<T, String>;
162
163/// Main interface for key management operations
164///
165/// This is the recommended API for library integration.
166/// It provides simple, safe methods for common key operations.
167pub struct KeyManager;
168
169impl KeyManager {
170    /// Generate a new Solana keypair
171    ///
172    /// # Example
173    ///
174    /// ```
175    /// use sol_safekey::KeyManager;
176    ///
177    /// let keypair = KeyManager::generate_keypair();
178    /// println!("Public key: {}", keypair.pubkey());
179    /// ```
180    pub fn generate_keypair() -> Keypair {
181        Keypair::new()
182    }
183
184    /// Encrypt a private key with a password
185    ///
186    /// # Arguments
187    ///
188    /// * `private_key` - The private key in base58 string format
189    /// * `password` - The password to use for encryption
190    ///
191    /// # Returns
192    ///
193    /// Base64-encoded encrypted string
194    ///
195    /// # Example
196    ///
197    /// ```
198    /// use sol_safekey::KeyManager;
199    ///
200    /// let keypair = KeyManager::generate_keypair();
201    /// let private_key = keypair.to_base58_string();
202    ///
203    /// let encrypted = KeyManager::encrypt_with_password(
204    ///     &private_key,
205    ///     "my_password"
206    /// ).unwrap();
207    /// ```
208    pub fn encrypt_with_password(private_key: &str, password: &str) -> EncryptionResult<String> {
209        let key = generate_encryption_key_simple(password);
210        encrypt_key(private_key, &key)
211    }
212
213    /// Decrypt a private key with a password
214    ///
215    /// # Arguments
216    ///
217    /// * `encrypted_data` - Base64-encoded encrypted data
218    /// * `password` - The password used for encryption
219    ///
220    /// # Returns
221    ///
222    /// The original private key in base58 string format
223    ///
224    /// # Example
225    ///
226    /// ```
227    /// use sol_safekey::KeyManager;
228    ///
229    /// let encrypted = "..."; // from encryption
230    /// let decrypted = KeyManager::decrypt_with_password(
231    ///     encrypted,
232    ///     "my_password"
233    /// ).unwrap();
234    /// ```
235    pub fn decrypt_with_password(encrypted_data: &str, password: &str) -> EncryptionResult<String> {
236        let key = generate_encryption_key_simple(password);
237        decrypt_key(encrypted_data, &key)
238    }
239
240    /// Get public key from a private key
241    ///
242    /// # Arguments
243    ///
244    /// * `private_key` - Private key in base58 string format
245    ///
246    /// # Returns
247    ///
248    /// Public key as a base58 string
249    pub fn get_public_key(private_key: &str) -> EncryptionResult<String> {
250        use solana_sdk::signature::Keypair;
251
252        // Solana 3.0 uses from_base58_string directly
253        let keypair = Keypair::from_base58_string(private_key);
254
255        Ok(keypair.pubkey().to_string())
256    }
257
258    /// Encrypt a keypair to a JSON keystore format
259    ///
260    /// This creates a standard encrypted keystore file compatible with Solana tools.
261    ///
262    /// # Arguments
263    ///
264    /// * `keypair` - The Solana keypair to encrypt
265    /// * `password` - The password for encryption
266    ///
267    /// # Returns
268    ///
269    /// JSON string containing the encrypted keystore
270    pub fn keypair_to_encrypted_json(keypair: &Keypair, password: &str) -> EncryptionResult<String> {
271        use serde_json::json;
272        use chrono::Utc;
273
274        let private_key = keypair.to_base58_string();
275        let public_key = keypair.pubkey().to_string();
276
277        let encrypted = Self::encrypt_with_password(&private_key, password)?;
278
279        let keystore = json!({
280            "encrypted_private_key": encrypted,
281            "public_key": public_key,
282            "encryption_type": "password_only",
283            "created_at": Utc::now().to_rfc3339(),
284        });
285
286        Ok(keystore.to_string())
287    }
288
289    /// Decrypt a keypair from encrypted JSON keystore
290    ///
291    /// # Arguments
292    ///
293    /// * `json_data` - The encrypted keystore JSON string
294    /// * `password` - The password used for encryption
295    ///
296    /// # Returns
297    ///
298    /// The restored Keypair
299    pub fn keypair_from_encrypted_json(json_data: &str, password: &str) -> EncryptionResult<Keypair> {
300        use serde_json::Value;
301
302        let data: Value = serde_json::from_str(json_data)
303            .map_err(|_| "Invalid JSON format")?;
304
305        let encrypted = data["encrypted_private_key"]
306            .as_str()
307            .ok_or("Missing encrypted_private_key field")?;
308
309        let private_key_str = Self::decrypt_with_password(encrypted, password)?;
310
311        // Solana 3.0 uses from_base58_string directly
312        let keypair = Keypair::from_base58_string(&private_key_str);
313
314        Ok(keypair)
315    }
316}
317
318// ============================================================================
319// Advanced 2FA Functions (CLI 工具使用,库集成可选)
320// ============================================================================
321
322// ============================================================================
323// 2FA Functions (only available with "2fa" feature)
324// ============================================================================
325
326#[cfg(feature = "2fa")]
327/// Derive a TOTP secret from password
328///
329/// This is used internally for deterministic 2FA key generation.
330#[allow(dead_code)]
331fn derive_totp_secret_from_password(password: &str, account: &str, issuer: &str) -> Result<String, String> {
332    use ring::pbkdf2;
333    use data_encoding::BASE32_NOPAD;
334    use std::num::NonZeroU32;
335
336    let salt = format!("sol-safekey-totp-{}-{}", issuer, account);
337    let iterations = NonZeroU32::new(100_000)
338        .ok_or("Invalid iteration count")?;
339
340    let mut secret = [0u8; 20]; // 160 bits for TOTP
341    pbkdf2::derive(
342        pbkdf2::PBKDF2_HMAC_SHA256,
343        iterations,
344        salt.as_bytes(),
345        password.as_bytes(),
346        &mut secret,
347    );
348
349    Ok(BASE32_NOPAD.encode(&secret))
350}
351
352#[cfg(feature = "2fa")]
353/// Derive TOTP secret from hardware fingerprint and password
354///
355/// This creates a deterministic 2FA key bound to specific hardware.
356pub fn derive_totp_secret_from_hardware_and_password(
357    hardware_fingerprint: &str,
358    master_password: &str,
359    account: &str,
360    issuer: &str,
361) -> Result<String, String> {
362    use ring::pbkdf2;
363    use data_encoding::BASE32_NOPAD;
364    use std::num::NonZeroU32;
365
366    let key_material = format!("{}::{}", hardware_fingerprint, master_password);
367    let salt = format!("sol-safekey-2fa-{}-{}", issuer, account);
368    let iterations = NonZeroU32::new(100_000)
369        .ok_or("Invalid iteration count")?;
370
371    let mut secret = [0u8; 20];
372    pbkdf2::derive(
373        pbkdf2::PBKDF2_HMAC_SHA256,
374        iterations,
375        salt.as_bytes(),
376        key_material.as_bytes(),
377        &mut secret,
378    );
379
380    Ok(BASE32_NOPAD.encode(&secret))
381}
382
383#[cfg(feature = "2fa")]
384/// Verify a TOTP code
385fn verify_current_totp_code(totp_secret: &str, current_code: &str) -> Result<(), String> {
386    use crate::totp::{TOTPConfig, TOTPManager};
387
388    let config = TOTPConfig {
389        secret: totp_secret.to_string(),
390        account: "wallet".to_string(),
391        issuer: "Sol-SafeKey".to_string(),
392        algorithm: "SHA1".to_string(),
393        digits: 6,
394        step: 30,
395    };
396
397    let totp_manager = TOTPManager::new(config);
398
399    match totp_manager.verify_code(current_code) {
400        Ok(true) => Ok(()),
401        Ok(false) => Err("验证失败,请检查主密码、安全问题答案或2FA验证码".to_string()),
402        Err(e) => Err(format!("验证失败: {}", e)),
403    }
404}
405
406// ============================================================================
407// Triple-Factor Encryption (only available with "2fa" feature)
408// ============================================================================
409
410#[cfg(feature = "2fa")]
411/// Generate a triple-factor encryption key
412///
413/// Combines hardware fingerprint + master password + security answer
414pub fn generate_triple_factor_key(
415    hardware_fingerprint: &str,
416    master_password: &str,
417    security_answer: &str,
418) -> [u8; 32] {
419    use ring::pbkdf2;
420    use std::num::NonZeroU32;
421
422    let key_material = format!(
423        "HW:{}|PASS:{}|QA:{}",
424        hardware_fingerprint,
425        master_password,
426        security_answer.trim().to_lowercase()
427    );
428
429    let salt = b"sol-safekey-triple-factor-v1";
430    let iterations = NonZeroU32::new(200_000).unwrap();
431
432    let mut key = [0u8; 32];
433    pbkdf2::derive(
434        pbkdf2::PBKDF2_HMAC_SHA256,
435        iterations,
436        salt,
437        key_material.as_bytes(),
438        &mut key,
439    );
440
441    key
442}
443
444#[cfg(feature = "2fa")]
445/// Encrypt with triple-factor authentication
446///
447/// Used by CLI for maximum security with device binding.
448pub fn encrypt_with_triple_factor(
449    private_key: &str,
450    twofa_secret: &str,
451    hardware_fingerprint: &str,
452    master_password: &str,
453    question_index: usize,
454    security_answer: &str,
455) -> Result<String, String> {
456    use serde_json::json;
457
458    let encryption_key = generate_triple_factor_key(
459        hardware_fingerprint,
460        master_password,
461        security_answer,
462    );
463
464    let data_package = json!({
465        "private_key": private_key,
466        "twofa_secret": twofa_secret,
467        "question_index": question_index,
468        "version": "triple_factor_v1",
469        "created_at": std::time::SystemTime::now()
470            .duration_since(std::time::UNIX_EPOCH)
471            .unwrap()
472            .as_secs()
473    });
474
475    let package_str = data_package.to_string();
476    let encrypted = encrypt_key(&package_str, &encryption_key)?;
477
478    Ok(encrypted)
479}
480
481#[cfg(feature = "2fa")]
482/// Decrypt with triple-factor authentication and verify 2FA code
483///
484/// Used by CLI for unlocking triple-factor encrypted wallets.
485pub fn decrypt_with_triple_factor_and_2fa(
486    encrypted_data: &str,
487    hardware_fingerprint: &str,
488    master_password: &str,
489    security_answer: &str,
490    twofa_code: &str,
491) -> Result<(String, String, usize), String> {
492    let decryption_key = generate_triple_factor_key(
493        hardware_fingerprint,
494        master_password,
495        security_answer,
496    );
497
498    let decrypted = decrypt_key(encrypted_data, &decryption_key)
499        .map_err(|_| "解密失败,请检查主密码、安全问题答案是否正确")?;
500
501    let data: serde_json::Value = serde_json::from_str(&decrypted)
502        .map_err(|_| "解密失败,请检查主密码、安全问题答案是否正确")?;
503
504    let private_key = data["private_key"]
505        .as_str()
506        .ok_or("缺少私钥数据")?
507        .to_string();
508
509    let twofa_secret = data["twofa_secret"]
510        .as_str()
511        .ok_or("缺少2FA密钥数据")?
512        .to_string();
513
514    let question_index = data["question_index"]
515        .as_u64()
516        .ok_or("缺少安全问题索引")? as usize;
517
518    // Verify 2FA code
519    verify_current_totp_code(&twofa_secret, twofa_code)?;
520
521    Ok((private_key, twofa_secret, question_index))
522}
523
524// ============================================================================
525// Tests
526// ============================================================================
527
528#[cfg(test)]
529mod tests {
530    use super::*;
531
532    #[test]
533    fn test_generate_keypair() {
534        let keypair = KeyManager::generate_keypair();
535        assert_eq!(keypair.to_bytes().len(), 64);
536    }
537
538    #[test]
539    fn test_encrypt_decrypt_with_password() {
540        let keypair = KeyManager::generate_keypair();
541        let private_key = keypair.to_base58_string();
542        let password = "test_password_123";
543
544        let encrypted = KeyManager::encrypt_with_password(&private_key, password).unwrap();
545        let decrypted = KeyManager::decrypt_with_password(&encrypted, password).unwrap();
546
547        assert_eq!(private_key, decrypted);
548    }
549
550    #[test]
551    fn test_get_public_key() {
552        let keypair = KeyManager::generate_keypair();
553        let private_key = keypair.to_base58_string();
554        let expected_pubkey = keypair.pubkey().to_string();
555
556        let pubkey = KeyManager::get_public_key(&private_key).unwrap();
557        assert_eq!(pubkey, expected_pubkey);
558    }
559
560    #[test]
561    fn test_keystore_json_round_trip() {
562        let keypair = KeyManager::generate_keypair();
563        let password = "secure_password";
564
565        let json = KeyManager::keypair_to_encrypted_json(&keypair, password).unwrap();
566        let restored_keypair = KeyManager::keypair_from_encrypted_json(&json, password).unwrap();
567
568        assert_eq!(keypair.to_bytes(), restored_keypair.to_bytes());
569    }
570
571    #[test]
572    fn test_wrong_password_fails() {
573        let keypair = KeyManager::generate_keypair();
574        let private_key = keypair.to_base58_string();
575
576        let encrypted = KeyManager::encrypt_with_password(&private_key, "correct").unwrap();
577        let result = KeyManager::decrypt_with_password(&encrypted, "wrong");
578
579        assert!(result.is_err());
580    }
581}