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:
@@ -31,6 +31,7 @@ use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator};
|
||||
use ethjson::misc::AccountMeta;
|
||||
use hardware_wallet::{Error as HardwareError, HardwareWalletManager, KeyPath};
|
||||
pub use ethstore::ethkey::Signature;
|
||||
pub use ethstore::{Derivation, IndexDerivation};
|
||||
|
||||
/// Type of unlock.
|
||||
#[derive(Clone)]
|
||||
@@ -197,6 +198,20 @@ impl AccountProvider {
|
||||
Ok(account.address)
|
||||
}
|
||||
|
||||
/// Generates new derived account based on the existing one
|
||||
/// If password is not provided, account must be unlocked
|
||||
/// New account will be created with the same password (if save: true)
|
||||
pub fn derive_account(&self, address: &Address, password: Option<String>, derivation: Derivation, save: bool)
|
||||
-> Result<Address, SignError>
|
||||
{
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(
|
||||
if save { self.sstore.insert_derived(SecretVaultRef::Root, &account, &password, derivation)?.address }
|
||||
else { self.sstore.generate_derived(&account, &password, derivation)? }
|
||||
)
|
||||
}
|
||||
|
||||
/// Import a new presale wallet.
|
||||
pub fn import_presale(&self, presale_json: &[u8], password: &str) -> Result<Address, Error> {
|
||||
let account = self.sstore.import_presale(SecretVaultRef::Root, presale_json, password)?;
|
||||
@@ -458,6 +473,15 @@ impl AccountProvider {
|
||||
Ok(self.sstore.sign(&account, &password, &message)?)
|
||||
}
|
||||
|
||||
/// Signs message using the derived secret. If password is not provided the account must be unlocked.
|
||||
pub fn sign_derived(&self, address: &Address, password: Option<String>, derivation: Derivation, message: Message)
|
||||
-> Result<Signature, SignError>
|
||||
{
|
||||
let account = self.sstore.account_ref(address)?;
|
||||
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(self.sstore.sign_derived(&account, &password, derivation, &message)?)
|
||||
}
|
||||
|
||||
/// Signs given message with supplied token. Returns a token to use in next signing within this session.
|
||||
pub fn sign_with_token(&self, address: Address, token: AccountToken, message: Message) -> Result<(Signature, AccountToken), SignError> {
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
@@ -593,7 +617,8 @@ mod tests {
|
||||
use super::{AccountProvider, Unlock, DappId};
|
||||
use std::time::Instant;
|
||||
use ethstore::ethkey::{Generator, Random};
|
||||
use ethstore::StoreAccountRef;
|
||||
use ethstore::{StoreAccountRef, Derivation};
|
||||
use util::H256;
|
||||
|
||||
#[test]
|
||||
fn unlock_account_temp() {
|
||||
@@ -606,6 +631,75 @@ mod tests {
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derived_account_nosave() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), "base").is_ok());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok());
|
||||
|
||||
let derived_addr = ap.derive_account(
|
||||
&kp.address(),
|
||||
None,
|
||||
Derivation::SoftHash(H256::from(999)),
|
||||
false,
|
||||
).expect("Derivation should not fail");
|
||||
|
||||
assert!(ap.unlock_account_permanently(derived_addr, "base".into()).is_err(),
|
||||
"There should be an error because account is not supposed to be saved");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derived_account_save() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), "base").is_ok());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok());
|
||||
|
||||
let derived_addr = ap.derive_account(
|
||||
&kp.address(),
|
||||
None,
|
||||
Derivation::SoftHash(H256::from(999)),
|
||||
true,
|
||||
).expect("Derivation should not fail");
|
||||
|
||||
assert!(ap.unlock_account_permanently(derived_addr, "base_wrong".into()).is_err(),
|
||||
"There should be an error because password is invalid");
|
||||
|
||||
assert!(ap.unlock_account_permanently(derived_addr, "base".into()).is_ok(),
|
||||
"Should be ok because account is saved and password is valid");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derived_account_sign() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), "base").is_ok());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok());
|
||||
|
||||
let derived_addr = ap.derive_account(
|
||||
&kp.address(),
|
||||
None,
|
||||
Derivation::SoftHash(H256::from(1999)),
|
||||
true,
|
||||
).expect("Derivation should not fail");
|
||||
ap.unlock_account_permanently(derived_addr, "base".into())
|
||||
.expect("Should be ok because account is saved and password is valid");
|
||||
|
||||
let msg = Default::default();
|
||||
let signed_msg1 = ap.sign(derived_addr, None, msg)
|
||||
.expect("Signing with existing unlocked account should not fail");
|
||||
let signed_msg2 = ap.sign_derived(
|
||||
&kp.address(),
|
||||
None,
|
||||
Derivation::SoftHash(H256::from(1999)),
|
||||
msg,
|
||||
).expect("Derived signing with existing unlocked account should not fail");
|
||||
|
||||
assert_eq!(signed_msg1, signed_msg2,
|
||||
"Signed messages should match");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unlock_account_perm() {
|
||||
let kp = Random.generate().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user