roughtime 0.1.0

A no_std-capable Roughtime secure time-sync client with pluggable crypto backends
Documentation
//! The `rustcrypto` backend: pure-Rust Ed25519 (via `ed25519-dalek`) and SHA-512 (via `sha2`).
#![allow(clippy::redundant_pub_crate)]

use ed25519_dalek::Verifier as _;
use ed25519_dalek::{Signature, VerifyingKey};
use sha2::{Digest as _, Sha512};

use super::CryptoError;

pub(crate) fn verify_ed25519(
    public_key: &[u8],
    message: &[u8],
    signature: &[u8],
) -> Result<(), CryptoError> {
    let key_bytes: [u8; 32] = public_key.try_into().map_err(|_err| CryptoError::InvalidKey)?;
    let sig_bytes: [u8; 64] = signature
        .try_into()
        .map_err(|_err| CryptoError::InvalidSignature)?;

    let verifying_key =
        VerifyingKey::from_bytes(&key_bytes).map_err(|_err| CryptoError::InvalidKey)?;
    let signature = Signature::from_bytes(&sig_bytes);

    verifying_key
        .verify(message, &signature)
        .map_err(|_err| CryptoError::InvalidSignature)
}

pub(crate) fn sha512(data: &[u8]) -> [u8; 64] {
    let mut hasher = Sha512::new();
    hasher.update(data);
    hasher.finalize().into()
}

#[cfg(feature = "std")]
pub(crate) fn fill_random(buf: &mut [u8]) -> Result<(), CryptoError> {
    getrandom::fill(buf).map_err(|_err| CryptoError::RandomUnavailable)
}

#[cfg(test)]
mod tests {
    #![allow(clippy::unwrap_used)]

    use ed25519_dalek::{Signer as _, SigningKey};

    use super::{sha512, verify_ed25519};

    #[test]
    fn verifies_a_valid_signature() {
        let key = SigningKey::from_bytes(&[7u8; 32]);
        let msg = b"hello roughtime";
        let sig = key.sign(msg);

        verify_ed25519(key.verifying_key().as_bytes(), msg, &sig.to_bytes()).unwrap();
    }

    #[test]
    fn rejects_a_tampered_message() {
        let key = SigningKey::from_bytes(&[7u8; 32]);
        let sig = key.sign(b"hello roughtime");

        assert!(
            verify_ed25519(key.verifying_key().as_bytes(), b"goodbye roughtime", &sig.to_bytes())
                .is_err()
        );
    }

    #[test]
    fn rejects_malformed_key_or_signature() {
        assert!(verify_ed25519(&[0u8; 4], b"msg", &[0u8; 64]).is_err());
        assert!(verify_ed25519(&[0u8; 32], b"msg", &[0u8; 4]).is_err());
    }

    #[test]
    fn sha512_matches_known_vector() {
        // NIST/RFC test vector: SHA-512("abc").
        let digest = sha512(b"abc");
        let expected = [
            0xdd, 0xaf, 0x35, 0xa1, 0x93, 0x61, 0x7a, 0xba, 0xcc, 0x41, 0x73, 0x49, 0xae, 0x20,
            0x41, 0x31, 0x12, 0xe6, 0xfa, 0x4e, 0x89, 0xa9, 0x7e, 0xa2, 0x0a, 0x9e, 0xee, 0xe6,
            0x4b, 0x55, 0xd3, 0x9a, 0x21, 0x92, 0x99, 0x2a, 0x27, 0x4f, 0xc1, 0xa8, 0x36, 0xba,
            0x3c, 0x23, 0xa3, 0xfe, 0xeb, 0xbd, 0x45, 0x4d, 0x44, 0x23, 0x64, 0x3c, 0xe8, 0x0e,
            0x2a, 0x9a, 0xc9, 0x4f, 0xa5, 0x4c, 0xa4, 0x9f,
        ];
        assert_eq!(digest, expected);
    }
}