Skip to content
This repository was archived by the owner on Nov 6, 2020. It is now read-only.
Merged

EIP 191 #9701

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions rpc/src/v1/helpers/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ use v1::types::{
RichRawTransaction as RpcRichRawTransaction,
ConfirmationPayload as RpcConfirmationPayload,
ConfirmationResponse,
SignRequest as RpcSignRequest,
EthSignRequest as RpcEthSignRequest,
EIP191SignRequest as RpcSignRequest,
DecryptRequest as RpcDecryptRequest,
};
use rlp;
Expand Down Expand Up @@ -693,6 +694,19 @@ pub fn execute<D: Dispatcher + 'static>(
);
Box::new(future::done(res))
},
ConfirmationPayload::SignMessage(address, data) => {
if accounts.is_hardware_address(&address) {
return Box::new(future::err(errors::account("Error signing message with hardware_wallet",
"Message signing is unsupported")));
}
let res = signature(&accounts, address, data, pass)
.map(|result| result
.map(|rsv| H520(rsv.into_electrum()))
.map(RpcH520::from)
.map(ConfirmationResponse::Signature)
);
Box::new(future::done(res))
},
ConfirmationPayload::Decrypt(address, data) => {
if accounts.is_hardware_address(&address) {
return Box::new(future::err(errors::unsupported("Decrypting via hardware wallets is not supported.", None)));
Expand Down Expand Up @@ -775,8 +789,11 @@ pub fn from_rpc<D>(payload: RpcConfirmationPayload, default_account: Address, di
RpcConfirmationPayload::Decrypt(RpcDecryptRequest { address, msg }) => {
Box::new(future::ok(ConfirmationPayload::Decrypt(address.into(), msg.into())))
},
RpcConfirmationPayload::EthSignMessage(RpcSignRequest { address, data }) => {
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())))
},
}
}
61 changes: 61 additions & 0 deletions rpc/src/v1/helpers/eip191.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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/>.

//! EIP-191 compliant decoding + hashing
use v1::types::{EIP191Version, Bytes, PresignedTransaction};
use eip712::{hash_structured_data, EIP712};
use serde_json::{Value, from_value};
use v1::helpers::errors;
use jsonrpc_core::Error;
use v1::helpers::dispatch::eth_data_hash;
use hash::keccak;
use std::fmt::Display;
use ethereum_types::H256;

/// deserializes and hashes the message depending on the version specifier
pub fn hash_message(version: EIP191Version, message: Value) -> Result<H256, Error> {
let data = match version {
EIP191Version::StructuredData => {
let typed_data = from_value::<EIP712>(message)
.map_err(map_serde_err("StructuredData"))?;

hash_structured_data(typed_data)
.map_err(|err| errors::invalid_call_data(err.kind()))?
}

EIP191Version::PresignedTransaction => {
let data = from_value::<PresignedTransaction>(message)
.map_err(map_serde_err("WithValidator"))?;
let prefix = b"\x19\x00";
let data = [&prefix[..], &data.validator.0[..], &data.data.0[..]].concat();
keccak(data)
}

EIP191Version::PersonalMessage => {
let bytes = from_value::<Bytes>(message)
.map_err(map_serde_err("Bytes"))?;
eth_data_hash(bytes.0)
}
};

Ok(data)
}

fn map_serde_err<T: Display>(struct_name: &'static str) -> impl Fn(T) -> Error {
move |error: T| {
errors::invalid_call_data(format!("Error deserializing '{}': {}", struct_name, error))
}
}
1 change: 1 addition & 0 deletions rpc/src/v1/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub mod light_fetch;
pub mod nonce;
pub mod oneshot;
pub mod secretstore;
pub mod eip191;

mod network_settings;
mod poll_filter;
Expand Down
4 changes: 4 additions & 0 deletions rpc/src/v1/helpers/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use ethereum_types::{U256, Address};
use bytes::Bytes;

use v1::types::{Origin, TransactionCondition};
use ethereum_types::H256;

/// Transaction request coming from RPC
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
Expand Down Expand Up @@ -117,6 +118,8 @@ pub enum ConfirmationPayload {
SignTransaction(FilledTransactionRequest),
/// Sign a message with an Ethereum specific security prefix.
EthSignMessage(Address, Bytes),
/// Sign a message
SignMessage(Address, H256),
/// Decrypt request
Decrypt(Address, Bytes),
}
Expand All @@ -127,6 +130,7 @@ impl ConfirmationPayload {
ConfirmationPayload::SendTransaction(ref request) => request.from,
ConfirmationPayload::SignTransaction(ref request) => request.from,
ConfirmationPayload::EthSignMessage(ref address, _) => *address,
ConfirmationPayload::SignMessage(ref address, _) => *address,
ConfirmationPayload::Decrypt(ref address, _) => *address,
}
}
Expand Down
28 changes: 25 additions & 3 deletions rpc/src/v1/impls/personal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ 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::{errors, eip191};
use v1::helpers::dispatch::{self, eth_data_hash, Dispatcher, SignWith};
use v1::traits::Personal;
use v1::types::{
Expand All @@ -36,9 +36,11 @@ use v1::types::{
ConfirmationResponse as RpcConfirmationResponse,
TransactionRequest,
RichRawTransaction as RpcRichRawTransaction,
EIP191Version,
};
use v1::metadata::Metadata;
use eip712::{EIP712, hash_structured_data};
use jsonrpc_core::types::Value;

/// Account management (personal) rpc implementation.
pub struct PersonalClient<D: Dispatcher> {
Expand Down Expand Up @@ -151,15 +153,35 @@ impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
}))
}

fn sign_191(&self, version: EIP191Version, data: Value, account: RpcH160, password: String) -> BoxFuture<RpcH520> {
let data = try_bf!(eip191::hash_message(version, data));
let dispatcher = self.dispatcher.clone();
let accounts = self.accounts.clone();

let payload = RpcConfirmationPayload::EIP191SignMessage((account.clone(), data.into()).into());

Box::new(dispatch::from_rpc(payload, account.into(), &dispatcher)
.and_then(|payload| {
dispatch::execute(dispatcher, accounts, payload, dispatch::SignWith::Password(password.into()))
})
.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 sign_typed_data(&self, typed_data: EIP712, account: RpcH160, password: String) -> BoxFuture<RpcH520> {
let data = match hash_structured_data(typed_data) {
Ok(d) => d,
Err(err) => return Box::new(future::done(Err(errors::invalid_call_data(err.kind())))),
Err(err) => return Box::new(future::err(errors::invalid_call_data(err.kind()))),
};
let dispatcher = self.dispatcher.clone();
let accounts = self.accounts.clone();

let payload = RpcConfirmationPayload::EthSignMessage((account.clone(), RpcBytes(data)).into());
let payload = RpcConfirmationPayload::EIP191SignMessage((account.clone(), data.into()).into());

Box::new(dispatch::from_rpc(payload, account.into(), &dispatcher)
.and_then(|payload| {
Expand Down
8 changes: 8 additions & 0 deletions rpc/src/v1/impls/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ impl<D: Dispatcher + 'static> Signer for SignerClient<D> {
Err(err) => Err(errors::invalid_params("Invalid signature received.", err)),
}
},
ConfirmationPayload::SignMessage(address, hash) => {
let signature = ethkey::Signature::from_electrum(&bytes.0);
match ethkey::verify_address(&address, &signature, &hash) {
Ok(true) => Ok(ConfirmationResponse::Signature(bytes.0.as_slice().into())),
Ok(false) => Err(errors::invalid_params("Sender address does not match the signature.", ())),
Err(err) => Err(errors::invalid_params("Invalid signature received.", err)),
}
},
ConfirmationPayload::Decrypt(_address, _data) => {
// TODO [ToDr]: Decrypt can we verify if the answer is correct?
Ok(ConfirmationResponse::Decrypt(bytes))
Expand Down
94 changes: 92 additions & 2 deletions rpc/src/v1/tests/mocked/personal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@ use jsonrpc_core::IoHandler;
use parking_lot::Mutex;
use transaction::{Action, Transaction};
use parity_runtime::Runtime;
use hash::keccak;

use v1::{PersonalClient, Personal, Metadata};
use v1::helpers::nonce;
use v1::helpers::{nonce, eip191};
use v1::helpers::dispatch::{eth_data_hash, FullDispatcher};
use v1::tests::helpers::TestMinerService;
use v1::types::H520;
use v1::types::{EIP191Version, PresignedTransaction, H520};
use rustc_hex::ToHex;
use serde_json::to_value;
use ethkey::Secret;

struct PersonalTester {
_runtime: Runtime,
Expand Down Expand Up @@ -328,3 +332,89 @@ fn should_unlock_account_permanently() {
assert_eq!(tester.io.handle_request_sync(&request), Some(response.into()));
assert!(tester.accounts.sign(address, None, Default::default()).is_ok(), "Should unlock account.");
}

#[test]
fn sign_eip191_with_validator() {
let tester = setup();
let address = tester.accounts.new_account(&"password123".into()).unwrap();
let request = r#"{
"jsonrpc": "2.0",
"method": "personal_sign191",
"params": [
"0x00",
{
"validator": ""#.to_owned() + &format!("0x{:x}", address) + r#"",
"data": ""# + &format!("0x{:x}", keccak("hello world")) + r#""
},
""# + &format!("0x{:x}", address) + r#"",
"password123"
],
"id": 1
}"#;
let with_validator = to_value(PresignedTransaction {
validator: address.into(),
data: keccak("hello world").to_vec().into()
}).unwrap();
let result = eip191::hash_message(EIP191Version::PresignedTransaction, with_validator).unwrap();
let result = tester.accounts.sign(address, Some("password123".into()), result).unwrap().into_electrum();
let expected = r#"{"jsonrpc":"2.0","result":""#.to_owned() + &format!("0x{}", result.to_hex()) + r#"","id":1}"#;
let response = tester.io.handle_request_sync(&request).unwrap();
assert_eq!(response, expected)
}

#[test]
fn sign_eip191_structured_data() {
let tester = setup();
let secret: Secret = keccak("cow").into();
let address = tester.accounts.insert_account(secret, &"lol".into()).unwrap();
let request = r#"{
"jsonrpc": "2.0",
"method": "personal_sign191",
"params": [
"0x01",
{
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": "0x1",
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
},
"types": {
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" },
{ "name": "verifyingContract", "type": "address" }
],
"Person": [
{ "name": "name", "type": "string" },
{ "name": "wallet", "type": "address" }
],
"Mail": [
{ "name": "from", "type": "Person" },
{ "name": "to", "type": "Person" },
{ "name": "contents", "type": "string" }
]
}
},
""#.to_owned() + &format!("0x{:x}", address) + r#"",
"lol"
],
"id": 1
}"#;
let expected = r#"{"jsonrpc":"2.0","result":"0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c","id":1}"#;
let response = tester.io.handle_request_sync(&request).unwrap();
assert_eq!(response, expected)
}
9 changes: 7 additions & 2 deletions rpc/src/v1/traits/personal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

//! Personal rpc interface.
use jsonrpc_core::{BoxFuture, Result};
use eip712::EIP712;
use v1::types::{Bytes, U128, H160, H256, H520, TransactionRequest, RichRawTransaction as RpcRichRawTransaction};
use jsonrpc_core::types::Value;
use jsonrpc_core::{BoxFuture, Result};
use v1::types::{Bytes, U128, H160, H256, H520, TransactionRequest, RichRawTransaction as RpcRichRawTransaction, EIP191Version};

build_rpc_trait! {
/// Personal rpc interface. Safe (read-only) functions.
Expand Down Expand Up @@ -47,6 +48,10 @@ build_rpc_trait! {
#[rpc(name = "personal_signTypedData")]
fn sign_typed_data(&self, EIP712, H160, String) -> BoxFuture<H520>;

/// Signs an arbitrary message based on the version specified
#[rpc(name = "personal_sign191")]
fn sign_191(&self, EIP191Version, Value, 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")]
Expand Down
Loading