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