use std::{cell::RefCell, rc::Rc};
use bc_rand::RandomNumberGenerator;
#[cfg(feature = "secp256k1")]
use bc_rand::SecureRandomNumberGenerator;
use bc_ur::prelude::*;
#[cfg(feature = "ssh")]
use ssh_key::{HashAlg, LineEnding, private::PrivateKey as SSHPrivateKey};
use super::Verifier;
#[cfg(feature = "ed25519")]
use crate::Ed25519PrivateKey;
#[cfg(any(feature = "secp256k1", feature = "ssh", feature = "pqcrypto"))]
use crate::Error;
#[cfg(feature = "pqcrypto")]
use crate::MLDSAPrivateKey;
use crate::{
Digest, Reference, ReferenceProvider, Result, Signature, Signer,
SigningPublicKey, tags,
};
#[cfg(feature = "secp256k1")]
use crate::{ECKey, ECPrivateKey};
#[derive(Clone)]
pub enum SigningOptions {
Schnorr {
rng: Rc<RefCell<dyn RandomNumberGenerator>>,
},
#[cfg(feature = "ssh")]
Ssh {
namespace: String,
hash_alg: HashAlg,
},
}
#[derive(Clone, PartialEq)]
pub enum SigningPrivateKey {
#[cfg(feature = "secp256k1")]
Schnorr(ECPrivateKey),
#[cfg(feature = "secp256k1")]
ECDSA(ECPrivateKey),
#[cfg(feature = "ed25519")]
Ed25519(Ed25519PrivateKey),
#[cfg(feature = "ssh")]
SSH(Box<SSHPrivateKey>),
#[cfg(feature = "pqcrypto")]
MLDSA(MLDSAPrivateKey),
}
impl std::hash::Hash for SigningPrivateKey {
#[allow(unreachable_patterns)]
fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {
match self {
#[cfg(feature = "secp256k1")]
Self::Schnorr(key) => key.hash(_state),
#[cfg(feature = "secp256k1")]
Self::ECDSA(key) => key.hash(_state),
#[cfg(feature = "ed25519")]
Self::Ed25519(key) => key.hash(_state),
#[cfg(feature = "ssh")]
Self::SSH(key) => key.to_bytes().unwrap().hash(_state),
#[cfg(feature = "pqcrypto")]
Self::MLDSA(key) => key.as_bytes().hash(_state),
#[cfg(not(any(
feature = "secp256k1",
feature = "ed25519",
feature = "ssh",
feature = "pqcrypto"
)))]
_ => unreachable!(),
}
}
}
impl Eq for SigningPrivateKey {}
impl SigningPrivateKey {
#[cfg(feature = "secp256k1")]
pub const fn new_schnorr(key: ECPrivateKey) -> Self { Self::Schnorr(key) }
#[cfg(feature = "secp256k1")]
pub const fn new_ecdsa(key: ECPrivateKey) -> Self { Self::ECDSA(key) }
#[cfg(feature = "ed25519")]
pub const fn new_ed25519(key: Ed25519PrivateKey) -> Self {
Self::Ed25519(key)
}
#[cfg(feature = "ssh")]
pub fn new_ssh(key: SSHPrivateKey) -> Self { Self::SSH(Box::new(key)) }
#[cfg(feature = "secp256k1")]
pub fn to_schnorr(&self) -> Option<&ECPrivateKey> {
match self {
Self::Schnorr(key) => Some(key),
_ => None,
}
}
#[cfg(feature = "secp256k1")]
pub fn is_schnorr(&self) -> bool { self.to_schnorr().is_some() }
#[cfg(feature = "secp256k1")]
pub fn to_ecdsa(&self) -> Option<&ECPrivateKey> {
match self {
Self::ECDSA(key) => Some(key),
_ => None,
}
}
#[cfg(feature = "secp256k1")]
pub fn is_ecdsa(&self) -> bool { self.to_ecdsa().is_some() }
#[cfg(feature = "ssh")]
pub fn to_ssh(&self) -> Option<&SSHPrivateKey> {
match self {
Self::SSH(key) => Some(key),
#[cfg(any(
feature = "secp256k1",
feature = "ed25519",
feature = "pqcrypto"
))]
_ => None,
}
}
#[cfg(feature = "ssh")]
pub fn is_ssh(&self) -> bool { self.to_ssh().is_some() }
#[allow(unreachable_patterns)]
pub fn public_key(&self) -> Result<SigningPublicKey> {
match self {
#[cfg(feature = "secp256k1")]
Self::Schnorr(key) => {
Ok(SigningPublicKey::from_schnorr(key.schnorr_public_key()))
}
#[cfg(feature = "secp256k1")]
Self::ECDSA(key) => {
Ok(SigningPublicKey::from_ecdsa(key.public_key()))
}
#[cfg(feature = "ed25519")]
Self::Ed25519(key) => {
Ok(SigningPublicKey::from_ed25519(key.public_key()))
}
#[cfg(feature = "ssh")]
Self::SSH(key) => {
Ok(SigningPublicKey::from_ssh(key.public_key().clone()))
}
#[cfg(feature = "pqcrypto")]
Self::MLDSA(_) => {
Err(Error::general("Deriving ML-DSA public key not supported"))
}
#[cfg(not(any(
feature = "secp256k1",
feature = "ed25519",
feature = "ssh",
feature = "pqcrypto"
)))]
_ => unreachable!(),
}
}
}
impl SigningPrivateKey {
#[cfg(feature = "secp256k1")]
fn ecdsa_sign(&self, message: impl AsRef<[u8]>) -> Result<Signature> {
if let Some(private_key) = self.to_ecdsa() {
let sig = private_key.ecdsa_sign(message);
Ok(Signature::ecdsa_from_data(sig))
} else {
Err(Error::crypto("Invalid key type for ECDSA signing"))
}
}
#[cfg(feature = "secp256k1")]
pub fn schnorr_sign(
&self,
message: impl AsRef<[u8]>,
rng: Rc<RefCell<dyn RandomNumberGenerator>>,
) -> Result<Signature> {
if let Some(private_key) = self.to_schnorr() {
let sig =
private_key.schnorr_sign_using(message, &mut *rng.borrow_mut());
Ok(Signature::schnorr_from_data(sig))
} else {
Err(Error::crypto("Invalid key type for Schnorr signing"))
}
}
#[cfg(feature = "ed25519")]
pub fn ed25519_sign(&self, message: impl AsRef<[u8]>) -> Result<Signature> {
#[cfg(any(
feature = "secp256k1",
feature = "ssh",
feature = "pqcrypto"
))]
if let Self::Ed25519(key) = self {
let sig = key.sign(message.as_ref());
Ok(Signature::ed25519_from_data(sig))
} else {
Err(Error::crypto("Invalid key type for Ed25519 signing"))
}
#[cfg(not(any(
feature = "secp256k1",
feature = "ssh",
feature = "pqcrypto"
)))]
{
let Self::Ed25519(key) = self;
let sig = key.sign(message.as_ref());
Ok(Signature::ed25519_from_data(sig))
}
}
#[cfg(feature = "ssh")]
fn ssh_sign(
&self,
message: impl AsRef<[u8]>,
namespace: impl AsRef<str>,
hash_alg: HashAlg,
) -> Result<Signature> {
if let Some(private) = self.to_ssh() {
let sig =
private.sign(namespace.as_ref(), hash_alg, message.as_ref())?;
Ok(Signature::from_ssh(sig))
} else {
Err(Error::ssh("Invalid key type for SSH signing"))
}
}
#[cfg(feature = "pqcrypto")]
#[allow(irrefutable_let_patterns)]
fn mldsa_sign(&self, message: impl AsRef<[u8]>) -> Result<Signature> {
if let Self::MLDSA(key) = self {
let sig = key.sign(message.as_ref());
Ok(Signature::MLDSA(sig))
} else {
Err(Error::post_quantum("Invalid key type for MLDSA signing"))
}
}
}
impl Signer for SigningPrivateKey {
#[allow(unreachable_patterns)]
fn sign_with_options(
&self,
_message: &dyn AsRef<[u8]>,
#[cfg_attr(
not(any(feature = "secp256k1", feature = "ssh")),
allow(unused_variables)
)]
options: Option<SigningOptions>,
) -> Result<Signature> {
match self {
#[cfg(feature = "secp256k1")]
Self::Schnorr(_) => {
if let Some(SigningOptions::Schnorr { rng }) = options {
self.schnorr_sign(_message, rng)
} else {
self.schnorr_sign(
_message,
Rc::new(RefCell::new(SecureRandomNumberGenerator)),
)
}
}
#[cfg(feature = "secp256k1")]
Self::ECDSA(_) => self.ecdsa_sign(_message),
#[cfg(feature = "ed25519")]
Self::Ed25519(_) => self.ed25519_sign(_message),
#[cfg(feature = "ssh")]
Self::SSH(_) => {
if let Some(SigningOptions::Ssh { namespace, hash_alg }) =
options
{
self.ssh_sign(_message, namespace, hash_alg)
} else {
Err(Error::ssh(
"Missing namespace and hash algorithm for SSH signing",
))
}
}
#[cfg(feature = "pqcrypto")]
Self::MLDSA(_) => self.mldsa_sign(_message),
#[cfg(not(any(
feature = "secp256k1",
feature = "ed25519",
feature = "ssh",
feature = "pqcrypto"
)))]
_ => unreachable!(),
}
}
}
impl Verifier for SigningPrivateKey {
fn verify(
&self,
_signature: &Signature,
_message: &dyn AsRef<[u8]>,
) -> bool {
match self {
#[cfg(feature = "secp256k1")]
Self::Schnorr(key) => {
if let Signature::Schnorr(sig) = _signature {
key.schnorr_public_key().schnorr_verify(sig, _message)
} else {
false
}
}
_ => false,
}
}
}
impl CBORTagged for SigningPrivateKey {
fn cbor_tags() -> Vec<Tag> {
tags_for_values(&[tags::TAG_SIGNING_PRIVATE_KEY])
}
}
impl From<SigningPrivateKey> for CBOR {
fn from(value: SigningPrivateKey) -> Self { value.tagged_cbor() }
}
impl CBORTaggedEncodable for SigningPrivateKey {
fn untagged_cbor(&self) -> CBOR {
match self {
#[cfg(feature = "secp256k1")]
SigningPrivateKey::Schnorr(key) => CBOR::to_byte_string(key.data()),
#[cfg(feature = "secp256k1")]
SigningPrivateKey::ECDSA(key) => {
vec![(1).into(), CBOR::to_byte_string(key.data())].into()
}
#[cfg(feature = "ed25519")]
SigningPrivateKey::Ed25519(key) => {
vec![(2).into(), CBOR::to_byte_string(key.data())].into()
}
#[cfg(feature = "ssh")]
SigningPrivateKey::SSH(key) => {
let string = key.to_openssh(LineEnding::LF).unwrap();
CBOR::to_tagged_value(
tags::TAG_SSH_TEXT_PRIVATE_KEY,
(*string).clone(),
)
}
#[cfg(feature = "pqcrypto")]
SigningPrivateKey::MLDSA(key) => key.clone().into(),
#[cfg(not(any(
feature = "secp256k1",
feature = "ed25519",
feature = "ssh",
feature = "pqcrypto"
)))]
_ => unreachable!(),
}
}
}
impl TryFrom<CBOR> for SigningPrivateKey {
type Error = dcbor::Error;
fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
Self::from_tagged_cbor(cbor)
}
}
impl CBORTaggedDecodable for SigningPrivateKey {
fn from_untagged_cbor(untagged_cbor: CBOR) -> dcbor::Result<Self> {
match untagged_cbor.into_case() {
CBORCase::ByteString(data) => {
#[cfg(feature = "secp256k1")]
{
Ok(Self::Schnorr(ECPrivateKey::from_data_ref(data)?))
}
#[cfg(not(feature = "secp256k1"))]
{
let _ = data;
Err("Schnorr private key not available without secp256k1 feature".into())
}
}
CBORCase::Array(mut elements) => {
let discriminator = usize::try_from(elements.remove(0))?;
match discriminator {
#[cfg(feature = "secp256k1")]
1 => {
let data = elements.remove(0).try_into_byte_string()?;
let key = ECPrivateKey::from_data_ref(data)?;
Ok(Self::ECDSA(key))
}
#[cfg(feature = "ed25519")]
2 => {
let data = elements.remove(0).try_into_byte_string()?;
let key = Ed25519PrivateKey::from_data_ref(data)?;
Ok(Self::Ed25519(key))
}
_ => Err(format!(
"Invalid discriminator for SigningPrivateKey: {}",
discriminator
)
.into()),
}
}
#[cfg_attr(
not(any(feature = "ssh", feature = "pqcrypto")),
allow(unused_variables)
)]
CBORCase::Tagged(tag, item) => {
let value = tag.value();
match value {
#[cfg(feature = "ssh")]
tags::TAG_SSH_TEXT_PRIVATE_KEY => {
let string = item.try_into_text()?;
let key = SSHPrivateKey::from_openssh(string).map_err(
|_| dcbor::Error::msg("Invalid SSH private key"),
)?;
Ok(Self::SSH(Box::new(key)))
}
#[cfg(feature = "pqcrypto")]
tags::TAG_MLDSA_PRIVATE_KEY => {
let key = MLDSAPrivateKey::from_untagged_cbor(item)?;
Ok(Self::MLDSA(key))
}
_ => Err(format!(
"Invalid CBOR tag for SigningPrivateKey: {value}"
)
.into()),
}
}
_ => Err("Invalid CBOR case for SigningPrivateKey".into()),
}
}
}
impl std::fmt::Debug for SigningPrivateKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SigningPrivateKey")
}
}
impl From<&SigningPrivateKey> for SigningPrivateKey {
fn from(key: &SigningPrivateKey) -> Self { key.clone() }
}
#[cfg(feature = "ssh")]
impl ReferenceProvider for SSHPrivateKey {
fn reference(&self) -> Reference {
let string = self.to_openssh(LineEnding::default()).unwrap();
let bytes = string.as_bytes();
let digest = Digest::from_image(bytes);
Reference::from_digest(digest)
}
}
impl ReferenceProvider for SigningPrivateKey {
fn reference(&self) -> Reference {
Reference::from_digest(Digest::from_image(
self.tagged_cbor().to_cbor_data(),
))
}
}
impl std::fmt::Display for SigningPrivateKey {
#[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")]
SigningPrivateKey::Schnorr(key) => {
format!("SchnorrPrivateKey({})", key.ref_hex_short())
}
#[cfg(feature = "secp256k1")]
SigningPrivateKey::ECDSA(key) => {
format!("ECDSAPrivateKey({})", key.ref_hex_short())
}
#[cfg(feature = "ed25519")]
SigningPrivateKey::Ed25519(key) => key.to_string(),
#[cfg(feature = "ssh")]
SigningPrivateKey::SSH(key) => {
format!("SSHPrivateKey({})", key.ref_hex_short())
}
#[cfg(feature = "pqcrypto")]
SigningPrivateKey::MLDSA(key) => key.to_string(),
_ => unreachable!(),
};
write!(
_f,
"SigningPrivateKey({}, {})",
self.ref_hex_short(),
display_key
)
}
#[cfg(not(any(
feature = "secp256k1",
feature = "ed25519",
feature = "ssh",
feature = "pqcrypto"
)))]
{
match *self {}
}
}
}