diff --git a/Cargo.lock b/Cargo.lock index 09bb3cc762..61dc33eb4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1092,6 +1092,7 @@ dependencies = [ "blake3", "bs58", "cipher", + "config", "digest 0.9.0", "ed25519-dalek", "generic-array 0.14.4", @@ -1101,6 +1102,7 @@ dependencies = [ "nymsphinx-types", "pemstore", "rand 0.7.3", + "subtle-encoding", "x25519-dalek", ] @@ -3694,7 +3696,6 @@ dependencies = [ "rocket", "serde", "serial_test", - "subtle-encoding", "tokio", "tokio-util", "toml", diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index 284be17faa..cf0032de13 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -19,7 +19,9 @@ x25519-dalek = "1.1" ed25519-dalek = "1.0" log = "0.4" rand = { version = "0.7.3", features = ["wasm-bindgen"] } +subtle-encoding = { version = "0.5", features = ["bech32-preview"]} # internal nymsphinx-types = { path = "../nymsphinx/types" } pemstore = { path = "../../common/pemstore" } +config = { path="../../common/config" } diff --git a/common/crypto/src/asymmetric/identity/mod.rs b/common/crypto/src/asymmetric/identity/mod.rs index 662488ebc3..73b67adc2d 100644 --- a/common/crypto/src/asymmetric/identity/mod.rs +++ b/common/crypto/src/asymmetric/identity/mod.rs @@ -189,6 +189,13 @@ impl PrivateKey { let sig = expanded_secret_key.sign(message, &public_key.0); Signature(sig) } + + /// Signs text with the provided Ed25519 private key, returning a base58 signature + pub fn sign_text(&self, text: &str) -> String { + let signature_bytes = self.sign(text.as_ref()).to_bytes(); + let signature = bs58::encode(signature_bytes).into_string(); + signature + } } impl PemStorableKey for PrivateKey { diff --git a/mixnode/src/crypto/bech32_address_validation.rs b/common/crypto/src/bech32_address_validation.rs similarity index 100% rename from mixnode/src/crypto/bech32_address_validation.rs rename to common/crypto/src/bech32_address_validation.rs diff --git a/common/crypto/src/lib.rs b/common/crypto/src/lib.rs index 04ea643513..b28a630dfd 100644 --- a/common/crypto/src/lib.rs +++ b/common/crypto/src/lib.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 pub mod asymmetric; +pub mod bech32_address_validation; pub mod crypto_hash; pub mod hkdf; pub mod hmac; diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs index 041af5d5e2..a7464e2b0c 100644 --- a/gateway/src/commands/init.rs +++ b/gateway/src/commands/init.rs @@ -60,6 +60,13 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { Arg::with_name(TESTNET_MODE_ARG_NAME) .long(TESTNET_MODE_ARG_NAME) .help("Set this gateway to work in a testnet mode that would allow clients to bypass bandwidth credential requirement") + ) + .arg( + Arg::with_name(WALLET_ADDRESS) + .long(WALLET_ADDRESS) + .help("The wallet address you will use to bond this gateway, e.g. nymt1z9egw0knv47nmur0p8vk4rcx59h9gg4zuxrrr9") + .takes_value(true) + .required(true) ); #[cfg(not(feature = "coconut"))] diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index 7273927827..09f0d7e0c6 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -1,8 +1,12 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::process; + use crate::config::Config; use clap::ArgMatches; +use colored::Colorize; +use crypto::bech32_address_validation; use url::Url; pub(crate) mod init; @@ -25,6 +29,7 @@ pub(crate) const ETH_ENDPOINT: &str = "eth_endpoint"; pub(crate) const ANNOUNCE_HOST_ARG_NAME: &str = "announce-host"; pub(crate) const DATASTORE_PATH: &str = "datastore"; pub(crate) const TESTNET_MODE_ARG_NAME: &str = "testnet-mode"; +pub(crate) const WALLET_ADDRESS: &str = "wallet-address"; fn parse_validators(raw: &str) -> Vec { raw.split(',') @@ -96,9 +101,36 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi config = config.with_eth_endpoint(String::from(eth_endpoint)); } + if let Some(wallet_address) = matches.value_of(WALLET_ADDRESS) { + let trimmed = wallet_address.trim(); + validate_bech32_address_or_exit(trimmed); + config = config.with_wallet_address(trimmed); + } + if matches.is_present(TESTNET_MODE_ARG_NAME) { config.with_testnet_mode(true) } else { config } } + +/// Ensures that a given bech32 address is valid, or exits +pub(crate) fn validate_bech32_address_or_exit(address: &str) { + if let Err(bech32_address_validation::Bech32Error::DecodeFailed(err)) = + bech32_address_validation::try_bech32_decode(address) + { + let error_message = format!("Error: wallet address decoding failed: {}", err).red(); + println!("{}", error_message); + println!("Exiting..."); + process::exit(1); + } + + if let Err(bech32_address_validation::Bech32Error::WrongPrefix(err)) = + bech32_address_validation::validate_bech32_prefix(address) + { + let error_message = format!("Error: wallet address type is wrong, {}", err).red(); + println!("{}", error_message); + println!("Exiting..."); + process::exit(1); + } +} diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index ef7643aaa2..f89cf213ed 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -139,6 +139,9 @@ pub async fn execute(matches: ArgMatches<'static>) { } let mut gateway = Gateway::new(config).await; + println!( + "\nTo bond your gateway you will need to install the Nym wallet, go to https://nymtech.net/get-involved and select the Download button.\n\ + Select the correct version and install it to your machine. You will need to provide the following: \n "); gateway.print_node_details(); gateway.run().await; diff --git a/gateway/src/commands/sign.rs b/gateway/src/commands/sign.rs index cbe738d221..1fa8963100 100644 --- a/gateway/src/commands/sign.rs +++ b/gateway/src/commands/sign.rs @@ -51,7 +51,7 @@ pub fn command_args<'a, 'b>() -> App<'a, 'b> { cmd.arg(address_sign_cmd) } -fn load_identity_keys(pathfinder: &GatewayPathfinder) -> identity::KeyPair { +pub fn load_identity_keys(pathfinder: &GatewayPathfinder) -> identity::KeyPair { let identity_keypair: identity::KeyPair = pemstore::load_keypair(&pemstore::KeyPairPath::new( pathfinder.private_identity_key().to_owned(), pathfinder.public_identity_key().to_owned(), @@ -111,45 +111,25 @@ fn sign_derived_address(private_key: &identity::PrivateKey, address: &AccountId) // we do tiny bit of sanity check validation #[cfg(feature = "coconut")] -fn sign_provided_address(private_key: &identity::PrivateKey, raw_address: &str) { +fn print_signed_address(private_key: &identity::PrivateKey, raw_address: &str) -> String { let trimmed = raw_address.trim(); - - // try to decode the address (to make sure it's a valid bech32 encoding) - let (prefix, _) = match bech32::decode(trimmed) { - Ok(decoded) => decoded, - Err(err) => { - let error_message = - format!("Your wallet address failed to get decoded! Are you sure you copied it correctly? The error was: {}", err).red(); - println!("{}", error_message); - process::exit(1); - } - }; - - if prefix != BECH32_PREFIX { - let error_message = - format!("Your wallet address must start with a '{}'", BECH32_PREFIX).red(); - println!("{}", error_message); - process::exit(1); - } - - let signature_bytes = private_key.sign(trimmed.as_ref()).to_bytes(); - let signature = bs58::encode(signature_bytes).into_string(); + validate_bech32_address_or_exit(trimmed); + let signature = private_key.sign_text(trimmed); println!( "The base58-encoded signature on '{}' is: {}", trimmed, signature - ) + ); + signature } -// we just sign whatever the user has provided -fn sign_text(private_key: &identity::PrivateKey, text: &str) { +fn print_signed_text(private_key: &identity::PrivateKey, text: &str) { println!( "Signing the text {:?} using your mixnode's Ed25519 identity key...", text ); - let signature_bytes = private_key.sign(text.as_ref()).to_bytes(); - let signature = bs58::encode(signature_bytes).into_string(); + let signature = private_key.sign_text(text); println!( "The base58-encoded signature on '{}' is: {}", @@ -171,7 +151,7 @@ pub fn execute(matches: &ArgMatches) { let identity_keypair = load_identity_keys(&pathfinder); if let Some(text) = matches.value_of(SIGN_TEXT_ARG_NAME) { - sign_text(identity_keypair.private_key(), text) + print_signed_text(identity_keypair.private_key(), text) } #[cfg(not(feature = "coconut"))] diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index 172e985388..ffc2a25c88 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -193,6 +193,11 @@ impl Config { self } + pub fn with_wallet_address(mut self, wallet_address: &str) -> Self { + self.gateway.wallet_address = wallet_address.to_string(); + self + } + // getters pub fn get_config_file_save_location(&self) -> PathBuf { self.config_directory().join(Self::config_file_name()) @@ -280,6 +285,10 @@ impl Config { pub fn get_version(&self) -> &str { &self.gateway.version } + + pub fn get_wallet_address(&self) -> &str { + &self.gateway.wallet_address + } } #[derive(Debug, Deserialize, PartialEq, Serialize)] @@ -350,6 +359,9 @@ pub struct Gateway { /// Path to sqlite database containing all persistent data: messages for offline clients, /// derived shared keys and available client bandwidths. persistent_storage: PathBuf, + + /// The Cosmos wallet address that will control this gateway + wallet_address: String, } impl Gateway { @@ -397,6 +409,7 @@ impl Default for Gateway { cosmos_mnemonic: "".to_string(), nym_root_directory: Config::default_root_directory(), persistent_storage: Default::default(), + wallet_address: "nymXXXXXXXX".to_string(), } } } diff --git a/gateway/src/config/template.rs b/gateway/src/config/template.rs index 3a7995c57c..0e610d8abc 100644 --- a/gateway/src/config/template.rs +++ b/gateway/src/config/template.rs @@ -72,6 +72,9 @@ validator_nymd_urls = [ cosmos_mnemonic = "{{ gateway.cosmos_mnemonic }}" +# Nym wallet address on the blockchain that should control this gateway +wallet_address = '{{ gateway.wallet_address }}' + ##### advanced configuration options ##### # nym_home_directory specifies absolute path to the home nym gateway directory. diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 5e3c931311..b7252c582a 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -1,6 +1,8 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::commands::sign::load_identity_keys; +use crate::commands::validate_bech32_address_or_exit; use crate::config::Config; use crate::node::client_handling::active_clients::ActiveClientsStore; use crate::node::client_handling::websocket; @@ -78,6 +80,17 @@ impl Gateway { sphinx_keypair } + /// Signs the node config's bech32 address to produce a verification code for use in the wallet. + /// Exits if the address isn't valid (which should protect against manual edits). + fn generate_verification_code(&self) -> String { + let pathfinder = GatewayPathfinder::new_from_config(&self.config); + let identity_keypair = load_identity_keys(&pathfinder); + let address = self.config.get_wallet_address(); + validate_bech32_address_or_exit(address); + let verification_code = identity_keypair.private_key().sign_text(address); + verification_code + } + pub(crate) fn print_node_details(&self) { println!( "Identity Key: {}", @@ -87,6 +100,10 @@ impl Gateway { "Sphinx Key: {}", self.sphinx_keypair.public_key().to_base58_string() ); + println!( + "Node Verification Code: {}", + self.generate_verification_code() + ); println!( "Host: {} (bind address: {})", self.config.get_announce_address(), diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 611f55bb74..dd9a77fffe 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -27,7 +27,6 @@ pretty_env_logger = "0.4.0" rand = "0.7.3" rocket = { version="0.5.0-rc.1", features = ["json"] } serde = { version="1.0", features = ["derive"] } -subtle-encoding = { version = "0.5", features = ["bech32-preview"]} tokio = { version="1.8", features = ["rt-multi-thread", "net", "signal"] } tokio-util = { version="0.6.7", features = ["codec"] } toml = "0.5.8" diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index 8775b56b80..fdedab8267 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -3,9 +3,10 @@ use std::process; -use crate::{config::Config, crypto::bech32_address_validation}; +use crate::config::Config; use clap::ArgMatches; use colored::Colorize; +use crypto::bech32_address_validation; use url::Url; pub(crate) mod describe; diff --git a/mixnode/src/commands/sign.rs b/mixnode/src/commands/sign.rs index e2b28331de..79193c5d77 100644 --- a/mixnode/src/commands/sign.rs +++ b/mixnode/src/commands/sign.rs @@ -3,7 +3,6 @@ use crate::commands::*; use crate::config::{persistence::pathfinder::MixNodePathfinder, Config}; -use crate::crypto::ed25519::sign_text; use clap::{App, Arg, ArgMatches}; use colored::Colorize; use config::NymConfig; @@ -51,7 +50,7 @@ pub fn load_identity_keys(pathfinder: &MixNodePathfinder) -> identity::KeyPair { fn print_signed_address(private_key: &identity::PrivateKey, raw_address: &str) -> String { let trimmed = raw_address.trim(); validate_bech32_address_or_exit(trimmed); - let signature = sign_text(private_key, trimmed); + let signature = private_key.sign_text(trimmed); println!( "The base58-encoded signature on '{}' is: {}", @@ -66,7 +65,7 @@ fn print_signed_text(private_key: &identity::PrivateKey, text: &str) { text ); - let signature = sign_text(private_key, text); + let signature = private_key.sign_text(text); println!( "The base58-encoded signature on '{}' is: {}", diff --git a/mixnode/src/crypto/ed25519.rs b/mixnode/src/crypto/ed25519.rs deleted file mode 100644 index ed5c966131..0000000000 --- a/mixnode/src/crypto/ed25519.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crypto::asymmetric::identity; - -/// Signs text with the provided Ed25519 private key -pub fn sign_text(private_key: &identity::PrivateKey, text: &str) -> String { - let signature_bytes = private_key.sign(text.as_ref()).to_bytes(); - let signature = bs58::encode(signature_bytes).into_string(); - signature -} diff --git a/mixnode/src/crypto/mod.rs b/mixnode/src/crypto/mod.rs deleted file mode 100644 index 1227cfb416..0000000000 --- a/mixnode/src/crypto/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod bech32_address_validation; -pub mod ed25519; diff --git a/mixnode/src/main.rs b/mixnode/src/main.rs index cfb94ddc4b..bdc9c6d013 100644 --- a/mixnode/src/main.rs +++ b/mixnode/src/main.rs @@ -8,7 +8,6 @@ use clap::{crate_version, App, ArgMatches}; mod commands; mod config; -mod crypto; mod node; fn main() { diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index 80f2a5abfd..ca8133fcd0 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -3,7 +3,6 @@ use crate::commands::validate_bech32_address_or_exit; use crate::config::Config; -use crate::crypto::ed25519::sign_text; use crate::node::http::{ description::description, not_found, @@ -91,7 +90,7 @@ impl MixNode { let identity_keypair = load_identity_keys(&pathfinder); let address = self.config.get_wallet_address(); validate_bech32_address_or_exit(address); - let verification_code = sign_text(identity_keypair.private_key(), address); + let verification_code = identity_keypair.private_key().sign_text(address); verification_code }