RPC for confirming with token
This commit is contained in:
parent
6397556cbb
commit
c028f106b1
@ -401,6 +401,28 @@ impl AccountProvider {
|
||||
Ok((signature, new_token))
|
||||
}
|
||||
|
||||
/// Decrypts a message with given token. Returns a token to use in next operation for this account.
|
||||
pub fn decrypt_with_token(&self, account: Address, token: AccountToken, shared_mac: &[u8], message: &[u8])
|
||||
-> Result<(Vec<u8>, AccountToken), Error>
|
||||
{
|
||||
let is_std_password = try!(self.sstore.test_password(&account, &token));
|
||||
|
||||
let new_token = random_string(16);
|
||||
let message = if is_std_password {
|
||||
// Insert to transient store
|
||||
try!(self.sstore.copy_account(&self.transient_sstore, &account, &token, &new_token));
|
||||
// decrypt
|
||||
try!(self.sstore.decrypt(&account, &token, shared_mac, message))
|
||||
} else {
|
||||
// check transient store
|
||||
try!(self.transient_sstore.change_password(&account, &token, &new_token));
|
||||
// and decrypt
|
||||
try!(self.transient_sstore.decrypt(&account, &token, shared_mac, message))
|
||||
};
|
||||
|
||||
Ok((message, new_token))
|
||||
}
|
||||
|
||||
/// 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)));
|
||||
@ -477,8 +499,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn should_sign_and_return_token() {
|
||||
let kp = Random.generate().unwrap();
|
||||
// given
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
||||
|
||||
|
@ -14,6 +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 std::fmt::Debug;
|
||||
use rlp;
|
||||
use util::{Address, H256, U256, Uint, Bytes};
|
||||
use util::bytes::ToPretty;
|
||||
@ -37,46 +38,101 @@ use v1::types::{
|
||||
|
||||
pub const DEFAULT_MAC: [u8; 2] = [0, 0];
|
||||
|
||||
pub fn execute<C, M>(client: &C, miner: &M, accounts: &AccountProvider, payload: ConfirmationPayload, pass: Option<String>) -> Result<ConfirmationResponse, Error>
|
||||
type AccountToken = String;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum SignWith {
|
||||
Nothing,
|
||||
Password(String),
|
||||
Token(AccountToken),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WithToken<T: Debug> {
|
||||
No(T),
|
||||
Yes(T, AccountToken),
|
||||
}
|
||||
|
||||
impl<T: Debug> WithToken<T> {
|
||||
pub fn map<S, F>(self, f: F) -> WithToken<S> where
|
||||
S: Debug,
|
||||
F: FnOnce(T) -> S,
|
||||
{
|
||||
match self {
|
||||
WithToken::No(v) => WithToken::No(f(v)),
|
||||
WithToken::Yes(v, token) => WithToken::Yes(f(v), token),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_value(self) -> T {
|
||||
match self {
|
||||
WithToken::No(v) => v,
|
||||
WithToken::Yes(v, ..) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> From<(T, AccountToken)> for WithToken<T> {
|
||||
fn from(tuple: (T, AccountToken)) -> Self {
|
||||
WithToken::Yes(tuple.0, tuple.1)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute<C, M>(client: &C, miner: &M, accounts: &AccountProvider, payload: ConfirmationPayload, pass: SignWith) -> Result<WithToken<ConfirmationResponse>, Error>
|
||||
where C: MiningBlockChainClient, M: MinerService
|
||||
{
|
||||
match payload {
|
||||
ConfirmationPayload::SendTransaction(request) => {
|
||||
sign_and_dispatch(client, miner, accounts, request, pass)
|
||||
.map(RpcH256::from)
|
||||
.map(ConfirmationResponse::SendTransaction)
|
||||
.map(|result| result
|
||||
.map(RpcH256::from)
|
||||
.map(ConfirmationResponse::SendTransaction)
|
||||
)
|
||||
},
|
||||
ConfirmationPayload::SignTransaction(request) => {
|
||||
sign_no_dispatch(client, miner, accounts, request, pass)
|
||||
.map(RpcRichRawTransaction::from)
|
||||
.map(ConfirmationResponse::SignTransaction)
|
||||
.map(|result| result
|
||||
.map(RpcRichRawTransaction::from)
|
||||
.map(ConfirmationResponse::SignTransaction)
|
||||
)
|
||||
},
|
||||
ConfirmationPayload::Signature(address, hash) => {
|
||||
signature(accounts, address, hash, pass)
|
||||
.map(RpcH520::from)
|
||||
.map(ConfirmationResponse::Signature)
|
||||
.map(|result| result
|
||||
.map(RpcH520::from)
|
||||
.map(ConfirmationResponse::Signature)
|
||||
)
|
||||
},
|
||||
ConfirmationPayload::Decrypt(address, data) => {
|
||||
decrypt(accounts, address, data, pass)
|
||||
.map(RpcBytes)
|
||||
.map(ConfirmationResponse::Decrypt)
|
||||
.map(|result| result
|
||||
.map(RpcBytes)
|
||||
.map(ConfirmationResponse::Decrypt)
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn signature(accounts: &AccountProvider, address: Address, hash: H256, password: Option<String>) -> 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),
|
||||
fn signature(accounts: &AccountProvider, address: Address, hash: H256, password: SignWith) -> Result<WithToken<Signature>, Error> {
|
||||
match password.clone() {
|
||||
SignWith::Nothing => accounts.sign(address, None, hash).map(WithToken::No),
|
||||
SignWith::Password(pass) => accounts.sign(address, Some(pass), hash).map(WithToken::No),
|
||||
SignWith::Token(token) => accounts.sign_with_token(address, token, hash).map(Into::into),
|
||||
}.map_err(|e| match password {
|
||||
SignWith::Nothing => errors::from_signing_error(e),
|
||||
_ => errors::from_password_error(e),
|
||||
})
|
||||
}
|
||||
|
||||
fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: Option<String>) -> Result<Bytes, 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),
|
||||
})
|
||||
fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: SignWith) -> Result<WithToken<Bytes>, Error> {
|
||||
match password.clone() {
|
||||
SignWith::Nothing => accounts.decrypt(address, None, &DEFAULT_MAC, &msg).map(WithToken::No),
|
||||
SignWith::Password(pass) => accounts.decrypt(address, Some(pass), &DEFAULT_MAC, &msg).map(WithToken::No),
|
||||
SignWith::Token(token) => accounts.decrypt_with_token(address, token, &DEFAULT_MAC, &msg).map(Into::into),
|
||||
}.map_err(|e| match password {
|
||||
SignWith::Nothing => errors::from_signing_error(e),
|
||||
_ => errors::from_password_error(e),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result<H256, Error>
|
||||
@ -88,7 +144,7 @@ pub fn dispatch_transaction<C, M>(client: &C, miner: &M, signed_transaction: Sig
|
||||
.map(|_| hash)
|
||||
}
|
||||
|
||||
pub fn sign_no_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: Option<String>) -> Result<SignedTransaction, Error>
|
||||
pub fn sign_no_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: SignWith) -> Result<WithToken<SignedTransaction>, Error>
|
||||
where C: MiningBlockChainClient, M: MinerService {
|
||||
|
||||
let network_id = client.signing_network_id();
|
||||
@ -110,20 +166,32 @@ pub fn sign_no_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider,
|
||||
|
||||
let hash = t.hash(network_id);
|
||||
let signature = try!(signature(accounts, address, hash, password));
|
||||
t.with_signature(signature, network_id)
|
||||
signature.map(|sig| {
|
||||
t.with_signature(sig, network_id)
|
||||
})
|
||||
};
|
||||
Ok(signed_transaction)
|
||||
}
|
||||
|
||||
pub fn sign_and_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: Option<String>) -> Result<H256, Error>
|
||||
pub fn sign_and_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: SignWith) -> Result<WithToken<H256>, Error>
|
||||
where C: MiningBlockChainClient, M: MinerService
|
||||
{
|
||||
|
||||
let network_id = client.signing_network_id();
|
||||
let signed_transaction = try!(sign_no_dispatch(client, miner, accounts, filled, password));
|
||||
|
||||
let (signed_transaction, token) = match signed_transaction {
|
||||
WithToken::No(signed_transaction) => (signed_transaction, None),
|
||||
WithToken::Yes(signed_transaction, token) => (signed_transaction, Some(token)),
|
||||
};
|
||||
|
||||
trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", rlp::encode(&signed_transaction).to_vec().pretty(), network_id);
|
||||
dispatch_transaction(&*client, &*miner, signed_transaction)
|
||||
dispatch_transaction(&*client, &*miner, signed_transaction).map(|hash| {
|
||||
match token {
|
||||
Some(ref token) => WithToken::Yes(hash, token.clone()),
|
||||
None => WithToken::No(hash),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fill_optional_fields<C, M>(request: TransactionRequest, client: &C, miner: &M) -> FilledTransactionRequest
|
||||
|
@ -114,7 +114,7 @@ impl<C: 'static, M: 'static> Personal for PersonalClient<C, M> where C: MiningBl
|
||||
&*miner,
|
||||
&*accounts,
|
||||
request,
|
||||
Some(password)
|
||||
).map(Into::into)
|
||||
dispatch::SignWith::Password(password)
|
||||
).map(|v| v.into_value().into())
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ use ethcore::miner::MinerService;
|
||||
|
||||
use jsonrpc_core::Error;
|
||||
use v1::traits::Signer;
|
||||
use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, U256, Bytes};
|
||||
use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, U256, Bytes};
|
||||
use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload};
|
||||
use v1::helpers::dispatch::{self, dispatch_transaction};
|
||||
|
||||
@ -60,6 +60,35 @@ impl<C: 'static, M: 'static> SignerClient<C, M> where C: MiningBlockChainClient,
|
||||
take_weak!(self.client).keep_alive();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn confirm_internal<F>(&self, id: U256, modification: TransactionModification, f: F) -> Result<ConfirmationResponse, Error> where
|
||||
F: FnOnce(&C, &M, &AccountProvider, ConfirmationPayload) -> Result<ConfirmationResponse, Error>,
|
||||
{
|
||||
try!(self.active());
|
||||
|
||||
let id = id.into();
|
||||
let accounts = take_weak!(self.accounts);
|
||||
let signer = take_weak!(self.signer);
|
||||
let client = take_weak!(self.client);
|
||||
let miner = take_weak!(self.miner);
|
||||
|
||||
signer.peek(&id).map(|confirmation| {
|
||||
let mut payload = confirmation.payload.clone();
|
||||
// Modify payload
|
||||
match (&mut payload, modification.gas_price) {
|
||||
(&mut ConfirmationPayload::SendTransaction(ref mut request), Some(gas_price)) => {
|
||||
request.gas_price = gas_price.into();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
let result = f(&*client, &*miner, &*accounts, payload);
|
||||
// Execute
|
||||
if let Ok(ref response) = result {
|
||||
signer.request_confirmed(id, Ok(response.clone()));
|
||||
}
|
||||
result
|
||||
}).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: 'static, M: 'static> Signer for SignerClient<C, M> where C: MiningBlockChainClient, M: MinerService {
|
||||
@ -78,30 +107,14 @@ impl<C: 'static, M: 'static> Signer for SignerClient<C, M> where C: MiningBlockC
|
||||
// TODO [ToDr] TransactionModification is redundant for some calls
|
||||
// might be better to replace it in future
|
||||
fn confirm_request(&self, id: U256, modification: TransactionModification, pass: String) -> Result<ConfirmationResponse, Error> {
|
||||
try!(self.active());
|
||||
self.confirm_internal(id, modification, move |client, miner, accounts, payload| {
|
||||
dispatch::execute(client, miner, accounts, payload, dispatch::SignWith::Password(pass))
|
||||
.map(|v| v.into_value())
|
||||
})
|
||||
}
|
||||
|
||||
let id = id.into();
|
||||
let accounts = take_weak!(self.accounts);
|
||||
let signer = take_weak!(self.signer);
|
||||
let client = take_weak!(self.client);
|
||||
let miner = take_weak!(self.miner);
|
||||
|
||||
signer.peek(&id).map(|confirmation| {
|
||||
let mut payload = confirmation.payload.clone();
|
||||
// Modify payload
|
||||
match (&mut payload, modification.gas_price) {
|
||||
(&mut ConfirmationPayload::SendTransaction(ref mut request), Some(gas_price)) => {
|
||||
request.gas_price = gas_price.into();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
// Execute
|
||||
let result = dispatch::execute(&*client, &*miner, &*accounts, payload, Some(pass));
|
||||
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 confirm_request_with_token(&self, id: U256, modification: TransactionModification, token: String) -> Result<ConfirmationResponseWithToken, Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn confirm_request_raw(&self, id: U256, bytes: Bytes) -> Result<ConfirmationResponse, Error> {
|
||||
|
@ -99,7 +99,9 @@ impl<C, M> SigningQueueClient<C, M> where
|
||||
|
||||
let sender = payload.sender();
|
||||
if accounts.is_unlocked(sender) {
|
||||
return dispatch::execute(&*client, &*miner, &*accounts, payload, None).map(DispatchResult::Value);
|
||||
return dispatch::execute(&*client, &*miner, &*accounts, payload, dispatch::SignWith::Nothing)
|
||||
.map(|v| v.into_value())
|
||||
.map(DispatchResult::Value);
|
||||
}
|
||||
|
||||
take_weak!(self.signer).add_request(payload)
|
||||
|
@ -75,7 +75,8 @@ impl<C, M> SigningUnsafeClient<C, M> where
|
||||
let accounts = take_weak!(self.accounts);
|
||||
|
||||
let payload = dispatch::from_rpc(payload, &*client, &*miner);
|
||||
dispatch::execute(&*client, &*miner, &*accounts, payload, None)
|
||||
dispatch::execute(&*client, &*miner, &*accounts, payload, dispatch::SignWith::Nothing)
|
||||
.map(|v| v.into_value())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,6 +209,52 @@ fn should_confirm_transaction_and_dispatch() {
|
||||
assert_eq!(tester.miner.imported_transactions.lock().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_confirm_transaction_with_token() {
|
||||
// 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![]
|
||||
};
|
||||
let (signature, token) = tester.accounts.sign_with_token(address, "test".into(), t.hash(None)).unwrap();
|
||||
let t = t.with_signature(signature, None);
|
||||
|
||||
assert_eq!(tester.signer.requests().len(), 1);
|
||||
|
||||
// when
|
||||
let request = r#"{
|
||||
"jsonrpc":"2.0",
|
||||
"method":"signer_confirmRequestWithToken",
|
||||
"params":["0x1", {"gasPrice":"0x1000"}, ""#.to_owned() + &token + r#""],
|
||||
"id":1
|
||||
}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":{"result":""#.to_owned() +
|
||||
format!("0x{:?}", t.hash()).as_ref() +
|
||||
r#""token":""},"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_confirm_transaction_with_rlp() {
|
||||
// given
|
||||
|
@ -18,7 +18,7 @@
|
||||
use jsonrpc_core::Error;
|
||||
|
||||
use v1::helpers::auto_args::Wrap;
|
||||
use v1::types::{U256, Bytes, TransactionModification, ConfirmationRequest, ConfirmationResponse};
|
||||
use v1::types::{U256, Bytes, TransactionModification, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken};
|
||||
|
||||
|
||||
build_rpc_trait! {
|
||||
@ -33,6 +33,10 @@ build_rpc_trait! {
|
||||
#[rpc(name = "signer_confirmRequest")]
|
||||
fn confirm_request(&self, U256, TransactionModification, String) -> Result<ConfirmationResponse, Error>;
|
||||
|
||||
/// Confirm specific request with token.
|
||||
#[rpc(name = "signer_confirmRequestWithToken")]
|
||||
fn confirm_request_with_token(&self, U256, TransactionModification, String) -> Result<ConfirmationResponseWithToken, Error>;
|
||||
|
||||
/// Confirm specific request with already signed data.
|
||||
#[rpc(name = "signer_confirmRequestRaw")]
|
||||
fn confirm_request_raw(&self, U256, Bytes) -> Result<ConfirmationResponse, Error>;
|
||||
|
@ -101,6 +101,15 @@ impl Serialize for ConfirmationResponse {
|
||||
}
|
||||
}
|
||||
|
||||
/// Confirmation response with additional token for further requests
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct ConfirmationResponseWithToken {
|
||||
/// Actual response
|
||||
pub result: ConfirmationResponse,
|
||||
/// New token
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
/// Confirmation payload, i.e. the thing to be confirmed
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
|
||||
pub enum ConfirmationPayload {
|
||||
@ -247,5 +256,21 @@ mod tests {
|
||||
gas_price: None,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serialize_confirmation_response_with_token() {
|
||||
// given
|
||||
let response = ConfirmationResponseWithToken {
|
||||
result: ConfirmationResponse::SendTransaction(H256::default()),
|
||||
token: "test-token".into(),
|
||||
};
|
||||
|
||||
// when
|
||||
let res = serde_json::to_string(&response);
|
||||
let expected = r#"{"result":"0x0000000000000000000000000000000000000000","token":"test-token"}"#;
|
||||
|
||||
// then
|
||||
assert_eq!(res.unwrap(), expected.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,9 @@ pub use self::bytes::Bytes;
|
||||
pub use self::block::{RichBlock, Block, BlockTransactions};
|
||||
pub use self::block_number::BlockNumber;
|
||||
pub use self::call_request::CallRequest;
|
||||
pub use self::confirmations::{ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, TransactionModification, SignRequest, DecryptRequest, Either};
|
||||
pub use self::confirmations::{
|
||||
ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, TransactionModification, SignRequest, DecryptRequest, Either
|
||||
};
|
||||
pub use self::filter::{Filter, FilterChanges};
|
||||
pub use self::hash::{H64, H160, H256, H512, H520, H2048};
|
||||
pub use self::index::Index;
|
||||
|
Loading…
Reference in New Issue
Block a user