I have been working on implementing BIP 32 in Python. This is my code for deriving normal (not hardened) child keys as part of an HD_Key class:

def CKDpriv(self, index):
    key = bytes.fromhex(self.c) #key for hmac
    K = self.point.sec(compressed=True)
    data = K + index.to_bytes(4, 'big')
    I = hmac.new(key, data, hashlib.sha512).digest()
    L = I[:32]
    R = I[32:]
    ck = S256Field(int.from_bytes(L, 'big')) + S256Field(int(self.k, 16)) #adding two field elements with secp256k1 parameters
    ck = ck.num
    cc = R 
    level = int(self.level) + 1 
    fingerprint = self.get_fingerprint()
    return HD_Key(str(level).zfill(2), fingerprint, hex(index)[2:].zfill(8), hex(ck)[2:], cc.hex())

Given master key:

level: 00, fingerprint: 00000000, index: 00000000, k: 4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e, c: 60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689, testnet: False

when I try to derive a child key at index 2 using this code, the xprv which is supposed to be this: xprv9vHkqa6EV4sPcjMMuDToT9SVa6UHCwR4pxvYKZdKTqWpcgQqmPuphAbteLtH9GTXaB87d9zYGuVN497UHmPB462kDjovoB7YoYXYKYphJVv is actually this: xprv9vHkqa6EV4sPcjMMuDToT9SVa6UHCwR4pxvYKZdKTqWpcgQqmPuphAbteLtH9GTXaB87d9zYGuVN497Pku2GX4mxm2YwYKQEr52Q25Z4pip.

When I print out the information for the correct extended private key I get this:

level: 01, fingerprint: bd16bee5, index: 00000002, key: 32b6b93726e1121f2553bb18ff5b9f263067afe01019538fe34db8761cb94ad0, chain: 4a92983ca7cede2311c61bf911c76d662b0257404659872c792448c09453deba, testnet: False

And for the incorrect one which my code generated I get this:

level: 01, fingerprint: bd16bee5, index: 00000002, key: 32b6b93726e1121f2553bb18ff5b9f24eb168cc6bf61f3cba3201703ecef8fe2, chain: 4a92983ca7cede2311c61bf911c76d662b0257404659872c792448c09453deba, testnet: False

This indicates that there is a problem with the key being derived. I've tested my extended key serialization and parsing methods, so I don't think that the issue is contained within those. What could be causing this issue?

1 Answer 1


It appears S256Field uses the incorrect modulo. You arrive at the correct chaincode, and the first half of the key is correct. So the issue is that when you do the step parse_256(I_L) + k_par (mod n), your n is incorrect.

We can reverse engineer what value you used for n by finding the difference between the expected key and the calculated key, and adding that to the real value of n.

0x32b6b93726e1121f2553bb18ff5b9f263067afe01019538fe34db8761cb94ad0 - 0x32b6b93726e1121f2553bb18ff5b9f24eb168cc6bf61f3cba3201703ecef8fe2 = 0x14551231950b75fc4402da1722fc9baee
0x14551231950b75fc4402da1722fc9baee + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f

The n your modulo is computed with is 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f, which is actually the curve parameter p. However the value your modulo needs to be computed with is n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141.

p is the finite field for the curve. It defines that largest value for the coordinates of a curve point. You do mod p when doing arithmetic with curve points.

n is the order of the generator point G. It is the number of discrete logarithms (number of private keys) for the curve. You do mod n when doing arithmetic with scalars (private keys).

Since in this step you are adding two scalars, you need to be doing it mod n rather than mod p.

  • Thanks for the clear response, it's working now.
    – ana408
    Jan 31, 2022 at 21:38

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.