Merge branch '1.3.x'
authorMark Wooding <mdw@distorted.org.uk>
Wed, 27 Nov 2019 15:12:23 +0000 (15:12 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 27 Nov 2019 15:12:23 +0000 (15:12 +0000)
* 1.3.x: (101 commits)
  rand.c: Show keyword argument as optional.
  mp.c: Fix punctuation error in docstrings.
  t/t-*.py: Use the `WriteBuffer.contents' property.
  t/t-bytes.py: Check that indexing, slicing, etc. return `C.ByteString'.
  t/t-algorithms.py: Add a simple test for `Keccak1600.copy'.
  t/t-algorithms.py: Add tests for other HSalsa20 and HChaCha key sizes.
  t/t-algorithms.py: Add AEAD tests.
  t/t-algorithms.py: Add tests for the new `KeySZ.pad' method.
  catacomb/__init__.py (KeySZRange.pad): Return correct value.
  algorithms.c: Propagate `AEADF_NOAAD' to `aad' objects.
  algorithms.c (AEADAAD.copy): Propagate the hashed length to the copy.
  t/: Add a test suite.
  ec.c: Don't lose error status when constructing points from a sequence.
  ec.c: Free partially constructed points coordinatewise.
  *.c: Be more careful about `PySequence_Size'.
  key.c: Reformat the rest of the `KeyError' constructor.
  key.c: Parse `KeyError' constructor arguments by hand.
  catacomb-python.h: Add a macro for raising `OverflowError'.
  key.c: Collect `KeyError' argument count as a separate step.
  key.c: Use tuple functions on `KeyError' argument tuple.
  ...

1  2 
algorithms.c
catacomb-python.h
catacomb.c
debian/changelog
debian/control
ec.c
field.c
group.c
rand.c
setup.py

diff --combined algorithms.c
@@@ -27,9 -27,7 +27,9 @@@
  /*----- Header files ------------------------------------------------------*/
  
  #include "catacomb-python.h"
 +PUBLIC_SYMBOLS;
  #include "algorithms.h"
 +PRIVATE_SYMBOLS;
  
  /*----- Key sizes ---------------------------------------------------------*/
  
@@@ -137,9 -135,8 +137,8 @@@ static PyObject *keyszset_pynew(PyTypeO
    if (!set) set = PyTuple_New(0);
    else Py_INCREF(set);
    if (!PySequence_Check(set)) TYERR("want a sequence");
-   n = PySequence_Size(set);
-   l = PyList_New(0);
-   if (PyErr_Occurred()) goto end;
+   n = PySequence_Size(set); if (n < 0) goto end;
+   l = PyList_New(0); if (!l) goto end;
    if (dfl < 0) VALERR("key size cannot be negative");
    x = PyInt_FromLong(dfl);
    PyList_Append(l, x);
@@@ -208,7 -205,7 +207,7 @@@ static PyMemberDef keysz_pymembers[] = 
  static PyGetSetDef keyszany_pygetset[] = {
  #define GETSETNAME(op, name) ka##op##_##name
    GET (min,                   "KSZ.min -> smallest allowed key size")
-   GET (max,                   "KSZ.min -> largest allowed key size")
+   GET (max,                   "KSZ.max -> largest allowed key size")
  #undef GETSETNAME
    { 0 }
  };
  static PyMemberDef keyszrange_pymembers[] = {
  #define MEMBERSTRUCT keyszrange_pyobj
    MEMBER(min, T_INT, READONLY,        "KSZ.min -> smallest allowed key size")
-   MEMBER(max, T_INT, READONLY,        "KSZ.min -> largest allowed key size")
+   MEMBER(max, T_INT, READONLY,        "KSZ.max -> largest allowed key size")
    MEMBER(mod, T_INT, READONLY,
         "KSZ.mod -> key size must be a multiple of this")
  #undef MEMBERSTRUCT
  static PyGetSetDef keyszset_pygetset[] = {
  #define GETSETNAME(op, name) ks##op##_##name
    GET (min,                   "KSZ.min -> smallest allowed key size")
-   GET (max,                   "KSZ.min -> largest allowed key size")
+   GET (max,                   "KSZ.max -> largest allowed key size")
  #undef GETSETNAME
    { 0 }
  };
@@@ -990,6 -987,7 +989,7 @@@ static PyObject *gaeameth_copy(PyObjec
      VALERR("can't duplicate nonce-dependent aad");
    rc = gaeadaad_pywrap((PyObject *)me->ob_type,
                       GAEAD_DUP(GAEADAAD_A(me)), 0, 0);
+   GAEADAAD_HLEN(rc) = GAEADAAD_HLEN(me);
  end:
    return (rc);
  }
@@@ -1133,7 -1131,8 +1133,8 @@@ static PyObject *gaeemeth_aad(PyObject 
      if (!ge->aad)
        ge->aad = (gaeadaad_pyobj *)
        gaeadaad_pywrap((PyObject *)GCAEADENC_KEY(ge->ob_type)->aad,
-                       GAEAD_AAD(ge->e), ge->f&AEADF_PCHSZ, ge->hsz);
+                       GAEAD_AAD(ge->e), ge->f&(AEADF_PCHSZ | AEADF_NOAAD),
+                       ge->hsz);
      Py_INCREF(ge->aad);
      rc = (PyObject *)ge->aad;
    }
@@@ -1304,7 -1303,8 +1305,8 @@@ static PyObject *gaedmeth_aad(PyObject 
      if (!gd->aad)
        gd->aad = (gaeadaad_pyobj *)
        gaeadaad_pywrap((PyObject *)GCAEADENC_KEY(gd->ob_type)->aad,
-                       GAEAD_AAD(gd->d), gd->f&AEADF_PCHSZ, gd->hsz);
+                       GAEAD_AAD(gd->d), gd->f&(AEADF_PCHSZ | AEADF_NOAAD),
+                       gd->hsz);
      Py_INCREF(gd->aad);
      return ((PyObject *)gd->aad);
    }
@@@ -2639,7 -2639,7 +2641,7 @@@ static PyTypeObject poly1305hash_pytype
      if (!PyArg_ParseTuple(arg, "s#s#:" #hdance "_prf",                        \
                          &k, &ksz, &n, &nsz))                          \
        goto end;                                                               \
-     if (ksz != DANCE##_KEYSZ) VALERR("bad key length");                       \
+     if (ksz != keysz(ksz, dance##_keysz)) VALERR("bad key length");   \
      if (nsz != HDANCE##_INSZ) VALERR("bad input length");             \
      rc = bytestring_pywrap(0, HSALSA20_OUTSZ);                                \
      dance##_init(&dance, k, ksz, 0);                                  \
@@@ -2667,7 -2667,8 +2669,7 @@@ typedef struct kxvik_pyobj 
    unsigned n;
  } kxvik_pyobj;
  
 -static PyObject *kxvik_pynew(PyTypeObject *ty,
 -                                PyObject *arg, PyObject *kw)
 +static PyObject *kxvik_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
  {
    unsigned n = 24;
    kxvik_pyobj *rc = 0;
@@@ -2725,7 -2726,7 +2727,7 @@@ static PyObject *kxvikmeth_extract(PyOb
    unsigned i;
    unsigned n;
  
-   if (!PyArg_ParseTuple(arg, "O&:mix", convuint, &n)) goto end;
+   if (!PyArg_ParseTuple(arg, "O&:extract", convuint, &n)) goto end;
    if (n > 200) VALERR("out of range");
    rc = bytestring_pywrap(0, n);
    q = (octet *)PyString_AS_STRING(rc);
@@@ -2755,10 -2756,14 +2757,14 @@@ static int kxvikset_nround(PyObject *me
  {
    kxvik_pyobj *k = (kxvik_pyobj *)me;
    unsigned n;
+   int rc = -1;
  
-   if (!convuint(val, &n)) return (-1);
+   if (!val) NIERR("__del__");
+   if (!convuint(val, &n)) goto end;
    k->n = n;
-   return (0);
+   rc = 0;
+ end:
+   return (rc);
  }
  
  static PyGetSetDef kxvik_pygetset[] = {
@@@ -2955,7 -2960,7 +2961,7 @@@ static PyObject *shakemeth_copy(PyObjec
    rc->h = *SHAKE_H(me);
    rc->st = SHAKE_ST(me);
  end:
-   return ((PyObject *)me);
+   return ((PyObject *)rc);
  }
  
  static PyObject *shakemeth_get(PyObject *me, PyObject *arg)
diff --combined catacomb-python.h
  #include <catacomb/share.h>
  #include <catacomb/gfshare.h>
  
 +/*----- Other preliminaries -----------------------------------------------*/
 +
 +#define GOBBLE_SEMI extern int notexist
 +#if defined(__GNUC__) && defined(__ELF__)
 +#  define PRIVATE_SYMBOLS _Pragma("GCC visibility push(hidden)") GOBBLE_SEMI
 +#  define PUBLIC_SYMBOLS _Pragma("GCC visibility pop") GOBBLE_SEMI
 +#  define EXPORT __attribute__((__visibility__("default")))
 +#else
 +#  define PRIVATE_SYMBOLS GOBBLE_SEMI
 +#  define PUBLIC_SYMBOLS GOBBLE_SEMI
 +#  define EXPORT
 +#endif
 +
 +PRIVATE_SYMBOLS;
 +
  /*----- Utility macros ----------------------------------------------------*/
  
  #define RETURN_OBJ(obj) do { Py_INCREF(obj); return (obj); } while (0)
    goto end;                                                           \
  } while (0)
  #define VALERR(str) EXCERR(PyExc_ValueError, str)
+ #define OVFERR(str) EXCERR(PyExc_OverflowError, str)
  #define TYERR(str) EXCERR(PyExc_TypeError, str)
  #define IXERR(str) EXCERR(PyExc_IndexError, str)
  #define ZDIVERR(str) EXCERR(PyExc_ZeroDivisionError, str)
    PyErr_SetFromErrnoWithFilename(PyExc_OSError, name);                        \
    goto end;                                                           \
  } while (0)
- #define PGENERR do { pgenerr(); goto end; } while (0)
+ #define PGENERR(exc) do { pgenerr(exc); goto end; } while (0)
  
  #define CONVFUNC(ty, cty, ext)                                                \
    int conv##ty(PyObject *o, void *p)                                  \
  } while (0)
  #define INITTYPE(ty, base) INITTYPE_META(ty, base, type)
  
- #define INSERT(name, ob) do {                                         \
+ extern PyObject *home_module;
+ #define INSERT(name, ob) do { \
    PyObject *_o = (PyObject *)(ob);                                    \
    Py_INCREF(_o);                                                      \
    PyModule_AddObject(mod, name, _o);                                  \
@@@ -250,7 -238,8 +253,8 @@@ MODULES(DO
  
  #define KWLIST (/*unconst*/ char **)kwlist
  
- struct nameval { const char *name; unsigned long value; };
+ struct nameval { const char *name; unsigned f; unsigned long value; };
+ #define CF_SIGNED 1u
  extern void setconstants(PyObject *, const struct nameval *);
  
  extern PyObject *mexp_common(PyObject *, PyObject *, size_t,
@@@ -274,7 -263,34 +278,34 @@@ extern PyObject *getulong(unsigned long
  extern PyObject *getk64(kludge64);
  extern void *newtype(PyTypeObject *, const PyTypeObject *, const char *);
  
+ struct excinfo { PyObject *ty, *val, *tb; };
+ #define EXCINFO_INIT { 0, 0, 0 }
  extern PyObject *mkexc(PyObject *, PyObject *, const char *, PyMethodDef *);
+ #define INIT_EXCINFO(exc) do {                                                \
+   struct excinfo *_exc = (exc); _exc->ty = _exc->val = _exc->tb = 0;  \
+ } while (0)
+ #define RELEASE_EXCINFO(exc) do {                                     \
+   struct excinfo *_exc = (exc);                                               \
+   Py_XDECREF(_exc->ty);        _exc->ty  = 0;                                 \
+   Py_XDECREF(_exc->val); _exc->val = 0;                                       \
+   Py_XDECREF(_exc->tb);        _exc->tb  = 0;                                 \
+ } while (0)
+ #define STASH_EXCINFO(exc) do {                                               \
+   struct excinfo *_exc = (exc);                                               \
+   PyErr_Fetch(&_exc->ty, &_exc->val, &_exc->tb);                      \
+   PyErr_NormalizeException(&_exc->ty, &_exc->val, &_exc->tb);         \
+ } while (0)
+ #define RESTORE_EXCINFO(exc) do {                                     \
+   struct excinfo *_exc = (exc);                                               \
+   PyErr_Restore(_exc->ty, _exc->val, _exc->tb);                               \
+   _exc->ty = _exc->val = _exc->tb = 0;                                        \
+ } while (0)
+ extern void report_lost_exception(struct excinfo *, const char *, ...);
+ extern void report_lost_exception_v(struct excinfo *, const char *, va_list);
+ extern void stash_exception(struct excinfo *, const char *, ...);
+ extern void restore_exception(struct excinfo *, const char *, ...);
  extern void typeready(PyTypeObject *);
  extern PyTypeObject *inittype(PyTypeObject *, PyTypeObject *);
  extern void addmethods(const PyMethodDef *);
@@@ -343,6 -359,7 +374,7 @@@ extern mp *getgf(PyObject *)
  extern int convgf(PyObject *, void *);
  extern PyObject *mp_pywrap(mp *);
  extern PyObject *gf_pywrap(mp *);
+ extern long mphash(mp *);
  extern mp *mp_frompyobject(PyObject *, int);
  extern PyObject *mp_topystring(mp *, int,
                               const char *, const char *, const char *);
@@@ -361,7 -378,6 +393,6 @@@ extern PyTypeObject *fe_pytype
  #define FE_FOBJ(o) ((PyObject *)(o)->ob_type)
  #define FE_X(o) (((fe_pyobj *)(o))->x)
  extern PyObject *fe_pywrap(PyObject *, mp *);
- extern mp *getfe(field *, PyObject *);
  
  typedef struct fe_pyobj {
    PyObject_HEAD
@@@ -715,9 -731,15 +746,15 @@@ extern PyTypeObject *pgev_pytype
  #define PGEV_PYCHECK(o) PyObject_TypeCheck(o, pgev_pytype)
  #define PGEV_PG(o) (&((pgev_pyobj *)(o))->pg)
  
+ typedef struct pypgev {
+   pgev ev;
+   PyObject *obj;
+   struct excinfo *exc;
+ } pypgev;
  extern int convpgev(PyObject *, void *);
- extern void droppgev(pgev *);
- extern void pgenerr(void);
+ extern void droppgev(pypgev *);
+ extern void pgenerr(struct excinfo *exc);
  
  /*----- That's all, folks -------------------------------------------------*/
  
diff --combined catacomb.c
  /*----- Main code ---------------------------------------------------------*/
  
  static const struct nameval consts[] = {
- #define C(x) { #x, x }
+ #define CF(f, x) { #x, f, x }
+ #define C(x) { #x, (x) >= 0 ? 0 : CF_SIGNED, x }
    C(FTY_PRIME), C(FTY_BINARY),
    C(PGEN_PASS), C(PGEN_FAIL), C(PGEN_BEGIN), C(PGEN_TRY), C(PGEN_DONE),
    C(PGEN_ABORT),
    C(MPW_MAX),
+   C(RAND_IBITS),
    C(PMODE_READ), C(PMODE_VERIFY),
    C(KOPEN_READ), C(KOPEN_WRITE), C(KOPEN_NOFILE),
-   C(KEXP_FOREVER), C(KEXP_EXPIRE),
+   CF(0, KEXP_FOREVER), CF(0, KEXP_EXPIRE),
    C(KF_ENCMASK), C(KENC_BINARY), C(KENC_MP), C(KENC_STRUCT),
      C(KENC_ENCRYPT), C(KENC_STRING), C(KENC_EC),
    C(KF_CATMASK), C(KCAT_SYMM), C(KCAT_PRIV), C(KCAT_PUB), C(KCAT_SHARE),
@@@ -56,6 -58,7 +58,7 @@@
    KEY_ERRORS(ENTRY)
  #undef ENTRY
  #undef C
+ #undef CF
    { 0 }
  };
  
@@@ -75,7 -78,8 +78,8 @@@ PyObject *mexp_common(PyObject *me, PyO
      arg = PyTuple_GetItem(arg, 0);
    Py_INCREF(arg);
    if (!PySequence_Check(arg)) TYERR("not a sequence");
-   n = PySequence_Size(arg); if (!n) { z = id(me); goto end; }
+   n = PySequence_Size(arg); if (n < 0) goto end;
+   if (!n) { z = id(me); goto end; }
    x = PySequence_GetItem(arg, 0);
    if (PySequence_Check(x))
      flat = 0;
@@@ -140,7 -144,7 +144,7 @@@ static PyObject *meth__ego(PyObject *me
    char *argv0;
    if (!PyArg_ParseTuple(arg, "s:_ego", &argv0))
      return (0);
 -  if (strcmp(QUIS, "<UNNAMED>") == 0)
 +  if (STRCMP(QUIS, ==, "<UNNAMED>"))
      ego(argv0);
    RETURN_NONE;
  }
@@@ -160,14 -164,14 +164,14 @@@ static void init_random(void
  
    if (!Py_HashRandomizationFlag) return;
    seed = getenv("PYTHONHASHSEED");
 -  if (!seed || strcmp(seed, "random") == 0) r = GR_WORD(&rand_global);
 +  if (!seed || STRCMP(seed, ==, "random")) r = GR_WORD(&rand_global);
    else r = strtoul(seed, 0, 0);
    if (!r) r = 0xe011f220; /* zero doesn't work well */
    unihash_setkey(&unihash_global, r);
  #endif
  }
  
 -void init_base(void)
 +EXPORT void init_base(void)
  {
    PyObject *mod;
    addmethods(methods);
diff --combined debian/changelog
@@@ -1,9 -1,3 +1,9 @@@
 +catacomb-python (1.3.99~) experimental; urgency=medium
 +
 +  * (placeholder for next release)
 +
 + -- Mark Wooding <mdw@distorted.org.uk>  Tue, 01 Oct 2019 12:57:58 +0100
 +
  catacomb-python (1.3.0.1) experimental; urgency=medium
  
    * Fix required Catacomb version in `setup.py' script.  Only affects the
@@@ -27,6 -21,12 +27,12 @@@ catacomb-python (1.3.0) experimental; u
  
   -- Mark Wooding <mdw@distorted.org.uk>  Sat, 21 Sep 2019 23:00:25 +0100
  
+ catacomb-python (1.2.1.1) experimental; urgency=medium
+   * Fixing to build against Debian `stretch'.
+  -- Mark Wooding <mdw@distorted.org.uk>  Mon, 24 Dec 2018 15:21:08 +0000
  catacomb-python (1.2.1) experimental; urgency=low
  
    * Fix use-after-free bug in ECPt hashing causing hash instability.
diff --combined debian/control
@@@ -3,9 -3,9 +3,9 @@@ Section: pytho
  Priority: extra
  XS-Python-Version: >= 2.6, << 2.8
  Maintainer: Mark Wooding <mdw@distorted.org.uk>
- Build-Depends: debhelper (>= 10), pkg-config,
 -Build-Depends: debhelper (>= 9), dh-python, pkg-config,
++Build-Depends: debhelper (>= 10), dh-python, pkg-config,
        python (>= 2.6.6-3~), python-all-dev,
 -      mlib-dev (>= 2.2.2.1), catacomb-dev (>= 2.5.0)
 +      mlib-dev (>= 2.4.99~), catacomb-dev (>= 2.5.0)
  Standards-Version: 3.8.0
  
  Package: python-catacomb
diff --combined ec.c
--- 1/ec.c
--- 2/ec.c
+++ b/ec.c
@@@ -48,13 -48,13 +48,13 @@@ ec_curve *eccurve_copy(ec_curve *c
      return (0);
    a = F_OUT(f, MP_NEW, c->a);
    b = F_OUT(f, MP_NEW, c->b);
 -  if (strcmp(EC_NAME(c), "prime") == 0)
 +  if (STRCMP(EC_NAME(c), ==, "prime"))
      c = ec_prime(f, a, b);
 -  else if (strcmp(EC_NAME(c), "primeproj") == 0)
 +  else if (STRCMP(EC_NAME(c), ==, "primeproj"))
      c = ec_primeproj(f, a, b);
 -  else if (strcmp(EC_NAME(c), "bin") == 0)
 +  else if (STRCMP(EC_NAME(c), ==, "bin"))
      c = ec_bin(f, a, b);
 -  else if (strcmp(EC_NAME(c), "binproj") == 0)
 +  else if (STRCMP(EC_NAME(c), ==, "binproj"))
      c = ec_binproj(f, a, b);
    else
      c = 0;
@@@ -188,38 -188,31 +188,31 @@@ static PyObject *ecpt_pymul(PyObject *x
    if (ECPT_PYCHECK(x)) { PyObject *t; t = x; x = y; y = t; }
    if (!ECPT_PYCHECK(y) || (xx = tomp(x)) == 0) RETURN_NOTIMPL;
    ec_imul(ECPT_C(y), &zz, ECPT_P(y), xx);
+   MP_DROP(xx);
    return (ecpt_pywrap(ECPT_COBJ(y), &zz));
  }
  
  static long ecpt_pyhash(PyObject *me)
  {
    uint32 h;
-   buf b;
    ec p = EC_INIT;
-   size_t sz = 2*ECPT_C(me)->f->noctets + 1;
-   octet *q = xmalloc(sz);
  
-   h = 0xe0fdd039 + ECPT_C(me)->f->ops->ty;
-   buf_init(&b, q, sz);
-   EC_OUT(ECPT_C(me), &p, ECPT_P(me));
-   ec_putraw(ECPT_C(me), &b, &p);
+   getecptout(&p, me);
+   if (EC_ATINF(&p)) h = 0x81d81a94;
+   else h = 0xe0fdd039 ^ (2*mphash(p.x)) ^ (3*mphash(p.y));
    EC_DESTROY(&p);
-   h = unihash_hash(&unihash_global, h, BBASE(&b), BLEN(&b));
-   xfree(q);
-   return (h % LONG_MAX);
+   return (h%LONG_MAX);
  }
  
  static PyObject *ecpt_pyrichcompare(PyObject *x, PyObject *y, int op)
  {
-   ec_curve *c;
-   PyObject *cobj;
    ec p = EC_INIT, q = EC_INIT;
    int b;
    PyObject *rc = 0;
  
-   if (ecbinop(x, y, &c, &cobj, &p, &q)) RETURN_NOTIMPL;
-   EC_OUT(c, &p, &p);
-   EC_OUT(c, &q, &q);
+   if (!ECPT_PYCHECK(y)) RETURN_NOTIMPL;
+   getecptout(&p, x);
+   getecptout(&q, y);
    switch (op) {
      case Py_EQ: b = EC_EQ(&p, &q); break;
      case Py_NE: b = !EC_EQ(&p, &q); break;
@@@ -259,7 -252,7 +252,7 @@@ static PyObject *epmeth_tobuf(PyObject 
    if (EC_ATINF(&p))
      n = 2;
    else
-     n = mp_octets(p.x) + mp_octets(p.y) + 4;
+     n = mp_octets(p.x) + mp_octets(p.y) + 6;
    rc = bytestring_pywrap(0, n);
    buf_init(&b, PyString_AS_STRING(rc), n);
    buf_putec(&b, &p);
@@@ -297,11 -290,12 +290,12 @@@ static PyObject *epmeth_ec2osp(PyObjec
    char *p;
    ec_curve *c = ECPT_C(me);
    ec pp = EC_INIT;
-   int f = EC_EXPLY;
+   unsigned f = EC_EXPLY;
    int len;
    static const char *const kwlist[] = { "flags", 0 };
  
-   if (!PyArg_ParseTupleAndKeywords(arg, kw, "|i:ectosp", KWLIST, &f))
+   if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:ec2osp", KWLIST,
+                                  convuint, &f))
      return (0);
    len = c->f->noctets * 2 + 1;
    rc = bytestring_pywrap(0, len);
@@@ -419,9 -413,9 +413,9 @@@ static int ecptxl_3(ec_curve *c, ec *p
  
    if (!x || !y || !z) TYERR("missing argument");
    if (!c) VALERR("internal form with no curve!");
-   if ((p->x == coord_in(c->f, x)) == 0 ||
-       (p->y == coord_in(c->f, y)) == 0 ||
-       (z != Py_None && (p->z = coord_in(c->f, z))) == 0)
+   if ((p->x = coord_in(c->f, x)) == 0 ||
+       (p->y = coord_in(c->f, y)) == 0 ||
+       (z != Py_None && (p->z = coord_in(c->f, z)) == 0))
      goto end;
    if (!p->z) p->z = MP_COPY(c->f->one); /* just in case */
    rc = 0;
@@@ -459,7 -453,7 +453,7 @@@ static int ecptxl_1(ec_curve *c, ec *p
      getecptout(p, x);
      goto fix;
    } else if (PyString_Check(x)) {
-     if (PyObject_AsReadBuffer(x, &q, 0))
+     if (PyObject_AsReadBuffer(x, &q, &n))
        goto end;
      qd.p = q;
      qd.e = 0;
      if (!EC_FIND(c, p, xx)) VALERR("not on the curve");
    } else if (PySequence_Check(x)) {
      t = x; x = 0;
-     n = PySequence_Size(t);
+     n = PySequence_Size(t); if (n < 0) goto end;
      if (n != 2 && (n != 3 || !c))
        TYERR("want sequence of two or three items");
      if ((x = PySequence_GetItem(t, 0)) == 0 ||
        (n == 3 && (z = PySequence_GetItem(t, 2)) == 0))
        goto end;
      rc = (n == 2) ? ecptxl_2(c, p, x, y) : ecptxl_3(c, p, x, y, z);
+     goto end;
    } else
      TYERR("can't convert to curve point");
    goto ok;
@@@ -514,7 -509,7 +509,7 @@@ static PyObject *ecptnc_pynew(PyTypeObj
      goto end;
    return (ecpt_pywrapout(ty, &p));
  end:
-   EC_DESTROY(&p);
+   mp_drop(p.x); mp_drop(p.y); mp_drop(p.z);
    return (0);
  }
  
@@@ -556,7 -551,7 +551,7 @@@ static PyObject *ecpt_pynew(PyTypeObjec
      goto end;
    return (ecpt_pywrap((PyObject *)ty, &p));
  end:
-   EC_DESTROY(&p);
+   mp_drop(p.x); mp_drop(p.y); mp_drop(p.z);
    return (0);
  }
  
@@@ -789,10 -784,14 +784,14 @@@ static PyTypeObject ecptcurve_pytype_sk
  
  static PyObject *eccurve_pyrichcompare(PyObject *x, PyObject *y, int op)
  {
-   int b = ec_samep(ECCURVE_C(x), ECCURVE_C(y));
+   int b;
+   assert(ECCURVE_PYCHECK(x));
+   if (!ECCURVE_PYCHECK(y)) RETURN_NOTIMPL;
+   b = ec_samep(ECCURVE_C(x), ECCURVE_C(y));
    switch (op) {
      case Py_EQ: break;
-     case Py_NE: b = !b;
+     case Py_NE: b = !b; break;
      default: TYERR("can't order elliptic curves");
    }
    return (getbool(b));
@@@ -863,12 -862,12 +862,12 @@@ static PyObject *meth__ECPtCurve_os2ecp
    buf b;
    PyObject *rc = 0;
    ec_curve *cc;
-   int f = EC_XONLY | EC_LSB | EC_SORT | EC_EXPLY;
+   unsigned f = EC_XONLY | EC_LSB | EC_SORT | EC_EXPLY;
    ec pp = EC_INIT;
-   static const char *const kwlist[] = { "buf", "flags", 0 };
+   static const char *const kwlist[] = { "class", "buf", "flags", 0 };
  
-   if (!PyArg_ParseTupleAndKeywords(arg, kw, "Os#|f:os2ecp", KWLIST,
-                                  &me, &p, &len, &f))
+   if (!PyArg_ParseTupleAndKeywords(arg, kw, "Os#|O&:os2ecp", KWLIST,
+                                  &me, &p, &len, convuint, &f))
      return (0);
    buf_init(&b, p, len);
    cc = ECCURVE_C(me);
@@@ -984,13 -983,13 +983,13 @@@ PyObject *eccurve_pywrap(PyObject *fobj
    else
      Py_INCREF(fobj);
    assert(FIELD_F(fobj) == c->f);
 -  if (strcmp(EC_NAME(c), "prime") == 0)
 +  if (STRCMP(EC_NAME(c), ==, "prime"))
      ty = ecprimecurve_pytype;
 -  else if (strcmp(EC_NAME(c), "primeproj") == 0)
 +  else if (STRCMP(EC_NAME(c), ==, "primeproj"))
      ty = ecprimeprojcurve_pytype;
 -  else if (strcmp(EC_NAME(c), "bin") == 0)
 +  else if (STRCMP(EC_NAME(c), ==, "bin"))
      ty = ecbincurve_pytype;
 -  else if (strcmp(EC_NAME(c), "binproj") == 0)
 +  else if (STRCMP(EC_NAME(c), ==, "binproj"))
      ty = ecbinprojcurve_pytype;
    else
      abort();
@@@ -1067,7 -1066,7 +1066,7 @@@ static PyMethodDef eccurve_pymethods[] 
    METH        (mmul,          "\
  E.mmul([(P0, N0), (P1, N1), ...]) = N0 P0 + N1 P1 + ...")
    METH        (find,          "E.find(X) -> P")
-   KWMETH(rand,                "E.rand(rng = rand) ->P")
+   KWMETH(rand,                "E.rand([rng = rand]) -> P")
  #undef METHNAME
    { 0 }
  };
@@@ -1492,7 -1491,7 +1491,7 @@@ static PyGetSetDef ecinfo_pygetset[] = 
  
  static PyMethodDef ecinfo_pymethods[] = {
  #define METHNAME(name) eimeth_##name
-   KWMETH(check,               "I.check() -> None")
+   KWMETH(check,               "I.check([rng = rand]) -> None")
  #undef METHNAME
    { 0 }
  };
diff --combined field.c
+++ b/field.c
@@@ -74,23 -74,23 +74,23 @@@ PyObject *field_pywrap(field *f
  {
    PyTypeObject *ty;
  
 -  if (strcmp(F_NAME(f), "prime") == 0) ty = primefield_pytype;
 -  else if (strcmp(F_NAME(f), "niceprime") == 0) ty = niceprimefield_pytype;
 -  else if (strcmp(F_NAME(f), "binpoly") == 0) ty = binpolyfield_pytype;
 -  else if (strcmp(F_NAME(f), "binnorm") == 0) ty = binnormfield_pytype;
 +  if (STRCMP(F_NAME(f), ==, "prime")) ty = primefield_pytype;
 +  else if (STRCMP(F_NAME(f), ==, "niceprime")) ty = niceprimefield_pytype;
 +  else if (STRCMP(F_NAME(f), ==, "binpoly")) ty = binpolyfield_pytype;
 +  else if (STRCMP(F_NAME(f), ==, "binnorm")) ty = binnormfield_pytype;
    else abort();
    return (field_dopywrap(ty, f));
  }
  
  field *field_copy(field *f)
  {
 -  if (strcmp(F_NAME(f), "prime") == 0)
 +  if (STRCMP(F_NAME(f), ==, "prime"))
      f = field_prime(f->m);
 -  else if (strcmp(F_NAME(f), "niceprime") == 0)
 +  else if (STRCMP(F_NAME(f), ==, "niceprime"))
      f = field_niceprime(f->m);
 -  else if (strcmp(F_NAME(f), "binpoly") == 0)
 +  else if (STRCMP(F_NAME(f), ==, "binpoly"))
      f = field_binpoly(f->m);
 -  else if (strcmp(F_NAME(f), "binnorm") == 0) {
 +  else if (STRCMP(F_NAME(f), ==, "binnorm")) {
      fctx_binnorm *fc = (fctx_binnorm *)f;
      f = field_binnorm(f->m, fc->ntop.r[fc->ntop.n - 1]);
    } else
@@@ -126,16 -126,6 +126,6 @@@ static mp *tofe(field *f, PyObject *o
    return (y);
  }
  
- mp *getfe(field *f, PyObject *o)
- {
-   mp *x = 0;
-   if ((x = tofe(f, o)) == 0) {
-     PyErr_Format(PyExc_TypeError, "can't convert %.100s to fe",
-                o->ob_type->tp_name);
-   }
-   return (x);
- }
  /*----- Field elements ----------------------------------------------------*/
  
  static int febinop(PyObject *x, PyObject *y,
@@@ -229,15 -219,7 +219,7 @@@ end
  }
  
  static long fe_pyhash(PyObject *me)
- {
-   size_t sz = FE_F(me)->noctets;
-   uint32 h = 0xe0c127ca + FE_F(me)->ops->ty;
-   octet *p = xmalloc(sz);
-   mp_storeb(FE_X(me), p, sz);
-   h = unihash_hash(&unihash_global, h, p, sz);
-   xfree(p);
-   return (h % LONG_MAX);
- }
+   { return (mphash(FE_X(me))); }
  
  static int fe_pycoerce(PyObject **x, PyObject **y)
  {
@@@ -282,7 -264,7 +264,7 @@@ static PyObject *fe_pylong(PyObject *x
  #define BASEOP(name, radix, pre)                                      \
    static PyObject *fe_py##name(PyObject *x) {                         \
      mp *xx = F_OUT(FE_F(x), MP_NEW, FE_X(x));                         \
-     PyObject *rc = mp_topystring(FE_X(x), radix, 0, pre, 0);          \
+     PyObject *rc = mp_topystring(xx, radix, 0, pre, 0);                       \
      MP_DROP(xx);                                                      \
      return (rc);                                                      \
    }
@@@ -345,8 -327,8 +327,8 @@@ static PyObject *feget__value(PyObject 
  static PyGetSetDef fe_pygetset[] = {
  #define GETSETNAME(op, name) fe##op##_##name
    GET (field,         "X.field -> field containing X")
-   GET (value,         "X.value -> `natural' integer representation of X")
-   GET (_value,        "X._value -> internal integer representation of X")
+   GET (value,         "X.value -> `natural' MP/GF representation of X")
+   GET (_value,        "X._value -> internal MP/GF representation of X")
  #undef GETSETNAME
    { 0 }
  };
@@@ -533,7 -515,7 +515,7 @@@ static PyGetSetDef field_pygetset[] = 
  static PyMethodDef field_pymethods[] = {
  #define METHNAME(name) fmeth_##name
    METH        (_adopt,        "F._adopt(X) -> FE")
-   KWMETH(rand,                "F.rand(rng = rand) -> FE, uniformly distributed")
+   KWMETH(rand,                "F.rand([rng = rand]) -> FE, uniformly distributed")
  #undef METHNAME
    { 0 }
  };
@@@ -809,8 -791,11 +791,11 @@@ end
    return (0);
  }
  
+ static PyObject *bfget_p(PyObject *me, void *hunoz)
+   { return (gf_pywrap(MP_COPY(FIELD_F(me)->m))); }
  static PyGetSetDef binpolyfield_pygetset[] = {
- #define GETSETNAME(op, name) pf##op##_##name
+ #define GETSETNAME(op, name) bf##op##_##name
    GET (p,             "F.p -> field polynomial")
  #undef GETSETNAME
    { 0 }
@@@ -889,7 -874,7 +874,7 @@@ static PyObject *bnfget_beta(PyObject *
  }
  
  static PyGetSetDef binnormfield_pygetset[] = {
- #define GETSETNAME(op, name) pf##op##_##name
+ #define GETSETNAME(op, name) bf##op##_##name
    GET (p,             "F.p -> field polynomial")
  #undef GETSETNAME
  #define GETSETNAME(op, name) bnf##op##_##name
diff --combined group.c
+++ b/group.c
@@@ -92,18 -92,20 +92,20 @@@ static PyObject *meth__DHInfo_generate(
    unsigned ql = 0, pl;
    unsigned steps = 0;
    grand *r = &rand_global;
-   pgev evt = { 0 };
+   struct excinfo exc = EXCINFO_INIT;
+   pypgev evt = { { 0 } };
    static const char *const kwlist[] =
      { "class", "pbits", "qbits", "event", "rng", "nsteps", 0 };
    PyObject *rc = 0;
  
+   evt.exc = &exc;
    if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&|O&O&O&O&:generate", KWLIST,
                                   &me, convuint, &pl, convuint, &ql,
                                   convpgev, &evt, convgrand, &r,
                                   convuint, &steps))
      goto end;
-   if (dh_gen(&dp, ql, pl, steps, r, evt.proc, evt.ctx))
-     PGENERR;
+   if (dh_gen(&dp, ql, pl, steps, r, evt.ev.proc, evt.ev.ctx))
+     PGENERR(&exc);
    rc = fginfo_pywrap(&dp, dhinfo_pytype);
  end:
    droppgev(&evt);
@@@ -117,7 -119,8 +119,8 @@@ static PyObject *meth__DHInfo_genlimlee
    unsigned ql, pl;
    unsigned steps = 0;
    grand *r = &rand_global;
-   pgev oe = { 0 }, ie = { 0 };
+   struct excinfo exc = EXCINFO_INIT;
+   pypgev oe = { { 0 } }, ie = { { 0 } };
    int subgroupp = 1;
    unsigned f = 0;
    static const char *const kwlist[] = {
    mp **v = 0;
    PyObject *rc = 0, *vec = 0;
  
+   oe.exc = ie.exc = &exc;
    if (!PyArg_ParseTupleAndKeywords(arg, kw,
                                   "OO&O&|O&O&O&O&O&:genlimlee", KWLIST,
                                   &me, convuint, &pl, convuint, &ql,
      goto end;
    if (subgroupp) f |= DH_SUBGROUP;
    if (dh_limlee(&dp, ql, pl, f, steps, r,
-               oe.proc, oe.ctx, ie.proc, ie.ctx, &nf, &v))
-     PGENERR;
+               oe.ev.proc, oe.ev.ctx, ie.ev.proc, ie.ev.ctx, &nf, &v))
+     PGENERR(&exc);
    vec = PyList_New(nf);
    for (i = 0; i < nf; i++)
      PyList_SetItem(vec, i, mp_pywrap(v[i]));
@@@ -156,19 -160,21 +160,21 @@@ static PyObject *meth__DHInfo_genkcdsa(
    unsigned ql, pl;
    unsigned steps = 0;
    grand *r = &rand_global;
-   pgev evt = { 0 };
+   struct excinfo exc = EXCINFO_INIT;
+   pypgev evt = { { 0 } };
    static const char *const kwlist[] =
      { "class", "pbits", "qbits", "event", "rng", "nsteps", 0 };
    mp *v = MP_NEW;
    PyObject *rc = 0;
  
+   evt.exc = &exc;
    if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&O&|O&O&O&:genkcdsa", KWLIST,
                                   &me, convuint, &pl, convuint, &ql,
                                   convpgev, &evt, convgrand, &r,
                                   convuint, &steps))
      goto end;
-   if (dh_kcdsagen(&dp, ql, pl, 0, steps, r, evt.proc, evt.ctx))
-     PGENERR;
+   if (dh_kcdsagen(&dp, ql, pl, 0, steps, r, evt.ev.proc, evt.ev.ctx))
+     PGENERR(&exc);
    mp_div(&v, 0, dp.p, dp.q);
    v = mp_lsr(v, v, 1);
    rc = Py_BuildValue("(NN)", fginfo_pywrap(&dp, dhinfo_pytype),
@@@ -187,18 -193,20 +193,20 @@@ static PyObject *meth__DHInfo_gendsa(Py
    dsa_seed ds;
    char *k;
    Py_ssize_t ksz;
-   pgev evt = { 0 };
+   struct excinfo exc = EXCINFO_INIT;
+   pypgev evt = { { 0 } };
    static const char *const kwlist[] =
      { "class", "pbits", "qbits", "seed", "event", "nsteps", 0 };
    PyObject *rc = 0;
  
+   evt.exc = &exc;
    if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&O&s#|O&O&:gendsa", KWLIST,
                                   &me, convuint, &pl, convuint, &ql,
                                   &k, &ksz, convpgev, &evt,
                                   convuint, &steps))
      goto end;
-   if (dsa_gen(&dp, ql, pl, steps, k, ksz, &ds, evt.proc, evt.ctx))
-     PGENERR;
+   if (dsa_gen(&dp, ql, pl, steps, k, ksz, &ds, evt.ev.proc, evt.ev.ctx))
+     PGENERR(&exc);
    rc = Py_BuildValue("(NNl)", fginfo_pywrap(&dp, dhinfo_pytype),
                     bytestring_pywrap(ds.p, ds.sz), (long)ds.count);
    xfree(ds.p);
@@@ -453,7 -461,7 +461,7 @@@ PyTypeObject *primegroup_pytype, *bingr
  
  group *group_copy(group *g)
  {
 -  if (strcmp(G_NAME(g), "prime") == 0) {
 +  if (STRCMP(G_NAME(g), ==, "prime")) {
      gctx_prime *gc = (gctx_prime *)g;
      gprime_param gp;
      gp.g = G_TOINT(g, MP_NEW, g->g);
      gp.q = gc->g.r;
      g = group_prime(&gp);
      MP_DROP(gp.g);
 -  } else if (strcmp(G_NAME(g), "bin") == 0) {
 +  } else if (STRCMP(G_NAME(g), ==, "bin")) {
      gctx_bin *gc = (gctx_bin *)g;
      gbin_param gb;
      gb.g = G_TOINT(g, MP_NEW, g->g);
      gb.q = gc->g.r;
      g = group_binary(&gb);
      MP_DROP(gb.g);
 -  } else if (strcmp(G_NAME(g), "ec") == 0) {
 +  } else if (STRCMP(G_NAME(g), ==, "ec")) {
      gctx_ec *gc = (gctx_ec *)g;
      ec_info ei;
      if ((ei.c = eccurve_copy(gc->ei.c)) == 0)
@@@ -552,9 -560,9 +560,9 @@@ PyObject *group_pywrap(group *g
  {
    PyTypeObject *ty;
  
 -  if (strcmp(G_NAME(g), "prime") == 0) ty = primegroup_pytype;
 -  else if (strcmp(G_NAME(g), "bin") == 0) ty = bingroup_pytype;
 -  else if (strcmp(G_NAME(g), "ec") == 0) ty = ecgroup_pytype;
 +  if (STRCMP(G_NAME(g), ==, "prime")) ty = primegroup_pytype;
 +  else if (STRCMP(G_NAME(g), ==, "bin")) ty = bingroup_pytype;
 +  else if (STRCMP(G_NAME(g), ==, "ec")) ty = ecgroup_pytype;
    else abort();
    return (group_dopywrap(ty, g));
  }
@@@ -732,7 -740,7 +740,7 @@@ static PyObject *gemeth_toec(PyObject *
      if (!PyType_Check(cty) || !PyType_IsSubtype(cty, ecpt_pytype))
        TYERR("want subtype of catacomb.ECPt");
      Py_INCREF((PyObject *)cty);
 -  } else if (strcmp(G_NAME(g), "ec") == 0) {
 +  } else if (STRCMP(G_NAME(g), ==, "ec")) {
      c = eccurve_copy(((gctx_ec *)g)->ei.c);
      cty = (PyTypeObject *)eccurve_pywrap(0, c);
    } else  {
@@@ -966,7 -974,7 +974,7 @@@ static PyMethodDef ge_pymethods[] = 
    METH        (check,         "X.check() -> check X really belongs to its group")
    METH        (toint,         "X.toint() -> X converted to an integer")
    KWMETH(toec,                "\
- X.toec(curve = ecpt) -> X converted to elliptic curve point")
+ X.toec([curve = ECPt]) -> X converted to elliptic curve point")
    METH        (tobuf,         "X.tobuf() -> X in buffer representation")
    METH        (toraw,         "X.toraw() -> X in raw representation")
  #undef METHNAME
@@@ -1081,7 -1089,7 +1089,7 @@@ static PyMethodDef group_pymethods[] = 
  #define METHNAME(name) gmeth_##name
    METH        (mexp,          "\
  G.mexp([(X0, N0), (X1, N1), ...]) -> X0^N0 X1^N1 ...")
-   KWMETH(checkgroup,  "G.checkgroup(rand = random): check group is good")
+   KWMETH(checkgroup,  "G.checkgroup([rng = rand]): check group is good")
  #undef METHNAME
    { 0 }
  };
@@@ -1379,16 -1387,16 +1387,16 @@@ static PyMethodDef methods[] = 
    METH        (_DHInfo__groupn,       0)
    METH        (_BinDHInfo__groupn,    0)
    KWMETH(_DHInfo_generate,    "\
- generate(PBITS, [qbits = 0, event = pgen_nullev,\n\
-        rng = rand, nsteps = 0]) -> D")
+ generate(PBITS, [qbits = 0], [event = pgen_nullev],\n\
+        [rng = rand], [nsteps = 0]) -> D")
    KWMETH(_DHInfo_genlimlee,   "\
- genlimlee(PBITS, QBITS, [event = pgen_nullev, ievent = pgen_nullev,\n\
-         rng = rand, nsteps = 0, subgroupp = True]) -> (D, [Q, ...])")
+ genlimlee(PBITS, QBITS, [event = pgen_nullev], [ievent = pgen_nullev],\n\
+         [rng = rand], [nsteps = 0], [subgroupp = True]) -> (D, [Q, ...])")
    KWMETH(_DHInfo_gendsa,      "\
- gendsa(PBITS, QBITS, SEED, [event = pgen_nullevnsteps = 0])\n\
+ gendsa(PBITS, QBITS, SEED, [event = pgen_nullev], [nsteps = 0])\n\
    -> (D, SEED, COUNT)")
    KWMETH(_DHInfo_genkcdsa,    "\
- gendsa(PBITS, QBITS, [event = pgen_nullev, rng = rand, nsteps = 0])\n\
+ gendsa(PBITS, QBITS, [event = pgen_nullev], [rng = rand], [nsteps = 0])\n\
    -> (D, V)")
  #undef METHNAME
    { 0 }
diff --combined rand.c
--- 1/rand.c
--- 2/rand.c
+++ b/rand.c
@@@ -27,9 -27,7 +27,9 @@@
  /*----- Header files ------------------------------------------------------*/
  
  #include "catacomb-python.h"
 +PUBLIC_SYMBOLS;
  #include "algorithms.h"
 +PRIVATE_SYMBOLS;
  
  /*----- Main code ---------------------------------------------------------*/
  
@@@ -56,14 -54,14 +56,14 @@@ PyObject *grand_pywrap(grand *r, unsign
    PyTypeObject *ty = grand_pytype;
    PyObject *ob;
  
 -  if (strcmp(r->ops->name, "rand") == 0) ty = truerand_pytype;
 -  else if (strcmp(r->ops->name, "lcrand") == 0) ty = lcrand_pytype;
 -  else if (strcmp(r->ops->name, "fibrand") == 0) ty = fibrand_pytype;
 -  else if (strcmp(r->ops->name, "dsarand") == 0) ty = dsarand_pytype;
 -  else if (strcmp(r->ops->name, "bbs") == 0) ty = bbs_pytype;
 -  else if (strcmp(r->ops->name, "sslprf") == 0) ty = sslprf_pytype;
 -  else if (strcmp(r->ops->name, "tlsdx") == 0) ty = tlsdx_pytype;
 -  else if (strcmp(r->ops->name, "tlsprf") == 0) ty = tlsprf_pytype;
 +  if (STRCMP(r->ops->name, ==, "rand")) ty = truerand_pytype;
 +  else if (STRCMP(r->ops->name, ==, "lcrand")) ty = lcrand_pytype;
 +  else if (STRCMP(r->ops->name, ==, "fibrand")) ty = fibrand_pytype;
 +  else if (STRCMP(r->ops->name, ==, "dsarand")) ty = dsarand_pytype;
 +  else if (STRCMP(r->ops->name, ==, "bbs")) ty = bbs_pytype;
 +  else if (STRCMP(r->ops->name, ==, "sslprf")) ty = sslprf_pytype;
 +  else if (STRCMP(r->ops->name, ==, "tlsdx")) ty = tlsdx_pytype;
 +  else if (STRCMP(r->ops->name, ==, "tlsprf")) ty = tlsprf_pytype;
    else if ((ob = PyDict_GetItemString(gccrands_dict, r->ops->name)) != 0)
      ty = (PyTypeObject *)ob;
    return (grand_dopywrap(ty, r, f));
@@@ -508,7 -506,7 +508,7 @@@ static PyObject *truerand_pynew(PyTypeO
    static const char *const kwlist[] = { 0 };
    grand *r;
    PyObject *rc = 0;
-   if (PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST)) goto end;
+   if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST)) goto end;
    r = rand_create();
    r->ops->misc(r, RAND_NOISESRC, &noise_source);
    r->ops->misc(r, RAND_SEED, 160);
@@@ -1269,7 -1267,7 +1269,7 @@@ static PyObject *bbsget_x(PyObject *me
  
  static int bbsset_x(PyObject *me, PyObject *val, void *hunoz)
  {
-   mp *x = 0; grand *r = GRAND_R(me); int rc = -1; if (!x) NIERR("__del__");
+   mp *x = 0; grand *r = GRAND_R(me); int rc = -1; if (!val) NIERR("__del__");
    if ((x = getmp(val)) == 0) goto end;
    r->ops->misc(r, BBS_SET, x); rc = 0;
    end: mp_drop(x); return (rc);
@@@ -1385,19 -1383,21 +1385,21 @@@ static PyObject *meth__BBSPriv_generate
  {
    bbs_priv bp = { 0 };
    mp *x = MP_TWO;
-   pgev evt = { 0 };
+   struct excinfo exc = EXCINFO_INIT;
+   pypgev evt = { { 0 } };
    unsigned nbits, n = 0;
    grand *r = &rand_global;
    static const char *const kwlist[] =
      { "class", "nbits", "event", "rng", "nsteps", "seed", 0 };
    bbspriv_pyobj *rc = 0;
  
+   evt.exc = &exc;
    if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&|O&O&O&O&:generate", KWLIST,
                                   &me, convuint, &nbits, convpgev, &evt,
                                   convgrand, &r, convuint, &n, convmp, &x))
      goto end;
-   if (bbs_gen(&bp, nbits, r, n, evt.proc, evt.ctx))
-     VALERR("prime genration failed");
+   if (bbs_gen(&bp, nbits, r, n, evt.ev.proc, evt.ev.ctx))
+     PGENERR(&exc);
    rc = PyObject_New(bbspriv_pyobj, bbspriv_pytype);
    rc->gr.r = bbs_rand(bp.n, x);
    rc->gr.f = f_freeme;
@@@ -1483,7 -1483,7 +1485,7 @@@ static PyTypeObject bbspriv_pytype_ske
      Py_TPFLAGS_BASETYPE,
  
    /* @tp_doc@ */
- "BBSPriv(..., seed = 2]): Blum-Blum-Shub, with private key.\n\
+ "BBSPriv(..., [seed = 2]): Blum-Blum-Shub, with private key.\n\
    Keywords: n, p, q; must provide at least two",
  
    0,                                  /* @tp_traverse@ */
  static PyMethodDef methods[] = {
  #define METHNAME(name) meth_##name
    KWMETH(_BBSPriv_generate,           "\
- generate(NBITS, [event = pgen_nullev, rng = rand, nsteps = 0, seed = 2])")
+ generate(NBITS, [event = pgen_nullev], [rng = rand],\n\
+        [nsteps = 0], [seed = 2]) -> R")
  #undef METHNAME
    { 0 }
  };
diff --combined setup.py
+++ b/setup.py
@@@ -4,7 -4,7 +4,7 @@@ import distutils.core as D
  import mdwsetup as MS
  
  MS.pkg_config('catacomb', '2.5.0')
 -MS.pkg_config('mLib', '2.2.2.1')
 +MS.pkg_config('mLib', '2.4.99~')
  
  cat = DC.Extension('catacomb._base',
                     ['catacomb.c', 'bytestring.c', 'buffer.c',
@@@ -26,4 -26,9 +26,9 @@@ MS.setup(name = 'catacomb-python'
           scripts = ['pock', 'pwsafe'],
           data_files = [('share/man/man1', ['pock.1', 'pwsafe.1'])],
           genfiles = [MS.Generate('algorithms.h')],
+          unittest_dir = "t",
+          unittests = ["t-misc", "t-algorithms", "t-bytes", "t-buffer",
+                       "t-convert", "t-ec", "t-field", "t-group", "t-key",
+                       "t-mp", "t-passphrase", "t-pgen", "t-pubkey",
+                       "t-rand", "t-rat", "t-share"],
           ext_modules = [cat])