use bc_ur::prelude::*;
#[cfg(feature = "ssh")]
use ssh_key::public::PublicKey as SSHPublicKey;
#[cfg(feature = "ed25519")]
use crate::Ed25519PublicKey;
#[cfg(feature = "pqcrypto")]
use crate::MLDSAPublicKey;
use crate::{Digest, Reference, ReferenceProvider, Signature, Verifier, tags};
#[cfg(feature = "secp256k1")]
use crate::{ECKeyBase, ECPublicKey, SchnorrPublicKey};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum SigningPublicKey {
#[cfg(feature = "secp256k1")]
Schnorr(SchnorrPublicKey),
#[cfg(feature = "secp256k1")]
ECDSA(ECPublicKey),
#[cfg(feature = "ed25519")]
Ed25519(Ed25519PublicKey),
#[cfg(feature = "ssh")]
SSH(SSHPublicKey),
#[cfg(feature = "pqcrypto")]
MLDSA(MLDSAPublicKey),
}
impl SigningPublicKey {
#[cfg(feature = "secp256k1")]
pub fn from_schnorr(key: SchnorrPublicKey) -> Self { Self::Schnorr(key) }
#[cfg(feature = "secp256k1")]
pub fn from_ecdsa(key: ECPublicKey) -> Self { Self::ECDSA(key) }
#[cfg(feature = "ed25519")]
pub fn from_ed25519(key: Ed25519PublicKey) -> Self { Self::Ed25519(key) }
#[cfg(feature = "ssh")]
pub fn from_ssh(key: SSHPublicKey) -> Self { Self::SSH(key) }
#[cfg(feature = "secp256k1")]
pub fn to_schnorr(&self) -> Option<&SchnorrPublicKey> {
match self {
Self::Schnorr(key) => Some(key),
_ => None,
}
}
#[cfg(feature = "secp256k1")]
pub fn to_ecdsa(&self) -> Option<&ECPublicKey> {
match self {
Self::ECDSA(key) => Some(key),
_ => None,
}
}
#[cfg(feature = "ssh")]
pub fn to_ssh(&self) -> Option<&SSHPublicKey> {
match self {
Self::SSH(key) => Some(key),
#[cfg(any(
feature = "secp256k1",
feature = "ed25519",
feature = "pqcrypto"
))]
_ => None,
}
}
}
impl Verifier for SigningPublicKey {
#[allow(unreachable_patterns)]
fn verify(
&self,
_signature: &Signature,
_message: &dyn AsRef<[u8]>,
) -> bool {
match self {
#[cfg(feature = "secp256k1")]
SigningPublicKey::Schnorr(key) => match _signature {
Signature::Schnorr(sig) => key.schnorr_verify(sig, _message),
_ => false,
},
#[cfg(feature = "secp256k1")]
SigningPublicKey::ECDSA(key) => match _signature {
Signature::ECDSA(sig) => key.verify(sig, _message),
_ => false,
},
#[cfg(feature = "ed25519")]
SigningPublicKey::Ed25519(key) => match _signature {
Signature::Ed25519(sig) => key.verify(sig, _message),
#[cfg(any(
feature = "secp256k1",
feature = "ssh",
feature = "pqcrypto"
))]
_ => false,
},
#[cfg(feature = "ssh")]
SigningPublicKey::SSH(key) => match _signature {
Signature::SSH(sig) => {
key.verify(sig.namespace(), _message.as_ref(), sig).is_ok()
}
_ => false,
},
#[cfg(feature = "pqcrypto")]
SigningPublicKey::MLDSA(key) => match _signature {
Signature::MLDSA(sig) => key
.verify(sig, _message)
.map_err(|_| false)
.unwrap_or(false),
_ => false,
},
#[cfg(not(any(
feature = "secp256k1",
feature = "ed25519",
feature = "ssh",
feature = "pqcrypto"
)))]
_ => unreachable!(),
}
}
}
impl AsRef<SigningPublicKey> for SigningPublicKey {
fn as_ref(&self) -> &SigningPublicKey { self }
}
impl CBORTagged for SigningPublicKey {
fn cbor_tags() -> Vec<Tag> {
tags_for_values(&[tags::TAG_SIGNING_PUBLIC_KEY])
}
}
impl From<SigningPublicKey> for CBOR {
fn from(value: SigningPublicKey) -> Self { value.tagged_cbor() }
}
impl CBORTaggedEncodable for SigningPublicKey {
#[allow(unreachable_patterns)]
fn untagged_cbor(&self) -> CBOR {
match self {
#[cfg(feature = "secp256k1")]
SigningPublicKey::Schnorr(key) => CBOR::to_byte_string(key.data()),
#[cfg(feature = "secp256k1")]
SigningPublicKey::ECDSA(key) => {
vec![(1).into(), CBOR::to_byte_string(key.data())].into()
}
#[cfg(feature = "ed25519")]
SigningPublicKey::Ed25519(key) => {
vec![(2).into(), CBOR::to_byte_string(key.data())].into()
}
#[cfg(feature = "ssh")]
SigningPublicKey::SSH(key) => {
let string = key.to_openssh().unwrap();
CBOR::to_tagged_value(tags::TAG_SSH_TEXT_PUBLIC_KEY, string)
}
#[cfg(feature = "pqcrypto")]
SigningPublicKey::MLDSA(key) => key.clone().into(),
#[cfg(not(any(
feature = "secp256k1",
feature = "ed25519",
feature = "ssh",
feature = "pqcrypto"
)))]
_ => unreachable!(),
}
}
}
impl TryFrom<CBOR> for SigningPublicKey {
type Error = dcbor::Error;
fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
Self::from_tagged_cbor(cbor)
}
}
impl CBORTaggedDecodable for SigningPublicKey {
fn from_untagged_cbor(untagged_cbor: CBOR) -> dcbor::Result<Self> {
match untagged_cbor.clone().into_case() {
CBORCase::ByteString(data) => {
#[cfg(feature = "secp256k1")]
{
Ok(Self::Schnorr(SchnorrPublicKey::from_data_ref(data)?))
}
#[cfg(not(feature = "secp256k1"))]
{
let _ = data;
Err("Schnorr public key 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();
#[cfg(feature = "secp256k1")]
if let CBORCase::Unsigned(1) = ele_0
&& let CBORCase::ByteString(data) = ele_1
{
return Ok(Self::ECDSA(ECPublicKey::from_data_ref(
data,
)?));
}
#[cfg(not(feature = "secp256k1"))]
let _ = ele_0;
#[cfg(feature = "ed25519")]
if let CBORCase::Unsigned(2) = ele_0
&& let CBORCase::ByteString(data) = ele_1
{
return Ok(Self::Ed25519(
Ed25519PublicKey::from_data_ref(data)?,
));
}
}
Err("invalid signing public key".into())
}
#[cfg_attr(not(feature = "ssh"), allow(unused_variables))]
CBORCase::Tagged(tag, item) => match tag.value() {
#[cfg(feature = "ssh")]
tags::TAG_SSH_TEXT_PUBLIC_KEY => {
let string = item.try_into_text()?;
let key = SSHPublicKey::from_openssh(&string)
.map_err(|_| "invalid SSH public key")?;
Ok(Self::SSH(key))
}
#[cfg(feature = "pqcrypto")]
tags::TAG_MLDSA_PUBLIC_KEY => {
let key = MLDSAPublicKey::from_tagged_cbor(untagged_cbor)?;
Ok(Self::MLDSA(key))
}
_ => Err("invalid signing public key".into()),
},
_ => Err("invalid signing public key".into()),
}
}
}
#[cfg(feature = "ssh")]
impl ReferenceProvider for SSHPublicKey {
fn reference(&self) -> Reference {
let string = self.to_openssh().unwrap();
let bytes = string.as_bytes();
let digest = Digest::from_image(bytes);
Reference::from_digest(digest)
}
}
impl ReferenceProvider for SigningPublicKey {
fn reference(&self) -> Reference {
Reference::from_digest(Digest::from_image(
self.tagged_cbor().to_cbor_data(),
))
}
}
impl std::fmt::Display for SigningPublicKey {
#[allow(unreachable_patterns)]
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
#[cfg(any(
feature = "secp256k1",
feature = "ed25519",
feature = "ssh",
feature = "pqcrypto"
))]
{
let display_key = match self {
#[cfg(feature = "secp256k1")]
SigningPublicKey::Schnorr(key) => key.to_string(),
#[cfg(feature = "secp256k1")]
SigningPublicKey::ECDSA(key) => key.to_string(),
#[cfg(feature = "ed25519")]
SigningPublicKey::Ed25519(key) => key.to_string(),
#[cfg(feature = "ssh")]
SigningPublicKey::SSH(key) => {
format!("SSHPublicKey({})", key.ref_hex_short())
}
#[cfg(feature = "pqcrypto")]
SigningPublicKey::MLDSA(key) => key.to_string(),
_ => unreachable!(),
};
write!(
_f,
"SigningPublicKey({}, {})",
self.ref_hex_short(),
display_key
)
}
#[cfg(not(any(
feature = "secp256k1",
feature = "ed25519",
feature = "ssh",
feature = "pqcrypto"
)))]
{
match *self {}
}
}
}