Add personal_sign and personal_ecRecover RPC methods (#7453)
				
					
				
			* rpc: implement personal_sign * rpc: add test for personal_sign * rpc: implement personal_ecRecover * rpc: add test for personal_ecRecover * rpc: fix order of arguments in personal_sign * rpc: remove auxiliary methods for sign and ec_recover
This commit is contained in:
		
							parent
							
								
									73f5cc57be
								
							
						
					
					
						commit
						40551b8ffd
					
				@ -20,16 +20,23 @@ use std::sync::Arc;
 | 
			
		||||
use ethcore::account_provider::AccountProvider;
 | 
			
		||||
use ethcore::transaction::PendingTransaction;
 | 
			
		||||
 | 
			
		||||
use ethereum_types::{U128, Address};
 | 
			
		||||
use bytes::ToPretty;
 | 
			
		||||
 | 
			
		||||
use bytes::{Bytes, ToPretty};
 | 
			
		||||
use ethereum_types::{H520, U128, Address};
 | 
			
		||||
use ethkey::{public_to_address, recover, Signature};
 | 
			
		||||
use jsonrpc_core::{BoxFuture, Result};
 | 
			
		||||
use jsonrpc_core::futures::{future, Future};
 | 
			
		||||
use v1::helpers::errors;
 | 
			
		||||
use v1::helpers::dispatch::{Dispatcher, SignWith};
 | 
			
		||||
use v1::helpers::dispatch::{self, eth_data_hash, Dispatcher, SignWith};
 | 
			
		||||
use v1::helpers::accounts::unwrap_provider;
 | 
			
		||||
use v1::traits::Personal;
 | 
			
		||||
use v1::types::{H160 as RpcH160, H256 as RpcH256, U128 as RpcU128, TransactionRequest, RichRawTransaction as RpcRichRawTransaction};
 | 
			
		||||
use v1::types::{
 | 
			
		||||
	H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, U128 as RpcU128,
 | 
			
		||||
	Bytes as RpcBytes,
 | 
			
		||||
	ConfirmationPayload as RpcConfirmationPayload,
 | 
			
		||||
	ConfirmationResponse as RpcConfirmationResponse,
 | 
			
		||||
	TransactionRequest,
 | 
			
		||||
	RichRawTransaction as RpcRichRawTransaction,
 | 
			
		||||
};
 | 
			
		||||
use v1::metadata::Metadata;
 | 
			
		||||
 | 
			
		||||
/// Account management (personal) rpc implementation.
 | 
			
		||||
@ -132,6 +139,39 @@ impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn sign(&self, data: RpcBytes, account: RpcH160, password: String) -> BoxFuture<RpcH520> {
 | 
			
		||||
		let dispatcher = self.dispatcher.clone();
 | 
			
		||||
		let accounts = try_bf!(self.account_provider());
 | 
			
		||||
 | 
			
		||||
		let payload = RpcConfirmationPayload::EthSignMessage((account.clone(), data).into());
 | 
			
		||||
 | 
			
		||||
		Box::new(dispatch::from_rpc(payload, account.into(), &dispatcher)
 | 
			
		||||
				 .and_then(|payload| {
 | 
			
		||||
					 dispatch::execute(dispatcher, accounts, payload, dispatch::SignWith::Password(password))
 | 
			
		||||
				 })
 | 
			
		||||
				 .map(|v| v.into_value())
 | 
			
		||||
				 .then(|res| match res {
 | 
			
		||||
					 Ok(RpcConfirmationResponse::Signature(signature)) => Ok(signature),
 | 
			
		||||
					 Err(e) => Err(e),
 | 
			
		||||
					 e => Err(errors::internal("Unexpected result", e)),
 | 
			
		||||
				 }))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn ec_recover(&self, data: RpcBytes, signature: RpcH520) -> BoxFuture<RpcH160> {
 | 
			
		||||
		let signature: H520 = signature.into();
 | 
			
		||||
		let signature = Signature::from_electrum(&signature);
 | 
			
		||||
		let data: Bytes = data.into();
 | 
			
		||||
 | 
			
		||||
		let hash = eth_data_hash(data);
 | 
			
		||||
		let account = recover(&signature.into(), &hash)
 | 
			
		||||
			.map_err(errors::encryption)
 | 
			
		||||
			.map(|public| {
 | 
			
		||||
				public_to_address(&public).into()
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
		Box::new(future::done(account))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn sign_transaction(&self, meta: Metadata, request: TransactionRequest, password: String) -> BoxFuture<RpcRichRawTransaction> {
 | 
			
		||||
		Box::new(self.do_sign_transaction(meta, request, password)
 | 
			
		||||
			.map(|(pending_tx, dispatcher)| dispatcher.enrich(pending_tx.transaction)))
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,7 @@
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
use bytes::ToPretty;
 | 
			
		||||
use ethereum_types::{U256, Address};
 | 
			
		||||
use ethcore::account_provider::AccountProvider;
 | 
			
		||||
use ethcore::client::TestBlockChainClient;
 | 
			
		||||
@ -26,8 +27,9 @@ use parking_lot::Mutex;
 | 
			
		||||
 | 
			
		||||
use v1::{PersonalClient, Personal, Metadata};
 | 
			
		||||
use v1::helpers::nonce;
 | 
			
		||||
use v1::helpers::dispatch::FullDispatcher;
 | 
			
		||||
use v1::helpers::dispatch::{eth_data_hash, FullDispatcher};
 | 
			
		||||
use v1::tests::helpers::TestMinerService;
 | 
			
		||||
use v1::types::H520;
 | 
			
		||||
 | 
			
		||||
struct PersonalTester {
 | 
			
		||||
	accounts: Arc<AccountProvider>,
 | 
			
		||||
@ -118,6 +120,53 @@ fn invalid_password_test(method: &str)
 | 
			
		||||
	assert_eq!(tester.io.handle_request_sync(request.as_ref()), Some(response.into()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn sign() {
 | 
			
		||||
	let tester = setup();
 | 
			
		||||
	let address = tester.accounts.new_account("password123").unwrap();
 | 
			
		||||
	let data = vec![5u8];
 | 
			
		||||
 | 
			
		||||
	let request = r#"{
 | 
			
		||||
		"jsonrpc": "2.0",
 | 
			
		||||
		"method": "personal_sign",
 | 
			
		||||
		"params": [
 | 
			
		||||
			""#.to_owned() + format!("0x{}", data.to_hex()).as_ref() + r#"",
 | 
			
		||||
			""# + format!("0x{:?}", address).as_ref() + r#"",
 | 
			
		||||
			"password123"
 | 
			
		||||
		],
 | 
			
		||||
		"id": 1
 | 
			
		||||
	}"#;
 | 
			
		||||
 | 
			
		||||
	let hash = eth_data_hash(data);
 | 
			
		||||
	let signature = H520(tester.accounts.sign(address, Some("password123".into()), hash).unwrap().into_electrum());
 | 
			
		||||
	let signature = format!("0x{:?}", signature);
 | 
			
		||||
 | 
			
		||||
	let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + &signature + r#"","id":1}"#;
 | 
			
		||||
 | 
			
		||||
	assert_eq!(tester.io.handle_request_sync(request.as_ref()), Some(response));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn sign_with_invalid_password() {
 | 
			
		||||
	let tester = setup();
 | 
			
		||||
	let address = tester.accounts.new_account("password123").unwrap();
 | 
			
		||||
 | 
			
		||||
	let request = r#"{
 | 
			
		||||
		"jsonrpc": "2.0",
 | 
			
		||||
		"method": "personal_sign",
 | 
			
		||||
		"params": [
 | 
			
		||||
			"0x0000000000000000000000000000000000000000000000000000000000000005",
 | 
			
		||||
			""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"",
 | 
			
		||||
			""
 | 
			
		||||
		],
 | 
			
		||||
		"id": 1
 | 
			
		||||
	}"#;
 | 
			
		||||
 | 
			
		||||
	let response = r#"{"jsonrpc":"2.0","error":{"code":-32021,"message":"Account password is invalid or account does not exist.","data":"SStore(InvalidPassword)"},"id":1}"#;
 | 
			
		||||
 | 
			
		||||
	assert_eq!(tester.io.handle_request_sync(request.as_ref()), Some(response.into()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn sign_transaction_with_invalid_password() {
 | 
			
		||||
	invalid_password_test("personal_signTransaction");
 | 
			
		||||
@ -190,6 +239,52 @@ fn sign_and_send_test(method: &str) {
 | 
			
		||||
	assert_eq!(tester.io.handle_request_sync(request.as_ref()), Some(response));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn ec_recover() {
 | 
			
		||||
	let tester = setup();
 | 
			
		||||
	let address = tester.accounts.new_account("password123").unwrap();
 | 
			
		||||
	let data = vec![5u8];
 | 
			
		||||
 | 
			
		||||
	let hash = eth_data_hash(data.clone());
 | 
			
		||||
	let signature = H520(tester.accounts.sign(address, Some("password123".into()), hash).unwrap().into_electrum());
 | 
			
		||||
	let signature = format!("0x{:?}", signature);
 | 
			
		||||
 | 
			
		||||
	let request = r#"{
 | 
			
		||||
		"jsonrpc": "2.0",
 | 
			
		||||
		"method": "personal_ecRecover",
 | 
			
		||||
		"params": [
 | 
			
		||||
			""#.to_owned() + format!("0x{}", data.to_hex()).as_ref() + r#"",
 | 
			
		||||
			""# + &signature + r#""
 | 
			
		||||
		],
 | 
			
		||||
		"id": 1
 | 
			
		||||
	}"#;
 | 
			
		||||
 | 
			
		||||
	let address = format!("0x{:?}", address);
 | 
			
		||||
	let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + &address + r#"","id":1}"#;
 | 
			
		||||
 | 
			
		||||
	assert_eq!(tester.io.handle_request_sync(request.as_ref()), Some(response.into()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn ec_recover_invalid_signature() {
 | 
			
		||||
	let tester = setup();
 | 
			
		||||
	let data = vec![5u8];
 | 
			
		||||
 | 
			
		||||
	let request = r#"{
 | 
			
		||||
		"jsonrpc": "2.0",
 | 
			
		||||
		"method": "personal_ecRecover",
 | 
			
		||||
		"params": [
 | 
			
		||||
			""#.to_owned() + format!("0x{}", data.to_hex()).as_ref() + r#"",
 | 
			
		||||
			"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
 | 
			
		||||
		],
 | 
			
		||||
		"id": 1
 | 
			
		||||
	}"#;
 | 
			
		||||
 | 
			
		||||
	let response = r#"{"jsonrpc":"2.0","error":{"code":-32055,"message":"Encryption error.","data":"InvalidSignature"},"id":1}"#;
 | 
			
		||||
 | 
			
		||||
	assert_eq!(tester.io.handle_request_sync(request.as_ref()), Some(response.into()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn should_unlock_not_account_temporarily_if_allow_perm_is_disabled() {
 | 
			
		||||
	let tester = setup();
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@
 | 
			
		||||
//! Personal rpc interface.
 | 
			
		||||
use jsonrpc_core::{BoxFuture, Result};
 | 
			
		||||
 | 
			
		||||
use v1::types::{U128, H160, H256, TransactionRequest, RichRawTransaction as RpcRichRawTransaction};
 | 
			
		||||
use v1::types::{Bytes, U128, H160, H256, H520, TransactionRequest, RichRawTransaction as RpcRichRawTransaction};
 | 
			
		||||
 | 
			
		||||
build_rpc_trait! {
 | 
			
		||||
	/// Personal rpc interface. Safe (read-only) functions.
 | 
			
		||||
@ -37,6 +37,16 @@ build_rpc_trait! {
 | 
			
		||||
		#[rpc(name = "personal_unlockAccount")]
 | 
			
		||||
		fn unlock_account(&self, H160, String, Option<U128>) -> Result<bool>;
 | 
			
		||||
 | 
			
		||||
		/// Signs the hash of data with given account signature using the given password to unlock the account during
 | 
			
		||||
		/// the request.
 | 
			
		||||
		#[rpc(name = "personal_sign")]
 | 
			
		||||
		fn sign(&self, Bytes, H160, String) -> BoxFuture<H520>;
 | 
			
		||||
 | 
			
		||||
		/// Returns the account associated with the private key that was used to calculate the signature in
 | 
			
		||||
		/// `personal_sign`.
 | 
			
		||||
		#[rpc(name = "personal_ecRecover")]
 | 
			
		||||
		fn ec_recover(&self, Bytes, H520) -> BoxFuture<H160>;
 | 
			
		||||
 | 
			
		||||
		/// Signs transaction. The account is not unlocked in such case.
 | 
			
		||||
		#[rpc(meta, name = "personal_signTransaction")]
 | 
			
		||||
		fn sign_transaction(&self, Self::Metadata, TransactionRequest, String) -> BoxFuture<RpcRichRawTransaction>;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user