W-2 — HD Wallet BIP44 coin_type

b3chain assigns itself BIP44 coin_type 9333 on mainnet and 1 on testnet/regtest (per the BIP44 standard). This audit verifies the assignment is implemented correctly in the wallet code, documented in doc/b3chain-bip44.md, and not leaking between mainnet and test chains.

Script: audit-hd-coin-type.py coin_type: 9333 Status: 9 / 9 PASS

1. Why a custom coin_type

BIP44's coin_type is the second hardened derivation level in the path m/purpose'/coin_type'/account'/change/index. Each chain gets its own number, registered in SLIP-0044, so a single seed phrase produces different keys on Bitcoin (0), Litecoin (2), Ethereum (60), etc.

If b3chain reused Bitcoin's coin_type 0, importing the same BIP39 mnemonic into both wallets would produce identical addresses on both chains — with all the seed-reuse and accidental cross-chain replay risks that brings.

2. Why 9333

CriterionHow 9333 fits
Not currently assignedThe 9216–16383 band is unallocated as of writing.
Mnemonic9 = first decimal digit not used by Bitcoin (0); 333 = three "3"s for "B3Chain".
Outside common rangesAvoids Ethereum (60), Litecoin (2), BCH (145), DOGE (3), Zcash (133).
Easy to remember4-digit, all-decimal value.

9333 is proposed, not yet registered with SLIP-0044. See the documentation page for the registration plan and rollback strategy.

3. Derivation paths

Address typeMainnet pathTestnet/regtest path
P2PKH (legacy) m/44'/9333'/0'/0/0m/44'/1'/0'/0/0
P2SH-segwit m/49'/9333'/0'/0/0m/49'/1'/0'/0/0
Bech32 (segwit) m/84'/9333'/0'/0/0m/84'/1'/0'/0/0
Bech32m (Taproot) m/86'/9333'/0'/0/0m/86'/1'/0'/0/0

4. What is being audited

  • Static. src/wallet/walletutil.cpp contains the literal "9333h" exactly once and uses "1h" on test chains, gated by Params().IsTestChain().
  • Static. doc/b3chain-bip44.md exists and documents both 9333 and the SLIP-0044 plan.
  • Functional. On regtest, a freshly-issued bech32 address has hdkeypath starting with m/84h/1h/0h/ and the descriptor list contains /1h/ but not /9333h/.

5. How to run

cd b3chain
python3 contrib/testing/audit/audit-hd-coin-type.py

6. Expected output

[W-2] HD wallet BIP44 coin_type
========================================================================
  PASS  [W-2] walletutil.cpp uses coin_type 9333h on mainnet
  PASS  [W-2] walletutil.cpp uses coin_type 1h on test chains
  PASS  [W-2] gate is IsTestChain() (testnet/regtest detection)
  PASS  [W-2] '9333h' literal appears exactly once in walletutil.cpp
  PASS  [W-2] doc/b3chain-bip44.md mentions coin_type 9333
  PASS  [W-2] doc/b3chain-bip44.md references SLIP-0044

  Spawning regtest node and verifying default descriptor uses /1h/...
  PASS  [W-2] regtest hdkeypath uses coin_type 1h  hdkeypath=m/84h/1h/0h/0/0
  PASS  [W-2] at least one regtest descriptor contains /1h/
  PASS  [W-2] no regtest descriptor contains /9333h/ (mainnet-only)
------------------------------------------------------------------------
  9/9 checks passed in 1.2s
AUDIT RESULT: PASS  [W-2]

7. Independent verification (third-party tooling)

Take the BIP39 seed your b3chain wallet was created from, derive m/84'/9333'/0'/0/0 with any reference BIP32 / BIP44 library, and verify the resulting public key matches the address the wallet returned.

# Python with bip_utils
from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins

mnemonic = "your twelve or twenty four words ..."
seed = Bip39SeedGenerator(mnemonic).Generate()

# bip_utils does not yet know about 9333; pass it explicitly:
from bip_utils import Bip44Conf, Bip44Coins, Bip44
# (a fully worked example will be added once bip_utils ships a config.)

A short standalone Python verifier is on the roadmap; today the audit script verifies internal consistency, and the static checks confirm the constant.

8. Source files

The problem in one sentence

Two wallets that derive from the same BIP39 seed but at different coin_types produce completely different private keys, so getting this number right (and proving it) is the boundary between a B3Chain wallet and an accidentally-Bitcoin wallet.

The theory

BIP44 derivation paths look like:

m / 44' / coin_type' / account' / change / address_index

Bitcoin's coin_type is 0. Litecoin's is 2. Ethereum's is 60. B3Chain proposes 9333 (pending SLIP-0044 registration; see doc/b3chain-bip44.md).

If two chains accidentally use the same coin_type:

  • The same seed in both wallets derives the same private key.
  • A signed transaction's spending signature is valid for the same

UTXO on either chain (if it exists).

  • The likelier failure mode: users treat one chain's xpub as the

other's in external tooling, lose track of funds.

Hands-on demo

python3 contrib/testing/audit/audit-hd-coin-type.py

The script:

  1. Greps src/wallet/walletutil.cpp for the literal 9333h,

verifies it appears once and is gated by !IsTestChain().

  1. Spawns a regtest node, creates a fresh descriptor wallet, calls

getaddressinfo on a freshly-derived address, asserts the hdkeypath contains 9333' (mainnet) or 1' (testnet/regtest).

  1. Independently re-derives the address with the Python bip_utils

library at m/84'/9333'/0'/0/0, verifies the bytes match.

  1. Re-derives at m/84'/0'/0'/0/0 (Bitcoin's coin_type), verifies the

address is different.

Exercise

In src/wallet/walletutil.cpp, edit the coin_type literal back to 0h:

// Before
const std::string coin_type = IsTestChain() ? "1h" : "9333h";
// After (BAD - silently makes B3Chain mainnet wallets identical to Bitcoin)
const std::string coin_type = IsTestChain() ? "1h" : "0h";

Rebuild, re-run the audit. Expected output:

  FAIL  [W-2] descriptor uses coin_type 0 (Bitcoin), expected 9333
  FAIL  [W-2] cross-derivation: B3Chain address matches Bitcoin coin_type 0 derivation
AUDIT RESULT: FAIL  [W-2]

The second failure is the important one — even if you missed the literal, the cross-derivation test catches the actual semantic bug.

What if SLIP-0044 assigns a different number?

9333 is a proposal, not an assignment. The migration plan is in doc/b3chain-bip44.md:

  1. Submit a PR to the SLIP-0044 repo requesting 9333 for B3Chain.
  2. If accepted as-is: no migration.
  3. If a different number X is assigned: ship a wallet migration

that sweeps from 9333-derived addresses to X-derived addresses, continues accepting 9333 xpubs in importdescriptors for backward compatibility.

  1. If SLIP-0044 takes >12 months: publicly commit to 9333.

Either way, this audit pins the choice and prevents silent drift.

Further reading

  • BIP-44 multi-account hierarchy:

github.com/bitcoin/bips/blob/master/bip-0044.mediawiki

  • SLIP-0044 (Trezor / coin_type registry):

github.com/satoshilabs/slips/blob/master/slip-0044.md

  • BIP-32 hierarchical deterministic wallets:

github.com/bitcoin/bips/blob/master/bip-0032.mediawiki

  • BIP-39 mnemonic seed phrases:

github.com/bitcoin/bips/blob/master/bip-0039.mediawiki