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
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions ecdsa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ spki = { version = "0.8", optional = true, default-features = false }

[dev-dependencies]
elliptic-curve = { version = "0.14", default-features = false, features = ["dev"] }
hex-literal = "1"
sha2 = { version = "0.11", default-features = false }

[features]
Expand All @@ -48,6 +47,8 @@ pkcs8 = ["der", "digest", "elliptic-curve/pkcs8"]
pem = ["elliptic-curve/pem", "pkcs8"]
serde = ["dep:serdect", "elliptic-curve/serde", "pkcs8"]

[lints]
workspace = true

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
1 change: 1 addition & 0 deletions ecdsa/src/der.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ impl<'a> DecodeValue<'a> for SignatureRef<'a> {
impl<'a> Sequence<'a> for SignatureRef<'a> {}

/// Locate the range within a slice at which a particular subslice is located
#[allow(clippy::as_conversions)]
fn find_scalar_range(outer: &[u8], inner: &[u8]) -> Result<Range<usize>> {
let outer_start = outer.as_ptr() as usize;
let inner_start = inner.as_ptr() as usize;
Expand Down
2 changes: 2 additions & 0 deletions ecdsa/src/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ impl EcdsaCurve for MockCurve {
}

/// ECDSA test vector
#[derive(Clone, Copy, Debug)]
pub struct TestVector {
/// Private scalar
pub d: &'static [u8],
Expand All @@ -306,6 +307,7 @@ pub struct TestVector {
}

#[cfg(test)]
#[allow(clippy::integer_division_remainder_used, reason = "tests")]
mod tests {
use super::*;

Expand Down
2 changes: 1 addition & 1 deletion ecdsa/src/hazmat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ where
pub(crate) fn bytes2scalar<C: EcdsaCurve + CurveArithmetic>(mut bytes: &[u8]) -> Scalar<C> {
// Compute number of bytes in `n` (curve order)
let n_bits = C::ORDER.bits();
let n_bytes = n_bits.div_ceil(8) as usize;
let n_bytes = usize::try_from(n_bits.div_ceil(8)).expect("overflow");
if bytes.len() > n_bytes {
bytes = &bytes[..n_bytes];
}
Expand Down
93 changes: 32 additions & 61 deletions ecdsa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,26 @@
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
)]
#![forbid(unsafe_code)]
#![warn(
clippy::cast_lossless,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
clippy::checked_conversions,
clippy::implicit_saturating_sub,
clippy::panic,
clippy::panic_in_result_fn,
clippy::unwrap_used,
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications,
unreachable_pub
)]
#![allow(clippy::missing_errors_doc, reason = "TODO")]

//! ## `serde` support
//!
//! When the `serde` feature of this crate is enabled, `Serialize` and
//! `Deserialize` impls are provided for the [`Signature`] and [`VerifyingKey`]
//! types.
//! When the `serde` feature of this crate is enabled, `Serialize` and `Deserialize` impls are
//! provided for the [`Signature`] and [`VerifyingKey`] types.
//!
//! Please see type-specific documentation for more information.
//!
//! ## Interop
//!
//! Any crates which provide an implementation of ECDSA for a particular
//! elliptic curve can leverage the types from this crate, along with the
//! [`k256`], [`p256`], and/or [`p384`] crates to expose ECDSA functionality in
//! a generic, interoperable way by leveraging the [`Signature`] type with in
//! conjunction with the [`signature::Signer`] and [`signature::Verifier`]
//! traits.
//! Any crates which provide an implementation of ECDSA for a particular elliptic curve can leverage
//! the types from this crate, along with the [`k256`], [`p256`], and/or [`p384`] crates to expose
//! ECDSA functionality in a generic, interoperable way by leveraging the [`Signature`] type with in
//! conjunction with the [`signature::Signer`] and [`signature::Verifier`] traits.
//!
//! For example, the [`ring-compat`] crate implements the [`signature::Signer`]
//! and [`signature::Verifier`] traits in conjunction with the
//! [`p256::ecdsa::Signature`] and [`p384::ecdsa::Signature`] types to
//! wrap the ECDSA implementations from [*ring*] in a generic, interoperable
//! API.
//! For example, the [`ring-compat`] crate implements the [`signature::Signer`] and
//! [`signature::Verifier`] traits in conjunction with the [`p256::ecdsa::Signature`] and
//! [`p384::ecdsa::Signature`] types to wrap the ECDSA implementations from [*ring*] in a generic,
//! interoperable API.
//!
//! [`k256`]: https://docs.rs/k256
//! [`p256`]: https://docs.rs/p256
Expand Down Expand Up @@ -188,21 +167,19 @@ pub type SignatureBytes<C> = Array<u8, SignatureSize<C>>;
///
/// Both `r` and `s` MUST be non-zero.
///
/// For example, in a curve with a 256-bit modulus like NIST P-256 or
/// secp256k1, `r` and `s` will both be 32-bytes and serialized as big endian,
/// resulting in a signature with a total of 64-bytes.
/// For example, in a curve with a 256-bit modulus like NIST P-256 or secp256k1, `r` and `s` are
/// both 32-bytes and serialized as big endian, resulting in a signature with a total of 64-bytes.
///
/// ASN.1 DER-encoded signatures also supported via the
/// [`Signature::from_der`] and [`Signature::to_der`] methods.
/// ASN.1 DER-encoded signatures also supported via the [`Signature::from_der`] and
/// [`Signature::to_der`] methods.
///
/// # `serde` support
///
/// When the `serde` feature of this crate is enabled, it provides support for
/// serializing and deserializing ECDSA signatures using the `Serialize` and
/// `Deserialize` traits.
/// When the `serde` feature of this crate is enabled, it provides support for serializing and
/// deserializing ECDSA signatures using the `Serialize` and `Deserialize` traits.
///
/// The serialization uses a hexadecimal encoding when used with
/// "human readable" text formats, and a binary encoding otherwise.
/// The serialization uses a hexadecimal encoding when used with "human readable" text formats, and
/// a binary encoding otherwise.
///
/// [IEEE P1363]: https://en.wikipedia.org/wiki/IEEE_P1363
#[derive(Clone, Copy, Eq, PartialEq)]
Expand All @@ -218,11 +195,9 @@ where
/// Parse a signature from fixed-width bytes, i.e. 2 * the size of
/// [`FieldBytes`] for a particular curve.
///
/// # Returns
/// - `Ok(signature)` if the `r` and `s` components are both in the valid
/// range `1..n` when serialized as concatenated big endian integers.
/// - `Err(err)` if the `r` and/or `s` component of the signature is
/// out-of-range when interpreted as a big endian integer.
/// # Errors
/// If the `r` and/or `s` component of the signature is out-of-range when interpreted as a big
/// endian integer.
pub fn from_bytes(bytes: &SignatureBytes<C>) -> Result<Self> {
let chunks = FieldBytes::<C>::slice_as_chunks(bytes).0;
let r = chunks[0];
Expand Down Expand Up @@ -250,11 +225,8 @@ where
/// Create a [`Signature`] from the serialized `r` and `s` scalar values
/// which comprise the signature.
///
/// # Returns
/// - `Ok(signature)` if the `r` and `s` components are both in the valid
/// range `1..n` when serialized as concatenated big endian integers.
/// - `Err(err)` if the `r` and/or `s` component of the signature is
/// out-of-range when interpreted as a big endian integer.
/// # Errors
/// If the `r` and/or `s` component of the signature is out-of-range when interpreted as a big endian integer.
pub fn from_scalars(r: impl Into<FieldBytes<C>>, s: impl Into<FieldBytes<C>>) -> Result<Self> {
let r = ScalarValue::from_slice(&r.into()).map_err(|_| Error::new())?;
let s = ScalarValue::from_slice(&s.into()).map_err(|_| Error::new())?;
Expand Down Expand Up @@ -282,6 +254,7 @@ where

/// Serialize this signature as ASN.1 DER.
#[cfg(feature = "der")]
#[allow(clippy::missing_panics_doc, reason = "should not panic in practice")]
pub fn to_der(&self) -> der::Signature<C>
where
der::MaxSize<C>: ArraySize,
Expand Down Expand Up @@ -321,6 +294,7 @@ where
/// Normalize signature into "low S" form described in [BIP 0062: Dealing with Malleability][1].
///
/// [1]: https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki
#[must_use]
pub fn normalize_s(&self) -> Self {
let mut result = *self;
let s_inv = ScalarValue::from(-self.s());
Expand Down Expand Up @@ -439,11 +413,10 @@ where
}
}

/// ECDSA [`ObjectIdentifier`] which identifies the digest used by default
/// with the `Signer` and `Verifier` traits.
/// ECDSA [`ObjectIdentifier`] which identifies the digest used by default with the `Signer` and
/// `Verifier` traits.
///
/// To support non-default digest algorithms, use the [`SignatureWithOid`]
/// type instead.
/// To support non-default digest algorithms, use the [`SignatureWithOid`] type instead.
#[cfg(feature = "digest")]
impl<C> AssociatedOid for Signature<C>
where
Expand Down Expand Up @@ -507,9 +480,8 @@ impl<C: EcdsaCurve> Zeroize for Signature<C> {
}
}

/// An extended [`Signature`] type which is parameterized by an
/// `ObjectIdentifier` which identifies the ECDSA variant used by a
/// particular signature.
/// An extended [`Signature`] type which is parameterized by an `ObjectIdentifier` which identifies
/// the ECDSA variant used by a particular signature.
///
/// Valid `ObjectIdentifiers` are defined in [RFC5758 § 3.2]:
///
Expand Down Expand Up @@ -540,8 +512,7 @@ where
{
/// Create a new signature with an explicitly provided OID.
///
/// OID must begin with `1.2.840.10045.4`, the [RFC5758] OID prefix for
/// ECDSA variants.
/// OID must begin with `1.2.840.10045.4`, the [RFC5758] OID prefix for ECDSA variants.
///
/// [RFC5758]: https://www.rfc-editor.org/rfc/rfc5758#section-3.2
pub fn new(signature: Signature<C>, oid: ObjectIdentifier) -> Result<Self> {
Expand Down
47 changes: 30 additions & 17 deletions ecdsa/src/recovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,15 @@ use {

/// Recovery IDs, a.k.a. "recid".
///
/// This is an integer value `0`, `1`, `2`, or `3` included along with a
/// signature which is used during the recovery process to select the correct
/// public key from the signature.
/// This is an integer value `0`, `1`, `2`, or `3` included along with a signature which is used
/// during the recovery process to select the correct public key from the signature.
///
/// It consists of two bits of information:
///
/// - low bit (0/1): was the y-coordinate of the affine point resulting from
/// the fixed-base multiplication 𝑘×𝑮 odd? This part of the algorithm
/// functions similar to point decompression.
/// - hi bit (2/3): did the affine x-coordinate of 𝑘×𝑮 overflow the order of
/// the scalar field, requiring a reduction when computing `r`?
/// 1. low bit (0/1): was the y-coordinate of the affine point resulting from the fixed-base
/// multiplication 𝑘×𝑮 odd? This part of the algorithm functions similar to point decompression.
/// 2. hi bit (2/3): did the affine x-coordinate of 𝑘×𝑮 overflow the order of the scalar field `n`,
/// requiring a reduction when computing `r`?
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct RecoveryId(pub(crate) u8);

Expand All @@ -51,21 +49,26 @@ impl RecoveryId {
///
/// - `is_y_odd`: is the affine y-coordinate of 𝑘×𝑮 odd?
/// - `is_x_reduced`: did the affine x-coordinate of 𝑘×𝑮 overflow the curve order?
#[must_use]
#[allow(clippy::as_conversions, reason = "const fn")]
pub const fn new(is_y_odd: bool, is_x_reduced: bool) -> Self {
Self(((is_x_reduced as u8) << 1) | (is_y_odd as u8))
}

/// Did the affine x-coordinate of 𝑘×𝑮 overflow the curve order?
#[must_use]
pub const fn is_x_reduced(self) -> bool {
(self.0 & 0b10) != 0
}

/// Is the affine y-coordinate of 𝑘×𝑮 odd?
#[must_use]
pub const fn is_y_odd(self) -> bool {
(self.0 & 1) != 0
}

/// Convert a `u8` into a [`RecoveryId`].
#[must_use]
pub const fn from_byte(byte: u8) -> Option<Self> {
if byte <= Self::MAX {
Some(Self(byte))
Expand All @@ -75,16 +78,20 @@ impl RecoveryId {
}

/// Convert this [`RecoveryId`] into a `u8`.
#[must_use]
pub const fn to_byte(self) -> u8 {
self.0
}
}

#[cfg(feature = "algorithm")]
impl RecoveryId {
/// Given a public key, message, and signature, use trial recovery
/// to determine if a suitable recovery ID exists, or return an error
/// otherwise.
/// Given a public key, message, and signature, use trial recovery to determine if a suitable
/// recovery ID exists.
///
/// # Errors
/// Returns an error if a suitable solution could not be found and/or the signature does not
/// verify.
pub fn trial_recovery_from_msg<C>(
verifying_key: &VerifyingKey<C>,
msg: &[u8],
Expand All @@ -98,9 +105,12 @@ impl RecoveryId {
Self::trial_recovery_from_digest(verifying_key, C::Digest::new_with_prefix(msg), signature)
}

/// Given a public key, message digest, and signature, use trial recovery
/// to determine if a suitable recovery ID exists, or return an error
/// otherwise.
/// Given a public key, message digest, and signature, use trial recovery to determine if a
/// suitable recovery ID exists.
///
/// # Errors
/// Returns an error if a suitable solution could not be found and/or the signature does not
/// verify.
pub fn trial_recovery_from_digest<C, D>(
verifying_key: &VerifyingKey<C>,
digest: D,
Expand All @@ -115,9 +125,12 @@ impl RecoveryId {
Self::trial_recovery_from_prehash(verifying_key, &digest.finalize(), signature)
}

/// Given a public key, message digest, and signature, use trial recovery
/// to determine if a suitable recovery ID exists, or return an error
/// otherwise.
/// Given a public key, message digest, and signature, use trial recovery to determine if a
/// suitable recovery ID exists.
///
/// # Errors
/// Returns an error if a suitable solution could not be found and/or the signature does not
/// verify.
pub fn trial_recovery_from_prehash<C>(
verifying_key: &VerifyingKey<C>,
prehash: &[u8],
Expand Down
Loading