Merge with master
This commit is contained in:
59
ethstore/src/account/cipher.rs
Normal file
59
ethstore/src/account/cipher.rs
Normal 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
125
ethstore/src/account/kdf.rs
Normal 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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
25
ethstore/src/account/mod.rs
Normal file
25
ethstore/src/account/mod.rs
Normal 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;
|
||||
216
ethstore/src/account/safe_account.rs
Normal file
216
ethstore/src/account/safe_account.rs
Normal 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, ¶ms.salt, params.c),
|
||||
Kdf::Scrypt(ref params) => crypto::derive_key_scrypt(password, ¶ms.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, ¶ms.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());
|
||||
}
|
||||
}
|
||||
38
ethstore/src/account/version.rs
Normal file
38
ethstore/src/account/version.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
169
ethstore/src/bin/ethstore.rs
Normal file
169
ethstore/src/bin/ethstore.rs
Normal 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
21
ethstore/src/bin/main.rs
Normal 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
93
ethstore/src/crypto.rs
Normal 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
125
ethstore/src/dir/disk.rs
Normal 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
95
ethstore/src/dir/geth.rs
Normal 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
37
ethstore/src/dir/mod.rs
Normal 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;
|
||||
74
ethstore/src/dir/parity.rs
Normal file
74
ethstore/src/dir/parity.rs
Normal 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
58
ethstore/src/error.rs
Normal 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
41
ethstore/src/ethkey.rs
Normal 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
108
ethstore/src/ethstore.rs
Normal 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
28
ethstore/src/import.rs
Normal 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()
|
||||
}
|
||||
91
ethstore/src/json/cipher.rs
Normal file
91
ethstore/src/json/cipher.rs
Normal 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
200
ethstore/src/json/crypto.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
ethstore/src/json/error.rs
Normal file
50
ethstore/src/json/error.rs
Normal 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
112
ethstore/src/json/hash.rs
Normal 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
145
ethstore/src/json/id.rs
Normal 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
149
ethstore/src/json/kdf.rs
Normal 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),
|
||||
}
|
||||
271
ethstore/src/json/key_file.rs
Normal file
271
ethstore/src/json/key_file.rs
Normal 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
24
ethstore/src/json/mod.rs
Normal 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"));
|
||||
|
||||
20
ethstore/src/json/mod.rs.in
Normal file
20
ethstore/src/json/mod.rs.in
Normal 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;
|
||||
|
||||
42
ethstore/src/json/presale.rs
Normal file
42
ethstore/src/json/presale.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
54
ethstore/src/json/version.rs
Normal file
54
ethstore/src/json/version.rs
Normal 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
50
ethstore/src/lib.rs
Normal 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
79
ethstore/src/presale.rs
Normal 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
39
ethstore/src/random.rs
Normal 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
|
||||
}
|
||||
}
|
||||
31
ethstore/src/secret_store.rs
Normal file
31
ethstore/src/secret_store.rs
Normal 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>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user