diff --git a/allways/chain_providers/bitcoin.py b/allways/chain_providers/bitcoin.py index da9315bb..2da06281 100644 --- a/allways/chain_providers/bitcoin.py +++ b/allways/chain_providers/bitcoin.py @@ -400,9 +400,19 @@ def is_valid_address(self, address: str) -> bool: try: if address.lower().startswith(('bc1', 'tb1', 'bcrt1')): hrp, data = bech32.bech32_decode(address) - return data is not None + if data is None: + return False + if self.network == 'testnet': + return hrp == 'tb' + if self.network == 'regtest': + return hrp == 'bcrt' + return hrp == 'bc' decoded = base58.b58decode_check(address) - return len(decoded) == 21 and decoded[0] in (0x00, 0x05, 0x6F, 0xC4) + if len(decoded) != 21: + return False + if self.network in ('testnet', 'regtest'): + return decoded[0] in (0x6F, 0xC4) + return decoded[0] in (0x00, 0x05) except Exception: return False diff --git a/tests/test_bitcoin_signing.py b/tests/test_bitcoin_signing.py index e174f42e..d45aba98 100644 --- a/tests/test_bitcoin_signing.py +++ b/tests/test_bitcoin_signing.py @@ -112,6 +112,42 @@ def make_lightweight_provider() -> BitcoinProvider: return BitcoinProvider() +def make_lightweight_provider_for_network(network: str) -> BitcoinProvider: + with patch.dict( + os.environ, {'BTC_MODE': 'lightweight', 'BTC_NETWORK': network, 'BTC_PRIVATE_KEY': TEST_WIF}, clear=False + ): + return BitcoinProvider() + + +class TestBitcoinAddressValidationNetwork: + MAINNET_ADDRESSES = [ + 'bc1q6tvmnmetj8vfz98vuetpvtuplqtj4uvvwjgxxc', + '1LDsjB43N2NAQ1Vbc2xyHca4iBBciN8iwC', + '37XAVCtKEvPbx2rpkxx7FmrUsetFXSawx5', + ] + TESTNET_ADDRESSES = [ + 'tb1q6tvmnmetj8vfz98vuetpvtuplqtj4uvvy5n4at', + 'mzjq2E92B3oRB7yDKbwM7XnPaAnKfRERw2', + '2My5NYwpLrNtx9pVNS6Zysiqk616RJuHDuz', + ] + + def test_mainnet_accepts_mainnet_addresses(self): + provider = make_lightweight_provider_for_network('mainnet') + assert all(provider.is_valid_address(addr) for addr in self.MAINNET_ADDRESSES) + + def test_mainnet_rejects_testnet_addresses(self): + provider = make_lightweight_provider_for_network('mainnet') + assert all(not provider.is_valid_address(addr) for addr in self.TESTNET_ADDRESSES) + + def test_testnet_accepts_testnet_addresses(self): + provider = make_lightweight_provider_for_network('testnet') + assert all(provider.is_valid_address(addr) for addr in self.TESTNET_ADDRESSES) + + def test_testnet_rejects_mainnet_addresses(self): + provider = make_lightweight_provider_for_network('testnet') + assert all(not provider.is_valid_address(addr) for addr in self.MAINNET_ADDRESSES) + + class TestBitcoinProviderSignFromProof: """Direct coverage of BitcoinProvider.sign_from_proof — the wrapper our validator/CLI actually invoke, not the underlying library."""