People would take SHA256(secret) and use the 32-byte output as a BTC private key. Things got really interesting when someone set secret = "", i.e. an empty string. Its SHA256 is
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Here is a neat tool to convert 32-byte strings to Bitcoin key pairs. For the empty string hash above, it gives us
Private key uncompressed (WIF): 5Hvr8Y5y3QZE6cVciRcsRtmXSLbuJcsVXr5JooEVUEwwGpShw71
Private key compressed (WIF): KwifrGCvMcyEomfJdqfKasVXLqX3rmKrw9c1bZ4YfEZUYoVyHRGv
Legacy address uncompressed: 17DCsohf9Mx9xJxJx5i21VXw8c6xeKVj2d
Legacy address compressed: 151nEUuyFiwrxxqse4wmQfwPvpWACbyBUU
Nested segwit adress: 3FiUrxVsgBuGmTBtfbySRZ4z8kFffzYzTf
Native segwit address: bc1q8l84lncadllmcrn9wsulzj4e24nnjsv86w0jhh
Taproot address:
...and the infamous 50 BTC transaction belongs to one of them.
"What on Earth does any of this have to do with HD Wallets?" is a valid question.
Well, I realized we can easily recreate the madness using a master XPRV and leave out derivation paths.
Others have described the anatomy of XPRV better before me, so allow me to concatenate the bytes according to:
(pseudocode) b"\x04\x88\xad\xe4" + a string of zeros + SHA25&(""), so that we get a 156 characters long hexadecimal string like so
0488ade4000000000000000000000000000000000000000000000000000000000000000000000000000000000000e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
To verify is as an XPRV and make it human-readable, here is a one-liner that does the trick:
>>> base58.b58encode_check(binascii.unhexlify('0488ade4000000000000000000000000000000000000000000000000000000000000000000000000000000000000e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855')).decode()
>>> 'xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAmp8jihbB12h5EN88J7v7oXcjdCgi9zWxT3Ya8znix4tadVdyM'
Lo and behold, if we run
xprivkey = "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAmp8jihbB12h5EN88J7v7oXcjdCgi9zWxT3Ya8znix4tadVdyM"
path = ""
hdw = HDWallet(xprivkey, path)
#and so forth
we get... the same private keys and public addresses!
In other words, we have resurrected the decade-old brain wallet madness and shown that the same goes for XPRV.
Bonus:
If we instead use the stupid SHA("") twice in the same string, e.g.
>>> base58.b58encode_check(binascii.unhexlify('0488ade400000000000000000000e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855')).decode()
>>> 'xprv9s21ZrQH143K24sTZGBSPocQZqkzcNhss3CpUdhrJxgdUVyK3iG7QvdpuddRytfq997BhnDGVmrbqFmCEPDQhbAyXDmFfeFJh3AxsfSxCMp'
We get a seemingly new and unique XPRV, but it resolves to the same good old key pairs. If no derivation path is specified, none of these extra bytes matter.
This ultimately leads me to believe that we could scan using either ...01, ...02, ...03 like brainflayer or by feeding it with good old SHA256 that has been out in the wild for a long time.
Additionally, it will be interesting to see whether the derivation path can be the variable that leads us to the discovery of new (old) public keys and their private keys.
Mnemonic phrases and hierarchical deterministic (HD) wallets may be good. I mean, the intention was certainly good. But it is not so good if you can trick non-tech-savvy people into using XPRVs that are accepted by all major wallet software even if they are a cryptographic disaster.