-
Notifications
You must be signed in to change notification settings - Fork 29
ML-DSA: Support deterministic signing #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -28,6 +28,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from wolfcrypt.ciphers import MlDsaPrivate, MlDsaPublic, MlDsaType | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from wolfcrypt.random import Random | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ML_DSA_SIGNATURE_SEED_LENGTH = 32 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @pytest.fixture | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def rng(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Random() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -134,3 +136,31 @@ def test_sign_verify(mldsa_type, rng): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Verify with wrong message | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wrong_message = b"This is a wrong message for ML-DSA signature" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert not mldsa_pub.verify(signature, wrong_message) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_sign_with_seed(mldsa_type, rng): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| signature_seed = rng.bytes(ML_DSA_SIGNATURE_SEED_LENGTH) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mldsa_priv = MlDsaPrivate.make_key(mldsa_type, rng) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub_key = mldsa_priv.encode_pub_key() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Import public key | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mldsa_pub = MlDsaPublic(mldsa_type) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mldsa_pub.decode_key(pub_key) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Sign a message | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message = b"This is a test message for ML-DSA signature" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| signature = mldsa_priv.sign_with_seed(message, signature_seed) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert len(signature) == mldsa_priv.sig_size | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Verify the signature using public key | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert mldsa_pub.verify(signature, message) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # re-generate from the same seed: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| signature_from_same_seed = mldsa_priv.sign_with_seed(message, signature_seed) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert signature == signature_from_same_seed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Exercise the non-None ctx code path, including boundary conditions. | |
| empty_ctx = b"" | |
| signature_empty_ctx = mldsa_priv.sign_with_seed( | |
| message, signature_seed, ctx=empty_ctx | |
| ) | |
| assert len(signature_empty_ctx) == mldsa_priv.sig_size | |
| assert mldsa_pub.verify(signature_empty_ctx, message) | |
| assert signature_empty_ctx == mldsa_priv.sign_with_seed( | |
| message, signature_seed, ctx=empty_ctx | |
| ) | |
| ctx = b"ml-dsa ctx" | |
| signature_with_ctx = mldsa_priv.sign_with_seed(message, signature_seed, ctx=ctx) | |
| assert len(signature_with_ctx) == mldsa_priv.sig_size | |
| assert mldsa_pub.verify(signature_with_ctx, message) | |
| assert signature_with_ctx == mldsa_priv.sign_with_seed( | |
| message, signature_seed, ctx=ctx | |
| ) | |
| max_ctx = b"x" * 255 | |
| signature_max_ctx = mldsa_priv.sign_with_seed( | |
| message, signature_seed, ctx=max_ctx | |
| ) | |
| assert len(signature_max_ctx) == mldsa_priv.sig_size | |
| assert mldsa_pub.verify(signature_max_ctx, message) | |
| assert signature_max_ctx == mldsa_priv.sign_with_seed( | |
| message, signature_seed, ctx=max_ctx | |
| ) |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2152,6 +2152,9 @@ def verify(self, signature, message): | |||||||||||||||||||
| return res[0] == 1 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| class MlDsaPrivate(_MlDsaBase): | ||||||||||||||||||||
| _SIGNATURE_SEED_LENGTH = 32 | ||||||||||||||||||||
| """The length of a signature generation seed.""" | ||||||||||||||||||||
danielinux marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| @classmethod | ||||||||||||||||||||
| def make_key(cls, mldsa_type, rng=Random()): | ||||||||||||||||||||
| """ | ||||||||||||||||||||
|
|
@@ -2280,6 +2283,60 @@ def sign(self, message, rng=Random()): | |||||||||||||||||||
|
|
||||||||||||||||||||
| return _ffi.buffer(signature, out_size[0])[:] | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def sign_with_seed(self, message, seed, ctx=None): | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| :param message: message to be signed | ||||||||||||||||||||
| :type message: bytes or str | ||||||||||||||||||||
| :param seed: 32-byte seed for deterministic signature generation. | ||||||||||||||||||||
| :type seed: bytes | ||||||||||||||||||||
| :param ctx: context (optional) | ||||||||||||||||||||
| :type ctx: None for no context, str or bytes otherwise | ||||||||||||||||||||
| :return: signature | ||||||||||||||||||||
| :rtype: bytes | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| msg_bytestype = t2b(message) | ||||||||||||||||||||
| in_size = self.sig_size | ||||||||||||||||||||
| signature = _ffi.new(f"byte[{in_size}]") | ||||||||||||||||||||
| out_size = _ffi.new("word32 *") | ||||||||||||||||||||
| out_size[0] = in_size | ||||||||||||||||||||
|
|
||||||||||||||||||||
| assert isinstance(seed, bytes) and len(seed) == MlDsaPrivate._SIGNATURE_SEED_LENGTH, \ | ||||||||||||||||||||
| f"Seed for generating a signature must be {MlDsaPrivate._SIGNATURE_SEED_LENGTH} bytes." | ||||||||||||||||||||
|
Comment on lines
+2303
to
+2304
|
||||||||||||||||||||
| assert isinstance(seed, bytes) and len(seed) == MlDsaPrivate._SIGNATURE_SEED_LENGTH, \ | |
| f"Seed for generating a signature must be {MlDsaPrivate._SIGNATURE_SEED_LENGTH} bytes." | |
| if not isinstance(seed, bytes): | |
| raise TypeError("Seed for generating a signature must be bytes.") | |
| if len(seed) != MlDsaPrivate._SIGNATURE_SEED_LENGTH: | |
| raise ValueError( | |
| f"Seed for generating a signature must be " | |
| f"{MlDsaPrivate._SIGNATURE_SEED_LENGTH} bytes." | |
| ) |
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wc_dilithium_sign_ctx_msg_with_seed takes ctxLen as a byte (0–255), but len(ctx_bytestype) is passed without bounds checking. If ctx is longer than 255 bytes this will truncate/overflow at the C boundary and sign using an unintended context length. Validate len(ctx_bytestype) <= 255 (and raise a deterministic exception) before calling into _lib.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This duplicates the seed-length constant that is already defined on
MlDsaPrivate(_SIGNATURE_SEED_LENGTH). To avoid tests drifting if the library constant ever changes, consider referencingMlDsaPrivate._SIGNATURE_SEED_LENGTHdirectly instead of hard-coding32here.