Merge with master

This commit is contained in:
Robert Habermeier
2016-06-29 14:46:29 +02:00
parent 3850ee64bb
commit 49024a4f28
235 changed files with 36456 additions and 4814 deletions

View File

@@ -0,0 +1,59 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use json;
#[derive(Debug, PartialEq, Clone)]
pub struct Aes128Ctr {
pub iv: [u8; 16],
}
#[derive(Debug, PartialEq, Clone)]
pub enum Cipher {
Aes128Ctr(Aes128Ctr),
}
impl From<json::Aes128Ctr> for Aes128Ctr {
fn from(json: json::Aes128Ctr) -> Self {
Aes128Ctr {
iv: json.iv.into()
}
}
}
impl Into<json::Aes128Ctr> for Aes128Ctr {
fn into(self) -> json::Aes128Ctr {
json::Aes128Ctr {
iv: From::from(self.iv)
}
}
}
impl From<json::Cipher> for Cipher {
fn from(json: json::Cipher) -> Self {
match json {
json::Cipher::Aes128Ctr(params) => Cipher::Aes128Ctr(From::from(params)),
}
}
}
impl Into<json::Cipher> for Cipher {
fn into(self) -> json::Cipher {
match self {
Cipher::Aes128Ctr(params) => json::Cipher::Aes128Ctr(params.into()),
}
}
}

125
ethstore/src/account/kdf.rs Normal file
View File

@@ -0,0 +1,125 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use json;
#[derive(Debug, PartialEq, Clone)]
pub enum Prf {
HmacSha256,
}
#[derive(Debug, PartialEq, Clone)]
pub struct Pbkdf2 {
pub c: u32,
pub dklen: u32,
pub prf: Prf,
pub salt: [u8; 32],
}
#[derive(Debug, PartialEq, Clone)]
pub struct Scrypt {
pub dklen: u32,
pub p: u32,
pub n: u32,
pub r: u32,
pub salt: [u8; 32],
}
#[derive(Debug, PartialEq, Clone)]
pub enum Kdf {
Pbkdf2(Pbkdf2),
Scrypt(Scrypt),
}
impl From<json::Prf> for Prf {
fn from(json: json::Prf) -> Self {
match json {
json::Prf::HmacSha256 => Prf::HmacSha256,
}
}
}
impl Into<json::Prf> for Prf {
fn into(self) -> json::Prf {
match self {
Prf::HmacSha256 => json::Prf::HmacSha256,
}
}
}
impl From<json::Pbkdf2> for Pbkdf2 {
fn from(json: json::Pbkdf2) -> Self {
Pbkdf2 {
c: json.c,
dklen: json.dklen,
prf: From::from(json.prf),
salt: json.salt.into(),
}
}
}
impl Into<json::Pbkdf2> for Pbkdf2 {
fn into(self) -> json::Pbkdf2 {
json::Pbkdf2 {
c: self.c,
dklen: self.dklen,
prf: self.prf.into(),
salt: From::from(self.salt),
}
}
}
impl From<json::Scrypt> for Scrypt {
fn from(json: json::Scrypt) -> Self {
Scrypt {
dklen: json.dklen,
p: json.p,
n: json.n,
r: json.r,
salt: json.salt.into(),
}
}
}
impl Into<json::Scrypt> for Scrypt {
fn into(self) -> json::Scrypt {
json::Scrypt {
dklen: self.dklen,
p: self.p,
n: self.n,
r: self.r,
salt: From::from(self.salt),
}
}
}
impl From<json::Kdf> for Kdf {
fn from(json: json::Kdf) -> Self {
match json {
json::Kdf::Pbkdf2(params) => Kdf::Pbkdf2(From::from(params)),
json::Kdf::Scrypt(params) => Kdf::Scrypt(From::from(params)),
}
}
}
impl Into<json::Kdf> for Kdf {
fn into(self) -> json::Kdf {
match self {
Kdf::Pbkdf2(params) => json::Kdf::Pbkdf2(params.into()),
Kdf::Scrypt(params) => json::Kdf::Scrypt(params.into()),
}
}
}

View File

@@ -0,0 +1,25 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
mod cipher;
mod kdf;
mod safe_account;
mod version;
pub use self::cipher::{Cipher, Aes128Ctr};
pub use self::kdf::{Kdf, Pbkdf2, Scrypt, Prf};
pub use self::safe_account::{SafeAccount, Crypto};
pub use self::version::Version;

View File

@@ -0,0 +1,216 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::ops::{Deref, DerefMut};
use ethkey::{KeyPair, sign, Address, Secret, Signature, Message};
use {json, Error, crypto};
use crypto::Keccak256;
use random::Random;
use account::{Version, Cipher, Kdf, Aes128Ctr, Pbkdf2, Prf};
#[derive(Debug, PartialEq, Clone)]
pub struct Crypto {
pub cipher: Cipher,
pub ciphertext: [u8; 32],
pub kdf: Kdf,
pub mac: [u8; 32],
}
#[derive(Debug, PartialEq, Clone)]
pub struct SafeAccount {
pub id: [u8; 16],
pub version: Version,
pub address: Address,
pub crypto: Crypto,
}
impl From<json::Crypto> for Crypto {
fn from(json: json::Crypto) -> Self {
Crypto {
cipher: From::from(json.cipher),
ciphertext: json.ciphertext.into(),
kdf: From::from(json.kdf),
mac: json.mac.into(),
}
}
}
impl Into<json::Crypto> for Crypto {
fn into(self) -> json::Crypto {
json::Crypto {
cipher: self.cipher.into(),
ciphertext: From::from(self.ciphertext),
kdf: self.kdf.into(),
mac: From::from(self.mac),
}
}
}
impl From<json::KeyFile> for SafeAccount {
fn from(json: json::KeyFile) -> Self {
SafeAccount {
id: json.id.into(),
version: From::from(json.version),
address: From::from(json.address), //json.address.into(),
crypto: From::from(json.crypto),
}
}
}
impl Into<json::KeyFile> for SafeAccount {
fn into(self) -> json::KeyFile {
json::KeyFile {
id: From::from(self.id),
version: self.version.into(),
address: self.address.into(), //From::from(self.address),
crypto: self.crypto.into(),
}
}
}
impl Crypto {
pub fn create(secret: &Secret, password: &str, iterations: u32) -> Self {
let salt: [u8; 32] = Random::random();
let iv: [u8; 16] = Random::random();
// two parts of derived key
// DK = [ DK[0..15] DK[16..31] ] = [derived_left_bits, derived_right_bits]
let (derived_left_bits, derived_right_bits) = crypto::derive_key_iterations(password, &salt, iterations);
let mut ciphertext = [0u8; 32];
// aes-128-ctr with initial vector of iv
crypto::aes::encrypt(&derived_left_bits, &iv, secret.deref(), &mut ciphertext);
// KECCAK(DK[16..31] ++ <ciphertext>), where DK[16..31] - derived_right_bits
let mac = crypto::derive_mac(&derived_right_bits, &ciphertext).keccak256();
Crypto {
cipher: Cipher::Aes128Ctr(Aes128Ctr {
iv: iv,
}),
ciphertext: ciphertext,
kdf: Kdf::Pbkdf2(Pbkdf2 {
dklen: crypto::KEY_LENGTH as u32,
salt: salt,
c: iterations,
prf: Prf::HmacSha256,
}),
mac: mac,
}
}
pub fn secret(&self, password: &str) -> Result<Secret, Error> {
let (derived_left_bits, derived_right_bits) = match self.kdf {
Kdf::Pbkdf2(ref params) => crypto::derive_key_iterations(password, &params.salt, params.c),
Kdf::Scrypt(ref params) => crypto::derive_key_scrypt(password, &params.salt, params.n, params.p, params.r),
};
let mac = crypto::derive_mac(&derived_right_bits, &self.ciphertext).keccak256();
if mac != self.mac {
return Err(Error::InvalidPassword);
}
let mut secret = Secret::default();
match self.cipher {
Cipher::Aes128Ctr(ref params) => {
crypto::aes::decrypt(&derived_left_bits, &params.iv, &self.ciphertext, secret.deref_mut())
},
}
Ok(secret)
}
}
impl SafeAccount {
pub fn create(keypair: &KeyPair, id: [u8; 16], password: &str, iterations: u32) -> Self {
SafeAccount {
id: id,
version: Version::V3,
crypto: Crypto::create(keypair.secret(), password, iterations),
address: keypair.address(),
}
}
pub fn sign(&self, password: &str, message: &Message) -> Result<Signature, Error> {
let secret = try!(self.crypto.secret(password));
sign(&secret, message).map_err(From::from)
}
pub fn change_password(&self, old_password: &str, new_password: &str, iterations: u32) -> Result<Self, Error> {
let secret = try!(self.crypto.secret(old_password));
let result = SafeAccount {
id: self.id.clone(),
version: self.version.clone(),
crypto: Crypto::create(&secret, new_password, iterations),
address: self.address.clone(),
};
Ok(result)
}
pub fn check_password(&self, password: &str) -> bool {
self.crypto.secret(password).is_ok()
}
}
#[cfg(test)]
mod tests {
use ethkey::{Generator, Random, verify_public, Message};
use super::{Crypto, SafeAccount};
#[test]
fn crypto_create() {
let keypair = Random.generate().unwrap();
let crypto = Crypto::create(keypair.secret(), "this is sparta", 10240);
let secret = crypto.secret("this is sparta").unwrap();
assert_eq!(keypair.secret(), &secret);
}
#[test]
#[should_panic]
fn crypto_invalid_password() {
let keypair = Random.generate().unwrap();
let crypto = Crypto::create(keypair.secret(), "this is sparta", 10240);
let _ = crypto.secret("this is sparta!").unwrap();
}
#[test]
fn sign_and_verify_public() {
let keypair = Random.generate().unwrap();
let password = "hello world";
let message = Message::default();
let account = SafeAccount::create(&keypair, [0u8; 16], password, 10240);
let signature = account.sign(password, &message).unwrap();
assert!(verify_public(keypair.public(), &signature, &message).unwrap());
}
#[test]
fn change_password() {
let keypair = Random.generate().unwrap();
let first_password = "hello world";
let sec_password = "this is sparta";
let i = 10240;
let message = Message::default();
let account = SafeAccount::create(&keypair, [0u8; 16], first_password, i);
let new_account = account.change_password(first_password, sec_password, i).unwrap();
assert!(account.sign(first_password, &message).is_ok());
assert!(account.sign(sec_password, &message).is_err());
assert!(new_account.sign(first_password, &message).is_err());
assert!(new_account.sign(sec_password, &message).is_ok());
}
}

View File

@@ -0,0 +1,38 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use json;
#[derive(Debug, PartialEq, Clone)]
pub enum Version {
V3,
}
impl From<json::Version> for Version {
fn from(json: json::Version) -> Self {
match json {
json::Version::V3 => Version::V3,
}
}
}
impl Into<json::Version> for Version {
fn into(self) -> json::Version {
match self {
Version::V3 => json::Version::V3,
}
}
}

View File

@@ -0,0 +1,169 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate rustc_serialize;
extern crate docopt;
extern crate ethstore;
use std::{env, process, fs};
use std::io::Read;
use std::ops::Deref;
use std::str::FromStr;
use docopt::Docopt;
use ethstore::ethkey::{Secret, Address, Message};
use ethstore::dir::{KeyDirectory, ParityDirectory, DiskDirectory, GethDirectory, DirectoryType};
use ethstore::{EthStore, SecretStore, import_accounts, Error, PresaleWallet};
pub const USAGE: &'static str = r#"
Ethereum key management.
Copyright 2016 Ethcore (UK) Limited
Usage:
ethstore insert <secret> <password> [--dir DIR]
ethstore change-pwd <address> <old-pwd> <new-pwd> [--dir DIR]
ethstore list [--dir DIR]
ethstore import [--src DIR] [--dir DIR]
ethstore import-wallet <path> <password> [--dir DIR]
ethstore remove <address> <password> [--dir DIR]
ethstore sign <address> <password> <message> [--dir DIR]
ethstore [-h | --help]
Options:
-h, --help Display this message and exit.
--dir DIR Specify the secret store directory. It may be either
parity, parity-test, geth, geth-test
or a path [default: parity].
--src DIR Specify import source. It may be either
parity, parity-test, get, geth-test
or a path [default: geth].
Commands:
insert Save account with password.
change-pwd Change password.
list List accounts.
import Import accounts from src.
import-wallet Import presale wallet.
remove Remove account.
sign Sign message.
"#;
#[derive(Debug, RustcDecodable)]
struct Args {
cmd_insert: bool,
cmd_change_pwd: bool,
cmd_list: bool,
cmd_import: bool,
cmd_import_wallet: bool,
cmd_remove: bool,
cmd_sign: bool,
arg_secret: String,
arg_password: String,
arg_old_pwd: String,
arg_new_pwd: String,
arg_address: String,
arg_message: String,
arg_path: String,
flag_src: String,
flag_dir: String,
}
fn main() {
match execute(env::args()) {
Ok(result) => println!("{}", result),
Err(err) => {
println!("{}", err);
process::exit(1);
}
}
}
fn key_dir(location: &str) -> Result<Box<KeyDirectory>, Error> {
let dir: Box<KeyDirectory> = match location {
"parity" => Box::new(try!(ParityDirectory::create(DirectoryType::Main))),
"parity-test" => Box::new(try!(ParityDirectory::create(DirectoryType::Testnet))),
"geth" => Box::new(try!(GethDirectory::create(DirectoryType::Main))),
"geth-test" => Box::new(try!(GethDirectory::create(DirectoryType::Testnet))),
path => Box::new(try!(DiskDirectory::create(path))),
};
Ok(dir)
}
fn format_accounts(accounts: &[Address]) -> String {
accounts.iter()
.enumerate()
.map(|(i, a)| format!("{:2}: {}", i, a))
.collect::<Vec<String>>()
.join("\n")
}
fn load_password(path: &str) -> Result<String, Error> {
let mut file = try!(fs::File::open(path));
let mut password = String::new();
try!(file.read_to_string(&mut password));
// drop EOF
let _ = password.pop();
Ok(password)
}
fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item=S>, S: AsRef<str> {
let args: Args = Docopt::new(USAGE)
.and_then(|d| d.argv(command).decode())
.unwrap_or_else(|e| e.exit());
let store = try!(EthStore::open(try!(key_dir(&args.flag_dir))));
return if args.cmd_insert {
let secret = try!(Secret::from_str(&args.arg_secret));
let password = try!(load_password(&args.arg_password));
let address = try!(store.insert_account(secret, &password));
Ok(format!("{}", address))
} else if args.cmd_change_pwd {
let address = try!(Address::from_str(&args.arg_address));
let old_pwd = try!(load_password(&args.arg_old_pwd));
let new_pwd = try!(load_password(&args.arg_new_pwd));
let ok = store.change_password(&address, &old_pwd, &new_pwd).is_ok();
Ok(format!("{}", ok))
} else if args.cmd_list {
let accounts = store.accounts();
Ok(format_accounts(&accounts))
} else if args.cmd_import {
let src = try!(key_dir(&args.flag_src));
let dst = try!(key_dir(&args.flag_dir));
let accounts = try!(import_accounts(src.deref(), dst.deref()));
Ok(format_accounts(&accounts))
} else if args.cmd_import_wallet {
let wallet = try!(PresaleWallet::open(&args.arg_path));
let password = try!(load_password(&args.arg_password));
let kp = try!(wallet.decrypt(&password));
let address = try!(store.insert_account(kp.secret().clone(), &password));
Ok(format!("{}", address))
} else if args.cmd_remove {
let address = try!(Address::from_str(&args.arg_address));
let password = try!(load_password(&args.arg_password));
let ok = store.remove_account(&address, &password).is_ok();
Ok(format!("{}", ok))
} else if args.cmd_sign {
let address = try!(Address::from_str(&args.arg_address));
let message = try!(Message::from_str(&args.arg_message));
let password = try!(load_password(&args.arg_password));
let signature = try!(store.sign(&address, &password, &message));
Ok(format!("{}", signature))
} else {
unreachable!();
}
}

21
ethstore/src/bin/main.rs Normal file
View File

@@ -0,0 +1,21 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
#[cfg(feature = "cli")]
include!("ethstore.rs");
#[cfg(not(feature = "cli"))]
fn main() {}

93
ethstore/src/crypto.rs Normal file
View File

@@ -0,0 +1,93 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tiny_keccak::Keccak;
use rcrypto::pbkdf2::pbkdf2;
use rcrypto::scrypt::{scrypt, ScryptParams};
use rcrypto::sha2::Sha256;
use rcrypto::hmac::Hmac;
pub const KEY_LENGTH: usize = 32;
pub const KEY_ITERATIONS: usize = 10240;
pub const KEY_LENGTH_AES: usize = KEY_LENGTH / 2;
pub fn derive_key_iterations(password: &str, salt: &[u8; 32], c: u32) -> (Vec<u8>, Vec<u8>) {
let mut h_mac = Hmac::new(Sha256::new(), password.as_bytes());
let mut derived_key = vec![0u8; KEY_LENGTH];
pbkdf2(&mut h_mac, salt, c, &mut derived_key);
let derived_right_bits = &derived_key[0..KEY_LENGTH_AES];
let derived_left_bits = &derived_key[KEY_LENGTH_AES..KEY_LENGTH];
(derived_right_bits.to_vec(), derived_left_bits.to_vec())
}
pub fn derive_key_scrypt(password: &str, salt: &[u8; 32], n: u32, p: u32, r: u32) -> (Vec<u8>, Vec<u8>) {
let mut derived_key = vec![0u8; KEY_LENGTH];
let scrypt_params = ScryptParams::new(n.trailing_zeros() as u8, r, p);
scrypt(password.as_bytes(), salt, &scrypt_params, &mut derived_key);
let derived_right_bits = &derived_key[0..KEY_LENGTH_AES];
let derived_left_bits = &derived_key[KEY_LENGTH_AES..KEY_LENGTH];
(derived_right_bits.to_vec(), derived_left_bits.to_vec())
}
pub fn derive_mac(derived_left_bits: &[u8], cipher_text: &[u8]) -> Vec<u8> {
let mut mac = vec![0u8; KEY_LENGTH_AES + cipher_text.len()];
mac[0..KEY_LENGTH_AES].copy_from_slice(derived_left_bits);
mac[KEY_LENGTH_AES..cipher_text.len() + KEY_LENGTH_AES].copy_from_slice(cipher_text);
mac
}
pub trait Keccak256<T> {
fn keccak256(&self) -> T where T: Sized;
}
impl Keccak256<[u8; 32]> for [u8] {
fn keccak256(&self) -> [u8; 32] {
let mut keccak = Keccak::new_keccak256();
let mut result = [0u8; 32];
keccak.update(self);
keccak.finalize(&mut result);
result
}
}
/// AES encryption
pub mod aes {
use rcrypto::blockmodes::{CtrMode, CbcDecryptor, PkcsPadding};
use rcrypto::aessafe::{AesSafe128Encryptor, AesSafe128Decryptor};
use rcrypto::symmetriccipher::{Encryptor, Decryptor, SymmetricCipherError};
use rcrypto::buffer::{RefReadBuffer, RefWriteBuffer};
/// Encrypt a message
pub fn encrypt(k: &[u8], iv: &[u8], plain: &[u8], dest: &mut [u8]) {
let mut encryptor = CtrMode::new(AesSafe128Encryptor::new(k), iv.to_vec());
encryptor.encrypt(&mut RefReadBuffer::new(plain), &mut RefWriteBuffer::new(dest), true).expect("Invalid length or padding");
}
/// Decrypt a message
pub fn decrypt(k: &[u8], iv: &[u8], encrypted: &[u8], dest: &mut [u8]) {
let mut encryptor = CtrMode::new(AesSafe128Encryptor::new(k), iv.to_vec());
encryptor.decrypt(&mut RefReadBuffer::new(encrypted), &mut RefWriteBuffer::new(dest), true).expect("Invalid length or padding");
}
/// Decrypt a message using cbc mode
pub fn decrypt_cbc(k: &[u8], iv: &[u8], encrypted: &[u8], dest: &mut [u8]) -> Result<(), SymmetricCipherError> {
let mut encryptor = CbcDecryptor::new(AesSafe128Decryptor::new(k), PkcsPadding, iv.to_vec());
try!(encryptor.decrypt(&mut RefReadBuffer::new(encrypted), &mut RefWriteBuffer::new(dest), true));
Ok(())
}
}

125
ethstore/src/dir/disk.rs Normal file
View File

@@ -0,0 +1,125 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::{fs, ffi, io};
use std::path::{PathBuf, Path};
use std::collections::HashMap;
use ethkey::Address;
use {libc, json, SafeAccount, Error};
use super::KeyDirectory;
#[cfg(not(windows))]
fn restrict_permissions_to_owner(file_path: &Path) -> Result<(), i32> {
let cstr = ffi::CString::new(file_path.to_str().unwrap()).unwrap();
match unsafe { libc::chmod(cstr.as_ptr(), libc::S_IWUSR | libc::S_IRUSR) } {
0 => Ok(()),
x => Err(x),
}
}
#[cfg(windows)]
fn restrict_permissions_to_owner(file_path: &Path) -> Result<(), i32> {
Ok(())
}
pub struct DiskDirectory {
path: PathBuf,
}
impl DiskDirectory {
pub fn create<P>(path: P) -> Result<Self, Error> where P: AsRef<Path> {
try!(fs::create_dir_all(&path));
Ok(Self::at(path))
}
pub fn at<P>(path: P) -> Self where P: AsRef<Path> {
DiskDirectory {
path: path.as_ref().to_path_buf(),
}
}
/// all accounts found in keys directory
fn files(&self) -> Result<HashMap<PathBuf, SafeAccount>, Error> {
// it's not done using one iterator cause
// there is an issue with rustc and it takes tooo much time to compile
let paths = try!(fs::read_dir(&self.path))
.flat_map(Result::ok)
.filter(|entry| {
let metadata = entry.metadata();
metadata.is_ok() && !metadata.unwrap().is_dir()
})
.map(|entry| entry.path())
.collect::<Vec<PathBuf>>();
let files: Result<Vec<_>, _> = paths.iter()
.map(fs::File::open)
.collect();
let files = try!(files);
let accounts = files.into_iter()
.map(json::KeyFile::load)
.zip(paths.into_iter())
.filter_map(|(file, path)| file.ok().map(|file| (path, SafeAccount::from(file))))
.collect();
Ok(accounts)
}
}
impl KeyDirectory for DiskDirectory {
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
let accounts = try!(self.files())
.into_iter()
.map(|(_, account)| account)
.collect();
Ok(accounts)
}
fn insert(&self, account: SafeAccount) -> Result<(), Error> {
// transform account into key file
let keyfile: json::KeyFile = account.into();
// build file path
let mut keyfile_path = self.path.clone();
keyfile_path.push(format!("{}", keyfile.id));
// save the file
let mut file = try!(fs::File::create(&keyfile_path));
try!(keyfile.write(&mut file).map_err(|e| Error::Custom(format!("{:?}", e))));
if let Err(_) = restrict_permissions_to_owner(&keyfile_path) {
fs::remove_file(&keyfile_path).expect("Expected to remove recently created file");
return Err(Error::Io(io::Error::last_os_error()));
}
Ok(())
}
fn remove(&self, address: &Address) -> Result<(), Error> {
// enumerate all entries in keystore
// and find entry with given address
let to_remove = try!(self.files())
.into_iter()
.find(|&(_, ref account)| &account.address == address);
// remove it
match to_remove {
None => Err(Error::InvalidAccount),
Some((path, _)) => fs::remove_file(path).map_err(From::from)
}
}
}

95
ethstore/src/dir/geth.rs Normal file
View File

@@ -0,0 +1,95 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::env;
use std::path::PathBuf;
use ethkey::Address;
use {SafeAccount, Error};
use super::{KeyDirectory, DiskDirectory, DirectoryType};
#[cfg(target_os = "macos")]
fn geth_dir_path() -> PathBuf {
let mut home = env::home_dir().expect("Failed to get home dir");
home.push("Library");
home.push("Ethereum");
home
}
#[cfg(windows)]
/// Default path for ethereum installation on Windows
pub fn geth_dir_path() -> PathBuf {
let mut home = env::home_dir().expect("Failed to get home dir");
home.push("AppData");
home.push("Roaming");
home.push("Ethereum");
home
}
#[cfg(not(any(target_os = "macos", windows)))]
/// Default path for ethereum installation on posix system which is not Mac OS
pub fn geth_dir_path() -> PathBuf {
let mut home = env::home_dir().expect("Failed to get home dir");
home.push(".ethereum");
home
}
fn geth_keystore(t: DirectoryType) -> PathBuf {
let mut dir = geth_dir_path();
match t {
DirectoryType::Testnet => {
dir.push("testnet");
dir.push("keystore");
},
DirectoryType::Main => {
dir.push("keystore");
}
}
dir
}
pub struct GethDirectory {
dir: DiskDirectory,
}
impl GethDirectory {
pub fn create(t: DirectoryType) -> Result<Self, Error> {
let result = GethDirectory {
dir: try!(DiskDirectory::create(geth_keystore(t))),
};
Ok(result)
}
pub fn open(t: DirectoryType) -> Self {
GethDirectory {
dir: DiskDirectory::at(geth_keystore(t)),
}
}
}
impl KeyDirectory for GethDirectory {
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
self.dir.load()
}
fn insert(&self, account: SafeAccount) -> Result<(), Error> {
self.dir.insert(account)
}
fn remove(&self, address: &Address) -> Result<(), Error> {
self.dir.remove(address)
}
}

37
ethstore/src/dir/mod.rs Normal file
View File

@@ -0,0 +1,37 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use ethkey::Address;
use {SafeAccount, Error};
mod disk;
mod geth;
mod parity;
pub enum DirectoryType {
Testnet,
Main,
}
pub trait KeyDirectory: Send + Sync {
fn load(&self) -> Result<Vec<SafeAccount>, Error>;
fn insert(&self, account: SafeAccount) -> Result<(), Error>;
fn remove(&self, address: &Address) -> Result<(), Error>;
}
pub use self::disk::DiskDirectory;
pub use self::geth::GethDirectory;
pub use self::parity::ParityDirectory;

View File

@@ -0,0 +1,74 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::env;
use std::path::PathBuf;
use ethkey::Address;
use {SafeAccount, Error};
use super::{KeyDirectory, DiskDirectory, DirectoryType};
fn parity_dir_path() -> PathBuf {
let mut home = env::home_dir().expect("Failed to get home dir");
home.push(".parity");
home
}
fn parity_keystore(t: DirectoryType) -> PathBuf {
let mut dir = parity_dir_path();
match t {
DirectoryType::Testnet => {
dir.push("testnet_keys");
},
DirectoryType::Main => {
dir.push("keys");
}
}
dir
}
pub struct ParityDirectory {
dir: DiskDirectory,
}
impl ParityDirectory {
pub fn create(t: DirectoryType) -> Result<Self, Error> {
let result = ParityDirectory {
dir: try!(DiskDirectory::create(parity_keystore(t))),
};
Ok(result)
}
pub fn open(t: DirectoryType) -> Self {
ParityDirectory {
dir: DiskDirectory::at(parity_keystore(t)),
}
}
}
impl KeyDirectory for ParityDirectory {
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
self.dir.load()
}
fn insert(&self, account: SafeAccount) -> Result<(), Error> {
self.dir.insert(account)
}
fn remove(&self, address: &Address) -> Result<(), Error> {
self.dir.remove(address)
}
}

58
ethstore/src/error.rs Normal file
View File

@@ -0,0 +1,58 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::fmt;
use std::io::Error as IoError;
use ethkey::Error as EthKeyError;
#[derive(Debug)]
pub enum Error {
Io(IoError),
InvalidPassword,
InvalidSecret,
InvalidAccount,
CreationFailed,
EthKey(EthKeyError),
Custom(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let s = match *self {
Error::Io(ref err) => format!("{}", err),
Error::InvalidPassword => "Invalid password".into(),
Error::InvalidSecret => "Invalid secret".into(),
Error::InvalidAccount => "Invalid account".into(),
Error::CreationFailed => "Account creation failed".into(),
Error::EthKey(ref err) => format!("{}", err),
Error::Custom(ref s) => s.clone(),
};
write!(f, "{}", s)
}
}
impl From<IoError> for Error {
fn from(err: IoError) -> Self {
Error::Io(err)
}
}
impl From<EthKeyError> for Error {
fn from(err: EthKeyError) -> Self {
Error::EthKey(err)
}
}

41
ethstore/src/ethkey.rs Normal file
View File

@@ -0,0 +1,41 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! ethkey reexport to make documentation look pretty.
pub use _ethkey::*;
use json;
impl Into<json::H160> for Address {
fn into(self) -> json::H160 {
let a: [u8; 20] = self.into();
From::from(a)
}
}
impl From<json::H160> for Address {
fn from(json: json::H160) -> Self {
let a: [u8; 20] = json.into();
From::from(a)
}
}
impl<'a> From<&'a json::H160> for Address {
fn from(json: &'a json::H160) -> Self {
let mut a = [0u8; 20];
a.copy_from_slice(json);
From::from(a)
}
}

108
ethstore/src/ethstore.rs Normal file
View File

@@ -0,0 +1,108 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::collections::BTreeMap;
use std::sync::RwLock;
use ethkey::KeyPair;
use crypto::KEY_ITERATIONS;
use random::Random;
use ethkey::{Signature, Address, Message, Secret};
use dir::KeyDirectory;
use account::SafeAccount;
use {Error, SecretStore};
pub struct EthStore {
dir: Box<KeyDirectory>,
iterations: u32,
cache: RwLock<BTreeMap<Address, SafeAccount>>,
}
impl EthStore {
pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> {
Self::open_with_iterations(directory, KEY_ITERATIONS as u32)
}
pub fn open_with_iterations(directory: Box<KeyDirectory>, iterations: u32) -> Result<Self, Error> {
let accounts = try!(directory.load());
let cache = accounts.into_iter().map(|account| (account.address.clone(), account)).collect();
let store = EthStore {
dir: directory,
iterations: iterations,
cache: RwLock::new(cache),
};
Ok(store)
}
fn save(&self, account: SafeAccount) -> Result<(), Error> {
// save to file
try!(self.dir.insert(account.clone()));
// update cache
let mut cache = self.cache.write().unwrap();
cache.insert(account.address.clone(), account);
Ok(())
}
}
impl SecretStore for EthStore {
fn insert_account(&self, secret: Secret, password: &str) -> Result<Address, Error> {
let keypair = try!(KeyPair::from_secret(secret).map_err(|_| Error::CreationFailed));
let id: [u8; 16] = Random::random();
let account = SafeAccount::create(&keypair, id, password, self.iterations);
let address = account.address.clone();
try!(self.save(account));
Ok(address)
}
fn accounts(&self) -> Vec<Address> {
self.cache.read().unwrap().keys().cloned().collect()
}
fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> {
// change password
let account = {
let cache = self.cache.read().unwrap();
let account = try!(cache.get(address).ok_or(Error::InvalidAccount));
try!(account.change_password(old_password, new_password, self.iterations))
};
// save to file
self.save(account)
}
fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> {
let can_remove = {
let cache = self.cache.read().unwrap();
let account = try!(cache.get(address).ok_or(Error::InvalidAccount));
account.check_password(password)
};
if can_remove {
try!(self.dir.remove(address));
let mut cache = self.cache.write().unwrap();
cache.remove(address);
Ok(())
} else {
Err(Error::InvalidPassword)
}
}
fn sign(&self, account: &Address, password: &str, message: &Message) -> Result<Signature, Error> {
let cache = self.cache.read().unwrap();
let account = try!(cache.get(account).ok_or(Error::InvalidAccount));
account.sign(password, message)
}
}

28
ethstore/src/import.rs Normal file
View File

@@ -0,0 +1,28 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use ethkey::Address;
use dir::KeyDirectory;
use Error;
pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result<Vec<Address>, Error> {
let accounts = try!(src.load());
accounts.into_iter().map(|a| {
let address = a.address.clone();
try!(dst.insert(a));
Ok(address)
}).collect()
}

View File

@@ -0,0 +1,91 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use serde::{Serialize, Serializer, Deserialize, Deserializer, Error as SerdeError};
use serde::de::Visitor;
use super::{Error, H128};
#[derive(Debug, PartialEq)]
pub enum CipherSer {
Aes128Ctr,
}
impl Serialize for CipherSer {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: Serializer {
match *self {
CipherSer::Aes128Ctr => serializer.serialize_str("aes-128-ctr"),
}
}
}
impl Deserialize for CipherSer {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: Deserializer {
deserializer.deserialize(CipherSerVisitor)
}
}
struct CipherSerVisitor;
impl Visitor for CipherSerVisitor {
type Value = CipherSer;
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
match value {
"aes-128-ctr" => Ok(CipherSer::Aes128Ctr),
_ => Err(SerdeError::custom(Error::UnsupportedCipher))
}
}
fn visit_string<E>(&mut self, value: String) -> Result<Self::Value, E> where E: SerdeError {
self.visit_str(value.as_ref())
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Aes128Ctr {
pub iv: H128,
}
#[derive(Debug, PartialEq)]
pub enum CipherSerParams {
Aes128Ctr(Aes128Ctr),
}
impl Serialize for CipherSerParams {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: Serializer {
match *self {
CipherSerParams::Aes128Ctr(ref params) => params.serialize(serializer),
}
}
}
impl Deserialize for CipherSerParams {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: Deserializer {
Aes128Ctr::deserialize(deserializer)
.map(CipherSerParams::Aes128Ctr)
.map_err(|_| Error::InvalidCipherParams)
.map_err(SerdeError::custom)
}
}
#[derive(Debug, PartialEq)]
pub enum Cipher {
Aes128Ctr(Aes128Ctr),
}

200
ethstore/src/json/crypto.rs Normal file
View File

@@ -0,0 +1,200 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use serde::{Deserialize, Deserializer, Serialize, Serializer, Error};
use serde::de::{Visitor, MapVisitor};
use serde::ser;
use super::{Cipher, CipherSer, CipherSerParams, Kdf, KdfSer, KdfSerParams, H256};
#[derive(Debug, PartialEq)]
pub struct Crypto {
pub cipher: Cipher,
pub ciphertext: H256,
pub kdf: Kdf,
pub mac: H256,
}
enum CryptoField {
Cipher,
CipherParams,
CipherText,
Kdf,
KdfParams,
Mac,
}
impl Deserialize for CryptoField {
fn deserialize<D>(deserializer: &mut D) -> Result<CryptoField, D::Error>
where D: Deserializer
{
deserializer.deserialize(CryptoFieldVisitor)
}
}
struct CryptoFieldVisitor;
impl Visitor for CryptoFieldVisitor {
type Value = CryptoField;
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E>
where E: Error
{
match value {
"cipher" => Ok(CryptoField::Cipher),
"cipherparams" => Ok(CryptoField::CipherParams),
"ciphertext" => Ok(CryptoField::CipherText),
"kdf" => Ok(CryptoField::Kdf),
"kdfparams" => Ok(CryptoField::KdfParams),
"mac" => Ok(CryptoField::Mac),
_ => Err(Error::custom(format!("Unknown field: '{}'", value))),
}
}
}
impl Deserialize for Crypto {
fn deserialize<D>(deserializer: &mut D) -> Result<Crypto, D::Error>
where D: Deserializer
{
static FIELDS: &'static [&'static str] = &["id", "version", "crypto", "Crypto", "address"];
deserializer.deserialize_struct("Crypto", FIELDS, CryptoVisitor)
}
}
struct CryptoVisitor;
impl Visitor for CryptoVisitor {
type Value = Crypto;
fn visit_map<V>(&mut self, mut visitor: V) -> Result<Self::Value, V::Error>
where V: MapVisitor
{
let mut cipher = None;
let mut cipherparams = None;
let mut ciphertext = None;
let mut kdf = None;
let mut kdfparams = None;
let mut mac = None;
loop {
match try!(visitor.visit_key()) {
Some(CryptoField::Cipher) => { cipher = Some(try!(visitor.visit_value())); }
Some(CryptoField::CipherParams) => { cipherparams = Some(try!(visitor.visit_value())); }
Some(CryptoField::CipherText) => { ciphertext = Some(try!(visitor.visit_value())); }
Some(CryptoField::Kdf) => { kdf = Some(try!(visitor.visit_value())); }
Some(CryptoField::KdfParams) => { kdfparams = Some(try!(visitor.visit_value())); }
Some(CryptoField::Mac) => { mac = Some(try!(visitor.visit_value())); }
None => { break; }
}
}
let cipher = match (cipher, cipherparams) {
(Some(CipherSer::Aes128Ctr), Some(CipherSerParams::Aes128Ctr(params))) => Cipher::Aes128Ctr(params),
(None, _) => return Err(Error::missing_field("cipher")),
(Some(_), None) => return Err(Error::missing_field("cipherparams")),
};
let ciphertext = match ciphertext {
Some(ciphertext) => ciphertext,
None => try!(visitor.missing_field("ciphertext")),
};
let kdf = match (kdf, kdfparams) {
(Some(KdfSer::Pbkdf2), Some(KdfSerParams::Pbkdf2(params))) => Kdf::Pbkdf2(params),
(Some(KdfSer::Scrypt), Some(KdfSerParams::Scrypt(params))) => Kdf::Scrypt(params),
(Some(_), Some(_)) => return Err(Error::custom("Invalid cipherparams")),
(None, _) => return Err(Error::missing_field("kdf")),
(Some(_), None) => return Err(Error::missing_field("kdfparams")),
};
let mac = match mac {
Some(mac) => mac,
None => try!(visitor.missing_field("mac")),
};
try!(visitor.end());
let result = Crypto {
cipher: cipher,
ciphertext: ciphertext,
kdf: kdf,
mac: mac,
};
Ok(result)
}
}
impl Serialize for Crypto {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: Serializer
{
serializer.serialize_struct("Crypto", CryptoMapVisitor {
value: self,
state: 0,
})
}
}
struct CryptoMapVisitor<'a> {
value: &'a Crypto,
state: u8,
}
impl<'a> ser::MapVisitor for CryptoMapVisitor<'a> {
fn visit<S>(&mut self, serializer: &mut S) -> Result<Option<()>, S::Error>
where S: Serializer
{
match self.state {
0 => {
self.state += 1;
match self.value.cipher {
Cipher::Aes128Ctr(_) => Ok(Some(try!(serializer.serialize_struct_elt("cipher", &CipherSer::Aes128Ctr)))),
}
},
1 => {
self.state += 1;
match self.value.cipher {
Cipher::Aes128Ctr(ref params) => Ok(Some(try!(serializer.serialize_struct_elt("cipherparams", params)))),
}
},
2 => {
self.state += 1;
Ok(Some(try!(serializer.serialize_struct_elt("ciphertext", &self.value.ciphertext))))
},
3 => {
self.state += 1;
match self.value.kdf {
Kdf::Pbkdf2(_) => Ok(Some(try!(serializer.serialize_struct_elt("kdf", &KdfSer::Pbkdf2)))),
Kdf::Scrypt(_) => Ok(Some(try!(serializer.serialize_struct_elt("kdf", &KdfSer::Scrypt)))),
}
},
4 => {
self.state += 1;
match self.value.kdf {
Kdf::Pbkdf2(ref params) => Ok(Some(try!(serializer.serialize_struct_elt("kdfparams", params)))),
Kdf::Scrypt(ref params) => Ok(Some(try!(serializer.serialize_struct_elt("kdfparams", params)))),
}
},
5 => {
self.state += 1;
Ok(Some(try!(serializer.serialize_struct_elt("mac", &self.value.mac))))
},
_ => {
Ok(None)
}
}
}
}

View File

@@ -0,0 +1,50 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::fmt;
#[derive(Debug, PartialEq)]
pub enum Error {
UnsupportedCipher,
InvalidCipherParams,
UnsupportedKdf,
InvalidUUID,
UnsupportedVersion,
InvalidCiphertext,
InvalidH256,
InvalidPrf,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
Error::InvalidUUID => write!(f, "Invalid UUID"),
Error::UnsupportedVersion => write!(f, "Unsupported version"),
Error::UnsupportedKdf => write!(f, "Unsupported kdf"),
Error::InvalidCiphertext => write!(f, "Invalid ciphertext"),
Error::UnsupportedCipher => write!(f, "Unsupported cipher"),
Error::InvalidCipherParams => write!(f, "Invalid cipher params"),
Error::InvalidH256 => write!(f, "Invalid hash"),
Error::InvalidPrf => write!(f, "Invalid prf"),
}
}
}
impl Into<String> for Error {
fn into(self) -> String {
format!("{}", self)
}
}

112
ethstore/src/json/hash.rs Normal file
View File

@@ -0,0 +1,112 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::fmt;
use std::ops;
use std::str::FromStr;
use rustc_serialize::hex::{FromHex, ToHex};
use serde::{Serialize, Serializer, Deserialize, Deserializer, Error as SerdeError};
use serde::de::Visitor;
use super::Error;
macro_rules! impl_hash {
($name: ident, $size: expr) => {
pub struct $name([u8; $size]);
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let self_ref: &[u8] = &self.0;
write!(f, "{:?}", self_ref)
}
}
impl PartialEq for $name {
fn eq(&self, other: &Self) -> bool {
let self_ref: &[u8] = &self.0;
let other_ref: &[u8] = &other.0;
self_ref == other_ref
}
}
impl ops::Deref for $name {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Serialize for $name {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: Serializer {
serializer.serialize_str(&self.0.to_hex())
}
}
impl Deserialize for $name {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: Deserializer {
struct HashVisitor;
impl Visitor for HashVisitor {
type Value = $name;
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
FromStr::from_str(value).map_err(SerdeError::custom)
}
fn visit_string<E>(&mut self, value: String) -> Result<Self::Value, E> where E: SerdeError {
self.visit_str(value.as_ref())
}
}
deserializer.deserialize(HashVisitor)
}
}
impl FromStr for $name {
type Err = Error;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value.from_hex() {
Ok(ref hex) if hex.len() == $size => {
let mut hash = [0u8; $size];
hash.clone_from_slice(hex);
Ok($name(hash))
}
_ => Err(Error::InvalidH256),
}
}
}
impl From<[u8; $size]> for $name {
fn from(bytes: [u8; $size]) -> Self {
$name(bytes)
}
}
impl Into<[u8; $size]> for $name {
fn into(self) -> [u8; $size] {
self.0
}
}
}
}
impl_hash!(H128, 16);
impl_hash!(H160, 20);
impl_hash!(H256, 32);
impl_hash!(H768, 96);

145
ethstore/src/json/id.rs Normal file
View File

@@ -0,0 +1,145 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Universaly unique identifier.
use std::str::FromStr;
use std::fmt;
use rustc_serialize::hex::{ToHex, FromHex};
use serde::{Deserialize, Serialize, Deserializer, Serializer, Error as SerdeError};
use serde::de::Visitor;
use super::Error;
/// Universaly unique identifier.
#[derive(Debug, PartialEq)]
pub struct UUID([u8; 16]);
impl From<[u8; 16]> for UUID {
fn from(uuid: [u8; 16]) -> Self {
UUID(uuid)
}
}
impl<'a> Into<String> for &'a UUID {
fn into(self) -> String {
let d1 = &self.0[0..4];
let d2 = &self.0[4..6];
let d3 = &self.0[6..8];
let d4 = &self.0[8..10];
let d5 = &self.0[10..16];
[d1, d2, d3, d4, d5].into_iter().map(|d| d.to_hex()).collect::<Vec<String>>().join("-")
}
}
impl Into<String> for UUID {
fn into(self) -> String {
Into::into(&self)
}
}
impl Into<[u8; 16]> for UUID {
fn into(self) -> [u8; 16] {
self.0
}
}
impl fmt::Display for UUID {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let s: String = (self as &UUID).into();
write!(f, "{}", s)
}
}
fn copy_into(from: &str, into: &mut [u8]) -> Result<(), Error> {
let from = try!(from.from_hex().map_err(|_| Error::InvalidUUID));
if from.len() != into.len() {
return Err(Error::InvalidUUID);
}
into.copy_from_slice(&from);
Ok(())
}
impl FromStr for UUID {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split("-").collect();
if parts.len() != 5 {
return Err(Error::InvalidUUID);
}
let mut uuid = [0u8; 16];
try!(copy_into(parts[0], &mut uuid[0..4]));
try!(copy_into(parts[1], &mut uuid[4..6]));
try!(copy_into(parts[2], &mut uuid[6..8]));
try!(copy_into(parts[3], &mut uuid[8..10]));
try!(copy_into(parts[4], &mut uuid[10..16]));
Ok(UUID(uuid))
}
}
impl Serialize for UUID {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: Serializer {
let s: String = self.into();
serializer.serialize_str(&s)
}
}
impl Deserialize for UUID {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: Deserializer {
deserializer.deserialize(UUIDVisitor)
}
}
struct UUIDVisitor;
impl Visitor for UUIDVisitor {
type Value = UUID;
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
UUID::from_str(value).map_err(SerdeError::custom)
}
fn visit_string<E>(&mut self, value: String) -> Result<Self::Value, E> where E: SerdeError {
self.visit_str(value.as_ref())
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::UUID;
#[test]
fn uuid_from_str() {
let uuid = UUID::from_str("3198bc9c-6672-5ab3-d995-4942343ae5b6").unwrap();
assert_eq!(uuid, UUID::from([0x31, 0x98, 0xbc, 0x9c, 0x66, 0x72, 0x5a, 0xb3, 0xd9, 0x95, 0x49, 0x42, 0x34, 0x3a, 0xe5, 0xb6]));
}
#[test]
fn uuid_from_and_to_str() {
let from = "3198bc9c-6672-5ab3-d995-4942343ae5b6";
let uuid = UUID::from_str(from).unwrap();
let to: String = uuid.into();
assert_eq!(from, &to);
}
}

149
ethstore/src/json/kdf.rs Normal file
View File

@@ -0,0 +1,149 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use serde::{Serialize, Serializer, Deserialize, Deserializer, Error as SerdeError};
use serde::de::Visitor;
use serde_json::{Value, value};
use super::{Error, H256};
#[derive(Debug, PartialEq)]
pub enum KdfSer {
Pbkdf2,
Scrypt,
}
impl Serialize for KdfSer {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: Serializer {
match *self {
KdfSer::Pbkdf2 => serializer.serialize_str("pbkdf2"),
KdfSer::Scrypt => serializer.serialize_str("scrypt"),
}
}
}
impl Deserialize for KdfSer {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: Deserializer {
deserializer.deserialize(KdfSerVisitor)
}
}
struct KdfSerVisitor;
impl Visitor for KdfSerVisitor {
type Value = KdfSer;
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
match value {
"pbkdf2" => Ok(KdfSer::Pbkdf2),
"scrypt" => Ok(KdfSer::Scrypt),
_ => Err(SerdeError::custom(Error::UnsupportedKdf))
}
}
fn visit_string<E>(&mut self, value: String) -> Result<Self::Value, E> where E: SerdeError {
self.visit_str(value.as_ref())
}
}
#[derive(Debug, PartialEq)]
pub enum Prf {
HmacSha256,
}
impl Serialize for Prf {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: Serializer {
match *self {
Prf::HmacSha256 => serializer.serialize_str("hmac-sha256"),
}
}
}
impl Deserialize for Prf {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: Deserializer {
deserializer.deserialize(PrfVisitor)
}
}
struct PrfVisitor;
impl Visitor for PrfVisitor {
type Value = Prf;
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: SerdeError {
match value {
"hmac-sha256" => Ok(Prf::HmacSha256),
_ => Err(SerdeError::custom(Error::InvalidPrf)),
}
}
fn visit_string<E>(&mut self, value: String) -> Result<Self::Value, E> where E: SerdeError {
self.visit_str(value.as_ref())
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Pbkdf2 {
pub c: u32,
pub dklen: u32,
pub prf: Prf,
pub salt: H256,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Scrypt {
pub dklen: u32,
pub p: u32,
pub n: u32,
pub r: u32,
pub salt: H256,
}
#[derive(Debug, PartialEq)]
pub enum KdfSerParams {
Pbkdf2(Pbkdf2),
Scrypt(Scrypt),
}
impl Serialize for KdfSerParams {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: Serializer {
match *self {
KdfSerParams::Pbkdf2(ref params) => params.serialize(serializer),
KdfSerParams::Scrypt(ref params) => params.serialize(serializer),
}
}
}
impl Deserialize for KdfSerParams {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: Deserializer {
let v = try!(Value::deserialize(deserializer));
Deserialize::deserialize(&mut value::Deserializer::new(v.clone())).map(KdfSerParams::Pbkdf2)
.or_else(|_| Deserialize::deserialize(&mut value::Deserializer::new(v)).map(KdfSerParams::Scrypt))
.map_err(|e| D::Error::custom(format!("{}", e)))
}
}
#[derive(Debug, PartialEq)]
pub enum Kdf {
Pbkdf2(Pbkdf2),
Scrypt(Scrypt),
}

View File

@@ -0,0 +1,271 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::io::{Read, Write};
use serde::{Deserialize, Deserializer, Error};
use serde::de::{Visitor, MapVisitor};
use serde_json;
use super::{UUID, Version, Crypto, H160};
#[derive(Debug, PartialEq, Serialize)]
pub struct KeyFile {
pub id: UUID,
pub version: Version,
pub crypto: Crypto,
pub address: H160,
}
enum KeyFileField {
ID,
Version,
Crypto,
Address,
}
impl Deserialize for KeyFileField {
fn deserialize<D>(deserializer: &mut D) -> Result<KeyFileField, D::Error>
where D: Deserializer
{
deserializer.deserialize(KeyFileFieldVisitor)
}
}
struct KeyFileFieldVisitor;
impl Visitor for KeyFileFieldVisitor {
type Value = KeyFileField;
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E>
where E: Error
{
match value {
"id" => Ok(KeyFileField::ID),
"version" => Ok(KeyFileField::Version),
"crypto" => Ok(KeyFileField::Crypto),
"Crypto" => Ok(KeyFileField::Crypto),
"address" => Ok(KeyFileField::Address),
_ => Err(Error::custom(format!("Unknown field: '{}'", value))),
}
}
}
impl Deserialize for KeyFile {
fn deserialize<D>(deserializer: &mut D) -> Result<KeyFile, D::Error>
where D: Deserializer
{
static FIELDS: &'static [&'static str] = &["id", "version", "crypto", "Crypto", "address"];
deserializer.deserialize_struct("KeyFile", FIELDS, KeyFileVisitor)
}
}
struct KeyFileVisitor;
impl Visitor for KeyFileVisitor {
type Value = KeyFile;
fn visit_map<V>(&mut self, mut visitor: V) -> Result<Self::Value, V::Error>
where V: MapVisitor
{
let mut id = None;
let mut version = None;
let mut crypto = None;
let mut address = None;
loop {
match try!(visitor.visit_key()) {
Some(KeyFileField::ID) => { id = Some(try!(visitor.visit_value())); }
Some(KeyFileField::Version) => { version = Some(try!(visitor.visit_value())); }
Some(KeyFileField::Crypto) => { crypto = Some(try!(visitor.visit_value())); }
Some(KeyFileField::Address) => { address = Some(try!(visitor.visit_value())); }
None => { break; }
}
}
let id = match id {
Some(id) => id,
None => try!(visitor.missing_field("id")),
};
let version = match version {
Some(version) => version,
None => try!(visitor.missing_field("version")),
};
let crypto = match crypto {
Some(crypto) => crypto,
None => try!(visitor.missing_field("crypto")),
};
let address = match address {
Some(address) => address,
None => try!(visitor.missing_field("address")),
};
try!(visitor.end());
let result = KeyFile {
id: id,
version: version,
crypto: crypto,
address: address,
};
Ok(result)
}
}
impl KeyFile {
pub fn load<R>(reader: R) -> Result<Self, serde_json::Error> where R: Read {
serde_json::from_reader(reader)
}
pub fn write<W>(&self, writer: &mut W) -> Result<(), serde_json::Error> where W: Write {
serde_json::to_writer(writer, self)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use serde_json;
use json::{KeyFile, UUID, Version, Crypto, Cipher, Aes128Ctr, Kdf, Scrypt, H128, H160, H256};
#[test]
fn basic_keyfile() {
let json = r#"
{
"address": "6edddfc6349aff20bc6467ccf276c5b52487f7a8",
"crypto": {
"cipher": "aes-128-ctr",
"ciphertext": "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc",
"cipherparams": {
"iv": "b5a7ec855ec9e2c405371356855fec83"
},
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 262144,
"p": 1,
"r": 8,
"salt": "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209"
},
"mac": "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f"
},
"id": "8777d9f6-7860-4b9b-88b7-0b57ee6b3a73",
"version": 3
}"#;
let expected = KeyFile {
id: UUID::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(),
version: Version::V3,
address: H160::from_str("6edddfc6349aff20bc6467ccf276c5b52487f7a8").unwrap(),
crypto: Crypto {
cipher: Cipher::Aes128Ctr(Aes128Ctr {
iv: H128::from_str("b5a7ec855ec9e2c405371356855fec83").unwrap(),
}),
ciphertext: H256::from_str("7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc").unwrap(),
kdf: Kdf::Scrypt(Scrypt {
n: 262144,
dklen: 32,
p: 1,
r: 8,
salt: H256::from_str("1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209").unwrap(),
}),
mac: H256::from_str("46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f").unwrap(),
},
};
let keyfile: KeyFile = serde_json::from_str(json).unwrap();
assert_eq!(keyfile, expected);
}
#[test]
fn capital_crypto_keyfile() {
let json = r#"
{
"address": "6edddfc6349aff20bc6467ccf276c5b52487f7a8",
"Crypto": {
"cipher": "aes-128-ctr",
"ciphertext": "7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc",
"cipherparams": {
"iv": "b5a7ec855ec9e2c405371356855fec83"
},
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 262144,
"p": 1,
"r": 8,
"salt": "1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209"
},
"mac": "46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f"
},
"id": "8777d9f6-7860-4b9b-88b7-0b57ee6b3a73",
"version": 3
}"#;
let expected = KeyFile {
id: UUID::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(),
version: Version::V3,
address: H160::from_str("6edddfc6349aff20bc6467ccf276c5b52487f7a8").unwrap(),
crypto: Crypto {
cipher: Cipher::Aes128Ctr(Aes128Ctr {
iv: H128::from_str("b5a7ec855ec9e2c405371356855fec83").unwrap(),
}),
ciphertext: H256::from_str("7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc").unwrap(),
kdf: Kdf::Scrypt(Scrypt {
n: 262144,
dklen: 32,
p: 1,
r: 8,
salt: H256::from_str("1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209").unwrap(),
}),
mac: H256::from_str("46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f").unwrap(),
},
};
let keyfile: KeyFile = serde_json::from_str(json).unwrap();
assert_eq!(keyfile, expected);
}
#[test]
fn to_and_from_json() {
let file = KeyFile {
id: UUID::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(),
version: Version::V3,
address: H160::from_str("6edddfc6349aff20bc6467ccf276c5b52487f7a8").unwrap(),
crypto: Crypto {
cipher: Cipher::Aes128Ctr(Aes128Ctr {
iv: H128::from_str("b5a7ec855ec9e2c405371356855fec83").unwrap(),
}),
ciphertext: H256::from_str("7203da0676d141b138cd7f8e1a4365f59cc1aa6978dc5443f364ca943d7cb4bc").unwrap(),
kdf: Kdf::Scrypt(Scrypt {
n: 262144,
dklen: 32,
p: 1,
r: 8,
salt: H256::from_str("1e8642fdf1f87172492c1412fc62f8db75d796cdfa9c53c3f2b11e44a2a1b209").unwrap(),
}),
mac: H256::from_str("46325c5d4e8c991ad2683d525c7854da387138b6ca45068985aa4959fa2b8c8f").unwrap(),
},
};
let serialized = serde_json::to_string(&file).unwrap();
let deserialized = serde_json::from_str(&serialized).unwrap();
assert_eq!(file, deserialized);
}
}

24
ethstore/src/json/mod.rs Normal file
View File

@@ -0,0 +1,24 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Contract interface specification.
#[cfg(feature = "serde_macros")]
include!("mod.rs.in");
#[cfg(not(feature = "serde_macros"))]
include!(concat!(env!("OUT_DIR"), "/mod.rs"));

View File

@@ -0,0 +1,20 @@
mod cipher;
mod crypto;
mod error;
mod hash;
mod id;
mod kdf;
mod key_file;
mod presale;
mod version;
pub use self::cipher::{Cipher, CipherSer, CipherSerParams, Aes128Ctr};
pub use self::crypto::Crypto;
pub use self::error::Error;
pub use self::hash::{H128, H160, H256, H768};
pub use self::id::UUID;
pub use self::kdf::{Kdf, KdfSer, Prf, Pbkdf2, Scrypt, KdfSerParams};
pub use self::key_file::KeyFile;
pub use self::presale::PresaleWallet;
pub use self::version::Version;

View File

@@ -0,0 +1,42 @@
use std::io::Read;
use serde_json;
use super::{H160, H768};
#[derive(Debug, PartialEq, Deserialize)]
pub struct PresaleWallet {
pub encseed: H768,
#[serde(rename = "ethaddr")]
pub address: H160,
}
impl PresaleWallet {
pub fn load<R>(reader: R) -> Result<Self, serde_json::Error> where R: Read {
serde_json::from_reader(reader)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use serde_json;
use json::{PresaleWallet, H160, H768};
#[test]
fn presale_wallet() {
let json = r#"
{
"encseed": "137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066",
"ethaddr": "ede84640d1a1d3e06902048e67aa7db8d52c2ce1",
"email": "123@gmail.com",
"btcaddr": "1JvqEc6WLhg6GnyrLBe2ztPAU28KRfuseH"
} "#;
let expected = PresaleWallet {
encseed: H768::from_str("137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066").unwrap(),
address: H160::from_str("ede84640d1a1d3e06902048e67aa7db8d52c2ce1").unwrap(),
};
let wallet: PresaleWallet = serde_json::from_str(json).unwrap();
assert_eq!(expected, wallet);
}
}

View File

@@ -0,0 +1,54 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use serde::{Serialize, Serializer, Deserialize, Deserializer, Error as SerdeError};
use serde::de::Visitor;
use super::Error;
#[derive(Debug, PartialEq)]
pub enum Version {
V3,
}
impl Serialize for Version {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: Serializer {
match *self {
Version::V3 => serializer.serialize_u64(3)
}
}
}
impl Deserialize for Version {
fn deserialize<D>(deserializer: &mut D) -> Result<Version, D::Error>
where D: Deserializer {
deserializer.deserialize(VersionVisitor)
}
}
struct VersionVisitor;
impl Visitor for VersionVisitor {
type Value = Version;
fn visit_u64<E>(&mut self, value: u64) -> Result<Self::Value, E> where E: SerdeError {
match value {
3 => Ok(Version::V3),
_ => Err(SerdeError::custom(Error::UnsupportedVersion))
}
}
}

50
ethstore/src/lib.rs Normal file
View File

@@ -0,0 +1,50 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
#![cfg_attr(feature="nightly", feature(custom_derive, plugin))]
#![cfg_attr(feature="nightly", plugin(serde_macros))]
extern crate libc;
extern crate rand;
extern crate serde;
extern crate serde_json;
extern crate rustc_serialize;
extern crate crypto as rcrypto;
extern crate tiny_keccak;
// reexport it nicely
extern crate ethkey as _ethkey;
pub mod dir;
pub mod ethkey;
mod account;
mod json;
mod crypto;
mod error;
mod ethstore;
mod import;
mod presale;
mod random;
mod secret_store;
pub use self::account::SafeAccount;
pub use self::error::Error;
pub use self::ethstore::EthStore;
pub use self::import::import_accounts;
pub use self::presale::PresaleWallet;
pub use self::secret_store::SecretStore;

79
ethstore/src/presale.rs Normal file
View File

@@ -0,0 +1,79 @@
use std::fs;
use std::path::Path;
use rcrypto::pbkdf2::pbkdf2;
use rcrypto::sha2::Sha256;
use rcrypto::hmac::Hmac;
use json;
use ethkey::{Address, Secret, KeyPair};
use crypto::Keccak256;
use {crypto, Error};
pub struct PresaleWallet {
iv: [u8; 16],
ciphertext: [u8; 80],
address: Address,
}
impl From<json::PresaleWallet> for PresaleWallet {
fn from(wallet: json::PresaleWallet) -> Self {
let mut iv = [0u8; 16];
iv.copy_from_slice(&wallet.encseed[..16]);
let mut ciphertext = [0u8; 80];
ciphertext.copy_from_slice(&wallet.encseed[16..]);
PresaleWallet {
iv: iv,
ciphertext: ciphertext,
address: Address::from(wallet.address),
}
}
}
impl PresaleWallet {
pub fn open<P>(path: P) -> Result<Self, Error> where P: AsRef<Path> {
let file = try!(fs::File::open(path));
let presale = json::PresaleWallet::load(file).unwrap();
Ok(PresaleWallet::from(presale))
}
pub fn decrypt(&self, password: &str) -> Result<KeyPair, Error> {
let mut h_mac = Hmac::new(Sha256::new(), password.as_bytes());
let mut derived_key = vec![0u8; 16];
pbkdf2(&mut h_mac, password.as_bytes(), 2000, &mut derived_key);
let mut key = [0u8; 64];
try!(crypto::aes::decrypt_cbc(&derived_key, &self.iv, &self.ciphertext, &mut key).map_err(|_| Error::InvalidPassword));
let secret = Secret::from(key.keccak256());
if let Ok(kp) = KeyPair::from_secret(secret) {
if kp.address() == self.address {
return Ok(kp)
}
}
Err(Error::InvalidPassword)
}
}
#[cfg(test)]
mod tests {
use super::PresaleWallet;
use json;
#[test]
fn test() {
let json = r#"
{
"encseed": "137103c28caeebbcea5d7f95edb97a289ded151b72159137cb7b2671f394f54cff8c121589dcb373e267225547b3c71cbdb54f6e48ec85cd549f96cf0dedb3bc0a9ac6c79b9c426c5878ca2c9d06ff42a23cb648312fc32ba83649de0928e066",
"ethaddr": "ede84640d1a1d3e06902048e67aa7db8d52c2ce1",
"email": "123@gmail.com",
"btcaddr": "1JvqEc6WLhg6GnyrLBe2ztPAU28KRfuseH"
} "#;
let wallet = json::PresaleWallet::load(json.as_bytes()).unwrap();
let wallet = PresaleWallet::from(wallet);
assert!(wallet.decrypt("123").is_ok());
assert!(wallet.decrypt("124").is_err());
}
}

39
ethstore/src/random.rs Normal file
View File

@@ -0,0 +1,39 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use rand::{Rng, OsRng};
pub trait Random {
fn random() -> Self where Self: Sized;
}
impl Random for [u8; 16] {
fn random() -> Self {
let mut result = [0u8; 16];
let mut rng = OsRng::new().unwrap();
rng.fill_bytes(&mut result);
result
}
}
impl Random for [u8; 32] {
fn random() -> Self {
let mut result = [0u8; 32];
let mut rng = OsRng::new().unwrap();
rng.fill_bytes(&mut result);
result
}
}

View File

@@ -0,0 +1,31 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use ethkey::{Address, Message, Signature, Secret};
use Error;
pub trait SecretStore: Send + Sync {
fn insert_account(&self, secret: Secret, password: &str) -> Result<Address, Error>;
fn accounts(&self) -> Vec<Address>;
fn change_password(&self, account: &Address, old_password: &str, new_password: &str) -> Result<(), Error>;
fn remove_account(&self, account: &Address, password: &str) -> Result<(), Error>;
fn sign(&self, account: &Address, password: &str, message: &Message) -> Result<Signature, Error>;
}