HARDWARE SECURITY

密碼演算法 RTL 設計第 1 / 2 課

SHA-2 RTL 設計入門:從 SHA-256 到第一個硬體核心

給第一次設計雜湊 IP 的電機系與研究所學生:先看簡明 TOP view,再從 padding、W 排程、64 輪資料路徑走到跨區塊累積、FSM、握手與逐拍驗證。

18 分鐘

先用一句話抓住這篇

給第一次設計雜湊 IP 的電機系與研究所學生:先看簡明 TOP view,再從 padding、W 排程、64 輪資料路徑走到跨區塊累積、FSM、握手與逐拍驗證。

先把這篇當成一張閱讀地圖:上方摘要說明問題,圖片先建立直覺,下面文字再補上真正的技術取捨。

如果第一次讀覺得名詞很多,可以先記住比喻與結論;第二次再回頭看名詞,會順很多。

晶片收到一份新的韌體。Flash 不會一次交出整份映像,而是一段一段送來。系統需要的是整份映像的 SHA-256 摘要,不是每段資料各自的摘要。最後一段不足一個區塊,也不能直接丟掉。

如果用軟體,一次函式呼叫會替你安排這些工作。換成硬體,就要明確分工:誰補齊最後一塊?誰留下上一塊的結果?核心還在算時,下一塊由誰保管?這些問題決定暫存器與介面的設計。

本篇建立一個 SHA-256 iterative block core。它每次接收已補位的 512-bit 區塊,重複使用同一套電路完成 64 輪,再累積結果。先理解資料怎麼走,再接上公式。讀者需要同步邏輯、位元運算與基本 Verilog 概念。

本篇交付的是設計教學、介面與時序契約,以及可執行的參考模型。RTL 片段用來說明關鍵接線,並非已完成綜合、時序收斂或安全認證的完整 IP。想先建立雜湊的安全概念,可讀 SHA 漫畫小教室;熟悉本篇後,也可比較 AES RTL 入門 的資料路徑。

1. 先分清楚 SHA-2、SHA-256 與加密

SHA-2 是演算法家族,SHA-256 是其中一個成員。 本篇選擇 32-bit 運算、512-bit 區塊、256-bit 摘要的 SHA-256,讓你先把一種架構做清楚。FIPS 180-4 定義了相關函式、常數與處理流程。

成員Word輸入區塊輪數摘要
SHA-22432 bits512 bits64224 bits
SHA-25632 bits512 bits64256 bits
SHA-38464 bits1024 bits80384 bits
SHA-51264 bits1024 bits80512 bits
SHA-512/224、SHA-512/25664 bits1024 bits80224/256 bits

這些變體不只是改輸出寬度。SHA-224 與 SHA-256 的初始值不同;SHA-512/256 也不是直接把一般 SHA-512 的結果截成 256 bits。本文不把家族成員混在同一份控制器裡。

SHA-256 不是把資料加密成 256 bits,也沒有一把用來還原原文的金鑰。輸入可以有很多不同長度,輸出固定是摘要;摘要無法唯一表示所有可能訊息。普通 SHA-256 沒有金鑰輸入,後面的 K[t] 是公開輪常數,不是 secret key。

在韌體情境裡,摘要可用來和可信的預期值比對;若攻擊者連預期摘要也能一起替換,單靠雜湊不能證明發布者身分。簽章驗證與可信 metadata 是系統的另一層責任,本篇先處理摘要如何正確算出。

2. TOP view:先看整個任務,再打開核心

先用兩層來看。外層整理訊息,內層壓縮區塊。 外層知道原始訊息多長、何時結束;內層只需要知道眼前這個 512-bit 區塊是不是第一塊、是不是最後一塊。

SHA IP 總覽:訊息來源、padding 與分塊整理層、SHA-256 區塊核心,以及最後摘要輸出
圖 1 · 第一版先由軟體準備 padded blocks,集中完成中間的硬體核心。

訊息整理層負責補位、記錄原始 bit 長度、排列成 512-bit 區塊。這個功能最終可以做成硬體串流 wrapper,但第一版由 testbench 或軟體先準備,較容易把演算法錯誤與介面錯誤分開。不要把「已 padding 的區塊核心」誤稱成可直接接任意 bytes 的完整串流 SHA IP。

打開區塊核心後,先認識四個角色:W 排程器每輪提供一個 32-bit 訊息 word;Round 資料路徑計算下一組 a~h;H 暫存器保存跨區塊累積值;Controller決定何時接收、計算、累加與輸出。它們都在同一個任務裡,卻不應共用一個含糊的「state」名稱。

跟著一份訊息走一次

以很短的 abc 為例。外層先把三個 bytes 補成一個 64-byte 區塊,標記 first=1、last=1。核心接收後存住區塊、載入初始值,接著每拍完成一輪,總共 64 輪。輪運算結束,還要把工作結果加回原來的 H,這一步叫 feed-forward。完成後才可以宣告 digest 有效。

如果訊息跨兩個區塊,第一塊算完不交出最終 digest,而是留下 H,等待第二塊繼續。第二塊不能重新從初始值開始,也不能把兩塊各自的 SHA-256 串在一起。下游若還沒準備好收最終摘要,核心就保持摘要與 valid,直到交付成立。

3. Padding:最後不足一塊,誰來補?

SHA-256 處理的訊息 bit 長度 L 必須小於 2⁶⁴。規則是先接一個 1 bit,再補 k 個 0 bits,使 (L + 1 + k) mod 512 = 448,最後接 64-bit 大端序的原始 bit 長度 L。長度不包含 padding 本身;不要把 bytes 數直接寫進去。這對應 FIPS 180-4 §5.1.1。

本篇先處理 byte-aligned 訊息,所以接上那個 1 bit 表現為一個 0x80 byte。abc 的 bytes 是 61 62 63,原始長度是 24 bits;它後面接 80、52 個零 bytes,最後接 00 00 00 00 00 00 00 18。

abc 的 padding:三個原始 bytes 加 80、52 個零與 64-bit 長度,組成 W0 到 W15
圖 2 · 256 是摘要位數;每個輸入 block 則是 512 bits。
原始訊息長度Padding 後區塊數為什麼
0 bytes1空訊息仍要補 1 bit 與長度欄位
55 bytes155 + 1 + 8 = 64,剛好放得下
56 bytes2第一塊已放不下 8-byte 長度欄位
63 bytes2補位與長度仍需下一塊
64 bytes2原始資料剛好一整塊,仍必須另加 padding

因此 block_last 的意思是最後一個 padded block,不是「最後一塊含原始資料的 block」。原始資料已經 64 bytes 對齊時,這兩者尤其容易被混淆。

回到韌體讀取流程,Flash 告訴你「原始資料送完了」,不代表核心也收完了。整理層還要送出補位與長度欄位。若過早標記 last,核心即使每一輪都算對,得到的也不是規定的 SHA-256 摘要。

4. 固定 byte 與 word 排列

先固定「最早的 byte 放哪裡」,再設計取 word 的電路。本篇定義 block_data[511:0] = {B0, B1, …, B63},B0 是訊息中最早的 byte。第一個 word 是 W0 = {B0,B1,B2,B3} = block_data[511:480],最後一個 word 是 W15 = block_data[31:0]。每個 word 都採大端序組合。

// Interface convention; i is a constant generate index, 0..15.
assign word_i = block_data[511 - 32*i -: 32];

abc 的第一個 word 必須是 32'h61626380,不是 32'h80636261。這是核心介面約定;若 CPU 或匯流排的 byte lanes 不同,轉換應由 wrapper 明確處理。不要在 W 排程器與 round 電路各自交換一次 byte,讓兩個錯誤暫時互相抵銷。

測試時先看 W0,而不是直接等摘要。W0 就像交給核心的第一份資料清單。這裡排錯了,後面再正確的旋轉與加法也無法補救。介面、參考模型與波形顯示必須使用同一份排列約定。

5. H 與 a~h:兩組暫存器,不能省成一組

SHA-256 有八個 32-bit 的 chaining words,本文稱為 H0…H7。它們保存「目前整份訊息已處理到哪裡」;另有八個工作暫存器 a…h,負責「眼前這一塊正在算哪一輪」。注意小寫 h 只是工作暫存器之一,大寫 H 是整組跨區塊狀態。

H 鏈結狀態載入 a 到 h,64 輪期間 H 保持,最後逐 word 加回工作結果
圖 3 · 64 輪期間保留舊 H,因為 feed-forward 還要用它。

第一塊從下面的 IV 開始。IV 是公開固定常數,不是每次隨機產生的數值。第二塊與後續區塊,則從上一塊完成 feed-forward 的 H 載入工作暫存器。

H0 = 6a09e667   H1 = bb67ae85
H2 = 3c6ef372   H3 = a54ff53a
H4 = 510e527f   H5 = 9b05688c
H6 = 1f83d9ab   H7 = 5be0cd19

為什麼不能讓 H 直接跟著每輪變動?因為最後要做 H0 + a_final、H1 + b_final,一路到 H7 + h_final。如果早把 H 蓋掉,就失去加回去的基準。本架構在 ROUND 階段只更新 a~h,等到獨立的 ACCUM 階段才更新 H。

6. 先認識硬體積木:旋轉、選擇、多數決與加法

SHA-256 沒有 AES S-box。主要工作是固定旋轉、邏輯位移、XOR、AND、NOT 與 32-bit 加法。所有加法都取模 2³²,也就是只保留低 32 bits;這裡的加法有 carry,不能用 XOR 代替。

ROTRⁿ(x) 是向右循環旋轉,被移出的低位繞回高位;SHRⁿ(x) 是邏輯右移,高位補零。固定旋轉在 RTL 中通常是接線;可變 n 的通用旋轉器可能引入不必要的選擇電路。

// Example: fixed ROTR^7 and SHR^3, with unsigned 32-bit x.
assign r7 = {x[6:0], x[31:7]};
assign s3 = x >> 3;
函式定義用在哪裡
Ch(x,y,z)(x & y) ^ (~x & z)每一 bit 由 x 選擇 y 或 z
Maj(x,y,z)(x & y) ^ (x & z) ^ (y & z)每一 bit 取三者多數值
Σ0(x)ROTR² ⊕ ROTR¹³ ⊕ ROTR²²Round 電路,輸入 a
Σ1(x)ROTR⁶ ⊕ ROTR¹¹ ⊕ ROTR²⁵Round 電路,輸入 e
σ0(x)ROTR⁷ ⊕ ROTR¹⁸ ⊕ SHR³W 排程
σ1(x)ROTR¹⁷ ⊕ ROTR¹⁹ ⊕ SHR¹⁰W 排程

大寫 Σ 與小寫 σ 是不同函式,旋轉量不同,小寫還有一項 SHR。命名時使用 big_sigma0、small_sigma0 等明確名稱,比只用 sigma 更不容易接錯。

可以先只測一個 bit 的位置,確認旋轉會繞回、位移會補零。再測一組會溢位的加數,確認電路丟掉第 33 bit。把這些小積木分開驗證,能避免日後看到摘要不符,卻不知道該查接線還是算術。

先用小數值認識這些運算

以下 hex 值都固定為 32 bits。ROTR¹(00000001)=80000000,因為低位的 1 繞回最高位;SHR¹(00000001)=00000000,則直接丟掉它。模加法 ffffffff + 00000001 = 00000000,只保留低 32 bits;XOR 卻得到 fffffffe,兩者不可互換。

Ch 是逐 bit 選擇:x=1 選 y,x=0 選 z。取 x=ffffffff、y=12345678、z=9abcdef0,Ch=y;x=00000000 時 Ch=z。Maj 是逐 bit 多數:三個輸入中至少兩個為 1,輸出才是 1。這些函式不會把八個 words 當成八個整數投票。下面回到同一份 abc,使用完整 IV 計算第一輪。

7. 一輪資料路徑:先算 T1、T2,再一起更新

把 round 當成一個純組合函式:輸入目前的 a~h、這一輪的 W[t] 與公開常數 K[t],輸出下一組 a~h。t 從 0 到 63。K 是 64 個固定的 32-bit 值,例如 K[0]=428a2f98、K[63]=c67178f2;完整表見 FIPS 180-4 §4.2.2 與文末參考模型。

T1 = h + Σ1(e) + Ch(e,f,g) + K[t] + W[t]
T2 = Σ0(a) + Maj(a,b,c)

a_next = T1 + T2      e_next = d + T1
b_next = a           f_next = e
c_next = b           g_next = f
d_next = c           h_next = g
SHA-256 round:T1 與 T2 形成新的 a、e,其餘六個工作暫存器接收舊值
圖 4 · 每一輪一起更新八個暫存器;箭頭不代表依序執行軟體敘述。

這個圖的重點是全部右側都讀同一組舊值。如果先覆寫 a,再把 a 傳給 b,b 就拿到了錯誤的新 a。時序區塊使用 nonblocking assignments,可表達同一個上升緣共同取樣。

// Conceptual ROUND branch. t1 and t2 are combinational 32-bit signals.
a <= t1 + t2;
b <= a;
c <= b;
d <= c;
e <= d + t1;
f <= e;
g <= f;
h <= g;

組合電路在暫存器輸出改變後持續反應,時脈只決定何時保存下一組值。T1 涉及多個加數,通常比固定旋轉更值得注意時序。本文假設組合讀取 K 常數;如果改用同步 ROM,必須安排提前讀取或增加階段,不能直接沿用第 11 節的拍數。

從 abc 的舊值算出第一輪

所有值採十六進位,所有加法保留低 32 bits。Round 0 的舊值來自第 5 節 IV,W0=61626380、K0=428a2f98。依第 6 節三個旋轉量計算:

Σ1(510e527f)=3587272b;Ch(510e527f,9b05688c,1f83d9ab)=1f85c98c。

所以 T1 = 5be0cd19 + 3587272b + 1f85c98c + 428a2f98 + 61626380 = 54da50e8 (mod 2³²)。同樣,Σ0(6a09e667)=ce20b47e、Maj(6a09e667,bb67ae85,3c6ef372)=3a6fe667,得到 T2=08909ae5。

再算 a_next=54da50e8+08909ae5=5d6aebcd;e_next=a54ff53a+54da50e8=fa2a4622 (mod 2³²)。b_next 接舊 a=6a09e667,不能接新 a=5d6aebcd。這是「一起更新」的可手算檢查。

在互動台載入 abc,LOAD 顯示 IV,下一步 ROUND t=0 顯示上述 T1、T2 與新 a/e。它對應第 11 節 E1;LOAD 是 E0。若只核對摘要,就失去判斷錯誤先發生在公式還是暫存器更新的機會。

8. W 排程:16 個輸入 words 如何供應 64 輪?

區塊原本只有 W[0]~W[15]。SHA-256 依下式產生 W[16]~W[63],每輪消耗一個 W[t]:

W[t] = σ1(W[t-2]) + W[t-7] + σ0(W[t-15]) + W[t-16]

第一個容易理解的實作是保存全部 64 words,先展開再運算;但那會需要額外排程時間或較大的組合網路。本文選擇 16-word 滑動視窗,保存 512 bits,讓產生未來 word 與目前 round 並行。

在第 t 輪開始,q[0] 是本輪用的 W[t]。前 48 輪還有未來 word 要產生,此時 q[1]、q[9]、q[14] 分別是 W[t+1]、W[t+9]、W[t+14],所以:

next_w = σ1(q[14]) + q[9] + σ0(q[1]) + q[0]
round_w = q[0]
16-word 視窗:q0 供目前 round,q14、q9、q1、q0 組合產生 Wt加16,輪末移動並寫入尾端
圖 5 · 目前的 round 讀 q[0],不必等待同拍算出的 next_w。

每次輪末,把 q[1] 搬到 q[0],一路移到 q[14],尾端 q[15] 補 next_w。t=0 時計算 W16,t=47 時計算 W63;t=48~63 不再需要新 word,可在尾端補零。最後十六輪仍從 q[0] 依序讀完既有的 W48~W63。

// Inside ROUND; load all 16 words separately on block acceptance.
for (int j = 0; j < 15; j++) q[j] <= q[j+1];
q[15] <= (round_t < 6'd48) ? next_w : 32'b0;

所有右側都讀移動前的視窗。若先移動再算 next_w,四個 taps 就全部錯一格。這個排程器應先獨立驗證:把實際送入 round 的 64 個 words,逐一對照完整展開陣列,而不只是看最後 digest。

W16 不需要猜:沿同一份 padding 算

對 abc,W14=W9=W1=0、W0=61626380。代入展開式:W16=σ1(W14)+W9+σ0(W1)+W0=0+0+0+61626380=61626380。在 t=0 的視窗,q14、q9、q1、q0 正是這四個值,所以同拍產生相同的 next_w。E1 把它放進 q15,再經過十五次移動,E16 更新後到達 q0。t=16 的 round 在 E17 捕捉使用它的運算結果。

這並不表示 W16 永遠等於 W0;那是 abc 的三個零項造成的特例。換成 56-byte 範例時,先查看該區塊 W1、W9、W14,再算 W16。圖中的 taps、程式的 q 索引與下載模型的完整 W 陣列,必須得到同一個 word。

9. Feed-forward 與跨區塊累積

64 輪後,a~h 是工作結果,還不是最終 digest。需要逐 word 做:

H0_new = H0_old + a_final    H4_new = H4_old + e_final
H1_new = H1_old + b_final    H5_new = H5_old + f_final
H2_new = H2_old + c_final    H6_new = H6_old + g_final
H3_new = H3_old + d_final    H7_new = H7_old + h_final

這是八次獨立的 32-bit 模加法,不是一個帶著跨 word carry 的 256-bit 大加法。這個架構給它獨立一拍 ACCUM,因此讀到的 a~h 已是 t=63 完成後的值。

兩個區塊由 H 串接:第一塊從 IV 開始,第二塊從上一塊 feed-forward 結果繼續
圖 6 · 每塊都做 feed-forward,只有最後一塊完成才輸出訊息摘要。

若還有下一塊,新 H 留著供下一次載入;若是最後一塊,輸出 {H0,H1,…,H7},H0 在 digest[255:224]。這也說明為什麼同一份訊息的區塊有前後相依性:第二塊的起點必須等待第一塊完成。

因此,不能把同一份韌體的兩塊各送到一個核心,再把兩個摘要接起來。那是兩份不同的雜湊計算。本篇核心保存的 H,是連接這份訊息前後區塊的依據;first 旗標只在新訊息開始時要求回到 IV。

用第一個 word 看出 feed-forward 的作用

abc 的第 64 輪完成後,a_final=506e3058。原來的 H0=6a09e667 仍未被覆蓋,所以 H0_new=6a09e667+506e3058=ba7816bf。這才是摘要開頭。若直接把 a_final 當作摘要,就會錯從 506e3058 開始;若把所有 words 串成一次 256-bit 加法,也會錯把低 word 的 carry 傳到其他 word。

在互動台先停在最後 ROUND,查看工作值;再前進 ACCUMULATE,比對 H base 與 H next。多區塊訊息的下一次 LOAD 必須使用 H next。這是算法狀態的串接;畫面前進不量測實際 RTL 的運算延遲。

10. Top-Level 介面:先把交付規則寫下來

第一版不加入 AXI、DMA 或任意位元長度串流。上游把 padded block 準備好,與 first、last 旗標一起送入。輸入資料與兩個旗標只有在 block_valid && block_ready 的上升緣一起被接收。

訊號方向寬度約定
clk、rst_nIn1 各一上升緣;低有效同步 reset
block_dataIn512已 padding,B0 在最高 byte
block_first、block_lastIn1 各一訊息的第一/最後 padded block
block_validIn1上游資料與旗標有效
block_readyOut1核心可在本拍接收
digest_dataOut256H0 在最高 word
digest_validOut1最後一塊 feed-forward 已完成
digest_readyIn1下游可接收摘要
busyOut1ROUND、ACCUM、OUTPUT_HOLD 為 1

Controller 使用 WAIT_FIRST、WAIT_NEXT、ROUND、ACCUM、OUTPUT_HOLD 五個狀態。為了拒絕不合順序的 first 旗標,本設計定義:

block_ready = rst_n && (
    (fsm == WAIT_FIRST && block_first) ||
    (fsm == WAIT_NEXT  && !block_first)
)
digest_valid = rst_n && (fsm == OUTPUT_HOLD)

來源不可等 ready 才提出 valid、data 與 first;要先提出請求,等待握手。valid=1 且尚未握手時,data、first、last 與 valid 都必須保持。ready 組合相依於 first,wrapper 不可反過來由 ready 決定 first,造成組合回路。WAIT_FIRST 只接 first=1,WAIT_NEXT 只接 first=0;旗標錯誤時 ready 保持 0,不會悄悄改用錯的初始值。這是刻意簡化的協定,不是錯誤回報介面;testbench 應用 assertion 或 timeout 抓出違規來源。

first=last=1 表示一塊完成的訊息。last 會在輸入握手時鎖住,不能等到第 64 輪才讀外部 block_last。WAIT_NEXT 時 busy=0 代表核心可接下一塊,不代表整份訊息已結束。

11. 精確時序:64 輪不等於 64 拍交付

E0 是區塊接受的上升緣。下表列出每個邊緣完成更新後的狀態。第一塊把 H 與 a~h 都載入 IV;續塊保持 H,將 H 載入 a~h。兩者都把 16 個輸入 words 載入 q,counter 設為 0。

E0 接收、E1到E64做64輪、E65累加、E66最早輸出或接下一塊的時序
圖 7 · 輪數是演算法規定;接收、累加與交付拍數是架構決定。
邊緣動作邊緣後狀態
E0接收 block,載入 H/work/q,t=0ROUND
E1~E63完成 t=0~62;工作值與 q 同步更新ROUND,t 增加
E64完成 t=63;counter 保持 63ACCUM
E65,last=0H 加回 workWAIT_NEXT
E65,last=1H 加回 work,digest_valid=1OUTPUT_HOLD
E66 或更晚非末塊可接下一塊;末塊可完成摘要握手ROUND 或 WAIT_FIRST
E67 或更晚上一筆摘要已於 E66 送出時,最早開始新訊息ROUND

表中 E66 的轉移以握手成功為前提:若續塊尚未提出,保持 WAIT_NEXT;若下游不接摘要,保持 OUTPUT_HOLD。E0 只載入,不同時執行 t=0 或移動 q;ACCUM 也不因 digest_ready=1 就跳過 OUTPUT_HOLD。用「目前 FSM 狀態」選擇載入/ROUND/ACCUM 分支,避免同一個邊緣重複更新。

從最後區塊接受到 digest_valid 是 65 cycles;最早摘要握手在 E66。無 stall 的續塊接受間隔是 66 cycles,長訊息的核心區塊處理率約 512 × f_clk / 66 bit/s,未扣 padding、輸入供應與輸出等待。單一區塊訊息連續送入時,接受間隔最少 67 cycles,不能把 66 套到所有情況。

這些數字假設一輪一拍、組合常數讀取、八個 feed-forward 加法同拍完成,以及沒有額外 input/output pipeline。若增加暫存階段或共用一個加法器,就必須重寫這張表。實際 f_clk 由綜合、佈局與時序分析決定,不從算法輪數猜出。

12. 背壓、Reset 與容易忘記的狀態

摘要算完,只代表核心已有答案。下游可能還在處理上一件工作。digest_valid=1 && digest_ready=0 時,核心必須保管 H、digest_data 與 digest_valid,不能先清掉它們,也不能接新區塊來覆蓋 H。

兩者同為 1 的上升緣才完成傳送,之後回 WAIT_FIRST。本文不在輸出握手同拍接受新訊息,所以有 E67 的邊界。這個空拍是本篇的介面選擇,不是 SHA-256 的數學要求。

rst_n=0 時的 clk 上升緣清除 H、a~h、q、counter 與 latched last,回 WAIT_FIRST,取消尚未完成的訊息。reset 期間 ready/valid 都為 0。解除後第一筆必須 first=1,不能接著送被取消訊息的續塊。清成 0 不影響 SHA IV:第一塊握手時才載入規定的 IV。

上游也要知道這份訊息已被取消。若它仍從中間區塊接著送,核心會拒收,不能把這種等待誤當成硬體算得太慢。測試環境應清掉取消的期待結果,並要求來源重新開始一份完整訊息。

資料 register、演算法 round counter 與 FSM 不要混成一個 always block 裡難以辨認的流程。可以用一個清楚的 sequential block 統一更新,但 next-value 計算與各階段的 enable 必須可逐一追蹤。所有組合輸出都要完整賦值,避免意外推導 latch。

13. 用 abc 找出第一個分歧點

先不要用「看起來像亂碼」當正確性判斷。NIST SHA-256 中間值範例 給出了 abc 的每輪 a~h,也有兩個區塊的範例。

Input bytes   = 61 62 63
W0            = 61626380
W1 … W14     = 00000000
W15           = 00000018
K0            = 428a2f98

After t=0:
a=5d6aebcd  b=6a09e667  c=bb67ae85  d=3c6ef372
e=fa2a4622  f=510e527f  g=9b05688c  h=1f83d9ab

Final digest:
ba7816bf8f01cfea414140de5dae2223
b00361a396177a9cb410ff61f20015ad

上面摘要分兩行顯示,實際是連續 64 個 hex digits。NIST 的「t=0」表示第一輪完成後,對應本篇 E1,不是 E0 初始化的值。對照 trace 前先對齊取樣時點,否則會把正確結果誤認為永遠差一輪。

第一個錯的位置優先查哪裡
W0 就錯原始 bytes、padding、大小端
W0~W15 對,W16 錯σ 大小寫、四個 taps、更新前後順序
W 都對,t=0 的 a/e 錯K0、Ch/Maj、Σ、32-bit 模加
t=0 對,後面平移一輪counter、同步 ROM 延遲、nonblocking 更新
64 輪都對,digest 錯忘記 feed-forward、加到新 H、跨 word carry
短訊息對,長訊息錯每塊重載 IV、first/last、padding 邊界
數值對,輸出筆數錯背壓下 valid 提早消失或重複交付

14. 可執行的參考模型與驗證作業

下載 SHA-256 參考模型(Node.js) 與 abc 每輪 trace/邊界測試結果。在有 Node.js 的環境執行:

node sha256-rtl-reference.mjs sha256-trace.json

模型會獨立展開 64-word W 表,再逐輪確認 16-word 視窗消耗的 word 相同;同時計算 digest,與 Node/OpenSSL 的 SHA-256 比較。271 組訊息涵蓋空字串、abc、NIST 兩區塊訊息、55/56/63/64-byte 邊界及不同資料樣式。另核對 NIST 的第一輪 a~h。這是演算法與排程參考驗證,不是 RTL 模擬通過的證明。

真正寫 testbench 時,先送軟體已 padding 的 block,scoreboard 只在有效握手邊緣記錄收送。從正確數值擴充到控制情境:

  1. 空訊息、一區塊、兩區塊、三區塊;續塊中間插入空拍。
  2. digest_ready 持續為 1,以及隨機拉低數拍;摘要與 valid 必須保持。
  3. 前一份訊息結束後接不同的新訊息;確認 H 回到 IV 起點。
  4. ROUND、ACCUM、OUTPUT_HOLD 時 reset;清掉 scoreboard 取消的期待結果。
  5. WAIT_FIRST 故意送 first=0、WAIT_NEXT 故意送 first=1;確認不接受違規區塊,測試要有 timeout。
  6. 比對所有 W[t]、每輪 a~h、每塊 feed-forward H,再比對最終摘要。

15. 建議模組切法與第一次實作順序

模組責任先驗證什麼
sha256_round舊 a~h、Wt、Kt → 新 a~hNIST t=0,再核對全部 rounds
sha256_schedule載入 16 words,維護 q 視窗全部 64 個消耗 words
sha256_constantst → 32-bit Kt64 個常數與讀取延遲
sha256_coreH/work/q、FSM、握手與累加多區塊、stall、reset
message wrapper(下一階段)bytes、長度、padding、first/last55/56-byte 邊界與串流停頓

先完成純組合 round,再做 schedule,最後加 state、FSM 與介面。不要一開始就把 AXI、DMA、padding、SHA-224 切換與高效能 pipeline 全塞進來;每增加一個共享資源或階段,都會改變你必須證明的時序。

這個順序能把錯誤逐層縮小。Round 測試只問算式是否正確;schedule 測試只問每輪拿到哪個 W。兩者各自通過後,整合測試才集中檢查載入、累加與交付。若一開始就只比對最後摘要,資料與控制的錯誤會混在一起。

粗看資料儲存,H 256 bits、work 256 bits、q 512 bits,共 1024 bits,另外還有 counter、旗標與控制狀態;K 常數表 64×32=2048 bits,可由組合查表實現,不等於一定占用 2048 個 flip-flops。第一版做對後再看 synthesis report,找真正的加法 critical path、面積與切換功耗。

16. 完成本篇後,應該能回答什麼?

  • 為什麼需要 message wrapper?因為區塊核心不負責猜原始訊息長度與 padding。
  • 為什麼 H 不能每輪都覆寫?因為每塊結束還要加回該塊起始的 H。
  • 為什麼 K 不是 AES 的 round key?因為它是每個人相同的公開常數。
  • 為什麼 64 輪還要到 E65 才有效?因為還有 feed-forward。
  • 為什麼原始 64-byte 訊息仍需兩塊?因為 padding 與長度欄位不可省略。

把它接回韌體更新場景:核心先保證完整訊息的摘要正確,系統再把摘要放進簽章驗證與可信啟動流程。HMAC 也需要額外的 key 處理與內外層雜湊,不能把一把 key 接到本篇的 K 常數輸入就當成 HMAC。

17. 參考資料與設計範圍

本篇的 16-word 視窗、五狀態 FSM、first 旗標拒收策略與 E0~E67 時序,是為入門選定的硬體架構;不是標準強制規定的介面,也不代表特定製程的效能承諾。

本篇先不實作串流 padding、AXI/DMA 與多訊息並行。時序收斂、側通道與故障防護也需另外驗證。完成本核心後,可先補 message wrapper;若要分析摘要如何參與授權,再讀 RTL 防竄改第一課。

MY ACADEMY · LESSON FILM

教學影片

影片依序說明本課的資料路徑。看完一段,可以回到下面的互動練習,改變輸入或故障條件。動畫呈現教學模型;它沒有替代 RTL 模擬。

下載 MP4 · 字幕 VTT

旁白使用合成聲音。互動教學與動畫均有模型邊界;請以本課的來源與驗證範圍解讀結果。

MY ACADEMY · RTL LAB

操作 SHA-256:追蹤區塊與逐輪暫存器

先用 abc 走到第 0 輪,查看 W、K、T1、T2 與 a~h。改用 56 bytes 範例,再看 padding 為何產生兩個區塊,以及前一區塊如何更新 H。

教學排程包含 LOAD、64 次 ROUND 與 ACCUMULATE。每個 ROUND 視為一次暫存器更新;LOAD/ACCUMULATE 各自獨立。實際 RTL 的 latency 仍由自己的 FSM、排程與介面決定。

證據範圍:瀏覽器功能教學模型。獨立比對使用瀏覽器 Web Crypto。沒有執行 RTL、綜合、形式證明或側通道測試。

QD

運算前

運算後

查看區塊、排程與輪金鑰

輸出握手:valid/ready

走到 OUTPUT 後,輸出保持不變。切換 ready,再按「取樣一拍」才能記錄交付。設定 ready 本身不會交付。這個握手是另外定義的教學介面。

遷移練習:如果 downstream 尚未 ready,RTL 能否清除 output_valid 或覆寫結果?先用一句話寫出保持規則:除非 reset 取消交易,valid 與資料必須保持到交握成立。SVA 語法可作後續練習。

FIPS 180-4 §5 / §6.2

學習指南

密碼演算法 RTL 設計

0 / 2

查看課程大綱 → · 進度只計入已發布課程

先備知識

  • 同步暫存器、位元運算與基本 Verilog

我學會了什麼

  • 區分訊息整理層與區塊核心
  • 建立並驗證 16-word 排程
  • 協調 64 輪、feed-forward 與跨區塊累積
  • 明確定義拍數、背壓與 reset

本課術語

查看術語字典 →

延伸閱讀

課後小測驗

1. 56-byte 訊息經 SHA-256 padding 後需要幾塊?
2. K[t] 是什麼?
3. 第二塊的工作暫存器從哪裡載入?
4. 本篇 digest_valid 何時成立?
5. 第 t 輪使用哪個 word?

讀到這裡,辛苦了。

把概念帶走,比把術語背走更重要。

#SHA-2#SHA-256#RTL#數位IC#硬體架構#雜湊