Develop with pleasure!

福岡でCloudとかBlockchainとか。

BitcoinにTaprootを導入するBIPドラフトbip-taproot(BIP-341)

先日、Bitcoinの開発者MLでPieter WuilleがTaproot、Schnorr署名およびマークルブランチをベースとしたコインの使用ルールである新しいSegwitバージョン1のアウトプットタイプを提案した↓

https://github.com/sipa/bips/blob/bip-schnorr/bip-taproot.mediawiki

Taprootの概要については以前書いた↓参照。

techmedia-think.hatenablog.com

これを具体的にBitcoinに取り込むための仕様を定義したのが今回のbip-taprootで、Taprootアウトプットの構成方法や使用時のルール(BIP 143とは異なる新しいトランザクションダイジェストアルゴリズムも導入)などを定義している。尚、このドラフトではTaprootの構造的な仕様を定義しており、Taprootのアウトプット内のスクリプトがどのように動作するかのルールについては別のドラフト(bip-tapscript)に定義されている。

以下、BIP-341訳。追記:2020/06/02 時点の内容で以下の内容を更新

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

概要

このドキュメントではTaproot、Schnorr署名やマークルブランチに基づく使用ルールを使った新しいSegwit version 1のアウトプットタイプを提案する。

動機

この提案は新しいセキュリティ仮定を追加することなく1Bitcoinスクリプト機能のプライバシーや効率性および柔軟性を改善することを目的としている。具体的には、アウトプットの作成時もしくは使用時にオンチェーンで明らかになるトランザクションアウトプットの使用可能条件に関する情報を最小限に抑え、マイナーではあるが長年の課題を修正する多くのアップグレーの仕組みを追加しようとしている。

設計

Bitcoinスクリプト機能を改善するための多くのアイディアがこれまで提案されてきた。Schnorr署名(BIP340)やマークルブランチ(MAST, BIP114, BIP117)、新しいsighashモード(BIP118)、CHECKSIGFROMSTACKのような新しいopcode、TaprootGraftrootG'rootインプットを跨いだ署名の集約など。

これらすべてのアイディアを単一の提案にまとめるのは大きな変更であり、レビューをするのが難しく、そうでなければ途中で実現されていたかもしれない新しい発見を見逃す可能性がある。またすべてが同様に成熟しているわけではない。例えば、インプットを跨いだ署名の集約は、アップグレードの仕組みと複雑な方法で相互作用し、その解決策はまだ流動的だ。一方、すべての個々の独立した提案に分割すると、効率とプライバシーの利点が減少し、ウォレットやサービスプロバイダーが多くのインクリメンタルな更新をしない傾向がある。したがって機能とスコープクリープのトレードオフに直面している。この設計では、Taprootとマークルブランチによって提供される構造スクリプトの改善と、それらを使用可能かつ効率的にするために必要な変更にフォーカスすることでバランスをとる。sighashやopcodeのようなもにに対しては、既知の問題に対する修正を含めるが、欠点がなく独立して追加できる新しい機能については除外する。

結果として、以下の技術の組み合わせを選択する。

  • スクリプトを実行することができるすべての方法を開示するのとは対照的に、マークルブランチではスクリプトの実際に実行された部分のみをブロックチェーンに公開することができる。これを実装するのにさまざまな既知の仕組みがある中で、マークルツリーをスクリプトの構造の一部にする方法が、1番直接スペースを節約することができるので、そのアプローチが選択される。
  • その上にTaprootを使うと、従来別々だったpay-to-pubkeyポリシーとpay-to-scripthashポリシーをマージし、全てのアウトプットは鍵もしくは(オプションで)スクリプトのいずれかで使用でき、互いに区別できなくすることができる。鍵ベースの使用パスが使われている限り、同様にスクリプトパスも許可されているかどうかが明らかにされることはなく、その結果、スペースの節約と使用時のスクリプトのプライバシーの向上がもたらされる。
  • Taprootの利点は、ほとんどのアプリケーションがすべての参加者が同意することで使用可能になるアウトプットを必要とするという仮定の下で明らかになる。Schnorr署名が入ってくると、鍵の集約が可能になるため、公開鍵は複数の参加者の公開鍵から構成することができ、それに署名するには全ての参加者の協力が必要になる。このようなマルチパーティの公開鍵や署名は、シングルパーティのものと区別がつかない。これは、taprootを使用するとほとんどのアプリケーションが効率的でプライベートな鍵ベースの使用パスを使用できることを意味する。Schnorr署名は閾値署名をサポートしているため、より複雑なセットアッププロトコルが必要になるが、任意のM-of-Nポリシーに一般化できる。
  • Schnorr署名はバッチ検証を可能にし、複数の署名を個別に検証するよりも効率的に複数の署名をまとめて検証できる。設計の全ての部分がこれと互換性があることを確認している。
  • 上記の変更の結果、未使用のビットが現れた場合、それらは将来の拡張のための仕組みで予約されている。その結果、マークルツリー内の各スクリプトは関連するバージョンを持ち、このバージョンによりBIP 341との互換性を維持しながら、ソフトフォークで新しいスクリプトバージョンを導入できるようになる。さらに、将来のソフトフォークでは、witness内で現在未使用のannexを利用することができる(BIP-341参照)。
  • この提案では、署名ハッシュアルゴリズムのコアセマンティクスは変更されていないが、いくつかの改善点が含まれている。新しい署名ハッシュアルゴリズムは、署名メッセージににamountscriiptPubkeyを含めることで、オフライン署名デバイスの検証機能を改善し、不要なハッシュを回避し、タグ付きハッシュを導入し、デフォルトのSIGHASHバイトを定義する。
  • 公開鍵のハッシュやスクリプトのハッシュをアウトプット内に格納する典型的な以前の構成とは対照的に、公開鍵はアウトプットに直接含まれる。これは送信者にとって同じコストであり、鍵ベースのパスが使用される場合、全体的にスペース効率が高くなる2

略式に、結果の設計は次のとおり。新しいwitness versionが追加され(version 1)、そのwitness programあ点Qの32バイトエンコーディングで構成される。Qは公開鍵PについてQ = P + hash(P || m)Gとして計算され、mはリーフがバージョン番号とスクリプトで構成されるマークルツリーのルート。このようなアウトプットは、Qに対して有効な署名を提供することで直接使用するか、間接的にP、スクリプトとリーフバージョン、スクリプトを満たす入力とQがそのリーフにコミットしたことを証明するマークルパスを明らかにすることを使用できる。この構造のすべてのハッシュ(PからQを計算する際のハッシュ、マークルツリーの内部ノードのハッシュ、使用されるsignature hash)は、ドメインの分離を保証するためタグ付けされる。

仕様

このセクションではTaprootのコンセンサスルールを指定する。有効性は除外によって定義される:ブロックまたはトランザクションは、失敗を示す条件が存在しない場合、有効である。

以下の表記はBIP-340の表記に従う。これには、SHA256(SHA256(tag) || SHA256(tag) || x)を指すhashtag(x)表記が含まれる。著者の知る限り、Bitcoinにおいて2つの単一のSHA256出力で始まるメッセージが投入されるSHA256の使用はこれまでないため、ハッシュタグと他のハッシュとの衝突はほぼ起こらない。

Scriptの検証ルール

Taprootのアウトプットはバージョン番号1、32バイトのwitness programを持つネイティブSegwitアウトプットである。以下のルールは、そのようなアウトプットが使用される場合にのみ適用される。32バイト以外の長さを持つバージョン1のアウトプットや、P2SHでラップされたバージョン1アウトプットの場合3、関係ないままだ。

  • qをBIP340における公開鍵を表すwitness program(scriptPubkey内の2つめのプッシュ)を含む32バイトの配列とする。
  • witness stackが0の場合失敗する。
  • 少なくとも2つのwitness要素があり、最後の要素の先頭バイトが0x50の場合4、この最後の要素はannex a 5と呼ばれwitness stackから削除される。annex(およびその欠如)は、常に署名によってカバーされ、トランザクションのweightに寄与するが、それ以外はtaprootの検証中無視される。
  • witnessスタックに正確に1つだけ要素が残っている場合、コインを使用するのに鍵パスが使用される:
    • 単一のwitnessスタック要素は署名として解釈され、公開鍵q(次のサブセクション参照)とメッセージとしてTaprootのトランザクションダイジェスト(後述)に対して有効でなければならない。
  • witnessスタックに少なくとも2つの要素が残っている場合、コインを使用するのにスクリプトパスが使用される:
    • 最後から2つめのスタック要素sスクリプトである。
    • 最後のスタック要素をコントロールブロックcと呼び、33 + 32mの長さでなければならない。mの値は0〜128 6までの整数。そのような長さでない場合、スクリプトの評価は失敗する。
    • p = c[1:33]、P = point(p)としBIP340における点とされる。もしこれが曲線上の有効な点でない場合、スクリプトの評価は失敗する。
    • v = c[0] & 0xfeとし、リーフバージョン7と呼ぶ。
    •  {k_0 = hash_{TapLeaf}(v || compact_size(size of s) || s)}とし、これをtapleaf hashと呼ぶ。
    • For j in [0,1,...,m-1]の各jについて
      •  {e_j = c[33+32j:65+32j]}とする。
      •  {k_{j+1}}は、(辞書順で8 {k_{j} < e_j}かどうかで算出方法が変わる:
        •  {k_j < e_j}の場合、 {k_{j+1} = hash_{TapBranch}(k_j || e_j)}となる9
        •  {k_j ≧ e_j}の場合、 {k_{j+1} = hash_{TapBranch}(e_j || k_j)}となる。
    •  { t = hash_{TapTweak}(p || k_{m})}とする。
    • t ≥ 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141(secp256k1の位数)の場合、スクリプトの評価は失敗する。
    • Q = P + int(t)Gとする。
    • q != x(Q)もしくはc[0] & 1 ≠ y(Q) mod 2の場合、失敗する。
    • 適用可能なスクリプトルールに従い10スクリプトs、control block c、ある場合はannex aを除外したwitnessスタック要素を初期スタックとしてスクリプトを実行する。

qはTaprootアウトプットキーと呼ばれ、pはTaproot内部キーと呼ばれる。

署名検証ルール

最初に再利用可能な共通の署名メッセージ計算関数を定義し、次に鍵パスの使用で使用される実際の署名検証を定義する。

共通の署名メッセージ

SigMsg(hash_type, ext_flag)関数は署名されるメッセージをバイト列として計算する。この関数は暗黙的に支出トランザクションと支出アウトプットの関数でもあるが、表記を簡単にするために、これらはリストアップされていない。

hash_typeパラメータは8bitの符号なしの値。SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE, SIGHASH_ANYONECANPAYを含む従来のスクリプトシステムのSIGHASHエンコーディングが再利用される。さらに、SIGHASH_ALLと同様にトランザクション全体が署名されるhash_type値0x00がデフォルトとして追加される。以下の制限に違反すると検証エラーになる:

  • (0x00, 0x01, 0x02, 0x03, 0x81, 0x82, 0x83以外の)未定義のhash_typeを使ってはならない11
  • 対応するアウトプット(検証されるインプットと同じインデックスを持つアウトプット)が無いのに、SIGHASH_SINGLEを使用してはならない。

ext_flagパラメータは0-127の範囲の整数で、メッセージの最後12に拡張が追加されていることを示すのに使われる。

パラメータが許容値の場合、メッセージは以下のデータを順番に連結したものになる。2,4また8バイトの数値はリトルエンディアンでエンコードされる(カッコ内の数値は要素のバイトサイズを表す)。

  • Control:
    • hash_type(1)
  • トランザクションデータ:
    • nVersion(4): トランザクションnVersion
    • nLockTime(4): トランザクションnLockTime
    • hash_type & 0x80がSIGHASH_ANYONECANPAYと等しくない場合、
      • sha_prevouts (32): 全てのインプットのOutPointをシリアライゼーションした値のSHA256値
      • sha_amounts (32): 全てのインプットの量をシリアライゼーションした値のSHA256値
      • sha_scriptpubkeys (32): 使用する全てのアウトプットのscriptPubKeyをシリアライゼーションした値のSHA256値
      • sha_sequences (32): 全てのインプットのnSequenceをシリアライゼーションした値のSHA256値
    • hash_type & 3がSIGHASH_NONESIGHASH_SINGLEと等しくない場合、
      • sha_outputs (32): 全てのアウトプットのCTxOutフォーマットの値をシリアライゼーションした値のSHA256値
  • インプットに関するデータ:
    • spend_type (1): (ext_flag * 2) + annex_presentと等しい。annexが存在しない場合はannex_presentは0、存在する場合(元のwitnessスタックには2つ以上のwitness要素があり、最後の要素の先頭バイトが0x50)は1。
    • hash_type & 0x80がSIGHASH_ANYONECANPAYと等しい場合:
      • outpoint (36): このインプットのCOutPoint(32バイトのハッシュ+4バイトのリトルエンディアン)。
      • amount (8): このインプットが使用する前のアウトプットのvalue(コインの量)。
      • scriptPubKey (35): このインプットによって使用される前のアウトプットのscriptPubkey。CTxOut内のスクリプトとしてシリアライズされ、そのサイズは常に35バイト。
      • nSequence (4): このインプットのnSequence。
    • hash_type & 0x80がSIGHASH_ANYONECANPAYと等しくない場合
      • input_index (4): トランザクションインプット内のこのインプットのインデックス。最初のインプットのインデックスは0。
    • annexがある場合(spend_typeの最下位ビットがセットされている場合):
      • sha_annex (32): (compact_size(size of annex) || annex)のSHA256値。annexは必須の0x50プレフィックスを含む。
  • アウトプットに関するデータ:
    • hash_type & 3がSIGHASH_SINGLEと等しい場合:
      • sha_single_output (32): インプットに対応するアウトプットのCTxOut形式のSHA2256値。

SigMsg()の最大長は206バイト13。これには同じトランザクションの署名に間でキャッシュされる可能性があるsha_prevoutsなどのサブハッシュのサイズは含まれないことに注意すること。

まとめると、BIP143のshghash typeのセマンティクスからの変更点は以下のみ。

  1. リアライゼーションの方法と順序の変更14
  2. 署名メッセージは使用されたアウトプットのscriptPubkeyにコミットし、SIGHASH_ANYONECANPAYフラグがセットされていない場合、メッセージはトランザクションで使用された全てのアウトプットのscriptPubkeyにコミットする15
  3. SIGHASH_ANYONECANPAYフラグがセットされていない場合、メッセージは全てのトランザクションインプットの量にコミットする16
  4. SIGHASH_NONEもしくはSIGHASH_SINGLEがセットされている場合(SIGHASH_ANYONECANPAYがセットされていない限り)、署名メッセージは全てのインプットのnSequenceにコミットする17
  5. 署名メッセージにはtaroot固有のデータ、spend_typeおよび(あれば)annexへのコミットメントが含まれる。
Taprootの鍵パスを使った署名検証

公開鍵qで署名sigを検証するには:

  • sigが64バイトの場合、Verify(q, hashTapSigHash(0x00 || SigMsg(0x00, 0)), sig)18の結果を返す。VerifyはBIP340で定義されている。
  • sigが65バイトの場合、sig[64] ≠ 0x0019および、Verify(q, hashTapSighash(0x00 || SigMsg(sig[64], 0)), sig[0:64])の結果を返す。
  • それ以外の場合は失敗20

Taprootアウトプットの構築と使用

このセクションでは、Taprootアウトプットの構成方法および使用方法について説明する。これは受信と送金を実装することを選択したウォレットのみに影響し、コンセンサスクリティカルではない。

概念的に、各Taprootアウトプットは、単一の公開鍵条件(内部キー)とツリーに編成されたスクリプト内にエンコードされた0個以上の条件の組み合わせに対応している。これらの条件のいずれかを満たすとアウトプットを使用できる。

初期ステップ

最初のステップは、内部キーと残りのスクリプトの構成を決めることだ。詳細はおそらくアプリケーション依存になるが、ここではいくつかの一般的なガイドラインを示す。

  • (OP_IFなどの)条件付きのスクリプトを決定し、それらを複数のスクリプトに分割する(1つ1つが元のスクリプトの実行パスに対応する)際は、通常後半の選択を推奨する。
  • 単一の条件が複数の鍵の署名を要求する場合は、MuSigのような鍵集約技術を使ってそれらを単一の鍵に集約することができる。詳細はこのドキュメントの範囲外だが、この方法は署名手順を複雑にする可能性があることに注意すること。
  • 1つ以上の使用条件が(集約後の)単一の鍵のみで構成されている場合、最も使用する可能性が高いものを内部キーにする必要がある。そのような条件がない場合は、全てのスクリプトに参加している全ての鍵を集約したもので構成される鍵を追加するのを推奨する。これにより全員が同意するというブランチを効果的に追加する。それが許容できない場合は未知の離散対数を持つ点を内部キーとする。そのような点の例の1つは、 H = point(0x0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0) で、これはsecp256k1ジェネレータGの標準的な非圧縮エンコードのハッシュをx座標として取得することで構築できる。キーパスの使用が不可能であるという情報の漏洩を防ぐため、0..n-1の範囲内の整数rをランダムに選択し、内部キーとしてH+rGを使用することを推奨する。rを検証者に明らかにすることで、この内部キーがGに関して既知の離散対数を持たないことを証明し、検証者は内部キーの作成方法を再構築できる。
  • 使用条件がスクリプトパスを必要としない場合、アウトプットキーはスクリプトパスを持たないのではなく、使用できないスクリプトパスにコミットする必要がある。これは、アウトプットのキーポイントを {Q = P + int(hash_{TapTweak}(bytes(P)))G} と計算することで実現できる21
  • 残りのスクリプトは、二分木のリーフに編成する必要がある。スクリプトの各条件が同じようにリーフに対応する場合、ツリーはバランスがとれたツリーになる。各条件の確率が分かっている場合は、ツリーをハフマン木として構築することを検討してほしい。

アウトプットスクリプトの計算

使用条件が内部キーinternal_pubkeyとリーフが(leaf_version、スクリプト)のタプルである二分木に分割されると、以下のPython3 アルゴリズムを使ってアウトプットスクリプトを計算できる。このアルゴリズムは整数変換や点の乗算、タグ付きハッシュを利用するためBIP340のreference.pyのヘルパー関数を使用する。

最初に32バイトのBIP340の公開鍵のバイト列に対して、taproot_tweak_pubkeyを定義する。この関数は公開鍵のバイト列に加えて、調整された公開鍵のY座標を示すビットを返す。このパリティビットはアウトプットをスクリプトパスを使って使用する場合に必要になる。鍵パスで使用できるようにするため、taproot_tweak_seckeyを定義し、調整された公開鍵に対応する秘密鍵を計算する。任意のバイト文字列hに対してtaproot_tweak_pubkey(pubkey_gen(seckey), h)[0] == pubkey_gen(taproot_tweak_seckey(seckey, h))が成立する。

def taproot_tweak_pubkey(pubkey, h):
    t = int_from_bytes(tagged_hash("TapTweak", pubkey + h))
    if t >= SECP256K1_ORDER:
        raise ValueError
    Q = point_add(point(pubkey), point_mul(G, t))
    return 0 if has_even_y(Q) else 1, bytes_from_int(x(Q))

def taproot_tweak_seckey(seckey0, h):
    P = point_mul(G, int_from_bytes(seckey0))
    seckey = SECP256K1_ORDER - seckey0 if not has_square_y(P) else seckey
    t = int_from_bytes(tagged_hash("TapTweak", bytes_from_int(x(P)) + h))
    if t >= SECP256K1_ORDER:
        raise ValueError
    return (seckey + t) % SECP256K1_ORDER

次の関数taproot_output_scriptはscriptPubkeyのバイト配列を返す(BIP141参照)。ser_scriptは先頭CCompactSizeでエンコードされた長さを付与する関数を指す。

def taproot_tree_helper(script_tree):
    if isinstance(script_tree, tuple):
        leaf_version, script = script_tree
        h = tagged_hash("TapLeaf", bytes([leaf_version]) + ser_script(script))
        return ([((leaf_version, script), bytes())], h)
    left, left_h = taproot_tree_helper(script_tree[0])
    right, right_h = taproot_tree_helper(script_tree[1])
    ret = [(l, c + right_h) for l, c in left] + [(l, c + left_h) for l, c in right]
    if right_h < left_h:
        left_h, right_h = right_h, left_h
    return (ret, tagged_hash("TapBranch", left_h + right_h))

def taproot_output_script(internal_pubkey, script_tree):
    """Given a internal public key and a tree of scripts, compute the output script.
    script_tree is either:
     - a (leaf_version, script) tuple (leaf_version is 0xc0 for [[bip-0342.mediawiki|BIP342]] scripts)
     - a list of two elements, each with the same structure as script_tree itself
     - None
    """
    if script_tree is None:
        h = bytes()
    else:
        _, h = taproot_tree_helper(script_tree)
    output_pubkey, _ = taproot_tweak_pubkey(internal_pubkey, h)
    return bytes([0x51, 0x20]) + output_pubkey

f:id:techmedia-think:20200130114550p:plain

この図は内部キーPと5つのスクリプトリーフで構成されるマークルツリーからtweakを入手するハッシュ構造を示している。A,B,CおよびEはDと同様のTapLeafハッシュで、ABはTapBranchハッシュ。EはCDより小さいのでCDEを計算刷る際最初にEがハッシュされることに注意すること。

スクリプトDを使ってこのアウトプットを使用する場合、Control Blockに以下のデータがこの順番で含まれる。

<control byte with leaf version and parity bit> <internal key p> <C> <E> <AB>

次にTapTweakは以下のように計算される。

D = tagged_hash("TapLeaf", bytes([leaf_version]) + ser_script(script))
CD = tagged_hash("TapBranch", C + D)
CDE = tagged_hash("TapBranch", E + CD)
ABCDE = tagged_hash("TapBranch", AB + CDE)
TapTweak = tagged_hash("TapTweak", p + ABCDE)

鍵パスを使ったコインの使用

Taprootアウトプットは、internal_pubkeyに対応する秘密鍵を使って使用することができる。そのためには、witnessスタックは上記で定義されたsignature hashに対し、上記スニペット内と同じhを使って調整された秘密鍵で作成したBIP340署名という単一の要素で構成する。以下のコードを参照:

def taproot_sign_key(script_tree, internal_seckey, hash_type):
    _, h = taproot_tree_helper(script_tree)
    output_seckey = taproot_tweak_seckey(internal_seckey, h)
    sig = schnorr_sign(sighash(hash_type), output_seckey)
    if hash_type != 0:
        sig += bytes([hash_type])
    return [sig]

この関数は、必要なwitnessスタックと、上記で定義したsignature hashを計算するためのsighash関数を返す(簡単にするため、上記のスニペットトランザクション、インプットの位置などの情報をsighashのコードに渡していない)。

スクリプトの1つを使ったコインの使用

Taprootアウトプットは、その構築に使用されたスクリプトのいずれかを満たすことで使用できる。そのためには、スクリプトへの入力に加えて、スクリプト自体とControl Blockで構成されるwitnessスタックが必要になる。以下のコードを参照:

def taproot_sign_script(internal_pubkey, script_tree, script_num, inputs):
    info, h = taproot_tree_helper(script_tree)
    (leaf_version, script), path = info[script_num]
    output_pubkey_y_parity, _ = taproot_tweak_pubkey(internal_pubkey, h)
    pubkey_data = bytes([output_pubkey_y_parity + leaf_version]) + internal_pubkey
    return inputs + [script, pubkey_data + path]

安全性

Taprootは、アウトプットを使用する際に全ての条件を明らかにするのではなく、満たされた使用条件のみを公開すれば良いので、Bitcoinのプライバシーを向上させる。理想的には、アウトプットは観察者がコインの使用条件を知ることがないキーパスを使ったコインの使用である。キーパスを使ったコインの使用は、単一もしくは複数の署名のウォレットからの通常の支払いか、隠れたマルチパーティコントラクトの共同決済のいずれかである。

スクリプトパスを使ったコインの使用は、スクリプトパスが存在すること、(例えば関係者が合意に達しなかったなどで)キーパスが使われなかったことをリークすることになる。さらに、マークルルートのスクリプトの深さは、ツリーの最小の深さを含む情報をリークする。これはアウトプットを作成したウォレットソフトウェアを示唆し、クラスタリングを助けることになる。したがって、したがってスクリプトを使用する際のプライバシーは、リーフに対する確率分布により最適なツリー構造から逸脱することでプライバシーを改善する。

他の既存のアウトプットタイプと同様に、プライバシー上の理由からTaprootアウトプットはキーを再利用すべきではない。これはアウトプットを使用する際に使われた特定のリーフだけでなく、アウトプットにコミットされたすべてのリーフにも適用される。リーフが再利用されると、別のアウトプットを使用する際にマークルプルーフで同じマークルブランチが再利用されるといったことが起きる可能性がある。新しいキーを使用することで、Taprootで使われているブランチソートのマークルツリー構造によってすでにランダム化されているため、Taprootのアウトプットの構築においてリーフ位置のランダム化に特別な対策を講じる必要が無い。これはリーフの深さを通して情報が漏れるのを避けるものではないため、バランスの取れた(サブ)ツリーにのみ適用される。さらに、すべてのリーフは、他のリーフとは異なるキーのセットを持つ必要がある。この理由は、リーフのエントロピーを増加させ、ブルートフォース検索を使って観察者が未知のスクリプトを学習するのを防ぐためだ。

Test Vector

作成トランザクションと使用トランザクションの有効、無効のペアの例。

sighashモード毎のsighashのプリイメージの例。

まだないっぽい。

展開

まだ展開方法は未定義。

後方互換

ソフトフォークとして、古いソフトウェアは変更なく動作し続ける。ただし、アップグレードしていないノードではSegwitのバージョン1のwitness programを誰でも使用可能なスクリプトとみなす。新しいprogramを完全に検証するためにアップグレードすることを推奨する。

アップグレードされていないウォレットは、Segwitのバージョン0のprogramおよび従来のpay-to-pubkey-hashなどを使って、アップグレードしたウォレットおよびアップグレードしていないウォレットからBitcoinの受信、送金ができる。実装によっては、BIP 173のBech32アドレスへの送金をサポートしている場合、アップグレードされていないウォレットがSegwit version 1のprogramに送金できる場合がある。

論拠


  1. セキュリティの仮定を追加しないというのはどういう意味か?署名が偽造不可能であることは、盗難を防ぐための必須要件だ。少なくともスクリプトの実行をデジタル署名スキーム自体として扱う場合、離散対数問題の困難性を仮定とするランダムオラクルモデルで偽造不可能性が証明できる。現在のスクリプトシステムにおけるECDSAの偽造不可能性の証明には、さらにその上に非標準の仮定が必要だ。ウォレットソフトウェアで使用されるポリシーとプロトコルに依存するため、スクリプトのセキュリティが意味するものを正確にモデル化することは一般的に難しいことに注意してほしい。

  2. 公開鍵がアウトプットに直接含まれるのはなぜか? 初期の典型的な構造ではスクリプトや公開鍵のハッシュをアウトプットに格納するが、公開鍵が常に含まれている場合これはかなり無駄になる。バッチ検証が可能であることを保証するために、公開鍵は全ての検証者に知られている必要があり、したがって出力としてそのハッシュを明らかにすることだけが、witnessに追加の32バイトを追加することを意味する。さらに、アウトプットに対して128ビットの衝突の安全性を維持するためには、とくかく256ビットのハッシュが必要になる。これはサイズが(送信者にとってはコストが)、直接公開鍵を明らかにするのに匹敵する。公開鍵ハッシュの使用は、ECDLPの破壊や量子コンピューターへの保護になるとよく言われるが、その保護は非常に弱い。トランザクションは承認される間保護されず、通貨の供給の大部分はそのような保護の対象にならない。そのようなシステムへの実際の抵抗は、異なる暗号仮定に依存することで実現できる。しかしこの提案は、セキュリティモデルを変更しない改善にフォーカスしている。

  3. なぜP2SHによるラップをサポートしないのか? P2SHでラップしたアウトプットの使用は、160ビットのハッシュが使用されるため80ビットの衝突の安全性のみを提供する。この数値は低いとみなされアウトプットに複数のパーティのデータが含まれる場合、常にセキュリティリスクになる。

  4. annexの先頭バイトが0x50なのはなぜか? 有効なP2WPKHもしくはP2WSHのアウトプットと混同しないように0x50が選択されている。Control Blockの先頭バイトの最下位ビットが公開鍵のYのパリティを示すのに使われるため、各リーフバージョンには偶数バイト値とP2WPKHおよびP2WSHの使用時にまだ使われていない奇数バイト値を必要とする。annexを示すには、0x50のようにペアでない利用可能なバイトのみが必要だ。この選択は将来のスクリプトバージョンのために利用可能なオプションを最大にする。

  5. annexの目的は何か? annexは、使用されるアウトプットについて知らなくても認識できるような方法で、計算的に高価な新しいopcodeの検証コストを示すなど、将来の拡張のために予約されたスペースだ。このフィールドの意味が他のソフトフォークによって定義されるまでは、ユーザーはトランザクションannexを含めるべきではなく、或いは永久的な資金の喪失に繋がるかもしれない。

  6. マークルパスの長さが128までに制限されている理由は? ハフマンアルゴリズムを使用して、リーフのスクリプトの確立に基づき最適な空間効率のマークルツリーを構築できるため。このアルゴリズムはlog2(1/probability)にほぼ等しい長さのブランチを構築するが、128を超えるブランチを作成するには、1/2^{128}未満の実行チャンスを持つスクリプトが必要になり、このような低い可能性はおそらく完全に削除できる。

  7. リーフバージョンにはどのような制約がある? まずc[0] & 0xfeは常に偶数であるため、リーフバージョンを奇数にすることはできず、また0x50にすることもできない(0x50にした場合annexとの曖昧さが生じるため)。さらに、使用されるアウトプットにアクセスすることなくスクリプトの使用を特定できることに依存する静的分析のいくつかの形式をサポートするために、有効なP2WPKH公開鍵や有効なP2WSHスクリプトのいずれかの有効な先頭バイトと競合するリーフバージョンの使用を避けることを推奨する(つまり、vとv|1の両方とも未定義、無効、無効なopcodeもしくは最初のopcodeとして無効なものでなければならない)。このルールに準拠する値は、0xc0から0xfeまでの偶数値、0x66, 0x7e, 0x80, 0x84, 0x96, 0x98, 0xba, 0xbc 0xbeだ。またこの制約はリーフバージョンを異なるwitness version間で共有する必要があることを意味するこtに注意すること。witness versionを知るには使用されるアウトプットにアクセスする必要がある。

  8. マークルツリー内でハッシュされる前に子要素がソートされるのはなぜ? そうすることでマークルブランチのハッシュを明らかにする際に一緒に左右の方向を明らかにする必要がなくなるため。これが可能なのは、ツリー内の特定のスクリプトの位置を実際には気にしておらず、それらが実際にコミットされているということだけを気にしているためだ。

  9. 内部のマークルノードにとってもっと効率的なハッシュ構造を使わないのはなぜ? 選択された構造は、SHA256圧縮関数を2回呼び出す必要があり、2回のうち1回は理論的に避けることができる(BIP-98参照)。しかし、実装の単純さと分析可能性のため、標準の暗号プリミティブを使って実装できる構造に固執するのが好ましいように思われる。必要なら、64バイトの入力用に特化することで2つ目の圧縮関数の大部分を最適化できる。

  10. 使用するスクリプトパスに適用できるスクリプトルールは何? BIP342はリーフバージョンが0xc0の場合に適用される有効性ルールを指定するが、将来の提案では他のリーフバージョンに対してルールを導入できる。

  11. なぜ未知のhash_typeを拒否するのか? そうすることで、十分なキャッシングを持つ実装が実行しなければならない署名ハッシュのワーストケースを推論するのが簡単になる。

  12. どの拡張機能がext_flagの仕組みを利用するのか? BIP342は同じ共通の署名メッセージアルゴリズムを再利用するが、BIP342固有のデータを最後に追加する。これをext_flag = 1を使って明示する。

  13. SigMsg()の出力長は? SigMsg()の長さは次の式を使って計算できる。174 - is_anyonecanpay * 49 - is_none * 32 + has_annex * 32

  14. 署名メッセージのシリアライゼーションが変更されたのはなぜ? 署名メッセージとメッセージ自体に送られるハッシュががdouble SHA256ではなく単一のSHA256で計算されるようになった。SHA256を二回しても安全性の向上は期待できない。double SHA256は伸長攻撃への保護になるのみで、署名メッセージには秘密のデータは無いので意味がない。したがって、SHA256を2回するのはリソースの無駄である。メッセージの計算は、最初にトランザクションレベルのデータ、次にインプットのデータ、アウトプットのデータと論理的な順に行われる。これによりSHA256のmidstateを使って、異なるインプットに渡るメッセージのトランザクション部分を効率的にキャッシュできる。さらにメッセージの計算で、ハッシュの前に一部にゼロをセットするBIP 143ダイジェストとは対照的にサブハッシュをスキップできる。そのようにしても、可変長データの前に(hash_typespend_typeに暗黙的に含まれる)データの長さにコミットすることで衝突は不可能になる。

  15. 署名メッセージがscriptPubKeyにコミットするのはなぜ? これにより、実際に実行されたスクリプトが正しい場合でも(BIP 143のscriptCode)、使用されるアウトプットのタイプについてオフライン署名デバイスに嘘を付くのを防ぐ。つまり、ハードウェアウォレットに対して、どの(未使用の)実行パスが存在したかをコンパクトに証明することが可能だ。さらに使用済みの全てのscriptPubkeyにコミットすることで、オフライン署名デバイスが自身のウォレットに属するサブセットを決めるのに役立つ。これは自動化されたCoinJoinにとっても役立つ。

  16. 署名メッセージがすべてのインプットの量にコミットするのはなぜ? これにより取引の手数料についてオフラインの署名デバイスに嘘を付く可能性がなくなる。

  17. SIGHASH_SINGLEまたはSIGHASH_NONEがセットされている場合、署名メッセージがすべてのインプットのnSequenceにコミットするのはなぜ? これらをセットすると、すでにすべてのトランザクションインプットのprevoutにメッセージがコミットするため、nSequenceを別の値にすると役に立たなくなる。さらにこの変更により、nSequenceSIGHASH_SINGLESIGHASH_NONEトランザクションアウトプットに対してのみ署名メッセージの変更を許可し、インプットに関しては署名メッセージを変更できないという観点と一致する。

  18.  {hash_{TapSigHash}}への入力の先頭に0x00が付いているのはなぜ? このプレフィックスはsighash epochと呼ばれ、ハッシュの実行方法に外来性の変更を加える将来の署名アルゴリズム {hash_{TapSigHash}}タグ付きハッシュを再利用できる(インクリメンタル拡張に使われているext_flagとは対照的)。別の方法として異なるタグを使用することもできるが、サポートするタグの数が増えると望ましくない場合がある。

  19. 65バイトの署名でhash_typeを0x00できないのはなぜ? これを許可すると64バイトの署名を65バイトに変更することができ、作者が意図したものとは異なるwtxidと手数料が発生する。

  20. 2つの署名の長さを許可するのはなぜ? 最も一般的なタイプのhash_typeを暗黙的にすることで、多くの場合1バイト節約できる。

  21. スクリプトパスが無い場合でも、アウトプットキーが常にtaprootコミットメントを持つのはなぜ? taprootが鍵の集約である場合、悪意ある参加者が他の参加者に気付かれずにスクリプトパスを追加する可能性がある。これによりマルチパーティポリシーを迂回してコインを盗むことができる。MuSigの鍵集約には、内部キーが既にランダム化されているため、この問題はない。攻撃は次のように機能する。アリスとマロリーはスクリプトパスを使用せずに鍵をtaprootアウトプットキーに集約すると仮定する。鍵のキャンセルと関連する攻撃を防ぐのにMuSigの代わりにMSDL-popを使用する。MSDL-popプロトコルでは、全ての関係者が、対応する秘密鍵の所有の証明を提供する必要があり、集約鍵は各鍵の合計に過ぎない。マロリーがアリスの鍵Aを受け取った後、 {M = M_0 + int(t)G}を作成する。ここで {M_0}はマロリーの元々の鍵で、tは内部キー {P = A + M_0}スクリプトパスで、スクリプトはマロリーの鍵のみを含む。マロリーはMの所有の証明をアリスに送信し、両者はアウトプットキーQ = A + M = P + int(t)Gを計算する。アリスはスクリプトパスを認識できないが、マロリーはアウトプットキーQを持つコインを一方的に使用できる。