From 3a2bdf16dda123ceb476bd0fac15de67dc247997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Vejpustek?= Date: Fri, 15 Sep 2023 19:10:54 +0200 Subject: [PATCH] feat(crypto): implement Legendre symbol --- crypto/bignum.c | 33 +++++++++++++++++++++++++++++++++ crypto/bignum.h | 1 + crypto/tests/test_bignum.py | 20 ++++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/crypto/bignum.c b/crypto/bignum.c index b23adf7a83..901679325c 100644 --- a/crypto/bignum.c +++ b/crypto/bignum.c @@ -1591,6 +1591,39 @@ void bn_subtract(const bignum256 *x, const bignum256 *y, bignum256 *res) { // == 1 } +// Returns 0 if x is zero +// Returns 1 if x is a square modulo prime +// Returns -1 if x is not a square modulo prime +// Assumes x is normalized, x < 2**259 +// Assumes prime is normalized, 2**256 - 2**224 <= prime <= 2**256 +// Assumes prime is a prime +// The function doesn't have neither constant control flow nor constant memory +// access flow with regard to prime +int bn_legendre(const bignum256 *x, const bignum256 *prime) { + // This is a naive implementation + // A better implementation would be to use the Euclidean algorithm together with the quadratic reciprocity law + + // e = (prime - 1) / 2 + bignum256 e = {0}; + bn_copy(prime, &e); + bn_rshift(&e); + + // res = x**e % prime + bignum256 res = {0}; + bn_power_mod(x, &e, prime, &res); + bn_mod(&res, prime); + + if (bn_is_one(&res)) { + return 1; + } + + if (bn_is_zero(&res)) { + return 0; + } + + return -1; +} + // q = x // d, r = x % d // Assumes x is normalized, 1 <= d <= 61304 // Guarantees q is normalized diff --git a/crypto/bignum.h b/crypto/bignum.h index f9213fbe76..08e21a586f 100644 --- a/crypto/bignum.h +++ b/crypto/bignum.h @@ -108,6 +108,7 @@ void bn_subi(bignum256 *x, uint32_t y, const bignum256 *prime); void bn_subtractmod(const bignum256 *x, const bignum256 *y, bignum256 *res, const bignum256 *prime); void bn_subtract(const bignum256 *x, const bignum256 *y, bignum256 *res); +int bn_legendre(const bignum256 *x, const bignum256 *prime); void bn_long_division(bignum256 *x, uint32_t d, bignum256 *q, uint32_t *r); void bn_divmod58(bignum256 *x, uint32_t *r); void bn_divmod1000(bignum256 *x, uint32_t *r); diff --git a/crypto/tests/test_bignum.py b/crypto/tests/test_bignum.py index b2bbac187d..c41c1c1ac4 100755 --- a/crypto/tests/test_bignum.py +++ b/crypto/tests/test_bignum.py @@ -551,6 +551,21 @@ def assert_bn_subtractmod(x, y, prime): assert res % prime == (x - y) % prime +def legendre(x, prime): + res = pow(x, (prime - 1) // 2, prime) + if res == prime - 1: + return -1 + return res + + +def assert_bn_legendre(x, prime): + bn_x = int_to_bignum(x) + bn_prime = int_to_bignum(prime) + return_value = lib.bn_legendre(bn_x, bn_prime) + + assert return_value == legendre(x, prime) + + def assert_bn_subtract(x, y): bn_x = int_to_bignum(x) bn_y = int_to_bignum(y) @@ -1005,6 +1020,11 @@ def test_bn_subtract_2(r): assert_bn_subtract(a, b) +def test_bn_legendre(r, prime): + x = r.rand_int_bitsize(259) + assert_bn_legendre(x, prime) + + def test_bn_long_division(r): x = r.rand_int_normalized() d = r.randrange(1, 61304 + 1)