Skip to content

Commit fc09099

Browse files
committed
ML-DSA: Support deterministic signing
1 parent 5b13e52 commit fc09099

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
@@ -1031,6 +1031,8 @@ def build_ffi(local_wolfssl, features):
10311031
int wc_dilithium_export_public(dilithium_key* key, byte* out, word32* outLen);
10321032
int wc_dilithium_import_public(const byte* in, word32 inLen, dilithium_key* key);
10331033
int wc_dilithium_sign_msg(const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, WC_RNG* rng);
1034+
int wc_dilithium_sign_msg_with_seed(const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, const byte* seed);
1035+
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);
10341036
int wc_dilithium_verify_msg(const byte* sig, word32 sigLen, const byte* msg, word32 msgLen, int* res, dilithium_key* key);
10351037
typedef dilithium_key MlDsaKey;
10361038
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
@@ -2152,6 +2152,9 @@ def verify(self, signature, message):
21522152
return res[0] == 1
21532153

21542154
class MlDsaPrivate(_MlDsaBase):
2155+
_SIGNATURE_SEED_LENGTH = 32
2156+
"""The length of a signature generation seed."""
2157+
21552158
@classmethod
21562159
def make_key(cls, mldsa_type, rng=Random()):
21572160
"""
@@ -2280,6 +2283,60 @@ def sign(self, message, rng=Random()):
22802283

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

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

0 commit comments

Comments
 (0)