Skip to main content

libgrite_core/
signing.rs

1//! Ed25519 signing and verification for events
2//!
3//! Signatures are detached - they sign the 32-byte event_id, not the full event.
4//! This allows verification independent of serialization format.
5
6use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
7use rand::rngs::OsRng;
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11use crate::types::event::Event;
12use crate::types::ids::EventId;
13
14/// Ed25519 signing key pair
15pub struct SigningKeyPair {
16    signing_key: SigningKey,
17}
18
19impl SigningKeyPair {
20    /// Generate a new random Ed25519 key pair
21    pub fn generate() -> Self {
22        let signing_key = SigningKey::generate(&mut OsRng);
23        Self { signing_key }
24    }
25
26    /// Create from a 32-byte seed (hex-encoded)
27    pub fn from_seed_hex(seed_hex: &str) -> Result<Self, SigningError> {
28        let seed_bytes =
29            hex::decode(seed_hex).map_err(|e| SigningError::KeyParseError(e.to_string()))?;
30
31        if seed_bytes.len() != 32 {
32            return Err(SigningError::KeyParseError(format!(
33                "Seed must be 32 bytes, got {}",
34                seed_bytes.len()
35            )));
36        }
37
38        let mut seed_array = [0u8; 32];
39        seed_array.copy_from_slice(&seed_bytes);
40
41        let signing_key = SigningKey::from_bytes(&seed_array);
42        Ok(Self { signing_key })
43    }
44
45    /// Get the seed as hex (for storage)
46    pub fn seed_hex(&self) -> String {
47        hex::encode(self.signing_key.to_bytes())
48    }
49
50    /// Get the public key as hex
51    pub fn public_key_hex(&self) -> String {
52        hex::encode(self.signing_key.verifying_key().to_bytes())
53    }
54
55    /// Get the verifying key
56    pub fn verifying_key(&self) -> VerifyingKey {
57        self.signing_key.verifying_key()
58    }
59
60    /// Sign an event ID (32 bytes)
61    pub fn sign(&self, event_id: &EventId) -> Vec<u8> {
62        let signature = self.signing_key.sign(event_id);
63        signature.to_bytes().to_vec()
64    }
65
66    /// Sign an event, returning the signature
67    pub fn sign_event(&self, event: &Event) -> Vec<u8> {
68        self.sign(&event.event_id)
69    }
70}
71
72/// Signature verification policy
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
74#[serde(rename_all = "lowercase")]
75pub enum VerificationPolicy {
76    /// No signature verification
77    #[default]
78    Off,
79    /// Warn on missing or invalid signatures but continue
80    Warn,
81    /// Require valid signatures on all events
82    Require,
83}
84
85impl VerificationPolicy {
86    /// Parse from string
87    #[allow(clippy::should_implement_trait)]
88    pub fn from_str(s: &str) -> Option<Self> {
89        match s.to_lowercase().as_str() {
90            "off" => Some(VerificationPolicy::Off),
91            "warn" => Some(VerificationPolicy::Warn),
92            "require" => Some(VerificationPolicy::Require),
93            _ => None,
94        }
95    }
96
97    /// Convert to string
98    pub fn as_str(&self) -> &'static str {
99        match self {
100            VerificationPolicy::Off => "off",
101            VerificationPolicy::Warn => "warn",
102            VerificationPolicy::Require => "require",
103        }
104    }
105}
106
107/// Errors that can occur during signing or verification
108#[derive(Debug, Error)]
109pub enum SigningError {
110    #[error("signature missing")]
111    SignatureMissing,
112
113    #[error("invalid signature")]
114    InvalidSignature,
115
116    #[error("public key not found for actor {0}")]
117    PublicKeyNotFound(String),
118
119    #[error("key parse error: {0}")]
120    KeyParseError(String),
121
122    #[error("signature parse error: {0}")]
123    SignatureParseError(String),
124}
125
126/// Verify an event signature against a public key
127pub fn verify_signature(event: &Event, public_key_hex: &str) -> Result<(), SigningError> {
128    // Get signature from event
129    let sig_bytes = event.sig.as_ref().ok_or(SigningError::SignatureMissing)?;
130
131    // Parse public key
132    let pk_bytes =
133        hex::decode(public_key_hex).map_err(|e| SigningError::KeyParseError(e.to_string()))?;
134
135    if pk_bytes.len() != 32 {
136        return Err(SigningError::KeyParseError(format!(
137            "Public key must be 32 bytes, got {}",
138            pk_bytes.len()
139        )));
140    }
141
142    let mut pk_array = [0u8; 32];
143    pk_array.copy_from_slice(&pk_bytes);
144
145    let verifying_key = VerifyingKey::from_bytes(&pk_array)
146        .map_err(|e| SigningError::KeyParseError(e.to_string()))?;
147
148    // Parse signature
149    if sig_bytes.len() != 64 {
150        return Err(SigningError::SignatureParseError(format!(
151            "Signature must be 64 bytes, got {}",
152            sig_bytes.len()
153        )));
154    }
155
156    let mut sig_array = [0u8; 64];
157    sig_array.copy_from_slice(sig_bytes);
158
159    let signature = Signature::from_bytes(&sig_array);
160
161    // Verify
162    verifying_key
163        .verify(&event.event_id, &signature)
164        .map_err(|_| SigningError::InvalidSignature)
165}
166
167/// Verify a raw signature against event_id and public key
168pub fn verify_raw(
169    event_id: &EventId,
170    signature: &[u8],
171    public_key_hex: &str,
172) -> Result<(), SigningError> {
173    // Parse public key
174    let pk_bytes =
175        hex::decode(public_key_hex).map_err(|e| SigningError::KeyParseError(e.to_string()))?;
176
177    if pk_bytes.len() != 32 {
178        return Err(SigningError::KeyParseError(format!(
179            "Public key must be 32 bytes, got {}",
180            pk_bytes.len()
181        )));
182    }
183
184    let mut pk_array = [0u8; 32];
185    pk_array.copy_from_slice(&pk_bytes);
186
187    let verifying_key = VerifyingKey::from_bytes(&pk_array)
188        .map_err(|e| SigningError::KeyParseError(e.to_string()))?;
189
190    // Parse signature
191    if signature.len() != 64 {
192        return Err(SigningError::SignatureParseError(format!(
193            "Signature must be 64 bytes, got {}",
194            signature.len()
195        )));
196    }
197
198    let mut sig_array = [0u8; 64];
199    sig_array.copy_from_slice(signature);
200
201    let sig = Signature::from_bytes(&sig_array);
202
203    // Verify
204    verifying_key
205        .verify(event_id, &sig)
206        .map_err(|_| SigningError::InvalidSignature)
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212    use crate::types::event::EventKind;
213
214    #[test]
215    fn test_keypair_generation() {
216        let keypair = SigningKeyPair::generate();
217        let seed = keypair.seed_hex();
218        let pk = keypair.public_key_hex();
219
220        // Seed should be 64 hex chars (32 bytes)
221        assert_eq!(seed.len(), 64);
222        // Public key should be 64 hex chars (32 bytes)
223        assert_eq!(pk.len(), 64);
224    }
225
226    #[test]
227    fn test_keypair_from_seed() {
228        let keypair1 = SigningKeyPair::generate();
229        let seed = keypair1.seed_hex();
230
231        let keypair2 = SigningKeyPair::from_seed_hex(&seed).unwrap();
232
233        // Same seed should produce same public key
234        assert_eq!(keypair1.public_key_hex(), keypair2.public_key_hex());
235    }
236
237    #[test]
238    fn test_sign_and_verify() {
239        let keypair = SigningKeyPair::generate();
240        let event_id: EventId = [42u8; 32];
241
242        let signature = keypair.sign(&event_id);
243
244        // Signature should be 64 bytes
245        assert_eq!(signature.len(), 64);
246
247        // Verification should succeed
248        let result = verify_raw(&event_id, &signature, &keypair.public_key_hex());
249        assert!(result.is_ok());
250    }
251
252    #[test]
253    fn test_sign_event() {
254        let keypair = SigningKeyPair::generate();
255
256        let mut event = Event::new(
257            [1u8; 32],
258            [2u8; 16],
259            [3u8; 16],
260            1700000000000,
261            None,
262            EventKind::IssueCreated {
263                title: "Test".to_string(),
264                body: "Body".to_string(),
265                labels: vec![],
266            },
267        );
268
269        event.sig = Some(keypair.sign_event(&event));
270
271        // Verification should succeed
272        let result = verify_signature(&event, &keypair.public_key_hex());
273        assert!(result.is_ok());
274    }
275
276    #[test]
277    fn test_verify_missing_signature() {
278        let keypair = SigningKeyPair::generate();
279
280        let event = Event::new(
281            [1u8; 32],
282            [2u8; 16],
283            [3u8; 16],
284            1700000000000,
285            None,
286            EventKind::CommentAdded {
287                body: "test".to_string(),
288            },
289        );
290
291        let result = verify_signature(&event, &keypair.public_key_hex());
292        assert!(matches!(result, Err(SigningError::SignatureMissing)));
293    }
294
295    #[test]
296    fn test_verify_invalid_signature() {
297        let keypair = SigningKeyPair::generate();
298
299        let mut event = Event::new(
300            [1u8; 32],
301            [2u8; 16],
302            [3u8; 16],
303            1700000000000,
304            None,
305            EventKind::CommentAdded {
306                body: "test".to_string(),
307            },
308        );
309
310        // Set invalid signature (wrong bytes)
311        event.sig = Some(vec![0u8; 64]);
312
313        let result = verify_signature(&event, &keypair.public_key_hex());
314        assert!(matches!(result, Err(SigningError::InvalidSignature)));
315    }
316
317    #[test]
318    fn test_verify_wrong_public_key() {
319        let keypair1 = SigningKeyPair::generate();
320        let keypair2 = SigningKeyPair::generate();
321
322        let mut event = Event::new(
323            [1u8; 32],
324            [2u8; 16],
325            [3u8; 16],
326            1700000000000,
327            None,
328            EventKind::CommentAdded {
329                body: "test".to_string(),
330            },
331        );
332
333        // Sign with keypair1
334        event.sig = Some(keypair1.sign_event(&event));
335
336        // Verify with keypair2's public key - should fail
337        let result = verify_signature(&event, &keypair2.public_key_hex());
338        assert!(matches!(result, Err(SigningError::InvalidSignature)));
339    }
340
341    #[test]
342    fn test_verification_policy_parse() {
343        assert_eq!(
344            VerificationPolicy::from_str("off"),
345            Some(VerificationPolicy::Off)
346        );
347        assert_eq!(
348            VerificationPolicy::from_str("warn"),
349            Some(VerificationPolicy::Warn)
350        );
351        assert_eq!(
352            VerificationPolicy::from_str("require"),
353            Some(VerificationPolicy::Require)
354        );
355        assert_eq!(
356            VerificationPolicy::from_str("REQUIRE"),
357            Some(VerificationPolicy::Require)
358        );
359        assert_eq!(VerificationPolicy::from_str("invalid"), None);
360    }
361}