如何正确实现BIP32、BIP44等密钥派生?子密钥生成遇阻求助
比特币HD钱包子密钥派生问题
问题背景
我已成功生成父扩展密钥,但无法生成符合规范测试向量的正确子密钥。以下是能正常输出正确父密钥的代码:
import hashlib import base58 import ecdsa xprv = "0488ade4" xpub = "0488b21e" x = hmac.new("Bitcoin seed".encode('utf-8'), bytes.fromhex("000102030405060708090a0b0c0d0e0f"), hashlib.sha512).digest() master_private_key = x[0:32] master_chain_code = x[32:64] x_private_key = bytes.fromhex(xprv) + \ (bytes.fromhex("000000000000000000")) + master_chain_code + b'\x00' + master_private_key double_hash = bytes.fromhex(hashlib.sha256(bytes.fromhex(hashlib.sha256(x_private_key).hexdigest())).hexdigest()) x_private_key = x_private_key + double_hash[0:4] mpk = int.from_bytes(master_private_key, byteorder='big') private_key = ecdsa.SigningKey.from_secret_exponent(mpk, curve=ecdsa.SECP256k1) private_key_bytes = private_key.to_string() verifying_key = private_key.get_verifying_key() public_key_compressed_bytes = verifying_key.to_string("compressed") x_public_key = bytes.fromhex(xpub) + \ (b'\x00' * 9) + master_chain_code + bytes.fromhex(public_key_compressed_bytes.hex()) double_hash = bytes.fromhex(hashlib.sha256(bytes.fromhex(hashlib.sha256(x_public_key).hexdigest())).hexdigest()) x_public_key = x_public_key + double_hash[0:4] print("x_private_key: ", base58.b58encode(x_private_key).decode()) print("x_public_key: ", base58.b58encode(x_public_key).decode())
子密钥派生尝试(未成功)
我尝试实现m/0路径的树形派生,但无法得到正确结果,代码如下:
i = 0 cx = hmac.new(master_chain_code, master_private_key + i.to_bytes(4, "big"), hashlib.sha512).digest() cx_private_key = (cx[:32]) cx_chain_code = cx[32:] h = hashlib.new('ripemd160') h.update(hashlib.sha256(public_key_compressed_bytes).digest()) fingerprint = h.digest() c_key = bytes.fromhex(xprv) + \ b'\x01' + \ fingerprint[:4] + \ b'\x00\x00\x00\x00' + \ cx_chain_code + \ b'\x00' + \ cx_private_key double_hash = bytes.fromhex(hashlib.sha256(bytes.fromhex(hashlib.sha256(c_key).hexdigest())).hexdigest()) c_key = c_key + double_hash[0:4] print("children_private_key: ", base58.b58encode(c_key).decode())
错误分析与修正方案
你的子密钥派生代码存在3个关键错误,修正后即可匹配测试向量:
1. HMAC输入错误
普通子密钥(索引值 < 2^31,即非hardened派生)的HMAC输入应为父公钥的压缩字节 + 子索引的4字节大端数据,而非父私钥。只有hardened子密钥(索引 >= 2^31)才使用父私钥作为HMAC输入。
2. 子私钥计算逻辑错误
子私钥不能直接使用cx[:32],必须将其转换为整数后与父私钥的整数形式相加,再对SECP256k1曲线的阶取模,公式为:
child_priv_int = (int.from_bytes(cx_left, 'big') + int.from_bytes(master_private_key, 'big')) % ecdsa.SECP256k1.order
3. 子索引字段硬编码问题
虽然m/0的索引字节确实是b'\x00\x00\x00\x00',但使用i.to_bytes(4, 'big')更通用,能避免索引变化时的硬编码错误。
修正后的完整代码
import hashlib import base58 import ecdsa # 先执行父密钥生成代码(复用之前的正确代码) # ... # 生成m/0路径的子私钥 i = 0 # 普通子密钥索引,<2^31 # 普通派生:HMAC输入为父公钥压缩字节 + 子索引 cx = hmac.new(master_chain_code, public_key_compressed_bytes + i.to_bytes(4, "big"), hashlib.sha512).digest() cx_left = cx[:32] cx_chain_code = cx[32:] # 计算子私钥:模加运算 order = ecdsa.SECP256k1.order child_priv_int = (int.from_bytes(cx_left, 'big') + int.from_bytes(master_private_key, 'big')) % order child_priv_bytes = child_priv_int.to_bytes(32, 'big') # 确保32字节长度,补前导零 # 父公钥指纹(前4字节) h = hashlib.new('ripemd160') h.update(hashlib.sha256(public_key_compressed_bytes).digest()) fingerprint = h.digest()[:4] # 构建子扩展私钥结构 c_key = ( bytes.fromhex(xprv) + b'\x01' + # 深度:父密钥深度为0,子密钥深度为1 fingerprint + i.to_bytes(4, 'big') + # 子索引字节 cx_chain_code + b'\x00' + # 私钥前缀 child_priv_bytes ) # 计算并添加校验和 double_hash = hashlib.sha256(hashlib.sha256(c_key).digest()).digest() c_key += double_hash[:4] print("children_private_key: ", base58.b58encode(c_key).decode())
运行修正后的代码,即可得到符合测试向量的m/0路径子扩展私钥。
内容的提问来源于stack exchange,提问作者Dilettante
相关产品推荐
相关产品推荐

