use anyhow::{anyhow, Context};
use clap::ValueEnum;
use libp2p::{identity, identity::secp256k1};
use rand::{Rng, SeedableRng};
use sec1::{der::Decode, pkcs8::DecodePrivateKey};
use serde::{Deserialize, Serialize};
use serde_with::{base64::Base64, serde_as};
use std::{
fmt::Display,
io::Read,
path::{Path, PathBuf},
};
use tracing::info;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum PubkeyConfig {
#[serde(rename = "random")]
Random,
#[serde(rename = "random_seed")]
GenerateFromSeed(RNGSeed),
#[serde(rename = "existing")]
Existing(ExistingKeyPath),
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, ValueEnum)]
pub enum KeyType {
#[default]
#[serde(rename = "ed25519")]
Ed25519,
#[serde(rename = "secp256k1")]
Secp256k1,
}
impl Display for KeyType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
KeyType::Ed25519 => f.write_str("Ed25519"),
KeyType::Secp256k1 => f.write_str("Secp256k1"),
}
}
}
#[serde_as]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct RNGSeed {
#[serde(default)]
key_type: KeyType,
#[serde_as(as = "Base64")]
seed: [u8; 32],
}
impl RNGSeed {
pub fn new(key_type: KeyType, seed: [u8; 32]) -> Self {
Self { key_type, seed }
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ExistingKeyPath {
#[serde(default)]
key_type: KeyType,
path: PathBuf,
}
impl ExistingKeyPath {
pub fn new(key_type: KeyType, path: PathBuf) -> Self {
Self { key_type, path }
}
}
impl PubkeyConfig {
pub(crate) fn keypair(&self) -> anyhow::Result<identity::Keypair> {
match self {
PubkeyConfig::Random => {
info!(
subject = "pubkey_config.random",
category = "pubkey_config",
"generating random ed25519 key"
);
Ok(identity::Keypair::generate_ed25519())
}
PubkeyConfig::GenerateFromSeed(RNGSeed { key_type, seed }) => {
let mut r = rand::prelude::StdRng::from_seed(*seed);
let mut new_key: [u8; 32] = r.gen();
match key_type {
KeyType::Ed25519 => {
info!(
subject = "pubkey_config.random_seed.ed25519",
category = "pubkey_config",
"generating random ed25519 key from seed"
);
identity::Keypair::ed25519_from_bytes(new_key).map_err(|e| {
anyhow!("failed to generate ed25519 key from random: {:?}", e)
})
}
KeyType::Secp256k1 => {
info!(
subject = "pubkey_config.random_seed.secp256k1",
category = "pubkey_config",
"generating random secp256k1 key from seed"
);
let sk =
secp256k1::SecretKey::try_from_bytes(&mut new_key).map_err(|e| {
anyhow!("failed to generate secp256k1 key from random: {:?}", e)
})?;
let kp = secp256k1::Keypair::from(sk);
Ok(identity::Keypair::from(kp))
}
}
}
PubkeyConfig::Existing(ExistingKeyPath { key_type, path }) => {
let path = Path::new(&path);
let mut file = std::fs::File::open(path).context("unable to read key file")?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)
.context("unable to read bytes from file, is the file corrupted?")?;
match key_type {
KeyType::Ed25519 => {
info!(
subject = "pubkey_config.path.ed25519",
category = "pubkey_config",
"importing ed25519 key from: {}",
path.display()
);
String::from_utf8(buf.clone()).with_context(|| {
"unable to read PEM, file contained invalid UTF-8 code points"
})
.and_then(|pem| ed25519_dalek::SigningKey::from_pkcs8_pem(&pem).with_context(|| "unable to deserialize ed25119 key from PEM"))
.and_then(|pem| {
let mut key = pem.to_bytes();
identity::Keypair::ed25519_from_bytes(&mut key).with_context(|| "imported key material was invalid for ed25519")
})
.or_else(|_| {
const PEM_HEADER: &str = "PRIVATE KEY";
let (tag, mut key) = sec1::der::pem::decode_vec(&buf)
.map_err(|e| anyhow!("key file must be PEM formatted: {:#?}", e))?;
if tag != PEM_HEADER {
return Err(anyhow!("imported key file had a header of '{tag}', expected '{PEM_HEADER}' for ed25519"));
}
identity::Keypair::ed25519_from_bytes(&mut key)
.with_context(|| "imported key material was invalid for ed25519")
})
}
KeyType::Secp256k1 => {
info!(
subject = "pubkey_config.path.secp256k1",
category = "pubkey_config",
"importing secp256k1 key from: {}",
path.display()
);
let sk = match path.extension().and_then(|ext| ext.to_str()) {
Some("der") => sec1::EcPrivateKey::from_der(buf.as_slice()).map_err(|e| anyhow!("failed to parse DER encoded secp256k1 key: {e:#?}")),
Some("pem") => {
Err(anyhow!("PEM encoded secp256k1 keys are unsupported at the moment. Please file an issue if you require this."))
},
_ => Err(anyhow!("please disambiguate file from either PEM or DER with a file extension."))
}?;
let kp = secp256k1::SecretKey::try_from_bytes(sk.private_key.to_vec())
.map(secp256k1::Keypair::from)
.map_err(|e| anyhow!("failed to import secp256k1 key: {:#?}", e))?;
Ok(identity::Keypair::from(kp))
}
}
}
}
}
}