use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use sha2::{Digest, Sha256};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum SigningError {
#[error("Invalid private key: expected 32 bytes, got {0}")]
InvalidPrivateKeyLength(usize),
#[error("Invalid public key: expected 32 bytes, got {0}")]
InvalidPublicKeyLength(usize),
#[error("Invalid signature: expected 64 bytes, got {0}")]
InvalidSignatureLength(usize),
#[error("Failed to create signing key: {0}")]
KeyCreationFailed(String),
#[error("Failed to create signature: {0}")]
SignatureFailed(String),
#[error("Signature verification failed")]
VerificationFailed,
}
pub struct GeneratedKeypair {
pub private_key: Vec<u8>,
pub public_key: Vec<u8>,
pub fingerprint: String,
}
pub fn generate_signing_keypair() -> GeneratedKeypair {
let mut csprng = rand::thread_rng();
let signing_key = SigningKey::generate(&mut csprng);
let verifying_key = signing_key.verifying_key();
let public_key_bytes = verifying_key.to_bytes();
let fingerprint = compute_key_fingerprint(&public_key_bytes);
GeneratedKeypair {
private_key: signing_key.to_bytes().to_vec(),
public_key: public_key_bytes.to_vec(),
fingerprint,
}
}
pub fn compute_key_fingerprint(public_key: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(public_key);
let hash = hasher.finalize();
hex::encode(hash)
}
pub fn sign_package(package_hash: &[u8], private_key: &[u8]) -> Result<Vec<u8>, SigningError> {
if private_key.len() != 32 {
return Err(SigningError::InvalidPrivateKeyLength(private_key.len()));
}
let key_bytes: [u8; 32] = private_key
.try_into()
.map_err(|_| SigningError::InvalidPrivateKeyLength(private_key.len()))?;
let signing_key = SigningKey::from_bytes(&key_bytes);
let signature = signing_key.sign(package_hash);
Ok(signature.to_bytes().to_vec())
}
pub fn verify_signature(
package_hash: &[u8],
signature: &[u8],
public_key: &[u8],
) -> Result<(), SigningError> {
if public_key.len() != 32 {
return Err(SigningError::InvalidPublicKeyLength(public_key.len()));
}
if signature.len() != 64 {
return Err(SigningError::InvalidSignatureLength(signature.len()));
}
let key_bytes: [u8; 32] = public_key
.try_into()
.map_err(|_| SigningError::InvalidPublicKeyLength(public_key.len()))?;
let sig_bytes: [u8; 64] = signature
.try_into()
.map_err(|_| SigningError::InvalidSignatureLength(signature.len()))?;
let verifying_key = VerifyingKey::from_bytes(&key_bytes)
.map_err(|e| SigningError::KeyCreationFailed(e.to_string()))?;
let sig = Signature::from_bytes(&sig_bytes);
verifying_key
.verify(package_hash, &sig)
.map_err(|_| SigningError::VerificationFailed)
}
#[allow(dead_code)]
pub fn compute_package_hash(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
let hash = hasher.finalize();
hex::encode(hash)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_keypair() {
let keypair = generate_signing_keypair();
assert_eq!(keypair.private_key.len(), 32);
assert_eq!(keypair.public_key.len(), 32);
assert_eq!(keypair.fingerprint.len(), 64); }
#[test]
fn test_sign_and_verify() {
let keypair = generate_signing_keypair();
let package_hash = b"test package hash data";
let signature = sign_package(package_hash, &keypair.private_key).unwrap();
assert_eq!(signature.len(), 64);
let result = verify_signature(package_hash, &signature, &keypair.public_key);
assert!(result.is_ok());
}
#[test]
fn test_verify_wrong_key_fails() {
let keypair1 = generate_signing_keypair();
let keypair2 = generate_signing_keypair();
let package_hash = b"test package hash data";
let signature = sign_package(package_hash, &keypair1.private_key).unwrap();
let result = verify_signature(package_hash, &signature, &keypair2.public_key);
assert!(matches!(result, Err(SigningError::VerificationFailed)));
}
#[test]
fn test_verify_tampered_data_fails() {
let keypair = generate_signing_keypair();
let package_hash = b"test package hash data";
let tampered_hash = b"tampered package hash";
let signature = sign_package(package_hash, &keypair.private_key).unwrap();
let result = verify_signature(tampered_hash, &signature, &keypair.public_key);
assert!(matches!(result, Err(SigningError::VerificationFailed)));
}
#[test]
fn test_fingerprint_is_deterministic() {
let public_key = [0x42u8; 32];
let fp1 = compute_key_fingerprint(&public_key);
let fp2 = compute_key_fingerprint(&public_key);
assert_eq!(fp1, fp2);
}
#[test]
fn test_invalid_key_lengths() {
let package_hash = b"test";
let result = sign_package(package_hash, &[0u8; 16]);
assert!(matches!(
result,
Err(SigningError::InvalidPrivateKeyLength(16))
));
let result = verify_signature(package_hash, &[0u8; 64], &[0u8; 16]);
assert!(matches!(
result,
Err(SigningError::InvalidPublicKeyLength(16))
));
let result = verify_signature(package_hash, &[0u8; 32], &[0u8; 32]);
assert!(matches!(
result,
Err(SigningError::InvalidSignatureLength(32))
));
}
#[test]
fn test_compute_package_hash() {
let data = b"hello world";
let hash = compute_package_hash(data);
assert_eq!(hash.len(), 64);
assert_eq!(
hash,
"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
);
}
}