#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![doc(
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
)]
#[cfg(feature = "alloc")]
extern crate alloc;
mod normalized;
mod recovery;
#[cfg(feature = "der")]
pub mod der;
#[cfg(feature = "dev")]
pub mod dev;
#[cfg(feature = "hazmat")]
pub mod hazmat;
#[cfg(feature = "signing")]
mod signing;
#[cfg(feature = "verifying")]
mod verifying;
pub use crate::{normalized::NormalizedSignature, recovery::RecoveryId};
pub use elliptic_curve::{self, sec1::EncodedPoint, PrimeCurve};
pub use signature::{self, Error, Result, SignatureEncoding};
#[cfg(feature = "signing")]
pub use crate::signing::SigningKey;
#[cfg(feature = "verifying")]
pub use crate::verifying::VerifyingKey;
use core::{fmt, ops::Add};
use elliptic_curve::{
generic_array::{typenum::Unsigned, ArrayLength, GenericArray},
FieldBytes, FieldBytesSize, ScalarPrimitive,
};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "arithmetic")]
use {
core::str,
elliptic_curve::{scalar::IsHigh, CurveArithmetic, NonZeroScalar},
};
#[cfg(feature = "digest")]
use digest::{
const_oid::{AssociatedOid, ObjectIdentifier},
Digest,
};
#[cfg(feature = "pkcs8")]
use elliptic_curve::pkcs8::spki::{
der::AnyRef, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier,
};
#[cfg(feature = "serde")]
use serdect::serde::{de, ser, Deserialize, Serialize};
#[cfg(all(feature = "alloc", feature = "pkcs8"))]
use elliptic_curve::pkcs8::spki::{
self, AlgorithmIdentifierOwned, DynAssociatedAlgorithmIdentifier,
};
#[cfg(feature = "digest")]
pub const ECDSA_SHA224_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.1");
#[cfg(feature = "digest")]
pub const ECDSA_SHA256_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2");
#[cfg(feature = "digest")]
pub const ECDSA_SHA384_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.3");
#[cfg(feature = "digest")]
pub const ECDSA_SHA512_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.4");
#[cfg(feature = "digest")]
const SHA224_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.4");
#[cfg(feature = "digest")]
const SHA256_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1");
#[cfg(feature = "digest")]
const SHA384_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.2");
#[cfg(feature = "digest")]
const SHA512_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.3");
pub type SignatureSize<C> = <FieldBytesSize<C> as Add>::Output;
pub type SignatureBytes<C> = GenericArray<u8, SignatureSize<C>>;
#[derive(Clone, Eq, PartialEq)]
pub struct Signature<C: PrimeCurve> {
r: ScalarPrimitive<C>,
s: ScalarPrimitive<C>,
}
impl<C> Signature<C>
where
C: PrimeCurve,
SignatureSize<C>: ArrayLength<u8>,
{
pub fn from_bytes(bytes: &SignatureBytes<C>) -> Result<Self> {
let (r_bytes, s_bytes) = bytes.split_at(C::FieldBytesSize::USIZE);
let r = FieldBytes::<C>::clone_from_slice(r_bytes);
let s = FieldBytes::<C>::clone_from_slice(s_bytes);
Self::from_scalars(r, s)
}
pub fn from_slice(slice: &[u8]) -> Result<Self> {
if slice.len() == SignatureSize::<C>::USIZE {
Self::from_bytes(SignatureBytes::<C>::from_slice(slice))
} else {
Err(Error::new())
}
}
#[cfg(feature = "der")]
pub fn from_der(bytes: &[u8]) -> Result<Self>
where
der::MaxSize<C>: ArrayLength<u8>,
<FieldBytesSize<C> as Add>::Output: Add<der::MaxOverhead> + ArrayLength<u8>,
{
der::Signature::<C>::try_from(bytes).and_then(Self::try_from)
}
pub fn from_scalars(r: impl Into<FieldBytes<C>>, s: impl Into<FieldBytes<C>>) -> Result<Self> {
let r = ScalarPrimitive::from_slice(&r.into()).map_err(|_| Error::new())?;
let s = ScalarPrimitive::from_slice(&s.into()).map_err(|_| Error::new())?;
if r.is_zero().into() || s.is_zero().into() {
return Err(Error::new());
}
Ok(Self { r, s })
}
pub fn split_bytes(&self) -> (FieldBytes<C>, FieldBytes<C>) {
(self.r.to_bytes(), self.s.to_bytes())
}
pub fn to_bytes(&self) -> SignatureBytes<C> {
let mut bytes = SignatureBytes::<C>::default();
let (r_bytes, s_bytes) = bytes.split_at_mut(C::FieldBytesSize::USIZE);
r_bytes.copy_from_slice(&self.r.to_bytes());
s_bytes.copy_from_slice(&self.s.to_bytes());
bytes
}
#[cfg(feature = "der")]
pub fn to_der(&self) -> der::Signature<C>
where
der::MaxSize<C>: ArrayLength<u8>,
<FieldBytesSize<C> as Add>::Output: Add<der::MaxOverhead> + ArrayLength<u8>,
{
let (r, s) = self.split_bytes();
der::Signature::from_components(&r, &s).expect("DER encoding error")
}
#[cfg(feature = "alloc")]
pub fn to_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
}
#[cfg(feature = "arithmetic")]
impl<C> Signature<C>
where
C: PrimeCurve + CurveArithmetic,
SignatureSize<C>: ArrayLength<u8>,
{
pub fn r(&self) -> NonZeroScalar<C> {
NonZeroScalar::new(self.r.into()).unwrap()
}
pub fn s(&self) -> NonZeroScalar<C> {
NonZeroScalar::new(self.s.into()).unwrap()
}
pub fn split_scalars(&self) -> (NonZeroScalar<C>, NonZeroScalar<C>) {
(self.r(), self.s())
}
pub fn normalize_s(&self) -> Option<Self> {
let s = self.s();
if s.is_high().into() {
let mut result = self.clone();
result.s = ScalarPrimitive::from(-s);
Some(result)
} else {
None
}
}
}
impl<C> Copy for Signature<C>
where
C: PrimeCurve,
SignatureSize<C>: ArrayLength<u8>,
<SignatureSize<C> as ArrayLength<u8>>::ArrayType: Copy,
{
}
impl<C> From<Signature<C>> for SignatureBytes<C>
where
C: PrimeCurve,
SignatureSize<C>: ArrayLength<u8>,
{
fn from(signature: Signature<C>) -> SignatureBytes<C> {
signature.to_bytes()
}
}
impl<C> SignatureEncoding for Signature<C>
where
C: PrimeCurve,
SignatureSize<C>: ArrayLength<u8>,
{
type Repr = SignatureBytes<C>;
}
impl<C> TryFrom<&[u8]> for Signature<C>
where
C: PrimeCurve,
SignatureSize<C>: ArrayLength<u8>,
{
type Error = Error;
fn try_from(slice: &[u8]) -> Result<Self> {
Self::from_slice(slice)
}
}
impl<C> fmt::Debug for Signature<C>
where
C: PrimeCurve,
SignatureSize<C>: ArrayLength<u8>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ecdsa::Signature<{:?}>(", C::default())?;
for byte in self.to_bytes() {
write!(f, "{:02X}", byte)?;
}
write!(f, ")")
}
}
impl<C> fmt::Display for Signature<C>
where
C: PrimeCurve,
SignatureSize<C>: ArrayLength<u8>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:X}", self)
}
}
impl<C> fmt::LowerHex for Signature<C>
where
C: PrimeCurve,
SignatureSize<C>: ArrayLength<u8>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.to_bytes() {
write!(f, "{:02x}", byte)?;
}
Ok(())
}
}
impl<C> fmt::UpperHex for Signature<C>
where
C: PrimeCurve,
SignatureSize<C>: ArrayLength<u8>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.to_bytes() {
write!(f, "{:02X}", byte)?;
}
Ok(())
}
}
#[cfg(feature = "arithmetic")]
impl<C> str::FromStr for Signature<C>
where
C: PrimeCurve + CurveArithmetic,
SignatureSize<C>: ArrayLength<u8>,
{
type Err = Error;
fn from_str(hex: &str) -> Result<Self> {
if hex.as_bytes().len() != C::FieldBytesSize::USIZE * 4 {
return Err(Error::new());
}
if !hex
.as_bytes()
.iter()
.all(|&byte| matches!(byte, b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z'))
{
return Err(Error::new());
}
let (r_hex, s_hex) = hex.split_at(C::FieldBytesSize::USIZE * 2);
let r = r_hex
.parse::<NonZeroScalar<C>>()
.map_err(|_| Error::new())?;
let s = s_hex
.parse::<NonZeroScalar<C>>()
.map_err(|_| Error::new())?;
Self::from_scalars(r, s)
}
}
#[cfg(all(feature = "digest", feature = "hazmat"))]
impl<C> AssociatedOid for Signature<C>
where
C: hazmat::DigestPrimitive,
C::Digest: AssociatedOid,
{
const OID: ObjectIdentifier = match ecdsa_oid_for_digest(C::Digest::OID) {
Some(oid) => oid,
None => panic!("no RFC5758 ECDSA OID defined for DigestPrimitive::Digest"),
};
}
#[cfg(feature = "pkcs8")]
impl<C> AssociatedAlgorithmIdentifier for Signature<C>
where
C: PrimeCurve,
Self: AssociatedOid,
{
type Params = AnyRef<'static>;
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef {
oid: Self::OID,
parameters: None,
};
}
#[cfg(feature = "serde")]
impl<C> Serialize for Signature<C>
where
C: PrimeCurve,
SignatureSize<C>: ArrayLength<u8>,
{
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
serdect::array::serialize_hex_upper_or_bin(&self.to_bytes(), serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, C> Deserialize<'de> for Signature<C>
where
C: PrimeCurve,
SignatureSize<C>: ArrayLength<u8>,
{
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
let mut bytes = SignatureBytes::<C>::default();
serdect::array::deserialize_hex_or_bin(&mut bytes, deserializer)?;
Self::try_from(bytes.as_slice()).map_err(de::Error::custom)
}
}
#[cfg(feature = "digest")]
#[derive(Clone, Eq, PartialEq)]
pub struct SignatureWithOid<C: PrimeCurve> {
signature: Signature<C>,
oid: ObjectIdentifier,
}
#[cfg(feature = "digest")]
impl<C> SignatureWithOid<C>
where
C: PrimeCurve,
{
pub fn new(signature: Signature<C>, oid: ObjectIdentifier) -> Result<Self> {
for (arc1, arc2) in ObjectIdentifier::new_unwrap("1.2.840.10045.4.3")
.arcs()
.zip(oid.arcs())
{
if arc1 != arc2 {
return Err(Error::new());
}
}
Ok(Self { signature, oid })
}
pub fn new_with_digest<D>(signature: Signature<C>) -> Result<Self>
where
D: AssociatedOid + Digest,
{
let oid = ecdsa_oid_for_digest(D::OID).ok_or_else(Error::new)?;
Ok(Self { signature, oid })
}
pub fn from_bytes_with_digest<D>(bytes: &SignatureBytes<C>) -> Result<Self>
where
D: AssociatedOid + Digest,
SignatureSize<C>: ArrayLength<u8>,
{
Self::new_with_digest::<D>(Signature::<C>::from_bytes(bytes)?)
}
pub fn from_slice_with_digest<D>(slice: &[u8]) -> Result<Self>
where
D: AssociatedOid + Digest,
SignatureSize<C>: ArrayLength<u8>,
{
Self::new_with_digest::<D>(Signature::<C>::from_slice(slice)?)
}
pub fn signature(&self) -> &Signature<C> {
&self.signature
}
pub fn oid(&self) -> ObjectIdentifier {
self.oid
}
pub fn to_bytes(&self) -> SignatureBytes<C>
where
SignatureSize<C>: ArrayLength<u8>,
{
self.signature.to_bytes()
}
}
#[cfg(feature = "digest")]
impl<C> Copy for SignatureWithOid<C>
where
C: PrimeCurve,
SignatureSize<C>: ArrayLength<u8>,
<SignatureSize<C> as ArrayLength<u8>>::ArrayType: Copy,
{
}
#[cfg(feature = "digest")]
impl<C> From<SignatureWithOid<C>> for Signature<C>
where
C: PrimeCurve,
{
fn from(sig: SignatureWithOid<C>) -> Signature<C> {
sig.signature
}
}
#[cfg(feature = "digest")]
impl<C> From<SignatureWithOid<C>> for SignatureBytes<C>
where
C: PrimeCurve,
SignatureSize<C>: ArrayLength<u8>,
{
fn from(signature: SignatureWithOid<C>) -> SignatureBytes<C> {
signature.to_bytes()
}
}
#[cfg(all(feature = "digest", feature = "hazmat"))]
impl<C> SignatureEncoding for SignatureWithOid<C>
where
C: hazmat::DigestPrimitive,
C::Digest: AssociatedOid,
SignatureSize<C>: ArrayLength<u8>,
{
type Repr = SignatureBytes<C>;
}
#[cfg(all(feature = "digest", feature = "hazmat"))]
impl<C> TryFrom<&[u8]> for SignatureWithOid<C>
where
C: hazmat::DigestPrimitive,
C::Digest: AssociatedOid,
SignatureSize<C>: ArrayLength<u8>,
{
type Error = Error;
fn try_from(slice: &[u8]) -> Result<Self> {
Self::new(Signature::<C>::from_slice(slice)?, C::Digest::OID)
}
}
#[cfg(all(feature = "alloc", feature = "pkcs8"))]
impl<C> DynAssociatedAlgorithmIdentifier for SignatureWithOid<C>
where
C: PrimeCurve,
{
fn algorithm_identifier(&self) -> spki::Result<AlgorithmIdentifierOwned> {
Ok(AlgorithmIdentifierOwned {
oid: self.oid,
parameters: None,
})
}
}
#[cfg(feature = "digest")]
const fn ecdsa_oid_for_digest(digest_oid: ObjectIdentifier) -> Option<ObjectIdentifier> {
match digest_oid {
SHA224_OID => Some(ECDSA_SHA224_OID),
SHA256_OID => Some(ECDSA_SHA256_OID),
SHA384_OID => Some(ECDSA_SHA384_OID),
SHA512_OID => Some(ECDSA_SHA512_OID),
_ => None,
}
}