Develop with pleasure!

福岡でCloudとかBlockchainとか。

支払いの証明(Proof of Payment)について定義したBIP-120

Bitcoin決済をしたことを証明する支払いの証明(Proof of Payment)方法について定義しているのがBIP-120↓

https://github.com/bitcoin/bips/blob/master/bip-0120.mediawiki

Bitcoinによる支払いが行われたことを条件にサービスなどを提供するケースって結構あるんじゃないかなー。

(BIP-120自体はコンセンサスなどとは無関係なApplicationsレイヤーのBIPなので、デプロイなどは無い。)

動機

支払いの証明(Proof of Paymant = PoP)を利用することで以下のようなユースケースが考えられる。

  • ホテルなどで既に料金を支払っている場合に、そのPoPがドアの鍵になる。
  • オンラインのビデオサービスでビデオの代金を支払っていれば、どのデバイスでも視聴できる。
  • 事前に支払い済みの広告。広告の掲載期間内であればいつでも、PoPを使って新しいコンテンツを広告としてアップロードできる。
  • PoPを使って有料サイトにログインする。
  • 毎月支払いをする駐車場や車の認証をPoPを使って行う。
  • 全参加者が同じアドレスに支払いをする宝くじで、勝者はそのアドレスへ支払いをしたトランザクションの中から選択される。勝者はそのトランザクションのPoPと賞金を交換する。

Proof of Paymentを使うと個人情報(ユーザー名やパスワード、メールアドレス)などを必要とせず、こういったユースケースを実現できる。

論拠

PoPを実現する上で必要な特性:

  1. PoPは必要に応じて生成する。
  2. 盗難リスクを避けるため基本的に一度しか使用できない。
  3. (P2SHやP2PKHなどの)スクリプトタイプに関係なく、支払いのためをPoPを作成できる必要がある。
  4. 実証済みのトランザクションの全入力のロックを解除するのに、十分な資格を持っていることを証明する必要がある。
  5. 簡単に採用できるようウォレットとサーバーの実装は簡単でなければならない。

支払いを証明するための現在の方法:

  • BIP-70では、リクエストを満たすトランザクションとともに、PaymentRequestが何らかの証明をするが、1,2もしくは4は満たしておらず、BIP-70では3しかできない。そのため証明を要求/提供する標準的な方法は定義されていない。標準化すれば5を満たせる。
  • サーバーが生成したメッセージに、トランザクションに署名するのに使う秘密鍵を使って署名する。これは1と2を満たすかもしれないが、おそらく3は満たせない。これも標準化はされていない。4は設計されていれば満たせる。

仕様

データ構造

トランザクションTの支払いに対する証明(PoP(T)とする)は、Tの全ての入力をアンロックするのに必要な情報を所有していることを証明するデータでもある。PoP(T)の入力は基本的にTと同じトランザクション構造(同じ入力を同じ順で持つ)をもつデータで、入力データのうちシーケンス番号だけは元と違い0がセットされている。またpop outputと呼ばれるコインの量が0の出力を1つ持つ。このpop outputの形式は↓

OP_RETURN <version> <txid> <nonce>
フィールド バイト数 内容
version 2 バージョン。リトルエンディアンで現在は0x01 0x00
txid 32 証明するトランザクションのtxid
nonce 6 ランダムデータ

PoPのロックタイムは必ず499999999にセットする必要がある。これは誤ってPoPがBitcoinP2Pネットワークに流れても、ブロックに取り込まれることが無いようにするため。シーケンス番号がffffffffだとロックタイムを無効にするので、シーケンス番号に0をセットしているのもそのため。この仕様では全入力のシーケンス番号を0にするようにしている。ロックタイムを有効にするのには1つでも十分だけど、シンプルにするため。

TとPoP(T)の構造はそれぞれ以下のようになる。

  T
 +------------------------------------------------+
 |inputs                | outputs                 |
 |       Value,Sequence | Value,Script            |
 +------------------------------------------------+
 |input0 1,ffffffff     | 0,pay to A              |
 |input1 3,ffffffff     | 2,OP_RETURN <some data> |
 |input2 4,ffffffff     | 1,pay to B              |
 |                      | 4,pay to C              |
 +------------------------------------------------+
 
  PoP(T)
 +-------------------------------------------------------------+
 | inputs               | outputs                              |
 |       Value,Sequence | Value,Script                         |
 +-------------------------------------------------------------+
 |input0 1,00000000     | 0,OP_RETURN <version> <txid> <nonce> |
 |input1 3,00000000     |                                      |
 |input2 4,00000000     |                                      |
 +-------------------------------------------------------------+
 | lock_time=499999999                                         |
 +-------------------------------------------------------------+

PoPはBitcoinトランザクションと同じ署名プロセスで署名される。

nonceは盗まれたPoPの使用を困難にするための要素で、サーバーがPoPのリクエスト毎に新しいnonceを生成することで、盗まれたPoPを無効化する。

プロセス

  1. 最初にProof of Paymentのリクエストがサーバーからウォレットに送られる。このPoPリクエストには、以下が含まれる。
    1. ランダムなnonce
    2. PoPの送信先httpsなURLなど)
    3. どのトランザクションのproofを作ればいいのか示すウォレットへのヒントで、例えば
      • (サーバーが知っていれば)txid
      • (BIP-70の支払いの場合は)PaymentRequest.PaymentDetails.merchant_data
      • BIP-21のURIから得られる量、ラベル、メッセージやその他の情報
  2. ウォレットはサーバーの情報からトランザクションを特定する。ウォレット単体で特定できない場合は、1-3のヒントをユーザーに確認し、一致するものを選択してもらう。
  3. ウォレットは特定したトランザクションの未署名のUPoPを作成し、ユーザーに署名するか尋ねる。
  4. ユーザーは署名するか確認する
  5. ウォレットはUPoPに署名しPoPを作成する
  6. PoPを1の宛先に送信する
  7. サーバーは受信したPoPを検証し、その結果をvalidinvalidで返す
  8. ウォレットは何らかの方法でユーザーにレスポンスを表示する。

備考

  • ステップ1のPoPリクエストを送信する方法についてはここでは指定しない。BIP-121を参照。
  • nonceは新しいPoPリクエストごとにサーバによってランダムに生成される必要がある。

PoPの検証

サーバーはPoPを検証し、validinvalidを返す必要がある。この検証プロセスの概略を以下に示す。いずれかのステップでも失敗すると検証は途中で中止され、invalidが返される。

  1. PoPのフォーマットをチェックする。入力が使用済みであることを除いて、通常のトランザクションの検証をパスする必要がある。
  2. ロックタイムが499999999かチェックする。
  3. 出力は1つだけかチェックする。この出力のコインの量は必ず0で、上記のOP_RETURN出力フォーマットに準拠している必要がある。
  4. nonceがリクエストで送信したものと同じかチェックする。
  5. シーケンス番号が0であること以外は、PoPの入力が全てトランザクションTと全く同じであることを確認する(署名は除外)。この時入力の順序もTと同じこと。
  6. 全入力のスクリプトを実行し、全てtrueを返すこと。
  7. PoP出力に書かれているtxidが、実際に証明対象のトランザクションであるかチェックする。もし、どのトランザクションか知らない場合は、実際の製品やサービスに支払われたトランザクションを確認すること。
  8. 上記全てパスしたらvalidを返す。

セキュリティの考慮事項

  • 誰かがPoPリクエストをインターセプトし、その中のパラメータを変更する可能性があるが、これはSSLなどの安全な接続を使うことで軽減できる。以下のような改竄が考えられる↓
    • PoPの送付先を変更し、PoPを盗む
    • labelを変更し、意図しないPoPに署名したり、ウォレットに記録がないラベルが設定されるとサービスが利用できなくなる。
    • nonceを変更することで、PoPはサーバー上の検証で失敗する。
  • PoPリクエストを改竄してPoPを盗み、nonceが一致するまでサーバにリクエストを送る。実際に合致する確率は1 / 248。サーバーにはこの種の総当り攻撃を検出する仕組みを用意するか、少なくともPoPリクエストを100ms遅らせるなどしてプロセスを遅くする。
  • ウォレットに資金がない場合でも、PoP生成器としての価値がある可能性がある。そのためウォレットの残高が0でもセキュリティを維持することは重要。
  • トランザクションのmalleabilityによりサーバーとクライアントで異なるtxidを認識している可能性がある。その場合、クライアントはサーバーに対しトランザクションの証明ができない。ウォレットは自身がブロードキャストした時のtxidに依存してはいけない。代わりにネットワーク上のトランザクションをチェックし、そのトランザクションをリストに加えるなければならない。

参照実装

所感

  • 特殊なスクリプトなんかを設計すると、スクリプトタイプに依存して署名方法が複雑になったりするので、↑みたいに無効なトランザクション作って署名するパータンはありだなー。
  • 入力順も検証ルールに入ってるのは何でかな?単純にシンプルにしたいから?
  • 今のプロトコル仕様のままでも結構利用シーンありそうだけど、そんなに話題に上がらないのは別にもっと良いプロトコルある?

P2SHでネストしたP2WPKHベースのアカウント導出スキームを定義したBIP-49

HDウォレットのマルチアカウント機能がBIP-44で定義されているけど↓

techmedia-think.hatenablog.com

segwitがアクティベートされたこともあり、これにP2SHでネストされたP2WPKHをサポートしようということでBIP-49が新たに追加されたので見てみる↓

https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki

BIPの更新に伴い2019/12/18時点の内容で以下も更新

動機

同じマスターシードもしくは単一アカウントを持つ異なるHDウォレット間でシームレスにコインが利用できるように、BIP-141で定義されているP2SHでネストしたP2WPKHを使用するトランザクションでも、共通の鍵導出スキームが必要になる。

ユーザーはsegregated witness専用のアカウントを作る必要があり、またそのアカウントを検出し適切に処理することができるのはこのBIPと互換性のあるウォレットのみである。

検討事項

現行のBIP-44に対応したウォレットでは、以下の2つのアプローチが可能。

  1. ユーザーが既に使用しているアカウントで利用可能にし、そこにsegregated witnessでエンコードされたアドレスを追加する。
    1. BIP-44で定義されているものと同じ公開鍵を使用し、通常のP2PKHのアドレスに加えてP2SHアドレスも導出する。
    2. 同じアカウントルートは使用するが、そこからは外部チェーンと内部チェーンのルートを分岐し、そこでsegregated witness専用の公開鍵を生成する。
  2. segregated witnessのアドレス専用のアカウントを作成する。

1の解決策には共通の問題がある。ユーザーがこのBIPと互換性のないウォレットにマスターシードをインポートするとアカウントは表示されるが、一部のUTXO(P2SHでネストしたP2WPKHのUTXO)が表示されないといったことが起きる可能性がある。

そのためこのBIPでは、アカウントが表示されるかされないかで判断ができる、2の解決策を採用する。ユーザーは別のウォレットで同じシードをインポートして残高を確認する必要はない。

仕様

このBIPでは、BIP-32のルートアカウントをベースに、複数の決定性アドレスを導出するための2つのステップを定義する。

公開鍵の導出

ルートアカウントから公開鍵を導出するために、このBIPではBIP-44で定義されているアカウント構造を使用するが、トランザクションのシリアライゼーションフォーマットが異なることを表すため、レベルpurposeの値を変える。

m / purpose' / coin_type' / account' / change / address_index

Pathレベルpurposeの値には49を使用する。他のPathレベルの値はBIP-44の定義のまま。

アドレスの導出

導出された公開鍵からP2SHアドレスを導出するため、BIP-141で定義されている以下のルールを使用する。

witness:      <署名> <公開鍵>
scriptSig:    <0 <20バイトの公開鍵ハッシュ>>
              (0x160014{20バイトの公開鍵ハッシュ})
scriptPubKey: HASH160 <20バイトのスクリプトハッシュ> EQUAL
              (0xA914{20バイトのスクリプトハッシュ}87)

拡張鍵のバージョン

拡張鍵をシリアライズする場合、このスキームは代替のversion byteを使用する。拡張公開鍵は0x049d7cb2を使ってypubプレフィックスを生成し、拡張秘密鍵0x049d7878を使ってyprvプレフィックスを生成する。Testnetは0x044a5262を使ってupubを、0x044a4e28を使ってuprvを生成する。

追加で登録されているversion byteはSLIP-0132にリストアップされている。

後方互換

このBIPは↑の検討事項に記載したように後方互換性がない。互換性のないウォレットではアカウントが見つけられず、ユーザーは何かが間違っていることに気づくだろう。

Test vectors

masterseedWords = abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
masterseed = uprv8tXDerPXZ1QsVNjUJWTurs9kA1KGfKUAts74GCkcXtU8GwnH33GDRbNJpEqTvipfCyycARtQJhmdfWf8oKt41X9LL1zeD2pLsWmxEk3VAwd (testnet)

// Account 0のルート秘密鍵 = m/49'/1'/0'
account0Xpriv = uprv91G7gZkzehuMVxDJTYE6tLivdF8e4rvzSu1LFfKw3b2Qx1Aj8vpoFnHdfUZ3hmi9jsvPifmZ24RTN2KhwB8BfMLTVqaBReibyaFFcTP1s9n (testnet)
account0Xpub = upub5EFU65HtV5TeiSHmZZm7FUffBGy8UKeqp7vw43jYbvZPpoVsgU93oac7Wk3u6moKegAEWtGNF8DehrnHtv21XXEMYRUocHqguyjknFHYfgY (testnet)

// Account 0の最初の受信用秘密鍵 = m/49'/1'/0'/0/0
account0recvPrivateKey = cULrpoZGXiuC19Uhvykx7NugygA3k86b3hmdCeyvHYQZSxojGyXJ
account0recvPrivateKeyHex = 0xc9bdb49cfbaedca21c4b1f3a7803c34636b1d7dc55a717132443fc3f4c5867e8
account0recvPublickKeyHex = 0x03a1af804ac108a8a51782198c2d034b28bf90c8803f5a53f76276fa69a4eae77f

// アドレスの導出
keyhash = HASH160(account0recvPublickKeyHex) = 0x38971f73930f6c141d977ac4fd4a727c854935b3
scriptSig = <0 <keyhash>> = 0x001438971f73930f6c141d977ac4fd4a727c854935b3
addressBytes = HASH160(scriptSig) = 0x336caa13e08b96080a32b5d818d59b4ab3b36742

// base58checkエンコードしたtestnetのアドレス
address = base58check(prefix | addressBytes) = 2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2 (testnet)

所感

  • 単純にSegwit用のアドレス導出すれば良いんじゃ?と思ってたけど、ウォレットの互換性を意識しないといけないのね。
  • 今回のBIPの対象はP2SHでネストしたP2WPKHが対象なので、Bech32ベースのネイティブなP2WPKHの導出スキームはまた別途BIPで定義するのかね?
  • Bech32でP2WPKHのアドレスフォーマットが策定されたとはいえ、まずはそれをどうHDウォレットで管理するか仕様決めないと普段使ってるウォレットの対応も進まないよね。

HDウォレットのマルチアカウント階層を定義したBIP-44

以前BIP-32の階層的決定性ウォレットについて書いたけど↓

techmedia-think.hatenablog.com

このHDウォレットの階層を論理的に定義したBIP-44についてみてみる↓

https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki

動機

このBIPで提案している階層構造はとても包括的で、複数のコイン、複数のアカウントの管理に加え、アカウント毎に内部チェーン・外部チェーン、チェーン毎に何百のアドレスを管理できるようになる。

Path level

このBIPでは、BIP-32のパスを以下の5つのレベルで定義する。

m / purpose' / coin_type' / account' / change / address_index

↑のアポストロフィー(')は、BIP-32の強化鍵を使用することを示す。

各レベルは以下の特別な意味を持つ。

Purpose

PurposeにはBIP-43の勧告に従い定数44' (or 0x8000002C)がセットされ、このノードのサブツリーがBIP-44の仕様に従っていることを示す。

このレベルでは強化導出が使われる。

Coin type

BitcoinやLitecoin、Namecoinなどの独立した暗号通貨は1つのマスターノード(シード)を使用できるが、いろいろな暗号通貨で同じスペースを共有するのは問題がある。

このレベルでは、各暗号通貨についてそれぞれ別々のサブツリーを作成し、暗号通貨全体でアドレスの再利用やプライバシーの問題を回避する。

Coin typeは各暗号通貨毎に設定された定数で、暗号通貨の開発者は未使用の番号の登録を要求することができる。既に登録済みのCoin typeは後述。

このレベルでは強化導出が使われる。

Account

このレベルではユーザー毎に鍵スペースを分割することで、ウォレットが異なるアカウント間でコインを混在させることが無いようにする。

ユーザーはこのAccountを使って銀行口座と同じ方法で資金を整理することができる(寄付用口座、貯蓄口座、共用口座などのように)。

Accountはインデックス 0から順番に増加するよう番号が割り当てられ、この番号がBIP-32の鍵導出の際の子インデックスとして使われる。

このレベルでは強化導出が使われる。

以前のアカウントでトランザクション履歴がない場合(=そのアドレスが使用されたことがない場合)、ソフトウェアはそのアカウントを作成できないようにする必要がある(後述のアカウント検出の際にこのルールが必要)。

ソフトウェアはシードを外部からインポートした後、使用された全てのアカウントを検出する必要がある。このアルゴリズムについては"アカウント検出"の章で説明。

Change

このレベルでは、定数0が外部チェーン、定数1が内部チェーン(お釣り用のアドレス)として使われる。外部チェーンはウォレットの外側に見えるように意図されたコインの受け取り用のアドレスに使われる。内部チェーンはウォレットの外部に表示されることはなく、トランザクションでお釣りを受け取る際のアドレスに使われる。

このレベルでは強化導出でなく通常の鍵導出が行われる。

Index

アドレスはインデックス 0から順番に増えるよう番号が割り当てられる。この番号はBIP-32の鍵導出の際の子インデックスとして使われる。

このレベルでは強化導出でなく通常の鍵導出が行われる。

アカウント検出

マスターシードが外部からインポートされると、ソフトウェアは以下のステップでアカウントの検出をする。

  1. 最初のAccountノード(index = 0)を導出
  2. このAccountの外部チェーンノードを導出
  3. 後述するgap limitの数まで外部チェーンのアドレスをスキャンする。
  4. 外部チェーン上のトランザクションが見つからなければ、検出を停止する。
  5. トランザクションが見つかったら、indexをインクリメントして1のステップに戻る。

↑の"Account"の章で説明しているように、以前のAccountに取引履歴がない場合、新しいアカウントの作成を禁止するため、このアルゴリズムは成立する。

このアルゴリズムはAccountの残高ではなく取引履歴をベースに動作するので、残高が0コインのAccountがあっても検出は継続する。

アドレスのgap limit

アドレスのgap limitは現在20に設定されている。ソフトウェアはスキャンの際に、20個連続してアドレスが使われていない状態だったら、これ以上スキャンしても使用済みのアドレスは検出されないとみなし、アドレスの検出を停止する。内部チェーンは関連する外部チェーンのコインのみを受け取るので、外部チェーンだけをスキャンすればいい。

ウォレットのソフトウェアは、ユーザーがgap limitを超える数の新しいアドレスを生成しようとした場合、警告する必要がある。

登録済みのCoin type

以下は、このBIPのレベル2で使用するデフォルトの登録済みCoin type。

これらの定数は全て強化導出で使われる。

index hexa coin
0 0x80000000 Bitcoin
1 0x80000001 Bitcoin testnet

このBIPは登録済みのCoin typeのリポジトリではないので、登録済みの全Coin typeは以下を参照↓

https://github.com/satoshilabs/slips/blob/master/slip-0044.md

新しいCoin typeを登録する際は、そのコインを実装した既存のウォレットが必要で、上記ファイルへのプルリクを作成する必要がある。

サンプル

BIP参照

互換性のあるウォレット

参照

上記仕様はあくまでBIP-44に対応したウォレットでのみ有効な導出方法で、BIP-44に対応していないウォレットでは異なるルールで鍵導出されているので要注意。

ECDSA署名のエンコーディング関連のmalleabilityを解消するルールを追加するBIP-146

Segwitのトランザクションで署名が入力から分離されたことで、署名スクリプトに細工してtxidを変更することはできなくなったけど、witnessに移動した署名データの改変自体は可能。依然として可能なECDSA署名のエンコーディング関連の改変を解消するためにトランザクションの有効性を検証する際にルールを追加しようというのがBIP-146↓

https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki

追加のルール自体は以前からCoreのリレーポリシーとして適用されているので実質有効化されているようなものかな。

動機

署名のmalleabilityとは、ネットワーク上のリレーノードが(リレーノード自体はブロードキャストされたトランザクションの署名に使われた秘密鍵にアクセスすることはできない)、トランザクションの署名を改変する能力のことを指す。非Segregated Witnessのトランザクションでは、署名のmalleabilityによりtxidの変更が可能で、もしそのtxidを参照する子トランザクションが存在した場合、その子トランザクションを無効にする。Segregated Witness(BIP-141)のトランザクションであれば第三者がtxidを変更するようなことはできないが、malleabilityによりwtxidを改変することは可能で、Compact Block(BIP-152)のリレーの効率を低下させる可能性がある。

BIP-66で署名のフォーマットに厳密なDER形式を適用して以降、ECDSAの署名には既知の2つのmalleabilityの要因が残っている↓

  1. ECDSA署名固有のmalleability
    ECDSA固有の問題で、署名データ(r, s)があった場合に、sの値をマイナスにし(r, -s(mod n))したデータも署名として有効と判断される。
  2. 失敗した署名のmalleability
    OP_CHECKSIGまたはOP_CHECKMULTISIGで署名の検証に失敗した場合、FALSEがスタックに戻され、スクリプトの評価は続く。失敗した署名でも、BIP-66の規則に従う署名である限りそれには意味がある。

このBIPでは、これらのmalleabilityを解消するための新しいルールを定義する。

仕様

署名エンコーディングのmalleabilityを解消するため、以下の新しいルールをpre-segregated Witness及びsegregated witness scriptに適用する。

LOW_S

ECDSAの署名のSの値は最大でも曲線の位数を2で割った値以下でないといけない。OP_CHECKSIG*1, OP_CHECKSIGVERIFY, OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFYをパスする各ECDSA署名には、このルールが適用され、S0x1から0x7FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 5D576E73 57A4501D DFE92F46 681B20A0楕円曲線のベースポイントGの位数を2で割った値)の範囲の値で、厳密なDERフォーマットを適用した署名でなくてはならない。

ECDSAの署名検証の際、署名が空のバイト列ではなく、LOW_Sチェックに合格しない場合、スクリプト全体がfalseと判定される。

署名値にHIGH_Sの値が使われている場合は、以下を計算することで簡単にLOW_Sに置き換えることができる。

S' = 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141 - S

NULLFAIL

OP_CHECKSIGがスタックにfalseを返す際は、関連する署名は空のバイト配列でなければならない。

OP_CHECKMULTISIGがスタックにfalseを返す際は、OP_CHECKMULTISIGに渡す全ての署名は空のバイト配列でなければならない。この時、マルチシグ内の一部の署名の不正などで署名検証が早期に終了し一部の署名がスキップされたとしても、全署名についてこのNULLFAILチェックをしなければならない。

それ以外の場合は、スクリプト全体がすぐにfalseと判定される。

サンプル

以下の例はLOW_SルールとNULLFAILルールを組み合わせた結果*2

注釈

CO       : 曲線の位数(curve order) = 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141
HCO      : 曲線の位数を2で割った値(half curve order) = CO / 2 = 0x7FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 5D576E73 57A4501D DFE92F46 681B20A0
P1, P2   : シリアライズされた有効な公開鍵
S1L, S2L : P1とP2に対応した有効な low S 値を持つ署名(1 ≤ S ≤ HCO)
S1H, S2H : P1とP2に対応したhigh S 値を持つ署名 (HCO < S < CO)
F        : BIP-66に準拠しているが無効な署名(空ではない)

以下のスクリプトはこれまで通りスタックにTRUEを返す。

S1L P1 CHECKSIG
0 S1L S2L 2 P1 P2 2 CHECKMULTISIG

以下のスクリプトはこれまで通りスタックにFALSEを返す。

0 P1 CHECKSIG
0 0 0 2 P1 P2 2 CHECKMULTISIG

以下のスクリプトはこれまではTRUEだったが、新しいルールではスクリプト自体がすぐに失敗する。

S1H P1 CHECKSIG
0 S1H S2L 2 P1 P2 2 CHECKMULTISIG
0 S1L S2H 2 P1 P2 2 CHECKMULTISIG
0 S1H S2H 2 P1 P2 2 CHECKMULTISIG

以下のスクリプトはこれまではFALSEだったが、新しいルールではスクリプト自体がすぐに失敗する。

F P1 CHECKSIG
0 S2L S1L 2 P1 P2 2 CHECKMULTISIG
0 S1L F   2 P1 P2 2 CHECKMULTISIG
0 F   S2L 2 P1 P2 2 CHECKMULTISIG
0 S1L 0   2 P1 P2 2 CHECKMULTISIG
0 0   S2L 2 P1 P2 2 CHECKMULTISIG
0 F   0   2 P1 P2 2 CHECKMULTISIG
0 0   F   2 P1 P2 2 CHECKMULTISIG

デプロイメント

このBIPはBIP-9のversion bitsを利用してデプロイされるが詳細は未定。

互換性

参照クライアントはv0.9.0以降LOW_S互換の署名を生成しており、LOW_Sのルールはv0.11.0以降の参照クライアントでリレーポリシーとして適用されている。2016年8月時点で、ルールに違反するトランザクションはごくわずかである。実際に使われている全scriptPubKeyにおいて、準拠していない署名を準拠した署名に変換するのは非常に簡単なことであるため、このルールによって機能が損なわれることはない。

OP_CHECKSIGOP_CHECKMULTISIGに失敗するスクリプトがチェーン上に記録されるのは稀で、NULLFAILルールは参照クライアントのv0.13.1以降リレーポリシーとして適用されている。

ただ、ユーザーは変わったスクリプトを設計する際は、このルールに特別な注意を払うこと。

実装

参照クライアントの実装は↓

https://github.com/bitcoin/bitcoin/blob/35fe0393f216aa6020fc929272118eade5628636/src/script/interpreter.cpp#L185

で、そのプルリクは↓

github.com

*1:BIP-141のP2WPKHも含む

*2:参照クライアントv0.13.1の実装により、曲線の位数の半分の値より大きなS値を持つ署名がLOW_Sチェックをパスする可能性があることに注意が必要。ただそのような署名は無効かつ、その後のNULLFAILルールのチェックで失敗する。

ElementsのAsset tagの生成と追加発行の仕組み

ElementsでConfidential Assetsについて、以前ブログを書いた↓

techmedia-think.hatenablog.com

けど、Asset Tagや実際にアセットを追加発行する際のトークンなどがどういうロジックで生成されて、何を持って追加発行が承認されるのか仕組みについてよく分かってなかったので、ホワイトペーパーを読んでみた↓

https://blockstream.com/bitcoin17-final41.pdf

Asset tagを構成する要素

Ricardian contract

アセットの使用条件や償還に関して記載したマシンでパース可能な法的文書で、Elements固有の技術要素ではない。 この文書のハッシュをアセット発行時のエントロピーを生成する際のインプットにすることで、アセットのRicardian contractへのコミットとしてるみたい。

Elementsでは実際にどういうRicardian contractを生成しているのか確認してみたけど、現状のウォレットは空のハッシュ(uint256)を使ってるだけっぽい↓

基本的にRicardian contractの作成は外部レイヤーでやって、今後そのハッシュを引数に取るようになるのかもしれない。

Asset entropy

発行トランザクションに使用するUTXOのOutPointの値をIとし、アセットの発行者が指定したRicardian contract Cがあったとして、asset entropy E

E = Hash(Hash(I)||Hash(C))

として算出される。

Elementsでの実装は↓で、UTXOとRicardian contract をリーフにしたマークルルートを計算してる。

現状のElementsだとRicardian contractが空なので、実質UTXOのOutPointがエントロピーになってる。

Asset tag

↑のasset entropy E(このエントロピーはUTXOとRicardian contractへのコミットメントでもある)を使って、Hash(E||0) を計算しそれを入力としたPedersen commitmentをセットアップする。このPedersen commitment HA ∈ G がasset tagとなる。ランダムオラクルモデルにおいて、asset tagはGまたは他の任意のasset tagについてその離散対数が分からない、ランダムな曲線上のポイントと言える。

アセットの発行

アセットを発行する際のトランザクションの入力は以下の要素で構成される。

  • 使用するUTXO I
  • Ricardian contract C
  • アセットの発行量
    以下のどちらかで指定
    • 発行量を明示的に指定する場合はその量v0
    • 発行量は明示的に指定しない場合は、Pedersen commitment HとBackMaxwellのrangeproof P0
  • 追加発行を許可するかどうかを示すbooleanのフィールド

ICからエントロピーを生成し、エントロピーと追加発行を示すフィールドからasset tagを生成するのだろう。

アセットの追加発行

アセットは初期発行のみで発行量を固定するパターンと、あとでアセットを追加発行できる2つの発行パターンがある。

追加発行する場合は、最初のアセット発行時に、追加発行をすることができるトークンが生成される。

追加発行の有無の設定

アセットを発行する際は、↑にも書いたように、Ricardian contractとUTXOからasset entropy Eを生成し、そのEからHash(E||0) を計算し、それを使って作成したPedersen commitmentがasset tagになる。この時Hash(E||0)の0の部分が追加発行の有無を設定するフィールドになる。0だと発行量を固定するパターンで、これを1にすると追加発行を可能にするPedersen commitment(asset tag)になる。

追加発行が可能なアセットはアセット発行トランザクションの入力でそのことを示し、そのトランザクションにはasset tag HAにコミットするamountが1の追加の出力が作られる。

このようにasset tagはasset entropyと追加発行の有無(0 or 1)に対するコミットメントになっているので、オリジナルのasset entropyとblinding factorを明らかにすることで、再発行する権限を有することを証明できる。

トークンの実態

追加発行をすることができるトークンというのは、Elementsの実装を見るとHash(E||1)の計算結果みたい。

アセットが追加発行を許可しているアセットで、ウォレット内に↑のトークンがあればアセットの追加発行が可能になる。

追加発行時の入力

アセットの追加発行をする際のトランザクションの入力は、以下の要素で構成される。

  • アセットの再発行能力を持つUTXO
    再発行を許可する発行トランザクションにはamountが1の出力が追加されるとあったけど、それを利用する?
  • オリジナルのasset entropy E
  • UTXOのasset commitmentのblinding factor
  • アセットの発行量
    以下のどちらかで指定
    • 発行量を明示的に指定する場合はその量v0
    • 発行量は明示的に指定しない場合は、Pedersen commitment HとBackMaxwellのrangeproof P0

↑のトークンを入手すればアセットの再発行が可能になるんだろうけど、現状のようにRicardian contracが空のままだと発行トランザクションの入力からエントロピーを誰でも計算できてしまうような気がするんだけどそれは問題ないのかな?