Develop with pleasure!

福岡でCloudとかBlockchainとか。

Output Descriptorとscantxoutsetを使ってUTXOセットをスキャンする

Bitcoin Core 0.17.0からUTXOセットをスキャンするscantxoutsetというRPCが追加された。

Bitcoin Core :: scantxoutset (0.17.0 RPC)

今まではウォレット内に鍵を持つUTXOの情報はlistunspentなどで取ってこれてたけど、↑はウォレット機能とは別で、UTXOセットの中から指定した条件に合うUTXOを検索することができる。

この時、検索対象を指定する際に使用する記述子がOutput Descriptor↓

bitcoin/descriptors.md at 0.17 · bitcoin/bitcoin · GitHub

(訳は後述)

Output Descriptorを指定してscantxoutsetでUTXOを検索

scantxoutsetは以下のように2つの引数を取る。

$ bitcoin-cli scantxoutset <action> <scanobjects>
  • action
    実行するアクションを指定。
    • startでスキャンを開始
    • abortで現在のスキャンを打ち切り
    • statusで現在のスキャンの進捗を確認
  • scanobjects
    スキャン対象を配列で指定。各要素で指定可能なスキャン対象は今のところ以下の3つ
    • descriptor: 後述のOutput Descriptorの文字列を指定
    • ['desc': xxx, 'range': n]: (オプション)Output Descriptorとメタデータを含むオブジェクト。子HDチェーンをどこまで探索するかのインデックスを指定して検索したい場合(デフォルト:1000)

例えば、公開鍵0259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520のP2PKHのUTXOを探す場合、以下のようにscantxoutsetを実行してしばらく待つと結果が得られる↓

$ bitcoin-cli -testnet scantxoutset start "[  \"pkh(0259f6658325c4e3ca6fb38f657ffcbf4b1c45ef4f0c1dd86d5f6c0cebb0e09520)\" ]" 
{
  "success": true,
  "searched_items": 20353944,
  "unspents": [
    {
      "txid": "1915bdd359feb0bb12bc4f6f1f0791b982def6936af420473f892bacd16db014",
      "vout": 1,
      "scriptPubKey": "76a9143b9722f91a2e50d913dadc3a6a8a88a58a7b859788ac",
      "amount": 0.10015556,
      "height": 1117751
    },
    {
      "txid": "e9dcfee7eb0715570cf8b3cc2149309ca4c0af3a4387f4eb92a4c04d746986ed",
      "vout": 1,
      "scriptPubKey": "76a9143b9722f91a2e50d913dadc3a6a8a88a58a7b859788ac",
      "amount": 0.46970645,
      "height": 1117751
    }
  ],
  "total_amount": 0.56986201
}

実行中は別のターミナルから↓を実行するとスキャンの進捗(%)が確認可能。

$ bitcoin-cli -testnet scantxoutset status
{
  "progress": 45
}

とこんな感じで、手軽にUTXOセットを検索できるようになったのは便利だ。

scanobjectsの指定方法は以下のようにOutput Descriptorを構成することで様々な検索が可能。

Output Descriptorの機能

以下、Output Descriptorの機能部分の訳↓

現在以下のOutput Descriptorをサポートしている。

  • pk関数を介して、Pay-to-pubkeyスクリプト(P2PK)をサポート
  • pkh関数を介して、Pay-to-pubkey-hashスクリプト(P2PKH)をサポート
  • wpkh関数を介して、Pay-to-witness-pubkey-hashスクリプト(P2WPK)をサポート
  • sh関数を介して、Pay-to-script-hashスクリプト(P2SH)をサポート
  • wsh関数を介して、Pay-to-witness-script-hashスクリプト(P2WSH)をサポート
  • multi関数を介して、マルチシグスクリプトをサポート
  • addr関数を介して、サポートされる任意のアドレスタイプ
  • raw関数を介して、RAWスクリプトのhex表記をサポート
  • hex表記の公開鍵(圧縮、非圧縮)または、BIP32拡張公開鍵と導出パス

サンプル

  • pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)は、P2PKアウトプットを表す。
  • pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)は、P2PKHアウトプットを表す。
  • wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)は、P2WPKHアウトプットを表す。
  • sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))は、P2SH-P2WPKHアウトプットを表す。
  • combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)は、P2PK、P2PKH、P2WPKH、P2SH-P2WPKHアウトプットを表す。
  • sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))は、P2SH-P2WSH-P2PKHアウトプットを表す。
  • multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)は、素の1-of-2のマルチシグを表す。
  • sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))は、P2SHの2-of-2のマルチシグを表す。
  • wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))はP2WSHの2-of-3のマルチシグを表す。
  • sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))はP2SH-P2WSHの1-of-3のマルチシグを表す
  • pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)は、指定されたxpubの公開鍵部分を使った単一のP2PKアウトプットを示す。
  • pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1'/2)は、指定されたxpubの1'/2の子鍵を使った単一のP2PKHアウトプットを示す。
  • wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/0/*))は、2つのHDチェーンの対応する導出パスの公開鍵を使った1-of-2のP2WSHマルチシグアウトプットを示す。

参照

記述子はいくつかのタイプの式で構成される。トップレベルの式は常にSCRIPT

SCRIPT式:

  • sh(SCRIPT)(トップレベルのみ): 引数を埋め込んだP2SH
  • wsh(SCRIPT)(別のwshの内部であってはならない): 引数を埋め込んだP2WSH
  • pk(KEY)(どこでも可): 与えられた公開鍵P2PKアウトプット
  • pkh(KEY)(どこでも可): 与えられた公開鍵のP2PKHアウトプット(公開鍵ハッシュしか知らない場合は代わりにaddrを使用)
  • wpkh(KEY)wshの内部以外): 与えられた圧縮公開鍵のP2WPKHアウトプット
  • combo(KEY)(トップレベルのみ): pk(KEY)pkh(KEY)のコレクションのエイリアス。もしkeyが圧縮されている場合、wpkh(KEY)sh(wpkh(KEY))も含まれる。
  • multi(k,KEY_1,KEY_2,...,KEY_n)(どこでも可): k-of-nのマルチシグスクリプト
  • addr(ADDR)(トップレベルのみ): ADDRが展開されるスクリプト
  • raw(HEX)(トップレベルのみ): HEXとして16進エンコードされたスクリプト

KEY式:

  • Hexエンコードされた公開鍵(02および03で始まる場合は66文字、04で始まる場合は130文字)。
    • wpkhwshの内部では、圧縮された公開鍵のみが許可される。
  • WIFエンコードされた秘密鍵は、対応する公開鍵の代わりに同じ意味で指定できる。(BIP 32で定義されている)xpubエンコードされた拡張公開鍵もしくは、xprvエンコードされた拡張秘密鍵
    • 0以上の/NUMの非強化鍵もしくは/NUM'強化鍵のBIP 32導出パスが続く。
    • オプションで、/*もしくは/*で直接的な子どもの非強化鍵、強化鍵のステップが続く。
    • 強化鍵の導出ステップの使用には、秘密鍵の提供が必要となる。
    • 'の代わりにh suffixを使って強化導出を表すことができる。

ADDR式はサポートされている任意のアドレス:

  • P2PKHアドレス(1...から始まるbase58フォーマット)。記述子内のP2PKHアドレスは、P2PKアウトプットに使用できないことに注意すること(代わりにpk関数を使うこと)。
  • P2SHアドレス(3...から始まるbase58フォーマット、BIP 13で定義)
  • Segwitアドレス(bc1...から始まるbech32フォーマット、BIP 173で定義)

説明

単一鍵のスクリプト

実際には、単一キーの構成が多く使われ、一般にP2PK、P2PKH、P2WPKHおよびP2SH-P2WPKHが含まれる。最適ではないがもっと多くの組み合わせが考えられるP2SH-P2PK, P2SH-P2PKH, P2WSH-P2PK, P2WSH-P2PKH, P2SH-P2WSH-P2PK, P2SH-P2WSH-P2PKH。

これを記述するために、これらを関数としてモデル化する。関数pk (P2PK)、pkh (P2PKH)、wpkh (P2WPKH)は、16進表記の公開鍵を入力として受け取り、対応するscriptPubKeyを返す。関数sh (P2SH) 、wsh (P2WSH)は入力としてスクリプトを受け取り、インプットをスクリプトとして埋め込んだP2SH及びP2WSHのアウトプットを記述したスクリプトを返す。関数名は簡潔にするためp2を省略している。

マルチシグ

いくつかのソフトウェアでは、BitcoinのOP_CHECKMULTISIG opcodeに基づいてマルチシグスクリプトを使用する。これをサポートするために、multi(k,key_1,key_2,...,key_n)関数を導入する。これは提供されたn個の公開鍵の内、任意のk個の署名が必要なk-of-nのマルチシグを表す。

BIP 32導出鍵とチェーン

最新のウォレットソフトウェアやハードウェアはBIP 32(HDキー)を利用して導出した鍵を使用する。期待される公開鍵を(一般にxpubと呼ばれる)拡張公開鍵と導出パスで構成される文字列で指定することを許可することで、これらを直接サポートする。導出パスは0以上の整数の順序で構成され、それぞれオプションで'もしくはhが付き、/文字で区切られる。文字列はオプションで、非強化導出、強化導出の全ての子鍵を参照するため/*または/*'(もしくは/*h)で終わることがある。

強化導出ステップを使って公開鍵を指定する場合、対応する秘密鍵にアクセスすることなくスクリプトを計算することはできない。

秘密鍵の包含

必要な秘密鍵を一緒にスクリプトの説明を伝えることはよく役に立つ。このため、公開鍵またはxpubがサポートされている場合、代わりにWIFフォーマットもしくはxprvを提供することができる。これは強化導出ステップや、秘密鍵の材料を含むウォレット記述子のダンプなどで秘密鍵が必要な場合に便利だ。

古いウォレットとの互換性

既存のBitcoin Coreウォレットで現在サポートされているスクリプトのセットを簡単に表現するため、入力として公開鍵を受け取り、その鍵でP2PK, P2PKH, P2WPKHおよび P2SH-P2WPHスクリプトを構成する便利な関数comboが提供される。公開鍵が圧縮されていない場合、P2PKおよびP2PKHのみを構成する。

lndで実装されているBIP-39に代わるシード管理方式「aezeed」

lndでBIP-39*1の欠点を修正するために実装されたシードの新しい管理方式「aezeed」について↓読んでみた。

https://github.com/lightningnetwork/lnd/tree/master/aezeed

2018年3月のSF Bitcoin Devsで@roasbeefがlnd v0.4-betaの紹介した際にも触れられてる。

www.youtube.com

docs.google.com

aezeedはBIP-39の以下の欠点を修正する:

  • バージョンの欠如
    バージョン情報がない場合、ウォレットがリカバリープロセス中にどのアドレスを再導出すべきか分からない場合がある(秘密鍵や公開鍵は導出できても、そこからどのアドレス:P2PKH、P2WPKH、P2SH-P2WSHを導出すべきか決定できない)。
  • ウォレットの誕生日の欠如
    ウォレットがいつできたかという情報がない場合、ウォレットが正しいユーザーのアドレスを全て確実に導出するために、チェーン内をどこまで見ていけば良いか分からない。

簡単に言うとこれらのデータをシードデータに含め、さらに暗号化したデータからmnemonicのワードリストを生成するようBIP-39を拡張した仕様になる。

具体的な仕様は↓

プレーンテキストのaezeedエンコーディング

aezeed方式のシードは、↑の欠点に対処するため以下の3つのフィールドを結合した値になる。

1バイトの内部バージョン || 2バイトのタイムスタンプ(ウォレットの誕生日) || 16バイトのエントロピー

内部バージョンは、ウォレットがウォレットの鍵を再導出する方法を知ることができるようにするためのフィールド。

2バイトのタイムスタンプはウォレットの誕生日を管理するためのフィールド。通常タイムスタンプは4バイトのデータ領域を必要とするが、このフィールドは2バイトでタイムスタンプを表現するためBitcoin Days Genesisで表される。これはBitcoinのジェネシスブロックのタイムスタンプからの日数を意味する。このフォーマットによりスペースを節約し、無駄な粒度を使わないようにすると。現在、これは2188年までの時間を表現することができる。

最後のエントロピーは、ウォレットのHDルートを導出するのに使用される。

BIP-39ではエントロピーからmnemonicを直接生成していたのに対し、aezeedではバージョンとタイムスタンプが加わった上記データからmnemonicが作られることになる。

aezeed 暗号化/復号化

BIP-39ではエントロピーにそのチェックサムを付与したデータを11bit毎に分割してmnemonicのワードリストを作成していたが、aezeedでは上記のプレーンテキストシードを暗号化してできた暗号テキスト(CipherSeed)からmnemonicのワードリストを生成する。

暗号化して生成される最終的なデータは以下の3つのフィールドを結合した値になる。

1バイトの外部バージョン || 暗号テキスト || 8バイトのチェックサム

1つめの外部バージョンは、暗号化方式を定義したもので、2つめの暗号テキストが実際にプレーンテキストシードを暗号化したデータ、最後がチェックサムで、以下のプロセスで生成される(暗号化プロセスでは、ユーザーが定義したパスフレーズが使われる。パスフレーズが指定されていない場合は、文字列「aezeed」が使われる)。

  1. 最初に、外部バージョンをバッファに追加する。外部バージョンは使用する暗号化方法について定義する。最初のバージョン(version 0)では、暗号化にscrypt(n=32768, r=8, p=1)とaezeedを使う。
  2. 次にscrypt(n=32768, r=8, p=1)を使って、暗号化に使用する強めの鍵を生成する。saltとして5バイト使って、32バイトの鍵を生成する(saltを使うのはレインボーテーブルを作成できなくするため)。
  3. 続いて暗号化プロセス。ナンスの誤用に耐性があるモダンなAEADであるaezを使用する。ここで重要な特性は、任意の入力長のブロック暗号であること。さらに設定可能なMACサイズを持っている点で、aezeed方式では64bitのチェックサムとして機能する8を使う。生成されたシードとAD of (version || salt)を使って暗号化する。
  4. 最後にBIP-39のデフォルトのワードリストを使って、この33バイトの暗号テキストをエンコードし、24個の英単語を生成する。

こうやってプレーンテキストシード(19バイト)から暗号シード(33バイト)が生成される。

aezeed暗号シードの特性

aezeed暗号シード方式にはいくつかの特性がある。

  • mnemonic自体が暗号テキストから生成されているので、mnemonicだけ分かってもコインを盗むのは難しく、BIP-39でパスフレーズセットしていないとmnemonicが分かればコインが盗まれるのとは対照的。
  • パスフレーズを変更することで暗号シードを変更できる。ユーザーがより強力なパスフレーズを望む場合は、古いパスフレーズで復号し、次に新しいパスフレーズで暗号化することができる。BIP-39の場合、ユーザーがパスフレーズを使ったとしても、マッピングが一方向であるため、既存のHDキーチェーンのパスフレーズを変更することはできない。
  • 暗号シードをアップブレード可能。外部バージョンがあるので、オフラインツールを使って古いパラメータを復号し、新しいパラメータを使って暗号化することができる。将来暗号を変更したり、scryptを変更したり、scryptのパラメータのみを変更するば、ユーザーはオフラインツールでシードを簡単にアップグレードできる。

ウォレットの誕生日やバージョン情報の管理は、Lightningに限らずオンチェーンでも課題よね。

*1:BIP-39はHDウォレットのマスターシードを人間が覚えやすい単語リスト(mnemonic word)に変換する仕様を定義したBIP

Stratum protocol extensionsについて定義したBIP-310

Stratumはプールマイニングをサポートするマイニングプロトコルだが、その拡張の利用については長らく正式な規格を記述するBIPが無かったが、それをBIPとして定義したのがBIP-310↓

github.com

基本的には、Stratumサーバーとマイニングソフトウェアが接続された直後に、mining.configureメッセージを送信してサーバ/クライアント間でサポートしている拡張について認識し、利用する拡張について調整する仕組み。

BIP-310で定義されている拡張は以下の4つ。

  • version-rolling
  • minimum-difficulty
  • subscribe-extranonce
  • info

1つめのバージョンローリングは、こないだ書いたBIP-320の利用を前提としている(まぁ前提というか実際はもうそういう使い方されてる)。

techmedia-think.hatenablog.com

以下BIPに定義されているプロトコルの詳細↓

動機

Stratumプロトコルの拡張のサポートを指定する最初の動機は、マイナーにBitcoinのブロックヘッダの最初のフィールドの値を変更する「バージョンローリング」と呼ばれるものを許可することだった。

オリジナルのStratumプロトコルのバージョンではサーバーに異なるブロックバージョンの値を伝えることができなかったため、バージョンローリングはStratumプロトコル後方互換性がない。同様に、サーバーは安全なbitを使ってマイナーにローリングすることができなかった。したがってマイナーとプールの両方は、バージョンローリングをサポートするためにいつくかのプロトコル拡張を実装する必要がある。

通常、マイナーが未知のメッセージをサーバーに送信すると、サーバーは接続を閉じる(全てのサーバーが閉じるわけではないが、いくつかのサーバー実装ではそうなっている。)。そのため未知のメッセージをサーバーに送信することは安全ではない。

我々はこの機会を利用して、将来複数の拡張をサポートするためにプロトコルに対して後方互換性のない変更を行うことができる。マイナーがその能力を広告すると同時にサーバからいくつかの必要な機能を要求することができる。

機能のネゴシエーションのため同じ仕組みがまだ知られていない機能に対しても使用されることが望ましい。マイニングソフトウェアにも簡単に実装されるべきだ。

標準的な方法で機能の初期設定/ネゴシエーションを処理するstratumプロトコル(mining.configure)への新しいメッセージを1つ導入する。これにより将来、stratumプロトコルに新しいメッセージを追加することなく機能を追加することができる。

各拡張にはextension codeと呼ばれる一意の文字列名がある。

仕様

現在、以下の拡張が定義されている。

  • version-rolling
  • minimum-difficulty
  • subscribe-extranonce

追加のデータタイプ

以下の名称はタイプエイリアスとして使われ、メッセージの定義を簡単にする。

  • TMask
    32bitの符号なし整数([0-9a-fA-F]{8})をエンコードする長さ8の大文字小文字を区別しない16進文字列
  • TExtensionCode
    プロトコル拡張の名前と同じ値の空でない文字列
  • TExtensionResult
    true / false / String
    • true 要求された機能がサポートされ、その設定が理解でき適用される。
    • false この機能はサポートしていないか未知のもの
    • String 何がうまく行かなかったかに関する情報を含む文字列

mining.configure リクエス

このメッセージ(JSON RPCリクエスト)は、サーバーとの接続が確立された後にマイナーが送信する最初のメッセージである必要がある。クライアントはこのメッセージを使って自身がサポートする機能を宣伝し、いくつかのプロトコル拡張を要求/許可する。

最初の理由は、実装と可能な限りやりとりを簡単、シンプルにしたいからだ。拡張は、拡張の繰り返し設定が何を意味するか明示的に定義することができる。

各拡張コードは、その拡張パラメータや拡張の戻り値に対してネームスペースを提供する。慣例により、名称は拡張コードに”.”とパラメータ名を追加することで形成される。同様のことが戻り値にも適用され、result mapにも転送される。例えば「version-rolling.mask」は拡張「version-rolling」の「mask」というパラメータ名だ。

パラメータ
  • extensions(必須。TExtensionCodeのリスト)
    • リスト内の各文字列は有効な拡張コードでなければならない。各コードの意味は、拡張定義の一部として独立して記述される。マイナーは利用可能な全ての機能を広告しなければならない。
  • extension-parameters(必須。String -> 任意のデータのMap)
    • 最初のパラメータから要求/許可された拡張のパラメータ
戻り値
  • String -> 任意のデータのMap
    • 拡張リストの各コードは、定義された戻り値(TExtensionCode -> TExtensionResult)を持たなければならない。こうすることでマイナーは拡張が有効になっているかどうか確認することができる。例えばバージョンローリングをサポートしていない場合は{"version-rolling":false}
    • いくつかの拡張では、追加情報をマイナーに提供する必要がある。戻り値のMapはこのために使われる。

リクエストの例

{"method": "mining.configure",
  "id": 1,
  "params": [["minimum-difficulty", "version-rolling"],
         {"minimum-difficulty.value": 2048,
          "version-rolling.mask": "1fffe000", "version-rolling.min-bit-count": 2}]}

(マイナーは「version-rolling」と「minimum-difficulty」という拡張を要求する。この際、拡張の定義に従ってパラメータをセットする)

結果の例

{"error": null,
  "id": 1,
  "result": {"version-rolling": true,
         "version-rolling.mask": "18000000",
         "minimum-difficulty": true}}

定義された拡張

拡張「version-rolling」

この拡張によりマイナーはブロックヘッダのバージョンフィールドの一部のbitの値を変更できます。現在バージョンローリングに使用される標準bitは存在しないため、マイナーとサーバー間で調整する必要がある。

マイナーはマイナーが変更可能なbitを記述マスクをサーバーに送信する。1 = 変更可能なbit、0 = 変更不可能なbit (miner_mask)とバージョンローリングに必要な最小bit数。

サーバーは通常、version bits(server_mask)の一部のみを変更することができ、残りのversion bitsは固定されている。これは例えばブロックを有効なブロックにするためであったり、何らかのシグナリングに使われている場合があるためだ。

サーバーはコンフィギュレーションメッセージに応答し、マイナーのマスクとサーバーのマスクの共通bit交差を持つマスクを送信する(response = server_mask & miner_mask)。

リクエスト例(16bitのマスクから任意の2 bitを変更できるマイナー)

{"method": "mining.configure", "id": 1, "params": [["version-rolling"], {"version-rolling.mask": "1fffe000", "version-rolling.min-bit-count": 2}]}

結果の例(成功の場合)

{"error": null, "id": 1, "result": {"version-rolling": true, "version-rolling.mask": "18000000"}}

結果の例(未知の拡張の場合)

{"error": null, "id": 1, "result": {"version-rolling": false}}
拡張パラメータ
  • version-rolling.mask (オプション、TMask、デフォルト値は"ffffffff"
    • 1をセットされたbitは、マイナーによって変更することができる。この値はマイニングセッション全体で安定していることが期待される。マイナーはマスクを送信する必要はなく、この場合デフォルトのフルマスクが使用される。
拡張の戻り値
  • version-rolling(必須、TExtensionResult)
    • trueの場合サーバーは新しいパラメータmining.submitを受け取る(後述)。
  • version-rolling.mask(必須、TMask)
    • 1をセットされたbitは、マイナーによって変更することができる。マイナーがマスク値0のbitを変更した場合、サーバーは送信を拒否する。
    • サーバーは可能な限り最大のマスクを返すべきだ(可能な限り多くのbitが1に設定された)。これはプロキシが将来のクライアントに最適なマスクを調整する必要がある場合に、マイニングプロキシの設定に役立つ。利用可能なnVersion bitsについて記述したドラフトがある。サーバーはBIPで指定されたすべてのbitをカバーするマスクを作成するべきだ。
  • version-rolling.min-bit-count(必須、TMask)
    • マイナーはまたハードウェアでの効率的なバージョンローリングに必要な最小bit数も提供する。このパラメータはプールサーバに重要な診断情報を提供することに注意すること。要求されたbit数がプールサーバーの制限を超えた場合、ハッシュパワーを完全に使用することがない、常に劣化モードで動作する可能性がある。この稀なミスマッチが発生した場合、マイナーとの接続を終了してはならない。
「mining.set_version_mask」の通知

サーバーは接続に有効な新しいマスクについてマイナーに通知する。このメッセージは、mining.configureメッセージによりバージョンローリングの拡張が正常にセットアップされた後、いつでも送信できる。新しいマスクはすぐに有効になり、サーバーは次のジョブを待機しない。

パラメータ
  • mask(必須、TMask)
    意味はversion-rolling.mask 戻りパラメータと同じ。

サンプル

{"params":["00003000"], "id":null, "method": "mining.set_version_mask"}
mining.submit 要求の変更

バージョンローリング拡張が正常にアクティベートされた直後に(サーバーによって送信されたmining.configureの結果)、サーバーはメッセージmining.submitの追加パラメータを受け取らなければならない。クライアントは追加のパラメータversion_bitsを1つ送らなければならない(worker_name, job_id, extranonce2, ntime, nonceに続く6つめのパラメータとして)。

追加のパラメータ
  • version_bits(必須、TMask)
    マイナーによってセットされるversion bits。
    • マイナーはmining.configureもしくはmining.set_version_mask通知(last_mask)の応答として、サーバーから最後に受信したマスクの設定bitに対応するbitのみを設定できる。このためversion_bits & ~last_mask == 0となる
    • サーバーは次のように送信するnVersionを計算する。nVersion = (job_version & ~last_mask) | (version_bits & last_mask)job_versionjob_idと共にジョブの一部としてマイナーに送信されたブロックバージョン。

拡張「minimum-difficulty」

この拡張により、マイナーは接続されたマシンに最小難易度の要求をすることができる。これにより、接続されたデバイスのハードリミットを伝える方法がないオリジナルのStratumプロトコルの問題を解決する。

拡張パラメータ
  • minimum-difficulty.value(必須、Integer/Float, >= 0)
    マイナー/接続に許容される最小難易度の値。機能を無効にするには0をセットする。
拡張戻り値
  • minimum-difficulty(必須、TExtensionResult)
    • 最小難易度が受け入れられたかどうか
    • この拡張はminimum-difficultyコードを持つmining.configureを再度呼び出して複数回設定することができる。

拡張「subscribe-extranonce」

パラメータの無い拡張。マイナーはmining.set_extranonceメッセージを受信することができることを広告する(ハッシュレートルーティングシナリオに役立つ)。

拡張「info」

マイナーはテキストベースの追加情報を提供する。

拡張パラメータ
  • info.connection-url(オプション、文字列)
    マイニングソフトウェアがstratumサーバに接続するために使用する正確なURL。
  • info.hw-version(オプション、文字列)
    製造元固有のハードウェアバージョン文字列
  • info.sw-version(オプション、文字列)
    製造元固有のソフトウェアバージョン文字列
  • info.hw-id(オプション、文字列)
    マイニングデバイスの一意の識別子

互換性

現在、さまざまなプロトコル拡張を目的とした同様のプロトコル機能mining.capabilitiesが存在する。しかし、mining.configureは、全ての受け入れらた/調整中の拡張を確認するサーバーの応答を必要とするため、この機能と互換性がない。我々がこれを非互換にしたのは、mining.capabilitiesの要求には関連する応答がないためだ。

ブロックヘッダのバージョンについてソフトフォークのデプロイ以外の汎用利用を提案するBIP-320

Bitcoinのブロックヘッダにはバージョンを表すnVersionフィールドが存在する。現在このnVersionフィールドは、コンセンサスに影響するような新しい機能をソフトフォークで導入する際に、ソフトフォークの準備ができたことを各マイナーがシグナリングするのに使われている。(と言っても、Segwit以降新たなソフトフォークの計画はまだ発表されてないけど)

BIP-9で定義されたこの仕様は、nVersionフィールドをビット列として解釈し、ソフトフォークの対応状況をその各bitを使ってシグナリングしている。詳細な仕様ついては↓参照。

techmedia-think.hatenablog.com

BIP-9のversion bitsによるシグナリングにより、異なるソフトフォークを29個同時にデプロイできるようになっているが、実際にそんな数のソフトフォークを並行デプロイするようなことはこれまで無かったし、今後も考えにくい。

そこで、このブロックヘッダのnVersionフィールドをソフトフォークのデプロイ以外にも使用できるようにしようというのがBtcDrakによって提案されたBIP-320↓

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

簡単に説明すると、ブロックヘッダのnVersionフィールドは4バイト(32 bit)あるが、そのうち16 bitを汎用的な目的のために確保し、BIP-9のversion bitsの解釈から取り除こうという提案。16 bitを任意に利用できるようにすることで、マイニング効率の向上などに繋げようと。

一時期話題にあがったAsicBoostだが、covert型とovert型の内、Segwitのデプロイによりcovert型は実質不可能になったが(Bitcoin Cashでは可能)、overt型の方はバージョンローリングをする方式なので、ちょうどこのBIPの内容に当てはまる。AsicBoostの特許は、その所有者がブロックチェーンの防衛特許ライセンス↓

bitcoinmagazine.com

に加入したことで、BDPLに参加するメンバーはその特許が利用可能になっていることから、そのあたりの絡みもあっての提案なのかもね。まぁ実際に現段階でそういう使い方をされていることもあるので、適用には問題ないと思う(あと、そもそも29個並行デプロイとかないし)。

以下、BIP-320の定義内容。

動機

マイナーはnVersionフィールドのいくつかのbitを、様々な用途に使用したいと考えている。しかし現在は、マイナーによってアクティベートされるソフトフォークの調整に使用しているため、これらのbitをソフトフォーク以外のシグナリング目的で使用すると、フルノードが未知のソフトフォークだと認識して誤った警告を生成する。nVersionフィールドのbitを汎用的な使用のために確保することで、ノードソフトウェアがそれらのbitを無視するようにアップグレードすることができ、誤った警告を生成することもなくなる。汎用的な使用のために16 bitを確保しても、まだ13個のソフトフォークの並行デプロイが可能であり十分だ。

使用例

以下はnVersionフィールドの一部のbitを使用できるようにすることで得られる利益の一例だ。このリストは網羅的ではない。

Bitcoinのマイニングハードウェアは現在200ms未満で32 bitのnonceフィールドを使い切る。このためコントローラは多くの帯域幅とCPU時間を消費して新しいジョブを非常に頻繁に各マイニングチップに配布する必要がある。これはより多くのbitをローリングすることで大幅に削減できる。同じくブロックヘッダにあるnTimeの多くのbitをローリングするという方法もあるが、タイムスタンプをより長い時間に渡って歪める可能性があるので理想的ではない。

バージョンローリングをするAsicBoostでは、4方向の衝突を計算するのにnVersionフィールドから2 bit必要だ。任意の2 bitが使用できると、マイニング装置はStratumの「version-rolling」拡張を使ってどのbitがマイニングプールで使用されるか交渉することができる。

仕様

13から始まり28(0x1fffe000を含む)で終わるブロックヘッダのnVersionフィールドの16 bitを汎用的な使用のために確保され、BIP-8およびBIP-9の仕様からは削除される。nVersion bitに0xe0001fffのマスクを適用し、ソフトフォークのシグナリングおよびソフトフォークの警告に対してbit 13〜28を無視する。

参照実装

github.com

後方互換

アップグレードされていないノードは、この提案で確保されるbitをソフトフォークのシグナリングとして解釈し、さらに未知のソフトフォークの警告システムをアクティブにすることがある。

この提案を実装するのにソフトフォークは必要としない。

これが記載されている時点で、このBIPで確保しようとしている16 bitのいずれも既知のソフトフォークで確保されてはおらず、ハッシュレートの少なくない割合がすでにそれらのbitを使用していることを考慮すると、将来のソフトフォークでアクティベーションシグナリングにこれらのbitを使用すべきではない。

SchnorrベースのScriptlessな「Multi-Hop Locks」の実現方法

techmedia-think.hatenablog.com

techmedia-think.hatenablog.com

と書いたので、残ったSchnorrベースのMulti-Hop Locksについても書いておく。
(Multi-Hop Locksのコンセプトについては最初の記事参照)

SchnorrベースのMulti-Hop Locksは署名アルゴリズム自体がSchnorrとECDSAで違うだけで、基本的な構成はECDSAの時と同じだ。

鍵生成フェーズ

各ユーザー {U_i}が持つ秘密鍵 {x_i}とし、対応する公開鍵を {P_i = x_iG}とする。

決済の経路間のユーザー=ペイメントチャネルを直接開いているユーザーは、相手と鍵生成プロトコルを使用して二者間の公開鍵を計算する。例えば {Ui, U_{i+1}}は、それぞれ公開鍵 {P_i, P_{i+1}}を持っているが、Schnorrの公開鍵の集約特性を利用し、両者の公開鍵を加算した共有鍵 {P = P_i + P_{i+1}}を算出する。

通常、Schnorr署名では上記の共有鍵Pにロックされたコインは、

  •  {U_i} {s_i = k_i + H(P, R, m)x_i}
  •  {U_{i+1}} {s_{i+1} = k_{i+1} + H(P, R, m)x_{i+1}}

をそれぞれ計算し、 {s_i + s_{i+1}}を計算した {(R, s)}がPに対する有効な署名となる。

送信者から中間者、受信者の3人の場合、それぞれ {x_0, x_1, x_2}秘密鍵を持っている場合、

  •  {U_0}(送信者)と {U_1}(中間者)の共有鍵は {P_0 = x_0G + x_1G}
  •  {U_1}(中間者)と {U_2}(受信者)の共有鍵は {P_1 = x_1G + x_2G}

となる。

セットアップフェーズ

  1. LNで支払いを行う送信者は受信者までの数分(n)、ランダムに値をサンプリング( {y_0, ...., y_n})する。
  2. 続いて1〜nについて {Y_i = Y_{i-1} + y_iG}を計算する。なお、 {Y_0} = y_0G
  3. 送信者は受信者までの各ユーザーに対して3つのアイテム {(Y_{i-1}, Y_i, y_i)}を送信する。送信者から中間者、受信者の3人の場合、それぞれに送られるデータは
    •  {U_0}(送信者)には {(n/a, Y_0, y_0)}
    •  {U_1}(中間者)には {(Y_0, Y_1, y_1)}
    •  {U_2}(受信者)には {(Y_1, Y_2, y_2)}
  4. 最後に受信者にのみ追加で、全てのサンプリング値を加算した( {y_0 + y_1 + y_2})オープン鍵を送る。

この部分は、ECDSAと全く同じ。

ロックフェーズ

ECDSAとは署名アルゴリズムが異なるので署名の構成方法は異なるが、基本的には {Y_i}をRに含めることで、Rの構成にシークレットを含めるという仕掛けは変わらない。

ロックフェーズは {U_i, U_{i+1}}間で始まる。まず両者はECDSA署名に必要なランダムなnonceについて合意する。それぞれランダムに {r_i, r_{i+1}}を選択し、それぞれ {r_i G} {r_{i+1}G}を計算する。それぞれ計算した点を明らかにし、自分のnonceと組み合わせて共通の {R_{i} = r_iG + r_{i+1}G + Y_i}を計算する。

ここで {Y_i}を加算しているのがポイントだ。

ECDSAと違ってSchnorr署名は署名アルゴリズム自体が署名の集約をサポートしているため、まず以下のように {Y_i}がなかった場合に有効な署名を計算する。

  •  {U_i} {s_i = r_i + H(P, R, m)x_i}を計算
  •  {U_{i+1}} {s_{i+1} = r_{i+1} + H(P, R, m)x_{i+1}}を計算

 {s_i + s_{i+1}}を計算すると、

 {s' = r_i + r_{i+1} + H(P, R, m)(x_i + x_{i+1})}

になるが、このままでは {Y_i}の離散対数の情報がないので、 {R_i}に対応した署名としては無効だ。

これを有効な {R_i}に対応した有効な署名にするためには、s'に対して {Y_i}の離散対数 {y_i}を加算する必要がある。

ECDSAの場合と同様各 {Y_i}の離散対数が、受信者→送信者の経路で順にアンロックするトリガーとなる。

送信者から中間者、受信者の3人の場合、送信者から中間者、受信者の3人の場合、それぞれ {r_0,r_1,r_2}のnonceを選択した状態で、それぞれの間で作成されるRと署名データは以下のようになる。

 {U_0}(送信者)と {U_1}(中間者)
 {R_0 = r_0G + r_1G + Y_0}
 {\displaystyle \biggl( R_0, s' = r_0 + r_1 + H(P, R, m)(x_0 + x_1)\biggr)}

 {Y_0 = y_0G}であることから、この署名に対して必要な {Y_0}の離散対数は {y_0}

 {U_1}(中間者)と {U_2}(受信者)
 {R_1 = r_1G + r_2G + Y_1}
 {\displaystyle \biggl( R_1, s' = r_1 + r_2 + H(P, R, m)(x_1 + x_2)\biggl)}

 {Y_1 = Y_0 + y_1G}であることから、この署名に対して必要な {Y_1}の離散対数は {y_0 + y_1}

リリースフェーズ

リリースフェーズでは、ロックフェーズでロックされたコインを入手するのに各チャネルでアンロック条件となっている離散対数を明らかにする。

送信者から中間者、受信者の3人の場合、受信者はオープン鍵 {y_0 + y_1 + y_2} {y_2}を持っているため、中間者からコインを貰う際の署名に使った {Y_1}の離散対数をオープン鍵から {y_2}を引くことで算出できる。この離散対数を中間者に対し明らかにすることで、コインを入手する。

中間者は {y_0 + y_1}の離散対数を知ったので、そこから予め知っている {y_1}の値を引けば、 {Y_0}の離散対数 {y_0}を算出でき、これを使って送信者から資金を入手する署名を完成させることができる。

途中の参加者が離散対数を相手に伝えることがなく、トランザクションをブロードキャストした場合も、ブロードキャストされたトランザクションで使われている署名値sを使ってs - s'を計算することで、自身がコインの入手に必要な離散対数を手に入れることができる。

といった形で、Schnorrの場合もECDSAと同様、署名のR値に受信者→送信者の順にアンロック要素が明らかになる離散対数のロック条件をしのばせることで、Scriptlessな「Multi-Hop Locks」を構成しているのが分かる。