use std::io::{self, Read};
use crypto::symmetriccipher::SymmetricCipherError;
use crypto::aessafe::AesSafe128Encryptor;
use crypto::blockmodes::CtrMode;
use crypto::buffer::{RefReadBuffer, RefWriteBuffer};
use crypto::scrypt::{self, ScryptParams};
use crypto::symmetriccipher::Decryptor;
use hex::{FromHex, FromHexError};
use json;
use keccak::Keccak;
use secp256k1::{self, Secp256k1, SecretKey};
#[derive(Debug)]
pub enum Error {
Io(io::Error),
/// Error that is returned when failed to deserialize the account from JSON.
Json(json::Error),
FromHexError(FromHexError),
InvalidCipher(String),
InvalidPassphrase,
/// Unknown account version.
///
/// The linked id contains actual version number.
InvalidVersion(i32),
Secp256k1(secp256k1::Error),
CipherError(SymmetricCipherError),
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Error::Io(err)
}
}
impl From<json::Error> for Error {
fn from(err: json::Error) -> Self {
Error::Json(err)
}
}
impl From<FromHexError> for Error {
fn from(err: FromHexError) -> Self {
Error::FromHexError(err)
}
}
impl From<secp256k1::Error> for Error {
fn from(err: secp256k1::Error) -> Self {
Error::Secp256k1(err)
}
}
impl From<SymmetricCipherError> for Error {
fn from(err: SymmetricCipherError) -> Self {
Error::CipherError(err)
}
}
#[derive(Clone, Debug, Deserialize)]
struct KDFParams {
dklen: u32,
n: u32,
p: u32,
r: u32,
salt: String,
}
impl KDFParams {
#[inline]
fn log2n(&self) -> u8 {
(32 - self.n.leading_zeros() - 1) as u8
}
}
#[derive(Clone, Debug, Deserialize)]
struct CipherParams {
iv: String,
}
#[derive(Clone, Debug, Deserialize)]
struct Crypto {
/// The name of a symmetric AES algorithm.
cipher: String,
/// Your Ethereum private key encrypted using the "cipher" algorithm above.
ciphertext: String,
/// The parameters required for the "cipher" algorithm above.
cipherparams: CipherParams,
/// A Key Derivation Function used to let you encrypt your keystore file with a password.
kdf: String,
/// The parameters required for the "kdf" algorithm above.
kdfparams: KDFParams,
/// A code used to verify your password.
mac: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Account {
address: String,
crypto: Crypto,
id: String,
version: i32,
}
impl Account {
pub fn from_rd<R: Read>(rd: R) -> Result<Self, Error> {
let account = json::from_reader(rd)?;
Ok(account)
}
#[inline]
pub fn id(&self) -> &str {
&self.id
}
pub fn decrypt(&self, password: &str) -> Result<SecretKey, Error> {
match self.version {
3 => {
let key = decrypt_v3(&self.crypto, password)?;
let secp = Secp256k1::new();
let secret = SecretKey::from_slice(&secp, &key)?;
Ok(secret)
}
version => Err(Error::InvalidVersion(version)),
}
}
}
fn decrypt_v3(crypto: &Crypto, password: &str) -> Result<Vec<u8>, Error> {
match crypto.cipher.as_str() {
"aes-128-ctr" => {
let salt = crypto.kdfparams.salt.from_hex()?;
let params = ScryptParams::new(crypto.kdfparams.log2n(), crypto.kdfparams.r, crypto.kdfparams.p);
let mut derived_key = vec![0u8; crypto.kdfparams.dklen as usize];
scrypt::scrypt(password.as_bytes(), &salt, ¶ms, &mut derived_key);
let ciphertext = crypto.ciphertext.from_hex()?;
let mut keccak = Keccak::new_keccak256();
let mut mac = [0u8; 32];
keccak.update(&derived_key[16..32]);
keccak.update(&ciphertext);
keccak.finalize(&mut mac);
if mac == crypto.mac.from_hex()?.as_ref() {
let iv = crypto.cipherparams.iv.from_hex()?;
let mut buf = vec![0u8; ciphertext.len()];
let mut encrypt = CtrMode::new(AesSafe128Encryptor::new(&derived_key[..16]), iv.to_vec());
encrypt.decrypt(&mut RefReadBuffer::new(&ciphertext), &mut RefWriteBuffer::new(&mut buf), true)?;
Ok(buf)
} else {
Err(Error::InvalidPassphrase)
}
}
cipher => Err(Error::InvalidCipher(cipher.into())),
}
}
#[cfg(test)]
mod test {
use hex::FromHex;
use secp256k1;
use super::*;
#[test]
fn open_account() {
let buf = r#"{"address":"8125721c2413d99a33e351e1f6bb4e56b6b633fd","crypto":{"cipher":"aes-128-ctr","ciphertext":"ee00310ad9ab03f7b85cbabf72b919c7ed15f80b71f9531c167823995b28057a","cipherparams":{"iv":"67365b4d1ba21d457f3dbb22e46b627a"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":4096,"p":6,"r":8,"salt":"0b28c47f7b5cf61be98e157c1f12d52e039120c28f42d50291f4674dc262ef8f"},"mac":"5f370bb0bcc879ed0db74716cbd24861ab0a9839e6985b82f9c2b48a127b6453"},"id":"c0137aac-b22c-42f0-9b42-192af24fe103","version":3}"#;
let account = Account::from_rd(buf.as_bytes()).unwrap();
assert_eq!("c0137aac-b22c-42f0-9b42-192af24fe103", account.id());
let secret = account.decrypt("any").unwrap();
let x = secp256k1::Secp256k1::new();
assert_eq!(SecretKey::from_slice(&x, &"a5dd45e0810ca83e21f1063e6bf055bd13544398f280701cbfda1346bcf3ae64"[..].from_hex().unwrap()).unwrap(), secret);
}
}