Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
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
1 change: 1 addition & 0 deletions scripts/build_ffi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,7 @@ def build_ffi(local_wolfssl, features):
int wc_dilithium_set_level(dilithium_key* key, byte level);
void wc_dilithium_free(dilithium_key* key);
int wc_dilithium_make_key(dilithium_key* key, WC_RNG* rng);
int wc_dilithium_make_key_from_seed(dilithium_key* key, const byte* seed);
int wc_dilithium_export_private(dilithium_key* key, byte* out, word32* outLen);
int wc_dilithium_import_private(const byte* priv, word32 privSz, dilithium_key* key);
int wc_dilithium_export_public(dilithium_key* key, byte* out, word32* outLen);
Expand Down
33 changes: 32 additions & 1 deletion tests/test_mldsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
if _lib.ML_DSA_ENABLED:
import pytest

from wolfcrypt.ciphers import MlDsaPrivate, MlDsaPublic, MlDsaType
from wolfcrypt.ciphers import MlDsaPrivate, MlDsaPublic, MlDsaType, ML_DSA_KEYGEN_SEED_LENGTH
from wolfcrypt.random import Random

@pytest.fixture
Expand Down Expand Up @@ -134,3 +134,34 @@ 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_generate_from_seed(mldsa_type, rng):
private_key_seed = rng.bytes(ML_DSA_KEYGEN_SEED_LENGTH)
mldsa_priv = MlDsaPrivate.make_key_from_seed(mldsa_type, private_key_seed)
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(message, rng)
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:
mldsa_priv_regenerated = MlDsaPrivate.make_key_from_seed(mldsa_type, private_key_seed)
assert mldsa_priv_regenerated.encode_priv_key() == mldsa_priv.encode_priv_key()
assert mldsa_priv_regenerated.encode_pub_key() == mldsa_priv.encode_pub_key()

# test that the seed length is checked:
with pytest.raises(ValueError):
mldsa_priv = MlDsaPrivate.make_key_from_seed(mldsa_type, bytes(ML_DSA_KEYGEN_SEED_LENGTH - 1))
with pytest.raises(ValueError):
mldsa_priv = MlDsaPrivate.make_key_from_seed(mldsa_type, bytes(ML_DSA_KEYGEN_SEED_LENGTH + 1))
# test that the seed type is checked (should be bytes-like, not a string)
with pytest.raises(TypeError):
mldsa_priv = MlDsaPrivate.make_key_from_seed(mldsa_type, 'a' * ML_DSA_KEYGEN_SEED_LENGTH)
34 changes: 34 additions & 0 deletions wolfcrypt/ciphers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2031,6 +2031,9 @@ def decapsulate(self, ct):


if _lib.ML_DSA_ENABLED:
ML_DSA_KEYGEN_SEED_LENGTH = 32
"""The length of a private key generation seed."""

class MlDsaType(IntEnum):
"""
`MlDsaType` specifies supported ML-DSA types.
Expand Down Expand Up @@ -2152,6 +2155,7 @@ def verify(self, signature, message):
return res[0] == 1

class MlDsaPrivate(_MlDsaBase):

@classmethod
def make_key(cls, mldsa_type, rng=Random()):
"""
Expand All @@ -2172,6 +2176,36 @@ def make_key(cls, mldsa_type, rng=Random()):

return mldsa_priv

@classmethod
def make_key_from_seed(cls, mldsa_type, seed):
"""
Deterministically generate the key from a seed.

:param mldsa_type: ML-DSA type
:type mldsa_type: MlDsaType
:param seed: the (32 byte) seed from which to deterministically create the key
:type seed: bytes
"""
mldsa_priv = cls(mldsa_type)
try:
seed_view = memoryview(seed)
except TypeError as exception:
raise TypeError(
"seed must support the buffer protocol, such as `bytes` or `bytearray`"
) from exception
if len(seed_view) != ML_DSA_KEYGEN_SEED_LENGTH:
raise ValueError(
f"Seed for generating ML-DSA key must be {ML_DSA_KEYGEN_SEED_LENGTH} bytes"
)

ret = _lib.wc_dilithium_make_key_from_seed(mldsa_priv.native_object,
_ffi.from_buffer(seed_view))

if ret < 0: # pragma: no cover
raise WolfCryptError("wc_dilithium_make_key_from_seed() error (%d)" % ret)

return mldsa_priv

@property
def pub_key_size(self):
"""
Expand Down
Loading