4747FALCON_NAMES = ["falcon-512" , "falcon-1024" , "falcon-padded-512" , "falcon-padded-1024" ]
4848ML_DSA_NAMES = ["ml-dsa-44" , "ml-dsa-65" , "ml-dsa-87" ]
4949
50+ # Mapping of SLH-DSA algorithm names to their liboqs counterparts.
51+ SLH_DSA_LIBOQS_NAME_MAP = {
52+ "slh-dsa-sha2-128s" : "SLH_DSA_PURE_SHA2_128S" ,
53+ "slh-dsa-sha2-128f" : "SLH_DSA_PURE_SHA2_128F" ,
54+ "slh-dsa-sha2-192s" : "SLH_DSA_PURE_SHA2_192S" ,
55+ "slh-dsa-sha2-192f" : "SLH_DSA_PURE_SHA2_192F" ,
56+ "slh-dsa-sha2-256s" : "SLH_DSA_PURE_SHA2_256S" ,
57+ "slh-dsa-sha2-256f" : "SLH_DSA_PURE_SHA2_256F" ,
58+ "slh-dsa-shake-128s" : "SLH_DSA_PURE_SHAKE_128S" ,
59+ "slh-dsa-shake-128f" : "SLH_DSA_PURE_SHAKE_128F" ,
60+ "slh-dsa-shake-192s" : "SLH_DSA_PURE_SHAKE_192S" ,
61+ "slh-dsa-shake-192f" : "SLH_DSA_PURE_SHAKE_192F" ,
62+ "slh-dsa-shake-256s" : "SLH_DSA_PURE_SHAKE_256S" ,
63+ "slh-dsa-shake-256f" : "SLH_DSA_PURE_SHAKE_256F" ,
64+ # Pre-hash variants
65+ "slh-dsa-sha2-128s-sha256" : "SLH_DSA_SHA2_256_PREHASH_SHA2_128S" ,
66+ "slh-dsa-sha2-128f-sha256" : "SLH_DSA_SHA2_256_PREHASH_SHA2_128F" ,
67+ "slh-dsa-sha2-192s-sha512" : "SLH_DSA_SHA2_512_PREHASH_SHA2_192S" ,
68+ "slh-dsa-sha2-192f-sha512" : "SLH_DSA_SHA2_512_PREHASH_SHA2_192F" ,
69+ "slh-dsa-sha2-256s-sha512" : "SLH_DSA_SHA2_512_PREHASH_SHA2_256S" ,
70+ "slh-dsa-sha2-256f-sha512" : "SLH_DSA_SHA2_512_PREHASH_SHA2_256F" ,
71+ "slh-dsa-shake-128s-shake128" : "SLH_DSA_SHAKE_128_PREHASH_SHAKE_128S" ,
72+ "slh-dsa-shake-128f-shake128" : "SLH_DSA_SHAKE_128_PREHASH_SHAKE_128F" ,
73+ "slh-dsa-shake-192s-shake256" : "SLH_DSA_SHAKE_256_PREHASH_SHAKE_192S" ,
74+ "slh-dsa-shake-192f-shake256" : "SLH_DSA_SHAKE_256_PREHASH_SHAKE_192F" ,
75+ "slh-dsa-shake-256s-shake256" : "SLH_DSA_SHAKE_256_PREHASH_SHAKE_256S" ,
76+ "slh-dsa-shake-256f-shake256" : "SLH_DSA_SHAKE_256_PREHASH_SHAKE_256F" ,
77+ }
78+
5079
5180class MLDSAPublicKey (PQSignaturePublicKey ):
5281 """Represent an ML-DSA public key."""
@@ -382,6 +411,32 @@ def seed_size(self) -> int:
382411##########################
383412
384413
414+ def _get_liboqs_slh_dsa_name (alg_name : str , hash_alg : Optional [str ]) -> str :
415+ """Get the SLH-DSA algorithm name based on the base algorithm and hash algorithm.
416+
417+ :param alg_name: The base SLH-DSA algorithm name (e.g., "slh-dsa-sha2-128s").
418+ :param hash_alg: The optional hash algorithm name (e.g., "sha256").
419+ :return: The combined SLH-DSA algorithm name.
420+ """
421+ alg_name = alg_name .lower ()
422+
423+ if alg_name not in SLH_DSA_LIBOQS_NAME_MAP :
424+ raise ValueError (f"Invalid SLH-DSA algorithm name provided: { alg_name } ." )
425+
426+ if hash_alg is None :
427+ return SLH_DSA_LIBOQS_NAME_MAP [alg_name ]
428+
429+ hash_alg = hash_alg .lower ()
430+
431+ if hash_alg in ["sha256" , "sha512" , "shake128" , "shake256" ]:
432+ combined_name = f"{ alg_name } -{ hash_alg } "
433+ if combined_name in SLH_DSA_PRE_HASH_NAME_2_OID :
434+ return SLH_DSA_LIBOQS_NAME_MAP [combined_name ]
435+ raise ValueError (f"Invalid combination of SLH-DSA and hash algorithm: { combined_name } ." )
436+
437+ raise ValueError (f"Invalid hash algorithm for SLH-DSA: { hash_alg } ." )
438+
439+
385440class SLHDSAPublicKey (PQSignaturePublicKey ):
386441 """Represent an SLH-DSA public key."""
387442
@@ -399,6 +454,14 @@ def _initialize_key(self) -> None:
399454 _other = self .name .replace ("_" , "-" )
400455 self ._slh_class : SLH_DSA = fips205 .SLH_DSA_PARAMS [_other ]
401456
457+ if oqs is not None :
458+ oqs_name = SLH_DSA_LIBOQS_NAME_MAP .get (self .name )
459+ if oqs_name is not None :
460+ try :
461+ self ._sig_method = oqs .Signature (oqs_name )
462+ except Exception : # pragma: no cover - liboqs not available pylint: disable=broad-exception-caught
463+ self ._sig_method = None
464+
402465 def _check_name (self , name : str ) -> Tuple [str , str ]:
403466 """Check if the name is valid."""
404467 return name , name .replace ("_" , "-" )
@@ -421,6 +484,26 @@ def check_hash_alg(self, hash_alg: Union[None, str, hashes.HashAlgorithm]) -> Op
421484 logging .info ("%s does not support the hash algorithm: %s" , self .name , hash_alg )
422485 return None
423486
487+ def _verify_oqs_signature (self , signature : bytes , data : bytes , ctx : bytes , hash_alg : Optional [str ]) -> bool :
488+ """Verify the signature using liboqs.
489+
490+ :param signature: The signature to verify.
491+ :param data: The data to verify.
492+ :param ctx: The context to add for the signature. Defaults to `b""`.
493+ :return: True if the signature is valid, False otherwise.
494+ :raises `InvalidSignature`: If the signature method is not initialized.
495+ """
496+ if self ._sig_method is None :
497+ raise ValueError ("liboqs signature method is not initialized." )
498+ try :
499+ tmp_name = _get_liboqs_slh_dsa_name (alg_name = self .name , hash_alg = hash_alg )
500+ with oqs .Signature (tmp_name ) as _sig_method :
501+ result = _sig_method .verify_with_ctx_str (data , signature , ctx , self ._public_key_bytes )
502+
503+ except RuntimeError as exc :
504+ raise InvalidSignature () from exc
505+ return result
506+
424507 def verify (
425508 self ,
426509 signature : bytes ,
@@ -439,6 +522,19 @@ def verify(
439522 :raises InvalidSignature: If the signature is invalid.
440523 """
441524 hash_alg = self .check_hash_alg (hash_alg = hash_alg )
525+ msg = "SLH-DSA Signature verification failed."
526+
527+ if len (ctx ) > 255 :
528+ raise ValueError (f"The context length is longer than 255 bytes. Got: { len (ctx )} " )
529+
530+ if not is_prehashed and getattr (self , "_sig_method" , None ):
531+ logging .info ("Verify SLH-DSA signature with `liboqs`." )
532+
533+ try :
534+ self ._verify_oqs_signature (signature , data , ctx , hash_alg )
535+ except (RuntimeError , InvalidSignature ) as exc :
536+ raise InvalidSignature (msg ) from exc
537+
442538 if hash_alg is None :
443539 sig = self ._slh_class .slh_verify (m = data , sig = signature , pk = self ._public_key_bytes , ctx = ctx )
444540 else :
@@ -451,7 +547,7 @@ def verify(
451547 sig = self ._slh_class .slh_verify_internal (m = mp , sig = signature , pk = self ._public_key_bytes )
452548
453549 if not sig :
454- raise InvalidSignature ()
550+ raise InvalidSignature (msg )
455551
456552 @classmethod
457553 def from_public_bytes (cls , data : bytes , name : str ) -> "SLHDSAPublicKey" :
@@ -521,6 +617,20 @@ def _from_seed(alg_name: str, seed: Optional[bytes]) -> Tuple[bytes, bytes, byte
521617
522618 def _initialize_key (self ) -> None :
523619 """Initialize the SLH-DSA private key."""
620+ try :
621+ if oqs is not None :
622+ self ._sig_method = oqs .Signature (SLH_DSA_LIBOQS_NAME_MAP [self .name ], secret_key = self ._private_key_bytes )
623+ if self ._private_key_bytes is None and self ._seed is None :
624+ logging .info ("Generate SLH-DSA keypair with `liboqs`" )
625+ print ("Generate SLH-DSA keypair with `liboqs`" )
626+ self ._public_key_bytes = self ._sig_method .generate_keypair ()
627+ self ._private_key_bytes = self ._sig_method .export_secret_key ()
628+ seed_size = self ._seed_size (self .name )
629+ self ._seed = self ._private_key_bytes [:seed_size ]
630+
631+ except Exception : # pragma: no cover - liboqs not available pylint: disable=broad-exception-caught
632+ self ._sig_method = None
633+
524634 self ._slh_class : SLH_DSA = fips205 .SLH_DSA_PARAMS [self ._other_name ]
525635 if self ._private_key_bytes is None and self ._public_key_bytes is None :
526636 priv_key , pub_key , seed = self ._from_seed (self .name , self ._seed )
@@ -558,6 +668,26 @@ def public_key(self) -> SLHDSAPublicKey:
558668 """
559669 return SLHDSAPublicKey (alg_name = self .name , public_key = self ._public_key_bytes )
560670
671+ def _sign_with_oqs (self , data : bytes , ctx : bytes , hash_alg : Optional [str ]) -> bytes :
672+ """Sign the data using liboqs.
673+
674+ :param data: The data to sign.
675+ :param ctx: The context to add for the signature. Defaults to `b""`.
676+ :param hash_alg: The optional hash algorithm to use for the pre-hashing of the data.
677+ :return: The computed signature.
678+ :raises ValueError: If the signature method is not initialized.
679+ """
680+ if self ._sig_method is None :
681+ raise ValueError ("liboqs signature method is not initialized." )
682+ try :
683+ tmp_name = _get_liboqs_slh_dsa_name (alg_name = self .name , hash_alg = hash_alg )
684+ logging .info ("Sing data with: %s" , tmp_name )
685+ with oqs .Signature (tmp_name , secret_key = self ._private_key_bytes ) as _sig_method :
686+ sig = _sig_method .sign_with_ctx_str (data , ctx )
687+ except RuntimeError as exc :
688+ raise ValueError ("Could not sign the data with SLH-DSA" ) from exc
689+ return sig
690+
561691 def sign (
562692 self ,
563693 data : bytes ,
@@ -576,6 +706,14 @@ def sign(
576706 :raises ValueError: If the context is too long (255), or if the signature cannot be computed.
577707 """
578708 hash_alg = self .check_hash_alg (hash_alg = hash_alg )
709+
710+ if len (ctx ) > 255 :
711+ raise ValueError (f"The context length is longer than 255 bytes. Got: { len (ctx )} " )
712+
713+ if not is_prehashed and getattr (self , "_sig_method" , None ):
714+ logging .info ("Sign SLH-DSA signature with `liboqs`." )
715+ return self ._sign_with_oqs (data = data , ctx = ctx , hash_alg = hash_alg )
716+
579717 if hash_alg is None :
580718 sig = self ._slh_class .slh_sign (m = data , sk = self ._private_key_bytes , ctx = ctx )
581719
0 commit comments