進階
SegWit
隔離見證:BIP-141/143/144 解決交易可塑性並提升區塊容量
18 分鐘
概述
隔離見證(Segregated Witness,簡稱 SegWit)是 2017 年 8 月啟用的軟分叉升級。 它將簽名數據(witness)從交易結構中分離出來,解決了交易可塑性問題, 並有效提升了區塊容量。
啟用區塊: SegWit 在區塊高度 481,824(2017 年 8 月 24 日)啟用。 目前約 80% 的比特幣交易使用 SegWit。
解決的問題
交易可塑性
交易可塑性問題:
傳統交易 TXID 計算:
TXID = SHA256(SHA256(version + inputs + outputs + locktime))
問題: 簽名也在 inputs 中!
┌─────────────────────────────────────┐
│ Input: │
│ ├── prev_txid │
│ ├── prev_vout │
│ ├── scriptSig (包含簽名) ← 問題所在 │
│ └── sequence │
└─────────────────────────────────────┘
ECDSA 簽名有多種有效表示:
- (r, s) 和 (r, -s mod n) 都是有效簽名
- 可以改變簽名格式而不使其無效
結果:
1. 第三方可以修改 TXID 而不使交易無效
2. 破壞依賴 TXID 的鏈式交易
3. 影響 Lightning Network 等二層協議 區塊容量限制
傳統限制:
- 區塊大小上限: 1 MB (1,000,000 bytes)
- 簽名數據佔交易約 60%
- 實際交易吞吐量受限
SegWit 解決方案:
- 引入「區塊重量」概念
- 見證數據享有折扣
- 有效區塊容量提升至 ~4 MB (理論最大值)
- 實際平均 ~2 MB 交易結構變化
傳統交易 vs SegWit 交易
傳統交易結構:
┌─────────────────────────────────────┐
│ version (4 bytes) │
├─────────────────────────────────────┤
│ input count (varint) │
├─────────────────────────────────────┤
│ inputs[] (包含 scriptSig) │
├─────────────────────────────────────┤
│ output count (varint) │
├─────────────────────────────────────┤
│ outputs[] │
├─────────────────────────────────────┤
│ locktime (4 bytes) │
└─────────────────────────────────────┘
SegWit 交易結構:
┌─────────────────────────────────────┐
│ version (4 bytes) │
├─────────────────────────────────────┤
│ marker (0x00) ← 新增 │
├─────────────────────────────────────┤
│ flag (0x01) ← 新增 │
├─────────────────────────────────────┤
│ input count (varint) │
├─────────────────────────────────────┤
│ inputs[] (scriptSig 為空) │
├─────────────────────────────────────┤
│ output count (varint) │
├─────────────────────────────────────┤
│ outputs[] │
├─────────────────────────────────────┤
│ witness[] (簽名數據) ← 新增 │
├─────────────────────────────────────┤
│ locktime (4 bytes) │
└─────────────────────────────────────┘ TXID 與 WTXID
TXID (Transaction ID):
- 計算時排除 witness 數據
- 向後兼容舊節點
- 不受簽名修改影響
WTXID (Witness Transaction ID):
- 包含完整交易(含 witness)
- 用於區塊 witness 承諾
計算方式:
TXID = SHA256(SHA256(
version + inputs + outputs + locktime
))
WTXID = SHA256(SHA256(
version + marker + flag + inputs + outputs + witness + locktime
)) 區塊重量
Weight Units (WU)
區塊重量計算:
- 非見證數據: 1 byte = 4 WU
- 見證數據: 1 byte = 1 WU
- 區塊重量上限: 4,000,000 WU
Virtual Size (vBytes):
vsize = weight / 4
手續費計算:
fee = fee_rate (sat/vB) × vsize
範例 (P2WPKH 交易):
┌───────────────────────────────────────┐
│ 組成部分 │ Bytes │ Weight │
├───────────────────────────────────────┤
│ version │ 4 │ 16 │
│ marker + flag │ 2 │ 2 │
│ input count │ 1 │ 4 │
│ input (no sig) │ 41 │ 164 │
│ output count │ 1 │ 4 │
│ output │ 34 │ 136 │
│ witness │ 107 │ 107 │
│ locktime │ 4 │ 16 │
├───────────────────────────────────────┤
│ Total │ 194 │ 449 │
│ vsize │ │ 112.25 │
└───────────────────────────────────────┘ 容量提升
實際容量比較:
傳統交易 (P2PKH):
- 平均大小: ~225 bytes
- 區塊可容納: ~4,400 筆
SegWit 交易 (P2WPKH):
- 實際大小: ~194 bytes
- vsize: ~112 vBytes
- 區塊可容納: ~8,000 筆 (80% 提升)
理論最大區塊:
- 全部是 witness 數據: ~4 MB
- 實際混合: ~2-2.5 MB 輸出類型
P2WPKH (Pay-to-Witness-Public-Key-Hash)
Native SegWit 單簽地址 (bc1q...)
scriptPubKey:
OP_0 <20-byte-pubkey-hash>
Witness:
<signature> <pubkey>
結構:
┌─────────────────────────────────────┐
│ scriptPubKey (22 bytes): │
│ 00 14 <20-byte hash> │
└─────────────────────────────────────┘
範例地址:
bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 P2WSH (Pay-to-Witness-Script-Hash)
Native SegWit 腳本地址 (bc1q... 更長)
scriptPubKey:
OP_0 <32-byte-script-hash>
Witness:
<script inputs...> <witness script>
結構:
┌─────────────────────────────────────┐
│ scriptPubKey (34 bytes): │
│ 00 20 <32-byte hash> │
└─────────────────────────────────────┘
範例 (2-of-3 多簽):
bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3 P2SH-P2WPKH (Wrapped SegWit)
兼容舊錢包的 SegWit 地址 (3...)
scriptPubKey (P2SH):
OP_HASH160 <20-byte-script-hash> OP_EQUAL
scriptSig:
<redeemScript>
redeemScript:
OP_0 <20-byte-pubkey-hash>
Witness:
<signature> <pubkey>
優點:
- 舊錢包可以發送到此地址
- 享受 SegWit 折扣(部分)
缺點:
- 比 native SegWit 大 ~11 bytes
- 手續費略高 TypeScript 實作
地址生成
import { bech32 } from 'bech32';
import * as crypto from 'crypto';
// P2WPKH 地址
function createP2WPKHAddress(
publicKey: Uint8Array,
network: 'mainnet' | 'testnet' = 'mainnet'
): string {
// 計算 HASH160 (RIPEMD160(SHA256(pubkey)))
const sha256Hash = crypto.createHash('sha256').update(publicKey).digest();
const hash160 = crypto.createHash('ripemd160').update(sha256Hash).digest();
const prefix = network === 'mainnet' ? 'bc' : 'tb';
const version = 0; // witness version
const words = bech32.toWords(hash160);
words.unshift(version);
return bech32.encode(prefix, words);
}
// P2WSH 地址
function createP2WSHAddress(
witnessScript: Uint8Array,
network: 'mainnet' | 'testnet' = 'mainnet'
): string {
// 計算 SHA256(script)
const scriptHash = crypto.createHash('sha256')
.update(witnessScript)
.digest();
const prefix = network === 'mainnet' ? 'bc' : 'tb';
const version = 0;
const words = bech32.toWords(scriptHash);
words.unshift(version);
return bech32.encode(prefix, words);
}
// 解碼 Bech32 地址
function decodeSegWitAddress(address: string): {
version: number;
program: Uint8Array;
} {
const { prefix, words } = bech32.decode(address);
const version = words[0];
const program = new Uint8Array(bech32.fromWords(words.slice(1)));
return { version, program };
} 交易構建
interface SegWitInput {
txid: string;
vout: number;
sequence: number;
witness: Uint8Array[];
}
interface Output {
value: bigint;
scriptPubKey: Uint8Array;
}
interface SegWitTransaction {
version: number;
inputs: SegWitInput[];
outputs: Output[];
locktime: number;
}
function serializeSegWitTx(tx: SegWitTransaction): Uint8Array {
const parts: Uint8Array[] = [];
// Version (4 bytes, little-endian)
const versionBuf = new Uint8Array(4);
new DataView(versionBuf.buffer).setUint32(0, tx.version, true);
parts.push(versionBuf);
// Marker and flag
parts.push(new Uint8Array([0x00, 0x01]));
// Input count
parts.push(encodeVarInt(tx.inputs.length));
// Inputs (without witness)
for (const input of tx.inputs) {
// Previous txid (32 bytes, reversed)
const txidBytes = hexToBytes(input.txid).reverse();
parts.push(new Uint8Array(txidBytes));
// Previous vout (4 bytes)
const voutBuf = new Uint8Array(4);
new DataView(voutBuf.buffer).setUint32(0, input.vout, true);
parts.push(voutBuf);
// Empty scriptSig for SegWit
parts.push(new Uint8Array([0x00]));
// Sequence (4 bytes)
const seqBuf = new Uint8Array(4);
new DataView(seqBuf.buffer).setUint32(0, input.sequence, true);
parts.push(seqBuf);
}
// Output count
parts.push(encodeVarInt(tx.outputs.length));
// Outputs
for (const output of tx.outputs) {
// Value (8 bytes)
const valueBuf = new Uint8Array(8);
new DataView(valueBuf.buffer).setBigUint64(0, output.value, true);
parts.push(valueBuf);
// scriptPubKey
parts.push(encodeVarInt(output.scriptPubKey.length));
parts.push(output.scriptPubKey);
}
// Witness data
for (const input of tx.inputs) {
parts.push(encodeVarInt(input.witness.length));
for (const item of input.witness) {
parts.push(encodeVarInt(item.length));
parts.push(item);
}
}
// Locktime (4 bytes)
const locktimeBuf = new Uint8Array(4);
new DataView(locktimeBuf.buffer).setUint32(0, tx.locktime, true);
parts.push(locktimeBuf);
return concatBytes(parts);
}
// 計算 TXID(不含 witness)
function calculateTxid(tx: SegWitTransaction): string {
const parts: Uint8Array[] = [];
// Version
const versionBuf = new Uint8Array(4);
new DataView(versionBuf.buffer).setUint32(0, tx.version, true);
parts.push(versionBuf);
// Inputs (不含 marker, flag, witness)
parts.push(encodeVarInt(tx.inputs.length));
for (const input of tx.inputs) {
const txidBytes = hexToBytes(input.txid).reverse();
parts.push(new Uint8Array(txidBytes));
const voutBuf = new Uint8Array(4);
new DataView(voutBuf.buffer).setUint32(0, input.vout, true);
parts.push(voutBuf);
parts.push(new Uint8Array([0x00])); // empty scriptSig
const seqBuf = new Uint8Array(4);
new DataView(seqBuf.buffer).setUint32(0, input.sequence, true);
parts.push(seqBuf);
}
// Outputs
parts.push(encodeVarInt(tx.outputs.length));
for (const output of tx.outputs) {
const valueBuf = new Uint8Array(8);
new DataView(valueBuf.buffer).setBigUint64(0, output.value, true);
parts.push(valueBuf);
parts.push(encodeVarInt(output.scriptPubKey.length));
parts.push(output.scriptPubKey);
}
// Locktime
const locktimeBuf = new Uint8Array(4);
new DataView(locktimeBuf.buffer).setUint32(0, tx.locktime, true);
parts.push(locktimeBuf);
const serialized = concatBytes(parts);
const hash1 = crypto.createHash('sha256').update(serialized).digest();
const hash2 = crypto.createHash('sha256').update(hash1).digest();
return Buffer.from(hash2).reverse().toString('hex');
} BIP-143 簽名雜湊
// BIP-143: SegWit 簽名的新雜湊算法
interface SignatureHashInput {
txid: string;
vout: number;
scriptCode: Uint8Array;
value: bigint;
sequence: number;
}
function bip143SignatureHash(
tx: SegWitTransaction,
inputIndex: number,
input: SignatureHashInput,
sigHashType: number
): Uint8Array {
const SIGHASH_ALL = 0x01;
const SIGHASH_NONE = 0x02;
const SIGHASH_SINGLE = 0x03;
const SIGHASH_ANYONECANPAY = 0x80;
const parts: Uint8Array[] = [];
// 1. Version
const versionBuf = new Uint8Array(4);
new DataView(versionBuf.buffer).setUint32(0, tx.version, true);
parts.push(versionBuf);
// 2. hashPrevouts
if (!(sigHashType & SIGHASH_ANYONECANPAY)) {
const prevouts: Uint8Array[] = [];
for (const inp of tx.inputs) {
prevouts.push(new Uint8Array(hexToBytes(inp.txid).reverse()));
const voutBuf = new Uint8Array(4);
new DataView(voutBuf.buffer).setUint32(0, inp.vout, true);
prevouts.push(voutBuf);
}
parts.push(doubleSha256(concatBytes(prevouts)));
} else {
parts.push(new Uint8Array(32)); // 0x00...00
}
// 3. hashSequence
if (!(sigHashType & SIGHASH_ANYONECANPAY) &&
(sigHashType & 0x1f) !== SIGHASH_SINGLE &&
(sigHashType & 0x1f) !== SIGHASH_NONE) {
const sequences: Uint8Array[] = [];
for (const inp of tx.inputs) {
const seqBuf = new Uint8Array(4);
new DataView(seqBuf.buffer).setUint32(0, inp.sequence, true);
sequences.push(seqBuf);
}
parts.push(doubleSha256(concatBytes(sequences)));
} else {
parts.push(new Uint8Array(32));
}
// 4. outpoint
parts.push(new Uint8Array(hexToBytes(input.txid).reverse()));
const voutBuf = new Uint8Array(4);
new DataView(voutBuf.buffer).setUint32(0, input.vout, true);
parts.push(voutBuf);
// 5. scriptCode
parts.push(encodeVarInt(input.scriptCode.length));
parts.push(input.scriptCode);
// 6. value
const valueBuf = new Uint8Array(8);
new DataView(valueBuf.buffer).setBigUint64(0, input.value, true);
parts.push(valueBuf);
// 7. sequence
const seqBuf = new Uint8Array(4);
new DataView(seqBuf.buffer).setUint32(0, input.sequence, true);
parts.push(seqBuf);
// 8. hashOutputs
if ((sigHashType & 0x1f) !== SIGHASH_SINGLE &&
(sigHashType & 0x1f) !== SIGHASH_NONE) {
const outputs: Uint8Array[] = [];
for (const out of tx.outputs) {
const valueBuf = new Uint8Array(8);
new DataView(valueBuf.buffer).setBigUint64(0, out.value, true);
outputs.push(valueBuf);
outputs.push(encodeVarInt(out.scriptPubKey.length));
outputs.push(out.scriptPubKey);
}
parts.push(doubleSha256(concatBytes(outputs)));
} else if ((sigHashType & 0x1f) === SIGHASH_SINGLE &&
inputIndex < tx.outputs.length) {
const out = tx.outputs[inputIndex];
const outputParts: Uint8Array[] = [];
const valueBuf = new Uint8Array(8);
new DataView(valueBuf.buffer).setBigUint64(0, out.value, true);
outputParts.push(valueBuf);
outputParts.push(encodeVarInt(out.scriptPubKey.length));
outputParts.push(out.scriptPubKey);
parts.push(doubleSha256(concatBytes(outputParts)));
} else {
parts.push(new Uint8Array(32));
}
// 9. locktime
const locktimeBuf = new Uint8Array(4);
new DataView(locktimeBuf.buffer).setUint32(0, tx.locktime, true);
parts.push(locktimeBuf);
// 10. sighash type
const sigHashBuf = new Uint8Array(4);
new DataView(sigHashBuf.buffer).setUint32(0, sigHashType, true);
parts.push(sigHashBuf);
return doubleSha256(concatBytes(parts));
}
function doubleSha256(data: Uint8Array): Uint8Array {
const hash1 = crypto.createHash('sha256').update(data).digest();
return new Uint8Array(crypto.createHash('sha256').update(hash1).digest());
} Witness 承諾
區塊中的 Witness 承諾:
Coinbase 交易的 OP_RETURN 輸出:
┌─────────────────────────────────────┐
│ 6a (OP_RETURN) │
│ 24 (push 36 bytes) │
│ aa21a9ed (witness commitment prefix)│
│ <32-byte witness root hash> │
└─────────────────────────────────────┘
Witness Root 計算:
1. 計算所有 WTXID
2. Coinbase WTXID = 0x00...00
3. 構建 Merkle 樹
4. witness_root = Merkle root
5. commitment = SHA256(SHA256(witness_root || witness_nonce))
Witness Nonce:
- 存儲在 coinbase 的 witness 中
- 通常是 0x00...00 (32 bytes) 啟用歷史
SegWit 啟用時間線:
2015年12月: BIP-141/142/143/144 提出
2016年4月: Bitcoin Core 0.12.1 支持
2016年10月: Bitcoin Core 0.13.1 準備激活
2017年8月1日: BIP-91 鎖定
2017年8月24日: SegWit 正式啟用(區塊 481,824)
激活方式: BIP-9 版本位信號
- 95% 算力支持閾值
- 經過 UASF (User Activated Soft Fork) 推動
採用率:
- 2018年: ~35%
- 2020年: ~65%
- 2024年: ~80% 優勢總結
| 特性 | 傳統交易 | SegWit |
|---|---|---|
| 交易可塑性 | 存在 | 已解決 |
| 區塊容量 | ~1 MB | ~2-4 MB |
| 二層協議 | 不安全 | 安全 |
| 簽名驗證 | O(n²) | O(n) |
| 腳本升級 | 困難 | witness version |
| 手續費 | 基準 | 節省 30-50% |
最佳實踐
- 使用 Native SegWit:優先使用 bc1q/bc1p 地址
- 避免 P2SH-wrapped:除非需要兼容舊錢包
- 正確計算手續費:使用 vsize 而非實際大小
- 使用 BIP-143:SegWit 輸入必須使用新的簽名雜湊
- 驗證 witness 格式:確保 witness 結構正確
相關資源
已複製連結