ビットコインとイーサリアムの相互運用性
ブロックチェーン界隈での取り組みの1つに「相互運用性」というものがあります。英語ではInteroperability(インターオペラビリティ)と呼ばれていて、異なるブロックチェーン間でもコインの交換が相互にできる状態のことを指します。
相互運用性を目指したブロックチェーンにはCosmosやPolkadot、Kyber Networkなどがあり、Cosmosの場合、各ブロックチェーンとそれらを中継するハブから成り立ちます。
相互運用を実現するには、ハブやブリッジとなるエンティティがあったほうが便利なのかもしれません。また、そのエンティティが取引所のような中央集権型ではなく分散型であるブロックチェーンのようなシステムの方が、みんなが目指すトラストレスな世界に近づくのでしょう。
ただし、異なるブロックチェーン間を中継するためには、果たしてハブとしてのブロックチェーンが必要なのか?と言うとそんなことはなく、相互にコインを交換する方法が存在します。それはアトミックスワップと言われるもので、以前から存在するテクニックでした。
今回は、そのテクニックを使ってイーサリアムからビットコイン上のライトニングネットワークへコインを移動してくれるサービス「Redshift」について、そのスマートコントラクトと実際の交換フローについて解説したいと思います。
ユーザーの操作イメージは以下
- LNへ移動したいコインの金額を指定し、インボイスを発行する
- デポジット用アドレスへETHを送金する
- 2の送金完了後、1のインボイス宛にビットコインが送金される
2.デポジット用アドレスへの送金では、Redshift運営側のアドレスへ送金しているわけではなく、スマートコントラクトへETHを送金しています。この時点でのETHは空中に浮いている状態です。実際のコントラクトコードは以下になります。ポイントは、インボイス発行時に生成するPreimageから作るハッシュ値paymentHashをセットしているところです。
function fund(bytes16 orderUUID, bytes32 paymentHash) external payable {
SwapOrder storage order = orders[orderUUID];
if (!order.exist) {
order.user = msg.sender;
order.exist = true;
order.paymentHash = paymentHash;
order.refundBlockHeight = block.number + refundDelay;
order.state = OrderState.HasFundingBalance;
order.onchainAmount = 0;
} else {
require(order.state == OrderState.HasFundingBalance, "Order already claimed or refunded.");
}
order.onchainAmount += msg.value;
emit OrderFundingReceived(orderUUID, order.onchainAmount, order.paymentHash, order.refundBlockHeight);
}
ここで、Redshift運営側は1.で発行したインボイスに対してビットコインを送金します。この時、運営側はユーザーからPreimageと呼ばれるレシートを受け取ります。このPreimageを使うことで、空中に浮いているETHを引き出すことができます。実際のコントラクトコードは以下になります。運営側は受け取ったPreimageを引数にclaim関数を実行することで、ETHを取り出しています。
function claim(bytes16 orderUUID, bytes32 preimage) external {
SwapOrder storage order = orders[orderUUID];
require(order.exist == true, "Order does not exist.");
require(order.state == OrderState.HasFundingBalance, "Order cannot be claimed.");
require(sha256(abi.encodePacked(preimage)) == order.paymentHash, "Incorrect payment preimage.");
require(block.number <= order.refundBlockHeight, "Too late to claim.");
order.preimage = preimage;
order.state = OrderState.Claimed;
(bool success, ) = owner.call.value(order.onchainAmount)("");
require(success, "Transfer failed.");
emit OrderClaimed(orderUUID);
}
上記の流れをフローチャートにしたのが以下となります。