Hello,
sorry for the delay. I have done some changes to the patch based on your review comments. Right now I just need to know what the API should look like because there needs to be the pure and hash variants, init, update, final variants and you wrote about the specific param variants as well. I am not sure how to put them all together without creating tons of specific functions. Could you please describe how the nettle API should look like with all these variants? I will submit the updated patch after I fix the API.
Unfortunately there are a couple of variations (besides the rather important parameter tradeoffs for security, speed, signature size), though:
- Signatures can be randomized or deterministic.
- Signatures can be "pure" or prehash.
- One can use either shake or sha2.
- Some of the sha2 variants use "compressed" addresses, I don't quite
understand what's the point of that additional complication. How do these variants relate to your usecase (mainly tls?), and others'? Is there one variant that is of primary importance, which should be the first one implemented?
We want to use SLH-DSA for document signing. I think I will need to add the pure and prehash API signing functions. But otherwise whether shake or sha2 variant is available it does not matter.
For the api, I think it should be possible to support "pure" signatures without requiring all of the message in memory at once, using a init/update/final-style interface for sign and verify, based on the update function of the underlying hash. Is that worth while?
Sounds like a good idea. I can add it in.
Does the implementation aim to be side channel silent? (According to the spec, implementations should be side channel silent *both* for signature creation and verification; in Nettle, I don't think there's any signature verify code that promises side channel silence).
I think so, yes.
Is the implementation optimized for speed? I see some potential high-level optimizations of the algorithms as described in the spec, like producing merkle root hashes, and maybe auth paths too, based on sequential processing of the leaves, rather than recursion. As well as potential microoptimizations.
I believe the reference implementation does have some high level optimizations.
Why different order for the public key? I would have expected PUB_SEED followed by the root (as in figure 16 in the spec, and same order as in the secret key). Does the NIST spec or some other standard clearly specify the serialization of the keys as byte strings?
It seems to be a typo in the comment. When I checked the logic it is indeed pk: [PUB_SEED || root]
For the Nettle api, I would also consider to not include the pubkey as part of the secret key, but instead pass both pubkey and secret key to the secret key functions. Similar to, e.g., ed25519_sha512_sign.
That seems redundant to me when the spec defines a secret key to contain the public key too. Did you mean that the secret key shouldnt contain the public key part?
I think I would prefer separate top-level functions for the different algorithm variants, rather than the enum slh_dsa_mode. They could all call the same internal function, passing the appropriate params struct as argument.
What about the pure and pre-hash variants and init, update and final api? Can you describe how you would like the API to look like?
It's unclear how this differs from _nettle_treehash further above? Or in general, whats the meaning of "x1" suffix on some functions?
I don't exactly know but I think it is some kind of optimization where the number determines how many addresses are processed at once. Here is an example of x8 from the reference implementation https://github.com/sphincs/sphincsplus/blob/7ec789ace6874d875f4bb84cb61b8115...
Not sure I get why wots_k_mask is used? Is it intended to improve sidechannel-silence or performance?
To me it seems to only switch the logic to generate WOTS signatures. I don't know whether it aims to improve either performance or sidechannel-silence.
Kind regards, Zoltan