Skip to content
On this page

L2-20: Atomic Swaps(アトミックスワップ / HTLC)

1. 問題

相互に信頼していない二者が、第三者エスクローなしで異なるブロックチェーン間で資産を交換するにはどうすればよいか? — これが「アトミックスワップ」(Atomic Swap)です。

具体的には:Aliceは自分の10 ETHをBobの同等価値のBTCと交換したいが、両者とも相手が先に送金することを信用していません。どうすれば双方にとって安全にこのクロスチェーン交換を完了できるでしょうか?

2. 理由

アトミックスワップはクロスチェーンDeFiの基礎的プリミティブであり、ブリッジや仲介者を信頼することなくクロスチェーン資産交換を実現します。HTLC(Hash Time-Locked Contract)は、BitcoinのライトニングネットワークとEthereum間の交換の中核技術です。

その安全性は2つの暗号学的原理に基づいています:

  1. Hash Lock(ハッシュロック):秘密の原像 s を知っている者だけが資金を解除できる(hash(s) == hashLock
  2. Time Lock(タイムロック):タイムアウト後に送信者が資金を回収できる

組み合わせると:受信者は一定時間内に秘密を開示しなければならず、さもなければ送信者は返金できます。

3. 解決策

アーキテクチャ設計

AtomicSwapsコントラクトをデプロイ

  ├── lock(receiver, hashLock, timelock)   → AliceがETHをロック
  │     └── swapId = keccak256(sender, receiver, amount, hashLock, timelock, timestamp) を生成

  ├── withdraw(swapId, secret)              → Bobが秘密を開示してETHを取得
  │     └── 検証: keccak256(secret) == hashLock

  └── refund(swapId)                       → Aliceがタイムアウト後にETHを回収
        └── 検証: block.timestamp > timelock && msg.sender == sender

交換フロー(Alice ETH ↔ Bob BTC)

  1. AliceがEthereum上で lock(bobAddress, hashLock, timelock) を呼び出しETHをロック
  2. Bobはロックを確認後、Bitcoinチェーン上に対応するHTLCを作成(同じhashLock、より短いtimelock)
  3. AliceがBitcoinチェーン上で secret を開示してBTCを取得
  4. BobはBTCチェーン上で開示された secret を確認し、Ethereum上で withdraw(swapId, secret) を呼び出してETHを取得

ポイント: Aliceが先にsecretを開示し(BTCチェーン上で)、Bobがそれを見てからETHチェーン上で開示する — BobのBTC→ETHは一方向の情報フローです(Bobは自らBTCを放棄しない)。

状態マシン

None → Locked → Withdrawn(Bobが秘密を開示)
              → Refunded (Aliceがタイムロック後に返金)

コアコード

solidity
function withdraw(bytes32 swapId, bytes32 secret) external {
    Swap storage swap = swaps[swapId];
    require(swap.state == SwapState.Locked, "Not locked");
    require(swap.receiver == msg.sender, "Not receiver");
    require(keccak256(abi.encodePacked(secret)) == swap.hashLock, "Wrong secret");

    swap.state = SwapState.Withdrawn;
    (bool ok, ) = swap.receiver.call{value: swap.amount}("");
    require(ok, "Transfer failed");
}

function refund(bytes32 swapId) external {
    Swap storage swap = swaps[swapId];
    require(swap.state == SwapState.Locked, "Not locked");
    require(swap.sender == msg.sender, "Not sender");
    require(block.timestamp > swap.timelock, "Timelock not expired");

    swap.state = SwapState.Refunded;
    (bool ok, ) = swap.sender.call{value: swap.amount}("");
    require(ok, "Transfer failed");
}

4. 遭遇した落とし穴

4.1 swapIdの一意性

swapId = keccak256(sender, receiver, amount, hashLock, timelock, block.timestamp) はタイムスタンプを含めて一意性を確保しています。しかし、同じブロック内でパラメータが完全に同一の2つのトランザクションは衝突します — 理論的には可能ですが、実際には極めて稀です。

4.2 クロスチェーンタイミング攻撃

AliceとBobのtimelock設定が不適切な場合、以下のような事態が発生し得ます:AliceのETHがタイムアウト返金されたが、BTC側ではsecretがすでに開示されている — AliceはBTCと返金されたETHの両方を手に入れる。

4.3 リプレイ攻撃対策

withdrawrefund は両方とも SwapState.Locked をチェックし、実行後すぐに状態をWithdrawn/Refundedに更新します。これは古典的なChecks-Effects-Interactionsパターンです — リエントランシを防ぎ、安全な状態遷移を保証します。

4.4 secretの情報漏洩

withdraw トランザクションのcalldataでsecretが公開された場合、MEVサーチャーが先回りしてあなたのsecretをコピーし、もう一方のチェーンのHTLCを解除する可能性があります。実際のクロスチェーン交換では、secret公開のタイミングが極めて重要です。

5. 落とし穴の原因

5.1

block.timestamp は同じブロック内で定数ですが、swapIdは6つのパラメータをすべて含みます。sender/receiver/amount/hashLock/timelockがすべて同一で同じブロック内で発生した場合、swapIdは衝突します。典型的な解決策:nonce を追加するか、直接インクリメントカウンターを使用する。

5.2

正しいtimelock設定ルール:

  • 開始者(Alice)のtimelockは受信者(Bob)よりも長くする
  • BobのBTC上のtimelockはAliceのETH上のtimelockよりも短くする(例:24時間 vs 48時間)
  • これにより、Aliceは24時間以内にsecretを開示する(そしてBobは48時間以内に開示する)か、交換を放棄する(両者がそれぞれ返金)

5.3

solidity
// ✅ CEI: 先に状態を更新
swap.state = SwapState.Withdrawn;
// その後で外部呼び出しを実行
(bool ok, ) = swap.receiver.call{value: swap.amount}("");

状態更新の前に call があると、攻撃者がwithdraw関数に再入してもう一度引き出すことができます。

6. 落とし穴の解決方法

  • swapIdの一意性:本番環境ではハッシュ計算ではなく swapCount++ をswapIdとして使用
  • Timelock勾配:開始者のtimelock > 受信者のtimelock、少なくとも2倍の差をつける
  • CEIパターン:状態更新は常に外部呼び出しの前に行う
  • Secret保護:Flashbotsバンドルまたはプライベートトランザクションプールを使用してwithdrawトランザクションを中継

7. 技術的要点

要点説明
HTLCHash Time-Locked Contract — クロスチェーンアトミックスワップ標準
hashLockkeccak256(abi.encodePacked(secret)) — 原像を検証
timelockUnixタイムスタンプ、超過後に送信者が返金可能
状態マシンNone → Locked → Withdrawn/Refunded
CEIChecks-Effects-Interactions — リエントランシ防止
swapId設計block.timestampを含めて衝突防止
適用シナリオETH↔BTC、ETH↔ERC20、ライトニングネットワークルーティング

Built with AiAda