Wallet rpcs (#1898)

* Add wallet RPCs.

* Add wordlist file.

* Add standard brain wallet tests.

* Allow import of JSON wallets.

* Address grumble.
This commit is contained in:
Gav Wood
2016-08-10 17:57:40 +02:00
committed by Arkadiy Paronyan
parent c32244ea4a
commit 286b67d54b
18 changed files with 7685 additions and 6 deletions

View File

@@ -16,6 +16,8 @@ rust-crypto = "0.2.36"
tiny-keccak = "1.0"
docopt = { version = "0.6", optional = true }
time = "0.1.34"
lazy_static = "0.2"
itertools = "0.4"
[build-dependencies]
serde_codegen = { version = "0.7", optional = true }

7530
ethstore/res/wordlist.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -66,7 +66,7 @@ impl Into<json::KeyFile> for SafeAccount {
json::KeyFile {
id: From::from(self.id),
version: self.version.into(),
address: self.address.into(), //From::from(self.address),
address: self.address.into(),
crypto: self.crypto.into(),
name: Some(self.name.into()),
meta: Some(self.meta.into()),
@@ -150,13 +150,16 @@ impl SafeAccount {
}
}
pub fn from_file(json: json::KeyFile, filename: String) -> Self {
/// Create a new `SafeAccount` from the given `json`; if it was read from a
/// file, the `filename` should be `Some` name. If it is as yet anonymous, then it
/// can be left `None`.
pub fn from_file(json: json::KeyFile, filename: Option<String>) -> Self {
SafeAccount {
id: json.id.into(),
version: json.version.into(),
address: json.address.into(),
crypto: json.crypto.into(),
filename: Some(filename),
filename: filename,
name: json.name.unwrap_or(String::new()),
meta: json.meta.unwrap_or("{}".to_owned()),
}

View File

@@ -87,7 +87,7 @@ impl DiskDirectory {
.zip(paths.into_iter())
.map(|(file, path)| match file {
Ok(file) => Ok((path.clone(), SafeAccount::from_file(
file, path.file_name().and_then(|n| n.to_str()).expect("Keys have valid UTF8 names only.").to_owned()
file, Some(path.file_name().and_then(|n| n.to_str()).expect("Keys have valid UTF8 names only.").to_owned())
))),
Err(err) => Err(Error::InvalidKeyFile(format!("{:?}: {}", path, err))),
})

View File

@@ -24,7 +24,9 @@ use ethkey::{Signature, Address, Message, Secret};
use dir::KeyDirectory;
use account::SafeAccount;
use {Error, SecretStore};
use json;
use json::UUID;
use presale::PresaleWallet;
pub struct EthStore {
dir: Box<KeyDirectory>,
@@ -89,6 +91,23 @@ impl SecretStore for EthStore {
Ok(address)
}
fn import_presale(&self, json: &[u8], password: &str) -> Result<Address, Error> {
let json_wallet = try!(json::PresaleWallet::load(json).map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned())));
let wallet = PresaleWallet::from(json_wallet);
let keypair = try!(wallet.decrypt(password).map_err(|_| Error::InvalidPassword));
self.insert_account(keypair.secret().clone(), password)
}
fn import_wallet(&self, json: &[u8], password: &str) -> Result<Address, Error> {
let json_keyfile = try!(json::KeyFile::load(json).map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned())));
let mut safe_account = SafeAccount::from_file(json_keyfile, None);
let secret = try!(safe_account.crypto.secret(password).map_err(|_| Error::InvalidPassword));
safe_account.address = try!(KeyPair::from_secret(secret)).address();
let address = safe_account.address.clone();
try!(self.save(safe_account));
Ok(address)
}
fn accounts(&self) -> Result<Vec<Address>, Error> {
try!(self.reload_accounts());
Ok(self.cache.read().unwrap().keys().cloned().collect())

View File

@@ -18,6 +18,7 @@
#![cfg_attr(feature="nightly", plugin(serde_macros))]
extern crate libc;
extern crate itertools;
extern crate rand;
extern crate time;
extern crate serde;
@@ -25,6 +26,8 @@ extern crate serde_json;
extern crate rustc_serialize;
extern crate crypto as rcrypto;
extern crate tiny_keccak;
#[macro_use]
extern crate lazy_static;
// reexport it nicely
extern crate ethkey as _ethkey;
@@ -48,4 +51,5 @@ pub use self::ethstore::EthStore;
pub use self::import::import_accounts;
pub use self::presale::PresaleWallet;
pub use self::secret_store::SecretStore;
pub use self::random::random_phrase;

View File

@@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use rand::{Rng, OsRng};
use itertools::Itertools;
pub trait Random {
fn random() -> Self where Self: Sized;
@@ -37,3 +38,25 @@ impl Random for [u8; 32] {
result
}
}
/// Generate a string which is a random phrase of a number of lowercase words.
///
/// `words` is the number of words, chosen from a dictionary of 7,530. An value of
/// 12 gives 155 bits of entropy (almost saturating address space); 20 gives 258 bits
/// which is enough to saturate 32-byte key space
pub fn random_phrase(words: usize) -> String {
lazy_static! {
static ref WORDS: Vec<String> = String::from_utf8_lossy(include_bytes!("../res/wordlist.txt"))
.split("\n")
.map(|s| s.to_owned())
.collect();
}
let mut rng = OsRng::new().unwrap();
(0..words).map(|_| rng.choose(&WORDS).unwrap()).join(" ")
}
#[test]
fn should_produce_right_number_of_words() {
let p = random_phrase(10);
assert_eq!(p.split(" ").count(), 10);
}

View File

@@ -21,6 +21,10 @@ use json::UUID;
pub trait SecretStore: Send + Sync {
fn insert_account(&self, secret: Secret, password: &str) -> Result<Address, Error>;
fn import_presale(&self, json: &[u8], password: &str) -> Result<Address, Error>;
fn import_wallet(&self, json: &[u8], password: &str) -> Result<Address, Error>;
fn accounts(&self) -> Result<Vec<Address>, Error>;
fn change_password(&self, account: &Address, old_password: &str, new_password: &str) -> Result<(), Error>;