2

I am currently working on a challenge that involves deriving a non-hardened extended private key for a specific search path in Bitcoin. The challenge provides the following information:

Bitcoin root extended public key (starting with xpub...) Bitcoin non-hardened extended private key for the derivation path m/44/0/0/1/404 (starting with xprv...) Search path for which I need to derive the associated (non-hardened) extended private key: m/44/0/0/0/402 The task is to derive the non-hardened extended private key for the given search path, and the submission should be encoded as per the BIP 32 specification.

I've attempted to use the pycoin and python-hdwallet libraries, but I'm facing difficulties understanding the process and obtaining the correct result. Could someone provide guidance or a step-by-step approach on how to achieve this using these libraries or any other relevant tools?

Additionally, if there are specific functions or methods within these libraries that are suitable for this task, please provide examples to help me grasp the implementation better.

I appreciate any assistance and clarification on this matter. Thank you!

Update

Here's the original problem statement:

The challenge data consists of: A Bitcoin root extended public key (for the derivation path m, as it is the root) starting with xpub... A Bitcoin non-hardened extended private key for the derivation path m/44/0/0/1/404 starting with xprv... A search path for which you have to derive the associated (non-hardened) extended private key (as a string). This path will be something like: m/44/0/0/0/402 Your task is it to derive the non-hardened extended private key for this search path, this is your flag. Your submission of the searched extended private key should be encoded as the one you received from the server, i.e., serialized string starting with xprv... according to the specification in BIP 39.

4

1 Answer 1

2
+50

Here's some working code for calculating a parent extended private key from a master extended public key and child extended private key.

#!/usr/bin/env python3

# pip install hdwallet
# pip install bitcoin-utils

import hmac
import hashlib
import struct
from binascii import  unhexlify
from base58check import b58encode
from hdwallet import HDWallet as HDWallet
from hdwallet.symbols import BTC as SYMBOL


# --------------------
# Provided information
# --------------------

# Master Extended Public Key (m)
MASTER_EXTENDED_PUBLIC_KEY: str = "xpub661MyMwAqRbcF2s3gyqdcUgw6dU7DjLGHCwkM9jndzGT8WXJjMxZiYnvNmfuzsEXC13FUn9BTeefy3uYf4Cj5k86iHUFzLS1yatpXN9hq4u"

# Child Extended Private Key (m/44/0/0/1/404)
CHILD_EXTENDED_PRIVATE_KEY: str = "xprvA3WvYTa5Xv4kSXuL9mAFMimc5psTTB1Dq8x4q9HB7R4EmpufsXFuhi9SpVVYXsmyjM5dvoGowU3W9hDuYRrgkQiiE1d881TZrFLhLNxBs7g"


# ------
# Method
# ------

# Initialize a wallet
hdwallet: HDWallet = HDWallet(symbol=SYMBOL)

# Create a HDWallet from the master extended public key
hdwallet.from_xpublic_key(xpublic_key=MASTER_EXTENDED_PUBLIC_KEY, strict=True)

# Get the parent extended public key at m/44/0/0/1
# We need this so that we can calculate the extended private key at m/44/0/0/1 (the level above the child private key that has been revealed)
PARENT_EXTENDED_PUBLIC_KEY = hdwallet.from_path("m/44/0/0/1")
PARENT_PUBLIC_KEY: str = PARENT_EXTENDED_PUBLIC_KEY.public_key()
PARENT_CHAIN_CODE: str = PARENT_EXTENDED_PUBLIC_KEY.chain_code()
print("Parent Extended Public Key (m/44/0/0/1):", PARENT_EXTENDED_PUBLIC_KEY.xpublic_key())
print("  Public Key:", PARENT_PUBLIC_KEY)
print("  Chain Code:", PARENT_CHAIN_CODE)
print("")

# Extract the raw private key from the child extended private key m/44/0/0/1/404
# We need this so that we can calculate the parent extended private key
child: HDWallet = HDWallet(symbol=SYMBOL)
child.from_xprivate_key(xprivate_key=CHILD_EXTENDED_PRIVATE_KEY, strict=False) # False allows you to initialize from a non-root extended key
CHILD_PRIVATE_KEY: str = child.private_key()
print("Child Private Key:", CHILD_PRIVATE_KEY)
print("")

# Calculate the parent extended private key for m/44/0/0/1 (this is the mathematical bit)

# Put the chain code and (parent public key + index) through HMAC-SHA512
# This step is used in HD Wallets when deriving non-hardened children for both private and public extended keys
index = struct.pack(">L", 404) # Use the same index number as the child extended private key that we know
data = unhexlify(PARENT_PUBLIC_KEY) + index
hmac_result = hmac.new(unhexlify(PARENT_CHAIN_CODE), data, hashlib.sha512).hexdigest()

# Get the left side of the hmac digest (the left 32 bytes)
hmac_result_left = hmac_result[:64]

# Calculate the parent private key (child private key - hmac left)
# Note: All private keys must be modulo the number of points on the Secp256k1 curve
secp256k1_n = 115792089237316195423570985008687907852837564279074904382605163141518161494337
PARENT_PRIVATE_KEY: str = ((int(CHILD_PRIVATE_KEY, 16) - int(hmac_result_left, 16)) % secp256k1_n).to_bytes(32, "big").hex()
print("Parent Private Key:", PARENT_PRIVATE_KEY)

# Create an address for the parent extended private key
# This is so that we can intialize a new HD Wallet using the extended private key and use it to derive children easily using the library

# Get the fingerprint, which can be found from the parent of this parent extended public key
parent_parent_extended_public_key: HDWallet = HDWallet(symbol=SYMBOL)
parent_parent_extended_public_key.from_xpublic_key(xpublic_key=MASTER_EXTENDED_PUBLIC_KEY, strict=True)
parent_fingerprint = parent_parent_extended_public_key.from_path("m/44/0/0").finger_print()

# Serialize all the data for the parent extended private key
version = "0488ade4" # xprv (BIP)
depth = "04" # how many levels deep this extended key is from master, so m/44/0/0/1 = 4
index = "00000001" # the index number of this extended key from its parent, which is the last number of m/44/0/0/1
chain_code = PARENT_CHAIN_CODE
data = "00" + PARENT_PRIVATE_KEY
raw = (version + depth + parent_fingerprint + index + chain_code + data)

# Create a checksum for this serialized data (first 4 bytes of the hash256 of the raw data above)
checksum = hashlib.sha256(hashlib.sha256(unhexlify(raw)).digest()).hexdigest()[0:8]

# Create address by converting to base58
PARENT_EXTENDED_PRIVATE_KEY = b58encode(unhexlify(raw + checksum)).decode("utf-8")
print("Parent Extended Private Key (m/44/0/0/1):", PARENT_EXTENDED_PRIVATE_KEY)
print("")


# -----
# Usage
# -----

# Now that we've calculated the extended private key for m/44/0/0/1, we can calculate all of the child keys below it.
# For example, we can calculate the extended private key at m/44/0/0/1/402

# Initialize a new wallet from the address for this parent extended private key at m/44/0/0/1
parent: HDWallet = HDWallet(symbol=SYMBOL)
parent.from_xprivate_key(xprivate_key=PARENT_EXTENDED_PRIVATE_KEY, strict=False) # False allows you to initialize from a non-root extended key

# Derive the extended private key at m/44/0/0/1/402
example = parent.from_path("m/402") # Note: This is m/44/0/0/1/402, because we are starting from m/44/0/0/1
print("Example (m/44/0/0/1/402):", example.xprivate_key())


# ------
# Checks
# ------

# I already knew the master extended private key for this wallet, so we can use it to check that it derives the same extended private key at m/44/0/0/1/402 as the parent extended private key we calculated.

MASTER_EXTENDED_PRIVATE_KEY: str = "xprv9s21ZrQH143K2YnaaxJdFLkCYbdcpGcQuz29YmLB5ejUFiCABpeKAkUSXVuqysESjMJhk8yTfhxR3BT6Ag7Z5fYJ1jFgidmrGgfZYNCLnNB"
check: HDWallet = HDWallet(symbol=SYMBOL)
check.from_xprivate_key(xprivate_key=MASTER_EXTENDED_PRIVATE_KEY, strict=True) # False allows you to initialize from a non-root extended key
check.from_path("m/44/0/0/1/402")
print("Check   (m/44/0/0/1/402):", check.xprivate_key())

Firstly, two caveats:

  1. I'm not very good at Python, and I've not used the hdwallet or bitcoin-utils Python libraries before.
  2. There aren't any built-in methods in these libraries for this, so I worked with what was available. I had to perform some steps manually (e.g. converting a raw private key in to an address).

So basically the code is very rough, and I just did the bare minimum to get it working.

Secondly, I've only worked out the extended private key at the m/44/0/0/1 level of the tree. This allows you to work out all the child keys below that level only.

Therefore, if you wanted to calculate a child extended private key at m/44/0/0/0/402 for example, you would need to work backwards again to calculate the extended private key at m/44/0/0 first. This can be achieved by calculating the extended public key at m/44/0/0 (from the master extended public key), then using this along with the newly-calculated extended private key at m/44/0/0/1 to calculate the parent extended private key above it at m/44/0/0. From here you can then derive down to m/44/0/0/0/402.

The above can be achieved by repeating the relevant parts of code.

I haven't given the exact solution to your challenge, as I didn't want take away from the learning experience, and this contains the code you need to get started.

In fairness, this is a fairly complex challenge that requires understanding of how HD Wallets work, and it's not something that you would be able to solve using the default functions found within a library. It would probably beneficial to write your own library for this kind of stuff.

Nonetheless, hopefully this code helps to get you on the right track.

This page explains some of the details: https://learnmeabitcoin.com/technical/extended-keys


Old reply

Using a modified version of the example code from here: https://github.com/meherett/python-hdwallet/blob/master/examples/from_xprivate_key.py

#!/usr/bin/env python3

from hdwallet import HDWallet as HDWallet
from hdwallet.symbols import BTC as SYMBOL

# Bitcoin root xprivate key
XPRIVATE_KEY: str = "xprv9s21ZrQH143K2YnaaxJdFLkCYbdcpGcQuz29YmLB5ejUFiCABpeKAkUSXVuqysESjMJhk8yTfhxR3BT6Ag7Z5fYJ1jFgidmrGgfZYNCLnNB"

# Initialize Bitcoin mainnet HDWallet
hdwallet: HDWallet = HDWallet(symbol=SYMBOL)

# Get Bitcoin HDWallet from xprivate key
hdwallet.from_xprivate_key(xprivate_key=XPRIVATE_KEY, strict=True)

# Derivation from path
hdwallet.from_path("m/44/0/0/1/404")

# Print results
print("Root XPrivate Key:", hdwallet.root_xprivate_key())
print("XPrivate Key:", hdwallet.xprivate_key())

You will need to replace the master extended private key with your own. This code just contains an example for testing.

I haven't used this library before, so I can't explain too much about how to use it. But this example should get you going.

EDIT:

Sorry I just realized your asking to derive an extended private key from a master extended public key.

It's not possible to derive private keys from a master extended public key in a HD wallet. Are you sure this is what you're being asked to do with this challenge?

8
  • Given a parent xpub, and a child private key that is (non-hardened) derived from it, one can compute the parent xprv. This is pointed out in BIP32, and is the reason why hardened derivation exists. Jan 6 at 22:55
  • 1
    @PieterWuille Thank you, I didn't realize a private key was also being given away.
    – inersha
    Jan 7 at 11:20
  • 1
    @AnkitTiwari I've updated my reply. Sorry for the delay and my misunderstanding.
    – inersha
    Jan 7 at 21:14
  • 1
    @AnkitTiwari You've only got the extended private key for m/44/0/0/1 for now, so this can only generate children for m/44/0/0/1/X. To work out keys within m/44/0/0/0/X, you would need to work out the extended private key for m/44/0/0. This can be done by re-using the script, but this time calculating the parent extended public key at m/44/0/0, and using the newly-calculated child extended private key at m/44/0/0/1. You will then have the extended private key for m/44/0/0, which can be used to derive down to m/44/0/0/0/402. So you basically need to go a level up the tree first.
    – inersha
    Jan 8 at 12:30
  • 1
    @AnkitTiwari The parent extended private key you calculate for m/44/0/0 should be xprv9z75bqka4L6nQrCX2vJBLWpX8dhkJEDJSAASarQhZ46YFxw7xEukKTiJ5XJSzkREMVLKkWYM1upX3g3vtEVE1X6SgMZcVvupzbtaxUWUfvY
    – inersha
    Jan 8 at 13:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.