Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 19 additions & 20 deletions ecdsa/src/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ macro_rules! new_signing_test {
array::{typenum::Unsigned, Array},
bigint::Encoding,
group::ff::PrimeField,
Curve, CurveArithmetic, Scalar,
Curve, CurveArithmetic, FieldBytes, Scalar,
},
hazmat::SignPrimitive,
hazmat::sign_prehashed,
};

fn decode_scalar(bytes: &[u8]) -> Option<Scalar<$curve>> {
if bytes.len() == <$curve as Curve>::FieldBytesSize::USIZE {
Scalar::<$curve>::from_repr(Array::clone_from_slice(bytes)).into()
Scalar::<$curve>::from_repr(bytes.try_into().unwrap()).into()
} else {
None
}
Expand All @@ -67,8 +67,10 @@ macro_rules! new_signing_test {
vector.m.len(),
"invalid vector.m (must be field-sized digest)"
);
let z = Array::clone_from_slice(vector.m);
let sig = d.try_sign_prehashed(k, &z).expect("ECDSA sign failed").0;
let z = FieldBytes::<$curve>::try_from(vector.m).unwrap();
let sig = sign_prehashed::<$curve>(&d, &k, &z)
.expect("ECDSA sign failed")
.0;

assert_eq!(vector.r, sig.r().to_bytes().as_slice());
assert_eq!(vector.s, sig.s().to_bytes().as_slice());
Expand All @@ -88,8 +90,8 @@ macro_rules! new_verification_test {
sec1::{EncodedPoint, FromEncodedPoint},
AffinePoint, CurveArithmetic, Scalar,
},
hazmat::VerifyPrimitive,
Signature,
signature::hazmat::PrehashVerifier,
Signature, VerifyingKey,
};

#[test]
Expand All @@ -101,16 +103,15 @@ macro_rules! new_verification_test {
false,
);

let q = AffinePoint::<$curve>::from_encoded_point(&q_encoded).unwrap();
let z = Array::clone_from_slice(vector.m);
let q = VerifyingKey::<$curve>::from_encoded_point(&q_encoded).unwrap();

let sig = Signature::from_scalars(
Array::clone_from_slice(vector.r),
Array::clone_from_slice(vector.s),
Array::try_from(vector.r).unwrap(),
Array::try_from(vector.s).unwrap(),
)
.unwrap();

let result = q.verify_prehashed(&z, &sig);
let result = q.verify_prehash(vector.m, &sig);
assert!(result.is_ok());
}
}
Expand All @@ -124,17 +125,15 @@ macro_rules! new_verification_test {
false,
);

let q = AffinePoint::<$curve>::from_encoded_point(&q_encoded).unwrap();
let z = Array::clone_from_slice(vector.m);
let q = VerifyingKey::<$curve>::from_encoded_point(&q_encoded).unwrap();
let r = Array::try_from(vector.r).unwrap();

// Flip a bit in `s`
let mut s_tweaked = Array::clone_from_slice(vector.s);
let mut s_tweaked = Array::try_from(vector.s).unwrap();
s_tweaked[0] ^= 1;

let sig =
Signature::from_scalars(Array::clone_from_slice(vector.r), s_tweaked).unwrap();

let result = q.verify_prehashed(&z, &sig);
let sig = Signature::from_scalars(r, s_tweaked).unwrap();
let result = q.verify_prehash(vector.m, &sig);
assert!(result.is_err());
}
}
Expand Down Expand Up @@ -169,7 +168,7 @@ macro_rules! new_wycheproof_test {
for v in data.iter().take(offset) {
assert_eq!(*v, 0, "EcdsaVerifier: point too large");
}
elliptic_curve::FieldBytes::<C>::clone_from_slice(&data[offset..])
elliptic_curve::FieldBytes::<C>::try_from(&data[offset..]).unwrap()
} else {
// Provided slice is too short and needs to be padded with zeros
// on the left. Build a combined exact iterator to do this.
Expand Down
189 changes: 66 additions & 123 deletions ecdsa/src/hazmat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ use {
ops::{Invert, LinearCombination, MulByGenerator, Reduce},
point::AffineCoordinates,
scalar::IsHigh,
subtle::CtOption,
CurveArithmetic, ProjectivePoint, Scalar,
},
};
Expand All @@ -38,120 +37,11 @@ use {
};

#[cfg(feature = "rfc6979")]
use elliptic_curve::{FieldBytesEncoding, ScalarPrimitive};
use elliptic_curve::FieldBytesEncoding;

#[cfg(any(feature = "arithmetic", feature = "digest"))]
use crate::{elliptic_curve::array::ArraySize, 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<C>:
AsRef<Self>
+ Into<FieldBytes<C>>
+ IsHigh
+ PrimeField<Repr = FieldBytes<C>>
+ Reduce<C::Uint, Bytes = FieldBytes<C>>
+ Sized
where
C: EcdsaCurve + CurveArithmetic<Scalar = Self>,
SignatureSize<C>: ArraySize,
{
/// 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<K>(
&self,
k: K,
z: &FieldBytes<C>,
) -> Result<(Signature<C>, Option<RecoveryId>)>
where
K: AsRef<Self> + Invert<Output = CtOption<Self>>,
{
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, i.e. `H(m)`. Does not have to be reduced in advance.
/// - `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<D>(
&self,
z: &FieldBytes<C>,
ad: &[u8],
) -> Result<(Signature<C>, Option<RecoveryId>)>
where
Self: From<ScalarPrimitive<C>> + Invert<Output = CtOption<Self>>,
D: Digest + BlockSizeUser + FixedOutput + FixedOutputReset,
{
// From RFC6979 § 2.4:
//
// H(m) is transformed into an integer modulo q using the bits2int
// transform and an extra modular reduction:
//
// h = bits2int(H(m)) mod q
let z2 = <Scalar<C> as Reduce<C::Uint>>::reduce_bytes(z);

let k = Scalar::<C>::from_repr(rfc6979::generate_k::<D, _>(
&self.to_repr(),
&C::ORDER.encode_field_bytes(),
&z2.to_repr(),
ad,
))
.unwrap();

self.try_sign_prehashed::<Self>(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<C>: AffineCoordinates<FieldRepr = FieldBytes<C>> + Copy + Sized
where
C: EcdsaCurve + CurveArithmetic<AffinePoint = Self>,
SignatureSize<C>: ArraySize,
{
/// 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<()> {
verify_prehashed(&ProjectivePoint::<C>::from(*self), z, sig)
}

/// Verify message digest against the provided signature.
#[cfg(feature = "digest")]
fn verify_digest<D>(&self, msg_digest: D, sig: &Signature<C>) -> Result<()>
where
D: FixedOutput,
{
self.verify_prehashed(&bits2field::<C>(&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
Expand Down Expand Up @@ -220,34 +110,37 @@ pub fn bits2field<C: EcdsaCurve>(bits: &[u8]) -> Result<FieldBytes<C>> {
/// - `z`: message digest to be signed. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY
/// SECURE DIGEST ALGORITHM!!!
///
/// # Low-S Normalization
///
/// This function will apply low-S normalization if `<C as EcdsaCurve>::NORMALIZE_S` is true.
///
/// # Returns
///
/// ECDSA [`Signature`] and, when possible/desired, a [`RecoveryId`]
/// which can be used to recover the verifying key for a given signature.
/// ECDSA [`Signature`] and 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, K>(
pub fn sign_prehashed<C>(
d: &Scalar<C>,
k: K,
k: &Scalar<C>,
z: &FieldBytes<C>,
) -> Result<(Signature<C>, RecoveryId)>
where
C: EcdsaCurve + CurveArithmetic,
K: AsRef<Scalar<C>> + Invert<Output = CtOption<Scalar<C>>>,
SignatureSize<C>: ArraySize,
{
// TODO(tarcieri): use `NonZeroScalar<C>` for `k`.
if k.as_ref().is_zero().into() {
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(k.invert()).ok_or_else(Error::new)?;
let k_inv = Option::<Scalar<C>>::from(Invert::invert(k)).ok_or_else(Error::new)?;

// Compute 𝑹 = 𝑘×𝑮
let R = ProjectivePoint::<C>::mul_by_generator(k.as_ref()).to_affine();
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
Expand All @@ -258,19 +151,69 @@ where
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);
let mut signature = Signature::from_scalars(r, s)?;
let mut recovery_id = RecoveryId::new(R.y_is_odd().into(), x_is_reduced);

// Apply low-S normalization if the curve is configured for it
if C::NORMALIZE_S {
recovery_id.0 ^= s.is_high().unwrap_u8();
signature = signature.normalize_s();
}

Ok((signature, recovery_id))
}

/// Try to sign the given message digest deterministically using the method
/// described in [RFC6979] for computing ECDSA ephemeral scalar `k`.
///
/// Accepts the following parameters:
/// - `d`: signing key. MUST BE UNIFORMLY RANDOM!!!
/// - `z`: message digest to be signed, i.e. `H(m)`. Does not have to be reduced in advance.
/// - `ad`: optional additional data, e.g. added entropy from an RNG
///
/// [RFC6979]: https://datatracker.ietf.org/doc/html/rfc6979
#[cfg(feature = "rfc6979")]
pub fn sign_prehashed_rfc6979<C, D>(
d: &Scalar<C>,
z: &FieldBytes<C>,
ad: &[u8],
) -> Result<(Signature<C>, RecoveryId)>
where
C: EcdsaCurve + CurveArithmetic,
D: Digest + BlockSizeUser + FixedOutput + FixedOutputReset,
SignatureSize<C>: ArraySize,
{
// From RFC6979 § 2.4:
//
// H(m) is transformed into an integer modulo q using the bits2int
// transform and an extra modular reduction:
//
// h = bits2int(H(m)) mod q
let z2 = <Scalar<C> as Reduce<C::Uint>>::reduce_bytes(z);

let k = Scalar::<C>::from_repr(rfc6979::generate_k::<D, _>(
&d.to_repr(),
&C::ORDER.encode_field_bytes(),
&z2.to_repr(),
ad,
))
.unwrap();

sign_prehashed(d, &k, z)
}

/// 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!!!
/// - `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.
///
/// # Low-S Normalization
///
/// This is a low-level function that does *NOT* apply the `EcdsaCurve::NORMALIZE_S` checks.
#[cfg(feature = "arithmetic")]
pub fn verify_prehashed<C>(
q: &ProjectivePoint<C>,
Expand Down
Loading