Archive remote_signer
code (#2559)
## Proposed Changes
This PR deletes all `remote_signer` code from Lighthouse, for the following reasons:
* The `remote_signer` code is unused, and we have no plans to use it now that we're moving to supporting the Web3Signer APIs: #2522
* It represents a significant maintenance burden. The HTTP API tests have been prone to platform-specific failures, and breakages due to dependency upgrades, e.g. #2400.
Although the code is deleted it remains in the Git history should we ever want to recover it. For ease of reference:
- The last commit containing remote signer code: 5a3bcd2904
- The last Lighthouse version: v1.5.1
This commit is contained in:
parent
d9910f96c5
commit
f4aa1d8aea
396
Cargo.lock
generated
396
Cargo.lock
generated
@ -205,31 +205,12 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd"
|
||||
|
||||
[[package]]
|
||||
name = "ascii-canvas"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6"
|
||||
dependencies = [
|
||||
"term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1_der"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6e24d2cce90c53b948c46271bfb053e4bdc2db9b5d3f65e20f8cf28a1b7fc3"
|
||||
|
||||
[[package]]
|
||||
name = "assert-json-diff"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f1c3703dd33532d7f0ca049168930e9099ecac238e23cf932f3a69c42f06da"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "1.6.1"
|
||||
@ -308,32 +289,6 @@ dependencies = [
|
||||
"event-listener",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-object-pool"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aeb901c30ebc2fc4ab46395bbfbdba9542c16559d853645d75190c3056caf3bc"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b21b63ab5a0db0369deb913540af2892750e42d949faacc7a61495ac418a1692"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"blocking",
|
||||
"cfg-if 1.0.0",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"signal-hook",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-std"
|
||||
version = "1.9.0"
|
||||
@ -344,7 +299,6 @@ dependencies = [
|
||||
"async-global-executor",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-process",
|
||||
"crossbeam-utils",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@ -488,17 +442,6 @@ version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "basic-cookies"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb53b6b315f924c7f113b162e53b3901c05fc9966baf84d201dfcc7432a4bb38"
|
||||
dependencies = [
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "beacon_chain"
|
||||
version = "0.2.0"
|
||||
@ -600,21 +543,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@ -1284,37 +1212,6 @@ dependencies = [
|
||||
"rand 0.7.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curl"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "003cb79c1c6d1c93344c7e1201bb51c2148f24ec2bd9c253709d6b2efb796515"
|
||||
dependencies = [
|
||||
"curl-sys",
|
||||
"libc",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"socket2 0.4.1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curl-sys"
|
||||
version = "0.4.45+curl-7.78.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de9e5a72b1c744eb5dd20b2be4d7eb84625070bb5c4ab9b347b70464ab1e62eb"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libnghttp2-sys",
|
||||
"libz-sys",
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "3.2.0"
|
||||
@ -1454,18 +1351,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
|
||||
|
||||
[[package]]
|
||||
name = "difference"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
@ -1666,15 +1551,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ena"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.28"
|
||||
@ -2874,33 +2750,6 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
|
||||
|
||||
[[package]]
|
||||
name = "httpmock"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b217899bcbe8ad3bdee7a46727bd3754b908831462755567852fb20eac585d46"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"async-object-pool",
|
||||
"async-trait",
|
||||
"base64 0.13.0",
|
||||
"basic-cookies",
|
||||
"crossbeam-utils",
|
||||
"difference",
|
||||
"futures-util",
|
||||
"hyper",
|
||||
"isahc",
|
||||
"lazy_static",
|
||||
"levenshtein",
|
||||
"log",
|
||||
"qstring",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_regex",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
@ -3131,32 +2980,6 @@ version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
|
||||
|
||||
[[package]]
|
||||
name = "isahc"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "431445cb4ba85a80cb1438a9ae8042dadb78ae4046ecee89ad027b614aa0ddb7"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"crossbeam-utils",
|
||||
"curl",
|
||||
"curl-sys",
|
||||
"encoding_rs",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"http",
|
||||
"log",
|
||||
"mime",
|
||||
"once_cell",
|
||||
"polling",
|
||||
"slab",
|
||||
"sluice",
|
||||
"tracing",
|
||||
"tracing-futures",
|
||||
"url",
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.1"
|
||||
@ -3223,38 +3046,6 @@ dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lalrpop"
|
||||
version = "0.19.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15174f1c529af5bf1283c3bc0058266b483a67156f79589fab2a25e23cf8988"
|
||||
dependencies = [
|
||||
"ascii-canvas",
|
||||
"atty",
|
||||
"bit-set",
|
||||
"diff",
|
||||
"ena",
|
||||
"itertools",
|
||||
"lalrpop-util",
|
||||
"petgraph",
|
||||
"pico-args",
|
||||
"regex",
|
||||
"regex-syntax",
|
||||
"string_cache",
|
||||
"term",
|
||||
"tiny-keccak 2.0.2",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lalrpop-util"
|
||||
version = "0.19.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3e58cce361efcc90ba8a0a5f982c741ff86b603495bb15a998412e957dcd278"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@ -3324,12 +3115,6 @@ dependencies = [
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "levenshtein"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.100"
|
||||
@ -3362,16 +3147,6 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
|
||||
|
||||
[[package]]
|
||||
name = "libnghttp2-sys"
|
||||
version = "0.1.6+1.43.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0af55541a8827e138d59ec9e5877fb6095ece63fb6f4da45e7491b4fbd262855"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libp2p"
|
||||
version = "0.40.0"
|
||||
@ -3904,7 +3679,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
@ -3932,7 +3706,6 @@ dependencies = [
|
||||
"lighthouse_version",
|
||||
"logging",
|
||||
"malloc_utils",
|
||||
"remote_signer",
|
||||
"serde_json",
|
||||
"slashing_protection",
|
||||
"slog",
|
||||
@ -4394,12 +4167,6 @@ dependencies = [
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.17.0"
|
||||
@ -4757,21 +4524,6 @@ dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "0.4.28"
|
||||
@ -4922,12 +4674,6 @@ version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "primitive-types"
|
||||
version = "0.7.3"
|
||||
@ -5124,15 +4870,6 @@ dependencies = [
|
||||
"unescape",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qstring"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
@ -5381,90 +5118,6 @@ version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "remote_signer"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"bls",
|
||||
"clap",
|
||||
"environment",
|
||||
"remote_signer_backend",
|
||||
"remote_signer_client",
|
||||
"remote_signer_test",
|
||||
"serde_json",
|
||||
"slog",
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remote_signer_backend"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"bls",
|
||||
"clap",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"remote_signer_test",
|
||||
"slog",
|
||||
"sloggers 1.0.1",
|
||||
"tempfile",
|
||||
"types",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remote_signer_client"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"environment",
|
||||
"futures",
|
||||
"hyper",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"remote_signer_backend",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slog",
|
||||
"task_executor",
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remote_signer_consumer"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"rand 0.7.3",
|
||||
"remote_signer_test",
|
||||
"reqwest",
|
||||
"sensitive_url",
|
||||
"serde",
|
||||
"tokio",
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remote_signer_test"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"environment",
|
||||
"hex",
|
||||
"httpmock",
|
||||
"remote_signer_client",
|
||||
"remote_signer_consumer",
|
||||
"reqwest",
|
||||
"sensitive_url",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"types",
|
||||
"winapi",
|
||||
"windows-acl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
@ -5875,16 +5528,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_regex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.7"
|
||||
@ -5973,16 +5616,6 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
@ -6033,12 +5666,6 @@ dependencies = [
|
||||
"validator_client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "729a25c17d72b06c68cb47955d44fda88ad2d3e7d77e025663fdd69b93dd71a1"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.4"
|
||||
@ -6237,17 +5864,6 @@ dependencies = [
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sluice"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.6.1"
|
||||
@ -6474,18 +6090,6 @@ dependencies = [
|
||||
"types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"new_debug_unreachable",
|
||||
"phf_shared",
|
||||
"precomputed-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
|
@ -33,7 +33,6 @@ members = [
|
||||
"common/logging",
|
||||
"common/lru_cache",
|
||||
"common/malloc_utils",
|
||||
"common/remote_signer_consumer",
|
||||
"common/sensitive_url",
|
||||
"common/slot_clock",
|
||||
"common/task_executor",
|
||||
@ -69,17 +68,12 @@ members = [
|
||||
"lighthouse",
|
||||
"lighthouse/environment",
|
||||
|
||||
"remote_signer",
|
||||
"remote_signer/backend",
|
||||
"remote_signer/client",
|
||||
|
||||
"slasher",
|
||||
"slasher/service",
|
||||
|
||||
"testing/ef_tests",
|
||||
"testing/eth1_test_rig",
|
||||
"testing/node_test_rig",
|
||||
"testing/remote_signer_test",
|
||||
"testing/simulator",
|
||||
"testing/state_transition_vectors",
|
||||
|
||||
|
@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "remote_signer_consumer"
|
||||
version = "0.2.0"
|
||||
authors = ["Herman Junge <herman@sigmaprime.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.7.3"
|
||||
remote_signer_test = { path = "../../testing/remote_signer_test" }
|
||||
|
||||
[dependencies]
|
||||
reqwest = { version = "0.11.0", features = ["json"] }
|
||||
serde = { version = "1.0.116", features = ["derive"] }
|
||||
tokio = { version = "1.10.0", features = ["time"] }
|
||||
types = { path = "../../consensus/types" }
|
||||
sensitive_url = { path = "../sensitive_url" }
|
@ -1,89 +0,0 @@
|
||||
use crate::{
|
||||
Error, RemoteSignerObject, RemoteSignerRequestBody, RemoteSignerResponseBodyError,
|
||||
RemoteSignerResponseBodyOK,
|
||||
};
|
||||
use reqwest::StatusCode;
|
||||
pub use reqwest::Url;
|
||||
use sensitive_url::SensitiveUrl;
|
||||
use types::{Domain, Fork, Hash256};
|
||||
|
||||
/// A wrapper around `reqwest::Client` which provides convenience methods
|
||||
/// to interface with a BLS Remote Signer.
|
||||
pub struct RemoteSignerHttpConsumer {
|
||||
client: reqwest::Client,
|
||||
server: SensitiveUrl,
|
||||
}
|
||||
|
||||
impl RemoteSignerHttpConsumer {
|
||||
pub fn from_components(server: SensitiveUrl, client: reqwest::Client) -> Self {
|
||||
Self { client, server }
|
||||
}
|
||||
|
||||
/// `POST /sign/:public-key`
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `public_key` - Goes within the url to identify the key we want to use as signer.
|
||||
/// * `bls_domain` - BLS Signature domain. Supporting `BeaconProposer`, `BeaconAttester`,`Randao`.
|
||||
/// * `data` - A `BeaconBlock`, `AttestationData`, or `Epoch`.
|
||||
/// * `fork` - A `Fork` object containing previous and current versions.
|
||||
/// * `genesis_validators_root` - A `Hash256` for domain separation and chain versioning.
|
||||
///
|
||||
/// It sends through the wire a serialized `RemoteSignerRequestBody`.
|
||||
pub async fn sign<R: RemoteSignerObject>(
|
||||
&self,
|
||||
public_key: &str,
|
||||
bls_domain: Domain,
|
||||
data: R,
|
||||
fork: Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
) -> Result<String, Error> {
|
||||
if public_key.is_empty() {
|
||||
return Err(Error::InvalidParameter(
|
||||
"Empty parameter public_key".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut path = self.server.full.clone();
|
||||
path.path_segments_mut()
|
||||
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||
.push("sign")
|
||||
.push(public_key);
|
||||
|
||||
let bls_domain = match bls_domain {
|
||||
Domain::BeaconProposer => data.validate_object(bls_domain),
|
||||
Domain::BeaconAttester => data.validate_object(bls_domain),
|
||||
Domain::Randao => data.validate_object(bls_domain),
|
||||
_ => Err(Error::InvalidParameter(format!(
|
||||
"Unsupported BLS Domain: {:?}",
|
||||
bls_domain
|
||||
))),
|
||||
}?;
|
||||
|
||||
let body = RemoteSignerRequestBody {
|
||||
bls_domain,
|
||||
data,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
};
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.post(path)
|
||||
.json(&body)
|
||||
.send()
|
||||
.await
|
||||
.map_err(Error::Reqwest)?;
|
||||
|
||||
match response.status() {
|
||||
StatusCode::OK => match response.json::<RemoteSignerResponseBodyOK>().await {
|
||||
Ok(resp_json) => Ok(resp_json.signature),
|
||||
Err(e) => Err(Error::Reqwest(e)),
|
||||
},
|
||||
_ => match response.json::<RemoteSignerResponseBodyError>().await {
|
||||
Ok(resp_json) => Err(Error::ServerMessage(resp_json.error)),
|
||||
Err(e) => Err(Error::Reqwest(e)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -1,213 +0,0 @@
|
||||
//! Enables the [Lighthouse Ethereum 2.0 Client] to consume signatures from the
|
||||
//! [BLS Remote Signer].
|
||||
//!
|
||||
//! ## About
|
||||
//!
|
||||
//! The lighthouse client needs to include this crate, and implement the
|
||||
//! adequate bypasses and CLI flags needed to find the remote signer and perform
|
||||
//! the HTTP requests.
|
||||
//!
|
||||
//! As defined by the [EIP-3030] specification, this crate will take the
|
||||
//! received object data and parameters, and send them to the remote signer
|
||||
//! for the production of a signing root hash and signature (the latter if the
|
||||
//! signer has in storage the key identified at request).
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! ### RemoteSignerHttpConsumer
|
||||
//!
|
||||
//! Just provide an `Url` and a timeout
|
||||
//!
|
||||
//! ```
|
||||
//! use remote_signer_consumer::RemoteSignerHttpConsumer;
|
||||
//! use reqwest::ClientBuilder;
|
||||
//! use sensitive_url::SensitiveUrl;
|
||||
//! use tokio::time::Duration;
|
||||
//!
|
||||
//! let url = SensitiveUrl::parse("http://127.0.0.1:9000").unwrap();
|
||||
//! let reqwest_client = ClientBuilder::new()
|
||||
//! .timeout(Duration::from_secs(2))
|
||||
//! .build()
|
||||
//! .unwrap();
|
||||
//!
|
||||
//! let signer = RemoteSignerHttpConsumer::from_components(url, reqwest_client);
|
||||
//!
|
||||
//! ```
|
||||
//!
|
||||
//! ## sign API
|
||||
//!
|
||||
//! `POST /sign/:identifier`
|
||||
//!
|
||||
//! ### Arguments
|
||||
//!
|
||||
//! #### `public_key`
|
||||
//!
|
||||
//! Goes within the url to identify the key we want to use as signer.
|
||||
//!
|
||||
//! #### `bls_domain`
|
||||
//!
|
||||
//! [BLS Signature domain]. Supporting `BeaconProposer`, `BeaconAttester`,
|
||||
//! `Randao`.
|
||||
//!
|
||||
//! #### `data`
|
||||
//!
|
||||
//! A `BeaconBlock`, `AttestationData`, or `Epoch`.
|
||||
//!
|
||||
//! #### `fork`
|
||||
//!
|
||||
//! A [`Fork`] object, containing previous and current versions.
|
||||
//!
|
||||
//! #### `genesis_validators_root`
|
||||
//!
|
||||
//! A [`Hash256`] for domain separation and chain versioning.
|
||||
//!
|
||||
//! ### Behavior
|
||||
//!
|
||||
//! Upon receiving and validating the parameters, the signer sends through the
|
||||
//! wire a serialized `RemoteSignerRequestBody`. Receiving a `200` message with
|
||||
//! the `signature` field inside a JSON payload, or an error.
|
||||
//!
|
||||
//! ## How it works
|
||||
//!
|
||||
//! The production of a _local_ signature (i.e. inside the Lighthouse client)
|
||||
//! has slight variations among the kind of objects (block, attestation,
|
||||
//! randao).
|
||||
//!
|
||||
//! To sign a message, the following procedures are needed:
|
||||
//!
|
||||
//! * Get the `fork_version` - From the objects `Fork` and `Epoch`.
|
||||
//! * Compute the [`fork_data_root`] - From the `fork_version` and the
|
||||
//! `genesis_validators_root`.
|
||||
//! * Compute the [`domain`] - From the `fork_data_root` and the `bls_domain`.
|
||||
//! * With the `domain`, the object (or `epoch` in the case of [`randao`])
|
||||
//! can be merkelized into its [`signing_root`] to be signed.
|
||||
//!
|
||||
//! In short, to obtain a signature from the remote signer, we need to produce
|
||||
//! (and serialize) the following objects:
|
||||
//!
|
||||
//! * `bls_domain`.
|
||||
//! * `data` of the object, if this is a block proposal, an attestation, or an epoch.
|
||||
//! * `epoch`, obtained from the object.
|
||||
//! * `fork`.
|
||||
//! * `genesis_validators_root`.
|
||||
//!
|
||||
//! And, of course, the identifier of the secret key, the `public_key`.
|
||||
//!
|
||||
//! ## Future Work
|
||||
//!
|
||||
//! ### EIP-3030
|
||||
//!
|
||||
//! Work is being done to [standardize the API of the remote signers].
|
||||
//!
|
||||
//! [`domain`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#compute_domain
|
||||
//! [`Epoch`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#custom-types
|
||||
//! [`fork_data_root`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#compute_fork_data_root
|
||||
//! [`Fork`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#fork
|
||||
//! [`Hash256`]: https://docs.rs/ethereum-types/0.9.2/ethereum_types/struct.H256.html
|
||||
//! [`randao`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#randao
|
||||
//! [`signing_root`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#compute_signing_root
|
||||
//! [BLS Remote Signer]: https://github.com/sigp/rust-bls-remote-signer
|
||||
//! [BLS Signature domain]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#domain-types
|
||||
//! [EIP-3030]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3030.md
|
||||
//! [Lighthouse Ethereum 2.0 Client]: https://github.com/sigp/lighthouse
|
||||
//! [standardize the API of the remote signers]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3030.md
|
||||
|
||||
mod http_client;
|
||||
|
||||
pub use http_client::RemoteSignerHttpConsumer;
|
||||
pub use reqwest::Url;
|
||||
use sensitive_url::SensitiveUrl;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use types::{AttestationData, BeaconBlock, Domain, Epoch, EthSpec, Fork, Hash256, SignedRoot};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// The `reqwest` client raised an error.
|
||||
Reqwest(reqwest::Error),
|
||||
/// The server returned an error message where the body was able to be parsed.
|
||||
ServerMessage(String),
|
||||
/// The supplied URL is badly formatted. It should look something like `http://127.0.0.1:5052`.
|
||||
InvalidUrl(SensitiveUrl),
|
||||
/// The supplied parameter is invalid.
|
||||
InvalidParameter(String),
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RemoteSignerRequestBody<T> {
|
||||
/// BLS Signature domain. Supporting `BeaconProposer`, `BeaconAttester`,`Randao`.
|
||||
bls_domain: String,
|
||||
|
||||
/// A `BeaconBlock`, `AttestationData`, or `Epoch`.
|
||||
data: T,
|
||||
|
||||
/// A `Fork` object containing previous and current versions.
|
||||
fork: Fork,
|
||||
|
||||
/// A `Hash256` for domain separation and chain versioning.
|
||||
genesis_validators_root: Hash256,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RemoteSignerResponseBodyOK {
|
||||
signature: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RemoteSignerResponseBodyError {
|
||||
error: String,
|
||||
}
|
||||
|
||||
/// Allows the verification of the BeaconBlock and AttestationData objects
|
||||
/// to be sent through the wire, against their BLS Domains.
|
||||
pub trait RemoteSignerObject: SignedRoot + Serialize {
|
||||
fn validate_object(&self, domain: Domain) -> Result<String, Error>;
|
||||
fn get_epoch(&self) -> Epoch;
|
||||
}
|
||||
|
||||
impl<E: EthSpec> RemoteSignerObject for BeaconBlock<E> {
|
||||
fn validate_object(&self, domain: Domain) -> Result<String, Error> {
|
||||
match domain {
|
||||
Domain::BeaconProposer => Ok("beacon_proposer".to_string()),
|
||||
_ => Err(Error::InvalidParameter(format!(
|
||||
"Domain mismatch for the BeaconBlock object. Expected BeaconProposer, got {:?}",
|
||||
domain
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_epoch(&self) -> Epoch {
|
||||
self.epoch()
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteSignerObject for AttestationData {
|
||||
fn validate_object(&self, domain: Domain) -> Result<String, Error> {
|
||||
match domain {
|
||||
Domain::BeaconAttester => Ok("beacon_attester".to_string()),
|
||||
_ => Err(Error::InvalidParameter(format!(
|
||||
"Domain mismatch for the AttestationData object. Expected BeaconAttester, got {:?}",
|
||||
domain
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_epoch(&self) -> Epoch {
|
||||
self.target.epoch
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteSignerObject for Epoch {
|
||||
fn validate_object(&self, domain: Domain) -> Result<String, Error> {
|
||||
match domain {
|
||||
Domain::Randao => Ok("randao".to_string()),
|
||||
_ => Err(Error::InvalidParameter(format!(
|
||||
"Domain mismatch for the Epoch object. Expected Randao, got {:?}",
|
||||
domain
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_epoch(&self) -> Epoch {
|
||||
*self
|
||||
}
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
mod message_preparation {
|
||||
use remote_signer_consumer::Error;
|
||||
use remote_signer_test::*;
|
||||
use types::Domain;
|
||||
|
||||
#[test]
|
||||
fn beacon_block_and_bls_domain_mismatch() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
|
||||
macro_rules! test_case {
|
||||
($f: expr, $bls_domain: expr, $msg: expr) => {
|
||||
match do_sign_request(&test_client, get_input_data_and_set_domain($f, $bls_domain))
|
||||
.unwrap_err()
|
||||
{
|
||||
Error::InvalidParameter(message) => assert_eq!(message, $msg),
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_case!(
|
||||
get_input_data_block,
|
||||
Domain::BeaconAttester,
|
||||
"Domain mismatch for the BeaconBlock object. Expected BeaconProposer, got BeaconAttester"
|
||||
);
|
||||
test_case!(
|
||||
get_input_data_block,
|
||||
Domain::Randao,
|
||||
"Domain mismatch for the BeaconBlock object. Expected BeaconProposer, got Randao"
|
||||
);
|
||||
test_case!(
|
||||
get_input_data_attestation,
|
||||
Domain::BeaconProposer,
|
||||
"Domain mismatch for the AttestationData object. Expected BeaconAttester, got BeaconProposer"
|
||||
);
|
||||
test_case!(
|
||||
get_input_data_attestation,
|
||||
Domain::Randao,
|
||||
"Domain mismatch for the AttestationData object. Expected BeaconAttester, got Randao"
|
||||
);
|
||||
test_case!(
|
||||
get_input_data_randao,
|
||||
Domain::BeaconProposer,
|
||||
"Domain mismatch for the Epoch object. Expected Randao, got BeaconProposer"
|
||||
);
|
||||
test_case!(
|
||||
get_input_data_randao,
|
||||
Domain::BeaconAttester,
|
||||
"Domain mismatch for the Epoch object. Expected Randao, got BeaconAttester"
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_public_key_parameter() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
|
||||
macro_rules! test_case {
|
||||
($f: expr, $p: expr, $msg: expr) => {
|
||||
match do_sign_request(&test_client, get_input_data_and_set_public_key($f, $p))
|
||||
.unwrap_err()
|
||||
{
|
||||
Error::InvalidParameter(message) => assert_eq!(message, $msg),
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_case!(get_input_data_block, "", "Empty parameter public_key");
|
||||
test_case!(get_input_data_attestation, "", "Empty parameter public_key");
|
||||
test_case!(get_input_data_randao, "", "Empty parameter public_key");
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_public_key_param() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
|
||||
macro_rules! test_case {
|
||||
($f: expr, $p: expr, $msg: expr) => {
|
||||
match do_sign_request(&test_client, get_input_data_and_set_public_key($f, $p))
|
||||
.unwrap_err()
|
||||
{
|
||||
Error::ServerMessage(message) => assert_eq!(message, $msg),
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_case!(get_input_data_block, "/", "Invalid public key: %2F");
|
||||
test_case!(get_input_data_attestation, "/", "Invalid public key: %2F");
|
||||
test_case!(get_input_data_randao, "/", "Invalid public key: %2F");
|
||||
test_case!(get_input_data_block, "//", "Invalid public key: %2F%2F");
|
||||
test_case!(get_input_data_block, "///", "Invalid public key: %2F%2F%2F");
|
||||
test_case!(
|
||||
get_input_data_block,
|
||||
"/?'or 1 = 1 --",
|
||||
"Invalid public key: %2F%3F\'or%201%20=%201%20--"
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_bls_domain() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
|
||||
let test_case = |bls_domain, msg| {
|
||||
let mut test_input = get_input_data_block(0xc137);
|
||||
test_input.bls_domain = bls_domain;
|
||||
let signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
match signature.unwrap_err() {
|
||||
Error::InvalidParameter(message) => assert_eq!(message, msg),
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
};
|
||||
|
||||
test_case(Domain::Deposit, "Unsupported BLS Domain: Deposit");
|
||||
test_case(
|
||||
Domain::VoluntaryExit,
|
||||
"Unsupported BLS Domain: VoluntaryExit",
|
||||
);
|
||||
test_case(
|
||||
Domain::SelectionProof,
|
||||
"Unsupported BLS Domain: SelectionProof",
|
||||
);
|
||||
test_case(
|
||||
Domain::AggregateAndProof,
|
||||
"Unsupported BLS Domain: AggregateAndProof",
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_public_key_param_additional_path_segments() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
|
||||
macro_rules! test_case {
|
||||
($f: expr, $p: expr, $msg: expr) => {
|
||||
match do_sign_request(&test_client, get_input_data_and_set_public_key($f, $p))
|
||||
.unwrap_err()
|
||||
{
|
||||
Error::ServerMessage(message) => assert_eq!(message, $msg),
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_case!(
|
||||
get_input_data_block,
|
||||
"this/receipt",
|
||||
"Invalid public key: this%2Freceipt"
|
||||
);
|
||||
test_case!(
|
||||
get_input_data_attestation,
|
||||
"/this/receipt/please",
|
||||
"Invalid public key: %2Fthis%2Freceipt%2Fplease"
|
||||
);
|
||||
test_case!(
|
||||
get_input_data_randao,
|
||||
"this/receipt/please?",
|
||||
"Invalid public key: this%2Freceipt%2Fplease%3F"
|
||||
);
|
||||
test_case!(
|
||||
get_input_data_block,
|
||||
&format!("{}/valid/pk", PUBLIC_KEY_1),
|
||||
format!("Invalid public key: {}%2Fvalid%2Fpk", PUBLIC_KEY_1)
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
mod mock {
|
||||
use remote_signer_consumer::Error;
|
||||
use remote_signer_test::*;
|
||||
|
||||
#[test]
|
||||
fn timeout() {
|
||||
let mock_server =
|
||||
set_up_mock_server_with_timeout(200, "{\"signature\":\"irrelevant_value\"}", 2);
|
||||
let test_client = set_up_test_consumer_with_timeout(&mock_server.url(""), 1);
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
|
||||
let r = do_sign_request(&test_client, test_input).unwrap_err();
|
||||
|
||||
match r {
|
||||
Error::Reqwest(e) => {
|
||||
let error_msg = e.to_string();
|
||||
assert!(error_msg.contains("error sending request for url (http://127.0.0.1:"));
|
||||
assert!(error_msg.contains("/sign/"));
|
||||
assert!(error_msg.contains(PUBLIC_KEY_1));
|
||||
assert!(error_msg.contains("): operation timed out"));
|
||||
}
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_json_in_ok_response() {
|
||||
let mock_server = set_up_mock_server(200, "NO JSON");
|
||||
let test_client = set_up_test_consumer(&mock_server.url(""));
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
|
||||
let r = do_sign_request(&test_client, test_input).unwrap_err();
|
||||
|
||||
match r {
|
||||
Error::Reqwest(e) => {
|
||||
let error_msg = e.to_string();
|
||||
assert_eq!(
|
||||
error_msg,
|
||||
"error decoding response body: expected value at line 1 column 1"
|
||||
);
|
||||
}
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_signature_in_ok_json() {
|
||||
let mock_server = set_up_mock_server(200, "{\"foo\":\"bar\"}");
|
||||
let test_client = set_up_test_consumer(&mock_server.url(""));
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
|
||||
let r = do_sign_request(&test_client, test_input).unwrap_err();
|
||||
|
||||
match r {
|
||||
Error::Reqwest(e) => {
|
||||
let error_msg = e.to_string();
|
||||
assert_eq!(
|
||||
error_msg,
|
||||
"error decoding response body: missing field `signature` at line 1 column 13"
|
||||
);
|
||||
}
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_signature_in_ok_json() {
|
||||
let mock_server = set_up_mock_server(200, "{\"signature\":\"\"}");
|
||||
let test_client = set_up_test_consumer(&mock_server.url(""));
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
|
||||
let r = do_sign_request(&test_client, test_input).unwrap();
|
||||
|
||||
assert_eq!(r, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extra_fields_in_ok_json() {
|
||||
let mock_server = set_up_mock_server(
|
||||
200,
|
||||
&format!(
|
||||
"{{\"signature\":\"{}\", \"foo\":\"bar\", \"red\":\"green\"}}",
|
||||
EXPECTED_SIGNATURE_1
|
||||
),
|
||||
);
|
||||
let test_client = set_up_test_consumer(&mock_server.url(""));
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
|
||||
let r = do_sign_request(&test_client, test_input).unwrap();
|
||||
|
||||
assert_eq!(r, EXPECTED_SIGNATURE_1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_json_in_error_response() {
|
||||
let mock_server = set_up_mock_server(500, "NO JSON");
|
||||
let test_client = set_up_test_consumer(&mock_server.url(""));
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
|
||||
let r = do_sign_request(&test_client, test_input).unwrap_err();
|
||||
|
||||
match r {
|
||||
Error::Reqwest(e) => {
|
||||
let error_msg = e.to_string();
|
||||
assert_eq!(
|
||||
error_msg,
|
||||
"error decoding response body: expected value at line 1 column 1"
|
||||
);
|
||||
}
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_error_field_in_error_json() {
|
||||
let mock_server = set_up_mock_server(500, "{\"foo\":\"bar\"}");
|
||||
let test_client = set_up_test_consumer(&mock_server.url(""));
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
|
||||
let r = do_sign_request(&test_client, test_input).unwrap_err();
|
||||
|
||||
match r {
|
||||
Error::Reqwest(e) => {
|
||||
let error_msg = e.to_string();
|
||||
assert_eq!(
|
||||
error_msg,
|
||||
"error decoding response body: missing field `error` at line 1 column 13"
|
||||
);
|
||||
}
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_error_field_in_error_json() {
|
||||
let mock_server = set_up_mock_server(500, "{\"error\":\"\"}");
|
||||
let test_client = set_up_test_consumer(&mock_server.url(""));
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
|
||||
let r = do_sign_request(&test_client, test_input).unwrap_err();
|
||||
|
||||
match r {
|
||||
Error::ServerMessage(msg) => {
|
||||
assert_eq!(msg, "");
|
||||
}
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extra_fields_in_error_json() {
|
||||
let mock_server = set_up_mock_server(
|
||||
500,
|
||||
"{\"error\":\"some_error_msg\", \"foo\":\"bar\", \"red\":\"green\"}",
|
||||
);
|
||||
let test_client = set_up_test_consumer(&mock_server.url(""));
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
|
||||
let r = do_sign_request(&test_client, test_input).unwrap_err();
|
||||
|
||||
match r {
|
||||
Error::ServerMessage(msg) => {
|
||||
assert_eq!(msg, "some_error_msg");
|
||||
}
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,242 +0,0 @@
|
||||
mod post {
|
||||
use remote_signer_consumer::{Error, RemoteSignerHttpConsumer};
|
||||
use remote_signer_test::*;
|
||||
use reqwest::ClientBuilder;
|
||||
use sensitive_url::SensitiveUrl;
|
||||
use tokio::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn server_unavailable() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
|
||||
test_signer.shutdown();
|
||||
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
let signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
match signature.unwrap_err() {
|
||||
Error::Reqwest(e) => {
|
||||
let error_msg = e.to_string();
|
||||
let pubkey_string = PUBLIC_KEY_1.to_string();
|
||||
let msgs = vec![
|
||||
"error sending request for url",
|
||||
&pubkey_string,
|
||||
"error trying to connect",
|
||||
"tcp connect error",
|
||||
match cfg!(windows) {
|
||||
true => "No connection could be made because the target machine actively refused it",
|
||||
false => "Connection refused",
|
||||
}
|
||||
];
|
||||
for msg in msgs.iter() {
|
||||
assert!(
|
||||
error_msg.contains(msg),
|
||||
"{:?} should contain {:?}",
|
||||
error_msg,
|
||||
msg
|
||||
);
|
||||
}
|
||||
}
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_error() {
|
||||
let (test_signer, tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
restrict_permissions(tmp_dir.path());
|
||||
restrict_permissions(&tmp_dir.path().join(PUBLIC_KEY_1));
|
||||
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
let signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
unrestrict_permissions(tmp_dir.path());
|
||||
unrestrict_permissions(&tmp_dir.path().join(PUBLIC_KEY_1));
|
||||
|
||||
match signature.unwrap_err() {
|
||||
Error::ServerMessage(message) => assert_eq!(message, "Storage error: PermissionDenied"),
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_url() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
|
||||
let run_testcase = |u: &str| -> Result<String, String> {
|
||||
let url = SensitiveUrl::parse(u).map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
let reqwest_client = ClientBuilder::new()
|
||||
.timeout(Duration::from_secs(12))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let test_client = RemoteSignerHttpConsumer::from_components(url, reqwest_client);
|
||||
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
let signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
signature.map_err(|e| match e {
|
||||
Error::InvalidUrl(message) => format!("{:?}", message),
|
||||
Error::Reqwest(re) => {
|
||||
if re.is_builder() {
|
||||
format!("[Reqwest - Builder] {:?}", re.url().unwrap())
|
||||
} else if re.is_request() {
|
||||
format!("[Reqwest - Request] {:?}", re.url().unwrap())
|
||||
} else {
|
||||
format!("[Reqwest] {:?}", re)
|
||||
}
|
||||
}
|
||||
_ => format!("{:?}", e),
|
||||
})
|
||||
};
|
||||
|
||||
let testcase = |u: &str, msg: &str| assert_eq!(run_testcase(u).unwrap_err(), msg);
|
||||
|
||||
// url::parser::ParseError.
|
||||
// These cases don't even make it to the step of building a RemoteSignerHttpConsumer.
|
||||
testcase("", "ParseError(RelativeUrlWithoutBase)");
|
||||
testcase("/4/8/15/16/23/42", "ParseError(RelativeUrlWithoutBase)");
|
||||
testcase("localhost", "ParseError(RelativeUrlWithoutBase)");
|
||||
testcase(":", "ParseError(RelativeUrlWithoutBase)");
|
||||
testcase("0.0:0", "ParseError(RelativeUrlWithoutBase)");
|
||||
testcase(":aa", "ParseError(RelativeUrlWithoutBase)");
|
||||
testcase("0:", "ParseError(RelativeUrlWithoutBase)");
|
||||
testcase("ftp://", "ParseError(EmptyHost)");
|
||||
testcase("http://", "ParseError(EmptyHost)");
|
||||
testcase("http://127.0.0.1:abcd", "ParseError(InvalidPort)");
|
||||
testcase("http://280.0.0.1", "ParseError(InvalidIpv4Address)");
|
||||
|
||||
// `Error::InvalidUrl`.
|
||||
// The RemoteSignerHttpConsumer is created, but fails at `path_segments_mut()`.
|
||||
testcase("localhost:abcd", "InvalidUrl(\"URL cannot be a base.\")");
|
||||
testcase("localhost:", "InvalidUrl(\"URL cannot be a base.\")");
|
||||
|
||||
// `Reqwest::Error` of the `Builder` kind.
|
||||
// POST is not made.
|
||||
testcase(
|
||||
"unix:/run/foo.socket",
|
||||
&format!(
|
||||
"[Reqwest - Builder] Url {{ scheme: \"unix\", cannot_be_a_base: false, username: \"\", password: None, host: None, port: None, path: \"/run/foo.socket/sign/{}\", query: None, fragment: None }}",
|
||||
PUBLIC_KEY_1
|
||||
),
|
||||
);
|
||||
// `Reqwest::Error` of the `Request` kind.
|
||||
testcase(
|
||||
"http://127.0.0.1:0",
|
||||
&format!(
|
||||
"[Reqwest - Request] Url {{ scheme: \"http\", cannot_be_a_base: false, username: \"\", password: None, host: Some(Ipv4(127.0.0.1)), port: Some(0), path: \"/sign/{}\", query: None, fragment: None }}",
|
||||
PUBLIC_KEY_1
|
||||
),
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_url() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
|
||||
let run_testcase = |u: &str| -> Result<String, String> {
|
||||
let url = SensitiveUrl::parse(u).unwrap();
|
||||
|
||||
let reqwest_client = ClientBuilder::new()
|
||||
.timeout(Duration::from_secs(12))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let test_client = RemoteSignerHttpConsumer::from_components(url, reqwest_client);
|
||||
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
let signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
signature.map_err(|e| format!("{:?}", e))
|
||||
};
|
||||
|
||||
let testcase = |u: &str, msgs: Vec<&str>| {
|
||||
let r = run_testcase(u).unwrap_err();
|
||||
for msg in msgs.iter() {
|
||||
assert!(r.contains(msg), "{:?} should contain {:?}", r, msg);
|
||||
}
|
||||
};
|
||||
|
||||
testcase(
|
||||
"http://error-dns",
|
||||
vec![
|
||||
"reqwest::Error",
|
||||
"kind: Request",
|
||||
&format!("/sign/{}", PUBLIC_KEY_1),
|
||||
"hyper::Error(Connect, ConnectError",
|
||||
"dns error",
|
||||
match cfg!(windows) {
|
||||
true => "No such host is known.",
|
||||
false => "failed to lookup address information",
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_public_key() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
|
||||
let mut test_input = get_input_data_block(0xc137);
|
||||
test_input.public_key = ABSENT_PUBLIC_KEY.to_string();
|
||||
|
||||
let signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
match signature.unwrap_err() {
|
||||
Error::ServerMessage(msg) => {
|
||||
assert_eq!(msg, format!("Key not found: {}", ABSENT_PUBLIC_KEY))
|
||||
}
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_secret_key() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
|
||||
let mut test_input = get_input_data_block(0xc137);
|
||||
test_input.public_key = PUBLIC_KEY_FOR_INVALID_SECRET_KEY.to_string();
|
||||
|
||||
let signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
match signature.unwrap_err() {
|
||||
Error::ServerMessage(msg) => assert_eq!(
|
||||
msg,
|
||||
format!(
|
||||
"Invalid secret key: public_key: {}; Invalid hex character: W at index 0",
|
||||
PUBLIC_KEY_FOR_INVALID_SECRET_KEY
|
||||
)
|
||||
),
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_mismatch() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
|
||||
let mut test_input = get_input_data_block(0xc137);
|
||||
test_input.public_key = MISMATCHED_PUBLIC_KEY.to_string();
|
||||
|
||||
let signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
match signature.unwrap_err() {
|
||||
Error::ServerMessage(msg) => {
|
||||
assert_eq!(msg, format!("Key mismatch: {}", MISMATCHED_PUBLIC_KEY))
|
||||
}
|
||||
e => panic!("{:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
mod sign_attestation {
|
||||
use rand::Rng;
|
||||
use remote_signer_test::*;
|
||||
|
||||
#[test]
|
||||
fn sanity_check_deterministic() {
|
||||
let test_input_local = get_input_local_signer_attestation(0xc137);
|
||||
let local_signature = test_input_local.sign();
|
||||
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
let test_input = get_input_data_attestation(0xc137);
|
||||
|
||||
let remote_signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
assert_eq!(local_signature, remote_signature.unwrap());
|
||||
assert_eq!(local_signature, HAPPY_PATH_ATT_SIGNATURE_C137);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sanity_check_random() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let seed = rng.gen::<u64>() / 1024;
|
||||
|
||||
let test_input_local = get_input_local_signer_attestation(seed);
|
||||
let local_signature = test_input_local.sign();
|
||||
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
let test_input = get_input_data_attestation(seed);
|
||||
|
||||
let remote_signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
assert_eq!(local_signature, remote_signature.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn happy_path() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
let test_input = get_input_data_attestation(0xc137);
|
||||
|
||||
let signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
assert_eq!(signature.unwrap(), HAPPY_PATH_ATT_SIGNATURE_C137);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
mod sign_block {
|
||||
use rand::Rng;
|
||||
use remote_signer_test::*;
|
||||
|
||||
#[test]
|
||||
fn sanity_check_deterministic() {
|
||||
let test_input_local = get_input_local_signer_block(0xc137);
|
||||
let local_signature = test_input_local.sign();
|
||||
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
|
||||
let remote_signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
assert_eq!(local_signature, remote_signature.unwrap());
|
||||
assert_eq!(local_signature, HAPPY_PATH_BLOCK_SIGNATURE_C137);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sanity_check_random() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let seed = rng.gen::<u64>() / 1024;
|
||||
|
||||
let test_input_local = get_input_local_signer_block(seed);
|
||||
let local_signature = test_input_local.sign();
|
||||
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
let test_input = get_input_data_block(seed);
|
||||
|
||||
let remote_signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
assert_eq!(local_signature, remote_signature.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn happy_path() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
let test_input = get_input_data_block(0xc137);
|
||||
|
||||
let signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
assert_eq!(signature.unwrap(), HAPPY_PATH_BLOCK_SIGNATURE_C137);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
mod sign_randao {
|
||||
use rand::Rng;
|
||||
use remote_signer_test::*;
|
||||
|
||||
#[test]
|
||||
fn sanity_check_deterministic() {
|
||||
let test_input_local = get_input_local_signer_randao(0xc137);
|
||||
let local_signature = test_input_local.sign();
|
||||
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
|
||||
let test_input = get_input_data_randao(0xc137);
|
||||
|
||||
let remote_signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
assert_eq!(local_signature, remote_signature.unwrap());
|
||||
assert_eq!(local_signature, HAPPY_PATH_RANDAO_SIGNATURE_C137);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sanity_check_random() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let seed = rng.gen::<u64>();
|
||||
|
||||
let test_input_local = get_input_local_signer_randao(seed);
|
||||
let local_signature = test_input_local.sign();
|
||||
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
|
||||
let test_input = get_input_data_randao(seed);
|
||||
|
||||
let remote_signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
assert_eq!(local_signature, remote_signature.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn happy_path() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_client = set_up_test_consumer(&test_signer.address);
|
||||
|
||||
let test_input = get_input_data_randao(0xc137);
|
||||
|
||||
let remote_signature = do_sign_request(&test_client, test_input);
|
||||
|
||||
assert_eq!(remote_signature.unwrap(), HAPPY_PATH_RANDAO_SIGNATURE_C137);
|
||||
}
|
||||
}
|
@ -40,7 +40,6 @@ eth2_network_config = { path = "../common/eth2_network_config" }
|
||||
directory = { path = "../common/directory" }
|
||||
lighthouse_version = { path = "../common/lighthouse_version" }
|
||||
account_utils = { path = "../common/account_utils" }
|
||||
remote_signer = { "path" = "../remote_signer" }
|
||||
lighthouse_metrics = { path = "../common/lighthouse_metrics" }
|
||||
lazy_static = "1.4.0"
|
||||
serde_json = "1.0.59"
|
||||
|
@ -164,7 +164,6 @@ fn main() {
|
||||
.subcommand(boot_node::cli_app())
|
||||
.subcommand(validator_client::cli_app())
|
||||
.subcommand(account_manager::cli_app())
|
||||
.subcommand(remote_signer::cli_app())
|
||||
.get_matches();
|
||||
|
||||
// Configure the allocator early in the process, before it has the chance to use the default values for
|
||||
@ -394,16 +393,6 @@ fn run<E: EthSpec>(
|
||||
));
|
||||
}
|
||||
}
|
||||
("remote_signer", Some(matches)) => {
|
||||
if let Err(e) = remote_signer::run(&mut environment, matches) {
|
||||
crit!(log, "Failed to start remote signer"; "reason" => e);
|
||||
let _ = environment
|
||||
.core_context()
|
||||
.executor
|
||||
.shutdown_sender()
|
||||
.try_send(ShutdownReason::Failure("Failed to start remote signer"));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
crit!(log, "No subcommand supplied. See --help .");
|
||||
return Err("No subcommand supplied.".into());
|
||||
|
@ -1,24 +0,0 @@
|
||||
[package]
|
||||
name = "remote_signer"
|
||||
version = "0.2.0"
|
||||
authors = ["Sigma Prime <contact@sigmaprime.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
# Compiles the BLS crypto code so that the binary is portable across machines.
|
||||
portable = ["bls/supranational-portable"]
|
||||
# Uses the slower Milagro BLS library, which is written in native Rust.
|
||||
milagro = ["bls/milagro"]
|
||||
|
||||
[dev-dependencies]
|
||||
client_backend = { path = "./backend", package = "remote_signer_backend" }
|
||||
helpers = { path = "../testing/remote_signer_test", package = "remote_signer_test" }
|
||||
|
||||
[dependencies]
|
||||
bls = { path = "../crypto/bls" }
|
||||
clap = "2.33.3"
|
||||
client = { path = "./client", package = "remote_signer_client" }
|
||||
environment = { path = "../lighthouse/environment" }
|
||||
serde_json = "1.0.58"
|
||||
slog = { version = "2.5.2", features = ["max_level_trace"] }
|
||||
types = { path = "../consensus/types"}
|
@ -1,134 +0,0 @@
|
||||
# Remote BLS Signer
|
||||
|
||||
## Overview
|
||||
|
||||
Simple HTTP BLS signer service.
|
||||
|
||||
This service is designed to be consumed by Ethereum 2.0 clients, looking for a more secure avenue to store their BLS12-381 secret keys, while running their validators in more permisive and/or scalable environments.
|
||||
|
||||
One goal of this package is to be standard compliant. There is a [current draft for an Ethereum Improvement Proposal (EIP)](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3030.md) in progress. Please refer to the [roadmap](#roadmap) for a list of advanced features.
|
||||
|
||||
## API
|
||||
|
||||
### Standard
|
||||
|
||||
### `GET /upcheck`
|
||||
|
||||
_**Responses**_
|
||||
|
||||
Success | <br>
|
||||
--- | ---
|
||||
Code | `200`
|
||||
Content | `{"status": "OK"}`
|
||||
|
||||
---
|
||||
|
||||
### `GET /keys`
|
||||
|
||||
Returns the identifiers of the keys available to the signer.
|
||||
|
||||
_**Responses**_
|
||||
|
||||
Success | <br>
|
||||
--- | ---
|
||||
Code | `200`
|
||||
Content | `{"keys": "[identifier]"}`
|
||||
|
||||
---
|
||||
|
||||
### `POST /sign/:identifier`
|
||||
|
||||
URL Parameter | <br>
|
||||
--- | ---
|
||||
`:identifier` | `public_key_hex_string_without_0x`
|
||||
|
||||
_**Request**_
|
||||
|
||||
JSON Body | <br> | <br>
|
||||
--- | --- | ---
|
||||
`bls_domain` | **Required** | The BLS Signature domain.<br>As defined in the [specification](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#domain-types), in lowercase, omitting the `domain` prefix.<br>Supporting `beacon_proposer`, `beacon_attester`, and `randao`.
|
||||
`data` | **Required** | The data to be signed.<br>As defined in the specifications for [block](https://github.com/ethereum/eth2.0-APIs/blob/master/types/block.yaml), [attestation](https://github.com/ethereum/eth2.0-APIs/blob/master/types/attestation.yaml), and [epoch](https://github.com/ethereum/eth2.0-APIs/blob/master/types/misc.yaml).
|
||||
`fork` | **Required** | A `Fork` object containing previous and current versions.<br>As defined in the [specification](https://github.com/ethereum/eth2.0-APIs/blob/master/types/misc.yaml)
|
||||
`genesis_validators_root` | **Required** | A `Hash256` for domain separation and chain versioning.
|
||||
<br> | Optional | Any other field will be ignored by the signer
|
||||
|
||||
_**Responses**_
|
||||
|
||||
Success | <br>
|
||||
--- | ---
|
||||
Code | `200`
|
||||
Content | `{"signature": "<signature_hex_string>"}`
|
||||
|
||||
_or_
|
||||
|
||||
Error | <br>
|
||||
--- | ---
|
||||
Code | `400`
|
||||
Content | `{"error": "<Bad Request Error Message>"}`
|
||||
|
||||
_or_
|
||||
|
||||
Error | <br>
|
||||
--- | ---
|
||||
Code | `404`
|
||||
Content | `{"error": "Key not found: <identifier>"}`
|
||||
|
||||
## Build instructions
|
||||
|
||||
1. [Get Rust](https://www.rust-lang.org/learn/get-started).
|
||||
2. Go to the root directory of this repository.
|
||||
3. Execute `make`
|
||||
4. The binary `lighthouse` will most likely be found in `./target/release`.
|
||||
5. Run it as `lighthouse remote_signer` or `lighthouse rs`.
|
||||
|
||||
## Running the signer
|
||||
|
||||
### Storing the secret keys as raw files
|
||||
|
||||
* Steps to store a secret key
|
||||
* Choose an empty directory, as the backend will parse every file looking for keys.
|
||||
* Create a file named after the **hex representation of the public key without 0x**.
|
||||
* Write the **hex representation of the secret key without 0x**.
|
||||
* Store the file in your chosen directory.
|
||||
* Use this directory as a command line parameter (`--storage-raw-dir`)
|
||||
|
||||
### Command line flags
|
||||
|
||||
```
|
||||
USAGE:
|
||||
lighthouse remote_signer [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
--debug-level <LEVEL> The verbosity level for emitting logs. [default: info] [possible values:
|
||||
info, debug, trace, warn, error, crit]
|
||||
--listen-address <ADDRESS> The address to listen for TCP connections. [default: 0.0.0.0]
|
||||
--log-format <FORMAT> Specifies the format used for logging. [possible values: JSON]
|
||||
--logfile <FILE> File path where output will be written.
|
||||
--port <PORT> The TCP port to listen on. [default: 9000]
|
||||
--spec <TITLE> Specifies the default eth2 spec type. [default: mainnet] [possible values:
|
||||
mainnet, minimal, interop]
|
||||
--storage-raw-dir <DIR> Data directory for secret keys in raw files.
|
||||
```
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [X] EIP standard compliant
|
||||
- [ ] Metrics
|
||||
- [ ] Benchmarking & Profiling
|
||||
- [ ] Release management
|
||||
- [ ] Architecture builds
|
||||
- [ ] Support EIP-2335, BLS12-381 keystore
|
||||
- [ ] Support storage in AWS Cloud HSM
|
||||
- [ ] Route with the `warp` library
|
||||
- [ ] Slashing protection pipeline
|
||||
- [ ] TLS/SSL support for requests
|
||||
- [ ] Authentication by HTTP Header support
|
||||
- [ ] Confidential computing support (e.g. Intel SGX)
|
||||
|
||||
## LICENSE
|
||||
|
||||
* Apache 2.0.
|
@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "remote_signer_backend"
|
||||
version = "0.2.0"
|
||||
authors = ["Herman Junge <herman@sigmaprime.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dev-dependencies]
|
||||
helpers = { path = "../../testing/remote_signer_test", package = "remote_signer_test" }
|
||||
sloggers = "1.0.1"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[dependencies]
|
||||
bls = { path = "../../crypto/bls" }
|
||||
clap = "2.33.3"
|
||||
hex = "0.4.2"
|
||||
lazy_static = "1.4.0"
|
||||
regex = "1.3.9"
|
||||
slog = "2.5.2"
|
||||
types = { path = "../../consensus/types" }
|
||||
zeroize = { version = "1.1.1", features = ["zeroize_derive"] }
|
@ -1,46 +0,0 @@
|
||||
#[derive(Debug)]
|
||||
pub enum BackendError {
|
||||
/// Parameter is not a hexadecimal representation of a BLS public key.
|
||||
InvalidPublicKey(String),
|
||||
|
||||
/// Retrieved value is not a hexadecimal representation of a BLS secret key.
|
||||
InvalidSecretKey(String),
|
||||
|
||||
/// Public and Secret key won't match.
|
||||
KeyMismatch(String),
|
||||
|
||||
/// Item requested by its public key is not found.
|
||||
KeyNotFound(String),
|
||||
|
||||
/// Errors from the storage medium.
|
||||
///
|
||||
/// When converted from `std::io::Error`, stores `std::io::ErrorKind`
|
||||
/// and `std::io::Error` both formatted to string.
|
||||
StorageError(String, String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BackendError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
BackendError::InvalidPublicKey(e) => write!(f, "Invalid public key: {}", e),
|
||||
|
||||
// Feed it with the public key value used to retrieve it.
|
||||
BackendError::InvalidSecretKey(e) => write!(f, "Invalid secret key: {}", e),
|
||||
|
||||
// Feed it with the public key value used to retrieve it.
|
||||
BackendError::KeyMismatch(e) => write!(f, "Key mismatch: {}", e),
|
||||
|
||||
BackendError::KeyNotFound(e) => write!(f, "Key not found: {}", e),
|
||||
|
||||
// Only outputs to string the first component of the tuple, accounting
|
||||
// for potential differences on error displays between OS distributions.
|
||||
BackendError::StorageError(e, _) => write!(f, "Storage error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for BackendError {
|
||||
fn from(e: std::io::Error) -> BackendError {
|
||||
BackendError::StorageError(format!("{:?}", e.kind()), format!("{}", e))
|
||||
}
|
||||
}
|
@ -1,343 +0,0 @@
|
||||
mod error;
|
||||
mod storage;
|
||||
mod storage_raw_dir;
|
||||
mod utils;
|
||||
mod zeroize_string;
|
||||
|
||||
use crate::zeroize_string::ZeroizeString;
|
||||
use bls::SecretKey;
|
||||
use clap::ArgMatches;
|
||||
pub use error::BackendError;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use slog::{info, Logger};
|
||||
pub use storage::Storage;
|
||||
use storage_raw_dir::StorageRawDir;
|
||||
use types::Hash256;
|
||||
use utils::{bytes96_to_hex_string, validate_bls_pair};
|
||||
|
||||
lazy_static! {
|
||||
static ref PUBLIC_KEY_REGEX: Regex = Regex::new(r"[0-9a-fA-F]{96}").unwrap();
|
||||
}
|
||||
|
||||
/// A backend to be used by the Remote Signer HTTP API.
|
||||
///
|
||||
/// Designed to support several types of storages.
|
||||
#[derive(Clone)]
|
||||
pub struct Backend<T> {
|
||||
storage: T,
|
||||
}
|
||||
|
||||
impl Backend<StorageRawDir> {
|
||||
/// Creates a Backend with the given storage type at the CLI arguments.
|
||||
///
|
||||
/// # Storage types supported
|
||||
///
|
||||
/// * Raw files in directory: `--storage-raw-dir <DIR>`
|
||||
///
|
||||
pub fn new(cli_args: &ArgMatches<'_>, log: &Logger) -> Result<Self, String> {
|
||||
// Storage types are mutually exclusive.
|
||||
if let Some(path) = cli_args.value_of("storage-raw-dir") {
|
||||
info!(
|
||||
log,
|
||||
"Loading Backend";
|
||||
"storage type" => "raw dir",
|
||||
"directory" => path
|
||||
);
|
||||
|
||||
StorageRawDir::new(path)
|
||||
.map(|storage| Self { storage })
|
||||
.map_err(|e| format!("Storage Raw Dir: {}", e))
|
||||
} else {
|
||||
Err("No storage type supplied.".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Storage> Backend<T> {
|
||||
/// Returns the available public keys in storage.
|
||||
pub fn get_keys(&self) -> Result<Vec<String>, BackendError> {
|
||||
self.storage.get_keys()
|
||||
}
|
||||
|
||||
/// Signs the message with the requested key in storage.
|
||||
pub fn sign_message(
|
||||
&self,
|
||||
public_key: &str,
|
||||
signing_root: Hash256,
|
||||
) -> Result<String, BackendError> {
|
||||
if !PUBLIC_KEY_REGEX.is_match(public_key) || public_key.len() != 96 {
|
||||
return Err(BackendError::InvalidPublicKey(public_key.to_string()));
|
||||
}
|
||||
|
||||
let secret_key: ZeroizeString = self.storage.get_secret_key(public_key)?;
|
||||
let secret_key: SecretKey = validate_bls_pair(public_key, secret_key)?;
|
||||
|
||||
let signature = secret_key.sign(signing_root);
|
||||
|
||||
let signature: String = bytes96_to_hex_string(signature.serialize())
|
||||
.expect("Writing to a string should never error.");
|
||||
|
||||
Ok(signature)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests_commons {
|
||||
use super::*;
|
||||
pub use crate::Storage;
|
||||
use helpers::*;
|
||||
use sloggers::{null::NullLoggerBuilder, Build};
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
type T = StorageRawDir;
|
||||
|
||||
pub fn new_storage_with_tmp_dir() -> (T, TempDir) {
|
||||
let tmp_dir = tempdir().unwrap();
|
||||
let storage = StorageRawDir::new(tmp_dir.path().to_str().unwrap()).unwrap();
|
||||
(storage, tmp_dir)
|
||||
}
|
||||
|
||||
pub fn get_null_logger() -> Logger {
|
||||
let log_builder = NullLoggerBuilder;
|
||||
log_builder.build().unwrap()
|
||||
}
|
||||
|
||||
pub fn new_backend_for_get_keys() -> (Backend<T>, TempDir) {
|
||||
let tmp_dir = tempdir().unwrap();
|
||||
|
||||
let matches = set_matches(vec![
|
||||
"this_test",
|
||||
"--storage-raw-dir",
|
||||
tmp_dir.path().to_str().unwrap(),
|
||||
]);
|
||||
|
||||
let backend = match Backend::new(&matches, &get_null_logger()) {
|
||||
Ok(backend) => (backend),
|
||||
Err(e) => panic!("We should not be getting an err here: {}", e),
|
||||
};
|
||||
|
||||
(backend, tmp_dir)
|
||||
}
|
||||
|
||||
pub fn new_backend_for_signing() -> (Backend<T>, TempDir) {
|
||||
let (backend, tmp_dir) = new_backend_for_get_keys();
|
||||
|
||||
// This one has the whole fauna.
|
||||
add_sub_dirs(&tmp_dir);
|
||||
add_key_files(&tmp_dir);
|
||||
add_non_key_files(&tmp_dir);
|
||||
add_mismatched_key_file(&tmp_dir);
|
||||
|
||||
(backend, tmp_dir)
|
||||
}
|
||||
|
||||
pub fn assert_backend_new_error(matches: &ArgMatches, error_msg: &str) {
|
||||
match Backend::new(matches, &get_null_logger()) {
|
||||
Ok(_) => panic!("This invocation to Backend::new() should return error"),
|
||||
Err(e) => assert_eq!(e, error_msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod backend_new {
|
||||
use super::*;
|
||||
use crate::tests_commons::*;
|
||||
use helpers::*;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn no_storage_type_supplied() {
|
||||
let matches = set_matches(vec!["this_test"]);
|
||||
|
||||
assert_backend_new_error(&matches, "No storage type supplied.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn given_path_does_not_exist() {
|
||||
let matches = set_matches(vec!["this_test", "--storage-raw-dir", "/dev/null/foo"]);
|
||||
|
||||
assert_backend_new_error(&matches, "Storage Raw Dir: Path does not exist.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn given_path_is_not_a_dir() {
|
||||
let matches = set_matches(vec![
|
||||
"this_test",
|
||||
"--storage-raw-dir",
|
||||
match cfg!(windows) {
|
||||
true => "C:\\Windows\\system.ini",
|
||||
false => "/dev/null",
|
||||
},
|
||||
]);
|
||||
|
||||
assert_backend_new_error(&matches, "Storage Raw Dir: Path is not a directory.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn given_inaccessible() {
|
||||
let tmp_dir = tempdir().unwrap();
|
||||
restrict_permissions(tmp_dir.path());
|
||||
|
||||
let matches = set_matches(vec![
|
||||
"this_test",
|
||||
"--storage-raw-dir",
|
||||
tmp_dir.path().to_str().unwrap(),
|
||||
]);
|
||||
|
||||
let result = Backend::new(&matches, &get_null_logger());
|
||||
|
||||
// A `d-wx--x--x` directory is innaccesible but not unwrittable.
|
||||
// By switching back to `drwxr-xr-x` we can get rid of the
|
||||
// temporal directory once we leave this scope.
|
||||
unrestrict_permissions(tmp_dir.path());
|
||||
|
||||
match result {
|
||||
Ok(_) => panic!("This invocation to Backend::new() should return error"),
|
||||
Err(e) => assert_eq!(e, "Storage Raw Dir: PermissionDenied",),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn happy_path() {
|
||||
let (_backend, _tmp_dir) = new_backend_for_get_keys();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod backend_raw_dir_get_keys {
|
||||
use crate::tests_commons::*;
|
||||
use helpers::*;
|
||||
|
||||
#[test]
|
||||
fn empty_dir() {
|
||||
let (backend, _tmp_dir) = new_backend_for_get_keys();
|
||||
|
||||
assert_eq!(backend.get_keys().unwrap().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_files_are_not_public_keys() {
|
||||
let (backend, tmp_dir) = new_backend_for_get_keys();
|
||||
|
||||
add_sub_dirs(&tmp_dir);
|
||||
add_key_files(&tmp_dir);
|
||||
add_non_key_files(&tmp_dir);
|
||||
|
||||
assert_eq!(backend.get_keys().unwrap().len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_files_are_public_keys() {
|
||||
let (backend, tmp_dir) = new_backend_for_get_keys();
|
||||
add_key_files(&tmp_dir);
|
||||
|
||||
assert_eq!(backend.get_keys().unwrap().len(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod backend_raw_dir_sign_message {
|
||||
use crate::tests_commons::*;
|
||||
use helpers::*;
|
||||
use types::Hash256;
|
||||
|
||||
#[test]
|
||||
fn invalid_public_key() {
|
||||
let (backend, _tmp_dir) = new_backend_for_signing();
|
||||
|
||||
let test_case = |public_key_param: &str| {
|
||||
assert_eq!(
|
||||
backend
|
||||
.clone()
|
||||
.sign_message(
|
||||
public_key_param,
|
||||
Hash256::from_slice(&hex::decode(SIGNING_ROOT).unwrap())
|
||||
)
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
format!("Invalid public key: {}", public_key_param)
|
||||
);
|
||||
};
|
||||
|
||||
test_case("abcdef"); // Length < 96.
|
||||
test_case(&format!("{}55", PUBLIC_KEY_1)); // Length > 96.
|
||||
test_case(SILLY_FILE_NAME_1); // Length == 96; Invalid hex characters.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_error() {
|
||||
let (backend, tmp_dir) = new_backend_for_signing();
|
||||
|
||||
restrict_permissions(tmp_dir.path());
|
||||
restrict_permissions(&tmp_dir.path().join(PUBLIC_KEY_1));
|
||||
|
||||
let result = backend.sign_message(
|
||||
PUBLIC_KEY_1,
|
||||
Hash256::from_slice(&hex::decode(SIGNING_ROOT).unwrap()),
|
||||
);
|
||||
|
||||
unrestrict_permissions(tmp_dir.path());
|
||||
unrestrict_permissions(&tmp_dir.path().join(PUBLIC_KEY_1));
|
||||
|
||||
assert_eq!(
|
||||
result.unwrap_err().to_string(),
|
||||
"Storage error: PermissionDenied"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_not_found() {
|
||||
let (backend, _tmp_dir) = new_backend_for_signing();
|
||||
|
||||
assert_eq!(
|
||||
backend
|
||||
.sign_message(
|
||||
ABSENT_PUBLIC_KEY,
|
||||
Hash256::from_slice(&hex::decode(SIGNING_ROOT).unwrap())
|
||||
)
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
format!("Key not found: {}", ABSENT_PUBLIC_KEY)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_mismatch() {
|
||||
let (backend, _tmp_dir) = new_backend_for_signing();
|
||||
|
||||
assert_eq!(
|
||||
backend
|
||||
.sign_message(
|
||||
MISMATCHED_PUBLIC_KEY,
|
||||
Hash256::from_slice(&hex::decode(SIGNING_ROOT).unwrap())
|
||||
)
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
format!("Key mismatch: {}", MISMATCHED_PUBLIC_KEY)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn happy_path() {
|
||||
let (backend, _tmp_dir) = new_backend_for_signing();
|
||||
|
||||
let test_case = |public_key: &str, signature: &str| {
|
||||
assert_eq!(
|
||||
backend
|
||||
.clone()
|
||||
.sign_message(
|
||||
public_key,
|
||||
Hash256::from_slice(&hex::decode(SIGNING_ROOT).unwrap())
|
||||
)
|
||||
.unwrap(),
|
||||
signature
|
||||
);
|
||||
};
|
||||
|
||||
test_case(PUBLIC_KEY_1, EXPECTED_SIGNATURE_1);
|
||||
test_case(PUBLIC_KEY_2, EXPECTED_SIGNATURE_2);
|
||||
test_case(PUBLIC_KEY_3, EXPECTED_SIGNATURE_3);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
use crate::{BackendError, ZeroizeString};
|
||||
|
||||
/// The storage medium for the secret keys used by a `Backend`.
|
||||
pub trait Storage: 'static + Clone + Send + Sync {
|
||||
/// Queries storage for the available keys to sign.
|
||||
fn get_keys(&self) -> Result<Vec<String>, BackendError>;
|
||||
|
||||
/// Retrieves secret key from storage, using its public key as reference.
|
||||
fn get_secret_key(&self, input: &str) -> Result<ZeroizeString, BackendError>;
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
use crate::{BackendError, Storage, ZeroizeString, PUBLIC_KEY_REGEX};
|
||||
use std::fs::read_dir;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::Read;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StorageRawDir {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl StorageRawDir {
|
||||
/// Initializes the storage with the given path, verifying
|
||||
/// whether it is a directory and if its available to the user.
|
||||
/// Does not list, nor verify the contents of the directory.
|
||||
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, String> {
|
||||
let path = path.as_ref();
|
||||
|
||||
if !path.exists() {
|
||||
return Err("Path does not exist.".to_string());
|
||||
}
|
||||
|
||||
if !path.is_dir() {
|
||||
return Err("Path is not a directory.".to_string());
|
||||
}
|
||||
|
||||
read_dir(path).map_err(|e| format!("{:?}", e.kind()))?;
|
||||
|
||||
Ok(Self {
|
||||
path: path.to_path_buf(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Storage for StorageRawDir {
|
||||
/// List all the files in the directory having a BLS public key name.
|
||||
/// This function DOES NOT check the contents of each file.
|
||||
fn get_keys(&self) -> Result<Vec<String>, BackendError> {
|
||||
let entries = read_dir(&self.path).map_err(BackendError::from)?;
|
||||
|
||||
// We are silently suppressing errors in this chain
|
||||
// because we only care about files actually passing these filters.
|
||||
let keys: Vec<String> = entries
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter(|entry| !entry.path().is_dir())
|
||||
.map(|entry| entry.file_name().into_string())
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter(|name| PUBLIC_KEY_REGEX.is_match(name))
|
||||
.collect();
|
||||
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
/// Gets a requested secret key by their reference, its public key.
|
||||
/// This function DOES NOT check the contents of the retrieved file.
|
||||
fn get_secret_key(&self, input: &str) -> Result<ZeroizeString, BackendError> {
|
||||
let file = File::open(self.path.join(input)).map_err(|e| match e.kind() {
|
||||
std::io::ErrorKind::NotFound => BackendError::KeyNotFound(input.to_string()),
|
||||
_ => e.into(),
|
||||
})?;
|
||||
let mut buf_reader = BufReader::new(file);
|
||||
|
||||
let mut secret_key = String::new();
|
||||
buf_reader.read_to_string(&mut secret_key)?;
|
||||
|
||||
// Remove that `\n` without cloning.
|
||||
secret_key.pop();
|
||||
|
||||
Ok(ZeroizeString::from(secret_key))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod get_keys {
|
||||
use crate::tests_commons::*;
|
||||
use helpers::*;
|
||||
|
||||
#[test]
|
||||
fn problem_with_path() {
|
||||
let (storage, tmp_dir) = new_storage_with_tmp_dir();
|
||||
add_key_files(&tmp_dir);
|
||||
|
||||
// All good and fancy, let's make the dir innacessible now.
|
||||
restrict_permissions(tmp_dir.path());
|
||||
|
||||
let result = storage.get_keys();
|
||||
|
||||
// Give permissions back, we want the tempdir to be deleted.
|
||||
unrestrict_permissions(tmp_dir.path());
|
||||
|
||||
assert_eq!(
|
||||
result.unwrap_err().to_string(),
|
||||
"Storage error: PermissionDenied"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_files_in_dir() {
|
||||
let (storage, _tmp_dir) = new_storage_with_tmp_dir();
|
||||
|
||||
assert_eq!(storage.get_keys().unwrap().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_files_in_dir_are_public_keys() {
|
||||
let (storage, tmp_dir) = new_storage_with_tmp_dir();
|
||||
add_sub_dirs(&tmp_dir);
|
||||
add_non_key_files(&tmp_dir);
|
||||
|
||||
assert_eq!(storage.get_keys().unwrap().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_all_files_have_public_key_names() {
|
||||
let (storage, tmp_dir) = new_storage_with_tmp_dir();
|
||||
add_sub_dirs(&tmp_dir);
|
||||
add_key_files(&tmp_dir);
|
||||
add_non_key_files(&tmp_dir);
|
||||
|
||||
assert_eq!(storage.get_keys().unwrap().len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_files_do_have_public_key_names() {
|
||||
let (storage, tmp_dir) = new_storage_with_tmp_dir();
|
||||
add_key_files(&tmp_dir);
|
||||
|
||||
assert_eq!(storage.get_keys().unwrap().len(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod get_secret_key {
|
||||
use crate::tests_commons::*;
|
||||
use helpers::*;
|
||||
|
||||
#[test]
|
||||
fn unaccessible_file() {
|
||||
let (storage, tmp_dir) = new_storage_with_tmp_dir();
|
||||
add_key_files(&tmp_dir);
|
||||
|
||||
restrict_permissions(tmp_dir.path());
|
||||
restrict_permissions(&tmp_dir.path().join(PUBLIC_KEY_1));
|
||||
|
||||
let result = storage.get_secret_key(PUBLIC_KEY_1);
|
||||
|
||||
unrestrict_permissions(tmp_dir.path());
|
||||
unrestrict_permissions(&tmp_dir.path().join(PUBLIC_KEY_1));
|
||||
|
||||
assert_eq!(
|
||||
result.unwrap_err().to_string(),
|
||||
"Storage error: PermissionDenied"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_does_not_exist() {
|
||||
let (storage, _tmp_dir) = new_storage_with_tmp_dir();
|
||||
|
||||
assert_eq!(
|
||||
storage
|
||||
.get_secret_key(PUBLIC_KEY_1)
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
format!("Key not found: {}", PUBLIC_KEY_1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn happy_path() {
|
||||
let (storage, tmp_dir) = new_storage_with_tmp_dir();
|
||||
add_key_files(&tmp_dir);
|
||||
|
||||
assert_eq!(
|
||||
storage.get_secret_key(PUBLIC_KEY_1).unwrap().as_ref(),
|
||||
SECRET_KEY_1.as_bytes()
|
||||
);
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
use crate::{BackendError, ZeroizeString};
|
||||
use bls::SecretKey;
|
||||
use hex::decode;
|
||||
use std::fmt::{Error, Write};
|
||||
use std::str;
|
||||
|
||||
// hex::encode only allows up to 32 bytes.
|
||||
pub fn bytes96_to_hex_string(data: [u8; 96]) -> Result<String, Error> {
|
||||
static CHARS: &[u8] = b"0123456789abcdef";
|
||||
let mut s = String::with_capacity(96 * 2 + 2);
|
||||
|
||||
s.write_char('0')?;
|
||||
s.write_char('x')?;
|
||||
|
||||
for &byte in data.iter() {
|
||||
s.write_char(CHARS[(byte >> 4) as usize].into())?;
|
||||
s.write_char(CHARS[(byte & 0xf) as usize].into())?;
|
||||
}
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// Validates the match as a BLS pair of the public and secret keys given,
|
||||
/// consuming the secret key parameter, and returning a deserialized SecretKey.
|
||||
pub fn validate_bls_pair(
|
||||
public_key: &str,
|
||||
secret_key: ZeroizeString,
|
||||
) -> Result<SecretKey, BackendError> {
|
||||
let secret_key: SecretKey = secret_key.into_bls_sk().map_err(|e| {
|
||||
BackendError::InvalidSecretKey(format!("public_key: {}; {}", public_key, e))
|
||||
})?;
|
||||
|
||||
let pk_param_as_bytes = decode(&public_key)
|
||||
.map_err(|e| BackendError::InvalidPublicKey(format!("{}; {}", public_key, e)))?;
|
||||
|
||||
if &secret_key.public_key().serialize()[..] != pk_param_as_bytes.as_slice() {
|
||||
return Err(BackendError::KeyMismatch(public_key.to_string()));
|
||||
}
|
||||
|
||||
Ok(secret_key)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod functions {
|
||||
use super::*;
|
||||
use helpers::*;
|
||||
|
||||
#[test]
|
||||
fn fn_bytes96_to_hex_string() {
|
||||
assert_eq!(
|
||||
bytes96_to_hex_string(EXPECTED_SIGNATURE_1_BYTES).unwrap(),
|
||||
EXPECTED_SIGNATURE_1
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
bytes96_to_hex_string(EXPECTED_SIGNATURE_2_BYTES).unwrap(),
|
||||
EXPECTED_SIGNATURE_2
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
bytes96_to_hex_string(EXPECTED_SIGNATURE_3_BYTES).unwrap(),
|
||||
EXPECTED_SIGNATURE_3
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_validate_bls_pair() {
|
||||
let test_ok_case = |pk: &str, sk: ZeroizeString, sk_bytes: &[u8; 32]| {
|
||||
let serialized_secret_key = validate_bls_pair(pk, sk).unwrap().serialize();
|
||||
assert_eq!(serialized_secret_key.as_bytes().to_vec(), sk_bytes.to_vec());
|
||||
};
|
||||
|
||||
test_ok_case(
|
||||
PUBLIC_KEY_1,
|
||||
ZeroizeString::from(SECRET_KEY_1.to_string()),
|
||||
&SECRET_KEY_1_BYTES,
|
||||
);
|
||||
|
||||
let test_error_case = |pk: &str, sk: ZeroizeString, expected_error: &str| {
|
||||
assert_eq!(
|
||||
validate_bls_pair(pk, sk).err().unwrap().to_string(),
|
||||
expected_error
|
||||
);
|
||||
};
|
||||
|
||||
test_error_case(
|
||||
PUBLIC_KEY_2,
|
||||
ZeroizeString::from("TamperedKey%#$#%#$$&##00£$%$$£%$".to_string()),
|
||||
&format!(
|
||||
"Invalid secret key: public_key: {}; Invalid hex character: T at index 0",
|
||||
PUBLIC_KEY_2
|
||||
),
|
||||
);
|
||||
|
||||
test_error_case(
|
||||
PUBLIC_KEY_2,
|
||||
ZeroizeString::from("deadbeef".to_string()),
|
||||
&format!(
|
||||
"Invalid secret key: public_key: {}; InvalidSecretKeyLength {{ got: 4, expected: 32 }}",
|
||||
PUBLIC_KEY_2
|
||||
),
|
||||
);
|
||||
|
||||
let bad_pk_param = "not_validated_by_the_api_handler!";
|
||||
test_error_case(
|
||||
bad_pk_param,
|
||||
ZeroizeString::from(SECRET_KEY_1.to_string()),
|
||||
&format!("Invalid public key: {}; Odd number of digits", bad_pk_param),
|
||||
);
|
||||
|
||||
test_error_case(
|
||||
PUBLIC_KEY_1,
|
||||
ZeroizeString::from(SECRET_KEY_2.to_string()),
|
||||
&format!("Key mismatch: {}", PUBLIC_KEY_1),
|
||||
);
|
||||
|
||||
test_error_case(
|
||||
PUBLIC_KEY_2,
|
||||
ZeroizeString::from(SECRET_KEY_3.to_string()),
|
||||
&format!("Key mismatch: {}", PUBLIC_KEY_2),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,222 +0,0 @@
|
||||
use bls::SecretKey;
|
||||
use std::str;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
/// Provides a new-type wrapper around `String` that is zeroized on `Drop`.
|
||||
///
|
||||
/// Useful for ensuring that secret key memory is zeroed-out on drop.
|
||||
#[derive(Debug, Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
pub struct ZeroizeString(String);
|
||||
|
||||
impl From<String> for ZeroizeString {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for ZeroizeString {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl ZeroizeString {
|
||||
/// Consumes the ZeroizeString, attempting to return a BLS SecretKey.
|
||||
pub fn into_bls_sk(self) -> Result<SecretKey, String> {
|
||||
let mut decoded_bytes = hex_string_to_bytes(&self.0)?;
|
||||
|
||||
let secret_key = SecretKey::deserialize(&decoded_bytes).map_err(|e| format!("{:?}", e))?;
|
||||
decoded_bytes.zeroize();
|
||||
|
||||
Ok(secret_key)
|
||||
}
|
||||
}
|
||||
|
||||
// An alternative to `hex::decode`, to allow for more control of
|
||||
// the objects created while decoding the secret key.
|
||||
fn hex_string_to_bytes(data: &str) -> Result<Vec<u8>, String> {
|
||||
if data.len() % 2 != 0 {
|
||||
return Err("Odd length".to_string());
|
||||
}
|
||||
|
||||
let mut vec: Vec<u8> = Vec::new();
|
||||
for i in 0..data.len() / 2 {
|
||||
vec.push(
|
||||
val(&data.as_bytes()[2 * i], 2 * i)? << 4
|
||||
| val(&data.as_bytes()[2 * i + 1], 2 * i + 1)?,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
// Auxiliar function for `hex_string_to_bytes`.
|
||||
fn val(c: &u8, idx: usize) -> Result<u8, String> {
|
||||
match c {
|
||||
b'A'..=b'F' => Ok(c - b'A' + 10),
|
||||
b'a'..=b'f' => Ok(c - b'a' + 10),
|
||||
b'0'..=b'9' => Ok(c - b'0'),
|
||||
_ => Err(format!(
|
||||
"Invalid hex character: {} at index {}",
|
||||
*c as char, idx
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod object {
|
||||
use super::*;
|
||||
use helpers::*;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[test]
|
||||
fn v_u8_zeroized() {
|
||||
// Create from `hex_string_to_bytes`, and record the pointer to its buffer.
|
||||
let mut decoded_bytes = hex_string_to_bytes(SECRET_KEY_1).unwrap();
|
||||
let old_pointer = decoded_bytes.as_ptr() as usize;
|
||||
|
||||
// Do something with the borrowed vector, and zeroize.
|
||||
let _ = SecretKey::deserialize(&decoded_bytes)
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
.unwrap();
|
||||
decoded_bytes.zeroize();
|
||||
|
||||
// Check it is pointing to the same buffer, and that it was deleted.
|
||||
assert_eq!(old_pointer as usize, decoded_bytes.as_ptr() as usize);
|
||||
assert!(decoded_bytes.is_empty());
|
||||
|
||||
// Check if the underlying bytes were zeroized.
|
||||
for i in 0..SECRET_KEY_1.len() / 2 {
|
||||
unsafe {
|
||||
assert_eq!(*((old_pointer + i) as *const u8), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_to_bls_sk() {
|
||||
let test_ok_case = |sk: &str, sk_b: &[u8]| {
|
||||
let z = ZeroizeString::from(sk.to_string());
|
||||
let sk: SecretKey = z.into_bls_sk().unwrap();
|
||||
assert_eq!(sk.serialize().as_bytes(), sk_b);
|
||||
};
|
||||
|
||||
let test_error_case = |sk: &str, err_msg: &str| {
|
||||
let z = ZeroizeString::from(sk.to_string());
|
||||
let err = z.into_bls_sk().err();
|
||||
assert_eq!(err, Some(err_msg.to_string()));
|
||||
};
|
||||
|
||||
test_ok_case(SECRET_KEY_1, &SECRET_KEY_1_BYTES);
|
||||
|
||||
test_error_case("Trolololololo", "Odd length");
|
||||
test_error_case("Trololololol", "Invalid hex character: T at index 0");
|
||||
test_error_case(
|
||||
"そんなことないでしょうけどう",
|
||||
"Invalid hex character: ã at index 0",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zeroized_after_drop() {
|
||||
let some_scope = |s: &str| -> usize {
|
||||
// Convert our literal into a String, then store the pointer
|
||||
// to the first byte of its slice.
|
||||
let s: String = s.to_string();
|
||||
let s_ptr = s.as_ptr();
|
||||
|
||||
// Just to make sure that the pointer of the string is NOT
|
||||
// the same as the pointer of the underlying buffer.
|
||||
assert_ne!(&s as *const String as usize, s_ptr as usize);
|
||||
|
||||
let z = ZeroizeString::from(s);
|
||||
|
||||
// Get the pointer to the underlying buffer,
|
||||
// We want to make sure is the same as the received string literal.
|
||||
// That is, no copies of the contents.
|
||||
let ptr_to_buf = z.as_ref().as_ptr();
|
||||
assert_eq!(ptr_to_buf, s_ptr);
|
||||
|
||||
// We exit this scope, returning to the caller the pointer to
|
||||
// the buffer, that we'll use to verify the zeroization.
|
||||
ptr_to_buf as usize
|
||||
};
|
||||
|
||||
// Call the closure.
|
||||
let ptr_to_buf = some_scope(SECRET_KEY_1);
|
||||
|
||||
// Check if the underlying bytes were zeroized.
|
||||
// At this point the first half is already reclaimed and assigned,
|
||||
// so we will just examine the other half.
|
||||
for i in SECRET_KEY_1.len() / 2..SECRET_KEY_1.len() {
|
||||
unsafe {
|
||||
assert_eq!(*((ptr_to_buf + i) as *const u8), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod functions {
|
||||
use super::*;
|
||||
use helpers::*;
|
||||
|
||||
#[test]
|
||||
fn fn_hex_string_to_bytes() {
|
||||
assert_eq!(
|
||||
hex_string_to_bytes(&"0aa".to_string()).err(),
|
||||
Some("Odd length".to_string())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
hex_string_to_bytes(&"0xdeadbeef".to_string()).err(),
|
||||
Some("Invalid hex character: x at index 1".to_string())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
hex_string_to_bytes(&"n00bn00b".to_string()).err(),
|
||||
Some("Invalid hex character: n at index 0".to_string())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
hex_string_to_bytes(&"abcdefgh".to_string()).err(),
|
||||
Some("Invalid hex character: g at index 6".to_string())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
hex_string_to_bytes(SECRET_KEY_1).unwrap(),
|
||||
SECRET_KEY_1_BYTES
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
hex_string_to_bytes(PUBLIC_KEY_1).unwrap(),
|
||||
PUBLIC_KEY_1_BYTES.to_vec()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
hex_string_to_bytes(SIGNING_ROOT).unwrap(),
|
||||
SIGNING_ROOT_BYTES.to_vec()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
hex_string_to_bytes(&EXPECTED_SIGNATURE_1[2..]).unwrap(),
|
||||
EXPECTED_SIGNATURE_1_BYTES.to_vec()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
hex_string_to_bytes(&EXPECTED_SIGNATURE_2[2..]).unwrap(),
|
||||
EXPECTED_SIGNATURE_2_BYTES.to_vec()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
hex_string_to_bytes(&EXPECTED_SIGNATURE_3[2..]).unwrap(),
|
||||
EXPECTED_SIGNATURE_3_BYTES.to_vec()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
hex_string_to_bytes(&"0a0b11".to_string()).unwrap(),
|
||||
vec![10, 11, 17]
|
||||
);
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "remote_signer_client"
|
||||
version = "0.2.0"
|
||||
authors = ["Herman Junge <herman@sigmaprime.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.3"
|
||||
client_backend = { path = "../backend", package = "remote_signer_backend" }
|
||||
environment = { path = "../../lighthouse/environment" }
|
||||
futures = "0.3.6"
|
||||
hyper = "0.14.4"
|
||||
lazy_static = "1.4.0"
|
||||
regex = "1.3.9"
|
||||
serde = { version = "1.0.116", features = ["derive"] }
|
||||
serde_json = "1.0.58"
|
||||
slog = "2.5.2"
|
||||
types = { path = "../../consensus/types" }
|
||||
task_executor = { "path" = "../../common/task_executor" }
|
@ -1,57 +0,0 @@
|
||||
use hyper::{Body, Response, StatusCode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::to_string;
|
||||
use std::error::Error as StdError;
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum ApiError {
|
||||
ServerError(String),
|
||||
NotImplemented(String),
|
||||
BadRequest(String),
|
||||
NotFound(String),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct ApiErrorDesc {
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
pub type ApiResult = Result<Response<Body>, ApiError>;
|
||||
|
||||
impl ApiError {
|
||||
pub fn status_code(self) -> (StatusCode, String) {
|
||||
match self {
|
||||
ApiError::ServerError(desc) => (StatusCode::INTERNAL_SERVER_ERROR, desc),
|
||||
ApiError::NotImplemented(desc) => (StatusCode::NOT_IMPLEMENTED, desc),
|
||||
ApiError::BadRequest(desc) => (StatusCode::BAD_REQUEST, desc),
|
||||
ApiError::NotFound(desc) => (StatusCode::NOT_FOUND, desc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Response<Body>> for ApiError {
|
||||
fn into(self) -> Response<Body> {
|
||||
let (status_code, desc) = self.status_code();
|
||||
|
||||
let json_desc = to_string(&ApiErrorDesc { error: desc })
|
||||
.expect("The struct ApiErrorDesc should always serialize.");
|
||||
|
||||
Response::builder()
|
||||
.status(status_code)
|
||||
.body(Body::from(json_desc))
|
||||
.expect("Response should always be created.")
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for ApiError {
|
||||
fn cause(&self) -> Option<&dyn StdError> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ApiError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let status = self.clone().status_code();
|
||||
write!(f, "{:?}: {:?}", status.0, status.1)
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UpcheckApiResponse {
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
/// Contains the response to the `get_keys` API.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct KeysApiResponse {
|
||||
pub keys: Vec<String>,
|
||||
}
|
||||
|
||||
/// Contains the response to the `sign_message` API.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct SignatureApiResponse {
|
||||
pub signature: String,
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
use crate::api_error::ApiError;
|
||||
use crate::api_response::{KeysApiResponse, SignatureApiResponse};
|
||||
use crate::rest_api::Context;
|
||||
use crate::signing_root::get_signing_root;
|
||||
use client_backend::{BackendError, Storage};
|
||||
use hyper::Request;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::sync::Arc;
|
||||
use types::EthSpec;
|
||||
|
||||
lazy_static! {
|
||||
static ref PUBLIC_KEY_FROM_PATH_REGEX: Regex = Regex::new(r"^/[^/]+/([^/]*)").unwrap();
|
||||
}
|
||||
|
||||
/// HTTP handler to get the list of public keys in the backend.
|
||||
pub fn get_keys<E: EthSpec, S: Storage, U>(
|
||||
_: U,
|
||||
ctx: Arc<Context<E, S>>,
|
||||
) -> Result<KeysApiResponse, ApiError> {
|
||||
let keys = ctx
|
||||
.backend
|
||||
.get_keys()
|
||||
.map_err(|e| ApiError::ServerError(format!("{}", e)))?;
|
||||
|
||||
if keys.is_empty() {
|
||||
return Err(ApiError::NotFound("No keys found in storage.".to_string()));
|
||||
}
|
||||
|
||||
Ok(KeysApiResponse { keys })
|
||||
}
|
||||
|
||||
/// HTTP handler to sign a message with the requested key.
|
||||
pub fn sign_message<E: EthSpec, S: Storage>(
|
||||
req: Request<Vec<u8>>,
|
||||
ctx: Arc<Context<E, S>>,
|
||||
) -> Result<SignatureApiResponse, ApiError> {
|
||||
// Parse the request body and compute the signing root.
|
||||
let signing_root = get_signing_root::<E>(&req, ctx.spec.clone())?;
|
||||
|
||||
// This public key parameter should have been validated by the router.
|
||||
// We are just going to extract it from the request.
|
||||
let path = req.uri().path().to_string();
|
||||
|
||||
let rc = |path: &str| -> Result<String, String> {
|
||||
let caps = PUBLIC_KEY_FROM_PATH_REGEX.captures(path).ok_or("")?;
|
||||
let re_match = caps.get(1).ok_or("")?;
|
||||
Ok(re_match.as_str().to_string())
|
||||
};
|
||||
|
||||
let public_key = rc(&path).map_err(|_| {
|
||||
ApiError::BadRequest(format!("Unable to get public key from path: {:?}", path))
|
||||
})?;
|
||||
|
||||
match ctx.backend.sign_message(&public_key, signing_root) {
|
||||
Ok(signature) => Ok(SignatureApiResponse { signature }),
|
||||
|
||||
Err(BackendError::KeyNotFound(_)) => {
|
||||
Err(ApiError::NotFound(format!("Key not found: {}", public_key)))
|
||||
}
|
||||
|
||||
Err(BackendError::InvalidPublicKey(_)) => Err(ApiError::BadRequest(format!(
|
||||
"Invalid public key: {}",
|
||||
public_key
|
||||
))),
|
||||
|
||||
// Catches InvalidSecretKey, KeyMismatch and StorageError.
|
||||
Err(e) => Err(ApiError::ServerError(e.to_string())),
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
/// HTTP REST API Configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
/// The IPv4 address the REST API HTTP server will listen on.
|
||||
pub listen_address: Ipv4Addr,
|
||||
/// The port the REST API HTTP server will listen on.
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
listen_address: Ipv4Addr::new(127, 0, 0, 1),
|
||||
port: 9000,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
use crate::api_error::{ApiError, ApiResult};
|
||||
use crate::rest_api::Context;
|
||||
use hyper::{Body, Request, Response, StatusCode};
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
use types::EthSpec;
|
||||
|
||||
/// Provides a HTTP request handler with specific functionality.
|
||||
pub struct Handler<E: EthSpec, S: Send + Sync> {
|
||||
req: Request<()>,
|
||||
body: Body,
|
||||
ctx: Arc<Context<E, S>>,
|
||||
allow_body: bool,
|
||||
}
|
||||
|
||||
impl<E: EthSpec, S: 'static + Send + Sync> Handler<E, S> {
|
||||
/// Start handling a new request.
|
||||
pub fn new(req: Request<Body>, ctx: Arc<Context<E, S>>) -> Result<Self, ApiError> {
|
||||
let (req_parts, body) = req.into_parts();
|
||||
let req = Request::from_parts(req_parts, ());
|
||||
|
||||
Ok(Self {
|
||||
req,
|
||||
body,
|
||||
ctx,
|
||||
allow_body: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return a simple static value.
|
||||
///
|
||||
/// Does not use the blocking executor.
|
||||
pub async fn static_value<V>(self, value: V) -> Result<HandledRequest<V>, ApiError> {
|
||||
// Always check and disallow a body for a static value.
|
||||
let _ = Self::get_body(self.body, false).await?;
|
||||
|
||||
Ok(HandledRequest { value })
|
||||
}
|
||||
|
||||
/// The default behaviour is to return an error if any body is supplied in the request. Calling
|
||||
/// this function disables that error.
|
||||
pub fn allow_body(mut self) -> Self {
|
||||
self.allow_body = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Spawns `func` on the blocking executor.
|
||||
///
|
||||
/// This method is suitable for handling long-running or intensive tasks.
|
||||
pub async fn in_blocking_task<F, V>(self, func: F) -> Result<HandledRequest<V>, ApiError>
|
||||
where
|
||||
V: Send + Sync + 'static,
|
||||
F: Fn(Request<Vec<u8>>, Arc<Context<E, S>>) -> Result<V, ApiError> + Send + Sync + 'static,
|
||||
{
|
||||
let ctx = self.ctx;
|
||||
let executor = ctx.executor.clone();
|
||||
let body = Self::get_body(self.body, self.allow_body).await?;
|
||||
let (req_parts, _) = self.req.into_parts();
|
||||
let req = Request::from_parts(req_parts, body);
|
||||
|
||||
// NOTE: The task executor now holds a weak reference to the global runtime. On shutdown
|
||||
// there may be no runtime available.
|
||||
// All these edge cases must be handled here.
|
||||
let value = executor
|
||||
.spawn_blocking_handle(move || func(req, ctx), "remote_signer_request")
|
||||
.ok_or_else(|| ApiError::ServerError("Runtime does not exist".to_string()))?
|
||||
.await
|
||||
.map_err(|_| ApiError::ServerError("Panic during execution".to_string()))??;
|
||||
|
||||
Ok(HandledRequest { value })
|
||||
}
|
||||
|
||||
/// Downloads the bytes for `body`.
|
||||
async fn get_body(body: Body, allow_body: bool) -> Result<Vec<u8>, ApiError> {
|
||||
let bytes = hyper::body::to_bytes(body)
|
||||
.await
|
||||
.map_err(|e| ApiError::ServerError(format!("Unable to get request body: {:?}", e)))?;
|
||||
|
||||
if !allow_body && !bytes[..].is_empty() {
|
||||
Err(ApiError::BadRequest(
|
||||
"The request body must be empty".to_string(),
|
||||
))
|
||||
} else {
|
||||
Ok(bytes.into_iter().collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A request that has been "handled" and now a result (`value`) needs to be serialized and
|
||||
/// returned.
|
||||
pub struct HandledRequest<V> {
|
||||
value: V,
|
||||
}
|
||||
|
||||
impl<V: Serialize> HandledRequest<V> {
|
||||
/// Suitable for items which only implement `serde`.
|
||||
pub fn serde_encodings(self) -> ApiResult {
|
||||
let body = Body::from(serde_json::to_string(&self.value).map_err(|e| {
|
||||
ApiError::ServerError(format!(
|
||||
"Unable to serialize response body as JSON: {:?}",
|
||||
e
|
||||
))
|
||||
})?);
|
||||
|
||||
Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("content-type", "application/json")
|
||||
.body(body)
|
||||
.map_err(|e| ApiError::ServerError(format!("Failed to build response: {:?}", e)))
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
pub mod api_error;
|
||||
pub mod api_response;
|
||||
|
||||
mod backend;
|
||||
mod config;
|
||||
mod handler;
|
||||
mod rest_api;
|
||||
mod router;
|
||||
mod signing_root;
|
||||
mod upcheck;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use client_backend::Backend;
|
||||
use config::Config;
|
||||
use environment::RuntimeContext;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::SocketAddr;
|
||||
use types::EthSpec;
|
||||
|
||||
pub struct Client {
|
||||
listening_address: SocketAddr,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub async fn new<E: EthSpec>(
|
||||
context: RuntimeContext<E>,
|
||||
cli_args: &ArgMatches<'_>,
|
||||
) -> Result<Self, String> {
|
||||
let log = context.executor.log();
|
||||
|
||||
let mut config = Config::default();
|
||||
|
||||
if let Some(address) = cli_args.value_of("listen-address") {
|
||||
config.listen_address = address
|
||||
.parse::<Ipv4Addr>()
|
||||
.map_err(|_| "listen-address is not a valid IPv4 address.")?;
|
||||
}
|
||||
|
||||
if let Some(port) = cli_args.value_of("port") {
|
||||
config.port = port
|
||||
.parse::<u16>()
|
||||
.map_err(|_| "port is not a valid u16.")?;
|
||||
}
|
||||
|
||||
let backend = Backend::new(cli_args, log)?;
|
||||
|
||||
// It is useful to get the listening address if you have set up your port to be 0.
|
||||
let listening_address =
|
||||
rest_api::start_server(context.executor, config, backend, context.eth_spec_instance)
|
||||
.map_err(|e| format!("Failed to start HTTP API: {:?}", e))?;
|
||||
|
||||
Ok(Self { listening_address })
|
||||
}
|
||||
|
||||
pub fn get_listening_address(&self) -> SocketAddr {
|
||||
self.listening_address
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
use crate::config::Config;
|
||||
use client_backend::{Backend, Storage};
|
||||
use futures::future::TryFutureExt;
|
||||
use hyper::server::conn::AddrStream;
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Request, Server};
|
||||
use slog::{info, warn};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use task_executor::TaskExecutor;
|
||||
use types::{ChainSpec, EthSpec};
|
||||
|
||||
pub struct Context<E: EthSpec, S: Send + Sync> {
|
||||
pub config: Config,
|
||||
pub executor: TaskExecutor,
|
||||
pub log: slog::Logger,
|
||||
pub backend: Backend<S>,
|
||||
pub eth_spec_instance: E,
|
||||
pub spec: ChainSpec,
|
||||
}
|
||||
|
||||
pub fn start_server<E: EthSpec, S: Storage>(
|
||||
executor: TaskExecutor,
|
||||
config: Config,
|
||||
backend: Backend<S>,
|
||||
eth_spec_instance: E,
|
||||
) -> Result<SocketAddr, hyper::Error> {
|
||||
let log = executor.log();
|
||||
|
||||
let context = Arc::new(Context {
|
||||
executor: executor.clone(),
|
||||
log: log.clone(),
|
||||
config: config.clone(),
|
||||
backend,
|
||||
eth_spec_instance,
|
||||
spec: E::default_spec(),
|
||||
});
|
||||
|
||||
// Define the function that will build the request handler.
|
||||
let make_service = make_service_fn(move |_socket: &AddrStream| {
|
||||
let ctx = context.clone();
|
||||
|
||||
async move {
|
||||
Ok::<_, hyper::Error>(service_fn(move |req: Request<Body>| {
|
||||
crate::router::on_http_request(req, ctx.clone())
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
let bind_addr = (config.listen_address, config.port).into();
|
||||
let server = Server::bind(&bind_addr).serve(make_service);
|
||||
|
||||
// Determine the address the server is actually listening on.
|
||||
//
|
||||
// This may be different to `bind_addr` if bind port was 0 (this allows the OS to choose a free
|
||||
// port).
|
||||
let actual_listen_addr = server.local_addr();
|
||||
|
||||
// Build a channel to kill the HTTP server.
|
||||
let exit = executor.exit();
|
||||
let inner_log = log.clone();
|
||||
let server_exit = async move {
|
||||
let _ = exit.await;
|
||||
info!(inner_log, "HTTP service shutdown");
|
||||
};
|
||||
|
||||
// Configure the `hyper` server to gracefully shutdown when the shutdown channel is triggered.
|
||||
let inner_log = log.clone();
|
||||
let server_future = server
|
||||
.with_graceful_shutdown(async {
|
||||
server_exit.await;
|
||||
})
|
||||
.map_err(move |e| {
|
||||
warn!(
|
||||
inner_log,
|
||||
"HTTP server failed to start, Unable to bind"; "address" => format!("{:?}", e)
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|_| ());
|
||||
|
||||
info!(
|
||||
log,
|
||||
"HTTP API started";
|
||||
"address" => format!("{}", actual_listen_addr.ip()),
|
||||
"port" => actual_listen_addr.port(),
|
||||
);
|
||||
|
||||
executor.spawn_without_exit(server_future, "http");
|
||||
|
||||
Ok(actual_listen_addr)
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
use crate::api_error::ApiError;
|
||||
use crate::backend::{get_keys, sign_message};
|
||||
use crate::handler::Handler;
|
||||
use crate::rest_api::Context;
|
||||
use crate::upcheck::upcheck;
|
||||
use client_backend::Storage;
|
||||
use hyper::{Body, Method, Request, Response};
|
||||
use slog::debug;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use types::EthSpec;
|
||||
|
||||
pub async fn on_http_request<E: EthSpec, S: Storage>(
|
||||
req: Request<Body>,
|
||||
ctx: Arc<Context<E, S>>,
|
||||
) -> Result<Response<Body>, ApiError> {
|
||||
let path = req.uri().path().to_string();
|
||||
let received_instant = Instant::now();
|
||||
let log = ctx.log.clone();
|
||||
|
||||
match route(req, ctx).await {
|
||||
Ok(response) => {
|
||||
debug!(
|
||||
log,
|
||||
"HTTP API request successful";
|
||||
"path" => path,
|
||||
"duration_ms" => Instant::now().duration_since(received_instant).as_millis()
|
||||
);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
Err(error) => {
|
||||
debug!(
|
||||
log,
|
||||
"HTTP API request failure";
|
||||
"path" => path,
|
||||
"duration_ms" => Instant::now().duration_since(received_instant).as_millis()
|
||||
);
|
||||
Ok(error.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn route<E: EthSpec, S: Storage>(
|
||||
req: Request<Body>,
|
||||
ctx: Arc<Context<E, S>>,
|
||||
) -> Result<Response<Body>, ApiError> {
|
||||
let path = req.uri().path().to_string();
|
||||
let method = req.method().clone();
|
||||
let ctx = ctx.clone();
|
||||
let handler = Handler::new(req, ctx)?;
|
||||
|
||||
match (method, path.as_ref()) {
|
||||
(Method::GET, "/upcheck") => handler.static_value(upcheck()).await?.serde_encodings(),
|
||||
|
||||
(Method::GET, "/keys") => handler.in_blocking_task(get_keys).await?.serde_encodings(),
|
||||
|
||||
(Method::POST, _) => route_post(&path, handler).await,
|
||||
|
||||
_ => Err(ApiError::NotFound(
|
||||
"Request path and/or method not found.".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Responds to all the POST requests.
|
||||
///
|
||||
/// Should be deprecated once a better routing library is used, such as `warp`
|
||||
async fn route_post<E: EthSpec, S: Storage>(
|
||||
path: &str,
|
||||
handler: Handler<E, S>,
|
||||
) -> Result<Response<Body>, ApiError> {
|
||||
let mut path_segments = path[1..].trim_end_matches('/').split('/');
|
||||
|
||||
match path_segments.next() {
|
||||
Some("sign") => {
|
||||
let path_segments_count = path_segments.clone().count();
|
||||
|
||||
if path_segments_count == 0 {
|
||||
return Err(ApiError::BadRequest(
|
||||
"Parameter public_key needed in route /sign/:public_key".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if path_segments_count > 1 {
|
||||
return Err(ApiError::BadRequest(
|
||||
"Only one path segment is allowed after /sign".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
handler
|
||||
.allow_body()
|
||||
.in_blocking_task(sign_message)
|
||||
.await?
|
||||
.serde_encodings()
|
||||
}
|
||||
_ => Err(ApiError::NotFound(
|
||||
"Request path and/or method not found.".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
use crate::api_error::ApiError;
|
||||
use serde::Deserialize;
|
||||
use serde_json::{from_value, Value};
|
||||
|
||||
use types::{
|
||||
AttestationData, BeaconBlock, ChainSpec, Domain, Epoch, EthSpec, Fork, Hash256, SignedRoot,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SignMessageRequestBody {
|
||||
/// BLS Signature domain.
|
||||
/// Supporting `beacon_proposer`, `beacon_attester`, and `randao`.
|
||||
/// As defined in
|
||||
/// * https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#domain-types
|
||||
/// * in lowercase, omitting the `domain` prefix.
|
||||
bls_domain: String,
|
||||
|
||||
/// Supporting `block`, `attestation`, and `epoch`.
|
||||
/// (In LH these are `BeaconBlock`, `AttestationData`, and `Epoch`).
|
||||
/// As defined in
|
||||
/// * https://github.com/ethereum/eth2.0-APIs/blob/master/types/block.yaml
|
||||
/// * https://github.com/ethereum/eth2.0-APIs/blob/master/types/attestation.yaml
|
||||
/// * https://github.com/ethereum/eth2.0-APIs/blob/master/types/misc.yaml
|
||||
data: Value,
|
||||
|
||||
/// A `Fork` object containing previous and current versions.
|
||||
/// As defined in
|
||||
/// * https://github.com/ethereum/eth2.0-APIs/blob/master/types/misc.yaml
|
||||
fork: Fork,
|
||||
|
||||
/// A `Hash256` for domain separation and chain versioning.
|
||||
genesis_validators_root: Hash256,
|
||||
}
|
||||
|
||||
pub fn get_signing_root<E: EthSpec>(
|
||||
req: &hyper::Request<std::vec::Vec<u8>>,
|
||||
spec: ChainSpec,
|
||||
) -> Result<Hash256, ApiError> {
|
||||
let body: SignMessageRequestBody = serde_json::from_slice(req.body()).map_err(|e| {
|
||||
ApiError::BadRequest(format!("Unable to parse body message from JSON: {:?}", e))
|
||||
})?;
|
||||
|
||||
let get_domain = |epoch, bls_domain| {
|
||||
spec.get_domain(epoch, bls_domain, &body.fork, body.genesis_validators_root)
|
||||
};
|
||||
|
||||
match body.bls_domain.as_str() {
|
||||
"beacon_proposer" => {
|
||||
let block = from_value::<BeaconBlock<E>>(body.data.clone()).map_err(|e| {
|
||||
ApiError::BadRequest(format!("Unable to parse block from JSON: {:?}", e))
|
||||
})?;
|
||||
|
||||
Ok(block.signing_root(get_domain(block.epoch(), Domain::BeaconProposer)))
|
||||
}
|
||||
|
||||
"beacon_attester" => {
|
||||
let attestation = from_value::<AttestationData>(body.data.clone()).map_err(|e| {
|
||||
ApiError::BadRequest(format!("Unable to parse attestation from JSON: {:?}", e))
|
||||
})?;
|
||||
|
||||
Ok(attestation
|
||||
.signing_root(get_domain(attestation.target.epoch, Domain::BeaconAttester)))
|
||||
}
|
||||
|
||||
"randao" => {
|
||||
let epoch = from_value::<Epoch>(body.data.clone()).map_err(|e| {
|
||||
ApiError::BadRequest(format!("Unable to parse attestation from JSON: {:?}", e))
|
||||
})?;
|
||||
|
||||
Ok(epoch.signing_root(get_domain(epoch, Domain::Randao)))
|
||||
}
|
||||
|
||||
s => Err(ApiError::BadRequest(format!(
|
||||
"Unsupported bls_domain parameter: {}",
|
||||
s
|
||||
))),
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
use crate::api_response::UpcheckApiResponse;
|
||||
|
||||
pub fn upcheck() -> UpcheckApiResponse {
|
||||
UpcheckApiResponse {
|
||||
status: "OK".to_string(),
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
use clap::{App, Arg};
|
||||
|
||||
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
// Parse the CLI parameters.
|
||||
App::new("remote_signer")
|
||||
.visible_alias("rs")
|
||||
.author("Sigma Prime <contact@sigmaprime.io>")
|
||||
.setting(clap::AppSettings::ColoredHelp)
|
||||
.about(
|
||||
"Simple HTTP BLS signer service. \
|
||||
This service is designed to be consumed by Ethereum 2.0 clients, \
|
||||
looking for a more secure avenue to store their BLS12-381 secret keys, \
|
||||
while running their validators in more permisive and/or scalable environments.",
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("storage-raw-dir")
|
||||
.long("storage-raw-dir")
|
||||
.value_name("DIR")
|
||||
.help("Data directory for secret keys in raw files."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("listen-address")
|
||||
.long("listen-address")
|
||||
.value_name("ADDRESS")
|
||||
.help("The address to listen for TCP connections.")
|
||||
.default_value("0.0.0.0")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("port")
|
||||
.long("port")
|
||||
.value_name("PORT")
|
||||
.help("The TCP port to listen on.")
|
||||
.default_value("9000")
|
||||
.takes_value(true),
|
||||
)
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
mod cli;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use client::Client;
|
||||
use environment::Environment;
|
||||
use slog::info;
|
||||
use types::EthSpec;
|
||||
|
||||
pub use cli::cli_app;
|
||||
|
||||
pub fn run<E: EthSpec>(
|
||||
environment: &mut Environment<E>,
|
||||
matches: &ArgMatches,
|
||||
) -> Result<(), String> {
|
||||
let context = environment.core_context();
|
||||
let exit = context.executor.exit();
|
||||
|
||||
info!(
|
||||
context.log(),
|
||||
"Starting remote signer";
|
||||
);
|
||||
|
||||
let client = environment
|
||||
.runtime()
|
||||
.block_on(Client::new(context, matches))
|
||||
.map_err(|e| format!("Failed to init Rest API: {}", e))?;
|
||||
|
||||
environment.runtime().spawn(async move {
|
||||
exit.await;
|
||||
drop(client);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
mod get_keys {
|
||||
use client::api_response::KeysApiResponse;
|
||||
use helpers::*;
|
||||
|
||||
fn assert_ok(resp: ApiTestResponse, expected_keys_len: usize) {
|
||||
assert_eq!(resp.status, 200);
|
||||
assert_eq!(
|
||||
serde_json::from_value::<KeysApiResponse>(resp.json)
|
||||
.unwrap()
|
||||
.keys
|
||||
.len(),
|
||||
expected_keys_len
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_error(resp: ApiTestResponse, http_status: u16, error_msg: &str) {
|
||||
assert_eq!(resp.status, http_status);
|
||||
assert_eq!(resp.json["error"], error_msg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_files_in_dir_are_public_keys() {
|
||||
let (test_signer, tmp_dir) = set_up_api_test_signer_raw_dir();
|
||||
add_key_files(&tmp_dir);
|
||||
|
||||
let url = format!("{}/keys", test_signer.address);
|
||||
|
||||
let resp = http_get(&url);
|
||||
assert_ok(resp, 3);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_files_in_dir_are_public_keys() {
|
||||
let (test_signer, tmp_dir) = set_up_api_test_signer_raw_dir();
|
||||
add_sub_dirs(&tmp_dir);
|
||||
add_key_files(&tmp_dir);
|
||||
add_non_key_files(&tmp_dir);
|
||||
|
||||
let url = format!("{}/keys", test_signer.address);
|
||||
|
||||
let resp = http_get(&url);
|
||||
assert_ok(resp, 3);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_files_in_dir_are_public_keys() {
|
||||
let (test_signer, tmp_dir) = set_up_api_test_signer_raw_dir();
|
||||
add_sub_dirs(&tmp_dir);
|
||||
add_non_key_files(&tmp_dir);
|
||||
|
||||
let url = format!("{}/keys", test_signer.address);
|
||||
|
||||
let resp = http_get(&url);
|
||||
assert_error(resp, 404, "No keys found in storage.");
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn directory_failure() {
|
||||
let (test_signer, tmp_dir) = set_up_api_test_signer_raw_dir();
|
||||
add_sub_dirs(&tmp_dir);
|
||||
add_key_files(&tmp_dir);
|
||||
add_non_key_files(&tmp_dir);
|
||||
|
||||
// Somebody tripped over a wire.
|
||||
restrict_permissions(tmp_dir.path());
|
||||
|
||||
let url = format!("{}/keys", test_signer.address);
|
||||
|
||||
let resp = http_get(&url);
|
||||
|
||||
// Be able to delete the tempdir afterward, regardless of this test result.
|
||||
unrestrict_permissions(tmp_dir.path());
|
||||
|
||||
assert_error(resp, 500, "Storage error: PermissionDenied");
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
}
|
@ -1,402 +0,0 @@
|
||||
mod sign {
|
||||
use helpers::*;
|
||||
|
||||
#[test]
|
||||
fn additional_field() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let test_block_body = get_test_block_body(0xc137).replace(
|
||||
",\"genesis_validators_root\":\"0x000000000000000000000000000000000000000000000000000000000000c137\"",
|
||||
",\"genesis_validators_root\":\"0x000000000000000000000000000000000000000000000000000000000000c137\", \"foo\":\"bar\"",
|
||||
);
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_ok(response, HAPPY_PATH_BLOCK_SIGNATURE_C137);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_error() {
|
||||
let (test_signer, tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_block_body = get_test_block_body(0xc137);
|
||||
restrict_permissions(tmp_dir.path());
|
||||
restrict_permissions(&tmp_dir.path().join(PUBLIC_KEY_1));
|
||||
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
unrestrict_permissions(tmp_dir.path());
|
||||
unrestrict_permissions(&tmp_dir.path().join(PUBLIC_KEY_1));
|
||||
|
||||
assert_sign_error(response, 500, "Storage error: PermissionDenied");
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_public_key_in_path() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_block_body = get_test_block_body(0xc137);
|
||||
|
||||
let testcase = |url: String| {
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(
|
||||
response,
|
||||
400,
|
||||
"Parameter public_key needed in route /sign/:public_key",
|
||||
);
|
||||
};
|
||||
|
||||
testcase(format!("{}/sign/", test_signer.address));
|
||||
testcase(format!("{}/sign", test_signer.address));
|
||||
testcase(format!("{}/sign//", test_signer.address));
|
||||
testcase(format!("{}/sign///", test_signer.address));
|
||||
testcase(format!("{}/sign/?'or 1 = 1 --", test_signer.address));
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn additional_path_segments() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_block_body = get_test_block_body(0xc137);
|
||||
|
||||
let testcase = |url: String| {
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(
|
||||
response,
|
||||
400,
|
||||
"Only one path segment is allowed after /sign",
|
||||
);
|
||||
};
|
||||
|
||||
testcase(format!("{}/sign/this/receipt", test_signer.address));
|
||||
testcase(format!("{}/sign/this/receipt/please", test_signer.address));
|
||||
testcase(format!("{}/sign/this/receipt/please?", test_signer.address));
|
||||
testcase(format!(
|
||||
"{}/sign//{}/valid/pk",
|
||||
test_signer.address, PUBLIC_KEY_1
|
||||
));
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_public_key() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let test_block_body = get_test_block_body(0xc137);
|
||||
|
||||
let testcase = |url: String, expected_err: &str| {
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(response, 400, expected_err);
|
||||
};
|
||||
|
||||
testcase(
|
||||
format!("{}/sign/{}", test_signer.address, "ScottBakula"),
|
||||
"Invalid public key: ScottBakula",
|
||||
);
|
||||
testcase(
|
||||
format!("{}/sign/{}", test_signer.address, "deadbeef"),
|
||||
"Invalid public key: deadbeef",
|
||||
);
|
||||
testcase(
|
||||
format!("{}/sign/{}", test_signer.address, SILLY_FILE_NAME_1),
|
||||
&format!("Invalid public key: {}", SILLY_FILE_NAME_1),
|
||||
);
|
||||
testcase(
|
||||
format!("{}/sign/{}", test_signer.address, SILLY_FILE_NAME_1),
|
||||
&format!("Invalid public key: {}", SILLY_FILE_NAME_1),
|
||||
);
|
||||
testcase(
|
||||
format!("{}/sign/0x{}", test_signer.address, PUBLIC_KEY_1),
|
||||
&format!("Invalid public key: 0x{}", PUBLIC_KEY_1),
|
||||
);
|
||||
testcase(
|
||||
format!("{}/sign/{}55", test_signer.address, PUBLIC_KEY_1),
|
||||
&format!("Invalid public key: {}55", PUBLIC_KEY_1),
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_not_found() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, ABSENT_PUBLIC_KEY);
|
||||
let test_block_body = get_test_block_body(0xc137);
|
||||
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(
|
||||
response,
|
||||
404,
|
||||
&format!("Key not found: {}", ABSENT_PUBLIC_KEY),
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_secret_key() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!(
|
||||
"{}/sign/{}",
|
||||
test_signer.address, PUBLIC_KEY_FOR_INVALID_SECRET_KEY
|
||||
);
|
||||
let test_block_body = get_test_block_body(0xc137);
|
||||
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(
|
||||
response,
|
||||
500,
|
||||
&format!(
|
||||
"Invalid secret key: public_key: {}; Invalid hex character: W at index 0",
|
||||
PUBLIC_KEY_FOR_INVALID_SECRET_KEY
|
||||
),
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_mismatch() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, MISMATCHED_PUBLIC_KEY);
|
||||
let test_block_body = get_test_block_body(0xc137);
|
||||
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(
|
||||
response,
|
||||
500,
|
||||
&format!("Key mismatch: {}", MISMATCHED_PUBLIC_KEY),
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_json() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let testcase = |custom_body: &str, expected_err: &str| {
|
||||
let response = http_post_custom_body(&url, custom_body);
|
||||
assert_sign_error(response, 400, expected_err);
|
||||
};
|
||||
|
||||
testcase(
|
||||
"Trolololololo",
|
||||
"Unable to parse body message from JSON: Error(\"expected value\", line: 1, column: 1)",
|
||||
);
|
||||
testcase(
|
||||
"{\"bls_domain\"}",
|
||||
"Unable to parse body message from JSON: Error(\"expected `:`\", line: 1, column: 14)",
|
||||
);
|
||||
testcase(
|
||||
"{\"bls_domain\":}",
|
||||
"Unable to parse body message from JSON: Error(\"expected value\", line: 1, column: 15)",
|
||||
);
|
||||
|
||||
testcase(
|
||||
"{\"bls_domain\":\"}",
|
||||
"Unable to parse body message from JSON: Error(\"EOF while parsing a string\", line: 1, column: 16)",
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_field_bls_domain() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let testcase = |json_patch, expected_err| {
|
||||
let test_block_body = get_test_block_body(0xc137).replace(
|
||||
"\"bls_domain\":\"beacon_proposer\"",
|
||||
&format!("\"bls_domain\":{}", json_patch),
|
||||
);
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(response, 400, expected_err);
|
||||
};
|
||||
|
||||
testcase("\"blah\"", "Unsupported bls_domain parameter: blah");
|
||||
testcase("\"domain\"", "Unsupported bls_domain parameter: domain");
|
||||
testcase("\"\"", "Unsupported bls_domain parameter: ");
|
||||
testcase("", "Unable to parse body message from JSON: Error(\"expected value\", line: 1, column: 15)");
|
||||
testcase("1", "Unable to parse body message from JSON: Error(\"invalid type: integer `1`, expected a string\", line: 1, column: 15)");
|
||||
testcase("true", "Unable to parse body message from JSON: Error(\"invalid type: boolean `true`, expected a string\", line: 1, column: 18)");
|
||||
testcase("{\"cats\":\"3\"}", "Unable to parse body message from JSON: Error(\"invalid type: map, expected a string\", line: 1, column: 14)");
|
||||
testcase("[\"a\"]", "Unable to parse body message from JSON: Error(\"invalid type: sequence, expected a string\", line: 1, column: 14)");
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_field_bls_domain() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let test_block_body =
|
||||
get_test_block_body(0xc137).replace("\"bls_domain\":\"beacon_proposer\",", "");
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(response, 400, "Unable to parse body message from JSON: Error(\"missing field `bls_domain`\", line: 1, column: 237203)");
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_field_fork() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let testcase = |json_patch, expected_err| {
|
||||
let test_block_body = get_test_block_body(0xc137).replace(
|
||||
"\"fork\":{\"previous_version\":\"0x01010101\",\"current_version\":\"0x02020202\",\"epoch\":\"1545\"},",
|
||||
json_patch,
|
||||
);
|
||||
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(response, 400, expected_err);
|
||||
};
|
||||
|
||||
testcase(
|
||||
"\"fork\":{\"current_version\":\"0x02020202\",\"epoch\":\"1545\"},",
|
||||
"Unable to parse body message from JSON: Error(\"missing field `previous_version`\", line: 1, column: 237106)",
|
||||
);
|
||||
testcase(
|
||||
"\"fork\":{\"previous_version\":\"0x01010101\",\"epoch\":\"1545\"},",
|
||||
"Unable to parse body message from JSON: Error(\"missing field `current_version`\", line: 1, column: 237107)",
|
||||
);
|
||||
testcase(
|
||||
"\"fork\":{\"previous_version\":\"0x01010101\",\"current_version\":\"0x02020202\",",
|
||||
"Unable to parse body message from JSON: Error(\"missing field `epoch`\", line: 1, column: 237218)",
|
||||
);
|
||||
testcase(
|
||||
"\"fork\":{\"previous_version\":\"INVALID_VALUE\",\"current_version\":\"0x02020202\",\"epoch\":\"1545\"},",
|
||||
"Unable to parse body message from JSON: Error(\"missing 0x prefix\", line: 1, column: 237094)",
|
||||
);
|
||||
testcase(
|
||||
"\"fork\":{\"previous_version\":\"0xINVALID_VALUE\",\"current_version\":\"0x02020202\",\"epoch\":\"1545\"},",
|
||||
"Unable to parse body message from JSON: Error(\"invalid hex (OddLength)\", line: 1, column: 237096)",
|
||||
);
|
||||
testcase(
|
||||
"\"fork\":{\"previous_version\":\"0xINVALID_VALUE_\",\"current_version\":\"0x02020202\",\"epoch\":\"1545\"},",
|
||||
"Unable to parse body message from JSON: Error(\"invalid hex (InvalidHexCharacter { c: 'I', index: 0 })\", line: 1, column: 237097)",
|
||||
);
|
||||
testcase(
|
||||
"\"fork\":{\"previous_version\":\"0x01010101\",\"current_version\":\"INVALID_VALUE\",\"epoch\":\"1545\"},",
|
||||
"Unable to parse body message from JSON: Error(\"missing 0x prefix\", line: 1, column: 237125)"
|
||||
);
|
||||
testcase(
|
||||
"\"fork\":{\"previous_version\":\"0x01010101\",\"current_version\":\"0xINVALID_VALUE\",\"epoch\":\"1545\"},",
|
||||
"Unable to parse body message from JSON: Error(\"invalid hex (OddLength)\", line: 1, column: 237127)"
|
||||
);
|
||||
testcase(
|
||||
"\"fork\":{\"previous_version\":\"0x01010101\",\"current_version\":\"0xINVALID_VALUE_\",\"epoch\":\"1545\"},",
|
||||
"Unable to parse body message from JSON: Error(\"invalid hex (InvalidHexCharacter { c: 'I', index: 0 })\", line: 1, column: 237128)"
|
||||
);
|
||||
testcase(
|
||||
"\"fork\":{\"previous_version\":\"0x01010101\",\"current_version\":\"0x02020202\",\"epoch\":},",
|
||||
"Unable to parse body message from JSON: Error(\"expected value\", line: 1, column: 237132)"
|
||||
);
|
||||
testcase(
|
||||
"\"fork\":{\"previous_version\":\"0x01010101\",\"current_version\":\"0x02020202\",\"epoch\":\"zzz\"},",
|
||||
"Unable to parse body message from JSON: Error(\"invalid digit found in string\", line: 1, column: 237136)"
|
||||
);
|
||||
testcase(
|
||||
"\"fork\":{\"previous_version\":\"0x01010101\",\"current_version\":\"0x02020202\",\"epoch\":true},",
|
||||
"Unable to parse body message from JSON: Error(\"invalid type: boolean `true`, expected a quoted or unquoted integer\", line: 1, column: 237135)"
|
||||
);
|
||||
testcase(
|
||||
"\"fork\":{\"previous_version\":\"0x01010101\",\"current_version\":\"0x02020202\",\"epoch\":[\"a\"]},",
|
||||
"Unable to parse body message from JSON: Error(\"invalid type: sequence, expected a quoted or unquoted integer\", line: 1, column: 237132)"
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_field_fork() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let test_block_body = get_test_block_body(0xc137).replace(
|
||||
"\"fork\":{\"previous_version\":\"0x01010101\",\"current_version\":\"0x02020202\",\"epoch\":\"1545\"},",
|
||||
"",
|
||||
);
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(response, 400, "Unable to parse body message from JSON: Error(\"missing field `fork`\", line: 1, column: 237147)");
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_field_data() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let test_block_body = get_test_block_body(0xc137).replace("\"data\":", "\"not-data\":");
|
||||
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(response, 400, "Unable to parse body message from JSON: Error(\"missing field `data`\", line: 1, column: 237830)");
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_field_genesis_validators_root() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let testcase = |json_patch, expected_err| {
|
||||
let test_block_body = get_test_block_body(0xc137).replace(
|
||||
",\"genesis_validators_root\":\"0x000000000000000000000000000000000000000000000000000000000000c137\"",
|
||||
&format!(",\"genesis_validators_root\":{}", json_patch),
|
||||
);
|
||||
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(response, 400, expected_err);
|
||||
};
|
||||
|
||||
testcase("\"0\"", "Unable to parse body message from JSON: Error(\"0x prefix is missing\", line: 1, column: 237168)");
|
||||
testcase("\"0x\"", "Unable to parse body message from JSON: Error(\"invalid length 0, expected a 0x-prefixed hex string with length of 64\", line: 1, column: 237169)");
|
||||
testcase("\"0xa\"", "Unable to parse body message from JSON: Error(\"invalid length 1, expected a 0x-prefixed hex string with length of 64\", line: 1, column: 237170)");
|
||||
testcase("\"deadbeef\"", "Unable to parse body message from JSON: Error(\"0x prefix is missing\", line: 1, column: 237175)");
|
||||
testcase("\"0xdeadbeefzz\"", "Unable to parse body message from JSON: Error(\"invalid length 10, expected a 0x-prefixed hex string with length of 64\", line: 1, column: 237179)");
|
||||
testcase("\"0xdeadbeef1\"", "Unable to parse body message from JSON: Error(\"invalid length 9, expected a 0x-prefixed hex string with length of 64\", line: 1, column: 237178)");
|
||||
testcase("", "Unable to parse body message from JSON: Error(\"expected value\", line: 1, column: 237166)");
|
||||
testcase("1", "Unable to parse body message from JSON: Error(\"invalid type: integer `1`, expected a 0x-prefixed hex string with length of 64\", line: 1, column: 237166)");
|
||||
testcase("true", "Unable to parse body message from JSON: Error(\"invalid type: boolean `true`, expected a 0x-prefixed hex string with length of 64\", line: 1, column: 237169)");
|
||||
testcase("{\"cats\":\"3\"}", "Unable to parse body message from JSON: Error(\"invalid type: map, expected a 0x-prefixed hex string with length of 64\", line: 1, column: 237165)");
|
||||
testcase("[\"a\"]", "Unable to parse body message from JSON: Error(\"invalid type: sequence, expected a 0x-prefixed hex string with length of 64\", line: 1, column: 237165)");
|
||||
testcase(
|
||||
"\"0x000000000000000000000000000000000000000000000000000000000000c1370\"",
|
||||
"Unable to parse body message from JSON: Error(\"invalid length 65, expected a 0x-prefixed hex string with length of 64\", line: 1, column: 237234)",
|
||||
);
|
||||
testcase(
|
||||
"\"0x000000000000000000000000000000000000000000000000000000000000c13700\"",
|
||||
"Unable to parse body message from JSON: Error(\"invalid length 66, expected a 0x-prefixed hex string with length of 64\", line: 1, column: 237235)",
|
||||
);
|
||||
testcase(
|
||||
"\"0x000000000000000000000000000000000000000000000000000000000000c1370000\"",
|
||||
"Unable to parse body message from JSON: Error(\"invalid length 68, expected a 0x-prefixed hex string with length of 64\", line: 1, column: 237237)",
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_field_genesis_validators_root() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let test_block_body = get_test_block_body(0xc137).replace(
|
||||
",\"genesis_validators_root\":\"0x000000000000000000000000000000000000000000000000000000000000c137\"",
|
||||
"",
|
||||
);
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(response, 400, "Unable to parse body message from JSON: Error(\"missing field `genesis_validators_root`\", line: 1, column: 237139)");
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
mod sign_attestation {
|
||||
use helpers::*;
|
||||
|
||||
#[test]
|
||||
fn happy_path() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
let test_attestation_body = get_test_attestation_body(0xc137);
|
||||
|
||||
let response = http_post_custom_body(&url, &test_attestation_body);
|
||||
|
||||
assert_sign_ok(response, HAPPY_PATH_ATT_SIGNATURE_C137);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn domain_mismatch() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let testcase = |json_patch, expected_err| {
|
||||
let test_attestation_body = get_test_attestation_body(0xc137).replace(
|
||||
"\"bls_domain\":\"beacon_attester\"",
|
||||
&format!("\"bls_domain\":{}", json_patch),
|
||||
);
|
||||
let response = http_post_custom_body(&url, &test_attestation_body);
|
||||
assert_sign_error(response, 400, expected_err);
|
||||
};
|
||||
|
||||
testcase(
|
||||
"\"beacon_proposer\"",
|
||||
"Unable to parse block from JSON: Error(\"data did not match any variant of untagged enum BeaconBlock\", line: 0, column: 0)"
|
||||
);
|
||||
testcase(
|
||||
"\"randao\"",
|
||||
"Unable to parse attestation from JSON: Error(\"invalid type: map, expected a quoted or unquoted integer\", line: 0, column: 0)"
|
||||
);
|
||||
testcase("\"blah\"", "Unsupported bls_domain parameter: blah");
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_or_invalid_fields_within_attestation_data() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let testcase = |json_patch, expected_err| {
|
||||
let test_attestation_body = get_test_attestation_body(0xc137).replace(
|
||||
"\"data\":{\"slot\":\"49463\",\"index\":\"49463\"",
|
||||
json_patch,
|
||||
);
|
||||
let response = http_post_custom_body(&url, &test_attestation_body);
|
||||
assert_sign_error(response, 400, expected_err);
|
||||
};
|
||||
|
||||
testcase(
|
||||
"\"data\":{\"slot\":\"a\",\"index\":49463",
|
||||
"Unable to parse attestation from JSON: Error(\"invalid digit found in string\", line: 0, column: 0)"
|
||||
);
|
||||
testcase(
|
||||
"\"data\":{\"slot\":\"\",\"index\":\"49463\"",
|
||||
"Unable to parse attestation from JSON: Error(\"cannot parse integer from empty string\", line: 0, column: 0)"
|
||||
);
|
||||
testcase(
|
||||
"\"data\":{\"slot\":-1,\"index\":\"49463\"",
|
||||
"Unable to parse attestation from JSON: Error(\"invalid type: integer `-1`, expected a quoted or unquoted integer\", line: 0, column: 0)"
|
||||
);
|
||||
testcase(
|
||||
"\"data\":{\"slot\":\"-1\",\"index\":\"49463\"",
|
||||
"Unable to parse attestation from JSON: Error(\"invalid digit found in string\", line: 0, column: 0)"
|
||||
);
|
||||
testcase(
|
||||
"\"data\":{\"index\":\"49463\"",
|
||||
"Unable to parse attestation from JSON: Error(\"missing field `slot`\", line: 0, column: 0)",
|
||||
);
|
||||
testcase(
|
||||
"\"data\":{\"slot\":\"49463\"",
|
||||
"Unable to parse attestation from JSON: Error(\"missing field `index`\", line: 0, column: 0)"
|
||||
);
|
||||
testcase(
|
||||
"\"data\":{\"slot\":\"49463\",\"index\":\"\"",
|
||||
"Unable to parse attestation from JSON: Error(\"cannot parse integer from empty string\", line: 0, column: 0)",
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
mod sign_block {
|
||||
use helpers::*;
|
||||
|
||||
#[test]
|
||||
fn happy_path() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
let test_block_body = get_test_block_body(0xc137);
|
||||
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
|
||||
assert_sign_ok(response, HAPPY_PATH_BLOCK_SIGNATURE_C137);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn domain_mismatch() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let testcase = |json_patch, expected_err| {
|
||||
let test_block_body = get_test_block_body(0xc137).replace(
|
||||
"\"bls_domain\":\"beacon_proposer\"",
|
||||
&format!("\"bls_domain\":{}", json_patch),
|
||||
);
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(response, 400, expected_err);
|
||||
};
|
||||
|
||||
testcase(
|
||||
"\"beacon_attester\"",
|
||||
"Unable to parse attestation from JSON: Error(\"missing field `index`\", line: 0, column: 0)",
|
||||
);
|
||||
testcase(
|
||||
"\"randao\"",
|
||||
"Unable to parse attestation from JSON: Error(\"invalid type: map, expected a quoted or unquoted integer\", line: 0, column: 0)"
|
||||
);
|
||||
testcase("\"blah\"", "Unsupported bls_domain parameter: blah");
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_or_invalid_fields_within_block_data() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let testcase = |json_patch, expected_err| {
|
||||
let test_block_body = get_test_block_body(0xc137).replace(
|
||||
"\"data\":{\"slot\":\"49463\",\"proposer_index\":\"0\"",
|
||||
json_patch,
|
||||
);
|
||||
let response = http_post_custom_body(&url, &test_block_body);
|
||||
assert_sign_error(response, 400, expected_err);
|
||||
};
|
||||
|
||||
testcase(
|
||||
"\"data\":{\"slot\":\"\",\"proposer_index\":\"0\"",
|
||||
"Unable to parse block from JSON: Error(\"data did not match any variant of untagged enum BeaconBlock\", line: 0, column: 0)"
|
||||
);
|
||||
testcase(
|
||||
"\"data\":{\"slot\":\"-1\",\"proposer_index\":\"0\"",
|
||||
"Unable to parse block from JSON: Error(\"data did not match any variant of untagged enum BeaconBlock\", line: 0, column: 0)"
|
||||
);
|
||||
testcase(
|
||||
"\"data\":{\"proposer_index\":\"0\"",
|
||||
"Unable to parse block from JSON: Error(\"data did not match any variant of untagged enum BeaconBlock\", line: 0, column: 0)"
|
||||
);
|
||||
testcase(
|
||||
"\"data\":{\"slot\":\"49463\"",
|
||||
"Unable to parse block from JSON: Error(\"data did not match any variant of untagged enum BeaconBlock\", line: 0, column: 0)"
|
||||
);
|
||||
testcase(
|
||||
"\"data\":{\"slot\":\"49463\",\"proposer_index\":\"\"",
|
||||
"Unable to parse block from JSON: Error(\"data did not match any variant of untagged enum BeaconBlock\", line: 0, column: 0)"
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
mod sign_randao {
|
||||
|
||||
use helpers::*;
|
||||
|
||||
#[test]
|
||||
fn happy_path() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
let test_randao_body = get_test_randao_body(0xc137);
|
||||
|
||||
let response = http_post_custom_body(&url, &test_randao_body);
|
||||
assert_sign_ok(response, HAPPY_PATH_RANDAO_SIGNATURE_C137);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn domain_mismatch() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let testcase = |json_patch, expected_err| {
|
||||
let test_randao_body = get_test_randao_body(0xc137).replace(
|
||||
"\"bls_domain\":\"randao\"",
|
||||
&format!("\"bls_domain\":{}", json_patch),
|
||||
);
|
||||
let response = http_post_custom_body(&url, &test_randao_body);
|
||||
assert_sign_error(response, 400, expected_err);
|
||||
};
|
||||
|
||||
testcase(
|
||||
"\"beacon_proposer\"",
|
||||
"Unable to parse block from JSON: Error(\"data did not match any variant of untagged enum BeaconBlock\", line: 0, column: 0)",
|
||||
);
|
||||
testcase(
|
||||
"\"beacon_attester\"",
|
||||
"Unable to parse attestation from JSON: Error(\"invalid type: string \"49463\", expected struct AttestationData\", line: 0, column: 0)"
|
||||
);
|
||||
testcase("\"blah\"", "Unsupported bls_domain parameter: blah");
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_field_data() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_to_sign_message();
|
||||
let url = format!("{}/sign/{}", test_signer.address, PUBLIC_KEY_1);
|
||||
|
||||
let testcase = |json_patch, expected_err| {
|
||||
let test_randao_body = get_test_randao_body(0xc137)
|
||||
.replace(",\"data\":\"49463\"", &format!(",\"data\":{}", json_patch));
|
||||
let response = http_post_custom_body(&url, &test_randao_body);
|
||||
assert_sign_error(response, 400, expected_err);
|
||||
};
|
||||
|
||||
testcase(
|
||||
"",
|
||||
"Unable to parse body message from JSON: Error(\"expected value\", line: 1, column: 31)"
|
||||
);
|
||||
testcase(
|
||||
"\"\"",
|
||||
"Unable to parse attestation from JSON: Error(\"cannot parse integer from empty string\", line: 0, column: 0)"
|
||||
);
|
||||
testcase(
|
||||
"\"-1\"",
|
||||
"Unable to parse attestation from JSON: Error(\"invalid digit found in string\", line: 0, column: 0)"
|
||||
);
|
||||
testcase(
|
||||
"true",
|
||||
"Unable to parse attestation from JSON: Error(\"invalid type: boolean `true`, expected a quoted or unquoted integer\", line: 0, column: 0)"
|
||||
);
|
||||
testcase(
|
||||
"{\"cats\":\"3\"}",
|
||||
"Unable to parse attestation from JSON: Error(\"invalid type: map, expected a quoted or unquoted integer\", line: 0, column: 0)",
|
||||
);
|
||||
testcase(
|
||||
"[\"a\"]",
|
||||
"Unable to parse attestation from JSON: Error(\"invalid type: sequence, expected a quoted or unquoted integer\", line: 0, column: 0)"
|
||||
);
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
mod upcheck {
|
||||
use helpers::*;
|
||||
|
||||
#[test]
|
||||
fn happy_path() {
|
||||
let (test_signer, _tmp_dir) = set_up_api_test_signer_raw_dir();
|
||||
|
||||
let url = format!("{}/upcheck", test_signer.address);
|
||||
|
||||
let resp = http_get(&url);
|
||||
assert_eq!(resp.status, 200);
|
||||
assert_eq!(resp.json["status"], "OK");
|
||||
|
||||
test_signer.shutdown();
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
[package]
|
||||
name = "remote_signer_test"
|
||||
version = "0.2.0"
|
||||
authors = ["Herman Junge <herman@sigmaprime.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.3"
|
||||
environment = { path = "../../lighthouse/environment" }
|
||||
hex = "0.4.2"
|
||||
httpmock = "0.5.8"
|
||||
remote_signer_client = { path = "../../remote_signer/client" }
|
||||
remote_signer_consumer = { path = "../../common/remote_signer_consumer" }
|
||||
reqwest = { version = "0.11.0", features = ["blocking", "json"] }
|
||||
serde = { version = "1.0.116", features = ["derive"] }
|
||||
serde_json = "1.0.58"
|
||||
tempfile = "3.1.0"
|
||||
tokio = { version = "1.10.0", features = ["time"] }
|
||||
types = { path = "../../consensus/types" }
|
||||
sensitive_url = { path = "../../common/sensitive_url" }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "~0.3.5"
|
||||
windows-acl = "~0.3.0"
|
||||
|
@ -1,141 +0,0 @@
|
||||
use crate::*;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use environment::{Environment, EnvironmentBuilder};
|
||||
pub use local_signer_test_data::*;
|
||||
use remote_signer_client::Client;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use tempfile::{Builder as TempBuilder, TempDir};
|
||||
use types::EthSpec;
|
||||
|
||||
pub struct ApiTestSigner<E: EthSpec> {
|
||||
pub address: String,
|
||||
environment: Environment<E>,
|
||||
}
|
||||
|
||||
pub struct ApiTestResponse {
|
||||
pub status: u16,
|
||||
pub json: Value,
|
||||
}
|
||||
|
||||
impl ApiTestSigner<E> {
|
||||
pub fn new(arg_vec: Vec<&str>) -> Self {
|
||||
let matches = set_matches(arg_vec);
|
||||
let mut environment = get_environment(false);
|
||||
let runtime_context = environment.core_context();
|
||||
|
||||
let client = environment
|
||||
.runtime()
|
||||
.block_on(Client::new(runtime_context, &matches))
|
||||
.map_err(|e| format!("Failed to init Rest API: {}", e))
|
||||
.unwrap();
|
||||
|
||||
let address = get_address(&client);
|
||||
|
||||
Self {
|
||||
address,
|
||||
environment,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shutdown(mut self) {
|
||||
self.environment.fire_signal()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_matches(arg_vec: Vec<&str>) -> ArgMatches<'static> {
|
||||
let matches = App::new("BLS_Remote_Signer")
|
||||
.arg(
|
||||
Arg::with_name("storage-raw-dir")
|
||||
.long("storage-raw-dir")
|
||||
.value_name("DIR"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("port")
|
||||
.long("port")
|
||||
.value_name("PORT")
|
||||
.default_value("9000")
|
||||
.takes_value(true),
|
||||
);
|
||||
|
||||
matches.get_matches_from(arg_vec)
|
||||
}
|
||||
|
||||
pub fn get_environment(is_log_active: bool) -> Environment<E> {
|
||||
let environment_builder = EnvironmentBuilder::mainnet();
|
||||
|
||||
let builder = if is_log_active {
|
||||
environment_builder.async_logger("info", None).unwrap()
|
||||
} else {
|
||||
environment_builder.null_logger().unwrap()
|
||||
};
|
||||
|
||||
builder
|
||||
.multi_threaded_tokio_runtime()
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn set_up_api_test_signer_raw_dir() -> (ApiTestSigner<E>, TempDir) {
|
||||
let tmp_dir = TempBuilder::new()
|
||||
.prefix("bls-remote-signer-test")
|
||||
.tempdir()
|
||||
.unwrap();
|
||||
let arg_vec = vec![
|
||||
"this_test",
|
||||
"--port",
|
||||
"0",
|
||||
"--storage-raw-dir",
|
||||
tmp_dir.path().to_str().unwrap(),
|
||||
];
|
||||
let test_signer = ApiTestSigner::new(arg_vec);
|
||||
|
||||
(test_signer, tmp_dir)
|
||||
}
|
||||
|
||||
pub fn set_up_api_test_signer_to_sign_message() -> (ApiTestSigner<E>, TempDir) {
|
||||
let (test_signer, tmp_dir) = set_up_api_test_signer_raw_dir();
|
||||
add_sub_dirs(&tmp_dir);
|
||||
add_key_files(&tmp_dir);
|
||||
add_non_key_files(&tmp_dir);
|
||||
add_mismatched_key_file(&tmp_dir);
|
||||
add_invalid_secret_key_file(&tmp_dir);
|
||||
|
||||
(test_signer, tmp_dir)
|
||||
}
|
||||
|
||||
pub fn http_get(url: &str) -> ApiTestResponse {
|
||||
let response = reqwest::blocking::get(url).unwrap();
|
||||
|
||||
ApiTestResponse {
|
||||
status: response.status().as_u16(),
|
||||
json: serde_json::from_str(&response.text().unwrap()).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn http_post(url: &str, hashmap: HashMap<&str, &str>) -> ApiTestResponse {
|
||||
let response = reqwest::blocking::Client::new()
|
||||
.post(url)
|
||||
.json(&hashmap)
|
||||
.send()
|
||||
.unwrap();
|
||||
|
||||
ApiTestResponse {
|
||||
status: response.status().as_u16(),
|
||||
json: serde_json::from_str(&response.text().unwrap()).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn http_post_custom_body(url: &str, body: &str) -> ApiTestResponse {
|
||||
let response = reqwest::blocking::Client::new()
|
||||
.post(url)
|
||||
.body(body.to_string())
|
||||
.send()
|
||||
.unwrap();
|
||||
|
||||
ApiTestResponse {
|
||||
status: response.status().as_u16(),
|
||||
json: serde_json::from_str(&response.text().unwrap()).unwrap(),
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
// Legit BLS pairs.
|
||||
pub const PUBLIC_KEY_1: &str = "b7354252aa5bce27ab9537fd0158515935f3c3861419e1b4b6c8219b5dbd15fcf907bddf275442f3e32f904f79807a2a";
|
||||
pub const SECRET_KEY_1: &str = "68081afeb7ad3e8d469f87010804c3e8d53ef77d393059a55132637206cc59ec";
|
||||
|
||||
pub const PUBLIC_KEY_1_BYTES: [u8; 48] = [
|
||||
183, 53, 66, 82, 170, 91, 206, 39, 171, 149, 55, 253, 1, 88, 81, 89, 53, 243, 195, 134, 20, 25,
|
||||
225, 180, 182, 200, 33, 155, 93, 189, 21, 252, 249, 7, 189, 223, 39, 84, 66, 243, 227, 47, 144,
|
||||
79, 121, 128, 122, 42,
|
||||
];
|
||||
|
||||
pub const SECRET_KEY_1_BYTES: [u8; 32] = [
|
||||
104, 8, 26, 254, 183, 173, 62, 141, 70, 159, 135, 1, 8, 4, 195, 232, 213, 62, 247, 125, 57, 48,
|
||||
89, 165, 81, 50, 99, 114, 6, 204, 89, 236,
|
||||
];
|
||||
|
||||
pub const PUBLIC_KEY_2: &str = "9324739760579527b4f8c34c5df42f9fd89f59fdbe8a01d58675769f60fec5da9b9c8d7a3203cf2217692e49e7b98d97";
|
||||
pub const SECRET_KEY_2: &str = "45b5e876e5e57b23af3e86c37d708626cf1dcca6a650091bba2ddb3e0b7304ae";
|
||||
|
||||
pub const PUBLIC_KEY_3: &str = "8244ac66a8bffa0ce0af04d69ed7ed009951061259173a7c7ae1f25c049f0fcbbf2fad67b6d2b276a697315be755dac5";
|
||||
pub const SECRET_KEY_3: &str = "1e52a4e54e89ccba813d5f902545749c356f6187341b4e765bf43ece401762f6";
|
||||
|
||||
// It is valid (from 0731e07e99a0b1c69f0de13ad65e5c374e72d0a997d43387ad70448485879ca1),
|
||||
// But we are not uploading it.
|
||||
pub const ABSENT_PUBLIC_KEY: &str = "827803e94e4b8d306735df9002465b310fabb39802341dc5c616a204e4e8dc7dbb6caa4733b5da54f8cdeec7788e7500";
|
||||
|
||||
// This is the public key of 0e5faaa97a63929cecb8597949ae148c0607f1b30bd057a7487efeb4c701fbf8.
|
||||
pub const MISMATCHED_PUBLIC_KEY: &str = "83d40dfb1cbcf2a55c139faa3feec14bdae92dd485009ac8c5670d241f71c2ce064afa48dbaf091e16d0e4356038b948";
|
||||
|
||||
// The valid secret key is 3d703bd0dfdf2abb925b2d6bf1adf045ce8d93b8baff07e3313c5e150b043e89
|
||||
pub const PUBLIC_KEY_FOR_INVALID_SECRET_KEY: &str = "aac313c0bc04880c4e9f4b0b69a9f310b09b9325027666cc7f255f88c7f35b82a82b2aa004c9be655b5696fea67f7300";
|
||||
pub const INVALID_SECRET_KEY: &str = "WubbaLubbaDubDub";
|
||||
|
||||
// This is the public key of 34e62afe7c4402009a46bf8af574f9d6701c2cf72b3868eeeb59dfa6e7ff6bcf.
|
||||
pub const SUB_DIR_NAME: &str = "aadbe2d5c0316dd3c9a522029f332cde578730e61d759685d7ad3bf1166c5f0bf094c3abc105384506f052e2b7a1bae0";
|
||||
|
||||
// Silly files with long names (96 chars) to fill your BLS raw file directory.
|
||||
pub const SILLY_FILE_NAME_1: &str =
|
||||
"IAmAdamPrinceofEterniaDefenderofthesecretsoftheCastleGrayskullThisisCringermyfearlessfriendFabul";
|
||||
pub const SILLY_CONTENT_1: &str = "HemanandtheMastersoftheUniverse";
|
||||
|
||||
pub const SILLY_FILE_NAME_2: &str =
|
||||
"InthenearfutureDocTerrorandhiscyborgcompanionHackerunleashtheirforcestoconquerEarthOnlyoneforcec";
|
||||
pub const SILLY_CONTENT_2: &str = "Centurions";
|
||||
|
||||
pub const SILLY_FILE_NAME_3: &str =
|
||||
"OurworldisinperilGaiathespiritoftheearthcannolongerstandtheterribledestructionplaguingourplanetS";
|
||||
pub const SILLY_CONTENT_3: &str = "CaptainPlanet";
|
||||
|
||||
// Taken from some random string.
|
||||
pub const SIGNING_ROOT: &str = "b6bb8f3765f93f4f1e7c7348479289c9261399a3c6906685e320071a1a13955c";
|
||||
|
||||
pub const SIGNING_ROOT_BYTES: [u8; 32] = [
|
||||
182, 187, 143, 55, 101, 249, 63, 79, 30, 124, 115, 72, 71, 146, 137, 201, 38, 19, 153, 163,
|
||||
198, 144, 102, 133, 227, 32, 7, 26, 26, 19, 149, 92,
|
||||
];
|
||||
|
||||
// Expected signature for the message 0xb6bb8f3765f93f4f1e7c7348479289c9261399a3c6906685e320071a1a13955c
|
||||
// using 68081afeb7ad3e8d469f87010804c3e8d53ef77d393059a55132637206cc59ec as secret key
|
||||
pub const EXPECTED_SIGNATURE_1: &str = "0xb5d0c01cef3b028e2c5f357c2d4b886f8e374d09dd660cd7dd14680d4f956778808b4d3b2ab743e890fc1a77ae62c3c90d613561b23c6adaeb5b0e288832304fddc08c7415080be73e556e8862a1b4d0f6aa8084e34a901544d5bb6aeed3a612";
|
||||
|
||||
pub const EXPECTED_SIGNATURE_1_BYTES: [u8; 96] = [
|
||||
181, 208, 192, 28, 239, 59, 2, 142, 44, 95, 53, 124, 45, 75, 136, 111, 142, 55, 77, 9, 221,
|
||||
102, 12, 215, 221, 20, 104, 13, 79, 149, 103, 120, 128, 139, 77, 59, 42, 183, 67, 232, 144,
|
||||
252, 26, 119, 174, 98, 195, 201, 13, 97, 53, 97, 178, 60, 106, 218, 235, 91, 14, 40, 136, 50,
|
||||
48, 79, 221, 192, 140, 116, 21, 8, 11, 231, 62, 85, 110, 136, 98, 161, 180, 208, 246, 170, 128,
|
||||
132, 227, 74, 144, 21, 68, 213, 187, 106, 238, 211, 166, 18,
|
||||
];
|
||||
|
||||
// Expected signature for the message 0xb6bb8f3765f93f4f1e7c7348479289c9261399a3c6906685e320071a1a13955c
|
||||
// using 45b5e876e5e57b23af3e86c37d708626cf1dcca6a650091bba2ddb3e0b7304ae as secret key
|
||||
pub const EXPECTED_SIGNATURE_2: &str = "0xb6b63e3cecd0967d9f9b90e3ee113dfb21ecd3901dbc654ca69649ac5a0746758661306627f18bb6d7a6ea03ace069500ee79a28154c172dd71ffe4b711875e48b60466a90f3a4dcacdbc9b5f5434ad68c91e603fe1703324d83617f5270aead";
|
||||
|
||||
pub const EXPECTED_SIGNATURE_2_BYTES: [u8; 96] = [
|
||||
182, 182, 62, 60, 236, 208, 150, 125, 159, 155, 144, 227, 238, 17, 61, 251, 33, 236, 211, 144,
|
||||
29, 188, 101, 76, 166, 150, 73, 172, 90, 7, 70, 117, 134, 97, 48, 102, 39, 241, 139, 182, 215,
|
||||
166, 234, 3, 172, 224, 105, 80, 14, 231, 154, 40, 21, 76, 23, 45, 215, 31, 254, 75, 113, 24,
|
||||
117, 228, 139, 96, 70, 106, 144, 243, 164, 220, 172, 219, 201, 181, 245, 67, 74, 214, 140, 145,
|
||||
230, 3, 254, 23, 3, 50, 77, 131, 97, 127, 82, 112, 174, 173,
|
||||
];
|
||||
|
||||
// Expected signature for the message 0xb6bb8f3765f93f4f1e7c7348479289c9261399a3c6906685e320071a1a13955c
|
||||
// using 1e52a4e54e89ccba813d5f902545749c356f6187341b4e765bf43ece401762f6 as secret key
|
||||
pub const EXPECTED_SIGNATURE_3: &str = "0x874f7d6d4174df1088ab40bd9a3c808554c55d6de1dffcacc7ef56c3ca22e20b52a23dd5bb6568a123b59df0bacef3de14d4c197a2fb2a5868a18c4b11f6d7957673d9a302bf6812b1d5df9b264504f682b43dfbcf4f9130cb5ebb9b8e3737de";
|
||||
|
||||
pub const EXPECTED_SIGNATURE_3_BYTES: [u8; 96] = [
|
||||
135, 79, 125, 109, 65, 116, 223, 16, 136, 171, 64, 189, 154, 60, 128, 133, 84, 197, 93, 109,
|
||||
225, 223, 252, 172, 199, 239, 86, 195, 202, 34, 226, 11, 82, 162, 61, 213, 187, 101, 104, 161,
|
||||
35, 181, 157, 240, 186, 206, 243, 222, 20, 212, 193, 151, 162, 251, 42, 88, 104, 161, 140, 75,
|
||||
17, 246, 215, 149, 118, 115, 217, 163, 2, 191, 104, 18, 177, 213, 223, 155, 38, 69, 4, 246,
|
||||
130, 180, 61, 251, 207, 79, 145, 48, 203, 94, 187, 155, 142, 55, 55, 222,
|
||||
];
|
||||
|
||||
// These HAPPY_PATH constants were obtained running "sanity check" tests. i.e. Usign the non-remote way for producing the signature.
|
||||
pub const HAPPY_PATH_BLOCK_SIGNATURE_C137: &str = "0x87c2a5bbd71d6277802e8bf5319b84c9fbb7441c4ce56dc39e721ba4371e7521a04a36c166c5cba37e8c645e91cc31fc02884bf44fdeb51c52173e642934fd3f3e8f72f53c2ec7da284630dc86e49da75cb2578761403ed24c3a8e4bccf33e4c";
|
||||
|
||||
pub const HAPPY_PATH_ATT_SIGNATURE_C137: &str = "0xada13507d81feb5a5057565f45abed9248be56a463efa944598090bdcdd61c3fa51bb5ef34f845100efe0c14dc0c0fa20d068a7ea4f14c3e9b43aa1c44f14cb73371e48338a90622b0bee4c3a988b726d3ad87ea8a111115a0d6e1e908c421d8";
|
||||
|
||||
pub const HAPPY_PATH_RANDAO_SIGNATURE_C137: &str = "0x8c5c88491486d0c4572e4043fd42b3f774778c6f9c44464b61272c9667d18f3ca894ae08344fcd7f6dd8b6954572b90a10ce7457367cecaa9f6ef7bf105aa2e79ae7e9568317d933ac2a8e45fb06f3edfc3f6f5881ca96c8eed0c2a83fa9bc2d";
|
@ -1,152 +0,0 @@
|
||||
use crate::*;
|
||||
use remote_signer_client::api_response::SignatureApiResponse;
|
||||
use remote_signer_consumer::{Error, RemoteSignerHttpConsumer, RemoteSignerObject};
|
||||
use reqwest::ClientBuilder;
|
||||
use sensitive_url::SensitiveUrl;
|
||||
use serde::Serialize;
|
||||
use tokio::runtime::Builder;
|
||||
use tokio::time::Duration;
|
||||
use types::{AttestationData, BeaconBlock, Epoch, EthSpec, Fork, Hash256};
|
||||
|
||||
pub fn set_up_test_consumer(test_signer_address: &str) -> RemoteSignerHttpConsumer {
|
||||
set_up_test_consumer_with_timeout(test_signer_address, 12)
|
||||
}
|
||||
|
||||
pub fn set_up_test_consumer_with_timeout(
|
||||
test_signer_address: &str,
|
||||
timeout: u64,
|
||||
) -> RemoteSignerHttpConsumer {
|
||||
let url = SensitiveUrl::parse(test_signer_address).unwrap();
|
||||
let reqwest_client = ClientBuilder::new()
|
||||
.timeout(Duration::from_secs(timeout))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
RemoteSignerHttpConsumer::from_components(url, reqwest_client)
|
||||
}
|
||||
|
||||
pub fn do_sign_request<E: EthSpec, T: RemoteSignerObject>(
|
||||
test_client: &RemoteSignerHttpConsumer,
|
||||
test_input: RemoteSignerTestData<E, T>,
|
||||
) -> Result<String, Error> {
|
||||
let runtime = Builder::new_multi_thread().enable_all().build().unwrap();
|
||||
|
||||
runtime.block_on(test_client.sign(
|
||||
&test_input.public_key,
|
||||
test_input.bls_domain,
|
||||
test_input.data,
|
||||
test_input.fork,
|
||||
test_input.genesis_validators_root,
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct BlockRequestBody<E: EthSpec> {
|
||||
bls_domain: String,
|
||||
data: BeaconBlock<E>,
|
||||
fork: Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
}
|
||||
|
||||
pub fn get_test_block_body(seed: u64) -> String {
|
||||
let block: BeaconBlock<E> = get_block(seed);
|
||||
let epoch = block.epoch();
|
||||
|
||||
let fork = Fork {
|
||||
previous_version: [1; 4],
|
||||
current_version: [2; 4],
|
||||
epoch,
|
||||
};
|
||||
|
||||
let genesis_validators_root = Hash256::from_low_u64_be(seed);
|
||||
|
||||
let block_request_body = BlockRequestBody {
|
||||
bls_domain: "beacon_proposer".to_string(),
|
||||
data: block,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
};
|
||||
|
||||
serde_json::to_string(&block_request_body).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AttestationRequestBody {
|
||||
bls_domain: String,
|
||||
data: AttestationData,
|
||||
fork: Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
}
|
||||
|
||||
pub fn get_test_attestation_body(seed: u64) -> String {
|
||||
let attestation = get_attestation::<E>(seed);
|
||||
let epoch = attestation.target.epoch;
|
||||
|
||||
let fork = Fork {
|
||||
previous_version: [1; 4],
|
||||
current_version: [2; 4],
|
||||
epoch,
|
||||
};
|
||||
|
||||
let genesis_validators_root = Hash256::from_low_u64_be(seed);
|
||||
|
||||
let attestation_request_body = AttestationRequestBody {
|
||||
bls_domain: "beacon_attester".to_string(),
|
||||
data: attestation,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
};
|
||||
|
||||
serde_json::to_string(&attestation_request_body).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct RandaoRequestBody {
|
||||
bls_domain: String,
|
||||
data: Epoch,
|
||||
fork: Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
}
|
||||
|
||||
pub fn get_test_randao_body(seed: u64) -> String {
|
||||
let epoch = Epoch::new(seed);
|
||||
|
||||
let fork = Fork {
|
||||
previous_version: [1; 4],
|
||||
current_version: [2; 4],
|
||||
epoch,
|
||||
};
|
||||
|
||||
let genesis_validators_root = Hash256::from_low_u64_be(seed);
|
||||
|
||||
let randao_request_body = RandaoRequestBody {
|
||||
bls_domain: "randao".to_string(),
|
||||
data: epoch,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
};
|
||||
|
||||
serde_json::to_string(&randao_request_body).unwrap()
|
||||
}
|
||||
|
||||
pub fn assert_sign_ok(resp: ApiTestResponse, expected_signature: &str) {
|
||||
assert_eq!(resp.status, 200);
|
||||
assert_eq!(
|
||||
serde_json::from_value::<SignatureApiResponse>(resp.json)
|
||||
.unwrap()
|
||||
.signature,
|
||||
expected_signature
|
||||
);
|
||||
}
|
||||
|
||||
pub fn assert_sign_error(resp: ApiTestResponse, http_status: u16, error_msg: &str) {
|
||||
assert_eq!(resp.status, http_status);
|
||||
assert_eq!(
|
||||
resp.json["error"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
// cross-platform compatiblity
|
||||
.replace("\\", ""),
|
||||
error_msg
|
||||
);
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
mod api_test_signer;
|
||||
mod constants;
|
||||
mod consumer;
|
||||
mod local_signer_test_data;
|
||||
mod mock;
|
||||
mod remote_signer_test_data;
|
||||
mod utils;
|
||||
|
||||
pub use api_test_signer::*;
|
||||
pub use constants::*;
|
||||
pub use consumer::*;
|
||||
pub use local_signer_test_data::*;
|
||||
pub use mock::*;
|
||||
pub use remote_signer_test_data::*;
|
||||
use types::MainnetEthSpec;
|
||||
pub use utils::*;
|
||||
|
||||
pub type E = MainnetEthSpec;
|
@ -1,103 +0,0 @@
|
||||
use crate::*;
|
||||
use hex::decode;
|
||||
use remote_signer_consumer::RemoteSignerObject;
|
||||
use std::mem;
|
||||
use types::{
|
||||
AttestationData, BeaconBlock, ChainSpec, Domain, Epoch, EthSpec, Fork, Hash256, SecretKey,
|
||||
SignedRoot,
|
||||
};
|
||||
|
||||
pub struct LocalSignerTestData<T: RemoteSignerObject> {
|
||||
secret_key: SecretKey,
|
||||
spec: ChainSpec,
|
||||
fork: Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
obj: T,
|
||||
}
|
||||
|
||||
impl<T: RemoteSignerObject> LocalSignerTestData<T> {
|
||||
pub fn new(obj: T) -> Self {
|
||||
let epoch = obj.get_epoch();
|
||||
|
||||
Self {
|
||||
secret_key: SecretKey::deserialize(&decode(SECRET_KEY_1).unwrap()).unwrap(),
|
||||
spec: E::default_spec(),
|
||||
fork: Fork {
|
||||
previous_version: [1; 4],
|
||||
current_version: [2; 4],
|
||||
epoch,
|
||||
},
|
||||
genesis_validators_root: Hash256::from_low_u64_be(0xc137),
|
||||
obj,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalSignerTestData<BeaconBlock<E>> {
|
||||
pub fn sign(&self) -> String {
|
||||
let signed_block = self.obj.clone().sign(
|
||||
&self.secret_key,
|
||||
&self.fork,
|
||||
self.genesis_validators_root,
|
||||
&self.spec,
|
||||
);
|
||||
|
||||
signed_block.signature().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalSignerTestData<AttestationData> {
|
||||
pub fn sign(&self) -> String {
|
||||
let domain = self.spec.get_domain(
|
||||
self.obj.target.epoch,
|
||||
Domain::BeaconAttester,
|
||||
&self.fork,
|
||||
self.genesis_validators_root,
|
||||
);
|
||||
|
||||
let message = self.obj.signing_root(domain);
|
||||
let signature = &self.secret_key.sign(message);
|
||||
|
||||
signature.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalSignerTestData<Epoch> {
|
||||
pub fn sign(&self) -> String {
|
||||
let domain = self.spec.get_domain(
|
||||
self.obj,
|
||||
Domain::Randao,
|
||||
&self.fork,
|
||||
self.genesis_validators_root,
|
||||
);
|
||||
|
||||
let message = self.obj.signing_root(domain);
|
||||
let signature = &self.secret_key.sign(message);
|
||||
|
||||
signature.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_input_local_signer_block(seed: u64) -> LocalSignerTestData<BeaconBlock<E>> {
|
||||
let block: BeaconBlock<E>;
|
||||
|
||||
unsafe {
|
||||
block = mem::transmute(get_block::<E>(seed));
|
||||
}
|
||||
|
||||
LocalSignerTestData::new(block)
|
||||
}
|
||||
|
||||
pub fn get_input_local_signer_attestation(seed: u64) -> LocalSignerTestData<AttestationData> {
|
||||
let attestation: AttestationData;
|
||||
|
||||
unsafe {
|
||||
attestation = mem::transmute(get_attestation::<E>(seed));
|
||||
}
|
||||
|
||||
LocalSignerTestData::new(attestation)
|
||||
}
|
||||
|
||||
pub fn get_input_local_signer_randao(seed: u64) -> LocalSignerTestData<Epoch> {
|
||||
LocalSignerTestData::new(Epoch::new(seed))
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
use crate::*;
|
||||
use httpmock::{Method::POST, MockServer};
|
||||
use tokio::time::Duration;
|
||||
|
||||
pub fn set_up_mock_server(status: u16, body: &str) -> MockServer {
|
||||
set_up_mock_server_with_timeout(status, body, 0)
|
||||
}
|
||||
|
||||
pub fn set_up_mock_server_with_timeout(status: u16, body: &str, delay: u64) -> MockServer {
|
||||
let server = MockServer::start();
|
||||
|
||||
server.mock(|when, then| {
|
||||
when.method(POST).path(format!("/sign/{}", PUBLIC_KEY_1));
|
||||
then.status(status)
|
||||
.delay(Duration::from_secs(delay))
|
||||
.body(body);
|
||||
});
|
||||
|
||||
server
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
use crate::*;
|
||||
use remote_signer_consumer::RemoteSignerObject;
|
||||
use std::marker::PhantomData;
|
||||
use types::{AttestationData, BeaconBlock, Domain, Epoch, EthSpec, Fork, Hash256};
|
||||
|
||||
pub struct RemoteSignerTestData<E: EthSpec, T: RemoteSignerObject> {
|
||||
pub public_key: String,
|
||||
pub bls_domain: Domain,
|
||||
pub data: T,
|
||||
pub fork: Fork,
|
||||
pub genesis_validators_root: Hash256,
|
||||
|
||||
_phantom: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<'a, E: EthSpec, T: RemoteSignerObject> RemoteSignerTestData<E, T> {
|
||||
pub fn new(public_key: &str, data: T, bls_domain: Domain) -> Self {
|
||||
let epoch = data.get_epoch();
|
||||
|
||||
Self {
|
||||
public_key: public_key.to_string(),
|
||||
bls_domain,
|
||||
data,
|
||||
fork: Fork {
|
||||
previous_version: [1; 4],
|
||||
current_version: [2; 4],
|
||||
epoch,
|
||||
},
|
||||
genesis_validators_root: Hash256::from_low_u64_be(0xc137),
|
||||
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_input_data_block(seed: u64) -> RemoteSignerTestData<E, BeaconBlock<E>> {
|
||||
let block = get_block::<E>(seed);
|
||||
RemoteSignerTestData::new(PUBLIC_KEY_1, block, Domain::BeaconProposer)
|
||||
}
|
||||
|
||||
pub fn get_input_data_attestation(seed: u64) -> RemoteSignerTestData<E, AttestationData> {
|
||||
let attestation = get_attestation::<E>(seed);
|
||||
RemoteSignerTestData::new(PUBLIC_KEY_1, attestation, Domain::BeaconAttester)
|
||||
}
|
||||
|
||||
pub fn get_input_data_randao(seed: u64) -> RemoteSignerTestData<E, Epoch> {
|
||||
let epoch = Epoch::new(seed);
|
||||
RemoteSignerTestData::new(PUBLIC_KEY_1, epoch, Domain::Randao)
|
||||
}
|
||||
|
||||
pub fn get_input_data_and_set_domain<E: EthSpec, T: RemoteSignerObject>(
|
||||
f: fn(u64) -> RemoteSignerTestData<E, T>,
|
||||
bls_domain: Domain,
|
||||
) -> RemoteSignerTestData<E, T> {
|
||||
let mut test_input = f(0xc137);
|
||||
test_input.bls_domain = bls_domain;
|
||||
|
||||
test_input
|
||||
}
|
||||
|
||||
pub fn get_input_data_and_set_public_key<E: EthSpec, T: RemoteSignerObject>(
|
||||
f: fn(u64) -> RemoteSignerTestData<E, T>,
|
||||
p: &str,
|
||||
) -> RemoteSignerTestData<E, T> {
|
||||
let mut test_input = f(0xc137);
|
||||
test_input.public_key = p.to_string();
|
||||
|
||||
test_input
|
||||
}
|
@ -1,302 +0,0 @@
|
||||
use crate::*;
|
||||
pub use constants::*;
|
||||
pub use consumer::*;
|
||||
pub use local_signer_test_data::*;
|
||||
pub use mock::*;
|
||||
use remote_signer_client::Client;
|
||||
pub use remote_signer_test_data::*;
|
||||
use std::fs::{create_dir, File};
|
||||
use std::io::Write;
|
||||
use std::net::IpAddr::{V4, V6};
|
||||
use std::path::Path;
|
||||
use tempfile::TempDir;
|
||||
use types::{
|
||||
AggregateSignature, Attestation, AttestationData, AttesterSlashing, BeaconBlock,
|
||||
BeaconBlockHeader, BitList, Checkpoint, Deposit, DepositData, Epoch, EthSpec, FixedVector,
|
||||
Hash256, IndexedAttestation, ProposerSlashing, PublicKeyBytes, Signature, SignatureBytes,
|
||||
SignedBeaconBlockHeader, SignedVoluntaryExit, Slot, Unsigned, VariableList, VoluntaryExit,
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use winapi::um::winnt::{
|
||||
FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_READ_ATTRIBUTES, FILE_READ_EA, READ_CONTROL,
|
||||
STANDARD_RIGHTS_ALL, SYNCHRONIZE, WRITE_DAC,
|
||||
};
|
||||
|
||||
/// This is the security identifier in Windows for the owner of a file. See:
|
||||
/// - https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/security-identifiers-in-windows#well-known-sids-all-versions-of-windows
|
||||
#[cfg(windows)]
|
||||
const OWNER_SID_STR: &str = "S-1-3-4";
|
||||
/// We don't need any of the `AceFlags` listed here:
|
||||
/// - https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header
|
||||
#[cfg(windows)]
|
||||
const OWNER_ACL_ENTRY_FLAGS: u8 = 0;
|
||||
/// See here for explanation:
|
||||
/// - https://docs.microsoft.com/en-us/windows/win32/wmisdk/file-and-directory-access-rights-constants
|
||||
#[cfg(windows)]
|
||||
const OWNER_ACL_ENTRY_RESTRICT_MASK: u32 =
|
||||
FILE_READ_ATTRIBUTES | FILE_READ_EA | READ_CONTROL | WRITE_DAC | SYNCHRONIZE;
|
||||
/// Generic Rights:
|
||||
/// - https://docs.microsoft.com/en-us/windows/win32/fileio/file-security-and-access-rights
|
||||
/// STANDARD_RIGHTS_ALL
|
||||
/// - https://docs.microsoft.com/en-us/windows/win32/secauthz/access-mask
|
||||
#[cfg(windows)]
|
||||
const OWNER_ACL_ENTRY_UNRESTRICT_MASK: u32 =
|
||||
FILE_GENERIC_READ | FILE_GENERIC_WRITE | STANDARD_RIGHTS_ALL;
|
||||
|
||||
pub fn get_address(client: &Client) -> String {
|
||||
let listening_address = client.get_listening_address();
|
||||
let ip = match listening_address.ip() {
|
||||
V4(ip) => ip.to_string(),
|
||||
V6(ip) => ip.to_string(),
|
||||
};
|
||||
|
||||
format!("http://{}:{}", ip, listening_address.port())
|
||||
}
|
||||
|
||||
pub fn restrict_permissions(path: &Path) {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::fs;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let metadata = fs::metadata(path).unwrap();
|
||||
let mut permissions = metadata.permissions();
|
||||
permissions.set_mode(0o0311); // set to '*-wx--x--x'
|
||||
fs::set_permissions(path, permissions).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use winapi::um::winnt::PSID;
|
||||
use windows_acl::acl::{AceType, ACL};
|
||||
|
||||
let path_str = path.to_str().unwrap();
|
||||
let mut acl = ACL::from_file_path(&path_str, false).unwrap();
|
||||
|
||||
let owner_sid = windows_acl::helper::string_to_sid(OWNER_SID_STR).unwrap();
|
||||
let entries = acl.all().unwrap();
|
||||
// remove all AccessAllow entries
|
||||
for entry in &entries {
|
||||
if let Some(ref entry_sid) = entry.sid {
|
||||
acl.remove(entry_sid.as_ptr() as PSID, Some(AceType::AccessAllow), None)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
// add single entry for minimal access to file owner
|
||||
// allowing them only to read attributes of the file
|
||||
// and read/modify permissions
|
||||
acl.add_entry(
|
||||
owner_sid.as_ptr() as PSID,
|
||||
AceType::AccessAllow,
|
||||
OWNER_ACL_ENTRY_FLAGS,
|
||||
OWNER_ACL_ENTRY_RESTRICT_MASK,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unrestrict_permissions(path: &Path) {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::fs;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let metadata = fs::metadata(path).unwrap();
|
||||
let mut permissions = metadata.permissions();
|
||||
permissions.set_mode(0o0755); // set to '*rwxr-xr-x'
|
||||
fs::set_permissions(path, permissions).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use winapi::um::winnt::PSID;
|
||||
use windows_acl::acl::{AceType, ACL};
|
||||
|
||||
let path_str = path.to_str().unwrap();
|
||||
let mut acl = ACL::from_file_path(&path_str, false).unwrap();
|
||||
|
||||
let owner_sid = windows_acl::helper::string_to_sid(OWNER_SID_STR).unwrap();
|
||||
// add single entry for file owner
|
||||
acl.add_entry(
|
||||
owner_sid.as_ptr() as PSID,
|
||||
AceType::AccessAllow,
|
||||
OWNER_ACL_ENTRY_FLAGS,
|
||||
OWNER_ACL_ENTRY_UNRESTRICT_MASK,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_key_files(tmp_dir: &TempDir) {
|
||||
let pairs = vec![
|
||||
(PUBLIC_KEY_1, SECRET_KEY_1),
|
||||
(PUBLIC_KEY_2, SECRET_KEY_2),
|
||||
(PUBLIC_KEY_3, SECRET_KEY_3),
|
||||
];
|
||||
|
||||
add_files(tmp_dir, pairs);
|
||||
}
|
||||
|
||||
pub fn add_mismatched_key_file(tmp_dir: &TempDir) {
|
||||
let pairs = vec![(MISMATCHED_PUBLIC_KEY, SECRET_KEY_1)];
|
||||
|
||||
add_files(tmp_dir, pairs);
|
||||
}
|
||||
|
||||
pub fn add_invalid_secret_key_file(tmp_dir: &TempDir) {
|
||||
let pairs = vec![(PUBLIC_KEY_FOR_INVALID_SECRET_KEY, INVALID_SECRET_KEY)];
|
||||
|
||||
add_files(tmp_dir, pairs);
|
||||
}
|
||||
|
||||
pub fn add_non_key_files(tmp_dir: &TempDir) {
|
||||
let pairs = vec![
|
||||
(SILLY_FILE_NAME_1, SILLY_CONTENT_1),
|
||||
(SILLY_FILE_NAME_2, SILLY_CONTENT_2),
|
||||
(SILLY_FILE_NAME_3, SILLY_CONTENT_3),
|
||||
];
|
||||
|
||||
add_files(tmp_dir, pairs);
|
||||
}
|
||||
|
||||
fn add_files(tmp_dir: &TempDir, pairs: Vec<(&str, &str)>) {
|
||||
for pair in pairs {
|
||||
let file_path = tmp_dir.path().join(pair.0);
|
||||
let mut tmp_file = File::create(file_path).unwrap();
|
||||
writeln!(tmp_file, "{}", pair.1).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_sub_dirs(tmp_dir: &TempDir) {
|
||||
let random_sub_dir_path = tmp_dir.path().join("random_sub_dir_name");
|
||||
create_dir(random_sub_dir_path).unwrap();
|
||||
|
||||
let another_sub_dir_path = tmp_dir.path().join(SUB_DIR_NAME);
|
||||
create_dir(another_sub_dir_path).unwrap();
|
||||
}
|
||||
|
||||
/// We spice up some of the values, based on a given `seed` parameter.
|
||||
pub fn get_block<E: EthSpec>(seed: u64) -> BeaconBlock<E> {
|
||||
let spec = &mut E::default_spec();
|
||||
spec.genesis_slot = Slot::new(seed);
|
||||
|
||||
let header = BeaconBlockHeader {
|
||||
slot: Slot::new(seed),
|
||||
proposer_index: 0,
|
||||
parent_root: Hash256::from_low_u64_be(222 * seed),
|
||||
state_root: Hash256::from_low_u64_be(333 * seed),
|
||||
body_root: Hash256::from_low_u64_be(444 * seed),
|
||||
};
|
||||
|
||||
let signed_header = SignedBeaconBlockHeader {
|
||||
message: header,
|
||||
signature: Signature::empty(),
|
||||
};
|
||||
let indexed_attestation: IndexedAttestation<E> = IndexedAttestation {
|
||||
attesting_indices: VariableList::new(vec![0_u64; E::MaxValidatorsPerCommittee::to_usize()])
|
||||
.unwrap(),
|
||||
data: AttestationData::default(),
|
||||
signature: AggregateSignature::empty(),
|
||||
};
|
||||
|
||||
let deposit_data = DepositData {
|
||||
pubkey: PublicKeyBytes::empty(),
|
||||
withdrawal_credentials: Hash256::from_low_u64_be(555 * seed),
|
||||
amount: 0,
|
||||
signature: SignatureBytes::empty(),
|
||||
};
|
||||
let proposer_slashing = ProposerSlashing {
|
||||
signed_header_1: signed_header.clone(),
|
||||
signed_header_2: signed_header,
|
||||
};
|
||||
|
||||
let attester_slashing = AttesterSlashing {
|
||||
attestation_1: indexed_attestation.clone(),
|
||||
attestation_2: indexed_attestation,
|
||||
};
|
||||
|
||||
let attestation: Attestation<E> = Attestation {
|
||||
aggregation_bits: BitList::with_capacity(E::MaxValidatorsPerCommittee::to_usize()).unwrap(),
|
||||
data: AttestationData::default(),
|
||||
signature: AggregateSignature::empty(),
|
||||
};
|
||||
|
||||
let deposit = Deposit {
|
||||
proof: FixedVector::from_elem(Hash256::from_low_u64_be(666 * seed)),
|
||||
data: deposit_data,
|
||||
};
|
||||
|
||||
let voluntary_exit = VoluntaryExit {
|
||||
epoch: Epoch::new(1),
|
||||
validator_index: 1,
|
||||
};
|
||||
|
||||
let signed_voluntary_exit = SignedVoluntaryExit {
|
||||
message: voluntary_exit,
|
||||
signature: Signature::empty(),
|
||||
};
|
||||
|
||||
let mut block: BeaconBlock<E> = BeaconBlock::empty(spec);
|
||||
for _ in 0..E::MaxProposerSlashings::to_usize() {
|
||||
block
|
||||
.body_mut()
|
||||
.proposer_slashings_mut()
|
||||
.push(proposer_slashing.clone())
|
||||
.unwrap();
|
||||
}
|
||||
for _ in 0..E::MaxDeposits::to_usize() {
|
||||
block
|
||||
.body_mut()
|
||||
.deposits_mut()
|
||||
.push(deposit.clone())
|
||||
.unwrap();
|
||||
}
|
||||
for _ in 0..E::MaxVoluntaryExits::to_usize() {
|
||||
block
|
||||
.body_mut()
|
||||
.voluntary_exits_mut()
|
||||
.push(signed_voluntary_exit.clone())
|
||||
.unwrap();
|
||||
}
|
||||
for _ in 0..E::MaxAttesterSlashings::to_usize() {
|
||||
block
|
||||
.body_mut()
|
||||
.attester_slashings_mut()
|
||||
.push(attester_slashing.clone())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..E::MaxAttestations::to_usize() {
|
||||
block
|
||||
.body_mut()
|
||||
.attestations_mut()
|
||||
.push(attestation.clone())
|
||||
.unwrap();
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
pub fn get_attestation<E: EthSpec>(seed: u64) -> AttestationData {
|
||||
let slot = Slot::from(seed);
|
||||
let epoch = slot.epoch(E::slots_per_epoch());
|
||||
|
||||
let build_checkpoint = |epoch_u64: u64| -> Checkpoint {
|
||||
Checkpoint {
|
||||
epoch: Epoch::new(epoch_u64),
|
||||
root: Hash256::from_low_u64_be(333 * seed),
|
||||
}
|
||||
};
|
||||
|
||||
let source = build_checkpoint(epoch.as_u64().saturating_sub(2));
|
||||
let target = build_checkpoint(epoch.as_u64());
|
||||
|
||||
let index = 0xc137u64;
|
||||
|
||||
AttestationData {
|
||||
slot,
|
||||
index,
|
||||
beacon_block_root: Hash256::from_low_u64_be(666 * seed),
|
||||
source,
|
||||
target,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user