diff --git a/rpc/src/v1/helpers/mod.rs b/rpc/src/v1/helpers/mod.rs
index 0259818af..c770836fb 100644
--- a/rpc/src/v1/helpers/mod.rs
+++ b/rpc/src/v1/helpers/mod.rs
@@ -36,8 +36,10 @@ mod signing_queue;
mod subscribers;
mod subscription_manager;
mod work;
+mod signature;
pub use self::dispatch::{Dispatcher, FullDispatcher, LightDispatcher};
+pub use self::signature::verify_signature;
pub use self::network_settings::NetworkSettings;
pub use self::poll_manager::PollManager;
pub use self::poll_filter::{PollFilter, SyncPollFilter, limit_logs};
diff --git a/rpc/src/v1/helpers/signature.rs b/rpc/src/v1/helpers/signature.rs
new file mode 100644
index 000000000..47664fc74
--- /dev/null
+++ b/rpc/src/v1/helpers/signature.rs
@@ -0,0 +1,182 @@
+// Copyright 2015-2018 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity 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 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. If not, see .
+
+use ethkey::{recover, public_to_address, Signature};
+use jsonrpc_core::Result;
+use v1::types::{Bytes, RecoveredAccount, H256, U64};
+use v1::helpers::errors;
+use v1::helpers::dispatch::eth_data_hash;
+use hash::keccak;
+
+/// helper method for parity_verifySignature
+pub fn verify_signature(
+ is_prefixed: bool,
+ message: Bytes,
+ r: H256,
+ s: H256,
+ v: U64,
+ chain_id: Option
+) -> Result {
+ let hash = if is_prefixed {
+ eth_data_hash(message.0)
+ } else {
+ keccak(message.0)
+ };
+ let v: u64 = v.into();
+ let is_valid_for_current_chain = match (chain_id, v) {
+ (None, v) if v == 0 || v == 1 => true,
+ (Some(chain_id), v) if v >= 35 => (v - 35) / 2 == chain_id,
+ _ => false,
+ };
+
+ let v = if v >= 35 { (v - 1) % 2 } else { v };
+
+ let signature = Signature::from_rsv(&r.into(), &s.into(), v as u8);
+ let public_key = recover(&signature, &hash).map_err(errors::encryption)?;
+ let address = public_to_address(&public_key);
+ Ok(RecoveredAccount { address, public_key, is_valid_for_current_chain })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::sync::Arc;
+ use ethcore::account_provider::AccountProvider;
+ use v1::types::H160;
+
+ pub fn add_chain_replay_protection(v: u64, chain_id: Option) -> u64 {
+ v + if let Some(n) = chain_id { 35 + n * 2 } else { 0 }
+ }
+
+ struct TestCase {
+ should_prefix: bool,
+ signing_chain_id: Option,
+ rpc_chain_id: Option,
+ is_valid_for_current_chain: bool,
+ }
+
+ /// mocked signer
+ fn sign(should_prefix: bool, data: Vec, signing_chain_id: Option) -> (H160, [u8; 32], [u8; 32], U64) {
+ let hash = if should_prefix { eth_data_hash(data) } else { keccak(data) };
+ let accounts = Arc::new(AccountProvider::transient_provider());
+ let address = accounts.new_account(&"password123".into()).unwrap();
+ let sig = accounts.sign(address, Some("password123".into()), hash).unwrap();
+ let (r, s, v) = (sig.r(), sig.s(), sig.v());
+ let v = add_chain_replay_protection(v as u64, signing_chain_id);
+ let (r_buf, s_buf) = {
+ let (mut r_buf, mut s_buf) = ([0u8; 32], [0u8; 32]);
+ r_buf.copy_from_slice(r);
+ s_buf.copy_from_slice(s);
+ (r_buf, s_buf)
+ };
+ (address.into(), r_buf, s_buf, v.into())
+ }
+
+ fn run_test(test_case: TestCase) {
+ let TestCase { should_prefix, signing_chain_id, rpc_chain_id, is_valid_for_current_chain } = test_case;
+ let data = vec![5u8];
+
+ let (address, r, s, v) = sign(should_prefix, data.clone(), signing_chain_id);
+ let account = verify_signature(should_prefix, data.into(), r.into(), s.into(), v, rpc_chain_id).unwrap();
+
+ assert_eq!(account.address, address.into());
+ assert_eq!(account.is_valid_for_current_chain, is_valid_for_current_chain)
+ }
+
+ #[test]
+ fn test_verify_signature_prefixed_mainnet() {
+ run_test(TestCase {
+ should_prefix: true,
+ signing_chain_id: Some(1),
+ rpc_chain_id: Some(1),
+ is_valid_for_current_chain: true,
+ })
+ }
+
+ #[test]
+ fn test_verify_signature_not_prefixed_mainnet() {
+ run_test(TestCase {
+ should_prefix: false,
+ signing_chain_id: Some(1),
+ rpc_chain_id: Some(1),
+ is_valid_for_current_chain: true,
+ })
+ }
+
+ #[test]
+ fn test_verify_signature_incompatible_chain_id() {
+ run_test(TestCase {
+ should_prefix: false,
+ signing_chain_id: Some(65),
+ rpc_chain_id: Some(1),
+ is_valid_for_current_chain: false,
+ });
+ run_test(TestCase {
+ should_prefix: true,
+ signing_chain_id: Some(65),
+ rpc_chain_id: Some(1),
+ is_valid_for_current_chain: false,
+ });
+ }
+
+ #[test]
+ fn test_verify_signature_no_signing_chain_id() {
+ run_test(TestCase {
+ should_prefix: false,
+ signing_chain_id: None,
+ rpc_chain_id: Some(1),
+ is_valid_for_current_chain: false,
+ });
+ run_test(TestCase {
+ should_prefix: true,
+ signing_chain_id: None,
+ rpc_chain_id: Some(1),
+ is_valid_for_current_chain: false,
+ });
+ }
+
+ #[test]
+ fn test_verify_signature_no_rpc_chain_id() {
+ run_test(TestCase {
+ should_prefix: false,
+ signing_chain_id: Some(1),
+ rpc_chain_id: None,
+ is_valid_for_current_chain: false,
+ });
+ run_test(TestCase {
+ should_prefix: true,
+ signing_chain_id: Some(1),
+ rpc_chain_id: None,
+ is_valid_for_current_chain: false,
+ });
+ }
+
+ #[test]
+ fn test_verify_signature_no_chain_replay_protection() {
+ run_test(TestCase {
+ should_prefix: false,
+ signing_chain_id: None,
+ rpc_chain_id: None,
+ is_valid_for_current_chain: true,
+ });
+ run_test(TestCase {
+ should_prefix: true,
+ signing_chain_id: None,
+ rpc_chain_id: None,
+ is_valid_for_current_chain: true,
+ });
+ }
+}
diff --git a/rpc/src/v1/impls/light/parity.rs b/rpc/src/v1/impls/light/parity.rs
index 3d5bf7897..c281760f3 100644
--- a/rpc/src/v1/impls/light/parity.rs
+++ b/rpc/src/v1/impls/light/parity.rs
@@ -30,18 +30,18 @@ use ethcore_logger::RotatingLogger;
use jsonrpc_core::{Result, BoxFuture};
use jsonrpc_core::futures::Future;
use jsonrpc_macros::Trailing;
-use v1::helpers::{self, errors, ipfs, SigningQueue, SignerService, NetworkSettings};
+use v1::helpers::{self, errors, ipfs, SigningQueue, SignerService, NetworkSettings, verify_signature};
use v1::helpers::dispatch::LightDispatcher;
use v1::helpers::light_fetch::{LightFetch, light_all_transactions};
use v1::metadata::Metadata;
use v1::traits::Parity;
use v1::types::{
- Bytes, U256, H64, H160, H256, H512, CallRequest,
+ Bytes, U256, U64, H64, H160, H256, H512, CallRequest,
Peers, Transaction, RpcSettings, Histogram,
TransactionStats, LocalTransactionStatus,
- BlockNumber, LightBlockNumber, ConsensusCapability, VersionInfo,
- OperationsInfo, ChainStatus,
- AccountInfo, HwAccountInfo, Header, RichHeader, Receipt,
+ LightBlockNumber, ChainStatus, Receipt,
+ BlockNumber, ConsensusCapability, VersionInfo,
+ OperationsInfo, AccountInfo, HwAccountInfo, Header, RichHeader, RecoveredAccount
};
use Host;
@@ -425,4 +425,7 @@ impl Parity for ParityClient {
Err(errors::status_error(has_peers))
}
}
+ fn verify_signature(&self, is_prefixed: bool, message: Bytes, r: H256, s: H256, v: U64) -> Result {
+ verify_signature(is_prefixed, message, r, s, v, self.light_dispatch.client.signing_chain_id())
+ }
}
diff --git a/rpc/src/v1/impls/parity.rs b/rpc/src/v1/impls/parity.rs
index 2671a0eab..9d73238a0 100644
--- a/rpc/src/v1/impls/parity.rs
+++ b/rpc/src/v1/impls/parity.rs
@@ -38,17 +38,17 @@ use jsonrpc_core::{BoxFuture, Result};
use jsonrpc_core::futures::future;
use jsonrpc_macros::Trailing;
-use v1::helpers::{self, errors, fake_sign, ipfs, SigningQueue, SignerService, NetworkSettings};
use v1::helpers::block_import::is_major_importing;
+use v1::helpers::{self, errors, fake_sign, ipfs, SigningQueue, SignerService, NetworkSettings, verify_signature};
use v1::metadata::Metadata;
use v1::traits::Parity;
use v1::types::{
- Bytes, U256, H64, H160, H256, H512, CallRequest,
+ Bytes, U256, H64, U64, H160, H256, H512, CallRequest,
Peers, Transaction, RpcSettings, Histogram,
TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo,
OperationsInfo, ChainStatus,
- AccountInfo, HwAccountInfo, RichHeader, Receipt,
+ AccountInfo, HwAccountInfo, RichHeader, Receipt, RecoveredAccount,
block_number_to_id
};
use Host;
@@ -504,4 +504,8 @@ impl Parity for ParityClient where
Err(errors::status_error(has_peers))
}
}
+
+ fn verify_signature(&self, is_prefixed: bool, message: Bytes, r: H256, s: H256, v: U64) -> Result {
+ verify_signature(is_prefixed, message, r, s, v, self.client.signing_chain_id())
+ }
}
diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs
index 0fa623ed4..d978a4ac7 100644
--- a/rpc/src/v1/tests/mocked/parity.rs
+++ b/rpc/src/v1/tests/mocked/parity.rs
@@ -604,3 +604,26 @@ fn rpc_status_error_sync() {
assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
}
+
+#[test]
+fn rpc_parity_verify_signature() {
+ let deps = Dependencies::new();
+ let io = deps.default_client();
+
+ let request = r#"{
+ "jsonrpc": "2.0",
+ "method": "parity_verifySignature",
+ "params": [
+ false,
+ "0xe552acf4caabe9661893fd48c7b5e68af20bf007193442f8ca05ce836699d75e",
+ "0x2089e84151c3cdc45255c07557b349f5bf2ed3e68f6098723eaa90a0f8b2b3e5",
+ "0x5f70e8df7bd0c4417afb5f5a39d82e15d03adeff8796725d8b14889ed1d1aa8a",
+ "0x1"
+ ],
+ "id": 0
+ }"#;
+
+ let response = r#"{"jsonrpc":"2.0","result":{"address":"0x9a2a08a1170f51208c2f3cede0d29ada94481eed","isValidForCurrentChain":true,"publicKey":"0xbeec94ea24444906fe247c47841a45220f07e5718d06157fe4502fac326dab617e973e221e45746721330c2db3f63202268686378cc28b9800c1daaf0bbafeb1"},"id":0}"#;
+
+ assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
+}
diff --git a/rpc/src/v1/traits/parity.rs b/rpc/src/v1/traits/parity.rs
index a81e50089..5ac467018 100644
--- a/rpc/src/v1/traits/parity.rs
+++ b/rpc/src/v1/traits/parity.rs
@@ -20,10 +20,9 @@ use std::collections::BTreeMap;
use jsonrpc_core::{BoxFuture, Result};
use jsonrpc_macros::Trailing;
-
use v1::types::{
- H64, H160, H256, H512, U256, Bytes, CallRequest,
- Peers, Transaction, RpcSettings, Histogram,
+ H160, H256, H512, U256, U64, H64, Bytes, CallRequest,
+ Peers, Transaction, RpcSettings, Histogram, RecoveredAccount,
TransactionStats, LocalTransactionStatus,
BlockNumber, ConsensusCapability, VersionInfo,
OperationsInfo, ChainStatus,
@@ -237,5 +236,10 @@ build_rpc_trait! {
/// Otherwise the RPC returns error.
#[rpc(name = "parity_nodeStatus")]
fn status(&self) -> Result<()>;
+
+ /// Extracts Address and public key from signature using the r, s and v params. Equivalent to Solidity erecover
+ /// as well as checks the signature for chain replay protection
+ #[rpc(name = "parity_verifySignature")]
+ fn verify_signature(&self, bool, Bytes, H256, H256, U64) -> Result;
}
}
diff --git a/rpc/src/v1/types/account_info.rs b/rpc/src/v1/types/account_info.rs
index 487507de9..e0e2464d8 100644
--- a/rpc/src/v1/types/account_info.rs
+++ b/rpc/src/v1/types/account_info.rs
@@ -13,6 +13,10 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
+
+//! Return types for RPC calls
+
+use ethereum_types::{Public, Address};
use v1::types::{H160, H256, U256, Bytes};
/// Account information.
@@ -64,3 +68,19 @@ pub struct HwAccountInfo {
/// Device manufacturer.
pub manufacturer: String,
}
+
+/// account derived from a signature
+/// as well as information that tells if it is valid for
+/// the current chain
+#[derive(Debug, Clone, Serialize)]
+#[serde(rename_all="camelCase")]
+pub struct RecoveredAccount {
+ /// address of the recovered account
+ pub address: Address,
+ /// public key of the recovered account
+ pub public_key: Public,
+ /// If the signature contains chain replay protection,
+ /// And the chain_id encoded within the signature
+ /// matches the current chain this would be true, otherwise false.
+ pub is_valid_for_current_chain: bool
+}
diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs
index 68401fb0c..cde98e597 100644
--- a/rpc/src/v1/types/mod.rs
+++ b/rpc/src/v1/types/mod.rs
@@ -48,7 +48,7 @@ mod eip191;
pub mod pubsub;
pub use self::eip191::{EIP191Version, PresignedTransaction};
-pub use self::account_info::{AccountInfo, ExtAccountInfo, HwAccountInfo, EthAccount, StorageProof};
+pub use self::account_info::{AccountInfo, ExtAccountInfo, HwAccountInfo, EthAccount, StorageProof, RecoveredAccount};
pub use self::bytes::Bytes;
pub use self::block::{RichBlock, Block, BlockTransactions, Header, RichHeader, Rich};
pub use self::block_number::{BlockNumber, LightBlockNumber, block_number_to_id};