use std::str::FromStr;
use aes::{
cipher::{self, InnerIvInit, KeyInit, StreamCipherCore},
Aes128,
};
use ethers_primitives::*;
use k256::SecretKey;
use rand::{CryptoRng, Rng};
use scrypt::{scrypt, Params as ScryptParams};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use sha3::{Digest, Keccak256};
use uuid::Uuid;
use crate::{hash::pbkdf2::pbkdf2_hmac, wallet::KeyProvider, WalletError};
pub type IV = Bytes;
pub type CipherText = Bytes;
pub type MAC = Bytes;
pub type Salt = Bytes;
const DEFAULT_CIPHER: &str = "aes-128-ctr";
const DEFAULT_KEY_SIZE: usize = 32usize;
const DEFAULT_IV_SIZE: usize = 16usize;
const DEFAULT_KDF_PARAMS_DKLEN: u8 = 32u8;
const DEFAULT_KDF_PARAMS_LOG_N: u8 = 13u8;
const DEFAULT_KDF_PARAMS_R: u32 = 8u32;
const DEFAULT_KDF_PARAMS_P: u32 = 1u32;
#[derive(Debug, thiserror::Error)]
pub enum KeyStoreError {
#[error("Scrypt error,{0}")]
Scrypt(String),
#[error("Keystore from json, mac mismatch")]
MacMismatch,
#[error("Load key error,{0}")]
KeyProvider(WalletError),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KeyStore {
pub address: Option<Address>,
pub crypto: CryptoJson,
pub id: Uuid,
pub version: u8,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct CryptoJson {
pub cipher: String,
pub cipherparams: CipherparamsJson,
pub ciphertext: CipherText,
pub kdf: KdfType,
pub kdfparams: KdfparamsType,
pub mac: MAC,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct CipherparamsJson {
pub iv: IV,
}
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum KdfType {
Pbkdf2,
Scrypt,
}
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum KdfparamsType {
Pbkdf2 {
c: u32,
dklen: u8,
prf: String,
salt: Salt,
},
Scrypt {
dklen: u8,
n: u32,
p: u32,
r: u32,
salt: Salt,
},
}
impl TryInto<String> for KeyStore {
type Error = serde_json::Error;
fn try_into(self) -> Result<String, Self::Error> {
serde_json::to_string_pretty(&self)
}
}
impl FromStr for KeyStore {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s)
}
}
pub trait KeyStoreEncrypt {
fn encrypt<B, P>(pk: B, password: P) -> anyhow::Result<KeyStore>
where
P: AsRef<[u8]>,
B: KeyProvider,
{
Self::encrypt_with(&mut rand::rngs::OsRng, pk, password)
}
fn encrypt_with<R, B, P>(rng: &mut R, pk: B, password: P) -> anyhow::Result<KeyStore>
where
R: Rng + CryptoRng,
P: AsRef<[u8]>,
B: KeyProvider,
{
let mut salt = vec![0u8; DEFAULT_KEY_SIZE];
rng.fill_bytes(salt.as_mut_slice());
let mut key = vec![0u8; DEFAULT_KDF_PARAMS_DKLEN as usize];
let scrypt_params = ScryptParams::new(
DEFAULT_KDF_PARAMS_LOG_N,
DEFAULT_KDF_PARAMS_R,
DEFAULT_KDF_PARAMS_P,
)
.map_err(|err| KeyStoreError::Scrypt(err.to_string()))?;
scrypt(password.as_ref(), &salt, &scrypt_params, key.as_mut_slice())
.map_err(|err| KeyStoreError::Scrypt(err.to_string()))?;
let mut iv = vec![0u8; DEFAULT_IV_SIZE];
rng.fill_bytes(iv.as_mut_slice());
let encryptor = Aes128Ctr::new(&key[..16], &iv[..16]).expect("invalid length");
let mut ciphertext = pk.load()?;
let sec_key = SecretKey::from_be_bytes(&ciphertext)?;
let address = Address::try_from(sec_key).ok();
encryptor.apply_keystream(&mut ciphertext);
let mut mac = Keccak256::new();
mac.update(&key[16..32]);
mac.update(&ciphertext);
let mac = mac.finalize();
let id = Uuid::new_v4();
Ok(KeyStore {
id,
version: 3,
crypto: CryptoJson {
cipher: String::from(DEFAULT_CIPHER),
cipherparams: CipherparamsJson { iv: iv.into() },
ciphertext: ciphertext.to_vec().into(),
kdf: KdfType::Scrypt,
kdfparams: KdfparamsType::Scrypt {
dklen: DEFAULT_KDF_PARAMS_DKLEN,
n: 2u32.pow(DEFAULT_KDF_PARAMS_LOG_N as u32),
p: DEFAULT_KDF_PARAMS_P,
r: DEFAULT_KDF_PARAMS_R,
salt: salt.into(),
},
mac: mac.to_vec().into(),
},
address,
})
}
}
struct Aes128Ctr {
inner: ctr::CtrCore<Aes128, ctr::flavors::Ctr128BE>,
}
impl Aes128Ctr {
fn new(key: &[u8], iv: &[u8]) -> Result<Self, cipher::InvalidLength> {
let cipher = aes::Aes128::new_from_slice(key).unwrap();
let inner = ctr::CtrCore::inner_iv_slice_init(cipher, iv).unwrap();
Ok(Self { inner })
}
fn apply_keystream(self, buf: &mut [u8]) {
self.inner.apply_keystream_partial(buf.into());
}
}
impl KeyStoreEncrypt for KeyStore {}
impl KeyStore {
pub fn decrypt_into<S>(self, password: S) -> Result<Vec<u8>, KeyStoreError>
where
S: AsRef<[u8]>,
{
let keystore = self;
let key = match keystore.crypto.kdfparams {
KdfparamsType::Pbkdf2 {
c,
dklen,
prf: _,
salt,
} => {
let mut key = vec![0u8; dklen as usize];
pbkdf2_hmac::<Sha256>(password.as_ref(), &salt.0, c, key.as_mut_slice());
key
}
KdfparamsType::Scrypt {
dklen,
n,
p,
r,
salt,
} => {
let mut key = vec![0u8; dklen as usize];
let log_n = (n as f32).log2() as u8;
let scrypt_params = ScryptParams::new(log_n, r, p)
.map_err(|err| KeyStoreError::Scrypt(err.to_string()))?;
scrypt(
password.as_ref(),
&salt.0,
&scrypt_params,
key.as_mut_slice(),
)
.map_err(|err| KeyStoreError::Scrypt(err.to_string()))?;
key
}
};
let mut derived_mac = Keccak256::new();
derived_mac.update(&key[16..32]);
derived_mac.update(&keystore.crypto.ciphertext.0);
let derived_mac = derived_mac.finalize();
if derived_mac.as_slice() != keystore.crypto.mac.0.as_slice() {
return Err(KeyStoreError::MacMismatch);
}
let decryptor = Aes128Ctr::new(&key[..16], &keystore.crypto.cipherparams.iv.0[..16])
.expect("invalid length");
let mut pk = keystore.crypto.ciphertext.0;
decryptor.apply_keystream(&mut pk);
Ok(pk)
}
}
#[cfg(test)]
mod tests {
use ethers_primitives::*;
use k256::SecretKey;
use super::{KeyStore, KeyStoreEncrypt};
#[test]
fn test_encrypt_decrypt() {
let keystore = KeyStore::encrypt(
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
"thebestrandompassword",
)
.expect("Encrypt into keystore format");
assert_eq!(
keystore.address,
Some(
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
.try_into()
.expect("")
)
);
let pk = keystore
.decrypt_into("thebestrandompassword")
.expect("Descrypt keystore");
assert_eq!(
pk.to_eth_hex(),
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
);
}
#[test]
fn test_decrypt_pbkdf2() {
let keystore: KeyStore = include_str!("test-keys/pbkdf2.json")
.parse()
.expect("Load keystore from file");
let pk = keystore
.decrypt_into("testpassword")
.expect("Decrypt keystore with password");
assert_eq!(
pk.to_eth_hex(),
"0x7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
);
}
#[test]
fn test_decrypt_scrypt() {
let _ = pretty_env_logger::try_init();
let keystore: KeyStore = include_str!("test-keys/scrypt.json")
.parse()
.expect("Load keystore from file");
let pk = keystore
.decrypt_into("grOQ8QDnGHvpYJf")
.expect("Decrypt keystore with password");
assert_eq!(
pk.to_eth_hex(),
"0x80d3a6ed7b24dcd652949bc2f3827d2f883b3722e3120b15a93a2e0790f03829"
);
}
#[test]
fn test_decrypt_ethersjs_keystore() {
let keystore: KeyStore = include_str!("test-keys/ethersjs.json")
.parse()
.expect("Load keystore from file");
let expect_address = keystore.address.clone();
let pk = SecretKey::from_be_bytes(
&keystore
.decrypt_into("test")
.expect("Decrypt keystore with password"),
)
.unwrap();
let address = Address::try_from(pk).expect("Get address from descrypt pk");
assert_eq!(expect_address, Some(address));
}
}