From 03c1559eadce34e66c4805f240adf018678a9cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 15 Oct 2016 14:44:08 +0200 Subject: [PATCH] Support for decryption in Signer (#2421) * Adding some tests * Implementing decrypt in queue * Removing code duplication. * Printing public key in ethstore * Bump UI * Normalizing dapps format for signer. * Fixing tests compilation * fix whitespace [ci:skip] --- Cargo.lock | 13 +- ethcore/src/account_provider.rs | 95 ++++++------- ethcore/src/engines/basic_authority.rs | 2 +- ethstore/src/account/safe_account.rs | 7 +- ethstore/src/bin/ethstore.rs | 16 ++- ethstore/src/ethstore.rs | 7 +- ethstore/src/secret_store.rs | 3 +- rpc/src/v1/helpers/dispatch.rs | 104 +++++++------- rpc/src/v1/helpers/errors.rs | 9 ++ rpc/src/v1/helpers/requests.rs | 2 + rpc/src/v1/impls/eth_signing.rs | 151 +++++++++++---------- rpc/src/v1/impls/ethcore.rs | 6 +- rpc/src/v1/impls/personal.rs | 15 +- rpc/src/v1/impls/personal_signer.rs | 12 +- rpc/src/v1/tests/eth.rs | 5 +- rpc/src/v1/tests/mocked/eth.rs | 8 +- rpc/src/v1/tests/mocked/eth_signing.rs | 76 ++++++++++- rpc/src/v1/tests/mocked/ethcore.rs | 23 +++- rpc/src/v1/tests/mocked/personal.rs | 4 +- rpc/src/v1/tests/mocked/personal_signer.rs | 2 +- rpc/src/v1/traits/eth.rs | 44 ------ rpc/src/v1/traits/eth_signing.rs | 63 +++++++++ rpc/src/v1/traits/mod.rs | 5 +- rpc/src/v1/types/confirmations.rs | 18 ++- signer/Cargo.toml | 3 +- signer/src/lib.rs | 2 + signer/src/ws_server/session.rs | 40 ++++-- 27 files changed, 457 insertions(+), 278 deletions(-) create mode 100644 rpc/src/v1/traits/eth_signing.rs diff --git a/Cargo.lock b/Cargo.lock index cee712727..2cfb39db8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -505,6 +505,7 @@ dependencies = [ "ethcore-util 1.4.0", "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "parity-dapps-signer 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1167,7 +1168,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "parity-dapps" version = "1.4.0" -source = "git+https://github.com/ethcore/parity-ui.git#926b09b66c4940b09dc82c52adb4afd9e31155bc" +source = "git+https://github.com/ethcore/parity-ui.git#8b1c31319228ad4cf9bd4ae740a0b933aa9e19c7" dependencies = [ "aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1181,7 +1182,7 @@ dependencies = [ [[package]] name = "parity-dapps-home" version = "1.4.0" -source = "git+https://github.com/ethcore/parity-ui.git#926b09b66c4940b09dc82c52adb4afd9e31155bc" +source = "git+https://github.com/ethcore/parity-ui.git#8b1c31319228ad4cf9bd4ae740a0b933aa9e19c7" dependencies = [ "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", ] @@ -1189,7 +1190,7 @@ dependencies = [ [[package]] name = "parity-dapps-signer" version = "1.4.0" -source = "git+https://github.com/ethcore/parity-ui.git#926b09b66c4940b09dc82c52adb4afd9e31155bc" +source = "git+https://github.com/ethcore/parity-ui.git#8b1c31319228ad4cf9bd4ae740a0b933aa9e19c7" dependencies = [ "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", ] @@ -1197,7 +1198,7 @@ dependencies = [ [[package]] name = "parity-dapps-status" version = "1.4.0" -source = "git+https://github.com/ethcore/parity-ui.git#926b09b66c4940b09dc82c52adb4afd9e31155bc" +source = "git+https://github.com/ethcore/parity-ui.git#8b1c31319228ad4cf9bd4ae740a0b933aa9e19c7" dependencies = [ "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", ] @@ -1205,7 +1206,7 @@ dependencies = [ [[package]] name = "parity-dapps-wallet" version = "1.4.0" -source = "git+https://github.com/ethcore/parity-ui.git#926b09b66c4940b09dc82c52adb4afd9e31155bc" +source = "git+https://github.com/ethcore/parity-ui.git#8b1c31319228ad4cf9bd4ae740a0b933aa9e19c7" dependencies = [ "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", ] @@ -1851,7 +1852,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ws" version = "0.5.2" -source = "git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable#afbff59776ce16ccec5ee9e218b8891830ee6fdf" +source = "git+https://github.com/ethcore/ws-rs.git?branch=mio-upstream-stable#609b21fdab96c8fffedec8699755ce3bea9454cb" dependencies = [ "bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)", "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 851d015ba..3f4511f4b 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -23,7 +23,7 @@ use std::time::{Instant, Duration}; use util::{Mutex, RwLock}; use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore}; use ethstore::dir::{KeyDirectory}; -use ethstore::ethkey::{Address, Message, Secret, Random, Generator}; +use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator}; use ethjson::misc::AccountMeta; pub use ethstore::ethkey::Signature; @@ -182,9 +182,16 @@ impl AccountProvider { /// Creates new random account. pub fn new_account(&self, password: &str) -> Result { - let secret = Random.generate().unwrap().secret().clone(); + self.new_account_and_public(password).map(|d| d.0) + } + + /// Creates new random account and returns address and public key + pub fn new_account_and_public(&self, password: &str) -> Result<(Address, Public), Error> { + let acc = Random.generate().unwrap(); + let public = acc.public().clone(); + let secret = acc.secret().clone(); let address = try!(self.sstore.insert_account(secret, password)); - Ok(address) + Ok((address, public)) } /// Inserts new account into underlying store. @@ -280,6 +287,21 @@ impl AccountProvider { Ok(()) } + fn password(&self, account: &Address) -> Result { + let mut unlocked = self.unlocked.lock(); + let data = try!(unlocked.get(account).ok_or(Error::NotUnlocked)).clone(); + if let Unlock::Temp = data.unlock { + unlocked.remove(account).expect("data exists: so key must exist: qed"); + } + if let Unlock::Timed((ref start, ref duration)) = data.unlock { + if start.elapsed() > Duration::from_millis(*duration as u64) { + unlocked.remove(account).expect("data exists: so key must exist: qed"); + return Err(Error::NotUnlocked); + } + } + Ok(data.password.clone()) + } + /// Unlocks account permanently. pub fn unlock_account_permanently(&self, account: Address, password: String) -> Result<(), Error> { self.unlock_account(account, password, Unlock::Perm) @@ -301,51 +323,16 @@ impl AccountProvider { unlocked.get(&account).is_some() } - /// Signs the message. Account must be unlocked. - pub fn sign(&self, account: Address, message: Message) -> Result { - let data = { - let mut unlocked = self.unlocked.lock(); - let data = try!(unlocked.get(&account).ok_or(Error::NotUnlocked)).clone(); - if let Unlock::Temp = data.unlock { - unlocked.remove(&account).expect("data exists: so key must exist: qed"); - } - if let Unlock::Timed((ref start, ref duration)) = data.unlock { - if start.elapsed() > Duration::from_millis(*duration as u64) { - unlocked.remove(&account).expect("data exists: so key must exist: qed"); - return Err(Error::NotUnlocked); - } - } - data - }; - - let signature = try!(self.sstore.sign(&account, &data.password, &message)); - Ok(signature) + /// Signs the message. If password is not provided the account must be unlocked. + pub fn sign(&self, account: Address, password: Option, message: Message) -> Result { + let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account))); + Ok(try!(self.sstore.sign(&account, &password, &message))) } - /// Decrypts a message. Account must be unlocked. - pub fn decrypt(&self, account: Address, shared_mac: &[u8], message: &[u8]) -> Result, Error> { - let data = { - let mut unlocked = self.unlocked.lock(); - let data = try!(unlocked.get(&account).ok_or(Error::NotUnlocked)).clone(); - if let Unlock::Temp = data.unlock { - unlocked.remove(&account).expect("data exists: so key must exist: qed"); - } - if let Unlock::Timed((ref start, ref duration)) = data.unlock { - if start.elapsed() > Duration::from_millis(*duration as u64) { - unlocked.remove(&account).expect("data exists: so key must exist: qed"); - return Err(Error::NotUnlocked); - } - } - data - }; - - Ok(try!(self.sstore.decrypt(&account, &data.password, shared_mac, message))) - } - - /// Unlocks an account, signs the message, and locks it again. - pub fn sign_with_password(&self, account: Address, password: String, message: Message) -> Result { - let signature = try!(self.sstore.sign(&account, &password, &message)); - Ok(signature) + /// Decrypts a message. If password is not provided the account must be unlocked. + pub fn decrypt(&self, account: Address, password: Option, shared_mac: &[u8], message: &[u8]) -> Result, Error> { + let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account))); + Ok(try!(self.sstore.decrypt(&account, &password, shared_mac, message))) } /// Returns the underlying `SecretStore` reference if one exists. @@ -386,8 +373,8 @@ mod tests { assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); assert!(ap.unlock_account_temporarily(kp.address(), "test1".into()).is_err()); assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok()); - assert!(ap.sign(kp.address(), Default::default()).is_ok()); - assert!(ap.sign(kp.address(), Default::default()).is_err()); + assert!(ap.sign(kp.address(), None, Default::default()).is_ok()); + assert!(ap.sign(kp.address(), None, Default::default()).is_err()); } #[test] @@ -397,11 +384,11 @@ mod tests { assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); assert!(ap.unlock_account_permanently(kp.address(), "test1".into()).is_err()); assert!(ap.unlock_account_permanently(kp.address(), "test".into()).is_ok()); - assert!(ap.sign(kp.address(), Default::default()).is_ok()); - assert!(ap.sign(kp.address(), Default::default()).is_ok()); + assert!(ap.sign(kp.address(), None, Default::default()).is_ok()); + assert!(ap.sign(kp.address(), None, Default::default()).is_ok()); assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok()); - assert!(ap.sign(kp.address(), Default::default()).is_ok()); - assert!(ap.sign(kp.address(), Default::default()).is_ok()); + assert!(ap.sign(kp.address(), None, Default::default()).is_ok()); + assert!(ap.sign(kp.address(), None, Default::default()).is_ok()); } #[test] @@ -411,8 +398,8 @@ mod tests { assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); assert!(ap.unlock_account_timed(kp.address(), "test1".into(), 2000).is_err()); assert!(ap.unlock_account_timed(kp.address(), "test".into(), 2000).is_ok()); - assert!(ap.sign(kp.address(), Default::default()).is_ok()); + assert!(ap.sign(kp.address(), None, Default::default()).is_ok()); ::std::thread::sleep(Duration::from_millis(2000)); - assert!(ap.sign(kp.address(), Default::default()).is_err()); + assert!(ap.sign(kp.address(), None, Default::default()).is_err()); } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 6e3c2f1dd..bd3eb5bc6 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -112,7 +112,7 @@ impl Engine for BasicAuthority { let header = block.header(); let message = header.bare_hash(); // account should be pernamently unlocked, otherwise sealing will fail - if let Ok(signature) = ap.sign(*block.header().author(), message) { + if let Ok(signature) = ap.sign(*block.header().author(), None, message) { return Some(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]); } else { trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); diff --git a/ethstore/src/account/safe_account.rs b/ethstore/src/account/safe_account.rs index 315fd283b..5dab35251 100644 --- a/ethstore/src/account/safe_account.rs +++ b/ethstore/src/account/safe_account.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use ethkey::{KeyPair, sign, Address, Secret, Signature, Message}; +use ethkey::{KeyPair, sign, Address, Secret, Signature, Message, Public}; use {json, Error, crypto}; use crypto::Keccak256; use random::Random; @@ -180,6 +180,11 @@ impl SafeAccount { crypto::ecies::decrypt(&secret, shared_mac, message).map_err(From::from) } + pub fn public(&self, password: &str) -> Result { + let secret = try!(self.crypto.secret(password)); + Ok(try!(KeyPair::from_secret(secret)).public().clone()) + } + pub fn change_password(&self, old_password: &str, new_password: &str, iterations: u32) -> Result { let secret = try!(self.crypto.secret(old_password)); let result = SafeAccount { diff --git a/ethstore/src/bin/ethstore.rs b/ethstore/src/bin/ethstore.rs index 94823dc06..9d499723b 100644 --- a/ethstore/src/bin/ethstore.rs +++ b/ethstore/src/bin/ethstore.rs @@ -37,6 +37,7 @@ Usage: ethstore import-wallet [--dir DIR] ethstore remove
[--dir DIR] ethstore sign
[--dir DIR] + ethstore public
ethstore [-h | --help] Options: @@ -56,6 +57,7 @@ Commands: import-wallet Import presale wallet. remove Remove account. sign Sign message. + public Displays public key for an address. "#; #[derive(Debug, RustcDecodable)] @@ -67,6 +69,7 @@ struct Args { cmd_import_wallet: bool, cmd_remove: bool, cmd_sign: bool, + cmd_public: bool, arg_secret: String, arg_password: String, arg_old_pwd: String, @@ -103,7 +106,7 @@ fn key_dir(location: &str) -> Result, Error> { fn format_accounts(accounts: &[Address]) -> String { accounts.iter() .enumerate() - .map(|(i, a)| format!("{:2}: {}", i, a)) + .map(|(i, a)| format!("{:2}: 0x{:?}", i, a)) .collect::>() .join("\n") } @@ -128,7 +131,7 @@ fn execute(command: I) -> Result where I: IntoIterator(command: I) -> Result where I: IntoIterator(command: I) -> Result where I: IntoIterator Result { + let account = try!(self.get(account)); + account.public(password) + } + fn uuid(&self, address: &Address) -> Result { let account = try!(self.get(address)); Ok(account.id.into()) diff --git a/ethstore/src/secret_store.rs b/ethstore/src/secret_store.rs index aa79cb8b6..06f38922b 100644 --- a/ethstore/src/secret_store.rs +++ b/ethstore/src/secret_store.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use ethkey::{Address, Message, Signature, Secret}; +use ethkey::{Address, Message, Signature, Secret, Public}; use Error; use json::UUID; @@ -27,6 +27,7 @@ pub trait SecretStore: Send + Sync { fn sign(&self, account: &Address, password: &str, message: &Message) -> Result; fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error>; + fn public(&self, account: &Address, password: &str) -> Result; fn accounts(&self) -> Result, Error>; fn uuid(&self, account: &Address) -> Result; diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index df2d8cbd3..56124108a 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -14,23 +14,73 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use util::{Address, H256, U256, Uint}; +use util::{Address, H256, U256, Uint, Bytes}; use util::bytes::ToPretty; +use ethkey::Signature; use ethcore::miner::MinerService; use ethcore::client::MiningBlockChainClient; use ethcore::transaction::{Action, SignedTransaction, Transaction}; use ethcore::account_provider::AccountProvider; use jsonrpc_core::{Error, Value, to_value}; use v1::helpers::TransactionRequest; -use v1::types::{H256 as RpcH256, H520 as RpcH520}; +use v1::types::{H256 as RpcH256, H520 as RpcH520, Bytes as RpcBytes}; use v1::helpers::errors; +pub const DEFAULT_MAC: [u8; 2] = [0, 0]; + +pub fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result + where C: MiningBlockChainClient, M: MinerService { + let hash = RpcH256::from(signed_transaction.hash()); + + miner.import_own_transaction(client, signed_transaction) + .map_err(errors::from_transaction_error) + .map(|_| hash) +} + +fn signature(accounts: &AccountProvider, address: Address, password: Option, hash: H256) -> Result { + accounts.sign(address, password.clone(), hash).map_err(|e| match password { + Some(_) => errors::from_password_error(e), + None => errors::from_signing_error(e), + }) +} + +pub fn sign(accounts: &AccountProvider, address: Address, password: Option, hash: H256) -> Result { + signature(accounts, address, password, hash) + .map(RpcH520::from) + .map(to_value) +} + +pub fn decrypt(accounts: &AccountProvider, address: Address, password: Option, msg: Bytes) -> Result { + accounts.decrypt(address, password.clone(), &DEFAULT_MAC, &msg) + .map_err(|e| match password { + Some(_) => errors::from_password_error(e), + None => errors::from_signing_error(e), + }) + .map(RpcBytes::from) + .map(to_value) +} + +pub fn sign_and_dispatch(client: &C, miner: &M, accounts: &AccountProvider, request: TransactionRequest, password: Option) -> Result + where C: MiningBlockChainClient, M: MinerService { + + let address = request.from; + let signed_transaction = { + let t = prepare_transaction(client, miner, request); + let hash = t.hash(); + let signature = try!(signature(accounts, address, password, hash)); + t.with_signature(signature) + }; + + trace!(target: "miner", "send_transaction: dispatching tx: {}", ::rlp::encode(&signed_transaction).to_vec().pretty()); + dispatch_transaction(&*client, &*miner, signed_transaction).map(to_value) +} + fn prepare_transaction(client: &C, miner: &M, request: TransactionRequest) -> Transaction where C: MiningBlockChainClient, M: MinerService { Transaction { nonce: request.nonce .or_else(|| miner - .last_nonce(&request.from) - .map(|nonce| nonce + U256::one())) + .last_nonce(&request.from) + .map(|nonce| nonce + U256::one())) .unwrap_or_else(|| client.latest_nonce(&request.from)), action: request.to.map_or(Action::Create, Action::Call), @@ -41,52 +91,6 @@ fn prepare_transaction(client: &C, miner: &M, request: TransactionRequest) } } -pub fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result - where C: MiningBlockChainClient, M: MinerService { - let hash = RpcH256::from(signed_transaction.hash()); - - let import = miner.import_own_transaction(client, signed_transaction); - - import - .map_err(errors::from_transaction_error) - .map(|_| hash) -} - -pub fn signature_with_password(accounts: &AccountProvider, address: Address, hash: H256, pass: String) -> Result { - accounts.sign_with_password(address, pass, hash) - .map_err(errors::from_password_error) - .map(|hash| to_value(&RpcH520::from(hash))) -} - -pub fn unlock_sign_and_dispatch(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, password: String) -> Result - where C: MiningBlockChainClient, M: MinerService { - - let address = request.from; - let signed_transaction = { - let t = prepare_transaction(client, miner, request); - let hash = t.hash(); - let signature = try!(account_provider.sign_with_password(address, password, hash).map_err(errors::from_password_error)); - t.with_signature(signature) - }; - - trace!(target: "miner", "send_transaction: dispatching tx: {}", ::rlp::encode(&signed_transaction).to_vec().pretty()); - dispatch_transaction(&*client, &*miner, signed_transaction).map(to_value) -} - -pub fn sign_and_dispatch(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, address: Address) -> Result - where C: MiningBlockChainClient, M: MinerService { - - let signed_transaction = { - let t = prepare_transaction(client, miner, request); - let hash = t.hash(); - let signature = try!(account_provider.sign(address, hash).map_err(errors::from_signing_error)); - t.with_signature(signature) - }; - - trace!(target: "miner", "send_transaction: dispatching tx: {}", ::rlp::encode(&signed_transaction).to_vec().pretty()); - dispatch_transaction(&*client, &*miner, signed_transaction).map(to_value) -} - pub fn default_gas_price(client: &C, miner: &M) -> U256 where C: MiningBlockChainClient, M: MinerService { client .gas_price_statistics(100, 8) diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 0d7902897..c54cd9c34 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -43,6 +43,7 @@ mod codes { pub const REQUEST_REJECTED_LIMIT: i64 = -32041; pub const REQUEST_NOT_FOUND: i64 = -32042; pub const COMPILATION_ERROR: i64 = -32050; + pub const ENCRYPTION_ERROR: i64 = -32055; pub const FETCH_ERROR: i64 = -32060; } @@ -166,6 +167,14 @@ pub fn signer_disabled() -> Error { } } +pub fn encryption_error(error: T) -> Error { + Error { + code: ErrorCode::ServerError(codes::ENCRYPTION_ERROR), + message: "Encryption error.".into(), + data: Some(Value::String(format!("{:?}", error))), + } +} + pub fn from_fetch_error(error: FetchError) -> Error { Error { code: ErrorCode::ServerError(codes::FETCH_ERROR), diff --git a/rpc/src/v1/helpers/requests.rs b/rpc/src/v1/helpers/requests.rs index 6a30d80bb..5cb6108c1 100644 --- a/rpc/src/v1/helpers/requests.rs +++ b/rpc/src/v1/helpers/requests.rs @@ -103,4 +103,6 @@ pub enum ConfirmationPayload { Transaction(FilledTransactionRequest), /// Sign request Sign(Address, H256), + /// Decrypt request + Decrypt(Address, Bytes), } diff --git a/rpc/src/v1/impls/eth_signing.rs b/rpc/src/v1/impls/eth_signing.rs index 9290a9425..e8b81d0c8 100644 --- a/rpc/src/v1/impls/eth_signing.rs +++ b/rpc/src/v1/impls/eth_signing.rs @@ -24,9 +24,9 @@ use util::{U256, Address, H256, Mutex}; use transient_hashmap::TransientHashMap; use ethcore::account_provider::AccountProvider; use v1::helpers::{errors, SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationPayload, TransactionRequest as TRequest, FilledTransactionRequest as FilledRequest, SignerService}; -use v1::helpers::dispatch::{default_gas_price, sign_and_dispatch}; +use v1::helpers::dispatch::{default_gas_price, sign_and_dispatch, sign, decrypt}; use v1::traits::EthSigning; -use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, U256 as RpcU256, Bytes as RpcBytes}; +use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, U256 as RpcU256, Bytes as RpcBytes}; fn fill_optional_fields(request: TRequest, client: &C, miner: &M) -> FilledRequest where C: MiningBlockChainClient, M: MinerService { @@ -76,41 +76,59 @@ impl EthSigningQueueClient where C: MiningBlockChainClient, M: Miner Ok(()) } + fn add_to_queue(&self, sender: Address, when_unlocked: WhenUnlocked, payload: Payload) + -> Result where + WhenUnlocked: Fn(&AccountProvider) -> Result, + Payload: Fn() -> ConfirmationPayload, { + + let accounts = take_weak!(self.accounts); + if accounts.is_unlocked(sender) { + return when_unlocked(&accounts).map(DispatchResult::Value); + } + + take_weak!(self.signer).add_request(payload()) + .map(DispatchResult::Promise) + .map_err(|_| errors::request_rejected_limit()) + } + + fn handle_dispatch(&self, res: Result, ready: Ready) { + match res { + Ok(DispatchResult::Value(v)) => ready.ready(Ok(v)), + Ok(DispatchResult::Promise(promise)) => { + promise.wait_for_result(move |result| { + ready.ready(result.unwrap_or_else(|| Err(errors::request_rejected()))) + }) + }, + Err(e) => ready.ready(Err(e)), + } + } + fn dispatch_sign(&self, params: Params) -> Result { from_params::<(RpcH160, RpcH256)>(params).and_then(|(address, msg)| { let address: Address = address.into(); let msg: H256 = msg.into(); - let accounts = take_weak!(self.accounts); - if accounts.is_unlocked(address) { - return Ok(DispatchResult::Value(to_value(&accounts.sign(address, msg).ok().map_or_else(RpcH520::default, Into::into)))) - } - - let signer = take_weak!(self.signer); - signer.add_request(ConfirmationPayload::Sign(address, msg)) - .map(DispatchResult::Promise) - .map_err(|_| errors::request_rejected_limit()) + self.add_to_queue( + address, + |accounts| sign(accounts, address, None, msg.clone()), + || ConfirmationPayload::Sign(address, msg.clone()), + ) }) } fn dispatch_transaction(&self, params: Params) -> Result { - from_params::<(TransactionRequest, )>(params) - .and_then(|(request, )| { - let request: TRequest = request.into(); - let accounts = take_weak!(self.accounts); - let (client, miner) = (take_weak!(self.client), take_weak!(self.miner)); - - if accounts.is_unlocked(request.from) { - let sender = request.from; - return sign_and_dispatch(&*client, &*miner, request, &*accounts, sender).map(DispatchResult::Value); + from_params::<(TransactionRequest, )>(params).and_then(|(request, )| { + let request: TRequest = request.into(); + let (client, miner) = (take_weak!(self.client), take_weak!(self.miner)); + self.add_to_queue( + request.from, + |accounts| sign_and_dispatch(&*client, &*miner, accounts, request.clone(), None), + || { + let request = fill_optional_fields(request.clone(), &*client, &*miner); + ConfirmationPayload::Transaction(request) } - - let signer = take_weak!(self.signer); - let request = fill_optional_fields(request, &*client, &*miner); - signer.add_request(ConfirmationPayload::Transaction(request)) - .map(DispatchResult::Promise) - .map_err(|_| errors::request_rejected_limit()) - }) + ) + }) } } @@ -118,19 +136,6 @@ impl EthSigning for EthSigningQueueClient where C: MiningBlockChainClient + 'static, M: MinerService + 'static { - fn sign(&self, params: Params, ready: Ready) { - let res = self.active().and_then(|_| self.dispatch_sign(params)); - match res { - Ok(DispatchResult::Promise(promise)) => { - promise.wait_for_result(move |result| { - ready.ready(result.unwrap_or_else(|| Err(errors::request_rejected()))) - }) - }, - Ok(DispatchResult::Value(v)) => ready.ready(Ok(v)), - Err(e) => ready.ready(Err(e)), - } - } - fn post_sign(&self, params: Params) -> Result { try!(self.active()); self.dispatch_sign(params).map(|result| match result { @@ -143,19 +148,6 @@ impl EthSigning for EthSigningQueueClient }) } - fn send_transaction(&self, params: Params, ready: Ready) { - let res = self.active().and_then(|_| self.dispatch_transaction(params)); - match res { - Ok(DispatchResult::Promise(promise)) => { - promise.wait_for_result(move |result| { - ready.ready(result.unwrap_or_else(|| Err(errors::request_rejected()))) - }) - }, - Ok(DispatchResult::Value(v)) => ready.ready(Ok(v)), - Err(e) => ready.ready(Err(e)), - } - } - fn post_transaction(&self, params: Params) -> Result { try!(self.active()); self.dispatch_transaction(params).map(|result| match result { @@ -168,13 +160,6 @@ impl EthSigning for EthSigningQueueClient }) } - fn decrypt_message(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(RpcH160, RpcBytes)>(params).and_then(|(_account, _ciphertext)| { - Err(errors::unimplemented()) - }) - } - fn check_request(&self, params: Params) -> Result { try!(self.active()); let mut pending = self.pending.lock(); @@ -192,6 +177,32 @@ impl EthSigning for EthSigningQueueClient res }) } + + fn sign(&self, params: Params, ready: Ready) { + let res = self.active().and_then(|_| self.dispatch_sign(params)); + self.handle_dispatch(res, ready); + } + + fn send_transaction(&self, params: Params, ready: Ready) { + let res = self.active().and_then(|_| self.dispatch_transaction(params)); + self.handle_dispatch(res, ready); + } + + fn decrypt_message(&self, params: Params, ready: Ready) { + let res = self.active() + .and_then(|_| from_params::<(RpcH160, RpcBytes)>(params)) + .and_then(|(address, msg)| { + let address: Address = address.into(); + + self.add_to_queue( + address, + |accounts| decrypt(accounts, address, None, msg.clone().into()), + || ConfirmationPayload::Decrypt(address, msg.clone().into()) + ) + }); + + self.handle_dispatch(res, ready); + } } /// Implementation of functions that require signing when no trusted signer is used. @@ -232,9 +243,7 @@ impl EthSigning for EthSigningUnsafeClient where ready.ready(self.active() .and_then(|_| from_params::<(RpcH160, RpcH256)>(params)) .and_then(|(address, msg)| { - let address: Address = address.into(); - let msg: H256 = msg.into(); - Ok(to_value(&take_weak!(self.accounts).sign(address, msg).ok().map_or_else(RpcH520::default, Into::into))) + sign(&*take_weak!(self.accounts), address.into(), None, msg.into()) })) } @@ -242,18 +251,16 @@ impl EthSigning for EthSigningUnsafeClient where ready.ready(self.active() .and_then(|_| from_params::<(TransactionRequest, )>(params)) .and_then(|(request, )| { - let request: TRequest = request.into(); - let sender = request.from; - sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, &*take_weak!(self.accounts), sender) + sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), &*take_weak!(self.accounts), request.into(), None) })) } - fn decrypt_message(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(RpcH160, RpcBytes)>(params).and_then(|(address, ciphertext)| { - let s = try!(take_weak!(self.accounts).decrypt(address.into(), &[0; 0], &ciphertext.0).map_err(|_| Error::internal_error())); - Ok(to_value(RpcBytes::from(s))) - }) + fn decrypt_message(&self, params: Params, ready: Ready) { + ready.ready(self.active() + .and_then(|_| from_params::<(RpcH160, RpcBytes)>(params)) + .and_then(|(address, ciphertext)| { + decrypt(&*take_weak!(self.accounts), address.into(), None, ciphertext.0) + })) } fn post_sign(&self, _: Params) -> Result { diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs index 3b756342d..33123ddd5 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/ethcore.rs @@ -35,6 +35,8 @@ use jsonrpc_core::Error; use v1::traits::Ethcore; use v1::types::{Bytes, U256, H160, H256, H512, Peers, Transaction, RpcSettings}; use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings}; +use v1::helpers::dispatch::DEFAULT_MAC; +use v1::helpers::params::expect_no_params; use v1::helpers::auto_args::Ready; /// Ethcore implementation. @@ -265,8 +267,8 @@ impl Ethcore for EthcoreClient where fn encrypt_message(&self, key: H512, phrase: Bytes) -> Result { try!(self.active()); - ecies::encrypt(&key.into(), &[0; 0], &phrase.0) - .map_err(|_| Error::internal_error()) + ecies::encrypt(&key.into(), &DEFAULT_MAC, &phrase.0) + .map_err(errors::encryption_error) .map(Into::into) } diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 009f173b3..0830effd5 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -22,9 +22,9 @@ use jsonrpc_core::*; use ethkey::{Brain, Generator}; use v1::traits::Personal; use v1::types::{H160 as RpcH160, TransactionRequest}; -use v1::helpers::{errors, TransactionRequest as TRequest}; +use v1::helpers::errors; use v1::helpers::params::expect_no_params; -use v1::helpers::dispatch::unlock_sign_and_dispatch; +use v1::helpers::dispatch::sign_and_dispatch; use ethcore::account_provider::AccountProvider; use ethcore::client::MiningBlockChainClient; use ethcore::miner::MinerService; @@ -139,10 +139,13 @@ impl Personal for PersonalClient where C: MiningBl try!(self.active()); from_params::<(TransactionRequest, String)>(params) .and_then(|(request, password)| { - let request: TRequest = request.into(); - let accounts = take_weak!(self.accounts); - - unlock_sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, &*accounts, password) + sign_and_dispatch( + &*take_weak!(self.client), + &*take_weak!(self.miner), + &*take_weak!(self.accounts), + request.into(), + Some(password) + ) }) } diff --git a/rpc/src/v1/impls/personal_signer.rs b/rpc/src/v1/impls/personal_signer.rs index 441ed679b..b3a93736a 100644 --- a/rpc/src/v1/impls/personal_signer.rs +++ b/rpc/src/v1/impls/personal_signer.rs @@ -25,7 +25,7 @@ use v1::traits::PersonalSigner; use v1::types::{TransactionModification, ConfirmationRequest, U256}; use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload}; use v1::helpers::params::expect_no_params; -use v1::helpers::dispatch::{unlock_sign_and_dispatch, signature_with_password}; +use v1::helpers::dispatch::{sign_and_dispatch, sign, decrypt}; /// Transactions confirmation (personal) rpc implementation. pub struct SignerClient where C: MiningBlockChainClient, M: MinerService { @@ -87,12 +87,14 @@ impl PersonalSigner for SignerClient where C: Mini if let Some(gas_price) = modification.gas_price { request.gas_price = gas_price.into(); } - - unlock_sign_and_dispatch(&*client, &*miner, request.into(), &*accounts, pass) + sign_and_dispatch(&*client, &*miner, &*accounts, request.into(), Some(pass)) }, ConfirmationPayload::Sign(address, hash) => { - signature_with_password(&*accounts, address, hash, pass) - } + sign(&*accounts, address, Some(pass), hash) + }, + ConfirmationPayload::Decrypt(address, msg) => { + decrypt(&*accounts, address, Some(pass), msg) + }, }; if let Ok(ref response) = result { signer.request_confirmed(id, Ok(response.clone())); diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index ff531d57a..1dfe97968 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -33,9 +33,10 @@ use util::{U256, H256, Uint, Address}; use jsonrpc_core::IoHandler; use ethjson::blockchain::BlockChain; -use v1::types::U256 as NU256; -use v1::traits::eth::{Eth, EthSigning}; use v1::impls::{EthClient, EthSigningUnsafeClient}; +use v1::types::U256 as NU256; +use v1::traits::eth::Eth; +use v1::traits::eth_signing::EthSigning; use v1::tests::helpers::{TestSyncProvider, Config}; fn account_provider() -> Arc { diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index eb3fbaf6e..e41ca3231 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -264,7 +264,7 @@ fn rpc_eth_sign() { let account = tester.accounts_provider.new_account("abcd").unwrap(); tester.accounts_provider.unlock_account_permanently(account, "abcd".into()).unwrap(); let message = H256::from("0x0cc175b9c0f1b6a831c399e26977266192eb5ffee6ae2fec3ad71c777531578f"); - let signed = tester.accounts_provider.sign(account, message).unwrap(); + let signed = tester.accounts_provider.sign(account, None, message).unwrap(); let req = r#"{ "jsonrpc": "2.0", @@ -709,7 +709,7 @@ fn rpc_eth_send_transaction() { value: U256::from(0x9184e72au64), data: vec![] }; - let signature = tester.accounts_provider.sign(address, t.hash()).unwrap(); + let signature = tester.accounts_provider.sign(address, None, t.hash()).unwrap(); let t = t.with_signature(signature); let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; @@ -726,7 +726,7 @@ fn rpc_eth_send_transaction() { value: U256::from(0x9184e72au64), data: vec![] }; - let signature = tester.accounts_provider.sign(address, t.hash()).unwrap(); + let signature = tester.accounts_provider.sign(address, None, t.hash()).unwrap(); let t = t.with_signature(signature); let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; @@ -791,7 +791,7 @@ fn rpc_eth_send_raw_transaction() { value: U256::from(0x9184e72au64), data: vec![] }; - let signature = tester.accounts_provider.sign(address, t.hash()).unwrap(); + let signature = tester.accounts_provider.sign(address, None, t.hash()).unwrap(); let t = t.with_signature(signature); let rlp = ::rlp::encode(&t).to_vec().to_hex(); diff --git a/rpc/src/v1/tests/mocked/eth_signing.rs b/rpc/src/v1/tests/mocked/eth_signing.rs index 1bf901e5f..fc5800cd7 100644 --- a/rpc/src/v1/tests/mocked/eth_signing.rs +++ b/rpc/src/v1/tests/mocked/eth_signing.rs @@ -16,16 +16,20 @@ use std::str::FromStr; use std::sync::Arc; -use jsonrpc_core::{IoHandler, to_value}; +use jsonrpc_core::{IoHandler, to_value, Success}; use v1::impls::EthSigningQueueClient; -use v1::traits::EthSigning; +use v1::traits::{EthSigning, Ethcore}; use v1::helpers::{SignerService, SigningQueue}; -use v1::types::{H256 as RpcH256, H520 as RpcH520}; +use v1::types::{H256 as RpcH256, H520 as RpcH520, Bytes}; use v1::tests::helpers::TestMinerService; +use v1::tests::mocked::ethcore; + use util::{Address, FixedHash, Uint, U256, H256, H520}; use ethcore::account_provider::AccountProvider; use ethcore::client::TestBlockChainClient; use ethcore::transaction::{Transaction, Action}; +use ethstore::ethkey::{Generator, Random}; +use serde_json; struct EthSigningTester { pub signer: Arc, @@ -178,7 +182,7 @@ fn should_sign_if_account_is_unlocked() { let acc = tester.accounts.new_account("test").unwrap(); tester.accounts.unlock_account_permanently(acc, "test".into()).unwrap(); - let signature = tester.accounts.sign(acc, hash).unwrap(); + let signature = tester.accounts.sign(acc, None, hash).unwrap(); // when let request = r#"{ @@ -242,7 +246,7 @@ fn should_dispatch_transaction_if_account_is_unlock() { value: U256::from(0x9184e72au64), data: vec![] }; - let signature = tester.accounts.sign(acc, t.hash()).unwrap(); + let signature = tester.accounts.sign(acc, None, t.hash()).unwrap(); let t = t.with_signature(signature); // when @@ -263,3 +267,65 @@ fn should_dispatch_transaction_if_account_is_unlock() { // then assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); } + +#[test] +fn should_decrypt_message_if_account_is_unlocked() { + // given + let tester = eth_signing(); + let sync = ethcore::sync_provider(); + let net = ethcore::network_service(); + let ethcore_client = ethcore::ethcore_client(&tester.client, &tester.miner, &sync, &net); + tester.io.add_delegate(ethcore_client.to_delegate()); + let (address, public) = tester.accounts.new_account_and_public("test").unwrap(); + tester.accounts.unlock_account_permanently(address, "test".into()).unwrap(); + + + // First encrypt message + let request = format!("{}0x{:?}{}", + r#"{"jsonrpc": "2.0", "method": "ethcore_encryptMessage", "params":[""#, + public, + r#"", "0x01020304"], "id": 1}"# + ); + let encrypted: Success = serde_json::from_str(&tester.io.handle_request_sync(&request).unwrap()).unwrap(); + + // then call decrypt + let request = format!("{}{:?}{}{:?}{}", + r#"{"jsonrpc": "2.0", "method": "ethcore_decryptMessage", "params":["0x"#, + address, + r#"","#, + encrypted.result, + r#"], "id": 1}"# + ); + println!("Request: {:?}", request); + let response = r#"{"jsonrpc":"2.0","result":"0x01020304","id":1}"#; + + // then + assert_eq!(tester.io.handle_request_sync(&request), Some(response.into())); +} + +#[test] +fn should_add_decryption_to_the_queue() { + // given + let tester = eth_signing(); + let acc = Random.generate().unwrap(); + assert_eq!(tester.signer.requests().len(), 0); + + // when + let request = r#"{ + "jsonrpc": "2.0", + "method": "ethcore_decryptMessage", + "params": ["0x"#.to_owned() + &format!("{:?}", acc.address()) + r#"", + "0x012345"], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":"0x0102","id":1}"#; + + // then + let async_result = tester.io.handle_request(&request).unwrap(); + assert_eq!(tester.signer.requests().len(), 1); + // respond + tester.signer.request_confirmed(U256::from(1), Ok(to_value(Bytes(vec![0x1, 0x2])))); + assert!(async_result.on_result(move |res| { + assert_eq!(res, response.to_owned()); + })); +} diff --git a/rpc/src/v1/tests/mocked/ethcore.rs b/rpc/src/v1/tests/mocked/ethcore.rs index f09d84d5b..4cf23d46e 100644 --- a/rpc/src/v1/tests/mocked/ethcore.rs +++ b/rpc/src/v1/tests/mocked/ethcore.rs @@ -19,6 +19,7 @@ use util::log::RotatingLogger; use util::U256; use ethsync::ManageNetwork; use ethcore::client::{TestBlockChainClient}; +use ethstore::ethkey::{Generator, Random}; use jsonrpc_core::IoHandler; use v1::{Ethcore, EthcoreClient}; @@ -34,7 +35,7 @@ fn client_service() -> Arc { Arc::new(TestBlockChainClient::default()) } -fn sync_provider() -> Arc { +pub fn sync_provider() -> Arc { Arc::new(TestSyncProvider::new(Config { network_id: U256::from(3), num_peers: 120, @@ -56,13 +57,13 @@ fn settings() -> Arc { }) } -fn network_service() -> Arc { +pub fn network_service() -> Arc { Arc::new(TestManageNetwork) } -type TestEthcoreClient = EthcoreClient; +pub type TestEthcoreClient = EthcoreClient; -fn ethcore_client( +pub fn ethcore_client( client: &Arc, miner: &Arc, sync: &Arc, @@ -324,3 +325,17 @@ fn rpc_ethcore_pending_transactions() { assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } + +#[test] +fn rpc_ethcore_encrypt() { + let miner = miner_service(); + let client = client_service(); + let sync = sync_provider(); + let net = network_service(); + let io = IoHandler::new(); + io.add_delegate(ethcore_client(&client, &miner, &sync, &net).to_delegate()); + let key = format!("{:?}", Random.generate().unwrap().public()); + + let request = r#"{"jsonrpc": "2.0", "method": "ethcore_encryptMessage", "params":["0x"#.to_owned() + &key + r#"", "0x01"], "id": 1}"#; + assert!(io.handle_request_sync(&request).unwrap().contains("result"), "Should return success."); +} diff --git a/rpc/src/v1/tests/mocked/personal.rs b/rpc/src/v1/tests/mocked/personal.rs index aa4bf29c1..c3c3d2954 100644 --- a/rpc/src/v1/tests/mocked/personal.rs +++ b/rpc/src/v1/tests/mocked/personal.rs @@ -227,7 +227,7 @@ fn sign_and_send_transaction() { data: vec![] }; tester.accounts.unlock_account_temporarily(address, "password123".into()).unwrap(); - let signature = tester.accounts.sign(address, t.hash()).unwrap(); + let signature = tester.accounts.sign(address, None, t.hash()).unwrap(); let t = t.with_signature(signature); let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; @@ -245,7 +245,7 @@ fn sign_and_send_transaction() { data: vec![] }; tester.accounts.unlock_account_temporarily(address, "password123".into()).unwrap(); - let signature = tester.accounts.sign(address, t.hash()).unwrap(); + let signature = tester.accounts.sign(address, None, t.hash()).unwrap(); let t = t.with_signature(signature); let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; diff --git a/rpc/src/v1/tests/mocked/personal_signer.rs b/rpc/src/v1/tests/mocked/personal_signer.rs index 04ae829ee..650f16553 100644 --- a/rpc/src/v1/tests/mocked/personal_signer.rs +++ b/rpc/src/v1/tests/mocked/personal_signer.rs @@ -186,7 +186,7 @@ fn should_confirm_transaction_and_dispatch() { data: vec![] }; tester.accounts.unlock_account_temporarily(address, "test".into()).unwrap(); - let signature = tester.accounts.sign(address, t.hash()).unwrap(); + let signature = tester.accounts.sign(address, None, t.hash()).unwrap(); let t = t.with_signature(signature); assert_eq!(tester.signer.requests().len(), 1); diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index 62301e21f..7d9aa47f3 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -15,7 +15,6 @@ // along with Parity. If not, see . //! Eth rpc interface. -use std::sync::Arc; use jsonrpc_core::*; use v1::types::{Block, BlockNumber, Bytes, CallRequest, Filter, FilterChanges, Index}; @@ -198,46 +197,3 @@ build_rpc_trait! { fn uninstall_filter(&self, Index) -> Result; } } - -/// Signing methods implementation relying on unlocked accounts. -pub trait EthSigning: Sized + Send + Sync + 'static { - /// Signs the data with given address signature. - fn sign(&self, _: Params, _: Ready); - - /// Posts sign request asynchronously. - /// Will return a confirmation ID for later use with check_transaction. - fn post_sign(&self, _: Params) -> Result; - - /// Sends transaction; will block for 20s to try to return the - /// transaction hash. - /// If it cannot yet be signed, it will return a transaction ID for - /// later use with check_transaction. - fn send_transaction(&self, _: Params, _: Ready); - - /// Posts transaction asynchronously. - /// Will return a transaction ID for later use with check_transaction. - fn post_transaction(&self, _: Params) -> Result; - - /// Checks the progress of a previously posted request (transaction/sign). - /// Should be given a valid send_transaction ID. - /// Returns the transaction hash, the zero hash (not yet available), - /// or the signature, - /// or an error. - fn check_request(&self, _: Params) -> Result; - - /// Decrypt some ECIES-encrypted message. - /// First parameter is the address with which it is encrypted, second is the ciphertext. - fn decrypt_message(&self, _: Params) -> Result; - - /// Should be used to convert object to io delegate. - fn to_delegate(self) -> IoDelegate { - let mut delegate = IoDelegate::new(Arc::new(self)); - delegate.add_async_method("eth_sign", EthSigning::sign); - delegate.add_async_method("eth_sendTransaction", EthSigning::send_transaction); - delegate.add_method("eth_postSign", EthSigning::post_sign); - delegate.add_method("eth_postTransaction", EthSigning::post_transaction); - delegate.add_method("eth_checkRequest", EthSigning::check_request); - delegate.add_method("ethcore_decryptMessage", EthSigning::decrypt_message); - delegate - } -} diff --git a/rpc/src/v1/traits/eth_signing.rs b/rpc/src/v1/traits/eth_signing.rs new file mode 100644 index 000000000..1d6f6e501 --- /dev/null +++ b/rpc/src/v1/traits/eth_signing.rs @@ -0,0 +1,63 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Eth rpc interface. +use std::sync::Arc; +use jsonrpc_core::*; + +/// Signing methods implementation relying on unlocked accounts. +pub trait EthSigning: Sized + Send + Sync + 'static { + /// Signs the data with given address signature. + fn sign(&self, _: Params, _: Ready); + + /// Posts sign request asynchronously. + /// Will return a confirmation ID for later use with check_transaction. + fn post_sign(&self, _: Params) -> Result; + + /// Sends transaction; will block for 20s to try to return the + /// transaction hash. + /// If it cannot yet be signed, it will return a transaction ID for + /// later use with check_transaction. + fn send_transaction(&self, _: Params, _: Ready); + + /// Posts transaction asynchronously. + /// Will return a transaction ID for later use with check_transaction. + fn post_transaction(&self, _: Params) -> Result; + + /// Checks the progress of a previously posted request (transaction/sign). + /// Should be given a valid send_transaction ID. + /// Returns the transaction hash, the zero hash (not yet available), + /// or the signature, + /// or an error. + fn check_request(&self, _: Params) -> Result; + + /// Decrypt some ECIES-encrypted message. + /// First parameter is the address with which it is encrypted, second is the ciphertext. + fn decrypt_message(&self, _: Params, _: Ready); + + /// Should be used to convert object to io delegate. + fn to_delegate(self) -> IoDelegate { + let mut delegate = IoDelegate::new(Arc::new(self)); + delegate.add_async_method("eth_sign", EthSigning::sign); + delegate.add_async_method("eth_sendTransaction", EthSigning::send_transaction); + delegate.add_async_method("ethcore_decryptMessage", EthSigning::decrypt_message); + + delegate.add_method("eth_postSign", EthSigning::post_sign); + delegate.add_method("eth_postTransaction", EthSigning::post_transaction); + delegate.add_method("eth_checkRequest", EthSigning::check_request); + delegate + } +} diff --git a/rpc/src/v1/traits/mod.rs b/rpc/src/v1/traits/mod.rs index 3ca11b654..e804c5553 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -18,6 +18,7 @@ pub mod web3; pub mod eth; +pub mod eth_signing; pub mod net; pub mod personal; pub mod ethcore; @@ -26,7 +27,8 @@ pub mod traces; pub mod rpc; pub use self::web3::Web3; -pub use self::eth::{Eth, EthFilter, EthSigning}; +pub use self::eth::{Eth, EthFilter}; +pub use self::eth_signing::EthSigning; pub use self::net::Net; pub use self::personal::{Personal, PersonalSigner}; pub use self::ethcore::Ethcore; @@ -34,4 +36,3 @@ pub use self::ethcore_set::EthcoreSet; pub use self::traces::Traces; pub use self::rpc::Rpc; - diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index c074df443..c5ef4efa9 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -16,7 +16,7 @@ //! Types used in Confirmations queue (Trusted Signer) -use v1::types::{U256, TransactionRequest, H160, H256}; +use v1::types::{U256, TransactionRequest, H160, H256, Bytes}; use v1::helpers; @@ -47,6 +47,15 @@ pub struct SignRequest { pub hash: H256, } +/// Decrypt request +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] +pub struct DecryptRequest { + /// Address + pub address: H160, + /// Message to decrypt + pub msg: Bytes, +} + /// Confirmation payload, i.e. the thing to be confirmed #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] pub enum ConfirmationPayload { @@ -56,6 +65,9 @@ pub enum ConfirmationPayload { /// Signature #[serde(rename="sign")] Sign(SignRequest), + /// Decryption + #[serde(rename="decrypt")] + Decrypt(DecryptRequest), } impl From for ConfirmationPayload { @@ -66,6 +78,10 @@ impl From for ConfirmationPayload { address: address.into(), hash: hash.into(), }), + helpers::ConfirmationPayload::Decrypt(address, msg) => ConfirmationPayload::Decrypt(DecryptRequest { + address: address.into(), + msg: msg.into(), + }), } } } diff --git a/signer/Cargo.toml b/signer/Cargo.toml index 3d0e76896..43b6bd84a 100644 --- a/signer/Cargo.toml +++ b/signer/Cargo.toml @@ -20,11 +20,12 @@ ethcore-util = { path = "../util" } ethcore-io = { path = "../util/io" } ethcore-rpc = { path = "../rpc" } ethcore-devtools = { path = "../devtools" } +parity-dapps = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4", optional = true} parity-dapps-signer = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4", optional = true} clippy = { version = "0.0.90", optional = true} [features] dev = ["clippy"] -ui = ["parity-dapps-signer"] +ui = ["parity-dapps", "parity-dapps-signer"] use-precompiled-js = ["parity-dapps-signer/use-precompiled-js"] diff --git a/signer/src/lib.rs b/signer/src/lib.rs index abe84a03e..630cefb82 100644 --- a/signer/src/lib.rs +++ b/signer/src/lib.rs @@ -54,6 +54,8 @@ extern crate jsonrpc_core; extern crate ws; #[cfg(feature = "ui")] extern crate parity_dapps_signer as signer; +#[cfg(feature = "ui")] +extern crate parity_dapps as dapps; #[cfg(test)] extern crate ethcore_devtools as devtools; diff --git a/signer/src/ws_server/session.rs b/signer/src/ws_server/session.rs index afc6606d7..1e3a9a7d2 100644 --- a/signer/src/ws_server/session.rs +++ b/signer/src/ws_server/session.rs @@ -26,21 +26,39 @@ use util::{H256, Mutex, version}; #[cfg(feature = "ui")] mod signer { - use signer; + use signer::SignerApp; + use dapps::{self, WebApp}; - pub fn handle(req: &str) -> Option { - signer::handle(req) + #[derive(Default)] + pub struct Handler { + signer: SignerApp, + } + + impl Handler { + pub fn handle(&self, req: &str) -> Option<&dapps::File> { + let file = match req { + "" | "/" => "index.html", + path => &path[1..], + }; + self.signer.file(file) + } } } #[cfg(not(feature = "ui"))] mod signer { pub struct File { - pub content: String, - pub mime: String, + pub content: &'static str, + pub content_type: &'static str, } - pub fn handle(_req: &str) -> Option { - None + #[derive(Default)] + pub struct Handler { + } + + impl Handler { + pub fn handle(&self, _req: &str) -> Option<&File> { + None + } } } @@ -107,6 +125,7 @@ pub struct Session { self_origin: String, authcodes_path: PathBuf, handler: Arc, + file_handler: Arc, } impl ws::Handler for Session { @@ -152,12 +171,12 @@ impl ws::Handler for Session { } // Otherwise try to serve a page. - Ok(signer::handle(req.resource()) + Ok(self.file_handler.handle(req.resource()) .map_or_else( // return 404 not found || error(ErrorType::NotFound, "Not found", "Requested file was not found.", None), // or serve the file - |f| add_headers(ws::Response::ok(f.content.into()), &f.mime) + |f| add_headers(ws::Response::ok_raw(f.content.to_vec()), f.content_type) )) } @@ -181,6 +200,7 @@ pub struct Factory { skip_origin_validation: bool, self_origin: String, authcodes_path: PathBuf, + file_handler: Arc, } impl Factory { @@ -190,6 +210,7 @@ impl Factory { skip_origin_validation: skip_origin_validation, self_origin: self_origin, authcodes_path: authcodes_path, + file_handler: Arc::new(signer::Handler::default()), } } } @@ -204,6 +225,7 @@ impl ws::Factory for Factory { skip_origin_validation: self.skip_origin_validation, self_origin: self.self_origin.clone(), authcodes_path: self.authcodes_path.clone(), + file_handler: self.file_handler.clone(), } } }