Summary
BitcoinProvider.is_valid_address rejects every valid Taproot (P2TR) Bitcoin address. As a result, any TAO→BTC swap whose BTC receiving address is a Taproot address (bc1p… on mainnet, tb1p…/bcrt1p… on test networks) is rejected 100% of the time — both in the CLI before sending and in the validator at confirm time — with a misleading "Invalid destination address format" error.
Taproot is the default receive-address type in many modern wallets and exchanges, so this blocks a large and growing class of legitimate swaps.
Root cause
allways/chain_providers/bitcoin.py (is_valid_address):
def is_valid_address(self, address: str) -> bool:
"""Validate BTC address format without RPC (bech32/base58 decode)."""
if not address or not isinstance(address, str):
return False
try:
if address.lower().startswith(('bc1', 'tb1', 'bcrt1')):
hrp, data = bech32.bech32_decode(address)
return data is not None
decoded = base58.b58decode_check(address)
return len(decoded) == 21 and decoded[0] in (0x00, 0x05, 0x6F, 0xC4)
except Exception:
return False
bech32.bech32_decode validates only the BIP-173 bech32 checksum (polymod constant 1). Taproot addresses (witness v1) are encoded with bech32m (BIP-350, polymod constant 0x2bc830a3), so bech32_decode returns (None, None) for them and the function returns False.
Reproduction
Run against the pinned bech32 dependency:
import bech32, base58
def is_valid_address(address):
if not address or not isinstance(address, str):
return False
try:
if address.lower().startswith(('bc1', 'tb1', 'bcrt1')):
hrp, data = bech32.bech32_decode(address)
return data is not None
decoded = base58.b58decode_check(address)
return len(decoded) == 21 and decoded[0] in (0x00, 0x05, 0x6F, 0xC4)
except Exception:
return False
cases = {
'P2WPKH v0 (bc1q)': 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4',
'P2TR v1 (bc1p)': 'bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr',
'P2WSH v0 (bc1q)': 'bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3',
'P2PKH (1...)': '1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2',
'P2SH (3...)': '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy',
}
for name, addr in cases.items():
print(f'{name:18} -> {is_valid_address(addr)}')
Output:
P2WPKH v0 (bc1q) -> True
P2TR v1 (bc1p) -> False <-- valid mainnet Taproot, rejected
P2WSH v0 (bc1q) -> True
P2PKH (1...) -> True
P2SH (3...) -> True
bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr is a valid mainnet Taproot address, yet it is rejected.
Impact
For a TAO→BTC swap the BTC address is a pure payout destination — the user only receives funds there, so no message signing is involved. Every other stage of the pipeline already supports Taproot:
- Miner payout derives a valid P2TR scriptPubKey via embit's
address_to_scriptpubkey.
- Validator dest-tx verification matches on Esplora's
scriptpubkey_address, which is the Taproot address.
The only thing that fails is the format gate:
allways/validator/axon_handlers.py (handle_swap_confirm):
if not to_provider.is_valid_address(synapse.to_address):
reject_synapse(synapse, 'Invalid destination address format', ctx)
return synapse
allways/cli/swap_commands/swap.py:
if not to_provider.is_valid_address(receive_address):
...
So a user swapping TAO→BTC into a Taproot wallet is blocked deterministically, every time, with a misleading "invalid format" message for an address that is valid and fully payable/verifiable by the rest of the system.
Note: the "Taproot not yet supported" comments elsewhere in bitcoin.py refer to BIP-137 message signing, which only applies to a BTC source address (BTC→TAO). They do not apply to a BTC payout destination, so this rejection is over-broad.
Expected behavior
is_valid_address should accept valid Taproot addresses (witness v1+, bech32m), consistent with the miner send path and the validator verification path that already handle them.
Suggested fix
Make is_valid_address witness-version-aware: validate witness v0 with bech32 and witness v1+ with bech32m (BIP-350). The currently pinned bech32 package does not implement bech32m, so either add a bech32m checksum check (polymod constant 0x2bc830a3) for v1+ programs or switch to a library that implements BIP-350.
Environment
- File:
allways/chain_providers/bitcoin.py
- Affected flows: TAO→BTC swaps with a Taproot BTC receiving address (CLI
swap and validator handle_swap_confirm).
- Reproducible: every time.
Summary
BitcoinProvider.is_valid_addressrejects every valid Taproot (P2TR) Bitcoin address. As a result, any TAO→BTC swap whose BTC receiving address is a Taproot address (bc1p…on mainnet,tb1p…/bcrt1p…on test networks) is rejected 100% of the time — both in the CLI before sending and in the validator at confirm time — with a misleading "Invalid destination address format" error.Taproot is the default receive-address type in many modern wallets and exchanges, so this blocks a large and growing class of legitimate swaps.
Root cause
allways/chain_providers/bitcoin.py(is_valid_address):bech32.bech32_decodevalidates only the BIP-173 bech32 checksum (polymod constant1). Taproot addresses (witness v1) are encoded with bech32m (BIP-350, polymod constant0x2bc830a3), sobech32_decodereturns(None, None)for them and the function returnsFalse.Reproduction
Run against the pinned
bech32dependency:Output:
bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcris a valid mainnet Taproot address, yet it is rejected.Impact
For a TAO→BTC swap the BTC address is a pure payout destination — the user only receives funds there, so no message signing is involved. Every other stage of the pipeline already supports Taproot:
address_to_scriptpubkey.scriptpubkey_address, which is the Taproot address.The only thing that fails is the format gate:
allways/validator/axon_handlers.py(handle_swap_confirm):allways/cli/swap_commands/swap.py:So a user swapping TAO→BTC into a Taproot wallet is blocked deterministically, every time, with a misleading "invalid format" message for an address that is valid and fully payable/verifiable by the rest of the system.
Note: the "Taproot not yet supported" comments elsewhere in
bitcoin.pyrefer to BIP-137 message signing, which only applies to a BTC source address (BTC→TAO). They do not apply to a BTC payout destination, so this rejection is over-broad.Expected behavior
is_valid_addressshould accept valid Taproot addresses (witness v1+, bech32m), consistent with the miner send path and the validator verification path that already handle them.Suggested fix
Make
is_valid_addresswitness-version-aware: validate witness v0 with bech32 and witness v1+ with bech32m (BIP-350). The currently pinnedbech32package does not implement bech32m, so either add a bech32m checksum check (polymod constant0x2bc830a3) for v1+ programs or switch to a library that implements BIP-350.Environment
allways/chain_providers/bitcoin.pyswapand validatorhandle_swap_confirm).