// Copyright 2015-2019 Parity Technologies (UK) Ltd. // This file is part of Parity Ethereum. // Parity Ethereum 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 Ethereum 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 Ethereum. If not, see . //! Utilities and helpers for transaction dispatch. pub(crate) mod light; mod full; mod prospective_signer; #[cfg(any(test, feature = "accounts"))] mod signing; #[cfg(not(any(test, feature = "accounts")))] mod signing { use super::*; use v1::helpers::errors; /// Dummy signer implementation #[derive(Debug, Clone)] pub struct Signer; impl Signer { /// Create new instance of dummy signer (accept any AccountProvider) pub fn new(_ap: T) -> Self { Signer } } impl super::Accounts for Signer { fn sign_transaction(&self, _filled: FilledTransactionRequest, _chain_id: Option, _nonce: U256, _password: SignWith) -> Result> { Err(errors::account("Signing unsupported", "See #9997")) } fn sign_message(&self, _address: Address, _password: SignWith, _hash: SignMessage) -> Result> { Err(errors::account("Signing unsupported", "See #9997")) } fn decrypt(&self, _address: Address, _password: SignWith, _data: Bytes) -> Result> { Err(errors::account("Signing unsupported", "See #9997")) } fn supports_prospective_signing(&self, _address: &Address, _password: &SignWith) -> bool { false } fn default_account(&self) -> Address { Default::default() } fn is_unlocked(&self, _address: &Address) -> bool { false } } } pub use self::light::LightDispatcher; pub use self::full::FullDispatcher; pub use self::signing::Signer; pub use v1::helpers::nonce::Reservations; use std::fmt::Debug; use std::ops::Deref; use std::sync::Arc; use bytes::Bytes; use ethcore::client::BlockChainClient; use ethcore::miner::MinerService; use ethereum_types::{H520, H256, U256, Address}; use ethkey::{Password, Signature}; use hash::keccak; use types::transaction::{SignedTransaction, PendingTransaction}; use jsonrpc_core::{BoxFuture, Result, Error}; use jsonrpc_core::futures::{future, Future, IntoFuture}; use v1::helpers::{TransactionRequest, FilledTransactionRequest, ConfirmationPayload}; use v1::types::{ H520 as RpcH520, Bytes as RpcBytes, RichRawTransaction as RpcRichRawTransaction, ConfirmationPayload as RpcConfirmationPayload, ConfirmationResponse, EthSignRequest as RpcEthSignRequest, EIP191SignRequest as RpcSignRequest, DecryptRequest as RpcDecryptRequest, }; /// Has the capability to dispatch, sign, and decrypt. /// /// Requires a clone implementation, with the implication that it be cheap; /// usually just bumping a reference count or two. pub trait Dispatcher: Send + Sync + Clone { // TODO: when ATC exist, use zero-cost // type Out: IntoFuture /// Fill optional fields of a transaction request, fetching gas price but not nonce. fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool) -> BoxFuture; /// Sign the given transaction request without dispatching, fetching appropriate nonce. fn sign

( &self, filled: FilledTransactionRequest, signer: &Arc, password: SignWith, post_sign: P, ) -> BoxFuture where P: PostSign + 'static, ::Future: Send; /// Converts a `SignedTransaction` into `RichRawTransaction` fn enrich(&self, SignedTransaction) -> RpcRichRawTransaction; /// "Dispatch" a local transaction. fn dispatch_transaction(&self, signed_transaction: PendingTransaction) -> Result; } /// Payload to sign pub enum SignMessage { /// Eth-sign kind data (requires prefixing) Data(Bytes), /// Prefixed data hash Hash(H256), } /// Abstract transaction signer. /// /// NOTE This signer is semi-correct, it's a temporary measure to avoid moving too much code. /// If accounts are ultimately removed all password-dealing endpoints will be wiped out. pub trait Accounts: Send + Sync { /// Sign given filled transaction request for the specified chain_id. fn sign_transaction(&self, filled: FilledTransactionRequest, chain_id: Option, nonce: U256, password: SignWith) -> Result>; /// Sign given message. fn sign_message(&self, address: Address, password: SignWith, hash: SignMessage) -> Result>; /// Decrypt given message. fn decrypt(&self, address: Address, password: SignWith, data: Bytes) -> Result>; /// Returns `true` if the accounts can sign multiple times. fn supports_prospective_signing(&self, address: &Address, password: &SignWith) -> bool; /// Returns default account. fn default_account(&self) -> Address; /// Returns true if account is unlocked (i.e. can sign without a password) fn is_unlocked(&self, address: &Address) -> bool; } /// action to execute after signing /// e.g importing a transaction into the chain pub trait PostSign: Send { /// item that this PostSign returns type Item: Send; /// incase you need to perform async PostSign actions type Out: IntoFuture + Send; /// perform an action with the signed transaction fn execute(self, signer: WithToken) -> Self::Out; } impl PostSign for () { type Item = WithToken; type Out = Result; fn execute(self, signed: WithToken) -> Self::Out { Ok(signed) } } impl PostSign for F where F: FnOnce(WithToken) -> Result { type Item = T; type Out = Result; fn execute(self, signed: WithToken) -> Self::Out { (self)(signed) } } /// Single-use account token. pub type AccountToken = Password; /// Values used to unlock accounts for signing. #[derive(Clone, PartialEq)] pub enum SignWith { /// Nothing -- implies the account is already unlocked. Nothing, /// Unlock with password. Password(Password), /// Unlock with single-use token. Token(AccountToken), } impl SignWith { #[cfg(any(test, feature = "accounts"))] fn is_password(&self) -> bool { if let SignWith::Password(_) = *self { true } else { false } } } /// A value, potentially accompanied by a signing token. pub enum WithToken { /// No token. No(T), /// With token. Yes(T, AccountToken), } impl Deref for WithToken { type Target = T; fn deref(&self) -> &Self::Target { match *self { WithToken::No(ref v) => v, WithToken::Yes(ref v, _) => v, } } } impl WithToken { /// Map the value with the given closure, preserving the token. pub fn map(self, f: F) -> WithToken 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), } } /// Convert into inner value, ignoring possible token. pub fn into_value(self) -> T { match self { WithToken::No(v) => v, WithToken::Yes(v, _) => v, } } /// Convert the `WithToken` into a tuple. pub fn into_tuple(self) -> (T, Option) { match self { WithToken::No(v) => (v, None), WithToken::Yes(v, token) => (v, Some(token)) } } } impl From<(T, AccountToken)> for WithToken { fn from(tuple: (T, AccountToken)) -> Self { WithToken::Yes(tuple.0, tuple.1) } } impl From<(T, Option)> for WithToken { fn from(tuple: (T, Option)) -> Self { match tuple.1 { Some(token) => WithToken::Yes(tuple.0, token), None => WithToken::No(tuple.0), } } } /// Execute a confirmation payload. pub fn execute( dispatcher: D, signer: &Arc, payload: ConfirmationPayload, pass: SignWith ) -> BoxFuture> { match payload { ConfirmationPayload::SendTransaction(request) => { let condition = request.condition.clone().map(Into::into); let cloned_dispatcher = dispatcher.clone(); let post_sign = move |with_token_signed: WithToken| { let (signed, token) = with_token_signed.into_tuple(); let signed_transaction = PendingTransaction::new(signed, condition); cloned_dispatcher.dispatch_transaction(signed_transaction) .map(|hash| (hash, token)) }; Box::new( dispatcher.sign(request, &signer, pass, post_sign).map(|(hash, token)| { WithToken::from((ConfirmationResponse::SendTransaction(hash.into()), token)) }) ) }, ConfirmationPayload::SignTransaction(request) => { Box::new(dispatcher.sign(request, &signer, pass, ()) .map(move |result| result .map(move |tx| dispatcher.enrich(tx)) .map(ConfirmationResponse::SignTransaction) )) }, ConfirmationPayload::EthSignMessage(address, data) => { let res = signer.sign_message(address, pass, SignMessage::Data(data)) .map(|result| result .map(|s| H520(s.into_electrum())) .map(RpcH520::from) .map(ConfirmationResponse::Signature) ); Box::new(future::done(res)) }, ConfirmationPayload::SignMessage(address, data) => { let res = signer.sign_message(address, pass, SignMessage::Hash(data)) .map(|result| result .map(|rsv| H520(rsv.into_electrum())) .map(RpcH520::from) .map(ConfirmationResponse::Signature) ); Box::new(future::done(res)) }, ConfirmationPayload::Decrypt(address, data) => { let res = signer.decrypt(address, pass, data) .map(|result| result .map(RpcBytes) .map(ConfirmationResponse::Decrypt) ); Box::new(future::done(res)) }, } } /// Returns a eth_sign-compatible hash of data to sign. /// The data is prepended with special message to prevent /// malicious DApps from using the function to sign forged transactions. pub fn eth_data_hash(mut data: Bytes) -> H256 { let mut message_data = format!("\x19Ethereum Signed Message:\n{}", data.len()) .into_bytes(); message_data.append(&mut data); keccak(message_data) } /// Extract the default gas price from a client and miner. pub fn default_gas_price(client: &C, miner: &M, percentile: usize) -> U256 where C: BlockChainClient, M: MinerService, { client.gas_price_corpus(100).percentile(percentile).cloned().unwrap_or_else(|| miner.sensible_gas_price()) } /// Convert RPC confirmation payload to signer confirmation payload. /// May need to resolve in the future to fetch things like gas price. pub fn from_rpc(payload: RpcConfirmationPayload, default_account: Address, dispatcher: &D) -> BoxFuture where D: Dispatcher { match payload { RpcConfirmationPayload::SendTransaction(request) => { Box::new(dispatcher.fill_optional_fields(request.into(), default_account, false) .map(ConfirmationPayload::SendTransaction)) }, RpcConfirmationPayload::SignTransaction(request) => { Box::new(dispatcher.fill_optional_fields(request.into(), default_account, false) .map(ConfirmationPayload::SignTransaction)) }, RpcConfirmationPayload::Decrypt(RpcDecryptRequest { address, msg }) => { Box::new(future::ok(ConfirmationPayload::Decrypt(address.into(), msg.into()))) }, RpcConfirmationPayload::EthSignMessage(RpcEthSignRequest { address, data }) => { Box::new(future::ok(ConfirmationPayload::EthSignMessage(address.into(), data.into()))) }, RpcConfirmationPayload::EIP191SignMessage(RpcSignRequest { address, data }) => { Box::new(future::ok(ConfirmationPayload::SignMessage(address.into(), data.into()))) }, } }