Skip to content

Instantly share code, notes, and snippets.

@mflaxman
Last active November 20, 2020 20:57
Show Gist options
  • Save mflaxman/d72d25ad00941cea8241b6667cf008a8 to your computer and use it in GitHub Desktop.
Save mflaxman/d72d25ad00941cea8241b6667cf008a8 to your computer and use it in GitHub Desktop.
Using a BIP32 Path as a Blinding Factor for 1 key in your p2wsh - RESEARCH IDEA, DO NOT USE
from hashlib import sha256
# https://bitcoin.stackexchange.com/questions/92056/what-is-the-max-allowed-depth-for-bip32-derivation-paths
MAX_BIP32 = 2 ** 31 - 1
def encode(num, base=MAX_BIP32):
result = []
while num > 0:
num, remainder = divmod(num, base)
result.append(remainder)
return result
def decode(encoded, base=MAX_BIP32):
res = 0
for cnt, r in enumerate(encoded):
res += r * base ** cnt
return res
# Poor test coverage
NUM, BASE = 98278243937, 34
assert decode(encode(NUM, BASE), BASE) == NUM
def passphrase_to_bip32path(passphrase_as_string):
passphrase_as_int = int(sha256(passphrase_as_string.encode()).hexdigest(), 16)
return "m/" + "/".join(
[str(x) for x in encode(num=passphrase_as_int, base=MAX_BIP32)]
)
#####
from buidl.hd import HDPrivateKey, HDPublicKey
SECRET_PASSPHRASE = "correct horse battery staple"
MNEMONIC = "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo abstract"
print(f"Service creates seed from good source of randomness: {MNEMONIC}")
hd_priv = HDPrivateKey.from_mnemonic(MNEMONIC)
print(
"Service gives xpub to user (in practice they'd give a child public key but let's ignore that):"
)
print(hd_priv.pub)
print("\n" + "-" * 70)
print(
f"User creates secret passphrase to give to family member and their software uses that to create BIP32 paths: {SECRET_PASSPHRASE}"
)
path = passphrase_to_bip32path(SECRET_PASSPHRASE)
print("This results in the following path:", path)
print(
'User "blinds" xpub with this path resulting in new child xpub to use in the quorum:'
)
hd_pub = HDPublicKey.parse(hd_priv.pub.xpub())
print(hd_pub.traverse(path=path))
print("User stacks many sats...")
print("\n" + "-" * 70)
print(
"User is hit by a bus and their family member presents the passphphase (which can calculate the path) or the path itself to the service."
)
print("Here is confirmation the service has the corresponding xpriv from before")
child_priv = hd_priv.traverse(path=path)
print(child_priv)
print(child_priv.pub)
$ python3 blinding.py
Service creates seed from good source of randomness: zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo abstract
Service gives xpub to user (in practice they'd give a child public key but let's ignore that):
xpub661MyMwAqRbcGTGRjiWquQ5BqBY18kdxCET7URgUDhzs2uBNwsgvToGYSW6FJziJUMkK49N8ivbdAKj4T4ZY5eZchRXNoFHuAVM5eGW5xWi
----------------------------------------------------------------------
User creates secret passphrase to give to family member and their software uses that to create BIP32 paths: correct horse battery staple
This results in the following path: m/805017443/1460853890/917824691/16906294/771442409/1299979404/1135500815/1575327236/196
User "blinds" xpub with this path resulting in new child xpub to use in the quorum:
xpub6PTRGqvJk8jfGaEDJUvHUZTwPytvQawprpbc4GC2DW4ymui3fRSezj7g3tKEdP5Nk8cZQDkUF71UT1EoDrsWK1kH6S3J7a1GmJL2ymvFy5D
User stacks many sats...
----------------------------------------------------------------------
User is hit by a bus and their family member presents the passphphase (which can calculate the path) or the path itself to the service.
Here is confirmation the service has the corresponding xpriv from before
xprvAAU4sLPQumBN469kCTPH7RXCqx4S18DyVbg1FsnQfAXzu7Nu7t8QSvoCCcqVdyhWqPxKFRmenqCH6uMRCJiqm3Gj71HHVE7f1miG2fqghmx
xpub6PTRGqvJk8jfGaEDJUvHUZTwPytvQawprpbc4GC2DW4ymui3fRSezj7g3tKEdP5Nk8cZQDkUF71UT1EoDrsWK1kH6S3J7a1GmJL2ymvFy5D
@mflaxman
Copy link
Author

mflaxman commented Nov 19, 2020

Pros:

  • Service and family member cannot see anything about your HODLing (balances, transaction history, etc) unless they combine the original xpub with the passphrase. Neither individually is sufficient to leak any information.
  • Service and family member cannot gain access to this key without colluding. If either is hacked and the other isn't, the key is not at risk (great for giving to family members who may not be able to properly guard private keys).
  • Possession of the passphrase proves you are the person designated by the original owner to receive the bitcoins (or that you stole the passphrase from someone who was). This can help authenticate you with the service.
  • Assuming service and family member don't collude nor both get hacked, then you have perfect privacy throughout your lifetime, and your family gets this 1 keys should anything happen to you. Your privacy is violated only after your death.

Cons:

  • Current hardware wallets have some limitations around which paths they'll sign transactions on.
  • Deriving a path like this may be slow on old hardware (need to do more profiling), although it's an emergency recovery key so it's OK if signing is a little slow the 1 time it's ever used.
  • Loss: if service loses their xpriv OR family member loses their passphrase, then the private key is lost forever. For example, if you and your family member die in the same boating accident then your other family members have no recourse (assuming the password wasn't written down). One solution is to give the passphrase to many family members, but that introduces new risks if they're hacked. Service should already be good about backups here, but the point is that for this key it's a 2-of-2 situation.
  • Complexity is the enemy of security.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment