diff --git a/Cargo.lock b/Cargo.lock index 815ab080c..4611213da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -237,11 +237,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.0.4" @@ -1743,11 +1738,6 @@ dependencies = [ "unicode-segmentation 1.2.1 (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 = "hmac" version = "0.7.0" @@ -2593,15 +2583,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" @@ -2735,7 +2716,6 @@ dependencies = [ "parity-updater 1.12.0", "parity-util-mem 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-version 2.7.0", - "parity-whisper 0.1.0", "parking_lot 0.8.0 (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.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3021,37 +3001,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.6.0 (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 12.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-derive 12.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-pubsub 12.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "ordered-float 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-util-mem 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rlp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.39 (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.10 (registry+https://github.com/rust-lang/crates.io-index)", - "time-utils 0.1.0", - "tiny-keccak 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "zeroize 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "parity-wordlist" version = "1.3.0" @@ -4720,26 +4669,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.0.2 (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 12.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-http-server 12.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-pubsub 12.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.6 (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.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "winapi" version = "0.2.8" @@ -4877,7 +4806,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.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" "checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" @@ -4965,7 +4893,6 @@ dependencies = [ "checksum hashmap_core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8e04cb7a5051270ef3fa79f8c7604d581ecfa73d520e74f554e45541c4b5881a" "checksum heapsize 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461" "checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82" -"checksum hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" "checksum hmac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f127a908633569f208325f86f71255d3363c79721d7f9fe31cd5569908819771" "checksum home 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "80dff82fb58cfbbc617fb9a9184b010be0529201553cda50ad04372bc2333aff" "checksum http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "eed324f0f0daf6ec10c474f150505af2c143f251722bf9dbd1261bd1f2ee2c1a" @@ -5051,7 +4978,6 @@ dependencies = [ "checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c" "checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" "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.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" "checksum parity-bytes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa5168b4cf41f3835e4bc6ffb32f51bc9365dc50cb351904595b3931d917fd0c" diff --git a/Cargo.toml b/Cargo.toml index f85a72d6e..5adec8028 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" } @@ -134,7 +133,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 e4aec8992..9304f6057 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ We recommend installing Rust through [rustup](https://www.rustup.rs/). If you do `clang` is required. It comes with Xcode command line tools or can be installed with homebrew. -- Windows: +- Windows: Make sure you have Visual Studio 2015 with C++ support installed. Next, download and run the `rustup` installer from https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe, start "VS2015 x64 Native Tools Command Prompt", and use the following command to install and set up the `msvc` toolchain: ```bash @@ -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,10 +356,10 @@ 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) +- [whisper](https://github.com/paritytech/whisper) - Parity Ethereum Whisper-v2 PoC Implementation. ## 7. Community diff --git a/accounts/ethkey/README.md b/accounts/ethkey/README.md index 23c57fa4c..3cec30917 100644 --- a/accounts/ethkey/README.md +++ b/accounts/ethkey/README.md @@ -218,4 +218,4 @@ _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. +- [whisper](https://github.com/paritytech/whisper) - Implementation of Whisper-v2 PoC. diff --git a/accounts/ethstore/README.md b/accounts/ethstore/README.md index 77c37bd24..7106b2e12 100644 --- a/accounts/ethstore/README.md +++ b/accounts/ethstore/README.md @@ -337,4 +337,4 @@ _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. +- [whisper](https://github.com/paritytech/whisper) - Implementation of Whisper-v2 PoC. diff --git a/ethcore/sync/src/api.rs b/ethcore/sync/src/api.rs index db3da875e..9a8c6a8bf 100644 --- a/ethcore/sync/src/api.rs +++ b/ethcore/sync/src/api.rs @@ -219,31 +219,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. @@ -291,8 +266,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 @@ -303,8 +276,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. @@ -409,7 +380,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), is_major_syncing }); @@ -613,9 +583,6 @@ impl ChainNotify for EthSync { self.network.register_protocol(light_proto, self.light_subprotocol_name, ::light::net::PROTOCOL_VERSIONS) .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) { @@ -886,15 +853,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, @@ -937,7 +901,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, @@ -996,8 +959,6 @@ impl ManageNetwork for LightSync { self.network.register_protocol(light_proto, self.subprotocol_name, ::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..936522977 100644 --- a/evmbin/README.md +++ b/evmbin/README.md @@ -52,4 +52,4 @@ _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. +- [whisper](https://github.com/paritytech/whisper) - Implementation of Whisper-v2 PoC. diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index b17ad6bf9..111199b87 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -494,9 +494,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,rpc,shh,shh_pubsub,parity_transactions_pool", 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,rpc,parity_transactions_pool", 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, rpc, 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, rpc, 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, rpc, 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, rpc", ARG arg_jsonrpc_hosts: (String) = "none", or |c: &Config| c.rpc.as_ref()?.hosts.as_ref().map(|vec| vec.join(",")), "--jsonrpc-hosts=[HOSTS]", @@ -535,9 +535,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,rpc,shh,shh_pubsub,parity_transactions_pool", 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,rpc,parity_transactions_pool", 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, rpc, 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, rpc, 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, rpc, 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, rpc", 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]", @@ -560,9 +560,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,rpc,shh,shh_pubsub,parity_transactions_pool", 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,rpc,parity_transactions_pool", 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, rpc, 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, rpc, 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, rpc, 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, rpc", ["API and Console Options – IPFS"] FLAG flag_ipfs_api: (bool) = false, or |c: &Config| c.ipfs.as_ref()?.enable.clone(), @@ -926,11 +926,11 @@ usage! { ["Whisper Options"] FLAG flag_whisper: (bool) = false, or |c: &Config| c.whisper.as_ref()?.enabled, "--whisper", - "Enable the Whisper network.", + "Does nothing. Whisper has been moved to https://github.com/paritytech/whisper", - ARG arg_whisper_pool_size: (usize) = 10usize, or |c: &Config| c.whisper.as_ref()?.pool_size.clone(), + ARG arg_whisper_pool_size: (Option) = None, or |c: &Config| c.whisper.as_ref()?.pool_size.clone(), "--whisper-pool-size=[MB]", - "Target size of the whisper message pool in megabytes.", + "Does nothing. Whisper has been moved to https://github.com/paritytech/whisper", ["Legacy Options"] // Options that are hidden from config, but are still unique for its functionality. @@ -1942,7 +1942,7 @@ mod tests { // -- Whisper options. flag_whisper: false, - arg_whisper_pool_size: 20, + arg_whisper_pool_size: Some(20), // -- Legacy Options flag_warp: false, diff --git a/parity/configuration.rs b/parity/configuration.rs index 3aa054147..b4e2d175b 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -362,7 +362,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()?; let run_cmd = RunCmd { @@ -412,7 +411,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, @@ -1179,13 +1177,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(s: Option<&String>) -> Result, String> { @@ -1481,7 +1472,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/deprecated.rs b/parity/deprecated.rs index 49155225a..1c50ab5d3 100644 --- a/parity/deprecated.rs +++ b/parity/deprecated.rs @@ -229,6 +229,16 @@ pub fn find_deprecated(args: &Args) -> Vec { result.push(Deprecated::Removed("--ntp-servers")); } + // Removed in 2.7. + + if args.flag_whisper { + result.push(Deprecated::Removed("--whisper")); + } + + if args.arg_whisper_pool_size.is_some() { + result.push(Deprecated::Removed("--whisper-pool-size")); + } + result } @@ -261,6 +271,8 @@ mod tests { args.flag_dapps_apis_all = true; args.flag_fast_and_loose = true; args.arg_ntp_servers = Some(Default::default()); + args.flag_whisper = true; + args.arg_whisper_pool_size = Some(Default::default()); args }), vec![ Deprecated::DoesNothing("--warp"), @@ -282,6 +294,8 @@ mod tests { Deprecated::Replaced("--dapps-apis-all", "--jsonrpc-apis"), Deprecated::Removed("--fast-and-loose"), Deprecated::Removed("--ntp-servers"), + Deprecated::Removed("--whisper"), + Deprecated::Removed("--whisper-pool-size"), ]); } } diff --git a/parity/lib.rs b/parity/lib.rs index 9dbc8c6ea..1092ff061 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] @@ -111,7 +110,6 @@ mod signer; mod snapshot; mod upgrade; mod user_defaults; -mod whisper; mod db; use std::fs::File; diff --git a/parity/modules.rs b/parity/modules.rs index d5dc3a0de..8c86cda9b 100644 --- a/parity/modules.rs +++ b/parity/modules.rs @@ -17,7 +17,7 @@ use std::sync::{Arc, mpsc}; use ethcore::client::BlockChainClient; -use sync::{self, AttachedProtocol, SyncConfig, NetworkConfiguration, Params, ConnectionFilter}; +use sync::{self, SyncConfig, NetworkConfiguration, Params, ConnectionFilter}; use ethcore::snapshot::SnapshotService; use light::Provider; use parity_runtime::Executor; @@ -42,7 +42,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(Params { @@ -53,7 +52,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 66d376c03..b3aec40dc 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -66,12 +66,6 @@ pub enum Api { Rpc, /// 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)) @@ -85,6 +79,8 @@ pub enum Api { Debug, /// Parity Transactions pool PubSub ParityTransactionsPool, + /// Deprecated api + Deprecated, } impl FromStr for Api { @@ -106,12 +102,11 @@ impl FromStr for Api { "pubsub" => Ok(EthPubSub), "rpc" => Ok(Rpc), "secretstore" => Ok(SecretStore), - "shh" => Ok(Whisper), - "shh_pubsub" => Ok(WhisperPubSub), "signer" => Ok(Signer), "traces" => Ok(Traces), "web3" => Ok(Web3), "parity_transactions_pool" => Ok(ParityTransactionsPool), + "shh" | "shh_pubsub" => Ok(Deprecated), api => Err(format!("Unknown api: {}", api)), } } @@ -193,9 +188,10 @@ fn to_modules(apis: &HashSet) -> BTreeMap { Api::Signer => ("signer", "1.0"), Api::Traces => ("traces", "1.0"), Api::Web3 => ("web3", "1.0"), - Api::Whisper => ("shh", "1.0"), - Api::WhisperPubSub => ("shh_pubsub", "1.0"), Api::ParityTransactionsPool => ("parity_transactions_pool", "1.0"), + Api::Deprecated => { + continue; + } }; modules.insert(name.into(), version.into()); } @@ -255,7 +251,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, @@ -449,28 +444,13 @@ 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())) .to_delegate(), ); } + Api::Deprecated => {}, } } } @@ -517,7 +497,6 @@ pub struct LightDependencies { pub geth_compatibility: bool, 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, @@ -686,26 +665,13 @@ 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()); handler.extend_with(PrivateClient::new(private_tx_service).to_delegate()); } } + Api::Deprecated => {}, } } } @@ -740,8 +706,6 @@ impl ApiSet { Api::EthPubSub, Api::Parity, Api::Rpc, - Api::Whisper, - Api::WhisperPubSub, Api::Private, ] .into_iter() @@ -749,7 +713,10 @@ impl ApiSet { .collect(); match *self { - ApiSet::List(ref apis) => apis.clone(), + ApiSet::List(ref apis) => apis.into_iter() + .filter(|api| *api != &Api::Deprecated) + .cloned() + .collect(), ApiSet::UnsafeContext => { public_list.insert(Api::Traces); public_list.insert(Api::ParityPubSub); @@ -810,8 +777,6 @@ mod test { assert_eq!(Api::Rpc, "rpc".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_eq!(Api::ParityTransactionsPool, "parity_transactions_pool".parse().unwrap()); assert!("rp".parse::().is_err()); } @@ -841,8 +806,6 @@ mod test { Api::ParityPubSub, Api::Traces, Api::Rpc, - Api::Whisper, - Api::WhisperPubSub, Api::Private, Api::ParityTransactionsPool, ].into_iter() @@ -862,8 +825,6 @@ mod test { Api::ParityPubSub, Api::Traces, Api::Rpc, - Api::Whisper, - Api::WhisperPubSub, Api::Private, Api::ParityTransactionsPool, // semi-safe @@ -888,8 +849,6 @@ mod test { Api::Traces, Api::Rpc, Api::SecretStore, - Api::Whisper, - Api::WhisperPubSub, Api::ParityAccounts, Api::ParitySet, Api::Signer, @@ -918,8 +877,6 @@ mod test { Api::Traces, Api::Rpc, Api::SecretStore, - Api::Whisper, - Api::WhisperPubSub, Api::ParityAccounts, Api::ParitySet, Api::Signer, @@ -946,8 +903,6 @@ mod test { Api::ParityPubSub, Api::Traces, Api::Rpc, - Api::Whisper, - Api::WhisperPubSub, Api::Private, Api::ParityTransactionsPool, ].into_iter() diff --git a/parity/run.rs b/parity/run.rs index 0652e8f9d..4e74f0d25 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -130,7 +130,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, @@ -269,15 +268,6 @@ fn execute_light_impl(cmd: RunCmd, logger: Arc, on_client_rq 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 { @@ -286,7 +276,6 @@ fn execute_light_impl(cmd: RunCmd, logger: Arc, on_client_rq 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))?; let light_sync = Arc::new(light_sync); @@ -326,7 +315,6 @@ fn execute_light_impl(cmd: RunCmd, logger: Arc, on_client_rq geth_compatibility: cmd.geth_compatibility, 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 @@ -633,17 +621,6 @@ fn execute_impl(cmd: RunCmd, logger: Arc, on_client_rq: .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, @@ -659,7 +636,6 @@ fn execute_impl(cmd: RunCmd, logger: Arc, on_client_rq: private_tx_sync, client.clone(), &cmd.logger_config, - attached_protos, connection_filter.clone().map(|f| f as Arc<::sync::ConnectionFilter + 'static>), ).map_err(|e| format!("Sync error: {}", e))?; @@ -745,7 +721,6 @@ fn execute_impl(cmd: RunCmd, logger: Arc, on_client_rq: 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 e9a744b54..000000000 --- a/parity/whisper.rs +++ /dev/null @@ -1,114 +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::sync::Arc; -use std::io; - -use sync::{AttachedProtocol, ManageNetwork}; -use parity_rpc::Metadata; -use parity_whisper::message::Message; -use parity_whisper::net::{self as whisper_net, Network as WhisperNetwork}; -use parity_whisper::rpc::{WhisperClient, PoolHandle, FilterManager}; - -/// 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/gitlab/build-linux.sh b/scripts/gitlab/build-linux.sh index 8f3e6f14e..75a8682bb 100755 --- a/scripts/gitlab/build-linux.sh +++ b/scripts/gitlab/build-linux.sh @@ -27,7 +27,6 @@ else time cargo build --target $CARGO_TARGET --verbose --color=always --release -p evmbin time cargo build --target $CARGO_TARGET --verbose --color=always --release -p ethstore-cli time cargo build --target $CARGO_TARGET --verbose --color=always --release -p ethkey-cli - time cargo build --target $CARGO_TARGET --verbose --color=always --release -p whisper-cli fi echo "_____ Post-processing binaries _____" @@ -43,7 +42,6 @@ else cp -v ../../target/$CARGO_TARGET/release/parity-evm ./parity-evm cp -v ../../target/$CARGO_TARGET/release/ethstore ./ethstore cp -v ../../target/$CARGO_TARGET/release/ethkey ./ethkey - cp -v ../../target/$CARGO_TARGET/release/whisper ./whisper fi echo "_____ Calculating checksums _____" diff --git a/scripts/gitlab/build-windows.sh b/scripts/gitlab/build-windows.sh index 3c32e866a..477a8edde 100755 --- a/scripts/gitlab/build-windows.sh +++ b/scripts/gitlab/build-windows.sh @@ -22,14 +22,12 @@ time cargo build --target $CARGO_TARGET --verbose --release --features final time cargo build --target $CARGO_TARGET --verbose --release -p evmbin time cargo build --target $CARGO_TARGET --verbose --release -p ethstore-cli time cargo build --target $CARGO_TARGET --verbose --release -p ethkey-cli -time cargo build --target $CARGO_TARGET --verbose --release -p whisper-cli echo "__________Sign binaries__________" scripts/gitlab/sign-win.cmd $keyfile $certpass target/$CARGO_TARGET/release/parity.exe scripts/gitlab/sign-win.cmd $keyfile $certpass target/$CARGO_TARGET/release/parity-evm.exe scripts/gitlab/sign-win.cmd $keyfile $certpass target/$CARGO_TARGET/release/ethstore.exe scripts/gitlab/sign-win.cmd $keyfile $certpass target/$CARGO_TARGET/release/ethkey.exe -scripts/gitlab/sign-win.cmd $keyfile $certpass target/$CARGO_TARGET/release/whisper.exe echo "_____ Post-processing binaries _____" rm -rf artifacts @@ -41,7 +39,6 @@ cp --verbose ../../target/$CARGO_TARGET/release/parity.exe ./parity.exe cp --verbose ../../target/$CARGO_TARGET/release/parity-evm.exe ./parity-evm.exe cp --verbose ../../target/$CARGO_TARGET/release/ethstore.exe ./ethstore.exe cp --verbose ../../target/$CARGO_TARGET/release/ethkey.exe ./ethkey.exe -cp --verbose ../../target/$CARGO_TARGET/release/whisper.exe ./whisper.exe echo "_____ Calculating checksums _____" for binary in $(ls) 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 4ab1e3f6d..000000000 --- a/whisper/Cargo.toml +++ /dev/null @@ -1,33 +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.6.0" -ethcore-network = { path = "../util/network" } -ring = "0.14.6" -ethkey = { path = "../accounts/ethkey" } -hex = "0.2" -log = "0.4" -parity-util-mem = "0.1" -ordered-float = "0.5" -parking_lot = "0.8" -rand = "0.6" -rand_xorshift = "0.1.1" -rlp = "0.4.0" -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 = "12.0.0" -jsonrpc-derive = "12.0.0" -jsonrpc-pubsub = "12.0.0" -zeroize = "0.9.1" 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 8e0e2a905..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 = "12.0.0" -jsonrpc-http-server = "12.0.0" -jsonrpc-pubsub = "12.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 fbf5b5aa7..000000000 --- a/whisper/cli/src/main.rs +++ /dev/null @@ -1,352 +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 jsonrpc_core; -extern crate jsonrpc_pubsub; -extern crate jsonrpc_http_server; -extern crate ethkey; -extern crate rustc_hex; - -#[macro_use] -extern crate log as rlog; - -#[macro_use] -extern crate serde_derive; - -use docopt::Docopt; -use std::{fmt, io, process, env, sync::Arc}; -use jsonrpc_core::{Metadata, MetaIoHandler}; -use jsonrpc_pubsub::{PubSubMetadata, Session}; -use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation}; -use std::net::{SocketAddr, SocketAddrV4, Ipv4Addr}; -use std::str::FromStr; -use ethkey::Secret; -use rustc_hex::FromHex; - -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 FnMut(&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] - // The Whisper pool size is of type usize. Invalid Whisper pool sizes include - // values below 0 and either above 2 ** 64 - 1 on a 64-bit processor or - // above 2 ** 32 - 1 on a 32-bit processor. - fn invalid_whisper_pool_size() { - let command_pool_size_too_low = vec!["whisper", "--whisper-pool-size=-1"] - .into_iter() - .map(Into::into) - .collect::>(); - - let command_pool_size_too_high = vec!["whisper", "--whisper-pool-size=18446744073709552000"] - .into_iter() - .map(Into::into) - .collect::>(); - - assert!(execute(command_pool_size_too_low).is_err()); - assert!(execute(command_pool_size_too_high).is_err()); - } -} diff --git a/whisper/src/aes_gcm/mod.rs b/whisper/src/aes_gcm/mod.rs deleted file mode 100644 index d7db87b32..000000000 --- a/whisper/src/aes_gcm/mod.rs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2015-2019 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -use ring; - -/// AES GCM encryptor. -pub struct Encryptor<'a> { - key: ring::aead::SealingKey, - ad: &'a [u8], - offset: usize, -} - -impl<'a> Encryptor<'a> { - pub fn aes_256_gcm(key: &[u8; 32]) -> Result, ring::error::Unspecified> { - let sk = ring::aead::SealingKey::new(&ring::aead::AES_256_GCM, key)?; - Ok(Encryptor { - key: sk, - ad: &[], - offset: 0, - }) - } - - /// Optional offset value. Only the slice `[offset..]` will be encrypted. - pub fn offset(&mut self, off: usize) -> &mut Self { - self.offset = off; - self - } - - /// Please note that the pair (key, nonce) must never be reused. Using random nonces - /// limits the number of messages encrypted with the same key to 2^32 (cf. [[1]]) - /// - /// [1]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf - pub fn encrypt(&self, nonce: &[u8; 12], mut data: Vec) -> Result, ring::error::Unspecified> { - if self.offset > data.len() { - return Err(ring::error::Unspecified) - } - let tag_len = ring::aead::AES_256_GCM.tag_len(); - data.extend(::std::iter::repeat(0).take(tag_len)); - let nonce = ring::aead::Nonce::assume_unique_for_key(*nonce); - let aad = ring::aead::Aad::from(self.ad); - let len = ring::aead::seal_in_place(&self.key, nonce, aad, &mut data[self.offset ..], tag_len)?; - data.truncate(self.offset + len); - Ok(data) - } -} - -/// AES GCM decryptor. -pub struct Decryptor<'a> { - key: ring::aead::OpeningKey, - ad: &'a [u8], - offset: usize, -} - -impl<'a> Decryptor<'a> { - pub fn aes_256_gcm(key: &[u8; 32]) -> Result, ring::error::Unspecified> { - let ok = ring::aead::OpeningKey::new(&ring::aead::AES_256_GCM, key)?; - Ok(Decryptor { - key: ok, - ad: &[], - offset: 0, - }) - } - - pub fn decrypt(&self, nonce: &[u8; 12], mut data: Vec) -> Result, ring::error::Unspecified> { - if self.offset > data.len() { - return Err(ring::error::Unspecified) - } - let nonce = ring::aead::Nonce::assume_unique_for_key(*nonce); - let aad = ring::aead::Aad::from(self.ad); - let len = ring::aead::open_in_place(&self.key, nonce, aad, 0, &mut data[self.offset ..])?.len(); - data.truncate(self.offset + len); - Ok(data) - } -} - -#[cfg(test)] -mod tests { - use super::{Encryptor, Decryptor}; - - #[test] - fn aes_gcm_256() { - let secret = b"12345678901234567890123456789012"; - let nonce = b"123456789012"; - let message = b"So many books, so little time"; - - let ciphertext = Encryptor::aes_256_gcm(secret) - .unwrap() - .encrypt(nonce, message.to_vec()) - .unwrap(); - - assert!(ciphertext != message); - - let plaintext = Decryptor::aes_256_gcm(secret) - .unwrap() - .decrypt(nonce, ciphertext) - .unwrap(); - - assert_eq!(plaintext, message) - } -} diff --git a/whisper/src/lib.rs b/whisper/src/lib.rs deleted file mode 100644 index a9dc5889c..000000000 --- a/whisper/src/lib.rs +++ /dev/null @@ -1,65 +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))] - -extern crate byteorder; -extern crate ethcore_network as network; -extern crate ethereum_types; -extern crate ethkey; -extern crate hex; -extern crate parity_util_mem; -extern crate ordered_float; -extern crate parking_lot; -extern crate rand; -extern crate rand_xorshift; -extern crate ring; -extern crate rlp; -extern crate serde; -extern crate slab; -extern crate smallvec; -extern crate tiny_keccak; -extern crate zeroize; - -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; -pub use self::net::{Network, MessageHandler}; - -pub mod message; -pub mod net; -pub mod rpc; - -mod aes_gcm; diff --git a/whisper/src/message.rs b/whisper/src/message.rs deleted file mode 100644 index cf12d65b3..000000000 --- a/whisper/src/message.rs +++ /dev/null @@ -1,577 +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; -use std::time::{self, SystemTime, Duration, Instant}; - -use ethereum_types::{H256, H512}; -use rlp::{self, DecoderError, RlpStream, Rlp}; -use smallvec::SmallVec; -use tiny_keccak::{keccak256, Keccak}; - -#[cfg(not(time_checked_add))] -use time_utils::CheckedSystemTime; - -/// Bloom of topics. -type Bloom = H512; -/// Topic data index within a bloom. -type BloomTopicIndex = usize; -/// List of envelope topics. -type EnvelopeTopics = SmallVec<[EnvelopeTopic; 4]>; -/// Envelope topic data. -type EnvelopeTopicData = u8; -/// List of envelope topics data. -type EnvelopeTopicsData = [EnvelopeTopicData; 4]; -/// Expiry timestamp of an envelope. -type EnvelopeExpiryTimestamp = u64; -/// Message contained within an envelope -type EnvelopeMessage = Vec; -/// Arbitrary value used to target lower PoW hash. -type EnvelopeNonce = u64; -/// Envelope nonce in bytes. -type EnvelopeNonceBytes = [u8; 8]; -/// Envelope proving work duration in milliseconds. -type EnvelopeProvingWorkDuration = u64; -/// Envelope message uniquely identifying proving hash. -type EnvelopeProvingHash = H256; -/// Envelope work that has been proved by the proving hash. -type EnvelopeProvenWork = f64; -/// Time-to-live of an envelope in seconds. -type EnvelopeTTLDuration = u64; - -/// 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: EnvelopeTTLDuration, hash: EnvelopeProvingHash) -> EnvelopeProvenWork { - assert!(size != 0 && ttl != 0); - - let leading_zeros = { - let leading_bytes = hash.as_ref().iter().take_while(|&&x| x == 0).count(); - let remaining_leading_bits = hash.as_ref().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. The topic is an abridged version of the first four bytes of the original topic's hash. -#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct EnvelopeTopic(pub EnvelopeTopicsData); - -impl From for EnvelopeTopic { - fn from(x: EnvelopeTopicsData) -> Self { - EnvelopeTopic(x) - } -} - -impl EnvelopeTopic { - /// 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 Bloom) { - - let topics_data = &self.0; - for i in 0..3 { - let mut topic_idx = topics_data[i] as BloomTopicIndex; - - if topics_data[3] & (1 << i) != 0 { - topic_idx += 256; - } - - debug_assert!(topic_idx <= 511); - bloom.as_bytes_mut()[topic_idx / 8] |= 1 << (7 - topic_idx % 8); - } - } - - /// Get bloom for single topic. - pub fn bloom(&self) -> Bloom { - let mut bloom = Default::default(); - self.bloom_into(&mut bloom); - bloom - } -} - -impl rlp::Encodable for EnvelopeTopic { - fn rlp_append(&self, s: &mut RlpStream) { - s.encoder().encode_value(&self.0); - } -} - -impl rlp::Decodable for EnvelopeTopic { - 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: EnvelopeTopicsData = [0u8; 4]; - t.copy_from_slice(bytes); - Ok(EnvelopeTopic(t)) - } - }) - } -} - -/// Calculate union of blooms for given topics. -pub fn bloom_topics(topics: &[EnvelopeTopic]) -> Bloom { - 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."), - } - } -} - -/// Append given topic(s) to RLP stream. -fn append_topics<'a>(s: &'a mut RlpStream, topics: &[EnvelopeTopic]) -> &'a mut RlpStream { - if topics.len() == 1 { - s.append(&topics[0]) - } else { - s.append_list(&topics) - } -} - -fn decode_topics(rlp: Rlp) -> Result { - if rlp.is_list() { - rlp.iter().map(|r| r.as_val::()).collect() - } else { - rlp.as_val().map(|t| SmallVec::from_slice(&[t])) - } -} - -/// An `Envelope` instance is contained in each `Message`. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Envelope { - /// Expiry timestamp. - pub expiry: EnvelopeExpiryTimestamp, - /// Time-to-live in seconds. - pub ttl: EnvelopeTTLDuration, - /// Series of 4-byte topics. - pub topics: EnvelopeTopics, - /// The message contained within an envelope. - pub message_data: EnvelopeMessage, - /// Arbitrary value used to target lower PoW hash. - pub nonce: EnvelopeNonce, -} - -impl Envelope { - /// Whether the message is multi-topic. Only relay these to Parity peers. - pub fn is_multitopic(&self) -> bool { - self.topics.len() != 1 - } - - // Generate the uniquely identifying proving hash for the message. - fn proving_hash(&self) -> EnvelopeProvingHash { - 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.message_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.message_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)?)?, - message_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 { - /// Envelope time-to-live in seconds. - pub ttl: EnvelopeTTLDuration, - /// Envelope payload of message data. - pub payload: EnvelopeMessage, - /// Envelope topics. Must not be empty. - pub topics: Vec, - /// How many milliseconds to spend proving work. - pub work: EnvelopeProvingWorkDuration, -} - -/// A whisper message. This is a checked message carrying around metadata. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Message { - envelope: Envelope, - bloom: Bloom, - hash: EnvelopeProvingHash, - 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}; - use rand_xorshift::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()) - }; - - assert!(params.ttl > 0); - - // Expiry period since the last epoch rounded up to the nearest second. - 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 } - }; - - // Encrypt an RLP stream into a digest. Create the RLP stream by appending - // to it the envelope topics, envelope payload of message data, - // envelope ttl, and the expiry period since the last epoch. - 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 - }; - - // Find the best nonce based on using updating the digest with - // randomly generated envelope nonce bytes - let mut buf = [0; 32]; - let mut try_nonce = move |nonce: &EnvelopeNonceBytes| { - let mut digest = start_digest.clone(); - digest.update(&nonce[..]); - digest.finalize(&mut buf[..]); - - buf.clone() - }; - - let mut nonce: EnvelopeNonceBytes = rng.gen(); - let mut best_found = try_nonce(&nonce); - - // Start proving work, which involves repeatedly trying to create another - // nonce hash that is better (lower PoW hash) than the latest best nonce, - // to replace it. - 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, - ttl: params.ttl, - topics: params.topics.into_iter().collect(), - message_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: EnvelopeProvingHash, 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, - bloom, - 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 proving hash for the message. - pub fn hash(&self) -> &EnvelopeProvingHash { - &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) -> EnvelopeProvenWork { - 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) -> &[EnvelopeTopic] { - &self.envelope.topics - } - - /// Get the message data. - pub fn message_data(&self) -> &EnvelopeMessage { - &self.envelope.message_data - } -} - -#[cfg(test)] -mod tests { - use ethereum_types::H256; - use super::*; - use std::time::{self, Duration, SystemTime}; - use rlp::Rlp; - use smallvec::SmallVec; - - 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![EnvelopeTopic([1, 2, 1, 2])], - work: 50, - }).is_ok()); - } - - #[test] - fn round_trip() { - let envelope = Envelope { - expiry: 100_000, - ttl: 30, - message_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, - message_data: vec![9; 256], - topics: SmallVec::from_slice(&[Default::default(), EnvelopeTopic([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, - message_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, - message_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, - message_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, EnvelopeProvingHash::zero()), 115792089237316200000000000000000000000000000000000000000000000000000000000000.0); - // 255 leading zeros -> 2^255 / 1 - assert_eq!(work_factor_proved(1, 1, EnvelopeProvingHash::from_low_u64_be(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 d263f6cfb..000000000 --- a/whisper/src/net/mod.rs +++ /dev/null @@ -1,731 +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::collections::{HashMap, HashSet}; -use std::cmp::Ordering; -use std::fmt; -use std::time::{Duration, SystemTime}; -use std::sync::Arc; - -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, RlpStream, Rlp}; - -use message::{Message, Error as MessageError}; - -#[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::{ByteOrder, BigEndian}; - - 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: &NetworkContext) { - // set up broadcast timer (< 1s) - io.register_timer(RALLY_TOKEN, RALLY_TIMEOUT) - .expect("Failed to initialize message rally timer"); - } - - fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { - self.on_packet(io, peer, packet_id, data) - } - - fn connected(&self, io: &NetworkContext, peer: &PeerId) { - // peer with higher ID should begin rallying. - self.on_connect(io, peer) - } - - fn disconnected(&self, _io: &NetworkContext, peer: &PeerId) { - self.on_disconnect(peer) - } - - fn timeout(&self, io: &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: &NetworkContext) { } - - fn read(&self, _io: &NetworkContext, _peer: &PeerId, _id: u8, _msg: &[u8]) { } - - fn connected(&self, _io: &NetworkContext, _peer: &PeerId) { } - - fn disconnected(&self, _io: &NetworkContext, _peer: &PeerId) { } - - fn timeout(&self, _io: &NetworkContext, _timer: TimerToken) { } -} diff --git a/whisper/src/net/tests.rs b/whisper/src/net/tests.rs deleted file mode 100644 index fc4534dcc..000000000 --- a/whisper/src/net/tests.rs +++ /dev/null @@ -1,192 +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; -use std::sync::mpsc; - -use parking_lot::Mutex; -use network::{NodeId, PeerId}; - -use message::{CreateParams, Message}; -use super::*; - -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.as_bytes_mut()[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 ad2b59e21..000000000 --- a/whisper/src/rpc/crypto.rs +++ /dev/null @@ -1,269 +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 aes_gcm::{Encryptor, Decryptor}; -use ethkey::crypto::ecies; -use ethereum_types::H256; -use ethkey::{self, Public, Secret}; -use zeroize::Zeroizing; - -/// 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(Zeroizing<[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: Zeroizing<[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: Zeroizing<[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(Zeroizing<[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: Zeroizing<[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::zero(); - salted_topic.as_bytes_mut().copy_from_slice(&ciphertext[(known_index * 32)..][..32]); - let key = Zeroizing::new((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::*; - use rand::{Rng, rngs::OsRng}; - use std::ops::Deref; - - - #[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() { - let mut rng = OsRng::new().unwrap(); - let mut test_message = move |message: &[u8]| { - let key = Zeroizing::new(rng.gen::<[u8; 32]>()); - - let instance = EncryptionInstance::aes(Zeroizing::new(*key.deref()), 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() { - let mut rng = OsRng::new().unwrap(); - - let mut test_message = move |message: &[u8]| { - let all_topics = (0..5).map(|_| H256::random_using(&mut rng)).collect::>(); - let known_idx = 2; - let known_topic = all_topics[2]; - let key = Zeroizing::new(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 84a6445ca..000000000 --- a/whisper/src/rpc/filter.rs +++ /dev/null @@ -1,432 +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; -use std::{sync::{Arc, atomic, atomic::AtomicBool, mpsc}, thread}; - -use ethereum_types::{H256, H512}; -use ethkey::Public; -use jsonrpc_pubsub::typed::{Subscriber, Sink}; -use parking_lot::{Mutex, RwLock}; - -use message::{Message, EnvelopeTopic}; -use super::{key_store::KeyStore, types::{self, FilterItem, HexEncode}}; - -/// 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 = H256::random(); - 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::random(); - - 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, EnvelopeTopic)>, - 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.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 message::{CreateParams, Message, EnvelopeTopic}; - use rpc::types::{FilterRequest, HexEncode}; - use rpc::abridge_topic; - use super::*; - - #[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![EnvelopeTopic([1, 8, 3, 99])], - work: 0, - }).unwrap(); - - assert!(!filter.basic_matches(&message)); - } - - #[test] - fn decrypt_and_decode() { - use rpc::payload::{self, EncodeParams}; - use rpc::key_store::{Key, KeyStore}; - - 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 4d1815788..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 zeroize::Zeroizing; -use std::ops::Deref; -use rand::{Rng, rngs::OsRng}; - -use rpc::crypto::{AES_KEY_LEN, EncryptionInstance, DecryptionInstance}; - -/// 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(Zeroizing<[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(Zeroizing::new(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(Zeroizing::new(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 = H256::random_using(self.rng()); - 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(Zeroizing::new(*key.deref()), 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(Zeroizing::new(*key.deref())), - }) - } - - /// 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 56cc41b66..000000000 --- a/whisper/src/rpc/mod.rs +++ /dev/null @@ -1,390 +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::{Session, PubSubMetadata, SubscriptionId, typed::Subscriber}; - -use ethereum_types::H256; -use zeroize::Zeroizing; -use parking_lot::RwLock; - -use self::filter::Filter; -use self::key_store::{Key, KeyStore}; -use self::types::HexEncode; - -use message::{CreateParams, Message, EnvelopeTopic}; - -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]) -> EnvelopeTopic { - 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; - - /// Get symmetric key. Succeeds if identity has been stored. - #[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::{Rng, rngs::OsRng}; - - // 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 = Zeroizing::new(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 326a6b6e2..000000000 --- a/whisper/src/rpc/payload.rs +++ /dev/null @@ -1,356 +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 ethereum_types::H256; -use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; -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), - 0 ... 8 => Some(1), - 0 ... 16 => Some(2), - 0 ... 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 ethkey::{Generator, Random}; - use super::*; - - #[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 b5550dbff..000000000 --- a/whisper/src/rpc/types.rs +++ /dev/null @@ -1,298 +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; -use std::convert::AsRef; -use std::ops::Deref; - -use ethereum_types::{H32, H64, H128, H256, H264, H512}; -use hex::{ToHex, FromHex}; - -use serde::{Serialize, Serializer, Deserialize, Deserializer}; -use serde::de::{Error, Visitor}; - -/// Helper trait for generic hex bytes encoding. -pub trait HexEncodable: Sized + AsRef<[u8]> { - 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_bytes() { - 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.as_ref(); - 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 serde_json; - use hex::FromHex; - - #[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); - } -}