ML-DSA: Add optional context to signing and verification

pull/90/head
Martijn de Milliano 2026-03-24 21:32:17 +01:00
parent 5b13e52d57
commit 9a10f58601
3 changed files with 68 additions and 18 deletions

View File

@ -1031,7 +1031,9 @@ def build_ffi(local_wolfssl, features):
int wc_dilithium_export_public(dilithium_key* key, byte* out, word32* outLen);
int wc_dilithium_import_public(const byte* in, word32 inLen, dilithium_key* key);
int wc_dilithium_sign_msg(const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, WC_RNG* rng);
int wc_dilithium_sign_ctx_msg(const byte* ctx, byte ctxLen, const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, WC_RNG* rng);
int wc_dilithium_verify_msg(const byte* sig, word32 sigLen, const byte* msg, word32 msgLen, int* res, dilithium_key* key);
int wc_dilithium_verify_ctx_msg(const byte* sig, word32 sigLen, const byte* ctx, word32 ctxLen, const byte* msg, word32 msgLen, int* res, dilithium_key* key);
typedef dilithium_key MlDsaKey;
int wc_MlDsaKey_GetPrivLen(MlDsaKey* key, int* len);
int wc_MlDsaKey_GetPubLen(MlDsaKey* key, int* len);

View File

@ -134,3 +134,21 @@ if _lib.ML_DSA_ENABLED:
# Verify with wrong message
wrong_message = b"This is a wrong message for ML-DSA signature"
assert not mldsa_pub.verify(signature, wrong_message)
# Verify with ctx for signature generated without
ctx = b"This is a test context for ML-DSA signature"
wrong_ctx = b"This is a wrong context for ML-DSA signature"
assert not mldsa_pub.verify(signature, message, ctx=wrong_ctx)
# Sign a message with context
signature = mldsa_priv.sign(message, rng, ctx=ctx)
assert len(signature) == mldsa_priv.sig_size
# Verify the signature by MlDsaPrivate
assert mldsa_priv.verify(signature, message, ctx=ctx)
# Verify the signature by MlDsaPublic
assert mldsa_pub.verify(signature, message, ctx=ctx)
# Verify with wrong ctx
assert not mldsa_pub.verify(signature, message, ctx=wrong_ctx)

View File

@ -2124,12 +2124,14 @@ if _lib.ML_DSA_ENABLED:
return _ffi.buffer(pub_key, out_size[0])[:]
def verify(self, signature, message):
def verify(self, signature, message, ctx=None):
"""
:param signature: signature to be verified
:type signature: bytes or str
:param message: message to be verified
:type message: bytes or str
:param ctx: context (optional)
:type ctx: None for no context, str or bytes otherwise
:return: True if the verification is successful, False otherwise
:rtype: bool
"""
@ -2137,14 +2139,27 @@ if _lib.ML_DSA_ENABLED:
msg_bytestype = t2b(message)
res = _ffi.new("int *")
ret = _lib.wc_dilithium_verify_msg(
_ffi.from_buffer(sig_bytestype),
len(sig_bytestype),
_ffi.from_buffer(msg_bytestype),
len(msg_bytestype),
res,
self.native_object,
)
if ctx is not None:
ctx_bytestype = t2b(ctx)
ret = _lib.wc_dilithium_verify_ctx_msg(
_ffi.from_buffer(sig_bytestype),
len(sig_bytestype),
_ffi.from_buffer(ctx_bytestype),
len(ctx_bytestype),
_ffi.from_buffer(msg_bytestype),
len(msg_bytestype),
res,
self.native_object,
)
else:
ret = _lib.wc_dilithium_verify_msg(
_ffi.from_buffer(sig_bytestype),
len(sig_bytestype),
_ffi.from_buffer(msg_bytestype),
len(msg_bytestype),
res,
self.native_object,
)
if ret < 0: # pragma: no cover
raise WolfCryptError("wc_dilithium_verify_msg() error (%d)" % ret)
@ -2246,12 +2261,14 @@ if _lib.ML_DSA_ENABLED:
if pub_key is not None:
self._decode_pub_key(pub_key)
def sign(self, message, rng=Random()):
def sign(self, message, rng=Random(), ctx=None):
"""
:param message: message to be signed
:type message: bytes or str
:param rng: random number generator for sign
:type rng: Random
:param ctx: context (optional)
:type ctx: None for no context, str or bytes otherwise
:return: signature
:rtype: bytes
"""
@ -2261,14 +2278,27 @@ if _lib.ML_DSA_ENABLED:
out_size = _ffi.new("word32 *")
out_size[0] = in_size
ret = _lib.wc_dilithium_sign_msg(
_ffi.from_buffer(msg_bytestype),
len(msg_bytestype),
signature,
out_size,
self.native_object,
rng.native_object,
)
if ctx is not None:
ctx_bytestype = t2b(ctx)
ret = _lib.wc_dilithium_sign_ctx_msg(
_ffi.from_buffer(ctx_bytestype),
len(ctx_bytestype),
_ffi.from_buffer(msg_bytestype),
len(msg_bytestype),
signature,
out_size,
self.native_object,
rng.native_object,
)
else:
ret = _lib.wc_dilithium_sign_msg(
_ffi.from_buffer(msg_bytestype),
len(msg_bytestype),
signature,
out_size,
self.native_object,
rng.native_object,
)
if ret < 0: # pragma: no cover
raise WolfCryptError("wc_dilithium_sign_msg() error (%d)" % ret)