conundrum 0.1.0

Hard-to-misuse crypto primitives with purpose scoping.
Documentation
use ed25519::signature::Signer;
use litl::{Litl, impl_debug_as_litl};
use rand07::rngs::OsRng;
use serde_derive::{Serialize, Deserialize};
use serde::{Serialize};
use std::{hash::Hash, ops::Deref, fmt::Debug};

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename = "Conundrum/Signature:Ed25519Dalek")]
pub struct RawSignature(
    #[serde(with = "serde_bytes")]
    ed25519::Signature
);

impl_debug_as_litl!(RawSignature);

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

impl Ord for RawSignature {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.0.as_ref().cmp(other.0.as_ref())
    }
}

impl PartialOrd for RawSignature {
    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, S> {
    pub verified: T,
    pub signature: RawSignature,
    pub purpose: S,
}

impl <T: Serialize, S: Serialize> Debug for Signed<T, S> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        Litl::from_se(self).fmt(f)
    }
}

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

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

pub type SignatureError = ed25519_dalek::SignatureError;

impl<T: Clone + Serialize, S> Signed<T, S> {
    pub fn ensure_signed_by(&self, id: &SignPublicKey<S>) -> Result<(), SignatureError> {
        id.key
            .0
            .verify_strict(&Litl::write_from(self.verified.clone()), &self.signature.0)
    }
}

#[derive(Copy, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename = "Conundrum/SignPubKey:Ed25519Dalek")]
struct RawSignPublicKey(ed25519_dalek::PublicKey);

#[allow(clippy::derive_hash_xor_eq)]
impl Hash for RawSignPublicKey {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.0.as_bytes().hash(state);
    }
}

#[derive(Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SignPublicKey<S> {
    key: RawSignPublicKey,
    purpose: S,
}

impl<S: Serialize> Debug for SignPublicKey<S> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        Litl::from_se(self).fmt(f)
    }
}

#[derive(Serialize, Deserialize)]
#[serde(rename = "Conundrum/SignKeyPair:Ed25519Dalek")]
pub struct RawSignKeyPair(ed25519_dalek::Keypair);

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

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

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

#[derive(Serialize, Deserialize)]
pub struct SignKeyPair<S> {
    keypair: RawSignKeyPair,
    purpose: S,
}

impl <S> Deref for SignKeyPair<S> {
    type Target = RawSignKeyPair;

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

impl<S: Clone + Default> SignKeyPair<S> {
    pub fn new_random() -> Self {
        SignKeyPair {
            keypair: RawSignKeyPair::new_random(),
            purpose: S::default(),
        }
    }

    pub fn sign<T: Clone + Serialize>(&self, data: T) -> Signed<T, S> {
        let signature = RawSignature(self.keypair.sign(&Litl::write_from(data.clone())));
        Signed {
            verified: data,
            signature,
            purpose: self.purpose.clone(),
        }
    }

    pub fn public(&self) -> SignPublicKey<S> {
        SignPublicKey {
            key: RawSignPublicKey(self.keypair.public),
            purpose: self.purpose.clone(),
        }
    }
}

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

    use crate::{purpose, signing::SignKeyPair};

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

    purpose!(TestPurpose);

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

        let keypair = SignKeyPair::<TestPurpose>::new_random();
        let signed = keypair.sign(data);

        println!("{:?}", Litl::from_se(&signed));

        assert!(signed.ensure_signed_by(&keypair.public()).is_ok());
    }
}