Skip to content

Commit 0297859

Browse files
authored
Merge pull request #90 from mjdemilliano/ml-dsa-sign-with-context
ML-DSA: Add optional context to signing and verification
2 parents e5242a8 + 9ebe2fe commit 0297859

3 files changed

Lines changed: 84 additions & 25 deletions

File tree

scripts/build_ffi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,9 +1033,11 @@ def build_ffi(local_wolfssl, features):
10331033
int wc_dilithium_export_public(dilithium_key* key, byte* out, word32* outLen);
10341034
int wc_dilithium_import_public(const byte* in, word32 inLen, dilithium_key* key);
10351035
int wc_dilithium_sign_msg(const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, WC_RNG* rng);
1036+
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);
10361037
int wc_dilithium_sign_msg_with_seed(const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, const byte* seed);
10371038
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);
10381039
int wc_dilithium_verify_msg(const byte* sig, word32 sigLen, const byte* msg, word32 msgLen, int* res, dilithium_key* key);
1040+
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);
10391041
typedef dilithium_key MlDsaKey;
10401042
int wc_MlDsaKey_GetPrivLen(MlDsaKey* key, int* len);
10411043
int wc_MlDsaKey_GetPubLen(MlDsaKey* key, int* len);

tests/test_mldsa.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,28 @@ def test_sign_verify(mldsa_type, rng):
133133
# Verify with wrong message
134134
wrong_message = b"This is a wrong message for ML-DSA signature"
135135
assert not mldsa_pub.verify(signature, wrong_message)
136+
137+
# Verify a signature generated without a context but where a context
138+
# is provided during verify
139+
ctx = b"This is a test context for ML-DSA signature"
140+
wrong_ctx = b"This is a wrong context for ML-DSA signature"
141+
assert not mldsa_pub.verify(signature, message, ctx=wrong_ctx)
142+
143+
# Sign a message with context
144+
signature = mldsa_priv.sign(message, rng, ctx=ctx)
145+
assert len(signature) == mldsa_priv.sig_size
146+
147+
# Verify the signature by MlDsaPrivate
148+
assert mldsa_priv.verify(signature, message, ctx=ctx)
149+
150+
# Verify the signature by MlDsaPublic
151+
assert mldsa_pub.verify(signature, message, ctx=ctx)
152+
153+
# Verify but do not provide a context
154+
assert not mldsa_pub.verify(signature, message, ctx=None)
155+
156+
# Verify with wrong context
157+
assert not mldsa_pub.verify(signature, message, ctx=wrong_ctx)
136158

137159
def test_sign_with_seed(mldsa_type, rng):
138160
signature_seed = rng.bytes(ML_DSA_SIGNATURE_SEED_LENGTH)
@@ -180,3 +202,4 @@ def test_sign_with_seed_and_context(mldsa_type, rng):
180202
# test that the context length is checked (more than 255 bytes is invalid):
181203
with pytest.raises(ValueError):
182204
_ = mldsa_priv.sign_with_seed(message, signature_seed[:-1], ctx=bytes(1000))
205+

wolfcrypt/ciphers.py

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2124,30 +2124,46 @@ def _encode_pub_key(self):
21242124

21252125
return _ffi.buffer(pub_key, out_size[0])[:]
21262126

2127-
def verify(self, signature, message):
2127+
def verify(self, signature, message, ctx=None):
21282128
"""
21292129
:param signature: signature to be verified
21302130
:type signature: bytes or str
21312131
:param message: message to be verified
21322132
:type message: bytes or str
2133+
:param ctx: context (optional)
2134+
:type ctx: None for no context, str or bytes otherwise
21332135
:return: True if the verification is successful, False otherwise
21342136
:rtype: bool
21352137
"""
21362138
sig_bytestype = t2b(signature)
21372139
msg_bytestype = t2b(message)
21382140
res = _ffi.new("int *")
21392141

2140-
ret = _lib.wc_dilithium_verify_msg(
2141-
_ffi.from_buffer(sig_bytestype),
2142-
len(sig_bytestype),
2143-
_ffi.from_buffer(msg_bytestype),
2144-
len(msg_bytestype),
2145-
res,
2146-
self.native_object,
2147-
)
2148-
2149-
if ret < 0: # pragma: no cover
2150-
raise WolfCryptError("wc_dilithium_verify_msg() error (%d)" % ret)
2142+
if ctx is not None:
2143+
ctx_bytestype = t2b(ctx)
2144+
ret = _lib.wc_dilithium_verify_ctx_msg(
2145+
_ffi.from_buffer(sig_bytestype),
2146+
len(sig_bytestype),
2147+
_ffi.from_buffer(ctx_bytestype),
2148+
len(ctx_bytestype),
2149+
_ffi.from_buffer(msg_bytestype),
2150+
len(msg_bytestype),
2151+
res,
2152+
self.native_object,
2153+
)
2154+
if ret < 0: # pragma: no cover
2155+
raise WolfCryptError("wc_dilithium_verify_ctx_msg() error (%d)" % ret)
2156+
else:
2157+
ret = _lib.wc_dilithium_verify_msg(
2158+
_ffi.from_buffer(sig_bytestype),
2159+
len(sig_bytestype),
2160+
_ffi.from_buffer(msg_bytestype),
2161+
len(msg_bytestype),
2162+
res,
2163+
self.native_object,
2164+
)
2165+
if ret < 0: # pragma: no cover
2166+
raise WolfCryptError("wc_dilithium_verify_msg() error (%d)" % ret)
21512167

21522168
return res[0] == 1
21532169

@@ -2277,12 +2293,14 @@ def decode_key(self, priv_key, pub_key=None):
22772293
if pub_key is not None:
22782294
self._decode_pub_key(pub_key)
22792295

2280-
def sign(self, message, rng=Random()):
2296+
def sign(self, message, rng=Random(), ctx=None):
22812297
"""
22822298
:param message: message to be signed
22832299
:type message: bytes or str
22842300
:param rng: random number generator for sign
22852301
:type rng: Random
2302+
:param ctx: context (optional, maximum 255 bytes)
2303+
:type ctx: None for no context, str or bytes otherwise
22862304
:return: signature
22872305
:rtype: bytes
22882306
"""
@@ -2292,18 +2310,34 @@ def sign(self, message, rng=Random()):
22922310
out_size = _ffi.new("word32 *")
22932311
out_size[0] = in_size
22942312

2295-
ret = _lib.wc_dilithium_sign_msg(
2296-
_ffi.from_buffer(msg_bytestype),
2297-
len(msg_bytestype),
2298-
signature,
2299-
out_size,
2300-
self.native_object,
2301-
rng.native_object,
2302-
)
2303-
2304-
if ret < 0: # pragma: no cover
2305-
raise WolfCryptError("wc_dilithium_sign_msg() error (%d)" % ret)
2306-
2313+
if ctx is not None:
2314+
ctx_bytestype = t2b(ctx)
2315+
if len(ctx_bytestype) > 255:
2316+
raise ValueError(f"context length {len(ctx_bytestype)} too large: must be 255 bytes or less")
2317+
ret = _lib.wc_dilithium_sign_ctx_msg(
2318+
_ffi.from_buffer(ctx_bytestype),
2319+
len(ctx_bytestype), # length must be < 256 bytes
2320+
_ffi.from_buffer(msg_bytestype),
2321+
len(msg_bytestype),
2322+
signature,
2323+
out_size,
2324+
self.native_object,
2325+
rng.native_object,
2326+
)
2327+
if ret < 0: # pragma: no cover
2328+
raise WolfCryptError("wc_dilithium_sign_ctx_msg() error (%d)" % ret)
2329+
else:
2330+
ret = _lib.wc_dilithium_sign_msg(
2331+
_ffi.from_buffer(msg_bytestype),
2332+
len(msg_bytestype),
2333+
signature,
2334+
out_size,
2335+
self.native_object,
2336+
rng.native_object,
2337+
)
2338+
if ret < 0: # pragma: no cover
2339+
raise WolfCryptError("wc_dilithium_sign_msg() error (%d)" % ret)
2340+
23072341
if in_size != out_size[0]:
23082342
raise WolfCryptError(
23092343
"in_size=%d and out_size=%d don't match" % (in_size, out_size[0])

0 commit comments

Comments
 (0)