use std::sync::LazyLock; use bincode::{config, serde::encode_to_vec}; use ed25519::signature::{Signer, Verifier}; use ed25519_dalek::{SigningKey, VerifyingKey}; use serde::Serialize; use tokio::sync::RwLock; use zeroize::Zeroize; pub type SignatureBundle = SignatureKeyBundle; static SIGNATURE_BUNDLE: LazyLock> = LazyLock::new(|| RwLock::new(SignatureKeyBundle::new())); pub trait Verifiable { fn sign(&self) -> impl std::future::Future + Send; fn verify( &self, signature: &ed25519::Signature, ) -> impl std::future::Future> + Send; } #[derive(Zeroize)] struct SignatureKeyBundle where S: Signer, V: Verifier, { pub signing_key: S, pub verifying_key: V, } impl SignatureKeyBundle where S: Signer, V: Verifier, { /// Signs a serializable payload pub fn sign(&self, payload: &P) -> ed25519::Signature { self.sign_bytes(&SignatureKeyBundle::::hash_struct(payload)) } fn sign_bytes(&self, payload: &[u8]) -> ed25519::Signature { // NOTE: use `try_sign` if you'd like to be able to handle // errors from external signing services/devices (e.g. HSM/KMS) // self.signing_key.sign(payload) } /// Verifies a serializable payload against a given signature. pub fn verify( &self, payload: &P, signature: &ed25519::Signature, ) -> Result<(), ed25519::Error> { self.verify_bytes(&SignatureKeyBundle::::hash_struct(payload), signature) } fn verify_bytes( &self, payload: &[u8], signature: &ed25519::Signature, ) -> Result<(), ed25519::Error> { self.verifying_key.verify(payload, signature) } /// Serializes and hashes payload. /// Uses `bincode` for serialization and `blake3` for hashing. fn hash_struct(payload: &P) -> [u8; blake3::OUT_LEN] { let serialized_payload = encode_to_vec(payload, config::standard()).expect("Failed to serialize payload"); let hash: blake3::Hash = blake3::hash(&serialized_payload); let hash_bytes = hash.as_bytes(); *hash_bytes } } impl SignatureKeyBundle { pub fn new() -> SignatureKeyBundle { let mut rng = aes_gcm_siv::aead::OsRng; let signing_key = SigningKey::generate(&mut rng); let verifying_key: VerifyingKey = signing_key.verifying_key(); Self { signing_key, verifying_key, } } } pub async fn sign(payload: &P) -> ed25519::Signature { SIGNATURE_BUNDLE.read().await.sign(payload) } pub async fn verify( payload: &P, signature: &ed25519::Signature, ) -> Result<(), ed25519::Error> { SIGNATURE_BUNDLE.read().await.verify(payload, signature) } #[cfg(test)] mod tests { use super::*; #[derive(Serialize)] struct TestPayload { data: String, } #[derive(Serialize)] struct TestPayloadEvolution { data: String, new_prop: Option, } #[test] fn test_sign_and_verify() { let key_bundle = SignatureKeyBundle::new(); let payload = TestPayload { data: "test data".to_string(), }; let signature = key_bundle.sign(&payload); assert!(key_bundle.verify(&payload, &signature).is_ok()); } #[test] fn test_verify_with_invalid_signature() { let key_bundle = SignatureKeyBundle::new(); let payload = TestPayload { data: "test data".to_string(), }; let signature = key_bundle.sign(&payload); let invalid_payload = TestPayload { data: "tampered data".to_string(), }; assert!(key_bundle.verify(&invalid_payload, &signature).is_err()); } #[test] fn test_verify_with_different_bundles() { let key_bundle = SignatureKeyBundle::new(); let payload = TestPayload { data: "test data".to_string(), }; let signature = key_bundle.sign(&payload); let key_bundle = SignatureKeyBundle::new(); assert!(key_bundle.verify(&payload, &signature).is_err()); } #[test] fn test_sign_bytes_and_verify_bytes() { let key_bundle = SignatureKeyBundle::new(); let payload = b"test bytes"; let signature = key_bundle.sign_bytes(payload); assert!(key_bundle.verify_bytes(payload, &signature).is_ok()); } #[test] fn test_verify_bytes_with_invalid_signature() { let key_bundle = SignatureKeyBundle::new(); let payload = b"test bytes"; let signature = key_bundle.sign_bytes(payload); let invalid_payload = b"tampered bytes"; assert!( key_bundle .verify_bytes(invalid_payload, &signature) .is_err() ); } }