1use ciborium::{Value, value::Integer};
7use coset::{
8 CborSerializable, MlDsaVariant, RegisteredLabel, RegisteredLabelWithPrivate,
9 iana::{Algorithm, EllipticCurve, EnumI64, KeyOperation, KeyType, OkpKeyParameter},
10};
11use ml_dsa::{MlDsa44, signature::Verifier};
12
13use super::{SignatureAlgorithm, ed25519_verifying_key, key_id, mldsa44_verifying_key};
14use crate::{
15 CoseKeyBytes, CryptoError,
16 content_format::CoseKeyContentFormat,
17 cose::CoseSerializable,
18 error::{EncodingError, SignatureError},
19 keys::KeyId,
20};
21
22pub(super) enum RawVerifyingKey {
25 Ed25519(ed25519_dalek::VerifyingKey),
26 MlDsa44(Box<ml_dsa::VerifyingKey<MlDsa44>>),
27}
28
29pub struct VerifyingKey {
33 pub(super) id: KeyId,
34 pub(super) inner: RawVerifyingKey,
35}
36
37impl std::fmt::Debug for VerifyingKey {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 let key_suffix = match &self.inner {
40 RawVerifyingKey::Ed25519(_) => "Ed25519",
41 RawVerifyingKey::MlDsa44(_) => "MlDsa44",
42 };
43 let mut debug_struct = f.debug_struct(format!("VerifyingKey::{}", key_suffix).as_str());
44 debug_struct.field("id", &self.id);
45 match &self.inner {
46 RawVerifyingKey::Ed25519(key) => {
47 debug_struct.field("key", &hex::encode(key.to_bytes()));
48 }
49 RawVerifyingKey::MlDsa44(key) => {
50 let encoded = key.encode();
51 debug_struct.field("key", &hex::encode(encoded));
52 }
53 }
54 debug_struct.finish()
55 }
56}
57
58impl VerifyingKey {
59 pub fn algorithm(&self) -> SignatureAlgorithm {
61 match &self.inner {
62 RawVerifyingKey::Ed25519(_) => SignatureAlgorithm::Ed25519,
63 RawVerifyingKey::MlDsa44(_) => SignatureAlgorithm::MlDsa44,
64 }
65 }
66
67 pub(super) fn verify_raw(&self, signature: &[u8], data: &[u8]) -> Result<(), CryptoError> {
71 match &self.inner {
72 RawVerifyingKey::Ed25519(key) => {
73 let sig = ed25519_dalek::Signature::from_bytes(
74 signature
75 .try_into()
76 .map_err(|_| SignatureError::InvalidSignature)?,
77 );
78 key.verify_strict(data, &sig)
79 .map_err(|_| SignatureError::InvalidSignature.into())
80 }
81 RawVerifyingKey::MlDsa44(key) => {
82 let sig = ml_dsa::Signature::<MlDsa44>::try_from(signature)
83 .map_err(|_| SignatureError::InvalidSignature)?;
84 key.verify(data, &sig)
85 .map_err(|_| SignatureError::InvalidSignature.into())
86 }
87 }
88 }
89}
90
91impl CoseSerializable<CoseKeyContentFormat> for VerifyingKey {
92 fn to_cose(&self) -> CoseKeyBytes {
93 match &self.inner {
94 RawVerifyingKey::Ed25519(key) => coset::CoseKeyBuilder::new_okp_key()
95 .key_id((&self.id).into())
96 .algorithm(Algorithm::EdDSA)
97 .param(
98 OkpKeyParameter::Crv.to_i64(), Value::Integer(Integer::from(EllipticCurve::Ed25519.to_i64())),
100 )
101 .param(
106 OkpKeyParameter::X.to_i64(), Value::Bytes(key.to_bytes().to_vec()),
108 )
109 .add_key_op(KeyOperation::Verify)
110 .build()
111 .to_vec()
112 .expect("Verifying key is always serializable")
113 .into(),
114 RawVerifyingKey::MlDsa44(key) => coset::CoseKeyBuilder::new_mldsa_pub_key(
115 MlDsaVariant::MlDsa44,
116 key.encode().to_vec(),
117 )
118 .key_id((&self.id).into())
119 .add_key_op(KeyOperation::Verify)
120 .build()
121 .to_vec()
122 .expect("Verifying key is always serializable")
123 .into(),
124 }
125 }
126
127 fn from_cose(bytes: &CoseKeyBytes) -> Result<Self, EncodingError>
128 where
129 Self: Sized,
130 {
131 let cose_key = coset::CoseKey::from_slice(bytes.as_ref())
132 .map_err(|_| EncodingError::InvalidCoseEncoding)?;
133
134 let algorithm = cose_key
135 .alg
136 .as_ref()
137 .ok_or(EncodingError::MissingValue("COSE key algorithm"))?;
138 match (&cose_key.kty, algorithm) {
139 (
140 RegisteredLabel::Assigned(KeyType::OKP),
141 RegisteredLabelWithPrivate::Assigned(Algorithm::EdDSA),
142 ) => Ok(VerifyingKey {
143 id: key_id(&cose_key)?,
144 inner: RawVerifyingKey::Ed25519(ed25519_verifying_key(&cose_key)?),
145 }),
146 (
147 RegisteredLabel::Assigned(KeyType::AKP),
148 RegisteredLabelWithPrivate::Assigned(Algorithm::ML_DSA_44),
149 ) => Ok(VerifyingKey {
150 id: key_id(&cose_key)?,
151 inner: RawVerifyingKey::MlDsa44(Box::new(mldsa44_verifying_key(&cose_key)?)),
152 }),
153 _ => Err(EncodingError::UnsupportedValue(
154 "COSE key type or algorithm",
155 )),
156 }
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 const VERIFYING_KEY: &[u8] = &[
165 166, 1, 1, 2, 80, 55, 131, 40, 191, 230, 137, 76, 182, 184, 139, 94, 152, 45, 63, 13, 71,
166 3, 39, 4, 129, 2, 32, 6, 33, 88, 32, 93, 213, 35, 177, 81, 219, 226, 241, 147, 140, 238,
167 32, 34, 183, 213, 107, 227, 92, 75, 84, 208, 47, 198, 80, 18, 188, 172, 145, 184, 154, 26,
168 170,
169 ];
170 const SIGNED_DATA_RAW: &[u8] = &[
171 247, 239, 74, 181, 75, 54, 137, 225, 2, 158, 14, 0, 61, 210, 254, 208, 255, 16, 8, 81, 173,
172 33, 59, 67, 204, 31, 45, 38, 147, 118, 228, 84, 235, 252, 104, 38, 194, 173, 62, 52, 9,
173 184, 1, 22, 113, 134, 154, 108, 24, 83, 78, 2, 23, 235, 80, 22, 57, 110, 100, 24, 151, 33,
174 186, 12,
175 ];
176
177 #[test]
178 fn test_cose_roundtrip_encode_verifying() {
179 let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY)).unwrap();
180 let cose = verifying_key.to_cose();
181 let parsed_key = VerifyingKey::from_cose(&cose).unwrap();
182
183 assert_eq!(verifying_key.to_cose(), parsed_key.to_cose());
184 }
185
186 #[test]
187 fn test_testvector() {
188 let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY)).unwrap();
189 assert_eq!(verifying_key.algorithm(), SignatureAlgorithm::Ed25519);
190
191 verifying_key
192 .verify_raw(SIGNED_DATA_RAW, b"Test message")
193 .unwrap();
194 }
195
196 #[test]
197 fn test_invalid_testvector() {
198 let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY)).unwrap();
199 assert_eq!(verifying_key.algorithm(), SignatureAlgorithm::Ed25519);
200
201 assert!(
203 verifying_key
204 .verify_raw(SIGNED_DATA_RAW, b"Invalid message")
205 .is_err()
206 );
207 }
208}