Develop with pleasure!

福岡でCloudとかBlockchainとか。

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の課題であったような資金の消失に繋がることはない。

witプロトコルを利用したBeam Sync

こないだGethに導入された新しい同期方法Snap Syncについて書いたけど↓

techmedia-think.hatenablog.com

今回は、Beam Syncという同期方法↓

https://github.com/ethereum/stateless-ethereum-specs/blob/master/beam-sync-phase0.md

を調べてみる。

Beam Syncの同期方法

Beam SyncもSnap Syncと同様、スタンドアロンの同期プロトコルではなく、既存のethプロトコルと一緒に動作する同期方法になる。

現在Gethのデフォルトの同期方法であるFast Syncは、現在のブロックのステートをピアからダウンロードすることで、同期速度を向上させるアプローチを採っている。ただ、それでも同期には結構時間がかかり、ノードとして機能できるまで待つ必要がある。

これに対し、Beam Syncは、最新のブロックを選択し、そのブロックを実行しながらそのブロックで必要とされるステートをピアからオンデマンドに取得することで、クライアントが初期起動後すぐに利用可能にできるようにしようというアプローチを採っている。

https://github.com/ethereum/stateless-ethereum-specs/raw/master/assets/beam-sync-flow-phase0.png

基本的な処理のフローは:

  1. 先頭ブロックを選択すると、そのブロックの全トランザクションを実行する。
  2. EVM実行中に、必要なステート(アカウントの残高やストレージのデータ、コントラクトのバイトコードなど)を保持していない場合、EVMを一時停止する。
    1. 不足しているステートのトライノードをリモートピアに要求する。
    2. ステートがダウンロードできたら、EVMの実行を再開する。
  3. EVMの実行がすべて終わると、次のブロックを処理する。

もちろん、ブロックで必要とされるステートだけを常にオンデマンドで取得していたのでは、完全なステートツリーを構築することはできないので、バックグラウンドで不足しているステートをピアから随時ダウンロードする。この2つの処理を並行して実行することで、クライアントを素早く利用可能にしようというアプローチだ。完全にステートツリーが同期されると、その後は他の同期モードと同様Full syncに移行する。

Ethereumの研究用クライアントの1つであるTrinityがこのBeam Syncを実装しており、こないだ、新規クライアントを起動後80秒で先頭のブロックの検証を終えたという内容が投稿されていた↓

snakecharmers.ethereum.org

ステート入手の課題

一見すると、シンプルなソリューションに見えるけど、オンデマンドでデータを入手する際のパフォーマンスが重要なポイントになる。

初回起動時には、ステートを1つも持っていないので、その状態でブロックを処理すると、EVM実行中にすぐにアカウントの残高など必要なステートを要求することになる。ただ、そのステートをリモートピアに要求するにしても、アカウントのキーを指定したらそのステートを返ってくるような単純な処理ではない。ethプロトコルで定義されているGetNodeDataメッセージは、Merkle Patricia Trieのツリー上のノードのハッシュを指定してそのトライノードのデータを要求するメッセージで、アカウントであればツリーのリーフノードにその残高等のデータが保持されている。

つまり、アカウントのステートを取得しようとすると、そのツリーのルートノードからリーフノードまでのすべてのトライノードのハッシュの情報が必要になり、これを取得するためにルートノードから順番に子ノードのハッシュを知るためにGetNodeDataを実行していく必要がある。現在のmainnetではアカウントのステートが保持されているのはルートから大体7階層めくらいとされているので、その分のGetNodeData/NodeDataのやりとりが必要になる。

ピボット

オンデマンドでステートを要求しながらブロックの全トランザクションを実行していくと、リモートピアとのRTTなども影響し、ブロックタイムが15秒なので、処理が完了するまでに次のブロックが到着するということが容易に想像できる。また、クライアントによって異なるがGethだと直近128ブロックのステートをインメモリで保持しているが、それを超えたブロックの処理を続けるようなケースになるとリモートピアからステートが取得できなくなり処理がスタックしてしまう。

そのため、チェーンの先頭から離されると、そのブロックの実行を中止し、再度先頭のブロックを選択し、そのブロックの実行するようピボットする必要がある。このピボットを繰り返しながら、ローカルのステートを満たしていくことで、ピボットの機会は減っていく。

Beam Syncを使った実験で、起動後22時間経過しても、1つの新しいブロックを処理するのに中央値で約300の新しいトライノードを必要とする模様。

witプロトコルによる高速化

↑のステート入手を高速化するため(最終的にはステートレスクライアントを支援するため)にdevp2p上に設計されたのがwitプロトコル

https://github.com/ethereum/devp2p/blob/master/caps/wit.md

現状このプロトコルのバージョンは0で、以下のP2Pメッセージが定義されている。

  • GetBlockWitnessHashes(0x01):
    指定されたブロックで使用されるトライノードハッシュのリストを要求するメッセージ。
  • BlockWitnessHashes(0x02):
    指定されたブロックの実行および検証中に読み取られたトライノードのリストを返すメッセージ。

witというのはwitnessから来ており、ステートレスクライアントで必要となるステートをwitnessとして提供するのが目的だと思われるが、↑のメッセージからもわかるように現時点ではwitnessは提供されず、そのメタデータ(ハッシュ)のみを提供するプロトコル仕様になっている。

Beam Syncではこのwitプロトコルを使うことで、ブロック内のトランザクションを実行する際に必要となるトライノードのハッシュを事前にリストで取得できるようになる。この結果、Beam Syncのオンデマンドのステート取得の際に余分なGetNodeData/NodeDataのやりとりをしなくて済むことになり、同期速度を向上させることができる。↑のTrinityの80秒で利用可能にしたというのも、このwitプロトコルを使った結果。

もちろん、この高速化のためには、リモートピアがwitプロトコルに対応している必要がある。ただ、witに限らず、Beam Syncのノードは直近のブロックの実行に必要なステートを保持しているノードでもあるので、Beam Syncノードは互いにデータを提供でき、ネットワークに多くのBeam Syncノードが参加しても、ステートの要求はそれらの間で分散して共有される可能性がある。

というのがBeam Syncの仕組み。フェーズ0〜フェーズ2までの段階があり、witを使った高速化がフェーズ1。フェーズ2はブロックヘッダーにwitness proofを追加するコンセンサスの変更が必要なので、まだ先っぽい。