Skip to main content

script_webcrypto/subtlecrypto/
ml_dsa_operation.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use js::context::JSContext;
6use ml_dsa::common::getrandom::SysRng;
7use ml_dsa::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey};
8use ml_dsa::{
9    Generate, KeyExport, KeyInit, Keypair, MlDsa44, MlDsa65, MlDsa87, Signature, SignatureEncoding,
10    SigningKey, VerifyingKey,
11};
12use script_bindings::codegen::GenericBindings::CryptoKeyBinding::{
13    CryptoKeyMethods, CryptoKeyPair, KeyType, KeyUsage,
14};
15use script_bindings::codegen::GenericBindings::SubtleCryptoBinding::{JsonWebKey, KeyFormat};
16use script_bindings::error::Error;
17use script_bindings::root::DomRoot;
18use script_bindings::str::DOMString;
19
20use crate::cryptokey::{CryptoKey, Handle, KeyUsageSliceHelper};
21use crate::subtlecrypto::{
22    Algorithm, ContextParams, CryptoAlgorithm, ExportedKey, JsonWebKeyExt, JwkStringField,
23    KeyAlgorithm, KeyAlgorithmAndDerivatives,
24};
25use crate::traits::Equivalence;
26
27/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-sign>
28pub(crate) fn sign<D: Equivalence>(
29    normalized_algorithm: &ContextParams,
30    key: &CryptoKey<D>,
31    message: &[u8],
32) -> Result<Vec<u8>, Error> {
33    // Step 1. If the context member of normalizedAlgorithm is present and has a length greater than
34    // 255 bytes, then throw an OperationError.
35    if normalized_algorithm
36        .context
37        .as_ref()
38        .is_some_and(|context| context.len() > 255)
39    {
40        return Err(Error::Operation(Some(
41            "The context is present and has a length greater than 255 bytes".into(),
42        )));
43    }
44
45    // Step 2. If the [[type]] internal slot of key is not "private", then throw an
46    // InvalidAccessError.
47    key.ensure_type(KeyType::Private)?;
48
49    // Step 3. Let context be the context member of normalizedAlgorithm or the empty octet string
50    // if the context member of normalizedAlgorithm is not present.
51    let context = normalized_algorithm.context.as_deref().unwrap_or_default();
52
53    // Step 4. Let result be the result of performing the ML-DSA.Sign signing algorithm, as
54    // specified in Section 5.2 of [FIPS-204], with the parameter set indicated by the name member
55    // of normalizedAlgorithm, using the ML-DSA private key associated with key as sk, message as M
56    // and context as ctx.
57    // Step 5. If the ML-DSA.Sign algorithm returned an error, return an OperationError.
58    let result = match normalized_algorithm.name {
59        CryptoAlgorithm::MlDsa44 => {
60            let Handle::MlDsa44PrivateKey(private_key) = key.handle() else {
61                return Err(Error::Operation(Some(
62                    "The key handle is not representing an ML-DSA-44 private key".into(),
63                )));
64            };
65            private_key
66                .expanded_key()
67                .sign_randomized(message, context, &mut SysRng)
68                .map_err(|_| Error::Operation(Some("ML-DSA-44 failed to sign the message".into())))?
69                .to_vec()
70        },
71        CryptoAlgorithm::MlDsa65 => {
72            let Handle::MlDsa65PrivateKey(private_key) = key.handle() else {
73                return Err(Error::Operation(Some(
74                    "The key handle is not representing an ML-DSA-65 private key".into(),
75                )));
76            };
77            private_key
78                .expanded_key()
79                .sign_randomized(message, context, &mut SysRng)
80                .map_err(|_| Error::Operation(Some("ML-DSA-65 failed to sign the message".into())))?
81                .to_vec()
82        },
83        CryptoAlgorithm::MlDsa87 => {
84            let Handle::MlDsa87PrivateKey(private_key) = key.handle() else {
85                return Err(Error::Operation(Some(
86                    "The key handle is not representing an ML-DSA-87 private key".into(),
87                )));
88            };
89            private_key
90                .expanded_key()
91                .sign_randomized(message, context, &mut SysRng)
92                .map_err(|_| Error::Operation(Some("ML-DSA-87 failed to sign the message".into())))?
93                .to_vec()
94        },
95        _ => {
96            return Err(Error::NotSupported(Some(format!(
97                "{} is not an ML-DSA algorithm",
98                normalized_algorithm.name.as_str()
99            ))));
100        },
101    };
102
103    // Step 6. Return result.
104    Ok(result)
105}
106
107/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-verify>
108pub(crate) fn verify<D: Equivalence>(
109    normalized_algorithm: &ContextParams,
110    key: &CryptoKey<D>,
111    message: &[u8],
112    signature: &[u8],
113) -> Result<bool, Error> {
114    // Step 1. If the context member of normalizedAlgorithm is present and has a length greater than
115    // 255 bytes, then throw an OperationError.
116    if normalized_algorithm
117        .context
118        .as_ref()
119        .is_some_and(|context| context.len() > 255)
120    {
121        return Err(Error::Operation(Some(
122            "The context is present and has a length greater than 255 bytes".into(),
123        )));
124    }
125
126    // Step 2. If the [[type]] internal slot of key is not "public", then throw an
127    // InvalidAccessError.
128    key.ensure_type(KeyType::Public)?;
129
130    // Step 3. Let context be the context member of normalizedAlgorithm or the empty octet string
131    // if the context member of normalizedAlgorithm is not present.
132    let context = normalized_algorithm.context.as_deref().unwrap_or_default();
133
134    // Step 4. Let result be the result of performing the ML-DSA.Verify verification algorithm, as
135    // specified in Section 5.3 of [FIPS-204], with the parameter set indicated by the name member
136    // of normalizedAlgorithm, using the ML-DSA public key associated with key as pk, message as M,
137    // signature as σ and context as ctx.
138    // Step 5. If the ML-DSA.Verify algorithm returned an error, return an OperationError.
139    let result = match normalized_algorithm.name {
140        CryptoAlgorithm::MlDsa44 => {
141            let Handle::MlDsa44PublicKey(public_key) = key.handle() else {
142                return Err(Error::Operation(Some(
143                    "The key handle is not representing an ML-DSA-44 public key".into(),
144                )));
145            };
146            match Signature::try_from(signature) {
147                Ok(signature) => public_key.verify_with_context(message, context, &signature),
148                Err(_) => false,
149            }
150        },
151        CryptoAlgorithm::MlDsa65 => {
152            let Handle::MlDsa65PublicKey(public_key) = key.handle() else {
153                return Err(Error::Operation(Some(
154                    "The key handle is not representing an ML-DSA-65 public key".into(),
155                )));
156            };
157            match Signature::try_from(signature) {
158                Ok(signature) => public_key.verify_with_context(message, context, &signature),
159                Err(_) => false,
160            }
161        },
162        CryptoAlgorithm::MlDsa87 => {
163            let Handle::MlDsa87PublicKey(public_key) = key.handle() else {
164                return Err(Error::Operation(Some(
165                    "The key handle is not representing an ML-DSA-87 public key".into(),
166                )));
167            };
168            match Signature::try_from(signature) {
169                Ok(signature) => public_key.verify_with_context(message, context, &signature),
170                Err(_) => false,
171            }
172        },
173        _ => {
174            return Err(Error::NotSupported(Some(format!(
175                "{} is not an ML-DSA algorithm",
176                normalized_algorithm.name.as_str()
177            ))));
178        },
179    };
180
181    // Step 6. Return result.
182    Ok(result)
183}
184
185/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-generate-key>
186pub(crate) fn generate_key<D: Equivalence>(
187    cx: &mut JSContext,
188    global: &D::GlobalScope,
189    normalized_algorithm: &Algorithm,
190    extractable: bool,
191    usages: Vec<KeyUsage>,
192) -> Result<CryptoKeyPair<D>, Error> {
193    // Step 1. If usages contains a value which is not one of "sign" or "verify", then throw a
194    // SyntaxError.
195    usages.ensure_only_contain_entries_from(&[KeyUsage::Sign, KeyUsage::Verify])?;
196
197    // Step 2. Generate an ML-DSA key pair, as described in Section 5.1 of [FIPS-204], with the
198    // parameter set indicated by the name member of normalizedAlgorithm.
199    // Step 3. If the key generation step fails, then throw an OperationError.
200    let (private_key_handle, public_key_handle) = match normalized_algorithm.name {
201        CryptoAlgorithm::MlDsa44 => {
202            let signing_key = SigningKey::<MlDsa44>::generate();
203            let verifying_key = signing_key.verifying_key();
204            (
205                Handle::MlDsa44PrivateKey(signing_key),
206                Handle::MlDsa44PublicKey(verifying_key),
207            )
208        },
209        CryptoAlgorithm::MlDsa65 => {
210            let signing_key = SigningKey::<MlDsa65>::generate();
211            let verifying_key = signing_key.verifying_key();
212            (
213                Handle::MlDsa65PrivateKey(signing_key),
214                Handle::MlDsa65PublicKey(verifying_key),
215            )
216        },
217        CryptoAlgorithm::MlDsa87 => {
218            let signing_key = SigningKey::<MlDsa87>::generate();
219            let verifying_key = signing_key.verifying_key();
220            (
221                Handle::MlDsa87PrivateKey(signing_key),
222                Handle::MlDsa87PublicKey(verifying_key),
223            )
224        },
225        name => {
226            return Err(Error::NotSupported(Some(format!(
227                "{} is not an ML-DSA algorithm",
228                name.as_str()
229            ))));
230        },
231    };
232
233    // Step 4. Let algorithm be a new KeyAlgorithm object.
234    // Step 5. Set the name attribute of algorithm to the name attribute of normalizedAlgorithm.
235    let algorithm = KeyAlgorithm {
236        name: normalized_algorithm.name,
237    };
238
239    // Step 6. Let publicKey be a new CryptoKey representing the public key of the generated key
240    // pair.
241    // Step 7. Set the [[type]] internal slot of publicKey to "public".
242    // Step 8. Set the [[algorithm]] internal slot of publicKey to algorithm.
243    // Step 9. Set the [[extractable]] internal slot of publicKey to true.
244    // Step 10. Set the [[usages]] internal slot of publicKey to be the usage intersection of
245    // usages and [ "verify" ].
246    let public_key = CryptoKey::new(
247        cx,
248        global,
249        KeyType::Public,
250        true,
251        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm.clone()),
252        usages.usage_intersection(&[KeyUsage::Verify]),
253        public_key_handle,
254    );
255
256    // Step 11. Let privateKey be a new CryptoKey representing the private key of the generated key
257    // pair.
258    // Step 12. Set the [[type]] internal slot of privateKey to "private".
259    // Step 13. Set the [[algorithm]] internal slot of privateKey to algorithm.
260    // Step 14. Set the [[extractable]] internal slot of privateKey to extractable.
261    // Step 15. Set the [[usages]] internal slot of privateKey to be the usage intersection of
262    // usages and [ "sign" ].
263    let private_key = CryptoKey::new(
264        cx,
265        global,
266        KeyType::Private,
267        extractable,
268        KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
269        usages.usage_intersection(&[KeyUsage::Sign]),
270        private_key_handle,
271    );
272
273    // Step 16. Let result be a new CryptoKeyPair dictionary.
274    // Step 17. Set the publicKey attribute of result to be publicKey.
275    // Step 18. Set the privateKey attribute of result to be privateKey.
276    let result = CryptoKeyPair {
277        publicKey: Some(public_key),
278        privateKey: Some(private_key),
279    };
280
281    // Step 19. Return result.
282    Ok(result)
283}
284
285/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-import-key>
286pub(crate) fn import_key<D: Equivalence>(
287    cx: &mut JSContext,
288    global: &D::GlobalScope,
289    normalized_algorithm: &Algorithm,
290    format: KeyFormat,
291    key_data: &[u8],
292    extractable: bool,
293    usages: Vec<KeyUsage>,
294) -> Result<DomRoot<CryptoKey<D>>, Error> {
295    // Step 1. Let keyData be the key data to be imported.
296
297    // Step 2.
298    let key = match format {
299        // If format is "spki":
300        KeyFormat::Spki => {
301            // Step 2.1. If usages contains a value which is not "verify" then throw a SyntaxError.
302            usages.ensure_only_contain_entries_from(&[KeyUsage::Verify])?;
303
304            // Step 2.2. Let spki be the result of running the parse a subjectPublicKeyInfo
305            // algorithm over keyData.
306            // Step 2.3. If an error occurred while parsing, then throw a DataError.
307            // Step 2.4.
308            // If the name member of normalizedAlgorithm is "ML-DSA-44":
309            //     Let expectedOid be id-ml-dsa-44 (2.16.840.1.101.3.4.3.17).
310            // If the name member of normalizedAlgorithm is "ML-DSA-65":
311            //     Let expectedOid be id-ml-dsa-65 (2.16.840.1.101.3.4.3.18).
312            // If the name member of normalizedAlgorithm is "ML-DSA-87":
313            //     Let expectedOid be id-ml-dsa-87 (2.16.840.1.101.3.4.3.19).
314            // Otherwise:
315            //     throw a NotSupportedError.
316            // Step 2.5. If the algorithm object identifier field of the algorithm
317            // AlgorithmIdentifier field of spki is not equal to expectedOid, then throw a
318            // DataError.
319            // Step 2.6. If the parameters field of the algorithm AlgorithmIdentifier field of spki
320            // is present, then throw a DataError.
321            // Step 2.7. Let publicKey be the ML-DSA public key identified by the subjectPublicKey
322            // field of spki.
323            let public_key = match normalized_algorithm.name {
324                CryptoAlgorithm::MlDsa44 => Handle::MlDsa44PublicKey(
325                    VerifyingKey::from_public_key_der(key_data).map_err(|_| {
326                        Error::Data(Some(
327                            "Failed to decode the ML-DSA-44 public key from SPKI format".into(),
328                        ))
329                    })?,
330                ),
331                CryptoAlgorithm::MlDsa65 => Handle::MlDsa65PublicKey(
332                    VerifyingKey::from_public_key_der(key_data).map_err(|_| {
333                        Error::Data(Some(
334                            "Failed to decode the ML-DSA-65 public key from SPKI format".into(),
335                        ))
336                    })?,
337                ),
338                CryptoAlgorithm::MlDsa87 => Handle::MlDsa87PublicKey(
339                    VerifyingKey::from_public_key_der(key_data).map_err(|_| {
340                        Error::Data(Some(
341                            "Failed to decode the ML-DSA-87 pulic key from SPKI format".into(),
342                        ))
343                    })?,
344                ),
345                name => {
346                    return Err(Error::NotSupported(Some(format!(
347                        "{} is not an ML-DSA algorithm",
348                        name.as_str()
349                    ))));
350                },
351            };
352
353            // Step 2.8. Let key be a new CryptoKey that represents publicKey.
354            // Step 2.9. Set the [[type]] internal slot of key to "public"
355            // Step 2.10. Let algorithm be a new KeyAlgorithm.
356            // Step 2.11. Set the name attribute of algorithm to the name attribute of
357            // normalizedAlgorithm.
358            // Step 2.12. Set the [[algorithm]] internal slot of key to algorithm.
359            let algorithm = KeyAlgorithm {
360                name: normalized_algorithm.name,
361            };
362            CryptoKey::new(
363                cx,
364                global,
365                KeyType::Public,
366                extractable,
367                KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
368                usages.normalized_value(),
369                public_key,
370            )
371        },
372        // If format is "pkcs8":
373        KeyFormat::Pkcs8 => {
374            // Step 2.1. If usages contains a value which is not "sign" then throw a SyntaxError.
375            usages.ensure_only_contain_entries_from(&[KeyUsage::Sign])?;
376
377            // Step 2.2. Let privateKeyInfo be the result of running the parse a privateKeyInfo
378            // algorithm over keyData.
379            // Step 2.3. If an error occurs while parsing, then throw a DataError.
380            // Step 2.4.
381            // If the name member of normalizedAlgorithm is "ML-DSA-44":
382            //     Let expectedOid be id-ml-dsa-44 (2.16.840.1.101.3.4.3.17).
383            //     Let asn1Structure be the ASN.1 ML-DSA-44-PrivateKey structure.
384            // If the name member of normalizedAlgorithm is "ML-DSA-65":
385            //     Let expectedOid be id-ml-dsa-65 (2.16.840.1.101.3.4.3.18).
386            //     Let asn1Structure be the ASN.1 ML-DSA-65-PrivateKey structure.
387            // If the name member of normalizedAlgorithm is "ML-DSA-87":
388            //     Let expectedOid be id-ml-dsa-87 (2.16.840.1.101.3.4.3.19).
389            //     Let asn1Structure be the ASN.1 ML-DSA-87-PrivateKey structure.
390            // Otherwise:
391            //     throw a NotSupportedError.
392            // Step 2.5. If the algorithm object identifier field of the privateKeyAlgorithm
393            // PrivateKeyAlgorithm field of privateKeyInfo is not equal to expectedOid, then throw
394            // a DataError.
395            // Step 2.6. If the parameters field of the privateKeyAlgorithm
396            // PrivateKeyAlgorithmIdentifier field of privateKeyInfo is present, then throw a
397            // DataError.
398            // Step 2.7. Let mlDsaPrivateKey be the result of performing the parse an ASN.1
399            // structure algorithm, with data as the privateKey field of privateKeyInfo, structure
400            // as asn1Structure, and exactData set to true.
401            // Step 2.8. If an error occurred while parsing, then throw a DataError.
402            // Step 2.9. If mlDsaPrivateKey represents an ML-DSA key in the expandedKey format, or
403            // if mlDsaPrivateKey represents an ML-DSA key in the both format and the both format
404            // is not supported, throw a NotSupportedError.
405            // Step 2.10. If mlDsaPrivateKey represents an ML-DSA key in the both format, and the
406            // seed field does not correspond to the expandedKey field, throw a DataError.
407            //
408            // NOTE: We do not support the `both` format.
409            let ml_dsa_private_key = match normalized_algorithm.name {
410                CryptoAlgorithm::MlDsa44 => Handle::MlDsa44PrivateKey(
411                    SigningKey::from_pkcs8_der(key_data).map_err(|_| {
412                        Error::Data(Some(
413                            "Failed to decode the ML-DSA-44 private key from PKCS#8 format".into(),
414                        ))
415                    })?,
416                ),
417                CryptoAlgorithm::MlDsa65 => Handle::MlDsa65PrivateKey(
418                    SigningKey::from_pkcs8_der(key_data).map_err(|_| {
419                        Error::Data(Some(
420                            "Failed to decode the ML-DSA-65 private key from PKCS#8 format".into(),
421                        ))
422                    })?,
423                ),
424                CryptoAlgorithm::MlDsa87 => Handle::MlDsa87PrivateKey(
425                    SigningKey::from_pkcs8_der(key_data).map_err(|_| {
426                        Error::Data(Some(
427                            "Failed to decode the ML-DSA-87 private key from PKCS#8 format".into(),
428                        ))
429                    })?,
430                ),
431                name => {
432                    return Err(Error::NotSupported(Some(format!(
433                        "{} is not an ML-DSA algorithm",
434                        name.as_str()
435                    ))));
436                },
437            };
438
439            // Step 2.11. Let key be a new CryptoKey that represents the ML-DSA private key
440            // identified by mlDsaPrivateKey.
441            // Step 2.12. Set the [[type]] internal slot of key to "private"
442            // Step 2.13. Let algorithm be a new KeyAlgorithm.
443            // Step 2.14. Set the name attribute of algorithm to the name attribute of
444            // normalizedAlgorithm.
445            // Step 2.15. Set the [[algorithm]] internal slot of key to algorithm.
446            let algorithm = KeyAlgorithm {
447                name: normalized_algorithm.name,
448            };
449            CryptoKey::new(
450                cx,
451                global,
452                KeyType::Private,
453                extractable,
454                KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
455                usages.normalized_value(),
456                ml_dsa_private_key,
457            )
458        },
459        // If format is "raw-public":
460        KeyFormat::Raw_public => {
461            // Step 2.1. If usages contains a value which is not "verify" then throw a SyntaxError.
462            usages.ensure_only_contain_entries_from(&[KeyUsage::Verify])?;
463
464            // Step 2.2. Let algorithm be a new KeyAlgorithm object.
465            // Step 2.3. Set the name attribute of algorithm to the name attribute of
466            // normalizedAlgorithm.
467            // Step 2.4. Let key be a new CryptoKey representing the key data provided in keyData.
468            // Step 2.5. Set the [[type]] internal slot of key to "public"
469            // Step 2.6. Set the [[algorithm]] internal slot of key to algorithm.
470            let public_key = match normalized_algorithm.name {
471                CryptoAlgorithm::MlDsa44 => {
472                    let verifying_key = VerifyingKey::new_from_slice(key_data).map_err(|_| {
473                        Error::Data(Some(
474                            "Failed to parse the public ML-DSA-44 key in raw format".into(),
475                        ))
476                    })?;
477                    Handle::MlDsa44PublicKey(verifying_key)
478                },
479                CryptoAlgorithm::MlDsa65 => {
480                    let verifying_key = VerifyingKey::new_from_slice(key_data).map_err(|_| {
481                        Error::Data(Some(
482                            "Failed to parse the public ML-DSA-65 key in raw format".into(),
483                        ))
484                    })?;
485                    Handle::MlDsa65PublicKey(verifying_key)
486                },
487                CryptoAlgorithm::MlDsa87 => {
488                    let verifying_key = VerifyingKey::new_from_slice(key_data).map_err(|_| {
489                        Error::Data(Some(
490                            "Failed to parse the public ML-DSA-87 key in raw format".into(),
491                        ))
492                    })?;
493                    Handle::MlDsa87PublicKey(verifying_key)
494                },
495                name => {
496                    return Err(Error::NotSupported(Some(format!(
497                        "{} is not an ML-DSA algorithm",
498                        name.as_str()
499                    ))));
500                },
501            };
502            let algorithm = KeyAlgorithm {
503                name: normalized_algorithm.name,
504            };
505            CryptoKey::new(
506                cx,
507                global,
508                KeyType::Public,
509                extractable,
510                KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
511                usages.normalized_value(),
512                public_key,
513            )
514        },
515        // If format is "raw-seed":
516        KeyFormat::Raw_seed => {
517            // Step 2.1. If usages contains an entry which is not "sign" then throw a SyntaxError.
518            usages.ensure_only_contain_entries_from(&[KeyUsage::Sign])?;
519
520            // Step 2.2. Let data be keyData.
521            // Step 2.3. If the length in bits of data is not 256 then throw a DataError.
522            // Step 2.4. Let privateKey be the result of performing the ML-DSA.KeyGen_internal
523            // function described in Section 6.1 of [FIPS-204] with the parameter set indicated by
524            // the name member of normalizedAlgorithm, using data as ξ.
525            let private_key = match normalized_algorithm.name {
526                CryptoAlgorithm::MlDsa44 => {
527                    let signing_key = SigningKey::new_from_slice(key_data).map_err(|_| {
528                        Error::Data(Some(
529                            "Failed to parse the private ML-DSA-44 key in raw format".into(),
530                        ))
531                    })?;
532                    Handle::MlDsa44PrivateKey(signing_key)
533                },
534                CryptoAlgorithm::MlDsa65 => {
535                    let signing_key = SigningKey::new_from_slice(key_data).map_err(|_| {
536                        Error::Data(Some(
537                            "Failed to parse the private ML-DSA-65 key in raw format".into(),
538                        ))
539                    })?;
540                    Handle::MlDsa65PrivateKey(signing_key)
541                },
542                CryptoAlgorithm::MlDsa87 => {
543                    let signing_key = SigningKey::new_from_slice(key_data).map_err(|_| {
544                        Error::Data(Some(
545                            "Failed to parse the private ML-DSA-87 key in raw format".into(),
546                        ))
547                    })?;
548                    Handle::MlDsa87PrivateKey(signing_key)
549                },
550                name => {
551                    return Err(Error::NotSupported(Some(format!(
552                        "{} is not an ML-DSA algorithm",
553                        name.as_str()
554                    ))));
555                },
556            };
557
558            // Step 2.5. Let key be a new CryptoKey that represents the ML-DSA private key
559            // identified by privateKey.
560            // Step 2.6. Set the [[type]] internal slot of key to "private"
561            // Step 2.7. Let algorithm be a new KeyAlgorithm.
562            // Step 2.8. Set the name attribute of algorithm to the name attribute of
563            // normalizedAlgorithm.
564            // Step 2.9. Set the [[algorithm]] internal slot of key to algorithm.
565            let algorithm = KeyAlgorithm {
566                name: normalized_algorithm.name,
567            };
568            CryptoKey::new(
569                cx,
570                global,
571                KeyType::Private,
572                extractable,
573                KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
574                usages.normalized_value(),
575                private_key,
576            )
577        },
578        // If format is "jwk":
579        KeyFormat::Jwk => {
580            // Step 2.1.
581            // If keyData is a JsonWebKey dictionary:
582            //     Let jwk equal keyData.
583            // Otherwise:
584            //     Throw a DataError.
585            let jwk = JsonWebKey::parse(cx, key_data)?;
586
587            // Step 2.2. If the priv field is present and usages contains a value which is not
588            // "sign", or, if the priv field is not present and usages contains a value which is
589            // not "verify" then throw a SyntaxError.
590            match jwk.priv_.as_ref() {
591                Some(_) => usages.ensure_only_contain_entries_from(&[KeyUsage::Sign])?,
592                None => usages.ensure_only_contain_entries_from(&[KeyUsage::Verify])?,
593            }
594
595            // Step 2.3. If the kty field of jwk is not "AKP", then throw a DataError.
596            if jwk.kty.as_ref().is_none_or(|kty| kty != "AKP") {
597                return Err(Error::Data(Some(
598                    "The kty field of jwk is not \"AKP\"".into(),
599                )));
600            }
601
602            // Step 2.4. If the alg field of jwk is not equal to the name member of
603            // normalizedAlgorithm, then throw a DataError.
604            if jwk
605                .alg
606                .as_ref()
607                .is_none_or(|alg| alg != normalized_algorithm.name.as_str())
608            {
609                return Err(Error::Data(Some(
610                    "The alg field of jwk is not equal to the name member of normalizedAlgorithm"
611                        .into(),
612                )));
613            }
614
615            // Step 2.5. If usages is non-empty and the use field of jwk is present and is not
616            // equal to "sig", then throw a DataError.
617            if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "enc") {
618                return Err(Error::Data(Some(
619                    "Usages is non-empty and the use field of jwk is present and is not \
620                    equal to \"sig\""
621                        .into(),
622                )));
623            }
624
625            // Step 2.6. If the key_ops field of jwk is present, and is invalid according to the
626            // requirements of JSON Web Key [JWK], or it does not contain all of the specified
627            // usages values, then throw a DataError.
628            jwk.check_key_ops(&usages)?;
629
630            // Step 2.7. If the ext field of jwk is present and has the value false and extractable
631            // is true, then throw a DataError.
632            if jwk.ext.is_some_and(|ext| !ext) && extractable {
633                return Err(Error::Data(Some(
634                    "The ext field of jwk is present and has the value false and extractable \
635                    is true"
636                        .into(),
637                )));
638            }
639
640            // Step 2.8.
641            // If the priv field of jwk is present:
642            let (key_type, key_handle) = if jwk.priv_.is_some() {
643                // Step 2.8.1. If the priv attribute of jwk does not contain a valid base64url
644                // encoded seed representing an ML-DSA private key, then throw a DataError.
645                let priv_bytes = jwk.decode_required_string_field(JwkStringField::Priv)?;
646
647                // Step 2.8.2. Let key be a new CryptoKey object that represents the ML-DSA private
648                // key identified by interpreting the priv attribute of jwk as a base64url encoded
649                // seed.
650                // Step 2.8.3. Set the [[type]] internal slot of key to "private".
651                // Step 2.8.4. If the pub attribute of jwk does not contain the base64url encoded
652                // public key representing the ML-DSA public key corresponding to key, then throw a
653                // DataError.
654                // NOTE: The CryptoKey object is created in Step 2.9 - 2.11.
655                let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
656                let private_key_handle = match normalized_algorithm.name {
657                    CryptoAlgorithm::MlDsa44 => {
658                        let signing_key =
659                            SigningKey::new_from_slice(&priv_bytes).map_err(|_| {
660                                Error::Data(Some(
661                                    "Failed to parse the private ML-DSA-44 key in priv attribute"
662                                        .into(),
663                                ))
664                            })?;
665                        let verifying_key =
666                            VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
667                                Error::Data(Some(
668                                    "Failed to parse the public ML-DSA-44 key in pub attribute"
669                                        .into(),
670                                ))
671                            })?;
672                        if signing_key.verifying_key() != verifying_key {
673                            return Err(Error::Data(Some(
674                                "The public key in pub attribute does not match \
675                                    the private key in priv attribute"
676                                    .into(),
677                            )));
678                        }
679                        Handle::MlDsa44PrivateKey(signing_key)
680                    },
681                    CryptoAlgorithm::MlDsa65 => {
682                        let signing_key =
683                            SigningKey::new_from_slice(&priv_bytes).map_err(|_| {
684                                Error::Data(Some(
685                                    "Failed to parse the private ML-DSA-65 key in priv attribute"
686                                        .into(),
687                                ))
688                            })?;
689                        let verifying_key =
690                            VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
691                                Error::Data(Some(
692                                    "Failed to parse the public ML-DSA-65 key in pub attribute"
693                                        .into(),
694                                ))
695                            })?;
696                        if signing_key.verifying_key() != verifying_key {
697                            return Err(Error::Data(Some(
698                                "The public key in pub attribute does not match \
699                                    the private key in priv attribute"
700                                    .into(),
701                            )));
702                        }
703                        Handle::MlDsa65PrivateKey(signing_key)
704                    },
705                    CryptoAlgorithm::MlDsa87 => {
706                        let signing_key =
707                            SigningKey::new_from_slice(&priv_bytes).map_err(|_| {
708                                Error::Data(Some(
709                                    "Failed to parse the private ML-DSA-87 key in priv attribute"
710                                        .into(),
711                                ))
712                            })?;
713                        let verifying_key =
714                            VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
715                                Error::Data(Some(
716                                    "Failed to parse the public ML-DSA-87 key in pub attribute"
717                                        .into(),
718                                ))
719                            })?;
720                        if signing_key.verifying_key() != verifying_key {
721                            return Err(Error::Data(Some(
722                                "The public key in pub attribute does not match \
723                                    the private key in priv attribute"
724                                    .into(),
725                            )));
726                        }
727                        Handle::MlDsa87PrivateKey(signing_key)
728                    },
729                    name => {
730                        return Err(Error::NotSupported(Some(format!(
731                            "{} is not an ML-DSA algorithm",
732                            name.as_str()
733                        ))));
734                    },
735                };
736                (KeyType::Private, private_key_handle)
737            }
738            // Otherwise:
739            else {
740                // Step 2.8.1. If the pub attribute of jwk does not contain a valid base64url
741                // encoded public key representing an ML-DSA public key, then throw a DataError.
742                let pub_bytes = jwk.decode_required_string_field(JwkStringField::Pub)?;
743
744                // Step 2.8.2. Let key be a new CryptoKey object that represents the ML-DSA public
745                // key identified by interpreting the pub attribute of jwk as a base64url encoded
746                // public key.
747                // Step 2.8.3. Set the [[type]] internal slot of key to "public".
748                // NOTE: The CryptoKey object is created in Step 2.9 - 2.11.
749                let public_key_handle = match normalized_algorithm.name {
750                    CryptoAlgorithm::MlDsa44 => {
751                        let verifying_key =
752                            VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
753                                Error::Data(Some(
754                                    "Failed to parse the public ML-DSA-44 key in pub attribute"
755                                        .into(),
756                                ))
757                            })?;
758                        Handle::MlDsa44PublicKey(verifying_key)
759                    },
760                    CryptoAlgorithm::MlDsa65 => {
761                        let verifying_key =
762                            VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
763                                Error::Data(Some(
764                                    "Failed to parse the public ML-DSA-65 key in pub attribute"
765                                        .into(),
766                                ))
767                            })?;
768                        Handle::MlDsa65PublicKey(verifying_key)
769                    },
770                    CryptoAlgorithm::MlDsa87 => {
771                        let verifying_key =
772                            VerifyingKey::new_from_slice(&pub_bytes).map_err(|_| {
773                                Error::Data(Some(
774                                    "Failed to parse the public ML-DSA-87 key in pub attribute"
775                                        .into(),
776                                ))
777                            })?;
778                        Handle::MlDsa87PublicKey(verifying_key)
779                    },
780                    name => {
781                        return Err(Error::NotSupported(Some(format!(
782                            "{} is not an ML-DSA algorithm",
783                            name.as_str()
784                        ))));
785                    },
786                };
787                (KeyType::Public, public_key_handle)
788            };
789
790            // Step 2.9. Let algorithm be a new instance of a KeyAlgorithm object.
791            // Step 2.10. Set the name attribute of algorithm to the name member of
792            // normalizedAlgorithm.
793            // Step 2.11. Set the [[algorithm]] internal slot of key to algorithm.
794            let algorithm = KeyAlgorithm {
795                name: normalized_algorithm.name,
796            };
797            CryptoKey::new(
798                cx,
799                global,
800                key_type,
801                extractable,
802                KeyAlgorithmAndDerivatives::KeyAlgorithm(algorithm),
803                usages.normalized_value(),
804                key_handle,
805            )
806        },
807        // Otherwise:
808        _ => {
809            // throw a NotSupportedError.
810            return Err(Error::NotSupported(Some(
811                "Unsupported import key format for ML-DSA key".into(),
812            )));
813        },
814    };
815
816    // Step 3. Return key.
817    Ok(key)
818}
819
820/// <https://wicg.github.io/webcrypto-modern-algos/#ml-dsa-operations-export-key>
821pub(crate) fn export_key<D: Equivalence>(
822    format: KeyFormat,
823    key: &CryptoKey<D>,
824) -> Result<ExportedKey, Error> {
825    // Step 1. Let key be the CryptoKey to be exported.
826
827    // Step 2. If the underlying cryptographic key material represented by the [[handle]] internal
828    // slot of key cannot be accessed, then throw an OperationError.
829
830    // Step 3.
831    let result = match format {
832        KeyFormat::Spki => {
833            // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
834            // InvalidAccessError.
835            key.ensure_type(KeyType::Public)?;
836
837            // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
838            let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
839                return Err(Error::Operation(Some(
840                    "[[algorithm]] internal slot of key is not a KeyAlgorithm".into(),
841                )));
842            };
843
844            // Step 3.3.
845            // Let data be an instance of the SubjectPublicKeyInfo ASN.1 structure defined in
846            // [RFC5280] with the following properties:
847            //
848            //     Set the algorithm field to an AlgorithmIdentifier ASN.1 type with the following
849            //     properties:
850            //
851            //         If the name member of keyAlgorithm is "ML-DSA-44":
852            //             Set the algorithm object identifier to the id-ml-dsa-44
853            //             (2.16.840.1.101.3.4.3.17) OID.
854            //
855            //         If the name member of keyAlgorithm is "ML-DSA-65":
856            //             Set the algorithm object identifier to the id-ml-dsa-65
857            //             (2.16.840.1.101.3.4.3.18) OID.
858            //
859            //         If the name member of keyAlgorithm is "ML-DSA-87":
860            //             Set the algorithm object identifier to the id-ml-dsa-87
861            //             (2.16.840.1.101.3.4.3.19) OID.
862            //
863            //         Otherwise:
864            //             throw a NotSupportedError.
865            //
866            //     Set the subjectPublicKey field to keyData.
867            let data = match (key_algorithm.name, key.handle()) {
868                (CryptoAlgorithm::MlDsa44, Handle::MlDsa44PublicKey(public_key)) => {
869                    public_key.to_public_key_der().map_err(|_| {
870                        Error::Operation(Some(
871                            "Failed to encode the ML-DSA-44 public key into SPKI format".into(),
872                        ))
873                    })?
874                },
875                (CryptoAlgorithm::MlDsa65, Handle::MlDsa65PublicKey(public_key)) => {
876                    public_key.to_public_key_der().map_err(|_| {
877                        Error::Operation(Some(
878                            "Failed to encode the ML-DSA-65 public key into SPKI format".into(),
879                        ))
880                    })?
881                },
882                (CryptoAlgorithm::MlDsa87, Handle::MlDsa87PublicKey(public_key)) => {
883                    public_key.to_public_key_der().map_err(|_| {
884                        Error::Operation(Some(
885                            "Failed to encode the ML-DSA-87 public key into SPKI format".into(),
886                        ))
887                    })?
888                },
889                _ => {
890                    return Err(Error::Operation(Some(
891                        "The key handle is not representing an ML-DSA public key".into(),
892                    )));
893                },
894            };
895
896            // Step 3.4. Let result be the result of DER-encoding data.
897            ExportedKey::new_bytes(data.into_vec())
898        },
899        KeyFormat::Pkcs8 => {
900            // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
901            // InvalidAccessError.
902            key.ensure_type(KeyType::Private)?;
903
904            // Step 3.2. Let keyAlgorithm be the [[algorithm]] internal slot of key.
905            let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
906                return Err(Error::Operation(Some(
907                    "[[algorithm]] internal slot of key is not a KeyAlgorithm".into(),
908                )));
909            };
910
911            // Step 3.3.
912            // Let data be an instance of the PrivateKeyInfo ASN.1 structure defined in [RFC5208]
913            // with the following properties:
914            //
915            //     Set the version field to 0.
916            //
917            //     Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type
918            //     with the following properties:
919            //
920            //         If the name member of keyAlgorithm is "ML-DSA-44":
921            //             Set the algorithm object identifier to the id-ml-dsa-44
922            //             (2.16.840.1.101.3.4.3.17) OID.
923            //
924            //         If the name member of keyAlgorithm is "ML-DSA-65":
925            //             Set the algorithm object identifier to the id-ml-dsa-65
926            //             (2.16.840.1.101.3.4.3.18) OID.
927            //
928            //         If the name member of keyAlgorithm is "ML-DSA-87":
929            //             Set the algorithm object identifier to the id-ml-dsa-87
930            //             (2.16.840.1.101.3.4.3.19) OID.
931            //
932            //         Otherwise:
933            //             throw a NotSupportedError.
934            //
935            //     Set the privateKey field as follows:
936            //
937            //         If the name member of keyAlgorithm is "ML-DSA-44":
938            //             Set the privateKey field to the result of DER-encoding a
939            //             ML-DSA-44-PrivateKey ASN.1 type that represents the ML-DSA private key
940            //             seed represented by the [[handle]] internal slot of key using the
941            //             seed-only format (using a context-specific [0] primitive tag with an
942            //             implicit encoding of OCTET STRING).
943            //
944            //         If the name member of keyAlgorithm is "ML-DSA-65":
945            //             Set the privateKey field to the result of DER-encoding a
946            //             ML-DSA-65-PrivateKey ASN.1 type that represents the ML-DSA private key
947            //             seed represented by the [[handle]] internal slot of key using the
948            //             seed-only format (using a context-specific [0] primitive tag with an
949            //             implicit encoding of OCTET STRING).
950            //
951            //         If the name member of keyAlgorithm is "ML-DSA-87":
952            //             Set the privateKey field to the result of DER-encoding a
953            //             ML-DSA-87-PrivateKey ASN.1 type that represents the ML-DSA private key
954            //             seed represented by the [[handle]] internal slot of key using the
955            //             seed-only format (using a context-specific [0] primitive tag with an
956            //             implicit encoding of OCTET STRING).
957            //
958            //         Otherwise:
959            //             throw a NotSupportedError.
960            let private_key_info = match (key_algorithm.name, key.handle()) {
961                (CryptoAlgorithm::MlDsa44, Handle::MlDsa44PrivateKey(private_key)) => {
962                    private_key.to_pkcs8_der().map_err(|_| {
963                        Error::Operation(Some(
964                            "Failed to encode the ML-DSA-44 private key into PKCS#8 format".into(),
965                        ))
966                    })?
967                },
968                (CryptoAlgorithm::MlDsa65, Handle::MlDsa65PrivateKey(private_key)) => {
969                    private_key.to_pkcs8_der().map_err(|_| {
970                        Error::Operation(Some(
971                            "Failed to encode the ML-DSA-65 private key into PKCS#8 format".into(),
972                        ))
973                    })?
974                },
975                (CryptoAlgorithm::MlDsa87, Handle::MlDsa87PrivateKey(private_key)) => {
976                    private_key.to_pkcs8_der().map_err(|_| {
977                        Error::Operation(Some(
978                            "Failed to encode the ML-DSA-87 private key into PKCS#8 format".into(),
979                        ))
980                    })?
981                },
982                _ => {
983                    return Err(Error::Operation(Some(
984                        "The key handle is not representing an ML-DSA private key".into(),
985                    )));
986                },
987            };
988
989            // Step 3.4. Let result be the result of DER-encoding data.
990            ExportedKey::Bytes(private_key_info.to_bytes())
991        },
992        // If format is "raw-public":
993        KeyFormat::Raw_public => {
994            // Step 3.1. If the [[type]] internal slot of key is not "public", then throw an
995            // InvalidAccessError.
996            key.ensure_type(KeyType::Public)?;
997
998            // Step 3.2. Let data be a byte sequence containing the ML-DSA public key represented
999            // by the [[handle]] internal slot of key.
1000            let data = match key.handle() {
1001                Handle::MlDsa44PublicKey(public_key) => public_key.to_bytes().as_slice().to_vec(),
1002                Handle::MlDsa65PublicKey(public_key) => public_key.to_bytes().as_slice().to_vec(),
1003                Handle::MlDsa87PublicKey(public_key) => public_key.to_bytes().as_slice().to_vec(),
1004                _ => {
1005                    return Err(Error::Operation(Some(
1006                        "The key handle is not representing an ML-DSA public key".into(),
1007                    )));
1008                },
1009            };
1010
1011            // Step 3.2. Let result be data.
1012            ExportedKey::new_bytes(data)
1013        },
1014        // If format is "raw-seed":
1015        KeyFormat::Raw_seed => {
1016            // Step 3.1. If the [[type]] internal slot of key is not "private", then throw an
1017            // InvalidAccessError.
1018            key.ensure_type(KeyType::Private)?;
1019
1020            // Step 3.2. Let data be a byte sequence containing the ξ seed variable of the key
1021            // represented by the [[handle]] internal slot of key.
1022            let data = match key.handle() {
1023                Handle::MlDsa44PrivateKey(private_key) => private_key.as_seed().as_slice().to_vec(),
1024                Handle::MlDsa65PrivateKey(private_key) => private_key.as_seed().as_slice().to_vec(),
1025                Handle::MlDsa87PrivateKey(private_key) => private_key.as_seed().as_slice().to_vec(),
1026                _ => {
1027                    return Err(Error::Operation(Some(
1028                        "The key handle is not representing an ML-DSA private key".into(),
1029                    )));
1030                },
1031            };
1032
1033            // Step 3.3. Let result be data.
1034            ExportedKey::new_bytes(data)
1035        },
1036        // If format is "jwk":
1037        KeyFormat::Jwk => {
1038            // Step 3.1. Let jwk be a new JsonWebKey dictionary.
1039            let mut jwk = JsonWebKey::default();
1040
1041            // Step 3.2.  Let keyAlgorithm be the [[algorithm]] internal slot of key.
1042            let KeyAlgorithmAndDerivatives::KeyAlgorithm(key_algorithm) = key.algorithm() else {
1043                return Err(Error::Operation(Some(
1044                    "[[algorithm]] internal slot of key is not a KeyAlgorithm".into(),
1045                )));
1046            };
1047
1048            // Step 3.3. Set the kty attribute of jwk to "AKP".
1049            jwk.kty = Some(DOMString::from_static("AKP"));
1050
1051            // Step 3.4. Set the alg attribute of jwk to the name member of normalizedAlgorithm.
1052            jwk.alg = Some(DOMString::from(key_algorithm.name.as_str()));
1053
1054            // Step 3.5. Set the pub attribute of jwk to the base64url encoded public key
1055            // corresponding to the [[handle]] internal slot of key.
1056            // Step 3.6.
1057            // If the [[type]] internal slot of key is "private":
1058            //     Set the priv attribute of jwk to the base64url encoded seed represented by the
1059            //     [[handle]] internal slot of key.
1060            if key.Type() == KeyType::Private {
1061                match key.handle() {
1062                    Handle::MlDsa44PrivateKey(private_key) => {
1063                        jwk.encode_string_field(
1064                            JwkStringField::Priv,
1065                            private_key.as_seed().as_slice(),
1066                        );
1067                        jwk.encode_string_field(
1068                            JwkStringField::Pub,
1069                            private_key.as_ref().to_bytes().as_slice(),
1070                        );
1071                    },
1072                    Handle::MlDsa65PrivateKey(private_key) => {
1073                        jwk.encode_string_field(
1074                            JwkStringField::Priv,
1075                            private_key.as_seed().as_slice(),
1076                        );
1077                        jwk.encode_string_field(
1078                            JwkStringField::Pub,
1079                            private_key.as_ref().to_bytes().as_slice(),
1080                        );
1081                    },
1082                    Handle::MlDsa87PrivateKey(private_key) => {
1083                        jwk.encode_string_field(
1084                            JwkStringField::Priv,
1085                            private_key.as_seed().as_slice(),
1086                        );
1087                        jwk.encode_string_field(
1088                            JwkStringField::Pub,
1089                            private_key.as_ref().to_bytes().as_slice(),
1090                        );
1091                    },
1092                    _ => {
1093                        return Err(Error::Operation(Some(
1094                            "The key handle is not representing an ML-DSA private key".into(),
1095                        )));
1096                    },
1097                }
1098            } else {
1099                match key.handle() {
1100                    Handle::MlDsa44PublicKey(public_key) => {
1101                        jwk.encode_string_field(
1102                            JwkStringField::Pub,
1103                            public_key.to_bytes().as_slice(),
1104                        );
1105                    },
1106                    Handle::MlDsa65PublicKey(public_key) => {
1107                        jwk.encode_string_field(
1108                            JwkStringField::Pub,
1109                            public_key.to_bytes().as_slice(),
1110                        );
1111                    },
1112                    Handle::MlDsa87PublicKey(public_key) => {
1113                        jwk.encode_string_field(
1114                            JwkStringField::Pub,
1115                            public_key.to_bytes().as_slice(),
1116                        );
1117                    },
1118                    _ => {
1119                        return Err(Error::Operation(Some(
1120                            "The key handle is not representing an ML-DSA public key".into(),
1121                        )));
1122                    },
1123                };
1124            }
1125
1126            // Step 3.7. Set the key_ops attribute of jwk to the usages attribute of key.
1127            jwk.set_key_ops(key.usages());
1128
1129            // Step 3.8. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
1130            jwk.ext = Some(key.Extractable());
1131
1132            // Step 3.9. Let result be jwk.
1133            ExportedKey::new_jwk(jwk)
1134        },
1135        // Otherwise:
1136        _ => {
1137            // throw a NotSupportedError.
1138            return Err(Error::NotSupported(Some(
1139                "Unsupported export key format for ML-DSA key".into(),
1140            )));
1141        },
1142    };
1143
1144    // Step 3. Return result.
1145    Ok(result)
1146}
1147
1148/// <https://wicg.github.io/webcrypto-modern-algos/#SubtleCrypto-method-getPublicKey>
1149/// Step 9 - 15, for ML-DSA
1150pub(crate) fn get_public_key<D: Equivalence>(
1151    cx: &mut JSContext,
1152    global: &D::GlobalScope,
1153    key: &CryptoKey<D>,
1154    algorithm: &KeyAlgorithmAndDerivatives,
1155    usages: Vec<KeyUsage>,
1156) -> Result<DomRoot<CryptoKey<D>>, Error> {
1157    // Step 9. If usages contains an entry which is not supported for a public key by the algorithm
1158    // identified by algorithm, then throw a SyntaxError.
1159    //
1160    // NOTE: See "importKey" operation for supported usages
1161    usages.ensure_only_contain_entries_from(&[KeyUsage::Verify])?;
1162
1163    // Step 10. Let publicKey be a new CryptoKey representing the public key corresponding to the
1164    // private key represented by the [[handle]] internal slot of key.
1165    // Step 11. If an error occurred, then throw a OperationError.
1166    // Step 12. Set the [[type]] internal slot of publicKey to "public".
1167    // Step 13. Set the [[algorithm]] internal slot of publicKey to algorithm.
1168    // Step 14. Set the [[extractable]] internal slot of publicKey to true.
1169    // Step 15. Set the [[usages]] internal slot of publicKey to usages.
1170    let public_key_handle = match key.handle() {
1171        Handle::MlDsa44PrivateKey(private_key) => {
1172            Handle::MlDsa44PublicKey(private_key.verifying_key())
1173        },
1174        Handle::MlDsa65PrivateKey(private_key) => {
1175            Handle::MlDsa65PublicKey(private_key.verifying_key())
1176        },
1177        Handle::MlDsa87PrivateKey(private_key) => {
1178            Handle::MlDsa87PublicKey(private_key.verifying_key())
1179        },
1180        _ => {
1181            return Err(Error::Operation(Some(
1182                "[[handle]] internal slot of key is not an ML-DSA private key".into(),
1183            )));
1184        },
1185    };
1186    let public_key = CryptoKey::new(
1187        cx,
1188        global,
1189        KeyType::Public,
1190        true,
1191        algorithm.clone(),
1192        usages,
1193        public_key_handle,
1194    );
1195
1196    Ok(public_key)
1197}