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

@ -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();

View File

@ -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]

View File

@ -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};

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),
}

View File

@ -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

View File

@ -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()));
}

View File

@ -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>;
}
}

View 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())
}
}

View File

@ -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;