Develop with pleasure!

福岡でCloudとかBlockchainとか。

非対話型のSchnorr署名のハーフアグリゲーション

先日、Blockstreamのブログ記事↓で、非対話型のSchnorrの署名集約スキームについて知ったので、

medium.com

↑にリンクされているドラフトBIPに記載されていた↓のペーパーを斜め読みしてみた。

https://eprint.iacr.org/2022/222.pdf

Schnorr署名の集約と言えば、Bitcoinの文脈だと、MuSig2などの対話型の集約プロトコルが有名で、このような集約方法をフルアグリゲーションと呼ぶみたい。これはマルチシグをSchnorr署名の集約特性を利用して実現する方式で、複数の参加者が共通のメッセージに対して対話的に署名を生成し、それを単一の署名に集約する。

↑のハーフアグリゲーションは、マルチシグのような対話型の署名方式ではなく、署名者同士はそれぞれ互いを知らずに個々の署名を生成し、その後不特定の集約者がその署名を集約することを想定している。マルチシグは対話型で、共通のチャレンジに対して署名を生成し、それが単一のSchnorr署名と見分けが付かないような集約アプローチになるのに対して、ハーフアグリゲーションでは各署名者が異なるメッセージにそれぞれ署名し、その署名のサイズが個々の署名データの合算値の約半分になるというもの(フルアグリゲーションのように単一のSchnorr署名と同じサイズになる訳ではない)。そして個々の有効なSchnorr署名をインクリメンタル/シーケンシャルに集約することができる。

この仕組みを活用すると、例えば、

といった事が可能になる。

ペーパーでは、3つのハーフアグリゲーション方式が説明されている(以下、表記は楕円曲線ベースのものに変えてる)。

ASchnorr

Chalkiasらによって提案された集約方式をASchnorrと呼んでいる。

公開鍵 {X_1, X_2}とメッセージ {m_1, m_2}に対するSchnorr署名を {\sigma_1 = (R_1, s_1), \sigma_2 = (R_2, s_2)}として、各チャレンジを {c_1 = H_1(R_1, m_1), c_2 = H_1(R_2, m_2)}とした場合、それぞれ有効なSchnorr署名であれば、以下が成立する。

  •  {s_1G = R_1 + c_1X_1}
  •  {s_2G = R_2 + c_2X_2}

この2つの署名を単純にハーフアグリゲーションしようとすると、 {\tilde{s} = s_1 + s_2}とする方法で、 {\tilde{s}G = R_1 + c_1X_1 + R_2 + c_2X_2}が成立するか検証する方法。

ただ、この集約方法は安全ではない。ある署名のチャレンジ(たとえば、 {c_1})がもう1つの署名のコミットメント( {R_2})に依存していないため、例えば {R_2} {R_2 = r_2G - (R_1 + c_1X_1)}と細工すれば、 {X_1}の離散対数の知識を必要とせずに、 {X_2}の離散対数の知識のみで集約署名を偽造することができる。

ASchnorrでは、各署名に以下のような外部係数を使用して、この偽造問題に対処している。

 {L = \lbrace (R_1, X_1, m_1), (R_2, X_2, m_2) \rbrace}

および、

 {a_1 = H_2(L, 1), a_2 = H_2(L, 2)}

として、集約署名を {\tilde{s} = a_1s_1 + a_2s_2}として、その署名の有効性の検証は、 {\tilde{s}G = a_1 \cdot (R_1 + c_1X_1) + a_2\cdot(R_2 + c_2X_2)}とするというもの。MuSigのアプローチと似てる。

IASchnorr

↑のASchnorrで集約は可能だが、インクリメンタルな集約サポートしていないので、個々の署名がすべて集まってから集約する必要がある。そこで、インクリメンタルな集約をサポートするハーフアグリゲーション方式がIASchnorrと呼ばれる方式。

↑の2つの署名が集約された後に、公開鍵 {X_3}とメッセージ {m_3}に対する署名 {\sigma_3 = (R_3, s_s)}が届いて、これを既存の集約署名に追加するケースを想定する。

単純に集約使用とすると、既に集約済みの署名を通常の署名として扱うことで再度ASchnorrを行うというもの。つまり、

 {L' = \lbrace L, (R_3, X_3, m_3) \rbrace}

および、

 {a'_1 = H_2(L', 1), a'_2 = H_2(L', 2)}

として、 {\tilde{s}' = a'_1 \tilde{s} + a'_2s_3}という集約をする方法。

ただ、この方法の問題は、3つの署名が同じタイミングで集約された場合と、↑のようにインクリメンタルに集約された場合とで、集約結果の署名値が異なり、正しく検証するために、それがどのように集約されたのかという情報が追加で必要になる。

IASchnorrでは、係数を個々の署名すべてから計算するのではなく、それ以前の署名にのみ依存させるよう生成することで、この問題に対処するようになっている。つまり

 {L_i = \lbrace (R_1, X_1, m_1), ..., (R_i, X_i, m_i)\rbrace}

として、i番めの係数は {a_i = H_2(L_i)}となる。こうすると、↑のように署名集約のタイミングによって集約値が異なるようなケースを回避できる。

この方式では、n個の公開鍵とメッセージに対する最終的な署名値は、 {(\lbrace R_1, ..., R_n \rbrace, \tilde{s}'_n)}

SASchnorr

最後に、SASchnorrが、このペーパーで新しく提案されているハーフアグリゲーションの方式で、シーケンシャルに集約した個別の署名をリカバリーできるという特徴を持つ。また集約の方式もASchnorrやIASchnorrとは結構違う。

署名者nは、n-1個の公開鍵 {X_1, ..., X_{n-1}}とメッセージ {m_1, ..., m_{n-1}}に対する集約署名 {(\tilde{R}_{n-1}, \lbrace s_1, ..., s_{n-1} \rbrace)}を受け取ると、 {R_n = r_nG}の代わりに集約コミットメント {\tilde{R_n} = \tilde{R}_{n-1} + R_n}を計算し、チャレンジ {c_n = H(\tilde{R}_n, X_n, m_n, s_{n-1}, n)}を計算し、署名値 {s_n = r_n + c_n x_i}を計算する。

 {(\tilde{R}_{n}, \lbrace s_1, ..., s_{n} \rbrace)}を受け取った検証者は、 {c_n}を計算すると、 {R_n = s_nG - c_nX_n}を計算することで、1つ前の {\tilde{R}_{n-1} = \tilde{R}_n - R_n}を復元することができる。これを繰り返すことで、検証者はすべての個々の署名を復元することができる。

ペーパーでは、ランダムオラクルモデルの下で、Schnorr署名と同等の安全性を証明している。

↑の方式見た感じだと、各署名値が前の署名値に依存しているので、署名フェーズがシーケンシャルになる必要が出てくる。

データサイズ

↑のようにn個の公開鍵とメッセージに対して、以下署名データが必要になる。

  • IASchnorrの場合、 {(\lbrace R_1, ..., R_n \rbrace, \tilde{s}'_n)}=n個のRと1つのs
  • SASchnorrの場合、 {(\tilde{R}_{n}, \lbrace s_1, ..., s_{n} \rbrace)}=1つのR値とn個のs値

個別のSchnorr署名だとn個のRとn個のsで構成されるため、約半分のデータサイズになっていることが分かる。だからハーフアグリゲーションというネーミングなのね。

ドラフトBIP

ドラフトBIPでは、インクリメンタルなハーフアグリゲーションをサポートするようなので、↑の2番めのアプローチがベースになってる模様。まぁ、ブロックやトランザクションの署名を集約するようなケースだと、SASchnorrのようなシーケンシャルな署名プロセスは採用できないので、インクリメンタルなアプローチになるよね。SASchnorrの方は証明書チェーンやルーティングプロトコルなでの利用が期待されているみたい。

IONのDIDを作ってみる

IONはMSが中心になって開発したDIDのプロトコルの1つ(did:ion)。実体は、アンカリング先のブロックチェーンBitcoin、Content-Addressed Storage System(CAS)ノードにIPFSを用いたSidetreeプロトコルになる。この他にアンカリング先をEthereumにしてSidetreeを実装したElement(did:elem)とか、他にもFabricやAmazon QLDB、S3を使った実装もあるみたい。

今回は、そんなION(正確にはSidetree)で発行されるDIDを実際に作ってみる。

DIDの作成

Sidetreeでは、以下の3つの鍵ペアを生成する。

  • 署名鍵:DID自体に関連付けられるメインの鍵で、DIDのユースケースにおける署名や認証に用いる鍵。
  • Update Key:DIDの更新とUpdate Key自体の更新に使用する鍵。
  • Recovery Key:DIDの更新、失効とUpdate Key、Recovery Key自体の更新に使用する鍵。

これらの鍵を利用して、DIDのCreate / Update/ Recover / Deactivateといった操作をサポートする。鍵は、Bitcoinと同じ楕円曲線secp256k1の鍵で、デジタル署名スキームはECDSA。

DIDを作成する場合、まず最初に上記の3つの鍵ペアを生成する。

続いて、DID Documentを作成する。DID DocumentにはDIDに関連付けられる公開鍵のリスト(↑の署名鍵)と、serviceを定義できる。serviceは、このDIDの保持するエンティティのドメインのエンドポイントや、Identity Hubのエンドポイントなどを指定できる。

次に、DIDのステートを変更するためのDID State Patcheを作成する。具体的には、DID Documentの内容を元に以下のようなreplace Patch ActionのJSONオブジェクトを作成する(今回はserviceは未定義)。

[
  {
    "action": "replace",
    "document": {
      "publicKeys": [
        {
          "id": "signing-key",
          "publicKeyJwk": {
            "crv": "secp256k1",
            "kty": "EC",
            "x": "LyZoDfdajWdfTlAqUT8V8Kb3Xy0Rc9PVXoiGpS82prs",
            "y": "vgV-yaevK2o0SXobM996p6RLCurb97ySnEj5mLBd5B0"
          },
          "purposes": []
        }
      ],
      "services": []
    }
  }
]

次にUpdate Keyと作成したPatchデータからDeltaオブジェクトを生成する↓

{
  "patches": [
    <↑のPatchオブジェクト>
  ],
  "updateCommitment": "EiB4CgS-uYVMAEKJOI1rV061MTUPqJW8tzjOCDOrBHERjA"
}

ここでUpdate Keyはそのまま使われるのではなく、Json Web Key形式のJCSで正規化されたJSONデータをSHA256ハッシュし、それをMultihashでエンコードしたものを更にBase64エンコードしたものを使用する。つまりUpdate Keyに対するコミットメントを指定することになる。

※ 基本的にDID Documentで公開する必要のある署名鍵以外(Update KeyおよびRecovery Key)は、登録時にはそのコミットメント値が使用される。

続いて、↑のDeltaオブジェクトのハッシュとRecovery Keyを使ってSuffixオブジェクトを生成する↓

{
  "deltaHash": "EiCUyXdZZrJH18glwLiuSpFM_CT5LRP7SRqcTcDnNRHEOw",
  "recoveryCommitment": "EiAN0dDol63ocIfiQQK9TiPYoOd6lXM4pr7JzpVj7h4VXw"
}

ここでも、Recovery Keyのコミットメントが使われる。また、Deltaオブジェクトのハッシュも、これらのコミットメントと同様の方法で計算される。

そして最後に、↑のSuffixオブジェクトのハッシュ値がDIDの値になる。

IONの場合、DIDの形式としてShort FormのDIDとLong FormのDIDの2通りの表現があり、Long FormのDIDはDeltaオブジェクト、Suffixオブジェクト両方を展開したJSONデータから作られる。つまり、DIDドキュメントを含むすべてのデータで構成される。そのため、ブロックチェーンやIPFSなどにアンカリングされていないDIDでも、Long Form形式のDIDであれば、そのデータを使ってDIDの検証がそのままできる。これは、そもそもDIDをブロックチェーンやIPFSにアンカリングせずに利用可能になるというメリットや*1、アンカリングまでの時間的な遅延の解消などに有用な仕組み。

↑のように、SidetreeはDIDへの変更をPatcheという形で定義して管理しようとしているのが分かる。UpdateRecoverという操作では、変更内容を定義したDeltaオブジェクトを作成し、現在有効なUpdate Key / Recovery Keyのコミットメントに対応する公開鍵とそのデジタル署名を提供することで、DIDに対する変更を適用するようになっている。発行時のDIDに対して、順番にパッチを適用していけば、最新のDIDの有効性が検証できるみたいな感じかな。

Rubyで実装してみた

なかなかドキュメントだけ見てても理解が深まらないので、↑の処理をRuby実装してみた。ロジックの詳細はコード参照

require 'sidetree'

recovery_key = Sidetree::Key.generate
update_key = Sidetree::Key.generate
signing_key = Sidetree::Key.generate(id: 'signing-key')

document = Sidetree::Model::Document.new(public_keys: [signing_key])

did = Sidetree::DID.create(document, update_key, recovery_key, method: Sidetree::Params::METHODS[:ion])

puts "DID: #{did.short_form}"
=> "did:ion:EiC4gAQxXVCnbGQKFW7wgr0nbrOxXsF5CnglU8j1sXARaA"

puts "Long-Form DID: #{did.to_s}"
=> "did:ion:EiC4gAQxXVCnbGQKFW7wgr0nbrOxXsF5CnglU8j1sXARaA:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJzaWduaW5nLWtleSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiJyU2RyX3dMSno5WEVFRHFkRGo4RjZCeDNKanlXR3RVZjNJY1c0OWJxQ21VIiwieSI6IlBwTUtvTXpmVHoxSVdnRDlyU3lUMC1Ya2NKTzFIb2JtWmtXMG1zM3lvaE0ifSwicHVycG9zZXMiOltdfV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRDMtOXE1ZjRYUTEwWWQySlNzM1QxYTh4Vloycmw2VFNic0xYYVdzVVBHbUEifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUFHblhKellkUDREcUk2blRnZjRSblFQQjRVaUVVS3pCZldWQlVRRXlkZ3FRIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlCVy1PRWRMLUtWYWo4NG42OEhDclRpcWNFd1RFRFZ6Z1I4bDhsb2tQc2NrdyJ9fQ"

op = did.create_op

puts "DID suffix data: #{op.suffix.to_h.to_json}"
=> "DID suffix data: {"deltaHash":"EiAGnXJzYdP4DqI6nTgf4RnQPB4UiEUKzBfWVBUQEydgqQ","recoveryCommitment":"EiBW-OEdL-KVaj84n68HCrTiqcEwTEDVzgR8l8lokPsckw"}"

puts "Document delta: #{op.delta.to_h.to_json}"
=> "Document delta: {"patches":[{"action":"replace","document":{"publicKeys":[{"id":"signing-key","publicKeyJwk":{"crv":"secp256k1","kty":"EC","x":"rSdr_wLJz9XEEDqdDj8F6Bx3JjyWGtUf3IcW49bqCmU","y":"PpMKoMzfTz1IWgD9rSyT0-XkcJO1HobmZkW0ms3yohM"},"purposes":[]}],"services":[]}}],"updateCommitment":"EiD3-9q5f4XQ10Yd2JSs3T1a8xVZ2rl6TSbsLXaWsUPGmA"}"

DIDの作成や操作方法は分かってきたので、次は、IPFSやBitcoinへのアンカリング方法を確認したい。

*1:ただ、アンカリングしないとそのDIDが最新かどうかは検証できない。こういった特性が必要な場合はアンカリングが必要になる。まぁ多くのユースケースではDIDの有効性よりVCの有効性を検証することの方が重要なんじゃないかとも思えるけど。

USDTを使ってBitcoin Coreをトレース

Bitcoin Coreでは、User-space Statically Defined Tracing(USDT、※Tetherではない)ベースのトレースポイントが提供されており、Linux環境で動作するBitcoin Coreでは、このトレースポイントを使って、内部のデータにアクセスできるようになっている。

Bitcoin Coreのトレースポイント

Bitcoin Coreには、v23.0の時点で以下のトレースポイントが組み込まれている。

参考:https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md

トレースポイント 内容
net:inbound_message P2Pネットワーク上のピアからメッセージを受信した際に呼び出される
net:outbound_message P2Pネットワーク上でピアにメッセージを送信する際に呼び出される
validation:block_connected ブロックがチェーンに接続された後に呼び出される
utxocache:flush インメモリのUTXOキャッシュがフラッシュされた後に呼び出される
utxocache:add UTXOキャッシュにコインが追加された際に呼び出される
utxocache:spent UTXOキャッシュからコインが使用された際に呼び出される
utxocache:uncache UTXOキャッシュから意図的にアンロードされ際に呼び出される
coin_selection:selected_coins SelectCoinsが完了すると呼び出される
coin_selection:normal_create_tx_internal 最初のCreateTransactionInternalが完了した際に呼び出される
coin_selection:attempting_aps_create_tx CreateTransactionInternalが楽観的なAvoid Partial Spendsの選択試行のために2回目に呼び出された際に呼び出される
coin_selection:aps_create_tx_internal Avoid Partial Spendが有効になっている2回目のCreateTransactionInternalが完了すると呼び出される

各トレースポイントで取得可能なデータについては↑のドキュメント参照。

トーレス

実際に、上記のトレースポイントに対してトレースしてみる。Bitcoin Coreではリポジトリcontrib/tracingにトレースのサンプルが用意されてる。

bpftraceを使ったトレース

最初は、bpftraceを使ったトレース。

bpftraceをまだインストールしていない場合はインストールから↓(Ubuntu

$ sudo apt-get install bpftrace

P2Pのネットワークメッセージの情報を出力するbpftraceのスクリプトが用意されてるので実行してみる。リポジトリのルートから以下のコマンドを実行↓

$ sudo bpftrace contrib/tracing/log_p2p_traffic.bt
Attaching 3 probes...
Logging P2P traffic
outbound 'inv' msg to peer 0 (outbound-full-relay, 73.166.84.222:8333) with 2 bytes
inbound 'inv' msg from peer 0 (outbound-full-relay, 159.100.255.18:8333) with 1 bytes
outbound 'getdata' msg to peer 0 (outbound-full-relay, 159.100.255.18:8333) with 2 bytes
inbound 'inv' msg from peer 0 (outbound-full-relay, 94.231.253.18:8333) with 1 bytes
outbound 'addrv2' msg to peer 0 (outbound-full-relay, 104.36.175.37:8333) with 2 bytes
inbound 'tx' msg from peer 0 (outbound-full-relay, 159.100.255.18:8333) with 1 bytes
inbound 'inv' msg from peer 0 (outbound-full-relay, 65.21.122.162:8333) with 1 bytes
inbound 'inv' msg from peer 0 (outbound-full-relay, 128.0.51.17:8333) with 1 bytes
...

と、net:inbound_messagenet:outbound_messageの2つのトレースポイントをフックして、データを出力できる。

log_p2p_traffic.btに実行中のbitcoindのパスを記載する必要があり、ローカルでビルドしたものではない場合、適宜パスを修正する必要がある。

pythonでのトレース

サンプルとして、Pythonのスクリプトもある。こちらは、事前に↓のインストールが必要

$ sudo apt-get install python3-bpfcc 

そして、p2p_monitor.pyを実行すると、

$ sudo python3 contrib/tracing/p2p_monitor.py <実行中のbitcoindのパス>

ターミナルにP2Pメッセージのやりとりが表示される↓

Pythonの方は、BCC(BPF Compiler Collection)を使ってeBPF用のバイトコードコンパイルしていて、eBPFのコードはPythonのコード内にCで書く必要がある。bpftraceの方は、独自のDSLを記述する形なので、DSLの仕様さえ学習すればこっちの方がカジュアルに使えそう。

MWEBチェーン上での送金と非対話型のトランザクションの構築

前回、MWEBチェーンへのペグインまで行ったので↓

techmedia-think.hatenablog.com

今回は、MWEBチェーン上でMWトランザクションを使った送金をしてみる。

MWEB上での送金

新しいmwebタイプのアドレスを作成し、そこに送金する↓

$  ./litecoin-cli sendtoaddress tmweb1qqfhtsgcqxz0ez0je526ay5v8uerrtjsu33q7may3v43kwe4n0paz7qlwzsag2dschehz7qm4p4f8gfpu3cngpwsthjckhlah9txn24ukxulxx2yf 0.0001
2c83e8f14fed5bb6c8d4630c0d833f860dc48262b31af05f3d3acab8a362ca9e

このトランザクションの内容は↓

$ ./litecoin-cli getrawtransaction 2c83e8f14fed5bb6c8d4630c0d833f860dc48262b31af05f3d3acab8a362ca9e 1
{
  "txid": "2c83e8f14fed5bb6c8d4630c0d833f860dc48262b31af05f3d3acab8a362ca9e",
  "hash": "2c83e8f14fed5bb6c8d4630c0d833f860dc48262b31af05f3d3acab8a362ca9e",
  "version": 2,
  "size": 2203,
  "vsize": 0,
  "weight": 0,
  "locktime": 2352363,
  "vin": [
    {
      "ismweb": true,
      "output_id": "147785fb93659118be70445d947496dbedd7983086446f73d5f803eec2ff7691"
    }
  ],
  "vout": [
    {
      "ismweb": true,
      "output_id": "fc93c77ff288e5e2a0247662ffaf5470878b99a2805a76b8e4715d1b5ebfbf1c"
    },
    {
      "ismweb": true,
      "output_id": "fda598167f8f0973b711427c21673a90093b9ca2b8cd6ef285c7879d6ecb2f22"
    }
  ],
  "vkern": [
    {
      "kernel_id": "9eca62a3b8ca3a3d5ff01ab36282c40d863f830d0c63d4c8b65bed4ff1e8832c",
      "fee": 0.00003900,
      "pegin": 0.00000000,
      "pegout": [
      ]
    }
  ],
...

※ ちなみにトランザクションがブロックに格納されるとgetrawtransactionはデータを返さなくなる。これはトランザクション・カットスルーによりチェーン上から消えるためだと思われる。ただ、ウォレットRPCであるgettransactionを使えば確認は可能。

↑のレスポンスを見ると、アウトプットにはoutput_idしか表示されていないが、MWEBのアウトプットは以下の要素で構成されており、output_idはこれらのデータから生成したハッシュ値になる。

  • commitment:コインの量を秘匿したPedersen commitment
  • sender_pubkey:送信者がトランザクション作成時に作成する一時鍵
  • receiver_pubkey:受信者の公開鍵。ステルスアドレスの使用鍵の公開鍵に共有シークレットから導出した値を乗算して導出される公開鍵であるため、チェーン上でステルスアドレスの公開鍵と直接リンクすることはない。
  • message:アウトプットに関する以下のメタデータ
    • 機能ビット
    • 鍵交換に使用する公開鍵:送信者が生成したシークレットを受信者のステルスアドレスの使用鍵の公開鍵に乗算して導出した公開鍵。
    • View Tag:受信者が自分宛の送金であることの判断に使用するタグ。送信者が生成したシークレットを受信者のステルスアドレスのスキャン鍵の公開鍵に乗算して導出した公開鍵のハッシュ値の先頭1バイト。
    • 暗号化されたブラインドファクター
    • 暗号化されたコインの量
  • rangeproof:コミットしたコインの量がある範囲内にあることを証明する範囲証明のデータ
  • signature:送信者がsender_pubkeyに対応する秘密鍵messageに署名した署名データ

トランザクションは送信者が作成するので、受信者にだけ送信したアウトプットのコインの量とブラインドファクターを伝えるため、それぞれ暗号化した上でmessageにセットしている。暗号鍵は送信者と受信者間のECDHで導出。

↑のデータもgettransaction RPCで返してくれればいいんだけど現状output_idしか返ってこない。一応、ブロックに格納されればgetblock RPCをverbosityを2で呼べば、上記のデータも確認できる。

$ ./litecoin-cli getblock 28a45a7be6451ee0ea3711f98a5ccd873a924527ab57b2770c7f566d381d1edf 2
{
...
  "mweb": {
  ...
    "outputs": [
      {
        "output_id": "fc93c77ff288e5e2a0247662ffaf5470878b99a2805a76b8e4715d1b5ebfbf1c",
        "commit": "09ae2d9b5e4165866b243f0f9904c1cad209fd1555ac73f082ffd54da84f104779",
        "sender_pubkey": "03142b67a14b6a5b25c5ad6dcc27e9460df313e2471fa27423afdba16fb1cd687b",
        "receiver_pubkey": "03de1719eed6d102124709c1ea77f9dd1685a55f65a9528d8f95e9c3266baed22a",
        "range_proof": "45ba23016fe6bd67061a31b075e92368cbcc0866bca5676eafc3e92b2da2d56ef35dc23ef3013667c1fb5247757fbf65b13d2b83b519182b18574cc6939b11d4065cbaa033847a9c5807f1b20cdc6dcae6218c4b0aae437ddb34c64ba45b3878a8bef89605e3bf7616f40197b4e8874d4c3a23cc1e6ac742158dc85c030d832c2cd960f105ceffca0aacfe83d653d34c844592b0cb8cc4ea917b44893e09d340367724ec419de1a5cbd8f5a6b578061496ce2aec1887a1cfa39feb246c4f99b3c8a094501ab5e1d39021415fcb7a215d450218a45404dd5b9432a7b9072bb6a37ca8cbaba7e4d92be693a6b7bd36b2f131840db69a0467aa454b3158af0558cdf690f65dd0b41407cca1a1c6c43e4de0c60383148d9fb115a3194cdab9bfc3abaef46484dd3ff0ce8fde4e23da3c999688c54e71fd7c9a195f535c4e98ce16f887026bc97b4e37bbf65391dbc974d5c376b54b6601e513def1b57f9ed984bb1edba9002e7902990c915c0d0e5eeed1663c8d369965b7ed7352d7389ee0cca3ed62de420439c3598b77ec9093c34913e550bebf63429536b3a8e9571e9b9d8fef5a19ecc784009e0f52c653a81d6ce3bcf9eb1768965ce4724cadc65c6dd198837a57367c7b12913d0bb7c03c28f23101a31cdf41a63087395d413bca5ccfc6c7ac16450d93591ef0bd8ab31315cf46feb42755615c7ba8c3a1572eeab0a78b419e5d5b27dfffdd45cdf252243dad23b3a3341fc2539da2ff2d8aae55799ee6bbcbfba956a4adef7f7a2d45156be7e335357935ace521158a28921bad3192ba5d59b2b7c998399daa1527c7ff71d3291ee87610ab50ee9328efc548d403f573c8ae14e1be5aad1099276c1b2b60c2be22c5e7ec2a86585cce87407027c5dbbeadb76bff361890e9bc92b6d769f09c5c26b8d7a5963696ef8d8d38a6cae2f8a45440a2a2",
        "message": "0102abb3c11c7f9ad6ccdca7cbe4cc77c31ea5c1947fdc3ec4673dd199a0c90fd17f1b1aa7b9a2e319c7a0bfda7fa299099e47879f793cf4b31b45"
      },
...

何故かsignatureは出力されない(ソースコード変更すれば出力は可能)。

アウトプットに受信者ではなく送信者の公開鍵や署名があるのは、LitecoinがMimblewimbleを導入するにあたって非対話的にトランザクションを構築できるようにしているため↓

非対話型のトランザクション構築

Mimblewimbleプロトコルでは、インプットのコミットメントの合計とアウトプットのコミットメントの合計の差分、つまりPedersen commitmentのブラインドファクターの差分値である公開鍵に対して有効なデジタル署名を提供する必要がある。

インプットとアウトプットのブラインドファクターの差分値であるため、それに対してデジタル署名を作成するには、送信者と受信者の協力が必要になる。つまり、トランザクションの作成は対話型になる。

ただ、MWEBではトランザクションを非対話型で構築できるように拡張している。

Mimblewimbleプロトコルトランザクションの構築を対話的に行う必要があるのは、ブラインドファクターがそのコインの所有権を制御する秘密鍵に役割をしているため。送信者がアウトプットのブラインドファクターを知ってしまうと、送信者と受信者両方がコインの所有権を持つことになってしまうのでまずい。そのため、インプットのブラインドファクターは送信者のみが、アウトプットのブラインドファクターは受信者のみが知っているという状況になり、その状態で両者が部分的なSchnorr署名を作成し、それを集約することでデジタル署名を完成させる仕組みになっている。

MWEBでは、この対話型のプロセスを非対話型にするために、コインの所有権の仕組みに手を加えている。

インプットとアウトプットの差分のKernel Excessに対するデジタル署名は、そのままコインのインフレーションが行われていないことの証明に使用するが、コインの所有者であることの証明はこれとは別に行われる。

具体的には、↑でアウトプットにsender_pubkeyreceiver_pubkeyという2つの公開鍵が追加されているが、インプットが参照するアウトプットのreceiver_pubkeyと、アウトプット内のsender_pubkeyの差分であるOwner Offsetの提供を求めるというもの。コインを使用する際のインプットのreceiver_pubkeyは受信者しか知らないため、ブラインドファクターを送信者が知っていたとしても、そのアウトプットのreceiver_pubkeyの所有権の証明はできないため、送信者にブラインドファクターが知られていても、コインが盗まれることはない。

内容が少し古いけど詳細は↓(もともとLIP-0004として提案されていたけど、どうも現在のLIP-0004はまったく別の仕様の提案になってる)

techmedia-think.hatenablog.com

LitecoinでMimblewimbleが利用可能なMWEBチェーンにペグインしてみる

LitecoinはExtension Blockという手法を使ってMimblewimbleを導入し(MWEB)、mainnetで2022年5月22日にアクティベートされた。今回はそんなMimblewimbleを試してみる(mainnetじゃなくtestnetだけど)。

トランザクション構造が全く異なるMimblewimbleをソフトフォークでLitecoinに追加した仕組み(MWEB)およびMimblewimbleについては、↓GBECの動画や過去記事を参照:

Litecoin Core

LitecoinにMWEBのコンセンサスを実装したLitecoin Core v0.21.2をインストールする↓

github.com

今回は、testnetで起動しチェーンの同期が終わると、MWEBがアクティベートされてることを確認する↓

$ ./litecoin-cli getblockchaininfo
...
    "mweb": {
      "type": "bip8",
      "bip8": {
        "status": "active",
        "start_height": 2209536,
        "timeout_height": 2419200,
        "since": 2215584
      },
      "height": 2215584,
      "active": true
    }
...

コインの入手

Testnetのコインを入手する。最初にウォレットを作成して、アドレスを生成する。

$ ./litecoin-cli createwallet "default"
{
  "name": "default",
  "warning": ""
}
$ ./litecoin-cli getnewaddress
tltc1qzy3fzen7gvhazyqypwtfr2f0crj97wf5njv2hp

生成したアドレスで、LitecoinのtestnetのFaucetからコインを入手する。

MWEB

P2P関連の変更

Litecoin CoreのP2P関連のコードについては以下の変更が行われている模様:

  • ピアとのハンドシェイク時に、NODE_MWEB service flagを使って、MWEBをサポートしていることを通知
  • 新たにCompact Blockのバージョン3を定義し、cmpctblockメッセージでMWEBのブロックを含むブロックの伝播をサポート
  • inventoryのタイプに、MSG_MWEB_BLOCKMSG_MWEB_TXが追加され、MWEBのブロックやトランザクションの通知、伝播をサポート

Litecoin→MWEBへのペグイン

MWのトランザクションは、Litecoinの通常のブロックではなく、Extension Block側で処理される。まずは、通常のチェーンからMWEBのExtension Blockのチェーンにコインを移動(ペグイン)してみよう。

ステルスアドレス

ペグインするにあたって、MWEB用のアドレスを生成する。getnewaddress RPCに対してaddress_typemwebを指定する↓

$ ./litecoin-cli getnewaddress "" "mweb" 
tmweb1qq0h2drunehmgm55str9gp26xplhdwyekklc0g4l8kcqst0vxwv3z6qsx9up7zjmr0rurfpy95fmsx9k2pxs7at4u25nhsuz6dyqcu5030uh9vz0u

bech32エンコードされたアドレスが生成されているのが分かる。hrpは通常チェーンのP2WPKHやTaprootのアドレスとは違ってMWEBチェーン用にtmwebになってる。これはステルスアドレスで、witness programをデコードすると↓のデータになってるいる。

03eea68f93cdf68dd29058ca80ab460feed71336b7f0f457e7b60105bd8673222d02062f03e14b6378f8348485a2770316ca09a1eeaebc552778705a69018e51f17f

これは、以下の2つの公開鍵を連結したデータであることが分かる。

  • 03eea68f93cdf68dd29058ca80ab460feed71336b7f0f457e7b60105bd8673222d
  • 02062f03e14b6378f8348485a2770316ca09a1eeaebc552778705a69018e51f17f

Litecoinではデュアル・キー・ステルスアドレスでステルスアドレスを生成しているため、1つはスキャン用の公開鍵で、もう1つはこのコインを使用する際に使用する公開鍵になる。

ちなみに↑のステルスアドレスアドレスに対してdumpprivkeyを実行すると、後者の使用鍵の秘密鍵が取得できる。

MWEBへのペグインやMWEBでのコインの支払いには、基本的にこのステルスアドレスを使用することになる。

ペグイン・トランザクション

生成したアドレスに対して、Faucetで入手したコインを送ってみる↓

$ ./litecoin-cli sendtoaddress tmweb1qq0h2drunehmgm55str9gp26xplhdwyekklc0g4l8kcqst0vxwv3z6qsx9up7zjmr0rurfpy95fmsx9k2pxs7at4u25nhsuz6dyqcu5030uh9vz0u 0.0005
e091a837886c078e530bc7f50d5559ddb677d5e99bb94ef41021d99af7e4b9cb

このTxが標準チェーン→MWEBチェーンへコインを移動するペグイン・トランザクションで、そのアウトプットが↓

$ ./litecoin-cli  getrawtransaction e091a837886c078e530bc7f50d5559ddb677d5e99bb94ef41021d99af7e4b9cb 1
...
  "vout": [
    {
      "ismweb": false,
      "value": 0.00067400,
      "n": 0,
      "scriptPubKey": {
        "asm": "9 2ac1d7fcb62c17072e8ba34a3711eff346b3b7f4f937cacacafddde7301b701a",
        "hex": "59202ac1d7fcb62c17072e8ba34a3711eff346b3b7f4f937cacacafddde7301b701a",
        "type": "witness_mweb_pegin"
      }
    }
  ]

ここでscriptPubKeyに注目してみよう。通常、sendtoaddressでコインを送付すると、アウトプットのscriptPubkeyはsendtoaddressで指定したアドレスになるはずだが、ここでは全く違うsegwitアウトプットになっていることが分かる。このwitness programはMWEBブロックのペグイン・カーネルカーネルコミットメントの値を表しており、MWEB側のチェーンにLTCを移動する役割になる。

このペグイン・トランザクションのUTXO自体は、マイニングされたブロック内のHogExトランザクション↓ですぐに使用される。

ホグワーツ・エクスプレス

Extension Blockの仕様で、標準チェーン→EBへのペグイン、EB→標準チェーンへのペグアウトを処理するために作成されるのがインテグレーション・トランザクションで、標準ブロック内のトランザクションリストの最後に配置される。

LIIP-0003では、このインテグレーション・トランザクションを、Hogwarts Express(HogEx)トランザクション命名している。

↑のペグイントランザクションを含むブロックのHogExトランザクションは↓のような構成になっている。

$ ./litecoin-cli  getrawtransaction b941effb6e58795a1bcb0f27f5c3f75ee2325a247890d9897e594c9612c95ce1 1
{
  "txid": "b941effb6e58795a1bcb0f27f5c3f75ee2325a247890d9897e594c9612c95ce1",
  "hash": "b941effb6e58795a1bcb0f27f5c3f75ee2325a247890d9897e594c9612c95ce1",
  "version": 2,
  "size": 138,
  "vsize": 135,
  "weight": 540,
  "locktime": 0,
  "vin": [
    {
      "ismweb": false,
      "txid": "fdd51df3e001a542f9f2ef03612041b0192e42265972428437f0f5de23af93df",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "sequence": 4294967295
    },
    {
      "ismweb": false,
      "txid": "e091a837886c078e530bc7f50d5559ddb677d5e99bb94ef41021d99af7e4b9cb",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "ismweb": false,
      "value": 556411.83427695,
      "n": 0,
      "scriptPubKey": {
        "asm": "8 bffeac085bb3249c38558008b99c825dc091058ed7aeb42d3143311621afb834",
        "hex": "5820bffeac085bb3249c38558008b99c825dc091058ed7aeb42d3143311621afb834",
        "type": "witness_mweb_hogaddr"
      }
    }
  ],
  "hex": "02000000000802df93af23def5f0378442725926422e19b041206103eff2f942a501e0f31dd5fd0000000000ffffffffcbb9e4f79ad92110f44eb99be9d577b6dd59550df5c70b538e076c8837a891e00000000000ffffffff016f1cf9f89a320000225820bffeac085bb3249c38558008b99c825dc091058ed7aeb42d3143311621afb8340000000000",
  "blockhash": "a91e4217f411821e3e98a9e235dc76e1672fea4ddfd119426f5ed8742c4d41f9",
  "confirmations": 4680,
  "time": 1653051210,
  "blocktime": 1653051210
}

ここで、インテグレーション・トランザクションのインプット数は、ブロック内のペグイン・トランザクションの数 + 1で、アウトプットの数はブロック内のペグアウト・トランザクションの数 + 1であるため、このブロックには、自分がペグインしたトランザクションしかないことが分かる。

最初のインプットは前のブロックのExtAddr UTXO*1。2つめのアウトプットを見ると、↑で作成したペグイン・トランザクションのUTXOであることが分かる。

このブロックにはペグアウト・トランザクションは無いので、アウトプットはこのブロック時点でEBにあるコインの総量を表すExtAddrアウトプットのみ。このscriptPubykeyは、

OP_8 bffeac085bb3249c38558008b99c825dc091058ed7aeb42d3143311621afb834

と、witness versionがOP_8で、witness programはこのトランザクションが含まれるブロックに対応するMWEB側のブロックのハッシュ値と一致する。ちなみにこのアウトプットはLIP-0002ではExtension Addressとして定義されているもので、MWEBではホグワーツアドレス(HogAddr)と命名している。

この1つの前のブロックのインテグレーション・トランザクションのExtAddr UTXOが保持するコインの量が556411.83364195なので、0.000635がEBチェーンに移動している。なお、MWEB側のマイニングの手数料は全て、このHogExトランザクションで徴収されるようになっている。

Extension Block

getbock RPCを使うと標準チェーンのブロックの情報に加えて、MWEBのブロック情報も確認できるようになっている。その際、verbosityを2で指定すると、MWEBブロックのインプットやアウトプットの詳細も確認できる。

↑のペグイン・トランザクションが含まれるMWEBブロックは↓

$ ./litecoin-cli  getblock a91e4217f411821e3e98a9e235dc76e1672fea4ddfd119426f5ed8742c4d41f9 2
{
...
"mweb": {
    "hash": "bffeac085bb3249c38558008b99c825dc091058ed7aeb42d3143311621afb834",
    "height": 2344901,
    "kernel_offset": "f53d32b816b823d579b6cfde70e0db41da88f313fe5d328577cc81287d2bcbe1",
    "stealth_offset": "7b9f50585e9ed0af0a09815bd1db86ff99e3f4f5b7e20bfa1fd53065ed213c6f",
    "num_kernels": 1,
    "num_txos": 3498,
    "kernel_root": "7ea915a3bbd5baf771e21a58ec674f423d3fcc115a0cbd14d46a3001ae92c364",
    "output_root": "7d78fedf65359ba2c83a4374bf10593b1eeb715a127a3b775bf2f226e4643e02",
    "leaf_root": "c8b998f21bb2009320916422a7784cb1d139da25ee981bff653c06c4e8d8d53f",
    "inputs": [
    ],
    "outputs": [
      {
        "output_id": "147785fb93659118be70445d947496dbedd7983086446f73d5f803eec2ff7691",
        "commit": "09db9a47459753159ec66cc85fb35baf6fb2d16fb3062c860fb7cfc559fcbe2f0b",
        "sender_pubkey": "03414cf1b5c709f228dbae62706400baf82fffca94676e2975c3be2f3700e3efc3",
        "receiver_pubkey": "0230a1f2999419e1e745fdde8f67cda99064a9f17732f0768997e982511d836b6f",
        "range_proof": "37d5d12d300c3159eedb5ec207ca3108e1a4e7afe9b1831273e775f641d3a5dcb812ffb48ef7e369b63c198fcf646ecd700303ba9325ab7ca127510e8be124b801c38b04dbe0899555cb5fd8274f8c841d3a53fc15b3d0cbfd383b0ea2e7939f20a012f5edf7954cb9e8ec42aadefd0404d7698bd6164e3832f4d6454070bea5ea11ba97fb41ff0565d468e7ef0463151c6e2784c53f72d9c5e819a0b1df8c959597df8bea0eef6a768da1b5b71adf07e66a250b8096876e97e40fc5148b99b06968bf0b7cb08d8f6b20783b954ffb60fe430f2d500656cda59ff473fdb64013348edba2a29dcc8de5edb84050343ee528709f0fe9e1af31026c708290c7c68236deb4c2d85b8927f8204f2e37dce349dd69d717c7fe0b9986c75345693777608c51335fa5301e2d166ff5ac5ce021b2009abccd20997752099d2c39b829505531feab7a26d2592066ba7f10ed80081ad72a8f694ea2c2d9b4a893b60921cca915cf01b110b040d64fe5249fbd3eeaa0e6a73054b78f85d1228340b15c03730816cfa10d11a4a92bee916626a84cb4b1c352b91e8ef7f1b4202881145863051d0297d417383c6dfc97d3410d234d795476d1196e7daa5c8923a06a419387b91f3168bbf780683031759f0a5d1d9c12312547157446c986050550a412b12ffb5c2a0bf7acd111c524c0babe4edbef0a4c35f6ed24267f3441270790f18e844e015f8b67f0fa6ec5e3d2afb1cacb25aa1409a639a9254580b26c680a48c10e6572d3520bc565816e9432a8fa2752c0701d86808e6ed5f802976fb635c3f189932fceda2478fa16b22b1b6cd5c68bf3c3a930372442350f8974259c5711dac7433b4e175bcd3276dbc328361297e503a82fb25687a563dd112661786d270e80ea8c5c6c73cbc8ae864c9ffa9686772cb78c3cabb3a7a5c00cc16efcd044533bb04b3b44c5",
        "message": "01035efb14bd692a54b188abb3e1db529b7d7e4037b4367488b02e70efec67b810cf10892bd871463f2a12ea7157450f50ddc1edce1195afa2795e"
      },
      {
        "output_id": "a5cd2f891fb43046db53245881ca7c5e5b8bd2f6d36c44bfd6a107480d9c145a",
        "commit": "099f5fded238f7122ad54048bec596a8ea3fe7d4d90834ca50d2cb2cbb6eb17a3b",
        "sender_pubkey": "037f92823e0a59c99d91b26eae4d5c6eb6629e2d0993b9db9ff41e93319e5a82f8",
        "receiver_pubkey": "03356ee34bb852bb5d871dc00df708f6c5ade5dca081fc734a0368325de39fb241",
        "range_proof": "7b4dacdb883036e8cbced235159ba50d5b0f99ca0dbbf10b29317ff7814d7a8a837d14ead5b79616e43c5ddc0e68b21e3aae76ef0c9201e2b6d6da557eeac9c50135898a289e16e5269ff6ef44c28840384a2a32ba405dff7b4f7e454912589d987945705198c0ebe8040e7f7939ff9d2bb1b9f74104a773f2c5c8bf525832462a94a5e42e1aca1a4e9d637c2e1ebdf40f84b7503e926f9cac1209a8f385c9d9b5f5db3a5db0d42d047e938ddadbbb97b590c502d066eb1b1804a6f2517ebc358b82e9cfcbfeb4f0006fd29cc27e9219879c364b537aaf5ffea8f6844f044e50e681da9ae9ed4baa09282f1dd38518a9d3cba4ea5fb36bec6ac992f5e58e068ff04ed2df14c09da9cd18415c6a9ce3a0980f7931d20f0c9f6af2bfc91240b07a73ca1408b98e92b81911d0a05f4c95fa6f6dd2e1b250f91adcd1f8224730ccb5228021135aa40eeb04720be905852cc9f3de188718f709b81c210ded15628e181415028882ed97f2845b0504a781e539394b6f51f4a819870ac77e476b8820e5c56066fe53d5713b28a7f9f53810ef440f6435d03b27b43e9805db170cb748b624258a358386e647bf0dbfbe8b9b4d4847351723ed412aa6176d887196248c3fab24b05e8f6884ee6290131762ef7b3f995eeab0c8cf1a4618d244c40dd4196ea3a151b3cba8c4605b97f494b1f703acb50ec88e353c4531f9e9ff6585d63d3199372d3d22adcd10da885a9d17de6ce22973f08a2ea3d41d6a2760f799af5c01ca29415516e54a50d452f990421497e066311f2f4632edf7f853c07f2ea8adcf1873b02550fb9eb7a79e4ddafe41e77e4df95f83bac8329b9c170a64daec92f3bf42e2bdc583127fca1333fac98f1e206ced37bab4d5c115a42d3593a4dee44f196854824f6a0406f07d7206a87b6a41d752f3b9eeddd78123e60f9f474637bd5cf5a8",
        "message": "0103e5661d3743eb97e26eabecf67f020aaf4c6d6e27775be2d694bc702d266f47ca399fb31d3347d549537d3ce3aa7bdc1c837fea0b3bee61b93a"
      }
    ],
    "kernels": [
      {
        "kernel_id": "2ac1d7fcb62c17072e8ba34a3711eff346b3b7f4f937cacacafddde7301b701a",
        "features": 19,
        "commit": "0976f36bfc5f1a0f354fd9a3f829220a09f38c3118625b97829833fd242d00f756",
        "fee": 3900,
        "lock_height": 0,
        "excess": "0976f36bfc5f1a0f354fd9a3f829220a09f38c3118625b97829833fd242d00f756",
        "signature": "1033b9f23eb987efe0a4246e88864652f2ff2c8fd0a70ec81df6c904f48e1ceca7899103f22ba183c393fec13a262c6e1c881507402351d1c1050b70423ef923"
      }
    ]
  }
...

ここで、カーネルのデータに注目する。kernel_id: 2ac1d7fcb62c17072e8ba34a3711eff346b3b7f4f937cacacafddde7301b701aは、↑のペグイン・トランザクションのwitness programと一致しているのが分かる。標準チェーンからMWEBチェーンへの移動でマイナーがコインを不正な宛先に送れらないよう、MWEBのブロックのカーネルには、対応する標準チェーンのペグイン・トランザクションのコミットメント値が含まれるようになっている。

ここまでの一連のデータが、LIP-0003の↓の図のペグイン(緑色)の部分のデータになる。

https://github.com/litecoin-project/lips/raw/master/lip-0003/MW-EB.png

ペグイン後のUTXO

ちなみに、↑ペグインの際にsendtoaddressで0.0005送ったところ、↑のペグイン・トランザクションのアウトプットの値は、0.00067400になっており、listunspentすると2つUTXOが返ってくる↓

$ ./litecoin-cli listunspent
[
  {
    "txid": "e091a837886c078e530bc7f50d5559ddb677d5e99bb94ef41021d99af7e4b9cb",
    "amount": 0.00050000,
    "confirmations": 44,
    "spendable": true,
    "address": "tmweb1qq0h2drunehmgm55str9gp26xplhdwyekklc0g4l8kcqst0vxwv3z6qsx9up7zjmr0rurfpy95fmsx9k2pxs7at4u25nhsuz6dyqcu5030uh9vz0u",
    "label": ""
  },
  {
    "txid": "e091a837886c078e530bc7f50d5559ddb677d5e99bb94ef41021d99af7e4b9cb",
    "amount": 0.00013500,
    "confirmations": 44,
    "spendable": true,
    "address": "tmweb1qqwwlz6l4rdrxfpc0gvch6jtyyyav2tda8mgx6utjgy5zsfvxdcdegqmr0erv0drpnywetc5ekggnupd7thj6ue2z35tz90fu3g286fv3zcgn8zew"
  }
]

ただ通常のUTXOとは少し変わっていて、アウトプットのインデックスはなく、両方とも同じ↑のペグイン・トランザクションを参照してる。1つは、送金額と同じ0.0005で、もう1つは、0.000135。↑のペグイン・トランザクションがマイニングされたブロックのペグイン・カーネルの手数料が0.000039になってるので、これらを全て合算すると、ペグイン・トランザクションのアウトプットの額と一致する。

不思議なのは、ペグインしたMWEB側のUTXOが送金額を指定した0.0005だけでなく複数になっている点。単純に1つだけだと、そのコインの量が分かってしまうので、ブラインドするために最低2つのUTXOが作られてるのか?この辺りの挙動含めて、まだあまりドキュメントに書いてないのでよくわからな部分も多い。

長くなったので、MWEBでの送金やペグアウトについては、また別の記事で。

*1:EB側のチェーンに移動したコインを標準チェーン側でロックしておくためのUTXO