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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user