Post Quantum Signatures in a Browser with JavaScript

Prof Bill Buchanan OBE FRSE
3 min readNov 6, 2024

NIST has now standardized Dilithium with FIPS 204 (Module-Lattice-Based Digital Signature Standard — ML-DSA) for a PQC signature. The ML-DSA methods are ML-DSA-44 (128-bit security), ML-DSA-65 (192-bit security) and ML-DSA-87 (256-bit security).

With a digital signature, we can prove the integrity of a message, and also the identity of the sender. In this, Bob will take a hash of the message, and then create a signature with his private key. He then sends this signature with the message, and Alice will check the signature with his public key. It is important that we can trust Bob’s public key, so we typically sign it with the private key of Trent, of which Alice will check the signature of Trent for the validity of Bob’s public key.

For ECDSA, RSA, Ed25519 and Ed448 we have:

Method        Public key size (B) Private key size (B)  Signature size (B)  Security level
------------------------------------------------------------------------------------------------------
Ed25519 32 32 64 1 (128-bit) EdDSA
Ed448 57 57 112 3 (192-bit) EdDSA
ECDSA 64 32 48 1 (128-bit) ECDSA
RSA-2048 256 256 256 1 (128-bit) RSA

For key sizes, we see that ML-DSA has larger key sizes than our traditional public key encryption methods:

Method                           Public key size    Private key size   Signature size  Security level
------------------------------------------------------------------------------------------------------
ML-DSA-44 1,312 2,560 2,420 1 (128-bit) Lattice
ML-DSA-65 1,952 4,032 3,309 3 (192-bit) Lattice
ML-DSA-87 2,592 4,866 4,627 5 (256-bit) Lattice

Falcon 512 (Lattice) 897 1,281 690 1 (128-bit) Lattice
Falcon 1024 1,793 2,305 1,330 5 (256-bit) Lattice
Sphincs SHA256-128f Simple 32 64 17,088 1 (128-bit) Hash-based
Sphincs SHA256-192f Simple 48 96 35,664 3 (192-bit) Hash-based
Sphincs SHA256-256f Simple 64 128 49,856 5 (256-bit) Hash-based

The following is some code [here]:

<script type="application/javascript" src="/dilithium.js"></script>
<script type="application/javascript">
for (let key of Object.keys(DilithiumAlgorithm)) {
window[key] = DilithiumAlgorithm[key];
}
function generateKeyPair() {
const level = DilithiumLevel.get(Number(document.getElementById('gen-level').value));
document.getElementById('sign-level').value = document.getElementById('gen-level').value;
document.getElementById('validate-level').value = document.getElementById('gen-level').value;
const keyPair = DilithiumKeyPair.generate(level);
const privateKey = keyPair.getPrivateKey();
document.getElementById('bob-priv-key').value = privateKey.toHex();
document.getElementById('bob-sign-priv-key').value = privateKey.toHex();

const publicKey = keyPair.getPublicKey();
document.getElementById('bob-pub-key').value = publicKey.toHex();
document.getElementById('validate-pub-key').value = publicKey.toHex();
document.getElementById('bob-priv-key-size').value = privateKey.getBytes().length;
document.getElementById('bob-pub-key-size').value = publicKey.getBytes().length;
}
function sign() {
const level = DilithiumLevel.get(Number(document.getElementById('sign-level').value));
document.getElementById('validate-level').value = document.getElementById('sign-level').value;
let privateKey;
try {
privateKey = DilithiumPrivateKey.fromHex(document.getElementById('bob-sign-priv-key').value, level);
} catch (ex) {
alert("Invalid private key provided: " + ex.message);
console.error(ex);
return;
}
const message = new TextEncoder().encode(document.getElementById('bob-sign-message').value);
const signature = privateKey.sign(message);
document.getElementById('validate-message').value = document.getElementById('bob-sign-message').value;
document.getElementById('sign-signature').value = signature.toHex();
document.getElementById('sign-signature-size').value = signature.getBytes().length;
document.getElementById('validate-signature').value = signature.toHex();
}
function validateSignature() {
const level = DilithiumLevel.get(Number(document.getElementById('validate-level').value));
const message = new TextEncoder().encode(document.getElementById('validate-message').value);
let publicKey;
try {
publicKey = DilithiumPublicKey.fromHex(document.getElementById('validate-pub-key').value, level)
} catch (ex) {
alert("Invalid public key provided: " + ex.message);
console.error(ex);
return;
}
let signature;
try {
signature = DilithiumSignature.fromHex(document.getElementById('validate-signature').value, level)
} catch (ex) {
alert("Invalid signature provided: " + ex.message);
console.error(ex);
return;
}
let valid;
try {
valid = publicKey.verifySignature(message, signature);
} catch (ex) {
alert("Error: " + ex.message);
console.error(ex);
return;
}
if (valid) {
document.getElementById('validate-result').value = "Valid Signature";
} else {
document.getElementById('validate-result').value = "Invalid Signature";
}
}
</script>

The coding is here:

A quick demo is:

--

--

Prof Bill Buchanan OBE FRSE
Prof Bill Buchanan OBE FRSE

Written by Prof Bill Buchanan OBE FRSE

Professor of Cryptography. Serial innovator. Believer in fairness, justice & freedom. Based in Edinburgh. Old World Breaker. New World Creator. Building trust.