ecdsa/
hazmat.rs

1//! Low-level ECDSA primitives.
2//!
3//! # ⚠️ Warning: Hazmat!
4//!
5//! YOU PROBABLY DON'T WANT TO USE THESE!
6//!
7//! These primitives are easy-to-misuse low-level interfaces.
8//!
9//! If you are an end user / non-expert in cryptography, do not use these!
10//! Failure to use them correctly can lead to catastrophic failures including
11//! FULL PRIVATE KEY RECOVERY!
12
13#[cfg(feature = "arithmetic")]
14use {
15    crate::{RecoveryId, SignatureSize},
16    core::borrow::Borrow,
17    elliptic_curve::{
18        group::Curve as _,
19        ops::{Invert, LinearCombination, Reduce},
20        subtle::CtOption,
21        AffineArithmetic, AffineXCoordinate, Field, Group, ProjectiveArithmetic, ProjectivePoint,
22        Scalar, ScalarArithmetic,
23    },
24};
25
26#[cfg(feature = "digest")]
27use {
28    core::cmp,
29    elliptic_curve::{bigint::Encoding, FieldSize},
30    signature::{digest::Digest, PrehashSignature},
31};
32
33#[cfg(any(feature = "arithmetic", feature = "digest"))]
34use crate::{
35    elliptic_curve::{generic_array::ArrayLength, FieldBytes, PrimeCurve},
36    Error, Result, Signature,
37};
38
39#[cfg(all(feature = "arithmetic", feature = "digest"))]
40use signature::digest::FixedOutput;
41
42#[cfg(all(feature = "rfc6979"))]
43use {
44    elliptic_curve::ScalarCore,
45    signature::digest::{core_api::BlockSizeUser, FixedOutputReset},
46};
47
48/// Try to sign the given prehashed message using ECDSA.
49///
50/// This trait is intended to be implemented on a type with access to the
51/// secret scalar via `&self`, such as particular curve's `Scalar` type.
52#[cfg(feature = "arithmetic")]
53#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))]
54pub trait SignPrimitive<C>: Field + Into<FieldBytes<C>> + Reduce<C::UInt> + Sized
55where
56    C: PrimeCurve + ProjectiveArithmetic + ScalarArithmetic<Scalar = Self>,
57    SignatureSize<C>: ArrayLength<u8>,
58{
59    /// Try to sign the prehashed message.
60    ///
61    /// Accepts the following arguments:
62    ///
63    /// - `k`: ephemeral scalar value. MUST BE UNIFORMLY RANDOM!!!
64    /// - `z`: message digest to be signed. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY
65    ///        SECURE DIGEST ALGORITHM!!!
66    ///
67    /// # Returns
68    ///
69    /// ECDSA [`Signature`] and, when possible/desired, a [`RecoveryId`]
70    /// which can be used to recover the verifying key for a given signature.
71    #[allow(non_snake_case)]
72    fn try_sign_prehashed<K>(
73        &self,
74        k: K,
75        z: FieldBytes<C>,
76    ) -> Result<(Signature<C>, Option<RecoveryId>)>
77    where
78        K: Borrow<Self> + Invert<Output = CtOption<Self>>,
79    {
80        if k.borrow().is_zero().into() {
81            return Err(Error::new());
82        }
83
84        let z = Self::from_be_bytes_reduced(z);
85
86        // Compute scalar inversion of 𝑘
87        let k_inv = Option::<Scalar<C>>::from(k.invert()).ok_or_else(Error::new)?;
88
89        // Compute 𝑹 = 𝑘×𝑮
90        let R = (C::ProjectivePoint::generator() * k.borrow()).to_affine();
91
92        // Lift x-coordinate of 𝑹 (element of base field) into a serialized big
93        // integer, then reduce it into an element of the scalar field
94        let r = Self::from_be_bytes_reduced(R.x());
95
96        // Compute 𝒔 as a signature over 𝒓 and 𝒛.
97        let s = k_inv * (z + (r * self));
98
99        if s.is_zero().into() {
100            return Err(Error::new());
101        }
102
103        // TODO(tarcieri): support for computing recovery ID
104        Ok((Signature::from_scalars(r, s)?, None))
105    }
106
107    /// Try to sign the given message digest deterministically using the method
108    /// described in [RFC6979] for computing ECDSA ephemeral scalar `k`.
109    ///
110    /// Accepts the following parameters:
111    /// - `z`: message digest to be signed.
112    /// - `ad`: optional additional data, e.g. added entropy from an RNG
113    ///
114    /// [RFC6979]: https://datatracker.ietf.org/doc/html/rfc6979
115    #[cfg(all(feature = "rfc6979"))]
116    #[cfg_attr(docsrs, doc(cfg(feature = "rfc6979")))]
117    fn try_sign_prehashed_rfc6979<D>(
118        &self,
119        z: FieldBytes<C>,
120        ad: &[u8],
121    ) -> Result<(Signature<C>, Option<RecoveryId>)>
122    where
123        Self: From<ScalarCore<C>>,
124        C::UInt: for<'a> From<&'a Self>,
125        D: Digest + BlockSizeUser + FixedOutput<OutputSize = FieldSize<C>> + FixedOutputReset,
126    {
127        let x = C::UInt::from(self);
128        let k = rfc6979::generate_k::<D, C::UInt>(&x, &C::ORDER, &z, ad);
129        let k = Self::from(ScalarCore::<C>::new(*k).unwrap());
130        self.try_sign_prehashed(k, z)
131    }
132
133    /// Try to sign the given digest instance using the method described in
134    /// [RFC6979].
135    ///
136    /// [RFC6979]: https://datatracker.ietf.org/doc/html/rfc6979
137    #[cfg(all(feature = "rfc6979"))]
138    #[cfg_attr(docsrs, doc(cfg(feature = "rfc6979")))]
139    fn try_sign_digest_rfc6979<D>(
140        &self,
141        msg_digest: D,
142        ad: &[u8],
143    ) -> Result<(Signature<C>, Option<RecoveryId>)>
144    where
145        Self: From<ScalarCore<C>>,
146        C::UInt: for<'a> From<&'a Self>,
147        D: Digest + BlockSizeUser + FixedOutput<OutputSize = FieldSize<C>> + FixedOutputReset,
148    {
149        self.try_sign_prehashed_rfc6979::<D>(msg_digest.finalize_fixed(), ad)
150    }
151}
152
153/// Verify the given prehashed message using ECDSA.
154///
155/// This trait is intended to be implemented on type which can access
156/// the affine point represeting the public key via `&self`, such as a
157/// particular curve's `AffinePoint` type.
158#[cfg(feature = "arithmetic")]
159#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))]
160pub trait VerifyPrimitive<C>: AffineXCoordinate<C> + Copy + Sized
161where
162    C: PrimeCurve + AffineArithmetic<AffinePoint = Self> + ProjectiveArithmetic,
163    Scalar<C>: Reduce<C::UInt>,
164    SignatureSize<C>: ArrayLength<u8>,
165{
166    /// Verify the prehashed message against the provided signature
167    ///
168    /// Accepts the following arguments:
169    ///
170    /// - `z`: message digest to be verified. MUST BE OUTPUT OF A
171    ///        CRYPTOGRAPHICALLY SECURE DIGEST ALGORITHM!!!
172    /// - `sig`: signature to be verified against the key and message
173    fn verify_prehashed(&self, z: FieldBytes<C>, sig: &Signature<C>) -> Result<()> {
174        let z = Scalar::<C>::from_be_bytes_reduced(z);
175        let (r, s) = sig.split_scalars();
176        let s_inv = *s.invert();
177        let u1 = z * s_inv;
178        let u2 = *r * s_inv;
179        let x = ProjectivePoint::<C>::lincomb(
180            &ProjectivePoint::<C>::generator(),
181            &u1,
182            &ProjectivePoint::<C>::from(*self),
183            &u2,
184        )
185        .to_affine()
186        .x();
187
188        if Scalar::<C>::from_be_bytes_reduced(x) == *r {
189            Ok(())
190        } else {
191            Err(Error::new())
192        }
193    }
194
195    /// Verify message digest against the provided signature.
196    #[cfg(feature = "digest")]
197    #[cfg_attr(docsrs, doc(cfg(feature = "digest")))]
198    fn verify_digest<D>(&self, msg_digest: D, sig: &Signature<C>) -> Result<()>
199    where
200        D: FixedOutput<OutputSize = FieldSize<C>>,
201    {
202        self.verify_prehashed(msg_digest.finalize_fixed(), sig)
203    }
204}
205
206/// Bind a preferred [`Digest`] algorithm to an elliptic curve type.
207///
208/// Generally there is a preferred variety of the SHA-2 family used with ECDSA
209/// for a particular elliptic curve.
210///
211/// This trait can be used to specify it, and with it receive a blanket impl of
212/// [`PrehashSignature`], used by [`signature_derive`][1]) for the [`Signature`]
213/// type for a particular elliptic curve.
214///
215/// [1]: https://github.com/RustCrypto/traits/tree/master/signature/derive
216#[cfg(feature = "digest")]
217#[cfg_attr(docsrs, doc(cfg(feature = "digest")))]
218pub trait DigestPrimitive: PrimeCurve {
219    /// Preferred digest to use when computing ECDSA signatures for this
220    /// elliptic curve. This is typically a member of the SHA-2 family.
221    // TODO(tarcieri): add BlockSizeUser + FixedOutput(Reset) bounds in next breaking release
222    // These bounds ensure the digest algorithm can be used for HMAC-DRBG for RFC6979
223    type Digest: Digest;
224
225    /// Compute field bytes for a prehash (message digest), either zero-padding
226    /// or truncating if the prehash size does not match the field size.
227    fn prehash_to_field_bytes(prehash: &[u8]) -> Result<FieldBytes<Self>> {
228        // Minimum allowed prehash size is half the field size
229        if prehash.len() < Self::UInt::BYTE_SIZE / 2 {
230            return Err(Error::new());
231        }
232
233        let mut field_bytes = FieldBytes::<Self>::default();
234
235        // This is a operation according to RFC6979 Section 2.3.2. and SEC1 Section 2.3.8.
236        // https://datatracker.ietf.org/doc/html/rfc6979#section-2.3.2
237        // https://www.secg.org/sec1-v2.pdf
238        match prehash.len().cmp(&Self::UInt::BYTE_SIZE) {
239            cmp::Ordering::Equal => field_bytes.copy_from_slice(prehash),
240            cmp::Ordering::Less => {
241                // If prehash is smaller than the field size, pad with zeroes on the left
242                field_bytes[(Self::UInt::BYTE_SIZE - prehash.len())..].copy_from_slice(prehash);
243            }
244            cmp::Ordering::Greater => {
245                // If prehash is larger than the field size, truncate
246                field_bytes.copy_from_slice(&prehash[..Self::UInt::BYTE_SIZE]);
247            }
248        }
249
250        Ok(field_bytes)
251    }
252}
253
254#[cfg(feature = "digest")]
255impl<C> PrehashSignature for Signature<C>
256where
257    C: DigestPrimitive,
258    <FieldSize<C> as core::ops::Add>::Output: ArrayLength<u8>,
259{
260    type Digest = C::Digest;
261}