From 54afb333337e7e766ef3696f41c4ba3f1574f9a7 Mon Sep 17 00:00:00 2001 From: Artem Vorotnikov Date: Wed, 12 Aug 2020 13:00:28 +0300 Subject: [PATCH] Remove whisper (#10855) --- Cargo.lock | 72 --- Cargo.toml | 2 - README.md | 5 - accounts/ethkey/README.md | 1 - accounts/ethstore/README.md | 1 - ethcore/sync/src/api.rs | 39 -- evmbin/README.md | 1 - parity/cli/mod.rs | 39 +- parity/cli/tests/config.full.toml | 4 - parity/cli/tests/config.toml | 4 - parity/configuration.rs | 10 - parity/lib.rs | 2 - parity/modules.rs | 4 +- parity/rpc_apis.rs | 54 -- parity/run.rs | 27 - parity/whisper.rs | 127 ----- scripts/snap/snapcraft.template.yaml | 4 - whisper/Cargo.toml | 31 -- whisper/README.md | 33 -- whisper/cli/Cargo.toml | 26 - whisper/cli/src/main.rs | 377 ------------- whisper/src/lib.rs | 64 --- whisper/src/message.rs | 576 -------------------- whisper/src/net/mod.rs | 766 --------------------------- whisper/src/net/tests.rs | 198 ------- whisper/src/rpc/crypto.rs | 279 ---------- whisper/src/rpc/filter.rs | 486 ----------------- whisper/src/rpc/key_store.rs | 196 ------- whisper/src/rpc/mod.rs | 424 --------------- whisper/src/rpc/payload.rs | 366 ------------- whisper/src/rpc/types.rs | 310 ----------- 31 files changed, 8 insertions(+), 4520 deletions(-) delete mode 100644 parity/whisper.rs delete mode 100644 whisper/Cargo.toml delete mode 100644 whisper/README.md delete mode 100644 whisper/cli/Cargo.toml delete mode 100644 whisper/cli/src/main.rs delete mode 100644 whisper/src/lib.rs delete mode 100644 whisper/src/message.rs delete mode 100644 whisper/src/net/mod.rs delete mode 100644 whisper/src/net/tests.rs delete mode 100644 whisper/src/rpc/crypto.rs delete mode 100644 whisper/src/rpc/filter.rs delete mode 100644 whisper/src/rpc/key_store.rs delete mode 100644 whisper/src/rpc/mod.rs delete mode 100644 whisper/src/rpc/payload.rs delete mode 100644 whisper/src/rpc/types.rs diff --git a/Cargo.lock b/Cargo.lock index 10a368d98..71f0cd7d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,11 +188,6 @@ name = "bitflags" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "bitflags" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "bitflags" version = "1.2.1" @@ -1716,11 +1711,6 @@ dependencies = [ "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "hex" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "hex-literal" version = "0.2.1" @@ -2631,15 +2621,6 @@ name = "order-stat" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "ordered-float" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", - "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "ordermap" version = "0.3.5" @@ -2783,7 +2764,6 @@ dependencies = [ "parity-runtime 0.1.0", "parity-updater 1.12.0", "parity-version 2.5.13", - "parity-whisper 0.1.0", "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "pretty_assertions 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3054,35 +3034,6 @@ dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "parity-whisper" -version = "0.1.0" -dependencies = [ - "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-network 1.12.0", - "ethereum-types 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "ethkey 0.3.0", - "hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 14.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-derive 14.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-pubsub 14.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "memzero 0.1.0", - "ordered-float 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-crypto 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rlp 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", - "time-utils 0.1.0", - "tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "parity-wordlist" version = "1.3.0" @@ -4825,26 +4776,6 @@ dependencies = [ "webpki 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "whisper-cli" -version = "0.1.0" -dependencies = [ - "docopt 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-network 1.12.0", - "ethcore-network-devp2p 1.12.0", - "ethkey 0.3.0", - "jsonrpc-core 14.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-http-server 14.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-pubsub 14.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "panic_hook 0.1.0", - "parity-whisper 0.1.0", - "rustc-hex 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "winapi" version = "0.2.8" @@ -4982,7 +4913,6 @@ dependencies = [ "checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c" "checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" -"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" @@ -5071,7 +5001,6 @@ dependencies = [ "checksum heapsize 0.4.2 (git+https://github.com/cheme/heapsize.git?branch=ec-macfix)" = "" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120" -"checksum hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" "checksum hex-literal 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "961de220ec9a91af2e1e5bd80d02109155695e516771762381ef8581317066e0" "checksum hex-literal-impl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9d4c5c844e2fee0bf673d54c2c177f1713b3d2af2ff6e666b49cb7572e6cf42d" "checksum hmac 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "733e1b3ac906631ca01ebb577e9bb0f5e37a454032b9036b5eaea4013ed6f99a" @@ -5161,7 +5090,6 @@ dependencies = [ "checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" "checksum order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "efa535d5117d3661134dbf1719b6f0ffe06f2375843b13935db186cd094105eb" -"checksum ordered-float 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7eb5259643245d3f292c7a146b2df53bba24d7eab159410e648eb73dc164669d" "checksum ordermap 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063" "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" "checksum parity-bytes 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0c276d76c5333b8c2579e02d49a06733a55b8282d2d9b13e8d53b6406bd7e30a" diff --git a/Cargo.toml b/Cargo.toml index 9d4fc33b8..a65eefd1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,6 @@ parity-runtime = { path = "util/runtime" } parity-rpc = { path = "rpc" } parity-updater = { path = "updater" } parity-version = { path = "util/version" } -parity-whisper = { path = "whisper" } parity-path = "0.1" dir = { path = "util/dir" } panic_hook = { path = "util/panic-hook" } @@ -137,7 +136,6 @@ members = [ "ethcore/wasm/run", "evmbin", "parity-clib", - "whisper/cli", "util/triehash-ethereum", "util/keccak-hasher", "util/patricia-trie-ethereum", diff --git a/README.md b/README.md index a2df962be..6d590e8b5 100644 --- a/README.md +++ b/README.md @@ -320,10 +320,6 @@ Caching, Importing Blocks, and Block Information patricia-trie-ethereum registrar rlp_compress rlp_derive parity-runtime stats time-utils triehash-ethereum unexpected parity-version ``` -* Parity Whisper Protocol Implementation - ```bash - parity-whisper whisper-cli - ```

@@ -360,7 +356,6 @@ In addition to the Parity Ethereum client, there are additional tools in this re - [evmbin](./evmbin) - Parity Ethereum EVM Implementation. - [ethstore](./accounts/ethstore) - Parity Ethereum Key Management. - [ethkey](./accounts/ethkey) - Parity Ethereum Keys Generator. -- [whisper](./whisper) - Parity Ethereum Whisper-v2 PoC Implementation. The following tool is available in a separate repository: - [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum Encoding of Function Calls. [Docs here](https://crates.io/crates/ethabi) diff --git a/accounts/ethkey/README.md b/accounts/ethkey/README.md index 23c57fa4c..f7abe9286 100644 --- a/accounts/ethkey/README.md +++ b/accounts/ethkey/README.md @@ -218,4 +218,3 @@ _This project is a part of the Parity Ethereum toolchain._ - [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding. - [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management. - [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator. -- [whisper](https://github.com/paritytech/parity-ethereum/blob/master/whisper/) - Implementation of Whisper-v2 PoC. diff --git a/accounts/ethstore/README.md b/accounts/ethstore/README.md index 77c37bd24..ceebe8100 100644 --- a/accounts/ethstore/README.md +++ b/accounts/ethstore/README.md @@ -337,4 +337,3 @@ _This project is a part of the Parity Ethereum toolchain._ - [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding. - [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management. - [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator. -- [whisper](https://github.com/paritytech/parity-ethereum/blob/master/whisper/) - Implementation of Whisper-v2 PoC. diff --git a/ethcore/sync/src/api.rs b/ethcore/sync/src/api.rs index 6bd358508..9c90854fc 100644 --- a/ethcore/sync/src/api.rs +++ b/ethcore/sync/src/api.rs @@ -214,27 +214,6 @@ impl From for PipProtocolInfo { } } -/// Configuration to attach alternate protocol handlers. -/// Only works when IPC is disabled. -pub struct AttachedProtocol { - /// The protocol handler in question. - pub handler: Arc, - /// 3-character ID for the protocol. - pub protocol_id: ProtocolId, - /// Supported versions and their packet counts. - pub versions: &'static [(u8, u8)], -} - -impl AttachedProtocol { - fn register(&self, network: &NetworkService) { - let res = network.register_protocol(self.handler.clone(), self.protocol_id, self.versions); - - if let Err(e) = res { - warn!(target: "sync", "Error attaching protocol {:?}: {:?}", self.protocol_id, e); - } - } -} - /// A prioritized tasks run in a specialised timer. /// Every task should be completed within a hard deadline, /// if it's not it's either cancelled or split into multiple tasks. @@ -282,8 +261,6 @@ pub struct Params { pub provider: Arc, /// Network layer configuration. pub network_config: NetworkConfiguration, - /// Other protocols to attach. - pub attached_protos: Vec, } /// Ethereum network protocol handler @@ -294,8 +271,6 @@ pub struct EthSync { eth_handler: Arc, /// Light (pip) protocol handler light_proto: Option>, - /// Other protocols to attach. - attached_protos: Vec, /// The main subprotocol name subprotocol_name: [u8; 3], /// Light subprotocol name. @@ -387,7 +362,6 @@ impl EthSync { light_proto: light_proto, subprotocol_name: params.config.subprotocol_name, light_subprotocol_name: params.config.light_subprotocol_name, - attached_protos: params.attached_protos, priority_tasks: Mutex::new(priority_tasks_tx), }); @@ -643,11 +617,6 @@ impl ChainNotify for EthSync { ) .unwrap_or_else(|e| warn!("Error registering light client protocol: {:?}", e)); } - - // register any attached protocols. - for proto in &self.attached_protos { - proto.register(&self.network) - } } fn stop(&self) { @@ -979,15 +948,12 @@ pub struct LightSyncParams { pub subprotocol_name: [u8; 3], /// Other handlers to attach. pub handlers: Vec>, - /// Other subprotocols to run. - pub attached_protos: Vec, } /// Service for light synchronization. pub struct LightSync { proto: Arc, sync: Arc, - attached_protos: Vec, network: NetworkService, subprotocol_name: [u8; 3], network_id: u64, @@ -1031,7 +997,6 @@ impl LightSync { Ok(LightSync { proto: light_proto, sync: sync, - attached_protos: params.attached_protos, network: service, subprotocol_name: params.subprotocol_name, network_id: params.network_id, @@ -1104,10 +1069,6 @@ impl ManageNetwork for LightSync { ::light::net::PROTOCOL_VERSIONS, ) .unwrap_or_else(|e| warn!("Error registering light client protocol: {:?}", e)); - - for proto in &self.attached_protos { - proto.register(&self.network) - } } fn stop_network(&self) { diff --git a/evmbin/README.md b/evmbin/README.md index e0a8c9d96..4fbd7df33 100644 --- a/evmbin/README.md +++ b/evmbin/README.md @@ -52,4 +52,3 @@ _This project is a part of the Parity Ethereum toolchain._ - [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding. - [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management. - [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator. -- [whisper](https://github.com/paritytech/parity-ethereum/blob/master/whisper/) - Implementation of Whisper-v2 PoC. diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index ec563a2e8..132d17610 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -483,9 +483,9 @@ usage! { "--jsonrpc-interface=[IP]", "Specify the hostname portion of the HTTP JSON-RPC API server, IP should be an interface's IP address, or all (all interfaces) or local.", - ARG arg_jsonrpc_apis: (String) = "web3,eth,pubsub,net,parity,private,parity_pubsub,traces,shh,shh_pubsub", or |c: &Config| c.rpc.as_ref()?.apis.as_ref().map(|vec| vec.join(",")), + ARG arg_jsonrpc_apis: (String) = "web3,eth,pubsub,net,parity,private,parity_pubsub,traces", or |c: &Config| c.rpc.as_ref()?.apis.as_ref().map(|vec| vec.join(",")), "--jsonrpc-apis=[APIS]", - "Specify the APIs available through the HTTP JSON-RPC interface using a comma-delimited list of API names. Possible names are: all, safe, debug, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, secretstore, shh, shh_pubsub. You can also disable a specific API by putting '-' in the front, example: all,-personal. 'safe' enables the following APIs: web3, net, eth, pubsub, parity, parity_pubsub, traces, shh, shh_pubsub", + "Specify the APIs available through the HTTP JSON-RPC interface using a comma-delimited list of API names. Possible names are: all, safe, debug, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, secretstore. You can also disable a specific API by putting '-' in the front, example: all,-personal. 'safe' enables the following APIs: web3, net, eth, pubsub, parity, parity_pubsub, traces", ARG arg_jsonrpc_hosts: (String) = "none", or |c: &Config| c.rpc.as_ref()?.hosts.as_ref().map(|vec| vec.join(",")), "--jsonrpc-hosts=[HOSTS]", @@ -524,9 +524,9 @@ usage! { "--ws-interface=[IP]", "Specify the hostname portion of the WebSockets JSON-RPC server, IP should be an interface's IP address, or all (all interfaces) or local.", - ARG arg_ws_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,private,traces,shh,shh_pubsub", or |c: &Config| c.websockets.as_ref()?.apis.as_ref().map(|vec| vec.join(",")), + ARG arg_ws_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,private,traces", or |c: &Config| c.websockets.as_ref()?.apis.as_ref().map(|vec| vec.join(",")), "--ws-apis=[APIS]", - "Specify the JSON-RPC APIs available through the WebSockets interface using a comma-delimited list of API names. Possible names are: all, safe, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, secretstore, shh, shh_pubsub. You can also disable a specific API by putting '-' in the front, example: all,-personal. 'safe' enables the following APIs: web3, net, eth, pubsub, parity, parity_pubsub, traces, shh, shh_pubsub", + "Specify the JSON-RPC APIs available through the WebSockets interface using a comma-delimited list of API names. Possible names are: all, safe, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, secretstore. You can also disable a specific API by putting '-' in the front, example: all,-personal. 'safe' enables the following APIs: web3, net, eth, pubsub, parity, parity_pubsub, traces", ARG arg_ws_origins: (String) = "parity://*,chrome-extension://*,moz-extension://*", or |c: &Config| c.websockets.as_ref()?.origins.as_ref().map(|vec| vec.join(",")), "--ws-origins=[URL]", @@ -549,9 +549,9 @@ usage! { "--ipc-path=[PATH]", "Specify custom path for JSON-RPC over IPC service.", - ARG arg_ipc_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,parity_accounts,private,traces,shh,shh_pubsub", or |c: &Config| c.ipc.as_ref()?.apis.as_ref().map(|vec| vec.join(",")), + ARG arg_ipc_apis: (String) = "web3,eth,pubsub,net,parity,parity_pubsub,parity_accounts,private,traces", or |c: &Config| c.ipc.as_ref()?.apis.as_ref().map(|vec| vec.join(",")), "--ipc-apis=[APIS]", - "Specify custom API set available via JSON-RPC over IPC using a comma-delimited list of API names. Possible names are: all, safe, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, secretstore, shh, shh_pubsub. You can also disable a specific API by putting '-' in the front, example: all,-personal. 'safe' enables the following APIs: web3, net, eth, pubsub, parity, parity_pubsub, traces, shh, shh_pubsub", + "Specify custom API set available via JSON-RPC over IPC using a comma-delimited list of API names. Possible names are: all, safe, web3, net, eth, pubsub, personal, signer, parity, parity_pubsub, parity_accounts, parity_set, traces, secretstore. You can also disable a specific API by putting '-' in the front, example: all,-personal. 'safe' enables the following APIs: web3, net, eth, pubsub, parity, parity_pubsub, traces", ["API and Console Options – IPFS"] FLAG flag_ipfs_api: (bool) = false, or |c: &Config| c.ipfs.as_ref()?.enable.clone(), @@ -905,15 +905,6 @@ usage! { ARG arg_snapshot_threads: (Option) = None, or |c: &Config| c.snapshots.as_ref()?.processing_threads, "--snapshot-threads=[NUM]", "Enables multiple threads for snapshots creation.", - - ["Whisper Options"] - FLAG flag_whisper: (bool) = false, or |c: &Config| c.whisper.as_ref()?.enabled, - "--whisper", - "Enable the Whisper network.", - - ARG arg_whisper_pool_size: (usize) = 10usize, or |c: &Config| c.whisper.as_ref()?.pool_size.clone(), - "--whisper-pool-size=[MB]", - "Target size of the whisper message pool in megabytes.", } } @@ -935,7 +926,6 @@ struct Config { snapshots: Option, misc: Option, stratum: Option, - whisper: Option, light: Option, } @@ -1164,13 +1154,6 @@ struct Misc { unsafe_expose: Option, } -#[derive(Default, Debug, PartialEq, Deserialize)] -#[serde(deny_unknown_fields)] -struct Whisper { - enabled: Option, - pool_size: Option, -} - #[derive(Default, Debug, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] struct Light { @@ -1185,7 +1168,7 @@ struct Light { mod tests { use super::{ Account, Args, ArgsError, Config, Footprint, Ipc, Ipfs, Light, Mining, Misc, Network, - Operating, Rpc, SecretStore, Snapshots, Whisper, Ws, + Operating, Rpc, SecretStore, Snapshots, Ws, }; use clap::ErrorKind as ClapErrorKind; use toml; @@ -1606,10 +1589,6 @@ mod tests { arg_on_demand_request_backoff_rounds_max: Some(100), arg_on_demand_request_consecutive_failures: Some(1), - // -- Whisper options. - flag_whisper: false, - arg_whisper_pool_size: 20, - // -- Internal Options flag_can_restart: false, @@ -1826,10 +1805,6 @@ mod tests { ports_shift: Some(0), unsafe_expose: Some(false), }), - whisper: Some(Whisper { - enabled: Some(true), - pool_size: Some(50), - }), stratum: None, } ); diff --git a/parity/cli/tests/config.full.toml b/parity/cli/tests/config.full.toml index 659d6f084..e3dd40b66 100644 --- a/parity/cli/tests/config.full.toml +++ b/parity/cli/tests/config.full.toml @@ -156,7 +156,3 @@ disable_periodic = false logging = "own_tx=trace" log_file = "/var/log/parity.log" color = true - -[whisper] -enabled = false -pool_size = 20 diff --git a/parity/cli/tests/config.toml b/parity/cli/tests/config.toml index bae85a0ba..9742e1b81 100644 --- a/parity/cli/tests/config.toml +++ b/parity/cli/tests/config.toml @@ -78,7 +78,3 @@ log_file = "/var/log/parity.log" color = true ports_shift = 0 unsafe_expose = false - -[whisper] -enabled = true -pool_size = 50 diff --git a/parity/configuration.rs b/parity/configuration.rs index 491eac4a9..82ead3e94 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -398,7 +398,6 @@ impl Configuration { }; let verifier_settings = self.verifier_settings(); - let whisper_config = self.whisper_config(); let (private_provider_conf, private_enc_conf, private_tx_enabled) = self.private_provider_config()?; @@ -448,7 +447,6 @@ impl Configuration { serve_light: !self.args.flag_no_serve_light, light: self.args.flag_light, no_persistent_txqueue: self.args.flag_no_persistent_txqueue, - whisper: whisper_config, no_hardcoded_sync: self.args.flag_no_hardcoded_sync, max_round_blocks_to_import: self.args.arg_max_round_blocks_to_import, on_demand_response_time_window: self.args.arg_on_demand_response_time_window, @@ -1332,13 +1330,6 @@ impl Configuration { settings } - - fn whisper_config(&self) -> ::whisper::Config { - ::whisper::Config { - enabled: self.args.flag_whisper, - target_message_pool_size: self.args.arg_whisper_pool_size * 1024 * 1024, - } - } } fn into_secretstore_service_contract_address( @@ -1687,7 +1678,6 @@ mod tests { light: false, no_hardcoded_sync: false, no_persistent_txqueue: false, - whisper: Default::default(), max_round_blocks_to_import: 12, on_demand_response_time_window: None, on_demand_request_backoff_start: None, diff --git a/parity/lib.rs b/parity/lib.rs index 1959dd798..78bb1e1e4 100644 --- a/parity/lib.rs +++ b/parity/lib.rs @@ -69,7 +69,6 @@ extern crate parity_rpc; extern crate parity_runtime; extern crate parity_updater as updater; extern crate parity_version; -extern crate parity_whisper; extern crate registrar; #[macro_use] @@ -115,7 +114,6 @@ mod signer; mod snapshot; mod upgrade; mod user_defaults; -mod whisper; use std::{fs::File, io::BufReader, sync::Arc}; diff --git a/parity/modules.rs b/parity/modules.rs index 2b2b977c8..5df1f81b8 100644 --- a/parity/modules.rs +++ b/parity/modules.rs @@ -18,7 +18,7 @@ use std::sync::{mpsc, Arc}; use ethcore::{client::BlockChainClient, snapshot::SnapshotService}; use light::Provider; -use sync::{self, AttachedProtocol, ConnectionFilter, NetworkConfiguration, Params, SyncConfig}; +use sync::{self, ConnectionFilter, NetworkConfiguration, Params, SyncConfig}; pub use ethcore::client::ChainNotify; use ethcore_logger::Config as LogConfig; @@ -39,7 +39,6 @@ pub fn sync( private_tx_handler: Option>, provider: Arc, _log_settings: &LogConfig, - attached_protos: Vec, connection_filter: Option>, ) -> Result { let eth_sync = EthSync::new( @@ -50,7 +49,6 @@ pub fn sync( snapshot_service, private_tx_handler, network_config, - attached_protos, }, connection_filter, )?; diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index fd03cfb02..95abe721f 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -64,12 +64,6 @@ pub enum Api { Traces, /// Private transaction manager (Safe) Private, - /// Whisper (Safe) - // TODO: _if_ someone guesses someone else's key or filter IDs they can remove - // BUT these are all ephemeral so it seems fine. - Whisper, - /// Whisper Pub-Sub (Safe but same concerns as above). - WhisperPubSub, /// Parity PubSub - Generic Publish-Subscriber (Safety depends on other APIs exposed). ParityPubSub, /// Parity Accounts extensions (UNSAFE: Passwords, Side Effects (new account)) @@ -101,8 +95,6 @@ impl FromStr for Api { "private" => Ok(Private), "pubsub" => Ok(EthPubSub), "secretstore" => Ok(SecretStore), - "shh" => Ok(Whisper), - "shh_pubsub" => Ok(WhisperPubSub), "signer" => Ok(Signer), "traces" => Ok(Traces), "web3" => Ok(Web3), @@ -220,7 +212,6 @@ pub struct FullDependencies { pub ws_address: Option, pub fetch: FetchClient, pub executor: Executor, - pub whisper_rpc: Option<::whisper::RpcFactory>, pub gas_price_percentile: usize, pub poll_lifetime: u32, pub allow_missing_blocks: bool, @@ -407,22 +398,6 @@ impl FullDependencies { #[cfg(feature = "accounts")] handler.extend_with(SecretStoreClient::new(&self.accounts).to_delegate()); } - Api::Whisper => { - if let Some(ref whisper_rpc) = self.whisper_rpc { - let whisper = whisper_rpc.make_handler(self.net.clone()); - handler.extend_with(::parity_whisper::rpc::Whisper::to_delegate(whisper)); - } - } - Api::WhisperPubSub => { - if !for_generic_pubsub { - if let Some(ref whisper_rpc) = self.whisper_rpc { - let whisper = whisper_rpc.make_handler(self.net.clone()); - handler.extend_with(::parity_whisper::rpc::WhisperPubSub::to_delegate( - whisper, - )); - } - } - } Api::Private => { handler.extend_with( PrivateClient::new(self.private_tx_service.as_ref().map(|p| p.provider())) @@ -474,7 +449,6 @@ pub struct LightDependencies { pub fetch: FetchClient, pub experimental_rpcs: bool, pub executor: Executor, - pub whisper_rpc: Option<::whisper::RpcFactory>, pub private_tx_service: Option>, pub gas_price_percentile: usize, pub poll_lifetime: u32, @@ -640,20 +614,6 @@ impl LightDependencies { #[cfg(feature = "accounts")] handler.extend_with(SecretStoreClient::new(&self.accounts).to_delegate()); } - Api::Whisper => { - if let Some(ref whisper_rpc) = self.whisper_rpc { - let whisper = whisper_rpc.make_handler(self.net.clone()); - handler.extend_with(::parity_whisper::rpc::Whisper::to_delegate(whisper)); - } - } - Api::WhisperPubSub => { - if let Some(ref whisper_rpc) = self.whisper_rpc { - let whisper = whisper_rpc.make_handler(self.net.clone()); - handler.extend_with(::parity_whisper::rpc::WhisperPubSub::to_delegate( - whisper, - )); - } - } Api::Private => { if let Some(ref tx_manager) = self.private_tx_service { let private_tx_service = Some(tx_manager.clone()); @@ -693,8 +653,6 @@ impl ApiSet { Api::Eth, Api::EthPubSub, Api::Parity, - Api::Whisper, - Api::WhisperPubSub, Api::Private, ] .iter() @@ -758,8 +716,6 @@ mod test { assert_eq!(Api::Traces, "traces".parse().unwrap()); assert_eq!(Api::SecretStore, "secretstore".parse().unwrap()); assert_eq!(Api::Private, "private".parse().unwrap()); - assert_eq!(Api::Whisper, "shh".parse().unwrap()); - assert_eq!(Api::WhisperPubSub, "shh_pubsub".parse().unwrap()); assert!("rp".parse::().is_err()); } @@ -787,8 +743,6 @@ mod test { Api::Parity, Api::ParityPubSub, Api::Traces, - Api::Whisper, - Api::WhisperPubSub, Api::Private, ] .into_iter() @@ -807,8 +761,6 @@ mod test { Api::Parity, Api::ParityPubSub, Api::Traces, - Api::Whisper, - Api::WhisperPubSub, Api::Private, // semi-safe Api::ParityAccounts, @@ -832,8 +784,6 @@ mod test { Api::ParityPubSub, Api::Traces, Api::SecretStore, - Api::Whisper, - Api::WhisperPubSub, Api::ParityAccounts, Api::ParitySet, Api::Signer, @@ -861,8 +811,6 @@ mod test { Api::ParityPubSub, Api::Traces, Api::SecretStore, - Api::Whisper, - Api::WhisperPubSub, Api::ParityAccounts, Api::ParitySet, Api::Signer, @@ -888,8 +836,6 @@ mod test { Api::Parity, Api::ParityPubSub, Api::Traces, - Api::Whisper, - Api::WhisperPubSub, Api::Private, ] .into_iter() diff --git a/parity/run.rs b/parity/run.rs index e91f7f74e..d50afc7f4 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -135,7 +135,6 @@ pub struct RunCmd { pub serve_light: bool, pub light: bool, pub no_persistent_txqueue: bool, - pub whisper: ::whisper::Config, pub no_hardcoded_sync: bool, pub max_round_blocks_to_import: usize, pub on_demand_response_time_window: Option, @@ -309,16 +308,6 @@ where net_conf.boot_nodes = spec.nodes.clone(); } - let mut attached_protos = Vec::new(); - let whisper_factory = if cmd.whisper.enabled { - let whisper_factory = - ::whisper::setup(cmd.whisper.target_message_pool_size, &mut attached_protos) - .map_err(|e| format!("Failed to initialize whisper: {}", e))?; - whisper_factory - } else { - None - }; - // set network path. net_conf.net_config_path = Some(db_dirs.network_path().to_string_lossy().into_owned()); let sync_params = LightSyncParams { @@ -329,7 +318,6 @@ where network_id: cmd.network_id.unwrap_or(spec.network_id()), subprotocol_name: sync::LIGHT_PROTOCOL, handlers: vec![on_demand.clone()], - attached_protos: attached_protos, }; let light_sync = LightSync::new(sync_params).map_err(|e| format!("Error starting network: {}", e))?; @@ -376,7 +364,6 @@ where fetch: fetch, experimental_rpcs: cmd.experimental_rpcs, executor: runtime.executor(), - whisper_rpc: whisper_factory, private_tx_service: None, //TODO: add this to client. gas_price_percentile: cmd.gas_price_percentile, poll_lifetime: cmd.poll_lifetime, @@ -748,18 +735,6 @@ where .map_err(|e| format!("Stratum start error: {:?}", e))?; } - let mut attached_protos = Vec::new(); - - let whisper_factory = if cmd.whisper.enabled { - let whisper_factory = - ::whisper::setup(cmd.whisper.target_message_pool_size, &mut attached_protos) - .map_err(|e| format!("Failed to initialize whisper: {}", e))?; - - whisper_factory - } else { - None - }; - let private_tx_sync: Option> = match cmd.private_tx_enabled { true => Some(private_tx_service.clone() as Arc), false => None, @@ -774,7 +749,6 @@ where private_tx_sync, client.clone(), &cmd.logger_config, - attached_protos, connection_filter .clone() .map(|f| f as Arc), @@ -867,7 +841,6 @@ where ws_address: cmd.ws_conf.address(), fetch: fetch.clone(), executor: runtime.executor(), - whisper_rpc: whisper_factory, private_tx_service: Some(private_tx_service.clone()), gas_price_percentile: cmd.gas_price_percentile, poll_lifetime: cmd.poll_lifetime, diff --git a/parity/whisper.rs b/parity/whisper.rs deleted file mode 100644 index f9da44d7f..000000000 --- a/parity/whisper.rs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2015-2019 Parity Technologies (UK) Ltd. -// This file is part of Parity Ethereum. - -// Parity Ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity Ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity Ethereum. If not, see . - -use std::{io, sync::Arc}; - -use parity_rpc::Metadata; -use parity_whisper::{ - message::Message, - net::{self as whisper_net, Network as WhisperNetwork}, - rpc::{FilterManager, PoolHandle, WhisperClient}, -}; -use sync::{AttachedProtocol, ManageNetwork}; - -/// Whisper config. -#[derive(Debug, PartialEq, Eq)] -pub struct Config { - pub enabled: bool, - pub target_message_pool_size: usize, -} - -impl Default for Config { - fn default() -> Self { - Config { - enabled: false, - target_message_pool_size: 10 * 1024 * 1024, - } - } -} - -/// Standard pool handle. -pub struct NetPoolHandle { - /// Pool handle. - handle: Arc>>, - /// Network manager. - net: Arc, -} - -impl PoolHandle for NetPoolHandle { - fn relay(&self, message: Message) -> bool { - let mut res = false; - let mut message = Some(message); - self.net - .with_proto_context(whisper_net::PROTOCOL_ID, &mut |ctx| { - if let Some(message) = message.take() { - res = self.handle.post_message(message, ctx); - } - }); - res - } - - fn pool_status(&self) -> whisper_net::PoolStatus { - self.handle.pool_status() - } -} - -/// Factory for standard whisper RPC. -pub struct RpcFactory { - net: Arc>>, - manager: Arc, -} - -impl RpcFactory { - pub fn make_handler( - &self, - net: Arc, - ) -> WhisperClient { - let handle = NetPoolHandle { - handle: self.net.clone(), - net: net, - }; - WhisperClient::new(handle, self.manager.clone()) - } -} - -/// Sets up whisper protocol and RPC handler. -/// -/// Will target the given pool size. -#[cfg(not(feature = "ipc"))] -pub fn setup( - target_pool_size: usize, - protos: &mut Vec, -) -> io::Result> { - let manager = Arc::new(FilterManager::new()?); - let net = Arc::new(WhisperNetwork::new(target_pool_size, manager.clone())); - - protos.push(AttachedProtocol { - handler: net.clone() as Arc<_>, - versions: whisper_net::SUPPORTED_VERSIONS, - protocol_id: whisper_net::PROTOCOL_ID, - }); - - // parity-only extensions to whisper. - protos.push(AttachedProtocol { - handler: Arc::new(whisper_net::ParityExtensions), - versions: whisper_net::SUPPORTED_VERSIONS, - protocol_id: whisper_net::PARITY_PROTOCOL_ID, - }); - - let factory = RpcFactory { - net: net, - manager: manager, - }; - - Ok(Some(factory)) -} - -// TODO: make it possible to attach generic protocols in IPC. -#[cfg(feature = "ipc")] -pub fn setup( - _target_pool_size: usize, - _protos: &mut Vec, -) -> io::Result> { - Ok(None) -} diff --git a/scripts/snap/snapcraft.template.yaml b/scripts/snap/snapcraft.template.yaml index b85454110..eba7c7f25 100644 --- a/scripts/snap/snapcraft.template.yaml +++ b/scripts/snap/snapcraft.template.yaml @@ -25,9 +25,6 @@ apps: ethstore: command: ethstore plugs: [home, removable-media] - whisper: - command: whisper - plugs: [home, network-bind, removable-media] icon: ./scripts/snap/icon.png @@ -49,5 +46,4 @@ parts: cp -v parity-evm $SNAPCRAFT_PART_INSTALL/usr/bin/parity-evm cp -v ethkey $SNAPCRAFT_PART_INSTALL/usr/bin/ethkey cp -v ethstore $SNAPCRAFT_PART_INSTALL/usr/bin/ethstore - cp -v whisper $SNAPCRAFT_PART_INSTALL/usr/bin/whisper stage-packages: [libstdc++6, cmake, libdb5.3] diff --git a/whisper/Cargo.toml b/whisper/Cargo.toml deleted file mode 100644 index a0d44d51d..000000000 --- a/whisper/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -description = "Parity Ethereum Whisper Protocol Implementation" -name = "parity-whisper" -version = "0.1.0" -authors = ["Parity Technologies "] - -[dependencies] -bitflags = "0.9" -byteorder = "1.0.0" -ethereum-types = "0.4" -ethcore-network = { path = "../util/network" } -parity-crypto = "0.3.0" -ethkey = { path = "../accounts/ethkey" } -hex = "0.2" -log = "0.4" -memzero = { path = "../util/memzero" } -ordered-float = "0.5" -parking_lot = "0.7" -rand = "0.4" -rlp = { version = "0.3.0", features = ["ethereum"] } -serde = "1.0" -serde_derive = "1.0" -serde_json = "1.0" -slab = "0.3" -smallvec = "0.6" -tiny-keccak = "1.4" -time-utils = { path = "../util/time-utils" } - -jsonrpc-core = "14.0.3" -jsonrpc-derive = "14.0.3" -jsonrpc-pubsub = "14.0.3" diff --git a/whisper/README.md b/whisper/README.md deleted file mode 100644 index b64c0244f..000000000 --- a/whisper/README.md +++ /dev/null @@ -1,33 +0,0 @@ -## Whisper - -Implementation of Whisper based on the Whisper-v2 PoC. - -### Usage - -``` -Parity Whisper-v2 CLI. - Copyright 2015-2019 Parity Technologies (UK) Ltd. - -Usage: - whisper [options] - whisper [-h | --help] - -Options: - --whisper-pool-size SIZE Specify Whisper pool size [default: 10]. - -p, --port PORT Specify which P2P port to use [default: random]. - -a, --address ADDRESS Specify which P2P address to use [default: 127.0.0.1]. - -s, --secret KEYFILE Specify which file contains the key to generate the enode. - -P, --rpc-port PORT Specify which RPC port to use [default: 8545]. - -A, --rpc-address ADDRESS Specify which RPC address to use [default: 127.0.0.1]. - -l, --log LEVEL Specify the logging level. Must conform to the same format as RUST_LOG [default: Error]. - -h, --help Display this message and exit. -``` - -## Parity Ethereum toolchain -_This project is a part of the Parity Ethereum toolchain._ - -- [evmbin](https://github.com/paritytech/parity-ethereum/blob/master/evmbin/) - EVM implementation for Parity Ethereum. -- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding. -- [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management. -- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator. -- [whisper](https://github.com/paritytech/parity-ethereum/blob/master/whisper/) - Implementation of Whisper-v2 PoC. diff --git a/whisper/cli/Cargo.toml b/whisper/cli/Cargo.toml deleted file mode 100644 index 253df7cef..000000000 --- a/whisper/cli/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "whisper-cli" -description = "Whisper command line interface" -version = "0.1.0" -authors = ["Parity Technologies "] -license = "GPL-3.0" - -[dependencies] -docopt = "1.0" -env_logger = "0.5" -ethcore-network = { path = "../../util/network" } -ethcore-network-devp2p = { path = "../../util/network-devp2p" } -jsonrpc-core = "14.0.0" -jsonrpc-http-server = "14.0.0" -jsonrpc-pubsub = "14.0.0" -log = "0.4" -panic_hook = { path = "../../util/panic-hook" } -parity-whisper = { path = "../" } -serde = "1.0" -serde_derive = "1.0" -ethkey = { path = "../../accounts/ethkey" } -rustc-hex = "2.0" - -[[bin]] -name = "whisper" -path = "src/main.rs" diff --git a/whisper/cli/src/main.rs b/whisper/cli/src/main.rs deleted file mode 100644 index fe71e3ef3..000000000 --- a/whisper/cli/src/main.rs +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright 2015-2019 Parity Technologies (UK) Ltd. -// This file is part of Parity Ethereum. - -// Parity Ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity Ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity Ethereum. If not, see . - -//! Whisper command line interface -//! -//! Spawns an Ethereum network instance and attaches the Whisper protocol RPCs to it. -//! - -#![warn(missing_docs)] -#![cfg_attr(feature = "cargo-clippy", deny(clippy, clippy_pedantic))] - -extern crate docopt; -extern crate env_logger; -extern crate ethcore_network as net; -extern crate ethcore_network_devp2p as devp2p; -extern crate panic_hook; -extern crate parity_whisper as whisper; -extern crate serde; - -extern crate ethkey; -extern crate jsonrpc_core; -extern crate jsonrpc_http_server; -extern crate jsonrpc_pubsub; -extern crate rustc_hex; - -#[macro_use] -extern crate log as rlog; - -#[macro_use] -extern crate serde_derive; - -use docopt::Docopt; -use ethkey::Secret; -use jsonrpc_core::{MetaIoHandler, Metadata}; -use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation}; -use jsonrpc_pubsub::{PubSubMetadata, Session}; -use rustc_hex::FromHex; -use std::{ - env, fmt, io, - net::{Ipv4Addr, SocketAddr, SocketAddrV4}, - process, - str::FromStr, - sync::Arc, -}; - -const POOL_UNIT: usize = 1024 * 1024; -const USAGE: &'static str = r#" -Parity Whisper-v2 CLI. - Copyright 2015-2019 Parity Technologies (UK) Ltd. - -Usage: - whisper [options] - whisper [-h | --help] - -Options: - --whisper-pool-size SIZE Specify Whisper pool size [default: 10]. - -p, --port PORT Specify which P2P port to use [default: random]. - -a, --address ADDRESS Specify which P2P address to use [default: 127.0.0.1]. - -s, --secret KEYFILE Specify which file contains the key to generate the enode. - -P, --rpc-port PORT Specify which RPC port to use [default: 8545]. - -A, --rpc-address ADDRESS Specify which RPC address to use [default: 127.0.0.1]. - -l, --log LEVEL Specify the logging level. Must conform to the same format as RUST_LOG [default: Error]. - -h, --help Display this message and exit. -"#; - -#[derive(Clone, Default)] -struct Meta; - -impl Metadata for Meta {} - -impl PubSubMetadata for Meta { - fn session(&self) -> Option> { - None - } -} - -#[derive(Debug, Deserialize)] -struct Args { - flag_whisper_pool_size: usize, - flag_port: String, - flag_address: String, - flag_rpc_port: String, - flag_rpc_address: String, - flag_log: String, - flag_secret: String, -} - -struct WhisperPoolHandle { - /// Pool handle. - handle: Arc>>, - /// Network manager. - net: Arc, -} - -impl whisper::rpc::PoolHandle for WhisperPoolHandle { - fn relay(&self, message: whisper::message::Message) -> bool { - let mut res = false; - let mut message = Some(message); - self.with_proto_context(whisper::net::PROTOCOL_ID, &mut |ctx| { - if let Some(message) = message.take() { - res = self.handle.post_message(message, ctx); - } - }); - res - } - - fn pool_status(&self) -> whisper::net::PoolStatus { - self.handle.pool_status() - } -} - -impl WhisperPoolHandle { - fn with_proto_context( - &self, - proto: net::ProtocolId, - f: &mut dyn FnMut(&dyn net::NetworkContext), - ) { - self.net.with_context_eval(proto, f); - } -} - -struct RpcFactory { - handle: Arc>>, - manager: Arc, -} - -impl RpcFactory { - fn make_handler( - &self, - net: Arc, - ) -> whisper::rpc::WhisperClient { - let whisper_pool_handle = WhisperPoolHandle { - handle: self.handle.clone(), - net: net, - }; - whisper::rpc::WhisperClient::new(whisper_pool_handle, self.manager.clone()) - } -} - -#[derive(Debug)] -enum Error { - Docopt(docopt::Error), - Io(io::Error), - JsonRpc(jsonrpc_core::Error), - Network(net::Error), - SockAddr(std::net::AddrParseError), - FromHex(rustc_hex::FromHexError), - ParseInt(std::num::ParseIntError), -} - -impl From for Error { - fn from(err: std::net::AddrParseError) -> Self { - Error::SockAddr(err) - } -} - -impl From for Error { - fn from(err: net::Error) -> Self { - Error::Network(err) - } -} - -impl From for Error { - fn from(err: docopt::Error) -> Self { - Error::Docopt(err) - } -} - -impl From for Error { - fn from(err: io::Error) -> Self { - Error::Io(err) - } -} - -impl From for Error { - fn from(err: jsonrpc_core::Error) -> Self { - Error::JsonRpc(err) - } -} - -impl From for Error { - fn from(err: rustc_hex::FromHexError) -> Self { - Error::FromHex(err) - } -} - -impl From for Error { - fn from(err: std::num::ParseIntError) -> Self { - Error::ParseInt(err) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { - Error::SockAddr(ref e) => write!(f, "{}", e), - Error::Docopt(ref e) => write!(f, "{}", e), - Error::Io(ref e) => write!(f, "{}", e), - Error::JsonRpc(ref e) => write!(f, "{:?}", e), - Error::Network(ref e) => write!(f, "{}", e), - Error::ParseInt(ref e) => write!(f, "Invalid port: {}", e), - Error::FromHex(ref e) => write!(f, "Error deciphering key: {}", e), - } - } -} - -fn main() { - panic_hook::set_abort(); - - match execute(env::args()) { - Ok(_) => { - println!("whisper-cli terminated"); - process::exit(1); - } - Err(Error::Docopt(ref e)) => e.exit(), - Err(err) => { - println!("{}", err); - process::exit(1); - } - } -} - -fn execute(command: I) -> Result<(), Error> -where - I: IntoIterator, - S: AsRef, -{ - // Parse arguments - let args: Args = Docopt::new(USAGE).and_then(|d| d.argv(command).deserialize())?; - let pool_size = args.flag_whisper_pool_size * POOL_UNIT; - let rpc_url = format!("{}:{}", args.flag_rpc_address, args.flag_rpc_port); - - initialize_logger(args.flag_log); - info!(target: "whisper-cli", "start"); - - // Filter manager that will dispatch `decryption tasks` - let manager = Arc::new(whisper::rpc::FilterManager::new()?); - - // Whisper protocol network handler - let whisper_network_handler = Arc::new(whisper::net::Network::new(pool_size, manager.clone())); - - let network_config = { - let mut cfg = net::NetworkConfiguration::new(); - let port = match args.flag_port.as_str() { - "random" => 0 as u16, - port => port.parse::()?, - }; - let addr = Ipv4Addr::from_str(&args.flag_address[..])?; - cfg.listen_address = Some(SocketAddr::V4(SocketAddrV4::new(addr, port))); - cfg.use_secret = match args.flag_secret.as_str() { - "" => None, - fname => { - let key_text = std::fs::read_to_string(fname)?; - let key: Vec = FromHex::from_hex(key_text.as_str())?; - Secret::from_slice(key.as_slice()) - } - }; - cfg.nat_enabled = false; - cfg - }; - - // Create network service - let network = devp2p::NetworkService::new(network_config, None)?; - - // Start network service - network.start().map_err(|(err, _)| err)?; - - // Attach whisper protocol to the network service - network.register_protocol( - whisper_network_handler.clone(), - whisper::net::PROTOCOL_ID, - whisper::net::SUPPORTED_VERSIONS, - )?; - network.register_protocol( - Arc::new(whisper::net::ParityExtensions), - whisper::net::PARITY_PROTOCOL_ID, - whisper::net::SUPPORTED_VERSIONS, - )?; - - // Request handler - let mut io = MetaIoHandler::default(); - - // Shared network service - let shared_network = Arc::new(network); - - // Pool handler - let whisper_factory = RpcFactory { - handle: whisper_network_handler, - manager: manager, - }; - - io.extend_with(whisper::rpc::Whisper::to_delegate( - whisper_factory.make_handler(shared_network.clone()), - )); - io.extend_with(whisper::rpc::WhisperPubSub::to_delegate( - whisper_factory.make_handler(shared_network.clone()), - )); - - let server = jsonrpc_http_server::ServerBuilder::new(io) - .cors(DomainsValidation::AllowOnly(vec![ - AccessControlAllowOrigin::Null, - ])) - .start_http(&rpc_url.parse()?)?; - - server.wait(); - - // This will never return if the http server runs without errors - Ok(()) -} - -fn initialize_logger(log_level: String) { - env_logger::Builder::from_env(env_logger::Env::default()) - .parse(&log_level) - .init(); -} - -#[cfg(test)] -mod tests { - use super::execute; - - #[test] - fn invalid_argument() { - let command = vec!["whisper", "--foo=12"] - .into_iter() - .map(Into::into) - .collect::>(); - - assert!(execute(command).is_err()); - } - - #[test] - #[ignore] - fn privileged_port() { - let command = vec!["whisper", "--port=3"] - .into_iter() - .map(Into::into) - .collect::>(); - - assert!(execute(command).is_err()); - } - - #[test] - fn invalid_ip_address() { - let command = vec!["whisper", "--address=x.x.x.x"] - .into_iter() - .map(Into::into) - .collect::>(); - - assert!(execute(command).is_err()); - } - - #[test] - fn invalid_whisper_pool_size() { - let command = vec![ - "whisper", - "--whisper-pool-size=-100000000000000000000000000000000000000", - ] - .into_iter() - .map(Into::into) - .collect::>(); - - assert!(execute(command).is_err()); - } -} diff --git a/whisper/src/lib.rs b/whisper/src/lib.rs deleted file mode 100644 index a154a322a..000000000 --- a/whisper/src/lib.rs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2015-2019 Parity Technologies (UK) Ltd. -// This file is part of Parity Ethereum. - -// Parity Ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity Ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity Ethereum. If not, see . - -//! Whisper P2P messaging system as a DevP2P subprotocol, with RPC and Rust -//! interface. - -#![cfg_attr(feature = "time_checked_add", feature(time_checked_add))] -#![allow(deprecated)] - -extern crate byteorder; -extern crate ethcore_network as network; -extern crate ethereum_types; -extern crate ethkey; -extern crate hex; -extern crate memzero; -extern crate ordered_float; -extern crate parity_crypto as crypto; -extern crate parking_lot; -extern crate rand; -extern crate rlp; -extern crate serde; -extern crate slab; -extern crate smallvec; -extern crate tiny_keccak; - -extern crate jsonrpc_core; -extern crate jsonrpc_derive; -extern crate jsonrpc_pubsub; - -#[macro_use] -extern crate bitflags; - -#[macro_use] -extern crate log; - -extern crate serde_derive; - -#[cfg(not(time_checked_add))] -extern crate time_utils; - -#[cfg(test)] -extern crate serde_json; - -pub use self::{ - message::Message, - net::{MessageHandler, Network}, -}; - -pub mod message; -pub mod net; -pub mod rpc; diff --git a/whisper/src/message.rs b/whisper/src/message.rs deleted file mode 100644 index 7d1df9b34..000000000 --- a/whisper/src/message.rs +++ /dev/null @@ -1,576 +0,0 @@ -// Copyright 2015-2019 Parity Technologies (UK) Ltd. -// This file is part of Parity Ethereum. - -// Parity Ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity Ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity Ethereum. If not, see . - -//! Whisper message parsing, handlers, and construction. - -use std::{ - fmt, - time::{self, Duration, Instant, SystemTime}, -}; - -use ethereum_types::{H256, H512}; -use rlp::{self, DecoderError, Rlp, RlpStream}; -use smallvec::SmallVec; -use tiny_keccak::{keccak256, Keccak}; - -#[cfg(not(time_checked_add))] -use time_utils::CheckedSystemTime; - -/// Work-factor proved. Takes 3 parameters: size of message, time to live, -/// and hash. -/// -/// Panics if size or TTL is zero. -pub fn work_factor_proved(size: u64, ttl: u64, hash: H256) -> f64 { - assert!(size != 0 && ttl != 0); - - let leading_zeros = { - let leading_bytes = hash.iter().take_while(|&&x| x == 0).count(); - let remaining_leading_bits = hash - .get(leading_bytes) - .map_or(0, |byte| byte.leading_zeros() as usize); - (leading_bytes * 8) + remaining_leading_bits - }; - let spacetime = size as f64 * ttl as f64; - - 2.0_f64.powi(leading_zeros as i32) / spacetime -} - -/// A topic of a message. -#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct Topic(pub [u8; 4]); - -impl From<[u8; 4]> for Topic { - fn from(x: [u8; 4]) -> Self { - Topic(x) - } -} - -impl Topic { - /// set up to three bits in the 64-byte bloom passed. - /// - /// this takes 3 sets of 9 bits, treating each as an index in the range - /// 0..512 into the bloom and setting the corresponding bit in the bloom to 1. - pub fn bloom_into(&self, bloom: &mut H512) { - let data = &self.0; - for i in 0..3 { - let mut idx = data[i] as usize; - - if data[3] & (1 << i) != 0 { - idx += 256; - } - - debug_assert!(idx <= 511); - bloom[idx / 8] |= 1 << (7 - idx % 8); - } - } - - /// Get bloom for single topic. - pub fn bloom(&self) -> H512 { - let mut bloom = Default::default(); - self.bloom_into(&mut bloom); - bloom - } -} - -impl rlp::Encodable for Topic { - fn rlp_append(&self, s: &mut RlpStream) { - s.encoder().encode_value(&self.0); - } -} - -impl rlp::Decodable for Topic { - fn decode(rlp: &Rlp) -> Result { - use std::cmp; - - rlp.decoder() - .decode_value(|bytes| match bytes.len().cmp(&4) { - cmp::Ordering::Less => Err(DecoderError::RlpIsTooShort), - cmp::Ordering::Greater => Err(DecoderError::RlpIsTooBig), - cmp::Ordering::Equal => { - let mut t = [0u8; 4]; - t.copy_from_slice(bytes); - Ok(Topic(t)) - } - }) - } -} - -/// Calculate union of blooms for given topics. -pub fn bloom_topics(topics: &[Topic]) -> H512 { - let mut bloom = H512::default(); - for topic in topics { - topic.bloom_into(&mut bloom); - } - bloom -} - -/// Message errors. -#[derive(Debug)] -pub enum Error { - Decoder(DecoderError), - EmptyTopics, - LivesTooLong, - IssuedInFuture, - TimestampOverflow, - ZeroTTL, -} - -impl From for Error { - fn from(err: DecoderError) -> Self { - Error::Decoder(err) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::Decoder(ref err) => write!(f, "Failed to decode message: {}", err), - Error::LivesTooLong => write!(f, "Message claims to be issued before the unix epoch."), - Error::IssuedInFuture => write!(f, "Message issued in future."), - Error::ZeroTTL => write!(f, "Message live for zero time."), - Error::TimestampOverflow => write!(f, "Timestamp overflow"), - Error::EmptyTopics => write!(f, "Message has no topics."), - } - } -} - -fn append_topics<'a>(s: &'a mut RlpStream, topics: &[Topic]) -> &'a mut RlpStream { - if topics.len() == 1 { - s.append(&topics[0]) - } else { - s.append_list(&topics) - } -} - -fn decode_topics(rlp: Rlp) -> Result, DecoderError> { - if rlp.is_list() { - rlp.iter().map(|r| r.as_val::()).collect() - } else { - rlp.as_val().map(|t| SmallVec::from_slice(&[t])) - } -} - -// Raw envelope struct. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Envelope { - /// Expiry timestamp - pub expiry: u64, - /// Time-to-live in seconds - pub ttl: u64, - /// series of 4-byte topics. - pub topics: SmallVec<[Topic; 4]>, - /// The message contained within. - pub data: Vec, - /// Arbitrary value used to target lower PoW hash. - pub nonce: u64, -} - -impl Envelope { - /// Whether the message is multi-topic. Only relay these to Parity peers. - pub fn is_multitopic(&self) -> bool { - self.topics.len() != 1 - } - - fn proving_hash(&self) -> H256 { - use byteorder::{BigEndian, ByteOrder}; - - let mut buf = [0; 32]; - - let mut stream = RlpStream::new_list(4); - stream.append(&self.expiry).append(&self.ttl); - - append_topics(&mut stream, &self.topics).append(&self.data); - - let mut digest = Keccak::new_keccak256(); - digest.update(&*stream.drain()); - digest.update(&{ - let mut nonce_bytes = [0u8; 8]; - BigEndian::write_u64(&mut nonce_bytes, self.nonce); - - nonce_bytes - }); - - digest.finalize(&mut buf); - H256(buf) - } -} - -impl rlp::Encodable for Envelope { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(5).append(&self.expiry).append(&self.ttl); - - append_topics(s, &self.topics) - .append(&self.data) - .append(&self.nonce); - } -} - -impl rlp::Decodable for Envelope { - fn decode(rlp: &Rlp) -> Result { - if rlp.item_count()? != 5 { - return Err(DecoderError::RlpIncorrectListLen); - } - - Ok(Envelope { - expiry: rlp.val_at(0)?, - ttl: rlp.val_at(1)?, - topics: decode_topics(rlp.at(2)?)?, - data: rlp.val_at(3)?, - nonce: rlp.val_at(4)?, - }) - } -} - -/// Message creation parameters. -/// Pass this to `Message::create` to make a message. -pub struct CreateParams { - /// time-to-live in seconds. - pub ttl: u64, - /// payload data. - pub payload: Vec, - /// Topics. May not be empty. - pub topics: Vec, - /// How many milliseconds to spend proving work. - pub work: u64, -} - -/// A whisper message. This is a checked message carrying around metadata. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Message { - envelope: Envelope, - bloom: H512, - hash: H256, - encoded_size: usize, -} - -impl Message { - /// Create a message from creation parameters. - /// Panics if TTL is 0. - pub fn create(params: CreateParams) -> Result { - use byteorder::{BigEndian, ByteOrder}; - use rand::{Rng, SeedableRng, XorShiftRng}; - - if params.topics.is_empty() { - return Err(Error::EmptyTopics); - } - - let mut rng = { - let mut thread_rng = ::rand::thread_rng(); - XorShiftRng::from_seed(thread_rng.gen::<[u32; 4]>()) - }; - - assert!(params.ttl > 0); - - let expiry = { - let since_epoch = SystemTime::now() - .checked_add(Duration::from_secs(params.ttl)) - .and_then(|t| t.checked_add(Duration::from_millis(params.work))) - .ok_or(Error::TimestampOverflow)? - .duration_since(time::UNIX_EPOCH) - .expect("time after now is after unix epoch; qed"); - - // round up the sub-second to next whole second. - since_epoch.as_secs() - + if since_epoch.subsec_nanos() == 0 { - 0 - } else { - 1 - } - }; - - let start_digest = { - let mut stream = RlpStream::new_list(4); - stream.append(&expiry).append(¶ms.ttl); - append_topics(&mut stream, ¶ms.topics).append(¶ms.payload); - - let mut digest = Keccak::new_keccak256(); - digest.update(&*stream.drain()); - digest - }; - - let mut buf = [0; 32]; - let mut try_nonce = move |nonce: &[u8; 8]| { - let mut digest = start_digest.clone(); - digest.update(&nonce[..]); - digest.finalize(&mut buf[..]); - - buf.clone() - }; - - let mut nonce: [u8; 8] = rng.gen(); - let mut best_found = try_nonce(&nonce); - - let start = Instant::now(); - - while start.elapsed() <= Duration::from_millis(params.work) { - let temp_nonce = rng.gen(); - let hash = try_nonce(&temp_nonce); - - if hash < best_found { - nonce = temp_nonce; - best_found = hash; - } - } - - let envelope = Envelope { - expiry: expiry, - ttl: params.ttl, - topics: params.topics.into_iter().collect(), - data: params.payload, - nonce: BigEndian::read_u64(&nonce[..]), - }; - - debug_assert_eq!(H256(best_found.clone()), envelope.proving_hash()); - - let encoded = ::rlp::encode(&envelope); - - Ok(Message::from_components( - envelope, - encoded.len(), - H256(keccak256(&encoded)), - SystemTime::now(), - ) - .expect("Message generated here known to be valid; qed")) - } - - /// Decode message from RLP and check for validity against system time. - pub fn decode(rlp: Rlp, now: SystemTime) -> Result { - let envelope: Envelope = rlp.as_val()?; - let encoded_size = rlp.as_raw().len(); - let hash = H256(keccak256(rlp.as_raw())); - - Message::from_components(envelope, encoded_size, hash, now) - } - - // create message from envelope, hash, and encoded size. - // does checks for validity. - fn from_components( - envelope: Envelope, - size: usize, - hash: H256, - now: SystemTime, - ) -> Result { - const LEEWAY_SECONDS: u64 = 2; - - if envelope.expiry <= envelope.ttl { - return Err(Error::LivesTooLong); - } - if envelope.ttl == 0 { - return Err(Error::ZeroTTL); - } - - if envelope.topics.is_empty() { - return Err(Error::EmptyTopics); - } - - let issue_time_adjusted = - Duration::from_secs((envelope.expiry - envelope.ttl).saturating_sub(LEEWAY_SECONDS)); - - let issue_time_adjusted = time::UNIX_EPOCH - .checked_add(issue_time_adjusted) - .ok_or(Error::TimestampOverflow)?; - - if issue_time_adjusted > now { - return Err(Error::IssuedInFuture); - } - - // other validity checks? - let bloom = bloom_topics(&envelope.topics); - - Ok(Message { - envelope: envelope, - bloom: bloom, - hash: hash, - encoded_size: size, - }) - } - - /// Get a reference to the envelope. - pub fn envelope(&self) -> &Envelope { - &self.envelope - } - - /// Get the encoded size of the envelope. - pub fn encoded_size(&self) -> usize { - self.encoded_size - } - - /// Get a uniquely identifying hash for the message. - pub fn hash(&self) -> &H256 { - &self.hash - } - - /// Get the bloom filter of the topics - pub fn bloom(&self) -> &H512 { - &self.bloom - } - - /// Get the work proved by the hash. - pub fn work_proved(&self) -> f64 { - let proving_hash = self.envelope.proving_hash(); - - work_factor_proved(self.encoded_size as _, self.envelope.ttl, proving_hash) - } - - /// Get the expiry time. - pub fn expiry(&self) -> Option { - time::UNIX_EPOCH.checked_add(Duration::from_secs(self.envelope.expiry)) - } - - /// Get the topics. - pub fn topics(&self) -> &[Topic] { - &self.envelope.topics - } - - /// Get the message data. - pub fn data(&self) -> &[u8] { - &self.envelope.data - } -} - -#[cfg(test)] -mod tests { - use super::*; - use ethereum_types::H256; - use rlp::Rlp; - use smallvec::SmallVec; - use std::time::{self, Duration, SystemTime}; - - fn unix_time(x: u64) -> SystemTime { - time::UNIX_EPOCH + Duration::from_secs(x) - } - - #[test] - fn create_message() { - assert!(Message::create(CreateParams { - ttl: 100, - payload: vec![1, 2, 3, 4], - topics: vec![Topic([1, 2, 1, 2])], - work: 50, - }) - .is_ok()); - } - - #[test] - fn round_trip() { - let envelope = Envelope { - expiry: 100_000, - ttl: 30, - data: vec![9; 256], - topics: SmallVec::from_slice(&[Default::default()]), - nonce: 1010101, - }; - - let encoded = ::rlp::encode(&envelope); - let decoded = ::rlp::decode(&encoded).expect("failure decoding Envelope"); - - assert_eq!(envelope, decoded) - } - - #[test] - fn round_trip_multitopic() { - let envelope = Envelope { - expiry: 100_000, - ttl: 30, - data: vec![9; 256], - topics: SmallVec::from_slice(&[Default::default(), Topic([1, 2, 3, 4])]), - nonce: 1010101, - }; - - let encoded = ::rlp::encode(&envelope); - let decoded = ::rlp::decode(&encoded).expect("failure decoding Envelope"); - - assert_eq!(envelope, decoded) - } - - #[test] - fn passes_checks() { - let envelope = Envelope { - expiry: 100_000, - ttl: 30, - data: vec![9; 256], - topics: SmallVec::from_slice(&[Default::default()]), - nonce: 1010101, - }; - - let encoded = ::rlp::encode(&envelope); - - for i in 0..30 { - let now = unix_time(100_000 - i); - Message::decode(Rlp::new(&*encoded), now).unwrap(); - } - } - - #[test] - #[should_panic] - fn future_message() { - let envelope = Envelope { - expiry: 100_000, - ttl: 30, - data: vec![9; 256], - topics: SmallVec::from_slice(&[Default::default()]), - nonce: 1010101, - }; - - let encoded = ::rlp::encode(&envelope); - - let now = unix_time(100_000 - 1_000); - Message::decode(Rlp::new(&*encoded), now).unwrap(); - } - - #[test] - #[should_panic] - fn pre_epoch() { - let envelope = Envelope { - expiry: 100_000, - ttl: 200_000, - data: vec![9; 256], - topics: SmallVec::from_slice(&[Default::default()]), - nonce: 1010101, - }; - - let encoded = ::rlp::encode(&envelope); - - let now = unix_time(95_000); - Message::decode(Rlp::new(&*encoded), now).unwrap(); - } - - #[test] - fn work_factor() { - // 256 leading zeros -> 2^256 / 1 - assert_eq!( - work_factor_proved(1, 1, H256::from(0)), - 115792089237316200000000000000000000000000000000000000000000000000000000000000.0 - ); - // 255 leading zeros -> 2^255 / 1 - assert_eq!( - work_factor_proved(1, 1, H256::from(1)), - 57896044618658100000000000000000000000000000000000000000000000000000000000000.0 - ); - // 0 leading zeros -> 2^0 / 1 - assert_eq!( - work_factor_proved( - 1, - 1, - serde_json::from_str::( - "\"0xff00000000000000000000000000000000000000000000000000000000000000\"" - ) - .unwrap() - ), - 1.0 - ); - } -} diff --git a/whisper/src/net/mod.rs b/whisper/src/net/mod.rs deleted file mode 100644 index 90f7ca212..000000000 --- a/whisper/src/net/mod.rs +++ /dev/null @@ -1,766 +0,0 @@ -// Copyright 2015-2019 Parity Technologies (UK) Ltd. -// This file is part of Parity Ethereum. - -// Parity Ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity Ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity Ethereum. If not, see . - -//! Whisper messaging system as a DevP2P subprotocol. - -use std::{ - cmp::Ordering, - collections::{HashMap, HashSet}, - fmt, - sync::Arc, - time::{Duration, SystemTime}, -}; - -use ethereum_types::{H256, H512}; -use network::{self, NetworkContext, NodeId, PeerId, ProtocolId, TimerToken}; -use ordered_float::OrderedFloat; -use parking_lot::{Mutex, RwLock}; -use rlp::{DecoderError, Rlp, RlpStream}; - -use message::{Error as MessageError, Message}; - -#[cfg(test)] -mod tests; - -// how often periodic relays are. when messages are imported -// we directly broadcast. -const RALLY_TOKEN: TimerToken = 1; -const RALLY_TIMEOUT: Duration = Duration::from_millis(2500); - -/// Current protocol version. -pub const PROTOCOL_VERSION: usize = 6; - -/// Number of packets. A bunch are reserved. -const PACKET_COUNT: u8 = 128; - -/// Supported protocol versions. -pub const SUPPORTED_VERSIONS: &'static [(u8, u8)] = &[(PROTOCOL_VERSION as u8, PACKET_COUNT)]; - -// maximum tolerated delay between messages packets. -const MAX_TOLERATED_DELAY: Duration = Duration::from_millis(5000); - -/// Whisper protocol ID -pub const PROTOCOL_ID: ::network::ProtocolId = *b"shh"; - -/// Parity-whisper protocol ID -/// Current parity-specific extensions: -/// - Multiple topics in packet. -pub const PARITY_PROTOCOL_ID: ::network::ProtocolId = *b"pwh"; - -mod packet { - pub const STATUS: u8 = 0; - pub const MESSAGES: u8 = 1; - pub const POW_REQUIREMENT: u8 = 2; - pub const TOPIC_FILTER: u8 = 3; - - // 126, 127 for mail server stuff we will never implement here. -} - -/// Handles messages within a single packet. -pub trait MessageHandler: Send + Sync { - /// Evaluate the message and handle it. - /// - /// The same message will not be passed twice. - /// Heavy handling should be done asynchronously. - /// If there is a significant overhead in this thread, then an attacker - /// can determine which kinds of messages we are listening for. - fn handle_messages(&self, message: &[Message]); -} - -// errors in importing a whisper message. -#[derive(Debug)] -enum Error { - Decoder(DecoderError), - Network(network::Error), - Message(MessageError), - UnknownPeer(PeerId), - UnexpectedMessage, - InvalidPowReq, -} - -impl From for Error { - fn from(err: DecoderError) -> Self { - Error::Decoder(err) - } -} - -impl From for Error { - fn from(err: network::Error) -> Self { - Error::Network(err) - } -} - -impl From for Error { - fn from(err: MessageError) -> Self { - Error::Message(err) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::Decoder(ref err) => write!(f, "Failed to decode packet: {}", err), - Error::Network(ref err) => write!(f, "Network error: {}", err), - Error::Message(ref err) => write!(f, "Error decoding message: {}", err), - Error::UnknownPeer(ref id) => write!(f, "Message received from unknown peer: {}", id), - Error::UnexpectedMessage => write!(f, "Unexpected message."), - Error::InvalidPowReq => write!(f, "Peer sent invalid PoW requirement."), - } - } -} - -// sorts by work proved, descending. -#[derive(PartialEq, Eq)] -struct SortedEntry { - slab_id: usize, - work_proved: OrderedFloat, - expiry: SystemTime, -} - -impl Ord for SortedEntry { - fn cmp(&self, other: &SortedEntry) -> Ordering { - self.work_proved.cmp(&other.work_proved) - } -} - -impl PartialOrd for SortedEntry { - fn partial_cmp(&self, other: &SortedEntry) -> Option { - Some(self.cmp(other)) - } -} - -// stores messages by two metrics: expiry and PoW rating -// when full, will accept messages above the minimum stored. -struct Messages { - slab: ::slab::Slab, - sorted: Vec, - known: HashSet, - removed_hashes: Vec, - cumulative_size: usize, - ideal_size: usize, -} - -impl Messages { - fn new(ideal_size: usize) -> Self { - Messages { - slab: ::slab::Slab::with_capacity(0), - sorted: Vec::new(), - known: HashSet::new(), - removed_hashes: Vec::new(), - cumulative_size: 0, - ideal_size: ideal_size, - } - } - - // reserve space for additional elements. - fn reserve(&mut self, additional: usize) { - self.slab.reserve_exact(additional); - self.sorted.reserve(additional); - self.known.reserve(additional); - } - - // whether a message is not known and within the bounds of PoW. - fn may_accept(&self, message: &Message) -> bool { - !self.known.contains(message.hash()) && { - self.sorted.last().map_or(true, |entry| { - let work_proved = OrderedFloat(message.work_proved()); - OrderedFloat(self.slab[entry.slab_id].work_proved()) < work_proved - }) - } - } - - // insert a message into the store. for best performance, - // call `reserve` before inserting a bunch. - // - fn insert(&mut self, message: Message) -> bool { - if !self.known.insert(message.hash().clone()) { - return false; - } - - let work_proved = OrderedFloat(message.work_proved()); - - // pop off entries by low PoW until we have enough space for the higher - // PoW message being inserted. - let size_upon_insertion = self.cumulative_size + message.encoded_size(); - if size_upon_insertion >= self.ideal_size { - let diff = size_upon_insertion - self.ideal_size; - let mut found_diff = 0; - for entry in self.sorted.iter().rev() { - if found_diff >= diff { - break; - } - - // if we encounter a message with at least the PoW we're looking - // at, don't push that message out. - if entry.work_proved >= work_proved { - return false; - } - found_diff += self.slab[entry.slab_id].encoded_size(); - } - - // message larger than ideal size. - if found_diff < diff { - return false; - } - - while found_diff > 0 { - let entry = self.sorted.pop() - .expect("found_diff built by traversing entries; therefore that many entries exist; qed"); - - let message = self - .slab - .remove(entry.slab_id) - .expect("sorted entry slab IDs always filled; qed"); - - found_diff -= message.encoded_size(); - - self.cumulative_size -= message.encoded_size(); - self.known.remove(message.hash()); - self.removed_hashes.push(message.hash().clone()); - } - } - - let expiry = match message.expiry() { - Some(time) => time, - _ => return false, - }; - - self.cumulative_size += message.encoded_size(); - - if !self.slab.has_available() { - self.slab.reserve_exact(1) - } - let id = self - .slab - .insert(message) - .expect("just ensured enough space in slab; qed"); - - let sorted_entry = SortedEntry { - slab_id: id, - work_proved, - expiry, - }; - - match self.sorted.binary_search(&sorted_entry) { - Ok(idx) | Err(idx) => self.sorted.insert(idx, sorted_entry), - } - - true - } - - // prune expired messages, and then prune low proof-of-work messages - // until below ideal size. - fn prune(&mut self, now: SystemTime) -> Vec { - { - let slab = &mut self.slab; - let known = &mut self.known; - let cumulative_size = &mut self.cumulative_size; - let ideal_size = &self.ideal_size; - let removed = &mut self.removed_hashes; - - // first pass, we look just at expired entries. - let all_expired = self - .sorted - .iter() - .filter(|entry| entry.expiry <= now) - .map(|x| (true, x)); - - // second pass, we look at entries which aren't expired but in order - // by PoW - let low_proof = self - .sorted - .iter() - .rev() - .filter(|entry| entry.expiry > now) - .map(|x| (false, x)); - - for (is_expired, entry) in all_expired.chain(low_proof) { - // break once we've removed all expired entries - // or have taken enough low-work entries. - if !is_expired && *cumulative_size <= *ideal_size { - break; - } - - let message = slab.remove(entry.slab_id).expect( - "references to ID kept upon creation; only destroyed upon removal; qed", - ); - - known.remove(message.hash()); - removed.push(message.hash().clone()); - - *cumulative_size -= message.encoded_size(); - } - } - - // clear all the sorted entries we removed from slab. - let slab = &self.slab; - self.sorted.retain(|entry| slab.contains(entry.slab_id)); - - ::std::mem::replace(&mut self.removed_hashes, Vec::new()) - } - - fn iter(&self) -> ::slab::Iter { - self.slab.iter() - } - - fn is_full(&self) -> bool { - self.cumulative_size >= self.ideal_size - } - - fn status(&self) -> PoolStatus { - PoolStatus { - required_pow: if self.is_full() { - self.sorted.last().map(|entry| entry.work_proved.0) - } else { - None - }, - message_count: self.sorted.len(), - cumulative_size: self.cumulative_size, - target_size: self.ideal_size, - } - } -} - -enum State { - Unconfirmed(SystemTime), // awaiting status packet. - Confirmed, -} - -#[allow(dead_code)] // for node key. this will be useful for topic routing. -struct Peer { - node_key: NodeId, - state: State, - known_messages: HashSet, - topic_filter: Option, - pow_requirement: f64, - is_parity: bool, - _protocol_version: usize, -} - -impl Peer { - // note that a message has been evicted from the queue. - fn note_evicted(&mut self, messages: &[H256]) { - for message_hash in messages { - self.known_messages.remove(message_hash); - } - } - - // whether this peer will accept the message. - fn will_accept(&self, message: &Message) -> bool { - if self.known_messages.contains(message.hash()) { - return false; - } - - // only parity peers will accept multitopic messages. - if message.envelope().is_multitopic() && !self.is_parity { - return false; - } - if message.work_proved() < self.pow_requirement { - return false; - } - - self.topic_filter.as_ref().map_or(true, |filter| { - &(filter & message.bloom()) == message.bloom() - }) - } - - // note a message as known. returns false if it was already - // known, true otherwise. - fn note_known(&mut self, message: &Message) -> bool { - self.known_messages.insert(message.hash().clone()) - } - - fn set_topic_filter(&mut self, topic: H512) { - self.topic_filter = Some(topic); - } - - fn set_pow_requirement(&mut self, pow_requirement: f64) { - self.pow_requirement = pow_requirement; - } - - fn can_send_messages(&self) -> bool { - match self.state { - State::Unconfirmed(_) => false, - State::Confirmed => true, - } - } -} - -/// Pool status. -pub struct PoolStatus { - /// Required PoW to be accepted into the pool - pub required_pow: Option, - /// Number of messages in the pool. - pub message_count: usize, - /// Cumulative size of the messages in the pool - pub cumulative_size: usize, - /// Target size of the pool. - pub target_size: usize, -} - -/// Generic network context. -pub trait Context { - /// Disconnect a peer. - fn disconnect_peer(&self, PeerId); - /// Disable a peer. - fn disable_peer(&self, PeerId); - /// Get a peer's node key. - fn node_key(&self, PeerId) -> Option; - /// Get a peer's protocol version for given protocol. - fn protocol_version(&self, ProtocolId, PeerId) -> Option; - /// Send message to peer. - fn send(&self, PeerId, u8, Vec); -} - -impl Context for T -where - T: ?Sized + NetworkContext, -{ - fn disconnect_peer(&self, peer: PeerId) { - NetworkContext::disconnect_peer(self, peer); - } - fn disable_peer(&self, peer: PeerId) { - NetworkContext::disable_peer(self, peer) - } - fn node_key(&self, peer: PeerId) -> Option { - self.session_info(peer).and_then(|info| info.id) - } - fn protocol_version(&self, proto_id: ProtocolId, peer: PeerId) -> Option { - NetworkContext::protocol_version(self, proto_id, peer) - } - - fn send(&self, peer: PeerId, packet_id: u8, message: Vec) { - if let Err(e) = NetworkContext::send(self, peer, packet_id, message) { - debug!(target: "whisper", "Failed to send packet {} to peer {}: {}", - packet_id, peer, e); - - self.disconnect_peer(peer) - } - } -} - -/// The whisper network protocol handler. -pub struct Network { - messages: Arc>, - handler: T, - peers: RwLock>>, -} - -// public API. -impl Network { - /// Create a new network handler. - pub fn new(messages_size_bytes: usize, handler: T) -> Self { - Network { - messages: Arc::new(RwLock::new(Messages::new(messages_size_bytes))), - handler: handler, - peers: RwLock::new(HashMap::new()), - } - } - - /// Post a message to the whisper network to be relayed. - pub fn post_message(&self, message: Message, context: &C) -> bool - where - T: MessageHandler, - { - let ok = self.messages.write().insert(message); - if ok { - self.rally(context) - } - ok - } - - /// Get number of messages and amount of memory used by them. - pub fn pool_status(&self) -> PoolStatus { - self.messages.read().status() - } -} - -impl Network { - fn rally(&self, io: &C) { - // cannot be greater than 16MB (protocol limitation) - const MAX_MESSAGES_PACKET_SIZE: usize = 8 * 1024 * 1024; - - // prune messages. - let now = SystemTime::now(); - let pruned_hashes = self.messages.write().prune(now); - - let messages = self.messages.read(); - let peers = self.peers.read(); - - // send each peer a packet with new messages it may find relevant. - for (peer_id, peer) in peers.iter() { - let mut peer_data = peer.lock(); - peer_data.note_evicted(&pruned_hashes); - - let punish_timeout = |last_activity: &SystemTime| { - if *last_activity + MAX_TOLERATED_DELAY <= now { - debug!(target: "whisper", "Disconnecting peer {} due to excessive timeout.", peer_id); - io.disconnect_peer(*peer_id); - } - }; - - // check timeouts and skip peers who we can't send a rally to. - match peer_data.state { - State::Unconfirmed(ref time) => { - punish_timeout(time); - continue; - } - State::Confirmed => {} - } - - // construct packet, skipping messages the peer won't accept. - let mut stream = RlpStream::new(); - stream.begin_unbounded_list(); - - for message in messages.iter() { - if !peer_data.will_accept(message) { - continue; - } - - if stream.estimate_size(message.encoded_size()) > MAX_MESSAGES_PACKET_SIZE { - break; - } - - peer_data.note_known(message); - stream.append(message.envelope()); - } - - stream.complete_unbounded_list(); - - io.send(*peer_id, packet::MESSAGES, stream.out()); - } - } - - // handle status packet from peer. - fn on_status(&self, peer: &PeerId, _status: Rlp) -> Result<(), Error> { - let peers = self.peers.read(); - - match peers.get(peer) { - Some(peer) => { - peer.lock().state = State::Confirmed; - Ok(()) - } - None => { - debug!(target: "whisper", "Received message from unknown peer."); - Err(Error::UnknownPeer(*peer)) - } - } - } - - fn on_messages(&self, peer: &PeerId, message_packet: Rlp) -> Result<(), Error> { - let mut messages_vec = { - let peers = self.peers.read(); - let peer = match peers.get(peer) { - Some(peer) => peer, - None => { - debug!(target: "whisper", "Received message from unknown peer."); - return Err(Error::UnknownPeer(*peer)); - } - }; - - let mut peer = peer.lock(); - - if !peer.can_send_messages() { - return Err(Error::UnexpectedMessage); - } - - let now = SystemTime::now(); - let mut messages_vec = message_packet - .iter() - .map(|rlp| Message::decode(rlp, now)) - .collect::, _>>()?; - - if messages_vec.is_empty() { - return Ok(()); - } - - // disallow duplicates in packet. - messages_vec.retain(|message| peer.note_known(&message)); - messages_vec - }; - - // import for relaying. - let mut messages = self.messages.write(); - - messages_vec.retain(|message| messages.may_accept(&message)); - messages.reserve(messages_vec.len()); - - self.handler.handle_messages(&messages_vec); - - for message in messages_vec { - messages.insert(message); - } - - Ok(()) - } - - fn on_pow_requirement(&self, peer: &PeerId, requirement: Rlp) -> Result<(), Error> { - use byteorder::{BigEndian, ByteOrder}; - - let peers = self.peers.read(); - match peers.get(peer) { - Some(peer) => { - let mut peer = peer.lock(); - - if let State::Unconfirmed(_) = peer.state { - return Err(Error::UnexpectedMessage); - } - let bytes: Vec = requirement.as_val()?; - if bytes.len() != ::std::mem::size_of::() { - return Err(Error::InvalidPowReq); - } - - // as of byteorder 1.1.0, this is always defined. - let req = BigEndian::read_f64(&bytes[..]); - - if !req.is_normal() { - return Err(Error::InvalidPowReq); - } - - peer.set_pow_requirement(req); - } - None => { - debug!(target: "whisper", "Received message from unknown peer."); - return Err(Error::UnknownPeer(*peer)); - } - } - - Ok(()) - } - - fn on_topic_filter(&self, peer: &PeerId, filter: Rlp) -> Result<(), Error> { - let peers = self.peers.read(); - match peers.get(peer) { - Some(peer) => { - let mut peer = peer.lock(); - - if let State::Unconfirmed(_) = peer.state { - return Err(Error::UnexpectedMessage); - } - - peer.set_topic_filter(filter.as_val()?) - } - None => { - debug!(target: "whisper", "Received message from unknown peer."); - return Err(Error::UnknownPeer(*peer)); - } - } - - Ok(()) - } - - fn on_connect(&self, io: &C, peer: &PeerId) { - trace!(target: "whisper", "Connecting peer {}", peer); - - let node_key = match io.node_key(*peer) { - Some(node_key) => node_key, - None => { - debug!(target: "whisper", "Disconnecting peer {}, who has no node key.", peer); - io.disable_peer(*peer); - return; - } - }; - - let version = match io.protocol_version(PROTOCOL_ID, *peer) { - Some(version) => version as usize, - None => { - io.disable_peer(*peer); - return; - } - }; - - self.peers.write().insert( - *peer, - Mutex::new(Peer { - node_key: node_key, - state: State::Unconfirmed(SystemTime::now()), - known_messages: HashSet::new(), - topic_filter: None, - pow_requirement: 0f64, - is_parity: io.protocol_version(PARITY_PROTOCOL_ID, *peer).is_some(), - _protocol_version: version, - }), - ); - - io.send(*peer, packet::STATUS, ::rlp::EMPTY_LIST_RLP.to_vec()); - } - - fn on_packet(&self, io: &C, peer: &PeerId, packet_id: u8, data: &[u8]) { - let rlp = Rlp::new(data); - let res = match packet_id { - packet::STATUS => self.on_status(peer, rlp), - packet::MESSAGES => self.on_messages(peer, rlp), - packet::POW_REQUIREMENT => self.on_pow_requirement(peer, rlp), - packet::TOPIC_FILTER => self.on_topic_filter(peer, rlp), - _ => Ok(()), // ignore unknown packets. - }; - - if let Err(e) = res { - trace!(target: "whisper", "Disabling peer due to misbehavior: {}", e); - io.disable_peer(*peer); - } - } - - fn on_disconnect(&self, peer: &PeerId) { - trace!(target: "whisper", "Disconnecting peer {}", peer); - let _ = self.peers.write().remove(peer); - } -} - -impl ::network::NetworkProtocolHandler for Network { - fn initialize(&self, io: &dyn NetworkContext) { - // set up broadcast timer (< 1s) - io.register_timer(RALLY_TOKEN, RALLY_TIMEOUT) - .expect("Failed to initialize message rally timer"); - } - - fn read(&self, io: &dyn NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { - self.on_packet(io, peer, packet_id, data) - } - - fn connected(&self, io: &dyn NetworkContext, peer: &PeerId) { - // peer with higher ID should begin rallying. - self.on_connect(io, peer) - } - - fn disconnected(&self, _io: &dyn NetworkContext, peer: &PeerId) { - self.on_disconnect(peer) - } - - fn timeout(&self, io: &dyn NetworkContext, timer: TimerToken) { - // rally with each peer and handle timeouts. - match timer { - RALLY_TOKEN => self.rally(io), - other => debug!(target: "whisper", "Timeout triggered on unknown token {}", other), - } - } -} - -/// Dummy subprotocol used for parity extensions. -#[derive(Debug, Copy, Clone)] -pub struct ParityExtensions; - -impl ::network::NetworkProtocolHandler for ParityExtensions { - fn initialize(&self, _io: &dyn NetworkContext) {} - - fn read(&self, _io: &dyn NetworkContext, _peer: &PeerId, _id: u8, _msg: &[u8]) {} - - fn connected(&self, _io: &dyn NetworkContext, _peer: &PeerId) {} - - fn disconnected(&self, _io: &dyn NetworkContext, _peer: &PeerId) {} - - fn timeout(&self, _io: &dyn NetworkContext, _timer: TimerToken) {} -} diff --git a/whisper/src/net/tests.rs b/whisper/src/net/tests.rs deleted file mode 100644 index 9f00f28a3..000000000 --- a/whisper/src/net/tests.rs +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2015-2019 Parity Technologies (UK) Ltd. -// This file is part of Parity Ethereum. - -// Parity Ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity Ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity Ethereum. If not, see . - -//! Tests for the whisper network module. - -use std::{collections::HashSet, sync::mpsc}; - -use network::{NodeId, PeerId}; -use parking_lot::Mutex; - -use super::*; -use message::{CreateParams, Message}; - -struct TestHandler(Mutex>); - -impl MessageHandler for TestHandler { - fn handle_messages(&self, messages: &[Message]) { - let tx = self.0.lock(); - for message in messages { - let _ = tx.send(message.clone()); - } - } -} - -struct TestPeer { - network: Network, - recv: mpsc::Receiver, - disconnected: Mutex>, -} - -impl TestPeer { - fn create() -> Self { - let (tx, rx) = mpsc::channel(); - - TestPeer { - network: Network::new(10 * 1024 * 1024, TestHandler(Mutex::new(tx))), - recv: rx, - disconnected: Mutex::new(HashSet::new()), - } - } -} - -struct TestNetwork { - peers: Vec, -} - -impl TestNetwork { - fn new(n_peers: usize) -> Self { - let unconnected_peers: Vec<_> = (0..n_peers).map(|_| TestPeer::create()).collect(); - for i in 0..n_peers { - for j in (i + 1)..n_peers { - let (peer1, peer2) = (&unconnected_peers[i], &unconnected_peers[j]); - let ctx1 = TestContext::new(&unconnected_peers, i); - let ctx2 = TestContext::new(&unconnected_peers, j); - - peer1.network.on_connect(&ctx1, &j); - peer2.network.on_connect(&ctx2, &i); - } - } - - TestNetwork { - peers: unconnected_peers, - } - } - - fn post_message_from(&self, id: PeerId, msg: Message) { - self.peers[id] - .network - .post_message(msg, &TestContext::new(&self.peers, id)); - } -} - -enum Event { - Disconnect(PeerId, PeerId), - Send(PeerId, PeerId, u8, Vec), -} - -struct TestContext<'a> { - peers: &'a [TestPeer], - local_id: PeerId, - events: Mutex>, -} - -impl<'a> TestContext<'a> { - fn new(peers: &'a [TestPeer], local_id: PeerId) -> Self { - TestContext { - peers, - local_id, - events: Mutex::new(Vec::new()), - } - } -} - -impl<'a> Context for TestContext<'a> { - fn disconnect_peer(&self, id: PeerId) { - self.events - .lock() - .push(Event::Disconnect(self.local_id, id)); - } - - fn disable_peer(&self, id: PeerId) { - self.events - .lock() - .push(Event::Disconnect(self.local_id, id)); - } - - fn node_key(&self, peer: PeerId) -> Option { - let mut id = NodeId::default(); - id[0] = peer as _; - Some(id) - } - - fn protocol_version(&self, id: ::network::ProtocolId, _peer: PeerId) -> Option { - if &id == b"shh" || &id == b"pwh" { - Some(PROTOCOL_VERSION as _) - } else { - None - } - } - - fn send(&self, peer: PeerId, packet: u8, data: Vec) { - self.events - .lock() - .push(Event::Send(self.local_id, peer, packet, data)); - } -} - -impl<'a> Drop for TestContext<'a> { - fn drop(&mut self) { - let events = self.events.get_mut(); - while !events.is_empty() { - let mut deferred = Vec::new(); - for event in events.drain(..) { - match event { - Event::Disconnect(from, target) => { - self.peers[from].network.on_disconnect(&target); - self.peers[target].network.on_disconnect(&from); - - self.peers[from].disconnected.lock().insert(target); - self.peers[target].disconnected.lock().insert(from); - } - Event::Send(from, target, packet, data) => { - if self.peers[from].disconnected.lock().contains(&target) { - continue; - } - - let mut inner_ctx = TestContext::new(self.peers, target); - - self.peers[target] - .network - .on_packet(&inner_ctx, &from, packet, &data[..]); - - // don't recursively apply disconnects or new messages - // from the receiver's actions yet. - let inner_events = - ::std::mem::replace(inner_ctx.events.get_mut(), Vec::new()); - deferred.extend(inner_events); - } - } - } - - events.extend(deferred); - } - } -} - -#[test] -fn message_gets_relayed() { - let network = TestNetwork::new(5); - let message = Message::create(CreateParams { - ttl: 500, - payload: b"this is my payload, pal".to_vec(), - topics: vec![[0, 1, 2, 3].into()], - work: 25, - }) - .unwrap(); - - network.post_message_from(0, message.clone()); - - assert!(network.peers[0].recv.try_recv().is_err()); - - for i in 1..5 { - assert_eq!(network.peers[i].recv.try_recv().unwrap(), message); - } -} diff --git a/whisper/src/rpc/crypto.rs b/whisper/src/rpc/crypto.rs deleted file mode 100644 index c14bb7f1d..000000000 --- a/whisper/src/rpc/crypto.rs +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright 2015-2019 Parity Technologies (UK) Ltd. -// This file is part of Parity Ethereum. - -// Parity Ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity Ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity Ethereum. If not, see . - -//! Encryption schemes supported by RPC layer. - -use crypto::aes_gcm::{Decryptor, Encryptor}; -use ethereum_types::H256; -use ethkey::{self, crypto::ecies, Public, Secret}; -use memzero::Memzero; - -/// Length of AES key -pub const AES_KEY_LEN: usize = 32; -/// Length of AES nonce (IV) -pub const AES_NONCE_LEN: usize = 12; - -// nonce used for encryption when broadcasting -const BROADCAST_IV: [u8; AES_NONCE_LEN] = [0xff; AES_NONCE_LEN]; - -// how to encode aes key/nonce. -enum AesEncode { - AppendedNonce, // receiver known, random nonce appended. - OnTopics(Vec), // receiver knows topics but not key. nonce global. -} - -enum EncryptionInner { - AES(Memzero<[u8; AES_KEY_LEN]>, [u8; AES_NONCE_LEN], AesEncode), - ECIES(Public), -} - -/// Encryption good for single usage. -pub struct EncryptionInstance(EncryptionInner); - -impl EncryptionInstance { - /// ECIES encryption using public key. Fails if invalid public key. - pub fn ecies(public: Public) -> Result { - if !ethkey::public_is_valid(&public) { - return Err("Invalid public key"); - } - - Ok(EncryptionInstance(EncryptionInner::ECIES(public))) - } - - /// 256-bit AES GCM encryption with given nonce. - /// It is extremely insecure to reuse nonces. - /// - /// If generating nonces with a secure RNG, limit uses such that - /// the chance of collision is negligible. - pub fn aes(key: Memzero<[u8; AES_KEY_LEN]>, nonce: [u8; AES_NONCE_LEN]) -> Self { - EncryptionInstance(EncryptionInner::AES(key, nonce, AesEncode::AppendedNonce)) - } - - /// Broadcast encryption for the message based on the given topics. - /// - /// Key reuse here is extremely dangerous. It should be randomly generated - /// with a secure RNG. - pub fn broadcast(key: Memzero<[u8; AES_KEY_LEN]>, topics: Vec) -> Self { - EncryptionInstance(EncryptionInner::AES( - key, - BROADCAST_IV, - AesEncode::OnTopics(topics), - )) - } - - /// Encrypt the supplied plaintext - pub fn encrypt(self, plain: &[u8]) -> Option> { - match self.0 { - EncryptionInner::AES(key, nonce, encode) => match encode { - AesEncode::AppendedNonce => { - let enc = Encryptor::aes_256_gcm(&*key).ok()?; - let mut buf = enc.encrypt(&nonce, plain.to_vec()).ok()?; - buf.extend(&nonce[..]); - Some(buf) - } - AesEncode::OnTopics(topics) => { - let mut buf = Vec::new(); - for mut t in topics { - xor(&mut t.0, &key); - buf.extend(&t.0); - } - let mut enc = Encryptor::aes_256_gcm(&*key).ok()?; - enc.offset(buf.len()); - buf.extend(plain); - let ciphertext = enc.encrypt(&nonce, buf).ok()?; - Some(ciphertext) - } - }, - EncryptionInner::ECIES(valid_public) => ecies::encrypt(&valid_public, &[], plain).ok(), - } - } -} - -#[inline] -fn xor(a: &mut [u8; 32], b: &[u8; 32]) { - for i in 0..32 { - a[i] ^= b[i] - } -} - -enum AesExtract { - AppendedNonce(Memzero<[u8; AES_KEY_LEN]>), // extract appended nonce. - OnTopics(usize, usize, H256), // number of topics, index we know, topic we know. -} - -enum DecryptionInner { - AES(AesExtract), - ECIES(Secret), -} - -/// Decryption instance good for single usage. -pub struct DecryptionInstance(DecryptionInner); - -impl DecryptionInstance { - /// ECIES decryption using secret key. Fails if invalid secret. - pub fn ecies(secret: Secret) -> Result { - secret.check_validity().map_err(|_| "Invalid secret key")?; - - Ok(DecryptionInstance(DecryptionInner::ECIES(secret))) - } - - /// 256-bit AES GCM decryption with appended nonce. - pub fn aes(key: Memzero<[u8; AES_KEY_LEN]>) -> Self { - DecryptionInstance(DecryptionInner::AES(AesExtract::AppendedNonce(key))) - } - - /// Decode broadcast based on number of topics and known topic. - /// Known topic index may not be larger than num topics - 1. - pub fn broadcast( - num_topics: usize, - topic_idx: usize, - known_topic: H256, - ) -> Result { - if topic_idx >= num_topics { - return Err("topic index out of bounds"); - } - - Ok(DecryptionInstance(DecryptionInner::AES( - AesExtract::OnTopics(num_topics, topic_idx, known_topic), - ))) - } - - /// Decrypt ciphertext. Fails if it's an invalid message. - pub fn decrypt(self, ciphertext: &[u8]) -> Option> { - match self.0 { - DecryptionInner::AES(extract) => { - match extract { - AesExtract::AppendedNonce(key) => { - if ciphertext.len() < AES_NONCE_LEN { - return None; - } - // nonce is the suffix of ciphertext. - let mut nonce = [0; AES_NONCE_LEN]; - let nonce_offset = ciphertext.len() - AES_NONCE_LEN; - nonce.copy_from_slice(&ciphertext[nonce_offset..]); - Decryptor::aes_256_gcm(&*key) - .ok()? - .decrypt(&nonce, Vec::from(&ciphertext[..nonce_offset])) - .ok() - } - AesExtract::OnTopics(num_topics, known_index, known_topic) => { - if ciphertext.len() < num_topics * 32 { - return None; - } - let mut salted_topic = H256::new(); - salted_topic.copy_from_slice(&ciphertext[(known_index * 32)..][..32]); - let key = Memzero::from((salted_topic ^ known_topic).0); - let offset = num_topics * 32; - Decryptor::aes_256_gcm(&*key) - .ok()? - .decrypt(&BROADCAST_IV, Vec::from(&ciphertext[offset..])) - .ok() - } - } - } - DecryptionInner::ECIES(secret) => { - // secret is checked for validity, so only fails on invalid message. - ecies::decrypt(&secret, &[], ciphertext).ok() - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn encrypt_asymmetric() { - use ethkey::{Generator, Random}; - - let key_pair = Random.generate().unwrap(); - let test_message = move |message: &[u8]| { - let instance = EncryptionInstance::ecies(key_pair.public().clone()).unwrap(); - let ciphertext = instance.encrypt(&message).unwrap(); - - if !message.is_empty() { - assert!(&ciphertext[..message.len()] != message) - } - - let instance = DecryptionInstance::ecies(key_pair.secret().clone()).unwrap(); - let decrypted = instance.decrypt(&ciphertext).unwrap(); - - assert_eq!(message, &decrypted[..]) - }; - - test_message(&[1, 2, 3, 4, 5]); - test_message(&[]); - test_message(&[255; 512]); - } - - #[test] - fn encrypt_symmetric() { - use rand::{OsRng, Rng}; - - let mut rng = OsRng::new().unwrap(); - let mut test_message = move |message: &[u8]| { - let key = Memzero::from(rng.gen::<[u8; 32]>()); - - let instance = EncryptionInstance::aes(key.clone(), rng.gen()); - let ciphertext = instance.encrypt(message).unwrap(); - - if !message.is_empty() { - assert!(&ciphertext[..message.len()] != message) - } - - let instance = DecryptionInstance::aes(key); - let decrypted = instance.decrypt(&ciphertext).unwrap(); - - assert_eq!(message, &decrypted[..]) - }; - - test_message(&[1, 2, 3, 4, 5]); - test_message(&[]); - test_message(&[255; 512]); - } - - #[test] - fn encrypt_broadcast() { - use rand::{OsRng, Rng}; - - let mut rng = OsRng::new().unwrap(); - - let mut test_message = move |message: &[u8]| { - let all_topics = (0..5).map(|_| rng.gen()).collect::>(); - let known_idx = 2; - let known_topic = all_topics[2]; - let key = Memzero::from(rng.gen::<[u8; 32]>()); - - let instance = EncryptionInstance::broadcast(key, all_topics); - let ciphertext = instance.encrypt(message).unwrap(); - - if !message.is_empty() { - assert!(&ciphertext[..message.len()] != message) - } - - let instance = DecryptionInstance::broadcast(5, known_idx, known_topic).unwrap(); - - let decrypted = instance.decrypt(&ciphertext).unwrap(); - - assert_eq!(message, &decrypted[..]) - }; - - test_message(&[1, 2, 3, 4, 5]); - test_message(&[]); - test_message(&[255; 512]); - } -} diff --git a/whisper/src/rpc/filter.rs b/whisper/src/rpc/filter.rs deleted file mode 100644 index 599d9069d..000000000 --- a/whisper/src/rpc/filter.rs +++ /dev/null @@ -1,486 +0,0 @@ -// Copyright 2015-2019 Parity Technologies (UK) Ltd. -// This file is part of Parity Ethereum. - -// Parity Ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity Ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity Ethereum. If not, see . - -//! Abstraction over filters which works with polling and subscription. - -use std::{ - collections::HashMap, - sync::{atomic, atomic::AtomicBool, mpsc, Arc}, - thread, -}; - -use ethereum_types::{H256, H512}; -use ethkey::Public; -use jsonrpc_pubsub::typed::{Sink, Subscriber}; -use parking_lot::{Mutex, RwLock}; -use rand::{OsRng, Rng}; - -use super::{ - key_store::KeyStore, - types::{self, FilterItem, HexEncode}, -}; -use message::{Message, Topic}; - -/// Kinds of filters, -#[derive(PartialEq, Eq, Clone, Copy)] -pub enum Kind { - /// Polled filter only returns data upon request - Poll, - /// Subscription filter pushes data to subscriber immediately. - Subscription, -} - -pub type ItemBuffer = Arc>>; - -enum FilterEntry { - Poll(Arc, ItemBuffer), - Subscription(Arc, Sink), -} - -/// Filter manager. Handles filters as well as a thread for doing decryption -/// and payload decoding. -pub struct Manager { - key_store: Arc>, - filters: RwLock>, - tx: Mutex>>, - join: Option>, - exit: Arc, -} - -impl Manager { - /// Create a new filter manager that will dispatch decryption tasks onto - /// the given thread pool. - pub fn new() -> ::std::io::Result { - let (tx, rx) = mpsc::channel::>(); - let exit = Arc::new(AtomicBool::new(false)); - let e = exit.clone(); - - let join_handle = thread::Builder::new() - .name("Whisper Decryption Worker".to_string()) - .spawn(move || { - trace!(target: "parity_whisper", "Start decryption worker"); - loop { - if exit.load(atomic::Ordering::Acquire) { - break; - } - if let Ok(item) = rx.try_recv() { - item(); - } - } - })?; - - Ok(Manager { - key_store: Arc::new(RwLock::new(KeyStore::new()?)), - filters: RwLock::new(HashMap::new()), - tx: Mutex::new(tx), - join: Some(join_handle), - exit: e, - }) - } - - /// Get a handle to the key store. - pub fn key_store(&self) -> Arc> { - self.key_store.clone() - } - - /// Get filter kind if it's known. - pub fn kind(&self, id: &H256) -> Option { - self.filters.read().get(id).map(|filter| match *filter { - FilterEntry::Poll(_, _) => Kind::Poll, - FilterEntry::Subscription(_, _) => Kind::Subscription, - }) - } - - /// Remove filter by ID. - pub fn remove(&self, id: &H256) { - self.filters.write().remove(id); - } - - /// Add a new polled filter. - pub fn insert_polled(&self, filter: Filter) -> Result { - let buffer = Arc::new(Mutex::new(Vec::new())); - let entry = FilterEntry::Poll(Arc::new(filter), buffer); - let id = OsRng::new() - .map_err(|_| "unable to acquire secure randomness")? - .gen(); - - self.filters.write().insert(id, entry); - Ok(id) - } - - /// Insert new subscription filter. Generates a secure ID and sends it to - /// the subscriber - pub fn insert_subscription( - &self, - filter: Filter, - sub: Subscriber, - ) -> Result<(), &'static str> { - let id: H256 = OsRng::new() - .map_err(|_| "unable to acquire secure randomness")? - .gen(); - - sub.assign_id(::jsonrpc_pubsub::SubscriptionId::String(format!( - "{:x}", - id - ))) - .map(move |sink| { - let entry = FilterEntry::Subscription(Arc::new(filter), sink); - self.filters.write().insert(id, entry); - }) - .map_err(|_| "subscriber disconnected") - } - - /// Poll changes on filter identified by ID. - pub fn poll_changes(&self, id: &H256) -> Option> { - self.filters - .read() - .get(id) - .and_then(|filter| match *filter { - FilterEntry::Subscription(_, _) => None, - FilterEntry::Poll(_, ref changes) => { - Some(::std::mem::replace(&mut *changes.lock(), Vec::new())) - } - }) - } -} - -// machinery for attaching the manager to the network instance. -impl ::net::MessageHandler for Arc { - fn handle_messages(&self, messages: &[Message]) { - let filters = self.filters.read(); - let filters_iter = filters - .values() - .flat_map(|filter| messages.iter().map(move |msg| (filter, msg))); - - for (filter, message) in filters_iter { - // if the message matches any of the possible bloom filters, - // send to thread pool to attempt decryption and avoid - // blocking the network thread for long. - let failed_send = match *filter { - FilterEntry::Poll(ref filter, _) | FilterEntry::Subscription(ref filter, _) - if !filter.basic_matches(message) => - { - None - } - FilterEntry::Poll(ref filter, ref buffer) => { - let (message, key_store) = (message.clone(), self.key_store.clone()); - let (filter, buffer) = (filter.clone(), buffer.clone()); - - self.tx - .lock() - .send(Box::new(move || { - filter.handle_message(&message, &*key_store, |matched| { - buffer.lock().push(matched) - }) - })) - .err() - .map(|x| x.0) - } - FilterEntry::Subscription(ref filter, ref sink) => { - let (message, key_store) = (message.clone(), self.key_store.clone()); - let (filter, sink) = (filter.clone(), sink.clone()); - - self.tx - .lock() - .send(Box::new(move || { - filter.handle_message(&message, &*key_store, |matched| { - let _ = sink.notify(Ok(matched)); - }) - })) - .err() - .map(|x| x.0) - } - }; - - // if we failed to send work, no option but to do it locally. - if let Some(local_work) = failed_send { - (local_work)() - } - } - } -} - -impl Drop for Manager { - fn drop(&mut self) { - trace!(target: "parity_whisper", "waiting to drop FilterManager"); - self.exit.store(true, atomic::Ordering::Release); - if let Some(guard) = self.join.take() { - let _ = guard.join(); - } - trace!(target: "parity_whisper", "FilterManager dropped"); - } -} - -/// Filter incoming messages by critera. -pub struct Filter { - topics: Vec<(Vec, H512, Topic)>, - from: Option, - decrypt_with: Option, -} - -impl Filter { - /// Create a new filter from filter request. - /// - /// Fails if the topics vector is empty. - pub fn new(params: types::FilterRequest) -> Result { - if params.topics.is_empty() { - return Err("no topics for filter"); - } - - let topics: Vec<_> = params - .topics - .into_iter() - .map(|x| x.into_inner()) - .map(|topic| { - let abridged = super::abridge_topic(&topic); - (topic, abridged.bloom(), abridged) - }) - .collect(); - - Ok(Filter { - topics: topics, - from: params.from.map(|x| x.into_inner()), - decrypt_with: params.decrypt_with.map(|x| x.into_inner()), - }) - } - - // does basic matching: - // whether the given message matches at least one of the topics of the - // filter. - // TODO: minimum PoW heuristic. - fn basic_matches(&self, message: &Message) -> bool { - self.topics - .iter() - .any(|&(_, ref bloom, _)| &(bloom & message.bloom()) == bloom) - } - - // handle a message that matches the bloom. - fn handle_message( - &self, - message: &Message, - store: &RwLock, - on_match: F, - ) { - use rpc::crypto::DecryptionInstance; - use tiny_keccak::keccak256; - - let matched_indices: Vec<_> = self - .topics - .iter() - .enumerate() - .filter_map(|(i, &(_, ref bloom, ref abridged))| { - let contains_topic = - &(bloom & message.bloom()) == bloom && message.topics().contains(abridged); - - if contains_topic { - Some(i) - } else { - None - } - }) - .collect(); - - if matched_indices.is_empty() { - return; - } - - let decrypt = match self.decrypt_with { - Some(ref id) => match store.read().decryption_instance(id) { - Some(d) => d, - None => { - warn!(target: "whisper", "Filter attempted to decrypt with destroyed identity {}", - id); - - return; - } - }, - None => { - let known_idx = matched_indices[0]; - let known_topic = H256(keccak256(&self.topics[0].0)); - - DecryptionInstance::broadcast(message.topics().len(), known_idx, known_topic) - .expect("known idx is within the range 0..message.topics.len(); qed") - } - }; - - let decrypted = match decrypt.decrypt(message.data()) { - Some(d) => d, - None => { - trace!(target: "whisper", "Failed to decrypt message with {} matching topics", - matched_indices.len()); - - return; - } - }; - - match ::rpc::payload::decode(&decrypted) { - Ok(decoded) => { - if decoded.from != self.from { - return; - } - - let matched_topics = matched_indices - .into_iter() - .map(|i| self.topics[i].0.clone()) - .map(HexEncode) - .collect(); - - on_match(FilterItem { - from: decoded.from.map(HexEncode), - recipient: self.decrypt_with.map(HexEncode), - ttl: message.envelope().ttl, - topics: matched_topics, - timestamp: message.envelope().expiry - message.envelope().ttl, - payload: HexEncode(decoded.message.to_vec()), - padding: decoded.padding.map(|pad| HexEncode(pad.to_vec())), - }) - } - Err(reason) => { - trace!(target: "whisper", "Bad payload in decrypted message with {} topics: {}", - matched_indices.len(), reason) - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use message::{CreateParams, Message, Topic}; - use rpc::{ - abridge_topic, - types::{FilterRequest, HexEncode}, - }; - - #[test] - fn rejects_empty_topics() { - let req = FilterRequest { - decrypt_with: Default::default(), - from: None, - topics: Vec::new(), - }; - - assert!(Filter::new(req).is_err()); - } - - #[test] - fn basic_match() { - let topics = vec![vec![1, 2, 3, 4], vec![5, 6, 7, 8]]; - let abridged_topics: Vec<_> = topics.iter().map(|x| abridge_topic(&x)).collect(); - - let req = FilterRequest { - decrypt_with: Default::default(), - from: None, - topics: topics.into_iter().map(HexEncode).collect(), - }; - - let filter = Filter::new(req).unwrap(); - let message = Message::create(CreateParams { - ttl: 100, - payload: vec![1, 3, 5, 7, 9], - topics: abridged_topics.clone(), - work: 0, - }) - .unwrap(); - - assert!(filter.basic_matches(&message)); - - let message = Message::create(CreateParams { - ttl: 100, - payload: vec![1, 3, 5, 7, 9], - topics: abridged_topics.clone(), - work: 0, - }) - .unwrap(); - - assert!(filter.basic_matches(&message)); - - let message = Message::create(CreateParams { - ttl: 100, - payload: vec![1, 3, 5, 7, 9], - topics: vec![Topic([1, 8, 3, 99])], - work: 0, - }) - .unwrap(); - - assert!(!filter.basic_matches(&message)); - } - - #[test] - fn decrypt_and_decode() { - use rpc::{ - key_store::{Key, KeyStore}, - payload::{self, EncodeParams}, - }; - - let topics = vec![vec![1, 2, 3, 4], vec![5, 6, 7, 8]]; - let abridged_topics: Vec<_> = topics.iter().map(|x| abridge_topic(&x)).collect(); - - let mut store = KeyStore::new().unwrap(); - let signing_pair = Key::new_asymmetric(store.rng()); - let encrypting_key = Key::new_symmetric(store.rng()); - - let decrypt_id = store.insert(encrypting_key); - let encryption_instance = store.encryption_instance(&decrypt_id).unwrap(); - - let store = ::parking_lot::RwLock::new(store); - - let payload = payload::encode(EncodeParams { - message: &[1, 2, 3], - padding: Some(&[4, 5, 4, 5]), - sign_with: Some(signing_pair.secret().unwrap()), - }) - .unwrap(); - - let encrypted = encryption_instance.encrypt(&payload).unwrap(); - - let message = Message::create(CreateParams { - ttl: 100, - payload: encrypted, - topics: abridged_topics.clone(), - work: 0, - }) - .unwrap(); - - let message2 = Message::create(CreateParams { - ttl: 100, - payload: vec![3, 5, 7, 9], - topics: abridged_topics, - work: 0, - }) - .unwrap(); - - let filter = Filter::new(FilterRequest { - decrypt_with: Some(HexEncode(decrypt_id)), - from: Some(HexEncode(signing_pair.public().unwrap().clone())), - topics: topics.into_iter().map(HexEncode).collect(), - }) - .unwrap(); - - assert!(filter.basic_matches(&message)); - assert!(filter.basic_matches(&message2)); - - let items = ::std::cell::Cell::new(0); - let on_match = |_| { - items.set(items.get() + 1); - }; - - filter.handle_message(&message, &store, &on_match); - filter.handle_message(&message2, &store, &on_match); - - assert_eq!(items.get(), 1); - } -} diff --git a/whisper/src/rpc/key_store.rs b/whisper/src/rpc/key_store.rs deleted file mode 100644 index 101430693..000000000 --- a/whisper/src/rpc/key_store.rs +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright 2015-2019 Parity Technologies (UK) Ltd. -// This file is part of Parity Ethereum. - -// Parity Ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity Ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity Ethereum. If not, see . - -//! Identity and keystore for Whisper sessions. -//! -//! Can handle symmetric and asymmetric keys. -//! Symmetric encryption is done via AES-256 in GCM mode. - -use std::collections::HashMap; - -use ethereum_types::H256; -use ethkey::{KeyPair, Public, Secret}; -use memzero::Memzero; -use rand::{OsRng, Rng}; - -use rpc::crypto::{DecryptionInstance, EncryptionInstance, AES_KEY_LEN}; - -/// A symmetric or asymmetric key used for encryption, decryption, and signing -/// of payloads. -pub enum Key { - /// ECIES key pair for Secp2561k curve. Suitable for encryption, decryption, - /// and signing. - Asymmetric(KeyPair), - /// AES-256 GCM mode. Suitable for encryption, decryption, but not signing. - Symmetric(Memzero<[u8; AES_KEY_LEN]>), -} - -impl Key { - /// Generate a random asymmetric key with the given cryptographic RNG. - pub fn new_asymmetric(rng: &mut OsRng) -> Self { - match ::ethkey::Generator::generate(rng) { - Ok(pair) => Key::Asymmetric(pair), - Err(void) => match void {}, - } - } - - /// Generate a random symmetric key with the given cryptographic RNG. - pub fn new_symmetric(rng: &mut OsRng) -> Self { - Key::Symmetric(Memzero::from(rng.gen::<[u8; 32]>())) - } - - /// From secret asymmetric key. Fails if secret is invalid. - pub fn from_secret(secret: Secret) -> Option { - KeyPair::from_secret(secret).map(Key::Asymmetric).ok() - } - - /// From raw symmetric key. - pub fn from_raw_symmetric(key: [u8; AES_KEY_LEN]) -> Self { - Key::Symmetric(Memzero::from(key)) - } - - /// Get a handle to the public key if this is an asymmetric key. - pub fn public(&self) -> Option<&Public> { - match *self { - Key::Asymmetric(ref pair) => Some(pair.public()), - Key::Symmetric(_) => None, - } - } - - /// Get a handle to the secret key if this is an asymmetric key. - pub fn secret(&self) -> Option<&Secret> { - match *self { - Key::Asymmetric(ref pair) => Some(pair.secret()), - Key::Symmetric(_) => None, - } - } - - /// Get a handle to the symmetric key. - pub fn symmetric(&self) -> Option<&[u8; AES_KEY_LEN]> { - match *self { - Key::Asymmetric(_) => None, - Key::Symmetric(ref key) => Some(key), - } - } -} - -/// Key store. -pub struct KeyStore { - rng: OsRng, - identities: HashMap, -} - -impl KeyStore { - /// Create the key store. Returns any error in accessing the system's secure - /// RNG. - pub fn new() -> Result { - Ok(KeyStore { - rng: OsRng::new()?, - identities: HashMap::new(), - }) - } - - /// Import a key, generating a random identity for it. - pub fn insert(&mut self, key: Key) -> H256 { - let id = self.rng().gen(); - self.identities.insert(id, key); - - id - } - - /// Get a key by ID. - pub fn get<'a>(&'a self, id: &H256) -> Option<&'a Key> { - self.identities.get(id) - } - - /// Get asymmetric ID's public key. - pub fn public<'a>(&'a self, id: &H256) -> Option<&'a Public> { - self.get(id).and_then(Key::public) - } - - /// Get asymmetric ID's secret key. - pub fn secret<'a>(&'a self, id: &H256) -> Option<&'a Secret> { - self.get(id).and_then(Key::secret) - } - - /// Get symmetric ID's key. - pub fn symmetric<'a>(&'a self, id: &H256) -> Option<&'a [u8; AES_KEY_LEN]> { - self.get(id).and_then(Key::symmetric) - } - - /// Get encryption instance for identity. - pub fn encryption_instance(&self, id: &H256) -> Result { - self.get(id) - .ok_or("no such identity") - .and_then(|key| match *key { - Key::Asymmetric(ref pair) => EncryptionInstance::ecies(pair.public().clone()) - .map_err(|_| "could not create encryption instance for id"), - Key::Symmetric(ref key) => OsRng::new() - .map(|mut rng| EncryptionInstance::aes(key.clone(), rng.gen())) - .map_err(|_| "unable to get secure randomness"), - }) - } - - /// Get decryption instance for identity. - /// If the identity is known, always succeeds. - pub fn decryption_instance(&self, id: &H256) -> Option { - self.get(id).map(|key| match *key { - Key::Asymmetric(ref pair) => DecryptionInstance::ecies(pair.secret().clone()) - .expect("all keys stored are valid; qed"), - Key::Symmetric(ref key) => DecryptionInstance::aes(key.clone()), - }) - } - - /// Whether the store contains a key by this ID. - pub fn contains(&self, id: &H256) -> bool { - self.identities.contains_key(id) - } - - /// Remove a key by ID. - pub fn remove(&mut self, id: &H256) -> bool { - self.identities.remove(id).is_some() - } - - /// Get RNG. - pub fn rng(&mut self) -> &mut OsRng { - &mut self.rng - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn rejects_invalid_secret() { - let bad_secret = ::ethkey::Secret::from([0xff; 32]); - assert!(Key::from_secret(bad_secret).is_none()); - } - - #[test] - fn generated_key_should_exist() { - let mut store = KeyStore::new().unwrap(); - let key = Key::new_asymmetric(store.rng()); - - assert!(key.public().is_some()); - assert!(key.secret().is_some()); - - let id = store.insert(key); - - assert!(store.contains(&id)); - assert!(store.get(&id).is_some()); - } -} diff --git a/whisper/src/rpc/mod.rs b/whisper/src/rpc/mod.rs deleted file mode 100644 index 94ccba4c7..000000000 --- a/whisper/src/rpc/mod.rs +++ /dev/null @@ -1,424 +0,0 @@ -// Copyright 2015-2019 Parity Technologies (UK) Ltd. -// This file is part of Parity Ethereum. - -// Parity Ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity Ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity Ethereum. If not, see . - -//! JSONRPC interface for Whisper. -//! -//! Manages standard message format decoding, ephemeral identities, signing, -//! encryption, and decryption. -//! -//! Provides an interface for using whisper to transmit data securely. - -use std::sync::Arc; - -use jsonrpc_core::{Error, ErrorCode, Metadata}; -use jsonrpc_derive::rpc; -use jsonrpc_pubsub::{typed::Subscriber, PubSubMetadata, Session, SubscriptionId}; - -use ethereum_types::H256; -use memzero::Memzero; -use parking_lot::RwLock; - -use self::{ - filter::Filter, - key_store::{Key, KeyStore}, - types::HexEncode, -}; - -use message::{CreateParams, Message, Topic}; - -mod crypto; -mod filter; -mod key_store; -mod payload; -mod types; - -pub use self::filter::Manager as FilterManager; - -// create whisper RPC error. -fn whisper_error>(message: T) -> Error { - const ERROR_CODE: i64 = -32085; - - Error { - code: ErrorCode::ServerError(ERROR_CODE), - message: message.into(), - data: None, - } -} - -fn topic_hash(topic: &[u8]) -> H256 { - H256(::tiny_keccak::keccak256(topic)) -} - -// abridge topic using first four bytes of hash. -fn abridge_topic(topic: &[u8]) -> Topic { - let mut abridged = [0; 4]; - let hash = topic_hash(topic).0; - abridged.copy_from_slice(&hash[..4]); - abridged.into() -} - -/// Whisper RPC interface. -#[rpc(server)] -pub trait Whisper { - /// Info about the node. - #[rpc(name = "shh_info")] - fn info(&self) -> Result; - - /// Generate a new asymmetric key pair and return an identity. - #[rpc(name = "shh_newKeyPair")] - fn new_key_pair(&self) -> Result; - - /// Import the given SECP2561k private key and return an identity. - #[rpc(name = "shh_addPrivateKey")] - fn add_private_key(&self, _: types::Private) -> Result; - - /// Generate a new symmetric key and return an identity. - #[rpc(name = "shh_newSymKey")] - fn new_sym_key(&self) -> Result; - - /// Import the given symmetric key and return an identity. - #[rpc(name = "shh_addSymKey")] - fn add_sym_key(&self, _: types::Symmetric) -> Result; - - /// Get public key. Succeeds if identity is stored and asymmetric. - #[rpc(name = "shh_getPublicKey")] - fn get_public(&self, _: types::Identity) -> Result; - - /// Get private key. Succeeds if identity is stored and asymmetric. - #[rpc(name = "shh_getPrivateKey")] - fn get_private(&self, _: types::Identity) -> Result; - - #[rpc(name = "shh_getSymKey")] - fn get_symmetric(&self, _: types::Identity) -> Result; - - /// Delete key pair denoted by given identity. - /// - /// Return true if successfully removed, false if unknown, - /// and error otherwise. - #[rpc(name = "shh_deleteKey")] - fn remove_key(&self, _: types::Identity) -> Result; - - /// Post a message to the network with given parameters. - #[rpc(name = "shh_post")] - fn post(&self, _: types::PostRequest) -> Result; - - /// Create a new polled filter. - #[rpc(name = "shh_newMessageFilter")] - fn new_filter(&self, _: types::FilterRequest) -> Result; - - /// Poll changes on a polled filter. - #[rpc(name = "shh_getFilterMessages")] - fn poll_changes(&self, _: types::Identity) -> Result, Error>; - - /// Delete polled filter. Return bool indicating success. - #[rpc(name = "shh_deleteMessageFilter")] - fn delete_filter(&self, _: types::Identity) -> Result; -} - -/// Whisper RPC pubsub. -#[rpc(server)] -pub trait WhisperPubSub { - // RPC Metadata - type Metadata; - /// Subscribe to messages matching the filter. - #[pubsub(subscription = "shh_subscription", subscribe, name = "shh_subscribe")] - fn subscribe( - &self, - _: Self::Metadata, - _: Subscriber, - _: types::FilterRequest, - ); - - /// Unsubscribe from filter matching given ID. Return - /// true on success, error otherwise. - #[pubsub( - subscription = "shh_subscription", - unsubscribe, - name = "shh_unsubscribe" - )] - fn unsubscribe(&self, _: Option, _: SubscriptionId) -> Result; -} - -/// Something which can send messages to the network. -pub trait PoolHandle: Send + Sync { - /// Give message to the whisper network for relay. - /// Returns false if PoW too low. - fn relay(&self, message: Message) -> bool; - - /// Number of messages and memory used by resident messages. - fn pool_status(&self) -> ::net::PoolStatus; -} - -/// Default, simple metadata implementation. -#[derive(Clone, Default)] -pub struct Meta { - session: Option>, -} - -impl Metadata for Meta {} -impl PubSubMetadata for Meta { - fn session(&self) -> Option> { - self.session.clone() - } -} - -/// Implementation of whisper RPC. -pub struct WhisperClient { - store: Arc>, - pool: P, - filter_manager: Arc, - _meta: ::std::marker::PhantomData, -} - -impl

WhisperClient

{ - /// Create a new whisper client with basic metadata. - pub fn with_simple_meta(pool: P, filter_manager: Arc) -> Self { - WhisperClient::new(pool, filter_manager) - } -} - -impl WhisperClient { - /// Create a new whisper client. - pub fn new(pool: P, filter_manager: Arc) -> Self { - WhisperClient { - store: filter_manager.key_store(), - pool: pool, - filter_manager: filter_manager, - _meta: ::std::marker::PhantomData, - } - } - - fn delete_filter_kind(&self, id: H256, kind: filter::Kind) -> bool { - match self.filter_manager.kind(&id) { - Some(k) if k == kind => { - self.filter_manager.remove(&id); - true - } - None | Some(_) => false, - } - } -} - -impl Whisper for WhisperClient { - fn info(&self) -> Result { - let status = self.pool.pool_status(); - - Ok(types::NodeInfo { - required_pow: status.required_pow, - messages: status.message_count, - memory: status.cumulative_size, - target_memory: status.target_size, - }) - } - - fn new_key_pair(&self) -> Result { - let mut store = self.store.write(); - let key_pair = Key::new_asymmetric(store.rng()); - - Ok(HexEncode(store.insert(key_pair))) - } - - fn add_private_key(&self, private: types::Private) -> Result { - let key_pair = Key::from_secret(private.into_inner().into()) - .ok_or_else(|| whisper_error("Invalid private key"))?; - - Ok(HexEncode(self.store.write().insert(key_pair))) - } - - fn new_sym_key(&self) -> Result { - let mut store = self.store.write(); - let key = Key::new_symmetric(store.rng()); - - Ok(HexEncode(store.insert(key))) - } - - fn add_sym_key(&self, raw_key: types::Symmetric) -> Result { - let raw_key = raw_key.into_inner().0; - let key = Key::from_raw_symmetric(raw_key); - - Ok(HexEncode(self.store.write().insert(key))) - } - - fn get_public(&self, id: types::Identity) -> Result { - self.store - .read() - .public(&id.into_inner()) - .cloned() - .map(HexEncode) - .ok_or_else(|| whisper_error("Unknown identity")) - } - - fn get_private(&self, id: types::Identity) -> Result { - self.store - .read() - .secret(&id.into_inner()) - .map(|x| (&**x).clone()) - .map(HexEncode) - .ok_or_else(|| whisper_error("Unknown identity")) - } - - fn get_symmetric(&self, id: types::Identity) -> Result { - self.store - .read() - .symmetric(&id.into_inner()) - .cloned() - .map(H256) - .map(HexEncode) - .ok_or_else(|| whisper_error("Unknown identity")) - } - - fn remove_key(&self, id: types::Identity) -> Result { - Ok(self.store.write().remove(&id.into_inner())) - } - - fn post(&self, req: types::PostRequest) -> Result { - use self::crypto::EncryptionInstance; - - let encryption = match req.to { - Some(types::Receiver::Public(public)) => { - EncryptionInstance::ecies(public.into_inner()).map_err(whisper_error)? - } - Some(types::Receiver::Identity(id)) => self - .store - .read() - .encryption_instance(&id.into_inner()) - .map_err(whisper_error)?, - None => { - use rand::{OsRng, Rng}; - - // broadcast mode: use fixed nonce and fresh key each time. - - let mut rng = OsRng::new() - .map_err(|_| whisper_error("unable to acquire secure randomness"))?; - - let key = Memzero::from(rng.gen::<[u8; 32]>()); - if req.topics.is_empty() { - return Err(whisper_error( - "must supply at least one topic for broadcast message", - )); - } - - EncryptionInstance::broadcast( - key, - req.topics.iter().map(|x| topic_hash(&x)).collect(), - ) - } - }; - - let sign_with = match req.from { - Some(from) => Some( - self.store - .read() - .secret(&from.into_inner()) - .cloned() - .ok_or_else(|| whisper_error("Unknown identity `from`"))?, - ), - None => None, - }; - - let encrypted = { - let payload = payload::encode(payload::EncodeParams { - message: &req.payload.into_inner(), - padding: req.padding.map(|p| p.into_inner()).as_ref().map(|x| &x[..]), - sign_with: sign_with.as_ref(), - }) - .map_err(whisper_error)?; - - encryption - .encrypt(&payload) - .ok_or(whisper_error("encryption error"))? - }; - - // mining the packet is the heaviest item of work by far. - // there may be a benefit to dispatching this onto the CPU pool - // and returning a future. but then things get _less_ efficient - // if the server infrastructure has more threads than the CPU pool. - let message = Message::create(CreateParams { - ttl: req.ttl, - payload: encrypted, - topics: req - .topics - .into_iter() - .map(|x| abridge_topic(&x.into_inner())) - .collect(), - work: req.priority, - }) - .map_err(|_| whisper_error("Empty topics"))?; - - if !self.pool.relay(message) { - Err(whisper_error("PoW too low to compete with other messages")) - } else { - Ok(true) - } - } - - fn new_filter(&self, req: types::FilterRequest) -> Result { - let filter = Filter::new(req).map_err(whisper_error)?; - - self.filter_manager - .insert_polled(filter) - .map(HexEncode) - .map_err(whisper_error) - } - - fn poll_changes(&self, id: types::Identity) -> Result, Error> { - match self.filter_manager.poll_changes(&id.into_inner()) { - None => Err(whisper_error("no such message filter")), - Some(items) => Ok(items), - } - } - - fn delete_filter(&self, id: types::Identity) -> Result { - Ok(self.delete_filter_kind(id.into_inner(), filter::Kind::Poll)) - } -} - -impl WhisperPubSub - for WhisperClient -{ - type Metadata = M; - - fn subscribe( - &self, - _meta: Self::Metadata, - subscriber: Subscriber, - req: types::FilterRequest, - ) { - match Filter::new(req) { - Ok(filter) => { - if let Err(e) = self.filter_manager.insert_subscription(filter, subscriber) { - debug!(target: "whisper", "Failed to add subscription: {}", e); - } - } - Err(reason) => { - let _ = subscriber.reject(whisper_error(reason)); - } - } - } - - fn unsubscribe(&self, _: Option, id: SubscriptionId) -> Result { - use std::str::FromStr; - - let res = match id { - SubscriptionId::String(s) => H256::from_str(&s) - .map_err(|_| "unrecognized ID") - .map(|id| self.delete_filter_kind(id, filter::Kind::Subscription)), - SubscriptionId::Number(_) => Err("unrecognized ID"), - }; - - res.map_err(whisper_error) - } -} diff --git a/whisper/src/rpc/payload.rs b/whisper/src/rpc/payload.rs deleted file mode 100644 index 7f255e85d..000000000 --- a/whisper/src/rpc/payload.rs +++ /dev/null @@ -1,366 +0,0 @@ -// Copyright 2015-2019 Parity Technologies (UK) Ltd. -// This file is part of Parity Ethereum. - -// Parity Ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity Ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity Ethereum. If not, see . - -//! Common payload format definition, construction, and decoding. -//! -//! Format: -//! flags: 1 byte -//! -//! payload size: 0..4 bytes, BE, determined by flags. -//! optional padding: byte array up to 2^24 bytes in length. encoded in payload size. -//! optional signature: 65 bytes (r, s, v) -//! -//! payload: byte array of length of arbitrary size. -//! -//! flag bits used: -//! 0, 1 => how many bytes indicate padding length (up to 3) -//! 2 => whether signature is present -//! -//! padding is used to mask information about size of message. -//! -//! AES-256-GCM will append 12 bytes of metadata to the front of the message. - -use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; -use ethereum_types::H256; -use ethkey::{Public, Secret}; -use tiny_keccak::keccak256; - -const SIGNATURE_LEN: usize = 65; - -const STANDARD_PAYLOAD_VERSION: u8 = 1; - -bitflags! { - struct Flags: u8 { - const FLAG_PAD_LEN_HIGH = 0b10000000; - const FLAG_PAD_LEN_LOW = 0b01000000; - const FLAG_SIGNED = 0b00100000; - } -} - -// number of bytes of padding length (in the range 0..4) -fn padding_length_bytes(flags: Flags) -> usize { - match (flags & FLAG_PAD_LEN_HIGH, flags & FLAG_PAD_LEN_LOW) { - (FLAG_PAD_LEN_HIGH, FLAG_PAD_LEN_LOW) => 3, - (FLAG_PAD_LEN_HIGH, _) => 2, - (_, FLAG_PAD_LEN_LOW) => 1, - (_, _) => 0, - } -} - -// how many bytes are necessary to encode the given length. Range 0..4. -// `None` if too large. -fn num_padding_length_bytes(padding_len: usize) -> Option { - let bits = 64 - (padding_len as u64).leading_zeros(); - match bits { - 0 => Some(0), - 1..=8 => Some(1), - 9..=16 => Some(2), - 17..=24 => Some(3), - _ => None, - } -} - -/// Parameters for encoding a standard payload. -pub struct EncodeParams<'a> { - /// Message to encode. - pub message: &'a [u8], - /// Padding bytes. Maximum padding allowed is 65536 bytes. - pub padding: Option<&'a [u8]>, - /// Private key to sign with. - pub sign_with: Option<&'a Secret>, -} - -impl<'a> Default for EncodeParams<'a> { - fn default() -> Self { - EncodeParams { - message: &[], - padding: None, - sign_with: None, - } - } -} - -/// Parameters for decoding a standard payload. -pub struct Decoded<'a> { - /// Decoded message. - pub message: &'a [u8], - /// optional padding. - pub padding: Option<&'a [u8]>, - /// Recovered signature. - pub from: Option, -} - -/// Encode using provided parameters. -pub fn encode(params: EncodeParams) -> Result, &'static str> { - const VEC_WRITE_INFALLIBLE: &'static str = "writing to a Vec can never fail; qed"; - - let padding_len = params.padding.map_or(0, |x| x.len()); - let padding_len_bytes = - num_padding_length_bytes(padding_len).ok_or_else(|| "padding size too long")?; - - let signature = params.sign_with.map(|secret| { - let hash = H256(keccak256(params.message)); - ::ethkey::sign(secret, &hash) - }); - - let signature = match signature { - Some(Ok(sig)) => Some(sig), - Some(Err(_)) => return Err("invalid signing key provided"), - None => None, - }; - - let (flags, plaintext_size) = { - let mut flags = Flags::empty(); - - // 1 byte each for flags and version. - let mut plaintext_size = 2 + padding_len_bytes + padding_len + params.message.len(); - - flags.bits = (padding_len_bytes << 6) as u8; - debug_assert_eq!(padding_length_bytes(flags), padding_len_bytes); - - if let Some(ref sig) = signature { - plaintext_size += sig.len(); - flags |= FLAG_SIGNED; - } - - (flags, plaintext_size) - }; - - let mut plaintext = Vec::with_capacity(plaintext_size); - - plaintext.push(STANDARD_PAYLOAD_VERSION); - plaintext.push(flags.bits); - - if let Some(padding) = params.padding { - plaintext - .write_uint::(padding_len as u64, padding_len_bytes) - .expect(VEC_WRITE_INFALLIBLE); - - plaintext.extend(padding) - } - - if let Some(signature) = signature { - plaintext.extend(signature.r()); - plaintext.extend(signature.s()); - plaintext.push(signature.v()); - } - - plaintext.extend(params.message); - - Ok(plaintext) -} - -/// Decode using provided parameters -pub fn decode(payload: &[u8]) -> Result { - let mut offset = 0; - - let (padding, signature) = { - // use a closure for reading slices since std::io::Read would require - // us to copy. - let mut next_slice = |len| { - let end = offset + len; - if payload.len() >= end { - let slice = &payload[offset..end]; - offset = end; - - Ok(slice) - } else { - return Err("unexpected end of payload"); - } - }; - - if next_slice(1)?[0] != STANDARD_PAYLOAD_VERSION { - return Err("unknown payload version."); - } - - let flags = Flags::from_bits_truncate(next_slice(1)?[0]); - - let padding_len_bytes = padding_length_bytes(flags); - let padding = if padding_len_bytes != 0 { - let padding_len = - BigEndian::read_uint(next_slice(padding_len_bytes)?, padding_len_bytes); - - Some(next_slice(padding_len as usize)?) - } else { - None - }; - - let signature = if flags & FLAG_SIGNED == FLAG_SIGNED { - let slice = next_slice(SIGNATURE_LEN)?; - let mut arr = [0; SIGNATURE_LEN]; - - arr.copy_from_slice(slice); - let signature = ::ethkey::Signature::from(arr); - - let not_rsv = signature.r() != &slice[..32] - || signature.s() != &slice[32..64] - || signature.v() != slice[64]; - - if not_rsv { - return Err("signature not in RSV format"); - } else { - Some(signature) - } - } else { - None - }; - - (padding, signature) - }; - - // remaining data is the message. - let message = &payload[offset..]; - - let from = match signature { - None => None, - Some(sig) => { - let hash = H256(keccak256(message)); - Some(::ethkey::recover(&sig, &hash).map_err(|_| "invalid signature")?) - } - }; - - Ok(Decoded { - message: message, - padding: padding, - from: from, - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use ethkey::{Generator, Random}; - - #[test] - fn padding_len_bytes_sanity() { - const U24_MAX: usize = (1 << 24) - 1; - - assert_eq!( - padding_length_bytes(FLAG_PAD_LEN_HIGH | FLAG_PAD_LEN_LOW), - 3 - ); - assert_eq!(padding_length_bytes(FLAG_PAD_LEN_HIGH), 2); - assert_eq!(padding_length_bytes(FLAG_PAD_LEN_LOW), 1); - assert_eq!(padding_length_bytes(Flags::empty()), 0); - - assert!(num_padding_length_bytes(u32::max_value() as _).is_none()); - assert!(num_padding_length_bytes(U24_MAX + 1).is_none()); - - assert_eq!(num_padding_length_bytes(U24_MAX), Some(3)); - - assert_eq!( - num_padding_length_bytes(u16::max_value() as usize + 1), - Some(3) - ); - assert_eq!(num_padding_length_bytes(u16::max_value() as usize), Some(2)); - - assert_eq!( - num_padding_length_bytes(u8::max_value() as usize + 1), - Some(2) - ); - assert_eq!(num_padding_length_bytes(u8::max_value() as usize), Some(1)); - - assert_eq!(num_padding_length_bytes(1), Some(1)); - assert_eq!(num_padding_length_bytes(0), Some(0)); - } - - #[test] - fn encode_decode_roundtrip() { - let message = [1, 2, 3, 4, 5]; - let encoded = encode(EncodeParams { - message: &message, - padding: None, - sign_with: None, - }) - .unwrap(); - - let decoded = decode(&encoded).unwrap(); - - assert_eq!(message, decoded.message); - } - - #[test] - fn encode_empty() { - let encoded = encode(EncodeParams { - message: &[], - padding: None, - sign_with: None, - }) - .unwrap(); - - let decoded = decode(&encoded).unwrap(); - - assert!(decoded.message.is_empty()); - } - - #[test] - fn encode_with_signature() { - let key_pair = Random.generate().unwrap(); - let message = [1, 3, 5, 7, 9]; - - let encoded = encode(EncodeParams { - message: &message, - padding: None, - sign_with: Some(key_pair.secret()), - }) - .unwrap(); - - let decoded = decode(&encoded).unwrap(); - - assert_eq!(decoded.message, message); - assert_eq!(decoded.from, Some(key_pair.public().clone())); - assert!(decoded.padding.is_none()); - } - - #[test] - fn encode_with_padding() { - let message = [1, 3, 5, 7, 9]; - let padding = [0xff; 1024 - 5]; - - let encoded = encode(EncodeParams { - message: &message, - padding: Some(&padding), - sign_with: None, - }) - .unwrap(); - - let decoded = decode(&encoded).unwrap(); - - assert_eq!(decoded.message, message); - assert_eq!(decoded.padding, Some(&padding[..])); - assert!(decoded.from.is_none()); - } - - #[test] - fn encode_with_padding_and_signature() { - let key_pair = Random.generate().unwrap(); - let message = [1, 3, 5, 7, 9]; - let padding = [0xff; 1024 - 5]; - - let encoded = encode(EncodeParams { - message: &message, - padding: Some(&padding), - sign_with: Some(key_pair.secret()), - }) - .unwrap(); - - let decoded = decode(&encoded).unwrap(); - - assert_eq!(decoded.message, message); - assert_eq!(decoded.padding, Some(&padding[..])); - assert_eq!(decoded.from, Some(key_pair.public().clone())); - } -} diff --git a/whisper/src/rpc/types.rs b/whisper/src/rpc/types.rs deleted file mode 100644 index a4f6d9284..000000000 --- a/whisper/src/rpc/types.rs +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright 2015-2019 Parity Technologies (UK) Ltd. -// This file is part of Parity Ethereum. - -// Parity Ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity Ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity Ethereum. If not, see . - -//! Types for Whisper RPC. - -use std::{fmt, ops::Deref}; - -use ethereum_types::{H128, H256, H264, H32, H512, H64}; -use hex::{FromHex, ToHex}; - -use serde::{ - de::{Error, Visitor}, - Deserialize, Deserializer, Serialize, Serializer, -}; - -/// Helper trait for generic hex bytes encoding. -pub trait HexEncodable: Sized + ::std::ops::Deref { - fn from_bytes(bytes: Vec) -> Option; -} - -impl HexEncodable for Vec { - fn from_bytes(bytes: Vec) -> Option { - Some(bytes) - } -} - -macro_rules! impl_hex_for_hash { - ($($t: ident)*) => { - $( - impl HexEncodable for $t { - fn from_bytes(bytes: Vec) -> Option { - if bytes.len() != $t::len() { - None - } else { - Some($t::from_slice(&bytes)) - } - } - } - )* - } -} - -impl_hex_for_hash!( - H32 H64 H128 H256 H264 H512 -); - -/// Wrapper structure around hex-encoded data. -#[derive(Debug, PartialEq, Eq, Default, Hash, Clone)] -pub struct HexEncode(pub T); - -impl From for HexEncode { - fn from(x: T) -> Self { - HexEncode(x) - } -} - -impl HexEncode { - /// Create a new wrapper from the inner value. - pub fn new(x: T) -> Self { - HexEncode(x) - } - - /// Consume the wrapper, yielding the inner value. - pub fn into_inner(self) -> T { - self.0 - } -} - -impl Deref for HexEncode { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -/// Hex-encoded arbitrary-byte vector. -pub type Bytes = HexEncode>; - -/// 32-byte local identity -pub type Identity = HexEncode; - -/// Public key for ECIES, SECP256k1 -pub type Public = HexEncode<::ethkey::Public>; - -/// Unvalidated private key for ECIES, SECP256k1 -pub type Private = HexEncode; - -/// Abridged topic is four bytes. -// only used in tests for now. -#[cfg(test)] -pub type AbridgedTopic = HexEncode; - -/// 32-byte AES key. -pub type Symmetric = HexEncode; - -impl Serialize for HexEncode { - fn serialize(&self, serializer: S) -> Result { - let data = &self.0[..]; - let serialized = "0x".to_owned() + &data.to_hex(); - - serializer.serialize_str(serialized.as_ref()) - } -} - -impl<'a, T: 'a + HexEncodable> Deserialize<'a> for HexEncode { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'a>, - { - deserializer.deserialize_any(HexEncodeVisitor::(::std::marker::PhantomData)) - } -} - -// helper type for decoding anything from hex. -struct HexEncodeVisitor(::std::marker::PhantomData); - -impl<'a, T: HexEncodable> Visitor<'a> for HexEncodeVisitor { - type Value = HexEncode; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "a 0x-prefixed, hex-encoded vector of bytes") - } - - fn visit_str(self, value: &str) -> Result { - let decoded = if value.len() >= 2 && &value[0..2] == "0x" && value.len() & 1 == 0 { - Ok(Vec::from_hex(&value[2..]).map_err(|_| Error::custom("invalid hex"))?) - } else { - Err(Error::custom("invalid format")) - }; - - decoded - .and_then(|x| T::from_bytes(x).ok_or(Error::custom("invalid format"))) - .map(HexEncode) - } - - fn visit_string(self, value: String) -> Result - where - E: Error, - { - self.visit_str(value.as_ref()) - } -} - -/// Receiver of a message. Either a public key, identity (presumably symmetric), -/// or broadcast over the topics. -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum Receiver { - Public(Public), - Identity(Identity), -} - -/// A request to post a message to the whisper network. -#[derive(Deserialize)] -pub struct PostRequest { - /// Receiver of the message. Either a public key or - /// an identity. If the identity is symmetric, it will - /// encrypt to that identity. - /// - /// If the receiver is missing, this will be a broadcast message. - pub to: Option, - - /// Sender of the message. - /// - /// If present, the payload will be signed by this - /// identity. The call will fail if the whisper node doesn't store the - /// signing key for this identity. - #[serde(skip_serializing_if = "Option::is_none")] - pub from: Option, - - /// Full topics to identify a message by. - /// At least one topic must be specified if the receiver is - /// not specified. - pub topics: Vec, - - /// Payload of the message - pub payload: Bytes, - - /// Optional padding of the message. No larger than 2^24 - 1. - pub padding: Option, - - /// Priority of the message: how many milliseconds to spend doing PoW - pub priority: u64, - - /// Time-To-Live of the message in seconds. - pub ttl: u64, -} - -/// Request for filter or subscription creation. -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FilterRequest { - /// ID of key used for decryption. - /// - /// If this identity is removed, then no further messages will be returned. - /// - /// If optional, this will listen for broadcast messages. - pub decrypt_with: Option, - - /// Accept only messages signed by given public key. - pub from: Option, - - /// Possible topics. Cannot be empty if the identity is `None` - pub topics: Vec, -} - -/// A message captured by a filter or subscription. -#[derive(Serialize, Clone)] -pub struct FilterItem { - /// Public key that signed this message. - #[serde(skip_serializing_if = "Option::is_none")] - pub from: Option, - - /// Identity of recipient. If the filter wasn't registered with a - /// recipient, this will be `None`. - #[serde(skip_serializing_if = "Option::is_none")] - pub recipient: Option, - - /// Time to live in seconds. - pub ttl: u64, - - /// Topics that matched the filter. - pub topics: Vec, - - /// Unix timestamp of the message generation. - pub timestamp: u64, - - /// Decrypted/Interpreted payload. - pub payload: Bytes, - - /// Optional padding data. - #[serde(skip_serializing_if = "Option::is_none")] - pub padding: Option, -} - -/// Whisper node info. -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct NodeInfo { - /// min PoW to be accepted into the local pool. - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename = "minPow")] - pub required_pow: Option, - - /// Number of messages in the pool. - pub messages: usize, - - /// Memory used by messages in the pool. - pub memory: usize, - - /// Target memory of the pool. - pub target_memory: usize, -} - -#[cfg(test)] -mod tests { - use super::*; - use hex::FromHex; - use serde_json; - - #[test] - fn test_bytes_serialize() { - let bytes = Bytes::new(Vec::from_hex("0123456789abcdef").unwrap()); - let serialized = serde_json::to_string(&bytes).unwrap(); - assert_eq!(serialized, r#""0x0123456789abcdef""#); - } - - #[test] - fn test_bytes_deserialize() { - let bytes2: Result = serde_json::from_str(r#""0x123""#); - let bytes3: Result = serde_json::from_str(r#""0xgg""#); - - let bytes4: Bytes = serde_json::from_str(r#""0x""#).unwrap(); - let bytes5: Bytes = serde_json::from_str(r#""0x12""#).unwrap(); - let bytes6: Bytes = serde_json::from_str(r#""0x0123""#).unwrap(); - - assert!(bytes2.is_err()); - assert!(bytes3.is_err()); - assert_eq!(bytes4, Bytes::new(vec![])); - assert_eq!(bytes5, Bytes::new(vec![0x12])); - assert_eq!(bytes6, Bytes::new(vec![0x1, 0x23])); - } - - #[test] - fn deserialize_topic() { - let topic = AbridgedTopic::new([1, 2, 3, 15].into()); - - let topic1: Result = serde_json::from_str(r#""0x010203""#); - let topic2: Result = serde_json::from_str(r#""0102030F""#); - let topic3: AbridgedTopic = serde_json::from_str(r#""0x0102030F""#).unwrap(); - - assert!(topic1.is_err()); - assert!(topic2.is_err()); - assert_eq!(topic3, topic); - } -}