+#! /usr/bin/python
+### -*- mode: python, coding: utf-8 -*-
+###
+### Tool for generating and verifying primality certificates
+###
+### (c) 2017 Straylight/Edgeware
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the Python interface to Catacomb.
+###
+### Catacomb/Python is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### Catacomb/Python is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with Catacomb/Python; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+###--------------------------------------------------------------------------
+### Imported modules.
+
+from sys import argv, stdin, stdout, stderr
+import os as OS
+import itertools as I
+import math as M
+import optparse as OP
+
+import catacomb as C
+
+###--------------------------------------------------------------------------
+### Utilities.
+
+class ExpectedError (Exception):
+ """
+ I represent an expected error, which should be reported in a friendly way.
+ """
+ pass
+
+def prod(ff, one = 1):
+ """
+ Return ONE times the product of the elements of FF.
+
+ This is not done very efficiently.
+ """
+ return reduce(lambda prod, f: prod*f, ff, one)
+
+def parse_label(line):
+ """
+ Split LINE at an `=' character and return the left and right halves.
+
+ The returned pieces have leading and trailing whitespace trimmed.
+ """
+ eq = line.find('=')
+ if eq < 0: raise ExpectedError('expected `LABEL = ...\'')
+ return line[:eq].strip(), line[eq + 1:].strip()
+
+def parse_list(s, n):
+ l = s.split(',', n - 1)
+ if n is not None and len(l) != n:
+ raise ExpectedError('expected `,\'-separated list of %d items' % n)
+ return l
+
+def conv_int(s):
+ """Convert S to a integer."""
+ try: return C.MP(s, 0)
+ except TypeError: raise ExpectedError('invalid integer `%s\'' % s)
+
+VERBOSITY = 1
+
+class ProgressReporter (object):
+ """
+ I keep users amused while the program looks for large prime numbers.
+
+ My main strategy is the printing of incomprehensible runes. I can be
+ muffled by lowering by verbosity level.
+
+ Prime searches are recursive in nature. When a new recursive level is
+ started, call `push'; and call `pop' when the level is finished. This must
+ be done around the top level too.
+ """
+ def __init__(me):
+ """Initialize the ProgressReporter."""
+ me._level = -1
+ me._update()
+ def _update(me):
+ """
+ Update our idea of whether we're active.
+
+ We don't write inscrutable runes when inactive. The current policy is to
+ write nothing if verbosity is zero, to write runes for the top level only
+ if verbosity is 1, and to write runes always if verbosity is higher than
+ that.
+ """
+ me._active = VERBOSITY >= 2 or (VERBOSITY == 1 and me._level == 0)
+ def push(me):
+ """Push a new search level."""
+ me._level += 1
+ me._update()
+ if me._level > 0: me.p('[')
+ else: me.p(';; ')
+ def pop(me):
+ """Pop a search level."""
+ if me._level > 0: me.p(']')
+ else: me.p('\n')
+ me._level -= 1
+ me._update()
+ def p(me, ch):
+ """Print CH as a progress rune."""
+ if me._active: stderr.write(ch); stderr.flush()
+
+def combinations(r, v):
+ """
+ Return an iterator which yields all combinations of R elements from V.
+
+ V must be an indexable sequence. The each combination is returned as a
+ list, containing elements from V in their original order.
+ """
+
+ ## Set up the selection vector. C will contain the indices of the items of
+ ## V we've selected for the current combination. At all times, C contains
+ ## a strictly increasing sequence of integers in the interval [0, N).
+ n = len(v)
+ c = range(r)
+
+ while True:
+
+ ## Yield up the current combination.
+ vv = [v[i] for i in c]
+ yield vv
+
+ ## Now advance to the next one. Find the last index in C which we can
+ ## increment subject to the rules. As we iterate downwards, i will
+ ## contain the index into C, and j will be the maximum acceptable value
+ ## for the corresponding item. We'll step the last index until it
+ ## reaches the limit, and then step the next one down, resetting the last
+ ## index, and so on.
+ i, j = r, n
+ while True:
+
+ ## If i is zero here, then we've advanced everything as far as it will
+ ## go. We're done.
+ if i == 0: return
+
+ ## Move down to the next index.
+ i -= 1; j -= 1
+
+ ## If this index isn't at its maximum value, then we've found the place
+ ## to step.
+ if c[i] != j: break
+
+ ## Step this index on by one, and set the following indices to the
+ ## immediately following values.
+ j = c[i] + 1
+ while i < r: c[i] = j; i += 1; j += 1
+
+class ArgFetcher (object):
+ """
+ I return arguments from a list, reporting problems when they occur.
+ """
+ def __init__(me, argv, errfn):
+ """
+ Initialize, returning successive arguments from ARGV.
+
+ Errors are reported to ERRFN.
+ """
+ me._argv = argv
+ me._argc = len(argv)
+ me._errfn = errfn
+ me._i = 0
+ def arg(me, default = None, must = True):
+ """
+ Return the next argument.
+
+ If MUST is true, then report an error (to the ERRFN) if there are no more
+ arguments; otherwise, return the DEFAULT.
+ """
+ if me._i >= me._argc:
+ if must: me._errfn('missing argument')
+ return default
+ arg = me._argv[me._i]; me._i += 1
+ return arg
+ def int(me, default = None, must = True, min = None, max = None):
+ """
+ Return the next argument converted to an integer.
+
+ If MUST is true, then report an error (to the ERRFN) if there are no more
+ arguments; otherwise return the DEFAULT. Report an error if the next
+ argument is not a valid integer, or if the integer is beyond the MIN and
+ MAX bounds.
+ """
+ arg = me.arg(default = None, must = must)
+ if arg is None: return default
+ try: arg = int(arg)
+ except ValueError: me._errfn('bad integer')
+ if (min is not None and arg < min) or (max is not None and arg > max):
+ me._errfn('out of range')
+ return arg
+
+###--------------------------------------------------------------------------
+### Sieving for small primes.
+
+class Sieve (object):
+ """
+ I represent a collection of small primes, up to some chosen limit.
+
+ The limit is available as the `limit' attribute. Let L be this limit;
+ then, if N < L^2 is some composite, then N has at least one prime factor
+ less than L.
+ """
+
+ ## Figure out the number of bits in a (nonnegative) primitive `int'. We'll
+ ## use a list of these as our sieve.
+ _NBIT = 15
+ while type(1 << (_NBIT + 1)) == int: _NBIT += 1
+
+ def __init__(me, limit):
+ """
+ Initialize a sieve holding all primes below LIMIT.
+ """
+
+ ## The sieve is maintained in the `_bits' attribute. This is a list of
+ ## integers, used as a bitmask: let 2 < n < L be an odd integer; then bit
+ ## (n - 3)/2 will be clear iff n is prime. Let W be the value of
+ ## `_NBIT', above; then bit W i + j in the sieve is stored in bit j of
+ ## `_bits[i]'.
+
+ ## Store the limit for later inspection.
+ me.limit = limit
+
+ ## Calculate the size of sieve we'll need and initialize the bit list.
+ n = (limit - 2)/2
+ sievesz = (n + me._NBIT - 1)/me._NBIT
+ me._sievemax = sievesz*me._NBIT
+ me._bits = n*[0]
+
+ ## This is standard Sieve of Eratosthenes. For each index i: if
+ ## bit i is clear, then p = 2 i + 3 is prime, so set the bits
+ ## corresponding to each multiple of p, i.e., bits (k p - 3)/2 =
+ ## (2 k i + 3 - 3)/2 = k i for k > 1.
+ for i in xrange(me._sievemax):
+ if me._bitp(i): i += 1; continue
+ p = 2*i + 3
+ if p >= limit: break
+ for j in xrange(i + p, me._sievemax, p): me._setbit(j)
+ i += 1
+
+ def _bitp(me, i): i, j = divmod(i, me._NBIT); return (me._bits[i] >> j)&1
+ def _setbit(me, i): i, j = divmod(i, me._NBIT); me._bits[i] |= 1 << j
+
+ def smallprimes(me):
+ """
+ Return an iterator over the known small primes.
+ """
+ yield 2
+ n = 3
+ for b in me._bits:
+ for j in xrange(me._NBIT):
+ if not (b&1): yield n
+ b >>= 1; n += 2
+
+## We generate the sieve on demand.
+SIEVE = None
+
+def initsieve(sievebits):
+ """
+ Generate the sieve.
+
+ Ensure that it can be used to check the primality of numbers up to (but not
+ including) 2^SIEVEBITS.
+ """
+ global SIEVE
+ if SIEVE is not None: raise ValueError('sieve already defined')
+ if sievebits < 6: sievebits = 6
+ SIEVE = Sieve(1 << (sievebits + 1)/2)
+
+###--------------------------------------------------------------------------
+### Primality checking.
+
+def small_test(p):
+ """
+ Check that P is a small prime.
+
+ If not, raise an `ExpectedError'. The `SIEVE' variable must have been
+ initialized.
+ """
+ if p < 2: raise ExpectedError('%d too small' % p)
+ if SIEVE.limit*SIEVE.limit < p:
+ raise ExpectedError('%d too large for small prime' % p)
+ for q in SIEVE.smallprimes():
+ if q*q > p: return
+ if p%q == 0: raise ExpectedError('%d divides %d' % (q, p))
+
+def pock_test(p, a, qq):
+ """
+ Check that P is prime using Pocklington's criterion.
+
+ If not, raise an `ExpectedError'.
+
+ Let Q be the product of the elements of the sequence QQ. The test works as
+ follows. Suppose p is the smallest prime factor of P. If A^{P-1} /== 1
+ (mod P) then P is certainly composite (Fermat's test); otherwise, we have
+ established that the order of A in (Z/pZ)^* divides P - 1. Next, let t =
+ A^{(P-1)/q} for some prime factor q of Q, and let g = gcd(t - 1, P). If g
+ = P then the proof is inconclusive; if 1 < g < P then g is a nontrivial
+ factor of P, so P is composite; otherwise, t has order q in (Z/pZ)^*, so
+ (Z/pZ)^* contains a subgroup of size q, and therefore q divides p - 1. If
+ QQ is a sequence of distinct primes, and the preceding criterion holds for
+ all q in QQ, then Q divides p - 1. If Q^2 < P then the proof is
+ inconclusive; otherwise, let p' be any prime dividing P/p. Then p' >= p >
+ Q, so p p' > Q^2 > P; but p p' divides P, so this is a contradiction.
+ Therefore P/p has no prime factors, and P is prime.
+ """
+
+ ## We don't actually need the distinctness criterion. Suppose that q^e
+ ## divides Q. Then gcd(t - 1, P) = 1 implies that A^{(P-1)/q^{e-1}} has
+ ## order q^e in (Z/pZ)^*, which accounts for the multiplicity.
+
+ Q = prod(qq)
+ if p < 2: raise ExpectedError('%d too small' % p)
+ if Q*Q <= p:
+ raise ExpectedError('too few Pocklington factors for %d' % p)
+ if pow(a, p - 1, p) != 1:
+ raise ExpectedError('%d is Fermat witness for %d' % (a, p))
+ for q in qq:
+ if Q%(q*q) == 0:
+ raise ExpectedError('duplicate Pocklington factor %d for %d' % (q, p))
+ g = p.gcd(pow(a, (p - 1)/q, p) - 1)
+ if g == p:
+ raise ExpectedError('%d order not multiple of %d mod %d' % (a, q, p))
+ elif g != 1:
+ raise ExpectedError('%d divides %d' % (g, p))
+
+def ecpp_test(p, a, b, x, y, qq):
+ """
+ Check that P is prime using Goldwasser and Kilian's ECPP method.
+
+ If not, raise an `ExpectedError'.
+
+ Let Q be the product of the elements of the sequence QQ. Suppose p is the
+ smallest prime factor of P. Let g = gcd(4 A^3 + 27 B^2, P). If g = P then
+ the test is inconclusive; otherwise, if g /= 1 then g is a nontrivial
+ factor of P. Define E(GF(p)) = { (x, y) | y^2 = x^3 + A x + B } U { inf }
+ to be the elliptic curve over p with short-Weierstraß coefficients A and B;
+ we have just checked that this curve is not singular. If R = (X, Y) is not
+ a point on this curve, then the test is inconclusive. If Q R is not the
+ point at infinity, then the test fails; otherwise we deduce that P has
+ Q-torsion in E. Let S = (Q/q) R for some prime factor q of Q. If S is the
+ point at infinity then the test is inconclusive; otherwise, q divides the
+ order of S in E. If QQ is a sequence of distinct primes, and the preceding
+ criterion holds for all q in QQ, then Q divides the order of S. Therefore
+ #E(p) >= Q. If Q <= (qrrt(P) + 1)^2 then the test is inconclusive.
+ Otherwise, Hasse's theorem tells us that |p + 1 - #E(p)| <= 2 sqrt(p);
+ hence we must have p + 1 + 2 sqrt(p) = (sqrt(p) + 1)^2 >= #E(p) >= Q >
+ (qrrt(P) + 1)^2; so sqrt(p) + 1 > qrrt(P) + 1, i.e., p^2 > P. As for
+ Pocklington above, if p' is any prime factor of P/p, then p p' >= p^2 > P,
+ which is a contradiction, and we conclude that P is prime.
+ """
+
+ ## This isn't going to work if gcd(P, 6) /= 1: we're going to use the
+ ## large-characteristic addition formulae.
+ g = p.gcd(6)
+ if g != 1: raise ExpectedError('%d divides %d' % (g, p))
+
+ ## We want to check that Q > (qrrt(P) + 1)^2 iff sqrt(Q) > qrrt(P) + 1; but
+ ## calculating square roots is not enjoyable (partly because we have to
+ ## deal with the imprecision). Fortunately, some algebra will help: the
+ ## condition holds iff qrrt(P) < sqrt(Q) - 1 iff P < Q^2 - 4 Q sqrt(Q) +
+ ## 6 Q - 4 sqrt(Q) + 1 = Q (Q + 6) + 1 - 4 sqrt(Q) (Q + 1) iff Q (Q + 6) -
+ ## P + 1 > 4 sqrt(Q) (Q + 1) iff (Q (Q + 6) - P + 1)^2 > 16 Q (Q + 1)^2
+ Q = prod(qq)
+ t, u = Q*(Q + 6) - p + 1, 4*(Q + 1)
+ if t*t <= Q*u*u: raise ExpectedError('too few subgroups for ECPP')
+
+ ## Construct the curve.
+ E = C.PrimeField(p).ec(a, b) # careful: may not be a prime!
+
+ ## Find the base point.
+ R = E(x, y)
+ if not R.oncurvep():
+ raise ExpectedError('(%d, %d) is not on the curve' % (x, y))
+
+ ## Check that it has Q-torsion.
+ if Q*R: raise ExpectedError('(%d, %d) not a %d-torsion point' % (x, y, Q))
+
+ ## Now check the individual factors.
+ for q in qq:
+ if Q%(q*q) == 0:
+ raise ExpectedError('duplicate ECPP factor %d for %d' % (q, p))
+ S = (Q/q)*R
+ if not S:
+ raise ExpectedError('(%d, %d) order not a multiple of %d' % (x, y, q))
+ g = p.gcd(S._z)
+ if g != 1:
+ raise ExpectedError('%d divides %d' % (g, p))
+
+###--------------------------------------------------------------------------
+### Proof steps and proofs.
+
+class BaseStep (object):
+ """
+ I'm a step in a primality proof.
+
+ I assert that a particular number is prime, and can check this.
+
+ This class provides basic protocol for proof steps, mostly to do with
+ handling labels.
+
+ The step's label is kept in its `label' attribute. It can be set by the
+ constructor, and is `None' by default. Users can modify this attribute if
+ they like. Labels beginning `$' are assumed to be internal and
+ uninteresting; other labels cause `check' lines to be written to the output
+ listing the actual number of interest.
+
+ Protocol that proof steps should provide:
+
+ label A string labelling the proof step and the associated prime
+ number.
+
+ p The prime number which this step proves to be prime.
+
+ check() Check that the proof step is actually correct, assuming that
+ any previous steps have already been verified.
+
+ out(FILE) Write an appropriate encoding of the proof step to the output
+ FILE.
+ """
+ def __init__(me, label = None, *arg, **kw):
+ """Initialize a proof step, setting a default label if necessary."""
+ super(BaseStep, me).__init__(*arg, **kw)
+ me.label = label
+ def out(me, file):
+ """
+ Write the proof step to an output FILE.
+
+ Subclasses must implement a method `_out' which actually does the work.
+ Here, we write a `check' line to verify that the proof actually applies
+ to the number we wanted, if the label says that this is an interesting
+ step.
+ """
+ me._out(file)
+ if me.label is not None and not me.label.startswith('$'):
+ file.write('check %s, %d, %d\n' % (me.label, me.p.nbits, me.p))
+
+class SmallStep (BaseStep):
+ """
+ I represent a claim that a number is a small prime.
+
+ Such claims act as the base cases in a complicated primality proof. When
+ verifying, the claim is checked by trial division using a collection of
+ known small primes.
+ """
+ def __init__(me, pp, p, *arg, **kw):
+ """
+ Initialize a small-prime step.
+
+ PP is the overall PrimeProof object of which this is a step; P is the
+ small number whose primality is asserted.
+ """
+ super(SmallStep, me).__init__(*arg, **kw)
+ me.p = p
+ def check(me):
+ """Check that the number is indeed a small prime."""
+ return small_test(me.p)
+ def _out(me, file):
+ """Write a small-prime step to the FILE."""
+ file.write('small %s = %d\n' % (me.label, me.p))
+ def __repr__(me): return 'SmallStep(%d)' % (me.p)
+ @classmethod
+ def parse(cls, pp, line):
+ """
+ Parse a small-prime step from a LINE in a proof file.
+
+ SMALL-STEP ::= `small' LABEL `=' P
+
+ PP is a PrimeProof object holding the results from the previous steps.
+ """
+ if SIEVE is None: raise ExpectedError('missing `sievebits\' line')
+ label, p = parse_label(line)
+ return cls(pp, conv_int(p), label = label)
+
+class PockStep (BaseStep):
+ """
+ I represent a Pocklington certificate for a number.
+
+ The number is not explicitly represented in a proof file. See `pock_test'
+ for the underlying mathematics.
+ """
+ def __init__(me, pp, a, R, qqi, *arg, **kw):
+ """
+ Inititialize a Pocklington step.
+
+ PP is the overall PrimeProof object of which this is a step; A is the
+ generator of a substantial subgroup of units; R is a cofactor; and QQI is
+ a sequence of labels for previous proof steps. If Q is the product of
+ the primes listed in QQI, then the number whose primality is asserted is
+ 2 Q R + 1.
+ """
+ super(PockStep, me).__init__(*arg, **kw)
+ me._a = a
+ me._R = R
+ me._qqi = qqi
+ me._qq = [pp.get_step(qi).p for qi in qqi]
+ me.p = prod(me._qq, 2*R) + 1
+ def check(me):
+ """Verify a proof step based on Pocklington's theorem."""
+ return pock_test(me.p, me._a, me._qq)
+ def _out(me, file):
+ """Write a Pocklington step to the FILE."""
+ file.write('pock %s = %d, %d, [%s]\n' % \
+ (me.label, me._a,
+ me._R, ', '.join('%s' % qi for qi in me._qqi)))
+ def __repr__(me): return 'PockStep(%d, %d, %s)' % (me._a, me._R, me._qqi)
+ @classmethod
+ def parse(cls, pp, line):
+ """
+ Parse a Pocklington step from a LINE in a proof file.
+
+ POCK-STEP ::= `pock' LABEL `=' A `,' R `,' `[' Q-LIST `]'
+ Q-LIST ::= Q [`,' Q-LIST]
+
+ PP is a PrimeProof object holding the results from the previous steps.
+ """
+ label, rest = parse_label(line)
+ a, R, qq = parse_list(rest, 3)
+ qq = qq.strip()
+ if not qq.startswith('[') or not qq.endswith(']'):
+ raise ExpectedError('missing `[...]\' around Pocklington factors')
+ return cls(pp, conv_int(a), conv_int(R),
+ [q.strip() for q in qq[1:-1].split(',')], label = label)
+
+class ECPPStep (BaseStep):
+ """
+ I represent a Goldwasser--Kilian ECPP certificate for a number.
+ """
+ def __init__(me, pp, p, a, b, x, y, qqi, *arg, **kw):
+ """
+ Inititialize an ECPP step.
+
+ PP is the overall PrimeProof object of which this is a step; P is the
+ number whose primality is asserted; A and B are the short Weierstraß
+ curve coefficients; X and Y are the base point coordinates; and QQI is a
+ sequence of labels for previous proof steps.
+ """
+ super(ECPPStep, me).__init__(*arg, **kw)
+ me._a, me._b = a, b
+ me._x, me._y = x, y
+ me._qqi = qqi
+ me._qq = [pp.get_step(qi).p for qi in qqi]
+ me.p = p
+ def check(me):
+ """Verify a proof step based on Goldwasser and Kilian's theorem."""
+ return ecpp_test(me.p, me._a, me._b, me._x, me._y, me._qq)
+ def _out(me, file):
+ """Write an ECPP step to the FILE."""
+ file.write('ecpp %s = %d, %d, %d, %d, %d, [%s]\n' % \
+ (me.label, me.p, me._a, me._b, me._x, me._y,
+ ', '.join('%s' % qi for qi in me._qqi)))
+ def __repr__(me):
+ return 'ECPPstep(%d, %d, %d, %d, %d, %s)' % \
+ (me.p, me._a, me._b, me._x, me._y, me._qqi)
+ @classmethod
+ def parse(cls, pp, line):
+ """
+ Parse an ECPP step from a LINE in a proof file.
+
+ ECPP-STEP ::= `ecpp' LABEL `=' P `,' A `,' B `,' X `,' Y `,'
+ `[' Q-LIST `]'
+ Q-LIST ::= Q [`,' Q-LIST]
+
+ PP is a PrimeProof object holding the results from the previous steps.
+ """
+ label, rest = parse_label(line)
+ p, a, b, x, y, qq = parse_list(rest, 6)
+ qq = qq.strip()
+ if not qq.startswith('[') or not qq.endswith(']'):
+ raise ExpectedError('missing `[...]\' around ECPP factors')
+ return cls(pp, conv_int(p), conv_int(a), conv_int(b),
+ conv_int(x), conv_int(y),
+ [q.strip() for q in qq[1:-1].split(',')], label = label)
+
+def check(pp, line):
+ """
+ Handle a `check' line in a proof file.
+
+ CHECK ::= `check' LABEL, B, N
+
+ Verify that the proof step with the given LABEL asserts the primality of
+ the integer N, and that 2^{B-1} <= N < 2^B.
+ """
+ label, nb, p = parse_list(line, 3)
+ label, nb, p = label.strip(), conv_int(nb), conv_int(p)
+ pi = pp.get_step(label).p
+ if pi != p:
+ raise ExpectedError('check failed: %s = %d /= %d' % (label, pi, p))
+ if p.nbits != nb:
+ raise ExpectedError('check failed: nbits(%s) = %d /= %d' % \
+ (label, p.nbits, nb))
+ if VERBOSITY: print ';; %s = %d [%d]' % (label, p, nb)
+
+def setsievebits(pp, line):
+ """
+ Handle a `sievebits' line in a proof file.
+
+ SIEVEBITS ::= `sievebits' N
+
+ Ensure that the verifier is willing to accept small primes up to 2^N.
+ """
+ initsieve(int(line))
+
+class PrimeProof (object):
+ """
+ I represent a proof of primality for one or more numbers.
+
+ I can encode my proof as a line-oriented text file, in a simple format, and
+ read such a proof back to check it.
+ """
+
+ ## A table to dispatch on keywords read from a file.
+ STEPMAP = { 'small': SmallStep.parse,
+ 'pock': PockStep.parse,
+ 'ecpp': ECPPStep.parse,
+ 'sievebits': setsievebits,
+ 'check': check }
+
+ def __init__(me):
+ """
+ Initialize a proof object.
+ """
+ me._steps = {} # Maps labels to steps.
+ me._stepseq = [] # Sequence of labels, in order.
+ me._pmap = {} # Maps primes to steps.
+ me._i = 0
+
+ def addstep(me, step):
+ """
+ Add a new STEP to the proof.
+
+ The STEP may have a label already. If not, a new internal label is
+ chosen. The proof step is checked before being added to the proof. The
+ label is returned.
+ """
+
+ ## If there's already a step for this prime, and the new step doesn't
+ ## have a label, then return the old one instead.
+ if step.label is None:
+ try: return me._pmap[step.p]
+ except KeyError: pass
+
+ ## Make sure the step is actually correct.
+ step.check()
+
+ ## Generate a label if the step doesn't have one already.
+ if step.label is None: step.label = '$t%d' % me._i; me._i += 1
+
+ ## If the label is already taken then we have a problem.
+ if step.label in me._steps:
+ raise ValueError('duplicate label `%s\'' % step.label)
+
+ ## Store the proof step.
+ me._pmap[step.p] = step.label
+ me._steps[step.label] = step
+ me._stepseq.append(step.label)
+ return step.label
+
+ def get_step(me, label):
+ """
+ Check that LABEL labels a known step, and return that step.
+ """
+ try: return me._steps[label]
+ except KeyError: raise ExpectedError('unknown label `%s\'' % label)
+
+ def write(me, file):
+ """
+ Write the proof to the given FILE.
+ """
+
+ ## Prefix the main steps with a `sievebits' line.
+ file.write('sievebits %d\n' % (2*(SIEVE.limit.bit_length() - 1)))
+
+ ## Write the steps out one by one.
+ for label in me._stepseq: me._steps[label].out(file)
+
+ def read(me, file):
+ """
+ Read a proof from a given FILE.
+
+ FILE ::= {STEP | CHECK | SIEVEBITS} [FILE]
+ STEP ::= SMALL-STEP | POCK-STEP
+
+ Comments (beginning `;') and blank lines are ignored. Other lines begin
+ with a keyword.
+ """
+ lastp = None
+ for lno, line in enumerate(file, 1):
+ line = line.strip()
+ if line.startswith(';'): continue
+ ww = line.split(None, 1)
+ if not ww: continue
+ w = ww[0]
+ if len(ww) > 1: tail = ww[1]
+ else: tail = ''
+ try:
+ try: op = me.STEPMAP[w]
+ except KeyError:
+ raise ExpectedError('unrecognized keyword `%s\'' % w)
+ step = op(me, tail)
+ if step is not None:
+ me.addstep(step)
+ lastp = step.p
+ except ExpectedError, e:
+ raise ExpectedError('%s:%d: %s' % (file.name, lno, e.message))
+ return lastp
+
+###--------------------------------------------------------------------------
+### Finding provable primes.
+
+class BasePrime (object):
+ """
+ I represent a prime number which has been found and can be proven.
+
+ This object can eventually be turned into a sequence of proof steps and
+ added to a PrimeProof. This isn't done immediately, because some
+ prime-search strategies want to build a pool of provable primes and will
+ then select some subset of them to actually construct the number of final
+ interest. This way, we avoid cluttering the output proof with proofs of
+ uninteresting numbers.
+
+ Protocol required.
+
+ p The prime number in question.
+
+ label(LABEL) Associate LABEL with this prime, and the corresponding proof
+ step. A label can be set in the constructor, or later using
+ this method.
+
+ register(PP) Register the prime with a PrimeProof, adding any necessary
+ proof steps. Returns the label of the proof step for this
+ number.
+
+ _mkstep(PP, **KW)
+ Return a proof step for this prime.
+ """
+ def __init__(me, label = None, *args, **kw):
+ """Initialize a provable prime number object."""
+ super(BasePrime, me).__init__(*args, **kw)
+ me._index = me._pp = None
+ me._label = label
+ def label(me, label):
+ """Set this number's LABEL."""
+ me._label = label
+ def register(me, pp):
+ """
+ Register the prime's proof steps with PrimeProof PP.
+
+ Return the final step's label.
+ """
+ if me._pp is not None:
+ assert me._pp == pp
+ else:
+ me._pp = pp
+ me._index = pp.addstep(me._mkstep(pp, label = me._label))
+ ##try: me._index = pp.addstep(me._mkstep(pp, label = me._label))
+ ##except: raise RuntimeError('generated proof failed sanity check')
+ return me._index
+
+class SmallPrime (BasePrime):
+ """I represent a prime small enough to be checked in isolation."""
+ def __init__(me, p, *args, **kw):
+ super(SmallPrime, me).__init__(*args, **kw)
+ me.p = p
+ def _mkstep(me, pp, **kw):
+ return SmallStep(pp, me.p, **kw)
+
+class PockPrime (BasePrime):
+ """I represent a prime proven using Pocklington's theorem."""
+ def __init__(me, p, a, qq, *args, **kw):
+ super(PockPrime, me).__init__(*args, **kw)
+ me.p = p
+ me._a = a
+ me._qq = qq
+ def _mkstep(me, pp, **kw):
+ return PockStep(pp, me._a, (me.p - 1)/prod((q.p for q in me._qq), 2),
+ [q.register(pp) for q in me._qq], **kw)
+
+def gen_small(nbits, label = None, p = None):
+ """
+ Return a new small prime.
+
+ The prime will be exactly NBITS bits long. The proof step will have the
+ given LABEL attached. Report progress to the ProgressReporter P.
+ """
+ while True:
+
+ ## Pick a random NBITS-bit number.
+ n = C.rand.mp(nbits, 1)
+ assert n.nbits == nbits
+
+ ## If it's probably prime, then check it against the small primes we
+ ## know. If it passes then we're done. Otherwise, try again.
+ if n.primep():
+ for q in SIEVE.smallprimes():
+ if q*q > n: return SmallPrime(n, label = label)
+ if n%q == 0: break
+
+def gen_pock(nbits, nsubbits = 0, label = None, p = ProgressReporter()):
+ """
+ Return a new prime provable using Pocklington's theorem.
+
+ The prime N will be exactly NBITS long, of the form N = 2 Q R + 1. If
+ NSUBBITS is nonzero, then each prime factor of Q will be NSUBBITS bits
+ long; otherwise a suitable default will be chosen. The proof step will
+ have the given LABEL attached. Report progress to the ProgressReporter P.
+
+ The prime numbers this function returns are a long way from being uniformly
+ distributed.
+ """
+
+ ## Pick a suitable value for NSUBBITS if we don't have one.
+ if not nsubbits:
+
+ ## This is remarkably tricky. Picking about 1/3 sqrt(NBITS) factors
+ ## seems about right for large numbers, but there's serious trouble
+ ## lurking for small sizes.
+ nsubbits = int(3*M.sqrt(nbits))
+ if nbits < nsubbits + 3: nsubbits = nbits//2 + 1
+ if nbits == 2*nsubbits: nsubbits += 1
+
+ ## Figure out how many subgroups we'll need.
+ npiece = ((nbits + 1)//2 + nsubbits - 1)//nsubbits
+ p.push()
+
+ ## Keep searching...
+ while True:
+
+ ## Come up with a collection of known prime factors.
+ p.p('!'); qq = [gen(nsubbits, p = p) for i in xrange(npiece)]
+ Q = prod(q.p for q in qq)
+
+ ## Come up with bounds on the cofactor. If we're to have N = 2 Q R + 1,
+ ## and 2^{B-1} <= N < 2^B, then we must have 2^{B-2}/Q <= R < 2^{B-1}/Q.
+ Rbase = (C.MP(0).setbit(nbits - 2) + Q - 1)//Q
+ Rwd = C.MP(0).setbit(nbits - 2)//Q
+
+ ## Probe the available space of cofactors. If the space is kind of
+ ## narrow, then we want to give up quickly if we're not finding anything
+ ## suitable.
+ step = 0
+ while step < Rwd:
+ step += 1
+
+ ## Pick a random cofactor and examine the number we ended up with.
+ ## Make sure it really does have the length we expect.
+ R = C.rand.range(Rwd) + Rbase
+ n = 2*Q*R + 1
+ assert n.nbits == nbits
+
+ ## As a complication, if NPIECE is 1, it's just about possible that Q^2
+ ## <= n, in which case this isn't going to work.
+ if Q*Q < n: continue
+
+ ## If n has small factors, then pick another cofactor.
+ if C.PrimeFilter.smallfactor(n) == C.PGEN_FAIL: continue
+
+ ## Work through the small primes to find a suitable generator. The
+ ## value 2 is almost always acceptable, so don't try too hard here.
+ for a in I.islice(SIEVE.smallprimes(), 16):
+
+ ## First, try the Fermat test. If that fails, then n is definitely
+ ## composite.
+ if pow(a, n - 1, n) != 1: p.p('.'); break
+ p.p('*')
+
+ ## Work through the subgroup orders, checking that suitable powers of
+ ## a generate the necessary subgroups.
+ for q in qq:
+ if n.gcd(pow(a, (n - 1)/q.p, n) - 1) != 1:
+ p.p('@'); ok = False; break
+ else:
+ ok = True
+
+ ## we're all good.
+ if ok: p.pop(); return PockPrime(n, a, qq, label = label)
+
+def gen(nbits, label = None, p = ProgressReporter()):
+ """
+ Generate a prime number with NBITS bits.
+
+ Give it the LABEL, and report progress to P.
+ """
+ if SIEVE.limit >> (nbits + 1)/2: g = gen_small
+ else: g = gen_pock
+ return g(nbits, label = label, p = p)
+
+def gen_limlee(nbits, nsubbits,
+ label = None, qlfmt = None, p = ProgressReporter()):
+ """
+ Generate a Lim--Lee prime with NBITS bits.
+
+ Let p be the prime. Then we'll have p = 2 q_0 q_1 ... q_k, with all q_i at
+ least NSUBBITS bits long, and all but q_k exactly that long.
+
+ The prime will be given the LABEL; progress is reported to P. The factors
+ q_i will be labelled by filling in the `printf'-style format string QLFMT
+ with the argument i.
+ """
+
+ ## Figure out how many factors (p - 1)/2 will have.
+ npiece = nbits//nsubbits
+ if npiece < 2: raise ExpectedError('too few pieces')
+
+ ## Decide how big to make the pool of factors.
+ poolsz = max(3*npiece + 5, 25) # Heuristic from GnuPG
+
+ ## Prepare for the main loop.
+ disp = nstep = 0
+ qbig = None
+ p.push()
+
+ ## Try to make a prime.
+ while True:
+ p.p('!')
+
+ ## Construct a pool of NSUBBITS-size primes. There's a problem with very
+ ## small sizes: we might not be able to build a pool of distinct primes.
+ pool = []; qmap = {}
+ for i in xrange(poolsz):
+ for j in xrange(64):
+ q = gen(nsubbits, p = p)
+ if q.p not in qmap: break
+ else:
+ raise ExpectedError('insufficient diversity')
+ qmap[q.p] = q
+ pool.append(q)
+
+ ## Work through combinations of factors from the pool.
+ for qq in combinations(npiece - 1, pool):
+
+ ## Construct the product of the selected factors.
+ qsmall = prod(q.p for q in qq)
+
+ ## Maybe we'll need to replace the large factor. Try not to do this
+ ## too often. DISP measures the large factor's performance at
+ ## producing candidates with the right length. If it looks bad then
+ ## we'll have to replace it.
+ if 3*disp*disp > nstep*nstep:
+ qbig = None
+ if disp < 0: p.p('<')
+ else: p.p('>')
+
+ ## If we don't have a large factor, then make one.
+ if qbig is None:
+ qbig = gen(nbits - qsmall.nbits, p = p)
+ disp = 0; nstep = 0
+
+ ## We have a candidate. Calculate it and make sure it has the right
+ ## length.
+ n = 2*qsmall*qbig.p + 1
+ nstep += 1
+ if n.nbits < nbits: disp -= 1
+ elif n.nbits > nbits: disp += 1
+ elif C.PrimeFilter.smallfactor(n) == C.PGEN_FAIL: pass
+ else:
+
+ ## The candidate has passed the small-primes test. Now check it
+ ## against Pocklington.
+ for a in I.islice(SIEVE.smallprimes(), 16):
+
+ ## Fermat test.
+ if pow(a, n - 1, n) != 1: p.p('.'); break
+ p.p('*')
+
+ ## Find a generator of a sufficiently large subgroup.
+ if n.gcd(pow(a, (n - 1)/qbig.p, n) - 1) != 1: p.p('@'); continue
+ ok = True
+ for q in qq:
+ if n.gcd(pow(a, (n - 1)/q.p, n) - 1) != 1:
+ p.p('@'); ok = False; break
+
+ ## We're done.
+ if ok:
+
+ ## Label the factors.
+ qq.append(qbig)
+ if qlfmt:
+ for i, q in enumerate(qq): q.label(qlfmt % i)
+
+ ## Return the number we found.
+ p.pop(); return PockPrime(n, a, qq, label = label)
+
+###--------------------------------------------------------------------------
+### Main program.
+
+def __main__():
+ global VERBOSITY
+
+ ## Prepare an option parser.
+ op = OP.OptionParser(
+ usage = '''\
+pock [-qv] CMD ARGS...
+ gen NBITS
+ ll NBITS NSUBBITS
+ [check] [FILE]''',
+ description = 'Generate or verify certified prime numbers.')
+ op.add_option('-v', '--verbose', dest = 'verbosity',
+ action = 'count', default = 1,
+ help = 'Print mysterious runes while looking for prime numbers.')
+ op.add_option('-q', '--quiet', dest = 'quietude',
+ action = 'count', default = 0,
+ help = 'be quiet while looking for prime numbers.')
+ op.add_option('-s', '--sievebits', dest = 'sievebits',
+ type = 'int', default = 32,
+ help = 'Size (in bits) of largest small prime.')
+ opts, argv = op.parse_args()
+ VERBOSITY = opts.verbosity - opts.quietude
+ p = ProgressReporter()
+ a = ArgFetcher(argv, op.error)
+
+ ## Process arguments and do what the user asked.
+ w = a.arg()
+
+ if w == 'gen':
+ ## Generate a prime with no special structure.
+ initsieve(opts.sievebits)
+ nbits = a.int(min = 4)
+ pp = PrimeProof()
+ p = gen(nbits, 'p', p = p)
+ p.register(pp)
+ pp.write(stdout)
+
+ elif w == 'll':
+ ## Generate a Lim--Lee prime.
+ initsieve(opts.sievebits)
+ nbits = a.int(min = 4)
+ nsubbits = a.int(min = 4, max = nbits)
+ pp = PrimeProof()
+ p = gen_limlee(nbits, nsubbits, 'p', 'q_%d', p = p)
+ p.register(pp)
+ pp.write(stdout)
+
+ elif w == 'check':
+ ## Check an existing certificate.
+ fn = a.arg(default = '-', must = False)
+ if fn == '-': f = stdin
+ else: f = open(fn, 'r')
+ pp = PrimeProof()
+ p = pp.read(f)
+
+ else:
+ raise ExpectedError("unknown command `%s'" % w)
+
+if __name__ == '__main__':
+ prog = OS.path.basename(argv[0])
+ try: __main__()
+ except ExpectedError, e: exit('%s: %s' % (prog, e.message))
+ except IOError, e: exit('%s: %s' % (prog, e))
+
+###----- That's all, folks --------------------------------------------------