Develop with pleasure!

福岡でCloudとかBlockchainとか。

Bitcoin Secure Multisig Setup (BSMS)

先日、マルチシグを安全にセットアップするための提案Bitcoin Secure Multisig Setup (BSMS)が、新しくBIP-129として定義された、↓

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

PSBTなどで、ウォレット間で協力してトランザクションを構築する際のデータフォーマットは標準化されたけど、そのプロセスの標準化はされていなかったので、その提案。

BSMSでは、マルチシグで使用するスクリプトや必要な鍵の数、署名の数に、鍵の導出パスが改ざんされることなく参加者間で共有され、第三者に対してその漏洩を防ぐためのプロトコルが定義されている。

コーディネーターと呼ばれるロールが、セットアップの調整を行い、署名者は主にハードウェアウォレットを対象としている。コーディネーターがシークレットトークンを生成・配布し、そこから暗号鍵を導出し、各署名者が署名に用いる鍵情報をAESで暗号化して返送する。コーディネーターはそれらを収集し、マルチシグに使用するdescriptorを生成し、各署名者に配布するというのが大まかな流れ。プロセスをまとめて絵にすると↓な感じ。

f:id:techmedia-think:20210531153400p:plain
BSMSのセットアップフロー

プロトコルの詳細は↓のBIP訳参照。

ただ、これを利用するためには、マルチシグ参加者のすべてのウォレットがBSMSに対応していないといけないので、それが利用までの一番のハードルになりそう。

イントロダクション

概要

このドキュメントは、マルチシグウォレットを安全にセットアップする仕組みを提案する。

Copyright

このBIPは2-clause BSDライセンス

動機

Bitcoinのマルチシグのエクスペリエンスは、BIP-0174(Partially Signed Bitcoin Transaction)で、大幅に合理化された。しかし、異なるベンダー間でマルチシグウォレットを安全にセットアップするための標準的なプロセスはまだない。

マルチシグウォレットをセットアップする際には、いくつかの懸念事項がある:

  1. マルチシグの設定には、署名者のメンバーシップや、スクリプトの種類、必要な署名の数や鍵の導出パスが求められ、それが正しく、改ざんされていないかどうか。
  2. セットアップ中に鍵やマルチシグの設定やが漏洩していないかどうか。
  3. 署名者がマルチシグの設定をそれぞれのストレージに保存しているか、またどんなフォーマットで保存しているか。
  4. 署名者のストレージが改ざん防止されているかどうか。
  5. 署名者がその後マルチシグの設定を使って、受信アドレスやお釣り用のアドレスを生成および検証しているかどうか。

マルチシグの設定を改ざんできる攻撃者は、ユーザーを騙して間違ったアドレスに資金を送信させることで、資金を盗んだり身代金を要求することができる。設定は改ざんできないが、鍵や設定について知ることができる攻撃者は、ウォレット内のトランザクションを関しすることができ、その結果プライバシーが失われる。

この提案は、上記の1, 2, 3の懸念を解決するために初期設定段階での改ざんリスクを軽減し、相互運用可能なマルチシグの設定フォーマットを定義することを目的としている。

懸念事項4, 5については署名者が処理すべきであり、本提案のスコープ外である。

仕様

前提条件

この提案は、マルチシグの参加者がBIP-0032およびBIP-0322、descriptor言語とAES暗号化をサポートしていることを前提とする。

ファイルの拡張子

すべてのdescriptorおよび鍵レコードのファイル拡張子は.bsmsとする。暗号化されたデータは.dat拡張子を付ける。

ロール

コーディネーター

コーディネーターがマルチシグのセットアップを開始する。コーディネーターはどんな種類のマルチシグを使用するか、また正確なポリシースクリプトを決定する。暗号化が必要な場合は、コーディネーターは安全な通信のために、関係者に共有シークレットを配布する。コーディネーターは、署名者から情報を収集し、descriptorレコードを生成する。コーディネーターはdescriptorrレコードを署名者に配布する。

署名者

署名者は、秘密鍵を管理し、その鍵を使って署名できるソフトウェアもしくはハードウェアである。署名者はマルチシグの参加者で、公開鍵や拡張公開鍵(XPUB)を含む鍵レコードをコーディネーターに提供し、その鍵がdescriptorレコードに含まれていることを検証し、descriptorレコードをストレージに保存する責任がある。

セットアップ手順

ラウンド1

コーディネーター

  • コーディネーターは新しいマルチシグウォレット作成セッションを作成する。コーディネーターはmultisigスクリプトと、必要な署名数や署名者の総数(MおよびN)などのポリシーパラメータを作成する。
  • セッションは、コーディネーターが決めた時間(例えば24時間など)を超えると失効する。このタイムアウトにより暗号鍵のエントロピーが小さくなる。
  • 暗号化が必要な場合、コーディネーターは秘密のTOKENを安全なチャネルを介して各署名者に配布する。署名者はそのTOKENを使ってENCRYPTION_KEYを導出することができる。TOKENおよび鍵導出関数と暗号化方式の詳細については、以下の暗号化セクションを参照。コーディネーターは、ユースケースに応じて、すべての署名者の共通のTOKENを使用するか、署名者毎にTOKENを使用するか決定することができる。
  • 暗号化が必要ない場合、TOKEN0x00に設定され、以下のすべての暗号化/復号化の手順はスキップされる。

署名者

  • 署名者は、TOKENを設定することでマルチシグウォレット作成セッションを開始する。署名者はTOKENからENCRYPTION_KEYを導出する。異なるTOKENの値が設定されるまでセッションを継続できる。
  • 署名者は、ユーザーにマルチシグの導出パスの入力を求め、導出パスのKEYを取得し、鍵レコードを生成する。また署名者がユーザーに代わってパスを選択することも可能。署名者がパスを選択する場合は、異なるウォレット間でKEYを使い回さないようにすべき。
  • レコードの最初の行は仕様のバージョンでなければならない(これを書いている時点ではBSMS 1.0)。2行目には、hexエンコードされたTOKENで、3行目はKEYでなければならない。KEYは公開鍵もしくはXPUBにkey origin情報を加えたもので、descriptorで定義されたフォーマット、例えば[{master key fingerprint}/{derivation path}]{KEY}で記述される。4行目は、鍵の説明のテキストで、最大80文字。5行目はSIGでなければならず、このSIGは公開鍵もしくはXPUBの関連する秘密鍵を使って、最初の4行に署名した署名データである。署名はBIP-0322に運居し、レガシーフォーマットも受け入れられる。
  • 署名者は、レコードのメッセージ認証コード(MAC)を計算する。MACの先頭16バイトは、暗号化のための初期ベクトル(IV)として機能する。
  • 署名者は、ENCRYPTION_KEYIVを使って鍵レコードを暗号化する。
  • 署名者は、MACと暗号文を16進フォーマットでエンコードし、その結果を結合す:(MAC || ciphertext)
ラウンド2

コーディネーター

  • コーディネーターは、すべての参加署名者から鍵レコードを集める。
  • コーディネーターは、ウォレットセットアップセッションが失効する前に、正確にN個の一意な鍵レコードがあることを確認する。
  • 各鍵レコードについて、コーディネーターはデータからMACを取り出し、MACの先頭16バイトをIVにセットして、ENCRYPTION_KEYIVを使って暗号文を復号する。
  • コーディネーターは、得られた平文からMACが正しいことを検証する。
  • コーディネーターは、鍵レコードが互換性のある仕様バージョンであることを確認する。
  • コーディネーターは、含まれるSIGKEYに対して有効か検証する。
  • すべての鍵レコードに問題がなければ、コーディネーターは、必要な情報をすべて使って、descriptorレコードを生成する。
  • descriptorレコードの最初の行は、仕様のバージョンでなければならない(これを書いている時点ではBSMS 1.0)。2行目は、descriptorもしくはdescriptorテンプレートでなければならない。3行目はカンマ区切りの導出パス制限のリストでなければならない。パスは必ず/から始まり、非強化導出でなければならない。テンプレートや制限がない場合、No path restrictionsと記述しなければならない。4行目はウォレットの最初のアドレスでなければならない。パスの制限がある場合、パス制限を最初のアドレスから使用する。
  • コーディネーターは、レコードのMACを計算する。MACの先頭16バイトは、暗号化のための初期ベクトル(IV)として機能する。
  • コーディネーターは、ENCRYPTION_KEYIVを使ってdescriptorレコードを暗号化する。
  • コーディネーターは、MACと暗号文を16進フォーマットでエンコードし、その結果を結合す:(MAC || ciphertext)
  • コーディネーターは、暗号化されたdescriptorレコードを参加中のすべての署名者に送信する。

署名者

  • 署名者は、descriptorレコードをインポートする。
  • 署名者は、データからMACを取り出し、MACの先頭16バイトをIVにセットして、(セッション開始時の)ENCRYPTION_KEYIVを使って暗号文を復号する。
  • 署名者は、得られた平文からMACが正しいことを検証する。
  • 署名者は、descriptorレコードが互換性のある仕様バージョンであることを確認する。
  • 署名者は、そのdescriptorもしくはdescriptorテンプレートをサポートできるか検証する。
  • 署名者は、提供されたパスとfingerprintの情報を使って、descriptorもしくはdescriptorテンプレートにKEYが含まれていることを確認する。チェックではKEYが正確に一致するか検証し、偽装が容易なfingerprintを一致させるようなショートカットを使ってはならない。
  • 署名者は、導出パスの制限と互換性があることを検証する。
  • 署名者は、ウォレットの最初のアドレスが有効であることを検証する。
  • 確認のため、署名者はユーザーに、ウォレットの最初のアドレスと導出パスの制限、MN、ポリシースクリプト内で署名者が所有するKEYの位置などのポリシーパラメータを表示しなえればならない。署名者の総数Nは、KEY挿入攻撃を防ぐのに重要である。位置は、KEYの順序が重要なスクリプトにとて重要で、該当する場合、KEYのすべての位置を表示しなければならない。完全なdescriptorもしくはdescriptorテンプレートは、ユーザーの要求に応じて確認できなければならない。
  • 参加者は、すべての署名者が同じ確認事項(KEYの位置を除く)を持っていることを確認しなければならない。
  • すべてのチェックをパスした場合、署名者はそのdescriptorレコードを保存しなければならない。

これでセットアップは完了。

暗号化

トーク

3つの暗号化モードを定義する。

  1. NO_ENCRYPTIONTOKENには0x00をセットする。暗号化は無効。
  2. STANDARDTOKENは64ビットのnonce。
  3. EXTENDEDTOKENは128ビットのnonce。

TOKENは次のいずれかのフォーマットに変換できる:

データフォーマットの柔軟性により、各署名者はそれぞれの機能に基づいてUXをカスタマイズできる。

鍵の導出

鍵導出関数はPBKDF2で、PRF = SHA512。具体的には:

DKey = PBKDF2(PRF, Password, Salt, c, dkLen)

ここで:

  • PRF = SHA512
  • Password = "No SPOF"
  • Salt = TOKEN
  • c = 2048
  • dkLen = 256
  • DKEY = 導出されたENCRYPTION_KEY
暗号化方式

暗号化方式は、AES-256-CTR

MAC = HMAC-SHA256(HMAC_Key, hexエンコードされた TOKEN || Data)

IV = MACの先頭16バイト

暗号文 = AES-256-CTR-Encrypt(平文, DKey, IV)

平文 = AES-256-CTR-Decrypt(暗号文, DKey, IV)

ここで:

  • DKey = ENCRYPTION_KEY
  • HMAC_Key = SHA256(ENCRYPTION_KEY)
  • Data = 平文。例:ラウンド1では鍵レコード全体、ラウンド2ではdescriptorレコード全体

MACは、上記のように鍵レコードおよびdescriptorレコードと一緒に送信される。これは平文全体に対するMACであるため、基本的に認証付き暗号のEncrypt-and-MACフォーマットである。

descriptorテンプレート

output descriptor言語は、1次元のリストのみをサポートする。この提案では、多次元リストを表すdescriptorテンプレートを導入している。

XPUB/**

/**は、導出パスの制限いくつでも置き換えることができる。

descriptorテンプレートには導出パスの制限を伴う必要がある。署名者は/**を制限に置き換えて、テンプレートを具体的なdescriptorに拡張する必要がある。

例えば、以下のテンプレートと導出パス制限がある場合:

  • wsh(sortedmulti(2,XPUB1/**,XPUB2/**))
  • /0/*,/1/*

以下のような2つの具体的なdescriptorに変換する必要がある:

  • wsh(sortedmulti(2,XPUB1/0/*,XPUB2/0/*))
  • wsh(sortedmulti(2,XPUB1/1/*,XPUB2/1/*))

QRコード

QRコードを使ってデータを送信する署名者の場合、BCR標準に従って鍵レコードとdescriptorレコードをQRコードに変換できる。

詳細は、BIP44アカウントのURタイプ定義およびBitcoin Output DescriptorのURタイプ定義を参照。

互換性

この仕様は、既存のマルチシグ実装との後方互換性はない。

BSMSはオプトインで、つまり既存のマルチシグ実装はそのまま継続して使うことができるが、さまざまな落とし穴がある可能性がある。既存のソリューションの問題のいくつかは動機セクションで説明されている。

この標準に準拠するには、署名者はdescriptorレコードをストレージに永続化できる必要がある。

マルチシグウォレットにBSMSを使用するためには、マルチシグに参加しているすべての参加者がBSMSを実装するまで待つ必要がある。

安全性

この提案では2つの保護レイヤーを導入している。1つめは、一時的なシークレットTOKENで、もう1つはウォレットの最初のアドレスを確認すること。

TOKENは、署名者とコーディネーター間の2階の通信を暗号化するのに使われる。また、交換したデータを認証するために、TOKENと平文からMACが生成される。TOKENはセットアップフェーズでのみ必要で、その後は安全に破棄することができる。同じTOKENを複数のウォレット作成セッションで使うのは推奨されない。

一方、ウォレットの最初のアドレスは、マルチシグ設定の安全性を確認するために使用することができる。マルチシグの設定を改ざんした攻撃者は、ウォレットの最初のアドレスも変更しなければならない。参加者は、改ざんの可能性を減らすために、すべての署名者が同じアドレスとポリシーパラメータを確認していることを互いに確認しなければならない。

プライバシー

暗号化は、平文での鍵とdescriptorの共有を回避することで、ウォレットのプライバシーを向上させるのに役立つ。

参加者がより強いプライバシーを望む場合、ビット数の大きいTOKENを使用し、マルチシグのセットアップ完了後TOKENの知識を完全に消去することを推奨する。

Test Vector

BIP参照。

Berlinアップグレード前に存在した脅威の開示

Ethereum Foundation Blogで公開された、先月アップグレードされたBerlin以前に存在した脅威に関する開示記事↓

blog.ethereum.org

脅威とは?

↑の記事で取り上げられている脅威というは、処理に時間のかかるトランザクションを作成することでDoS攻撃を可能にするというもの。似たような攻撃として、上海アタックと呼ばれる攻撃が実際に2016年に行われ、gethが一時的に使えなくなるといった事態が発生した。

DoS攻撃を可能にするのは、Ethereumで成長中のステートツリーを利用したもの。このツリーはMerkle Patricia Trieと呼ばれるデータ構造で管理されている。この仕組みやボトルネックについては以前書いた↓参照。

techmedia-think.hatenablog.com

2019年に公開されたBroken Metreという論文では、遺伝的アルゴリズムを使って、処理に時間のかかるトランザクションを発見しようとする研究をしている。この遺伝的アルゴリズムで作成されたコントラクトを実行して分析したところ、アルゴリズムはEVMのopcodeのうちGASBALANCEの組み合わせに絞って時間のかかるトランザクションを作成していることが判明した。

これらの研究を基に、調査されたDoSトランザクションに関する開示が↓

hackmd.io

DoS攻撃用に考えられたエクスプロイトは、巨大なEthereumのステートツリーに対して存在しないアカウントのデータを照会するopcodeを繰り返し呼び出すという方法で、以下のようなopcodeの呼び出しで実現できる:

JUMPDEST:(ジャンプ先としてマーク)
GAS:(利用可能なgas量をスタックにプッシュ)
BALANCE:(指定したアカウントの残高を取得)
POP:(スタック要素をポップ)
PUSH:(ジャンプ先をプッシュ)
JUMP:(ジャンプ)

GASで得られた適当な値をアドレスとして、その残高を取得するという操作をgasが尽きるまで繰り返すというもの。BALANCE opcodeの部分は、他のEXTCODEHASHEXTCODESIZEでもいい。

このようなアカウントをMerkle Patricia Trie内で検索する場合、ステートツリーのルートから最後の目的のリーフまで辿り着くのに8〜9個のハッシュのDBに対するルックアップ操作が行われる。またさらにLevelDBの階層に伴うオーバーヘッドも発生する。

1,000万gas分↑を実行するトランザクションを実行すると、400 gasのEXTCODEHASHを使った場合で、

  • Parityが90秒
  • gethが70秒

かかるという結果が報告されてる。現状のEthereumのmainnetだとgas limitは1,500万なのでさらに時間がかかることになる。ブロックの生成間隔が15秒であることを考えると、このような処理時間のトランザクションが発生すると上海アタックの時と同様ネットワークは動作しなくなる。

これが今回報告された脅威の内容。

Berlinのアップグレード後は安全なのか?

↑のような攻撃を回避するための方法として、導入されたのが↓の2つ。

EIP-2929

プロトコルレベルの改修としてEIP-2929で、BALANCEEXTCODEHASHEXTCODESIZEを含むステートへアクセスするopcodeのgasコストの引き上げる。EIPの詳細については、id:y_nakajo のGBEC動画参照↓

goblockchain.network

スナップショットの導入

Geth v1.10から導入されたスナップショット構造↓

techmedia-think.hatenablog.com

スナップショットは新しい同期モードSnap syncを可能にするために利用されるが、同期以外にも、Merkle Patricia Trieからステートを読み取るとディスク読み込みのコストが↑のように発生するけど、スナップショットに対してステートを読み取る場合、O(1)で直接アクセスできるようになる。これによりステートへのアクセスコストを劇的に削減できる。

Berlinのアップグレード後、現在のmainnetの上限1500万gasでは、スナップショットを使わない場合に2.5〜3秒かかるブロックを作成することができると推定されている。これは今後のステートの成長と伴にさらに長くなっていく。ただ、スナップショットを利用するとさらに高速に処理することもでき、現時点で↑のエクスプロイトが深刻な脅威になることはひとまずなくなったことから、今回このような開示がされたみたい。

gas代の値上げによる手数料の増加はユーザーにとっては負担増になるけど、こういった攻撃が実行されるとチェーンそのものが停止してしまうというリスクがあるので、この辺りのバランスを調整していくのがどのチェーンでも大変な部分かもね。

SafegcdによるECDSA署名検証の高速化

2019年に発表された拡張ユークリッド互除法の新しいアルゴリズムを使った、ECDSA署名の生成/検証の高速化について↓

medium.com

libsecp256k1にもマージされたので、どういった内容か見てみる。

ユークリッド互除法

まず基本的なユークリッド互除法について。ユークリッド互除法は、2つの自然数a, bについて、その最大公約数を求める方法の1つ*1。これは、aをbで割った商を {q_1}、余りを {r_1}とした場合、

 {a ÷ b = q_1 ... r_1}

さらに、

 {b ÷ r_1 = q_2 ... r_2}

とし、これを余り {r_n}が0になるまで続けた場合、 {r_{n-1}}が最大公約数になるというアルゴリズム。よくgcd(a, b)と表記される。

拡張ユークリッド互除法

そして、拡張ユークリッド互除法は、2つの自然数a, bについて、ベズーの等式とも呼ばれる {ax + by = gcd(a, b)}を満たす整数解(x, y)を求める際に使われるアルゴリズム

この一次不定方程式は、↑のユークリッド互除法から、a ÷ bの商を {q}、余りを {r}とすると、 {a = qb + r}となるため、

 {(qb + r)x + by = gcd(a, b)}

とすることができ、さらに

 {b(q + y) + r x = gcd(a, b)}

に変形できる。これはgcd(a, b)をより小さい値のgcd(b, r)にしている。そしてこれを再帰的に繰り返す↓

 {x_1 = q + y, y_1 = y }として、 {bx_1 + ry_1 = gcd(a, b)}に対して、 {b = q_1r + r_1}から

 {(q_1r + r_1)x_1 + ry_1 = r(q_1x_1 + y_1)  + r_1x = gcd(a, b)}

と、計算の過程で余りの値 {r_n}が0になるまで繰り返す。すると、最後の式には{x_n = 0, y_n = 1}が成立する。この2つの値が分かると、再起を逆に辿っていくと、 {x_{n - 1}, y_{n - 1}}が順次計算でき、最終的に(x, y)の解を求めることができる。

楕円曲線暗号と拡張ユークリッド互除法

BitcoinやEthereumなどの暗号通貨で使用されている楕円曲線暗号において、鍵の導出やデジタル署名の生成でコアとなる計算が、逆元(モジュラ逆数)の計算になる。逆元は、整数aと法をnとした場合に

 {ax ≡ 1(\mod n)}

を満たすようなxを指す。つまり、 {x(\mod n) ≡ a ^{-1}}となる値。

↑の式は、変形すると

 { ax - 1 = qn}

とすることができ(qは適当な整数)、結果

 {ax - qn = 1}

と変形でき、↑の拡張ユークリッド互除法の不定方程式に似ているのがわかる。また、計算にあたって実際にaとnは既知の値である。つまり拡張ユークリッド互除法を使えば逆元の計算が可能で、それも高速に。

libsecp256k1の実装

楕円曲線ライブラリlibsecp256k1では、タイミング攻撃により秘密情報のヒントが漏れないよう定数時間の計算アルゴリズムを採用している(タイミング攻撃というのは計算の処理時間の差を分析することで、鍵の情報などを推測するサイドチャネル攻撃の一種)。このような攻撃により、秘密鍵に関する情報が漏れないように、入力されたデータに関係なく一定の時間で処理を行うアルゴリズムを定数時間アルゴリズムと言う。

拡張ユークリッド互除法を使った逆元の計算は高速だが、↑のように値によって計算回数が変わるため定数時間アルゴリズムにするのが難しいという課題があり、libsecp256k1ではBitcoinで使用する際は、それよりも遅いexponentiation ladderと呼ばれるアルゴリズムが使用されてきた。

Safegcd

しかし2019年に、定数時間で高速なユークリッド互除法および逆元の計算を行うSafegcdと呼ばれる新しいユークリッド互助法のアルゴリズムが発表された↓

Fast constant-time gcd and modular inversion

Safegcdでは、divstepと呼ばれる一定回数繰り返すと入力のgcdやモジュラ逆数が明らかになるアルゴリズムを使用している。libsecp256k1のSafegcdの実装ガイドから、この計算方法をハイレベルに記述したのが↓の、fを奇数の整数、gを任意の整数とした場合の最大公約数を求める計算(ruby版)

def gcd(f, g)
  delta = 1
  until g == 0
    if delta > 0 && g.odd?
      delta, f, g = 1 - delta, g, (g - f) >> 1
    elsif g.odd?
      delta, f, g = 1 + delta, f, (g + f) >> 1
    else
      delta, f, g = 1 + delta, f, (g    ) >> 1
    end
  end
  f.abs
end

再帰ではなく単純なループになっており、このループの各反復をdivstepと呼んでいる。各反復で行っているのは、

  • gが奇数なら、(f, g)を(g, g - f)もしくは、(g, g + f)に置き換えて、gを偶数にする。
  • gが偶数なら(f, g)を(f, g/2)に置き換える。

というもの。

このコードはg == 0になると計算が終わるが、g == 0になってもfの値はそれ以上変更されなくなるので、その後反復を続けても出力値は変わらない。そのため定数時間にする場合、終了条件をuntil g == 0ではなく、一定値にすれば良い。このアルゴリズムの場合、反復回数の上限=実行時間になる。

ただ、必要な反復計算の回数(上限)を決定する必要がある。libsecp256k1の場合、入力の数値は256 bitなので、その範囲の数値の拡張ユークリッド互助法の計算をするのに十分な反復回数はいくつだろう?その問に対して、Pieter WuilleがSafegcdの必要な反復回数の境界を算出する方法を提案し↓

github.com

さらに、この方法を使ってSafegcdの変形について調査すると、最大590回で反復を終えられる変形が発見された。これを適用すると、既存のlibsecp256k1の実装に比べて

  • ECDSAの署名生成速度が25%向上
  • ECDSAの署名検証速度が15%向上

すると。

そして重要なポイントとして、もしこの上限値以内に計算が終わらないケースが発生すると、誤った逆元の計算が行われてしまう。そこで、この境界の算出方法を正しさを検証するためにCoqを使った定理証明が行われている↓

Coq proof of correctness of divstep and hddivsteps for 256-bit inputs. by roconnor-blockstream · Pull Request #7 · sipa/safegcd-bounds · GitHub

*1:他にも素因数分解などでも解けるが、a, bの値が大きくなるとユークリッド互助法で求めた方が高速

Speedy Trialを使ったTaprootのデプロイ始まる

Taprootのアクティベーション方法について長らく議論されていたけど、Speedy Trialという方式が採用され、BIP-9ベースにSpeedy Trialを実装したBitcoin Core 0.21.1がリリースされた↓

https://bitcoincore.org/en/releases/0.21.1/

デプロイ方法を巡る議論

前回のSegwitのデプロイは、BIP-9を使って行われた。ただ、Segwitのアクティベートまでは長期間かかり、アクティベートのために、Segwitサポートのシグナリングを設定していないブロックをリレーしないUASFや、Segwitサポートのシグナリングを強制することを目的とした新たなシグナリングを要求する2段階のアクティベートなど、すったもんだがあった。

この経験から、確実にアクティベートをするためのBIP-8のようなデプロイ方法も提案された↓

techmedia-think.hatenablog.com

特にLOT = TrueとするかFalseとするかが議論の的になっていたが、他にもいくつかのデプロイ方法が提案される中で、Speedy Trialという方式が注目され、今回採用されるに至った。

Speedy Trialとは?

Speedy Trialというのはソフトフォークのアクティベーションを確実に実行するようなデプロイ方法ではなく、

  • マイナーに対してソフトフォークの適用に3ヶ月間の期間を与える。
  • 3ヶ月以内にマイナーのシグナリングによりソフトフォークがロックインした場合、数ヶ月の猶予期間の後、そのソフトフォークをアクティベートする。
  • 3ヶ月以内にマイナーのシグナリングによりソフトフォークがロックインしなかった場合、別の仕組みでソフトフォークのアクティベーションを目指す。

という方法。

BIP-9を使ったデプロイでは、シグナリングを開始しても、マイナーによるロックインが進まないと、約1年後のタイムアウトまでずっと待っている必要がある(Segwitの場合はタイムアウトの3ヶ月前に↑のようないろんな動きがあってアクティベートしたけど)。それに対して、Speedy Trialの場合は、ダメだった場合に、1年という長い期間を待つのではなく、早めに別のアクティベーション方法に切り替えることができるというメリットがある。

Speedy Trialの実装

Speedy Trialの実装にはBIP-9ベースのものと、BIP-8ベースのものが提案されていたけど、今回Bitcoin Core 0.21.1に取り込まれたのはBIP-9ベースのもの。

BIP-9からの変更点

BIP-9自体はもともと、Speedy Trialのような仕様はないので、正確にはBIP-9のバリエーションという位置づけになる。Speedy Trialを実装するためのBIP-9からの主な変更点は↓

閾値の変更

BIP-9ではリターゲット期間2,016ブロックの95%がソフトフォークサポートの通知をすることで、ソフトフォークがロックインされるが、Speedy Trialでは、この閾値が90%に削減されている。

min_activate_heightパラメータの追加

BIP-9では、ソフトフォークがロックインされると、次のリターゲット期間でアクティベートされるが、Speedy Trialでは、3ヶ月の期間内のリターゲット期間でロックインしたら、数ヶ月の猶予期間後にアクティベートされるため、ロックイン後すぐにアクティベートされないように、min_activation_heightというパラメータが追加されている。そのため、ロックインした場合は、このパラメータのブロック高でアクティベートされる。

MTPとブロック高

アクティベーションで議論になった要素の1つが、パラメータ(スタートタイムやタイムアウト)をMTPで指定するかブロック高で指定するかという点。MTPはMedian Time Pastの略で、時間ベースの指定方法。判定に使用される時刻は、過去11ブロックのブロックのタイムスタンプの中央値が使われる。BIP-9ではMTPが使用され、BIP-8ではブロック高を使用している。MTPはブロックのタイムスタンプから計算されるので、マイナーによる操作が可能なパラメータであるのがデメリットである一方、アクティベーションパラメータを他のネットワーク(Testnetなど)と共有できるというメリットはある。

結果的に、スタートタイムとタイムアウトはいずれもMTPで指定されるが、

  • 実際にシグナルのカウントを開始するのは、スタートタイムのMTPが経過した後の最初のリターゲット期間から
  • カウントを停止するのは、タイムアウトのMTPが経過したリターゲット期間の終了時

と変更されている。

アクティベーションパラメータ

今回のTaprootのアクティベーションパラメータは↓

  • version bit:2
    ブロックの4バイトのversionフィールドのbit 2に1をセットしたらTaprootのサポートの通知になる。
  • スタートタイム:1619222400(2021年4月24 0:00 UTC
    ↑のルールにより、実際にシグナリングが始まるのはスタートタイムを経過した最初のリターゲット期間であるブロック高681408のブロックからで、このブロックがマイニングされたのは2021-05-02 05:27。
  • タイムアウト1628640000(2021年8月11 0:00 UTC
  • min_activation_height:709632(2021年11月上旬〜中旬に作られるであろうブロック709632

ということで、今日からSpeedy Trialを使ったTaprootのシグナリングが始まった。シグナリングの状況は↓のサイトで確認できるっぽい。興味あればブロックのversionフィールドのビット2を確認してみるのも良い。

https://taproot.watch/

Speedy TrialをサポートしたBitcoin Core 0.21.1がリリースされたのも5/2なので、まだシグナルは少ないけど、今後どう変化していくか興味深い。

署名データを利用したコミットメントスキームSign to contract

ブロックチェーンでよく使われる技術要素の1つとしてコミットメントスキームが挙げられる。シンプルなコミットメントスキームのユースケースとしては、OpenTimestampなど。BitcoinであればOP_RETURN opcodeを使ってファイルのハッシュを記録することで、そのファイルの存在証明を提供することができる。

他にもある契約に関する支払いを行う際に、その契約にコミットした支払いであることを表現するのに利用できるPay to contractのようなプロトコルもある↓

techmedia-think.hatenablog.com

Pay to contractでは、契約内容のハッシュ値と受信者の公開鍵から新しい公開鍵を作成し、その公開鍵宛に支払いを行うことで、契約内容にコミットした支払いを表現する。これは楕円曲線の鍵の準同型性を利用したもので、OP_RETURNで記録するアプローチと比較すると、オンチェーン上のフットプリントを増やすくことなくコミットできるのがポイントだ。

BitcoinのBIPでは、Pay to contractベースの支払いのアドレスを決定論的に導出する仕様(BIP-175)も提案されている↓

techmedia-think.hatenablog.com

そんなコミットメントスキームについて、最近Sign to contractというスキームを耳にしたのでどんなスキームなのかみてみよう↓

blog.eternitywall.com

Pay to contractの課題

Pay to contractにより、ブロックチェーンに追加のフットプリントを課すことなく、通常の支払いで任意のデータへのコミットもできるようになるが、状況によっては課題が生じる場合もある。

例えば、受信者の鍵ペアをP = xGとし、コミットメントしたい契約内容のハッシュ値をdとした場合、Pay to contractの受信アドレスQは、以下のように計算される(H()は暗号学的ハッシュ関数、Gは楕円曲線のジェネレータ、||は結合)。

Q = H(P || d)G + P

アドレスQに送られたコインを使用するためには、H(P || d)とPの秘密鍵xの情報が必要になる。

この内、xはHDウォレットなどの仕組みで決定論的に導出することができるため、シードさえバックアップ取っていれば、バックアップから復元することが可能だが、dのデータはそのように決定論的に導出できるものではなく、契約単位に変わるデータになるだろう。そのため、dのデータについては別途バックアップが必要になる。このバックアップが失われるとQにロックされたコインは取り出せなくなってしまう。

Sign to contract

Pay to contractでは、受信アドレスにコミットメントを含めるアプローチであるため、↑のような課題が発生するが、Sign to contractでは受信アドレスではなく署名データにコミットメントを含めるというアプローチで、この課題を解消する。

ECDSAの署名スキーム

鍵ペアP = xG、署名対象のメッセージをm(通常はトランザクションデータ)とした場合、Pとmに対して有効なECDSA署名は、一般的に以下の手順で作成される。

  1. nonce kを選択(xとmを使ってRFC 6979で決定論的に生成される)
  2. R = kGを計算
  3. r = R.x mod nを計算(nは楕円曲線の位数)
  4.  {s = k^{-1} * (m + r * x) \mod n}を計算
  5. (r, s)がECDSA署名データ

Sign to contractの署名スキーム

Sign to contractでは、署名アルゴリズムは同じだが、↑のPublic nonce Rの部分の計算にdへのコミットメントを含める。具体的には、以下のようにPublic nonceの計算を変える↓

  1. nonce kを選択(xとmを使ってRFC 6979で決定論的に生成される)
  2. R = kGを計算
  3. e = k + H(R || d)を計算
  4. Q = R + H(R || d)Gを計算
  5. q = Q.x mod nを計算
  6.  {s = e^{-1} * (m + q * x) \mod n}を計算
  7. (q, s)がECDSA署名データ

Public nonceとして単純にRを使うのではなく、RにH(R || d)Gを加算した値QをPublic nonceとしている。Pay to contractでは受信アドレスに適用していた計算を、署名のPublic nonceに適用したものになる。

単純にdGをそのままPublic nonceとしないのは、dを開示すると、署名データとdから秘密鍵xの値が逆算できてしまうため。そのためR = kGを加算することで、dを開示しても秘密鍵の逆算をできないようにしている。

このように署名データにコミットメントスキームを組み込むことで、たとえコミットメントデータ(d)が失われたとしても、コミットメントが消失するだけで、Pay to contractの課題であったような資金の消失に繋がることはない。