ビットコイン・インスティテュート

BIP 380 — 出力スクリプト記述子の​総論

  BIP: 380
  Layer: Applications
  Title: Output Script Descriptors General Operation
  Authors: Pieter Wuille <pieter@wuille.net>
           Ava Chow <me@achow101.com>
  Status: Deployed
  Type: Informational
  Assigned: 2021-06-27
  License: BSD-2-Clause

概要

出力スクリプト記述子(Output Script Descriptors)は、出力スクリプトの集合を記述するために使える簡潔な言語である。 記述子の断片や関数には多くの種類がありうる。 本文書は記述子の一般的な構文、記述子チェックサム、および共通の式について記述する。

著作権

本 BIP は BSD 2 条項ライセンスの下でライセンスされる。

動機

ビットコインウォレットは伝統的に鍵の集合を保存し、それをのちにシリアライズ・変形することで、ウォレットが監視する出力スクリプトや利用者に提示するアドレスを生成してきた。 バックアップは通常、秘密鍵のみで構成されており、近年ではおおむね BIP 39 ニーモニックの形をとる。 しかし、このバックアップ方式は不十分である。とりわけ、新しい出力タイプを追加した Segregated Witness の導入以降はなおさらである。 秘密鍵だけが与えられても、復元されたウォレットにはどの種類の出力スクリプトやアドレスを生成すればよいかが分からない。 これがバックアップの復元やウォッチオンリーウォレット向けのデータ出力の際に、ウォレット間の非互換性を招いてきた。

さらに事態を複雑にしているのが BIP 32 の導出パスである。 BIP 44、49、84 はそれぞれ異なる出力スクリプトやアドレスに対応する標準的な BIP 32 導出パスを規定しているが、すべてのウォレットがこれらの導出パスに対応し、あるいは実際に用いているわけではない。 こうしたバックアップやデータ出力に導出パスの情報が欠けていることが、ウォレット間のさらなる非互換性を招いている。

これらの問題への現行の解決策は汎用的でなく、レイヤー違反とも見なせる。 拡張鍵シリアライズのために異なるバージョンバイトを導入するといった解決策は、レイヤー違反である(鍵導出はスクリプトタイプの意味と分離されるべきである)うえに、特定の導出パスとスクリプトタイプにしか通用しない。

出力スクリプト記述子は、これらの問題に対する汎用的な解決策を導入する。 スクリプトタイプはスクリプト式(Script Expression)を用いて明示的に指定される。 鍵の導出パスは鍵式(Key Expression)で明示的に指定される。 これにより、正確なスクリプト・サブスクリプト(redeemScript、witnessScript 等)・鍵を指定するウォレットバックアップやデータ出力を作成できるようになる。 本 BIP が規定する一般的な構造により、新しいスクリプトタイプが追加されるたびに新しいスクリプト式を導入できる。 最後に、共通の用語や既存の標準を用いることで、出力スクリプト記述子はエンジニアにとって読みやすく、結果を一目で理解できるものになる。

仕様

記述子はいくつかの種類の式で構成される。 最上位の式は SCRIPT である。 この式のあとに #CHECKSUM を続けてよい。CHECKSUM は 8 文字の英数字からなる記述子チェックサムである。 チェックサムはパース上は省略可能だが、アプリケーションはチェックサムを含まない記述子を拒否してよい。

スクリプト式

スクリプト式(SCRIPT と表記)は、ビットコインのスクリプトに直接対応する式である。 これらの式は関数として書かれ、引数を取る。 こうした式はスクリプトのテンプレートを持ち、対応する引数でそれを埋める。 式は人間が読める識別子文字列として書かれ、引数は丸括弧で囲んで示す。 識別子文字列は英数字であり、アンダースコアを含んでよい。

スクリプト式に対する引数は、その式自身によって定義される。 引数はスクリプト式であることも、鍵式であることも、あるいはまったく別の式であることもある。

鍵式

スクリプト式への引数として使われる共通の式が鍵式(KEY と表記)である。 これは公開鍵または秘密鍵を表し、任意でその鍵の出自に関する情報も表す。 鍵式はスクリプト式への引数としてのみ使用できる。

鍵式は次の要素からなる。

  • 任意で、鍵の出自情報。これは次の要素からなる。
    • 開き角括弧 [
    • 導出が始まる鍵のフィンガープリントを表す、正確に 8 文字の 16 進文字(詳細は BIP 32 参照)
    • 続いて 0 個以上の /NUM または /NUMh というパス要素。フィンガープリントとそれに続く鍵の間の、非硬化または硬化の導出ステップを示す。
    • 閉じ角括弧 ]
  • 続いて実際の鍵。これは次のいずれかである。
    • 16 進エンコードされた公開鍵。スクリプト式によっては次のいずれかになる。
      • 02 または 03 で始まる 66 文字の 16 進文字列。圧縮公開鍵を表す。
      • 04 で始まる 130 文字の 16 進文字列。非圧縮公開鍵を表す。
    • WIF (https://en.bitcoin.it/wiki/Wallet_import_format) エンコードされた秘密鍵
    • xpub エンコードされた拡張公開鍵、または xprv エンコードされた拡張秘密鍵(BIP 32 で定義されるもの)
      • 続いて 0 個以上の /NUM または /NUMh というパス要素。与えられた拡張鍵のあとに取るべき BIP 32 導出ステップを示す。
      • 任意で、その後に単一の /* または /*h という最終ステップを続け、直接のすべての非硬化または硬化の子を示す。

KEY が BIP 32 拡張鍵である場合、出力スクリプトを作成する前に、拡張鍵に続く導出情報を用いて子鍵を導出しなければならない。 最終ステップが /* または /*' の場合、すべての子鍵インデックスについて出力スクリプトが生成される。 導出された鍵は非圧縮公開鍵としてシリアライズしてはならない。 スクリプト式は、スクリプト作成のために導出された公開鍵をどのようにシリアライズすべきかについて、さらなる要件を持つことがある。

上記の仕様において、硬化を示す指標 h は代替の硬化指標 ' に置き換えてよい。

硬化導出を含む鍵式の正規化

記述子を秘密鍵なしでエクスポートする場合、エクスポートされた記述子を有用にするために、途中にある硬化導出ステップをすべて取り除く追加の導出を行う必要がある。 エクスポートする側は、最後の硬化導出ステップにおける拡張公開鍵を導出し、その拡張公開鍵を記述子内の鍵として用いるべきである。 その鍵に至るまでに取られた導出ステップは、それ以前の鍵の出自情報に追加しなければならない。 鍵の出自情報がまだ存在しない場合は、新たに導出された拡張公開鍵のために出自情報を追加しなければならない。 最後の導出が硬化導出である場合は、追加の導出を行う必要はない。

文字集合

記述子チェックサムが機能するよう、記述子で使われる式はこの文字集合内の文字のみを含まなければならない。

許可される文字は次のとおりである。

0123456789()[],'/*abcdefgh@:$%{}
IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~
ijklmnopqrstuvwxyzABCDEFGH`#"\<space>

最終行の <space> は空白文字を意味することに注意されたい。

この文字集合は、下記のチェックサムがより多くの誤りを識別できるよう、この特定の順序で 32 文字ずつ 3 グループに分けて書かれている。 最初のグループは最も一般的な「保護されていない」文字(すなわち 16 進数や鍵パスなど、それ自体は既にチェックサムを持たないもの)である。 大文字・小文字の誤りは 32 の倍数のオフセットを生じさせる。その一方で、上記の制約に従いながら、できるだけ多くのアルファベット文字を同じグループにまとめている。

チェックサム

最上位のスクリプト式に続けて、単一の井桁記号(#)と、それに続く 8 文字のチェックサムを置く。 このチェックサムは bech32 に似た誤り訂正チェックサムである。

チェックサムには次の性質がある。

  • 記述子文字列の誤りは「シンボルエラー」の数で測られる。シンボルエラーの数が多いほど、検出は難しくなる。
    • 0123456789()[],'/*abcdefgh@:$%{} のうちのある文字を、同じ集合内の別の文字に置き換える誤りは、常に 1 シンボルエラーとして数えられる。
      • 16 進エンコードされた鍵はこれらの文字でカバーされることに注意されたい。拡張鍵(xpubxprv)は他の文字も使うが、それら自身のチェックサム機構も持つ。
      • SCRIPT 式の関数名は他の文字も使うが、これらの誤りは一般にパース不能な記述子という結果になる。
    • 大文字・小文字の誤りは常に 1 シンボルエラーとして数えられる。
    • それ以外の 1 文字置換誤りは 1 または 2 シンボルエラーとして数えられる。
  • 1 シンボルエラーは常に検出される。
  • 49154 文字以下の記述子における 2 または 3 シンボルエラーは常に検出される。
  • 507 文字以下の記述子における 4 シンボルエラーは常に検出される。
  • 77 文字以下の記述子における 5 シンボルエラーは常に検出される。
  • 387 文字以下の記述子において 5 シンボルエラーが見逃される確率を最小化するよう最適化されている。
  • ランダムな誤りが見逃される確率は 240 分の 1 である。

チェックサム自体は bech32 と同じ文字集合を用いる: qpzry9x8gf2tvdw0s3jn54khce6mua7l

チェックサム付きの有効な記述子文字列は、下記の Python3 コード片で規定される妥当性の基準を満たさなければならない。 関数 descsum_check は、引数 sSCRIPT#CHECKSUM の形の記述子であるとき true を返さなければならない。

INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "
CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
GENERATOR = [0xf5dee51989, 0xa9fdca3312, 0x1bab10e32d, 0x3706b1677a, 0x644d626ffd]

def descsum_polymod(symbols):
    """Internal function that computes the descriptor checksum."""
    chk = 1
    for value in symbols:
        top = chk >> 35
        chk = (chk & 0x7ffffffff) << 5 ^ value
        for i in range(5):
            chk ^= GENERATOR[i] if ((top >> i) & 1) else 0
    return chk

def descsum_expand(s):
    """Internal function that does the character to symbol expansion"""
    groups = []
    symbols = []
    for c in s:
        if not c in INPUT_CHARSET:
            return None
        v = INPUT_CHARSET.find(c)
        symbols.append(v & 31)
        groups.append(v >> 5)
        if len(groups) == 3:
            symbols.append(groups[0] * 9 + groups[1] * 3 + groups[2])
            groups = []
    if len(groups) == 1:
        symbols.append(groups[0])
    elif len(groups) == 2:
        symbols.append(groups[0] * 3 + groups[1])
    return symbols

def descsum_check(s):
    """Verify that the checksum is correct in a descriptor"""
    if s[-9] != '#':
        return False
    if not all(x in CHECKSUM_CHARSET for x in s[-8:]):
        return False
    symbols = descsum_expand(s[:-9]) + [CHECKSUM_CHARSET.find(x) for x in s[-8:]]
    return descsum_polymod(symbols) == 1

これは上記の性質を持つ BCH 符号を実装している。 記述子文字列全体は、まずシンボルの配列に変換される。 各文字のシンボルは、その文字が属するグループ内での位置である。 3 個ごとのシンボルのあとに、それまでのグループ番号を組み合わせた 4 個目のシンボルが挿入される。 これにより、グループ内の位置だけを変える変更や、グループ番号だけを変える変更は、単一のシンボルにしか影響を与えない。

あるスクリプト式に対して有効なチェックサムを構築するには、以下のコードを使用できる。

def descsum_create(s):
    """Add a checksum to a descriptor without"""
    symbols = descsum_expand(s) + [0, 0, 0, 0, 0, 0, 0, 0]
    checksum = descsum_polymod(symbols) ^ 1
    return s + '#' + ''.join(CHECKSUM_CHARSET[(checksum >> (5 * (7 - i))) & 31] for i in range(8))

テストベクトル

以下のテストはチェックサムと文字集合を対象とする。

  • 有効なチェックサム: raw(deadbeef)#89f8spxm
  • チェックサムなし: raw(deadbeef)
  • チェックサム欠落: raw(deadbeef)#
  • 長すぎるチェックサム(9 文字): raw(deadbeef)#89f8spxmx
  • 短すぎるチェックサム(7 文字): raw(deadbeef)#89f8spx
  • ペイロード内の誤り: raw(deedbeef)#89f8spxm
  • チェックサム内の誤り: raw(deedbeef)##9f8spxm
  • ペイロード内の無効な文字: raw(Ü)#00000000

以下のテストは鍵式を対象とする。

有効な式:

  • 圧縮公開鍵: 0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600
  • 非圧縮公開鍵: 04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235
  • 鍵の出自付き公開鍵: [deadbeef/0h/0h/0h]0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600
  • 鍵の出自付き公開鍵(硬化指標として ' を使用): [deadbeef/0'/0'/0']0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600
  • 鍵の出自付き公開鍵(硬化指標が混在): [deadbeef/0'/0h/0']0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600
  • WIF 非圧縮秘密鍵: 5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss
  • WIF 圧縮秘密鍵: L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1
  • 拡張公開鍵: xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL
  • 鍵の出自付き拡張公開鍵: [deadbeef/0h/1h/2h]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL
  • 導出付き拡張公開鍵: [deadbeef/0h/1h/2h]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/3/4/5
  • 導出と子鍵付き拡張公開鍵: [deadbeef/0h/1h/2h]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/3/4/5/*
  • 硬化導出と非硬化の子鍵付き拡張公開鍵: xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/3h/4h/5h/*
  • 硬化導出と硬化の子鍵付き拡張公開鍵: xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/3h/4h/5h/*h
  • 鍵の出自と硬化導出と硬化の子鍵付き拡張公開鍵: [deadbeef/0h/1h/2]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/3h/4h/5h/*h
  • 拡張秘密鍵: xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc
  • 鍵の出自付き拡張秘密鍵: [deadbeef/0h/1h/2h]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc
  • 導出付き拡張秘密鍵: [deadbeef/0h/1h/2h]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/3/4/5
  • 導出と子鍵付き拡張秘密鍵: [deadbeef/0h/1h/2h]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/3/4/5/*
  • 硬化導出と非硬化の子鍵付き拡張秘密鍵: xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/3h/4h/5h/*
  • 硬化導出と硬化の子鍵付き拡張秘密鍵: xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/3h/4h/5h/*h
  • 鍵の出自と硬化導出と硬化の子鍵付き拡張秘密鍵: [deadbeef/0h/1h/2]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/3h/4h/5h/*h

無効な式:

  • 鍵の出自内の子鍵指標: [deadbeef/0h/0h/0h/*]0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600
  • 鍵の出自内の末尾スラッシュ: [deadbeef/0h/0h/0h/]0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600
  • 短すぎるフィンガープリント: [deadbef/0h/0h/0h]0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600
  • 長すぎるフィンガープリント: [deadbeeef/0h/0h/0h]0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600
  • 無効な硬化指標: [deadbeef/0f/0f/0f]0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600
  • 無効な硬化指標: [deadbeef/-0/-0/-0]0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600
  • 無効な硬化指標: [deadbeef/0H/0H/0H]0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600
  • 無効な硬化指標: [deadbeef/0h/1h/2]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/3H/4h/5h/*H
  • 導出付き秘密鍵: L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1/0
  • 子鍵導出付き秘密鍵: L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1/*
  • 範囲外の導出インデックス: xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648
  • 無効な導出インデックス: xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/1aa
  • 複数の鍵の出自: [aaaaaaaa][aaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0
  • 鍵の出自の開始が欠落: aaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0
  • 16 進でないフィンガープリント: [gaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0
  • 公開鍵を伴わない鍵の出自: [deadbeef]

後方互換性

出力スクリプト記述子は、既存のいかなるソフトウェアとも互換性のない、まったく新しい言語である。 しかし、式の多くの構成要素は、それ以前の BIP で定義されたエンコーディングやシリアライゼーションを再利用している。

出力スクリプト記述子は、新たな断片タイプや新しいスクリプト式による将来の拡張を見込んで設計されている。 これらは追加の BIP で規定される。

参照実装

記述子は Bitcoin Core のバージョン 0.17 以降で実装されている。

付録 A:式の索引

将来の BIP は追加の種類の式を規定することがある。 利用可能なすべての式の種類を以下の表に示す。

名前表記BIP
スクリプトSCRIPT380
KEY380
ツリーTREE386
注釈ANNOT?

付録 B:スクリプト式の索引

スクリプト式は追加の BIP で規定される。 以下の表は利用可能なすべてのスクリプト式と、それを規定する BIP を示す。

BIP
pk(KEY)381
pkh(KEY)381
sh(SCRIPT)381
wpkh(KEY)382
wsh(SCRIPT)382
multi(NUM, KEY, ..., KEY)383
sortedmulti(NUM, KEY, ..., KEY)383
combo(KEY)384
raw(HEX)385
addr(ADDR)385
tr(KEY)tr(KEY, TREE)386
musig(KEY, KEY, ..., KEY)390
sp(KEY)sp(KEY, KEY)392