Add SLH-DSA design document

Reviewed-by: Paul Dale <ppzgs1@gmail.com>
Reviewed-by: Viktor Dukhovni <viktor@openssl.org>
Reviewed-by: Tim Hudson <tjh@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/25882)
This commit is contained in:
slontis 2024-11-08 17:23:18 +11:00 committed by Tomas Mraz
parent 6de411963f
commit acdd2c8bff
2 changed files with 100 additions and 1 deletions

99
doc/designs/slh-dsa.md Normal file
View file

@ -0,0 +1,99 @@
SLH-DSA Design
==============
This document covers OpenSSL specific SLH-DSA implementation details.
FIPS 205 clearly states most of the requirements of SLH-DSA and has comprehensive
pseudo code for all its algorithms.
SLH_DSA Parameters & Functions
------------------------------
There are 12 different parameter sets in FIPS 205. (See Section 11)
There are constants related to these, as well as there being a group of functions
associated with each set.
The constants include things like hash sizes and tree heights.
OpenSSL will have 12 different key managers and 12 corresponding signature functions.
The names used are of the form "SLH-DSA-SHA2-128s" and "SLH-DSA-SHAKE-128f".
There are 7 hash functions used. The algorithms using SHAKE have a much simpler
set of 7 functions as they just use SHAKE-256 XOF (Even for the SHAKE-128 names).
The SHA2 algorithms are much more complex and require HMAC, MGF1, and well as digests.
There are 2 sets of functions for the SHA2 case.
Some of the hash functions use an ADRS object. This is 32 bytes for SHAKE algorithms
and 22 bytes for SHA2. Because SHA2 used a compressed format the ADRS functions are
different.
There are many functions required to implement the sign and verify paths, which include
Merkle trees and WOTS+. The different functions normally call one of 2 of the
7 hash functions, as well as calling ADRS functions to pass to the HASH functions.
Rather that duplicating this code 12 times for every function, instead a
SLH_DSA_CTX object is created.
This contains the HASH functions, the ADRS functions, and the parameter constants.
It also contains pre fetched algorithms.
This SLH_DSA_CTX is then passed to all functions. This context is allocated in the
providers SLH_DSA signature context.
SLH-DSA keys
------------
SLH-DSA keys have 2 elements of size n for both the public and private keys.
Since different algorithms have different key sizes, buffers of the maximum size
will be used to hold the keys (since the keys are only a maximum of 64 bytes each)
struct slh_dsa_key_st {
/* The public key consists of a SEED and ROOT values each of size |n| */
uint8_t pub[SLH_DSA_MAX_KEYLEN];
/* The private key consists of a SEED and PRF values of size |n| */
uint8_t priv[SLH_DSA_MAX_KEYLEN];
size_t key_len; /* This value is set to 2 * n if there is a public key */
/* contains the algorithm name and constants such as |n| */
const SLH_DSA_PARAMS *params;
int has_priv; /* Set to 1 if there is a private key component */
};
The fields 'key_len' and 'has_priv' are used to determine if a key has loaded
the public and private key elements.
The 'params' field is the parameter set which is resolved via the algorithm name.
The FIPS 205 the SLH_DSA private key contains the public key.
In OpenSSL these components are stored separately, so there must always be a
public key in order for the key to be valid.
The key generation process creates a private key and half of the public key
using DRBG's. The public key root component is then computed based on these
values. For ACVP testing these values are supplied as an ENTROPY parameter.
It is assumed that from data will not deal with a partial public key, and if this
is required the user should use the key generation operation.
Pure vs Pre Hashed Signature Generation
----------------------------------------
The normal signing process (called Pure SLH-DSA Signature Generation)
encodes the message internally as 0x00 || len(ctx) || ctx || message.
where B<ctx> is some optional value of size 0x00..0xFF.
ACVP Testing requires the ability for the message to not be encoded also. This
will be controlled by settable parameters.
Pre Hash SLH-DSA Signature Generation encode the message as
0x01 || len(ctx) || ctx || digest_OID || H(message).
The scenario that is stated that this is useful for is when this encoded message
is supplied from an external source.
Currently I do not support the Pre Hash variant as this does not sit well with the
OpenSSL API's. The user could do the encoding themselves and then set the settable
to not encode the passed in message.
Buffers
-------
Many functions need to pass around key elements and return signature buffers of
various sizes which are often updated in loops in parts, all of these sizes
are known quantities. Currently there is no attempt to use wpacket to pass
around these sizes. asserts are currently done by the child functions to check
that the expected size does not exceed the size passed in by the parent.