Функция pseudo_encrypt() в plpgsql, которая принимает bigint

Я работаю над системой, которая генерирует случайные идентификаторы, например, в ответе # 2 здесь.

Моя проблема в том, что упомянутая функция pseudo_encrypt() работает с int not bigint. Я попытался переписать его, но он всегда возвращает тот же результат:

CREATE OR REPLACE FUNCTION pseudo_encrypt(VALUE bigint) returns bigint AS $$
DECLARE
l1 bigint;
l2 int;
r1 bigint;
r2 int;
i int:=0;
BEGIN
    l1:= (VALUE >> 32) & 4294967296::bigint;
    r1:= VALUE & 4294967296;
    WHILE i < 3 LOOP
        l2 := r1;
        r2 := l1 # ((((1366.0 * r1 + 150889) % 714025) / 714025.0) * 32767)::int;
        l1 := l2;
        r1 := r2;
        i := i + 1;
    END LOOP;
RETURN ((l1::bigint << 32) + r1);
END;
$$ LANGUAGE plpgsql strict immutable;

Может кто-нибудь это проверить?

Ответы

Ответ 1

4294967295 должен использоваться как битовая маска для выбора 32 бит (вместо 4294967296). Это причина, по которой в настоящее время вы получаете одинаковое значение для разных входов.

Я также предлагаю использовать bigint для типов l2 и r2, они не должны сильно отличаться от r1 и l1

И для лучшей случайности используйте функцию более высокого множителя в функции PRNG, чтобы получить промежуточный блок, который действительно занимает 32 бита, например 32767 * 32767 вместо 32767.

Полная измененная версия:

CREATE OR REPLACE FUNCTION pseudo_encrypt(VALUE bigint) returns bigint AS $$
DECLARE
l1 bigint;
l2 bigint;
r1 bigint;
r2 bigint;
i int:=0;
BEGIN
    l1:= (VALUE >> 32) & 4294967295::bigint;
    r1:= VALUE & 4294967295;
    WHILE i < 3 LOOP
        l2 := r1;
        r2 := l1 # ((((1366.0 * r1 + 150889) % 714025) / 714025.0) * 32767*32767)::int;
        l1 := l2;
        r1 := r2;
        i := i + 1;
    END LOOP;
RETURN ((l1::bigint << 32) + r1);
END;
$$ LANGUAGE plpgsql strict immutable;

Первые результаты:

select x,pseudo_encrypt(x::bigint) from generate_series (1, 10) as x;
 x  |   pseudo_encrypt    
----+---------------------
  1 | 3898573529235304961
  2 | 2034171750778085465
  3 |  169769968641019729
  4 | 2925594765163772086
  5 | 1061193016228543981
  6 | 3808195743949274374
  7 | 1943793931158625313
  8 |   88214277952430814
  9 | 2835217030863818694
 10 |  970815170807835400
(10 rows)

Ответ 2

Старый, но еще интересный вопрос. Сравнивая с Дэниелсом ответ, я использую слегка модифицированную версию, меняя оператор return на это (обмениваемый r1 и l1), как также упоминается в конце статьи Pseudo шифровать:

RETURN ((r1::bigint << 32) + l1);

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

pseudo_encrypt(pseudo_encrypt(x) == x // always returns true

Вот полный код в pgsql:

CREATE OR REPLACE FUNCTION pseudo_encrypt(VALUE bigint) returns bigint AS $$
DECLARE
l1 bigint;
l2 bigint;
r1 bigint;
r2 bigint;
i int:=0;
BEGIN
    l1:= (VALUE >> 32) & 4294967295::bigint;
    r1:= VALUE & 4294967295;
    WHILE i < 3 LOOP
        l2 := r1;
        r2 := l1 # ((((1366.0 * r1 + 150889) % 714025) / 714025.0) * 32767*32767)::int;
        l1 := l2;
        r1 := r2;
    i := i + 1;
    END LOOP;
RETURN ((r1::bigint << 32) + l1);
END;
$$ LANGUAGE plpgsql strict immutable;