EIP-712 implementation (#9631)

* EIP-712 impl

* added more tests

* removed size parsing unwrap

* corrected TYPE_REGEX to disallow zero sized fixed length arrays, replaced LinkedHashSet with IndexSet, added API spec to docs, fixed Type::Byte encoding branch

* use Option<u64> instead of u64 for Type::Array::Length

* replace `.iter()` with  `.values()`

Co-Authored-By: seunlanlege <seunlanlege@gmail.com>

* tabify eip712.rs

* use proper comments for docs

* Cargo.lock: revert unrelated changes

* tabify encode.rs
This commit is contained in:
Seun LanLege 2018-10-30 22:12:42 +01:00 committed by Andronik Ordian
parent 1b9396dcbb
commit 61c1646b43
12 changed files with 1277 additions and 4 deletions

136
Cargo.lock generated
View File

@ -424,6 +424,29 @@ name = "edit-distance"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "eip712"
version = "0.1.0"
dependencies = [
"ethabi 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethereum-types 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)",
"keccak-hash 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lunarity-lexer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-hex 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)",
"toolshed 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"validator 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"validator_derive 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "either"
version = "1.5.0"
@ -1061,6 +1084,26 @@ dependencies = [
"vm 0.1.0",
]
[[package]]
name = "failure"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "failure_derive"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.11 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fake-fetch"
version = "0.0.1"
@ -1173,6 +1216,14 @@ dependencies = [
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "gcc"
version = "0.3.55"
@ -1216,7 +1267,7 @@ dependencies = [
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
"http 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"string 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1389,6 +1440,11 @@ dependencies = [
"unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "if_chain"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "igd"
version = "0.7.0"
@ -1407,7 +1463,7 @@ dependencies = [
[[package]]
name = "indexmap"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -1442,6 +1498,14 @@ dependencies = [
"either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itertools"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itoa"
version = "0.4.3"
@ -1751,6 +1815,14 @@ dependencies = [
"linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lunarity-lexer"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"toolshed 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "macros"
version = "0.1.0"
@ -2261,6 +2333,7 @@ version = "1.12.0"
dependencies = [
"ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"cid 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"eip712 0.1.0",
"ethash 1.12.0",
"ethcore 1.12.0",
"ethcore-devtools 1.12.0",
@ -3176,6 +3249,17 @@ dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "synstructure"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.11 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "target_info"
version = "0.1.0"
@ -3515,6 +3599,14 @@ dependencies = [
"serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "toolshed"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "trace-time"
version = "0.1.1"
@ -3687,6 +3779,34 @@ name = "utf8-ranges"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "validator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "validator_derive"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"if_chain 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.11 (registry+https://github.com/rust-lang/crates.io-index)",
"validator 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "vec_map"
version = "0.8.1"
@ -3972,6 +4092,8 @@ dependencies = [
"checksum ethbloom 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a93a43ce2e9f09071449da36bfa7a1b20b950ee344b6904ff23de493b03b386"
"checksum ethereum-types 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "35b3c5a18bc5e73a32a110ac743ec04b02bbbcd3b71d3118d40a6113d509378a"
"checksum ethereum-types-serialize 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ac59a21a9ce98e188f3dace9eb67a6c4a3c67ec7fbc7218cb827852679dc002"
"checksum failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6dd377bcc1b1b7ce911967e3ec24fa19c3224394ec05b54aa7b083d498341ac7"
"checksum failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "64c2d913fe8ed3b6c6518eedf4538255b989945c14c2a7d5cbff62a5e2120596"
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
"checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa"
"checksum fixed-hash 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d5ec8112f00ea8a483e04748a85522184418fd1cf02890b626d8fc28683f7de"
@ -3982,6 +4104,7 @@ dependencies = [
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b"
"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
"checksum fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
"checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d"
"checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797"
@ -4001,13 +4124,15 @@ dependencies = [
"checksum hyper 0.12.11 (registry+https://github.com/rust-lang/crates.io-index)" = "78d50abbd1790e0f4c74cb1d4a2211b439bac661d54107ad5564c55e77906762"
"checksum hyper-rustls 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "68f2aa6b1681795bf4da8063f718cd23145aa0c9a5143d9787b345aa60d38ee4"
"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
"checksum if_chain 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4bac95d9aa0624e7b78187d6fb8ab012b41d9f6f54b1bcb61e61c4845f8357ec"
"checksum igd 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8a254e265e8810deb357a9de757f784787ec415d056ededf410c0aa460afee9e"
"checksum indexmap 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08173ba1e906efb6538785a8844dd496f5d34f0a2d88038e95195172fc667220"
"checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d"
"checksum integer-encoding 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "26746cbc2e680af687e88d717f20ff90079bd10fc984ad57d277cd0e37309fa5"
"checksum interleaved-ordered 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "141340095b15ed7491bd3d4ced9d20cebfb826174b6bb03386381f62b01e3d77"
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
"checksum ipnetwork 0.12.8 (registry+https://github.com/rust-lang/crates.io-index)" = "70783119ac90828aaba91eae39db32c6c1b8838deea3637e5238efa0130801ab"
"checksum itertools 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4833d6978da405305126af4ac88569b5d71ff758581ce5a987dbfa3755f694fc"
"checksum itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "f58856976b776fedd95533137617a02fb25719f40e7d9b01c7043cd65474f450"
"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
"checksum jni 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1ecfa3b81afc64d9a6539c4eece96ac9a93c551c713a313800dade8e33d7b5c1"
"checksum jni-sys 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
@ -4038,6 +4163,7 @@ dependencies = [
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f"
"checksum lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d06ff7ff06f729ce5f4e227876cb88d10bc59cd4ae1e09fbb2bde15c850dc21"
"checksum lunarity-lexer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8a1670671f305792567116d4660e6e5bd785d6fa973e817c3445c0a7a54cecb6"
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
"checksum memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4b3629fe9fdbff6daa6c33b90f7c08355c1aca05a3d01fa8063b822fcf185f3b"
"checksum memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff"
@ -4157,6 +4283,7 @@ dependencies = [
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum syn 0.15.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b036b7b35e846707c0e55c2c9441fa47867c0f87fca416921db3261b1d8c741a"
"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
"checksum target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c63f48baada5c52e65a29eef93ab4f8982681b67f9e8d29c7b05abcfec2b9ffe"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
"checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327"
@ -4189,6 +4316,7 @@ dependencies = [
"checksum tokio-udp 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "da941144b816d0dcda4db3a1ba87596e4df5e860a72b70783fe435891f80601c"
"checksum tokio-uds 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22e3aa6d1fcc19e635418dc0a30ab5bd65d347973d6f43f1a37bf8d9d1335fc9"
"checksum toml 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "4a2ecc31b0351ea18b3fe11274b8db6e4d82bce861bbb22e6dbed40417902c65"
"checksum toolshed 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "450441e131c7663af72e63a33c02a6a1fbaaa8601dc652ed6757813bb55aeec7"
"checksum trace-time 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe82f2f0bf1991e163e757baf044282823155dd326e70f44ce2186c3c320cc9"
"checksum transaction-pool 1.13.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e5866e5126b14358f1d7af4bf51a0be677a363799b90e655edcec8254edef1d2"
"checksum transient-hashmap 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aeb4b191d033a35edfce392a38cdcf9790b6cebcb30fa690c312c29da4dc433e"
@ -4211,6 +4339,8 @@ dependencies = [
"checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f"
"checksum url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2a321979c09843d272956e73700d12c4e7d3d92b2ee112b31548aef0d4efc5a6"
"checksum utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd70f467df6810094968e2fce0ee1bd0e87157aceb026a8c083bcf5e25b9efe4"
"checksum validator 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "236a5eda3df2c877872e98dbc55d497d943792e6405d8fc65bd4f8a5e3b53c99"
"checksum validator_derive 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d360d6f5754972c0c1da14fb3d5580daa31aee566e1e45e2f8d3bf5950ecd3e9"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum vergen 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c3365f36c57e5df714a34be40902b27a992eeddb9996eca52d0584611cf885d"
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"

View File

@ -59,6 +59,7 @@ parity-updater = { path = "../updater" }
parity-version = { path = "../util/version" }
patricia-trie = "0.3.0"
rlp = { version = "0.3.0", features = ["ethereum"] }
eip712 = { path = "../util/EIP-712" }
stats = { path = "../util/stats" }
vm = { path = "../ethcore/vm" }

View File

@ -63,6 +63,7 @@ extern crate parity_runtime;
extern crate parity_updater as updater;
extern crate parity_version as version;
extern crate patricia_trie as trie;
extern crate eip712;
extern crate rlp;
extern crate stats;
extern crate vm;

View File

@ -286,6 +286,14 @@ pub fn signing(error: AccountError) -> Error {
}
}
pub fn invalid_call_data<T: fmt::Display>(error: T) -> Error {
Error {
code: ErrorCode::ServerError(codes::ENCODING_ERROR),
message: format!("{}", error),
data: None
}
}
pub fn password(error: AccountError) -> Error {
Error {
code: ErrorCode::ServerError(codes::PASSWORD_INVALID),

View File

@ -38,6 +38,7 @@ use v1::types::{
RichRawTransaction as RpcRichRawTransaction,
};
use v1::metadata::Metadata;
use eip712::{EIP712, hash_structured_data};
/// Account management (personal) rpc implementation.
pub struct PersonalClient<D: Dispatcher> {
@ -150,6 +151,29 @@ impl<D: Dispatcher + 'static> Personal for PersonalClient<D> {
}))
}
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())))),
};
let dispatcher = self.dispatcher.clone();
let accounts = self.accounts.clone();
let payload = RpcConfirmationPayload::EthSignMessage((account.clone(), RpcBytes(data)).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 ec_recover(&self, data: RpcBytes, signature: RpcH520) -> BoxFuture<RpcH160> {
let signature: H520 = signature.into();
let signature = Signature::from_electrum(&signature);

View File

@ -16,7 +16,7 @@
//! Personal rpc interface.
use jsonrpc_core::{BoxFuture, Result};
use eip712::EIP712;
use v1::types::{Bytes, U128, H160, H256, H520, TransactionRequest, RichRawTransaction as RpcRichRawTransaction};
build_rpc_trait! {
@ -42,6 +42,11 @@ build_rpc_trait! {
#[rpc(name = "personal_sign")]
fn sign(&self, Bytes, H160, String) -> BoxFuture<H520>;
/// 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>;
/// Returns the account associated with the private key that was used to calculate the signature in
/// `personal_sign`.
#[rpc(name = "personal_ecRecover")]

23
util/EIP-712/Cargo.toml Normal file
View File

@ -0,0 +1,23 @@
[package]
name = "eip712"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
serde_derive = "1.0"
serde = "1.0"
serde_json = "1.0"
ethabi = "6.0"
keccak-hash = "0.1"
ethereum-types = "0.4"
failure = "0.1"
itertools = "0.7"
failure_derive = "0.1"
lazy_static = "1.1"
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"

177
util/EIP-712/src/eip712.rs Normal file
View File

@ -0,0 +1,177 @@
// 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/>.
//! EIP712 structs
use serde_json::{Value};
use std::collections::HashMap;
use ethereum_types::{U256, H256, Address};
use regex::Regex;
use validator::Validate;
use validator::ValidationErrors;
pub(crate) type MessageTypes = HashMap<String, Vec<FieldType>>;
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]*(\[([1-9]\d*)*\])*$").unwrap();
static ref IDENT_REGEX: Regex = Regex::new(r"^[a-zA-Z_$][a-zA-Z_$0-9]*$").unwrap();
}
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
#[derive(Deserialize, Serialize, Validate, Debug, Clone)]
pub(crate) struct EIP712Domain {
pub(crate) name: String,
pub(crate) version: String,
pub(crate) chain_id: U256,
pub(crate) verifying_contract: Address,
#[serde(skip_serializing_if="Option::is_none")]
pub(crate) salt: Option<H256>,
}
/// EIP-712 struct
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
#[derive(Deserialize, Debug, Clone)]
pub struct EIP712 {
pub(crate) types: MessageTypes,
pub(crate) primary_type: String,
pub(crate) message: Value,
pub(crate) domain: EIP712Domain,
}
impl Validate for EIP712 {
fn validate(&self) -> Result<(), ValidationErrors> {
for field_types in self.types.values() {
for field_type in field_types {
field_type.validate()?;
}
}
Ok(())
}
}
#[derive(Serialize, Deserialize, Validate, Debug, Clone)]
pub(crate) struct FieldType {
#[validate(regex = "IDENT_REGEX")]
pub name: String,
#[serde(rename = "type")]
#[validate(regex = "TYPE_REGEX")]
pub type_: String,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::from_str;
#[test]
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)
}
let test_cases = vec!["bytes32", "Foo[]", "bytes1", "bytes32[][]", "byte[9]", "contents"];
for case in test_cases {
assert_eq!(TYPE_REGEX.is_match(case), true)
}
}
#[test]
fn test_deserialization() {
let string = r#"{
"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" }
]
}
}"#;
let _ = from_str::<EIP712>(string).unwrap();
}
#[test]
fn test_failing_deserialization() {
let string = r#"{
"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": "7uint256[x] Seun" },
{ "name": "verifyingContract", "type": "address" }
],
"Person": [
{ "name": "name", "type": "string" },
{ "name": "wallet amen", "type": "address" }
],
"Mail": [
{ "name": "from", "type": "Person" },
{ "name": "to", "type": "Person" },
{ "name": "contents", "type": "string" }
]
}
}"#;
let data = from_str::<EIP712>(string).unwrap();
assert_eq!(data.validate().is_err(), true);
}
}

415
util/EIP-712/src/encode.rs Normal file
View File

@ -0,0 +1,415 @@
// 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/>.
//! EIP712 Encoder
use ethabi::{encode, Token as EthAbiToken};
use ethereum_types::{Address as EthAddress, U256, H256};
use keccak_hash::keccak;
use serde_json::Value;
use std::str::FromStr;
use itertools::Itertools;
use indexmap::IndexSet;
use serde_json::to_value;
use parser::{Parser, Type};
use error::{Result, ErrorKind, serde_error};
use eip712::{EIP712, MessageTypes};
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<String, Vec<FieldType>>
/// 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>)>
{
if message_types.get(message_type).is_none() {
return None;
}
let mut types = IndexSet::new();
types.insert(message_type);
let mut deps = HashSet::new();
while let Some(item) = types.pop() {
if let Some(fields) = message_types.get(item) {
deps.insert(item);
for field in fields {
// seen this type before? or not a custom type skip
if deps.contains(&*field.type_) || !message_types.contains_key(&*field.type_) {
continue;
}
types.insert(&*field.type_);
}
}
};
return Some(deps)
}
fn encode_type(message_type: &str, message_types: &MessageTypes) -> Result<String> {
let deps = {
let mut temp = build_dependencies(message_type, message_types).ok_or_else(|| ErrorKind::NonExistentType)?;
temp.remove(message_type);
let mut temp = temp.into_iter().collect::<Vec<_>>();
(&mut temp[..]).sort_unstable();
temp.insert(0, message_type);
temp
};
let encoded = deps
.into_iter()
.filter_map(|dep| {
message_types.get(dep).map(|field_types| {
let types = field_types
.iter()
.map(|value| format!("{} {}", value.type_, value.name))
.join(",");
return format!("{}({})", dep, types);
})
})
.collect::<Vec<_>>()
.concat();
Ok(encoded)
}
fn type_hash(message_type: &str, typed_data: &MessageTypes) -> Result<H256> {
Ok(keccak(encode_type(message_type, typed_data)?))
}
fn encode_data(
parser: &Parser,
message_type: &Type,
message_types: &MessageTypes,
value: &Value,
field_name: Option<&str>
) -> Result<Vec<u8>>
{
let encoded = match message_type {
Type::Array {
inner,
length
} => {
let mut items = vec![];
let values = value.as_array().ok_or_else(|| serde_error("array", field_name))?;
// check if the type definition actually matches
// the length of items to be encoded
if length.is_some() && Some(values.len() as u64) != *length {
let array_type = format!("{}[{}]", *inner, length.unwrap());
return Err(ErrorKind::UnequalArrayItems(length.unwrap(), array_type, values.len() as u64))?
}
for item in values {
let mut encoded = encode_data(parser, &*inner, &message_types, item, field_name)?;
items.append(&mut encoded);
}
keccak(items).to_vec()
}
Type::Custom(ref ident) if message_types.get(&*ident).is_some() => {
let type_hash = (&type_hash(ident, &message_types)?).to_vec();
let mut tokens = encode(&[EthAbiToken::FixedBytes(type_hash)]);
for field in message_types.get(ident).expect("Already checked in match guard; qed") {
let value = &value[&field.name];
let type_ = parser.parse_type(&*field.type_)?;
let mut encoded = encode_data(parser, &type_, &message_types, &value, Some(&*field.name))?;
tokens.append(&mut encoded);
}
keccak(tokens).to_vec()
}
Type::Bytes => {
let string = value.as_str().ok_or_else(|| serde_error("string", field_name))?;
check_hex(&string)?;
let bytes = (&string[2..])
.from_hex::<Vec<u8>>()
.map_err(|err| ErrorKind::HexParseError(format!("{}", err)))?;
let bytes = keccak(&bytes).to_vec();
encode(&[EthAbiToken::FixedBytes(bytes)])
}
Type::Byte(_) => {
let string = value.as_str().ok_or_else(|| serde_error("string", field_name))?;
check_hex(&string)?;
let mut bytes = (&string[2..])
.from_hex::<Vec<u8>>()
.map_err(|err| ErrorKind::HexParseError(format!("{}", err)))?;
encode(&[EthAbiToken::FixedBytes(bytes)])
}
Type::String => {
let value = value.as_str().ok_or_else(|| serde_error("string", field_name))?;
let hash = keccak(value).to_vec();
encode(&[EthAbiToken::FixedBytes(hash)])
}
Type::Bool => encode(&[EthAbiToken::Bool(value.as_bool().ok_or_else(|| serde_error("bool", field_name))?)]),
Type::Address => {
let addr = value.as_str().ok_or_else(|| serde_error("string", field_name))?;
if addr.len() != 42 {
return Err(ErrorKind::InvalidAddressLength(addr.len()))?;
}
let address = EthAddress::from_str(&addr[2..]).map_err(|err| ErrorKind::HexParseError(format!("{}", err)))?;
encode(&[EthAbiToken::Address(address)])
}
Type::Uint | Type::Int => {
let string = value.as_str().ok_or_else(|| serde_error("int/uint", field_name))?;
check_hex(&string)?;
let uint = U256::from_str(&string[2..]).map_err(|err| ErrorKind::HexParseError(format!("{}", err)))?;
let token = if *message_type == Type::Uint {
EthAbiToken::Uint(uint)
} else {
EthAbiToken::Int(uint)
};
encode(&[token])
}
_ => return Err(ErrorKind::UnknownType(format!("{}", field_name.unwrap_or("")), format!("{}", *message_type)))?
};
Ok(encoded)
}
/// encodes and hashes the given EIP712 struct
pub fn hash_structured_data(typed_data: EIP712) -> Result<Vec<u8>> {
// validate input
typed_data.validate()?;
// EIP-191 compliant
let prefix = (b"\x19\x01").to_vec();
let domain = to_value(&typed_data.domain).unwrap();
let parser = Parser::new();
let (domain_hash, data_hash) = (
encode_data(&parser, &Type::Custom("EIP712Domain".into()), &typed_data.types, &domain, None)?,
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())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::from_str;
use rustc_hex::ToHex;
const JSON: &'static str = r#"{
"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" }
]
}
}"#;
#[test]
fn test_build_dependencies() {
let string = r#"{
"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" }
]
}"#;
let value = from_str::<MessageTypes>(string).expect("alas error!");
let mail = "Mail";
let person = "Person";
let hashset = {
let mut temp = HashSet::new();
temp.insert(mail);
temp.insert(person);
temp
};
assert_eq!(build_dependencies(mail, &value), Some(hashset));
}
#[test]
fn test_encode_type() {
let string = r#"{
"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" }
]
}"#;
let value = from_str::<MessageTypes>(string).expect("alas error!");
let mail = &String::from("Mail");
assert_eq!(
"Mail(Person from,Person to,string contents)Person(string name,address wallet)",
encode_type(&mail, &value).expect("alas error!")
)
}
#[test]
fn test_encode_type_hash() {
let string = r#"{
"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" }
]
}"#;
let value = from_str::<MessageTypes>(string).expect("alas error!");
let mail = &String::from("Mail");
let hash = (type_hash(&mail, &value).expect("alas error!").0).to_hex::<String>();
assert_eq!(
hash,
"a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2"
);
}
#[test]
fn test_hash_data() {
let typed_data = from_str::<EIP712>(JSON).expect("alas error!");
assert_eq!(
hash_structured_data(typed_data).expect("alas error!").to_hex::<String>(),
"be609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2"
)
}
#[test]
fn test_unequal_array_lengths() {
const TEST: &'static str = r#"{
"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[2]" },
{ "name": "contents", "type": "string" }
]
}
}"#;
let typed_data = from_str::<EIP712>(TEST).expect("alas error!");
assert_eq!(
hash_structured_data(typed_data).unwrap_err().kind(),
ErrorKind::UnequalArrayItems(2, "Person[2]".into(), 1)
)
}
}

125
util/EIP-712/src/error.rs Normal file
View File

@ -0,0 +1,125 @@
// 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/>.
use std::fmt::{self, Display};
use failure::{Fail, Context, Backtrace};
use validator::ValidationErrors;
use validator::ValidationErrorsKind;
pub(crate) type Result<T> = ::std::result::Result<T, Error>;
/// Error type
#[derive(Debug)]
pub struct Error {
inner: Context<ErrorKind>,
}
/// Possible errors encountered while hashing/encoding an EIP-712 compliant data structure
#[derive(Clone, Fail, Debug, PartialEq)]
pub enum ErrorKind {
/// if we fail to deserialize from a serde::Value as a type specified in message types
/// fail with this error.
#[fail(display = "Expected type '{}' for field '{}'", _0, _1)]
UnexpectedType(String, String),
/// the primary type supplied doesn't exist in the MessageTypes
#[fail(display = "The given primaryType wasn't found in the types field")]
NonExistentType,
/// an invalid address was encountered during encoding
#[fail(display = "Address string should be a 0x-prefixed 40 character string, got '{}'", _0)]
InvalidAddressLength(usize),
/// a hex parse error occured
#[fail(display = "Failed to parse hex '{}'", _0)]
HexParseError(String),
/// the field was declared with a unknown type
#[fail(display = "The field '{}' has an unknown type '{}'", _0, _1)]
UnknownType(String, String),
/// Unexpected token
#[fail(display = "Unexpected token '{}' while parsing typename '{}'", _0, _1)]
UnexpectedToken(String, String),
/// the user has attempted to define a typed array with a depth > 10
#[fail(display = "Maximum depth for nested arrays is 10")]
UnsupportedArrayDepth,
/// FieldType validation error
#[fail(display = "{}", _0)]
ValidationError(String),
/// the typed array defined in message types was declared with a fixed length
/// that is of unequal length with the items to be encoded
#[fail(display = "Expected {} items for array type {}, got {} items", _0, _1, _2)]
UnequalArrayItems(u64, String, u64),
/// Typed array length doesn't fit into a u64
#[fail(display = "Attempted to declare fixed size with length {}", _0)]
InvalidArraySize(String)
}
pub(crate) fn serde_error(expected: &str, field: Option<&str>) -> ErrorKind {
ErrorKind::UnexpectedType(expected.to_owned(), field.unwrap_or("").to_owned())
}
impl Fail for Error {
fn cause(&self) -> Option<&Fail> {
self.inner.cause()
}
fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.inner, f)
}
}
impl Error {
/// extract the error kind
pub fn kind(&self) -> ErrorKind {
self.inner.get_context().clone()
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error { inner: Context::new(kind) }
}
}
impl From<Context<ErrorKind>> for Error {
fn from(inner: Context<ErrorKind>) -> Error {
Error { inner }
}
}
impl From<ValidationErrors> for Error {
fn from(error: ValidationErrors) -> Self {
let mut string: String = "".into();
for (field_name, error_kind) in error.errors() {
match error_kind {
ValidationErrorsKind::Field(validation_errors) => {
for error in validation_errors {
let str_error = format!(
"the field '{}', has an invalid value {}",
field_name,
error.params["value"]
);
string.push_str(&str_error);
}
},
_ => unreachable!("#[validate] is only used on fields for regex;\
its impossible to get any other ErrorKind; qed")
}
}
ErrorKind::ValidationError(string).into()
}
}

192
util/EIP-712/src/lib.rs Normal file
View File

@ -0,0 +1,192 @@
// 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-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)`
//! <br/>
//! <br/>
//! Struct named `EIP712Domain` with the following fields
//!
//! - `name: String`
//! - `version: String`
//! - `chain_id: U256`,
//! - `verifying_contract: H160`
//! - `salt: Option<H256>`
//!
//! ## C) hashStruct
//!
//! `hashStruct(s : 𝕊) = keccak256(typeHash ‖ encodeData(s))`
//! <br/>
//! `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;
extern crate ethabi;
extern crate ethereum_types;
extern crate keccak_hash;
extern crate itertools;
extern crate failure;
extern crate indexmap;
extern crate lunarity_lexer;
extern crate toolshed;
extern crate regex;
extern crate validator;
#[macro_use]
extern crate validator_derive;
#[macro_use]
extern crate failure_derive;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate lazy_static;
extern crate rustc_hex;
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;

172
util/EIP-712/src/parser.rs Normal file
View File

@ -0,0 +1,172 @@
// 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/>.
//! Solidity type-name parsing
use lunarity_lexer::{Lexer, Token};
use error::*;
use toolshed::Arena;
use std::{fmt, result};
#[derive(Debug, Clone, PartialEq)]
pub enum Type {
Address,
Uint,
Int,
String,
Bool,
Bytes,
Byte(u8),
Custom(String),
Array {
length: Option<u64>,
inner: Box<Type>
}
}
impl From<Type> for String {
fn from(field_type: Type) -> String {
match field_type {
Type::Address => "address".into(),
Type::Uint => "uint".into(),
Type::Int => "int".into(),
Type::String => "string".into(),
Type::Bool => "bool".into(),
Type::Bytes => "bytes".into(),
Type::Byte(len) => format!("bytes{}", len),
Type::Custom(custom) => custom,
Type::Array {
inner,
length
} => {
let inner: String = (*inner).into();
match length {
None => format!("{}[]", inner),
Some(length) => format!("{}[{}]", inner, length)
}
}
}
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
let item: String = self.clone().into();
write!(f, "{}", item)
}
}
pub struct Parser {
arena: Arena,
}
impl Parser {
pub fn new() -> Self {
Parser {
arena: Arena::new()
}
}
/// the type string is being validated before it's parsed.
pub fn parse_type(&self, field_type: &str) -> Result<Type> {
#[derive(PartialEq)]
enum State { Open, Close }
let mut lexer = Lexer::new(&self.arena, field_type);
let mut token = None;
let mut state = State::Close;
let mut array_depth = 0;
let mut current_array_length: Option<u64> = None;
while lexer.token != Token::EndOfProgram {
let type_ = match lexer.token {
Token::Identifier => Type::Custom(lexer.token_as_str().to_owned()),
Token::TypeByte => Type::Byte(lexer.type_size.0),
Token::TypeBytes => Type::Bytes,
Token::TypeBool => Type::Bool,
Token::TypeUint => Type::Uint,
Token::TypeInt => Type::Int,
Token::TypeString => Type::String,
Token::TypeAddress => Type::Address,
Token::LiteralInteger => {
let length = lexer.token_as_str();
current_array_length = Some(length
.parse()
.map_err(|_|
ErrorKind::InvalidArraySize(length.into())
)?
);
lexer.consume();
continue;
},
Token::BracketOpen if token.is_some() && state == State::Close => {
state = State::Open;
lexer.consume();
continue
}
Token::BracketClose if array_depth < 10 => {
if state == State::Open && token.is_some() {
let length = current_array_length.take();
state = State::Close;
token = Some(Type::Array {
inner: Box::new(token.expect("if statement checks for some; qed")),
length
});
lexer.consume();
array_depth += 1;
continue
} else {
return Err(ErrorKind::UnexpectedToken(lexer.token_as_str().to_owned(), field_type.to_owned()))?
}
}
Token::BracketClose if array_depth == 10 => {
return Err(ErrorKind::UnsupportedArrayDepth)?
}
_ => return Err(ErrorKind::UnexpectedToken(lexer.token_as_str().to_owned(), field_type.to_owned()))?
};
token = Some(type_);
lexer.consume();
}
Ok(token.ok_or_else(|| ErrorKind::NonExistentType)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parser() {
let parser = Parser::new();
let source = "byte[][][7][][][][][][][]";
parser.parse_type(source).unwrap();
}
#[test]
fn test_nested_array() {
let parser = Parser::new();
let source = "byte[][][7][][][][][][][][]";
assert_eq!(parser.parse_type(source).is_err(), true);
}
#[test]
fn test_malformed_array_type() {
let parser = Parser::new();
let source = "byte[7[]uint][]";
assert_eq!(parser.parse_type(source).is_err(), true)
}
}