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

Commit

Permalink
added sign_191 rpc method
Browse files Browse the repository at this point in the history
  • Loading branch information
seunlanlege committed Oct 3, 2018
1 parent 08344e0 commit 05526ca
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 13 deletions.
60 changes: 60 additions & 0 deletions rpc/src/v1/helpers/eip191.rs
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

//! 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<H256, Error> {
let data = match version {
EIP191Version::StructuredData => {
let typed_data = from_value::<EIP712>(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::<WithValidator>(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::<Bytes>(data)
.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
32 changes: 29 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,39 @@ impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
}))
}

fn sign_191(&self, version: EIP191Version, data: Value, account: RpcH160, password: String) -> BoxFuture<RpcH520> {
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<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::EthSignMessage((account.clone(), RpcBytes(data.to_vec())).into());

Box::new(dispatch::from_rpc(payload, account.into(), &dispatcher)
.and_then(|payload| {
Expand Down
9 changes: 7 additions & 2 deletions rpc/src/v1/traits/personal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -42,11 +43,15 @@ build_rpc_trait! {
#[rpc(name = "personal_sign")]
fn sign(&self, Bytes, H160, String) -> BoxFuture<H520>;

/// 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<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
50 changes: 50 additions & 0 deletions rpc/src/v1/types/eip191.rs
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

//! 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<D>(deserializer: D) -> Result<Self, D::Error>
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)
}
}
3 changes: 2 additions & 1 deletion rpc/src/v1/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
17 changes: 10 additions & 7 deletions util/EIP-712/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ fn encode_data(
message_type: &Type,
message_types: &MessageTypes,
value: &Value,
field_name: Option<&str>
field_name: Option<&str>,
) -> Result<Vec<u8>>
{
let encoded = match message_type {
Expand Down Expand Up @@ -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)))?;
Expand All @@ -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)))?;
Expand Down Expand Up @@ -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)))?
Expand All @@ -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<Vec<u8>> {
pub fn hash_structured_data(typed_data: EIP712) -> Result<H256> {
// EIP-191 compliant
let prefix = (b"\x19\x01").to_vec();
let domain = to_value(&typed_data.domain).unwrap();
Expand All @@ -208,7 +211,7 @@ pub fn hash_structured_data(typed_data: EIP712) -> Result<Vec<u8>> {
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)]
Expand Down

0 comments on commit 05526ca

Please sign in to comment.