use crate::error::{ExportError, Result};
use rustywallet_keys::prelude::PrivateKey;
use rustywallet_address::{P2PKHAddress, Network as AddrNetwork};
use sha2::{Sha256, Digest};
use aes::cipher::{BlockEncrypt, KeyInit};
use aes::Aes256;
use scrypt::{scrypt, Params};
pub fn export_bip38(key: &PrivateKey, password: &str, compressed: bool) -> Result<String> {
let public_key = key.public_key();
let address = P2PKHAddress::from_public_key(&public_key, AddrNetwork::BitcoinMainnet)
.map_err(|e| ExportError::AddressError(e.to_string()))?
.to_string();
let addr_hash1 = Sha256::digest(address.as_bytes());
let addr_hash2 = Sha256::digest(addr_hash1);
let address_hash: [u8; 4] = addr_hash2[0..4].try_into().unwrap();
let params = Params::new(14, 8, 8, 64) .map_err(|e| ExportError::EncryptionFailed(format!("Scrypt params error: {}", e)))?;
let mut derived_key = [0u8; 64];
scrypt(password.as_bytes(), &address_hash, ¶ms, &mut derived_key)
.map_err(|e| ExportError::EncryptionFailed(format!("Scrypt failed: {}", e)))?;
let derived_half1 = &derived_key[0..32];
let derived_half2 = &derived_key[32..64];
let key_bytes = key.to_bytes();
let mut xored = [0u8; 32];
for i in 0..32 {
xored[i] = key_bytes[i] ^ derived_half1[i];
}
let cipher = Aes256::new_from_slice(derived_half2)
.map_err(|e| ExportError::EncryptionFailed(format!("AES init failed: {}", e)))?;
let mut encrypted = [0u8; 32];
let mut block1: [u8; 16] = xored[0..16].try_into().unwrap();
cipher.encrypt_block((&mut block1).into());
encrypted[0..16].copy_from_slice(&block1);
let mut block2: [u8; 16] = xored[16..32].try_into().unwrap();
cipher.encrypt_block((&mut block2).into());
encrypted[16..32].copy_from_slice(&block2);
let flag = if compressed { 0xE0 } else { 0xC0 };
let mut payload = Vec::with_capacity(39);
payload.push(0x01);
payload.push(0x42);
payload.push(flag);
payload.extend_from_slice(&address_hash);
payload.extend_from_slice(&encrypted);
let check1 = Sha256::digest(&payload);
let check2 = Sha256::digest(check1);
payload.extend_from_slice(&check2[0..4]);
Ok(bs58::encode(payload).into_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_export_bip38_format() {
let key = PrivateKey::random();
let encrypted = export_bip38(&key, "testpassword", true).unwrap();
assert!(encrypted.starts_with("6P"));
assert_eq!(encrypted.len(), 58);
}
}