Key derivation in ethstore & rpc (#4515)

* initial in secret store

* generation

* test

* refactor of the derivation

* signing

* account provider

* tests for account provider

* rpc types

* rpc types converts

* rpc tests

* fix warnings

* some extra docs

* derivate -> derive

* secret() -> as_raw()

* secret() -> as_raw() in rpc

* fix merge bug

* align with new serde changes
This commit is contained in:
Nikolay Volf
2017-02-15 18:56:15 +03:00
committed by Gav Wood
parent 4889cff310
commit 494a0de1e2
12 changed files with 464 additions and 24 deletions

View File

@@ -18,6 +18,7 @@ use std::fmt;
use std::io::Error as IoError;
use ethkey::Error as EthKeyError;
use crypto::Error as EthCryptoError;
use ethkey::DerivationError;
#[derive(Debug)]
pub enum Error {
@@ -35,6 +36,7 @@ pub enum Error {
CreationFailed,
EthKey(EthKeyError),
EthCrypto(EthCryptoError),
Derivation(DerivationError),
Custom(String),
}
@@ -55,6 +57,7 @@ impl fmt::Display for Error {
Error::CreationFailed => "Account creation failed".into(),
Error::EthKey(ref err) => err.to_string(),
Error::EthCrypto(ref err) => err.to_string(),
Error::Derivation(ref err) => format!("Derivation error: {:?}", err),
Error::Custom(ref s) => s.clone(),
};
@@ -79,3 +82,9 @@ impl From<EthCryptoError> for Error {
Error::EthCrypto(err)
}
}
impl From<DerivationError> for Error {
fn from(err: DerivationError) -> Self {
Error::Derivation(err)
}
}

View File

@@ -21,12 +21,12 @@ use parking_lot::{Mutex, RwLock};
use crypto::KEY_ITERATIONS;
use random::Random;
use ethkey::{Signature, Address, Message, Secret, Public, KeyPair};
use ethkey::{self, Signature, Address, Message, Secret, Public, KeyPair, ExtendedKeyPair};
use dir::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError};
use account::SafeAccount;
use presale::PresaleWallet;
use json::{self, Uuid};
use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef};
use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation};
pub struct EthStore {
store: EthMultiStore,
@@ -54,6 +54,16 @@ impl SimpleSecretStore for EthStore {
self.store.insert_account(vault, secret, password)
}
fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation)
-> Result<StoreAccountRef, Error>
{
self.store.insert_derived(vault, account_ref, password, derivation)
}
fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result<Address, Error> {
self.store.generate_derived(account_ref, password, derivation)
}
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
self.store.account_ref(address)
}
@@ -71,8 +81,13 @@ impl SimpleSecretStore for EthStore {
}
fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result<Signature, Error> {
let account = self.get(account)?;
account.sign(password, message)
self.get(account)?.sign(password, message)
}
fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message)
-> Result<Signature, Error>
{
self.store.sign_derived(account_ref, password, derivation, message)
}
fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
@@ -340,6 +355,23 @@ impl EthMultiStore {
return Ok(());
}
fn generate(&self, secret: Secret, derivation: Derivation) -> Result<ExtendedKeyPair, Error> {
let mut extended = ExtendedKeyPair::new(secret);
match derivation {
Derivation::Hierarchical(path) => {
for path_item in path {
extended = extended.derive(
if path_item.soft { ethkey::Derivation::Soft(path_item.index) }
else { ethkey::Derivation::Hard(path_item.index) }
)?;
}
},
Derivation::SoftHash(h256) => { extended = extended.derive(ethkey::Derivation::Soft(h256))?; }
Derivation::HardHash(h256) => { extended = extended.derive(ethkey::Derivation::Hard(h256))?; }
}
Ok(extended)
}
}
impl SimpleSecretStore for EthMultiStore {
@@ -350,6 +382,54 @@ impl SimpleSecretStore for EthMultiStore {
self.import(vault, account)
}
fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation)
-> Result<StoreAccountRef, Error>
{
let accounts = self.get(account_ref)?;
for account in accounts {
// Skip if password is invalid
if !account.check_password(password) {
continue;
}
let extended = self.generate(account.crypto.secret(password)?, derivation)?;
return self.insert_account(vault, extended.secret().as_raw().clone(), password);
}
Err(Error::InvalidPassword)
}
fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation)
-> Result<Address, Error>
{
let accounts = self.get(&account_ref)?;
for account in accounts {
// Skip if password is invalid
if !account.check_password(password) {
continue;
}
let extended = self.generate(account.crypto.secret(password)?, derivation)?;
return Ok(ethkey::public_to_address(extended.public().public()));
}
Err(Error::InvalidPassword)
}
fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message)
-> Result<Signature, Error>
{
let accounts = self.get(&account_ref)?;
for account in accounts {
// Skip if password is invalid
if !account.check_password(password) {
continue;
}
let extended = self.generate(account.crypto.secret(password)?, derivation)?;
let secret = extended.secret().as_raw();
return Ok(ethkey::sign(&secret, message)?)
}
Err(Error::InvalidPassword)
}
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
self.reload_accounts()?;
self.cache.read().keys()
@@ -511,7 +591,7 @@ impl SimpleSecretStore for EthMultiStore {
let vault_provider = self.dir.as_vault_provider().ok_or(Error::VaultsAreNotSupported)?;
vault_provider.vault_meta(name)
})
}
fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error> {
@@ -527,9 +607,10 @@ mod tests {
use dir::{KeyDirectory, MemoryDirectory, RootDiskDirectory};
use ethkey::{Random, Generator, KeyPair};
use secret_store::{SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef};
use secret_store::{SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation};
use super::{EthStore, EthMultiStore};
use devtools::RandomTempPath;
use util::H256;
fn keypair() -> KeyPair {
Random.generate().unwrap()
@@ -898,4 +979,27 @@ mod tests {
assert_eq!(store.get_vault_meta(name1).unwrap(), "Hello, world!!!".to_owned());
assert!(store.get_vault_meta("vault2").is_err());
}
#[test]
fn should_store_derived_keys() {
// given we have one account in the store
let store = store();
let keypair = keypair();
let address = store.insert_account(SecretVaultRef::Root, keypair.secret().clone(), "test").unwrap();
// when we deriving from that account
let derived = store.insert_derived(
SecretVaultRef::Root,
&address,
"test",
Derivation::HardHash(H256::from(0)),
).unwrap();
// there should be 2 accounts in the store
let accounts = store.accounts().unwrap();
assert_eq!(accounts.len(), 2);
// and we can sign with the derived contract
assert!(store.sign(&derived, "test", &Default::default()).is_ok(), "Second password should work for second store.");
}
}

View File

@@ -57,5 +57,8 @@ pub use self::error::Error;
pub use self::ethstore::{EthStore, EthMultiStore};
pub use self::import::{import_accounts, read_geth_accounts};
pub use self::presale::PresaleWallet;
pub use self::secret_store::{SecretVaultRef, StoreAccountRef, SimpleSecretStore, SecretStore};
pub use self::secret_store::{
SecretVaultRef, StoreAccountRef, SimpleSecretStore, SecretStore,
Derivation, IndexDerivation,
};
pub use self::random::{random_phrase, random_string};

View File

@@ -19,6 +19,7 @@ use std::path::PathBuf;
use ethkey::{Address, Message, Signature, Secret, Public};
use Error;
use json::Uuid;
use util::H256;
/// Key directory reference
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
@@ -40,10 +41,12 @@ pub struct StoreAccountRef {
pub trait SimpleSecretStore: Send + Sync {
fn insert_account(&self, vault: SecretVaultRef, secret: Secret, password: &str) -> Result<StoreAccountRef, Error>;
fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result<StoreAccountRef, Error>;
fn change_password(&self, account: &StoreAccountRef, old_password: &str, new_password: &str) -> Result<(), Error>;
fn remove_account(&self, account: &StoreAccountRef, password: &str) -> Result<(), Error>;
fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result<Address, Error>;
fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result<Signature, Error>;
fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message) -> Result<Signature, Error>;
fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error>;
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error>;
@@ -116,3 +119,21 @@ impl Hash for StoreAccountRef {
self.address.hash(state);
}
}
/// Node in hierarchical derivation.
pub struct IndexDerivation {
/// Node is soft (allows proof of parent from parent node).
pub soft: bool,
/// Index sequence of the node.
pub index: u32,
}
/// Derivation scheme for keys
pub enum Derivation {
/// Hierarchical derivation
Hierarchical(Vec<IndexDerivation>),
/// Hash derivation, soft.
SoftHash(H256),
/// Hash derivation, hard.
HardHash(H256),
}