In a different thread, I wrote:
(Somewhat related: I just realized that the slh-dsa signing functions hit an assertion failure if public and private keys don't match. I think it may be useful with an alternative signing function with a success/fail return value).
This isn't good; having asserts triggered by invalid input is ok only when inputs are invalid in a way that is (1) documented and (2) easy to check for at the call site.
What happens is that creating an SLH-DSA signature will more or less for free recompute the top-level Merkle tree root hash, which is one of the pieces of the public key. So it's easy to compare them. If the two values differ, then either the private or public key doesn't match, or there's a bug in nettle, or there's some miscomputation, possibly caused by some fault injection attack.
At least for some applications, it makes sense to always verify that a computed signature is valid before passing it on. This is pretty important, e.g., if using RSA in a setting where fault injection attacks are relevant, and some of Nettle' too many RSA signing functions have a check for that.
As far as I understand, SLH-DSA is not that vulnerable to fault injection, but it may still be good practice to check that a computed signature is valid, in particular if one gets it for free when signing.
So the options I see:
1. Keep assert, let it be caller's responsibilty to make sure that the public and private keys passed to the signing function are consistent.
2. Just delete the assert.
3. Drop the assert, but instead introduce a success/fail return value. Applications that care can check that; if one believes fault injection and the like isn't a relevant threat to one's application or to SLH-DSA in general, it seems fine to ignore the return value.
4. Have two flavors of signing functions, one with the assert, one with the return value.
Any opinions?
I'm currently leaning towards (3). In principle, changing return type from void to int is an ABI change, but I'm not aware of any platform where it actually is an ABI breakage.
Regards, /Niels
Niels Möller nisse@lysator.liu.se writes:
- Have two flavors of signing functions, one with the assert, one with the return value.
Or two flavours of the signing function, one with the assert, and one with the assert deleted?
Your 3) seems fine too IMHO, almost any non-trivial C function should be able to return an error code. From the principle of least surprise, I think one ought to return an error code if public and private key doesn't match, if checking that is reliable and cheap.
/Simon
Simon Josefsson simon@josefsson.org writes:
Niels Möller nisse@lysator.liu.se writes:
- Have two flavors of signing functions, one with the assert, one with the return value.
Or two flavours of the signing function, one with the assert, and one with the assert deleted?
I'm thinking that having a function with a return value is useful if the application wants to provide an error message that is more helpful than an assertion failure.
Regards, /Niels
Simon Josefsson simon@josefsson.org writes:
Your 3) seems fine too IMHO, almost any non-trivial C function should be able to return an error code. From the principle of least surprise, I think one ought to return an error code if public and private key doesn't match, if checking that is reliable and cheap.
And on failure, it might make sense to also clear (memset to zero) the signature area before returning? To ensure that no information about the private key is leaked form an incorrect signature operation, and make it a bit safer if the return value is ignored.
Regards, /Niels
Niels Möller nisse@lysator.liu.se writes:
And on failure, it might make sense to also clear (memset to zero) the signature area before returning? To ensure that no information about the private key is leaked form an incorrect signature operation, and make it a bit safer if the return value is ignored.
Suggested new paragraph for the SLH-DSA section in the manual:
+The signing operation can recompute the top-level Merkle tree root +almost for free. Nettle's SLH-DSA signing functions do that, and compare +the resulting root hash to the provided public key. If the root hash is +not as expected, the signing operation fail, and the returned signature +is cleared to all zeros before return. Failure can happen if the public +or private key has been corrupted and don't match (although not all +kinds of corruption is detected), or if some software or hardware bug +affects the computation. Checking the return value from these signing +functions is not strictly required; it's the application's choice if it +prefers to handle this error, or pass on an invalid all-zero signature.
Does this sound reasonable? I think this should be safe enough to not be considered an ABI or API break. For ABI issues, all archs I'm aware of have an ABI where a function returning void uses the same calling convention as a function returning an int with an undefined value. For the API, a call with inputs that previously hit an assert instead returns an invalid all-zero signature, and behavior for other inputs is unchanged.
Regards, /Niels
On Wed, Jun 24, 2026 at 8:22 PM Niels Möller nisse@lysator.liu.se wrote:
Niels Möller nisse@lysator.liu.se writes:
And on failure, it might make sense to also clear (memset to zero) the signature area before returning? To ensure that no information about the private key is leaked form an incorrect signature operation, and make it a bit safer if the return value is ignored.
Suggested new paragraph for the SLH-DSA section in the manual:
+The signing operation can recompute the top-level Merkle tree root +almost for free. Nettle's SLH-DSA signing functions do that, and compare +the resulting root hash to the provided public key. If the root hash is +not as expected, the signing operation fail, and the returned signature +is cleared to all zeros before return. Failure can happen if the public +or private key has been corrupted and don't match (although not all +kinds of corruption is detected), or if some software or hardware bug +affects the computation. Checking the return value from these signing +functions is not strictly required; it's the application's choice if it +prefers to handle this error, or pass on an invalid all-zero signature.
Does this sound reasonable? I think this should be safe enough to not be considered an ABI or API break. For ABI issues, all archs I'm aware of have an ABI where a function returning void uses the same calling convention as a function returning an int with an undefined value. For the API, a call with inputs that previously hit an assert instead returns an invalid all-zero signature, and behavior for other inputs is unchanged.
The change looks sensible. One note on the manual wording: it may be worth being explicit that this root check is a best-effort guard against accidental corruption and bugs, not a defense against deliberate fault attacks.
best regards, Mamone
Regards, /Niels
-- Niels Möller. PGP key CB4962D070D77D7FCB8BA36271D8F1FF368C6677. Internet email is subject to wholesale government surveillance. _______________________________________________ nettle-bugs mailing list -- nettle-bugs@lists.lysator.liu.se To unsubscribe send an email to nettle-bugs-leave@lists.lysator.liu.se
Maamoun TK maamoun.tk@googlemail.com writes:
The change looks sensible. One note on the manual wording: it may be worth being explicit that this root check is a best-effort guard against accidental corruption and bugs, not a defense against deliberate fault attacks.
Hence the "(although not all kinds of corruption is detected)". It's hard to make promises about fault attacks.
But to expand a bit on this: Each of public and private key includes a random "seed" used to produce the random values populating the merkle trees. In addition the private key includes a "prf" for randomizing the message, and the public key includes the merkle tree root. My understanding is that any corruption that affects the computation of the message randomization only will go undetected (e.g., consider a hypothetical fault attacks that replaces the prf value with all zeros). I don't see attacks that leak information about the prf itself, and thereby predicting the randomization of future messages, but there might be some.
Faults introduced later during the signature process could trigger, e.g., violations of the one-time-ness of the underlying "wots" signatures. I initially thought that any fault would be highly likely affect the top-level root hash, but that seems wrong: a fault that modifies the *input* to one of the wots signatures could produce a valid wots signature (on the wrong data), and leak information about the secrets for that wots instance, without that being detected via the top-level root hash.
I'll see if I can clarify the wording. Applications that want a bit stronger protection against fault attacks could make an exlicit call to verify the produced signature; then a successful attack will require the attacker to introduce consistent faults to both the signing and the verify operation.
Regards, /Niels
On Fri, Jun 26, 2026 at 9:26 AM Niels Möller nisse@lysator.liu.se wrote:
Maamoun TK maamoun.tk@googlemail.com writes:
The change looks sensible. One note on the manual wording: it may be worth being explicit that this root check is a best-effort guard against accidental corruption and bugs, not a defense against deliberate fault attacks.
Hence the "(although not all kinds of corruption is detected)". It's hard to make promises about fault attacks.
But to expand a bit on this: Each of public and private key includes a random "seed" used to produce the random values populating the merkle trees. In addition the private key includes a "prf" for randomizing the message, and the public key includes the merkle tree root. My understanding is that any corruption that affects the computation of the message randomization only will go undetected (e.g., consider a hypothetical fault attacks that replaces the prf value with all zeros). I don't see attacks that leak information about the prf itself, and thereby predicting the randomization of future messages, but there might be some.
Faults introduced later during the signature process could trigger, e.g., violations of the one-time-ness of the underlying "wots" signatures. I initially thought that any fault would be highly likely affect the top-level root hash, but that seems wrong: a fault that modifies the *input* to one of the wots signatures could produce a valid wots signature (on the wrong data), and leak information about the secrets for that wots instance, without that being detected via the top-level root hash.
I'll see if I can clarify the wording. Applications that want a bit stronger protection against fault attacks could make an exlicit call to verify the produced signature; then a successful attack will require the attacker to introduce consistent faults to both the signing and the verify operation.
Agreed, and the reason the cheap root check can't catch a faulted WOTS input is neat: the WOTS public key (hence the leaf and root) depends only on the secret seed, not on the value signed, so a wrong-input signature still recomputes to the right root. Full verify catches it, as you say.
Take my read with a grain of salt though, SLH-DSA hasn't been my focus lately; I've been heads-down in ML-DSA, and more recently on some GF(2^128) witness-generation work, so my hash-based-sig intuition is rusty and you are the better authority on the corner cases.
Tangentially, on that GF(2^128) work: the implementation's done but the manual I wrote up for it could use a second pair of eyes. Happy to send it your way if you'd have any interest, no worries at all if not.
Regards, /Niels
-- Niels Möller. PGP key CB4962D070D77D7FCB8BA36271D8F1FF368C6677. Internet email is subject to wholesale government surveillance.
nettle-bugs@lists.lysator.liu.se