use crate::error::{ImportError, Result};
use rustywallet_keys::prelude::PrivateKey;
use rustywallet_address::{P2PKHAddress, Network as AddrNetwork};
use sha2::{Sha256, Digest};
use aes::cipher::{BlockDecrypt, KeyInit};
use aes::Aes256;
use scrypt::{scrypt, Params};
pub fn import_bip38(encrypted: &str, password: &str) -> Result<PrivateKey> {
let encrypted = encrypted.trim();
if !encrypted.starts_with("6P") {
return Err(ImportError::InvalidBip38(
"BIP38 key must start with '6P'".to_string()
));
}
if encrypted.len() != 58 {
return Err(ImportError::InvalidBip38(format!(
"Invalid length: expected 58 characters, got {}",
encrypted.len()
)));
}
let decoded = bs58::decode(encrypted)
.into_vec()
.map_err(|e| ImportError::InvalidBip38(format!("Base58 decode failed: {}", e)))?;
if decoded.len() != 43 {
return Err(ImportError::InvalidBip38(format!(
"Invalid decoded length: expected 43 bytes, got {}",
decoded.len()
)));
}
let payload = &decoded[..39];
let checksum = &decoded[39..];
let hash1 = Sha256::digest(payload);
let hash2 = Sha256::digest(hash1);
if &hash2[..4] != checksum {
return Err(ImportError::InvalidChecksum);
}
let prefix = &payload[0..2];
let flag = payload[2];
let address_hash = &payload[3..7];
let encrypted_data = &payload[7..39];
if prefix != [0x01, 0x42] {
if prefix == [0x01, 0x43] {
return Err(ImportError::UnsupportedFormat(
"BIP38 EC-multiply mode not supported".to_string()
));
}
return Err(ImportError::InvalidBip38(format!(
"Unknown prefix: {:02x}{:02x}",
prefix[0], prefix[1]
)));
}
let compressed = (flag & 0x20) != 0;
let params = Params::new(14, 8, 8, 64) .map_err(|e| ImportError::DecryptionFailed(format!("Scrypt params error: {}", e)))?;
let mut derived_key = [0u8; 64];
scrypt(password.as_bytes(), address_hash, ¶ms, &mut derived_key)
.map_err(|e| ImportError::DecryptionFailed(format!("Scrypt failed: {}", e)))?;
let derived_half1 = &derived_key[0..32];
let derived_half2 = &derived_key[32..64];
let cipher = Aes256::new_from_slice(derived_half2)
.map_err(|e| ImportError::DecryptionFailed(format!("AES init failed: {}", e)))?;
let mut decrypted = [0u8; 32];
let mut block1: [u8; 16] = encrypted_data[0..16].try_into().unwrap();
cipher.decrypt_block((&mut block1).into());
let mut block2: [u8; 16] = encrypted_data[16..32].try_into().unwrap();
cipher.decrypt_block((&mut block2).into());
for i in 0..16 {
decrypted[i] = block1[i] ^ derived_half1[i];
decrypted[i + 16] = block2[i] ^ derived_half1[i + 16];
}
let private_key = PrivateKey::from_bytes(decrypted)
.map_err(|e| ImportError::DecryptionFailed(format!("Invalid key: {}", e)))?;
let public_key = private_key.public_key();
let address = P2PKHAddress::from_public_key(&public_key, AddrNetwork::BitcoinMainnet)
.map_err(|e| ImportError::DecryptionFailed(format!("Address generation failed: {}", e)))?;
let addr_str = address.to_string();
let addr_hash1 = Sha256::digest(addr_str.as_bytes());
let addr_hash2 = Sha256::digest(addr_hash1);
let _ = compressed;
if &addr_hash2[0..4] != address_hash {
return Err(ImportError::WrongPassword);
}
Ok(private_key)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_prefix() {
let encrypted = "5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ";
let result = import_bip38(encrypted, "password");
assert!(matches!(result, Err(ImportError::InvalidBip38(_))));
}
#[test]
fn test_invalid_length() {
let encrypted = "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg";
let result = import_bip38(encrypted, "password");
assert!(matches!(result, Err(ImportError::InvalidBip38(_))));
}
}