進階
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
- 驗證地址類型:確保地址與預期網路匹配
- 不要重複使用地址:每次接收使用新地址
- 備份助記詞:地址可以重新派生
相關資源
已複製連結