diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs
index 6744c6bd2..a71251727 100644
--- a/ethcore/src/account_provider.rs
+++ b/ethcore/src/account_provider.rs
@@ -205,6 +205,13 @@ impl AccountProvider {
self.unlock_account(account, password, Unlock::Temp)
}
+ /// Checks if given account is unlocked
+ pub fn is_unlocked(&self, account: A) -> bool where Address: From {
+ let account = Address::from(account).into();
+ let unlocked = self.unlocked.read().unwrap();
+ unlocked.get(&account).is_some()
+ }
+
/// Signs the message. Account must be unlocked.
pub fn sign(&self, account: A, message: M) -> Result where Address: From, Message: From {
let account = Address::from(account).into();
diff --git a/ethstore/README.md b/ethstore/README.md
index aba4911bf..0b85d99b4 100644
--- a/ethstore/README.md
+++ b/ethstore/README.md
@@ -20,6 +20,7 @@ Usage:
ethstore change-pwd [--dir DIR]
ethstore list [--dir DIR]
ethstore import [--src DIR] [--dir DIR]
+ ethstore import-wallet [--dir DIR]
ethstore remove [--dir DIR]
ethstore sign [--dir DIR]
ethstore [-h | --help]
@@ -38,6 +39,7 @@ Commands:
change-pwd Change account password.
list List accounts.
import Import accounts from src.
+ import-wallet Import presale wallet.
remove Remove account.
sign Sign message.
```
@@ -48,11 +50,11 @@ Commands:
*Encrypt secret with a password and save it in secret store.*
- `` - ethereum secret, 32 bytes long
-- `` - account password, any string
+- `` - account password, file path
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
```
-ethstore insert 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5 "this is sparta"
+ethstore insert 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5 password.txt
```
```
@@ -75,12 +77,12 @@ ethstore insert `ethkey generate random -s` "this is sparta"
*Change account password.*
- `` - ethereum address, 20 bytes long
-- `` - old account password, any string
-- `` - new account password, any string
+- `` - old account password, file path
+- `` - new account password, file path
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
```
-ethstore change-pwd a8fa5dd30a87bb9e3288d604eb74949c515ab66e "this is sparta" "hello world"
+ethstore change-pwd a8fa5dd30a87bb9e3288d604eb74949c515ab66e old_pwd.txt new_pwd.txt
```
```
@@ -112,6 +114,10 @@ ethstore list
- `[--src DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: geth
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
+```
+ethstore import
+```
+
```
0: e6a3d25a7cb7cd21cb720df5b5e8afd154af1bbb
1: 6edddfc6349aff20bc6467ccf276c5b52487f7a8
@@ -119,15 +125,32 @@ ethstore list
--
+#### `import-wallet [--dir DIR]`
+*Import account from presale wallet.*
+
+- `` - presale wallet path
+- `` - account password, file path
+- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
+
+```
+ethstore import-wallet ethwallet.json password.txt
+```
+
+```
+e6a3d25a7cb7cd21cb720df5b5e8afd154af1bbb
+```
+
+--
+
#### `remove [--dir DIR]`
*Remove account from secret store.*
- `` - ethereum address, 20 bytes long
-- `` - account password, any string
+- `` - account password, file path
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
```
-ethstore remove a8fa5dd30a87bb9e3288d604eb74949c515ab66e "hello world"
+ethstore remove a8fa5dd30a87bb9e3288d604eb74949c515ab66e password.txt
```
```
@@ -140,12 +163,12 @@ true
*Sign message with account's secret.*
- `` - ethereum address, 20 bytes long
-- `` - account password, any string
+- `` - account password, file path
- `` - message to sign, 32 bytes long
- `[--dir DIR]` - secret store directory, It may be either parity, parity-test, geth, geth-test or a path. default: parity
```
-ethstore sign 24edfff680d536a5f6fe862d36df6f8f6f40f115 "this is sparta" 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5
+ethstore sign 24edfff680d536a5f6fe862d36df6f8f6f40f115 password.txt 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5
```
```
diff --git a/ethstore/src/bin/ethstore.rs b/ethstore/src/bin/ethstore.rs
index 6020679d4..5683a8116 100644
--- a/ethstore/src/bin/ethstore.rs
+++ b/ethstore/src/bin/ethstore.rs
@@ -18,13 +18,14 @@ extern crate rustc_serialize;
extern crate docopt;
extern crate ethstore;
-use std::{env, process};
+use std::{env, process, fs};
+use std::io::Read;
use std::ops::Deref;
use std::str::FromStr;
use docopt::Docopt;
use ethstore::ethkey::{Secret, Address, Message};
use ethstore::dir::{KeyDirectory, ParityDirectory, DiskDirectory, GethDirectory, DirectoryType};
-use ethstore::{EthStore, SecretStore, import_accounts, Error};
+use ethstore::{EthStore, SecretStore, import_accounts, Error, PresaleWallet};
pub const USAGE: &'static str = r#"
Ethereum key management.
@@ -35,6 +36,7 @@ Usage:
ethstore change-pwd [--dir DIR]
ethstore list [--dir DIR]
ethstore import [--src DIR] [--dir DIR]
+ ethstore import-wallet [--dir DIR]
ethstore remove [--dir DIR]
ethstore sign [--dir DIR]
ethstore [-h | --help]
@@ -53,6 +55,7 @@ Commands:
change-pwd Change password.
list List accounts.
import Import accounts from src.
+ import-wallet Import presale wallet.
remove Remove account.
sign Sign message.
"#;
@@ -63,6 +66,7 @@ struct Args {
cmd_change_pwd: bool,
cmd_list: bool,
cmd_import: bool,
+ cmd_import_wallet: bool,
cmd_remove: bool,
cmd_sign: bool,
arg_secret: String,
@@ -71,6 +75,7 @@ struct Args {
arg_new_pwd: String,
arg_address: String,
arg_message: String,
+ arg_path: String,
flag_src: String,
flag_dir: String,
}
@@ -105,6 +110,15 @@ fn format_accounts(accounts: &[Address]) -> String {
.join("\n")
}
+fn load_password(path: &str) -> Result {
+ let mut file = try!(fs::File::open(path));
+ let mut password = String::new();
+ try!(file.read_to_string(&mut password));
+ // drop EOF
+ let _ = password.pop();
+ Ok(password)
+}
+
fn execute(command: I) -> Result where I: IntoIterator- , S: AsRef {
let args: Args = Docopt::new(USAGE)
.and_then(|d| d.argv(command).decode())
@@ -114,11 +128,14 @@ fn execute
(command: I) -> Result where I: IntoIterator- (command: I) -> Result where I: IntoIterator
- for [u8] {
pub mod aes {
use rcrypto::blockmodes::{CtrMode, CbcDecryptor, PkcsPadding};
use rcrypto::aessafe::{AesSafe128Encryptor, AesSafe128Decryptor};
- use rcrypto::symmetriccipher::{Encryptor, Decryptor};
+ use rcrypto::symmetriccipher::{Encryptor, Decryptor, SymmetricCipherError};
use rcrypto::buffer::{RefReadBuffer, RefWriteBuffer};
/// Encrypt a message
@@ -83,9 +83,10 @@ pub mod aes {
}
/// Decrypt a message using cbc mode
- pub fn decrypt_cbc(k: &[u8], iv: &[u8], encrypted: &[u8], dest: &mut [u8]) {
+ pub fn decrypt_cbc(k: &[u8], iv: &[u8], encrypted: &[u8], dest: &mut [u8]) -> Result<(), SymmetricCipherError> {
let mut encryptor = CbcDecryptor::new(AesSafe128Decryptor::new(k), PkcsPadding, iv.to_vec());
- encryptor.decrypt(&mut RefReadBuffer::new(encrypted), &mut RefWriteBuffer::new(dest), true).expect("Invalid length or padding");
+ try!(encryptor.decrypt(&mut RefReadBuffer::new(encrypted), &mut RefWriteBuffer::new(dest), true));
+ Ok(())
}
}
diff --git a/ethstore/src/presale.rs b/ethstore/src/presale.rs
index 5ba57b8d4..09c86abea 100644
--- a/ethstore/src/presale.rs
+++ b/ethstore/src/presale.rs
@@ -43,7 +43,7 @@ impl PresaleWallet {
pbkdf2(&mut h_mac, password.as_bytes(), 2000, &mut derived_key);
let mut key = [0u8; 64];
- crypto::aes::decrypt_cbc(&derived_key, &self.iv, &self.ciphertext, &mut key);
+ try!(crypto::aes::decrypt_cbc(&derived_key, &self.iv, &self.ciphertext, &mut key).map_err(|_| Error::InvalidPassword));
let secret = Secret::from(key.keccak256());
if let Ok(kp) = KeyPair::from_secret(secret) {
@@ -58,7 +58,6 @@ impl PresaleWallet {
#[cfg(test)]
mod tests {
- use ethkey::Address;
use super::PresaleWallet;
use json;
@@ -74,7 +73,7 @@ mod tests {
let wallet = json::PresaleWallet::load(json.as_bytes()).unwrap();
let wallet = PresaleWallet::from(wallet);
- let kp = wallet.decrypt("123").unwrap();
- assert_eq!(kp.address(), Address::from(wallet.address));
+ assert!(wallet.decrypt("123").is_ok());
+ assert!(wallet.decrypt("124").is_err());
}
}
diff --git a/parity/cli.rs b/parity/cli.rs
index 1e77031e2..7192cc9ce 100644
--- a/parity/cli.rs
+++ b/parity/cli.rs
@@ -25,6 +25,7 @@ Usage:
parity daemon [options]
parity account (new | list ) [options]
parity account import ... [options]
+ parity wallet import --password FILE [options]
parity import [ ] [options]
parity export [ ] [options]
parity signer new-token [options]
@@ -115,7 +116,7 @@ API and Console Options:
--dapps-path PATH Specify directory where dapps should be installed.
[default: $HOME/.parity/dapps]
- --signer Enable Trusted Signer WebSocket endpoint used by
+ --signer-off Disable Trusted Signer WebSocket endpoint used by
Signer UIs.
--signer-port PORT Specify the port of Trusted Signer server
[default: 8180].
@@ -223,6 +224,7 @@ Miscellaneous Options:
pub struct Args {
pub cmd_daemon: bool,
pub cmd_account: bool,
+ pub cmd_wallet: bool,
pub cmd_new: bool,
pub cmd_list: bool,
pub cmd_export: bool,
@@ -272,7 +274,7 @@ pub struct Args {
pub flag_dapps_user: Option,
pub flag_dapps_pass: Option,
pub flag_dapps_path: String,
- pub flag_signer: bool,
+ pub flag_signer_off: bool,
pub flag_signer_port: u16,
pub flag_signer_path: String,
pub flag_no_token: bool,
diff --git a/parity/configuration.rs b/parity/configuration.rs
index 94835a8a9..4196564b5 100644
--- a/parity/configuration.rs
+++ b/parity/configuration.rs
@@ -432,10 +432,10 @@ impl Configuration {
}
pub fn signer_port(&self) -> Option {
- if self.args.flag_signer {
- Some(self.args.flag_signer_port)
- } else {
+ if self.args.flag_signer_off {
None
+ } else {
+ Some(self.args.flag_signer_port)
}
}
}
diff --git a/parity/main.rs b/parity/main.rs
index a44cdf6d3..17d941a5d 100644
--- a/parity/main.rs
+++ b/parity/main.rs
@@ -129,6 +129,11 @@ fn execute(conf: Configuration) {
return;
}
+ if conf.args.cmd_wallet {
+ execute_wallet_cli(conf);
+ return;
+ }
+
if conf.args.cmd_export {
execute_export(conf);
return;
@@ -187,12 +192,18 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig)
let sync_config = conf.sync_config(&spec);
// Create and display a new token for UIs.
- if conf.args.flag_signer && !conf.args.flag_no_token {
+ if !conf.args.flag_signer_off && !conf.args.flag_no_token {
new_token(conf.directories().signer).unwrap_or_else(|e| {
die!("Error generating token: {:?}", e)
});
}
+ // Display warning about using unlock with signer
+ if conf.args.flag_signer && conf.args.flag_unlock.is_some() {
+ warn!("Using Trusted Signer and --unlock is not recommended!");
+ warn!("NOTE that Signer will not ask you to confirm transactions from unlocked account.");
+ }
+
// Secret Store
let account_service = Arc::new(conf.account_service());
@@ -534,6 +545,30 @@ fn execute_account_cli(conf: Configuration) {
}
}
+fn execute_wallet_cli(conf: Configuration) {
+ use ethcore::ethstore::{PresaleWallet, SecretStore, EthStore};
+ use ethcore::ethstore::dir::DiskDirectory;
+ use ethcore::account_provider::AccountProvider;
+
+ let wallet_path = conf.args.arg_path.first().unwrap();
+ let filename = conf.args.flag_password.first().unwrap();
+ let mut file = File::open(filename).unwrap_or_else(|_| die!("{} Unable to read password file.", filename));
+ let mut file_content = String::new();
+ file.read_to_string(&mut file_content).unwrap_or_else(|_| die!("{} Unable to read password file.", filename));
+
+ let dir = Box::new(DiskDirectory::create(conf.keys_path()).unwrap());
+ let iterations = conf.keys_iterations();
+ let store = AccountProvider::new(Box::new(EthStore::open_with_iterations(dir, iterations).unwrap()));
+
+ // remove eof
+ let pass = &file_content[..file_content.len() - 1];
+ let wallet = PresaleWallet::open(wallet_path).unwrap_or_else(|_| die!("Unable to open presale wallet."));
+ let kp = wallet.decrypt(pass).unwrap_or_else(|_| die!("Invalid password"));
+ let address = store.insert_account(kp.secret().clone(), pass).unwrap();
+
+ println!("Imported account: {}", address);
+}
+
fn wait_for_exit(
panic_handler: Arc,
_rpc_server: Option,
diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs
index 22520f266..c0daaa926 100644
--- a/parity/rpc_apis.rs
+++ b/parity/rpc_apis.rs
@@ -150,7 +150,7 @@ pub fn setup_rpc(server: T, deps: Arc, apis: ApiSet
server.add_delegate(EthFilterClient::new(&deps.client, &deps.miner).to_delegate());
if deps.signer_port.is_some() {
- server.add_delegate(EthSigningQueueClient::new(&deps.signer_queue, &deps.client, &deps.miner).to_delegate());
+ server.add_delegate(EthSigningQueueClient::new(&deps.signer_queue, &deps.client, &deps.miner, &deps.secret_store).to_delegate());
} else {
server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate());
}
diff --git a/rpc/src/v1/impls/eth_signing.rs b/rpc/src/v1/impls/eth_signing.rs
index c5103fd2d..700c679d6 100644
--- a/rpc/src/v1/impls/eth_signing.rs
+++ b/rpc/src/v1/impls/eth_signing.rs
@@ -43,15 +43,17 @@ fn fill_optional_fields(request: &mut TransactionRequest, client: &C, mine
/// Implementation of functions that require signing when no trusted signer is used.
pub struct EthSigningQueueClient where C: MiningBlockChainClient, M: MinerService {
queue: Weak,
+ accounts: Weak,
client: Weak,
miner: Weak,
}
impl EthSigningQueueClient where C: MiningBlockChainClient, M: MinerService {
/// Creates a new signing queue client given shared signing queue.
- pub fn new(queue: &Arc, client: &Arc, miner: &Arc) -> Self {
+ pub fn new(queue: &Arc, client: &Arc, miner: &Arc, accounts: &Arc) -> Self {
EthSigningQueueClient {
queue: Arc::downgrade(queue),
+ accounts: Arc::downgrade(accounts),
client: Arc::downgrade(client),
miner: Arc::downgrade(miner),
}
@@ -71,9 +73,18 @@ impl EthSigning for EthSigningQueueClient
fn send_transaction(&self, params: Params) -> Result {
from_params::<(TransactionRequest, )>(params)
.and_then(|(mut request, )| {
- let queue = take_weak!(self.queue);
+ 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 match sign_and_dispatch(&*client, &*miner, request, &*accounts, sender) {
+ Ok(hash) => to_value(&hash),
+ _ => to_value(&H256::zero()),
+ }
+ }
+
+ let queue = take_weak!(self.queue);
fill_optional_fields(&mut request, &*client, &*miner);
let id = queue.add_request(request);
let result = id.wait_with_timeout();
diff --git a/rpc/src/v1/tests/mocked/eth_signing.rs b/rpc/src/v1/tests/mocked/eth_signing.rs
index a2755ce18..5f3e75d35 100644
--- a/rpc/src/v1/tests/mocked/eth_signing.rs
+++ b/rpc/src/v1/tests/mocked/eth_signing.rs
@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
+use std::str::FromStr;
use std::sync::Arc;
use jsonrpc_core::IoHandler;
use v1::impls::EthSigningQueueClient;
@@ -21,12 +22,16 @@ use v1::traits::EthSigning;
use v1::helpers::{ConfirmationsQueue, SigningQueue};
use v1::tests::helpers::TestMinerService;
use util::{Address, FixedHash};
+use util::numbers::{Uint, U256};
+use ethcore::account_provider::AccountProvider;
use ethcore::client::TestBlockChainClient;
+use ethcore::transaction::{Transaction, Action};
struct EthSigningTester {
pub queue: Arc,
pub client: Arc,
pub miner: Arc,
+ pub accounts: Arc,
pub io: IoHandler,
}
@@ -35,13 +40,15 @@ impl Default for EthSigningTester {
let queue = Arc::new(ConfirmationsQueue::default());
let client = Arc::new(TestBlockChainClient::default());
let miner = Arc::new(TestMinerService::default());
+ let accounts = Arc::new(AccountProvider::transient_provider());
let io = IoHandler::new();
- io.add_delegate(EthSigningQueueClient::new(&queue, &client, &miner).to_delegate());
+ io.add_delegate(EthSigningQueueClient::new(&queue, &client, &miner, &accounts).to_delegate());
EthSigningTester {
queue: queue,
client: client,
miner: miner,
+ accounts: accounts,
io: io,
}
}
@@ -78,5 +85,41 @@ fn should_add_transaction_to_queue() {
// then
assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
assert_eq!(tester.queue.requests().len(), 1);
-
+}
+
+#[test]
+fn should_dispatch_transaction_if_account_is_unlocked() {
+ // given
+ let tester = eth_signing();
+ let acc = tester.accounts.new_account("test").unwrap();
+ tester.accounts.unlock_account_permanently(acc, "test".into()).unwrap();
+
+ let t = Transaction {
+ nonce: U256::zero(),
+ gas_price: U256::from(0x9184e72a000u64),
+ gas: U256::from(0x76c0),
+ action: Action::Call(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()),
+ value: U256::from(0x9184e72au64),
+ data: vec![]
+ };
+ let signature = tester.accounts.sign(acc, t.hash()).unwrap();
+ let t = t.with_signature(signature);
+
+ // when
+ let request = r#"{
+ "jsonrpc": "2.0",
+ "method": "eth_sendTransaction",
+ "params": [{
+ "from": ""#.to_owned() + format!("0x{:?}", acc).as_ref() + r#"",
+ "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
+ "gas": "0x76c0",
+ "gasPrice": "0x9184e72a000",
+ "value": "0x9184e72a"
+ }],
+ "id": 1
+ }"#;
+ let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#;
+
+ // then
+ assert_eq!(tester.io.handle_request(&request), Some(response.to_owned()));
}
diff --git a/signer/src/ws_server/mod.rs b/signer/src/ws_server/mod.rs
index beffbd2f7..b6b7c15f7 100644
--- a/signer/src/ws_server/mod.rs
+++ b/signer/src/ws_server/mod.rs
@@ -93,7 +93,6 @@ impl Server {
let config = {
let mut config = ws::Settings::default();
// It's also used for handling min-sysui requests (browser can make many of them in paralel)
- config.max_connections = 15;
config.method_strict = true;
// Was shutting down server when suspending on linux:
config.shutdown_on_interrupt = false;