W-1 — Bitcoin Address Rejection

Every b3chain RPC and wallet path must refuse Bitcoin addresses. Otherwise users would lose money the first time someone copy-pastes a Bitcoin address into a b3chain sendtoaddress call.

Script: audit-address-rejection.py Samples: 36 real Bitcoin addresses Status: 8 / 8 PASS

1. Address-format table

FormatBitcoin exampleb3chain regtest example
P2PKH (legacy) 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa (prefix B on mainnet)
P2SH 3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy (prefix b on mainnet)
Bech32 (segwit v0) bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 b3rt1q0kexyq2nsz3sufuw74py0acaa6l4k7ad8ns2mx
Bech32m (taproot) bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0 (prefix b3p mainnet)

2. What is being audited

  • Sanity: A freshly issued b3rt1 regtest address validates as isvalid: true.
  • Validation: All 36 Bitcoin sample addresses (15 P2PKH, 8 P2SH, 8 Bech32, 5 Bech32m) return isvalid: false.
  • Send safety: sendtoaddress raises an error when given a Bitcoin P2PKH, Bech32, and Taproot address.
  • Descriptor isolation: Importing a Bitcoin xpub via importdescriptors is either rejected outright or, if accepted, the derived addresses use b3chain prefixes (not bc1).

3. How to run

cd b3chain
python3 contrib/testing/audit/audit-address-rejection.py

4. Expected output

[W-1] Bitcoin address rejection
========================================================================
  Testing 36 Bitcoin addresses against b3chain validateaddress...
  PASS  [W-1] sanity: own b3rt1 address validates true  b3rt1q0kexyq2nsz3sufuw74py0acaa6l4k7ad8ns2mx
  PASS  [W-1] own address uses b3rt1 prefix  b3rt1q0kexyq2nsz3sufuw74py0acaa6l4k7ad8ns2mx
  PASS  [W-1] every Bitcoin mainnet address is rejected by validateaddress
  PASS  [W-1] 36/36 Bitcoin addresses rejected as invalid
  PASS  [W-1] sendtoaddress to Bitcoin P2PKH raises error  Invalid B3Chain address: ...
  PASS  [W-1] sendtoaddress to Bitcoin bech32 raises error  Invalid B3Chain address: ...
  PASS  [W-1] sendtoaddress to Bitcoin taproot raises error  Invalid B3Chain address: ...
  PASS  [W-1] xpub import raises an error (acceptable behaviour)
------------------------------------------------------------------------
  8/8 checks passed in 3.3s
AUDIT RESULT: PASS  [W-1]

5. Defence: what if I lose money anyway?

The audit guarantees b3chaind itself rejects Bitcoin addresses. It cannot prevent every kind of address mistake (e.g. malware injecting a different b3chain address into your clipboard). General defences:

  • Always copy-paste the destination address from the source you trust, then read at least the first 6 and last 4 characters back.
  • For large transfers, send a tiny test amount first.
  • Use a wallet UI that displays the address checksum prominently.

6. Source files

The problem in one sentence

Users routinely paste Bitcoin addresses into B3Chain wallets (and vice versa); a fork that silently accepts them is asking to lose money for those users.

The theory

Two address formats coexist on Bitcoin:

  • Base58Check P2PKH / P2SH — version byte 0x00 (P2PKH, 1...),

0x05 (P2SH, 3...).

  • Bech32 / Bech32m — HRP bc for mainnet (bc1q... for SegWit v0,

bc1p... for Taproot), tb for testnet.

B3Chain reuses Base58 mathematics with a different version byte and Bech32 mathematics with HRP b3 (mainnet) / tb3 (testnet). The algorithm is unchanged; the prefix differs.

This is enough that an attacker cannot trivially make a B3Chain address validate as a Bitcoin address. But it is not enough on its own — RPC commands like validateaddress, sendtoaddress, importdescriptors must each independently reject the wrong-network input. A single missed code path means a user can lose funds by typing in a Bitcoin address that the wallet politely accepts.

Hands-on demo

python3 contrib/testing/audit/audit-address-rejection.py

The script tries 36 real Bitcoin addresses (12 P2PKH, 8 P2SH, 8 Bech32 v0, 4 Bech32m Taproot, plus 4 invalid edge cases) against:

  • validateaddress
  • sendtoaddress (with -fallbackfee set, no-op if rejected)
  • importdescriptors with a Bitcoin xpub

Every test must produce isvalid: false or an RPC error.

Exercise

In src/key_io.cpp, locate DecodeDestination. Add a fallback that also tries decoding with HRP bc:

// Before
auto dest = DecodeBech32(str, params.Bech32HRP());
// After (BAD)
auto dest = DecodeBech32(str, params.Bech32HRP());
if (!IsValidDestination(dest)) {
    dest = DecodeBech32(str, "bc");  // accept Bitcoin too "for compatibility"
}

Rebuild, re-run the audit. Expected output:

  FAIL  [W-1] Bitcoin address bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 was accepted
  FAIL  [W-1] sendtoaddress to bc1q... succeeded (should have rejected)
AUDIT RESULT: FAIL  [W-1]

Defence in depth

Even with all 36 addresses rejected at the RPC layer, the audit also checks the cross-derivation property: a B3Chain xpub at coin_type 9333 must produce different addresses than the same seed at Bitcoin's coin_type 0. This catches the much subtler bug where the address format is correctly rejected, but the underlying private key is shared with a Bitcoin wallet — making any signed B3Chain transaction potentially replayable on Bitcoin.

Further reading

  • BIP-173 Bech32 and SegWit address format
  • BIP-350 Bech32m for Taproot
  • BIP-44 multi-account hierarchy (and why coin_type matters)
  • "I sent BSV to a BTC address" / "I sent ETH to a BTC address" — every

exchange's customer-support FAQ has a section on this; B3Chain should not contribute to it.