跳至主要內容
進階

Difficulty Adjustment

難度調整:比特幣自動調節挖礦難度的共識機制

15 分鐘

概述

難度調整是比特幣最重要的自我調節機制之一。它確保無論全網算力如何變化, 平均每 10 分鐘產生一個區塊。這個機制每 2016 個區塊(約兩週)自動調整一次, 維持了比特幣的可預測發行計劃。

設計目標: 2016 個區塊 × 10 分鐘 = 20,160 分鐘 = 14 天。 如果實際時間偏離這個目標,難度會相應調整。

目標值與難度

區塊雜湊目標

工作量證明要求:

區塊雜湊必須小於目標值 (target):
SHA256(SHA256(block_header)) < target

目標值是一個 256-bit 數字:
┌─────────────────────────────────────────────────────────┐
│ 目標值 (target)                                         │
│ 0x00000000000000000004b0c4...                           │
│                                                         │
│ 有效區塊雜湊必須有足夠多的前導零                         │
└─────────────────────────────────────────────────────────┘

難度 (difficulty) 定義:
difficulty = max_target / current_target

max_target (難度 1):
0x00000000FFFF0000000000000000000000000000000000000000000000000000

難度越高 → 目標值越小 → 需要更多前導零

nBits 編碼

區塊頭中的 nBits 欄位 (4 bytes):

格式: 0xEEMMMMMMM
- EE: 指數 (exponent)
- MMMMMM: 尾數 (mantissa)

目標值計算:
target = mantissa × 2^(8 × (exponent - 3))

範例: nBits = 0x1903a30c
- exponent = 0x19 = 25
- mantissa = 0x03a30c

target = 0x03a30c × 2^(8 × (25 - 3))
       = 0x03a30c × 2^176
       = 0x0000000000000000000003a30c0000000000000000000000...

驗證:
區塊雜湊必須 < 這個目標值

調整算法

基本公式

難度調整公式:

new_target = old_target × (actual_time / expected_time)

其中:
- old_target: 當前難度週期的目標值
- actual_time: 過去 2016 個區塊的實際耗時
- expected_time: 2016 × 10 分鐘 = 1,209,600 秒

調整限制:
- 最大增加: 4 倍 (×4)
- 最大減少: 1/4 (×0.25)
- 防止極端波動

範例:
如果 2016 個區塊花了 10 天 (而非 14 天):
new_target = old_target × (10 / 14)
           = old_target × 0.714
→ 難度增加約 40%

調整時機

調整週期:

區塊高度     難度週期
0-2015       週期 0
2016-4031    週期 1
4032-6047    週期 2
...
n×2016 - (n+1)×2016-1  週期 n

調整發生在每個週期的第一個區塊:
- 區塊 2016, 4032, 6048, ...
- 計算基於前一週期的時間

時間計算:
actual_time = block[2015].timestamp - block[0].timestamp
            (或相對於前一週期)

注意: 使用 2015 個區塊間隔,而非 2016 個
這是因為計算的是區塊之間的時間差

TypeScript 實作

目標值計算

// 從 nBits 解碼目標值
function nbitsToTarget(nbits: number): bigint {
  const exponent = nbits >> 24;
  const mantissa = nbits & 0x007fffff;

  // 處理負數標記 (最高位)
  const isNegative = (mantissa & 0x00800000) !== 0;
  const cleanMantissa = isNegative ? mantissa & 0x007fffff : mantissa;

  let target: bigint;
  if (exponent <= 3) {
    target = BigInt(cleanMantissa) >> BigInt(8 * (3 - exponent));
  } else {
    target = BigInt(cleanMantissa) << BigInt(8 * (exponent - 3));
  }

  return isNegative ? -target : target;
}

// 將目標值編碼為 nBits
function targetToNbits(target: bigint): number {
  if (target === 0n) return 0;

  const isNegative = target < 0n;
  let absTarget = isNegative ? -target : target;

  // 計算需要的字節數
  let size = 0;
  let temp = absTarget;
  while (temp > 0n) {
    size++;
    temp >>= 8n;
  }

  // 提取尾數 (最高 3 字節)
  let mantissa: number;
  if (size <= 3) {
    mantissa = Number(absTarget << BigInt(8 * (3 - size)));
  } else {
    mantissa = Number(absTarget >> BigInt(8 * (size - 3)));
  }

  // 處理溢出
  if (mantissa & 0x00800000) {
    mantissa >>= 8;
    size++;
  }

  // 組合 nBits
  let nbits = (size << 24) | (mantissa & 0x007fffff);
  if (isNegative) {
    nbits |= 0x00800000;
  }

  return nbits;
}

// 計算難度
function calculateDifficulty(nbits: number): number {
  const target = nbitsToTarget(nbits);
  const maxTarget = nbitsToTarget(0x1d00ffff); // 難度 1
  return Number(maxTarget) / Number(target);
}

難度調整計算

const DIFFICULTY_ADJUSTMENT_INTERVAL = 2016;
const TARGET_TIMESPAN = 14 * 24 * 60 * 60; // 14 天 (秒)
const TARGET_SPACING = 10 * 60; // 10 分鐘 (秒)

interface BlockHeader {
  height: number;
  timestamp: number;
  nbits: number;
  // ... 其他欄位
}

function calculateNextTarget(
  lastBlock: BlockHeader,
  firstBlockOfPeriod: BlockHeader
): bigint {
  // 計算實際耗時
  const actualTimespan = lastBlock.timestamp - firstBlockOfPeriod.timestamp;

  // 應用限制 (1/4 到 4 倍)
  let adjustedTimespan = actualTimespan;
  if (adjustedTimespan < TARGET_TIMESPAN / 4) {
    adjustedTimespan = TARGET_TIMESPAN / 4;
  }
  if (adjustedTimespan > TARGET_TIMESPAN * 4) {
    adjustedTimespan = TARGET_TIMESPAN * 4;
  }

  // 計算新目標值
  const currentTarget = nbitsToTarget(lastBlock.nbits);
  let newTarget = currentTarget * BigInt(adjustedTimespan);
  newTarget = newTarget / BigInt(TARGET_TIMESPAN);

  // 確保不超過最大目標值
  const maxTarget = nbitsToTarget(0x1d00ffff);
  if (newTarget > maxTarget) {
    newTarget = maxTarget;
  }

  return newTarget;
}

// 完整的難度計算
function getNextWorkRequired(
  lastBlock: BlockHeader,
  getBlockByHeight: (height: number) => BlockHeader
): number {
  // 檢查是否需要調整
  if ((lastBlock.height + 1) % DIFFICULTY_ADJUSTMENT_INTERVAL !== 0) {
    // 非調整區塊,保持當前難度
    return lastBlock.nbits;
  }

  // 獲取週期第一個區塊
  const firstHeight = lastBlock.height - (DIFFICULTY_ADJUSTMENT_INTERVAL - 1);
  const firstBlock = getBlockByHeight(firstHeight);

  // 計算新目標值
  const newTarget = calculateNextTarget(lastBlock, firstBlock);

  return targetToNbits(newTarget);
}

難度驗證

// 驗證區塊是否滿足難度要求
function verifyProofOfWork(
  blockHash: Uint8Array,
  nbits: number
): boolean {
  const target = nbitsToTarget(nbits);

  // 將區塊雜湊轉換為大整數 (小端序)
  let hashValue = 0n;
  for (let i = blockHash.length - 1; i >= 0; i--) {
    hashValue = (hashValue << 8n) | BigInt(blockHash[i]);
  }

  // 雜湊必須小於目標值
  return hashValue <= target;
}

// 驗證區塊的 nbits 是否正確
function verifyDifficulty(
  block: BlockHeader,
  prevBlock: BlockHeader,
  getBlockByHeight: (height: number) => BlockHeader
): boolean {
  const expectedNbits = getNextWorkRequired(prevBlock, getBlockByHeight);
  return block.nbits === expectedNbits;
}

// 計算預期下次調整
function predictNextAdjustment(
  currentHeight: number,
  currentDifficulty: number,
  recentBlockTimes: number[] // 最近區塊的時間戳
): {
  blocksUntilAdjustment: number;
  estimatedNewDifficulty: number;
  percentChange: number;
} {
  const nextAdjustmentHeight =
    Math.ceil((currentHeight + 1) / DIFFICULTY_ADJUSTMENT_INTERVAL) *
    DIFFICULTY_ADJUSTMENT_INTERVAL;

  const blocksUntilAdjustment = nextAdjustmentHeight - currentHeight;

  // 根據最近區塊時間估算
  if (recentBlockTimes.length < 2) {
    return {
      blocksUntilAdjustment,
      estimatedNewDifficulty: currentDifficulty,
      percentChange: 0
    };
  }

  const avgBlockTime =
    (recentBlockTimes[recentBlockTimes.length - 1] - recentBlockTimes[0]) /
    (recentBlockTimes.length - 1);

  const expectedBlockTime = TARGET_SPACING;
  const ratio = expectedBlockTime / avgBlockTime;

  // 應用限制
  const clampedRatio = Math.max(0.25, Math.min(4, ratio));

  const estimatedNewDifficulty = currentDifficulty * clampedRatio;
  const percentChange = (clampedRatio - 1) * 100;

  return {
    blocksUntilAdjustment,
    estimatedNewDifficulty,
    percentChange
  };
}

歷史難度變化

比特幣難度歷史里程碑:

2009年1月:
- 創世區塊
- 難度 = 1

2010年12月:
- 首次突破 10,000
- GPU 挖礦開始

2013年:
- ASIC 礦機出現
- 難度快速攀升

2017年:
- 難度達到 1 萬億

2021年:
- 中國礦業禁令
- 難度下降 28% (歷史最大單次下降)

2024年:
- 難度超過 80 萬億

增長模式:
┌──────────────────────────────────────┐
│ 年份    │ 大約難度                    │
├──────────────────────────────────────┤
│ 2009    │ 1                           │
│ 2012    │ 3,000,000                   │
│ 2015    │ 50,000,000,000              │
│ 2018    │ 5,000,000,000,000           │
│ 2021    │ 20,000,000,000,000          │
│ 2024    │ 80,000,000,000,000          │
└──────────────────────────────────────┘

安全考量

時間戳攻擊

時間戳操縱風險:

礦工可能嘗試:
1. 操縱時間戳使難度降低
2. 在調整前加速產塊

防禦機制:

1. 未來時間限制
   - 區塊時間戳不能超過網路時間 + 2 小時
   - 節點會拒絕未來區塊

2. 過去時間限制
   - 時間戳必須大於前 11 個區塊的中位數
   - 防止時間倒退

3. 難度調整限制
   - 單次調整最多 4 倍
   - 限制極端操縱的影響

4. 長期安全
   - 2016 個區塊的平均稀釋操縱影響
   - 需要持續控制才能有意義地影響

算力波動

算力突然變化的影響:

情況 1: 算力大幅增加
- 區塊產生加快
- 等待下次調整 (最多 2016 區塊)
- 調整後恢復 10 分鐘間隔

情況 2: 算力大幅下降
- 區塊產生變慢
- 可能需要數週才到調整點
- 這期間網路效能下降

2021 中國禁令案例:
- 約 50% 算力離線
- 區塊時間增加到 ~20 分鐘
- 難度下降 28% 後恢復正常

這是 4× 限制的原因之一:
防止算力驟降後難度調整過度

替代機制

其他幣種的方法

其他難度調整算法:

1. Bitcoin Cash (BCH)
   - ASERT (絕對時間目標計算)
   - 每個區塊調整
   - 更平滑的難度曲線

2. Ethereum (PoW 時期)
   - 每個區塊調整
   - 目標 15 秒區塊時間
   - 包含難度炸彈

3. Litecoin
   - 類似比特幣
   - 2.5 分鐘區塊時間
   - 相同的 2016 區塊週期

4. Monero
   - 每個區塊調整
   - 使用最近 720 個區塊
   - 更快響應算力變化

比特幣的保守選擇:
- 穩定性優先
- 防止遊戲機制
- 14 年驗證有效

監控與工具

// 難度監控工具
interface DifficultyStats {
  currentDifficulty: number;
  nextEstimatedDifficulty: number;
  percentChange: number;
  blocksUntilAdjustment: number;
  estimatedTimeUntilAdjustment: number;
  averageBlockTime: number;
  hashrate: number;
}

async function getDifficultyStats(
  rpc: BitcoinRPC
): Promise<DifficultyStats> {
  const info = await rpc.call('getblockchaininfo');
  const difficulty = info.difficulty;
  const height = info.blocks;

  // 獲取最近區塊
  const recentBlocks = [];
  for (let i = 0; i < 144; i++) { // 最近約 1 天
    const hash = await rpc.call('getblockhash', [height - i]);
    const block = await rpc.call('getblockheader', [hash]);
    recentBlocks.push(block);
  }

  // 計算平均區塊時間
  const avgBlockTime =
    (recentBlocks[0].time - recentBlocks[recentBlocks.length - 1].time) /
    (recentBlocks.length - 1);

  // 估算算力 (H/s)
  // hashrate = difficulty × 2^32 / block_time
  const hashrate = (difficulty * Math.pow(2, 32)) / avgBlockTime;

  // 預測下次調整
  const prediction = predictNextAdjustment(
    height,
    difficulty,
    recentBlocks.map(b => b.time)
  );

  return {
    currentDifficulty: difficulty,
    nextEstimatedDifficulty: prediction.estimatedNewDifficulty,
    percentChange: prediction.percentChange,
    blocksUntilAdjustment: prediction.blocksUntilAdjustment,
    estimatedTimeUntilAdjustment:
      prediction.blocksUntilAdjustment * avgBlockTime,
    averageBlockTime: avgBlockTime,
    hashrate
  };
}

相關資源

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