use std::{
fmt::Write as _,
path::{Path, PathBuf},
};
use ed25519_dalek::SigningKey;
pub(crate) fn load(inline: Option<&str>, file: Option<&Path>) -> Result<SigningKey, SeedError> {
match (inline, file) {
(Some(hex), None) => signing_key(hex),
(None, Some(path)) => {
let contents =
std::fs::read_to_string(path).map_err(|source| SeedError::Unreadable {
path: path.to_path_buf(),
source,
})?;
signing_key(&contents)
}
(None, None) => Err(SeedError::Missing),
(Some(_), Some(_)) => Err(SeedError::Ambiguous),
}
}
pub(crate) fn signing_key(hex: &str) -> Result<SigningKey, SeedError> {
let raw = hex.trim();
if raw.len() != 64 {
return Err(SeedError::WrongLength { chars: raw.len() });
}
let mut seed = [0u8; 32];
for (index, byte) in seed.iter_mut().enumerate() {
let pair = raw.get(index * 2..index * 2 + 2).ok_or(SeedError::NotHex)?;
*byte = u8::from_str_radix(pair, 16).map_err(|_| SeedError::NotHex)?;
}
Ok(SigningKey::from_bytes(&seed))
}
#[must_use]
pub(crate) fn to_hex(seed: &[u8; 32]) -> String {
seed.iter()
.fold(String::with_capacity(64), |mut hex, byte| {
let _ = write!(hex, "{byte:02x}");
hex
})
}
#[derive(Debug, thiserror::Error)]
pub(crate) enum SeedError {
#[error("give either an inline seed or a key file, not both")]
Ambiguous,
#[error("a seed is required: inline hex or a key file")]
Missing,
#[error("seed must be lowercase hex")]
NotHex,
#[error("key file {path}: {source}")]
Unreadable {
path: PathBuf,
source: std::io::Error,
},
#[error("seed must be 64 hex characters (32 bytes), got {chars}")]
WrongLength {
chars: usize,
},
}