Home | History | Annotate | Line # | Download | only in designs
      1 ML-DSA Design
      2 ==============
      3 
      4 This document covers OpenSSL specific ML-DSA implementation details.
      5 FIPS 204 clearly states most of the requirements of ML-DSA and has comprehensive
      6 pseudo code for all its algorithms.
      7 
      8 The base code for OpenSSL has been derived from the BoringSSL C++ code.
      9 As OpenSSL is C code, templates can not be used. The OpenSSL code instead uses
     10 parameters to pass algorithm specific constants, and also uses these constants
     11 to run different conditional functions when required.
     12 
     13 ML-DSA Parameters & Per algorithm Functions
     14 -------------------------------------------
     15 
     16 FIPS 204 contains 3 algorithms for different security strengths.
     17 FIPS 204 Section 4 Table 1 & Table 2 specifies different constants that are
     18 stored in a table of `ML_DSA_PARAM` objects.
     19 The constants include key sizes and coefficient ranges.
     20 
     21 OpenSSL uses 3 key managers and 3 signature functions corresponding to the algorithms
     22 ML-DSA-44, ML-DSA-65 and ML-DSA-87.
     23 
     24 ML-DSA only uses SHAKE-128 and SHAKE-256 for digest operations, so these digest
     25 algorithms are pre-fetched and stored within a `ML_DSA` key.
     26 Any functions that require these pre-fetched objects must pass either the key
     27 or the pre-fetched object within the key as a parameter.
     28 A temporary `EVP_MD_CTX` is created as needed and the shake object(s) are set
     29 into this ctx.
     30 
     31 Initially a separate object called `ML_DSA_CTX` object was passed around that
     32 contained 2 `EVP_MD_CTXs` with the pre-fetched `EVP_MD` shake objects. It was
     33 modified to match the ML-KEM code.
     34 
     35 ML-DSA keys
     36 ------------
     37 
     38 Once loaded an 'ML-DSA-KEY' object contains either a public key or a
     39 public/private key pair.
     40 When loading a private key, the public key is always generated. In the event
     41 that the public key is also supplied, an error will occur if the generated public
     42 key does not match the supplied public key.
     43 
     44 An `ML_DSA` polynomial contains 256 32 bit values which is 1K of space.
     45 Keys store vectors of size 'k' or 'l' plus a matrix of size 'k' * 'l',
     46 where (k, l) correspond to (4,4), (6,5) or (8,7). The key data could be large
     47 if buffers of the maximum size of (8,7) are used for the (4,4) use case.
     48 To save space rather than use fixed size buffers, allocations are used instead,
     49 for both the public and private elements. (i.e. The private allocations are not
     50 done when only a public key is loaded).
     51 
     52 Since keys consist of vectors and a matrix of polynomials, a single block
     53 is used to allocate all polynomials, and then the polynomial blocks are
     54 assigned to the individual vectors and matrix. This approach is also used when temporary
     55 vectors, matrices or polynomials are required
     56 
     57 Keys are not allowed to mutate, so checks are done during load to check that the
     58 public and private key components are not changed once set.
     59 
     60 `ossl_ml_dsa_key_get_pub()` and `ossl_ml_dsa_key_get_priv()` return the
     61 encoded forms of the key components (which are stored within the key).
     62 The hash of the encoded public key is also stored in the key.
     63 
     64 The key generation process uses a seed to create a private key, and the public
     65 key is then generated using this private key.
     66 
     67 For ACVP testing the seed may be supplied.
     68 
     69 Pure vs Pre Hashed Signature Generation
     70 ----------------------------------------
     71 
     72 The normal signing process (called Pure ML-DSA Signature Generation)
     73 encodes the message internally as 0x00 || len(ctx) || ctx || message.
     74 where B<ctx> is some optional value of size 0x00..0xFF.
     75 
     76 ACVP Testing requires the ability to process raw messages without the above encoding.
     77 This will be controlled by settable parameters.
     78 
     79 Pre Hash ML-DSA Signature Generation encode the message as
     80 
     81 ```text
     82 0x01 || len(ctx) || ctx || digest_OID || H(message).
     83 ```
     84 
     85 The scenario that is stated that this is useful for is when this encoded message
     86 is supplied from an external source.
     87 This ensures domain separation between signature variants
     88 
     89 Currently OpenSSL does not support the Pre Hash variant as this does not sit
     90 well with the OpenSSL API's.
     91 The user could do the encoding themselves and then set the settable to not
     92 encode the passed in message.
     93 
     94 Signing API
     95 -------------
     96 
     97 As only the one-shot implementation is required and the message is not digested
     98 the API's used should be
     99 
    100 `EVP_PKEY_sign_message_init()`, `EVP_PKEY_sign()`,
    101 `EVP_PKEY_verify_message_init()`, `EVP_PKEY_verify()`.
    102 
    103 OpenSSL command line support
    104 ----------------------------
    105 
    106 For backwards compatibility reasons `EVP_DigestSignInit_ex()`,
    107 `EVP_DigestSign()`, `EVP_DigestVerifyInit_ex()` and `EVP_DigestVerify()` may
    108 also be used, but the digest passed in `mdname` must be NULL (i.e. it
    109 effectively behaves the same as above).
    110 Passing a non NULL digest results in an error.
    111 
    112 `OSSL_PKEY_PARAM_MANDATORY_DIGEST` must return "" in the key manager getter and
    113 `OSSL_SIGNATURE_PARAM_ALGORITHM_ID` in the signature context getter.
    114 
    115 Encoding/Decoding
    116 -----------------
    117 
    118 Where it makes sense to, WPACKET is used for output (such as signature generation)
    119 and PACKET for reading signature data.
    120 
    121 Constant Time Considerations
    122 ----------------------------
    123 
    124 Similar code to BoringSSL will be added that allows ctgrind to be used to
    125 detect constant time issues.
    126 
    127 There are many places that do hashing in the code, and these are capable (although
    128 it is not likely) of returning errors. There is not attempt to deal with these cases.
    129 
    130 Changes from BoringSSL
    131 ----------------------
    132 
    133 At the time of writing, BoringSSL code only supported ML-DSA-65. Since there
    134 is specialized code for encoding and decoding of different sizes of
    135 polynomial coefficients, code was added to support these different sizes
    136 (e.g hints have 1 bit coefficients so 8 coefficients can be packed into 1 byte)
    137 
    138 Differences between BoringSSL and FIPS 204 pseudo code
    139 ------------------------------------------------------
    140 
    141 The symmetric modulus operation normally gives a result in the range -a/2 ...
    142 a/2.
    143 BoringSSL chose to keep the result positive by adding q and reducing once as
    144 required.
    145 
    146 Montgomery multiplication is used to speed up multiplications (See FIPS 204
    147 Appendix A).
    148