use ciborium::{Value, value::Integer};
use coset::{
CborSerializable, MlDsaVariant, RegisteredLabel, RegisteredLabelWithPrivate,
iana::{Algorithm, EllipticCurve, EnumI64, KeyOperation, KeyType, OkpKeyParameter},
};
use ml_dsa::{MlDsa44, signature::Verifier};
use super::{SignatureAlgorithm, ed25519_verifying_key, key_id, mldsa44_verifying_key};
use crate::{
CoseKeyBytes, CryptoError,
content_format::CoseKeyContentFormat,
cose::CoseSerializable,
error::{EncodingError, SignatureError},
keys::KeyId,
};
pub(super) enum RawVerifyingKey {
Ed25519(ed25519_dalek::VerifyingKey),
MlDsa44(Box<ml_dsa::VerifyingKey<MlDsa44>>),
}
pub struct VerifyingKey {
pub(super) id: KeyId,
pub(super) inner: RawVerifyingKey,
}
impl std::fmt::Debug for VerifyingKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let key_suffix = match &self.inner {
RawVerifyingKey::Ed25519(_) => "Ed25519",
RawVerifyingKey::MlDsa44(_) => "MlDsa44",
};
let mut debug_struct = f.debug_struct(format!("VerifyingKey::{}", key_suffix).as_str());
debug_struct.field("id", &self.id);
match &self.inner {
RawVerifyingKey::Ed25519(key) => {
debug_struct.field("key", &hex::encode(key.to_bytes()));
}
RawVerifyingKey::MlDsa44(key) => {
let encoded = key.encode();
debug_struct.field("key", &hex::encode(encoded));
}
}
debug_struct.finish()
}
}
impl VerifyingKey {
pub fn algorithm(&self) -> SignatureAlgorithm {
match &self.inner {
RawVerifyingKey::Ed25519(_) => SignatureAlgorithm::Ed25519,
RawVerifyingKey::MlDsa44(_) => SignatureAlgorithm::MlDsa44,
}
}
pub(super) fn verify_raw(&self, signature: &[u8], data: &[u8]) -> Result<(), CryptoError> {
match &self.inner {
RawVerifyingKey::Ed25519(key) => {
let sig = ed25519_dalek::Signature::from_bytes(
signature
.try_into()
.map_err(|_| SignatureError::InvalidSignature)?,
);
key.verify_strict(data, &sig)
.map_err(|_| SignatureError::InvalidSignature.into())
}
RawVerifyingKey::MlDsa44(key) => {
let sig = ml_dsa::Signature::<MlDsa44>::try_from(signature)
.map_err(|_| SignatureError::InvalidSignature)?;
key.verify(data, &sig)
.map_err(|_| SignatureError::InvalidSignature.into())
}
}
}
}
impl CoseSerializable<CoseKeyContentFormat> for VerifyingKey {
fn to_cose(&self) -> CoseKeyBytes {
match &self.inner {
RawVerifyingKey::Ed25519(key) => coset::CoseKeyBuilder::new_okp_key()
.key_id((&self.id).into())
.algorithm(Algorithm::EdDSA)
.param(
OkpKeyParameter::Crv.to_i64(), Value::Integer(Integer::from(EllipticCurve::Ed25519.to_i64())),
)
.param(
OkpKeyParameter::X.to_i64(), Value::Bytes(key.to_bytes().to_vec()),
)
.add_key_op(KeyOperation::Verify)
.build()
.to_vec()
.expect("Verifying key is always serializable")
.into(),
RawVerifyingKey::MlDsa44(key) => coset::CoseKeyBuilder::new_mldsa_pub_key(
MlDsaVariant::MlDsa44,
key.encode().to_vec(),
)
.key_id((&self.id).into())
.add_key_op(KeyOperation::Verify)
.build()
.to_vec()
.expect("Verifying key is always serializable")
.into(),
}
}
fn from_cose(bytes: &CoseKeyBytes) -> Result<Self, EncodingError>
where
Self: Sized,
{
let cose_key = coset::CoseKey::from_slice(bytes.as_ref())
.map_err(|_| EncodingError::InvalidCoseEncoding)?;
let algorithm = cose_key
.alg
.as_ref()
.ok_or(EncodingError::MissingValue("COSE key algorithm"))?;
match (&cose_key.kty, algorithm) {
(
RegisteredLabel::Assigned(KeyType::OKP),
RegisteredLabelWithPrivate::Assigned(Algorithm::EdDSA),
) => Ok(VerifyingKey {
id: key_id(&cose_key)?,
inner: RawVerifyingKey::Ed25519(ed25519_verifying_key(&cose_key)?),
}),
(
RegisteredLabel::Assigned(KeyType::AKP),
RegisteredLabelWithPrivate::Assigned(Algorithm::ML_DSA_44),
) => Ok(VerifyingKey {
id: key_id(&cose_key)?,
inner: RawVerifyingKey::MlDsa44(Box::new(mldsa44_verifying_key(&cose_key)?)),
}),
_ => Err(EncodingError::UnsupportedValue(
"COSE key type or algorithm",
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const VERIFYING_KEY: &[u8] = &[
166, 1, 1, 2, 80, 55, 131, 40, 191, 230, 137, 76, 182, 184, 139, 94, 152, 45, 63, 13, 71,
3, 39, 4, 129, 2, 32, 6, 33, 88, 32, 93, 213, 35, 177, 81, 219, 226, 241, 147, 140, 238,
32, 34, 183, 213, 107, 227, 92, 75, 84, 208, 47, 198, 80, 18, 188, 172, 145, 184, 154, 26,
170,
];
const SIGNED_DATA_RAW: &[u8] = &[
247, 239, 74, 181, 75, 54, 137, 225, 2, 158, 14, 0, 61, 210, 254, 208, 255, 16, 8, 81, 173,
33, 59, 67, 204, 31, 45, 38, 147, 118, 228, 84, 235, 252, 104, 38, 194, 173, 62, 52, 9,
184, 1, 22, 113, 134, 154, 108, 24, 83, 78, 2, 23, 235, 80, 22, 57, 110, 100, 24, 151, 33,
186, 12,
];
#[test]
fn test_cose_roundtrip_encode_verifying() {
let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY)).unwrap();
let cose = verifying_key.to_cose();
let parsed_key = VerifyingKey::from_cose(&cose).unwrap();
assert_eq!(verifying_key.to_cose(), parsed_key.to_cose());
}
#[test]
fn test_testvector() {
let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY)).unwrap();
assert_eq!(verifying_key.algorithm(), SignatureAlgorithm::Ed25519);
verifying_key
.verify_raw(SIGNED_DATA_RAW, b"Test message")
.unwrap();
}
#[test]
fn test_invalid_testvector() {
let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY)).unwrap();
assert_eq!(verifying_key.algorithm(), SignatureAlgorithm::Ed25519);
assert!(
verifying_key
.verify_raw(SIGNED_DATA_RAW, b"Invalid message")
.is_err()
);
}
}