//! Low-level ECDSA primitives. //! //! # ⚠️ Warning: Hazmat! //! //! YOU PROBABLY DON'T WANT TO USE THESE! //! //! These primitives are easy-to-misuse low-level interfaces. //! //! If you are an end user / non-expert in cryptography, do not use these! //! Failure to use them correctly can lead to catastrophic failures including //! FULL PRIVATE KEY RECOVERY! use crate::{Error, Result}; use core::cmp; use elliptic_curve::{generic_array::typenum::Unsigned, FieldBytes, PrimeCurve}; #[cfg(feature = "arithmetic")] use { crate::{RecoveryId, SignatureSize}, elliptic_curve::{ ff::{Field, PrimeField}, group::{Curve as _, Group}, ops::{Invert, LinearCombination, MulByGenerator, Reduce}, point::AffineCoordinates, scalar::IsHigh, subtle::CtOption, CurveArithmetic, ProjectivePoint, Scalar, }, }; #[cfg(feature = "digest")] use { elliptic_curve::FieldBytesSize, signature::{ digest::{core_api::BlockSizeUser, Digest, FixedOutput, FixedOutputReset}, PrehashSignature, }, }; #[cfg(feature = "rfc6979")] use elliptic_curve::{FieldBytesEncoding, ScalarPrimitive}; #[cfg(any(feature = "arithmetic", feature = "digest"))] use crate::{elliptic_curve::generic_array::ArrayLength, Signature}; /// Try to sign the given prehashed message using ECDSA. /// /// This trait is intended to be implemented on a type with access to the /// secret scalar via `&self`, such as particular curve's `Scalar` type. #[cfg(feature = "arithmetic")] pub trait SignPrimitive: AsRef + Into> + IsHigh + PrimeField> + Reduce> + Sized where C: PrimeCurve + CurveArithmetic, SignatureSize: ArrayLength, { /// Try to sign the prehashed message. /// /// Accepts the following arguments: /// /// - `k`: ephemeral scalar value. MUST BE UNIFORMLY RANDOM!!! /// - `z`: message digest to be signed. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY /// SECURE DIGEST ALGORITHM!!! /// /// # Returns /// /// ECDSA [`Signature`] and, when possible/desired, a [`RecoveryId`] /// which can be used to recover the verifying key for a given signature. fn try_sign_prehashed( &self, k: K, z: &FieldBytes, ) -> Result<(Signature, Option)> where K: AsRef + Invert>, { sign_prehashed(self, k, z).map(|(sig, recid)| (sig, (Some(recid)))) } /// Try to sign the given message digest deterministically using the method /// described in [RFC6979] for computing ECDSA ephemeral scalar `k`. /// /// Accepts the following parameters: /// - `z`: message digest to be signed. /// - `ad`: optional additional data, e.g. added entropy from an RNG /// /// [RFC6979]: https://datatracker.ietf.org/doc/html/rfc6979 #[cfg(feature = "rfc6979")] fn try_sign_prehashed_rfc6979( &self, z: &FieldBytes, ad: &[u8], ) -> Result<(Signature, Option)> where Self: From> + Invert>, D: Digest + BlockSizeUser + FixedOutput> + FixedOutputReset, { let k = Scalar::::from_repr(rfc6979::generate_k::( &self.to_repr(), &C::ORDER.encode_field_bytes(), z, ad, )) .unwrap(); self.try_sign_prehashed::(k, z) } } /// Verify the given prehashed message using ECDSA. /// /// This trait is intended to be implemented on type which can access /// the affine point represeting the public key via `&self`, such as a /// particular curve's `AffinePoint` type. #[cfg(feature = "arithmetic")] pub trait VerifyPrimitive: AffineCoordinates> + Copy + Sized where C: PrimeCurve + CurveArithmetic, SignatureSize: ArrayLength, { /// Verify the prehashed message against the provided ECDSA signature. /// /// Accepts the following arguments: /// /// - `z`: message digest to be verified. MUST BE OUTPUT OF A /// CRYPTOGRAPHICALLY SECURE DIGEST ALGORITHM!!! /// - `sig`: signature to be verified against the key and message fn verify_prehashed(&self, z: &FieldBytes, sig: &Signature) -> Result<()> { verify_prehashed(&ProjectivePoint::::from(*self), z, sig) } /// Verify message digest against the provided signature. #[cfg(feature = "digest")] fn verify_digest(&self, msg_digest: D, sig: &Signature) -> Result<()> where D: FixedOutput>, { self.verify_prehashed(&msg_digest.finalize_fixed(), sig) } } /// Bind a preferred [`Digest`] algorithm to an elliptic curve type. /// /// Generally there is a preferred variety of the SHA-2 family used with ECDSA /// for a particular elliptic curve. /// /// This trait can be used to specify it, and with it receive a blanket impl of /// [`PrehashSignature`], used by [`signature_derive`][1]) for the [`Signature`] /// type for a particular elliptic curve. /// /// [1]: https://github.com/RustCrypto/traits/tree/master/signature/derive #[cfg(feature = "digest")] pub trait DigestPrimitive: PrimeCurve { /// Preferred digest to use when computing ECDSA signatures for this /// elliptic curve. This is typically a member of the SHA-2 family. type Digest: BlockSizeUser + Digest + FixedOutput> + FixedOutputReset; } #[cfg(feature = "digest")] impl PrehashSignature for Signature where C: DigestPrimitive, as core::ops::Add>::Output: ArrayLength, { type Digest = C::Digest; } /// Partial implementation of the `bits2int` function as defined in /// [RFC6979 § 2.3.2] as well as [SEC1] § 2.3.8. /// /// This is used to convert a message digest whose size may be smaller or /// larger than the size of the curve's scalar field into a serialized /// (unreduced) field element. /// /// [RFC6979 § 2.3.2]: https://datatracker.ietf.org/doc/html/rfc6979#section-2.3.2 /// [SEC1]: https://www.secg.org/sec1-v2.pdf pub fn bits2field(bits: &[u8]) -> Result> { // Minimum allowed bits size is half the field size if bits.len() < C::FieldBytesSize::USIZE / 2 { return Err(Error::new()); } let mut field_bytes = FieldBytes::::default(); match bits.len().cmp(&C::FieldBytesSize::USIZE) { cmp::Ordering::Equal => field_bytes.copy_from_slice(bits), cmp::Ordering::Less => { // If bits is smaller than the field size, pad with zeroes on the left field_bytes[(C::FieldBytesSize::USIZE - bits.len())..].copy_from_slice(bits); } cmp::Ordering::Greater => { // If bits is larger than the field size, truncate field_bytes.copy_from_slice(&bits[..C::FieldBytesSize::USIZE]); } } Ok(field_bytes) } /// Sign a prehashed message digest using the provided secret scalar and /// ephemeral scalar, returning an ECDSA signature. /// /// Accepts the following arguments: /// /// - `d`: signing key. MUST BE UNIFORMLY RANDOM!!! /// - `k`: ephemeral scalar value. MUST BE UNIFORMLY RANDOM!!! /// - `z`: message digest to be signed. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY /// SECURE DIGEST ALGORITHM!!! /// /// # Returns /// /// ECDSA [`Signature`] and, when possible/desired, a [`RecoveryId`] /// which can be used to recover the verifying key for a given signature. #[cfg(feature = "arithmetic")] #[allow(non_snake_case)] pub fn sign_prehashed( d: &Scalar, k: K, z: &FieldBytes, ) -> Result<(Signature, RecoveryId)> where C: PrimeCurve + CurveArithmetic, K: AsRef> + Invert>>, SignatureSize: ArrayLength, { // TODO(tarcieri): use `NonZeroScalar` for `k`. if k.as_ref().is_zero().into() { return Err(Error::new()); } let z = as Reduce>::reduce_bytes(z); // Compute scalar inversion of 𝑘 let k_inv = Option::>::from(k.invert()).ok_or_else(Error::new)?; // Compute 𝑹 = 𝑘×𝑮 let R = ProjectivePoint::::mul_by_generator(k.as_ref()).to_affine(); // Lift x-coordinate of 𝑹 (element of base field) into a serialized big // integer, then reduce it into an element of the scalar field let r = Scalar::::reduce_bytes(&R.x()); let x_is_reduced = r.to_repr() != R.x(); // Compute 𝒔 as a signature over 𝒓 and 𝒛. let s = k_inv * (z + (r * d)); // NOTE: `Signature::from_scalars` checks that both `r` and `s` are non-zero. let signature = Signature::from_scalars(r, s)?; let recovery_id = RecoveryId::new(R.y_is_odd().into(), x_is_reduced); Ok((signature, recovery_id)) } /// Verify the prehashed message against the provided ECDSA signature. /// /// Accepts the following arguments: /// /// - `q`: public key with which to verify the signature. /// - `z`: message digest to be verified. MUST BE OUTPUT OF A /// CRYPTOGRAPHICALLY SECURE DIGEST ALGORITHM!!! /// - `sig`: signature to be verified against the key and message. #[cfg(feature = "arithmetic")] pub fn verify_prehashed( q: &ProjectivePoint, z: &FieldBytes, sig: &Signature, ) -> Result<()> where C: PrimeCurve + CurveArithmetic, SignatureSize: ArrayLength, { let z = Scalar::::reduce_bytes(z); let (r, s) = sig.split_scalars(); let s_inv = *s.invert_vartime(); let u1 = z * s_inv; let u2 = *r * s_inv; let x = ProjectivePoint::::lincomb(&ProjectivePoint::::generator(), &u1, q, &u2) .to_affine() .x(); if *r == Scalar::::reduce_bytes(&x) { Ok(()) } else { Err(Error::new()) } } #[cfg(test)] mod tests { use super::bits2field; use elliptic_curve::dev::MockCurve; use hex_literal::hex; #[test] fn bits2field_too_small() { assert!(bits2field::(b"").is_err()); } #[test] fn bits2field_size_less() { let prehash = hex!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); let field_bytes = bits2field::(&prehash).unwrap(); assert_eq!( field_bytes.as_slice(), &hex!("00000000000000000000000000000000AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") ); } #[test] fn bits2field_size_eq() { let prehash = hex!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); let field_bytes = bits2field::(&prehash).unwrap(); assert_eq!(field_bytes.as_slice(), &prehash); } #[test] fn bits2field_size_greater() { let prehash = hex!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); let field_bytes = bits2field::(&prehash).unwrap(); assert_eq!( field_bytes.as_slice(), &hex!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") ); } }