From 52eae66c72433bbfb359ce19a2935dff7b071ff1 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 12 Apr 2017 11:15:13 +0100 Subject: [PATCH] Add raw hash signing (#5423) * add sign any * Add RPC signMessage call to JS API * Add signMessage to JSON RPC docs * PostSignTransaction -> EthSignMessage * fix doc typo * revert incorect naming --- js/src/api/rpc/parity/parity.js | 5 +++++ js/src/jsonrpc/interfaces/parity.js | 26 ++++++++++++++++++++++ rpc/src/v1/helpers/dispatch.rs | 6 ++--- rpc/src/v1/helpers/requests.rs | 6 ++--- rpc/src/v1/impls/parity_accounts.rs | 15 +++++++++++-- rpc/src/v1/impls/signing.rs | 4 ++-- rpc/src/v1/impls/signing_unsafe.rs | 2 +- rpc/src/v1/tests/mocked/parity_accounts.rs | 17 ++++++++++++++ rpc/src/v1/tests/mocked/signer.rs | 4 ++-- rpc/src/v1/traits/parity_accounts.rs | 6 ++++- rpc/src/v1/types/confirmations.rs | 8 +++---- 11 files changed, 81 insertions(+), 18 deletions(-) diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index f38e29e7b..16b14eaef 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -522,6 +522,11 @@ export default class Parity { .then(outNumber); } + signMessage (address, password, messageHash) { + return this._transport + .execute('parity_signMessage', inAddress(address), password, inHex(messageHash)); + } + testPassword (account, password) { return this._transport .execute('parity_testPassword', inAddress(account), password); diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index 3e6fa8475..e7a5f46c9 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -1881,5 +1881,31 @@ export default { desc: 'Decrypted message.', example: withComment('0x68656c6c6f20776f726c64', 'hello world') } + }, + + signMessage: { + desc: 'Sign the hashed message bytes with the given account.', + params: [ + { + type: Address, + desc: 'Account which signs the message.', + example: '0xc171033d5cbff7175f29dfd3a63dda3d6f8f385e' + }, + { + type: String, + desc: 'Passphrase to unlock the account.', + example: 'password1' + }, + { + type: Data, + desc: 'Hashed message.', + example: '0xbc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a' + } + ], + returns: { + type: Data, + desc: 'Message signature.', + example: '0x1d9e33a8cf8bfc089a172bca01da462f9e359c6cb1b0f29398bc884e4d18df4f78588aee4fb5cc067ca62d2abab995e0bba29527be6ac98105b0320020a2efaf00' + } } }; diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index d58a211ed..c6af8d7e7 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -474,7 +474,7 @@ pub fn execute( .map(ConfirmationResponse::SignTransaction) ).boxed() }, - ConfirmationPayload::Signature(address, mut data) => { + ConfirmationPayload::EthSignMessage(address, mut data) => { let mut message_data = format!("\x19Ethereum Signed Message:\n{}", data.len()) .into_bytes(); @@ -574,8 +574,8 @@ pub fn from_rpc(payload: RpcConfirmationPayload, default_account: Address, di RpcConfirmationPayload::Decrypt(RpcDecryptRequest { address, msg }) => { future::ok(ConfirmationPayload::Decrypt(address.into(), msg.into())).boxed() }, - RpcConfirmationPayload::Signature(RpcSignRequest { address, data }) => { - future::ok(ConfirmationPayload::Signature(address.into(), data.into())).boxed() + RpcConfirmationPayload::EthSignMessage(RpcSignRequest { address, data }) => { + future::ok(ConfirmationPayload::EthSignMessage(address.into(), data.into())).boxed() }, } } diff --git a/rpc/src/v1/helpers/requests.rs b/rpc/src/v1/helpers/requests.rs index 4a3a3704d..aa3a4c3d4 100644 --- a/rpc/src/v1/helpers/requests.rs +++ b/rpc/src/v1/helpers/requests.rs @@ -113,8 +113,8 @@ pub enum ConfirmationPayload { SendTransaction(FilledTransactionRequest), /// Sign Transaction SignTransaction(FilledTransactionRequest), - /// Sign request - Signature(Address, Bytes), + /// Sign a message with an Ethereum specific security prefix. + EthSignMessage(Address, Bytes), /// Decrypt request Decrypt(Address, Bytes), } @@ -124,7 +124,7 @@ impl ConfirmationPayload { match *self { ConfirmationPayload::SendTransaction(ref request) => request.from, ConfirmationPayload::SignTransaction(ref request) => request.from, - ConfirmationPayload::Signature(ref address, _) => *address, + ConfirmationPayload::EthSignMessage(ref address, _) => *address, ConfirmationPayload::Decrypt(ref address, _) => *address, } } diff --git a/rpc/src/v1/impls/parity_accounts.rs b/rpc/src/v1/impls/parity_accounts.rs index c22285cc9..cc206696f 100644 --- a/rpc/src/v1/impls/parity_accounts.rs +++ b/rpc/src/v1/impls/parity_accounts.rs @@ -17,7 +17,7 @@ //! Account management (personal) rpc implementation use std::sync::{Arc, Weak}; use std::collections::BTreeMap; -use util::{Address}; +use util::Address; use ethkey::{Brain, Generator, Secret}; use ethstore::KeyFile; @@ -27,7 +27,7 @@ use jsonrpc_core::Error; use v1::helpers::errors; use v1::helpers::accounts::unwrap_provider; use v1::traits::ParityAccounts; -use v1::types::{H160 as RpcH160, H256 as RpcH256, DappId, Derive, DeriveHierarchical, DeriveHash}; +use v1::types::{H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, DappId, Derive, DeriveHierarchical, DeriveHash}; /// Account management (personal) rpc implementation. pub struct ParityAccountsClient { @@ -334,6 +334,17 @@ impl ParityAccounts for ParityAccountsClient { .map(Into::into) .map_err(|e| errors::account("Could not export account.", e)) } + + fn sign_message(&self, addr: RpcH160, password: String, message: RpcH256) -> Result { + self.account_provider()? + .sign( + addr.into(), + Some(password), + message.into() + ) + .map(Into::into) + .map_err(|e| errors::account("Could not sign message.", e)) + } } fn into_vec(a: Vec) -> Vec where diff --git a/rpc/src/v1/impls/signing.rs b/rpc/src/v1/impls/signing.rs index dfb0c4f80..376418ead 100644 --- a/rpc/src/v1/impls/signing.rs +++ b/rpc/src/v1/impls/signing.rs @@ -140,7 +140,7 @@ impl ParitySigning for SigningQueueClient { fn post_sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture, Error> { let pending = self.pending.clone(); self.dispatch( - RpcConfirmationPayload::Signature((address.clone(), data).into()), + RpcConfirmationPayload::EthSignMessage((address.clone(), data).into()), DefaultAccount::Provided(address.into()), meta.origin ).map(move |result| match result { @@ -216,7 +216,7 @@ impl EthSigning for SigningQueueClient { fn sign(&self, meta: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture { let res = self.dispatch( - RpcConfirmationPayload::Signature((address.clone(), data).into()), + RpcConfirmationPayload::EthSignMessage((address.clone(), data).into()), address.into(), meta.origin, ); diff --git a/rpc/src/v1/impls/signing_unsafe.rs b/rpc/src/v1/impls/signing_unsafe.rs index 8fbb6595a..6fb483c5e 100644 --- a/rpc/src/v1/impls/signing_unsafe.rs +++ b/rpc/src/v1/impls/signing_unsafe.rs @@ -78,7 +78,7 @@ impl EthSigning for SigningUnsafeClient type Metadata = Metadata; fn sign(&self, _: Metadata, address: RpcH160, data: RpcBytes) -> BoxFuture { - self.handle(RpcConfirmationPayload::Signature((address.clone(), data).into()), address.into()) + self.handle(RpcConfirmationPayload::EthSignMessage((address.clone(), data).into()), address.into()) .then(|res| match res { Ok(RpcConfirmationResponse::Signature(signature)) => Ok(signature), Err(e) => Err(e), diff --git a/rpc/src/v1/tests/mocked/parity_accounts.rs b/rpc/src/v1/tests/mocked/parity_accounts.rs index 949498c61..b3ea644fa 100644 --- a/rpc/src/v1/tests/mocked/parity_accounts.rs +++ b/rpc/src/v1/tests/mocked/parity_accounts.rs @@ -500,3 +500,20 @@ fn should_export_account() { println!("Response: {:?}", response); assert_eq!(result, Some(response.into())); } + +#[test] +fn should_sign_message() { + let tester = setup(); + let hash = tester.accounts + .insert_account( + "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a".parse().unwrap(), + "password1") + .expect("account should be inserted ok"); + + assert_eq!(hash, "c171033d5cbff7175f29dfd3a63dda3d6f8f385e".parse().unwrap()); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_signMessage", "params": ["0xc171033d5cbff7175f29dfd3a63dda3d6f8f385e", "password1", "0xbc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a"], "id": 3}"#; + let response = r#"{"jsonrpc":"2.0","result":"0x1d9e33a8cf8bfc089a172bca01da462f9e359c6cb1b0f29398bc884e4d18df4f78588aee4fb5cc067ca62d2abab995e0bba29527be6ac98105b0320020a2efaf00","id":3}"#; + let res = tester.io.handle_request_sync(&request); + assert_eq!(res, Some(response.into())); +} diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index a20e94bcc..973e33528 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -90,7 +90,7 @@ fn should_return_list_of_items_to_confirm() { nonce: None, condition: None, }), Origin::Dapps("http://parity.io".into())).unwrap(); - tester.signer.add_request(ConfirmationPayload::Signature(1.into(), vec![5].into()), Origin::Unknown).unwrap(); + tester.signer.add_request(ConfirmationPayload::EthSignMessage(1.into(), vec![5].into()), Origin::Unknown).unwrap(); // when let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#; @@ -163,7 +163,7 @@ fn should_not_remove_transaction_if_password_is_invalid() { fn should_not_remove_sign_if_password_is_invalid() { // given let tester = signer_tester(); - tester.signer.add_request(ConfirmationPayload::Signature(0.into(), vec![5].into()), Origin::Unknown).unwrap(); + tester.signer.add_request(ConfirmationPayload::EthSignMessage(0.into(), vec![5].into()), Origin::Unknown).unwrap(); assert_eq!(tester.signer.requests().len(), 1); // when diff --git a/rpc/src/v1/traits/parity_accounts.rs b/rpc/src/v1/traits/parity_accounts.rs index 46372560c..7d49148b1 100644 --- a/rpc/src/v1/traits/parity_accounts.rs +++ b/rpc/src/v1/traits/parity_accounts.rs @@ -19,7 +19,7 @@ use std::collections::BTreeMap; use jsonrpc_core::Error; use ethstore::KeyFile; -use v1::types::{H160, H256, DappId, DeriveHash, DeriveHierarchical}; +use v1::types::{H160, H256, H520, DappId, DeriveHash, DeriveHierarchical}; build_rpc_trait! { /// Personal Parity rpc interface. @@ -180,5 +180,9 @@ build_rpc_trait! { /// Exports an account with given address if provided password matches. #[rpc(name = "parity_exportAccount")] fn export_account(&self, H160, String) -> Result; + + /// Sign raw hash with the key corresponding to address and password. + #[rpc(name = "parity_signMessage")] + fn sign_message(&self, H160, String, H256) -> Result; } } diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index f749df449..ac1fba4fe 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -57,7 +57,7 @@ impl fmt::Display for ConfirmationPayload { match *self { ConfirmationPayload::SendTransaction(ref transaction) => write!(f, "{}", transaction), ConfirmationPayload::SignTransaction(ref transaction) => write!(f, "(Sign only) {}", transaction), - ConfirmationPayload::Signature(ref sign) => write!(f, "{}", sign), + ConfirmationPayload::EthSignMessage(ref sign) => write!(f, "{}", sign), ConfirmationPayload::Decrypt(ref decrypt) => write!(f, "{}", decrypt), } } @@ -169,7 +169,7 @@ pub enum ConfirmationPayload { SignTransaction(TransactionRequest), /// Signature #[serde(rename="sign")] - Signature(SignRequest), + EthSignMessage(SignRequest), /// Decryption #[serde(rename="decrypt")] Decrypt(DecryptRequest), @@ -180,7 +180,7 @@ impl From for ConfirmationPayload { match c { helpers::ConfirmationPayload::SendTransaction(t) => ConfirmationPayload::SendTransaction(t.into()), helpers::ConfirmationPayload::SignTransaction(t) => ConfirmationPayload::SignTransaction(t.into()), - helpers::ConfirmationPayload::Signature(address, data) => ConfirmationPayload::Signature(SignRequest { + helpers::ConfirmationPayload::EthSignMessage(address, data) => ConfirmationPayload::EthSignMessage(SignRequest { address: address.into(), data: data.into(), }), @@ -255,7 +255,7 @@ mod tests { // given let request = helpers::ConfirmationRequest { id: 15.into(), - payload: helpers::ConfirmationPayload::Signature(1.into(), vec![5].into()), + payload: helpers::ConfirmationPayload::EthSignMessage(1.into(), vec![5].into()), origin: Origin::Rpc("test service".into()), };