Module QP
POST QUANTUM (QP)
Post-quantum cryptography (PQC) refers to cryptographic algorithms that are designed to be secure against attacks by quantum computers.
Traditional cryptographic systems, such as RSA and ECC (Elliptic Curve Cryptography), rely on mathematical problems (e.g., integer factorization and discrete logarithms) that are hard for classical computers but can be efficiently solved by quantum computers using algorithms like Shor's algorithm. Post-quantum cryptography aims to address this vulnerability by developing new algorithms based on mathematical problems that are believed to be resistant to quantum attacks.
In this section two post-quantum schemes are described: ML-KEM and ML-DSA. The first one deals with key encapsulation and the second one is a signature scheme.
All function inputs (secret key, public key, message, ciphertext, signature, seed and context) are octets and some of them have a fixed length. Only the version for ML-KEM is passed to the functions as a string.
Global ML-KEM Functions
mlkem_keygen (seed1, seed2, string) | Allow to create a couple (public key, secret key) taking in input two octet of 32 byte length. |
mlkem_pubgen (sk, string) | Generate a public key starting from an octet (the secret key). |
mlkem_pubcheck (pk, string) | Check if the public key has the correct length (it depends on the version of MLKEM used). |
mlkem_sscheck (ss, string) | Check if the length of the shared secret coincides with the expected length of a plaintext encrypted with a version of MLKEM. |
mlkem_enc (pk, m, string) | Encrypt a message of 32 bytes length and generate a shared secret using a public key generated from one of the previous functions. |
mlkem_dec (sk, ct, string) | Decrypt a cyphertext retrieving the shared secret using a secret key generated from one of the previous functions. |
Global ML-DSA Functions
ml_dsa_44_keypair (seed) | Generate a key pair for the ML-DSA-44 cryptographic scheme. |
mldsa44_pubgen (sk) | Generate a public key from a given private key for the ML-DSA-44 cryptographic scheme. |
mldsa44_signature (sk, m, ctx) | Generate a signature for a given message using the ML-DSA-44 cryptographic scheme. |
mldsa44_verify (pk, sig, m, ctx) | Verify a signature for a given message using the ML-DSA-44 cryptographic scheme. |
mldsa44_pubcheck (pk) | Check whether a given public key is valid for the ML-DSA-44 cryptographic scheme. |
mldsa44_signature_check (sig) | Check whether a given signature is valid for the ML-DSA-44 cryptographic scheme. |
Global ML-KEM Functions
ML-KEM (Kyber Key Encapsulation Mechanism) is a post-quantum cryptographic key encapsulation mechanism (KEM) designed for secure key exchange. It is part of the NIST Post-Quantum Cryptography Standardization process and has been selected as a standard for public-key encryption and KEMs. ML-KEM is based on Module Learning With Errors (MLWE), making it resistant to attacks from quantum computers.
It provides different security levels (Kyber-512, Kyber-768, Kyber-1024) that correspond to classical and quantum security estimates. In particular, for each of the below functions, it is possible define in input which version of ML-KEM to use. The three possibilities are:
MLKEM512: size pk = 800 byte, size sk = 1632 byte
MLKEM768: size pk = 1184 byte, size sk = 2400 byte
MLKEM1024: size pk = 1568 byte, size sk = 3168 byte
Unlike traditional encryption schemes, ML-KEM encapsulates a symmetric key, which is then used for further encrypted communication.
At the beginning of each function you require QP = require("qp")
. In this way you load the QP module and assign it to the variable QP.
- mlkem_keygen (seed1, seed2, string)
-
Allow to create a couple (public key, secret key) taking in input two octet of 32 byte length.
The length of the public and secret keys generated depends on the version of MLKEM required. The version
is passed as third argument of the function. If the third argument is NULL, the function applies MLKEM512.
The function generates 64 bytes of random data: the first 32 bytes can be provided as the first argument (seed 1);
the next 32 bytes can be provided as the second argument (seed 2). If either seed is not provided, the function generates random bytes.
Parameters:
- seed1 (optional)
- seed2 (optional)
- string where string can be: "mlkem512", "mlkem768", "mlkem1024"
Returns:
-
a couple of public key and and secret key
Usage:
QP = require("qp") oct1 = OCTET.random(32) oct2 = OCTET.random(32) pk768 = QP.mlkem_keygen(oct1, oct2, "mlkem768").public sk768 = QP.mlkem_keygen(oct1, oct2, "mlkem768").private
- mlkem_pubgen (sk, string)
-
Generate a public key starting from an octet (the secret key). The number of bytes
should coincide with the number of bytes of the secret key of the asscociated MLKEM version used.
Parameters:
- sk a secret key
- string where string can be: "mlkem512", "mlkem768", "mlkem1024"
Returns:
-
pk, the public key associated to an octet
Usage:
QP = require("qp") sk = OCTET.random(2400) -- is the secret key pk = QP.mlkem_pubgen(sk, "mlkem768")
- mlkem_pubcheck (pk, string)
-
Check if the public key has the correct length (it depends on the version of MLKEM used).
Parameters:
- pk a public key
- string where string can be: "mlkem512", "mlkem768", "mlkem1024"
Returns:
-
a boolean value: true if the length is correct, false otherwise
Usage:
QP = require("qp") oct = OCTET.random(230) -- an octet of the wrong length 230 bytes pk = QP.mlkem_pubgen(oct, "mlkem768") -- an octet generated with the correct algorithm bool1 = QP.mlkem_pubcheck(oct, "mlkem768") bool2 = QP.mlkem_pubcheck(pk, "mlkem768") if bool1 then print("true" ) else print("false") end -- Output: false if bool2 then print("true") else print("false") end -- Output: true
- mlkem_sscheck (ss, string)
-
Check if the length of the shared secret coincides with the expected length of a plaintext encrypted with a version of MLKEM.
Parameters:
- ss shared secret
- string , where string can be: "mlkem512", "mlkem768", "mlkem1024"
Returns:
-
boolean value, it is true if the length is correct and false otherwise
Usage:
QP = require("qp") oct1 = OCTET.random(32) oct2 = OCTET.random(32) mess = OCTET.random(32) pk768 = QP.mlkem_keygen(oct1,oct2, "mlkem768").public sk768 = QP.mlkem_keygen(oct1,oct2, "mlkem768").private ss = QP.mlkem_enc(pk768,mess,"mlkem768").secret if QP.mlkem_sscheck(ss, "mlkem768") then print("ok") end
- mlkem_enc (pk, m, string)
-
Encrypt a message of 32 bytes length and generate a shared secret using a public key generated from one
of the previous functions.
Parameters:
- pk a public key whose length depends on the kind of MLKEM used
- m an octet of 32 bytes length
- string , where string can be: "mlkem512", "mlkem768", "mlkem1024"
Returns:
- shared secret (an octet of 32 bytes length)
- ciphertext associated to m
Usage:
QP = require("qp") oct1 = OCTET.random(32) oct2 = OCTET.random(32) mess = OCTET.random(32) pk768 = QP.mlkem_keygen(oct1,oct2, "mlkem768").public ss = QP.mlkem_enc(pk768,oct2,"mlkem768").secret ct = QP.mlkem_enc(pk768,oct2,"mlkem768").cipher
- mlkem_dec (sk, ct, string)
-
Decrypt a cyphertext retrieving the shared secret using a secret key generated from one of the previous functions.
Parameters:
- sk a secret key whose length depends on the kind of MLKEM used
- ct an ciphertext
- string where string can be: "mlkem512", "mlkem768", "mlkem1024"
Returns:
-
shared secret (an octet of 32 bytes length)
Usage:
oct1 = OCTET.random(32) oct2 = OCTET.random(32) mess = OCTET.random(32) sk768 = QP.mlkem_keygen(oct1,oct2, "mlkem768").private pk768 = QP.mlkem_keygen(oct1,oct2, "mlkem768").public ss = QP.mlkem_enc(pk768, mess, "mlkem768").secret ct = QP.mlkem_enc(pk768, mess, "mlkem768").cipher dec = QP.mlkem_dec(sk768, ct, "mlkem768") if ss == dec then print("ok") -- Check if the shared secret coincides with the decryption of the ct end -- Output: ok
Global ML-DSA Functions
The "44" in ML-DSA-44 refers to its security level, which corresponds to approximately 128 bits of classical security and 44 bits of quantum security. This makes it suitable for applications requiring strong security guarantees.
ML-DSA-44 is designed to have relatively small key and signature sizes compared to other post-quantum signature schemes, making it efficient for storage and transmission. The key generation, signing, and verification algorithms are optimized for performance, making ML-DSA-44 practical for use in resource-constrained environments.
ML-DSA-44 can be used in a wide range of applications, including: secure communication, digital identity, blockchain and cryptocurrencies, IoT device.
At the beginning of each function you require QP = require("qp")
. In this way you load the QP module and assign it to the variable QP.
- ml_dsa_44_keypair (seed)
-
Generate a key pair for the ML-DSA-44 cryptographic scheme.
It creates a public and a private key, optionally using a provided seed, and returns them in a Lua table. If the seed is not provided, the function generates random bytes using a random number generator.Parameters:
- seed an optional seed of 32 bytes
Returns:
-
a table containing the two keys
Usage:
QP = require("qp") --select a random seed and generate the key pair seed = OCTET.random(32) keys = QP.mldsa44_keypair(seed) --obtain sk and pk sk = keys.private pk = keys.public
- mldsa44_pubgen (sk)
-
Generate a public key from a given private key for the
ML-DSA-44 cryptographic scheme.
Parameters:
- sk a private key
Returns:
-
a public key
Usage:
QP = require("qp") --generate a random seed and the key pair seed = OCTET.random(32) keys = QP.mldsa44_keypair(seed) --check if the public key is correct if (QP.mldsa44_pubgen(keys.private) == keys.public) then print("ok") else print("different public key") end --print: ok
- mldsa44_signature (sk, m, ctx)
-
Generate a signature for a given message using the ML-DSA-44 cryptographic scheme.
The function takes in input also an optional parameter for the context.
It provides additional flexibility and customization for the signature scheme, allowing you to bind the signature to specific contextual information.
The context is passed as an octet and can be up to 255 bytes in length.
Parameters:
- sk a secret key
- m a message to sign
- ctx an optional context for the signature
Usage:
QP = require("qp") --generate a random seed, a key pair and a random message seed = OCTET.random(32) keys = QP.mldsa44_keypair(seed) m = OCTET.random(32) --sign the message sign = QP.mldsa44_signature(keys.private,m)
- mldsa44_verify (pk, sig, m, ctx)
-
Verify a signature for a given message using the ML-DSA-44 cryptographic scheme.
Parameters:
- pk a public key
- sig a signature
- m a message
- ctx an optional context
Usage:
--from mldsa44_signature, check if the signature is valid if (QP.mldsa44_verify(keys.public, sign, m)) then print("signature verified") else print("signature not verified") end --print: signature verified
- mldsa44_pubcheck (pk)
-
Check whether a given public key is valid for the ML-DSA-44 cryptographic scheme.
The function checks if the public key has the correct length in bytes.
Parameters:
- pk a public key
Returns:
-
a boolean value
Usage:
QP = require("qp") --generate a random seed, and a key pair seed = OCTET.random(32) keys = QP.mldsa44_keypair(seed) --check if the pk is valid if (QP.mldsa44_pubcheck(keys.public)) then print("valid pk") else print("invalid pk") end --print: valid pk
- mldsa44_signature_check (sig)
-
Check whether a given signature is valid for the ML-DSA-44 cryptographic scheme.
Specifically, it verifies if the signature has the correct length.Parameters:
- sig a signature
Returns:
-
a boolean value
Usage:
QP = require("qp") --generate a random seed and a key pair seed = OCTET.random(32) keys = QP.mldsa44_keypair(seed) m = OCTET.random(32) --generate a random message and sign it sign = QP.mldsa44_signature(keys.private,m) --check if the signature is valid if (QP.mldsa44_signature_check(sign)) then print("signature ok") else print("error in the signature") end --print: signature ok