Skip to content

Commit

Permalink
Merge pull request #2444 from subspace/domain-key-updates
Browse files Browse the repository at this point in the history
Domain key updates
  • Loading branch information
nazar-pc authored Jan 24, 2024
2 parents ea98d08 + 526a7b1 commit cbd11c2
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 89 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/subspace-node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ include = [
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
bip39 = "2.0.0"
clap = { version = "4.4.18", features = ["derive"] }
cross-domain-message-gossip = { version = "0.1.0", path = "../../domains/client/cross-domain-message-gossip" }
dirs = "5.0.1"
Expand Down
6 changes: 4 additions & 2 deletions crates/subspace-node/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
mod insert_domain_key;
mod domain_key;
mod run;
mod shared;
mod wipe;

pub use insert_domain_key::{insert_domain_key, InsertDomainKeyOptions};
pub use domain_key::{
create_domain_key, insert_domain_key, CreateDomainKeyOptions, InsertDomainKeyOptions,
};
pub use run::{run, RunOptions};
pub use wipe::{wipe, WipeOptions};
128 changes: 128 additions & 0 deletions crates/subspace-node/src/commands/domain_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use crate::commands::shared::{
derive_keypair, init_logger, store_key_in_keystore, KeystoreOptions,
};
use bip39::Mnemonic;
use clap::Parser;
use sc_cli::{Error, KeystoreParams};
use sc_service::config::KeystoreConfig;
use sp_core::crypto::{ExposeSecret, SecretString};
use sp_core::Pair;
use sp_domains::DomainId;
use std::path::PathBuf;
use tracing::{info, warn};

/// Options for creating domain key
#[derive(Debug, Parser)]
pub struct CreateDomainKeyOptions {
/// Base path where to store node files
#[arg(long)]
base_path: PathBuf,
/// ID of the domain to store key for
#[arg(long, required = true)]
domain_id: DomainId,
/// Options for domain keystore
#[clap(flatten)]
keystore_options: KeystoreOptions,
}

pub fn create_domain_key(options: CreateDomainKeyOptions) -> Result<(), Error> {
init_logger();

let CreateDomainKeyOptions {
base_path,
domain_id,
keystore_options,
} = options;
let domain_path = base_path.join("domains").join(domain_id.to_string());

let keystore_params = KeystoreParams {
keystore_path: None,
password_interactive: keystore_options.keystore_password_interactive,
password: keystore_options.keystore_password,
password_filename: keystore_options.keystore_password_filename,
};

let keystore_config = keystore_params.keystore_config(&domain_path)?;

let (path, password) = match &keystore_config {
KeystoreConfig::Path { path, password, .. } => (path.clone(), password.clone()),
KeystoreConfig::InMemory => {
unreachable!("Just constructed non-memory keystore config; qed");
}
};

let has_password = password.is_some();

let mnemonic = Mnemonic::generate(12)
.map_err(|error| Error::Input(format!("Mnemonic generation failed: {error}")))?;
let phrase = SecretString::from(mnemonic.to_string());

let public_key = derive_keypair(&phrase, &password)?.public();

store_key_in_keystore(path, &phrase, password)?;

info!("Successfully generated and imported keypair!");
info!("Public key: {}", hex::encode(public_key.0));
info!("Seed: \"{}\"", phrase.expose_secret());
if has_password {
info!("Password: as specified in CLI options");
}
warn!("⚠ Make sure to keep ^ seed secure and never share with anyone to avoid loss of funds ⚠");

Ok(())
}

/// Options for inserting domain key
#[derive(Debug, Parser)]
pub struct InsertDomainKeyOptions {
/// Base path where to store node files
#[arg(long)]
base_path: PathBuf,
/// ID of the domain to store key for
#[arg(long, required = true)]
domain_id: DomainId,
/// Operator secret key URI to insert into keystore.
///
/// Example: "//Alice".
///
/// If the value is a file, the file content is used as URI.
#[arg(long, required = true)]
keystore_suri: SecretString,
/// Options for domain keystore
#[clap(flatten)]
keystore_options: KeystoreOptions,
}

pub fn insert_domain_key(options: InsertDomainKeyOptions) -> Result<(), Error> {
init_logger();

let InsertDomainKeyOptions {
base_path,
domain_id,
keystore_suri,
keystore_options,
} = options;
let domain_path = base_path.join("domains").join(domain_id.to_string());

let keystore_params = KeystoreParams {
keystore_path: None,
password_interactive: keystore_options.keystore_password_interactive,
password: keystore_options.keystore_password,
password_filename: keystore_options.keystore_password_filename,
};

let keystore_config = keystore_params.keystore_config(&domain_path)?;

let (path, password) = match &keystore_config {
KeystoreConfig::Path { path, password, .. } => (path.clone(), password.clone()),
KeystoreConfig::InMemory => {
unreachable!("Just constructed non-memory keystore config; qed");
}
};

store_key_in_keystore(path, &keystore_suri, password)?;

info!("Success");

Ok(())
}
58 changes: 0 additions & 58 deletions crates/subspace-node/src/commands/insert_domain_key.rs

This file was deleted.

23 changes: 15 additions & 8 deletions crates/subspace-node/src/commands/run/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,17 @@ pub(super) struct DomainOptions {
#[clap(flatten)]
network_options: SubstrateNetworkOptions,

/// Operator secret key URI to insert into keystore.
///
/// Example: "//Alice".
///
/// If the value is a file, the file content is used as URI.
#[arg(long)]
keystore_suri: Option<SecretString>,

/// Options for domain keystore
#[clap(flatten)]
keystore_options: KeystoreOptions<false>,
keystore_options: KeystoreOptions,

/// Options for transaction pool
#[clap(flatten)]
Expand Down Expand Up @@ -138,7 +146,8 @@ pub(super) fn create_domain_configuration(
prometheus_listen_on,
pruning_params,
network_options,
mut keystore_options,
mut keystore_suri,
keystore_options,
pool_config,
additional_args,
} = domain_options;
Expand All @@ -152,10 +161,8 @@ pub(super) fn create_domain_configuration(
if operator_id.is_none() {
operator_id.replace(OperatorId::default());
}
if keystore_options.keystore_suri.is_none() {
keystore_options
.keystore_suri
.replace(SecretString::new("//Alice".to_string()));
if keystore_suri.is_none() {
keystore_suri.replace(SecretString::new("//Alice".to_string()));
}
}

Expand Down Expand Up @@ -285,15 +292,15 @@ pub(super) fn create_domain_configuration(

let keystore_config = keystore_params.keystore_config(&base_path)?;

if let Some(keystore_suri) = keystore_options.keystore_suri {
if let Some(keystore_suri) = keystore_suri {
let (path, password) = match &keystore_config {
KeystoreConfig::Path { path, password, .. } => (path.clone(), password.clone()),
KeystoreConfig::InMemory => {
unreachable!("Just constructed non-memory keystore config; qed");
}
};

store_key_in_keystore(path, password, &keystore_suri)?;
store_key_in_keystore(path, &keystore_suri, password)?;
}

keystore_config
Expand Down
32 changes: 16 additions & 16 deletions crates/subspace-node/src/commands/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use clap::Parser;
use sc_cli::Error;
use sc_keystore::LocalKeystore;
use sp_core::crypto::{ExposeSecret, SecretString};
use sp_core::Pair;
use sp_core::sr25519::Pair;
use sp_core::Pair as PairT;
use sp_domains::KEY_TYPE;
use sp_keystore::Keystore;
use std::path::PathBuf;
Expand All @@ -12,14 +13,7 @@ use tracing_subscriber::{fmt, EnvFilter};

/// Options used for keystore
#[derive(Debug, Parser)]
pub(super) struct KeystoreOptions<const SURI_REQUIRED: bool> {
/// Operator secret key URI to insert into keystore.
///
/// Example: "//Alice".
///
/// If the value is a file, the file content is used as URI.
#[arg(long, required = SURI_REQUIRED)]
pub(super) keystore_suri: Option<SecretString>,
pub(super) struct KeystoreOptions {
/// Use interactive shell for entering the password used by the keystore.
#[arg(long, conflicts_with_all = &["keystore_password", "keystore_password_filename"])]
pub(super) keystore_password_interactive: bool,
Expand All @@ -32,20 +26,26 @@ pub(super) struct KeystoreOptions<const SURI_REQUIRED: bool> {
pub(super) keystore_password_filename: Option<PathBuf>,
}

pub(super) fn store_key_in_keystore(
keystore_path: PathBuf,
password: Option<SecretString>,
pub(super) fn derive_keypair(
suri: &SecretString,
) -> Result<(), Error> {
let keypair_result = sp_core::sr25519::Pair::from_string(
password: &Option<SecretString>,
) -> Result<Pair, Error> {
let keypair_result = Pair::from_string(
suri.expose_secret(),
password
.as_ref()
.map(|password| password.expose_secret().as_str()),
);

let keypair =
keypair_result.map_err(|err| Error::Input(format!("Invalid password {:?}", err)))?;
keypair_result.map_err(|err| Error::Input(format!("Invalid password {:?}", err)))
}

pub(super) fn store_key_in_keystore(
keystore_path: PathBuf,
suri: &SecretString,
password: Option<SecretString>,
) -> Result<(), Error> {
let keypair = derive_keypair(suri, &password)?;

LocalKeystore::open(keystore_path, password)?
.insert(KEY_TYPE, suri.expose_secret(), &keypair.public())
Expand Down
17 changes: 13 additions & 4 deletions crates/subspace-node/src/domain/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use crate::commands::InsertDomainKeyOptions;
use crate::commands::{CreateDomainKeyOptions, InsertDomainKeyOptions};
use crate::domain::evm_chain_spec;
use clap::Parser;
use domain_runtime_primitives::opaque::Block as DomainBlock;
Expand All @@ -39,12 +39,13 @@ use std::net::SocketAddr;
use std::path::Path;
use subspace_runtime::Block;

/// Sub-commands supported by the executor.
/// Sub-commands supported by the operator.
#[derive(Debug, clap::Subcommand)]
#[allow(clippy::large_enum_variant)]
pub enum Subcommand {
/// Insert key into domain's keystore
InsertKey(InsertDomainKeyOptions),
/// Domain key management
#[clap(subcommand)]
Key(DomainKey),

/// Export the state of a given block into a chain spec.
ExportState(sc_cli::ExportStateCmd),
Expand All @@ -63,6 +64,14 @@ pub enum Subcommand {
ExportExecutionReceipt(ExportExecutionReceiptCmd),
}

#[derive(Debug, clap::Subcommand)]
pub enum DomainKey {
/// Create key and import into domain's keystore
Create(CreateDomainKeyOptions),
/// Insert key into domain's keystore
Insert(InsertDomainKeyOptions),
}

#[derive(Debug, Parser)]
pub struct DomainCli {
/// Run a domain node.
Expand Down
6 changes: 5 additions & 1 deletion crates/subspace-node/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ mod cli;
mod domain;

use crate::cli::{Cli, SubspaceCliPlaceholder};
use crate::domain::cli::DomainKey;
use crate::domain::{DomainCli, DomainSubcommand};
use clap::Parser;
use domain_runtime_primitives::opaque::Block as DomainBlock;
Expand Down Expand Up @@ -311,7 +312,10 @@ fn main() -> Result<(), Error> {
})?;
}
Cli::Domain(domain_cmd) => match domain_cmd {
DomainSubcommand::InsertKey(insert_domain_key_options) => {
DomainSubcommand::Key(DomainKey::Create(create_domain_key_options)) => {
commands::create_domain_key(create_domain_key_options)?;
}
DomainSubcommand::Key(DomainKey::Insert(insert_domain_key_options)) => {
commands::insert_domain_key(insert_domain_key_options)?;
}
DomainSubcommand::Benchmark(cmd) => {
Expand Down

0 comments on commit cbd11c2

Please sign in to comment.