CordaとエンタープライズEVM(本記事ではHyperledger BESUを使用します)でアトミックスワップを実現する方法
はじめに
この記事では,Hyperledger Labs Harmoniaに基づいてCorda-EVMでアトミックスワップを実現する方法について説明します.Harmoniaについてはまた別の記事で詳しく説明したいと思いますが,Harmoniaの目的を一言で説明するならば次のようになるでしょう:
つまり,既存の技術では対応できない規制金融市場におけるIOPの一助になろうということです.
この考えを念頭に置いていただいてから,以降の説明を読んでいただけると幸いです.
参照:https://github.com/hyperledger-labs/harmonia
アトミックスワップとは
この記事をご覧の方には釈迦に説法かと存じますが,念のために一般的なブロックチェーンにおけるアトミックスワップ一般について説明させていただきます.
一言で言うとアトミックスワップは異なるブロックチェーン間での資産の直接交換を可能にする技術です.
特徴としては:
- 信頼できる第三者(仲介者)を必要としない
- 取引は全く行われないか,完全に完了するかのどちらかである(取引が非対称で終わらない)
といったことが挙げられます.
ユースケースとしては,通貨と通貨を交換するPvP(Payment VS Payment)やデジタルアセットとデジタル通貨の交換(Deliverly VS Payment)が挙げられます.
実現方式
アトミックスワップを実現するためには,実装方法によって多少の違いはあると思いますが,基本的に以下の流れは共通していると思います.
1. アリスが資産をロック:
アリスはボブに送りたい資産を自分のブロックチェーンでHTLCにロックします.このHTLCにはハッシュロックとタイムロックが設定されています.
アリスはプレイイメージを作成し,そのハッシュ値をボブに送ります.
2. ボブが資産をロック:
ボブはアリスに送りたい資産を自分のブロックチェーンでHTLCにロックします.このHTLCには同じハッシュ値のハッシュロックとタイムロックが設定されています.
3. アリスがボブの資産を引き出す:
アリスはボブのブロックチェーンでボブがロックした資産を引き出すために,プレイイメージを提示します.このプレイイメージが正しければ,アリスは資産を受け取ります.
4. ボブがアリスの資産を引き出す:
アリスがプレイイメージを公開したことで,ボブはそれを使用してアリスのブロックチェーンでロックされた資産を引き出します.
5. タイムロックの管理:
一定期間内に取引が完了しない場合.タイムロックが発動し,ロックされた資産は元の所有者に戻ります.
このプロセスにより,仲介者を必要とせずに異なるブロックチェーン間で安全に資産を交換することができます.
後に紹介するR3の参照実装が上記と異なる点は,Harmoniaは時限による取引の不成立を良しとしないので,ハッシュタイムロックではなく,単なるハッシュロックを使用していると言う点です.
参照実装
HarmoniaのGitHubリポジトリに上がっている参照実装のうち,R3が実装したものについてご紹介します.
HarmoniaではIOPを実現する上で考慮しなければならない,いくつかの原則や規制金融市場で求められる要件について説明されています. 実際に参照実装のフローを説明する前に,ここではR3の参照実装が順守しているアーキテクチャレベルのIOPの原則について紹介します.
- ファイナリティの尊重: トランザクションはネットワークでファイナライズされると,取り消すことができません.クロスネットワークの合意を待つ「保留」状態は,取り消すことができないローカルにファイナライズされたトランザクションの結果として実装する必要があります.
- 非決定性の回避: クロスネットワークワークフローの結果は,ネットワークのレイテンシー時間やタイムスタンプ機関間のクロックドリフトによって発生するような,観察可能な非決定性に依存すべきではありません.
- 一方的なボトルネックの排除: クロスネットワークワークフローの状態を進める機能は,1つの当事者に限定すべきではありません.
- 信頼の活用による証明の簡素化: 信頼できる当事者による事実の証明は事実そのものを検証するよりも簡単なことがよくあります.
これらは信頼性の高いIOPを実現する上で必要な要素と言えます.
最後の項目については,アトミックスワップは基本的に第三者の仲介を不要としている方式ですが,ビジネス要件や環境上の制約によっては信頼できる第三者の介入も必要というのがR3の見解です.実際にこの後に紹介する参照実装では,相手チェーンの証明の検証に第三者が介入するモデルとなっています.
参照:https://github.com/hyperledger-labs/harmonia/blob/main/docs/r3/architecture_principles.md
前提
- 取引方式:アトミックスワップ
- 対応基盤:
- Corda:4.9.8
- EVM:BESU
- 対応トークン:
- Corda:OwnableStateを実装した任意のState.
- EVM:ERC20, ERC721, ERC1155準拠トークン.
- ID:取引当事者がCordaとEVMにそれぞれにIDを持つ(Cordaの場合はノード,EVMの場合はEOA)
- IDのマッピング:Corda(オフチェーン)
- 証明検証:オンチェーン検証
- バリデータ:
- Corda:Oracleノード
- EVM:CordaのOracleノードのEOA
NW全体像
登場人物
- Alice:Cordaデジタルアセットの買い手(EVMのデジタル通貨の支払い手)
- Bob:Cordaデジタルアセット売り手主体(EVMのデジタル通貨の受け取り手)
- バリデータ:Corda/EVMトランザクションの検証と署名を行うCorda上のOracleノード(EOAを持つ)
オフチェーンで合意する内容
- 取引するデジタルアセットと支払いデジタル通貨
- 取引の期間
- バリデータの選定
- トランザクションを正当とみなすバリデータ署名数の閾値
- 互いのチェーンでのトランザクションの検証方法
- チェーン間でのIDのマッピング
- チェーン間のエンドポイント
など
DvP全体像
Flow
- オフレッジャーで取引を合意する(合意内容は前述の通り).
- Bob@CordaがDraft TX(ファイナライズ前のTXで,署名なし.)を作成し,Alice@Cordaに送付する.
- Alice@CordaはDraft TXを受領し,オフレッジャーで合意した内容と相違ないか検証する.
- Alice@EVMはデジタル通貨をスワップコントラクトにコミットする.パラメータはDraft TXID(この取引のIDとなる),トークンコントラクトアドレス,数量,Bob@EVMのEOAアドレス,バリデータ@EVMのEOAアドレスとその署名数の閾値m (< n),取引内容のハッシュ.
- Bob@Cordaはコミットのトランザクションレシートを検証し,事前に合意した内容と相違なければDraft TXに署名しファイナライズ,さらにバリデータ@Cordaに署名してもらう(Notaryの署名も入る).ここでCordaのデジタルアセットがロックされる.
- Alice@CordaはNotarizeされたDraft TXを検証し,事前に合意した内容と相違なければ,Alice@EVMはBob@EVMに支払いを行う.(Cordaのデジタルアセットのロックの証明を提出することで,Bob@EVMからでもEVM上のデジタル通貨の移転は可能.これにより取引の非対称終了を回避)
- Bob@CordaはEVMでの支払いのトランザクションレシートを検証し,事前に合意した内容と相違なければ,バリデータにEVMでのトランザクションの証明を作成してもらう.(Alice@Cordaからも可能)
- Bob@Cordaは上記で作成してもらった証明をパラメータに,ロックされていたCordaアセットのロックを解除し,Alice@Corda宛にアセットを移転する.(EVM上でのデジタル通貨の支払いの証明を提出することでAlice@CordaからでもCordaのデジタルアセットは可能.これにより取引の非対称終了を回避)
ハンズオン
文章による説明だけではイメージが湧きにくいと思いますので,ここからは実際にローカル環境でアプリを動かしていこうと思います.
環境準備
デモ用アプリは以下の環境で動作確認してます.
Software | version |
Java | Oracle JDK 8 |
node.js | 16.19.0 |
npm | 9.7.1 |
npx | 9.7.1 |
Intellij | ~v2021.x.y Community Edition |
Docker | Docker Desktop 4.28.0 |
OS | macOS 14.2.1 |
BESUテストネットワークセットアップ
- quickstartのソース一式を取得します
- ネットワーク:Hyperledger BESU
- private transaction あり
- ロギング:Loki
- Chainlens なし
- Blockscount なし
- ソースの保存先:
./quorum-test-network
- スマートコントラクトのデプロイ
次のコマンドでソース一式を取得します。
以降、ガイドに沿ってオプションを選択します。
npx quorum-dev-quickstart
上記の画面が表示されます.
今回は全てデフォルトの設定で問題ないため,全ての質問にENTERキーを押してください.
すると:
となります.
インストールが完了したらディレクトリに入ってrun.sh
を実行します。
上記の画面が表示されれば,BESUテストネットワークの記号が完了です.
続いて,アトミックスワップに必要なスマートコントラクトをデプロイしていきます.
以下のコマンドを実行し,GitHubからソースをクローンしてきます.
オリジナルのソースはhttps://github.com/hyperledger-labs/harmonia/tree/main/src/r3/atomic-swapにあるのですが,諸事情により手を加えないと動作しないので,SBI R3 Japanで一部改変した以下のプロジェクトを使用していきます.
git clone https://github.com/sbir3japan/harmonia-hands-on
./harmonia-hands-on/evmに移動します.
hardhat.config.jsのnetwork.besu.accountsにハードコーディングされている秘密鍵を,今回BESUで使用するEOAの秘密鍵に書き換えます.
besu: {
url: "http://localhost:8545",
accounts: [
"0x8bbbb1b345af56b560a5b20bd4b0ed1cd8cc9958a16262bc75118453cb546df7",
"0x4762e04d10832808a0aebdaa79c12de54afbe006bfffd228b3abcc494fe986f9",
"0x61dced5af778942996880120b303fc11ee28cc8e5036d2fdff619b5675ded3f0",
],
},
以下のコマンドを実行し,コントラクトをデプロイします.
npx hardhat run deploy.js --network besu
デプロイに成功すると以下の文字列が出力されます:
USD Tethered (USD) Token deployed to: 0x00fFD3548725459255f1e78A61A07f1539Db0271(デプロイ時に割り当てられるアドレス.環境によって異なります.)
GBP Tethered (GBP) Token deployed to: 0x899CE22c2142f60ecA9574c3781A076136D46373(デプロイ時に割り当てられるアドレス.環境によって異なります.)
SwapVault deployed to: 0x695Baaf717370fcBb42aB45CD83C531C27D79eF1(デプロイ時に割り当てられるアドレス.環境によって異なります.)
これらのアドレスはCorda側でFlowを実行する際に必要となりますので,メモしておいてください.
Corda テストネットワークのセットアップ
- Cordappのビルド
- Alice, Bob, Charlie, Notary ディレクトリが corda/build/nodes 配下に作成されるので,以下のコマンド実行し,DB migration を行います.
- migration が終了したら以下のコマンドを実行し,Corda ノードを起動します.
- Alice, Bob, Charlie ノードでそれぞれ以下のコマンドを実行し,EVM ネットワークとの接続設定をします.
- Alice
- Bob
- Charlie
harmonia-hands-on/cordaに移動します.
以下のコマンドを実行します.
./gradlew deployNodes
ビルドに成功すると以下の画面が表示されます.
テストネットワークの起動
java -jar corda.jar run-migration-scripts --core-schemas --app-schemas
java -jar corda.jar
start com.r3.corda.evminterop.workflows.demo.DemoNetworkSetUpFlow privateKey: "0x8bbbb1b345af56b560a5b20bd4b0ed1cd8cc9958a16262bc75118453cb546df7", protocolAddress: "0x695Baaf717370fcBb42aB45CD83C531C27D79eF1", evmDeployerAddress: "0x0fBDc686b912d7722dc86510934589E0AAf3b55A"
start com.r3.corda.evminterop.workflows.demo.DemoNetworkSetUpFlow privateKey: "0x4762e04d10832808a0aebdaa79c12de54afbe006bfffd228b3abcc494fe986f9", protocolAddress: "0x695Baaf717370fcBb42aB45CD83C531C27D79eF1", evmDeployerAddress: "0x0fBDc686b912d7722dc86510934589E0AAf3b55A"
start com.r3.corda.evminterop.workflows.demo.DemoNetworkSetUpFlow privateKey: "0xe6181caaffff94a09d7e332fc8da9884d99902c7874eb74354bdcadf411929f1", protocolAddress: "0x695Baaf717370fcBb42aB45CD83C531C27D79eF1", evmDeployerAddress: "0x0fBDc686b912d7722dc86510934589E0AAf3b55A"
取引開始
- Bob@CordaでRWAトークンを発行する
- Bob@CordaでDraft TXを作成し,Alice@Cordaに共有する
- Alice@Cordaはスワップコントラクトにデジタル通貨をコミットする
- Bob@EVMはデジタル通貨のコミットが確認できたら,Draft TXに署名する.
- Bob@Cordaはバリデータに署名依頼する.
- Bob@Cordaはスワップコントラクトコミットされたトークンを引き出す
- Alice@CordaはEVMにおける支払いの証明を作成する
- Alice@Cordaは上記で作成した支払い証明を以って,デジタルアセットを引き出す
start com.r3.corda.evminterop.workflows.IssueGenericAssetFlow assetName: "RWA"
start com.r3.corda.evminterop.workflows.demo.DemoBuildAndProposeDraftTransactionFlow transactionId: "{txid}", outputIndex: 0, buyerAddress: "0xf0E2Db6C8dC6c681bB5D6aD121A107f300e9B2b5", buyerCordaName: "Alice", sellerAddress: "0xcA843569e3427144cEad5e4d5999a3D0cCF92B8e", tokenAddress: "0x00fFD3548725459255f1e78A61A07f1539Db0271", protocolAddress: "0x695Baaf717370fcBb42aB45CD83C531C27D79eF1", amount: 100
start com.r3.corda.evminterop.workflows.swap.CommitWithTokenFlow transactionId: "{txid}", tokenAddress: "0x00fFD3548725459255f1e78A61A07f1539Db0271", amount: 100, recipient: "0xcA843569e3427144cEad5e4d5999a3D0cCF92B8e", signaturesThreshold: 1, signers: ["0xcA843569e3427144cEad5e4d5999a3D0cCF92B8e", "0xf0E2Db6C8dC6c681bB5D6aD121A107f300e9B2b5"]
start com.r3.corda.evminterop.workflows.demo.DemoSignDraftTransaction transactionId: "{txid}"
start com.r3.corda.evminterop.workflows.demo.NotarizationSignaturesCollectorFlow$CollectNotarizationSignaturesFlow transactionId: "{txid}", blocking: true
start com.r3.corda.evminterop.workflows.demo.DemoClaimCommitment transactionId: "{txid}"
start com.r3.corda.evminterop.workflows.demo.BlockSignaturesCollectorFlow$CollectBlockSignaturesFlow transactionId: "{txid}", blockNumber: 151, blocking: true
start com.r3.corda.evminterop.workflows.demo.DemoUnlockAssetFlow transactionId: "{txid}", blockNumber: 151, transactionIndex: 0
以上の操作を行うことでCorda上のデジタルアセットのBob→Aliceへの移転,EVM上のデジタル通貨のAlice→Bobへの支払いがアトミックに完了しているはずです.
取引結果の確認
実際に資産及び通貨が移動しているかは以下のコマンドで確認できます.
- Cordaデジタルアセットの確認(Aliceノードで実行)
run vaultQuery contractStateType: com.r3.corda.evminterop.workflows.GenericAssetState
- Alice@EVMのデジタル通貨の残高確認
curl -X POST http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"method":"eth_call",
"params":[
{
"to": "0x00fFD3548725459255f1e78A61A07f1539Db0271",
"data": "70a08231000000000000000000000000f0E2Db6C8dC6c681bB5D6aD121A107f300e9B2b5"
},
"latest"
],
"id":1
}'
- Bob@EVMのデジタル通貨の残高確認
curl -X POST http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"method":"eth_call",
"params":[
{
"to": "0x00fFD3548725459255f1e78A61A07f1539Db0271",
"data": "70a08231000000000000000000000000cA843569e3427144cEad5e4d5999a3D0cCF92B8e"
},
"latest"
],
"id":1
}'
お疲れ様でした.
おわりに
この記事ではHarmoniのR3参照実装に基づくCorda-EVMのアトミックスワップについて説明しました.
ここまでの説明を受けて,「こんなことをしなくてもトランザクションマネージャによる2層コミットで十分ではないか」と思った方もいると思います.まさにその通りで,やりたいことの要件が2層コミットで満たせるのならそれに越したことはありません.ミッションクリティカルでない限定的なユースケースのIOPでしたらトランザクションマネージャによる2層コミットでも十分だと思います.
ただ,次の要件がある場合はアトミックスワップする価値があると思います.
- オペレーションミスが許されない規制金融市場のユースケースである
- ステークホルダーを完全には信用できない
- 仲介者を不在にしたい(プライバシー担保,単一障害点回避,コスト削減,運営座組の簡素化)
- 将来的にグローバルなデジタル流動性プールへの接続を視野に入れている
本記事があなたのクロスチェーンIOPを実現する一助となれれば幸いです.
<ご質問・ご要望の例>
- Corda Portalの記事について質問したい
- ブロックチェーンを活用した新規事業を相談したい
- 企業でのブロックチェーン活用方法を教えて欲しい 等々