adds parity_verifySignature RPC method (#9507)
* closes #7009 adds parity_verifySignature RPC method * removed unneccesary into * adds support for chain replay protected signatures * corrected chain replay protection check * corrected possible overflow * added tests * use checked_sub to prevent possible overflow * use saturating_mul to prevent possible overflow * changed method signature to accept r,s,v components * added unit tests * more tests * refactored tests for better readability moved verify_signature tests to live in the same file. * removed unneccesary imports * fixed PR grumbles * fixed rsv notation * fixed rsv notation * renamed BasicAcount to RecoveredAccount, Support zero chain_id * fixed compile errors * fixed tests * doc comment
This commit is contained in:
		
							parent
							
								
									0b5bbf6048
								
							
						
					
					
						commit
						0e94ac0111
					
				@ -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};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										182
									
								
								rpc/src/v1/helpers/signature.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								rpc/src/v1/helpers/signature.rs
									
									
									
									
									
										Normal file
									
								
							@ -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 <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
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<u64>
 | 
			
		||||
) -> Result<RecoveredAccount> {
 | 
			
		||||
	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>) -> u64 {
 | 
			
		||||
		v + if let Some(n) = chain_id { 35 + n * 2 } else { 0 }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	struct TestCase {
 | 
			
		||||
		should_prefix: bool,
 | 
			
		||||
		signing_chain_id: Option<u64>,
 | 
			
		||||
		rpc_chain_id: Option<u64>,
 | 
			
		||||
		is_valid_for_current_chain: bool,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// mocked signer
 | 
			
		||||
	fn sign(should_prefix: bool, data: Vec<u8>, signing_chain_id: Option<u64>) -> (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,
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -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<RecoveredAccount> {
 | 
			
		||||
		verify_signature(is_prefixed, message, r, s, v, self.light_dispatch.client.signing_chain_id())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<C, M, U, S> Parity for ParityClient<C, M, U> where
 | 
			
		||||
			Err(errors::status_error(has_peers))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn verify_signature(&self, is_prefixed: bool, message: Bytes, r: H256, s: H256, v: U64) -> Result<RecoveredAccount> {
 | 
			
		||||
		verify_signature(is_prefixed, message, r, s, v, self.client.signing_chain_id())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<RecoveredAccount>;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,10 @@
 | 
			
		||||
 | 
			
		||||
// You should have received a copy of the GNU General Public License
 | 
			
		||||
// along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
//! 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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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};
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user