iT邦幫忙

2025 iThome 鐵人賽

DAY 2
0

本日重點:實作: toByte, ADRS member function

SLH-DSA 的簽章需要用到金鑰,金鑰的產生需要用到公私鑰種子,這些 seed 需要透過 approved random bit generator (RBG) 來獲得,我將用3天的篇幅來說明如何 產生 SLH-DSA 金鑰,以及如何實現跨平台。

approved 在 FIPS 205 具有很重要的意義, 他代表 FIPS-approved and/or NIST-recommended, 如果直接用中文, 恐怕難以表達他的意義, 所以本文直接用 approved

在我們開始實現 SLH-DSA 金鑰產生 之前, 有必要先澄清一件事 ---- 能不能直接用 Secure Element (SE) 產生 SLH-DSA 金鑰?

不行,至少截至 2025-09-14 還不行。

SE 普遍支援 產生RSA金鑰,但截至 2025-09-14,市售的 SE 還不支援 產生SLH-DSA 金鑰。所以,還不能 直接從 SE 獲得 SLH-DSA 金鑰,我們只能在 SE 外部去產生 SLH-DSA 金鑰。所謂的外部,可以是 MCU (例如 M33 或 M4),也可以是 FPGA,也可以是 SoC,甚至是自己設計的 PCB。一旦未來 SE 支援產生SLH-DSA 金鑰,就能直接用 SE 產生 SLH-DSA 金鑰

雖然 SE 不能直接產生 SLH-DSA key pair,但 SE 仍然有它的作用,幾天後我將對此做更進一步的探討,今天不會對 SE 有過多的著墨。

FIPS 205 第 34 頁的 Algorithm 18 slh_keygen_internal 是產生 SLH-DSA key pair 的演算法,如下所示

Algorithm 18 slh_keygen_internal(SK.seed, SK.prf, PK.seed)
Generates an SLH-DSA key pair.
Input: Secret seed SK.seed, PRF key SK.prf, public seed PK.seed Output: SLH-DSA key pair (SK, PK).
1: ADRS ← toByte(0, 32)        ▷ generate the public key for the top-level XMSS tree
2: ADRS.setLayerAddress(𝑑 −1)
3: PK.root ← xmss_node(SK.seed, 0, ℎ′,PK.seed,ADRS)
4: return ((SK.seed, SK.prf, PK.seed, PK.root), (PK.seed, PK.root) )

Day 2、Day 3、Day 4 將基於此演算法,參考 SPHINCS+ reference code,然後寫 toBytexmss_node 和 ADRS 的 member function setLayerAddress

基於跨平台的考量,random bit generator (RBG) 需要一個抽象層,好處是如果想更換底層的亂數產生器或是更換平台,可以不用修改 slh_keygen_internal,只需要提供不同的 HAL, 就能在不改程式的情況下直接更換亂數產生器,例如從軟體版換成硬體版(Arm 的 CryptoCell-310 或 CryptoCell-312, 或是其他),而且還有一個好處,就是在 CryptoCell 導入前就能用 軟體版的亂數產生器 先完成 SLH-DSA key pair 的產生,然後等到整個簽章都完成了,再改用 CryptoCell-310 或 CryptoCell-312 來重新一次這個流程 "產生亂數 -> 產生 key pair",這些會在 Day 4 詳述。

slh_keygen_internal 用來產生 key pair,它的演算法在 FIPS 205 的第 34 頁,這裡要寫的有 toBytexmss_node 以及 setLayerAddress,以下 breakdown slh_keygen_internal 還有哪些 function 要寫

xmss_node 的演算法在 FIPS 205 的第 23 頁,這裡要寫的有 wots_pkGen、setTypeAndClear、setKeyPairAddress、setTreeHeight、setTreeIndex、H

wots_pkGen 的演算法在 FIPS 205 的第 18 頁,這裡要寫的還有 function 有 setTypeAndClear、setKeyPairAddress、getKeyPairAddress、setChainAddress、PRF、chain、T

chain 的演算法在 FIPS 205 的第 18 頁,這裡要寫的 function 有 setHashAddress、F

H、T、F 這些 function name 只有一個字,Day 3、Day4 會陸續說明

至此,盤點一下有什麼 function 要寫:

  1. toByte
  2. setLayerAddress
  3. setTypeAndClear
  4. setKeyPairAddress
  5. getKeyPairAddress
  6. setTreeHeight
  7. setTreeIndex
  8. setChainAddress
  9. setHashAddress
  10. F
  11. xmss_node
  12. wots_pkGen
  13. H
  14. PRF
  15. chain
  16. T

上述的 2 到 9 是 ADRS 的 member function,由於 C11 沒有 member function 的機制,如果在 struct 裡放 function pointer 模擬 call 物件的 method, 也是一種方法,不過這樣仍然要放一個類似 this 的 pointer 做為參數,還不如直接用 helper function + pointer,所以,上述的 2 到 9 會用 helper function 的方式去實現。

ADRS 在上述 16 個 function 都會用到,FIPS 205 的第 12 頁是 ADRS 的格式,它在記憶體佔了 32 bytes,所以我們用 unsigned char[32] 來表示 ADRS。

這是 ADRS 的結構, page 12, FIPS 205
https://ithelp.ithome.com.tw/upload/images/20250915/20140129AajUcAmzFz.png

我們己經將 ADRS 定義為 unsigned char[32],layer address 佔了 4 個 byte,tree address 佔了 12 個 bytes,要對他們賦值,直接用 memcpy 就可以了,不過,layer address、tree address、type 都是一個數字,要完成 n-bytes 的賦值,需要先將數字轉成 byte array,然後才能用 memcpy,將數字轉成 byte array,就要用到 toByte。

舉例來說,如果要指定 layer address,就像這樣

unsigned char S[4];
toByte((unsigned long long)layer, 4, S);
memcpy(adrs, S, 4); // 0, 1, 2, 3

Figure 2 沒有看到 key pair address、chain address 和 hash address,因為 ADRS 的 type 有 7 種: WOTS_HASH、WOTS_PK,
TREE、FORS_TREE、FORS_ROOTS、WOTS_PRF 和 FORS_PRF,每一種 type,前 20 個 bytes 都一樣,由 layer address、tree address 和 tree 所組成,剩下的 12 個 bytes 不完全相同,相關的 member function 將會在本系列的文章中陸續提及。

這是 WOTS+ hash address 的結構 (type = WOTS_HASH),page 12, FIPS 205

https://ithelp.ithome.com.tw/upload/images/20250915/201401295ltt4PYocp.png

key pair address 未必在每一個 type 都有,但如果有,他都是從 index 20 開始,所以如果要指定 key pair address,就像這樣

unsigned char key_pair_addr[4];
toByte(i, 4, key_pair_addr);
// ADRS[20, 21, 22, 23]
memcpy(adrs + 20, key_pair_addr, 4);  // 20, 21, 22, 23

指定 chain address 和 hash address 依此類推。

至此,我們完成了 setLayerAddress、setKeyPairAddress、setChainAddress 和 setHashAddress。這些,都建立在 toByte 能將一個數字轉成 byte array 的基礎上,現在我們來看 toByte 是怎麼轉的。以下是 FIPS 205 第15頁,toByte 的演算法
https://ithelp.ithome.com.tw/upload/images/20250915/20140129y9sVXiigJs.png

注意,這是 big-endian

big-endian 跟 little-endian 在記憶體裡放 array element 是不一樣的順序,舉例來說,同樣都是 1,它在 4 byte array 的 index,會有以下的差異。

這是 little-endian

a[0] = 0x01
a[1] = 0x00
a[2] = 0x00
a[3] = 0x00

這是 big-endian

a[0] = 0x00
a[1] = 0x00
a[2] = 0x00
a[3] = 0x01

回到寫 toByte,total & 0xff 就是取最低的那一個 byte,取完就 assign 給 byte array,接著 total = total >> 8直到 byte array 每一個 element 都被賦值

for (unsigned int i = 0; i < n; ++i) {
    pS[n - 1 - i] = (unsigned char)total & 0xff;

    // total ← total >> 8       ▷ least significant 8 bits of 𝑡𝑜𝑡𝑎𝑙
    total = total >> 8;
}

這個賦值的過程,是從 array[n-1] 開始直到 array[0],因為這是 big-endian

至此,我們完成了 toByte 和 4 個 ADRS 的 member function,Day 3、Day 4 會陸續完成 產生 SLH-DSA 金鑰 所需要的 function。

References

  1. FIPS 205 Stateless Hash-Based Digital Signature Standard
  2. The SPHINCS+ reference code, accompanying the submission to NIST's Post-Quantum Cryptography project

上一篇
Day 1 序篇: 裸機實作 SLH-DSA 簽章的幾個面向
下一篇
Day 3 跨平台產生 SLH-DSA 金鑰 (二)
系列文
裸機實作 SLH-DSA 簽章:FIPS 205 之演算法實作、測試、防禦與跨平台5
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言