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:
parent
85eeb3ea6e
commit
03c1559ead
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -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)",
|
||||
|
@ -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<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));
|
||||
Ok(address)
|
||||
Ok((address, public))
|
||||
}
|
||||
|
||||
/// Inserts new account into underlying store.
|
||||
@ -280,6 +287,21 @@ impl AccountProvider {
|
||||
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.
|
||||
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<Signature, 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
|
||||
};
|
||||
|
||||
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<String>, message: Message) -> Result<Signature, Error> {
|
||||
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<Vec<u8>, 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<Signature, Error> {
|
||||
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<String>, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, 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());
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 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<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> {
|
||||
let secret = try!(self.crypto.secret(old_password));
|
||||
let result = SafeAccount {
|
||||
|
@ -37,6 +37,7 @@ Usage:
|
||||
ethstore import-wallet <path> <password> [--dir DIR]
|
||||
ethstore remove <address> <password> [--dir DIR]
|
||||
ethstore sign <address> <password> <message> [--dir DIR]
|
||||
ethstore public <address> <password>
|
||||
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<Box<KeyDirectory>, 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::<Vec<String>>()
|
||||
.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 password = try!(load_password(&args.arg_password));
|
||||
let address = try!(store.insert_account(secret, &password));
|
||||
Ok(format!("{}", address))
|
||||
Ok(format!("0x{:?}", address))
|
||||
} else if args.cmd_change_pwd {
|
||||
let address = try!(args.arg_address.parse().map_err(|_| Error::InvalidAccount));
|
||||
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 kp = try!(wallet.decrypt(&password));
|
||||
let address = try!(store.insert_account(kp.secret().clone(), &password));
|
||||
Ok(format!("{}", address))
|
||||
Ok(format!("0x{:?}", address))
|
||||
} else if args.cmd_remove {
|
||||
let address = try!(args.arg_address.parse().map_err(|_| Error::InvalidAccount));
|
||||
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 password = try!(load_password(&args.arg_password));
|
||||
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 {
|
||||
Ok(format!("{}", USAGE))
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ use std::mem;
|
||||
use ethkey::KeyPair;
|
||||
use crypto::KEY_ITERATIONS;
|
||||
use random::Random;
|
||||
use ethkey::{Signature, Address, Message, Secret};
|
||||
use ethkey::{Signature, Address, Message, Secret, Public};
|
||||
use dir::KeyDirectory;
|
||||
use account::SafeAccount;
|
||||
use {Error, SecretStore};
|
||||
@ -149,6 +149,11 @@ impl SecretStore for EthStore {
|
||||
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> {
|
||||
let account = try!(self.get(address));
|
||||
Ok(account.id.into())
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 json::UUID;
|
||||
|
||||
@ -27,6 +27,7 @@ pub trait SecretStore: Send + Sync {
|
||||
|
||||
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 public(&self, account: &Address, password: &str) -> Result<Public, Error>;
|
||||
|
||||
fn accounts(&self) -> Result<Vec<Address>, Error>;
|
||||
fn uuid(&self, account: &Address) -> Result<UUID, Error>;
|
||||
|
@ -14,17 +14,67 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 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<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 {
|
||||
Transaction {
|
||||
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 {
|
||||
client
|
||||
.gas_price_statistics(100, 8)
|
||||
|
@ -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<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 {
|
||||
Error {
|
||||
code: ErrorCode::ServerError(codes::FETCH_ERROR),
|
||||
|
@ -103,4 +103,6 @@ pub enum ConfirmationPayload {
|
||||
Transaction(FilledTransactionRequest),
|
||||
/// Sign request
|
||||
Sign(Address, H256),
|
||||
/// Decrypt request
|
||||
Decrypt(Address, Bytes),
|
||||
}
|
||||
|
@ -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<C, M>(request: TRequest, client: &C, miner: &M) -> FilledRequest
|
||||
where C: MiningBlockChainClient, M: MinerService {
|
||||
@ -76,40 +76,58 @@ impl<C, M> EthSigningQueueClient<C, M> where C: MiningBlockChainClient, M: Miner
|
||||
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> {
|
||||
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<DispatchResult, Error> {
|
||||
from_params::<(TransactionRequest, )>(params)
|
||||
.and_then(|(request, )| {
|
||||
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);
|
||||
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<C, M> EthSigning for EthSigningQueueClient<C, M>
|
||||
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> {
|
||||
try!(self.active());
|
||||
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> {
|
||||
try!(self.active());
|
||||
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> {
|
||||
try!(self.active());
|
||||
let mut pending = self.pending.lock();
|
||||
@ -192,6 +177,32 @@ impl<C, M> EthSigning for EthSigningQueueClient<C, M>
|
||||
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<C, M> EthSigning for EthSigningUnsafeClient<C, M> 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<C, M> EthSigning for EthSigningUnsafeClient<C, M> 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<Value, Error> {
|
||||
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<Value, Error> {
|
||||
|
@ -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<C, M, S: ?Sized, F> Ethcore for EthcoreClient<C, M, S, F> where
|
||||
fn encrypt_message(&self, key: H512, phrase: Bytes) -> Result<Bytes, Error> {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -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<C: 'static, M: 'static> Personal for PersonalClient<C, M> 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)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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<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 {
|
||||
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()));
|
||||
|
@ -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<AccountProvider> {
|
||||
|
@ -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();
|
||||
|
@ -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<SignerService>,
|
||||
@ -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());
|
||||
}));
|
||||
}
|
||||
|
@ -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<TestBlockChainClient> {
|
||||
Arc::new(TestBlockChainClient::default())
|
||||
}
|
||||
|
||||
fn sync_provider() -> Arc<TestSyncProvider> {
|
||||
pub fn sync_provider() -> Arc<TestSyncProvider> {
|
||||
Arc::new(TestSyncProvider::new(Config {
|
||||
network_id: U256::from(3),
|
||||
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)
|
||||
}
|
||||
|
||||
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>,
|
||||
miner: &Arc<TestMinerService>,
|
||||
sync: &Arc<TestSyncProvider>,
|
||||
@ -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.");
|
||||
}
|
||||
|
@ -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}"#;
|
||||
|
@ -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);
|
||||
|
@ -15,7 +15,6 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<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
|
||||
}
|
||||
}
|
||||
|
63
rpc/src/v1/traits/eth_signing.rs
Normal file
63
rpc/src/v1/traits/eth_signing.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
||||
|
@ -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<helpers::ConfirmationPayload> for ConfirmationPayload {
|
||||
@ -66,6 +78,10 @@ impl From<helpers::ConfirmationPayload> for ConfirmationPayload {
|
||||
address: address.into(),
|
||||
hash: hash.into(),
|
||||
}),
|
||||
helpers::ConfirmationPayload::Decrypt(address, msg) => ConfirmationPayload::Decrypt(DecryptRequest {
|
||||
address: address.into(),
|
||||
msg: msg.into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"]
|
||||
|
@ -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;
|
||||
|
||||
|
@ -26,23 +26,41 @@ 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::File> {
|
||||
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<File> {
|
||||
#[derive(Default)]
|
||||
pub struct Handler {
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub fn handle(&self, _req: &str) -> Option<&File> {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn origin_is_allowed(self_origin: &str, header: Option<&[u8]>) -> bool {
|
||||
match header {
|
||||
@ -107,6 +125,7 @@ pub struct Session {
|
||||
self_origin: String,
|
||||
authcodes_path: PathBuf,
|
||||
handler: Arc<IoHandler>,
|
||||
file_handler: Arc<signer::Handler>,
|
||||
}
|
||||
|
||||
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<signer::Handler>,
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user