Skip to content

Commit 3da164c

Browse files
[crypto/mldsa] Cryptolib implementation of ML-DSA-87 sign
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>
1 parent ae1133f commit 3da164c

5 files changed

Lines changed: 330 additions & 52 deletions

File tree

sw/device/lib/crypto/impl/mldsa.c

Lines changed: 100 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ enum {
2828
kOtCryptoMldsaBufferWords = kOtCryptoMldsaBufferBytes / sizeof(uint32_t),
2929
// Maximum size of a pre-hash message digest.
3030
kOtcryptoMldsaPhMaxWords = 16,
31+
// Size of the rnd string.
32+
kOtcryptoMldsaRndBytes = 32,
33+
kOtcryptoMldsaRndWords = kOtcryptoMldsaRndBytes / sizeof(uint32_t),
3134
};
3235

3336
// Lookup table for the supported pre-hash functions, indexed by their OID.
@@ -81,6 +84,24 @@ static otcrypto_status_t check_unblinded_key(
8184
return OTCRYPTO_OK;
8285
}
8386

87+
// Check the integrity and length of a blinded key.
88+
static otcrypto_status_t check_blinded_key(const otcrypto_blinded_key_t *key) {
89+
// Integrity check.
90+
if (launder32(otcrypto_integrity_blinded_key_check(key)) !=
91+
kHardenedBoolTrue) {
92+
return OTCRYPTO_BAD_ARGS;
93+
}
94+
HARDENED_CHECK_EQ(otcrypto_integrity_blinded_key_check(key),
95+
kHardenedBoolTrue);
96+
97+
// Length check.
98+
if (key->keyblob_length != kOtcryptoMldsa87SkBytes) {
99+
return OTCRYPTO_BAD_ARGS;
100+
}
101+
HARDENED_CHECK_EQ(key->keyblob_length, kOtcryptoMldsa87SkBytes);
102+
return OTCRYPTO_OK;
103+
}
104+
84105
// Check the integrity and length of a constant byte buffer.
85106
static otcrypto_status_t check_byte_buf(const otcrypto_const_byte_buf_t *buf,
86107
size_t max_len) {
@@ -233,11 +254,15 @@ otcrypto_status_t otcrypto_mldsa87_keygen(
233254

234255
otcrypto_status_t otcrypto_mldsa87_sign(
235256
const otcrypto_blinded_key_t *private_key,
236-
const otcrypto_const_byte_buf_t message,
237-
const otcrypto_const_byte_buf_t context,
238-
otcrypto_mldsa_hash_mode_t hash_mode, otcrypto_word32_buf_t signature) {
239-
// TODO: Connect ML-DSA operations to API.
240-
return OTCRYPTO_NOT_IMPLEMENTED;
257+
const otcrypto_const_byte_buf_t *message,
258+
const otcrypto_const_byte_buf_t *context,
259+
otcrypto_mldsa_hash_mode_t hash_mode, otcrypto_mldsa_sign_mode_t sign_mode,
260+
otcrypto_word32_buf_t *signature) {
261+
HARDENED_TRY(otcrypto_mldsa87_sign_async_start(private_key, message, context,
262+
hash_mode, sign_mode));
263+
HARDENED_TRY(otcrypto_mldsa87_double_sign_async_finalize(signature));
264+
265+
return OTCRYPTO_OK;
241266
}
242267

243268
otcrypto_status_t otcrypto_mldsa87_verify(
@@ -276,20 +301,76 @@ otcrypto_status_t otcrypto_mldsa87_keygen_async_finalize(
276301

277302
otcrypto_status_t otcrypto_mldsa87_sign_async_start(
278303
const otcrypto_blinded_key_t *private_key,
279-
const otcrypto_const_byte_buf_t message,
280-
const otcrypto_const_byte_buf_t context,
281-
otcrypto_mldsa_hash_mode_t hash_mode, otcrypto_word32_buf_t signature) {
282-
// TODO: Connect ML-DSA operations to API.
283-
return OTCRYPTO_NOT_IMPLEMENTED;
304+
const otcrypto_const_byte_buf_t *message,
305+
const otcrypto_const_byte_buf_t *context,
306+
otcrypto_mldsa_hash_mode_t hash_mode,
307+
otcrypto_mldsa_sign_mode_t sign_mode) {
308+
#ifndef OTCRYPTO_DISABLE_NULL_CHECKS
309+
if (private_key == NULL || private_key->keyblob == NULL || message == NULL ||
310+
context == NULL) {
311+
return OTCRYPTO_BAD_ARGS;
312+
}
313+
#endif
314+
315+
// Check the integrity and length of the input buffers.
316+
HARDENED_TRY(check_blinded_key(private_key));
317+
HARDENED_TRY(check_byte_buf(context, kOtcryptoMldsa87CtxMaxBytes));
318+
HARDENED_TRY(check_byte_buf(message, kOtcryptoMldsa87MsgMaxBytes));
319+
320+
// Allocate the 64-byte mu digest.
321+
uint32_t mu_data[kOtcryptoMldsaMuWords] = {0};
322+
otcrypto_hash_digest_t mu = {
323+
.data = mu_data,
324+
.len = kOtcryptoMldsaMuWords,
325+
};
326+
327+
// Extract tr from the secret key.
328+
otcrypto_hash_digest_t tr = {
329+
.data = private_key->keyblob + 24,
330+
.len = kOtcryptoMldsaTrWords,
331+
};
332+
333+
// mu = Shake256(tr || M').
334+
HARDENED_TRY(compute_mu(&tr, context, message, hash_mode, &mu));
335+
336+
// Invoke the signature generation OTBN app.
337+
HARDENED_TRY_WIPE_DMEM(
338+
mldsa87_sign_internal_start(private_key, &mu, sign_mode));
339+
340+
// Check the buffers again before exiting.
341+
HARDENED_TRY(check_blinded_key(private_key));
342+
HARDENED_TRY(check_byte_buf(context, kOtcryptoMldsa87CtxMaxBytes));
343+
HARDENED_TRY(check_byte_buf(message, kOtcryptoMldsa87MsgMaxBytes));
344+
345+
return otcrypto_eval_exit(OTCRYPTO_OK);
284346
}
285347

286348
otcrypto_status_t otcrypto_mldsa87_sign_async_finalize(
287-
const otcrypto_blinded_key_t *private_key,
288-
const otcrypto_const_byte_buf_t message,
289-
const otcrypto_const_byte_buf_t context,
290-
otcrypto_mldsa_hash_mode_t hash_mode, otcrypto_word32_buf_t signature) {
291-
// TODO: Connect ML-DSA operations to API.
292-
return OTCRYPTO_NOT_IMPLEMENTED;
349+
otcrypto_word32_buf_t *signature) {
350+
#ifndef OTCRYPTO_DISABLE_NULL_CHECKS
351+
if (signature == NULL || signature->data == NULL) {
352+
return OTCRYPTO_BAD_ARGS;
353+
}
354+
#endif
355+
356+
HARDENED_TRY_WIPE_DMEM(
357+
mldsa87_sign_internal_finalize(signature, kMldsa87SingleSign));
358+
359+
return otcrypto_eval_exit(OTCRYPTO_OK);
360+
}
361+
362+
otcrypto_status_t otcrypto_mldsa87_double_sign_async_finalize(
363+
otcrypto_word32_buf_t *signature) {
364+
#ifndef OTCRYPTO_DISABLE_NULL_CHECKS
365+
if (signature == NULL || signature->data == NULL) {
366+
return OTCRYPTO_BAD_ARGS;
367+
}
368+
#endif
369+
370+
HARDENED_TRY_WIPE_DMEM(
371+
mldsa87_sign_internal_finalize(signature, kMldsa87DoubleSign));
372+
373+
return otcrypto_eval_exit(OTCRYPTO_OK);
293374
}
294375

295376
otcrypto_status_t otcrypto_mldsa87_verify_async_start(
@@ -358,8 +439,10 @@ otcrypto_status_t otcrypto_mldsa87_verify_async_finalize(
358439

359440
HARDENED_TRY(check_word32_buf(signature, kOtcryptoMldsa87SigWords));
360441

361-
return otcrypto_eval_exit(
442+
HARDENED_TRY_WIPE_DMEM(
362443
mldsa87_verify_internal_finalize(signature, verification_result));
444+
445+
return otcrypto_eval_exit(OTCRYPTO_OK);
363446
}
364447

365448
otcrypto_status_t otcrypto_mldsa87_keycheck_async_start(

sw/device/lib/crypto/impl/mldsa/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ cc_library(
1616
"//sw/device/lib/base:hardened_memory",
1717
"//sw/device/lib/crypto/drivers:otbn",
1818
"//sw/device/lib/crypto/impl:status",
19+
"//sw/otbn/crypto/mldsa87/sign:mldsa87_sign",
1920
"//sw/otbn/crypto/mldsa87/verify:mldsa87_verify",
2021
],
2122
)

sw/device/lib/crypto/impl/mldsa/mldsa.c

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,18 @@
1313
// Module ID for status codes.
1414
#define MODULE_ID MAKE_MODULE_ID('m', 'l', 'd')
1515

16-
// OTBN app.
16+
// Sign app.
17+
OTBN_DECLARE_APP_SYMBOLS(mldsa87_sign);
18+
// Inputs.
19+
OTBN_DECLARE_SYMBOL_ADDR(mldsa87_sign, mldsa87_sign_mode);
20+
OTBN_DECLARE_SYMBOL_ADDR(mldsa87_sign, mldsa87_sign_sk);
21+
OTBN_DECLARE_SYMBOL_ADDR(mldsa87_sign, mldsa87_sign_mu);
22+
// Outputs.
23+
OTBN_DECLARE_SYMBOL_ADDR(mldsa87_sign, mldsa87_sign_sig_c_tilde);
24+
OTBN_DECLARE_SYMBOL_ADDR(mldsa87_sign, mldsa87_sign_sig_z);
25+
OTBN_DECLARE_SYMBOL_ADDR(mldsa87_sign, mldsa87_sign_sig_h);
26+
27+
// Verify app.
1728
OTBN_DECLARE_APP_SYMBOLS(mldsa87_verify);
1829
// Inputs.
1930
OTBN_DECLARE_SYMBOL_ADDR(mldsa87_verify, mldsa87_verify_pk);
@@ -23,6 +34,101 @@ OTBN_DECLARE_SYMBOL_ADDR(mldsa87_verify, mldsa87_verify_mu);
2334
OTBN_DECLARE_SYMBOL_ADDR(mldsa87_verify, mldsa87_verify_res_ok);
2435
OTBN_DECLARE_SYMBOL_ADDR(mldsa87_verify, mldsa87_verify_res_c_tilde_prime);
2536

37+
enum {
38+
kMldsa87AbridgedMode = 0x29d8e5c9,
39+
};
40+
41+
static status_t read_signature(uint32_t *sig) {
42+
// Read c_tilde_prime.
43+
const otbn_addr_t kOtbnCTilde =
44+
OTBN_ADDR_T_INIT(mldsa87_sign, mldsa87_sign_sig_c_tilde);
45+
HARDENED_TRY(otbn_dmem_read(kMldsa87CTildeWords, kOtbnCTilde, sig));
46+
47+
// Read Z.
48+
const otbn_addr_t kOtbnZ = OTBN_ADDR_T_INIT(mldsa87_sign, mldsa87_sign_sig_z);
49+
HARDENED_TRY(
50+
otbn_dmem_read(kMldsa87ZWords, kOtbnZ, sig + kMldsa87CTildeWords));
51+
52+
// Read hint.
53+
const otbn_addr_t kOtbnH = OTBN_ADDR_T_INIT(mldsa87_sign, mldsa87_sign_sig_h);
54+
HARDENED_TRY(otbn_dmem_read(kMldsa87HWords, kOtbnH,
55+
sig + kMldsa87CTildeWords + kMldsa87ZWords));
56+
57+
// Clear the Z vector.
58+
HARDENED_TRY(otbn_dmem_set(kMldsa87ZWords, 0, kOtbnZ));
59+
60+
return OTCRYPTO_OK;
61+
}
62+
63+
status_t mldsa87_sign_internal_start(const otcrypto_blinded_key_t *secret_key,
64+
const otcrypto_hash_digest_t *mu,
65+
uint32_t mode) {
66+
// Load the ML-DSA-87 sign app and write the inputs.
67+
const otbn_app_t kOtbnAppMldsa87Sign = OTBN_APP_T_INIT(mldsa87_sign);
68+
HARDENED_TRY(otbn_load_app(kOtbnAppMldsa87Sign));
69+
70+
const otbn_addr_t kOtbnMode =
71+
OTBN_ADDR_T_INIT(mldsa87_sign, mldsa87_sign_mode);
72+
HARDENED_TRY(otbn_dmem_write(1, &mode, kOtbnMode));
73+
74+
const otbn_addr_t kOtbnSk = OTBN_ADDR_T_INIT(mldsa87_sign, mldsa87_sign_sk);
75+
HARDENED_TRY(otbn_dmem_write(secret_key->keyblob_length / sizeof(uint32_t),
76+
secret_key->keyblob, kOtbnSk));
77+
78+
const otbn_addr_t kOtbnMu = OTBN_ADDR_T_INIT(mldsa87_sign, mldsa87_sign_mu);
79+
HARDENED_TRY(otbn_dmem_write(mu->len, mu->data, kOtbnMu));
80+
81+
return otbn_execute();
82+
}
83+
84+
status_t mldsa87_sign_internal_finalize(otcrypto_word32_buf_t *signature,
85+
mldsa_sign_redundancy_t redundancy) {
86+
// Stall until the OTBN finishes.
87+
HARDENED_TRY(otbn_busy_wait_for_done());
88+
89+
// Read back the signature.
90+
HARDENED_TRY(read_signature(signature->data));
91+
92+
// Return if single-sign redundancy is selected.
93+
if (redundancy == kMldsa87SingleSign) {
94+
HARDENED_CHECK_EQ(redundancy, kMldsa87SingleSign);
95+
return OTCRYPTO_OK;
96+
}
97+
HARDENED_CHECK_EQ(redundancy, kMldsa87DoubleSign);
98+
99+
/*
100+
* To thwart FI attacks, we execute a second sign operation in abridged mode
101+
* without clearing the secret key, nonce KAPPA, RND and RHO_PRIME. After
102+
* the completion of the second sign the entire DMEM is wiped.
103+
*/
104+
105+
uint32_t mode = kMldsa87AbridgedMode;
106+
const otbn_addr_t kOtbnMode =
107+
OTBN_ADDR_T_INIT(mldsa87_sign, mldsa87_sign_mode);
108+
HARDENED_TRY(otbn_dmem_write(1, &mode, kOtbnMode));
109+
110+
// Buffer for the second signature value.
111+
uint32_t sig_cmp_data[kMldsa87SigWords];
112+
113+
// Execute the second abridged sign and wait for its completion.
114+
HARDENED_TRY(otbn_execute());
115+
HARDENED_TRY(otbn_busy_wait_for_done());
116+
117+
// Read back the second signature.
118+
HARDENED_TRY(read_signature(sig_cmp_data));
119+
120+
// Make sure both signature values are equal.
121+
if (hardened_memeq(signature->data, sig_cmp_data, kMldsa87SigWords) !=
122+
kHardenedBoolTrue) {
123+
HARDENED_TRAP();
124+
}
125+
HARDENED_CHECK_EQ(
126+
hardened_memeq(signature->data, sig_cmp_data, kMldsa87SigWords),
127+
kHardenedBoolTrue);
128+
129+
return OTCRYPTO_OK;
130+
}
131+
26132
status_t mldsa87_verify_internal_start(
27133
const otcrypto_unblinded_key_t *public_key,
28134
const otcrypto_const_word32_buf_t *signature,
@@ -74,5 +180,5 @@ status_t mldsa87_verify_internal_finalize(
74180
*result =
75181
hardened_memeq(signature->data, c_tilde_prime, kMldsa87CTildePrimeWords);
76182

77-
return otbn_dmem_sec_wipe();
183+
return OTCRYPTO_OK;
78184
}

sw/device/lib/crypto/impl/mldsa/mldsa.h

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,64 @@ enum {
2121
*/
2222
kMldsa87CTildePrimeWords = 16,
2323

24+
kMldsa87CTildeBytes = 64,
25+
kMldsa87CTildeWords = kMldsa87CTildeBytes / sizeof(uint32_t),
26+
27+
kMldsa87ZBytes = 4480,
28+
kMldsa87ZWords = kMldsa87ZBytes / sizeof(uint32_t),
29+
30+
kMldsa87HBytes = 83 + 1,
31+
kMldsa87HWords = kMldsa87HBytes / sizeof(uint32_t),
32+
33+
kMldsa87SigBytes = 4627 + 1,
34+
kMldsa87SigWords = kMldsa87SigBytes / sizeof(uint32_t),
35+
2436
/**
2537
* 32-bit success and error indicators.
2638
*/
2739
kMldsa87StatusOk = 0x7baf73d2,
2840
kMldsa87StatusFail = 0xadf1aebd,
2941
};
3042

43+
/**
44+
* Redundancy modes for ML-DSA sign.
45+
*/
46+
typedef enum mldsa_sign_redundancy {
47+
/**
48+
* Compute a single signature.
49+
*/
50+
kMldsa87SingleSign = 0x5514edb7,
51+
/**
52+
* Compute two signatures and compare that they are equal.
53+
*/
54+
kMldsa87DoubleSign = 0xfaacd725,
55+
} mldsa_sign_redundancy_t;
56+
57+
/**
58+
* Start an async ML-DSA-87 signature generation on the OTBN.
59+
*
60+
* Returns an `OTCRYPTO_ASYNC_INCOMPLETE` error if OTBN is busy.
61+
*
62+
* @param secret_key Masked key for the signature generation (6368 bytes).
63+
* @param rnd The randomness string (32 bytes).
64+
* @param mu The message hash (64 bytes).
65+
* @param kappa The rejection loop nonce.
66+
*/
67+
status_t mldsa87_sign_internal_start(const otcrypto_blinded_key_t *secret_key,
68+
const otcrypto_hash_digest_t *mu,
69+
uint32_t mode);
70+
71+
/**
72+
* Finish an async ML-DSA-87 signature generation on the OTBN.
73+
*
74+
* Blocks until OTBN is idle.
75+
*
76+
* @param[out] signature The generated signature (4627 + 1 bytes).
77+
* @return Result of the operation (OK or error).
78+
*/
79+
status_t mldsa87_sign_internal_finalize(otcrypto_word32_buf_t *signature,
80+
mldsa_sign_redundancy_t redundancy);
81+
3182
/**
3283
* Start an async ML-DSA-87 signature verification on the OTBN.
3384
*
@@ -58,7 +109,7 @@ status_t mldsa87_verify_internal_start(
58109
* status will be OK but `result` will be `kHardenedBoolFalse`.
59110
*
60111
* @param signature Signature to be verified (4628 bytes).
61-
* @param result (true if signature is valid, false otherwise).
112+
* @param[out] result (true if signature is valid, false otherwise).
62113
* @return Result of the operation (OK or error).
63114
*/
64115
OT_WARN_UNUSED_RESULT

0 commit comments

Comments
 (0)