rustywallet_export/
bip38.rs1use crate::error::{ExportError, Result};
4use rustywallet_keys::prelude::PrivateKey;
5use rustywallet_address::{P2PKHAddress, Network as AddrNetwork};
6use sha2::{Sha256, Digest};
7use aes::cipher::{BlockEncrypt, KeyInit};
8use aes::Aes256;
9use scrypt::{scrypt, Params};
10
11pub fn export_bip38(key: &PrivateKey, password: &str, compressed: bool) -> Result<String> {
26 let public_key = key.public_key();
27
28 let address = P2PKHAddress::from_public_key(&public_key, AddrNetwork::BitcoinMainnet)
30 .map_err(|e| ExportError::AddressError(e.to_string()))?
31 .to_string();
32
33 let addr_hash1 = Sha256::digest(address.as_bytes());
35 let addr_hash2 = Sha256::digest(addr_hash1);
36 let address_hash: [u8; 4] = addr_hash2[0..4].try_into().unwrap();
37
38 let params = Params::new(14, 8, 8, 64) .map_err(|e| ExportError::EncryptionFailed(format!("Scrypt params error: {}", e)))?;
42
43 let mut derived_key = [0u8; 64];
44 scrypt(password.as_bytes(), &address_hash, ¶ms, &mut derived_key)
45 .map_err(|e| ExportError::EncryptionFailed(format!("Scrypt failed: {}", e)))?;
46
47 let derived_half1 = &derived_key[0..32];
48 let derived_half2 = &derived_key[32..64];
49
50 let key_bytes = key.to_bytes();
52 let mut xored = [0u8; 32];
53 for i in 0..32 {
54 xored[i] = key_bytes[i] ^ derived_half1[i];
55 }
56
57 let cipher = Aes256::new_from_slice(derived_half2)
59 .map_err(|e| ExportError::EncryptionFailed(format!("AES init failed: {}", e)))?;
60
61 let mut encrypted = [0u8; 32];
62
63 let mut block1: [u8; 16] = xored[0..16].try_into().unwrap();
65 cipher.encrypt_block((&mut block1).into());
66 encrypted[0..16].copy_from_slice(&block1);
67
68 let mut block2: [u8; 16] = xored[16..32].try_into().unwrap();
70 cipher.encrypt_block((&mut block2).into());
71 encrypted[16..32].copy_from_slice(&block2);
72
73 let flag = if compressed { 0xE0 } else { 0xC0 };
77
78 let mut payload = Vec::with_capacity(39);
79 payload.push(0x01);
80 payload.push(0x42);
81 payload.push(flag);
82 payload.extend_from_slice(&address_hash);
83 payload.extend_from_slice(&encrypted);
84
85 let check1 = Sha256::digest(&payload);
87 let check2 = Sha256::digest(check1);
88 payload.extend_from_slice(&check2[0..4]);
89
90 Ok(bs58::encode(payload).into_string())
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_export_bip38_format() {
100 let key = PrivateKey::random();
101 let encrypted = export_bip38(&key, "testpassword", true).unwrap();
102
103 assert!(encrypted.starts_with("6P"));
104 assert_eq!(encrypted.len(), 58);
105 }
106
107 }