1const algorithm = { name: "ML-DSA-65" };
2const encoder = new TextEncoder();
3
4export async function signAndVerify() {
5 const { publicKey, privateKey } = await crypto.subtle.generateKey(algorithm, true, ["sign", "verify"]);
6 const message = encoder.encode("hello, post-quantum world");
7
8 const signature = await crypto.subtle.sign(algorithm, privateKey, message);
9 const raw = await crypto.subtle.exportKey("raw-public", publicKey); // the bare 1952-byte key
10 console.log("public key", raw.byteLength, "bytes, signature", signature.byteLength, "bytes");
11 console.log("verified:", await crypto.subtle.verify(algorithm, publicKey, signature, message));
12 console.log("tampered message:", await crypto.subtle.verify(algorithm, publicKey, signature, encoder.encode("hello, post-quantum world!")));
13
14 // The context string is part of the algorithm parameters, on both sides
15 const withContext = { ...algorithm, context: encoder.encode("invoice-v1") };
16 const invoiceSignature = await crypto.subtle.sign(withContext, privateKey, message);
17 console.log("with the context:", await crypto.subtle.verify(withContext, publicKey, invoiceSignature, message),
18 "without:", await crypto.subtle.verify(algorithm, publicKey, invoiceSignature, message));
19}
20
21// Verify with an SPKI public key (the DER inside a "BEGIN PUBLIC KEY" file, e.g. from OpenSSL 3.5)
22export async function verifyWithSpki(spki, message, signature) {
23 const publicKey = await crypto.subtle.importKey("spki", spki, algorithm, false, ["verify"]);
24 return crypto.subtle.verify(algorithm, publicKey, signature, message);
25}
26