Split 'pyke/' into commit 'e5aa77d831ad8b42167f3205ee290f238003e20a'
authorMark Wooding <mdw@distorted.org.uk>
Mon, 25 Nov 2019 18:03:03 +0000 (18:03 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 27 Nov 2019 00:39:57 +0000 (00:39 +0000)
git-subtree-dir: pyke
git-subtree-mainline: c719150915e19e1be2a5a315e5e565e1a541921f
git-subtree-split: e5aa77d831ad8b42167f3205ee290f238003e20a

* commit 'e5aa77d831ad8b42167f3205ee290f238003e20a': (102 commits)
  pyke/pyke-mLib.c: Raise `OverflowError' on out-of-range inputs.
  Port to Python 3.
  *.[ch]: Some preparatory reformatting for the Python 3 porting.
  mp.c, catacomb/__init__.py, pyke/: Fix mixed-mode arithmetic involving `float'.
  *.c: Separate string function calls according to text/binary usage.
  *.c: Introduce a new input conversion for binary strings.
  *.c: Use the new `Py_hash_t' type.
  *.c: Use `PyVarObject_HEAD_INIT' to initialize type object headers.
  *.c: Use the new `Py_TYPE' and `Py_SIZE' macros; define them if necessary.
  pyke/mapping.c, key.c: Make the mapping code more intrusive and complete.
  pyke/mapping.c: Introduce macro for unconstifying common keyword list.
  pyke/pyke.c (newtype): Explicitly clear `ht_slots'.
  *.c: Split the constant definitions into the various submodules.
  pyke/pyke.[ch]: Make type skeleton structures be read-only.
  pyke/pyke.h: Add a `MEMBER' variant with explicit member name.
  *.c: Use Python `METH_NOARGS' methods where applicable.
  *.c: Use Python's facilities for defining class and static methods.
  *.c: Make all of the type-definition tables read-only.
  pyke/pyke.c: Check conversions hidden inside `KWLIST' and `KWMETH'.
  pyke/pyke.h, key.c: Rename `INDEXERR' to `MAPERR'.
  ...

Note that history prior to a172f897f4fdd800b3f70cdfe2140205f6b2f531
(`pyke/, ...: Extract utilities into a sort-of reusable library') is
pared down from the original Catacomb/Python history, but should be
enough for useful blaming and bisection.

64 files changed:
.gitignore [new file with mode: 0644]
.links [new file with mode: 0644]
.mailmap [new file with mode: 0644]
.skelrc
MANIFEST.in [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
algorithms.c [new file with mode: 0644]
algorithms.py [new file with mode: 0644]
buffer.c [new file with mode: 0644]
bytestring.c [new file with mode: 0644]
catacomb-python.h [new file with mode: 0644]
catacomb.c [new file with mode: 0644]
catacomb/__init__.py [new file with mode: 0644]
catacomb/pwsafe.py [new file with mode: 0644]
debian/.gitignore [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/python-catacomb.install [new file with mode: 0644]
debian/python3-catacomb.install [new file with mode: 0644]
debian/rules [new file with mode: 0755]
debian/source/format [new file with mode: 0644]
ec.c [new file with mode: 0644]
field.c [new file with mode: 0644]
group.c [new file with mode: 0644]
key.c [new file with mode: 0644]
mp.c [new file with mode: 0644]
passphrase.c [new file with mode: 0644]
pgen.c [new file with mode: 0644]
pock [new file with mode: 0644]
pock.1 [new file with mode: 0644]
pubkey.c [new file with mode: 0644]
pwsafe [new file with mode: 0644]
pwsafe.1 [new file with mode: 0644]
pyke/.skelrc [new file with mode: 0644]
pyke/mapping-mLib.c [moved from mapping-mLib.c with 100% similarity]
pyke/mapping.c [moved from mapping.c with 100% similarity]
pyke/pyke-mLib.c [moved from pyke-mLib.c with 100% similarity]
pyke/pyke-mLib.h [moved from pyke-mLib.h with 100% similarity]
pyke/pyke.c [moved from pyke.c with 100% similarity]
pyke/pyke.h [moved from pyke.h with 100% similarity]
rand.c [new file with mode: 0644]
setup.py [new file with mode: 0755]
share.c [new file with mode: 0644]
t/keyring [new file with mode: 0644]
t/t-algorithms.py [new file with mode: 0644]
t/t-buffer.py [new file with mode: 0644]
t/t-bytes.py [new file with mode: 0644]
t/t-convert.py [new file with mode: 0644]
t/t-ec.py [new file with mode: 0644]
t/t-field.py [new file with mode: 0644]
t/t-group.py [new file with mode: 0644]
t/t-key.py [new file with mode: 0644]
t/t-misc.py [new file with mode: 0644]
t/t-mp.py [new file with mode: 0644]
t/t-passphrase.py [new file with mode: 0644]
t/t-pgen.py [new file with mode: 0644]
t/t-pubkey.py [new file with mode: 0644]
t/t-rand.py [new file with mode: 0644]
t/t-rat.py [new file with mode: 0644]
t/t-share.py [new file with mode: 0644]
t/testutils.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..d245190
--- /dev/null
@@ -0,0 +1,12 @@
+*.pyc
+__pycache__/
+
+/COPYING
+/MANIFEST
+/algorithms.h
+/auto-version
+/build/
+/mdwsetup.py
+/pysetup.mk
+
+/t/keyring.old
diff --git a/.links b/.links
new file mode 100644 (file)
index 0000000..4e00440
--- /dev/null
+++ b/.links
@@ -0,0 +1,4 @@
+COPYING
+auto-version
+mdwsetup.py
+pysetup.mk
diff --git a/.mailmap b/.mailmap
new file mode 100644 (file)
index 0000000..96fe7ad
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1 @@
+Mark Wooding <mdw@distorted.org.uk> <mdw>
diff --git a/.skelrc b/.skelrc
index 21912db..f72cc74 100644 (file)
--- a/.skelrc
+++ b/.skelrc
@@ -4,6 +4,6 @@
       (append
        '((author . "Straylight/Edgeware")
         (licence-text . "[[gpl]]")
-        (full-title . "Pyke: the Python Kit for Extensions")
-        (program . "Pyke"))
+        (full-title . "the Python interface to Catacomb")
+        (program . "Catacomb/Python"))
        skel-alist))
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644 (file)
index 0000000..84b81d1
--- /dev/null
@@ -0,0 +1,29 @@
+### Manifest template for Catacomb/Python. -*-conf-*-
+
+## Generated build machinery.
+include COPYING auto-version mdwsetup.py pysetup.mk
+
+## Basic build stuff.
+include MANIFEST.in setup.py Makefile
+
+## C extension code.
+include *.c *.h
+include pyke/*.c pyke/*.h
+include t/*.py t/keyring
+include algorithms.py
+exclude algorithms.h
+
+## Scripts.
+include pock
+include pwsafe
+
+## Manual pages.
+include *.[13]
+
+## Python wrapping.
+recursive-include catacomb *.py
+
+## Debian.
+include debian/rules debian/control debian/changelog
+include debian/copyright debian/compat debian/source/format
+include debian/*.install
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..b9f9347
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,2 @@
+### -*-makefile-*-
+include pysetup.mk
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..bd28614
--- /dev/null
+++ b/README
@@ -0,0 +1,69 @@
+Nothing much to say yet.
+
+Class hierarchy
+
+       bytestring
+
+       mp
+       mpmont
+       mpbarrett
+       mpreduce
+       mpcrt
+
+       gf
+       gfreduce
+       gfn
+
+       field
+         primefield
+           niceprimefield
+         binfield
+           binpolyfield
+           binnormfield
+       fe
+         [field objects]
+
+       eccurve
+         ecprimecurve
+           ecprimeprojcurve
+         ecbincurve
+           ecbinprojcurve
+       ecinfo
+       ecpt
+         [eccurve objects]
+
+       fginfo
+         dhinfo
+         bindhinfo
+
+       group
+         primegroup
+         bingroup
+         ecgroup
+       ge
+         [group objects]
+
+       grand
+         truerand
+
+       keysz
+         keyszany
+         keyszrange
+         keyszset
+
+       gccipher
+       gcipher
+         [gccipher objects]
+       gcmac
+       gmac
+         [gcmac objects]
+       gchash
+       ghash
+         [gchash objects]
+         gmhash
+           [gmac objects]
+
+ecinfo(curve = ecprimeprojcurve(),
+      G = ecprimeprojcurve()(),
+      r = 115792089210356248762697446949407573529996955224135760342422259061068512044369,
+      h = 1)
diff --git a/algorithms.c b/algorithms.c
new file mode 100644 (file)
index 0000000..be07fb7
--- /dev/null
@@ -0,0 +1,3939 @@
+/* -*-c-*-
+ *
+ * Symmetric cryptography
+ *
+ * (c) 2004 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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "catacomb-python.h"
+PUBLIC_SYMBOLS;
+#include "algorithms.h"
+PRIVATE_SYMBOLS;
+
+/*----- Key sizes ---------------------------------------------------------*/
+
+static PyTypeObject *keysz_pytype;
+static PyTypeObject *keyszany_pytype, *keyszrange_pytype, *keyszset_pytype;
+
+typedef struct keysz_pyobj {
+  PyObject_HEAD
+  int dfl;
+} keysz_pyobj;
+
+typedef struct keyszrange_pyobj {
+  PyObject_HEAD
+  int dfl;
+  int min, max, mod;
+} keyszrange_pyobj;
+
+typedef struct keyszset_pyobj {
+  PyObject_HEAD
+  int dfl;
+  PyObject *set;
+} keyszset_pyobj;
+
+#define KEYSZ_PYCHECK(o) PyObject_TypeCheck((o), keysz_pytype)
+
+#ifndef KSZ_OPMASK
+#  define KSZ_OPMASK 0x1f
+#endif
+
+#ifndef KSZ_16BIT
+#  define KSZ_16BIT 0x20
+#endif
+
+PyObject *keysz_pywrap(const octet *k)
+{
+  unsigned op = *k++;
+#define ARG(i) (op&KSZ_16BIT ? LOAD16(k + 2*(i)) : k[i])
+  switch (op&KSZ_OPMASK) {
+    case KSZ_ANY: {
+      keysz_pyobj *o = PyObject_New(keysz_pyobj, keyszany_pytype);
+      o->dfl = ARG(0);
+      return ((PyObject *)o);
+    } break;
+    case KSZ_RANGE: {
+      keyszrange_pyobj *o =
+       PyObject_New(keyszrange_pyobj, keyszrange_pytype);
+      o->dfl = ARG(0);
+      o->min = ARG(1);
+      o->max = ARG(2);
+      o->mod = ARG(3);
+      if (!o->mod) o->mod = 1;
+      return ((PyObject *)o);
+    } break;
+    case KSZ_SET: {
+      keyszset_pyobj *o =
+       PyObject_New(keyszset_pyobj, keyszset_pytype);
+      PyObject *l;
+      int i, n;
+      o->dfl = ARG(0);
+      for (i = 0; ARG(i); i++) ;
+      n = i; l = PyList_New(n);
+      for (i = 0; i < n; i++) PyList_SET_ITEM(l, i, PyInt_FromLong(ARG(i)));
+      o->set = PyFrozenSet_New(l); Py_DECREF(l);
+      return ((PyObject *)o);
+    } break;
+    default:
+      abort();
+  }
+#undef ARG
+}
+
+static PyObject *keyszany_pynew(PyTypeObject *ty,
+                               PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "default", 0 };
+  int dfl;
+  keysz_pyobj *o;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "i:new", KWLIST, &dfl))
+    goto end;
+  if (dfl < 0) VALERR("key size cannot be negative");
+  o = (keysz_pyobj *)ty->tp_alloc(ty, 0);
+  o->dfl = dfl;
+  return ((PyObject *)o);
+end:
+  return (0);
+}
+
+static PyObject *keyszrange_pynew(PyTypeObject *ty,
+                                 PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "default", "min", "max", "mod", 0 };
+  int dfl, min = 0, max, mod = 1;
+  PyObject *maxobj = Py_None;
+  keyszrange_pyobj *o;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "i|iOi:new", KWLIST,
+                                  &dfl, &min, &maxobj, &mod))
+    goto end;
+  if (maxobj == Py_None)
+    max = 0;
+  else {
+    max = PyInt_AsLong(maxobj);
+    if (max == -1 && PyErr_Occurred()) goto end;
+  }
+  if (dfl < 0 || min < 0 || max < 0) VALERR("key size cannot be negative");
+  if (min > dfl || (max && dfl > max)) VALERR("bad key size bounds");
+  if (mod <= 0 || dfl%mod || min%mod || max%mod)
+    VALERR("bad key size modulus");
+  o = (keyszrange_pyobj *)ty->tp_alloc(ty, 0);
+  o->dfl = dfl;
+  o->min = min;
+  o->max = max;
+  o->mod = mod;
+  return ((PyObject *)o);
+end:
+  return (0);
+}
+
+static PyObject *keyszset_pynew(PyTypeObject *ty,
+                               PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "default", "set", 0 };
+  int dfl, xx;
+  PyObject *set = 0;
+  PyObject *x = 0, *l = 0, *i = 0;
+  keyszset_pyobj *o = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "i|O:new", KWLIST, &dfl, &set))
+    goto end;
+  if (set) i = PyObject_GetIter(set);
+  else { set = PyTuple_New(0); i = PyObject_GetIter(set); Py_DECREF(set); }
+  if (!i) 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); Py_DECREF(x); x = 0;
+  for (;;) {
+    x = PyIter_Next(i); if (!x) break;
+    xx = PyInt_AsLong(x); if (xx == -1 && PyErr_Occurred()) goto end;
+    if (xx < 0) VALERR("key size cannot be negative");
+    PyList_Append(l, x); Py_DECREF(x); x = 0;
+  }
+  if ((set = PyFrozenSet_New(l)) == 0) goto end;
+  o = (keyszset_pyobj *)ty->tp_alloc(ty, 0);
+  o->dfl = dfl;
+  o->set = set;
+end:
+  Py_XDECREF(l);
+  Py_XDECREF(x);
+  return ((PyObject *)o);
+}
+
+static PyObject *kaget_min(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(0)); }
+static PyObject *kaget_max(PyObject *me, void *hunoz)
+  { RETURN_NONE; }
+
+static PyObject *krget_max(PyObject *me, void *hunoz)
+{
+  int max = ((keyszrange_pyobj *)me)->max;
+  if (max) return (PyInt_FromLong(max));
+  else RETURN_NONE;
+}
+
+static PyObject *ksget_min(PyObject *me, void *hunoz)
+{
+  PyObject *i = PyObject_GetIter(((keyszset_pyobj *)me)->set);
+  PyObject *v = 0;
+  int y, x = -1;
+  for (;;) {
+    v = PyIter_Next(i); if (!v) break;
+    y = PyInt_AsLong(v); assert(y >= 0);
+    if (x == -1 || y < x) x = y;
+  }
+  Py_DECREF(i); Py_XDECREF(v);
+  if (PyErr_Occurred()) return (0);
+  return (PyInt_FromLong(x));
+}
+
+static PyObject *ksget_max(PyObject *me, void *hunoz)
+{
+  PyObject *i = PyObject_GetIter(((keyszset_pyobj *)me)->set);
+  PyObject *v = 0;
+  int y, x = -1;
+  for (;;) {
+    v = PyIter_Next(i); if (!v) break;
+    y = PyInt_AsLong(v); assert(y >= 0);
+    if (y > x) x = y;
+  }
+  Py_DECREF(i); Py_XDECREF(v);
+  if (PyErr_Occurred()) return (0);
+  return (PyInt_FromLong(x));
+}
+
+static const PyMemberDef keysz_pymembers[] = {
+#define MEMBERSTRUCT keysz_pyobj
+  MEMRNM(default, T_INT, dfl, READONLY, "KSZ.default -> default key size")
+#undef MEMBERSTRUCT
+  { 0 }
+};
+
+#define KSZCONVOP(op)                                                  \
+  static PyObject *kszmeth_##op(PyObject *me, PyObject *arg)           \
+  {                                                                    \
+    double x, y;                                                       \
+    if (!PyArg_ParseTuple(arg, "d:" #op, &x)) return (0);              \
+    y = keysz_##op(x);                                                 \
+    return (PyFloat_FromDouble(y));                                    \
+  }
+KSZCONVOP(fromdl)
+KSZCONVOP(fromschnorr)
+KSZCONVOP(fromif)
+KSZCONVOP(fromec)
+KSZCONVOP(todl)
+KSZCONVOP(toschnorr)
+KSZCONVOP(toif)
+KSZCONVOP(toec)
+#undef KSZCONVOP
+
+static const PyMethodDef keysz_pymethods[] = {
+#define METHNAME(name) kszmeth_##name
+  SMTH (fromdl,        "fromdl(N) -> M: "
+                   "convert integer discrete log field size to work factor")
+  SMTH (fromschnorr, "fromschnorr(N) -> M: "
+                               "convert Schnorr group order to work factor")
+  SMTH (fromif,        "fromif(N) -> M: "
+                "convert integer factorization problem size to work factor")
+  SMTH (fromec,        "fromec(N) -> M: "
+                        "convert elliptic curve group order to work factor")
+  SMTH (todl,          "todl(N) -> M: "
+                   "convert work factor to integer discrete log field size")
+  SMTH (toschnorr,     "toschnorr(N) -> M: "
+                               "convert work factor to Schnorr group order")
+  SMTH (toif,          "toif(N) -> M: "
+                "convert work factor to integer factorization problem size")
+  SMTH (toec,          "toec(N) -> M: "
+                        "convert work factor to elliptic curve group order")
+  SMTH (toec,          "toec(N) -> M: "
+                        "convert work factor to elliptic curve group order")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyGetSetDef keyszany_pygetset[] = {
+#define GETSETNAME(op, name) ka##op##_##name
+  GET  (min,           "KSZ.min -> smallest allowed key size")
+  GET  (max,           "KSZ.max -> largest allowed key size")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMemberDef keyszrange_pymembers[] = {
+#define MEMBERSTRUCT keyszrange_pyobj
+  MEMBER(min,  T_INT,    READONLY, "KSZ.min -> smallest allowed key size")
+  MEMBER(mod,  T_INT,    READONLY,
+                           "KSZ.mod -> key size must be a multiple of this")
+#undef MEMBERSTRUCT
+  { 0 }
+};
+
+static const PyGetSetDef keyszrange_pygetset[] = {
+#define GETSETNAME(op, name) kr##op##_##name
+  GET  (max,           "KSZ.max -> largest allowed key size")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyGetSetDef keyszset_pygetset[] = {
+#define GETSETNAME(op, name) ks##op##_##name
+  GET  (min,           "KSZ.min -> smallest allowed key size")
+  GET  (max,           "KSZ.max -> largest allowed key size")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMemberDef keyszset_pymembers[] = {
+#define MEMBERSTRUCT keyszset_pyobj
+  MEMBER(set,  T_OBJECT, READONLY, "KSZ.set -> allowed key sizes")
+#undef MEMBERSTRUCT
+  { 0 }
+};
+
+static const PyTypeObject keysz_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KeySZ",                             /* @tp_name@ */
+  sizeof(keysz_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Key size constraints.  Abstract.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(keysz),                    /* @tp_methods@ */
+  PYMEMBERS(keysz),                    /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject keyszany_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KeySZAny",                          /* @tp_name@ */
+  sizeof(keysz_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KeySZAny(DEFAULT)\n"
+  "  Key size constraints.  This object imposes no constraints on size.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(keyszany),                  /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  keyszany_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject keyszrange_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KeySZRange",                                /* @tp_name@ */
+  sizeof(keyszrange_pyobj),            /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KeySZRange(DEFAULT, [min = 0], [max = 0], [mod = 1])\n"
+  "  Key size constraints: size must be between MIN and MAX inclusive, and\n"
+  "  be a multiple of MOD.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  PYMEMBERS(keyszrange),               /* @tp_members@ */
+  PYGETSET(keyszrange),                        /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  keyszrange_pynew,                    /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject keyszset_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KeySZSet",                          /* @tp_name@ */
+  sizeof(keyszset_pyobj),              /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KeySZSet(DEFAULT, ITER)\n"
+  "  Key size constraints: size must be DEFAULT or an element of ITER.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  PYMEMBERS(keyszset),                 /* @tp_members@ */
+  PYGETSET(keyszset),                  /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  keyszset_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Symmetric encryption ----------------------------------------------*/
+
+static PyTypeObject *gccipher_pytype, *gcipher_pytype;
+
+typedef struct gccipher_pyobj {
+  PyHeapTypeObject ty;
+  gccipher *cc;
+} gccipher_pyobj;
+
+#define GCCIPHER_PYCHECK(o) PyObject_TypeCheck((o), gccipher_pytype)
+#define GCCIPHER_CC(o) (((gccipher_pyobj *)(o))->cc)
+
+typedef struct gcipher_pyobj {
+  PyObject_HEAD
+  gcipher *c;
+} gcipher_pyobj;
+
+#define GCIPHER_PYCHECK(o) PyObject_TypeCheck((o), gcipher_pytype)
+#define GCIPHER_C(o) (((gcipher_pyobj *)(o))->c)
+
+CONVFUNC(gccipher, gccipher *, GCCIPHER_CC)
+
+static PyObject *gcipher_pywrap(PyObject *cobj, gcipher *c)
+{
+  gcipher_pyobj *g;
+  if (!cobj) cobj = gccipher_pywrap((/*unconst*/ gccipher *)GC_CLASS(c));
+  else Py_INCREF(cobj);
+  g = PyObject_NEW(gcipher_pyobj, (PyTypeObject *)cobj);
+  g->c = c;
+  return ((PyObject *)g);
+}
+
+static PyObject *gcipher_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "k", 0 };
+  struct bin k;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convbin, &k))
+    goto end;
+  if (keysz(k.sz, GCCIPHER_CC(ty)->keysz) != k.sz) VALERR("bad key length");
+  return (gcipher_pywrap((PyObject *)ty,
+                        GC_INIT(GCCIPHER_CC(ty), k.p, k.sz)));
+end:
+  return (0);
+}
+
+PyObject *gccipher_pywrap(gccipher *cc)
+{
+  gccipher_pyobj *g = newtype(gccipher_pytype, 0, cc->name);
+  g->cc = cc;
+  g->ty.ht_type.tp_basicsize = sizeof(gcipher_pyobj);
+  g->ty.ht_type.tp_base = gcipher_pytype;
+  Py_INCREF(gcipher_pytype);
+  g->ty.ht_type.tp_flags = (Py_TPFLAGS_DEFAULT |
+                           Py_TPFLAGS_BASETYPE |
+                           Py_TPFLAGS_HEAPTYPE);
+  g->ty.ht_type.tp_alloc = PyType_GenericAlloc;
+  g->ty.ht_type.tp_free = 0;
+  g->ty.ht_type.tp_new = gcipher_pynew;
+  typeready(&g->ty.ht_type);
+  return ((PyObject *)g);
+}
+
+static void gcipher_pydealloc(PyObject *me)
+{
+  GC_DESTROY(GCIPHER_C(me));
+  Py_DECREF(Py_TYPE(me));
+  FREEOBJ(me);
+}
+
+static PyObject *gccget_name(PyObject *me, void *hunoz)
+  { return (TEXT_FROMSTR(GCCIPHER_CC(me)->name)); }
+
+static PyObject *gccget_keysz(PyObject *me, void *hunoz)
+  { return (keysz_pywrap(GCCIPHER_CC(me)->keysz)); }
+
+static PyObject *gccget_blksz(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GCCIPHER_CC(me)->blksz)); }
+
+static PyObject *gcmeth_encrypt(PyObject *me, PyObject *arg)
+{
+  struct bin m;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:encrypt", convbin, &m)) return (0);
+  rc = bytestring_pywrap(0, m.sz);
+  GC_ENCRYPT(GCIPHER_C(me), m.p, BIN_PTR(rc), m.sz);
+  return (rc);
+}
+
+static PyObject *gcmeth_enczero(PyObject *me, PyObject *arg)
+{
+  char *p;
+  int sz;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "i:enczero", &sz)) return (0);
+  rc = bytestring_pywrap(0, sz);
+  p = BIN_PTR(rc);
+  memset(p, 0, sz);
+  GC_ENCRYPT(GCIPHER_C(me), p, p, sz);
+  return (rc);
+}
+
+static PyObject *gcmeth_decrypt(PyObject *me, PyObject *arg)
+{
+  struct bin c;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:decrypt", convbin, &c)) return (0);
+  rc = bytestring_pywrap(0, c.sz);
+  GC_DECRYPT(GCIPHER_C(me), c.p, BIN_PTR(rc), c.sz);
+  return (rc);
+}
+
+static PyObject *gcmeth_deczero(PyObject *me, PyObject *arg)
+{
+  char *p;
+  int sz;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "i:deczero", &sz)) return (0);
+  rc = bytestring_pywrap(0, sz);
+  p = BIN_PTR(rc);
+  memset(p, 0, sz);
+  GC_DECRYPT(GCIPHER_C(me), p, p, sz);
+  return (rc);
+}
+
+static PyObject *gcmeth_setiv(PyObject *me, PyObject *arg)
+{
+  struct bin iv;
+
+  if (!PyArg_ParseTuple(arg, "O&:setiv", convbin, &iv)) goto end;
+  if (!GCIPHER_C(me)->ops->setiv) VALERR("`setiv' not supported");
+  if (!GC_CLASS(GCIPHER_C(me))->blksz) VALERR("not a block cipher mode");
+  if (iv.sz != GC_CLASS(GCIPHER_C(me))->blksz) VALERR("bad IV length");
+  GC_SETIV(GCIPHER_C(me), iv.p);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *gcmeth_bdry(PyObject *me)
+{
+  if (!GCIPHER_C(me)->ops->bdry) VALERR("`bdry' not supported");
+  if (!GC_CLASS(GCIPHER_C(me))->blksz) VALERR("not a block cipher mode");
+  GC_BDRY(GCIPHER_C(me));
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static const PyGetSetDef gccipher_pygetset[] = {
+#define GETSETNAME(op, name) gcc##op##_##name
+  GET  (keysz,         "CC.keysz -> acceptable key sizes")
+  GET  (blksz,         "CC.blksz -> block size, or zero")
+  GET  (name,          "CC.name -> name of this kind of cipher")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef gcipher_pymethods[] = {
+#define METHNAME(name) gcmeth_##name
+  METH (encrypt,       "C.encrypt(PT) -> CT")
+  METH (enczero,       "C.enczero(N) -> CT")
+  METH (decrypt,       "C.decrypt(CT) -> PT")
+  METH (deczero,       "C.deczero(N) -> PT")
+  METH (setiv,         "C.setiv(IV)")
+  NAMETH(bdry,         "C.bdry()")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject gccipher_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GCCipher",                          /* @tp_name@ */
+  sizeof(gccipher_pyobj),              /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Symmetric cipher metaclass.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gccipher),                  /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject gcipher_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GCipher",                           /* @tp_name@ */
+  sizeof(gcipher_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  gcipher_pydealloc,                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Symmetric cipher, abstract base class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gcipher),                  /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Authenticated encryption ------------------------------------------*/
+
+static PyTypeObject *gcaead_pytype, *gaeadkey_pytype;
+static PyTypeObject *gcaeadaad_pytype, *gaeadaad_pytype;
+static PyTypeObject *gcaeadenc_pytype, *gaeadenc_pytype;
+static PyTypeObject *gcaeaddec_pytype, *gaeaddec_pytype;
+
+typedef struct gcaead_pyobj {
+  PyHeapTypeObject ty;
+  gcaead *aec;
+  struct gcaeadaad_pyobj *aad;
+  struct gcaeadenc_pyobj *enc;
+  struct gcaeaddec_pyobj *dec;
+} gcaead_pyobj;
+
+#define GCAEAD_PYCHECK(o) PyObject_TypeCheck((o), gcaead_pytype)
+#define GCAEAD_AEC(o) (((gcaead_pyobj *)(o))->aec)
+#define GCAEAD_AAD(o) (((gcaead_pyobj *)(o))->aad)
+#define GCAEAD_ENC(o) (((gcaead_pyobj *)(o))->enc)
+#define GCAEAD_DEC(o) (((gcaead_pyobj *)(o))->dec)
+static PyObject *gcaead_pywrap(gcaead *);
+
+typedef struct gaeadkey_pyobj {
+  PyObject_HEAD
+  gaead_key *k;
+} gaeadkey_pyobj;
+
+#define GAEADKEY_PYCHECK(o) PyObject_TypeCheck((o), gaeadkey_pytype)
+#define GAEADKEY_K(o) (((gaeadkey_pyobj *)(o))->k)
+
+typedef struct gcaeadaad_pyobj {
+  PyHeapTypeObject ty;
+  gcaead_pyobj *key;
+} gcaeadaad_pyobj;
+
+#define GCAEADAAD_KEY(o) (((gcaeadaad_pyobj *)(o))->key)
+static PyObject *gaeadaad_pywrap(PyObject *, gaead_aad *, unsigned, size_t);
+
+typedef struct gaeadaad_pyobj {
+  PyObject_HEAD
+  gaead_aad *a;
+  unsigned f;
+#define AEADF_DEAD 32768u
+  size_t hsz, hlen;
+} gaeadaad_pyobj;
+
+#define GAEADAAD_PYCHECK(o) PyObject_TypeCheck((o), gaeadaad_pytype)
+#define GAEADAAD_A(o) (((gaeadaad_pyobj *)(o))->a)
+#define GAEADAAD_F(o) (((gaeadaad_pyobj *)(o))->f)
+#define GAEADAAD_HSZ(o) (((gaeadaad_pyobj *)(o))->hsz)
+#define GAEADAAD_HLEN(o) (((gaeadaad_pyobj *)(o))->hlen)
+
+typedef struct gcaeadenc_pyobj {
+  PyHeapTypeObject ty;
+  gcaead_pyobj *key;
+} gcaeadenc_pyobj;
+
+#define GCAEADENC_KEY(o) (((gcaeadenc_pyobj *)(o))->key)
+static PyObject *gaeadenc_pywrap(PyObject *, gaead_enc *, unsigned,
+                                size_t, size_t, size_t);
+
+typedef struct gaeadenc_pyobj {
+  PyObject_HEAD
+  gaead_enc *e;
+  gaeadaad_pyobj *aad;
+  unsigned f;
+  size_t hsz, msz, tsz;
+  size_t mlen;
+} gaeadenc_pyobj;
+
+#define GAEADENC_PYCHECK(o) PyObject_TypeCheck((o), gaeadenc_pytype)
+#define GAEADENC_AAD(o) (((gaeadenc_pyobj *)(o))->aad)
+#define GAEADENC_E(o) (((gaeadenc_pyobj *)(o))->e)
+#define GAEADENC_F(o) (((gaeadenc_pyobj *)(o))->f)
+#define GAEADENC_HSZ(o) (((gaeadenc_pyobj *)(o))->hsz)
+#define GAEADENC_MSZ(o) (((gaeadenc_pyobj *)(o))->msz)
+#define GAEADENC_TSZ(o) (((gaeadenc_pyobj *)(o))->tsz)
+#define GAEADENC_MLEN(o) (((gaeadenc_pyobj *)(o))->mlen)
+
+typedef struct gcaeaddec_pyobj {
+  PyHeapTypeObject ty;
+  gcaead_pyobj *key;
+} gcaeaddec_pyobj;
+
+#define GCAEADDEC_KEY(o) (((gcaeaddec_pyobj *)(o))->key)
+static PyObject *gaeaddec_pywrap(PyObject *, gaead_dec *, unsigned,
+                                size_t, size_t, size_t);
+
+typedef struct gaeaddec_pyobj {
+  PyObject_HEAD
+  gaead_dec *d;
+  gaeadaad_pyobj *aad;
+  unsigned f;
+  size_t hsz, csz, tsz;
+  size_t clen;
+} gaeaddec_pyobj;
+
+#define GAEADDEC_PYCHECK(o) PyObject_TypeCheck((o), gaeaddec_pytype)
+#define GAEADDEC_AAD(o) (((gaeaddec_pyobj *)(o))->aad)
+#define GAEADDEC_D(o) (((gaeaddec_pyobj *)(o))->d)
+#define GAEADDEC_F(o) (((gaeaddec_pyobj *)(o))->f)
+#define GAEADDEC_HSZ(o) (((gaeaddec_pyobj *)(o))->hsz)
+#define GAEADDEC_CSZ(o) (((gaeaddec_pyobj *)(o))->csz)
+#define GAEADDEC_TSZ(o) (((gaeaddec_pyobj *)(o))->tsz)
+#define GAEADDEC_CLEN(o) (((gaeaddec_pyobj *)(o))->clen)
+
+static PyObject *gaeadkey_pywrap(PyObject *cobj, gaead_key *k)
+{
+  gaeadkey_pyobj *gk;
+
+  if (!cobj) cobj = gcaead_pywrap((/*unconst*/ gcaead *)GAEAD_CLASS(k));
+  else Py_INCREF(cobj);
+  gk = PyObject_NEW(gaeadkey_pyobj, (PyTypeObject *)cobj);
+  gk->k = k;
+  return ((PyObject *)gk);
+}
+
+static PyObject *gaeadkey_pynew(PyTypeObject *ty,
+                               PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "k", 0 };
+  struct bin k;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convbin, &k))
+    goto end;
+  if (keysz(k.sz, GCAEAD_AEC(ty)->keysz) != k.sz) VALERR("bad key length");
+  return (gaeadkey_pywrap((PyObject *)ty,
+                         GAEAD_KEY(GCAEAD_AEC(ty), k.p, k.sz)));
+end:
+  return (0);
+}
+
+static PyObject *gcaead_pywrap(gcaead *aec)
+{
+  gcaead_pyobj *gck;
+  gcaeadaad_pyobj *gca;
+  gcaeadenc_pyobj *gce;
+  gcaeaddec_pyobj *gcd;
+
+#define MKTYPE(obj, thing, newfn, namefmt) do {                                \
+  (obj) = newtype(gcaead_pytype, 0, 0);                                        \
+  (obj)->ty.ht_name = TEXT_FORMAT(namefmt, aec->name);                 \
+  (obj)->ty.ht_type.tp_name = TEXT_PTR((obj)->ty.ht_name);             \
+  (obj)->ty.ht_type.tp_basicsize = sizeof(gaead##thing##_pyobj);       \
+  (obj)->ty.ht_type.tp_base = gaead##thing##_pytype;                   \
+  Py_INCREF(gaead##thing##_pytype);                                    \
+  (obj)->ty.ht_type.tp_flags =                                         \
+    (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE);  \
+  (obj)->ty.ht_type.tp_alloc = PyType_GenericAlloc;                    \
+  (obj)->ty.ht_type.tp_free = 0;                                       \
+  (obj)->ty.ht_type.tp_new = newfn;                                    \
+  typeready(&(obj)->ty.ht_type);                                       \
+} while (0)
+
+  MKTYPE(gck, key, gaeadkey_pynew, "%s(key)");
+  MKTYPE(gca, aad, abstract_pynew, "%s(aad-hash)");
+  MKTYPE(gce, enc, abstract_pynew, "%s(encrypt)");
+  MKTYPE(gcd, dec, abstract_pynew, "%s(decrypt)");
+
+#undef MKTYPE
+
+  gck->aec = aec; gck->aad = gca; gck->enc = gce; gck->dec = gcd;
+  gca->key = gce->key = gcd->key = gck;
+  return ((PyObject *)gck);
+}
+
+static void gaeadkey_pydealloc(PyObject *me)
+  { GAEAD_DESTROY(GAEADKEY_K(me)); Py_DECREF(Py_TYPE(me)); FREEOBJ(me); }
+
+static PyObject *gcaeget_name(PyObject *me, void *hunoz)
+  { return (TEXT_FROMSTR(GCAEAD_AEC(me)->name)); }
+
+static PyObject *gcaeget_keysz(PyObject *me, void *hunoz)
+  { return (keysz_pywrap(GCAEAD_AEC(me)->keysz)); }
+
+static PyObject *gcaeget_noncesz(PyObject *me, void *hunoz)
+  { return (keysz_pywrap(GCAEAD_AEC(me)->noncesz)); }
+
+static PyObject *gcaeget_tagsz(PyObject *me, void *hunoz)
+  { return (keysz_pywrap(GCAEAD_AEC(me)->tagsz)); }
+
+static PyObject *gcaeget_blksz(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GCAEAD_AEC(me)->blksz)); }
+
+static PyObject *gcaeget_bufsz(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GCAEAD_AEC(me)->bufsz)); }
+
+static PyObject *gcaeget_ohd(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GCAEAD_AEC(me)->ohd)); }
+
+static PyObject *gcaeget_flags(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GCAEAD_AEC(me)->f)); }
+
+static const PyGetSetDef gcaead_pygetset[] = {
+#define GETSETNAME(op, name) gcae##op##_##name
+  GET  (keysz,         "AEC.keysz -> acceptable key sizes")
+  GET  (noncesz,       "AEC.noncesz -> acceptable nonce sizes")
+  GET  (tagsz,         "AEC.tagsz -> acceptable tag sizes")
+  GET  (blksz,         "AEC.blksz -> block size, or zero")
+  GET  (bufsz,         "AEC.bufsz -> amount of data buffered internally")
+  GET  (ohd,           "AEC.ohd -> maximum encryption overhead")
+  GET  (name,          "AEC.name -> name of this kind of AEAD scheme")
+  GET  (flags,         "AEC.flags -> mask of `AEADF_...' flags")
+#undef GETSETNAME
+  { 0 }
+};
+
+static PyObject *gaekmeth_aad(PyObject *me)
+{
+  const gaead_key *k = GAEADKEY_K(me);
+  PyObject *rc = 0;
+
+  if (k->ops->c->f&AEADF_AADNDEP)
+    VALERR("aad must be associated with enc/dec op");
+  rc = gaeadaad_pywrap((PyObject *)GCAEAD_AAD(Py_TYPE(me)),
+                      GAEAD_AAD(k), 0, 0);
+end:
+  return (rc);
+}
+
+static int check_aead_encdec(const gcaead *aec, unsigned *f_out, size_t nsz,
+                            PyObject *hszobj, size_t *hsz_out,
+                            PyObject *mszobj, size_t *msz_out,
+                            PyObject *tszobj, size_t *tsz_out)
+{
+  unsigned f = 0, miss;
+  int rc = -1;
+
+  if (hszobj != Py_None)
+    { f |= AEADF_PCHSZ; if (!convszt(hszobj, hsz_out)) goto end; }
+  if (mszobj != Py_None)
+    { f |= AEADF_PCMSZ; if (!convszt(mszobj, msz_out)) goto end; }
+  if (tszobj != Py_None)
+    { f |= AEADF_PCTSZ; if (!convszt(tszobj, tsz_out)) goto end; }
+  miss = aec->f&~f;
+  if (miss&AEADF_PCHSZ) VALERR("header length precommitment required");
+  if (miss&AEADF_PCMSZ) VALERR("message length precommitment required");
+  if (miss&AEADF_PCTSZ) VALERR("tag length precommitment required");
+  if (keysz(nsz, aec->noncesz) != nsz) VALERR("bad nonce length");
+  if (tszobj != Py_None && keysz(*tsz_out, aec->tagsz) != *tsz_out)
+    VALERR("bad tag length");
+  *f_out = f | aec->f; rc = 0;
+end:
+  return (rc);
+}
+
+static PyObject *gaekmeth_enc(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "nonce", "hsz", "msz", "tsz", 0 };
+  const gaead_key *k = GAEADKEY_K(me);
+  gaead_enc *e;
+  PyObject *rc = 0;
+  struct bin n;
+  PyObject *hszobj = Py_None, *mszobj = Py_None, *tszobj = Py_None;
+  size_t hsz = 0, msz = 0, tsz = 0;
+  unsigned f;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|OOO:enc", KWLIST,
+                                  convbin, &n, &hszobj, &mszobj, &tszobj))
+    goto end;
+  if (check_aead_encdec(k->ops->c, &f, n.sz,
+                       hszobj, &hsz, mszobj, &msz, tszobj, &tsz))
+    goto end;
+  e = GAEAD_ENC(GAEADKEY_K(me), n.p, n.sz, hsz, msz, tsz);
+  if (!e) VALERR("bad aead parameter combination");
+  rc = gaeadenc_pywrap((PyObject *)GCAEAD_ENC(Py_TYPE(me)),
+                      e, f, hsz, msz, tsz);
+end:
+  return (rc);
+}
+
+static PyObject *gaekmeth_dec(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "nonce", "hsz", "csz", "tsz", 0 };
+  const gaead_key *k = GAEADKEY_K(me);
+  gaead_dec *d;
+  PyObject *rc = 0;
+  struct bin n;
+  PyObject *hszobj = Py_None, *cszobj = Py_None, *tszobj = Py_None;
+  size_t hsz = 0, csz = 0, tsz = 0;
+  unsigned f;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|OOO:dec", KWLIST,
+                                  convbin, &n, &hszobj, &cszobj, &tszobj))
+    goto end;
+  if (check_aead_encdec(k->ops->c, &f, n.sz,
+                       hszobj, &hsz, cszobj, &csz, tszobj, &tsz))
+    goto end;
+  d = GAEAD_DEC(GAEADKEY_K(me), n.p, n.sz, hsz, csz, tsz);
+  if (!d) VALERR("bad aead parameter combination");
+  rc = gaeaddec_pywrap((PyObject *)GCAEAD_DEC(Py_TYPE(me)),
+                      d, f, hsz, csz, tsz);
+end:
+  return (rc);
+}
+
+static const PyMethodDef gaeadkey_pymethods[] = {
+#define METHNAME(name) gaekmeth_##name
+  NAMETH(aad,          "KEY.aad() -> AAD")
+  KWMETH(enc,          "KEY.enc(NONCE, [hsz], [msz], [tsz]) -> ENC")
+  KWMETH(dec,          "KEY.dec(NONCE, [hsz], [csz], [tsz]) -> DEC")
+#undef METHNAME
+  { 0 }
+};
+
+static PyObject *gaeadaad_pywrap(PyObject *cobj, gaead_aad *a,
+                                unsigned f, size_t hsz)
+{
+  gaeadaad_pyobj *ga;
+
+  assert(cobj); Py_INCREF(cobj);
+  ga = PyObject_NEW(gaeadaad_pyobj, (PyTypeObject *)cobj);
+  ga->a = a; ga->f = f; ga->hsz = hsz; ga->hlen = 0;
+  return ((PyObject *)ga);
+}
+
+static void gaeadaad_pydealloc(PyObject *me)
+{
+  gaeadaad_pyobj *ga = (gaeadaad_pyobj *)me;
+
+  if (ga->a) GAEAD_DESTROY(ga->a);
+  Py_DECREF(Py_TYPE(me)); FREEOBJ(me);
+}
+
+static int gaea_check(PyObject *me)
+{
+  gaeadaad_pyobj *ga = (gaeadaad_pyobj *)me;
+  int rc = -1;
+
+  if ((ga->f&AEADF_DEAD) || !ga->a) VALERR("aad object no longer active");
+  rc = 0;
+end:
+  return (rc);
+}
+
+static void gaea_invalidate(gaeadaad_pyobj *ga)
+  { if (ga) ga->f |= AEADF_DEAD; }
+
+static void gaea_sever(gaeadaad_pyobj **ga_inout)
+{
+  gaeadaad_pyobj *ga = *ga_inout;
+  if (ga) { ga->f |= AEADF_DEAD; ga->a = 0; Py_DECREF(ga); *ga_inout = 0; }
+}
+
+static PyObject *gaeaget_hsz(PyObject *me, void *hunoz)
+{
+  if (gaea_check(me)) return (0);
+  else if (GAEADAAD_F(me)&AEADF_PCHSZ) return getulong(GAEADAAD_HSZ(me));
+  else RETURN_NONE;
+}
+
+static PyObject *gaeaget_hlen(PyObject *me, void *hunoz)
+  { return (gaea_check(me) ? 0 : getulong(GAEADAAD_HLEN(me))); }
+
+static const PyGetSetDef gaeadaad_pygetset[] = {
+#define GETSETNAME(op, name) gaea##op##_##name
+  GET  (hsz,           "AAD.hsz -> precommitted header length or `None'")
+  GET  (hlen,          "AAD.hlen -> header length so far")
+#undef GETSETNAME
+  { 0 }
+};
+
+static PyObject *gaeameth_copy(PyObject *me)
+{
+  PyObject *rc = 0;
+
+  if (gaea_check(me)) goto end;
+  if (GAEADAAD_F(me)&AEADF_AADNDEP)
+    VALERR("can't duplicate nonce-dependent aad");
+  rc = gaeadaad_pywrap((PyObject *)Py_TYPE(me),
+                      GAEAD_DUP(GAEADAAD_A(me)), 0, 0);
+  GAEADAAD_HLEN(rc) = GAEADAAD_HLEN(me);
+end:
+  return (rc);
+}
+
+static int gaeadaad_hash(PyObject *me, const void *h, size_t hsz)
+{
+  gaeadaad_pyobj *ga = (gaeadaad_pyobj *)me;
+  int rc = -1;
+
+  if (gaea_check(me)) goto end;
+  if ((ga->f&AEADF_NOAAD) && hsz)
+    VALERR("header data not permitted");
+  if ((ga->f&AEADF_PCHSZ) && hsz > ga->hsz - ga->hlen)
+    VALERR("too large for precommitted header length");
+  GAEAD_HASH(ga->a, h, hsz); ga->hlen += hsz;
+  rc = 0;
+end:
+  return (rc);
+}
+
+static PyObject *gaeameth_hash(PyObject *me, PyObject *arg)
+{
+  struct bin h;
+
+  if (!PyArg_ParseTuple(arg, "O&:hash", convbin, &h)) return (0);
+  if (gaeadaad_hash(me, h.p, h.sz)) return (0);
+  RETURN_ME;
+}
+
+#define GAEAMETH_HASHU_(n, W, w)                                       \
+  static PyObject *gaeameth_hashu##w(PyObject *me, PyObject *arg)      \
+  {                                                                    \
+    uint##n x; octet b[SZ_##W];                                                \
+    if (!PyArg_ParseTuple(arg, "O&:hashu" #w, convu##n, &x)) return (0); \
+    STORE##W(b, x); if (gaeadaad_hash(me, b, sizeof(b))) return (0);   \
+    RETURN_ME;                                                         \
+  }
+DOUINTCONV(GAEAMETH_HASHU_)
+
+#define GAEAMETH_HASHBUF_(n, W, w)                                     \
+  static PyObject *gaeameth_hashbuf##w(PyObject *me, PyObject *arg)    \
+  {                                                                    \
+    struct bin in; octet b[SZ_##W];                                    \
+    if (!PyArg_ParseTuple(arg, "O&:hashbuf" #w, convbin, &in)) goto end; \
+    if (in.sz > MASK##n) VALERR("too large");                          \
+    STORE##W(b, in.sz); if (gaeadaad_hash(me, b, sizeof(b))) goto end; \
+    if (gaeadaad_hash(me, in.p, in.sz)) goto end;                      \
+    RETURN_ME;                                                         \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+DOUINTCONV(GAEAMETH_HASHBUF_)
+
+static PyObject *gaeameth_hashstrz(PyObject *me, PyObject *arg)
+{
+  char *p;
+  if (!PyArg_ParseTuple(arg, "s:hashstrz", &p)) return (0);
+  if (gaeadaad_hash(me, p, strlen(p) + 1)) return (0);
+  RETURN_ME;
+}
+
+static const PyMethodDef gaeadaad_pymethods[] = {
+#define METHNAME(name) gaeameth_##name
+  NAMETH(copy,         "AAD.copy() -> AAD'")
+  METH (hash,          "AAD.hash(H)")
+#define METHU_(n, W, w) METH(hashu##w, "AAD.hashu" #w "(WORD)")
+  DOUINTCONV(METHU_)
+#undef METHU_
+#define METHBUF_(n, W, w) METH(hashbuf##w, "AAD.hashbuf" #w "(BYTES)")
+  DOUINTCONV(METHBUF_)
+#undef METHBUF_
+  METH (hashstrz,      "AAD.hashstrz(STRING)")
+#undef METHNAME
+  { 0 }
+};
+
+static PyObject *gaeadenc_pywrap(PyObject *cobj, gaead_enc *e, unsigned f,
+                                size_t hsz, size_t msz, size_t tsz)
+{
+  gaeadenc_pyobj *ge;
+
+  assert(cobj); Py_INCREF(cobj);
+  ge = PyObject_NEW(gaeadenc_pyobj, (PyTypeObject *)cobj);
+  ge->e = e; ge->f = f; ge->hsz = hsz; ge->msz = msz; ge->tsz = tsz;
+  ge->aad = 0; ge->mlen = 0;
+  return ((PyObject *)ge);
+}
+
+static void gaeadenc_pydealloc(PyObject *me)
+{
+  gaeadenc_pyobj *ge = (gaeadenc_pyobj *)me;
+
+  gaea_sever(&ge->aad); GAEAD_DESTROY(ge->e);
+  Py_DECREF(Py_TYPE(me)); FREEOBJ(me);
+}
+
+static PyObject *gaeeget_hsz(PyObject *me, void *hunoz)
+{
+  if (GAEADENC_F(me)&AEADF_PCHSZ) return getulong(GAEADENC_HSZ(me));
+  else RETURN_NONE;
+}
+
+static PyObject *gaeeget_msz(PyObject *me, void *hunoz)
+{
+  if (GAEADENC_F(me)&AEADF_PCMSZ) return getulong(GAEADENC_MSZ(me));
+  else RETURN_NONE;
+}
+
+static PyObject *gaeeget_tsz(PyObject *me, void *hunoz)
+{
+  if (GAEADENC_F(me)&AEADF_PCTSZ) return getulong(GAEADENC_TSZ(me));
+  else RETURN_NONE;
+}
+
+static PyObject *gaeeget_mlen(PyObject *me, void *hunoz)
+  { return getulong(GAEADENC_MLEN(me)); }
+
+static const PyGetSetDef gaeadenc_pygetset[] = {
+#define GETSETNAME(op, name) gaee##op##_##name
+  GET  (hsz,           "ENC.hsz -> precommitted header length or `None'")
+  GET  (msz,           "ENC.msz -> precommitted message length or `None'")
+  GET  (tsz,           "ENC.tsz -> precommitted tag length or `None'")
+  GET  (mlen,          "ENC.mlen -> message length so far")
+#undef GETSETNAME
+  { 0 }
+};
+
+static PyObject *gaeemeth_aad(PyObject *me)
+{
+  gaeadenc_pyobj *ge = (gaeadenc_pyobj *)me;
+  PyObject *rc = 0;
+
+  if (!(ge->f&AEADF_AADNDEP))
+    rc = gaeadaad_pywrap((PyObject *)GCAEADENC_KEY(Py_TYPE(ge))->aad,
+                        GAEAD_AAD(ge->e), 0, 0);
+  else {
+    if ((ge->f&AEADF_AADFIRST) && ge->mlen)
+      VALERR("too late for aad");
+    if (!ge->aad)
+      ge->aad = (gaeadaad_pyobj *)
+       gaeadaad_pywrap((PyObject *)GCAEADENC_KEY(Py_TYPE(ge))->aad,
+                       GAEAD_AAD(ge->e), ge->f&(AEADF_PCHSZ | AEADF_NOAAD),
+                       ge->hsz);
+    Py_INCREF(ge->aad);
+    rc = (PyObject *)ge->aad;
+  }
+end:
+  return (rc);
+}
+
+static PyObject *gaeemeth_reinit(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "nonce", "hsz", "msz", "tsz", 0 };
+  gaeadenc_pyobj *ge = (gaeadenc_pyobj *)me;
+  struct bin n;
+  PyObject *hszobj = Py_None, *mszobj = Py_None, *tszobj = Py_None;
+  size_t hsz = 0, msz = 0, tsz = 0;
+  unsigned f;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|OOO:enc", KWLIST,
+                                  convbin, &n, &hszobj, &mszobj, &tszobj))
+    goto end;
+  if (check_aead_encdec(ge->e->ops->c, &f, n.sz,
+                       hszobj, &hsz, mszobj, &msz, tszobj, &tsz))
+    goto end;
+  if (GAEAD_REINIT(ge->e, n.p, n.sz, hsz, msz, tsz))
+    VALERR("bad aead parameter combination");
+  gaea_sever(&ge->aad);
+  ge->f = f; ge->hsz = hsz; ge->msz = msz; ge->tsz = tsz;
+end:
+  return (0);
+}
+
+static PyObject *gaeemeth_encrypt(PyObject *me, PyObject *arg)
+{
+  gaeadenc_pyobj *ge = (gaeadenc_pyobj *)me;
+  struct bin m;
+  char *c = 0; size_t csz; buf b;
+  int err;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:encrypt", convbin, &m)) goto end;
+  if (ge->f&AEADF_AADFIRST) {
+    if ((ge->f&AEADF_PCHSZ) && (ge->aad ? ge->aad->hlen : 0) != ge->hsz)
+      VALERR("header doesn't match precommitted length");
+    gaea_invalidate(ge->aad);
+  }
+  if ((ge->f&AEADF_PCMSZ) && m.sz > ge->msz - ge->mlen)
+    VALERR("too large for precommitted message length");
+  csz = m.sz + ge->e->ops->c->bufsz; c = xmalloc(csz); buf_init(&b, c, csz);
+  err = GAEAD_ENCRYPT(ge->e, m.p, m.sz, &b); assert(!err); (void)err;
+  buf_flip(&b); rc = bytestring_pywrapbuf(&b); ge->mlen += m.sz;
+end:
+  xfree(c);
+  return (rc);
+}
+
+static PyObject *gaeemeth_done(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "tsz", "aad", 0 };
+  gaeadenc_pyobj *ge = (gaeadenc_pyobj *)me;
+  PyObject *aad = Py_None;
+  char *c = 0; size_t csz; buf b;
+  PyObject *tszobj = Py_None; PyObject *tag; size_t tsz;
+  int err;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OO:done", KWLIST,
+                                  &tszobj, &aad))
+    goto end;
+  if (tszobj != Py_None && !convszt(tszobj, &tsz)) goto end;
+  if (aad != Py_None &&
+      !PyObject_TypeCheck(aad,
+                         (PyTypeObject *)GCAEADENC_KEY(Py_TYPE(me))->aad))
+    TYERR("wanted aad");
+  if ((ge->f&AEADF_AADNDEP) && aad != Py_None && aad != (PyObject *)ge->aad)
+    VALERR("mismatched aad");
+  if ((ge->f&AEADF_PCHSZ) &&
+      (aad == Py_None ? 0 : GAEADAAD_HLEN(aad)) != ge->hsz)
+    VALERR("header doesn't match precommitted length");
+  if ((ge->f&AEADF_PCMSZ) && ge->mlen != ge->msz)
+    VALERR("message doesn't match precommitted length");
+  if (tszobj == Py_None) {
+    if (ge->f&AEADF_PCTSZ) tsz = ge->tsz;
+    else tsz = keysz(0, ge->e->ops->c->tagsz);
+  } else {
+    if ((ge->f&AEADF_PCTSZ) && tsz != ge->tsz)
+      VALERR("tag length doesn't match precommitted value");
+    if (keysz(tsz, ge->e->ops->c->tagsz) != tsz) VALERR("bad tag length");
+  }
+  csz = ge->e->ops->c->bufsz; c = xmalloc(csz); buf_init(&b, c, csz);
+  tag = bytestring_pywrap(0, tsz);
+  err = GAEAD_DONE(ge->e, aad == Py_None ? 0 : GAEADAAD_A(aad), &b,
+                  BIN_PTR(tag), tsz);
+  assert(!err); (void)err;
+  buf_flip(&b); rc = Py_BuildValue("NN", bytestring_pywrapbuf(&b), tag);
+end:
+  xfree(c);
+  return (rc);
+}
+
+static const PyMethodDef gaeadenc_pymethods[] = {
+#define METHNAME(name) gaeemeth_##name
+  NAMETH(aad,          "ENC.aad() -> AAD")
+  KWMETH(reinit,       "ENC.reinit(NONCE, [hsz], [msz], [tsz])")
+  METH (encrypt,       "ENC.encrypt(MSG) -> CT")
+  KWMETH(done,         "ENC.done([tsz], [aad]) -> CT, TAG")
+#undef METHNAME
+  { 0 }
+};
+
+static PyObject *gaeaddec_pywrap(PyObject *cobj, gaead_dec *d, unsigned f,
+                                size_t hsz, size_t csz, size_t tsz)
+{
+  gaeaddec_pyobj *gd;
+  assert(cobj); Py_INCREF(cobj);
+  gd = PyObject_NEW(gaeaddec_pyobj, (PyTypeObject *)cobj);
+  gd->d = d; gd->f = f; gd->hsz = hsz; gd->csz = csz; gd->tsz = tsz;
+  gd->aad = 0; gd->clen = 0;
+  return ((PyObject *)gd);
+}
+
+static void gaeaddec_pydealloc(PyObject *me)
+{
+  gaeaddec_pyobj *gd = (gaeaddec_pyobj *)me;
+
+  gaea_sever(&gd->aad); GAEAD_DESTROY(GAEADDEC_D(me));
+  Py_DECREF(Py_TYPE(me)); FREEOBJ(me);
+}
+
+static PyObject *gaedget_hsz(PyObject *me, void *hunoz)
+{
+  if (GAEADDEC_F(me)&AEADF_PCHSZ) return getulong(GAEADDEC_HSZ(me));
+  else RETURN_NONE;
+}
+
+static PyObject *gaedget_csz(PyObject *me, void *hunoz)
+{
+  if (GAEADDEC_F(me)&AEADF_PCMSZ) return getulong(GAEADDEC_CSZ(me));
+  else RETURN_NONE;
+}
+
+static PyObject *gaedget_tsz(PyObject *me, void *hunoz)
+{
+  if (GAEADDEC_F(me)&AEADF_PCTSZ) return getulong(GAEADDEC_TSZ(me));
+  else RETURN_NONE;
+}
+
+static PyObject *gaedget_clen(PyObject *me, void *hunoz)
+  { return getulong(GAEADDEC_CLEN(me)); }
+
+static const PyGetSetDef gaeaddec_pygetset[] = {
+#define GETSETNAME(op, name) gaed##op##_##name
+  GET  (hsz,           "DEC.hsz -> precommitted header length or `None'")
+  GET  (csz,          "DEC.csz -> precommitted ciphertext length or `None'")
+  GET  (tsz,           "DEC.tsz -> precommitted tag length or `None'")
+  GET  (clen,          "DEC.clen -> ciphertext length so far")
+#undef GETSETNAME
+  { 0 }
+};
+
+static PyObject *gaedmeth_aad(PyObject *me)
+{
+  gaeaddec_pyobj *gd = (gaeaddec_pyobj *)me;
+
+  if (!(gd->f&AEADF_AADNDEP))
+    return (gaeadaad_pywrap((PyObject *)GCAEADDEC_KEY(Py_TYPE(gd))->aad,
+                           GAEAD_AAD(gd->d), 0, 0));
+  else {
+    if (!gd->aad)
+      gd->aad = (gaeadaad_pyobj *)
+       gaeadaad_pywrap((PyObject *)GCAEADENC_KEY(Py_TYPE(gd))->aad,
+                       GAEAD_AAD(gd->d), gd->f&(AEADF_PCHSZ | AEADF_NOAAD),
+                       gd->hsz);
+    Py_INCREF(gd->aad);
+    return ((PyObject *)gd->aad);
+  }
+}
+
+static PyObject *gaedmeth_reinit(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "nonce", "hsz", "csz", "tsz", 0 };
+  gaeaddec_pyobj *gd = (gaeaddec_pyobj *)me;
+  struct bin n;
+  PyObject *hszobj = Py_None, *cszobj = Py_None, *tszobj = Py_None;
+  size_t hsz = 0, csz = 0, tsz = 0;
+  unsigned f;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|OOO:enc", KWLIST,
+                                  convbin, &n, &hszobj, &cszobj, &tszobj))
+    goto end;
+  if (check_aead_encdec(gd->d->ops->c, &f, n.sz,
+                       hszobj, &hsz, cszobj, &csz, tszobj, &tsz))
+    goto end;
+  if (GAEAD_REINIT(gd->d, n.p, n.sz, hsz, csz, tsz))
+    VALERR("bad aead parameter combination");
+  gaea_sever(&gd->aad);
+  gd->f = f; gd->hsz = hsz; gd->csz = csz; gd->tsz = tsz;
+end:
+  return (0);
+}
+
+static PyObject *gaedmeth_decrypt(PyObject *me, PyObject *arg)
+{
+  gaeaddec_pyobj *gd = (gaeaddec_pyobj *)me;
+  struct bin c;
+  char *m = 0; size_t msz; buf b;
+  int err;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:decrypt", convbin, &c)) goto end;
+  if (gd->f&AEADF_AADFIRST) {
+    if ((gd->f&AEADF_PCHSZ) && (gd->aad ? gd->aad->hlen : 0) != gd->hsz)
+      VALERR("header doesn't match precommitted length");
+    gaea_invalidate(gd->aad);
+  }
+  if ((gd->f&AEADF_PCMSZ) && c.sz > gd->csz - gd->clen)
+    VALERR("too large for precommitted message length");
+  msz = c.sz + gd->d->ops->c->bufsz; m = xmalloc(msz); buf_init(&b, m, msz);
+  err = GAEAD_DECRYPT(gd->d, c.p, c.sz, &b); assert(!err); (void)err;
+  buf_flip(&b); rc = bytestring_pywrapbuf(&b); gd->clen += c.sz;
+end:
+  xfree(m);
+  return (rc);
+}
+
+static PyObject *gaedmeth_done(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "tag", "aad", 0 };
+  gaeaddec_pyobj *gd = (gaeaddec_pyobj *)me;
+  PyObject *aad = Py_None;
+  struct bin t;
+  char *m = 0; size_t msz; buf b;
+  int err;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O:done", KWLIST,
+                                  convbin, &t, &aad))
+    goto end;
+  if (aad != Py_None &&
+      !PyObject_TypeCheck(aad,
+                         (PyTypeObject *)GCAEADENC_KEY(Py_TYPE(me))->aad))
+    TYERR("wanted aad");
+  if ((gd->f&AEADF_AADNDEP) && aad != Py_None && aad != (PyObject *)gd->aad)
+    VALERR("mismatched aad");
+  if ((gd->f&AEADF_PCHSZ) &&
+      (aad == Py_None ? 0 : GAEADAAD_HLEN(aad)) != gd->hsz)
+    VALERR("header doesn't match precommitted length");
+  if ((gd->f&AEADF_PCMSZ) && gd->clen != gd->csz)
+    VALERR("message doesn't match precommitted length");
+  if ((gd->f&AEADF_PCTSZ) && t.sz != gd->tsz)
+    VALERR("tag length doesn't match precommitted value");
+  if (keysz(t.sz, gd->d->ops->c->tagsz) != t.sz) VALERR("bad tag length");
+  msz = gd->d->ops->c->bufsz; m = xmalloc(msz); buf_init(&b, m, msz);
+  err = GAEAD_DONE(gd->d, aad == Py_None ? 0 : GAEADAAD_A(aad),
+                  &b, t.p, t.sz);
+  assert(err >= 0);
+  if (!err) VALERR("decryption failed");
+  buf_flip(&b); rc = bytestring_pywrapbuf(&b);
+end:
+  xfree(m);
+  return (rc);
+}
+
+static const PyMethodDef gaeaddec_pymethods[] = {
+#define METHNAME(name) gaedmeth_##name
+  NAMETH(aad,          "DEC.aad() -> AAD")
+  KWMETH(reinit,       "DEC.reinit(NONCE, [hsz], [csz], [tsz])")
+  METH (decrypt,       "DEC.decrypt(CT) -> MSG")
+  KWMETH(done,         "DEC.done(TAG, [aad]) -> MSG | None")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject gcaead_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GCAEAD",                            /* @tp_name@ */
+  sizeof(gcaead_pyobj),                        /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Authenticated encryption (key) metaclass.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gcaead),                    /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject gaeadkey_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GAEKey",                            /* @tp_name@ */
+  sizeof(gaeadkey_pyobj),              /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  gaeadkey_pydealloc,                  /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Authenticated encryption key.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gaeadkey),                 /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject gcaeadaad_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GAEAADClass",                       /* @tp_name@ */
+  sizeof(gcaeadaad_pyobj),             /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Authenticated encryption additional-data hash metaclass.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject gaeadaad_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GAEAAD",                            /* @tp_name@ */
+  sizeof(gaeadaad_pyobj),              /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  gaeadaad_pydealloc,                  /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Authenticated encryption AAD hash.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gaeadaad),                 /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gaeadaad),                  /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject gcaeadenc_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GAEEncClass",                       /* @tp_name@ */
+  sizeof(gcaeadenc_pyobj),             /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Authenticated encryption operation metaclass.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject gaeadenc_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GAEEnc",                            /* @tp_name@ */
+  sizeof(gaeadenc_pyobj),              /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  gaeadenc_pydealloc,                  /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Authenticated encryption operation.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gaeadenc),                 /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gaeadenc),                  /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject gcaeaddec_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GAEDecClass",                       /* @tp_name@ */
+  sizeof(gcaeaddec_pyobj),             /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Authenticated decryption operation metaclass.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject gaeaddec_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GAEDec",                            /* @tp_name@ */
+  sizeof(gaeaddec_pyobj),              /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  gaeaddec_pydealloc,                  /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Authenticated decryption operation.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gaeaddec),                 /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gaeaddec),                  /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Hash functions ----------------------------------------------------*/
+
+PyTypeObject *gchash_pytype;
+static PyTypeObject *ghash_pytype;
+PyObject *sha_pyobj, *has160_pyobj;
+
+typedef struct ghash_pyobj {
+  PyObject_HEAD
+  ghash *h;
+} ghash_pyobj;
+
+#define GHASH_PYCHECK(o) PyObject_TypeCheck((o), ghash_pytype)
+#define GHASH_H(o) (((ghash_pyobj *)(o))->h)
+
+CONVFUNC(gchash, gchash *, GCHASH_CH)
+CONVFUNC(ghash, ghash *, GHASH_H)
+
+static PyObject *ghash_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST))
+    goto end;
+  return (ghash_pywrap((PyObject *)ty, GH_INIT(GCHASH_CH(ty))));
+end:
+  return (0);
+}
+
+static PyObject *gchash_pywrap(gchash *ch)
+{
+  gchash_pyobj *g = newtype(gchash_pytype, 0, ch->name);
+  g->ch = ch;
+  g->ty.ht_type.tp_basicsize = sizeof(ghash_pyobj);
+  g->ty.ht_type.tp_base = ghash_pytype;
+  Py_INCREF(ghash_pytype);
+  g->ty.ht_type.tp_flags = (Py_TPFLAGS_DEFAULT |
+                           Py_TPFLAGS_BASETYPE |
+                           Py_TPFLAGS_HEAPTYPE);
+  g->ty.ht_type.tp_alloc = PyType_GenericAlloc;
+  g->ty.ht_type.tp_free = 0;
+  g->ty.ht_type.tp_new = ghash_pynew;
+  typeready(&g->ty.ht_type);
+  return ((PyObject *)g);
+}
+
+PyObject *ghash_pywrap(PyObject *cobj, ghash *h)
+{
+  ghash_pyobj *g;
+  if (!cobj) cobj = gchash_pywrap((/*unconst*/ gchash *)GH_CLASS(h));
+  else Py_INCREF(cobj);
+  g = PyObject_NEW(ghash_pyobj, (PyTypeObject *)cobj);
+  g->h = h;
+  return ((PyObject *)g);
+}
+
+static void ghash_pydealloc(PyObject *me)
+{
+  GH_DESTROY(GHASH_H(me));
+  Py_DECREF(Py_TYPE(me));
+  FREEOBJ(me);
+}
+
+static PyObject *gchget_name(PyObject *me, void *hunoz)
+  { return (TEXT_FROMSTR(GCHASH_CH(me)->name)); }
+
+static PyObject *gchget_hashsz(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GCHASH_CH(me)->hashsz)); }
+
+static PyObject *gchget_bufsz(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GCHASH_CH(me)->bufsz)); }
+
+static PyObject *ghmeth_copy(PyObject *me)
+  { return (ghash_pywrap((PyObject *)Py_TYPE(me), GH_COPY(GHASH_H(me)))); }
+
+static PyObject *ghmeth_hash(PyObject *me, PyObject *arg)
+{
+  struct bin m;
+  if (!PyArg_ParseTuple(arg, "O&:hash", convbin, &m)) return (0);
+  GH_HASH(GHASH_H(me), m.p, m.sz);
+  RETURN_ME;
+}
+
+#define GHMETH_HASHU_(n, W, w)                                         \
+  static PyObject *ghmeth_hashu##w(PyObject *me, PyObject *arg)                \
+  {                                                                    \
+    uint##n x;                                                         \
+    if (!PyArg_ParseTuple(arg, "O&:hashu" #w, convu##n, &x)) return (0); \
+    GH_HASHU##W(GHASH_H(me), x);                                       \
+    RETURN_ME;                                                         \
+  }
+DOUINTCONV(GHMETH_HASHU_)
+
+#define GHMETH_HASHBUF_(n, W, w)                                       \
+  static PyObject *ghmeth_hashbuf##w(PyObject *me, PyObject *arg)      \
+  {                                                                    \
+    struct bin in;                                                     \
+    if (!PyArg_ParseTuple(arg, "O&:hashbuf" #w, convbin, &in)) goto end; \
+    if (in.sz > MASK##n) VALERR("too large");                          \
+    GH_HASHBUF##W(GHASH_H(me), in.p, in.sz);                           \
+    RETURN_ME;                                                         \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+DOUINTCONV(GHMETH_HASHBUF_)
+
+static PyObject *ghmeth_hashstrz(PyObject *me, PyObject *arg)
+{
+  char *p;
+  if (!PyArg_ParseTuple(arg, "s:hashstrz", &p)) return (0);
+  GH_HASHSTRZ(GHASH_H(me), p);
+  RETURN_ME;
+}
+
+static PyObject *ghmeth_done(PyObject *me)
+{
+  ghash *g;
+  PyObject *rc;
+  g = GH_COPY(GHASH_H(me));
+  rc = bytestring_pywrap(0, g->ops->c->hashsz);
+  GH_DONE(g, BIN_PTR(rc));
+  GH_DESTROY(g);
+  return (rc);
+}
+
+static const PyGetSetDef gchash_pygetset[] = {
+#define GETSETNAME(op, name) gch##op##_##name
+  GET  (bufsz,         "CH.bufsz -> hash buffer size, or zero")
+  GET  (hashsz,        "CH.hashsz -> hash output size")
+  GET  (name,          "CH.name -> name of this kind of hash")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef ghash_pymethods[] = {
+#define METHNAME(name) ghmeth_##name
+  NAMETH(copy,         "H.copy() -> HH")
+  METH (hash,          "H.hash(M)")
+#define METHU_(n, W, w) METH(hashu##w, "H.hashu" #w "(WORD)")
+  DOUINTCONV(METHU_)
+#undef METHU_
+#define METHBUF_(n, W, w) METH(hashbuf##w, "H.hashbuf" #w "(BYTES)")
+  DOUINTCONV(METHBUF_)
+#undef METHBUF_
+  METH (hashstrz,      "H.hashstrz(STRING)")
+  NAMETH(done,         "H.done() -> HASH")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject gchash_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GCHash",                            /* @tp_name@ */
+  sizeof(gchash_pyobj),                        /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Hash function metaclass.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gchash),                    /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject ghash_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GHash",                             /* @tp_name@ */
+  sizeof(ghash_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  ghash_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Hash function, abstract base class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(ghash),                    /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Message authentication --------------------------------------------*/
+
+static PyTypeObject *gcmac_pytype, *gmac_pytype, *gmhash_pytype;
+
+typedef struct gcmac_pyobj {
+  PyHeapTypeObject ty;
+  gcmac *cm;
+} gcmac_pyobj;
+
+#define GCMAC_PYCHECK(o) PyObject_TypeCheck((o), gcmac_pytype)
+#define GCMAC_CM(o) (((gcmac_pyobj *)(o))->cm)
+#define GCMAC_F(o) (((gcmac_pyobj *)(o))->f)
+CONVFUNC(gcmac, gcmac *, GCMAC_CM)
+static PyObject *gmac_pywrap(PyObject *, gmac *);
+
+typedef struct gmac_pyobj {
+  PyHeapTypeObject ty;
+  gmac *m;
+} gmac_pyobj;
+
+extern PyTypeObject *gmac_pytype;
+#define GMAC_PYCHECK(o) PyObject_TypeCheck((o), gmac_pytype)
+#define GMAC_M(o) (((gmac_pyobj *)(o))->m)
+#define GMAC_F(o) (((gmac_pyobj *)(o))->f)
+extern PyObject *gmac_pywrap(PyObject *, gmac *);
+extern int convgmac(PyObject *, void *);
+
+static PyObject *gmac_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "k", 0 };
+  struct bin k;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convbin, &k))
+    goto end;
+  if (keysz(k.sz, GCMAC_CM(ty)->keysz) != k.sz) VALERR("bad key length");
+  return (gmac_pywrap((PyObject *)ty,
+                     GM_KEY(GCMAC_CM(ty), k.p, k.sz)));
+end:
+  return (0);
+}
+
+static PyObject *gmhash_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { 0 };
+  ghash_pyobj *g;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST)) return (0);
+  g = PyObject_NEW(ghash_pyobj, ty);
+  g->h = GM_INIT(GMAC_M(ty));
+  Py_INCREF(ty);
+  return ((PyObject *)g);
+}
+
+static PyObject *gcmac_pywrap(gcmac *cm)
+{
+  gcmac_pyobj *g = newtype(gcmac_pytype, 0, cm->name);
+  g->cm = cm;
+  g->ty.ht_type.tp_basicsize = sizeof(gmac_pyobj);
+  g->ty.ht_type.tp_base = gmac_pytype;
+  Py_INCREF(gmac_pytype);
+  g->ty.ht_type.tp_flags = (Py_TPFLAGS_DEFAULT |
+                           Py_TPFLAGS_BASETYPE |
+                           Py_TPFLAGS_HEAPTYPE);
+  g->ty.ht_type.tp_alloc = PyType_GenericAlloc;
+  g->ty.ht_type.tp_free = 0;
+  g->ty.ht_type.tp_new = gmac_pynew;
+  typeready(&g->ty.ht_type);
+  return ((PyObject *)g);
+}
+
+static PyObject *gmac_pywrap(PyObject *cobj, gmac *m)
+{
+  gmac_pyobj *g;
+  if (!cobj) cobj = gcmac_pywrap((/*unconst*/ gcmac *)GM_CLASS(m));
+  else Py_INCREF(cobj);
+  g = newtype((PyTypeObject *)cobj, 0, 0);
+  g->ty.ht_type.tp_basicsize = sizeof(ghash_pyobj);
+  g->ty.ht_name = TEXT_FORMAT("%s(keyed)", m->ops->c->name);
+  g->ty.ht_type.tp_name = TEXT_PTR(g->ty.ht_name);
+  g->ty.ht_type.tp_base = gmhash_pytype;
+  Py_INCREF(gmac_pytype);
+  g->ty.ht_type.tp_flags = (Py_TPFLAGS_DEFAULT |
+                           Py_TPFLAGS_BASETYPE |
+                           Py_TPFLAGS_HEAPTYPE);
+  g->ty.ht_type.tp_alloc = PyType_GenericAlloc;
+  g->ty.ht_type.tp_free = 0;
+  g->ty.ht_type.tp_new = gmhash_pynew;
+  typeready(&g->ty.ht_type);
+  g->m = m;
+  return ((PyObject *)g);
+}
+
+static void gmac_pydealloc(PyObject *me)
+{
+  GM_DESTROY(GMAC_M(me));
+  Py_DECREF(Py_TYPE(me));
+  PyType_Type.tp_dealloc(me);
+}
+
+static PyObject *gcmget_name(PyObject *me, void *hunoz)
+  { return (TEXT_FROMSTR(GCMAC_CM(me)->name)); }
+
+static PyObject *gcmget_keysz(PyObject *me, void *hunoz)
+  { return (keysz_pywrap(GCMAC_CM(me)->keysz)); }
+
+static PyObject *gcmget_tagsz(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GCMAC_CM(me)->hashsz)); }
+
+static const PyGetSetDef gcmac_pygetset[] = {
+#define GETSETNAME(op, name) gcm##op##_##name
+  GET  (keysz,         "CM.keysz -> acceptable key sizes")
+  GET  (tagsz,         "CM.tagsz -> MAC output size")
+  GET  (name,          "CM.name -> name of this kind of MAC")
+#undef GETSETNAME
+  { 0 }
+};
+
+static PyObject *gmget_name(PyObject *me, void *hunoz)
+  { return (TEXT_FROMSTR(GMAC_M(me)->ops->c->name)); }
+
+static PyObject *gmget_hashsz(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GMAC_M(me)->ops->c->hashsz)); }
+#define gmget_tagsz gmget_hashsz
+
+static const PyGetSetDef gmac_pygetset[] = {
+#define GETSETNAME(op, name) gm##op##_##name
+  GET  (hashsz,        "M.hashsz -> MAC output size")
+  GET  (tagsz,         "M.tagsz -> MAC output size")
+  GET  (name,          "M.name -> name of this kind of MAC")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject gcmac_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GCMAC",                             /* @tp_name@ */
+  sizeof(gchash_pyobj),                        /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Message authentication code metametaclass.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gcmac),                     /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject gmac_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GMAC",                              /* @tp_name@ */
+  sizeof(gmac_pyobj),                  /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  gmac_pydealloc,                      /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Message authentication code metaclass, abstract base class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gmac),                      /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject gmhash_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GMACHash",                          /* @tp_name@ */
+  sizeof(ghash_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  ghash_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Message authentication code, abstract base class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Special snowflake for Poly1305 ------------------------------------*/
+
+PyTypeObject *poly1305cls_pytype, *poly1305key_pytype, *poly1305hash_pytype;
+
+typedef struct poly1305key_pyobj {
+  PyHeapTypeObject ty;
+  poly1305_key k;
+} poly1305key_pyobj;
+
+typedef struct poly1305hash_pyobj {
+  PyObject_HEAD
+  unsigned f;
+#define f_mask 1u
+  poly1305_ctx ctx;
+} poly1305hash_pyobj;
+
+#define P1305_F(o) (((poly1305hash_pyobj *)(o))->f)
+#define P1305_CTX(o) (&((poly1305hash_pyobj *)(o))->ctx)
+CONVFUNC(poly1305hash, poly1305_ctx *, P1305_CTX)
+
+static PyObject *poly1305hash_pynew(PyTypeObject *ty,
+                                   PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "mask", 0 };
+  poly1305key_pyobj *pk = (poly1305key_pyobj *)ty;
+  poly1305hash_pyobj *ph;
+  struct bin m = { 0, 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", KWLIST, convbin, &m))
+    return (0);
+  if (m.p && m.sz != POLY1305_MASKSZ) VALERR("bad mask length");
+  ph = PyObject_NEW(poly1305hash_pyobj, ty);
+  ph->f = 0;
+  if (m.p) ph->f |= f_mask;
+  poly1305_macinit(&ph->ctx, &pk->k, m.p);
+  Py_INCREF(ty);
+  return ((PyObject *)ph);
+end:
+  return (0);
+}
+
+static PyObject *poly1305key_pynew(PyTypeObject *ty,
+                                  PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "k", 0 };
+  poly1305key_pyobj *pk;
+  struct bin k;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convbin, &k))
+    goto end;
+  if (keysz(k.sz, poly1305_keysz) != k.sz) VALERR("bad key length");
+
+  pk = newtype(ty, 0, 0);
+  pk->ty.ht_name = TEXT_FROMSTR("poly1305(keyed)");
+  pk->ty.ht_type.tp_basicsize = sizeof(poly1305hash_pyobj);
+  pk->ty.ht_type.tp_name = TEXT_PTR(pk->ty.ht_name);
+  pk->ty.ht_type.tp_base = poly1305hash_pytype;
+  Py_INCREF(poly1305key_pytype);
+  pk->ty.ht_type.tp_flags = (Py_TPFLAGS_DEFAULT |
+                            Py_TPFLAGS_BASETYPE |
+                            Py_TPFLAGS_HEAPTYPE);
+  pk->ty.ht_type.tp_alloc = PyType_GenericAlloc;
+  pk->ty.ht_type.tp_free = 0;
+  pk->ty.ht_type.tp_new = poly1305hash_pynew;
+  typeready(&pk->ty.ht_type);
+
+  poly1305_keyinit(&pk->k, k.p, k.sz);
+  return ((PyObject *)pk);
+
+end:
+  return (0);
+}
+
+static PyObject *poly1305clsget_name(PyObject *me, void *hunoz)
+  { return (TEXT_FROMSTR("poly1305")); }
+
+static PyObject *poly1305clsget_keysz(PyObject *me, void *hunoz)
+  { return (keysz_pywrap(poly1305_keysz)); }
+
+static PyObject *poly1305clsget_masksz(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(POLY1305_MASKSZ)); }
+
+static PyObject *poly1305clsget_tagsz(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(POLY1305_TAGSZ)); }
+
+static PyObject *polymeth_copy(PyObject *me)
+{
+  poly1305hash_pyobj *ph;
+  ph = PyObject_NEW(poly1305hash_pyobj, Py_TYPE(me));
+  poly1305_copy(&ph->ctx, P1305_CTX(me));
+  Py_INCREF(Py_TYPE(me));
+  return ((PyObject *)ph);
+}
+
+static PyObject *polymeth_hash(PyObject *me, PyObject *arg)
+{
+  struct bin m;
+  if (!PyArg_ParseTuple(arg, "O&:hash", convbin, &m)) return (0);
+  poly1305_hash(P1305_CTX(me), m.p, m.sz);
+  RETURN_ME;
+}
+
+#define POLYMETH_HASHU_(n, W, w)                                       \
+  static PyObject *polymeth_hashu##w(PyObject *me, PyObject *arg)      \
+  {                                                                    \
+    uint##n x;                                                         \
+    octet b[SZ_##W];                                                   \
+    if (!PyArg_ParseTuple(arg, "O&:hashu" #w, convu##n, &x)) return (0); \
+    STORE##W(b, x); poly1305_hash(P1305_CTX(me), b, sizeof(b));                \
+    RETURN_ME;                                                         \
+  }
+DOUINTCONV(POLYMETH_HASHU_)
+
+#define POLYMETH_HASHBUF_(n, W, w)                                     \
+  static PyObject *polymeth_hashbuf##w(PyObject *me, PyObject *arg)    \
+  {                                                                    \
+    struct bin in;                                                     \
+    octet b[SZ_##W];                                                   \
+    if (!PyArg_ParseTuple(arg, "O&:hashbuf" #w, convbin, &in)) goto end; \
+    if (in.sz > MASK##n) VALERR("too large");                          \
+    STORE##W(b, in.sz); poly1305_hash(P1305_CTX(me), b, sizeof(b));    \
+    poly1305_hash(P1305_CTX(me), in.p, in.sz);                         \
+    RETURN_ME;                                                         \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+DOUINTCONV(POLYMETH_HASHBUF_)
+
+static PyObject *polymeth_hashstrz(PyObject *me, PyObject *arg)
+{
+  char *p;
+  if (!PyArg_ParseTuple(arg, "s:hashstrz", &p)) return (0);
+  poly1305_hash(P1305_CTX(me), p, strlen(p) + 1);
+  RETURN_ME;
+}
+
+static PyObject *polymeth_flush(PyObject *me)
+  { poly1305_flush(P1305_CTX(me)); RETURN_ME; }
+
+static PyObject *polymeth_flushzero(PyObject *me)
+  { poly1305_flushzero(P1305_CTX(me)); RETURN_ME; }
+
+static PyObject *polymeth_concat(PyObject *me, PyObject *arg)
+{
+  PyObject *pre, *suff;
+  if (!PyArg_ParseTuple(arg, "OO:concat", &pre, &suff)) return (0);
+  if (!PyObject_TypeCheck(pre, poly1305hash_pytype) ||
+      !PyObject_TypeCheck(suff, poly1305hash_pytype))
+    TYERR("wanted a poly1305hash");
+  if (Py_TYPE(me) != Py_TYPE(pre) || Py_TYPE(me) != Py_TYPE(suff))
+    TYERR("key mismatch");
+  if (P1305_CTX(pre)->nbuf) VALERR("prefix is not block-aligned");
+  poly1305_concat(P1305_CTX(me), P1305_CTX(pre), P1305_CTX(suff));
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *polymeth_done(PyObject *me)
+{
+  PyObject *rc;
+  if (!(P1305_F(me) & f_mask)) VALERR("no mask");
+  rc = bytestring_pywrap(0, POLY1305_TAGSZ);
+  poly1305_done(P1305_CTX(me), BIN_PTR(rc));
+  return (rc);
+end:
+  return (0);
+}
+
+static const PyGetSetDef poly1305cls_pygetset[] = {
+#define GETSETNAME(op, name) poly1305cls##op##_##name
+  GET  (keysz,         "PC.keysz -> acceptable key sizes")
+  GET  (masksz,        "PC.masksz -> mask size")
+  GET  (tagsz,         "PC.tagsz -> MAC output size")
+  GET  (name,          "PC.name -> name of this kind of MAC")
+#undef GETSETNAME
+  { 0 }
+};
+
+static PyObject *poly1305get_name(PyObject *me, void *hunoz)
+  { RETURN_OBJ(((PyHeapTypeObject *)poly1305key_pytype)->ht_name); }
+
+static PyObject *poly1305get_tagsz(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(16)); }
+
+static const PyGetSetDef poly1305_pygetset[] = {
+#define GETSETNAME(op, name) poly1305##op##_##name
+  GET  (tagsz,         "PK.tagsz -> MAC output size")
+  GET  (name,          "PK.name -> name of this kind of MAC")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef poly1305hash_pymethods[] = {
+#define METHNAME(name) polymeth_##name
+  NAMETH(copy,         "P.copy() -> PP")
+  METH (hash,          "P.hash(M)")
+#define METHU_(n, W, w) METH(hashu##w, "P.hashu" #w "(WORD)")
+  DOUINTCONV(METHU_)
+#undef METHU_
+#define METHBUF_(n, W, w) METH(hashbuf##w, "P.hashbuf" #w "(BYTES)")
+  DOUINTCONV(METHBUF_)
+#undef METHBUF_
+  METH (hashstrz,      "P.hashstrz(STRING)")
+  NAMETH(flush,                "P.flush()")
+  NAMETH(flushzero,    "P.flushzero()")
+  METH (concat,        "P.concat(PREFIX, SUFFIX)")
+  NAMETH(done,         "P.done() -> TAG")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject poly1305cls_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "_Poly1305Class",                    /* @tp_name@ */
+  sizeof(PyHeapTypeObject),            /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Poly1305 metametaclass.  Best not to ask.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(poly1305cls),               /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject poly1305key_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "poly1305",                          /* @tp_name@ */
+  sizeof(poly1305key_pyobj),           /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "poly1305(K): Poly1305 key.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(poly1305),                  /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  poly1305key_pynew,                   /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject poly1305hash_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "Poly1305Hash",                      /* @tp_name@ */
+  sizeof(poly1305hash_pyobj),          /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Poly1305 MAC context base class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(poly1305hash),             /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Special snowflake for HSalsa and HChaCha --------------------------*/
+
+#define DEF_HDANCE(DANCE, HDANCE, dance, hdance)                       \
+  static PyObject *meth_##hdance##_prf(PyObject *me, PyObject *arg)    \
+  {                                                                    \
+    dance##_ctx dance;                                                 \
+    struct bin k, n;                                                   \
+    PyObject *rc;                                                      \
+    if (!PyArg_ParseTuple(arg, "O&O&:" #hdance "_prf",                 \
+                         convbin, &k, convbin, &n))                    \
+      goto end;                                                                \
+    if (k.sz != keysz(k.sz, dance##_keysz)) VALERR("bad key length");  \
+    if (n.sz != HDANCE##_INSZ) VALERR("bad input length");             \
+    rc = bytestring_pywrap(0, HSALSA20_OUTSZ);                         \
+    dance##_init(&dance, k.p, k.sz, 0);                                        \
+    hdance##_prf(&dance, n.p, BIN_PTR(rc));                            \
+    return (rc);                                                       \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+
+DEF_HDANCE(SALSA20, HSALSA20, salsa20, hsalsa20)
+DEF_HDANCE(SALSA20, HSALSA20, salsa20, hsalsa2012)
+DEF_HDANCE(SALSA20, HSALSA20, salsa20, hsalsa208)
+
+DEF_HDANCE(CHACHA, HCHACHA, chacha, hchacha20)
+DEF_HDANCE(CHACHA, HCHACHA, chacha, hchacha12)
+DEF_HDANCE(CHACHA, HCHACHA, chacha, hchacha8)
+
+/*----- Keccak-p[1600, n] -------------------------------------------------*/
+
+static PyTypeObject *kxvik_pytype;
+
+typedef struct kxvik_pyobj {
+  PyObject_HEAD
+  keccak1600_state s;
+  unsigned n;
+} kxvik_pyobj;
+
+static PyObject *kxvik_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  unsigned n = 24;
+  kxvik_pyobj *rc = 0;
+  static const char *const kwlist[] = { "nround", 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", KWLIST,
+                                  convuint, &n))
+    goto end;
+  rc = (kxvik_pyobj *)ty->tp_alloc(ty, 0);
+  rc->n = n;
+  keccak1600_init(&rc->s);
+end:
+  return ((PyObject *)rc);
+}
+
+static PyObject *kxvikmeth_copy(PyObject *me)
+{
+  kxvik_pyobj *k = (kxvik_pyobj *)me, *rc = 0;
+  rc = (kxvik_pyobj *)Py_TYPE(k)->tp_alloc(Py_TYPE(k), 0);
+  rc->s = k->s; rc->n = k->n;
+  return ((PyObject *)rc);
+}
+
+static PyObject *kxvikmeth_mix(PyObject *me, PyObject *arg)
+{
+  kxvik_pyobj *k = (kxvik_pyobj *)me;
+  kludge64 t[25];
+  const octet *q;
+  octet buf[8];
+  unsigned i;
+  struct bin in;
+  size_t n;
+
+  if (!PyArg_ParseTuple(arg, "O&:mix", convbin, &in)) goto end;
+  if (in.sz > 200) VALERR("out of range");
+  q = in.p; n = in.sz;
+  i = 0;
+  while (n > 8) { LOAD64_L_(t[i], q); i++; q += 8; n -= 8; }
+  if (n) {
+    memcpy(buf, q, n); memset(buf + n, 0, 8 - n);
+    LOAD64_L_(t[i], buf); i++;
+  }
+  keccak1600_mix(&k->s, t, i);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *kxvikmeth_set(PyObject *me, PyObject *arg)
+{
+  kxvik_pyobj *k = (kxvik_pyobj *)me;
+  kludge64 t[25];
+  const octet *q;
+  unsigned i;
+  struct bin in;
+  size_t n;
+
+  if (!PyArg_ParseTuple(arg, "O&:set", convbin, &in)) goto end;
+  if (in.sz > 200) VALERR("out of range");
+  q = in.p; n = in.sz;
+  i = 0;
+  while (n >= 8) { LOAD64_L_(t[i], q); i++; q += 8; n -= 8; }
+  if (n) VALERR("not 64-bit aligned");
+  keccak1600_set(&k->s, t, i);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *kxvikmeth_extract(PyObject *me, PyObject *arg)
+{
+  kxvik_pyobj *k = (kxvik_pyobj *)me;
+  PyObject *rc = 0;
+  kludge64 t[25];
+  octet *q, buf[8];
+  unsigned i;
+  unsigned n;
+
+  if (!PyArg_ParseTuple(arg, "O&:extract", convuint, &n)) goto end;
+  if (n > 200) VALERR("out of range");
+  rc = bytestring_pywrap(0, n);
+  q = (octet *)BIN_PTR(rc);
+  keccak1600_extract(&k->s, t, (n + 7)/8);
+  i = 0;
+  while (n > 8) { STORE64_L_(q, t[i]); i++; q += 8; n -= 8; }
+  if (n) { STORE64_L_(buf, t[i]); memcpy(q, buf, n); }
+end:
+  return (rc);
+}
+
+static PyObject *kxvikmeth_step(PyObject *me)
+{
+  kxvik_pyobj *k = (kxvik_pyobj *)me;
+  keccak1600_p(&k->s, &k->s, k->n);
+  RETURN_ME;
+}
+
+static const PyMemberDef kxvik_pymembers[] = {
+#define MEMBERSTRUCT kxvik_pyobj
+  MEMRNM(nround, T_UINT, n, 0,     "KECCAC.nround -> number of rounds")
+#undef MEMBERSTRUCT
+  { 0 }
+};
+
+static const PyMethodDef kxvik_pymethods[] = {
+#define METHNAME(func) kxvikmeth_##func
+  NAMETH(copy,         "KECCAK.copy() -> KECCAK'")
+  METH (mix,           "KECCAK.mix(DATA)")
+  METH (set,           "KECCAK.set(DATA)")
+  METH (extract,       "KECCAK.extract(NOCTETS)")
+  NAMETH(step,         "KECCAK.step()")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject kxvik_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "Keccak1600",                                /* @tp_name@ */
+  sizeof(kxvik_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Keccak1600([nround = 24]): Keccak-p[1600, n] state.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(kxvik),                    /* @tp_methods@ */
+  PYMEMBERS(kxvik),                    /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  kxvik_pynew,                         /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyTypeObject *shake_pytype, *shake128_pytype, *shake256_pytype;
+
+typedef struct shake_pyobj {
+  PyObject_HEAD
+  int st;
+  shake_ctx h;
+} shake_pyobj;
+
+#define SHAKE_H(o) (&((shake_pyobj *)(o))->h)
+#define SHAKE_ST(o) (((shake_pyobj *)(o))->st)
+
+static PyObject *shake_dopynew(void (*initfn)(shake_ctx *,
+                                             const void *, size_t,
+                                             const void *, size_t),
+                              PyTypeObject *ty,
+                              PyObject *arg, PyObject *kw)
+{
+  shake_pyobj *rc = 0;
+  PyObject *pobj = Py_None, *fobj = Py_None;
+  struct bin p = { 0, 0 }, f = { 0, 0 };
+  static const char *const kwlist[] = { "perso", "func", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OO:new", KWLIST, &pobj, &fobj))
+      goto end;
+  if (pobj != Py_None && !convbin(pobj, &p)) goto end;
+  if (fobj != Py_None && !convbin(fobj, &f)) goto end;
+  rc = (shake_pyobj *)ty->tp_alloc(ty, 0);
+  initfn(&rc->h, f.p, f.sz, p.p, p.sz);
+  rc->st = 0;
+end:
+  return ((PyObject *)rc);
+}
+
+static PyObject *shake128_pynew(PyTypeObject *ty,
+                               PyObject *arg, PyObject *kw)
+  { return (shake_dopynew(cshake128_init, ty, arg, kw)); }
+
+static PyObject *shake256_pynew(PyTypeObject *ty,
+                               PyObject *arg, PyObject *kw)
+  { return (shake_dopynew(cshake256_init, ty, arg, kw)); }
+
+static int shake_check(PyObject *me, int st)
+{
+  if (SHAKE_ST(me) != st) VALERR("wrong state");
+  return (0);
+end:
+  return (-1);
+}
+
+static PyObject *shakemeth_hash(PyObject *me, PyObject *arg)
+{
+  struct bin m;
+  if (!PyArg_ParseTuple(arg, "O&:hash", convbin, &m)) return (0);
+  if (shake_check(me, 0)) return (0);
+  shake_hash(SHAKE_H(me), m.p, m.sz);
+  RETURN_ME;
+}
+
+#define SHAKEMETH_HASHU_(n, W, w)                                      \
+  static PyObject *shakemeth_hashu##w(PyObject *me, PyObject *arg)     \
+  {                                                                    \
+    uint##n x;                                                         \
+    octet b[SZ_##W];                                                   \
+    if (!PyArg_ParseTuple(arg, "O&:hashu" #w, convu##n, &x)) return (0); \
+    if (shake_check(me, 0)) return (0);                                        \
+    STORE##W(b, x); shake_hash(SHAKE_H(me), b, sizeof(b));             \
+    RETURN_ME;                                                         \
+  }
+DOUINTCONV(SHAKEMETH_HASHU_)
+
+#define SHAKEMETH_HASHBUF_(n, W, w)                                    \
+  static PyObject *shakemeth_hashbuf##w(PyObject *me, PyObject *arg)   \
+  {                                                                    \
+    struct bin in;                                                     \
+    octet b[SZ_##W];                                                   \
+    if (!PyArg_ParseTuple(arg, "O&:hashbuf" #w, convbin, &in)) goto end; \
+    if (in.sz > MASK##n) VALERR("too large");                          \
+    if (shake_check(me, 0)) goto end;                                  \
+    STORE##W(b, in.sz); shake_hash(SHAKE_H(me), b, sizeof(b));         \
+    shake_hash(SHAKE_H(me), in.p, in.sz);                              \
+    RETURN_ME;                                                         \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+DOUINTCONV(SHAKEMETH_HASHBUF_)
+
+static PyObject *shakemeth_hashstrz(PyObject *me, PyObject *arg)
+{
+  char *p;
+  if (!PyArg_ParseTuple(arg, "s:hashstrz", &p)) return (0);
+  if (shake_check(me, 0)) return (0);
+  shake_hash(SHAKE_H(me), p, strlen(p) + 1);
+  RETURN_ME;
+}
+
+static PyObject *shakemeth_xof(PyObject *me)
+{
+  if (shake_check(me, 0)) goto end;
+  shake_xof(SHAKE_H(me));
+  SHAKE_ST(me) = 1;
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *shakemeth_done(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  PyObject *rc = 0;
+  size_t n = 100 - SHAKE_H(me)->h.r/2;
+  static const char *const kwlist[] = { "hsz", 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:done", KWLIST, convszt, &n))
+    goto end;
+  if (shake_check(me, 0)) goto end;
+  rc = bytestring_pywrap(0, n);
+  shake_done(SHAKE_H(me), BIN_PTR(rc), n);
+  SHAKE_ST(me) = -1;
+end:
+  return (rc);
+}
+
+static PyObject *shakemeth_copy(PyObject *me)
+{
+  shake_pyobj *rc = 0;
+
+  rc = PyObject_NEW(shake_pyobj, Py_TYPE(me));
+  rc->h = *SHAKE_H(me);
+  rc->st = SHAKE_ST(me);
+  return ((PyObject *)rc);
+}
+
+static PyObject *shakemeth_get(PyObject *me, PyObject *arg)
+{
+  PyObject *rc = 0;
+  size_t sz;
+
+  if (!PyArg_ParseTuple(arg, "O&:get", convszt, &sz)) goto end;
+  if (shake_check(me, 1)) goto end;
+  rc = bytestring_pywrap(0, sz);
+  shake_get(SHAKE_H(me), BIN_PTR(rc), sz);
+end:
+  return (rc);
+}
+
+static PyObject *shakemeth_mask(PyObject *me, PyObject *arg)
+{
+  PyObject *rc = 0;
+  struct bin in;
+
+  if (!PyArg_ParseTuple(arg, "O&:mask", convbin, &in)) goto end;
+  if (shake_check(me, 1)) goto end;
+  rc = bytestring_pywrap(0, in.sz);
+  shake_mask(SHAKE_H(me), in.p, BIN_PTR(rc), in.sz);
+end:
+  return (rc);
+}
+
+static PyObject *shakeget_state(PyObject *me, void *hunoz)
+{
+  int st = SHAKE_ST(me);
+  return (TEXT_FROMSTR(st == 0 ? "absorb" :
+                      st == 1 ? "squeeze" : "dead"));
+}
+
+static const PyMemberDef shake_pymembers[] = {
+#define MEMBERSTRUCT shake_pyobj
+  MEMRNM(rate, T_UINT, h.h.r, READONLY, "S.rate -> rate, in bytes")
+  MEMRNM(buffered, T_UINT, h.h.n, READONLY,
+                                  "S.buffered -> amount currently buffered")
+#undef MEMBERSTRUCT
+  { 0 }
+};
+
+static const PyGetSetDef shake_pygetset[] = {
+#define GETSETNAME(op, name) shake##op##_##name
+  GET  (state,         "S.state -> `absorb', `squeeze', `dead'")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef shake_pymethods[] = {
+#define METHNAME(func) shakemeth_##func
+  NAMETH(copy,         "S.copy() -> SS")
+  METH (hash,          "S.hash(M)")
+#define METHU_(n, W, w) METH(hashu##w, "S.hashu" #w "(WORD)")
+  DOUINTCONV(METHU_)
+#undef METHU_
+#define METHBUF_(n, W, w) METH(hashbuf##w, "S.hashbuf" #w "(BYTES)")
+  DOUINTCONV(METHBUF_)
+#undef METHBUF_
+  METH (hashstrz,      "S.hashstrz(STRING)")
+  NAMETH(xof,          "S.xof()")
+  KWMETH(done,         "S.done([hsz = CAP]) -> H")
+  METH (get,           "S.get(LEN) -> H")
+  METH (mask,          "S.mask(M) -> C")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject shake_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "Shake",                             /* @tp_name@ */
+  sizeof(shake_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "SHAKE/cSHAKE/KMAC base class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(shake),                    /* @tp_methods@ */
+  PYMEMBERS(shake),                    /* @tp_members@ */
+  PYGETSET(shake),                     /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject shake128_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "Shake128",                          /* @tp_name@ */
+  0,                                   /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Shake128([perso = STR], [func = STR]): SHAKE128/cSHAKE128 XOF.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  shake128_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject shake256_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "Shake256",                          /* @tp_name@ */
+  0,                                   /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Shake256([perso = STR], [func = STR]): SHAKE256/cSHAKE256 XOF.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  shake256_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyTypeObject *kmac_pytype, *kmac128_pytype, *kmac256_pytype;
+
+static PyObject *kmac_dopynew(void (*initfn)(shake_ctx *,
+                                            const void *, size_t,
+                                            const void *, size_t),
+                              PyTypeObject *ty,
+                              PyObject *arg, PyObject *kw)
+{
+  shake_pyobj *rc = 0;
+  PyObject *pobj = Py_None;
+  struct bin k = { 0, 0 }, p = { 0, 0 };
+  static const char *const kwlist[] = { "key", "perso", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O:new", KWLIST,
+                                  convbin, &k, &pobj))
+      goto end;
+  if (pobj != Py_None && !convbin(pobj, &p)) goto end;
+  rc = (shake_pyobj *)ty->tp_alloc(ty, 0);
+  initfn(&rc->h, p.p, p.sz, k.p, k.sz);
+  rc->st = 0;
+end:
+  return ((PyObject *)rc);
+}
+
+static PyObject *kmac128_pynew(PyTypeObject *ty,
+                              PyObject *arg, PyObject *kw)
+  { return (kmac_dopynew(kmac128_init, ty, arg, kw)); }
+
+static PyObject *kmac256_pynew(PyTypeObject *ty,
+                              PyObject *arg, PyObject *kw)
+  { return (kmac_dopynew(kmac256_init, ty, arg, kw)); }
+
+static PyObject *kmacmeth_xof(PyObject *me)
+{
+  if (shake_check(me, 0)) goto end;
+  kmac_xof(SHAKE_H(me));
+  SHAKE_ST(me) = 1;
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *kmacmeth_done(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  PyObject *rc = 0;
+  size_t n = 100 - SHAKE_H(me)->h.r/2;
+  static const char *const kwlist[] = { "hsz", 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:done", KWLIST, convszt, &n))
+    goto end;
+  if (shake_check(me, 0)) goto end;
+  rc = bytestring_pywrap(0, n);
+  kmac_done(SHAKE_H(me), BIN_PTR(rc), n);
+  SHAKE_ST(me) = -1;
+end:
+  return (rc);
+}
+
+static const PyMethodDef kmac_pymethods[] = {
+#define METHNAME(func) kmacmeth_##func
+  NAMETH(xof,          "K.xof()")
+  KWMETH(done,         "K.done([hsz = CAP/2]) -> T")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject kmac_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KMAC",                              /* @tp_name@ */
+  sizeof(shake_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KMAC base class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(kmac),                     /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject kmac128_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KMAC128",                           /* @tp_name@ */
+  0,                                   /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KMAC128(KEY, [perso = STR]): KMAC XOMAC.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  kmac128_pynew,                       /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject kmac256_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KMAC256",                           /* @tp_name@ */
+  0,                                   /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KMAC256(KEY, [perso = STR]): KMAC XOMAC.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  kmac256_pynew,                       /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Pseudorandom permutations -----------------------------------------*/
+
+static PyTypeObject *gcprp_pytype, *gprp_pytype;
+
+typedef struct prpinfo {
+  const char *name;
+  const octet *keysz;
+  size_t ctxsz;
+  size_t blksz;
+  void (*init)(void *, const void *, size_t);
+  void (*eblk)(void *, const void *, void *);
+  void (*dblk)(void *, const void *, void *);
+} prpinfo;
+
+#define PRP_DEF(PRE, pre)                                              \
+  static void pre##_prpinit(void *ctx, const void *k, size_t ksz)      \
+    { pre##_init(ctx, k, ksz); }                                       \
+  static void pre##_prpeblk(void *ctx, const void *in, void *out)      \
+  {                                                                    \
+    uint32 w[PRE##_BLKSZ/4]; BLKC_LOAD(PRE, w, in);                    \
+    pre##_eblk(ctx, w, w); BLKC_STORE(PRE, out, w);                    \
+  }                                                                    \
+  static void pre##_prpdblk(void *ctx, const void *in, void *out)      \
+  {                                                                    \
+    uint32 w[PRE##_BLKSZ/4]; BLKC_LOAD(PRE, w, in);                    \
+    pre##_dblk(ctx, w, w); BLKC_STORE(PRE, out, w);                    \
+  }                                                                    \
+  static const prpinfo pre##_prpinfo = {                               \
+    #pre, pre##_keysz, sizeof(pre##_ctx), PRE##_BLKSZ,                 \
+    pre##_prpinit, pre##_prpeblk, pre##_prpdblk                                \
+  };
+PRPS(PRP_DEF)
+
+static const struct prpinfo *const gprptab[] = {
+#define PRP_ENTRY(PRE, pre) &pre##_prpinfo,
+  PRPS(PRP_ENTRY)
+  0
+};
+
+typedef struct gcprp_pyobj {
+  PyHeapTypeObject ty;
+  const prpinfo *prp;
+} gcprp_pyobj;
+#define GCPRP_PRP(o) (((gcprp_pyobj *)(o))->prp)
+
+typedef struct gprp_pyobj {
+  PyObject_HEAD
+  const prpinfo *prp;
+} gprp_pyobj;
+#define GPRP_PRP(o) (((gprp_pyobj *)(o))->prp)
+#define GPRP_CTX(o) (((gprp_pyobj *)(o)) + 1)
+
+typedef struct prp {
+  const prpinfo *prp;
+  void *ctx;
+} prp;
+
+static PyObject *gprp_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "key", 0 };
+  struct bin k;
+  const prpinfo *prp = GCPRP_PRP(ty);
+  PyObject *me;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convbin, &k))
+    goto end;
+  if (keysz(k.sz, prp->keysz) != k.sz) VALERR("bad key length");
+  me = (PyObject *)ty->tp_alloc(ty, 0);
+  GPRP_PRP(me) = prp;
+  prp->init(GPRP_CTX(me), k.p, k.sz);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static void gprp_pydealloc(PyObject *me)
+  { Py_DECREF(Py_TYPE(me)); FREEOBJ(me); }
+
+static PyObject *gcprp_pywrap(const prpinfo *prp)
+{
+  gcprp_pyobj *g = newtype(gcprp_pytype, 0, prp->name);
+  g->prp = prp;
+  g->ty.ht_type.tp_basicsize = sizeof(gprp_pyobj) + prp->ctxsz;
+  g->ty.ht_type.tp_base = gprp_pytype;
+  Py_INCREF(gprp_pytype);
+  g->ty.ht_type.tp_flags = (Py_TPFLAGS_DEFAULT |
+                           Py_TPFLAGS_BASETYPE |
+                           Py_TPFLAGS_HEAPTYPE);
+  g->ty.ht_type.tp_alloc = PyType_GenericAlloc;
+  g->ty.ht_type.tp_free = 0;
+  g->ty.ht_type.tp_new = gprp_pynew;
+  typeready(&g->ty.ht_type);
+  return ((PyObject *)g);
+}
+
+static PyObject *gcpget_name(PyObject *me, void *hunoz)
+  { return (TEXT_FROMSTR(GCPRP_PRP(me)->name)); }
+static PyObject *gcpget_keysz(PyObject *me, void *hunoz)
+  { return (keysz_pywrap(GCPRP_PRP(me)->keysz)); }
+static PyObject *gcpget_blksz(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GCPRP_PRP(me)->blksz)); }
+
+static PyObject *gpmeth_encrypt(PyObject *me, PyObject *arg)
+{
+  struct bin m;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:encrypt", convbin, &m)) goto end;
+  if (m.sz != GPRP_PRP(me)->blksz) VALERR("incorrect block length");
+  rc = bytestring_pywrap(0, m.sz);
+  GPRP_PRP(me)->eblk(GPRP_CTX(me), m.p, BIN_PTR(rc));
+end:
+  return (rc);
+}
+
+static PyObject *gpmeth_decrypt(PyObject *me, PyObject *arg)
+{
+  struct bin c;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:decrypt", convbin, &c)) goto end;
+  if (c.sz != GPRP_PRP(me)->blksz) VALERR("incorrect block length");
+  rc = bytestring_pywrap(0, c.sz);
+  GPRP_PRP(me)->dblk(GPRP_CTX(me), c.p, BIN_PTR(rc));
+end:
+  return (rc);
+}
+
+static const PyGetSetDef gcprp_pygetset[] = {
+#define GETSETNAME(op, name) gcp##op##_##name
+  GET  (keysz,         "CP.keysz -> acceptable key sizes")
+  GET  (blksz,         "CP.blksz -> block size")
+  GET  (name,          "CP.name -> name of this kind of PRP")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef gprp_pymethods[] = {
+#define METHNAME(name) gpmeth_##name
+  METH (encrypt,       "P.encrypt(PT) -> CT")
+  METH (decrypt,       "P.decrypt(CT) -> PT")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject gcprp_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GCPRP",                             /* @tp_name@ */
+  sizeof(gcprp_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Pseudorandom permutation metaclass.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gcprp),                     /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject gprp_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GPRP",                              /* @tp_name@ */
+  sizeof(gprp_pyobj),                  /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  gprp_pydealloc,                      /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Pseudorandom permutation, abstract base class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gprp),                     /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+static const struct nameval consts[] = {
+  CONST(AEADF_PCHSZ), CONST(AEADF_PCMSZ), CONST(AEADF_PCTSZ),
+  CONST(AEADF_AADNDEP), CONST(AEADF_AADFIRST), CONST(AEADF_NOAAD),
+  { 0 }
+};
+
+static const PyMethodDef methods[] = {
+#define METHNAME(func) meth_##func
+#define METH_HDANCE(hdance, HDance) METH(hdance##_prf,                 \
+        "" #hdance "_prf(K, N) -> H: calculate " HDance " hash of N with K")
+  METH_HDANCE(hsalsa20, "HSalsa20")
+  METH_HDANCE(hsalsa2012, "HSalsa20/12")
+  METH_HDANCE(hsalsa208, "HSalsa20/8")
+  METH_HDANCE(hchacha20, "HChaCha20")
+  METH_HDANCE(hchacha12, "HChaCha12")
+  METH_HDANCE(hchacha8, "HChaCha8")
+#undef METH_DANCE
+#undef METHNAME
+  { 0 }
+};
+
+void algorithms_pyinit(void)
+{
+  INITTYPE(keysz, root);
+  INITTYPE(keyszany, keysz);
+  INITTYPE(keyszrange, keysz);
+  INITTYPE(keyszset, keysz);
+  INITTYPE(gccipher, type);
+  INITTYPE(gcipher, root);
+  INITTYPE(gcaead, type);
+  INITTYPE(gaeadkey, root);
+  INITTYPE(gcaeadaad, type);
+  INITTYPE(gaeadaad, root);
+  INITTYPE(gcaeadenc, type);
+  INITTYPE(gaeadenc, root);
+  INITTYPE(gcaeaddec, type);
+  INITTYPE(gaeaddec, root);
+  INITTYPE(gchash, type);
+  INITTYPE(ghash, root);
+  INITTYPE(gcmac, type);
+  INITTYPE(gmac, type);
+  INITTYPE(gmhash, ghash);
+  INITTYPE(poly1305cls, type);
+  INITTYPE_META(poly1305key, type, poly1305cls);
+  INITTYPE(poly1305hash, root);
+  INITTYPE(kxvik, root);
+  INITTYPE(shake, root);
+  INITTYPE(shake128, shake);
+  INITTYPE(shake256, shake);
+  INITTYPE(kmac, shake);
+  INITTYPE(kmac128, kmac);
+  INITTYPE(kmac256, kmac);
+  INITTYPE(gcprp, type);
+  INITTYPE(gprp, root);
+  addmethods(methods);
+}
+
+#define gcprp prpinfo
+#define CLASS_TABLES(_) _(cipher) _(aead) _(hash) _(mac) _(prp)
+#define TABLE_FNS(pre)                                                 \
+  static const char *pre##_namefn(const void *p)                       \
+    { const gc##pre *const *cls = p; return (*cls ? (*cls)->name : 0); } \
+  static PyObject *pre##_valfn(const void *p)                          \
+    { gc##pre *const*cls = p; return (gc##pre##_pywrap(*cls)); }
+CLASS_TABLES(TABLE_FNS)
+
+void algorithms_pyinsert(PyObject *mod)
+{
+  PyObject *d;
+  INSERT("KeySZ", keysz_pytype);
+  INSERT("KeySZAny", keyszany_pytype);
+  INSERT("KeySZRange", keyszrange_pytype);
+  INSERT("KeySZSet", keyszset_pytype);
+  INSERT("GCCipher", gccipher_pytype);
+  INSERT("GCipher", gcipher_pytype);
+  INSERT("gcciphers", make_algtab(gciphertab, sizeof(gccipher *),
+                                 cipher_namefn, cipher_valfn));
+  INSERT("GCAEAD", gcaead_pytype);
+  INSERT("GAEKey", gaeadkey_pytype);
+  INSERT("GAEAADClass", gcaeadaad_pytype);
+  INSERT("GAEAAD", gaeadaad_pytype);
+  INSERT("GAEEncClass", gcaeadenc_pytype);
+  INSERT("GAEEnc", gaeadenc_pytype);
+  INSERT("GAEDecClass", gcaeaddec_pytype);
+  INSERT("GAEDec", gaeaddec_pytype);
+  INSERT("gcaeads", make_algtab(gaeadtab, sizeof(gcaead *),
+                               aead_namefn, aead_valfn));
+  INSERT("GCHash", gchash_pytype);
+  INSERT("GHash", ghash_pytype);
+  d = make_algtab(ghashtab, sizeof(gchash *), hash_namefn, hash_valfn);
+  INSERT("gchashes", d);
+  sha_pyobj = PyMapping_GetItemString(d, "sha"); Py_INCREF(sha_pyobj);
+  has160_pyobj = PyMapping_GetItemString(d, "has160"); Py_INCREF(has160_pyobj);
+  INSERT("GCMAC", gcmac_pytype);
+  INSERT("GMAC", gmac_pytype);
+  INSERT("GMACHash", gmhash_pytype);
+  INSERT("gcmacs", make_algtab(gmactab, sizeof(gcmac *),
+                              mac_namefn, mac_valfn));
+  INSERT("_Poly1305Class", poly1305cls_pytype);
+  INSERT("poly1305", poly1305key_pytype);
+  INSERT("Poly1305Hash", poly1305hash_pytype);
+  INSERT("Keccak1600", kxvik_pytype);
+  INSERT("Shake", shake_pytype);
+  INSERT("Shake128", shake128_pytype);
+  INSERT("Shake256", shake256_pytype);
+  INSERT("KMAC", kmac_pytype);
+  INSERT("KMAC128", kmac128_pytype);
+  INSERT("KMAC256", kmac256_pytype);
+  INSERT("GCPRP", gcprp_pytype);
+  INSERT("GPRP", gprp_pytype);
+  INSERT("gcprps", make_algtab(gprptab, sizeof(gcprp *),
+                              prp_namefn, prp_valfn));
+  setconstants(mod, consts);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/algorithms.py b/algorithms.py
new file mode 100644 (file)
index 0000000..2444382
--- /dev/null
@@ -0,0 +1,109 @@
+## -*-python-*-
+
+def cross(*seq):
+  if not len(seq):
+    return [(),]
+  x = seq[0]
+  if type(x) is not tuple and type(x) is not list:
+    x = x,
+  r = []
+  for i in x:
+    for j in cross(*seq[1:]):
+      r.append((i,) + j)
+  return r
+
+prps = '''
+des desx des3 mars
+idea safer safersk
+blowfish twofish
+tea xtea
+rc2 rc5
+skipjack
+cast128 cast256
+square rijndael rijndael192 rijndael256
+serpent noekeon
+'''.split()
+pmodes = '''
+ecb cbc cfb ofb counter
+cmac pmac1
+ccm eax gcm ocb1 ocb3
+'''.split()
+streamciphers = '''
+rc4 seal
+'''.split()
+latindances = '''
+salsa20 salsa20/12 salsa20/8
+salsa20-ietf salsa20/12-ietf salsa20/8-ietf
+xsalsa20 xsalsa20/12 xsalsa20/8
+chacha20 chacha12 chacha8
+chacha20-ietf chacha12-ietf chacha8-ietf
+xchacha20 xchacha12 xchacha8
+'''.split()
+streamciphers += map(lambda s: s.replace('/', ''), latindances)
+hashes = '''
+md2 md4 md5 tiger has160
+sha sha224 sha256 sha512/224 sha512/256 sha384 sha512
+rmd128 rmd160 rmd256 rmd320
+whirlpool whirlpool256
+sha3-224 sha3-256 sha3-384 sha3-512
+'''.split()
+hmodes = '''
+mgf hmac
+'''.split()
+
+print('/* algorithms.h [generated] */')
+print('')
+
+for i in prps:
+  print('#include <catacomb/%s.h>' % i.replace('/', '-'))
+  for j in pmodes:
+    print('#include <catacomb/%s-%s.h>' % (i.replace('/', '-'), j))
+for i in streamciphers:
+  print('#include <catacomb/%s.h>' % i.replace('/', '-'))
+print('')
+for i in hashes:
+  print('#include <catacomb/%s.h>' % i.replace('/', '-'))
+  for j in hmodes:
+    print('#include <catacomb/%s-%s.h>' % (i.replace('/', '-'), j))
+print('')
+
+print('#define PRPS(_) \\')
+for i in prps:
+  print('\t_(%s, %s) \\' % (i.upper(), i))
+print('\t/* end */')
+print('')
+
+print('#define RNGS(_) \\')
+for i in (cross(prps, ['ofb', 'counter'])):
+  print(('\t_("%(prim)s-%(mode)s", %(primid)s_keysz, ' +
+         '%(primid)s_%(mode)srand, RNG_PLAIN, 0) \\') %
+        {'prim': i[0], 'mode': i[1],
+         'primid': i[0].replace('-', '_').replace('/', '_')})
+for i in (cross(hashes, 'mgf')):
+  print(('\t_("%(prim)s-%(mode)s", %(primid)s_%(mode)skeysz, ' +
+         '%(primid)s_%(mode)srand, RNG_PLAIN, 0) \\') %
+        {'prim': i[0], 'mode': i[1],
+         'primid': i[0].replace('-', '_').replace('/', '_')})
+print('\t_("rc4", rc4_keysz, rc4_rand, 0, 0) \\')
+print('\t_("seal", seal_keysz, seal_rand, RNG_SEAL, 0) \\')
+for i in latindances:
+  for r in ['salsa20', 'xsalsa20', 'chacha', 'xchacha']:
+    if i.startswith(r):
+      root = r
+      break
+  else:
+    raise ValueError('failed to find root name for %s' % i)
+  if i.endswith('-ietf'): root += '_ietf'
+  print(('\t_("%(name)s", %(root)s_keysz, %(id)s_rand, ' +
+         'RNG_LATIN, %(ROOT)s_NONCESZ) \\') % \
+        {'name': i, 'id': i.replace('/', '').replace('-', '_'),
+         'root': root, 'ROOT': root.upper()})
+for i in [128, 256]:
+  print(('\t_("shake%(w)d", shake%(w)d_keysz, cshake%(w)d_rand, ' +
+         'RNG_SHAKE, 0) \\') % \
+        {'w': i})
+  print(('\t_("kmac%(w)d", kmac%(w)d_keysz, kmac%(w)d_rand, ' +
+         'RNG_KMAC, 0) \\') % \
+        {'w': i})
+print('\t/* end */')
+print('')
diff --git a/buffer.c b/buffer.c
new file mode 100644 (file)
index 0000000..d0df068
--- /dev/null
+++ b/buffer.c
@@ -0,0 +1,646 @@
+/* -*-c-*-
+ *
+ * Reading and writing buffers of stuff
+ *
+ * (c) 2005 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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "catacomb-python.h"
+
+/*----- Read buffers ------------------------------------------------------*/
+
+PyTypeObject *rbuf_pytype;
+
+static PyObject *rbuf_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  struct bin in;
+  void *q;
+  buf_pyobj *me = 0;
+  static const char *const kwlist[] = { "data", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convbin, &in))
+    goto end;
+  q = xmalloc(in.sz);
+  memcpy(q, in.p, in.sz);
+  me = (buf_pyobj *)ty->tp_alloc(ty, 0);
+  me->sub = 0; me->lk = 0;
+  buf_init(&me->b, q, in.sz);
+end:
+  return ((PyObject *)me);
+}
+
+static void buf_pydealloc(PyObject *me)
+{
+  assert(!BUF_LK(me));
+  if (BUF_SUB(me)) Py_DECREF(BUF_SUB(me));
+  else xfree(BBASE(BUF_B(me)));
+  FREEOBJ(me);
+}
+
+#ifdef PY3
+  static int rbuf_pygetbuf(PyObject *me, Py_buffer *vw, int f)
+  {
+    buf *b = BUF_B(me);
+    return (PyBuffer_FillInfo(vw, me, BCUR(b), BLEFT(b), 1, f));
+  }
+#else
+  static Py_ssize_t rbuf_pysegcount(PyObject *me, Py_ssize_t *nn)
+    { if (nn) *nn = BSZ(BUF_B(me)); return (1); }
+  static Py_ssize_t rbuf_pyreadbuf(PyObject *me, Py_ssize_t seg, void **q)
+    { assert(seg == 0); *q = BCUR(BUF_B(me)); return (BLEFT(BUF_B(me))); }
+#endif
+
+static PyObject *rbmeth_skip(PyObject *me, PyObject *arg)
+{
+  size_t n;
+
+  if (!PyArg_ParseTuple(arg, "O&:skip", convszt, &n)) goto end;
+  if (!buf_get(BUF_B(me), n)) BUFERR("buffer exhausted");
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *rbmeth_get(PyObject *me, PyObject *arg)
+{
+  void *p;
+  size_t n;
+
+  if (!PyArg_ParseTuple(arg, "O&:get", convszt, &n)) goto end;
+  if ((p = buf_get(BUF_B(me), n)) == 0) BUFERR("buffer exhausted");
+  return (bytestring_pywrap(p, n));
+end:
+  return (0);
+}
+
+#define RBMETH_GETU_(n, W, w)                                          \
+  static PyObject *rbmeth_getu##w(PyObject *me)                                \
+  {                                                                    \
+    uint##n x;                                                         \
+    if (buf_getu##w(BUF_B(me), &x)) BUFERR("buffer exhausted");                \
+    if (MASK##W <= ULONG_MAX) return (getulong(x));                    \
+    else { kludge64 y; ASSIGN64(y, x); return (getk64(y)); }           \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+DOUINTCONV(RBMETH_GETU_)
+
+#define RBMETH_GETBLK_(n, W, w)                                                \
+  static PyObject *rbmeth_getblk##w(PyObject *me)                      \
+  {                                                                    \
+    size_t sz;                                                         \
+    char *q;                                                           \
+    if ((q = buf_getmem##w(BUF_B(me), &sz)) == 0)                      \
+      BUFERR("buffer exhausted");                                      \
+    return (bytestring_pywrap(q, sz));                                 \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+BUF_DOSUFFIXES(RBMETH_GETBLK_)
+
+#define RBMETH_GETBUF_(n, W, w)                                                \
+  static PyObject *rbmeth_getbuf##w(PyObject *me)                      \
+  {                                                                    \
+    buf_pyobj *b;                                                      \
+    buf bb;                                                            \
+    if (buf_getbuf##w(BUF_B(me), &bb)) BUFERR("buffer exhausted");     \
+    b = PyObject_NEW(buf_pyobj, rbuf_pytype);                          \
+    b->b = bb;                                                         \
+    b->sub = me;                                                       \
+    b->lk = 0;                                                         \
+    Py_INCREF(me);                                                     \
+    return ((PyObject *)b);                                            \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+BUF_DOSUFFIXES(RBMETH_GETBUF_)
+
+static PyObject *rbmeth_getmp(PyObject *me)
+{
+  mp *x;
+  if ((x = buf_getmp(BUF_B(me))) == 0) BUFERR("buffer exhausted");
+  return (mp_pywrap(x));
+end:
+  return (0);
+}
+
+static PyObject *rbmeth_getgf(PyObject *me)
+{
+  mp *x;
+  if ((x = buf_getmp(BUF_B(me))) == 0) BUFERR("buffer exhausted");
+  return (gf_pywrap(x));
+end:
+  return (0);
+}
+
+static PyObject *rbmeth_getecpt(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  PyObject *cobj = Py_None;
+  static const char *const kwlist[] = { "curve", 0 };
+  ec pt = EC_INIT;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O:getecpt", KWLIST, &cobj))
+    goto end;
+  if (cobj == Py_None) cobj = (PyObject *)ecpt_pytype;
+  if (!PyType_Check(cobj) ||
+      !PyType_IsSubtype((PyTypeObject *)cobj, ecpt_pytype))
+    TYERR("expected elliptic curve type");
+  if (buf_getec(BUF_B(me), &pt)) BUFERR("buffer exhausted");
+  return (ecpt_pywrapout(cobj, &pt));
+end:
+  return (0);
+}
+
+static PyObject *rbmeth_getecptraw(PyObject *me, PyObject *arg)
+{
+  PyObject *cobj;
+  ec pt = EC_INIT;
+  PyObject *rc = 0;
+  if (!PyArg_ParseTuple(arg, "O!:getecptraw", eccurve_pytype, &cobj))
+    goto end;
+  if (ec_getraw(ECCURVE_C(cobj), BUF_B(me), &pt)) BUFERR("buffer exhausted");
+  rc = ecpt_pywrapout(cobj, &pt);
+end:
+  return (rc);
+}
+
+static PyObject *rbmeth_os2ecp(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  PyObject *cobj;
+  ec pt = EC_INIT;
+  unsigned f = EC_XONLY | EC_LSB | EC_SORT | EC_EXPLY;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "curve", "flags", 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!|O&:os2ecp", KWLIST,
+                                  eccurve_pytype, &cobj, convuint, &f))
+    goto end;
+  if (ec_os2ecp(ECCURVE_C(cobj), f, BUF_B(me), &pt)) VALERR("bad point");
+  rc = ecpt_pywrapout(cobj, &pt);
+end:
+  return (rc);
+}
+
+static PyObject *rbmeth_getge(PyObject *me, PyObject *arg)
+{
+  PyObject *gobj;
+  ge *x = 0;
+  if (!PyArg_ParseTuple(arg, "O!:getge", group_pytype, &gobj)) goto end;
+  x = G_CREATE(GROUP_G(gobj));
+  if (G_FROMBUF(GROUP_G(gobj), BUF_B(me), x)) BUFERR("buffer exhausted");
+  return (ge_pywrap(gobj, x));
+end:
+  if (x) G_DESTROY(GROUP_G(gobj), x);
+  return (0);
+}
+
+static PyObject *rbmeth_getgeraw(PyObject *me, PyObject *arg)
+{
+  PyObject *gobj;
+  ge *x = 0;
+  if (!PyArg_ParseTuple(arg, "O!:getgeraw", group_pytype, &gobj)) goto end;
+  x = G_CREATE(GROUP_G(gobj));
+  if (G_FROMRAW(GROUP_G(gobj), BUF_B(me), x)) BUFERR("buffer exhausted");
+  return (ge_pywrap(gobj, x));
+end:
+  if (x) G_DESTROY(GROUP_G(gobj), x);
+  return (0);
+}
+
+static PyObject *rbget_size(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(BSZ(BUF_B(me)))); }
+static PyObject *rbget_left(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(BLEFT(BUF_B(me)))); }
+static PyObject *rbget_endp(PyObject *me, void *hunoz)
+  { return (getbool(!BLEFT(BUF_B(me)))); }
+static PyObject *rbget_offset(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(BLEN(BUF_B(me)))); }
+static int rbset_offset(PyObject *me, PyObject *x, void *hunoz)
+{
+  size_t n;
+  if (!x) NIERR("__del__");
+  if (!convszt(x, &n)) goto end;
+  if (n > BSZ(BUF_B(me))) VALERR("out of range");
+  BCUR(BUF_B(me)) = BBASE(BUF_B(me)) + n;
+  return (0);
+end:
+  return (-1);
+}
+
+static const PyGetSetDef rbuf_pygetset[] = {
+#define GETSETNAME(op, name) rb##op##_##name
+  GET  (size,          "RBUF.size -> SIZE")
+  GET  (left,          "RBUF.left -> REMAINDER")
+  GET  (endp,          "RBUF.endp -> BOOL")
+  GETSET(offset,       "RBUF.offset -> OFFSET")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef rbuf_pymethods[] = {
+#define METHNAME(func) rbmeth_##func
+  METH (skip,          "RBUF.skip(N)")
+  METH (get,           "RBUF.get(N) -> BYTES")
+#define RBMETH_DECL_GETU_(n, W, w)                                     \
+    NAMETH(getu##w,    "RBUF.getu" #w "() -> INT")
+  DOUINTCONV(RBMETH_DECL_GETU_)
+#define RBMETH_DECL_GETBLK_(n, W, w)                                   \
+    NAMETH(getblk##w,  "RBUF.getblk" #w "() -> BYTES")
+  BUF_DOSUFFIXES(RBMETH_DECL_GETBLK_)
+#define RBMETH_DECL_GETBUF_(n, W, w)                                   \
+    NAMETH(getbuf##w,  "RBUF.getbuf" #w "() -> RBUF'")
+  BUF_DOSUFFIXES(RBMETH_DECL_GETBUF_)
+  NAMETH(getmp,                "RBUF.getmp() -> X")
+  NAMETH(getgf,                "RBUF.getgf() -> X")
+  KWMETH(getecpt,      "RBUF.getecpt([curve = None]) -> P")
+  METH (getecptraw,    "RBUF.getecptraw(CURVE) -> P")
+  KWMETH(os2ecp,       "RBUF.os2ecp(CURVE, [flags = ...]) -> P")
+  METH (getge,         "RBUF.getge(GROUP) -> X")
+  METH (getgeraw,      "RBUF.getgeraw(GROUP) -> X")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyBufferProcs rbuf_pybuffer = {
+#ifdef PY3
+  rbuf_pygetbuf,                       /* @bf_getbuffer@ */
+  0,                                   /* @bf_releasebuffer@ */
+#else
+  rbuf_pyreadbuf,                      /* @bf_getreadbuffer@ */
+  0,                                   /* @bf_getwritebuffer@ */
+  rbuf_pysegcount,                     /* @bf_getsegcount@ */
+  0                                    /* @bf_getcharbuffer@ */
+#endif
+};
+
+static const PyTypeObject rbuf_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "ReadBuffer",                                /* @tp_name@ */
+  sizeof(buf_pyobj),                   /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  buf_pydealloc,                       /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  PYBUFFER(rbuf),                      /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "ReadBuffer(STR): a read buffer.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(rbuf),                     /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(rbuf),                      /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  rbuf_pynew,                          /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Write buffers -----------------------------------------------------*/
+
+PyTypeObject *wbuf_pytype;
+
+int ensurebuf(PyObject *me, size_t n)
+{
+  buf *b = BUF_B(me);
+  size_t nn = BSZ(b);
+  octet *p;
+  size_t want = BLEN(b) + n;
+
+  if (BLEFT(b) >= n)
+    return (0);
+  else if (BUF_LK(me))
+    BUFERR("buffer locked");
+  else {
+    while (nn < want) nn <<= 1;
+    p = xrealloc(BBASE(b), nn, BSZ(b));
+    BCUR(b) = p + BLEN(b);
+    BLIM(b) = p + nn;
+    BBASE(b) = p;
+    return (0);
+  }
+end:
+  return (-1);
+}
+
+static PyObject *wbuf_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  char *p;
+  size_t n = 64;
+  buf_pyobj *me = 0;
+  static const char *const kwlist[] = { "size", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", KWLIST,
+                                  convszt, &n))
+    goto end;
+  me = (buf_pyobj *)ty->tp_alloc(ty, 0);
+  p = xmalloc(n);
+  me->sub = 0; me->lk = 0;
+  buf_init(&me->b, p, n);
+end:
+  return ((PyObject *)me);
+}
+
+#ifdef PY3
+  static int wbuf_pygetbuf(PyObject *me, Py_buffer *vw, int f)
+  {
+    buf *b = BUF_B(me);
+    if (PyBuffer_FillInfo(vw, me, BBASE(b), BLEN(b), 0, f)) return (-1);
+    BUF_LK(me)++; return (0);
+  }
+  static void wbuf_pyrlsbuf(PyObject *me, Py_buffer *vw)
+    { BUF_LK(me)--; }
+#else
+  static Py_ssize_t wbuf_pysegcount(PyObject *me, Py_ssize_t *nn)
+    { if (nn) *nn = BLEN(BUF_B(me)); return (1); }
+  static Py_ssize_t wbuf_pyreadbuf(PyObject *me, Py_ssize_t seg, void **q)
+    { assert(seg == 0); *q = BBASE(BUF_B(me)); return (BLEN(BUF_B(me))); }
+#endif
+
+static PyObject *wbmeth_zero(PyObject *me, PyObject *arg)
+{
+  void *p;
+  size_t n;
+  if (!PyArg_ParseTuple(arg, "O&:zero", convszt, &n)) return (0);
+  if (ensurebuf(me, n)) return (0);
+  p = buf_get(BUF_B(me), n); assert(p && BOK(BUF_B(me)));
+  memset(p, 0, n);
+  RETURN_ME;
+}
+
+static PyObject *wbmeth_put(PyObject *me, PyObject *arg)
+{
+  struct bin in;
+  if (!PyArg_ParseTuple(arg, "O&:put", convbin, &in)) return (0);
+  if (ensurebuf(me, in.sz)) return (0);
+  buf_put(BUF_B(me), in.p, in.sz); assert(BOK(BUF_B(me)));
+  RETURN_ME;
+}
+
+#define WBMETH_PUTU_(n, W, w)                                          \
+  static PyObject *wbmeth_putu##w(PyObject *me, PyObject *arg)         \
+  {                                                                    \
+    uint##n i;                                                         \
+    if (!PyArg_ParseTuple(arg, "O&:putu" #w, convu##n, &i)) return (0);        \
+    if (ensurebuf(me, SZ_##n)) return (0);                             \
+    buf_putu##w(BUF_B(me), i); assert(BOK(BUF_B(me)));                 \
+    RETURN_ME;                                                         \
+  }
+DOUINTCONV(WBMETH_PUTU_)
+
+#define MASKz 0
+#define SZ_z 1
+#define WBMETH_PUTBLK_(n, W, w)                                                \
+  static PyObject *wbmeth_putblk##w(PyObject *me, PyObject *arg)       \
+  {                                                                    \
+    struct bin in;                                                     \
+    if (!PyArg_ParseTuple(arg, "O&:putblk" #w, convbin, &in)) goto end;        \
+    if (MASK##W && in.sz > MASK##W) VALERR("too large");               \
+    if (ensurebuf(me, in.sz + SZ_##n)) return (0);                     \
+    buf_putmem##w(BUF_B(me), in.p, in.sz); assert(BOK(BUF_B(me)));     \
+    RETURN_ME;                                                         \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+BUF_DOSUFFIXES(WBMETH_PUTBLK_)
+
+static PyObject *wbmeth_putmp(PyObject *me, PyObject *arg)
+{
+  mp *x = 0;
+  if (!PyArg_ParseTuple(arg, "O&:putmp", convmp, &x)) return (0);
+  if (ensurebuf(me, mp_octets(x) + 2)) return (0);
+  buf_putmp(BUF_B(me), x); assert(BOK(BUF_B(me)));
+  RETURN_ME;
+}
+
+static PyObject *wbmeth_putgf(PyObject *me, PyObject *arg)
+{
+  mp *x = 0;
+  if (!PyArg_ParseTuple(arg, "O&:putgf", convgf, &x)) return (0);
+  if (ensurebuf(me, mp_octets(x) + 2)) return (0);
+  buf_putmp(BUF_B(me), x); assert(BOK(BUF_B(me)));
+  MP_DROP(x);
+  RETURN_ME;
+}
+
+static PyObject *wbmeth_putecpt(PyObject *me, PyObject *arg)
+{
+  ec pt = EC_INIT;
+  if (!PyArg_ParseTuple(arg, "O&:putecpt", convecpt, &pt)) return (0);
+  if (ensurebuf(me, EC_ATINF(&pt) ? 2 :
+               6 + mp_octets(pt.x) + mp_octets(pt.y)))
+    return (0);
+  buf_putec(BUF_B(me), &pt); assert(BOK(BUF_B(me)));
+  EC_DESTROY(&pt);
+  RETURN_ME;
+}
+
+static PyObject *wbmeth_putecptraw(PyObject *me, PyObject *arg)
+{
+  PyObject *ptobj;
+  ec_curve *cc;
+  ec pt = EC_INIT;
+  if (!PyArg_ParseTuple(arg, "O!:putecptraw", ecptcurve_pytype, &ptobj))
+    return (0);
+  cc = ECPT_C(ptobj);
+  EC_OUT(cc, &pt, ECPT_P(ptobj));
+  if (ensurebuf(me, 2*cc->f->noctets + 1)) return (0);
+  ec_putraw(cc, BUF_B(me), &pt); assert(BOK(BUF_B(me)));
+  EC_DESTROY(&pt);
+  RETURN_ME;
+}
+
+static PyObject *wbmeth_ec2osp(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  PyTypeObject *ptobj;
+  ec_curve *cc;
+  ec pt = EC_INIT;
+  unsigned f = EC_EXPLY;
+  static const char *const kwlist[] = { "point", "flags", 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!|O&:os2ecp", KWLIST,
+                                  ecptcurve_pytype, &ptobj, convuint, &f))
+    goto end;
+  cc = ECPT_C(ptobj);
+  EC_OUT(cc, &pt, ECPT_P(ptobj));
+  if (ensurebuf(me, 2*cc->f->noctets + 1)) return (0);
+  if (ec_ec2osp(cc, f, BUF_B(me), &pt)) VALERR("invalid flags");
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *wbmeth_putge(PyObject *me, PyObject *arg)
+{
+  PyObject *geobj;
+  if (!PyArg_ParseTuple(arg, "O!:putge", ge_pytype, &geobj)) return (0);
+  if (ensurebuf(me, GE_G(geobj)->noctets)) return (0);
+  G_TOBUF(GE_G(geobj), BUF_B(me), GE_X(geobj)); assert(BOK(BUF_B(me)));
+  RETURN_ME;
+}
+
+static PyObject *wbmeth_putgeraw(PyObject *me, PyObject *arg)
+{
+  PyObject *geobj;
+  if (!PyArg_ParseTuple(arg, "O!:putgeraw", ge_pytype, &geobj)) return (0);
+  if (ensurebuf(me, GE_G(geobj)->noctets)) return (0);
+  G_TORAW(GE_G(geobj), BUF_B(me), GE_X(geobj)); assert(BOK(BUF_B(me)));
+  RETURN_ME;
+}
+
+static PyObject *wbget_size(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(BLEN(BUF_B(me)))); }
+
+static PyObject *wbget_contents(PyObject *me, void *hunoz)
+  { return (bytestring_pywrap(BBASE(BUF_B(me)), BLEN(BUF_B(me)))); }
+
+static const PyGetSetDef wbuf_pygetset[] = {
+#define GETSETNAME(op, name) wb##op##_##name
+  GET  (size,          "WBUF.size -> SIZE")
+  GET  (contents,      "WBUF.contents -> STR")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef wbuf_pymethods[] = {
+#define METHNAME(func) wbmeth_##func
+  METH (zero,          "WBUF.zero(N)")
+  METH (put,           "WBUF.put(BYTES)")
+#define WBMETH_DECL_PUTU_(n, W, w)                                     \
+    METH(putu##w,      "WBUF.putu" #w "(INT)")
+  DOUINTCONV(WBMETH_DECL_PUTU_)
+#define WBMETH_DECL_PUTBLK_(n, W, w)                                   \
+    METH(putblk##w,    "WBUF.putblk" #w "(BYTES)")
+  BUF_DOSUFFIXES(WBMETH_DECL_PUTBLK_)
+  METH (putmp,         "WBUF.putmp(X)")
+  METH (putgf,         "WBUF.putgf(X)")
+  METH (putecpt,       "WBUF.putecpt(P)")
+  METH (putecptraw,    "WBUF.putecptraw(P)")
+  KWMETH(ec2osp,       "WBUF.ec2osp(P, [flags = EC_EXPLY])")
+  METH (putge,         "WBUF.putge(X)")
+  METH (putgeraw,      "WBUF.putgeraw(X)")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyBufferProcs wbuf_pybuffer = {
+#ifdef PY3
+  wbuf_pygetbuf,                       /* @bf_getbuffer@ */
+  wbuf_pyrlsbuf                                /* @bf_releasebuffer@ */
+#else
+  wbuf_pyreadbuf,                      /* @bf_getreadbuffer@ */
+  0,                                   /* @bf_getwritebuffer@ */
+  wbuf_pysegcount,                     /* @bf_getsegcount@ */
+  0                                    /* @bf_getcharbuffer@ */
+#endif
+};
+
+static const PyTypeObject wbuf_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "WriteBuffer",                       /* @tp_name@ */
+  sizeof(buf_pyobj),                   /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  buf_pydealloc,                       /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  PYBUFFER(wbuf),                      /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "WriteBuffer([size = ?]): a write buffer.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(wbuf),                     /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(wbuf),                      /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  wbuf_pynew,                          /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Initialization ----------------------------------------------------*/
+
+PyObject *buferr;
+
+void buffer_pyinit(void)
+{
+  INITTYPE(rbuf, root);
+  INITTYPE(wbuf, root);
+}
+
+void buffer_pyinsert(PyObject *mod)
+{
+  INSEXC("BufferError", buferr, PyExc_Exception, 0);
+  INSERT("ReadBuffer", rbuf_pytype);
+  INSERT("WriteBuffer", wbuf_pytype);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/bytestring.c b/bytestring.c
new file mode 100644 (file)
index 0000000..89f3e4a
--- /dev/null
@@ -0,0 +1,388 @@
+/* -*-c-*-
+ *
+ * Byte strings
+ *
+ * (c) 2004 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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "catacomb-python.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+static PyTypeObject *bytestring_pytype;
+
+static PyObject *empty, *bytev[256];
+
+static PyObject *allocate(PyTypeObject *ty, size_t n)
+{
+  BINOBJ *x;
+  x = (BINOBJ *)ty->tp_alloc(ty, n);
+  x->ob_sval[n] = 0;
+#if defined(CACHE_HASH) || PY_VERSION_HEX >= 0x02030000
+  x->ob_shash = -1;
+#endif
+#ifdef PY2
+  x->ob_sstate = SSTATE_NOT_INTERNED;
+#endif
+  return ((PyObject *)x);
+}
+
+static PyObject *dowrap(PyTypeObject *ty, const void *p, size_t n)
+{
+  PyObject *x;
+  int ch;
+
+  if (p && ty == bytestring_pytype) {
+    if (!n) {
+      if (!empty) empty = allocate(ty, 0);
+      Py_INCREF(empty); return (empty);
+    } else if (n == 1 && (ch = *(unsigned char *)p) < sizeof(bytev)) {
+      if (!bytev[ch])
+       { bytev[ch] = allocate(ty, 1); *BIN_PTR(bytev[ch]) = ch; }
+      Py_INCREF(bytev[ch]); return (bytev[ch]);
+    }
+  }
+
+  x = allocate(ty, n);
+  if (p) memcpy(BIN_PTR(x), p, n);
+  return (x);
+}
+
+PyObject *bytestring_pywrap(const void *p, size_t n)
+  { return (dowrap(bytestring_pytype, p, n)); }
+
+PyObject *bytestring_pywrapbuf(buf *b)
+  { return (dowrap(bytestring_pytype, BCUR(b), BLEFT(b))); }
+
+static PyObject *bytestring_pynew(PyTypeObject *ty,
+                                 PyObject *arg, PyObject *kw)
+{
+  struct bin in;
+  static const char *const kwlist[] = { "data", 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convbin, &in))
+    return (0);
+  return (dowrap(ty, in.p, in.sz));
+}
+
+static PyObject *meth_ctstreq(PyObject *me, PyObject *arg)
+{
+  struct bin s0, s1;
+  if (!PyArg_ParseTuple(arg, "O&O&:ctstreq", convbin, &s0 , convbin, &s1))
+    goto end;
+  if (s0.sz == s1.sz && ct_memeq(s0.p, s1.p, s0.sz)) RETURN_TRUE;
+  else RETURN_FALSE;
+end:
+  return (0);
+}
+
+static PyObject *bymeth_zero(PyObject *me, PyObject *arg)
+{
+  size_t sz;
+  PyObject *rc = 0;
+  if (!PyArg_ParseTuple(arg, "O&:zero", convszt, &sz)) goto end;
+  rc = bytestring_pywrap(0, sz);
+  memset(BIN_PTR(rc), 0, sz);
+end:
+  return (rc);
+}
+
+static PyObject *bytestring_pyrichcompare(PyObject *me,
+                                         PyObject *you, int op)
+{
+  struct bin s0, s1;
+  int b;
+  Py_ssize_t minlen;
+
+  s0.p = BIN_PTR(me); s0.sz = BIN_LEN(me);
+  if (!convbin(you, &s1)) { PyErr_Clear(); RETURN_NOTIMPL; }
+
+  switch (op) {
+    case Py_EQ:
+      b = s0.sz == s1.sz && ct_memeq(s0.p, s1.p, s1.sz);
+      break;
+    case Py_NE:
+      b = s0.sz != s1.sz || !ct_memeq(s0.p, s1.p, s1.sz);
+      break;
+    default:
+      minlen = s0.sz < s1.sz ? s0.sz : s1.sz;
+      b = memcmp(s0.p, s1.p, minlen);
+      if (!b) b = s0.sz < s1.sz ? -1 : s0.sz > s1.sz ? +1 : 0;
+      switch (op) {
+       case Py_LT: b = b <  0; break;
+       case Py_LE: b = b <= 0; break;
+       case Py_GE: b = b >= 0; break;
+       case Py_GT: b = b >  0; break;
+       default: abort();
+      }
+  }
+  if (b) RETURN_TRUE;
+  else RETURN_FALSE;
+}
+
+static PyObject *bytestring_pyconcat(PyObject *x, PyObject *y)
+{
+  struct bin xx, yy;
+  PyObject *z = 0; char *zp; size_t zsz;
+
+  if (!convbin(x, &xx) || !convbin(y, &yy)) goto end;
+  zsz = (size_t)xx.sz + (size_t)yy.sz;
+  if (xx.sz < 0 || yy.sz < 0 || zsz < xx.sz) VALERR("too long");
+  z = bytestring_pywrap(0, zsz); zp = BIN_PTR(z);
+  memcpy(zp, xx.p, xx.sz); memcpy(zp + xx.sz, yy.p, yy.sz);
+end:
+  return (z);
+}
+
+static PyObject *bytestring_pyrepeat(PyObject *me, Py_ssize_t n)
+{
+  const unsigned char *xp; size_t xsz;
+  PyObject *z = 0; char *zp; size_t zsz;
+
+  xp = (const unsigned char *)BIN_PTR(me);
+  xsz = BIN_LEN(me);
+  if (n < 0 || (n && xsz >= (size_t)-1/n)) VALERR("too long");
+  zsz = n*xsz; z = bytestring_pywrap(0, zsz); zp = BIN_PTR(z);
+  if (xsz == 1) memset(zp, *xp, zsz);
+  else while (zsz) { memcpy(zp, xp, xsz); zp += xsz; zsz -= xsz; }
+end:
+  return (z);
+}
+
+static PyObject *bytestring_pyitem(PyObject *me, Py_ssize_t i)
+{
+  PyObject *rc = 0;
+
+  if (i < 0 || i >= BIN_LEN(me)) IXERR("out of range");
+#ifdef PY3
+  rc = getulong(BIN_PTR(me)[i]&0xff);
+#else
+  rc = bytestring_pywrap(BIN_PTR(me) + i, 1);
+#endif
+end:
+  return (rc);
+}
+
+static PyObject *bytestring_pyslice(PyObject *me, Py_ssize_t i, Py_ssize_t j)
+{
+  PyObject *rc = 0;
+  size_t n = BIN_LEN(me);
+
+  if (i < 0) i = 0;
+  if (j < 0) j = 0;
+  else if (j > n) j = n;
+  if (j < i) i = j = 0;
+  if (i == 0 && j == n && Py_TYPE(me) == bytestring_pytype)
+    { Py_INCREF(me); rc = me; goto end; }
+  rc = bytestring_pywrap(BIN_PTR(me) + i, j - i);
+end:
+  return (rc);
+}
+
+static PyObject *bytestring_pysubscript(PyObject *me, PyObject *ix)
+{
+  Py_ssize_t i, j, k, n;
+  const unsigned char *p;
+  unsigned char *q;
+  PyObject *rc = 0;
+
+  if (PyIndex_Check(ix)) {
+    i = PyNumber_AsSsize_t(ix, PyExc_IndexError);
+    if (i == -1 && PyErr_Occurred()) return (0);
+    if (i < 0) i += BIN_LEN(me);
+    rc = bytestring_pyitem(me, i);
+  } else if (PySlice_Check(ix)) {
+    if (PySlice_GetIndicesEx(PY23((PySliceObject *), NOTHING)ix,
+                            BIN_LEN(me), &i, &j, &k, &n))
+      return (0);
+    if (k == 1) return bytestring_pyslice(me, i, j);
+    rc = bytestring_pywrap(0, n);
+    p = (unsigned char *)BIN_PTR(me) + i;
+    q = (unsigned char *)BIN_PTR(rc);
+    while (n--) { *q++ = *p; p += k; }
+  } else
+    TYERR("wanted integer or slice");
+end:
+  return (rc);
+}
+
+#define BINOP(name, op)                                                        \
+  static PyObject *bytestring_py##name(PyObject *x, PyObject *y) {     \
+    struct bin xx, yy;                                                 \
+    const unsigned char *xp, *yp;                                      \
+    unsigned char *zp;                                                 \
+    int i;                                                             \
+    PyObject *rc = 0;                                                  \
+    if (!convbin(x, &xx) || !convbin(y, &yy)) goto end;                        \
+    if (xx.sz != yy.sz) VALERR("length mismatch");                     \
+    rc = bytestring_pywrap(0, xx.sz);                                  \
+    xp = xx.p; yp = yy.p; zp = (unsigned char *)BIN_PTR(rc);           \
+    for (i = xx.sz; i > 0; i--) *zp++ = *xp++ op *yp++;                        \
+  end:                                                                 \
+    return (rc);                                                       \
+  }
+BINOP(and, &)
+BINOP(or, |)
+BINOP(xor, ^)
+
+#define UNOP(name, op)                                                 \
+  static PyObject *bytestring_py##name(PyObject *x) {                  \
+    struct bin xx;                                                     \
+    const unsigned char *xp;                                           \
+    unsigned char *zp;                                                 \
+    int i;                                                             \
+    PyObject *rc = 0;                                                  \
+    if (!convbin(x, &xx)) goto end;                                    \
+    rc = bytestring_pywrap(0, xx.sz);                                  \
+    xp = xx.p; zp = (unsigned char *)BIN_PTR(rc);                      \
+    for (i = xx.sz; i > 0; i--) *zp++ = op *xp++;                      \
+  end:                                                                 \
+    return (rc);                                                       \
+  }
+UNOP(not, ~)
+
+static const PyMethodDef bytestring_pymethods[] = {
+#define METHNAME(name) bymeth_##name
+  SMTH (zero,          "zero(N) -> 0000...00")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyNumberMethods bytestring_pynumber = {
+  0,                                   /* @nb_add@ */
+  0,                                   /* @nb_subtract@ */
+  0,                                   /* @nb_multiply@ */
+#ifdef PY2
+  0,                                   /* @nb_divide@ */
+#endif
+  0,                                   /* @nb_remainder@ */
+  0,                                   /* @nb_divmod@ */
+  0,                                   /* @nb_power@ */
+  0,                                   /* @nb_negative@ */
+  0,                                   /* @nb_positive@ */
+  0,                                   /* @nb_absolute@ */
+  0,                                   /* @nb_nonzero@ */
+  bytestring_pynot,                    /* @nb_invert@ */
+  0,                                   /* @nb_lshift@ */
+  0,                                   /* @nb_rshift@ */
+  bytestring_pyand,                    /* @nb_and@ */
+  bytestring_pyxor,                    /* @nb_xor@ */
+  bytestring_pyor,                     /* @nb_or@ */
+  0,                                   /* @nb_coerce@ */
+  0,                                   /* @nb_int@ */
+  0,                                   /* @nb_long@ */
+  0,                                   /* @nb_float@ */
+  0,                                   /* @nb_oct@ */
+  0,                                   /* @nb_hex@ */
+};
+
+static const PySequenceMethods bytestring_pysequence = {
+  0,                                   /* @sq_length@ */
+  bytestring_pyconcat,                 /* @sq_concat@ */
+  bytestring_pyrepeat,                 /* @sq_repeat@ */
+  bytestring_pyitem,                   /* @sq_item@ */
+  bytestring_pyslice,                  /* @sq_slice@ */
+  0,                                   /* @sq_ass_item@ */
+  0,                                   /* @sq_ass_slice@ */
+  0,                                   /* @sq_contains@ */
+  0,                                   /* @sq_inplace_concat@ */
+  0,                                   /* @sq_inplace_repeat@ */
+};
+
+static const PyMappingMethods bytestring_pymapping = {
+  0,                                   /* @mp_length@ */
+  bytestring_pysubscript,              /* @mp_subscript@ */
+  0,                                   /* @mp_ass_subscript@ */
+};
+
+static const PyTypeObject bytestring_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "ByteString",                                /* @tp_name@ */
+  0,                                   /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  PYNUMBER(bytestring),                        /* @tp_as_number@ */
+  PYSEQUENCE(bytestring),              /* @tp_as_sequence@ */
+  PYMAPPING(bytestring),               /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_CHECKTYPES |
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "ByteString(STR): byte string class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  bytestring_pyrichcompare,            /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(bytestring),               /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  bytestring_pynew,                    /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Initialization ----------------------------------------------------*/
+
+static const PyMethodDef methods[] = {
+#define METHNAME(func) meth_##func
+  METH (ctstreq,       "ctstreq(S, T) -> BOOL")
+#undef METHNAME
+  { 0 }
+};
+
+#define string_pytype &BIN_TYPE
+void bytestring_pyinit(void)
+{
+  INITTYPE(bytestring, string);
+  addmethods(methods);
+}
+
+void bytestring_pyinsert(PyObject *mod)
+{
+  INSERT("ByteString", bytestring_pytype);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/catacomb-python.h b/catacomb-python.h
new file mode 100644 (file)
index 0000000..6128cc8
--- /dev/null
@@ -0,0 +1,401 @@
+/* -*-c-*-
+ *
+ * Definitions for Catacomb bindings
+ *
+ * (c) 2004 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.
+ */
+
+#ifndef CATACOMB_PYTHON_H
+#define CATACOMB_PYTHON_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "pyke/pyke-mLib.h"
+
+PUBLIC_SYMBOLS;
+#include <longintrepr.h>
+
+#include <mLib/dstr.h>
+#include <mLib/macros.h>
+#include <mLib/quis.h>
+#include <mLib/unihash.h>
+
+#include <catacomb/buf.h>
+#include <catacomb/ct.h>
+
+#include <catacomb/grand.h>
+#include <catacomb/rand.h>
+#include <catacomb/noise.h>
+#include <catacomb/bbs.h>
+#include <catacomb/mprand.h>
+#include <catacomb/lcrand.h>
+#include <catacomb/fibrand.h>
+#include <catacomb/dsarand.h>
+#include <catacomb/sslprf.h>
+#include <catacomb/tlsprf.h>
+#include <catacomb/blkc.h>
+
+#include <catacomb/gcipher.h>
+#include <catacomb/gaead.h>
+#include <catacomb/ghash.h>
+#include <catacomb/gmac.h>
+#include <catacomb/md5.h>
+#include <catacomb/md5-hmac.h>
+#include <catacomb/poly1305.h>
+#include <catacomb/sha.h>
+#include <catacomb/sha-mgf.h>
+#include <catacomb/sha-hmac.h>
+#include <catacomb/keccak1600.h>
+#include <catacomb/sha3.h>
+
+#include <catacomb/mp.h>
+#include <catacomb/mpint.h>
+#include <catacomb/mpmul.h>
+#include <catacomb/mpcrt.h>
+#include <catacomb/mpmont.h>
+#include <catacomb/mpbarrett.h>
+#include <catacomb/mpreduce.h>
+#include <catacomb/mp-fibonacci.h>
+
+#include <catacomb/pgen.h>
+#include <catacomb/primeiter.h>
+#include <catacomb/pfilt.h>
+#include <catacomb/strongprime.h>
+#include <catacomb/limlee.h>
+#include <catacomb/dh.h>
+#include <catacomb/ptab.h>
+#include <catacomb/bintab.h>
+#include <catacomb/dsa.h>
+#include <catacomb/x25519.h>
+#include <catacomb/x448.h>
+#include <catacomb/ed25519.h>
+#include <catacomb/ed448.h>
+
+#include <catacomb/gf.h>
+#include <catacomb/gfreduce.h>
+#include <catacomb/gfn.h>
+
+#include <catacomb/field.h>
+#include <catacomb/field-guts.h>
+
+#include <catacomb/ec.h>
+#include <catacomb/ec-raw.h>
+#include <catacomb/ectab.h>
+
+#include <catacomb/group.h>
+#include <catacomb/group-guts.h>
+
+#include <catacomb/gdsa.h>
+#include <catacomb/gkcdsa.h>
+#include <catacomb/rsa.h>
+
+#include <catacomb/key.h>
+#include <catacomb/passphrase.h>
+#include <catacomb/pixie.h>
+
+#include <catacomb/share.h>
+#include <catacomb/gfshare.h>
+PRIVATE_SYMBOLS;
+
+/*----- Miscellaneous preliminaries ---------------------------------------*/
+
+/* Submodules. */
+#define MODULES(_)                                                     \
+  _(pyke_core) _(pyke_gmap)                                            \
+  _(bytestring) _(buffer)                                              \
+  _(rand) _(algorithms) _(pubkey) _(pgen)                              \
+  _(mp) _(field) _(ec) _(group)                                                \
+  _(passphrase) _(share) _(key)
+MODULES(DECLARE_MODINIT)
+
+/* Exceptions. */
+#define PGENERR(err) do { pgenerr(err); goto end; } while (0)
+
+/* Conversions. */
+extern int convmpw(PyObject *, void *);
+
+/* Building tables of things. */
+extern PyObject *make_algtab(const void *tab, size_t esz,
+                            const char *(*namefn)(const void *),
+                            PyObject *(*valfn)(const void *));
+extern PyObject *make_grouptab(const void *tab, size_t esz,
+                              const char *(*namefn)(const void *),
+                              int (*ixfn)(const void *),
+                              PyObject *(*valfn)(int));
+
+/* Common handling for simultaneous exponentiation. */
+extern PyObject *mexp_common(PyObject *, PyObject *, size_t,
+                            PyObject *(*id)(PyObject *),
+                            int (*fill)(void *, PyObject *,
+                                        PyObject *, PyObject *),
+                            PyObject *(*exp)(PyObject *, void *, size_t),
+                            void (*drop)(void *));
+
+/*----- Bytestrings -------------------------------------------------------*/
+
+PyObject *bytestring_pywrap(const void *, size_t);
+PyObject *bytestring_pywrapbuf(buf *);
+
+/*----- Buffers -----------------------------------------------------------*/
+
+typedef struct buf_pyobj {
+  PyObject_HEAD
+  buf b;
+  PyObject *sub;
+  unsigned lk;
+} buf_pyobj;
+
+extern PyTypeObject *rbuf_pytype, *wbuf_pytype;
+#define RBUF_PYCHECK(o) PyObject_TypeCheck((o), rbuf_pytype)
+#define WBUF_PYCHECK(o) PyObject_TypeCheck((o), wbuf_pytype)
+#define BUF_B(o) (&((buf_pyobj *)(o))->b)
+#define BUF_SUB(o) (((buf_pyobj *)(o))->sub)
+#define BUF_LK(o) (((buf_pyobj *)(o))->lk)
+
+extern PyObject *buferr;
+#define BUFERR(str) do { PyErr_SetString(buferr, str); goto end; } while (0)
+
+extern int ensurebuf(PyObject *, size_t);
+
+/*----- Multiprecision arithmetic -----------------------------------------*/
+
+typedef struct mp_pyobj {
+  PyObject_HEAD
+  mp *x;
+} mp_pyobj;
+
+extern PyTypeObject *mp_pytype;
+extern PyTypeObject *gf_pytype;
+#define MP_X(o) (((mp_pyobj *)(o))->x)
+#define MP_PYCHECK(o) PyObject_TypeCheck((o), mp_pytype)
+#define GF_PYCHECK(o) PyObject_TypeCheck((o), gf_pytype)
+
+extern mp *mp_frompylong(PyObject *);
+extern PyObject *mp_topylong(mp *);
+extern mp *tomp(PyObject *);
+extern mp *implicitmp(PyObject *);
+extern mp *getmp(PyObject *);
+extern int convmp(PyObject *, void *);
+extern mp *implicitgf(PyObject *);
+extern mp *getgf(PyObject *);
+extern int convgf(PyObject *, void *);
+extern PyObject *mp_pywrap(mp *);
+extern PyObject *gf_pywrap(mp *);
+extern Py_hash_t mphash(mp *);
+extern mp *mp_frompyobject(PyObject *, int);
+extern PyObject *mp_topystring(mp *, int,
+                              const char *, const char *, const char *);
+extern int mp_tolong_checked(mp *, long *, int);
+
+/*----- Abstract fields ---------------------------------------------------*/
+
+typedef struct field_pyobj {
+  PyHeapTypeObject ty;
+  field *f;
+} field_pyobj;
+
+extern PyTypeObject *field_pytype;
+extern PyTypeObject *primefield_pytype;
+extern PyTypeObject *niceprimefield_pytype;
+extern PyTypeObject *binfield_pytype;
+extern PyTypeObject *binpolyfield_pytype;
+extern PyTypeObject *binnormfield_pytype;
+#define FIELD_PYCHECK(o) PyObject_TypeCheck((o), field_pytype)
+#define FIELD_F(o) (((field_pyobj *)(o))->f)
+extern PyObject *field_pywrap(field *);
+extern field *field_copy(field *);
+
+typedef struct fe_pyobj {
+  PyObject_HEAD
+  field *f;
+  mp *x;
+} fe_pyobj;
+
+extern PyTypeObject *fe_pytype;
+#define FE_PYCHECK(o) PyObject_TypeCheck((o), fe_pytype)
+#define FE_F(o) (((fe_pyobj *)(o))->f)
+#define FE_FOBJ(o) ((PyObject *)Py_TYPE(o))
+#define FE_X(o) (((fe_pyobj *)(o))->x)
+extern PyObject *fe_pywrap(PyObject *, mp *);
+
+/*----- Elliptic curves ---------------------------------------------------*/
+
+typedef struct eccurve_pyobj {
+  PyHeapTypeObject ty;
+  ec_curve *c;
+  PyObject *fobj;
+} eccurve_pyobj;
+
+extern PyTypeObject *eccurve_pytype;
+extern PyTypeObject *ecprimecurve_pytype;
+extern PyTypeObject *ecprimeprojcurve_pytype;
+extern PyTypeObject *ecbincurve_pytype;
+extern PyTypeObject *ecbinprojcurve_pytype;
+#define ECCURVE_PYCHECK(o) PyObject_TypeCheck((o), eccurve_pytype)
+#define ECCURVE_C(o) (((eccurve_pyobj *)(o))->c)
+#define ECCURVE_FOBJ(o) (((eccurve_pyobj *)(o))->fobj)
+extern PyObject *eccurve_pywrap(PyObject *, ec_curve *);
+extern ec_curve *eccurve_copy(ec_curve *);
+
+typedef struct ecpt_pyobj {
+  PyObject_HEAD
+  ec_curve *c;
+  ec p;
+} ecpt_pyobj;
+
+extern PyTypeObject *ecpt_pytype, *ecptcurve_pytype;
+#define ECPT_PYCHECK(o) PyObject_TypeCheck((o), ecpt_pytype)
+#define ECPTCURVE_PYCHECK(o) PyObject_TypeCheck((o), ecptcurve_pytype)
+#define ECPT_C(o) (((ecpt_pyobj *)(o))->c)
+#define ECPT_COBJ(o) ((PyObject *)Py_TYPE(o))
+#define ECPT_FOBJ(o) ECCURVE_FOBJ(ECPT_COBJ((o)))
+#define ECPT_P(o) (&((ecpt_pyobj *)(o))->p)
+extern PyObject *ecpt_pywrap(PyObject *, ec *);
+extern PyObject *ecpt_pywrapout(void *, ec *);
+extern int toecpt(ec_curve *, ec *, PyObject *);
+extern int getecpt(ec_curve *, ec *, PyObject *);
+extern void getecptout(ec *, PyObject *);
+extern int convecpt(PyObject *, void *);
+
+typedef struct ecinfo_pyobj {
+  PyObject_HEAD
+  ec_info ei;
+  PyObject *cobj;
+} ecinfo_pyobj;
+
+extern PyTypeObject *ecinfo_pytype;
+#define ECINFO_PYCHECK(o) PyObject_TypeCheck((o), ecinfo_pytype)
+#define ECINFO_EI(o) (&((ecinfo_pyobj *)(o))->ei)
+#define ECINFO_COBJ(o) (((ecinfo_pyobj *)(o))->cobj)
+extern void ecinfo_copy(ec_info *, const ec_info *);
+extern PyObject *ecinfo_pywrap(ec_info *);
+
+/*----- Cyclic groups -----------------------------------------------------*/
+
+typedef struct ge_pyobj {
+  PyObject_HEAD
+  ge *x;
+  group *g;
+} ge_pyobj;
+
+extern PyTypeObject *ge_pytype;
+#define GE_PYCHECK(o) PyObject_TypeCheck((o), ge_pytype)
+#define GE_X(o) (((ge_pyobj *)(o))->x)
+#define GE_G(o) (((ge_pyobj *)(o))->g)
+#define GE_GOBJ(o) (PyObject *)(Py_TYPE(o))
+extern PyObject *ge_pywrap(PyObject *, ge *);
+
+typedef struct group_pyobj {
+  PyHeapTypeObject ty;
+  group *g;
+} group_pyobj;
+
+extern PyTypeObject *group_pytype;
+#define GROUP_G(o) (((group_pyobj *)(o))->g)
+extern PyObject *group_pywrap(group *);
+extern group *group_copy(group *);
+
+/*----- Random number generators ------------------------------------------*/
+
+#define f_freeme 1u
+
+typedef struct grand_pyobj {
+  PyObject_HEAD
+  unsigned f;
+  grand *r;
+} grand_pyobj;
+
+extern PyTypeObject *grand_pytype;
+extern PyObject *rand_pyobj;
+#define GRAND_PYCHECK(o) PyObject_TypeCheck((o), grand_pytype)
+#define GRAND_F(o) (((grand_pyobj *)(o))->f)
+#define GRAND_R(o) (((grand_pyobj *)(o))->r)
+extern PyObject *grand_pywrap(grand *, unsigned);
+extern int convgrand(PyObject *, void *);
+
+/*----- Symmetric cryptography --------------------------------------------*/
+
+extern PyObject *keysz_pywrap(const octet *);
+
+extern int convgccipher(PyObject *, void *);
+extern PyObject *gccipher_pywrap(gccipher *);
+
+typedef struct gchash_pyobj {
+  PyHeapTypeObject ty;
+  gchash *ch;
+} gchash_pyobj;
+
+extern PyTypeObject *gchash_pytype;
+extern PyObject *sha_pyobj, *has160_pyobj;
+#define GCHASH_PYCHECK(o) PyObject_TypeCheck((o), gchash_pytype)
+#define GCHASH_CH(o) (((gchash_pyobj *)(o))->ch)
+extern PyObject *ghash_pywrap(PyObject *, ghash *);
+extern int convgchash(PyObject *, void *);
+extern int convghash(PyObject *, void *);
+
+extern int convgcmac(PyObject *, void *);
+
+/*----- Key generation ----------------------------------------------------*/
+
+typedef struct pfilt_pyobj {
+  PyObject_HEAD
+  pfilt f;
+  int st;
+} pfilt_pyobj;
+
+extern PyTypeObject *pfilt_pytype;
+#define PFILT_PYCHECK(o) PyObject_TypeCheck(o, pfilt_pytype)
+#define PFILT_F(o) (&((pfilt_pyobj *)(o))->f)
+#define PFILT_ST(o) (((pfilt_pyobj *)(o))->st)
+
+typedef struct { pgen_proc *proc; void *ctx; } pgev;
+#define PGEV_HEAD PyObject_HEAD pgev pg;
+
+typedef struct pgev_pyobj {
+  PGEV_HEAD
+} pgev_pyobj;
+
+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(pypgev *);
+extern void pgenerr(struct excinfo *exc);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/catacomb.c b/catacomb.c
new file mode 100644 (file)
index 0000000..23906eb
--- /dev/null
@@ -0,0 +1,369 @@
+/* -*-c-*-
+ *
+ * Where the fun begins
+ *
+ * (c) 2004 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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "catacomb-python.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+PyObject *mexp_common(PyObject *me, PyObject *arg,
+                     size_t efsz,
+                     PyObject *(*id)(PyObject *),
+                     int (*fill)(void *, PyObject *,
+                                 PyObject *, PyObject *),
+                     PyObject *(*exp)(PyObject *, void *, size_t),
+                     void (*drop)(void *))
+{
+  size_t i = 0, o, n;
+  int flat;
+  PyObject *qq = 0, *x = 0, *y = 0, *z = 0, *it = 0;
+  char *v = 0;
+
+  if (PyTuple_Size(arg) == 1) arg = PyTuple_GET_ITEM(arg, 0);
+  it = PyObject_GetIter(arg); if (!it) goto end;
+  qq = PyIter_Next(it);
+  if (!qq) {
+    if (!PyErr_Occurred()) z = id(me);
+    else goto end;
+  }
+  flat = !PySequence_Check(qq);
+  if (!PySequence_Check(arg))
+    n = 16;
+  else {
+    n = PySequence_Size(arg);
+    if (n == (size_t)-1 && PyErr_Occurred()) goto end;
+    if (flat) n /= 2;
+    if (!n) n = 16;
+  }
+
+  v = xmalloc(n*efsz);
+  o = 0;
+  for (;;) {
+    if (!flat) {
+      if (!PySequence_Check(qq) || PySequence_Size(qq) != 2)
+       TYERR("want a sequence of pairs");
+      x = PySequence_GetItem(qq, 0);
+      y = PySequence_GetItem(qq, 1);
+    } else {
+      x = qq; qq = 0;
+      y = PyIter_Next(it);
+      if (!y) {
+       if (PyErr_Occurred()) goto end;
+       VALERR("must have even number of operands");
+      }
+    }
+    if (!x || !y) goto end;
+
+    if (i >= n) { n *= 2; v = xrealloc(v, n*efsz, i*efsz); }
+    if (fill(v + o, me, x, y)) {
+      if (PyErr_Occurred()) goto end;
+      TYERR("type mismatch");
+    }
+    i++; o += efsz;
+    Py_DECREF(x); x = 0;
+    Py_DECREF(y); y = 0;
+    Py_XDECREF(qq);
+
+    qq = PyIter_Next(it);
+    if (!qq) {
+      if (PyErr_Occurred()) goto end;
+      else break;
+    }
+  }
+
+  z = exp(me, v, i);
+
+end:
+  while (i--) { o -= efsz; drop(v + o); }
+  xfree(v);
+  Py_XDECREF(it); Py_XDECREF(qq); Py_XDECREF(x); Py_XDECREF(y);
+  return (z);
+}
+
+int convmpw(PyObject *o, void *pp)
+{
+  unsigned long u;
+  unsigned *p = pp;
+
+  if (!convulong(o, &u)) goto end;
+  if (u > MPW_MAX) VALERR("out of range");
+  *p = u;
+  return (1);
+end:
+  return (0);
+}
+
+static PyTypeObject *thingtab_pytype;
+
+typedef struct thingentry {
+  sym_base _b;
+  PyObject *val;
+} thingentry;
+#define THING_VAL(x) (((thingentry *)(x))->val)
+
+typedef struct thingtab_pyobj {
+  GMAP_PYOBJ_HEAD
+  sym_table t;
+} thingtab_pyobj;
+#define THINGTAB_T(x) (&((thingtab_pyobj *)(x))->t)
+
+static void *thingtab_gmlookup(PyObject *me, PyObject *key, unsigned *f)
+{
+  const char *p;
+
+  p = TEXT_STR(key); if (!p) return (0);
+  return (sym_find(THINGTAB_T(me), p, -1, 0, f));
+}
+
+static void thingtab_gmiterinit(PyObject *me, void *i)
+  { sym_mkiter(i, THINGTAB_T(me)); }
+
+static void *thingtab_gmiternext(PyObject *me, void *i)
+  { sym_iter *it = i; void *e; SYM_NEXT(it, e); return (e); }
+
+static PyObject *thingtab_gmentrykey(PyObject *me, void *e)
+  { return (TEXT_FROMSTR(SYM_NAME(e))); }
+
+static PyObject *thingtab_gmentryvalue(PyObject *me, void *e)
+  { PyObject *rc = THING_VAL(e); RETURN_OBJ(rc); }
+
+static const gmap_ops thingtab_gmops = {
+  sizeof(sym_iter),
+  thingtab_gmlookup,
+  thingtab_gmiterinit,
+  thingtab_gmiternext,
+  thingtab_gmentrykey,
+  thingtab_gmentryvalue
+};
+
+static Py_ssize_t thing_pysize(PyObject *me)
+  { return gmap_pysize_from_sym(THINGTAB_T(me)); }
+
+static const PyMappingMethods thingtab_pymapping = {
+  thing_pysize,
+  gmap_pylookup,
+  0
+};
+
+static thingtab_pyobj *make_thingtab(void)
+{
+  thingtab_pyobj *map = PyObject_NEW(thingtab_pyobj, thingtab_pytype);
+
+  map->gmops = &thingtab_gmops;
+  sym_create(&map->t);
+  return (map);
+}
+
+PyObject *make_algtab(const void *tab, size_t esz,
+                     const char *(*namefn)(const void *),
+                     PyObject *(*valfn)(const void *))
+{
+  thingtab_pyobj *map = make_thingtab();
+  const char *p = tab;
+  const char *name;
+  thingentry *e;
+  unsigned f;
+
+  for (;;) {
+    name = namefn(p); if (!name) break;
+    e = sym_find(&map->t, name, -1, sizeof(*e), &f); assert(!f);
+    e->val = valfn(p);
+    p += esz;
+  }
+  return ((PyObject *)map);
+}
+
+PyObject *make_grouptab(const void *tab, size_t esz,
+                       const char *(*namefn)(const void *),
+                       int (*ixfn)(const void *), PyObject *(*valfn)(int))
+{
+  thingtab_pyobj *map = make_thingtab();
+  struct { const char *name; int ix; } *ixtab = 0;
+  PyObject **valtab, **vv;
+  size_t i = 0, n = 0;
+  const char *p = tab;
+  const char *name;
+  thingentry *e;
+  unsigned f;
+
+  for (;;) {
+    name = namefn(p); if (!name) break;
+    if (i >= n) {
+      if (!n) n = 16;
+      else n *= 2;
+      ixtab = xrealloc(ixtab, n*sizeof(*ixtab), i*sizeof(*ixtab));
+    }
+    ixtab[i].name = name; ixtab[i].ix = ixfn(p); assert(ixtab[i].ix >= 0);
+    p += esz; i++;
+  }
+  n = i;
+
+  valtab = xmalloc(n*sizeof(*valtab));
+  for (i = 0; i < n; i++) valtab[i] = 0;
+
+  for (i = 0; i < n; i++) {
+    e = sym_find(&map->t, ixtab[i].name, -1, sizeof(*e), &f); assert(!f);
+    vv = &valtab[ixtab[i].ix];
+    if (*vv) Py_INCREF(*vv);
+    else *vv = valfn(ixtab[i].ix);
+    e->val = *vv;
+  }
+
+  xfree(ixtab); xfree(valtab);
+  return ((PyObject *)map);
+}
+
+static const PyTypeObject thingtab_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "_MiscTable",                                /* @tp_name@ */
+  sizeof(thingtab_pyobj),              /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  PYSEQUENCE(gmap),                    /* @tp_as_sequence@ */
+  PYMAPPING(thingtab),                 /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Class for tables of algorithms and abstract-group data.\n"
+  "  Not instantiable by users.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  gmap_pyiter,                         /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gmapro),                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *smallprimes(void)
+{
+  PyObject *v = PyList_New(NPRIME);
+  int i;
+
+  for (i = 0; i < NPRIME; i++)
+    PyList_SET_ITEM(v, i, PyInt_FromLong(primetab[i]));
+  return (v);
+}
+
+static PyObject *meth__ego(PyObject *me, PyObject *arg)
+{
+  char *argv0;
+  if (!PyArg_ParseTuple(arg, "s:_ego", &argv0))
+    return (0);
+  if (STRCMP(QUIS, ==, "<UNNAMED>"))
+    ego(argv0);
+  RETURN_NONE;
+}
+
+static const PyMethodDef methods[] = {
+#define METHNAME(func) meth_##func
+  METH (_ego,          "_ego(ARGV0)")
+#undef METHNAME
+  { 0 }
+};
+
+static void init_random(void)
+{
+#if PY_VERSION_HEX >= 0x02060000
+  char *seed;
+  uint32 r;
+
+  if (!Py_HashRandomizationFlag) return;
+  seed = getenv("PYTHONHASHSEED");
+  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
+}
+
+#ifdef PY3
+static PyModuleDef moddef = {
+  PyModuleDef_HEAD_INIT,
+  "catacomb._base",                    /* @m_name@ */
+  "Low-level module for Catacomb bindings.  Use `catacomb' instead.",
+                                       /* @m_doc@ */
+  0,                                   /* @m_size@ */
+  0,                                   /* @m_methods@ */
+  0,                                   /* @m_slots@ */
+  0,                                   /* @m_traverse@ */
+  0,                                   /* @m_clear@ */
+  0                                    /* @m_free@ */
+};
+#endif
+
+EXPORT PyMODINIT_FUNC PY23(init_base, PyInit__base)(void)
+{
+  PyObject *mod;
+
+  modname = TEXT_FROMSTR("catacomb");
+  addmethods(methods);
+  INIT_MODULES;
+  INITTYPE(thingtab, root);
+  init_random();
+#ifdef PY3
+  moddef.m_methods = donemethods();
+  mod = PyModule_Create(&moddef);
+#else
+  mod = Py_InitModule("catacomb._base", donemethods());
+#endif
+  INSERT_MODULES;
+  INSERT("_MiscTable", thingtab_pytype);
+  INSERT("smallprimes", smallprimes());
+#ifdef PY3
+  return (mod);
+#endif
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/catacomb/__init__.py b/catacomb/__init__.py
new file mode 100644 (file)
index 0000000..2b433cf
--- /dev/null
@@ -0,0 +1,1204 @@
+### -*-python-*-
+###
+### Setup for Catacomb/Python bindings
+###
+### (c) 2004 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.
+
+from __future__ import with_statement
+
+from binascii import hexlify as _hexify, unhexlify as _unhexify
+from contextlib import contextmanager as _ctxmgr
+try: import DLFCN as _dlfcn
+except ImportError: _dlfcn = None
+import os as _os
+from struct import pack as _pack
+import sys as _sys
+import types as _types
+
+###--------------------------------------------------------------------------
+### Import the main C extension module.
+
+try:
+  _dlflags = _odlflags = _sys.getdlopenflags()
+except AttributeError:
+  _dlflags = _odlflags = -1
+
+## Set the `deep binding' flag.  Python has its own different MD5
+## implementation, and some distributions export `md5_init' and friends so
+## they override our versions, which doesn't end well.  Figure out how to
+## turn this flag on so we don't have the problem.
+if _dlflags >= 0:
+  try: _dlflags |= _dlfcn.RTLD_DEEPBIND
+  except AttributeError:
+    try: _dlflags |= _os.RTLD_DEEPBIND
+    except AttributeError:
+      if _os.uname()[0] == 'Linux': _dlflags |= 8 # magic knowledge
+      else: pass # can't do this.
+  _sys.setdlopenflags(_dlflags)
+
+if _sys.version_info >= (3,): from . import _base
+else: import _base
+
+if _odlflags >= 0:
+  _sys.setdlopenflags(_odlflags)
+
+del _dlflags, _odlflags
+
+###--------------------------------------------------------------------------
+### Basic stuff.
+
+## For the benefit of the default keyreporter, we need the program name.
+_base._ego(_sys.argv[0])
+
+## Register our module.
+_base._set_home_module(_sys.modules[__name__])
+def default_lostexchook(why, ty, val, tb):
+  """`catacomb.lostexchook(WHY, TY, VAL, TB)' reports lost exceptions."""
+  _sys.stderr.write("\n\n!!! LOST EXCEPTION: %s\n" % why)
+  _sys.excepthook(ty, val, tb)
+  _sys.stderr.write("\n")
+lostexchook = default_lostexchook
+
+## Text/binary conversions.
+if _sys.version_info >= (3,):
+  def _bin(s): return s.encode('iso8859-1')
+else:
+  def _bin(s): return s
+
+## Iterating over dictionaries.
+if _sys.version_info >= (3,):
+  def _iteritems(dict): return dict.items()
+  def _itervalues(dict): return dict.values()
+else:
+  def _iteritems(dict): return dict.iteritems()
+  def _itervalues(dict): return dict.itervalues()
+
+## The built-in bignum type.
+try: long
+except NameError: _long = int
+else: _long = long
+
+## How to fix a name back into the right identifier.  Alas, the rules are not
+## consistent.
+def _fixname(name):
+
+  ## Hyphens consistently become underscores.
+  name = name.replace('-', '_')
+
+  ## But slashes might become underscores or just vanish.
+  if name.startswith('salsa20'): name = name.replace('/', '')
+  else: name = name.replace('/', '_')
+
+  ## Done.
+  return name
+
+## Initialize the module.  Drag in the static methods of the various
+## classes; create names for the various known crypto algorithms.
+def _init():
+  d = globals()
+  b = _base.__dict__;
+  for i in b:
+    if i[0] != '_':
+      d[i] = b[i];
+  for i in [gcciphers, gcaeads, gchashes, gcmacs, gcprps]:
+    for c in _itervalues(i):
+      d[_fixname(c.name)] = c
+  for c in _itervalues(gccrands):
+    d[_fixname(c.name + 'rand')] = c
+_init()
+
+## A handy function for our work: add the methods of a named class to an
+## existing class.  This is how we write the Python-implemented parts of our
+## mostly-C types.
+def _augment(c, cc):
+  for i in cc.__dict__:
+    a = cc.__dict__[i]
+    if type(a) is _types.MethodType:
+      a = a.im_func
+    elif type(a) not in (_types.FunctionType, staticmethod, classmethod):
+      continue
+    setattr(c, i, a)
+
+## Parsing functions tend to return the object parsed and the remainder of
+## the input.  This checks that the remainder is input and, if so, returns
+## just the object.
+def _checkend(r):
+  x, rest = r
+  if rest != '':
+    raise SyntaxError('junk at end of string')
+  return x
+
+## Some pretty-printing utilities.
+PRINT_SECRETS = False
+def _clsname(me): return type(me).__name__
+def _repr_secret(thing, secretp = True):
+  if not secretp or PRINT_SECRETS: return repr(thing)
+  else: return '#<SECRET>'
+def _pp_str(me, pp, cyclep): pp.text(cyclep and '...' or str(me))
+def _pp_secret(pp, thing, secretp = True):
+  if not secretp or PRINT_SECRETS: pp.pretty(thing)
+  else: pp.text('#<SECRET>')
+def _pp_bgroup(pp, text):
+  ind = len(text)
+  pp.begin_group(ind, text)
+  return ind
+def _pp_bgroup_tyname(pp, obj, open = '('):
+  return _pp_bgroup(pp, _clsname(obj) + open)
+def _pp_kv(pp, k, v, secretp = False):
+  ind = _pp_bgroup(pp, k + ' = ')
+  _pp_secret(pp, v, secretp)
+  pp.end_group(ind, '')
+def _pp_commas(pp, printfn, items):
+  firstp = True
+  for i in items:
+    if firstp: firstp = False
+    else: pp.text(','); pp.breakable()
+    printfn(i)
+def _pp_dict(pp, items):
+  def p(kv):
+    k, v = kv
+    pp.begin_group(0)
+    pp.pretty(k)
+    pp.text(':')
+    pp.begin_group(2)
+    pp.breakable()
+    pp.pretty(v)
+    pp.end_group(2)
+    pp.end_group(0)
+  _pp_commas(pp, p, items)
+
+###--------------------------------------------------------------------------
+### Mappings.
+
+if _sys.version_info >= (3,):
+  class _tmp:
+    def __str__(me): return '%s(%r)' % (type(me).__name__, list(me))
+    __repr__ = __str__
+    def _repr_pretty_(me, pp, cyclep):
+      ind = _pp_bgroup_tyname(pp, me, '([')
+      _pp_commas(pp, pp.pretty, me)
+      pp.end_group(ind, '])')
+  _augment(_base._KeyView, _tmp)
+  _augment(_base._ValueView, _tmp)
+  _augment(_base._ItemView, _tmp)
+
+###--------------------------------------------------------------------------
+### Bytestrings.
+
+class _tmp:
+  def fromhex(x):
+    return ByteString(_unhexify(x))
+  fromhex = staticmethod(fromhex)
+  if _sys.version_info >= (3,):
+    def hex(me): return _hexify(me).decode()
+  else:
+    def hex(me): return _hexify(me)
+    __hex__ = hex
+  def __repr__(me):
+    return 'bytes(%r)' % me.hex()
+_augment(ByteString, _tmp)
+ByteString.__hash__ = str.__hash__
+bytes = ByteString.fromhex
+
+###--------------------------------------------------------------------------
+### Symmetric encryption.
+
+class _tmp:
+  def encrypt(me, n, m, tsz = None, h = ByteString.zero(0)):
+    if tsz is None: tsz = me.__class__.tagsz.default
+    e = me.enc(n, len(h), len(m), tsz)
+    if not len(h): a = None
+    else: a = e.aad().hash(h)
+    c0 = e.encrypt(m)
+    c1, t = e.done(aad = a)
+    return c0 + c1, t
+  def decrypt(me, n, c, t, h = ByteString.zero(0)):
+    d = me.dec(n, len(h), len(c), len(t))
+    if not len(h): a = None
+    else: a = d.aad().hash(h)
+    m = d.decrypt(c)
+    m += d.done(t, aad = a)
+    return m
+_augment(GAEKey, _tmp)
+
+###--------------------------------------------------------------------------
+### Hashing.
+
+class _tmp:
+  def check(me, h):
+    hh = me.done()
+    return ctstreq(h, hh)
+_augment(GHash, _tmp)
+_augment(Poly1305Hash, _tmp)
+
+class _tmp:
+  def check(me, h):
+    return ctstreq(h, me.done(len(h)))
+_augment(Shake, _tmp)
+
+KMAC128.keysz = KeySZAny(16); KMAC128.tagsz = 16
+KMAC256.keysz = KeySZAny(32); KMAC256.tagsz = 32
+
+###--------------------------------------------------------------------------
+### NaCl `secretbox'.
+
+def secret_box(k, n, m):
+  y, t = salsa20_naclbox(k).encrypt(n, m)
+  return t + y
+
+def secret_unbox(k, n, c):
+  tsz = poly1305.tagsz
+  return salsa20_naclbox(k).decrypt(n, c[tsz:], c[0:tsz])
+
+###--------------------------------------------------------------------------
+### Multiprecision integers and binary polynomials.
+
+class BaseRat (object):
+  """Base class implementing fields of fractions over Euclidean domains."""
+  def __new__(cls, a, b):
+    a, b = cls.RING._implicit(a), cls.RING._implicit(b)
+    q, r = divmod(a, b)
+    if r == cls.ZERO: return q
+    g = b.gcd(r)
+    me = super(BaseRat, cls).__new__(cls)
+    me._n = a//g
+    me._d = b//g
+    return me
+  @property
+  def numer(me): return me._n
+  @property
+  def denom(me): return me._d
+  def __str__(me): return '%s/%s' % (me._n, me._d)
+  def __repr__(me): return '%s(%s, %s)' % (_clsname(me), me._n, me._d)
+  _repr_pretty_ = _pp_str
+
+  def _split_rat(me, x):
+    if isinstance(x, me.__class__): return x._n, x._d
+    else: return x, me.ONE
+  def __add__(me, you):
+    n, d = me._split_rat(you)
+    return type(me)(me._n*d + n*me._d, d*me._d)
+  __radd__ = __add__
+  def __sub__(me, you):
+    n, d = me._split_rat(you)
+    return type(me)(me._n*d - n*me._d, d*me._d)
+  def __rsub__(me, you):
+    n, d = me._split_rat(you)
+    return type(me)(n*me._d - me._n*d, d*me._d)
+  def __mul__(me, you):
+    n, d = me._split_rat(you)
+    return type(me)(me._n*n, me._d*d)
+  __rmul__ = __mul__
+  def __truediv__(me, you):
+    n, d = me._split_rat(you)
+    return type(me)(me._n*d, me._d*n)
+  def __rtruediv__(me, you):
+    n, d = me._split_rat(you)
+    return type(me)(me._d*n, me._n*d)
+  if _sys.version_info < (3,):
+    __div__ = __truediv__
+    __rdiv__ = __rtruediv__
+  def _order(me, you, op):
+    n, d = me._split_rat(you)
+    return op(me._n*d, n*me._d)
+  def __eq__(me, you): return me._order(you, lambda x, y: x == y)
+  def __ne__(me, you): return me._order(you, lambda x, y: x != y)
+  def __le__(me, you): return me._order(you, lambda x, y: x <= y)
+  def __lt__(me, you): return me._order(you, lambda x, y: x <  y)
+  def __gt__(me, you): return me._order(you, lambda x, y: x >  y)
+  def __ge__(me, you): return me._order(you, lambda x, y: x >= y)
+
+class IntRat (BaseRat):
+  RING = MP
+  ZERO, ONE = MP(0), MP(1)
+  def __new__(cls, a, b):
+    if isinstance(a, float) or isinstance(b, float): return a/b
+    return super(IntRat, cls).__new__(cls, a, b)
+  def __float__(me): return float(me._n)/float(me._d)
+
+class GFRat (BaseRat):
+  RING = GF
+  ZERO, ONE = GF(0), GF(1)
+
+class _tmp:
+  def negp(x): return x < 0
+  def posp(x): return x > 0
+  def zerop(x): return x == 0
+  def oddp(x): return x.testbit(0)
+  def evenp(x): return not x.testbit(0)
+  def mont(x): return MPMont(x)
+  def barrett(x): return MPBarrett(x)
+  def reduce(x): return MPReduce(x)
+  def __truediv__(me, you):
+    if isinstance(you, float): return _long(me)/you
+    else: return IntRat(me, you)
+  def __rtruediv__(me, you):
+    if isinstance(you, float): return you/_long(me)
+    else: return IntRat(you, me)
+  if _sys.version_info < (3,):
+    __div__ = __truediv__
+    __rdiv__ = __rtruediv__
+  _repr_pretty_ = _pp_str
+_augment(MP, _tmp)
+
+class _tmp:
+  def zerop(x): return x == 0
+  def reduce(x): return GFReduce(x)
+  def trace(x, y): return x.reduce().trace(y)
+  def halftrace(x, y): return x.reduce().halftrace(y)
+  def modsqrt(x, y): return x.reduce().sqrt(y)
+  def quadsolve(x, y): return x.reduce().quadsolve(y)
+  def __truediv__(me, you): return GFRat(me, you)
+  def __rtruediv__(me, you): return GFRat(you, me)
+  if _sys.version_info < (3,):
+    __div__ = __truediv__
+    __rdiv__ = __rtruediv__
+  _repr_pretty_ = _pp_str
+_augment(GF, _tmp)
+
+class _tmp:
+  def product(*arg):
+    'product(ITERABLE) or product(I, ...) -> PRODUCT'
+    return MPMul(*arg).done()
+  product = staticmethod(product)
+_augment(MPMul, _tmp)
+
+###--------------------------------------------------------------------------
+### Abstract fields.
+
+class _tmp:
+  def fromstring(str): return _checkend(Field.parse(str))
+  fromstring = staticmethod(fromstring)
+_augment(Field, _tmp)
+
+class _tmp:
+  def __repr__(me): return '%s(%s)' % (_clsname(me), me.p)
+  def __hash__(me): return 0x114401de ^ hash(me.p)
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep: pp.text('...')
+    else: pp.pretty(me.p)
+    pp.end_group(ind, ')')
+  def ec(me, a, b): return ECPrimeProjCurve(me, a, b)
+_augment(PrimeField, _tmp)
+
+class _tmp:
+  def __repr__(me): return '%s(%#x)' % (_clsname(me), me.p)
+  def ec(me, a, b): return ECBinProjCurve(me, a, b)
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep: pp.text('...')
+    else: pp.text('%#x' % me.p)
+    pp.end_group(ind, ')')
+_augment(BinField, _tmp)
+
+class _tmp:
+  def __hash__(me): return 0x23e4701c ^ hash(me.p)
+_augment(BinPolyField, _tmp)
+
+class _tmp:
+  def __hash__(me):
+    h = 0x9a7d6240
+    h ^=   hash(me.p)
+    h ^= 2*hash(me.beta) & 0xffffffff
+    return h
+_augment(BinNormField, _tmp)
+
+class _tmp:
+  def __str__(me): return str(me.value)
+  def __repr__(me): return '%s(%s)' % (repr(me.field), repr(me.value))
+  _repr_pretty_ = _pp_str
+_augment(FE, _tmp)
+
+###--------------------------------------------------------------------------
+### Elliptic curves.
+
+class _tmp:
+  def __repr__(me):
+    return '%s(%r, %s, %s)' % (_clsname(me), me.field, me.a, me.b)
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep:
+      pp.text('...')
+    else:
+      pp.pretty(me.field); pp.text(','); pp.breakable()
+      pp.pretty(me.a); pp.text(','); pp.breakable()
+      pp.pretty(me.b)
+    pp.end_group(ind, ')')
+  def fromstring(str): return _checkend(ECCurve.parse(str))
+  fromstring = staticmethod(fromstring)
+  def frombuf(me, s):
+    return ecpt.frombuf(me, s)
+  def fromraw(me, s):
+    return ecpt.fromraw(me, s)
+  def pt(me, *args):
+    return me(*args)
+_augment(ECCurve, _tmp)
+
+class _tmp:
+  def __hash__(me):
+    h = 0x6751d341
+    h ^=   hash(me.field)
+    h ^= 2*hash(me.a) ^ 0xffffffff
+    h ^= 5*hash(me.b) ^ 0xffffffff
+    return h
+_augment(ECPrimeCurve, _tmp)
+
+class _tmp:
+  def __hash__(me):
+    h = 0x2ac203c5
+    h ^=   hash(me.field)
+    h ^= 2*hash(me.a) ^ 0xffffffff
+    h ^= 5*hash(me.b) ^ 0xffffffff
+    return h
+_augment(ECBinCurve, _tmp)
+
+class _tmp:
+  def __repr__(me):
+    if not me: return '%s()' % _clsname(me)
+    return '%s(%s, %s)' % (_clsname(me), me.ix, me.iy)
+  def __str__(me):
+    if not me: return 'inf'
+    return '(%s, %s)' % (me.ix, me.iy)
+  def _repr_pretty_(me, pp, cyclep):
+    if cyclep:
+      pp.text('...')
+    elif not me:
+      pp.text('inf')
+    else:
+      ind = _pp_bgroup(pp, '(')
+      pp.pretty(me.ix); pp.text(','); pp.breakable()
+      pp.pretty(me.iy)
+      pp.end_group(ind, ')')
+_augment(ECPt, _tmp)
+
+class _tmp:
+  def __repr__(me):
+    return '%s(curve = %r, G = %r, r = %s, h = %s)' % \
+           (_clsname(me), me.curve, me.G, me.r, me.h)
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep:
+      pp.text('...')
+    else:
+      _pp_kv(pp, 'curve', me.curve); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'G', me.G); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'r', me.r); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'h', me.h)
+    pp.end_group(ind, ')')
+  def __hash__(me):
+    h = 0x9bedb8de
+    h ^=   hash(me.curve)
+    h ^= 2*hash(me.G) & 0xffffffff
+    return h
+  def fromstring(str): return _checkend(ECInfo.parse(str))
+  fromstring = staticmethod(fromstring)
+  def group(me):
+    return ECGroup(me)
+_augment(ECInfo, _tmp)
+
+class _tmp:
+  def __repr__(me):
+    if not me: return '%r()' % (me.curve)
+    return '%r(%s, %s)' % (me.curve, me.x, me.y)
+  def __str__(me):
+    if not me: return 'inf'
+    return '(%s, %s)' % (me.x, me.y)
+  def _repr_pretty_(me, pp, cyclep):
+    if cyclep:
+      pp.text('...')
+    elif not me:
+      pp.text('inf')
+    else:
+      ind = _pp_bgroup(pp, '(')
+      pp.pretty(me.x); pp.text(','); pp.breakable()
+      pp.pretty(me.y)
+      pp.end_group(ind, ')')
+_augment(ECPtCurve, _tmp)
+
+###--------------------------------------------------------------------------
+### Key sizes.
+
+class _tmp:
+  def __repr__(me): return '%s(%d)' % (_clsname(me), me.default)
+  def check(me, sz): return True
+  def best(me, sz): return sz
+  def pad(me, sz): return sz
+_augment(KeySZAny, _tmp)
+
+class _tmp:
+  def __repr__(me):
+    return '%s(%d, %d, %d, %d)' % \
+           (_clsname(me), me.default, me.min, me.max, me.mod)
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep:
+      pp.text('...')
+    else:
+      pp.pretty(me.default); pp.text(','); pp.breakable()
+      pp.pretty(me.min); pp.text(','); pp.breakable()
+      pp.pretty(me.max); pp.text(','); pp.breakable()
+      pp.pretty(me.mod)
+    pp.end_group(ind, ')')
+  def check(me, sz): return me.min <= sz <= me.max and sz%me.mod == 0
+  def best(me, sz):
+    if sz < me.min: raise ValueError('key too small')
+    elif me.max is not None and sz > me.max: return me.max
+    else: return sz - sz%me.mod
+  def pad(me, sz):
+    if me.max is not None and sz > me.max: raise ValueError('key too large')
+    elif sz < me.min: return me.min
+    else: sz += me.mod - 1; return sz - sz%me.mod
+_augment(KeySZRange, _tmp)
+
+class _tmp:
+  def __repr__(me): return '%s(%d, %s)' % (_clsname(me), me.default, me.set)
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep:
+      pp.text('...')
+    else:
+      pp.pretty(me.default); pp.text(','); pp.breakable()
+      ind1 = _pp_bgroup(pp, '{')
+      _pp_commas(pp, pp.pretty, me.set)
+      pp.end_group(ind1, '}')
+    pp.end_group(ind, ')')
+  def check(me, sz): return sz in me.set
+  def best(me, sz):
+    found = -1
+    for i in me.set:
+      if found < i <= sz: found = i
+    if found < 0: raise ValueError('key too small')
+    return found
+  def pad(me, sz):
+    found = -1
+    for i in me.set:
+      if sz <= i and (found == -1 or i < found): found = i
+    if found < 0: raise ValueError('key too large')
+    return found
+_augment(KeySZSet, _tmp)
+
+###--------------------------------------------------------------------------
+### Key data objects.
+
+class _tmp:
+  def merge(me, file, report = None):
+    """KF.merge(FILE, [report = <built-in-reporter>])"""
+    name = file.name
+    lno = 1
+    for line in file:
+      me.mergeline(name, lno, line, report)
+      lno += 1
+    return me
+  def __repr__(me): return '%s(%r)' % (_clsname(me), me.name)
+_augment(KeyFile, _tmp)
+
+class _tmp:
+  def extract(me, file, filter = ''):
+    """KEY.extract(FILE, [filter = <any>])"""
+    line = me.extractline(filter)
+    file.write(line)
+    return me
+  def __repr__(me): return '%s(%r)' % (_clsname(me), me.fulltag)
+_augment(Key, _tmp)
+
+class _tmp:
+  def __repr__(me):
+    return '%s({%s})' % (_clsname(me),
+                         ', '.join(['%r: %r' % kv for kv in _iteritems(me)()]))
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep: pp.text('...')
+    else: _pp_dict(pp, _iteritems(me))
+    pp.end_group(ind, ')')
+_augment(KeyAttributes, _tmp)
+
+class _tmp:
+  def __repr__(me):
+    return '%s(%s, %r)' % (_clsname(me),
+                           _repr_secret(me._guts(),
+                                        not (me.flags & KF_NONSECRET)),
+                           me.writeflags(me.flags))
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep:
+      pp.text('...')
+    else:
+      _pp_secret(pp, me._guts(), not (me.flags & KF_NONSECRET))
+      pp.text(','); pp.breakable()
+      pp.pretty(me.writeflags(me.flags))
+    pp.end_group(ind, ')')
+  def __hash__(me): return me._HASHBASE ^ hash(me._guts())
+  def __eq__(me, kd):
+    return type(me) == type(kd) and \
+      me._guts() == kd._guts() and \
+      me.flags == kd.flags
+  def __ne__(me, kd):
+    return not me == kd
+_augment(KeyData, _tmp)
+
+class _tmp:
+  def _guts(me): return me.bin
+  def __eq__(me, kd):
+    return isinstance(kd, KeyDataBinary) and me.bin == kd.bin
+_augment(KeyDataBinary, _tmp)
+KeyDataBinary._HASHBASE = 0x961755c3
+
+class _tmp:
+  def _guts(me): return me.ct
+_augment(KeyDataEncrypted, _tmp)
+KeyDataEncrypted._HASHBASE = 0xffe000d4
+
+class _tmp:
+  def _guts(me): return me.mp
+_augment(KeyDataMP, _tmp)
+KeyDataMP._HASHBASE = 0x1cb64d69
+
+class _tmp:
+  def _guts(me): return me.str
+_augment(KeyDataString, _tmp)
+KeyDataString._HASHBASE = 0x349c33ea
+
+class _tmp:
+  def _guts(me): return me.ecpt
+_augment(KeyDataECPt, _tmp)
+KeyDataECPt._HASHBASE = 0x2509718b
+
+class _tmp:
+  def __repr__(me):
+    return '%s({%s})' % (_clsname(me),
+                         ', '.join(['%r: %r' % kv for kv in _iteritems(me)]))
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me, '({ ')
+    if cyclep: pp.text('...')
+    else: _pp_dict(pp, _iteritems(me))
+    pp.end_group(ind, ' })')
+  def __hash__(me):
+    h = me._HASHBASE
+    for k, v in _iteritems(me):
+      h = ((h << 1) ^ 3*hash(k) ^ 5*hash(v))&0xffffffff
+    return h
+  def __eq__(me, kd):
+    if type(me) != type(kd) or me.flags != kd.flags or len(me) != len(kd):
+      return False
+    for k, v in _iteritems(me):
+      try: vv = kd[k]
+      except KeyError: return False
+      if v != vv: return False
+    return True
+_augment(KeyDataStructured, _tmp)
+KeyDataStructured._HASHBASE = 0x85851b21
+
+###--------------------------------------------------------------------------
+### Abstract groups.
+
+class _tmp:
+  def __repr__(me):
+    return '%s(p = %s, r = %s, g = %s)' % (_clsname(me), me.p, me.r, me.g)
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep:
+      pp.text('...')
+    else:
+      _pp_kv(pp, 'p', me.p); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'r', me.r); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'g', me.g)
+    pp.end_group(ind, ')')
+_augment(FGInfo, _tmp)
+
+class _tmp:
+  def group(me): return PrimeGroup(me)
+_augment(DHInfo, _tmp)
+
+class _tmp:
+  def group(me): return BinGroup(me)
+_augment(BinDHInfo, _tmp)
+
+class _tmp:
+  def __repr__(me):
+    return '%s(%r)' % (_clsname(me), me.info)
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep: pp.text('...')
+    else: pp.pretty(me.info)
+    pp.end_group(ind, ')')
+_augment(Group, _tmp)
+
+class _tmp:
+  def __hash__(me):
+    info = me.info
+    h = 0xbce3cfe6
+    h ^=   hash(info.p)
+    h ^= 2*hash(info.r) & 0xffffffff
+    h ^= 5*hash(info.g) & 0xffffffff
+    return h
+  def _get_geval(me, x): return MP(x)
+_augment(PrimeGroup, _tmp)
+
+class _tmp:
+  def __hash__(me):
+    info = me.info
+    h = 0x80695949
+    h ^=   hash(info.p)
+    h ^= 2*hash(info.r) & 0xffffffff
+    h ^= 5*hash(info.g) & 0xffffffff
+    return h
+  def _get_geval(me, x): return GF(x)
+_augment(BinGroup, _tmp)
+
+class _tmp:
+  def __hash__(me): return 0x0ec23dab ^ hash(me.info)
+  def _get_geval(me, x): return x.toec()
+_augment(ECGroup, _tmp)
+
+class _tmp:
+  def __repr__(me):
+    return '%r(%r)' % (me.group, str(me))
+  def _repr_pretty_(me, pp, cyclep):
+    pp.pretty(type(me)._get_geval(me))
+_augment(GE, _tmp)
+
+###--------------------------------------------------------------------------
+### RSA encoding techniques.
+
+class PKCS1Crypt (object):
+  def __init__(me, ep = _bin(''), rng = rand):
+    me.ep = ep
+    me.rng = rng
+  def encode(me, msg, nbits):
+    return _base._p1crypt_encode(msg, nbits, me.ep, me.rng)
+  def decode(me, ct, nbits):
+    return _base._p1crypt_decode(ct, nbits, me.ep, me.rng)
+
+class PKCS1Sig (object):
+  def __init__(me, ep = _bin(''), rng = rand):
+    me.ep = ep
+    me.rng = rng
+  def encode(me, msg, nbits):
+    return _base._p1sig_encode(msg, nbits, me.ep, me.rng)
+  def decode(me, msg, sig, nbits):
+    return _base._p1sig_decode(msg, sig, nbits, me.ep, me.rng)
+
+class OAEP (object):
+  def __init__(me, mgf = sha_mgf, hash = sha, ep = _bin(''), rng = rand):
+    me.mgf = mgf
+    me.hash = hash
+    me.ep = ep
+    me.rng = rng
+  def encode(me, msg, nbits):
+    return _base._oaep_encode(msg, nbits, me.mgf, me.hash, me.ep, me.rng)
+  def decode(me, ct, nbits):
+    return _base._oaep_decode(ct, nbits, me.mgf, me.hash, me.ep, me.rng)
+
+class PSS (object):
+  def __init__(me, mgf = sha_mgf, hash = sha, saltsz = None, rng = rand):
+    me.mgf = mgf
+    me.hash = hash
+    if saltsz is None:
+      saltsz = hash.hashsz
+    me.saltsz = saltsz
+    me.rng = rng
+  def encode(me, msg, nbits):
+    return _base._pss_encode(msg, nbits, me.mgf, me.hash, me.saltsz, me.rng)
+  def decode(me, msg, sig, nbits):
+    return _base._pss_decode(msg, sig, nbits,
+                             me.mgf, me.hash, me.saltsz, me.rng)
+
+class _tmp:
+  def encrypt(me, msg, enc):
+    return me.pubop(enc.encode(msg, me.n.nbits))
+  def verify(me, msg, sig, enc):
+    if msg is None: return enc.decode(msg, me.pubop(sig), me.n.nbits)
+    try:
+      x = enc.decode(msg, me.pubop(sig), me.n.nbits)
+      return x is None or x == msg
+    except ValueError:
+      return False
+  def __repr__(me):
+    return '%s(n = %r, e = %r)' % (_clsname(me), me.n, me.e)
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep:
+      pp.text('...')
+    else:
+      _pp_kv(pp, 'n', me.n); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'e', me.e)
+    pp.end_group(ind, ')')
+_augment(RSAPub, _tmp)
+
+class _tmp:
+  def decrypt(me, ct, enc): return enc.decode(me.privop(ct), me.n.nbits)
+  def sign(me, msg, enc): return me.privop(enc.encode(msg, me.n.nbits))
+  def __repr__(me):
+    return '%s(n = %r, e = %r, d = %s, ' \
+      'p = %s, q = %s, dp = %s, dq = %s, q_inv = %s)' % \
+      (_clsname(me), me.n, me.e,
+       _repr_secret(me.d), _repr_secret(me.p), _repr_secret(me.q),
+       _repr_secret(me.dp), _repr_secret(me.dq), _repr_secret(me.q_inv))
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep:
+      pp.text('...')
+    else:
+      _pp_kv(pp, 'n', me.n); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'e', me.e); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'd', me.d, secretp = True); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'p', me.p, secretp = True); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'q', me.q, secretp = True); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'dp', me.dp, secretp = True); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'dq', me.dq, secretp = True); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'q_inv', me.q_inv, secretp = True)
+    pp.end_group(ind, ')')
+_augment(RSAPriv, _tmp)
+
+###--------------------------------------------------------------------------
+### DSA and related schemes.
+
+class _tmp:
+  def __repr__(me): return '%s(G = %r, p = %r, hash = %r)' % \
+        (_clsname(me), me.G, me.p, me.hash)
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep:
+      pp.text('...')
+    else:
+      _pp_kv(pp, 'G', me.G); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'p', me.p); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'hash', me.hash)
+    pp.end_group(ind, ')')
+_augment(DSAPub, _tmp)
+_augment(KCDSAPub, _tmp)
+
+class _tmp:
+  def __repr__(me): return '%s(G = %r, u = %s, p = %r, hash = %r)' % \
+      (_clsname(me), me.G, _repr_secret(me.u), me.p, me.hash)
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep:
+      pp.text('...')
+    else:
+      _pp_kv(pp, 'G', me.G); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'u', me.u, True); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'p', me.p); pp.text(','); pp.breakable()
+      _pp_kv(pp, 'hash', me.hash)
+    pp.end_group(ind, ')')
+_augment(DSAPriv, _tmp)
+_augment(KCDSAPriv, _tmp)
+
+###--------------------------------------------------------------------------
+### Bernstein's elliptic curve crypto and related schemes.
+
+X25519_BASE = MP(9).storel(32)
+X448_BASE = MP(5).storel(56)
+
+Z128 = ByteString.zero(16)
+
+class _BasePub (object):
+  def __init__(me, pub, *args, **kw):
+    if not me._PUBSZ.check(len(pub)): raise ValueError('bad public key')
+    super(_BasePub, me).__init__(*args, **kw)
+    me.pub = pub
+  def __repr__(me): return '%s(pub = %r)' % (_clsname(me), me.pub)
+  def _pp(me, pp): _pp_kv(pp, 'pub', me.pub)
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup_tyname(pp, me)
+    if cyclep: pp.text('...')
+    else: me._pp(pp)
+    pp.end_group(ind, ')')
+
+class _BasePriv (object):
+  def __init__(me, priv, pub = None, *args, **kw):
+    if not me._KEYSZ.check(len(priv)): raise ValueError('bad private key')
+    if pub is None: pub = me._pubkey(priv)
+    super(_BasePriv, me).__init__(pub = pub, *args, **kw)
+    me.priv = priv
+  @classmethod
+  def generate(cls, rng = rand):
+    return cls(rng.block(cls._KEYSZ.default))
+  def __repr__(me):
+    return '%s(priv = %d, pub = %r)' % \
+        (_clsname(me), _repr_secret(me.priv), me.pub)
+  def _pp(me, pp):
+    _pp_kv(pp, 'priv', me.priv, secretp = True); pp.text(','); pp.breakable()
+    super(_BasePriv, me)._pp(pp)
+
+class _XDHPub (_BasePub):  pass
+
+class _XDHPriv (_BasePriv):
+  def _pubkey(me, priv): return me._op(priv, me._BASE)
+  def agree(me, you): return me._op(me.priv, you.pub)
+  def boxkey(me, recip): return me._hashkey(me.agree(recip))
+  def box(me, recip, n, m): return secret_box(me.boxkey(recip), n, m)
+  def unbox(me, recip, n, c): return secret_unbox(me.boxkey(recip), n, c)
+
+class X25519Pub (_XDHPub):
+  _PUBSZ = KeySZSet(X25519_PUBSZ)
+  _BASE = X25519_BASE
+
+class X25519Priv (_XDHPriv, X25519Pub):
+  _KEYSZ = KeySZSet(X25519_KEYSZ)
+  def _op(me, k, X): return x25519(k, X)
+  def _hashkey(me, z): return hsalsa20_prf(z, Z128)
+
+class X448Pub (_XDHPub):
+  _PUBSZ = KeySZSet(X448_PUBSZ)
+  _BASE = X448_BASE
+
+class X448Priv (_XDHPriv, X448Pub):
+  _KEYSZ = KeySZSet(X448_KEYSZ)
+  def _op(me, k, X): return x448(k, X)
+  def _hashkey(me, z): return Shake256().hash(z).done(salsa20.keysz.default)
+
+class _EdDSAPub (_BasePub):
+  def beginhash(me): return me._HASH()
+  def endhash(me, h): return h.done()
+
+class _EdDSAPriv (_BasePriv, _EdDSAPub):
+  pass
+
+class Ed25519Pub (_EdDSAPub):
+  _PUBSZ = KeySZSet(ED25519_PUBSZ)
+  _HASH = sha512
+  def verify(me, msg, sig, **kw):
+    return ed25519_verify(me.pub, msg, sig, **kw)
+
+class Ed25519Priv (_EdDSAPriv, Ed25519Pub):
+  _KEYSZ = KeySZAny(ED25519_KEYSZ)
+  def _pubkey(me, priv): return ed25519_pubkey(priv)
+  def sign(me, msg, **kw):
+    return ed25519_sign(me.priv, msg, pub = me.pub, **kw)
+
+class Ed448Pub (_EdDSAPub):
+  _PUBSZ = KeySZSet(ED448_PUBSZ)
+  _HASH = shake256
+  def verify(me, msg, sig, **kw):
+    return ed448_verify(me.pub, msg, sig, **kw)
+
+class Ed448Priv (_EdDSAPriv, Ed448Pub):
+  _KEYSZ = KeySZAny(ED448_KEYSZ)
+  def _pubkey(me, priv): return ed448_pubkey(priv)
+  def sign(me, msg, **kw):
+    return ed448_sign(me.priv, msg, pub = me.pub, **kw)
+
+###--------------------------------------------------------------------------
+### Built-in algorithm and group tables.
+
+class _tmp:
+  def __repr__(me):
+    return '{%s}' % ', '.join(['%r: %r' % kv for kv in _iteritems(me)])
+  def _repr_pretty_(me, pp, cyclep):
+    ind = _pp_bgroup(pp, '{ ')
+    if cyclep: pp.text('...')
+    else: _pp_dict(pp, _iteritems(me))
+    pp.end_group(ind, ' }')
+_augment(_base._MiscTable, _tmp)
+
+###--------------------------------------------------------------------------
+### Prime number generation.
+
+class PrimeGenEventHandler (object):
+  def pg_begin(me, ev):
+    return me.pg_try(ev)
+  def pg_done(me, ev):
+    return PGEN_DONE
+  def pg_abort(me, ev):
+    return PGEN_TRY
+  def pg_fail(me, ev):
+    return PGEN_TRY
+  def pg_pass(me, ev):
+    return PGEN_TRY
+
+class SophieGermainStepJump (object):
+  def pg_begin(me, ev):
+    me.lf = PrimeFilter(ev.x)
+    me.hf = me.lf.muladd(2, 1)
+    return me.cont(ev)
+  def pg_try(me, ev):
+    me.step()
+    return me.cont(ev)
+  def cont(me, ev):
+    while me.lf.status == PGEN_FAIL or me.hf.status == PGEN_FAIL:
+      me.step()
+    if me.lf.status == PGEN_ABORT or me.hf.status == PGEN_ABORT:
+      return PGEN_ABORT
+    ev.x = me.lf.x
+    if me.lf.status == PGEN_DONE and me.hf.status == PGEN_DONE:
+      return PGEN_DONE
+    return PGEN_TRY
+  def pg_done(me, ev):
+    del me.lf
+    del me.hf
+
+class SophieGermainStepper (SophieGermainStepJump):
+  def __init__(me, step):
+    me.lstep = step;
+    me.hstep = 2 * step
+  def step(me):
+    me.lf.step(me.lstep)
+    me.hf.step(me.hstep)
+
+class SophieGermainJumper (SophieGermainStepJump):
+  def __init__(me, jump):
+    me.ljump = PrimeFilter(jump);
+    me.hjump = me.ljump.muladd(2, 0)
+  def step(me):
+    me.lf.jump(me.ljump)
+    me.hf.jump(me.hjump)
+  def pg_done(me, ev):
+    del me.ljump
+    del me.hjump
+    SophieGermainStepJump.pg_done(me, ev)
+
+class SophieGermainTester (object):
+  def __init__(me):
+    pass
+  def pg_begin(me, ev):
+    me.lr = RabinMiller(ev.x)
+    me.hr = RabinMiller(2 * ev.x + 1)
+  def pg_try(me, ev):
+    lst = me.lr.test(ev.rng.range(me.lr.x))
+    if lst != PGEN_PASS and lst != PGEN_DONE:
+      return lst
+    rst = me.hr.test(ev.rng.range(me.hr.x))
+    if rst != PGEN_PASS and rst != PGEN_DONE:
+      return rst
+    if lst == PGEN_DONE and rst == PGEN_DONE:
+      return PGEN_DONE
+    return PGEN_PASS
+  def pg_done(me, ev):
+    del me.lr
+    del me.hr
+
+class PrimitiveStepper (PrimeGenEventHandler):
+  def __init__(me):
+    pass
+  def pg_try(me, ev):
+    ev.x = me.i.next()
+    return PGEN_TRY
+  def pg_begin(me, ev):
+    me.i = iter(smallprimes)
+    return me.pg_try(ev)
+
+class PrimitiveTester (PrimeGenEventHandler):
+  def __init__(me, mod, hh = [], exp = None):
+    me.mod = MPMont(mod)
+    me.exp = exp
+    me.hh = hh
+  def pg_try(me, ev):
+    x = ev.x
+    if me.exp is not None:
+      x = me.mod.exp(x, me.exp)
+      if x == 1: return PGEN_FAIL
+    for h in me.hh:
+      if me.mod.exp(x, h) == 1: return PGEN_FAIL
+    ev.x = x
+    return PGEN_DONE
+
+class SimulStepper (PrimeGenEventHandler):
+  def __init__(me, mul = 2, add = 1, step = 2):
+    me.step = step
+    me.mul = mul
+    me.add = add
+  def _stepfn(me, step):
+    if step <= 0:
+      raise ValueError('step must be positive')
+    if step <= MPW_MAX:
+      return lambda f: f.step(step)
+    j = PrimeFilter(step)
+    return lambda f: f.jump(j)
+  def pg_begin(me, ev):
+    x = ev.x
+    me.lf = PrimeFilter(x)
+    me.hf = PrimeFilter(x * me.mul + me.add)
+    me.lstep = me._stepfn(me.step)
+    me.hstep = me._stepfn(me.step * me.mul)
+    SimulStepper._cont(me, ev)
+  def pg_try(me, ev):
+    me._step()
+    me._cont(ev)
+  def _step(me):
+    me.lstep(me.lf)
+    me.hstep(me.hf)
+  def _cont(me, ev):
+    while me.lf.status == PGEN_FAIL or me.hf.status == PGEN_FAIL:
+      me._step()
+    if me.lf.status == PGEN_ABORT or me.hf.status == PGEN_ABORT:
+      return PGEN_ABORT
+    ev.x = me.lf.x
+    if me.lf.status == PGEN_DONE and me.hf.status == PGEN_DONE:
+      return PGEN_DONE
+    return PGEN_TRY
+  def pg_done(me, ev):
+    del me.lf
+    del me.hf
+    del me.lstep
+    del me.hstep
+
+class SimulTester (PrimeGenEventHandler):
+  def __init__(me, mul = 2, add = 1):
+    me.mul = mul
+    me.add = add
+  def pg_begin(me, ev):
+    x = ev.x
+    me.lr = RabinMiller(x)
+    me.hr = RabinMiller(x * me.mul + me.add)
+  def pg_try(me, ev):
+    lst = me.lr.test(ev.rng.range(me.lr.x))
+    if lst != PGEN_PASS and lst != PGEN_DONE:
+      return lst
+    rst = me.hr.test(ev.rng.range(me.hr.x))
+    if rst != PGEN_PASS and rst != PGEN_DONE:
+      return rst
+    if lst == PGEN_DONE and rst == PGEN_DONE:
+      return PGEN_DONE
+    return PGEN_PASS
+  def pg_done(me, ev):
+    del me.lr
+    del me.hr
+
+def sgprime(start, step = 2, name = 'p', event = pgen_nullev, nsteps = 0):
+  start = MP(start)
+  return pgen(start, name, SimulStepper(step = step), SimulTester(), event,
+              nsteps, RabinMiller.iters(start.nbits))
+
+def findprimitive(mod, hh = [], exp = None, name = 'g', event = pgen_nullev):
+  return pgen(0, name, PrimitiveStepper(), PrimitiveTester(mod, hh, exp),
+              event, 0, 1)
+
+def kcdsaprime(pbits, qbits, rng = rand,
+               event = pgen_nullev, name = 'p', nsteps = 0):
+  hbits = pbits - qbits - 1
+  while True:
+    h = pgen(rng.mp(hbits, 1), name + ' [h]',
+             PrimeGenStepper(2), PrimeGenTester(),
+             event, nsteps, RabinMiller.iters(hbits))
+    while True:
+      q0 = rng.mp(qbits, 1)
+      p0 = 2*q0*h + 1
+      if p0.nbits == pbits: break
+    q = pgen(q0, name, SimulStepper(2*h, 1, 2),
+             SimulTester(2 * h, 1), event, nsteps, RabinMiller.iters(qbits))
+    p = 2*q*h + 1
+    if q.nbits == qbits and p.nbits == pbits: return p, q, h
+    elif nsteps: raise ValueError("prime generation failed")
+
+#----- That's all, folks ----------------------------------------------------
diff --git a/catacomb/pwsafe.py b/catacomb/pwsafe.py
new file mode 100644 (file)
index 0000000..944cfa7
--- /dev/null
@@ -0,0 +1,1150 @@
+### -*-python-*-
+###
+### Management of a secure password database
+###
+### (c) 2005 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 __future__ import with_statement
+
+import binascii as _B
+import errno as _E
+import os as _OS
+import sys as _SYS
+
+if _SYS.version_info >= (3,): from io import StringIO as _StringIO
+else: from cStringIO import StringIO as _StringIO
+
+import catacomb as _C
+
+###--------------------------------------------------------------------------
+### Python version portability.
+
+if _SYS.version_info >= (3,):
+  def _iterkeys(dict): return dict.keys()
+  def _itervalues(dict): return dict.values()
+  def _iteritems(dict): return dict.items()
+  def _bin(text): return text.encode(errors = "surrogateescape")
+  def _text(bin): return bin.decode(errors = "surrogateescape")
+else:
+  def _iterkeys(dict): return dict.iterkeys()
+  def _itervalues(dict): return dict.itervalues()
+  def _iteritems(dict): return dict.iteritems()
+  def _bin(text): return text
+  def _text(bin): return bin
+
+_NUL = _bin('\0')
+_CIPHER = _bin('cipher:')
+_MAC = _bin('mac:')
+
+def _with_metaclass(meta, *supers):
+  return meta("#<anonymous base %s>" % meta.__name__,
+              supers or (object,), dict())
+
+def _excval(): return SYS.exc_info()[1]
+
+_M600 = int("600", 8)
+_M700 = int("700", 8)
+
+###--------------------------------------------------------------------------
+### Text encoding utilities.
+
+def _literalp(s):
+  """
+  Answer whether S can be represented literally.
+
+  If True, then S can be stored literally, as a metadata item name or
+  value; if False, then S requires some kind of encoding.
+  """
+  return all(ch.isalnum() or ch in '-_:' for ch in s)
+
+def _enc_metaname(name):
+  """Encode NAME as a metadata item name, returning the result."""
+  if _literalp(name):
+    return name
+  else:
+    sio = _StringIO()
+    sio.write('!')
+    for ch in name:
+      if _literalp(ch): sio.write(ch)
+      elif ch == ' ': sio.write('+')
+      else: sio.write('%%%02x' % ord(ch))
+    return sio.getvalue()
+
+def _dec_metaname(name):
+  """Decode NAME as a metadata item name, returning the result."""
+  if not name.startswith('!'):
+    return name
+  else:
+    sio = _StringIO()
+    i, n = 1, len(name)
+    while i < n:
+      ch = name[i]
+      i += 1
+      if ch == '+':
+        sio.write(' ')
+      elif ch == '%':
+        sio.write(chr(int(name[i:i + 2], 16)))
+        i += 2
+      else:
+        sio.write(ch)
+    return sio.getvalue()
+
+def _b64(s):
+  """Encode S as base64, without newlines, and trimming `=' padding."""
+  return _text(_B.b2a_base64(s)).replace('\n', '').rstrip('=')
+def _unb64(s):
+  """Decode S as base64 with trimmed `=' padding."""
+  return _B.a2b_base64(s + '='*((4 - len(s))%4))
+
+def _enc_metaval(val):
+  """Encode VAL as a metadata item value, returning the result."""
+  if _literalp(val): return val
+  else: return '?' + _b64(val)
+
+def _dec_metaval(val):
+  """Decode VAL as a metadata item value, returning the result."""
+  if not val.startswith('?'): return val
+  else: return _unb64(val[1:])
+
+###--------------------------------------------------------------------------
+### Underlying cryptography.
+
+class DecryptError (Exception):
+  """
+  I represent a failure to decrypt a message.
+
+  Usually this means that someone used the wrong key, though it can also
+  mean that a ciphertext has been modified.
+  """
+  pass
+
+class Crypto (object):
+  """
+  I represent a symmetric crypto transform.
+
+  There's currently only one transform implemented, which is the obvious
+  generic-composition construction: given a message m, and keys K0 and K1, we
+  choose an IV v, and compute:
+
+    * y = v || E(K0, v; m)
+    * t = M(K1; y)
+
+  The final ciphertext is t || y.
+  """
+
+  def __init__(me, c, h, m, ck, mk):
+    """
+    Initialize the Crypto object with a given algorithm selection and keys.
+
+    We need a GCipher subclass C, a GHash subclass H, a GMAC subclass M, and
+    keys CK and MK for C and M respectively.
+    """
+    me.c = c(ck)
+    me.m = m(mk)
+    me.h = h
+
+  def encrypt(me, pt):
+    """
+    Encrypt the message PT and return the resulting ciphertext.
+    """
+    blksz = me.c.__class__.blksz
+    b = _C.WriteBuffer()
+    if blksz:
+      iv = _C.rand.block(blksz)
+      me.c.setiv(iv)
+      b.put(iv)
+    b.put(me.c.encrypt(pt))
+    t = me.m().hash(b).done()
+    return t + str(buffer(b))
+
+  def decrypt(me, ct):
+    """
+    Decrypt the ciphertext CT, returning the plaintext.
+
+    Raises DecryptError if anything goes wrong.
+    """
+    blksz = me.c.__class__.blksz
+    tagsz = me.m.__class__.tagsz
+    b = _C.ReadBuffer(ct)
+    t = b.get(tagsz)
+    h = me.m()
+    if blksz:
+      iv = b.get(blksz)
+      me.c.setiv(iv)
+      h.hash(iv)
+    x = b.get(b.left)
+    h.hash(x)
+    if t != h.done(): raise DecryptError
+    return me.c.decrypt(x)
+
+class PPK (Crypto):
+  """
+  I represent a crypto transform whose keys are derived from a passphrase.
+
+  The password is salted and hashed; the salt is available as the `salt'
+  attribute.
+  """
+
+  def __init__(me, pp, c, h, m, salt = None):
+    """
+    Initialize the PPK object with a passphrase and algorithm selection.
+
+    We want a passphrase PP, a GCipher subclass C, a GHash subclass H, a GMAC
+    subclass M, and a SALT.  The SALT may be None, if we're generating new
+    keys, indicating that a salt should be chosen randomly.
+    """
+    if not salt: salt = _C.rand.block(h.hashsz)
+    tag = pp + _NUL + salt
+    Crypto.__init__(me, c, h, m,
+                    h().hash(_CIPHER).hash(tag).done(),
+                    h().hash(_MAC).hash(tag).done())
+    me.salt = salt
+
+###--------------------------------------------------------------------------
+### Backend storage.
+
+class StorageBackendRefusal (Exception):
+  """
+  I signify that a StorageBackend subclass has refused to open a file.
+
+  This is used by the StorageBackend.open class method.
+  """
+  pass
+
+class StorageBackendClass (type):
+  """
+  I am a metaclass for StorageBackend classes.
+
+  My main feature is that I register my concrete instances (with a `NAME'
+  which is not `None') with the StorageBackend class.
+  """
+  def __init__(me, name, supers, dict):
+    """
+    Register a new concrete StorageBackend subclass.
+    """
+    super(StorageBackendClass, me).__init__(name, supers, dict)
+    try: name = me.NAME
+    except AttributeError: pass
+    else: StorageBackend.register_concrete_subclass(me)
+
+class StorageBackend (_with_metaclass(StorageBackendClass)):
+  """
+  I provide basic protocol for password storage backends.
+
+  I'm an abstract class: you want one of my subclasses if you actually want
+  to do something useful.  But I maintain a list of my subclasses and can
+  choose an appropriate one to open a database file you've found lying about.
+
+  Backends are responsible for storing and retrieving stuff, but not for the
+  cryptographic details.  Backends need to store two kinds of information:
+
+    * metadata, consisting of a number of property names and their values;
+      and
+
+    * password mappings, consisting of a number of binary labels and
+      payloads.
+
+  Backends need to implement the following ordinary methods.  See the calling
+  methods for details of the subclass responsibilities.
+
+  BE._create(FILE)      Create a new database in FILE; used by `create'.
+
+  BE._open(FILE, WRITEP)
+                        Open the existing database FILE; used by `open'.
+
+  BE._close(ABRUPTP)    Close the database, freeing up any resources.  If
+                        ABRUPTP then don't try to commit changes.
+
+  BE._get_meta(NAME, DEFAULT)
+                        Return the value of the metadata item with the given
+                        NAME, or DEFAULT if it doesn't exist; used by
+                        `get_meta'.
+
+  BE._put_meta(NAME, VALUE)
+                        Set the VALUE of the metadata item with the given
+                        NAME, creating one if necessary; used by `put_meta'.
+
+  BE._del_meta(NAME)    Forget the metadata item with the given NAME; raise
+                        `KeyError' if there is no such item; used by
+                        `del_meta'.
+
+  BE._iter_meta()       Return an iterator over the metadata (NAME, VALUE)
+                        pairs; used by `iter_meta'.
+
+  BE._get_passwd(LABEL)
+                        Return the password payload stored with the (binary)
+                        LABEL; used by `get_passwd'.
+
+  BE._put_passwd(LABEL, PAYLOAD)
+                        Associate the (binary) PAYLOAD with the LABEL,
+                        forgetting any previous payload for that LABEL; used
+                        by `put_passwd'.
+
+  BE._del_passwd(LABEL) Forget the password record with the given LABEL; used
+                        by `_del_passwd'.
+
+  BE._iter_passwds()    Return an iterator over the password (LABEL, PAYLOAD)
+                        pairs; used by `iter_passwds'.
+
+  Also, concrete subclasses should define the following class attributes.
+
+  NAME                  The name of the backend, so that the user can select
+                        it when creating a new database.
+
+  PRIO                  An integer priority: backends are tried in decreasing
+                        priority order when opening an existing database.
+  """
+
+  PRIO = 10
+
+  ## The registry of subclasses.
+  CLASSES = {}
+
+  FAIL = ['FAIL']
+
+  @staticmethod
+  def register_concrete_subclass(sub):
+    """Register a concrete subclass, so that `open' can try it."""
+    StorageBackend.CLASSES[sub.NAME] = sub
+
+  @staticmethod
+  def byname(name):
+    """
+    Return the concrete subclass with the given NAME.
+
+    Raise `KeyError' if the name isn't found.
+    """
+    return StorageBackend.CLASSES[name]
+
+  @staticmethod
+  def classes():
+    """Return an iterator over the concrete subclasses."""
+    return _itervalues(StorageBackend.CLASSES)
+
+  @staticmethod
+  def open(file, writep = False):
+    """Open a database FILE, using some appropriate backend."""
+    _OS.stat(file)
+    for cls in sorted(StorageBackend.CLASSES.values(), reverse = True,
+                      key = lambda cls: cls.PRIO):
+      try: return cls(file, writep)
+      except StorageBackendRefusal: pass
+    raise StorageBackendRefusal
+
+  @classmethod
+  def create(cls, file):
+    """
+    Create a new database in the named FILE, using this backend.
+
+    Subclasses must implement the `_create' instance method.
+    """
+    return cls(writep = True, _magic = lambda me: me._create(file))
+
+  def __init__(me, file = None, writep = False, _magic = None, *args, **kw):
+    """
+    Main constructor.
+
+    Subclasses are not, in general, expected to override this: there's a
+    somewhat hairy protocol between the constructor and some of the class
+    methods.  Instead, the main hook for customization is the subclass's
+    `_open' method, which is invoked in the usual case.
+    """
+    super(StorageBackend, me).__init__(*args, **kw)
+    if me.NAME is None: raise ValueError('abstract class')
+    if _magic is not None: _magic(me)
+    elif file is None: raise ValueError('missing file parameter')
+    else: me._open(file, writep)
+    me._writep = writep
+    me._livep = True
+
+  def close(me, abruptp = False):
+    """
+    Close the database.
+
+    It is harmless to attempt to close a database which has been closed
+    already.  Calls the subclass's `_close' method.
+    """
+    if me._livep:
+      me._livep = False
+      me._close(abruptp)
+
+  ## Utilities.
+
+  def _check_live(me):
+    """Raise an error if the receiver has been closed."""
+    if not me._livep: raise ValueError('database is closed')
+
+  def _check_write(me):
+    """Raise an error if the receiver is not open for writing."""
+    me._check_live()
+    if not me._writep: raise ValueError('database is read-only')
+
+  def _check_meta_name(me, name):
+    """
+    Raise an error unless NAME is a valid name for a metadata item.
+
+    Metadata names may not start with `$': such names are reserved for
+    password storage.
+    """
+    if name.startswith('$'):
+      raise ValueError("invalid metadata key `%s'" % name)
+
+  ## Context protocol.
+
+  def __enter__(me):
+    """Context protocol: make sure the database is closed on exit."""
+    return me
+  def __exit__(me, exctype, excvalue, exctb):
+    """Context protocol: see `__enter__'."""
+    me.close(excvalue is not None)
+
+  ## Metadata.
+
+  def get_meta(me, name, default = FAIL):
+    """
+    Fetch the value for the metadata item NAME.
+
+    If no such item exists, then return DEFAULT if that was set; otherwise
+    raise a `KeyError'.
+
+    This calls the subclass's `_get_meta' method, which should return the
+    requested item or return the given DEFAULT value.  It may assume that the
+    name is valid and the database is open.
+    """
+    me._check_meta_name(name)
+    me._check_live()
+    value = me._get_meta(name, default)
+    if value is StorageBackend.FAIL: raise KeyError(name)
+    return value
+
+  def put_meta(me, name, value):
+    """
+    Store VALUE in the metadata item called NAME.
+
+    This calls the subclass's `_put_meta' method, which may assume that the
+    name is valid and the database is open for writing.
+    """
+    me._check_meta_name(name)
+    me._check_write()
+    me._put_meta(name, value)
+
+  def del_meta(me, name):
+    """
+    Forget about the metadata item with the given NAME.
+
+    This calls the subclass's `_del_meta' method, which may assume that the
+    name is valid and the database is open for writing.
+    """
+    me._check_meta_name(name)
+    me._check_write()
+    me._del_meta(name)
+
+  def iter_meta(me):
+    """
+    Return an iterator over the name/value metadata items.
+
+    This calls the subclass's `_iter_meta' method, which may assume that the
+    database is open.
+    """
+    me._check_live()
+    return me._iter_meta()
+
+  def get_passwd(me, label):
+    """
+    Fetch and return the payload stored with the (opaque, binary) LABEL.
+
+    If there is no such payload then raise `KeyError'.
+
+    This calls the subclass's `_get_passwd' method, which may assume that the
+    database is open.
+    """
+    me._check_live()
+    return me._get_passwd(label)
+
+  def put_passwd(me, label, payload):
+    """
+    Associate the (opaque, binary) PAYLOAD with the (opaque, binary) LABEL.
+
+    Any previous payload for LABEL is forgotten.
+
+    This calls the subclass's `_put_passwd' method, which may assume that the
+    database is open for writing.
+    """
+    me._check_write()
+    me._put_passwd(label, payload)
+
+  def del_passwd(me, label):
+    """
+    Forget any PAYLOAD associated with the (opaque, binary) LABEL.
+
+    If there is no such payload then raise `KeyError'.
+
+    This calls the subclass's `_del_passwd' method, which may assume that the
+    database is open for writing.
+    """
+    me._check_write()
+    me._del_passwd(label)
+
+  def iter_passwds(me):
+    """
+    Return an iterator over the stored password label/payload pairs.
+
+    This calls the subclass's `_iter_passwds' method, which may assume that
+    the database is open.
+    """
+    me._check_live()
+    return me._iter_passwds()
+
+try: import gdbm as _G
+except ImportError: pass
+else:
+  class GDBMStorageBackend (StorageBackend):
+    """
+    My instances store password data in a GDBM database.
+
+    Metadata and password entries are mixed into the same database.  The key
+    for a metadata item is simply its name; the key for a password entry is
+    the entry's label prefixed by `$', since we're guaranteed that no
+    metadata item name begins with `$'.
+    """
+
+    NAME = 'gdbm'
+
+    def _open(me, file, writep):
+      try: me._db = _G.open(file, writep and 'w' or 'r')
+      except _G.error: raise StorageBackendRefusal(_excval())
+
+    def _create(me, file):
+      me._db = _G.open(file, 'n', _M600)
+
+    def _close(me, abruptp):
+      me._db.close()
+      me._db = None
+
+    def _get_meta(me, name, default):
+      try: return me._db[name]
+      except KeyError: return default
+
+    def _put_meta(me, name, value):
+      me._db[name] = value
+
+    def _del_meta(me, name):
+      del me._db[name]
+
+    def _iter_meta(me):
+      k = me._db.firstkey()
+      while k is not None:
+        if not k.startswith('$'): yield k, me._db[k]
+        k = me._db.nextkey(k)
+
+    def _get_passwd(me, label):
+      return me._db['$' + label]
+
+    def _put_passwd(me, label, payload):
+      me._db['$' + label] = payload
+
+    def _del_passwd(me, label):
+      del me._db['$' + label]
+
+    def _iter_passwds(me):
+      k = me._db.firstkey()
+      while k is not None:
+        if k.startswith('$'): yield k[1:], me._db[k]
+        k = me._db.nextkey(k)
+
+try: import sqlite3 as _Q
+except ImportError: pass
+else:
+  class SQLiteStorageBackend (StorageBackend):
+    """
+    I represent a password database stored in SQLite.
+
+    Metadata and password items are stored in separate tables, so there's no
+    conflict.  Some additional metadata is stored in the `meta' table, with
+    names beginning with `$' so as not to conflict with clients:
+
+    $version            The schema version of the table.
+    """
+
+    NAME = 'sqlite'
+    VERSION = 0
+
+    def _open(me, file, writep):
+      try:
+        me._db = _Q.connect(file)
+        ver = me._query_scalar(
+          "SELECT value FROM meta WHERE name = '$version'",
+          "version check")
+      except (_Q.DatabaseError, _Q.OperationalError):
+        raise StorageBackendRefusal(_excval())
+      if ver is None: raise ValueError('database broken (missing $version)')
+      elif ver < me.VERSION: me._upgrade(ver)
+      elif ver > me.VERSION: raise ValueError \
+        ('unknown database schema version (%d > %d)' % (ver, me.VERSION))
+
+    def _create(me, file):
+      fd = _OS.open(file, _OS.O_WRONLY | _OS.O_CREAT | _OS.O_EXCL, _M600)
+      _OS.close(fd)
+      try:
+        me._db = _Q.connect(file)
+        c = me._db.cursor()
+        c.execute("""
+          CREATE TABLE meta (
+                  name TEXT PRIMARY KEY NOT NULL,
+                  value BLOB NOT NULL);
+        """)
+        c.execute("""
+          CREATE TABLE passwd (
+                  label BLOB PRIMARY KEY NOT NULL,
+                  payload BLOB NOT NULL);
+        """)
+        c.execute("""
+          INSERT INTO meta (name, value) VALUES ('$version', ?);
+        """, [me.VERSION])
+      except:
+        try: _OS.unlink(file)
+        except OSError: pass
+        raise
+
+    def _upgrade(me, ver):
+      """Upgrade the database from schema version VER."""
+      assert False, 'how embarrassing'
+
+    def _close(me, abruptp):
+      if not abruptp: me._db.commit()
+      me._db.close()
+      me._db = None
+
+    def _fetch_scalar(me, c, what, default = None):
+      try: row = next(c)
+      except StopIteration: val = default
+      else: val, = row
+      try: row = next(c)
+      except StopIteration: pass
+      else: raise ValueError('multiple matching records for %s' % what)
+      return val
+
+    def _query_scalar(me, query, what, default = None, args = []):
+      c = me._db.cursor()
+      c.execute(query, args)
+      return me._fetch_scalar(c, what, default)
+
+    def _get_meta(me, name, default):
+      v = me._query_scalar("SELECT value FROM meta WHERE name = ?",
+                           "metadata item `%s'" % name,
+                           default = default, args = [name])
+      if v is default: return v
+      else: return str(v)
+
+    def _put_meta(me, name, value):
+      c = me._db.cursor()
+      c.execute("INSERT OR REPLACE INTO meta (name, value) VALUES (?, ?)",
+                [name, buffer(value)])
+
+    def _del_meta(me, name):
+      c = me._db.cursor()
+      c.execute("DELETE FROM meta WHERE name = ?", [name])
+      if not c.rowcount: raise KeyError(name)
+
+    def _iter_meta(me):
+      c = me._db.cursor()
+      c.execute("SELECT name, value FROM meta WHERE name NOT LIKE '$%'")
+      for k, v in c: yield k, str(v)
+
+    def _get_passwd(me, label):
+      pld = me._query_scalar("SELECT payload FROM passwd WHERE label = ?",
+                             "password", default = None,
+                             args = [buffer(label)])
+      if pld is None: raise KeyError(label)
+      return str(pld)
+
+    def _put_passwd(me, label, payload):
+      c = me._db.cursor()
+      c.execute("INSERT OR REPLACE INTO passwd (label, payload) "
+                "VALUES (?, ?)",
+                [buffer(label), buffer(payload)])
+
+    def _del_passwd(me, label):
+      c = me._db.cursor()
+      c.execute("DELETE FROM passwd WHERE label = ?", [label])
+      if not c.rowcount: raise KeyError(label)
+
+    def _iter_passwds(me):
+      c = me._db.cursor()
+      c.execute("SELECT label, payload FROM passwd")
+      for k, v in c: yield str(k), str(v)
+
+class PlainTextBackend (StorageBackend):
+  """
+  I'm a utility base class for storage backends which use plain text files.
+
+  I provide subclasses with the following capabilities.
+
+    * Creating files, with given modes, optionally ensuring that the file
+      doesn't exist already.
+
+    * Parsing flat text files, checking leading magic, skipping comments, and
+      providing standard encodings of troublesome characters and binary
+      strings in metadata and password records.  See below.
+
+    * Maintenance of metadata and password records in in-memory dictionaries,
+      with ready implementations of the necessary StorageBackend subclass
+      responsibility methods.  (Subclasses can override these if they want to
+      make different arrangements.)
+
+  Metadata records are written with an optional prefix string chosen by the
+  caller, followed by a `NAME=VALUE' pair.  The NAME is form-urlencoded and
+  prefixed with `!' if it contains strange characters; the VALUE is base64-
+  encoded (without the pointless trailing `=' padding) and prefixed with `?'
+  if necessary.
+
+  Password records are written with an optional prefix string chosen by the
+  caller, followed by a LABEL=PAYLOAD pair, both of which are base64-encoded
+  (without padding).
+
+  The following attributes are available for subclasses:
+
+  _meta         Dictionary mapping metadata item names to their values.
+                Populated by `_parse_meta' and managed by `_get_meta' and
+                friends.
+
+  _pw           Dictionary mapping password labels to encrypted payloads.
+                Populated by `_parse_passwd' and managed by `_get_passwd' and
+                friends.
+
+  _dirtyp       Boolean: set if either of the dictionaries has been modified.
+  """
+
+  def __init__(me, *args, **kw):
+    """
+    Hook for initialization.
+
+    Sets up the published instance attributes.
+    """
+    me._meta = {}
+    me._pw = {}
+    me._dirtyp = False
+    super(PlainTextBackend, me).__init__(*args, **kw)
+
+  def _create_file(me, file, mode = _M600, freshp = False):
+    """
+    Make sure FILE exists, creating it with the given MODE if necessary.
+
+    If FRESHP is true, then make sure the file did not exist previously.
+    Return a file object for the newly created file.
+    """
+    flags = _OS.O_CREAT | _OS.O_WRONLY
+    if freshp: flags |= _OS.O_EXCL
+    else: flags |= _OS.O_TRUNC
+    fd = _OS.open(file, flags, mode)
+    return _OS.fdopen(fd, 'w')
+
+  def _mark_dirty(me):
+    """
+    Set the `_dirtyp' flag.
+
+    Subclasses might find it useful to intercept this method.
+    """
+    me._dirtyp = True
+
+  def _eqsplit(me, line):
+    """
+    Extract the KEY, VALUE pair from a LINE of the form `KEY=VALUE'.
+
+    Raise `ValueError' if there is no `=' in the LINE.
+    """
+    eq = line.index('=')
+    return line[:eq], line[eq + 1:]
+
+  def _parse_file(me, file, magic = None):
+    """
+    Parse a FILE.
+
+    Specifically:
+
+      * Raise `StorageBackendRefusal' if that the first line doesn't match
+        MAGIC (if provided).  MAGIC should not contain the terminating
+        newline.
+
+      * Ignore comments (beginning `#') and blank lines.
+
+      * Call `_parse_line' (provided by the subclass) for other lines.
+    """
+    with open(file, 'r') as f:
+      if magic is not None:
+        if f.readline().rstrip('\n') != magic: raise StorageBackendRefusal
+      for line in f:
+        line = line.rstrip('\n')
+        if not line or line.startswith('#'): continue
+        me._parse_line(line)
+
+  def _write_file(me, file, writebody, mode = _M600, magic = None):
+    """
+    Update FILE atomically.
+
+    The newly created file will have the given MODE.  If MAGIC is given, then
+    write that as the first line.  Calls WRITEBODY(F) to write the main body
+    of the file where F is a file object for the new file.
+    """
+    new = file + '.new'
+    with me._create_file(new, mode) as f:
+      if magic is not None: f.write(magic + '\n')
+      writebody(f)
+    _OS.rename(new, file)
+
+  def _parse_meta(me, line):
+    """Parse LINE as a metadata NAME=VALUE pair, and updates `_meta'."""
+    k, v = me._eqsplit(line)
+    me._meta[_dec_metaname(k)] = _dec_metaval(v)
+
+  def _write_meta(me, f, prefix = ''):
+    """Write the metadata records to F, each with the given PREFIX."""
+    f.write('\n## Metadata.\n')
+    for k, v in _iteritems(me._meta):
+      f.write('%s%s=%s\n' % (prefix, _enc_metaname(k), _enc_metaval(v)))
+
+  def _get_meta(me, name, default):
+    return me._meta.get(name, default)
+  def _put_meta(me, name, value):
+    me._mark_dirty()
+    me._meta[name] = value
+  def _del_meta(me, name):
+    me._mark_dirty()
+    del me._meta[name]
+  def _iter_meta(me):
+    return _iteritems(me._meta)
+
+  def _parse_passwd(me, line):
+    """Parse LINE as a password LABEL=PAYLOAD pair, and updates `_pw'."""
+    k, v = me._eqsplit(line)
+    me._pw[_unb64(k)] = _unb64(v)
+
+  def _write_passwd(me, f, prefix = ''):
+    """Write the password records to F, each with the given PREFIX."""
+    f.write('\n## Password data.\n')
+    for k, v in _iteritems(me._pw):
+      f.write('%s%s=%s\n' % (prefix, _b64(k), _b64(v)))
+
+  def _get_passwd(me, label):
+    return me._pw[str(label)]
+  def _put_passwd(me, label, payload):
+    me._mark_dirty()
+    me._pw[str(label)] = payload
+  def _del_passwd(me, label):
+    me._mark_dirty()
+    del me._pw[str(label)]
+  def _iter_passwds(me):
+    return _iteritems(me._pw)
+
+class FlatFileStorageBackend (PlainTextBackend):
+  """
+  I maintain a password database in a plain text file.
+
+  The text file consists of lines, as follows.
+
+    * Empty lines, and lines beginning with `#' (in the leftmost column only)
+      are ignored.
+
+    * Lines of the form `$LABEL=PAYLOAD' store password data.  Both LABEL and
+      PAYLOAD are base64-encoded, without `=' padding.
+
+    * Lines of the form `NAME=VALUE' store metadata.  If the NAME contains
+      characters other than alphanumerics, hyphens, underscores, and colons,
+      then it is form-urlencoded, and prefixed wth `!'.  If the VALUE
+      contains such characters, then it is base64-encoded, without `='
+      padding, and prefixed with `?'.
+
+    * Other lines are erroneous.
+
+  The file is rewritten from scratch when it's changed: any existing
+  commentary is lost, and items may be reordered.  There is no file locking,
+  but the file is updated atomically, by renaming.
+
+  It is expected that the FlatFileStorageBackend is used mostly for
+  diagnostics and transfer, rather than for a live system.
+  """
+
+  NAME = 'flat'
+  PRIO = 0
+  MAGIC = '### pwsafe password database'
+
+  def _open(me, file, writep):
+    if not _OS.path.isfile(file): raise StorageBackendRefusal
+    me._parse_file(file, magic = me.MAGIC)
+  def _parse_line(me, line):
+    if line.startswith('$'): me._parse_passwd(line[1:])
+    else: me._parse_meta(line)
+
+  def _create(me, file):
+    with me._create_file(file, freshp = True) as f: pass
+    me._file = file
+    me._mark_dirty()
+
+  def _close(me, abruptp):
+    if not abruptp and me._dirtyp:
+      me._write_file(me._file, me._write_body, magic = me.MAGIC)
+
+  def _write_body(me, f):
+    me._write_meta(f)
+    me._write_passwd(f, '$')
+
+class DirectoryStorageBackend (PlainTextBackend):
+  """
+  I maintain a password database in a directory, with one file per password.
+
+  This makes password databases easy to maintain in a revision-control system
+  such as Git.
+
+  The directory is structured as follows.
+
+  dir/meta      Contains metadata, similar to the `FlatFileBackend'.
+
+  dir/pw/LABEL  Contains the (raw binary) payload for the given password
+                LABEL (base64-encoded, without the useless `=' padding, and
+                with `/' replaced by `.').
+
+  dir/tmp/      Contains temporary files used by the implementation.
+  """
+
+  NAME = 'dir'
+  METAMAGIC = '### pwsafe password directory metadata'
+
+  def _open(me, file, writep):
+    if not _OS.path.isdir(file) or \
+          not _OS.path.isdir(_OS.path.join(file, 'pw')) or \
+          not _OS.path.isdir(_OS.path.join(file, 'tmp')) or \
+          not _OS.path.isfile(_OS.path.join(file, 'meta')):
+      raise StorageBackendRefusal
+    me._dir = file
+    me._parse_file(_OS.path.join(file, 'meta'), magic = me.METAMAGIC)
+  def _parse_line(me, line):
+    me._parse_meta(line)
+
+  def _create(me, file):
+    _OS.mkdir(file, _M700)
+    _OS.mkdir(_OS.path.join(file, 'pw'), _M700)
+    _OS.mkdir(_OS.path.join(file, 'tmp'), _M700)
+    me._mark_dirty()
+    me._dir = file
+
+  def _close(me, abruptp):
+    if not abruptp and me._dirtyp:
+      me._write_file(_OS.path.join(me._dir, 'meta'),
+                     me._write_meta, magic = me.METAMAGIC)
+
+  def _pwfile(me, label, dir = 'pw'):
+    return _OS.path.join(me._dir, dir, _b64(label).replace('/', '.'))
+  def _get_passwd(me, label):
+    try:
+      f = open(me._pwfile(label), 'rb')
+    except (OSError, IOError):
+      if _excval().errno == _E.ENOENT: raise KeyError(label)
+      else: raise
+    with f: return f.read()
+  def _put_passwd(me, label, payload):
+    new = me._pwfile(label, 'tmp')
+    fd = _OS.open(new, _OS.O_WRONLY | _OS.O_CREAT | _OS.O_TRUNC, _M600)
+    _OS.close(fd)
+    with open(new, 'wb') as f: f.write(payload)
+    _OS.rename(new, me._pwfile(label))
+  def _del_passwd(me, label):
+    try:
+      _OS.remove(me._pwfile(label))
+    except (OSError, IOError):
+      if _excval().errno == _E.ENOENT: raise KeyError(label)
+      else: raise
+  def _iter_passwds(me):
+    pw = _OS.path.join(me._dir, 'pw')
+    for i in _OS.listdir(pw):
+      with open(_OS.path.join(pw, i), 'rb') as f: pld = f.read()
+      yield _unb64(i.replace('.', '/')), pld
+
+###--------------------------------------------------------------------------
+### Password storage.
+
+class PW (object):
+  """
+  I represent a secure (ish) password store.
+
+  I can store short secrets, associated with textual names, in a way which
+  doesn't leak too much information about them.
+
+  I implement (some of) the Python mapping protocol.
+
+  I keep track of everything using a StorageBackend object.  This contains
+  password entries, identified by cryptographic labels, and a number of
+  metadata items.
+
+  cipher        Names the Catacomb cipher selected.
+
+  hash          Names the Catacomb hash function selected.
+
+  key           Cipher and MAC keys, each prefixed by a 16-bit big-endian
+                length and concatenated, encrypted using the master
+                passphrase.
+
+  mac           Names the Catacomb message authentication code selected.
+
+  magic         A magic string for obscuring password tag names.
+
+  salt          The salt for hashing the passphrase.
+
+  tag           The master passphrase's tag, for the Pixie's benefit.
+
+  Password entries are assigned labels of the form `$' || H(MAGIC || TAG);
+  the corresponding value consists of a pair (TAG, PASSWD), prefixed with
+  16-bit lengths, concatenated, padded to a multiple of 256 octets, and
+  encrypted using the stored keys.
+  """
+
+  def __init__(me, file, writep = False):
+    """
+    Initialize a PW object from the database in FILE.
+
+    If WRITEP is false (the default) then the database is opened read-only;
+    if true then it may be written.  Requests the database password from the
+    Pixie, which may cause interaction.
+    """
+
+    ## Open the database.
+    me.db = StorageBackend.open(file, writep)
+
+    ## Find out what crypto to use.
+    c = _C.gcciphers[me.db.get_meta('cipher')]
+    h = _C.gchashes[me.db.get_meta('hash')]
+    m = _C.gcmacs[me.db.get_meta('mac')]
+
+    ## Request the passphrase and extract the master keys.
+    tag = me.db.get_meta('tag')
+    ppk = PPK(_C.ppread(tag), c, h, m, me.db.get_meta('salt'))
+    try:
+      b = _C.ReadBuffer(ppk.decrypt(me.db.get_meta('key')))
+    except DecryptError:
+      _C.ppcancel(tag)
+      raise
+    me.ck = b.getblk16()
+    me.mk = b.getblk16()
+    if not b.endp: raise ValueError('trailing junk')
+
+    ## Set the key, and stash it and the tag-hashing secret.
+    me.k = Crypto(c, h, m, me.ck, me.mk)
+    me.magic = me.k.decrypt(me.db.get_meta('magic'))
+
+  @classmethod
+  def create(cls, dbcls, file, tag, c, h, m):
+    """
+    Create and initialize a new database FILE using StorageBackend DBCLS.
+
+    We want a GCipher subclass C, a GHash subclass H, and a GMAC subclass M;
+    and a Pixie passphrase TAG.
+
+    This doesn't return a working object: it just creates the database file
+    and gets out of the way.
+    """
+
+    ## Set up the cryptography.
+    pp = _C.ppread(tag, _C.PMODE_VERIFY)
+    ppk = PPK(pp, c, h, m)
+    ck = _C.rand.block(c.keysz.default)
+    mk = _C.rand.block(c.keysz.default)
+    k = Crypto(c, h, m, ck, mk)
+
+    ## Set up and initialize the database.
+    kct = ppk.encrypt(_C.WriteBuffer().putblk16(ck).putblk16(mk))
+    with dbcls.create(file) as db:
+      db.put_meta('tag', tag)
+      db.put_meta('salt', ppk.salt)
+      db.put_meta('cipher', c.name)
+      db.put_meta('hash', h.name)
+      db.put_meta('mac', m.name)
+      db.put_meta('key', kct)
+      db.put_meta('magic', k.encrypt(_C.rand.block(h.hashsz)))
+
+  def keyxform(me, key):
+    """Transform the KEY (actually a password tag) into a password label."""
+    return me.k.h().hash(me.magic).hash(key).done()
+
+  def changepp(me):
+    """
+    Change the database password.
+
+    Requests the new password from the Pixie, which will probably cause
+    interaction.
+    """
+    tag = me.db.get_meta('tag')
+    _C.ppcancel(tag)
+    ppk = PPK(_C.ppread(tag, _C.PMODE_VERIFY),
+              me.k.c.__class__, me.k.h, me.k.m.__class__)
+    kct = ppk.encrypt(_C.WriteBuffer().putblk16(me.ck).putblk16(me.mk))
+    me.db.put_meta('key', kct)
+    me.db.put_meta('salt', ppk.salt)
+
+  def pack(me, key, value):
+    """Pack the KEY and VALUE into a ciphertext, and return it."""
+    b = _C.WriteBuffer()
+    b.putblk16(key).putblk16(value)
+    b.zero(((b.size + 255) & ~255) - b.size)
+    return me.k.encrypt(b)
+
+  def unpack(me, ct):
+    """
+    Unpack a ciphertext CT and return a (KEY, VALUE) pair.
+
+    Might raise DecryptError, of course.
+    """
+    b = _C.ReadBuffer(me.k.decrypt(ct))
+    key = b.getblk16()
+    value = b.getblk16()
+    return key, value
+
+  ## Mapping protocol.
+
+  def __getitem__(me, key):
+    """Return the password for the given KEY."""
+    try: return me.unpack(me.db.get_passwd(me.keyxform(key)))[1]
+    except KeyError: raise KeyError(key)
+
+  def __setitem__(me, key, value):
+    """Associate the password VALUE with the KEY."""
+    me.db.put_passwd(me.keyxform(key), me.pack(key, value))
+
+  def __delitem__(me, key):
+    """Forget all about the KEY."""
+    try: me.db.del_passwd(me.keyxform(key))
+    except KeyError: raise KeyError(key)
+
+  def __iter__(me):
+    """Iterate over the known password tags."""
+    for _, pld in me.db.iter_passwds():
+      yield me.unpack(pld)[0]
+
+  ## Context protocol.
+
+  def __enter__(me):
+    return me
+  def __exit__(me, excty, excval, exctb):
+    me.db.close(excval is not None)
+
+###----- That's all, folks --------------------------------------------------
diff --git a/debian/.gitignore b/debian/.gitignore
new file mode 100644 (file)
index 0000000..24336ea
--- /dev/null
@@ -0,0 +1,8 @@
+files
+tmp
+substvars
+*.substvars
+*.debhelper
+python-catacomb-bin
+python-catacomb
+*.log
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..66c14f7
--- /dev/null
@@ -0,0 +1,151 @@
+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
+    source package.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 22 Sep 2019 01:21:28 +0100
+
+catacomb-python (1.3.0) experimental; urgency=medium
+
+  * catacomb: Bindings for new blockcipher-based MACs, and AEAD schemes.
+  * catacomb: Invalidate `grand' objects passed into Python through prime-
+    generation events.
+  * catacomb: Improve class docstrings.  (They're still extremely terse.)
+  * catacomb: Add missing `copy' methods on hash and Keccak objects.
+  * catacomb: Add `WriteBuffer.contents' as a more convenient way to
+    extract the contents than coercing to `str' or `ByteString'.
+  * catacomb: Set `RTLD_DEEPBIND' while loading the native module to work
+    around #868366.
+  * pock: New program for generating efficiently verifiable prime numbers,
+    and for verifying their certificates.
+
+ -- 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.
+  * Fix keywrd argument mismatch in ECDSAPriv.
+  * Coerce DSA private key values to `MP'.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Fri, 16 Jun 2017 01:01:05 +0100
+
+catacomb-python (1.2.0) experimental; urgency=low
+
+  * Bindings for HSalsa20 and HChaCha PRFs.
+  * Bindings for Poly1305.
+  * Bindings for X25519.
+  * Bindings for X448.
+  * Bindings for Ed25519.
+  * Bindings for Ed448.
+  * Bindings for Keccak[1600, n].
+  * Bindings for cSHAKE; Python implementation of KMAC.
+  * Improved pretty-printing for IPython.
+  * Allow selection of public exponent when generating RSA keys.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 14 May 2017 17:29:53 +0100
+
+catacomb-python (1.1.2) experimental; urgency=low
+
+  * Further 64-bit compatibility improvements.
+  * Fixed docstrings for a number of native methods.
+  * Fix range checking for `GRand' methods.
+  * Fix crash when deleting natively implemented object attributes.
+  * Fix bug which assigned the wrong hash to bytestrings.
+  * Fix `__int__' methods to return `long' objects when necessary.
+    Without this, there are unnecessary failures and bizarrely timed
+    exceptions.
+  * A couple of patches to provide forward compatibility with upstream
+    library changes.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 14 May 2017 04:25:35 +0100
+
+catacomb-python (1.1.1) experimental; urgency=low
+
+  * ByteString operators: fix crashes on 64-bit platforms resulting from
+    the use of the wrong length types.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sat, 04 Jun 2016 01:17:14 +0100
+
+catacomb-python (1.1.0.1) experimental; urgency=low
+
+  * Just Debian packaging fixes.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Fri, 19 Feb 2016 19:39:28 +0000
+
+catacomb-python (1.1.0) experimental; urgency=low
+
+  * Fix crash on trying to import RSA public and private keys with even
+    modulus.
+  * Add bindings for key-strength conversion functions.
+  * Add bindings for factorial and Fibonacci calculations.
+  * Implement exact field-of-fractions arithmetic for integers and binary
+    polynomials.
+  * Set module nameson types properly, so that IPython doesn't choke on
+    printing them.
+  * Implement hashing for comparable types.  Again, this makes IPython
+    work better.
+  * Support Daniel Bernstein's Salsa20 and ChaCha stream ciphers.
+  * Stop abusing SyntaxError.
+  * Support elliptic curve point compression formats.
+  * Major overhaul of the `pwsafe' utility: multiple storage backends, so
+    (a) we no longer require GDBM, and (b) we can use a Git-friendly
+    directory-based format.  Or SQLite.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Mon, 20 Jul 2015 16:07:51 +0100
+
+catacomb-python (1.0.6) experimental; urgency=low
+
+  * Fix stupid bug in WriteBuffer resizing.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 21 Jul 2013 15:57:06 +0100
+
+catacomb-python (1.0.5) experimental; urgency=low
+
+  * Make Key constructor actually work.
+  * Include human-readable text in KeyError exceptions.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sat, 04 Jan 2014 01:38:26 +0000
+
+catacomb-python (1.0.4) experimental; urgency=low
+
+  * Build against Python 2.7.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Wed, 09 Jan 2013 02:47:59 +0000
+
+catacomb-python (1.0.3) experimental; urgency=low
+
+  * Remove catacomb-python-bin package, because it made building hard and
+    didn't provide any noticeable benefits.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Fri, 05 Nov 2010 15:12:35 +0000
+
+catacomb-python (1.0.2) experimental; urgency=low
+
+  * Overhaul build system with new support module.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 04 Oct 2009 14:17:20 +0100
+
+catacomb-python (1.0.1) experimental; urgency=low
+
+  * Make it work with Python 2.6.  Also switched over to the (increasingly
+    winning) CDBS.
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Sat, 03 Oct 2009 23:06:29 +0100
+
+catacomb-python (1.0.0) experimental; urgency=low
+
+  * Debianization!
+
+ -- Mark Wooding <mdw@distorted.org.uk>  Mon, 26 Sep 2005 11:27:00 +0100
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..f599e28
--- /dev/null
@@ -0,0 +1 @@
+10
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..66a2caa
--- /dev/null
@@ -0,0 +1,47 @@
+Source: catacomb-python
+Section: python
+Priority: extra
+XS-Python-Version: >= 2.6, << 2.8
+XS-Python3-Version: >= 3.0
+Maintainer: Mark Wooding <mdw@distorted.org.uk>
+Build-Depends: debhelper (>= 10), dh-python, pkg-config,
+       python (>= 2.6.6-3~), python-all-dev, python3-all-dev,
+       mlib-dev (>= 2.4.99~), catacomb-dev (>= 2.5.99~)
+Standards-Version: 3.8.0
+
+Package: python-catacomb
+Architecture: any
+XB-Python-Version: ${python:Versions}
+Depends: ${shlibs:Depends}, ${python:Depends}
+Provides: ${python:Provides}, catacomb-python-bin
+Conflicts: catacomb-python-bin
+Description: Python bindings for the Catacomb cryptographic library.
+ Catacomb is a cryptographic library.  It implements a large number of
+ encryption algorithms, hash functions, message authentication codes
+ and random number generators.  It has a multi-precision maths library,
+ for implementing public key schemes such as RSA, DSA and Diffie-Hellman.
+ It contains rudimentary key-management tools.
+ .
+ The objective of Catacomb is to make a crypto library which is
+ relatively straightforward to audit for security.  Its focus is on
+ clarity of source code and portability more than performance.
+ .
+ This package allows the Catacomb library to be used in Python programs.
+
+Package: python3-catacomb
+Architecture: any
+XB-Python-Version: ${python3:Versions}
+Depends: ${shlibs:Depends}, ${python3:Depends}
+Provides: ${python3:Provides}
+Description: Python 3 bindings for the Catacomb cryptographic library.
+ Catacomb is a cryptographic library.  It implements a large number of
+ encryption algorithms, hash functions, message authentication codes
+ and random number generators.  It has a multi-precision maths library,
+ for implementing public key schemes such as RSA, DSA and Diffie-Hellman.
+ It contains rudimentary key-management tools.
+ .
+ The objective of Catacomb is to make a crypto library which is
+ relatively straightforward to audit for security.  Its focus is on
+ clarity of source code and portability more than performance.
+ .
+ This package allows the Catacomb library to be used in Python 3 programs.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..65c9432
--- /dev/null
@@ -0,0 +1,30 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Copyright: 2006--2010, 2012--2017 Straylight/Edgeware
+Upstream-Name: catacomb-python
+Upstream-Contact: Mark Wooding <mdw@distorted.org.uk>
+Source: https://ftp.distorted.org.uk/pub/mdw/
+License: LGPL-2.0+
+
+Files: *
+Copyright: 2006--2010, 2012--2017 Straylight/Edgeware
+License: LGPL-2.0+
+
+License: LGPL-2.0+
+ Catacomb/Python is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Library 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
+ Library General Public License for more details.
+ .
+ You should have a copy of the GNU Library General Public License in
+ /usr/share/common-licenses/LGPL-2; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ USA.
+ .
+ On Debian systems, the full text of the GNU Library General Public
+ License version 2 can be found in the file
+ `/usr/share/common-licenses/LGPL-2'.
diff --git a/debian/python-catacomb.install b/debian/python-catacomb.install
new file mode 100644 (file)
index 0000000..4d1b425
--- /dev/null
@@ -0,0 +1,3 @@
+debian/tmp/usr/bin
+debian/tmp/usr/share/man
+debian/tmp/usr/lib/python2.*
diff --git a/debian/python3-catacomb.install b/debian/python3-catacomb.install
new file mode 100644 (file)
index 0000000..3cff559
--- /dev/null
@@ -0,0 +1 @@
+debian/tmp/usr/lib/python3.*
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..7b85a0a
--- /dev/null
@@ -0,0 +1,10 @@
+#! /usr/bin/make -f
+%:; dh $@ --parallel --with python2,python3
+
+export PYTHONS := $(shell pyversions -r && py3versions -r)
+
+override_dh_auto_test:
+       dh_auto_test -- OPTS-check=-V
+
+override_dh_auto_install:
+       dh_auto_install -- prefix=/usr
diff --git a/debian/source/format b/debian/source/format
new file mode 100644 (file)
index 0000000..d3827e7
--- /dev/null
@@ -0,0 +1 @@
+1.0
diff --git a/ec.c b/ec.c
new file mode 100644 (file)
index 0000000..04bd482
--- /dev/null
+++ b/ec.c
@@ -0,0 +1,1609 @@
+/* -*-c-*-
+ *
+ * Elliptic curves
+ *
+ * (c) 2004 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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "catacomb-python.h"
+
+/*----- Utility functions -------------------------------------------------*/
+
+PyTypeObject *ecpt_pytype;
+PyTypeObject *ecptcurve_pytype;
+PyTypeObject *eccurve_pytype;
+PyTypeObject *ecprimecurve_pytype;
+PyTypeObject *ecprimeprojcurve_pytype;
+PyTypeObject *ecbincurve_pytype;
+PyTypeObject *ecbinprojcurve_pytype;
+PyTypeObject *ecinfo_pytype;
+
+ec_curve *eccurve_copy(ec_curve *c)
+{
+  field *f;
+  mp *a, *b;
+
+  if ((f = field_copy(c->f)) == 0)
+    return (0);
+  a = F_OUT(f, MP_NEW, c->a);
+  b = F_OUT(f, MP_NEW, c->b);
+  if (STRCMP(EC_NAME(c), ==, "prime"))
+    c = ec_prime(f, a, b);
+  else if (STRCMP(EC_NAME(c), ==, "primeproj"))
+    c = ec_primeproj(f, a, b);
+  else if (STRCMP(EC_NAME(c), ==, "bin"))
+    c = ec_bin(f, a, b);
+  else if (STRCMP(EC_NAME(c), ==, "binproj"))
+    c = ec_binproj(f, a, b);
+  else
+    c = 0;
+  MP_DROP(a);
+  MP_DROP(b);
+  if (!c) F_DESTROY(f);
+  return (c);
+}
+
+static PyObject *ecpt_dopywrap(PyObject *cobj, ec_curve *c, ec *p)
+{
+  ecpt_pyobj *z = PyObject_New(ecpt_pyobj, (PyTypeObject *)cobj);
+  z->p = *p;
+  z->c = c;
+  Py_INCREF(cobj);
+  return ((PyObject *)z);
+}
+
+PyObject *ecpt_pywrap(PyObject *cobj, ec *p)
+  { return (ecpt_dopywrap(cobj, ECCURVE_C(cobj), p)); }
+
+PyObject *ecpt_pywrapout(void *cobj, ec *p)
+{
+  ec_curve *c;
+
+  if (!PyType_IsSubtype(cobj, ecptcurve_pytype))
+    c = 0;
+  else {
+    c = ECCURVE_C(cobj);
+    EC_IN(ECCURVE_C(cobj), p, p);
+  }
+  return (ecpt_dopywrap(cobj, c, p));
+}
+
+int toecpt(ec_curve *c, ec *d, PyObject *p)
+{
+  if (ECPTCURVE_PYCHECK(p)) {
+    if (ECPT_C(p) != c && !ec_samep(ECPT_C(p), c))
+      return (-1);
+    EC_COPY(d, ECPT_P(p));
+  } else if (ECPT_PYCHECK(p))
+    EC_IN(c, d, ECPT_P(p));
+  else
+    return (-1);
+  return (0);
+}
+
+int getecpt(ec_curve *c, ec *d, PyObject *p)
+{
+  if (toecpt(c, d, p)) {
+    PyErr_Format(PyExc_TypeError, "can't convert %.100s to ecpt",
+                p->ob_type->tp_name);
+    return (-1);
+  }
+  return (0);
+}
+
+void getecptout(ec *d, PyObject *p)
+{
+  if (ECPTCURVE_PYCHECK(p))
+    EC_OUT(ECPT_C(p), d, ECPT_P(p));
+  else {
+    assert(ECPT_PYCHECK(p));
+    EC_COPY(d, ECPT_P(p));
+  }
+}
+
+int convecpt(PyObject *o, void *p)
+{
+  if (!ECPT_PYCHECK(o))
+    TYERR("want elliptic curve point");
+  getecptout(p, o);
+  return (1);
+end:
+  return (0);
+}
+
+/*----- Curve points ------------------------------------------------------*/
+
+static int ecbinop(PyObject *x, PyObject *y,
+                  ec_curve **c, PyObject **cobj, ec *xx, ec *yy)
+{
+  if (ECPTCURVE_PYCHECK(x)) *cobj = ECPT_COBJ(x);
+  else if (ECPTCURVE_PYCHECK(y)) *cobj = ECPT_COBJ(y);
+  else return (-1);
+  *c = ECCURVE_C(*cobj);
+  if (toecpt(*c, xx, x) || toecpt(*c, yy, y)) return (-1);
+  return (0);
+}
+
+#define BINOP(name)                                                    \
+  static PyObject *ecpt_py##name(PyObject *x, PyObject *y) {           \
+    ec xx = EC_INIT, yy = EC_INIT, zz = EC_INIT;                       \
+    PyObject *cobj;                                                    \
+    ec_curve *c;                                                       \
+    if (ecbinop(x, y, &c, &cobj, &xx, &yy)) RETURN_NOTIMPL;            \
+    c->ops->name(c, &zz, &xx, &yy);                                    \
+    EC_DESTROY(&xx); EC_DESTROY(&yy);                                  \
+    return (ecpt_pywrap(ECPT_COBJ(x), &zz));                           \
+  }
+BINOP(add)
+BINOP(sub)
+#undef BINOP
+
+#define UNOP(name)                                                     \
+  static PyObject *ecpt_py##name(PyObject *x) {                                \
+    ec zz = EC_INIT;                                                   \
+    ec_curve *c = ECPT_C(x);                                           \
+    c->ops->name(c, &zz, ECPT_P(x));                                   \
+    return (ecpt_pywrap(ECPT_COBJ(x), &zz));                           \
+  }
+UNOP(neg)
+#undef UNOP
+
+static PyObject *ecpt_pyid(PyObject *x) { RETURN_OBJ(x); }
+
+static int ecpt_pynonzerop(PyObject *x) { return (!EC_ATINF(ECPT_P(x))); }
+
+static void ecpt_pydealloc(PyObject *x)
+{
+  EC_DESTROY(ECPT_P(x));
+  Py_DECREF(ECPT_COBJ(x));
+  FREEOBJ(x);
+}
+
+static PyObject *ecpt_pymul(PyObject *x, PyObject *y)
+{
+  mp *xx;
+  ec zz = EC_INIT;
+
+  if (ECPT_PYCHECK(x)) { PyObject *t; t = x; x = y; y = t; }
+  if (!ECPT_PYCHECK(y)) RETURN_NOTIMPL;
+  if (FE_PYCHECK(x) && FE_F(x)->ops->ty == FTY_PRIME)
+    xx = F_OUT(FE_F(x), MP_NEW, FE_X(x));
+  else if ((xx = implicitmp(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 Py_hash_t ecpt_pyhash(PyObject *me)
+{
+  uint32 h;
+  ec p = EC_INIT;
+
+  getecptout(&p, me);
+  if (EC_ATINF(&p)) h = 0x81d81a94;
+  else h = 0xe0fdd039 ^ (2*mphash(p.x)) ^ (3*mphash(p.y));
+  EC_DESTROY(&p);
+  return (h%LONG_MAX);
+}
+
+static PyObject *ecpt_pyrichcompare(PyObject *x, PyObject *y, int op)
+{
+  ec p = EC_INIT, q = EC_INIT;
+  int b;
+  PyObject *rc = 0;
+
+  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;
+    default: TYERR("elliptic curve points are unordered");
+  }
+  rc = getbool(b);
+end:
+  EC_DESTROY(&p);
+  EC_DESTROY(&q);
+  return (rc);
+}
+
+static PyObject *epmeth_oncurvep(PyObject *me)
+{
+  return (getbool(EC_ATINF(ECPT_P(me)) ||
+                 !EC_CHECK(ECPT_C(me), ECPT_P(me))));
+}
+
+static PyObject *epmeth_dbl(PyObject *me)
+{
+  ec p = EC_INIT;
+  EC_DBL(ECPT_C(me), &p, ECPT_P(me));
+  return (ecpt_pywrap(ECPT_COBJ(me), &p));
+}
+
+static PyObject *epmeth_tobuf(PyObject *me)
+{
+  buf b;
+  ec p = EC_INIT;
+  PyObject *rc;
+  size_t n;
+
+  getecptout(&p, me);
+  if (EC_ATINF(&p))
+    n = 2;
+  else
+    n = mp_octets(p.x) + mp_octets(p.y) + 6;
+  rc = bytestring_pywrap(0, n);
+  buf_init(&b, BIN_PTR(rc), n);
+  buf_putec(&b, &p);
+  assert(BOK(&b));
+  BIN_SETLEN(rc, BLEN(&b));
+  EC_DESTROY(&p);
+  return (rc);
+}
+
+static PyObject *epmeth_toraw(PyObject *me)
+{
+  buf b;
+  PyObject *rc;
+  char *p;
+  ec_curve *c = ECPT_C(me);
+  ec pp = EC_INIT;
+  int len;
+
+  len = c->f->noctets * 2 + 1;
+  rc = bytestring_pywrap(0, len);
+  p = BIN_PTR(rc);
+  buf_init(&b, p, len);
+  EC_OUT(c, &pp, ECPT_P(me));
+  ec_putraw(c, &b, &pp);
+  EC_DESTROY(&pp);
+  BIN_SETLEN(rc, BLEN(&b));
+  return (rc);
+}
+
+static PyObject *epmeth_ec2osp(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  buf b;
+  PyObject *rc;
+  char *p;
+  ec_curve *c = ECPT_C(me);
+  ec pp = EC_INIT;
+  unsigned f = EC_EXPLY;
+  int len;
+  static const char *const kwlist[] = { "flags", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:ec2osp", KWLIST,
+                                  convuint, &f))
+    return (0);
+  len = c->f->noctets * 2 + 1;
+  rc = bytestring_pywrap(0, len);
+  p = BIN_PTR(rc);
+  buf_init(&b, p, len);
+  EC_OUT(c, &pp, ECPT_P(me));
+  if (ec_ec2osp(c, f, &b, &pp)) {
+    Py_DECREF(rc); rc = 0;
+    VALERR("invalid flags");
+  }
+  EC_DESTROY(&pp);
+  BIN_SETLEN(rc, BLEN(&b));
+end:
+  return (rc);
+}
+
+static PyObject *epmeth_frombuf(PyObject *me, PyObject *arg)
+{
+  struct bin in;
+  buf b;
+  PyObject *rc = 0;
+  ec pp = EC_INIT;
+
+  if (!PyArg_ParseTuple(arg, "O&:frombuf", convbin, &in)) goto end;
+  buf_init(&b, (/*unconst*/ void *)in.p, in.sz);
+  if (buf_getec(&b, &pp)) VALERR("malformed data");
+  rc = Py_BuildValue("(NN)", ecpt_pywrapout(me, &pp),
+                    bytestring_pywrapbuf(&b));
+end:
+  return (rc);
+}
+
+static PyObject *epmeth_parse(PyObject *me, PyObject *arg)
+{
+  char *p;
+  qd_parse qd;
+  PyObject *rc = 0;
+  int paren;
+  ec pp = EC_INIT;
+
+  if (!PyArg_ParseTuple(arg, "s:parse", &p)) goto end;
+  qd.p = p; qd.e = 0;
+  qd_skipspc(&qd); paren = qd_delim(&qd, '(');
+  if (!ec_ptparse(&qd, &pp)) VALERR(qd.e);
+  qd_skipspc(&qd); if (paren && !qd_delim(&qd, ')'))
+    { EC_DESTROY(&pp); VALERR("missing `)'"); }
+  rc = Py_BuildValue("(Ns)", ecpt_pywrapout(me, &pp), qd.p);
+end:
+  return (rc);
+}
+
+static PyObject *epmeth_fromraw(PyObject *me, PyObject *arg)
+{
+  struct bin in;
+  buf b;
+  PyObject *rc = 0;
+  ec_curve *cc;
+  ec pp = EC_INIT;
+
+  if (!PyArg_ParseTuple(arg, "O&:fromraw", convbin, &in)) return (0);
+  buf_init(&b, (/*unconst*/ void *)in.p, in.sz);
+  cc = ECCURVE_C(me);
+  if (ec_getraw(cc, &b, &pp))
+    VALERR("bad point");
+  EC_IN(cc, &pp, &pp);
+  rc = Py_BuildValue("(NN)", ecpt_pywrap(me, &pp), bytestring_pywrapbuf(&b));
+end:
+  return (rc);
+}
+
+static PyObject *epmeth_os2ecp(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  struct bin in;
+  buf b;
+  PyObject *rc = 0;
+  ec_curve *cc;
+  unsigned f = EC_XONLY | EC_LSB | EC_SORT | EC_EXPLY;
+  ec pp = EC_INIT;
+  static const char *const kwlist[] = { "buf", "flags", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:os2ecp", KWLIST,
+                                  convbin, &in, convuint, &f))
+    return (0);
+  buf_init(&b, (/*unconst*/ void *)in.p, in.sz);
+  cc = ECCURVE_C(me);
+  if (ec_os2ecp(cc, f, &b, &pp)) VALERR("bad point");
+  EC_IN(cc, &pp, &pp);
+  rc = Py_BuildValue("(NN)", ecpt_pywrap(me, &pp), bytestring_pywrapbuf(&b));
+end:
+  return (rc);
+}
+
+static PyObject *epncget_ix(PyObject *me, void *hunoz)
+{
+  ec p = EC_INIT;
+  PyObject *rc;
+  if (EC_ATINF(ECPT_P(me))) RETURN_NONE;
+  getecptout(&p, me);
+  rc = mp_pywrap(MP_COPY(p.x));
+  EC_DESTROY(&p);
+  return (rc);
+}
+
+static PyObject *epncget_iy(PyObject *me, void *hunoz)
+{
+  ec p = EC_INIT;
+  PyObject *rc;
+  if (EC_ATINF(ECPT_P(me))) RETURN_NONE;
+  getecptout(&p, me);
+  rc = mp_pywrap(MP_COPY(p.y));
+  EC_DESTROY(&p);
+  return (rc);
+}
+
+static PyObject *epncget_point(PyObject *me, void *hunoz)
+  { RETURN_ME; }
+
+static PyObject *epget_point(PyObject *me, void *hunoz)
+{
+  ec p = EC_INIT;
+  getecptout(&p, me);
+  return (ecpt_pywrapout(ecpt_pytype, &p));
+}
+
+static PyObject *epget_x(PyObject *me, void *hunoz)
+{
+  ec_curve *c = ECPT_C(me);
+  ec *pp = ECPT_P(me);
+  PyObject *fobj = ECPT_FOBJ(me);
+  ec p = EC_INIT;
+  PyObject *rc;
+
+  if (EC_ATINF(pp)) RETURN_NONE;
+  EC_OUT(c, &p, pp);
+  rc = fe_pywrap(fobj, F_IN(c->f, MP_NEW, p.x));
+  EC_DESTROY(&p);
+  return (rc);
+}
+
+static PyObject *epget_y(PyObject *me, void *hunoz)
+{
+  ec_curve *c = ECPT_C(me);
+  ec *pp = ECPT_P(me);
+  PyObject *fobj = ECPT_FOBJ(me);
+  ec p = EC_INIT;
+  PyObject *rc;
+
+  if (EC_ATINF(pp)) RETURN_NONE;
+  EC_OUT(c, &p, pp);
+  rc = fe_pywrap(fobj, F_IN(c->f, MP_NEW, p.y));
+  EC_DESTROY(&p);
+  return (rc);
+}
+
+static PyObject *epget__x(PyObject *me, void *hunoz)
+{
+  if (EC_ATINF(ECPT_P(me))) RETURN_NONE;
+  return (fe_pywrap(ECPT_FOBJ(me), MP_COPY(ECPT_P(me)->x)));
+}
+
+static PyObject *epget__y(PyObject *me, void *hunoz)
+{
+  if (EC_ATINF(ECPT_P(me))) RETURN_NONE;
+  return (fe_pywrap(ECPT_FOBJ(me), MP_COPY(ECPT_P(me)->y)));
+}
+
+static PyObject *epget__z(PyObject *me, void *hunoz)
+{
+  if (EC_ATINF(ECPT_P(me)) || !ECPT_P(me)->z) RETURN_NONE;
+  return (fe_pywrap(ECPT_FOBJ(me), MP_COPY(ECPT_P(me)->z)));
+}
+
+static mp *coord_in(field *f, PyObject *x)
+{
+  mp *xx;
+  if (FE_PYCHECK(x) && (FE_F(x) == f || field_samep(FE_F(x), f)))
+    return (MP_COPY(FE_X(x)));
+  else if ((xx = getmp(x)) == 0)
+    return (0);
+  else
+    return (F_IN(f, xx, xx));
+}
+
+static int ecptxl_3(ec_curve *c, ec *p,
+                   PyObject *x, PyObject *y, PyObject *z)
+{
+  int rc = -1;
+
+  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))
+    goto end;
+  if (!p->z) p->z = MP_COPY(c->f->one); /* just in case */
+  rc = 0;
+end:
+  return (rc);
+}
+
+static int ecptxl_2(ec_curve *c, ec *p, PyObject *x, PyObject *y)
+{
+  int rc = -1;
+
+  if (!x || !y) TYERR("missing argument");
+  if ((p->x = getmp(x)) == 0 ||
+      (p->y = getmp(y)) == 0)
+    goto end;
+  if (c) EC_IN(c, p, p);
+  rc = 0;
+end:
+  return (rc);
+}
+
+static int ecptxl_1(ec_curve *c, ec *p, PyObject *x)
+{
+  int rc = -1;
+  PyObject *y = 0, *z = 0, *t = 0, *u = 0;
+  mp *xx = 0;
+  qd_parse qd;
+  int paren;
+
+  Py_XINCREF(x);
+  if (!x || x == Py_None)
+    /*cool*/;
+  else if (ECPT_PYCHECK(x)) {
+    getecptout(p, x);
+    goto fix;
+  } else if (TEXT_CHECK(x)) {
+    qd.p = TEXT_PTR(x); qd.e = 0;
+    qd_skipspc(&qd); paren = qd_delim(&qd, '(');
+    if (!ec_ptparse(&qd, p)) VALERR(qd.e);
+    qd_skipspc(&qd); if (paren && !qd_delim(&qd, ')'))
+      { EC_DESTROY(p); VALERR("missing `)'"); }
+    qd_skipspc(&qd); if (!qd_eofp(&qd)) VALERR("junk at eof");
+    goto fix;
+  } else if (c && (xx = tomp(x)) != 0) {
+    xx = F_IN(c->f, xx, xx);
+    if (!EC_FIND(c, p, xx)) VALERR("not on the curve");
+  } else if ((t = PyObject_GetIter(x)) != 0) {
+    Py_DECREF(x);
+    x = PyIter_Next(t); if (!x) goto enditer;
+    y = PyIter_Next(t); if (!y) goto enditer;
+    z = PyIter_Next(t); if (!z && PyErr_Occurred()) goto end;
+    if (z) {
+      u = PyIter_Next(t);
+      if (u) goto enditer;
+      else if (PyErr_Occurred()) goto end;
+    }
+    rc = !z ? ecptxl_2(c, p, x, y) : ecptxl_3(c, p, x, y, z);
+    goto end;
+  } else {
+    PyErr_Clear();
+    TYERR("can't convert to curve point");
+  }
+  goto ok;
+
+enditer:
+  if (PyErr_Occurred()) goto end;
+  TYERR("expected sequence of 2 or 3 items");
+fix:
+  if (c) EC_IN(c, p, p);
+ok:
+  rc = 0;
+end:
+  Py_XDECREF(x); Py_XDECREF(y); Py_XDECREF(z); Py_XDECREF(t); Py_XDECREF(u);
+  mp_drop(xx);
+  return (rc);
+}
+
+static int ecptxl(ec_curve *c, ec *p, PyObject *x, PyObject *y, PyObject *z)
+{
+  if (z)
+    return (ecptxl_3(c, p, x, y, z));
+  else if (y)
+    return (ecptxl_2(c, p, x, y));
+  else
+    return (ecptxl_1(c, p, x));
+}
+
+static PyObject *ecptnc_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  PyObject *x = 0, *y = 0, *z = 0;
+  ec p = EC_INIT;
+  static const char *const kwlist[] = { "x", "y", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OO:new", KWLIST, &x, &y) ||
+      ecptxl(0, &p, x, y, z))
+    goto end;
+  return (ecpt_pywrapout(ty, &p));
+end:
+  mp_drop(p.x); mp_drop(p.y); mp_drop(p.z);
+  return (0);
+}
+
+static PyObject *ecpt_pyint(PyObject *me)
+{
+  ec p = EC_INIT;
+  long l;
+  PyObject *rc = 0;
+  if (EC_ATINF(ECPT_P(me))) VALERR("point at infinity");
+  getecptout(&p, me);
+  if (!mp_tolong_checked(p.x, &l, 0)) rc = PyInt_FromLong(l);
+  else rc = mp_topylong(p.x);
+end:
+  EC_DESTROY(&p);
+  return (rc);
+}
+
+#ifdef PY2
+static PyObject *ecpt_pylong(PyObject *me)
+{
+  ec p = EC_INIT;
+  PyObject *rc = 0;
+  if (EC_ATINF(ECPT_P(me))) VALERR("point at infinity");
+  getecptout(&p, me);
+  rc = mp_topylong(p.x);
+end:
+  EC_DESTROY(&p);
+  return (rc);
+}
+#endif
+
+static PyObject *ecpt_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  PyObject *x = 0, *y = 0, *z = 0;
+  ec p = EC_INIT;
+  static const char *const kwlist[] = { "x", "y", "z", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|OOO:new", KWLIST,
+                                  &x, &y, &z) ||
+      ecptxl(ECCURVE_C(ty), &p, x, y, z))
+    goto end;
+  return (ecpt_pywrap((PyObject *)ty, &p));
+end:
+  mp_drop(p.x); mp_drop(p.y); mp_drop(p.z);
+  return (0);
+}
+
+static const PyGetSetDef ecptnc_pygetset[] = {
+#define GETSETNAME(op, name) epnc##op##_##name
+  GET  (ix,            "P.ix -> integer x coordinate of P")
+  GET  (iy,            "P.iy -> integer y coordinate of P")
+  GET  (point,         "P.point -> standalone curve point (no-op)")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef ecptnc_pymethods[] = {
+#define METHNAME(func) epmeth_##func
+  NAMETH(tobuf,                "X.tobuf() -> BIN")
+  CMTH (frombuf,       "frombuf(STR) -> (P, REST)")
+  CMTH (parse,         "parse(STR) -> (P, REST)")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyNumberMethods ecpt_pynumber = {
+  0,                                   /* @nb_add@ */
+  0,                                   /* @nb_subtract@ */
+  0,                                   /* @nb_multiply@ */
+#ifdef PY2
+  0,                                   /* @nb_divide@ */
+#endif
+  0,                                   /* @nb_remainder@ */
+  0,                                   /* @nb_divmod@ */
+  0,                                   /* @nb_power@ */
+  0,                                   /* @nb_negative@ */
+  0,                                   /* @nb_positive@ */
+  0,                                   /* @nb_absolute@ */
+  ecpt_pynonzerop,                     /* @nb_nonzero@ */
+  0,                                   /* @nb_invert@ */
+  0,                                   /* @nb_lshift@ */
+  0,                                   /* @nb_rshift@ */
+  0,                                   /* @nb_and@ */
+  0,                                   /* @nb_xor@ */
+  0,                                   /* @nb_or@ */
+#ifdef PY2
+  0,                                   /* @nb_coerce@ */
+#endif
+  ecpt_pyint,                          /* @nb_int@ */
+  PY23(ecpt_pylong, 0),                        /* @nb_long@ */
+  0,                                   /* @nb_float@ */
+#ifdef PY2
+  0,                                   /* @nb_oct@ */
+  0,                                   /* @nb_hex@ */
+#endif
+
+  0,                                   /* @nb_inplace_add@ */
+  0,                                   /* @nb_inplace_subtract@ */
+  0,                                   /* @nb_inplace_multiply@ */
+#ifdef PY2
+  0,                                   /* @nb_inplace_divide@ */
+#endif
+  0,                                   /* @nb_inplace_remainder@ */
+  0,                                   /* @nb_inplace_power@ */
+  0,                                   /* @nb_inplace_lshift@ */
+  0,                                   /* @nb_inplace_rshift@ */
+  0,                                   /* @nb_inplace_and@ */
+  0,                                   /* @nb_inplace_xor@ */
+  0,                                   /* @nb_inplace_or@ */
+
+  0,                                   /* @nb_floor_divide@ */
+  0,                                   /* @nb_true_divide@ */
+  0,                                   /* @nb_inplace_floor_divide@ */
+  0,                                   /* @nb_inplace_true_divide@ */
+};
+
+static const PyTypeObject ecpt_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "ECPt",                              /* @tp_name@ */
+  sizeof(ecpt_pyobj),                  /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  ecpt_pydealloc,                      /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  PYNUMBER(ecpt),                      /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  ecpt_pyhash,                         /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_CHECKTYPES |
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "ECPt([X, [Y]]): elliptic curve points, not associated with any curve.\n"
+  "  X alone may be None, an existing point, a string 'X, Y', an\n"
+  "  x-coordinate, or a pair (X, Y); X and Y should be a coordinate pair.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  ecpt_pyrichcompare,                  /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(ecptnc),                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(ecptnc),                    /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  ecptnc_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyMemberDef ecpt_pymembers[] = {
+#define MEMBERSTRUCT ecpt_pyobj
+  MEMRNM(curve, T_OBJECT, PY23(ob_type, ob_base.ob_type), READONLY,
+                                   "P.curve -> elliptic curve containing P")
+#undef MEMBERSTRUCT
+  { 0 }
+};
+
+static const PyGetSetDef ecpt_pygetset[] = {
+#define GETSETNAME(op, name) ep##op##_##name
+  GET  (point,         "P.point -> standalone curve point")
+  GET  (x,             "P.x -> Cartesian x coordinate of P")
+  GET  (y,             "P.y -> Cartesian y coordinate of P")
+  GET  (_x,            "P._x -> internal x coordinate of P")
+  GET  (_y,            "P._y -> internal y coordinate of P")
+  GET  (_z,            "P._z -> internal z coordinate of P, or None")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef ecpt_pymethods[] = {
+#define METHNAME(func) epmeth_##func
+  NAMETH(toraw,                "X.toraw() -> BIN")
+  KWMETH(ec2osp,       "X.ec2osp([flags = EC_EXPLY]) -> BIN")
+  NAMETH(dbl,          "X.dbl() -> X + X")
+  NAMETH(oncurvep,     "X.oncurvep() -> BOOL")
+  CMTH (fromraw,       "fromraw(STR) -> (P, REST)")
+  KWCMTH(os2ecp,       "os2ecp(STR, [flags = ...]) -> (P, REST)")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyNumberMethods ecptcurve_pynumber = {
+  ecpt_pyadd,                          /* @nb_add@ */
+  ecpt_pysub,                          /* @nb_subtract@ */
+  ecpt_pymul,                          /* @nb_multiply@ */
+#ifdef PY2
+  0,                                   /* @nb_divide@ */
+#endif
+  0,                                   /* @nb_remainder@ */
+  0,                                   /* @nb_divmod@ */
+  0,                                   /* @nb_power@ */
+  ecpt_pyneg,                          /* @nb_negative@ */
+  ecpt_pyid,                           /* @nb_positive@ */
+  0,                                   /* @nb_absolute@ */
+  0,                                   /* @nb_nonzero@ */
+  0,                                   /* @nb_invert@ */
+  0,                                   /* @nb_lshift@ */
+  0,                                   /* @nb_rshift@ */
+  0,                                   /* @nb_and@ */
+  0,                                   /* @nb_xor@ */
+  0,                                   /* @nb_or@ */
+#ifdef PY2
+  0,                                   /* @nb_coerce@ */
+#endif
+  0,                                   /* @nb_int@ */
+  0,                                   /* @nb_long@ */
+  0,                                   /* @nb_float@ */
+#ifdef PY2
+  0,                                   /* @nb_oct@ */
+  0,                                   /* @nb_hex@ */
+#endif
+
+  0,                                   /* @nb_inplace_add@ */
+  0,                                   /* @nb_inplace_subtract@ */
+  0,                                   /* @nb_inplace_multiply@ */
+  0,                                   /* @nb_inplace_divide@ */
+  0,                                   /* @nb_inplace_remainder@ */
+  0,                                   /* @nb_inplace_power@ */
+  0,                                   /* @nb_inplace_lshift@ */
+  0,                                   /* @nb_inplace_rshift@ */
+  0,                                   /* @nb_inplace_and@ */
+  0,                                   /* @nb_inplace_xor@ */
+  0,                                   /* @nb_inplace_or@ */
+
+  0,                                   /* @nb_floor_divide@ */
+  0,                                   /* @nb_true_divide@ */
+  0,                                   /* @nb_inplace_floor_divide@ */
+  0,                                   /* @nb_inplace_true_divide@ */
+};
+
+static const PyTypeObject ecptcurve_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "ECPtCurve",                         /* @tp_name@ */
+  sizeof(ecpt_pyobj),                  /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  ecpt_pydealloc,                      /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  PYNUMBER(ecptcurve),                 /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_CHECKTYPES |
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Elliptic curve points; abstract base class for points on given curves.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(ecpt),                     /* @tp_methods@ */
+  PYMEMBERS(ecpt),                     /* @tp_members@ */
+  PYGETSET(ecpt),                      /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Elliptic curves themselves ----------------------------------------*/
+
+static PyObject *eccurve_pyrichcompare(PyObject *x, PyObject *y, int op)
+{
+  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; break;
+    default: TYERR("can't order elliptic curves");
+  }
+  return (getbool(b));
+end:
+  return (0);
+}
+
+static PyObject *ecmmul_id(PyObject *me)
+  { ec p = EC_INIT; return (ecpt_pywrap(me, &p)); }
+
+static int ecmmul_fill(void *pp, PyObject *me, PyObject *x, PyObject *m)
+{
+  ec_mulfactor *f = pp;
+
+  EC_CREATE(&f->base);
+  if (getecpt(ECCURVE_C(me), &f->base, x) ||
+      (f->exp = getmp(m)) == 0)
+    return (-1);
+  return (0);
+}
+
+static PyObject *ecmmul_exp(PyObject *me, void *pp, size_t n)
+{
+  ec p = EC_INIT;
+  ec_immul(ECCURVE_C(me), &p, pp, n);
+  return (ecpt_pywrap(me, &p));
+}
+
+static void ecmmul_drop(void *pp)
+{
+  ec_mulfactor *f = pp;
+  EC_DESTROY(&f->base);
+  MP_DROP(f->exp);
+}
+
+static PyObject *ecmeth_mmul(PyObject *me, PyObject *arg)
+{
+  return (mexp_common(me, arg, sizeof(ec_mulfactor),
+                     ecmmul_id, ecmmul_fill, ecmmul_exp, ecmmul_drop));
+}
+
+static void eccurve_pydealloc(PyObject *me)
+{
+  ec_destroycurve(ECCURVE_C(me));
+  Py_DECREF(ECCURVE_FOBJ(me));
+  PyType_Type.tp_dealloc(me);
+}
+
+static PyObject *ecmeth_find(PyObject *me, PyObject *arg)
+{
+  PyObject *x;
+  mp *xx = 0;
+  ec p = EC_INIT;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O:find", &x)) goto end;
+  if (FIELD_PYCHECK(x) && FE_F(x) == ECCURVE_C(me)->f)
+    xx = MP_COPY(FE_X(x));
+  else if ((xx = getmp(x)) == 0)
+    goto end;
+  else
+    xx = F_IN(ECCURVE_C(me)->f, xx, xx);
+  if (EC_FIND(ECCURVE_C(me), &p, xx) == 0)
+    VALERR("not on the curve");
+  rc = ecpt_pywrap(me, &p);
+end:
+  if (xx) MP_DROP(xx);
+  return (rc);
+}
+
+static PyObject *ecmeth_rand(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "rng", 0 };
+  grand *r = &rand_global;
+  ec p = EC_INIT;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:rand", KWLIST,
+                                  convgrand, &r))
+    return (0);
+  ec_rand(ECCURVE_C(me), &p, r);
+  EC_IN(ECCURVE_C(me), &p, &p);
+  return (ecpt_pywrap(me, &p));
+}
+
+static PyObject *ecmeth_parse(PyObject *me, PyObject *arg)
+{
+  char *p;
+  qd_parse qd;
+  ec_curve *c;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "s:parse", &p)) goto end;
+  qd.p = p; qd.e = 0;
+  if ((c = ec_curveparse(&qd)) == 0) VALERR(qd.e);
+  rc = Py_BuildValue("(Ns)", eccurve_pywrap(0, c), qd.p);
+end:
+  return (rc);
+}
+
+static PyObject *eccurve_dopywrap(PyTypeObject *ty,
+                                 PyObject *fobj, ec_curve *c)
+{
+  eccurve_pyobj *cobj = newtype(ty, 0, c->ops->name);
+  cobj->c = c;
+  cobj->fobj = fobj;
+  cobj->ty.ht_type.tp_basicsize = sizeof(ecpt_pyobj);
+  cobj->ty.ht_type.tp_base = ecptcurve_pytype;
+  Py_INCREF(ecptcurve_pytype);
+  cobj->ty.ht_type.tp_flags = (Py_TPFLAGS_DEFAULT |
+                              Py_TPFLAGS_BASETYPE |
+                              Py_TPFLAGS_CHECKTYPES |
+                              Py_TPFLAGS_HEAPTYPE);
+  cobj->ty.ht_type.tp_alloc = PyType_GenericAlloc;
+  cobj->ty.ht_type.tp_free = 0;
+  cobj->ty.ht_type.tp_new = ecpt_pynew;
+  typeready(&cobj->ty.ht_type);
+  return ((PyObject *)cobj);
+}
+
+PyObject *eccurve_pywrap(PyObject *fobj, ec_curve *c)
+{
+  PyTypeObject *ty;
+
+  if (!fobj)
+    fobj = field_pywrap(c->f);
+  else
+    Py_INCREF(fobj);
+  assert(FIELD_F(fobj) == c->f);
+  if (STRCMP(EC_NAME(c), ==, "prime"))
+    ty = ecprimecurve_pytype;
+  else if (STRCMP(EC_NAME(c), ==, "primeproj"))
+    ty = ecprimeprojcurve_pytype;
+  else if (STRCMP(EC_NAME(c), ==, "bin"))
+    ty = ecbincurve_pytype;
+  else if (STRCMP(EC_NAME(c), ==, "binproj"))
+    ty = ecbinprojcurve_pytype;
+  else
+    abort();
+  return (eccurve_dopywrap(ty, fobj, c));
+}
+
+static PyObject *eccurve_pynew(PyTypeObject *ty,
+                              ec_curve *(*make)(field *, mp *, mp *),
+                              PyObject *arg, PyObject *kw)
+{
+  PyObject *fobj;
+  PyObject *cobj = 0;
+  static const char *const kwlist[] = { "field", "a", "b", 0 };
+  mp *aa = 0, *bb = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O&O&", KWLIST,
+                                  field_pytype, &fobj,
+                                  convmp, &aa, convmp, &bb))
+    goto end;
+  Py_INCREF(fobj);
+  cobj = eccurve_dopywrap(ty, fobj, make(FIELD_F(fobj), aa, bb));
+end:
+  if (aa) MP_DROP(aa);
+  if (bb) MP_DROP(bb);
+  return (cobj);
+}
+
+static PyObject *ecget_name(PyObject *me, void *hunoz)
+  { return (TEXT_FROMSTR(EC_NAME(ECCURVE_C(me)))); }
+
+static PyObject *ecget_a(PyObject *me, void *hunoz)
+  { return (fe_pywrap(ECCURVE_FOBJ(me), MP_COPY(ECCURVE_C(me)->a))); }
+
+static PyObject *ecget_b(PyObject *me, void *hunoz)
+  { return (fe_pywrap(ECCURVE_FOBJ(me), MP_COPY(ECCURVE_C(me)->b))); }
+
+static PyObject *ecget_field(PyObject *me, void *hunoz)
+  { RETURN_OBJ(ECCURVE_FOBJ(me)); }
+
+static PyObject *ecget_inf(PyObject *me, void *hunoz)
+  { ec inf = EC_INIT; return (ecpt_pywrap(me, &inf)); }
+
+static const PyGetSetDef eccurve_pygetset[] = {
+#define GETSETNAME(op, name) ec##op##_##name
+  GET  (name,          "E.name -> name of this kind of curve")
+  GET  (a,             "E.a -> first parameter of curve")
+  GET  (b,             "E.b -> second parameter of curve")
+  GET  (field,         "E.field -> finite field containing this curve")
+  GET  (inf,           "E.inf -> point at infinity of this curve")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef eccurve_pymethods[] = {
+#define METHNAME(name) ecmeth_##name
+  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")
+  SMTH (parse,         "parse(STR) -> (E, REST)")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject eccurve_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "ECCurve",                           /* @tp_name@ */
+  sizeof(eccurve_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  eccurve_pydealloc,                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "An elliptic curve.  Abstract class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  eccurve_pyrichcompare,               /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(eccurve),                  /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(eccurve),                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *ecprimecurve_pynew(PyTypeObject *ty,
+                                   PyObject *arg, PyObject *kw)
+{
+  return (eccurve_pynew(ty, ec_prime, arg, kw));
+}
+
+static const PyTypeObject ecprimecurve_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "ECPrimeCurve",                      /* @tp_name@ */
+  sizeof(eccurve_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  eccurve_pydealloc,                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "ECPrimeCurve(FIELD, A, B): an elliptic curve over a prime field.\n"
+  "  Use ECPrimeProjCurve instead.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  ecprimecurve_pynew,                  /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *ecprimeprojcurve_pynew(PyTypeObject *ty,
+                                       PyObject *arg, PyObject *kw)
+{
+  return (eccurve_pynew(ty, ec_primeproj, arg, kw));
+}
+
+static const PyTypeObject ecprimeprojcurve_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "ECPrimeProjCurve",                  /* @tp_name@ */
+  sizeof(eccurve_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  eccurve_pydealloc,                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "ECPrimeProjCurve(FIELD, A, B): an elliptic curve over a prime field\n"
+  "  using projective coordinates.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  ecprimeprojcurve_pynew,              /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *ecbincurve_pynew(PyTypeObject *ty,
+                                 PyObject *arg, PyObject *kw)
+{
+  return (eccurve_pynew(ty, ec_bin, arg, kw));
+}
+
+static const PyTypeObject ecbincurve_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "ECBinCurve",                                /* @tp_name@ */
+  sizeof(eccurve_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  eccurve_pydealloc,                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "ECBinCurve(FIELD, A, B): an elliptic curve over a binary field.\n"
+  "  Use ECBinProjCurve instead.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  ecbincurve_pynew,                    /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *ecbinprojcurve_pynew(PyTypeObject *ty,
+                                     PyObject *arg, PyObject *kw)
+{
+  return (eccurve_pynew(ty, ec_binproj, arg, kw));
+}
+
+static const PyTypeObject ecbinprojcurve_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "ECBinProjCurve",                    /* @tp_name@ */
+  sizeof(eccurve_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  eccurve_pydealloc,                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "ECBinProjCurve(FIELD, A, B): an elliptic curve over a binary field,\n"
+  "  using projective coordinates.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  ecbinprojcurve_pynew,                        /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Curve info --------------------------------------------------------*/
+
+void ecinfo_copy(ec_info *eic, const ec_info *ei)
+{
+  eic->c = eccurve_copy(ei->c);
+  EC_CREATE(&eic->g);
+  EC_COPY(&eic->g, &ei->g);
+  eic->r = MP_COPY(ei->r);
+  eic->h = MP_COPY(ei->h);
+}
+
+PyObject *ecinfo_pywrap(ec_info *ei)
+{
+  ecinfo_pyobj *o;
+
+  o = PyObject_NEW(ecinfo_pyobj, ecinfo_pytype);
+  o->ei = *ei;
+  o->cobj = eccurve_pywrap(0, o->ei.c);
+  Py_INCREF(o->cobj);
+  return ((PyObject *)o);
+}
+
+static void ecinfo_pydealloc(PyObject *me)
+{
+  ec_info *ei = ECINFO_EI(me);
+  EC_DESTROY(&ei->g);
+  MP_DROP(ei->r);
+  MP_DROP(ei->h);
+  Py_DECREF(ECINFO_COBJ(me));
+  FREEOBJ(me);
+}
+
+static PyObject *ecinfo_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  ec_info ei = { 0 };
+  PyObject *e, *g;
+  static const char *const kwlist[] = { "curve", "G", "r", "h", 0 };
+  ecinfo_pyobj *rc = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O!O&O&:new", KWLIST,
+                                  eccurve_pytype, &e, ecpt_pytype, &g,
+                                  convmp, &ei.r, convmp, &ei.h))
+    goto end;
+  if (ECPT_C(g) != ECCURVE_C(e) && !ec_samep(ECPT_C(g), ECCURVE_C(e)))
+    TYERR("point not from this curve");
+  ei.c = ECCURVE_C(e);
+  EC_CREATE(&ei.g);
+  EC_OUT(ei.c, &ei.g, ECPT_P(g));
+  rc = (ecinfo_pyobj *)ty->tp_alloc(ty, 0);
+  rc->ei = ei;
+  rc->cobj = e;
+  Py_INCREF(rc->cobj);
+  return ((PyObject *)rc);
+
+end:
+  mp_drop(ei.r);
+  mp_drop(ei.h);
+  return (0);
+}
+
+static PyObject *eimeth_parse(PyObject *me, PyObject *arg)
+{
+  char *p;
+  qd_parse qd;
+  ec_info ei;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "s:parse", &p)) goto end;
+  qd.p = p; qd.e = 0;
+  if (ec_infoparse(&qd, &ei)) VALERR(qd.e);
+  rc = Py_BuildValue("(Ns)", ecinfo_pywrap(&ei), qd.p);
+end:
+  return (rc);
+}
+
+static PyObject *ecinfo_pyrichcompare(PyObject *x, PyObject *y, int op)
+{
+  int b = ec_sameinfop(ECINFO_EI(x), ECINFO_EI(y));
+  switch (op) {
+    case Py_EQ: break;
+    case Py_NE: b = !b;
+    default: TYERR("can't order elliptic curve infos");
+  }
+  return (getbool(b));
+end:
+  return (0);
+}
+
+static PyObject *eimeth_check(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "rng", 0 };
+  grand *r = &rand_global;
+  const char *p;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:check", KWLIST,
+                                  convgrand, &r))
+    goto end;
+  if ((p = ec_checkinfo(ECINFO_EI(me), r)) != 0)
+    VALERR(p);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *eiget_curve(PyObject *me, void *hunoz)
+  { RETURN_OBJ(ECINFO_COBJ(me)); }
+
+static PyObject *eiget_G(PyObject *me, void *hunoz)
+{
+  ec_info *ei = ECINFO_EI(me);
+  ec p = EC_INIT;
+  EC_IN(ei->c, &p, &ei->g);
+  return (ecpt_pywrap(ECINFO_COBJ(me), &p));
+}
+
+static PyObject *eiget_r(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(ECINFO_EI(me)->r))); }
+
+static PyObject *eiget_h(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(ECINFO_EI(me)->h))); }
+
+static const PyGetSetDef ecinfo_pygetset[] = {
+#define GETSETNAME(op, name) ei##op##_##name
+  GET  (curve,         "I.curve -> the elliptic curve")
+  GET  (G,             "I.G -> generator point for the group")
+  GET  (r,             "I.r -> order of the group (and hence of G")
+  GET  (h,             "I.h -> cofactor of the group")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef ecinfo_pymethods[] = {
+#define METHNAME(name) eimeth_##name
+  KWMETH(check,                "I.check([rng = rand]) -> None")
+  SMTH (parse,         "parse(STR) -> (I, REST)")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject ecinfo_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "ECInfo",                            /* @tp_name@ */
+  sizeof(ecinfo_pyobj),                        /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  ecinfo_pydealloc,                    /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "ECInfo(CURVE, G, R, H): elliptic curve domain parameters.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  ecinfo_pyrichcompare,                        /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(ecinfo),                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(ecinfo),                    /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  ecinfo_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Setup -------------------------------------------------------------*/
+
+static const struct nameval consts[] = {
+  CONST(EC_XONLY), CONST(EC_YBIT), CONST(EC_LSB),
+  CONST(EC_CMPR), CONST(EC_EXPLY), CONST(EC_SORT),
+  { 0 }
+};
+
+void ec_pyinit(void)
+{
+  INITTYPE(ecpt, root);
+  INITTYPE(ecptcurve, ecpt);
+  INITTYPE(eccurve, type);
+  INITTYPE(ecprimecurve, eccurve);
+  INITTYPE(ecprimeprojcurve, ecprimecurve);
+  INITTYPE(ecbincurve, eccurve);
+  INITTYPE(ecbinprojcurve, ecbincurve);
+  INITTYPE(ecinfo, root);
+}
+
+static const char *ec_namefn(const void *p)
+  { const ecentry *ec = p; return (ec->name); }
+
+static int ec_ixfn(const void *p)
+{
+  const ecentry *ec = p;
+  int i;
+
+  for (i = 0; ectab[i].name; i++)
+    if (ectab[i].data == ec->data) return (i);
+  return (-1);
+}
+
+static PyObject *ec_valfn(int i)
+{
+  ec_info ei;
+
+  ec_infofromdata(&ei, ectab[i].data);
+  return (ecinfo_pywrap(&ei));
+}
+
+void ec_pyinsert(PyObject *mod)
+{
+  INSERT("ECPt", ecpt_pytype);
+  INSERT("ECPtCurve", ecptcurve_pytype);
+  INSERT("ECCurve", eccurve_pytype);
+  INSERT("ECPrimeCurve", ecprimecurve_pytype);
+  INSERT("ECPrimeProjCurve", ecprimeprojcurve_pytype);
+  INSERT("ECBinCurve", ecbincurve_pytype);
+  INSERT("ECBinProjCurve", ecbinprojcurve_pytype);
+  INSERT("ECInfo", ecinfo_pytype);
+  INSERT("eccurves", make_grouptab(ectab, sizeof(*ectab),
+                                  ec_namefn, ec_ixfn, ec_valfn));
+  setconstants(mod, consts);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/field.c b/field.c
new file mode 100644 (file)
index 0000000..2e99efc
--- /dev/null
+++ b/field.c
@@ -0,0 +1,988 @@
+/* -*-c-*-
+ *
+ * Abstract fields
+ *
+ * (c) 2004 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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "catacomb-python.h"
+
+/*----- Various utilities -------------------------------------------------*/
+
+PyTypeObject *field_pytype;
+PyTypeObject *primefield_pytype;
+PyTypeObject *niceprimefield_pytype;
+PyTypeObject *binfield_pytype;
+PyTypeObject *binpolyfield_pytype;
+PyTypeObject *binnormfield_pytype;
+PyTypeObject *fe_pytype;
+
+static PyObject *fe_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  PyObject *x;
+  mp *z;
+  static const char *const kwlist[] = { "x", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:fe", KWLIST, &x))
+    return (0);
+  if (FE_PYCHECK(x) && FE_F(x) == FIELD_F(ty)) RETURN_OBJ(x);
+  if ((z = getmp(x)) == 0) return (0);
+  z = F_IN(FIELD_F(ty), z, z);
+  return (fe_pywrap((PyObject *)ty, z));
+}
+
+static PyObject *field_dopywrap(PyTypeObject *ty, field *f)
+{
+  field_pyobj *fobj = newtype(ty, 0, f->ops->name);
+  fobj->f = f;
+  fobj->ty.ht_type.tp_basicsize = sizeof(fe_pyobj);
+  fobj->ty.ht_type.tp_base = fe_pytype;
+  Py_INCREF(fe_pytype);
+  fobj->ty.ht_type.tp_flags = (Py_TPFLAGS_DEFAULT |
+                              Py_TPFLAGS_BASETYPE |
+                              Py_TPFLAGS_CHECKTYPES |
+                              Py_TPFLAGS_HEAPTYPE);
+  fobj->ty.ht_type.tp_alloc = PyType_GenericAlloc;
+  fobj->ty.ht_type.tp_free = 0;
+  fobj->ty.ht_type.tp_new = fe_pynew;
+  typeready(&fobj->ty.ht_type);
+  return ((PyObject *)fobj);
+}
+
+PyObject *field_pywrap(field *f)
+{
+  PyTypeObject *ty;
+
+  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"))
+    f = field_prime(f->m);
+  else if (STRCMP(F_NAME(f), ==, "niceprime"))
+    f = field_niceprime(f->m);
+  else if (STRCMP(F_NAME(f), ==, "binpoly"))
+    f = field_binpoly(f->m);
+  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
+    abort();
+  return (f);
+}
+
+PyObject *fe_pywrap(PyObject *fobj, mp *x)
+{
+  fe_pyobj *z = PyObject_New(fe_pyobj, (PyTypeObject *)fobj);
+  z->f = FIELD_F(fobj);
+  Py_INCREF(fobj);
+  z->x = x;
+  return ((PyObject *)z);
+}
+
+static mp *implicitfe(field *f, PyObject *o)
+{
+  mp *x;
+
+  if (FE_PYCHECK(o) && FE_F(o) == f) return (MP_COPY(FE_X(o)));
+  switch (f->ops->ty) {
+    case FTY_PRIME: x = implicitmp(o); break;
+    case FTY_BINARY: x = implicitgf(o); break;
+    default: assert(!"huh?");
+  }
+  if (!x) return (0);
+  return (F_IN(f, MP_NEW, x));
+}
+
+/*----- Field elements ----------------------------------------------------*/
+
+static int febinop(PyObject *x, PyObject *y,
+                  field **f, PyObject **fobj, mp **xx, mp **yy)
+{
+  if (FE_PYCHECK(x)) *fobj = FE_FOBJ(x);
+  else if (FE_PYCHECK(y)) *fobj = FE_FOBJ(y);
+  else return (-1);
+  *f = FIELD_F(*fobj);
+  if ((*xx = implicitfe(*f, x)) == 0)
+    return (-1);
+  if ((*yy = implicitfe(*f, y)) == 0) {
+    MP_DROP(*xx);
+    return (-1);
+  }
+  return (0);
+}
+
+#define BINOP(name)                                                    \
+  static PyObject *fe_py##name(PyObject *x, PyObject *y) {             \
+    PyObject *fobj;                                                    \
+    field *ff;                                                         \
+    mp *xx, *yy, *zz;                                                  \
+    if (febinop(x, y, &ff, &fobj, &xx, &yy)) RETURN_NOTIMPL;           \
+    zz = ff->ops->name(ff, MP_NEW, xx, yy);                            \
+    MP_DROP(xx); MP_DROP(yy);                                          \
+    return (fe_pywrap(fobj, zz));                                      \
+  }
+BINOP(add)
+BINOP(sub)
+BINOP(mul)
+#undef BINOP
+
+static PyObject *fe_pydiv(PyObject *x, PyObject *y)
+{
+  PyObject *fobj;
+  field *ff;
+  mp *xx, *yy;
+  PyObject *z = 0;
+  if (febinop(x, y, &ff, &fobj, &xx, &yy)) RETURN_NOTIMPL;
+  if (F_ZEROP(ff, yy)) ZDIVERR("division by zero");
+  yy = F_INV(ff, yy, yy);
+  z = fe_pywrap(fobj, F_MUL(ff, MP_NEW, xx, yy));
+end:
+  MP_DROP(xx); MP_DROP(yy);
+  return (z);
+}
+
+static PyObject *fe_pyexp(PyObject *x, PyObject *y, PyObject *z)
+{
+  field *ff;
+  mp *xx, *yy;
+
+  if (z != Py_None || !FE_PYCHECK(x)) RETURN_NOTIMPL;
+  if (FE_PYCHECK(y) && FE_F(y)->ops->ty == FTY_PRIME)
+    yy = F_OUT(FE_F(y), MP_NEW, FE_X(y));
+  else if ((yy = implicitmp(y)) == 0)
+    RETURN_NOTIMPL;
+  ff = FE_F(x); xx = FE_X(x); MP_COPY(xx);
+  if (MP_NEGP(yy) && F_ZEROP(ff, xx)) ZDIVERR("division by zero");
+  z = fe_pywrap(FE_FOBJ(x), field_exp(ff, MP_NEW, xx, yy));
+end:
+  MP_DROP(xx); MP_DROP(yy);
+  return (z);
+}
+
+static PyObject *fe_pyneg(PyObject *x)
+{
+  return fe_pywrap(FE_FOBJ(x), FE_F(x)->ops->neg(FE_F(x), MP_NEW, FE_X(x)));
+}
+
+static PyObject *fe_pyid(PyObject *x) { RETURN_OBJ(x); }
+
+static int fe_pynonzerop(PyObject *x) { return !F_ZEROP(FE_F(x), FE_X(x)); }
+
+static PyObject *fe_pyrichcompare(PyObject *x, PyObject *y, int op)
+{
+  PyObject *fobj;
+  field *ff;
+  mp *xx, *yy;
+  int b;
+  PyObject *rc = 0;
+
+  if (febinop(x, y, &ff, &fobj, &xx, &yy)) RETURN_NOTIMPL;
+  switch (op) {
+    case Py_EQ: b = MP_EQ(xx, yy); break;
+    case Py_NE: b = !MP_EQ(xx, yy); break;
+    default: TYERR("field elements are unordered");
+  }
+  rc = getbool(b);
+end:
+  MP_DROP(xx); MP_DROP(yy);
+  return (rc);
+}
+
+static Py_hash_t fe_pyhash(PyObject *me)
+  { return (mphash(FE_X(me))); }
+
+#ifdef PY2
+static int fe_pycoerce(PyObject **x, PyObject **y)
+{
+  mp *z;
+
+  if (FE_PYCHECK(*y)) {
+    if (FE_F(*x) != FE_F(*y) && !field_samep(FE_F(*x), FE_F(*y)))
+      TYERR("field mismatch");
+    Py_INCREF(*x); Py_INCREF(*y);
+    return (0);
+  }
+  if ((z = implicitfe(FE_F(*x), *y)) != 0) {
+    Py_INCREF(*x);
+    *y = fe_pywrap(FE_FOBJ(*x), z);
+    return (0);
+  }
+  return (1);
+
+end:
+  return (-1);
+}
+#endif
+
+static PyObject *fe_pyint(PyObject *x)
+{
+  long l;
+  PyObject *rc;
+  mp *xx = F_OUT(FE_F(x), MP_NEW, FE_X(x));
+  if (!mp_tolong_checked(xx, &l, 0)) rc = PyInt_FromLong(l);
+  else rc = mp_topylong(xx);
+  MP_DROP(xx);
+  return (rc);
+}
+
+#ifdef PY2
+static PyObject *fe_pylong(PyObject *x)
+{
+  mp *xx = F_OUT(FE_F(x), MP_NEW, FE_X(x));
+  PyObject *rc = mp_topylong(xx);
+  MP_DROP(xx);
+  return (rc);
+}
+#endif
+
+#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(xx, radix, 0, pre, 0);                        \
+    MP_DROP(xx);                                                       \
+    return (rc);                                                       \
+  }
+#ifdef PY2
+BASEOP(oct, 8, "0");
+#endif
+BASEOP(hex, 16, "0x");
+#undef BASEOP
+
+static void fe_pydealloc(PyObject *me)
+{
+  Py_DECREF(FE_FOBJ(me));
+  MP_DROP(FE_X(me));
+  FREEOBJ(me);
+}
+
+#define UNOP(name, check)                                              \
+  static PyObject *femeth_##name(PyObject *me) {                       \
+    field *f = FE_F(me);                                               \
+    mp *x = FE_X(me);                                                  \
+    if (!f->ops->name) TYERR(#name " not supported for this field");   \
+    check                                                              \
+    x = f->ops->name(f, MP_NEW, x);                                    \
+    if (!x) RETURN_NONE;                                               \
+    return (fe_pywrap(FE_FOBJ(me), x));                                        \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+UNOP(inv, if (F_ZEROP(f, x)) ZDIVERR("division by zero"); )
+UNOP(sqr, ; )
+UNOP(sqrt, ; )
+UNOP(quadsolve, ; )
+UNOP(dbl, ; )
+UNOP(tpl, ; )
+UNOP(qdl, ; )
+UNOP(hlv, ; )
+#undef UNOP
+
+static PyObject *feget_value(PyObject *me, void *hunoz)
+{
+  mp *x = F_OUT(FE_F(me), MP_NEW, FE_X(me));
+  if (F_TYPE(FE_F(me)) == FTY_BINARY)
+    return (gf_pywrap(x));
+  else
+    return (mp_pywrap(x));
+}
+
+static PyObject *feget__value(PyObject *me, void *hunoz)
+{
+  mp *x = FE_X(me);
+  MP_COPY(x);
+  if (F_TYPE(FE_F(me)) == FTY_BINARY)
+    return (gf_pywrap(x));
+  else
+    return (mp_pywrap(x));
+}
+
+static const PyMemberDef fe_pymembers[] = {
+#define MEMBERSTRUCT fe_pyobj
+  MEMRNM(field, T_OBJECT, PY23(ob_type, ob_base.ob_type), READONLY,
+                       "X.field -> field containing X")
+#undef MEMBERSTRUCT
+  { 0 }
+};
+
+static const PyGetSetDef fe_pygetset[] = {
+#define GETSETNAME(op, name) fe##op##_##name
+  GET  (value,         "X.value -> `natural' MP/GF representation of X")
+  GET  (_value,        "X._value -> internal MP/GF representation of X")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef fe_pymethods[] = {
+#define METHNAME(func) femeth_##func
+  NAMETH(inv,          "X.inv() -> X^{-1}")
+  NAMETH(sqr,          "X.sqr() -> X^2")
+  NAMETH(sqrt,         "X.sqrt() -> sqrt(X)")
+  NAMETH(quadsolve,    "X.quadsolve() -> Y where Y^2 + Y = X (binary only)")
+  NAMETH(dbl,          "X.dbl() -> 2 * X (prime only)")
+  NAMETH(tpl,          "X.tpl() -> 3 * X (prime only)")
+  NAMETH(qdl,          "X.qdl() -> 4 * X (prime only)")
+  NAMETH(hlv,          "X.hlv() -> X/2 (prime only)")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyNumberMethods fe_pynumber = {
+  fe_pyadd,                            /* @nb_add@ */
+  fe_pysub,                            /* @nb_subtract@ */
+  fe_pymul,                            /* @nb_multiply@ */
+#ifdef PY2
+  fe_pydiv,                            /* @nb_divide@ */
+#endif
+  0,                                   /* @nb_remainder@ */
+  0,                                   /* @nb_divmod@ */
+  fe_pyexp,                            /* @nb_power@ */
+  fe_pyneg,                            /* @nb_negative@ */
+  fe_pyid,                             /* @nb_positive@ */
+  0,                                   /* @nb_absolute@ */
+  fe_pynonzerop,                       /* @nb_nonzero@ */
+  0,                                   /* @nb_invert@ */
+  0,                                   /* @nb_lshift@ */
+  0,                                   /* @nb_rshift@ */
+  0,                                   /* @nb_and@ */
+  0,                                   /* @nb_xor@ */
+  0,                                   /* @nb_or@ */
+#ifdef PY2
+  fe_pycoerce,                         /* @nb_coerce@ */
+#endif
+  fe_pyint,                            /* @nb_int@ */
+  PY23(fe_pylong, 0),                  /* @nb_long@ */
+  0 /* meaningless */,                 /* @nb_float@ */
+#ifdef PY2
+  fe_pyoct,                            /* @nb_oct@ */
+  fe_pyhex,                            /* @nb_hex@ */
+#endif
+
+  0,                                   /* @nb_inplace_add@ */
+  0,                                   /* @nb_inplace_subtract@ */
+  0,                                   /* @nb_inplace_multiply@ */
+#ifdef PY2
+  0,                                   /* @nb_inplace_divide@ */
+#endif
+  0,                                   /* @nb_inplace_remainder@ */
+  0,                                   /* @nb_inplace_power@ */
+  0,                                   /* @nb_inplace_lshift@ */
+  0,                                   /* @nb_inplace_rshift@ */
+  0,                                   /* @nb_inplace_and@ */
+  0,                                   /* @nb_inplace_xor@ */
+  0,                                   /* @nb_inplace_or@ */
+
+  0,                                   /* @nb_floor_divide@ */
+  fe_pydiv,                            /* @nb_true_divide@ */
+  0,                                   /* @nb_inplace_floor_divide@ */
+  0,                                   /* @nb_inplace_true_divide@ */
+
+  fe_pyint,                            /* @nb_index@ */
+};
+
+static const PyTypeObject fe_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "FE",                                        /* @tp_name@ */
+  sizeof(fe_pyobj),                    /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  fe_pydealloc,                                /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  PYNUMBER(fe),                                /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  fe_pyhash,                           /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  fe_pyhex,                            /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_CHECKTYPES |
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Finite field elements, abstract base class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  fe_pyrichcompare,                    /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(fe),                       /* @tp_methods@ */
+  PYMEMBERS(fe),                       /* @tp_members@ */
+  PYGETSET(fe),                                /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Fields ------------------------------------------------------------*/
+
+static PyObject *field_pyrichcompare(PyObject *x, PyObject *y, int op)
+{
+  int b = field_samep(FIELD_F(x), FIELD_F(y));
+  switch (op) {
+    case Py_EQ: break;
+    case Py_NE: b = !b;
+    default: TYERR("can't order fields");
+  }
+  return (getbool(b));
+end:
+  return (0);
+}
+
+static PyObject *fmeth_rand(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "rng", 0 };
+  grand *r = &rand_global;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:rand", KWLIST,
+                                  convgrand, &r))
+    return (0);
+  return (fe_pywrap(me, F_RAND(FIELD_F(me), MP_NEW, r)));
+}
+
+static PyObject *fmeth__adopt(PyObject *me, PyObject *arg)
+{
+  mp *xx;
+  if (!PyArg_ParseTuple(arg, "O&:_adopt", convmp, &xx)) return (0);
+  return (fe_pywrap(me, xx));
+}
+
+static PyObject *fmeth_parse(PyObject *me, PyObject *arg)
+{
+  field *f;
+  char *p;
+  PyObject *rc = 0;
+  qd_parse qd;
+
+  if (!PyArg_ParseTuple(arg, "s:parse", &p)) goto end;
+  qd.p = p; qd.e = 0;
+  if ((f = field_parse(&qd)) == 0) VALERR(qd.e);
+  rc = Py_BuildValue("(Ns)", field_pywrap(f), qd.p);
+end:
+  return (rc);
+}
+
+static void field_pydealloc(PyObject *me)
+{
+  F_DESTROY(FIELD_F(me));
+  PyType_Type.tp_dealloc(me);
+}
+
+static PyObject *fget_zero(PyObject *me, void *hunoz)
+  { return (fe_pywrap(me, MP_COPY(FIELD_F(me)->zero))); }
+
+static PyObject *fget_one(PyObject *me, void *hunoz)
+  { return (fe_pywrap(me, MP_COPY(FIELD_F(me)->one))); }
+
+static PyObject *fget_q(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(FIELD_F(me)->q))); }
+
+static PyObject *fget_nbits(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(FIELD_F(me)->nbits)); }
+
+static PyObject *fget_noctets(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(FIELD_F(me)->noctets)); }
+
+static PyObject *fget_name(PyObject *me, void *hunoz)
+  { return (TEXT_FROMSTR(F_NAME(FIELD_F(me)))); }
+
+static PyObject *fget_type(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(F_TYPE(FIELD_F(me)))); }
+
+static const PyGetSetDef field_pygetset[] = {
+#define GETSETNAME(op, name) f##op##_##name
+  GET  (zero,          "F.zero -> field additive identity")
+  GET  (one,           "F.one -> field multiplicative identity")
+  GET  (q,             "F.q -> number of elements in field")
+  GET  (nbits,         "F.nbits -> bits needed to represent element")
+  GET  (noctets,       "F.noctets -> octetss needed to represent element")
+  GET  (name,          "F.name -> name of this kind of field")
+  GET  (type,          "F.type -> type code of this kind of field")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef field_pymethods[] = {
+#define METHNAME(name) fmeth_##name
+  METH (_adopt,        "F._adopt(X) -> FE")
+  KWMETH(rand,         "F.rand([rng = rand]) -> FE, uniformly distributed")
+  SMTH (parse,         "parse(STR) -> F, REST")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject field_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "Field",                             /* @tp_name@ */
+  sizeof(field_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  field_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "An abstract field.  This is an abstract type.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  field_pyrichcompare,                 /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(field),                    /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(field),                     /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Prime fields ------------------------------------------------------*/
+
+static PyObject *primefield_pynew(PyTypeObject *ty,
+                                 PyObject *arg, PyObject *kw)
+{
+  mp *xx = 0;
+  field *f;
+  static const char *const kwlist[] = { "p", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:primefield", KWLIST,
+                                  convmp, &xx))
+    goto end;
+  if ((f = field_prime(xx)) == 0)
+    VALERR("bad prime for primefield");
+  MP_DROP(xx);
+  return (field_dopywrap(ty, f));
+end:
+  mp_drop(xx);
+  return (0);
+}
+
+static PyObject *pfget_p(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(FIELD_F(me)->m))); }
+
+static const PyGetSetDef primefield_pygetset[] = {
+#define GETSETNAME(op, name) pf##op##_##name
+  GET  (p,             "F.p -> prime field characteristic")
+#undef GETSETNAME
+};
+
+static const PyTypeObject primefield_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "PrimeField",                                /* @tp_name@ */
+  sizeof(field_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  field_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "PrimeField(P): prime fields.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(primefield),                        /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  primefield_pynew,                    /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *niceprimefield_pynew(PyTypeObject *ty,
+                                     PyObject *arg, PyObject *kw)
+{
+  mp *xx = 0;
+  field *f;
+  static const char *const kwlist[] = { "p", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:niceprimefield",
+                                  KWLIST, convmp, &xx))
+    goto end;
+  if ((f = field_niceprime(xx)) == 0)
+    VALERR("bad prime for niceprimefield");
+  MP_DROP(xx);
+  return (field_dopywrap(ty, f));
+end:
+  mp_drop(xx);
+  return (0);
+}
+
+static const PyTypeObject niceprimefield_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "NicePrimeField",                    /* @tp_name@ */
+  sizeof(field_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  field_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "NicePrimeField(P): prime field using Solinas reduction.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  niceprimefield_pynew,                        /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Binary fields -----------------------------------------------------*/
+
+static PyObject *bfget_m(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(FIELD_F(me)->nbits)); }
+
+static PyObject *bfget_p(PyObject *me, void *hunoz)
+  { return (gf_pywrap(MP_COPY(FIELD_F(me)->m))); }
+
+static const PyGetSetDef binfield_pygetset[] = {
+#define GETSETNAME(op, name) bf##op##_##name
+  GET  (m,             "F.m -> field polynomial degree")
+  GET  (p,             "F.p -> field polynomial")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject binfield_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "BinField",                          /* @tp_name@ */
+  sizeof(field_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  field_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Binary fields.  Abstract class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(binfield),                  /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *binpolyfield_pynew(PyTypeObject *ty,
+                                   PyObject *arg, PyObject *kw)
+{
+  mp *xx = 0;
+  field *f;
+  static const char *const kwlist[] = { "p", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:binpolyfield", KWLIST,
+                                  convgf, &xx))
+    goto end;
+  if ((f = field_binpoly(xx)) == 0) VALERR("bad poly for binpolyfield");
+  MP_DROP(xx);
+  return (field_dopywrap(ty, f));
+end:
+  mp_drop(xx);
+  return (0);
+}
+
+static const PyTypeObject binpolyfield_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "BinPolyField",                      /* @tp_name@ */
+  sizeof(field_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  field_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "BinPolyField(P): binary fields with polynomial basis representation.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  binpolyfield_pynew,                  /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *binnormfield_pynew(PyTypeObject *ty,
+                                   PyObject *arg, PyObject *kw)
+{
+  mp *xx = 0, *yy = 0;
+  field *f;
+  static const char *const kwlist[] = { "p", "beta", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:binnormfield",
+                                  KWLIST, convgf, &xx, convgf, &yy))
+    goto end;
+  if ((f = field_binnorm(xx, yy)) == 0) VALERR("bad args for binnormfield");
+  MP_DROP(xx); MP_DROP(yy);
+  return (field_dopywrap(ty, f));
+end:
+  mp_drop(xx); mp_drop(yy);
+  return (0);
+}
+
+static PyObject *bnfget_beta(PyObject *me, void *hunoz)
+{
+  fctx_binnorm *fc = (fctx_binnorm *)FIELD_F(me);
+  return (gf_pywrap(MP_COPY(fc->ntop.r[fc->ntop.n - 1])));
+}
+
+static const PyGetSetDef binnormfield_pygetset[] = {
+#define GETSETNAME(op, name) bnf##op##_##name
+  GET  (beta,          "F.beta -> conversion factor")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject binnormfield_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "BinNormField",                      /* @tp_name@ */
+  sizeof(field_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  field_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "BinNormField(P, BETA): binary fields with normal basis representation.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(binnormfield),              /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  binnormfield_pynew,                  /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Setup -------------------------------------------------------------*/
+
+static const struct nameval consts[] = {
+  CONST(FTY_PRIME), CONST(FTY_BINARY),
+  { 0 }
+};
+
+void field_pyinit(void)
+{
+  INITTYPE(fe, root);
+  INITTYPE(field, type);
+  INITTYPE(primefield, field);
+  INITTYPE(niceprimefield, primefield);
+  INITTYPE(binfield, field);
+  INITTYPE(binpolyfield, binfield);
+  INITTYPE(binnormfield, binfield);
+}
+
+void field_pyinsert(PyObject *mod)
+{
+  INSERT("FE", fe_pytype);
+  INSERT("Field", field_pytype);
+  INSERT("PrimeField", primefield_pytype);
+  INSERT("NicePrimeField", niceprimefield_pytype);
+  INSERT("BinField", binfield_pytype);
+  INSERT("BinPolyField", binpolyfield_pytype);
+  INSERT("BinNormField", binnormfield_pytype);
+  setconstants(mod, consts);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/group.c b/group.c
new file mode 100644 (file)
index 0000000..77c3e9f
--- /dev/null
+++ b/group.c
@@ -0,0 +1,1418 @@
+/* -*-c-*-
+ *
+ * Abstract group inteface
+ *
+ * (c) 2004 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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "catacomb-python.h"
+
+/*----- DH and binary group infos -----------------------------------------*/
+
+static PyTypeObject *fginfo_pytype, *dhinfo_pytype, *bindhinfo_pytype;
+
+typedef struct fginfo_pyobj {
+  PyObject_HEAD
+  gprime_param dp;
+} fginfo_pyobj;
+
+#define FGINFO_DP(fg) (&((fginfo_pyobj *)(fg))->dp)
+
+static PyObject *fginfo_pywrap(gprime_param *dp, PyTypeObject *ty)
+{
+  fginfo_pyobj *z = PyObject_New(fginfo_pyobj, ty);
+  z->dp = *dp;
+  return ((PyObject *)z);
+}
+
+static PyObject *fginfo_pynew(PyTypeObject *ty,
+                             PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "p", "r", "g", 0 };
+  gprime_param dp = { 0 };
+  fginfo_pyobj *z = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&O&:new", KWLIST,
+                                  convmp, &dp.p,
+                                  convmp, &dp.q,
+                                  convmp, &dp.g))
+    goto end;
+  z = PyObject_New(fginfo_pyobj, ty);
+  z->dp = dp;
+  return ((PyObject *)z);
+end:
+  mp_drop(dp.p);
+  mp_drop(dp.q);
+  mp_drop(dp.g);
+  return (0);
+}
+
+static PyObject *figet_r(PyObject *me, void *hunoz)
+  { return mp_pywrap(MP_COPY(FGINFO_DP(me)->q)); }
+
+static PyObject *diget_p(PyObject *me, void *hunoz)
+  { return mp_pywrap(MP_COPY(FGINFO_DP(me)->p)); }
+
+static PyObject *diget_g(PyObject *me, void *hunoz)
+  { return mp_pywrap(MP_COPY(FGINFO_DP(me)->g)); }
+
+static PyObject *biget_p(PyObject *me, void *hunoz)
+  { return gf_pywrap(MP_COPY(FGINFO_DP(me)->p)); }
+
+static PyObject *biget_m(PyObject *me, void *hunoz)
+  { return PyInt_FromLong(mp_octets(FGINFO_DP(me)->p) - 1); }
+
+static PyObject *biget_g(PyObject *me, void *hunoz)
+  { return gf_pywrap(MP_COPY(FGINFO_DP(me)->g)); }
+
+static void fginfo_pydealloc(PyObject *me)
+{
+  mp_drop(FGINFO_DP(me)->p);
+  mp_drop(FGINFO_DP(me)->q);
+  mp_drop(FGINFO_DP(me)->g);
+  FREEOBJ(me);
+}
+
+static PyObject *dimeth_generate(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  dh_param dp;
+  unsigned ql = 0, pl;
+  unsigned steps = 0;
+  grand *r = &rand_global;
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
+  static const char *const kwlist[] =
+    { "pbits", "qbits", "event", "rng", "nsteps", 0 };
+  PyObject *rc = 0;
+
+  evt.exc = &exc;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&O&O&O&:generate", KWLIST,
+                                  convuint, &pl, convuint, &ql,
+                                  convpgev, &evt, convgrand, &r,
+                                  convuint, &steps))
+    goto end;
+  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);
+  return (rc);
+}
+
+static PyObject *dimeth_genlimlee(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  dh_param dp;
+  unsigned ql, pl;
+  unsigned steps = 0;
+  grand *r = &rand_global;
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev oe = { { 0 } }, ie = { { 0 } };
+  int subgroupp = 1;
+  unsigned f = 0;
+  static const char *const kwlist[] = {
+    "pbits", "qbits", "event", "ievent",
+    "rng", "nsteps", "subgroupp", 0
+  };
+  size_t i, nf;
+  mp **v = 0;
+  PyObject *rc = 0, *vec = 0;
+
+  oe.exc = ie.exc = &exc;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw,
+                                  "O&O&|O&O&O&O&O&:genlimlee", KWLIST,
+                                  convuint, &pl, convuint, &ql,
+                                  convpgev, &oe, convpgev, &ie,
+                                  convgrand, &r, convuint, &steps,
+                                  convbool, &subgroupp))
+    goto end;
+  if (subgroupp) f |= DH_SUBGROUP;
+  if (dh_limlee(&dp, ql, pl, f, steps, r,
+               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_SET_ITEM(vec, i, mp_pywrap(v[i]));
+  xfree(v);
+  rc = Py_BuildValue("(NN)", fginfo_pywrap(&dp, dhinfo_pytype), vec);
+end:
+  droppgev(&oe); droppgev(&ie);
+  return (rc);
+}
+
+static PyObject *dimeth_genkcdsa(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  dh_param dp;
+  unsigned ql, pl;
+  unsigned steps = 0;
+  grand *r = &rand_global;
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
+  static const char *const kwlist[] =
+    { "pbits", "qbits", "event", "rng", "nsteps", 0 };
+  mp *v = MP_NEW;
+  PyObject *rc = 0;
+
+  evt.exc = &exc;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&O&O&:genkcdsa", KWLIST,
+                                  convuint, &pl, convuint, &ql,
+                                  convpgev, &evt, convgrand, &r,
+                                  convuint, &steps))
+    goto end;
+  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),
+                    mp_pywrap(v));
+end:
+  droppgev(&evt);
+  return (rc);
+}
+
+static PyObject *dimeth_gendsa(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  dsa_param dp;
+  unsigned ql, pl;
+  unsigned steps = 0;
+  dsa_seed ds;
+  struct bin k;
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
+  static const char *const kwlist[] =
+    { "pbits", "qbits", "seed", "event", "nsteps", 0 };
+  PyObject *rc = 0;
+
+  evt.exc = &exc;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&O&|O&O&:gendsa", KWLIST,
+                                  convuint, &pl, convuint, &ql,
+                                  convbin, &k,
+                                  convpgev, &evt, convuint, &steps))
+    goto end;
+  if (dsa_gen(&dp, ql, pl, steps, k.p, k.sz, &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);
+end:
+  droppgev(&evt);
+  return (rc);
+}
+
+static PyObject *meth__parse(PyObject *me, PyObject *arg, PyTypeObject *ty,
+                            int (*parse)(qd_parse *, gprime_param *))
+{
+  qd_parse qd;
+  char *p;
+  gprime_param gp;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "s:parse", &p)) goto end;
+  qd.p = p; qd.e = 0;
+  if (parse(&qd, &gp)) VALERR(qd.e);
+  rc = fginfo_pywrap(&gp, ty);
+end:
+  return (rc);
+}
+
+static PyObject *dimeth_parse(PyObject *me, PyObject *arg)
+  { return (meth__parse(me, arg, dhinfo_pytype, dh_parse)); }
+
+static PyObject *bimeth_parse(PyObject *me, PyObject *arg)
+  { return (meth__parse(me, arg, bindhinfo_pytype, dhbin_parse)); }
+
+static const PyGetSetDef fginfo_pygetset[] = {
+#define GETSETNAME(op, name) fi##op##_##name
+  GET  (r,             "I.r -> group order")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyGetSetDef dhinfo_pygetset[] = {
+#define GETSETNAME(op, name) di##op##_##name
+  GET  (p,             "I.p -> prime")
+  GET  (g,             "I.g -> generator")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef dhinfo_pymethods[] = {
+#define METHNAME(name) dimeth_##name
+  SMTH (parse,         "parse(STR) -> D, REST")
+  KWSMTH(generate,
+       "generate(PBITS, [qbits = 0], [event = pgen_nullev],\n"
+       "        [rng = rand], [nsteps = 0]) -> D")
+  KWSMTH(genlimlee,
+       "genlimlee(PBITS, QBITS, [event = pgen_nullev], "
+                                                 "[ievent = pgen_nullev],\n"
+       "         [rng = rand], [nsteps = 0], [subgroupp = True]) "
+                                                         "-> (D, [Q, ...])")
+  KWSMTH(gendsa,
+       "gendsa(PBITS, QBITS, SEED, [event = pgen_nullev], [nsteps = 0])\n"
+       "  -> (D, SEED, COUNT)")
+  KWSMTH(genkcdsa,
+       "gendsa(PBITS, QBITS, [event = pgen_nullev], "
+                                             "[rng = rand], [nsteps = 0])\n"
+       "  -> (D, V)")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyGetSetDef bindhinfo_pygetset[] = {
+#define GETSETNAME(op, name) bi##op##_##name
+  GET  (p,             "I.p -> irreducible polynomial")
+  GET  (m,             "I.m -> degree of polynomial")
+  GET  (g,             "I.g -> generator")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef bindhinfo_pymethods[] = {
+#define METHNAME(name) bimeth_##name
+  SMTH (parse,         "parse(STR) -> D, REST")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject fginfo_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "FGInfo",                            /* @tp_name@ */
+  sizeof(fginfo_pyobj),                        /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  fginfo_pydealloc,                    /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Abstract base class for field-group information objects.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(fginfo),                    /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject dhinfo_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "DHInfo",                            /* @tp_name@ */
+  sizeof(fginfo_pyobj),                        /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  fginfo_pydealloc,                    /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "DHInfo(P, R, G): standard (integer) Diffie-Hellman group information.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(dhinfo),                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(dhinfo),                    /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  fginfo_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject bindhinfo_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "BinDHInfo",                         /* @tp_name@ */
+  sizeof(fginfo_pyobj),                        /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  fginfo_pydealloc,                    /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "BinDHInfo(P, R, G): binary-field Diffie-Hellman group information.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(bindhinfo),                        /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(bindhinfo),                 /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  fginfo_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- General utilities -------------------------------------------------*/
+
+PyTypeObject *ge_pytype, *group_pytype;
+static PyTypeObject *primegroup_pytype, *bingroup_pytype, *ecgroup_pytype;
+
+group *group_copy(group *g)
+{
+  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.p = gc->mm.m;
+    gp.q = gc->g.r;
+    g = group_prime(&gp);
+    MP_DROP(gp.g);
+  } 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.p = gc->r.p;
+    gb.q = gc->g.r;
+    g = group_binary(&gb);
+    MP_DROP(gb.g);
+  } 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)
+      return (0);
+    EC_CREATE(&ei.g);
+    EC_COPY(&ei.g, &gc->ei.g);
+    ei.r = MP_COPY(gc->ei.r);
+    ei.h = MP_COPY(gc->ei.h);
+    g = group_ec(&ei);
+  } else
+    g = 0;
+  return (g);
+}
+
+PyObject *ge_pywrap(PyObject *gobj, ge *x)
+{
+  ge_pyobj *z = PyObject_New(ge_pyobj, (PyTypeObject *)gobj);
+  z->x = x;
+  z->g = GROUP_G(gobj);
+  Py_INCREF(gobj);
+  return ((PyObject *)z);
+}
+
+static PyObject *ge_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "x", 0 };
+  PyObject *x;
+  group *g;
+  ec p = EC_INIT;
+  mp *y = 0;
+  ge *xx = 0;
+  size_t n;
+  mptext_stringctx sc;
+
+  g = GROUP_G(ty);
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:new", KWLIST, &x)) goto end;
+  xx = G_CREATE(g);
+  if (ECPT_PYCHECK(x)) {
+    getecptout(&p, x);
+    if (G_FROMEC(g, xx, &p))
+      TYERR("can't convert from elliptic curve point");
+    EC_DESTROY(&p);
+  } else if ((y = tomp(x)) != 0) {
+    if (G_FROMINT(g, xx, y))
+      TYERR("can't convert from integer");
+    MP_DROP(y);
+  } else if (TEXT_CHECK(x)) {
+    TEXT_PTRLEN(x, sc.buf, n); sc.lim = sc.buf + n;
+    if (G_READ(g, xx, &mptext_stringops, &sc) || sc.buf < sc.lim)
+      VALERR("malformed group element string");
+  } else
+    TYERR("can't convert to group element");
+  return (ge_pywrap((PyObject *)ty, xx));
+end:
+  mp_drop(y);
+  EC_DESTROY(&p);
+  if (xx) G_DESTROY(g, xx);
+  return (0);
+}
+
+static PyObject *group_dopywrap(PyTypeObject *ty, group *g)
+{
+  group_pyobj *gobj = newtype(ty, 0, g->ops->name);
+  gobj->g = g;
+  gobj->ty.ht_type.tp_basicsize = sizeof(ge_pyobj);
+  gobj->ty.ht_type.tp_base = ge_pytype;
+  Py_INCREF(group_pytype);
+  gobj->ty.ht_type.tp_flags = (Py_TPFLAGS_DEFAULT |
+                              Py_TPFLAGS_BASETYPE |
+                              Py_TPFLAGS_CHECKTYPES |
+                              Py_TPFLAGS_HEAPTYPE);
+  gobj->ty.ht_type.tp_alloc = PyType_GenericAlloc;
+  gobj->ty.ht_type.tp_free = 0;
+  gobj->ty.ht_type.tp_new = ge_pynew;
+  typeready(&gobj->ty.ht_type);
+  return ((PyObject *)gobj);
+}
+
+PyObject *group_pywrap(group *g)
+{
+  PyTypeObject *ty;
+
+  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));
+}
+
+/*----- Group elements ----------------------------------------------------*/
+
+#define BINOP(name)                                                    \
+  static PyObject *ge_py##name(PyObject *x, PyObject *y)               \
+  {                                                                    \
+    ge *z;                                                             \
+    group *g;                                                          \
+    if (!GE_PYCHECK(x) || !GE_PYCHECK(y) ||                            \
+       (GE_G(x) != GE_G(y) && !group_samep(GE_G(x), GE_G(y))))         \
+      RETURN_NOTIMPL;                                                  \
+    g = GE_G(x);                                                       \
+    z = G_CREATE(g);                                                   \
+    g->ops->name(g, z, GE_X(x), GE_X(y));                              \
+    return (ge_pywrap(GE_GOBJ(x), z));                                 \
+  }
+BINOP(mul)
+BINOP(div)
+#undef BINOP
+
+#define UNOP(name)                                                     \
+  static PyObject *gemeth_##name(PyObject *me)                         \
+  {                                                                    \
+    group *g;                                                          \
+    ge *z;                                                             \
+    g = GE_G(me);                                                      \
+    z = G_CREATE(g);                                                   \
+    g->ops->name(g, z, GE_X(me));                                      \
+    return (ge_pywrap(GE_GOBJ(me), z));                                        \
+  }
+UNOP(sqr)
+UNOP(inv)
+#undef UNOP
+
+static PyObject *ge_pyexp(PyObject *x, PyObject *n, PyObject *m)
+{
+  mp *nn;
+  ge *z;
+
+  if (m != Py_None || !GE_PYCHECK(x)) RETURN_NOTIMPL;
+  if (FE_PYCHECK(n) && FE_F(n)->ops->ty == FTY_PRIME)
+    nn = F_OUT(FE_F(n), MP_NEW, FE_X(n));
+  else if ((nn = implicitmp(n)) == 0)
+    RETURN_NOTIMPL;
+  z = G_CREATE(GE_G(x));
+  G_EXP(GE_G(x), z, GE_X(x), nn);
+  MP_DROP(nn);
+  return (ge_pywrap(GE_GOBJ(x), z));
+}
+
+static void ge_pydealloc(PyObject *me)
+{
+  G_DESTROY(GE_G(me), GE_X(me));
+  Py_DECREF(GE_GOBJ(me));
+  FREEOBJ(me);
+}
+
+static void group_pydealloc(PyObject *me)
+{
+  G_DESTROYGROUP(GROUP_G(me));
+  PyType_Type.tp_dealloc(me);
+}
+
+static PyObject *gmexp_id(PyObject *me)
+{
+  group *g = GROUP_G(me); ge *x = G_CREATE(g);
+  G_COPY(g, x, g->i); return (ge_pywrap(me, x));
+}
+
+static int gmexp_fill(void *pp, PyObject  *me, PyObject *x, PyObject *m)
+{
+  group_expfactor *f = pp;
+
+  if (!GE_PYCHECK(x) || GE_G(x) != GROUP_G(me) || (f->exp = getmp(m)) == 0)
+    return (-1);
+  f->base = GE_X(x);
+  return (0);
+}
+
+static PyObject *ge_pyrichcompare(PyObject *x, PyObject *y, int op)
+{
+  int b;
+  PyObject *rc = 0;
+
+  if (!GE_PYCHECK(x) || !GE_PYCHECK(y) ||
+      (GE_G(x) != GE_G(y) && !group_samep(GE_G(x), GE_G(y))))
+    RETURN_NOTIMPL;
+  switch (op) {
+    case Py_EQ: b = G_EQ(GE_G(x), GE_X(x), GE_X(y)); break;
+    case Py_NE: b = !G_EQ(GE_G(x), GE_X(x), GE_X(y)); break;
+    default: TYERR("group elements are unordered");
+  }
+  rc = getbool(b);
+end:
+  return (rc);
+}
+
+static PyObject *gemeth_check(PyObject *me)
+{
+  if (group_check(GE_G(me), GE_X(me))) VALERR("bad group element");
+  RETURN_OBJ(me);
+end:
+  return (0);
+}
+
+static int ge_pynonzerop(PyObject *x)
+  { return (!G_IDENTP(GE_G(x), GE_X(x))); }
+
+static PyObject *ge_pystr(PyObject *me)
+{
+  dstr d = DSTR_INIT;
+  PyObject *rc;
+
+  group_writedstr(GE_G(me), GE_X(me), &d);
+  rc = TEXT_FROMSTRLEN(d.buf, d.len);
+  DDESTROY(&d);
+  return (rc);
+}
+
+#ifdef PY2
+static PyObject *ge_pylong(PyObject *me)
+{
+  mp *x = 0;
+  PyObject *rc = 0;
+
+  if ((x = G_TOINT(GE_G(me), MP_NEW, GE_X(me))) == 0)
+    TYERR("can't convert to integer");
+  rc = mp_topylong(x);
+end:
+  mp_drop(x);
+  return (rc);
+}
+#endif
+
+static PyObject *ge_pyint(PyObject *me)
+{
+  mp *x = 0;
+  PyObject *rc = 0;
+  long l;
+
+  if ((x = G_TOINT(GE_G(me), MP_NEW, GE_X(me))) == 0)
+    TYERR("can't convert to integer");
+  if (!mp_tolong_checked(x, &l, 0)) rc = PyInt_FromLong(l);
+  else rc = mp_topylong(x);
+end:
+  mp_drop(x);
+  return (rc);
+}
+
+static PyObject *gemeth_toint(PyObject *me)
+{
+  mp *x;
+
+  if ((x = G_TOINT(GE_G(me), MP_NEW, GE_X(me))) == 0)
+    TYERR("can't convert to integer");
+  return (mp_pywrap(x));
+end:
+  return (0);
+}
+
+static PyObject *gemeth_toec(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "curve", 0 };
+  PyTypeObject *cty = 0;
+  PyObject *rc = 0;
+  group *g;
+  ec_curve *c;
+  ec p = EC_INIT;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O:toec", KWLIST,
+                                  &cty)) goto end;
+  g = GROUP_G(GE_GOBJ(me));
+  if (cty) {
+    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")) {
+    c = eccurve_copy(((gctx_ec *)g)->ei.c);
+    cty = (PyTypeObject *)eccurve_pywrap(0, c);
+  } else  {
+    cty = ecpt_pytype;
+    Py_INCREF((PyObject *)cty);
+  }
+  if (G_TOEC(GE_G(me), &p, GE_X(me))) {
+    Py_DECREF((PyObject *)cty);
+    TYERR("can't convert to ec point");
+  }
+  rc = ecpt_pywrapout(cty, &p);
+  Py_DECREF((PyObject *)cty);
+end:
+  return (rc);
+}
+
+static PyObject *gemeth_tobuf(PyObject *me)
+{
+  buf b;
+  PyObject *rc;
+  size_t n;
+
+  n = GE_G(me)->noctets + 4;
+  rc = bytestring_pywrap(0, n);
+  buf_init(&b, BIN_PTR(rc), n);
+  G_TOBUF(GE_G(me), &b, GE_X(me));
+  assert(BOK(&b));
+  BIN_SETLEN(rc, BLEN(&b));
+  return (rc);
+}
+
+static PyObject *gemeth_toraw(PyObject *me)
+{
+  buf b;
+  PyObject *rc;
+  size_t n;
+
+  n = GE_G(me)->noctets;
+  rc = bytestring_pywrap(0, n);
+  buf_init(&b, BIN_PTR(rc), n);
+  G_TORAW(GE_G(me), &b, GE_X(me));
+  assert(BOK(&b));
+  BIN_SETLEN(rc, BLEN(&b));
+  return (rc);
+}
+
+static PyObject *gmexp_exp(PyObject *me, void *pp, size_t n)
+{
+  ge *z = G_CREATE(GROUP_G(me));
+  G_MEXP(GROUP_G(me), z, pp, n);
+  return (ge_pywrap(me, z));
+}
+
+static void gmexp_drop(void *pp)
+{
+  group_expfactor *f = pp;
+  MP_DROP(f->exp);
+}
+
+static PyObject *gmeth_mexp(PyObject *me, PyObject *arg)
+{
+  return (mexp_common(me, arg, sizeof(group_expfactor),
+                     gmexp_id, gmexp_fill, gmexp_exp, gmexp_drop));
+}
+
+static PyObject *gmeth_checkgroup(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "rng", 0 };
+  grand *r = &rand_global;
+  const char *p;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:checkgroup", KWLIST,
+                                  convgrand, &r))
+    goto end;
+  if ((p = G_CHECK(GROUP_G(me), r)) != 0)
+    VALERR(p);
+  RETURN_OBJ(me);
+end:
+  return (0);
+}
+
+static PyObject *group_pyrichcompare(PyObject *x, PyObject *y, int op)
+{
+  int b = group_samep(GROUP_G(x), GROUP_G(y));
+  switch (op) {
+    case Py_EQ: break;
+    case Py_NE: b = !b;
+    default: TYERR("can't order groups");
+  }
+  return (getbool(b));
+end:
+  return (0);
+}
+
+static PyObject *gemeth_frombuf(PyObject *me, PyObject *arg)
+{
+  buf b;
+  struct bin in;
+  group *g;
+  ge *x = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:frombuf", convbin, &in)) return (0);
+  g = GROUP_G(me);
+  buf_init(&b, (/*unconst*/ void *)in.p, in.sz);
+  x = G_CREATE(g);
+  if (G_FROMBUF(g, &b, x)) VALERR("invalid data");
+  return (Py_BuildValue("(NN)", ge_pywrap(me, x), bytestring_pywrapbuf(&b)));
+end:
+  if (x) G_DESTROY(g, x);
+  return (0);
+}
+
+static PyObject *gemeth_fromraw(PyObject *me, PyObject *arg)
+{
+  buf b;
+  struct bin in;
+  group *g;
+  ge *x = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:fromraw", convbin, &in)) return (0);
+  g = GROUP_G(me);
+  buf_init(&b, (/*unconst*/ void *)in.p, in.sz);
+  x = G_CREATE(g);
+  if (G_FROMRAW(g, &b, x)) VALERR("invalid data");
+  return (Py_BuildValue("(NN)", ge_pywrap(me, x), bytestring_pywrapbuf(&b)));
+end:
+  if (x) G_DESTROY(g, x);
+  return (0);
+}
+
+static PyObject *gemeth_fromstring(PyObject *me, PyObject *arg)
+{
+  mptext_stringctx sc;
+  char *p;
+  Py_ssize_t n;
+  group *g;
+  ge *x = 0;
+
+  if (!PyArg_ParseTuple(arg, "s#:fromstring", &p, &n)) return (0);
+  sc.buf = p;
+  sc.lim = sc.buf + n;
+  g = GROUP_G(me);
+  x = G_CREATE(g);
+  if (G_READ(g, x, &mptext_stringops, &sc))
+    VALERR("bad group element string");
+  return (Py_BuildValue("(Ns#)", ge_pywrap(me, x),
+                       sc.buf, (Py_ssize_t)(sc.lim - sc.buf)));
+end:
+  if (x) G_DESTROY(g, x);
+  return (0);
+}
+
+static PyObject *gmeth_parse(PyObject *me, PyObject *arg)
+{
+  char *p;
+  qd_parse qd;
+  group *g;
+
+  if (!PyArg_ParseTuple(arg, "s:parse", &p)) goto end;
+  qd.p = p; qd.e = 0;
+  if ((g = group_parse(&qd)) == 0) VALERR(qd.e);
+  return (group_pywrap(g));
+end:
+  return (0);
+}
+
+static PyObject *geget_group(PyObject *me, void *hunoz)
+  { RETURN_OBJ(GE_GOBJ(me)); }
+
+static PyObject *gget_nbits(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GROUP_G(me)->nbits)); }
+
+static PyObject *gget_noctets(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GROUP_G(me)->noctets)); }
+
+static PyObject *gget_i(PyObject *me, void *hunoz)
+{
+  group *g = GROUP_G(me); ge *x = G_CREATE(g);
+  G_COPY(g, x, g->i); return (ge_pywrap(me, x));
+}
+
+static PyObject *gget_g(PyObject *me, void *hunoz)
+{
+  group *g = GROUP_G(me); ge *x = G_CREATE(g);
+  G_COPY(g, x, g->g); return (ge_pywrap(me, x));
+}
+
+static Py_hash_t ge_pyhash(PyObject *me)
+{
+  buf b;
+  size_t sz = GE_G(me)->noctets + 4;
+  uint32 h = 0xf672c776 + GE_G(me)->ops->ty;
+  octet *p = xmalloc(sz);
+  buf_init(&b, p, sz);
+  G_TOBUF(GE_G(me), &b, GE_X(me));
+  assert(BOK(&b));
+  h = unihash_hash(&unihash_global, h, BBASE(&b), BLEN(&b));
+  xfree(p);
+  return (h % LONG_MAX);
+}
+
+static PyObject *gget_r(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(GROUP_G(me)->r))); }
+
+static PyObject *gget_h(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(GROUP_G(me)->h))); }
+
+static const PyGetSetDef ge_pygetset[] = {
+#define GETSETNAME(op, name) ge##op##_##name
+  GET  (group,         "X.group -> group containing X")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef ge_pymethods[] = {
+#define METHNAME(name) gemeth_##name
+  NAMETH(inv,          "X.inv() -> inverse element of X")
+  NAMETH(sqr,          "X.sqr() -> X^2 = X * X")
+  NAMETH(check,                "X.check() -> check X really belongs to its group")
+  NAMETH(toint,                "X.toint() -> X converted to an integer")
+  KWMETH(toec,         "X.toec([curve = ECPt]) -> "
+                                      "X converted to elliptic curve point")
+  NAMETH(tobuf,                "X.tobuf() -> X in buffer representation")
+  NAMETH(toraw,                "X.toraw() -> X in raw representation")
+  CMTH (frombuf,       "frombuf(BUF) -> X, REST")
+  CMTH (fromraw,       "fromraw(BUF) -> X, REST")
+  CMTH (fromstring,    "fromstring(STR) -> X, REST")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyNumberMethods ge_pynumber = {
+  0,                                   /* @nb_add@ */
+  0,                                   /* @nb_subtract@ */
+  ge_pymul,                            /* @nb_multiply@ */
+#ifdef PY2
+  ge_pydiv,                            /* @nb_divide@ */
+#endif
+  0,                                   /* @nb_remainder@ */
+  0,                                   /* @nb_divmod@ */
+  ge_pyexp,                            /* @nb_power@ */
+  0,                                   /* @nb_negative@ */
+  0,                                   /* @nb_positive@ */
+  0,                                   /* @nb_absolute@ */
+  ge_pynonzerop,                       /* @nb_nonzero@ */
+  0,                                   /* @nb_invert@ */
+  0,                                   /* @nb_lshift@ */
+  0,                                   /* @nb_rshift@ */
+  0,                                   /* @nb_and@ */
+  0,                                   /* @nb_xor@ */
+  0,                                   /* @nb_or@ */
+#ifdef PY2
+  0,                                   /* @nb_coerce@ */
+#endif
+  ge_pyint,                            /* @nb_int@ */
+  PY23(ge_pylong, 0),                  /* @nb_long@ */
+  0 /* meaningless */,                 /* @nb_float@ */
+#ifdef PY2
+  0,                                   /* @nb_oct@ */
+  0,                                   /* @nb_hex@ */
+#endif
+
+  0,                                   /* @nb_inplace_add@ */
+  0,                                   /* @nb_inplace_subtract@ */
+  0,                                   /* @nb_inplace_multiply@ */
+#ifdef PY2
+  0,                                   /* @nb_inplace_divide@ */
+#endif
+  0,                                   /* @nb_inplace_remainder@ */
+  0,                                   /* @nb_inplace_power@ */
+  0,                                   /* @nb_inplace_lshift@ */
+  0,                                   /* @nb_inplace_rshift@ */
+  0,                                   /* @nb_inplace_and@ */
+  0,                                   /* @nb_inplace_xor@ */
+  0,                                   /* @nb_inplace_or@ */
+
+  0,                                   /* @nb_floor_divide@ */
+  ge_pydiv,                            /* @nb_true_divide@ */
+  0,                                   /* @nb_inplace_floor_divide@ */
+  0,                                   /* @nb_inplace_true_divide@ */
+};
+
+static const PyTypeObject ge_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GE",                                        /* @tp_name@ */
+  sizeof(ge_pyobj),                    /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  ge_pydealloc,                                /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  PYNUMBER(ge),                                /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  ge_pyhash,                           /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  ge_pystr,                            /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_CHECKTYPES |
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Group elements, abstract base class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  ge_pyrichcompare,                    /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(ge),                       /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(ge),                                /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyGetSetDef group_pygetset[] = {
+#define GETSETNAME(op, name) g##op##_##name
+  GET  (noctets,       "G.noctets -> size in octets of element")
+  GET  (nbits,         "G.nbits -> size in bits of element")
+  GET  (i,             "G.i -> group identity")
+  GET  (g,             "G.g -> group generator")
+  GET  (r,             "G.r -> group order")
+  GET  (h,             "G.h -> group cofactor")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef group_pymethods[] = {
+#define METHNAME(name) gmeth_##name
+  METH (mexp,        "G.mexp([(X0, N0), (X1, N1), ...]) -> X0^N0 X1^N1 ...")
+  KWMETH(checkgroup,   "G.checkgroup([rng = rand]): check group is good")
+  SMTH (parse,         "parse(STR) -> G, REST")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject group_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "Group",                             /* @tp_name@ */
+  sizeof(group_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  group_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Abstract base class for groups.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  group_pyrichcompare,                 /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(group),                    /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(group),                     /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *pgget_info(PyObject *me, void *hunoz)
+{
+  gprime_param dp;
+  gctx_prime *gg = (gctx_prime *)GROUP_G(me);
+  dp.p = MP_COPY(gg->mm.m);
+  dp.q = MP_COPY(gg->g.r);
+  dp.g = mpmont_reduce(&gg->mm, MP_NEW, gg->gen.x);
+  return (fginfo_pywrap(&dp, dhinfo_pytype));
+}
+
+static const PyGetSetDef primegroup_pygetset[] = {
+#define GETSETNAME(op, name) pg##op##_##name
+  GET  (info,          "G.info -> information about the group")
+#undef GETSETNAME
+  { 0 }
+};
+
+static PyObject *primegroup_pynew(PyTypeObject *ty,
+                                 PyObject *arg, PyObject *kw)
+{
+  PyObject *i;
+  static const char *const kwlist[] = { "info", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!:new", KWLIST,
+                                  dhinfo_pytype, &i))
+    return (0);
+  return (group_dopywrap(ty, group_prime(FGINFO_DP(i))));
+}
+
+static const PyTypeObject primegroup_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "PrimeGroup",                                /* @tp_name@ */
+  sizeof(group_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  group_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "PrimeGroup(INFO): subgroups of prime fields.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(primegroup),                        /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  primegroup_pynew,                    /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *bgget_info(PyObject *me, void *hunoz)
+{
+  gbin_param dp;
+  gctx_bin *gg = (gctx_bin *)GROUP_G(me);
+  dp.p = MP_COPY(gg->r.p);
+  dp.q = MP_COPY(gg->g.r);
+  dp.g = MP_COPY(gg->gen.x);
+  return (fginfo_pywrap(&dp, bindhinfo_pytype));
+}
+
+static const PyGetSetDef bingroup_pygetset[] = {
+#define GETSETNAME(op, name) bg##op##_##name
+  GET  (info,          "G.info -> information about the group")
+#undef GETSETNAME
+  { 0 }
+};
+
+static PyObject *bingroup_pynew(PyTypeObject *ty,
+                               PyObject *arg, PyObject *kw)
+{
+  PyObject *i;
+  static const char *const kwlist[] = { "info", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!:new", KWLIST,
+                                  bindhinfo_pytype, &i))
+    return (0);
+  return (group_dopywrap(ty, group_binary(FGINFO_DP(i))));
+}
+
+static const PyTypeObject bingroup_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "BinGroup",                          /* @tp_name@ */
+  sizeof(group_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  group_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "BinGroup(INFO): subgroups of binary fields.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(bingroup),                  /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  bingroup_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *egget_info(PyObject *me, void *hunoz)
+{
+  ec_info ei;
+  gctx_ec *gg = (gctx_ec *)GROUP_G(me);
+
+  ecinfo_copy(&ei, &gg->ei);
+  return (ecinfo_pywrap(&ei));
+}
+
+static const PyGetSetDef ecgroup_pygetset[] = {
+#define GETSETNAME(op, name) eg##op##_##name
+  GET  (info,          "G.info -> information about the group")
+#undef GETSETNAME
+  { 0 }
+};
+
+static PyObject *ecgroup_pynew(PyTypeObject *ty,
+                              PyObject *arg, PyObject *kw)
+{
+  PyObject *i;
+  ec_info ei;
+  static const char *const kwlist[] = { "info", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!:new", KWLIST,
+                                  ecinfo_pytype, &i))
+    return (0);
+  ecinfo_copy(&ei, ECINFO_EI(i));
+  return (group_dopywrap(ty, group_ec(&ei)));
+}
+
+static const PyTypeObject ecgroup_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "ECGroup",                           /* @tp_name@ */
+  sizeof(group_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  group_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "ECGroup(INFO): elliptic curve groups.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(ecgroup),                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  ecgroup_pynew,                       /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Global stuff ------------------------------------------------------*/
+
+void group_pyinit(void)
+{
+  INITTYPE(fginfo, root);
+  INITTYPE(dhinfo, fginfo);
+  INITTYPE(bindhinfo, dhinfo);
+  INITTYPE(ge, root);
+  INITTYPE(group, type);
+  INITTYPE(primegroup, group);
+  INITTYPE(bingroup, group);
+  INITTYPE(ecgroup, group);
+}
+
+static const char *grp_namefn(const void *p)
+  { const pentry *pt = p; return (pt->name); }
+
+static int grp_ixfn(const pentry *tab, const pentry *pt)
+{
+  int i;
+
+  for (i = 0; tab[i].name; i++)
+    if (tab[i].data == pt->data) return (i);
+  return (-1);
+}
+static int pgrp_ixfn(const void *p) { return (grp_ixfn(ptab, p)); }
+static int bgrp_ixfn(const void *p) { return (grp_ixfn(bintab, p)); }
+
+static PyObject *grp_valfn(const pentry *tab, PyTypeObject *ty, int i)
+{
+  gprime_param gp;
+
+  dh_infofromdata(&gp, tab[i].data);
+  return (fginfo_pywrap(&gp, ty));
+}
+static PyObject *pgrp_valfn(int i)
+  { return (grp_valfn(ptab, dhinfo_pytype, i)); }
+static PyObject *bgrp_valfn(int i)
+  { return (grp_valfn(bintab, bindhinfo_pytype, i)); }
+
+void group_pyinsert(PyObject *mod)
+{
+  INSERT("FGInfo", fginfo_pytype);
+  INSERT("DHInfo", dhinfo_pytype);
+  INSERT("BinDHInfo", bindhinfo_pytype);
+  INSERT("GE", ge_pytype);
+  INSERT("Group", group_pytype);
+  INSERT("PrimeGroup", primegroup_pytype);
+  INSERT("BinGroup", bingroup_pytype);
+  INSERT("ECGroup", ecgroup_pytype);
+  INSERT("primegroups", make_grouptab(ptab, sizeof(*ptab),
+                                     grp_namefn, pgrp_ixfn, pgrp_valfn));
+  INSERT("bingroups", make_grouptab(bintab, sizeof(*bintab),
+                                   grp_namefn, bgrp_ixfn, bgrp_valfn));
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/key.c b/key.c
new file mode 100644 (file)
index 0000000..2ec8a61
--- /dev/null
+++ b/key.c
@@ -0,0 +1,1964 @@
+/* -*-c-*-
+ *
+ * Key files and data
+ *
+ * (c) 2005 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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "catacomb-python.h"
+
+/*----- Exceptions --------------------------------------------------------*/
+
+static PyObject *keyexc;
+static PyObject *keyioexc;
+static PyObject *keyfilebrokenexc;
+
+static PyObject *kxmeth___init__(PyObject *me, PyObject *arg)
+{
+  long err;
+  PyObject *x = 0;
+  Py_ssize_t n;
+
+  n = PyTuple_GET_SIZE(arg);
+  if (n < 2) TYERR("__init__() takes at least two arguments");
+  me = PyTuple_GET_ITEM(arg, 0);
+  err = PyInt_AsLong(PyTuple_GET_ITEM(arg, 1));
+  if (err == -1 && PyErr_Occurred()) goto end;
+  if (INT_MIN > err || err > INT_MAX) OVFERR("error code out of range");
+
+  x = PyInt_FromLong(err); if (!x) goto end;
+  if (PyObject_SetAttrString(me, "err", x)) goto end;
+  Py_DECREF(x); x = 0;
+
+  x = TEXT_FROMSTR(key_strerror(err)); if (!x) goto end;
+  if (PyObject_SetAttrString(me, "errstring", x)) goto end;
+  Py_DECREF(x); x = 0;
+
+  x = PyTuple_GetSlice(arg, 1, n); if (!x) goto end;
+  if (PyObject_SetAttrString(me, "args", x)) goto end;
+  Py_DECREF(x); x = 0;
+  RETURN_NONE;
+
+end:
+  Py_XDECREF(x);
+  return (0);
+}
+
+static PyObject *kxmeth___str__(PyObject *me, PyObject *arg)
+{
+  long err;
+  const char *errstr, *errtag;
+  PyObject *x = 0;
+  PyObject *rc = 0;
+
+  static const char *const tab[] = {
+#define ENTRY(tag, num, str) "KERR_" #tag,
+    KEY_ERRORS(ENTRY)
+#undef ENTRY
+  };
+
+  if (!PyArg_ParseTuple(arg, "O:__str__", &me) ||
+      (x = PyObject_GetAttrString(me, "err")) == 0 ||
+      (err = PyInt_AsLong(x), PyErr_Occurred()))
+    goto done;
+  Py_DECREF(x); x = 0;
+  err = -err;
+  if (err >= 0 && err < N(tab)) errtag = tab[err];
+  else errtag = "<unknown>";
+  if ((x = PyObject_GetAttrString(me, "errstring")) == 0 ||
+      (errstr = TEXT_STR(x)) == 0)
+    goto done;
+  rc = TEXT_FORMAT("%s (%ld): %s", errtag, -err, errstr);
+
+done:
+  Py_XDECREF(x);
+  return (rc);
+}
+
+static const PyMethodDef keyexc_pymethods[] = {
+#define METHNAME(func) kxmeth_##func
+  METH (__init__,      "KeyError(CODE)")
+  METH (__str__,       "E.__str__() -> STRING")
+#undef METHNAME
+  { 0 }
+};
+
+static void keyexc_raise(int err)
+{
+  PyObject *arg = Py_BuildValue("(i)", err);
+  if (arg) PyErr_SetObject(keyexc, arg);
+  Py_XDECREF(arg);
+}
+#define KEYERR(err) do { keyexc_raise(err); goto end; } while (0)
+#define KEYIOERR(name) do {                                            \
+  PyErr_SetFromErrnoWithFilename(keyioexc, name);                      \
+  goto end;                                                            \
+} while (0)
+#define KEYFILEBROKEN(name) do {                                       \
+  PyErr_SetFromErrnoWithFilename(keyfilebrokenexc, name);              \
+  goto end;                                                            \
+} while (0)
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct keydata_pyobj {
+  GMAP_PYOBJ_HEAD
+  key_data *kd;
+} keydata_pyobj;
+
+static PyTypeObject *keydata_pytype;
+static PyTypeObject *keydatabin_pytype;
+static PyTypeObject *keydataenc_pytype;
+static PyTypeObject *keydatamp_pytype;
+static PyTypeObject *keydatastruct_pytype;
+static PyTypeObject *keydatastr_pytype;
+static PyTypeObject *keydataec_pytype;
+#define KEYDATA_PYCHECK(o) PyObject_TypeCheck(o, keydata_pytype)
+#define KEYDATA_KD(o) (((keydata_pyobj *)(o))->kd)
+
+typedef struct keyfile_pyobj {
+  GMAP_PYOBJ_HEAD
+  key_file kf;
+} keyfile_pyobj;
+
+static PyTypeObject *keyfile_pytype;
+#define KEYFILE_PYCHECK(o) PyObject_TypeCheck(o, keyfile_pytype)
+#define KEYFILE_KF(o) (&((keyfile_pyobj *)(o))->kf)
+
+typedef struct key_pyobj {
+  PyObject_HEAD
+  key *k;
+  PyObject *kfobj;
+} key_pyobj;
+
+static PyTypeObject *key_pytype;
+#define KEY_PYCHECK(o) PyObject_TypeCheck(o, key_pytype)
+#define KEY_K(o) (((key_pyobj *)(o))->k)
+#define KEY_KFOBJ(o) (((key_pyobj *)(o))->kfobj)
+#define KEY_KF(o) KEYFILE_KF(KEY_KFOBJ(o))
+
+typedef struct keyattrs_pyobj {
+  GMAP_PYOBJ_HEAD
+  PyObject *kobj;
+} keyattrs_pyobj;
+
+static PyTypeObject *keyattrs_pytype;
+#define KEYATTRS_PYCHECK(o) PyObject_TypeCheck(o, keyattrs_pytype)
+#define KEYATTRS_KOBJ(o) (((keyattrs_pyobj *)(o))->kobj)
+#define KEYATTRS_KF(o) KEY_KF(KEYATTRS_KOBJ(o))
+#define KEYATTRS_K(o) KEY_K(KEYATTRS_KOBJ(o))
+
+/*----- Filters -----------------------------------------------------------*/
+
+static int convfilter(PyObject *x, void *p)
+{
+  key_filter *f = p;
+  const char *fs;
+  char *end;
+  int n;
+  PyObject *a = 0, *b = 0;
+  int err;
+  int rc = 0;
+
+  if ((fs = TEXT_STR(x)) != 0) {
+    if ((err = key_readflags(fs, &end, &f->f, &f->m)) != 0)
+      KEYERR(err);
+    if (*end)
+      KEYERR(KERR_BADFLAGS);
+  } else {
+    PyErr_Clear();
+    if (!PySequence_Check(x))
+      goto tyerr;
+    else if ((n = PySequence_Size(x)) < 0)
+      goto end;
+    else if (n != 2)
+      goto tyerr;
+    else if ((a = PySequence_GetItem(x, 0)) == 0 || !convuint(a, &f->f) ||
+            (b = PySequence_GetItem(x, 1)) == 0 || !convuint(b, &f->m))
+      goto end;
+  }
+  rc = 1;
+  goto end;
+tyerr:
+  TYERR("expected flag string or flag/mask pair");
+end:
+  Py_XDECREF(a);
+  Py_XDECREF(b);
+  return (rc);
+}
+
+static int convflags(PyObject *x, void *p)
+{
+  unsigned *f = p;
+  const char *fs;
+  char *end;
+  int err;
+  int rc = 0;
+
+  if (convuint(x, p))
+    return (1);
+  else {
+    PyErr_Clear();
+    if ((fs = TEXT_STR(x)) != 0) {
+      if ((err = key_readflags(fs, &end, f, 0)) != 0)
+       KEYERR(err);
+      if (*end)
+       KEYERR(KERR_BADFLAGS);
+    } else {
+      PyErr_Clear();
+      goto tyerr;
+    }
+  }
+  rc = 1;
+  goto end;
+tyerr:
+  TYERR("expected flag string or integer bitfield");
+end:
+  return (rc);
+}
+
+static PyObject *kdmeth_readflags(PyObject *me, PyObject *arg)
+{
+  const char *p;
+  char *end;
+  unsigned f, m;
+  PyObject *rc = 0;
+  int err;
+
+  if (!PyArg_ParseTuple(arg, "s:readflags", &p)) goto end;
+  if ((err = key_readflags(p, &end, &f, &m)) != 0) KEYERR(err);
+  rc = Py_BuildValue("(NNs)", getulong(f), getulong(m), end);
+end:
+  return (rc);
+}
+
+static PyObject *kdmeth_writeflags(PyObject *me, PyObject *arg)
+{
+  dstr d = DSTR_INIT;
+  PyObject *rc;
+  unsigned f;
+
+  if (!PyArg_ParseTuple(arg, "O&:key_writeflags", convuint, &f)) return (0);
+  key_writeflags(f, &d);
+  rc = TEXT_FROMSTRLEN(d.buf, d.len);
+  dstr_destroy(&d);
+  return (rc);
+}
+
+/*----- Key data ----------------------------------------------------------*/
+
+static const gmap_ops keydatastruct_gmops;
+
+static PyObject *keydata_pywrap(key_data *kd)
+{
+  PyTypeObject *ty;
+  keydata_pyobj *kdobj;
+  const gmap_ops *gmops = 0;
+
+  switch (kd->e & KF_ENCMASK) {
+    case KENC_BINARY: ty = keydatabin_pytype; break;
+    case KENC_ENCRYPT: ty = keydataenc_pytype; break;
+    case KENC_MP: ty = keydatamp_pytype; break;
+    case KENC_STRUCT:
+      ty = keydatastruct_pytype;
+      gmops = &keydatastruct_gmops;
+      break;
+    case KENC_STRING: ty = keydatastr_pytype; break;
+    case KENC_EC: ty = keydataec_pytype; break;
+    default: abort();
+  }
+  kdobj = PyObject_NEW(keydata_pyobj, ty);
+  kdobj->gmops = gmops;
+  kdobj->kd = kd;
+  return ((PyObject *)kdobj);
+}
+
+static void keydata_pydealloc(PyObject *me)
+{
+  key_drop(KEYDATA_KD(me));
+  FREEOBJ(me);
+}
+
+static PyObject *kdmeth_matchp(PyObject *me, PyObject *arg)
+{
+  key_filter f;
+
+  if (!PyArg_ParseTuple(arg, "O&:matchp", convfilter, &f))
+    return (0);
+  return (getbool(KEY_MATCH(KEYDATA_KD(me), &f)));
+}
+
+static PyObject *kdmeth_split(PyObject *me)
+  { key_split(&KEYDATA_KD(me)); RETURN_ME; }
+
+static PyObject *kdmeth_copy(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  key_filter f = { 0, 0 };
+  static const char *const kwlist[] = { "filter", 0 };
+  key_data *kd;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:copy", KWLIST,
+                                  convfilter, &f))
+    return (0);
+  if ((kd = key_copydata(KEYDATA_KD(me), &f)) == 0)
+    RETURN_NONE;
+  else
+    return (keydata_pywrap(kd));
+}
+
+static PyObject *kdmeth_write(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  key_filter f = { 0, 0 };
+  dstr d = DSTR_INIT;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "filter", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:write", KWLIST,
+                                  convfilter, &f))
+    return (0);
+  key_write(KEYDATA_KD(me), &d, &f);
+  rc = TEXT_FROMSTRLEN(d.buf, d.len);
+  dstr_destroy(&d);
+  return (rc);
+}
+
+static PyObject *kdmeth_encode(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  key_filter f = { 0, 0 };
+  dstr d = DSTR_INIT;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "filter", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:encode", KWLIST,
+                                  convfilter, &f))
+    return (0);
+  key_encode(KEYDATA_KD(me), &d, &f);
+  rc = bytestring_pywrap(d.buf, d.len);
+  dstr_destroy(&d);
+  return (rc);
+}
+
+static PyObject *kdmeth_plock(PyObject *me, PyObject *arg)
+{
+  char *tag;
+  int err;
+  PyObject *rc = 0;
+  key_data *kd;
+
+  if (!PyArg_ParseTuple(arg, "s:plock", &tag))
+    goto end;
+  if ((err = key_plock(&kd, KEYDATA_KD(me), tag)) != 0)
+    KEYERR(err);
+  rc = keydata_pywrap(kd);
+end:
+  return (rc);
+}
+
+static PyObject *kdmeth_lock(PyObject *me, PyObject *arg)
+{
+  struct bin pp;
+  PyObject *rc = 0;
+  key_data *kd;
+
+  if (!PyArg_ParseTuple(arg, "O&:lock", convbin, &pp))
+    goto end;
+  key_lock(&kd, KEYDATA_KD(me), pp.p, pp.sz);
+  rc = keydata_pywrap(kd);
+end:
+  return (rc);
+}
+
+static PyObject *kdmeth_read(PyObject *me, PyObject *arg)
+{
+  const char *p;
+  char *end;
+  key_data *kd;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "s:read", &p)) goto end;
+  if ((kd = key_read(p, &end)) == 0) KEYERR(KERR_MALFORMED);
+  rc = Py_BuildValue("(Ns)", keydata_pywrap(kd), end);
+end:
+  return (rc);
+}
+
+static PyObject *kdmeth_decode(PyObject *me, PyObject *arg)
+{
+  struct bin in;
+  key_data *kd;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:decode", convbin, &in)) goto end;
+  if ((kd = key_decode(in.p, in.sz)) == 0) KEYERR(KERR_MALFORMED);
+  rc = keydata_pywrap(kd);
+end:
+  return (rc);
+}
+
+static PyObject *kdget_flags(PyObject *me, void *hunoz)
+  { return (getulong(KEYDATA_KD(me)->e)); }
+
+static const PyMethodDef keydata_pymethods[] = {
+#define METHNAME(func) kdmeth_##func
+  METH (matchp,        "KD.matchp(FILTER) -> BOOL")
+  NAMETH(split,                "KD.split()")
+  KWMETH(write,                "KD.write([filter = <any>]) -> STRING")
+  KWMETH(encode,       "KD.encode([filter = <any>]) -> BYTES")
+  KWMETH(copy,         "KD.copy([filter = <any>]) -> KD")
+  METH (plock,         "KD.plock(TAG) -> ENCRYPTED-KD")
+  METH (lock,          "KD.lock(KEY) -> ENCRYPTED-KD")
+  SMTH (readflags,     "readflags(STRING) -> (FLAGS, MASK, REST)")
+  SMTH (writeflags,    "writeflags(FLAGS) -> STRING")
+  SMTH (read,          "read(STRING) -> (KD, REST)")
+  SMTH (decode,        "decode(BYTES) -> KD")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyGetSetDef keydata_pygetset[] = {
+#define GETSETNAME(op, name) kd##op##_##name
+  GET  (flags,         "KD.flags -> FLAGS")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject keydata_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KeyData",                           /* @tp_name@ */
+  sizeof(keydata_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  keydata_pydealloc,                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Key data base class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(keydata),                  /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(keydata),                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *keydatabin_pynew(PyTypeObject *ty,
+                                 PyObject *arg, PyObject *kw)
+{
+  struct bin in;
+  unsigned f = 0;
+  keydata_pyobj *me = 0;
+  static const char *const kwlist[] = { "key", "flags", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new", KWLIST,
+                                  convbin, &in, convflags, &f))
+    goto end;
+  me = (keydata_pyobj *)ty->tp_alloc(ty, 0);
+  me->kd = key_newbinary(f & ~KF_ENCMASK, in.p, in.sz);
+end:
+  return ((PyObject *)me);
+}
+
+static PyObject *kdbget_bin(PyObject *me, void *hunoz)
+  { return (bytestring_pywrap(KEYDATA_KD(me)->u.k.k,
+                             KEYDATA_KD(me)->u.k.sz)); }
+
+static const PyGetSetDef keydatabin_pygetset[] = {
+#define GETSETNAME(op, name) kdb##op##_##name
+  GET  (bin,           "KD.bin -> BYTES")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject keydatabin_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KeyDataBinary",                     /* @tp_name@ */
+  sizeof(keydata_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KeyDataBinary(KEY, [flags = 0]): key data for binary keys.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(keydatabin),                        /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  keydatabin_pynew,                    /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *keydataenc_pynew(PyTypeObject *ty,
+                                 PyObject *arg, PyObject *kw)
+{
+  struct bin in;
+  unsigned f = 0;
+  keydata_pyobj *me = 0;
+  static const char *const kwlist[] = { "key", "flags", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new", KWLIST,
+                                  convbin, &in, convflags, &f))
+    goto end;
+  me = (keydata_pyobj *)ty->tp_alloc(ty, 0);
+  me->kd = key_newencrypted(f & ~KF_ENCMASK, in.p, in.sz);
+end:
+  return ((PyObject *)me);
+}
+
+static PyObject *kdemeth_plock(PyObject *me, PyObject *arg)
+{
+  char *hunoz;
+  if (!PyArg_ParseTuple(arg, "s:plock", &hunoz)) goto end;
+  KEYERR(KERR_WRONGTYPE);
+end:
+  return (0);
+}
+
+static PyObject *kdemeth_lock(PyObject *me, PyObject *arg)
+{
+  struct bin hunoz;
+  if (!PyArg_ParseTuple(arg, "O&:lock", convbin, &hunoz)) goto end;
+  KEYERR(KERR_WRONGTYPE);
+end:
+  return (0);
+}
+
+static PyObject *kdemeth_punlock(PyObject *me, PyObject *arg)
+{
+  char *tag;
+  int err;
+  PyObject *rc = 0;
+  key_data *kd;
+
+  if (!PyArg_ParseTuple(arg, "s:punlock", &tag))
+    goto end;
+  if ((err = key_punlock(&kd, KEYDATA_KD(me), tag)) != 0)
+    KEYERR(err);
+  rc = keydata_pywrap(kd);
+end:
+  return (rc);
+}
+
+static PyObject *kdemeth_unlock(PyObject *me, PyObject *arg)
+{
+  struct bin pp;
+  int err;
+  PyObject *rc = 0;
+  key_data *kd;
+
+  if (!PyArg_ParseTuple(arg, "O&:unlock", convbin, &pp))
+    goto end;
+  if ((err = key_unlock(&kd, KEYDATA_KD(me), pp.p, pp.sz)) != 0)
+    KEYERR(err);
+  rc = keydata_pywrap(kd);
+end:
+  return (rc);
+}
+
+#define kdeget_ct kdbget_bin
+
+static const PyMethodDef keydataenc_pymethods[] = {
+#define METHNAME(func) kdemeth_##func
+  METH (plock,         "KD.plock(TAG) -> ENCRYPTED-KD")
+  METH (lock,          "KD.lock(KEY) -> ENCRYPTED-KD")
+  METH (punlock,       "KD.punlock(TAG) -> KD")
+  METH (unlock,        "KD.unlock(KEY) -> KD")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyGetSetDef keydataenc_pygetset[] = {
+#define GETSETNAME(op, name) kde##op##_##name
+  GET  (ct,            "KD.ct -> BYTES")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject keydataenc_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KeyDataEncrypted",                  /* @tp_name@ */
+  sizeof(keydata_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KeyDataEncrypted(KEY, [flags = 0]): key data for encrypted keys.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(keydataenc),               /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(keydataenc),                        /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  keydataenc_pynew,                    /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *keydatamp_pynew(PyTypeObject *ty,
+                                PyObject *arg, PyObject *kw)
+{
+  mp *x = 0;
+  unsigned f = 0;
+  keydata_pyobj *me = 0;
+  static const char *const kwlist[] = { "key", "flags", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new", KWLIST,
+                                  convmp, &x, convflags, &f))
+    goto end;
+  me = (keydata_pyobj *)ty->tp_alloc(ty, 0);
+  me->kd = key_newmp(f & ~KF_ENCMASK, x);
+end:
+  mp_drop(x);
+  return ((PyObject *)me);
+}
+
+static PyObject *kdmget_mp(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(KEYDATA_KD(me)->u.m))); }
+
+static const PyGetSetDef keydatamp_pygetset[] = {
+#define GETSETNAME(op, name) kdm##op##_##name
+  GET  (mp,            "KD.mp -> X")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject keydatamp_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KeyDataMP",                         /* @tp_name@ */
+  sizeof(keydata_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KeyDataMP(KEY, [flags = 0]): key data for large-integer keys.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(keydatamp),                 /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  keydatamp_pynew,                     /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *keydatastr_pynew(PyTypeObject *ty,
+                                 PyObject *arg, PyObject *kw)
+{
+  char *p;
+  unsigned f = 0;
+  keydata_pyobj *me = 0;
+  static const char *const kwlist[] = { "key", "flags", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|O&:new", KWLIST,
+                                  &p, convflags, &f))
+    goto end;
+  me = (keydata_pyobj *)ty->tp_alloc(ty, 0);
+  me->kd = key_newstring(f & ~KF_ENCMASK, p);
+end:
+  return ((PyObject *)me);
+}
+
+static PyObject *kdsget_str(PyObject *me, void *hunoz)
+  { return (TEXT_FROMSTR(KEYDATA_KD(me)->u.p)); }
+
+static const PyGetSetDef keydatastr_pygetset[] = {
+#define GETSETNAME(op, name) kds##op##_##name
+  GET  (str,           "KD.str -> STRING")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject keydatastr_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KeyDataString",                     /* @tp_name@ */
+  sizeof(keydata_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KeyDataString(KEY, [flags = 0]): key data for string keys.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(keydatastr),                        /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  keydatastr_pynew,                    /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *keydataec_pynew(PyTypeObject *ty,
+                                PyObject *arg, PyObject *kw)
+{
+  ec x = EC_INIT;
+  unsigned f = 0;
+  keydata_pyobj *me = 0;
+  static const char *const kwlist[] = { "key", "flags", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new", KWLIST,
+                                  convecpt, &x, convflags, &f))
+    goto end;
+  me = (keydata_pyobj *)ty->tp_alloc(ty, 0);
+  me->kd = key_newec(f & ~KF_ENCMASK, &x);
+end:
+  EC_DESTROY(&x);
+  return ((PyObject *)me);
+}
+
+static PyObject *kdeget_ecpt(PyObject *me, void *hunoz)
+{
+  ec pt = EC_INIT;
+  EC_COPY(&pt, &KEYDATA_KD(me)->u.e);
+  return (ecpt_pywrapout(ecpt_pytype, &pt));
+}
+
+static const PyGetSetDef keydataec_pygetset[] = {
+#define GETSETNAME(op, name) kde##op##_##name
+  GET  (ecpt,          "KD.ecpt -> ECPT")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject keydataec_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KeyDataECPt",                       /* @tp_name@ */
+  sizeof(keydata_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KeyDataECPt(KEY, [flags = 0]): key data for elliptic-curve keys.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(keydataec),                 /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  keydataec_pynew,                     /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static void *keydatastruct_gmlookup(PyObject *me, PyObject *k, unsigned *f)
+{
+  key_data *kd = KEYDATA_KD(me);
+  const char *tag;
+  key_struct *ks;
+
+  assert((kd->e&KF_ENCMASK) == KENC_STRUCT);
+  if ((tag = TEXT_STR(k)) == 0) return (0);
+  if (f) { key_split(&kd); KEYDATA_KD(me) = kd; }
+  ks = sym_find(&kd->u.s, tag, -1, f ? sizeof(key_struct) : 0, f);
+  if (ks && f && !*f) ks->k = 0;
+  return (ks);
+}
+
+static void keydatastruct_gmiterinit(PyObject *me, void *i)
+{
+  key_data *kd = KEYDATA_KD(me);
+
+  assert((kd->e&KF_ENCMASK) == KENC_STRUCT);
+  sym_mkiter(i, &kd->u.s);
+}
+
+static void *keydatastruct_gmiternext(PyObject *me, void *i)
+  { return (sym_next(i)); }
+
+static PyObject *keydatastruct_gmentrykey(PyObject *me, void *e)
+  { key_struct *ks = e; return (TEXT_FROMSTR(SYM_NAME(ks))); }
+
+static PyObject *keydatastruct_gmentryvalue(PyObject *me, void *e)
+{
+  key_struct *ks = e;
+  key_data *kd = ks->k;
+
+  key_incref(kd);
+  return (keydata_pywrap(kd));
+}
+
+static key_struct *maybe_split_and_reprobe(PyObject *me, key_struct *ks)
+{
+  key_data *kd = KEYDATA_KD(me);
+
+  if (kd->ref == 1) return (ks);
+  key_split(&kd); KEYDATA_KD(me) = kd;
+  ks = sym_find(&kd->u.s, SYM_NAME(ks), SYM_LEN(ks), 0, 0); assert(ks);
+  return (ks);
+}
+
+static int keydatastruct_gmsetentry(PyObject *me, void *e, PyObject *val)
+{
+  key_struct *ks;
+  key_data *kd;
+  int rc = -1;
+
+  if (!KEYDATA_PYCHECK(val)) TYERR("expected KeyData value");
+  ks = maybe_split_and_reprobe(me, e);
+  if (ks->k) key_drop(ks->k);
+  ks->k = kd = KEYDATA_KD(val); key_incref(kd);
+  rc = 0;
+end:
+  return (rc);
+}
+
+static int keydatastruct_gmdelentry(PyObject *me, void *e)
+{
+  key_struct *ks;
+
+  ks = maybe_split_and_reprobe(me, e);
+  if (ks->k) key_drop(ks->k);
+  sym_remove(&KEYDATA_KD(me)->u.s, ks);
+  return (0);
+}
+
+static const gmap_ops keydatastruct_gmops = {
+  sizeof(sym_iter),
+  keydatastruct_gmlookup,
+  keydatastruct_gmiterinit,
+  keydatastruct_gmiternext,
+  keydatastruct_gmentrykey,
+  keydatastruct_gmentryvalue,
+  keydatastruct_gmsetentry,
+  keydatastruct_gmdelentry
+};
+
+static int populate_struct(key_data *kd, PyObject *map)
+{
+  PyObject *it = 0, *name = 0, *val = 0;
+  const char *p;
+  int rc = -1;
+
+  if (!PyMapping_Check(map)) TYERR("subkeys must be an iterable mapping");
+  if ((it = PyObject_GetIter(map)) == 0) goto end;
+  while ((name = PyIter_Next(it)) != 0) {
+    if ((p = TEXT_STR(name)) == 0 ||
+       (val = PyObject_GetItem(map, name)) == 0)
+      goto end;
+    if (!KEYDATA_PYCHECK(val))
+      TYERR("subkey objects must be instances of KeyData");
+    if (key_structfind(kd, p)) VALERR("duplicate tag");
+    key_structset(kd, p, KEYDATA_KD(val));
+    Py_DECREF(name); name = 0;
+    Py_DECREF(val); val = 0;
+  }
+  if (PyErr_Occurred()) goto end;
+  rc = 0;
+end:
+  Py_XDECREF(it); Py_XDECREF(name); Py_XDECREF(val);
+  return (rc);
+}
+
+static PyObject *keydatastruct_pynew(PyTypeObject *ty,
+                                    PyObject *arg, PyObject *kw)
+{
+  PyObject *sub = 0;
+  keydata_pyobj *me = 0;
+  key_data *kd = 0;
+
+  if (!PyArg_ParseTuple(arg, "|O:new", &sub)) goto end;
+  kd = key_newstruct();
+  if (sub && populate_struct(kd, sub)) goto end;
+  if (kw && populate_struct(kd, kw)) goto end;
+  me = (keydata_pyobj *)ty->tp_alloc(ty, 0);
+  me->gmops = &keydatastruct_gmops;
+  me->kd = kd; kd = 0;
+end:
+  if (kd) key_drop(kd);
+  return ((PyObject *)me);
+}
+
+static Py_ssize_t keydatastruct_pysize(PyObject *me)
+  { return gmap_pysize_from_sym(&KEYDATA_KD(me)->u.s); }
+
+static const PyMappingMethods keydatastruct_pymapping = {
+  keydatastruct_pysize,
+  gmap_pylookup,
+  gmap_pystore
+};
+
+static const PyTypeObject keydatastruct_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KeyDataStructured",                 /* @tp_name@ */
+  sizeof(keydata_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  PYSEQUENCE(gmap),                    /* @tp_as_sequence@ */
+  PYMAPPING(keydatastruct),            /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KeyDataStructured([subkeys = []]): key data for structured keys.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  gmap_pyiter,                         /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gmap),                     /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  keydatastruct_pynew,                 /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Key attributes ----------------------------------------------------*/
+
+static void *keyattrs_gmlookup(PyObject *me, PyObject *k, unsigned *f)
+{
+  char *name = TEXT_STR(k);
+  key_attr *a = 0;
+
+  if (!name) goto end;
+  if (f && !(KEYATTRS_KF(me)->f&KF_WRITE)) KEYERR(KERR_READONLY);
+  a = sym_find(&KEYATTRS_K(me)->a, name, -1, f ? sizeof(key_attr) : 0, f);
+  if (f && !*f) a->p = 0;
+end:
+  return (a);
+}
+
+static void keyattrs_gmiterinit(PyObject *me, void *i)
+  { sym_mkiter(i, &KEYATTRS_K(me)->a); }
+
+static void *keyattrs_gmiternext(PyObject *me, void *i)
+  { return (sym_next(i)); }
+
+static PyObject *keyattrs_gmentrykey(PyObject *me, void *e)
+  { return (TEXT_FROMSTR(SYM_NAME(e))); }
+
+static PyObject *keyattrs_gmentryvalue(PyObject *me, void *e)
+  { return (TEXT_FROMSTR(((key_attr *)e)->p)); }
+
+static int keyattrs_gmsetentry(PyObject *me, void *e, PyObject *val)
+{
+  key_attr *a = e;
+  const char *p;
+  Py_ssize_t n;
+  int rc = -1;
+
+  if (!TEXT_CHECK(val)) TYERR("expected string");
+  TEXT_PTRLEN(val, p, n);
+  if (n > 255) VALERR("attribute too long");
+  if (memchr(p, 0, n)) VALERR("attribute must not contain nul");
+  if (a->p) xfree(a->p);
+  a->p = xmalloc(n + 1); memcpy(a->p, p, n + 1);
+  KEYATTRS_KF(me)->f |= KF_MODIFIED;
+  rc = 0;
+end:
+  return (rc);
+}
+
+static int keyattrs_gmdelentry(PyObject *me, void *e)
+{
+  key_attr *a = e;
+  int rc = -1;
+
+  if (!(KEYATTRS_KF(me)->f&KF_WRITE)) KEYERR(KERR_READONLY);
+  if (a->p) { KEYATTRS_KF(me)->f |= KF_MODIFIED; xfree(a->p); }
+  sym_remove(&KEYATTRS_K(me)->a, a);
+  rc = 0;
+end:
+  return (rc);
+}
+
+static const gmap_ops keyattrs_gmops = {
+  sizeof(sym_iter),
+  keyattrs_gmlookup,
+  keyattrs_gmiterinit,
+  keyattrs_gmiternext,
+  keyattrs_gmentrykey,
+  keyattrs_gmentryvalue,
+  keyattrs_gmsetentry,
+  keyattrs_gmdelentry
+};
+
+static PyObject *keyattrs_make(PyObject *kobj)
+{
+  keyattrs_pyobj *me = PyObject_NEW(keyattrs_pyobj, keyattrs_pytype);
+  me->gmops = &keyattrs_gmops;
+  me->kobj = kobj;
+  Py_INCREF(kobj);
+  return ((PyObject *)me);
+}
+
+static void keyattrs_pydealloc(PyObject *me)
+{
+  Py_DECREF(KEYATTRS_KOBJ(me));
+  FREEOBJ(me);
+}
+
+static Py_ssize_t keyattrs_pysize(PyObject *me)
+  { return gmap_pysize_from_sym(&KEYATTRS_K(me)->a); }
+
+static const PyMappingMethods keyattrs_pymapping = {
+  keyattrs_pysize,
+  gmap_pylookup,
+  gmap_pystore
+};
+
+static const PyTypeObject keyattrs_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KeyAttributes",                     /* @tp_name@ */
+  sizeof(keyattrs_pyobj),              /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  keyattrs_pydealloc,                  /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  PYSEQUENCE(gmap),                    /* @tp_as_sequence@ */
+  PYMAPPING(keyattrs),                 /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Proxy thing for talking about key attributes.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  gmap_pyiter,                         /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gmap),                     /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Key objects -------------------------------------------------------*/
+
+static PyObject *key_dowrap(PyTypeObject *ty, PyObject *kfobj, key *k)
+{
+  key_pyobj *kobj = (key_pyobj *)ty->tp_alloc(ty, 0);
+  kobj->kfobj = kfobj;
+  Py_INCREF(kfobj);
+  kobj->k = k;
+  return ((PyObject *)kobj);
+}
+
+static PyObject *key_pywrap(PyObject *kfobj, key *k)
+  { return (key_dowrap(key_pytype, kfobj, k)); }
+
+static PyObject *key_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  PyObject *kfobj;
+  uint32 id;
+  char *type;
+  unsigned long exptime = KEXP_FOREVER;
+  static const char *const kwlist[] =
+    { "keyfile", "id", "type", "exptime", 0 };
+  key *k;
+  int err;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O&s|O&:new", KWLIST,
+                                  keyfile_pytype, &kfobj, convu32, &id,
+                                  &type, convulong, &exptime))
+    goto end;
+  if ((err = key_new(KEYFILE_KF(kfobj), id, type, exptime, &k)) != 0)
+    KEYERR(err);
+  return (key_dowrap(ty, kfobj, k));
+end:
+  return (0);
+}
+
+static void key_pydealloc(PyObject *me)
+{
+  Py_DECREF(KEY_KFOBJ(me));
+  FREEOBJ(me);
+}
+
+static Py_hash_t key_pyhash(PyObject *me)
+  { return ((Py_hash_t)KEY_K(me)); }
+
+static PyObject *key_pyrichcompare(PyObject *me, PyObject *you, int op)
+{
+  if (!KEY_PYCHECK(you)) RETURN_NOTIMPL;
+  switch (op) {
+    case Py_EQ: return (getbool(KEY_K(me) == KEY_K(you)));
+    case Py_NE: return (getbool(KEY_K(me) == KEY_K(you)));
+    default: TYERR("ordering makes no sense");
+  }
+end:
+  return (0);
+}
+
+static PyObject *kmeth_delete(PyObject *me)
+{
+  int err;
+
+  if ((err = key_delete(KEY_KF(me), KEY_K(me))) != 0) KEYERR(err);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *kmeth_expire(PyObject *me)
+{
+  int err;
+
+  if ((err = key_expire(KEY_KF(me), KEY_K(me))) != 0) KEYERR(err);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *kmeth_used(PyObject *me, PyObject *arg)
+{
+  long t;
+  int err;
+
+  if (!PyArg_ParseTuple(arg, "l:used", &t)) goto end;
+  if ((err = key_used(KEY_KF(me), KEY_K(me), t)) != 0) KEYERR(err);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *kmeth_extractline(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  key_filter f = { 0, 0 };
+  dstr d = DSTR_INIT;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "filter", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:extract", KWLIST,
+                                  convfilter, &f))
+    goto end;
+  key_extractline(KEY_KF(me), KEY_K(me), &d, &f);
+  rc = TEXT_FROMSTRLEN(d.buf, d.len);
+end:
+  dstr_destroy(&d);
+  return (rc);
+}
+
+static PyObject *kmeth_fingerprint(PyObject *me,
+                                  PyObject *arg, PyObject *kw)
+{
+  ghash *h;
+  key_filter f = { KF_NONSECRET, KF_NONSECRET };
+  static const char *const kwlist[] = { "hash", "filter", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:fingerprint", KWLIST,
+                                  convghash, &h, convfilter, &f))
+    return (0);
+  return (getbool(key_fingerprint(KEY_K(me), h, &f)));
+}
+
+static PyObject *kget_id(PyObject *me, void *hunoz)
+  { return (getulong(KEY_K(me)->id)); }
+static PyObject *kget_file(PyObject *me, void *hunoz)
+  { RETURN_OBJ(KEY_KFOBJ(me)); }
+static PyObject *kget_type(PyObject *me, void *hunoz)
+  { return (TEXT_FROMSTR(KEY_K(me)->type)); }
+static PyObject *kget_exptime(PyObject *me, void *hunoz)
+  { return (getulong(KEY_K(me)->exp)); }
+static PyObject *kget_deltime(PyObject *me, void *hunoz)
+  { return (getulong(KEY_K(me)->del)); }
+static PyObject *kget_expiredp(PyObject *me, void *hunoz)
+  { return (getbool(key_expired(KEY_K(me)))); }
+static PyObject *kget_attr(PyObject *me, void *hunoz)
+  { return (keyattrs_make(me)); }
+
+static int kset_exptime(PyObject *me, PyObject *x, void *hunoz)
+{
+  key *k = KEY_K(me);
+  unsigned long et;
+
+  if (!x) NIERR("__del__");
+  if (!convulong(x, &et))
+    goto end;
+  if (!(KEY_KF(me)->f & KF_WRITE))
+    KEYERR(KERR_READONLY);
+  k->exp = et;
+  KEY_KF(me)->f |= KF_MODIFIED;
+  return (0);
+end:
+  return (-1);
+}
+
+static int kset_deltime(PyObject *me, PyObject *x, void *hunoz)
+{
+  key *k = KEY_K(me);
+  unsigned long dt;
+
+  if (!x) NIERR("__del__");
+  if (!convulong(x, &dt))
+    goto end;
+  if (dt == KEXP_FOREVER && k->exp != KEXP_FOREVER)
+    VALERR("key will eventually expire");
+  if (!(KEY_KF(me)->f & KF_WRITE))
+    KEYERR(KERR_READONLY);
+  k->del = dt;
+  KEY_KF(me)->f |= KF_MODIFIED;
+  return (0);
+end:
+  return (-1);
+}
+
+static PyObject *kget_data(PyObject *me, void *hunoz)
+{
+  key_data *kd = KEY_K(me)->k;
+  key_incref(kd);
+  return (keydata_pywrap(kd));
+}
+static int kset_data(PyObject *me, PyObject *x, void *hunoz)
+{
+  int err;
+
+  if (!x) NIERR("__del__");
+  if (!KEYDATA_PYCHECK(x)) TYERR("expected KeyData object");
+  if ((err = key_setkeydata(KEY_KF(me), KEY_K(me), KEYDATA_KD(x))) != 0)
+    KEYERR(err);
+  return (0);
+end:
+  return (-1);
+}
+
+static PyObject *kget_fulltag(PyObject *me, void *hunoz)
+{
+  dstr d = DSTR_INIT;
+  PyObject *rc;
+
+  key_fulltag(KEY_K(me), &d);
+  rc = TEXT_FROMSTRLEN(d.buf, d.len);
+  dstr_destroy(&d);
+  return (rc);
+}
+
+static PyObject *kget_tag(PyObject *me, void *hunoz)
+{
+  if (!KEY_K(me)->tag) RETURN_NONE;
+  return (TEXT_FROMSTR(KEY_K(me)->tag));
+}
+static int kset_tag(PyObject *me, PyObject *x, void *hunoz)
+{
+  int err;
+  char *tag;
+
+  if (!x || x == Py_None) tag = 0;
+  else if ((tag = TEXT_STR(x)) == 0) goto end;
+  if ((err = key_settag(KEY_KF(me), KEY_K(me), tag)) != 0) KEYERR(err);
+  return (0);
+end:
+  return (-1);
+}
+
+static PyObject *kget_comment(PyObject *me, void *hunoz)
+{
+  if (!KEY_K(me)->c) RETURN_NONE;
+  return (TEXT_FROMSTR(KEY_K(me)->c));
+}
+static int kset_comment(PyObject *me, PyObject *x, void *hunoz)
+{
+  int err;
+  char *c;
+
+  if (!x || x == Py_None) c = 0;
+  else if ((c = TEXT_STR(x)) == 0) goto end;
+  if ((err = key_setcomment(KEY_KF(me), KEY_K(me), c)) != 0) KEYERR(err);
+  return (0);
+end:
+  return (-1);
+}
+
+static const PyMethodDef key_pymethods[] = {
+#define METHNAME(func) kmeth_##func
+  NAMETH(delete,       "KEY.delete()")
+  NAMETH(expire,       "KEY.expire()")
+  METH (used,          "KEY.used(TIME)")
+  KWMETH(extractline,  "KEY.extractline([filter = <any>])")
+  KWMETH(fingerprint,  "KEY.fingerprint(HASH, [filter = '-secret'])")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyGetSetDef key_pygetset[] = {
+#define GETSETNAME(op, name) k##op##_##name
+  GET  (file,          "KEY.file -> KF")
+  GET  (id,            "KEY.id -> ID")
+  GETSET(tag,          "KEY.tag -> TAG")
+  GET  (type,          "KEY.type -> TYPE")
+  GETSET(exptime,      "KEY.exptime -> TIME")
+  GETSET(deltime,      "KEY.deltime -> TIME")
+  GET  (expiredp,      "KEY.expiredp -> BOOL")
+  GET  (attr,          "KEY.attr -> ATTRIBUTES")
+  GETSET(data,         "KEY.data -> KD")
+  GETSET(comment,      "KEY.comment -> COMMENT")
+  GET  (fulltag,       "KEY.fulltag -> FULLTAG")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject key_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "Key",                               /* @tp_name@ */
+  sizeof(key_pyobj),                   /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  key_pydealloc,                       /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  key_pyhash,                          /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Key(KF, ID, TYPE, [exptime = KEXP_FOREVER]): key object.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  key_pyrichcompare,                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(key),                      /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(key),                       /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  key_pynew,                           /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Key files ---------------------------------------------------------*/
+
+static key *bytag(PyObject *me, PyObject *tagobj)
+{
+  uint32 id;
+  char *tag;
+  key *k = 0;
+
+  if (convu32(tagobj, &id))
+    k = key_byid(KEYFILE_KF(me), id);
+  else {
+    PyErr_Clear();
+    if ((tag = TEXT_STR(tagobj)) == 0)
+      goto end;
+    k = key_bytag(KEYFILE_KF(me), tag);
+  }
+end:
+  return (k);
+}
+
+static void *keyfile_gmlookup(PyObject *me, PyObject *k, unsigned *f)
+  { key *kk = bytag(me, k); if (f) *f = !!kk; return (kk); }
+
+static void keyfile_gmiterinit(PyObject *me, void *i)
+  { key_mkiter(i, KEYFILE_KF(me)); }
+
+static void *keyfile_gmiternext(PyObject *me, void *i)
+  { return (key_next(i)); }
+
+static PyObject *keyfile_gmentrykey(PyObject *me, void *e)
+  { key *k = e; return (getulong(k->id)); }
+
+static PyObject *keyfile_gmentryvalue(PyObject *me, void *e)
+  { return (key_pywrap(me, e)); }
+
+static const gmap_ops keyfile_gmops = {
+  sizeof(key_iter),
+  keyfile_gmlookup,
+  keyfile_gmiterinit,
+  keyfile_gmiternext,
+  keyfile_gmentrykey,
+  keyfile_gmentryvalue,
+  0,
+  0
+};
+
+struct reportinfo {
+  PyObject *func;
+  int stop;
+};
+
+static void pythonreporter(const char *file, int line,
+                          const char *msg, void *p)
+{
+  struct reportinfo *ri = p;
+  PyObject *res = 0;
+
+  if (ri->stop)
+    return;
+  if (ri->func == Py_None)
+    key_moan(file, line, msg, 0);
+  else if ((res = PyObject_CallFunction(ri->func, "sis",
+                                       file, line, msg)) == 0)
+    ri->stop = 1;
+  else
+    Py_DECREF(res);
+}
+
+static PyObject *keyfile_pynew(PyTypeObject *ty,
+                              PyObject *arg, PyObject *kw)
+{
+  struct reportinfo ri = { Py_None, 0 };
+  char *file = 0;
+  unsigned how = KOPEN_READ;
+  keyfile_pyobj *rc = 0;
+  static const char *const kwlist[] = { "file", "how", "report", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|iO:new", KWLIST,
+                                  &file, &how, &ri.func))
+    goto end;
+  if (ri.func != Py_None && !PyCallable_Check(ri.func))
+    TYERR("reporter function not callable");
+  if ((rc = (keyfile_pyobj *)ty->tp_alloc(ty, 0)) == 0)
+    goto end;
+  rc->gmops = &keyfile_gmops;
+  if (key_open(&rc->kf, file, how, pythonreporter, &ri))
+    OSERR(file);
+  if (ri.stop) {
+    key_discard(&rc->kf);
+    goto end;
+  }
+  goto done;
+
+end:
+  if (rc) {
+    FREEOBJ(rc);
+    rc = 0;
+  }
+done:
+  return ((PyObject *)rc);
+}
+
+static void keyfile_pydealloc(PyObject *me)
+{
+  key_discard(KEYFILE_KF(me));
+  FREEOBJ(me);
+}
+
+static PyObject *kfmeth_save(PyObject *me)
+{
+  switch (key_save(KEYFILE_KF(me))) {
+    case KWRITE_OK:
+      RETURN_ME;
+    case KWRITE_FAIL:
+      KEYIOERR(KEYFILE_KF(me)->name);
+    case KWRITE_BROKEN:
+      KEYFILEBROKEN(KEYFILE_KF(me)->name);
+    default:
+      abort();
+  }
+end:
+  return (0);
+}
+
+static PyObject *kfmeth_mergeline(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  struct reportinfo ri = { Py_None, 0 };
+  const char *file, *line;
+  int lno, rc;
+  static const char *const kwlist[] = { "name", "lno", "line", "report", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "sis|O:merge", KWLIST,
+                                  &file, &lno, &line, &ri.func))
+    goto end;
+  if (ri.func != Py_None && !PyCallable_Check(ri.func))
+    TYERR("reporter function not callable");
+  rc = key_mergeline(KEYFILE_KF(me), file, lno, line, pythonreporter, &ri);
+  if (ri.stop)
+    goto end;
+  if (rc != 0)
+    KEYERR(rc);
+  RETURN_ME;
+
+end:
+  return (0);
+}
+
+static PyObject *kfmeth_byid(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  uint32 id;
+  key *k;
+  PyObject *failp = Py_True;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "id", "fail", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O:byid", KWLIST,
+                                  convu32, &id, &failp))
+    goto end;
+  if ((k = key_byid(KEYFILE_KF(me), id)) != 0) rc = key_pywrap(me, k);
+  else if (PyObject_IsTrue(failp)) KEYERR(KERR_NOTFOUND);
+  else RETURN_NONE;
+end:
+  return (rc);
+}
+
+static PyObject *kfmeth_bytype(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  char *type;
+  key *k;
+  PyObject *failp = Py_True;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "type", "fail", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|O:bytype", KWLIST,
+                                  &type, &failp))
+    goto end;
+  if ((k = key_bytype(KEYFILE_KF(me), type)) != 0) rc = key_pywrap(me, k);
+  else if (PyObject_IsTrue(failp)) KEYERR(KERR_NOTFOUND);
+  else RETURN_NONE;
+end:
+  return (rc);
+}
+
+static PyObject *kfmeth_bytag(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  PyObject *tagobj;
+  key *k;
+  PyObject *failp = Py_True;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "type", "fail", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:bytag", KWLIST,
+                                  &tagobj, &failp))
+    goto end;
+  if ((k = bytag(me, tagobj)) != 0) rc = key_pywrap(me, k);
+  else if (PyObject_IsTrue(failp)) KEYERR(KERR_NOTFOUND);
+  else RETURN_NONE;
+end:
+  return (rc);
+}
+
+static PyObject *kfmeth_newkey(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  uint32 id;
+  char *type;
+  long exptime = KEXP_FOREVER;
+  static const char *const kwlist[] = { "id", "type", "exptime", 0 };
+  key *k;
+  int err;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&s|l:newkey", KWLIST,
+                                  convu32, &id, &type, &exptime))
+    goto end;
+  if ((err = key_new(KEYFILE_KF(me), id, type, exptime, &k)) != 0)
+    KEYERR(err);
+  return (key_pywrap(me, k));
+end:
+  return (0);
+}
+
+static PyObject *kfmeth_qtag(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  key *k;
+  key_data **kd, *okd;
+  PyObject *newkdobj = 0;
+  char *tag;
+  dstr d = DSTR_INIT;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "tag", "new", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|O!:qtag", KWLIST,
+                                  &tag, keydata_pytype, &newkdobj))
+    goto end;
+  if (key_qtag(KEYFILE_KF(me), tag, &d, &k, &kd))
+    KEYERR(KERR_NOTFOUND);
+  okd = *kd;
+  if (newkdobj) {
+    if (!(KEYFILE_KF(me)->f & KF_WRITE))
+      KEYERR(KERR_READONLY);
+    KEYFILE_KF(me)->f |= KF_MODIFIED;
+    *kd = KEYDATA_KD(newkdobj);
+  }
+  key_incref(*kd);
+  rc = Py_BuildValue("(s#NN)",
+                    d.buf, (Py_ssize_t)d.len,
+                    key_pywrap(me, k),
+                    keydata_pywrap(okd));
+end:
+  return (rc);
+}
+
+static PyObject *kfget_name(PyObject *me, void *hunoz)
+  { return (TEXT_FROMSTR(KEYFILE_KF(me)->name)); }
+static PyObject *kfget_modifiedp(PyObject *me, void *hunoz)
+  { return (getbool(KEYFILE_KF(me)->f & KF_MODIFIED)); }
+static PyObject *kfget_writep(PyObject *me, void *hunoz)
+  { return (getbool(KEYFILE_KF(me)->f & KF_WRITE)); }
+static PyObject *kfget_filep(PyObject *me, void *hunoz)
+  { return (getbool(!!KEYFILE_KF(me)->fp)); }
+
+static const PyMethodDef keyfile_pymethods[] = {
+#define METHNAME(func) kfmeth_##func
+  NAMETH(save,         "KF.save()")
+  KWMETH(mergeline,    "KF.mergeline(NAME, LNO, LINE, "
+                                          "[report = <built-in-reporter>])")
+  KWMETH(newkey,       "KF.newkey(ID, TYPE, [exptime = KEXP_FOREVER]) "
+                                                                   "-> KEY")
+  KWMETH(byid,         "KF.byid(KEYID, [fail = True]) -> KEY|None")
+  KWMETH(bytype,       "KF.bytype(TYPE, [fail = True]) -> KEY|None")
+  KWMETH(bytag,                "KF.bytag(TAG, [fail = True]) -> KEY|None")
+  KWMETH(qtag,         "KF.qtag(TAG, [new = KD]) -> FULLTAG, KEY, OLDKD")
+  GMAP_ROMETHODS
+#undef METHNAME
+  { 0 }
+};
+
+static const PyGetSetDef keyfile_pygetset[] = {
+#define GETSETNAME(op, name) kf##op##_##name
+  GET  (name,          "KF.name -> file name")
+  GET  (modifiedp,     "KF.modifiedp -> has keyring been modified?")
+  GET  (writep,        "KF.writep -> is keyring modifiable?")
+  GET  (filep,         "KF.filep -> does keyring have a backing file?")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMappingMethods keyfile_pymapping = {
+  gmap_pysize,
+  gmap_pylookup,
+  0
+};
+
+static const PyTypeObject keyfile_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KeyFile",                           /* @tp_name@ */
+  sizeof(keyfile_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  keyfile_pydealloc,                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  PYSEQUENCE(gmap),                    /* @tp_as_sequence@ */
+  PYMAPPING(keyfile),                  /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KeyFile(FILE, [how = KOPEN_READ], [report = ?]): Keyring file.\n"
+  "   calls REPORT(FILE, LINE, MSG) on problems",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  gmap_pyiter,                         /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(keyfile),                  /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(keyfile),                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  keyfile_pynew,                       /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Initialization ----------------------------------------------------*/
+
+static const struct nameval consts[] = {
+  CONST(KOPEN_READ), CONST(KOPEN_WRITE), CONST(KOPEN_NOFILE),
+  CONST(KEXP_FOREVER), CONST(KEXP_EXPIRE),
+  CONST(KF_ENCMASK), CONST(KENC_BINARY), CONST(KENC_MP), CONST(KENC_STRUCT),
+    CONST(KENC_ENCRYPT), CONST(KENC_STRING), CONST(KENC_EC),
+  CONST(KF_CATMASK), CONST(KCAT_SYMM), CONST(KCAT_PRIV), CONST(KCAT_PUB),
+    CONST(KCAT_SHARE),
+  CONST(KF_NONSECRET),
+  CONST(KF_BURN), CONST(KF_OPT),
+#define ENTRY(tag, val, str) CONSTFLAG(CF_SIGNED, KERR_##tag),
+  KEY_ERRORS(ENTRY)
+#undef ENTRY
+  { 0 }
+};
+
+void key_pyinit(void)
+{
+  INITTYPE(keyfile, root);
+  INITTYPE(key, root);
+  INITTYPE(keydata, root);
+  INITTYPE(keydatabin, keydata);
+  INITTYPE(keydataenc, keydata);
+  INITTYPE(keydatastr, keydata);
+  INITTYPE(keydatamp, keydata);
+  INITTYPE(keydataec, keydata);
+  INITTYPE(keydatastruct, keydata);
+  INITTYPE(keyattrs, root);
+}
+
+void key_pyinsert(PyObject *mod)
+{
+  INSEXC("KeyError", keyexc, PyExc_Exception, keyexc_pymethods);
+  INSEXC("KeyFileIOError", keyioexc, PyExc_OSError, 0);
+  INSEXC("KeyFileBroken", keyfilebrokenexc, keyioexc, 0);
+  INSERT("KeyFile", keyfile_pytype);
+  INSERT("Key", key_pytype);
+  INSERT("KeyAttributes", keyattrs_pytype);
+  INSERT("KeyData", keydata_pytype);
+  INSERT("KeyDataBinary", keydatabin_pytype);
+  INSERT("KeyDataEncrypted", keydataenc_pytype);
+  INSERT("KeyDataMP", keydatamp_pytype);
+  INSERT("KeyDataECPt", keydataec_pytype);
+  INSERT("KeyDataString", keydatastr_pytype);
+  INSERT("KeyDataStructured", keydatastruct_pytype);
+  setconstants(mod, consts);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/mp.c b/mp.c
new file mode 100644 (file)
index 0000000..3549ccb
--- /dev/null
+++ b/mp.c
@@ -0,0 +1,2650 @@
+/* -*-c-*-
+ *
+ * Multiprecision arithmetic
+ *
+ * (c) 2004 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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "catacomb-python.h"
+
+/*----- General utilities -------------------------------------------------*/
+
+PyTypeObject *mp_pytype = 0;
+PyTypeObject *gf_pytype = 0;
+
+#ifndef PyLong_SHIFT
+#  define PyLong_SHIFT SHIFT
+#endif
+
+#ifndef PyLong_MASK
+#  define PyLong_MASK MASK
+#endif
+
+STATIC_ASSERT(MPW_BITS >= PyLong_SHIFT,
+             "Catacomb's limbs are now narrower than than Python's!");
+
+mp *mp_frompylong(PyObject *obj)
+{
+  unsigned long bits;
+  PyLongObject *l = (PyLongObject *)obj;
+  int sz;
+  size_t w;
+  mpd r = 0;
+  int b = 0;
+  int i;
+  mp *x;
+  mpw *p;
+
+#ifdef PY3
+  int ov;
+  long j = PyLong_AsLongAndOverflow(obj, &ov);
+  if (!ov) return mp_fromlong(MP_NEW, j);
+#endif
+
+  sz = Py_SIZE(l);
+  if (sz < 0) sz = -sz;
+  bits = (unsigned long)sz * PyLong_SHIFT;
+  w = (bits + MPW_BITS - 1)/MPW_BITS;
+  x = mp_new(w, Py_SIZE(l) < 0 ? MP_NEG : 0);
+  p = x->v;
+  for (i = 0; i < sz; i++) {
+    r |= (mpd)l->ob_digit[i] << b;
+    b += PyLong_SHIFT;
+    while (b >= MPW_BITS) {
+      *p++ = MPW(r);
+      r >>= MPW_BITS;
+      b -= MPW_BITS;
+    }
+  }
+  while (r) {
+    *p++ = MPW(r);
+    r >>= MPW_BITS;
+  }
+  x->vl = p;
+  MP_SHRINK(x);
+  return (x);
+}
+
+PyObject *mp_topylong(mp *x)
+{
+  unsigned long bits = mp_bits(x);
+  int sz = (bits + PyLong_SHIFT - 1)/PyLong_SHIFT;
+  PyLongObject *l = _PyLong_New(sz);
+  mpd r = 0;
+  int b = 0;
+  mpw *p = x->v;
+  int i = 0;
+
+  while (i < sz && p < x->vl) {
+    r |= (mpd)*p++ << b;
+    b += MPW_BITS;
+    while (i < sz && b >= PyLong_SHIFT) {
+      l->ob_digit[i++] = r & PyLong_MASK;
+      r >>= PyLong_SHIFT;
+      b -= PyLong_SHIFT;
+    }
+  }
+  while (i < sz && r) {
+    l->ob_digit[i++] = r & PyLong_MASK;
+    r >>= PyLong_SHIFT;
+  }
+  Py_SIZE(l) = (x->f & MP_NEG) ? -sz : sz;
+  return ((PyObject *)l);
+}
+
+mp *mp_frompyobject(PyObject *o, int radix)
+{
+  mp *x;
+
+  if (TEXT_CHECK(o)) {
+    mptext_stringctx sc;
+    mp *x;
+    size_t sz;
+    TEXT_PTRLEN(o, sc.buf, sz); sc.lim = sc.buf + sz;
+    if (sc.buf + 2 < sc.lim && sc.buf[0] == '0' &&
+       (radix == 16 ? (sc.buf[1] == 'x' || sc.buf[1] == 'X') :
+        radix ==  8 ? (sc.buf[1] == 'o' || sc.buf[1] == 'O') :
+        radix ==  2 ? (sc.buf[1] == 'b' || sc.buf[1] == 'B') :
+        0))
+      sc.buf += 2;
+    x = mp_read(MP_NEW, radix, &mptext_stringops, &sc);
+    if (!x) return (0);
+    if (sc.buf < sc.lim) { MP_DROP(x); return (0); }
+    return (x);
+  }
+  if ((x = tomp(o)) != 0)
+    return (x);
+  return (0);
+}
+
+PyObject *mp_topystring(mp *x, int radix, const char *xpre,
+                       const char *pre, const char *post)
+{
+  int len = mptext_len(x, radix) + 1;
+  mptext_stringctx sc;
+  PyObject *o;
+  size_t xprelen = xpre ? strlen(xpre) : 0;
+  size_t prelen = pre ? strlen(pre) : 0;
+  size_t postlen = post ? strlen(post) : 0;
+  char *p;
+  MP_COPY(x);
+  TEXT_PREPAREWRITE(o, p, len + 1 + xprelen + prelen + postlen);
+  sc.buf = p;
+  if (xpre) { memcpy(sc.buf, xpre, xprelen); sc.buf += xprelen; }
+  if (MP_NEGP(x)) { *sc.buf++ = '-'; x = mp_neg(x, x); }
+  if (pre) { memcpy(sc.buf, pre, prelen); sc.buf += prelen; }
+  sc.lim = sc.buf + len;
+  mp_write(x, radix, &mptext_stringops, &sc);
+  if (post) { memcpy(sc.buf, post, postlen); sc.buf += postlen; }
+  MP_DROP(x);
+  TEXT_DONEWRITE(o, sc.buf - p);
+  return (o);
+}
+
+static int good_radix_p(int r, int readp)
+{
+  return ((r >= -255 && r <= -2) ||
+         (readp && r == 0) ||
+         (r >= 2 && r <= 62));
+}
+
+PyObject *mp_pywrap(mp *x)
+{
+  mp_pyobj *z = PyObject_New(mp_pyobj, mp_pytype);
+  z->x = x;
+  return ((PyObject *)z);
+}
+
+PyObject *gf_pywrap(mp *x)
+{
+  mp_pyobj *z = PyObject_New(mp_pyobj, gf_pytype);
+  z->x = x;
+  return ((PyObject *)z);
+}
+
+int mp_tolong_checked(mp *x, long *l, int must)
+{
+  static mp *longmin = 0, *longmax = 0;
+  int rc = -1;
+
+  if (!longmax) {
+    longmin = mp_fromlong(MP_NEW, LONG_MIN);
+    longmax = mp_fromlong(MP_NEW, LONG_MAX);
+  }
+  if (MP_CMP(x, <, longmin) || MP_CMP(x, >, longmax)) {
+    if (must) VALERR("mp out of range for int");
+    else goto end;
+  }
+  *l = mp_tolong(x);
+  rc = 0;
+end:
+  return (rc);
+}
+
+/*----- Arbitrary-precision integers --------------------------------------*/
+
+static void mp_pydealloc(PyObject *o)
+{
+  MP_DROP(MP_X(o));
+  FREEOBJ(o);
+}
+
+static PyObject *mp_pyrepr(PyObject *o)
+  { return mp_topystring(MP_X(o), 10, "MP(", 0, ")"); }
+
+static PyObject *mp_pystr(PyObject *o)
+  { return mp_topystring(MP_X(o), 10, 0, 0, 0); }
+
+mp *tomp(PyObject *o)
+{
+  PyObject *l;
+  mp *x;
+
+  if (!o || PyFloat_Check(o))
+    return (0);
+  else if (MP_PYCHECK(o) || GF_PYCHECK(o))
+    return (MP_COPY(MP_X(o)));
+  else if (FE_PYCHECK(o))
+    return (F_OUT(FE_F(o), MP_NEW, FE_X(o)));
+  else if (PFILT_PYCHECK(o))
+    return (MP_COPY(PFILT_F(o)->m));
+  else if (ECPT_PYCHECK(o)) {
+    ec p = EC_INIT;
+    if (EC_ATINF(ECPT_P(o))) return (0);
+    getecptout(&p, o);
+    x = MP_COPY(p.x);
+    EC_DESTROY(&p);
+    return (x);
+  } else if (GE_PYCHECK(o)) {
+    if ((x = G_TOINT(GE_G(o), MP_NEW, GE_X(o))) == 0)
+      return (0);
+    return (x);
+  }
+#ifdef PY2
+  else if (PyInt_Check(o))
+    return (mp_fromlong(MP_NEW, PyInt_AS_LONG(o)));
+#endif
+  else if ((l = PyNumber_Long(o)) != 0) {
+    x = mp_frompylong(l);
+    Py_DECREF(l);
+    return (x);
+  } else {
+    PyErr_Clear();
+    return (0);
+  }
+}
+
+mp *getmp(PyObject *o)
+{
+  mp *x = 0;
+  if (!o) return (0);
+  if ((x = tomp(o)) == 0) {
+    PyErr_Format(PyExc_TypeError, "can't convert %.100s to mp",
+                Py_TYPE(o)->tp_name);
+  }
+  return (x);
+}
+
+int convmp(PyObject *o, void *p)
+{
+  mp *x;
+  if ((x = getmp(o)) == 0) return (0);
+  *(mp **)p = x;
+  return (1);
+}
+
+mp *getgf(PyObject *o)
+{
+  mp *x = 0;
+  if (!o) return (0);
+  if ((x = tomp(o)) == 0) {
+    PyErr_Format(PyExc_TypeError, "can't convert %.100s to gf",
+                Py_TYPE(o)->tp_name);
+  }
+  return (x);
+}
+
+int convgf(PyObject *o, void *p)
+{
+  mp *x;
+  if ((x = getgf(o)) == 0) return (0);
+  *(mp **)p = x;
+  return (1);
+}
+
+mp *implicitmp(PyObject *o)
+{
+  PyObject *l;
+
+  if (!o || GF_PYCHECK(o) || FE_PYCHECK(o)) return (0);
+  else if (MP_PYCHECK(o)) return (MP_COPY(MP_X(o)));
+  else if (PFILT_PYCHECK(o)) return (MP_COPY(PFILT_F(o)->m));
+#ifdef PY2
+  else if (PyInt_Check(o)) return (mp_fromlong(MP_NEW, PyInt_AS_LONG(o)));
+#endif
+  else if ((l = PyNumber_Index(o)) != 0) {
+#ifdef PY2
+    if (PyInt_Check(o)) return (mp_fromlong(MP_NEW, PyInt_AS_LONG(o)));
+#endif
+    if (PyLong_Check(o)) return (mp_frompylong(o));
+  }
+  PyErr_Clear(); return (0);
+}
+
+mp *implicitgf(PyObject *o)
+{
+  if (GF_PYCHECK(o)) return (MP_COPY(MP_X(o)));
+  return (0);
+}
+
+static int mpbinop(PyObject *x, PyObject *y, mp **xx, mp **yy)
+{
+  if ((*xx = implicitmp(x)) == 0)
+    return (-1);
+  if ((*yy = implicitmp(y)) == 0) {
+    MP_DROP(*xx);
+    return (-1);
+  }
+  return (0);
+}
+
+static int gfbinop(PyObject *x, PyObject *y, mp **xx, mp **yy)
+{
+  if ((*xx = implicitgf(x)) == 0)
+    return (-1);
+  if ((*yy = implicitgf(y)) == 0) {
+    MP_DROP(*xx);
+    return (-1);
+  }
+  return (0);
+}
+
+#define FPBINOP(name, pyop)                                            \
+  static PyObject *mp_py##name(PyObject *x, PyObject *y) {             \
+    mp *xx, *yy, *zz;                                                  \
+    PyObject *l, *rc;                                                  \
+    if (PyFloat_Check(x)) {                                            \
+      l = mp_topylong(MP_X(y)); rc = PyNumber_##pyop(x, l);            \
+      Py_DECREF(l); return (rc);                                       \
+    } else if (PyFloat_Check(y)) {                                     \
+      l = mp_topylong(MP_X(x)); rc = PyNumber_##pyop(l, y);            \
+      Py_DECREF(l); return (rc);                                       \
+    }                                                                  \
+    if (mpbinop(x, y, &xx, &yy)) RETURN_NOTIMPL;                       \
+    zz = mp_##name(MP_NEW, xx, yy);                                    \
+    MP_DROP(xx); MP_DROP(yy);                                          \
+    return (mp_pywrap(zz));                                            \
+  }
+FPBINOP(add, Add)
+FPBINOP(sub, Subtract)
+FPBINOP(mul, Multiply)
+
+#define gf_and mp_and
+#define gf_or mp_or
+#define gf_xor mp_xor
+#define BINOP(pre, name)                                               \
+  static PyObject *pre##_py##name(PyObject *x, PyObject *y) {          \
+    mp *xx, *yy, *zz;                                                  \
+    if (pre##binop(x, y, &xx, &yy)) RETURN_NOTIMPL;                    \
+    zz = pre##_##name(MP_NEW, xx, yy);                                 \
+    MP_DROP(xx); MP_DROP(yy);                                          \
+    return (pre##_pywrap(zz));                                         \
+  }
+BINOP(mp, and2c)
+BINOP(mp, or2c)
+BINOP(mp, xor2c)
+BINOP(gf, add)
+BINOP(gf, sub)
+BINOP(gf, mul)
+BINOP(gf, and)
+BINOP(gf, or)
+BINOP(gf, xor)
+#undef BINOP
+
+static mp *mp_abs(mp *d, mp *x)
+{
+  if (MP_NEGP(x))
+    return (mp_neg(d, x));
+  MP_COPY(x);
+  if (d) MP_DROP(d);
+  return (x);
+}
+
+#define UNOP(pre, name)                                                        \
+  static PyObject *pre##_py##name(PyObject *x)                         \
+    { return mp_pywrap(pre##_##name(MP_NEW, MP_X(x))); }
+UNOP(mp, neg)
+UNOP(mp, abs)
+UNOP(mp, not2c)
+#undef UNOP
+
+static PyObject *mp_pyid(PyObject *x) { RETURN_OBJ(x); }
+
+#define gf_lsr mp_lsr
+#define SHIFTOP(pre, PRE, name, rname)                                 \
+  static PyObject *pre##_py##name(PyObject *x, PyObject *y) {          \
+    PyObject *yix = 0;                                                 \
+    PyObject *z = 0;                                                   \
+    long n;                                                            \
+    if (!PRE##_PYCHECK(x)) RETURN_NOTIMPL;                             \
+    if (GF_PYCHECK(y) || FE_PYCHECK(y)) RETURN_NOTIMPL;                        \
+    yix = PyNumber_Index(y); if (!yix) { PyErr_Clear(); RETURN_NOTIMPL; } \
+    n = PyInt_AsLong(yix); Py_DECREF(yix);                             \
+    if (n == -1 && PyErr_Occurred())  { PyErr_Clear(); RETURN_NOTIMPL; } \
+    if (n < 0) z = pre##_pywrap(mp_##rname(MP_NEW, MP_X(x), -n));      \
+    else z = pre##_pywrap(mp_##name(MP_NEW, MP_X(x), n));              \
+    return (z);                                                                \
+  }
+SHIFTOP(mp, MP, lsl2c, lsr2c)
+SHIFTOP(mp, MP, lsr2c, lsl2c)
+SHIFTOP(gf, GF, lsl, lsr)
+SHIFTOP(gf, GF, lsr, lsl)
+#undef SHIFTOP
+
+#define DIVOP(pre, name, qq, rr, gather)                               \
+  static PyObject *pre##_py##name(PyObject *x, PyObject *y) {          \
+    mp *xx, *yy;                                                       \
+    PyObject *z = 0;                                                   \
+    INIT_##qq(q) INIT_##rr(r)                                          \
+    if (pre##binop(x, y, &xx, &yy)) RETURN_NOTIMPL;                    \
+    if (MP_ZEROP(yy))                                                  \
+      ZDIVERR("division by zero");                                     \
+    pre##_div(ARG_##qq(q), ARG_##rr(r), xx, yy);                       \
+    z = gather;                                                                \
+  end:                                                                 \
+    MP_DROP(xx); MP_DROP(yy);                                          \
+    return (z);                                                                \
+  }
+#define INIT_YES(p) mp *p = MP_NEW;
+#define INIT_NO(p)
+#define ARG_YES(p) &p
+#define ARG_NO(p) 0
+DIVOP(mp, divmod, YES, YES,
+      Py_BuildValue("(NN)", mp_pywrap(q), mp_pywrap(r)))
+DIVOP(mp, div, YES, NO, mp_pywrap(q))
+DIVOP(mp, mod, NO, YES, mp_pywrap(r))
+DIVOP(gf, divmod, YES, YES,
+      Py_BuildValue("(NN)", gf_pywrap(q), gf_pywrap(r)))
+DIVOP(gf, div, YES, NO, gf_pywrap(q))
+DIVOP(gf, mod, NO, YES, gf_pywrap(r))
+#undef INIT_YES
+#undef INIT_NO
+#undef ARG_YES
+#undef ARG_NO
+#undef DIVOP
+
+static mp *mp_modinv_checked(mp *d, mp *x, mp *p)
+{
+  mp *g = MP_NEW;
+  mp_gcd(&g, 0, &d, p, x);
+  if (!MP_EQ(g, MP_ONE)) {
+    MP_DROP(g); MP_DROP(d);
+    PyErr_SetString(PyExc_ZeroDivisionError, "no modular inverse");
+    return (0);
+  }
+  MP_DROP(g); return (d);
+}
+
+static PyObject *mp_pyexp(PyObject *x, PyObject *y, PyObject *z)
+{
+  mp *xx = 0, *yy = 0, *zz = 0;
+  mp *r = 0;
+  PyObject *rc = 0;
+
+  if ((xx = implicitmp(x)) == 0 || (yy = implicitmp(y)) == 0 ||
+      (z && z != Py_None && (zz = implicitmp(z)) == 0)) {
+    mp_drop(xx); mp_drop(yy); mp_drop(zz);
+    RETURN_NOTIMPL;
+  }
+  if (!z || z == Py_None) {
+    if (MP_NEGP(yy)) VALERR("negative exponent");
+    r = mp_exp(MP_NEW, xx, yy);
+  } else {
+    if (!MP_POSP(zz)) VALERR("modulus must be positive");
+    if (MP_NEGP(yy)) {
+      if ((xx = mp_modinv_checked(xx, xx, zz)) == 0) goto end;
+      yy = mp_neg(yy, yy);
+    }
+    if (MP_ODDP(zz)) {
+      mpmont mm;
+      mpmont_create(&mm, zz);
+      r = mpmont_exp(&mm, MP_NEW, xx, yy);
+      mpmont_destroy(&mm);
+    } else {
+      mpbarrett mb;
+      mpbarrett_create(&mb, zz);
+      r = mpbarrett_exp(&mb, MP_NEW, xx, yy);
+      mpbarrett_destroy(&mb);
+    }
+  }
+  rc = mp_pywrap(r);
+end:
+  mp_drop(xx); mp_drop(yy); mp_drop(zz);
+  return (rc);
+}
+
+static mp *gf_modinv_checked(mp *d, mp *x, mp *p)
+{
+  mp *g = MP_NEW;
+  gf_gcd(&g, 0, &d, p, x);
+  if (!MP_EQ(g, MP_ONE)) {
+    MP_DROP(g); MP_DROP(d);
+    PyErr_SetString(PyExc_ZeroDivisionError, "no modular inverse");
+    return (0);
+  }
+  MP_DROP(g); return (d);
+}
+
+#define BASEOP(name, radix, pre)                                       \
+  static PyObject *mp_py##name(PyObject *x)                            \
+    { return mp_topystring(MP_X(x), radix, 0, pre, 0); }
+#ifdef PY2
+BASEOP(oct, 8, "0");
+#endif
+BASEOP(hex, 16, "0x");
+#undef BASEOP
+
+static int mp_pynonzerop(PyObject *x) { return !MP_ZEROP(MP_X(x)); }
+
+static PyObject *mp_pyint(PyObject *x)
+{
+  long l;
+  if (!mp_tolong_checked(MP_X(x), &l, 0)) return (PyInt_FromLong(l));
+  else return mp_topylong(MP_X(x));
+}
+#ifdef PY2
+static PyObject *mp_pylong(PyObject *x)
+  { return (mp_topylong(MP_X(x))); }
+#endif
+static PyObject *mp_pyfloat(PyObject *x)
+{
+  PyObject *l = mp_topylong(MP_X(x));
+  double f = PyLong_AsDouble(l);
+  Py_DECREF(l);
+  return (PyFloat_FromDouble(f));
+}
+
+#ifdef PY2
+#define COERCE(pre, PRE)                                               \
+  static int pre##_pycoerce(PyObject **x, PyObject **y)                        \
+  {                                                                    \
+    mp *z;                                                             \
+                                                                       \
+    if (PRE##_PYCHECK(*y)) {                                           \
+      Py_INCREF(*x); Py_INCREF(*y);                                    \
+      return (0);                                                      \
+    }                                                                  \
+    if ((z = implicit##pre(*y)) != 0) {                                        \
+      Py_INCREF(*x);                                                   \
+      *y = pre##_pywrap(z);                                            \
+      return (0);                                                      \
+    }                                                                  \
+    return (1);                                                                \
+  }
+COERCE(mp, MP)
+COERCE(gf, GF)
+#undef COERCE
+#endif
+
+static PyObject *mp_pyrichcompare(PyObject *x, PyObject *y, int op)
+{
+  mp *xx, *yy;
+  PyObject *l, *rc;
+  if (PyFloat_Check(y)) {
+    l = mp_topylong(MP_X(x)); rc = PyObject_RichCompare(l, y, op);
+    Py_DECREF(l); return (rc);
+  }
+  if (mpbinop(x, y, &xx, &yy)) RETURN_NOTIMPL;
+  rc = enrich_compare(op, mp_cmp(xx, yy));
+  MP_DROP(xx); MP_DROP(yy);
+  return (rc);
+}
+
+#ifdef PY2
+static int mp_pycompare(PyObject *x, PyObject *y)
+  { return mp_cmp(MP_X(x), MP_X(y)); }
+#endif
+
+static PyObject *mp_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  PyObject *x;
+  mp *z;
+  mp_pyobj *zz = 0;
+  int radix = 0;
+  static const char *const kwlist[] = { "x", "radix", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|i:new", KWLIST, &x, &radix))
+    goto end;
+  if (MP_PYCHECK(x)) RETURN_OBJ(x);
+  if (!good_radix_p(radix, 1)) VALERR("bad radix");
+  if ((z = mp_frompyobject(x, radix)) == 0) {
+    PyErr_Format(PyExc_TypeError, "can't convert %.100s to mp",
+                Py_TYPE(x)->tp_name);
+    goto end;
+  }
+  zz = (mp_pyobj *)ty->tp_alloc(ty, 0);
+  zz->x = z;
+end:
+  return ((PyObject *)zz);
+}
+
+#define IMPLICIT(pre)                                                  \
+  static PyObject *pre##meth__implicit(PyObject *me, PyObject *arg)    \
+  {                                                                    \
+    PyObject *x, *rc = 0;                                              \
+    mp *y = MP_NEW;                                                    \
+    if (!PyArg_ParseTuple(arg, "O:_implicit", &x)) goto end;           \
+    y = implicit##pre(x);                                              \
+    if (!y) TYERR("can't convert implicitly to " #pre);                        \
+    rc = pre##_pywrap(y);                                              \
+  end:                                                                 \
+    return (rc);                                                       \
+  }
+IMPLICIT(mp)
+IMPLICIT(gf)
+#undef IMPLICIT
+
+Py_hash_t mphash(mp *x)
+{
+  PyObject *l = mp_topylong(x);
+  Py_hash_t h = PyObject_Hash(l);
+  Py_DECREF(l); return (h);
+}
+
+static Py_hash_t mp_pyhash(PyObject *me) { return (mphash(MP_X(me))); }
+
+static PyObject *mpmeth_jacobi(PyObject *me, PyObject *arg)
+{
+  mp *y = 0;
+  PyObject *z = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:jacobi", convmp, &y)) goto end;
+  z = PyInt_FromLong(mp_jacobi(y, MP_X(me)));
+end:
+  if (y) MP_DROP(y);
+  return (z);
+}
+
+#define BITOP(pre, name, c)                                            \
+  static PyObject *pre##meth_##name(PyObject *me, PyObject *arg)       \
+  {                                                                    \
+    unsigned long i;                                                   \
+    if (!PyArg_ParseTuple(arg, "O&:" #name, convulong, &i)) return (0);        \
+    return (pre##_pywrap(mp_##name##c(MP_NEW, MP_X(me), i)));          \
+  }
+BITOP(mp, setbit, 2c);
+BITOP(mp, clearbit, 2c);
+BITOP(gf, setbit, );
+BITOP(gf, clearbit, );
+#undef BITOP
+
+static PyObject *mpmeth_testbit(PyObject *me, PyObject *arg)
+{
+  unsigned long i;
+  if (!PyArg_ParseTuple(arg, "O&:testbit", convulong, &i)) return (0);
+  return (getbool(mp_testbit2c(MP_X(me), i)));
+}
+
+static PyObject *gfmeth_testbit(PyObject *me, PyObject *arg)
+{
+  unsigned long i;
+  if (!PyArg_ParseTuple(arg, "O&:testbit", convulong, &i)) return (0);
+  return (getbool(mp_testbit(MP_X(me), i)));
+}
+
+static PyObject *mpmeth_odd(PyObject *me)
+{
+  mp *t;
+  size_t s;
+
+  t = mp_odd(MP_NEW, MP_X(me), &s);
+  return (Py_BuildValue("(lN)", (long)s, mp_pywrap(t)));
+}
+
+static PyObject *mpmeth_sqr(PyObject *me)
+  { return (mp_pywrap(mp_sqr(MP_NEW, MP_X(me)))); }
+
+static PyObject *mpmeth_sqrt(PyObject *me)
+{
+  if (MP_NEGP(MP_X(me))) VALERR("negative root");
+  return (mp_pywrap(mp_sqrt(MP_NEW, MP_X(me))));
+end:
+  return (0);
+}
+
+static PyObject *mpmeth_gcd(PyObject *me, PyObject *arg)
+{
+  mp *y = 0, *zz = MP_NEW;
+  PyObject *z = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:gcd", convmp, &y)) goto end;
+  mp_gcd(&zz, 0, 0, MP_X(me), y);
+  z = mp_pywrap(zz);
+end:
+  if (y) MP_DROP(y);
+  return (z);
+}
+
+static PyObject *mpmeth_gcdx(PyObject *me, PyObject *arg)
+{
+  PyObject *z = 0;
+  mp *yy = 0, *zz = MP_NEW, *uu = MP_NEW, *vv = MP_NEW;
+
+  if (!PyArg_ParseTuple(arg, "O&:gcdx", convmp, &yy)) goto end;
+  mp_gcd(&zz, &uu, &vv, MP_X(me), yy);
+  z = Py_BuildValue("(NNN)",
+                   mp_pywrap(zz), mp_pywrap(uu), mp_pywrap(vv));
+end:
+  if (yy) MP_DROP(yy);
+  return (z);
+}
+
+static PyObject *mpmeth_modinv(PyObject *me, PyObject *arg)
+{
+  PyObject *z = 0;
+  mp *yy = 0, *zz = MP_NEW;
+
+  if (!PyArg_ParseTuple(arg, "O&:modinv", convmp, &yy) ||
+      (zz = mp_modinv_checked(MP_NEW, yy, MP_X(me))) == 0)
+    goto end;
+  z = mp_pywrap(zz);
+end:
+  if (yy) MP_DROP(yy);
+  return (z);
+}
+
+static PyObject *mpmeth_tostring(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  int radix = 10;
+  static const char *const kwlist[] = { "radix", 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|i:tostring", KWLIST, &radix))
+    goto end;
+  if (!good_radix_p(radix, 0)) VALERR("bad radix");
+  return (mp_topystring(MP_X(me), radix, 0, 0, 0));
+end:
+  return (0);
+}
+
+static PyObject *mpmeth_modsqrt(PyObject *me, PyObject *arg)
+{
+  PyObject *z = 0;
+  mp *yy = 0, *zz = MP_NEW;
+
+  if (!PyArg_ParseTuple(arg, "O&:modsqrt", convmp, &yy)) goto end;
+  if ((zz = mp_modsqrt(MP_NEW, yy, MP_X(me))) == 0)
+    VALERR("no modular square root");
+  z = mp_pywrap(zz);
+end:
+  if (yy) MP_DROP(yy);
+  return (z);
+}
+
+static PyObject *mpmeth_leastcongruent(PyObject *me, PyObject *arg)
+{
+  mp *z, *b, *m;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&O&:leastcongruent", convmp, &b, convmp, &m))
+    goto end;
+  z = mp_leastcongruent(MP_NEW, b, MP_X(me), m);
+  rc = mp_pywrap(z);
+end:
+  return (rc);
+}
+
+#define STOREOP(name, c)                                               \
+  static PyObject *mpmeth_##name(PyObject *me,                         \
+                                PyObject *arg, PyObject *kw)           \
+  {                                                                    \
+    long len = -1;                                                     \
+    static const char *const kwlist[] = { "len", 0 };                  \
+    PyObject *rc = 0;                                                  \
+                                                                       \
+    if (!PyArg_ParseTupleAndKeywords(arg, kw, "|l:" #name,             \
+                                   KWLIST, &len))                      \
+      goto end;                                                                \
+    if (len < 0) {                                                     \
+      len = mp_octets##c(MP_X(me));                                    \
+      if (!len) len = 1;                                               \
+    }                                                                  \
+    rc = bytestring_pywrap(0, len);                                    \
+    mp_##name(MP_X(me), BIN_PTR(rc), len);                             \
+  end:                                                                 \
+    return (rc);                                                       \
+  }
+STOREOP(storel, )
+STOREOP(storeb, )
+STOREOP(storel2c, 2c)
+STOREOP(storeb2c, 2c)
+#undef STOREOP
+
+#define BUFOP(ty)                                                      \
+  static PyObject *ty##meth_frombuf(PyObject *me, PyObject *arg)       \
+  {                                                                    \
+    buf b;                                                             \
+    struct bin in;                                                     \
+    PyObject *rc = 0;                                                  \
+    mp *x;                                                             \
+                                                                       \
+    if (!PyArg_ParseTuple(arg, "O&:frombuf", convbin, &in)) goto end;  \
+    buf_init(&b, (/*unconst*/ void *)in.p, in.sz);                     \
+    if ((x = buf_getmp(&b)) == 0) VALERR("malformed data");            \
+    rc = Py_BuildValue("(NN)", ty##_pywrap(x),                         \
+                      bytestring_pywrapbuf(&b));                       \
+  end:                                                                 \
+    return (rc);                                                       \
+  }
+BUFOP(mp)
+BUFOP(gf)
+#undef BUFOP
+
+static PyObject *mpmeth_tobuf(PyObject *me)
+{
+  buf b;
+  PyObject *rc;
+  mp *x;
+  size_t n;
+
+  x = MP_X(me);
+  n = mp_octets(x) + 3;
+  rc = bytestring_pywrap(0, n);
+  buf_init(&b, BIN_PTR(rc), n);
+  buf_putmp(&b, x);
+  assert(BOK(&b));
+  BIN_SETLEN(rc, BLEN(&b));
+  return (rc);
+}
+
+static PyObject *mpmeth_primep(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  grand *r = &rand_global;
+  static const char *const kwlist[] = { "rng", 0 };
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&", KWLIST, convgrand, &r))
+    goto end;
+  rc = getbool(pgen_primep(MP_X(me), r));
+end:
+  return (rc);
+}
+
+static PyObject *mpmeth_fromstring(PyObject *me,
+                                  PyObject *arg, PyObject *kw)
+{
+  int r = 0;
+  char *p;
+  Py_ssize_t len;
+  PyObject *z = 0;
+  mp *zz;
+  mptext_stringctx sc;
+  static const char *const kwlist[] = { "x", "radix", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|i:fromstring", KWLIST,
+                                  &p, &len, &r))
+    goto end;
+  if (!good_radix_p(r, 1)) VALERR("bad radix");
+  sc.buf = p; sc.lim = p + len;
+  if ((zz = mp_read(MP_NEW, r, &mptext_stringops, &sc)) == 0)
+    VALERR("bad integer");
+  z = Py_BuildValue("(Ns#)", mp_pywrap(zz),
+                   sc.buf, (Py_ssize_t)(sc.lim - sc.buf));
+end:
+  return (z);
+}
+
+static PyObject *mpmeth_factorial(PyObject *me, PyObject *arg)
+{
+  unsigned long i;
+  mp *x;
+  if (!PyArg_ParseTuple(arg, "O&:factorial", convulong, &i)) return (0);
+  x = mp_factorial(i);
+  return mp_pywrap(x);
+}
+
+static PyObject *mpmeth_fibonacci(PyObject *me, PyObject *arg)
+{
+  long i;
+  mp *x;
+  if (!PyArg_ParseTuple(arg, "l:fibonacci", &i)) return (0);
+  x = mp_fibonacci(i);
+  return mp_pywrap(x);
+}
+
+#define LOADOP(pre, name)                                              \
+  static PyObject *pre##meth_##name(PyObject *me, PyObject *arg)       \
+  {                                                                    \
+    struct bin in;                                                     \
+    if (!PyArg_ParseTuple(arg, "O&:" #name, convbin, &in)) return (0); \
+    return (pre##_pywrap(mp_##name(MP_NEW, in.p, in.sz)));             \
+  }
+LOADOP(mp, loadl)
+LOADOP(mp, loadb)
+LOADOP(mp, loadl2c)
+LOADOP(mp, loadb2c)
+LOADOP(gf, loadl)
+LOADOP(gf, loadb)
+#undef LOADOP
+
+static PyObject *mpget_nbits(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(mp_bits(MP_X(me)))); }
+
+static PyObject *mpget_noctets(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(mp_octets(MP_X(me)))); }
+
+static PyObject *mpget_noctets2c(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(mp_octets2c(MP_X(me)))); }
+
+static const PyGetSetDef mp_pygetset[] = {
+#define GETSETNAME(op, func) mp##op##_##func
+  GET  (nbits,         "X.nbits -> bit length of X")
+  GET  (noctets,       "X.noctets -> octet length of X")
+  GET  (noctets2c,     "X.noctets2c -> two's complement octet length of X")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef mp_pymethods[] = {
+#define METHNAME(func) mpmeth_##func
+  METH (jacobi,        "X.jacobi(Y) -> Jacobi symbol (Y|X) (NB inversion!)")
+  METH (setbit,        "X.setbit(N) -> X with bit N set")
+  METH (clearbit,      "X.clearbit(N) -> X with bit N clear")
+  METH (testbit,       "X.testbit(N) -> true/false if bit N set/clear in X")
+  NAMETH(odd,          "X.odd() -> S, T where X = 2^S T with T odd")
+  NAMETH(sqr,          "X.sqr() -> X^2")
+  NAMETH(sqrt,         "X.sqrt() -> largest integer <= sqrt(X)")
+  METH (gcd,           "X.gcd(Y) -> gcd(X, Y)")
+  METH (gcdx,          "X.gcdx(Y) -> (gcd(X, Y), U, V) "
+                                               "with X U + Y V = gcd(X, Y)")
+  METH (modinv,        "X.modinv(Y) -> multiplicative inverse of Y mod X")
+  METH (modsqrt,       "X.modsqrt(Y) -> square root of Y mod X, if X prime")
+  METH (leastcongruent, "X.leastcongruent(B, M) -> "
+                                      "smallest Z >= B with Z == X (mod M)")
+  KWMETH(primep,       "X.primep([rng = rand]) -> X is prime?")
+  KWMETH(tostring,     "X.tostring([radix = 10]) -> STR")
+  KWMETH(storel,       "X.storel([len = -1]) -> little-endian bytes")
+  KWMETH(storeb,       "X.storeb([len = -1]) -> big-endian bytes")
+  KWMETH(storel2c,     "X.storel2c([len = -1]) -> "
+                                    "little-endian bytes, two's complement")
+  KWMETH(storeb2c,     "X.storeb2c([len = -1]) -> "
+                                       "big-endian bytes, two's complement")
+  NAMETH(tobuf,                "X.tobuf() -> buffer format")
+  KWSMTH(fromstring,   "fromstring(STR, [radix = 0]) -> (X, REST)\n"
+    "  Parse STR as a large integer, according to RADIX.  If RADIX is\n"
+    "  zero, read a prefix from STR to decide radix: allow `0b' for binary,\n"
+    "  `0' or `0o' for octal, `0x' for hex, or `R_' for other radix R.")
+  SMTH (_implicit,     0)
+  SMTH (factorial,     "factorial(I) -> I!: compute factorial")
+  SMTH (fibonacci,     "fibonacci(I) -> F(I): compute Fibonacci number")
+  SMTH (loadl,         "loadl(STR) -> X: read little-endian bytes")
+  SMTH (loadb,         "loadb(STR) -> X: read big-endian bytes")
+  SMTH (loadl2c,       "loadl2c(STR) -> X: "
+                               "read little-endian bytes, two's complement")
+  SMTH (loadb2c,       "loadb2c(STR) -> X: "
+                                  "read big-endian bytes, two's complement")
+  SMTH (frombuf,       "frombuf(STR) -> (X, REST): read buffer format")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyNumberMethods mp_pynumber = {
+  mp_pyadd,                            /* @nb_add@ */
+  mp_pysub,                            /* @nb_subtract@ */
+  mp_pymul,                            /* @nb_multiply@ */
+#ifdef PY2
+  0,                                   /* @nb_divide@ */
+#endif
+  mp_pymod,                            /* @nb_remainder@ */
+  mp_pydivmod,                         /* @nb_divmod@ */
+  mp_pyexp,                            /* @nb_power@ */
+  mp_pyneg,                            /* @nb_negative@ */
+  mp_pyid,                             /* @nb_positive@ */
+  mp_pyabs,                            /* @nb_absolute@ */
+  mp_pynonzerop,                       /* @nb_nonzero@ */
+  mp_pynot2c,                          /* @nb_invert@ */
+  mp_pylsl2c,                          /* @nb_lshift@ */
+  mp_pylsr2c,                          /* @nb_rshift@ */
+  mp_pyand2c,                          /* @nb_and@ */
+  mp_pyxor2c,                          /* @nb_xor@ */
+  mp_pyor2c,                           /* @nb_or@ */
+#ifdef PY2
+  mp_pycoerce,                         /* @nb_coerce@ */
+#endif
+  mp_pyint,                            /* @nb_int@ */
+  PY23(mp_pylong, 0),                  /* @nb_long@ */
+  mp_pyfloat,                          /* @nb_float@ */
+#ifdef PY2
+  mp_pyoct,                            /* @nb_oct@ */
+  mp_pyhex,                            /* @nb_hex@ */
+#endif
+
+  0,                                   /* @nb_inplace_add@ */
+  0,                                   /* @nb_inplace_subtract@ */
+  0,                                   /* @nb_inplace_multiply@ */
+#ifdef PY2
+  0,                                   /* @nb_inplace_divide@ */
+#endif
+  0,                                   /* @nb_inplace_remainder@ */
+  0,                                   /* @nb_inplace_power@ */
+  0,                                   /* @nb_inplace_lshift@ */
+  0,                                   /* @nb_inplace_rshift@ */
+  0,                                   /* @nb_inplace_and@ */
+  0,                                   /* @nb_inplace_xor@ */
+  0,                                   /* @nb_inplace_or@ */
+
+  mp_pydiv,                            /* @nb_floor_divide@ */
+  0,                                   /* @nb_true_divide@ */
+  0,                                   /* @nb_inplace_floor_divide@ */
+  0,                                   /* @nb_inplace_true_divide@ */
+
+  mp_pyint,                            /* @nb_index@ */
+};
+
+static const PyTypeObject mp_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "MP",                                        /* @tp_name@ */
+  sizeof(mp_pyobj),                    /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  mp_pydealloc,                                /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  PY23(mp_pycompare, 0),               /* @tp_compare@/@tp_as_async@ */
+  mp_pyrepr,                           /* @tp_repr@ */
+  PYNUMBER(mp),                                /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  mp_pyhash,                           /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  mp_pystr,                            /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_CHECKTYPES |
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Multiprecision integers, similar to `long' but more efficient and\n"
+  "versatile.  Support all the standard arithmetic operations, with\n"
+  "implicit conversions from `PrimeFilter', and other objects which\n"
+  "convert to `" PY23("long", "int") "'.\n"
+  "\n"
+  "Constructor MP(X, [radix = R]) attempts to convert X to an `MP'.  If\n"
+  "X is a string, it's read in radix-R form, or we look for a prefix\n"
+  "if R = 0.  Other acceptable things are field elements, elliptic curve\n"
+  PY23(
+  "points, group elements, Python `int' and `long' objects, and anything\n"
+  "with an integer conversion.\n",
+  "points, group elements, Python `int' objects, and anything with an\n"
+  "integer conversion.\n")
+  "\n"
+  "Notes:\n"
+  "\n"
+  "  * Use `//' for integer division: `/' gives exact rational division.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  mp_pyrichcompare,                    /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(mp),                       /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(mp),                                /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  mp_pynew,                            /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Products of small integers ----------------------------------------*/
+
+static PyTypeObject *mpmul_pytype;
+
+typedef struct mpmul_pyobj {
+  PyObject_HEAD
+  int livep;
+  mpmul mm;
+} mpmul_pyobj;
+
+#define MPMUL_LIVEP(o) (((mpmul_pyobj *)(o))->livep)
+#define MPMUL_PY(o) (&((mpmul_pyobj *)(o))->mm)
+
+static void mpmul_pydealloc(PyObject *me)
+{
+  if (MPMUL_LIVEP(me))
+    mp_drop(mpmul_done(MPMUL_PY(me)));
+  FREEOBJ(me);
+}
+
+static PyObject *mmmeth_factor(PyObject *me, PyObject *arg)
+{
+  PyObject *q, *i;
+  mp *x;
+
+  if (!MPMUL_LIVEP(me)) VALERR("MPMul object invalid");
+  if (PyTuple_GET_SIZE(arg) != 1)
+    i = PyObject_GetIter(arg);
+  else {
+    if ((q = PyTuple_GET_ITEM(arg, 0)) == 0) goto end;
+    if ((i = PyObject_GetIter(q)) == 0) {
+      PyErr_Clear(); /* that's ok */
+      i = PyObject_GetIter(arg);
+    }
+  }
+  if (!i) goto end;
+  while ((q = PyIter_Next(i)) != 0) {
+    x = getmp(q); Py_DECREF(q); if (!x) {
+      Py_DECREF(i);
+      goto end;
+    }
+    mpmul_add(MPMUL_PY(me), x);
+    MP_DROP(x);
+  }
+  Py_DECREF(i);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *mmmeth_done(PyObject *me)
+{
+  mp *x;
+
+  if (!MPMUL_LIVEP(me)) VALERR("MPMul object invalid");
+  x = mpmul_done(MPMUL_PY(me));
+  MPMUL_LIVEP(me) = 0;
+  return (mp_pywrap(x));
+end:
+  return (0);
+}
+
+static PyObject *mpmul_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  mpmul_pyobj *mm;
+
+  if (kw) TYERR("keyword arguments not allowed here");
+  mm = (mpmul_pyobj *)ty->tp_alloc(ty, 0);
+  mpmul_init(&mm->mm);
+  mm->livep = 1;
+  if (mmmeth_factor((PyObject *)mm, arg) == 0) {
+    Py_DECREF(mm);
+    goto end;
+  }
+  return ((PyObject *)mm);
+end:
+  return (0);
+}
+
+static PyObject *mmget_livep(PyObject *me, void *hunoz)
+  { return (getbool(MPMUL_LIVEP(me))); }
+
+static const PyGetSetDef mpmul_pygetset[] = {
+#define GETSETNAME(op, name) mm##op##_##name
+  GET  (livep,         "MM.livep -> flag: object still valid?")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef mpmul_pymethods[] = {
+#define METHNAME(name) mmmeth_##name
+  METH (factor,        "MM.factor(ITERABLE) or MM.factor(I, ...)")
+  NAMETH(done,         "MM.done() -> PRODUCT")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject mpmul_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "MPMul",                             /* @tp_name@ */
+  sizeof(mpmul_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  mpmul_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "MPMul(N_0, N_1, ....): an object for multiplying many small integers.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(mpmul),                    /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(mpmul),                     /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  mpmul_pynew,                         /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Montgomery reduction ----------------------------------------------*/
+
+static PyTypeObject *mpmont_pytype;
+
+typedef struct mpmont_pyobj {
+  PyObject_HEAD
+  mpmont mm;
+} mpmont_pyobj;
+
+#define MPMONT_PY(o) (&((mpmont_pyobj *)(o))->mm)
+
+static PyObject *mmmeth_int(PyObject *me, PyObject *arg)
+{
+  PyObject *z = 0;
+  mp *yy = 0;
+  mpmont *mm = MPMONT_PY(me);
+
+  if (!PyArg_ParseTuple(arg, "O&:in", convmp, &yy))
+    goto end;
+  mp_div(0, &yy, yy, mm->m);
+  z = mp_pywrap(mpmont_mul(mm, MP_NEW, yy, mm->r2));
+end:
+  if (yy) MP_DROP(yy);
+  return (z);
+}
+
+static PyObject *mmmeth_mul(PyObject *me, PyObject *arg)
+{
+  PyObject *rc = 0;
+  mp *yy = 0, *zz = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&O&:mul", convmp, &yy, convmp, &zz))
+    goto end;
+  rc = mp_pywrap(mpmont_mul(MPMONT_PY(me), MP_NEW, yy, zz));
+end:
+  if (yy) MP_DROP(yy); if (zz) MP_DROP(zz);
+  return (rc);
+}
+
+static PyObject *mmmeth_exp(PyObject *me, PyObject *arg)
+{
+  PyObject *rc = 0;
+  mp *yy = 0, *zz = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&O&:exp", convmp, &yy, convmp, &zz))
+    goto end;
+  if (MP_NEGP(zz)) {
+    if ((yy = mp_modinv_checked(yy, yy, MPMONT_PY(me)->m)) == 0) goto end;
+    zz = mp_neg(zz, zz);
+  }
+  rc = mp_pywrap(mpmont_exp(MPMONT_PY(me), MP_NEW, yy, zz));
+end:
+  if (yy) MP_DROP(yy); if (zz) MP_DROP(zz);
+  return (rc);
+}
+
+static PyObject *mmmeth_expr(PyObject *me, PyObject *arg)
+{
+  PyObject *rc = 0;
+  mp *yy = 0, *zz = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&O&:expr", convmp, &yy, convmp, &zz))
+    goto end;
+  if (MP_NEGP(zz)) {
+    yy = mpmont_reduce(MPMONT_PY(me), yy, yy);
+    if ((yy = mp_modinv_checked(yy, yy, MPMONT_PY(me)->m)) == 0) goto end;
+    yy = mpmont_mul(MPMONT_PY(me), yy, yy, MPMONT_PY(me)->r2);
+    zz = mp_neg(zz, zz);
+  }
+  rc = mp_pywrap(mpmont_expr(MPMONT_PY(me), MP_NEW, yy, zz));
+end:
+  if (yy) MP_DROP(yy); if (zz) MP_DROP(zz);
+  return (rc);
+}
+
+static PyObject *mm_mexpr_id(PyObject *me)
+  { return mp_pywrap(MP_COPY(MPMONT_PY(me)->r)); }
+
+static int mm_mexpr_fill(void *p, PyObject *me, PyObject *x, PyObject *y)
+{
+  mp *xx = 0, *yy = 0;
+  mp_expfactor *f = p;
+  mpmont *mm = MPMONT_PY(me);
+
+  if ((xx = getmp(x)) == 0 || (yy = getmp(y)) == 0)
+    goto fail;
+  if (MP_NEGP(yy)) {
+    xx = mpmont_reduce(mm, xx, xx);
+    if ((xx = mp_modinv_checked(xx, xx, yy)) == 0)
+      goto fail;
+    xx = mpmont_mul(mm, xx, xx, mm->r2);
+    yy = mp_neg(yy, yy);
+  }
+  f->base = xx;
+  f->exp = yy;
+  return (0);
+
+fail:
+  mp_drop(xx); mp_drop(yy);
+  return (-1);
+}
+
+static PyObject *mm_mexpr(PyObject *me, void *v, size_t n)
+  { return mp_pywrap(mpmont_mexpr(MPMONT_PY(me), MP_NEW, v, n)); }
+
+static void mp_mexp_drop(void *p)
+{
+  mp_expfactor *f = p;
+  mp_drop(f->base);
+  mp_drop(f->exp);
+}
+
+static PyObject *mmmeth_mexpr(PyObject *me, PyObject *arg)
+{
+  return mexp_common(me, arg, sizeof(mp_expfactor),
+                    mm_mexpr_id, mm_mexpr_fill, mm_mexpr, mp_mexp_drop);
+}
+
+static PyObject *mp_mexp_id(PyObject *me)
+  { return mp_pywrap(MP_ONE); }
+
+static int mp_mexp_fill(void *p, PyObject *me, PyObject *x, PyObject *y)
+{
+  mp *xx = 0, *yy = 0;
+  mp_expfactor *f = p;
+
+  if ((xx = getmp(x)) == 0 || (yy = getmp(y)) == 0)
+    goto fail;
+  if (MP_NEGP(yy)) {
+    if ((xx = mp_modinv_checked(xx, xx, yy)) == 0)
+      goto fail;
+    yy = mp_neg(yy, yy);
+  }
+  f->base = xx;
+  f->exp = yy;
+  return (0);
+
+fail:
+  mp_drop(xx); mp_drop(yy);
+  return (-1);
+}
+
+static PyObject *mm_mexp(PyObject *me, void *v, size_t n)
+  { return mp_pywrap(mpmont_mexp(MPMONT_PY(me), MP_NEW, v, n)); }
+
+static PyObject *mmmeth_mexp(PyObject *me, PyObject *arg)
+{
+  return mexp_common(me, arg, sizeof(mp_expfactor),
+                    mp_mexp_id, mp_mexp_fill, mm_mexp, mp_mexp_drop);
+}
+
+#define mmmeth_ext mmmeth_reduce
+static PyObject *mmmeth_reduce(PyObject *me, PyObject *arg)
+{
+  PyObject *z = 0;
+  mp *yy = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&", convmp, &yy)) goto end;
+  z = mp_pywrap(mpmont_reduce(MPMONT_PY(me), MP_NEW, yy));
+end:
+  return (z);
+}
+
+static void mpmont_pydealloc(PyObject *me)
+{
+  mpmont_destroy(MPMONT_PY(me));
+  FREEOBJ(me);
+}
+
+static PyObject *mpmont_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  mpmont_pyobj *mm = 0;
+  static const char *const kwlist[] = { "m", 0 };
+  mp *xx = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convmp, &xx))
+    goto end;
+  if (!MP_POSP(xx) || !MP_ODDP(xx)) VALERR("m must be positive and odd");
+  mm = (mpmont_pyobj *)ty->tp_alloc(ty, 0);
+  mpmont_create(&mm->mm, xx);
+end:
+  if (xx) MP_DROP(xx);
+  return ((PyObject *)mm);
+}
+
+static PyObject *mmget_m(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(MPMONT_PY(me)->m))); }
+
+static PyObject *mmget_r(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(MPMONT_PY(me)->r))); }
+
+static PyObject *mmget_r2(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(MPMONT_PY(me)->r2))); }
+
+static const PyGetSetDef mpmont_pygetset[] = {
+#define GETSETNAME(op, name) mm##op##_##name
+  GET  (m,             "M.m -> modulus for reduction")
+  GET  (r,             "M.r -> multiplicative identity")
+  GET  (r2,            "M.r2 -> M.r^2, Montgomerization factor")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef mpmont_pymethods[] = {
+#define METHNAME(name) mmmeth_##name
+  METH (int,           "M.int(X) -> XR")
+  METH (mul,           "M.mul(XR, YR) -> ZR where Z = X Y")
+  METH (expr,          "M.expr(XR, N) -> ZR where Z = X^N mod M.m")
+  METH (mexpr,         "M.mexpr([(XR0, N0), (XR1, N1), ...]) = ZR "
+                                       "where Z = X0^N0 X1^N1 ... mod M.m\n"
+                   "\t(the list may be flattened if this more convenient.)")
+  METH (reduce,        "M.reduce(XR) -> X")
+  METH (ext,           "M.ext(XR) -> X")
+  METH (exp,           "M.exp(X, N) -> X^N mod M.m")
+  METH (mexp,          "M.mexp([(X0, N0), (X1, N1), ...]) = "
+                                                 "X0^N0 X1^N1 ... mod M.m\n"
+                   "\t(the list may be flattened if this more convenient.)")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject mpmont_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "MPMont",                            /* @tp_name@ */
+  sizeof(mpmont_pyobj),                        /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  mpmont_pydealloc,                    /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "MPMont(N): a Montgomery reduction context.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(mpmont),                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(mpmont),                    /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  mpmont_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Barrett reduction -------------------------------------------------*/
+
+static PyTypeObject *mpbarrett_pytype;
+
+typedef struct mpbarrett_pyobj {
+  PyObject_HEAD
+  mpbarrett mb;
+} mpbarrett_pyobj;
+
+#define MPBARRETT_PY(o) (&((mpbarrett_pyobj *)(o))->mb)
+
+static PyObject *mbmeth_exp(PyObject *me, PyObject *arg)
+{
+  PyObject *rc = 0;
+  mp *yy = 0, *zz = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&O&:exp", convmp, &yy, convmp, &zz))
+    goto end;
+  if (MP_NEGP(zz)) {
+    if ((yy = mp_modinv_checked(yy, yy, MPBARRETT_PY(me)->m)) == 0) goto end;
+    zz = mp_neg(zz, zz);
+  }
+  rc = mp_pywrap(mpbarrett_exp(MPBARRETT_PY(me), MP_NEW, yy, zz));
+end:
+  if (yy) MP_DROP(yy); if (zz) MP_DROP(zz);
+  return (rc);
+}
+
+static PyObject *mb_mexp(PyObject *me, void *v, size_t n)
+  { return mp_pywrap(mpbarrett_mexp(MPBARRETT_PY(me), MP_NEW, v, n)); }
+
+static PyObject *mbmeth_mexp(PyObject *me, PyObject *arg)
+{
+  return mexp_common(me, arg, sizeof(mp_expfactor),
+                    mp_mexp_id, mp_mexp_fill, mb_mexp, mp_mexp_drop);
+}
+
+static PyObject *mbmeth_reduce(PyObject *me, PyObject *arg)
+{
+  PyObject *z = 0;
+  mp *yy = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:reduce", convmp, &yy))
+    goto end;
+  z = mp_pywrap(mpbarrett_reduce(MPBARRETT_PY(me), MP_NEW, yy));
+end:
+  return (z);
+}
+
+static void mpbarrett_pydealloc(PyObject *me)
+{
+  mpbarrett_destroy(MPBARRETT_PY(me));
+  FREEOBJ(me);
+}
+
+static PyObject *mpbarrett_pynew(PyTypeObject *ty,
+                                PyObject *arg, PyObject *kw)
+{
+  mpbarrett_pyobj *mb = 0;
+  static const char *const kwlist[] = { "m", 0 };
+  mp *xx = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convmp, &xx))
+    goto end;
+  if (!MP_POSP(xx)) VALERR("m must be positive");
+  mb = (mpbarrett_pyobj *)ty->tp_alloc(ty, 0);
+  mpbarrett_create(&mb->mb, xx);
+end:
+  if (xx) MP_DROP(xx);
+  return ((PyObject *)mb);
+}
+
+static PyObject *mbget_m(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(MPBARRETT_PY(me)->m))); }
+
+static const PyGetSetDef mpbarrett_pygetset[] = {
+#define GETSETNAME(op, name) mb##op##_##name
+  GET  (m,             "B.m -> modulus for reduction")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef mpbarrett_pymethods[] = {
+#define METHNAME(name) mbmeth_##name
+  METH (reduce,        "B.reduce(X) -> X mod B.m")
+  METH (exp,           "B.exp(X, N) -> X^N mod B.m")
+  METH (mexp,          "B.mexp([(X0, N0), (X1, N1), ...]) = "
+                                                 "X0^N0 X1^N1 ... mod B.m\n"
+                   "\t(the list may be flattened if this more convenient.)")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject mpbarrett_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "MPBarrett",                         /* @tp_name@ */
+  sizeof(mpbarrett_pyobj),             /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  mpbarrett_pydealloc,                 /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "MPBarrett(N): a Barrett reduction context.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(mpbarrett),                        /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(mpbarrett),                 /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  mpbarrett_pynew,                     /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Nice prime reduction ----------------------------------------------*/
+
+static PyTypeObject *mpreduce_pytype;
+
+typedef struct mpreduce_pyobj {
+  PyObject_HEAD
+  mpreduce mr;
+} mpreduce_pyobj;
+
+#define MPREDUCE_PY(o) (&((mpreduce_pyobj *)(o))->mr)
+
+static PyObject *mrmeth_exp(PyObject *me, PyObject *arg)
+{
+  PyObject *rc = 0;
+  mp *yy = 0, *zz = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&O&:exp", convmp, &yy, convmp, &zz))
+    goto end;
+  if (MP_NEGP(zz)) {
+    if ((yy = mp_modinv_checked(yy, yy, MPREDUCE_PY(me)->p)) == 0) goto end;
+    zz = mp_neg(zz, zz);
+  }
+  rc = mp_pywrap(mpreduce_exp(MPREDUCE_PY(me), MP_NEW, yy, zz));
+end:
+  if (yy) MP_DROP(yy); if (zz) MP_DROP(zz);
+  return (rc);
+}
+
+static PyObject *mrmeth_reduce(PyObject *me, PyObject *arg)
+{
+  PyObject *z = 0;
+  mp *yy = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:reduce", convmp, &yy)) goto end;
+  z = mp_pywrap(mpreduce_do(MPREDUCE_PY(me), MP_NEW, yy));
+end:
+  return (z);
+}
+
+static void mpreduce_pydealloc(PyObject *me)
+{
+  mpreduce_destroy(MPREDUCE_PY(me));
+  FREEOBJ(me);
+}
+
+static PyObject *mpreduce_pynew(PyTypeObject *ty,
+                                PyObject *arg, PyObject *kw)
+{
+  mpreduce_pyobj *mr = 0;
+  mpreduce r;
+  static const char *const kwlist[] = { "m", 0 };
+  mp *xx = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convmp, &xx))
+    goto end;
+  if (!MP_POSP(xx)) VALERR("m must be positive");
+  if (mpreduce_create(&r, xx)) VALERR("bad modulus (must be 2^k - ...)");
+  mr = (mpreduce_pyobj *)ty->tp_alloc(ty, 0);
+  mr->mr = r;
+end:
+  if (xx) MP_DROP(xx);
+  return ((PyObject *)mr);
+}
+
+static PyObject *mrget_m(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(MPREDUCE_PY(me)->p))); }
+
+static const PyGetSetDef mpreduce_pygetset[] = {
+#define GETSETNAME(op, name) mr##op##_##name
+  GET  (m,             "R.m -> modulus for reduction")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const const PyMethodDef mpreduce_pymethods[] = {
+#define METHNAME(name) mrmeth_##name
+  METH (reduce,        "R.reduce(X) -> X mod B.m")
+  METH (exp,           "R.exp(X, N) -> X^N mod B.m")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject mpreduce_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "MPReduce",                          /* @tp_name@ */
+  sizeof(mpreduce_pyobj),              /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  mpreduce_pydealloc,                  /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "MPReduce(N): a reduction context for reduction modulo Solinas primes.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(mpreduce),                 /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(mpreduce),                  /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  mpreduce_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Chinese Remainder Theorem solution --------------------------------*/
+
+static PyTypeObject *mpcrt_pytype;
+
+typedef struct mpcrt_pyobj {
+  PyObject_HEAD
+  mpcrt c;
+} mpcrt_pyobj;
+
+#define MPCRT_PY(o) (&((mpcrt_pyobj *)(o))->c)
+
+static PyObject *mcmeth_solve(PyObject *me, PyObject *arg)
+{
+  mpcrt *c = MPCRT_PY(me);
+  PyObject *q = 0, *it, *x, *z = 0;
+  mp *xx;
+  mp **v = 0;
+  Py_ssize_t i = 0, n = c->k;
+
+  if (PyTuple_GET_SIZE(arg) == n)
+    q = arg;
+  else if (!PyArg_ParseTuple(arg, "O:solve", &q))
+    goto end;
+  v = xmalloc(n*sizeof(*v));
+  it = PyObject_GetIter(q); if (!it) goto end;
+  while (i < n) {
+    x = PyIter_Next(it);
+    if (!x) {
+      if (PyErr_Occurred()) goto end;
+      VALERR("residue count mismatch");
+    }
+    xx = getmp(x); Py_DECREF(x); if (!xx) goto end;
+    v[i++] = xx; xx = 0;
+  }
+  x = PyIter_Next(it);
+  if (x) { Py_DECREF(x); VALERR("residue count mismatch"); }
+  else if (PyErr_Occurred()) goto end;
+  z = mp_pywrap(mpcrt_solve(c, MP_NEW, v));
+end:
+  if (v) {
+    while (i--) MP_DROP(v[i]);
+    xfree(v);
+  }
+  return (z);
+}
+
+static void mpcrt_pydealloc(PyObject *me)
+{
+  mpcrt *c = MPCRT_PY(me);
+  mpcrt_destroy(c);
+  xfree(c->v);
+}
+
+static PyObject *mpcrt_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  mpcrt_mod *v = 0;
+  Py_ssize_t n, i = 0, j;
+  static const char *const kwlist[] = { "mv", 0 };
+  PyObject *q, *it = 0, *x;
+  mp *xx = MP_NEW, *y = MP_NEW, *g = MP_NEW;
+  mpmul mm;
+  mpcrt_pyobj *c = 0;
+
+  if (PyTuple_GET_SIZE(arg) > 1)
+    q = arg;
+  else if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:new", KWLIST, &q))
+    goto end;
+
+  if (!PySequence_Check(q))
+    n = 16;
+  else {
+    n = PySequence_Size(arg);
+    if (n == (size_t)-1 && PyErr_Occurred()) goto end;
+  }
+
+  v = xmalloc(n*sizeof(*v));
+  it = PyObject_GetIter(q); if (!it) goto end;
+  for (;;) {
+    x = PyIter_Next(it); if (!x) break;
+    xx = getmp(x); Py_DECREF(x); if (!xx) goto end;
+    if (MP_CMP(xx, <=, MP_ZERO)) VALERR("moduli must be positive");
+    if (i >= n) { n *= 2; v = xrealloc(v, n*sizeof(*v), i*sizeof(*v)); }
+    v[i].m = xx; v[i].n = 0; v[i].ni = 0; v[i].nni = 0; i++; xx = MP_NEW;
+  }
+  if (PyErr_Occurred()) goto end;
+  Py_DECREF(it); it = 0;
+
+  mpmul_init(&mm);
+  for (j = 0; j < i; j++) mpmul_add(&mm, v[j].m);
+  xx = mpmul_done(&mm);
+  for (j = 0; j < i; j++) {
+    mp_div(&y, 0, xx, v[j].m);
+    mp_gcd(&g, 0, 0, y, v[j].m);
+    if (!MP_EQ(g, MP_ONE)) VALERR("moduli must be pairwise coprime");
+  }
+
+  c = (mpcrt_pyobj *)ty->tp_alloc(ty, 0);
+  mpcrt_create(&c->c, v, i, 0);
+  mp_drop(xx); mp_drop(y); mp_drop(g);
+  return ((PyObject *)c);
+
+end:
+  if (v) {
+    while (i--) MP_DROP(v[i].m);
+    xfree(v);
+  }
+  mp_drop(xx); mp_drop(y); mp_drop(g);
+  Py_XDECREF(it);
+  return (0);
+}
+
+static PyObject *mcget_product(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(MPCRT_PY(me)->mb.m))); }
+
+static PyObject *mcget_moduli(PyObject *me, void *hunoz)
+{
+  int i;
+  PyObject *q;
+  mpcrt *c = MPCRT_PY(me);
+
+  if ((q = PyList_New(c->k)) == 0) return (0);
+  for (i = 0; i < c->k; i++)
+    PyList_SET_ITEM(q, i, mp_pywrap(c->v[i].m));
+  return (q);
+}
+
+static const PyGetSetDef mpcrt_pygetset[] = {
+#define GETSETNAME(op, name) mc##op##_##name
+  GET  (product,       "C.product -> product of moduli")
+  GET  (moduli,        "C.moduli -> list of individual moduli")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef mpcrt_pymethods[] = {
+#define METHNAME(name) mcmeth_##name
+  METH (solve,         "C.solve([R0, R1]) -> X mod C.product")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject mpcrt_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "MPCRT",                             /* @tp_name@ */
+  sizeof(mpcrt_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  mpcrt_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "MPCRT(SEQ): a context for solving Chinese Remainder Theorem problems.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(mpcrt),                    /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(mpcrt),                     /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  mpcrt_pynew,                         /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Binary polynomials ------------------------------------------------*/
+
+static PyObject *gf_pyrepr(PyObject *o)
+  { return mp_topystring(MP_X(o), 16, "GF(", "0x", ")"); }
+
+static PyObject *gf_pyrichcompare(PyObject *x, PyObject *y, int op)
+{
+  mp *xx, *yy;
+  int xl, yl;
+  int b;
+
+  if (gfbinop(x, y, &xx, &yy)) RETURN_NOTIMPL;
+  switch (op) {
+    case Py_EQ: b = MP_EQ(xx, yy); break;
+    case Py_NE: b = !MP_EQ(xx, yy); break;
+    default:
+      xl = mp_bits(xx);
+      yl = mp_bits(yy);
+      switch (op) {
+       case Py_LT: b = xl < yl; break;
+       case Py_LE: b = xl <= yl; break;
+       case Py_GT: b = xl > yl; break;
+       case Py_GE: b = xl >= yl; break;
+       default: abort();
+      }
+      break;
+  }
+  MP_DROP(xx); MP_DROP(yy);
+  return (getbool(b));
+}
+
+static PyObject *gf_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  PyObject *x;
+  mp *z;
+  mp_pyobj *zz = 0;
+  int radix = 0;
+  static const char *const kwlist[] = { "x", "radix", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|i:gf", KWLIST, &x, &radix))
+    goto end;
+  if (GF_PYCHECK(x)) RETURN_OBJ(x);
+  if (!good_radix_p(radix, 1)) VALERR("radix out of range");
+  if ((z = mp_frompyobject(x, radix)) == 0) {
+    PyErr_Format(PyExc_TypeError, "can't convert %.100s to gf",
+                Py_TYPE(x)->tp_name);
+    goto end;
+  }
+  if (MP_NEGP(z)) {
+    MP_DROP(z);
+    VALERR("gf cannot be negative");
+  }
+  zz = (mp_pyobj *)ty->tp_alloc(ty, 0);
+  zz->x = z;
+end:
+  return ((PyObject *)zz);
+}
+
+static PyObject *gf_pyexp(PyObject *x, PyObject *y, PyObject *z)
+{
+  mp *xx = 0, *yy = 0, *zz = 0;
+  mp *r = 0;
+  PyObject *rc = 0;
+
+  if ((xx = implicitgf(x)) == 0 || (yy = implicitmp(y)) == 0 ||
+      (z && z != Py_None && (zz = implicitgf(z)) == 0)) {
+    mp_drop(xx); mp_drop(yy); mp_drop(zz);
+    RETURN_NOTIMPL;
+  }
+  if (!z || z == Py_None) {
+    if (MP_NEGP(yy)) VALERR("negative exponent");
+    r = gf_exp(MP_NEW, xx, yy);
+  } else {
+    gfreduce gr;
+    if (MP_ZEROP(zz)) ZDIVERR("zero modulus");
+    if (MP_NEGP(yy)) {
+      if ((xx = gf_modinv_checked(xx, xx, zz)) == 0) goto end;
+      yy = mp_neg(yy, yy);
+    }
+    gfreduce_create(&gr, zz);
+    r = gfreduce_exp(&gr, MP_NEW, xx, yy);
+    gfreduce_destroy(&gr);
+  }
+  rc = gf_pywrap(r);
+end:
+  mp_drop(xx); mp_drop(yy); mp_drop(zz);
+  return (rc);
+}
+
+static PyObject *gfmeth_sqr(PyObject *me)
+  { return (gf_pywrap(gf_sqr(MP_NEW, MP_X(me)))); }
+
+static PyObject *gfmeth_gcd(PyObject *me, PyObject *arg)
+{
+  PyObject *z = 0;
+  mp *yy = 0, *zz = MP_NEW;
+
+  if (!PyArg_ParseTuple(arg, "O&:gcd", convgf, &yy)) goto end;
+  gf_gcd(&zz, 0, 0, MP_X(me), yy);
+  z = gf_pywrap(zz);
+end:
+  if (yy) MP_DROP(yy);
+  return (z);
+}
+
+static PyObject *gfmeth_gcdx(PyObject *me, PyObject *arg)
+{
+  PyObject *z = 0;
+  mp *yy = 0, *zz = MP_NEW, *uu = MP_NEW, *vv = MP_NEW;
+
+  if (!PyArg_ParseTuple(arg, "O&:gcdx", convgf, &yy))
+    goto end;
+  gf_gcd(&zz, &uu, &vv, MP_X(me), yy);
+  z = Py_BuildValue("(NNN)",
+                   gf_pywrap(zz), gf_pywrap(uu), gf_pywrap(vv));
+end:
+  if (yy) MP_DROP(yy);
+  return (z);
+}
+
+static PyObject *gfmeth_modinv(PyObject *me, PyObject *arg)
+{
+  PyObject *z = 0;
+  mp *yy = 0, *zz = MP_NEW;
+
+  if (!PyArg_ParseTuple(arg, "O&:modinv", convgf, &yy) ||
+      (zz = gf_modinv_checked(MP_NEW, yy, MP_X(me))) == 0)
+    goto end;
+  z = gf_pywrap(zz);
+end:
+  if (yy) MP_DROP(yy);
+  return (z);
+}
+
+static PyObject *gfmeth_fromstring(PyObject *me,
+                                  PyObject *arg, PyObject *kw)
+{
+  int r = 0;
+  char *p;
+  Py_ssize_t len;
+  PyObject *z = 0;
+  mp *zz;
+  mptext_stringctx sc;
+  static const char *const kwlist[] = { "x", "radix", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "s#|i:fromstring", KWLIST,
+                                  &p, &len, &r))
+    goto end;
+  if (!good_radix_p(r, 1)) VALERR("bad radix");
+  sc.buf = p; sc.lim = p + len;
+  if ((zz = mp_read(MP_NEW, r, &mptext_stringops, &sc)) == 0 ||
+      MP_NEGP(zz)) {
+    if (zz) MP_DROP(zz);
+    VALERR("bad binary polynomial");
+  }
+  z = Py_BuildValue("(Ns#)", gf_pywrap(zz),
+                   sc.buf, (Py_ssize_t)(sc.lim - sc.buf));
+end:
+  return (z);
+}
+
+static PyObject *gfmeth_irreduciblep(PyObject *me)
+  { return getbool(gf_irreduciblep(MP_X(me))); }
+
+static PyObject *gfget_degree(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(mp_bits(MP_X(me)) - 1)); }
+
+static const PyGetSetDef gf_pygetset[] = {
+#define GETSETNAME(op, name) gf##op##_##name
+  GET  (degree,        "X.degree -> polynomial degree of X")
+#undef GETSETNAME
+#define GETSETNAME(op, name) mp##op##_##name
+  GET  (nbits,         "X.nbits -> bit length of X")
+  GET  (noctets,       "X.noctets -> octet length of X")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef gf_pymethods[] = {
+#define METHNAME(func) gfmeth_##func
+  METH (setbit,        "X.setbit(N) -> X with bit N set")
+  METH (clearbit,      "X.clearbit(N) -> X with bit N clear")
+  METH (testbit,       "X.testbit(N) -> true/false if bit N set/clear in X")
+  NAMETH(sqr,          "X.sqr() -> X^2")
+  METH (gcd,           "X.gcd(Y) -> gcd(X, Y)")
+  METH (gcdx,   "X.gcdx(Y) -> (gcd(X, Y), U, V) with X U + Y V = gcd(X, Y)")
+  METH (modinv,        "X.modinv(Y) -> multiplicative inverse of Y mod X")
+  NAMETH(irreduciblep, "X.irreduciblep() -> true/false")
+  KWSMTH(fromstring,   "fromstring(STR, [radix = 0]) -> (X, REST)\n"
+    "  Parse STR as a binary polynomial, according to RADIX.  If RADIX is\n"
+    "  zero, read a prefix from STR to decide radix: allow `0b' for binary,\n"
+    "  `0' or `0o' for octal, `0x' for hex, or `R_' for other radix R.")
+  SMTH (_implicit,     0)
+  SMTH (loadl,         "loadl(STR) -> X: read little-endian bytes")
+  SMTH (loadb,         "loadb(STR) -> X: read big-endian bytes")
+  SMTH (frombuf,       "frombuf(STR) -> (X, REST): read buffer format")
+#undef METHNAME
+#define METHNAME(func) mpmeth_##func
+  KWMETH(tostring,     "X.tostring([radix = 10]) -> STR")
+  KWMETH(storel,       "X.storel([len = -1]) -> little-endian bytes")
+  KWMETH(storeb,       "X.storeb([len = -1]) -> big-endian bytes")
+  KWMETH(storel2c,     "X.storel2c([len = -1]) -> "
+                                    "little-endian bytes, two's complement")
+  KWMETH(storeb2c,     "X.storeb2c([len = -1]) -> "
+                                       "big-endian bytes, two's complement")
+  NAMETH(tobuf,                "X.tobuf() -> buffer format")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyNumberMethods gf_pynumber = {
+  gf_pyadd,                            /* @nb_add@ */
+  gf_pysub,                            /* @nb_subtract@ */
+  gf_pymul,                            /* @nb_multiply@ */
+#ifdef PY2
+  0,                                   /* @nb_divide@ */
+#endif
+  gf_pymod,                            /* @nb_remainder@ */
+  gf_pydivmod,                         /* @nb_divmod@ */
+  gf_pyexp,                            /* @nb_power@ */
+  mp_pyid,                             /* @nb_negative@ */
+  mp_pyid,                             /* @nb_positive@ */
+  mp_pyid,                             /* @nb_absolute@ */
+  mp_pynonzerop,                       /* @nb_nonzero@ */
+  0 /* doesn't make any sense */,      /* @nb_invert@ */
+  gf_pylsl,                            /* @nb_lshift@ */
+  gf_pylsr,                            /* @nb_rshift@ */
+  gf_pyand,                            /* @nb_and@ */
+  gf_pyxor,                            /* @nb_xor@ */
+  gf_pyor,                             /* @nb_or@ */
+#ifdef PY2
+  gf_pycoerce,                         /* @nb_coerce@ */
+#endif
+  mp_pyint,                            /* @nb_int@ */
+  PY23(mp_pylong, 0),                  /* @nb_long@ */
+  0 /* doesn't make any sense */,      /* @nb_float@ */
+#ifdef PY2
+  mp_pyoct,                            /* @nb_oct@ */
+  mp_pyhex,                            /* @nb_hex@ */
+#endif
+
+  0,                                   /* @nb_inplace_add@ */
+  0,                                   /* @nb_inplace_subtract@ */
+  0,                                   /* @nb_inplace_multiply@ */
+#ifdef PY2
+  0,                                   /* @nb_inplace_divide@ */
+#endif
+  0,                                   /* @nb_inplace_remainder@ */
+  0,                                   /* @nb_inplace_power@ */
+  0,                                   /* @nb_inplace_lshift@ */
+  0,                                   /* @nb_inplace_rshift@ */
+  0,                                   /* @nb_inplace_and@ */
+  0,                                   /* @nb_inplace_xor@ */
+  0,                                   /* @nb_inplace_or@ */
+
+  gf_pydiv,                            /* @nb_floor_divide@ */
+  0,                                   /* @nb_true_divide@ */
+  0,                                   /* @nb_inplace_floor_divide@ */
+  0,                                   /* @nb_inplace_true_divide@ */
+
+  mp_pyint,                            /* @nb_index@ */
+};
+
+static const PyTypeObject gf_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GF",                                        /* @tp_name@ */
+  sizeof(mp_pyobj),                    /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  mp_pydealloc,                                /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  gf_pyrepr,                           /* @tp_repr@ */
+  PYNUMBER(gf),                                /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  mp_pyhash,                           /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  mp_pyhex,                            /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_CHECKTYPES |
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Binary polynomials.  Support almost all the standard arithmetic\n"
+  "operations.\n"
+  "\n"
+  "Constructor GF(X, [radix = R]) attempts to convert X to a `GF'.  If\n"
+  "X is a string, it's read in radix-R form, or we look for a prefix\n"
+  "if R = 0.  Other acceptable things are field elements, elliptic curve\n"
+  PY23(
+  "points, group elements, Python `int' and `long' objects, and anything\n"
+  "with an integer conversion.\n",
+  "points, group elements, Python `int' objects, and anything with an\n"
+  "integer conversion.\n")
+  "\n"
+  "The name is hopelessly wrong from a technical point of view, but\n"
+  "but it's much easier to type than `p2' or `c2' or whatever.\n"
+  "\n"
+  "Notes:\n"
+  "\n"
+  "  * Use `//' for Euclidean division: `/' gives exact rational division.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  gf_pyrichcompare,                    /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gf),                       /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gf),                                /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  gf_pynew,                            /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Sparse poly reduction ---------------------------------------------*/
+
+static PyTypeObject *gfreduce_pytype;
+
+typedef struct gfreduce_pyobj {
+  PyObject_HEAD
+  gfreduce mr;
+} gfreduce_pyobj;
+
+#define GFREDUCE_PY(o) (&((gfreduce_pyobj *)(o))->mr)
+
+static PyObject *grmeth_exp(PyObject *me, PyObject *arg)
+{
+  PyObject *rc = 0;
+  mp *yy = 0, *zz = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&O&:exp", convgf, &yy, convgf, &zz))
+    goto end;
+  if (MP_NEGP(zz)) {
+    if ((yy = gf_modinv_checked(yy, yy, GFREDUCE_PY(me)->p)) == 0) goto end;
+    zz = mp_neg(zz, zz);
+  }
+  rc = gf_pywrap(gfreduce_exp(GFREDUCE_PY(me), MP_NEW, yy, zz));
+end:
+  if (yy) MP_DROP(yy); if (zz) MP_DROP(zz);
+  return (rc);
+}
+
+static PyObject *grmeth_trace(PyObject *me, PyObject *arg)
+{
+  PyObject *rc = 0;
+  mp *xx = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:trace", convgf, &xx)) goto end;
+  rc = PyInt_FromLong(gfreduce_trace(GFREDUCE_PY(me), xx));
+end:
+  if (xx) MP_DROP(xx);
+  return (rc);
+}
+
+static PyObject *grmeth_halftrace(PyObject *me, PyObject *arg)
+{
+  PyObject *rc = 0;
+  mp *xx = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:halftrace", convgf, &xx)) goto end;
+  rc = gf_pywrap(gfreduce_halftrace(GFREDUCE_PY(me), MP_NEW, xx));
+end:
+  if (xx) MP_DROP(xx);
+  return (rc);
+}
+
+static PyObject *grmeth_sqrt(PyObject *me, PyObject *arg)
+{
+  PyObject *rc = 0;
+  mp *xx = 0, *yy;
+
+  if (!PyArg_ParseTuple(arg, "O&:sqrt", convgf, &xx)) goto end;
+  if ((yy = gfreduce_sqrt(GFREDUCE_PY(me), MP_NEW, xx)) == 0)
+    VALERR("no modular square root");
+  rc = gf_pywrap(yy);
+end:
+  if (xx) MP_DROP(xx);
+  return (rc);
+}
+
+static PyObject *grmeth_quadsolve(PyObject *me, PyObject *arg)
+{
+  PyObject *rc = 0;
+  mp *xx = 0, *yy;
+
+  if (!PyArg_ParseTuple(arg, "O&:quadsolve", convgf, &xx)) goto end;
+  if ((yy = gfreduce_quadsolve(GFREDUCE_PY(me), MP_NEW, xx)) == 0)
+    VALERR("no solution found");
+  rc = gf_pywrap(yy);
+end:
+  if (xx) MP_DROP(xx);
+  return (rc);
+}
+
+static PyObject *grmeth_reduce(PyObject *me, PyObject *arg)
+{
+  PyObject *z = 0;
+  mp *yy = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:reduce", convgf, &yy)) goto end;
+  z = gf_pywrap(gfreduce_do(GFREDUCE_PY(me), MP_NEW, yy));
+end:
+  return (z);
+}
+
+static void gfreduce_pydealloc(PyObject *me)
+{
+  gfreduce_destroy(GFREDUCE_PY(me));
+  FREEOBJ(me);
+}
+
+static PyObject *gfreduce_pynew(PyTypeObject *ty,
+                                PyObject *arg, PyObject *kw)
+{
+  gfreduce_pyobj *mr = 0;
+  gfreduce r;
+  static const char *const kwlist[] = { "m", 0 };
+  mp *xx = 0;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convgf, &xx))
+    goto end;
+  if (MP_ZEROP(xx)) ZDIVERR("modulus is zero!");
+  gfreduce_create(&r, xx);
+  mr = (gfreduce_pyobj *)ty->tp_alloc(ty, 0);
+  mr->mr = r;
+end:
+  if (xx) MP_DROP(xx);
+  return ((PyObject *)mr);
+}
+
+static PyObject *grget_m(PyObject *me, void *hunoz)
+  { return (gf_pywrap(MP_COPY(GFREDUCE_PY(me)->p))); }
+
+static const PyGetSetDef gfreduce_pygetset[] = {
+#define GETSETNAME(op, name) gr##op##_##name
+  GET  (m,             "R.m -> reduction polynomial")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef gfreduce_pymethods[] = {
+#define METHNAME(name) grmeth_##name
+  METH (reduce,        "R.reduce(X) -> X mod B.m")
+  METH (trace,        "R.trace(X) -> Tr(X) = x + x^2 + ... + x^{2^{m - 1}}")
+  METH (halftrace,    "R.halftrace(X) -> x + x^{2^2} + ... + x^{2^{m - 1}}")
+  METH (sqrt,          "R.sqrt(X) -> Y where Y^2 = X mod R")
+  METH (quadsolve,     "R.quadsolve(X) -> Y where Y^2 + Y = X mod R")
+  METH (exp,           "R.exp(X, N) -> X^N mod B.m")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject gfreduce_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GFReduce",                          /* @tp_name@ */
+  sizeof(gfreduce_pyobj),              /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  gfreduce_pydealloc,                  /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "GFReduce(N): a context for reduction modulo sparse polynomials.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gfreduce),                 /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gfreduce),                  /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  gfreduce_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Normal/poly transformation ----------------------------------------*/
+
+static PyTypeObject *gfn_pytype;
+
+typedef struct gfn_pyobj {
+  PyObject_HEAD
+  mp *p;
+  gfn ntop, pton;
+} gfn_pyobj;
+
+#define GFN_P(o) (((gfn_pyobj *)(o))->p)
+#define GFN_PTON(o) (&((gfn_pyobj *)(o))->pton)
+#define GFN_NTOP(o) (&((gfn_pyobj *)(o))->ntop)
+
+static PyObject *gfn_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  mp *p = 0, *beta = 0;
+  gfn_pyobj *gg = 0;
+  static const char *const kwlist[] = { "p", "beta", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:new", KWLIST,
+                                  convgf, &p, convgf, &beta))
+    goto end;
+  gg = PyObject_New(gfn_pyobj, ty);
+  gg->p = 0;
+  if (gfn_create(p, beta, &gg->ntop, &gg->pton)) {
+    Py_DECREF(gg);
+    gg = 0;
+    VALERR("can't invert transformation matrix");
+  }
+  gg->p = MP_COPY(p);
+end:
+  mp_drop(p);
+  mp_drop(beta);
+  return ((PyObject *)gg);
+}
+
+static PyObject *gfnget_p(PyObject *me, void *hunoz)
+  { return (gf_pywrap(MP_COPY(GFN_P(me)))); }
+
+static PyObject *gfnget_beta(PyObject *me, void *hunoz)
+{
+  gfn *n = GFN_NTOP(me);
+  mp *x = n->r[n->n - 1];
+  return (gf_pywrap(MP_COPY(x)));
+}
+
+#define XFORMOP(name, NAME)                                            \
+  static PyObject *gfnmeth_##name(PyObject *me, PyObject *arg)         \
+  {                                                                    \
+    mp *xx = 0;                                                                \
+    mp *z = 0;                                                         \
+                                                                       \
+    if (!PyArg_ParseTuple(arg, "O&:" #name, convgf, &xx)) goto end;    \
+    z = gfn_transform(GFN_##NAME(me), MP_NEW, xx);                     \
+  end:                                                                 \
+    mp_drop(xx);                                                       \
+    if (!z) return (0);                                                        \
+    return (gf_pywrap(z));                                             \
+  }
+XFORMOP(pton, PTON)
+XFORMOP(ntop, NTOP)
+#undef XFORMOP
+
+static void gfn_pydealloc(PyObject *me)
+{
+  if (GFN_P(me)) {
+    MP_DROP(GFN_P(me));
+    gfn_destroy(GFN_PTON(me));
+    gfn_destroy(GFN_NTOP(me));
+  }
+  FREEOBJ(me);
+}
+
+static const PyGetSetDef gfn_pygetset[] = {
+#define GETSETNAME(op, name) gfn##op##_##name
+  GET  (p,             "X.p -> polynomial basis, as polynomial")
+  GET  (beta,          "X.beta -> normal basis element, in poly form")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef gfn_pymethods[] = {
+#define METHNAME(name) gfnmeth_##name
+  METH (pton,          "X.pton(A) -> normal-basis representation of A")
+  METH (ntop,          "X.ntop(A) -> polynomial-basis representation of A")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject gfn_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GFN",                               /* @tp_name@ */
+  sizeof(gfn_pyobj),                   /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  gfn_pydealloc,                       /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "GFN(P, BETA): an object for transforming elements of binary fields\n"
+  "  between polynomial and normal basis representations.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gfn),                      /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gfn),                       /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  gfn_pynew,                           /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Glue --------------------------------------------------------------*/
+
+static const struct nameval consts[] = {
+  CONST(MPW_MAX),
+  { 0 }
+};
+
+void mp_pyinit(void)
+{
+  INITTYPE(mp, root);
+  INITTYPE(gf, root);
+  INITTYPE(mpmul, root);
+  INITTYPE(mpmont, root);
+  INITTYPE(mpbarrett, root);
+  INITTYPE(mpreduce, root);
+  INITTYPE(mpcrt, root);
+  INITTYPE(gfreduce, root);
+  INITTYPE(gfn, root);
+}
+
+void mp_pyinsert(PyObject *mod)
+{
+  INSERT("MP", mp_pytype);
+  INSERT("MPMul", mpmul_pytype);
+  INSERT("MPMont", mpmont_pytype);
+  INSERT("MPBarrett", mpbarrett_pytype);
+  INSERT("MPReduce", mpreduce_pytype);
+  INSERT("MPCRT", mpcrt_pytype);
+  INSERT("GF", gf_pytype);
+  INSERT("GFReduce", gfreduce_pytype);
+  INSERT("GFN", gfn_pytype);
+  setconstants(mod, consts);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/passphrase.c b/passphrase.c
new file mode 100644 (file)
index 0000000..424bcc9
--- /dev/null
@@ -0,0 +1,249 @@
+/* -*-c-*-
+ *
+ * Reading and writing passphrases
+ *
+ * (c) 2005 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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "catacomb-python.h"
+
+/*----- Pixie stuff -------------------------------------------------------*/
+
+typedef struct pixie_pyobj {
+  PyObject_HEAD
+  int fd;
+} pixie_pyobj;
+
+static PyTypeObject *pixie_pytype;
+#define PIXIE_PYCHECK(o) PyObject_TypeCheck((o), pixie_pytype)
+#define PIXIE_FD(o) (((pixie_pyobj *)(o))->fd)
+
+#ifdef WANT_CONVPIXIE
+static int convpixie(PyObject *o, void *p)
+{
+  if (!PIXIE_PYCHECK(o))
+    TYERR("want pixie");
+  *(int *)p = PIXIE_FD(o);
+  return (1);
+end:
+  return (0);
+}
+#endif
+
+static PyObject *pixie_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  pixie_pyobj *rc = 0;
+  static const char *const kwlist[] = { "socket", 0 };
+  char *sock = 0;
+  int fd;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|s:new", KWLIST, &sock))
+    goto end;
+  if ((fd = pixie_open(sock)) < 0)
+    OSERR(sock);
+  rc = (pixie_pyobj *)ty->tp_alloc(ty, 0);
+  rc->fd = fd;
+end:
+  return ((PyObject *)rc);
+}
+
+static void pixie_pydealloc(PyObject *me)
+{
+  close(PIXIE_FD(me));
+  FREEOBJ(me);
+}
+
+static PyObject *pixmeth_read(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  unsigned mode = PMODE_READ;
+  char *tag;
+  static const char *const kwlist[] = { "tag", "mode", 0 };
+  PyObject *rc = 0;
+  int r;
+  char buf[1024];
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|O&:read", KWLIST,
+                                  &tag, convuint, &mode))
+    goto end;
+  r = pixie_read(PIXIE_FD(me), tag, mode, buf, sizeof(buf));
+  if (r < 0)
+    OSERR(0);
+  else if (r > 0)
+    RETURN_NONE;
+  else
+    rc = BIN_FROMSTR(buf);
+end:
+  return (rc);
+}
+
+static PyObject *pixmeth_set(PyObject *me, PyObject *arg)
+{
+  char *tag;
+  char *phrase;
+
+  if (!PyArg_ParseTuple(arg, "s"Y":set", &tag, &phrase))
+    return (0);
+  pixie_set(PIXIE_FD(me), tag, phrase);
+  RETURN_ME;
+}
+
+static PyObject *pixmeth_cancel(PyObject *me, PyObject *arg)
+{
+  char *tag;
+
+  if (!PyArg_ParseTuple(arg, "s:cancel", &tag))
+    return (0);
+  pixie_cancel(PIXIE_FD(me), tag);
+  RETURN_ME;
+}
+
+static const PyMethodDef pixie_pymethods[] = {
+#define METHNAME(name) pixmeth_##name
+  KWMETH(read,         "P.read(TAG, [mode = PMODE_READ]) -> STRING")
+  METH (set,           "P.set(TAG, PHRASE)")
+  METH (cancel,        "P.cancel(TAG)")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject pixie_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "Pixie",                             /* @tp_name@ */
+  sizeof(pixie_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  pixie_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Pixie([socket = ?]): passphrase pixie connection.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(pixie),                    /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  pixie_pynew,                         /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+static const struct nameval consts[] = {
+  CONST(PMODE_READ), CONST(PMODE_VERIFY),
+  { 0 }
+};
+
+static PyObject *meth_ppread(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  char *tag;
+  unsigned f = PMODE_READ;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "tag", "mode", 0 };
+  char buf[1024];
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "s|O&:ppread", KWLIST,
+                                  &tag, convuint, &f))
+    goto end;
+  if (passphrase_read(tag, f, buf, sizeof(buf)))
+    SYSERR("passphrase read failed");
+  rc = BIN_FROMSTR(buf);
+end:
+  return (rc);
+}
+
+static PyObject *meth_ppcancel(PyObject *me, PyObject *arg)
+{
+  char *tag;
+
+  if (!PyArg_ParseTuple(arg, "s:ppcancel", &tag))
+    return (0);
+  passphrase_cancel(tag);
+  RETURN_NONE;
+}
+
+static PyObject *meth_getpass(PyObject *me, PyObject *arg)
+{
+  char *prompt;
+  char buf[1024];
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "s:getpass", &prompt))
+    goto end;
+  if (pixie_getpass(prompt, buf, sizeof(buf)))
+    OSERR(0);
+  rc = BIN_FROMSTR(buf);
+end:
+  return (rc);
+}
+
+static const PyMethodDef methods[] = {
+#define METHNAME(name) meth_##name
+  KWMETH(ppread,       "ppread(TAG, [mode = PMODE_READ]) -> STRING")
+  METH (ppcancel,      "ppcancel(TAG)")
+  METH (getpass,       "getpass(PROMPT) -> STRING")
+#undef METHNAME
+  { 0 }
+};
+
+void passphrase_pyinit(void)
+{
+  INITTYPE(pixie, root);
+  addmethods(methods);
+}
+
+void passphrase_pyinsert(PyObject *mod)
+{
+  INSERT("Pixie", pixie_pytype);
+  setconstants(mod, consts);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/pgen.c b/pgen.c
new file mode 100644 (file)
index 0000000..27d1f9c
--- /dev/null
+++ b/pgen.c
@@ -0,0 +1,1199 @@
+/* -*-c-*-
+ *
+ * Prime number generation
+ *
+ * (c) 2005 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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "catacomb-python.h"
+
+/*----- Filters -----------------------------------------------------------*/
+
+PyTypeObject *pfilt_pytype;
+
+static PyObject *pfilt_pywrap(pfilt *f)
+{
+  pfilt_pyobj *o = 0;
+  o = PyObject_New(pfilt_pyobj, pfilt_pytype);
+  o->f = *f;
+  o->st = pfilt_step(f, 0);
+  return ((PyObject *)o);
+}
+
+static PyObject *pfilt_pymake(PyTypeObject *ty, PyObject *xobj)
+{
+  mp *x = 0;
+  pfilt_pyobj *o = 0;
+
+  if (PFILT_PYCHECK(xobj)) RETURN_OBJ(xobj);
+  if ((x = getmp(xobj)) == 0) goto end;
+  o = (pfilt_pyobj *)ty->tp_alloc(ty, 0);
+  o->st = pfilt_create(&o->f, x);
+end:
+  mp_drop(x);
+  return ((PyObject *)o);
+}
+
+static PyObject *pfilt_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "x", 0 };
+  PyObject *xobj;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:new", KWLIST, &xobj))
+    return (0);
+  return (pfilt_pymake(ty, xobj));
+}
+
+static void pfilt_pydealloc(PyObject *me)
+  { pfilt_destroy(PFILT_F(me)); FREEOBJ(me); }
+
+static PyObject *pfmeth_step(PyObject *me, PyObject *arg)
+{
+  mpw x;
+
+  if (!PyArg_ParseTuple(arg, "O&:step", convmpw, &x)) return (0);
+  PFILT_ST(me) = pfilt_step(PFILT_F(me), x);
+  RETURN_ME;
+}
+
+static PyObject *pfmeth_muladd(PyObject *me, PyObject *arg)
+{
+  mpw m, a;
+  pfilt_pyobj *o = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&O&:muladd", convmpw, &m, convmpw, &a))
+    return (0);
+  o = PyObject_New(pfilt_pyobj, pfilt_pytype);
+  o->st = pfilt_muladd(&o->f, PFILT_F(me), m, a);
+  return ((PyObject *)o);
+}
+
+static CONVFUNC(pfilt, pfilt *, PFILT_F)
+
+static PyObject *pfmeth_jump(PyObject *me, PyObject *arg)
+{
+  pfilt *f;
+
+  if (!PyArg_ParseTuple(arg, "O&:jump", convpfilt, &f)) return (0);
+  PFILT_ST(me) = pfilt_jump(PFILT_F(me), f);
+  RETURN_ME;
+}
+
+static PyObject *pfmeth_smallfactor(PyObject *me, PyObject *arg)
+{
+  mp *x = 0;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:smallfactor", convmp, &x)) goto end;
+  rc = PyInt_FromLong(pfilt_smallfactor(x));
+end:
+  mp_drop(x);
+  return (rc);
+}
+
+static int pfilt_pynonzerop(PyObject *me)
+  { return (PFILT_ST(me) != PGEN_FAIL); }
+
+static PyObject *pfilt_pyint(PyObject *me)
+{
+  long l;
+  PyObject *rc = 0;
+
+  if (!mp_tolong_checked(PFILT_F(me)->m, &l, 0)) rc = PyInt_FromLong(l);
+  else rc = mp_topylong(PFILT_F(me)->m);
+  return (rc);
+}
+
+#ifdef PY2
+static PyObject *pfilt_pylong(PyObject *me)
+  { return (mp_topylong(PFILT_F(me)->m)); }
+#endif
+
+static PyObject *pfget_x(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(PFILT_F(me)->m))); }
+
+static PyObject *pfget_status(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(PFILT_ST(me))); }
+
+static const PyGetSetDef pfilt_pygetset[] = {
+#define GETSETNAME(op, name) pf##op##_##name
+  GET  (x,             "F.x -> current position of filter")
+  GET  (status,        "F.status -> primality status of filter")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef pfilt_pymethods[] = {
+#define METHNAME(name) pfmeth_##name
+  METH (step,          "F.step(N)")
+  METH (muladd,        "F.muladd(M, A)")
+  METH (jump,          "F.jump(FF)")
+  SMTH (smallfactor,   "smallfactor(X) -> PGST")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyNumberMethods pfilt_pynumber = {
+  0,                                   /* @nb_add@ */
+  0,                                   /* @nb_subtract@ */
+  0,                                   /* @nb_multiply@ */
+#ifdef PY2
+  0,                                   /* @nb_divide@ */
+#endif
+  0,                                   /* @nb_remainder@ */
+  0,                                   /* @nb_divmod@ */
+  0,                                   /* @nb_power@ */
+  0,                                   /* @nb_negative@ */
+  0,                                   /* @nb_positive@ */
+  0,                                   /* @nb_absolute@ */
+  pfilt_pynonzerop,                    /* @nb_nonzero@ */
+  0,                                   /* @nb_invert@ */
+  0,                                   /* @nb_lshift@ */
+  0,                                   /* @nb_rshift@ */
+  0,                                   /* @nb_and@ */
+  0,                                   /* @nb_xor@ */
+  0,                                   /* @nb_or@ */
+#ifdef PY2
+  0,                                   /* @nb_coerce@ */
+#endif
+  pfilt_pyint,                         /* @nb_int@ */
+  PY23(pfilt_pylong, 0),               /* @nb_long@ */
+  0,                                   /* @nb_float@ */
+#ifdef PY2
+  0,                                   /* @nb_oct@ */
+  0,                                   /* @nb_hex@ */
+#endif
+
+  0,                                   /* @nb_inplace_add@ */
+  0,                                   /* @nb_inplace_subtract@ */
+  0,                                   /* @nb_inplace_multiply@ */
+#ifdef PY2
+  0,                                   /* @nb_inplace_divide@ */
+#endif
+  0,                                   /* @nb_inplace_remainder@ */
+  0,                                   /* @nb_inplace_power@ */
+  0,                                   /* @nb_inplace_lshift@ */
+  0,                                   /* @nb_inplace_rshift@ */
+  0,                                   /* @nb_inplace_and@ */
+  0,                                   /* @nb_inplace_xor@ */
+  0,                                   /* @nb_inplace_or@ */
+
+  0,                                   /* @nb_floor_divide@ */
+  0,                                   /* @nb_true_divide@ */
+  0,                                   /* @nb_inplace_floor_divide@ */
+  0,                                   /* @nb_inplace_true_divide@ */
+};
+
+static const PyTypeObject pfilt_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "PrimeFilter",                       /* @tp_name@ */
+  sizeof(pfilt_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  pfilt_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  PYNUMBER(pfilt),                     /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "PrimeFilter(X): small-primes filter.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(pfilt),                    /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(pfilt),                     /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  pfilt_pynew,                         /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Rabin-Miller testing ----------------------------------------------*/
+
+typedef struct rabin_pyobj {
+  PyObject_HEAD
+  rabin r;
+} rabin_pyobj;
+
+static PyTypeObject *rabin_pytype;
+#define RABIN_R(o) (&((rabin_pyobj *)(o))->r)
+
+static PyObject *rabin_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  mp *x = 0;
+  rabin_pyobj *o = 0;
+  static const char *const kwlist[] = { "x", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convmp, &x))
+    goto end;
+  if (!MP_POSP(x) || MP_EVENP(x)) VALERR("must be positive and odd");
+  o = (rabin_pyobj *)ty->tp_alloc(ty, 0);
+  rabin_create(&o->r, x);
+end:
+  return ((PyObject *)o);
+}
+
+static void rabin_pydealloc(PyObject *me)
+{
+  rabin_destroy(RABIN_R(me));
+  FREEOBJ(me);
+}
+
+static PyObject *rmeth_test(PyObject *me, PyObject *arg)
+{
+  mp *w = 0;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:test", convmp, &w)) goto end;
+  rc = PyInt_FromLong(rabin_test(RABIN_R(me), w));
+end:
+  mp_drop(w);
+  return (rc);
+}
+
+static PyObject *rmeth_rtest(PyObject *me, PyObject *arg)
+{
+  mp *w = 0;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:rtest", convmp, &w)) goto end;
+  rc = PyInt_FromLong(rabin_rtest(RABIN_R(me), w));
+end:
+  mp_drop(w);
+  return (rc);
+}
+
+static PyObject *rget_niters(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(rabin_iters(mp_bits(RABIN_R(me)->mm.m)))); }
+
+static PyObject *rget_x(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(RABIN_R(me)->mm.m))); }
+
+static PyObject *rmeth_iters(PyObject *me, PyObject *arg)
+{
+  unsigned n;
+
+  if (!PyArg_ParseTuple(arg, "O&:iters", convuint, &n)) return (0);
+  return (PyInt_FromLong(rabin_iters(n)));
+}
+
+static const PyGetSetDef rabin_pygetset[] = {
+#define GETSETNAME(op, name) r##op##_##name
+  GET  (x,             "R.x -> number under test")
+  GET  (niters,        "R.niters -> suggested number of tests")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef rabin_pymethods[] = {
+#define METHNAME(name) rmeth_##name
+  METH (test,          "R.test(W) -> PGST")
+  METH (rtest,         "R.rtest(W) -> PGST")
+  SMTH (iters,         "iters(NBITS) -> NITERS")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject rabin_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "RabinMiller",                       /* @tp_name@ */
+  sizeof(rabin_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  rabin_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "RabinMiller(X): Rabin-Miller strong primality test.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(rabin),                    /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(rabin),                     /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  rabin_pynew,                         /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Events ------------------------------------------------------------*/
+
+typedef struct pgevent_pyobj {
+  PyObject_HEAD
+  PyObject *r;
+  pgen_event *ev;
+} pgevent_pyobj;
+
+static PyTypeObject *pgevent_pytype;
+#define PGEVENT_EV(o) (((pgevent_pyobj *)(o))->ev)
+
+static PyObject *pgevent_pywrap(pgen_event *ev)
+{
+  pgevent_pyobj *o = PyObject_New(pgevent_pyobj, pgevent_pytype);
+  o->ev = ev; o->r = 0;
+  return ((PyObject *)o);
+}
+
+static CONVFUNC(pgevent, pgen_event *, PGEVENT_EV)
+
+static void pgevent_kill(PyObject *me)
+{
+  pgevent_pyobj *ev = (pgevent_pyobj *)me;
+
+  ev->ev = 0;
+  if (ev->r) GRAND_R(ev->r) = 0;
+}
+
+static void pgevent_pydealloc(PyObject *me)
+{
+  pgevent_pyobj *ev = (pgevent_pyobj *)me;
+  Py_XDECREF(ev->r); FREEOBJ(me);
+}
+
+#define PGEVENT_CHECK(me) do {                                         \
+  if (!PGEVENT_EV(me)) {                                               \
+    PyErr_SetString(PyExc_ValueError, "event object is no longer valid"); \
+    return (0);                                                                \
+  }                                                                    \
+} while (0)
+
+static PyObject *peget_name(PyObject *me, void *hunoz)
+  { PGEVENT_CHECK(me); return (TEXT_FROMSTR(PGEVENT_EV(me)->name)); }
+
+static PyObject *peget_x(PyObject *me, void *hunoz)
+  { PGEVENT_CHECK(me); return (mp_pywrap(MP_COPY(PGEVENT_EV(me)->m))); }
+
+static PyObject *peget_steps(PyObject *me, void *hunoz)
+  { PGEVENT_CHECK(me); return (PyInt_FromLong(PGEVENT_EV(me)->steps)); }
+
+static PyObject *peget_tests(PyObject *me, void *hunoz)
+  { PGEVENT_CHECK(me); return (PyInt_FromLong(PGEVENT_EV(me)->tests)); }
+
+static PyObject *peget_rng(PyObject *me, void *hunoz)
+{
+  pgevent_pyobj *ev = (pgevent_pyobj *)me;
+
+  PGEVENT_CHECK(me);
+  if (!ev->r) ev->r = grand_pywrap(ev->ev->r, 0);
+  Py_INCREF(ev->r); return ((PyObject *)ev->r);
+}
+
+static int peset_x(PyObject *me, PyObject *xobj, void *hunoz)
+{
+  mp *x = 0;
+  pgen_event *ev = PGEVENT_EV(me);
+  int rc = -1;
+  if (!xobj) NIERR("__del__");
+  PGEVENT_CHECK(me);
+  if ((x = getmp(xobj)) == 0) goto end;
+  mp_drop(ev->m);
+  ev->m = MP_COPY(x);
+  rc = 0;
+end:
+  mp_drop(x);
+  return (rc);
+}
+
+static const PyGetSetDef pgevent_pygetset[] = {
+#define GETSETNAME(op, name) pe##op##_##name
+  GET  (name,          "EV.name -> value being generated")
+  GETSET(x,            "EV.x -> value under test")
+  GET  (steps,         "EV.steps -> number of steps left")
+  GET  (tests,         "EV.tests -> tests before passing")
+  GET  (rng,           "EV.rng -> (noncrypto) random number generator")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject pgevent_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "PrimeGenEvent",                     /* @tp_name@ */
+  sizeof(pgevent_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  pgevent_pydealloc,                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Prime-generation event.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(pgevent),                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Event handlers ----------------------------------------------------*/
+
+PyTypeObject *pgev_pytype;
+
+typedef struct pgstep_pyobj {
+  PGEV_HEAD
+  pgen_filterctx f;
+} pgstep_pyobj;
+
+static PyTypeObject *pgstep_pytype;
+#define PGSTEP_STEP(o) (((pgstep_pyobj *)(o))->f.step)
+
+typedef struct pgjump_pyobj {
+  PGEV_HEAD
+  PyObject *fobj;
+  pgen_jumpctx j;
+} pgjump_pyobj;
+
+static PyTypeObject *pgjump_pytype;
+#define PGJUMP_FOBJ(o) (((pgjump_pyobj *)(o))->fobj)
+#define PGJUMP_J(o) (&((pgjump_pyobj *)(o))->j)
+
+typedef struct pgtest_pyobj {
+  PGEV_HEAD
+  rabin r;
+} pgtest_pyobj;
+
+static PyTypeObject *pgtest_pytype;
+
+static int pgev_python(int rq, pgen_event *ev, void *p)
+{
+  pypgev *pg = p;
+  PyObject *pyev = 0;
+  PyObject *rc = 0;
+  int st = PGEN_ABORT;
+  long l;
+  static const char *const meth[] =
+    { "pg_abort", "pg_done", "pg_begin", "pg_try", "pg_fail", "pg_pass" };
+
+  rq++;
+  if (rq > N(meth)) SYSERR("event code out of range");
+  pyev = pgevent_pywrap(ev);
+  if ((rc = PyObject_CallMethod(pg->obj, (/*unconst*/ char *)meth[rq],
+                               "(O)", pyev)) == 0)
+    goto end;
+  if (rc == Py_None)
+    st = PGEN_TRY;
+  else if ((l = PyInt_AsLong(rc)) == -1 && PyErr_Occurred())
+    goto end;
+  else if (l < PGEN_ABORT || l > PGEN_PASS)
+    VALERR("return code out of range");
+  else
+    st = l;
+end:
+  if (PyErr_Occurred())
+    stash_exception(pg->exc, "exception from `pgen' handler");
+  if (pyev) {
+    pgevent_kill(pyev);
+    Py_DECREF(pyev);
+  }
+  Py_XDECREF(rc);
+  return (st);
+}
+
+static PyObject *pgev_pywrap(const pgev *pg)
+{
+  pgev_pyobj *o;
+
+  o = PyObject_New(pgev_pyobj, pgev_pytype);
+  o->pg = *pg;
+  return ((PyObject *)o);
+}
+
+int convpgev(PyObject *o, void *p)
+{
+  pypgev *pg = p;
+
+  if (PGEV_PYCHECK(o))
+    pg->ev = *PGEV_PG(o);
+  else {
+    pg->ev.proc = pgev_python;
+    pg->ev.ctx = pg;
+    pg->obj = o; Py_INCREF(o);
+  }
+  return (1);
+}
+
+void droppgev(pypgev *pg)
+{
+  if (pg->ev.proc == pgev_python)
+    { assert(pg->ev.ctx == pg); Py_DECREF(pg->obj); }
+}
+
+static PyObject *pgmeth_common(PyObject *me, PyObject *arg, int rq)
+{
+  pgen_event *ev;
+  pgev *pg = PGEV_PG(me);
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&", convpgevent, &ev)) goto end;
+  rc = PyInt_FromLong(!pg->proc ? rq : pg->proc(rq, ev, pg->ctx));
+end:
+  return (rc);
+}
+
+#define PGMETH(lc, uc)                                                 \
+  static PyObject *pgmeth_pg_##lc(PyObject *me, PyObject *arg)         \
+    { return pgmeth_common(me, arg, PGEN_##uc); }
+PGMETH(abort,  ABORT)
+PGMETH(done,   DONE)
+PGMETH(begin,  BEGIN)
+PGMETH(try,    TRY)
+PGMETH(pass,   PASS)
+PGMETH(fail,   FAIL)
+#undef PGMETH
+
+static PyObject *pgev_stdev(pgen_proc *proc)
+  { pgev pg; pg.proc = proc; pg.ctx = 0; return (pgev_pywrap(&pg)); }
+
+static const PyMethodDef pgev_pymethods[] = {
+#define METHNAME(name) pgmeth_##name
+  METH (pg_abort,      "E.pg_abort(EV) -> PGST -- prime generation aborted")
+  METH (pg_done,       "E.pg_done(EV) -> PGST -- prime generation finished")
+  METH (pg_begin,     "E.pg_begin(EV) -> PGST -- commence stepping/testing")
+  METH (pg_try,        "E.pg_try(EV) -> PGST -- found new candidate")
+  METH (pg_pass,       "E.pg_pass(EV) -> PGST -- passed primality test")
+  METH (pg_fail,       "E.pg_fail(EV) -> PGST -- failed primality test")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject pgev_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "PrimeGenBuiltinHandler",            /* @tp_name@ */
+  sizeof(pgev_pyobj),                  /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Built-in prime-generation event handler, base class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(pgev),                     /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *pgstep_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  mpw s;
+  pgstep_pyobj *rc = 0;
+  static const char *const kwlist[] = { "step", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST, convmpw, &s))
+    goto end;
+  rc = (pgstep_pyobj *)ty->tp_alloc(ty, 0);
+  rc->f.step = s;
+  rc->pg.proc = pgen_filter;
+  rc->pg.ctx = &rc->f;
+end:
+  return ((PyObject *)rc);
+}
+
+static PyObject *psget_step(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(PGSTEP_STEP(me))); }
+
+static const PyGetSetDef pgstep_pygetset[] = {
+#define GETSETNAME(op, name) ps##op##_##name
+  GET  (step,          "S.step -> step size for the stepper")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject pgstep_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "PrimeGenStepper",                   /* @tp_name@ */
+  sizeof(pgstep_pyobj),                        /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "PrimeGenStepper(STEP): simple stepper with small-factors filter.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(pgstep),                    /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  pgstep_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *pgjump_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  PyObject *o, *fobj;
+  pgjump_pyobj *rc = 0;
+  static const char *const kwlist[] = { "jump", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O:new", KWLIST, &o) ||
+      (fobj = pfilt_pymake(pfilt_pytype, o)) == 0)
+    goto end;
+  rc = (pgjump_pyobj *)ty->tp_alloc(ty, 0);
+  rc->fobj = fobj;
+  rc->j.j = PFILT_F(fobj);
+  rc->pg.proc = pgen_jump;
+  rc->pg.ctx = &rc->j;
+end:
+  return ((PyObject *)rc);
+}
+
+static void pgjump_pydealloc(PyObject *me)
+{
+  Py_DECREF(PGJUMP_FOBJ(me));
+  FREEOBJ(me);
+}
+
+static PyObject *pjget_jump(PyObject *me, void *hunoz)
+  { RETURN_OBJ(PGJUMP_FOBJ(me)); }
+
+static const PyGetSetDef pgjump_pygetset[] = {
+#define GETSETNAME(op, name) pj##op##_##name
+  GET  (jump,          "S.jump -> jump size for the stepper")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject pgjump_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "PrimeGenJumper",                    /* @tp_name@ */
+  sizeof(pgjump_pyobj),                        /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  pgjump_pydealloc,                    /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "PrimeGenJumper(JUMP): "
+                      "stepper for larger steps with small-factors filter.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(pgjump),                    /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  pgjump_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *pgtest_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  pgtest_pyobj *rc = 0;
+  static const char *const kwlist[] = { 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST)) goto end;
+  rc = (pgtest_pyobj *)ty->tp_alloc(ty, 0);
+  rc->pg.proc = pgen_test;
+  rc->pg.ctx = &rc->r;
+end:
+  return ((PyObject *)rc);
+}
+
+static const PyTypeObject pgtest_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "PrimeGenTester",                    /* @tp_name@ */
+  sizeof(pgtest_pyobj),                        /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "PrimeGenTester(): Rabin-Miller tester.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  pgtest_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Prime iteration ---------------------------------------------------*/
+
+static PyTypeObject *piter_pytype;
+
+typedef struct piter_pyobj {
+  PyObject_HEAD
+  primeiter i;
+} piter_pyobj;
+#define PITER_I(o) (&((piter_pyobj *)(o))->i)
+
+static PyObject *piter_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  piter_pyobj *rc;
+  mp *n = 0;
+  static const char *const kwlist[] = { "start", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", KWLIST, convmp, &n))
+    return (0);
+  rc = (piter_pyobj *)ty->tp_alloc(ty, 0);
+  primeiter_create(&rc->i, n);
+  return ((PyObject *)rc);
+}
+
+static void piter_pydealloc(PyObject *me)
+  { primeiter_destroy(PITER_I(me)); FREEOBJ(me); }
+
+static PyObject *piter_pynext(PyObject *me)
+  { return (mp_pywrap(primeiter_next(PITER_I(me), 0))); }
+
+static const PyTypeObject piter_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "PrimeIter",                         /* @tp_name@ */
+  sizeof(piter_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  piter_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "PrimeIter([start = N]): Prime-number iterator.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  PyObject_SelfIter,                   /* @tp_iter@ */
+  piter_pynext,                                /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  piter_pynew,                         /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Prime generation functions ----------------------------------------*/
+
+void pgenerr(struct excinfo *exc)
+{
+  if (exc->ty) RESTORE_EXCINFO(exc);
+  else PyErr_SetString(PyExc_ValueError, "prime generation failed");
+}
+
+static PyObject *meth_pgen(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  mp *x = 0;
+  mp *r = 0;
+  PyObject *rc = 0;
+  char *p = "p";
+  pgen_filterctx fc = { 2 };
+  rabin tc;
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev step = { { 0 } }, test = { { 0 } }, evt = { { 0 } };
+  unsigned nsteps = 0, ntests = 0;
+  static const char *const kwlist[] =
+    { "start", "name", "stepper", "tester", "event", "nsteps", "ntests", 0 };
+
+  step.exc = &exc; step.ev.proc = pgen_filter; step.ev.ctx = &fc;
+  test.exc = &exc; test.ev.proc = pgen_test; test.ev.ctx = &tc;
+  evt.exc = &exc;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|sO&O&O&O&O&:pgen", KWLIST,
+                                  convmp, &x, &p, convpgev, &step,
+                                  convpgev, &test, convpgev, &evt,
+                                  convuint, &nsteps, convuint, &ntests))
+    goto end;
+  if (!ntests) ntests = rabin_iters(mp_bits(x));
+  if ((r = pgen(p, MP_NEW, x, evt.ev.proc, evt.ev.ctx,
+               nsteps, step.ev.proc, step.ev.ctx,
+               ntests, test.ev.proc, test.ev.ctx)) == 0)
+    PGENERR(&exc);
+  rc = mp_pywrap(r); r = 0;
+end:
+  mp_drop(r); mp_drop(x);
+  droppgev(&step); droppgev(&test); droppgev(&evt);
+  return (rc);
+}
+
+static PyObject *meth_strongprime_setup(PyObject *me,
+                                       PyObject *arg, PyObject *kw)
+{
+  mp *x = 0;
+  pfilt f;
+  grand *r = &rand_global;
+  unsigned nbits;
+  char *name = "p";
+  unsigned n = 0;
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
+  PyObject *rc = 0;
+  static const char *const kwlist[] =
+    { "nbits", "name", "event", "rng", "nsteps", 0 };
+
+  evt.exc = &exc;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|sO&O&O&", KWLIST,
+                                  convuint, &nbits, &name,
+                                  convpgev, &evt, convgrand, &r,
+                                  convuint, &n))
+    goto end;
+  if ((x = strongprime_setup(name, MP_NEW, &f, nbits,
+                            r, n, evt.ev.proc, evt.ev.ctx)) == 0)
+    PGENERR(&exc);
+  rc = Py_BuildValue("(NN)", mp_pywrap(x), pfilt_pywrap(&f));
+  x = 0;
+end:
+  mp_drop(x);
+  droppgev(&evt);
+  return (rc);
+}
+
+static PyObject *meth_strongprime(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  mp *x = 0;
+  grand *r = &rand_global;
+  unsigned nbits;
+  char *name = "p";
+  unsigned n = 0;
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
+  PyObject *rc = 0;
+  static const char *const kwlist[] =
+    { "nbits", "name", "event", "rng", "nsteps", 0 };
+
+  evt.exc = &exc;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|sO&O&O&", KWLIST,
+                                  convuint, &nbits, &name,
+                                  convpgev, &evt, convgrand, &r,
+                                  convuint, &n))
+    goto end;
+  if ((x = strongprime(name, MP_NEW, nbits,
+                      r, n, evt.ev.proc, evt.ev.ctx)) == 0)
+    PGENERR(&exc);
+  rc = mp_pywrap(x);
+  x = 0;
+end:
+  mp_drop(x);
+  droppgev(&evt);
+  return (rc);
+}
+
+static PyObject *meth_limlee(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  char *p = "p";
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev ie = { { 0 } }, oe = { { 0 } };
+  unsigned ql, pl;
+  grand *r = &rand_global;
+  unsigned on = 0;
+  size_t i, nf = 0;
+  PyObject *rc = 0, *vec;
+  static const char *const kwlist[] =
+    { "pbits", "qbits", "name", "event", "ievent", "rng", "nsteps", 0 };
+  mp *x = 0, **v = 0;
+
+  ie.exc = oe.exc = &exc;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|sO&O&O&O&:limlee", KWLIST,
+                                  convuint, &pl, convuint, &ql,
+                                  &p, convpgev, &oe, convpgev, &ie,
+                                  convgrand, &r, convuint, &on))
+    goto end;
+  if ((x = limlee(p, MP_NEW, MP_NEW, ql, pl, r, on,
+                 oe.ev.proc, oe.ev.ctx, ie.ev.proc, ie.ev.ctx,
+                 &nf, &v)) == 0)
+    PGENERR(&exc);;
+  vec = PyList_New(nf);
+  for (i = 0; i < nf; i++)
+    PyList_SET_ITEM(vec, i, mp_pywrap(v[i]));
+  xfree(v);
+  rc = Py_BuildValue("(NN)", mp_pywrap(x), vec);
+end:
+  droppgev(&oe); droppgev(&ie);
+  return (rc);
+}
+
+/*----- Global stuff ------------------------------------------------------*/
+
+static const struct nameval consts[] = {
+  CONST(PGEN_PASS), CONST(PGEN_FAIL), CONST(PGEN_BEGIN), CONST(PGEN_TRY),
+  CONST(PGEN_DONE), CONST(PGEN_ABORT),
+  { 0 }
+};
+
+static const PyMethodDef methods[] = {
+#define METHNAME(name) meth_##name
+  KWMETH(pgen,
+       "pgen(START, [name = 'p'], [stepper = PrimeGenStepper(2)],\n"
+       "     [tester = PrimeGenTester()], [event = pgen_nullev],\n"
+       "     [nsteps = 0], [ntests = RabinMiller.iters(START.nbits)]) -> P")
+  KWMETH(strongprime_setup,
+       "strongprime_setup(NBITS, [name = 'p'], [event = pgen_nullev],\n"
+       "                 [rng = rand], [nsteps = 0]) -> (START, JUMP)")
+  KWMETH(strongprime,
+       "strongprime(NBITS, [name = 'p'], [event = pgen_nullev],\n"
+       "           [rng = rand], [nsteps = 0]) -> P")
+  KWMETH(limlee,
+       "limlee(PBITS, QBITS, [name = 'p'], [event = pgen_nullev],\n"
+       "       [ievent = pgen_nullev], [rng = rand], [nsteps = 0]) "
+                                                         "-> (P, [Q, ...])")
+#undef METHNAME
+  { 0 }
+};
+
+void pgen_pyinit(void)
+{
+  INITTYPE(pfilt, root);
+  INITTYPE(rabin, root);
+  INITTYPE(pgevent, root);
+  INITTYPE(pgev, root);
+  INITTYPE(pgstep, pgev);
+  INITTYPE(pgjump, pgev);
+  INITTYPE(pgtest, pgev);
+  INITTYPE(piter, root);
+  addmethods(methods);
+}
+
+static PyObject *obj;
+
+void pgen_pyinsert(PyObject *mod)
+{
+  INSERT("PrimeFilter", pfilt_pytype);
+  INSERT("RabinMiller", rabin_pytype);
+  INSERT("PrimeGenEvent", pgevent_pytype);
+  INSERT("PrimeGenBuiltinHandler", pgev_pytype);
+  INSERT("PrimeGenStepper", pgstep_pytype);
+  INSERT("PrimeGenJumper", pgjump_pytype);
+  INSERT("PrimeGenTester", pgtest_pytype);
+  INSERT("pgen_nullev", obj = pgev_stdev(0));
+  INSERT("pgen_stdev", pgev_stdev(pgen_ev));
+  INSERT("pgen_spinev", pgev_stdev(pgen_evspin));
+  INSERT("pgen_subev", pgev_stdev(pgen_subev));
+  INSERT("PrimeIter", piter_pytype);
+  setconstants(mod, consts);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/pock b/pock
new file mode 100644 (file)
index 0000000..5d7cc50
--- /dev/null
+++ b/pock
@@ -0,0 +1,1072 @@
+#! /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.
+
+import sys as SYS; 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
+
+if SYS.version_info >= (3,):
+  xrange = range
+  range = lambda *args: list(xrange(*args))
+
+###--------------------------------------------------------------------------
+### Utilities.
+
+def _excval():
+  """Return the most recent exception object."""
+  return SYS.exc_info()[1]
+
+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."""
+  return C.MPMul().factor(one).factor(ff).done()
+
+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.
+  try: _MAX = SYS.maxint
+  except AttributeError: _MAX = SYS.maxsize
+  _NBIT = 15
+  while 1 << (_NBIT + 1) < _MAX: _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 = sievesz*[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 ExpectedError('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:
+        raise ExpectedError('%s:%d: %s' %
+                            (file.name, lno, _excval().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] [-s SIEVEBITS] 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: exit('%s: %s' % (prog, _excval().message))
+  except IOError: exit('%s: %s' % (prog, _excval()))
+
+###----- That's all, folks --------------------------------------------------
diff --git a/pock.1 b/pock.1
new file mode 100644 (file)
index 0000000..6879bc5
--- /dev/null
+++ b/pock.1
@@ -0,0 +1,853 @@
+.\" -*-nroff-*-
+.\"
+.\" Describe the primality certificate generator and checker
+.\"
+.\" (c) 2016 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.
+.
+.ie t \{\
+.  if \n(.g \{\
+.    fam P
+.  \}
+.  ds o \(bu
+.  ds ss \s7\v'-4p'
+.  ds se \v'4p'\s0
+.  ds us \s7\v'2p'
+.  ds ue \v'-2p'\s0
+.  ds *e \(*e
+.  ds mo \(mo
+.  ds sr \(sr
+.  ds cu \(cu
+.  ds ca \(ca
+.  ds iy \(if
+.  ds == \(==
+.  ds .. \&.\h'2p'.\h'2p'.\&
+.  ds /= \h'(\w'\*(=='-\w'/')/2'/\h'-(\w'\*(=='+\w'/')/2'\*(==
+.\}
+.el \{\
+.  ds o o
+.  ds ss ^
+.  ds se
+.  ds us _
+.  ds ue
+.  ds *e \fIepsilon\fP
+.  ds mo in
+.  ds sr sqrt
+.  ds cu union
+.  ds ca isect
+.  ds iy infty
+.  ds == ==
+.  ds .. \&...\&
+.  ds /= /==
+.\}
+.de hP
+.IP
+\h'-\w'\fB\\$1\ \fP'u'\fB\\$1\ \fP\c
+..
+.
+.TH pock 1 "28 May 2017" "Straylight/Edgeware" "Catacomb cryptographic library"
+.SH NAME
+pock \- generate and verify primality certificates
+.
+.\"--------------------------------------------------------------------------
+.SH SYNOPSIS
+.
+.B pock
+.RB [ \-qv ]
+.I command
+.IR [ arguments ...]
+.PP
+Subcommands:
+.RS
+.br
+.B gen
+.I nbits
+.br
+.B ll
+.I nbits
+.I nsubbits
+.br
+.B check
+.RI [ file ]
+.RE
+.
+.\"--------------------------------------------------------------------------
+.SH DESCRIPTION
+.
+Many cryptographic protocols make use of large prime numbers.
+The usual way of determining primality in such circumstances
+is due to Rabin and Miller.
+Given a candidate
+.I n
+and a
+.I witness
+2 \(<=
+.I a
+<
+.IR n ,
+the test answers either `composite' or `unknown'.
+If
+.I n
+is prime then the test answers `unknown' for every witness
+.IR a ;
+if
+.I n
+is in fact composite
+then the test answers `composite'
+for at least three quarters of the possible witnesses.
+If
+.I n
+is a composite,
+then the witnesses
+.I a
+for which the test reports `unknown'
+are called
+.IR liars .
+.PP
+Naively, then,
+to reduce the probability of falsely accepting a composite
+below some bound \*(*e,
+one must perform
+\-(log\*(us2\*(ue \*(*e)/2
+iterations of the test,
+with independent, uniformly distributed witnesses.
+This is especially slow when high security levels are wanted,
+both because tests take longer on larger candidate numbers,
+and because one must do more tests
+to reach the necessary higher confidence level.
+.PP
+The above is a worst-case bound:
+very few composite numbers
+.I n
+have anywhere near
+.IR n /4
+liars.
+In situations such as RSA key generation,
+the user generating the prime number is the only one
+who must be convinced of the number's primality,
+and they have valuable additional knowledge:
+specifically that the candidate has been chosen at random
+according to some suitable probability distribution.
+In this case, one needs many fewer iterations,
+and the number of iterations needed
+.I decreases
+with the size of the candidate being tested.
+.PP
+But in cases where many users must share some large prime parameter,
+each of them must test the proposed prime separately,
+and often they must pessimistically assume
+that the number was chosen maliciously,
+and the worst-case
+.IR n /4
+bound is the best one can do using the Rabin\(enMiller test.
+For large candidates,
+this is inconveniently slow.
+(Also, many implementations incorrectly use
+a number of iterations suitable for randomly chosen primes
+for testing candidates of unknown provenance.)
+.PP
+There
+.I are
+stronger probabilistic tests.
+A combination of Rabin\(enMiller and
+Grantham's Frobenius test
+is known as the
+Baillie\(enPSW test
+(after Baillie, Pomerance, Selfridge, and Wagstaff);
+there are
+.I no
+known composites which pass this test,
+nor is it known how to construct any.
+On the other hand, it's been conjectured that
+infinitely many Baillie\(enPSW pseudoprimes exist.
+While it may be reasonable to assume
+the strength of the Baillie\(enPSW test,
+it must be borne in mind that this
+.I does
+constitute a security assumption.
+.PP
+By contrast,the
+.B pock
+program will generate prime numbers
+of sizes suitable for use in cryptography,
+along with a
+.I certificate of primality
+which can be independently verified fairly efficiently.
+Specifically, verifying a proof takes a little longer
+than a single iteration of the Rabin\(enMiller probabilistic test.
+It can also verify such certificates.
+.PP
+Note that the primes selected by
+.B pock
+are a long way from uniformly distributed.
+Indeed, they have somewhat unusual structure,
+but it seems unlikely that this structure
+renders them unsuitable for e.g., discrete-logarithm cryptography.
+.
+.SS "Command line"
+The following options are recognized.
+.TP
+.B "\-h, \-\-help"
+Write help text to standard output and exit with status 0.
+.TP
+.B "\-q, \-\-quiet"
+Be less verbose during prime generation or certificate verification.
+.TP
+.B "\-v, \-\-verbose"
+Be more verbose during prime generation or certificate verification.
+.TP
+.BI "\-s, \-\-sievebits " b
+When generating certificates,
+require that the verifier can check numbers smaller than
+.RI 2\*(ss b \*(se
+without assistance.
+Larger values lead to sightly shorter proofs,
+but spend more time at startup constructing a sieve;
+smaller values lead to somewhat longer proofs,
+but spend less time constructing the sieve.
+The default is 32,
+which seems to work well in practice.
+.
+.SS "gen"
+The
+.B gen
+command generates a prime
+.I p
+which is
+.I nbits
+long (i.e.,
+.RI 2\*(ss nbits \-1\*(se
+<
+.I p
+<
+.RI 2\*(ss nbits \*(se,
+and writes a certificate to standard output.
+By default, mysterious runes are written to standard error
+to keep the user amused and informed about the operation's progress;
+the
+.B \-q
+option suppresses the runes;
+the
+.B \-v
+option produces more detailed runes.
+.
+.SS "ll"
+The
+.B ll
+command generates a Lim\(enLee prime
+.I p
+=
+2
+.IR q \*(us0\*(ue
+.IR q \*(us1\*(ue
+\*(..
+.IR q \*(us k \*(ue
++
+1
+which is
+.I nbits
+long (i.e.,
+.RI 2\*(ss nbits \-1\*(se
+<
+.I p
+<
+.RI 2\*(ss nbits \*(se,
+such that each
+.IR q \*(us i \*(ue
+is an
+.IR nsubbits -bit
+prime, for
+0 \(<=
+.I i
+<
+.IR k ,
+and
+.IR q \*(us k \*(ue
+is an
+.IR nsubbits -bit
+prime,
+and writes a certificate to standard output.
+By default, mysterious runes are written to standard error
+to keep the user amused and informed about the operation's progress;
+the
+.B \-q
+option suppresses the runes;
+the
+.B \-v
+option produces more detailed runes.
+.
+.SS "check"
+The
+.B check
+command reads a primality certificate from the named
+.I file
+or from standard input,
+and checks it.
+By default,
+each
+.B check
+line in the certificate causes a line
+.IP
+.B ;;
+.I label
+.B =
+.I n
+.BI [ b ]
+.PP
+to be printed to standard output,
+listing the prime's
+.IR label ,
+value
+.IR n ,
+and length
+.I b
+in bits;
+this behaviour is inhibited by the
+.B \-q
+option.
+.
+.SS Runes
+The following mysterious runes are printed during prime searches.
+This information is provided for diagnostic purposes
+and to satisfy idle curiosity:
+later versions may print different runes,
+or use the same rune characters to mean different things.
+.TP
+.B !
+Started to generate a large prime.
+The next step is to generate a number of smaller primes.
+Usually, this will only need to be done once.
+.TP
+.B .
+Candidate failed a Fermat test.
+.TP
+.B *
+Candidate passed a Fermat test.
+This is usually the last rune for a prime search.
+.TP
+.B @
+A candidate generator failed to generate the necessary subgroup.
+This is unusual.
+.TP
+.B <
+For Lim\(enLee primes,
+discarding the large prime
+because it produces results which are too small.
+.TP
+.B >
+For Lim\(enLee primes,
+discarding the large prime
+because it produces results which are too large.
+.TP
+.B [
+Starting a subsidiary prime search.
+.TP
+.B ]
+Finished a subsidiary prime search.
+.
+.\"--------------------------------------------------------------------------
+.SH CERTIFICATE FORMAT
+.
+A certificate consists of a number of lines.
+Blank lines,
+and lines beginning with a
+.RB ` ; ',
+are ignored.
+Other lines are as follows.
+.TP
+.BI "sievebits " b
+Declares that the verifier is expected to be able to check
+primes smaller than
+.RI 2\*(ss b \*(se
+for primality for itself.
+A
+.B sievebits
+line must appear before the first
+.B small
+line.
+.TP
+.BI "small " label " = " p
+Asserts that
+.I p
+is a small prime,
+and associates it with the
+.IR label .
+It is an error if the label has been assigned by a previous line.
+It is required that
+1 <
+.I p
+<
+.RI 2\*(ss b \*(se
+and that
+.I p
+is prime.
+Such small primes constitute the leaves of a proof tree.
+.TP
+.BI "pock " label " = " a ", " R ", [" i ", " j ", \fR\*(..\fB]"
+Asserts that a number
+.I n
+(defined below) is prime by Pocklington's criterion,
+and associates it with the
+.IR label .
+It is an error if the label has been assigned by a previous line.
+.RS
+.PP
+The strings
+.IR i ,
+.IR j ,
+\*(..
+must be labels assigned by earlier lines.
+For each such label
+.IR i ,
+let
+.IR q \*(us i \*(ue
+be the associated prime.
+Let
+.I Q
+=
+.IR q \*(us i \*(ue
+.IR q \*(us j \*(ue
+\*(..
+be the product of these primes.
+Let
+.I n
+=
+.RI 2 QR
++
+1.
+It is required that:
+.hP 1.
+The
+.IR q \*(us i \*(ue
+are distinct.
+.hP 2.
+.IR Q \*(ss2\*(se
+\(>=
+.IR n .
+.hP 3.
+.IR a \*(ss n \-1\*(se
+\*(==
+1
+(mod
+.IR n ).
+.hP 4.
+.RI gcd( a \*(ss( n \-1)/ q \*(se
+\-
+1,
+.IR n )
+=
+1
+for each prime
+.IR q
+dividing
+.IR Q .
+.PP
+To see why this works, let
+.I p
+be the smallest prime factor of
+.IR n .
+From
+.B 3
+we see that
+the order of
+.I a
+in
+.RB ( Z /\fIp Z )\*(ss\(**\*(se
+divides
+.I n
+\-
+1.
+Consider some prime
+.I q
+dividing
+.I Q
+and let
+.I t
+=
+.IR a \*(ss( n \-1)/ q \*(se;
+then
+.I t
+has order dividing
+.IR q .
+From
+.BR 4 ,
+we have
+.I t
+\*(/=
+1
+(mod
+.IR p ),
+and hence
+.I t
+has order precisely
+.I q
+in
+.RB ( Z /\fIp Z )\*(ss\(**\*(se.
+This implies that
+.I q
+divides
+.I p
+\-
+1.
+Since this holds for each prime
+.I q
+dividing
+.IR Q ,
+and,
+from
+.BR 1 ,
+these primes are distinct,
+we deduce that
+.I Q
+divides
+.I p
+\-
+1
+and hence that
+.I p
+>
+.IR Q .
+Let
+.IR p \(fm
+be any prime factor of
+.IR n / p .
+Then
+.IR p \(fm
+\(>=
+.I p
+>
+.I Q
+so,
+from
+.BR 2 ,
+.IR pp \(fm
+>
+.IR Q \*(ss2\*(se
+\(>=
+.IR n ;
+but
+.IR pp \(fm
+divides
+.I n
+so this is a contradiction.
+We must conclude that
+.IR p \(fm
+does not exist,
+and
+.I n
+must be prime.
+.RE
+.TP
+.BI "ecpp " label " = " n ", " A ", " B ", " x ", " y ", [" i ", " j ", \fR\*(..\fB]"
+Asserts that the number
+.I n
+is prime by Goldwasser and Kilian's ECPP method,
+and associates it with the
+.IR label .
+It is an error if the label has been assigned by a previous line.
+.RS
+.PP
+The strings
+.IR i ,
+.IR j ,
+\*..
+must be labels assigned by earlier lines.
+For each such label
+.IR i ,
+let
+.IR q \*(us i \*(ue
+be the associated prime.
+Let
+.I Q
+=
+.IR q \*(us i \*(ue
+.IR q \*(us j \*(ue
+\*(..
+be the product of these primes.
+Define
+.I E\*(usn\*(ue
+= {
+.RI ( x ", " y )
+\*(mo
+.RB ( Z /\fIn Z )\*(ss2\*(se
+|
+.IR y \*(ss2\*(se
+=
+.IR x \*(ss3\*(se
++
+.I Ax
++
+.I B
+}
+\*(cu
+{ \*(iy };
+if
+.I n
+is prime and the curve is not singular
+then this is the elliptic curve over
+.RI GF( n )
+with short-Weierstrass coefficients
+.I A
+and
+.IR B .
+Let
+.I R
+=
+.RI ( x ,
+.IR y ).
+It is required that:
+.hP 1.
+.I g
+=
+.RI gcd(4 a \*(ss3\*(se
++
+.RI 27 b \*(ss2\*(se,
+.IR n )
+= 1.
+.hP 2.
+.I R
+\*(mo
+.IR E\*(usn\*(ue ;
+i.e.,
+.IR y \*(ss2\*(se
+\*(==
+.IR x \*(ss3\*(se
++
+.I Ax
++
+.I B
+(mod
+.IR n ).
+.hP 3. The
+.I q\*(usi\*(ue
+are distinct.
+.hP 4.
+.IR QR ,
+the elliptic-curve scalar product of the point
+.I R
+by the integer
+.IR Q ,
+calculated as if
+.I E
+is a true elliptic curve,
+is the point at infinity.
+.hP 5.
+.RI ( Q / q ) R
+is finite for each prime
+.I q
+dividing
+.IR Q .
+.hP 6.
+.I Q
+>
+.RI ( n \*(ss1/4\*(se
++ 1)\*(ss2\*(se.
+.PP
+To see why this test works, let
+.I p
+be the smallest prime factor of
+.IR n ,
+and let
+.I E\*(usp\*(ue
+= {
+.RI ( x ", " y )
+\*(mo
+.RI GF( p )\*(ss2\*(se
+|
+.IR y \*(ss2\*(se
+=
+.IR x \*(ss3\*(se
++
+.I Ax
++
+.I B
+}
+\*(cu
+{ \*(iy }.
+From
+.BR 1 ,
+.I g
+= 1,
+.I E\*(usp\*(ue
+is an elliptic curve.
+(If 1 <
+.I g
+<
+.I n
+then
+.I g
+is a proper factor of
+.I n
+and
+.I n
+is certainly not prime;
+if
+.I g
+=
+.I n
+then the curve will be singular and the test fails.)
+From
+.BR 2 ,
+.I R
+is a point on
+.IR E\*(usp\*(ue .
+From
+.BR 4 ,
+.I R
+has
+.IR Q -torsion
+in
+.IR E\*(usp\*(ue .
+Consider some prime
+.I q
+dividing
+.I Q
+and let
+.I T
+=
+.RI ( Q/q ) R ;
+then
+.I T
+has torsion dividing
+.IR q .
+From
+.BR 5 ,
+.RI ( Q/q ) R
+\(!= 0,
+and hence
+.I T
+has order precisely
+.I q
+in
+.IR E\*(usp\*(ue .
+This implies that
+.I q
+divides
+.RI # E\*(usp\*(ue .
+Since this holds for each prime
+.I q
+dividing
+.IR Q ,
+and, from
+.BR 3 ,
+the
+.I q
+are distinct,
+we deduce that
+.I Q
+divides
+.RI # E\*(usp\*(ue
+and hence that
+.RI # E\*(usp\*(ue
+\(>=
+.IR Q .
+Hasse's theorem tells us that
+.RI | p
++ 1 \-
+.RI # E\*(usp\*(ue |
+\(<=
+.RI 2\*(sr p ,
+so, in particular,
+.RI # E\*(usp\*(ue
+\-
+.I p
+\- 1
+\(<=
+.RI 2\*(sr p ,
+whence
+.I p
++ 1 +
+.RI 2\*(sr p
+=
+.RI (\*(sr p
++ 1)\*(ss2\*(se
+\(>=
+.RI # E\*(usp\*(ue
+\(>=
+.IR Q
+>
+.RI ( n \*(ss1/4\*(se
++ 1)\*(ss2\*(se
+(from
+.BR 6 );
+so
+.IR p\*(ss2\*(se
+>
+.IR n .
+Let
+.IR p \(fm
+be any prime factor of
+.IR n / p .
+Then
+.IR p \(fm
+\(>=
+.I p
+and
+.IR pp \(fm
+\(>=
+.IR p \*(ss2\*(se
+>
+.IR n ;
+but
+.IR pp \(fm
+divides
+.I n
+so this is a contradiction.
+We must conclude that
+.IR p \(fm
+does not exist,
+and
+.I n
+must be prime.
+.PP
+Note that
+.B pock
+currently cannot generate proofs which use
+.BR ecpp ,
+though it will verify them.
+.RE
+.TP
+.BI "check " label ", " b ", " p
+Verify that the prime associated with the
+.I label
+is equal to
+.I p
+and that it is
+.I b
+bits long;
+i.e., that
+.RI 2\*(ss b \-1\*(se
+\(<=
+.I p
+<
+.RI 2\*(ss b \*(se.
+Unless
+inhibited by
+.BR \-q ,
+the label and value are printed to stdout during verification.
+.
+.\"--------------------------------------------------------------------------
+.SH "SEE ALSO"
+.BR key (1).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
+.
+.\"----- That's all, folks --------------------------------------------------
diff --git a/pubkey.c b/pubkey.c
new file mode 100644 (file)
index 0000000..9ec115a
--- /dev/null
+++ b/pubkey.c
@@ -0,0 +1,1306 @@
+/* -*-c-*-
+ *
+ * Public-key cryptography
+ *
+ * (c) 2004 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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "catacomb-python.h"
+
+/*----- DSA and similar ---------------------------------------------------*/
+
+typedef struct dsa_pyobj {
+  PyObject_HEAD
+  PyObject *G, *u, *p, *rng, *hash;
+  gdsa d;
+} dsa_pyobj;
+
+static PyTypeObject *dsapub_pytype, *dsapriv_pytype;
+static PyTypeObject *kcdsapub_pytype, *kcdsapriv_pytype;
+#define DSA_D(o) (&((dsa_pyobj *)(o))->d)
+#define DSA_G(o) (((dsa_pyobj *)(o))->G)
+#define DSA_U(o) (((dsa_pyobj *)(o))->u)
+#define DSA_P(o) (((dsa_pyobj *)(o))->p)
+#define DSA_RNG(o) (((dsa_pyobj *)(o))->rng)
+#define DSA_HASH(o) (((dsa_pyobj *)(o))->hash)
+
+static void dsa_pydealloc(PyObject *me)
+{
+  dsa_pyobj *g = (dsa_pyobj *)me;
+  Py_DECREF(g->G); Py_DECREF(g->u); Py_DECREF(g->p);
+  Py_DECREF(g->rng); Py_DECREF(g->hash);
+  FREEOBJ(me);
+}
+
+static PyObject *dsa_setup(PyTypeObject *ty, PyObject *G, PyObject *u,
+                          PyObject *p, PyObject *rng, PyObject *hash,
+                          void (*calcpub)(group *, ge *, mp *))
+{
+  dsa_pyobj *g;
+  ge *pp;
+
+  g = PyObject_New(dsa_pyobj, ty);
+  if (p) Py_INCREF(p);
+  if (!u) {
+    g->d.u = 0;
+    u = Py_None;
+  } else {
+    if ((g->d.u = getmp(u)) == 0)
+      goto end;
+    if (MP_PYCHECK(u)) Py_INCREF(u);
+    else u = mp_pywrap(g->d.u);
+  }
+  if (!p) {
+    assert(g->d.u); assert(calcpub);
+    pp = G_CREATE(GROUP_G(G));
+    calcpub(GROUP_G(G), pp, g->d.u);
+    p = ge_pywrap(G, pp);
+  } else if (GROUP_G(G) != GE_G(p) && !group_samep(GROUP_G(G), GE_G(p)))
+    TYERR("public key not from group");
+  g->d.g = GROUP_G(G);
+  g->d.p = GE_X(p);
+  g->d.r = GRAND_R(rng);
+  g->d.h = GCHASH_CH(hash);
+  g->G = G; Py_INCREF(G); g->u = u; g->p = p;
+  g->rng = rng; Py_INCREF(rng); g->hash = hash; Py_INCREF(hash);
+  return ((PyObject *)g);
+end:
+  Py_XDECREF(p); FREEOBJ(g);
+  return (0);
+}
+
+static PyObject *dsapub_pynew(PyTypeObject *ty,
+                             PyObject *arg, PyObject *kw)
+{
+  PyObject *G, *p, *rng = rand_pyobj, *hash = sha_pyobj;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "G", "p", "hash", "rng", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O!|O!O!:new", KWLIST,
+                                  group_pytype, &G,
+                                  ge_pytype, &p,
+                                  gchash_pytype, &hash,
+                                  grand_pytype, &rng) ||
+      (rc = dsa_setup(dsapub_pytype, G, 0, p, rng, hash, 0)) == 0)
+    goto end;
+end:
+  return (rc);
+}
+
+static PyObject *dsameth_beginhash(PyObject *me)
+  { return (ghash_pywrap(DSA_HASH(me), gdsa_beginhash(DSA_D(me)))); }
+
+static PyObject *dsameth_endhash(PyObject *me, PyObject *arg)
+{
+  ghash *h;
+  PyObject *rc;
+  if (!PyArg_ParseTuple(arg, "O&:endhash", convghash, &h)) return (0);
+  gdsa_endhash(DSA_D(me), h);
+  h = GH_COPY(h);
+  rc = bytestring_pywrap(0, GH_CLASS(h)->hashsz);
+  GH_DONE(h, BIN_PTR(rc));
+  GH_DESTROY(h);
+  return (rc);
+}
+
+static PyObject *dsameth_sign(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  gdsa_sig s = GDSA_SIG_INIT;
+  struct bin h;
+  mp *k = 0;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "msg", "k", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:sign", KWLIST,
+                                  convbin, &h, convmp, &k))
+    goto end;
+  if (h.sz != DSA_D(me)->h->hashsz)
+    VALERR("bad message length (doesn't match hash size)");
+  gdsa_sign(DSA_D(me), &s, h.p, k);
+  rc = Py_BuildValue("(NN)", mp_pywrap(s.r), mp_pywrap(s.s));
+end:
+  mp_drop(k);
+  return (rc);
+}
+
+static PyObject *dsameth_verify(PyObject *me, PyObject *arg)
+{
+  struct bin h;
+  gdsa_sig s = GDSA_SIG_INIT;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&(O&O&):verify",
+                       convbin, &h, convmp, &s.r, convmp, &s.s))
+    goto end;
+  if (h.sz != DSA_D(me)->h->hashsz)
+    VALERR("bad message length (doesn't match hash size)");
+  rc = getbool(!gdsa_verify(DSA_D(me), &s, h.p));
+end:
+  mp_drop(s.r);
+  mp_drop(s.s);
+  return (rc);
+}
+
+static void dsa_calcpub(group *g, ge *p, mp *u) { G_EXP(g, p, g->g, u); }
+
+static PyObject *dsapriv_pynew(PyTypeObject *ty,
+                              PyObject *arg, PyObject *kw)
+{
+  PyObject *G, *p = 0, *u, *rng = rand_pyobj, *hash = sha_pyobj;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "G", "u", "p", "hash", "rng", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O|O!O!O!:new", KWLIST,
+                                  group_pytype, &G,
+                                  &u,
+                                  ge_pytype, &p,
+                                  gchash_pytype, &hash,
+                                  grand_pytype, &rng) ||
+      (rc = dsa_setup(dsapriv_pytype, G, u, p, rng, hash, dsa_calcpub)) == 0)
+    goto end;
+end:
+  return (rc);
+}
+
+static const PyMethodDef dsapub_pymethods[] = {
+#define METHNAME(name) dsameth_##name
+  NAMETH(beginhash,    "D.beginhash() -> hash object")
+  METH (endhash,       "D.endhash(H) -> BYTES")
+  METH (verify,        "D.verify(MSG, (R, S)) -> true/false")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyMethodDef dsapriv_pymethods[] = {
+#define METHNAME(name) dsameth_##name
+  KWMETH(sign,         "D.sign(MSG, [k = K]) -> R, S")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyMemberDef dsapub_pymembers[] = {
+#define MEMBERSTRUCT dsa_pyobj
+  MEMBER(G,    T_OBJECT, READONLY, "D.G -> group to work in")
+  MEMBER(p,    T_OBJECT, READONLY, "D.p -> public key (group element")
+  MEMBER(rng,  T_OBJECT, READONLY, "D.rng -> random number generator")
+  MEMBER(hash, T_OBJECT, READONLY, "D.hash -> hash class")
+#undef MEMBERSTRUCT
+  { 0 }
+};
+
+static const PyMemberDef dsapriv_pymembers[] = {
+#define MEMBERSTRUCT dsa_pyobj
+  MEMBER(u,    T_OBJECT, READONLY, "D.u -> private key (exponent)")
+#undef MEMBERSTRUCT
+  { 0 }
+};
+
+static const PyTypeObject dsapub_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "DSAPub",                            /* @tp_name@ */
+  sizeof(dsa_pyobj),                   /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  dsa_pydealloc,                       /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "DSAPub(GROUP, P, [hash = sha], [rng = rand]): DSA public key.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(dsapub),                   /* @tp_methods@ */
+  PYMEMBERS(dsapub),                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  dsapub_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject dsapriv_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "DSAPriv",                           /* @tp_name@ */
+  sizeof(dsa_pyobj),                   /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "DSAPriv(GROUP, U, [p = u G], [hash = sha], [rng = rand]): "
+                                                         "DSA private key.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(dsapriv),                  /* @tp_methods@ */
+  PYMEMBERS(dsapriv),                  /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  dsapriv_pynew,                       /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *kcdsapub_pynew(PyTypeObject *ty,
+                               PyObject *arg, PyObject *kw)
+{
+  PyObject *G, *p, *rng = rand_pyobj, *hash = has160_pyobj;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "G", "p", "hash", "rng", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O!|O!O!:new", KWLIST,
+                                  group_pytype, &G,
+                                  ge_pytype, &p,
+                                  gchash_pytype, &hash,
+                                  grand_pytype, &rng) ||
+      (rc = dsa_setup(kcdsapub_pytype, G, 0, p, rng, hash, 0)) == 0)
+    goto end;
+end:
+  return (rc);
+}
+
+static void kcdsa_calcpub(group *g, ge *p, mp *u)
+{
+  mp *uinv = mp_modinv(MP_NEW, u, g->r);
+  G_EXP(g, p, g->g, uinv);
+  mp_drop(uinv);
+}
+
+static PyObject *kcdsapriv_pynew(PyTypeObject *ty,
+                                PyObject *arg, PyObject *kw)
+{
+  PyObject *G, *u, *p = 0, *rng = rand_pyobj, *hash = has160_pyobj;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "G", "u", "p", "hash", "rng", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O!O|O!O!O!:new", KWLIST,
+                                  group_pytype, &G,
+                                  &u,
+                                  ge_pytype, &p,
+                                  gchash_pytype, &hash,
+                                  grand_pytype, &rng) ||
+      (rc = dsa_setup(kcdsapriv_pytype, G, u, p,
+                     rng, hash, kcdsa_calcpub)) == 0)
+    goto end;
+end:
+  return (rc);
+}
+
+static PyObject *kcdsameth_beginhash(PyObject *me)
+  { return (ghash_pywrap(DSA_HASH(me), gkcdsa_beginhash(DSA_D(me)))); }
+
+static PyObject *kcdsameth_endhash(PyObject *me, PyObject *arg)
+{
+  ghash *h;
+  PyObject *rc;
+  if (!PyArg_ParseTuple(arg, "O&:endhash", convghash, &h)) return (0);
+  gkcdsa_endhash(DSA_D(me), h);
+  h = GH_COPY(h);
+  rc = bytestring_pywrap(0, GH_CLASS(h)->hashsz);
+  GH_DONE(h, BIN_PTR(rc));
+  GH_DESTROY(h);
+  return (rc);
+}
+
+static PyObject *kcdsameth_sign(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  gkcdsa_sig s = GKCDSA_SIG_INIT;
+  struct bin h;
+  mp *k = 0;
+  PyObject *r = 0, *rc = 0;
+  static const char *const kwlist[] = { "msg", "k", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:sign", KWLIST,
+                                  convbin, &h, convmp, &k))
+    goto end;
+  if (h.sz != DSA_D(me)->h->hashsz)
+    VALERR("bad message length (doesn't match hash size)");
+  r = bytestring_pywrap(0, DSA_D(me)->h->hashsz);
+  s.r = (octet *)BIN_PTR(r);
+  gkcdsa_sign(DSA_D(me), &s, h.p, k);
+  rc = Py_BuildValue("(ON)", r, mp_pywrap(s.s));
+end:
+  Py_XDECREF(r);
+  mp_drop(k);
+  return (rc);
+}
+
+static PyObject *kcdsameth_verify(PyObject *me, PyObject *arg)
+{
+  struct bin h, sr;
+  gkcdsa_sig s = GKCDSA_SIG_INIT;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&(O&O&):verify",
+                       convbin, &h, convbin, &sr, convmp, &s.s))
+    goto end;
+  if (h.sz != DSA_D(me)->h->hashsz)
+    VALERR("bad message length (doesn't match hash size)");
+  if (sr.sz != DSA_D(me)->h->hashsz)
+    VALERR("bad signature `r' length (doesn't match hash size)");
+  s.r = (/*unconst*/ octet *)sr.p;
+  rc = getbool(!gkcdsa_verify(DSA_D(me), &s, h.p));
+end:
+  mp_drop(s.s);
+  return (rc);
+}
+
+static const PyMethodDef kcdsapub_pymethods[] = {
+#define METHNAME(name) kcdsameth_##name
+  NAMETH(beginhash,    "D.beginhash() -> hash object")
+  METH (endhash,       "D.endhash(H) -> BYTES")
+  METH (verify,        "D.verify(MSG, (R, S)) -> true/false")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyMethodDef kcdsapriv_pymethods[] = {
+#define METHNAME(name) kcdsameth_##name
+  KWMETH(sign,         "D.sign(MSG, [k = K]) -> R, S")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject kcdsapub_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KCDSAPub",                          /* @tp_name@ */
+  sizeof(dsa_pyobj),                   /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  dsa_pydealloc,                       /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KCDSAPub(GROUP, P, [hash = sha], [rng = rand]): KCDSA public key.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(kcdsapub),                 /* @tp_methods@ */
+  PYMEMBERS(dsapub),                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  kcdsapub_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject kcdsapriv_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "KCDSAPriv",                         /* @tp_name@ */
+  sizeof(dsa_pyobj),                   /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "KCDSAPriv(GROUP, U, [p = u G], [hash = sha], [rng = rand]): "
+                                                       "KCDSA private key.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(kcdsapriv),                        /* @tp_methods@ */
+  PYMEMBERS(dsapriv),                  /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  kcdsapriv_pynew,                     /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- RSA ---------------------------------------------------------------*/
+
+typedef struct rsapub_pyobj {
+  PyObject_HEAD
+  rsa_pub pub;
+  rsa_pubctx pubctx;
+} rsapub_pyobj;
+
+#define RSA_PUB(o) (&((rsapub_pyobj *)(o))->pub)
+#define RSA_PUBCTX(o) (&((rsapub_pyobj *)(o))->pubctx)
+
+typedef struct rsapriv_pyobj {
+  PyObject_HEAD
+  rsa_pub pub;
+  rsa_pubctx pubctx;
+  rsa_priv priv;
+  rsa_privctx privctx;
+  PyObject *rng;
+} rsapriv_pyobj;
+
+#define RSA_PRIV(o) (&((rsapriv_pyobj *)(o))->priv)
+#define RSA_PRIVCTX(o) (&((rsapriv_pyobj *)(o))->privctx)
+#define RSA_RNG(o) (((rsapriv_pyobj *)(o))->rng)
+
+static PyTypeObject *rsapub_pytype, *rsapriv_pytype;
+
+static PyObject *rsapub_pynew(PyTypeObject *ty,
+                             PyObject *arg, PyObject *kw)
+{
+  rsa_pub rp = { 0 };
+  rsapub_pyobj *o;
+  static const char *const kwlist[] = { "n", "e", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:new", KWLIST,
+                                  convmp, &rp.n, convmp, &rp.e))
+    goto end;
+  if (!MP_ODDP(rp.n)) VALERR("RSA modulus must be even");
+  o = (rsapub_pyobj *)ty->tp_alloc(ty, 0);
+  o->pub = rp;
+  rsa_pubcreate(&o->pubctx, &o->pub);
+  return ((PyObject *)o);
+end:
+  rsa_pubfree(&rp);
+  return (0);
+}
+
+static void rsapub_pydealloc(PyObject *me)
+{
+  rsa_pubdestroy(RSA_PUBCTX(me));
+  rsa_pubfree(RSA_PUB(me));
+  FREEOBJ(me);
+}
+
+static PyObject *rsaget_n(PyObject *me, void *hunoz)
+  { return mp_pywrap(MP_COPY(RSA_PUB(me)->n)); }
+
+static PyObject *rsaget_e(PyObject *me, void *hunoz)
+  { return mp_pywrap(MP_COPY(RSA_PUB(me)->e)); }
+
+static PyObject *rsameth_pubop(PyObject *me, PyObject *arg)
+{
+  mp *x = 0;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:pubop", convmp, &x)) goto end;
+  rc = mp_pywrap(rsa_pubop(RSA_PUBCTX(me), MP_NEW, x));
+end:
+  mp_drop(x);
+  return (rc);
+}
+
+static PyObject *rsapriv_dopywrap(PyTypeObject *ty,
+                                 rsa_priv *rp, PyObject *rng)
+{
+  rsapriv_pyobj *o;
+
+  o = (rsapriv_pyobj *)ty->tp_alloc(ty, 0);
+  o->priv = *rp;
+  o->pub.n = rp->n;
+  o->pub.e = rp->e;
+  rsa_privcreate(&o->privctx, &o->priv, &rand_global);
+  rsa_pubcreate(&o->pubctx, &o->pub);
+  if (!rng) {
+    rng = Py_None;
+    Py_INCREF(rng);
+  }
+  o->rng = rng;
+  return ((PyObject *)o);
+}
+
+PyObject *rsapriv_pywrap(rsa_priv *rp)
+  { return rsapriv_dopywrap(rsapriv_pytype, rp, 0); }
+
+static PyObject *rsapriv_pynew(PyTypeObject *ty,
+                              PyObject *arg, PyObject *kw)
+{
+  rsa_priv rp = { 0 };
+  PyObject *rng = Py_None;
+  static const char *const kwlist[] =
+    { "n", "e", "d", "p", "q", "dp", "dq", "q_inv", "rng", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&O&O&O&O&O&O&O&O:new", KWLIST,
+                                  convmp, &rp.n, convmp, &rp.e,
+                                  convmp, &rp.d,
+                                  convmp, &rp.p, convmp, &rp.q,
+                                  convmp, &rp.dp, convmp, &rp.dq,
+                                  convmp, &rp.q_inv,
+                                  &rng))
+    goto end;
+  if ((rp.n && !MP_ODDP(rp.n)) ||
+      (rp.p && !MP_ODDP(rp.p)) ||
+      (rp.q && !MP_ODDP(rp.q)))
+    VALERR("RSA modulus and factors must be odd");
+  if (rsa_recover(&rp)) VALERR("couldn't construct private key");
+  if (rng != Py_None && !GRAND_PYCHECK(rng))
+    TYERR("not a random number source");
+  Py_INCREF(rng);
+  return (rsapriv_dopywrap(ty, &rp, rng));
+end:
+  rsa_privfree(&rp);
+  return (0);
+}
+
+static void rsapriv_pydealloc(PyObject *me)
+{
+  RSA_PRIVCTX(me)->r = &rand_global;
+  rsa_privdestroy(RSA_PRIVCTX(me));
+  rsa_privfree(RSA_PRIV(me));
+  Py_DECREF(RSA_RNG(me));
+  FREEOBJ(me);
+}
+
+static PyObject *rsaget_d(PyObject *me, void *hunoz)
+  { return mp_pywrap(MP_COPY(RSA_PRIV(me)->d)); }
+
+static PyObject *rsaget_p(PyObject *me, void *hunoz)
+  { return mp_pywrap(MP_COPY(RSA_PRIV(me)->p)); }
+
+static PyObject *rsaget_q(PyObject *me, void *hunoz)
+  { return mp_pywrap(MP_COPY(RSA_PRIV(me)->q)); }
+
+static PyObject *rsaget_dp(PyObject *me, void *hunoz)
+  { return mp_pywrap(MP_COPY(RSA_PRIV(me)->dp)); }
+
+static PyObject *rsaget_dq(PyObject *me, void *hunoz)
+  { return mp_pywrap(MP_COPY(RSA_PRIV(me)->dq)); }
+
+static PyObject *rsaget_q_inv(PyObject *me, void *hunoz)
+  { return mp_pywrap(MP_COPY(RSA_PRIV(me)->q_inv)); }
+
+static PyObject *rsaget_rng(PyObject *me, void *hunoz)
+  { RETURN_OBJ(RSA_RNG(me)); }
+
+static int rsaset_rng(PyObject *me, PyObject *val, void *hunoz)
+{
+  int rc = -1;
+  if (!val)
+    val = Py_None;
+  else if (val != Py_None && !GRAND_PYCHECK(val))
+    TYERR("expected grand or None");
+  Py_DECREF(RSA_RNG(me));
+  RSA_RNG(me) = val;
+  Py_INCREF(val);
+  rc = 0;
+end:
+  return (rc);
+}
+
+static PyObject *rsameth_privop(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  PyObject *rng = RSA_RNG(me);
+  mp *x = 0;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "x", "rng", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O:privop", KWLIST,
+                                  convmp, &x, &rng))
+    goto end;
+  if (rng != Py_None && !GRAND_PYCHECK(rng))
+    TYERR("not a random number source");
+  RSA_PRIVCTX(me)->r = (rng == Py_None) ? 0 : GRAND_R(rng);
+  rc = mp_pywrap(rsa_privop(RSA_PRIVCTX(me), MP_NEW, x));
+end:
+  mp_drop(x);
+  return (rc);
+}
+
+static PyObject *rsameth_generate(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  grand *r = &rand_global;
+  unsigned nbits;
+  unsigned n = 0;
+  rsa_priv rp;
+  mp *e = 0;
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
+  static const char *const kwlist[] =
+    { "nbits", "event", "rng", "nsteps", "e", 0 };
+  PyObject *rc = 0;
+
+  evt.exc = &exc;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&O&O&O&:generate", KWLIST,
+                                  convuint, &nbits, convpgev, &evt,
+                                  convgrand, &r, convuint, &n,
+                                  convmp, &e))
+    goto end;
+  if (e) MP_COPY(e);
+  else e = mp_fromulong(MP_NEW, 65537);
+  if (rsa_gen_e(&rp, nbits, e, r, n, evt.ev.proc, evt.ev.ctx))
+    PGENERR(&exc);
+  rc = rsapriv_pywrap(&rp);
+end:
+  droppgev(&evt);
+  mp_drop(e);
+  return (rc);
+}
+
+static const PyGetSetDef rsapub_pygetset[] = {
+#define GETSETNAME(op, name) rsa##op##_##name
+  GET  (n,             "R.n -> N")
+  GET  (e,             "R.e -> E")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef rsapub_pymethods[] = {
+#define METHNAME(name) rsameth_##name
+  METH (pubop,         "R.pubop(X) -> X^E (mod N)")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyGetSetDef rsapriv_pygetset[] = {
+#define GETSETNAME(op, name) rsa##op##_##name
+  GET  (d,             "R.d -> D")
+  GET  (p,             "R.p -> P")
+  GET  (q,             "R.q -> Q")
+  GET  (dp,            "R.dp -> D mod (P - 1)")
+  GET  (dq,            "R.dq -> D mod (Q - 1)")
+  GET  (q_inv,         "R.q_inv -> Q^{-1} mod P")
+  GETSET(rng,          "R.rng -> random number source for blinding")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef rsapriv_pymethods[] = {
+#define METHNAME(name) rsameth_##name
+  KWMETH(privop,       "R.privop(X, [rng = None]) -> X^D (mod N)")
+  KWSMTH(generate, "generate(NBITS, [event = pgen_nullev], [rng = rand], "
+                                                       "[nsteps = 0]) -> R")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject rsapub_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "RSAPub",                            /* @tp_name@ */
+  sizeof(rsapub_pyobj),                        /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  rsapub_pydealloc,                    /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "RSAPub(N, E): RSA public key.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(rsapub),                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(rsapub),                    /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  rsapub_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject rsapriv_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "RSAPriv",                           /* @tp_name@ */
+  sizeof(rsapriv_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  rsapriv_pydealloc,                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "RSAPriv(..., [rng = rand]): RSA private key.\n"
+  "  Keywords: n, e, d, p, q, dp, dq, q_inv; must provide enough",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(rsapriv),                  /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(rsapriv),                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  rsapriv_pynew,                       /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- RSA padding schemes -----------------------------------------------*/
+
+static PyObject *meth__p1crypt_encode(PyObject *me,
+                                     PyObject *arg, PyObject *kw)
+{
+  pkcs1 p1;
+  struct bin m, ep = { 0, 0 };
+  unsigned long nbits;
+  PyObject *rc = 0;
+  octet *b = 0;
+  size_t sz;
+  mp *x;
+  static const char *const kwlist[] = { "msg", "nbits", "ep", "rng", 0 };
+
+  p1.r = &rand_global;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&O&:encode", KWLIST,
+                                  convbin, &m, convulong, &nbits,
+                                  convbin, &ep, convgrand, &p1.r))
+    goto end;
+  sz = (nbits + 7)/8;
+  p1.ep = ep.p; p1.epsz = ep.sz;
+  if (ep.sz + m.sz + 11 > sz) VALERR("buffer underflow");
+  b = xmalloc(sz);
+  x = pkcs1_cryptencode(MP_NEW, m.p, m.sz, b, sz, nbits, &p1);
+  rc = mp_pywrap(x);
+end:
+  xfree(b);
+  return (rc);
+}
+
+static PyObject *meth__p1crypt_decode(PyObject *me,
+                                     PyObject *arg, PyObject *kw)
+{
+  pkcs1 p1;
+  struct bin ep = { 0, 0 };
+  unsigned long nbits;
+  int n;
+  PyObject *rc = 0;
+  octet *b = 0;
+  size_t sz;
+  mp *x = 0;
+  static const char *const kwlist[] = { "ct", "nbits", "ep", "rng", 0 };
+
+  p1.r = &rand_global;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&O&:decode", KWLIST,
+                                  convmp, &x, convulong, &nbits,
+                                  convbin, &ep, convgrand, &p1.r))
+    goto end;
+  sz = (nbits + 7)/8;
+  p1.ep = ep.p; p1.epsz = ep.sz;
+  if (ep.sz + 11 > sz) VALERR("buffer underflow");
+  b = xmalloc(sz);
+  if ((n = pkcs1_cryptdecode(x, b, sz, nbits, &p1)) < 0)
+    VALERR("decryption failed");
+  rc = bytestring_pywrap(b, n);
+end:
+  mp_drop(x);
+  xfree(b);
+  return (rc);
+}
+
+static PyObject *meth__p1sig_encode(PyObject *me,
+                                   PyObject *arg, PyObject *kw)
+{
+  pkcs1 p1;
+  struct bin m, ep = { 0, 0 };
+  unsigned long nbits;
+  PyObject *rc = 0;
+  octet *b = 0;
+  size_t sz;
+  mp *x;
+  static const char *const kwlist[] = { "msg", "nbits", "ep", "rng", 0 };
+
+  p1.r = &rand_global;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&O&:encode", KWLIST,
+                                  convbin, &m, convulong, &nbits,
+                                  convbin, &ep, convgrand, &p1.r))
+    goto end;
+  sz = (nbits + 7)/8;
+  p1.ep = ep.p; p1.epsz = ep.sz;
+  if (ep.sz + m.sz + 11 > sz) VALERR("buffer underflow");
+  b = xmalloc(sz);
+  x = pkcs1_sigencode(MP_NEW, m.p, m.sz, b, sz, nbits, &p1);
+  rc = mp_pywrap(x);
+end:
+  xfree(b);
+  return (rc);
+}
+
+static PyObject *meth__p1sig_decode(PyObject *me,
+                                   PyObject *arg, PyObject *kw)
+{
+  pkcs1 p1;
+  struct bin ep = { 0, 0 };
+  unsigned long nbits;
+  int n;
+  PyObject *hukairz;
+  PyObject *rc = 0;
+  octet *b = 0;
+  size_t sz;
+  mp *x = 0;
+  static const char *const kwlist[] =
+    { "msg", "sig", "nbits", "ep", "rng", 0 };
+
+  p1.r = &rand_global;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&O&|O&O&:decode", KWLIST,
+                                  &hukairz, convmp, &x, convulong, &nbits,
+                                  convbin, &ep, convgrand, &p1.r))
+    goto end;
+  sz = (nbits + 7)/8;
+  p1.ep = ep.p; p1.epsz = ep.sz;
+  if (ep.sz + 10 > sz) VALERR("buffer underflow");
+  b = xmalloc(sz);
+  if ((n = pkcs1_sigdecode(x, 0, 0, b, sz, nbits, &p1)) < 0)
+    VALERR("verification failed");
+  rc = bytestring_pywrap(b, n);
+end:
+  mp_drop(x);
+  xfree(b);
+  return (rc);
+}
+
+static PyObject *meth__oaep_encode(PyObject *me,
+                                  PyObject *arg, PyObject *kw)
+{
+  oaep o;
+  struct bin m, ep = { 0, 0 };
+  unsigned long nbits;
+  PyObject *rc = 0;
+  octet *b = 0;
+  size_t sz;
+  mp *x;
+  static const char *const kwlist[] =
+    { "msg", "nbits", "mgf", "hash", "ep", "rng", 0 };
+
+  o.r = &rand_global; o.cc = &sha_mgf; o.ch = &sha;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&O&O&O&:encode",
+                                  KWLIST,
+                                  convbin, &m, convulong, &nbits,
+                                  convgccipher, &o.cc,
+                                  convgchash, &o.ch,
+                                  convbin, &ep,
+                                  convgrand, &o.r))
+    goto end;
+  sz = (nbits + 7)/8;
+  o.ep = ep.p; o.epsz = ep.sz;
+  if (2 * o.ch->hashsz + 2 + m.sz > sz) VALERR("buffer underflow");
+  b = xmalloc(sz);
+  x = oaep_encode(MP_NEW, m.p, m.sz, b, sz, nbits, &o);
+  rc = mp_pywrap(x);
+end:
+  xfree(b);
+  return (rc);
+}
+
+static PyObject *meth__oaep_decode(PyObject *me,
+                                  PyObject *arg, PyObject *kw)
+{
+  oaep o;
+  struct bin ep = { 0, 0 };
+  unsigned long nbits;
+  int n;
+  PyObject *rc = 0;
+  octet *b = 0;
+  size_t sz;
+  mp *x = 0;
+  static const char *const kwlist[] =
+    { "ct", "nbits", "mgf", "hash", "ep", "rng", 0 };
+
+  o.r = &rand_global; o.cc = &sha_mgf; o.ch = &sha;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&O&O&O&:decode", KWLIST,
+                                  convmp, &x, convulong, &nbits,
+                                  convgccipher, &o.cc,
+                                  convgchash, &o.ch,
+                                  convbin, &ep,
+                                  convgrand, &o.r))
+    goto end;
+  sz = (nbits + 7)/8;
+  o.ep = ep.p; o.epsz = ep.sz;
+  if (2 * o.ch->hashsz > sz) VALERR("buffer underflow");
+  b = xmalloc(sz);
+  if ((n = oaep_decode(x, b, sz, nbits, &o)) < 0)
+    VALERR("decryption failed");
+  rc = bytestring_pywrap(b, n);
+end:
+  mp_drop(x);
+  xfree(b);
+  return (rc);
+}
+
+static PyObject *meth__pss_encode(PyObject *me,
+                                 PyObject *arg, PyObject *kw)
+{
+  pss p;
+  struct bin m;
+  unsigned long nbits;
+  PyObject *rc = 0;
+  octet *b = 0;
+  size_t sz;
+  mp *x = 0;
+  static const char *const kwlist[] =
+    { "msg", "nbits", "mgf", "hash", "saltsz", "rng", 0 };
+
+  p.cc = &sha_mgf; p.ch = &sha; p.r = &rand_global; p.ssz = (size_t)-1;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&O&O&O&:encode", KWLIST,
+                                  convbin, &m, convulong, &nbits,
+                                  convgccipher, &p.cc,
+                                  convgchash, &p.ch,
+                                  convszt, &p.ssz,
+                                  convgrand, &p.r))
+    goto end;
+  sz = (nbits + 7)/8;
+  if (p.ssz == (size_t)-1) p.ssz = p.ch->hashsz;
+  if (p.ch->hashsz + p.ssz + 2 > sz) VALERR("buffer underflow");
+  b = xmalloc(sz);
+  x = pss_encode(MP_NEW, m.p, m.sz, b, sz, nbits, &p);
+  rc = mp_pywrap(x);
+end:
+  xfree(b);
+  return (rc);
+}
+
+static PyObject *meth__pss_decode(PyObject *me,
+                                 PyObject *arg, PyObject *kw)
+{
+  pss p;
+  struct bin m;
+  unsigned long nbits;
+  PyObject *rc = 0;
+  octet *b = 0;
+  size_t sz;
+  int n;
+  mp *x = 0;
+  static const char *const kwlist[] =
+    { "msg", "sig", "nbits", "mgf", "hash", "saltsz", "rng", 0 };
+
+  p.cc = &sha_mgf; p.ch = &sha; p.r = &rand_global; p.ssz = (size_t)-1;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&O&|O&O&O&O&:decode",
+                                  KWLIST,
+                                  convbin, &m, convmp, &x,
+                                  convulong, &nbits,
+                                  convgccipher, &p.cc,
+                                  convgchash, &p.ch,
+                                  convszt, &p.ssz,
+                                  convgrand, &p.r))
+    goto end;
+  sz = (nbits + 7)/8;
+  if (p.ssz == (size_t)-1) p.ssz = p.ch->hashsz;
+  if (p.ch->hashsz + p.ssz + 2 > sz) VALERR("buffer underflow");
+  b = xmalloc(sz);
+  if ((n = pss_decode(x, m.p, m.sz, b, sz, nbits, &p)) < 0)
+    VALERR("verification failed");
+  rc = Py_None; Py_INCREF(rc);
+end:
+  mp_drop(x);
+  xfree(b);
+  return (rc);
+}
+
+/*----- X25519 and related algorithms -------------------------------------*/
+
+#define XDHS(_)                                                                \
+  _(X25519, x25519)                                                    \
+  _(X448, x448)
+
+#define DEFXDH(X, x)                                                   \
+  static PyObject *meth_##x(PyObject *me, PyObject *arg)               \
+  {                                                                    \
+    struct bin k, p;                                                   \
+    PyObject *rc = 0;                                                  \
+    if (!PyArg_ParseTuple(arg, "O&O&:" #x, convbin, &k, convbin, &p))  \
+      goto end;                                                                \
+    if (k.sz != X##_KEYSZ) VALERR("bad key length");                   \
+    if (p.sz != X##_PUBSZ) VALERR("bad public length");                        \
+    rc = bytestring_pywrap(0, X##_OUTSZ);                              \
+    x((octet *)BIN_PTR(rc), k.p, p.p);                                 \
+    return (rc);                                                       \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+XDHS(DEFXDH)
+#undef DEFXDH
+
+/*----- Ed25519 and related algorithms ------------------------------------*/
+
+#define EDDSAS(_)                                                      \
+  _(ED25519, ed25519, -1, ctx)                                         \
+  _(ED448, ed448, 0, )
+
+#define DEFEDDSA(ED, ed, phdflt, sigver)                               \
+                                                                       \
+  static PyObject *meth_##ed##_pubkey(PyObject *me, PyObject *arg)     \
+  {                                                                    \
+   struct bin k;                                                       \
+    PyObject *rc = 0;                                                  \
+    if (!PyArg_ParseTuple(arg, "O&:" #ed "_pubkey", convbin, &k))      \
+      goto end;                                                                \
+    rc = bytestring_pywrap(0, ED##_PUBSZ);                             \
+    ed##_pubkey((octet *)BIN_PTR(rc), k.p, k.sz);                      \
+    return (rc);                                                       \
+  end:                                                                 \
+    return (0);                                                                \
+  }                                                                    \
+                                                                       \
+  static PyObject *meth_##ed##_sign(PyObject *me, PyObject *arg,       \
+                                   PyObject *kw)                       \
+  {                                                                    \
+    struct bin k, p = { 0, 0}, c = { 0, 0 }, m;                                \
+    int ph = phdflt;                                                   \
+    PyObject *rc = 0;                                                  \
+    octet pp[ED##_PUBSZ];                                              \
+    static const char *const kwlist[] =                                        \
+      { "key", "msg", "pub", "perso", "phflag", 0 };                   \
+    if (!PyArg_ParseTupleAndKeywords(arg, kw,                          \
+                                    "O&O&|O&O&O&:" #ed "_sign",        \
+                                    KWLIST,                            \
+                                    convbin, &k, convbin, &m,          \
+                                    convbin, &p, convbin, &c,          \
+                                    convbool, &ph))                    \
+      goto end;                                                                \
+    if (p.p && p.sz != ED##_PUBSZ) VALERR("bad public length");                \
+    if (c.p && c.sz > ED##_MAXPERSOSZ)                                 \
+      VALERR("personalization string too long");                       \
+    if (c.p && ph == -1) ph = 0;                                       \
+    if (!p.p) { p.p = pp; ed##_pubkey(pp, k.p, k.sz); }                        \
+    rc = bytestring_pywrap(0, ED##_SIGSZ);                             \
+    ed##sigver##_sign((octet *)BIN_PTR(rc), k.p, k.sz,                 \
+                     p.p, ph, c.p, c.sz, m.p, m.sz);                   \
+    return (rc);                                                       \
+  end:                                                                 \
+    return (0);                                                                \
+  }                                                                    \
+                                                                       \
+  static PyObject *meth_##ed##_verify(PyObject *me,                    \
+                                     PyObject *arg, PyObject *kw)      \
+  {                                                                    \
+    struct bin p, c = { 0, 0 }, m, s;                                  \
+    int ph = phdflt;                                                   \
+    PyObject *rc = 0;                                                  \
+    static const char *const kwlist[] =                                        \
+      { "pub", "msg", "sig", "perso", "phflag", 0 };                   \
+    if (!PyArg_ParseTupleAndKeywords(arg, kw,                          \
+                                    "O&O&O&|O&O&:" #ed "_verify",      \
+                                    KWLIST,                            \
+                                    convbin, &p, convbin, &m,          \
+                                    convbin, &s,                       \
+                                    convbin, &c, convbool, &ph))       \
+      goto end;                                                                \
+    if (p.sz != ED##_PUBSZ) VALERR("bad public length");               \
+    if (s.sz != ED##_SIGSZ) VALERR("bad signature length");            \
+    if (c.p && c.sz > ED##_MAXPERSOSZ)                                 \
+      VALERR("personalization string too long");                       \
+    if (c.p && ph == -1) ph = 0;                                       \
+    rc = getbool(!ed##sigver##_verify(p.p, ph, c.p, c.sz,              \
+                                     m.p, m.sz, s.p));                 \
+    return (rc);                                                       \
+  end:                                                                 \
+    return (0);                                                                \
+  }
+EDDSAS(DEFEDDSA)
+#undef DEFEDDSA
+
+/*----- Global stuff ------------------------------------------------------*/
+
+static const struct nameval consts[] = {
+  CONST(X25519_KEYSZ), CONST(X25519_PUBSZ), CONST(X25519_OUTSZ),
+  CONST(X448_KEYSZ), CONST(X448_PUBSZ), CONST(X448_OUTSZ),
+  CONST(ED25519_KEYSZ), CONST(ED25519_PUBSZ), CONST(ED25519_SIGSZ),
+    CONST(ED25519_MAXPERSOSZ),
+  CONST(ED448_KEYSZ), CONST(ED448_PUBSZ), CONST(ED448_SIGSZ),
+    CONST(ED448_MAXPERSOSZ),
+  { 0 }
+};
+
+static const PyMethodDef methods[] = {
+#define METHNAME(name) meth_##name
+  KWMETH(_p1crypt_encode, 0)
+  KWMETH(_p1crypt_decode, 0)
+  KWMETH(_p1sig_encode,        0)
+  KWMETH(_p1sig_decode,        0)
+  KWMETH(_oaep_encode, 0)
+  KWMETH(_oaep_decode, 0)
+  KWMETH(_pss_encode,  0)
+  KWMETH(_pss_decode,  0)
+#define DEFMETH(X, x)                                                  \
+  METH (x,             "" #x "(KEY, PUBLIC) -> SHARED")
+  XDHS(DEFMETH)
+#undef DEFMETH
+#define DEFMETH(ED, ed, phdflt, sigver)                                        \
+  METH (ed##_pubkey,   "" #ed "_pubkey(KEY) -> PUBLIC")                \
+  KWMETH(ed##_sign,    "" #ed "_sign(KEY, MSG, [pub = PUBLIC], "       \
+                          "[perso = STRING], [phflag = BOOL]) -> SIG") \
+  KWMETH(ed##_verify,  "" #ed "_verify(PUBLIC, MSG, SIG, "             \
+                         "[perso = STRING], [phflag = BOOL]) -> BOOL")
+  EDDSAS(DEFMETH)
+#undef DEFMETH
+#undef METHNAME
+  { 0 }
+};
+
+void pubkey_pyinit(void)
+{
+  INITTYPE(dsapub, root);
+  INITTYPE(dsapriv, dsapub);
+  INITTYPE(kcdsapub, root);
+  INITTYPE(kcdsapriv, kcdsapub);
+  INITTYPE(rsapub, root);
+  INITTYPE(rsapriv, rsapub);
+  addmethods(methods);
+}
+
+void pubkey_pyinsert(PyObject *mod)
+{
+  INSERT("DSAPub", dsapub_pytype);
+  INSERT("DSAPriv", dsapriv_pytype);
+  INSERT("KCDSAPub", kcdsapub_pytype);
+  INSERT("KCDSAPriv", kcdsapriv_pytype);
+  INSERT("RSAPub", rsapub_pytype);
+  INSERT("RSAPriv", rsapriv_pytype);
+  setconstants(mod, consts);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/pwsafe b/pwsafe
new file mode 100644 (file)
index 0000000..40a64ce
--- /dev/null
+++ b/pwsafe
@@ -0,0 +1,280 @@
+#! /usr/bin/python
+### -*-python-*-
+###
+### Tool for maintaining a secure-ish password database
+###
+### (c) 2005 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 __future__ import with_statement
+
+from os import environ
+import sys as SYS; from sys import argv, exit, stdin, stdout, stderr
+from getopt import getopt, GetoptError
+from fnmatch import fnmatch
+import re
+
+import catacomb as C
+from catacomb.pwsafe import *
+
+###--------------------------------------------------------------------------
+### Python version portability.
+
+if SYS.version_info >= (3,):
+  import io as IO
+  def hack_stream(stream):
+    _enc = stream.encoding
+    _lbuf = stream.line_buffering
+    _nl = stream.newlines
+    return IO.TextIOWrapper(stream.detach(),
+                            encoding = _enc,
+                            line_buffering = _lbuf,
+                            newline = _nl,
+                            errors = "surrogateescape")
+  SYS.stdout = stdout = hack_stream(stdout)
+  def _text(bin): return bin.decode(errors = "surrogateescape")
+  def _bin(text): return text.encode(errors = "surrogateescape")
+else:
+  def _text(bin): return bin
+  def _bin(text): return text
+
+def _excval(): return SYS.exc_info()[1]
+
+###--------------------------------------------------------------------------
+### Utilities.
+
+## The program name.
+prog = re.sub(r'^.*[/\\]', '', argv[0])
+
+def moan(msg):
+  """Issue a warning message MSG."""
+  stderr.write('%s: %s\n' % (prog, msg))
+
+def die(msg):
+  """Report MSG as a fatal error, and exit."""
+  moan(msg)
+  exit(1)
+
+###--------------------------------------------------------------------------
+### Subcommand implementations.
+
+def cmd_create(av):
+
+  ## Default crypto-primitive selections.
+  cipher = 'rijndael-cbc'
+  hash = 'sha256'
+  mac = None
+
+  ## Parse the options.
+  try:
+    opts, args = getopt(av, 'c:d:h:m:',
+                        ['cipher=', 'database=', 'mac=', 'hash='])
+  except GetoptError:
+    return 1
+  dbty = 'flat'
+  for o, a in opts:
+    if o in ('-c', '--cipher'): cipher = a
+    elif o in ('-m', '--mac'): mac = a
+    elif o in ('-h', '--hash'): hash = a
+    elif o in ('-d', '--database'): dbty = a
+    else: raise Exception("barf")
+  if len(args) > 2: return 1
+  if len(args) >= 1: tag = args[0]
+  else: tag = 'pwsafe'
+
+  ## Set up the database.
+  if mac is None: mac = hash + '-hmac'
+  try: dbcls = StorageBackend.byname(dbty)
+  except KeyError: die("Unknown database backend `%s'" % dbty)
+  PW.create(dbcls, file, tag,
+            C.gcciphers[cipher], C.gchashes[hash], C.gcmacs[mac])
+
+def cmd_changepp(av):
+  if len(av) != 0: return 1
+  with PW(file, writep = True) as pw: pw.changepp()
+
+def cmd_find(av):
+  if len(av) != 1: return 1
+  with PW(file) as pw:
+    try: print(_text(pw[_bin(av[0])]))
+    except KeyError: die("Password `%s' not found" % _excval().args[0])
+
+def cmd_store(av):
+  if len(av) < 1 or len(av) > 2: return 1
+  tag = av[0]
+  with PW(file, writep = True) as pw:
+    if len(av) < 2:
+      pp = C.getpass("Enter passphrase `%s': " % tag)
+      vpp = C.getpass("Confirm passphrase `%s': " % tag)
+      if pp != vpp: die("passphrases don't match")
+    elif av[1] == '-':
+      pp = _bin(stdin.readline().rstrip('\n'))
+    else:
+      pp = _bin(av[1])
+    pw[_bin(av[0])] = pp
+
+def cmd_copy(av):
+  if len(av) < 1 or len(av) > 2: return 1
+  with PW(file) as pw_in:
+    with PW(av[0], writep = True) as pw_out:
+      if len(av) >= 3: pat = av[1]
+      else: pat = None
+      for k in pw_in:
+        ktext = _text(k)
+        if pat is None or fnmatch(ktext, pat): pw_out[k] = pw_in[k]
+
+def cmd_list(av):
+  if len(av) > 1: return 1
+  with PW(file) as pw:
+    if len(av) >= 1: pat = av[0]
+    else: pat = None
+    for k in pw:
+      ktext = _text(k)
+      if pat is None or fnmatch(ktext, pat): print(ktext)
+
+def cmd_topixie(av):
+  if len(av) > 2: return 1
+  with PW(file) as pw:
+    pix = C.Pixie()
+    if len(av) == 0:
+      for tag in pw: pix.set(tag, pw[_bin(tag)])
+    else:
+      tag = _bin(av[0])
+      if len(av) >= 2: pptag = av[1]
+      else: pptag = av[0]
+      pix.set(pptag, pw[tag])
+
+def cmd_del(av):
+  if len(av) != 1: return 1
+  with PW(file, writep = True) as pw:
+    tag = _bin(av[0])
+    try: del pw[tag]
+    except KeyError: die("Password `%s' not found" % _excval().args[0])
+
+def cmd_xfer(av):
+
+  ## Parse the command line.
+  try: opts, args = getopt(av, 'd:', ['database='])
+  except GetoptError: return 1
+  dbty = 'flat'
+  for o, a in opts:
+    if o in ('-d', '--database'): dbty = a
+    else: raise Exception("barf")
+  if len(args) != 1: return 1
+  try: dbcls = StorageBackend.byname(dbty)
+  except KeyError: die("Unknown database backend `%s'" % dbty)
+
+  ## Create the target database.
+  with StorageBackend.open(file) as db_in:
+    with dbcls.create(args[0]) as db_out:
+      for k, v in db_in.iter_meta(): db_out.put_meta(k, v)
+      for k, v in db_in.iter_passwds(): db_out.put_passwd(k, v)
+
+commands = { 'create': [cmd_create,
+                        '[-c CIPHER] [-d DBTYPE] [-h HASH] [-m MAC] [PP-TAG]'],
+             'find' : [cmd_find, 'LABEL'],
+             'store' : [cmd_store, 'LABEL [VALUE]'],
+             'list' : [cmd_list, '[GLOB-PATTERN]'],
+             'changepp' : [cmd_changepp, ''],
+             'copy' : [cmd_copy, 'DEST-FILE [GLOB-PATTERN]'],
+             'to-pixie' : [cmd_topixie, '[TAG [PIXIE-TAG]]'],
+             'delete' : [cmd_del, 'TAG'],
+             'xfer': [cmd_xfer, '[-d DBTYPE] DEST-FILE'] }
+
+###--------------------------------------------------------------------------
+### Command-line handling and dispatch.
+
+def version():
+  print('%s 1.0.0' % prog)
+  print('Backend types: %s' %
+        ' '.join([c.NAME for c in StorageBackend.classes()]))
+
+def usage(fp):
+  fp.write('Usage: %s COMMAND [ARGS...]\n' % prog)
+
+def help():
+  version()
+  print('')
+  usage(stdout)
+  print('''
+Maintains passwords or other short secrets securely.
+
+Options:
+
+-h, --help             Show this help text.
+-v, --version          Show program version number.
+-u, --usage            Show short usage message.
+
+-f, --file=FILE                Where to find the password-safe file.
+
+Commands provided:
+''')
+  for c in sorted(commands):
+    print('%s %s' % (c, commands[c][1]))
+
+## Choose a default database file.
+if 'PWSAFE' in environ:
+  file = environ['PWSAFE']
+else:
+  file = '%s/.pwsafe' % environ['HOME']
+
+## Parse the command-line options.
+try:
+  opts, argv = getopt(argv[1:], 'hvuf:',
+                      ['help', 'version', 'usage', 'file='])
+except GetoptError:
+  usage(stderr)
+  exit(1)
+for o, a in opts:
+  if o in ('-h', '--help'):
+    help()
+    exit(0)
+  elif o in ('-v', '--version'):
+    version()
+    exit(0)
+  elif o in ('-u', '--usage'):
+    usage(stdout)
+    exit(0)
+  elif o in ('-f', '--file'):
+    file = a
+  else:
+    raise Exception("barf")
+if len(argv) < 1:
+  usage(stderr)
+  exit(1)
+
+## Dispatch to a command handler.
+if argv[0] in commands:
+  c = argv[0]
+  argv = argv[1:]
+else:
+  c = 'find'
+try:
+  if commands[c][0](argv):
+    stderr.write('Usage: %s %s %s\n' % (prog, c, commands[c][1]))
+    exit(1)
+except DecryptError:
+  die("decryption failure")
+
+###----- That's all, folks --------------------------------------------------
diff --git a/pwsafe.1 b/pwsafe.1
new file mode 100644 (file)
index 0000000..361ba95
--- /dev/null
+++ b/pwsafe.1
@@ -0,0 +1,878 @@
+.\" -*-nroff-*-
+.\"
+.\" Describe the password safe
+.\"
+.\" (c) 2016 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.
+.
+.ie t \{\
+.  if \n(.g \{\
+.    fam P
+.  \}
+.  ds o \(bu
+.\}
+.el \{\
+.  ds o o
+.\}
+.
+.de hP
+.IP
+\h'-\w'\fB\\$1\ \fP'u'\fB\\$1\ \fP\c
+..
+.
+.TH pwsafe 1 "13 May 2016" "Straylight/Edgeware" "Catacomb cryptographic library"
+.SH NAME
+pwsafe \- store passwords somewhat securely
+.
+.\"--------------------------------------------------------------------------
+.SH SYNOPSIS
+.
+.B pwsafe
+.RB [ \-f
+.IR file ]
+.I command
+.RI [ arguments
+\&...]
+.PP
+Subcommands:
+'RS
+.B changepp
+.br
+.B copy
+.I dest-file
+.RI [ glob-pattern ]
+.br
+.B create
+.RB [ \-c
+.IR cipher ]
+.RB [ \-d
+.IR db-type ]
+.RB [ \-h
+.IR hash ]
+.RB [ \-m
+.IR mac ]
+.RI [ pp-tag ]
+.br
+.B delete
+.I tag
+.br
+.RB [ find ]
+.I label
+.br
+.B list
+.RI [ glob-pattern ]
+.br
+.B store
+.I label
+.RI [ value ]
+.br
+.B to-pixie
+.RI [ label
+.RI [ pixie-tag ]]
+.br
+.B xfer
+.RB [ \-d
+.IR db-type ]
+.I dest-file
+.RE
+.
+.\"--------------------------------------------------------------------------
+.SH DESCRIPTION
+.
+The
+.B pwsafe
+command maintains a database of passwords
+or other short-ish textual secrets.
+Each password is identified by a
+.IR label .
+The database is ultimately protected by a master password.
+It should quite difficult for an adversary
+who has the database file(s)
+but not your master password
+to find out what any of the stored passwords are,
+or even what the labels are.
+.PP
+The
+.B pwsafe
+program will prompt you for the password as necessary.
+If you are running the Catacomb
+.BR pixie (1)
+then it will first ask the pixie for any necessary passwords,
+and use the pixie to remember the master password for a short while.
+.PP
+A number of database backends are available.
+.TP
+.B gdbm
+Uses the GDBM database library to store a database as a single file.
+Provided for compatibility;
+not recommended for new databases.
+.TP
+.B sqlite
+Uses the SQLite3 library to store a database as a single file.
+SQLite3 has good performance and integrity properties.
+.TP
+.B flat
+Stores the password database as a flat text file.
+Not recommended for large databases because performance will be bad,
+but the simple format admits easy hacking.
+.TP
+.B dir
+Stores the password database as a directory structure.
+This uses much more disk space than the alternatives,
+and enumerating passwords is slow,
+but the directory structure can easily be managed by
+a version control system such as Git.
+.PP
+The following command-line options are available.
+.TP
+.B "\-h, \-\-help"
+Print a help summary to standard output,
+and exit with status zero.
+.TP
+.B "\-v, \-\-version"
+Print the program's version number to standard output,
+and exit with status zero.
+.TP
+.B "\-u, \-\-usage"
+Print a very terse usage summary to standard output,
+and exit with status zero.
+.TP
+.BI "\-f, \-\-file=" file
+Use
+.I file
+as the password database file or directory.
+If this is not specified,
+then the value of the
+.B PWSAFE
+environment variable is used;
+if that too is unset, then the default
+.IB home /.pwsafe \fR,
+is used, where
+.I home
+is the value of the
+.B HOME
+environment variable.
+It is a fatal error if
+.B HOME
+is unset.
+.PP
+The provided commands are described in their own sections below.
+.
+.SS "create"
+Create a new, empty password database.
+As a safety check,
+the file/directory named by the top-level
+.B \-f
+option (or default value)
+must not exist.
+.PP
+You will be prompted (twice) for a master password for the database.
+.PP
+The optional positional argument is the tag
+by which the database's master password
+will be known to the passphrase pixie.
+The default tag is
+.BR pwsafe .
+.PP
+The following options are accepted.
+.TP
+.BI "\-c, \-\-cipher=" cipher
+Use the Catacomb
+.I cipher
+to encrypt blobs.
+Run
+.B catcrypt show cipher
+for a list.
+The default is
+.BR rijndael-cbc .
+.TP
+.BI "\-d, \-\-database=" db-type
+Use the
+.I db-type
+database backend.
+See above for a list of the supported backends.
+Note that
+.B gdbm
+and
+.B sqlite
+depend on external modules, and may not be available.
+The default is
+.BR flat .
+.TP
+.BI "\-h, \-\-hash=" hash
+Use the Catacomb
+.I hash
+for key derivation and password-label mangling.
+Run
+.B catcrypt show hash
+for a list.
+The default is
+.BR sha256 .
+.TP
+.BI "\-m, \-\-mac=" mac
+Use the Catacomb
+.I mac
+to protect the integrity of blobs.
+Run
+.B catcrypt show mac
+for a list.
+The default is
+.IR hash -hmac
+where
+.I hash
+is the hash function chosen by the
+.B \-h
+option.
+.
+.SS "changepp"
+Change the master password for a database.
+This will
+.I not
+re-encrypt all of the records,
+so its utility is somewhat limited.
+See also the
+.B copy
+command.
+The program will prompt you for
+the existing master password (if it's not known by the pixie)
+and the new one (twice, always).
+.
+.SS "copy"
+Copy password records from the
+.I file
+to the
+.I dest-file
+which must be an existing password database.
+If a
+.B glob-pattern
+is given,
+then only records whose
+.I label
+matches the pattern are copied;
+otherwise all password records are copied.
+Any existing passwords in the destination database with the same labels
+will be overwritten.
+.PP
+The destination need not use the same database backend
+or cryptographic parameters as the source.
+.PP
+You will be prompted for the necessary master passwords.
+.
+.SS "delete"
+Delete the password with the given
+.I label
+from the database.
+An error is reported if there is no such password.
+.PP
+You will be prompted for master password if necessary.
+.
+.SS "find"
+Write the password with the given label to standard output,
+followed by a newline.
+.PP
+You will be prompted for master password if necessary.
+.PP
+This is the default operation:
+as a convenience,
+you can write
+.IP
+.B pwsafe
+.I label
+.PP
+rather than
+.IP
+.B pwsafe
+.B find
+.I label
+.PP
+if the
+.I label
+isn't the same as any of
+.BR pwsafe 's
+command names.
+.
+.SS "list"
+Write the labels of the passwords in the database,
+one per line,
+to standard output.
+(If labels contain newline characters,
+you will end up with a mess.)
+If a
+.I glob-pattern
+is supplied,
+then only labels which match the pattern are written.
+.PP
+You will be prompted for master password if necessary.
+.
+.SS "store"
+Store a password, associating it with the given
+.IR label .
+.PP
+If a
+.I value
+is supplied on the command line,
+then it is used as the password value.
+(Note that command-line arguments can be seen
+by other users of the system,
+and may be recorded by the shell.
+This is usually a bad idea.)
+.PP
+As a special case, if the
+.I value
+is
+.BR \- ,
+then the password is read from the first line of standard input;
+the trailing newline is removed.
+The author commonly writes
+.IP
+.BI "gorp -fbase64 | pwsafe store " label " \-"
+.PP
+to set random passwords.
+.PP
+Finally, if no
+.I value
+is given,
+then
+.B pwsafe
+will prompt twice for the password.
+.PP
+You will be prompted for the master password if necessary
+.I before
+the new password value is fetched.
+.PP
+If there is an existing password with the same
+.I label
+then it is overwritten.
+.
+.SS "topixie"
+With no arguments,
+store
+.I all
+of the passwords in the database in the pixie,
+with correspondingly named tags.
+This is probably a bad idea.
+.PP
+With a
+.IR label ,
+store only the labelled password in the pixie.
+With a
+.IR pixie-tag ,
+use that as the tag;
+otherwise use the
+.IR label .
+.PP
+You will be prompted for the master password if necessary.
+.
+.SS "xfer"
+Create a new database containing all of the records of an existing one.
+.PP
+This works at the storage-backend level.
+The new database contains exactly the same metadata and passwords
+as the original.
+It is
+.I not
+necessary to enter a password:
+the blobs are simply copied over without being decrypted.
+.PP
+The following options are accepted.
+.TP
+.BI "\-d, \-\-database=" db-type
+Use the
+.I db-type
+database backend.
+See above for a list of the supported backends.
+Note that
+.B gdbm
+and
+.B sqlite
+depend on external modules, and may not be available.
+The default is
+.BR flat .
+.
+.\"--------------------------------------------------------------------------
+.SH TECHNICAL DETAILS
+.
+The password database contains two kinds of records:
+.I metadata records
+hold important information about the database itself,
+and particularly the various cryptographic options
+chosen when the database was created,
+and the various internal keys used to secure the database;
+while
+.I password records
+actually store your encrypted passwords.
+The various backends store these kinds records in different ways;
+see below for the gory details.
+.
+.SS Metadata
+The metadata records are as follows.
+.TP
+.B cipher
+The symmetric cipher used to encrypt data.
+This names a Catacomb
+.B cipher
+class.
+.TP
+.B hash
+The hash function used in various places.
+This names a Catacomb
+.B hash
+class.
+.TP
+.B key
+A blob,
+protected by the
+.I derived
+keys (see below),
+containing the
+.I main
+secrecy and integrity keys.
+The blob payload consists of the main secrecy and integrity keys,
+each prefixed by its 16-bit length (in network byte order)
+and concatenated.
+.TP
+.B mac
+The message authentication code used to protect integrity.
+This names a Catacomb
+.B mac
+class.
+.TP
+.B magic
+A blob containing a string
+used to construct the database keys for password records;
+see below.
+The magic string is chosen at random
+when the database is created,
+and never changes;
+it is the same length as the chosen hash function's output.
+The blob is protected by the
+.I main
+keys.
+.TP
+.B salt
+A random string
+mixed into the key derivation process.
+.TP
+.B tag
+The passphrase tag,
+used to identify the master password
+to the passphrase
+.BR pixie (1).
+.
+.SS Keys
+The following keys are used.
+.TP
+The \fImaster password\fP
+Remembered (hopefully) by the user;
+used to unlock the
+.I main
+keys.
+.TP
+The \fIderived\fP keys
+A secrecy and integrity pair,
+derived from the
+.I master password
+and
+salt using a hash function.
+.TP
+The \fImain\fP keys
+A secrecy and integrity pair,
+kept in a blob in the database
+(the
+.B key
+metadata item)
+protected by the
+.I derived
+keys.
+The main keys are generated at random
+when the database is created
+and they never change;
+the Catacomb default key lengths are used.
+.
+.SS Deriving keys from the master password
+The keys used for protecting the
+.I main
+secrecy and integrity keys
+are derived by hashing strings of the form
+.IB purpose : password \e0 salt \fR,
+where
+.I purpose
+is
+.B cipher
+or
+.B mac
+to derive the secrecy and integrity keys, respectively.
+The
+.I salt
+string is the value of the
+.B salt
+metadata item described below.
+.PP
+No attempt is made to make the key derivation slow;
+.B pwsafe
+takes the view that you are have been specifically targetted for attack
+by a well-resourced adversary,
+and that you
+.I will
+lose if your password is guessable.
+.
+.SS Making a blob
+A
+.I blob
+contains a
+.IR payload ,
+protecting its secrecy and integrity.
+A blob is constructed using a pair of secrecy and integrity keys;
+most blobs are protected with the
+.I main
+keys;
+the main keys themselves are protected with the
+.I derived
+keys.
+.PP
+The steps to construct a blob are as follows.
+.hP 1.
+Choose a random IV of the appropriate length for the chosen
+.BR cipher .
+.hP 2.
+Encrypt the blob payload
+using the chosen
+.B cipher
+with the secrecy key
+and the IV from step 1,
+to form a ciphertext.
+Prefix the ciphertext with the IV
+to form an augmented ciphertext.
+.hP 3.
+Compute a tag over the augmented ciphertext from step 2
+using the chosen
+.B mac
+with the integrity key.
+Prefix the augmented ciphertext with the tag
+to form the blob.
+.PP
+(It seems more usual to put the tag on the end of the ciphertext,
+but that turned out to be pointlessly harder to implement.)
+.
+.SS Password records
+Conceptually,
+password records are indexed with a textual
+.I label
+chosen by the user.
+But users may want to not only keep their passwords secret,
+but also information about
+.I which
+passwords they have.
+The
+.B pwsafe
+program attempts to maintain the privacy of password record labels,
+but it isn't completely successful, as we shall see.
+Most critically,
+the database backends tend to leak information about
+the
+.I order
+in which records were added into the database.
+.PP
+At the database backend,
+the key used for looking up a password record is a hash,
+in binary:
+specifically, it's a hash of
+the record label
+prefixed by the
+.I magic
+string which is the payload of the blob stored in the
+.B magic
+metadata record.
+.PP
+The value of the password record is a blob,
+protected by the
+.I main
+keys,
+whose payload consists of
+.hP \*o
+the 16-bit network-byte-order length of the record label;
+.hP \*o
+the record label itself;
+.hP \*o
+the 16-bit network-byte-order length of the password;
+.hP \*o
+the password itself; and
+.hP \*o
+zero or more zero-valued octets,
+so as to make the payload a multiple of 256 octets long.
+.PP
+The padding serves to conceal the length of the label and password
+from an adversary who has obtained a copy of the database.
+.
+.SS Backend formats
+The various password-database backends
+represent the records described above as follows.
+.TP
+.B gdbm
+A GDBM-backed database is stored in a single file.
+A metadata record with key
+.I r
+and value
+.I v
+is stored in a GDBM record also named
+.IR r ,
+and with value
+.I v ;
+a password record with hash
+.I h
+and blob
+.I b
+is stored in a GDBM record named
+.BI $ h
+with value
+.IR b ,
+both in raw binary.
+.TP
+.B sqlite
+A SQLite-backed database is stored in a single file.
+It contains two tables,
+named
+.B meta
+and
+.BR passwd .
+The
+.B meta
+table has a primary key
+.B name
+and a further column
+.BR value ;
+a metadata record with key
+.I r
+and value
+.I v
+is held in a
+.B meta
+record
+with
+.B name
+.I r
+and
+.B value
+.IR v ;
+additionally, there is a record with
+.B name
+.B $version
+whose
+.B value
+is the schema version;
+this is currently always 0.
+The
+.B passwd
+table has a primary key
+.B label
+and a further column
+.BR payload ;
+a password record with hash
+.I h
+and blob
+.I b
+is stored in a
+.B passwd
+record with
+.B label
+.IR h
+and
+.B payload
+.IR b ,
+both in raw binary.
+.TP
+.B flat
+A flat-file-backed database is stored in a single file,
+with one record per line.
+The first line must be exactly
+.RS
+.IP
+.B "### pwsafe password database"
+.PP
+Blank lines and lines beginning with a
+.RB ` # '
+are ignored.
+.PP
+A metadata record named
+.I r
+with value
+.I v
+is stored as a line of the form
+.IB r\fR\(fm = v\fR\(fm
+where
+.IR r \fR\(fm
+and
+.IR v \fR\(fm
+are encodings of the strings
+.I r
+and
+.I v
+respectively.
+If
+.I r
+consists only of letters, digits,
+and the punctuation characters
+.RB ` \- ',
+.RB ` _ ',
+.RB ` : ',
+.RB ` . ',
+and
+.RB ` / '
+then
+.IR r \fR\(fm
+is simply
+.IR r ;
+otherwise
+.IR r \fR\(fm
+is formed by (simultaneously) replacing
+each space character in
+.I r
+with
+.RB ` + '
+and each other character
+which is not a letter, digit, or
+one of the punctuation characters listed above
+with
+.RB ` % '
+followed by that character's ASCII code in hex,
+and prefixing the whole lot by
+.RB ` ! '.
+Similarly,
+if
+.I v
+consists of letters, digits,
+and the punctuation characters listed above,
+then
+.IR v \fR\(fm
+is simply
+.IR v ;
+otherwise
+.IR v \fR\(fm
+consists of a
+.RB ` ? '
+followed by the base64 encoding of
+.IR v ,
+without any trailing
+.RB ` = '
+characters.
+.PP
+A password record with hash
+.I h
+and blob
+.I b
+is stored as a line of the form
+.BI $ h\fR\(fm = b\fR\(fm
+where
+.IR h \(fm
+and
+.IR b \(fm
+are the base64 encodings of
+.I h
+and
+.I b
+respectively,
+without trailing
+.RB ` = '
+characters.
+.PP
+The records may appear in any order.
+The file is completely rewritten when any change is made;
+if the file is named
+.I f
+then this is done by writing the new contents to
+.IB f .new
+and then renaming
+.IB f .new
+to
+.IR f .
+.RE
+.TP
+.B dir
+A directory-backed database is stored as a directory,
+named
+.I d
+in the sequel.
+The directory must contain a file
+.IB d /meta
+whose first line is
+.RS
+.IP
+.B "### pwsafe password directory metadata"
+.PP
+and directories
+.IB d /pw
+and
+.IB d /tmp \fR.
+.PP
+Metadata records are stored in file
+.IB d /meta
+with one record per line,
+exactly as for the
+.B file
+backend described above.
+.PP
+A password record with hash
+.I h
+and blob
+.I b
+is stored as file named
+.IB d /pw/ h \fR\(fm
+where
+.IR h \(fm
+is the base64 encodings of
+.I h
+without trailing
+.RB ` = '
+characters,
+and with all
+.RB ` / '
+characters
+replaced by
+.RB ` . 's,
+whose content is
+.IR b .
+.PP
+The directory
+.IB d /tmp/
+is used in an unspecified manner
+when creating new password-record files.
+The
+.IB d /meta
+and
+.IB d /pw/ h \fR\(fm
+files are updated by creating a new temporary file and renaming.
+.RE
+.
+.\"--------------------------------------------------------------------------
+.
+.SH BUGS
+This is quite an old program,
+though the manpage is new.
+It provides more footguns than is ideal.
+.
+.SH SEE ALSO
+.BR catcrypt (1),
+.BR pixie (1).
+.
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
+.
+.\"----- That's all, folks --------------------------------------------------
diff --git a/pyke/.skelrc b/pyke/.skelrc
new file mode 100644 (file)
index 0000000..21912db
--- /dev/null
@@ -0,0 +1,9 @@
+;;; -*-emacs-lisp-*-
+
+(setq skel-alist
+      (append
+       '((author . "Straylight/Edgeware")
+        (licence-text . "[[gpl]]")
+        (full-title . "Pyke: the Python Kit for Extensions")
+        (program . "Pyke"))
+       skel-alist))
similarity index 100%
rename from mapping-mLib.c
rename to pyke/mapping-mLib.c
similarity index 100%
rename from mapping.c
rename to pyke/mapping.c
similarity index 100%
rename from pyke-mLib.c
rename to pyke/pyke-mLib.c
similarity index 100%
rename from pyke-mLib.h
rename to pyke/pyke-mLib.h
similarity index 100%
rename from pyke.c
rename to pyke/pyke.c
similarity index 100%
rename from pyke.h
rename to pyke/pyke.h
diff --git a/rand.c b/rand.c
new file mode 100644 (file)
index 0000000..e72f450
--- /dev/null
+++ b/rand.c
@@ -0,0 +1,1569 @@
+/* -*-c-*-
+ *
+ * Random-number generators
+ *
+ * (c) 2004 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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "catacomb-python.h"
+PUBLIC_SYMBOLS;
+#include "algorithms.h"
+PRIVATE_SYMBOLS;
+
+/*----- Main code ---------------------------------------------------------*/
+
+PyTypeObject *grand_pytype;
+static PyTypeObject *truerand_pytype;
+static PyTypeObject *lcrand_pytype, *fibrand_pytype;
+static PyTypeObject *dsarand_pytype, *bbs_pytype, *bbspriv_pytype;
+static PyTypeObject *sslprf_pytype, *tlsdx_pytype, *tlsprf_pytype;
+PyObject *rand_pyobj;
+
+static PyObject *gccrands_dict;
+
+static PyObject *grand_dopywrap(PyTypeObject *ty, grand *r, unsigned f)
+{
+  grand_pyobj *g;
+
+  g = (grand_pyobj *)ty->tp_alloc(ty, 0);
+  g->r = r;
+  g->f = f;
+  return ((PyObject *)g);
+}
+
+PyObject *grand_pywrap(grand *r, unsigned f)
+{
+  PyTypeObject *ty = grand_pytype;
+  PyObject *ob;
+
+  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 = PyMapping_GetItemString
+                  (gccrands_dict, (/*unconst*/ char *)r->ops->name)) != 0)
+    ty = (PyTypeObject *)ob;
+  return (grand_dopywrap(ty, r, f));
+}
+
+CONVFUNC(grand, grand *, GRAND_R)
+
+static int grand_check(PyObject *me)
+{
+  if (!GRAND_R(me)) VALERR("random generator object is no longer valid");
+  return (0);
+end:
+  return (-1);
+}
+
+static PyObject *grmeth_byte(PyObject *me)
+{
+  if (grand_check(me)) return (0);
+  return (PyInt_FromLong(grand_byte(GRAND_R(me))));
+}
+
+static PyObject *grmeth_word(PyObject *me)
+{
+  if (grand_check(me)) return (0);
+  return (getulong(grand_word(GRAND_R(me))));
+}
+
+static PyObject *grmeth_range(PyObject *me, PyObject *arg)
+{
+  PyObject *m;
+  mp *x = 0;
+  mp *y = 0;
+
+  if (!PyArg_ParseTuple(arg, "O:range", &m)) return (0);
+  if (grand_check(me)) return (0);
+  if (PyInt_Check(m)) {
+    long mm = PyInt_AsLong(m);
+    if (mm == -1 && PyErr_Occurred()) PyErr_Clear();
+    else if (mm <= 0) goto notpos;
+    else if (mm <= 0xffffffff)
+      return (PyInt_FromLong(grand_range(GRAND_R(me), mm)));
+  }
+  if ((x = getmp(m)) == 0) goto end;
+  if (!MP_POSP(x)) goto notpos;
+  y = mprand_range(MP_NEW, x, GRAND_R(me), 0);
+  MP_DROP(x);
+  return (mp_pywrap(y));
+notpos:
+  VALERR("range must be strictly positive");
+end:
+  if (x) MP_DROP(x);
+  return (0);
+}
+
+static PyObject *grmeth_mp(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  size_t l;
+  mpw o = 0;
+  static const char *const kwlist[] = { "bits", "or", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:mp", KWLIST,
+                                  convszt, &l, convmpw, &o))
+    goto end;
+  if (grand_check(me)) return (0);
+  if (l < MPW_BITS && (o >> l)) VALERR("or mask too large");
+  return (mp_pywrap(mprand(MP_NEW, l, GRAND_R(me), o)));
+end:
+  return (0);
+}
+
+static PyObject *grmeth_block(PyObject *me, PyObject *arg)
+{
+  unsigned long n;
+  PyObject *rc = 0;
+
+  if (!PyArg_ParseTuple(arg, "O&:block", convulong, &n)) goto end;
+  if (grand_check(me)) return (0);
+  rc = bytestring_pywrap(0, n);
+  grand_fill(GRAND_R(me), BIN_PTR(rc), n);
+end:
+  return (rc);
+}
+
+static int checkop(grand *r, unsigned op, const char *what)
+{
+  if (r->ops->misc(r, GRAND_CHECK, op)) return (0);
+  PyErr_Format(PyExc_TypeError, "operation %s not supported", what);
+  return (-1);
+}
+
+static PyObject *grmeth_seedint(PyObject *me, PyObject *arg)
+{
+  int i;
+  grand *r = GRAND_R(me);
+  if (!PyArg_ParseTuple(arg, "i:seedint", &i) ||
+      grand_check(me) || checkop(r, GRAND_SEEDINT, "seedint"))
+    goto end;
+  r->ops->misc(r, GRAND_SEEDINT, i);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *grmeth_seedword(PyObject *me, PyObject *arg)
+{
+  uint32 u;
+  grand *r = GRAND_R(me);
+  if (!PyArg_ParseTuple(arg, "O&:seedword", convu32, &u) ||
+      grand_check(me) || checkop(r, GRAND_SEEDUINT32, "seedword"))
+    goto end;
+  r->ops->misc(r, GRAND_SEEDUINT32, u);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *grmeth_seedblock(PyObject *me, PyObject *arg)
+{
+  struct bin in;
+  grand *r = GRAND_R(me);
+  if (!PyArg_ParseTuple(arg, "O&:seedblock", convbin, &in) ||
+      grand_check(me) || checkop(r, GRAND_SEEDBLOCK, "seedblock"))
+    goto end;
+  r->ops->misc(r, GRAND_SEEDBLOCK, in.p, (size_t)in.sz);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *grmeth_seedmp(PyObject *me, PyObject *arg)
+{
+  PyObject *x;
+  mp *xx;
+  grand *r = GRAND_R(me);
+  if (!PyArg_ParseTuple(arg, "O:seedmp", &x) ||
+      grand_check(me) || checkop(r, GRAND_SEEDMP, "seedmp") ||
+      (xx = getmp(x)) == 0)
+    goto end;
+  r->ops->misc(r, GRAND_SEEDMP, xx);
+  MP_DROP(xx);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *grmeth_seedrand(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { "rng", 0 };
+  grand *r = GRAND_R(me);
+  grand *rr = &rand_global;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:seedrand", KWLIST,
+                                  convgrand, &rr) ||
+      grand_check(me) || checkop(r, GRAND_SEEDRAND, "seedrand"))
+    goto end;
+  r->ops->misc(r, GRAND_SEEDRAND, rr);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *grmeth_mask(PyObject *me, PyObject *arg)
+{
+  grand *r = GRAND_R(me);
+  struct bin in;
+  const octet *p; size_t n;
+  octet *q;
+  PyObject *rc;
+
+  if (!PyArg_ParseTuple(arg, "O&:mask", convbin, &in)) return (0);
+  if (grand_check(me)) return (0);
+  rc = bytestring_pywrap(0, in.sz);
+  q = (octet *)BIN_PTR(rc);
+  GR_FILL(r, q, in.sz);
+  p = in.p; n = in.sz; while (n--) *q++ ^= *p++;
+  return (rc);
+}
+
+static void grand_pydealloc(PyObject *me)
+{
+  grand_pyobj *g = (grand_pyobj *)me;
+  if ((g->f & f_freeme) && g->r) GR_DESTROY(g->r);
+  FREEOBJ(me);
+}
+
+static PyObject *grget_name(PyObject *me, void *hunoz)
+  { return (grand_check(me) ? 0 : TEXT_FROMSTR(GRAND_R(me)->ops->name)); }
+
+static PyObject *grget_cryptop(PyObject *me, void *hunoz)
+  { return (grand_check(me) ? 0 : getbool(GRAND_R(me)->ops->f & GRAND_CRYPTO)); }
+
+static const PyGetSetDef grand_pygetset[] = {
+#define GETSETNAME(op, name) gr##op##_##name
+  GET  (name,          "R.name -> name of this kind of generator")
+  GET  (cryptop,       "R.cryptop -> flag: cryptographically strong?")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef grand_pymethods[] = {
+#define METHNAME(name) grmeth_##name
+  NAMETH(byte,         "R.byte() -> BYTE")
+  NAMETH(word,         "R.word() -> WORD")
+  METH (block,         "R.block(N) -> STRING")
+  KWMETH(mp,           "R.mp(bits, [or = 0]) -> MP")
+  METH (range,         "R.range(MAX) -> INT")
+  METH (mask,          "R.mask(STR) -> STR")
+  METH (seedint,       "R.seedint(I)")
+  METH (seedword,      "R.seedword(I)")
+  METH (seedblock,     "R.seedblock(BYTES)")
+  METH (seedmp,        "R.seedmp(X)")
+  KWMETH(seedrand,     "R.seedrand(RR)")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject grand_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GRand",                             /* @tp_name@ */
+  sizeof(grand_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  grand_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Generic random number source.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(grand),                    /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(grand),                     /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *lcrand_pynew(PyTypeObject *me, PyObject *arg, PyObject *kw)
+{
+  uint32 n = 0;
+  static const char *const kwlist[] = { "seed", 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", KWLIST, convu32, &n))
+    return (0);
+  return (grand_dopywrap(lcrand_pytype, lcrand_create(n), f_freeme));
+}
+
+static const PyTypeObject lcrand_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "LCRand",                            /* @tp_name@ */
+  sizeof(grand_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  grand_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "LCRand([seed = 0]): linear congruential generator.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  lcrand_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *fibrand_pynew(PyTypeObject *me, PyObject *arg, PyObject *kw)
+{
+  uint32 n = 0;
+  static const char *const kwlist[] = { "seed", 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:new", KWLIST, convu32, &n))
+    return (0);
+  return (grand_dopywrap(fibrand_pytype, fibrand_create(n), f_freeme));
+}
+
+static const PyTypeObject fibrand_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "FibRand",                           /* @tp_name@ */
+  sizeof(grand_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  grand_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "FibRand([seed = 0]): Fibonacci generator.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  fibrand_pynew,                       /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- True random generator ---------------------------------------------*/
+
+static PyObject *trmeth_gate(PyObject *me)
+  { grand *r = GRAND_R(me); r->ops->misc(GRAND_R(me), RAND_GATE); RETURN_ME; }
+
+static PyObject *trmeth_stretch(PyObject *me)
+  { grand *r = GRAND_R(me); r->ops->misc(r, RAND_STRETCH); RETURN_ME; }
+
+static PyObject *trmeth_add(PyObject *me, PyObject *arg)
+{
+  grand *r = GRAND_R(me);
+  struct bin in; unsigned goodbits;
+  if (!PyArg_ParseTuple(arg, "O&O&:add", convbin, &in, convuint, &goodbits))
+    return (0);
+  r->ops->misc(r, RAND_ADD, in.p, (size_t)in.sz, goodbits);
+  RETURN_ME;
+}
+
+static PyObject *trmeth_key(PyObject *me, PyObject *arg)
+{
+  grand *r = GRAND_R(me);
+  struct bin k;
+  if (!PyArg_ParseTuple(arg, "O&:key", convbin, &k)) return (0);
+  r->ops->misc(r, RAND_KEY, k.p, (size_t)k.sz);
+  RETURN_ME;
+}
+
+static PyObject *trmeth_seed(PyObject *me, PyObject *arg)
+{
+  grand *r = GRAND_R(me);
+  unsigned u;
+  if (!PyArg_ParseTuple(arg, "O&:seed", convuint, &u)) return (0);
+  if (u > RAND_IBITS) VALERR("pointlessly large");
+  r->ops->misc(r, RAND_SEED, u);
+  RETURN_ME;
+end:
+  return (0);
+}
+
+static PyObject *trmeth_timer(PyObject *me)
+  { grand *r = GRAND_R(me); r->ops->misc(r, RAND_TIMER); RETURN_ME; }
+
+static PyObject *truerand_pynew(PyTypeObject *ty,
+                               PyObject *arg, PyObject *kw)
+{
+  static const char *const kwlist[] = { 0 };
+  grand *r;
+  PyObject *rc = 0;
+  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);
+  rc = grand_dopywrap(ty, r, f_freeme);
+end:
+  return (rc);
+}
+
+static const PyMethodDef truerand_pymethods[] = {
+#define METHNAME(name) trmeth_##name
+  NAMETH(gate,         "R.gate()")
+  NAMETH(stretch,      "R.stretch()")
+  METH (key,           "R.key(BYTES)")
+  METH (seed,          "R.seed(NBITS)")
+  METH (add,           "R.add(BYTES, GOODBITS")
+  NAMETH(timer,                "R.timer()")
+#undef METHNAME
+  { 0 }
+};
+
+static PyObject *trget_goodbits(PyObject *me, void *hunoz)
+{
+  grand *r = GRAND_R(me);
+  return (PyInt_FromLong(r->ops->misc(r, RAND_GOODBITS)));
+}
+
+static const PyGetSetDef truerand_pygetset[] = {
+#define GETSETNAME(op, name) tr##op##_##name
+  GET  (goodbits,      "R.goodbits -> good bits of entropy remaining")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject truerand_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "TrueRand",                          /* @tp_name@ */
+  sizeof(grand_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  grand_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "TrueRand(): true random number source.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(truerand),                 /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(truerand),                  /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  truerand_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Generators from symmetric encryption algorithms -------------------*/
+
+static PyTypeObject *gccrand_pytype, *gcrand_pytype, *gclatinrand_pytype;
+
+typedef grand *gcrand_func(const void *, size_t sz);
+typedef grand *gcirand_func(const void *, size_t sz, uint32);
+typedef grand *gcnrand_func(const void *, size_t sz, const void *);
+typedef grand *gcshakerand_func(const void *, size_t,
+                               const void *, size_t,
+                               const void *, size_t);
+typedef grand *gcshafuncrand_func(const void *, size_t,
+                                 const void *, size_t);
+typedef grand *gckmacrand_func(const void *, size_t, const void *, size_t);
+typedef struct gccrand_info {
+  const char *name;
+  const octet *keysz;
+  unsigned f;
+  size_t noncesz;
+  gcrand_func *func;
+} gccrand_info;
+
+#define RNGF_MASK 255u
+
+enum {
+  RNG_PLAIN = 0,
+  RNG_SEAL,
+  RNG_LATIN,
+  RNG_SHAKE,
+  RNG_KMAC
+};
+
+typedef struct gccrand_pyobj {
+  PyHeapTypeObject ty;
+  const gccrand_info *info;
+} gccrand_pyobj;
+#define GCCRAND_INFO(o) (((gccrand_pyobj *)(o))->info)
+
+#define GCCRAND_DEF(name, ksz, func, f, nsz)                           \
+  static const gccrand_info func##_info =                              \
+    { name, ksz, f, nsz, (gcrand_func *)func };
+RNGS(GCCRAND_DEF)
+
+static const gccrand_info *const gcrandtab[] = {
+#define GCCRAND_ENTRY(name, ksz, func, f, nsz) &func##_info,
+  RNGS(GCCRAND_ENTRY)
+  0
+};
+
+static PyObject *gcrand_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  const gccrand_info *info = GCCRAND_INFO(ty);
+  static const char *const kwlist[] = { "key", 0 };
+  struct bin k;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&:new", KWLIST,  convbin, &k))
+    goto end;
+  if (keysz(k.sz, info->keysz) != k.sz) VALERR("bad key length");
+  return (grand_dopywrap(ty, info->func(k.p, k.sz), f_freeme));
+end:
+  return (0);
+}
+
+static PyObject *gcirand_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  const gccrand_info *info = GCCRAND_INFO(ty);
+  uint32 i = 0;
+  static const char *const kwlist[] = { "key", "i", 0 };
+  struct bin k;
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new", KWLIST,
+                                  convbin, &k, convu32, &i))
+    goto end;
+  if (keysz(k.sz, info->keysz) != k.sz) VALERR("bad key length");
+  return (grand_dopywrap(ty,
+                        ((gcirand_func *)info->func)(k.p, k.sz, i),
+                        f_freeme));
+end:
+  return (0);
+}
+
+static PyObject *gcnrand_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  const gccrand_info *info = GCCRAND_INFO(ty);
+  static const char *const kwlist[] = { "key", "nonce", 0 };
+  static const octet zn[24] = { 0 };
+  struct bin k, n;
+
+  n.p = zn; n.sz = info->noncesz; assert(info->noncesz <= sizeof(zn));
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new", KWLIST,
+                                  convbin, &k, convbin, &n))
+    goto end;
+  if (keysz(k.sz, info->keysz) != k.sz) VALERR("bad key length");
+  if (n.sz != info->noncesz) VALERR("bad nonce length");
+  return (grand_dopywrap(ty,
+                        ((gcnrand_func *)info->func)(k.p, k.sz, n.p),
+                        f_freeme));
+end:
+  return (0);
+}
+
+static PyObject *gcshakyrand_pynew(PyTypeObject *ty,
+                                  PyObject *arg, PyObject *kw)
+{
+  const gccrand_info *info = GCCRAND_INFO(ty);
+  static const char
+    *const kwlist_shake[] = { "key", "func", "perso", 0 },
+    *const kwlist_func[] = { "key", "perso", 0 };
+  struct bin k, f = { 0, 0 }, p = { 0, 0 };
+
+  if ((info->f&RNGF_MASK) == RNG_SHAKE
+       ? !PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&O&:new",
+                                      (/*unconst*/ char **)kwlist_shake,
+                                      convbin, &k,
+                                      convbin, &f, convbin, &p)
+       : !PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new",
+                                      (/*unconst*/ char **)kwlist_func,
+                                      convbin, &k, convbin, &p))
+    goto end;
+  if (keysz(k.sz, info->keysz) != k.sz) VALERR("bad key length");
+  return (grand_dopywrap(ty,
+                        (info->f&RNGF_MASK) == RNG_SHAKE
+                          ? ((gcshakerand_func *)info->func)(f.p, f.sz,
+                                                             p.p, p.sz,
+                                                             k.p, k.sz)
+                          : ((gcshafuncrand_func *)info->func)(p.p, p.sz,
+                                                               k.p, k.sz),
+                        f_freeme));
+end:
+  return (0);
+}
+
+static PyObject *gccrand_pywrap(const gccrand_info *info)
+{
+  gccrand_pyobj *g = newtype(gccrand_pytype, 0, info->name);
+  g->info = info;
+  g->ty.ht_type.tp_basicsize = sizeof(grand_pyobj);
+  switch (info->f&RNGF_MASK) {
+    case RNG_LATIN: g->ty.ht_type.tp_base = gclatinrand_pytype; break;
+    default: g->ty.ht_type.tp_base = gcrand_pytype; break;
+  }
+  Py_INCREF(g->ty.ht_type.tp_base);
+  g->ty.ht_type.tp_flags = (Py_TPFLAGS_DEFAULT |
+                           Py_TPFLAGS_BASETYPE |
+                           Py_TPFLAGS_HEAPTYPE);
+  g->ty.ht_type.tp_alloc = PyType_GenericAlloc;
+  g->ty.ht_type.tp_free = 0;
+  switch (info->f&RNGF_MASK) {
+    case RNG_LATIN: g->ty.ht_type.tp_new = gcnrand_pynew; break;
+    case RNG_SEAL: g->ty.ht_type.tp_new = gcirand_pynew; break;
+    case RNG_SHAKE: case RNG_KMAC:
+      g->ty.ht_type.tp_new = gcshakyrand_pynew; break;
+    default: g->ty.ht_type.tp_new = gcrand_pynew; break;
+  }
+  typeready(&g->ty.ht_type);
+  return ((PyObject *)g);
+}
+
+static PyObject *gccrget_name(PyObject *me, void *hunoz)
+  { return (TEXT_FROMSTR(GCCRAND_INFO(me)->name)); }
+static PyObject *gccrget_keysz(PyObject *me, void *hunoz)
+  { return (keysz_pywrap(GCCRAND_INFO(me)->keysz)); }
+
+static PyObject *gclrmeth_tell(PyObject *me)
+{
+  grand *r = GRAND_R(me);
+  PyObject *rc = 0;
+  kludge64 off;
+
+  r->ops->misc(r, SALSA20_TELLU64, &off);
+  rc = getk64(off);
+  return (rc);
+}
+
+static PyObject *gclrmeth_seek(PyObject *me, PyObject *arg)
+{
+  grand *r = GRAND_R(me);
+  kludge64 off;
+
+  if (!PyArg_ParseTuple(arg, "O&:seek", convk64, &off)) return (0);
+  r->ops->misc(r, SALSA20_SEEKU64, off);
+  RETURN_ME;
+}
+
+static const PyGetSetDef gccrand_pygetset[] = {
+#define GETSETNAME(op, name) gccr##op##_##name
+  GET  (keysz,         "CR.keysz -> acceptable key sizes")
+  GET  (name,          "CR.name -> name of this kind of generator")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyMethodDef gclatinrand_pymethods[] = {
+#define METHNAME(name) gclrmeth_##name
+  NAMETH(tell,         "R.tell() -> OFF")
+  METH (seek,          "R.seek(OFF)")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject gccrand_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GCCRand",                           /* @tp_name@ */
+  sizeof(gccrand_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  0,                                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Metaclass for symmetric crypto-based generators.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gccrand),                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject gcrand_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GCRand",                            /* @tp_name@ */
+  sizeof(grand_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  grand_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Abstract base class for symmetric crypto-based generators.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject gclatinrand_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GCLatinRand",                       /* @tp_name@ */
+  sizeof(grand_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  grand_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Abstract base class for symmetric crypto-based generators.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gclatinrand),              /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- SSL and TLS generators --------------------------------------------*/
+
+static PyObject *sslprf_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  struct bin k, s;
+  const gchash *hco = &md5, *hci = &sha;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "key", "seed", "ohash", "ihash", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&O&:new", KWLIST,
+                                  convbin, &k, convbin, &s,
+                                  convgchash, &hco, convgchash, &hci))
+    goto end;
+  rc = grand_dopywrap(ty, sslprf_rand(hco, hci, k.p, k.sz, s.p, s.sz),
+                     f_freeme);
+end:
+  return (rc);
+}
+
+static PyObject *tlsdx_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  struct bin k, s;
+  const gcmac *mc = &sha_hmac;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "key", "seed", "mac", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&:new", KWLIST,
+                                  convbin, &k, convbin, &s,
+                                  convgcmac, &mc))
+    goto end;
+  rc = grand_dopywrap(ty, tlsdx_rand(mc, k.p, k.sz, s.p, s.sz), f_freeme);
+end:
+  return (rc);
+}
+
+static PyObject *tlsprf_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  struct bin k, s;
+  const gcmac *mcl = &md5_hmac, *mcr = &sha_hmac;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "key", "seed", "lmac", "rmac", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&O&:new", KWLIST,
+                                  convbin, &k, convbin, &s,
+                                  convgcmac, &mcl, convgcmac, &mcr))
+    goto end;
+  rc = grand_dopywrap(ty, tlsprf_rand(mcl, mcr, k.p, k.sz, s.p, s.sz),
+                     f_freeme);
+end:
+  return (rc);
+}
+
+static const PyTypeObject sslprf_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "SSLRand",                           /* @tp_name@ */
+  sizeof(grand_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  grand_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "SSLRand(KEY, SEED, [ohash = md5], [ihash = sha]):\n"
+  "  RNG for SSL master secret.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  sslprf_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject tlsdx_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "TLSDataExpansion",                  /* @tp_name@ */
+  sizeof(grand_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  grand_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "TLSDataExpansion(KEY, SEED, [mac = sha_hmac]):\n"
+  "  TLS data expansion function.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  tlsdx_pynew,                         /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static const PyTypeObject tlsprf_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "TLSPRF",                            /* @tp_name@ */
+  sizeof(grand_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  grand_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "TLSPRF(KEY, SEED, [lmac = md5_hmac], [rmac = sha_hmac]):\n"
+  "  TLS pseudorandom function.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  tlsprf_pynew,                                /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- DSA generator -----------------------------------------------------*/
+
+static PyObject *dsarand_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  struct bin in;
+  unsigned passes = 1;
+  grand *r;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "seed", "passes", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new", KWLIST,
+                                  convbin, &in, convuint, &passes))
+    goto end;
+  if (!passes) VALERR("must be positive");
+  r = dsarand_create(in.p, in.sz);
+  if (passes != 1) r->ops->misc(r, DSARAND_PASSES, passes);
+  rc = grand_dopywrap(ty, r, f_freeme);
+end:
+  return (rc);
+}
+
+static PyObject *drget_seed(PyObject *me, void *hunoz)
+{
+  grand *r = GRAND_R(me);
+  int n = r->ops->misc(r, DSARAND_SEEDSZ);
+  PyObject *rc = bytestring_pywrap(0, n);
+  r->ops->misc(r, DSARAND_GETSEED, BIN_PTR(rc));
+  return (rc);
+}
+
+static PyObject *drget_passes(PyObject *me, void *hunoz)
+{
+  grand *r = GRAND_R(me);
+  return (PyInt_FromLong(r->ops->misc(r, DSARAND_PASSES, 0)));
+}
+
+static int drset_passes(PyObject *me, PyObject *val, void *hunoz)
+{
+  grand *r = GRAND_R(me);
+  long n;
+  int rc = -1;
+
+  if (!val) NIERR("__del__");
+  n = PyInt_AsLong(val); if (n == -1 && PyErr_Occurred()) goto end;
+  if (n <= 0) VALERR("must be positive");
+  if (n > ULONG_MAX) VALERR("out of range");
+  r->ops->misc(r, DSARAND_PASSES, (unsigned)n);
+  rc = 0;
+end:
+  return (rc);
+}
+
+static const PyGetSetDef dsarand_pygetset[] = {
+#define GETSETNAME(op, name) dr##op##_##name
+  GET  (seed,          "R.seed -> current generator seed")
+  GETSET(passes,       "R.passes -> number of passes to create output")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject dsarand_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "DSARand",                           /* @tp_name@ */
+  sizeof(grand_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  grand_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "DSARand(SEED): pseudorandom number generator for DSA parameters.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(dsarand),                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  dsarand_pynew,                       /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Blum-Blum-Shub generator ------------------------------------------*/
+
+static PyObject *bbs_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
+{
+  mp *n = 0, *x = MP_TWO;
+  PyObject *rc = 0;
+  static const char *const kwlist[] = { "n", "x", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&:new", KWLIST,
+                                  convmp, &n, convmp, &x))
+    goto end;
+  rc = grand_dopywrap(ty, bbs_rand(n, x), f_freeme);
+end:
+  mp_drop(n);
+  mp_drop(x);
+  return (rc);
+}
+
+static PyObject *bbsmeth_step(PyObject *me)
+  { grand *r = GRAND_R(me); r->ops->misc(r, BBS_STEP); RETURN_ME; }
+
+static PyObject *bbsmeth_bits(PyObject *me, PyObject *arg)
+{
+  grand *r = GRAND_R(me); unsigned n; uint32 w;
+  if (!PyArg_ParseTuple(arg, "O&:bits", convuint, &n)) goto end;
+  if (n > 32) VALERR("can't get more than 32 bits");
+  r->ops->misc(r, BBS_BITS, n, &w); return (getulong(w));
+end:
+  return (0);
+}
+
+static PyObject *bbsmeth_wrap(PyObject *me)
+  { grand *r = GRAND_R(me); r->ops->misc(r, BBS_WRAP); RETURN_ME; }
+
+static PyObject *bbsget_n(PyObject *me, void *hunoz)
+{
+  mp *x = MP_NEW; grand *r = GRAND_R(me);
+  r->ops->misc(r, BBS_MOD, &x); return (mp_pywrap(x));
+}
+
+static PyObject *bbsget_x(PyObject *me, void *hunoz)
+{
+  mp *x = MP_NEW; grand *r = GRAND_R(me);
+  r->ops->misc(r, BBS_STATE, &x); return (mp_pywrap(x));
+}
+
+static int bbsset_x(PyObject *me, PyObject *val, void *hunoz)
+{
+  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);
+}
+
+static PyObject *bbsget_stepsz(PyObject *me, void *hunoz)
+{
+  grand *r = GRAND_R(me);
+  return (PyInt_FromLong(r->ops->misc(r, BBS_STEPSZ)));
+}
+
+static const PyMethodDef bbs_pymethods[] = {
+#define METHNAME(name) bbsmeth_##name
+  NAMETH(step,         "R.step(): steps the generator (not useful)")
+  METH (bits,   "R.bits(N) -> W: returns N bits (<= 32) from the generator")
+  NAMETH(wrap,         "R.wrap(): flushes unused bits in internal buffer")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyGetSetDef bbs_pygetset[] = {
+#define GETSETNAME(op, name) bbs##op##_##name
+  GET  (n,             "R.n -> Blum modulus")
+  GETSET(x,            "R.x -> current seed value")
+  GET  (stepsz,        "R.stepsz -> number of bits generated per step")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject bbs_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "BlumBlumShub",                      /* @tp_name@ */
+  sizeof(grand_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  grand_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "BlumBlumShub(N, [x = 2]): Blum-Blum-Shub pseudorandom number generator.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(bbs),                      /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(bbs),                       /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  bbs_pynew,                           /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+typedef struct bbspriv_pyobj {
+  grand_pyobj gr;
+  bbs_priv bp;
+} bbspriv_pyobj;
+
+#define BBSPRIV_BP(o) (&((bbspriv_pyobj *)(o))->bp)
+
+static PyObject *bbspriv_pynew(PyTypeObject *ty,
+                              PyObject *arg, PyObject *kw)
+{
+  mp *p = 0, *q = 0, *n = 0, *x = MP_TWO;
+  bbspriv_pyobj *rc = 0;
+  static const char *const kwlist[] = { "n", "p", "q", "seed", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&O&O&O&:new", KWLIST,
+                                  convmp, &n, convmp, &p, convmp, &q,
+                                  convmp, &x))
+    goto end;
+  if (!n + !p + !q > 1) VALERR("must specify at least two of n, p, q");
+  if (!n) n = mp_mul(MP_NEW, p, q);
+  else if (!p) mp_div(&p, 0, n, q);
+  else if (!q) mp_div(&q, 0, n, p);
+  rc = (bbspriv_pyobj *)ty->tp_alloc(ty, 0);
+  rc->gr.r = bbs_rand(n, x);
+  rc->gr.f = f_freeme;
+  rc->bp.p = MP_COPY(p);
+  rc->bp.q = MP_COPY(q);
+  rc->bp.n = MP_COPY(n);
+end:
+  mp_drop(p); mp_drop(q); mp_drop(n); mp_drop(x);
+  return ((PyObject *)rc);
+}
+
+static PyObject *bpmeth_generate(PyObject *me, PyObject *arg, PyObject *kw)
+{
+  bbs_priv bp = { 0 };
+  mp *x = MP_TWO;
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
+  unsigned nbits, n = 0;
+  grand *r = &rand_global;
+  static const char *const kwlist[] =
+    { "nbits", "event", "rng", "nsteps", "seed", 0 };
+  bbspriv_pyobj *rc = 0;
+
+  evt.exc = &exc;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&|O&O&O&O&:generate", KWLIST,
+                                  convuint, &nbits, convpgev, &evt,
+                                  convgrand, &r, convuint, &n, convmp, &x))
+    goto end;
+  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;
+  rc->bp.p = MP_COPY(bp.p);
+  rc->bp.q = MP_COPY(bp.q);
+  rc->bp.n = MP_COPY(bp.n);
+end:
+  mp_drop(bp.p); mp_drop(bp.q); mp_drop(bp.n); mp_drop(x);
+  return ((PyObject *)rc);
+}
+
+static void bbspriv_pydealloc(PyObject *me)
+{
+  bbs_priv *bp = BBSPRIV_BP(me);
+  mp_drop(bp->n);
+  mp_drop(bp->p);
+  mp_drop(bp->q);
+  grand_pydealloc(me);
+}
+
+static PyObject *bpmeth_ff(PyObject *me, PyObject *arg)
+{
+  mp *n = 0; grand *r = GRAND_R(me); bbs_priv *bp = BBSPRIV_BP(me);
+  if (!PyArg_ParseTuple(arg, "O&:ff", convmp, &n)) return (0);
+  r->ops->misc(r, BBS_FF, bp, n); RETURN_ME;
+}
+
+static PyObject *bpmeth_rew(PyObject *me, PyObject *arg)
+{
+  mp *n = 0; grand *r = GRAND_R(me); bbs_priv *bp = BBSPRIV_BP(me);
+  if (!PyArg_ParseTuple(arg, "O&:rew", convmp, &n)) return (0);
+  r->ops->misc(r, BBS_REW, bp, n); RETURN_ME;
+}
+
+static PyObject *bpget_n(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(BBSPRIV_BP(me)->n))); }
+
+static PyObject *bpget_p(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(BBSPRIV_BP(me)->p))); }
+
+static PyObject *bpget_q(PyObject *me, void *hunoz)
+  { return (mp_pywrap(MP_COPY(BBSPRIV_BP(me)->q))); }
+
+static const PyMethodDef bbspriv_pymethods[] = {
+#define METHNAME(name) bpmeth_##name
+  METH (ff,            "R.ff(N): fast-forward N places")
+  METH (rew,           "R.rew(N): rewind N places")
+  KWSMTH(generate,     "generate(NBITS, [event = pgen_nullev], "
+                             "[rng = rand], [nsteps = 0], [seed = 2]) -> R")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyGetSetDef bbspriv_pygetset[] = {
+#define GETSETNAME(op, name) bp##op##_##name
+  GET  (n,             "R.n -> Blum modulus")
+  GET  (p,             "R.p -> one of the factors of the modulus")
+  GET  (q,             "R.q -> one of the factors of the modulus")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject bbspriv_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "BBSPriv",                           /* @tp_name@ */
+  sizeof(bbspriv_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  bbspriv_pydealloc,                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "BBSPriv(..., [seed = 2]): Blum-Blum-Shub, with private key.\n"
+  "  Keywords: n, p, q; must provide at least two",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(bbspriv),                  /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(bbspriv),                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  bbspriv_pynew,                       /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Global stuff ------------------------------------------------------*/
+
+static const struct nameval consts[] = {
+  CONST(RAND_IBITS),
+  { 0 }
+};
+
+void rand_pyinit(void)
+{
+  INITTYPE(grand, root);
+  INITTYPE(truerand, grand);
+  INITTYPE(fibrand, grand);
+  INITTYPE(lcrand, grand);
+  INITTYPE(dsarand, grand);
+  INITTYPE(bbs, grand);
+  INITTYPE(bbspriv, bbs);
+  INITTYPE(sslprf, grand);
+  INITTYPE(tlsdx, grand);
+  INITTYPE(tlsprf, grand);
+  INITTYPE(gccrand, type);
+  INITTYPE(gcrand, grand);
+  INITTYPE(gclatinrand, gcrand);
+  rand_noisesrc(RAND_GLOBAL, &noise_source);
+  rand_seed(RAND_GLOBAL, 160);
+}
+
+static const char *crand_namefn(const void *p)
+  { const gccrand_info *const *cls = p; return (*cls ? (*cls)->name : 0); }
+static PyObject *crand_valfn(const void *p)
+  { const gccrand_info *const *cls = p; return (gccrand_pywrap(*cls)); }
+
+void rand_pyinsert(PyObject *mod)
+{
+  INSERT("GRand", grand_pytype);
+  INSERT("TrueRand", truerand_pytype);
+  INSERT("LCRand", lcrand_pytype);
+  INSERT("FibRand", fibrand_pytype);
+  INSERT("SSLRand", sslprf_pytype);
+  INSERT("TLSDataExpansion", tlsdx_pytype);
+  INSERT("TLSPRF", tlsprf_pytype);
+  INSERT("DSARand", dsarand_pytype);
+  INSERT("BlumBlumShub", bbs_pytype);
+  INSERT("BBSPriv", bbspriv_pytype);
+  INSERT("GCCRand", gccrand_pytype);
+  INSERT("GCRand", gcrand_pytype);
+  INSERT("GCLatinRand", gclatinrand_pytype);
+  rand_pyobj = grand_pywrap(&rand_global, 0); Py_INCREF(rand_pyobj);
+  gccrands_dict = make_algtab(gcrandtab, sizeof(gccrand_info *),
+                             crand_namefn, crand_valfn);
+  INSERT("gccrands", gccrands_dict); Py_INCREF(gccrands_dict);
+  INSERT("rand", rand_pyobj);
+  setconstants(mod, consts);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/setup.py b/setup.py
new file mode 100755 (executable)
index 0000000..045ee3e
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,36 @@
+#! /usr/bin/python
+
+import distutils.core as DC
+import mdwsetup as MS
+
+MS.pkg_config('catacomb', '2.5.99~')
+MS.pkg_config('mLib', '2.4.99~')
+
+cat = DC.Extension('catacomb._base',
+                   ['catacomb.c', 'bytestring.c', 'buffer.c',
+                    'rand.c', 'algorithms.c', 'pubkey.c', 'pgen.c',
+                    'mp.c', 'field.c', 'ec.c', 'group.c', 'passphrase.c',
+                    'share.c', 'key.c',
+                    'pyke/pyke.c', 'pyke/pyke-mLib.c',
+                    'pyke/mapping.c', 'pyke/mapping-mLib.c'],
+                   ##extra_compile_args = ['-O0'],
+                   include_dirs = MS.uniquify(MS.INCLUDEDIRS),
+                   library_dirs = MS.uniquify(MS.LIBDIRS),
+                   libraries = MS.uniquify(MS.LIBS))
+
+MS.setup(name = 'catacomb-python',
+         description = 'Interface to Catacomb cryptographic library',
+         url = 'https://git.distorted.org.uk/~mdw/catacomb-python/',
+         author = 'Straylight/Edgeware',
+         author_email = 'mdw@distorted.org.uk',
+         license = 'GNU General Public License',
+         packages = ['catacomb'],
+         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])
diff --git a/share.c b/share.c
new file mode 100644 (file)
index 0000000..30bff92
--- /dev/null
+++ b/share.c
@@ -0,0 +1,642 @@
+/* -*-c-*-
+ *
+ * Secret sharing
+ *
+ * (c) 2005 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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "catacomb-python.h"
+
+/*----- GF(2^8)-based secret-sharing --------------------------------------*/
+
+typedef struct gfshare_pyobj {
+  PyObject_HEAD
+  gfshare s;
+} gfshare_pyobj;
+
+static PyTypeObject
+  *gfshare_pytype, *gfsharesplit_pytype, *gfsharejoin_pytype;
+#define GFSHARE_PYCHECK(o) PyObject_TypeCheck((o), gfshare_pytype)
+#define GFSHARESPLIT_PYCHECK(o) PyObject_TypeCheck((o), gfsharesplit_pytype)
+#define GFSHAREJOIN_PYCHECK(o) PyObject_TypeCheck((o), gfsharejoin_pytype)
+#define GFSHARE_S(o) (&((gfshare_pyobj *)(o))->s)
+
+static void gfshare_pydealloc(PyObject *me)
+{
+  gfshare_destroy(GFSHARE_S(me));
+  FREEOBJ(me);
+}
+
+static PyObject *gfsget_threshold(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GFSHARE_S(me)->t)); }
+static PyObject *gfsget_size(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GFSHARE_S(me)->sz)); }
+
+static const PyGetSetDef gfshare_pygetset[]= {
+#define GETSETNAME(op, name) gfs##op##_##name
+  GET  (threshold,     "S.threshold -> THRESHOLD")
+  GET  (size,          "S.size -> SECRETSZ")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject gfshare_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GFShare",                           /* @tp_name@ */
+  sizeof(gfshare_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  gfshare_pydealloc,                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Binary-field secret sharing base class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gfshare),                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *gfsharesplit_pynew(PyTypeObject *ty,
+                                   PyObject *arg, PyObject *kw)
+{
+  struct bin in;
+  unsigned t;
+  grand *r = &rand_global;
+  gfshare_pyobj *s;
+  static const char *const kwlist[] = { "threshold", "secret", "rng", 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&:new", KWLIST,
+                                  convuint, &t, convbin, &in,
+                                  convgrand, &r))
+    goto end;
+  if (!t || t > 255) VALERR("threshold must be nonzero and < 256");
+  s = (gfshare_pyobj *)ty->tp_alloc(ty, 0);
+  gfshare_create(&s->s, t, in.sz);
+  gfshare_mkshares(&s->s, r, in.p);
+  return ((PyObject *)s);
+end:
+  return (0);
+}
+
+static PyObject *gfsmeth_get(PyObject *me, PyObject *arg)
+{
+  unsigned i;
+  PyObject *rc = 0;
+  if (!PyArg_ParseTuple(arg, "O&:get", convuint, &i)) goto end;
+  if (i >= 255) VALERR("index must be < 255");
+  rc = bytestring_pywrap(0, GFSHARE_S(me)->sz);
+  gfshare_get(GFSHARE_S(me), i, BIN_PTR(rc));
+end:
+  return (rc);
+}
+
+static const PyMethodDef gfsharesplit_pymethods[] = {
+#define METHNAME(name) gfsmeth_##name
+  METH (get,           "S.get(I) -> SHARE")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject gfsharesplit_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GFShareSplit",                      /* @tp_name@ */
+  sizeof(gfshare_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  gfshare_pydealloc,                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "GFShareSplit(THRESHOLD, SECRET, [rng = rand]): binary-field sharing:\n"
+  "   split secret into shares.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gfsharesplit),             /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  gfsharesplit_pynew,                  /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *gfsharejoin_pynew(PyTypeObject *ty,
+                                  PyObject *arg, PyObject *kw)
+{
+  unsigned t, sz;
+  gfshare_pyobj *s;
+  static const char *const kwlist[] = { "threshold", "size", 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:new", KWLIST,
+                                  convuint, &t, convuint, &sz))
+    goto end;
+  if (!t || t > 255) VALERR("threshold must be nonzero and < 256");
+  s = (gfshare_pyobj *)ty->tp_alloc(ty, 0);
+  gfshare_create(&s->s, t, sz);
+  return ((PyObject *)s);
+end:
+  return (0);
+}
+
+static PyObject *gfsmeth_addedp(PyObject *me, PyObject *arg)
+{
+  unsigned i;
+  if (!PyArg_ParseTuple(arg, "O&:addedp", convuint, &i)) goto end;
+  if (i > 254) VALERR("index must be < 255");
+  return (getbool(gfshare_addedp(GFSHARE_S(me), i)));
+end:
+  return (0);
+}
+
+static PyObject *gfsmeth_add(PyObject *me, PyObject *arg)
+{
+  unsigned i;
+  struct bin s;
+  if (!PyArg_ParseTuple(arg, "O&O&:add", convuint, &i, convbin, &s))
+    goto end;
+  if (i > 254) VALERR("index must be < 255");
+  if (s.sz != GFSHARE_S(me)->sz) VALERR("bad share size");
+  if (gfshare_addedp(GFSHARE_S(me), i)) VALERR("this share already added");
+  if (GFSHARE_S(me)->i >= GFSHARE_S(me)->t) VALERR("enough shares already");
+  gfshare_add(GFSHARE_S(me), i, s.p);
+  return (PyInt_FromLong(GFSHARE_S(me)->t - GFSHARE_S(me)->i));
+end:
+  return (0);
+}
+
+static PyObject *gfsmeth_combine(PyObject *me)
+{
+  PyObject *rc = 0;
+  if (GFSHARE_S(me)->i < GFSHARE_S(me)->t) VALERR("not enough shares yet");
+  rc = bytestring_pywrap(0, GFSHARE_S(me)->sz);
+  gfshare_combine(GFSHARE_S(me), BIN_PTR(rc));
+end:
+  return (rc);
+}
+
+static const PyMethodDef gfsharejoin_pymethods[] = {
+#define METHNAME(name) gfsmeth_##name
+  METH (addedp,        "S.addedp(I) -> BOOL")
+  METH (add,           "S.add(I, SHARE) -> REMAIN")
+  NAMETH(combine,      "S.combine() -> SECRET")
+#undef METHNAME
+  { 0 }
+};
+
+static PyObject *gfsget_remain(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(GFSHARE_S(me)->t - GFSHARE_S(me)->i)); }
+
+static const PyGetSetDef gfsharejoin_pygetset[]= {
+#define GETSETNAME(op, name) gfs##op##_##name
+  GET  (remain,        "S.remain -> REMAIN")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject gfsharejoin_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "GFShareJoin",                       /* @tp_name@ */
+  sizeof(gfshare_pyobj),               /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  gfshare_pydealloc,                   /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "GFShareJoin(THRESHOLD, SIZE): binary field sharing:\n"
+  "  join shares to recover secret.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(gfsharejoin),              /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(gfsharejoin),               /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  gfsharejoin_pynew,                   /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Prime-field secret-sharing ----------------------------------------*/
+
+typedef struct share_pyobj {
+  PyObject_HEAD
+  share s;
+} share_pyobj;
+
+static PyTypeObject
+  *share_pytype, *sharesplit_pytype, *sharejoin_pytype;
+#define SHARE_PYCHECK(o) PyObject_TypeCheck((o), share_pytype)
+#define SHARESPLIT_PYCHECK(o) PyObject_TypeCheck((o), sharesplit_pytype)
+#define SHAREJOIN_PYCHECK(o) PyObject_TypeCheck((o), sharejoin_pytype)
+#define SHARE_S(o) (&((share_pyobj *)(o))->s)
+
+static void share_pydealloc(PyObject *me)
+{
+  share_destroy(SHARE_S(me));
+  FREEOBJ(me);
+}
+
+static PyObject *sget_threshold(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(SHARE_S(me)->t)); }
+static PyObject *sget_modulus(PyObject *me, void *hunoz)
+  { return (mp_pywrap(SHARE_S(me)->p)); }
+
+static const PyGetSetDef share_pygetset[]= {
+#define GETSETNAME(op, name) s##op##_##name
+  GET  (threshold,     "S.threshold -> THRESHOLD")
+  GET  (modulus,       "S.modulus -> MODULUS")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject share_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "Share",                             /* @tp_name@ */
+  sizeof(share_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  share_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "Prime-field secret sharing base class.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  0,                                   /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(share),                     /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  abstract_pynew,                      /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *sharesplit_pynew(PyTypeObject *ty,
+                                 PyObject *arg, PyObject *kw)
+{
+  mp *sec = 0;
+  unsigned t;
+  grand *r = &rand_global;
+  mp *m = 0;
+  share_pyobj *s;
+  static const char *const kwlist[] =
+    { "threshold", "secret", "modulus", "rng", 0 };
+
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&|O&O&:new", KWLIST,
+                                  convuint, &t, convmp, &sec,
+                                  convmp, &m, convgrand, &r))
+    goto end;
+  if (!t) VALERR("threshold must be nonzero");
+  s = (share_pyobj *)ty->tp_alloc(ty, 0);
+  share_create(&s->s, t);
+  s->s.p = m;
+  share_mkshares(&s->s, r, sec);
+  mp_drop(sec);
+  return ((PyObject *)s);
+end:
+  mp_drop(m);
+  mp_drop(sec);
+  return (0);
+}
+
+static PyObject *smeth_get(PyObject *me, PyObject *arg)
+{
+  unsigned i;
+  PyObject *rc = 0;
+  if (!PyArg_ParseTuple(arg, "O&:get", convuint, &i)) goto end;
+  rc = mp_pywrap(share_get(SHARE_S(me), MP_NEW, i));
+end:
+  return (rc);
+}
+
+static const PyMethodDef sharesplit_pymethods[] = {
+#define METHNAME(name) smeth_##name
+  METH (get,           "S.get(I) -> SHARE")
+#undef METHNAME
+  { 0 }
+};
+
+static const PyTypeObject sharesplit_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "ShareSplit",                                /* @tp_name@ */
+  sizeof(share_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  share_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "ShareSplit(THRESHOLD, SECRET, [modulus = ?], [rng = rand]):\n"
+  "  prime field secret sharing: split secret into shares.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(sharesplit),               /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  0,                                   /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  sharesplit_pynew,                    /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+static PyObject *sharejoin_pynew(PyTypeObject *ty,
+                                PyObject *arg, PyObject *kw)
+{
+  unsigned t;
+  mp *m = 0;
+  share_pyobj *s;
+  static const char *const kwlist[] = { "threshold", "modulus", 0 };
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O&O&:new", KWLIST,
+                                  convuint, &t, convmp, &m))
+    goto end;
+  if (!t) VALERR("threshold must be nonzero");
+  s = (share_pyobj *)ty->tp_alloc(ty, 0);
+  share_create(&s->s, t);
+  s->s.p = m;
+  return ((PyObject *)s);
+end:
+  mp_drop(m);
+  return (0);
+}
+
+static PyObject *smeth_addedp(PyObject *me, PyObject *arg)
+{
+  unsigned i;
+  if (!PyArg_ParseTuple(arg, "O&:addedp", convuint, &i)) goto end;
+  return (getbool(share_addedp(SHARE_S(me), i)));
+end:
+  return (0);
+}
+
+static PyObject *smeth_add(PyObject *me, PyObject *arg)
+{
+  unsigned i;
+  mp *s = 0;
+  PyObject *rc = 0;
+  if (!PyArg_ParseTuple(arg, "O&O&:add", convuint, &i, convmp, &s)) goto end;
+  if (MP_NEGP(s) || MP_CMP(s, >=, SHARE_S(me)->p))
+    VALERR("share out of range");
+  if (share_addedp(SHARE_S(me), i)) VALERR("this share already added");
+  if (SHARE_S(me)->i >= SHARE_S(me)->t) VALERR("enough shares already");
+  share_add(SHARE_S(me), i, s);
+  rc = PyInt_FromLong(SHARE_S(me)->t - SHARE_S(me)->i);
+end:
+  mp_drop(s);
+  return (rc);
+}
+
+static PyObject *smeth_combine(PyObject *me)
+{
+  PyObject *rc = 0;
+  if (SHARE_S(me)->i < SHARE_S(me)->t) VALERR("not enough shares yet");
+  rc = mp_pywrap(share_combine(SHARE_S(me)));
+end:
+  return (rc);
+}
+
+static const PyMethodDef sharejoin_pymethods[] = {
+#define METHNAME(name) smeth_##name
+  METH (addedp,        "S.addedp(I) -> BOOL")
+  METH (add,           "S.add(I, SHARE) -> REMAIN")
+  NAMETH(combine,      "S.combine() -> SECRET")
+#undef METHNAME
+  { 0 }
+};
+
+static PyObject *sget_remain(PyObject *me, void *hunoz)
+  { return (PyInt_FromLong(SHARE_S(me)->t - SHARE_S(me)->i)); }
+
+static const PyGetSetDef sharejoin_pygetset[]= {
+#define GETSETNAME(op, name) s##op##_##name
+  GET  (remain,        "S.remain -> REMAIN")
+#undef GETSETNAME
+  { 0 }
+};
+
+static const PyTypeObject sharejoin_pytype_skel = {
+  PyVarObject_HEAD_INIT(0, 0)          /* Header */
+  "ShareJoin",                         /* @tp_name@ */
+  sizeof(share_pyobj),                 /* @tp_basicsize@ */
+  0,                                   /* @tp_itemsize@ */
+
+  share_pydealloc,                     /* @tp_dealloc@ */
+  0,                                   /* @tp_print@ */
+  0,                                   /* @tp_getattr@ */
+  0,                                   /* @tp_setattr@ */
+  0,                                   /* @tp_compare@ */
+  0,                                   /* @tp_repr@ */
+  0,                                   /* @tp_as_number@ */
+  0,                                   /* @tp_as_sequence@ */
+  0,                                   /* @tp_as_mapping@ */
+  0,                                   /* @tp_hash@ */
+  0,                                   /* @tp_call@ */
+  0,                                   /* @tp_str@ */
+  0,                                   /* @tp_getattro@ */
+  0,                                   /* @tp_setattro@ */
+  0,                                   /* @tp_as_buffer@ */
+  Py_TPFLAGS_DEFAULT |                 /* @tp_flags@ */
+    Py_TPFLAGS_BASETYPE,
+
+  /* @tp_doc@ */
+  "ShareJoin(THRESHOLD, MODULUS): prime field secret sharing:\n"
+  "  join shares to recover secret.",
+
+  0,                                   /* @tp_traverse@ */
+  0,                                   /* @tp_clear@ */
+  0,                                   /* @tp_richcompare@ */
+  0,                                   /* @tp_weaklistoffset@ */
+  0,                                   /* @tp_iter@ */
+  0,                                   /* @tp_iternext@ */
+  PYMETHODS(sharejoin),                        /* @tp_methods@ */
+  0,                                   /* @tp_members@ */
+  PYGETSET(sharejoin),                 /* @tp_getset@ */
+  0,                                   /* @tp_base@ */
+  0,                                   /* @tp_dict@ */
+  0,                                   /* @tp_descr_get@ */
+  0,                                   /* @tp_descr_set@ */
+  0,                                   /* @tp_dictoffset@ */
+  0,                                   /* @tp_init@ */
+  PyType_GenericAlloc,                 /* @tp_alloc@ */
+  sharejoin_pynew,                     /* @tp_new@ */
+  0,                                   /* @tp_free@ */
+  0                                    /* @tp_is_gc@ */
+};
+
+/*----- Global stuff ------------------------------------------------------*/
+
+void share_pyinit(void)
+{
+  INITTYPE(gfshare, root);
+  INITTYPE(gfsharesplit, gfshare);
+  INITTYPE(gfsharejoin, gfshare);
+  INITTYPE(share, root);
+  INITTYPE(sharesplit, share);
+  INITTYPE(sharejoin, share);
+}
+
+void share_pyinsert(PyObject *mod)
+{
+  INSERT("GFShare", gfshare_pytype);
+  INSERT("GFShareSplit", gfsharesplit_pytype);
+  INSERT("GFShareJoin", gfsharejoin_pytype);
+  INSERT("Share", share_pytype);
+  INSERT("ShareSplit", sharesplit_pytype);
+  INSERT("ShareJoin", sharejoin_pytype);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/t/keyring b/t/keyring
new file mode 100644 (file)
index 0000000..65fcca4
--- /dev/null
+++ b/t/keyring
@@ -0,0 +1,4 @@
+60090be2:twofish encrypt,secret:w6Qmlm8zVc0Y6O4PwXfOrl4ru7+DTCrDD9pkbnh5xhsZc2CSRb3NKu+ihi9hykSkyXdSgn9G7E52xQUA/wyp4Nsp7X+Jg2X6bdvLag== forever forever -
+bd761d35:ec struct:[p=ec,public:0x42d95404921b2d8f19c761211753196901d7695049a96db2f0d7e00bd1dbf8ec,0x8a959136f7ca7e75718b9bc2d2ac3b0680daf4030288324816c8028cf8ebc8c4,private=encrypt,secret:/ddlDkc0+W5cfukYUHBxQoNNwxLmYnjRWYW+GnUwxmqdgv+8NTNIWN/GdahdsMT1GSnWKKGObd0Abz+tEiyJJa/hElI6JaxX/UirHahChpWbKTr/,curve=string,shared:nist%2dp256] forever forever -
+4a4e1ee7:ec-param struct:[curve=string,shared:nist%2dp256] forever forever -
+8599dbab:rsa:ron struct:[private=encrypt,secret:JER0qvxt53DtgAl03m+Rf4KggCkzT3YZ0JkrhJsuuvFVMYXK1gBjp4bk/zGMhWYjRKPVtNujiox3oSL933HwJLL7r0MVhUtZl+3zaS9DQ3c/lpGOMS8/VYDV84Ny1oU7IJtZakdTbRNd7YJapMq44SX5xamERBNEe1dslxVdLNVLn/Cc54fDNbhMVx/S3MrkuQaxF/wNFiL5nOezlbCVal5ln0RF7AXulIwnYkIeeC2P0h4oUfmhXWuiRAvk9bPFc5fMES1JruBk3YaQ7FzErIYV4vTMxotclwcAJvQDo5reUHQzAaaLXY80b3VlxhPQaKCcnZNcAJTDJRYh/OcSDt4AfVcTDqGToTXDfw/PL89KNI4R0vxF9Vqtyes4iPCwr6WJh4ZDBbT9r4WdIoAhO+2gFO1yHii3YbdAwPM4WrFE6caIkmgZHmkbc4v2rMz4DRGNOj1cegfz0xGTBFjvy/haTHy/tzV9qPb8N4VFgTvEroN7OwgS+l6GExlPH/JOff+hsOwcwfTUC0vJ/Cm8tOgL7guivADPL3ThGnqdt0/9S11MIoiYF51+nFH4JvcyJ7kjOI4EQbc50YcoGYVEhatUeUzOwMV4zxymlMGVUVZH509JC5ABCJLM4+qmklRT4G6K2kMHo9w32kJwBcAg5u2RR/2v0ALYWZ7FqAwjP40u3t2LrMeXStiAKs2YzkEqB/20kqlv1D+pasGVUUE3MvfHLm8JyrXjYG2erHs8BZRuXwz+DfZhmC9rZ6v7WaJFu2joIBcLMWouIU3WL5vpaNklDzBXfynZyPSwsgfmUUsi1xXWJPvUJH71IY5UAcHiWaaiBLIuJ3eCpqxR33LA6S8tJmnAC+VgwT9ATutVVW1CS++Q7mGdkgPWAJ3AkJL2lPMfousldvvknoS9Ybx1jJtpRyInFlgdjHoYpfe4DWXjoRDLQbkDemQNHHudog3n9GQ/kM1N/3xRcIxIBp0hzTFg+axMJU9pz+105/ymYQXIRkT9R3kglIa0l7G+uy760+kvh5VZp6wA4qmdqbvEQOl//pJ2hl52bntHd9w0Q61je8ByXSFxZeKqSkJkl1KSX8Z/JMiXWq1ea5enwWk5d9UX4aBgEtQdv2NCkirVSofrXIFvR3+VWfLwlPd2VcS3FAXU4LyjATiNZRp/0t0X81H/ozY6XzSXMmU8xs/FPnbVWL4fmXhspNbfK+uuib/Xd9lpzrxrNu+YbaNbtG97OIUwbtSakD+TbS3QTWvFwGNyIiuBm2t4R/M+vN1/qB+eTupGFmB/bFRv3H0h8fl4SaNSXqsZ6LTjWUQ+oQZ6Hu0Ri/qLtM62Dm3cou0yOASSMINDhScMOlxCcCONwpANefmMmx+HevmdGxSY8oKPMXysU7ZPFNYYPnqPhDg/4YW8Rga305ZwoWWV3kFcfeS5CxjOOtlsLDDsmDT5OYWCZczzseXS8Q2B5a8ZKJRmw7eAKW14yJmdk3HlLnBqGUAdtYlqXuEKR3c9QeIDJJsijEOxOyaTEL/Zp+mKQ9YRfmj/mPHbFJ1kJI9mkgosGdc9LUqCQpE5vsBCgmFljlD3Br7V651TtvnCeuOZGmlhuXz/79+0aEYfJ9/vjXA7S/W+AEdovbSGemATuuXIC792x6Mmy8hWt/U8YTB2kakbMWlJK/zWWFOn5gggc0c7n5D/BagZc6MwD6z8KmlLszgiBhBD904V66nzKQKoc5QJypK52SALhqfBQik8S5Ss4K/XEf7hgR5HNiZUNe3+Y07ru+zp3hejqgT8qJZg9YfZ6H/rkJ+41YdOf0UknyY8+Oeub0BQJquk3kLXWywCMtOYMaN391n/PP6T3GUVJlkhNk7LbrbjadwzLmB12CIgfI4AYDrCcR+/sLPTHOUXHne4F1xqEe1jb8/FWhxBOk+WqbBdgzr0nviHWWI=,n=integer,public:4588518152824847065809864017017980752300172722911691501294534688567191677622293877055275853619101138154946767929897875236974063360161364832318034181230762058282548891215880925776054212542819092223319882319288851559780722078087931927390589564076952023872334935403192026988691452623591714907960325366105508662452732895343767851573374920807255520857672360953324211494673508138823790217244758900285029386826393365768875455330392381958999387168679887837434942105961538360802941784841026202770984642852469173489052850527654973593434682838716079442275717555051916006906119658853435209224439445648915415615440379170113919377940230580258468751352164223236121841848896011526918868701912075848851443130268164520732142338177464314742042248866304723629029407667423868594096042267348555827683577338882853478253550493572887955844539064781262100314281062281504561302389580828642599153244753523052134755591791992951760425215644414834705106809,e=integer,public:65537] forever forever attr=value
diff --git a/t/t-algorithms.py b/t/t-algorithms.py
new file mode 100644 (file)
index 0000000..455e947
--- /dev/null
@@ -0,0 +1,870 @@
+### -*- mode: python, coding: utf-8 -*-
+###
+### Test symmetric algorithms
+###
+### (c) 2019 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.
+
+import catacomb as C
+import unittest as U
+import testutils as T
+
+###--------------------------------------------------------------------------
+### Utilities.
+
+def bad_key_size(ksz):
+  if isinstance(ksz, C.KeySZAny): return None
+  elif isinstance(ksz, C.KeySZRange):
+    if ksz.mod != 1: return ksz.min + 1
+    elif ksz.max is not None: return ksz.max + 1
+    elif ksz.min != 0: return ksz.min - 1
+    else: return None
+  elif isinstance(ksz, C.KeySZSet):
+    for sz in sorted(ksz.set):
+      if sz + 1 not in ksz.set: return sz + 1
+    assert False, "That should have worked."
+  else:
+    return None
+
+def different_key_size(ksz, sz):
+  if isinstance(ksz, C.KeySZAny): return sz + 1
+  elif isinstance(ksz, C.KeySZRange):
+    if sz > ksz.min: return sz - ksz.mod
+    elif ksz.max is None or sz < ksz.max: return sz + ksz.mod
+    else: return None
+  elif isinstance(ksz, C.KeySZSet):
+    for sz1 in sorted(ksz.set):
+      if sz != sz1: return sz1
+    return None
+  else:
+    return None
+
+class HashBufferTestMixin (U.TestCase):
+  """Mixin class for testing all of the various `hash...' methods."""
+
+  def check_hashbuffer_hashn(me, w, bigendp, makefn, hashfn):
+    """Check `hashuN'."""
+
+    ## Check encoding an integer.
+    h0, donefn0 = makefn(w + 2)
+    hashfn(h0.hashu8(0x00), T.bytes_as_int(w, bigendp)).hashu8(w + 1)
+    h1, donefn1 = makefn(w + 2)
+    h1.hash(T.span(w + 2))
+    me.assertEqual(donefn0(), donefn1())
+
+    ## Check overflow detection.
+    h0, _ = makefn(w)
+    me.assertRaises(OverflowError, hashfn, h0, 1 << 8*w)
+
+  def check_hashbuffer_bufn(me, w, bigendp, makefn, hashfn):
+    """Check `hashbufN'."""
+
+    ## Go through a number of different sizes.
+    for n in [0, 1, 7, 8, 19, 255, 12345, 65535, 123456]:
+      if n >= 1 << 8*w: continue
+      h0, donefn0 = makefn(2 + w + n)
+      hashfn(h0.hashu8(0x00), T.span(n)).hashu8(0xff)
+      h1, donefn1 = makefn(2 + w + n)
+      h1.hash(T.prep_lenseq(w, n, bigendp, True))
+      me.assertEqual(donefn0(), donefn1())
+
+    ## Check blocks which are too large for the length prefix.
+    if w <= 3:
+      n = 1 << 8*w
+      h0, _ = makefn(w + n)
+      me.assertRaises(ValueError, hashfn, h0, C.ByteString.zero(n))
+
+  def check_hashbuffer(me, makefn):
+    """Test the various `hash...' methods."""
+
+    ## Check `hashuN'.
+    me.check_hashbuffer_hashn(1, True, makefn, lambda h, n: h.hashu8(n))
+    me.check_hashbuffer_hashn(2, True, makefn, lambda h, n: h.hashu16(n))
+    me.check_hashbuffer_hashn(2, True, makefn, lambda h, n: h.hashu16b(n))
+    me.check_hashbuffer_hashn(2, False, makefn, lambda h, n: h.hashu16l(n))
+    me.check_hashbuffer_hashn(3, True, makefn, lambda h, n: h.hashu24(n))
+    me.check_hashbuffer_hashn(3, True, makefn, lambda h, n: h.hashu24b(n))
+    me.check_hashbuffer_hashn(3, False, makefn, lambda h, n: h.hashu24l(n))
+    me.check_hashbuffer_hashn(4, True, makefn, lambda h, n: h.hashu32(n))
+    me.check_hashbuffer_hashn(4, True, makefn, lambda h, n: h.hashu32b(n))
+    me.check_hashbuffer_hashn(4, False, makefn, lambda h, n: h.hashu32l(n))
+    if hasattr(makefn(0)[0], "hashu64"):
+      me.check_hashbuffer_hashn(8, True, makefn, lambda h, n: h.hashu64(n))
+      me.check_hashbuffer_hashn(8, True, makefn, lambda h, n: h.hashu64b(n))
+      me.check_hashbuffer_hashn(8, False, makefn, lambda h, n: h.hashu64l(n))
+
+    ## Check `hashbufN'.
+    me.check_hashbuffer_bufn(1, True, makefn, lambda h, x: h.hashbuf8(x))
+    me.check_hashbuffer_bufn(2, True, makefn, lambda h, x: h.hashbuf16(x))
+    me.check_hashbuffer_bufn(2, True, makefn, lambda h, x: h.hashbuf16b(x))
+    me.check_hashbuffer_bufn(2, False, makefn, lambda h, x: h.hashbuf16l(x))
+    me.check_hashbuffer_bufn(3, True, makefn, lambda h, x: h.hashbuf24(x))
+    me.check_hashbuffer_bufn(3, True, makefn, lambda h, x: h.hashbuf24b(x))
+    me.check_hashbuffer_bufn(3, False, makefn, lambda h, x: h.hashbuf24l(x))
+    me.check_hashbuffer_bufn(4, True, makefn, lambda h, x: h.hashbuf32(x))
+    me.check_hashbuffer_bufn(4, True, makefn, lambda h, x: h.hashbuf32b(x))
+    me.check_hashbuffer_bufn(4, False, makefn, lambda h, x: h.hashbuf32l(x))
+    if hasattr(makefn(0)[0], "hashbuf64"):
+      me.check_hashbuffer_bufn(8, True, makefn, lambda h, x: h.hashbuf64(x))
+      me.check_hashbuffer_bufn(8, True, makefn, lambda h, x: h.hashbuf64b(x))
+      me.check_hashbuffer_bufn(8, False, makefn, lambda h, x: h.hashbuf64l(x))
+
+###--------------------------------------------------------------------------
+class TestKeysize (U.TestCase):
+
+  def test_any(me):
+
+    ## A typical one-byte spec.
+    ksz = C.seal.keysz
+    me.assertEqual(type(ksz), C.KeySZAny)
+    me.assertEqual(ksz.default, 20)
+    me.assertEqual(ksz.min, 0)
+    me.assertEqual(ksz.max, None)
+    for n in [0, 12, 20, 5000]:
+      me.assertTrue(ksz.check(n))
+      me.assertEqual(ksz.best(n), n)
+      me.assertEqual(ksz.pad(n), n)
+
+    ## A typical two-byte spec.  (No published algorithms actually /need/ a
+    ## two-byte key-size spec, but all of the HMAC variants use one anyway.)
+    ksz = C.sha256_hmac.keysz
+    me.assertEqual(type(ksz), C.KeySZAny)
+    me.assertEqual(ksz.default, 32)
+    me.assertEqual(ksz.min, 0)
+    me.assertEqual(ksz.max, None)
+    for n in [0, 12, 20, 5000]:
+      me.assertTrue(ksz.check(n))
+      me.assertEqual(ksz.best(n), n)
+      me.assertEqual(ksz.pad(n), n)
+
+    ## Check construction.
+    ksz = C.KeySZAny(15)
+    me.assertEqual(ksz.default, 15)
+    me.assertEqual(ksz.min, 0)
+    me.assertEqual(ksz.max, None)
+    me.assertRaises(ValueError, lambda: C.KeySZAny(-8))
+    me.assertEqual(C.KeySZAny(0).default, 0)
+
+  def test_set(me):
+    ## Note that no published algorithm uses a 16-bit `set' spec.
+
+    ## A typical spec.
+    ksz = C.salsa20.keysz
+    me.assertEqual(type(ksz), C.KeySZSet)
+    me.assertEqual(ksz.default, 32)
+    me.assertEqual(ksz.min, 10)
+    me.assertEqual(ksz.max, 32)
+    me.assertEqual(ksz.set, set([10, 16, 32]))
+    for x, best, pad in [(9, None, 10), (10, 10, 10), (11, 10, 16),
+                         (15, 10, 16), (16, 16, 16), (17, 16, 32),
+                         (31, 16, 32), (32, 32, 32), (33, 32, None)]:
+      if x == best == pad: me.assertTrue(ksz.check(x))
+      else: me.assertFalse(ksz.check(x))
+      if best is None: me.assertRaises(ValueError, ksz.best, x)
+      else: me.assertEqual(ksz.best(x), best)
+      if pad is None: me.assertRaises(ValueError, ksz.pad, x)
+      else: me.assertEqual(ksz.pad(x), pad)
+
+    ## Check construction.
+    ksz = C.KeySZSet(7)
+    me.assertEqual(ksz.default, 7)
+    me.assertEqual(ksz.set, set([7]))
+    me.assertEqual(ksz.min, 7)
+    me.assertEqual(ksz.max, 7)
+    ksz = C.KeySZSet(7, iter([3, 6, 9]))
+    me.assertEqual(ksz.default, 7)
+    me.assertEqual(ksz.set, set([3, 6, 7, 9]))
+    me.assertEqual(ksz.min, 3)
+    me.assertEqual(ksz.max, 9)
+
+  def test_range(me):
+    ## Note that no published algorithm uses a 16-bit `range' spec, or an
+    ## unbounded `range'.
+
+    ## A typical spec.
+    ksz = C.rijndael.keysz
+    me.assertEqual(type(ksz), C.KeySZRange)
+    me.assertEqual(ksz.default, 32)
+    me.assertEqual(ksz.min, 4)
+    me.assertEqual(ksz.max, 32)
+    me.assertEqual(ksz.mod, 4)
+    for x, best, pad in [(3, None, 4), (4, 4, 4), (5, 4, 8),
+                         (15, 12, 16), (16, 16, 16), (17, 16, 20),
+                         (31, 28, 32), (32, 32, 32), (33, 32, None)]:
+      if x == best == pad: me.assertTrue(ksz.check(x))
+      else: me.assertFalse(ksz.check(x))
+      if best is None: me.assertRaises(ValueError, ksz.best, x)
+      else: me.assertEqual(ksz.best(x), best)
+      if pad is None: me.assertRaises(ValueError, ksz.pad, x)
+      else: me.assertEqual(ksz.pad(x), pad)
+
+    ## Check construction.
+    ksz = C.KeySZRange(28, 21, 35, 7)
+    me.assertEqual(ksz.default, 28)
+    me.assertEqual(ksz.min, 21)
+    me.assertEqual(ksz.max, 35)
+    me.assertEqual(ksz.mod, 7)
+    ksz = C.KeySZRange(28, 21, None, 7)
+    me.assertEqual(ksz.min, 21)
+    me.assertEqual(ksz.max, None)
+    me.assertEqual(ksz.mod, 7)
+    me.assertEqual(ksz.pad(36), 42)
+    me.assertRaises(ValueError, C.KeySZRange, 29, 21, 35, 7)
+    me.assertRaises(ValueError, C.KeySZRange, 28, 20, 35, 7)
+    me.assertRaises(ValueError, C.KeySZRange, 28, 21, 34, 7)
+    me.assertRaises(ValueError, C.KeySZRange, 28, -7, 35, 7)
+    me.assertRaises(ValueError, C.KeySZRange, 28, 35, 21, 7)
+    me.assertRaises(ValueError, C.KeySZRange, 35, 21, 28, 7)
+    me.assertRaises(ValueError, C.KeySZRange, 21, 28, 35, 7)
+
+  def test_conversions(me):
+    me.assertEqual(C.KeySZ.fromec(256), 128)
+    me.assertEqual(C.KeySZ.fromschnorr(256), 128)
+    me.assertEqual(round(C.KeySZ.fromdl(2958.6875)), 128)
+    me.assertEqual(round(C.KeySZ.fromif(2958.6875)), 128)
+    me.assertEqual(C.KeySZ.toec(128), 256)
+    me.assertEqual(C.KeySZ.toschnorr(128), 256)
+    me.assertEqual(C.KeySZ.todl(128), 2958.6875)
+    me.assertEqual(C.KeySZ.toif(128), 2958.6875)
+
+###--------------------------------------------------------------------------
+class TestCipher (T.GenericTestMixin):
+  """Test basic symmetric ciphers."""
+
+  def _test_cipher(me, ccls):
+
+    ## Check the class properties.
+    me.assertEqual(type(ccls.name), str)
+    me.assertTrue(isinstance(ccls.keysz, C.KeySZ))
+    me.assertEqual(type(ccls.blksz), int)
+
+    ## Check round-tripping.
+    k = T.span(ccls.keysz.default)
+    iv = T.span(ccls.blksz)
+    m = T.span(253)
+    enc = ccls(k)
+    dec = ccls(k)
+    try: enc.setiv(iv)
+    except ValueError: can_setiv = False
+    else:
+      can_setiv = True
+      dec.setiv(iv)
+    c0 = enc.encrypt(m[0:57])
+    m0 = dec.decrypt(c0)
+    c1 = enc.encrypt(m[57:189])
+    m1 = dec.decrypt(c1)
+    try: enc.bdry()
+    except ValueError: can_bdry = False
+    else:
+      dec.bdry()
+      can_bdry = True
+    c2 = enc.encrypt(m[189:253])
+    m2 = dec.decrypt(c2)
+    me.assertEqual(len(c0) + len(c1) + len(c2), len(m))
+    me.assertEqual(m0, m[0:57])
+    me.assertEqual(m1, m[57:189])
+    me.assertEqual(m2, m[189:253])
+
+    ## Check the `enczero' and `deczero' methods.
+    c3 = enc.enczero(32)
+    me.assertEqual(dec.decrypt(c3), C.ByteString.zero(32))
+    m4 = dec.deczero(32)
+    me.assertEqual(enc.encrypt(m4), C.ByteString.zero(32))
+
+    ## Check that ciphers which support a `boundary' operation actually
+    ## need it.
+    if can_bdry:
+      dec = ccls(k)
+      if can_setiv: dec.setiv(iv)
+      m01 = dec.decrypt(c0 + c1)
+      me.assertEqual(m01, m[0:189])
+
+    ## Check that the boundary actually does something.
+    if can_bdry:
+      dec = ccls(k)
+      if can_setiv: dec.setiv(iv)
+      m012 = dec.decrypt(c0 + c1 + c2)
+      me.assertNotEqual(m012, m)
+
+    ## Check that bad key lengths are rejected.
+    badlen = bad_key_size(ccls.keysz)
+    if badlen is not None: me.assertRaises(ValueError, ccls, T.span(badlen))
+
+TestCipher.generate_testcases((name, C.gcciphers[name]) for name in
+  ["des-ecb", "rijndael-cbc", "twofish-cfb", "serpent-ofb",
+   "blowfish-counter", "rc4", "seal", "salsa20/8", "shake128-xof"])
+
+###--------------------------------------------------------------------------
+class TestAuthenticatedEncryption \
+        (HashBufferTestMixin, T.GenericTestMixin):
+  """Test authenticated encryption schemes."""
+
+  def _test_aead(me, aecls):
+
+    ## Check the class properties.
+    me.assertEqual(type(aecls.name), str)
+    me.assertTrue(isinstance(aecls.keysz, C.KeySZ))
+    me.assertTrue(isinstance(aecls.noncesz, C.KeySZ))
+    me.assertTrue(isinstance(aecls.tagsz, C.KeySZ))
+    me.assertEqual(type(aecls.blksz), int)
+    me.assertEqual(type(aecls.bufsz), int)
+    me.assertEqual(type(aecls.ohd), int)
+    me.assertEqual(type(aecls.flags), int)
+
+    ## Check round-tripping, with full precommitment.  First, select some
+    ## parameters.  (It's conceivable that some AEAD schemes are more
+    ## restrictive than advertised by the various properties, but this works
+    ## out OK in practice.)
+    k = T.span(aecls.keysz.default)
+    n = T.span(aecls.noncesz.default)
+    if aecls.flags&C.AEADF_NOAAD: h = T.span(0)
+    else: h = T.span(131)
+    m = T.span(253)
+    tsz = aecls.tagsz.default
+    key = aecls(k)
+
+    ## Next, encrypt a message, checking that things are proper as we go.
+    enc = key.enc(nonce = n, hsz = len(h), msz = len(m), tsz = tsz)
+    me.assertEqual(enc.hsz, len(h))
+    me.assertEqual(enc.msz, len(m))
+    me.assertEqual(enc.mlen, 0)
+    me.assertEqual(enc.tsz, tsz)
+    aad = enc.aad()
+    if aecls.flags&C.AEADF_AADNDEP: me.assertEqual(aad.hsz, len(h))
+    else: me.assertEqual(aad.hsz, None)
+    me.assertEqual(aad.hlen, 0)
+    if not aecls.flags&C.AEADF_NOAAD:
+      aad.hash(h[0:83])
+      me.assertEqual(aad.hlen, 83)
+      aad.hash(h[83:131])
+      me.assertEqual(aad.hlen, 131)
+    c0 = enc.encrypt(m[0:57])
+    me.assertEqual(enc.mlen, 57)
+    me.assertTrue(57 - aecls.bufsz <= len(c0) <= 57 + aecls.ohd)
+    c1 = enc.encrypt(m[57:189])
+    me.assertEqual(enc.mlen, 189)
+    me.assertTrue(132 - aecls.bufsz <= len(c1) <=
+                  132 + aecls.bufsz + aecls.ohd)
+    c2 = enc.encrypt(m[189:253])
+    me.assertEqual(enc.mlen, 253)
+    me.assertTrue(64 - aecls.bufsz <= len(c2) <=
+                  64 + aecls.bufsz + aecls.ohd)
+    c3, t = enc.done(aad = aad)
+    me.assertTrue(len(c3) <= aecls.bufsz + aecls.ohd)
+    c = c0 + c1 + c2 + c3
+    me.assertTrue(len(m) <= len(c) <= len(m) + aecls.ohd)
+    me.assertEqual(len(t), tsz)
+
+    ## And now decrypt it again, with different record boundaries.
+    dec = key.dec(nonce = n, hsz = len(h), csz = len(c), tsz = tsz)
+    me.assertEqual(dec.hsz, len(h))
+    me.assertEqual(dec.csz, len(c))
+    me.assertEqual(dec.clen, 0)
+    me.assertEqual(dec.tsz, tsz)
+    aad = dec.aad()
+    if aecls.flags&C.AEADF_AADNDEP: me.assertEqual(aad.hsz, len(h))
+    else: me.assertEqual(aad.hsz, None)
+    me.assertEqual(aad.hlen, 0)
+    aad.hash(h)
+    m0 = dec.decrypt(c[0:156])
+    me.assertTrue(156 - aecls.bufsz <= len(m0) <= 156)
+    m1 = dec.decrypt(c[156:])
+    me.assertTrue(len(c) - 156 - aecls.bufsz <= len(m1) <=
+                  len(c) - 156 + aecls.bufsz)
+    m2 = dec.done(tag = t, aad = aad)
+    me.assertEqual(m0 + m1 + m2, m)
+
+    ## And again, with the wrong tag.
+    dec = key.dec(nonce = n, hsz = len(h), csz = len(c), tsz = tsz)
+    aad = dec.aad(); aad.hash(h)
+    _ = dec.decrypt(c)
+    me.assertRaises(ValueError, dec.done, tag = t ^ tsz*C.bytes("55"))
+
+    ## Check that the all-in-one methods work.
+    me.assertEqual((c, t),
+                   key.encrypt(n = n, h = h, m = m, tsz = tsz))
+    me.assertEqual(m,
+                   key.decrypt(n = n, h = h, c = c, t = t))
+
+    ## Check that bad key, nonce, and tag lengths are rejected.
+    badlen = bad_key_size(aecls.keysz)
+    if badlen is not None: me.assertRaises(ValueError, aecls, T.span(badlen))
+    badlen = bad_key_size(aecls.noncesz)
+    if badlen is not None:
+      me.assertRaises(ValueError, key.enc, nonce = T.span(badlen),
+                      hsz = len(h), msz = len(m), tsz = tsz)
+      me.assertRaises(ValueError, key.dec, nonce = T.span(badlen),
+                      hsz = len(h), csz = len(c), tsz = tsz)
+      if not aecls.flags&C.AEADF_PCTSZ:
+        enc = key.enc(nonce = n, hsz = 0, msz = len(m))
+        _ = enc.encrypt(m)
+        me.assertRaises(ValueError, enc.done, tsz = badlen)
+    badlen = bad_key_size(aecls.tagsz)
+    if badlen is not None:
+      me.assertRaises(ValueError, key.enc, nonce = n,
+                      hsz = len(h), msz = len(m), tsz = badlen)
+      me.assertRaises(ValueError, key.dec, nonce = n,
+                      hsz = len(h), csz = len(c), tsz = badlen)
+
+    ## Check that we can't get a loose `aad' object from a scheme which has
+    ## nonce-dependent AAD processing.
+    if aecls.flags&C.AEADF_AADNDEP: me.assertRaises(ValueError, key.aad)
+
+    ## Check the menagerie of AAD hashing methods.
+    if not aecls.flags&C.AEADF_NOAAD:
+      def mkhash(hsz):
+        enc = key.enc(nonce = n, hsz = hsz, msz = 0, tsz = tsz)
+        aad = enc.aad()
+        return aad, lambda: enc.done(aad = aad)[1]
+      me.check_hashbuffer(mkhash)
+
+    ## Check that encryption/decryption works with the given precommitments.
+    def quick_enc_check(**kw):
+      enc = key.enc(**kw)
+      aad = enc.aad().hash(h)
+      c0 = enc.encrypt(m); c1, tt = enc.done(aad = aad, tsz = tsz)
+      me.assertEqual((c, t), (c0 + c1, tt))
+    def quick_dec_check(**kw):
+      dec = key.dec(**kw)
+      aad = dec.aad().hash(h)
+      m0 = dec.decrypt(c); m1 = dec.done(aad = aad, tag = t)
+      me.assertEqual(m, m0 + m1)
+
+    ## Check that we can get away without precommitting to the header length
+    ## if and only if the AEAD scheme says it will let us.
+    if aecls.flags&C.AEADF_PCHSZ:
+      me.assertRaises(ValueError, key.enc, nonce = n,
+                      msz = len(m), tsz = tsz)
+      me.assertRaises(ValueError, key.dec, nonce = n,
+                      csz = len(c), tsz = tsz)
+    else:
+      quick_enc_check(nonce = n, msz = len(m), tsz = tsz)
+      quick_dec_check(nonce = n, csz = len(c), tsz = tsz)
+
+    ## Check that we can get away without precommitting to the message/
+    ## ciphertext length if and only if the AEAD scheme says it will let us.
+    if aecls.flags&C.AEADF_PCMSZ:
+      me.assertRaises(ValueError, key.enc, nonce = n,
+                      hsz = len(h), tsz = tsz)
+      me.assertRaises(ValueError, key.dec, nonce = n,
+                      hsz = len(h), tsz = tsz)
+    else:
+      quick_enc_check(nonce = n, hsz = len(h), tsz = tsz)
+      quick_dec_check(nonce = n, hsz = len(h), tsz = tsz)
+
+    ## Check that we can get away without precommitting to the tag length if
+    ## and only if the AEAD scheme says it will let us.
+    if aecls.flags&C.AEADF_PCTSZ:
+      me.assertRaises(ValueError, key.enc, nonce = n,
+                      hsz = len(h), msz = len(m))
+      me.assertRaises(ValueError, key.dec, nonce = n,
+                      hsz = len(h), csz = len(c))
+    else:
+      quick_enc_check(nonce = n, hsz = len(h), msz = len(m))
+      quick_dec_check(nonce = n, hsz = len(h), csz = len(c))
+
+    ## Check that if we precommit to the header length, we're properly held
+    ## to the commitment.
+    if not aecls.flags&C.AEADF_NOAAD:
+
+      ## First, check encryption with underrun.  If we must supply AAD first,
+      ## then the underrun will be reported when we start trying to encrypt;
+      ## otherwise, checking is delayed until `done'.
+      enc = key.enc(nonce = n, hsz = len(h), msz = len(m), tsz = tsz)
+      aad = enc.aad().hash(h[0:83])
+      if aecls.flags&C.AEADF_AADFIRST:
+        me.assertRaises(ValueError, enc.encrypt, m)
+      else:
+        _ = enc.encrypt(m)
+        me.assertRaises(ValueError, enc.done, aad = aad)
+
+      ## Next, check decryption with underrun.  If we must supply AAD first,
+      ## then the underrun will be reported when we start trying to encrypt;
+      ## otherwise, checking is delayed until `done'.
+      dec = key.dec(nonce = n, hsz = len(h), csz = len(c), tsz = tsz)
+      aad = dec.aad().hash(h[0:83])
+      if aecls.flags&C.AEADF_AADFIRST:
+        me.assertRaises(ValueError, dec.decrypt, c)
+      else:
+        _ = dec.decrypt(c)
+        me.assertRaises(ValueError, dec.done, tag = t, aad = aad)
+
+      ## If AAD processing is nonce-dependent then an overrun will be
+      ## detected imediately.
+      if aecls.flags&C.AEADF_AADNDEP:
+        enc = key.enc(nonce = n, hsz = len(h), msz = len(m), tsz = tsz)
+        aad = enc.aad().hash(h[0:83])
+        me.assertRaises(ValueError, aad.hash, h[82:131])
+        dec = key.dec(nonce = n, hsz = len(h), csz = len(c), tsz = tsz)
+        aad = dec.aad().hash(h[0:83])
+        me.assertRaises(ValueError, aad.hash, h[82:131])
+
+    ## Some additional tests for nonce-dependent `aad' objects.
+    if aecls.flags&C.AEADF_AADNDEP:
+
+      ## Check that `aad' objects can't be used once their parents are gone.
+      enc = key.enc(nonce = n, hsz = len(h), msz = len(m), tsz = tsz)
+      aad = enc.aad()
+      del enc
+      me.assertRaises(ValueError, aad.hash, h)
+
+      ## Check that they can't be crossed over.
+      enc0 = key.enc(nonce = n, hsz = len(h), msz = len(m), tsz = tsz)
+      enc1 = key.enc(nonce = n, hsz = len(h), msz = len(m), tsz = tsz)
+      enc0.aad().hash(h)
+      aad1 = enc1.aad().hash(h)
+      _ = enc0.encrypt(m)
+      me.assertRaises(ValueError, enc0.done, tsz = tsz, aad = aad1)
+
+    ## Test copying AAD.
+    if not aecls.flags&C.AEADF_AADNDEP and not aecls.flags&C.AEADF_NOAAD:
+      aad0 = key.aad()
+      aad0.hash(h[0:83])
+      aad1 = aad0.copy()
+      aad2 = aad1.copy()
+      aad0.hash(h[83:131])
+      aad1.hash(h[83:131])
+      aad2.hash(h[83:131] ^ 48*C.bytes("ff"))
+      me.assertEqual(key.enc(nonce = n, hsz = len(h),
+                             msz = 0, tsz = tsz).done(aad = aad0),
+                     key.enc(nonce = n, hsz = len(h),
+                             msz = 0, tsz = tsz).done(aad = aad1))
+      me.assertNotEqual(key.enc(nonce = n, hsz = len(h),
+                                msz = 0, tsz = tsz).done(aad = aad0),
+                        key.enc(nonce = n, hsz = len(h),
+                                msz = 0, tsz = tsz).done(aad = aad2))
+
+    ## Check that if we precommit to the message length, we're properly held
+    ## to the commitment.  (Fortunately, this is way simpler than the AAD
+    ## case above.)  First, try an underrun.
+    enc = key.enc(nonce = n, hsz = 0, msz = len(m), tsz = tsz)
+    _ = enc.encrypt(m[0:183])
+    me.assertRaises(ValueError, enc.done, tsz = tsz)
+    dec = key.dec(nonce = n, hsz = 0, csz = len(c), tsz = tsz)
+    _ = dec.decrypt(c[0:183])
+    me.assertRaises(ValueError, dec.done, tag = t)
+
+    ## And now an overrun.
+    enc = key.enc(nonce = n, hsz = 0, msz = 183, tsz = tsz)
+    me.assertRaises(ValueError, enc.encrypt, m)
+    dec = key.dec(nonce = n, hsz = 0, csz = 183, tsz = tsz)
+    me.assertRaises(ValueError, dec.decrypt, c)
+
+    ## Finally, check that if we precommit to a tag length, we're properly
+    ## held to the commitment.  This depends on being able to find a tag size
+    ## which isn't the default.
+    tsz1 = different_key_size(aecls.tagsz, tsz)
+    if tsz1 is not None:
+      enc = key.enc(nonce = n, hsz = 0, msz = len(m), tsz = tsz1)
+      _ = enc.encrypt(m)
+      me.assertRaises(ValueError, enc.done, tsz = tsz)
+      dec = key.dec(nonce = n, hsz = len(h), csz = len(c), tsz = tsz1)
+      aad = dec.aad().hash(h)
+      _ = dec.decrypt(c)
+      me.assertRaises(ValueError, enc.done, tsz = tsz, aad = aad)
+
+TestAuthenticatedEncryption.generate_testcases \
+  ((name, C.gcaeads[name]) for name in
+   ["des3-ccm", "blowfish-ocb1", "square-ocb3", "rijndael-gcm",
+    "serpent-eax", "salsa20-naclbox", "chacha20-poly1305"])
+
+###--------------------------------------------------------------------------
+class BaseTestHash (HashBufferTestMixin):
+  """Base class for testing hash functions."""
+
+  def check_hash(me, hcls, need_bufsz = True):
+    """
+    Check hash class HCLS.
+
+    If NEED_BUFSZ is false, then don't insist that HCLS has a working `bufsz'
+    attribute.  This test is mostly reused for MACs, which don't have this
+    attribute.
+    """
+    ## Check the class properties.
+    me.assertEqual(type(hcls.name), str)
+    if need_bufsz: me.assertEqual(type(hcls.bufsz), int)
+    me.assertEqual(type(hcls.hashsz), int)
+
+    ## Set some initial values.
+    m = T.span(131)
+    h = hcls().hash(m).done()
+
+    ## Check that hash length comes out right.
+    me.assertEqual(len(h), hcls.hashsz)
+
+    ## Check that we get the same answer if we split the message up.
+    me.assertEqual(h, hcls().hash(m[0:73]).hash(m[73:131]).done())
+
+    ## Check the `check' method.
+    me.assertTrue(hcls().hash(m).check(h))
+    me.assertFalse(hcls().hash(m).check(h ^ hcls.hashsz*C.bytes("aa")))
+
+    ## Check the menagerie of random hashing methods.
+    def mkhash(_):
+      h = hcls()
+      return h, h.done
+    me.check_hashbuffer(mkhash)
+
+class TestHash (BaseTestHash, T.GenericTestMixin):
+  """Test hash functions."""
+  def _test_hash(me, hcls):    me.check_hash(hcls, need_bufsz = True)
+
+TestHash.generate_testcases((name, C.gchashes[name]) for name in
+  ["md5", "sha", "whirlpool", "sha256", "sha512/224", "sha3-384", "shake256",
+   "crc32"])
+
+###--------------------------------------------------------------------------
+class TestMessageAuthentication (BaseTestHash, T.GenericTestMixin):
+  """Test message authentication codes."""
+
+  def _test_mac(me, mcls):
+
+    ## Check the MAC properties.
+    me.assertEqual(type(mcls.name), str)
+    me.assertTrue(isinstance(mcls.keysz, C.KeySZ))
+    me.assertEqual(type(mcls.tagsz), int)
+
+    ## Test hashing.
+    k = T.span(mcls.keysz.default)
+    key = mcls(k)
+    me.assertEqual(key.hashsz, key.tagsz)
+    me.check_hash(key, need_bufsz = False)
+
+    ## Check that bad key lengths are rejected.
+    badlen = bad_key_size(mcls.keysz)
+    if badlen is not None: me.assertRaises(ValueError, mcls, T.span(badlen))
+
+TestMessageAuthentication.generate_testcases \
+  ((name, C.gcmacs[name]) for name in
+   ["sha-hmac", "rijndael-cmac", "twofish-pmac1", "kmac128"])
+
+class TestPoly1305 (HashBufferTestMixin):
+  """Check the Poly1305 one-time message authentication function."""
+
+  def test_poly1305(me):
+
+    ## Check the MAC properties.
+    me.assertEqual(C.poly1305.name, "poly1305")
+    me.assertEqual(type(C.poly1305.keysz), C.KeySZSet)
+    me.assertEqual(C.poly1305.keysz.default, 16)
+    me.assertEqual(C.poly1305.keysz.set, set([16]))
+    me.assertEqual(C.poly1305.tagsz, 16)
+    me.assertEqual(C.poly1305.masksz, 16)
+
+    ## Set some initial values.
+    k = T.span(16)
+    u = T.span(64)[-16:]
+    m = T.span(149)
+    key = C.poly1305(k)
+    t = key(u).hash(m).done()
+
+    ## Check the key properties.
+    me.assertEqual(key.name, "poly1305")
+    me.assertEqual(key.tagsz, 16)
+    me.assertEqual(key.tagsz, 16)
+    me.assertEqual(len(t), 16)
+
+    ## Check that we get the same answer if we split the message up.
+    me.assertEqual(t, key(u).hash(m[0:86]).hash(m[86:149]).done())
+
+    ## Check the `check' method.
+    me.assertTrue(key(u).hash(m).check(t))
+    me.assertFalse(key(u).hash(m).check(t ^ 16*C.bytes("cc")))
+
+    ## Check the menagerie of random hashing methods.
+    def mkhash(_):
+      h = key(u)
+      return h, h.done
+    me.check_hashbuffer(mkhash)
+
+    ## Check that we can't complete hashing without a mask.
+    me.assertRaises(ValueError, key().hash(m).done)
+
+    ## Check `concat'.
+    h0 = key().hash(m[0:96])
+    h1 = key().hash(m[96:117])
+    me.assertEqual(t, key(u).concat(h0, h1).hash(m[117:149]).done())
+    key1 = C.poly1305(k)
+    me.assertRaises(TypeError, key().concat, key1().hash(m[0:96]), h1)
+    me.assertRaises(TypeError, key().concat, h0, key1().hash(m[96:117]))
+    me.assertRaises(ValueError, key().concat, key().hash(m[0:93]), h1)
+
+###--------------------------------------------------------------------------
+class TestHLatin (U.TestCase):
+  """Test the `hsalsa20' and `hchacha20' functions."""
+
+  def test_hlatin(me):
+    kk = [T.span(sz) for sz in [10, 16, 32]]
+    n = T.span(16)
+    bad_k = T.span(18)
+    bad_n = T.span(13)
+    for fn in [C.hsalsa208_prf, C.hsalsa2012_prf, C.hsalsa20_prf,
+               C.hchacha8_prf, C.hchacha12_prf, C.hchacha20_prf]:
+      for k in kk:
+        h = fn(k, n)
+        me.assertEqual(len(h), 32)
+      me.assertRaises(ValueError, fn, bad_k, n)
+      me.assertRaises(ValueError, fn, k, bad_n)
+
+###--------------------------------------------------------------------------
+class TestKeccak (HashBufferTestMixin):
+  """Test the Keccak-p[1600, n] sponge function."""
+
+  def test_keccak(me):
+
+    ## Make a state and feed some stuff into it.
+    m0 = T.bin("some initial string")
+    m1 = T.bin("awesome follow-up string")
+    st0 = C.Keccak1600()
+    me.assertEqual(st0.nround, 24)
+    st0.mix(m0).step()
+
+    ## Make another step with a different round count.
+    st1 = C.Keccak1600(23)
+    st1.mix(m0).step()
+    me.assertNotEqual(st0.extract(32), st1.extract(32))
+
+    ## Check state copying and `mix' vs `set'.
+    st1 = st0.copy()
+    mask = st1.extract(len(m1))
+    st0.mix(m1)
+    st1.set(m1 ^ mask)
+    me.assertEqual(st0.extract(32), st1.extract(32))
+
+    ## Check error conditions.
+    _ = st0.extract(200)
+    me.assertRaises(ValueError, st0.extract, 201)
+    st0.mix(T.span(200))
+    me.assertRaises(ValueError, st0.mix, T.span(201))
+    st0.set(T.span(200))
+    me.assertRaises(ValueError, st0.set, T.span(201))
+    me.assertRaises(ValueError, st0.set, T.span(199))
+
+  def check_shake(me, xcls, c, done_matches_xof = True):
+    """
+    Test the SHAKE and cSHAKE XOFs.
+
+    This is also used for testing KMAC, but that sets DONE_MATCHES_XOF false
+    to indicate that the XOF output is range-separated from the fixed-length
+    outputs (unlike the basic SHAKE functions).
+    """
+
+    ## Check the hash attributes.
+    x = xcls()
+    me.assertEqual(x.rate, 200 - c)
+    me.assertEqual(x.buffered, 0)
+    me.assertEqual(x.state, "absorb")
+
+    ## Set some initial values.
+    func = T.bin("TESTXOF")
+    perso = T.bin("catacomb-python test")
+    m = T.span(167)
+    h0 = xcls().hash(m).done(193)
+    me.assertEqual(len(h0), 193)
+    h1 = xcls(func = func, perso = perso).hash(m).done(193)
+    me.assertEqual(len(h1), 193)
+    me.assertNotEqual(h0, h1)
+
+    ## Check input and output in pieces, and the state machine.
+    if done_matches_xof: h = h0
+    else: h = xcls().hash(m).xof().get(len(h0))
+    x = xcls().hash(m[0:76]).hash(m[76:167]).xof()
+    me.assertEqual(h, x.get(98) + x.get(95))
+
+    ## Check masking.
+    x = xcls().hash(m).xof()
+    me.assertEqual(x.mask(m), m ^ h[0:len(m)])
+
+    ## Check the `check' method.
+    me.assertTrue(xcls().hash(m).check(h0))
+    me.assertFalse(xcls().hash(m).check(h1))
+
+    ## Check the menagerie of random hashing methods.
+    def mkhash(_):
+      x = xcls(func = func, perso = perso)
+      return x, x.done
+    me.check_hashbuffer(mkhash)
+
+    ## Check the state machine tracking.
+    x = xcls(); me.assertEqual(x.state, "absorb")
+    x.hash(m); me.assertEqual(x.state, "absorb")
+    xx = x.copy()
+    h = xx.done(); me.assertEqual(len(h), 100 - x.rate//2)
+    me.assertEqual(xx.state, "dead")
+    me.assertRaises(ValueError, xx.done, 1)
+    me.assertRaises(ValueError, xx.get, 1)
+    me.assertEqual(x.state, "absorb")
+    me.assertRaises(ValueError, x.get, 1)
+    x.xof(); me.assertEqual(x.state, "squeeze")
+    me.assertRaises(ValueError, x.done, 1)
+    _ = x.get(1)
+    yy = x.copy(); me.assertEqual(yy.state, "squeeze")
+
+  def test_shake128(me): me.check_shake(C.Shake128, 32)
+  def test_shake256(me): me.check_shake(C.Shake256, 64)
+
+  def check_kmac(me, mcls, c):
+    k = T.span(32)
+    me.check_shake(lambda func = None, perso = None:
+                     mcls(k, perso = perso),
+                   c, done_matches_xof = False)
+
+  def test_kmac128(me): me.check_kmac(C.KMAC128, 32)
+  def test_kmac256(me): me.check_kmac(C.KMAC256, 64)
+
+###--------------------------------------------------------------------------
+class TestPRP (T.GenericTestMixin):
+  """Test pseudorandom permutations (PRPs)."""
+
+  def _test_prp(me, pcls):
+
+    ## Check the PRP properties.
+    me.assertEqual(type(pcls.name), str)
+    me.assertTrue(isinstance(pcls.keysz, C.KeySZ))
+    me.assertEqual(type(pcls.blksz), int)
+
+    ## Check round-tripping.
+    k = T.span(pcls.keysz.default)
+    key = pcls(k)
+    m = T.span(pcls.blksz)
+    c = key.encrypt(m)
+    me.assertEqual(len(c), pcls.blksz)
+    me.assertEqual(m, key.decrypt(c))
+
+    ## Check that bad key lengths are rejected.
+    badlen = bad_key_size(pcls.keysz)
+    if badlen is not None: me.assertRaises(ValueError, pcls, T.span(badlen))
+
+    ## Check that bad blocks are rejected.
+    badblk = T.span(pcls.blksz + 1)
+    me.assertRaises(ValueError, key.encrypt, badblk)
+    me.assertRaises(ValueError, key.decrypt, badblk)
+
+TestPRP.generate_testcases((name, C.gcprps[name]) for name in
+  ["desx", "blowfish", "rijndael"])
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-buffer.py b/t/t-buffer.py
new file mode 100644 (file)
index 0000000..db51226
--- /dev/null
@@ -0,0 +1,250 @@
+### -*- mode: python, coding: utf-8 -*-
+###
+### Test read and write buffers
+###
+### (c) 2019 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.
+
+import catacomb as C
+import unittest as U
+import testutils as T
+
+###--------------------------------------------------------------------------
+class TestReadBuffer (U.TestCase):
+
+  def check_getn(me, w, bigendp, getfn):
+    """Check that `getuN' works."""
+    buf = C.ReadBuffer(T.span(w + 2))
+    me.assertEqual(buf.getu8(), 0x00)
+    me.assertEqual(getfn(buf), T.bytes_as_int(w, bigendp))
+    me.assertEqual(buf.getu8(), w + 1)
+    me.assertTrue(buf.endp)
+    me.assertRaises(C.BufferError, getfn, C.ReadBuffer(T.span(w - 1)))
+    me.assertEqual(getfn(C.ReadBuffer(w*C.bytes("00"))), 0)
+    me.assertEqual(getfn(C.ReadBuffer(w*C.bytes("ff"))), (1 << 8*w) - 1)
+
+  def check_getbufn(me, w, bigendp, blkfn, buffn):
+    """Check that `getblkN' and `getbufN' work."""
+
+    ## Run tests for several different data sizes.
+    for n in [0, 1, 7, 8, 19, 255, 12345, 65535, 123456]:
+
+      ## Make a sequence to parse.  If it's too large, then skip.
+      if n >= 1 << 8*w: continue
+      seq = T.prep_lenseq(w, n, bigendp, True)
+
+      ## Check `getblkN'.
+      buf = C.ReadBuffer(seq)
+      me.assertEqual(buf.getu8(), 0)
+      me.assertEqual(blkfn(buf), T.span(n))
+      me.assertEqual(buf.getu8(), 0xff)
+      me.assertTrue(buf.endp)
+
+      ## Check `getbufN'.  Delete the outside buffer early, to make sure that
+      ## the subbuffer keeps it alive.
+      buf = C.ReadBuffer(seq)
+      me.assertEqual(buf.getu8(), 0)
+      b = buffn(buf)
+      me.assertEqual(buf.getu8(), 0xff)
+      me.assertTrue(buf.endp)
+      del buf
+      me.assertEqual(b.offset, 0)
+      me.assertEqual(b.size, n)
+      if n > 0:
+        me.assertEqual(b.getu8(), 0x00)
+        b.offset = n - 1
+        me.assertEqual(b.getu8(), (n - 1)&0xff)
+      me.assertTrue(b.endp)
+
+      ## Test invalid lengths.  This is going to work by setting the top bit
+      ## of the length, so if it's already set, then that won't be any good.
+      if n >= 1 << 8*w - 1: continue
+      seq = T.prep_lenseq(w, n, bigendp, False)
+
+      ## Check `getblkN'.
+      me.assertRaises(C.BufferError, blkfn, C.ReadBuffer(T.span(w - 1)))
+      b = C.ReadBuffer(seq)
+      me.assertEqual(b.getu8(), 0)
+      me.assertRaises(C.BufferError, blkfn, b)
+
+      ## Check `getbufN'.
+      me.assertRaises(C.BufferError, buffn, C.ReadBuffer(T.span(w - 1)))
+      b = C.ReadBuffer(seq)
+      me.assertEqual(b.getu8(), 0)
+      me.assertRaises(C.BufferError, buffn, b)
+
+  def test_readbuffer(me):
+
+    ## Check `getuN'.
+    me.check_getn(1, True, lambda buf: buf.getu8())
+    me.check_getn(2, True, lambda buf: buf.getu16())
+    me.check_getn(2, True, lambda buf: buf.getu16b())
+    me.check_getn(2, False, lambda buf: buf.getu16l())
+    me.check_getn(3, True, lambda buf: buf.getu24())
+    me.check_getn(3, True, lambda buf: buf.getu24b())
+    me.check_getn(3, False, lambda buf: buf.getu24l())
+    me.check_getn(4, True, lambda buf: buf.getu32())
+    me.check_getn(4, True, lambda buf: buf.getu32b())
+    me.check_getn(4, False, lambda buf: buf.getu32l())
+    if hasattr(C.ReadBuffer, "getu64"):
+      me.check_getn(8, True, lambda buf: buf.getu64())
+      me.check_getn(8, True, lambda buf: buf.getu64b())
+      me.check_getn(8, False, lambda buf: buf.getu64l())
+
+    ## Check `getblkN' and `getbufN'.
+    me.check_getbufn(1, True,
+                     lambda buf: buf.getblk8(),
+                     lambda buf: buf.getbuf8())
+    me.check_getbufn(2, True,
+                     lambda buf: buf.getblk16(),
+                     lambda buf: buf.getbuf16())
+    me.check_getbufn(2, True,
+                     lambda buf: buf.getblk16b(),
+                     lambda buf: buf.getbuf16b())
+    me.check_getbufn(2, False,
+                     lambda buf: buf.getblk16l(),
+                     lambda buf: buf.getbuf16l())
+    me.check_getbufn(3, True,
+                     lambda buf: buf.getblk24(),
+                     lambda buf: buf.getbuf24())
+    me.check_getbufn(3, True,
+                     lambda buf: buf.getblk24b(),
+                     lambda buf: buf.getbuf24b())
+    me.check_getbufn(3, False,
+                     lambda buf: buf.getblk24l(),
+                     lambda buf: buf.getbuf24l())
+    me.check_getbufn(4, True,
+                     lambda buf: buf.getblk32(),
+                     lambda buf: buf.getbuf32())
+    me.check_getbufn(4, True,
+                     lambda buf: buf.getblk32b(),
+                     lambda buf: buf.getbuf32b())
+    me.check_getbufn(4, False,
+                     lambda buf: buf.getblk32l(),
+                     lambda buf: buf.getbuf32l())
+    if hasattr(C.ReadBuffer, "getu64"):
+      me.check_getbufn(8, True,
+                       lambda buf: buf.getblk64(),
+                       lambda buf: buf.getbuf64())
+      me.check_getbufn(8, True,
+                       lambda buf: buf.getblk64b(),
+                       lambda buf: buf.getbuf64b())
+      me.check_getbufn(8, False,
+                       lambda buf: buf.getblk64l(),
+                       lambda buf: buf.getbuf64l())
+
+    ## Check other `ReadBuffer' methods and properties.
+    buf = C.ReadBuffer(T.span(256))
+    me.assertEqual(buf.size, 256)
+    me.assertEqual(buf.left, 256)
+    me.assertEqual(buf.offset, 0)
+    buf.offset = 52
+    me.assertEqual(buf.left, 204)
+    buf.skip(7)
+    me.assertEqual(buf.offset, 59)
+    me.assertEqual(buf.left, 197)
+    me.assertRaises(C.BufferError, C.ReadBuffer(T.span(6)).skip, 7)
+    me.assertEqual(buf.get(5), C.bytes("3b3c3d3e3f"))
+    me.assertRaises(C.BufferError, C.ReadBuffer(T.span(4)).get, 5)
+
+###--------------------------------------------------------------------------
+class TestWriteBuffer (U.TestCase):
+
+  def check_putn(me, w, bigendp, putfn):
+    """Check `putuN'."""
+
+    ## Check encoding an integer.
+    buf = C.WriteBuffer()
+    buf.putu8(0x00)
+    putfn(buf, T.bytes_as_int(w, bigendp))
+    buf.putu8(w + 1)
+    me.assertEqual(buf.contents, T.span(w + 2))
+    me.assertEqual(putfn(C.WriteBuffer(), (1 << 8*w) - 1).contents,
+                   w*C.bytes("ff"))
+    me.assertEqual(putfn(C.WriteBuffer(), C.MP(0)).contents, w*C.bytes("00"))
+
+    ## Check overflow detection.
+    me.assertRaises(OverflowError, putfn, C.WriteBuffer(), 1 << 8*w)
+
+  def check_putbufn(me, w, bigendp, putfn):
+    """Check `putblkN'."""
+
+    ## Go through a number of different sizes.
+    for n in [0, 1, 7, 8, 19, 255, 12345, 65535, 123456]:
+      if n >= 1 << 8*w: continue
+      me.assertEqual(putfn(C.WriteBuffer().putu8(0x00),
+                           T.span(n)).putu8(0xff).contents,
+                     T.prep_lenseq(w, n, bigendp, True))
+
+    ## Check blocks which are too large for the length prefix.
+    if w <= 3:
+      me.assertRaises(ValueError, putfn,
+                      C.WriteBuffer(), C.ByteString.zero(1 << 8*w))
+
+  def test_writebuffer(me):
+
+    ## Check `putuN'.
+    me.check_putn(1, True, lambda buf, n: buf.putu8(n))
+    me.check_putn(2, True, lambda buf, n: buf.putu16(n))
+    me.check_putn(2, True, lambda buf, n: buf.putu16b(n))
+    me.check_putn(2, False, lambda buf, n: buf.putu16l(n))
+    me.check_putn(3, True, lambda buf, n: buf.putu24(n))
+    me.check_putn(3, True, lambda buf, n: buf.putu24b(n))
+    me.check_putn(3, False, lambda buf, n: buf.putu24l(n))
+    me.check_putn(4, True, lambda buf, n: buf.putu32(n))
+    me.check_putn(4, True, lambda buf, n: buf.putu32b(n))
+    me.check_putn(4, False, lambda buf, n: buf.putu32l(n))
+    if hasattr(C.WriteBuffer, "putu64"):
+      me.check_putn(8, True, lambda buf, n: buf.putu64(n))
+      me.check_putn(8, True, lambda buf, n: buf.putu64b(n))
+      me.check_putn(8, False, lambda buf, n: buf.putu64l(n))
+
+    ## Check `putblkN".
+    me.check_putbufn(1, True, lambda buf, x: buf.putblk8(x))
+    me.check_putbufn(2, True, lambda buf, x: buf.putblk16(x))
+    me.check_putbufn(2, True, lambda buf, x: buf.putblk16b(x))
+    me.check_putbufn(2, False, lambda buf, x: buf.putblk16l(x))
+    me.check_putbufn(3, True, lambda buf, x: buf.putblk24(x))
+    me.check_putbufn(3, True, lambda buf, x: buf.putblk24b(x))
+    me.check_putbufn(3, False, lambda buf, x: buf.putblk24l(x))
+    me.check_putbufn(4, True, lambda buf, x: buf.putblk32(x))
+    me.check_putbufn(4, True, lambda buf, x: buf.putblk32b(x))
+    me.check_putbufn(4, False, lambda buf, x: buf.putblk32l(x))
+    if hasattr(C.WriteBuffer, "putu64"):
+      me.check_putbufn(8, True, lambda buf, x: buf.putblk64(x))
+      me.check_putbufn(8, True, lambda buf, x: buf.putblk64b(x))
+      me.check_putbufn(8, False, lambda buf, x: buf.putblk64l(x))
+
+    ## Check other methods and properties.
+    buf = C.WriteBuffer()
+    buf.zero(17)
+    buf.put(T.span(23))
+    me.assertEqual(buf.size, 40)
+    me.assertEqual(buf, C.ByteString.zero(17) + T.span(23))
+    me.assertEqual(buf.contents, C.ByteString.zero(17) + T.span(23))
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-bytes.py b/t/t-bytes.py
new file mode 100644 (file)
index 0000000..27b7e49
--- /dev/null
@@ -0,0 +1,175 @@
+### -*- mode: python, coding: utf-8 -*-
+###
+### Test `ByteString'
+###
+### (c) 2019 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.
+
+import sys as SYS
+import catacomb as C
+import unittest as U
+import testutils as T
+
+###--------------------------------------------------------------------------
+class TestByteString (U.TestCase):
+
+  def test_create(me):
+
+    ## Create a string and make sure it looks right.
+    x = C.ByteString(T.bin("abcde"))
+    me.assertEqual(x, T.bin("abcde"))
+    me.assertEqual(x, C.bytes("6162636465"))
+    me.assertEqual(len(x), 5)
+
+  def test_index(me):
+
+    x = C.ByteString(T.bin("once upon a time there was a string"))
+
+    ## Check that simple indexing works.  Alas the behaviour differs between
+    ## Python major versions.
+    if T.PY3:
+      me.assertEqual(type(x[3]), int)
+      me.assertEqual(x[3], 101)
+      me.assertEqual(x[-5], 116)
+    else:
+      me.assertEqual(type(x[3]), C.ByteString)
+      me.assertEqual(x[3], 'e')
+      me.assertEqual(x[-5], 't')
+
+    ## Check out-of-range detection.
+    x[34]; me.assertRaises(IndexError, lambda: x[35])
+    x[-35]; me.assertRaises(IndexError, lambda: x[-36])
+
+    ## Check slicing.  This should always give us bytes.
+    me.assertEqual(type(x[7:17]), C.ByteString)
+    me.assertEqual(x[7:17], T.bin("on a time "))
+
+    ## Complex slicing is also supported.
+    me.assertEqual(x[5:23:3], C.bytes("756e206d7472"))
+
+  def test_compare(me):
+    """
+    Test byte string comparison.
+
+    This is rather important, since we override it and many of the other
+    tests assume that comparison works.
+    """
+
+    def check(big, small):
+      """Check comparisons between BIG and SMALL strings."""
+
+      ## Equality.
+      me.assertTrue(big == big)
+      me.assertFalse(big == small)
+
+      ## Inequality.
+      me.assertFalse(big != big)
+      me.assertTrue(big != small)
+
+      ## Strict less-than.
+      me.assertFalse(big < big)
+      me.assertFalse(big < small)
+      me.assertTrue(small < big)
+
+      ## Non-strict less-than.
+      me.assertTrue(big <= big)
+      me.assertFalse(big <= small)
+      me.assertTrue(small <= big)
+
+      ## Non-strict greater-than.
+      me.assertTrue(big >= big)
+      me.assertTrue(big >= small)
+      me.assertFalse(small >= big)
+
+      ## Strict greater-than.
+      me.assertFalse(big > big)
+      me.assertTrue(big > small)
+      me.assertFalse(small > big)
+
+    ## Strings with equal length.
+    check(C.ByteString(T.bin("a string which is unlike the second")),
+          C.ByteString(T.bin("a string that is not like the first")))
+
+    ## A string and a prefix of it.
+    check(C.ByteString(T.bin("short strings order before longer ones")),
+          C.ByteString(T.bin("short string")))
+
+    ## The `ctstreq' function.
+    x = T.bin("special test string")
+    y = T.bin("my different string")
+    me.assertTrue(C.ctstreq(x, x))
+    me.assertFalse(C.ctstreq(x, y))
+
+  def test_operators(me):
+
+    ## Some example strings.
+    x = C.bytes("03a5fc")
+    y = C.bytes("5fac30")
+    z = C.bytes("00000000")
+
+    ## Operands of a binary operator must have equal lengths.
+    me.assertRaises(ValueError, lambda: x&z)
+    me.assertRaises(ValueError, lambda: x|z)
+    me.assertRaises(ValueError, lambda: x^z)
+
+    ## Bitwise AND.
+    me.assertEqual(type(x&y), C.ByteString)
+    me.assertEqual(x&y, C.bytes("03a430"))
+
+    ## Bitwise OR.
+    me.assertEqual(type(x | y), C.ByteString)
+    me.assertEqual(x | y, C.bytes("5fadfc"))
+
+    # Bitwise XOR.
+    me.assertEqual(type(x ^ y), C.ByteString)
+    me.assertEqual(x ^ y, C.bytes("5c09cc"))
+
+    # Bitwise NOT.
+    me.assertEqual(type(~x), C.ByteString)
+    me.assertEqual(~x, C.bytes("fc5a03"))
+
+    ## Concatenation.
+    me.assertEqual(type(x + y), C.ByteString)
+    me.assertEqual(x + y, C.bytes("03a5fc5fac30"))
+
+    ## Replication (asymmetric but commutative).
+    me.assertEqual(type(3*x), C.ByteString)
+    me.assertEqual(type(x*3), C.ByteString)
+    me.assertEqual(3*x, C.bytes("03a5fc03a5fc03a5fc"))
+    me.assertEqual(x*3, C.bytes("03a5fc03a5fc03a5fc"))
+
+    ## Replication by zero (regression test).
+    me.assertEqual(type(0*x), C.ByteString)
+    me.assertEqual(type(x*0), C.ByteString)
+    me.assertEqual(0*x, C.ByteString(T.bin("")))
+    me.assertEqual(x*0, C.ByteString(T.bin("")))
+
+  def test_zero(me):
+    me.assertEqual(C.ByteString.zero(7), T.bin(7*"\0"))
+    me.assertEqual(C.ByteString.zero(0), T.bin(""))
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-convert.py b/t/t-convert.py
new file mode 100644 (file)
index 0000000..19d6918
--- /dev/null
@@ -0,0 +1,131 @@
+### -*- mode: python, coding: utf-8 -*-
+###
+### Testing implicit conversions
+###
+### (c) 2019 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.
+
+import catacomb as C
+import sys as SYS
+import unittest as U
+import testutils as T
+
+###--------------------------------------------------------------------------
+class TestConvert (U.TestCase):
+  """Test implicit conversions between various mathematical types."""
+
+  def test(me):
+
+    k = C.PrimeField(17)
+    E = k.ec(-3, 4)
+    P = E(1) # order 15
+    kk = C.BinPolyField(0x13)
+    EE = kk.ec(1, 2)
+    PP = E(9) # order 16
+
+    ## `MP' asymmetric operations.
+    me.assertEqual(pow(C.MP(5), 2), 25)
+    me.assertEqual(pow(5, C.MP(2)), 25)
+    me.assertEqual(pow(C.MP(5), 2, 7), 4)
+    me.assertEqual(pow(5, C.MP(2), 7), 4)
+    me.assertEqual(pow(5, 2, C.MP(7)), 4)
+    for bad in [C.GF, k, kk, str, float, lambda x: [x]]:
+      me.assertRaises(TypeError, pow, C.MP(5), bad(2))
+      me.assertRaises(TypeError, pow, C.MP(5), bad(2), 7)
+      if not (T.PY2 and T.DEBUGP):
+        ## Debug builds of Python 2 crash here, and it's not our fault.  Run
+        ##
+        ## $ python2.7-dbg -c 'pow(long(5), 2, [7])'
+        ##
+        ## to confirm.  The `[7]' causes coercion to occur.  The first and
+        ## second operands are coerced first, and successfully replaced by
+        ## the results: the first operand (in this case) is unchanged, but
+        ## has its refcount bumped, and the second operand is replaced by the
+        ## result of coercion, which now has a refcount of 1.  Then the first
+        ## and third operands are coerced, which fails.  Python decrements
+        ## the refcounts of the results of the first coercion, so the second
+        ## operand is now freed and, in debug builds, clobbered.  Python then
+        ## tries to format an error message, quoting the types of the
+        ## operands, but one of them has been lost.
+        me.assertRaises(TypeError, pow, C.MP(5), 2, bad(7))
+      me.assertRaises(TypeError, T.lsl, C.MP(5), bad(2))
+
+    ## `GF' asymmetric operations.
+    me.assertEqual(pow(C.GF(0x5), 2), C.GF(0x11))
+    me.assertEqual(pow(C.GF(0x5), C.MP(2)), C.GF(0x11))
+    me.assertEqual(pow(C.GF(5), 2, C.GF(0x13)), C.GF(0x2))
+    for bad in [k, kk, str, float, lambda x: [x]]:
+      me.assertRaises(TypeError, pow, C.GF(5), bad(2))
+      me.assertRaises(TypeError, T.lsl, C.GF(5), bad(2))
+    for bad in [int, C.MP, k, kk, str, float, lambda x: [x]]:
+      me.assertRaises(TypeError, pow, bad(5), C.GF(2))
+      me.assertRaises(TypeError, pow, bad(5), C.GF(2), bad(7))
+      me.assertRaises(TypeError, pow, bad(5), bad(2), C.GF(7))
+      me.assertRaises(TypeError, pow, C.GF(5), bad(2), bad(7))
+      if not (T.PY2 and T.DEBUGP):
+        ## Python bug: see above.
+        me.assertRaises(TypeError, pow, C.GF(5), 2, bad(7))
+
+    ## `MP' and `GF'.
+    me.assertEqual(C.MP(5), 5)
+    me.assertEqual(5, C.MP(5))
+    me.assertNotEqual(C.GF(5), 5)
+    me.assertNotEqual(5, C.GF(5))
+    me.assertNotEqual(C.MP(5), C.GF(5))
+    me.assertNotEqual(C.GF(5), C.MP(5))
+
+    me.assertEqual(C.MP(5) + 3, 8)
+    me.assertEqual(3 + C.MP(5), 8)
+    me.assertRaises(TypeError, T.add, C.GF(5), 3)
+    me.assertRaises(TypeError, T.add, 3, C.GF(5))
+    me.assertRaises(TypeError, T.add, C.MP(5), C.GF(3))
+    me.assertRaises(TypeError, T.add, C.GF(3), C.MP(5))
+    if T.PY3: me.assertRaises(TypeError, T.le, C.MP(4), C.GF(5))
+
+    ## Field elements.
+    me.assertEqual(k(3) + 4, 7)
+    me.assertEqual(4 + k(3), 7)
+    me.assertEqual(k(3) + C.MP(4), 7)
+    me.assertEqual(C.MP(4) + k(3), 7)
+    me.assertEqual(k(3) + 4, 7)
+    me.assertEqual(C.GF(7) + kk(3), C.GF(4))
+    me.assertEqual(kk(3) + C.GF(7), C.GF(4))
+
+    me.assertRaises(TypeError, T.add, k(3), 3.0)
+    me.assertRaises(TypeError, T.add, k(3), "3")
+    me.assertRaises(TypeError, T.add, k(3), kk(3))
+    me.assertRaises(TypeError, T.add, kk(3), k(3))
+    me.assertRaises(TypeError, T.add, k(3), C.GF(7))
+    me.assertRaises(TypeError, T.add, C.GF(7), k(3))
+    me.assertRaises(TypeError, T.add, kk(3), 7)
+    me.assertRaises(TypeError, T.add, kk(3), 7.0)
+    me.assertRaises(TypeError, T.add, kk(3), "7")
+    me.assertRaises(TypeError, T.add, 7, kk(3))
+    me.assertRaises(TypeError, T.add, kk(3), C.MP(7))
+    me.assertRaises(TypeError, T.add, C.MP(7), kk(3))
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-ec.py b/t/t-ec.py
new file mode 100644 (file)
index 0000000..b85105d
--- /dev/null
+++ b/t/t-ec.py
@@ -0,0 +1,263 @@
+### -*- mode: python, coding: utf-8 -*-
+###
+### Testing elliptic curve functionality
+###
+### (c) 2019 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.
+
+import catacomb as C
+import unittest as U
+import testutils as T
+
+k = C.PrimeField(19)
+E = k.ec(-3, 6)
+P = E(0) # order 26
+
+kk = C.BinPolyField(0x13)
+EE = kk.ec(1, 8)
+PP = EE(8) # order 20
+
+###--------------------------------------------------------------------------
+class TestCurvelessPoints (U.TestCase):
+  """Test handling of points without an explicit curve."""
+
+  def test(me):
+
+    ## Construction.
+    O = C.ECPt(); me.assertFalse(O)
+    P = C.ECPt(12345, 67890); me.assertTrue(P)
+    Q = C.ECPt(23456, 78901); me.assertTrue(Q); me.assertNotEqual(P, Q)
+    R = C.ECPt(O); me.assertFalse(R); me.assertEqual(O, R); me.assertNotEqual(P, R)
+    R = C.ECPt(None); me.assertFalse(R); me.assertEqual(O, R)
+    me.assertEqual(C.ECPt("12345, 67890"), P)
+    me.assertEqual(C.ECPt((12345, 67890)), P)
+    me.assertRaises(TypeError, C.ECPt, ())
+    me.assertRaises(TypeError, C.ECPt, (1234,))
+    me.assertRaises(TypeError, C.ECPt, (1, 2, 3, 4))
+    me.assertRaises(ValueError, C.ECPt, "12345")
+    me.assertRaises(ValueError, C.ECPt, "12345,")
+    me.assertRaises(ValueError, C.ECPt, "12345, xyzzy")
+    me.assertRaises(ValueError, C.ECPt, "12345, 67890!??")
+    me.assertRaises(ValueError, C.ECPt, (1, 2, 3))
+    me.assertRaises(TypeError, C.ECPt, 1, 2, 3)
+    me.assertRaises(TypeError, C.ECPt, 1234)
+    me.assertRaises(TypeError, C.ECPt, object())
+    me.assertRaises(TypeError, C.ECPt, 1, None)
+    #me.assertRaises(TypeError, C.ECPt, (1, None))
+
+    ## Arithmetic shouldn't work.
+    me.assertRaises(TypeError, T.neg, P)
+    me.assertRaises(TypeError, T.add, P, Q)
+
+    ## Attributes.  We only have raw integer access.
+    me.assertTrue(P.point is P)
+    me.assertEqual(P.ix, 12345)
+    me.assertEqual(P.iy, 67890)
+    me.assertEqual(P.tobuf(), C.bytes("000230390003010932"))
+    me.assertRaises(AttributeError, lambda: P.curve)
+    me.assertRaises(AttributeError, lambda: P.x)
+    me.assertRaises(AttributeError, lambda: P.y)
+    me.assertRaises(AttributeError, lambda: P._x)
+    me.assertRaises(AttributeError, lambda: P._y)
+    me.assertRaises(AttributeError, lambda: P._z)
+
+    ## Encoding and decoding.
+    P = C.ECPt(254, 291)
+    me.assertEqual(O.tobuf(), C.bytes("0000"))
+    me.assertEqual(C.ECPt(0, 0).tobuf(), C.bytes("000100000100"))
+    me.assertEqual(P.tobuf(), C.bytes("0001fe00020123"))
+    me.assertEqual(C.ECPt.frombuf(C.bytes("0001fe000201233f")),
+                   (P, C.bytes("3f")))
+    me.assertRaises(ValueError, C.ECPt.frombuf, C.bytes("0001fe000201"))
+
+    ## String conversion and parsing.
+    me.assertEqual(str(P), "(254, 291)")
+    me.assertEqual(C.ECPt.parse("254, 291)"), (P, ")"))
+    me.assertEqual(C.ECPt.parse("(254, 291)"), (P, ""))
+    me.assertRaises(ValueError, C.ECPt.parse, "(254, 291")
+
+###--------------------------------------------------------------------------
+class TestCurves (T.GenericTestMixin):
+  """Test elliptic curves."""
+
+  def test_compare(me):
+    me.assertEqual(E, E)
+    E1 = k.ec(-3, 6)
+    me.assertFalse(E is E1)
+    me.assertEqual(E, E1)
+    me.assertNotEqual(E, EE)
+    me.assertNotEqual(E, [])
+
+  def _test_curve(me, einfo, checkfail = False):
+
+    ## Some useful values.
+    E = einfo.curve
+    P = einfo.G
+    O = E()
+    n = einfo.r
+    h = einfo.h
+    k = E.field
+    me.assertTrue(n.primep()); l = C.NicePrimeField(n)
+
+    ## Check that things are basically sane.
+    me.assertFalse(O)
+    me.assertTrue(P)
+    me.assertTrue(n)
+    nP = n*P; me.assertFalse(nP); me.assertEqual(nP, O)
+
+    ## Check point construction.
+    me.assertEqual(type(P.ix), C.MP)
+    me.assertEqual(type(P.iy), C.MP)
+    me.assertTrue(isinstance(P.x, C.FE))
+    me.assertTrue(isinstance(P.y, C.FE))
+    me.assertTrue(isinstance(P._x, C.FE))
+    me.assertTrue(isinstance(P._y, C.FE))
+    if isinstance(E, C.ECPrimeProjCurve) or isinstance(E, C.ECBinProjCurve):
+      me.assertTrue(isinstance(P._z, C.FE))
+    else:
+      me.assertEqual(P._z, None)
+    me.assertEqual(E(None), O)
+    me.assertEqual(E(P.x, P.y), P)
+    me.assertEqual(E((P.x, P.y)), P)
+    me.assertEqual(E(P._x, P._y, P._z), P)
+    me.assertEqual(E((P._x, P._y, P._z)), P)
+    Q = E(P.point); me.assertEqual(type(Q), E); me.assertEqual(Q, P)
+    me.assertEqual(E("%s, %s" % (P.ix, P.iy)), P)
+    me.assertRaises(ValueError, E, "1234")
+    me.assertRaises(ValueError, E, "1234,")
+    me.assertRaises(ValueError, E, "1234, 5678?")
+    me.assertRaises(TypeError, E, 1, None)
+    Q = E(P.ix); me.assertTrue(Q == P or Q == -P)
+    for i in T.range(128):
+      x = P.ix + i
+      try: E(x)
+      except ValueError: badx = x; break
+    else:
+      me.fail("no off-curve point found")
+
+    ## Attributes.
+    me.assertEqual(P.ix, P.point.ix)
+    me.assertEqual(P.iy, P.point.iy)
+    me.assertEqual(P.x, k(P.point.ix))
+    me.assertEqual(P.y, k(P.point.iy))
+    R = 6*P
+    if isinstance(E, C.ECPrimeProjCurve) or isinstance(E, C.ECBinProjCurve):
+      me.assertEqual(P._z, k.one)
+      me.assertEqual(R._x, R.x*R._z**2)
+      me.assertEqual(R._y, R.y*R._z**3)
+      me.assertNotEqual(R._z, k.one)
+    else:
+      me.assertEqual(P._z, None)
+      me.assertEqual(R._x, R.x)
+      me.assertEqual(R._y, R.y)
+      me.assertEqual(R._z, None)
+    me.assertEqual(R.curve, E)
+
+    ## Arithmetic.
+    Q = 17*P
+    me.assertEqual(Q, P*17)
+    me.assertEqual(-Q, (n - 17)*P)
+    me.assertEqual(Q + R, 23*P)
+    me.assertEqual(Q + R.point, 23*P)
+    me.assertRaises(TypeError, T.add, Q.point, R.point)
+    me.assertRaises(TypeError, T.mul, kk(1), Q)
+    me.assertEqual(Q - R, 11*P)
+    me.assertEqual(l(17)*P, Q)
+    me.assertEqual(P*l(17), Q)
+
+    ## Ordering.
+    me.assertTrue(P == P)
+    me.assertTrue(P != Q)
+    me.assertRaises(TypeError, T.lt, P, Q)
+    me.assertRaises(TypeError, T.le, P, Q)
+    me.assertRaises(TypeError, T.ge, P, Q)
+    me.assertRaises(TypeError, T.gt, P, Q)
+
+    ## Encoding.
+    Z0 = C.ByteString.zero(0)
+    Z1 = C.ByteString.zero(1)
+    me.assertEqual(O.ec2osp(), Z1)
+    me.assertEqual(E.os2ecp(Z1), (O, Z0))
+    t = C.WriteBuffer() \
+         .putu8(0x04) \
+         .put(P.ix.storeb(k.noctets)) \
+         .put(P.iy.storeb(k.noctets)) \
+         .contents
+    me.assertEqual(P.ec2osp(), t)
+    me.assertEqual(C.WriteBuffer().putecptraw(P).contents, t)
+    me.assertEqual(C.WriteBuffer().ec2osp(P).contents, t)
+    me.assertEqual(E.os2ecp(t), (P, Z0))
+    me.assertEqual(C.ReadBuffer(t).getecptraw(E), P)
+    me.assertEqual(C.ReadBuffer(t).os2ecp(E), P)
+    if isinstance(k, C.PrimeField): ybit = int(P.iy&1)
+    else:
+      try: ybit = int((P.y/P.x).value&C.GF(1))
+      except ZeroDivisionError: ybit = 0
+    t = C.WriteBuffer() \
+         .putu8(0x02 | ybit) \
+         .put(P.ix.storeb(k.noctets)) \
+         .contents
+    me.assertEqual(P.ec2osp(C.EC_LSB), t)
+    me.assertEqual(C.WriteBuffer().ec2osp(P, C.EC_LSB).contents, t)
+    me.assertEqual(E.os2ecp(t, C.EC_LSB), (P, Z0))
+    me.assertEqual(C.ReadBuffer(t).os2ecp(E, C.EC_LSB), P)
+
+    ## Curve methods.
+    Q = E.find(P.x); me.assertTrue(Q == P or Q == -P)
+    Q = E.find(P.ix); me.assertTrue(Q == P or Q == -P)
+    me.assertRaises(ValueError, E.find, badx)
+    for i in T.range(128):
+      if E.rand() != P: break
+    else:
+      me.fail("random point always gives me P")
+    for i in T.range(128):
+      R = E.rand(C.LCRand(i))
+      if R != P: break
+    else:
+      me.fail("random point always gives me P")
+    me.assertEqual(R, E.rand(C.LCRand(i)))
+    me.assertEqual(E.parse("%s, %s!xyzzy" % (P.ix, P.iy)), (P, "!xyzzy"))
+
+    ## Simultaneous multiplication.
+    Q, R, S = 5*P, 7*P, 11*P
+    me.assertEqual(E.mmul(set([(Q, 9), (R, 8), (S, 5)])), 156*P)
+    me.assertEqual(E.mmul([Q, 9, R, 8, S, 5]), 156*P)
+    me.assertEqual(E.mmul(Q, 9, R, 8, S, 5), 156*P)
+
+    ## Test other curve info things while we're here.
+    if not checkfail: einfo.check()
+    else: me.assertRaises(ValueError, einfo.check)
+
+  def test_tinycurves(me):
+    me._test_curve(C.ECInfo(E, 2*P, 13, 2), checkfail = True)
+    ei = C.ECInfo.fromstring("binpoly: 0x13; bin: 0x01, 0x08; 0x02, 0x0c: 5*4")
+    me._test_curve(ei, checkfail = True)
+
+TestCurves.generate_testcases((name, C.eccurves[name]) for name in
+  ["nist-p256", "nist-k233", "nist-b163", "nist-b283n"])
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-field.py b/t/t-field.py
new file mode 100644 (file)
index 0000000..3871b45
--- /dev/null
@@ -0,0 +1,150 @@
+### -*-python-*-
+###
+### Testing finite-field functionality
+###
+### (c) 2019 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.
+
+import itertools as I
+
+import catacomb as C
+import unittest as U
+import testutils as T
+
+###--------------------------------------------------------------------------
+class TestFields (T.GenericTestMixin):
+  """Test finite fields."""
+
+  def _test_field(me, k):
+
+    ## Some initial values.
+    zero = k.zero
+    one = k.one
+    x = k(2760820866)
+    y = k(3757175895)
+    z = k(920571320)
+
+    ## Check that they're all different.
+    v = [zero, one, x, y, z]
+    for i in T.range(len(v)):
+      for j in T.range(len(v)):
+        if i == j: me.assertEqual(v[i], v[j])
+        else: me.assertNotEqual(v[i], v[j])
+
+    ## Basic arithmetic.  Knowing the answers is too hard.  For now, just
+    ## check that the field laws hold.
+    t = x + y; me.assertEqual(t - y, x); me.assertEqual(t - x, y)
+    t = x - y; me.assertEqual(t + y, x)
+    t = x*y; me.assertEqual(t/x, y); me.assertEqual(t/y, x)
+    me.assertEqual(+x, x)
+    me.assertEqual(x + -x, zero)
+    me.assertEqual(x - x, zero)
+    me.assertEqual(x*x.inv(), one)
+    me.assertEqual(x/x, one)
+    me.assertRaises(ZeroDivisionError, k.inv, zero)
+
+    ## Exponentiation.  At least we know the group exponent.
+    me.assertEqual(x**(k.q - 1), one)
+
+    ## Comparisons.  We've already done equality and inequality, and nothing
+    ## else should work.
+    me.assertRaises(TypeError, T.lt, x, y)
+    me.assertRaises(TypeError, T.le, x, y)
+    me.assertRaises(TypeError, T.ge, x, y)
+    me.assertRaises(TypeError, T.gt, x, y)
+
+    ## Conversion back to integer.
+    me.assertEqual(int(x), 2760820866)
+
+    ## Square and square-root.  In a prime field, we may need to search
+    ## around to find a quadratic residue.  In binary fields, squaring is
+    ## linear, and every element has a unique square root.
+    me.assertEqual(x*x, x.sqr())
+    for i in T.range(128):
+      t = k(int(x) + i)
+      q = t.sqrt()
+      if q is not None: break
+    else:
+      me.fail("no quadratic residue found")
+    me.assertEqual(q.sqr(), t)
+
+    ## Hex and octal conversions.
+    me.assertEqual(hex(x), hex(T.long(x.value)).rstrip("L"))
+    me.assertEqual(oct(x), oct(T.long(x.value)).rstrip("L"))
+
+    if isinstance(k, C.PrimeField):
+
+      ## Representation.
+      me.assertEqual(type(x.value), C.MP)
+      me.assertEqual(k.type, C.FTY_PRIME)
+
+      ## Properties.
+      me.assertEqual(k.p, k.q)
+
+      ## Operations.
+      me.assertEqual(x.dbl(), 2*x)
+      me.assertEqual(x.tpl(), 3*x)
+      me.assertEqual(x.qdl(), 4*x)
+      me.assertEqual(x.hlv(), x/2)
+
+    else:
+
+      ## Representation.
+      me.assertEqual(type(x.value), C.GF)
+      me.assertEqual(k.type, C.FTY_BINARY)
+
+      ## Properties.
+      me.assertEqual(k.m, k.nbits)
+      me.assertEqual(k.p.degree, k.m)
+      if isinstance(k, C.BinNormField):
+        l = C.BinPolyField(k.p)
+        a, b = l.zero, l(k.beta)
+        for i in T.range(k.m):
+          a += b
+          b = b.sqr()
+        me.assertEqual(a, l.one)
+
+      ## Operations.
+      for i in T.range(128):
+        t = k(int(x) + i)
+        u = t.quadsolve()
+        if u is not None: break
+      else:
+        me.fail("no quadratic solution found")
+      me.assertEqual(u*u + u, t)
+
+    ## Encoding.
+    me.assertEqual(k.nbits, (k.q - 1).nbits)
+    me.assertEqual(k.noctets, (k.q - 1).noctets)
+
+TestFields.generate_testcases \
+  (("%s-%s" % (name, suffix), getfn(C.eccurves[name]))
+   for suffix, getfn in [("coords", lambda einfo: einfo.curve.field),
+                         ("scalars", lambda einfo: C.PrimeField(einfo.r))]
+   for name in ["nist-p256", "nist-k233", "nist-b163", "nist-b283n"])
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-group.py b/t/t-group.py
new file mode 100644 (file)
index 0000000..e91de94
--- /dev/null
@@ -0,0 +1,255 @@
+### -*-python-*-
+###
+### Testing cyclic-group functionality
+###
+### (c) 2019 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.
+
+import catacomb as C
+import unittest as U
+import testutils as T
+
+###--------------------------------------------------------------------------
+class TestGroups (T.GenericTestMixin):
+  """Test cyclic groups."""
+
+  def _test_group(me, G):
+
+    ## Some initial values.
+    id = G.i
+    g = G.g
+    x = g**2014319630
+    y = g**721326623
+
+    ## Check that they're all different.
+    v = [id, g, x, y]
+    for i in T.range(len(v)):
+      for j in T.range(len(v)):
+        if i == j: me.assertEqual(v[i], v[j])
+        else: me.assertNotEqual(v[i], v[j])
+
+    ## Basic arithmetic.  Knowing the answers is too hard.  For now, just
+    ## check that the field laws hold.
+    t = x*y; me.assertEqual(t/x, y); me.assertEqual(t/y, x)
+    me.assertEqual(x*x.inv(), id)
+    me.assertEqual(x.sqr(), x*x)
+    me.assertEqual(x/x, id)
+
+    ## Exponentiation.
+    me.assertEqual(x**G.r, id)
+    me.assertEqual(G.mexp((x, 5), (y, 7)), g**15120884511)
+
+    ## Comparisons.  We've already done equality and inequality, and nothing
+    ## else should work.
+    me.assertRaises(TypeError, T.lt, x, y)
+    me.assertRaises(TypeError, T.le, x, y)
+    me.assertRaises(TypeError, T.ge, x, y)
+    me.assertRaises(TypeError, T.gt, x, y)
+
+    if isinstance(G, C.ECGroup):
+
+      ## Properties.
+      me.assertEqual(G.noctets, 2*G.info.curve.field.noctets + 1)
+      me.assertEqual(G.nbits, 2*G.info.curve.field.nbits)
+
+      ## Conversion to integer.
+      i = y.toint(); me.assertEqual(type(i), C.MP)
+      t = G(i); me.assertTrue(t == y or t == y.inv())
+
+      ## Conversion to elliptic curve.
+      Q = y.toec()
+      me.assertTrue(isinstance(Q, C.ECPtCurve))
+      me.assertEqual(G(Q), y)
+
+      ## Encoding.
+      t = y.tobuf()
+      me.assertEqual(t, Q.tobuf())
+      me.assertEqual(G.frombuf(t)[0], y)
+      me.assertEqual(id.tobuf(), C.ByteString.zero(2))
+      t = y.toraw()
+      me.assertEqual(t, Q.ec2osp())
+      me.assertEqual(G.fromraw(t)[0], y)
+      me.assertEqual(id.toraw(), C.ByteString.zero(1))
+
+    else:
+
+      ## Properties.
+      me.assertEqual(G.noctets, G.info.p.noctets)
+      if isinstance(G.info, C.BinDHInfo):
+        me.assertEqual(G.nbits, G.info.p.degree)
+      else:
+        me.assertEqual(G.nbits, G.info.p.nbits)
+
+      ## Conversion to integer.
+      i = y.toint(); me.assertEqual(type(i), C.MP)
+      me.assertTrue(G(i) == y)
+
+      ## Conversion to elliptic curve.
+      me.assertRaises(TypeError, G.toec, x)
+
+      ## Encoding.
+      t = y.tobuf()
+      me.assertEqual(t, C.WriteBuffer().putmp(i).contents)
+      me.assertEqual(G.frombuf(t)[0], y)
+      me.assertEqual(id.tobuf(), C.bytes("000101"))
+      t = y.toraw()
+      me.assertEqual(t, i.storeb(G.noctets))
+      me.assertEqual(G.fromraw(t)[0], y)
+      me.assertEqual(id.toraw(),
+                     C.WriteBuffer().zero(G.noctets - 1).putu8(1).contents)
+
+    ## String conversion.
+    ystr = str(y)
+    me.assertEqual(G.fromstring(ystr), (y, ""))
+
+    ## Checking operations.
+    me.assertRaises(ValueError, id.check)
+    x.check()
+    y.check()
+    G.checkgroup()
+
+  def test_dhinfo(me):
+    dhinfo = C.DHInfo.parse("127, 7, 2")
+    me.assertEqual(dhinfo.p, 127)
+    me.assertEqual(dhinfo.r, 7)
+    me.assertEqual(dhinfo.g, 2)
+    dhinfo.group().checkgroup()
+
+  def test_bindhinfo(me):
+    bindhinfo = C.BinDHInfo.parse("0x805, 89, 0x22")
+    me.assertEqual(bindhinfo.p, C.GF(0x805))
+    me.assertEqual(bindhinfo.r, 89)
+    me.assertEqual(bindhinfo.g, C.GF(0x22))
+    bindhinfo.group().checkgroup()
+
+  def test_parse(me):
+
+    G = C.Group.parse("prime 127, 7, 2")
+    me.assertEqual(G.info.p, 127)
+    me.assertEqual(G.r, 7)
+    me.assertEqual(G.info.g, 2)
+    G.checkgroup()
+
+    G = C.Group.parse("bin 0x805, 89, 0x22")
+    me.assertEqual(G.info.p, C.GF(0x805))
+    me.assertEqual(G.r, 89)
+    me.assertEqual(G.info.g, C.GF(0x22))
+    G.checkgroup()
+
+  def test_gen_schnorr(me):
+    ev = T.EventRecorder()
+    dhinfo = C.DHInfo.generate(512, 64, event = ev,
+                               rng = T.detrand("schnorr"))
+    me.assertEqual(dhinfo.p.nbits, 512)
+    me.assertEqual(dhinfo.r.nbits, 64)
+    me.assertTrue(dhinfo.p.primep())
+    me.assertTrue(dhinfo.r.primep())
+    me.assertEqual(dhinfo.p%dhinfo.r, 1)
+    me._test_group(dhinfo.group())
+    me.assertEqual(ev.events, "[q:F4/P26/D][p:F5/P5/D][g:D]")
+
+  def test_gen_limlee(me):
+    ev = T.EventRecorder()
+    dhinfo, ff = C.DHInfo.genlimlee(512, 64, event = ev, ievent = ev,
+                                    rng = T.detrand("limlee"))
+    me.assertEqual(dhinfo.p.nbits, 512)
+    me.assertEqual(dhinfo.r.nbits, 64)
+    me.assertTrue(dhinfo.p.primep())
+    me.assertTrue(dhinfo.r.primep())
+    for f in ff:
+      me.assertTrue(f.primep())
+      me.assertTrue(f.nbits >= 64)
+    me.assertEqual(C.MPMul().factor(2).factor(ff).done() + 1, dhinfo.p)
+    me._test_group(dhinfo.group())
+    me.assertEqual(ev.events,
+                   "[p:"
+                   "[p_0:F8/P26/D]"
+                   "[p_1:P26/D]"
+                   "[p_2:F4/P26/D]"
+                   "[p_3:P26/D]"
+                   "[p_4:F1/P26/D]"
+                   "[p_5:F1/P26/D]"
+                   "[p_6:P26/D]"
+                   "[p*_7:P26/D]"
+                   "[p_8:F1/P26/D]"
+                   "[p_9:F1/P26/D]"
+                   "[p*_10:P26/D]"
+                   "[p_11:F6/P26/D]"
+                   "[p_12:P26/D]"
+                   "[p_13:P26/D]"
+                   "[p_14:F4/P26/D]"
+                   "[p_15:F1/P26/D]"
+                   "[p_16:F1/P26/D]"
+                   "[p_17:F1/P26/D]"
+                   "[p_18:F6/P26/D]"
+                   "[p_19:F1/P26/D]"
+                   "[p_20:F3/P26/D]"
+                   "[p_21:P26/D]"
+                   "[p_22:F2/P26/D]"
+                   "[p_23:F4/P26/D]"
+                   "[p_24:F7/P26/D]"
+                   "[p_25:F2/P26/D]"
+                   "[p_26:F9/P26/D]"
+                   "[p_27:F4/P26/D]"
+                   "[p*_28:F11/P26/D]"
+                   "[p*_29:F4/P26/D]"
+                   "[p*_30:F1/P26/D]"
+                   "[p*_31:F6/P26/D]"
+                   "[p*_32:F4/P26/D]"
+                   "[p*_33:F3/P26/D]"
+                   "[p*_34:P26/D]"
+                   "[p*_35:F3/P26/D]"
+                   "[p*_36:F1/P26/D]"
+                   "[p*_37:F1/P26/D]"
+                   "[p*_38:F4/P26/D]"
+                   "[p*_39:P26/D]"
+                   "[p*_40:P26/D]"
+                   "[p*_41:F2/P26/D]"
+                   "[p*_42:F1/P26/D]"
+                   "F22/P5/D]"
+                   "[g:D]")
+
+  def test_gen_kcdsa(me):
+    ev = T.EventRecorder()
+    dhinfo, h = C.DHInfo.genkcdsa(512, 64, event = ev,
+                                  rng = T.detrand("kcdsa"))
+    me.assertEqual(dhinfo.p.nbits, 512)
+    me.assertEqual(dhinfo.r.nbits, 64)
+    me.assertTrue(dhinfo.p.primep())
+    me.assertTrue(dhinfo.r.primep())
+    me.assertTrue(h.primep())
+    me.assertEqual(2*h*dhinfo.r + 1, dhinfo.p)
+    me._test_group(dhinfo.group())
+    me.assertEqual(ev.events, "[v:F23/P6/D][p:F86/P26/D][g:D]")
+
+TestGroups.generate_testcases((name, map[name].group()) for name, map in
+  [("nist-p256", C.eccurves),
+   ("nist-b283", C.eccurves),
+   ("catacomb-ll-128-512", C.primegroups),
+   ("p1363-64", C.bingroups)])
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-key.py b/t/t-key.py
new file mode 100644 (file)
index 0000000..27808a4
--- /dev/null
@@ -0,0 +1,345 @@
+### -*-python-*-
+###
+### Testing key-management functionality
+###
+### (c) 2019 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.
+
+import catacomb as C
+import sys as SYS
+import unittest as U
+import testutils as T
+import time as TM
+
+###--------------------------------------------------------------------------
+class TestKeyError (U.TestCase):
+
+  def test_keyerror(me):
+
+    try: C.KeyFile("notexist", C.KOPEN_NOFILE).newkey(1, "foo")
+    except C.KeyError: e = SYS.exc_info()[1]
+    else: me.fail("expected `catacomb.KeyError'")
+    me.assertEqual(e.err, C.KERR_READONLY)
+    me.assertEqual(e.errstring, "Key file is read-only")
+    me.assertEqual(e.args, (C.KERR_READONLY,))
+    me.assertEqual(str(e),
+                   "KERR_READONLY (%d): Key file is read-only" %
+                     C.KERR_READONLY)
+
+    me.assertRaises(TypeError, C.KeyError)
+    token = ["TOKEN"]
+    e = C.KeyError(C.KERR_DUPID, token)
+    me.assertEqual(e.err, C.KERR_DUPID)
+    me.assertEqual(e.errstring, "Key id already exists")
+    me.assertEqual(e.args, (C.KERR_DUPID, token))
+
+###--------------------------------------------------------------------------
+class TestKeyFile (U.TestCase):
+
+  def test_keyring(me):
+
+    kf = C.KeyFile("t/keyring")
+
+    ## Check basic attributes.
+    me.assertEqual(kf.name, "t/keyring")
+    me.assertEqual(kf.modifiedp, False)
+    me.assertEqual(kf.writep, False)
+    me.assertEqual(kf.filep, False)
+
+    ## Check enumeration.
+    me.assertEqual(set(k.type for k in T.itervalues(kf)),
+                   set(["rsa", "ec", "ec-param", "twofish"]))
+    me.assertEqual(len(kf), 4)
+
+    ## Start with `rsa'.
+    k = kf.bytag("ron")
+    me.assertEqual(k.type, "rsa")
+    me.assertEqual(k.id, 0x8599dbab)
+    me.assertEqual(type(k.data), C.KeyDataStructured)
+    me.assertEqual(set(k.data), set(["e", "n", "private"]))
+    priv = k.data["private"]
+    me.assertEqual(type(priv), C.KeyDataEncrypted)
+    me.assertRaises(C.KeyError, priv.unlock, T.bin("wrong secret"))
+    priv = priv.unlock(T.bin("very secret"))
+    me.assertEqual(type(priv), C.KeyDataStructured)
+    me.assertEqual(set(priv),
+                   set(["p", "q", "d", "d-mod-p", "d-mod-q", "q-inv"]))
+    me.assertEqual(k.data["n"].mp, priv["p"].mp*priv["q"].mp)
+
+    ## This key has an attribute.  Poke about at them.
+    a = k.attr
+    me.assertEqual(len(a), 1)
+    me.assertEqual(set(a), set(["attr"]))
+    me.assertEqual(a["attr"], "value")
+    me.assertRaises(KeyError, lambda: a["notexist"])
+    me.assertEqual(a.get("attr"), "value")
+    me.assertEqual(a.get("notexist"), None)
+
+    ## Check fingerprinting while we're here.
+    for filter in ["-secret", "none"]:
+      h = C.sha256(); me.assertTrue(k.fingerprint(h, filter)); fp0 = h.done()
+      h = C.sha256()
+      h.hash(T.bin("catacomb-key-fingerprint:")) \
+       .hashu32(k.id) \
+       .hashbuf8(T.bin(k.type))
+      h.hash(k.data.encode(filter))
+      for a in sorted(T.iterkeys(k.attr)):
+        h.hashbuf8(T.bin(a)).hashbuf16(T.bin(k.attr[a]))
+      fp1 = h.done()
+      me.assertEqual(fp0, fp1)
+
+    ## Try `ec-param'.  This should be fairly easy.
+    k = kf["ec-param"]
+    me.assertEqual(k.tag, None)
+    me.assertEqual(k.id, 0x4a4e1ee7)
+    me.assertEqual(type(k.data), C.KeyDataStructured)
+    me.assertEqual(set(k.data), set(["curve"]))
+    curve = k.data["curve"]
+    me.assertEqual(type(curve), C.KeyDataString)
+    me.assertEqual(curve.str, "nist-p256")
+
+    ## Check qualified-tag lookups.
+    me.assertRaises(C.KeyError, kf.qtag, "notexist.curve")
+    me.assertRaises(C.KeyError, kf.qtag, "ec-param.notexist")
+    t, k, kd = kf.qtag("ec-param.curve")
+    me.assertEqual(t, "4a4e1ee7:ec-param.curve")
+    me.assertEqual(k.type, "ec-param")
+    me.assertEqual(type(kd), C.KeyDataString)
+    me.assertEqual(kd.str, "nist-p256")
+
+    ## Try `ec'.  A little trickier.
+    k = kf.bytype("ec")
+    me.assertEqual(k.tag, None)
+    me.assertEqual(k.id, 0xbd761d35)
+    me.assertEqual(type(k.data), C.KeyDataStructured)
+    me.assertEqual(set(k.data), set(["curve", "p", "private"]))
+    curve = k.data["curve"]
+    me.assertEqual(type(curve), C.KeyDataString)
+    me.assertEqual(curve.str, "nist-p256")
+    einfo = C.eccurves[curve.str]
+    me.assertEqual(type(k.data["p"]), C.KeyDataECPt)
+    X = k.data["p"].ecpt
+    priv = k.data["private"]
+    me.assertEqual(type(priv), C.KeyDataEncrypted)
+    me.assertRaises(C.KeyError, priv.unlock, T.bin("wrong secret"))
+    priv = priv.unlock(T.bin("super secret"))
+    me.assertEqual(type(priv), C.KeyDataStructured)
+    me.assertEqual(set(priv), set(["x"]))
+    x = priv["x"].mp
+    me.assertEqual(x*einfo.G, X)
+
+    ## Finish with `twofish'.
+    k = kf.byid(0x60090be2)
+    me.assertEqual(k.tag, None)
+    me.assertEqual(k.type, "twofish")
+    me.assertEqual(type(k.data), C.KeyDataEncrypted)
+    me.assertRaises(C.KeyError, k.data.unlock, T.bin("wrong secret"))
+    kd = k.data.unlock(T.bin("not secret"))
+    me.assertEqual(type(kd), C.KeyDataBinary)
+    me.assertEqual(kd.bin, C.bytes("d337b98eea24425826df202a6a3d1ef8"
+                                   "377b71923fe1179451564776da29bb84"))
+
+    ## Check unsuccessful searches.
+    me.assertRaises(KeyError, lambda: kf["notexist"])
+    me.assertRaises(C.KeyError, kf.bytag, "notexist")
+    me.assertRaises(C.KeyError, kf.bytag, 12345)
+    me.assertEqual(kf.bytag("notexist", fail = False), None)
+    me.assertRaises(C.KeyError, kf.bytype, "notexist")
+    me.assertRaises(TypeError, kf.bytype, 12345)
+    me.assertEqual(kf.bytype("notexist", fail = False), None)
+    me.assertRaises(C.KeyError, kf.byid, 0x12345678)
+    me.assertEqual(kf.byid(0x12345678, fail = False), None)
+
+    me.assertRaises(C.KeyError, kf.mergeline, "nowhere", 2, "")
+
+    ## The keyring should be readonly.
+    me.assertRaises(C.KeyError, kf.newkey, 0x12345678, "fail")
+    me.assertRaises(C.KeyError, setattr, k, "tag", "foo")
+    me.assertRaises(C.KeyError, delattr, k, "tag")
+    me.assertRaises(C.KeyError, kf.mergeline, "notexist", 1,
+                    "22222222:test integer,public:32519164 forever forever -")
+    me.assertRaises(C.KeyError, setattr, k, "data", C.KeyDataString("foo"))
+
+  def test_keywrite(me):
+    kf = C.KeyFile("test", C.KOPEN_WRITE | C.KOPEN_NOFILE)
+    me.assertEqual(kf.modifiedp, False)
+    now = int(TM.time())
+    exp = now + 86400
+
+    k = kf.newkey(0x11111111, "first", exp)
+    me.assertEqual(kf.modifiedp, True)
+
+    me.assertEqual(k, kf[0x11111111])
+    me.assertEqual(k.exptime, exp)
+    me.assertEqual(k.deltime, exp)
+    me.assertRaises(ValueError, setattr, k, "deltime", C.KEXP_FOREVER)
+    k.exptime = exp + 5
+    me.assertEqual(k.data.str, "<unset>")
+    n = 9876543210
+    k.data = C.KeyDataMP(n)
+    me.assertEqual(k.data.mp, n)
+    me.assertEqual(k.comment, None)
+    c = ";; just a test"
+    k.comment = c
+    me.assertEqual(k.comment, c)
+    k.comment = None
+    me.assertEqual(k.comment, None)
+    k.comment = c
+    me.assertEqual(k.comment, c)
+    del k.comment
+    me.assertEqual(k.comment, None)
+
+    kf.mergeline("notexist", 1,
+                 "22222222:test integer,public:32519164 forever forever -")
+
+###--------------------------------------------------------------------------
+class TestKeyData (U.TestCase):
+
+  def test_flags(me):
+    me.assertEqual(C.KeyData.readflags("none"), (0, 0, ""))
+    me.assertEqual(C.KeyData.readflags("ec,public:..."),
+                   (C.KENC_EC | C.KCAT_PUB,
+                    C.KF_ENCMASK | C.KF_CATMASK,
+                    ":..."))
+    me.assertEqual(C.KeyData.readflags("int,burn"),
+                   (C.KENC_MP | C.KF_BURN, C.KF_ENCMASK | C.KF_BURN, ""))
+    me.assertRaises(C.KeyError, C.KeyData.readflags, "int,burn?")
+    me.assertRaises(C.KeyError, C.KeyData.readflags, "int,ec")
+    me.assertRaises(C.KeyError, C.KeyData.readflags, "snork")
+    me.assertEqual(C.KeyData.writeflags(0), "binary,symmetric")
+    me.assertEqual(C.KeyData.writeflags(C.KENC_EC | C.KCAT_PUB), "ec,public")
+
+  def test_misc(me):
+    kd = C.KeyDataStructured({ "a": C.KeyDataString("foo", "public"),
+                               "b": C.KeyDataMP(12345, "private"),
+                               "c": C.KeyDataString("bar", "public") })
+
+    kd2 = kd.copy()
+    me.assertEqual(type(kd2), C.KeyDataStructured)
+    me.assertEqual(set(T.iterkeys(kd2)), set(["a", "b", "c"]))
+
+    kd2 = C.KeyDataMP(12345, C.KCAT_PRIV).copy("private")
+
+    kd2 = kd.copy("-secret")
+    me.assertEqual(type(kd2), C.KeyDataStructured)
+    me.assertEqual(set(T.iterkeys(kd2)), set(["a", "c"]))
+
+    kd2 = kd.copy((0, C.KF_NONSECRET))
+    me.assertEqual(type(kd2), C.KeyDataStructured)
+    me.assertEqual(set(T.iterkeys(kd2)), set(["b"]))
+
+  def check_encode(me, kd):
+    me.assertEqual(C.KeyData.decode(kd.encode()), kd)
+    me.assertEqual(C.KeyData.read(kd.write()), (kd, ""))
+
+  def test_bin(me):
+    rng = T.detrand("kd-bin")
+    by = rng.block(16)
+    kd = C.KeyDataBinary(by, "symm,burn")
+    me.assertEqual(kd.bin, by)
+    me.check_encode(kd)
+
+  def test_mp(me):
+    rng = T.detrand("kd-mp")
+    x = rng.mp(128)
+    kd = C.KeyDataMP(x, "symm,burn")
+    me.assertEqual(kd.mp, x)
+    me.check_encode(kd)
+
+  def test_string(me):
+    s = "some random string"
+    kd = C.KeyDataString(s, "symm,burn")
+    me.assertEqual(kd.str, s)
+    me.check_encode(kd)
+
+  def test_enc(me):
+    rng = T.detrand("kd-enc")
+    ct = rng.block(16)
+    kd = C.KeyDataEncrypted(ct, "symm")
+    me.assertEqual(kd.ct, ct)
+    me.check_encode(kd)
+
+  def test_ecpt(me):
+    rng = T.detrand("kd-ec")
+    Q = C.ECPt(rng.mp(128), rng.mp(128))
+    kd = C.KeyDataECPt(Q, "symm,burn")
+    me.assertEqual(kd.ecpt, Q)
+    me.check_encode(kd)
+
+  def test_struct(me):
+    rng = T.detrand("kd-struct")
+    kd = C.KeyDataStructured({ "a": C.KeyDataString("a"),
+                               "b": C.KeyDataString("b") },
+                             c = C.KeyDataString("c"),
+                             d = C.KeyDataString("d"))
+    for i in ["a", "b", "c", "d"]: me.assertEqual(kd[i].str, i)
+    me.assertEqual(len(kd), 4)
+    me.check_encode(kd)
+    me.assertRaises(TypeError, C.KeyDataStructured, { "a": "a" })
+    me.assertRaises(ValueError, C.KeyDataStructured,
+                    { "a": C.KeyDataString("a") },
+                    a = C.KeyDataString("b"))
+
+###--------------------------------------------------------------------------
+### Mappings.
+
+class TestKeyFileMapping (T.ImmutableMappingTextMixin):
+  def _mkkey(me, i): return i
+  def _getkey(me, k): return k
+  def _getvalue(me, v): return v.data.mp
+
+  def test_keyfile(me):
+    kf = C.KeyFile("test", C.KOPEN_WRITE | C.KOPEN_NOFILE)
+    model = {}
+    for i in [1, 2, 3]:
+      model[i] = 100 + i
+      kf.newkey(i, "k#%d" % i).data = C.KeyDataMP(100 + i)
+
+    me.assertEqual(kf[1], kf[1])
+
+    me.check_immutable_mapping(kf, model)
+
+class TestKeyStructMapping (T.MutableMappingTestMixin):
+  def _mkvalue(me, i): return C.KeyDataMP(i)
+  def _getvalue(me, v): return v.mp
+
+  def test_keystructmap(me):
+    me.check_mapping(C.KeyDataStructured)
+
+class TestKeyAttrMapping (T.MutableMappingTestMixin):
+
+  def test_attrmap(me):
+    def mkmap():
+      kf = C.KeyFile("test", C.KOPEN_WRITE | C.KOPEN_NOFILE)
+      k = kf.newkey(0x12345678, "test-key")
+      return k.attr
+    me.check_mapping(mkmap)
+
+    a = mkmap()
+    me.assertRaises(TypeError, a.update, { 3: 3, 4: 5 })
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-misc.py b/t/t-misc.py
new file mode 100644 (file)
index 0000000..18a3170
--- /dev/null
@@ -0,0 +1,41 @@
+### -*- mode: python, coding: utf-8 -*-
+###
+### Miscellaneous tests
+###
+### (c) 2019 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.
+
+import catacomb as C
+import unittest as U
+
+###--------------------------------------------------------------------------
+class Tests (U.TestCase):
+
+  def test_path(me):
+    me.assertTrue(C.__file__.startswith("build"))
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-mp.py b/t/t-mp.py
new file mode 100644 (file)
index 0000000..f856f21
--- /dev/null
+++ b/t/t-mp.py
@@ -0,0 +1,603 @@
+### -*-python-*-
+###
+### Testing multiprecision integer (and related) functionality
+###
+### (c) 2019 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.
+
+import catacomb as C
+import unittest as U
+import testutils as T
+
+###--------------------------------------------------------------------------
+class TestMP (U.TestCase):
+
+  def test_make(me):
+    x = C.MP(5)
+    k = C.PrimeField(17)
+    kk = C.BinPolyField(C.GF(0x13))
+    E = k.ec(-3, 1)
+    me.assertEqual(x, 5)
+    me.assertTrue(C.MP(x) is x)
+    me.assertEqual(C.MP(k(8)), 8)
+    me.assertEqual(C.MP(kk(8)), 8)
+    me.assertEqual(C.MP(E(1, 4)), 1)
+    me.assertRaises(TypeError, C.MP, E())
+
+    me.assertEqual(int(x), 5)
+    big = 6556380541834372447694561492436749633
+    me.assertEqual(type(big), T.long)
+    y = C.MP(big)
+    me.assertEqual(y, big)
+    me.assertEqual(int(y), big)
+
+    me.assertEqual(C.MP(str(big)), big)
+    me.assertEqual(C.MP('0x4eeb684a0954ec4ceb255e3e9778d41'), big)
+    me.assertEqual(C.MP('4eeb684a0954ec4ceb255e3e9778d41', 16), big)
+    me.assertEqual(C.MP('0x4eeb684a0954ec4ceb255e3e9778d41', 16), big)
+    me.assertEqual(C.MP('0b0', 16), 176) # not 0
+
+    me.assertEqual(C.MP('047353320450112516611472622536175135706501'), big)
+    me.assertEqual(C.MP('0o47353320450112516611472622536175135706501'), big)
+    me.assertEqual(C.MP('047353320450112516611472622536175135706501', 8), big)
+    me.assertEqual(C.MP('0o47353320450112516611472622536175135706501', 8), big)
+    me.assertEqual(C.MP('47353320450112516611472622536175135706501', 8), big)
+
+    me.assertEqual(C.MP('0b100111011011001100000010001011'), 661438603)
+    me.assertEqual(C.MP('0b100111011011001100000010001011', 2), 661438603)
+    me.assertEqual(C.MP('100111011011001100000010001011', 2), 661438603)
+
+  def test_string(me):
+    y = C.MP(6556380541834372447694561492436749633)
+    me.assertEqual(str(y), '6556380541834372447694561492436749633')
+    me.assertEqual(repr(y), 'MP(6556380541834372447694561492436749633)')
+    me.assertEqual(hex(y), '0x4eeb684a0954ec4ceb255e3e9778d41')
+    me.assertEqual(oct(y), T.py23('0', '0o') +
+                   '47353320450112516611472622536175135706501')
+    try: bin
+    except NameError: pass
+    else: me.assertEqual(bin(C.MP(661438603)),
+                         '0b100111011011001100000010001011')
+
+  def test_number(me):
+    x, y, m, zero = C.MP(169), C.MP(24), C.MP(205), C.MP(0)
+
+    me.assertEqual(-x, -169)
+    me.assertEqual(~x, -170)
+    me.assertEqual(abs(x), 169)
+    me.assertEqual(abs(-x), 169)
+
+    me.assertEqual(x + y, 193)
+    me.assertEqual(x - y, 145)
+    me.assertEqual(x*y, 4056)
+    me.assertEqual(x&y, 8)
+    me.assertEqual(x&-y, 168)
+    me.assertEqual(x | y, 185)
+    me.assertEqual(x | -y, -23)
+    me.assertEqual(x ^ y, 177)
+    me.assertEqual(x ^ -y, -191)
+
+    me.assertEqual(x << 3, 1352)
+    me.assertEqual(x << -2, 42)
+    me.assertEqual(x >> 2, 42)
+    me.assertEqual(x >> -3, 1352)
+    me.assertEqual(-x << 3, -1352)
+    me.assertEqual(-x >> 2, -43)
+
+    u = x/y; me.assertEqual((u.numer, u.denom), (169, 24))
+    me.assertEqual(x//y, 7)
+    me.assertEqual(x%y, 1)
+    me.assertEqual(divmod(x, y), (7, 1))
+    me.assertRaises(ZeroDivisionError, lambda: x/zero)
+    me.assertRaises(ZeroDivisionError, lambda: x//zero)
+    me.assertRaises(ZeroDivisionError, lambda: x%zero)
+    me.assertRaises(ZeroDivisionError, divmod, x, zero)
+
+    me.assertEqual(pow(x, y), 294632676319010105335586872991323185304149065116720321)
+    me.assertEqual(pow(x, y, m), 51)
+    me.assertRaises(ValueError, pow, x, -y)
+    me.assertEqual(pow(x, -y, m), 201)
+    me.assertRaises(ZeroDivisionError, pow, x, -y, 208)
+
+    me.assertTrue(x)
+    me.assertFalse(zero)
+
+  def test_order(me):
+    x, y = C.MP(169), C.MP(24)
+    me.assertTrue(x == x)
+    me.assertFalse(x != x)
+    me.assertFalse(x == y)
+    me.assertTrue(x != y)
+    me.assertTrue(x > y)
+    me.assertFalse(y > x)
+    me.assertFalse(x > x)
+    me.assertTrue(x >= y)
+    me.assertFalse(y >= x)
+    me.assertTrue(x >= x)
+    me.assertFalse(x <= y)
+    me.assertTrue(y <= x)
+    me.assertTrue(x <= x)
+    me.assertFalse(x < y)
+    me.assertTrue(y < x)
+    me.assertFalse(x < x)
+
+  def test_float(me):
+    x, y = C.MP(169), 24.0
+    for fn in [T.add, T.sub, T.mul, T.div]:
+      me.assertEqual(type(fn(x, y)), float)
+      me.assertEqual(type(fn(y, x)), float)
+    me.assertEqual(x, 169.0)
+    me.assertNotEqual(x, 169.1)
+    me.assertNotEqual(x, 168.9)
+    me.assertTrue(x > 168.9)
+    me.assertTrue(x < 169.1)
+    z = 1.0
+    while z == z + 1: z *= 2.0
+    me.assertNotEqual(C.MP(int(z)) + 1, z)
+
+  def test_strconv(me):
+    x, y = C.MP(169), "24"
+    for fn in [T.add, T.sub, T.div]:
+      me.assertRaises(TypeError, fn, x, y)
+      me.assertRaises(TypeError, fn, y, x)
+    me.assertEqual(x*y, 169*"24")
+    me.assertEqual(y*x, 169*"24")
+
+  def test_bits(me):
+    x, y, zero = C.MP(169), C.MP(-24), C.MP(0)
+    me.assertTrue(x.testbit(0))
+    me.assertFalse(x.testbit(1))
+    me.assertFalse(x.testbit(1000))
+    me.assertFalse(y.testbit(0))
+    me.assertTrue(y.testbit(3))
+    me.assertTrue(y.testbit(1000))
+
+    me.assertEqual(x.setbit(0), x)
+    me.assertEqual(x.clearbit(0), 168)
+    me.assertEqual(x.setbit(1), 171)
+    me.assertEqual(x.clearbit(1), x)
+    me.assertEqual(y.setbit(0), -23)
+    me.assertEqual(y.clearbit(0), y)
+    me.assertEqual(y.setbit(3), y)
+    me.assertEqual(y.clearbit(3), -32)
+    me.assertEqual(y.setbit(1000), y)
+
+    me.assertEqual(x.nbits, 8)
+    me.assertEqual(y.nbits, 5)
+    me.assertEqual(zero.nbits, 0)
+
+  def test_loadstore(me):
+    x = C.MP(0x0123456789ab)
+    y = -x
+    u = C.MP(0xfedcba9876)
+
+    me.assertEqual(x.noctets, 6)
+    me.assertEqual(x.noctets2c, 6)
+    me.assertEqual(y.noctets, 6)
+    me.assertEqual(y.noctets2c, 6)
+    me.assertEqual(u.noctets, 5)
+    me.assertEqual(u.noctets2c, 6)
+
+    me.assertEqual(x, C.MP.loadb(C.bytes("0123456789ab")))
+    me.assertEqual(x, C.MP.loadb2c(C.bytes("0123456789ab")))
+    me.assertEqual(y, C.MP.loadb2c(C.bytes("fedcba987655")))
+
+    me.assertEqual(x.storeb(), C.bytes("0123456789ab"))
+    me.assertEqual(x.storeb(3), C.bytes("6789ab"))
+    me.assertEqual(x.storeb(8), C.bytes("00000123456789ab"))
+    me.assertEqual(x.storeb2c(), C.bytes("0123456789ab"))
+    me.assertEqual(x.storeb2c(3), C.bytes("6789ab"))
+    me.assertEqual(x.storeb2c(8), C.bytes("00000123456789ab"))
+    me.assertEqual(u.storeb2c(), C.bytes("00fedcba9876"))
+    me.assertEqual(y.storeb2c(), C.bytes("fedcba987655"))
+    me.assertEqual(y.storeb2c(3), C.bytes("987655"))
+    me.assertEqual(y.storeb2c(8), C.bytes("fffffedcba987655"))
+
+    me.assertEqual(x, C.MP.loadl(C.bytes("ab8967452301")))
+    me.assertEqual(x, C.MP.loadl2c(C.bytes("ab8967452301")))
+    me.assertEqual(y, C.MP.loadl2c(C.bytes("557698badcfe")))
+
+    me.assertEqual(x.storel(), C.bytes("ab8967452301"))
+    me.assertEqual(x.storel(3), C.bytes("ab8967"))
+    me.assertEqual(x.storel(8), C.bytes("ab89674523010000"))
+    me.assertEqual(x.storel2c(), C.bytes("ab8967452301"))
+    me.assertEqual(x.storel2c(3), C.bytes("ab8967"))
+    me.assertEqual(x.storel2c(8), C.bytes("ab89674523010000"))
+    me.assertEqual(u.storel2c(), C.bytes("7698badcfe00"))
+    me.assertEqual(y.storel2c(), C.bytes("557698badcfe"))
+    me.assertEqual(y.storel2c(3), C.bytes("557698"))
+    me.assertEqual(y.storel2c(8), C.bytes("557698badcfeffff"))
+
+    me.assertEqual(x.tobuf(), C.bytes("00060123456789ab"))
+    me.assertEqual((x, T.bin("abcd")),
+                   C.MP.frombuf(C.bytes("00060123456789ab61626364")))
+
+  def test_numbertheory(me):
+    p, x, y, z = C.MP(173), C.MP(169), C.MP(24), C.MP(20)
+
+    me.assertEqual(x.odd(), (0, x))
+    me.assertEqual(y.odd(), (3, 3))
+
+    me.assertEqual(x.sqr(), 28561)
+    me.assertEqual(x.sqrt(), 13)
+    me.assertEqual(y.sqrt(), 4)
+
+    me.assertEqual(x.gcd(y), 1)
+    me.assertEqual(x.gcdx(y), (1, -23, 162))
+    me.assertEqual(y.gcdx(x), (1, -7, 1))
+    me.assertEqual(x.modinv(y), 162)
+    me.assertEqual(y.modinv(x), 1)
+
+    me.assertEqual(x.jacobi(y), 1)
+    me.assertEqual(x.jacobi(13), 0)
+    me.assertEqual(y.jacobi(x), 1)
+    me.assertEqual(p.jacobi(y), 1)
+    me.assertEqual(p.jacobi(z), -1)
+    me.assertEqual(p.modsqrt(y), 71)
+    me.assertRaises(ValueError, p.modsqrt, z)
+
+    me.assertEqual(y.leastcongruent(x, 32), 184)
+
+    me.assertTrue(p.primep())
+    me.assertFalse(x.primep())
+
+  def test_bang(me):
+    me.assertEqual(C.MP.factorial(0), 1)
+    me.assertEqual \
+      (C.MP.factorial(50),
+       30414093201713378043612608166064768844377641568960512000000000000)
+    me.assertRaises((ValueError, OverflowError), C.MP.factorial, -1)
+
+  def test_fib(me):
+    me.assertEqual(C.MP.fibonacci(-2), -1)
+    me.assertEqual(C.MP.fibonacci(-1), +1)
+    me.assertEqual(C.MP.fibonacci( 0),  0)
+    me.assertEqual(C.MP.fibonacci(+1), +1)
+    me.assertEqual(C.MP.fibonacci(+2), +1)
+    me.assertEqual(C.MP.fibonacci(50), 12586269025)
+
+###--------------------------------------------------------------------------
+class TestMPMul (U.TestCase):
+
+  def test(me):
+    m = C.MPMul()
+    me.assertTrue(m.livep)
+    m.factor(1, 2, 3)
+    m.factor([4, 5, 6])
+    me.assertEqual(m.done(), 720)
+    me.assertFalse(m.livep)
+
+    me.assertEqual(C.MPMul(T.range(1, 7)).done(), 720)
+    me.assertEqual(C.MP.factorial(6), 720)
+
+###--------------------------------------------------------------------------
+class TestMPMont (U.TestCase):
+
+  def test(me):
+
+    me.assertRaises(ValueError,
+                    C.MPMont, 35315021952044908656941308411353985942)
+    me.assertRaises(ValueError, C.MPMont, -9)
+
+    p = C.MP(269464705320809171350781605680038324101)
+    g = C.MP(2) # lucky chance
+    x = C.MP(211184293914316080585277908844600399612)
+    y = C.MP(154454671298730680774195646814344206562)
+    xy = C.MP(209444562478584646216087606217820187655)
+    me.assertTrue(p.primep())
+    m = C.MPMont(p)
+    me.assertEqual(m.m, p)
+
+    ## The precise values of m.r and m.r2 are dependent on the internal
+    ## bignum representation.  But we expect m.r to be congruent to some
+    ## power of two.  (It should be 2^128.)
+    t = p.modinv(m.r)
+    for i in T.range(1025):
+      if t == 1: break
+      t *= 2
+      if t >= p: t -= p
+    else:
+      me.fail("m.r is not a small-ish power of 2")
+    me.assertEqual(m.r2, pow(2, 2*i, p))
+    me.assertEqual(m.ext(m.r), 1)
+    me.assertEqual(m.reduce(m.r), 1)
+
+    me.assertEqual(m.ext(m.int(x)), x)
+    me.assertEqual(m.int(x), m.mul(x, m.r2))
+    me.assertEqual(m.mul(m.int(x), y), xy)
+    me.assertEqual(m.ext(m.mul(m.int(x), m.int(y))), xy)
+
+    me.assertEqual(m.exp(2, p - 1), 1)
+    me.assertEqual(m.expr(m.int(2), p - 1), m.r)
+
+    q, r, s, z = 32, 128, 2048, pow(g, 156, p)
+    me.assertEqual(m.mexp(set([(q, 9), (r, 8), (s, 5)])), z)
+    me.assertEqual(m.mexp([(q, 9), (r, 8), (s, 5)]), z)
+    me.assertEqual(m.mexp(q, 9, r, 8, s, 5), z)
+
+    q, r, s, z = T.imap(m.int, [32, 128, 2048, pow(g, 156, p)])
+    me.assertEqual(m.mexpr(set([(q, 9), (r, 8), (s, 5)])), z)
+    me.assertEqual(m.mexpr([(q, 9), (r, 8), (s, 5)]), z)
+    me.assertEqual(m.mexpr(q, 9, r, 8, s, 5), z)
+
+###--------------------------------------------------------------------------
+class TestMPBarrett (U.TestCase):
+
+  def test(me):
+
+    p = C.MP(269464705320809171350781605680038324101)
+    g = C.MP(2) # lucky chance
+    x = C.MP(211184293914316080585277908844600399612)
+    y = C.MP(154454671298730680774195646814344206562)
+    xy = C.MP(209444562478584646216087606217820187655)
+    me.assertTrue(p.primep())
+    m = C.MPBarrett(p)
+    me.assertEqual(m.m, p)
+
+    me.assertEqual(m.reduce(x*y), xy)
+
+    me.assertEqual(m.exp(2, p - 1), 1)
+
+    q, r, s, z = 32, 128, 2048, pow(g, 156, p)
+    me.assertEqual(m.mexp(set([(q, 9), (r, 8), (s, 5)])), z)
+    me.assertEqual(m.mexp([(q, 9), (r, 8), (s, 5)]), z)
+    me.assertEqual(m.mexp(q, 9, r, 8, s, 5), z)
+
+###--------------------------------------------------------------------------
+class TestMPReduce (U.TestCase):
+
+  def test(me):
+
+    p = C.MP(2)**127 - 1
+    g = C.MP(2) # lucky chance
+    x = C.MP(94827182170881245766374991987593163418)
+    y = C.MP(106025009945795266831396608563402138277)
+    xy = C.MP(80027041045616838298103413933629021123)
+    me.assertTrue(p.primep())
+    m = C.MPReduce(p)
+    me.assertEqual(m.m, p)
+
+    me.assertEqual(m.reduce(x*y), xy)
+
+    me.assertEqual(m.exp(2, 127), 1)
+
+###--------------------------------------------------------------------------
+class TestMPCRT (U.TestCase):
+
+  def test(me):
+
+    c = C.MPCRT(5, 7, 11)
+    me.assertEqual(c.moduli, [5, 7, 11])
+    me.assertEqual(c.product, 385)
+    me.assertEqual(c.solve([2, 3, 4]), 367)
+    me.assertEqual(c.solve([2, -4, -7]), 367)
+
+    me.assertRaises(ValueError, C.MPCRT, [6, 15, 35])
+
+###--------------------------------------------------------------------------
+class TestGF (U.TestCase):
+
+  def test_make(me):
+    x = C.GF(5)
+    k = C.PrimeField(17)
+    kk = C.BinPolyField(C.GF(0x13))
+    E = k.ec(-3, 1)
+    me.assertTrue(C.GF(x) is x)
+    me.assertEqual(C.GF(k(8)), C.GF(8))
+    me.assertEqual(C.GF(kk(8)), C.GF(8))
+    me.assertEqual(C.GF(E(1, 4)), C.GF(1))
+    me.assertRaises(TypeError, C.GF, E())
+
+    me.assertNotEqual(x, 5) # no implicit conversion to int
+    me.assertEqual(int(x), 5)
+    y = C.GF(0x4eeb684a0954ec4ceb255e3e9778d41)
+    me.assertEqual(type(int(y)), T.long)
+
+    me.assertEqual(C.GF('0x4eeb684a0954ec4ceb255e3e9778d41'), y)
+    me.assertEqual(C.GF('4eeb684a0954ec4ceb255e3e9778d41', 16), y)
+    me.assertEqual(C.GF('0x4eeb684a0954ec4ceb255e3e9778d41', 16), y)
+    me.assertEqual(C.GF('0b0', 16), C.GF(176)) # not 0
+
+    me.assertEqual(C.GF('047353320450112516611472622536175135706501'), y)
+    me.assertEqual(C.GF('0o47353320450112516611472622536175135706501'), y)
+    me.assertEqual(C.GF('047353320450112516611472622536175135706501', 8), y)
+    me.assertEqual(C.GF('0o47353320450112516611472622536175135706501', 8), y)
+    me.assertEqual(C.GF('47353320450112516611472622536175135706501', 8), y)
+
+    t = C.GF(661438603)
+    me.assertEqual(C.GF('0b100111011011001100000010001011'), t)
+    me.assertEqual(C.GF('0b100111011011001100000010001011', 2), t)
+    me.assertEqual(C.GF('100111011011001100000010001011', 2), t)
+
+  def test_string(me):
+    y = C.GF(0x4eeb684a0954ec4ceb255e3e9778d41)
+    me.assertEqual(str(y), '0x4eeb684a0954ec4ceb255e3e9778d41')
+    me.assertEqual(repr(y), 'GF(0x4eeb684a0954ec4ceb255e3e9778d41)')
+    me.assertEqual(hex(y), '0x4eeb684a0954ec4ceb255e3e9778d41')
+    me.assertEqual(oct(y), T.py23('0', '0o') +
+                   '47353320450112516611472622536175135706501')
+    try: bin
+    except NameError: pass
+    else: me.assertEqual(bin(C.GF(661438603)),
+                         '0b100111011011001100000010001011')
+
+  def test_number(me):
+    x, y, m, zero = C.GF(0xa9), C.GF(0x18), C.GF(0x11b), C.GF(0)
+
+    me.assertEqual(x, -x)
+    me.assertEqual(abs(x), x)
+
+    me.assertEqual(x + y, C.GF(0xb1))
+    me.assertEqual(x - y, C.GF(0xb1))
+    me.assertEqual(x*y, C.GF(0xfd8))
+    me.assertEqual(x&y, C.GF(0x8))
+    me.assertEqual(x | y, C.GF(0xb9))
+    me.assertEqual(x ^ y, C.GF(0xb1))
+
+    me.assertEqual(x << 3, C.GF(0x548))
+    me.assertEqual(x << -2, C.GF(0x2a))
+    me.assertEqual(x >> 2, C.GF(0x2a))
+    me.assertEqual(x >> -3, C.GF(0x548))
+
+    u = x/y; me.assertEqual((u.numer, u.denom), (C.GF(0x67), C.GF(0x8)))
+    me.assertEqual(x//y, C.GF(0xc))
+    me.assertEqual(x%y, C.GF(0x9))
+    me.assertEqual(divmod(x, y), (C.GF(0xc), C.GF(0x9)))
+    me.assertRaises(ZeroDivisionError, lambda: x/zero)
+    me.assertRaises(ZeroDivisionError, lambda: x//zero)
+    me.assertRaises(ZeroDivisionError, lambda: x%zero)
+    me.assertRaises(ZeroDivisionError, divmod, x, zero)
+
+    me.assertEqual(pow(x, 24),
+                   C.GF(0x1000100000001010000010101000101010001000001))
+    me.assertEqual(pow(x, 24, m), C.GF(0x78))
+    me.assertEqual(pow(x, -24, m), C.GF(0xb6))
+    me.assertRaises(ZeroDivisionError, pow, x, -24, C.GF(0x18))
+
+    me.assertTrue(x)
+    me.assertFalse(zero)
+
+  def test_order(me):
+    x, y, z = C.GF(0xa9), C.GF(0x18), C.GF(0xb3)
+    me.assertTrue(x == x)
+    me.assertFalse(x != x)
+    me.assertFalse(x == y)
+    me.assertTrue(x != y)
+    me.assertTrue(x > y)
+    me.assertFalse(y > x)
+    me.assertFalse(x > x)
+    me.assertFalse(x > z)
+    me.assertFalse(z > x)
+    me.assertTrue(x >= y)
+    me.assertFalse(y >= x)
+    me.assertTrue(x >= x)
+    me.assertTrue(x >= z)
+    me.assertTrue(z >= x)
+    me.assertFalse(x <= y)
+    me.assertTrue(y <= x)
+    me.assertTrue(x <= x)
+    me.assertTrue(x <= z)
+    me.assertTrue(z <= x)
+    me.assertFalse(x < y)
+    me.assertTrue(y < x)
+    me.assertFalse(x < x)
+    me.assertFalse(x < z)
+    me.assertFalse(z < x)
+
+  def test_bits(me):
+    x, zero = C.GF(0xa9), C.GF(0)
+    me.assertTrue(x.testbit(0))
+    me.assertFalse(x.testbit(1))
+    me.assertFalse(x.testbit(1000))
+
+    me.assertEqual(x.setbit(0), x)
+    me.assertEqual(x.clearbit(0), C.GF(0xa8))
+    me.assertEqual(x.setbit(1), C.GF(0xab))
+    me.assertEqual(x.clearbit(1), x)
+
+    me.assertEqual(x.nbits, 8)
+    me.assertEqual(x.degree, 7)
+    me.assertEqual(zero.nbits, 0)
+    me.assertEqual(zero.degree, -1)
+
+  def test_loadstore(me):
+    x = C.GF(0x0123456789ab)
+
+    me.assertEqual(x.noctets, 6)
+
+    me.assertEqual(x, C.GF.loadb(C.bytes("0123456789ab")))
+
+    me.assertEqual(x.storeb(), C.bytes("0123456789ab"))
+    me.assertEqual(x.storeb(3), C.bytes("6789ab"))
+    me.assertEqual(x.storeb(8), C.bytes("00000123456789ab"))
+
+    me.assertEqual(x, C.GF.loadl(C.bytes("ab8967452301")))
+
+    me.assertEqual(x.storel(), C.bytes("ab8967452301"))
+    me.assertEqual(x.storel(3), C.bytes("ab8967"))
+    me.assertEqual(x.storel(8), C.bytes("ab89674523010000"))
+
+    me.assertEqual(x.tobuf(), C.bytes("00060123456789ab"))
+    me.assertEqual((x, T.bin("abcd")),
+                   C.GF.frombuf(C.bytes("00060123456789ab61626364")))
+
+  def test_numbertheory(me):
+    p, x, y = C.GF(0x11b), C.GF(0xa9), C.GF(0x18)
+
+    me.assertEqual(x.sqr(), C.GF(0x4441))
+
+    me.assertEqual(x.gcd(y), C.GF(0x3))
+    me.assertEqual(x.gcdx(y), (C.GF(0x3), C.GF(0x3), C.GF(0x15)))
+    me.assertEqual(p.modinv(x), C.GF(0xc8))
+
+    me.assertTrue(p.irreduciblep())
+    me.assertFalse(x.irreduciblep())
+
+###--------------------------------------------------------------------------
+class TestGFReduce (U.TestCase):
+
+  def test(me):
+    p = C.GF(0x87).setbit(128)
+    me.assertTrue(p.irreduciblep())
+    m = C.GFReduce(p)
+
+    x = C.GF(0xce46b4c1d3a1523520b1bb6eb5c61883)
+    y = C.GF(0xb5b0b3566b8e03f4b4a2b1ac413f8566)
+    xy = C.GF(0x28e5b895c11b08edc2fe7e1be5694c64)
+
+    me.assertEqual(m.reduce(x*y), xy)
+    me.assertEqual(m.trace(x), 0)
+    me.assertEqual(m.trace(y), 1)
+    me.assertEqual(m.sqrt(x), C.GF(0xa277ee4bf770e5974cf1e31b1ccb54a1))
+    me.assertEqual(m.halftrace(y), C.GF(0x9cea73e79ffd190dd3c81d33e58d8e6f))
+    me.assertEqual(m.quadsolve(x), C.GF(0x9664c09d23d168147a438de6a813c784))
+
+###--------------------------------------------------------------------------
+class TestGFN (U.TestCase):
+
+  def test(me):
+    p = C.GF(0x87).setbit(128)
+    beta = C.GF(0xc50f387e37194d4a4b41e157a3e2b5e1)
+    y = C.GF(0xdaca76dc2578a63c788a2ce0fc7878f6)
+    yy = C.GF(0x298a98f955100f054fcee3433f96b00e)
+    zero, one, fff = C.GF(0), C.GF(1), C.GF(T.long(2)**128 - 1)
+    me.assertTrue(p.irreduciblep())
+
+    gfn = C.GFN(p, beta)
+    me.assertEqual(gfn.p, p)
+    me.assertEqual(gfn.beta, beta)
+    me.assertEqual(gfn.pton(zero), zero)
+    me.assertEqual(gfn.ntop(zero), zero)
+    me.assertEqual(gfn.pton(one), fff)
+    me.assertEqual(gfn.ntop(fff), one)
+    me.assertEqual(gfn.pton(y), yy)
+    me.assertEqual(gfn.ntop(yy), y)
+
+    ## Doesn't generate a normal basis.
+    me.assertRaises(ValueError, C.GFN, p, y)
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-passphrase.py b/t/t-passphrase.py
new file mode 100644 (file)
index 0000000..d9a985a
--- /dev/null
@@ -0,0 +1,90 @@
+### -*-python-*-
+###
+### Testing (some) passsword management functionality
+###
+### (c) 2019 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.
+
+from __future__ import with_statement
+
+###--------------------------------------------------------------------------
+### Imported modules.
+
+import catacomb as C
+import errno as E
+import unittest as U
+import os as OS
+import subprocess as SUB
+import sys as SYS
+import testutils as T
+import time as TM
+
+###--------------------------------------------------------------------------
+### Running the pixie.
+
+class PixieTestCase (U.TestCase):
+  def setUp(me):
+    pix = "pixie-py%d.%d%s" % (SYS.version_info[0],
+                               SYS.version_info[1],
+                               T.DEBUGP and "dbg" or "")
+    me.token = C.rand.block(8).hex()
+    try: OS.mkdir(OS.path.join("build", pix), int("700", 8))
+    except OSError:
+      if SYS.exc_info()[1].errno == E.EEXIST: pass
+      else: raise
+    me.sock = OS.path.join("build", pix, "sock")
+    OS.environ["CATACOMB_PIXIE_SOCKET"] = me.sock
+    with open(OS.path.join("build", pix, "log"), "a") as logf:
+      SUB.check_call(["pixie", "-dr", "-s" + me.sock,
+                      "-c", "echo 'pp.%%t.%s'" % me.token], stderr = logf)
+  def tearDown(me):
+    SUB.check_call(["pixie", "-s" + me.sock, "-C", "quit"])
+
+###--------------------------------------------------------------------------
+class TestPixie (PixieTestCase):
+
+  def test(me):
+    px = C.Pixie(socket = me.sock)
+
+    pp = T.bin("super secret")
+    pp1 = T.bin("pp.test1.%s" % me.token)
+    pp2 = T.bin("pp.test2.%s" % me.token)
+    px.set("test1", pp)
+    me.assertEqual(px.read("test1"), pp)
+    me.assertEqual(px.read("test1", C.PMODE_READ), pp)
+    me.assertEqual(px.read("test1", C.PMODE_VERIFY), pp1)
+    me.assertEqual(px.read("test1", C.PMODE_READ), pp1)
+    px.set("test1", pp)
+    me.assertEqual(px.read("test1", C.PMODE_READ), pp)
+    me.assertEqual(px.read("test2"), pp2)
+    px.cancel("test1")
+    me.assertEqual(px.read("test1"), pp1)
+
+    px.set("test1", pp)
+    me.assertEqual(C.ppread("test1"), pp)
+    C.ppcancel("test1")
+    me.assertEqual(px.read("test1"), pp1)
+    C.ppcancel("test1")
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-pgen.py b/t/t-pgen.py
new file mode 100644 (file)
index 0000000..e200add
--- /dev/null
@@ -0,0 +1,244 @@
+### -*-python-*-
+###
+### Testing prime-generation functionality
+###
+### (c) 2019 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.
+
+import catacomb as C
+import itertools as I
+import unittest as U
+import testutils as T
+
+###--------------------------------------------------------------------------
+class TestPrimeFilter (U.TestCase):
+
+  def test(me):
+    n0 = 55614384877957400296344428606761687241
+    pf = C.PrimeFilter(n0)
+    me.assertEqual(pf.status, C.PGEN_FAIL)
+    me.assertFalse(pf)
+    me.assertEqual(pf.x, n0)
+    me.assertEqual(int(pf), n0)
+    while not pf: pf.step(2)
+    me.assertEqual(pf.status, C.PGEN_TRY)
+    me.assertEqual(pf.x, n0 + 6)
+
+    n1 = 111228769755914800592688857213523374495
+    pf1 = pf.muladd(2, 1)
+    me.assertEqual(pf1.x, n1)
+    me.assertEqual(pf1.status, C.PGEN_FAIL)
+
+    step = 2275063338
+    pf0 = C.PrimeFilter(step)
+    me.assertEqual(pf0.status, C.PGEN_FAIL)
+    while not pf1: pf1.jump(pf0)
+    me.assertEqual(pf1.x, n1 + 6*step)
+
+###--------------------------------------------------------------------------
+class TestRabinMiller (U.TestCase):
+
+  def test(me):
+    ## See `Prime and Prejudice' by Martin R. Albrecht, Jake Massimo,
+    ## Kenneth G. Paterson, and Juraj Somorovsky.
+    p1 = C.MP(142445387161415482404826365418175962266689133006163)
+    p2 = C.MP(5840260873618034778597880982145214452934254453252643)
+    p3 = C.MP(14386984103302963722887462907235772188935602433622363)
+    n = p1*p2*p3
+
+    rm = C.RabinMiller(n)
+    me.assertEqual(rm.test(2), C.PGEN_PASS)
+    me.assertEqual(rm.test(41), C.PGEN_FAIL)
+    me.assertEqual(rm.niters, 6)
+
+  def test_iters(me):
+    me.assertEqual(C.RabinMiller.iters(50), 27)
+    me.assertEqual(C.RabinMiller.iters(100), 27)
+    me.assertEqual(C.RabinMiller.iters(500), 6)
+    me.assertEqual(C.RabinMiller.iters(1000), 3)
+    me.assertEqual(C.RabinMiller.iters(2000), 2)
+
+###--------------------------------------------------------------------------
+
+class FailingHandler (object):
+  def pg_begin(me, ev): return C.PGEN_TRY
+  def pg_try(me, ev): raise T.Explosion
+  def pg_done(me, ev): raise ValueError("pg_done")
+
+class TestPGen (U.TestCase):
+
+  def test_pgen(me):
+    ev = T.EventRecorder()
+    p0 = T.detrand("pgen").mp(256, 1)
+    p = C.pgen(p0, name = "p", event = ev)
+    me.assertEqual(p, p0 + 54)
+    me.assertTrue(p.primep())
+    me.assertEqual(ev.events, "[p:F4/P11/D]")
+
+  def test_pgen_exc(me):
+    rng = T.detrand("pgen_exc")
+    exc = [None]
+    def hook(why, ty, val, tb): exc[0] = val
+    C.lostexchook = hook
+    me.assertRaises(T.Explosion, C.pgen, rng.mp(256, 1), name = "p",
+                    event = T.EventRecorder(explode_after = 19))
+    me.assertEqual(exc[0], None)
+    me.assertRaises(T.Explosion, C.pgen, rng.mp(256, 1), name = "p",
+                    event = FailingHandler(), stepper = FailingHandler())
+    me.assertEqual(exc[0].args[0], "pg_done")
+    exc = [None]
+    me.assertRaises(T.Explosion, C.limlee, 512, 96, name = "p", rng = rng,
+                    event = T.EventRecorder(explode_after = 8))
+    ev = T.EventRecorder(explode_after = 19)
+    me.assertRaises(T.Explosion, C.limlee, 512, 96, name = "p", rng = rng,
+                    ievent = ev)
+    me.assertRaises(ValueError, ev.rng.byte)
+    me.assertEqual(exc[0], None)
+    C.lostexchook = C.default_lostexchook
+
+  def test_strongprime_setup(me):
+    ev = T.EventRecorder()
+    p0, delta = C.strongprime_setup(256, name = "p", event = ev,
+                                    rng = T.detrand("strongprime_setup"))
+    p = C.pgen(p0, name = "p", event = ev,
+               stepper = C.PrimeGenJumper(delta))
+    me.assertTrue(p.primep())
+    me.assertEqual((p - p0)%delta, 0)
+    me.assertEqual(p.nbits, 256)
+    me.assertEqual(ev.events,
+                   "[p [s]:F3/P26/D]"
+                   "[p [t]:F6/P26/D]"
+                   "[p [r]:F5/P26/D]"
+                   "[p:F7/P11/D]")
+
+  def test_strongprime(me):
+    ev = T.EventRecorder()
+    p = C.strongprime(256, name = "p", event = ev,
+                      rng = T.detrand("strongprime"))
+    me.assertTrue(p.primep())
+    me.assertEqual(p.nbits, 256)
+    me.assertEqual(ev.events,
+                   "[p [s]:F5/P26/D]"
+                   "[p [t]:F13/P26/D]"
+                   "[p [r]:F7/P26/D]"
+                   "[p:F13/P11/D]")
+
+  def test_limlee(me):
+    ev = T.EventRecorder()
+    p, qq = C.limlee(512, 96, name = "p", event = ev, ievent = ev,
+                     rng = T.detrand("limlee"))
+    me.assertTrue(p.primep())
+    me.assertEqual(p.nbits, 512)
+    qbig = qq.pop()
+    me.assertTrue(qbig.primep())
+    me.assertTrue(qbig.nbits >= 96)
+    for q in qq:
+      me.assertTrue(q.primep())
+      me.assertEqual(q.nbits, 96)
+    me.assertEqual(p, C.MPMul(qq).factor(qbig).factor(2).done() + 1)
+    me.assertEqual(ev.events,
+                   "[p:"
+                   "[p_0:P26/D]"
+                   "[p_1:P26/D]"
+                   "[p_2:P26/D]"
+                   "[p_3:F5/P26/D]"
+                   "[p*_4:P26/D]"
+                   "[p_5:F6/P26/D]"
+                   "[p_6:F3/P26/D]"
+                   "[p*_7:P26/D]"
+                   "[p_8:F5/P26/D]"
+                   "[p*_9:F3/P26/D]"
+                   "[p_10:F3/P26/D]"
+                   "[p_11:F1/P26/D]"
+                   "[p_12:F2/P26/D]"
+                   "[p_13:F4/P26/D]"
+                   "[p_14:F15/P26/D]"
+                   "[p_15:P26/D]"
+                   "[p_16:F3/P26/D]"
+                   "[p_17:P26/D]"
+                   "[p_18:F1/P26/D]"
+                   "[p_19:F8/P26/D]"
+                   "[p_20:F12/P26/D]"
+                   "[p_21:F2/P26/D]"
+                   "[p_22:F2/P26/D]"
+                   "[p_23:F2/P26/D]"
+                   "[p_24:F1/P26/D]"
+                   "[p_25:F9/P26/D]"
+                   "[p_26:P26/D]"
+                   "[p_27:F2/P26/D]"
+                   "[p*_28:F8/P26/D]"
+                   "[p*_29:F14/P26/D]"
+                   "[p*_30:F4/P26/D]"
+                   "[p*_31:F6/P26/D]"
+                   "[p*_32:P26/D]"
+                   "[p*_33:F1/P26/D]"
+                   "[p*_34:F6/P26/D]"
+                   "[p*_35:F5/P26/D]"
+                   "[p*_36:F6/P26/D]"
+                   "[p*_37:P26/D]"
+                   "[p*_38:F3/P26/D]"
+                   "[p*_39:P26/D]"
+                   "[p*_40:P26/D]"
+                   "F41/P5/D]")
+
+  def test_sgprime(me):
+    ev = T.EventRecorder()
+    q0 = T.detrand("sgprime").mp(255, 1)
+    q = C.sgprime(q0, event = ev)
+    me.assertTrue(q.primep())
+    p = 2*q + 1
+    me.assertEqual(p.nbits, 256)
+    me.assertTrue(p.primep())
+
+  def test_kcdsa(me):
+    ev = T.EventRecorder()
+    p, q, h = C.kcdsaprime(512, 96, event = ev, rng = T.detrand("kcdsa"))
+    me.assertTrue(q.primep())
+    me.assertEqual(q.nbits, 96)
+    me.assertTrue(h.primep())
+    me.assertEqual(p, 2*q*h + 1)
+    me.assertTrue(p.primep())
+    me.assertEqual(p.nbits, 512)
+    me.assertEqual(ev.events, "[p [h]:F53/P6/D][p:F32/P26/D]")
+
+###--------------------------------------------------------------------------
+class TestPrimeIter (U.TestCase):
+
+  def test(me):
+    me.assertEqual(list(I.islice(C.PrimeIter(0), 5)), [2, 3, 5, 7, 11])
+    me.assertEqual(list(I.islice(C.PrimeIter(1000), 5)),
+                   [1009, 1013, 1019, 1021, 1031])
+    me.assertEqual(list(I.islice(C.PrimeIter(1000000), 5)),
+                   [1000003, 1000033, 1000037, 1000039, 1000081])
+    me.assertEqual(list(I.islice(C.PrimeIter(1000000000000000000000000000000000000), 5)),
+                   [1000000000000000000000000000000000067,
+                    1000000000000000000000000000000000123,
+                    1000000000000000000000000000000000141,
+                    1000000000000000000000000000000000159,
+                    1000000000000000000000000000000000163])
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-pubkey.py b/t/t-pubkey.py
new file mode 100644 (file)
index 0000000..5b930b0
--- /dev/null
@@ -0,0 +1,192 @@
+### -*-python-*-
+###
+### Testing public-key crypto functionality
+###
+### (c) 2019 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.
+
+import catacomb as C
+import unittest as U
+import testutils as T
+
+###--------------------------------------------------------------------------
+class TestDSA (U.TestCase):
+
+  def check(me, privcls, pubcls, rng, H, G):
+    msg = T.bin("A simple test message")
+    wrong = T.bin("This is not the message you're looking for")
+    a = privcls(G, rng.range(G.r), hash = H, rng = rng)
+    me.assertEqual(a.hash, H)
+    me.assertEqual(a.rng, rng)
+    A = pubcls(G, a.p, hash = H, rng = rng)
+    h = a.endhash(a.beginhash().hash(msg))
+    me.assertEqual(h, A.endhash(a.beginhash().hash(msg)))
+    sig = a.sign(h)
+    me.assertTrue(A.verify(h, sig))
+    me.assertFalse(A.verify(A.endhash(A.beginhash().hash(wrong)), sig))
+
+    me.assertRaises(ValueError, a.sign, h + C.bytes("00"))
+    me.assertRaises(ValueError, a.verify, h + C.bytes("00"), sig)
+
+  def test_dsa(me):
+    me.check(C.DSAPriv, C.DSAPub, T.detrand("dsa"), C.sha256,
+             C.primegroups["catacomb-ll-256-3072"].group())
+  def test_ecdsa(me):
+    me.check(C.DSAPriv, C.DSAPub, T.detrand("ecdsa"), C.sha256,
+             C.eccurves["nist-p256"].group())
+  def test_kcdsa(me):
+    me.check(C.KCDSAPriv, C.KCDSAPub, T.detrand("kcdsa"), C.has160,
+             C.primegroups["catacomb-ll-160-1024"].group())
+  def test_eckcdsa(me):
+    me.check(C.KCDSAPriv, C.KCDSAPub, T.detrand("eckcdsa"), C.sha256,
+             C.eccurves["nist-p256"].group())
+
+###--------------------------------------------------------------------------
+class TestRSA (U.TestCase):
+
+  def test(me):
+    rng = T.detrand("rsa")
+    ev = T.EventRecorder()
+    msg = T.bin("A simple test message")
+    h = C.sha256().hash(msg).done()
+    wrong = T.bin("This is not the message you're looking for")
+    hh = C.sha256().hash(wrong).done()
+
+    a = C.RSAPriv.generate(1536, event = ev, rng = rng)
+    A = C.RSAPub(n = a.n, e = a.e)
+    me.assertEqual(a.n.nbits, 1536)
+    me.assertEqual(a.rng, None)
+    a.rng = rng
+    me.assertEqual(a.rng, rng)
+    me.assertEqual(ev.events,
+                   "[p [s]:F6/P7/D]"
+                   "[p [t]:F68/P7/D]"
+                   "[p [r]:F20/P7/D]"
+                   "[p:F35/P3/D]"
+                   "[q [s]:F47/P7/D]"
+                   "[q [t]:F4/P7/D]"
+                   "[q [r]:F8/P7/D]"
+                   "[q:F22/P3/D]")
+
+    C.RSAPriv(n = a.n, e = a.e, d = a.d)
+    C.RSAPriv(n = a.n, p = a.p, d = a.d)
+    C.RSAPriv(n = a.n, q = a.q, e = a.e)
+    C.RSAPriv(p = a.p, q = a.q, e = a.e)
+    me.assertRaises(ValueError, C.RSAPriv, p = a.p, q = a.q, dp = a.dp)
+
+    me.assertTrue(a.p.primep())
+    me.assertTrue(a.q.primep())
+    me.assertEqual(a.n, a.p*a.q)
+    me.assertEqual(a.dp, a.d%(a.p - 1))
+    me.assertEqual(a.dq, a.d%(a.q - 1))
+    me.assertEqual((a.e*a.dp)%(a.p - 1), 1)
+    me.assertEqual((a.e*a.dq)%(a.q - 1), 1)
+    me.assertEqual((a.q*a.q_inv)%a.p, 1)
+
+    x = rng.range(a.n)
+    y = a.privop(x)
+    me.assertEqual(x, a.pubop(y))
+    me.assertEqual(x, A.pubop(y))
+    z = a.pubop(x)
+    me.assertEqual(z, A.pubop(x))
+    me.assertEqual(x, a.privop(z))
+
+    pad = C.PKCS1Crypt(rng = rng)
+    ct = A.encrypt(msg, pad)
+    me.assertEqual(a.decrypt(ct, pad), msg)
+    me.assertRaises(ValueError, a.decrypt, ct ^ 1, pad)
+
+    pad = C.PKCS1Sig(ep = C.bytes("3031300d060960864801650304020105000420"))
+    sig = a.sign(h, pad)
+    me.assertTrue(A.verify(h, sig, pad))
+    me.assertFalse(A.verify(hh, sig, pad))
+
+    pad = C.OAEP(mgf = C.sha256_mgf, hash = C.sha256, rng = rng)
+    ct = A.encrypt(msg, pad)
+    me.assertEqual(a.decrypt(ct, pad), msg)
+    me.assertRaises(ValueError, a.decrypt, ct ^ 1, pad)
+
+    pad = C.PSS(mgf = C.sha256_mgf, hash = C.sha256, rng = rng)
+    sig = a.sign(h, pad)
+    me.assertTrue(A.verify(h, sig, pad))
+    me.assertFalse(A.verify(hh, sig, pad))
+
+###--------------------------------------------------------------------------
+class TestXDH (U.TestCase):
+
+  def check(me, privcls, pubcls, rng):
+    msg = T.bin("A simple test message")
+    n = rng.block(24)
+    n1 = rng.block(24)
+
+    a = privcls.generate(rng)
+    A = pubcls(a.pub)
+    b = privcls.generate(rng)
+    B = pubcls(b.pub)
+    Z = a.agree(B)
+    me.assertEqual(b.agree(A), Z)
+    me.assertEqual(b.agree(a), Z)
+
+    ct = a.box(B, n, msg)
+    me.assertEqual(b.unbox(A, n, ct), msg)
+    me.assertRaises(ValueError, b.unbox, A, n1, ct)
+
+  def test_x25519(me):
+    me.check(C.X25519Priv, C.X25519Pub, T.detrand("x25519"))
+  def test_x448(me):
+    me.check(C.X448Priv, C.X448Pub, T.detrand("x448"))
+
+###--------------------------------------------------------------------------
+class TestEdDSA (U.TestCase):
+
+  def check(me, privcls, pubcls, rng):
+    msg = T.bin("A simple test message")
+    wrong = T.bin("This is not the message you're looking for")
+    perso = T.bin("Catacomb/Python test")
+    a = privcls.generate(rng)
+    A = pubcls(a.pub)
+
+    sig = a.sign(msg)
+    me.assertTrue(a.verify(msg, sig))
+    me.assertTrue(A.verify(msg, sig))
+    me.assertFalse(A.verify(wrong, sig))
+
+    sig = a.sign(msg, perso = perso)
+    me.assertTrue(a.verify(msg, sig, perso = perso))
+    me.assertFalse(A.verify(msg, sig))
+
+    h = a.endhash(a.beginhash().hash(msg))
+    sig = a.sign(h, phflag = True)
+    me.assertTrue(a.verify(h, sig, phflag = True))
+    me.assertFalse(A.verify(h, sig))
+
+  def test_ed25519(me):
+    me.check(C.Ed25519Priv, C.Ed25519Pub, T.detrand("ed25519"))
+  def test_ed448(me):
+    me.check(C.Ed448Priv, C.Ed448Pub, T.detrand("ed448"))
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-rand.py b/t/t-rand.py
new file mode 100644 (file)
index 0000000..9acf693
--- /dev/null
@@ -0,0 +1,172 @@
+### -*-python-*-
+###
+### Testing random-generator functionality
+###
+### (c) 2019 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.
+
+import catacomb as C
+import unittest as U
+import testutils as T
+
+###--------------------------------------------------------------------------
+class TestRandomGenerator (U.TestCase):
+
+  def check_rand(me, rng):
+
+    for i in T.range(50):
+      x = rng.byte()
+      me.assertEqual(type(x), int)
+      me.assertTrue(0 <= x < 256)
+
+    x = rng.word()
+    me.assertTrue(0 <= x <= 0xffffffff)
+
+    for i in T.range(50):
+      x = rng.range(10)
+      me.assertEqual(type(x), int)
+      me.assertTrue(0 <= x < 10)
+    x = rng.range(T.MAXFIXNUM + 1)
+    me.assertEqual(type(x), C.MP)
+
+    for i in T.range(50):
+      x = rng.mp(123, 7)
+      me.assertEqual(type(x), C.MP)
+      me.assertEqual(x.nbits, 123)
+      me.assertEqual(x&7, 7)
+    me.assertRaises((OverflowError, ValueError), rng.mp, 128, -1)
+    me.assertRaises((OverflowError, ValueError), rng.mp, 128, C.MPW_MAX + 1)
+
+    x = rng.block(17)
+    me.assertEqual(type(x), C.ByteString)
+    me.assertEqual(len(x), 17)
+
+  def test_lcrand(me):
+    rng = C.LCRand(0)
+    me.assertFalse(rng.cryptop)
+    w0 = rng.word()
+    rng.seedint(0)
+    me.assertEqual(rng.word(), w0)
+
+  def test_firand(me):
+    rng = C.FibRand(0)
+    me.assertFalse(rng.cryptop)
+    w0 = rng.word()
+    rng.seedint(0)
+    me.assertEqual(rng.word(), w0)
+
+  def test_truerand(me):
+    rng = C.TrueRand()
+    me.assertTrue(rng.cryptop)
+    me.check_rand(rng)
+    rng.key(T.span(23))
+    rng.seed(256)
+    me.assertRaises(ValueError, rng.seed, C.RAND_IBITS + 1)
+    rng.seedint(-314)
+    rng.seedword(0x12345678)
+    rng.seedrand(T.detrand("seed-truerand"))
+    rng.seedblock(T.span(123))
+    rng.add(T.span(123), 978)
+    rng.gate()
+    rng.stretch()
+    rng.timer()
+    me.check_rand(rng)
+
+  def test_cryptorand(me):
+    for r, kw in [("rijndael-counter", {}),
+                  ("rc4", {}),
+                  ("xchacha20", { "nonce": T.span(24) }),
+                  ("seal", { "i": 12345678 }),
+                  ("shake128", { "func": T.bin("TEST"),
+                                 "perso": T.bin("Catacomb/Python test") }),
+                  ("kmac256", { "perso": T.bin("Catacomb/Python test") })]:
+      rcls = C.gccrands[r]
+      rng = rcls(T.span(rcls.keysz.default), **kw)
+      me.assertTrue(rng.cryptop)
+      if kw:
+        rng = rcls(T.span(rcls.keysz.default))
+        me.check_rand(rng)
+
+  def test_sslrand(me):
+    rng = C.SSLRand(T.span(16), T.span(32), C.md5, C.sha)
+    me.check_rand(rng)
+  def test_tlsdx(me):
+    rng = C.TLSDataExpansion(T.span(16), T.span(32), C.sha256_hmac)
+    me.check_rand(rng)
+  def test_tlsprf(me):
+    rng = C.TLSPRF(T.span(16), T.span(32), C.md5_hmac, C.sha_hmac)
+    me.check_rand(rng)
+
+  def test_dsarand(me):
+    seed = T.span(16)
+    n = C.MP.loadb(seed)
+    rng = C.DSARand(seed, passes = 2)
+    me.check_rand(rng)
+    me.assertEqual(rng.seed, (n + 2*153).storeb(16))
+    me.assertEqual(rng.passes, 2);
+    rng.passes = 1
+    me.check_rand(rng)
+    me.assertEqual(rng.seed, (n + 3*153 + 1).storeb(16))
+
+  def test_bbs(me):
+    ev = T.EventRecorder()
+    drng = T.detrand("bbs")
+    rngpriv = C.BBSPriv.generate(1536, event = ev, rng = drng)
+    me.assertEqual(rngpriv.n.nbits, 1536)
+    me.assertEqual(rngpriv.p&3, 3)
+    me.assertEqual(rngpriv.q&3, 3)
+    me.assertTrue(rngpriv.p.primep())
+    me.assertTrue(rngpriv.q.primep())
+    me.assertEqual(rngpriv.n, rngpriv.p*rngpriv.q)
+    me.assertEqual(ev.events,
+                   "[p [s]:F10/P7/D]"
+                   "[p [t]:F2/P7/D]"
+                   "[p [r]:F7/P7/D]"
+                   "[p:F6/P3/D]"
+                   "[q [s]:P7/D]"
+                   "[q [t]:F33/P7/D]"
+                   "[q [r]:F33/P7/D]"
+                   "[q:F55/P3/D]")
+
+    x0 = drng.range(rngpriv.n)
+    rngpriv.seedmp(x0)
+    rng = C.BlumBlumShub(rngpriv.n, x0)
+    me.check_rand(rngpriv)
+    me.check_rand(rng)
+    me.assertEqual(rngpriv.x, rng.x)
+
+    msg = T.span(123)
+    rng.wrap()
+    ct = rng.mask(msg)
+    rng.wrap()
+
+    rngpriv.x = rng.x
+    nsteps = (123*8 + 7)//(rng.stepsz)
+    rngpriv.rew(nsteps + 1)
+    me.assertEqual(rngpriv.mask(ct), msg)
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-rat.py b/t/t-rat.py
new file mode 100644 (file)
index 0000000..a2198bd
--- /dev/null
@@ -0,0 +1,104 @@
+### -*-python-*-
+###
+### Testing rational arithmetic functionality
+###
+### (c) 2019 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.
+
+import catacomb as C
+import unittest as U
+import testutils as T
+
+###--------------------------------------------------------------------------
+class TestRat (U.TestCase):
+
+  def check_rat(me, k):
+    R = k.RING
+
+    ## Check that exact true division in the base ring gives base-ring
+    ## elements.
+    a, b, c = R(19), R(5), R(8)
+    m = a*b
+    q = m/b
+    me.assertEqual(type(q), R)
+    me.assertEqual(q, a)
+
+    ## Check that inexact division gives a fraction.
+    f = (m + c)/b
+    me.assertNotEqual(type(f), R)
+    me.assertNotEqual(f, a)
+    r = f - q
+    me.assertEqual(b*r, c)
+
+    ## More complicated arithmetic.
+    u, v = a/b, a/c
+    me.assertEqual((u + v)*(b*c), a*(b + c))
+    me.assertEqual((u - v)*(b*c), a*(c - b))
+
+    ## Ordering.
+    me.assertTrue(b < c)
+    me.assertTrue(b/a < c/a)
+    me.assertFalse(c/a < b/a)
+    me.assertTrue(b/a <= c/a)
+    me.assertFalse(c/a <= b/a)
+    me.assertFalse(b/a >= c/a)
+    me.assertTrue(c/a >= b/a)
+    me.assertFalse(b/a > c/a)
+    me.assertTrue(c/a > b/a)
+
+    ## Conversions from string.
+    me.assertRaises(TypeError, T.add, f, "3")
+
+  def test_intrat(me):
+    me.check_rat(C.IntRat)
+
+    ## Check interaction with floating point.
+    u = C.MP(5)/C.MP(4)
+    me.assertEqual(type(u + 0.0), float)
+    me.assertEqual(u, 1.25)
+    me.assertTrue(u < 1.26)
+    me.assertTrue(u > 1.24)
+
+    ## Check string conversions.
+    me.assertEqual(str(u), "5/4")
+    me.assertEqual(repr(u), "IntRat(5, 4)")
+
+  def test_gfrat(me):
+    me.check_rat(C.GFRat)
+
+    ## Check string conversions.
+    u = C.GF(5)/C.GF(4)
+    me.assertRaises(TypeError, T.add, u, 0.0)
+    me.assertEqual(str(u), "0x5/0x4")
+    me.assertEqual(repr(u), "GFRat(0x5, 0x4)")
+
+  def test_cross(me):
+    u = C.MP(5)/C.MP(3)
+    v = C.GF(5)/C.GF(3)
+    me.assertRaises(TypeError, T.add, u, v)
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/t-share.py b/t/t-share.py
new file mode 100644 (file)
index 0000000..089d2ff
--- /dev/null
@@ -0,0 +1,68 @@
+### -*-python-*-
+###
+### Testing (some) passsword management functionality
+###
+### (c) 2019 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.
+
+import catacomb as C
+import unittest as U
+import testutils as T
+
+###--------------------------------------------------------------------------
+class TestShare (U.TestCase):
+
+  def check_share(me, splitcls, splitargs, joincls, joinargs, secret):
+
+    split = splitcls(3, secret, *splitargs)
+    me.assertEqual(split.threshold, 3)
+    shares = [split.get(i) for i in T.range(5)]
+
+    join = joincls(3, *joinargs)
+    me.assertEqual(join.threshold, 3)
+    me.assertFalse(join.addedp(1))
+    me.assertEqual(join.remain, 3)
+    join.add(1, shares[1])
+    me.assertTrue(join.addedp(1))
+    me.assertEqual(join.remain, 2)
+    me.assertRaises(ValueError, join.combine)
+    join.add(0, shares[0])
+    join.add(3, shares[3])
+    me.assertEqual(join.remain, 0)
+    me.assertEqual(join.combine(), secret)
+
+  def test_share(me):
+    rng = T.detrand("share")
+    p = C.MP(2)**127 - 1
+    me.check_share(C.ShareSplit, [p, rng], C.ShareJoin, [p], rng.range(p))
+
+  def test_gfshare(me):
+    rng = T.detrand("gfshare")
+    me.check_share(C.GFShareSplit, [rng], C.GFShareJoin, [123],
+                   rng.block(123))
+
+###----- That's all, folks --------------------------------------------------
+
+if __name__ == "__main__": U.main()
diff --git a/t/testutils.py b/t/testutils.py
new file mode 100644 (file)
index 0000000..67bcc0e
--- /dev/null
@@ -0,0 +1,360 @@
+### -*- mode: python, coding: utf-8 -*-
+###
+### Test utilities
+###
+### (c) 2019 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.
+
+import catacomb as C
+import sys as SYS
+if SYS.version_info >= (3,): import builtins as B
+else: import __builtin__ as B
+import unittest as U
+
+###--------------------------------------------------------------------------
+### Main code.
+
+## Some compatibility hacks.
+if SYS.version_info >= (3,):
+  PY2, PY3 = False, True
+  def bin(x): return x.encode('iso8859-1')
+  def py23(x, y): return y
+  range = range
+  byteseq = bytes
+  long = int
+  imap = map
+  def iterkeys(m): return m.keys()
+  def itervalues(m): return m.values()
+  def iteritems(m): return m.items()
+  from io import StringIO
+  MAXFIXNUM = SYS.maxsize
+else:
+  import itertools as I
+  PY2, PY3 = True, False
+  def bin(x): return x
+  def py23(x, y): return x
+  range = xrange
+  long = long
+  imap = I.imap
+  def byteseq(seq): return "".join(map(chr, seq))
+  def iterkeys(m): return m.iterkeys()
+  def itervalues(m): return m.itervalues()
+  def iteritems(m): return m.iteritems()
+  from cStringIO import StringIO
+  MAXFIXNUM = SYS.maxint
+
+DEBUGP = hasattr(SYS, "gettotalrefcount")
+
+FULLSPAN = byteseq(range(256))
+def span(n):
+  """A string `00 01 .. NN'."""
+  return (n >> 8)*FULLSPAN + FULLSPAN[:n&255]
+
+def bytes_as_int(w, bigendp):
+  """Convert the byte-sequence `01 02 ... WW' to an integer."""
+  x = 0
+  if bigendp:
+    for i in range(w): x = x << 8 | i + 1
+  else:
+    for i in range(w): x |= i + 1 << 8*i
+  return x
+
+def prep_lenseq(w, n, bigendp, goodp):
+  """
+  Return a reference buffer containing `00 LL .. LL 00 01 02 .. NN ff'.
+
+  Here, LL .. LL is the length of following sequence, not including the final
+  `ff', as a W-byte integer.  If GOODP is false, then the most significant
+  bit of LL .. LL is set, to provoke an overflow.
+  """
+  if goodp: l = n
+  else: l = n + (1 << 8*w - 1)
+  lenbyte = bigendp \
+    and (lambda i: (l >> 8*(w - i - 1))&0xff) \
+    or (lambda i: (l >> 8*i)&0xff)
+  return byteseq([0x00]) + \
+    byteseq([lenbyte(i) for i in range(w)]) + \
+    span(n) + \
+    byteseq([0xff])
+
+def detrand(seed):
+  """Return a fast deterministic random generator with the given SEED."""
+  return C.chacha8rand(C.sha256().hash(bin(seed)).done())
+
+class GenericTestMixin (U.TestCase):
+  """
+  A mixin class to generate test-case functions for all similar things.
+  """
+
+  @classmethod
+  def generate_testcases(cls, things):
+    testfns = dict()
+    checkfns = []
+    for k, v in iteritems(cls.__dict__):
+      if k.startswith("_test_"): checkfns.append((k[6:], v))
+    for name, thing in things:
+      for test, checkfn in checkfns:
+        testfn = lambda me, thing = thing: checkfn(me, thing)
+        doc = getattr(checkfn, "__doc__", None)
+        if doc is not None: testfn.__doc__ = doc % name
+        testfns["test_%s%%%s" % (test, name)] = testfn
+    tmpcls =  type("_tmp", (cls,), testfns)
+    for k, v in iteritems(tmpcls.__dict__):
+      if k.startswith("test_"): setattr(cls, k, v)
+
+class ImmutableMappingTextMixin (U.TestCase):
+
+  ## Subclass stubs.
+  def _mkkey(me, i): return "k#%d" % i
+  def _getkey(me, k): return int(k[2:])
+  def _getvalue(me, v): return int(v[2:])
+  def _getitem(me, it): k, v = it; return me._getkey(k), me._getvalue(v)
+
+  def check_immutable_mapping(me, map, model):
+
+    ## Lookup.
+    limk = 0
+    any = False
+    me.assertEqual(len(map), len(model))
+    for k, v in iteritems(model):
+      any = True
+      if k >= limk: limk = k + 1
+      me.assertTrue(me._mkkey(k) in map)
+      if PY2: me.assertTrue(map.has_key(me._mkkey(k)))
+      me.assertEqual(me._getvalue(map[me._mkkey(k)]), v)
+      me.assertEqual(me._getvalue(map.get(me._mkkey(k))), v)
+    if any: me.assertTrue(me._mkkey(k) in map)
+    if PY2: me.assertFalse(map.has_key(me._mkkey(limk)))
+    me.assertRaises(KeyError, lambda: map[me._mkkey(limk)])
+    me.assertEqual(map.get(me._mkkey(limk)), None)
+
+    if PY3:
+      empty = set()
+
+      for k, v in iteritems(map):
+        me.assertTrue(k in map.keys())
+        me.assertTrue((k, v) in map.items())
+      me.assertFalse(me._mkkey(limk) in map.keys())
+
+      for viewfn, getfn in [(lambda x: x.keys(), me._getkey),
+                            (lambda x: x.items(), me._getitem)]:
+        rview, rview2, mview = viewfn(map), viewfn(map), viewfn(model)
+        me.assertEqual(set(imap(getfn, rview)), set(mview))
+        me.assertEqual(rview, rview2)
+        me.assertEqual(rview, set(rview2))
+        me.assertEqual(rview | empty, set(rview))
+        me.assertEqual(rview | rview2, set(rview))
+        me.assertEqual(rview ^ empty, set(rview))
+        me.assertEqual(rview ^ rview, empty)
+        me.assertEqual(rview & empty, empty)
+        me.assertEqual(len(rview), len(model))
+
+        if any: subset = set(rview2); subset.pop()
+        superset = set(rview2); superset.add(object())
+
+        me.assertFalse(rview < rview2)
+        me.assertTrue(rview < superset)
+        me.assertFalse(superset < rview)
+        me.assertFalse(rview < empty)
+        if any:
+          me.assertTrue(empty < rview)
+          me.assertTrue(subset < rview)
+          me.assertFalse(rview < subset)
+
+        me.assertTrue(rview <= rview2)
+        me.assertTrue(rview <= superset)
+        me.assertFalse(superset <= rview)
+        if any:
+          me.assertTrue(empty <= rview)
+          me.assertFalse(rview <= empty)
+          me.assertTrue(subset <= rview)
+          me.assertFalse(rview <= subset)
+
+        me.assertTrue(rview >= rview2)
+        me.assertTrue(superset >= rview)
+        me.assertFalse(rview >= superset)
+        if any:
+          me.assertTrue(rview >= empty)
+          me.assertFalse(empty >= rview)
+          me.assertTrue(rview >= subset)
+          me.assertFalse(subset >= rview)
+
+        me.assertFalse(rview > rview2)
+        me.assertTrue(superset > rview)
+        me.assertFalse(rview > superset)
+        me.assertFalse(empty > rview)
+        if any:
+          me.assertTrue(rview > empty)
+          me.assertTrue(rview > subset)
+          me.assertFalse(subset > rview)
+
+    else:
+      for listfn, getfn in [(lambda x: x.keys(), me._getkey),
+                            (lambda x: x.values(), me._getvalue),
+                            (lambda x: x.items(), me._getitem)]:
+        rlist, mlist = listfn(map), listfn(model)
+        me.assertEqual(type(rlist), list)
+        rlist = B.map(getfn, rlist)
+        rlist.sort(); mlist.sort(); me.assertEqual(rlist, mlist)
+      for iterfn, getfn in [(lambda x: x.iterkeys(), me._getkey),
+                            (lambda x: x.itervalues(), me._getvalue),
+                            (lambda x: x.iteritems(), me._getitem)]:
+        me.assertEqual(set(imap(getfn, iterfn(map))), set(iterfn(model)))
+
+class MutableMappingTestMixin (ImmutableMappingTextMixin):
+
+  ## Subclass stubs.
+  def _mkvalue(me, i): return "v#%d" % i
+
+  def check_mapping(me, emptymapfn):
+
+    map = emptymapfn()
+    me.assertEqual(len(map), 0)
+
+    if not PY3:
+      def check_views():
+        me.check_immutable_mapping(map, model)
+    else:
+      kview, iview, vview = map.keys(), map.items(), map.values()
+      def check_views():
+        me.check_immutable_mapping(map, model)
+        me.assertEqual(set(imap(me._getkey, kview)), model.keys())
+        me.assertEqual(set(imap(me._getitem, iview)), model.items())
+        me.assertEqual(set(imap(me._getvalue, vview)), set(model.values()))
+
+    model = { 1: 101, 2: 202, 4: 404 }
+    for k, v in iteritems(model): map[me._mkkey(k)] = me._mkvalue(v)
+    check_views()
+
+    model.update({ 2: 212, 6: 606, 7: 707 })
+    map.update({ me._mkkey(2): me._mkvalue(212),
+                 me._mkkey(6): me._mkvalue(606) },
+               **{ me._mkkey(7): me._mkvalue(707) })
+    check_views()
+
+    model[9] = 909
+    map[me._mkkey(9)] = me._mkvalue(909)
+    check_views()
+
+    model[9] = 919
+    map[me._mkkey(9)] = me._mkvalue(919)
+    check_views()
+
+    map.setdefault(me._mkkey(9), me._mkvalue(929))
+    check_views()
+
+    model[8] = 808
+    map.setdefault(me._mkkey(8), me._mkvalue(808))
+    check_views()
+
+    me.assertRaises(KeyError, map.pop, me._mkkey(5))
+    obj = object()
+    me.assertEqual(map.pop(me._mkkey(5), obj), obj)
+    me.assertEqual(me._getvalue(map.pop(me._mkkey(8))), 808)
+    del model[8]
+    check_views()
+
+    del model[9]
+    del map[me._mkkey(9)]
+    check_views()
+
+    k, v = map.popitem()
+    mk, mv = me._getkey(k), me._getvalue(v)
+    me.assertEqual(model[mk], mv)
+    del model[mk]
+    check_views()
+
+    map.clear()
+    model = {}
+    check_views()
+
+class Explosion (Exception): pass
+
+class EventRecorder (C.PrimeGenEventHandler):
+  def __init__(me, parent = None, explode_after = None, *args, **kw):
+    super(EventRecorder, me).__init__(*args, **kw)
+    me._streak = 0
+    me._op = None
+    me._parent = parent
+    me._countdown = explode_after
+    me.rng = None
+    if parent is None: me._buf = StringIO()
+    else: me._buf = parent._buf
+  def _event_common(me, ev):
+    if me.rng is None: me.rng = ev.rng
+    if me._countdown is None: pass
+    elif me._countdown == 0: raise Explosion()
+    else: me._countdown -= 1
+  def _put(me, op):
+    if op == me._op:
+      me._streak += 1
+    else:
+      if me._op is not None: me._buf.write("%s%d/" % (me._op, me._streak))
+      me._op = op
+      me._streak = 1
+  def pg_begin(me, ev):
+    me._event_common(ev)
+    me._buf.write("[%s:" % ev.name)
+  def pg_try(me, ev):
+    me._event_common(ev)
+  def pg_fail(me, ev):
+    me._event_common(ev)
+    me._put("F")
+  def pg_pass(me, ev):
+    me._event_common(ev)
+    me._put("P")
+  def pg_done(me, ev):
+    me._event_common(ev)
+    me._put(None); me._buf.write("D]")
+  def pg_abort(me, ev):
+    me._event_common(ev)
+    me._put(None); me._buf.write("A]")
+  @property
+  def events(me):
+    return me._buf.getvalue()
+
+## Functions for operators.
+neg = lambda x: -x
+pos = lambda x: +x
+add = lambda x, y: x + y
+sub = lambda x, y: x - y
+mul = lambda x, y: x*y
+div = lambda x, y: x/y
+mod = lambda x, y: x%y
+floordiv = lambda x, y: x//y
+bitand = lambda x, y: x&y
+bitor = lambda x, y: x | y
+bitxor = lambda x, y: x ^ y
+bitnot = lambda x: ~x
+lsl = lambda x, y: x << y
+lsr = lambda x, y: x >> y
+eq = lambda x, y: x == y
+ne = lambda x, y: x != y
+lt = lambda x, y: x < y
+le = lambda x, y: x <= y
+ge = lambda x, y: x >= y
+gt = lambda x, y: x > y
+
+###----- That's all, folks --------------------------------------------------