Проблема при реализации длинной арифметики в конечном поле

Тема в разделе "WASM.BEGINNERS", создана пользователем Aoizora, 1 авг 2017.

  1. Aoizora

    Aoizora Active Member

    Публикаций:
    0
    Регистрация:
    29 янв 2017
    Сообщения:
    351
    Я пишу библиотеку 256-битной арифметики для реализации эллиптической криптографии на микроконтроллерах. Вычисления должны проводиться в конечном после, которое, для оптимизации, будет выбрано заранее, и во все вычисления будут заточены под это поле. Таким образом, длинные числа у нас фиксированной длины: допустим, 256 или 128 бит. Для простоты отладки я сейчас реализую 32-битную арифметику.

    Для тестов мне понадобилось переводить данное число в систему счисления с произвольным основанием. Я для этого написал скрипт на хаскеле:

    Код (Text):
    1.  
    2. xpand :: Integer -> Integer -> [Integer]
    3. expand num base
    4.   | num < base = [num]
    5.   | otherwise = num `mod` base : expand (num `div` base) base
    6.  
    expand 1000000 64 переведет число в массив цифр по основанию 64.

    Ближе к реализации. Пусть у нас "длинные числа" имеют размерность 32 бита. Разделим 32 бита на 5 частей: каждый кусок числа содержит 6-битную цифру. Вместе получается 30 бит. Вполне достаточно. Это называется редуцированной записью числа и позволяет не использовать инструкцию adc при суммировании длинных чисел. Дело в том, что для эффективной реализации перенос из разрядов только вредит, т.к. инструкция adc в 6 раз медленее инструкции add. Кроме того, вычисления могут производиться на векторном процессоре, тогда 5 разрядов будут суммироваться за такт, при этом carry теряются, т.к. они бы замедлили вычисления. Поэтому используем редуцированную форму записи (уменьшенный диапазон цифр).

    При переполнении разрядов переносы нужно посчитать вручную (уже после того, как закончилоссь поразрядное сложение за один такт):
    Код (C):
    1. typedef struct
    2. {
    3.         signed char a[5];
    4. } bigint32;
    5.  
    6.  
    7. void make_bigint32(
    8.         bigint32* x,
    9.         signed char a0,
    10.         signed char a1,
    11.         signed char a2,
    12.         signed char a3,
    13.         signed char a4)
    14. {
    15.         x->a[0] = a0;
    16.         x->a[1] = a1;
    17.         x->a[2] = a2;
    18.         x->a[3] = a3;
    19.         x->a[4] = a4;
    20. }
    21. void bigint32_add(bigint32* r,
    22.   const bigint32* x,
    23.   const bigint32* y)
    24. {
    25.   // Addition without carry
    26.   r->a[0] = x->a[0] + y->a[0];
    27.   r->a[1] = x->a[1] + y->a[1];
    28.   r->a[2] = x->a[2] + y->a[2];
    29.   r->a[3] = x->a[3] + y->a[3];
    30.   r->a[4] = x->a[4] + y->a[4];
    31.  
    32.   // Carry propagation
    33.   r->a[1] += r->a[0] >> 6;  r->a[0] &= 0x3F;
    34.   r->a[2] += r->a[1] >> 6;  r->a[1] &= 0x3F;
    35.   r->a[3] += r->a[2] >> 6;  r->a[2] &= 0x3F;
    36.   r->a[4] += r->a[3] >> 6;  r->a[3] &= 0x3F;
    37.   r->a[4] &= 0x3F;
    38. }
    39. }
    40.  
    Имеем 6-битовые цифры числа. При переполнении carry оказывается в старших битах. Чтобы получить carry, сдвигаем число вправо на 6 бит. Переносим этот carry в старший разряд суммированием. Накладывая маску 0x3F, обнуляем все старшие разряды кроме шести младших. Так работает алгоритм суммирования.

    У меня не получается правильно реализовать умножение по модулю. Кстати, выберем конечное поле и модуль: пусть [math]p = 2^{12}- 5[/math], [math]F_p[/math] - поле, состоящее из остатков по модулю [math]2^{12} - 5[/math].
    Функция умножения:
    Код (C):
    1. #include <stdio.h>
    2. #include <stdint.h>
    3.  
    4. typedef struct
    5. {
    6.     signed char a[5];
    7. } big;
    8.  
    9. void make_big(big* x,
    10.                 signed char a0,
    11.                 signed char a1,
    12.                 signed char a2)
    13. {
    14.     x->a[0] = a0;
    15.     x->a[1] = a1;
    16.     x->a[2] = a2;
    17. }
    18.  
    19. void print(big* x)
    20. {
    21.     for (int i = 0; i < 4; ++i)
    22.     {
    23.         printf("%02d", x->a[i]);
    24.         printf(" ");
    25.     }
    26.     printf("\n");
    27. }
    28.  
    29. void int32_mul(big* r,
    30.               const big* x,
    31.               const big* y)
    32. {
    33.     const signed char* a = x->a;
    34.     const signed char* b = y->a;
    35.     unsigned short t[5] =
    36.     {
    37.         a[0]*b[0],
    38.         a[0]*b[1] + a[1]*b[0],
    39.         a[0]*b[2] + a[1]*b[1] + a[2]*b[0],
    40.         a[1]*b[2] + a[2]*b[1],
    41.         a[2]*b[2]
    42.     };
    43.  
    44.     t[1] += t[0] >> 6;  t[0] &= 0x3F;
    45.     t[2] += t[1] >> 6;  t[1] &= 0x3F;
    46.     t[3] += t[2] >> 6;  t[2] &= 0x3F;
    47.     t[4] += t[3] >> 6;  t[3] &= 0x3F;
    48.  
    49.     t[0] += t[2] * 5;   // 2^0
    50.     t[1] += t[3] * 5;   // 2^6
    51.     t[2] = 0;
    52.     t[3] = 0;
    53.  
    54.     t[1] += t[0] >> 6;  t[0] &= 0x3F;
    55.  
    56.     t[0] += t[1] >> 6;  t[1] &= 0x3F;
    57.  
    58.     r->a[0] = t[0];
    59.     r->a[1] = t[1];
    60.     r->a[2] = t[2];
    61.     r->a[3] = t[3];
    62. }
    63.  
    64. void int32_pow(big* r, const big* base, int exp)
    65. {
    66.     big tmp;
    67.     make_big(&tmp, 1, 0, 0);
    68.     while (exp--)
    69.     {
    70.         int32_mul(&tmp, &tmp, base);
    71.     }
    72.     *r = tmp;
    73. }
    74. int main()
    75. {
    76.     big r, x, y;
    77.  
    78.     make_big(&x, 40, 15, 0);
    79.     make_big(&y, 16, 31, 0);
    80.     int32_mul(&r, &x, &y);
    81.     print(&r);
    82.  
    83.     make_big(&x, 39, 15, 0);
    84.     make_big(&y, 9, 12, 0);
    85.     int32_mul(&r, &x, &y);
    86.     print(&r);
    87.  
    88.     make_big(&x, 23, 17, 0);
    89.     make_big(&y, 43, 8, 0);
    90.     int32_mul(&r, &x, &y);
    91.     print(&r);
    92.  
    93.     make_big(&x, 43, 8, 0);
    94.     int32_pow(&r, &x, 5);
    95.     print(&r);
    96.  
    97.     make_big(&x, 27, 63, 0);
    98.     make_big(&y, 43, 8, 0);
    99.     int32_mul(&r, &x, &y);
    100.     print(&r);
    101. }
    Объясняю принцип работы по частям:
    Код (C):
    1. unsigned short t[5] =
    2.     {
    3.         a[0]*b[0],
    4.         a[0]*b[1] + a[1]*b[0],
    5.         a[0]*b[2] + a[1]*b[1] + a[2]*b[0],
    6.         a[1]*b[2] + a[2]*b[1],
    7.         a[2]*b[2]
    8.     };
    Это массив коэффициентов произведения без учета переносов и редукции цифр. Пусть длинным числам соответствуют векторы их коэффициентов. Построим многочлены от [math]X[/math] с этими коэффициентами и перемножим. Коэффициента многочлена результата вычисляются по тем формулам (можно расписать на бумажке). Если вместо [math]X[/math] подставить 6, получим результат - число, равное произведению двух длинных чисел.

    Но в результате цифры получились слишком большими, а как мы помним, под цифры мы отвели всего 6 бит. Для нормализации числа нам помогут carry:
    Код (C):
    1.     t[1] += t[0] >> 6;  t[0] &= 0x3F;
    2.     t[2] += t[1] >> 6;  t[1] &= 0x3F;
    3.     t[3] += t[2] >> 6;  t[2] &= 0x3F;
    4.     t[4] += t[3] >> 6;  t[3] &= 0x3F;
    Как и в коде сложения, сдвигом получаем carry (то, что переполнило цифру) и переносим в старший разряд, а в текущем разряде обнуляем все, что за пределами младших 6 бит.
    Код (C):
    1.     t[0] += t[2] * 5;   // 2^0
    2.     t[1] += t[3] * 5;   // 2^6
    3.     t[2] = 0;
    4.     t[3] = 0;
    Понять этот код без модулярной арифметики в конечном поле не получится. Как мы помним, мы зафиксировали конечное поле, состоящее из остатков от деления на [math]2^{12} - 5[/math] (это простое число, поэтому данная алгебраическая структура - поле).

    Результат умножения можно записать как:
    [math]a_0 + a_1\cdot 2^6 + a_2\cdot 2^{12} + a_3\cdot 2^{18} + a_4\cdot 2^{24}[/math].Избавимся от больших степеней. Можно заметить, что поскольку [math]2^{12} - 5 = 0\; mod (2^{12} - 5)[/math], то [math]2^{12} = 5\; mod (2^{12} - 5)[/math]. Кроме того, [math]2^{18} = 2^{12}\cdot 2^6 = 5\cdot 2^6[/math], а [math]2^{24} = 2^{12}\cdot 2^{12} = 5 \cdot 5 = 25[/math].

    Таким образом, число запишется как [math]a_0 + a_1\cdot 2^6 + 5\cdot a_2 + 5\cdot 2^6\cdot a_3 + 5\cdot 5\cdot a_4 = a_0 + 5\cdot a_2 + (a_1 + 5)\cdot 2^6 + 25\cdot a_4[/math].

    В последнем коде я просто сделал группировку слагаемых (в выражении [math]t_0=t_0+ 5\cdot t_2[/math]; [math]t_0[/math] и [math]t_2[/math] - это коэффициенты при шестых степенях, а в выражении [math]t_1=t_1+ 5\cdot t_3[/math]; [math]t_3[/math] это коэффициент при 18-й степени, которая редуцировалась до [math]5\cdot 2^6[/math], поэтому коэффициент [math]t_3[/math] мы умножаем на 5 и прибавляем к коэффициенту при шестой степени, то есть к [math]t_1[/math].

    Далее
    Код (C):
    1.     t[1] += t[0] >> 6;  t[0] &= 0x3F;
    2.  
    3.     t[0] += t[1] >> 6;  t[1] &= 0x3F;
    Учитываем возможность переполнения разрядов и нормализуем запись числа при помощи переносов, чтобы все цифры стали 6-битными.

    Вопрос: почему этот алгоритм работает на первых трех умножениях при возведении в степень, но не работает на четвертом и далее?
     
  2. Pavia

    Pavia Well-Known Member

    Публикаций:
    0
    Регистрация:
    17 июн 2003
    Сообщения:
    2.409
    Адрес:
    Fryazino
    Покажите код возведения в степень.
     
  3. Aoizora

    Aoizora Active Member

    Публикаций:
    0
    Регистрация:
    29 янв 2017
    Сообщения:
    351
    Код (Text):
    1. void int32_pow(big* r, const big* base, int exp)
    2. {
    3.     big tmp;
    4.     make_big(&tmp, 1, 0, 0);
    5.     while (exp--)
    6.     {
    7.         int32_mul(&tmp, &tmp, base);
    8.     }
    9.     *r = tmp;
    10. }
    Ошибка вычисления возникает и в том случае, когда я умножаю число на его предыдущую степень (вроде, на третью оно вчера умножслось уже с ошибкой).
     
  4. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.709
    в форме для ответа есть буква f , которая обрамляет текст тэгами [МАТН] и [/МАТН], степень a^{b}=[math]a^{b}[/math], индекс a_{b}=[math]a_{b}[/math], справка по редактору формул здесь
     
  5. ol.

    ol. Active Member

    Публикаций:
    0
    Регистрация:
    21 фев 2017
    Сообщения:
    118
    Что-то не похоже, что умножения вообще работают.

    Код (C):
    1.  
    2.   make_big(&x, 40, 15, 0);
    3.   make_big(&y, 16, 31, 0);
    4.   int32_mul(&r, &x, &y);
    5.   print(&x);
    6.   print(&y);
    7.   print(&r);
    8.   printf("\n");
    9.  
    Код (Text):
    1.  
    2. 40 15 00 00
    3. 16 31 00 00
    4. 08 56 00 00
    5.  
    Должно было вывести 13 15 31 15, разве нет?
     
  6. Aoizora

    Aoizora Active Member

    Публикаций:
    0
    Регистрация:
    29 янв 2017
    Сообщения:
    351
    Так вычисления по модулю [math]2^{12} - 5[/math] же.
     
  7. ol.

    ol. Active Member

    Публикаций:
    0
    Регистрация:
    21 фев 2017
    Сообщения:
    118
    А, да. Не заметил.
    В таком случае результат должен быть 00 16. А не 08 56, не так ли?
    Кроме того, 56 - это 7 бит, а не шесть.
     
  8. Aoizora

    Aoizora Active Member

    Публикаций:
    0
    Регистрация:
    29 янв 2017
    Сообщения:
    351
    [math]111111_2 = 63_{10}[/math] - последнее 6-битное число. Стыдно на васме путаться в битах.
     
  9. TermoSINteZ

    TermoSINteZ Синоби даоса Команда форума

    Публикаций:
    2
    Регистрация:
    11 июн 2004
    Сообщения:
    3.548
    Адрес:
    Russia
    Aoizora, Я думаю он имел ввиду HEX(56)
     
    ol. нравится это.