跳至主要內容
進階

Address Types

比特幣地址格式:Base58、Bech32 編碼和各種地址類型詳解

18 分鐘

概述

比特幣地址是用戶接收資金的標識符。隨著協議的演進, 出現了多種地址格式,每種都有不同的特性和用途。 理解這些格式對於開發比特幣應用至關重要。

地址類型總覽

類型 前綴 編碼 腳本類型 引入時間
P2PKH 1 Base58Check Pay to Public Key Hash 2009
P2SH 3 Base58Check Pay to Script Hash 2012 (BIP-16)
P2WPKH bc1q Bech32 Native SegWit 2017 (BIP-141)
P2WSH bc1q Bech32 SegWit Script 2017 (BIP-141)
P2TR bc1p Bech32m Taproot 2021 (BIP-341)

Base58Check 編碼

Base58 是為比特幣特別設計的編碼,避免了容易混淆的字符(0, O, I, l)。

字符集

Base58 字符集:
123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

排除的字符:
- 0 (零) 和 O (大寫 O) - 容易混淆
- I (大寫 i) 和 l (小寫 L) - 容易混淆

編碼流程

Base58Check 編碼:
1. 添加版本前綴
   - 0x00: P2PKH mainnet
   - 0x05: P2SH mainnet
   - 0x6f: P2PKH testnet
   - 0xc4: P2SH testnet

2. 計算校驗碼
   checksum = SHA256(SHA256(version + payload))[:4]

3. Base58 編碼
   address = Base58(version + payload + checksum)

範例:
payload = HASH160(公鑰) = 20 bytes
version = 0x00
checksum = SHA256(SHA256(0x00 + payload))[:4]
address = Base58(0x00 + payload + checksum)
結果: 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2

P2PKH (Pay to Public Key Hash)

最經典的比特幣地址格式,以數字 1 開頭。

結構

公鑰 → SHA256 → RIPEMD160 → Base58Check

scriptPubKey:
OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG

地址長度: 25-34 字符
範例: 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2

TypeScript 實作

import * as crypto from 'crypto';
import bs58check from 'bs58check';

function hash160(buffer: Buffer): Buffer {
  const sha256 = crypto.createHash('sha256').update(buffer).digest();
  return crypto.createHash('ripemd160').update(sha256).digest();
}

function publicKeyToP2PKH(publicKey: Buffer, network: 'mainnet' | 'testnet' = 'mainnet'): string {
  const pubKeyHash = hash160(publicKey);

  // 版本前綴
  const version = network === 'mainnet' ? 0x00 : 0x6f;

  // 添加版本並編碼
  const payload = Buffer.concat([Buffer.from([version]), pubKeyHash]);

  return bs58check.encode(payload);
}

// 使用範例
const pubKey = Buffer.from('02...', 'hex');
const address = publicKeyToP2PKH(pubKey);
console.log(address); // 1...

P2SH (Pay to Script Hash)

允許支付到腳本雜湊,支持多簽和複雜條件,以數字 3 開頭。

結構

redeemScript → SHA256 → RIPEMD160 → Base58Check

scriptPubKey:
OP_HASH160 <scriptHash> OP_EQUAL

地址長度: 34 字符
範例: 3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy

常見用途

  • 多簽錢包 (2-of-3, 3-of-5 等)
  • P2SH-P2WPKH(兼容 SegWit)
  • 時間鎖合約

Bech32 編碼

Bech32(BIP-173)是為 SegWit 設計的新編碼格式, 提供更好的錯誤檢測和全小寫支持。

字符集

Bech32 字符集:
qpzry9x8gf2tvdw0s3jn54khce6mua7l

結構:
bc1q...  (mainnet P2WPKH/P2WSH)
tb1q...  (testnet)
bc1p...  (mainnet P2TR)
tb1p...  (testnet P2TR)

組成部分

Bech32 地址結構:
hrp + "1" + data + checksum

hrp (Human Readable Part):
- bc: Bitcoin mainnet
- tb: Bitcoin testnet/signet
- bcrt: Bitcoin regtest

data:
- witness version (0-16)
- witness program

checksum:
- 6 字符 BCH 校驗碼

P2WPKH (Native SegWit)

原生 SegWit 地址,以 bc1q 開頭,交易更小、手續費更低。

結構

scriptPubKey:
OP_0 <20-byte-pubKeyHash>

witness:
<signature> <publicKey>

地址長度: 42 字符 (mainnet)
範例: bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq

TypeScript 實作

import { bech32 } from 'bech32';

function publicKeyToP2WPKH(
  publicKey: Buffer,
  network: 'mainnet' | 'testnet' = 'mainnet'
): string {
  const pubKeyHash = hash160(publicKey);

  // witness version 0
  const words = [0, ...bech32.toWords(pubKeyHash)];

  const hrp = network === 'mainnet' ? 'bc' : 'tb';

  return bech32.encode(hrp, words);
}

// 解碼 Bech32 地址
function decodeBech32(address: string): {
  version: number;
  program: Buffer;
} {
  const { prefix, words } = bech32.decode(address);

  const version = words[0];
  const program = Buffer.from(bech32.fromWords(words.slice(1)));

  return { version, program };
}

P2WSH (SegWit Script)

SegWit 版本的 P2SH,用於複雜腳本。

scriptPubKey:
OP_0 <32-byte-scriptHash>

scriptHash = SHA256(witnessScript)

地址長度: 62 字符 (mainnet)
範例: bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3

P2TR (Taproot)

Taproot 地址(BIP-341),以 bc1p 開頭,使用 Bech32m 編碼。 支持 Schnorr 簽名和 MAST。

Bech32m

Bech32m 是 Bech32 的改進版本(BIP-350):
- 修復了 Bech32 的一個缺陷
- 用於 witness version 1+ 的地址
- witness version 0 仍使用 Bech32

結構

scriptPubKey:
OP_1 <32-byte-tweaked-pubkey>

tweaked_pubkey = internal_pubkey + H(internal_pubkey || merkle_root) × G

地址長度: 62 字符 (mainnet)
範例: bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297

TypeScript 實作

import { bech32m } from 'bech32';

function internalKeyToP2TR(
  internalPubKey: Buffer,  // 32 bytes x-only
  network: 'mainnet' | 'testnet' = 'mainnet'
): string {
  // 計算 tweaked public key
  const tweakedKey = tweakPublicKey(internalPubKey);

  // witness version 1
  const words = [1, ...bech32m.toWords(tweakedKey)];

  const hrp = network === 'mainnet' ? 'bc' : 'tb';

  return bech32m.encode(hrp, words);
}

function tweakPublicKey(internalKey: Buffer, merkleRoot?: Buffer): Buffer {
  // t = H_TapTweak(P || r)
  const data = merkleRoot
    ? Buffer.concat([internalKey, merkleRoot])
    : internalKey;

  const tweak = taggedHash('TapTweak', data);

  // Q = P + t×G
  // 需要橢圓曲線運算
  return computeTweakedKey(internalKey, tweak);
}

function taggedHash(tag: string, data: Buffer): Buffer {
  const tagHash = crypto.createHash('sha256').update(tag).digest();
  return crypto
    .createHash('sha256')
    .update(Buffer.concat([tagHash, tagHash, data]))
    .digest();
}

地址驗證

通用驗證

import * as bitcoin from 'bitcoinjs-lib';

interface AddressInfo {
  valid: boolean;
  type?: 'p2pkh' | 'p2sh' | 'p2wpkh' | 'p2wsh' | 'p2tr';
  network?: 'mainnet' | 'testnet';
  scriptPubKey?: Buffer;
}

function validateAddress(address: string): AddressInfo {
  try {
    // 嘗試 mainnet
    const outputScript = bitcoin.address.toOutputScript(
      address,
      bitcoin.networks.bitcoin
    );

    return {
      valid: true,
      type: getAddressType(address),
      network: 'mainnet',
      scriptPubKey: outputScript,
    };
  } catch {
    try {
      // 嘗試 testnet
      const outputScript = bitcoin.address.toOutputScript(
        address,
        bitcoin.networks.testnet
      );

      return {
        valid: true,
        type: getAddressType(address),
        network: 'testnet',
        scriptPubKey: outputScript,
      };
    } catch {
      return { valid: false };
    }
  }
}

function getAddressType(address: string): AddressInfo['type'] {
  if (address.startsWith('1')) return 'p2pkh';
  if (address.startsWith('3')) return 'p2sh';
  if (address.startsWith('bc1q') || address.startsWith('tb1q')) {
    return address.length === 42 ? 'p2wpkh' : 'p2wsh';
  }
  if (address.startsWith('bc1p') || address.startsWith('tb1p')) return 'p2tr';
  return undefined;
}

地址轉換

從腳本推導地址

import * as bitcoin from 'bitcoinjs-lib';

// 從 scriptPubKey 獲取地址
function scriptToAddress(
  script: Buffer,
  network: bitcoin.Network = bitcoin.networks.bitcoin
): string | null {
  try {
    return bitcoin.address.fromOutputScript(script, network);
  } catch {
    return null;
  }
}

// 從地址獲取 scriptPubKey
function addressToScript(
  address: string,
  network: bitcoin.Network = bitcoin.networks.bitcoin
): Buffer {
  return bitcoin.address.toOutputScript(address, network);
}

手續費比較

地址類型 輸入大小 (vBytes) 相對成本
P2PKH ~148 100%
P2SH-P2WPKH ~91 ~61%
P2WPKH ~68 ~46%
P2TR (key path) ~57.5 ~39%

建議: 優先使用 P2TR 或 P2WPKH 地址,可節省約 50-60% 的交易手續費。

最佳實踐

  • 新應用使用 P2TR:最低手續費,最佳隱私
  • 兼容性需求用 P2WPKH:廣泛支持的 SegWit
  • 驗證地址類型:確保地址與預期網路匹配
  • 不要重複使用地址:每次接收使用新地址
  • 備份助記詞:地址可以重新派生

相關資源

已複製連結
已複製到剪貼簿