diff --git a/Recipe b/Recipe
index 51b8c21c..7d6de75f 100644
--- a/Recipe
+++ b/Recipe
@@ -282,7 +282,7 @@ UXSSH    = SSH uxnoise uxagentc uxgss uxshare
 SFTP     = psftpcommon sftp sftpcommon logging cmdline
 
 # Components of the prime-generation system.
-SSHPRIME = sshprime smallprimes primecandidate millerrabin mpunsafe
+SSHPRIME = sshprime smallprimes primecandidate millerrabin pockle mpunsafe
 
 # Miscellaneous objects appearing in all the utilities, or all the
 # network ones, or the Unix or Windows subsets of those in turn.
diff --git a/pockle.c b/pockle.c
new file mode 100644
index 00000000..0aca8ecf
--- /dev/null
+++ b/pockle.c
@@ -0,0 +1,343 @@
+#include <assert.h>
+#include "ssh.h"
+#include "sshkeygen.h"
+#include "mpint.h"
+#include "mpunsafe.h"
+#include "tree234.h"
+
+struct Pockle {
+    tree234 *tree;
+
+    mp_int **list;
+    size_t nlist, listsize;
+};
+
+static int mpcmp(void *av, void *bv)
+{
+    mp_int *a = (mp_int *)av, *b = (mp_int *)bv;
+    return mp_cmp_hs(a, b) - mp_cmp_hs(b, a);
+}
+
+Pockle *pockle_new(void)
+{
+    Pockle *pockle = snew(Pockle);
+    pockle->tree = newtree234(mpcmp);
+    pockle->list = NULL;
+    pockle->nlist = pockle->listsize = 0;
+    return pockle;
+}
+
+void pockle_free(Pockle *pockle)
+{
+    for (mp_int *mp; (mp = delpos234(pockle->tree, 0)) != NULL ;)
+        mp_free(mp);
+    freetree234(pockle->tree);
+    sfree(pockle->list);
+    sfree(pockle);
+}
+
+static PockleStatus pockle_insert(Pockle *pockle, mp_int *p)
+{
+    mp_int *copy = mp_copy(p);
+    mp_int *found = add234(pockle->tree, copy);
+    if (copy != found) {
+        mp_free(copy); /* it was already in there */
+        return POCKLE_OK;
+    }
+    sgrowarray(pockle->list, pockle->listsize, pockle->nlist);
+    pockle->list[pockle->nlist++] = copy;
+    return POCKLE_OK;
+}
+
+size_t pockle_mark(Pockle *pockle)
+{
+    return pockle->nlist;
+}
+
+void pockle_release(Pockle *pockle, size_t mark)
+{
+    while (pockle->nlist > mark) {
+        mp_int *val = pockle->list[--pockle->nlist];
+        del234(pockle->tree, val);
+        mp_free(val);
+    }
+}
+
+PockleStatus pockle_add_small_prime(Pockle *pockle, mp_int *p)
+{
+    if (mp_hs_integer(p, (1ULL << 32)))
+        return POCKLE_SMALL_PRIME_NOT_SMALL;
+
+    uint32_t val = mp_get_integer(p);
+
+    if (val < 2)
+        return POCKLE_PRIME_SMALLER_THAN_2;
+
+    init_smallprimes();
+    for (size_t i = 0; i < NSMALLPRIMES; i++) {
+        if (val == smallprimes[i])
+            break; /* success */
+        if (val % smallprimes[i] == 0)
+            return POCKLE_SMALL_PRIME_NOT_PRIME;
+    }
+
+    return pockle_insert(pockle, p);
+}
+
+PockleStatus pockle_add_prime(Pockle *pockle, mp_int *p,
+                              mp_int **factors, size_t nfactors,
+                              mp_int *witness)
+{
+    MontyContext *mc = NULL;
+    mp_int *x = NULL, *f = NULL, *w = NULL;
+    PockleStatus status;
+
+    /*
+     * We're going to try to verify that p is prime by using
+     * Pocklington's theorem. The idea is that we're given w such that
+     *          w^{p-1} == 1 (mod p)             (1)
+     * and for a collection of primes q | p-1,
+     *       w^{(p-1)/q} - 1 is coprime to p.    (2)
+     *
+     * Suppose r is a prime factor of p itself. Consider the
+     * multiplicative order of w mod r. By (1), r | w^{p-1}-1. But by
+     * (2), r does not divide w^{(p-1)/q}-1. So the order of w mod r
+     * is a factor of p-1, but not a factor of (p-1)/q. Hence, the
+     * largest power of q that divides p-1 must also divide ord w.
+     *
+     * Repeating this reasoning for all q, we find that the product of
+     * all the q (which we'll denote f) must divide ord w, which in
+     * turn divides r-1. So f | r-1 for any r | p.
+     *
+     * In particular, this means f < r. That is, all primes r | p are
+     * bigger than f. So if f > sqrt(p), then we've shown p is prime,
+     * because otherwise it would have to be the product of at least
+     * two factors bigger than its own square root.
+     *
+     * With an extra check, we can also show p to be prime even if
+     * we're only given enough factors to make f > cbrt(p). See below
+     * for that part, when we come to it.
+     */
+
+    /*
+     * Start by checking p > 1. It certainly can't be prime otherwise!
+     * (And since we're going to prove it prime by showing all its
+     * prime factors are large, we do also have to know it _has_ at
+     * least one prime factor for that to tell us anything.)
+     */
+    if (!mp_hs_integer(p, 2))
+        return POCKLE_PRIME_SMALLER_THAN_2;
+
+    /*
+     * Check that all the factors we've been given really are primes
+     * (in the sense that we already had them in our index). Make the
+     * product f, and check it really does divide p-1.
+     */
+    x = mp_copy(p);
+    mp_sub_integer_into(x, x, 1);
+    f = mp_from_integer(1);
+    for (size_t i = 0; i < nfactors; i++) {
+        mp_int *q = factors[i];
+
+        if (!find234(pockle->tree, q, NULL)) {
+            status = POCKLE_FACTOR_NOT_KNOWN_PRIME;
+            goto out;
+        }
+
+        mp_int *quotient = mp_new(mp_max_bits(x));
+        mp_int *residue = mp_new(mp_max_bits(q));
+        mp_divmod_into(x, q, quotient, residue);
+
+        unsigned exact = mp_eq_integer(residue, 0);
+        mp_free(residue);
+
+        mp_free(x);
+        x = quotient;
+
+        if (!exact) {
+            status = POCKLE_FACTOR_NOT_A_FACTOR;
+            goto out;
+        }
+
+        mp_int *tmp = f;
+        f = mp_unsafe_shrink(mp_mul(tmp, q));
+        mp_free(tmp);
+    }
+
+    /*
+     * Check that f > cbrt(p).
+     */
+    mp_int *f2 = mp_mul(f, f);
+    mp_int *f3 = mp_mul(f2, f);
+    bool too_big = mp_cmp_hs(p, f3);
+    mp_free(f3);
+    mp_free(f2);
+    if (too_big) {
+        status = POCKLE_PRODUCT_OF_FACTORS_TOO_SMALL;
+        goto out;
+    }
+
+    /*
+     * Now do the extra check that allows us to get away with only
+     * having f > cbrt(p) instead of f > sqrt(p).
+     *
+     * If we can show that f | r-1 for any r | p, then we've ruled out
+     * p being a product of _more_ than two primes (because then it
+     * would be the product of at least three things bigger than its
+     * own cube root). But we still have to rule out it being a
+     * product of exactly two.
+     *
+     * Suppose for the sake of contradiction that p is the product of
+     * two prime factors. We know both of those factors would have to
+     * be congruent to 1 mod f. So we'd have to have
+     *
+     *    p = (uf+1)(vf+1) = (uv)f^2 + (u+v)f + 1        (3)
+     *
+     * We can't have uv >= f, or else that expression would come to at
+     * least f^3, i.e. it would exceed p. So uv < f. Hence, u,v < f as
+     * well.
+     *
+     * Can we have u+v >= f? If we did, then we could write v >= f-u,
+     * and hence f > uv >= u(f-u). That can be rearranged to show that
+     * u^2 > (u-1)f; decrementing the LHS makes the inequality no
+     * longer necessarily strict, so we have u^2-1 >= (u-1)f, and
+     * dividing off u-1 gives u+1 >= f. But we know u < f, so the only
+     * way this could happen would be if u=f-1, which makes v=1. But
+     * _then_ (3) gives us p = (f-1)f^2 + f^2 + 1 = f^3+1. But that
+     * can't be true if f^3 > p. So we can't have u+v >= f either, by
+     * contradiction.
+     *
+     * After all that, what have we shown? We've shown that we can
+     * write p = (uv)f^2 + (u+v)f + 1, with both uv and u+v strictly
+     * less than f. In other words, if you write down p in base f, it
+     * has exactly three digits, and they are uv, u+v and 1.
+     *
+     * But that means we can _find_ u and v: we know p and f, so we
+     * can just extract those digits of p's base-f representation.
+     * Once we've done so, they give the sum and product of the
+     * potential u,v. And given the sum and product of two numbers,
+     * you can make a quadratic which has those numbers as roots.
+     *
+     * We don't actually have to _solve_ the quadratic: all we have to
+     * do is check if its discriminant is a perfect square. If not,
+     * we'll know that no integers u,v can match this description.
+     */
+    {
+        /* We already have x = (p-1)/f. So we just need to write x in
+         * the form aF + b, and then we have a=uv and b=u+v. */
+        mp_int *a = mp_new(mp_max_bits(x));
+        mp_int *b = mp_new(mp_max_bits(f));
+        mp_divmod_into(x, f, a, b);
+        assert(!mp_cmp_hs(a, f));
+        assert(!mp_cmp_hs(b, f));
+
+        /* If a=0, then that means p < f^2, so we don't need to do
+         * this check at all: the straightforward Pocklington theorem
+         * is all we need. */
+        if (!mp_eq_integer(a, 0)) {
+            /* Compute the discriminant b^2 - 4a. */
+            mp_int *bsq = mp_mul(b, b);
+            mp_lshift_fixed_into(a, a, 2);
+            mp_int *discriminant = mp_sub(bsq, a);
+
+            /* See if it's a perfect square. */
+            mp_int *remainder = mp_new(mp_max_bits(discriminant));
+            mp_int *root = mp_nthroot(discriminant, 2, remainder);
+            unsigned perfect_square = mp_eq_integer(remainder, 0);
+            mp_free(bsq);
+            mp_free(discriminant);
+            mp_free(root);
+            mp_free(remainder);
+
+            if (perfect_square) {
+                mp_free(b);
+                mp_free(a);
+                status = POCKLE_DISCRIMINANT_IS_SQUARE;
+                goto out;
+            }
+        }
+        mp_free(b);
+        mp_free(a);
+    }
+
+    /*
+     * Now we've done all the checks that are cheaper than a modpow,
+     * so we've ruled out as many things as possible before having to
+     * do any hard work. But there's nothing for it now: make a
+     * MontyContext.
+     */
+    mc = monty_new(p);
+    w = monty_import(mc, witness);
+
+    /*
+     * The initial Fermat check: is w^{p-1} itself congruent to 1 mod
+     * p?
+     */
+    {
+        mp_int *pm1 = mp_copy(p);
+        mp_sub_integer_into(pm1, pm1, 1);
+        mp_int *power = monty_pow(mc, w, pm1);
+        unsigned fermat_pass = mp_cmp_eq(power, monty_identity(mc));
+        mp_free(power);
+        mp_free(pm1);
+
+        if (!fermat_pass) {
+            status = POCKLE_FERMAT_TEST_FAILED;
+            goto out;
+        }
+    }
+
+    /*
+     * And now, for each factor q, is w^{(p-1)/q}-1 coprime to p?
+     */
+    for (size_t i = 0; i < nfactors; i++) {
+        mp_int *q = factors[i];
+        mp_int *exponent = mp_unsafe_shrink(mp_div(p, q));
+        mp_int *power = monty_pow(mc, w, exponent);
+        mp_int *power_extracted = monty_export(mc, power);
+        mp_sub_integer_into(power_extracted, power_extracted, 1);
+
+        unsigned coprime = mp_coprime(power_extracted, p);
+        if (!coprime) {
+            /*
+             * If w^{(p-1)/q}-1 is not coprime to p, the test has
+             * failed. But it makes a difference why. If the power of
+             * w turned out to be 1, so that we took gcd(1-1,p) =
+             * gcd(0,p) = p, that's like an inconclusive Fermat or M-R
+             * test: it might just mean you picked a witness integer
+             * that wasn't a primitive root. But if the power is any
+             * _other_ value mod p that is not coprime to p, it means
+             * we've detected that the number is *actually not prime*!
+             */
+            if (mp_eq_integer(power_extracted, 0))
+                status = POCKLE_WITNESS_POWER_IS_1;
+            else
+                status = POCKLE_WITNESS_POWER_NOT_COPRIME;
+        }
+
+        mp_free(exponent);
+        mp_free(power);
+        mp_free(power_extracted);
+
+        if (!coprime)
+            goto out; /* with the status we set up above */
+    }
+
+    /*
+     * Success! p is prime. Insert it into our tree234 of known
+     * primes, so that future calls to this function can cite it in
+     * evidence of larger numbers' primality.
+     */
+    status = pockle_insert(pockle, p);
+
+  out:
+    if (x)
+        mp_free(x);
+    if (f)
+        mp_free(f);
+    if (w)
+        mp_free(w);
+    if (mc)
+        monty_free(mc);
+    return status;
+}
diff --git a/sshkeygen.h b/sshkeygen.h
index 2565302a..cccb1fb2 100644
--- a/sshkeygen.h
+++ b/sshkeygen.h
@@ -92,6 +92,68 @@ unsigned miller_rabin_checks_needed(unsigned bits);
  * that proves the number to be composite. */
 mp_int *miller_rabin_find_potential_primitive_root(MillerRabin *mr);
 
+/* ----------------------------------------------------------------------
+ * A system for proving numbers to be prime, using the Pocklington
+ * test, which requires knowing a partial factorisation of p-1
+ * (specifically, factors whose product is at least cbrt(p)) and a
+ * primitive root.
+ *
+ * The API consists of instantiating a 'Pockle' object, which
+ * internally stores a list of numbers you've already convinced it is
+ * prime, and can accept further primes if you give a satisfactory
+ * certificate of their primality based on primes it already knows
+ * about.
+ */
+
+typedef struct Pockle Pockle;
+
+/* In real use, you only really need to know whether the Pockle
+ * successfully accepted your prime. But for testcrypt, it's useful to
+ * expose many different failure modes so we can try to provoke them
+ * all in unit tests and check they're working. */
+#define POCKLE_STATUSES(X)                      \
+    X(POCKLE_OK)                                \
+    X(POCKLE_SMALL_PRIME_NOT_SMALL)             \
+    X(POCKLE_SMALL_PRIME_NOT_PRIME)             \
+    X(POCKLE_PRIME_SMALLER_THAN_2)              \
+    X(POCKLE_FACTOR_NOT_KNOWN_PRIME)            \
+    X(POCKLE_FACTOR_NOT_A_FACTOR)               \
+    X(POCKLE_PRODUCT_OF_FACTORS_TOO_SMALL)      \
+    X(POCKLE_FERMAT_TEST_FAILED)                \
+    X(POCKLE_DISCRIMINANT_IS_SQUARE)            \
+    X(POCKLE_WITNESS_POWER_IS_1)                \
+    X(POCKLE_WITNESS_POWER_NOT_COPRIME)         \
+    /* end of list */
+
+#define DEFINE_ENUM(id) id,
+typedef enum PockleStatus { POCKLE_STATUSES(DEFINE_ENUM) } PockleStatus;
+#undef DEFINE_ENUM
+
+/* Make a new empty Pockle, containing no primes. */
+Pockle *pockle_new(void);
+
+/* Insert a prime below 2^32 into the Pockle. No evidence is required:
+ * Pockle will check it itself. */
+PockleStatus pockle_add_small_prime(Pockle *pockle, mp_int *p);
+
+/* Insert a general prime into the Pockle. You must provide a list of
+ * prime factors of p-1, whose product exceeds the cube root of p, and
+ * also a primitive root mod p. */
+PockleStatus pockle_add_prime(Pockle *pockle, mp_int *p,
+                              mp_int **factors, size_t nfactors,
+                              mp_int *primitive_root);
+
+/* If you call pockle_mark, and later pass the returned value to
+ * pockle_release, it will free all the primes that were added to the
+ * Pockle between those two calls. Useful in recursive algorithms, to
+ * stop the Pockle growing unboundedly if the recursion keeps having
+ * to backtrack. */
+size_t pockle_mark(Pockle *pockle);
+void pockle_release(Pockle *pockle, size_t mark);
+
+/* Free a Pockle. */
+void pockle_free(Pockle *pockle);
+
 /* ----------------------------------------------------------------------
  * Callback API that allows key generation to report progress to its
  * caller.
diff --git a/test/cryptsuite.py b/test/cryptsuite.py
index 190324ef..225b69e4 100755
--- a/test/cryptsuite.py
+++ b/test/cryptsuite.py
@@ -970,6 +970,152 @@ class keygen(MyTestBase):
                 for p in [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61]:
                     self.assertNotEqual(n % p, 0)
 
+    def testPocklePositive(self):
+        def add_small(po, *ps):
+            for p in ps:
+                self.assertEqual(pockle_add_small_prime(po, p), 'POCKLE_OK')
+        def add(po, *args):
+            self.assertEqual(pockle_add_prime(po, *args), 'POCKLE_OK')
+
+        # Transcription of the proof that 2^130-5 is prime from
+        # Theorem 3.1 from http://cr.yp.to/mac/poly1305-20050329.pdf
+        po = pockle_new()
+        p1 = (2**130 - 6) // 1517314646
+        p2 = (p1 - 1) // 222890620702
+        add_small(po, 37003, 221101)
+        add(po, p2, [37003, 221101], 2)
+        add(po, p1, [p2], 2)
+        add(po, 2**130 - 5, [p1], 2)
+
+        # My own proof that 2^255-19 is prime
+        po = pockle_new()
+        p1 = 8574133
+        p2 = 1919519569386763
+        p3 = 75445702479781427272750846543864801
+        p4 = (2**255 - 20) // (65147*12)
+        p = 2**255 - 19
+        add_small(po, p1)
+        add(po, p2, [p1], 2)
+        add(po, p3, [p2], 2)
+        add(po, p4, [p3], 2)
+        add(po, p, [p4], 2)
+
+        # And the prime used in Ed448, while I'm here
+        po = pockle_new()
+        p1 = 379979
+        p2 = 1764234391
+        p3 = 97859369123353
+        p4 = 34741861125639557
+        p5 = 36131535570665139281
+        p6 = 167773885276849215533569
+        p7 = 596242599987116128415063
+        p = 2**448 - 2**224 - 1
+        add_small(po, p1, p2)
+        add(po, p3, [p1], 2)
+        add(po, p4, [p2], 2)
+        add(po, p5, [p4], 2)
+        add(po, p6, [p3], 3)
+        add(po, p7, [p5], 3)
+        add(po, p, [p6, p7], 2)
+
+        p = 4095744004479977
+        factors = [2, 79999] # just enough factors to exceed cbrt(p)
+        po = pockle_new()
+        for q in factors:
+            add_small(po, q)
+        add(po, p, factors, 3)
+
+        # The order of the generator in Ed25519
+        po = pockle_new()
+        p1a, p1b = 132667, 137849
+        p2 = 3044861653679985063343
+        p3 = 198211423230930754013084525763697
+        p = 2**252 + 0x14def9dea2f79cd65812631a5cf5d3ed
+        add_small(po, p1a, p1b)
+        add(po, p2, [p1a, p1b], 2)
+        add(po, p3, [p2], 2)
+        add(po, p, [p3], 2)
+
+        # And the one in Ed448
+        po = pockle_new()
+        p1 = 766223
+        p2 = 3009341
+        p3 = 7156907
+        p4 = 671065561
+        p5 = 342682509629
+        p6 = 6730519843040614479184435237013
+        p = 2**446 - 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d
+        add_small(po, p1, p2, p3, p4)
+        add(po, p5, [p1], 2)
+        add(po, p6, [p3,p4], 2)
+        add(po, p, [p2,p5,p6], 2)
+
+    def testPockleNegative(self):
+        def add_small(po, p):
+            self.assertEqual(pockle_add_small_prime(po, p), 'POCKLE_OK')
+
+        po = pockle_new()
+        self.assertEqual(pockle_add_small_prime(po, 0),
+                         'POCKLE_PRIME_SMALLER_THAN_2')
+        self.assertEqual(pockle_add_small_prime(po, 1),
+                         'POCKLE_PRIME_SMALLER_THAN_2')
+        self.assertEqual(pockle_add_small_prime(po, 2**61 - 1),
+                         'POCKLE_SMALL_PRIME_NOT_SMALL')
+        self.assertEqual(pockle_add_small_prime(po, 4),
+                         'POCKLE_SMALL_PRIME_NOT_PRIME')
+
+        po = pockle_new()
+        self.assertEqual(pockle_add_prime(po, 1919519569386763, [8574133], 2),
+                         'POCKLE_FACTOR_NOT_KNOWN_PRIME')
+
+        po = pockle_new()
+        add_small(po, 8574133)
+        self.assertEqual(pockle_add_prime(po, 1919519569386765, [8574133], 2),
+                         'POCKLE_FACTOR_NOT_A_FACTOR')
+
+        p = 4095744004479977
+        factors = [2, 79997] # not quite enough factors to reach cbrt(p)
+        po = pockle_new()
+        for q in factors:
+            add_small(po, q)
+        self.assertEqual(pockle_add_prime(po, p, factors, 3),
+                         'POCKLE_PRODUCT_OF_FACTORS_TOO_SMALL')
+
+        p = 1999527 * 3999053
+        factors = [999763]
+        po = pockle_new()
+        for q in factors:
+            add_small(po, q)
+        self.assertEqual(pockle_add_prime(po, p, factors, 3),
+                         'POCKLE_DISCRIMINANT_IS_SQUARE')
+
+        p = 9999929 * 9999931
+        factors = [257, 2593]
+        po = pockle_new()
+        for q in factors:
+            add_small(po, q)
+        self.assertEqual(pockle_add_prime(po, p, factors, 3),
+                         'POCKLE_FERMAT_TEST_FAILED')
+
+        p = 1713000920401 # a Carmichael number
+        po = pockle_new()
+        add_small(po, 561787)
+        self.assertEqual(pockle_add_prime(po, p, [561787], 2),
+                         'POCKLE_WITNESS_POWER_IS_1')
+
+        p = 4294971121
+        factors = [3, 5, 11, 17]
+        po = pockle_new()
+        for q in factors:
+            add_small(po, q)
+        self.assertEqual(pockle_add_prime(po, p, factors, 17),
+                         'POCKLE_WITNESS_POWER_NOT_COPRIME')
+
+        po = pockle_new()
+        add_small(po, 2)
+        self.assertEqual(pockle_add_prime(po, 1, [2], 1),
+                         'POCKLE_PRIME_SMALLER_THAN_2')
+
 class crypt(MyTestBase):
     def testSSH1Fingerprint(self):
         # Example key and reference fingerprint value generated by
diff --git a/test/testcrypt.py b/test/testcrypt.py
index de27ee38..5767f558 100644
--- a/test/testcrypt.py
+++ b/test/testcrypt.py
@@ -105,6 +105,7 @@ method_prefixes = {
     'val_rsakex': 'ssh_rsakex_',
     'val_prng': 'prng_',
     'val_pcs': 'pcs_',
+    'val_pockle': 'pockle_',
 }
 method_lists = {t: [] for t in method_prefixes}
 
@@ -181,6 +182,13 @@ def make_argword(arg, argtype, fnname, argindex, to_preserve):
         arg = unicode_to_bytes(arg)
         if isinstance(arg, bytes) and b" " not in arg:
             return arg
+    if typename == "mpint_list":
+        sublist = [make_argword(len(arg), ("uint", False),
+                                fnname, argindex, to_preserve)]
+        for val in arg:
+            sublist.append(make_argword(val, ("val_mpint", False),
+                                        fnname, argindex, to_preserve))
+        return b" ".join(unicode_to_bytes(sub) for sub in sublist)
     raise TypeError(
         "Can't convert {}() argument {:d} to {} (value was {!r})".format(
             fnname, argindex, typename, arg))
@@ -227,6 +235,8 @@ def make_retval(rettype, word, unpack_strings):
     elif rettype == "boolean":
         assert word == b"true" or word == b"false"
         return word == b"true"
+    elif rettype == "pocklestatus":
+        return word.decode("ASCII")
     raise TypeError("Can't deal with return value {!r} of type {!r}"
                     .format(word, rettype))
 
diff --git a/testcrypt.c b/testcrypt.c
index c2c1e3cd..40108704 100644
--- a/testcrypt.c
+++ b/testcrypt.c
@@ -96,6 +96,7 @@ uint64_t prng_reseed_time_ms(void)
     X(keycomponents, key_components *, key_components_free(v))          \
     X(pcs, PrimeCandidateSource *, pcs_free(v))                         \
     X(pgc, PrimeGenerationContext *, primegen_free_context(v))          \
+    X(pockle, Pockle *, pockle_free(v))                                 \
     /* end of list */
 
 typedef struct Value Value;
@@ -440,6 +441,11 @@ static void finaliser_return_value(strbuf *out, void *ctx)
     put_byte(out, '\n');
 }
 
+static void finaliser_sfree(strbuf *out, void *ctx)
+{
+    sfree(ctx);
+}
+
 #define VALTYPE_GETFN(n,t,f)                                            \
     static Value *unwrap_value_##n(Value *val) {                        \
         ValueType expected = VT_##n;                                    \
@@ -483,6 +489,26 @@ static mp_int **get_out_val_mpint(BinarySource *in)
     return &val->vu_mpint;
 }
 
+struct mpint_list {
+    size_t n;
+    mp_int **integers;
+};
+
+static struct mpint_list get_mpint_list(BinarySource *in)
+{
+    size_t n = get_uint(in);
+
+    struct mpint_list mpl;
+    mpl.n = n;
+
+    mpl.integers = snewn(n, mp_int *);
+    for (size_t i = 0; i < n; i++)
+        mpl.integers[i] = get_val_mpint(in);
+
+    add_finaliser(finaliser_sfree, mpl.integers);
+    return mpl;
+}
+
 static void finaliser_return_uint(strbuf *out, void *ctx)
 {
     unsigned *uval = (unsigned *)ctx;
@@ -546,11 +572,6 @@ static const char **get_out_opt_val_string_asciz_const(BinarySource *in)
     return valp;
 }
 
-static void finaliser_sfree(strbuf *out, void *ctx)
-{
-    sfree(ctx);
-}
-
 static BinarySource *get_val_string_binarysource(BinarySource *in)
 {
     strbuf *sb = get_val_string(in);
@@ -588,6 +609,25 @@ static void return_boolean(strbuf *out, bool b)
     strbuf_catf(out, "%s\n", b ? "true" : "false");
 }
 
+static void return_pocklestatus(strbuf *out, PockleStatus status)
+{
+    switch (status) {
+      default:
+        strbuf_catf(out, "POCKLE_BAD_STATUS_VALUE\n");
+        break;
+
+#define STATUS_CASE(id)                         \
+      case id:                                  \
+        strbuf_catf(out, "%s\n", #id);          \
+        break;
+
+        POCKLE_STATUSES(STATUS_CASE);
+
+#undef STATUS_CASE
+
+    }
+}
+
 static void return_val_string_asciz_const(strbuf *out, const char *s)
 {
     strbuf *sb = strbuf_new();
@@ -1147,6 +1187,13 @@ mp_int *key_components_nth_mp(key_components *kc, size_t n)
             mp_copy(kc->components[n].mp));
 }
 
+PockleStatus pockle_add_prime_wrapper(Pockle *pockle, mp_int *p,
+                                      struct mpint_list mpl, mp_int *witness)
+{
+    return pockle_add_prime(pockle, p, mpl.integers, mpl.n, witness);
+}
+#define pockle_add_prime pockle_add_prime_wrapper
+
 #define OPTIONAL_PTR_FUNC(type)                                         \
     typedef TD_val_##type TD_opt_val_##type;                            \
     static TD_opt_val_##type get_opt_val_##type(BinarySource *in) {     \
@@ -1179,6 +1226,8 @@ typedef const ssh_kex *TD_ecdh_alg;
 typedef RsaSsh1Order TD_rsaorder;
 typedef key_components *TD_keycomponents;
 typedef const PrimeGenerationPolicy *TD_primegenpolicy;
+typedef struct mpint_list TD_mpint_list;
+typedef PockleStatus TD_pocklestatus;
 
 #define FUNC0(rettype, function)                                        \
     static void handle_##function(BinarySource *in, strbuf *out) {      \
diff --git a/testcrypt.h b/testcrypt.h
index 1eca3759..89cbaadf 100644
--- a/testcrypt.h
+++ b/testcrypt.h
@@ -277,6 +277,11 @@ FUNC3(void, pcs_avoid_residue_small, val_pcs, uint, uint)
 FUNC1(void, pcs_ready, val_pcs)
 FUNC4(void, pcs_inspect, val_pcs, out_val_mpint, out_val_mpint, out_val_mpint)
 FUNC1(val_mpint, pcs_generate, val_pcs)
+FUNC0(val_pockle, pockle_new)
+FUNC1(uint, pockle_mark, val_pockle)
+FUNC2(void, pockle_release, val_pockle, uint)
+FUNC2(pocklestatus, pockle_add_small_prime, val_pockle, val_mpint)
+FUNC4(pocklestatus, pockle_add_prime, val_pockle, val_mpint, mpint_list, val_mpint)
 
 /*
  * Miscellaneous.