Source code for pyecsca.ec.scalar

"""Provides functions for computing various scalar representations (like NAF, or different bases)."""
from typing import List, Tuple, Literal
from itertools import dropwhile
from public import public


[docs] @public def convert_base(i: int, base: int) -> List[int]: """ Convert an integer to base. :param i: The scalar. :param base: The base. :return: The resulting digit list (little-endian). """ if i == 0: return [0] res = [] while i: i, r = divmod(i, base) res.append(r) return res
[docs] @public def sliding_window_ltr(i: int, w: int) -> List[int]: """ Compute the sliding-window left-to-right form. From [BBG+17]_. :param i: The scalar. :param w: The width. :return: The sliding-window LTR form. """ result: List[int] = [] b = i.bit_length() - 1 while b >= 0: val = i & (1 << b) if not val: result.append(0) b -= 1 else: u = 0 for v in range(1, w + 1): if b + 1 < v: break mask = ((2**v) - 1) << (b - v + 1) c = (i & mask) >> (b - v + 1) if c & 1: u = c k = u.bit_length() result.extend([0] * (k - 1)) result.append(u) b -= k return list(dropwhile(lambda x: x == 0, result))
[docs] @public def sliding_window_rtl(i: int, w: int) -> List[int]: """ Compute the sliding-window right-to-left form. From [BBG+17]_. :param i: The scalar. :param w: The width. :return: The sliding-window RTL form. """ result: List[int] = [] while i >= 1: val = i & 1 if not val: result = [0] + result i >>= 1 else: window = i & ((2**w) - 1) result = ([0] * (w - 1)) + [window] + result i >>= w return list(dropwhile(lambda x: x == 0, result))
[docs] @public def wnaf(k: int, w: int) -> List[int]: """ Compute width `w` NAF (Non-Adjacent Form) of the scalar `k`. Algorithm 9.35 from [GECC]_, Algorithm 9.20 from [HEHCC]_. .. note:: According to HEHCC this is actually not unique A left-to-right variant to compute an NAFw expansion of an integer can be found both in [AVA 2005a] and in [MUST 2005]. The result may differ from the expansion produced by Algorithm 9.20 but they have the same digit set and the same optimal weight. According to GECC it is. :param k: The scalar. :param w: The width. :return: The NAF. """ half_width = 2 ** (w - 1) full_width = half_width * 2 def mods(val: int) -> int: val_mod = val % full_width if val_mod > half_width: val_mod -= full_width return val_mod result: List[int] = [] while k >= 1: if k & 1: k_val = mods(k) result.insert(0, k_val) k -= k_val else: result.insert(0, 0) k >>= 1 return result
[docs] @public def naf(k: int) -> List[int]: """ Compute the NAF (Non-Adjacent Form) of the scalar `k`. :param k: The scalar. :return: The NAF. """ return wnaf(k, 2)
[docs] @public def booth(k: int) -> List[int]: """ Original Booth binary recoding, from [B51]_. :param k: The scalar to recode. :return: The recoded list of digits (0, 1, -1), little-endian. """ res = [] for i in range(k.bit_length()): a_i = (k >> i) & 1 b_i = (k >> (i + 1)) & 1 res.append(a_i - b_i) res.insert(0, -(k & 1)) return res
[docs] @public def booth_word(digit: int, w: int) -> int: """ Modified Booth recoding, from [M61]_ and BoringSSL NIST impl. Needs `w+1` bits of scalar in digit, but the one bit is overlapping (window size is `w`). :param digit: :param w: :return: """ if digit.bit_length() > (w + 1): raise ValueError("Invalid digit, cannot be larger than w + 1 bits.") s = ~((digit >> w) - 1) d = (1 << (w + 1)) - digit - 1 d = (d & s) | (digit & ~s) d = (d >> 1) + (d & 1) return -d if s else d
[docs] @public def booth_window(k: int, w: int, blen: int) -> List[int]: """ Recode a whole scalar using Booth recoding as in BoringSSL. :param k: The scalar. :param w: The window size. :param blen: The bit-length of the group. :return: The big-endian recoding """ mask = (1 << (w + 1)) - 1 res = [] for i in range(blen + (w - (blen % w) - 1), -1, -w): if i >= w: d = (k >> (i - w)) & mask else: d = (k << (w - i)) & mask res.append(booth_word(d, w)) return res