Skip to content

Accept Taproot (bech32m) BTC addresses in address validation#453

Merged
entrius merged 2 commits into
testfrom
fix/taproot-valid-address-448
Jun 8, 2026
Merged

Accept Taproot (bech32m) BTC addresses in address validation#453
entrius merged 2 commits into
testfrom
fix/taproot-valid-address-448

Conversation

@anderdc

@anderdc anderdc commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Fixes #448.

Problem

BitcoinProvider.is_valid_address validated with the bech32 package, which implements only BIP-173 bech32 (witness v0). Every Taproot address (bc1p… / tb1p… / bcrt1p…, witness v1, encoded with bech32m / BIP-350) fails that checksum, so TAO→BTC swaps paying out to a Taproot wallet were rejected 100% of the time — at the CLI pre-check (swap.py) and the validator confirm (axon_handlers.py) — with a misleading "Invalid destination address format". Taproot is a growing share of modern wallet receive addresses, so this blocked a real and growing class of legitimate swaps.

The rest of the pipeline already supports Taproot (payout build via embit's address_to_scriptpubkey; dest-verification string-matches Esplora's scriptpubkey_address) — only the format gate was in the way.

Fix

Route is_valid_address and to_mainnet_address through embit, which is already a direct dependency and already used in this same file for the send path. embit validates/encodes all address types (legacy, segwit v0, Taproot) offline — no RPC, so:

  • is_valid_address now accepts the full mainnet/testnet/regtest × P2PKH/P2SH/P2WPKH/P2WSH/P2TR matrix and still rejects malformed input (it's marginally stricter — it also validates witness-program length).
  • to_mainnet_address re-encodes the scriptPubKey under the mainnet network. Verified byte-identical to the prior bech32/base58 output for every type that reaches it (testnet/regtest P2PKH/P2SH/P2WPKH + mainnet passthrough).
  • The direct bech32 dependency is dropped from pyproject.toml. It remains in uv.lock transitively via bitcoin-message-tool, so nothing else breaks. base58 stays — still used by to_mainnet_wif.

Scope

Targeted to the TAO→BTC payout-to-Taproot flow. Explicitly out of scope (separate, larger work; no change here):

  • Taproot send support (miner spending from a P2TR address — needs Schnorr/BIP-341 signing).
  • BIP-137 Taproot message-signing for BTC→TAO source proofs (needs BIP-322).

Tests

Added TestIsValidAddress (full valid/invalid matrix incl. an explicit Taproot regression guard) and TestToMainnetAddress (byte-identical conversion checks) to tests/test_bitcoin_signing.py.

  • uv run pytest tests/ -q682 passed. Existing BIP-137 sign/verify roundtrip tests still pass (confirms to_mainnet_address/to_mainnet_wif unaffected).
  • grep -rn bech32 allways/ → zero references.

Credit to the issue reporter for the diagnosis and the BIP-350-library fix direction.

anderdc added 2 commits June 8, 2026 12:20
is_valid_address validated with the bech32 package, which implements only
BIP-173 bech32 (witness v0), so every Taproot address (bc1p…, witness v1 /
bech32m) failed the checksum. That rejected all TAO->BTC swaps paying out to a
Taproot wallet — at the CLI pre-check and validator confirm — with a misleading
"Invalid destination address format" (fixes #448).

Route is_valid_address and to_mainnet_address through embit (already a
dependency, already used for the send path). embit validates/encodes all
address types offline — no RPC — so the hand-rolled bech32/base58 logic is
replaced and the direct bech32 dependency is dropped (it stays installed
transitively via bitcoin-message-tool). to_mainnet_address output is verified
byte-identical to the prior behavior for every type that reaches it.

Diagnosis and the BIP-350-library fix direction came from the issue reporter.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] is_valid_address rejects all Taproot (P2TR / bech32m) addresses — TAO→BTC swaps to a bc1p… payout address always fail

2 participants