Показаны сообщения с ярлыком GFMUL. Показать все сообщения
Показаны сообщения с ярлыком GFMUL. Показать все сообщения

среда, 30 июня 2021 г.

GF2p8 Умножение на аффинных преобразованиях

Пока изучал операции умножения в композитных полях, нашел два варианта аппаратной архитектуры умножителя в поле GF(2m).

Большая штука на рисунке -- умножитель в поле GF(2^4) построен на операциях типа И-ИсключающееИЛИ, каскадно, в два каскада. Из структуры умножителя видно, что в основе лежат аффинные преобразования, точнее такое разложение можно представить тремя аффинными преобразованиями, тремя инструкциями gf2p8affine. На первом каскаде - выполняется распределение и преобразования в две матрицы, выполняются две операции типа аффинного преобразования, на втором - выполняется операция аффинного преобразования и сложение аргументов.

На картинке представлена базовая идея построения умножителя [0,1]. Ничего особенно удивительного тут не вижу, потому что ранее представлял уже алгоритм формирования матрицы аффинного преобразования для операции умножения на константу. Тут будет что-то близкое: на первом каскаде формируется матрица умножения на константу А - матрица аффинного преобразования, второй каскад в чистом виде процессорная инструкция gf2p8affine.

Сразу можно отметить, что операция изоморфного преобразования ради умножения - три каскада, а тут мы видим два каскада - так быстрее должно получаться, потому что две операции аффинного преобразования выполняются параллельно.

Для решения предлагается составить матрицу Тёплица. Выполнить LU разложение. Перемножить матрицу L с вектором b, матрицу U с b. Произвести редуцирование с использованием матрицы Q:

d = L(a)·b
e = U(a)·b
c = L(a)·b + Q·U(a)·b = (L + Q·U)b
 Нижняя и верхняя матрица заполняются битами аргумента (a)
   |a0  0  0  0  0  0  0  0|
   |a1 a0  0  0  0  0  0  0|
   |a2 a1 a0  0  0  0  0  0|
L =|a3 a2 a1 a0  0  0  0  0|
   |a4 a3 a2 a1 a0  0  0  0|
   |a5 a4 a3 a2 a1 a0  0  0|
   |a6 a5 a4 a3 a2 a1 a0  0|
   |a7 a6 a5 a4 a3 a2 a1 a0|

   | 0 a7 a6 a5 a4 a3 a2 a1|
   | 0  0 a7 a1 a2 a3 a4 a5|
   | 0  0  0 a7 a1 a2 a3 a4|
U =| 0  0  0  0 a7 a1 a2 a3|
   | 0  0  0  0  0 a7 a1 a2|
   | 0  0  0  0  0  0 a7 a1|
   | 0  0  0  0  0  0  0 a7|
   | 0  0  0  0  0  0  0  0|

Матрица Тёплица разбивается на две треугольные матрицы L U дополняющие друг друга: нижнюю (L) и верхнюю(U). Вместе они могут быть получены методом циклического сдвига строк. В системе команд Intel AVX512_VBMI есть подходящая инструкция для изготовления сдвиговой матрицы, vpmultishiftqb. Далее сдвиговую матрицу можно разделить на две треугольные с использованием маски.

Да, ладно! Вся эта логика, теоремы и доказательства построены вокруг одного тривиального утверждения: матрица умножения складывется из трех пребразований - это матрица умножения без переноса (младшая часть) плюс редуцированная по образующему полиному старшая часть умножения без переносов. Весь вопрос в том, как составить налету матрицу треугольно-диагональную (матрицу Тёплица) из двоичных разрядов числа A. Матрицы редуцирования мы уже вводили! См. предыдущие сообщения.


S = _mm_set1_epi64x(0x0807060504030201);
m = _mm_multishift_epi64_epi8( S, m);
Результат вращения матрицы приведен в таблице, даны индексы исходной матрицы после применения сдвигов.
  8  7  6  5  4  3  2  1
  9  8  7  6  5  4  3  2
 10  9  8  7  6  5  4  3
 11 10  9  8  7  6  5  4
 12 11 10  9  8  7  6  5
 13 12 11 10  9  8  7  6
 14 13 12 11 10  9  8  7
 15 14 13 12 11 10  9  8
// Алгоритм умножения c = a*b в поле GF(2^8)
// циклические сдвиги
S  = _mm_set1_epi64x(0x0807060504030201);
// маска выделяет нижнюю матрицу L
ML = _mm_set1_epi64x(0x0103070F1F3F7FFF);
// матрица редуцирования для полинома 0x11B
Q  = _mm_set1_epi64x(0xB1D3A6FD4B962C58);

v  = _mm_set1_epi8(a); // размножить аргумент
v  = _mm_multishift_epi64_epi8(S, v); // циклические сдвиги
// младшая часть умножения без переносов
lo = _mm_gf2p8affine_epi64_epi8(_mm_and_si128(ML, v), b, 0);
// старшая часть умножения без переносов
hi = _mm_gf2p8affine_epi64_epi8(_mm_andnot_si128(ML, v), b, 0); 
// Q - матрица редуцирования по образующему полиному поля
hi = _mm_gf2p8affine_epi64_epi8(hi, Q, 0); 
return hi ^ lo;

Код не отлаживал. Вероятно надо транспонировать или отразить некторые матрицы.

Почему остановился? Начал составлять алгоритм с другого конца. С этого мне сложно отладить. У нас есть готовые тесты на матрицу умножения на константу. Так вот, один из вариантов получения матрицы редуцирования Q - это вычислить матрицу умножения на специальную константу примерно так, отбросив старший бит:

Poly = 0x11B;
Q = m_mulC(Poly&0xFF, Poly);

Если отбросить старший бит, то это будет корень образующего полинома. Второй вывод: матрицы Тёплица я не хочу использовать, более подходящий термин - циркулянты. Мы вводили функцию генерации циркулянта - сдвиговой матрицы. Таким образом LU разложение дается через матрицу - циркулянт. Я по шагам показываю, как я синтезировал алгоритм..


const uint64_t mask = 0x0103070F1F3F7FFF;
C = m_circulant(a);
L = C &  mask;
U = C & ~mask;

Надо заметить, что матрица L - это матрица малдщей части умножения без переносов на константу (a), а матрица U - матрица старшей части. В результате получаем референсный алгоритм умножения:

// Алгоритм умножения через аффинные преобразования
uint8_t gf2p8_mul_affine(uint8_t a, uint8_t b, uint32_t Poly)
{
    const uint64_t mask = 0x0103070F1F3F7FFF;
	uint64_t Q = m_mulC(Poly&0xFF, Poly);
    uint64_t C = m_circulant(a);
    uint64_t L = C &  mask;
    uint64_t U = C & ~mask;
    return affine(L, b) ^ affine(Q, affine(U, b));
}

В качестве теста сравниваем вывод функции gf2p8_mul_affine с выводом функции умнжения gf2p8_mul, для любых a и b. Результат совпадает!

Матрицы редуцирования в контексте задачи - константы, для каждого полинома поля - своя.

Таблица матриц редуцирования (фрагмент)
0x11B: Q=0xB1D3A6FD4B962C58
0x11D: Q=0x71E2B51B478E1C38
0x165: Q=0xDDBAA952A495F7EE
0x171: Q=0x8D1A34685D37E3C6
0x177: Q=0x4DD7E3C6C1CFD3A6
0x1C3: Q=0x5BEDDAB468D0FBAD
0x1F5: Q=0x2346AF5E9F1D1911

При переходе к векторным инструкциям GFNI применим алгоритм составления циркулянта


const __m128i S  = _mm_set1_epi64x(0x0807060504030201);
const __m128i E  = _mm_set1_epi64x(0x0102040810204080);
__m128i m_circulant(uint8_t a) {
    v  = _mm_set1_epi8(a); // размножить аргумент
    v  = _mm_multishift_epi64_epi8(S, v); // циклические сдвиги
    return _mm_gf2p8affine_epi64_epi8(E, v, 0);// транспонирование
}
// Алгоритм умножения c = a*b в поле GF(2^8)
// маска выделяет нижнюю матрицу L
ML = _mm_set1_epi64x(0x0103070F1F3F7FFF);
v  = m_circulant(a);
// младшая часть умножения без переносов
lo = _mm_gf2p8affine_epi64_epi8(b, _mm_and_si128(ML, v), 0);
// старшая часть умножения без переносов
hi = _mm_gf2p8affine_epi64_epi8(b, _mm_andnot_si128(ML, v), 0); 
// Q - матрица редуцирования по образующему полиному поля
hi = _mm_gf2p8affine_epi64_epi8(hi, Q, 0); 
return hi ^ lo;

Следующий шаг - оптимизация. Мы могли бы объединить матрицы в одну используя алгоритм перемножения матриц.


__m128i m_mul(__m128i A, __m128i B) {
	B = _mm_gf2p8affine_epi64_epi8(E, B, 0);
	A = _mm_gf2p8affine_epi64_epi8(A, B, 0);
	return A;
}
// Алгоритм синтеза Матрицы умножения на константу
__m128i m_mulC(uint8_t a, __m128i Q)
{
	const __m128i ML = _mm_set1_epi64x(0x0103070F1F3F7FFF);
	__m128i v  = m_circulant(a);
	return  _mm_and_si128(ML, v) ^ m_mul(Q, _mm_andnot_si128(ML, v));
}

Количество аффинных преобразований можно сократить до двух, выполняемых параллельно.

// Матрица умножения на константу (a) в поле GF(2^8)
__m128i m_mulC(uint8_t a, __m128i Q)
{
    const __m128i MLT = _mm_set1_epi64x(0xFFFEFCF8F0E0C080);
    __m128i v  = _mm_set1_epi8(a); // размножить аргумент
    v  = _mm_multishift_epi64_epi8(S, v); // циклические сдвиги
    return  _mm_gf2p8affine_epi64_epi8(E, _mm_and_si128(MLT, v), 0) 
       ^ _mm_gf2p8affine_epi64_epi8(Q, _mm_andnot_si128(MLT, v), 0);
}

MLT - транспонированная маска для нижней матрицы L. Надо протестировать. В качестве теста, сверяю все три реализации функции m_mulC. Результат совпадает!

Цель достигнута, мы научились эффективно синтезировать матрицу умножения на константу в произвольном поле GF(2^8) без необходимости выполнения изоморфных преобразований. Алгоритм использует каскад из двух инструкций аффинного преобразования. Для каждого поля используется своя матрица редуцирования (Q).

Есть еще несколько тождественных преобразований с матрицами LU, которые могут ускорить алгоритм при условии, что матрица редуцирования Q-константа.

// вариант 2 завершения алгоритма m_mulC
    return  _mm_gf2p8affine_epi64_epi8(E^Q, _mm_and_si128(MLT, v), 0) 
       ^ _mm_gf2p8affine_epi64_epi8(Q, v, 0);
// вариант 3 завершения алгоритма m_mulC
    return  _mm_gf2p8affine_epi64_epi8(E, v, 0) 
       ^ _mm_gf2p8affine_epi64_epi8(Q^E, _mm_andnot_si128(MLT, v), 0);

Эти преобразования можно выразить математическими формулами:
M = L(a)+Q·U(a) = (L(a) + U(a)) + (Q−E)·U(a) = C(a) + (Q−E)·U(a)
M = L(a)+Q·U(a) = (L(a) − Q·L(a)) + Q·(L(a)+U(a)) = (E−Q)·L(a) + (Q)·C(a)

Сформулировал вариант алгоритма, при котором возможно параллельное исполнение инструкций аффинного преобразования. Чтобы избавиться от масок, ввожу две параллельные инструкции циклического сдвига "multishift", матрицы LU расчитываются параллельно.

// Матрица умножения на константу (a) в поле GF(2^8)
__m128i m_mulC_bp(uint8_t a, __m128i Q)
{
    const __m128i SU = _mm_set1_epi64x(0x0807060504030201);
    const __m128i SL = _mm_set1_epi64x(0x00FFFEFDFCFBFAF9);

    __m128i v  = _mm_set1_epi64x(a);
    __m128i L  = _mm_multishift_epi64_epi8(SL, v); // циклические сдвиги
    __m128i U  = _mm_multishift_epi64_epi8(SU, v); // циклические сдвиги
    return  _mm_gf2p8affine_epi64_epi8(E, L, 0)
          ^ _mm_gf2p8affine_epi64_epi8(Q, U, 0);
}

Доказательсвто оптимальности кода. Хочу показать, как выглядит оптимизация кода. Для этого вывожу ассемблерный код и симулирую загрузки ядра процессора полученного кода с использованием анализатора производительности машинных кодов (llvm-mca).

 $ gcc -O3 -S -o mul_c.s mul_c.c 
 $ llvm-mca.exe --mcpu=icelake-server -timeline mul_c.s
/* Ассемблерный код */
   vpmultishiftqb  %xmm2, %xmm4, %xmm3
   vpmultishiftqb  %xmm2, %xmm5, %xmm2
   vgf2p8affineqb  $0, %xmm3, %xmm0, %xmm0
   vgf2p8affineqb  $0, %xmm2, %xmm1, %xmm1
Временная диаграмма загрузки ядра процессора
[0,0]     DeER .    . vpmultishiftqb      %xmm2, %xmm4, %xmm3
[0,1]     DeER .    . vpmultishiftqb      %xmm2, %xmm5, %xmm2
[0,2]     D=eER.    . vgf2p8affineqb      $0, %xmm3, %xmm0, %xmm0
[0,3]     D=eER.    . vgf2p8affineqb      $0, %xmm2, %xmm1, %xmm1
[1,0]     D==eER    . vpmultishiftqb      %xmm2, %xmm4, %xmm3
[1,1]     D==eER    . vpmultishiftqb      %xmm2, %xmm5, %xmm2
[1,2]     .D==eER   . vgf2p8affineqb      $0, %xmm3, %xmm0, %xmm0
[1,3]     .D==eER   . vgf2p8affineqb      $0, %xmm2, %xmm1, %xmm1

Во временной диаграмме показана загрузка и стадии обработки инструкций (D-декодирование, e-исполнение, R-запись в регистр). Из диаграммы видно, что инструкции могут выполняться параллельно. Производительность достигает два такта на цикл алгоритма, второй цикл выполняется за два такта. Алгоритм m_mulC_bp "bit parallel" признается готовым к использованию.

[0] E. Mastrovito, “VLSI architectures for computation in Galois fields,”Ph.D. dissertation, Linkoping University, Sweden, 1991
-- не читал, но все ссылаются, как на первоисточник идеи.
[1] Low complexity bit parallel architectures for polynomial basis multiplication over GF(2m).
A. Reyhani-Masoleh, M. Hasan: Mathematics, Computer Science IEEE Transactions on Computers, 2004.

пятница, 18 июня 2021 г.

GF2p8 Аффинные преобразования (ч. 5): блочный шифр ГОСТ "Кузнечик"

Блочный шифр "Кузнечик". Умножение в изоморфном представлении поля GF(2^8)

Весь цикл сообщений посвященный аффинным преобразованиям мы затеяли ради того чтобы представить этот и подобные ему алгоритмы. Ниже мы рассматриваем преобразование алгоритма шифрования ГОСТ Р 34.12-2015 Кузнечик к изоморфному представлению поля. Целью преобразования является ускорение работы за счет использования инструкций Intel GFNI.

Алгоритм зашифровывания выглядит, как последовательное примение частей преобразования: наложение ключа, биективное нелинейное преобразование S-Box, Линейные преобразования. Линейные преобразования можно свести к умножению в поле GF(2^8)/0x1C3. Можно записать линейные преобразование, как умножение вектора состояния (uint8x16_t, 16 байт) на матрицу коэффициентов (16х16 элементов). Алгоритм линейных преобразований (L) выглядит, как умножение матрицы на вектор:

// Линейное преобразование ГОСТ "Кузнечик"
uint8x16_t LMT[16];
// пересчет матрицы линейного преобразования
for(i=0; i<16; i++)
    LMT_iso[i] = _mm_gf2p8affine_epi64_epi8(LMT[i], M, 0);
// пересчет коэффициентов S-Box
for(i=0; i<256; i++)
    sbox_iso[i]= affine_byte(M, sbox[affine_byte(Mr, i)]);
/*! Линейное преобразование
В цикле выполняется умножение с редуцированием по полиному 0x11B */
uint8x16_t LS(uint8x16_t s) {
    uint8x16_t r = {0};
    int i;
    for(i=0; i<16; i++) {
        uint8_t v = sbox_iso[s[i]];
        r ^= _mm_gf2p8mul_epi8(_mm_set1_epi8(v), LMT_iso[i]);
    }
    return r;
}
/*! Зашифровывание */
uint8x16_t kuzn_encrypt(uint8x16_t* K, const uint8x16_t a)
{
    int i;
    a = _mm_gf2p8affine_epi64_epi8(a, M, 0);
    uint8x16_t s = a ^ K[0];
    for (i=0; i<9; i++)
        s = LS(s) ^ K[i+1];
    return _mm_gf2p8affine_epi64_epi8(s, Mr, 0);
}

Получается простое описание. Преобразование констант из матрицы коэффициентов LMT, как и аффинные преобразования S-Box, можно выполнить статически. Попробую алгоритм выразить формулой. Я убежден, что для программистов проще читать код, чем разбираться в формулах. Я применяю формулы там, где запись дает понимание. В формуле видны все места, куда надо вставить прямое и обратное преобразование. В частности, надо выполнить аффинные преобразования ключа K из представления поля 0x1C3 в 0x11B. Если дана функция двух переменных, то следует выполнить преобразования обеих переменных. Для разработчиков важно уяснить, что прямую и обратную матрицу добавляют в формулу в сочетании M-1·M·А, а потом, используя линейные преобразования, "вдвигают" матрицу внутрь формулы: M-1·А·M. Или же применяют к уравнению (слева) одно и тоже линейное преобразование: к левой и правой частям. В аналитических выкладках, надо четко понимать, какая операция в каком представлении поля и на каком полиноме выполняется.

gfmul[0x1C3](K, x) = M-1gfmul[0x11B](M·K, M·x);
sbox[0x1C3](x) = M-1sbox[0x11B](M·x);
sbox[0x11B](x) = M·sbox[0x1C3](M-1x);

Ключи (K) пересчитываются в представление поля 0x11B на этапе развертывания ключа. После подстановки формулы sbox[0x1C3] в качестве аргумента gfmul[0x1C3], произведение матриц M· M-1 = E дает единицу, сокращается:

gfmul[0x1C3](K, x) = M-1gfmul[0x11B](M·K, (M· M-1)sbox[0x11B](M·a));

Благодаря тому, что gfmul[0x1C3] выполняется в цикле, умножение вектора состояния на обратную и прямую матрицу можно вынести из цикла. В итоге в цикле аффинные преобразования не используются, матрицы изоморфного преобразования применяются только ко входным и выходным данным алгоритма зашифровывания, в то время как умножение производится на полиноме 0x11B.

В таблице представлены матрицы изоморфного аффинного преобразования из представления поля GF(2^8) 0x1C3 в 0x11B и обратно. В расчете можно использовать любую пару.

Таблица преобразований из представления поля 0x1C3 в 0x11B
M =0x5D0CE430CEE6BCD0 M-1 =0xC9248C8EB6BE7C4A
M =0x61D8CC543E9296A4 M-1 =0x359C60B0663A0EDA
M =0x2FA2EA44FA5AD66C M-1 =0x6332D8D6145ED06E
M =0x59A208A62EC29AF4 M-1 =0x61EC0A04B4F2D01C
M =0xED406082D258646E M-1 =0x89F844381A0602F0
M =0x5BD81880DC3CDA0A M-1 =0x494212C2C6360E08
M =0x0340F81A7C8C1EBA M-1 =0x87864834BAD4025C
M =0xC90C4A9E5604C632 M-1 =0x195A202216CC7C46

Идеальный S-Box

Глядя на алгоритм Кузнечик, видно его несовершенство.. Совершенный алгоритм -- тот, который сохраняя высокую степень нелинейности S-Box дает высокую производительность и высокую степень параллельности кода. Для этого нелинейное преобразование S-Box должно выражаться математическими операциями. В статьях предлагают S-Box полученные с использованием операции инверсии (возведения в степень) и аффинных преобразований, (англ. APA -- affine-power-affine).

s(x) = A2·Inv(A1·a(x)⊕C1) ⊕C2

В итоге формула "совершенного алгоритма" для следующего поколения ГОСТ выражается следующим циклом. При этом стандартизировать алгоритм можно с использованием любого из 30 нередуцируемых полиномов поля GF(2^8) и любых немыслимых аффинных преобразований, прежде всего в качестве основы для проектирования S-Box выбираются циркулянты - матрицы построенные на вращении полинома, которые дают обратимые преобразования. Возможно существует декомпозиция, которая представляет S-Box Кузнечик в совершенном виде.

// Предлагаемый алгоритм с использованием APA S-Box и матричного умножения.
uint8x16_t LS(uint8x16_t s) {
    s = _mm_gf2p8affine_epi64_epi8(s, A1, C1);
    s = _mm_gf2p8affineinv_epi64_epi8(s, A2, C2);
    uint8x16_t r = {0};
    for(i=0; i<N; i++) {
    	s = _mm_alignr_epi8(s,s,1);
        r^= _mm_gf2p8mul_epi8(s, LMR[i]);
    }
    return r;
}

Такая структура алгоритма позволяет получить производительность 1 такт на цикл алгоритма.

пятница, 11 июня 2021 г.

GF2p8 Представления конечных полей

Речь пойдет про математику, и приложение новых инструкций Intel GFNI для криптографии и оптимизации разных алгоритмов.

Во-первых, никто не понимает, как пользоваться инструкциями GF-NI - нечеловеческая математика. Расширение состоит из трех инструкций, которые расчитывают матричные операции над полиномами: M*x+C, M*x-1+C и полиномиальное умножение с редуцированием A⊙B mod 0x11B. В систему команд введены инструкции для работы всего с одним полиномом (0x11B), в то время как этот полином используется мало в каких алгоритмах.

Первая операция M*x+C (аффинные преобразования gf2p8affine) не привязана никак к полиному(0x11B), можно сказать про полином вида x8+1, по которому выполняется редуцирование.

Операцию циклического сдвига A<<<n можно представить, как умножение и редуцирование по полиному x8+1. Запишется так: A×2n mod 0x101. Боже, откуда такое чувство, что меня никто не понимает?! Полиномиальное представление двоичных чисел - это просто термин такой, буквой x обозначается сдвиг на разряд. Вот что, например, написано про полиномиальное умножение в стандарте AES.. Rijndael S-box можно представить, как аффинное преобразование от обратного числа и записать в виде набора цикличекских сдвигов.
s = b ⊕ (b<<<1) ⊕ (b<<<2) ⊕ (b<<<3) ⊕ (b<<<4) ⊕ 6316

Это же утверждение записать через операцию полиномиального умножения (умнжения без переносов) и операцию редуцирования по полиному.

S = A×3110 mod 24710.

Это чтобы всех запутать сделано: числа даны в десятичном виде, операции в поле - умножение полиномиальное и редцирование по полиному x8+1.

Первое, что нашлось по теме использования инструкции - много циклических сдвигов можно представить, как аффинное преобразование [0x80.pl].

Из всего это следует, что подстановку AES S-box можно выполнить единственной инструкией gf2p8affineinv: S = A*inv(x)+C. Надо понять, как записать и протестировать эту возможность. Матрица преобразования записана в стандарте AES.

// Матрица аффинного преобразования, сдвиги: 0,1,2,3,4
|11111000|
|01111100|
|00111110|
|00011111|
|10001111|
|11000111|
|11100011|
|11110001|
// Матрица Обратного аффинного преобразования, сдвиги: 1,3,6
|01010010| 
|00101001|
|10010100|
|01001010|
|00100101|
|10010010|
|01001001|
|10100100|

Сдвиги в прямой матрице можно представить полиномом 7й степени (x7+x3+x2+x+1)=0xF1 сдвиги в обратной (x7+x5+x2)=0xA4. Для расчета обратного преобразования предлагается использовать утверждение (x7+x3+x2+x+1)^3 mod (x8+1)==(x7+x5+x2). Методом уножения на себя два раза из полинома 7й степени можно получить обратный ему полином.

Таким образом, утверждается, что имея полином A 7й степени, можно получить обратный ему полином B 7й степени, для которых выполняется условие A×B mod (x8+1) = 1. Не уверен, что это правило работает, но оставил.

// Нахождение обратного полинома 7й степени.
void inverse_af2(uint32_t poly)
{
	uint32_t mask = 0xFF;
	uint32_t p = poly & mask;
	uint32_t r;
	r = pmul(p, p);// умножение полиномиальное
	r = (r ^ (r>>8))& mask;// редуцирование
	r = pmul(r, p);// умножение полиномиальное
	r = (r ^ (r>>8))& mask;// редуцирование
	return r;
}

Навык поиска обратного значения нам понадобился, чтобы составлять прямые и обратные матрицы аффинных преобразований.

Обратный полином можно найти и методом подбора, перебрать все элементы поля, пока не найдется удовлетворяющий условию -- произведение на обратный элемент дает единицу.

// Операция умножения в поле GF(2^8) с полиномом Poly
uint32_t gf2p8_mul(uint32_t p, uint32_t i, uint32_t Poly){
    uint32_t r=0;
    while (i) {
        if (i&1)r^=p;
        if (p&0x80){
            p=(p<<1) ^ Poly;
        } else
            p=(p<<1);
        i>>=1;
    }
    return r;
}

Следующий инструмент разработки - составление таблиц преобразования, прямых и обратных. Тут надо вспомнить некоторое свойство. После g^(p-1) = g. Число g - генератор поля выбирается таким образом чтобы его степени проходили по всем элеементам - это, например, число 3. Методом уножения числа на 3 можно перебрать все элементы поля.

// Инициализация таблицы инверсии inv(x) == x-1
static void initialize_inv(uint8_t inv[256]) 
{
    uint8_t p = 1, q = 1;
    do {
        p = gf2p8_mul(p, 0x03, Poly);// умножить p на 3
        q = gf2p8_mul(q, 0xF6, Poly);// делить   q на 3
        inv[p] = q;
    } while p != 1);
    inv[0] = 0;
}

В цикле используется умножение на обратное число (0xF6), вместо деления на 3. Произведение чисел p и q единица, одно для другого на каждом цикле остается обратным числом. За счет выбора генератора, перебираются все числа, кроме нуля. В результате получаем таблицу обратных чисел.

Таблица обратных чисел для Poly=11B
 00 01 8D F6 CB 52 7B D1 E8 4F 29 C0 B0 E1 E5 C7
 74 B4 AA 4B 99 2B 60 5F 58 3F FD CC FF 40 EE B2
 3A 6E 5A F1 55 4D A8 C9 C1 0A 98 15 30 44 A2 C2
 2C 45 92 6C F3 39 66 42 F2 35 20 6F 77 BB 59 19
 1D FE 37 67 2D 31 F5 69 A7 64 AB 13 54 25 E9 09
 ED 5C 05 CA 4C 24 87 BF 18 3E 22 F0 51 EC 61 17
 16 5E AF D3 49 A6 36 43 F4 47 91 DF 33 93 21 3B
 79 B7 97 85 10 B5 BA 3C B6 70 D0 06 A1 FA 81 82
 83 7E 7F 80 96 73 BE 56 9B 9E 95 D9 F7 02 B9 A4
 DE 6A 32 6D D8 8A 84 72 2A 14 9F 88 F9 DC 89 9A
 FB 7C 2E C3 8F B8 65 48 26 C8 12 4A CE E7 D2 62
 0C E0 1F EF 11 75 78 71 A5 8E 76 3D BD BC 86 57
 0B 28 2F A3 DA D4 E4 0F A9 27 53 04 1B FC AC E6
 7A 07 AE 63 C5 DB E2 EA 94 8B C4 D5 9D F8 90 6B
 B1 0D D6 EB C6 0E CF AD 08 4E D7 E3 5D 50 1E B3
 5B 23 38 34 68 46 03 8C DD 9C 7D A0 CD 1A 41 1C

Заметим, что аналогичным образом можно составить таблицу экспоненты и логарифма числа, в качестве генератора используется число 2. Т.е. табличное представление умножения и инверсии через экспонирование и логарифирование чисел, с отображением на конечное поле 255 с обычной модульной арифметикой -- это нас не пугает, потому что традиционно используется в расчетах кодов Рида-Соломона. Интересно, можно ли аффинными преобразованиями получить экспоненту или логарифм числа, чтобы заменить таблицы на аффинные преобразования?

Вернемся к генерации таблиц.

S = A×inv(x)⊕C = AF(inv[x])⊕ 6316

Всего существует 30шт нередуцируемых полиномов восьмого порядка, которые можно использовать для построения полей GF(2^8). Утверждается, что все поля образованные нередуцируемыми полиномами 8-ого порядка изоморфны, т.е. имеют однозначное отображение одного множества чисел на другое множество. Это можно не доказывать, и так очевидное утверждение.

Другое утверждение -- представления полей GF(2^8) с разными полиномами можно отображать используя аффинные преобразования с матрицей 8x8 бит. Вот только как эти матрицы получать? Погуглить в интернете?! Нашел работы 2019-2021 года по разработке новых алгоритмов для пост-квантовой криптографии, нашел оптимизации таблиц подстановок типа S-box на основе таких преобразований. От Intel традиционно, внедрение новых инструкций Intel для криптографии продвигает Shay Gueron и группа товарищей, по его работам, патентам с его участием и статьям можно проследить развитие направления.

[US9800406] US Патент описывет преобразование представления GF(2^8) для вычисления S-Box китайского национального алгоритма блочного шифра SM4. Образующий поле AES полином x8 + x4 + x3 + x + 1=0x11B, в SM4 используется образующий полином x8 + x7 + x6 + x5 + x4 + x2 + 1=0x1F5. Преобразования Sbox записывается следующим образом.


SBoxSM4(x) = A2*inv(A1*x⊕C1)⊕C2
SBoxAES(x) = A*inv(x) + 6316
Матрицы SM4
   |1 0 1 0 0 1 1 1|    |1 1 0 0 1 0 1 1|
   |0 1 0 0 1 1 1 1|    |1 0 0 1 0 1 1 1|
A1=|1 0 0 1 1 1 1 0| A2=|0 0 1 0 1 1 1 1|
   |0 0 1 1 1 1 0 1|    |0 1 0 1 1 1 1 0|
   |0 1 1 1 1 0 1 0|    |1 0 1 1 1 1 0 0|
   |1 1 1 1 0 1 0 0|    |0 1 1 1 1 0 0 1|
   |1 1 1 0 1 0 0 1|    |1 1 1 1 0 0 1 0|
   |1 1 0 1 0 0 1 1|    |1 1 1 0 0 1 0 1|
   
C1=(1 1 0 0 1 0 1 1)TC2=(1 1 0 1 0 0 1 1)T
Матрица AES [NIST FIPS 197]
   |1 0 0 0 1 1 1 1|
   |1 1 0 0 0 1 1 1|
   |1 1 1 0 0 0 1 1|
A =|1 1 1 1 0 0 0 1|
   |1 1 1 1 1 0 0 0|
   |0 1 1 1 1 1 0 0|
   |0 0 1 1 1 1 1 0|
   |0 0 0 1 1 1 1 1|

C =(1 1 0 0 0 1 1 0)T

Патент представил матрицу преобразования элементов из поля SM4(0x1F5) в поле AES(0x11B)

Матрица отображения
  |0 1 1 0 0 0 1 0|
  |1 1 1 0 1 0 1 0|
  |0 0 1 1 1 1 0 0|
M=|1 0 0 0 0 1 1 0|
  |0 0 0 0 0 1 0 0|
  |0 0 1 0 0 1 1 0|
  |1 1 0 1 1 1 1 0|
  |0 1 0 1 0 0 0 1|

Отображение из SM4 в AES можно описать выражением

y = M*(A1*x⊕C1)

Обратное отображение из AES в SM4

s = A2*(M-1*A-1*u)⊕C2

Матрицы 8x8 можно перемножать между собой и сокращать выражения.

Матрицы отображения
     |01010010|             |11001011|
     |10111100|             |10011010|
     |00101101|             |00001010|
M*A1=|00000010|, A2*M-1*A-1=|10110100|
     |10011110|             |11000111|
     |00100101|             |10101100|
     |10101100|             |10000111|
     |00110100|             |01001110|

M*C1=0x65               C2 =0xD3

Такие же преобразования можно совершать в любом поле. Всего насчитывается 30 нередуцируемых полиномов пригодных для подобных преобразований.

Для разработки необходимы методы: 1) Поиск матриц M для перехода между представлениями, 2) Декомпозиция S-Box 3) перемножение матриц, поиск обратных матриц, произведение матрицы на вектор - линейная алгебра. Из своего образования вспоминаю LU-разложение матриц, и свойства линейности матриц, которое позволяет представлять аффинные преобразования в виде перестановок, т.е снизить разрядность операции. Надо вспоминать линейную алгебру! Довольно много разных алгоритмов используют различные полиномы. Эти инструкции позволяют существенно ускуорить их работу, но пока нет опыта использования и надо затратить усилия на освоение математики и разработку алгоритмов синтеза таблиц.

Ниже привожу таблицу нередуцируемых полиномов..

Таблица нередуцируемых полиномов (30 шт):
x^8 + x^7 + x^5 + x^4 + 1
x^8 + x^6 + x^5 + x^4 + 1
x^8 + x^7 + x^5 + x^3 + 1
x^8 + x^6 + x^5 + x^3 + 1
x^8 + x^5 + x^4 + x^3 + 1
x^8 + x^7 + x^6 + x^5 + x^4 + x^3 + 1
x^8 + x^6 + x^5 + x^2 + 1
x^8 + x^7 + x^6 + x^5 + x^4 + x^2 + 1
x^8 + x^7 + x^3 + x^2 + 1
x^8 + x^6 + x^3 + x^2 + 1
x^8 + x^5 + x^3 + x^2 + 1
x^8 + x^4 + x^3 + x^2 + 1
x^8 + x^7 + x^6 + x^4 + x^3 + x^2 + 1
x^8 + x^7 + x^5 + x^4 + x^3 + x^2 + 1
x^8 + x^7 + x^6 + x + 1
x^8 + x^7 + x^5 + x + 1
x^8 + x^6 + x^5 + x + 1
x^8 + x^7 + x^6 + x^5 + x^4 + x + 1
x^8 + x^7 + x^3 + x + 1
x^8 + x^5 + x^3 + x + 1
x^8 + x^4 + x^3 + x + 1 - AES
x^8 + x^6 + x^5 + x^4 + x^3 + x + 1
x^8 + x^7 + x^2 + x + 1
x^8 + x^7 + x^6 + x^5 + x^2 + x + 1
x^8 + x^7 + x^6 + x^4 + x^2 + x + 1
x^8 + x^6 + x^5 + x^4 + x^2 + x + 1
x^8 + x^7 + x^6 + x^3 + x^2 + x + 1
x^8 + x^7 + x^4 + x^3 + x^2 + x + 1
x^8 + x^6 + x^4 + x^3 + x^2 + x + 1
x^8 + x^5 + x^4 + x^3 + x^2 + x + 1

Следующая статья по теме - закрепление. Материала много для одного сообщения, но надо дополнить.

[S.Gueron et al. Speed up over the Rainbow] "All the representations of GF(2^8) are isomorphic. Therefore, it is possible to pass from one representation to another by means of multiplying by an 8 ×8-bit matrix"