HD Wallet 完整實作指南:從 BIP-32/39/44 到 testnet 金鑰派生

深入探討階層式確定性錢包(HD Wallet)的技術原理與實作。涵蓋 BIP-32 派生机制的數學推導、BIP-39 助記詞規範、完整的 BIP-44 多帳戶錢包結構,以及從零開始建構 HD Wallet 的 Python 程式碼範例。提供可直接在 testnet 上驗證的完整實作流程。

HD Wallet 完整實作指南:從 BIP-32/39/44 到 testnet 金鑰派生

概述

階層式確定性錢包(Hierarchical Deterministic Wallet,HD Wallet)是現代比特幣錢包的標準技術基礎。HD Wallet 允許從單一種子(Seed)派生出無限數量的金鑰對,極大地簡化了錢包備份流程,並為用戶提供了清晰的帳戶組織結構。本指南將深入探討 HD Wallet 的技術原理、BIP-32/39/44 規範細節,並提供完整的 Python 實作程式碼範例,讓讀者能夠從零開始建構自己的 HD Wallet 並在 testnet 上驗證。

HD Wallet 的設計動機

傳統比特幣錢包的局限性

在 HD Wallet 出現之前,比特幣錢包面臨幾個重要的實際問題:

金鑰管理困難:傳統錢包(Nondaeterministic Wallet)為每筆交易生成新的金鑰對。理論上,一個錢包可能包含數千個獨立金鑰。備份這些金鑰意味著必須定期備份整個錢包文件,否則可能丟失比特幣。

隱私洩露風險:如果攻擊者獲得了錢包的備份,可以推斷出錢包的所有金鑰。此外,若錢包的隱私策略不佳,使用同一金鑰的多次交易可能產生關聯。

金鑰派生的不連續性:使用傳統錢包時,新生成的金鑰無法從舊金鑰推導出來。這意味著錢包必須維護一個完整的金鑰池,增加了同步和存儲的複雜性。

HD Wallet 的核心優勢

HD Wallet 通過階層式確定性派生解決了上述問題:

單一種子備份:從種子(Seed)可以派生出整個錢包的所有金鑰對。用戶只需備份12或24個單詞的助記詞(Mnemonic),即可恢復所有金鑰。

確定性派生:給定相同的種子,總是產生相同的金鑰序列。這消除了「金鑰池同步」的問題。

階層式結構:HD Wallet 具有樹狀結構,允許用戶將不同用途的金鑰組織到不同分支。例如,一個分支用於日常支付,另一個分支用於冷存儲。

隱私保護:不同分支的金鑰之間沒有關聯性。用戶可以將不同分支的公鑰提供給不同的服務商,而不會洩露它們屬於同一錢包。

BIP-32:階層式確定性錢包

技術規範

BIP-32(Bitcoin Improvement Proposal 32)定義了 HD Wallet 的核心技術標準。根據 BIP-32 原始規範:

Master Seed:主種子是一個 128-512 位的隨機數。錢包軟體將其轉換為助記詞,方便人類記憶和抄寫。

Master HMAC:使用 HMAC-SHA512 從主種子派生出:

子金鑰派生函數 CKDpriv:從父金鑰派生子金鑰,使用以下公式:

CKDpriv((k_par, c_par), i) → (k_i, c_i)

其中:
- k_par: 父私鑰
- c_par: 父鏈碼
- i: 子金鑰索引(32 位整數)

強化派生 vs 普通派生

普通派生(i < 2^31)允許從公鑰派生子公鑰,無需暴露私鑰。這對接收地址場景很有用。

強化派生(i ≥ 2^31)需要私鑰參與派生。這提供了更高的安全性,因為無法從子公鑰推導父公鑰。建議用於高層次分支(如 m/i')。

BIP-32 派生算法推導

以下是 BIP-32 定義的 CKDpriv 函數詳細推導:

普通派生(Normal Derivation,i ∈ [0, 2^31 - 1])

I = HMAC-SHA512(Key = c_par, Data = ser_P(point(k_par)) || ser_32(i))
其中:
- ser_P(point(k_par)): 父公鑰的壓縮格式(33 位元組)
- ser_32(i): 子索引的大端序32位元表示
- I_L: 前256位作為子私鑰 k_i = parse_256(I_L)
- I_R: 後256位作為子鏈碼 c_i

強化派生(Hardened Derivation,i ∈ [2^31, 2^32 - 1])

I = HMAC-SHA512(Key = c_par, Data = 0x00 || ser_256(k_par) || ser_32(i))
其中:
- 0x00: 32位元組的0x00前綴
- ser_256(k_par): 父私鑰(256位)
- k_i = parse_256(I_L)
- c_i = I_R

推導函數的 Python 實現

import hashlib
import hmac
import ecdsa
import binascii
from ecdsa import SigningKey, SECP256k1

def hmac_sha512(key, data):
    """HMAC-SHA512 計算"""
    return hmac.new(key, data, hashlib.sha512).digest()

def point(pk):
    """將私鑰轉換為公鑰點"""
    signing_key = SigningKey.from_string(
        binascii.unhexlify(pk), curve=SECP256k1
    )
    verifying_key = signing_key.get_verifying_key()
    # 返回壓縮格式公鑰(33位元組)
    x = verifying_key.pubkey.point.x()
    y = verifying_key.pubkey.point.y()
    if y % 2 == 0:
        return binascii.hexlify(bytes([0x02]) + x.to_bytes(32, 'big')).decode()
    else:
        return binascii.hexlify(bytes([0x03]) + x.to_bytes(32, 'big')).decode()

def ser_32(n):
    """將32位元整數序列化為大端序32位元組"""
    return n.to_bytes(32, 'big')

def parse_256(s):
    """將32位元組轉換為256位整數(模運算)"""
    return int.from_bytes(s, 'big') % ecdsa.SECP256k1.order

def derive_child_private_key(parent_key, parent_chain_code, index, hardened=False):
    """
    從父私鑰派生子私鑰
    
    參數:
    - parent_key: 父私鑰(64位元組十六進位字串)
    - parent_chain_code: 父鏈碼(64位元組十六進位字串)
    - index: 子金鑰索引(0 到 2^32-1)
    - hardened: 是否為強化派生
    
    返回:(子私鑰, 子鏈碼)
    """
    parent_key_bytes = binascii.unhexlify(parent_key)
    parent_chain_bytes = binascii.unhexlify(parent_chain_code)
    
    if hardened:
        # 強化派生:使用 0x00 || 私鑰 || 索引
        data = b'\x00' + parent_key_bytes + ser_32(index)
    else:
        # 普通派生:使用 公鑰 || 索引
        pubkey = point(parent_key)
        data = binascii.unhexlify(pubkey) + ser_32(index)
    
    # HMAC-SHA512
    I = hmac_sha512(parent_chain_bytes, data)
    I_L = I[:32]  # 子私鑰
    I_R = I[32:]  # 子鏈碼
    
    # 計算子私鑰:k_i = parse_256(I_L) + k_par (mod n)
    il_int = parse_256(I_L)
    parent_int = parse_256(parent_key_bytes)
    child_int = (il_int + parent_int) % ecdsa.SECP256k1.order
    
    child_key = child_int.to_bytes(32, 'big')
    child_chain_code = I_R
    
    return binascii.hexlify(child_key).decode(), binascii.hexlify(child_chain_code).decode()

Master Key 生成

從 Entropy 生成 Master Key 的完整流程:

def generate_master_key(entropy):
    """
    從 Entropy 生成 Master Key
    
    參數:
    - entropy: 128/256/512 位元的隨機種子
    
    返回:(master_private_key, master_chain_code)
    """
    # 固定字串 "Bitcoin seed"
    I = hmac_sha512(b'Bitcoin seed', entropy)
    master_key = I[:32]
    master_chain_code = I[32:]
    
    return binascii.hexlify(master_key).decode(), \
           binascii.hexlify(master_chain_code).decode()

def generate_random_entropy(bits=256):
    """生成隨機 Entropy"""
    import secrets
    num_bytes = bits // 8
    return secrets.token_bytes(num_bytes)

BIP-39:助記詞規範

技術原理

BIP-39 定義了從隨機 Entropy 到人類可讀助記詞的轉換標準。根據 BIP-39 原始規範:

Entropy 長度與助記詞數量對應關係

Entropy (bits)Checksum (bits)Total (bits)Mnemonic (words)
128413212
160516515
192619818
224723121
256826424

助記詞詞庫:BIP-39 定義了 2048 個常用英文單詞。根據原始規範,所有助記詞均為精心挑選的低重複率單詞列表,確保:

Entropy 到助記詞的轉換

# BIP-39 英語詞庫(完整列表有 2048 個單詞)
# 以下為示例片段
WORDLIST = [
    "abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract",
    # ... 共 2048 個單詞
    "zoo", "zooKeeper", "zoom", "zoologist", "zygote"
]

def entropy_to_mnemonic(entropy):
    """
    將 Entropy 轉換為助記詞
    """
    # 計算 SHA256 校驗和
    entropy_hash = hashlib.sha256(entropy).hexdigest()
    
    # Entropy 位元數
    entropy_bits = len(entropy) * 8
    
    # Checksum 位元數 = entropy_bits / 32
    checksum_bits = entropy_bits // 32
    
    # 總位元數
    total_bits = entropy_bits + checksum_bits
    
    # 將校驗和的前 checksum_bits 位附加到 Entropy
    checksum_bitstring = bin(int(entropy_hash, 16))[2:].zfill(256)[:checksum_bits]
    bitstring = bin(int.from_bytes(entropy, 'big'))[2:].zfill(entropy_bits) + checksum_bitstring
    
    # 每 11 位元為一組,轉換為單詞索引
    words = []
    for i in range(0, len(bitstring), 11):
        bits = bitstring[i:i+11]
        index = int(bits, 2)
        words.append(WORDLIST[index])
    
    return ' '.join(words)

def mnemonic_to_entropy(mnemonic):
    """
    將助記詞轉換為 Entropy
    """
    words = mnemonic.split()
    
    # 驗證助記詞長度
    if len(words) not in [12, 15, 18, 21, 24]:
        raise ValueError("Invalid mnemonic length")
    
    # 將單詞轉換為位元串
    bitstring = ''
    for word in words:
        index = WORDLIST.index(word)
        bitstring += bin(index)[2:].zfill(11)
    
    # 提取 Entropy 和 Checksum
    entropy_bits = (len(words) * 11) - (len(words) // 3)
    entropy_bytes = entropy_bits // 8
    
    entropy = int(bitstring[:entropy_bits], 2).to_bytes(entropy_bytes, 'big')
    checksum = bitstring[entropy_bits:]
    
    # 驗證校驗和
    expected_checksum = bin(int(hashlib.sha256(entropy).hexdigest(), 16))[2:].zfill(256)[:len(checksum)]
    if checksum != expected_checksum:
        raise ValueError("Invalid checksum")
    
    return entropy

助記詞到 Seed 的轉換

def mnemonic_to_seed(mnemonic, passphrase=''):
    """
    將助記詞轉換為 Seed(使用 PBKDF2)
    BIP-39 定義:
    - password: UTF-8 NFKD 編碼的助記詞
    - salt: UTF-8 NFKD 編碼的 "mnemonic" + passphrase
    - iterations: 2048
    - key length: 512 bits (64 bytes)
    - algorithm: HMAC-SHA512
    """
    import unicodedata
    
    # NFKD 正規化
    mnemonic = unicodedata.normalize('NFKD', mnemonic)
    passphrase = unicodedata.normalize('NFKD', passphrase or '')
    
    # UTF-8 編碼
    password = mnemonic.encode('utf-8')
    salt = ('mnemonic' + passphrase).encode('utf-8')
    
    # PBKDF2
    seed = hashlib.pbkdf2_hmac(
        'sha512', password, salt, 2048, dklen=64
    )
    
    return binascii.hexlify(seed).decode()

# 完整範例
def generate_hd_wallet():
    """
    生成完整的 HD Wallet
    返回:助記詞, Seed, Master Key, Master Chain Code
    """
    # 1. 生成 256 位元 Entropy
    entropy = generate_random_entropy(256)
    
    # 2. 轉換為 24 單詞助記詞
    mnemonic = entropy_to_mnemonic(entropy)
    
    # 3. 從助記詞生成 Seed
    seed = mnemonic_to_seed(mnemonic)
    
    # 4. 從 Seed 生成 Master Key
    entropy_bytes = binascii.unhexlify(seed)
    master_key, master_chain_code = generate_master_key(entropy_bytes)
    
    return {
        'mnemonic': mnemonic,
        'seed': seed,
        'master_key': master_key,
        'master_chain_code': master_chain_code
    }

# 使用範例
wallet = generate_hd_wallet()
print(f"24-Word Mnemonic: {wallet['mnemonic']}")
print(f"Seed (512 bits): {wallet['seed']}")
print(f"Master Private Key: {wallet['master_key']}")
print(f"Master Chain Code: {wallet['master_chain_code']}")

BIP-44:多帳戶錢包結構

路徑層級定義

BIP-44 定義了 HD Wallet 的標準路徑結構(path levels)。根據原始規範:

m / purpose' / coin_type' / account' / change / address_index

各層級含義

常見硬幣類型

硬幣coin_type
Bitcoin0'
Bitcoin Testnet1'
Litecoin2'
Ethereum60'

BIP-44 路徑推導實現

# BIP-44 定義的常量
COIN_BITCOIN = 0
COIN_TESTNET = 1

PURPOSE_BIP44 = 0x8000002C  # 44' = 0x8000002C = 2147483692

def bip44_path_to_indices(path):
    """
    將 BIP-44 路徑轉換為派生子金鑰的索引列表
    例如:"m/44'/1'/0'/0/0" -> [2147483692, 2147483693, 2147483648, 0, 0]
    """
    parts = path.strip().split('/')
    if parts[0] != 'm':
        raise ValueError("Path must start with 'm'")
    
    indices = []
    for part in parts[1:]:
        hardened = part.endswith("'")
        index = int(part.rstrip("'"))
        if hardened:
            index += 0x80000000  # 強化派生標誌
        indices.append(index)
    
    return indices

def derive_path(master_key, master_chain_code, path):
    """
    從 Master Key 沿路徑派生子金鑰
    """
    indices = bip44_path_to_indices(path)
    
    current_key = master_key
    current_chain = master_chain_code
    
    for index in indices:
        hardened = index >= 0x80000000
        actual_index = index - 0x80000000 if hardened else index
        current_key, current_chain = derive_child_private_key(
            current_key, current_chain, actual_index, hardened
        )
    
    return current_key, current_chain

# 完整的 BIP-44 路徑派生活動
def bip44_derive_addresses(master_key, master_chain_code, count=10):
    """
    派生前 N 個接收地址
    BIP-44: m/44'/1'/0'/0/address_index
    """
    addresses = []
    
    for i in range(count):
        # 派生子私鑰:m/44'/1'/0'/0/i
        path = f"m/44'/1'/0'/0/{i}"
        child_key, child_chain = derive_path(master_key, master_chain_code, path)
        
        # 從私鑰計算公鑰
        child_pubkey = point(child_key)
        
        # 計算 P2PKH 地址(testnet)
        address = pubkey_to_p2pkh_address(child_pubkey, testnet=True)
        
        addresses.append({
            'path': path,
            'private_key': child_key,
            'public_key': child_pubkey,
            'address': address
        })
    
    return addresses

def pubkey_to_p2pkh_address(pubkey_hex, testnet=False):
    """
    將公鑰轉換為 P2PKH 地址
    """
    import base58
    
    # SHA-256
    h = hashlib.sha256(binascii.unhexlify(pubkey_hex)).digest()
    
    # RIPEMD-160
    r = hashlib.new('ripemd160')
    r.update(h)
    hash160 = r.hexdigest()
    
    # 版本位元組
    version = '6f' if testnet else '00'  # testnet = 0x6f, mainnet = 0x00
    
    # 校驗和
    payload = version + hash160
    checksum = hashlib.sha256(hashlib.sha256(
        binascii.unhexlify(payload)
    ).digest()).hexdigest()[:8]
    
    # Base58Check 編碼
    data = binascii.unhexlify(payload + checksum)
    return base58_encode(data)

def base58_encode(data):
    """Base58 編碼"""
    alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
    leading_zeros = len(data) - len(data.lstrip(b'\x00'))
    num = int.from_bytes(data, 'big')
    result = []
    while num > 0:
        num, rem = divmod(num, 58)
        result.append(alphabet[rem])
    return 'n' * leading_zeros + ''.join(reversed(result))

# 使用範例
wallet = generate_hd_wallet()
addresses = bip44_derive_addresses(
    wallet['master_key'],
    wallet['master_chain_code'],
    count=5
)

print("=" * 80)
print("BIP-44 Testnet Addresses (m/44'/1'/0'/0/i)")
print("=" * 80)

for addr in addresses:
    print(f"\nPath: {addr['path']}")
    print(f"Address: {addr['address']}")
    print(f"Public Key: {addr['public_key']}")

完整實作:比特幣 HD Wallet 類

以下是 HD Wallet 的完整 Python 類實現:

class HDWallet:
    """完整的 HD Wallet 實現"""
    
    def __init__(self, mnemonic=None, seed=None, passphrase=''):
        """
        初始化 HD Wallet
        
        參數:
        - mnemonic: 24 或 12 單詞助記詞
        - seed: 512 位元 Seed(可選)
        - passphrase: 可選的密碼短語
        """
        if mnemonic:
            self.mnemonic = mnemonic
            self.seed_hex = mnemonic_to_seed(mnemonic, passphrase)
        elif seed:
            self.seed_hex = seed
            self.mnemonic = None
        else:
            # 生成新的錢包
            entropy = generate_random_entropy(256)
            self.mnemonic = entropy_to_mnemonic(entropy)
            self.seed_hex = mnemonic_to_seed(self.mnemonic, passphrase)
        
        # 從 Seed 生成 Master Key
        entropy_bytes = binascii.unhexlify(self.seed_hex)
        self.master_key, self.master_chain_code = generate_master_key(entropy_bytes)
        
        # 錢包元數據
        self.accounts = {}
    
    def derive_private_key(self, path):
        """沿指定路徑派生子私鑰"""
        return derive_path(self.master_key, self.master_chain_code, path)[0]
    
    def derive_public_key(self, path):
        """沿指定路徑派生子公鑰"""
        private_key = self.derive_private_key(path)
        return point(private_key)
    
    def derive_address(self, path, address_type='p2pkh', testnet=True):
        """
        派生子地址
        
        地址類型:
        - 'p2pkh': Legacy 地址(1 或 m/n)
        - 'p2sh-p2wpkh': 嵌套隔離見證(3 或 2)
        - 'p2wpkh': 原生隔離見證(bc1q 或 tb1q)
        """
        public_key = self.derive_public_key(path)
        
        if address_type == 'p2pkh':
            return pubkey_to_p2pkh_address(public_key, testnet)
        elif address_type == 'p2sh-p2wpkh':
            return pubkey_to_p2sh_p2wpkh_address(public_key, testnet)
        elif address_type == 'p2wpkh':
            return pubkey_to_p2wpkh_address(public_key, testnet)
        else:
            raise ValueError(f"Unknown address type: {address_type}")
    
    def get_receive_addresses(self, account=0, change=0, count=10, address_type='p2pkh', testnet=True):
        """派生成年接收地址"""
        addresses = []
        for i in range(count):
            path = f"m/44'/1'/{account}'/{change}/{i}"
            addr = self.derive_address(path, address_type, testnet)
            addresses.append({
                'path': path,
                'address': addr,
                'private_key': self.derive_private_key(path)
            })
        return addresses
    
    def get_change_addresses(self, account=0, count=10, address_type='p2pkh', testnet=True):
        """派生成年找零地址"""
        return self.get_receive_addresses(account, 1, count, address_type, testnet)
    
    def to_dict(self):
        """導出錢包資訊"""
        return {
            'mnemonic': self.mnemonic,
            'seed': self.seed_hex,
            'master_key': self.master_key,
            'master_chain_code': self.master_chain_code
        }

# 輔助函數:P2SH-P2WPKH 和 P2WPKH 地址

def pubkey_to_p2sh_p2wpkh_address(pubkey_hex, testnet=False):
    """生成 P2SH-P2WPKH(嵌套隔離見證)地址"""
    import base58
    
    # 1. 計算 P2WPKH 腳本哈希
    h = hashlib.sha256(binascii.unhexlify(pubkey_hex)).digest()
    r = hashlib.new('ripemd160')
    r.update(h)
    witness_program = r.hexdigest()
    
    # 2. 構建 P2WSH 腳本
    witness_script = '0020' + witness_program  # OP_0 <20 bytes>
    
    # 3. 計算腳本哈希
    script_hash = hashlib.sha256(binascii.unhexlify(witness_script)).digest()
    r2 = hashlib.new('ripemd160')
    r2.update(script_hash)
    script_hash160 = r2.hexdigest()
    
    # 4. P2SH 地址
    version = 'c4' if testnet else '05'
    payload = version + script_hash160
    checksum = hashlib.sha256(hashlib.sha256(
        binascii.unhexlify(payload)
    ).digest()).hexdigest()[:8]
    
    return base58_encode(binascii.unhexlify(payload + checksum))

def pubkey_to_p2wpkh_address(pubkey_hex, testnet=False):
    """生成原生隔離見證(P2WPKH)地址"""
    import base58
    
    # 1. 計算 P2WPKH 腳本哈希
    h = hashlib.sha256(binascii.unhexlify(pubkey_hex)).digest()
    r = hashlib.new('ripemd160')
    r.update(h)
    witness_program = r.hexdigest()
    
    # 2. Bech32 編碼
    return bech32_encode(pubkey_hex, witness_program, testnet)

def bech32_encode(prefix, data, testnet=False):
    """Bech32 編碼(用於隔離見證地址)"""
    # Bech32 字符集
    charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
    
    # Bech32m(用於版本 0+)
    hrp = "tb" if testnet else "bc"
    
    # 轉換為 5 位元基
    combined = convertbits(binascii.unhexlify('00' + data), 8, 5)
    
    # 計算校驗和
    values = [ord(c) >> 5 for c in hrp] + [0] + [ord(c) & 31 for c in hrp] + combined
    polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1
    checksum = [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
    
    return hrp + '1' + ''.join([charset[v] for v in combined + checksum])

def convertbits(data, frombits, tobits, pad=True):
    """將位元組轉換為不同的基"""
    acc = 0
    bits = 0
    ret = []
    maxv = (1 << tobits) - 1
    max_acc = (1 << (frombits + tobits - 1)) - 1
    
    for value in data:
        if value < 0 or (value >> frombits):
            return None
        acc = ((acc << frombits) | value) & max_acc
        bits += frombits
        while bits >= tobits:
            bits -= tobits
            ret.append((acc >> bits) & maxv)
    
    if pad:
        if bits:
            ret.append((acc << (tobits - bits)) & maxv)
    elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
        return None
    
    return ret

def bech32_polymod(values):
    """Bech32 校驗和多項式"""
    generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
    chk = 1
    for value in values:
        top = chk >> 25
        chk = (chk & 0x1ffffff) << 5 ^ value
        for i in range(5):
            chk ^= generator[i] if ((top >> i) & 1) else 0
    return chk

testnet 驗證完整指南

使用 testnet 驗證 HD Wallet

以下是使用 testnet 驗證 HD Wallet 的完整流程:

import requests

def test_wallet_on_testnet(hd_wallet):
    """
    在 testnet 上測試 HD Wallet
    
    流程:
    1. 從水龍頭取得 testnet 比特幣
    2. 接收比特幣到錢包地址
    3. 將比特幣轉移到另一地址
    """
    print("=" * 80)
    print("Testnet HD Wallet 驗證")
    print("=" * 80)
    
    # 1. 生成 5 個接收地址
    addresses = hd_wallet.get_receive_addresses(
        account=0, 
        change=0, 
        count=5,
        address_type='p2wpkh',
        testnet=True
    )
    
    print("\n生成的 Testnet 地址(原生隔離見證):")
    for i, addr in enumerate(addresses):
        print(f"  [{i}] {addr['address']}")
    
    # 2. 從水龍頭領取測試比特幣
    test_address = addresses[0]['address']
    print(f"\n請從以下水龍頭領取測試比特幣到此地址:")
    print(f"  {test_address}")
    print(f"\n水龍頭連結:")
    print(f"  - Blockstream: https://blockstream.info/testnet/faucet")
    print(f"  - Bitcoin Faucet: https://bitcoinfaucet.uo1.net/")
    
    # 3. 查詢餘額
    balance = get_testnet_balance(test_address)
    print(f"\n當前餘額:{balance} satoshis")
    
    # 4. 構造並廣播交易
    if balance > 1000:
        # 發送 1000 satoshis 到另一地址
        dest_address = addresses[1]['address']
        txid = send_testnet_transaction(
            private_key=addresses[0]['private_key'],
            from_address=test_address,
            to_address=dest_address,
            amount_sats=1000
        )
        print(f"\n交易已廣播:{txid}")
    else:
        print("\n請先領取測試比特幣")
    
    return addresses

def get_testnet_balance(address):
    """查詢 testnet 地址餘額"""
    url = f"https://blockstream.info/testnet/api/address/{address}"
    try:
        response = requests.get(url, timeout=10)
        if response.status_code == 200:
            data = response.json()
            return data['chain_stats']['funded_txo_sum'] - data['chain_stats']['spent_txo_sum']
    except:
        pass
    return 0

def send_testnet_transaction(private_key, from_address, to_address, amount_sats):
    """在 testnet 上發送交易"""
    # 此函數需要完整的交易構造和簽名邏輯
    # 參見 bitcoin-script-basics.md 中的交易建構範例
    pass

# 完整範例執行
def main():
    # 使用標準 BIP-39 助記詞創建錢包
    # 警告:此助記詞僅用於測試,勿用於實際資金
    test_mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
    
    print("創建 HD Wallet...")
    wallet = HDWallet(mnemonic=test_mnemonic)
    
    print(f"助記詞: {wallet.mnemonic}")
    print(f"Master Key: {wallet.master_key[:16]}...")
    
    # 派生成年 BIP-44 地址
    print("\n派生成年接收地址 (m/44'/1'/0'/0/i):")
    addresses = wallet.get_receive_addresses(
        account=0,
        change=0,
        count=3,
        address_type='p2wpkh',
        testnet=True
    )
    
    for addr in addresses:
        print(f"  {addr['path']}: {addr['address']}")
    
    # testnet 驗證
    test_wallet_on_testnet(wallet)

if __name__ == "__main__":
    main()

測試指令

在 testnet 上驗證 HD Wallet 的完整指令:

# 1. 創建測試錢包並導出地址
python hd_wallet.py

# 2. 從水龍頭領取測試比特幣
# 將上面生成的 tb1q 地址粘貼到水龍頭表單

# 3. 使用 blockstream 查詢餘額
curl https://blockstream.info/testnet/api/address/tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx

# 4. 使用 bitcoin-cli 驗證(需運行 testnet 節點)
bitcoin-cli -testnet getnewaddress "" "bech32"
bitcoin-cli -testnet generate 1
bitcoin-cli -testnet listunspent

安全性最佳實踐

助記詞保護

備份策略

密碼短語(Passphrase)

def create_secure_wallet():
    """創建安全的 HD Wallet"""
    import secrets
    
    # 生成高 Entropy 的助記詞
    entropy = secrets.token_bytes(32)  # 256 位元
    mnemonic = entropy_to_mnemonic(entropy)
    
    # 用戶設置密碼短語
    passphrase = secrets.token_urlsafe(16)
    
    # 創建錢包
    wallet = HDWallet(mnemonic=mnemonic, passphrase=passphrase)
    
    return wallet, passphrase

# 警告:passphrase 必須牢記!
wallet, passphrase = create_secure_wallet()
print(f"Passphrase (請牢記!): {passphrase}")

金鑰管理策略

冷存儲錢包

def create_cold_storage():
    """
    創建冷存儲錢包
    使用網路隔離設備生成,之後永不連網
    """
    # 在完全離線的環境中執行
    mnemonic = generate_hd_wallet()['mnemonic']
    
    # 僅將 BIP-44 派生的最後一個地址索引導出
    # 其餘資訊(助記詞、金鑰)永不接觸網路
    
    return {
        'mnemonic_hash': hashlib.sha256(mnemonic.encode()).hexdigest(),
        'purpose': 44,
        'coin': 0,
        'account': 0
    }

多籤錢包

def create_multisig_wallet(threshold, total_signers):
    """
    創建多籤 HD Wallet
    每個參與者持有自己的 HD Wallet
    通過 M-of-N 派生存款地址
    """
    wallets = [HDWallet() for _ in range(total_signers)]
    
    # 收集所有參與者的第一個公鑰
    pubkeys = [wallet.derive_public_key("m/44'/0'/0'/0/0") for wallet in wallets]
    
    # 生成多籤 P2SH 地址
    multisig_script = create_multisig_script(pubkeys, threshold, total_signers)
    
    return {
        'wallets': wallets,
        'pubkeys': pubkeys,
        'threshold': threshold,
        'multisig_address': script_to_p2sh_address(multisig_script)
    }

常見問題解答

Q: 為什麼要使用強化派生(hardened derivation)?

A: 強化派生需要父私鑰才能派生子金鑰,而普通派生可以從父公鑰派生子公鑰。如果使用普通派生於高層級分支,一旦子公鑰被洩露,攻擊者可能通過暴力搜索父公鑰來推導整個錢包。建議對 purpose'coin_type'account' 使用強化派生。

Q: BIP-39 助記詞是否安全?

A: 12 單詞助記詞提供 128 位元 Entropy(2^128 種可能性),24 單詞提供 256 位元。考慮到硬體錢包的迭代速度,BIP-39 助記詞在可預見的未來是安全的。但請確保使用真正的隨機種子,而非人為選擇的助記詞。

Q: 錢包升級時如何保持相容性?

A: 只要遵循 BIP-44 標準,錢包可以在不同軟體之間完全相容。從助記詞可以恢復所有派生的金鑰。建議使用經過審計的開源錢包軟體。

結論

HD Wallet 是比特幣生態系統中最重要的技術創新之一。通過 BIP-32/39/44 標準,用戶可以安全、便捷地管理比特幣金鑰。本指南提供了從理論到實作的完整解説,包括助記詞生成、金鑰派生、地址計算和 testnet 驗證。建議讀者在 testnet 上反覆練習,熟悉完整流程後再在 mainnet 上操作。

如需進一步學習,建議閱讀 BIP-32、BIP-39 和 BIP-44 原始規範,並研究比特幣核心錢包的實現代碼。掌握 HD Wallet 原理,是深入理解比特幣錢包安全的必要前提。

參考文獻

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!