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

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進エンコードされた鍵はこれらの文字でカバーされることに注意されたい。拡張鍵(xpubとxprv)は他の文字も使うが、それら自身のチェックサム機構も持つ。
      • SCRIPT式の関数名は他の文字も使うが、これらの誤りは一般にパース不能な記述子という結果になる。
    • 大文字・小文字の誤りは常に1シンボルエラーとして数えられる。
    • それ以外の1文字置換誤りは1または2シンボルエラーとして数えられる。
  • 1シンボルエラーは常に検出される。
  • 49154文字以下の記述子における2または3シンボルエラーは常に検出される。
  • 507文字以下の記述子における4シンボルエラーは常に検出される。
  • 77文字以下の記述子における5シンボルエラーは常に検出される。
  • 387文字以下の記述子において5シンボルエラーが見逃される確率を最小化するよう最適化されている。
  • ランダムな誤りが見逃される確率は240分の1である。

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

チェックサム付きの有効な記述子文字列は、下記のPython3コード片で規定される妥当性の基準を満たさなければならない。 関数descsum_checkは、引数sがSCRIPT#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個のシンボルごとに、その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