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)]