1. Address-format table
| Format | Bitcoin example | b3chain 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:
sendtoaddressraises an error when given a Bitcoin P2PKH, Bech32, and Taproot address. - Descriptor isolation: Importing a Bitcoin xpub via
importdescriptorsis either rejected outright or, if accepted, the derived addresses use b3chain prefixes (notbc1).
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
- contrib/testing/audit/audit-address-rejection.py
- src/kernel/chainparams.cpp — address prefixes
- src/key_io.cpp — address parsing
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
bcfor 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:
validateaddresssendtoaddress(with-fallbackfeeset, no-op if rejected)importdescriptorswith 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.