Skip to main content

Module dsa

Module dsa 

Source
Expand description

Hybrid post-quantum digital signature combining ed25519 and ML-DSA-65, for signing JWTs (crate::misc::jwt) and Signed messages.

A signature consists of both an ML-DSA-65 and an ed25519 signature, and verification requires both to be valid.

§Relation to the standard, and the one deviation

This tracks draft-ietf-jose-pq-composite-sigs-01 (built on draft-ietf-lamps-pq-composite-sigs-19), which standardises this ML-DSA-65-Ed25519 composite. We follow it rather than an ad-hoc combiner so that, should Yivi adopt it for signing its JWTs in the future, conforming (see below) would let us verify Yivi’s signatures directly.

We replicate that combiner except for one detail: the standard signs the ML-DSA component with a non-empty context (mldsa_ctx = LABEL), but aws_lc_rs’s ML-DSA API (still unstable as of 1.17) exposes no context parameter, so we use the empty context (see ML_DSA_CTX). This makes our signatures non-conformant with the standard, and means the official test vectors (which use mldsa_ctx=LABEL) cannot be verified here yet.

Everything else — M', the SHA-512 prehash, the concatenation order, the key/signature byte formats, and the AKP JWK — is standard-correct. When aws_lc_rs exposes an ML-DSA context parameter (its C core already supports it), conforming is small and local:

  1. set ML_DSA_CTX to LABEL and thread it into ml_dsa_sign/ml_dsa_verify;
  2. rename ALG to "ML-DSA-65-Ed25519";
  3. add the official MLDSA65-Ed25519 test-vector check.

Structs§

SigningKey
Hybrid signing key, the ‘private key’. Generate using SigningKey::generate.
SigningKeyBytes
Encoded form of SigningKey, for storage.
VerifyingKey
Hybrid verifying key, the ‘public key’. Obtain via SigningKey::verifying_key.
VerifyingKeyBytes
Encoded form of VerifyingKey, for the wire.

Constants§

ALG
Value for the alg JWT header. Interim, non-conformant name; the standard reserves "ML-DSA-65-Ed25519" for the conformant variant (see module docs).
CTX 🔒
The composite application context ctx, encoded in M' as len(ctx) ‖ ctx — empty for our JWTs. Distinct from ML_DSA_CTX, the context of the underlying ML-DSA primitive.
ED25519_SIG_LEN 🔒
ed25519 signature length, in bytes.
LABEL 🔒
Label for the ML-DSA-65-Ed25519 combination, from draft-ietf-lamps-pq-composite-sigs-19 §6 “Algorithm Identifiers and Parameters”. It is the per-algorithm domain separator in the message representative M' = PREFIX ‖ LABEL ‖ … ‖ SHA-512(message) built by message_representative — and, in the conformant variant, the ML-DSA context (see ML_DSA_CTX).
ML_DSA_CTX 🔒
Context passed to the underlying ML-DSA primitive.
M_PRIME_LEN 🔒
Length of the message representative M' (see message_representative): PREFIX ‖ LABEL ‖ len(ctx) ‖ ctx ‖ SHA-512(..), a fixed size (the + 1 is the len(ctx) byte).
PREFIX 🔒
Prefix from the composite-signatures combiner — the fixed domain-separation string defined in draft-ietf-lamps-pq-composite-sigs-19 §2.2 “Prefix, Label, and CTX”.

Functions§

jwk_thumbprint 🔒
RFC 7638 JWK thumbprint of the AKP public key: base64url(SHA-256(canonical JSON of the required members alg, kty, pub, in lexicographic order)). pub_bytes is the composite public key ML-DSA-65 pk ‖ ed25519 pk (matching the JWK pub member); all member values are ASCII, so the canonical JSON needs no escaping. This value is the JWK kid. AKP key type and required members per RFC 9964 §6.
message_representative 🔒
The composite message representative M' = PREFIX ‖ LABEL ‖ len(ctx) ‖ ctx ‖ SHA-512(message). M' is not a hash we take but the message both components sign (each hashes it internally), so it must be passed whole. The application context is empty, so len(ctx) ‖ ctx is a single 0x00, making M' a fixed M_PRIME_LEN bytes. See draft-ietf-lamps-pq-composite-sigs-19 §2.2.
ml_dsa_sign 🔒
Signs m_prime with ML-DSA-65 into out, which must hold at least ML_DSA_65_SIGNING.signature_len() bytes.
ml_dsa_verify 🔒
Verifies an ML-DSA-65 signature on m_prime. See ml_dsa_sign for the context caveat.