この記事では、Argon2id とは何か、Blocknet がそれをどうプルーフ・オブ・ワークにしているか、なぜ大容量で高速なメモリが有利になるのか、そして seine が動作する CPU ごとにどうやって最大限のハッシュを引き出しているかを説明します。
Argon2id とは何ですか?
Argon2id は、パスワードハッシュとプルーフ・オブ・ワーク向けに RFC 9106 で規定されたメモリハードなハッシュ関数です。大きなメモリ領域を 1 KiB のブロックで埋め、各ブロックを直前のブロックとメモリ内の別の場所から選んだ以前のブロックから作るため、ハッシュの計算にはプロセッサーの時間だけでなく実際のメモリが必要になります。
2021 年に IRTF の Crypto Forum Research Group が公開した RFC 9106 は、「パスワードハッシュとプルーフ・オブ・ワーク用途のための Argon2 メモリハード関数」について記述しています。Argon2 には 3 つの種類があります。
- Argon2d は、すでに計算したデータに基づいて、どの以前のブロックを読み込むかを選びます。RFC はこれを「暗号資産とプルーフ・オブ・ワーク用途に適している」としています。
- Argon2i は、データに依存しない順序でブロックを選ぶため、パスワードハッシュに向いています。
- Argon2id は「メモリに対する最初のパスの前半は Argon2i として、残りは Argon2d として動作」します。これが主要な種類で、RFC はすべての実装がこれをサポートしなければならないと定めています。
内部では、Argon2 はメモリを 1,024 バイトのブロックが長く並んだものとして扱います。新しいブロックはそれぞれ、2 つの入力に圧縮関数 G を適用した出力です。入力は直前のブロックと、メモリ内のより前にある参照ブロックです。G は BLAKE2b ハッシュ関数の混合ラウンドをもとに作られており、64 ビットのワードに対して加算、XOR、ローテートを行います。
Blocknet は Argon2id をどのようにプルーフ・オブ・ワークとして使っていますか?
Blocknet はすべての Argon2id 設定をコンセンサスコードで固定しているので、すべてのマイナーとすべてのノードがまったく同じ関数を計算します。
| パラメーター | Blocknet の値 | 意味 |
|---|---|---|
| 種類 | Argon2id、バージョン 1.3 | RFC 9106 の主要な種類 |
| メモリ | 2 GiB(2,097,152 KiB) | ハッシュごとに 1 KiB のブロックを 2,097,152 個埋める |
| 反復回数 | 1 | メモリに対して 1 パス |
| 並列度 | 1 | レーンが 1 つなので、1 つのハッシュは 1 スレッドで動く |
| 出力 | 32 バイト | ターゲットと比較される値 |
| パスワード | 8 バイトのノンス(リトルエンディアン) | マイナーが試行ごとに変える数値 |
| ソルト | ノンスを除いたブロックヘッダー | ハッシュを 1 つのブロックテンプレートに結び付ける |
マイニングは探索です。マイナーは公開したいブロックヘッダーを用意し、ノンスを選んで Argon2id ハッシュを計算します。32 バイトの結果を数値として読んだときにネットワークのターゲット以下であれば、そのブロックは有効です。そうでなければ、マイナーは次のノンスを試します。
ターゲットは難易度から直接決まり、(2^256 − 1) を難易度で割った値です。そのため難易度は、1 ブロックを見つけるためにネットワーク全体が計算すると見込まれるハッシュ数にほぼ等しくなります。Blocknet は 5 分に 1 ブロックを目標としているので、ネットワークのハッシュレートはおよそ難易度を 300 秒で割った値です。難易度自体は、直近 60 ブロックの加重移動平均でブロック時間に追従します。
マイニングプールも同じ仕組みで、より易しいターゲットを使います。bntpool.com では、seine は接続ごとに設定された低い難易度を満たすシェアを提出し、ネットワークのターゲットも満たすシェアがあれば、それが見つかったブロックになります。
メモリハードなプルーフ・オブ・ワークはなぜ一般的なコンピューターに向いているのですか?
上の設定により、メモリがすべてのハッシュの中心になります。1 パス、1 レーンの場合、パスの後半はデータに依存します。各ブロックの参照先の位置は、直前に計算したブロックの最初の 64 ビットワードから決まります。マイナーはどの以前のブロックが必要になるかを、必要になるその瞬間まで知ることができないため、2 GiB のアリーナ全体をメモリに保持します。
保存するブロックを減らし、足りないブロックを必要に応じて再計算するという方法も考えられます。RFC 9106 はこれを時間とメモリのトレードオフと呼び、「内部の圧縮関数をより多く呼び出すことと引き換えに」メモリを節約するものだとしています。アリーナから削ったブロックは、そのたびに追加の計算で埋め合わせなければなりません。
これが、この設計が一般的なハードウェアに優しい理由です。デスクトップ、ノートパソコン、サーバーには、すでに数ギガバイトの RAM と、それにデータを送るためのメモリコントローラーが搭載されています。ハッシュ計算専用に設計したチップでも、ハッシュ計算コアと並んで数ギガバイトの高速メモリが必要になります。RFC 9106 が最初に推奨する設定は、2 GiB のメモリに対して 1 パスの Argon2id で、Blocknet と同じパス数とメモリサイズです。RFC はこの設定が「専用の総当たりハードウェアに対する攻撃者のコストを最大化する」としています。Blocknet のデーモンのコードでは、この選択を「ASIC に対抗し、公平なマイニングを確保する」ためのメモリハードなプルーフ・オブ・ワークと説明しています。Argon2id は ASIC に対抗するよう設計されており、2 GiB の設定がその設計を生かすことで、人々がすでに持っているコンピューターでマイニングできる状態が保たれます。ステーキングとの比較はプルーフ・オブ・ワークとプルーフ・オブ・ステークの違いで説明しています。
なぜ RAM とメモリ帯域幅がハッシュレートを決めるのですか?
マイニングスレッドはそれぞれ専用の 2 GiB のアリーナを持つため、RAM の容量で動かせるスレッド数が決まります。また、ハッシュは 1 回ごとに 2 GiB を書き込み、ほぼ同じ量をばらばらの場所から読み戻すため、メモリの速度とレイテンシーでスレッドの処理速度が決まります。メモリはコア数と同じくらい重要です。
RAM がスレッド数を決めます。 並列度 1 とは 1 つのハッシュが 1 スレッドで動くことを意味し、各スレッドには専用の 2 GiB のアリーナが丸ごと必要です。16 GB のマシンでは、システム用の余裕を残しておよそ 6〜7 スレッドを動かせます。seine はメモリを測定し、5% を予備として残して、スレッド数を自動で選びます。
帯域幅とレイテンシーが各スレッドの速度を決めます。 1 回のハッシュでは 2,097,152 個の新しいブロックが作られ、そのたびにアリーナ内のどこかから 1 KiB の参照ブロックを読み込みます。ハッシュ 1 回あたり約 2 GiB の書き込みと約 2 GiB の読み込みになる計算です。10 H/s なら、マシンは毎秒およそ 40 GiB のデータをメモリとやり取りします。データに依存する後半では、各読み込みアドレスは直前のブロックが完成して初めてわかるので、メモリが応答するまでの時間が純粋なスループットと同じくらい重要になります。
そのため、seine のパフォーマンスページでは、ハッシュレートは「コア数と同じくらいメモリ帯域幅とレイテンシーに左右される」と書いています。世代ごとの最高の結果は Blocknet マイニングハードウェアガイドで紹介しています。
ヒュージページが効果を発揮するのも同じ理由です。2 GiB のアリーナは通常の 4 KiB ページなら 524,288 ページ、2 MiB ページなら 1,024 ページにまたがります。より大きなページを少なく使えば、ばらばらの読み込みでのアドレス変換ミスが減り、seine の検証用マシンでは約 4〜6% 向上しました。設定方法はヒュージページガイドにあります。
seine の CPU カーネルはどのように Argon2id の性能を最大限に引き出していますか?
カーネルとは、ブロックを計算する内側のループです。seine の x86 ビルドにはスカラー、AVX2、AVX-512 の各カーネルとストリーミング版が含まれ、Arm ビルドには SHA3 の高速パスを備えた NEON が含まれます。それぞれが異なる方法でメモリに律速される処理に取り組みます。
- AVX2 と AVX-512 は、G の BLAKE2b 方式のラウンドを、幅の広いベクトルレジスターで多数の 64 ビットワードに対して同時に実行します。AVX-512 にはネイティブのローテート命令もあり、これらのラウンドで頻繁に使われます。
- プリフェッチは、参照ブロックが必要になる前にメモリへ要求します。パスの前半ではアドレスが事前にわかるので、seine は数ステップ先のブロックを要求します。後半では、現在のブロックの最初のワードが確定した時点で次の参照ブロックを要求し、データが届くまでの間に残りの圧縮処理を進めます。
- ストリーミングは、直近の 64 ブロックを小さなキャッシュ上のリングに保持し、完成したブロックをストリーミングストアで 2 GiB のアリーナへ書き出します。ストリーミングストアはメモリに直接書き込むので、キャッシュはハッシュがまだ必要とするブロックのために空けておけます。seine は AMD EPYC Zen 5 プロセッサーでこれを自動で比較します。
- NEON と SHA3 は、XOR とローテートを 1 ステップで行う Arm SHA3 拡張の XAR 命令を使い、より広い範囲のプリフェッチと組み合わせます。
テストしたハードウェアでは、適切なカーネルを選ぶことで大きな差が出ました。以下は seine のパフォーマンスページから、同じマシンで同じスレッド数を使った比較です。
| マシン | カーネルの変更 | 向上幅 |
|---|---|---|
| AWS Graviton4(Neoverse V2)、16 スレッド | NEON からプリフェッチ付き SHA3 へ | +56%(13.80 から 21.50 H/s) |
| AWS Graviton3(Neoverse V1)、16 スレッド | NEON からプリフェッチ付き SHA3 へ | +45%(12.52 から 18.17 H/s) |
| AMD EPYC 9R45(Zen 5)、24 スレッド | AVX2 からストリーミングへ | +30%(34.47 から 44.74 H/s) |
| Intel Xeon 8375C(Ice Lake)、1〜4 スレッド | AVX2 から AVX-512 へ | +21〜29% |
これらを自分で選ぶ必要はありません。seine はお使いの CPU が対応するカーネルを実際のマイニング処理で比較し、向上が 2 回確認できたときだけ切り替え、最も速かったものを次回の起動のためにキャッシュします。最適なカーネルはスレッド数、ページサイズ、CPU の配置によって変わるため、seine は選択を固定せず、お使いのマシンで測定します。
自分のメモリを活用するには、seine をダウンロードして実行し、アドレスを貼り付けてください。残りの手順はマイニングガイドで説明しています。
よくある質問
Blocknet はプルーフ・オブ・ワークにどんな Argon2id パラメーターを使っていますか?
Blocknet は Argon2id バージョン 1.3 を、メモリ 2 GiB、反復 1 回、並列度 1、出力 32 バイトで実行します。8 バイトのノンスがパスワード、ノンスを除いたブロックヘッダーがソルトです。ハッシュがネットワークの難易度で決まるターゲット以下であれば、そのブロックを獲得します。
Blocknet のマイニングスレッドはなぜ 1 つにつき 2 GB の RAM が必要なのですか?
Argon2id のハッシュは 1 回ごとに 1 KiB ブロックからなる 2 GiB のアリーナを埋め、そのパスの後半では、新しいブロックごとにマイナーが予測できないデータで選ばれた以前のブロックを読み込みます。スレッドはアリーナ全体をメモリに保持しておく必要があるため、動かすスレッド 1 つにつき約 2 GB の RAM が必要です。
Argon2id はなぜ ASIC に対抗するよう設計されているのですか?
Argon2id は、すべてのハッシュでメモリが最もコストのかかる部分になるようにしています。Blocknet のハッシュは 1 回ごとに 2 GiB の高速メモリを必要とし、そこに数ギガバイトのデータを流すため、ハッシュ計算専用に作ったチップでもコアごとに大量の RAM が必要になります。デーモンのコードでは、この選択を ASIC に対抗し公平なマイニングを確保するためのメモリハードなプルーフ・オブ・ワークと説明しています。
グラフィックカードで Blocknet の Argon2id をマイニングできますか?
はい。Windows と Linux では、seine はマシン内のすべての NVIDIA グラフィックカードで CPU と同時にマイニングし、ハッシュレートが合算されます。カーネルは起動時にコンパイルされ、初回起動時に各カードを調整して結果をキャッシュするので、始める前に設定することは何もありません。