diff --git a/rpc/src/v1/helpers/eip191.rs b/rpc/src/v1/helpers/eip191.rs new file mode 100644 index 00000000000..32985573d4e --- /dev/null +++ b/rpc/src/v1/helpers/eip191.rs @@ -0,0 +1,60 @@ +// 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 . + +//! EIP-191 compliant decoding +use v1::types::{EIP191Version, Bytes, WithValidator}; +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; + +pub fn decode_message(version: EIP191Version, data: Value) -> Result { + let data = match version { + EIP191Version::StructuredData => { + let typed_data = from_value::(data) + .map_err(map_serde_err("StructuredData"))?; + + hash_structured_data(typed_data) + .map_err(|err| errors::invalid_call_data(err.kind()))? + } + + EIP191Version::WithValidator => { + let typed_data = from_value::(data) + .map_err(map_serde_err("WithValidator"))?; + let prefix = b"\x19\x00"; + let data = [&prefix[..], &typed_data.address.0[..], &typed_data.application_data.0[..]].concat(); + keccak(data) + } + + EIP191Version::PersonalMessage => { + let bytes = from_value::(data) + .map_err(map_serde_err("Bytes"))?; + eth_data_hash(bytes.0) + } + }; + + Ok(data) +} + +fn map_serde_err(struct_name: &'static str) -> impl Fn(T) -> Error { + move |error: T| { + errors::invalid_call_data(format!("Error deserializing '{}': {}", struct_name, error)) + } +} diff --git a/rpc/src/v1/helpers/mod.rs b/rpc/src/v1/helpers/mod.rs index ba8356334b3..69635c4febe 100644 --- a/rpc/src/v1/helpers/mod.rs +++ b/rpc/src/v1/helpers/mod.rs @@ -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; diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 4f79f840bfe..f4350b0f4ee 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -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::{ @@ -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 { @@ -151,15 +153,39 @@ impl Personal for PersonalClient { })) } + fn sign_191(&self, version: EIP191Version, data: Value, account: RpcH160, password: String) -> BoxFuture { + let data = match eip191::decode_message(version, data) { + Ok(d) => d, + Err(e) => return Box::new(future::err(e)) + }; + + let dispatcher = self.dispatcher.clone(); + let accounts = self.accounts.clone(); + + let payload = RpcConfirmationPayload::EthSignMessage((account.clone(), RpcBytes(data.to_vec())).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 { 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::EthSignMessage((account.clone(), RpcBytes(data.to_vec())).into()); Box::new(dispatch::from_rpc(payload, account.into(), &dispatcher) .and_then(|payload| { diff --git a/rpc/src/v1/traits/personal.rs b/rpc/src/v1/traits/personal.rs index 265bd33801c..53cb1a2c138 100644 --- a/rpc/src/v1/traits/personal.rs +++ b/rpc/src/v1/traits/personal.rs @@ -17,7 +17,8 @@ //! 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 v1::types::{Bytes, U128, H160, H256, H520, TransactionRequest, RichRawTransaction as RpcRichRawTransaction, EIP191Version}; build_rpc_trait! { /// Personal rpc interface. Safe (read-only) functions. @@ -42,11 +43,15 @@ build_rpc_trait! { #[rpc(name = "personal_sign")] fn sign(&self, Bytes, H160, String) -> BoxFuture; - /// Produces an EIP-720 compliant signature with given account using the given password to unlock the + /// Produces an EIP-712 compliant signature with given account using the given password to unlock the /// account during the request. #[rpc(name = "personal_signTypedData")] fn sign_typed_data(&self, EIP712, H160, String) -> BoxFuture; + /// Signs an arbitrary message based on the version specified + #[rpc(name = "personal_sign191")] + fn sign_191(&self, EIP191Version, Value, H160, String) -> BoxFuture; + /// Returns the account associated with the private key that was used to calculate the signature in /// `personal_sign`. #[rpc(name = "personal_ecRecover")] diff --git a/rpc/src/v1/types/eip191.rs b/rpc/src/v1/types/eip191.rs new file mode 100644 index 00000000000..f5622a72973 --- /dev/null +++ b/rpc/src/v1/types/eip191.rs @@ -0,0 +1,50 @@ +// 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 . + +//! EIP-191 specific types +use serde::{Deserialize, Deserializer}; +use serde::de; +use v1::types::{H160, Bytes}; + +pub enum EIP191Version { + StructuredData, + PersonalMessage, + WithValidator +} + +#[derive(Deserialize)] +pub struct WithValidator { + // address of intended validator + pub address: H160, + // application specific data + pub application_data: Bytes +} + +impl<'de> Deserialize<'de> for EIP191Version { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + let byte_version = match s.as_str() { + "0x00" => EIP191Version::WithValidator, + "0x01" => EIP191Version::StructuredData, + "0x45" => EIP191Version::PersonalMessage, + other => return Err(de::Error::custom(format!("Invalid byte version '{}'", other))), + }; + Ok(byte_version) + } +} diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index eb8c0dc226c..92b140f8f8a 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -43,9 +43,10 @@ mod transaction_condition; mod uint; mod work; mod private_receipt; +mod eip191; pub mod pubsub; - +pub use self::eip191::{EIP191Version, WithValidator}; pub use self::account_info::{AccountInfo, ExtAccountInfo, HwAccountInfo}; pub use self::bytes::Bytes; pub use self::block::{RichBlock, Block, BlockTransactions, Header, RichHeader, Rich}; diff --git a/util/EIP-712/src/encode.rs b/util/EIP-712/src/encode.rs index a5527aef7a2..e7a8abce59b 100644 --- a/util/EIP-712/src/encode.rs +++ b/util/EIP-712/src/encode.rs @@ -94,7 +94,7 @@ fn encode_data( message_type: &Type, message_types: &MessageTypes, value: &Value, - field_name: Option<&str> + field_name: Option<&str>, ) -> Result> { let encoded = match message_type { @@ -128,7 +128,7 @@ fn encode_data( if string.len() < 2 { return Err(ErrorKind::HexParseError( format!("Expected a 0x-prefixed string of even length, found {} length string", string.len())) - )? + )?; } let string = string.get(2..).expect("line 188 checks for length; qed"); let bytes = H256::from_str(string).map_err(|err| ErrorKind::HexParseError(format!("{}", err)))?; @@ -141,7 +141,7 @@ fn encode_data( if string.len() < 2 { return Err(ErrorKind::HexParseError( format!("Expected a 0x-prefixed string of even length, found {} length string", string.len())) - )? + )?; } let string = string.get(2..).expect("line 200 checks for length; qed"); let bytes = H256::from_str(string).map_err(|err| ErrorKind::HexParseError(format!("{}", err)))?; @@ -175,7 +175,7 @@ fn encode_data( if string.len() < 2 { return Err(ErrorKind::HexParseError( format!("Expected a 0x-prefixed string of even length, found {} length string", string.len())) - )? + )?; } let string = string.get(2..).expect("line 200 checks for length"); U256::from_str(string).map_err(|err| ErrorKind::HexParseError(format!("{}", err)))? @@ -191,14 +191,17 @@ fn encode_data( encode(&[token]) } - _ => return Err(ErrorKind::UnknownType("".to_owned(), (*message_type).clone().into()))? + _ => return Err(ErrorKind::UnknownType( + field_name.unwrap_or("").to_owned(), + (*message_type).clone().into(), + ))? }; Ok(encoded) } /// encodes and hashes the given EIP712 struct -pub fn hash_structured_data(typed_data: EIP712) -> Result> { +pub fn hash_structured_data(typed_data: EIP712) -> Result { // EIP-191 compliant let prefix = (b"\x19\x01").to_vec(); let domain = to_value(&typed_data.domain).unwrap(); @@ -208,7 +211,7 @@ pub fn hash_structured_data(typed_data: EIP712) -> Result> { encode_data(&parser, &Type::Custom(typed_data.primary_type), &typed_data.types, &typed_data.message, None)? ); let concat = [&prefix[..], &domain_hash[..], &data_hash[..]].concat(); - Ok((&keccak(concat)).to_vec()) + Ok(keccak(concat)) } #[cfg(test)]