Feature/add wallet to gateway init (#984)

* Moving sign_text method into common/crypto to dry it up

* Moved bech32 address validation into common/crypto

* ibid

* Gateway now requires a --wallet-address arg on init
This commit is contained in:
Dave Hrycyszyn
2021-12-18 12:45:55 +00:00
committed by GitHub
parent ed2b515a83
commit 8e99ae8979
19 changed files with 101 additions and 48 deletions
Generated
+2 -1
View File
@@ -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",
+2
View File
@@ -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" }
@@ -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 {
+1
View File
@@ -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;
+7
View File
@@ -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"))]
+32
View File
@@ -1,8 +1,12 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// 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<Url> {
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);
}
}
+3
View File
@@ -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;
+9 -29
View File
@@ -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"))]
+13
View File
@@ -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(),
}
}
}
+3
View File
@@ -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.
+17
View File
@@ -1,6 +1,8 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// 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(),
-1
View File
@@ -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"
+2 -1
View File
@@ -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;
+2 -3
View File
@@ -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: {}",
-8
View File
@@ -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
}
-2
View File
@@ -1,2 +0,0 @@
pub mod bech32_address_validation;
pub mod ed25519;
-1
View File
@@ -8,7 +8,6 @@ use clap::{crate_version, App, ArgMatches};
mod commands;
mod config;
mod crypto;
mod node;
fn main() {
+1 -2
View File
@@ -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
}