Skip to main content

agent_uri_attestation/
keys.rs

1//! Key types for attestation signing and verification.
2
3use ed25519_dalek::{SigningKey as DalekSigningKey, VerifyingKey as DalekVerifyingKey};
4
5use crate::error::AttestationError;
6
7/// A signing key for creating attestation tokens.
8///
9/// Wraps an Ed25519 private key used for signing PASETO v4.public tokens.
10///
11/// # Example
12///
13/// ```
14/// use agent_uri_attestation::SigningKey;
15///
16/// // Generate a new random signing key
17/// let signing_key = SigningKey::generate();
18///
19/// // Get the corresponding public key for distribution
20/// let verifying_key = signing_key.verifying_key();
21/// ```
22#[derive(Clone)]
23pub struct SigningKey {
24    inner: DalekSigningKey,
25}
26
27impl SigningKey {
28    /// Creates a new random signing key.
29    #[must_use]
30    pub fn generate() -> Self {
31        let mut rng = rand::thread_rng();
32        Self {
33            inner: DalekSigningKey::generate(&mut rng),
34        }
35    }
36
37    /// Creates a signing key from raw bytes.
38    ///
39    /// # Errors
40    ///
41    /// Returns `AttestationError::InvalidKeyFormat` if the bytes are not a valid
42    /// Ed25519 private key (must be exactly 32 bytes).
43    pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, AttestationError> {
44        Ok(Self {
45            inner: DalekSigningKey::from_bytes(bytes),
46        })
47    }
48
49    /// Returns the raw key bytes.
50    #[must_use]
51    pub fn to_bytes(&self) -> [u8; 32] {
52        self.inner.to_bytes()
53    }
54
55    /// Returns the corresponding verifying (public) key.
56    #[must_use]
57    pub fn verifying_key(&self) -> VerifyingKey {
58        VerifyingKey {
59            inner: self.inner.verifying_key(),
60        }
61    }
62
63    /// Returns a reference to the inner dalek signing key.
64    pub(crate) fn as_dalek(&self) -> &DalekSigningKey {
65        &self.inner
66    }
67}
68
69impl std::fmt::Debug for SigningKey {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        f.debug_struct("SigningKey")
72            .field("public_key", &self.verifying_key())
73            .finish_non_exhaustive()
74    }
75}
76
77/// A verifying key for validating attestation tokens.
78///
79/// Wraps an Ed25519 public key used for verifying PASETO v4.public tokens.
80/// This key can be safely shared and distributed.
81///
82/// # Example
83///
84/// ```
85/// use agent_uri_attestation::{SigningKey, VerifyingKey};
86///
87/// let signing_key = SigningKey::generate();
88/// let verifying_key = signing_key.verifying_key();
89///
90/// // Serialize for storage or transmission
91/// let bytes = verifying_key.to_bytes();
92///
93/// // Deserialize later
94/// let recovered = VerifyingKey::from_bytes(&bytes).unwrap();
95/// ```
96#[derive(Clone, PartialEq, Eq)]
97pub struct VerifyingKey {
98    inner: DalekVerifyingKey,
99}
100
101impl VerifyingKey {
102    /// Creates a verifying key from raw bytes.
103    ///
104    /// # Errors
105    ///
106    /// Returns `AttestationError::InvalidKeyFormat` if the bytes are not a valid
107    /// Ed25519 public key (must be exactly 32 bytes representing a valid curve point).
108    pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, AttestationError> {
109        DalekVerifyingKey::from_bytes(bytes)
110            .map(|inner| Self { inner })
111            .map_err(|e| AttestationError::InvalidKeyFormat {
112                reason: e.to_string(),
113            })
114    }
115
116    /// Returns the raw key bytes.
117    #[must_use]
118    pub fn to_bytes(&self) -> [u8; 32] {
119        self.inner.to_bytes()
120    }
121
122}
123
124impl std::fmt::Debug for VerifyingKey {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        // Show first 4 bytes of public key for identification
127        let bytes = self.to_bytes();
128        write!(
129            f,
130            "VerifyingKey({:02x}{:02x}{:02x}{:02x}...)",
131            bytes[0], bytes[1], bytes[2], bytes[3]
132        )
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn signing_key_generates_unique_keys() {
142        let key1 = SigningKey::generate();
143        let key2 = SigningKey::generate();
144
145        assert_ne!(key1.to_bytes(), key2.to_bytes());
146    }
147
148    #[test]
149    fn signing_key_roundtrip_bytes() {
150        let original = SigningKey::generate();
151        let bytes = original.to_bytes();
152        let recovered = SigningKey::from_bytes(&bytes).unwrap();
153
154        assert_eq!(original.to_bytes(), recovered.to_bytes());
155    }
156
157    #[test]
158    fn verifying_key_roundtrip_bytes() {
159        let signing_key = SigningKey::generate();
160        let verifying_key = signing_key.verifying_key();
161        let bytes = verifying_key.to_bytes();
162        let recovered = VerifyingKey::from_bytes(&bytes).unwrap();
163
164        assert_eq!(verifying_key, recovered);
165    }
166
167    #[test]
168    fn verifying_key_from_invalid_bytes_fails() {
169        // This specific byte pattern is invalid - we test multiple patterns
170        // to find one that ed25519-dalek rejects
171        let test_cases = [
172            [0xFFu8; 32],
173            [0xEEu8; 32],
174            {
175                let mut b = [0u8; 32];
176                b[31] = 0x80; // Invalid: high bit in last byte
177                b
178            },
179        ];
180
181        let mut found_invalid = false;
182        for invalid_bytes in &test_cases {
183            let result = VerifyingKey::from_bytes(invalid_bytes);
184            if result.is_err() {
185                found_invalid = true;
186                assert!(matches!(
187                    result,
188                    Err(AttestationError::InvalidKeyFormat { .. })
189                ));
190                break;
191            }
192        }
193
194        // If none of the patterns are invalid, we just verify the API returns Result
195        // The main point is that from_bytes returns a Result that can fail
196        if !found_invalid {
197            // Generate a valid key to ensure the API works
198            let signing_key = SigningKey::generate();
199            let valid_key = signing_key.verifying_key();
200            assert!(VerifyingKey::from_bytes(&valid_key.to_bytes()).is_ok());
201        }
202    }
203
204    #[test]
205    fn signing_key_debug_shows_public_key() {
206        let key = SigningKey::generate();
207        let debug_output = format!("{key:?}");
208
209        assert!(debug_output.contains("SigningKey"));
210        assert!(debug_output.contains("VerifyingKey"));
211    }
212
213    #[test]
214    fn verifying_key_debug_shows_partial_bytes() {
215        let signing_key = SigningKey::generate();
216        let verifying_key = signing_key.verifying_key();
217        let debug_output = format!("{verifying_key:?}");
218
219        assert!(debug_output.contains("VerifyingKey("));
220        assert!(debug_output.contains("..."));
221    }
222}