Payjoin 深度解析

Payjoin 隱私交易機制詳解

PayJoin 實作:比特幣隱私的進階技術

PayJoin(又稱 Pay-to-End-Point,P2EP)是比特幣隱私技術中最被低估但最有效的方案之一。本文深入探討 PayJoin 的技術原理、實現方式、使用場景以及其在比特幣隱私生態系統中的獨特地位。

PayJoin 的核心價值

為什麼 PayJoin 與眾不同

隱私技術比較:

┌─────────────────────────────────────────────────────────────┐
│                    隱私技術比較圖                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  技術         外部可識別性      合法外觀      效率        │
│ ───────────────────────────────────────────────────────── │
│  傳統交易     ✓ 完全可識別    ✓ 普通        ✓ 高        │
│                                                             │
│  CoinJoin    ✗ 可識別混合    ✗ 可識別      ✓ 中        │
│                                                             │
│  PayJoin     ✗ 不可識別      ✓ 普通        ✓ 中        │
│                                                             │
│  ⚠️  關鍵差異:PayJoin 看起來就像普通的 P2P 交易          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

與其他隱私技術的互補性

PayJoin 與其他隱私技術可以組合使用:

PayJoin + 其他技術:

1. PayJoin + CoinJoin
   ┌─────────────────────────────────────────┐
   │  先進行 PayJoin,再進行 CoinJoin        │
   │  雙重隱私保護                          │
   │  • 交易對手不知道資金來源              │
   │  • 區塊鏈分析不知道真實金額            │
   └─────────────────────────────────────────┘

2. PayJoin + Lightning
   ┌─────────────────────────────────────────┐
   │  通過 Lightning 發送 PayJoin 輸出      │
   │  增加路由混淆                          │
   │  • Lightning 節點不知道最終目的地      │
   │  • 區塊鏈分析看不到通道餘額           │
   └─────────────────────────────────────────┘

3. PayJoin + Tor
   ┌─────────────────────────────────────────┐
   │  通過 Tor 網路進行 PayJoin 通訊       │
   │  • IP 地址隱藏                        │
   │  • 網路級別隱私                        │
   └─────────────────────────────────────────┘

技術原理深度解析

交易結構分析

PayJoin 的核心創新在於輸入輸出金額的不對稱性:

class PayJoinTransaction:
    """PayJoin 交易結構分析"""

    @staticmethod
    def analyze_coinjoin_appearance(payjoin_tx):
        """
        分析 PayJoin 的外觀特徵

        為什麼外部觀察者無法識別:
        """

        # 1. 多元輸入結構
        input_structure = {
            "num_inputs": payjoin_tx.input_count,
            # 2+ 個輸入表示可能是 CoinJoin 或 PayJoin
            # 但無法確定是哪種

            # 普通 P2P 交易也可能有 2+ 輸入
            # (如合併 UTXO)
        }

        # 2. 金額不對稱(關鍵特徵)
        amount_analysis = {
            # 輸入金額總和 > 輸出金額總和(礦工費)
            "input_sum": payjoin_tx.total_input_value,
            "output_sum": payjoin_tx.total_output_value,
            "difference": payjoin_tx.miner_fee,

            # 這與 CoinJoin 不同!
            # CoinJoin: 輸入 ≈ 輸出(各方得到相近金額)
            # PayJoin: 輸出明顯小於輸入
        }

        # 3. 輸出分析
        output_analysis = {
            # 通常只有 2 個輸出
            # • 實際支付金額
            # • 找零金額
            #
            # 這看起來像普通交易!
        }

        return {
            "likely_coinjoin": False,  # 無法識別為 CoinJoin
            "likely_payjoin": False,    # 也無法識別為 PayJoin
            "appearance": "ordinary_p2p"  # 像是普通交易
        }

協定流程

PayJoin 協定完整流程:

┌─────────────────────────────────────────────────────────────┐
│                    協定參與者                               │
│                                                             │
│    [付款方 Alice] ◄─────► [收款方 Bob]                    │
│         │                              │                   │
│         │    ┌────────────────────┐    │                   │
│         └────►  PayJoin 協調器     ◄───┘                   │
│              └────────────────────┘                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

步驟 1:提議(Alice → Bob)
┌─────────────────────────────────────────┐
│  {                                       │
│    "action": "create_payjoin",         │
│    "amount": "0.05 BTC",               │
│    "receiver_output": "bc1q...",       │
│    "payment_id": "uuid..."             │
│  }                                      │
└─────────────────────────────────────────┘

步驟 2:準備輸入(Bob → Alice)
┌─────────────────────────────────────────┐
│  {                                       │
│    "utxo": "outpoint:0",               │
│    "value": "0.1 BTC",                 │
│    "pubkey": "02...",                  │
│    "psbt": "psbt_base64..."            │
│  }                                      │
└─────────────────────────────────────────┘

步驟 3:構造交易(Alice)
┌─────────────────────────────────────────┐
│  交易結構:                             │
│                                         │
│  輸入:                                  │
│    • Alice UTXO: 0.2 BTC              │
│    • Bob UTXO: 0.1 BTC                │
│                                         │
│  輸出:                                  │
│    • Bob: 0.05 BTC(實際支付)         │
│    • Alice: 0.24 BTC(找零)          │
│    • 礦工費: 0.01 BTC                  │
└─────────────────────────────────────────┘

步驟 4:盲化簽名(雙方)
┌─────────────────────────────────────────┐
│  • Alice 盲化 Bob 的輸出               │
│  • Bob 用私鑰簽名自己的輸入            │
│  • 雙方交換盲化因子                    │
└─────────────────────────────────────────┘

步驟 5:揭示與廣播(Alice)
┌─────────────────────────────────────────┐
│  • 揭示盲化輸出                        │
│  • 收集所有簽名                        │
│  • 廣播到比特幣網路                    │
└─────────────────────────────────────────┘

密碼學基礎

PayJoin 使用盲化技術確保隱私:

class PayJoinBlinding:
    """PayJoin 盲化機制"""

    def __init__(self):
        self.r = None  # 盲化因子(隨機數)

    def generate_blinding_factor(self):
        """生成盲化因子"""
        self.r = secrets.randbelow(2**256)
        return self.r

    def blind_output(self, output_amount, receiver_pubkey):
        """
        盲化輸出地址

        公式:blinded = amount * H(pubkey || r)
        """
        # 計算盲化輸出
        h = sha256(receiver_pubkey + str(self.r).encode())
        blind_factor = int.from_bytes(h, 'big')

        blinded_amount = output_amount * blind_factor % SECP256K1_ORDER

        return blinded_amount

    def unblind_output(self, blinded_amount, receiver_privkey):
        """
        揭示盲化輸出

        公式:amount = blinded * H(pubkey || r)^(-1) mod n
        """
        # 計算盲化因子的逆元
        r_inv = pow(self.r, -1, SECP256K1_ORDER)

        # 恢復原始金額
        h = sha256(self.get_pubkey() + str(self.r).encode())
        blind_factor = int.from_bytes(h, 'big')

        amount = blinded_amount * pow(blind_factor, -1, SECP256K1_ORDER) % SECP256K1_ORDER

        return amount

實作教學

使用 Samourai Wallet

Samourai Wallet 是支援 PayJoin 最完整的錢包之一:

Samourai Wallet PayJoin 操作流程:

1. 發送準備
   ┌─────────────────────────────────────────┐
   │  Samourai Wallet                       │
   │                                         │
   │  發送比特幣 → 選擇收款人               │
   │                                         │
   │  ⚠️  對方必須也使用支援              │
   │     PayJoin 的錢包                     │
   └─────────────────────────────────────────┘
                     │
                     ▼
2. 選擇 PayJoin 選項
   ┌─────────────────────────────────────────┐
   │  發送選項:                             │
   │                                         │
   │  ○ 普通發送                            │
   │  ● PayJoin(增強隱私)               │
   │                                         │
   │  選擇「PayJoin」                       │
   └─────────────────────────────────────────┘
                     │
                     ▼
3. 創建提案
   ┌─────────────────────────────────────────┐
   │  填寫收款人資訊:                       │
   │                                         │
   │  收款人地址:bc1q...                  │
   │  金額:0.05 BTC                        │
   │                                         │
   │  點擊「創建 PayJoin 請求」             │
   └─────────────────────────────────────────┘
                     │
                     ▼
4. 對方確認
   ┌─────────────────────────────────────────┐
   │  付款人分享請求給收款人                │
   │                                         │
   │  收款人:                              │
   │  • 選擇要貢獻的 UTXO                  │
   │  • 確認金額                            │
   │  • 完成簽名                            │
   └─────────────────────────────────────────┘
                     │
                     ▼
5. 完成交易
   ┌─────────────────────────────────────────┐
   │  付款人:                              │
   │  • 添加自己的輸入                      │
   │  • 收集雙方簽名                        │
   │  • 廣播交易                            │
   │                                         │
   │  ✅ PayJoin 交易完成                   │
   │     看起來像普通 P2P 交易              │
   └─────────────────────────────────────────┘

使用 JoinMarket

JoinMarket 也支援 PayJoin 功能:

# JoinMarket PayJoin 命令行操作

# 1. 啟動 JoinMarket
cd joinmarket
python wallet_tool.py create

# 2. 餘額顯示
python wallet_tool.py show

# 3. 執行 PayJoin(maker-taker 模式)
# maker 等待交易,taker 主動發起

# 發起 PayJoin
python paynjoin.py

# 選項:
# -j, --maker 作為 maker 運行
# -m, --taker 作為 taker 運行
# -p, --price-factor 價格加成

開發者實現

對於開發者,以下是 PayJoin 的關鍵實現邏輯:

from btcpay import BTCPayClient
import binascii

class PayJoinClient:
    """PayJoin 用戶端實現"""

    def __init__(self, wallet, endpoint):
        self.wallet = wallet
        self.endpoint = endpoint  # BTCPay Server 或其他兼容端點

    def request_payjoin(self, amount_sats, destination):
        """
        請求 PayJoin

        流程:
        1. 獲取提議
        2. 添加輸入
        3. 完成簽名
        4. 廣播
        """
        # Step 1: 創建基礎交易
        psbt = self.create_unsigned_transaction(
            amount_sats,
            destination
        )

        # Step 2: 發送到 PayJoin 端點
        response = self.endpoint.create_payjoin_proposal(
            psbt.to_base64(),
            {
                "sendAmounts": {
                    "BTC": amount_sats / 100_000_000
                },
                "outputs": [
                    {"destination": destination}
                ]
            }
        )

        # Step 3: 端點返回包含額外輸入的 PSBT
        enriched_psbt = PSBT.from_base64(response["psbt"])

        # Step 4: 簽名我們的輸入
        enriched_psbt = self.wallet.sign_psbt(enriched_psbt)

        # Step 5: 廣播
        txid = self.endpoint.broadcast_payjoin(
            enriched_psbt.to_base64()
        )

        return txid


class PayJoinServer:
    """PayJoin 伺服器端實現"""

    def __init__(self, wallet):
        self.wallet = wallet

    def create_proposal(self, client_psbt):
        """
        創建 PayJoin 提案

        伺服器(收款方)添加自己的輸入
        """
        # 解析客戶的 PSBT
        psbt = PSBT.from_base64(client_psbt)

        # 選擇要貢獻的 UTXO
        our_utxo = self.wallet.select_utxo(
            target_amount=psbt.output_amount,
            exclude=[]
        )

        if not our_utxo:
            raise NoUtxoError("沒有可用的 UTXO")

        # 添加我們的輸入
        psbt.add_input(our_utxo)

        # 計算新的輸出金額
        # 客戶支付的金額 = 所有輸入 - 找零 - 礦工費
        total_in = psbt.total_input_value
        miner_fee = self.estimate_fee(psbt)
        client_amount = total_in - miner_fee

        # 添加輸出(不改變客戶指定的輸出金額)
        # 確保輸出金額與原提議一致

        # 簽名我們的輸入
        psbt.sign_input(our_utxo.outpoint, self.wallet.key)

        return {
            "psbt": psbt.to_base64(),
            "output_amount": client_amount
        }

風險與限制

技術限制

限制說明緩解措施
雙方在線必須雙方同時在線使用非同步消息隊列
錢包相容性需要雙方都支援 PayJoin推廣標準化
金額限制需要足夠的 UTXO提前準備 UTXO
隱私泄露對手可能成為弱點選擇可信對手

安全考量

PayJoin 安全檢查清單:

□ 對手識別
  □ 確認對手身份(如必要)
  □ 評估對手誠實度

□ 金額驗證
  □ 確認收到正確金額
  □ 驗證找零金額正確

□ UTXO 管理
  □ 選擇合適的 UTXO
  □ 避免價值過高/過低的 UTXO

□ 網路安全
  □ 使用 Tor(推薦)
  □ 避免 IP 泄露

□ 交易驗證
  □ 廣播前驗證簽名
  □ 檢查輸入輸出正確

對手模型

PayJoin 對手模型:

1. 誠實對手(正確執行協定)
   ✓ 雙方獲得隱私收益

2. 惡意對手(試圖學習信息)
   • 可能嘗試學習對方的 UTXO
   • 可能嘗試操縱金額

3. 協調者(如使用)
   • 不應該知道任何輸入輸出對應關係
   • 使用盲化技術防止信息泄露

4. 區塊鏈分析
   • 看到的是普通 P2P 交易外觀
   • 無法識別為 PayJoin

生態系統與標準化

BIP 78 標準

BIP 78 定義了 PayJoin 的標準化協議:

# BIP 78 消息格式示例

# 請求消息(payment request)
{
    "url": "https://example.com/payjoin",
    "psbt": "cHNidP8BA...",  # Base64 編碼的 PSBT

    # 可選字段
    "memo": "Payment for order #12345",
    "payeeEndpoint": "https://receiver.com",
    "payeeName": "Merchant Name"
}

# 響應消息
{
    "psbt": "cHNidP8BA...",  # 包含額外輸入的 PSBT
    "error": null  // 或錯誤信息
}

兼容性錢包

錢包PayJoin 版本說明
Samourai WalletBIP 78完整支持
BlueWalletBIP 78完整支持
BTCPay ServerBIP 78商戶解決方案
JoinMarket自定義maker-taker 模式
Armory有限支持舊版錢包

商家集成

對於商家,PayJoin 可以無縫集成:

# 使用 BTCPay Server 接收 PayJoin

from btcpay import BTCPayClient

# 初始化客戶端
client = BTCPayClient(
    url="https://your-btcpay-server.com",
    api_key="your-api-key"
)

# 創建 PayJoin 付款請求
invoice = client.create_invoice(
    amount=0.05,
    currency="BTC",
    checkout={"speedPolicy": "mediumSpeed"},
    receipt_data={"orderId": "12345"}
)

# 客戶可以使用任何 PayJoin 兼容錢包支付
# BTCPay Server 會自動處理 PayJoin 邏輯

# 查詢付款狀態
status = client.get_invoice(invoice["id"])
print(f"狀態: {status['status']}")

常見問題

Q: PayJoin 與 CoinJoin 有什麼區別?

A:

Q: 雙方都需要使用相同錢包嗎?

A: 不需要,只要雙方都支持 BIP 78 標準即可互通。

Q: PayJoin 是否有金額限制?

A: 取決於錢包可用 UTXO,理論上沒有上限。

Q: 使用 PayJoin 違法嗎?

A: 在大多數國家,PayJoin 是合法的隱私工具。使用前請了解當地法規。

Q: PayJoin 可以完全隱藏交易嗎?

A: PayJoin 增加了極大的隱私,但無法完全隱藏。建議結合其他工具(Tor、CoinJoin)使用。

總結

PayJoin 是比特幣隱私工具箱中強大且獨特的一員:

  1. 獨特優勢:看起來像普通交易,無法被區塊鏈分析識別
  2. 雙向隱私:交易雙方都受益
  3. 標準化:BIP 78 確保了跨錢包兼容性
  4. 實用性:無需特殊準備,適合日常使用

將 PayJoin 與其他隱私技術(CoinJoin、Lightning、Tor)結合使用,可以構建強大的比特幣隱私保護方案。


更新日期:2026-02-23

版本:1.0

本文包含

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

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

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