[crypto/mldsa] Cryptolib implementation of ML-DSA-87 sign#30676
[crypto/mldsa] Cryptolib implementation of ML-DSA-87 sign#30676andrea-caforio wants to merge 6 commits into
Conversation
The computation of the message hash mu is common to both signature generation and verification. Signed-off-by: Andrea Caforio <andrea.caforio@lowrisc.org>
Signed-off-by: Andrea Caforio <andrea.caforio@lowrisc.org>
8a07c0e to
dbfeb5e
Compare
siemen11
left a comment
There was a problem hiding this comment.
Thank you @andrea-caforio ! Amazing to see the sign in action!
| ARRAYSIZE(rnd_data), kHardenedBoolTrue)); | ||
| HARDENED_TRY(entropy_csrng_uninstantiate()); | ||
|
|
||
| if (launder32(sign_mode) == kOtcryptoMldsaSignModeDet) { |
There was a problem hiding this comment.
It is a general comment, but given the security concern with deterministic ML-DSA. Shouldn't we maybe think of trying to tie this rnd to query it from EDN1 in the OTBN instead of going through Ibex?
If really needed you can write a magic value in OTBN to skip the edn1 pull of this rnd (since DMEM starts at 0), and our cycle count check can be a cross reference that OTBN skipped (or did not skip) this instantiation correctly as FI protection?
There was a problem hiding this comment.
The problem is, how do we do the double-sign if we sample the randomness in OTBN? In fact, the current implementation is not correct either because we need to sample it at an even higher point such that it is the same randomness for both invocations of sign.
There was a problem hiding this comment.
Isn't the randomness for rho'? Then the same rho' is used every sign loop?
There was a problem hiding this comment.
Meaning, maybe we can place rho' in memory and skip its re-generation argh ok no redundancy for FI protection 🫠
There was a problem hiding this comment.
Yes, it would be good to re-execute the stuff before the rejection loop but reading out RHO' seems to be the only way currently we can achieve some kind of double-sign.
| // Stall until the OTBN finishes. | ||
| HARDENED_TRY(otbn_busy_wait_for_done()); | ||
|
|
||
| // Load c_tilde_prime. |
There was a problem hiding this comment.
Putting this out there, but do we have some protection against an early exit by ML-DSA? Meaning a fault makes OTBN quit out early we get a rejected z and c_tilde back which can be used for secret key retrieval. The way I understand it is that our double sign protects against this, but we do not wipe DMEM between the two iterations, so a double early exit would actually be problematic.
Thinking on that, maybe we should specifically clear z between the two redundant calls?
There was a problem hiding this comment.
Why is it not cleared in between calls? Both invocations are wrapped in a HARDENED_TRY_WIPE_DMEM macro. But you're right, there is currently no other protection against early exits.
There was a problem hiding this comment.
Hmm I see
HARDENED_TRY(otcrypto_mldsa87_sign_async_start(private_key, message, context,
hash_mode, sign_mode, kappa));
HARDENED_TRY(otcrypto_mldsa87_sign_async_finalize(signature, &kappa));
For efficiency's sake I guess we do not want to wipe DMEM fully between redundant sign calls right?
But we can do a dmem write to set z back to zero?
There was a problem hiding this comment.
Is this possible? Does DMEM persist even after the application exits? Is the invocation protocol different for the second run?
There was a problem hiding this comment.
Ah it calls the otbn_load_app again, my misinterpretation sorry. I was thinking the redundancy to be in the internal driver not in the wrapper
| * @param mu The resulting message hash (64 bytes). | ||
| * @return Result of the operation (OK or error). | ||
| */ | ||
| otcrypto_status_t compute_mu(const otcrypto_hash_digest_t *tr, |
There was a problem hiding this comment.
Thanks for putting this in a function, very clean!
| const otcrypto_const_byte_buf_t context, | ||
| otcrypto_mldsa_hash_mode_t hash_mode, otcrypto_word32_buf_t signature) { | ||
| // TODO: Connect ML-DSA operations to API. | ||
| return OTCRYPTO_NOT_IMPLEMENTED; |
There was a problem hiding this comment.
PQC is open for business 🥳
Add the abridged, random and deterministic signing modes to the ML-DSA-87 sign OTBN app and restructure the DMEM locations to allows for writing the key and reading the signature in single continuous blobs to the OTBN. Signed-off-by: Andrea Caforio <andrea.caforio@lowrisc.org>
Signed-off-by: Andrea Caforio <andrea.caforio@lowrisc.org>
dbfeb5e to
1df54f2
Compare
|
It took me a while. :-) The OTBN app no has three modes: abridged, random, deterministic. Abridged mode (used for the second redundant sign call) skips the computation of RHO and setting the RND and assumes that the nonce KAPPA remains in DMEM. Random and deterministic modes are protected with an instruction counter. The second sign call is now part of the finalize function. |
siemen11
left a comment
There was a problem hiding this comment.
Thank you @andrea-caforio ! Really nice!
| // Buffer for the second signature value. | ||
| uint32_t sig_cmp_data[kMldsa87SigWords]; | ||
|
|
||
| // Execute the second abridged sign and wait for its completion. |
There was a problem hiding this comment.
Nit: you can mention in a comment that this also wipes DMEM
| hardened_memeq(signature->data, sig_cmp_data, kMldsa87SigWords), | ||
| kHardenedBoolTrue); | ||
|
|
||
| return otbn_dmem_sec_wipe(); |
There was a problem hiding this comment.
I think this is better as a return OTCRYPTO_OK and wrap it with a HARDENED_TRY_WIPE_DMEM
The FIPS-compliant implementation of the FIPS-204 signature generation function with pure/pre-hash modes and random/deterministic signing. To thwart FI attacks, sign is executed twice (only the successful rejection loop in the second run). Signed-off-by: Andrea Caforio <andrea.caforio@lowrisc.org>
This commit includes a functest for pure and pre-hash mode. Signed-off-by: Andrea Caforio <andrea.caforio@lowrisc.org>
1df54f2 to
c9da807
Compare
|
great! from our discussion, please let's have the second sign configurable per key security level |
The problem is we don't pass the key (which contains the security level parameter) to finalize functions in the cryptolib, where the second sign is executed. Maybe we can leave it on by default for the moment and later reassess it? |
|
is there no precedence from ECDSA? |
Not sure, I can't find an instance where a key is passed to a finalize function. |
|
We would have two options:
My suggestion would be to keep it as it is (ie always do the double sign) and if integrators need a faster solution without double sign we could follow option 1 from above. |
Would we maybe just offer both API calls? double_sign_finalize and sign_finalize? |
Then we have the problem with code size overhead. Due to how the double sign works, we can basically not reuse much code of sign_finalize in double_sign_finalize. Not a big issue though. |
Hmm, that sounds unlikely, it feels that a couple of well named subroutines can cover this quite well. One regular sign finalize call and then some wrapper to bridge the new instance of the sign without cleaning DMEM entirely, and recalling the sign |
No description provided.