ts-crypto 0.4.0

Cryptography abstraction for my projects
Documentation
//! Elliptic curve keys
//!

use digest::{array::Array, typenum::Unsigned};
use elliptic_curve::point::AffineCoordinates;
use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
use sha2::{Digest as _, Sha256};
use signature::hazmat::{PrehashVerifier, RandomizedPrehashSigner};

use crate::Digest;

/// An elliptic curve verifying key.
pub enum EllipticVerifyingKey {
    /// Using the P256 curve.
    Prime256(p256::ecdsa::VerifyingKey),
    /// Using the P384 curve.
    Prime384(p384::ecdsa::VerifyingKey),
    /// Using the P521 curve.
    Prime521(p521::ecdsa::VerifyingKey),
}

/// An elliptic curve signing key.
pub enum EllipticSigningKey {
    /// Using the P256 curve.
    Prime256(p256::ecdsa::SigningKey),
    /// Using the P384 curve.
    Prime384(p384::ecdsa::SigningKey),
    /// Using the P521 curve.
    Prime521(p521::ecdsa::SigningKey),
}

impl EllipticVerifyingKey {
    /// Get the x coordinate bytes.
    pub fn x(&self) -> Vec<u8> {
        match self {
            Self::Prime256(key) => key.as_affine().x().to_vec(),
            Self::Prime384(key) => key.as_affine().x().to_vec(),
            Self::Prime521(key) => key.as_affine().x().to_vec(),
        }
    }

    /// Get the y coordinate bytes.
    pub fn y(&self) -> Vec<u8> {
        match self {
            Self::Prime256(key) => key.as_affine().y().to_vec(),
            Self::Prime384(key) => key.as_affine().y().to_vec(),
            Self::Prime521(key) => key.as_affine().y().to_vec(),
        }
    }

    /// Returns if this key verifies the signature to match the message.
    pub fn verifies<D: Digest>(&self, signature: &[u8], message: &[u8]) -> bool {
        match &self {
            Self::Prime256(key) => {
                let Ok(signature) = p256::ecdsa::Signature::from_slice(signature)
                    .or_else(|_| p256::ecdsa::Signature::from_der(signature))
                else {
                    return false;
                };
                let digest = D::digest(message);
                key.verify_prehash(&digest, &signature).is_ok()
            }
            Self::Prime384(key) => {
                let Ok(signature) = p384::ecdsa::Signature::from_slice(signature)
                    .or_else(|_| p384::ecdsa::Signature::from_der(signature))
                else {
                    return false;
                };
                let digest = D::digest(message);
                key.verify_prehash(&digest, &signature).is_ok()
            }
            Self::Prime521(key) => {
                let Ok(signature) = p521::ecdsa::Signature::from_slice(signature)
                    .or_else(|_| p521::ecdsa::Signature::from_der(signature))
                else {
                    return false;
                };
                let digest = D::digest(message);

                key.verify_prehash(&digest, &signature).is_ok()
            }
        }
    }

    /// Create an elliptic key from its coordinates.
    pub fn from_coordinates(x: &[u8], y: &[u8]) -> Option<Self> {
        if x.len() != y.len() {
            return None;
        }
        match x.len() {
            <NistP256 as elliptic_curve::Curve>::FieldBytesSize::USIZE => {
                let x = Array::try_from(x).ok()?;
                let y = Array::try_from(y).ok()?;
                let point = p256::AffinePoint::from_coordinates(&x, &y).into_option()?;
                let key = p256::ecdsa::VerifyingKey::from_affine(point).ok()?;
                Some(Self::Prime256(key))
            }
            <NistP384 as elliptic_curve::Curve>::FieldBytesSize::USIZE => {
                let x = Array::try_from(x).ok()?;
                let y = Array::try_from(y).ok()?;
                let point = p384::AffinePoint::from_coordinates(&x, &y).into_option()?;
                let key = p384::ecdsa::VerifyingKey::from_affine(point).ok()?;
                Some(Self::Prime384(key))
            }
            <NistP521 as elliptic_curve::Curve>::FieldBytesSize::USIZE => {
                let x = Array::try_from(x).ok()?;
                let y = Array::try_from(y).ok()?;
                let point = p521::AffinePoint::from_coordinates(&x, &y).into_option()?;
                let key = p521::ecdsa::VerifyingKey::from_affine(point).ok()?;
                Some(Self::Prime521(key))
            }
            _ => None,
        }
    }

    /// Returns the ID for this key.
    pub fn key_id(&self) -> Vec<u8> {
        let x = self.x();
        let y = self.y();
        let digest = Sha256::new().chain_update(x).chain_update(y).finalize();
        digest.to_vec()
    }
}

impl EllipticSigningKey {
    /// Sign the message using this key and the digest algorithm.
    ///
    /// ## Panics
    /// * If the digest algorithm produces a digest with a length that is less than half the field size of the curve.
    /// * Any other errors occur during signing.
    pub fn sign<D: Digest>(&self, message: &[u8]) -> Vec<u8> {
        let digest = D::digest(message);
        match &self {
            Self::Prime256(key) => {
                let signature: p256::ecdsa::Signature =
                    key.sign_prehash_with_rng(&mut rand::rng(), &digest).expect(
                        "the digest algorithm should produce a hash that is valid for this curve",
                    );
                signature.to_vec()
            }
            Self::Prime384(key) => {
                let signature: p384::ecdsa::Signature =
                    key.sign_prehash_with_rng(&mut rand::rng(), &digest).expect(
                        "the digest algorithm should produce a hash that is valid for this curve",
                    );
                signature.to_vec()
            }
            Self::Prime521(key) => {
                let signature: p521::ecdsa::Signature =
                    key.sign_prehash_with_rng(&mut rand::rng(), &digest).expect(
                        "the digest algorithm should produce a hash that is valid for this curve",
                    );
                signature.to_vec()
            }
        }
    }

    /// Get the verifying key for this signing key
    pub fn verifying_key(&self) -> EllipticVerifyingKey {
        match &self {
            Self::Prime256(key) => EllipticVerifyingKey::Prime256(*key.verifying_key()),
            Self::Prime384(key) => EllipticVerifyingKey::Prime384(*key.verifying_key()),
            Self::Prime521(key) => EllipticVerifyingKey::Prime521(*key.verifying_key()),
        }
    }
}