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