Skip to content

Commit cbd11c2

Browse files
authored
Merge pull request #2444 from subspace/domain-key-updates
Domain key updates
2 parents ea98d08 + 526a7b1 commit cbd11c2

File tree

9 files changed

+183
-89
lines changed

9 files changed

+183
-89
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/subspace-node/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ include = [
2020
targets = ["x86_64-unknown-linux-gnu"]
2121

2222
[dependencies]
23+
bip39 = "2.0.0"
2324
clap = { version = "4.4.18", features = ["derive"] }
2425
cross-domain-message-gossip = { version = "0.1.0", path = "../../domains/client/cross-domain-message-gossip" }
2526
dirs = "5.0.1"
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
mod insert_domain_key;
1+
mod domain_key;
22
mod run;
33
mod shared;
44
mod wipe;
55

6-
pub use insert_domain_key::{insert_domain_key, InsertDomainKeyOptions};
6+
pub use domain_key::{
7+
create_domain_key, insert_domain_key, CreateDomainKeyOptions, InsertDomainKeyOptions,
8+
};
79
pub use run::{run, RunOptions};
810
pub use wipe::{wipe, WipeOptions};
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use crate::commands::shared::{
2+
derive_keypair, init_logger, store_key_in_keystore, KeystoreOptions,
3+
};
4+
use bip39::Mnemonic;
5+
use clap::Parser;
6+
use sc_cli::{Error, KeystoreParams};
7+
use sc_service::config::KeystoreConfig;
8+
use sp_core::crypto::{ExposeSecret, SecretString};
9+
use sp_core::Pair;
10+
use sp_domains::DomainId;
11+
use std::path::PathBuf;
12+
use tracing::{info, warn};
13+
14+
/// Options for creating domain key
15+
#[derive(Debug, Parser)]
16+
pub struct CreateDomainKeyOptions {
17+
/// Base path where to store node files
18+
#[arg(long)]
19+
base_path: PathBuf,
20+
/// ID of the domain to store key for
21+
#[arg(long, required = true)]
22+
domain_id: DomainId,
23+
/// Options for domain keystore
24+
#[clap(flatten)]
25+
keystore_options: KeystoreOptions,
26+
}
27+
28+
pub fn create_domain_key(options: CreateDomainKeyOptions) -> Result<(), Error> {
29+
init_logger();
30+
31+
let CreateDomainKeyOptions {
32+
base_path,
33+
domain_id,
34+
keystore_options,
35+
} = options;
36+
let domain_path = base_path.join("domains").join(domain_id.to_string());
37+
38+
let keystore_params = KeystoreParams {
39+
keystore_path: None,
40+
password_interactive: keystore_options.keystore_password_interactive,
41+
password: keystore_options.keystore_password,
42+
password_filename: keystore_options.keystore_password_filename,
43+
};
44+
45+
let keystore_config = keystore_params.keystore_config(&domain_path)?;
46+
47+
let (path, password) = match &keystore_config {
48+
KeystoreConfig::Path { path, password, .. } => (path.clone(), password.clone()),
49+
KeystoreConfig::InMemory => {
50+
unreachable!("Just constructed non-memory keystore config; qed");
51+
}
52+
};
53+
54+
let has_password = password.is_some();
55+
56+
let mnemonic = Mnemonic::generate(12)
57+
.map_err(|error| Error::Input(format!("Mnemonic generation failed: {error}")))?;
58+
let phrase = SecretString::from(mnemonic.to_string());
59+
60+
let public_key = derive_keypair(&phrase, &password)?.public();
61+
62+
store_key_in_keystore(path, &phrase, password)?;
63+
64+
info!("Successfully generated and imported keypair!");
65+
info!("Public key: {}", hex::encode(public_key.0));
66+
info!("Seed: \"{}\"", phrase.expose_secret());
67+
if has_password {
68+
info!("Password: as specified in CLI options");
69+
}
70+
warn!("⚠ Make sure to keep ^ seed secure and never share with anyone to avoid loss of funds ⚠");
71+
72+
Ok(())
73+
}
74+
75+
/// Options for inserting domain key
76+
#[derive(Debug, Parser)]
77+
pub struct InsertDomainKeyOptions {
78+
/// Base path where to store node files
79+
#[arg(long)]
80+
base_path: PathBuf,
81+
/// ID of the domain to store key for
82+
#[arg(long, required = true)]
83+
domain_id: DomainId,
84+
/// Operator secret key URI to insert into keystore.
85+
///
86+
/// Example: "//Alice".
87+
///
88+
/// If the value is a file, the file content is used as URI.
89+
#[arg(long, required = true)]
90+
keystore_suri: SecretString,
91+
/// Options for domain keystore
92+
#[clap(flatten)]
93+
keystore_options: KeystoreOptions,
94+
}
95+
96+
pub fn insert_domain_key(options: InsertDomainKeyOptions) -> Result<(), Error> {
97+
init_logger();
98+
99+
let InsertDomainKeyOptions {
100+
base_path,
101+
domain_id,
102+
keystore_suri,
103+
keystore_options,
104+
} = options;
105+
let domain_path = base_path.join("domains").join(domain_id.to_string());
106+
107+
let keystore_params = KeystoreParams {
108+
keystore_path: None,
109+
password_interactive: keystore_options.keystore_password_interactive,
110+
password: keystore_options.keystore_password,
111+
password_filename: keystore_options.keystore_password_filename,
112+
};
113+
114+
let keystore_config = keystore_params.keystore_config(&domain_path)?;
115+
116+
let (path, password) = match &keystore_config {
117+
KeystoreConfig::Path { path, password, .. } => (path.clone(), password.clone()),
118+
KeystoreConfig::InMemory => {
119+
unreachable!("Just constructed non-memory keystore config; qed");
120+
}
121+
};
122+
123+
store_key_in_keystore(path, &keystore_suri, password)?;
124+
125+
info!("Success");
126+
127+
Ok(())
128+
}

crates/subspace-node/src/commands/insert_domain_key.rs

Lines changed: 0 additions & 58 deletions
This file was deleted.

crates/subspace-node/src/commands/run/domain.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,17 @@ pub(super) struct DomainOptions {
105105
#[clap(flatten)]
106106
network_options: SubstrateNetworkOptions,
107107

108+
/// Operator secret key URI to insert into keystore.
109+
///
110+
/// Example: "//Alice".
111+
///
112+
/// If the value is a file, the file content is used as URI.
113+
#[arg(long)]
114+
keystore_suri: Option<SecretString>,
115+
108116
/// Options for domain keystore
109117
#[clap(flatten)]
110-
keystore_options: KeystoreOptions<false>,
118+
keystore_options: KeystoreOptions,
111119

112120
/// Options for transaction pool
113121
#[clap(flatten)]
@@ -138,7 +146,8 @@ pub(super) fn create_domain_configuration(
138146
prometheus_listen_on,
139147
pruning_params,
140148
network_options,
141-
mut keystore_options,
149+
mut keystore_suri,
150+
keystore_options,
142151
pool_config,
143152
additional_args,
144153
} = domain_options;
@@ -152,10 +161,8 @@ pub(super) fn create_domain_configuration(
152161
if operator_id.is_none() {
153162
operator_id.replace(OperatorId::default());
154163
}
155-
if keystore_options.keystore_suri.is_none() {
156-
keystore_options
157-
.keystore_suri
158-
.replace(SecretString::new("//Alice".to_string()));
164+
if keystore_suri.is_none() {
165+
keystore_suri.replace(SecretString::new("//Alice".to_string()));
159166
}
160167
}
161168

@@ -285,15 +292,15 @@ pub(super) fn create_domain_configuration(
285292

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

288-
if let Some(keystore_suri) = keystore_options.keystore_suri {
295+
if let Some(keystore_suri) = keystore_suri {
289296
let (path, password) = match &keystore_config {
290297
KeystoreConfig::Path { path, password, .. } => (path.clone(), password.clone()),
291298
KeystoreConfig::InMemory => {
292299
unreachable!("Just constructed non-memory keystore config; qed");
293300
}
294301
};
295302

296-
store_key_in_keystore(path, password, &keystore_suri)?;
303+
store_key_in_keystore(path, &keystore_suri, password)?;
297304
}
298305

299306
keystore_config

crates/subspace-node/src/commands/shared.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use clap::Parser;
22
use sc_cli::Error;
33
use sc_keystore::LocalKeystore;
44
use sp_core::crypto::{ExposeSecret, SecretString};
5-
use sp_core::Pair;
5+
use sp_core::sr25519::Pair;
6+
use sp_core::Pair as PairT;
67
use sp_domains::KEY_TYPE;
78
use sp_keystore::Keystore;
89
use std::path::PathBuf;
@@ -12,14 +13,7 @@ use tracing_subscriber::{fmt, EnvFilter};
1213

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

35-
pub(super) fn store_key_in_keystore(
36-
keystore_path: PathBuf,
37-
password: Option<SecretString>,
29+
pub(super) fn derive_keypair(
3830
suri: &SecretString,
39-
) -> Result<(), Error> {
40-
let keypair_result = sp_core::sr25519::Pair::from_string(
31+
password: &Option<SecretString>,
32+
) -> Result<Pair, Error> {
33+
let keypair_result = Pair::from_string(
4134
suri.expose_secret(),
4235
password
4336
.as_ref()
4437
.map(|password| password.expose_secret().as_str()),
4538
);
4639

47-
let keypair =
48-
keypair_result.map_err(|err| Error::Input(format!("Invalid password {:?}", err)))?;
40+
keypair_result.map_err(|err| Error::Input(format!("Invalid password {:?}", err)))
41+
}
42+
43+
pub(super) fn store_key_in_keystore(
44+
keystore_path: PathBuf,
45+
suri: &SecretString,
46+
password: Option<SecretString>,
47+
) -> Result<(), Error> {
48+
let keypair = derive_keypair(suri, &password)?;
4949

5050
LocalKeystore::open(keystore_path, password)?
5151
.insert(KEY_TYPE, suri.expose_secret(), &keypair.public())

crates/subspace-node/src/domain/cli.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// You should have received a copy of the GNU General Public License
1515
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1616

17-
use crate::commands::InsertDomainKeyOptions;
17+
use crate::commands::{CreateDomainKeyOptions, InsertDomainKeyOptions};
1818
use crate::domain::evm_chain_spec;
1919
use clap::Parser;
2020
use domain_runtime_primitives::opaque::Block as DomainBlock;
@@ -39,12 +39,13 @@ use std::net::SocketAddr;
3939
use std::path::Path;
4040
use subspace_runtime::Block;
4141

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

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

67+
#[derive(Debug, clap::Subcommand)]
68+
pub enum DomainKey {
69+
/// Create key and import into domain's keystore
70+
Create(CreateDomainKeyOptions),
71+
/// Insert key into domain's keystore
72+
Insert(InsertDomainKeyOptions),
73+
}
74+
6675
#[derive(Debug, Parser)]
6776
pub struct DomainCli {
6877
/// Run a domain node.

crates/subspace-node/src/main.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ mod cli;
2424
mod domain;
2525

2626
use crate::cli::{Cli, SubspaceCliPlaceholder};
27+
use crate::domain::cli::DomainKey;
2728
use crate::domain::{DomainCli, DomainSubcommand};
2829
use clap::Parser;
2930
use domain_runtime_primitives::opaque::Block as DomainBlock;
@@ -311,7 +312,10 @@ fn main() -> Result<(), Error> {
311312
})?;
312313
}
313314
Cli::Domain(domain_cmd) => match domain_cmd {
314-
DomainSubcommand::InsertKey(insert_domain_key_options) => {
315+
DomainSubcommand::Key(DomainKey::Create(create_domain_key_options)) => {
316+
commands::create_domain_key(create_domain_key_options)?;
317+
}
318+
DomainSubcommand::Key(DomainKey::Insert(insert_domain_key_options)) => {
315319
commands::insert_domain_key(insert_domain_key_options)?;
316320
}
317321
DomainSubcommand::Benchmark(cmd) => {

0 commit comments

Comments
 (0)