Argon2id 工作量證明詳解

· 7 分鐘閱讀

本文介紹 Argon2id 是什麼、Blocknet 如何把它用作工作量證明、它為什麼偏好大容量高速記憶體,以及 seine 如何在它執行的每顆 CPU 上算出盡可能多的雜湊。

Argon2id 是什麼?

簡短回答

Argon2id 是一種記憶體密集型(memory-hard)雜湊函數,在 RFC 9106 中定義,用於密碼雜湊與工作量證明。它以 1 KiB 的資料區塊填滿一大片記憶體,每個區塊由前一個區塊與記憶體中另一處更早的區塊計算而來,因此計算一次雜湊既要花處理器時間,也要占用實實在在的記憶體。

RFC 9106 由 IRTF 的密碼論壇研究小組(CFRG)於 2021 年發布,描述了「用於密碼雜湊與工作量證明應用的 Argon2 記憶體密集型函數」。Argon2 有三種變體:

在內部,Argon2 把記憶體視為一長串 1,024 位元組的區塊。每個新區塊都是壓縮函數 G 作用於兩個輸入的結果:緊接在它前面的區塊,以及記憶體中更早位置的一個參考區塊。G 以 BLAKE2b 雜湊函數的混合輪函數為基礎,對 64 位元字組進行加法、互斥或與循環位移運算。

Blocknet 如何把 Argon2id 用作工作量證明?

Blocknet 在共識程式碼中固定了 Argon2id 的每一項設定,因此每個礦工與每個節點計算的都是完全相同的函數:

參數 Blocknet 設定值 意義
變體 Argon2id,1.3 版 RFC 9106 的主要變體
記憶體 2 GiB(2,097,152 KiB) 每次雜湊填滿 2,097,152 個 1 KiB 的區塊
迭代次數 1 對記憶體只處理一輪
平行度 1 只有一條通道,一次雜湊在一個執行緒上執行
輸出 32 位元組 與目標值比較的結果
密碼 8 位元組 nonce,小端序 礦工每次嘗試時改變的數字
鹽值 不含 nonce 的區塊標頭 把雜湊綁定到某一個區塊範本

挖礦就是一種搜尋。礦工拿著想要發布的區塊標頭,選一個 nonce,計算 Argon2id 雜湊。如果這 32 位元組的結果當作數字小於或等於全網的目標值,這個區塊就是有效的;否則礦工就換下一個 nonce 繼續嘗試。

目標值直接由難度得出:它等於 (2^256 − 1) 除以難度。因此,難度大致等於全網找到一個區塊預計需要計算的雜湊次數。Blocknet 的目標是每五分鐘出一個區塊,所以全網算力約等於難度除以 300 秒。難度本身透過 60 個區塊的加權移動平均來跟隨最近的出塊時間。

礦池的原理相同,只是目標更容易達到。在 bntpool.com 上,seine 提交符合較低的單一連線難度的份額,其中同時符合全網目標的份額就是找到的區塊。

為什麼記憶體密集型工作量證明適合一般電腦?

上面的設定讓記憶體成為每次雜湊的核心。由於只處理一輪、只有一條通道,這一輪的後半段是資料相依的:每個區塊的參考區塊位置取自緊接在它之前計算出的區塊的第一個 64 位元字組。礦工直到需要的那一刻才知道要用哪個更早的區塊,所以必須把整塊 2 GiB 區域留在記憶體中。

礦工也可以嘗試少存一些區塊,需要時再重新計算缺少的區塊。RFC 9106 把這稱為時間與記憶體的權衡:它們節省記憶體,「代價是更多次呼叫內部壓縮函數」。從區域中丟掉的每一個區塊,都要以額外的運算再付一次代價。

這正是這種設計對一般硬體友善的原因。桌上型電腦、筆記型電腦與伺服器本來就配有數 GB 記憶體,以及為供給這些記憶體而設計的記憶體控制器。專為雜湊打造的晶片,仍然需要在雜湊核心旁配備數 GB 高速記憶體。RFC 9106 的首選建議設定是對 2 GiB 記憶體只處理一輪的 Argon2id,與 Blocknet 採用的處理輪數與記憶體大小相同,RFC 指出這個設定「使對手在專用暴力破解硬體上的成本最大化」。Blocknet 節點程式的程式碼則把這個選擇描述為記憶體密集型工作量證明,「以抵禦 ASIC 並確保公平挖礦」。Argon2id 的設計旨在抵禦 ASIC,2 GiB 的設定讓這個設計發揮作用,使人們已經擁有的電腦都能參與挖礦。工作量證明與權益證明的差異一文說明了它與質押相比有何不同。

為什麼記憶體容量與記憶體頻寬決定算力?

簡短回答

每個挖礦執行緒都要占用自己的一塊 2 GiB 區域,所以記憶體容量決定你能執行多少個執行緒。每次雜湊還要寫入 2 GiB 資料,並從分散的位置讀回差不多同樣多的資料,所以記憶體的速度與延遲決定這些執行緒完成得有多快。記憶體的影響與核心數量同樣大。

記憶體容量決定執行緒數。 平行度為 1 表示一次雜湊在一個執行緒上執行,每個執行緒都需要一整塊屬於自己的 2 GiB 區域。一台 16 GB 記憶體的電腦在為系統留出空間後,大約能執行六到七個執行緒。seine 會偵測你的記憶體,預留 5%,並為你選擇執行緒數。

頻寬與延遲決定每個執行緒的速度。 一次雜湊會產生 2,097,152 個新區塊,每個區塊都要從區域中的某處讀取一個 1 KiB 的參考區塊。換算下來,每次雜湊大約寫入 2 GiB、讀取 2 GiB。以 10 H/s 計算,電腦每秒要在記憶體中搬移大約 40 GiB 資料。在資料相依的後半段,每個讀取位址要等前一個區塊算完才能得知,所以記憶體的回應時間與原始吞吐量同樣重要。

這就是 seine 的效能頁面說算力「既取決於核心數量,也同樣取決於記憶體頻寬與延遲」的原因。Blocknet 挖礦硬體指南列出了各代硬體的最佳結果。

大分頁記憶體有幫助也是同樣的道理。一塊 2 GiB 區域需要 524,288 個一般的 4 KiB 分頁,或 1,024 個 2 MiB 分頁。分頁更少、更大,那些分散的讀取就會更少發生位址轉譯未命中,在 seine 驗證用的機器上帶來了約 4–6% 的提升。設定方式請見大分頁記憶體指南。

seine 的 CPU 運算核心程式如何充分利用 Argon2id?

運算核心程式是計算這些區塊的內層迴圈。seine 的 x86 版本帶有純量、AVX2 與 AVX-512 運算核心程式以及一個串流變體,Arm 版本帶有 NEON 及其 SHA3 快速路徑。它們各自以不同方式處理受記憶體限制的工作:

在我們測試的硬體上,選對運算核心程式帶來了很大的提升。以下是同一台機器、相同執行緒數下的成對測試結果,來自 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 支援的各個運算核心程式,只在兩次確認有提升後才切換,並把勝出者快取起來供下次啟動使用。最佳運算核心程式取決於執行緒數、分頁大小與 CPU 綁定方式,所以 seine 會在你的電腦上實測,而不是寫死某個選擇。

想讓你的記憶體發揮作用,就下載 seine,執行它,然後貼上你的地址。其餘步驟請見挖礦指南。

常見問題

Blocknet 的工作量證明使用哪些 Argon2id 參數?

Blocknet 使用 Argon2id 1.3 版,記憶體 2 GiB,迭代 1 次,平行度 1,輸出 32 位元組。8 位元組的 nonce 作為密碼,不含 nonce 的區塊標頭作為鹽值。當雜湊值小於或等於由全網難度決定的目標值時,就找到了一個區塊。

為什麼每個 Blocknet 挖礦執行緒需要 2 GB 記憶體?

每次 Argon2id 雜湊都要填滿一塊由 1 KiB 資料區塊組成的 2 GiB 記憶體區域。在這一輪的後半段,每個新區塊都要讀取一個更早的區塊,究竟是哪一塊取決於礦工無法預知的資料。執行緒必須把整塊區域留在記憶體中,所以每執行一個執行緒大約需要 2 GB 記憶體。

為什麼說 Argon2id 的設計旨在抵禦 ASIC?

Argon2id 讓記憶體成為每次雜湊中成本最高的部分。每次 Blocknet 雜湊需要 2 GiB 高速記憶體,並在其中搬移數 GB 資料,因此專為雜湊打造的晶片仍需為每個核心配備大量記憶體。節點程式的程式碼把這個選擇描述為以記憶體密集型工作量證明來抵禦 ASIC,確保公平挖礦。

顯示卡能挖 Blocknet 的 Argon2id 嗎?

可以。在 Windows 與 Linux 上,seine 會在 CPU 挖礦的同時使用電腦上的每張 NVIDIA 顯示卡,兩者的算力會相加。運算核心程式在啟動時編譯,第一次啟動時會為每張顯示卡調校並快取結果,所以開始之前無需任何設定。