Skip to content

Conversation

@romen
Copy link
Member

@romen romen commented Dec 17, 2025

0.10.0 - 2025-12-17

⚠ BREAKING CHANGES

  • (pqclean) Switch to seed-only format for MLDSA44_Ed25519 private keys
  • (pqclean) Switch to seed-only format for MLDSA65_Ed25519 private keys

🚀 Features

  • Disallow building other profiles than debug
  • (encoders) Add text encoder for ML-DSA-65 public keys
  • (encoders) Add text encoder for ML-DSA-{44,87} public keys
  • (encoders) Add text encoder for ML-DSA-{44,65}-Ed25519 public keys
  • (adapters/common/transcoders) Do not clutter the current namespace when calling make_pubkey_text_encoder!
  • (pqclean) Add ENCODER_PrivateKeyInfo2Text for MLDSA and Composite MLDSA
  • (tests) Add basic wycheproof test for MLDSA65_Ed25519
  • (tests) Add wycheproof verify tests for pure ML-DSA
  • (tests) Use a testing harness for wycheproof mldsa65ed25519 tests
  • (tests) Add wycheproof signing tests for ML-DSA (pure & composite)
  • (tests) Run signing tests with seed-only keys
  • (pqclean) Fail gracefully on length error when decoding composite ML-DSA private keys
  • (pqclean) Implement sign and verify with ctx for pure ML-DSA
  • (pqclean) Implement sign and verify with ctx for composite ML-DSA
  • (pqclean) Consistently implement Signer/Verifier as a wrapper for SignerWithCtx/VerifierWithCtx
  • (pqclean) Derive private key from seed using rustcrypto-based helper
  • (pqclean) Validate decoding of private keys through foreign module

🐛 Bug Fixes

  • (tests) Don't refer to verify tests as sign tests in error message
  • (tests) Check test flags before describing key decoding error as "expected"
  • (tests) Remember to initialize crate::tests::common::setup for wycheproof tests
  • (pqclean) Validate ctx length before calling the backend

🚜 Refactor

  • (encoders) Extract a format_hex_bytes helper function
  • (encoders) Use a macro to generate plain text encoders for public keys
  • (encoders) Take encoder name as argument in text encoder generator macro
  • (common/transcoders) Make explicit that the Structureless2Text encoder is specific for public keys only
  • (common/transcoders/make_privkey_text_encoder) C functions should only do argument parsing and delegate logic to safe rust abstractions.
  • (common/transcoders/make_pubkey_text_encoder) C functions should only do argument parsing and delegate logic to safe rust abstractions.
  • (pqclean) Rename SupportedSecretKey trait to SupportedMlDsaSecretKey
  • (pqclean) Define ML-DSA seed type alias and enforce at callsite

📚 Documentation

  • (README) Add notes about SLH-DSA and hybrids
  • (readme) Fix typos and clarify project description
  • (doc,pqclean) Refer to pq-composite-sigs-13 everywhere

🧪 Testing

  • Add basic known-answer tests for composite signatures
  • (Cargo.toml) Revert to wycheproof-rs revision without the temporary extension for expanded private keys
  • (common/signature) Improve error message on expected signature length mismatch

Cleanup

  • (tests) Remove base64 dependency and hardcoded MLDSA44_Ed25519 test vectors
  • (tests) Build wycheproof module in test mode only
  • (pqclean/composites) Remove all legacy draft07 stuff
  • (pqclean) Rename helpers to make explicit they operate on mldsa

ashaindlin and others added 30 commits November 17, 2025 10:31
Now the public key material is displayed in the text output from e.g.
`openssl x509`.
This isn't specific to the pqclean adapter, so I created a new `helpers`
module in `adapters::common` for it.
…ublic keys

This is also a minor bugfix: the encoder now includes a terminating null
byte when writing out the string. The lack of one wasn't causing any
visible problems before, but since we're writing a string to memory for
a C application to use, I'm fairly certain it should be there.
…ce when calling `make_pubkey_text_encoder!`

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a6964001d50feeee0e099d332ab3ce1bd9af8
…xt encoder is specific for public keys only

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a696472f0c21fb074aa6882204c57729f9557
…ite MLDSA

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a69646ca9b10cb2dbf10e29ef781b7f8b9512
…hould only do argument parsing and delegate logic to safe rust abstractions.

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a696422f724d9619f94c9cf3f433c658fb1be
…ould only do argument parsing and delegate logic to safe rust abstractions.

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a6964239025a5abaf99d00d2014b971efb143
Kind of a proof-of-concept to get the base64 strings in there. Can't do
anything with the secret key until we have decoding for the seed-only
format implemented.

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a6964b3fe303177d8a9c1cb8a6c5b162ba646
This places the tests in the modules themselves (e.g.
`adapters::pqclean::MLDSA65`) instead of in the `signature` or submodule,
which is a bit awkward; it would be nice to figure out how to move them
there/if it's possible to make them generic enough to work in that file.

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a69647cb96f538961f26f61a1f5227a8da805
Also removes the old test for this algorithm that used hardcoded base64
strings. However, the dependency on the base64 crate remains, because
the mldsa44ed25519 test still uses them.
…"expected"

Previously, if decoding of the pubkey or privkey failed, the ML-DSA
Wycheproof tests would report that the attempt to decode the key "failed
as expected" (and mark the test as passed) for every test case that was
meant to fail, including test cases that were meant to successfully
decode the key but then fail for some other reason (e.g. the "context
too long" error expected in test case 5 of the signing tests).

(In practice, this only showed up for the privkey in the signing tests,
since tc5 was unexpectedly passing when I started running the seed-only
signing tests which aurora shouldn't yet be able to pass any of, but
I've preemptively fixed the same type of problem for the pubkey in the
verification tests as well.)
This fixes a bunch of "unused code" warnings.

Also, the wycheproof crate should only be marked as a dev-dependency as
we need it only for tests.

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a6964541da7ddcfef1aa3b7387b9eaaf217d5
…e ML-DSA private keys

This replaces commit 865da66e130867545a5587750e20708e0729b61f which was doing too much.

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a69643a74cc33d89de5ef7c77427822a7a069
Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a6964369686d749a864b024a3cfa566cf3b60
Done in preparation of breaking changes to composite mldsa private key
decoding

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a696493118910816276b7497e5713308888bd
Unfortunately there aren't any test vectors that actually use the
optional ctx field.
…r SignerWithCtx/VerifierWithCtx

Ideally one would actually make a blanket implementation like:

```
impl<T,S> Signer<S> for T
where T: SignerWithCtx<S>
{
    fn try_sign(&self, msg: &[u8]) -> Result<S, Error> {
        self.try_sign_with_ctx(msg, &[])
    }
}
```

Unfortunately this fails because of Rust’s orphan/coherence rules: you
can’t write a blanket impl of a foreign trait (Signer) for an
unconstrained type parameter (T), even if T is required to implement
your local trait (SignerWithCtx).

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a6964a1cd35113c5955988044576a19e50a36
Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a6964af0749dc6f3a74cfdec39a03fee0de91
…lper

All of the Wycheproof tests for signing with a seed-only key now pass.
However, none of the encoder/decoder code has been updated yet, and the
pqclean adapter still has no ability to store the seed; this commit only
adds a step during the initial "raw bytes to PrivateKey object"
conversion that derives the expanded key from seed if the bytes are a
seed (previously it required that the key already be in expanded form).

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a696454b47fbed251e59c4810364d7d3572ef
ashaindlin and others added 11 commits December 17, 2025 13:59
Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a69642a7106c52069a795c8eb8766bdba8197
- The type `PQPrivateKey` is now a struct that contains both the seed
  and the expanded form of the private key, instead of a type alias to
  the backend module's expanded-only key type.
- `PrivateKey::{decode,encode}` expect the key's encoded format to be a
  32-byte ML-DSA seed followed by a 32-byte Ed25519 key.
- `helpers::derive_secret_key_from_seed` is now `pub(super)`, so we can
  use it here without changing the more permissive (seed-only and
  expanded format both accepted) logic of the `helpers::decode_secret_key`
  function used by the pure ML-DSA algorithms in this adapter.
- The `to_DER` and `from_DER` methods on `PrivateKey` use
  `ASNPrivateKey::seed` instead of `ASNPrivateKey::expandedKey`. I don't
  think these methods were implemented correctly to begin with -- they
  use the pure ML-DSA key type from the `asn_definitions` module to wrap
  up bytes that represent a composite key -- hence why I bound the
  bytes to the name `bytes` instead of `seed` in the pattern-match, but
  this seemed to be the most sensible way to change the existing code
  for now to accompany the rest of this commit.

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a6964338980eb891819e5cabf8e9c6ea1b642
Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a6964ef6ceaf1fb03a2244f084cf915c16dec
…rary extension for expanded private keys

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a6964171d33da0d9bc01cde0f67602ae9ec0d
Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a6964159a65a1c86278fb4395b4fe1b8de68a
…cheproof tests

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a6964ea550087043150c7ad272187baa0ca0e
…length mismatch

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a6964e69e555d3e9bcbc25ff23a1e187d866c
Amend `try_sign_with_ctx` and `verify_with_ctx` to check the input ctx
length before calling the backend.

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a6964725f30821054fa2671f606eb1429c47a
Currently PQClean is too lenient in parsing private keys.
We can use a more strict-on-decode foreign module to try and correctly decode
the input, before asking PQClean to decode.

This is controlled at build time via
`VALIDATE_PRIVKEY_DECODING_VIA_FOREIGN_MODULE`,
defined as a const in src/adapters/pqclean/helpers.rs.
Currently it defaults to true.

Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a69649e6496591f506313a77d902220dae0bc
Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a69642e56d44e528bfd0dd86be82b7ee9e6c3
Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a69643261ffd92e91e851413559aa38cbc7fa
@romen romen self-assigned this Dec 17, 2025
Signed-off-by: Nicola Tuveri <nic.tuv@gmail.com>
Change-Id: I6a6a696407572af50cb79db6e4abf28e0d3a7acd
@romen romen force-pushed the nt/rel_prep/0.10.0 branch from 726de0d to 7da83ea Compare December 17, 2025 19:55
@romen romen merged commit 7da83ea into QUBIP:master Dec 17, 2025
9 checks passed
@romen romen deleted the nt/rel_prep/0.10.0 branch December 17, 2025 20:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants