#[cfg(feature = "ed25519")]
use bc_crypto::ED25519_SIGNATURE_SIZE;
#[cfg(feature = "secp256k1")]
use bc_crypto::{ECDSA_SIGNATURE_SIZE, SCHNORR_SIGNATURE_SIZE};
use bc_ur::prelude::*;
#[cfg(feature = "ssh")]
use ssh_key::{LineEnding, SshSig};
use super::SignatureScheme;
#[cfg(any(feature = "secp256k1", feature = "ed25519", feature = "ssh"))]
use crate::Error;
#[cfg(feature = "pqcrypto")]
use crate::MLDSASignature;
use crate::{Result, tags};
#[derive(Clone)]
pub enum Signature {
#[cfg(feature = "secp256k1")]
Schnorr([u8; SCHNORR_SIGNATURE_SIZE]),
#[cfg(feature = "secp256k1")]
ECDSA([u8; ECDSA_SIGNATURE_SIZE]),
#[cfg(feature = "ed25519")]
Ed25519([u8; ED25519_SIGNATURE_SIZE]),
#[cfg(feature = "ssh")]
SSH(SshSig),
#[cfg(feature = "pqcrypto")]
MLDSA(MLDSASignature),
}
impl PartialEq for Signature {
#[allow(unreachable_patterns)]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
#[cfg(feature = "secp256k1")]
(Self::Schnorr(a), Self::Schnorr(b)) => a == b,
#[cfg(feature = "secp256k1")]
(Self::ECDSA(a), Self::ECDSA(b)) => a == b,
#[cfg(feature = "ed25519")]
(Self::Ed25519(a), Self::Ed25519(b)) => a == b,
#[cfg(feature = "ssh")]
(Self::SSH(a), Self::SSH(b)) => a == b,
#[cfg(feature = "pqcrypto")]
(Self::MLDSA(a), Self::MLDSA(b)) => a.as_bytes() == b.as_bytes(),
#[cfg(any(
feature = "secp256k1",
feature = "ed25519",
feature = "ssh",
feature = "pqcrypto"
))]
_ => false,
#[cfg(not(any(
feature = "secp256k1",
feature = "ed25519",
feature = "ssh",
feature = "pqcrypto"
)))]
_ => unreachable!(),
}
}
}
impl Signature {
#[cfg(feature = "secp256k1")]
pub fn schnorr_from_data(data: [u8; SCHNORR_SIGNATURE_SIZE]) -> Self {
Self::Schnorr(data)
}
#[cfg(feature = "secp256k1")]
pub fn schnorr_from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
let data = data.as_ref();
if data.len() != SCHNORR_SIGNATURE_SIZE {
return Err(Error::invalid_size(
"Schnorr signature",
SCHNORR_SIGNATURE_SIZE,
data.len(),
));
}
let mut arr = [0u8; SCHNORR_SIGNATURE_SIZE];
arr.copy_from_slice(data);
Ok(Self::schnorr_from_data(arr))
}
#[cfg(feature = "secp256k1")]
pub fn ecdsa_from_data(data: [u8; ECDSA_SIGNATURE_SIZE]) -> Self {
Self::ECDSA(data)
}
#[cfg(feature = "secp256k1")]
pub fn ecdsa_from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
let data = data.as_ref();
if data.len() != ECDSA_SIGNATURE_SIZE {
return Err(Error::invalid_size(
"ECDSA signature",
ECDSA_SIGNATURE_SIZE,
data.len(),
));
}
let mut arr = [0u8; ECDSA_SIGNATURE_SIZE];
arr.copy_from_slice(data);
Ok(Self::ecdsa_from_data(arr))
}
#[cfg(feature = "ed25519")]
pub fn ed25519_from_data(data: [u8; ED25519_SIGNATURE_SIZE]) -> Self {
Self::Ed25519(data)
}
#[cfg(feature = "ed25519")]
pub fn ed25519_from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
let data = data.as_ref();
if data.len() != ED25519_SIGNATURE_SIZE {
return Err(Error::invalid_size(
"Ed25519 signature",
ED25519_SIGNATURE_SIZE,
data.len(),
));
}
let mut arr = [0u8; ED25519_SIGNATURE_SIZE];
arr.copy_from_slice(data);
Ok(Self::Ed25519(arr))
}
#[cfg(feature = "ssh")]
pub fn from_ssh(sig: SshSig) -> Self { Self::SSH(sig) }
#[cfg(feature = "secp256k1")]
pub fn to_schnorr(&self) -> Option<&[u8; SCHNORR_SIGNATURE_SIZE]> {
match self {
Self::Schnorr(sig) => Some(sig),
_ => None,
}
}
#[cfg(feature = "secp256k1")]
pub fn to_ecdsa(&self) -> Option<&[u8; ECDSA_SIGNATURE_SIZE]> {
match self {
Self::ECDSA(sig) => Some(sig),
_ => None,
}
}
#[cfg(feature = "ssh")]
pub fn to_ssh(&self) -> Option<&SshSig> {
match self {
Self::SSH(sig) => Some(sig),
#[cfg(any(
feature = "secp256k1",
feature = "ed25519",
feature = "pqcrypto"
))]
_ => None,
}
}
#[allow(unreachable_patterns)]
pub fn scheme(&self) -> Result<SignatureScheme> {
match self {
#[cfg(feature = "secp256k1")]
Self::Schnorr(_) => Ok(SignatureScheme::Schnorr),
#[cfg(feature = "secp256k1")]
Self::ECDSA(_) => Ok(SignatureScheme::Ecdsa),
#[cfg(feature = "ed25519")]
Self::Ed25519(_) => Ok(SignatureScheme::Ed25519),
#[cfg(feature = "ssh")]
Self::SSH(sig) => match sig.algorithm() {
ssh_key::Algorithm::Dsa => Ok(SignatureScheme::SshDsa),
ssh_key::Algorithm::Ecdsa { curve } => match curve {
ssh_key::EcdsaCurve::NistP256 => {
Ok(SignatureScheme::SshEcdsaP256)
}
ssh_key::EcdsaCurve::NistP384 => {
Ok(SignatureScheme::SshEcdsaP384)
}
_ => Err(Error::ssh("Unsupported SSH ECDSA curve")),
},
ssh_key::Algorithm::Ed25519 => Ok(SignatureScheme::SshEd25519),
_ => Err(Error::ssh("Unsupported SSH signature algorithm")),
},
#[cfg(feature = "ssh")]
Self::SSH(_) => unreachable!(),
#[cfg(feature = "pqcrypto")]
Self::MLDSA(sig) => match sig.level() {
crate::MLDSA::MLDSA44 => Ok(SignatureScheme::MLDSA44),
crate::MLDSA::MLDSA65 => Ok(SignatureScheme::MLDSA65),
crate::MLDSA::MLDSA87 => Ok(SignatureScheme::MLDSA87),
},
#[cfg(not(any(
feature = "secp256k1",
feature = "ed25519",
feature = "ssh",
feature = "pqcrypto"
)))]
_ => unreachable!(),
}
}
}
impl std::fmt::Debug for Signature {
#[allow(unreachable_patterns)]
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#[cfg(feature = "secp256k1")]
Signature::Schnorr(data) => _f
.debug_struct("Schnorr")
.field("data", &hex::encode(data))
.finish(),
#[cfg(feature = "secp256k1")]
Signature::ECDSA(data) => _f
.debug_struct("ECDSA")
.field("data", &hex::encode(data))
.finish(),
#[cfg(feature = "ed25519")]
Signature::Ed25519(data) => _f
.debug_struct("Ed25519")
.field("data", &hex::encode(data))
.finish(),
#[cfg(feature = "ssh")]
Signature::SSH(sig) => {
_f.debug_struct("SSH").field("sig", sig).finish()
}
#[cfg(feature = "pqcrypto")]
Signature::MLDSA(sig) => {
_f.debug_struct("MLDSA").field("sig", sig).finish()
}
#[cfg(not(any(
feature = "secp256k1",
feature = "ed25519",
feature = "ssh",
feature = "pqcrypto"
)))]
_ => unreachable!(),
}
}
}
impl AsRef<Signature> for Signature {
fn as_ref(&self) -> &Signature { self }
}
impl CBORTagged for Signature {
fn cbor_tags() -> Vec<dcbor::Tag> {
tags_for_values(&[tags::TAG_SIGNATURE])
}
}
impl From<Signature> for CBOR {
fn from(value: Signature) -> Self { value.tagged_cbor() }
}
impl CBORTaggedEncodable for Signature {
#[allow(unreachable_patterns)]
fn untagged_cbor(&self) -> CBOR {
match self {
#[cfg(feature = "secp256k1")]
Signature::Schnorr(data) => CBOR::to_byte_string(data),
#[cfg(feature = "secp256k1")]
Signature::ECDSA(data) => {
vec![(1).into(), CBOR::to_byte_string(data)].into()
}
#[cfg(feature = "ed25519")]
Signature::Ed25519(data) => {
vec![(2).into(), CBOR::to_byte_string(data)].into()
}
#[cfg(feature = "ssh")]
Signature::SSH(sig) => {
let pem = sig.to_pem(LineEnding::LF).unwrap();
CBOR::to_tagged_value(tags::TAG_SSH_TEXT_SIGNATURE, pem)
}
#[cfg(feature = "pqcrypto")]
Signature::MLDSA(sig) => sig.clone().into(),
#[cfg(not(any(
feature = "secp256k1",
feature = "ed25519",
feature = "ssh",
feature = "pqcrypto"
)))]
_ => unreachable!(),
}
}
}
impl TryFrom<CBOR> for Signature {
type Error = dcbor::Error;
fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
Self::from_tagged_cbor(cbor)
}
}
impl CBORTaggedDecodable for Signature {
fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
match cbor.clone().into_case() {
CBORCase::ByteString(bytes) => {
#[cfg(feature = "secp256k1")]
{
Ok(Self::schnorr_from_data_ref(bytes)?)
}
#[cfg(not(feature = "secp256k1"))]
{
let _ = bytes;
Err("Schnorr signature not available without secp256k1 feature".into())
}
}
CBORCase::Array(mut elements) => {
if elements.len() == 2 {
let mut drain = elements.drain(0..);
let ele_0 = drain.next().unwrap().into_case();
#[cfg_attr(
not(any(feature = "secp256k1", feature = "ed25519")),
allow(unused_variables)
)]
let ele_1 = drain.next().unwrap().into_case();
match ele_0 {
#[cfg(feature = "secp256k1")]
CBORCase::ByteString(data) => {
return Ok(Self::schnorr_from_data_ref(data)?);
}
#[cfg(feature = "secp256k1")]
CBORCase::Unsigned(1) => {
if let CBORCase::ByteString(data) = ele_1 {
return Ok(Self::ecdsa_from_data_ref(data)?);
}
}
#[cfg(feature = "ed25519")]
CBORCase::Unsigned(2) => {
if let CBORCase::ByteString(data) = ele_1 {
return Ok(Self::ed25519_from_data_ref(data)?);
}
}
_ => (),
}
}
Err("Invalid signature format".into())
}
#[cfg_attr(not(feature = "ssh"), allow(unused_variables))]
CBORCase::Tagged(tag, item) => match tag.value() {
#[cfg(feature = "pqcrypto")]
tags::TAG_MLDSA_SIGNATURE => {
let sig = MLDSASignature::try_from(cbor)?;
Ok(Self::MLDSA(sig))
}
#[cfg(feature = "ssh")]
tags::TAG_SSH_TEXT_SIGNATURE => {
let string = item.try_into_text()?;
let pem = SshSig::from_pem(string)
.map_err(|_| "Invalid PEM format")?;
Ok(Self::SSH(pem))
}
_ => Err("Invalid signature format".into()),
},
_ => Err("Invalid signature format".into()),
}
}
}