Эффективное умножение BigInteger по модулю n в Java

Я могу вычислить умножение двух BigIntegers (скажем a и b) по модулю n.

Это можно сделать:

a.multiply(b).mod(n);

Однако, считая, что a и b имеют один и тот же порядок n, это означает, что во время вычисления новый BigInteger и его длина (в байтах) равна ~ 2n.

Интересно, есть ли более эффективная реализация, которую я могу использовать. Что-то вроде modMultiply, которое реализовано как modPow (которое, я считаю, не вычисляет мощность, а затем модуль).

Ответы

Ответ 1

Я могу думать только о

a.mod(n).multiply(b.mod(n)).mod(n)

и вы, кажется, уже знаете об этом.

BigInteger имеет toByteArray(), но внутри int. поэтому n должно быть довольно большим, чтобы иметь эффект. Возможно, в криптографическом коде генерации ключей может быть такая работа.

Furhtermore, если вы думаете о сокращении умножения, вы получите следующее:

public static BigInteger multiply(BigInteger a, BigInteger b, int mod) {
    if (a.signum() == -1) {
        return multiply(a.negate(), b, mod).negate();
    }
    if (b.signum() == -1) {
        return multiply(a, b.negate(), mod).negate();
    }

    int n = (Integer.bitCount(mod - 1) + 7) / 8; // mod in bytes.
    byte[] aa = a.toByteArray(); // Highest byte at [0] !!
    int na = Math.min(n, aa.length); // Heuristic.
    byte[] bb = b.toByteArray();
    int nb = Math.min(n, bb.length); // Heuristic.
    byte[] prod = new byte[n];
    for (int ia = 0; ia < na; ++ia) {
        int m = ia + nb >= n ? n - ia - 1 : nb; // Heuristic.
        for (int ib = 0; ib < m; ++ib) {
            int p = (0xFF & aa[aa.length - 1 - ia]) * (0xFF & bb[bb.length - 1 - ib]);
            addByte(prod, ia + ib, p & 0xFF);
            if (ia + ib + 1 < n) {
                addByte(prod, ia + ib + 1, (p >> 8) & 0xFF);
            }
        }
    }
    // Still need to do an expensive mod:
    return new BigInteger(prod).mod(BigInteger.valueOf(mod));
}

private static void addByte(byte[] prod, int i, int value) {
    while (value != 0 && i < prod.length) {
        value += prod[prod.length - 1 - i] & 0xFF;
        prod[prod.length - 1 - i] = (byte) value;
        value >>= 8;
        ++i;
    }
}

Этот код не выглядит аппетитным. BigInteger имеет проблему отображения внутреннего значения только как big-endian byte[], где первый байт является самым значительным.

Намного лучше было бы иметь цифры в базе N. Это не невероятно: если N является степенью 2, возможны некоторые интересные оптимизации.

(Кстати, код непроверен - поскольку он не выглядит убедительно быстрее.)

Ответ 2

Во-первых, плохая новость: я не смог найти какие-либо существующие библиотеки Java, которые предоставили эту функциональность.

  • Я не мог найти чистые Java-библиотеки с большими целыми числами... кроме java.math.BigInteger.

  • Существуют оболочки Java/JNI для библиотеки GMP, но GMP также не реализует это.

Итак, каковы ваши варианты?

  • Возможно, есть какая-то чистая библиотека Java, которую я пропустил.

  • Может быть, там другая другая родная (C/С++) большая целочисленная библиотека поддерживает эту операцию... хотя вам может понадобиться написать свои собственные обертки JNI.

  • Вы должны иметь возможность реализовать такой метод для себя, скопировав исходный код java.math.BigInteger и добавив дополнительный настраиваемый метод. Кроме того, похоже, что вы можете extend его.


Сказав это, я не уверен, что существует алгоритм "значительно быстрее" для вычисления a * b mod n в Java или на любом другом языке. (Помимо особых случаев, например, когда n является степенью 2).

В частности, подход "Montgomery Reduction" не помог бы ни на один шаг умножения. (На странице Википедии говорится: "Поскольку числа должны быть преобразованы в определенную форму, подходящую для выполнения шага Монтгомери, и одно из них, одно модульное умножение, выполненное с использованием шага Монтгомери, на самом деле немного менее эффективно, чем" наивный ".)

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

Ответ 3

Вы можете использовать общие математики, например: (A * B) mod N = ((mod mod N) * (B mod N)) mod N

Он может быть более интенсивным, но нужно выбирать между процессором и памятью, не так ли?

Если мы говорим о модульной арифметике, то, действительно, сокращение Монтгомери может быть тем, что вам нужно. Однако не знаю каких-либо решений из коробки.

Ответ 4

Вы можете написать умножение BigInteger как стандартное длинное умножение в очень большой базе - например, в базе 2 ^ 32. Это довольно просто. Если вам нужен только результат по модулю n, то выгодно выбрать базу, которая является фактором n или из которых n является фактором. Затем вы можете игнорировать все, кроме одного или нескольких цифр с наименьшим результатом (Big), когда вы выполняете вычисления, экономя пространство и, возможно, время.

Это наиболее практично, если вы знаете n заранее, конечно, но такие предварительные знания не являются существенными. Это особенно приятно, если n является степенью двух, и это довольно беспорядочно, если n не является ни силой, ни меньшей, чем максимальный операнд, обрабатываемый непосредственно арифметической единицей системы, но все эти случаи могут быть рассмотрены в принципе.

Если вы должны сделать это специально с экземплярами Java BigInteger, однако, имейте в виду, что любой подход, не предоставленный самим классом BigInteger, будет нести накладные расходы для преобразования между внутренним и внешним представлениями.

Ответ 5

Возможно, это:

static BigInteger multiply(BigInteger c, BigInteger x)
{
    BigInteger sum = BigInteger.ZERO;
    BigInteger addOperand;
    for (int i=0; i < FIELD_ELEMENT_BIT_SIZE; i++)
    {
        if (c.testBit(i))
            addOperand = x;
        else
            addOperand = BigInteger.ZERO;

        sum = add(sum, addOperand);

        x = x.shiftRight(1);
    }

    return sum;
}

со следующими вспомогательными функциями:

static BigInteger add(BigInteger a, BigInteger b)
{
    return modOrder(a.add(b));
}

static BigInteger modOrder(BigInteger n)
{
    return n.remainder(FIELD_ORDER);
}

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