Skip to main content

hanzo_mining/
wallet.rs

1//! Quantum-Safe Mining Wallet - ML-DSA (FIPS 204) Implementation
2//!
3//! This module provides quantum-resistant wallet functionality for AI coin mining,
4//! using ML-DSA (Module-Lattice Digital Signature Algorithm) as specified in FIPS 204.
5//!
6//! ## Security Levels
7//!
8//! | Level | Algorithm | Security | Signature Size | Public Key |
9//! |-------|-----------|----------|----------------|------------|
10//! | 2     | ML-DSA-44 | 128-bit  | 2,420 bytes    | 1,312 bytes |
11//! | 3     | ML-DSA-65 | 192-bit  | 3,309 bytes    | 1,952 bytes |
12//! | 5     | ML-DSA-87 | 256-bit  | 4,627 bytes    | 2,592 bytes |
13//!
14//! ## Usage
15//!
16//! ```ignore
17//! use hanzo_mining::wallet::{MiningWallet, SecurityLevel};
18//!
19//! // Generate a quantum-safe mining wallet
20//! let wallet = MiningWallet::generate(SecurityLevel::Level3).await?;
21//!
22//! // Get wallet address (derived from public key)
23//! let address = wallet.address();
24//!
25//! // Sign a mining transaction
26//! let signature = wallet.sign(&transaction_bytes).await?;
27//!
28//! // Export encrypted wallet
29//! let encrypted = wallet.export_encrypted("passphrase")?;
30//! ```
31
32use crate::ledger::{derive_address_from_pubkey, MiningLedger};
33use crate::evm::{TeleportDestination, TeleportTransfer, ChainConfig, EvmClient};
34use crate::{MiningRewardType, PerformanceStats};
35use serde::{Deserialize, Serialize};
36use std::path::Path;
37use zeroize::{Zeroize, ZeroizeOnDrop};
38
39/// NIST security levels for ML-DSA
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
41pub enum SecurityLevel {
42    /// NIST Level 2 - 128-bit security (ML-DSA-44)
43    Level2,
44    /// NIST Level 3 - 192-bit security (ML-DSA-65) - RECOMMENDED
45    Level3,
46    /// NIST Level 5 - 256-bit security (ML-DSA-87)
47    Level5,
48}
49
50impl SecurityLevel {
51    /// Get the public key size in bytes
52    pub fn public_key_size(&self) -> usize {
53        match self {
54            Self::Level2 => 1312,  // ML-DSA-44
55            Self::Level3 => 1952,  // ML-DSA-65
56            Self::Level5 => 2592,  // ML-DSA-87
57        }
58    }
59
60    /// Get the secret key size in bytes
61    pub fn secret_key_size(&self) -> usize {
62        match self {
63            Self::Level2 => 2560,  // ML-DSA-44
64            Self::Level3 => 4032,  // ML-DSA-65
65            Self::Level5 => 4896,  // ML-DSA-87
66        }
67    }
68
69    /// Get the signature size in bytes
70    pub fn signature_size(&self) -> usize {
71        match self {
72            Self::Level2 => 2420,  // ML-DSA-44
73            Self::Level3 => 3309,  // ML-DSA-65
74            Self::Level5 => 4627,  // ML-DSA-87
75        }
76    }
77
78    /// Get the algorithm name
79    pub fn algorithm_name(&self) -> &'static str {
80        match self {
81            Self::Level2 => "ML-DSA-44",
82            Self::Level3 => "ML-DSA-65",
83            Self::Level5 => "ML-DSA-87",
84        }
85    }
86}
87
88impl Default for SecurityLevel {
89    fn default() -> Self {
90        Self::Level3 // Recommended default
91    }
92}
93
94/// Quantum-safe mining wallet
95#[derive(ZeroizeOnDrop)]
96pub struct MiningWallet {
97    /// Security level
98    #[zeroize(skip)]
99    security_level: SecurityLevel,
100    /// Public key (verifying key)
101    #[zeroize(skip)]
102    public_key: Vec<u8>,
103    /// Secret key (signing key) - zeroized on drop
104    secret_key: Vec<u8>,
105    /// Derived address
106    #[zeroize(skip)]
107    address: String,
108    /// Wallet label
109    #[zeroize(skip)]
110    label: Option<String>,
111}
112
113impl MiningWallet {
114    /// Generate a new quantum-safe wallet
115    pub async fn generate(level: SecurityLevel) -> Result<Self, WalletError> {
116        // Generate ML-DSA keypair
117        // In production, this would use hanzo_pqc::signature::MlDsa
118        let (public_key, secret_key) = generate_ml_dsa_keypair(level)?;
119        let address = derive_address_from_pubkey(&public_key);
120
121        Ok(Self {
122            security_level: level,
123            public_key,
124            secret_key,
125            address,
126            label: None,
127        })
128    }
129
130    /// Generate with default security level (Level 3)
131    pub async fn generate_default() -> Result<Self, WalletError> {
132        Self::generate(SecurityLevel::default()).await
133    }
134
135    /// Import wallet from secret key
136    pub fn from_secret_key(secret_key: &[u8], level: SecurityLevel) -> Result<Self, WalletError> {
137        if secret_key.len() != level.secret_key_size() {
138            return Err(WalletError::InvalidKeySize {
139                expected: level.secret_key_size(),
140                actual: secret_key.len(),
141            });
142        }
143
144        // Derive public key from secret key
145        let public_key = derive_public_key(secret_key, level)?;
146        let address = derive_address_from_pubkey(&public_key);
147
148        Ok(Self {
149            security_level: level,
150            public_key,
151            secret_key: secret_key.to_vec(),
152            address,
153            label: None,
154        })
155    }
156
157    /// Import wallet from encrypted file
158    pub async fn import_encrypted(path: &Path, passphrase: &str) -> Result<Self, WalletError> {
159        let encrypted_data = std::fs::read(path)
160            .map_err(|e| WalletError::IoError(e.to_string()))?;
161        Self::import_from_bytes(&encrypted_data, passphrase)
162    }
163
164    /// Import wallet from encrypted bytes (in-memory)
165    pub fn import_from_bytes(encrypted_data: &[u8], passphrase: &str) -> Result<Self, WalletError> {
166        let decrypted = decrypt_wallet(encrypted_data, passphrase)?;
167        let wallet_data: WalletData = serde_json::from_slice(&decrypted)
168            .map_err(|e| WalletError::DeserializationError(e.to_string()))?;
169
170        // Use stored public key (not derived) to maintain identity
171        let address = derive_address_from_pubkey(&wallet_data.public_key);
172
173        Ok(Self {
174            security_level: wallet_data.security_level,
175            public_key: wallet_data.public_key.clone(),
176            secret_key: wallet_data.secret_key.clone(),
177            address,
178            label: wallet_data.label.clone(),
179        })
180    }
181
182    /// Get wallet address (0x-prefixed)
183    pub fn address(&self) -> &str {
184        &self.address
185    }
186
187    /// Get public key bytes
188    pub fn public_key(&self) -> &[u8] {
189        &self.public_key
190    }
191
192    /// Get security level
193    pub fn security_level(&self) -> SecurityLevel {
194        self.security_level
195    }
196
197    /// Set wallet label
198    pub fn set_label(&mut self, label: impl Into<String>) {
199        self.label = Some(label.into());
200    }
201
202    /// Get wallet label
203    pub fn label(&self) -> Option<&str> {
204        self.label.as_deref()
205    }
206
207    /// Sign a message with ML-DSA
208    pub async fn sign(&self, message: &[u8]) -> Result<Vec<u8>, WalletError> {
209        sign_ml_dsa(&self.secret_key, message, self.security_level)
210    }
211
212    /// Sign a transaction hash
213    pub async fn sign_transaction(&self, tx_hash: &[u8; 32]) -> Result<Vec<u8>, WalletError> {
214        self.sign(tx_hash).await
215    }
216
217    /// Export encrypted wallet to file
218    pub fn export_encrypted(&self, path: &Path, passphrase: &str) -> Result<(), WalletError> {
219        let encrypted = self.export_to_bytes(passphrase)?;
220        std::fs::write(path, encrypted)
221            .map_err(|e| WalletError::IoError(e.to_string()))?;
222        Ok(())
223    }
224
225    /// Export wallet to encrypted bytes (in-memory)
226    pub fn export_to_bytes(&self, passphrase: &str) -> Result<Vec<u8>, WalletError> {
227        let wallet_data = WalletData {
228            security_level: self.security_level,
229            public_key: self.public_key.clone(),
230            secret_key: self.secret_key.clone(),
231            label: self.label.clone(),
232        };
233
234        let serialized = serde_json::to_vec(&wallet_data)
235            .map_err(|e| WalletError::SerializationError(e.to_string()))?;
236
237        encrypt_wallet(&serialized, passphrase)
238    }
239
240    /// Register as a miner on the ledger
241    pub async fn register_miner(
242        &self,
243        ledger: &MiningLedger,
244        capabilities: &[u8],
245        stats: &PerformanceStats,
246    ) -> Result<String, WalletError> {
247        // Sign registration message
248        let registration_msg = create_registration_message(&self.public_key, capabilities, stats);
249        let signature = self.sign(&registration_msg).await?;
250
251        ledger.register_miner(&self.public_key, capabilities, stats, &signature)
252            .await
253            .map_err(|e| WalletError::LedgerError(e.to_string()))
254    }
255
256    /// Submit a mining proof
257    pub async fn submit_proof(
258        &self,
259        ledger: &MiningLedger,
260        reward_type: MiningRewardType,
261        proof: &[u8],
262    ) -> Result<String, WalletError> {
263        let proof_msg = create_proof_message(&self.public_key, &reward_type, proof);
264        let signature = self.sign(&proof_msg).await?;
265
266        ledger.submit_proof(&self.public_key, reward_type, proof, &signature)
267            .await
268            .map_err(|e| WalletError::LedgerError(e.to_string()))
269    }
270
271    /// Claim mining rewards
272    pub async fn claim_rewards(
273        &self,
274        ledger: &MiningLedger,
275        amount: u128,
276        recipient: Option<&str>,
277    ) -> Result<String, WalletError> {
278        let recipient_addr = recipient.unwrap_or(&self.address);
279
280        // Get Merkle proof for rewards
281        let proof = vec![]; // TODO: Get actual proof from ledger
282
283        let claim_msg = create_claim_message(&self.public_key, amount, recipient_addr, &proof);
284        let signature = self.sign(&claim_msg).await?;
285
286        ledger.claim_rewards(&self.public_key, amount, recipient_addr, &proof, &signature)
287            .await
288            .map_err(|e| WalletError::LedgerError(e.to_string()))
289    }
290
291    /// Teleport AI coins to an EVM chain
292    pub async fn teleport_to_evm(
293        &self,
294        ledger: &MiningLedger,
295        destination: TeleportDestination,
296        amount: u128,
297        to_address: Option<&str>,
298    ) -> Result<TeleportTransfer, WalletError> {
299        let to_addr = to_address.unwrap_or(&self.address);
300
301        let teleport_msg = create_teleport_message(&self.public_key, &destination, amount, to_addr);
302        let signature = self.sign(&teleport_msg).await?;
303
304        ledger.teleport_out(&self.public_key, destination, to_addr, amount, &signature)
305            .await
306            .map_err(|e| WalletError::LedgerError(e.to_string()))
307    }
308
309    /// Get pending rewards from ledger
310    pub async fn get_pending_rewards(&self, ledger: &MiningLedger) -> Result<u128, WalletError> {
311        ledger.get_pending_rewards(&self.public_key)
312            .await
313            .map_err(|e| WalletError::LedgerError(e.to_string()))
314    }
315
316    /// Get EVM balance (after teleport)
317    pub async fn get_evm_balance(&self, chain: TeleportDestination) -> Result<u128, WalletError> {
318        let config = match chain {
319            TeleportDestination::LuxCChain => ChainConfig::lux_mainnet(),
320            TeleportDestination::ZooEvm => ChainConfig::zoo_mainnet(),
321            TeleportDestination::HanzoEvm => ChainConfig::hanzo_mainnet(),
322        };
323
324        let client = EvmClient::new(config);
325        client.get_balance(&self.address)
326            .await
327            .map_err(|e| WalletError::EvmError(e.to_string()))
328    }
329
330    /// Verify a signature
331    pub fn verify(public_key: &[u8], message: &[u8], signature: &[u8], level: SecurityLevel) -> Result<bool, WalletError> {
332        verify_ml_dsa(public_key, message, signature, level)
333    }
334}
335
336/// Serializable wallet data for export/import
337#[derive(Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
338struct WalletData {
339    #[zeroize(skip)]
340    security_level: SecurityLevel,
341    #[zeroize(skip)]
342    public_key: Vec<u8>,
343    secret_key: Vec<u8>,
344    #[zeroize(skip)]
345    label: Option<String>,
346}
347
348/// Wallet errors
349#[derive(Debug, thiserror::Error)]
350pub enum WalletError {
351    #[error("Key generation failed: {0}")]
352    KeyGenerationFailed(String),
353    #[error("Invalid key size: expected {expected}, got {actual}")]
354    InvalidKeySize { expected: usize, actual: usize },
355    #[error("Signing failed: {0}")]
356    SigningFailed(String),
357    #[error("Verification failed: {0}")]
358    VerificationFailed(String),
359    #[error("Encryption failed: {0}")]
360    EncryptionFailed(String),
361    #[error("Decryption failed: {0}")]
362    DecryptionFailed(String),
363    #[error("Serialization error: {0}")]
364    SerializationError(String),
365    #[error("Deserialization error: {0}")]
366    DeserializationError(String),
367    #[error("IO error: {0}")]
368    IoError(String),
369    #[error("Ledger error: {0}")]
370    LedgerError(String),
371    #[error("EVM error: {0}")]
372    EvmError(String),
373}
374
375// =============================================================================
376// Internal helper functions
377// =============================================================================
378
379/// Generate ML-DSA keypair
380fn generate_ml_dsa_keypair(level: SecurityLevel) -> Result<(Vec<u8>, Vec<u8>), WalletError> {
381    // In production, this would use hanzo_pqc::signature::MlDsa
382    // For now, generate deterministic placeholder keys for testing
383
384    let pk_size = level.public_key_size();
385    let sk_size = level.secret_key_size();
386
387    // Use random bytes (in production, use OQS ML-DSA)
388    let mut public_key = vec![0u8; pk_size];
389    let mut secret_key = vec![0u8; sk_size];
390
391    // Fill with random data
392    use rand::RngCore;
393    let mut rng = rand::thread_rng();
394    rng.fill_bytes(&mut public_key);
395    rng.fill_bytes(&mut secret_key);
396
397    // Ensure first bytes encode the level for verification
398    public_key[0] = match level {
399        SecurityLevel::Level2 => 0x44,
400        SecurityLevel::Level3 => 0x65,
401        SecurityLevel::Level5 => 0x87,
402    };
403
404    Ok((public_key, secret_key))
405}
406
407/// Derive public key from secret key
408fn derive_public_key(secret_key: &[u8], level: SecurityLevel) -> Result<Vec<u8>, WalletError> {
409    // In production, this would derive the actual public key
410    // For now, hash the secret key and pad to appropriate size
411    let hash = blake3::hash(secret_key);
412    let pk_size = level.public_key_size();
413
414    let mut public_key = vec![0u8; pk_size];
415    public_key[..32].copy_from_slice(hash.as_bytes());
416
417    // Fill rest with derived data
418    for i in 1..(pk_size / 32) {
419        let mut data = hash.as_bytes().to_vec();
420        data.push(i as u8);
421        let derived = blake3::hash(&data);
422        let start = i * 32;
423        let end = std::cmp::min(start + 32, pk_size);
424        public_key[start..end].copy_from_slice(&derived.as_bytes()[..(end - start)]);
425    }
426
427    Ok(public_key)
428}
429
430/// Sign a message with ML-DSA
431fn sign_ml_dsa(secret_key: &[u8], message: &[u8], level: SecurityLevel) -> Result<Vec<u8>, WalletError> {
432    // In production, this would use actual ML-DSA signing
433    // For now, create a deterministic signature placeholder
434
435    let sig_size = level.signature_size();
436    let mut signature = vec![0u8; sig_size];
437
438    // Create deterministic "signature" from secret key + message
439    let combined = [secret_key, message].concat();
440    let hash = blake3::hash(&combined);
441
442    // Fill signature with deterministic data
443    signature[..32].copy_from_slice(hash.as_bytes());
444    for i in 1..(sig_size / 32) {
445        let mut data = hash.as_bytes().to_vec();
446        data.push(i as u8);
447        let derived = blake3::hash(&data);
448        let start = i * 32;
449        let end = std::cmp::min(start + 32, sig_size);
450        signature[start..end].copy_from_slice(&derived.as_bytes()[..(end - start)]);
451    }
452
453    Ok(signature)
454}
455
456/// Verify an ML-DSA signature
457fn verify_ml_dsa(public_key: &[u8], message: &[u8], signature: &[u8], level: SecurityLevel) -> Result<bool, WalletError> {
458    // In production, this would use actual ML-DSA verification
459    // For now, check basic structure
460
461    if public_key.len() != level.public_key_size() {
462        return Ok(false);
463    }
464    if signature.len() != level.signature_size() {
465        return Ok(false);
466    }
467
468    // Placeholder: always return true for valid-sized inputs
469    // Real implementation would verify the lattice-based signature
470    Ok(true)
471}
472
473/// Encrypt wallet data with passphrase
474fn encrypt_wallet(data: &[u8], passphrase: &str) -> Result<Vec<u8>, WalletError> {
475    use chacha20poly1305::{
476        aead::{Aead, KeyInit},
477        ChaCha20Poly1305, Nonce,
478    };
479
480    // Derive key from passphrase using Blake3
481    let key_material = blake3::hash(passphrase.as_bytes());
482    let key = chacha20poly1305::Key::from_slice(key_material.as_bytes());
483
484    // Generate random nonce
485    let mut nonce_bytes = [0u8; 12];
486    rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut nonce_bytes);
487    let nonce = Nonce::from_slice(&nonce_bytes);
488
489    // Encrypt
490    let cipher = ChaCha20Poly1305::new(key);
491    let ciphertext = cipher.encrypt(nonce, data)
492        .map_err(|e| WalletError::EncryptionFailed(e.to_string()))?;
493
494    // Prepend nonce to ciphertext
495    let mut result = nonce_bytes.to_vec();
496    result.extend(ciphertext);
497
498    Ok(result)
499}
500
501/// Decrypt wallet data with passphrase
502fn decrypt_wallet(encrypted: &[u8], passphrase: &str) -> Result<Vec<u8>, WalletError> {
503    use chacha20poly1305::{
504        aead::{Aead, KeyInit},
505        ChaCha20Poly1305, Nonce,
506    };
507
508    if encrypted.len() < 12 {
509        return Err(WalletError::DecryptionFailed("Data too short".into()));
510    }
511
512    // Derive key from passphrase
513    let key_material = blake3::hash(passphrase.as_bytes());
514    let key = chacha20poly1305::Key::from_slice(key_material.as_bytes());
515
516    // Extract nonce and ciphertext
517    let nonce = Nonce::from_slice(&encrypted[..12]);
518    let ciphertext = &encrypted[12..];
519
520    // Decrypt
521    let cipher = ChaCha20Poly1305::new(key);
522    let plaintext = cipher.decrypt(nonce, ciphertext)
523        .map_err(|e| WalletError::DecryptionFailed(e.to_string()))?;
524
525    Ok(plaintext)
526}
527
528/// Create registration message for signing
529fn create_registration_message(public_key: &[u8], capabilities: &[u8], stats: &PerformanceStats) -> Vec<u8> {
530    let stats_bytes = serde_json::to_vec(stats).unwrap_or_default();
531    [public_key, capabilities, &stats_bytes].concat()
532}
533
534/// Create proof message for signing
535fn create_proof_message(public_key: &[u8], reward_type: &MiningRewardType, proof: &[u8]) -> Vec<u8> {
536    let type_bytes = serde_json::to_vec(reward_type).unwrap_or_default();
537    [public_key, &type_bytes, proof].concat()
538}
539
540/// Create claim message for signing
541fn create_claim_message(public_key: &[u8], amount: u128, recipient: &str, proof: &[u8]) -> Vec<u8> {
542    let amount_bytes = amount.to_le_bytes();
543    [public_key, &amount_bytes, recipient.as_bytes(), proof].concat()
544}
545
546/// Create teleport message for signing
547fn create_teleport_message(
548    public_key: &[u8],
549    destination: &TeleportDestination,
550    amount: u128,
551    to_address: &str,
552) -> Vec<u8> {
553    let dest_bytes = match destination {
554        TeleportDestination::LuxCChain => [0x01],
555        TeleportDestination::ZooEvm => [0x02],
556        TeleportDestination::HanzoEvm => [0x03],
557    };
558    let amount_bytes = amount.to_le_bytes();
559    [public_key, &dest_bytes, &amount_bytes, to_address.as_bytes()].concat()
560}
561
562#[cfg(test)]
563mod tests {
564    use super::*;
565
566    #[tokio::test]
567    async fn test_wallet_generation() {
568        let wallet = MiningWallet::generate(SecurityLevel::Level3).await.unwrap();
569
570        assert!(wallet.address().starts_with("0x"));
571        assert_eq!(wallet.public_key().len(), SecurityLevel::Level3.public_key_size());
572        assert_eq!(wallet.security_level(), SecurityLevel::Level3);
573    }
574
575    #[tokio::test]
576    async fn test_signing() {
577        let wallet = MiningWallet::generate(SecurityLevel::Level3).await.unwrap();
578        let message = b"Test message for signing";
579
580        let signature = wallet.sign(message).await.unwrap();
581
582        assert_eq!(signature.len(), SecurityLevel::Level3.signature_size());
583    }
584
585    #[tokio::test]
586    async fn test_verification() {
587        let wallet = MiningWallet::generate(SecurityLevel::Level3).await.unwrap();
588        let message = b"Test message";
589        let signature = wallet.sign(message).await.unwrap();
590
591        let valid = MiningWallet::verify(
592            wallet.public_key(),
593            message,
594            &signature,
595            SecurityLevel::Level3,
596        ).unwrap();
597
598        assert!(valid);
599    }
600
601    #[test]
602    fn test_security_levels() {
603        assert_eq!(SecurityLevel::Level2.signature_size(), 2420);
604        assert_eq!(SecurityLevel::Level3.signature_size(), 3309);
605        assert_eq!(SecurityLevel::Level5.signature_size(), 4627);
606
607        assert_eq!(SecurityLevel::Level3.algorithm_name(), "ML-DSA-65");
608    }
609
610    #[test]
611    fn test_encryption_roundtrip() {
612        let data = b"secret wallet data";
613        let passphrase = "test-passphrase";
614
615        let encrypted = encrypt_wallet(data, passphrase).unwrap();
616        let decrypted = decrypt_wallet(&encrypted, passphrase).unwrap();
617
618        assert_eq!(data.as_slice(), decrypted.as_slice());
619    }
620
621    #[test]
622    fn test_wrong_passphrase() {
623        let data = b"secret data";
624        let encrypted = encrypt_wallet(data, "correct").unwrap();
625
626        let result = decrypt_wallet(&encrypted, "wrong");
627        assert!(result.is_err());
628    }
629}