Skip to content
Snippets Groups Projects
Verified Commit 62779172 authored by Pascal Engélibert's avatar Pascal Engélibert :bicyclist:
Browse files

feat: flexible key handling, seed support

parent a89d7eae
No related branches found
No related tags found
No related merge requests found
......@@ -38,3 +38,11 @@ When your node is ready to forge blocks, rotate keys and go online:
gcli --secret "my secret phrase" update-keys
gcli --secret "my secret phrase" go-online
```
### Keys
Secret and/or public keys can always be passed using `--secret` and `--address`. If needed, stdin will be prompted for secret key. An error will occur if secret and address are both given but do not match.
Secret key format can be changed using `--secret-format` with the following values:
* `substrate`: a Substrate secret address (optionally followed by a derivation path), or BIP39 mnemonic
* `seed`: a 32-bytes seed in hexadecimal (Duniter v1 compatible)
use anyhow::{anyhow, Result};
use clap::builder::OsStr;
use sp_core::crypto::{AccountId32, Ss58Codec};
use sp_core::{crypto::Pair as _, sr25519::Pair};
use std::str::FromStr;
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NeededKeys {
None,
Public,
Secret,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SecretFormat {
/// Raw 32B seed
Seed,
/// Substrate secret key or BIP39 mnemonic (optionally followed by derivation path)
Substrate,
}
impl FromStr for SecretFormat {
type Err = std::io::Error;
fn from_str(s: &str) -> std::io::Result<Self> {
match s {
"seed" => Ok(SecretFormat::Seed),
"substrate" => Ok(SecretFormat::Substrate),
_ => Err(std::io::Error::from(std::io::ErrorKind::InvalidInput)),
}
}
}
impl From<SecretFormat> for &'static str {
fn from(val: SecretFormat) -> &'static str {
match val {
SecretFormat::Seed => "seed",
SecretFormat::Substrate => "substrate",
}
}
}
impl From<SecretFormat> for OsStr {
fn from(val: SecretFormat) -> OsStr {
OsStr::from(Into::<&str>::into(val))
}
}
pub fn pair_from_str(secret_format: SecretFormat, secret: &str) -> Result<Pair> {
match secret_format {
SecretFormat::Seed => {
let mut seed = [0; 32];
hex::decode_to_slice(secret, &mut seed).map_err(|_| anyhow!("Invalid secret"))?;
let pair = Pair::from_seed(&seed);
Ok(pair)
}
SecretFormat::Substrate => {
Pair::from_string(secret, None).map_err(|_| anyhow!("Invalid secret"))
}
}
}
pub fn prompt_secret(secret_format: SecretFormat) -> Pair {
let mut line = String::new();
loop {
println!("Secret key ({secret_format:?}): ");
std::io::stdin().read_line(&mut line).unwrap();
match pair_from_str(secret_format, line.trim()) {
Ok(pair) => return pair,
Err(_) => println!("Invalid secret"),
}
line.clear();
}
}
pub fn get_keys(
secret_format: SecretFormat,
address: &Option<String>,
secret: &Option<String>,
needed_keys: NeededKeys,
) -> Result<(Option<AccountId32>, Option<Pair>)> {
// Get from args
let mut account_id = match (address, secret) {
(Some(address), Some(secret)) => {
let pair = pair_from_str(secret_format, secret)?;
let address = AccountId32::from_string(address)
.map_err(|_| anyhow!("Invalid address {}", address))?;
assert_eq!(
address,
pair.public().into(),
"Secret and address do not match."
);
return Ok((Some(pair.public().into()), Some(pair)));
}
(None, Some(secret)) => {
let pair = pair_from_str(secret_format, secret)?;
return Ok((Some(pair.public().into()), Some(pair)));
}
(Some(address), None) => Some(
AccountId32::from_str(address).map_err(|_| anyhow!("Invalid address {}", address))?,
),
(None, None) => None,
};
// Prompt
if needed_keys == NeededKeys::Secret
|| (account_id.is_none() && needed_keys == NeededKeys::Public)
{
loop {
let pair = prompt_secret(secret_format);
if let Some(account_id) = &account_id {
if account_id != &pair.public().into() {
println!("Secret and address do not match.");
}
} else {
account_id = Some(pair.public().into());
return Ok((account_id, Some(pair)));
}
}
}
Ok((account_id, None))
}
mod cache;
mod commands;
mod indexer;
mod keys;
use anyhow::{anyhow, Result};
use keys::*;
use anyhow::Result;
use clap::Parser;
use codec::Encode;
use sp_core::{
crypto::{Pair as _, Ss58Codec},
sr25519::Pair,
H256,
};
use std::str::FromStr;
use sp_core::H256;
#[subxt::subxt(runtime_metadata_path = "res/metadata.scale")]
pub mod gdev {}
......@@ -71,6 +69,9 @@ pub struct Args {
/// (eventually followed by derivation path)
#[clap(short, long)]
secret: Option<String>,
/// Secret key format (seed, substrate)
#[clap(short = 'S', long, default_value = SecretFormat::Substrate)]
secret_format: SecretFormat,
/// Address
#[clap(short, long)]
address: Option<String>,
......@@ -181,54 +182,31 @@ async fn main() -> Result<()> {
let args = Args::parse();
let (account_id, pair) = match (&args.address, &args.secret) {
(Some(address), Some(secret)) => {
let pair = Pair::from_string(secret, None)
.map_err(|_| anyhow!("Invalid secret {}", secret))?;
let address = sp_core::crypto::AccountId32::from_string(address)
.map_err(|_| anyhow!("Invalid address {}", address))?;
assert_eq!(
address,
pair.public().into(),
"Secret and address do not match."
);
(Some(pair.public().into()), Some(pair))
}
(None, Some(secret)) => {
let pair = Pair::from_string(secret, None)
.map_err(|_| anyhow!("Invalid secret {}", secret))?;
(Some(pair.public().into()), Some(pair))
}
(Some(address), None) => (
Some(
sp_core::crypto::AccountId32::from_str(address)
.map_err(|_| anyhow!("Invalid address {}", address))?,
),
None,
),
(None, None) => (None, None),
};
if let Some(account_id) = &account_id {
/*if let Some(account_id) = &account_id {
println!("Account address: {account_id}");
}
let client = Client::from_url(&args.url).await.unwrap();
}*/
if let Some(account_id) = &account_id {
/*if let Some(account_id) = &account_id {
let account = client
.storage()
.fetch(&gdev::storage().system().account(account_id), None)
.await?
.expect("Cannot fetch account");
logs::info!("Account free balance: {}", account.data.free);
}
}*/
match args.subcommand {
Subcommand::CreateOneshot { balance, dest } => {
commands::oneshot::create_oneshot_account(
pair.expect("This subcommand needs a secret."),
client,
get_keys(
args.secret_format,
&args.address,
&args.secret,
NeededKeys::Secret,
)?
.1
.unwrap(),
Client::from_url(&args.url).await.unwrap(),
balance,
dest,
)
......@@ -236,8 +214,15 @@ async fn main() -> Result<()> {
}
Subcommand::ConsumeOneshot { dest, dest_oneshot } => {
commands::oneshot::consume_oneshot_account(
pair.expect("This subcommand needs a secret."),
client,
get_keys(
args.secret_format,
&args.address,
&args.secret,
NeededKeys::Secret,
)?
.1
.unwrap(),
Client::from_url(&args.url).await.unwrap(),
dest,
dest_oneshot,
)
......@@ -251,8 +236,15 @@ async fn main() -> Result<()> {
remaining_to_oneshot,
} => {
commands::oneshot::consume_oneshot_account_with_remaining(
pair.expect("This subcommand needs a secret."),
client,
get_keys(
args.secret_format,
&args.address,
&args.secret,
NeededKeys::Secret,
)?
.1
.unwrap(),
Client::from_url(&args.url).await.unwrap(),
balance,
dest,
dest_oneshot,
......@@ -262,7 +254,13 @@ async fn main() -> Result<()> {
.await?
}
Subcommand::Expire { blocks, sessions } => {
commands::expire::monitor_expirations(client, blocks, sessions, &args).await?
commands::expire::monitor_expirations(
Client::from_url(&args.url).await.unwrap(),
blocks,
sessions,
&args,
)
.await?
}
Subcommand::Identity {
ref account_id,
......@@ -270,7 +268,7 @@ async fn main() -> Result<()> {
ref username,
} => {
commands::identity::get_identity(
client,
Client::from_url(&args.url).await.unwrap(),
account_id.clone(),
identity_id,
username.clone(),
......@@ -280,30 +278,70 @@ async fn main() -> Result<()> {
}
Subcommand::GenRevocDoc => {
commands::revocation::gen_revoc_doc(
&client,
&pair.expect("This subcommand needs a secret."),
&Client::from_url(&args.url).await.unwrap(),
&get_keys(
args.secret_format,
&args.address,
&args.secret,
NeededKeys::Secret,
)?
.1
.unwrap(),
)
.await?
}
Subcommand::GoOffline => {
commands::smith::go_offline(pair.expect("This subcommand needs a secret."), client)
.await?
commands::smith::go_offline(
get_keys(
args.secret_format,
&args.address,
&args.secret,
NeededKeys::Secret,
)?
.1
.unwrap(),
Client::from_url(&args.url).await.unwrap(),
)
.await?
}
Subcommand::GoOnline => {
commands::smith::go_online(pair.expect("This subcommand needs a secret."), client)
.await?
commands::smith::go_online(
get_keys(
args.secret_format,
&args.address,
&args.secret,
NeededKeys::Secret,
)?
.1
.unwrap(),
Client::from_url(&args.url).await.unwrap(),
)
.await?
}
Subcommand::OneshotBalance { account } => {
commands::oneshot::oneshot_account_balance(client, account).await?
commands::oneshot::oneshot_account_balance(
Client::from_url(&args.url).await.unwrap(),
account,
)
.await?
}
Subcommand::Online => {
commands::smith::online(Client::from_url(&args.url).await.unwrap(), &args).await?
}
Subcommand::Online => commands::smith::online(client, &args).await?,
Subcommand::Repart {
target,
actual_repart,
} => {
commands::net_test::repart(
pair.expect("This subcommand needs a secret."),
client,
get_keys(
args.secret_format,
&args.address,
&args.secret,
NeededKeys::Secret,
)?
.1
.unwrap(),
Client::from_url(&args.url).await.unwrap(),
target,
actual_repart,
)
......@@ -311,25 +349,46 @@ async fn main() -> Result<()> {
}
Subcommand::SpamRoll { actual_repart } => {
commands::net_test::spam_roll(
pair.expect("This subcommand needs a secret."),
client,
get_keys(
args.secret_format,
&args.address,
&args.secret,
NeededKeys::Secret,
)?
.1
.unwrap(),
Client::from_url(&args.url).await.unwrap(),
actual_repart,
)
.await?
}
Subcommand::SudoSetKey { new_key } => {
commands::sudo::set_key(
pair.expect("This subcommand needs a secret."),
client,
get_keys(
args.secret_format,
&args.address,
&args.secret,
NeededKeys::Secret,
)?
.1
.unwrap(),
Client::from_url(&args.url).await.unwrap(),
new_key,
)
.await?
}
Subcommand::TechMembers => {
commands::collective::technical_committee_members(client, &args).await?
commands::collective::technical_committee_members(
Client::from_url(&args.url).await.unwrap(),
&args,
)
.await?
}
Subcommand::TechProposals => {
commands::collective::technical_committee_proposals(client).await?
commands::collective::technical_committee_proposals(
Client::from_url(&args.url).await.unwrap(),
)
.await?
}
Subcommand::TechVote { hash, index, vote } => {
let vote = match vote {
......@@ -338,8 +397,15 @@ async fn main() -> Result<()> {
_ => panic!("Vote must be written 0 if you disagree, or 1 if you agree."),
};
commands::collective::technical_committee_vote(
pair.expect("This subcommand needs a secret."),
client,
get_keys(
args.secret_format,
&args.address,
&args.secret,
NeededKeys::Secret,
)?
.1
.unwrap(),
Client::from_url(&args.url).await.unwrap(),
hash, //H256::from_str(&hash).expect("Invalid hash formatting"),
index,
vote,
......@@ -352,8 +418,15 @@ async fn main() -> Result<()> {
keep_alive,
} => {
commands::transfer::transfer(
pair.expect("This subcommand needs a secret."),
client,
get_keys(
args.secret_format,
&args.address,
&args.secret,
NeededKeys::Secret,
)?
.1
.unwrap(),
Client::from_url(&args.url).await.unwrap(),
amount,
dest,
keep_alive,
......@@ -362,16 +435,30 @@ async fn main() -> Result<()> {
}
Subcommand::TransferMultiple { amount, dests } => {
commands::transfer::transfer_multiple(
pair.expect("This subcommand needs a secret."),
client,
get_keys(
args.secret_format,
&args.address,
&args.secret,
NeededKeys::Secret,
)?
.1
.unwrap(),
Client::from_url(&args.url).await.unwrap(),
amount,
dests,
)
.await?
}
Subcommand::UpdateKeys => commands::smith::update_session_keys(
pair.expect("This subcommand needs a secret."),
client,
get_keys(
args.secret_format,
&args.address,
&args.secret,
NeededKeys::Secret,
)?
.1
.unwrap(),
Client::from_url(&args.url).await.unwrap(),
)
.await
.unwrap(),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment