Skip to content

feat(sig): add derandomized keypair generation for ML-DSA (OQS_SIG_keypair_derand) - #2476

Draft
systemslibrarian wants to merge 5 commits into
open-quantum-safe:mainfrom
systemslibrarian:feature/sig-keypair-derand
Draft

feat(sig): add derandomized keypair generation for ML-DSA (OQS_SIG_keypair_derand)#2476
systemslibrarian wants to merge 5 commits into
open-quantum-safe:mainfrom
systemslibrarian:feature/sig-keypair-derand

Conversation

@systemslibrarian

Copy link
Copy Markdown

Closes #2475.

Summary

Exposes deterministic / derandomized keypair generation for the OQS_SIG family, mirroring the existing OQS_KEM derand API:

  • Adds a keypair_derand(pk, sk, seed) function pointer and a length_keypair_seed field to OQS_SIG, plus a top-level OQS_SIG_keypair_derand(...) wrapper — the same shape OQS_KEM already has.
  • Implements OQS_SIG_ml_dsa_{44,65,87}_keypair_derand over FIPS-204's seed-based keygen (ML-DSA.KeyGen_internal(ξ), a 32-byte seed). For schemes without seed-based keygen, keypair_derand is NULL, length_keypair_seed is 0, and the wrapper returns OQS_ERROR.
  • Propagates a family-level derandomized_keypair flag through copy_from_upstream so the per-scheme symbol and the decl/def guards are generated, and regenerates the SIG docs (adds the "Keypair seed size (bytes)" column already present on the KEM docs).

This unblocks seed/passphrase-recoverable post-quantum signing keys (e.g. deriving an ML-DSA signing key from a BIP-39 mnemonic), which the KEM side already supports via keypair_derand.

Testing (all in CI)

  • tests/test_sig.c — verifies determinism (same seed → identical pk/sk) and that the derived keypair is functional (sign/verify), plus the NULL-contract for schemes without derand.
  • tests/vectors_sig.c — the ACVP keyGen vector's seed is exactly FIPS-204's ξ, so this now also feeds it to OQS_SIG_keypair_derand and requires the result to match NIST's known-answer pk/sk — proving the public API really computes KeyGen_internal(ξ). Runs under tests/test_acvp_vectors.py.
  • tests/fuzz_test_sig.c — exercises OQS_SIG_keypair_derand under libFuzzer/ASAN where supported.

Locally: ACVP keyGen KAT green for ML-DSA-44/65/87 (with a negative control), libFuzzer/ASAN clean, astyle clean on changed files, and test_code_conventions.py passes (265).

Version / API checklist

  • Does this PR change the input/output behaviour of a cryptographic algorithm (i.e., does it change known answer test values)? — No. Existing keygen output is unchanged; this only adds a new way to supply the seed. The derand path reproduces the same FIPS-204 ACVP known answers.
  • Does this PR change the list of algorithms available / otherwise change an API? — Yes, additively. It adds (does not remove/rename) a function pointer + a field at the end of OQS_SIG and a new OQS_SIG_keypair_derand symbol, mirroring OQS_KEM. No algorithms are added/removed/renamed. Flagging for maintainer judgment on whether oqs-provider / benchmarking-list updates are wanted, given the change is purely additive.

Generative AI disclosure

Portions of the test scaffolding and this description were drafted with the help of generative AI (Claude). The contributor has reviewed and verified the changes — including running the ACVP KAT, fuzzer, and convention checks — and affirms them.

systemslibrarian and others added 2 commits June 26, 2026 02:18
Add OQS_SIG_keypair_derand, mirroring the existing OQS_KEM derand API: a
keypair_derand function pointer and length_keypair_seed field on OQS_SIG,
a top-level OQS_SIG_keypair_derand wrapper, and per-scheme
OQS_SIG_ml_dsa_{44,65,87}_keypair_derand backed by FIPS-204
ML-DSA.KeyGen_internal over a 32-byte seed.

- copy_from_upstream: propagate the family-level derandomized_keypair flag
  into SIG scheme scope and derive the per-impl derand symbol as
  <signature_keypair>_internal (upstream mldsa-native META exposes no
  separate derand symbol); guard the whole keypair_derand definition on
  derandomized_keypair so non-derand schemes emit neither decl nor def.
- docs: add "Keypair seed size (bytes)" to the SIG parameter-set tables.
- tests: sig_test_derand verifies determinism (same seed -> identical
  pk/sk), seed-dependence (different seed -> different keypair), a
  sign/verify round-trip on the derived key, and the NULL/unsupported
  contract for schemes without derandomized keygen.

Implements open-quantum-safe#2475

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Paul Clark <paul@systemslibrarian.dev>
- fuzz_test_sig: exercise OQS_SIG_keypair_derand under libFuzzer/ASAN
- vectors_sig: cross-check derand keygen against FIPS-204 ACVP keyGen
  known answers (the ACVP seed is xi), proving KeyGen_internal(xi)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Paul Clark <paul@systemslibrarian.dev>
@systemslibrarian
systemslibrarian force-pushed the feature/sig-keypair-derand branch from 9fe9fe9 to c729851 Compare June 26, 2026 02:18
@systemslibrarian
systemslibrarian marked this pull request as ready for review June 26, 2026 02:21
@xuganyu96 xuganyu96 added this to the 0.17.0 milestone Jul 16, 2026
@xuganyu96 xuganyu96 added the focus Reserved for a small number of topics that have been identified as focus issues for the current week label Jul 16, 2026
Signed-off-by: Norman Ashley <nashley@cisco.com>
name: ml_dsa
default_implementation: ref
upstream_location: mldsa-native
derandomized_keypair: true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be maintained upstream in the META file.
Until the mechanism is approved by the liboqs maintainers, could you implement this via patches to be META files, please?

if scheme.get('derandomized_keypair'):
for imp in scheme['metadata']['implementations']:
if 'signature_keypair_derand' not in imp and 'signature_keypair' in imp:
imp['signature_keypair_derand'] = imp['signature_keypair'] + '_internal'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incredibly fragile and I guarantee you that this is going to break in the future.
If liboqs wants to support an additional API, the symbol for it should be defined in the META files rather than appending an _internal to a symbol that is already there. Again this can be implemented as a patch to the META files first and later it should be upstreamed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completely concur (that this is fragile). Along the same lines, I'm also concerned with this API changing its behaviour based on whether or not the struct it's contained in is callocd or mallocd...

The key question in my eyes: Is this additional API indeed one that is truly generic, i.e., supported by all algorithms, or is it specific to ML-DSA (it seems so as only those files' are touched by the PR)? If so, wouldn't it be more apt to call the API ml_dsa_keypair_derand?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key question in my eyes: Is this additional API indeed one that is truly generic, i.e., supported by all algorithms, or is it specific to ML-DSA (it seems so as only those files' are touched by the PR)? If so, wouldn't it be more apt to call the API ml_dsa_keypair_derand?

While not all algorithms support using seed values, there are other algorithms that do. This PR will address the first algorithm, and the others will be addressed in separate PRs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While not all algorithms support using seed values, there are other algorithms that do. This PR will address the first algorithm, and the others will be addressed in separate PRs.

Why separate PRs? If one introduces a design for an API meant for multi-algorithm use, it should be introduced showing that it serves all of them.

Otherwise the API may need to be revised again and again as the "separate PRs" roll in. In the worst case, someone starts to use the initial API before those PRs arrive and the initial API becomes immutable, making re-factor hard and the library even less usable (different APIs for different algs and the same functionality are absolute anathema to the core value of OQS: One API for all PQ algs).

This statement makes me reject this PR until there is a clear declaration which further algorithms the new API shall serve and until when these are integrated such as to ensure a coherent approach.

Anything other leads to just another set of open issues, "design-by-coincidence" (or first example) and brittle/seldomly used code paths.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@baentsch, Just to clarify... The previous ML_DSA code base did not support deterministic key generation. The current version now does. Separate PRs would only be necessary if other algorithm authors introduced new APIs that OQS desired to support. I am not aware of any other upstream projects implementing this feature.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification @ashman-p ! Then back to my question above "wouldn't it be more apt to call the API ml_dsa_keypair_derand"?

@baentsch baentsch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provide plan how this change is going to serve other algorithms.

Signed-off-by: ashman-p <nashley@cisco.com>
@xuganyu96 xuganyu96 removed the focus Reserved for a small number of topics that have been identified as focus issues for the current week label Jul 21, 2026
length-public-key: 1312
length-secret-key: 2560
length-signature: 2420
length-keypair-seed: 32

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the derandomized_keypair appear also here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RodriM11, can you elaborate? In what context and why?

@RodriM11

Copy link
Copy Markdown
Member

The changes in general LGTM. The approach is very similar to the inclusion made for KEMs, with the difference that, in PR #2070 the API was defined for every algorithm, with dummy failing implementations made for every algorithm that did not support derandomized KeyPair generation (i.e. ML-KEM).

In this case, this implementation is made only for ML-DSA specifically, when there is another algorithm that actually supports it (SLH-DSA). I wonder if it makes sense to, once putting the effort onto this, actually expand it for every SIG algorithm (which is what #2475 originally describes). I believe the work wouldn't be a lot, since most of the expansion can be done in a similar way to #2070 via the copy_from_upstream.

@baentsch

Copy link
Copy Markdown
Member

I wonder if it makes sense to, once putting the effort onto this, actually expand it for every SIG algorithm (which is what #2475 originally describes)

I'd even state that that'd be a minimum requirement. Otherwise this PR truly only adds a bespoke ml_dsa_keypair_derand API.

But in general, I still think it's a bad idea to add "generically looking" APIs that in reality only a subset of algorithms support: This makes for

  • more fragile code,
  • surprising corner cases,
  • much more complicated usage (every API call has to be checked not just for unexpected errors but also "not-implemented-here" responses) and
  • even more no-op/boilerplate code of the sort you're asking for @RodriM11 .

All properties not desirable in a "security library".

--> If there's truly a need for this (and willingness by someone to support it), why not create a separate API for algorithms with separate features, e.g., a "post-standardization API" (outside of "common")? This would make the distinction in algorithms clearly visible for users. Also reviewers could focus: Those that support such limited scope API look and validate such bespoke code, those that don't, don't.

@mkannwischer

Copy link
Copy Markdown
Contributor

I have a draft for making the necessary changes to mldsa-native to support derand keygen without patches: pq-code-package/mldsa-native#1311. I'll hold that PR back until a decision has been made here.

I have no strong opinion whether or not liboqs should add such a new API. Should the consensus be that such an API would be useful, we are happy to support it. My view is that any standardized algorithm is going to offer such an API.

@baentsch

Copy link
Copy Markdown
Member

My view is that any standardized algorithm is going to offer such an API.

Thanks for that take @mkannwischer ! Supports a) the request to validate/make available a new API for more than just 1 alg and 2) my suggestion of having a specific API set for standardized algs -- if there's enough people willing to support that and enough downstream interest.

@ashman-p
ashman-p marked this pull request as draft July 23, 2026 13:50
@ashman-p

Copy link
Copy Markdown
Contributor

I have a draft for making the necessary changes to mldsa-native to support derand keygen without patches: pq-code-package/mldsa-native#1311. I'll hold that PR back until a decision has been made here.

I have no strong opinion whether or not liboqs should add such a new API. Should the consensus be that such an API would be useful, we are happy to support it. My view is that any standardized algorithm is going to offer such an API.

@mkannwischer, thanks for sharing your work that enables a patch-free implementation. I’d love to see it.

At the risk of seeming to speak for @systemslibrarian, would you be open to contributing to this PR’s collaborative effort?

Otherwise, i would recommend you publish your PR and let’s discuss it. I think we all mostly just want to see the feature/functionality implemented and usable.
I also think this capability will likely become the norm rather than the exception going forward.
I’ve moved this PR to draft state in the hope that you join-in if that is feasible.

@baentsch, if you think a new API is the better path, can you put out what you think it should look like and how it works across existing algorithms?

Thank you all.

@mkannwischer

Copy link
Copy Markdown
Contributor

I have a draft for making the necessary changes to mldsa-native to support derand keygen without patches: pq-code-package/mldsa-native#1311. I'll hold that PR back until a decision has been made here.
I have no strong opinion whether or not liboqs should add such a new API. Should the consensus be that such an API would be useful, we are happy to support it. My view is that any standardized algorithm is going to offer such an API.

@mkannwischer, thanks for sharing your work that enables a patch-free implementation. I’d love to see it.

At the risk of seeming to speak for @systemslibrarian, would you be open to contributing to this PR’s collaborative effort?

I did not mean to say that my PR is an alternative to this one. It implements the same API and it meant be be compatible (just removing the need for patches and implementing the return value normalization discussed in #2469).
We should be able to merge #2476 first, and then pull in the new mldsa-native implementation once a new release is published.
Apologies if that was unclear.

@baentsch

Copy link
Copy Markdown
Member

@baentsch, if you think a new API is the better path, can you put out what you think it should look like and how it works across existing algorithms?

There's a myriad of options how to skin that cat. Expecting TL;DR syndrome: The most obvious one is what an object oriented environment would call a "derived class".

If reading on, details: In C, that would be an OQS_STD_SIG struct using the current OQS structs/APIs as the first component and the new, std-alg-only function pointers & data structs as additional elements --> All core/"research" algs would remain as easily to use as before and don't have to be concerned with features relevant only to some algorithms: integration, testing, usage of those would remain (or even become again easy, if also applied to KEMs) as-is; all "finished"/std algs get their specific features/APIs; boilerplate gets eliminated; internal structures would become more easily understandable again.

We could develop that into a fully formed architecture improvement proposal, if anyone had an interest in that: Better usage structure, much better focused PR review & testing, better internal structures than the ad-hoc "alg-specific-feature-bolt-on" approach the project currently pursues and that I think is brittle and will (imo already does) lead to more and more internal (and downstream) complexity, bugs and reduced usability.

That said I won't do that effort on my own: 1) I already made too many proposals that no-one had interest or time to cooperate in for improving the project. 2) I think this is an effort wasting energy as other FOSS caters to PQ std algs much better -- all the while it's an afterthought in OQS, as the mere presence of this PR already proves.

As a compromise/litmus test below a proposal written up by an AI so I don't waste too much time creating proposals no-one reads anyway: oqs-stdalgs-extension-proposal.md. Feedback welcome (and if only by another AI :-).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: expose deterministic/derandomized keygen for the SIG family (OQS_SIG_*_keypair_derand) — mirroring the existing OQS_KEM derand API

6 participants