openzeppelin_relayer/utils/
ed25519.rs

1//! Ed25519 (EdDSA) operations for cryptographic keys.
2//!
3//! This module provides utilities for parsing and extracting information from
4//! Ed25519 public keys in various formats (DER/SPKI, raw bytes).
5
6use ed25519_dalek::{pkcs8::DecodePublicKey, VerifyingKey};
7
8#[derive(Debug, thiserror::Error)]
9pub enum Ed25519Error {
10    #[error("Parse Error: {0}")]
11    ParseError(String),
12}
13
14/// Extract raw 32-byte Ed25519 public key from DER/SPKI encoded format.
15///
16/// AWS KMS and other providers return Ed25519 public keys in SPKI format.
17/// This function uses proper ASN.1 parsing via ed25519-dalek's pkcs8 support
18/// to extract the raw 32-byte public key from the SPKI structure.
19///
20/// This function accepts:
21/// - SPKI/DER encoded public keys (any valid length)
22/// - 32 bytes: Raw public key (already extracted)
23pub fn extract_ed25519_public_key_from_der(der: &[u8]) -> Result<[u8; 32], Ed25519Error> {
24    // If already 32 bytes, assume it's a raw public key
25    if der.len() == 32 {
26        let mut array = [0u8; 32];
27        array.copy_from_slice(der);
28        return Ok(array);
29    }
30
31    // Otherwise, parse as SPKI/DER format using ed25519-dalek
32    let verifying_key = VerifyingKey::from_public_key_der(der)
33        .map_err(|e| Ed25519Error::ParseError(format!("ASN.1 parse error: {e}")))?;
34
35    Ok(verifying_key.to_bytes())
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    // Well-known test Ed25519 public key (32 bytes)
43    const TEST_ED25519_PUBLIC_KEY: [u8; 32] = [
44        0x9d, 0x45, 0x7e, 0x45, 0xe4, 0x16, 0xc4, 0xc6, 0x77, 0x67, 0x6a, 0x42, 0xff, 0x96, 0x8e,
45        0x3c, 0xf8, 0xdc, 0x73, 0xc8, 0xf3, 0x3a, 0x8d, 0x19, 0x81, 0x29, 0x7b, 0xfa, 0x3e, 0x00,
46        0x30, 0xba,
47    ];
48
49    fn create_ed25519_spki_der(public_key: &[u8; 32]) -> Vec<u8> {
50        let mut der = vec![
51            0x30, 0x2a, // SEQUENCE, 42 bytes
52            0x30, 0x05, // SEQUENCE, 5 bytes
53            0x06, 0x03, 0x2b, 0x65, 0x70, // OID 1.3.101.112 (Ed25519)
54            0x03, 0x21, // BIT STRING, 33 bytes
55            0x00, // zero unused bits
56        ];
57        der.extend_from_slice(public_key);
58        der
59    }
60
61    #[test]
62    fn test_extract_ed25519_public_key_from_der_spki_format() {
63        let spki_der = create_ed25519_spki_der(&TEST_ED25519_PUBLIC_KEY);
64        assert_eq!(spki_der.len(), 44);
65
66        let result = extract_ed25519_public_key_from_der(&spki_der);
67        assert!(result.is_ok());
68        assert_eq!(result.unwrap(), TEST_ED25519_PUBLIC_KEY);
69    }
70
71    #[test]
72    fn test_extract_ed25519_public_key_from_der_raw_format() {
73        let result = extract_ed25519_public_key_from_der(&TEST_ED25519_PUBLIC_KEY);
74        assert!(result.is_ok());
75        assert_eq!(result.unwrap(), TEST_ED25519_PUBLIC_KEY);
76    }
77
78    #[test]
79    fn test_extract_ed25519_public_key_from_der_invalid_data() {
80        let invalid_der = vec![0u8; 10];
81        let result = extract_ed25519_public_key_from_der(&invalid_der);
82        assert!(result.is_err());
83        assert!(matches!(result, Err(Ed25519Error::ParseError(_))));
84    }
85
86    #[test]
87    fn test_extract_ed25519_public_key_preserves_key_bytes() {
88        // Verify the exact bytes are preserved
89        let spki_der = create_ed25519_spki_der(&TEST_ED25519_PUBLIC_KEY);
90        let extracted = extract_ed25519_public_key_from_der(&spki_der).unwrap();
91
92        for (i, (a, b)) in extracted
93            .iter()
94            .zip(TEST_ED25519_PUBLIC_KEY.iter())
95            .enumerate()
96        {
97            assert_eq!(a, b, "Byte mismatch at position {}", i);
98        }
99    }
100}