Develop with pleasure!

福岡でCloudとかBlockchainとか。

Bitcoinにアカウント機能を導入するLayer 2プロトコル「easypaysy」

BitcoinはUTXOモデルのブロックチェーンで、コインはアカウント単位ではなくUTXO単位(アドレス単位)に管理される。また、プライバシーの観点から支払い毎に異なるアドレスを使用することを推奨している。このような支払いは、コインの送金先のリンク性のハードルを上げることになるが、そのような知識を必要とする支払いはユーザーに複雑なUXを強いることになる。

これに対し、Bitcoinの支払いにおいても、各ユーザーが一意のアカウントを作成でき、各支払いもそのアカウントに対して行えるようにしようというのがLayer 2プロトコルとして提案されたeasypaysyプロトコルだ。ホワイトペーパーは↓

https://www.easypaysy.org/assets/easypaysy_white_paper.pdf

easypaysy プロトコル

easypaysyはBitcoinにアカウントベースの支払いを可能にするLayer 2プロトコルだ。実際の支払いは今まで通り支払い毎に新しいアドレス宛に送金するトランザクションが作られる訳だが、ユーザーはそのような事を意識することなく、ウォレットで毎回相手のアカウントを指定することでそのようなトランザクションを作成し、送金の受取側もそのような送金を検知するための仕組みをサポートするようになっている。

アカウントの作成

easypaysyを使った支払いをするためには、まずeasypaysyのアカウントIDを作成する必要がある。このアカウントはメールアドレスや銀行口座のようにユーザーを識別するためのIDで、アカウントベースの支払いをする際のIDとなる。このアカウントは以下の3つの情報を持つ。

  • Identity key
    メッセージへの署名や、送金者と受取人の間で暗号化されたメッセージを交換するのに使われる公開鍵。
  • Value key
    非対話型の支払いをする際に送金先の鍵の導出に使われる公開鍵。
  • ランデブー記述子
    アカウントの所有者が受け入れる支払いのタイプと、送金者がアカウントと通信する際に使用することができるプロトコルとその方法を定義したJSONドキュメント。

easypaysyアカウントを作成するには、上記3つの情報を持つトランザクションを作成し、ブロックチェーンに記録する。具体的にどういうトランザクションを作成するかというと、

① まずIdentity keyとValue keyの2つの公開鍵を使って2-of-2のマルチシグを構成する。

2 <Identity key> <Value key> 2 OP_CHECKMULTISIG

これをP2SHアドレスに変換し、そのP2SHアドレス宛にコインを送金する。

② ↑のトランザクションがブロックに格納されたら、続いて、そのUTXOをインプットにした以下のトランザクションを作成する。

  • Input
    1. Identity keyとValu keyのマルチシグのUTXO
  • Output
    1. Identity keyとValu keyのマルチシグのUTXO(つまりインプットと同じP2SHアドレス)
    2. OP_RETURN <ランデブー記述子>

①で作成したP2SHがインプットにあるため、そのscriptSigを確認するとIdentity keyとValue keyの値が分かる。また、OP_RETURNを使ってランデブー記述子のデータを格納する。

このトランザクションブロックチェーンに記録されると、アカウントに必要な3つの情報がブロックチェーンに記録されることになる。

②のトランザクションブロックチェーンに格納され100承認されるとeasypaysyアカウントがアクティベートされる。

アカウントID

↑のようにeasypaysyアカウントがアクティベートされるとアカウントIDができる。easypaysyアカウントのIDはブロックチェーンに記録された②のトランザクションのデータから以下の形式で表現される。

btc@<ブロック高>.<ブロック内のトランザクションのindex>/checksum

ブロック高は②のトランザクションが格納されたブロックのブロック高で、.の後にそのブロック内に②のトランザクションが入っている位置が続く。

チェックサムの計算

チェックサムは以下のアルゴリズムで計算される。

  1. ②のトランザクションが格納されたブロックのハッシュとマークルルートおよび②のWTXIDを結合し、そのハッシュ値を計算する。tx_digest = sha256(block chash || merkle root || wtxid)
  2. 32バイトのtx_digestを8バイトずつの4つのデータに分割する。
    • tx_digest_0 = tx_digest[0..7]
    • tx_digest_1 = tx_digest[8..15]
    • tx_digest_2 = tx_digest[16..23]
    • tx_digest_3 = tx_digest[24..31]
  3. 各tx_digest_xを1000で割ったあまりを計算する。
    • checksum_chunk_0 = tx_digest_0 % 1000
    • checksum_chunk_1 = tx_digest_1 % 1000
    • checksum_chunk_2 = tx_digest_2 % 1000
    • checksum_chunk_3 = tx_digest_3 % 1000

こうして計算した4つのチャンクがチェックサムデータになる。つまりチェックサムの値は、000-000-000-000から999-999-999-999の乱数とみなすことができる。

アカウントIDの表現方法

アカウントIDを表現する方法は3つある。

Canonical ID

Canonical IDは↑のアカウントIDの各データをそのまま10進数で表現する方法。

例えばブロック高が#543847のブロックの636番目トランザクションの場合のCanonical IDは、btc@543847.636/577-218-376-867

チェックサムは必ずしも4つすべて表記する必要はなく、btc@543847.636/577と省略してもいい。

Mnemonic ID

Mnemonic IDは↑のアカウントIDの各データをBIP-39のニーモニックワードを使って表現する方法。

↑のCanonical IDをMnemonic IDで表現するとbtc@cancel-mind.exhibit/motion-custom-fun-sugarとなる。

Domain ID

3つめのDomain IDは、DNSを利用した表現方法で、チェックサムが付いたアカウントIDを自身のドメインのTXTレコードに挿入するというもの。DNSに対してクエリするとそのドメインに対応するアカウントIDを返すという仕組み。

ランデブー記述子

アカウント情報の1つであるランデブー記述子は、アカウントの所有者が受け入れる支払いのタイプと、送金者がアカウントと通信する際に使用することができるプロトコルとその方法を定義するもので、②のトランザクションアウトプットでOP_RETURNを利用して記録される。

実際に定義されるのは以下のようなJSONデータになる。

{
 "Document_name": "EASYPAYSY_RENDEZVOUS_DESCRIPTOR",
 "Version": "0",
 "Accepted_payments": [
   "TYPE_1_RENDEZVOUS",
   "TYPE_2_IOC_OVERT"
 ],
 "Mail": "fiber.burden.erupt@gmail.com",
 "Bitmessage": "BM-2cTZhb···NnZTjC69ke9BieU"
}
  • Document_name: easypaysyドキュメントを識別する文字列の固定のリテラル
  • Version: ランデブー記述子のバージョン
  • Accepted_payments: アカウント所有者受け入れる支払いのリスト。現在可能なのは以下の4種類で、アカウントは少なくともこのペイメントタイプの1つを受け入れる必要がある。

Bitcoinの標準トランザクションのルールとして、OP_RETURNで登録可能なデータは80バイト以内であることという制限があるため、↑のJSONをそのまま記録しようとするとサイズオーバーになる。そこでeasypaysyでは、最適化された辞書ベースの圧縮アルゴリズムを使ってJSON形式のランデブー記述子をシリアライズして記録する。この圧縮アルゴリズムを使用すると↑のサンプルは7バイトになると。

支払いタイプ

easypaysyでは以下の4種類の支払いタイプをサポートしている。

TYPE_0_UNSAFE_FIXED

TYPE_0の支払いタイプは、各支払いにおいて常に同じアドレス、具体的にはValue keyに関連付けられたアドレスを使用する必要がある。これはプライバシーの欠如にもなるため基本的に推奨されていない。

TYPE_1_RENDEZVOUS

TYPE_1は、対話型の支払いで、支払いの都度、受取人と対話して支払先のアドレスを導出する。この支払いタイプを使用する場合、ランデブー記述子に定義された連絡プロトコルhttps, メール、Bitmessage、MQTT)とそのエンドポイントに従って、支払人が受取人に連絡し、支払先のアドレスを受け取る。この時、受取人は支払先のアドレスと一緒に受取人のeasypaysyアカウントのIdentity keyでアドレスに署名した署名データも一緒に返す。この署名データから、そのアドレスが確かに受取人によって承認されたアドレスであることを証明できるため、受取人による送金の否認防止につながる。

TYPE_2_IOC_OVERT, TYPE_3_IOC_COVERT

TYPE_2とTYPE_3は両方とも非対話型の支払い。IOCはInversion Of Controlの略で制御の反転、つまり支払いプロセスの制御を反転させ、各支払の宛先アドレスを選択するのは受取人ではなく支払人であるということを意味する。このためTYPE_1と違って、支払いの都度、受取人と対話する必要はなく、ランデブー記述子にも連絡プロトコルを定義する必要はない。

ボブからアリス(Value key = A = a * G)へeasypaysyアカウントを使った非対話型の支払いをする手順は以下の通り。※ アリスのeasypaysyアカウントはすでにチェーン上に公開されているとする。

  1. ボブは支払いに使用するキーペア:B = b * Gを作成する。
  2. ボブは以下の方法でnを計算し、それを秘密鍵とした公開鍵Nを計算する。
    • n = sha256(b * A) = sha256(b * a * G)
    • N = n * G
  3. ボブは別の公開鍵Cを以下の方法で計算する。
    • C = A + N (ボブはアリスのeasysypaysyアカウントからアリスのValue key = Aを知っている)
    • C = c * G = (a + n) * G (この時点でボブはaを知らないのでcを計算できず、アリスもnを知らないのでcを計算できない)
  4. ボブはCから送金先アドレスを導出し、最低2つのアウトプットを持つ支払いトランザクションを作成する。このアウトプットの1つはC宛の支払いで、もう1つはOP_RETURNアウトプットでBのデータが含まれる。このトランザクションをブロードキャストし、アリスにWTXIDを通知する(TXIDでもいいような?)。
  5. アリスはボブから通知を受け取るか、トランザクションを監視し、4のトランザクションについて以下を行う。
    1. OP_RETURN からBを取得する。
    2. nおよびc, Cを計算する。
      • n = sha256(a * B)
      • c = a + n
      • C = c * G
  6. アリスは4のトランザクションアウトプットにC宛の送金があるか確認する。アリスはそのコインを使用するために必要な秘密鍵cをこの時点知っているので、このコインはアリスのものになる。(ボブはaを知らないのでCのコインは使えない)

また、一度支払いをしてしまえば、その後の支払いではOP_RETURNに含めるBのデータを(支払い回数をiとした場合 i || bなどで)決定論的に導出すれば、OP_RETURNアウトプットを含める必要はなくなる。

なお、OVERTとCOVERTの違いは、OP_RETURNで公開鍵を伝える際にそのプレフィックスEP(hexだと4550)を付与するかどうかという点だけ。OVERTの場合、EPが付くのでそのトランザクションIOC支払いであることが明確になり、スキャンする側の負荷が軽減する。一方COVERTの場合プレフィックスが付かないため第三者のオブザーバーがいたとしてもIOC支払いであることを確実に判断するのが困難になる。

IOC支払いの否認防止性

IOC支払いの場合、非対話型のため実際にCのコインをアリスがアンロックできることを明示的に証明することができない。対話型の場合、受信側のアドレスへの署名データがあるため、その署名データによって否認防止性が担保できる。そのため、IOC支払いの場合、公平な第三者に支払いに使ったbの値を開示することで、それがアリスの鍵から導出可能であることを証明することができる。

通信の暗号化

支払人と受取人の間の全ての通信はECIESプロトコルを使って暗号化される。このセキュリティプロトコルはアカウントのIdentity keyを使ったECDH鍵交換を利用した共有AES鍵を使って行われる。

※ 実際にIdentity keyが使わるのは最初の通信のみで、後はメッセージ内のEncrypt_answer_with_public_keyで指定した一時鍵が使われる。

アカウントの更新

アクティベートされたアカウント情報(ランデブー記述子)はいつでも更新することができる。②のマルチシグのUTXOを使用するトランザクションを作成し、そのトランザクションアウトプットに更新したランデブー記述子を定義する(もう1つのトランザクションアウトプットは同じマルチシグのP2SH)。

アカウントの削除

アカウントを削除する場合は、アカウントのマルチシグのUTXOを、全く異なるアドレス宛に送金するだけ。

所感

  • 非対話で支払先のアドレスを導出するプロトコルは今までもステルスアドレスや、BIP-47のペイメントコードなどで提案されてきたけど、これらと違って基点となるアカウント情報もブロックチェーンで管理するというのが新しい試み。
  • Layer 2プロトコルを設計する際にランデブー記述子のようなメタデータをOP_RETURNに登録することはよくあるが、80バイト制限もあるので、OP_RETURNにはエンドポイントのみ記録して、実際の定義値はそのエンドポイントで公開するというアプローチが多い。ただその場合、エンドポイントが公開しているデータが不変であるという確証はないので、不変性を担保するのであればデータはブロックチェーン上に記録された方がいい。そういう意味で、easypaysyは専用の圧縮アルゴリズムを使って直接メタデータを記録するアプローチを取ってる。JSONである必要があるかは疑問だけど。
  • BitcoinのUXを改善するためにUTXOやそれに付随するアドレスの仕組みをできるだけエンドユーザーに意識させない方が望ましい。そういった意味ではアカウントベースの見せ方の方が向いてそう。
  • ↑の解説以外にもプル型の定期支払いの仕組みやスケーリングのための仕組みがホワイトペーパーには定義されている。