Skip to main content

substrate/crypto/
key.rs

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}