feat: generalize wallet/private key + yubihsm2 (#75)

* feat: generalize wallet/private key

* fix: adjust celo tests

* YubiHSM2 Support (#76)

* feat: support YubiHSM2
This commit is contained in:
Georgios Konstantopoulos 2020-10-02 11:41:16 +03:00 committed by GitHub
parent c65497543e
commit aa37f74c4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1088 additions and 675 deletions

503
Cargo.lock generated
View File

@ -10,6 +10,68 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "addr2line"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
[[package]]
name = "adler32"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "aead"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331"
dependencies = [
"generic-array",
]
[[package]]
name = "aes"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd2bc6d3f370b5666245ff421e231cba4353df936e26986d2918e61a8fd6aef6"
dependencies = [
"aes-soft",
"aesni",
"block-cipher",
]
[[package]]
name = "aes-soft"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63dd91889c49327ad7ef3b500fd1109dbd3c509a03db0d4a9ce413b79f575cb6"
dependencies = [
"block-cipher",
"byteorder",
"opaque-debug",
]
[[package]]
name = "aesni"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6fe808308bb07d393e2ea47780043ec47683fcf19cf5efc8ca51c50cc8c68a"
dependencies = [
"block-cipher",
"opaque-debug",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.13" version = "0.7.13"
@ -19,6 +81,15 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "anomaly"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "550632e31568ae1a5f47998c3aa48563030fc49b9ec91913ca337cf64fbc5ccb"
dependencies = [
"backtrace",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.32" version = "1.0.32"
@ -64,7 +135,7 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d373d78ded7d0b3fa8039375718cde0aace493f2e34fb60f51cbf567562ca801" checksum = "d373d78ded7d0b3fa8039375718cde0aace493f2e34fb60f51cbf567562ca801"
dependencies = [ dependencies = [
"async-task 4.0.2", "async-task",
"concurrent-queue", "concurrent-queue",
"fastrand", "fastrand",
"futures-lite", "futures-lite",
@ -74,9 +145,9 @@ dependencies = [
[[package]] [[package]]
name = "async-global-executor" name = "async-global-executor"
version = "1.2.1" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5586e693d02f9b439742e9d5d68bd64d923c6861954f7d78f91001a0e152d589" checksum = "fefeb39da249f4c33af940b779a56723ce45809ef5c54dad84bb538d4ffb6d9e"
dependencies = [ dependencies = [
"async-executor", "async-executor",
"async-io", "async-io",
@ -87,9 +158,9 @@ dependencies = [
[[package]] [[package]]
name = "async-io" name = "async-io"
version = "1.1.4" version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33be191d05a54ec120e4667375e2ad49fe506b846463df384460ab801c7ae5dc" checksum = "6e727cebd055ab2861a854f79def078c4b99ea722d54c6800a0e274389882d4c"
dependencies = [ dependencies = [
"concurrent-queue", "concurrent-queue",
"fastrand", "fastrand",
@ -114,15 +185,14 @@ dependencies = [
[[package]] [[package]]
name = "async-std" name = "async-std"
version = "1.6.4" version = "1.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c92085acfce8b32e5b261d0b59b8f3309aee69fea421ea3f271f8b93225754f" checksum = "a9fa76751505e8df1c7a77762f60486f60c71bbd9b8557f4da6ad47d083732ed"
dependencies = [ dependencies = [
"async-attributes", "async-attributes",
"async-global-executor", "async-global-executor",
"async-io", "async-io",
"async-mutex", "async-mutex",
"async-task 3.0.0",
"blocking", "blocking",
"crossbeam-utils", "crossbeam-utils",
"futures-channel", "futures-channel",
@ -141,12 +211,6 @@ dependencies = [
"wasm-bindgen-futures", "wasm-bindgen-futures",
] ]
[[package]]
name = "async-task"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3"
[[package]] [[package]]
name = "async-task" name = "async-task"
version = "4.0.2" version = "4.0.2"
@ -167,9 +231,9 @@ dependencies = [
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.40" version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "687c230d85c0a52504709705fc8a53e4a692b83a2184f03dae73e38e1e93a783" checksum = "b246867b8b3b6ae56035f1eb1ed557c1d8eae97f0d53696138a50fa0e3a3b8c0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -206,6 +270,20 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "backtrace"
version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec1931848a574faa8f7c71a12ea00453ff5effbb5f51afe7f77d7a48cace6ac1"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.11.0" version = "0.11.0"
@ -276,6 +354,25 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "block-cipher"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f337a3e6da609650eb74e02bc9fac7b735049f7623ab12f2e4c719316fcc7e80"
dependencies = [
"generic-array",
]
[[package]]
name = "block-modes"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c9b14fd8a4739e6548d4b6018696cf991dcf8c6effd9ef9eb33b29b8a650972"
dependencies = [
"block-cipher",
"block-padding",
]
[[package]] [[package]]
name = "block-padding" name = "block-padding"
version = "0.2.1" version = "0.2.1"
@ -332,6 +429,17 @@ version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c"
[[package]]
name = "ccm"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbba800a6a55058ecb75c7a42e3d16a715a2b1f1afa9acf07365d6ab30d62ce1"
dependencies = [
"aead",
"block-cipher",
"subtle",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "0.1.10" version = "0.1.10"
@ -340,17 +448,28 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.18" version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d021fddb7bd3e734370acfa4a83f34095571d8570c039f1420d77540f68d5772" checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [ dependencies = [
"libc", "libc",
"num-integer", "num-integer",
"num-traits", "num-traits",
"serde",
"time", "time",
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "cmac"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5220604fe5c112e2851b00da795c72cbb71bf112f2cbd532bdcfb4106eeb320b"
dependencies = [
"crypto-mac",
"dbl",
]
[[package]] [[package]]
name = "coins-ledger" name = "coins-ledger"
version = "0.1.0" version = "0.1.0"
@ -417,6 +536,15 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
[[package]]
name = "crc32fast"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.7.2" version = "0.7.2"
@ -440,6 +568,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca" checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca"
dependencies = [ dependencies = [
"block-cipher",
"generic-array", "generic-array",
"subtle", "subtle",
] ]
@ -474,6 +603,28 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "curve25519-dalek"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8492de420e9e60bc9a1d66e2dbb91825390b738a388606600663fc529b4b307"
dependencies = [
"byteorder",
"digest",
"rand_core",
"subtle",
"zeroize",
]
[[package]]
name = "dbl"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2735145c3b9ba15f2d7a3ae8cdafcbc8c98a7bef7f62afe9d08bd99fbf7130de"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.9.0" version = "0.9.0"
@ -491,15 +642,38 @@ checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
[[package]] [[package]]
name = "ecdsa" name = "ecdsa"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a7c278cc833033b4322122e05b99dbc5a2a2a9bb2c51534ff1f4899c7802d66" checksum = "765a6f9c0938fef990bb7af306fe1e534721bc52a1eb918cc938c5212d6e07eb"
dependencies = [ dependencies = [
"elliptic-curve", "elliptic-curve",
"hmac", "hmac",
"signature", "signature",
] ]
[[package]]
name = "ed25519"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07dfc993ea376e864fe29a4099a61ca0bb994c6d7745a61bf60ddb3d64e05237"
dependencies = [
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
dependencies = [
"curve25519-dalek",
"ed25519",
"rand",
"serde",
"sha2",
"zeroize",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.6.1" version = "1.6.1"
@ -657,7 +831,6 @@ dependencies = [
"rustc-hex", "rustc-hex",
"serde", "serde",
"serde_json", "serde_json",
"sha2",
"thiserror", "thiserror",
"tiny-keccak 2.0.2", "tiny-keccak 2.0.2",
] ]
@ -712,14 +885,18 @@ version = "0.1.3"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"coins-ledger", "coins-ledger",
"elliptic-curve",
"ethers", "ethers",
"ethers-core", "ethers-core",
"futures-util", "futures-util",
"rand",
"rustc-hex", "rustc-hex",
"serde", "serde",
"serde_json", "serde_json",
"sha2",
"thiserror", "thiserror",
"tokio", "tokio",
"yubihsm",
] ]
[[package]] [[package]]
@ -745,6 +922,18 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "filetime"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed85775dcc68644b5c950ac06a2b23768d3bc9390464151aaf27136998dcf9e"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "fixed-hash" name = "fixed-hash"
version = "0.6.1" version = "0.6.1"
@ -937,6 +1126,12 @@ dependencies = [
"wasi 0.9.0+wasi-snapshot-preview1", "wasi 0.9.0+wasi-snapshot-preview1",
] ]
[[package]]
name = "gimli"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.0" version = "0.3.0"
@ -987,10 +1182,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "hashbrown" name = "harp"
version = "0.9.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00d63df3d41950fb462ed38308eea019113ad1508da725bbedcd0fa5a85ef5f7" checksum = "60bf12c625ed5e96f81609ae4377c34e9fa3e4d1fada392404322daeace511ab"
[[package]]
name = "hashbrown"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
@ -1184,12 +1385,14 @@ dependencies = [
[[package]] [[package]]
name = "k256" name = "k256"
version = "0.5.5" version = "0.5.6"
source = "git+https://github.com/RustCrypto/elliptic-curves#8845cc09dc55ba3fa41ffa2750d799cf6a23d405" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be18b15cf00d98162fc37081a5098647f41168604d96174726fb1dff59c18a22"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"ecdsa", "ecdsa",
"elliptic-curve", "elliptic-curve",
"sha2",
"sha3", "sha3",
] ]
@ -1226,9 +1429,41 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.77" version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" checksum = "aa7087f49d294270db4e1928fc110c976cd4b9e5a16348e0a1df09afa99e6c98"
[[package]]
name = "libflate"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9bac9023e1db29c084f9f8cd9d3852e5e8fddf98fb47c4964a0ea4663d95949"
dependencies = [
"adler32",
"crc32fast",
"libflate_lz77",
"rle-decode-fast",
]
[[package]]
name = "libflate_lz77"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3286f09f7d4926fc486334f28d8d2e6ebe4f7f9994494b6dab27ddfad2c9b11b"
[[package]]
name = "libusb1-sys"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f02e930161703cc97c0aab3a905feb9740db03a80910f31ab0f8fa309223f39"
dependencies = [
"cc",
"libc",
"libflate",
"pkg-config",
"tar",
"vcpkg",
]
[[package]] [[package]]
name = "libz-sys" name = "libz-sys"
@ -1279,6 +1514,16 @@ dependencies = [
"unicase", "unicase",
] ]
[[package]]
name = "miniz_oxide"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c60c0dfe32c10b43a144bad8fc83538c52f58302c92300ea7ec7bf7b38d5a7b9"
dependencies = [
"adler",
"autocfg",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.6.22" version = "0.6.22"
@ -1330,9 +1575,9 @@ dependencies = [
[[package]] [[package]]
name = "nb-connect" name = "nb-connect"
version = "1.0.0" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e847c76b390f44529c2071ef06d0b52fbb4bdb04cc8987a5cfa63954c000abca" checksum = "701f47aeb98466d0a7fea67e2c2f667c33efa1f2e4fd7f76743aac1153196f72"
dependencies = [ dependencies = [
"libc", "libc",
"winapi 0.3.9", "winapi 0.3.9",
@ -1391,6 +1636,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "object"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.4.1" version = "1.4.1"
@ -1436,6 +1687,27 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "p256"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1909a5a44a469f7d21c8dec2c634721ba15379b6cad80040d16abfaa8609b37c"
dependencies = [
"ecdsa",
"elliptic-curve",
"sha2",
]
[[package]]
name = "p384"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51ce2b1182d022bfbe2af57aeca44fe7832356dd2d998e98e423cd9b94fae4f7"
dependencies = [
"ecdsa",
"elliptic-curve",
]
[[package]] [[package]]
name = "parity-scale-codec" name = "parity-scale-codec"
version = "1.3.5" version = "1.3.5"
@ -1454,6 +1726,15 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
[[package]]
name = "pbkdf2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7170d73bf11f39b4ce1809aabc95bf5c33564cdc16fc3200ddda17a5f6e5e48b"
dependencies = [
"crypto-mac",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@ -1462,18 +1743,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "0.4.24" version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f48fad7cfbff853437be7cf54d7b993af21f53be7f0988cbfe4a51535aa77205" checksum = "2b9e280448854bd91559252582173b3bd1f8e094a0e644791c0628ca9b1f144f"
dependencies = [ dependencies = [
"pin-project-internal", "pin-project-internal",
] ]
[[package]] [[package]]
name = "pin-project-internal" name = "pin-project-internal"
version = "0.4.24" version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24c6d293bdd3ca5a1697997854c6cf7855e43fb6a0ba1c47af57a5bcafd158ae" checksum = "c8c8b352676bc6a4c3d71970560b913cea444a7a921cc2e2d920225e4b91edaa"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1482,9 +1763,9 @@ dependencies = [
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.1.8" version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71f349a4f0e70676ffb2dbafe16d0c992382d02f0a952e3ddf584fc289dac6b3" checksum = "e555d9e657502182ac97b539fb3dae8b79cda19e3e4f8ffb5e8de4f18df93c95"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@ -1544,9 +1825,9 @@ checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.23" version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51ef7cd2518ead700af67bf9d1a658d90b6037d77110fd9c0445429d0ba1c6c9" checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
@ -1699,14 +1980,36 @@ dependencies = [
] ]
[[package]] [[package]]
name = "rlp" name = "rle-decode-fast"
version = "0.4.5" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a7d3f9bed94764eac15b8f14af59fac420c236adaff743b7bcc88e265cb4345" checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac"
[[package]]
name = "rlp"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1190dcc8c3a512f1eef5d09bb8c84c7f39e1054e174d1795482e18f5272f2e73"
dependencies = [ dependencies = [
"rustc-hex", "rustc-hex",
] ]
[[package]]
name = "rusb"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67fa037368ee577fca9ef237c5ec129084c18e7e3e5987cc611fb8b2d78cf84a"
dependencies = [
"libc",
"libusb1-sys",
]
[[package]]
name = "rustc-demangle"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
[[package]] [[package]]
name = "rustc-hex" name = "rustc-hex"
version = "2.1.0" version = "2.1.0"
@ -1822,9 +2125,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.57" version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" checksum = "a230ea9107ca2220eea9d46de97eddcb04cd00e92d13dda78e478dd33fa82bd4"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -1889,6 +2192,19 @@ checksum = "29f060a7d147e33490ec10da418795238fd7545bba241504d6b31a409f2e6210"
dependencies = [ dependencies = [
"digest", "digest",
"rand_core", "rand_core",
"signature_derive",
]
[[package]]
name = "signature_derive"
version = "1.0.0-pre.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0656c180103507cca4b82519908e813225b2f6b90b2bd59ee119f46155ae872"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
] ]
[[package]] [[package]]
@ -1938,6 +2254,30 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "synstructure"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]]
name = "tar"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "489997b7557e9a43e192c527face4feacc78bfbe6eed67fd55c4c9e381cba290"
dependencies = [
"filetime",
"libc",
"redox_syscall",
"xattr",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.1.0" version = "3.1.0"
@ -2089,20 +2429,21 @@ checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.19" version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d79ca061b032d6ce30c660fded31189ca0b9922bf483cd70759f13a2d86786c" checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"log", "log",
"pin-project-lite",
"tracing-core", "tracing-core",
] ]
[[package]] [[package]]
name = "tracing-core" name = "tracing-core"
version = "0.1.16" version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bcf46c1f1f06aeea2d6b81f3c863d0930a596c86ad1920d4e5bad6dd1d7119a" checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
] ]
@ -2207,6 +2548,15 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
[[package]]
name = "uuid"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.10" version = "0.2.10"
@ -2424,8 +2774,67 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]]
name = "xattr"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"
dependencies = [
"libc",
]
[[package]]
name = "yubihsm"
version = "0.35.0-rc"
source = "git+https://github.com/iqlusioninc/yubihsm.rs.git?branch=develop#a8fa868fbc50b8a326c46ab2b178f42e3e7a4e88"
dependencies = [
"aes",
"anomaly",
"bitflags",
"block-modes",
"ccm",
"chrono",
"cmac",
"digest",
"ecdsa",
"ed25519",
"ed25519-dalek",
"harp",
"hmac",
"k256",
"log",
"p256",
"p384",
"pbkdf2",
"rand_core",
"rusb",
"serde",
"serde_json",
"sha2",
"signature",
"subtle",
"thiserror",
"uuid",
"zeroize",
]
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.1.1" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f33972566adbd2d3588b0491eb94b98b43695c4ef897903470ede4f3f5a28a" checksum = "05f33972566adbd2d3588b0491eb94b98b43695c4ef897903470ede4f3f5a28a"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]

View File

@ -7,7 +7,7 @@ use ethers_contract::{Contract, ContractFactory};
use ethers_core::utils::{GanacheInstance, Solc}; use ethers_core::utils::{GanacheInstance, Solc};
use ethers_middleware::Client; use ethers_middleware::Client;
use ethers_providers::{Http, Middleware, Provider}; use ethers_providers::{Http, Middleware, Provider};
use ethers_signers::Wallet; use ethers_signers::LocalWallet;
use std::{convert::TryFrom, sync::Arc, time::Duration}; use std::{convert::TryFrom, sync::Arc, time::Duration};
// Note: We also provide the `abigen` macro for generating these bindings automatically // Note: We also provide the `abigen` macro for generating these bindings automatically
@ -44,14 +44,14 @@ pub fn compile_contract(name: &str, filename: &str) -> (Abi, Bytes) {
(contract.abi.clone(), contract.bytecode.clone()) (contract.abi.clone(), contract.bytecode.clone())
} }
type HttpWallet = Client<Provider<Http>, Wallet>; type HttpWallet = Client<Provider<Http>, LocalWallet>;
/// connects the private key to http://localhost:8545 /// connects the private key to http://localhost:8545
pub fn connect(ganache: &GanacheInstance, idx: usize) -> Arc<HttpWallet> { pub fn connect(ganache: &GanacheInstance, idx: usize) -> Arc<HttpWallet> {
let provider = Provider::<Http>::try_from(ganache.endpoint()) let provider = Provider::<Http>::try_from(ganache.endpoint())
.unwrap() .unwrap()
.interval(Duration::from_millis(10u64)); .interval(Duration::from_millis(10u64));
let wallet: Wallet = ganache.keys()[idx].clone().into(); let wallet: LocalWallet = ganache.keys()[idx].clone().into();
Arc::new(Client::new(provider, wallet)) Arc::new(Client::new(provider, wallet))
} }

View File

@ -322,14 +322,15 @@ mod eth_tests {
assert_eq!(return_data.2, multicall_contract.address()); assert_eq!(return_data.2, multicall_contract.address());
assert_eq!(return_data.3, multicall_contract.address()); assert_eq!(return_data.3, multicall_contract.address());
let addrs = ganache.addresses();
// query ETH balances of multiple addresses // query ETH balances of multiple addresses
// these keys haven't been used to do any tx // these keys haven't been used to do any tx
// so should have 100 ETH // so should have 100 ETH
multicall multicall
.clear_calls() .clear_calls()
.eth_balance_of(Address::from(&ganache.keys()[4])) .eth_balance_of(addrs[4])
.eth_balance_of(Address::from(&ganache.keys()[5])) .eth_balance_of(addrs[5])
.eth_balance_of(Address::from(&ganache.keys()[6])); .eth_balance_of(addrs[6]);
let balances: (U256, U256, U256) = multicall.call().await.unwrap(); let balances: (U256, U256, U256) = multicall.call().await.unwrap();
assert_eq!(balances.0, U256::from(100000000000000000000u128)); assert_eq!(balances.0, U256::from(100000000000000000000u128));
assert_eq!(balances.1, U256::from(100000000000000000000u128)); assert_eq!(balances.1, U256::from(100000000000000000000u128));
@ -343,7 +344,7 @@ mod celo_tests {
use ethers::{ use ethers::{
middleware::Client, middleware::Client,
providers::{Http, Provider}, providers::{Http, Provider},
signers::Wallet, signers::LocalWallet,
types::BlockNumber, types::BlockNumber,
}; };
use std::{convert::TryFrom, sync::Arc, time::Duration}; use std::{convert::TryFrom, sync::Arc, time::Duration};
@ -359,7 +360,7 @@ mod celo_tests {
// Funded with https://celo.org/developers/faucet // Funded with https://celo.org/developers/faucet
let wallet = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1" let wallet = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
.parse::<Wallet>() .parse::<LocalWallet>()
.unwrap(); .unwrap();
let client = Client::new(provider, wallet); let client = Client::new(provider, wallet);

View File

@ -20,10 +20,9 @@ arrayvec = { version = "0.5.1", default-features = false }
ecdsa = { version = "0.8.0", features = ["std"] } ecdsa = { version = "0.8.0", features = ["std"] }
elliptic-curve = { version = "0.6.1", features = ["arithmetic"] } elliptic-curve = { version = "0.6.1", features = ["arithmetic"] }
generic-array = "0.14.4" generic-array = "0.14.4"
k256 = { git = "https://github.com/RustCrypto/elliptic-curves", version = "0.5.2", features = ["keccak256", "ecdsa"] } k256 = { version = "0.5.2", features = ["keccak256", "ecdsa"] }
rand = "0.7.2" rand = "0.7.2"
tiny-keccak = { version = "2.0.2", default-features = false } tiny-keccak = { version = "2.0.2", default-features = false }
sha2 = { version = "0.9.1" }
# misc # misc
serde = { version = "1.0.110", default-features = false, features = ["derive"] } serde = { version = "1.0.110", default-features = false, features = ["derive"] }

View File

@ -13,19 +13,21 @@
//! signing the hash of the result. //! signing the hash of the result.
//! //!
//! ```rust //! ```rust
//! use ethers::core::types::{PrivateKey, Address}; //! # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
//! use ethers::signers::{Signer, LocalWallet};
//! //!
//! let message = "Some data"; //! let message = "Some data";
//! let key = PrivateKey::new(&mut rand::thread_rng()); //! let wallet = LocalWallet::new(&mut rand::thread_rng());
//! let address = Address::from(&key);
//! //!
//! // Sign the message //! // Sign the message
//! let signature = key.sign(message); //! let signature = wallet.sign_message(message).await?;
//! //!
//! // Recover the signer from the message //! // Recover the signer from the message
//! let recovered = signature.recover(message).unwrap(); //! let recovered = signature.recover(message)?;
//! //!
//! assert_eq!(recovered, address); //! assert_eq!(recovered, wallet.address());
//! # Ok(())
//! # }
//! ``` //! ```
//! //!
//! ## Utilities //! ## Utilities

View File

@ -1,26 +0,0 @@
pub type Selector = [u8; 4];
// Re-export common ethereum datatypes with more specific names
/// A transaction Hash
pub use ethereum_types::H256 as TxHash;
pub use ethereum_types::{Address, Bloom, H160, H256, U128, U256, U64};
mod transaction;
pub use transaction::{Transaction, TransactionReceipt, TransactionRequest};
mod bytes;
pub use bytes::Bytes;
mod block;
pub use block::{Block, BlockId, BlockNumber};
#[cfg(feature = "celo")]
pub use block::Randomness;
mod log;
pub use log::{Filter, Log, ValueOrArray};
mod ens;
pub use ens::NameOrAddress;

View File

@ -1,348 +0,0 @@
use super::hash::Sha256Proxy;
use crate::{
types::{Address, Signature, TransactionRequest, H256},
utils::{hash_message, keccak256},
};
use rand::{CryptoRng, Rng};
use rustc_hex::FromHex;
use serde::{
de::Error as DeserializeError,
de::{SeqAccess, Visitor},
ser::SerializeTuple,
Deserialize, Deserializer, Serialize, Serializer,
};
use std::{fmt, ops::Deref, str::FromStr};
use k256::{
ecdsa::{
recoverable::{Id as RecoveryId, Signature as RecoverableSignature},
signature::DigestSigner,
SigningKey,
},
elliptic_curve::{error::Error as EllipticCurveError, FieldBytes},
EncodedPoint as K256PublicKey, Secp256k1, SecretKey as K256SecretKey,
};
const SECRET_KEY_SIZE: usize = 32;
const COMPRESSED_PUBLIC_KEY_SIZE: usize = 33;
/// A private key on Secp256k1
#[derive(Clone, Debug)]
pub struct PrivateKey(pub(super) K256SecretKey);
impl PartialEq for PrivateKey {
fn eq(&self, other: &Self) -> bool {
self.0.to_bytes().eq(&other.0.to_bytes())
}
}
impl Serialize for PrivateKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_tuple(SECRET_KEY_SIZE)?;
for e in self.0.to_bytes() {
seq.serialize_element(&e)?;
}
seq.end()
}
}
impl<'de> Deserialize<'de> for PrivateKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let bytes = <[u8; SECRET_KEY_SIZE]>::deserialize(deserializer)?;
Ok(PrivateKey(
K256SecretKey::from_bytes(&bytes).map_err(DeserializeError::custom)?,
))
}
}
impl FromStr for PrivateKey {
type Err = EllipticCurveError;
fn from_str(src: &str) -> Result<PrivateKey, Self::Err> {
let src = src
.from_hex::<Vec<u8>>()
.expect("invalid hex when reading PrivateKey");
let sk = K256SecretKey::from_bytes(&src)?;
Ok(PrivateKey(sk))
}
}
impl PrivateKey {
pub fn new<R: Rng + CryptoRng>(rng: &mut R) -> Self {
PrivateKey(K256SecretKey::random(rng))
}
/// Sign arbitrary string data.
///
/// The data is UTF-8 encoded and enveloped the same way as with
/// `hash_message`. The returned signed data's signature is in 'Electrum'
/// notation, that is the recovery value `v` is either `27` or `28` (as
/// opposed to the standard notation where `v` is either `0` or `1`). This
/// is important to consider when using this signature with other crates.
pub fn sign<S>(&self, message: S) -> Signature
where
S: AsRef<[u8]>,
{
let message = message.as_ref();
let message_hash = hash_message(message);
self.sign_hash_with_eip155(message_hash, None)
}
/// RLP encodes and then signs the stransaction.
///
/// If no chain_id is provided, then EIP-155 is not used.
///
/// This will return an error if called if any of the `nonce`, `gas_price` or `gas`
/// fields are not populated.
///
/// # Panics
///
/// If `tx.to` is an ENS name. The caller MUST take care of name resolution before
/// calling this function.
pub fn sign_transaction(&self, tx: &TransactionRequest, chain_id: Option<u64>) -> Signature {
let sighash = tx.sighash(chain_id);
self.sign_hash_with_eip155(sighash, chain_id)
}
fn sign_hash_with_eip155(&self, hash: H256, chain_id: Option<u64>) -> Signature {
let signing_key = SigningKey::new(&self.0.to_bytes()).expect("invalid secret key");
let recoverable_sig: RecoverableSignature =
signing_key.sign_digest(Sha256Proxy::from(hash));
let v = to_eip155_v(recoverable_sig.recovery_id(), chain_id);
let r_bytes: FieldBytes<Secp256k1> = recoverable_sig.r().into();
let s_bytes: FieldBytes<Secp256k1> = recoverable_sig.s().into();
let r = H256::from_slice(&r_bytes.as_slice());
let s = H256::from_slice(&s_bytes.as_slice());
Signature { r, s, v }
}
}
/// Applies [EIP155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)
fn to_eip155_v(recovery_id: RecoveryId, chain_id: Option<u64>) -> u64 {
let standard_v: u8 = recovery_id.into();
if let Some(chain_id) = chain_id {
// When signing with a chain ID, add chain replay protection.
(standard_v as u64) + 35 + chain_id * 2
} else {
// Otherwise, convert to 'Electrum' notation.
(standard_v as u64) + 27
}
}
impl Deref for PrivateKey {
type Target = K256SecretKey;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// A secp256k1 Public Key
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PublicKey(pub(super) K256PublicKey);
impl From<K256PublicKey> for PublicKey {
/// Gets the public address of a private key.
fn from(src: K256PublicKey) -> PublicKey {
PublicKey(src)
}
}
impl From<&PrivateKey> for PublicKey {
/// Gets the public address of a private key.
fn from(src: &PrivateKey) -> PublicKey {
let public_key = K256PublicKey::from_secret_key(src, false);
PublicKey(public_key)
}
}
/// Gets the address of a public key.
///
/// The public address is defined as the low 20 bytes of the keccak hash of
/// the public key. Note that the public key returned from the `secp256k1`
/// crate is 65 bytes long, that is because it is prefixed by `0x04` to
/// indicate an uncompressed public key; this first byte is ignored when
/// computing the hash.
impl From<&PublicKey> for Address {
fn from(src: &PublicKey) -> Address {
let public_key = src.0.as_bytes();
debug_assert_eq!(public_key[0], 0x04);
let hash = keccak256(&public_key[1..]);
Address::from_slice(&hash[12..])
}
}
impl From<PublicKey> for Address {
fn from(src: PublicKey) -> Address {
Address::from(&src)
}
}
impl From<&PrivateKey> for Address {
fn from(src: &PrivateKey) -> Address {
let public_key = PublicKey::from(src);
Address::from(&public_key)
}
}
impl From<PrivateKey> for Address {
fn from(src: PrivateKey) -> Address {
Address::from(&src)
}
}
impl Serialize for PublicKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_tuple(COMPRESSED_PUBLIC_KEY_SIZE)?;
for e in self.0.compress().as_bytes().iter() {
seq.serialize_element(e)?;
}
seq.end()
}
}
impl<'de> Deserialize<'de> for PublicKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ArrayVisitor;
impl<'de> Visitor<'de> for ArrayVisitor {
type Value = PublicKey;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a valid proof")
}
fn visit_seq<S>(self, mut seq: S) -> Result<PublicKey, S::Error>
where
S: SeqAccess<'de>,
{
let mut bytes = [0u8; COMPRESSED_PUBLIC_KEY_SIZE];
for b in &mut bytes[..] {
*b = seq
.next_element()?
.ok_or_else(|| DeserializeError::custom("could not read bytes"))?;
}
let pub_key = K256PublicKey::from_bytes(&bytes[..])
.map_err(|_| DeserializeError::custom("parse pub key"))?;
let uncompressed_pub_key = pub_key.decompress();
if uncompressed_pub_key.is_some().into() {
Ok(PublicKey(uncompressed_pub_key.unwrap()))
} else {
Err(DeserializeError::custom("parse pub key"))
}
}
}
deserializer.deserialize_tuple(COMPRESSED_PUBLIC_KEY_SIZE, ArrayVisitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde() {
for _ in 0..10 {
let key = PrivateKey::new(&mut rand::thread_rng());
let serialized = bincode::serialize(&key).unwrap();
assert_eq!(serialized.as_slice(), key.0.to_bytes().as_slice());
let de: PrivateKey = bincode::deserialize(&serialized).unwrap();
assert_eq!(key, de);
let public = PublicKey::from(&key);
println!("public = {:?}", public);
let serialized = bincode::serialize(&public).unwrap();
let de: PublicKey = bincode::deserialize(&serialized).unwrap();
assert_eq!(public, de);
}
}
#[test]
#[cfg(not(feature = "celo"))]
fn signs_tx() {
use crate::types::Address;
// retrieved test vector from:
// https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction
let tx = TransactionRequest {
from: None,
to: Some(
"F0109fC8DF283027b6285cc889F5aA624EaC1F55"
.parse::<Address>()
.unwrap()
.into(),
),
value: Some(1_000_000_000.into()),
gas: Some(2_000_000.into()),
nonce: Some(0.into()),
gas_price: Some(21_000_000_000u128.into()),
data: None,
};
let chain_id = 1;
let key: PrivateKey = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
.parse()
.unwrap();
let sig = key.sign_transaction(&tx, Some(chain_id));
let sighash = tx.sighash(Some(chain_id));
assert!(sig.verify(sighash, Address::from(key)).is_ok());
}
#[test]
fn key_to_address() {
let priv_key: PrivateKey =
"0000000000000000000000000000000000000000000000000000000000000001"
.parse()
.unwrap();
let addr: Address = priv_key.into();
assert_eq!(
addr,
Address::from_str("7E5F4552091A69125d5DfCb7b8C2659029395Bdf").expect("Decoding failed")
);
let priv_key: PrivateKey =
"0000000000000000000000000000000000000000000000000000000000000002"
.parse()
.unwrap();
let addr: Address = priv_key.into();
assert_eq!(
addr,
Address::from_str("2B5AD5c4795c026514f8317c7a215E218DcCD6cF").expect("Decoding failed")
);
let priv_key: PrivateKey =
"0000000000000000000000000000000000000000000000000000000000000003"
.parse()
.unwrap();
let addr: Address = priv_key.into();
assert_eq!(
addr,
Address::from_str("6813Eb9362372EEF6200f3b1dbC3f819671cBA69").expect("Decoding failed")
);
}
}

View File

@ -5,3 +5,4 @@ mod signature;
pub use signature::Signature; pub use signature::Signature;
mod hash; mod hash;
pub use hash::Sha256Proxy;

View File

@ -1,6 +1,29 @@
//! Ethereum related datatypes pub type Selector = [u8; 4];
mod crypto;
pub use crypto::*;
mod chainstate; // Re-export common ethereum datatypes with more specific names
pub use chainstate::*;
/// A transaction Hash
pub use ethereum_types::H256 as TxHash;
pub use ethereum_types::{Address, Bloom, H160, H256, U128, U256, U64};
mod transaction;
pub use transaction::{Transaction, TransactionReceipt, TransactionRequest};
mod bytes;
pub use bytes::Bytes;
mod block;
pub use block::{Block, BlockId, BlockNumber};
#[cfg(feature = "celo")]
pub use block::Randomness;
mod log;
pub use log::{Filter, Log, ValueOrArray};
mod ens;
pub use ens::NameOrAddress;
mod signature;
pub use signature::*;

View File

@ -1,6 +1,6 @@
// Code adapted from: https://github.com/tomusdrw/rust-web3/blob/master/src/api/accounts.rs // Code adapted from: https://github.com/tomusdrw/rust-web3/blob/master/src/api/accounts.rs
use crate::{ use crate::{
types::{Address, PublicKey, H256}, types::{Address, H256},
utils::hash_message, utils::hash_message,
}; };
@ -106,7 +106,10 @@ impl Signature {
let uncompressed_pub_key = K256PublicKey::from(&verify_key).decompress(); let uncompressed_pub_key = K256PublicKey::from(&verify_key).decompress();
if uncompressed_pub_key.is_some().into() { if uncompressed_pub_key.is_some().into() {
Ok(PublicKey::from(uncompressed_pub_key.unwrap()).into()) let public_key = uncompressed_pub_key.unwrap().to_bytes();
debug_assert_eq!(public_key[0], 0x04);
let hash = crate::utils::keccak256(&public_key[1..]);
Ok(Address::from_slice(&hash[12..]))
} else { } else {
Err(SignatureError::RecoveryError) Err(SignatureError::RecoveryError)
} }
@ -245,55 +248,17 @@ impl From<H256> for RecoveryMessage {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::types::PrivateKey;
#[test]
fn recover_signature_from_message() {
let message = "Some data";
let hash = hash_message(message);
let key = PrivateKey::new(&mut rand::thread_rng());
let address = Address::from(&key);
// sign a message
let signature = key.sign(message);
// ecrecover via the message will hash internally
let recovered = signature.recover(message).unwrap();
// if provided with a hash, it will skip hashing
let recovered2 = signature.recover(hash).unwrap();
// verifies the signature is produced by `address`
signature.verify(message, address).unwrap();
assert_eq!(recovered, address);
assert_eq!(recovered2, address);
}
#[test] #[test]
fn recover_web3_signature() { fn recover_web3_signature() {
// test vector taken from: // test vector taken from:
// https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#sign // https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#sign
let key: PrivateKey = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
.parse()
.unwrap();
let address = Address::from(&key);
let our_signature = key.sign("Some data");
let signature = Signature::from_str( let signature = Signature::from_str(
"b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c" "b91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c"
).expect("could not parse signature"); ).expect("could not parse signature");
assert_eq!(our_signature.recover("Some data").unwrap(), address,); assert_eq!(
assert_eq!(signature.recover("Some data").unwrap(), address); signature.recover("Some data").unwrap(),
assert_eq!(our_signature, signature); Address::from_str("2c7536E3605D9C16a7a3D7b1898e529396a65c23").unwrap()
} );
#[test]
fn to_vec() {
let message = "Some data";
let key = PrivateKey::new(&mut rand::thread_rng());
let signature = key.sign(message);
let serialized = signature.to_vec();
let de = Signature::try_from(&serialized[..]).unwrap();
assert_eq!(signature, de);
} }
} }

View File

@ -1,4 +1,6 @@
use crate::types::PrivateKey; use crate::{types::Address, utils::secret_key_to_address};
use k256::{ecdsa::SigningKey, SecretKey as K256SecretKey};
use rustc_hex::FromHex;
use std::{ use std::{
io::{BufRead, BufReader}, io::{BufRead, BufReader},
net::TcpListener, net::TcpListener,
@ -14,16 +16,22 @@ const GANACHE_STARTUP_TIMEOUT_MILLIS: u64 = 10_000;
/// Construct this using [`Ganache`](crate::utils::Ganache) /// Construct this using [`Ganache`](crate::utils::Ganache)
pub struct GanacheInstance { pub struct GanacheInstance {
pid: Child, pid: Child,
private_keys: Vec<PrivateKey>, private_keys: Vec<K256SecretKey>,
addresses: Vec<Address>,
port: u16, port: u16,
} }
impl GanacheInstance { impl GanacheInstance {
/// Returns the private keys used to instantiate this instance /// Returns the private keys used to instantiate this instance
pub fn keys(&self) -> &[PrivateKey] { pub fn keys(&self) -> &[K256SecretKey] {
&self.private_keys &self.private_keys
} }
/// Returns the addresses used to instantiate this instance
pub fn addresses(&self) -> &[Address] {
&self.addresses
}
/// Returns the port of this instance /// Returns the port of this instance
pub fn port(&self) -> u16 { pub fn port(&self) -> u16 {
self.port self.port
@ -130,6 +138,7 @@ impl Ganache {
let mut reader = BufReader::new(stdout); let mut reader = BufReader::new(stdout);
let mut private_keys = Vec::new(); let mut private_keys = Vec::new();
let mut addresses = Vec::new();
let mut is_private_key = false; let mut is_private_key = false;
loop { loop {
if start + Duration::from_millis(GANACHE_STARTUP_TIMEOUT_MILLIS) <= Instant::now() { if start + Duration::from_millis(GANACHE_STARTUP_TIMEOUT_MILLIS) <= Instant::now() {
@ -150,7 +159,11 @@ impl Ganache {
if is_private_key && line.starts_with('(') { if is_private_key && line.starts_with('(') {
let key_str = &line[6..line.len() - 1]; let key_str = &line[6..line.len() - 1];
let key: PrivateKey = key_str.parse().expect("did not get private key"); let key_hex = key_str
.from_hex::<Vec<u8>>()
.expect("could not parse as hex");
let key = K256SecretKey::from_bytes(&key_hex).expect("did not get private key");
addresses.push(secret_key_to_address(&SigningKey::from(&key)));
private_keys.push(key); private_keys.push(key);
} }
} }
@ -160,6 +173,7 @@ impl Ganache {
GanacheInstance { GanacheInstance {
pid: child, pid: child,
private_keys, private_keys,
addresses,
port, port,
} }
} }

View File

@ -18,6 +18,7 @@ pub use hash::{hash_message, id, keccak256, serialize};
pub use rlp; pub use rlp;
use crate::types::{Address, Bytes, U256}; use crate::types::{Address, Bytes, U256};
use k256::{ecdsa::SigningKey, EncodedPoint as K256PublicKey};
use std::convert::TryInto; use std::convert::TryInto;
/// 1 Ether = 1e18 Wei == 0x0de0b6b3a7640000 Wei /// 1 Ether = 1e18 Wei == 0x0de0b6b3a7640000 Wei
@ -100,6 +101,16 @@ pub fn get_create2_address(
Address::from(bytes) Address::from(bytes)
} }
/// Converts a K256 SigningKey to an Ethereum Address
pub fn secret_key_to_address(secret_key: &SigningKey) -> Address {
// TODO: Can we do this in a better way?
let uncompressed_pub_key = K256PublicKey::from(&secret_key.verify_key()).decompress();
let public_key = uncompressed_pub_key.unwrap().to_bytes();
debug_assert_eq!(public_key[0], 0x04);
let hash = keccak256(&public_key[1..]);
Address::from_slice(&hash[12..])
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1,5 +1,3 @@
use ethers_signers::Signer;
use ethers_core::{ use ethers_core::{
types::{ types::{
Address, BlockNumber, Bytes, NameOrAddress, Signature, Transaction, TransactionRequest, Address, BlockNumber, Bytes, NameOrAddress, Signature, Transaction, TransactionRequest,
@ -7,7 +5,8 @@ use ethers_core::{
}, },
utils::keccak256, utils::keccak256,
}; };
use ethers_providers::Middleware; use ethers_providers::{FromErr, Middleware};
use ethers_signers::Signer;
use async_trait::async_trait; use async_trait::async_trait;
use futures_util::{future::ok, join}; use futures_util::{future::ok, join};
@ -23,7 +22,7 @@ use thiserror::Error;
/// ```no_run /// ```no_run
/// use ethers::{ /// use ethers::{
/// providers::{Middleware, Provider, Http}, /// providers::{Middleware, Provider, Http},
/// signers::Wallet, /// signers::LocalWallet,
/// middleware::Client, /// middleware::Client,
/// types::{Address, TransactionRequest}, /// types::{Address, TransactionRequest},
/// }; /// };
@ -35,7 +34,7 @@ use thiserror::Error;
/// ///
/// // Transactions will be signed with the private key below and will be broadcast /// // Transactions will be signed with the private key below and will be broadcast
/// // via the eth_sendRawTransaction API) /// // via the eth_sendRawTransaction API)
/// let wallet: Wallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc" /// let wallet: LocalWallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
/// .parse()?; /// .parse()?;
/// ///
/// let mut client = Client::new(provider, wallet); /// let mut client = Client::new(provider, wallet);
@ -55,7 +54,7 @@ use thiserror::Error;
/// let receipt = client.pending_transaction(tx_hash).confirmations(6).await?; /// let receipt = client.pending_transaction(tx_hash).confirmations(6).await?;
/// ///
/// // You can connect with other wallets at runtime via the `with_signer` function /// // You can connect with other wallets at runtime via the `with_signer` function
/// let wallet2: Wallet = "cd8c407233c0560f6de24bb2dc60a8b02335c959a1a17f749ce6c1ccf63d74a7" /// let wallet2: LocalWallet = "cd8c407233c0560f6de24bb2dc60a8b02335c959a1a17f749ce6c1ccf63d74a7"
/// .parse()?; /// .parse()?;
/// ///
/// let signed_msg2 = client.with_signer(wallet2).sign(b"hello".to_vec(), &client.address()).await?; /// let signed_msg2 = client.with_signer(wallet2).sign(b"hello".to_vec(), &client.address()).await?;
@ -78,8 +77,6 @@ pub struct Client<M, S> {
pub(crate) address: Address, pub(crate) address: Address,
} }
use ethers_providers::FromErr;
impl<M: Middleware, S: Signer> FromErr<M::Error> for ClientError<M, S> { impl<M: Middleware, S: Signer> FromErr<M::Error> for ClientError<M, S> {
fn from(src: M::Error) -> ClientError<M, S> { fn from(src: M::Error) -> ClientError<M, S> {
ClientError::MiddlewareError(src) ClientError::MiddlewareError(src)
@ -300,7 +297,7 @@ where
#[cfg(all(test, not(feature = "celo")))] #[cfg(all(test, not(feature = "celo")))]
mod tests { mod tests {
use super::*; use super::*;
use ethers::{providers::Provider, signers::Wallet}; use ethers::{providers::Provider, signers::LocalWallet};
use rustc_hex::FromHex; use rustc_hex::FromHex;
use std::convert::TryFrom; use std::convert::TryFrom;
@ -326,7 +323,7 @@ mod tests {
let provider = Provider::try_from("http://localhost:8545").unwrap(); let provider = Provider::try_from("http://localhost:8545").unwrap();
let key = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318" let key = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
.parse::<Wallet>() .parse::<LocalWallet>()
.unwrap() .unwrap()
.set_chain_id(chain_id); .set_chain_id(chain_id);
let client = Client::new(provider, key); let client = Client::new(provider, key);

View File

@ -9,7 +9,7 @@ use std::convert::TryFrom;
async fn using_gas_oracle() { async fn using_gas_oracle() {
let ganache = Ganache::new().spawn(); let ganache = Ganache::new().spawn();
let from = Address::from(ganache.keys()[0].clone()); let from = ganache.addresses()[0];
// connect to the network // connect to the network
let provider = Provider::<Http>::try_from(ganache.endpoint()).unwrap(); let provider = Provider::<Http>::try_from(ganache.endpoint()).unwrap();

View File

@ -4,7 +4,7 @@ async fn nonce_manager() {
use ethers_core::types::*; use ethers_core::types::*;
use ethers_middleware::{Client, NonceManager}; use ethers_middleware::{Client, NonceManager};
use ethers_providers::{Http, Middleware, Provider}; use ethers_providers::{Http, Middleware, Provider};
use ethers_signers::Wallet; use ethers_signers::LocalWallet;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::time::Duration; use std::time::Duration;
@ -14,7 +14,7 @@ async fn nonce_manager() {
.interval(Duration::from_millis(2000u64)); .interval(Duration::from_millis(2000u64));
let wallet = "59c37cb6b16fa2de30675f034c8008f890f4b2696c729d6267946d29736d73e4" let wallet = "59c37cb6b16fa2de30675f034c8008f890f4b2696c729d6267946d29736d73e4"
.parse::<Wallet>() .parse::<LocalWallet>()
.unwrap(); .unwrap();
let address = wallet.address(); let address = wallet.address();

View File

@ -2,7 +2,7 @@ use ethers_providers::{Http, Middleware, Provider};
use ethers_core::types::TransactionRequest; use ethers_core::types::TransactionRequest;
use ethers_middleware::Client; use ethers_middleware::Client;
use ethers_signers::Wallet; use ethers_signers::LocalWallet;
use std::{convert::TryFrom, time::Duration}; use std::{convert::TryFrom, time::Duration};
#[tokio::test] #[tokio::test]
@ -13,8 +13,8 @@ async fn send_eth() {
let ganache = Ganache::new().spawn(); let ganache = Ganache::new().spawn();
// this private key belongs to the above mnemonic // this private key belongs to the above mnemonic
let wallet: Wallet = ganache.keys()[0].clone().into(); let wallet: LocalWallet = ganache.keys()[0].clone().into();
let wallet2: Wallet = ganache.keys()[1].clone().into(); let wallet2: LocalWallet = ganache.keys()[1].clone().into();
// connect to the network // connect to the network
let provider = Provider::<Http>::try_from(ganache.endpoint()) let provider = Provider::<Http>::try_from(ganache.endpoint())
@ -52,7 +52,7 @@ async fn test_send_transaction() {
// Funded with https://celo.org/developers/faucet // Funded with https://celo.org/developers/faucet
// Please do not drain this account :) // Please do not drain this account :)
let wallet = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1" let wallet = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
.parse::<Wallet>() .parse::<LocalWallet>()
.unwrap(); .unwrap();
let client = Client::new(provider, wallet); let client = Client::new(provider, wallet);

View File

@ -7,12 +7,12 @@ async fn can_stack_middlewares() {
Client, GasOracleMiddleware, NonceManager, Client, GasOracleMiddleware, NonceManager,
}; };
use ethers_providers::{Http, Middleware, Provider}; use ethers_providers::{Http, Middleware, Provider};
use ethers_signers::Wallet; use ethers_signers::LocalWallet;
use std::convert::TryFrom; use std::convert::TryFrom;
let ganache = Ganache::new().block_time(5u64).spawn(); let ganache = Ganache::new().block_time(5u64).spawn();
let gas_oracle = GasNow::new().category(GasCategory::SafeLow); let gas_oracle = GasNow::new().category(GasCategory::SafeLow);
let signer: Wallet = ganache.keys()[0].clone().into(); let signer: LocalWallet = ganache.keys()[0].clone().into();
let address = signer.address(); let address = signer.address();
// the base provider // the base provider

View File

@ -20,15 +20,23 @@ futures-util = { version = "0.3.5", default-features = false }
serde = { version = "1.0.112", default-features = false } serde = { version = "1.0.112", default-features = false }
coins-ledger = { git = "https://github.com/summa-tx/bitcoins-rs", optional = true } coins-ledger = { git = "https://github.com/summa-tx/bitcoins-rs", optional = true }
rustc-hex = { version = "2.1.0", optional = true } rustc-hex = { version = "2.1.0" }
async-trait = "0.1.40" async-trait = "0.1.40"
elliptic-curve = { version = "0.6.1", features = ["arithmetic"] }
sha2 = { version = "0.9.1" }
rand = "0.7.3"
# todo: update to latest once published
yubihsm = { git = "https://github.com/iqlusioninc/yubihsm.rs.git", branch = "develop", features = ["secp256k1", "http", "usb"], optional = true }
[dev-dependencies] [dev-dependencies]
ethers = { version = "0.1.3", path = "../ethers" } ethers = { version = "0.1.3", path = "../ethers" }
yubihsm = { git = "https://github.com/iqlusioninc/yubihsm.rs.git", branch = "develop", features = ["secp256k1", "usb", "mockhsm"] }
tokio = { version = "0.2.21", features = ["macros"] } tokio = { version = "0.2.21", features = ["macros"] }
serde_json = "1.0.55" serde_json = "1.0.55"
[features] [features]
celo = ["ethers-core/celo"] celo = ["ethers-core/celo"]
ledger = ["coins-ledger", "rustc-hex"] ledger = ["coins-ledger"]
yubi = ["yubihsm"]

View File

@ -159,7 +159,7 @@ impl LedgerEthereum {
let mut result = Vec::new(); let mut result = Vec::new();
// Iterate in 255 byte chunks // Iterate in 255 byte chunks
while payload.len() > 0 { while !payload.is_empty() {
let chunk_size = std::cmp::min(payload.len(), 255); let chunk_size = std::cmp::min(payload.len(), 255);
let data = payload.drain(0..chunk_size).collect::<Vec<_>>(); let data = payload.drain(0..chunk_size).collect::<Vec<_>>();
command.data = APDUData::new(&data); command.data = APDUData::new(&data);
@ -188,10 +188,10 @@ impl LedgerEthereum {
let mut bytes = vec![depth as u8]; let mut bytes = vec![depth as u8];
for derivation_index in elements { for derivation_index in elements {
let hardened = derivation_index.contains("'"); let hardened = derivation_index.contains('\'');
let mut index = derivation_index.replace("'", "").parse::<u32>().unwrap(); let mut index = derivation_index.replace("'", "").parse::<u32>().unwrap();
if hardened { if hardened {
index = 0x80000000 | index; index |= 0x80000000;
} }
bytes.extend(&index.to_be_bytes()); bytes.extend(&index.to_be_bytes());

View File

@ -1,5 +1,6 @@
//! Helpers for interacting with the Ethereum Ledger App //! Helpers for interacting with the Ethereum Ledger App
//! [Official Docs](https://github.com/LedgerHQ/app-ethereum/blob/master/doc/ethapp.asc) //! [Official Docs](https://github.com/LedgerHQ/app-ethereum/blob/master/doc/ethapp.asc)
use std::fmt;
use thiserror::Error; use thiserror::Error;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -9,13 +10,17 @@ pub enum DerivationType {
Other(String), Other(String),
} }
impl DerivationType { impl fmt::Display for DerivationType {
pub fn to_string(&self) -> String { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self { write!(
DerivationType::Legacy(index) => format!("m/44'/60'/0'/{}", index), f,
DerivationType::LedgerLive(index) => format!("m/44'/60'/{}'/0/0", index), "{}",
DerivationType::Other(inner) => inner.to_owned(), match self {
} DerivationType::Legacy(index) => format!("m/44'/60'/0'/{}", index),
DerivationType::LedgerLive(index) => format!("m/44'/60'/{}'/0/0", index),
DerivationType::Other(inner) => inner.to_owned(),
}
)
} }
} }

View File

@ -12,13 +12,13 @@
//! //!
//! ```no_run //! ```no_run
//! # use ethers::{ //! # use ethers::{
//! signers::{Wallet, Signer}, //! signers::{LocalWallet, Signer},
//! core::types::TransactionRequest //! core::{k256::ecdsa::SigningKey, types::TransactionRequest},
//! }; //! };
//! # async fn foo() -> Result<(), Box<dyn std::error::Error>> { //! # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
//! // instantiate the wallet //! // instantiate the wallet
//! let wallet = "dcf2cbdd171a21c480aa7f53d77f31bb102282b3ff099c78e3118b37348c72f7" //! let wallet = "dcf2cbdd171a21c480aa7f53d77f31bb102282b3ff099c78e3118b37348c72f7"
//! .parse::<Wallet>()?; //! .parse::<LocalWallet>()?;
//! //!
//! // create a transaction //! // create a transaction
//! let tx = TransactionRequest::new() //! let tx = TransactionRequest::new()
@ -33,9 +33,18 @@
//! signature.verify("hello world", wallet.address()).unwrap(); //! signature.verify("hello world", wallet.address()).unwrap();
//! # Ok(()) //! # Ok(())
//! # } //! # }
// mod wallet;
// pub use wallet::Wallet;
mod wallet; mod wallet;
pub use wallet::Wallet; pub use wallet::Wallet;
/// A wallet instantiated with a locally stored private key
pub type LocalWallet = Wallet<ethers_core::k256::ecdsa::SigningKey>;
#[cfg(feature = "yubi")]
/// A wallet instantiated with a YubiHSM
pub type YubiWallet = Wallet<yubihsm::ecdsa::Signer<ethers_core::k256::Secp256k1>>;
#[cfg(feature = "ledger")] #[cfg(feature = "ledger")]
mod ledger; mod ledger;
#[cfg(feature = "ledger")] #[cfg(feature = "ledger")]
@ -44,6 +53,9 @@ pub use ledger::{
types::{DerivationType as HDPath, LedgerError}, types::{DerivationType as HDPath, LedgerError},
}; };
#[cfg(feature = "yubi")]
pub use yubihsm;
use async_trait::async_trait; use async_trait::async_trait;
use ethers_core::types::{Address, Signature, TransactionRequest}; use ethers_core::types::{Address, Signature, TransactionRequest};
use std::error::Error; use std::error::Error;
@ -52,7 +64,7 @@ use std::error::Error;
/// ///
/// Implement this trait to support different signing modes, e.g. Ledger, hosted etc. /// Implement this trait to support different signing modes, e.g. Ledger, hosted etc.
#[async_trait(?Send)] #[async_trait(?Send)]
pub trait Signer: Send + Sync + std::fmt::Debug { pub trait Signer: std::fmt::Debug + Send + Sync {
type Error: Error + Send + Sync; type Error: Error + Send + Sync;
/// Signs the hash of the provided message after prefixing it /// Signs the hash of the provided message after prefixing it
async fn sign_message<S: Send + Sync + AsRef<[u8]>>( async fn sign_message<S: Send + Sync + AsRef<[u8]>>(

View File

@ -1,141 +0,0 @@
use crate::Signer;
use ethers_core::{
k256::elliptic_curve::error::Error as K256Error,
rand::{CryptoRng, Rng},
types::{Address, PrivateKey, PublicKey, Signature, TransactionRequest},
};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
/// An Ethereum private-public key pair which can be used for signing messages.
///
/// # Examples
///
/// ## Signing and Verifying a message
///
/// The wallet can be used to produce ECDSA [`Signature`] objects, which can be
/// then verified. Note that this uses [`hash_message`] under the hood which will
/// prefix the message being hashed with the `Ethereum Signed Message` domain separator.
///
/// ```
/// use ethers_core::rand::thread_rng;
/// use ethers_signers::{Wallet, Signer};
///
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// let wallet = Wallet::new(&mut thread_rng());
///
/// // Optionally, the wallet's chain id can be set, in order to use EIP-155
/// // replay protection with different chains
/// let wallet = wallet.set_chain_id(1337u64);
///
/// // The wallet can be used to sign messages
/// let message = b"hello";
/// let signature = wallet.sign_message(message).await?;
/// assert_eq!(signature.recover(&message[..]).unwrap(), wallet.address());
/// # Ok(())
/// # }
/// ```
///
/// [`Signature`]: ethers_core::types::Signature
/// [`hash_message`]: fn@ethers_core::utils::hash_message
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Wallet {
/// The Wallet's private Key
private_key: PrivateKey,
/// The Wallet's public Key
public_key: PublicKey,
/// The wallet's address
address: Address,
/// The wallet's chain id (for EIP-155), signs w/o replay protection if left unset
chain_id: Option<u64>,
}
#[async_trait(?Send)]
impl Signer for Wallet {
type Error = std::convert::Infallible;
async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
&self,
message: S,
) -> Result<Signature, Self::Error> {
Ok(self.private_key.sign(message))
}
async fn sign_transaction(&self, tx: &TransactionRequest) -> Result<Signature, Self::Error> {
Ok(self.private_key.sign_transaction(tx, self.chain_id))
}
fn address(&self) -> Address {
self.address
}
}
impl Wallet {
// TODO: Add support for mnemonic and encrypted JSON
/// Creates a new random keypair seeded with the provided RNG
pub fn new<R: Rng + CryptoRng>(rng: &mut R) -> Self {
let private_key = PrivateKey::new(rng);
let public_key = PublicKey::from(&private_key);
let address = Address::from(&private_key);
Self {
private_key,
public_key,
address,
chain_id: None,
}
}
/// Sets the wallet's chain_id, used in conjunction with EIP-155 signing
pub fn set_chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
self.chain_id = Some(chain_id.into());
self
}
/// Gets the wallet's public key
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
/// Gets the wallet's private key
pub fn private_key(&self) -> &PrivateKey {
&self.private_key
}
/// Gets the wallet's chain id
pub fn chain_id(&self) -> Option<u64> {
self.chain_id
}
/// Returns the wallet's address
// (we duplicate this method
pub fn address(&self) -> Address {
self.address
}
}
impl From<PrivateKey> for Wallet {
fn from(private_key: PrivateKey) -> Self {
let public_key = PublicKey::from(&private_key);
let address = Address::from(&private_key);
Self {
private_key,
public_key,
address,
chain_id: None,
}
}
}
impl FromStr for Wallet {
type Err = K256Error;
fn from_str(src: &str) -> Result<Self, Self::Err> {
Ok(PrivateKey::from_str(src)?.into())
}
}

View File

@ -1,9 +1,11 @@
//! This is a helper module used to pass the pre-hashed message for signing to the //! This is a helper module used to pass the pre-hashed message for signing to the
//! `sign_digest` methods of K256. //! `sign_digest` methods of K256.
use crate::types::H256;
use elliptic_curve::consts::U64; use elliptic_curve::consts::U64;
use k256::ecdsa::signature::digest::{ use ethers_core::{
generic_array::GenericArray, BlockInput, Digest, FixedOutput, Output, Reset, Update, k256::ecdsa::signature::digest::{
generic_array::GenericArray, BlockInput, Digest, FixedOutput, Output, Reset, Update,
},
types::H256,
}; };
pub type Sha256Proxy = ProxyDigest<sha2::Sha256>; pub type Sha256Proxy = ProxyDigest<sha2::Sha256>;

View File

@ -0,0 +1,146 @@
mod hash;
mod private_key;
#[cfg(feature = "yubihsm")]
mod yubi;
use crate::Signer;
use ethers_core::{
k256::{
ecdsa::{
recoverable::{Id as RecoveryId, Signature as RecoverableSignature},
signature::DigestSigner,
},
elliptic_curve::FieldBytes,
Secp256k1,
},
types::{Address, Signature, TransactionRequest, H256},
utils::hash_message,
};
use hash::Sha256Proxy;
use async_trait::async_trait;
use std::fmt;
/// An Ethereum private-public key pair which can be used for signing messages.
///
/// # Examples
///
/// ## Signing and Verifying a message
///
/// The wallet can be used to produce ECDSA [`Signature`] objects, which can be
/// then verified. Note that this uses [`hash_message`] under the hood which will
/// prefix the message being hashed with the `Ethereum Signed Message` domain separator.
///
/// ```
/// use ethers_core::rand::thread_rng;
/// use ethers_signers::{LocalWallet, Signer};
///
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// let wallet = LocalWallet::new(&mut thread_rng());
///
/// // Optionally, the wallet's chain id can be set, in order to use EIP-155
/// // replay protection with different chains
/// let wallet = wallet.set_chain_id(1337u64);
///
/// // The wallet can be used to sign messages
/// let message = b"hello";
/// let signature = wallet.sign_message(message).await?;
/// assert_eq!(signature.recover(&message[..]).unwrap(), wallet.address());
/// # Ok(())
/// # }
/// ```
///
/// [`Signature`]: ethers_core::types::Signature
/// [`hash_message`]: fn@ethers_core::utils::hash_message
pub struct Wallet<D: DigestSigner<Sha256Proxy, RecoverableSignature>> {
/// The Wallet's private Key
pub(crate) signer: D,
/// The wallet's address
pub(crate) address: Address,
/// The wallet's chain id (for EIP-155), signs w/o replay protection if left unset
pub(crate) chain_id: Option<u64>,
}
#[async_trait(?Send)]
impl<D: Sync + Send + DigestSigner<Sha256Proxy, RecoverableSignature>> Signer for Wallet<D> {
type Error = std::convert::Infallible;
async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
&self,
message: S,
) -> Result<Signature, Self::Error> {
let message = message.as_ref();
let message_hash = hash_message(message);
Ok(self.sign_hash_with_eip155(message_hash, None))
}
async fn sign_transaction(&self, tx: &TransactionRequest) -> Result<Signature, Self::Error> {
let sighash = tx.sighash(self.chain_id);
Ok(self.sign_hash_with_eip155(sighash, self.chain_id))
}
fn address(&self) -> Address {
self.address
}
}
impl<D: DigestSigner<Sha256Proxy, RecoverableSignature>> Wallet<D> {
fn sign_hash_with_eip155(&self, hash: H256, chain_id: Option<u64>) -> Signature {
let recoverable_sig: RecoverableSignature =
self.signer.sign_digest(Sha256Proxy::from(hash));
let v = to_eip155_v(recoverable_sig.recovery_id(), chain_id);
let r_bytes: FieldBytes<Secp256k1> = recoverable_sig.r().into();
let s_bytes: FieldBytes<Secp256k1> = recoverable_sig.s().into();
let r = H256::from_slice(&r_bytes.as_slice());
let s = H256::from_slice(&s_bytes.as_slice());
Signature { r, s, v }
}
/// Sets the wallet's chain_id, used in conjunction with EIP-155 signing
pub fn set_chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
self.chain_id = Some(chain_id.into());
self
}
/// Gets the wallet's signer
pub fn signer(&self) -> &D {
&self.signer
}
/// Gets the wallet's chain id
pub fn chain_id(&self) -> Option<u64> {
self.chain_id
}
/// Returns the wallet's address
pub fn address(&self) -> Address {
self.address
}
}
/// Applies [EIP155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)
fn to_eip155_v(recovery_id: RecoveryId, chain_id: Option<u64>) -> u64 {
let standard_v: u8 = recovery_id.into();
if let Some(chain_id) = chain_id {
// When signing with a chain ID, add chain replay protection.
(standard_v as u64) + 35 + chain_id * 2
} else {
// Otherwise, convert to 'Electrum' notation.
(standard_v as u64) + 27
}
}
// do not log the signer
impl<D: DigestSigner<Sha256Proxy, RecoverableSignature>> fmt::Debug for Wallet<D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Wallet")
.field("address", &self.address)
.field("chain_Id", &self.chain_id)
.finish()
}
}

View File

@ -0,0 +1,188 @@
//! Specific helper functions for loading an offline K256 Private Key stored on disk
use super::Wallet;
use ethers_core::{
k256::{
ecdsa::SigningKey, elliptic_curve::error::Error as K256Error, EncodedPoint as K256PublicKey,
},
rand::{CryptoRng, Rng},
types::Address,
utils::keccak256,
};
use rustc_hex::FromHex;
use std::str::FromStr;
impl Clone for Wallet<SigningKey> {
fn clone(&self) -> Self {
Self {
// TODO: Can we have a better way to clone here?
signer: SigningKey::new(&*self.signer.to_bytes()).unwrap(),
address: self.address,
chain_id: self.chain_id,
}
}
}
impl Wallet<SigningKey> {
// TODO: Add support for mnemonic and encrypted JSON
/// Creates a new random keypair seeded with the provided RNG
pub fn new<R: Rng + CryptoRng>(rng: &mut R) -> Self {
let signer = SigningKey::random(rng);
let address = key_to_address(&signer);
Self {
signer,
address,
chain_id: None,
}
}
}
fn key_to_address(secret_key: &SigningKey) -> Address {
// TODO: Can we do this in a better way?
let uncompressed_pub_key = K256PublicKey::from(&secret_key.verify_key()).decompress();
let public_key = uncompressed_pub_key.unwrap().to_bytes();
debug_assert_eq!(public_key[0], 0x04);
let hash = keccak256(&public_key[1..]);
Address::from_slice(&hash[12..])
}
impl PartialEq for Wallet<SigningKey> {
fn eq(&self, other: &Self) -> bool {
self.signer.to_bytes().eq(&other.signer.to_bytes())
&& self.address == other.address
&& self.chain_id == other.chain_id
}
}
impl From<SigningKey> for Wallet<SigningKey> {
fn from(signer: SigningKey) -> Self {
let address = key_to_address(&signer);
Self {
signer,
address,
chain_id: None,
}
}
}
use ethers_core::k256::SecretKey as K256SecretKey;
impl From<K256SecretKey> for Wallet<SigningKey> {
fn from(key: K256SecretKey) -> Self {
let signer = SigningKey::new(&*key.to_bytes())
.expect("private key should always be convertible to signing key");
let address = key_to_address(&signer);
Self {
signer,
address,
chain_id: None,
}
}
}
impl FromStr for Wallet<SigningKey> {
type Err = K256Error;
fn from_str(src: &str) -> Result<Self, Self::Err> {
let src = src
.from_hex::<Vec<u8>>()
.expect("invalid hex when reading PrivateKey");
let sk = SigningKey::new(&src).unwrap(); // TODO
Ok(sk.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Signer;
#[tokio::test]
async fn signs_msg() {
let message = "Some data";
let hash = ethers_core::utils::hash_message(message);
let key = Wallet::<SigningKey>::new(&mut rand::thread_rng());
let address = key.address;
// sign a message
let signature = key.sign_message(message).await.unwrap();
// ecrecover via the message will hash internally
let recovered = signature.recover(message).unwrap();
// if provided with a hash, it will skip hashing
let recovered2 = signature.recover(hash).unwrap();
// verifies the signature is produced by `address`
signature.verify(message, address).unwrap();
assert_eq!(recovered, address);
assert_eq!(recovered2, address);
}
#[tokio::test]
#[cfg(not(feature = "celo"))]
async fn signs_tx() {
use ethers_core::types::TransactionRequest;
// retrieved test vector from:
// https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction
let tx = TransactionRequest {
from: None,
to: Some(
"F0109fC8DF283027b6285cc889F5aA624EaC1F55"
.parse::<Address>()
.unwrap()
.into(),
),
value: Some(1_000_000_000.into()),
gas: Some(2_000_000.into()),
nonce: Some(0.into()),
gas_price: Some(21_000_000_000u128.into()),
data: None,
};
let chain_id = 1u64;
let wallet: Wallet<SigningKey> =
"4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
.parse()
.unwrap();
let wallet = wallet.set_chain_id(chain_id);
let sig = wallet.sign_transaction(&tx).await.unwrap();
let sighash = tx.sighash(Some(chain_id));
assert!(sig.verify(sighash, wallet.address).is_ok());
}
#[test]
fn key_to_address() {
let wallet: Wallet<SigningKey> =
"0000000000000000000000000000000000000000000000000000000000000001"
.parse()
.unwrap();
assert_eq!(
wallet.address,
Address::from_str("7E5F4552091A69125d5DfCb7b8C2659029395Bdf").expect("Decoding failed")
);
let wallet: Wallet<SigningKey> =
"0000000000000000000000000000000000000000000000000000000000000002"
.parse()
.unwrap();
assert_eq!(
wallet.address,
Address::from_str("2B5AD5c4795c026514f8317c7a215E218DcCD6cF").expect("Decoding failed")
);
let wallet: Wallet<SigningKey> =
"0000000000000000000000000000000000000000000000000000000000000003"
.parse()
.unwrap();
assert_eq!(
wallet.address,
Address::from_str("6813Eb9362372EEF6200f3b1dbC3f819671cBA69").expect("Decoding failed")
);
}
}

View File

@ -0,0 +1,113 @@
//! Helpers for creating wallets for YubiHSM2
use super::Wallet;
use ethers_core::{k256::Secp256k1, types::Address, utils::keccak256};
use yubihsm::{
asymmetric::Algorithm::EcK256, ecdsa::Signer as YubiSigner, object, object::Label, Capability,
Client, Connector, Credentials, Domain,
};
impl Wallet<YubiSigner<Secp256k1>> {
/// Connects to a yubi key's ECDSA account at the provided id
pub fn connect(connector: Connector, credentials: Credentials, id: object::Id) -> Self {
let client = Client::open(connector, credentials, true).unwrap();
let signer = YubiSigner::create(client, id).unwrap();
signer.into()
}
/// Creates a new random ECDSA keypair on the yubi at the provided id
pub fn new(
connector: Connector,
credentials: Credentials,
id: object::Id,
label: Label,
domain: Domain,
) -> Self {
let client = Client::open(connector, credentials, true).unwrap();
let id = client
.generate_asymmetric_key(id, label, domain, Capability::SIGN_ECDSA, EcK256)
.unwrap();
let signer = YubiSigner::create(client, id).unwrap();
signer.into()
}
/// Uploads the provided keypair on the yubi at the provided id
pub fn from_key(
connector: Connector,
credentials: Credentials,
id: object::Id,
label: Label,
domain: Domain,
key: impl Into<Vec<u8>>,
) -> Self {
let client = Client::open(connector, credentials, true).unwrap();
let id = client
.put_asymmetric_key(id, label, domain, Capability::SIGN_ECDSA, EcK256, key)
.unwrap();
let signer = YubiSigner::create(client, id).unwrap();
signer.into()
}
}
impl From<YubiSigner<Secp256k1>> for Wallet<YubiSigner<Secp256k1>> {
fn from(signer: YubiSigner<Secp256k1>) -> Self {
let public_key = signer.public_key().decompress().unwrap().to_bytes();
debug_assert_eq!(public_key[0], 0x04);
let hash = keccak256(&public_key[1..]);
let address = Address::from_slice(&hash[12..]);
Self {
signer,
address,
chain_id: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Signer;
use rustc_hex::FromHex;
use std::str::FromStr;
#[tokio::test]
async fn from_key() {
let key = "2d8c44dc2dd2f0bea410e342885379192381e82d855b1b112f9b55544f1e0900"
.from_hex::<Vec<u8>>()
.unwrap();
let connector = yubihsm::Connector::mockhsm();
let wallet = Wallet::from_key(
connector,
Credentials::default(),
0,
Label::from_bytes(&[]).unwrap(),
Domain::at(1).unwrap(),
key,
);
let msg = "Some data";
let sig = wallet.sign_message(msg).await.unwrap();
assert_eq!(sig.recover(msg).unwrap(), wallet.address());
assert_eq!(
wallet.address(),
Address::from_str("2DE2C386082Cff9b28D62E60983856CE1139eC49").unwrap()
);
}
#[tokio::test]
async fn new_key() {
let connector = yubihsm::Connector::mockhsm();
let wallet = Wallet::<YubiSigner<Secp256k1>>::new(
connector,
Credentials::default(),
0,
Label::from_bytes(&[]).unwrap(),
Domain::at(1).unwrap(),
);
let msg = "Some data";
let sig = wallet.sign_message(msg).await.unwrap();
assert_eq!(sig.recover(msg).unwrap(), wallet.address());
}
}

View File

@ -44,6 +44,7 @@ providers = ["ethers-providers"]
middleware = ["ethers-middleware"] middleware = ["ethers-middleware"]
signers = ["ethers-signers"] signers = ["ethers-signers"]
ledger = ["ethers-signers/ledger"] ledger = ["ethers-signers/ledger"]
yubi = ["ethers-signers/yubi"]
[dependencies] [dependencies]
ethers-contract = { version = "0.1.3", path = "../ethers-contract", optional = true } ethers-contract = { version = "0.1.3", path = "../ethers-contract", optional = true }

View File

@ -24,7 +24,7 @@ async fn main() -> Result<()> {
let ganache = Ganache::new().spawn(); let ganache = Ganache::new().spawn();
// 3. instantiate our wallet // 3. instantiate our wallet
let wallet: Wallet = ganache.keys()[0].clone().into(); let wallet: LocalWallet = ganache.keys()[0].clone().into();
// 4. connect to the network // 4. connect to the network
let provider = let provider =

View File

@ -10,8 +10,8 @@ async fn main() -> Result<()> {
)?; )?;
// create a wallet and connect it to the provider // create a wallet and connect it to the provider
let wallet = let wallet = "dcf2cbdd171a21c480aa7f53d77f31bb102282b3ff099c78e3118b37348c72f7"
"dcf2cbdd171a21c480aa7f53d77f31bb102282b3ff099c78e3118b37348c72f7".parse::<Wallet>()?; .parse::<LocalWallet>()?;
let client = Client::new(provider, wallet); let client = Client::new(provider, wallet);
// craft the transaction // craft the transaction

View File

@ -6,8 +6,8 @@ use std::convert::TryFrom;
async fn main() -> Result<()> { async fn main() -> Result<()> {
let ganache = Ganache::new().spawn(); let ganache = Ganache::new().spawn();
let wallet: Wallet = ganache.keys()[0].clone().into(); let wallet: LocalWallet = ganache.keys()[0].clone().into();
let wallet2: Wallet = ganache.keys()[1].clone().into(); let wallet2: LocalWallet = ganache.keys()[1].clone().into();
// connect to the network // connect to the network
let provider = Provider::<Http>::try_from(ganache.endpoint())?; let provider = Provider::<Http>::try_from(ganache.endpoint())?;

View File

@ -4,7 +4,7 @@ use ethers::prelude::*;
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
let message = "Some data"; let message = "Some data";
let wallet = Wallet::new(&mut rand::thread_rng()); let wallet = LocalWallet::new(&mut rand::thread_rng());
// sign a message // sign a message
let signature = wallet.sign_message(message).await?; let signature = wallet.sign_message(message).await?;

31
ethers/examples/yubi.rs Normal file
View File

@ -0,0 +1,31 @@
#[tokio::main]
#[cfg(feature = "yubi")]
async fn main() -> anyhow::Result<()> {
use ethers::{prelude::*, utils::parse_ether};
use yubihsm::{Connector, Credentials, UsbConfig};
// Connect over websockets
let provider = Provider::new(Ws::connect("ws://localhost:8545").await?);
// We use USB for the example, but you can connect over HTTP as well. Refer
// to the [YubiHSM](https://docs.rs/yubihsm/0.34.0/yubihsm/) docs for more info
let connector = Connector::usb(&UsbConfig::default());
// Instantiate the connection to the YubiKey. Alternatively, use the
// `from_key` method to upload a key you already have, or the `new` method
// to generate a new keypair.
let wallet = YubiWallet::connect(connector, Credentials::default(), 0);
let client = Client::new(provider, wallet);
// Create and broadcast a transaction (ENS enabled!)
let tx = TransactionRequest::new()
.to("vitalik.eth")
.value(parse_ether(10)?);
let tx_hash = client.send_transaction(tx, None).await?;
// Get the receipt
let _receipt = client.pending_transaction(tx_hash).confirmations(3).await?;
Ok(())
}
#[cfg(not(feature = "yubi"))]
fn main() {}