On Fri, Apr 28, 2023 at 10:23:15AM +0200, Theo Buehler wrote:
> The behavior of BPSW for numbers > 2^64 is not very well understood.
> While there is no known composite that passes the test, there are
> heuristics that indicate that there are likely many. Therefore it seems
> appropriate to harden the test. Having a settable number of MR rounds
> before doing a version of BPSW is also the approach taken by Go's
> primality check in math/big.
> 
> This is a first step towards incorporating some of the considerations in
> "A performant misuse-resistant API for Primality Testing" by Massimo and
> Paterson. It should be noted that it is based on this paper (and due to
> FIPS, I suppose) that OpenSSL 3 made their primality check so slow that
> the bn_prime.c regress test took over 10 minutes on an M3000 before the
> libcrypto bump (it took less than a minute with ours).
> 
> This basically adds back an equivalent of the old inadequate prime number
> test before running the strong Lucas test, with slightly cleaner code.
> While I don't think performance matters a lot here, we're then effectively
> at about twice the cost of what we had a year ago. I also think that having
> some non-determinism in the algorithm is a plus.
> 
> The implementation is straightforward. It could easily be tweaked to use
> the additional gcds in the "enhanced" MR test of FIPS 186-5, but as long
> as we are only going to throw away the additional info, this doesn't add
> all that much.
> 
> I'd like to land this and then do further work in tree. We probably want
> to crank the number of MR rounds done by default considerably. I will
> also need to undo some of the clean-up done in BN_generate_prime_ex()
> after BPSW landed.
> 
> Index: bn/bn_bpsw.c
> ===================================================================
> RCS file: /cvs/src/lib/libcrypto/bn/bn_bpsw.c,v
> retrieving revision 1.8
> diff -u -p -r1.8 bn_bpsw.c
> --- bn/bn_bpsw.c      26 Nov 2022 16:08:51 -0000      1.8
> +++ bn/bn_bpsw.c      28 Apr 2023 07:56:01 -0000
> @@ -301,23 +301,94 @@ bn_strong_lucas_selfridge(int *is_prime,
>  }
>  
>  /*
> - * Miller-Rabin primality test for base 2.
> + * Fermat criterion in Miller-Rabin test.
> + *
> + * Check whether 1 < base < n - 1 witnesses that n is composite. For prime n:
> + *
> + *  * Fermat's little theorem: base^(n-1) = 1 (mod n).
> + *  * The only square roots of 1 (mod n) are 1 and -1.
> + *
> + * Calculate base^((n-1)/2) by writing n - 1 = k * 2^s with odd k: 
> iteratively
> + * compute (base^k)^(2^(s-1)) by successive squaring of base^k.
> + *
> + * If -1 is ever reached, base^(n-1) works out to 1 and n is a pseudoprime
> + * for base. If 1 is reached before -1, we have an unexpected square root of
> + * unity and n is composite. Otherwise base^(n-1) != 1, so n is composite.
>   */
>  
>  static int
> -bn_miller_rabin_base_2(int *is_prime, const BIGNUM *n, BN_CTX *ctx)
> +bn_fermat(int *is_prime, const BIGNUM *n, const BIGNUM *n_minus_one,
> +    const BIGNUM *k, int s, BIGNUM *base, BN_CTX *ctx, BN_MONT_CTX *mctx)
>  {
> -     BIGNUM *n_minus_one, *k, *x;
> -     int i, s;
> +     int ret = 0;
> +     int i;
> +
> +     /* Sanity check: ensure that 1 < base < n - 1. */
> +     if (BN_cmp(base, BN_value_one()) <= 0 || BN_cmp(base, n_minus_one) >= 0)
> +             goto err;
> +
> +     if (!BN_mod_exp_mont_ct(base, base, k, n, ctx, mctx))
> +             goto err;
> +
> +     if (BN_is_one(base) || BN_cmp(base, n_minus_one) == 0) {
> +             *is_prime = 1;
> +             goto done;
> +     }
> +
> +     /* Loop invariant: base is neither 1 nor -1 (mod n). */
> +     for (i = 1; i < s; i++) {
> +             if (!BN_mod_sqr(base, base, n, ctx))
> +                     goto err;
> +
> +             /* n is a pseudoprime for base. */
> +             if (BN_cmp(base, n_minus_one) == 0) {
> +                     *is_prime = 1;
> +                     goto done;
> +             }
> +
> +             /* n is composite: there's a square root of unity != 1 or -1. */
> +             if (BN_is_one(base)) {
> +                     *is_prime = 0;
> +                     goto done;
> +             }
> +     }
> +
> +     /*
> +      * If we get here, n is definitely composite: base^(n-1) != 1.
> +      */
> +
> +     *is_prime = 0;
> +
> + done:
> +     ret = 1;
> +
> + err:
> +     return ret;
> +}
> +
> +/*
> + * Miller-Rabin primality test for base 2 and for |rounds| of random bases.
> + * On success: is_prime == 0 implies n is composite - the converse is false.
> + */
> +
> +static int
> +bn_miller_rabin(int *is_prime, const BIGNUM *n, BN_CTX *ctx, size_t rounds)
> +{
> +     BN_MONT_CTX *mctx = NULL;
> +     BIGNUM *base, *k, *n_minus_one, *three;
> +     size_t i;
> +     int s;
>       int ret = 0;
>  
>       BN_CTX_start(ctx);
>  
> -     if ((n_minus_one = BN_CTX_get(ctx)) == NULL)
> +     if ((base = BN_CTX_get(ctx)) == NULL)
>               goto err;
>       if ((k = BN_CTX_get(ctx)) == NULL)
>               goto err;
> -     if ((x = BN_CTX_get(ctx)) == NULL)
> +     if ((n_minus_one = BN_CTX_get(ctx)) == NULL)
> +             goto err;
> +     if ((three = BN_CTX_get(ctx)) == NULL)
>               goto err;
>  
>       if (BN_is_word(n, 2) || BN_is_word(n, 3)) {
> @@ -344,43 +415,56 @@ bn_miller_rabin_base_2(int *is_prime, co
>               goto err;
>  
>       /*
> -      * If 2^k is 1 or -1 (mod n) then n is a 2-pseudoprime.
> +      * Montgomery setup for n.
>        */
>  
> -     if (!BN_set_word(x, 2))
> +     if ((mctx = BN_MONT_CTX_new()) == NULL)
>               goto err;
> -     if (!BN_mod_exp_ct(x, x, k, n, ctx))
> +
> +     if (!BN_MONT_CTX_set(mctx, n, ctx))
>               goto err;
>  
> -     if (BN_is_one(x) || BN_cmp(x, n_minus_one) == 0) {
> -             *is_prime = 1;
> +     /*
> +      * Perform a Miller-Rabin test for base 2 as required by BPSW.
> +      */
> +
> +     if (!BN_set_word(base, 2))
> +             goto err;
> +
> +     if (!bn_fermat(is_prime, n, n_minus_one, k, s, base, ctx, mctx))
> +             goto err;
> +     if (!*is_prime)
>               goto done;
> -     }
>  
>       /*
> -      * If 2^{2^i k} == -1 (mod n) for some 1 <= i < s, then n is a
> -      * 2-pseudoprime.
> +      * Perform Miller-Rabin tests with 3 <= base < n - 1 to reduce risk of
> +      * false positives in BPSW.
>        */
>  
> -     for (i = 1; i < s; i++) {
> -             if (!BN_mod_sqr(x, x, n, ctx))
> +     if (!BN_set_word(three, 3))
> +             goto err;
> +
> +     for (i = 0; i < rounds; i++) {
> +             if (!bn_rand_interval(base, three, n_minus_one))
>                       goto err;
> -             if (BN_cmp(x, n_minus_one) == 0) {
> -                     *is_prime = 1;
> +
> +             if (!bn_fermat(is_prime, n, n_minus_one, k, s, base, ctx, mctx))
> +                     goto err;
> +             if (!*is_prime)
>                       goto done;
> -             }
>       }
>  
>       /*
> -      * If we got here, n is definitely composite.
> +      * If we got here, we have a Miller-Rabin pseudoprime.
>        */
>  
> -     *is_prime = 0;
> +     *is_prime = 1;

Nit: you call this a pseudo-prime - which I agree with, but the variable you 
have added
is "is_prime" - maybe call it "maybe-prime" or "pseudo-prime"

>  
>   done:
>       ret = 1;
>  
>   err:
> +     BN_MONT_CTX_free(mctx);
>       BN_CTX_end(ctx);
>  
>       return ret;
> @@ -392,7 +476,7 @@ bn_miller_rabin_base_2(int *is_prime, co
>   */
>  
>  int
> -bn_is_prime_bpsw(int *is_prime, const BIGNUM *n, BN_CTX *in_ctx)
> +bn_is_prime_bpsw(int *is_prime, const BIGNUM *n, BN_CTX *in_ctx, size_t 
> rounds)
>  {
>       BN_CTX *ctx = NULL;
>       BN_ULONG mod;
> @@ -424,12 +508,10 @@ bn_is_prime_bpsw(int *is_prime, const BI
>       if (ctx == NULL)
>               goto err;
>  
> -     if (!bn_miller_rabin_base_2(is_prime, n, ctx))
> +     if (!bn_miller_rabin(is_prime, n, ctx, rounds))
>               goto err;
>       if (!*is_prime)
>               goto done;
> -
> -     /* XXX - Miller-Rabin for random bases? See FIPS 186-4, Table C.1. */
>  
>       if (!bn_strong_lucas_selfridge(is_prime, n, ctx))
>               goto err;
> Index: bn/bn_local.h
> ===================================================================
> RCS file: /cvs/src/lib/libcrypto/bn/bn_local.h,v
> retrieving revision 1.21
> diff -u -p -r1.21 bn_local.h
> --- bn/bn_local.h     25 Apr 2023 17:59:41 -0000      1.21
> +++ bn/bn_local.h     28 Apr 2023 07:56:01 -0000
> @@ -324,7 +324,7 @@ int bn_copy(BIGNUM *dst, const BIGNUM *s
>  int bn_isqrt(BIGNUM *out_sqrt, int *out_perfect, const BIGNUM *n, BN_CTX 
> *ctx);
>  int bn_is_perfect_square(int *out_perfect, const BIGNUM *n, BN_CTX *ctx);
>  
> -int bn_is_prime_bpsw(int *is_prime, const BIGNUM *n, BN_CTX *in_ctx);
> +int bn_is_prime_bpsw(int *is_prime, const BIGNUM *n, BN_CTX *ctx, size_t 
> rounds);
>  
>  __END_HIDDEN_DECLS
>  #endif /* !HEADER_BN_LOCAL_H */
> Index: bn/bn_prime.c
> ===================================================================
> RCS file: /cvs/src/lib/libcrypto/bn/bn_prime.c,v
> retrieving revision 1.31
> diff -u -p -r1.31 bn_prime.c
> --- bn/bn_prime.c     25 Apr 2023 19:57:59 -0000      1.31
> +++ bn/bn_prime.c     28 Apr 2023 07:56:01 -0000
> @@ -195,12 +195,12 @@ BN_generate_prime_ex(BIGNUM *ret, int bi
>               goto err;
>  
>       if (!safe) {
> -             if (!bn_is_prime_bpsw(&is_prime, ret, ctx))
> +             if (!bn_is_prime_bpsw(&is_prime, ret, ctx, 1))
>                       goto err;
>               if (!is_prime)
>                       goto loop;
>       } else {
> -             if (!bn_is_prime_bpsw(&is_prime, ret, ctx))
> +             if (!bn_is_prime_bpsw(&is_prime, ret, ctx, 1))
>                       goto err;
>               if (!is_prime)
>                       goto loop;
> @@ -213,7 +213,7 @@ BN_generate_prime_ex(BIGNUM *ret, int bi
>               if (!BN_rshift1(p, ret))
>                       goto err;
>  
> -             if (!bn_is_prime_bpsw(&is_prime, p, ctx))
> +             if (!bn_is_prime_bpsw(&is_prime, p, ctx, 1))
>                       goto err;
>               if (!is_prime)
>                       goto loop;
> @@ -243,8 +243,14 @@ BN_is_prime_fasttest_ex(const BIGNUM *a,
>  {
>       int is_prime;
>  
> +     if (checks < 0)
> +             return -1;
> +
> +     if (checks == BN_prime_checks)
> +             checks = BN_prime_checks_for_size(BN_num_bits(a));
> +
>       /* XXX - tickle BN_GENCB in bn_is_prime_bpsw(). */
> -     if (!bn_is_prime_bpsw(&is_prime, a, ctx_passed))
> +     if (!bn_is_prime_bpsw(&is_prime, a, ctx_passed, checks))
>               return -1;
>  
>       return is_prime;
> Index: man/BN_generate_prime.3
> ===================================================================
> RCS file: /cvs/src/lib/libcrypto/man/BN_generate_prime.3,v
> retrieving revision 1.20
> diff -u -p -r1.20 BN_generate_prime.3
> --- man/BN_generate_prime.3   24 Nov 2022 19:06:38 -0000      1.20
> +++ man/BN_generate_prime.3   28 Apr 2023 07:56:01 -0000
> @@ -84,7 +84,7 @@
>  .Nm BN_is_prime ,
>  .Nm BN_is_prime_fasttest
>  .\" Nm BN_prime_checks_for_size is intentionally undocumented
> -.\" because it is no longer used by LibreSSL.
> +.\" because it should not be used outside of libcrypto.
>  .Nd generate primes and test for primality
>  .Sh SYNOPSIS
>  .In openssl/bn.h
> @@ -178,18 +178,33 @@ test whether the number
>  .Fa a
>  is prime.
>  In LibreSSL, both functions behave identically,
> -use the Baillie-Pomerance-Selfridge-Wagstaff algorithm,
> -and ignore the
> +use the Baillie-Pomerance-Selfridge-Wagstaff algorithm
> +combined with
>  .Fa checks
> -and
> +rounds of the Miller-Rabin test.
> +They ignore the
>  .Fa do_trial_division
> -arguments.
> +argument.
>  .Pp
>  It is unknown whether any composite number exists that the
>  Baillie-PSW algorithm misclassifies as a prime.
>  Some suspect that there may be infinitely many such numbers,
>  but not a single one is currently known.
>  It is known that no such number exists below 2\(ha64.

You are now pre-checking with miller rabin to reduce the likelyhood of
a composite number of any size, but you are not explaining this here. 

The point of the miller-rabin pre-test is to cheaply reduce the likelyhood 
of a composite number being fed to Ballie-PSW to "very small" so that even
if the future hypothesis finds such numbers we are still protected, 
and this should be explained here. 


> +.Pp
> +The number of Miller-Rabin rounds performed by
> +.Fn BN_is_prime_fasttest_ex
> +and
> +.Fn BN_is_prime_ex
> +is determined by
> +.Fa checks :
> +If it is positive, it is used as the number of rounds.
> +If
> +.Fa checks
> +is zero or the special value
> +.Dv BN_prime_checks ,
> +a suitable number of rounds is calculated from the bit length of
> +.Fa a .
>  .Pp
>  If
>  .Dv NULL

Reply via email to