MLDSA: support creating expanded keys #871

Merged
gilles-peskine-arm merged 23 commits from mldsa-driver-generate-expanded-key into development 2026-09-17 11:16:06 -05:00
gilles-peskine-arm commented 2026-08-17 10:51:10 -05:00 (Migrated from github.com)
  • Add basic generate_key tests to test_suite_dispatch_transparent.
  • Small complements to #786 and #834 (documentation, tests).
  • Make buffer size macros visible to the dispatch layer. This means you can realistically perform ML-DSA operations from the driver dispatch layer without having to include a header from drivers/pqcp/builtin/src.
  • New function tf_psa_crypto_mldsa_expand_private_key() to convert an ML-DSA seed to a joined format (seed + expanded). This function is only declared in ``drivers/pqcp/builtin/src/psa_crypto_mldsa.h` for now. It is meant to become the driver's key import function when the design of that interface progresses.
  • Support generating a key, with a run-time choice of format (seed or joined). Use custom parameters to allow generating a key in joined format via the driver dispatch layer. Resolves https://github.com/Mbed-TLS/TF-PSA-Crypto/issues/634.
  • Add a changelog entry. This is debatable, since this is not yet a stable interface and is not reachable from the API. But it is functionality that we are committing to support, and its interface at the driver dispatch layer is likely to remain mostly stable.

Resolves https://github.com/Mbed-TLS/TF-PSA-Crypto/issues/862

Needs preceding PR

PR checklist

Please remove the segment/s on either side of the | symbol as appropriate, and add any relevant link/s to the end of the line.
If the provided content is part of the present PR remove the # symbol.

  • changelog provided
  • framework PR not required
  • TF-PSA-Crypto development PR provided here
  • TF-PSA-Crypto 1.1 PR not required because: new feature
  • mbedtls development PR not required because: crypto only
  • mbedtls 4.1 PR not required because: crypto only
  • mbedtls 3.6 PR not required because: new feature
  • tests provided
* Add basic `generate_key` tests to `test_suite_dispatch_transparent`. * Small complements to #786 and #834 (documentation, tests). * Make buffer size macros visible to the dispatch layer. This means you can realistically perform ML-DSA operations from the driver dispatch layer without having to include a header from `drivers/pqcp/builtin/src`. * New function `tf_psa_crypto_mldsa_expand_private_key()` to convert an ML-DSA seed to a joined format (seed + expanded). This function is only declared in ``drivers/pqcp/builtin/src/psa_crypto_mldsa.h` for now. It is meant to become the driver's [key import function](https://github.com/ARM-software/psa-api/issues/308) when the design of that interface progresses. * Support generating a key, with a run-time choice of format (seed or joined). Use custom parameters to allow generating a key in joined format via the driver dispatch layer. Resolves https://github.com/Mbed-TLS/TF-PSA-Crypto/issues/634. * Add a changelog entry. This is debatable, since this is not yet a stable interface and is not reachable from the API. But it is functionality that we are committing to support, and its interface at the driver dispatch layer is likely to remain mostly stable. Resolves https://github.com/Mbed-TLS/TF-PSA-Crypto/issues/862 ## Needs preceding PR * [x] Fix libtestdriver1 build with a new header: https://github.com/Mbed-TLS/mbedtls/pull/10925 * [x] Support non-ASCII characters in `.function` files: https://github.com/Mbed-TLS/mbedtls-framework/pull/66 ## PR checklist Please remove the segment/s on either side of the | symbol as appropriate, and add any relevant link/s to the end of the line. If the provided content is part of the present PR remove the # symbol. - [x] **changelog** provided - [x] **framework PR** not required - [x] **TF-PSA-Crypto development PR** provided here - [x] **TF-PSA-Crypto 1.1 PR** not required because: new feature - [x] **mbedtls development PR** not required because: crypto only - [x] **mbedtls 4.1 PR** not required because: crypto only - [x] **mbedtls 3.6 PR** not required because: new feature - **tests** provided
davidhorstmann-arm (Migrated from github.com) reviewed 2026-08-19 05:25:51 -05:00
@ -27,0 +91,4 @@
psa_custom_key_parameters_t custom = PSA_CUSTOM_KEY_PARAMETERS_INIT;
custom.flags = custom_flags_arg;
size_t output_size = PSA_EXPORT_KEY_OUTPUT_SIZE(type, bits);
size_t output_length1 = SIZE_MAX;
davidhorstmann-arm (Migrated from github.com) commented 2026-08-19 05:25:51 -05:00

This would benefit from a comment, something like:

Allow the generated key to be up to this much shorter than the output buffer.

This would benefit from a comment, something like: > Allow the generated key to be up to this much shorter than the output buffer.
yanesca (Migrated from github.com) reviewed 2026-08-19 05:30:47 -05:00
@ -1,3 +1,15 @@
generate_key: ECC secp256r1 key pair
yanesca (Migrated from github.com) commented 2026-08-19 05:30:47 -05:00

Wouldn't this fail intermittently with a probability of 1/2^240? This is vanishingly small, but still, what is the purpose of this length check?

Wouldn't this fail intermittently with a probability of 1/2^240? This is vanishingly small, but still, what is the purpose of this length check?
yanesca (Migrated from github.com) reviewed 2026-08-19 05:51:24 -05:00
@ -18,12 +18,172 @@
* defined unconditionally. */
yanesca (Migrated from github.com) commented 2026-08-19 05:51:25 -05:00
    /* For simplicity, we don't support values of e which might need a multi-byte
```suggestion /* For simplicity, we don't support values of e which might need a multi-byte ```
yanesca (Migrated from github.com) reviewed 2026-08-19 05:54:34 -05:00
@ -21,0 +33,4 @@
/* For simplicity, we don't support values of e that might need a multi-byte
* length encoding in ASN.1 (more than 0x7f digits in base 256, or exactly
* 0x7f where the most significant bit is 1). */
TEST_LE_U(expected_e_length, 0x7e);
yanesca (Migrated from github.com) commented 2026-08-19 05:54:34 -05:00

Why 0x7e and not 0x7f?

Why `0x7e` and not `0x7f`?
bjwtaylor (Migrated from github.com) reviewed 2026-08-19 05:58:07 -05:00
bjwtaylor (Migrated from github.com) left a comment

Still need to do a full review, though some initial comments

Still need to do a full review, though some initial comments
@ -104,6 +143,8 @@ psa_status_t tf_psa_crypto_mldsa_export_public_key(
* \param message_length The length of \p message, in bytes.
* \param[out] signature On success, the exported key.
* \param signature_size The size of \p signature, in bytes.
* It must be at least
bjwtaylor (Migrated from github.com) commented 2026-08-19 05:51:14 -05:00

The documented input-size requirement appears incorrect. For ML-DSA-87, the accepted input representations are exactly:

  • Seed: 32 bytes.
  • Joined private key: 32 + 4896 = 4928 bytes, comprising the seed and expanded secret key.

The 2592-byte value is the encoded public-key size:

32 + (8 × 320) = 2592

It therefore applies to the output buffer (data_size), not key_buffer_size.

Could this document key_buffer_size as exactly TF_PSA_CRYPTO_PQCP_MLDSA_SEED_SIZE or TF_PSA_CRYPTO_PQCP_MLDSA_JOINED_PRIVATE_KEY_SIZE(bits), and retain
TF_PSA_CRYPTO_PQCP_MLDSA_PUBLIC_KEY_SIZE(bits) as the required output size?

The documented input-size requirement appears incorrect. For ML-DSA-87, the accepted input representations are exactly: - Seed: 32 bytes. - Joined private key: 32 + 4896 = 4928 bytes, comprising the seed and expanded secret key. The 2592-byte value is the encoded public-key size: 32 + (8 × 320) = 2592 It therefore applies to the output buffer (data_size), not key_buffer_size. Could this document key_buffer_size as exactly TF_PSA_CRYPTO_PQCP_MLDSA_SEED_SIZE or TF_PSA_CRYPTO_PQCP_MLDSA_JOINED_PRIVATE_KEY_SIZE(bits), and retain TF_PSA_CRYPTO_PQCP_MLDSA_PUBLIC_KEY_SIZE(bits) as the required output size?
@ -1,3 +1,79 @@
MLDSA size macros
bjwtaylor (Migrated from github.com) commented 2026-08-19 05:39:50 -05:00

The comment here says the test is with bits 56, however the test used 44. Is there a mismatch issue?

The comment here says the test is with bits 56, however the test used 44. Is there a mismatch issue?
gilles-peskine-arm (Migrated from github.com) reviewed 2026-08-19 06:03:08 -05:00
@ -1,3 +1,15 @@
generate_key: ECC secp256r1 key pair
gilles-peskine-arm (Migrated from github.com) commented 2026-08-19 06:03:08 -05:00

I wanted some validation on the output of generate_key. Obviously there's no way to fully validate the content. So I wanted to at least validate that the length is sensible, and that the output isn't all-zeros or the same on every call.

I wanted some validation on the output of `generate_key`. Obviously there's no way to fully validate the content. So I wanted to at least validate that the length is sensible, and that the output isn't all-zeros or the same on every call.
gilles-peskine-arm (Migrated from github.com) reviewed 2026-08-19 06:04:07 -05:00
@ -21,0 +33,4 @@
/* For simplicity, we don't support values of e that might need a multi-byte
* length encoding in ASN.1 (more than 0x7f digits in base 256, or exactly
* 0x7f where the most significant bit is 1). */
TEST_LE_U(expected_e_length, 0x7e);
gilles-peskine-arm (Migrated from github.com) commented 2026-08-19 06:04:07 -05:00

0x7e is the maximum length of an integer in base 256 that can be represented in ASN.1 with a single byte for the length. 0x7f doesn't fit if the top bit is 1.

0x7e is the maximum length of an integer in base 256 that can be represented in ASN.1 with a single byte for the length. 0x7f doesn't fit if the top bit is 1.
gilles-peskine-arm (Migrated from github.com) reviewed 2026-08-19 06:04:52 -05:00
@ -104,6 +143,8 @@ psa_status_t tf_psa_crypto_mldsa_export_public_key(
* \param message_length The length of \p message, in bytes.
* \param[out] signature On success, the exported key.
* \param signature_size The size of \p signature, in bytes.
* It must be at least
gilles-peskine-arm (Migrated from github.com) commented 2026-08-19 06:04:52 -05:00

Yes, I must have put this sentence on the wrong line.

Yes, I must have put this sentence on the wrong line.
yanesca (Migrated from github.com) reviewed 2026-08-19 06:14:33 -05:00
@ -21,0 +33,4 @@
/* For simplicity, we don't support values of e that might need a multi-byte
* length encoding in ASN.1 (more than 0x7f digits in base 256, or exactly
* 0x7f where the most significant bit is 1). */
TEST_LE_U(expected_e_length, 0x7e);
yanesca (Migrated from github.com) commented 2026-08-19 06:14:32 -05:00

I am sorry, I still don't understand, here it says that the short form is max 127: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/#length. What am I missing?

I am sorry, I still don't understand, here it says that the short form is max 127: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/#length. What am I missing?
davidhorstmann-arm (Migrated from github.com) reviewed 2026-08-19 06:28:46 -05:00
davidhorstmann-arm (Migrated from github.com) left a comment

Some initial comments. Janos and I are pair-reviewing commit by commit, hence the sporadic comments.

Some initial comments. Janos and I are pair-reviewing commit by commit, hence the sporadic comments.
@ -18,12 +18,172 @@
* defined unconditionally. */
davidhorstmann-arm (Migrated from github.com) commented 2026-08-19 05:59:29 -05:00

If expected_e_length > 0 and there are no 0s in expected_e, this will run off the end of the buffer:

    for (; expected_e_length > 0; expected_e++, expected_e_length--) {
If `expected_e_length > 0` and there are no 0s in `expected_e`, this will run off the end of the buffer: ```suggestion for (; expected_e_length > 0; expected_e++, expected_e_length--) { ```
davidhorstmann-arm (Migrated from github.com) commented 2026-08-19 06:02:38 -05:00

Could you add the specific name of the public key format from the standard here? It would help to understand this part.

Could you add the specific name of the public key format from the standard here? It would help to understand this part.
@ -1,3 +1,15 @@
generate_key: ECC secp256r1 key pair
davidhorstmann-arm (Migrated from github.com) commented 2026-08-19 05:33:46 -05:00

Should this be 'no more than a handful of bytes shorter'? Or have I misunderstood.

There are 2 points:

  • Leading zeroes on pseudorandom would imply that we should expect no more than a handful of bytes smaller
  • PSA_KEY_EXPORT_RSA_KEY_PAIR_MAX_SIZE is a generous overapproximation, so we should expect more than a handful of bytes smaller.
Should this be 'no more than a handful of bytes shorter'? Or have I misunderstood. There are 2 points: * Leading zeroes on pseudorandom would imply that we should expect no more than a handful of bytes smaller * `PSA_KEY_EXPORT_RSA_KEY_PAIR_MAX_SIZE` is a generous overapproximation, so we should expect more than a handful of bytes smaller.
gilles-peskine-arm (Migrated from github.com) reviewed 2026-08-20 07:45:10 -05:00
@ -1,3 +1,15 @@
generate_key: ECC secp256r1 key pair
gilles-peskine-arm (Migrated from github.com) commented 2026-08-20 07:45:09 -05:00

It's the sum of:

  • A handful, varying from run to run, because of leading zeros.
  • More than a handful, depending only on the value of PSA_VENDOR_RSA_GENERATE_MIN_KEY_BITS, because PSA_KEY_EXPORT_RSA_KEY_PAIR_MAX_SIZE overapproximates.

I'll expand the comment.

It's the sum of: * A handful, varying from run to run, because of leading zeros. * More than a handful, depending only on the value of `PSA_VENDOR_RSA_GENERATE_MIN_KEY_BITS`, because `PSA_KEY_EXPORT_RSA_KEY_PAIR_MAX_SIZE` overapproximates. I'll expand the comment.
gilles-peskine-arm commented 2026-08-20 10:59:03 -05:00 (Migrated from github.com)

The CI is failing because I put a non-ASCII character in a .function file. I can either merge https://github.com/Mbed-TLS/mbedtls-framework/pull/66 (needs reviewers) or work around it.

The CI is failing because I put a non-ASCII character in a `.function` file. I can either merge https://github.com/Mbed-TLS/mbedtls-framework/pull/66 (needs reviewers) or work around it.
yanesca (Migrated from github.com) reviewed 2026-08-25 07:41:24 -05:00
@ -0,0 +20,4 @@
/* Define macros for key types and algorithms here in a private header,
* rather than in a public header, because ML-DSA is not yet supported
* through the API. Notably:
yanesca (Migrated from github.com) commented 2026-08-25 07:41:24 -05:00

This sentence fragment "through the API" needs to be competed or removed.

This sentence fragment "through the API" needs to be competed or removed.
yanesca (Migrated from github.com) reviewed 2026-08-25 07:42:22 -05:00
@ -0,0 +1,111 @@
/**
yanesca (Migrated from github.com) commented 2026-08-25 07:42:22 -05:00

Typo: Here and elsewhere:

 * 44, 65 or 87.
Typo: Here and elsewhere: ```suggestion * 44, 65 or 87. ```
yanesca commented 2026-08-25 07:47:54 -05:00 (Migrated from github.com)

I found some typos, but other than that, it looks good to me.

I found some typos, but other than that, it looks good to me.
yanesca (Migrated from github.com) reviewed 2026-08-26 06:16:50 -05:00
yanesca (Migrated from github.com) left a comment
No description provided.
LGTM
davidhorstmann-arm (Migrated from github.com) reviewed 2026-09-16 09:08:24 -05:00
davidhorstmann-arm (Migrated from github.com) left a comment

Apologies for the delay in getting this reviewed, and the resulting conflict. A lot of new context to load since I haven't looked at ML-DSA much before.

I have one relatively minor question but LGTM otherwise.

Apologies for the delay in getting this reviewed, and the resulting conflict. A lot of new context to load since I haven't looked at ML-DSA much before. I have one relatively minor question but LGTM otherwise.
@ -1,3 +1,79 @@
MLDSA size macros
davidhorstmann-arm (Migrated from github.com) commented 2026-09-16 09:04:23 -05:00

Just to clarify: Is this meant to test a standard size that is not supported or a non-standard size?

I'm just asking since "56" -> "65" has happened elsewhere.

Just to clarify: Is this meant to test a standard size that is not supported or a non-standard size? I'm just asking since "56" -> "65" has happened elsewhere.
gilles-peskine-arm (Migrated from github.com) reviewed 2026-09-17 03:49:42 -05:00
@ -1,3 +1,79 @@
MLDSA size macros
gilles-peskine-arm (Migrated from github.com) commented 2026-09-17 03:49:41 -05:00

That one is meant to be a standard size like the one above, not a non-standard size like the one below, thanks for catching it.

The dependencies and test names also don't seem quite right for test cases with bits≠87.

That one is meant to be a standard size like the one above, not a non-standard size like the one below, thanks for catching it. The dependencies and test names also don't seem quite right for test cases with bits≠87.
gilles-peskine-arm commented 2026-09-17 03:52:05 -05:00 (Migrated from github.com)

I've rebased on top of development because there was a merge conflict on the framework update. No framework update is needed now.

I'll fix the bits≠87 test case(s) in a new commit.

I've rebased on top of `development` because there was a merge conflict on the framework update. No framework update is needed now. I'll fix the bits≠87 test case(s) in a new commit.
davidhorstmann-arm (Migrated from github.com) approved these changes 2026-09-17 06:21:16 -05:00
davidhorstmann-arm (Migrated from github.com) left a comment

LGTM, thanks!

LGTM, thanks!
yanesca (Migrated from github.com) approved these changes 2026-09-17 08:51:42 -05:00
yanesca (Migrated from github.com) left a comment

LGTM

LGTM
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-Mbed-TLS/TF-PSA-Crypto!871
No description provided.