博客 / 詳情

返回

銅鎖 SM2 算法性能優化實踐(二)|快速模約減算法實現

本文是對 《銅鎖 SM2 算法性能優化實踐(一)|綜述》 的深入擴展,詳細介紹快速模約減算法實現
原文鏈接:https://www.yuque.com/tsdoc/ts/pacggl7s29yzsm19

背景

在 SM2 數字簽名算法中,有限域運算是橢圓曲線點運算和數字簽名運算的基礎,其運算性能將直接影響整體性能表現。而在有限域運算中,模約減運算的整體使用頻率最高,在模乘、模平方、模逆等運算中均有運用。以模乘法為例,其定義為:對於素數域參數 p 和乘數 a,模乘運算的結果等於 a*a mod p。也就是説,對於乘法結果大於 p 的中間值,模乘法需要將其約減到 p 以內,此過程因此被稱為模約減

由於 p 是一個非常大的素數,且上述運算中間值最大可達 p2-1,採用除法來計算模約減的效率將極其低下 在具體實踐中,有限域除法一般藉助乘法逆元實現,其耗時大約是模乘運算的 200-300 倍) 。目前,銅鎖項目中 SM2 算法使用了通用蒙哥馬利約分算法實現模約減過程,暫時沒有針對曲線特化的快速模約減算法實現。相應的,部分常用的 NIST 曲線 nistp224nistp256 和 nistp521 基於廣域梅森素數實現了曲線特化的快速模約減算法,並運用於各自的 64-bit 平台優化中。

考慮到 SM2 曲線同樣採用廣域梅森素數作為素數域參數,且實現 64-bit 平台優化也需要使用模約減算法,因此為 SM2 曲線實現曲線參數特化的快速模約減算法是非常有必要的。同時,為了保證對部分 32 位平台的兼容性和後續擴展,在功能開發的過程中保留了原有的條件編譯模式,同時支持 32 位和 64 位平台使用該快速模約減算法。測試結果表明,使用了快速模約減的 SM2 模乘運算與簡單模乘和蒙哥馬利模乘相比,時間開銷分別降低 81.34% 和 29.91%

預備知識

在數論中,梅森素數 (Mersenne Prime) 是形如 2n-1 的質數,其中 n 為整數。梅森素數的數量非常稀少,直到今天 (2023 年 8 月) 僅有 51 個梅森素數被發現。由於梅森素數的構成與 2 的指數冪相關,在以二進制為數據表示方式的現代計算機中,它不僅便於計算和存儲,也能夠加速有限域運算,因此在密碼學領域廣受青睞。NIST 曲線家族中,nistp521 曲線的素數域參數 p 就選擇了梅森素數 2521-1。

然而,梅森素數的數量實在是過於稀少,以至於在橢圓曲線素數域參數 p 的合理取值範圍內僅有一個梅森素數 2521-1,其他的梅森素數要麼過大,要麼太小,不滿足使用要求。因此,許多橢圓曲線的素數域參數 p 選用了梅森素數的拓展版本——廣義梅森素數 (General Mersenne Prime) 。廣義梅森素數是形如 f(2n) 的素數,其中 f(x) 是具有較小整數係數的低次多項式。例如,nistp256 曲線採用如下所示的廣義梅森素數作為素數域參數 p:

圖片

算法原理

在國標 GM/T 0003.5-2012 中,給出了 SM2 曲線的推薦素數域參數 p。p 是一個廣義梅森素數,表示如下:

圖片

下面以大整數 v(v<p2<2512) 和參數 psm2p256 為例,推導基於廣義梅森素數的快速模約減算法。該算法利用參數 psm2p256 各項係數之間最小差為 232 的特點,將表示中間值的大整數按照 32 位分割,並轉化為如下表示形式:

圖片

然後將 v[8]-v[15] 的高位參數約化到 v[0]-v[7]  的低位參數上。在約化之前,我們先根據將參數 psm2p256 轉化為按照 32 位分割的表示形式:

圖片

接下來處理所有高位參數,此處以 v[8] 為例,設 v[8]=a,那麼 v[8] 可以表示為:

圖片

注意到,根據模運算的運算規則:

圖片

也就是説,在原數的基礎上增減模數 p 的倍數並不影響模運算的結果。那麼,為了消去高位參數 a,不妨減去一個 a*p,此時有:

圖片

上下相減,高位參數 v[8] 被成功消除,低位約化的結果為:

圖片

依次對 v[8]-v[15] 做低位約化即可消除所有高位參數。將所有的低位約化結果合併同類項,可得到如下的 SM2 快速模約減算法。該算法由白國強[1]等人於 2014 年首次提出,實際運用中也有多種不同的參數組合形式:

圖片

圖1:基於參數 psm2p256 的快速模約減算法

最後對約化後的低位參數 v[0]-v[7] 再做一次約減,旨在將模約減結果規約到 [0,p)範圍內。
此處最多需要減去 13p 即可得到最終結果,無需使用成本昂貴的模除法。從圖1中不難看出,基於廣義梅森素數的 SM2 快速模約減算法僅需要少量的有限域加法和減法即可實現,性能表現明顯優於使用模逆元的模約減算法。

算法實現

本節將介紹所實現算法的核心部分,完整算法實現請參見 crypto/bn/bn_sm2.c

前文提到,在高位參數約化後,還需要對低位參數做約化以獲得最終結果,也就是減去參數 p 的某個倍數 np(0≤p≤13)。為了進一步提高性能,在代碼實現中我們構造一個預計算表以存儲 1-13p,以節約計算 np 的開銷。又因為約減結果在 【0,p) 範圍內,且
image.png
,因此只需要保留預計算參數的低 256 位:

/*
 * /* Pre-computed tables are "carry-less" values of modulus*(i+1),
 * all values are in little-endian format.
 */
static const BN_ULONG _sm2_p_256[][BN_SM2_256_TOP] = {
    {0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFF00000000ull,
     0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFEFFFFFFFFull},/*p*/
    {0xFFFFFFFFFFFFFFFEull, 0xFFFFFFFE00000001ull,
     0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFDFFFFFFFFull},/*2p*/
    ...
    {0xFFFFFFFFFFFFFFF4ull, 0xFFFFFFF40000000Bull,
     0xFFFFFFFFFFFFFFFFull, 0xFFFFFFF3FFFFFFFFull},/*12p*/
    {0xFFFFFFFFFFFFFFF3ull, 0xFFFFFFF30000000Cull,
     0xFFFFFFFFFFFFFFFFull, 0xFFFFFFF2FFFFFFFFull}/*13p*/
};

同時,為了保證被約減數 v 滿足 0≤v<p2 ,在模約減前還需要對輸入進行驗證。基於同樣的原因,我們還需要預計算 p2 的值:

/* pre-compute the value of p^2 check if the input satisfies input < p^2. */
static const BN_ULONG _sm2_p_256_sqr[] = {
    0x0000000000000001ULL, 0x00000001FFFFFFFEULL,
    0xFFFFFFFE00000001ULL, 0x0000000200000000ULL,
    0xFFFFFFFDFFFFFFFEULL, 0xFFFFFFFE00000003ULL,
    0xFFFFFFFFFFFFFFFFULL, 0xFFFFFFFE00000000ULL
};

在 64 位平台和部分支持 64 位無符號整型的 32 位平台上,由於 v[i] 滿足 0≤v[i]<232,因此可以使用 64 位無符號整形變量 accumulator 作為中間值存儲累加結果,從而在 v[0]-v[7] 參數上直接累加高位參數約化結果。以 v[0] 為例,其相應代碼如下:

        SM2_INT64 acc;         /* accumulator */

        acc = rp[0];
        acc += bp[8 - 8];
        acc += bp[9 - 8];
        acc += bp[10 - 8];
        acc += bp[11 - 8];
        acc += bp[12 - 8];
        acc += bp[13 - 8];
        acc += bp[14 - 8];
        acc += bp[15 - 8];
        acc += bp[13 - 8];
        acc += bp[14 - 8];
        acc += bp[15 - 8];    /*累加高位參數約化結果*/
        rp[0] = (unsigned int)acc; /*獲得結果的低32位*/
        acc >>= 32; /*進位部分右移32位,作為v[1]輸入的一部分*/

對於部分不支持 64 位無符號整型的 32 位平台,32 位整形變量由於數據溢出無法作為中間值保留進位結果,上述代碼將不能正確運行。此時需要藉助現有工具函數和銅鎖底層大數運算函數的支持,按照前文展示的算法逐一累加 s1~s14,每次還需要記錄進位 carry

        /*
         * r_d += s2 + s6 + s7 + s8 + s9
         * s2 = (c15, c14, c13, c12, c11, 0, c9, c8)
         */
        sm2_set_256(t_d, buf.bn, 15, 14, 13, 12, 11, 0, 9, 8);
        carry += (int)bn_add_words(r_d, r_d, t_d, BN_SM2_256_TOP);
        /*
         * s6 = (c11, c11, c10, c15, c14, 0, c13, c12)
         */
        sm2_set_256(t_d, buf.bn, 11, 11, 10, 15, 14, 0, 13, 12);
        carry += (int)bn_add_words(r_d, r_d, t_d, BN_SM2_256_TOP);
        /*
         * s7 = (c10, c15, c14, c13, c12, 0, c11, c10)
         */
        sm2_set_256(t_d, buf.bn, 10, 15, 14, 13, 12, 0, 11, 10);
        carry += (int)bn_add_words(r_d, r_d, t_d, BN_SM2_256_TOP);
        /*
         * s8 = (c9, 0, 0, c9, c8, 0, c10, c9)
         */
        sm2_set_256(t_d, buf.bn, 9, 0, 0, 9, 8, 0, 10, 9);
        carry += (int)bn_add_words(r_d, r_d, t_d, BN_SM2_256_TOP);
        /*
         * s9 = (c8, 0, 0, 0, c15, 0, c12, c11)
         */
        sm2_set_256(t_d, buf.bn, 8, 0, 0, 0, 15, 0, 12, 11);
        carry += (int)bn_add_words(r_d, r_d, t_d, BN_SM2_256_TOP);

最後處理進位,得到最終結果,此處變量 mask 的作用是減少代碼中不必要的分支,儘可能提高其抗側信道攻擊強度:

    mask =
        0 - (PTR_SIZE_INT) (*u.f) (c_d, r_d, _sm2_p_256[0], BN_SM2_256_TOP);
    mask &= 0 - (PTR_SIZE_INT) carry;
    res = c_d;
    res = (BN_ULONG *)(((PTR_SIZE_INT) res & ~mask) |
                       ((PTR_SIZE_INT) r_d & mask));
    sm2_cp_bn(r_d, res, BN_SM2_256_TOP);
    r->top = BN_SM2_256_TOP;
    bn_correct_top(r);

測試

正確性測試

為了驗證快速模約減算法的正確性,我們在測試環節設計了相應的單元測試。該測試的核心思路是,隨機生成一批有限域大整數 ai,bi,分別使用銅鎖提供的默認方法和本文實現的快速模約減方法計算 ai*bi mod p,然後比較二者計算結果是否相等:

    /*
     * Randomly generate two numbers in the prime field and multiply them,then compare the results of fast modular reduction and conventional algorithms.
     */
    for (i = 0; i < 100; i++){
        if (!TEST_true(BN_priv_rand_range_ex(a, p, 0, ctx))
            || !TEST_true(BN_priv_rand_range_ex(b, p, 0, ctx))
            || !TEST_true(BN_mul(ab_fast, a, b, ctx))
            || !TEST_true(BN_sm2_mod_256(ab_fast, ab_fast, p, ctx))
            || !TEST_true(BN_mod_mul(ab, a, b, p, ctx))) {
            goto done;
        }

        if (!TEST_int_eq(BN_ucmp(ab, ab_fast), 0)){
            goto done;
        }
    }

可以使用如下命令調用測試:

make test TESTS="test_mod_sm2"

測試結果:

➜  Tongsuo git:(sm2p256) ✗ make test TESTS="test_mod_sm2"
/Library/Developer/CommandLineTools/usr/bin/make depend && /Library/Developer/CommandLineTools/usr/bin/make _tests
10-test_mod_sm2.t .. ok   
All tests successful.
Files=1, Tests=1,  1 wallclock secs ( 0.00 usr  0.01 sys +  0.07 cusr  0.02 csys =  0.10 CPU)
Result: PASS

性能測試

為了充分比較快速模約減算法的性能,我們選擇比較 SM2 有限域運算中較常用的模乘法運算作為測試單元,測試使用了 SM2 快速模約減函數 BN_sm2_mod_256、簡單模約減函數 BN_nnmod 的模乘函數 ossl_ec_GFp_field_mul 和通用蒙哥馬利模乘函數 BN_mod_mul_montgomery 的模乘法運算耗時。測試在 Apple M2 pro 平台上進行,測試數據如下表所示:

圖片

測試結果表明,使用 SM2 快速模約減函數的模乘法耗時最少,相較於簡單模約減能夠減少 81.34% 的時間開銷,即使對使用了彙編優化的蒙哥馬利模乘運算,在計算時間上仍有 29.91% 的優勢。

下篇 SM2 算法性能優化實踐系列文章我們將介紹“快速模逆元算法實現”,敬請期待!

文中鏈接

[1]白國強等人提出的快速模約減算法:https://ieeexplore.ieee.org/document/7011249

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.