Skip to content

Commit

Permalink
ecdsa: extract hazmat::{sign_prehashed, verify_prehashed}.
Browse files Browse the repository at this point in the history
Extracts generic, reusable functions from the `SignPrimitive` and
`VerifyPrimitive` traits.

The main motivation for this is to make it possible for the `*Primitive`
trait impls to be composed in terms of a generic implementation, in
order to add support for low-S normalization in the `k256` crate.
  • Loading branch information
tarcieri committed Jul 17, 2023
1 parent 2c3d90e commit db9ceae
Showing 1 changed file with 92 additions and 49 deletions.
141 changes: 92 additions & 49 deletions ecdsa/src/hazmat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use elliptic_curve::{generic_array::typenum::Unsigned, FieldBytes, PrimeCurve};
use {
crate::{RecoveryId, SignatureSize},
elliptic_curve::{
ff::PrimeField,
ff::{Field, PrimeField},
group::{Curve as _, Group},
ops::{Invert, LinearCombination, MulByGenerator, Reduce},
point::AffineCoordinates,
Expand Down Expand Up @@ -56,7 +56,7 @@ pub trait SignPrimitive<C>:
+ Reduce<C::Uint, Bytes = FieldBytes<C>>
+ Sized
where
C: PrimeCurve + CurveArithmetic + CurveArithmetic<Scalar = Self>,
C: PrimeCurve + CurveArithmetic<Scalar = Self>,
SignatureSize<C>: ArrayLength<u8>,
{
/// Try to sign the prehashed message.
Expand All @@ -71,7 +71,6 @@ where
///
/// ECDSA [`Signature`] and, when possible/desired, a [`RecoveryId`]
/// which can be used to recover the verifying key for a given signature.
#[allow(non_snake_case)]
fn try_sign_prehashed<K>(
&self,
k: K,
Expand All @@ -80,30 +79,7 @@ where
where
K: AsRef<Self> + Invert<Output = CtOption<Self>>,
{
if k.as_ref().is_zero().into() {
return Err(Error::new());
}

let z = <Self as Reduce<C::Uint>>::reduce_bytes(z);

// Compute scalar inversion of 𝑘
let k_inv = Option::<Scalar<C>>::from(k.invert()).ok_or_else(Error::new)?;

// Compute 𝑹 = 𝑘×𝑮
let R = ProjectivePoint::<C>::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 = Self::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 * self));

// 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, Some(recovery_id)))
sign_prehashed(self, k.as_ref(), z).map(|(sig, recid)| (sig, (Some(recid))))
}

/// Try to sign the given message digest deterministically using the method
Expand All @@ -114,7 +90,7 @@ where
/// - `ad`: optional additional data, e.g. added entropy from an RNG
///
/// [RFC6979]: https://datatracker.ietf.org/doc/html/rfc6979
#[cfg(all(feature = "rfc6979"))]
#[cfg(feature = "rfc6979")]
fn try_sign_prehashed_rfc6979<D>(
&self,
z: &FieldBytes<C>,
Expand Down Expand Up @@ -144,36 +120,18 @@ where
#[cfg(feature = "arithmetic")]
pub trait VerifyPrimitive<C>: AffineCoordinates<FieldRepr = FieldBytes<C>> + Copy + Sized
where
C: PrimeCurve + CurveArithmetic<AffinePoint = Self> + CurveArithmetic,
C: PrimeCurve + CurveArithmetic<AffinePoint = Self>,
SignatureSize<C>: ArrayLength<u8>,
{
/// Verify the prehashed message against the provided signature
/// 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<C>, sig: &Signature<C>) -> Result<()> {
let z = Scalar::<C>::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::<C>::lincomb(
&ProjectivePoint::<C>::generator(),
&u1,
&ProjectivePoint::<C>::from(*self),
&u2,
)
.to_affine()
.x();

if *r == Scalar::<C>::reduce_bytes(&x) {
Ok(())
} else {
Err(Error::new())
}
verify_prehashed(&ProjectivePoint::<C>::from(*self), z, sig)
}

/// Verify message digest against the provided signature.
Expand Down Expand Up @@ -247,6 +205,91 @@ pub fn bits2field<C: PrimeCurve>(bits: &[u8]) -> Result<FieldBytes<C>> {
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<C>(
d: &Scalar<C>,
k: &Scalar<C>,
z: &FieldBytes<C>,
) -> Result<(Signature<C>, RecoveryId)>
where
C: PrimeCurve + CurveArithmetic,
SignatureSize<C>: ArrayLength<u8>,
{
if k.is_zero().into() {
return Err(Error::new());
}

let z = <Scalar<C> as Reduce<C::Uint>>::reduce_bytes(z);

// Compute scalar inversion of 𝑘
let k_inv = Option::<Scalar<C>>::from(Invert::invert(k)).ok_or_else(Error::new)?;

// Compute 𝑹 = 𝑘×𝑮
let R = ProjectivePoint::<C>::mul_by_generator(k).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::<C>::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<C>(
q: &ProjectivePoint<C>,
z: &FieldBytes<C>,
sig: &Signature<C>,
) -> Result<()>
where
C: PrimeCurve + CurveArithmetic,
SignatureSize<C>: ArrayLength<u8>,
{
let z = Scalar::<C>::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::<C>::lincomb(&ProjectivePoint::<C>::generator(), &u1, q, &u2)
.to_affine()
.x();

if *r == Scalar::<C>::reduce_bytes(&x) {
Ok(())
} else {
Err(Error::new())
}
}

#[cfg(test)]
mod tests {
use super::bits2field;
Expand Down

0 comments on commit db9ceae

Please sign in to comment.