//! WASM-compatible cryptographic implementations use bip39::Mnemonic; use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH}; use chacha20poly1305::{aead::{Aead, KeyInit}, XChaCha20Poly1305, Key, XNonce}; use hkdf::Hkdf; use sha2::Sha256; use crate::domain::entities::*; use crate::domain::error::DomainError; use crate::domain::traits::*; use crate::infrastructure::rng; use crate::infrastructure::time; use super::shared::*; #[derive(Clone)] pub struct Bip39MnemonicGenerator; impl MnemonicGenerator for Bip39MnemonicGenerator { type Error = DomainError; fn generate(&self) -> Result { let mut entropy = [0u8; 32]; let mut rng = rng::new_rng(); rng.fill_bytes(&mut entropy)?; let mnemonic = Mnemonic::from_entropy(&entropy) .map_err(|e| DomainError::CryptographicError(format!("Failed to generate mnemonic: {}", e)))?; let words: Vec = mnemonic.words().into_iter().map(|s| s.to_string()).collect(); Ok(RecoveryPhrase::new(words)) } fn validate(&self, words: &[String]) -> Result<(), Self::Error> { let phrase = words.join(" "); Mnemonic::parse(&phrase) .map_err(|e| DomainError::InvalidMnemonic(format!("Invalid mnemonic: {}", e)))?; Ok(()) } } #[derive(Clone)] pub struct Ed25519KeyDeriver; impl KeyDeriver for Ed25519KeyDeriver { type Error = DomainError; fn derive_from_seed(&self, seed: &Seed) -> Result<(PublicKey, PrivateKey), Self::Error> { validate_seed_length(seed.as_bytes())?; let signing_key = SigningKey::from_bytes(&seed.as_bytes()[..SECRET_KEY_LENGTH].try_into() .map_err(|_| DomainError::CryptographicError("Invalid seed length".to_string()))?); let verifying_key = signing_key.verifying_key(); Ok(( PublicKey(verifying_key.to_bytes().to_vec()), PrivateKey(signing_key.to_bytes().to_vec()), )) } fn derive_from_mnemonic(&self, mnemonic: &RecoveryPhrase, univ_id: &str) -> Result { let phrase = mnemonic.words().join(" "); let bip39_mnemonic = Mnemonic::parse(&phrase) .map_err(|e| DomainError::InvalidMnemonic(format!("Invalid mnemonic: {}", e)))?; // Use univ_id as passphrase to bind seed to universe let bip39_seed = bip39_mnemonic.to_seed(univ_id); // BIP39 produces 64-byte seed, but we only need 32 bytes for Ed25519 // Use the first 32 bytes of the BIP39 seed let ed25519_seed: [u8; 32] = bip39_seed[..32] .try_into() .map_err(|_| DomainError::CryptographicError("Failed to extract 32-byte seed from BIP39 seed".to_string()))?; Ok(Seed::new(ed25519_seed.to_vec())) } } #[derive(Clone)] pub struct XChaCha20FileEncryptor; impl FileEncryptor for XChaCha20FileEncryptor { type Error = DomainError; fn encrypt( &self, seed: &Seed, password: &str, public_key: &PublicKey, did: &Did, univ_id: &str, user_profiles: &[UserProfile], date_of_birth: &Option, default_user_profile_id: &Option, ) -> Result { // Generate salt and nonce using WASM-compatible RNG let mut salt = [0u8; SALT_LENGTH]; let mut nonce_bytes = [0u8; NONCE_LENGTH]; let mut rng = rng::new_rng(); rng.fill_bytes(&mut salt)?; rng.fill_bytes(&mut nonce_bytes)?; // Derive KEK from password using HKDF let hk = Hkdf::::new(Some(&salt), password.as_bytes()); let mut kek = [0u8; 32]; hk.expand(KDF_INFO, &mut kek) .map_err(|e| DomainError::CryptographicError(format!("HKDF failed: {}", e)))?; // Encrypt seed let cipher = XChaCha20Poly1305::new(&Key::from(kek)); let nonce = XNonce::from_slice(&nonce_bytes); let enc_seed = cipher .encrypt(&nonce, seed.as_bytes()) .map_err(|e| DomainError::CryptographicError(format!("Encryption failed: {}", e)))?; // Serialize and encrypt user profiles let user_profiles_vec: Vec = user_profiles.to_vec(); let user_profiles_bytes = serde_cbor::to_vec(&user_profiles_vec) .map_err(|e| DomainError::CryptographicError(format!("Failed to serialize user profiles: {}", e)))?; let enc_user_profiles = cipher .encrypt(&nonce, &*user_profiles_bytes) .map_err(|e| DomainError::CryptographicError(format!("User profiles encryption failed: {}", e)))?; // Serialize and encrypt date of birth let date_of_birth_bytes = serde_cbor::to_vec(&date_of_birth) .map_err(|e| DomainError::CryptographicError(format!("Failed to serialize date of birth: {}", e)))?; let enc_date_of_birth = cipher .encrypt(&nonce, &*date_of_birth_bytes) .map_err(|e| DomainError::CryptographicError(format!("Date of birth encryption failed: {}", e)))?; // Serialize and encrypt default user profile ID let default_user_profile_id_bytes = serde_cbor::to_vec(&default_user_profile_id) .map_err(|e| DomainError::CryptographicError(format!("Failed to serialize default user profile ID: {}", e)))?; let enc_default_user_profile_id = cipher .encrypt(&nonce, &*default_user_profile_id_bytes) .map_err(|e| DomainError::CryptographicError(format!("Default user profile ID encryption failed: {}", e)))?; // Get current timestamp using WASM-compatible time let created_at = time::now_seconds()?; Ok(PassportFile { enc_seed, kdf: KDF_HKDF_SHA256.to_string(), cipher: CIPHER_XCHACHA20_POLY1305.to_string(), salt: salt.to_vec(), nonce: nonce_bytes.to_vec(), public_key: public_key.0.clone(), did: did.0.clone(), univ_id: univ_id.to_string(), created_at, version: "1.0.0".to_string(), enc_user_profiles, enc_date_of_birth, enc_default_user_profile_id, }) } fn decrypt( &self, file: &PassportFile, password: &str, ) -> Result<(Seed, PublicKey, PrivateKey, Vec, Option, Option), Self::Error> { // Validate file format validate_file_format(&file.kdf, &file.cipher)?; // Derive KEK from password let hk = Hkdf::::new(Some(&file.salt), password.as_bytes()); let mut kek = [0u8; 32]; hk.expand(KDF_INFO, &mut kek) .map_err(|e| DomainError::CryptographicError(format!("HKDF failed: {}", e)))?; // Decrypt seed let cipher = XChaCha20Poly1305::new(&Key::from(kek)); let nonce = XNonce::from_slice(&file.nonce); let seed_bytes = cipher .decrypt(&nonce, &*file.enc_seed) .map_err(|e| DomainError::CryptographicError(format!("Decryption failed: {}", e)))?; let seed = Seed::new(seed_bytes); // Re-derive keys from seed to verify let key_deriver = Ed25519KeyDeriver; let (public_key, private_key) = key_deriver.derive_from_seed(&seed)?; // Verify public key matches if public_key.0 != file.public_key { return Err(DomainError::CryptographicError( "Public key mismatch - wrong password?".to_string(), )); } // Decrypt user profiles let user_profiles_bytes = cipher .decrypt(&nonce, &*file.enc_user_profiles) .map_err(|e| DomainError::CryptographicError(format!("User profiles decryption failed: {}", e)))?; let user_profiles: Vec = serde_cbor::from_slice(&user_profiles_bytes) .map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize user profiles: {}", e)))?; // Decrypt date of birth let date_of_birth_bytes = cipher .decrypt(&nonce, &*file.enc_date_of_birth) .map_err(|e| DomainError::CryptographicError(format!("Date of birth decryption failed: {}", e)))?; let date_of_birth: Option = serde_cbor::from_slice(&date_of_birth_bytes) .map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize date of birth: {}", e)))?; // Decrypt default user profile ID let default_user_profile_id_bytes = cipher .decrypt(&nonce, &*file.enc_default_user_profile_id) .map_err(|e| DomainError::CryptographicError(format!("Default user profile ID decryption failed: {}", e)))?; let default_user_profile_id: Option = serde_cbor::from_slice(&default_user_profile_id_bytes) .map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize default user profile ID: {}", e)))?; // Note: univ_id is stored in the PassportFile and will be used when creating the Passport Ok((seed, public_key, private_key, user_profiles, date_of_birth, default_user_profile_id)) } }