use anyhow::{anyhow, Result};
use super::{format_key, parse_key, KeyAlgorithm};
pub fn compute_hub_subdomain(public_key: &str) -> Result<String> {
let key = parse_key(public_key)?;
let b32 = data_encoding::BASE32_NOPAD
.encode(&key.bytes)
.to_ascii_lowercase();
Ok(format!("ed-{}", b32))
}
pub fn recover_pubkey_from_subdomain(subdomain: &str) -> Result<String> {
let b32 = subdomain
.strip_prefix("ed-")
.ok_or_else(|| anyhow!("Subdomain must start with 'ed-'"))?;
let key_bytes = data_encoding::BASE32_NOPAD
.decode(b32.to_ascii_uppercase().as_bytes())
.map_err(|e| anyhow!("Invalid base32 in subdomain: {}", e))?;
if key_bytes.len() != 32 {
return Err(anyhow!(
"Invalid ed25519 public key length in subdomain: expected 32 bytes, got {}",
key_bytes.len()
));
}
Ok(format_key(KeyAlgorithm::Ed25519, &key_bytes))
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn test_compute_hub_subdomain_shape() {
let sub =
compute_hub_subdomain("ed25519.2p3NPZceQ6njbPg8aMFsEynX3Cmv6uCt1XMGHhPcL4AT").unwrap();
assert!(sub.starts_with("ed-"));
assert_eq!(sub.len(), 55);
assert!(sub
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'));
}
#[test]
fn test_recover_pubkey_from_subdomain_roundtrip() {
let key = "ed25519.2p3NPZceQ6njbPg8aMFsEynX3Cmv6uCt1XMGHhPcL4AT";
let sub = compute_hub_subdomain(key).unwrap();
let recovered = recover_pubkey_from_subdomain(&sub).unwrap();
assert_eq!(recovered, key);
}
#[test]
fn test_recover_pubkey_from_subdomain_rejects_invalid_prefix() {
assert!(recover_pubkey_from_subdomain("xx-invalid").is_err());
}
}