ridl 0.6.1

Hard-to-misuse, modern crypto primitives and conventions for litl.
Documentation
use ed25519::signature::Signer;
use litl::{impl_debug_as_litl, impl_single_tagged_data_serde, SingleTaggedData, TaggedDataError};
use rand07::rngs::OsRng;
use serde::Serialize;
use serde_derive::{Deserialize, Serialize};
use std::{borrow::Cow, fmt::Debug, hash::Hash, ops::Deref};
use thiserror::Error;

#[derive(Clone, PartialEq, Eq)]
pub enum Signature {
    SignatureV1(ed25519::Signature),
}

impl SingleTaggedData for Signature {
    const TAG: &'static str = "signatureV1";

    fn as_bytes(&self) -> Cow<[u8]> {
        match self {
            Signature::SignatureV1(sig) => Cow::from(sig.as_ref()),
        }
    }

    fn from_bytes(data: &[u8]) -> Result<Self, TaggedDataError>
    where
        Self: Sized,
    {
        let sig_bytes: [u8; 64] = data
            .try_into()
            .map_err(|err| TaggedDataError::data_error(Into::<SignatureError>::into(err)))?;
        Ok(Signature::SignatureV1(ed25519::Signature::from(sig_bytes)))
    }
}

impl_single_tagged_data_serde!(Signature);
impl_debug_as_litl!(Signature);

#[allow(clippy::derive_hash_xor_eq)]
impl Hash for Signature {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        match self {
            Signature::SignatureV1(sig) => sig.to_bytes().hash(state),
        }
    }
}

impl Ord for Signature {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        match (self, other) {
            (Signature::SignatureV1(sig1), Signature::SignatureV1(sig2)) => {
                sig1.as_ref().cmp(sig2.as_ref())
            }
        }
    }
}

impl PartialOrd for Signature {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

#[derive(PartialEq, Eq, Serialize, Deserialize, Hash, Clone, Ord, PartialOrd)]
pub struct Signed<T> {
    pub attested: T,
    pub signature: Signature,
}

impl<T: Serialize> Debug for Signed<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&litl::to_string_pretty(&self).unwrap())
    }
}

impl<T> Deref for Signed<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.attested
    }
}

#[derive(Debug, Error)]
pub enum SignatureError {
    #[error(transparent)]
    WrongSignature(#[from] ed25519_dalek::SignatureError),
    #[error("Invalid Signature length")]
    InvalidLength(#[from] std::array::TryFromSliceError),
}

impl<T: Clone + Serialize> Signed<T> {
    pub fn ensure_signed_by(&self, id: &SignerID) -> Result<(), SignatureError> {
        match (id, &self.signature) {
            (SignerID::SignerV1(pub_key), Signature::SignatureV1(signature)) => Ok(pub_key
                .verify_strict(&litl::to_vec_canonical(&self.attested).unwrap(), signature)?),
        }
    }
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub enum SignerID {
    SignerV1(ed25519_dalek::PublicKey),
}

impl SingleTaggedData for SignerID {
    const TAG: &'static str = "signerV1";

    fn as_bytes(&self) -> Cow<[u8]> {
        match self {
            SignerID::SignerV1(pub_key) => Cow::from(pub_key.as_bytes().as_ref()),
        }
    }

    fn from_bytes(data: &[u8]) -> Result<Self, TaggedDataError>
    where
        Self: Sized,
    {
        Ok(SignerID::SignerV1(
            ed25519_dalek::PublicKey::from_bytes(data)
                .map_err(Into::<SignerIDError>::into)
                .map_err(TaggedDataError::data_error)?,
        ))
    }
}

impl_single_tagged_data_serde!(SignerID);
impl_debug_as_litl!(SignerID);

#[derive(Debug, Error)]
pub enum SignerIDError {
    #[error("Invalid SignedID length {0}")]
    InvalidLength(#[from] ed25519_dalek::SignatureError),
}

#[allow(clippy::derive_hash_xor_eq)]
impl Hash for SignerID {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        match self {
            SignerID::SignerV1(pub_key) => pub_key.as_bytes().hash(state),
        }
    }
}

pub enum SignerSecret {
    SignerSecretV1(ed25519_dalek::Keypair),
}

impl SingleTaggedData for SignerSecret {
    const TAG: &'static str = "signerSecretV1";

    fn as_bytes(&self) -> Cow<[u8]> {
        Cow::from(self.to_bytes().to_vec())
    }

    fn from_bytes(data: &[u8]) -> Result<Self, TaggedDataError>
    where
        Self: Sized,
    {
        Ok(SignerSecret::SignerSecretV1(
            ed25519_dalek::Keypair::from_bytes(data)
                .map_err(Into::<SignerSecretError>::into)
                .map_err(TaggedDataError::data_error)?,
        ))
    }
}

impl_single_tagged_data_serde!(SignerSecret);
impl_debug_as_litl!(SignerSecret);

#[derive(Debug, Error)]
pub enum SignerSecretError {
    #[error("Invalid SignerSecret length {0}")]
    InvalidLength(#[from] ed25519_dalek::SignatureError),
}

impl SignerSecret {
    pub fn new_random() -> Self {
        SignerSecret::SignerSecretV1(ed25519_dalek::Keypair::generate(&mut OsRng {}))
    }

    pub fn sign<T: Clone + Serialize>(&self, data: T) -> Signed<T> {
        let signature =
            Signature::SignatureV1(self.deref().sign(&litl::to_vec_canonical(&data).unwrap()));
        Signed {
            attested: data,
            signature,
        }
    }

    pub fn pub_id(&self) -> SignerID {
        match self {
            SignerSecret::SignerSecretV1(keypair) => SignerID::SignerV1(keypair.public),
        }
    }
}

impl Deref for SignerSecret {
    type Target = ed25519_dalek::Keypair;

    fn deref(&self) -> &Self::Target {
        match self {
            SignerSecret::SignerSecretV1(keypair) => keypair,
        }
    }
}

#[cfg(test)]
mod test {
    use serde_derive::{Deserialize, Serialize};

    use crate::signing::{SignerID, SignerSecret};

    #[derive(Clone, Serialize, Deserialize)]
    struct TestData {
        bla: [u8; 4],
    }

    #[test]
    fn signing_and_verifying_works() {
        let data = TestData { bla: [1, 2, 3, 4] };

        let signer = SignerSecret::new_random();
        let signed = signer.sign(data);

        println!("{}", litl::to_string_pretty(&signed).unwrap());

        assert!(signed.ensure_signed_by(&signer.pub_id()).is_ok());
    }

    #[test]
    fn signer_id_roundtrips_serialization() {
        let signer = SignerSecret::new_random();
        let signer_id = signer.pub_id();

        let serialized = litl::to_vec(&signer_id).unwrap();
        let deserialized: SignerID = litl::from_slice(&serialized).unwrap();

        assert_eq!(signer_id, deserialized);
    }

    #[test]
    fn serialization() {
        let data = TestData { bla: [1, 2, 3, 4] };

        let signer = SignerSecret::new_random();
        let _signed = signer.sign(data);

        assert!(
            litl::to_string(&signer.pub_id())
                .unwrap()
                .starts_with("\"signerV1_z"),
            "{:?}",
            litl::to_string(&signer.pub_id()).unwrap()
        );
        assert!(
            litl::to_string(&signer)
                .unwrap()
                .starts_with("\"signerSecretV1_z"),
            "{:?}",
            litl::to_string(&signer).unwrap()
        );
    }

    // TODO: add test for canoncical serialization
}