use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::types::event::Event;
use crate::types::ids::EventId;
pub struct SigningKeyPair {
signing_key: SigningKey,
}
impl SigningKeyPair {
pub fn generate() -> Self {
let signing_key = SigningKey::generate(&mut OsRng);
Self { signing_key }
}
pub fn from_seed_hex(seed_hex: &str) -> Result<Self, SigningError> {
let seed_bytes =
hex::decode(seed_hex).map_err(|e| SigningError::KeyParseError(e.to_string()))?;
if seed_bytes.len() != 32 {
return Err(SigningError::KeyParseError(format!(
"Seed must be 32 bytes, got {}",
seed_bytes.len()
)));
}
let mut seed_array = [0u8; 32];
seed_array.copy_from_slice(&seed_bytes);
let signing_key = SigningKey::from_bytes(&seed_array);
Ok(Self { signing_key })
}
pub fn seed_hex(&self) -> String {
hex::encode(self.signing_key.to_bytes())
}
pub fn public_key_hex(&self) -> String {
hex::encode(self.signing_key.verifying_key().to_bytes())
}
pub fn verifying_key(&self) -> VerifyingKey {
self.signing_key.verifying_key()
}
pub fn sign(&self, event_id: &EventId) -> Vec<u8> {
let signature = self.signing_key.sign(event_id);
signature.to_bytes().to_vec()
}
pub fn sign_event(&self, event: &Event) -> Vec<u8> {
self.sign(&event.event_id)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum VerificationPolicy {
#[default]
Off,
Warn,
Require,
}
impl VerificationPolicy {
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"off" => Some(VerificationPolicy::Off),
"warn" => Some(VerificationPolicy::Warn),
"require" => Some(VerificationPolicy::Require),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
VerificationPolicy::Off => "off",
VerificationPolicy::Warn => "warn",
VerificationPolicy::Require => "require",
}
}
}
#[derive(Debug, Error)]
pub enum SigningError {
#[error("signature missing")]
SignatureMissing,
#[error("invalid signature")]
InvalidSignature,
#[error("public key not found for actor {0}")]
PublicKeyNotFound(String),
#[error("key parse error: {0}")]
KeyParseError(String),
#[error("signature parse error: {0}")]
SignatureParseError(String),
}
pub fn verify_signature(event: &Event, public_key_hex: &str) -> Result<(), SigningError> {
let sig_bytes = event.sig.as_ref().ok_or(SigningError::SignatureMissing)?;
let pk_bytes =
hex::decode(public_key_hex).map_err(|e| SigningError::KeyParseError(e.to_string()))?;
if pk_bytes.len() != 32 {
return Err(SigningError::KeyParseError(format!(
"Public key must be 32 bytes, got {}",
pk_bytes.len()
)));
}
let mut pk_array = [0u8; 32];
pk_array.copy_from_slice(&pk_bytes);
let verifying_key = VerifyingKey::from_bytes(&pk_array)
.map_err(|e| SigningError::KeyParseError(e.to_string()))?;
if sig_bytes.len() != 64 {
return Err(SigningError::SignatureParseError(format!(
"Signature must be 64 bytes, got {}",
sig_bytes.len()
)));
}
let mut sig_array = [0u8; 64];
sig_array.copy_from_slice(sig_bytes);
let signature = Signature::from_bytes(&sig_array);
verifying_key
.verify(&event.event_id, &signature)
.map_err(|_| SigningError::InvalidSignature)
}
pub fn verify_raw(
event_id: &EventId,
signature: &[u8],
public_key_hex: &str,
) -> Result<(), SigningError> {
let pk_bytes =
hex::decode(public_key_hex).map_err(|e| SigningError::KeyParseError(e.to_string()))?;
if pk_bytes.len() != 32 {
return Err(SigningError::KeyParseError(format!(
"Public key must be 32 bytes, got {}",
pk_bytes.len()
)));
}
let mut pk_array = [0u8; 32];
pk_array.copy_from_slice(&pk_bytes);
let verifying_key = VerifyingKey::from_bytes(&pk_array)
.map_err(|e| SigningError::KeyParseError(e.to_string()))?;
if signature.len() != 64 {
return Err(SigningError::SignatureParseError(format!(
"Signature must be 64 bytes, got {}",
signature.len()
)));
}
let mut sig_array = [0u8; 64];
sig_array.copy_from_slice(signature);
let sig = Signature::from_bytes(&sig_array);
verifying_key
.verify(event_id, &sig)
.map_err(|_| SigningError::InvalidSignature)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::event::EventKind;
#[test]
fn test_keypair_generation() {
let keypair = SigningKeyPair::generate();
let seed = keypair.seed_hex();
let pk = keypair.public_key_hex();
assert_eq!(seed.len(), 64);
assert_eq!(pk.len(), 64);
}
#[test]
fn test_keypair_from_seed() {
let keypair1 = SigningKeyPair::generate();
let seed = keypair1.seed_hex();
let keypair2 = SigningKeyPair::from_seed_hex(&seed).unwrap();
assert_eq!(keypair1.public_key_hex(), keypair2.public_key_hex());
}
#[test]
fn test_sign_and_verify() {
let keypair = SigningKeyPair::generate();
let event_id: EventId = [42u8; 32];
let signature = keypair.sign(&event_id);
assert_eq!(signature.len(), 64);
let result = verify_raw(&event_id, &signature, &keypair.public_key_hex());
assert!(result.is_ok());
}
#[test]
fn test_sign_event() {
let keypair = SigningKeyPair::generate();
let mut event = Event::new(
[1u8; 32],
[2u8; 16],
[3u8; 16],
1700000000000,
None,
EventKind::IssueCreated {
title: "Test".to_string(),
body: "Body".to_string(),
labels: vec![],
},
);
event.sig = Some(keypair.sign_event(&event));
let result = verify_signature(&event, &keypair.public_key_hex());
assert!(result.is_ok());
}
#[test]
fn test_verify_missing_signature() {
let keypair = SigningKeyPair::generate();
let event = Event::new(
[1u8; 32],
[2u8; 16],
[3u8; 16],
1700000000000,
None,
EventKind::CommentAdded {
body: "test".to_string(),
},
);
let result = verify_signature(&event, &keypair.public_key_hex());
assert!(matches!(result, Err(SigningError::SignatureMissing)));
}
#[test]
fn test_verify_invalid_signature() {
let keypair = SigningKeyPair::generate();
let mut event = Event::new(
[1u8; 32],
[2u8; 16],
[3u8; 16],
1700000000000,
None,
EventKind::CommentAdded {
body: "test".to_string(),
},
);
event.sig = Some(vec![0u8; 64]);
let result = verify_signature(&event, &keypair.public_key_hex());
assert!(matches!(result, Err(SigningError::InvalidSignature)));
}
#[test]
fn test_verify_wrong_public_key() {
let keypair1 = SigningKeyPair::generate();
let keypair2 = SigningKeyPair::generate();
let mut event = Event::new(
[1u8; 32],
[2u8; 16],
[3u8; 16],
1700000000000,
None,
EventKind::CommentAdded {
body: "test".to_string(),
},
);
event.sig = Some(keypair1.sign_event(&event));
let result = verify_signature(&event, &keypair2.public_key_hex());
assert!(matches!(result, Err(SigningError::InvalidSignature)));
}
#[test]
fn test_verification_policy_parse() {
assert_eq!(
VerificationPolicy::from_str("off"),
Some(VerificationPolicy::Off)
);
assert_eq!(
VerificationPolicy::from_str("warn"),
Some(VerificationPolicy::Warn)
);
assert_eq!(
VerificationPolicy::from_str("require"),
Some(VerificationPolicy::Require)
);
assert_eq!(
VerificationPolicy::from_str("REQUIRE"),
Some(VerificationPolicy::Require)
);
assert_eq!(VerificationPolicy::from_str("invalid"), None);
}
}