Develop with pleasure!

福岡でCloudとかBlockchainとか。

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

先日、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-taprootの訳(ドラフトなので、MLなどでの議論を経てこれから更新されていく)。わりと途中でこの仕様、要素何のためにあるの?と疑問に思うところがあるけど、論拠と合わせて読むといい。

追記:2019/09/02 時点で以下の内容を更新

動機

Bitcoinスクリプト機能を改善するためのアイディアがこれまでいくつか提案されている。

  • Schnorr署名(bip-schnorr)
  • マークルブランチ(MAST, BIP-114,117)
  • 新しいSIGHASHモード(BIP-118)
  • CHECKSIGFROMSTACKのような新しいopcode
  • Taproot
  • Graftroot
  • G'root
  • インプットをまたぐ集約

これらの全てのアイディアを1つの提案にまとめるのは、大規模な変更となり、レビューが大変になり、そうでなければ途中で実現されていたかもしれない新しい発見を見逃す可能性がある。これらのアイディアの中には、他のものより成熟度が低いものもある。一方、すべての個々の独立した提案に分割すると、効率とプライバシーの利点が現象し、相互作用の分析が複雑になる。一度に一つのゴールセットにフォーカスし、それらを達成するために相互作用する技術を組み合わせることが望ましい。

設計

この提案は、以下の2つの制限を条件として、Bitcoinのスマートコントラクトのプライバシー、効率性、柔軟性の改善にフォーカスする。

  • 新しく強力なセキュリティ仮定を追加しない。
  • 独立して単純に実装される可能性がある機能を提案に組み入れない。

具体的には、トランザクションアウトプットの使用条件に関する情報が、作成時または使用時にチェーン上で明らかになる際に、その情報の量を最小限にすることを目的としている。将来の改善の有効性を損なわないようにするため、マイナーだが長年の問題に対する修正と同様に、いくつかのアップグレードの仕組みも含まれる。

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

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

この提案に含まれていないのは、将来の拡張としての有効性がある新しいSIGHASHモードやopcodeのような追加機能だ。またインプットをまたいだ集約も含まれていない。これはアップグレードの仕組みと複雑な方法で相互作用するため、それに対する解決策が依然として流動的であるため。

仕様

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

以下の表記はbip-schnorrの表記に従う。

Scriptの検証ルール

Taprootのアウトプットはバージョン番号1、32バイトのwitness programを持つ(ネイティブもしくはP2SHでネストされた)Segwitアウトプットである。以下のルールは、そのようなアウトプットが使用される場合にのみ適用される。32バイト以外の長さを持つバージョン1のアウトプットの場合、ルールは適用されず誰もが使用できる。

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

qはTaprootアウトプットキーと呼ばれ、c[1:33] はTaproot内部キーと呼ばれる。

署名検証ルール

以下のルールが適用される。

  • 署名が64バイトもしくは65バイトでない場合、失敗する。
  • 署名のサイズが65バイトの場合、
    • 最終バイトが有効なhash_type(後述)でない場合、失敗する。
    • 最終バイトが0x00の場合、失敗する。
    • 先頭64バイトが、最終バイトとしてhash_typeをセットしたトランザクションダイジェストをメッセージとし、それと公開鍵に対してbip-schnorrによる有効な署名ではない場合、失敗する。
  • 署名のサイズが64バイトの場合、
    • hash_type = 0x00トランザクションダイジェストをメッセージとし、それと公開鍵に対してbip-schnorrによる有効な署名ではない場合、失敗する。
  • それ以外の場合、署名は有効である。
hash_type

hash_typeは8bitの符号なしの値。SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE, SIGHASH_ANYONECANPAYを含む従来のスクリプトシステムのSIGHASHエンコーディングが使用される。

以下のhash_typeの使用は無効であり、実行が失敗する。

  • 対応するアウトプット(検証されるインプットと同じインデックスを持つアウトプット)が無いのに、SIGHASH_SINGLEを使用する。
  • 0x00, 0x01, 0x02, 0x03, 0x81, 0x82, 0x83以外のhash_typeを使用する。
  • 署名が65バイトで、hash_type0x00の場合。
Transaction digest

署名検証のメッセージとして、トランザクションダイジェストは以下の値を(バイト単位のサイズで)シリアライズされた {hash_{TapSighash}}になる。2,4または8バイトの数値はリトルエンディアンでエンコードされる。

  • Control:
    • epoch (1): 常に0
    • hash_type (1)
  • トランザクションデータ
  • このインプットのデータについて
    • spend_type(1):
      • 使用するscriptPubKeyがP2SHである場合はビット0をセットする。
      • annexが存在する場合、ビット1をセットする(元のwitnessスタックに2つ以上のwitness要素があり、最後の要素の最初のバイトが0x50)。
      • 他のビットはセットされない。
    • scriptPubKey(24 or 35): このインプットが使用する前のアウトプットのscriptPubKeyで、CTxOut無いのスクリプトとしてシリアライズされる。サイズはP2SHに埋め込んだSegwitの場合24バイトで、ネイティブSegwitの場合は36バイト。
    • SIGHASH_ANYONECANPAYフラグがセットされている場合、
      • outpoint(32): このインプットのCOutPoint(32バイトのハッシュ + 4バイトのリトルエンディアン)
      • amount(8): このインプットで使われる前のアウトプットのコインの量
      • nSequence(4): このインプットのnSequence
    • SIGHASH_ANYONECANPAYフラグがセットされていない場合
      • input_index(2): トランザクション無いでこのインプットのインデックス。最初のインプットのインデックスは0。
    • spend_typeにビット1がセットされている場合、
      • sha_annex(32): (compact_size(annexのサイズ) || annex)のSHA256ハッシュ値
  • このアウトプットのデータについて
    • SIGHASH_SINGLEフラグがセットされている場合
      • sha_single_output(32): 対応するアウトプットのCTxOutフォーマットのSHA256ハッシュ値

ハッシュされたバイト数の合計は最大209。

要約すると、BIP 143のセマンティクスはSIGHASHタイプは変更しないまま、以下を除いて変更されない。

  • リアライゼーションの方法と順番が変更される。
  • ダイジェストはscriptPubKeyにコミットする。
  • SIGHASH_ANYONECANPAYフラグがセットされない場合、ダイジェストはすべてのインプットの量にコミットする。
  • SIGHASH_NONEもしくはSIGHASH_SINGLEがセットされていた場合(SIGHASH_ANYONECANPAYがセットされていない限り)、ダイジェストはすべてのインプットのnSequenceにコミットする。
  • ダイジェストはTaproot固有のデータepochspend_type、(あれば)annexにコミットする。

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に関して既知の離散対数を持たないことを証明し、検証者は内部キーの作成方法を再構築できる。
  • 残りのスクリプトは、二分木のリーフに編成する必要がある。スクリプトの各条件が同じようにリーフに対応する場合、ツリーはバランスがとれたツリーになる。各条件の確率が分かっている場合は、ツリーをハフマン木として構築することを検討してほしい。

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

使用条件が内部キーinternal_pubkeyとリーフが(leaf_version、スクリプト)のタプルである二分木に分割されると、以下のPython3 アルゴリズムを使ってアウトプットスクリプトを計算できる。以下のコードでは、ser_scriptプレフィックスはその入力のCCompactSizeエンコードされた長さを前に付与し、public keyオブジェクトはbip-schnorrに従って32バイトの公開鍵を保持し、バイト配列を取得するget_bytesメソッドと、公開鍵とsecp256k1ジェネレータの乗算を合算に対応する新しい公開鍵を返すtweak_addメソッドを持つ(BIP32の導出と同様)。tweak_addの2つめの戻り値は結果の点のY座標の二次残渣を示すbool値。tagged_hashはbip-schnorrに従いタグ付きハッシュを計算する。

import hashlib

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-tapscript scripts)
     - a list of two elements, each with the same structure as script_tree itself"""
    _, h = taproot_tree_helper(script_tree)
    t = tagged_hash("TapTweak", internal_pubkey.get_bytes() + h)
    assert int.from_bytes(t, 'big') < SECP256K1_ORDER
    output_pubkey, _ = internal_pubkey.tweak_add(t)
    return bytes([0x51, 0x20]) + output_pubkey.get_bytes()

https://github.com/sipa/bips/raw/bip-schnorr/bip-taproot/tree.png

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

キーパスを使ったコインの使用

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

def taproot_sign_internal_key(internal_pubkey, script_tree, internal_privkey, hash_type):
    _, h = taproot_tree_helper(script_tree)
    t = tagged_hash("TapTweak", internal_pubkey.get_bytes() + h)
    output_privkey = internal_privkey.tweak_add(t)
    sig = output_privkey.sign_schnorr(sighash(hash_type))
    if hash_type != 0:
        sig += bytes([hash_type])
    return [sig]

この関数は、tweak_addメソッドが秘密鍵を導出し、sighash関数が上記で定義されたsignature hashを計算し、必要なwitnessスタックを返す(簡単にするため、上記のスニペットトランザクションやインプットの位置、P2SHかどうかといった情報をsighashのコードへの受け渡しを無視している)。

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

Taprootのアウトプットはinternal_pubkeyに対応する秘密鍵で使用できる。そのためには、witness stackは単一の要素で構成される。この要素は上記で定義されたsignature hashへ、上記のスニペットと同じくtで調整された秘密鍵を使ったbip-schnorr署名だ。以下のコードでは、internal_privkeyにはpubkey_genメソッドがあり、これはbip-schnorrに基づいて公開鍵とベースとなる点のY座標を示すbool値を返す。

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]
    t = tagged_hash("TapTweak", internal_pubkey.get_bytes() + h)
    _, is_y_qresidue = internal_pubkey.tweak_add(t)
    output_pubkey_tag = 0 if is_y_qresidue else 1
    pubkey_data = bytes([output_pubkey_tag + leaf_version]) + internal_pubkey.get_bytes()
    return inputs + [script, pubkey_data + path]

安全性

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

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

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

論拠

  1. 公開鍵がアウトプットに直接含まれるのはなぜか? 初期の典型的な構造ではスクリプトや公開鍵のハッシュをアウトプットに格納するが、公開鍵が常に含まれている場合これはかなり無駄になる。バッチ検証が可能であることを保証するために、Qは全ての検証者に知られている必要があり、したがって出力としてそのハッシュを明らかにすることだけが、witnessに追加の32バイトを追加することを意味する。さらに、アウトプットに対して128ビットの衝突の安全性を維持するためには、とくかく256ビットのハッシュが必要になる。これはサイズが(送信者にとってはコストが)、直接公開鍵を明らかにするのに匹敵する。公開鍵ハッシュの使用は、ECDLPの破壊や量子コンピューターへの保護になるとよく言われるが、その保護は非常に弱い。トランザクションは承認される間保護されず、通貨の供給の大部分はそのような保護の対象にならない。そのようなシステムへの実際の抵抗は、異なる暗号仮定頼ることで実現できる。しかしこの提案は、セキュリティモデルを変更しない改善にフォーカスしている。P2SHでラップされたアウトプットを使うと80ビットの衝突安全性しか得られないことに注意すること。これは低いとみなされ、アウトプットに複数の関係者からのデータが含まれている場合は常に関連がある。
  2. annexの先頭バイトが0x50なのはなぜか? 0xc0-0xc1定数と同様に、有効なP2WPKHもしくはP2WSHのアウトプットと混同しないように0x50を使っている。Control Blockの先頭バイトの最下位ビットが公開鍵のYの偶奇を示すのに使われるため、各スクリプトのバージョンはP2WPKHおよびP2WSHの使用時にまだ使われていない2つのサブシーケンスバイトを必要とする。annexを示すには、0x50のようにペアでない有効なバイトのみが必要だ。この選択は将来のスクリプトバージョンのために利用可能なオプションを最大にする。
  3. annexの目的は何か? annexは、使用されるアウトプットについて知らなくても認識できるような方法で、計算的に高価な新しいopcodeの検証コストを示すなど、将来の拡張のために予約されたスペースだ。このフィールドの意味が他のソフトフォークによって定義されるまでは、ユーザーはトランザクションannexを含めるべきではなく、或いは永久的な資金の喪失に繋がるかもしれない。
  4. Control Blockの先頭バイトの用途は? Control Blockの先頭バイトには3つの異なる機能がある:
    • 下位ビットは点PのY座標の偶奇を示すために使われる。
    • 上位2ビットにtrueをセットしておくことで、使用されるUTXOの知識が無くてもスクリプトを認識できるようになるため、分析が簡単になる。これは、P2WPKHやP2WSHを使用する際には最後のスタック要素の先頭バイトがそのような値になることは無いため。
    • 残りの5ビットは実際に実行されない限り観測できない新しいスクリプトバージョンを導入するのに使われる。
  5. マークルツリー内でハッシュされる前に子要素がソートされるのはなぜ? そうすることでマークルブランチのハッシュを明らかにする際に一緒に左右の方向を明らかにする必要がなくなるため。これが可能なのは、ツリー内の特定のスクリプトの位置を実際には気にしておらず、それらが実際にコミットされているということだけを気にしているためだ。
  6. 内部のマークルノードにとってもっと効率的なハッシュ構造を使わないのはなぜ? 選択された構造は、SHA256圧縮関数を2回呼び出す必要がなく、2回のうち1回は理論的に理論的に避けることができる(BIP-98参照)。しかし、実装の単純さと分析可能性のため、標準の暗号プリミティブを使って実装できる構造に固執するのが好ましいように思われる。必要なら、64バイトの入力用に特化することで2つ目の圧縮関数の大部分を最適化できる。
  7. 使用するスクリプトパスに適用できるスクリプトルールは何? bip-taprootはリーフバージョンが0xc0の場合に適用される有効性ルールを指定するが、将来の提案では他のリーフバージョンに対してルールを導入できる。
  8. 2つの署名の長さを許可するのはなぜ? 最も一般的なタイプのhash_typeを暗黙的にすることで、多くの場合1バイト節約できる。
  9. 65バイトの署名でhash_typeを0x00できないのはなぜ? これを許可すると64バイトの署名を65バイトに変更することができ、作者が意図したものとは異なる手数料率になる。
  10. なぜ未知のhash_typeを拒否するのか? そうすることで、十分なキャッシングを持つ実装が実行しなければならない署名ハッシュのワーストケースを推論するのが簡単になる。
  11. epochの用途は? epochを増加させることで、必要に応じてhash_typeの解釈を変更したり、構造を大幅に変更する新しいトランザクションダイジェストアルゴリズムを安全に作成できるようにする。
  12. signature hashのためにハッシュされたバイト数は?  {hash_{TapSighash}}への入力の(最初の64バイトのハッシュタグを除く)合計サイズは次の式で計算できる。177 - is_anyonecanpay * 50 - is_none * 32 - is_p2sh_spending * 12 + has_annex * 32
  13. トランザクションダイジェストのシリアライゼーションが変更されたのはなぜ? ダイジェストに入るハッシュとダイジェスト自身がdouble SHA256ではなく単一のSHA256で計算されるようになった。SHA256を二回しても安全性の向上は期待できない。double SHA256は伸長攻撃への保護になるのみで、トランザクションダイジェストには秘密のデータは無いので意味がない。したがって、SHA256を2回するのはリソースの無駄である。ダイジェストの計算は、最初にトランザクションデータ、次にインプットのデータ、アウトプットのデータと論理的な順に行われる。これによりSHA256のmitstateを使って、異なるインプットに渡るトランザクションのダイジェストの一部を効率的にキャッシュできる。さらにダイジェスト計算では、ハッシュの前に一部にゼロをセットするBIP 143ダイジェストとは対照的に不要なハッシュを回避する。そのようにしても、可変長データの前に(hash_typespend_typeに暗黙的に含まれる)データの長さにコミットすることで衝突は不可能になる。
  14. トランザクションダイジェストがscriptPubKeyにコミットするのはなぜ? これにより、実際に実行されたスクリプトが正しい場合でも(BIP 143のscriptCode)、使用されるアウトプットのタイプについてオフライン署名デバイスに嘘を付くのを防ぐ。scriptPubKeyにコミットしないと、攻撃者はオフライン署名デバイスに、実際にはSegwitのネイティブアウトプットにも関わらず、P2SHでラップされたSegwitアウトプットに署名するよう要求することで、デバイスに多めに手数料を支払わせるように騙すことができる。
  15. トランザクションダイジェストがすべてのインプットの量にコミットするのはなぜ? これにより取引の手数料についてオフラインの署名デバイスに嘘を付く可能性がなくなる。
  16. SIGHASH_SINGLEまたはSIGHASH_NONEがセットされている場合、トランザクションダイジェストがすべてのインプットのnSequenceにコミットするのはなぜ? これらをセットすると、すでにすべてのトランザクションインプットのprevoutにダイジェストがコミットされるため、nSequenceを別の値にすると役に立たなくなる。さらにこの変更により、nSequenceSIGHASH_SINGLESIGHASH_NONEトランザクションアウトプットに対してのみダイジェストの変更を許可し、インプットに関してはダイジェストを変更できないという観点と一致する。

後方互換

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

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