@@ -71,7 +78,7 @@ class TransactionPendingFormConfirm extends Component {
className={ styles.confirmButton }
fullWidth
primary
- disabled={ isSending }
+ disabled={ isSending || !isWalletOk }
icon={
}
label={ isSending ? 'Confirming...' : 'Confirm Transaction' }
/>
@@ -82,6 +89,20 @@ class TransactionPendingFormConfirm extends Component {
);
}
+ renderKeyInput () {
+ const { walletError } = this.state;
+
+ return (
+
+ );
+ }
+
renderTooltip () {
if (this.state.password.length) {
return;
@@ -94,6 +115,26 @@ class TransactionPendingFormConfirm extends Component {
);
}
+ onKeySelect = evt => {
+ const fileReader = new FileReader();
+ fileReader.onload = e => {
+ try {
+ const wallet = JSON.parse(e.target.result);
+ this.setState({
+ walletError: null,
+ wallet: wallet
+ });
+ } catch (e) {
+ this.setState({
+ walletError: 'Given wallet file is invalid.',
+ wallet: null
+ });
+ }
+ };
+
+ fileReader.readAsText(evt.target.files[0]);
+ }
+
onModifyPassword = evt => {
const password = evt.target.value;
this.setState({
@@ -102,8 +143,11 @@ class TransactionPendingFormConfirm extends Component {
}
onConfirm = () => {
- const { password } = this.state;
- this.props.onConfirm(password);
+ const { password, wallet } = this.state;
+
+ this.props.onConfirm({
+ password, wallet
+ });
}
onKeyDown = evt => {
diff --git a/js/webpack.vendor.js b/js/webpack.vendor.js
index a896cbfd8..74a51dada 100644
--- a/js/webpack.vendor.js
+++ b/js/webpack.vendor.js
@@ -22,6 +22,7 @@ const DEST = process.env.BUILD_DEST || '.build';
let modules = [
'babel-polyfill',
+ 'browserify-aes', 'ethereumjs-tx', 'scryptsy',
'react', 'react-dom', 'react-redux', 'react-router',
'redux', 'redux-thunk', 'react-router-redux',
'lodash', 'material-ui', 'moment', 'blockies'
diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs
index 50c22b187..d36feca4b 100644
--- a/rpc/src/v1/helpers/errors.rs
+++ b/rpc/src/v1/helpers/errors.rs
@@ -17,7 +17,7 @@
//! RPC Error codes and error objects
macro_rules! rpc_unimplemented {
- () => (Err(::v1::helpers::errors::unimplemented()))
+ () => (Err(::v1::helpers::errors::unimplemented(None)))
}
use std::fmt;
@@ -51,11 +51,11 @@ mod codes {
pub const FETCH_ERROR: i64 = -32060;
}
-pub fn unimplemented() -> Error {
+pub fn unimplemented(details: Option
) -> Error {
Error {
code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST),
message: "This request is not implemented yet. Please create an issue on Github repo.".into(),
- data: None
+ data: details.map(Value::String),
}
}
diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs
index 6986cf0ed..8207426ba 100644
--- a/rpc/src/v1/impls/eth.rs
+++ b/rpc/src/v1/impls/eth.rs
@@ -20,6 +20,7 @@ extern crate ethash;
use std::io::{Write};
use std::process::{Command, Stdio};
+use std::collections::BTreeSet;
use std::thread;
use std::time::{Instant, Duration};
use std::sync::{Arc, Weak};
@@ -339,7 +340,10 @@ impl Eth for EthClient where
let store = take_weak!(self.accounts);
let accounts = try!(store.accounts().map_err(|e| errors::internal("Could not fetch accounts.", e)));
- Ok(accounts.into_iter().map(Into::into).collect())
+ let addresses = try!(store.addresses_info().map_err(|e| errors::internal("Could not fetch accounts.", e)));
+
+ let set: BTreeSet = accounts.into_iter().chain(addresses.keys().cloned()).collect();
+ Ok(set.into_iter().map(Into::into).collect())
}
fn block_number(&self) -> Result {
diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs
index 0ee06b5c5..66f46ba01 100644
--- a/rpc/src/v1/impls/signer.rs
+++ b/rpc/src/v1/impls/signer.rs
@@ -18,14 +18,17 @@
use std::sync::{Arc, Weak};
-use jsonrpc_core::*;
+use rlp::{UntrustedRlp, View};
use ethcore::account_provider::AccountProvider;
use ethcore::client::MiningBlockChainClient;
+use ethcore::transaction::SignedTransaction;
use ethcore::miner::MinerService;
+
+use jsonrpc_core::Error;
use v1::traits::Signer;
-use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, U256};
+use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, U256, Bytes};
use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload};
-use v1::helpers::dispatch;
+use v1::helpers::dispatch::{self, dispatch_transaction};
/// Transactions confirmation (personal) rpc implementation.
pub struct SignerClient where C: MiningBlockChainClient, M: MinerService {
@@ -66,9 +69,9 @@ impl Signer for SignerClient where C: MiningBlockC
let signer = take_weak!(self.signer);
Ok(signer.requests()
- .into_iter()
- .map(Into::into)
- .collect()
+ .into_iter()
+ .map(Into::into)
+ .collect()
)
}
@@ -101,6 +104,60 @@ impl Signer for SignerClient where C: MiningBlockC
}).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id)))
}
+ fn confirm_request_raw(&self, id: U256, bytes: Bytes) -> Result {
+ try!(self.active());
+
+ let id = id.into();
+ let signer = take_weak!(self.signer);
+ let client = take_weak!(self.client);
+ let miner = take_weak!(self.miner);
+
+ signer.peek(&id).map(|confirmation| {
+ let result = match confirmation.payload {
+ ConfirmationPayload::SendTransaction(request) => {
+ let signed_transaction: SignedTransaction = try!(
+ UntrustedRlp::new(&bytes.0).as_val().map_err(errors::from_rlp_error)
+ );
+ let sender = try!(
+ signed_transaction.sender().map_err(|e| errors::invalid_params("Invalid signature.", e))
+ );
+
+ // Verification
+ let sender_matches = sender == request.from;
+ let data_matches = signed_transaction.data == request.data;
+ let value_matches = signed_transaction.value == request.value;
+ let nonce_matches = match request.nonce {
+ Some(nonce) => signed_transaction.nonce == nonce,
+ None => true,
+ };
+
+ // Dispatch if everything is ok
+ if sender_matches && data_matches && value_matches && nonce_matches {
+ dispatch_transaction(&*client, &*miner, signed_transaction)
+ .map(Into::into)
+ .map(ConfirmationResponse::SendTransaction)
+ } else {
+ let mut error = Vec::new();
+ if !sender_matches { error.push("from") }
+ if !data_matches { error.push("data") }
+ if !value_matches { error.push("value") }
+ if !nonce_matches { error.push("nonce") }
+
+ Err(errors::invalid_params("Sent transaction does not match the request.", error))
+ }
+ },
+ // TODO [ToDr]:
+ // 1. Sign - verify signature
+ // 2. Decrypt - pass through?
+ _ => Err(errors::unimplemented(Some("Non-transaction requests does not support RAW signing yet.".into()))),
+ };
+ if let Ok(ref response) = result {
+ signer.request_confirmed(id, Ok(response.clone()));
+ }
+ result
+ }).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id)))
+ }
+
fn reject_request(&self, id: U256) -> Result {
try!(self.active());
let signer = take_weak!(self.signer);
diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs
index 7119de2c1..67e77a6db 100644
--- a/rpc/src/v1/tests/mocked/eth.rs
+++ b/rpc/src/v1/tests/mocked/eth.rs
@@ -353,9 +353,16 @@ fn rpc_eth_gas_price() {
fn rpc_eth_accounts() {
let tester = EthTester::default();
let address = tester.accounts_provider.new_account("").unwrap();
+ let address2 = Address::default();
+
+ tester.accounts_provider.set_address_name(address2, "Test Account".into()).unwrap();
let request = r#"{"jsonrpc": "2.0", "method": "eth_accounts", "params": [], "id": 1}"#;
- let response = r#"{"jsonrpc":"2.0","result":[""#.to_owned() + &format!("0x{:?}", address) + r#""],"id":1}"#;
+ let response = r#"{"jsonrpc":"2.0","result":[""#.to_owned()
+ + &format!("0x{:?}", address2)
+ + r#"",""#
+ + &format!("0x{:?}", address)
+ + r#""],"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}
diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs
index 8807e2373..e2ba580e0 100644
--- a/rpc/src/v1/tests/mocked/signer.rs
+++ b/rpc/src/v1/tests/mocked/signer.rs
@@ -16,11 +16,14 @@
use std::sync::Arc;
use std::str::FromStr;
-use jsonrpc_core::IoHandler;
-use util::{U256, Uint, Address};
+use util::{U256, Uint, Address, ToPretty};
+
use ethcore::account_provider::AccountProvider;
use ethcore::client::TestBlockChainClient;
use ethcore::transaction::{Transaction, Action};
+use rlp::encode;
+
+use jsonrpc_core::IoHandler;
use v1::{SignerClient, Signer};
use v1::tests::helpers::TestMinerService;
use v1::helpers::{SigningQueue, SignerService, FilledTransactionRequest, ConfirmationPayload};
@@ -206,6 +209,98 @@ fn should_confirm_transaction_and_dispatch() {
assert_eq!(tester.miner.imported_transactions.lock().len(), 1);
}
+#[test]
+fn should_confirm_transaction_with_rlp() {
+ // given
+ let tester = signer_tester();
+ let address = tester.accounts.new_account("test").unwrap();
+ let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap();
+ tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest {
+ from: address,
+ to: Some(recipient),
+ gas_price: U256::from(10_000),
+ gas: U256::from(10_000_000),
+ value: U256::from(1),
+ data: vec![],
+ nonce: None,
+ })).unwrap();
+
+ let t = Transaction {
+ nonce: U256::zero(),
+ gas_price: U256::from(0x1000),
+ gas: U256::from(10_000_000),
+ action: Action::Call(recipient),
+ value: U256::from(0x1),
+ data: vec![]
+ };
+ tester.accounts.unlock_account_temporarily(address, "test".into()).unwrap();
+ let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap();
+ let t = t.with_signature(signature, None);
+ let rlp = encode(&t);
+
+ assert_eq!(tester.signer.requests().len(), 1);
+
+ // when
+ let request = r#"{
+ "jsonrpc":"2.0",
+ "method":"signer_confirmRequestRaw",
+ "params":["0x1", "0x"#.to_owned() + &rlp.to_hex() + r#""],
+ "id":1
+ }"#;
+println!("{:?}", request);
+ 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_sync(&request), Some(response.to_owned()));
+ assert_eq!(tester.signer.requests().len(), 0);
+ assert_eq!(tester.miner.imported_transactions.lock().len(), 1);
+}
+
+#[test]
+fn should_return_error_when_sender_does_not_match() {
+ // given
+ let tester = signer_tester();
+ let address = tester.accounts.new_account("test").unwrap();
+ let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap();
+ tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest {
+ from: Address::default(),
+ to: Some(recipient),
+ gas_price: U256::from(10_000),
+ gas: U256::from(10_000_000),
+ value: U256::from(1),
+ data: vec![],
+ nonce: None,
+ })).unwrap();
+
+ let t = Transaction {
+ nonce: U256::zero(),
+ gas_price: U256::from(0x1000),
+ gas: U256::from(10_000_000),
+ action: Action::Call(recipient),
+ value: U256::from(0x1),
+ data: vec![]
+ };
+ tester.accounts.unlock_account_temporarily(address, "test".into()).unwrap();
+ let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap();
+ let t = t.with_signature(signature, None);
+ let rlp = encode(&t);
+
+ assert_eq!(tester.signer.requests().len(), 1);
+
+ // when
+ let request = r#"{
+ "jsonrpc":"2.0",
+ "method":"signer_confirmRequestRaw",
+ "params":["0x1", "0x"#.to_owned() + &rlp.to_hex() + r#""],
+ "id":1
+ }"#;
+ let response = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Couldn't parse parameters: Sent transaction does not match the request.","data":"[\"from\"]"},"id":1}"#;
+
+ // then
+ assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned()));
+ assert_eq!(tester.signer.requests().len(), 1);
+}
+
#[test]
fn should_generate_new_token() {
// given
diff --git a/rpc/src/v1/traits/signer.rs b/rpc/src/v1/traits/signer.rs
index 26d6899cb..eafa520d4 100644
--- a/rpc/src/v1/traits/signer.rs
+++ b/rpc/src/v1/traits/signer.rs
@@ -18,7 +18,7 @@
use jsonrpc_core::Error;
use v1::helpers::auto_args::Wrap;
-use v1::types::{U256, TransactionModification, ConfirmationRequest, ConfirmationResponse};
+use v1::types::{U256, Bytes, TransactionModification, ConfirmationRequest, ConfirmationResponse};
build_rpc_trait! {
@@ -33,6 +33,10 @@ build_rpc_trait! {
#[rpc(name = "signer_confirmRequest")]
fn confirm_request(&self, U256, TransactionModification, String) -> Result;
+ /// Confirm specific request with already signed data.
+ #[rpc(name = "signer_confirmRequestRaw")]
+ fn confirm_request_raw(&self, U256, Bytes) -> Result;
+
/// Reject the confirmation request.
#[rpc(name = "signer_rejectRequest")]
fn reject_request(&self, U256) -> Result;