ethers_wallet_rs/
keystore.rs

1use std::str::FromStr;
2
3use aes::{
4    cipher::{self, InnerIvInit, KeyInit, StreamCipherCore},
5    Aes128,
6};
7use ethers_hash_rs::pbkdf2::pbkdf2_hmac;
8use ethers_types_rs::{bytes_def, Address, AddressEx};
9use rand::{CryptoRng, Rng};
10use scrypt::{scrypt, Params as ScryptParams};
11use serde::{Deserialize, Serialize};
12use sha2::Sha256;
13use sha3::{Digest, Keccak256};
14use uuid::Uuid;
15
16use crate::{wallet::KeyProvider, WalletError};
17
18bytes_def!(IV);
19bytes_def!(CipherText);
20bytes_def!(MAC);
21bytes_def!(Salt);
22
23const DEFAULT_CIPHER: &str = "aes-128-ctr";
24const DEFAULT_KEY_SIZE: usize = 32usize;
25const DEFAULT_IV_SIZE: usize = 16usize;
26const DEFAULT_KDF_PARAMS_DKLEN: u8 = 32u8;
27const DEFAULT_KDF_PARAMS_LOG_N: u8 = 13u8;
28const DEFAULT_KDF_PARAMS_R: u32 = 8u32;
29const DEFAULT_KDF_PARAMS_P: u32 = 1u32;
30
31#[derive(Debug, thiserror::Error)]
32pub enum KeyStoreError {
33    #[error("Scrypt error,{0}")]
34    Scrypt(String),
35    #[error("Keystore from json, mac mismatch")]
36    MacMismatch,
37
38    #[error("Load key error,{0}")]
39    KeyProvider(WalletError),
40}
41
42#[derive(Debug, Serialize, Deserialize)]
43pub struct KeyStore {
44    pub address: Option<Address>,
45    pub crypto: CryptoJson,
46    pub id: Uuid,
47    pub version: u8,
48}
49
50#[derive(Debug, Deserialize, Serialize)]
51/// Represents the "crypto" part of an encrypted JSON keystore.
52pub struct CryptoJson {
53    pub cipher: String,
54    pub cipherparams: CipherparamsJson,
55    pub ciphertext: CipherText,
56    pub kdf: KdfType,
57    pub kdfparams: KdfparamsType,
58    pub mac: MAC,
59}
60
61#[derive(Debug, Deserialize, Serialize)]
62/// Represents the "cipherparams" part of an encrypted JSON keystore.
63pub struct CipherparamsJson {
64    pub iv: IV,
65}
66
67#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
68#[serde(rename_all = "lowercase")]
69/// Types of key derivition functions supported by the Web3 Secret Storage.
70pub enum KdfType {
71    Pbkdf2,
72    Scrypt,
73}
74
75#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
76#[serde(untagged)]
77/// Defines the various parameters used in the supported KDFs.
78pub enum KdfparamsType {
79    Pbkdf2 {
80        c: u32,
81        dklen: u8,
82        prf: String,
83        salt: Salt,
84    },
85    Scrypt {
86        dklen: u8,
87        n: u32,
88        p: u32,
89        r: u32,
90        salt: Salt,
91    },
92}
93
94impl TryInto<String> for KeyStore {
95    type Error = serde_json::Error;
96    fn try_into(self) -> Result<String, Self::Error> {
97        serde_json::to_string_pretty(&self)
98    }
99}
100
101impl FromStr for KeyStore {
102    type Err = serde_json::Error;
103    fn from_str(s: &str) -> Result<Self, Self::Err> {
104        serde_json::from_str(s)
105    }
106}
107
108pub trait KeyStoreEncrypt {
109    /// encrypt private_key or other data into keystore format with provide `password`
110    fn encrypt<B, P>(pk: B, password: P) -> anyhow::Result<KeyStore>
111    where
112        P: AsRef<[u8]>,
113        B: KeyProvider,
114    {
115        Self::encrypt_with(&mut rand::rngs::OsRng, pk, password)
116    }
117    fn encrypt_with<R, B, P>(rng: &mut R, pk: B, password: P) -> anyhow::Result<KeyStore>
118    where
119        R: Rng + CryptoRng,
120        P: AsRef<[u8]>,
121        B: KeyProvider,
122    {
123        // Generate a random salt.
124        let mut salt = vec![0u8; DEFAULT_KEY_SIZE];
125        rng.fill_bytes(salt.as_mut_slice());
126
127        // Derive the key.
128        let mut key = vec![0u8; DEFAULT_KDF_PARAMS_DKLEN as usize];
129        let scrypt_params = ScryptParams::new(
130            DEFAULT_KDF_PARAMS_LOG_N,
131            DEFAULT_KDF_PARAMS_R,
132            DEFAULT_KDF_PARAMS_P,
133        )
134        .map_err(|err| KeyStoreError::Scrypt(err.to_string()))?;
135
136        scrypt(password.as_ref(), &salt, &scrypt_params, key.as_mut_slice())
137            .map_err(|err| KeyStoreError::Scrypt(err.to_string()))?;
138
139        // Encrypt the private key using AES-128-CTR.
140        let mut iv = vec![0u8; DEFAULT_IV_SIZE];
141        rng.fill_bytes(iv.as_mut_slice());
142
143        let encryptor = Aes128Ctr::new(&key[..16], &iv[..16]).expect("invalid length");
144
145        let mut ciphertext = pk.load()?;
146
147        let address = Address::from_private_key(&ciphertext).ok();
148
149        encryptor.apply_keystream(&mut ciphertext);
150
151        // Calculate the MAC.
152        let mut mac = Keccak256::new();
153
154        mac.update(&key[16..32]);
155        mac.update(&ciphertext);
156
157        let mac = mac.finalize();
158
159        // If a file name is not specified for the keystore, simply use the strigified uuid.
160        let id = Uuid::new_v4();
161
162        // Construct and serialize the encrypted JSON keystore.
163        Ok(KeyStore {
164            id,
165            version: 3,
166            crypto: CryptoJson {
167                cipher: String::from(DEFAULT_CIPHER),
168                cipherparams: CipherparamsJson { iv: iv.into() },
169                ciphertext: ciphertext.to_vec().into(),
170                kdf: KdfType::Scrypt,
171                kdfparams: KdfparamsType::Scrypt {
172                    dklen: DEFAULT_KDF_PARAMS_DKLEN,
173                    n: 2u32.pow(DEFAULT_KDF_PARAMS_LOG_N as u32),
174                    p: DEFAULT_KDF_PARAMS_P,
175                    r: DEFAULT_KDF_PARAMS_R,
176                    salt: salt.into(),
177                },
178                mac: mac.to_vec().into(),
179            },
180            address,
181        })
182    }
183}
184
185struct Aes128Ctr {
186    inner: ctr::CtrCore<Aes128, ctr::flavors::Ctr128BE>,
187}
188
189impl Aes128Ctr {
190    fn new(key: &[u8], iv: &[u8]) -> Result<Self, cipher::InvalidLength> {
191        let cipher = aes::Aes128::new_from_slice(key).unwrap();
192        let inner = ctr::CtrCore::inner_iv_slice_init(cipher, iv).unwrap();
193        Ok(Self { inner })
194    }
195
196    fn apply_keystream(self, buf: &mut [u8]) {
197        self.inner.apply_keystream_partial(buf.into());
198    }
199}
200
201impl KeyStoreEncrypt for KeyStore {}
202
203impl KeyStore {
204    pub fn decrypt_into<S>(self, password: S) -> Result<Vec<u8>, KeyStoreError>
205    where
206        S: AsRef<[u8]>,
207    {
208        let keystore = self;
209
210        // Derive the key.
211        let key = match keystore.crypto.kdfparams {
212            KdfparamsType::Pbkdf2 {
213                c,
214                dklen,
215                prf: _,
216                salt,
217            } => {
218                let mut key = vec![0u8; dklen as usize];
219                pbkdf2_hmac::<Sha256>(password.as_ref(), &salt.0, c, key.as_mut_slice());
220                key
221            }
222            KdfparamsType::Scrypt {
223                dklen,
224                n,
225                p,
226                r,
227                salt,
228            } => {
229                let mut key = vec![0u8; dklen as usize];
230                let log_n = (n as f32).log2() as u8;
231                let scrypt_params = ScryptParams::new(log_n, r, p)
232                    .map_err(|err| KeyStoreError::Scrypt(err.to_string()))?;
233
234                scrypt(
235                    password.as_ref(),
236                    &salt.0,
237                    &scrypt_params,
238                    key.as_mut_slice(),
239                )
240                .map_err(|err| KeyStoreError::Scrypt(err.to_string()))?;
241                key
242            }
243        };
244
245        // Calculate the MAC.
246        let mut derived_mac = Keccak256::new();
247
248        derived_mac.update(&key[16..32]);
249        derived_mac.update(&keystore.crypto.ciphertext.0);
250        let derived_mac = derived_mac.finalize();
251
252        if derived_mac.as_slice() != keystore.crypto.mac.0.as_slice() {
253            return Err(KeyStoreError::MacMismatch);
254        }
255
256        // Decrypt the private key bytes using AES-128-CTR
257        let decryptor = Aes128Ctr::new(&key[..16], &keystore.crypto.cipherparams.iv.0[..16])
258            .expect("invalid length");
259
260        let mut pk = keystore.crypto.ciphertext.0;
261
262        decryptor.apply_keystream(&mut pk);
263
264        Ok(pk)
265    }
266}
267
268#[cfg(test)]
269mod tests {
270    use ethers_types_rs::{bytes::bytes_to_string, Address, AddressEx};
271
272    use super::{KeyStore, KeyStoreEncrypt};
273
274    #[test]
275    fn test_encrypt_decrypt() {
276        let keystore = KeyStore::encrypt(
277            "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
278            "thebestrandompassword",
279        )
280        .expect("Encrypt into keystore format");
281
282        // Check generate address.
283        assert_eq!(
284            keystore.address,
285            Some(
286                "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
287                    .try_into()
288                    .expect("")
289            )
290        );
291
292        let pk = keystore
293            .decrypt_into("thebestrandompassword")
294            .expect("Descrypt keystore");
295
296        assert_eq!(
297            bytes_to_string(&pk),
298            "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
299        );
300    }
301
302    #[test]
303    fn test_decrypt_pbkdf2() {
304        let keystore: KeyStore = include_str!("test-keys/pbkdf2.json")
305            .parse()
306            .expect("Load keystore from file");
307
308        let pk = keystore
309            .decrypt_into("testpassword")
310            .expect("Decrypt keystore with password");
311
312        assert_eq!(
313            bytes_to_string(&pk),
314            "0x7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
315        );
316    }
317
318    #[test]
319    fn test_decrypt_scrypt() {
320        let _ = pretty_env_logger::try_init();
321
322        let keystore: KeyStore = include_str!("test-keys/scrypt.json")
323            .parse()
324            .expect("Load keystore from file");
325
326        let pk = keystore
327            .decrypt_into("grOQ8QDnGHvpYJf")
328            .expect("Decrypt keystore with password");
329
330        assert_eq!(
331            bytes_to_string(&pk),
332            "0x80d3a6ed7b24dcd652949bc2f3827d2f883b3722e3120b15a93a2e0790f03829"
333        );
334    }
335
336    #[test]
337    fn test_decrypt_ethersjs_keystore() {
338        let keystore: KeyStore = include_str!("test-keys/ethersjs.json")
339            .parse()
340            .expect("Load keystore from file");
341
342        let expect_address = keystore.address.clone();
343
344        let pk = keystore
345            .decrypt_into("test")
346            .expect("Decrypt keystore with password");
347
348        let address = Address::from_private_key(&pk).expect("Get address from descrypt pk");
349
350        assert_eq!(expect_address, Some(address));
351    }
352}