DISCLAIMER: Consider this a theoretical exercise in producing the worst imaginable HD Wallet and its private keys and public addresses from a safety and good practice perspective. It contains overriding as many safety measures as possible - on purpose. Don't try this at home.Brief introductionFirstly, I will skip a proper intro and instead recommend those interested to read up on
BIP32,
BIP39,
BIP44, and
BIP84 as needed. These are intended to add safety and utility to Bitcoin, and I chose to try my best to misuse them.
I will use Python and some common crypto-related libraries, leaving out most whys and hows.
My idea is to create the worst imaginable BTC mainnet extended private key (XPRV) from a cryptographic viewpoint and derive the first of its corresponding key pairs. In other words: "Catastrophic private keys" in WIF format and the different flavors of public addresses they control - details you can import in Electrum and sweep clean in a matter of seconds.
This is hopefully somewhat entertaining and, in the best of worlds, even rewarding in one way or the other.
I hypothesize that if you learn to do every step wrong, you become better at making it right when needed. I look forward to your comments, corrections, and feedback!
Let's get goingWe need a disastrous XPRV to begin with. Let's aim for the worst of them all. There are excellent online tools, such as
this,
this and
this you can use to create high-entropy and human-readable mnemonic phrases based on standardized word lists, but they have safety measures, as they should have, against week phrases. Also, remember that creating cryptographically sound XPRVs involves many rounds of one-way hashing and the incremental use of random salts and checksums (PBKDF2-HMAC-SHA512).
Can we break it? I think we can:
from bip32 import BIP32
bip32 = BIP32.from_seed(bytes.fromhex(""))
master_xpriv = bip32.get_xpriv()
xpriv_from_path = bip32.get_xpriv_from_path("m")
print(master_xpriv)
print(xpriv_from_path)
See what I did there? I overrode all traces of good practice: I used an empty string ("") as the passphrase, didn't hash anything, didn't salt, used no derivation path at all ("m"), and confirmed both outputs, the master and the first derivation, equate to the same value:
xprv9s21ZrQH143K37GktPqduLh1DoBuxUQvLPY9yR3eJ4zQB4KzSYeoHNutnWNasXJJmEHCneeFhpSuC8Ppc33RXvzsuhSTLLqurx7kLvw9d6p
Please tell me if you imagine a more unsafe and stupid method to create a master XPRV for Bitcoin! (Further, consider all this a homage to the marvelous 50 BTC transaction to the address that belongs to
SHA256(*empty string*) in 2015.)
Now, let's use this master XPRV, once again without derivation paths, to find the possible private keys and all(?) corresponding public addresses, given BTC standards of 2023:
import sympy
from bitcoinutils.setup import setup
from bitcoinutils.keys import P2pkhAddress, P2shAddress, PrivateKey
from bitcoinutils.hdwallet import HDWallet as hdw
setup('mainnet')
xprivkey = "xprv9s21ZrQH143K4YUcKrp6cVxQaX59ZFkN6MFdeZjt8CHVYNs55xxQSvZpHWfojWMv6zgjmzopCyWPSFAnV4RU33J4pwCcnhsB4R4mPEnTsMC"
path = ""
hdw = HDWallet(xprivkey, path)
priv1 = hdw.get_private_key()
wif1 = priv1.to_wif(compressed=False)
wif2 = priv1.to_wif()
pub1 = priv1.get_public_key()
script_witness = priv1.get_public_key().get_segwit_address().to_script_pub_key()
add1 = pub1.get_address(compressed=False).to_string()
add2 = pub1.get_address().to_string()
add3 = P2shAddress.from_script(script_witness).to_string()
add4 = pub1.get_segwit_address().to_string()
add5 = pub1.get_taproot_address().to_string()
After some prettification (actual print commands omitted), the output is
Private key uncompressed: 5JBSnNFYrX7VZcomqnzmmVZqTuubdCDJbkmR8Z7g9qJ48FmxDkV
Private key compressed: Kxq6oEoDbZuf4y3SvN1zbP5vunX9pkVJgQB3WLkTozAGns2p7PWe
Legacy address uncompressed: 15np5mEdrT7CAaFePSDY9PwiqZqZaT4noQ
Legacy address compressed: 1ATHjwQRMgxmCm6c8Pj9snyrd39UMZyKBZ
Nested segwit address: 32ZjiUAL8FNXj9gQ7Jqcsi2zGHL4VxAop7
Native segwit address: bc1qv7c7h5d8thdd8gxsmqaquhe8xkzk5cdmjmjyxg
Taproot address: bc1p6tx5tl7cp49alj4n48kfca0yslgvzhvakja7fysx3y384thl2wmqj8nszu
Concluding remarks and questionsAt least one of these public addresses has appeared on the blockchain before. I claim no fame in finding these. I just added what has been out for years together, but I sincerely wonder - can you think of an even more stupid and/or insecure way of using XPRV to set up an HD Wallet?
I haven't even bothered with playing around with derivation paths yet. It should be a simple loop in Python to spit out millions, if not billions, of "hardened" (not so hardened) children and change addresses that are derived from this single disastrous XPRV.
What say you? Can you possibly do worse?