Math-Prime-Util

 view release on metacpan or  search on metacpan

lib/Math/Prime/Util/RandomPrimes.pm  view on Meta::CPAN

  my($status,$prime,$prime_seed,$prime_gen_counter,$cert)
     = _ST_Random_prime($k, $seed);
  croak "Shawe-Taylor random prime failure" unless $status;
  croak "Shawe-Taylor random prime failure: prime $prime failed certificate verification!" unless verify_prime($cert);

  return ($prime,$cert);
}

sub _seed_plus_one {
    my($s) = @_;
    for (my $i = length($s)-1; $i >= 0; $i--) {
        vec($s, $i, 8)++;
        last unless vec($s, $i, 8) == 0;
    }
    return $s;
}

sub _ST_Random_prime {  # From FIPS 186-4
  my($k, $input_seed) = @_;
  croak "Shawe-Taylor random prime must have length >= 2"  if $k < 2;
  $k = int("$k");

  croak "Shawe-Taylor random prime, invalid input seed"
     unless defined $input_seed && length($input_seed) >= 32;

  if (!defined $Digest::SHA::VERSION) {
    eval { require Digest::SHA;
           my $version = $Digest::SHA::VERSION;
           $version =~ s/[^\d.]//g;
           $version >= 4.00; }
      or do { croak "Must have Digest::SHA 4.00 or later"; };
  }

  my $k2 = tobigint(powint(2,$k-1));    # $k2 is a bigint

  if ($k < 33) {
    my $seed = $input_seed;
    my $prime_gen_counter = 0;
    my $kmask    = 0xFFFFFFFF >> (32-$k);    # Does the mod operation
    my $kstencil = (1 << ($k-1)) | 1;        # Sets high and low bits
    while (1) {
      my $seedp1 = _seed_plus_one($seed);
      my $cvec = Digest::SHA::sha256($seed) ^ Digest::SHA::sha256($seedp1);
      # my $c = Math::BigInt->from_hex('0x' . unpack("H*", $cvec));
      # $c = $k2 + ($c % $k2);
      # $c = (2 * ($c >> 1)) + 1;
      my($c) = unpack("N*", substr($cvec,-4,4));
      $c = ($c & $kmask) | $kstencil;
      $prime_gen_counter++;
      $seed = _seed_plus_one($seedp1);
      my ($isp, $cert) = is_provable_prime_with_cert($c);
      return (1,$c,$seed,$prime_gen_counter,$cert) if $isp;
      return (0,0,0,0) if $prime_gen_counter > 10000 + 16*$k;
    }
  }
  my($status,$c0,$seed,$prime_gen_counter,$cert)
     = _ST_Random_prime( (($k+1)>>1)+1, $input_seed);
  return (0,0,0,0) unless $status;
  $cert = cmpint($c0,"18446744073709551615") <= 0
         ?  ""  :  _strip_proof_header($cert);
  my $iterations = int(($k + 255) / 256) - 1;  # SHA256 generates 256 bits
  my $old_counter = $prime_gen_counter;
  my $c02 = lshiftint($c0);  # $c02 = 2*$c0
  my $xstr = '';
  for my $i (0 .. $iterations) {
    $xstr = Digest::SHA::sha256_hex($seed) . $xstr;
    $seed = _seed_plus_one($seed);
  }
  my $x = fromdigits($xstr,16);
  $x = $k2 + ($x % $k2);
  my $t = cdivint($x, $c02);
  _make_big_gcds() if $_big_gcd_use < 0;
  while (1) {
    my $c = add1int(mulint($t,$c02));
    if ($c > 2*$k2) {
      $t = cdivint($k2, $c02);
      $c = add1int(mulint($t,$c02));
    }
    $prime_gen_counter++;

    # Don't do the Pocklington check unless the candidate looks prime
    my $looks_prime = 0;
    if (MPU_USE_GMP) {
      # MPU::GMP::is_prob_prime has fast tests built in.
      $looks_prime = Math::Prime::Util::GMP::is_prob_prime($c);
    } else {
      # No GMP, so first do trial divisions, then a SPSP test.
      $looks_prime = gcd($c, 111546435) == 1;
      if ($looks_prime && $_big_gcd_use && $c > $_big_gcd_top) {
        $looks_prime = gcd($c, $_big_gcd[0]) == 1 &&
                       gcd($c, $_big_gcd[1]) == 1 &&
                       gcd($c, $_big_gcd[2]) == 1 &&
                       gcd($c, $_big_gcd[3]) == 1;
      }
      $looks_prime = 0 if $looks_prime && !is_strong_pseudoprime($c, 3);
    }

    if ($looks_prime) {
      # We could use a in (2,3,5,7,11,13), but pedantically use FIPS 186-4.
      my $astr = '';
      for my $i (0 .. $iterations) {
        $astr = Digest::SHA::sha256_hex($seed) . $astr;
        $seed = _seed_plus_one($seed);
      }
      my $a = fromdigits($astr,16);
      $a = addint(modint($a,$c-3),2);
      my $z = powmod($a, lshiftint($t), $c);
      if (gcd($z-1,$c) == 1 && powmod($z, $c0, $c) == 1) {
        croak "Shawe-Taylor random prime failure at ($k): $c not prime"
          unless is_prob_prime($c);
        $cert = "[MPU - Primality Certificate]\nVersion 1.0\n\n" .
                 "Proof for:\nN $c\n\n" .
                 "Type Pocklington\nN $c\nQ $c0\nA $a\n" .
                 $cert;
        return (1, $c, $seed, $prime_gen_counter, $cert);
      }
    } else {
      # Update seed "as if" we performed the Pocklington check from FIPS 186-4
      for my $i (0 .. $iterations) {
        $seed = _seed_plus_one($seed);
      }
    }
    return (0,0,0,0) if $prime_gen_counter > 10000 + 16*$k + $old_counter;
    $t++;
  }
}

sub random_safe_prime {
  my $bits = int("$_[0]");
  croak "random_safe_prime, bits must be >= 3" unless $bits >= 3;
  return (5,7)[urandomb(1)] if $bits == 3;
  return 11 if $bits == 4;
  return 23 if $bits == 5;
  return (47,59)[urandomb(1)] if $bits == 6;
  return (83,107)[urandomb(1)] if $bits == 7;

  # Without GMP (e.g. Calc), this can be significantly faster.
  # With GMP, they are about the same.
  return _random_safe_prime_large($bits) if $bits > 35;

  my($p,$q);
  while (1) {
    $q = Math::Prime::Util::random_nbit_prime($bits-1);
    my $qm = modint($q, 1155);  # a nice native int
    next if ($qm % 3) != 2
         || ($qm % 5) == 2 || ($qm % 7) == 3 || ($qm % 11) == 5;
    $p = mulint(2, $q) + 1;
    # This is sufficient, but we'll do the full test including pre-tests.
    #last if is_pseudoprime($p,2);  # p is prime if q is prime
    last if is_prob_prime($p);
  }
  return $p;
}

sub _random_safe_prime_large {
  my $bits = shift;
  croak "Not enough bits for large random_safe_prime" if $bits <= 35;

  # Set first and last two bits
  my $base = addint(lshiftint(1, $bits-1),3);
  # Fill in lower portion with random bits, leaving 32 upper
  $base = addint($base, lshiftint(urandomb($bits - 35), 2));

  while (1) {
    my($p,$q,$qmod,$pmod);
    # 1. generate random nbit p
    $p = lshiftint(urandomb(32), $bits-33);
    $p = addint($base, $p);

    # 2. p = 2q+1  =>  q = p>>1
    $q = rshiftint($p);

    # 3. Force q mod 6 = 5
    $qmod = modint(add1int($q),6);
    if ($qmod > 0) {
      $q = subint($q,$qmod);
      $q = addint($q,12) if 1+logint($q,2) != $bits-1;
      $p = add1int(lshiftint($q));
    }



( run in 0.326 second using v1.01-cache-2.11-cpan-71847e10f99 )