"""Tests for muse.core.secp256k1_sign — BIP32 secp256k1 derivation, EIP-191 signing, AVAX addresses. All eight categories: 1. Unit — BIP32 path parsing, master key, child derivation, keccak256, eip55_checksum 2. Integration — derive_avax_key → sign → verify full pipeline 3. E2E — known-answer vectors (MetaMask-compatible address, cross-verified signatures) 4. Stress — 200 sign/verify cycles, 50-level deep derivation, high-index paths 5. Data integrity — determinism, account/index isolation, v ∈ {27, 28}, signature is 65 bytes 6. Performance — key derivation and sign/verify within time budgets 7. Security — bad sig rejected, wrong addr rejected, bad v rejected, truncated sig, malformed path 8. Docstrings — all public symbols have docstrings; module docstring present """ from __future__ import annotations import hashlib import time import types from typing import TYPE_CHECKING import pytest if TYPE_CHECKING: from eth_keys.keys import PrivateKey as _EthKey # --------------------------------------------------------------------------- # Constants — fixed test mnemonic (all-abandon, never used in production) # --------------------------------------------------------------------------- _MNEMONIC = ( "abandon abandon abandon abandon abandon abandon abandon abandon " "abandon abandon abandon about" ) #: Known-good AVAX C-Chain address for _MNEMONIC, account=0, index=0. #: Cross-verified against MetaMask and standard BIP44 tooling. _KNOWN_ADDRESS_LOWER = "0x9858effd232b4033e47d90003d41ec34ecaeda94" # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture(scope="module") def bip39_seed() -> bytes: """64-byte BIP39 seed derived from the all-abandon test mnemonic.""" from muse.core.bip39 import mnemonic_to_seed return mnemonic_to_seed(_MNEMONIC) @pytest.fixture(scope="module") def default_key(bip39_seed: bytes) -> "_EthKey": """eth_keys.PrivateKey at m/44'/60'/0'/0/0 from the test mnemonic.""" from muse.core.secp256k1_sign import derive_avax_key return derive_avax_key(bip39_seed, account=0, index=0) # type: ignore[return-value] @pytest.fixture(scope="module") def default_address(default_key: "_EthKey") -> str: """EIP-55 address for the default test key.""" from muse.core.secp256k1_sign import avax_c_chain_address return avax_c_chain_address(default_key.public_key) # --------------------------------------------------------------------------- # 1. Unit — low-level building blocks # --------------------------------------------------------------------------- class TestBip32PathParsing: """Unit tests for ``_parse_path``.""" def test_simple_path_parsed(self) -> None: from muse.core.secp256k1_sign import _parse_path result = _parse_path("m/44'/60'/0'/0/0") assert result == [44 | 0x80000000, 60 | 0x80000000, 0x80000000, 0, 0] def test_master_only_returns_empty(self) -> None: from muse.core.secp256k1_sign import _parse_path assert _parse_path("m") == [] assert _parse_path("m/") == [] def test_unhardened_indices(self) -> None: from muse.core.secp256k1_sign import _parse_path assert _parse_path("m/0/1/2") == [0, 1, 2] def test_hardened_indices(self) -> None: from muse.core.secp256k1_sign import _parse_path H = 0x80000000 assert _parse_path("m/44'/60'/1'") == [44 | H, 60 | H, 1 | H] def test_path_must_start_with_m(self) -> None: from muse.core.secp256k1_sign import Bip32Error, _parse_path with pytest.raises(Bip32Error, match="must start with 'm'"): _parse_path("44'/60'/0'/0/0") def test_non_integer_component_raises(self) -> None: from muse.core.secp256k1_sign import Bip32Error, _parse_path with pytest.raises(Bip32Error, match="Invalid BIP32 path component"): _parse_path("m/44'/abc/0") def test_negative_index_raises(self) -> None: from muse.core.secp256k1_sign import Bip32Error, _parse_path with pytest.raises(Bip32Error, match="Negative"): _parse_path("m/-1") def test_high_index_values(self) -> None: from muse.core.secp256k1_sign import _parse_path result = _parse_path("m/2147483647'/2147483647") assert result == [0xFFFFFFFF, 2147483647] class TestBip32MasterKey: """Unit tests for ``bip32_master_key``.""" def test_master_key_returns_node(self, bip39_seed: bytes) -> None: from muse.core.secp256k1_sign import _Bip32Node, bip32_master_key node = bip32_master_key(bip39_seed) assert isinstance(node, _Bip32Node) def test_master_private_int_in_range(self, bip39_seed: bytes) -> None: from muse.core.secp256k1_sign import _SECP256K1_N, bip32_master_key node = bip32_master_key(bip39_seed) assert 0 < node.private_int < _SECP256K1_N def test_chain_code_is_32_bytes(self, bip39_seed: bytes) -> None: from muse.core.secp256k1_sign import bip32_master_key node = bip32_master_key(bip39_seed) assert len(node.chain_code) == 32 def test_deterministic(self, bip39_seed: bytes) -> None: from muse.core.secp256k1_sign import bip32_master_key a = bip32_master_key(bip39_seed) b = bip32_master_key(bip39_seed) assert a.private_int == b.private_int assert a.chain_code == b.chain_code def test_different_seeds_different_keys(self, bip39_seed: bytes) -> None: from muse.core.secp256k1_sign import bip32_master_key other_seed = bytes(64) try: other = bip32_master_key(other_seed) node = bip32_master_key(bip39_seed) assert node.private_int != other.private_int except Exception: pass # all-zero seed may be invalid — that's fine class TestKeccak256: """Unit tests for ``keccak256``.""" def test_empty_string_known_vector(self) -> None: from muse.core.secp256k1_sign import keccak256 # keccak256("") is a known constant result = keccak256(b"") assert result.hex() == "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" def test_returns_32_bytes(self) -> None: from muse.core.secp256k1_sign import keccak256 assert len(keccak256(b"anything")) == 32 def test_is_not_sha3_256(self) -> None: """keccak256 and FIPS SHA3-256 produce different outputs for the same input.""" import hashlib from muse.core.secp256k1_sign import keccak256 sha3 = hashlib.sha3_256(b"").digest() keccak = keccak256(b"") assert sha3 != keccak class TestEip55Checksum: """Unit tests for ``eip55_checksum``.""" def test_known_vector(self) -> None: from muse.core.secp256k1_sign import eip55_checksum result = eip55_checksum("de0b295669a9fd93d5f28d9ec85e40f4cb697bae") assert result == "0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe" def test_adds_0x_prefix(self) -> None: from muse.core.secp256k1_sign import eip55_checksum result = eip55_checksum("de0b295669a9fd93d5f28d9ec85e40f4cb697bae") assert result.startswith("0x") def test_idempotent_on_lowercase_bytes(self) -> None: """eip55_checksum on already-lowercased input is deterministic.""" from muse.core.secp256k1_sign import eip55_checksum addr = "9858effd232b4033e47d90003d41ec34ecaeda94" assert eip55_checksum(addr) == eip55_checksum(addr) class TestEip191Message: """Unit tests for ``eip191_message``.""" def test_prefix_present(self) -> None: from muse.core.secp256k1_sign import eip191_message result = eip191_message("Hello") assert result.startswith(b"\x19Ethereum Signed Message:\n5Hello") def test_length_in_prefix(self) -> None: from muse.core.secp256k1_sign import eip191_message msg = "Hi" result = eip191_message(msg) assert b"2" in result # len("Hi") = 2 def test_bytes_input(self) -> None: from muse.core.secp256k1_sign import eip191_message a = eip191_message("test") b = eip191_message(b"test") assert a == b def test_empty_message(self) -> None: from muse.core.secp256k1_sign import eip191_message result = eip191_message("") assert result == b"\x19Ethereum Signed Message:\n0" def test_multibyte_utf8_length_is_byte_count(self) -> None: """Length prefix counts bytes, not Unicode code points.""" from muse.core.secp256k1_sign import eip191_message msg = "\u00e9" # é — 2 bytes in UTF-8 result = eip191_message(msg) assert b"\n2" in result # --------------------------------------------------------------------------- # 2. Integration — full pipeline # --------------------------------------------------------------------------- class TestSignVerifyPipeline: """Integration tests: derive_avax_key → eip191_sign → eip191_verify.""" def test_sign_returns_65_bytes(self, default_key: "_EthKey", default_address: str) -> None: from muse.core.secp256k1_sign import eip191_sign sig = eip191_sign(default_key, "test message") assert len(sig) == 65 def test_v_is_27_or_28(self, default_key: "_EthKey") -> None: from muse.core.secp256k1_sign import eip191_sign sig = eip191_sign(default_key, "test") assert sig[64] in (27, 28) def test_verify_correct_roundtrip(self, default_key: "_EthKey", default_address: str) -> None: from muse.core.secp256k1_sign import eip191_sign, eip191_verify msg = "MPAY2\ngabriel\nalice\n0.001\nAVAX\nnonce\n1234" sig = eip191_sign(default_key, msg) assert eip191_verify(sig, msg, default_address) def test_verify_bytes_message(self, default_key: "_EthKey", default_address: str) -> None: from muse.core.secp256k1_sign import eip191_sign, eip191_verify msg = b"raw bytes payload" sig = eip191_sign(default_key, msg) assert eip191_verify(sig, msg, default_address) def test_verify_rejects_wrong_message(self, default_key: "_EthKey", default_address: str) -> None: from muse.core.secp256k1_sign import eip191_sign, eip191_verify sig = eip191_sign(default_key, "original") assert not eip191_verify(sig, "tampered", default_address) def test_verify_rejects_wrong_address(self, default_key: "_EthKey", default_address: str) -> None: from muse.core.secp256k1_sign import eip191_sign, eip191_verify sig = eip191_sign(default_key, "msg") wrong_addr = f"0x{'a' * 40}" assert not eip191_verify(sig, "msg", wrong_addr) def test_different_keys_different_addresses(self, bip39_seed: bytes) -> None: from muse.core.secp256k1_sign import avax_c_chain_address, derive_avax_key key0 = derive_avax_key(bip39_seed, account=0) key1 = derive_avax_key(bip39_seed, account=1) assert avax_c_chain_address(key0.public_key) != avax_c_chain_address(key1.public_key) # --------------------------------------------------------------------------- # 3. E2E — known-answer vectors # --------------------------------------------------------------------------- class TestKnownAnswerVectors: """End-to-end tests using externally-verified reference values.""" def test_address_matches_metamask_all_abandon(self, default_address: str) -> None: """MetaMask and standard BIP44 tooling produce this address for the all-abandon mnemonic.""" assert default_address.lower() == _KNOWN_ADDRESS_LOWER def test_keccak256_known_vectors(self) -> None: """Cross-verify keccak256 against known Ethereum constants.""" from muse.core.secp256k1_sign import keccak256 # keccak256("") — used in Ethereum's trie assert keccak256(b"").hex() == "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" # keccak256(b"\x00" * 32) — well-known test vector result = keccak256(b"\x00" * 32) assert len(result) == 32 def test_eip55_known_vectors(self) -> None: """EIP-55 checksum is deterministic across multiple known addresses.""" from muse.core.secp256k1_sign import eip55_checksum # All-caps address — every nibble in hash >= 8 assert eip55_checksum("52908400098527886e0f7030069857d2e4169ee7") == "0x52908400098527886E0F7030069857D2E4169EE7" # Mixed checksum address assert eip55_checksum("de0b295669a9fd93d5f28d9ec85e40f4cb697bae") == "0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe" def test_signature_verify_across_accounts(self, bip39_seed: bytes) -> None: """Signatures from account N only verify against account N's address.""" from muse.core.secp256k1_sign import avax_c_chain_address, derive_avax_key, eip191_sign, eip191_verify for acct in range(3): key = derive_avax_key(bip39_seed, account=acct) addr = avax_c_chain_address(key.public_key) sig = eip191_sign(key, "cross-account test") assert eip191_verify(sig, "cross-account test", addr) # Signature from account N must not verify against account N+1 if acct > 0: other = derive_avax_key(bip39_seed, account=acct - 1) other_addr = avax_c_chain_address(other.public_key) assert not eip191_verify(sig, "cross-account test", other_addr) def test_bip44_path_depth_correct(self, bip39_seed: bytes) -> None: """Derivation uses exactly 5 levels: m/44'/60'/account'/0/index.""" from muse.core.secp256k1_sign import _HARDENED, _parse_path path = f"m/44'/{60}'/{0}'/0/{0}" parsed = _parse_path(path) assert len(parsed) == 5 assert parsed[0] == 44 | _HARDENED # purpose assert parsed[1] == 60 | _HARDENED # coin type assert parsed[2] == 0 | _HARDENED # account (hardened) assert parsed[3] == 0 # change (unhardened) assert parsed[4] == 0 # index (unhardened) # --------------------------------------------------------------------------- # 4. Stress — volume and depth # --------------------------------------------------------------------------- @pytest.mark.slow class TestStress: """Stress tests: high volume sign/verify cycles, deep derivation paths.""" def test_200_sign_verify_cycles(self, default_key: "_EthKey", default_address: str) -> None: from muse.core.secp256k1_sign import eip191_sign, eip191_verify for i in range(200): msg = f"stress message {i}" sig = eip191_sign(default_key, msg) assert eip191_verify(sig, msg, default_address), f"failed at iteration {i}" def test_50_accounts_all_unique_addresses(self, bip39_seed: bytes) -> None: from muse.core.secp256k1_sign import avax_c_chain_address, derive_avax_key addresses = { avax_c_chain_address(derive_avax_key(bip39_seed, account=i).public_key) for i in range(50) } assert len(addresses) == 50 def test_50_indices_all_unique_addresses(self, bip39_seed: bytes) -> None: from muse.core.secp256k1_sign import avax_c_chain_address, derive_avax_key addresses = { avax_c_chain_address(derive_avax_key(bip39_seed, account=0, index=i).public_key) for i in range(50) } assert len(addresses) == 50 def test_high_index_derivation(self, bip39_seed: bytes) -> None: """Derivation at high index values does not crash.""" from muse.core.secp256k1_sign import avax_c_chain_address, derive_avax_key key = derive_avax_key(bip39_seed, account=0, index=100_000) addr = avax_c_chain_address(key.public_key) assert addr.startswith("0x") and len(addr) == 42 def test_many_unique_messages_all_verify(self, default_key: "_EthKey", default_address: str) -> None: """100 distinct messages each produce a verifiable, distinct signature.""" from muse.core.secp256k1_sign import eip191_sign, eip191_verify signatures: set[bytes] = set() for i in range(100): msg = f"unique payload {i}" sig = eip191_sign(default_key, msg) assert eip191_verify(sig, msg, default_address) signatures.add(sig) # ECDSA is deterministic (RFC 6979) — same key + same msg = same sig. # 100 unique messages should produce 100 unique signatures. assert len(signatures) == 100 # --------------------------------------------------------------------------- # 5. Data integrity — determinism and isolation # --------------------------------------------------------------------------- class TestDataIntegrity: """Data integrity: determinism, isolation, canonical encoding.""" def test_signing_is_deterministic(self, default_key: "_EthKey", default_address: str) -> None: """RFC 6979 deterministic ECDSA: same key + same message = same signature.""" from muse.core.secp256k1_sign import eip191_sign msg = "deterministic test" sig1 = eip191_sign(default_key, msg) sig2 = eip191_sign(default_key, msg) assert sig1 == sig2 def test_address_derivation_deterministic(self, bip39_seed: bytes) -> None: from muse.core.secp256k1_sign import avax_c_chain_address, derive_avax_key addr1 = avax_c_chain_address(derive_avax_key(bip39_seed).public_key) addr2 = avax_c_chain_address(derive_avax_key(bip39_seed).public_key) assert addr1 == addr2 def test_account_isolation(self, bip39_seed: bytes) -> None: """Keys at different accounts are independent — signature from acct 0 fails on acct 1.""" from muse.core.secp256k1_sign import avax_c_chain_address, derive_avax_key, eip191_sign, eip191_verify key0 = derive_avax_key(bip39_seed, account=0) key1 = derive_avax_key(bip39_seed, account=1) addr1 = avax_c_chain_address(key1.public_key) sig = eip191_sign(key0, "isolation test") assert not eip191_verify(sig, "isolation test", addr1) def test_index_isolation(self, bip39_seed: bytes) -> None: """Keys at different indices within the same account are independent.""" from muse.core.secp256k1_sign import avax_c_chain_address, derive_avax_key, eip191_sign, eip191_verify key0 = derive_avax_key(bip39_seed, account=0, index=0) key1 = derive_avax_key(bip39_seed, account=0, index=1) addr1 = avax_c_chain_address(key1.public_key) sig = eip191_sign(key0, "index isolation") assert not eip191_verify(sig, "index isolation", addr1) def test_address_is_42_chars_0x_prefix(self, default_address: str) -> None: assert default_address.startswith("0x") assert len(default_address) == 42 def test_address_is_hex(self, default_address: str) -> None: int(default_address[2:], 16) # raises ValueError if not hex def test_signature_r_s_v_encoding(self, default_key: "_EthKey") -> None: """Signature byte layout: r(32) || s(32) || v(1).""" from muse.core.secp256k1_sign import eip191_sign sig = eip191_sign(default_key, "layout test") assert len(sig) == 65 r = int.from_bytes(sig[:32], "big") s = int.from_bytes(sig[32:64], "big") v = sig[64] assert r > 0 assert s > 0 assert v in (27, 28) def test_eip191_prefix_cannot_be_raw_tx(self) -> None: """The 0x19 prefix ensures the message is not a valid RLP transaction prefix.""" from muse.core.secp256k1_sign import eip191_message result = eip191_message("anything") assert result[0] == 0x19 def test_bip32_master_chain_code_is_entropy(self, bip39_seed: bytes) -> None: """Chain code differs from private key bytes — not a trivial copy.""" from muse.core.secp256k1_sign import bip32_master_key node = bip32_master_key(bip39_seed) assert node.chain_code != node.private_int.to_bytes(32, "big") # --------------------------------------------------------------------------- # 6. Performance — time budgets # --------------------------------------------------------------------------- @pytest.mark.perf class TestPerformance: """Performance: derivation and signing within acceptable time budgets.""" def test_key_derivation_under_200ms(self, bip39_seed: bytes) -> None: """BIP44 key derivation (5 levels) completes in < 200 ms.""" from muse.core.secp256k1_sign import derive_avax_key start = time.perf_counter() derive_avax_key(bip39_seed, account=0, index=0) elapsed = time.perf_counter() - start assert elapsed < 0.2, f"key derivation took {elapsed:.3f}s (budget: 200ms)" def test_sign_under_100ms(self, default_key: "_EthKey") -> None: """EIP-191 signing completes in < 100 ms.""" from muse.core.secp256k1_sign import eip191_sign start = time.perf_counter() eip191_sign(default_key, "perf test message") elapsed = time.perf_counter() - start assert elapsed < 0.1, f"signing took {elapsed:.3f}s (budget: 100ms)" def test_verify_under_100ms(self, default_key: "_EthKey", default_address: str) -> None: """EIP-191 verification (with recovery) completes in < 100 ms.""" from muse.core.secp256k1_sign import eip191_sign, eip191_verify sig = eip191_sign(default_key, "perf verify") start = time.perf_counter() eip191_verify(sig, "perf verify", default_address) elapsed = time.perf_counter() - start assert elapsed < 0.1, f"verify took {elapsed:.3f}s (budget: 100ms)" def test_10_sign_verify_cycles_under_1s(self, default_key: "_EthKey", default_address: str) -> None: """10 complete sign+verify cycles under 1 second.""" from muse.core.secp256k1_sign import eip191_sign, eip191_verify start = time.perf_counter() for i in range(10): sig = eip191_sign(default_key, f"throughput {i}") eip191_verify(sig, f"throughput {i}", default_address) elapsed = time.perf_counter() - start assert elapsed < 1.0, f"10 cycles took {elapsed:.3f}s (budget: 1s)" def test_keccak256_throughput(self) -> None: """keccak256 handles 10 000 calls in < 1 second.""" from muse.core.secp256k1_sign import keccak256 payload = b"benchmark" * 10 start = time.perf_counter() for _ in range(10_000): keccak256(payload) elapsed = time.perf_counter() - start assert elapsed < 1.0, f"10k keccak256 took {elapsed:.3f}s" # --------------------------------------------------------------------------- # 7. Security — rejection of bad inputs # --------------------------------------------------------------------------- class TestSecurity: """Security: all invalid inputs are rejected cleanly; no information leakage.""" def test_verify_rejects_truncated_signature(self, default_address: str) -> None: from muse.core.secp256k1_sign import eip191_verify assert not eip191_verify(b"\x00" * 64, "msg", default_address) def test_verify_rejects_empty_signature(self, default_address: str) -> None: from muse.core.secp256k1_sign import eip191_verify assert not eip191_verify(b"", "msg", default_address) def test_verify_rejects_all_zero_signature(self, default_address: str) -> None: from muse.core.secp256k1_sign import eip191_verify assert not eip191_verify(b"\x00" * 65, "msg", default_address) def test_verify_rejects_bad_v_byte(self, default_key: "_EthKey", default_address: str) -> None: """v must be 27 or 28; other values are rejected immediately.""" from muse.core.secp256k1_sign import eip191_sign, eip191_verify sig = eip191_sign(default_key, "bad v test") for bad_v in (0, 1, 26, 29, 255): bad_sig = sig[:64] + bytes([bad_v]) assert not eip191_verify(bad_sig, "bad v test", default_address), f"v={bad_v} should be rejected" def test_verify_rejects_bit_flipped_signature(self, default_key: "_EthKey", default_address: str) -> None: """A single bit flip in r or s invalidates the signature.""" from muse.core.secp256k1_sign import eip191_sign, eip191_verify sig = bytearray(eip191_sign(default_key, "bit flip test")) sig[0] ^= 0x01 # flip one bit in r assert not eip191_verify(bytes(sig), "bit flip test", default_address) def test_verify_rejects_bit_flipped_s(self, default_key: "_EthKey", default_address: str) -> None: from muse.core.secp256k1_sign import eip191_sign, eip191_verify sig = bytearray(eip191_sign(default_key, "flip s")) sig[32] ^= 0x01 # flip one bit in s assert not eip191_verify(bytes(sig), "flip s", default_address) def test_malformed_bip32_path_raises(self) -> None: from muse.core.secp256k1_sign import Bip32Error, bip32_derive_path seed = bytes(64) with pytest.raises(Bip32Error): bip32_derive_path(seed, "not/a/path") def test_address_case_insensitive_comparison(self, default_key: "_EthKey", default_address: str) -> None: """eip191_verify accepts addresses in any case.""" from muse.core.secp256k1_sign import eip191_sign, eip191_verify sig = eip191_sign(default_key, "case test") assert eip191_verify(sig, "case test", default_address.upper()) assert eip191_verify(sig, "case test", default_address.lower()) def test_eip191_message_not_raw_private_key(self, default_key: "_EthKey") -> None: """The private key scalar must not appear in the signed message.""" from muse.core.secp256k1_sign import eip191_message private_bytes = default_key.to_bytes() msg = eip191_message("safe message") assert private_bytes not in msg def test_different_seeds_produce_different_master_keys(self) -> None: from muse.core.secp256k1_sign import bip32_master_key seed1 = bytes(range(64)) seed2 = bytes(range(1, 65)) node1 = bip32_master_key(seed1) node2 = bip32_master_key(seed2) assert node1.private_int != node2.private_int assert node1.chain_code != node2.chain_code def test_no_custom_ec_math_in_module(self) -> None: """Guard: no hand-written point_mul or point_add in the module.""" import inspect import muse.core.secp256k1_sign as mod src = inspect.getsource(mod) assert "_point_mul" not in src, "hand-written EC point multiplication found" assert "_point_add" not in src, "hand-written EC point addition found" assert "_recover_public_key" not in src, "hand-written key recovery found" def test_signing_uses_eth_keys_not_custom_ecdsa(self) -> None: """Guard: eip191_sign delegates to eth_keys, not a custom ECDSA implementation.""" import inspect from muse.core.secp256k1_sign import eip191_sign src = inspect.getsource(eip191_sign) assert "sign_msg_hash" in src, "eth_keys sign_msg_hash not called" # --------------------------------------------------------------------------- # 8. Docstrings — all public symbols documented # --------------------------------------------------------------------------- class TestDocstrings: """Ensure all public symbols carry docstrings.""" def _public_functions(self) -> list[tuple[str, types.FunctionType]]: import inspect import muse.core.secp256k1_sign as mod return [ (name, obj) for name, obj in inspect.getmembers(mod, inspect.isfunction) if not name.startswith("_") ] def test_module_docstring(self) -> None: import muse.core.secp256k1_sign as mod assert mod.__doc__ and len(mod.__doc__.strip()) > 20 def test_all_public_functions_have_docstrings(self) -> None: for name, fn in self._public_functions(): assert fn.__doc__ and len(fn.__doc__.strip()) > 10, ( f"muse.core.secp256k1_sign.{name} missing docstring" ) def test_bip32_error_has_docstring(self) -> None: from muse.core.secp256k1_sign import Bip32Error assert Bip32Error.__doc__ def test_docstring_mentions_eth_keys(self) -> None: import muse.core.secp256k1_sign as mod assert "eth_keys" in (mod.__doc__ or "") def test_docstring_mentions_keccak(self) -> None: import muse.core.secp256k1_sign as mod assert "keccak" in (mod.__doc__ or "").lower() def test_derive_avax_key_documents_path(self) -> None: from muse.core.secp256k1_sign import derive_avax_key assert "44'" in (derive_avax_key.__doc__ or "") assert "60'" in (derive_avax_key.__doc__ or "") def test_eip191_sign_documents_v_range(self) -> None: from muse.core.secp256k1_sign import eip191_sign doc = eip191_sign.__doc__ or "" assert "27" in doc and "28" in doc def test_eip191_verify_documents_return_type(self) -> None: from muse.core.secp256k1_sign import eip191_verify doc = eip191_verify.__doc__ or "" assert "True" in doc or "bool" in doc.lower()