# FIPS-204 ML-DSA **Module-Lattice-Based Digital Signature Standard** - The signing, verifying, and key generation functions can be split into "external" and "internal" components to simplify APIs and Cryptographic Algorithm Validation Program (CAVP) testing. - The external components generate randomness and perform various checks before calling their internal counterparts. - The internal components are deterministic and can assume that the external components did not encounter error conditions. ## Key Generation ### External ML-DSA.KeyGen() - The key generation takes no input and outputs a public key and a private key, which are both encoded as byte strings. - The algorithm uses an **approved** RBG to generate a 256-bit(32-byte) random seed $\xi$ that is given as input to **ML-DSA.KeyGen_internal**, which produces the public and private keys. --- **ML-DSA.KeyGen()** $Generates \: a \: public-private \: key \: pair$ $\mathbf{Output:}$ Public key $pk \in \mathbb{B}^{32+32*(bitlen(q-1)-d)}$ $\qquad\qquad\:$ Private key $sk \in \mathbb{B}^{32+32+64+32*((l+k)*bitlen(2\eta)+dk)}$ 01: $\xi \leftarrow \mathbb{B}^{32}$ $\qquad\qquad\qquad\qquad$ **▷ Choose random seed** 02: $\mathbf{if}\:\: \xi = NULL \:\: \mathbf{then}$ 03: $\mathbf{return} \: \perp$ $\qquad\qquad\qquad\quad$ **▷ Return an error indication if RBG failed** 04: $\mathbf{end \: if}$ 05: $\mathbf{return}$ **ML-DSA.KeyGen_internal($\xi$)** --- ### Internal ML-DSA.KeyGen_internal($\xi$) - The internal key generation takes a 32-byte random reed as input and outputs a public key and a private key that are both encoded as byte strings. - The seed $\xi$ is expanded as needed using an XOF denoted by $H$ to produce other random values. - The core cryptographic operation computes the public value: - $\mathbf{t = As_1 + s_2}$ - The vector $\mathbf{t}$ together with the matrix $\mathbf{A}$ may be considered an expanded form of the public key. --- **ML-DSA.KeyGen_internal($\xi$)** $Generates \: a \: public-private \: key \: pair \: from \: a \: seed$ $\mathbf{Input:}$ Seed $\xi \in \mathbb{B}^{32}$ $\mathbf{Output:}$ Public key $pk \in \mathbb{B}^{32+32*(bitlen(q-1)-d)}$ $\qquad\qquad\:$ Private key $sk \in \mathbb{B}^{32+32+64+32*((l+k)*bitlen(2\eta)+dk)}$ 01: $(\rho,\rho^{'}, K) \in \mathbb{B}^{32}×\mathbb{B}^{64}×\mathbb{B}^{32} \leftarrow H(\xi||IntToBytes(k,1)||IntToBytes(l.1),128)$ 02: **▷ Expand seed** 03: $\mathbf{\hat{A}} \leftarrow ExpandA(\rho)$ $\qquad$ **▷ A is generated and stored in NTT representation as $\mathbf{\hat{A}}$** 04: $(\mathbf{s_1},\mathbf{s_2}) \leftarrow ExpandS(\rho^{'})$ 05: $\mathbf{t} \leftarrow NTT^{-1}(\mathbf{\hat{A}} \circ NTT(s_1)) + s_2$ $\qquad\qquad$ **▷ Compute $\mathbf{t = As_1 + s_2}$** 06: $(\mathbf{t_1}, \mathbf{t_0}) \leftarrow Power2Round(\mathbf{t})$ $\qquad\qquad\qquad$ **▷ Compress $\mathbf{t}$** 07: $pk \leftarrow pkEncode(\rho, \mathbf{t_1})$ 08: $tr \leftarrow H(pk,\:64)$ 09: $sk \leftarrow skEncode(\rho,\: K,\: tr,\: \mathbf{s_1},\: \mathbf{s_2},\: \mathbf{t_0})$ $\qquad$ **▷ $K$ and $tr$ are for use in signing** 10: $\mathbf{return}$ ($sk$, $pk$) --- ## Signature ### External ML-DSA.Sign($sk$, $M$, $ctx$) - The signing algorithm takes a private key, a message, and a context string as input. - It outputs a signature that is encoded as a byte string. - The algorithm uses an **approved** RBG to generate a 256-bit(32-byte) random seed $rnd$. - If the deterministic variant is desired, then $rnd$ is set to the fixed zero string $\{0\}^{32}$ - The value $rnd$, the private key, and the encoded message are input to **ML-DSA.Sign_internal**, which produces the signature. --- **ML-DSA.Sign($sk$, $M$, $ctx$)** $Generates \: an$ **ML-DSA** $signature.$ $\mathbf{Input:}$ Private key $sk \in \mathbb{B}^{32+32+64+32*((l+k)*bitlen(2\eta)+dk)}$, message $M \in \{0,1\}^*$, context string $ctx$ (a byte string of 255 or fewer bytes). $\mathbf{Output:}$ Signature $\sigma \in \mathbb{B}^{\lambda/4+l*32*(1+bitlen(\gamma_1-1))+\omega+k}.$ 01: $\mathbf{if}$ |$ctx$| $>255$ $\mathbf{then}$ 02: $\quad$ $\mathbf{return}$ $\perp$ $\qquad\qquad$ **▷ Return an error indication if the context string is too long** 03: $\mathbf{end \:\: if}$ 04: $rnd \leftarrow \mathbb{B}^{32}$ $\:\:\:\:\qquad$ **▷ for the optional deterministic variant, substiture $rnd \leftarrow \{0\}^{32}$** 06: $\mathbf{if}$ $rnd$ $=NULL$ $\mathbf{then}$ 07: $\quad$ $\mathbf{return}$ $\perp$ $\qquad\qquad$ **▷ Return an error indication if random bit generation failed** 08: $\mathbf{end \:\: if}$ 09: $M^{'} \leftarrow BytesToBits(IntToBytes(0,1)\:||\:IntToBytes(|ctx|,1)\:||\:ctx)\:||\:M$ 10: $\sigma \leftarrow$ **ML-DSA.Sign_internal($sk$, $M^{'}$, $rnd$)** 11: $\mathbf{return}$ $\:\sigma$ --- ### Internal ML-DSA.Sign_internal($sk$, $M^{'}$, $rnd$) - Outputs a signature encoded as a byte string. - It takes a private key $sk$ encoded as a byte string, a formatted message $M^{'}$ encoded as a bit string, and a 32-byte string $rnd$ as input. - There are 2 ways that a signing algorithm can use **ML-DSA.Sign_internal**: "hedged" and "deterministic". The default "hedged" variants of **ML-DSA.Sign** and **HashML-DSA.Sign** use a fresh random value for $rnd$, while the optional deterministic variants use the constant byte string $\{0\}^{32}$. - The signer first extracts the following from the private key: - the public random seed $\rho$ - the 32-byte private random seed $K$ - the 64-byte hash of the public key $tr$ - the secret polynomial vectors $\mathbf{s_1}$ and $\mathbf{s_2}$ - the polynomial vector $\mathbf{t_0}$ encoding the $d$ least significant bits of each coefficient of the uncompressed public-key polynomial $\mathbf{t}$ - $\rho$ is then expanded to the same matrix $\mathbf{A}$ as in key generation. - **The main part of the signing algorithm** consists of a rejection sampling loop in which each iteration of the loop either **produces a valid signature or an invalid signature** whose release would leak information about the private key. - The loop is repeated until a valid signature is produced, which can then be encoded as a byte string and output. - The signer first produces a "commitment" $\mathbf{w}_1$ and then pseudorandomly derives a "challenge" $c$ from $\mathbf{w}_1$ and the message representative $\mu$. - Finally, the signer computes a response $\mathbf{z} = \mathbf{y}+c\mathbf{s}_1$. - If the check pass, the signer can compute the hint polynomial $\mathbf{h}$, which allow the verifier to reconstruct $\mathbf{w}_1$. - The signer will then output the final signature, which is a byte encoding of the commitment hash $\tilde{c}$, the response $\mathbf{z}$, and the hint $\mathbf{h}$. --- **ML-DSA.Sign_internal($sk$, $M^{'}$, $rnd$)** $Deterministic \: algorithm \: to \: generate \: a \: signature \: for \: a \: formatted \: message \: M^{'}.$ $\mathbf{Input:}$ Private key $sk \in \mathbb{B}^{32+32+64+32*((l+k)*bitlen(2\eta)+dk)}$, formatted messgage $M^{'}\in \{0,1\}^{*}$, and per message randomness or dummy variable $rnd \in \mathbb{B}^{32}$ $\mathbf{Output:}$ Signature $\sigma \in \mathbb{B}^{\lambda/4+l*32*(1+bitlen(\gamma_1-1))+\omega+k}$ 01: $(\rho, K, tr, \mathbf{s_1}, \mathbf{s_2}, \mathbf{t_0}) \leftarrow skDecode(sk)$ 02: $\mathbf{\hat{s_1}} \leftarrow NTT(\mathbf{s_1})$ 03: $\mathbf{\hat{s_2}} \leftarrow NTT(\mathbf{s_2})$ 04: $\mathbf{\hat{t_0}} \leftarrow NTT(\mathbf{t_0})$ 05: $\mathbf{\hat{A}} \leftarrow ExpandA(\rho)$ $\quad$ **▷ $\mathbf{A}$ is generated and stored in NTT representation as $\mathbf{\hat{A}}$** 06: $\mu \leftarrow H(BytesToBits(tr)\:||\:M^{'},\:64)$ $\qquad$ **▷ message representative** 07: $\rho^{''} \leftarrow H(K\:||\:rnd\:||\:\mu,\:64)$ $\qquad\qquad\qquad$ **▷ compute private random seed** 08: $\kappa \leftarrow 0$ $\qquad\qquad\qquad\qquad\qquad\qquad\qquad$ **▷ initialize counter $\kappa$** 09: $(\mathbf{z},\mathbf{h}) \leftarrow \perp$ 10: $\mathbf{while}$ $(\mathbf{z},\mathbf{h}) = \perp$ $\mathbf{do}$ $\qquad\qquad\qquad\qquad$ **▷ rejection sampling loop** 11: $\quad \mathbf{y} \in R^l_q \leftarrow ExpandMask(\rho^{''}, \kappa)$ 12: $\quad \mathbf{w} \leftarrow NTT^{-1}(\mathbf{\hat{A}} \circ NTT(\mathbf{y}))$ $\quad\qquad\quad$ **▷ $w = Ay$** 13: $\quad \mathbf{w_1} \leftarrow HighBits(\mathbf{w})$ $\qquad\qquad\qquad\quad$ **▷ signer's commitment** 14: $\quad \tilde{c} \leftarrow H(\mu \: ||\: w1Encode(\mathbf{w_1})\:,\: \lambda/4)$ $\qquad$ **▷ commitment hash** 15: $\quad c \in R_q \leftarrow SampleInBall(\tilde{c})$ $\qquad\qquad$ **▷ verifier's challenge** 16: $\quad \hat{c} \leftarrow NTT(c)$ 17: $\quad \langle\langle c\mathbf{s_1} \rangle\rangle \leftarrow NTT^{-1}(\hat{c} \circ \mathbf{\hat{s_1}})$ 18: $\quad \langle\langle c\mathbf{s_2} \rangle\rangle \leftarrow NTT^{-1}(\hat{c} \circ \mathbf{\hat{s_2}})$ 19: $\quad \mathbf{z} \leftarrow \mathbf{y} + \langle\langle c\mathbf{s_1} \rangle\rangle$ $\qquad\qquad\qquad\qquad\quad$ **▷ Signer's response: $\mathbf{z} = \mathbf{y}+c\mathbf{s_1}$** 20: $\quad r_0 \leftarrow LowBits(\mathbf{w}- \langle\langle c\mathbf{s_2} \rangle\rangle)$ 21: $\quad \mathbf{if}$ $||\mathbf{z}||_{\infty} \ge \gamma_1 - \beta$ $\mathbf{or}$ $||r_0||_{\infty} \ge \gamma_2 - \beta\:$ $\mathbf{then}$ $(\mathbf{z}, \mathbf{h}) \leftarrow \perp$ **▷ validity checks** 22: $\quad \mathbf{else}$ 23: $\quad\quad \langle \langle c \mathbf{t_0} \rangle\rangle \leftarrow NTT^{-1}(\hat{c} \circ \mathbf{\hat{t_0}})$ 24: $\quad\quad \mathbf{h} \leftarrow MakeHint(-\langle\langle c\mathbf{t_0} \rangle\rangle, \mathbf{w} - \langle\langle c\mathbf{s_2} \rangle\rangle + \langle\langle c\mathbf{t_0} \rangle\rangle)$ $\qquad$ **▷ Signer's hint** 25: $\quad\quad \mathbf{if} \: ||\langle\langle c\mathbf{t_0} \rangle\rangle ||_{\infty} \ge \gamma_2$ $\mathbf{or}$ the number of 1's in $\mathbf{h} > \omega$, $\mathbf{then} \:(\mathbf{z, h}) \leftarrow \perp$ 26: $\quad\quad \mathbf{end \: if}$ 27: $\quad \mathbf{end \: if}$ 28: $\quad \kappa \leftarrow \kappa + l$ 29: $\mathbf{end \: while}$ 30: $\sigma \leftarrow sigEncode(\tilde{c},\mathbf{z}\:mod^{\pm}\:q,\:\mathbf{h})$ 31: $\mathbf{return}\:\: \sigma$ --- ## Verify ### External ML-DSA.Verify($pk$, $M$, $\sigma$, $ctx$) - The verification algorithm takes a public key, a message, a signature, and a context string as input. - Outputs a Boolean value that is true if the signature is valid - The verification is accomplished by calling **ML-DSA.Verify_internal** with the public key, the encoded message, and the signature. --- **ML-DSA.Verify($pk$, $M$, $\sigma$, $ctx$)** $Verifies \: a \: signature \: \sigma \: for \: a \: message \: M$ $\mathbf{Input:}$ Public key $pk \in \mathbb{B}^{32+32*(bitlen(q-1)-d)}$, message $M \in \{0,1\}^{*}$, signature $\sigma \in \mathbb{B}^{\lambda/4+l*32*(1+bitlen(\gamma_1-1))+\omega+k}$, context string $ctx$ (a byte string of 255 or fewer) $\mathbf{Output:}$ Boolean 01: $\mathbf{if}$ $|ctx| > 255$ $\mathbf{then}$ 02: $\quad \mathbf{return}\: \perp$ 03: $\mathbf{end\:if}$ 04: $M^{'} \leftarrow ByteToBits(IntToBytes(0,\:1)\:||\:IntToBytes(|ctx|,\:1)\:||\:ctx)\:||\:M$ 05: $\mathbf{return}$ **ML-DSA.Verify_internal($pk, M^{'}, \sigma$)** --- ### Internal ML-DSA.Verify_internal($pk$, $M^{'}$, $\sigma$) - Takes a public key $pk$ encoded as a byte string, a message $m$ encode as a bit string, and a signature $\sigma$ encoded as a byte string as input. - No randomness is requires for **ML-DSA.Verify_internal**. - The verifier attempts to reconstruct the signer's commitment ($\mathbf{w_1}$) from the public key $pk$ and the signature $\sigma$. - $\mathbf{w_1}$ is computed by rounding $\mathbf{w = Ay}$ - In **ML-DSA.Verify_internal**, the reconstructed value of $w_1$ is called $w_1^{'}$ since it may have been computed in a different way if the signature is invalid. - The $w_1^{'}$ is computed through the following process: 1. Derive the challenge polynomial $c$ from the signer's commitment hash $\tilde{c}$, as similarly is done in **ML-DSA.Sign_internal** 2. Use the signer's response $\mathbf{z}$ to compute - $\mathbf{w}^{'}_{Approx}=\mathbf{Az}-c\mathbf{t_1}*2^d$ - Assuming the signature was computed correctly, it follows that - $\mathbf{w}=\mathbf{Ay}=\mathbf{Az}-c\mathbf{t}+c\mathbf{s_2} \approx\mathbf{w}^{'}_{Approx}=\mathbf{Az}-c\mathbf{t_1}*2^d$ - because $c$ and $\mathbf{s_2}$ have small coefficients, and $\mathbf{t_1}*2^d \approx \mathbf{t}$ 3. Use the signer's hint $\mathbf{h}$ to obtain $\mathbf{w_1}^{'}$ from $\mathbf{w_1}^{'}_{Approx}$. - Finally, the verifier chechs that the signer's response $\mathbf{z}$ and the signer's hint $\mathbf{h}$ are valid and that the reconstructed $\mathbf{w_1}^{'}$ is consistent with the signer's commitment hash $\tilde{c}$. - More precisely, the verifier checks that 1. all of the coefficients of $\mathbf{z}$ are sufficiently small in range ($-(\gamma_1-\beta$), $\gamma_1-\beta$)), 2. $\mathbf{h}$ contains no mroe than $\omega$ nonzero coefficients, 3. $\tilde{c}$ matches the hash $\tilde{c}^{'}$ of the message representative $\mu$ concatenated with $\mathbf{w_1}^{'}$. --- **ML-DSA.Verify_internal($pk$, $M^{'}$, $\sigma$)** $Internal \: function \: to \: verify \: a \: signature \: \sigma \: for \: a \: formatted \: message \: M^{'}$ $\mathbf{Input:}$ Public key $pk \in \mathbb{B}^{32+32*(bitlen(q-1)-d)}$, message $M^{'} \in \{0,1\}^{*}$ $\mathbf{Input:}$ Signature $\sigma \in \mathbb{B}^{\lambda/4+l*32*(1+bitlen(\gamma_1-1))+\omega+k}$ $\mathbf{Output:}$ Boolean 01: $(\rho, \mathbf{t_1}) \leftarrow pkDecode(pk)$ 02: $(\tilde{c}, \mathbf{z, h}) \leftarrow sigDecode(\sigma)$ $\quad$ **▷ signer's commitment hash $\tilde{c}$, response $\mathbf{z}$, and hint $\mathbf{h}$** 03: $\mathbf{if\:h}=\perp\:\mathbf{then\:return} \: false \:\mathbf{end\:if}$ 04: $\mathbf{\hat{A}} \leftarrow ExpandA(\rho)$ $\qquad$ **▷ $\mathbf{A}$ is generated and stored in NTT representation as $\mathbf{\hat{A}}$** 05: $tr \leftarrow H(pk,64)$ 06: $\mu \leftarrow (H(BytesToBits(tr)\:||\:M^{'},\:64))$ 07: $c \in R_q \leftarrow SampleInBall(\tilde{c})$ $\qquad\qquad\qquad$ **▷ compute verifier's challenge from $\tilde{c}$** 08: $\mathbf{w}^{'}_{Approx} \leftarrow NTT^{-1}(\mathbf{\hat{A}} \circ NTT(\mathbf{z}) - NTT(c) \circ NTT(\mathbf{t_1}*2^d))$ <br>$\:\quad$ **▷ $\mathbf{w}^{'}_{Approx}=\mathbf{Az}-c\mathbf{t_1}*2^d$** 09: $\mathbf{w}^1 \leftarrow UseHint(\mathbf{h, w}^{'}_{Approx})$ $\qquad\qquad$ **▷ reconstruction of signer's commitment** 10: $\tilde{c}^{'} \leftarrow H(\mu\:||\:w1Encode(\mathbf{w}^{'}_1), \lambda/4)$ $\qquad\qquad\qquad\:\:$ **▷ hash it; this should match $\tilde{c}$** 11: $\mathbf{return}\:[[\:||\mathbf{z}||_{\infty}<\gamma_1-\beta]] \:\mathbf{and}\:[[\tilde{c}=\tilde{c}^{'}]]$ --- ## Parameter Set **$q$ - modulus**: $2^{23} - 2^{13} + 1 = 8380417$