Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions scripts/build_ffi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,8 @@ 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_msg_with_seed(const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, const byte* seed);
int wc_dilithium_sign_ctx_msg_with_seed(const byte* ctx, byte ctxLen, const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, const byte* seed);
int wc_dilithium_verify_msg(const byte* sig, word32 sigLen, const byte* msg, word32 msgLen, int* res, dilithium_key* key);
typedef dilithium_key MlDsaKey;
int wc_MlDsaKey_GetPrivLen(MlDsaKey* key, int* len);
Expand Down
30 changes: 30 additions & 0 deletions tests/test_mldsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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

# test that the seed size is checked:
with pytest.raises(AssertionError):
_ = mldsa_priv.sign_with_seed(message, signature_seed[:-1])

with pytest.raises(AssertionError):
_ = mldsa_priv.sign_with_seed(message, "")
57 changes: 57 additions & 0 deletions wolfcrypt/ciphers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

@classmethod
def make_key(cls, mldsa_type, rng=Random()):
"""
Expand Down Expand Up @@ -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."

if ctx is not None:
ctx_bytestype = t2b(ctx)
ret = _lib.wc_dilithium_sign_ctx_msg_with_seed(
_ffi.from_buffer(ctx_bytestype),
len(ctx_bytestype),
_ffi.from_buffer(msg_bytestype),
len(msg_bytestype),
signature,
out_size,
self.native_object,
_ffi.from_buffer(seed),
)
if ret < 0: # pragma: no cover
raise WolfCryptError("wc_dilithium_sign_ctx_msg_with_seed() error (%d)" % ret)
else:
ret = _lib.wc_dilithium_sign_msg_with_seed(
_ffi.from_buffer(msg_bytestype),
len(msg_bytestype),
signature,
out_size,
self.native_object,
_ffi.from_buffer(seed),
)
if ret < 0: # pragma: no cover
raise WolfCryptError("wc_dilithium_sign_msg_with_seed() error (%d)" % ret)


if in_size != out_size[0]:
raise WolfCryptError(
"in_size=%d and out_size=%d don't match" % (in_size, out_size[0])
)

return _ffi.buffer(signature, out_size[0])[:]

class MlDsaPublic(_MlDsaBase):
@property
def key_size(self):
Expand Down
Loading