From 9fe6fee5a33003769a35b65cd36207e2703c3e73 Mon Sep 17 00:00:00 2001 From: Seun LanLege Date: Tue, 30 Oct 2018 00:41:49 +0100 Subject: [PATCH] corrected TYPE_REGEX to disallow zero sized fixed length arrays, replaced LinkedHashSet with IndexSet, added API spec to docs, fixed Type::Byte encoding branch --- util/EIP-712/Cargo.toml | 2 +- util/EIP-712/src/eip712.rs | 6 +- util/EIP-712/src/encode.rs | 43 ++++++------ util/EIP-712/src/error.rs | 5 +- util/EIP-712/src/lib.rs | 139 ++++++++++++++++++++++++++++++++++++- 5 files changed, 165 insertions(+), 30 deletions(-) diff --git a/util/EIP-712/Cargo.toml b/util/EIP-712/Cargo.toml index 9492467acd5..308df98d8ad 100644 --- a/util/EIP-712/Cargo.toml +++ b/util/EIP-712/Cargo.toml @@ -14,10 +14,10 @@ failure = "0.1" itertools = "0.7" failure_derive = "0.1" lazy_static = "1.1" -linked_hash_set = "0.1.3" toolshed = "0.4" regex = "1.0" validator = "0.8" validator_derive = "0.8" lunarity-lexer = "0.1" rustc-hex = "2.0" +indexmap = "1.0.2" diff --git a/util/EIP-712/src/eip712.rs b/util/EIP-712/src/eip712.rs index a93a3f0a6f7..1b9a22a5b5b 100644 --- a/util/EIP-712/src/eip712.rs +++ b/util/EIP-712/src/eip712.rs @@ -26,7 +26,7 @@ pub(crate) type MessageTypes = HashMap>; lazy_static! { // match solidity identifier with the addition of '[(\d)*]*' - static ref TYPE_REGEX: Regex = Regex::new(r"^[a-zA-Z_$][a-zA-Z_$0-9]*(\[[0-9]*\])*$").unwrap(); + static ref TYPE_REGEX: Regex = Regex::new(r"^[a-zA-Z_$][a-zA-Z_$0-9]*(\[([1-9]\d*)*\])*$").unwrap(); static ref IDENT_REGEX: Regex = Regex::new(r"^[a-zA-Z_$][a-zA-Z_$0-9]*$").unwrap(); } @@ -78,8 +78,8 @@ mod tests { use serde_json::from_str; #[test] - fn test_ident_regex() { - let test_cases = vec!["unint bytes32", "Seun\\[]", "byte[]uint", "byte[7[]uint][]"]; + fn test_regex() { + let test_cases = vec!["unint bytes32", "Seun\\[]", "byte[]uint", "byte[7[]uint][]", "Person[0]"]; for case in test_cases { assert_eq!(TYPE_REGEX.is_match(case), false) } diff --git a/util/EIP-712/src/encode.rs b/util/EIP-712/src/encode.rs index 47a0c29d852..2f0863555be 100644 --- a/util/EIP-712/src/encode.rs +++ b/util/EIP-712/src/encode.rs @@ -21,7 +21,7 @@ use keccak_hash::keccak; use serde_json::Value; use std::str::FromStr; use itertools::Itertools; -use linked_hash_set::LinkedHashSet; +use indexmap::IndexSet; use serde_json::to_value; use parser::{Parser, Type}; use error::{Result, ErrorKind, serde_error}; @@ -30,6 +30,16 @@ use rustc_hex::FromHex; use validator::Validate; use std::collections::HashSet; + +fn check_hex(string: &str) -> Result<()> { + if string.len() >= 2 && &string[..2] == "0x" { + return Ok(()) + } + + return Err(ErrorKind::HexParseError( + format!("Expected a 0x-prefixed string of even length, found {} length string", string.len())) + )? +} /// given a type and HashMap> /// returns a HashSet of dependent types of the given type fn build_dependencies<'a>(message_type: &'a str, message_types: &'a MessageTypes) -> Option<(HashSet<&'a str>)> @@ -38,11 +48,11 @@ fn build_dependencies<'a>(message_type: &'a str, message_types: &'a MessageTypes return None; } - let mut types = LinkedHashSet::new(); + let mut types = IndexSet::new(); types.insert(message_type); let mut deps = HashSet::new(); - while let Some(item) = types.pop_back() { + while let Some(item) = types.pop() { if let Some(fields) = message_types.get(item) { deps.insert(item); @@ -136,11 +146,9 @@ fn encode_data( Type::Bytes => { let string = value.as_str().ok_or_else(|| serde_error("string", field_name))?; - if string.len() < 2 { - return Err(ErrorKind::HexParseError( - format!("Expected a 0x-prefixed string of even length, found {} length string", string.len())) - )? - } + + check_hex(&string)?; + let bytes = (&string[2..]) .from_hex::>() .map_err(|err| ErrorKind::HexParseError(format!("{}", err)))?; @@ -151,18 +159,13 @@ fn encode_data( Type::Byte(_) => { let string = value.as_str().ok_or_else(|| serde_error("string", field_name))?; - if string.len() < 2 { - return Err(ErrorKind::HexParseError( - format!("Expected a 0x-prefixed string of even length, found {} length string", string.len())) - )? - } + + check_hex(&string)?; + let mut bytes = (&string[2..]) .from_hex::>() .map_err(|err| ErrorKind::HexParseError(format!("{}", err)))?; - if *message_type == Type::Bytes { - bytes = keccak(&bytes).to_vec(); - } encode(&[EthAbiToken::FixedBytes(bytes)]) } @@ -185,11 +188,9 @@ fn encode_data( Type::Uint | Type::Int => { let string = value.as_str().ok_or_else(|| serde_error("int/uint", field_name))?; - if string.len() < 2 { - return Err(ErrorKind::HexParseError( - format!("Expected a 0x-prefixed string of even length, found {} length string", string.len())) - )? - } + + check_hex(&string)?; + let uint = U256::from_str(&string[2..]).map_err(|err| ErrorKind::HexParseError(format!("{}", err)))?; let token = if *message_type == Type::Uint { diff --git a/util/EIP-712/src/error.rs b/util/EIP-712/src/error.rs index b3de6b26457..e6b5634e546 100644 --- a/util/EIP-712/src/error.rs +++ b/util/EIP-712/src/error.rs @@ -116,9 +116,8 @@ impl From for Error { string.push_str(&str_error); } }, - // #[validate] is only used on fields for regex - // its impossible to get any other errorkind - _ => unreachable!() + _ => unreachable!("#[validate] is only used on fields for regex;\ + its impossible to get any other ErrorKind; qed") } } ErrorKind::ValidationError(string).into() diff --git a/util/EIP-712/src/lib.rs b/util/EIP-712/src/lib.rs index a176c3f7069..421ca2f1f41 100644 --- a/util/EIP-712/src/lib.rs +++ b/util/EIP-712/src/lib.rs @@ -15,6 +15,140 @@ // along with Parity. If not, see . //! EIP-712 encoding utilities +//! # Specification +//`encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domainSeparator ‖ hashStruct(message)` +//- data adheres to 𝕊, a structure defined in the rigorous eip-712 +//- `\x01` is needed to comply with EIP-191 +//- `domainSeparator` and `hashStruct` are defined below +// +//## A) domainSeparator +//`domainSeparator = hashStruct(eip712Domain)` +//
+//
+//Struct named `EIP712Domain` with the following fields +// +//- `name: String` +//- `version: String` +//- `chain_id: U256`, +//- `verifying_contract: H160` +//- `salt: Option` +// +//## C) hashStruct +//`hashStruct(s : 𝕊) = keccak256(typeHash ‖ encodeData(s))` +//
+//`typeHash = keccak256(encodeType(typeOf(s)))` +// +//### i) encodeType +//- `name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")"` +//- each member is written as `type ‖ " " ‖ name` +//- encodings cascade down and are sorted by name +// +//### ii) encodeData +//- `enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ)` +//- each encoded member is 32-byte long +// +// #### a) atomic +// +// - `boolean` => `U256` +// - `address` => `H160` +// - `uint` => sign-extended `U256` in big endian order +// - `bytes1:31` => `H@256` +// +// #### b) dynamic +// +// - `bytes` => `keccak256(bytes)` +// - `string` => `keccak256(string)` +// +// #### c) referenced +// +// - `array` => `keccak256(encodeData(array))` +// - `struct` => `rec(keccak256(hashStruct(struct)))` +// +//## D) Example +//### Query +//```json +//{ +// "jsonrpc": "2.0", +// "method": "eth_signTypedData", +// "params": [ +// "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", +// { +// "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" +// } +// ] +// }, +// "primaryType": "Mail", +// "domain": { +// "name": "Ether Mail", +// "version": "1", +// "chainId": 1, +// "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" +// }, +// "message": { +// "from": { +// "name": "Cow", +// "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" +// }, +// "to": { +// "name": "Bob", +// "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" +// }, +// "contents": "Hello, Bob!" +// } +// } +// ], +// "id": 1 +//} +//``` +// +//### Response +//```json +//{ +// "id":1, +// "jsonrpc": "2.0", +// "result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" +//} +//``` #![warn(missing_docs, unused_extern_crates)] extern crate serde_json; @@ -23,7 +157,7 @@ extern crate ethereum_types; extern crate keccak_hash; extern crate itertools; extern crate failure; -extern crate linked_hash_set; +extern crate indexmap; extern crate lunarity_lexer; extern crate toolshed; extern crate regex; @@ -42,9 +176,10 @@ mod eip712; mod error; mod parser; mod encode; + /// the EIP-712 encoding function pub use encode::hash_structured_data; /// encoding Error types pub use error::{ErrorKind, Error}; /// EIP712 struct -pub use eip712::{EIP712}; +pub use eip712::EIP712;