先來上競賽中最常見到的快速冪次寫法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| long long mul(long long a, long long b, long long mod) { long long ret = 0; for( ; b != 0; b>>=1, (a<<=1)%=mod) if(b&1) (ret += a) %= mod; return ret; } long long mpow(long long x, long long y, long long mod) { long long ret = 1; while (y) { if (y&1) ret = mul(ret, x, mod); y >>= 1, x = mul(x, x, mod); } return ret; }
|
這樣的寫法,只運算 64-bits 內的結果相當不錯,與接下來講的算法速度差異並不大。但對於近代加密所需要的離散對數過程,則會有放大速度差異,因為 x, y, mod 三個參數的 bit-length 都相當長。
Right-To-Left
做法就是最普遍見到的。
1 2 3 4 5 6 7 8 9
| void pow(x, a) { R = 1, g = x for i = 0 to n, step 1 if (a[i] == 1) // a[i] mean i-bit in a R = R * g g = g * g return R }
|
1 2 3 4 5 6
| example: a = 26 = (11010) --- #iterator 0 1 2 3 4 #g x, x^2, x^4, x^8, x^16, ... binary 0 1 0 1 1 x^a = x^2 x^8 x^16 = x^26
|
Left-To-Right
1 2 3 4 5 6 7 8
| void pow(x, a) { R = 1 for i = n-1 to 0, step -1 R = R ^ 2 if (a[i] == 1) R = R * x return R; }
|
1 2 3 4 5
| example: a = 26 = (11010) --- #iterator 0 1 2 3 4 binary 1 1 0 1 0 R x^1 x^11 x^110 x^1101 x^11010
|
也就是說,每一次迭代,將會將指數左移 (R = R ^ 2
就是要移動 R^xxxx
變成 R^xxxx0
,切著看是否要乘上 x 來變成 R^xxxx1
),這方法雖然不錯,但是計算高位元空迴圈可是不能忽略!當然對於設計電路的人,固定變量的迴圈展開後就不存在這問題。
效能上,期望的乘法次數與 Right-To-Left 是差不多的!長度為 n bits 的數字,期望值會有 $n/2$ 個 1,因此大約需要 $n/2$ 次乘法。
Left-To-Right(2-bits)
1 2 3 4 5 6 7 8 9 10
| void pow(x, a) { pre-computation: x^2, x^3 R = 1 for i = n/2 to 0, step -2 R = R ^ 4 if (a[i+1]a[i] != 0) R = R * x^(a[i+1]a[i]) return R }
|
1 2 3 4 5 6
| example: a = 1737 = (01 10 11 00 10 01) --- #iterator 0 1 2 3 4 5 R x^1 x^4 x^24 x^108 x^432 x^1736 x^6 x^27 x^434 x^1737 a[i+1]a[i] 01 10 11 00 10 01
|
長度為 n bits 的數字,倆倆一起期望值會有 $3n/8$ 個非 0 (00, 01, 10, 11 中 4 個有 3 個 $!= 0$,計算組數只有 $n/2$),因此大約需要 $3n/8$ 次乘法。比剛剛快上一點呢 ( $3n/8 < n/2$ )!
Left-To-Right(sliding)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void pow(x, a) { pre-computation: x^3 R = 1 for i = n-1 to 0 if a[i]a[i-1] = (11) R = R ^ 4 R = R * x^3 i -= 2 else if a[i]a[i-1] = (00) R = R ^ 4 i -= 2 else if a[i] = (0) R = R ^ 2 i -= 1 else if a[i] = (1) R = R ^ 2 R = R * x i -= 1 return R }
|
1 2 3 4 5
| example: a = (1 10 11 00 10 01) --- #iterator 0 1 2 3 4 5 6 R x^11 x^11011 x^11011001 x^11011001001 x^110 x^1101100 x^1101100100
|
期望的乘法次數 $n/3$,這很可怕,別問我!
Summary
Algorithm Name |
Table Size |
#squaring |
Average #Multiplication |
Right-To-Left |
1: $x^{2^i}$ |
$n$ |
$n/2$ |
Left-To-Right |
1: $x$ |
$n$ |
$n/2$ |
Left-To-Right(2-bits) |
3: $x$, $x^2$, $x^3$ |
$n$ |
$3n/8$ |
Left-To-Right(sliding) |
2: $x$, $x^3$ |
$n$ |
$n/3$ |
Attack
上述四種方法中,不外乎都存在 Conditional Jump 出現,會導致執行速度跟程式計數器 (Program Counter) 移動上會有所差別。可以進行實作攻擊,從分析時間、電源不穩攻擊,來找到 $a$ 究竟是何物 (通常要偷的都是 $x^a$ 上面的 $a$)!電源雜訊干擾於當指令執行到 Conditional Jump 時,干擾 $R$ 的計算發生錯誤,如果沒有發生錯誤,表示 $a \left [ i \right ] = 0$,反之就能知道 $a \left [ i \right ] = 1$。
很趣味地是提供一個不需要 Conditional Jump 的寫法 (實作上),一樣會有這種問題!
error example
1 2 3 4 5
| R[1] = 1 for i = n-1 to 0, step -1 R[1] = R[1] ^ 2 R[a[i]] = R[1] × g return R[1]
|
提供一個不相干的垃圾暫存器 (redundancy register) 來減少 Jump 的問題,很明顯地 電源雜訊干擾 的攻擊仍然存在!這作法很有趣的啊!只是會被攻擊。
Montgomery Ladder
1 2 3 4 5
| R[0] = 1, R[1] = g for i = n-1 to 0, step -1 R[1-a[i]] = R[0] × R[1] R[a[i] = R[a[i]] × R[a[i]] return R[0]
|
這作法避開之前的問題,解決其中一種被攻擊的方案。
過程中都滿足 $R \left [ 0 \right ] = R \left [ 1 \right ] \times g$,同時 $R \left [ 0 \right ]$ 會是最後的答案。
1 2 3 4 5 6
| example: a = 26 = (11010) --- #iterator 0 1 2 3 4 5 binary 0 1 1 0 1 0 R[0] 1 x x^3 x^6 x^13 x^26 R[1] x x^2 x^4 x^7 x^14
|
明顯地,$R \left [ 1 \right ]$ 的計算是為了下一次為 $a \left [ i+1 \right ] = 1$ 而存在的!