cpchain_rust_sdk/keystore/
mod.rs

1use crate::cpc_aes::{AESParams, AES, Mode, InitVector};
2use rand::thread_rng;
3use rand_core::RngCore;
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6use web3::signing::keccak256;
7
8use crate::accounts::Account;
9
10use self::{kdf::KDF, crypto_info::{CryptoInfo, CipherParams}};
11
12mod kdf;
13mod crypto_info;
14pub mod my_scrypt;
15
16/// https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
17#[derive(Serialize, Deserialize, Debug)]
18pub struct Keystore {
19    address: String,
20    #[serde(alias="Crypto")]
21    crypto: CryptoInfo,
22    id: String,
23    version: usize,
24}
25
26impl Keystore {
27    pub fn to_string(&self) -> Result<String, Box<dyn std::error::Error>> {
28        match serde_json::to_string(&self) {
29            Ok(s) => return Ok(s),
30            Err(e) => return Err(format!("{}", e).into()),
31        }
32    }
33
34    fn derive(kdf: &KDF, password: &str) -> Result<([u8; 16], [u8; 16], KDF), Box<dyn std::error::Error + Send + Sync>> {
35        let (password_hash, kdf) = kdf.encrypt(password)?;
36        let mut derived_key: [u8; 16] = [0; 16];
37        password_hash[..16].iter().enumerate().for_each(|(index, elem)| {
38            derived_key[index] = elem.clone();
39        });
40        let mut mac_prefix: [u8; 16] = [0; 16];
41        password_hash[16..].iter().enumerate().for_each(|(index, elem)| {
42            mac_prefix[index] = elem.clone();
43        });
44        Ok((derived_key, mac_prefix, kdf))
45    }
46
47    fn aes_128_ctr(derived_key: [u8; 16], data: &Vec<u8>, iv: [u8; 16]) -> Vec<u8> {
48        // AES encrypt
49        let params = AESParams {
50            mode: Some(Mode::CTR(InitVector::I16(iv)))
51        };
52        let encrypted = AES::AES128(derived_key).encrypt(&data, &params).unwrap();
53        encrypted
54    }
55
56    fn rand_iv() -> [u8; 16] {
57        let mut iv = [0x0; 16];
58        let mut rng = thread_rng();
59        rng.fill_bytes(&mut iv);
60        iv
61    }
62
63    fn caclute_mac(prefix: &[u8; 16], encrypted: &Vec<u8>) -> [u8; 32] {
64        let mut bytes = prefix.to_vec();
65        bytes.append(&mut encrypted.clone());
66        let mac = keccak256(&bytes);
67        mac
68    }
69
70    fn encrypt(account: &Account, derived_key: &[u8; 16], mac_prefix: &[u8; 16], kdf: KDF) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
71        // KDF encryption
72        // let (derived_key, mac_prefix, kdf) = Keystore::derive(&KDF::PBKDF2(None), password)?;
73        // AES encrypt
74        let iv = Keystore::rand_iv();
75        let encrypted = Keystore::aes_128_ctr(*derived_key, &account.private_key_bytes().to_vec(), iv);
76        // mac
77        let mac = Keystore::caclute_mac(&mac_prefix, &encrypted);
78        // id
79        let id = Uuid::new_v4();
80        Ok(Self {
81            address: account.address.to_string().to_lowercase(),
82            crypto: CryptoInfo { 
83                cipher: "aes-128-ctr".to_string(), 
84                cipher_params: CipherParams { iv: hex::encode(iv) }, 
85                cipher_text: hex::encode(&encrypted),
86                kdf: kdf, 
87                mac: hex::encode(mac),
88            },
89            id: id.to_string(),
90            version: 3,
91        })
92    }
93
94    pub fn encrypt_pbkdf2(account: &Account, password: &str) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
95        // KDF encryption
96        let (derived_key, mac_prefix, kdf) = Keystore::derive(&KDF::PBKDF2(None), password)?;
97        Keystore::encrypt(account, &derived_key, &mac_prefix, kdf)
98    }
99
100    pub fn encrypt_scrypt(account: &Account, password: &str) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
101        // KDF encryption
102        let (derived_key, mac_prefix, kdf) = Keystore::derive(&KDF::SCRYPT(None), password)?;
103        Keystore::encrypt(account, &derived_key, &mac_prefix, kdf)
104    }
105
106    pub fn decrypt(&self, password: &str) -> Result<Account, Box<dyn std::error::Error + Send + Sync>> {
107        // kdf
108        let (key, mac_prefix, _) = Keystore::derive(&self.crypto.kdf, password)?;
109        let bytes = hex::decode(&self.crypto.cipher_text)?;
110        // caclute_mac
111        let expected_mac = Keystore::caclute_mac(&mac_prefix, &bytes);
112        if hex::encode(expected_mac) != self.crypto.mac {
113            return Err("invalid mac".into())
114        }
115        // decrypt
116        let mut iv:[u8; 16] = [0; 16];
117        hex::decode(&self.crypto.cipher_params.iv)?.iter().enumerate().for_each(|(i, e)| {
118            iv[i] = e.clone();
119        });
120        let decrypted = Keystore::aes_128_ctr(key, &bytes, iv);
121        let account = Account::from_private_key(&hex::encode(&decrypted))?;
122        let mut addr = self.address.clone();
123        if !addr.starts_with("0x") {
124            addr = "0x".to_string() + &addr;
125        }
126        if addr != account.address.to_checksum().to_lowercase() {
127            return Err("address mismatch".into())
128        }
129        Ok(account)
130    }
131}
132
133impl From<String> for Keystore {
134    fn from(s: String) -> Self {
135        let ks: Keystore = serde_json::from_str(s.as_str()).unwrap();
136        ks
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use crate::keystore::kdf::{Pbkdf2Params, ScryptParams};
143
144    use super::*;
145
146    #[test]
147    fn test_serialize() {
148        // PBKDF2
149        let pbkdf2_params = Pbkdf2Params {
150            c: 26144,
151            dklen: 32,
152            prf: "hmac-sha256".to_string(),
153            salt: "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd".to_string(),
154        };
155        let keystore = Keystore {
156            address: "0x888".to_string(),
157            crypto: CryptoInfo {
158                cipher: "aes-128-ctr".to_string(),
159                cipher_params: CipherParams {
160                    iv: "83dbcc02d8ccb40e466191a123791e0e".to_string(),
161                },
162                cipher_text: "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c"
163                    .to_string(),
164                kdf: KDF::PBKDF2(Some(pbkdf2_params.clone())),
165                mac: "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2".to_string(),
166            },
167            id: "3198bc9c-6672-5ab3-d995-4942343ae5b6".to_string(),
168            version: 1,
169        };
170        let serialized = keystore.to_string().unwrap();
171        println!("{}", serialized);
172
173        // ScryptParams
174        let scrypt_params = ScryptParams {
175            dklen: 32,
176            n: 262144,
177            p: 8,
178            r: 1,
179            salt: "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19".to_string(),
180        };
181        let keystore = Keystore {
182            address: "0x888".to_string(),
183            crypto: CryptoInfo {
184                cipher: "aes-128-ctr".to_string(),
185                cipher_params: CipherParams {
186                    iv: "83dbcc02d8ccb40e466191a123791e0e".to_string(),
187                },
188                cipher_text: "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c"
189                    .to_string(),
190                kdf: KDF::SCRYPT(Some(scrypt_params.clone())),
191                mac: "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2".to_string(),
192            },
193            id: "3198bc9c-6672-5ab3-d995-4942343ae5b6".to_string(),
194            version: 1,
195        };
196        let serialized = keystore.to_string().unwrap();
197        println!("{}", serialized);
198    }
199
200    #[test]
201    fn test_deserialize() {
202        let j = "
203            {\"address\":\"0xd7998FD7F5454722a16Cd67E881CedF9896CE396\",\"crypto\":{\"cipher\":\"aes-128-ctr\",\"cipherparams\":{\"iv\":\"24242424242424242424242424242424\"},\"ciphertext\":\"9a4785cd9c59ac3550e7be9c47045e24ae93bcd85518dd510f0e83537a6b1cf7\",\"kdf\":\"pbkdf2\",\"kdfparams\":{\"c\":262144,\"dklen\":32,\"prf\":\"hmac-sha256\",\"salt\":\"6949646d3976356e2b636e74462b32476d3742465677\"},\"mac\":\"1da9056f139ee205cc342233a1bfc3fb7cbd9f4d904aa9b971e373c369aee840\"},\"id\":\"2a327cb3-776a-4a77-8cf9-66b1a615d9b5\",\"version\":3}
204        ";
205        let ks = Keystore::from(j.to_string());
206        println!("{:?}", ks);
207    }
208
209    #[test]
210    fn test_encrypt_pbkdf2() {
211        let mnemonic = "lyrics mean wisdom census merit sample always escape spread tone pipe current";
212        let account = Account::from_phrase(mnemonic, None).unwrap();
213        let ks = Keystore::encrypt_pbkdf2(&account, "123456").unwrap();
214        println!("{} {:?}", ks.crypto.cipher_text, account.private_key());
215        println!("{}", ks.to_string().unwrap());
216    }
217
218    #[test]
219    fn test_pbkdf2() {
220        // encrypt
221        let password = "password";
222        let mnemonic = "lyrics mean wisdom census merit sample always escape spread tone pipe current";
223        let account1 = Account::from_phrase(mnemonic, None).unwrap();
224        let ks = Keystore::encrypt_pbkdf2(&account1, password).unwrap();
225        // decrypt
226        let account2 = Keystore::from(ks.to_string().unwrap()).decrypt(password).unwrap();
227        assert_eq!(account1.address.to_checksum(), account2.address.to_checksum());
228        assert_eq!(account1.private_key(), account2.private_key())
229    }
230
231    #[test]
232    fn test_decrypt_pbkdf2() {
233        let j = "
234        {\"address\":\"0xd7998fd7f5454722a16cd67e881cedf9896ce396\",\"crypto\":{\"cipher\":\"aes-128-ctr\",\"cipherparams\":{\"iv\":\"fa9d17a51ba92d61515a6bb629ad842e\"},\"ciphertext\":\"89ad80b27d8325bf79dcef52994e8b4ea626a357120dc8fdca4f0e61058f0496\",\"kdf\":\"pbkdf2\",\"kdfparams\":{\"c\":262144,\"dklen\":32,\"prf\":\"hmac-sha256\",\"salt\":\"dc0b353e69db487ee8ea3716085fe7b6\"},\"mac\":\"e15200e3b57f22714f09629fcf98b79fd8af6f8493c663f09c771e9cb17c1075\"},\"id\":\"83798882-4537-41ae-b205-30b032f2c88d\",\"version\":3}
235        ";
236        let account = Keystore::from(j.to_string()).decrypt("123456").unwrap();
237        assert_eq!(account.address.to_string(), "0xd7998FD7F5454722a16Cd67E881CedF9896CE396")
238    }
239
240    #[test]
241    fn test_decrypt_pbkdf2_password_error() {
242        let j = "
243        {\"address\":\"0xd7998fd7f5454722a16cd67e881cedf9896ce396\",\"crypto\":{\"cipher\":\"aes-128-ctr\",\"cipherparams\":{\"iv\":\"fa9d17a51ba92d61515a6bb629ad842e\"},\"ciphertext\":\"89ad80b27d8325bf79dcef52994e8b4ea626a357120dc8fdca4f0e61058f0496\",\"kdf\":\"pbkdf2\",\"kdfparams\":{\"c\":262144,\"dklen\":32,\"prf\":\"hmac-sha256\",\"salt\":\"dc0b353e69db487ee8ea3716085fe7b6\"},\"mac\":\"e15200e3b57f22714f09629fcf98b79fd8af6f8493c663f09c771e9cb17c1075\"},\"id\":\"83798882-4537-41ae-b205-30b032f2c88d\",\"version\":3}
244        ";
245        let r = Keystore::from(j.to_string()).decrypt("1234567");
246        assert_eq!(r.is_err(), true);
247        // 密码错误,KDF 导出错误,故 MAC 错误
248        assert_eq!(r.err().unwrap().to_string(), "invalid mac");
249    }
250
251    #[test]
252    fn test_decrypt_pbkdf2_address_mismatch() {
253        let j = "
254        {\"address\":\"0x17998fd7f5454722a16cd67e881cedf9896ce396\",\"crypto\":{\"cipher\":\"aes-128-ctr\",\"cipherparams\":{\"iv\":\"fa9d17a51ba92d61515a6bb629ad842e\"},\"ciphertext\":\"89ad80b27d8325bf79dcef52994e8b4ea626a357120dc8fdca4f0e61058f0496\",\"kdf\":\"pbkdf2\",\"kdfparams\":{\"c\":262144,\"dklen\":32,\"prf\":\"hmac-sha256\",\"salt\":\"dc0b353e69db487ee8ea3716085fe7b6\"},\"mac\":\"e15200e3b57f22714f09629fcf98b79fd8af6f8493c663f09c771e9cb17c1075\"},\"id\":\"83798882-4537-41ae-b205-30b032f2c88d\",\"version\":3}
255        ";
256        let r = Keystore::from(j.to_string()).decrypt("123456");
257        assert_eq!(r.is_err(), true);
258        assert_eq!(r.err().unwrap().to_string(), "address mismatch");
259    }
260
261    #[test]
262    fn test_decrypt_pbkdf2_invalid_mac() {
263        let j = "
264        {\"address\":\"0xd7998fd7f5454722a16cd67e881cedf9896ce396\",\"crypto\":{\"cipher\":\"aes-128-ctr\",\"cipherparams\":{\"iv\":\"fa9d17a51ba92d61515a6bb629ad842e\"},\"ciphertext\":\"89ad80b27d8325bf79dcef52994e8b4ea626a357120dc8fdca4f0e61058f0496\",\"kdf\":\"pbkdf2\",\"kdfparams\":{\"c\":262144,\"dklen\":32,\"prf\":\"hmac-sha256\",\"salt\":\"dc0b353e69db487ee8ea3716085fe7b6\"},\"mac\":\"e15200e3b57f22714f09629fcf98b79fd8af6f8493c663f09c771e9cb17c1076\"},\"id\":\"83798882-4537-41ae-b205-30b032f2c88d\",\"version\":3}
265        ";
266        let r = Keystore::from(j.to_string()).decrypt("123456");
267        assert_eq!(r.is_err(), true);
268        assert_eq!(r.err().unwrap().to_string(), "invalid mac");
269    }
270
271    #[test]
272    fn test_encrypt_scrypt() {
273        let mnemonic = "lyrics mean wisdom census merit sample always escape spread tone pipe current";
274        let account = Account::from_phrase(mnemonic, None).unwrap();
275        let ks = Keystore::encrypt_scrypt(&account, "123456").unwrap();
276        println!("{} {:?}", ks.crypto.cipher_text, account.private_key());
277        println!("{}", ks.to_string().unwrap());
278    }
279
280    #[test]
281    fn test_scrypt() {
282        // encrypt
283        let password = "cpchain";
284        let mnemonic = "lyrics mean wisdom census merit sample always escape spread tone pipe current";
285        let account1 = Account::from_phrase(mnemonic, None).unwrap();
286        let ks = Keystore::encrypt_scrypt(&account1, password).unwrap();
287        // decrypt
288        let account2 = Keystore::from(ks.to_string().unwrap()).decrypt(password).unwrap();
289        assert_eq!(account1.address.to_checksum(), account2.address.to_checksum());
290        assert_eq!(account1.private_key(), account2.private_key())
291    }
292
293    #[test]
294    fn test_decrypt_scrypt() {
295        // Generated by cpchain-cli
296        let password = "123456";
297        let j = "
298        {
299            \"address\": \"6cbea203f4061855247cea3843e2e5957c4cd428\",
300            \"id\": \"873cffc0-efe3-4efa-a68d-a22f8b333c96\",
301            \"version\": 3,
302            \"Crypto\": {
303              \"cipher\": \"aes-128-ctr\",
304              \"cipherparams\": { \"iv\": \"cca926fcfd00939b9846b876d447f436\" },
305              \"ciphertext\": \"2c3d7a39276a57a12acb3f4282f015a483a2c5453526f6c6184bd5273d9e0ca1\",
306              \"kdf\": \"scrypt\",
307              \"kdfparams\": {
308                \"salt\": \"91ac0a8a80bcc103a11057b9cef57e25057ce680b1b5ab844e11c32627bd2e50\",
309                \"n\": 131072,
310                \"dklen\": 32,
311                \"p\": 1,
312                \"r\": 8
313              },
314              \"mac\": \"4af097b05e403e8e135b3c2e7bcc73d3bae62989206f55b66856ce1d7e5e3977\"
315            }
316          }
317        ";
318        let r = Keystore::from(j.to_string()).decrypt(password).unwrap();
319        assert_eq!(r.address.to_checksum().to_lowercase(), "0x6cbea203f4061855247cea3843e2e5957c4cd428")
320    }
321
322}