Skip to content

Commit e36859c

Browse files
mjdemillianodanielinux
authored andcommitted
ML-DSA: Support deterministic signing
1 parent 6fbdf3d commit e36859c

3 files changed

Lines changed: 89 additions & 0 deletions

File tree

scripts/build_ffi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,8 @@ def build_ffi(local_wolfssl, features):
10321032
int wc_dilithium_export_public(dilithium_key* key, byte* out, word32* outLen);
10331033
int wc_dilithium_import_public(const byte* in, word32 inLen, dilithium_key* key);
10341034
int wc_dilithium_sign_msg(const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, WC_RNG* rng);
1035+
int wc_dilithium_sign_msg_with_seed(const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, const byte* seed);
1036+
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);
10351037
int wc_dilithium_verify_msg(const byte* sig, word32 sigLen, const byte* msg, word32 msgLen, int* res, dilithium_key* key);
10361038
typedef dilithium_key MlDsaKey;
10371039
int wc_MlDsaKey_GetPrivLen(MlDsaKey* key, int* len);

tests/test_mldsa.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
from wolfcrypt.ciphers import MlDsaPrivate, MlDsaPublic, MlDsaType
2929
from wolfcrypt.random import Random
3030

31+
ML_DSA_SIGNATURE_SEED_LENGTH = 32
32+
3133
@pytest.fixture
3234
def rng():
3335
return Random()
@@ -134,3 +136,31 @@ def test_sign_verify(mldsa_type, rng):
134136
# Verify with wrong message
135137
wrong_message = b"This is a wrong message for ML-DSA signature"
136138
assert not mldsa_pub.verify(signature, wrong_message)
139+
140+
def test_sign_with_seed(mldsa_type, rng):
141+
signature_seed = rng.bytes(ML_DSA_SIGNATURE_SEED_LENGTH)
142+
mldsa_priv = MlDsaPrivate.make_key(mldsa_type, rng)
143+
pub_key = mldsa_priv.encode_pub_key()
144+
145+
# Import public key
146+
mldsa_pub = MlDsaPublic(mldsa_type)
147+
mldsa_pub.decode_key(pub_key)
148+
149+
# Sign a message
150+
message = b"This is a test message for ML-DSA signature"
151+
signature = mldsa_priv.sign_with_seed(message, signature_seed)
152+
assert len(signature) == mldsa_priv.sig_size
153+
154+
# Verify the signature using public key
155+
assert mldsa_pub.verify(signature, message)
156+
157+
# re-generate from the same seed:
158+
signature_from_same_seed = mldsa_priv.sign_with_seed(message, signature_seed)
159+
assert signature == signature_from_same_seed
160+
161+
# test that the seed size is checked:
162+
with pytest.raises(AssertionError):
163+
_ = mldsa_priv.sign_with_seed(message, signature_seed[:-1])
164+
165+
with pytest.raises(AssertionError):
166+
_ = mldsa_priv.sign_with_seed(message, "")

wolfcrypt/ciphers.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2149,6 +2149,9 @@ def verify(self, signature, message):
21492149
return res[0] == 1
21502150

21512151
class MlDsaPrivate(_MlDsaBase):
2152+
_SIGNATURE_SEED_LENGTH = 32
2153+
"""The length of a signature generation seed."""
2154+
21522155
@classmethod
21532156
def make_key(cls, mldsa_type, rng=Random()):
21542157
"""
@@ -2277,6 +2280,60 @@ def sign(self, message, rng=Random()):
22772280

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

2283+
def sign_with_seed(self, message, seed, ctx=None):
2284+
"""
2285+
:param message: message to be signed
2286+
:type message: bytes or str
2287+
:param seed: 32-byte seed for deterministic signature generation.
2288+
:type seed: bytes
2289+
:param ctx: context (optional)
2290+
:type ctx: None for no context, str or bytes otherwise
2291+
:return: signature
2292+
:rtype: bytes
2293+
"""
2294+
msg_bytestype = t2b(message)
2295+
in_size = self.sig_size
2296+
signature = _ffi.new(f"byte[{in_size}]")
2297+
out_size = _ffi.new("word32 *")
2298+
out_size[0] = in_size
2299+
2300+
assert isinstance(seed, bytes) and len(seed) == MlDsaPrivate._SIGNATURE_SEED_LENGTH, \
2301+
f"Seed for generating a signature must be {MlDsaPrivate._SIGNATURE_SEED_LENGTH} bytes."
2302+
2303+
if ctx is not None:
2304+
ctx_bytestype = t2b(ctx)
2305+
ret = _lib.wc_dilithium_sign_ctx_msg_with_seed(
2306+
_ffi.from_buffer(ctx_bytestype),
2307+
len(ctx_bytestype),
2308+
_ffi.from_buffer(msg_bytestype),
2309+
len(msg_bytestype),
2310+
signature,
2311+
out_size,
2312+
self.native_object,
2313+
_ffi.from_buffer(seed),
2314+
)
2315+
if ret < 0: # pragma: no cover
2316+
raise WolfCryptError("wc_dilithium_sign_ctx_msg_with_seed() error (%d)" % ret)
2317+
else:
2318+
ret = _lib.wc_dilithium_sign_msg_with_seed(
2319+
_ffi.from_buffer(msg_bytestype),
2320+
len(msg_bytestype),
2321+
signature,
2322+
out_size,
2323+
self.native_object,
2324+
_ffi.from_buffer(seed),
2325+
)
2326+
if ret < 0: # pragma: no cover
2327+
raise WolfCryptError("wc_dilithium_sign_msg_with_seed() error (%d)" % ret)
2328+
2329+
2330+
if in_size != out_size[0]:
2331+
raise WolfCryptError(
2332+
"in_size=%d and out_size=%d don't match" % (in_size, out_size[0])
2333+
)
2334+
2335+
return _ffi.buffer(signature, out_size[0])[:]
2336+
22802337
class MlDsaPublic(_MlDsaBase):
22812338
@property
22822339
def key_size(self):

0 commit comments

Comments
 (0)