SegWitの分かりづらい点とその理由

HashHub Space
GBEC Tech Blog
Published in
13 min readNov 27, 2018

序文

基礎的な内容なので、知っている方は飛ばしてください。

ビットコインはトランザクション(TX)と呼ばれるデータの単位を使って、送金を行うわけですが、TXにはインプットとアウトプットが存在します。

アウトプットはscriptPubKeyと呼ばれるスクリプトを、インプットはscriptSigというスクリプトをそれぞれ格納しています。

scriptPubKeyは、そのアウトプットが表す資産の移動(利用)条件を記述し、scriptSigはその利用条件を満たす解除方法を記述しています。

検証時に二つを合わせて実行し、結果がOKでなければ無効なTXとみなして破棄します。

scriptSigには署名データそのものも入っているため、署名データのうちの一つでも変更されると、txid (トランザクションを一意に識別するためのハッシュ値) が変わってしまうという問題点がありました。malleabilityというやつですね。「スクリプト自体はtxidの元データに含めたいが、署名を含めるとマルチシグの際にtxをアップデートしていくなどするとどうしてもtxidを固定できない」という問題の解決策として提案されたのがSegWitです。

おまけとして実質的なブロックサイズの拡張や署名に使うデータの効率化、スクリプトのバージョン管理機構なども導入されました。

問題点

以上が基礎中の基礎で、色々なサイトで解説されている訳ですが、実際にスクリプトを扱おうとすると色々と分かりづらい点があります。現在スクリプトを扱おうとするとSegWit対応TXを使うのが普通ですが、SegWitの用語や使用方法に歴史的な経緯が残っているのが分かりづらさの一因になっています。例えば

Q1.witness, witness program, script witness, witness scriptってそれぞれどう違うの?

Q2. なぜ P2SH では scriptPubKey が SegWit みたいになっていないの?

Q3. 一部しか署名されていない TX のスクリプトはどうなっているの?

Q4. scriptPubKey は locking script って呼ぶことになったんじゃないの?

Q5. scriptCode って何?

などです。背景を交えて説明します。

TL;DR

時間がない人のために上の疑問に簡単に回答すると

A1. それぞれ以下を意味する

  • witness script … 実行されるスクリプトそのもの
  • witness item … 公開鍵や署名など。witness script へのインプットとなるデータ
  • witness … witness item と witness script を合わせたもの。シリアライズしたTXの最後の方につく。
  • script witness … witness と同義 (非推奨)
  • witness program … witness script または pubkey のハッシュ値(ただのハッシュ値です。紛らわしい名前ですが。)

A2. P2SHが考案された当初はまだソフトフォークのバージョン管理についての議論が不十分だったため

A3. scriptSig ならば、署名が入るべき部分に OP_0 (無を push ) が入っている。script witness ならば何も入っていない (無そのもの) 。

A4. locking script という呼び方はあまり定着していない。

  • 多くの場合、scriptPubKey に入っているスクリプトは、実体としてはlockしているスクリプトのコミットメント (ハッシュ) であることが多いため。

A5. SegWit TX の場合、トランザクションにスクリプトの実体が含まれていない場合がある。そのような場合、SegWit のバージョンに応じてプログラムを自動生成する。この自動生成されたものを scriptCode と呼び、署名や検証の際に使用される。

当初の構想

ビットコインの仕組みをゼロから構築する際、スクリプトは必須ではありませんでした。単にアウトプットには公開鍵を記載し、インプットでは対応する署名を記載すれば一応は機能するためです。ホワイトペーパーにはスクリプトに関する説明がないことや、pubkeyに相当する部分を「scriptPubKey」、署名に相当する部分を「scriptSig」という名称にしていることから、当初はこのような想定だったと思われます。

しかしこれだけではあまりにも不便です。特にマルチシグが使えないことは利便性を大きく損ねます。Satoshi は一応その問題を見越して簡単なスクリプト言語を実装しましたが、セキュリティ上問題のあるオペコードが含まれていたり、匿名性がなかったりと突貫作業で作った様なものでした。

Satoshi の初めの想定では、ビットコインのアウトプットに解除条件を直接記載するつもりでしたが、これには以下の様な欠点があります。

  1. 支払い側が余分なTX手数料を支払う
  • 本来、送金する側は相手がマルチシグであろうとなかろうと関係ないはずですが、相手がマルチシグだとその分の条件を scriptPubKey に埋め込まなくてはならないため、TXが大きくなり余分なブロックスペースを消費することになってしまいます。

2. 匿名性がない。

  • 上と同様の理由で、送金者は相手の資産の解除条件を見ることができてしまいます。

3. アドレスが読みにくい

  • 送金先のアドレスの長さがまちまちなので、冗長なアドレスをホームページに貼ったりすると、すり替えに気づきにくくなるなどの問題があります。

P2SH

解決策として提案されたのが、P2SHです。アウトプットには解除条件のコミットメントだけを乗せれば良いというのが基本的なアイディアです。

scriptPubKey の形式は

`OP_HASH160 [20-byte-hash-value] OP_EQUAL`

の様になっているわけですが、これは古いノードから見ると「hash160したら20-byte-hash と一致するデータをscriptSigに与えれば解除条件を満たす」という意味です。

なぜこれがマルチシグとして機能するかというと、新しいノードには「この様な scriptPubKey を見つけたら scriptSig の最後のデータは新たなスクリプトとみなせ」というルールが追加されているからです。そこにマルチシグ(または任意の解除条件)のスクリプトを書き込めば、それまで output 側に記述してあった解除条件を input 側に移すことができるため、上記の問題点を解消できます。

マスタリングビットコインという定番の教科書には、「 scriptPubKey をlocking script と呼び、scirptSig を unlock script と呼びましょう」という提案がありますが、P2SHの機能を踏まえるとこれは誤解を招きやすいのでやめた方が良いというのが私見です。input 側にスクリプトの実体が移ったため、locking script という用語がどちらを指すのか曖昧になったためです。この傾向は SegWit でさらに強まりました。

P2SH の導入法は、「ルールを狭めることによってルールを追加する」というソフトフォークの一般的なパターンに従っています。

ソフトフォークを理解するには、「古いルールは新しいルールをエンコードするための基盤になっている」という点を理解できると簡単です。例えば、「じゃんけん」というゲームをソフトフォークして、「あいこなら0、それ以外なら1」というルールにすると、1と0を使って任意のゲームをエンコードできます。ソフトフォークでやっているのは本質的にはそういうことです。

この点を理解できると、「 P2SH の scriptPubKey は必ずしもこの形になっている必要はなかった」ということがわかります。「スクリプトの実行後に、stack の一番上のアイテムが0でなければ検証に成功したとみなす」というのがビットコインのスクリプト言語の基本的なルールだからです。

  1. 古いノードからみて有効なスクリプトである。
  2. 新しいノードが「これは P2SH の scirptPubKey である」ということが理解できる。

という条件さえ満たせればなんでもよかったのです。これはSegWitの理解で重要になります。

代替案としては OP_EVAL というものがありましたが、ハードフォークを必要とすることと、無限再帰に陥る可能性があったため却下されました。その後 OP_EVAL のアイディアを洗練させて復活させたのがEthereumです。

P2SHはシンプルながら大きな前進でしたが、malleabilityの問題を解決するものではありませんでした。

BIP62で、SegWitなしで malleability を解決するための方法が述べられていますが、かなり複雑です。おまけにこれだけ頑張ってもマルチシグなどの場合は完全になくすことは不可能です。(とはいえ、ECDSA署名の決定的導出などは理解しておくとソースコードを読む際に疑問が減ります)スクリプトそれ自体と、スクリプトへのインプット(多くの場合、署名データ) を分離させることを目標にしたのがSegWitです。署名データは対象データを「見る者」(Witness)とみなせるので、

「分離された目撃者」Segrigated Witness = SegWit

という名称がついています。

P2WPKH, P2WSH

SegWitでは、上のP2SHの方式をさらに洗練させて、スクリプトをやそのインプットデータを格納する部分をscriptSigから丸ごとscript witnessと呼ばれる別の場所に移動させています。P2SHでは、scriptPubKeyは普通に意味のあるスクリプトっぽい形をしていましたが、これは誤解を招きやすい形式でした。実際の機能は単なる識別子であったためです。

よって SegWit では本当に識別子になりました。スタック上の以下のアイテムをそれぞれpushするスクリプトが入っています。

[1byteのバージョン] [32byte以下の値]

1byteのバージョンは、後方互換性のための領域です。現在は0のみですが、後々スクリプトの仕組みをアップデートした場合、この部分で、「今実行しているのはどのバージョンか」を識別します。

二つ目が witness program と呼ばれている部分です。P2WPKH の場合、支払い対象となる pubkey のハッシュが入っています。P2WSH の場合、スクリプトのハッシュです。(それぞれ別の hash 関数を使うようにプロトコルで規定しているので、長さで見分けることができます。)

ただのハッシュなのになぜ program という名前が付いているのかというと、以下の理由からです。

  1. P2SH では、識別子は (たまたま) 所定の形式のプログラムであったため、そのアナロジーを継続して用いている。
  2. 後々のバージョンでは、スクリプトが入る可能性がある。

scriptSig は空になっているので、これをそのまま実行すると stack には二つの item が push されただけの状態になり、古いノードは、「よくわからんが有効なスクリプトであることは違いない」と判断して検証をパスします。

新しいノードは、SegWitの識別子の条件を満たしているので witness に格納された item を使って検証を試みます。

なぜこれがソフトフォークで導入できたのか?という疑問に関しては、Peter Toddのこちらの記事がわかりやすいです。Coinbase 上のデータをを新しいマークルルートとみなせば、ほぼ任意の変更を導入できるということを解説しています。

witness に格納されたアイテムとは、P2WPKH の場合署名と pubkey の二つだけです。

スクリプトがないので実行方法がわからないはずですが、「 P2WPKH である」という情報から、スクリプトの内容は一意に定まるので、検証時にノードがスクリプトを自動生成します。この「一意に定まるので導出可能なスクリプト」を" scriptCode "と呼びます。署名の際も生成してからシリアライズして署名の元データの一部にしていたりします。こちらは必然性はないです(多分)。単に「プロトコルでそう規定されているから」追加しています。

witness の item は、「アイテムのプッシュを指定した scirpt 」ではなく、アイテムそのものです。これは容量の削減のためですが、scirptSig と script witness の重要な違いなので覚えておきましょう。scriptCodeと同様、SegWitであるということさえわかっていれば自動で解釈できるため、このような形式でも問題が無いようになっています。

P2WSHの場合、アイテムには署名、公開鍵に加えてスクリプトそのものが入っています。このスクリプトを witness script と呼びます( script witness と混同しないように注意しましょう。script witness は witness 全体を指します。単に witness とだけ言っておいたほうが賢明です。)

スクリプトの自動生成などがないので、P2WPKHよりも簡単に理解できると思います。P2SHの場合、署名データそのものではなく、「署名データをプッシュするスクリプト」が、scirptSig に入っていました。そのため、まだ署名がない場合には、プレイスホルダーとして `OP_0` を置いていました。これは「無を push しろ」という意味のスクリプトです。

対してP2WSHの場合、これから署名データが入る予定の部分には何も入っていません。上記の理由で、アイテムそのものが入るため、「無を push しろ」ではなく「無そのもの」が入っていた方が一貫性があるためです。

結語

短くまとめるつもりでしたが、思ったより長い内容になってしまいました。この辺の知識は「わかってしまえば自明なのだが、わかりやすい説明が見つからなくて困る」ということが多いので、これから学習する方の一助になれば幸いです。

執筆者:宮本 丈(ブロックチェーン ソフトウェア開発者:@joemphilips

京都大学を卒業後、国立がんセンターでデータ解析を主とした研究活動に従事。仕事中に読んだ論文からビットコイン・ブロックチェーンの魅力に気づき、勢い余って退職。現在は暗号通貨領域の研究開発などにフルコミットしている。専門領域はビットコインを用いたクラウドファンディング・実用的なサイドチェーンの形式など。

お知らせ

■HashHubでは入居者募集中です!

HashHubは、ブロックチェーン業界で働いている人のためのコワーキングスペースを運営しています。ご利用をご検討の方は、下記のWEBサイトからお問い合わせください。また、最新情報はTwitterで発信中です。

HashHub:https://hashhub.tokyo/

Twitter:https://twitter.com/HashHub_Tokyo

■ブロックチェーンエンジニア集中講座開講中!

HashHubではブロックチェーンエンジニアを育成するための短期集中講座を開講しています。お申込み、詳細は下記のページをご覧ください。

ブロックチェーンエンジニア集中講座:https://www.blockchain-edu.jp/

■HashHubでは下記のポジションを積極採用中です!

・コミュニティマネージャー

・ブロックチェーン技術者・開発者

・ビジネスディベロップメント

詳細は下記Wantedlyのページをご覧ください。

Wantedly:https://www.wantedly.com/companies/hashhub/projects

--

--