use crate::{Error, Result, SigningPayload};
use base64::engine::general_purpose::STANDARD as BASE64;
use base64::Engine;
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use rand::rngs::OsRng;
pub fn generate_keypair() -> (String, String) {
let signing_key = SigningKey::generate(&mut OsRng);
let verifying_key = signing_key.verifying_key();
let private_key = BASE64.encode(signing_key.to_bytes());
let public_key = BASE64.encode(verifying_key.to_bytes());
(private_key, public_key)
}
pub fn sign_payload(private_key_base64: &str, payload: &SigningPayload) -> Result<String> {
let private_key_bytes = BASE64
.decode(private_key_base64)
.map_err(|e| Error::InvalidPublicKey(format!("Invalid base64: {}", e)))?;
let signing_key = SigningKey::from_bytes(
&private_key_bytes
.try_into()
.map_err(|_| Error::InvalidPublicKey("Invalid key length".to_string()))?,
);
let canonical_json = payload.to_canonical_json()?;
let signature = signing_key.sign(canonical_json.as_bytes());
Ok(BASE64.encode(signature.to_bytes()))
}
pub fn verify_signature(
public_key_base64: &str,
signature_base64: &str,
payload: &SigningPayload,
) -> Result<()> {
let public_key_bytes = BASE64
.decode(public_key_base64)
.map_err(|e| Error::InvalidPublicKey(format!("Invalid base64: {}", e)))?;
let verifying_key = VerifyingKey::from_bytes(
&public_key_bytes
.try_into()
.map_err(|_| Error::InvalidPublicKey("Invalid key length".to_string()))?,
)
.map_err(|e| Error::InvalidPublicKey(format!("Invalid public key: {}", e)))?;
let signature_bytes = BASE64
.decode(signature_base64)
.map_err(|e| Error::Crypto(format!("Invalid signature base64: {}", e)))?;
let signature = Signature::from_bytes(
&signature_bytes
.try_into()
.map_err(|_| Error::Crypto("Invalid signature length".to_string()))?,
);
let canonical_json = payload.to_canonical_json()?;
verifying_key
.verify(canonical_json.as_bytes(), &signature)
.map_err(|_| Error::InvalidSignature)?;
Ok(())
}
pub fn validate_public_key(public_key_base64: &str) -> Result<()> {
let public_key_bytes = BASE64
.decode(public_key_base64)
.map_err(|e| Error::InvalidPublicKey(format!("Invalid base64: {}", e)))?;
VerifyingKey::from_bytes(
&public_key_bytes
.try_into()
.map_err(|_| Error::InvalidPublicKey("Invalid key length".to_string()))?,
)
.map_err(|e| Error::InvalidPublicKey(format!("Invalid public key: {}", e)))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_keypair() {
let (private_key, public_key) = generate_keypair();
assert!(BASE64.decode(&private_key).is_ok());
assert!(BASE64.decode(&public_key).is_ok());
assert_eq!(BASE64.decode(&private_key).unwrap().len(), 32);
assert_eq!(BASE64.decode(&public_key).unwrap().len(), 32);
}
#[test]
fn test_sign_and_verify() {
let (private_key, public_key) = generate_keypair();
let payload = SigningPayload {
challenge: "test-challenge".to_string(),
domain: "example.com".to_string(),
signed_at: "2025-10-02T10:30:00Z".to_string(),
verifier_id: "service.example.net".to_string(),
};
let signature = sign_payload(&private_key, &payload).unwrap();
assert!(verify_signature(&public_key, &signature, &payload).is_ok());
}
#[test]
fn test_verify_with_wrong_key_fails() {
let (private_key, _) = generate_keypair();
let (_, wrong_public_key) = generate_keypair();
let payload = SigningPayload {
challenge: "test-challenge".to_string(),
domain: "example.com".to_string(),
signed_at: "2025-10-02T10:30:00Z".to_string(),
verifier_id: "service.example.net".to_string(),
};
let signature = sign_payload(&private_key, &payload).unwrap();
assert!(matches!(
verify_signature(&wrong_public_key, &signature, &payload),
Err(Error::InvalidSignature)
));
}
#[test]
fn test_verify_tampered_payload_fails() {
let (private_key, public_key) = generate_keypair();
let payload = SigningPayload {
challenge: "test-challenge".to_string(),
domain: "example.com".to_string(),
signed_at: "2025-10-02T10:30:00Z".to_string(),
verifier_id: "service.example.net".to_string(),
};
let signature = sign_payload(&private_key, &payload).unwrap();
let tampered_payload = SigningPayload {
domain: "evil.com".to_string(),
..payload
};
assert!(matches!(
verify_signature(&public_key, &signature, &tampered_payload),
Err(Error::InvalidSignature)
));
}
#[test]
fn test_validate_public_key() {
let (_, public_key) = generate_keypair();
assert!(validate_public_key(&public_key).is_ok());
assert!(matches!(
validate_public_key("invalid-key"),
Err(Error::InvalidPublicKey(_))
));
assert!(matches!(
validate_public_key("dG9vLXNob3J0"),
Err(Error::InvalidPublicKey(_))
));
}
}