ML-DSA: Support (re-)generating MlDsaPrivate from seed
As specified in FIPS 204, implementations can store the seed from which the key can be deterministically generated.pull/89/head
parent
5b13e52d57
commit
1c034b20d8
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ if _lib.ML_DSA_ENABLED:
|
|||
from wolfcrypt.ciphers import MlDsaPrivate, MlDsaPublic, MlDsaType
|
||||
from wolfcrypt.random import Random
|
||||
|
||||
ML_DSA_SEED_LENGTH = 32
|
||||
|
||||
@pytest.fixture
|
||||
def rng():
|
||||
return Random()
|
||||
|
|
@ -134,3 +136,35 @@ if _lib.ML_DSA_ENABLED:
|
|||
# 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_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 is checked:
|
||||
with pytest.raises(AssertionError):
|
||||
mldsa_priv = MlDsaPrivate.make_key_from_seed(mldsa_type, private_key_seed[:-1])
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
mldsa_priv = MlDsaPrivate.make_key_from_seed(mldsa_type, private_key_seed + bytes(1))
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
mldsa_priv = MlDsaPrivate.make_key_from_seed(mldsa_type, 'a' * 32)
|
||||
|
|
|
|||
|
|
@ -2152,6 +2152,9 @@ if _lib.ML_DSA_ENABLED:
|
|||
return res[0] == 1
|
||||
|
||||
class MlDsaPrivate(_MlDsaBase):
|
||||
_SEED_LENGTH = 32
|
||||
"""The length of a private key generation seed."""
|
||||
|
||||
@classmethod
|
||||
def make_key(cls, mldsa_type, rng=Random()):
|
||||
"""
|
||||
|
|
@ -2172,6 +2175,28 @@ if _lib.ML_DSA_ENABLED:
|
|||
|
||||
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)
|
||||
assert isinstance(seed, bytes) and len(seed) == MlDsaPrivate._SEED_LENGTH, \
|
||||
f"Seed for generating ML-DSA key must be {MlDsaPrivate._SEED_LENGTH} bytes"
|
||||
|
||||
ret = _lib.wc_dilithium_make_key_from_seed(mldsa_priv.native_object,
|
||||
_ffi.from_buffer(seed))
|
||||
|
||||
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):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue