Revision control

Copy as Markdown

Other Tools

"""
(C) 2026 Jack Lloyd
(C) 2026 René Meusel, Rohde & Schwarz Cybersecurity
Botan is released under the Simplified BSD License (see license.txt)
"""
import binascii
import unittest
import botan3 as botan
from .wycheproof import WycheproofTests
def _from_hex(value: str) -> bytes:
return binascii.unhexlify(value)
def _map_curve_name(wycheproof_curve: str) -> str:
"""Map Wycheproof curve name to Botan curve name."""
mapping = {
"brainpoolP224r1": "brainpool224r1",
"brainpoolP256r1": "brainpool256r1",
"brainpoolP320r1": "brainpool320r1",
"brainpoolP384r1": "brainpool384r1",
"brainpoolP512r1": "brainpool512r1",
}
return mapping.get(wycheproof_curve, wycheproof_curve)
def _map_hash_name(wycheproof_hash: str) -> str | None:
"""Map Wycheproof hash name to Botan hash name.
Returns None for unsupported hashes.
"""
mapping = {
"SHA-224": "SHA-224",
"SHA-256": "SHA-256",
"SHA-384": "SHA-384",
"SHA-512": "SHA-512",
"SHA3-224": "SHA-3(224)",
"SHA3-256": "SHA-3(256)",
"SHA3-384": "SHA-3(384)",
"SHA3-512": "SHA-3(512)",
"SHAKE128": "SHAKE-128(256)",
"SHAKE256": "SHAKE-256(512)",
}
return mapping.get(wycheproof_hash)
class TestECDSA(WycheproofTests, unittest.TestCase):
def input_files(self) -> list[str]:
return [
"ecdsa_brainpoolP224r1_sha224_p1363_test.json",
"ecdsa_brainpoolP224r1_sha224_test.json",
"ecdsa_brainpoolP224r1_sha3_224_test.json",
"ecdsa_brainpoolP256r1_sha256_p1363_test.json",
"ecdsa_brainpoolP256r1_sha256_test.json",
"ecdsa_brainpoolP256r1_sha3_256_test.json",
"ecdsa_brainpoolP320r1_sha384_p1363_test.json",
"ecdsa_brainpoolP320r1_sha384_test.json",
"ecdsa_brainpoolP320r1_sha3_384_test.json",
"ecdsa_brainpoolP384r1_sha384_p1363_test.json",
"ecdsa_brainpoolP384r1_sha384_test.json",
"ecdsa_brainpoolP384r1_sha3_384_test.json",
"ecdsa_brainpoolP512r1_sha3_512_test.json",
"ecdsa_brainpoolP512r1_sha512_p1363_test.json",
"ecdsa_brainpoolP512r1_sha512_test.json",
"ecdsa_secp160k1_sha256_p1363_test.json",
"ecdsa_secp160k1_sha256_test.json",
"ecdsa_secp160r1_sha256_p1363_test.json",
"ecdsa_secp160r1_sha256_test.json",
"ecdsa_secp160r2_sha256_p1363_test.json",
"ecdsa_secp160r2_sha256_test.json",
"ecdsa_secp192k1_sha256_p1363_test.json",
"ecdsa_secp192k1_sha256_test.json",
"ecdsa_secp192r1_sha256_p1363_test.json",
"ecdsa_secp192r1_sha256_test.json",
"ecdsa_secp224k1_sha224_p1363_test.json",
"ecdsa_secp224k1_sha224_test.json",
"ecdsa_secp224k1_sha256_p1363_test.json",
"ecdsa_secp224k1_sha256_test.json",
"ecdsa_secp224r1_sha224_p1363_test.json",
"ecdsa_secp224r1_sha224_test.json",
"ecdsa_secp224r1_sha256_p1363_test.json",
"ecdsa_secp224r1_sha256_test.json",
"ecdsa_secp224r1_sha3_224_test.json",
"ecdsa_secp224r1_sha3_256_test.json",
"ecdsa_secp224r1_sha3_512_test.json",
"ecdsa_secp224r1_sha512_p1363_test.json",
"ecdsa_secp224r1_sha512_test.json",
"ecdsa_secp224r1_shake128_p1363_test.json",
"ecdsa_secp224r1_shake128_test.json",
"ecdsa_secp256k1_sha256_bitcoin_test.json",
"ecdsa_secp256k1_sha256_p1363_test.json",
"ecdsa_secp256k1_sha256_test.json",
"ecdsa_secp256k1_sha3_256_test.json",
"ecdsa_secp256k1_sha3_512_test.json",
"ecdsa_secp256k1_sha512_p1363_test.json",
"ecdsa_secp256k1_sha512_test.json",
"ecdsa_secp256k1_shake128_p1363_test.json",
"ecdsa_secp256k1_shake128_test.json",
"ecdsa_secp256k1_shake256_p1363_test.json",
"ecdsa_secp256k1_shake256_test.json",
"ecdsa_secp256r1_sha256_p1363_test.json",
"ecdsa_secp256r1_sha256_test.json",
"ecdsa_secp256r1_sha3_256_test.json",
"ecdsa_secp256r1_sha3_512_test.json",
"ecdsa_secp256r1_sha512_p1363_test.json",
"ecdsa_secp256r1_sha512_test.json",
"ecdsa_secp256r1_shake128_p1363_test.json",
"ecdsa_secp256r1_shake128_test.json",
"ecdsa_secp384r1_sha256_test.json",
"ecdsa_secp384r1_sha384_p1363_test.json",
"ecdsa_secp384r1_sha384_test.json",
"ecdsa_secp384r1_sha3_384_test.json",
"ecdsa_secp384r1_sha3_512_test.json",
"ecdsa_secp384r1_sha512_p1363_test.json",
"ecdsa_secp384r1_sha512_test.json",
"ecdsa_secp384r1_shake256_p1363_test.json",
"ecdsa_secp384r1_shake256_test.json",
"ecdsa_secp521r1_sha3_512_test.json",
"ecdsa_secp521r1_sha512_p1363_test.json",
"ecdsa_secp521r1_sha512_test.json",
"ecdsa_secp521r1_shake256_p1363_test.json",
"ecdsa_secp521r1_shake256_test.json",
]
def run_test(self, data: dict, group: dict, test: dict) -> None:
botan_hash = _map_hash_name(group["sha"])
curve = _map_curve_name(group["publicKey"]["curve"])
if not botan.ECGroup.supports_named_group(curve):
self.skipTest(f"Curve {curve} not supported in this build")
# Load public key from DER
try:
pub_key = botan.PublicKey.load(_from_hex(group["publicKeyDer"]))
except botan.BotanException:
if test["result"] != "valid":
return
raise
# Determine signature format from group type
group_type = group["type"]
if group_type == "EcdsaBitcoinVerify":
# Botan currently does not prevent signature malleability via
# low-s normalization.
self.skipTest("Bitcoin variant of ECDSA is not supported")
if group_type in ("EcdsaVerify", "EcdsaBitcoinVerify"):
use_der = True
elif group_type == "EcdsaP1363Verify":
use_der = False
else:
self.fail(f"Unknown test group type: {group_type}")
# Perform verification
try:
verifier = botan.PKVerify(pub_key, botan_hash, der=use_der)
verifier.update(_from_hex(test["msg"]))
valid = verifier.check_signature(_from_hex(test["sig"]))
except botan.BotanException:
if test["result"] != "valid":
return
raise
# Validate the verification result
self.assertEqual(
valid,
test["result"] == "valid",
"Signature should be valid"
if test["result"] == "valid"
else "Signature should be invalid",
)