use anyhow::Result;
use base64::{Engine, engine::general_purpose::STANDARD};
use ed25519_dalek::{Signature, Signer, Verifier};
pub use ed25519_dalek::{SigningKey, VerifyingKey};
use crate::Error;
pub fn parse_signing_key(b64: &str) -> Result<SigningKey> {
let bytes = STANDARD.decode(b64).map_err(|_| Error::InvalidKey)?;
let bytes: [u8; 32] = bytes.try_into().map_err(|_| Error::InvalidKey)?;
Ok(SigningKey::from_bytes(&bytes))
}
pub fn parse_verifying_key(b64: &str) -> Result<VerifyingKey> {
let bytes = STANDARD.decode(b64).map_err(|_| Error::InvalidKey)?;
let bytes: [u8; 32] = bytes.try_into().map_err(|_| Error::InvalidKey)?;
VerifyingKey::from_bytes(&bytes).map_err(|_| Error::InvalidKey.into())
}
#[must_use]
pub fn public_key_b64(key: &SigningKey) -> String {
STANDARD.encode(key.verifying_key().as_bytes())
}
#[must_use]
pub fn sign_payload(key: &SigningKey, payload: &[u8]) -> Vec<u8> {
let signature = key.sign(payload);
let mut result = Vec::with_capacity(64 + payload.len());
result.extend_from_slice(&signature.to_bytes());
result.extend_from_slice(payload);
result
}
pub fn verify_and_extract(key: &VerifyingKey, data: &[u8]) -> Result<Vec<u8>> {
if data.len() < 64 {
return Err(Error::SignatureInvalid.into());
}
let (sig_bytes, payload) = data.split_at(64);
let sig_arr: [u8; 64] = sig_bytes.try_into().map_err(|_| Error::SignatureInvalid)?;
let signature = Signature::from_bytes(&sig_arr);
key.verify(payload, &signature)
.map_err(|_| Error::SignatureInvalid)?;
Ok(payload.to_vec())
}
#[cfg(test)]
mod tests {
use super::{
parse_signing_key, parse_verifying_key, public_key_b64, sign_payload, verify_and_extract,
};
use base64::{Engine, engine::general_purpose::STANDARD};
use ed25519_dalek::SigningKey;
fn make_keypair(seed: u8) -> (SigningKey, String, String) {
let signing_key = SigningKey::from_bytes(&[seed; 32]);
let sk_b64 = STANDARD.encode(signing_key.as_bytes());
let pk_b64 = public_key_b64(&signing_key);
(signing_key, sk_b64, pk_b64)
}
#[test]
fn test_parse_signing_key_roundtrip() {
let (_, sk_b64, _) = make_keypair(1);
assert!(parse_signing_key(&sk_b64).is_ok());
}
#[test]
fn test_parse_verifying_key_roundtrip() {
let (_, _, pk_b64) = make_keypair(1);
assert!(parse_verifying_key(&pk_b64).is_ok());
}
#[test]
fn test_parse_signing_key_invalid_base64() {
assert!(parse_signing_key("not-valid-base64!!!").is_err());
}
#[test]
fn test_parse_signing_key_wrong_length() {
let too_short = STANDARD.encode([0u8; 16]);
assert!(parse_signing_key(&too_short).is_err());
}
#[test]
fn test_parse_verifying_key_invalid_base64() {
assert!(parse_verifying_key("not-valid-base64!!!").is_err());
}
#[test]
fn test_parse_verifying_key_wrong_length() {
let too_short = STANDARD.encode([0u8; 16]);
assert!(parse_verifying_key(&too_short).is_err());
}
#[test]
fn test_sign_and_verify() {
let (_, sk_b64, pk_b64) = make_keypair(1);
let sk = parse_signing_key(&sk_b64).unwrap();
let vk = parse_verifying_key(&pk_b64).unwrap();
let payload = b"hello bartos";
let signed = sign_payload(&sk, payload);
assert_eq!(signed.len(), 64 + payload.len());
let extracted = verify_and_extract(&vk, &signed).unwrap();
assert_eq!(extracted, payload);
}
#[test]
fn test_verify_wrong_key() {
let (_, sk_b64, _) = make_keypair(1);
let (_, _, pk_b64_other) = make_keypair(2); let sk = parse_signing_key(&sk_b64).unwrap();
let vk_wrong = parse_verifying_key(&pk_b64_other).unwrap();
let signed = sign_payload(&sk, b"payload");
assert!(verify_and_extract(&vk_wrong, &signed).is_err());
}
#[test]
fn test_verify_tampered_payload() {
let (_, sk_b64, pk_b64) = make_keypair(1);
let sk = parse_signing_key(&sk_b64).unwrap();
let vk = parse_verifying_key(&pk_b64).unwrap();
let mut signed = sign_payload(&sk, b"original payload");
let last = signed.len() - 1;
signed[last] ^= 0xFF;
assert!(verify_and_extract(&vk, &signed).is_err());
}
#[test]
fn test_verify_too_short() {
let (_, _, pk_b64) = make_keypair(1);
let vk = parse_verifying_key(&pk_b64).unwrap();
assert!(verify_and_extract(&vk, &[0u8; 32]).is_err());
}
}