ts_crypto/
elliptic.rs

1//! Elliptic curve keys
2//!
3
4use digest::{array::Array, typenum::Unsigned};
5use elliptic_curve::point::AffineCoordinates;
6use p256::NistP256;
7use p384::NistP384;
8use p521::NistP521;
9use sha2::{Digest as _, Sha256};
10use signature::hazmat::{PrehashVerifier, RandomizedPrehashSigner};
11
12use crate::Digest;
13
14/// An elliptic curve verifying key.
15pub enum EllipticVerifyingKey {
16    /// Using the P256 curve.
17    Prime256(p256::ecdsa::VerifyingKey),
18    /// Using the P384 curve.
19    Prime384(p384::ecdsa::VerifyingKey),
20    /// Using the P521 curve.
21    Prime521(p521::ecdsa::VerifyingKey),
22}
23
24/// An elliptic curve signing key.
25pub enum EllipticSigningKey {
26    /// Using the P256 curve.
27    Prime256(p256::ecdsa::SigningKey),
28    /// Using the P384 curve.
29    Prime384(p384::ecdsa::SigningKey),
30    /// Using the P521 curve.
31    Prime521(p521::ecdsa::SigningKey),
32}
33
34impl EllipticVerifyingKey {
35    /// Get the x coordinate bytes.
36    pub fn x(&self) -> Vec<u8> {
37        match self {
38            Self::Prime256(key) => key.as_affine().x().to_vec(),
39            Self::Prime384(key) => key.as_affine().x().to_vec(),
40            Self::Prime521(key) => key.as_affine().x().to_vec(),
41        }
42    }
43
44    /// Get the y coordinate bytes.
45    pub fn y(&self) -> Vec<u8> {
46        match self {
47            Self::Prime256(key) => key.as_affine().y().to_vec(),
48            Self::Prime384(key) => key.as_affine().y().to_vec(),
49            Self::Prime521(key) => key.as_affine().y().to_vec(),
50        }
51    }
52
53    /// Returns if this key verifies the signature to match the message.
54    pub fn verifies<D: Digest>(&self, signature: &[u8], message: &[u8]) -> bool {
55        match &self {
56            Self::Prime256(key) => {
57                let Ok(signature) = p256::ecdsa::Signature::from_slice(signature)
58                    .or_else(|_| p256::ecdsa::Signature::from_der(signature))
59                else {
60                    return false;
61                };
62                let digest = D::digest(message);
63                key.verify_prehash(&digest, &signature).is_ok()
64            }
65            Self::Prime384(key) => {
66                let Ok(signature) = p384::ecdsa::Signature::from_slice(signature)
67                    .or_else(|_| p384::ecdsa::Signature::from_der(signature))
68                else {
69                    return false;
70                };
71                let digest = D::digest(message);
72                key.verify_prehash(&digest, &signature).is_ok()
73            }
74            Self::Prime521(key) => {
75                let Ok(signature) = p521::ecdsa::Signature::from_slice(signature)
76                    .or_else(|_| p521::ecdsa::Signature::from_der(signature))
77                else {
78                    return false;
79                };
80                let digest = D::digest(message);
81
82                key.verify_prehash(&digest, &signature).is_ok()
83            }
84        }
85    }
86
87    /// Create an elliptic key from its coordinates.
88    pub fn from_coordinates(x: &[u8], y: &[u8]) -> Option<Self> {
89        if x.len() != y.len() {
90            return None;
91        }
92        match x.len() {
93            <NistP256 as elliptic_curve::Curve>::FieldBytesSize::USIZE => {
94                let x = Array::try_from(x).ok()?;
95                let y = Array::try_from(y).ok()?;
96                let point = p256::AffinePoint::from_coordinates(&x, &y).into_option()?;
97                let key = p256::ecdsa::VerifyingKey::from_affine(point).ok()?;
98                Some(Self::Prime256(key))
99            }
100            <NistP384 as elliptic_curve::Curve>::FieldBytesSize::USIZE => {
101                let x = Array::try_from(x).ok()?;
102                let y = Array::try_from(y).ok()?;
103                let point = p384::AffinePoint::from_coordinates(&x, &y).into_option()?;
104                let key = p384::ecdsa::VerifyingKey::from_affine(point).ok()?;
105                Some(Self::Prime384(key))
106            }
107            <NistP521 as elliptic_curve::Curve>::FieldBytesSize::USIZE => {
108                let x = Array::try_from(x).ok()?;
109                let y = Array::try_from(y).ok()?;
110                let point = p521::AffinePoint::from_coordinates(&x, &y).into_option()?;
111                let key = p521::ecdsa::VerifyingKey::from_affine(point).ok()?;
112                Some(Self::Prime521(key))
113            }
114            _ => None,
115        }
116    }
117
118    /// Returns the ID for this key.
119    pub fn key_id(&self) -> Vec<u8> {
120        let x = self.x();
121        let y = self.y();
122        let digest = Sha256::new().chain_update(x).chain_update(y).finalize();
123        digest.to_vec()
124    }
125}
126
127impl EllipticSigningKey {
128    /// Sign the message using this key and the digest algorithm.
129    ///
130    /// ## Panics
131    /// * If the digest algorithm produces a digest with a length that is less than half the field size of the curve.
132    /// * Any other errors occur during signing.
133    pub fn sign<D: Digest>(&self, message: &[u8]) -> Vec<u8> {
134        let digest = D::digest(message);
135        match &self {
136            Self::Prime256(key) => {
137                let signature: p256::ecdsa::Signature =
138                    key.sign_prehash_with_rng(&mut rand::rng(), &digest).expect(
139                        "the digest algorithm should produce a hash that is valid for this curve",
140                    );
141                signature.to_vec()
142            }
143            Self::Prime384(key) => {
144                let signature: p384::ecdsa::Signature =
145                    key.sign_prehash_with_rng(&mut rand::rng(), &digest).expect(
146                        "the digest algorithm should produce a hash that is valid for this curve",
147                    );
148                signature.to_vec()
149            }
150            Self::Prime521(key) => {
151                let signature: p521::ecdsa::Signature =
152                    key.sign_prehash_with_rng(&mut rand::rng(), &digest).expect(
153                        "the digest algorithm should produce a hash that is valid for this curve",
154                    );
155                signature.to_vec()
156            }
157        }
158    }
159
160    /// Get the verifying key for this signing key
161    pub fn verifying_key(&self) -> EllipticVerifyingKey {
162        match &self {
163            Self::Prime256(key) => EllipticVerifyingKey::Prime256(*key.verifying_key()),
164            Self::Prime384(key) => EllipticVerifyingKey::Prime384(*key.verifying_key()),
165            Self::Prime521(key) => EllipticVerifyingKey::Prime521(*key.verifying_key()),
166        }
167    }
168}