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:
parent
4889cff310
commit
494a0de1e2
@ -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();
|
||||
|
@ -106,7 +106,7 @@ impl ExtendedSecret {
|
||||
}
|
||||
|
||||
/// Private key component of the extended key.
|
||||
pub fn secret(&self) -> &Secret {
|
||||
pub fn as_raw(&self) -> &Secret {
|
||||
&self.secret
|
||||
}
|
||||
}
|
||||
@ -127,7 +127,7 @@ impl ExtendedPublic {
|
||||
pub fn from_secret(secret: &ExtendedSecret) -> Result<Self, DerivationError> {
|
||||
Ok(
|
||||
ExtendedPublic::new(
|
||||
derivation::point(**secret.secret())?,
|
||||
derivation::point(**secret.as_raw())?,
|
||||
secret.chain_code.clone(),
|
||||
)
|
||||
)
|
||||
@ -410,7 +410,7 @@ mod tests {
|
||||
let (private_seed, chain_code) = master_chain_basic();
|
||||
let extended_secret = ExtendedSecret::with_code(Secret::from_slice(&*private_seed).unwrap(), chain_code);
|
||||
let derived = f(extended_secret);
|
||||
assert_eq!(**derived.secret(), test_private);
|
||||
assert_eq!(**derived.as_raw(), test_private);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -419,14 +419,14 @@ mod tests {
|
||||
let extended_secret = ExtendedSecret::with_code(secret.clone(), 0u64.into());
|
||||
|
||||
// hardened
|
||||
assert_eq!(&**extended_secret.secret(), &*secret);
|
||||
assert_eq!(&**extended_secret.derive(2147483648.into()).secret(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into());
|
||||
assert_eq!(&**extended_secret.derive(2147483649.into()).secret(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into());
|
||||
assert_eq!(&**extended_secret.as_raw(), &*secret);
|
||||
assert_eq!(&**extended_secret.derive(2147483648.into()).as_raw(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into());
|
||||
assert_eq!(&**extended_secret.derive(2147483649.into()).as_raw(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into());
|
||||
|
||||
// normal
|
||||
assert_eq!(&**extended_secret.derive(0.into()).secret(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into());
|
||||
assert_eq!(&**extended_secret.derive(1.into()).secret(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into());
|
||||
assert_eq!(&**extended_secret.derive(2.into()).secret(), &"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into());
|
||||
assert_eq!(&**extended_secret.derive(0.into()).as_raw(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into());
|
||||
assert_eq!(&**extended_secret.derive(1.into()).as_raw(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into());
|
||||
assert_eq!(&**extended_secret.derive(2.into()).as_raw(), &"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into());
|
||||
|
||||
let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created");
|
||||
let derived_public = extended_public.derive(0.into()).expect("First derivation of public should succeed");
|
||||
@ -436,7 +436,7 @@ mod tests {
|
||||
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(),
|
||||
064.into(),
|
||||
);
|
||||
assert_eq!(&**keypair.derive(2147483648u32.into()).expect("Derivation of keypair should succeed").secret().secret(), &"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into());
|
||||
assert_eq!(&**keypair.derive(2147483648u32.into()).expect("Derivation of keypair should succeed").secret().as_raw(), &"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -461,7 +461,7 @@ mod tests {
|
||||
let derivation_secret = H256::from_str("51eaf04f9dbbc1417dc97e789edd0c37ecda88bac490434e367ea81b71b7b015").unwrap();
|
||||
let extended_secret = ExtendedSecret::with_code(secret.clone(), 1u64.into());
|
||||
|
||||
assert_eq!(&**extended_secret.derive(Derivation::Hard(derivation_secret)).secret(), &"2bc2d696fb744d77ff813b4a1ef0ad64e1e5188b622c54ba917acc5ebc7c5486".into());
|
||||
assert_eq!(&**extended_secret.derive(Derivation::Hard(derivation_secret)).as_raw(), &"2bc2d696fb744d77ff813b4a1ef0ad64e1e5188b622c54ba917acc5ebc7c5486".into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -51,7 +51,7 @@ pub use self::prefix::Prefix;
|
||||
pub use self::random::Random;
|
||||
pub use self::signature::{sign, verify_public, verify_address, recover, Signature};
|
||||
pub use self::secret::Secret;
|
||||
pub use self::extended::{ExtendedPublic, ExtendedSecret, ExtendedKeyPair, DerivationError};
|
||||
pub use self::extended::{ExtendedPublic, ExtendedSecret, ExtendedKeyPair, DerivationError, Derivation};
|
||||
|
||||
use bigint::hash::{H160, H256, H512};
|
||||
|
||||
|
@ -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()
|
||||
@ -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),
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ use ethcore::account_provider::AccountProvider;
|
||||
use jsonrpc_core::Error;
|
||||
use v1::helpers::errors;
|
||||
use v1::traits::ParityAccounts;
|
||||
use v1::types::{H160 as RpcH160, H256 as RpcH256, DappId};
|
||||
use v1::types::{H160 as RpcH160, H256 as RpcH256, DappId, Derive, DeriveHierarchical, DeriveHash};
|
||||
|
||||
/// Account management (personal) rpc implementation.
|
||||
pub struct ParityAccountsClient {
|
||||
@ -261,6 +261,32 @@ impl ParityAccounts for ParityAccountsClient {
|
||||
.map_err(|e| errors::account("Could not update vault metadata.", e))
|
||||
.map(|_| true)
|
||||
}
|
||||
|
||||
fn derive_key_index(&self, addr: RpcH160, password: String, derivation: DeriveHierarchical, save_as_account: bool) -> Result<RpcH160, Error> {
|
||||
let addr: Address = addr.into();
|
||||
take_weak!(self.accounts)
|
||||
.derive_account(
|
||||
&addr,
|
||||
Some(password),
|
||||
Derive::from(derivation).to_derivation()
|
||||
.map_err(|c| errors::account("Could not parse derivation request: {:?}", c))?,
|
||||
save_as_account)
|
||||
.map(Into::into)
|
||||
.map_err(|e| errors::account("Could not derive account.", e))
|
||||
}
|
||||
|
||||
fn derive_key_hash(&self, addr: RpcH160, password: String, derivation: DeriveHash, save_as_account: bool) -> Result<RpcH160, Error> {
|
||||
let addr: Address = addr.into();
|
||||
take_weak!(self.accounts)
|
||||
.derive_account(
|
||||
&addr,
|
||||
Some(password),
|
||||
Derive::from(derivation).to_derivation()
|
||||
.map_err(|c| errors::account("Could not parse derivation request: {:?}", c))?,
|
||||
save_as_account)
|
||||
.map(Into::into)
|
||||
.map_err(|e| errors::account("Could not derive account.", e))
|
||||
}
|
||||
}
|
||||
|
||||
fn into_vec<A, B>(a: Vec<A>) -> Vec<B> where
|
||||
|
@ -393,3 +393,43 @@ fn rpc_parity_get_set_vault_meta() {
|
||||
|
||||
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
|
||||
}
|
||||
|
||||
// name: parity_deriveAddressHash
|
||||
// example: {"jsonrpc": "2.0", "method": "parity_deriveAddressHash", "params": ["0xc171033d5cbff7175f29dfd3a63dda3d6f8f385e", "password1", { "type": "soft", "hash": "0x0c0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0c0c" }, true ], "id": 3}
|
||||
#[test]
|
||||
fn derive_key_hash() {
|
||||
let tester = setup();
|
||||
let hash = tester.accounts
|
||||
.insert_account(
|
||||
"0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a".parse().unwrap(),
|
||||
"password1")
|
||||
.expect("account should be inserted ok");
|
||||
|
||||
assert_eq!(hash, "c171033d5cbff7175f29dfd3a63dda3d6f8f385e".parse().unwrap());
|
||||
|
||||
// derive by hash
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_deriveAddressHash", "params": ["0xc171033d5cbff7175f29dfd3a63dda3d6f8f385e", "password1", { "type": "soft", "hash": "0x0c0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0c0c" }, true ], "id": 3}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":"0xf28c28fcddf4a9b8f474237278d3647f9c0d1b3c","id":3}"#;
|
||||
let res = tester.io.handle_request_sync(&request);
|
||||
assert_eq!(res, Some(response.into()));
|
||||
}
|
||||
|
||||
// name: parity_deriveAddressIndex
|
||||
// example: {"jsonrpc": "2.0", "method": "parity_deriveAddressIndex", "params": ["0xc171033d5cbff7175f29dfd3a63dda3d6f8f385e", "password1", [{ "type": "soft", "index": 0 }, { "type": "soft", "index": 1 }], false ], "id": 3}
|
||||
#[test]
|
||||
fn derive_key_index() {
|
||||
let tester = setup();
|
||||
let hash = tester.accounts
|
||||
.insert_account(
|
||||
"0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a".parse().unwrap(),
|
||||
"password1")
|
||||
.expect("account should be inserted ok");
|
||||
|
||||
assert_eq!(hash, "c171033d5cbff7175f29dfd3a63dda3d6f8f385e".parse().unwrap());
|
||||
|
||||
// derive by hash
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_deriveAddressIndex", "params": ["0xc171033d5cbff7175f29dfd3a63dda3d6f8f385e", "password1", [{ "type": "soft", "index": 0 }, { "type": "soft", "index": 1 }], false ], "id": 3}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":"0xcc548e0bb2efe792a920ae0fbf583b13919f274f","id":3}"#;
|
||||
let res = tester.io.handle_request_sync(&request);
|
||||
assert_eq!(res, Some(response.into()));
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use jsonrpc_core::Error;
|
||||
use v1::types::{H160, H256, DappId};
|
||||
use v1::types::{H160, H256, DappId, DeriveHash, DeriveHierarchical};
|
||||
|
||||
build_rpc_trait! {
|
||||
/// Personal Parity rpc interface.
|
||||
@ -141,5 +141,16 @@ build_rpc_trait! {
|
||||
/// Set vault metadata string.
|
||||
#[rpc(name = "parity_setVaultMeta")]
|
||||
fn set_vault_meta(&self, String, String) -> Result<bool, Error>;
|
||||
|
||||
/// Derive new address from given account address using specific hash.
|
||||
/// Resulting address can be either saved as a new account (with the same password).
|
||||
#[rpc(name = "parity_deriveAddressHash")]
|
||||
fn derive_key_hash(&self, H160, String, DeriveHash, bool) -> Result<H160, Error>;
|
||||
|
||||
/// Derive new address from given account address using
|
||||
/// hierarchical derivation (sequence of 32-bit integer indices).
|
||||
/// Resulting address can be either saved as a new account (with the same password).
|
||||
#[rpc(name = "parity_deriveAddressIndex")]
|
||||
fn derive_key_index(&self, H160, String, DeriveHierarchical, bool) -> Result<H160, Error>;
|
||||
}
|
||||
}
|
||||
|
131
rpc/src/v1/types/derivation.rs
Normal file
131
rpc/src/v1/types/derivation.rs
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 serde::{Deserialize, Deserializer};
|
||||
use serde::de::{Error, Visitor};
|
||||
|
||||
use ethstore;
|
||||
|
||||
use super::hash::H256;
|
||||
|
||||
/// Type of derivation
|
||||
pub enum DerivationType {
|
||||
/// Soft - allow proof of parent
|
||||
Soft,
|
||||
/// Hard - does not allow proof of parent
|
||||
Hard,
|
||||
}
|
||||
|
||||
/// Derivation request by hash
|
||||
#[derive(Deserialize)]
|
||||
pub struct DeriveHash {
|
||||
hash: H256,
|
||||
#[serde(rename="type")]
|
||||
d_type: DerivationType,
|
||||
}
|
||||
|
||||
/// Node propertoes in hierarchical derivation request
|
||||
#[derive(Deserialize)]
|
||||
pub struct DeriveHierarchicalItem {
|
||||
index: u64,
|
||||
#[serde(rename="type")]
|
||||
d_type: DerivationType,
|
||||
}
|
||||
|
||||
/// Hierarchical (index sequence) request
|
||||
pub type DeriveHierarchical = Vec<DeriveHierarchicalItem>;
|
||||
|
||||
/// Generic derivate request
|
||||
pub enum Derive {
|
||||
/// Hierarchical (index sequence) request
|
||||
Hierarchical(DeriveHierarchical),
|
||||
/// Hash request
|
||||
Hash(DeriveHash),
|
||||
}
|
||||
|
||||
impl From<DeriveHierarchical> for Derive {
|
||||
fn from(d: DeriveHierarchical) -> Self {
|
||||
Derive::Hierarchical(d)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DeriveHash> for Derive {
|
||||
fn from(d: DeriveHash) -> Self {
|
||||
Derive::Hash(d)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error converting request data
|
||||
#[derive(Debug)]
|
||||
pub enum ConvertError {
|
||||
IndexOverlfow(u64),
|
||||
}
|
||||
|
||||
impl Derive {
|
||||
/// Convert to account provider struct dealing with possible overflows
|
||||
pub fn to_derivation(self) -> Result<ethstore::Derivation, ConvertError> {
|
||||
Ok(match self {
|
||||
Derive::Hierarchical(drv) => {
|
||||
ethstore::Derivation::Hierarchical({
|
||||
let mut members = Vec::<ethstore::IndexDerivation>::new();
|
||||
for h in drv {
|
||||
if h.index > ::std::u32::MAX as u64 { return Err(ConvertError::IndexOverlfow(h.index)); }
|
||||
members.push(match h.d_type {
|
||||
DerivationType::Soft => ethstore::IndexDerivation { soft: true, index: h.index as u32 },
|
||||
DerivationType::Hard => ethstore::IndexDerivation { soft: false, index: h.index as u32 },
|
||||
});
|
||||
}
|
||||
members
|
||||
})
|
||||
},
|
||||
Derive::Hash(drv) => {
|
||||
match drv.d_type {
|
||||
DerivationType::Soft => ethstore::Derivation::SoftHash(drv.hash.into()),
|
||||
DerivationType::Hard => ethstore::Derivation::HardHash(drv.hash.into()),
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserialize for DerivationType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<DerivationType, D::Error> where D: Deserializer {
|
||||
deserializer.deserialize(DerivationTypeVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct DerivationTypeVisitor;
|
||||
|
||||
impl Visitor for DerivationTypeVisitor {
|
||||
type Value = DerivationType;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(formatter, "'hard' or 'soft'")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: Error {
|
||||
match value {
|
||||
"soft" => Ok(DerivationType::Soft),
|
||||
"hard" => Ok(DerivationType::Hard),
|
||||
_ => Err(Error::custom("invalid derivation type")),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E> where E: Error {
|
||||
self.visit_str(value.as_ref())
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ mod bytes;
|
||||
mod call_request;
|
||||
mod confirmations;
|
||||
mod consensus_status;
|
||||
mod derivation;
|
||||
mod filter;
|
||||
mod hash;
|
||||
mod histogram;
|
||||
@ -51,6 +52,7 @@ pub use self::confirmations::{
|
||||
TransactionModification, SignRequest, DecryptRequest, Either
|
||||
};
|
||||
pub use self::consensus_status::*;
|
||||
pub use self::derivation::{DeriveHash, DeriveHierarchical, Derive};
|
||||
pub use self::filter::{Filter, FilterChanges};
|
||||
pub use self::hash::{H64, H160, H256, H512, H520, H2048};
|
||||
pub use self::histogram::Histogram;
|
||||
@ -70,4 +72,3 @@ pub use self::transaction_request::TransactionRequest;
|
||||
pub use self::transaction_condition::TransactionCondition;
|
||||
pub use self::uint::{U128, U256};
|
||||
pub use self::work::Work;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user