#!/usr/bin/env python3
"""Verify BIP84 address derivation independently of bip-utils."""
from mnemonic import Mnemonic
import hashlib
import hmac
from ecdsa import SECP256k1, SigningKey
mn = Mnemonic("english")
mnemonic_str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
seed = mn.to_seed(mnemonic_str)
print("Seed:", seed.hex())
def hmac_sha512(key, message):
return hmac.new(key, message, hashlib.sha512).digest()
def sha256(data):
return hashlib.sha256(data).digest()
def ripemd160(data):
h = hashlib.new("ripemd160")
h.update(data)
return h.digest()
def hash160(data):
return ripemd160(sha256(data))
def ckdf(seed):
I = hmac_sha512(b"Bitcoin seed", seed)
return I[:32], I[32:]
def derive_child(parent_key_bytes, parent_chain, index, hardened=True):
if hardened:
index = index + 0x80000000
data = b"\x00" + parent_key_bytes + index.to_bytes(4, "big")
I = hmac_sha512(parent_chain, data)
chain_code = I[32:]
n = SECP256k1.order
key_int = (int.from_bytes(I[:32], "big") + int.from_bytes(parent_key_bytes, "big")) % n
return key_int.to_bytes(32, "big"), chain_code
def secp256k1_pubkey(privkey_bytes):
sk = SigningKey.from_string(privkey_bytes, curve=SECP256k1)
return sk.verifying_key.to_string()
def bech32_encode(hrp, data_bytes):
charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
def convertbits(data, frombits, tobits, pad=True):
acc = 0
bits = 0
ret = []
maxv = (1 << tobits) - 1
for v in data:
acc = (acc << frombits) | v
bits += frombits
while bits >= tobits:
bits -= tobits
ret.append((acc >> bits) & maxv)
if pad and bits:
ret.append((acc << (tobits - bits)) & maxv)
return ret
def polymod(values):
GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
res = 1
for v in values:
b = res >> 25
res = ((res & 0x1ffffff) << 5) ^ v
for i in range(5):
res ^= GEN[i] if ((b >> i) & 1) else 0
return res ^ 1
data5 = convertbits([0x00] + list(data_bytes), 8, 5)
payload = [0x00] + data5
checksum = polymod([0] + [ord(c) for c in hrp] + [0] + payload + [0]*6)
for i in range(6):
payload.append((checksum >> (5 * (5 - i))) & 0x1f)
return hrp + "1" + "".join(charset[d] for d in payload)
# Derive master
master_key, master_chain = ckdf(seed)
print("Master key (hex):", master_key.hex())
# Derive m/84'/0'/0'/0/0
path_indices = [84, 0, 0, 0, 0]
key = master_key
chain = master_chain
for idx in path_indices:
key, chain = derive_child(key, chain, idx, hardened=True)
# Get public key
pub_uncompressed = secp256k1_pubkey(key)
# Compress: 02/03 prefix + x coordinate
y = int.from_bytes(pub_uncompressed, "big")
prefix = 0x02 if y % 2 == 0 else 0x03
pub_compressed = bytes([prefix]) + pub_uncompressed[:32]
print("PubKey compressed:", pub_compressed.hex())
h160 = hash160(pub_compressed)
print("H160:", h160.hex())
addr = bech32_encode("bc", h160)
print()
print("Independently computed BIP84 addr 0:", addr)
print("bip-utils says: bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu")
print("Commonly-cited test vector: bc1qcr8te4kr609gcawutmrza0j4xv80jy8z3z67nr")
print()
if addr == "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu":
print("MATCH: bip-utils derivation is correct.")
elif addr == "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z3z67nr":
print("MATCH: the commonly-cited test vector is correct.")
else:
print("NO MATCH - something is wrong with my derivation.")