ratchetx2 0.3.8

A double-ratchet implementation for building an E2EE message exchange app.
Documentation
//! [XEdDSA](https://signal.org/docs/specifications/xeddsa/) enables use of a single key pair format for both elliptic curve Diffie-Hellman and signatures.
//!
//! Based on Ed25519 and X25519.
//!
//! # Example
//! ```
//! use ratchetx2::xeddsa::XEdDSAPrivateKey;
//! use ratchetx2::rand::SystemRandom;
//!
//! let xeddsa = XEdDSAPrivateKey::generate(&SystemRandom::new());
//! let signature = xeddsa.sign("hello world");
//! let public_key = xeddsa.compute_public_key();
//! public_key.verify("hello world", &signature).unwrap();
//! assert!(public_key.verify("goodbye world", &signature).is_err());
//! let alice = XEdDSAPrivateKey::generate(&SystemRandom::new());
//! let bob = XEdDSAPrivateKey::generate(&SystemRandom::new());
//! assert_eq!(
//!     alice.agree_ephemeral(bob.compute_public_key().as_ref()).unwrap(),
//!     bob.agree_ephemeral(alice.compute_public_key().as_ref()).unwrap()
//! );
//! ```

use crate::error::{Error, Result};

use curve25519_dalek::{EdwardsPoint, MontgomeryPoint, Scalar};
use ring::digest::{SHA512, digest};
use ring::rand::{SecureRandom, SystemRandom, generate};
use zeroize::{Zeroize, ZeroizeOnDrop};

/// XEdDSA private key.
#[derive(Debug, Zeroize, ZeroizeOnDrop)]
pub struct XEdDSAPrivateKey {
    montgomery_private_key: Scalar,
}

impl From<Scalar> for XEdDSAPrivateKey {
    /// New a XEdDSAPrivateKey with give Scalar (private key).
    fn from(montgomery_private_key: Scalar) -> Self {
        XEdDSAPrivateKey {
            montgomery_private_key,
        }
    }
}

impl XEdDSAPrivateKey {
    /// Generate a new XEdDSA private key.
    pub fn generate(rng: &impl SecureRandom) -> Self {
        XEdDSAPrivateKey {
            montgomery_private_key: Scalar::from_bytes_mod_order(generate(rng).unwrap().expose()),
        }
    }

    /// The XEdDSA public key for the key pair.
    pub fn compute_public_key(&self) -> XEdDSAPublicKey {
        XEdDSAPublicKey {
            montgomery_public_key: MontgomeryPoint::mul_base(&self.montgomery_private_key),
        }
    }

    /// DH with the X25519(Montgomery) private key for the key pair.
    pub fn agree_ephemeral(&self, peer_public_key: &[u8]) -> Result<Vec<u8>> {
        Ok((self.montgomery_private_key
            * MontgomeryPoint(
                peer_public_key
                    .as_ref()
                    .try_into()
                    .map_err(|_| Error::Failed("Invalid DH public key.".to_string()))?,
            ))
        .as_bytes()
        .to_vec())
    }

    /// Sign with the Ed25519(Edwards) private key for the key pair.
    pub fn sign(&self, message: impl AsRef<[u8]>) -> Vec<u8> {
        let mut edwards_private_key = self.montgomery_private_key;
        let edwards_public_key = EdwardsPoint::mul_base(&edwards_private_key);
        let mut edwards_public_key_y = edwards_public_key.compress().to_bytes();
        if edwards_public_key_y[31] >= 0x80 {
            edwards_private_key = -self.montgomery_private_key;
            let edwards_public_key = EdwardsPoint::mul_base(&edwards_private_key);
            edwards_public_key_y = edwards_public_key.compress().to_bytes();
        }
        let mut to_digest = vec![0xFF; 32];
        to_digest.extend(edwards_private_key.as_bytes());
        to_digest.extend(message.as_ref());
        to_digest.extend(generate::<[u8; 64]>(&SystemRandom::new()).unwrap().expose());
        let r = Scalar::from_bytes_mod_order_wide(
            &digest(&SHA512, &to_digest).as_ref().try_into().unwrap(),
        );
        let r_ = EdwardsPoint::mul_base(&r);
        let mut to_digest = r_.compress().as_bytes().to_vec();
        to_digest.extend(edwards_public_key_y);
        to_digest.extend(message.as_ref());
        let h = Scalar::from_bytes_mod_order_wide(
            &digest(&SHA512, &to_digest).as_ref().try_into().unwrap(),
        );
        let s = r + h * edwards_private_key;
        let mut res = r_.compress().as_bytes().to_vec();
        res.extend(s.as_bytes());
        res
    }
}

/// XEdDSA public key.
#[derive(Debug)]
pub struct XEdDSAPublicKey {
    montgomery_public_key: MontgomeryPoint,
}

impl XEdDSAPublicKey {
    /// Create a new XEdDSA public key from bytes.
    pub fn new(bytes: &[u8]) -> Self {
        XEdDSAPublicKey {
            montgomery_public_key: MontgomeryPoint(bytes.try_into().unwrap()),
        }
    }

    /// Verify with the Ed25519(Edwards) public key for the key pair.
    pub fn verify(&self, message: impl AsRef<[u8]>, signature: &[u8]) -> Result<()> {
        if signature.len() != 64 {
            return Err(Error::Signature);
        }
        let edwards_public_key = self
            .montgomery_public_key
            .to_edwards(0)
            .ok_or(Error::Signature)?;
        // Use ring's implementation of Ed25519 verification.
        ring::signature::UnparsedPublicKey::new(
            &ring::signature::ED25519,
            edwards_public_key.compress().as_bytes(),
        )
        .verify(message.as_ref(), signature)
        .map_err(|_| Error::Signature)?;
        // // Below is manual verification.
        // let r_check = curve25519_dalek::edwards::CompressedEdwardsY::from_slice(&signature[..32])
        //     .map_err(|_| Error::Signature)?
        //     .decompress()
        //     .ok_or(Error::Signature)?;
        // let mut to_digest = r_check.compress().as_bytes().to_vec();
        // to_digest.extend(edwards_public_key.compress().as_bytes());
        // to_digest.extend(message);
        // let h = Scalar::from_bytes_mod_order_wide(
        //     digest(&SHA512, &to_digest).as_ref().try_into().unwrap(),
        // );
        // let s = Scalar::from_bytes_mod_order(signature[32..].try_into().unwrap());
        // // -h * edwards_public_key + s * basepoint == r_check
        // if EdwardsPoint::vartime_double_scalar_mul_basepoint(&-h, &edwards_public_key, &s)
        //     != r_check
        // {
        //     return Err(Error::Signature);
        // }
        Ok(())
    }
}

impl AsRef<[u8]> for XEdDSAPublicKey {
    fn as_ref(&self) -> &[u8] {
        self.montgomery_public_key.as_bytes()
    }
}