Another naming question, should it be dsa_slh_shake128, or maybe dsa_slh128_shake ?
We could stick to the spec param names like slh_dsa_shake_128s. Also do you prefer slh_dsa or dsa_slh prefix?
Do you think it would be reasonable to drop the sha2 variants in the first iteration?
Yes, I think its fine to just have shake variants in the first iteration.
On Wed, Jan 29, 2025 at 10:15 AM Niels Möller nisse@lysator.liu.se wrote:
Zoltan Fridrich zfridric@redhat.com writes:
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.
Do you think it would be reasonable to drop the sha2 variants in the first iteration? Then we wouldn't get the complexities of dealing with either "normal" or "compressed" addresses.
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.
Unfortunately, after a closer look I don't think this is doable. For pure signing, the message is hashed twice, once with the secret prf to produce the randomizer, and then together with randomizer and public key to produce the "digest". It should be possibly to do for the verify operation only, but I don't think that's worth the effort.
It seems to be a typo in the comment. When I checked the logic it is
indeed
pk: [PUB_SEED || root]
That's good.
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'm thinking of something analogous to Nettle's ed25519 api,
void ed25519_sha512_sign (const uint8_t *pub, const uint8_t *priv, size_t length, const uint8_t *msg, uint8_t *signature);
int ed25519_sha512_verify (const uint8_t *pub, size_t length, const uint8_t *msg, const uint8_t *signature);
Here, the pub argument is the same for both functions, and the priv argument is the additional private information needed for signing. If we do this for dsa-slh, pub would include PK.seed and PK.root, and priv would include SK.seed and SK.prf, which seems straightforward to me.
(Signing applications would still be free to store public and private parts together whenever convenient).
Nettle isn't totally consistent though, some older signing functions take only a private key argument which then needs to include everything, others take public key as an additional argument. But I prefer the separate style (it's seems more flexible, and it's also more explicit about what's public and private), if there's no strong reason to do it differently.
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?
Since it seems we can't have the init/update/final variant, I would suggest
void dsa_slh_shake128_sign (const uint8_t *pub, const uint8_t *priv, size_t length, const uint8_t *msg, uint8_t *signature); void dsa_slh_shake128_verify (const uint8_t *pub, size_t length, const uint8_t *msg, const uint8_t *signature);
for the pure variant, and
void dsa_slh_shake128_ph_sha256_sign (const uint8_t *pub, const uint8_t *priv, const uint8_t *digest, uint8_t *signature); void dsa_slh_shake128_ph_sha256_verify (const uint8_t *pub, const uint8_t *digest, const uint8_t *signature);
for the prehash with sha256 variant. There will be lots of functions, but they should all be simple wrappers around some internal function like
void _nettle_dsa_slh_sign_internal (const struct dsa_slh_variant *alg, const uint8_t *pub, const uint8_t *priv, size_t prefix_length, const uint8_t *prefix, size_t length, const uint8_t *msg, uint8_t *signature);
(with the prefix, including the extra zero bytes or prehash object id, passed as a separate argument to avoid allocation and copying of the message).
For link-time dependencies, it's desirable that code specific to one slh-dsa variant is referenced indirectly via the struct dsa_slh_variant, and that source files are organized so that applications using only a single variant don't link-depend on unrelated code.
(The name dsa_slh_variant is a tentative name I made up, feel free to use something different).
Another naming question, should it be dsa_slh_shake128, or maybe dsa_slh128_shake ? There's a shake128 algorithm, but that's *not* used for dsa-slh, it uses shake256. Instead, 128/192/256 refers to the size of the values used internally in slh-dsa and in the resulting signature.
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...
I don't get exactly how that 8-way operations works. It would be interesting to know what performance improvements they see from that (if it's a significant performance boost, we should consider adding something like that later).
And it's still unclear to me what's the difference between _nettle_treehash and _nettle_treehashx1 in the patch.
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.
I think it makes the code harder to understand when signature bytes are copied out to the result in a conditional in the middle a loop (both for wots and for the merkle inclusion proofs). I like the structure in my prototype better, see https://git.lysator.liu.se/nisse/poc-slh-dsa/-/blob/main/wots.c?ref_type=hea...
wots_chain (public_seed, addr, 0, msg, sig, sig); sha3_256_update (ctx, _SLH_DSA_128_SIZE, wots_chain (public_seed, addr, msg, 15 - msg, sig, pub));
where first line produces the signature, and second line extends the chain to produce the corresponding public key. But you probably don't want to do any extensive refactoring now.
To aid later refactoring, it would help with unit tests for the various components (even just using the intermadiate values from the top-level tests is helpful; that's what I did for my prototype).
Another useful addition would be benchmarking in examples/hogweed-benchmark.c.
Regards, /Niels
-- Niels Möller. PGP key CB4962D070D77D7FCB8BA36271D8F1FF368C6677. Internet email is subject to wholesale government surveillance.