1use anyhow::{anyhow, Result};
2use ed25519_dalek::VerifyingKey;
3
4use super::AlgorithmBytes;
5
6const KEY_SEPARATOR: char = '.';
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum KeyAlgorithm {
10 Ed25519,
11}
12
13pub fn parse_key(value: &str) -> Result<AlgorithmBytes<KeyAlgorithm>> {
14 let (algorithm_str, value_b58) = value
15 .split_once(KEY_SEPARATOR)
16 .ok_or_else(|| anyhow!("Key must use '{{algorithm}}.{{base58}}' format"))?;
17 let algorithm = match algorithm_str {
18 "ed25519" => KeyAlgorithm::Ed25519,
19 other => return Err(anyhow!("Unsupported key algorithm: '{}'", other)),
20 };
21 if value_b58.is_empty() {
22 return Err(anyhow!("Key payload must not be empty"));
23 }
24
25 let bytes = bs58::decode(value_b58)
26 .into_vec()
27 .map_err(|e| anyhow!("Invalid key base58 payload: {}", e))?;
28 if bytes.len() != 32 {
29 return Err(anyhow!(
30 "Invalid key length for {}: expected 32 bytes, got {}",
31 algorithm.prefix(),
32 bytes.len()
33 ));
34 }
35 validate_key_bytes(algorithm, &bytes)?;
36
37 Ok(AlgorithmBytes { algorithm, bytes })
38}
39
40pub fn format_key(algorithm: KeyAlgorithm, bytes: &[u8]) -> String {
41 format!(
42 "{}.{}",
43 algorithm.prefix(),
44 bs58::encode(bytes).into_string()
45 )
46}
47
48impl KeyAlgorithm {
49 fn prefix(self) -> &'static str {
50 match self {
51 Self::Ed25519 => "ed25519",
52 }
53 }
54}
55
56fn validate_key_bytes(algorithm: KeyAlgorithm, bytes: &[u8]) -> Result<()> {
57 match algorithm {
58 KeyAlgorithm::Ed25519 => {
59 let bytes: [u8; 32] = bytes
60 .try_into()
61 .map_err(|_| anyhow!("Invalid ed25519 public key length"))?;
62 let key = VerifyingKey::from_bytes(&bytes)
63 .map_err(|e| anyhow!("Invalid ed25519 public key: {}", e))?;
64 if key.is_weak() {
65 return Err(anyhow!(
66 "Invalid ed25519 public key: weak/small-order point"
67 ));
68 }
69 }
70 }
71 Ok(())
72}
73
74#[cfg(test)]
75#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
76mod tests {
77
78 use super::*;
79
80 #[test]
81 fn test_parse_key_roundtrip() {
82 let input = "ed25519.2p3NPZceQ6njbPg8aMFsEynX3Cmv6uCt1XMGHhPcL4AT";
83 let parsed = parse_key(input).unwrap();
84 assert_eq!(parsed.algorithm, KeyAlgorithm::Ed25519);
85 assert_eq!(format_key(parsed.algorithm, &parsed.bytes), input);
86 }
87
88 #[test]
89 fn test_parse_key_rejects_wrong_length() {
90 assert!(parse_key("ed25519.5Hue").is_err());
91 }
92
93 #[test]
94 fn test_parse_key_rejects_weak_public_key() {
95 let weak_key = format_key(KeyAlgorithm::Ed25519, &[0u8; 32]);
96 assert!(parse_key(&weak_key).is_err());
97 }
98}