use std::path::{Path, PathBuf};
use ijima_core::{IjimaError, Result};
const KEY_FILENAME: &str = "issuer.key";
pub fn default_key_path() -> Result<PathBuf> {
if let Ok(dir) = std::env::var("IJIMA_DIR") {
return Ok(PathBuf::from(dir).join(KEY_FILENAME));
}
let home = home_dir().ok_or_else(|| {
IjimaError::invalid_input("cannot resolve Ijima data dir: set IJIMA_DIR or HOME")
})?;
Ok(home.join(".ijima").join(KEY_FILENAME))
}
fn home_dir() -> Option<PathBuf> {
std::env::var_os("HOME")
.or_else(|| std::env::var_os("USERPROFILE"))
.map(PathBuf::from)
}
pub fn load_or_create(path: &Path) -> Result<[u8; 32]> {
schubert::crypto::KeyStore::load_or_create(path).map_err(|e| IjimaError::Store {
detail: format!("issuer key {}: {e}", path.display()),
})
}
pub fn load(path: &Path) -> Result<[u8; 32]> {
schubert::crypto::KeyStore::load(path).map_err(|e| IjimaError::Store {
detail: format!("issuer key {}: {e}", path.display()),
})
}
pub fn public_key_hex(seed: &[u8; 32]) -> String {
use ed25519_dalek::SigningKey;
SigningKey::from_bytes(seed)
.verifying_key()
.to_bytes()
.iter()
.map(|b| format!("{b:02x}"))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn load_or_create_then_load_round_trips() {
let dir = tempfile_dir();
let path = dir.join(KEY_FILENAME);
let created = load_or_create(&path).expect("create");
let loaded = load_or_create(&path).expect("load");
assert_eq!(created, loaded);
let direct = load(&path).expect("direct load");
assert_eq!(created, direct);
}
#[test]
fn public_key_hex_is_64_lowercase_chars() {
let seed = [1u8; 32];
let hex = public_key_hex(&seed);
assert_eq!(hex.len(), 64);
assert!(
hex.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
);
}
#[test]
fn wrong_size_key_file_rejected() {
let dir = tempfile_dir();
let path = dir.join(KEY_FILENAME);
std::fs::write(&path, b"too short").unwrap();
assert!(load(&path).is_err());
}
fn tempfile_dir() -> PathBuf {
let dir = std::env::temp_dir().join(format!(
"ijima-keystore-test-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&dir).unwrap();
dir
}
}