#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt as _;
use std::{io::Write as _, path::PathBuf};
use clap::Args;
use ed25519_dalek::SigningKey;
use onomancy_core::anchor::doc::DocAnchor;
use crate::{say, seed};
#[derive(Debug, Args)]
pub(crate) struct Keygen {
#[arg(long)]
out: Option<PathBuf>,
}
impl Keygen {
pub(crate) fn run(&self) -> Result<(), KeygenError> {
let mut bytes = [0u8; 32];
getrandom::fill(&mut bytes).map_err(|_| KeygenError::NoEntropy)?;
let key = SigningKey::from_bytes(&bytes);
let anchor = DocAnchor::from(key.verifying_key());
match &self.out {
Some(path) => {
write_key_file(path, &seed::to_hex(&bytes))?;
eprintln!("key file (SECRET): {}", path.display());
}
None => say(&seed::to_hex(&bytes)),
}
eprintln!("verifying key: {anchor}");
eprintln!("as automerge URL: automerge:{anchor}");
Ok(())
}
}
fn write_key_file(path: &std::path::Path, hex: &str) -> Result<(), KeygenError> {
let mut options = std::fs::OpenOptions::new();
options.write(true).create_new(true);
#[cfg(unix)]
{
options.mode(0o600);
}
let mut file = options.open(path).map_err(|source| KeygenError::Write {
path: path.to_path_buf(),
source,
})?;
writeln!(file, "{hex}").map_err(|source| KeygenError::Write {
path: path.to_path_buf(),
source,
})
}
#[derive(Debug, thiserror::Error)]
pub(crate) enum KeygenError {
#[error("no entropy available from the OS")]
NoEntropy,
#[error("key file {path}: {source}")]
Write {
path: PathBuf,
source: std::io::Error,
},
}