use clap::Args;
use pezsc_network::config::{ed25519, NodeKeyConfig};
use pezsc_service::Role;
use pezsp_core::H256;
use std::{path::PathBuf, str::FromStr};
use crate::{arg_enums::NodeKeyType, error, Error};
pub(crate) const NODE_KEY_ED25519_FILE: &str = "secret_ed25519";
#[derive(Debug, Clone, Args)]
pub struct NodeKeyParams {
#[arg(long, value_name = "KEY")]
pub node_key: Option<String>,
#[arg(long, value_name = "TYPE", value_enum, ignore_case = true, default_value_t = NodeKeyType::Ed25519)]
pub node_key_type: NodeKeyType,
#[arg(long, value_name = "FILE")]
pub node_key_file: Option<PathBuf>,
#[arg(long)]
pub unsafe_force_node_key_generation: bool,
}
impl NodeKeyParams {
pub fn node_key(
&self,
net_config_dir: &PathBuf,
role: Role,
is_dev: bool,
) -> error::Result<NodeKeyConfig> {
Ok(match self.node_key_type {
NodeKeyType::Ed25519 => {
let secret = if let Some(node_key) = self.node_key.as_ref() {
parse_ed25519_secret(node_key)?
} else {
let key_path = self
.node_key_file
.clone()
.unwrap_or_else(|| net_config_dir.join(NODE_KEY_ED25519_FILE));
if !self.unsafe_force_node_key_generation
&& role.is_authority()
&& !is_dev && !key_path.exists()
{
return Err(Error::NetworkKeyNotFound(key_path));
}
pezsc_network::config::Secret::File(key_path)
};
NodeKeyConfig::Ed25519(secret)
},
})
}
}
fn invalid_node_key(e: impl std::fmt::Display) -> error::Error {
error::Error::Input(format!("Invalid node key: {}", e))
}
fn parse_ed25519_secret(hex: &str) -> error::Result<pezsc_network::config::Ed25519Secret> {
H256::from_str(hex).map_err(invalid_node_key).and_then(|bytes| {
ed25519::SecretKey::try_from_bytes(bytes)
.map(pezsc_network::config::Secret::Input)
.map_err(invalid_node_key)
})
}
#[cfg(test)]
mod tests {
use super::*;
use clap::ValueEnum;
use pezsc_network::config::ed25519;
use std::fs::{self, File};
use tempfile::TempDir;
#[test]
fn test_node_key_config_input() {
fn secret_input(net_config_dir: &PathBuf) -> error::Result<()> {
NodeKeyType::value_variants().iter().try_for_each(|t| {
let node_key_type = *t;
let sk = match node_key_type {
NodeKeyType::Ed25519 => ed25519::SecretKey::generate().as_ref().to_vec(),
};
let params = NodeKeyParams {
node_key_type,
node_key: Some(format!("{:x}", H256::from_slice(sk.as_ref()))),
node_key_file: None,
unsafe_force_node_key_generation: false,
};
params.node_key(net_config_dir, Role::Authority, false).and_then(|c| match c {
NodeKeyConfig::Ed25519(pezsc_network::config::Secret::Input(ref ski))
if node_key_type == NodeKeyType::Ed25519 && &sk[..] == ski.as_ref() =>
{
Ok(())
},
_ => Err(error::Error::Input("Unexpected node key config".into())),
})
})
}
assert!(secret_input(&PathBuf::from_str("x").unwrap()).is_ok());
}
#[test]
fn test_node_key_config_file() {
fn check_key(file: PathBuf, key: &ed25519::SecretKey) {
let params = NodeKeyParams {
node_key_type: NodeKeyType::Ed25519,
node_key: None,
node_key_file: Some(file),
unsafe_force_node_key_generation: false,
};
let node_key = params
.node_key(&PathBuf::from("not-used"), Role::Authority, false)
.expect("Creates node key config")
.into_keypair()
.expect("Creates node key pair");
if node_key.secret().as_ref() != key.as_ref() {
panic!("Invalid key")
}
}
let tmp = tempfile::Builder::new().prefix("alice").tempdir().expect("Creates tempfile");
let file = tmp.path().join("mysecret").to_path_buf();
let key = ed25519::SecretKey::generate();
fs::write(&file, array_bytes::bytes2hex("", key.as_ref())).expect("Writes secret key");
check_key(file.clone(), &key);
fs::write(&file, &key).expect("Writes secret key");
check_key(file.clone(), &key);
}
#[test]
fn test_node_key_config_default() {
fn with_def_params<F>(f: F, unsafe_force_node_key_generation: bool) -> error::Result<()>
where
F: Fn(NodeKeyParams) -> error::Result<()>,
{
NodeKeyType::value_variants().iter().try_for_each(|t| {
let node_key_type = *t;
f(NodeKeyParams {
node_key_type,
node_key: None,
node_key_file: None,
unsafe_force_node_key_generation,
})
})
}
fn some_config_dir(
net_config_dir: &PathBuf,
unsafe_force_node_key_generation: bool,
role: Role,
is_dev: bool,
) -> error::Result<()> {
with_def_params(
|params| {
let dir = PathBuf::from(net_config_dir.clone());
let typ = params.node_key_type;
params.node_key(net_config_dir, role, is_dev).and_then(move |c| match c {
NodeKeyConfig::Ed25519(pezsc_network::config::Secret::File(ref f))
if typ == NodeKeyType::Ed25519
&& f == &dir.join(NODE_KEY_ED25519_FILE) =>
{
Ok(())
},
_ => Err(error::Error::Input("Unexpected node key config".into())),
})
},
unsafe_force_node_key_generation,
)
}
assert!(some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Full, false).is_ok());
assert!(
some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, true).is_ok()
);
assert!(
some_config_dir(&PathBuf::from_str("x").unwrap(), true, Role::Authority, false).is_ok()
);
assert!(matches!(
some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, false),
Err(Error::NetworkKeyNotFound(_))
));
let tempdir = TempDir::new().unwrap();
let _file = File::create(tempdir.path().join(NODE_KEY_ED25519_FILE)).unwrap();
assert!(some_config_dir(&tempdir.path().into(), false, Role::Authority, false).is_ok());
}
}