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]
This commit is contained in:
Tomasz Drwięga 2016-10-15 14:44:08 +02:00 committed by Gav Wood
parent 85eeb3ea6e
commit 03c1559ead
27 changed files with 457 additions and 278 deletions

13
Cargo.lock generated
View File

@ -505,6 +505,7 @@ dependencies = [
"ethcore-util 1.4.0", "ethcore-util 1.4.0",
"jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "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)", "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]] [[package]]
name = "parity-dapps" name = "parity-dapps"
version = "1.4.0" 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 = [ dependencies = [
"aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1181,7 +1182,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-dapps-home" name = "parity-dapps-home"
version = "1.4.0" 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 = [ dependencies = [
"parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)",
] ]
@ -1189,7 +1190,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-dapps-signer" name = "parity-dapps-signer"
version = "1.4.0" 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 = [ dependencies = [
"parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)",
] ]
@ -1197,7 +1198,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-dapps-status" name = "parity-dapps-status"
version = "1.4.0" 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 = [ dependencies = [
"parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)",
] ]
@ -1205,7 +1206,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-dapps-wallet" name = "parity-dapps-wallet"
version = "1.4.0" 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 = [ dependencies = [
"parity-dapps 1.4.0 (git+https://github.com/ethcore/parity-ui.git)", "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]] [[package]]
name = "ws" name = "ws"
version = "0.5.2" 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 = [ dependencies = [
"bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)", "bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)",
"httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -23,7 +23,7 @@ use std::time::{Instant, Duration};
use util::{Mutex, RwLock}; use util::{Mutex, RwLock};
use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore}; use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore};
use ethstore::dir::{KeyDirectory}; 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; use ethjson::misc::AccountMeta;
pub use ethstore::ethkey::Signature; pub use ethstore::ethkey::Signature;
@ -182,9 +182,16 @@ impl AccountProvider {
/// Creates new random account. /// Creates new random account.
pub fn new_account(&self, password: &str) -> Result<Address, Error> { pub fn new_account(&self, password: &str) -> Result<Address, Error> {
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)); let address = try!(self.sstore.insert_account(secret, password));
Ok(address) Ok((address, public))
} }
/// Inserts new account into underlying store. /// Inserts new account into underlying store.
@ -280,6 +287,21 @@ impl AccountProvider {
Ok(()) Ok(())
} }
fn password(&self, account: &Address) -> Result<String, Error> {
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. /// Unlocks account permanently.
pub fn unlock_account_permanently(&self, account: Address, password: String) -> Result<(), Error> { pub fn unlock_account_permanently(&self, account: Address, password: String) -> Result<(), Error> {
self.unlock_account(account, password, Unlock::Perm) self.unlock_account(account, password, Unlock::Perm)
@ -301,51 +323,16 @@ impl AccountProvider {
unlocked.get(&account).is_some() unlocked.get(&account).is_some()
} }
/// Signs the message. Account must be unlocked. /// Signs the message. If password is not provided the account must be unlocked.
pub fn sign(&self, account: Address, message: Message) -> Result<Signature, Error> { pub fn sign(&self, account: Address, password: Option<String>, message: Message) -> Result<Signature, Error> {
let data = { let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account)));
let mut unlocked = self.unlocked.lock(); Ok(try!(self.sstore.sign(&account, &password, &message)))
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)
} }
/// Decrypts a message. Account must be unlocked. /// Decrypts a message. If password is not provided the account must be unlocked.
pub fn decrypt(&self, account: Address, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> { pub fn decrypt(&self, account: Address, password: Option<String>, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
let data = { let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account)));
let mut unlocked = self.unlocked.lock(); Ok(try!(self.sstore.decrypt(&account, &password, shared_mac, message)))
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<Signature, Error> {
let signature = try!(self.sstore.sign(&account, &password, &message));
Ok(signature)
} }
/// Returns the underlying `SecretStore` reference if one exists. /// 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.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(), "test1".into()).is_err());
assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).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(), None, Default::default()).is_ok());
assert!(ap.sign(kp.address(), Default::default()).is_err()); assert!(ap.sign(kp.address(), None, Default::default()).is_err());
} }
#[test] #[test]
@ -397,11 +384,11 @@ mod tests {
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); 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(), "test1".into()).is_err());
assert!(ap.unlock_account_permanently(kp.address(), "test".into()).is_ok()); 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(), None, 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.unlock_account_temporarily(kp.address(), "test".into()).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(), None, Default::default()).is_ok());
assert!(ap.sign(kp.address(), Default::default()).is_ok()); assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
} }
#[test] #[test]
@ -411,8 +398,8 @@ mod tests {
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); 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(), "test1".into(), 2000).is_err());
assert!(ap.unlock_account_timed(kp.address(), "test".into(), 2000).is_ok()); 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)); ::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());
} }
} }

View File

@ -112,7 +112,7 @@ impl Engine for BasicAuthority {
let header = block.header(); let header = block.header();
let message = header.bare_hash(); let message = header.bare_hash();
// account should be pernamently unlocked, otherwise sealing will fail // 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()]); return Some(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]);
} else { } else {
trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable");

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use ethkey::{KeyPair, sign, Address, Secret, Signature, Message}; use ethkey::{KeyPair, sign, Address, Secret, Signature, Message, Public};
use {json, Error, crypto}; use {json, Error, crypto};
use crypto::Keccak256; use crypto::Keccak256;
use random::Random; use random::Random;
@ -180,6 +180,11 @@ impl SafeAccount {
crypto::ecies::decrypt(&secret, shared_mac, message).map_err(From::from) crypto::ecies::decrypt(&secret, shared_mac, message).map_err(From::from)
} }
pub fn public(&self, password: &str) -> Result<Public, Error> {
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<Self, Error> { pub fn change_password(&self, old_password: &str, new_password: &str, iterations: u32) -> Result<Self, Error> {
let secret = try!(self.crypto.secret(old_password)); let secret = try!(self.crypto.secret(old_password));
let result = SafeAccount { let result = SafeAccount {

View File

@ -37,6 +37,7 @@ Usage:
ethstore import-wallet <path> <password> [--dir DIR] ethstore import-wallet <path> <password> [--dir DIR]
ethstore remove <address> <password> [--dir DIR] ethstore remove <address> <password> [--dir DIR]
ethstore sign <address> <password> <message> [--dir DIR] ethstore sign <address> <password> <message> [--dir DIR]
ethstore public <address> <password>
ethstore [-h | --help] ethstore [-h | --help]
Options: Options:
@ -56,6 +57,7 @@ Commands:
import-wallet Import presale wallet. import-wallet Import presale wallet.
remove Remove account. remove Remove account.
sign Sign message. sign Sign message.
public Displays public key for an address.
"#; "#;
#[derive(Debug, RustcDecodable)] #[derive(Debug, RustcDecodable)]
@ -67,6 +69,7 @@ struct Args {
cmd_import_wallet: bool, cmd_import_wallet: bool,
cmd_remove: bool, cmd_remove: bool,
cmd_sign: bool, cmd_sign: bool,
cmd_public: bool,
arg_secret: String, arg_secret: String,
arg_password: String, arg_password: String,
arg_old_pwd: String, arg_old_pwd: String,
@ -103,7 +106,7 @@ fn key_dir(location: &str) -> Result<Box<KeyDirectory>, Error> {
fn format_accounts(accounts: &[Address]) -> String { fn format_accounts(accounts: &[Address]) -> String {
accounts.iter() accounts.iter()
.enumerate() .enumerate()
.map(|(i, a)| format!("{:2}: {}", i, a)) .map(|(i, a)| format!("{:2}: 0x{:?}", i, a))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n") .join("\n")
} }
@ -128,7 +131,7 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
let secret = try!(args.arg_secret.parse().map_err(|_| Error::InvalidSecret)); let secret = try!(args.arg_secret.parse().map_err(|_| Error::InvalidSecret));
let password = try!(load_password(&args.arg_password)); let password = try!(load_password(&args.arg_password));
let address = try!(store.insert_account(secret, &password)); let address = try!(store.insert_account(secret, &password));
Ok(format!("{}", address)) Ok(format!("0x{:?}", address))
} else if args.cmd_change_pwd { } else if args.cmd_change_pwd {
let address = try!(args.arg_address.parse().map_err(|_| Error::InvalidAccount)); let address = try!(args.arg_address.parse().map_err(|_| Error::InvalidAccount));
let old_pwd = try!(load_password(&args.arg_old_pwd)); let old_pwd = try!(load_password(&args.arg_old_pwd));
@ -148,7 +151,7 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
let password = try!(load_password(&args.arg_password)); let password = try!(load_password(&args.arg_password));
let kp = try!(wallet.decrypt(&password)); let kp = try!(wallet.decrypt(&password));
let address = try!(store.insert_account(kp.secret().clone(), &password)); let address = try!(store.insert_account(kp.secret().clone(), &password));
Ok(format!("{}", address)) Ok(format!("0x{:?}", address))
} else if args.cmd_remove { } else if args.cmd_remove {
let address = try!(args.arg_address.parse().map_err(|_| Error::InvalidAccount)); let address = try!(args.arg_address.parse().map_err(|_| Error::InvalidAccount));
let password = try!(load_password(&args.arg_password)); let password = try!(load_password(&args.arg_password));
@ -159,7 +162,12 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
let message = try!(args.arg_message.parse().map_err(|_| Error::InvalidMessage)); let message = try!(args.arg_message.parse().map_err(|_| Error::InvalidMessage));
let password = try!(load_password(&args.arg_password)); let password = try!(load_password(&args.arg_password));
let signature = try!(store.sign(&address, &password, &message)); let signature = try!(store.sign(&address, &password, &message));
Ok(format!("{}", signature)) Ok(format!("0x{:?}", signature))
} else if args.cmd_public {
let address = try!(args.arg_address.parse().map_err(|_| Error::InvalidAccount));
let password = try!(load_password(&args.arg_password));
let public = try!(store.public(&address, &password));
Ok(format!("0x{:?}", public))
} else { } else {
Ok(format!("{}", USAGE)) Ok(format!("{}", USAGE))
} }

View File

@ -20,7 +20,7 @@ use std::mem;
use ethkey::KeyPair; use ethkey::KeyPair;
use crypto::KEY_ITERATIONS; use crypto::KEY_ITERATIONS;
use random::Random; use random::Random;
use ethkey::{Signature, Address, Message, Secret}; use ethkey::{Signature, Address, Message, Secret, Public};
use dir::KeyDirectory; use dir::KeyDirectory;
use account::SafeAccount; use account::SafeAccount;
use {Error, SecretStore}; use {Error, SecretStore};
@ -149,6 +149,11 @@ impl SecretStore for EthStore {
account.decrypt(password, shared_mac, message) account.decrypt(password, shared_mac, message)
} }
fn public(&self, account: &Address, password: &str) -> Result<Public, Error> {
let account = try!(self.get(account));
account.public(password)
}
fn uuid(&self, address: &Address) -> Result<UUID, Error> { fn uuid(&self, address: &Address) -> Result<UUID, Error> {
let account = try!(self.get(address)); let account = try!(self.get(address));
Ok(account.id.into()) Ok(account.id.into())

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use ethkey::{Address, Message, Signature, Secret}; use ethkey::{Address, Message, Signature, Secret, Public};
use Error; use Error;
use json::UUID; use json::UUID;
@ -27,6 +27,7 @@ pub trait SecretStore: Send + Sync {
fn sign(&self, account: &Address, password: &str, message: &Message) -> Result<Signature, Error>; fn sign(&self, account: &Address, password: &str, message: &Message) -> Result<Signature, Error>;
fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error>; fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error>;
fn public(&self, account: &Address, password: &str) -> Result<Public, Error>;
fn accounts(&self) -> Result<Vec<Address>, Error>; fn accounts(&self) -> Result<Vec<Address>, Error>;
fn uuid(&self, account: &Address) -> Result<UUID, Error>; fn uuid(&self, account: &Address) -> Result<UUID, Error>;

View File

@ -14,17 +14,67 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use util::{Address, H256, U256, Uint}; use util::{Address, H256, U256, Uint, Bytes};
use util::bytes::ToPretty; use util::bytes::ToPretty;
use ethkey::Signature;
use ethcore::miner::MinerService; use ethcore::miner::MinerService;
use ethcore::client::MiningBlockChainClient; use ethcore::client::MiningBlockChainClient;
use ethcore::transaction::{Action, SignedTransaction, Transaction}; use ethcore::transaction::{Action, SignedTransaction, Transaction};
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
use jsonrpc_core::{Error, Value, to_value}; use jsonrpc_core::{Error, Value, to_value};
use v1::helpers::TransactionRequest; 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; use v1::helpers::errors;
pub const DEFAULT_MAC: [u8; 2] = [0, 0];
pub fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result<RpcH256, Error>
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<String>, hash: H256) -> Result<Signature, Error> {
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<String>, hash: H256) -> Result<Value, Error> {
signature(accounts, address, password, hash)
.map(RpcH520::from)
.map(to_value)
}
pub fn decrypt(accounts: &AccountProvider, address: Address, password: Option<String>, msg: Bytes) -> Result<Value, Error> {
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<C, M>(client: &C, miner: &M, accounts: &AccountProvider, request: TransactionRequest, password: Option<String>) -> Result<Value, Error>
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<C, M>(client: &C, miner: &M, request: TransactionRequest) -> Transaction where C: MiningBlockChainClient, M: MinerService { fn prepare_transaction<C, M>(client: &C, miner: &M, request: TransactionRequest) -> Transaction where C: MiningBlockChainClient, M: MinerService {
Transaction { Transaction {
nonce: request.nonce nonce: request.nonce
@ -41,52 +91,6 @@ fn prepare_transaction<C, M>(client: &C, miner: &M, request: TransactionRequest)
} }
} }
pub fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result<RpcH256, Error>
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<Value, Error> {
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<C, M>(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, password: String) -> Result<Value, Error>
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<C, M>(client: &C, miner: &M, request: TransactionRequest, account_provider: &AccountProvider, address: Address) -> Result<Value, Error>
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<C, M>(client: &C, miner: &M) -> U256 where C: MiningBlockChainClient, M: MinerService { pub fn default_gas_price<C, M>(client: &C, miner: &M) -> U256 where C: MiningBlockChainClient, M: MinerService {
client client
.gas_price_statistics(100, 8) .gas_price_statistics(100, 8)

View File

@ -43,6 +43,7 @@ mod codes {
pub const REQUEST_REJECTED_LIMIT: i64 = -32041; pub const REQUEST_REJECTED_LIMIT: i64 = -32041;
pub const REQUEST_NOT_FOUND: i64 = -32042; pub const REQUEST_NOT_FOUND: i64 = -32042;
pub const COMPILATION_ERROR: i64 = -32050; pub const COMPILATION_ERROR: i64 = -32050;
pub const ENCRYPTION_ERROR: i64 = -32055;
pub const FETCH_ERROR: i64 = -32060; pub const FETCH_ERROR: i64 = -32060;
} }
@ -166,6 +167,14 @@ pub fn signer_disabled() -> Error {
} }
} }
pub fn encryption_error<T: fmt::Debug>(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 { pub fn from_fetch_error(error: FetchError) -> Error {
Error { Error {
code: ErrorCode::ServerError(codes::FETCH_ERROR), code: ErrorCode::ServerError(codes::FETCH_ERROR),

View File

@ -103,4 +103,6 @@ pub enum ConfirmationPayload {
Transaction(FilledTransactionRequest), Transaction(FilledTransactionRequest),
/// Sign request /// Sign request
Sign(Address, H256), Sign(Address, H256),
/// Decrypt request
Decrypt(Address, Bytes),
} }

View File

@ -24,9 +24,9 @@ use util::{U256, Address, H256, Mutex};
use transient_hashmap::TransientHashMap; use transient_hashmap::TransientHashMap;
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
use v1::helpers::{errors, SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationPayload, TransactionRequest as TRequest, FilledTransactionRequest as FilledRequest, SignerService}; 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::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<C, M>(request: TRequest, client: &C, miner: &M) -> FilledRequest fn fill_optional_fields<C, M>(request: TRequest, client: &C, miner: &M) -> FilledRequest
where C: MiningBlockChainClient, M: MinerService { where C: MiningBlockChainClient, M: MinerService {
@ -76,40 +76,58 @@ impl<C, M> EthSigningQueueClient<C, M> where C: MiningBlockChainClient, M: Miner
Ok(()) Ok(())
} }
fn add_to_queue<WhenUnlocked, Payload>(&self, sender: Address, when_unlocked: WhenUnlocked, payload: Payload)
-> Result<DispatchResult, Error> where
WhenUnlocked: Fn(&AccountProvider) -> Result<Value, Error>,
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<DispatchResult, Error>, 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<DispatchResult, Error> { fn dispatch_sign(&self, params: Params) -> Result<DispatchResult, Error> {
from_params::<(RpcH160, RpcH256)>(params).and_then(|(address, msg)| { from_params::<(RpcH160, RpcH256)>(params).and_then(|(address, msg)| {
let address: Address = address.into(); let address: Address = address.into();
let msg: H256 = msg.into(); let msg: H256 = msg.into();
let accounts = take_weak!(self.accounts); self.add_to_queue(
if accounts.is_unlocked(address) { address,
return Ok(DispatchResult::Value(to_value(&accounts.sign(address, msg).ok().map_or_else(RpcH520::default, Into::into)))) |accounts| sign(accounts, address, None, msg.clone()),
} || ConfirmationPayload::Sign(address, msg.clone()),
)
let signer = take_weak!(self.signer);
signer.add_request(ConfirmationPayload::Sign(address, msg))
.map(DispatchResult::Promise)
.map_err(|_| errors::request_rejected_limit())
}) })
} }
fn dispatch_transaction(&self, params: Params) -> Result<DispatchResult, Error> { fn dispatch_transaction(&self, params: Params) -> Result<DispatchResult, Error> {
from_params::<(TransactionRequest, )>(params) from_params::<(TransactionRequest, )>(params).and_then(|(request, )| {
.and_then(|(request, )| {
let request: TRequest = request.into(); let request: TRequest = request.into();
let accounts = take_weak!(self.accounts);
let (client, miner) = (take_weak!(self.client), take_weak!(self.miner)); let (client, miner) = (take_weak!(self.client), take_weak!(self.miner));
self.add_to_queue(
if accounts.is_unlocked(request.from) { request.from,
let sender = request.from; |accounts| sign_and_dispatch(&*client, &*miner, accounts, request.clone(), None),
return sign_and_dispatch(&*client, &*miner, request, &*accounts, sender).map(DispatchResult::Value); || {
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<C, M> EthSigning for EthSigningQueueClient<C, M>
where C: MiningBlockChainClient + 'static, M: MinerService + 'static 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<Value, Error> { fn post_sign(&self, params: Params) -> Result<Value, Error> {
try!(self.active()); try!(self.active());
self.dispatch_sign(params).map(|result| match result { self.dispatch_sign(params).map(|result| match result {
@ -143,19 +148,6 @@ impl<C, M> EthSigning for EthSigningQueueClient<C, M>
}) })
} }
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<Value, Error> { fn post_transaction(&self, params: Params) -> Result<Value, Error> {
try!(self.active()); try!(self.active());
self.dispatch_transaction(params).map(|result| match result { self.dispatch_transaction(params).map(|result| match result {
@ -168,13 +160,6 @@ impl<C, M> EthSigning for EthSigningQueueClient<C, M>
}) })
} }
fn decrypt_message(&self, params: Params) -> Result<Value, Error> {
try!(self.active());
from_params::<(RpcH160, RpcBytes)>(params).and_then(|(_account, _ciphertext)| {
Err(errors::unimplemented())
})
}
fn check_request(&self, params: Params) -> Result<Value, Error> { fn check_request(&self, params: Params) -> Result<Value, Error> {
try!(self.active()); try!(self.active());
let mut pending = self.pending.lock(); let mut pending = self.pending.lock();
@ -192,6 +177,32 @@ impl<C, M> EthSigning for EthSigningQueueClient<C, M>
res 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. /// Implementation of functions that require signing when no trusted signer is used.
@ -232,9 +243,7 @@ impl<C, M> EthSigning for EthSigningUnsafeClient<C, M> where
ready.ready(self.active() ready.ready(self.active()
.and_then(|_| from_params::<(RpcH160, RpcH256)>(params)) .and_then(|_| from_params::<(RpcH160, RpcH256)>(params))
.and_then(|(address, msg)| { .and_then(|(address, msg)| {
let address: Address = address.into(); sign(&*take_weak!(self.accounts), address.into(), None, msg.into())
let msg: H256 = msg.into();
Ok(to_value(&take_weak!(self.accounts).sign(address, msg).ok().map_or_else(RpcH520::default, Into::into)))
})) }))
} }
@ -242,18 +251,16 @@ impl<C, M> EthSigning for EthSigningUnsafeClient<C, M> where
ready.ready(self.active() ready.ready(self.active()
.and_then(|_| from_params::<(TransactionRequest, )>(params)) .and_then(|_| from_params::<(TransactionRequest, )>(params))
.and_then(|(request, )| { .and_then(|(request, )| {
let request: TRequest = request.into(); sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), &*take_weak!(self.accounts), request.into(), None)
let sender = request.from;
sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, &*take_weak!(self.accounts), sender)
})) }))
} }
fn decrypt_message(&self, params: Params) -> Result<Value, Error> { fn decrypt_message(&self, params: Params, ready: Ready) {
try!(self.active()); ready.ready(self.active()
from_params::<(RpcH160, RpcBytes)>(params).and_then(|(address, ciphertext)| { .and_then(|_| from_params::<(RpcH160, RpcBytes)>(params))
let s = try!(take_weak!(self.accounts).decrypt(address.into(), &[0; 0], &ciphertext.0).map_err(|_| Error::internal_error())); .and_then(|(address, ciphertext)| {
Ok(to_value(RpcBytes::from(s))) decrypt(&*take_weak!(self.accounts), address.into(), None, ciphertext.0)
}) }))
} }
fn post_sign(&self, _: Params) -> Result<Value, Error> { fn post_sign(&self, _: Params) -> Result<Value, Error> {

View File

@ -35,6 +35,8 @@ use jsonrpc_core::Error;
use v1::traits::Ethcore; use v1::traits::Ethcore;
use v1::types::{Bytes, U256, H160, H256, H512, Peers, Transaction, RpcSettings}; use v1::types::{Bytes, U256, H160, H256, H512, Peers, Transaction, RpcSettings};
use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings}; 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; use v1::helpers::auto_args::Ready;
/// Ethcore implementation. /// Ethcore implementation.
@ -265,8 +267,8 @@ impl<C, M, S: ?Sized, F> Ethcore for EthcoreClient<C, M, S, F> where
fn encrypt_message(&self, key: H512, phrase: Bytes) -> Result<Bytes, Error> { fn encrypt_message(&self, key: H512, phrase: Bytes) -> Result<Bytes, Error> {
try!(self.active()); try!(self.active());
ecies::encrypt(&key.into(), &[0; 0], &phrase.0) ecies::encrypt(&key.into(), &DEFAULT_MAC, &phrase.0)
.map_err(|_| Error::internal_error()) .map_err(errors::encryption_error)
.map(Into::into) .map(Into::into)
} }

View File

@ -22,9 +22,9 @@ use jsonrpc_core::*;
use ethkey::{Brain, Generator}; use ethkey::{Brain, Generator};
use v1::traits::Personal; use v1::traits::Personal;
use v1::types::{H160 as RpcH160, TransactionRequest}; 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::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::account_provider::AccountProvider;
use ethcore::client::MiningBlockChainClient; use ethcore::client::MiningBlockChainClient;
use ethcore::miner::MinerService; use ethcore::miner::MinerService;
@ -139,10 +139,13 @@ impl<C: 'static, M: 'static> Personal for PersonalClient<C, M> where C: MiningBl
try!(self.active()); try!(self.active());
from_params::<(TransactionRequest, String)>(params) from_params::<(TransactionRequest, String)>(params)
.and_then(|(request, password)| { .and_then(|(request, password)| {
let request: TRequest = request.into(); sign_and_dispatch(
let accounts = take_weak!(self.accounts); &*take_weak!(self.client),
&*take_weak!(self.miner),
unlock_sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), request, &*accounts, password) &*take_weak!(self.accounts),
request.into(),
Some(password)
)
}) })
} }

View File

@ -25,7 +25,7 @@ use v1::traits::PersonalSigner;
use v1::types::{TransactionModification, ConfirmationRequest, U256}; use v1::types::{TransactionModification, ConfirmationRequest, U256};
use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload}; use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload};
use v1::helpers::params::expect_no_params; 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. /// Transactions confirmation (personal) rpc implementation.
pub struct SignerClient<C, M> where C: MiningBlockChainClient, M: MinerService { pub struct SignerClient<C, M> where C: MiningBlockChainClient, M: MinerService {
@ -87,12 +87,14 @@ impl<C: 'static, M: 'static> PersonalSigner for SignerClient<C, M> where C: Mini
if let Some(gas_price) = modification.gas_price { if let Some(gas_price) = modification.gas_price {
request.gas_price = gas_price.into(); request.gas_price = gas_price.into();
} }
sign_and_dispatch(&*client, &*miner, &*accounts, request.into(), Some(pass))
unlock_sign_and_dispatch(&*client, &*miner, request.into(), &*accounts, pass)
}, },
ConfirmationPayload::Sign(address, hash) => { 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 { if let Ok(ref response) = result {
signer.request_confirmed(id, Ok(response.clone())); signer.request_confirmed(id, Ok(response.clone()));

View File

@ -33,9 +33,10 @@ use util::{U256, H256, Uint, Address};
use jsonrpc_core::IoHandler; use jsonrpc_core::IoHandler;
use ethjson::blockchain::BlockChain; use ethjson::blockchain::BlockChain;
use v1::types::U256 as NU256;
use v1::traits::eth::{Eth, EthSigning};
use v1::impls::{EthClient, EthSigningUnsafeClient}; 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}; use v1::tests::helpers::{TestSyncProvider, Config};
fn account_provider() -> Arc<AccountProvider> { fn account_provider() -> Arc<AccountProvider> {

View File

@ -264,7 +264,7 @@ fn rpc_eth_sign() {
let account = tester.accounts_provider.new_account("abcd").unwrap(); let account = tester.accounts_provider.new_account("abcd").unwrap();
tester.accounts_provider.unlock_account_permanently(account, "abcd".into()).unwrap(); tester.accounts_provider.unlock_account_permanently(account, "abcd".into()).unwrap();
let message = H256::from("0x0cc175b9c0f1b6a831c399e26977266192eb5ffee6ae2fec3ad71c777531578f"); 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#"{ let req = r#"{
"jsonrpc": "2.0", "jsonrpc": "2.0",
@ -709,7 +709,7 @@ fn rpc_eth_send_transaction() {
value: U256::from(0x9184e72au64), value: U256::from(0x9184e72au64),
data: vec![] 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 t = t.with_signature(signature);
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; 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), value: U256::from(0x9184e72au64),
data: vec![] 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 t = t.with_signature(signature);
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; 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), value: U256::from(0x9184e72au64),
data: vec![] 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 t = t.with_signature(signature);
let rlp = ::rlp::encode(&t).to_vec().to_hex(); let rlp = ::rlp::encode(&t).to_vec().to_hex();

View File

@ -16,16 +16,20 @@
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use jsonrpc_core::{IoHandler, to_value}; use jsonrpc_core::{IoHandler, to_value, Success};
use v1::impls::EthSigningQueueClient; use v1::impls::EthSigningQueueClient;
use v1::traits::EthSigning; use v1::traits::{EthSigning, Ethcore};
use v1::helpers::{SignerService, SigningQueue}; 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::helpers::TestMinerService;
use v1::tests::mocked::ethcore;
use util::{Address, FixedHash, Uint, U256, H256, H520}; use util::{Address, FixedHash, Uint, U256, H256, H520};
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
use ethcore::client::TestBlockChainClient; use ethcore::client::TestBlockChainClient;
use ethcore::transaction::{Transaction, Action}; use ethcore::transaction::{Transaction, Action};
use ethstore::ethkey::{Generator, Random};
use serde_json;
struct EthSigningTester { struct EthSigningTester {
pub signer: Arc<SignerService>, pub signer: Arc<SignerService>,
@ -178,7 +182,7 @@ fn should_sign_if_account_is_unlocked() {
let acc = tester.accounts.new_account("test").unwrap(); let acc = tester.accounts.new_account("test").unwrap();
tester.accounts.unlock_account_permanently(acc, "test".into()).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 // when
let request = r#"{ let request = r#"{
@ -242,7 +246,7 @@ fn should_dispatch_transaction_if_account_is_unlock() {
value: U256::from(0x9184e72au64), value: U256::from(0x9184e72au64),
data: vec![] 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); let t = t.with_signature(signature);
// when // when
@ -263,3 +267,65 @@ fn should_dispatch_transaction_if_account_is_unlock() {
// then // then
assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); 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());
}));
}

View File

@ -19,6 +19,7 @@ use util::log::RotatingLogger;
use util::U256; use util::U256;
use ethsync::ManageNetwork; use ethsync::ManageNetwork;
use ethcore::client::{TestBlockChainClient}; use ethcore::client::{TestBlockChainClient};
use ethstore::ethkey::{Generator, Random};
use jsonrpc_core::IoHandler; use jsonrpc_core::IoHandler;
use v1::{Ethcore, EthcoreClient}; use v1::{Ethcore, EthcoreClient};
@ -34,7 +35,7 @@ fn client_service() -> Arc<TestBlockChainClient> {
Arc::new(TestBlockChainClient::default()) Arc::new(TestBlockChainClient::default())
} }
fn sync_provider() -> Arc<TestSyncProvider> { pub fn sync_provider() -> Arc<TestSyncProvider> {
Arc::new(TestSyncProvider::new(Config { Arc::new(TestSyncProvider::new(Config {
network_id: U256::from(3), network_id: U256::from(3),
num_peers: 120, num_peers: 120,
@ -56,13 +57,13 @@ fn settings() -> Arc<NetworkSettings> {
}) })
} }
fn network_service() -> Arc<ManageNetwork> { pub fn network_service() -> Arc<ManageNetwork> {
Arc::new(TestManageNetwork) Arc::new(TestManageNetwork)
} }
type TestEthcoreClient = EthcoreClient<TestBlockChainClient, TestMinerService, TestSyncProvider, TestFetch>; pub type TestEthcoreClient = EthcoreClient<TestBlockChainClient, TestMinerService, TestSyncProvider, TestFetch>;
fn ethcore_client( pub fn ethcore_client(
client: &Arc<TestBlockChainClient>, client: &Arc<TestBlockChainClient>,
miner: &Arc<TestMinerService>, miner: &Arc<TestMinerService>,
sync: &Arc<TestSyncProvider>, sync: &Arc<TestSyncProvider>,
@ -324,3 +325,17 @@ fn rpc_ethcore_pending_transactions() {
assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); 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.");
}

View File

@ -227,7 +227,7 @@ fn sign_and_send_transaction() {
data: vec![] data: vec![]
}; };
tester.accounts.unlock_account_temporarily(address, "password123".into()).unwrap(); 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 t = t.with_signature(signature);
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; 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![] data: vec![]
}; };
tester.accounts.unlock_account_temporarily(address, "password123".into()).unwrap(); 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 t = t.with_signature(signature);
let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#;

View File

@ -186,7 +186,7 @@ fn should_confirm_transaction_and_dispatch() {
data: vec![] data: vec![]
}; };
tester.accounts.unlock_account_temporarily(address, "test".into()).unwrap(); 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); let t = t.with_signature(signature);
assert_eq!(tester.signer.requests().len(), 1); assert_eq!(tester.signer.requests().len(), 1);

View File

@ -15,7 +15,6 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Eth rpc interface. //! Eth rpc interface.
use std::sync::Arc;
use jsonrpc_core::*; use jsonrpc_core::*;
use v1::types::{Block, BlockNumber, Bytes, CallRequest, Filter, FilterChanges, Index}; use v1::types::{Block, BlockNumber, Bytes, CallRequest, Filter, FilterChanges, Index};
@ -198,46 +197,3 @@ build_rpc_trait! {
fn uninstall_filter(&self, Index) -> Result<bool, Error>; fn uninstall_filter(&self, Index) -> Result<bool, Error>;
} }
} }
/// 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<Value, Error>;
/// 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<Value, Error>;
/// 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<Value, Error>;
/// 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<Value, Error>;
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
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
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
//! 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<Value, Error>;
/// 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<Value, Error>;
/// 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<Value, Error>;
/// 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<Self> {
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
}
}

View File

@ -18,6 +18,7 @@
pub mod web3; pub mod web3;
pub mod eth; pub mod eth;
pub mod eth_signing;
pub mod net; pub mod net;
pub mod personal; pub mod personal;
pub mod ethcore; pub mod ethcore;
@ -26,7 +27,8 @@ pub mod traces;
pub mod rpc; pub mod rpc;
pub use self::web3::Web3; 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::net::Net;
pub use self::personal::{Personal, PersonalSigner}; pub use self::personal::{Personal, PersonalSigner};
pub use self::ethcore::Ethcore; pub use self::ethcore::Ethcore;
@ -34,4 +36,3 @@ pub use self::ethcore_set::EthcoreSet;
pub use self::traces::Traces; pub use self::traces::Traces;
pub use self::rpc::Rpc; pub use self::rpc::Rpc;

View File

@ -16,7 +16,7 @@
//! Types used in Confirmations queue (Trusted Signer) //! 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; use v1::helpers;
@ -47,6 +47,15 @@ pub struct SignRequest {
pub hash: H256, 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 /// Confirmation payload, i.e. the thing to be confirmed
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
pub enum ConfirmationPayload { pub enum ConfirmationPayload {
@ -56,6 +65,9 @@ pub enum ConfirmationPayload {
/// Signature /// Signature
#[serde(rename="sign")] #[serde(rename="sign")]
Sign(SignRequest), Sign(SignRequest),
/// Decryption
#[serde(rename="decrypt")]
Decrypt(DecryptRequest),
} }
impl From<helpers::ConfirmationPayload> for ConfirmationPayload { impl From<helpers::ConfirmationPayload> for ConfirmationPayload {
@ -66,6 +78,10 @@ impl From<helpers::ConfirmationPayload> for ConfirmationPayload {
address: address.into(), address: address.into(),
hash: hash.into(), hash: hash.into(),
}), }),
helpers::ConfirmationPayload::Decrypt(address, msg) => ConfirmationPayload::Decrypt(DecryptRequest {
address: address.into(),
msg: msg.into(),
}),
} }
} }
} }

View File

@ -20,11 +20,12 @@ ethcore-util = { path = "../util" }
ethcore-io = { path = "../util/io" } ethcore-io = { path = "../util/io" }
ethcore-rpc = { path = "../rpc" } ethcore-rpc = { path = "../rpc" }
ethcore-devtools = { path = "../devtools" } 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} parity-dapps-signer = { git = "https://github.com/ethcore/parity-ui.git", version = "1.4", optional = true}
clippy = { version = "0.0.90", optional = true} clippy = { version = "0.0.90", optional = true}
[features] [features]
dev = ["clippy"] dev = ["clippy"]
ui = ["parity-dapps-signer"] ui = ["parity-dapps", "parity-dapps-signer"]
use-precompiled-js = ["parity-dapps-signer/use-precompiled-js"] use-precompiled-js = ["parity-dapps-signer/use-precompiled-js"]

View File

@ -54,6 +54,8 @@ extern crate jsonrpc_core;
extern crate ws; extern crate ws;
#[cfg(feature = "ui")] #[cfg(feature = "ui")]
extern crate parity_dapps_signer as signer; extern crate parity_dapps_signer as signer;
#[cfg(feature = "ui")]
extern crate parity_dapps as dapps;
#[cfg(test)] #[cfg(test)]
extern crate ethcore_devtools as devtools; extern crate ethcore_devtools as devtools;

View File

@ -26,22 +26,40 @@ use util::{H256, Mutex, version};
#[cfg(feature = "ui")] #[cfg(feature = "ui")]
mod signer { mod signer {
use signer; use signer::SignerApp;
use dapps::{self, WebApp};
pub fn handle(req: &str) -> Option<signer::File> { #[derive(Default)]
signer::handle(req) 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"))] #[cfg(not(feature = "ui"))]
mod signer { mod signer {
pub struct File { pub struct File {
pub content: String, pub content: &'static str,
pub mime: String, pub content_type: &'static str,
} }
pub fn handle(_req: &str) -> Option<File> { #[derive(Default)]
pub struct Handler {
}
impl Handler {
pub fn handle(&self, _req: &str) -> Option<&File> {
None None
} }
}
} }
fn origin_is_allowed(self_origin: &str, header: Option<&[u8]>) -> bool { fn origin_is_allowed(self_origin: &str, header: Option<&[u8]>) -> bool {
@ -107,6 +125,7 @@ pub struct Session {
self_origin: String, self_origin: String,
authcodes_path: PathBuf, authcodes_path: PathBuf,
handler: Arc<IoHandler>, handler: Arc<IoHandler>,
file_handler: Arc<signer::Handler>,
} }
impl ws::Handler for Session { impl ws::Handler for Session {
@ -152,12 +171,12 @@ impl ws::Handler for Session {
} }
// Otherwise try to serve a page. // Otherwise try to serve a page.
Ok(signer::handle(req.resource()) Ok(self.file_handler.handle(req.resource())
.map_or_else( .map_or_else(
// return 404 not found // return 404 not found
|| error(ErrorType::NotFound, "Not found", "Requested file was not found.", None), || error(ErrorType::NotFound, "Not found", "Requested file was not found.", None),
// or serve the file // 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, skip_origin_validation: bool,
self_origin: String, self_origin: String,
authcodes_path: PathBuf, authcodes_path: PathBuf,
file_handler: Arc<signer::Handler>,
} }
impl Factory { impl Factory {
@ -190,6 +210,7 @@ impl Factory {
skip_origin_validation: skip_origin_validation, skip_origin_validation: skip_origin_validation,
self_origin: self_origin, self_origin: self_origin,
authcodes_path: authcodes_path, 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, skip_origin_validation: self.skip_origin_validation,
self_origin: self.self_origin.clone(), self_origin: self.self_origin.clone(),
authcodes_path: self.authcodes_path.clone(), authcodes_path: self.authcodes_path.clone(),
file_handler: self.file_handler.clone(),
} }
} }
} }