jwt_compact/alg/
es256k.rs

1//! `ES256K` algorithm implementation using the `secp256k1` crate.
2
3use lazy_static::lazy_static;
4use secp256k1::{
5    constants::{
6        COMPACT_SIGNATURE_SIZE, FIELD_SIZE, SECRET_KEY_SIZE, UNCOMPRESSED_PUBLIC_KEY_SIZE,
7    },
8    ecdsa::Signature,
9    All, Message, PublicKey, Secp256k1, SecretKey,
10};
11use sha2::{
12    digest::{
13        crypto_common::BlockSizeUser, generic_array::typenum::U32, FixedOutputReset, HashMarker,
14    },
15    Digest, Sha256,
16};
17
18use core::{marker::PhantomData, num::NonZeroUsize};
19
20use crate::{
21    alg::{SecretBytes, SigningKey, VerifyingKey},
22    alloc::Cow,
23    jwk::{JsonWebKey, JwkError, KeyType},
24    Algorithm, AlgorithmSignature,
25};
26
27/// Byte size of a serialized EC coordinate.
28const COORDINATE_SIZE: usize = FIELD_SIZE.len();
29
30impl AlgorithmSignature for Signature {
31    const LENGTH: Option<NonZeroUsize> = NonZeroUsize::new(COMPACT_SIGNATURE_SIZE);
32
33    fn try_from_slice(slice: &[u8]) -> anyhow::Result<Self> {
34        Signature::from_compact(slice).map_err(Into::into)
35    }
36
37    fn as_bytes(&self) -> Cow<'_, [u8]> {
38        Cow::Owned(self.serialize_compact()[..].to_vec())
39    }
40}
41
42/// Algorithm implementing elliptic curve digital signatures (ECDSA) on the secp256k1 curve.
43///
44/// The algorithm does not fix the choice of the message digest algorithm; instead,
45/// it is provided as a type parameter. SHA-256 is the default parameter value,
46/// but it can be set to any cryptographically secure hash function with 32-byte output
47/// (e.g., SHA3-256).
48#[derive(Debug)]
49#[cfg_attr(docsrs, doc(cfg(any(feature = "es256k", feature = "k256"))))]
50pub struct Es256k<D = Sha256> {
51    context: Secp256k1<All>,
52    _digest: PhantomData<D>,
53}
54
55impl<D> Default for Es256k<D>
56where
57    D: FixedOutputReset<OutputSize = U32> + BlockSizeUser + Clone + Default + HashMarker,
58{
59    fn default() -> Self {
60        Es256k {
61            context: Secp256k1::new(),
62            _digest: PhantomData,
63        }
64    }
65}
66
67impl<D> Es256k<D>
68where
69    D: FixedOutputReset<OutputSize = U32> + BlockSizeUser + Clone + Default + HashMarker,
70{
71    /// Creates a new algorithm instance.
72    /// This is a (moderately) expensive operation, so if necessary, the algorithm should
73    /// be `clone()`d rather than created anew.
74    #[cfg_attr(docsrs, doc(cfg(feature = "es256k")))]
75    pub fn new(context: Secp256k1<All>) -> Self {
76        Es256k {
77            context,
78            _digest: PhantomData,
79        }
80    }
81}
82
83impl<D> Algorithm for Es256k<D>
84where
85    D: FixedOutputReset<OutputSize = U32> + BlockSizeUser + Clone + Default + HashMarker,
86{
87    type SigningKey = SecretKey;
88    type VerifyingKey = PublicKey;
89    type Signature = Signature;
90
91    fn name(&self) -> Cow<'static, str> {
92        Cow::Borrowed("ES256K")
93    }
94
95    fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature {
96        let mut digest = D::default();
97        digest.update(message);
98        let message = Message::from_digest(digest.finalize().into());
99
100        self.context.sign_ecdsa(&message, signing_key)
101    }
102
103    fn verify_signature(
104        &self,
105        signature: &Self::Signature,
106        verifying_key: &Self::VerifyingKey,
107        message: &[u8],
108    ) -> bool {
109        let mut digest = D::default();
110        digest.update(message);
111        let message = Message::from_digest(digest.finalize().into());
112
113        // Some implementations (e.g., OpenSSL) produce high-S signatures, which
114        // are considered invalid by this implementation. Hence, we perform normalization here.
115        //
116        // See also: https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki
117        let mut normalized_signature = *signature;
118        normalized_signature.normalize_s();
119
120        self.context
121            .verify_ecdsa(&message, &normalized_signature, verifying_key)
122            .is_ok()
123    }
124}
125
126/// This implementation initializes a `libsecp256k1` context once on the first call to
127/// `to_verifying_key` if it was not initialized previously.
128impl SigningKey<Es256k> for SecretKey {
129    fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
130        Self::from_slice(raw).map_err(From::from)
131    }
132
133    fn to_verifying_key(&self) -> PublicKey {
134        lazy_static! {
135            static ref CONTEXT: Secp256k1<All> = Secp256k1::new();
136        }
137        PublicKey::from_secret_key(&CONTEXT, self)
138    }
139
140    fn as_bytes(&self) -> SecretBytes<'_> {
141        SecretBytes::borrowed(&self[..])
142    }
143}
144
145impl VerifyingKey<Es256k> for PublicKey {
146    fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
147        Self::from_slice(raw).map_err(From::from)
148    }
149
150    /// Serializes the key as a 33-byte compressed form, as per [`Self::serialize()`].
151    fn as_bytes(&self) -> Cow<'_, [u8]> {
152        Cow::Owned(self.serialize().to_vec())
153    }
154}
155
156fn create_jwk<'a>(pk: &PublicKey, sk: Option<&'a SecretKey>) -> JsonWebKey<'a> {
157    let uncompressed = pk.serialize_uncompressed();
158    JsonWebKey::EllipticCurve {
159        curve: "secp256k1".into(),
160        x: Cow::Owned(uncompressed[1..=COORDINATE_SIZE].to_vec()),
161        y: Cow::Owned(uncompressed[(1 + COORDINATE_SIZE)..].to_vec()),
162        secret: sk.map(|sk| SecretBytes::borrowed(&sk.as_ref()[..])),
163    }
164}
165
166impl<'a> From<&'a PublicKey> for JsonWebKey<'a> {
167    fn from(key: &'a PublicKey) -> JsonWebKey<'a> {
168        create_jwk(key, None)
169    }
170}
171
172impl TryFrom<&JsonWebKey<'_>> for PublicKey {
173    type Error = JwkError;
174
175    fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
176        let JsonWebKey::EllipticCurve { curve, x, y, .. } = jwk else {
177            return Err(JwkError::key_type(jwk, KeyType::EllipticCurve));
178        };
179        JsonWebKey::ensure_curve(curve, "secp256k1")?;
180        JsonWebKey::ensure_len("x", x, COORDINATE_SIZE)?;
181        JsonWebKey::ensure_len("y", y, COORDINATE_SIZE)?;
182
183        let mut key_bytes = [0_u8; UNCOMPRESSED_PUBLIC_KEY_SIZE];
184        key_bytes[0] = 4; // uncompressed key marker
185        key_bytes[1..=COORDINATE_SIZE].copy_from_slice(x);
186        key_bytes[(1 + COORDINATE_SIZE)..].copy_from_slice(y);
187        PublicKey::from_slice(&key_bytes[..]).map_err(JwkError::custom)
188    }
189}
190
191impl<'a> From<&'a SecretKey> for JsonWebKey<'a> {
192    fn from(key: &'a SecretKey) -> JsonWebKey<'a> {
193        create_jwk(&key.to_verifying_key(), Some(key))
194    }
195}
196
197impl TryFrom<&JsonWebKey<'_>> for SecretKey {
198    type Error = JwkError;
199
200    fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
201        let JsonWebKey::EllipticCurve { secret, .. } = jwk else {
202            return Err(JwkError::key_type(jwk, KeyType::EllipticCurve));
203        };
204        let sk_bytes = secret.as_deref();
205        let sk_bytes = sk_bytes.ok_or_else(|| JwkError::NoField("d".into()))?;
206        JsonWebKey::ensure_len("d", sk_bytes, SECRET_KEY_SIZE)?;
207
208        let sk = SecretKey::from_slice(sk_bytes).map_err(JwkError::custom)?;
209        jwk.ensure_key_match(sk)
210    }
211}