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

41 files changed:
.gitignore
MANIFEST.in
algorithms.c
algorithms.py
buffer.c
bytestring.c
catacomb-python.h
catacomb.c
catacomb/__init__.py
catacomb/pwsafe.py
debian/changelog
debian/control
debian/rules
ec.c
field.c
group.c
key.c
mp.c
pgen.c
pubkey.c
rand.c
setup.py
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]
util.c

index 98bd1e8..a3d0fc5 100644 (file)
@@ -11,3 +11,4 @@ auto-version
 mdwsetup.py
 *.pyc
 pysetup.mk
+/t/keyring.old
index 36bf4c2..33ce54a 100644 (file)
@@ -8,6 +8,7 @@ include MANIFEST.in setup.py Makefile
 
 ## C extension code.
 include *.c *.h
+include t/*.py t/keyring
 include algorithms.py
 exclude algorithms.h
 
index 2a3bece..888ceb0 100644 (file)
@@ -137,9 +137,8 @@ static PyObject *keyszset_pynew(PyTypeObject *ty,
   if (!set) set = PyTuple_New(0);
   else Py_INCREF(set);
   if (!PySequence_Check(set)) TYERR("want a sequence");
-  n = PySequence_Size(set);
-  l = PyList_New(0);
-  if (PyErr_Occurred()) goto end;
+  n = PySequence_Size(set); if (n < 0) goto end;
+  l = PyList_New(0); if (!l) goto end;
   if (dfl < 0) VALERR("key size cannot be negative");
   x = PyInt_FromLong(dfl);
   PyList_Append(l, x);
@@ -208,7 +207,7 @@ static PyMemberDef keysz_pymembers[] = {
 static PyGetSetDef keyszany_pygetset[] = {
 #define GETSETNAME(op, name) ka##op##_##name
   GET  (min,                   "KSZ.min -> smallest allowed key size")
-  GET  (max,                   "KSZ.min -> largest allowed key size")
+  GET  (max,                   "KSZ.max -> largest allowed key size")
 #undef GETSETNAME
   { 0 }
 };
@@ -216,7 +215,7 @@ static PyGetSetDef keyszany_pygetset[] = {
 static PyMemberDef keyszrange_pymembers[] = {
 #define MEMBERSTRUCT keyszrange_pyobj
   MEMBER(min, T_INT, READONLY, "KSZ.min -> smallest allowed key size")
-  MEMBER(max, T_INT, READONLY, "KSZ.min -> largest allowed key size")
+  MEMBER(max, T_INT, READONLY, "KSZ.max -> largest allowed key size")
   MEMBER(mod, T_INT, READONLY,
         "KSZ.mod -> key size must be a multiple of this")
 #undef MEMBERSTRUCT
@@ -226,7 +225,7 @@ static PyMemberDef keyszrange_pymembers[] = {
 static PyGetSetDef keyszset_pygetset[] = {
 #define GETSETNAME(op, name) ks##op##_##name
   GET  (min,                   "KSZ.min -> smallest allowed key size")
-  GET  (max,                   "KSZ.min -> largest allowed key size")
+  GET  (max,                   "KSZ.max -> largest allowed key size")
 #undef GETSETNAME
   { 0 }
 };
@@ -990,6 +989,7 @@ static PyObject *gaeameth_copy(PyObject *me, PyObject *arg)
     VALERR("can't duplicate nonce-dependent aad");
   rc = gaeadaad_pywrap((PyObject *)me->ob_type,
                       GAEAD_DUP(GAEADAAD_A(me)), 0, 0);
+  GAEADAAD_HLEN(rc) = GAEADAAD_HLEN(me);
 end:
   return (rc);
 }
@@ -1133,7 +1133,8 @@ static PyObject *gaeemeth_aad(PyObject *me, PyObject *arg)
     if (!ge->aad)
       ge->aad = (gaeadaad_pyobj *)
        gaeadaad_pywrap((PyObject *)GCAEADENC_KEY(ge->ob_type)->aad,
-                       GAEAD_AAD(ge->e), ge->f&AEADF_PCHSZ, ge->hsz);
+                       GAEAD_AAD(ge->e), ge->f&(AEADF_PCHSZ | AEADF_NOAAD),
+                       ge->hsz);
     Py_INCREF(ge->aad);
     rc = (PyObject *)ge->aad;
   }
@@ -1304,7 +1305,8 @@ static PyObject *gaedmeth_aad(PyObject *me, PyObject *arg)
     if (!gd->aad)
       gd->aad = (gaeadaad_pyobj *)
        gaeadaad_pywrap((PyObject *)GCAEADENC_KEY(gd->ob_type)->aad,
-                       GAEAD_AAD(gd->d), gd->f&AEADF_PCHSZ, gd->hsz);
+                       GAEAD_AAD(gd->d), gd->f&(AEADF_PCHSZ | AEADF_NOAAD),
+                       gd->hsz);
     Py_INCREF(gd->aad);
     return ((PyObject *)gd->aad);
   }
@@ -2639,7 +2641,7 @@ static PyTypeObject poly1305hash_pytype_skel = {
     if (!PyArg_ParseTuple(arg, "s#s#:" #hdance "_prf",                 \
                          &k, &ksz, &n, &nsz))                          \
       goto end;                                                                \
-    if (ksz != DANCE##_KEYSZ) VALERR("bad key length");                        \
+    if (ksz != keysz(ksz, dance##_keysz)) VALERR("bad key length");    \
     if (nsz != HDANCE##_INSZ) VALERR("bad input length");              \
     rc = bytestring_pywrap(0, HSALSA20_OUTSZ);                         \
     dance##_init(&dance, k, ksz, 0);                                   \
@@ -2725,7 +2727,7 @@ static PyObject *kxvikmeth_extract(PyObject *me, PyObject *arg)
   unsigned i;
   unsigned n;
 
-  if (!PyArg_ParseTuple(arg, "O&:mix", convuint, &n)) goto end;
+  if (!PyArg_ParseTuple(arg, "O&:extract", convuint, &n)) goto end;
   if (n > 200) VALERR("out of range");
   rc = bytestring_pywrap(0, n);
   q = (octet *)PyString_AS_STRING(rc);
@@ -2755,10 +2757,14 @@ static int kxvikset_nround(PyObject *me, PyObject *val, void *hunoz)
 {
   kxvik_pyobj *k = (kxvik_pyobj *)me;
   unsigned n;
+  int rc = -1;
 
-  if (!convuint(val, &n)) return (-1);
+  if (!val) NIERR("__del__");
+  if (!convuint(val, &n)) goto end;
   k->n = n;
-  return (0);
+  rc = 0;
+end:
+  return (rc);
 }
 
 static PyGetSetDef kxvik_pygetset[] = {
@@ -2955,7 +2961,7 @@ static PyObject *shakemeth_copy(PyObject *me, PyObject *arg)
   rc->h = *SHAKE_H(me);
   rc->st = SHAKE_ST(me);
 end:
-  return ((PyObject *)me);
+  return ((PyObject *)rc);
 }
 
 static PyObject *shakemeth_get(PyObject *me, PyObject *arg)
index 4b65ce9..5730c45 100644 (file)
@@ -39,7 +39,7 @@ chacha20 chacha12 chacha8
 chacha20-ietf chacha12-ietf chacha8-ietf
 xchacha20 xchacha12 xchacha8
 '''.split()
-streamciphers += map(lambda s: s.translate(None, '/'), latindances)
+streamciphers += map(lambda s: s.replace('/', ''), latindances)
 hashes = '''
 md2 md4 md5 tiger has160
 sha sha224 sha256 sha512/224 sha512/256 sha384 sha512
@@ -96,7 +96,7 @@ for i in latindances:
   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.translate(None, '/').replace('-', '_'),
+      {'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, ' +
index b88232b..5b7bb64 100644 (file)
--- a/buffer.c
+++ b/buffer.c
@@ -112,7 +112,8 @@ end:
     uint##n x;                                                         \
     if (!PyArg_ParseTuple(arg, ":getu" #w)) goto end;                  \
     if (buf_getu##w(BUF_B(me), &x)) BUFERR();                          \
-    return (getulong(x));                                              \
+    if (MASK##W <= ULONG_MAX) return (getulong(x));                    \
+    else { kludge64 y; ASSIGN64(y, x); return (getk64(y)); }           \
   end:                                                                 \
     return (0);                                                                \
   }
@@ -261,14 +262,14 @@ static PyMethodDef rbuf_pymethods[] = {
     METH(getu##w, "RBUF.getu" #w "() -> INT")
   DOUINTCONV(RBMETH_DECL_GETU_)
 #define RBMETH_DECL_GETBLK_(n, W, w)                                   \
-    METH(getblk##w, "RBUF.getblk" #w "() -> INT")
+    METH(getblk##w, "RBUF.getblk" #w "() -> BYTES")
   BUF_DOSUFFIXES(RBMETH_DECL_GETBLK_)
 #define RBMETH_DECL_GETBUF_(n, W, w)                                   \
-    METH(getbuf##w, "RBUF.getbuf" #w "() -> INT")
+    METH(getbuf##w, "RBUF.getbuf" #w "() -> RBUF'")
   BUF_DOSUFFIXES(RBMETH_DECL_GETBUF_)
   METH (getmp,                 "RBUF.getmp() -> X")
   METH (getgf,                 "RBUF.getgf() -> X")
-  KWMETH(getecpt,              "RBUF.getecpt(curve = None) -> P")
+  KWMETH(getecpt,              "RBUF.getecpt([curve = None]) -> P")
   METH (getecptraw,            "RBUF.getecptraw(CURVE) -> P")
   METH (getge,                 "RBUF.getge(GROUP) -> X")
   METH (getgeraw,              "RBUF.getgeraw(GROUP) -> X")
@@ -405,16 +406,20 @@ static PyObject *wbmeth_put(PyObject *me, PyObject *arg)
   }
 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)       \
   {                                                                    \
     char *p;                                                           \
     Py_ssize_t sz;                                                     \
-    if (!PyArg_ParseTuple(arg, "s#:putblk" #w, &p, &sz)) return (0);   \
+    if (!PyArg_ParseTuple(arg, "s#:putblk" #w, &p, &sz)) goto end;     \
+    if (MASK##W && sz > MASK##W) VALERR("too large");                  \
     ensure(me, sz + SZ_##n);                                           \
     buf_putmem##w(BUF_B(me), p, sz); assert(BOK(BUF_B(me)));           \
     RETURN_ME;                                                         \
+  end:                                                                 \
+    return (0);                                                                \
   }
 BUF_DOSUFFIXES(WBMETH_PUTBLK_)
 
@@ -441,8 +446,7 @@ static PyObject *wbmeth_putecpt(PyObject *me, PyObject *arg)
 {
   ec pt = EC_INIT;
   if (!PyArg_ParseTuple(arg, "O&:putecpt", convecpt, &pt)) return (0);
-  if (EC_ATINF(&pt)) ensure(me, 2);
-  else ensure(me, 4 + mp_octets(pt.x) + mp_octets(pt.y));
+  ensure(me, EC_ATINF(&pt) ? 2 : 6 + mp_octets(pt.x) + mp_octets(pt.y));
   buf_putec(BUF_B(me), &pt); assert(BOK(BUF_B(me)));
   EC_DESTROY(&pt);
   RETURN_ME;
@@ -505,7 +509,7 @@ static PyMethodDef wbuf_pymethods[] = {
   BUF_DOSUFFIXES(WBMETH_DECL_PUTBLK_)
   METH (putmp,                 "WBUF.putmp(X)")
   METH (putgf,                 "WBUF.putgf(X)")
-  KWMETH(putecpt,              "WBUF.putecpt(P)")
+  METH (putecpt,               "WBUF.putecpt(P)")
   METH (putecptraw,            "WBUF.putecptraw(P)")
   METH (putge,                 "WBUF.putge(X)")
   METH (putgeraw,              "WBUF.putgeraw(X)")
index a7fe5cb..82d117f 100644 (file)
@@ -165,7 +165,7 @@ static PyObject *bytestring_pyrepeat(PyObject *me, Py_ssize_t n)
 
   xp = (const unsigned char *)PyString_AS_STRING(me);
   xsz = PyString_GET_SIZE(me);
-  if (n < 0 || xsz >= (size_t)-1/n) VALERR("too long");
+  if (n < 0 || (n && xsz >= (size_t)-1/n)) VALERR("too long");
   zsz = n*xsz; z = bytestring_pywrap(0, zsz); zp = PyString_AS_STRING(z);
   if (xsz == 1) memset(zp, *xp, zsz);
   else while (zsz) { memcpy(zp, xp, xsz); zp += xsz; zsz -= xsz; }
index 5815343..bf3d426 100644 (file)
@@ -149,6 +149,7 @@ PRIVATE_SYMBOLS;
   goto end;                                                            \
 } while (0)
 #define VALERR(str) EXCERR(PyExc_ValueError, str)
+#define OVFERR(str) EXCERR(PyExc_OverflowError, str)
 #define TYERR(str) EXCERR(PyExc_TypeError, str)
 #define IXERR(str) EXCERR(PyExc_IndexError, str)
 #define ZDIVERR(str) EXCERR(PyExc_ZeroDivisionError, str)
@@ -162,7 +163,7 @@ PRIVATE_SYMBOLS;
   PyErr_SetFromErrnoWithFilename(PyExc_OSError, name);                 \
   goto end;                                                            \
 } while (0)
-#define PGENERR do { pgenerr(); goto end; } while (0)
+#define PGENERR(exc) do { pgenerr(exc); goto end; } while (0)
 
 #define CONVFUNC(ty, cty, ext)                                         \
   int conv##ty(PyObject *o, void *p)                                   \
@@ -188,7 +189,9 @@ PRIVATE_SYMBOLS;
 } while (0)
 #define INITTYPE(ty, base) INITTYPE_META(ty, base, type)
 
-#define INSERT(name, ob) do {                                          \
+extern PyObject *home_module;
+
+#define INSERT(name, ob) do {  \
   PyObject *_o = (PyObject *)(ob);                                     \
   Py_INCREF(_o);                                                       \
   PyModule_AddObject(mod, name, _o);                                   \
@@ -250,7 +253,8 @@ MODULES(DO)
 
 #define KWLIST (/*unconst*/ char **)kwlist
 
-struct nameval { const char *name; unsigned long value; };
+struct nameval { const char *name; unsigned f; unsigned long value; };
+#define CF_SIGNED 1u
 extern void setconstants(PyObject *, const struct nameval *);
 
 extern PyObject *mexp_common(PyObject *, PyObject *, size_t,
@@ -274,7 +278,34 @@ extern PyObject *getulong(unsigned long);
 extern PyObject *getk64(kludge64);
 extern void *newtype(PyTypeObject *, const PyTypeObject *, const char *);
 
+struct excinfo { PyObject *ty, *val, *tb; };
+#define EXCINFO_INIT { 0, 0, 0 }
+
 extern PyObject *mkexc(PyObject *, PyObject *, const char *, PyMethodDef *);
+#define INIT_EXCINFO(exc) do {                                         \
+  struct excinfo *_exc = (exc); _exc->ty = _exc->val = _exc->tb = 0;   \
+} while (0)
+#define RELEASE_EXCINFO(exc) do {                                      \
+  struct excinfo *_exc = (exc);                                                \
+  Py_XDECREF(_exc->ty);         _exc->ty  = 0;                                 \
+  Py_XDECREF(_exc->val); _exc->val = 0;                                        \
+  Py_XDECREF(_exc->tb);         _exc->tb  = 0;                                 \
+} while (0)
+#define STASH_EXCINFO(exc) do {                                                \
+  struct excinfo *_exc = (exc);                                                \
+  PyErr_Fetch(&_exc->ty, &_exc->val, &_exc->tb);                       \
+  PyErr_NormalizeException(&_exc->ty, &_exc->val, &_exc->tb);          \
+} while (0)
+#define RESTORE_EXCINFO(exc) do {                                      \
+  struct excinfo *_exc = (exc);                                                \
+  PyErr_Restore(_exc->ty, _exc->val, _exc->tb);                                \
+  _exc->ty = _exc->val = _exc->tb = 0;                                 \
+} while (0)
+extern void report_lost_exception(struct excinfo *, const char *, ...);
+extern void report_lost_exception_v(struct excinfo *, const char *, va_list);
+extern void stash_exception(struct excinfo *, const char *, ...);
+extern void restore_exception(struct excinfo *, const char *, ...);
+
 extern void typeready(PyTypeObject *);
 extern PyTypeObject *inittype(PyTypeObject *, PyTypeObject *);
 extern void addmethods(const PyMethodDef *);
@@ -343,6 +374,7 @@ extern mp *getgf(PyObject *);
 extern int convgf(PyObject *, void *);
 extern PyObject *mp_pywrap(mp *);
 extern PyObject *gf_pywrap(mp *);
+extern long mphash(mp *);
 extern mp *mp_frompyobject(PyObject *, int);
 extern PyObject *mp_topystring(mp *, int,
                               const char *, const char *, const char *);
@@ -361,7 +393,6 @@ extern PyTypeObject *fe_pytype;
 #define FE_FOBJ(o) ((PyObject *)(o)->ob_type)
 #define FE_X(o) (((fe_pyobj *)(o))->x)
 extern PyObject *fe_pywrap(PyObject *, mp *);
-extern mp *getfe(field *, PyObject *);
 
 typedef struct fe_pyobj {
   PyObject_HEAD
@@ -715,9 +746,15 @@ extern PyTypeObject *pgev_pytype;
 #define PGEV_PYCHECK(o) PyObject_TypeCheck(o, pgev_pytype)
 #define PGEV_PG(o) (&((pgev_pyobj *)(o))->pg)
 
+typedef struct pypgev {
+  pgev ev;
+  PyObject *obj;
+  struct excinfo *exc;
+} pypgev;
+
 extern int convpgev(PyObject *, void *);
-extern void droppgev(pgev *);
-extern void pgenerr(void);
+extern void droppgev(pypgev *);
+extern void pgenerr(struct excinfo *exc);
 
 /*----- That's all, folks -------------------------------------------------*/
 
index 572f80f..e868f0f 100644 (file)
 /*----- Main code ---------------------------------------------------------*/
 
 static const struct nameval consts[] = {
-#define C(x) { #x, x }
+#define CF(f, x) { #x, f, x }
+#define C(x) { #x, (x) >= 0 ? 0 : CF_SIGNED, x }
   C(FTY_PRIME), C(FTY_BINARY),
   C(PGEN_PASS), C(PGEN_FAIL), C(PGEN_BEGIN), C(PGEN_TRY), C(PGEN_DONE),
   C(PGEN_ABORT),
   C(MPW_MAX),
+  C(RAND_IBITS),
   C(PMODE_READ), C(PMODE_VERIFY),
   C(KOPEN_READ), C(KOPEN_WRITE), C(KOPEN_NOFILE),
-  C(KEXP_FOREVER), C(KEXP_EXPIRE),
+  CF(0, KEXP_FOREVER), CF(0, KEXP_EXPIRE),
   C(KF_ENCMASK), C(KENC_BINARY), C(KENC_MP), C(KENC_STRUCT),
     C(KENC_ENCRYPT), C(KENC_STRING), C(KENC_EC),
   C(KF_CATMASK), C(KCAT_SYMM), C(KCAT_PRIV), C(KCAT_PUB), C(KCAT_SHARE),
@@ -56,6 +58,7 @@ static const struct nameval consts[] = {
   KEY_ERRORS(ENTRY)
 #undef ENTRY
 #undef C
+#undef CF
   { 0 }
 };
 
@@ -75,7 +78,8 @@ PyObject *mexp_common(PyObject *me, PyObject *arg,
     arg = PyTuple_GetItem(arg, 0);
   Py_INCREF(arg);
   if (!PySequence_Check(arg)) TYERR("not a sequence");
-  n = PySequence_Size(arg); if (!n) { z = id(me); goto end; }
+  n = PySequence_Size(arg); if (n < 0) goto end;
+  if (!n) { z = id(me); goto end; }
   x = PySequence_GetItem(arg, 0);
   if (PySequence_Check(x))
     flat = 0;
index 8038815..65695bb 100644 (file)
@@ -27,7 +27,8 @@ from __future__ import with_statement
 
 from binascii import hexlify as _hexify, unhexlify as _unhexify
 from contextlib import contextmanager as _ctxmgr
-import DLFCN as _dlfcn
+try: import DLFCN as _dlfcn
+except ImportError: _dlfcn = None
 import os as _os
 from struct import pack as _pack
 import sys as _sys
@@ -67,6 +68,15 @@ del _dlflags, _odlflags
 ## 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
+
 ## How to fix a name back into the right identifier.  Alas, the rules are not
 ## consistent.
 def _fixname(name):
@@ -75,7 +85,7 @@ def _fixname(name):
   name = name.replace('-', '_')
 
   ## But slashes might become underscores or just vanish.
-  if name.startswith('salsa20'): name = name.translate(None, '/')
+  if name.startswith('salsa20'): name = name.replace('/', '')
   else: name = name.replace('/', '_')
 
   ## Done.
@@ -217,15 +227,31 @@ _augment(Poly1305Hash, _tmp)
 class _HashBase (object):
   ## The standard hash methods.  Assume that `hash' is defined and returns
   ## the receiver.
-  def hashu8(me, n): return me.hash(_pack('B', n))
-  def hashu16l(me, n): return me.hash(_pack('<H', n))
-  def hashu16b(me, n): return me.hash(_pack('>H', n))
+  def _check_range(me, n, max):
+    if not (0 <= n <= max): raise OverflowError("out of range")
+  def hashu8(me, n):
+    me._check_range(n, 0xff)
+    return me.hash(_pack('B', n))
+  def hashu16l(me, n):
+    me._check_range(n, 0xffff)
+    return me.hash(_pack('<H', n))
+  def hashu16b(me, n):
+    me._check_range(n, 0xffff)
+    return me.hash(_pack('>H', n))
   hashu16 = hashu16b
-  def hashu32l(me, n): return me.hash(_pack('<L', n))
-  def hashu32b(me, n): return me.hash(_pack('>L', n))
+  def hashu32l(me, n):
+    me._check_range(n, 0xffffffff)
+    return me.hash(_pack('<L', n))
+  def hashu32b(me, n):
+    me._check_range(n, 0xffffffff)
+    return me.hash(_pack('>L', n))
   hashu32 = hashu32b
-  def hashu64l(me, n): return me.hash(_pack('<Q', n))
-  def hashu64b(me, n): return me.hash(_pack('>Q', n))
+  def hashu64l(me, n):
+    me._check_range(n, 0xffffffffffffffff)
+    return me.hash(_pack('<Q', n))
+  def hashu64b(me, n):
+    me._check_range(n, 0xffffffffffffffff)
+    return me.hash(_pack('>Q', n))
   hashu64 = hashu64b
   def hashbuf8(me, s): return me.hashu8(len(s)).hash(s)
   def hashbuf16l(me, s): return me.hashu16l(len(s)).hash(s)
@@ -248,8 +274,8 @@ class _ShakeBase (_HashBase):
     me._h = me._SHAKE(perso = perso, func = me._FUNC)
 
   ## Delegate methods...
-  def copy(me): new = me.__class__(); new._copy(me)
-  def _copy(me, other): me._h = other._h
+  def copy(me): new = me.__class__._bare_new(); new._copy(me); return new
+  def _copy(me, other): me._h = other._h.copy()
   def hash(me, m): me._h.hash(m); return me
   def xof(me): me._h.xof(); return me
   def get(me, n): return me._h.get(n)
@@ -262,6 +288,8 @@ class _ShakeBase (_HashBase):
   def buffered(me): return me._h.buffered
   @property
   def rate(me): return me._h.rate
+  @classmethod
+  def _bare_new(cls): return cls()
 
 class _tmp:
   def check(me, h):
@@ -300,6 +328,8 @@ class KMAC (_ShakeBase):
   def xof(me):
     me.rightenc(0)
     return super(KMAC, me).xof()
+  @classmethod
+  def _bare_new(cls): return cls("")
 
 class KMAC128 (KMAC): _SHAKE = Shake128; _TAGSZ = 16
 class KMAC256 (KMAC): _SHAKE = Shake256; _TAGSZ = 32
@@ -353,15 +383,18 @@ class BaseRat (object):
   def __mul__(me, you):
     n, d = _split_rat(you)
     return type(me)(me._n*n, me._d*d)
-  def __div__(me, you):
+  __rmul__ = __mul__
+  def __truediv__(me, you):
     n, d = _split_rat(you)
     return type(me)(me._n*d, me._d*n)
-  def __rdiv__(me, you):
+  def __rtruediv__(me, you):
     n, d = _split_rat(you)
     return type(me)(me._d*n, me._n*d)
+  __div__ = __truediv__
+  __rdiv__ = __rtruediv__
   def __cmp__(me, you):
     n, d = _split_rat(you)
-    return type(me)(me._n*d, n*me._d)
+    return cmp(me._n*d, n*me._d)
   def __rcmp__(me, you):
     n, d = _split_rat(you)
     return cmp(n*me._d, me._n*d)
@@ -381,8 +414,10 @@ class _tmp:
   def mont(x): return MPMont(x)
   def barrett(x): return MPBarrett(x)
   def reduce(x): return MPReduce(x)
-  def __div__(me, you): return IntRat(me, you)
-  def __rdiv__(me, you): return IntRat(you, me)
+  def __truediv__(me, you): return IntRat(me, you)
+  def __rtruediv__(me, you): return IntRat(you, me)
+  __div__ = __truediv__
+  __rdiv__ = __rtruediv__
   _repr_pretty_ = _pp_str
 _augment(MP, _tmp)
 
@@ -393,8 +428,10 @@ class _tmp:
   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 __div__(me, you): return GFRat(me, you)
-  def __rdiv__(me, you): return GFRat(you, me)
+  def __truediv__(me, you): return GFRat(me, you)
+  def __rtruediv__(me, you): return GFRat(you, me)
+  __div__ = __truediv__
+  __rdiv__ = __rtruediv__
   _repr_pretty_ = _pp_str
 _augment(GF, _tmp)
 
@@ -586,7 +623,7 @@ class _tmp:
   def pad(me, sz):
     if sz > me.max: raise ValueError, 'key too large'
     elif sz < me.min: return me.min
-    else: sz += me.mod; return sz - sz%me.mod
+    else: sz += me.mod - 1; return sz - sz%me.mod
 _augment(KeySZRange, _tmp)
 
 class _tmp:
index cfbac7e..03ed685 100644 (file)
@@ -80,7 +80,7 @@ def _dec_metaname(name):
 
 def _b64(s):
   """Encode S as base64, without newlines, and trimming `=' padding."""
-  return s.encode('base64').translate(None, '\n=')
+  return s.encode('base64').replace('\n', '').rstrip('=')
 def _unb64(s):
   """Decode S as base64 with trimmed `=' padding."""
   return (s + '='*((4 - len(s))%4)).decode('base64')
index fad1029..66c14f7 100644 (file)
@@ -27,6 +27,12 @@ catacomb-python (1.3.0) experimental; urgency=medium
 
  -- 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.
index 84a43a0..7b7400d 100644 (file)
@@ -3,7 +3,7 @@ Section: python
 Priority: extra
 XS-Python-Version: >= 2.6, << 2.8
 Maintainer: Mark Wooding <mdw@distorted.org.uk>
-Build-Depends: debhelper (>= 10), pkg-config,
+Build-Depends: debhelper (>= 10), dh-python, pkg-config,
        python (>= 2.6.6-3~), python-all-dev,
        mlib-dev (>= 2.4.99~), catacomb-dev (>= 2.5.0)
 Standards-Version: 3.8.0
index 4339423..04d6f4e 100755 (executable)
@@ -1,2 +1,10 @@
 #! /usr/bin/make -f
-%:; dh $@ --parallel -Bdebian/build --with python2
+%:; dh $@ --parallel --with python2
+
+export PYTHONS := $(shell pyversions -r)
+
+override_dh_auto_test:
+       dh_auto_test -- OPTS-check=-V
+
+override_dh_auto_install:
+       dh_auto_install -- prefix=/usr
diff --git a/ec.c b/ec.c
index 6d5e31f..a660eb6 100644 (file)
--- a/ec.c
+++ b/ec.c
@@ -188,38 +188,31 @@ static PyObject *ecpt_pymul(PyObject *x, PyObject *y)
   if (ECPT_PYCHECK(x)) { PyObject *t; t = x; x = y; y = t; }
   if (!ECPT_PYCHECK(y) || (xx = tomp(x)) == 0) RETURN_NOTIMPL;
   ec_imul(ECPT_C(y), &zz, ECPT_P(y), xx);
+  MP_DROP(xx);
   return (ecpt_pywrap(ECPT_COBJ(y), &zz));
 }
 
 static long ecpt_pyhash(PyObject *me)
 {
   uint32 h;
-  buf b;
   ec p = EC_INIT;
-  size_t sz = 2*ECPT_C(me)->f->noctets + 1;
-  octet *q = xmalloc(sz);
 
-  h = 0xe0fdd039 + ECPT_C(me)->f->ops->ty;
-  buf_init(&b, q, sz);
-  EC_OUT(ECPT_C(me), &p, ECPT_P(me));
-  ec_putraw(ECPT_C(me), &b, &p);
+  getecptout(&p, me);
+  if (EC_ATINF(&p)) h = 0x81d81a94;
+  else h = 0xe0fdd039 ^ (2*mphash(p.x)) ^ (3*mphash(p.y));
   EC_DESTROY(&p);
-  h = unihash_hash(&unihash_global, h, BBASE(&b), BLEN(&b));
-  xfree(q);
-  return (h % LONG_MAX);
+  return (h%LONG_MAX);
 }
 
 static PyObject *ecpt_pyrichcompare(PyObject *x, PyObject *y, int op)
 {
-  ec_curve *c;
-  PyObject *cobj;
   ec p = EC_INIT, q = EC_INIT;
   int b;
   PyObject *rc = 0;
 
-  if (ecbinop(x, y, &c, &cobj, &p, &q)) RETURN_NOTIMPL;
-  EC_OUT(c, &p, &p);
-  EC_OUT(c, &q, &q);
+  if (!ECPT_PYCHECK(y)) RETURN_NOTIMPL;
+  getecptout(&p, x);
+  getecptout(&q, y);
   switch (op) {
     case Py_EQ: b = EC_EQ(&p, &q); break;
     case Py_NE: b = !EC_EQ(&p, &q); break;
@@ -259,7 +252,7 @@ static PyObject *epmeth_tobuf(PyObject *me, PyObject *arg)
   if (EC_ATINF(&p))
     n = 2;
   else
-    n = mp_octets(p.x) + mp_octets(p.y) + 4;
+    n = mp_octets(p.x) + mp_octets(p.y) + 6;
   rc = bytestring_pywrap(0, n);
   buf_init(&b, PyString_AS_STRING(rc), n);
   buf_putec(&b, &p);
@@ -297,11 +290,12 @@ static PyObject *epmeth_ec2osp(PyObject *me, PyObject *arg, PyObject *kw)
   char *p;
   ec_curve *c = ECPT_C(me);
   ec pp = EC_INIT;
-  int f = EC_EXPLY;
+  unsigned f = EC_EXPLY;
   int len;
   static const char *const kwlist[] = { "flags", 0 };
 
-  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|i:ectosp", KWLIST, &f))
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "|O&:ec2osp", KWLIST,
+                                  convuint, &f))
     return (0);
   len = c->f->noctets * 2 + 1;
   rc = bytestring_pywrap(0, len);
@@ -419,9 +413,9 @@ static int ecptxl_3(ec_curve *c, ec *p,
 
   if (!x || !y || !z) TYERR("missing argument");
   if (!c) VALERR("internal form with no curve!");
-  if ((p->x == coord_in(c->f, x)) == 0 ||
-      (p->y == coord_in(c->f, y)) == 0 ||
-      (z != Py_None && (p->z = coord_in(c->f, z))) == 0)
+  if ((p->x = coord_in(c->f, x)) == 0 ||
+      (p->y = coord_in(c->f, y)) == 0 ||
+      (z != Py_None && (p->z = coord_in(c->f, z)) == 0))
     goto end;
   if (!p->z) p->z = MP_COPY(c->f->one); /* just in case */
   rc = 0;
@@ -459,7 +453,7 @@ static int ecptxl_1(ec_curve *c, ec *p, PyObject *x)
     getecptout(p, x);
     goto fix;
   } else if (PyString_Check(x)) {
-    if (PyObject_AsReadBuffer(x, &q, 0))
+    if (PyObject_AsReadBuffer(x, &q, &n))
       goto end;
     qd.p = q;
     qd.e = 0;
@@ -471,7 +465,7 @@ static int ecptxl_1(ec_curve *c, ec *p, PyObject *x)
     if (!EC_FIND(c, p, xx)) VALERR("not on the curve");
   } else if (PySequence_Check(x)) {
     t = x; x = 0;
-    n = PySequence_Size(t);
+    n = PySequence_Size(t); if (n < 0) goto end;
     if (n != 2 && (n != 3 || !c))
       TYERR("want sequence of two or three items");
     if ((x = PySequence_GetItem(t, 0)) == 0 ||
@@ -479,6 +473,7 @@ static int ecptxl_1(ec_curve *c, ec *p, PyObject *x)
        (n == 3 && (z = PySequence_GetItem(t, 2)) == 0))
       goto end;
     rc = (n == 2) ? ecptxl_2(c, p, x, y) : ecptxl_3(c, p, x, y, z);
+    goto end;
   } else
     TYERR("can't convert to curve point");
   goto ok;
@@ -514,7 +509,7 @@ static PyObject *ecptnc_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
     goto end;
   return (ecpt_pywrapout(ty, &p));
 end:
-  EC_DESTROY(&p);
+  mp_drop(p.x); mp_drop(p.y); mp_drop(p.z);
   return (0);
 }
 
@@ -556,7 +551,7 @@ static PyObject *ecpt_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
     goto end;
   return (ecpt_pywrap((PyObject *)ty, &p));
 end:
-  EC_DESTROY(&p);
+  mp_drop(p.x); mp_drop(p.y); mp_drop(p.z);
   return (0);
 }
 
@@ -789,10 +784,14 @@ static PyTypeObject ecptcurve_pytype_skel = {
 
 static PyObject *eccurve_pyrichcompare(PyObject *x, PyObject *y, int op)
 {
-  int b = ec_samep(ECCURVE_C(x), ECCURVE_C(y));
+  int b;
+
+  assert(ECCURVE_PYCHECK(x));
+  if (!ECCURVE_PYCHECK(y)) RETURN_NOTIMPL;
+  b = ec_samep(ECCURVE_C(x), ECCURVE_C(y));
   switch (op) {
     case Py_EQ: break;
-    case Py_NE: b = !b;
+    case Py_NE: b = !b; break;
     default: TYERR("can't order elliptic curves");
   }
   return (getbool(b));
@@ -863,12 +862,12 @@ static PyObject *meth__ECPtCurve_os2ecp(PyObject *me,
   buf b;
   PyObject *rc = 0;
   ec_curve *cc;
-  int f = EC_XONLY | EC_LSB | EC_SORT | EC_EXPLY;
+  unsigned f = EC_XONLY | EC_LSB | EC_SORT | EC_EXPLY;
   ec pp = EC_INIT;
-  static const char *const kwlist[] = { "buf", "flags", 0 };
+  static const char *const kwlist[] = { "class", "buf", "flags", 0 };
 
-  if (!PyArg_ParseTupleAndKeywords(arg, kw, "Os#|f:os2ecp", KWLIST,
-                                  &me, &p, &len, &f))
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "Os#|O&:os2ecp", KWLIST,
+                                  &me, &p, &len, convuint, &f))
     return (0);
   buf_init(&b, p, len);
   cc = ECCURVE_C(me);
@@ -1067,7 +1066,7 @@ static PyMethodDef eccurve_pymethods[] = {
   METH (mmul,          "\
 E.mmul([(P0, N0), (P1, N1), ...]) = N0 P0 + N1 P1 + ...")
   METH (find,          "E.find(X) -> P")
-  KWMETH(rand,         "E.rand(rng = rand) ->P")
+  KWMETH(rand,         "E.rand([rng = rand]) -> P")
 #undef METHNAME
   { 0 }
 };
@@ -1492,7 +1491,7 @@ static PyGetSetDef ecinfo_pygetset[] = {
 
 static PyMethodDef ecinfo_pymethods[] = {
 #define METHNAME(name) eimeth_##name
-  KWMETH(check,                "I.check() -> None")
+  KWMETH(check,                "I.check([rng = rand]) -> None")
 #undef METHNAME
   { 0 }
 };
diff --git a/field.c b/field.c
index b78c166..417c550 100644 (file)
--- a/field.c
+++ b/field.c
@@ -126,16 +126,6 @@ static mp *tofe(field *f, PyObject *o)
   return (y);
 }
 
-mp *getfe(field *f, PyObject *o)
-{
-  mp *x = 0;
-  if ((x = tofe(f, o)) == 0) {
-    PyErr_Format(PyExc_TypeError, "can't convert %.100s to fe",
-                o->ob_type->tp_name);
-  }
-  return (x);
-}
-
 /*----- Field elements ----------------------------------------------------*/
 
 static int febinop(PyObject *x, PyObject *y,
@@ -229,15 +219,7 @@ end:
 }
 
 static long fe_pyhash(PyObject *me)
-{
-  size_t sz = FE_F(me)->noctets;
-  uint32 h = 0xe0c127ca + FE_F(me)->ops->ty;
-  octet *p = xmalloc(sz);
-  mp_storeb(FE_X(me), p, sz);
-  h = unihash_hash(&unihash_global, h, p, sz);
-  xfree(p);
-  return (h % LONG_MAX);
-}
+  { return (mphash(FE_X(me))); }
 
 static int fe_pycoerce(PyObject **x, PyObject **y)
 {
@@ -282,7 +264,7 @@ static PyObject *fe_pylong(PyObject *x)
 #define BASEOP(name, radix, pre)                                       \
   static PyObject *fe_py##name(PyObject *x) {                          \
     mp *xx = F_OUT(FE_F(x), MP_NEW, FE_X(x));                          \
-    PyObject *rc = mp_topystring(FE_X(x), radix, 0, pre, 0);           \
+    PyObject *rc = mp_topystring(xx, radix, 0, pre, 0);                        \
     MP_DROP(xx);                                                       \
     return (rc);                                                       \
   }
@@ -345,8 +327,8 @@ static PyObject *feget__value(PyObject *me, void *hunoz)
 static PyGetSetDef fe_pygetset[] = {
 #define GETSETNAME(op, name) fe##op##_##name
   GET  (field,         "X.field -> field containing X")
-  GET  (value,         "X.value -> `natural' integer representation of X")
-  GET  (_value,        "X._value -> internal integer representation of X")
+  GET  (value,         "X.value -> `natural' MP/GF representation of X")
+  GET  (_value,        "X._value -> internal MP/GF representation of X")
 #undef GETSETNAME
   { 0 }
 };
@@ -533,7 +515,7 @@ static PyGetSetDef field_pygetset[] = {
 static PyMethodDef field_pymethods[] = {
 #define METHNAME(name) fmeth_##name
   METH (_adopt,        "F._adopt(X) -> FE")
-  KWMETH(rand,         "F.rand(rng = rand) -> FE, uniformly distributed")
+  KWMETH(rand,         "F.rand([rng = rand]) -> FE, uniformly distributed")
 #undef METHNAME
   { 0 }
 };
@@ -809,8 +791,11 @@ end:
   return (0);
 }
 
+static PyObject *bfget_p(PyObject *me, void *hunoz)
+  { return (gf_pywrap(MP_COPY(FIELD_F(me)->m))); }
+
 static PyGetSetDef binpolyfield_pygetset[] = {
-#define GETSETNAME(op, name) pf##op##_##name
+#define GETSETNAME(op, name) bf##op##_##name
   GET  (p,             "F.p -> field polynomial")
 #undef GETSETNAME
   { 0 }
@@ -889,7 +874,7 @@ static PyObject *bnfget_beta(PyObject *me, void *hunoz)
 }
 
 static PyGetSetDef binnormfield_pygetset[] = {
-#define GETSETNAME(op, name) pf##op##_##name
+#define GETSETNAME(op, name) bf##op##_##name
   GET  (p,             "F.p -> field polynomial")
 #undef GETSETNAME
 #define GETSETNAME(op, name) bnf##op##_##name
diff --git a/group.c b/group.c
index c3d2386..239456f 100644 (file)
--- a/group.c
+++ b/group.c
@@ -92,18 +92,20 @@ static PyObject *meth__DHInfo_generate(PyObject *me,
   unsigned ql = 0, pl;
   unsigned steps = 0;
   grand *r = &rand_global;
-  pgev evt = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
   static const char *const kwlist[] =
     { "class", "pbits", "qbits", "event", "rng", "nsteps", 0 };
   PyObject *rc = 0;
 
+  evt.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&|O&O&O&O&:generate", KWLIST,
                                   &me, convuint, &pl, convuint, &ql,
                                   convpgev, &evt, convgrand, &r,
                                   convuint, &steps))
     goto end;
-  if (dh_gen(&dp, ql, pl, steps, r, evt.proc, evt.ctx))
-    PGENERR;
+  if (dh_gen(&dp, ql, pl, steps, r, evt.ev.proc, evt.ev.ctx))
+    PGENERR(&exc);
   rc = fginfo_pywrap(&dp, dhinfo_pytype);
 end:
   droppgev(&evt);
@@ -117,7 +119,8 @@ static PyObject *meth__DHInfo_genlimlee(PyObject *me,
   unsigned ql, pl;
   unsigned steps = 0;
   grand *r = &rand_global;
-  pgev oe = { 0 }, ie = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev oe = { { 0 } }, ie = { { 0 } };
   int subgroupp = 1;
   unsigned f = 0;
   static const char *const kwlist[] = {
@@ -128,6 +131,7 @@ static PyObject *meth__DHInfo_genlimlee(PyObject *me,
   mp **v = 0;
   PyObject *rc = 0, *vec = 0;
 
+  oe.exc = ie.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw,
                                   "OO&O&|O&O&O&O&O&:genlimlee", KWLIST,
                                   &me, convuint, &pl, convuint, &ql,
@@ -137,8 +141,8 @@ static PyObject *meth__DHInfo_genlimlee(PyObject *me,
     goto end;
   if (subgroupp) f |= DH_SUBGROUP;
   if (dh_limlee(&dp, ql, pl, f, steps, r,
-               oe.proc, oe.ctx, ie.proc, ie.ctx, &nf, &v))
-    PGENERR;
+               oe.ev.proc, oe.ev.ctx, ie.ev.proc, ie.ev.ctx, &nf, &v))
+    PGENERR(&exc);
   vec = PyList_New(nf);
   for (i = 0; i < nf; i++)
     PyList_SetItem(vec, i, mp_pywrap(v[i]));
@@ -156,19 +160,21 @@ static PyObject *meth__DHInfo_genkcdsa(PyObject *me,
   unsigned ql, pl;
   unsigned steps = 0;
   grand *r = &rand_global;
-  pgev evt = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
   static const char *const kwlist[] =
     { "class", "pbits", "qbits", "event", "rng", "nsteps", 0 };
   mp *v = MP_NEW;
   PyObject *rc = 0;
 
+  evt.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&O&|O&O&O&:genkcdsa", KWLIST,
                                   &me, convuint, &pl, convuint, &ql,
                                   convpgev, &evt, convgrand, &r,
                                   convuint, &steps))
     goto end;
-  if (dh_kcdsagen(&dp, ql, pl, 0, steps, r, evt.proc, evt.ctx))
-    PGENERR;
+  if (dh_kcdsagen(&dp, ql, pl, 0, steps, r, evt.ev.proc, evt.ev.ctx))
+    PGENERR(&exc);
   mp_div(&v, 0, dp.p, dp.q);
   v = mp_lsr(v, v, 1);
   rc = Py_BuildValue("(NN)", fginfo_pywrap(&dp, dhinfo_pytype),
@@ -187,18 +193,20 @@ static PyObject *meth__DHInfo_gendsa(PyObject *me,
   dsa_seed ds;
   char *k;
   Py_ssize_t ksz;
-  pgev evt = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
   static const char *const kwlist[] =
     { "class", "pbits", "qbits", "seed", "event", "nsteps", 0 };
   PyObject *rc = 0;
 
+  evt.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&O&s#|O&O&:gendsa", KWLIST,
                                   &me, convuint, &pl, convuint, &ql,
                                   &k, &ksz, convpgev, &evt,
                                   convuint, &steps))
     goto end;
-  if (dsa_gen(&dp, ql, pl, steps, k, ksz, &ds, evt.proc, evt.ctx))
-    PGENERR;
+  if (dsa_gen(&dp, ql, pl, steps, k, ksz, &ds, evt.ev.proc, evt.ev.ctx))
+    PGENERR(&exc);
   rc = Py_BuildValue("(NNl)", fginfo_pywrap(&dp, dhinfo_pytype),
                     bytestring_pywrap(ds.p, ds.sz), (long)ds.count);
   xfree(ds.p);
@@ -966,7 +974,7 @@ static PyMethodDef ge_pymethods[] = {
   METH (check,         "X.check() -> check X really belongs to its group")
   METH (toint,         "X.toint() -> X converted to an integer")
   KWMETH(toec,         "\
-X.toec(curve = ecpt) -> X converted to elliptic curve point")
+X.toec([curve = ECPt]) -> X converted to elliptic curve point")
   METH (tobuf,         "X.tobuf() -> X in buffer representation")
   METH (toraw,         "X.toraw() -> X in raw representation")
 #undef METHNAME
@@ -1081,7 +1089,7 @@ static PyMethodDef group_pymethods[] = {
 #define METHNAME(name) gmeth_##name
   METH (mexp,          "\
 G.mexp([(X0, N0), (X1, N1), ...]) -> X0^N0 X1^N1 ...")
-  KWMETH(checkgroup,   "G.checkgroup(rand = random): check group is good")
+  KWMETH(checkgroup,   "G.checkgroup([rng = rand]): check group is good")
 #undef METHNAME
   { 0 }
 };
@@ -1379,16 +1387,16 @@ static PyMethodDef methods[] = {
   METH (_DHInfo__groupn,       0)
   METH (_BinDHInfo__groupn,    0)
   KWMETH(_DHInfo_generate,     "\
-generate(PBITS, [qbits = 0, event = pgen_nullev,\n\
-        rng = rand, nsteps = 0]) -> D")
+generate(PBITS, [qbits = 0], [event = pgen_nullev],\n\
+        [rng = rand], [nsteps = 0]) -> D")
   KWMETH(_DHInfo_genlimlee,    "\
-genlimlee(PBITS, QBITS, [event = pgen_nullev, ievent = pgen_nullev,\n\
-         rng = rand, nsteps = 0, subgroupp = True]) -> (D, [Q, ...])")
+genlimlee(PBITS, QBITS, [event = pgen_nullev], [ievent = pgen_nullev],\n\
+         [rng = rand], [nsteps = 0], [subgroupp = True]) -> (D, [Q, ...])")
   KWMETH(_DHInfo_gendsa,       "\
-gendsa(PBITS, QBITS, SEED, [event = pgen_nullevnsteps = 0])\n\
+gendsa(PBITS, QBITS, SEED, [event = pgen_nullev], [nsteps = 0])\n\
   -> (D, SEED, COUNT)")
   KWMETH(_DHInfo_genkcdsa,     "\
-gendsa(PBITS, QBITS, [event = pgen_nullev, rng = rand, nsteps = 0])\n\
+gendsa(PBITS, QBITS, [event = pgen_nullev], [rng = rand], [nsteps = 0])\n\
   -> (D, V)")
 #undef METHNAME
   { 0 }
diff --git a/key.c b/key.c
index 1700259..87462ab 100644 (file)
--- a/key.c
+++ b/key.c
@@ -36,29 +36,31 @@ static PyObject *keyfilebrokenexc;
 
 static PyObject *kxmeth___init__(PyObject *me, PyObject *arg)
 {
-  int err;
+  long err;
   PyObject *x = 0;
+  Py_ssize_t n;
 
-  if (!PyArg_ParseTuple(arg, "Oi:__init__", &me, &err) ||
-      (x = PyInt_FromLong(err)) == 0 ||
-      PyObject_SetAttrString(me, "err", x))
-    goto fail;
-  Py_DECREF(x); x = 0;
-  if ((x = PyString_FromString(key_strerror(err))) == 0 ||
-      PyObject_SetAttrString(me, "errstring", x))
-    goto fail;
+  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;
-  if ((x = PyString_FromString(key_strerror(err))) == 0 ||
-      PyObject_SetAttrString(me, "errstring", x))
-    goto fail;
+
+  x = PyString_FromString(key_strerror(err)); if (!x) goto end;
+  if (PyObject_SetAttrString(me, "errstring", x)) goto end;
   Py_DECREF(x); x = 0;
-  if ((x = PySequence_GetSlice(arg, 1, PySequence_Size(arg))) == 0 ||
-      PyObject_SetAttrString(me, "args", x))
-    goto fail;
+
+  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;
 
-fail:
+end:
   Py_XDECREF(x);
   return (0);
 }
@@ -104,7 +106,7 @@ static PyMethodDef keyexc_pymethods[] = {
 
 static void keyexc_raise(int err)
 {
-  PyObject *arg = Py_BuildValue("(is)", err, key_strerror(err));
+  PyObject *arg = Py_BuildValue("(i)", err);
   if (arg) PyErr_SetObject(keyexc, arg);
   Py_XDECREF(arg);
 }
@@ -229,8 +231,8 @@ static int convfilter(PyObject *x, void *p)
       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))
+    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;
@@ -465,9 +467,9 @@ static PyMethodDef keydata_pymethods[] = {
 #define METHNAME(func) kdmeth_##func
   METH (matchp,                "KD.matchp(FILTER) -> BOOL")
   METH (split,                 "KD.split()")
-  KWMETH(write,                        "KD.write(filter = <any>) -> STRING")
-  KWMETH(encode,               "KD.encode(filter = <any>) -> BYTES")
-  KWMETH(copy,                 "KD.copy(filter = <any>) -> KD")
+  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")
 #undef METHNAME
@@ -1498,6 +1500,7 @@ 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))
@@ -1514,6 +1517,7 @@ 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)
@@ -1598,8 +1602,8 @@ static PyMethodDef key_pymethods[] = {
   METH (delete,        "KEY.delete()")
   METH (expire,        "KEY.expire()")
   METH (used,          "KEY.used(TIME)")
-  KWMETH(extract,      "KEY.extract(FILE, filter = '')")
-  KWMETH(fingerprint,  "KEY.fingerprint(HASH, filtr = '-secret')")
+  KWMETH(extract,      "KEY.extract(FILE, [filter = <any>])")
+  KWMETH(fingerprint,  "KEY.fingerprint(HASH, [filter = '-secret'])")
 #undef METHNAME
   { 0 }
 };
@@ -1989,12 +1993,13 @@ static PyObject *kfget_filep(PyObject *me, void *hunoz)
 static PyMethodDef keyfile_pymethods[] = {
 #define METHNAME(func) kfmeth_##func
   METH (save,          "KF.save()")
-  KWMETH(merge,                "KF.merge(FILE, report = <built-in-reporter>)")
-  KWMETH(newkey,       "KF.newkey(ID, TYPE, exptime = KEXP_FOREVER) -> KEY")
+  KWMETH(merge,                "KF.merge(FILE, [report = <built-in-reporter>])")
+  KWMETH(newkey,       "KF.newkey(ID, TYPE, "
+                               "[exptime = KEXP_FOREVER]) -> KEY")
   METH (byid,          "KF.byid(KEYID) -> KEY|None")
   METH (bytype,        "KF.bytype(TYPE) -> KEY|None")
   METH (bytag,         "KF.bytag(TAG) -> KEY|None")
-  KWMETH(qtag,         "KF.qtag(TAG, new = KD) -> FULLTAG, KEY, OLDKD")
+  KWMETH(qtag,         "KF.qtag(TAG, [new = KD]) -> FULLTAG, KEY, OLDKD")
   GMAP_ROMETHODS
 #undef METHNAME
   { 0 }
diff --git a/mp.c b/mp.c
index e0f72d4..31eec24 100644 (file)
--- a/mp.c
+++ b/mp.c
@@ -211,6 +211,7 @@ mp *tomp(PyObject *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);
@@ -544,13 +545,15 @@ end:
   return ((PyObject *)zz);
 }
 
-static long mp_pyhash(PyObject *me)
+long mphash(mp *x)
 {
-  long h;
-  PyObject *l = mp_topylong(MP_X(me)); h = PyObject_Hash(l);
+  PyObject *l = mp_topylong(x);
+  long h = PyObject_Hash(l);
   Py_DECREF(l); return (h);
 }
 
+static long mp_pyhash(PyObject *me) { return (mphash(MP_X(me))); }
+
 static PyObject *mpmeth_jacobi(PyObject *me, PyObject *arg)
 {
   mp *y = 0;
@@ -793,7 +796,7 @@ static PyGetSetDef mp_pygetset[] = {
 
 static PyMethodDef mp_pymethods[] = {
 #define METHNAME(func) mpmeth_##func
-  METH (jacobi,        "X.jacobi(Y) -> Jacobi symbol (Y/X) (NB inversion!)")
+  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")
@@ -807,14 +810,14 @@ static PyMethodDef mp_pymethods[] = {
   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) -> true/false if 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(primep,       "X.primep([rng = rand]) -> true/false if 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")
+        "X.storel2c([len = -1]) -> little-endian bytes, two's complement")
   KWMETH(storeb2c,
-        "X.storeb2c(len = -1) -> big-endian bytes, two's complement")
+        "X.storeb2c([len = -1]) -> big-endian bytes, two's complement")
   METH (tobuf,         "X.tobuf() -> buffer format")
 #undef METHNAME
   { 0 }
@@ -890,15 +893,19 @@ static PyTypeObject mp_pytype_skel = {
 
   /* @tp_doc@ */
 "Multiprecision integers, similar to `long' but more efficient and\n\
-versatile.  Support all the standard arithmetic operations.\n\
+versatile.  Support all the standard arithmetic operations, with\n\
+implicit conversions from `PrimeFilter', and other objects which\n\
+convert to `long'.\n\
 \n\
-Constructor mp(X, [radix = R]) attempts to convert X to an `mp'.  If\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 ints and longs.\n\
+if R = 0.  Other acceptable things are field elements, elliptic curve\n\
+points, group elements, Python `int' and `long' objects, and anything\n\
+with an integer conversion.\n\
 \n\
 Notes:\n\
 \n\
-  * Use `//' for integer division `/' gives exact rational division.",
+  * Use `//' for integer division: `/' gives exact rational division.",
 
   0,                                   /* @tp_traverse@ */
   0,                                   /* @tp_clear@ */
@@ -1673,7 +1680,7 @@ static PyObject *mcmeth_solve(PyObject *me, PyObject *arg)
   PyObject *q = 0, *x, *z = 0;
   mp *xx;
   mp **v = 0;
-  int i = 0, n = c->k;
+  Py_ssize_t i = 0, n = c->k;
 
   Py_INCREF(me);
   if (PyTuple_Size(arg) == n)
@@ -1682,7 +1689,8 @@ static PyObject *mcmeth_solve(PyObject *me, PyObject *arg)
     goto end;
   Py_INCREF(q);
   if (!PySequence_Check(q)) TYERR("want a sequence of residues");
-  if (PySequence_Size(q) != n) VALERR("residue count mismatch");
+  i = PySequence_Size(q); if (i < 0) goto end;
+  if (i != n) VALERR("residue count mismatch");
   v = xmalloc(n * sizeof(*v));
   for (i = 0; i < n; i++) {
     if ((x = PySequence_GetItem(q, i)) == 0) goto end;
@@ -1712,10 +1720,11 @@ static void mpcrt_pydealloc(PyObject *me)
 static PyObject *mpcrt_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
 {
   mpcrt_mod *v = 0;
-  int n, i = 0;
+  Py_ssize_t n, i = 0, j;
   static const char *const kwlist[] = { "mv", 0 };
   PyObject *q = 0, *x;
-  mp *xx;
+  mp *xx = MP_NEW, *y = MP_NEW, *g = MP_NEW;
+  mpmul mm;
   mpcrt_pyobj *c = 0;
 
   if (PyTuple_Size(arg) > 1)
@@ -1724,18 +1733,28 @@ static PyObject *mpcrt_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
     goto end;
   Py_INCREF(q);
   if (!PySequence_Check(q)) TYERR("want a sequence of moduli");
-  n = PySequence_Size(q);
-  if (PyErr_Occurred()) goto end;
+  n = PySequence_Size(q); if (n < 0) goto end;
   if (!n) VALERR("want at least one modulus");
   v = xmalloc(n * sizeof(*v));
   for (i = 0; i < n; i++) {
     if ((x = PySequence_GetItem(q, i)) == 0) goto end;
     xx = getmp(x); Py_DECREF(x); if (!xx) goto end;
-    v[i].m = xx; v[i].n = 0; v[i].ni = 0; v[i].nni = 0;
+    if (MP_CMP(xx, <=, MP_ZERO)) VALERR("moduli must be positive");
+    v[i].m = xx; v[i].n = 0; v[i].ni = 0; v[i].nni = 0; xx = MP_NEW;
   }
+  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, n, 0);
   Py_DECREF(q);
+  mp_drop(xx); mp_drop(y); mp_drop(g);
   return ((PyObject *)c);
 
 end:
@@ -1746,6 +1765,7 @@ end:
     xfree(v);
   }
   Py_XDECREF(q);
+  mp_drop(xx); mp_drop(y); mp_drop(g);
   return (0);
 }
 
@@ -1885,15 +1905,6 @@ end:
   return ((PyObject *)zz);
 }
 
-static long gf_pyhash(PyObject *me)
-{
-  long i = mp_tolong(MP_X(me));
-  i ^= 0xc7ecd67c; /* random perturbance */
-  if (i == -1)
-    i = -2;
-  return (i);
-}
-
 static PyObject *gf_pyexp(PyObject *x, PyObject *y, PyObject *z)
 {
   mp *xx = 0, *yy = 0, *zz = 0;
@@ -2006,13 +2017,13 @@ static PyMethodDef gf_pymethods[] = {
   METH (irreduciblep,  "X.irreduciblep() -> true/false")
 #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(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")
+        "X.storel2c([len = -1]) -> little-endian bytes, two's complement")
   KWMETH(storeb2c,
-        "X.storeb2c(len = -1) -> big-endian bytes, two's complement")
+        "X.storeb2c([len = -1]) -> big-endian bytes, two's complement")
   METH (tobuf,         "X.tobuf() -> buffer format")
 #undef METHNAME
   { 0 }
@@ -2076,7 +2087,7 @@ static PyTypeObject gf_pytype_skel = {
   &gf_pynumber,                                /* @tp_as_number@ */
   0,                                   /* @tp_as_sequence@ */
   0,                                   /* @tp_as_mapping@ */
-  gf_pyhash,                           /* @tp_hash@ */
+  mp_pyhash,                           /* @tp_hash@ */
   0,                                   /* @tp_call@ */
   mp_pyhex,                            /* @tp_str@ */
   0,                                   /* @tp_getattro@ */
@@ -2090,16 +2101,18 @@ static PyTypeObject gf_pytype_skel = {
 "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\
+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 ints and longs.\n\
+if R = 0.  Other acceptable things are field elements, elliptic curve\n\
+points, group elements, Python `int' and `long' objects, and anything\n\
+with an 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.",
+  * Use `//' for Euclidean division: `/' gives exact rational division.",
 
   0,                                   /* @tp_traverse@ */
   0,                                   /* @tp_clear@ */
@@ -2357,8 +2370,9 @@ static PyObject *gfn_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw)
                                   convgf, &p, convgf, &beta))
     goto end;
   gg = PyObject_New(gfn_pyobj, ty);
+  gg->p = 0;
   if (gfn_create(p, beta, &gg->ntop, &gg->pton)) {
-    FREEOBJ(gg);
+    Py_DECREF(gg);
     gg = 0;
     VALERR("can't invert transformation matrix");
   }
@@ -2390,7 +2404,7 @@ static PyObject *gfnget_beta(PyObject *me, void *hunoz)
   end:                                                                 \
     mp_drop(xx);                                                       \
     if (!z) return (0);                                                        \
-    return (mp_pywrap(z));                                             \
+    return (gf_pywrap(z));                                             \
   }
 XFORMOP(pton, PTON)
 XFORMOP(ntop, NTOP)
@@ -2398,8 +2412,11 @@ XFORMOP(ntop, NTOP)
 
 static void gfn_pydealloc(PyObject *me)
 {
-  gfn_destroy(GFN_PTON(me));
-  gfn_destroy(GFN_NTOP(me));
+  if (GFN_P(me)) {
+    MP_DROP(GFN_P(me));
+    gfn_destroy(GFN_PTON(me));
+    gfn_destroy(GFN_NTOP(me));
+  }
   FREEOBJ(me);
 }
 
@@ -2473,13 +2490,13 @@ static PyTypeObject gfn_pytype_skel = {
 static PyMethodDef methods[] = {
 #define METHNAME(func) meth_##func
   KWMETH(_MP_fromstring,       "\
-fromstring(STR, radix = 0) -> (X, REST)\n\
+fromstring(STR, [radix = 0]) -> (X, REST)\n\
 \n\
 Parse STR as a large integer, according to radix.  If radix is zero,\n\
 read a prefix from STR to decide radix: allow `0' for octal, `0x' for hex\n\
 or `R_' for other radix R.")
   KWMETH(_GF_fromstring,       "\
-fromstring(STR, radix = 0) -> (X, REST)\n\
+fromstring(STR, [radix = 0]) -> (X, REST)\n\
 \n\
 Parse STR as a binary polynomial, according to radix.  If radix is zero,\n\
 read a prefix from STR to decide radix: allow `0' for octal, `0x' for hex\n\
diff --git a/pgen.c b/pgen.c
index 18b0c26..9f9e1a2 100644 (file)
--- a/pgen.c
+++ b/pgen.c
@@ -443,6 +443,7 @@ 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);
@@ -543,7 +544,7 @@ static PyTypeObject *pgtest_pytype;
 
 static int pgev_python(int rq, pgen_event *ev, void *p)
 {
-  PyObject *py = p;
+  pypgev *pg = p;
   PyObject *pyev = 0;
   PyObject *rc = 0;
   int st = PGEN_ABORT;
@@ -551,11 +552,10 @@ static int pgev_python(int rq, pgen_event *ev, void *p)
   static const char *const meth[] =
     { "pg_abort", "pg_done", "pg_begin", "pg_try", "pg_fail", "pg_pass" };
 
-  Py_INCREF(py);
   rq++;
   if (rq > N(meth)) SYSERR("event code out of range");
   pyev = pgevent_pywrap(ev);
-  if ((rc = PyObject_CallMethod(py, (/*unconst*/ char *)meth[rq],
+  if ((rc = PyObject_CallMethod(pg->obj, (/*unconst*/ char *)meth[rq],
                                "(O)", pyev)) == 0)
     goto end;
   if (rc == Py_None)
@@ -567,12 +567,13 @@ static int pgev_python(int rq, pgen_event *ev, void *p)
   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);
-  Py_DECREF(py);
   return (st);
 }
 
@@ -587,24 +588,22 @@ static PyObject *pgev_pywrap(const pgev *pg)
 
 int convpgev(PyObject *o, void *p)
 {
-  pgev *pg = p;
+  pypgev *pg = p;
 
   if (PGEV_PYCHECK(o))
-    *pg = *PGEV_PG(o);
+    pg->ev = *PGEV_PG(o);
   else {
-    pg->proc = pgev_python;
-    pg->ctx = o;
-    Py_INCREF(o);
+    pg->ev.proc = pgev_python;
+    pg->ev.ctx = pg;
+    pg->obj = o; Py_INCREF(o);
   }
   return (1);
 }
 
-void droppgev(pgev *p)
+void droppgev(pypgev *pg)
 {
-  if (p->proc == pgev_python) {
-    PyObject *py = p->ctx;
-    Py_DECREF(py);
-  }
+  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)
@@ -635,12 +634,12 @@ static PyObject *pgev_stdev(pgen_proc *proc)
 
 static PyMethodDef pgev_pymethods[] = {
 #define METHNAME(name) pgmeth_##name
-  METH (pg_abort,      "E.pg_abort() -> PGRC -- prime generation aborted")
-  METH (pg_done,       "E.pg_done() -> PGRC -- prime generation finished")
-  METH (pg_begin,      "E.pg_begin() -> PGRC -- commence stepping/testing")
-  METH (pg_try,        "E.pg_try() -> PGRC -- found new candidate")
-  METH (pg_pass,       "E.pg_pass() -> PGRC -- passed primality test")
-  METH (pg_fail,       "E.pg_fail() -> PGRC -- failed primality test")
+  METH (pg_abort,      "E.pg_abort(EV) -> PGRC -- prime generation aborted")
+  METH (pg_done,       "E.pg_done(EV) -> PGRC -- prime generation finished")
+  METH (pg_begin,     "E.pg_begin(EV) -> PGRC -- commence stepping/testing")
+  METH (pg_try,        "E.pg_try(EV) -> PGRC -- found new candidate")
+  METH (pg_pass,       "E.pg_pass(EV) -> PGRC -- passed primality test")
+  METH (pg_fail,       "E.pg_fail(EV) -> PGRC -- failed primality test")
 #undef METHNAME
   { 0 }
 };
@@ -912,10 +911,10 @@ static PyTypeObject pgtest_pytype_skel = {
 
 /*----- Prime generation functions ----------------------------------------*/
 
-void pgenerr(void)
+void pgenerr(struct excinfo *exc)
 {
-  if (!PyErr_Occurred())
-    PyErr_SetString(PyExc_ValueError, "prime generation failed");
+  if (exc->ty) RESTORE_EXCINFO(exc);
+  else PyErr_SetString(PyExc_ValueError, "prime generation failed");
 }
 
 static PyObject *meth_pgen(PyObject *me, PyObject *arg, PyObject *kw)
@@ -926,26 +925,26 @@ static PyObject *meth_pgen(PyObject *me, PyObject *arg, PyObject *kw)
   char *p = "p";
   pgen_filterctx fc = { 2 };
   rabin tc;
-  pgev step = { 0 }, test = { 0 }, evt = { 0 };
+  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.proc = pgen_filter; step.ctx = &fc;
-  test.proc = pgen_test; test.ctx = &tc;
+  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.proc, evt.ctx,
-               nsteps, step.proc, step.ctx,
-               ntests, test.proc, test.ctx)) == 0)
-    PGENERR;
-  if (PyErr_Occurred()) goto end;
-  rc = mp_pywrap(r);
-  r = 0;
+  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);
@@ -961,19 +960,21 @@ static PyObject *meth_strongprime_setup(PyObject *me,
   unsigned nbits;
   char *name = "p";
   unsigned n = 0;
-  pgev evt = { 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.proc, evt.ctx)) == 0)
-    PGENERR;
+                            r, n, evt.ev.proc, evt.ev.ctx)) == 0)
+    PGENERR(&exc);
   rc = Py_BuildValue("(NN)", mp_pywrap(x), pfilt_pywrap(&f));
   x = 0;
 end:
@@ -989,19 +990,21 @@ static PyObject *meth_strongprime(PyObject *me, PyObject *arg, PyObject *kw)
   unsigned nbits;
   char *name = "p";
   unsigned n = 0;
-  pgev evt = { 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.proc, evt.ctx)) == 0)
-    PGENERR;
+                      r, n, evt.ev.proc, evt.ev.ctx)) == 0)
+    PGENERR(&exc);
   rc = mp_pywrap(x);
   x = 0;
 end:
@@ -1013,7 +1016,8 @@ end:
 static PyObject *meth_limlee(PyObject *me, PyObject *arg, PyObject *kw)
 {
   char *p = "p";
-  pgev ie = { 0 }, oe = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev ie = { { 0 } }, oe = { { 0 } };
   unsigned ql, pl;
   grand *r = &rand_global;
   unsigned on = 0;
@@ -1023,14 +1027,16 @@ static PyObject *meth_limlee(PyObject *me, PyObject *arg, PyObject *kw)
     { "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.proc, oe.ctx, ie.proc, ie.ctx, &nf, &v)) == 0)
-    PGENERR;
+                 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_SetItem(vec, i, mp_pywrap(v[i]));
@@ -1048,18 +1054,18 @@ static PyMethodDef methods[] = {
   METH (_PrimeFilter_smallfactor,      "smallfactor(X) -> PGRC")
   METH (_RabinMiller_iters,            "iters(NBITS) -> NITERS")
   KWMETH(pgen,                         "\
-pgen(START, [name = 'p', stepper = PrimeGenStepper(2),\n\
-     tester = PrimeGenTester(), event = pgen_nullev,\n\
-     nsteps = 0, ntests = RabinMiller.iters(START.nbits)]) -> P")
+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)")
+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")
+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, ...])")
+limlee(PBITS, QBITS, [name = 'p'], [event = pgen_nullev],\n\
+       [ievent = pgen_nullev], [rng = rand], [nsteps = 0]) -> (P, [Q, ...])")
 #undef METHNAME
   { 0 }
 };
index 9c43ca7..5680429 100644 (file)
--- a/pubkey.c
+++ b/pubkey.c
@@ -200,7 +200,7 @@ static PyMethodDef dsapub_pymethods[] = {
 
 static PyMethodDef dsapriv_pymethods[] = {
 #define METHNAME(name) dsameth_##name
-  KWMETH(sign,                 "D.sign(MSG, k = K) -> R, S")
+  KWMETH(sign,                 "D.sign(MSG, [k = K]) -> R, S")
 #undef METHNAME
   { 0 }
 };
@@ -437,7 +437,7 @@ static PyMethodDef kcdsapub_pymethods[] = {
 
 static PyMethodDef kcdsapriv_pymethods[] = {
 #define METHNAME(name) kcdsameth_##name
-  KWMETH(sign,                 "D.sign(MSG, k = K) -> R, S")
+  KWMETH(sign,                 "D.sign(MSG, [k = K]) -> R, S")
 #undef METHNAME
   { 0 }
 };
@@ -649,7 +649,7 @@ static PyObject *rsapriv_pynew(PyTypeObject *ty,
     goto end;
   if ((rp.n && !MP_ODDP(rp.n)) ||
       (rp.p && !MP_ODDP(rp.p)) ||
-      (rp.p && !MP_ODDP(rp.q)))
+      (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))
@@ -733,11 +733,13 @@ static PyObject *meth__RSAPriv_generate(PyObject *me,
   unsigned n = 0;
   rsa_priv rp;
   mp *e = 0;
-  pgev evt = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
   static const char *const kwlist[] =
     { "class", "nbits", "event", "rng", "nsteps", "e", 0 };
   PyObject *rc = 0;
 
+  evt.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&|O&O&O&O&:generate", KWLIST,
                                   &me, convuint, &nbits, convpgev, &evt,
                                   convgrand, &r, convuint, &n,
@@ -745,8 +747,8 @@ static PyObject *meth__RSAPriv_generate(PyObject *me,
     goto end;
   if (e) MP_COPY(e);
   else e = mp_fromulong(MP_NEW, 65537);
-  if (rsa_gen_e(&rp, nbits, e, r, n, evt.proc, evt.ctx))
-    PGENERR;
+  if (rsa_gen_e(&rp, nbits, e, r, n, evt.ev.proc, evt.ev.ctx))
+    PGENERR(&exc);
   rc = rsapriv_pywrap(&rp);
 end:
   droppgev(&evt);
@@ -784,7 +786,7 @@ static PyGetSetDef rsapriv_pygetset[] = {
 
 static PyMethodDef rsapriv_pymethods[] = {
 #define METHNAME(name) rsameth_##name
-  KWMETH(privop,               "R.privop(X, rng = None) -> X^D (mod N)")
+  KWMETH(privop,               "R.privop(X, [rng = None]) -> X^D (mod N)")
 #undef METHNAME
   { 0 }
 };
@@ -1269,7 +1271,7 @@ static PyMethodDef methods[] = {
   KWMETH(_pss_encode,                  0)
   KWMETH(_pss_decode,                  0)
   KWMETH(_RSAPriv_generate,            "\
-generate(NBITS, [event = pgen_nullev, rng = rand, nsteps = 0]) -> R")
+generate(NBITS, [event = pgen_nullev], [rng = rand], [nsteps = 0]) -> R")
 #define DEFMETH(X, x)                                                  \
   METH  (x,                            "\
 " #x "(KEY, PUBLIC) -> SHARED")
@@ -1279,11 +1281,11 @@ generate(NBITS, [event = pgen_nullev, rng = rand, nsteps = 0]) -> R")
   METH  (ed##_pubkey,                  "\
 " #ed "_pubkey(KEY) -> PUBLIC")                                                \
   KWMETH(ed##_sign,                    "\
-" #ed "_sign(KEY, MSG, [pub = PUBLIC, "                                        \
-        "perso = STRING, phflag = BOOL]) -> SIG")                      \
+" #ed "_sign(KEY, MSG, [pub = PUBLIC], "                               \
+        "[perso = STRING], [phflag = BOOL]) -> SIG")                   \
   KWMETH(ed##_verify,                  "\
 " #ed "_verify(PUBLIC, MSG, SIG, "                                     \
-        "[perso = STRINGphflag = BOOL]) -> BOOL")
+        "[perso = STRING], [phflag = BOOL]) -> BOOL")
   EDDSAS(DEFMETH)
 #undef DEFMETH
 #undef METHNAME
diff --git a/rand.c b/rand.c
index 964a6a2..37ab5e4 100644 (file)
--- a/rand.c
+++ b/rand.c
@@ -508,7 +508,7 @@ static PyObject *truerand_pynew(PyTypeObject *ty,
   static const char *const kwlist[] = { 0 };
   grand *r;
   PyObject *rc = 0;
-  if (PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST)) goto end;
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, ":new", KWLIST)) goto end;
   r = rand_create();
   r->ops->misc(r, RAND_NOISESRC, &noise_source);
   r->ops->misc(r, RAND_SEED, 160);
@@ -1269,7 +1269,7 @@ static PyObject *bbsget_x(PyObject *me, void *hunoz)
 
 static int bbsset_x(PyObject *me, PyObject *val, void *hunoz)
 {
-  mp *x = 0; grand *r = GRAND_R(me); int rc = -1; if (!x) NIERR("__del__");
+  mp *x = 0; grand *r = GRAND_R(me); int rc = -1; if (!val) NIERR("__del__");
   if ((x = getmp(val)) == 0) goto end;
   r->ops->misc(r, BBS_SET, x); rc = 0;
   end: mp_drop(x); return (rc);
@@ -1385,19 +1385,21 @@ static PyObject *meth__BBSPriv_generate(PyObject *me,
 {
   bbs_priv bp = { 0 };
   mp *x = MP_TWO;
-  pgev evt = { 0 };
+  struct excinfo exc = EXCINFO_INIT;
+  pypgev evt = { { 0 } };
   unsigned nbits, n = 0;
   grand *r = &rand_global;
   static const char *const kwlist[] =
     { "class", "nbits", "event", "rng", "nsteps", "seed", 0 };
   bbspriv_pyobj *rc = 0;
 
+  evt.exc = &exc;
   if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO&|O&O&O&O&:generate", KWLIST,
                                   &me, convuint, &nbits, convpgev, &evt,
                                   convgrand, &r, convuint, &n, convmp, &x))
     goto end;
-  if (bbs_gen(&bp, nbits, r, n, evt.proc, evt.ctx))
-    VALERR("prime genration failed");
+  if (bbs_gen(&bp, nbits, r, n, evt.ev.proc, evt.ev.ctx))
+    PGENERR(&exc);
   rc = PyObject_New(bbspriv_pyobj, bbspriv_pytype);
   rc->gr.r = bbs_rand(bp.n, x);
   rc->gr.f = f_freeme;
@@ -1483,7 +1485,7 @@ static PyTypeObject bbspriv_pytype_skel = {
     Py_TPFLAGS_BASETYPE,
 
   /* @tp_doc@ */
-"BBSPriv(..., seed = 2]): Blum-Blum-Shub, with private key.\n\
+"BBSPriv(..., [seed = 2]): Blum-Blum-Shub, with private key.\n\
   Keywords: n, p, q; must provide at least two",
 
   0,                                   /* @tp_traverse@ */
@@ -1512,7 +1514,8 @@ static PyTypeObject bbspriv_pytype_skel = {
 static PyMethodDef methods[] = {
 #define METHNAME(name) meth_##name
   KWMETH(_BBSPriv_generate,            "\
-generate(NBITS, [event = pgen_nullev, rng = rand, nsteps = 0, seed = 2])")
+generate(NBITS, [event = pgen_nullev], [rng = rand],\n\
+        [nsteps = 0], [seed = 2]) -> R")
 #undef METHNAME
   { 0 }
 };
index 69bf393..8ac72ab 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -26,4 +26,9 @@ MS.setup(name = 'catacomb-python',
          scripts = ['pock', 'pwsafe'],
          data_files = [('share/man/man1', ['pock.1', 'pwsafe.1'])],
          genfiles = [MS.Generate('algorithms.h')],
+         unittest_dir = "t",
+         unittests = ["t-misc", "t-algorithms", "t-bytes", "t-buffer",
+                      "t-convert", "t-ec", "t-field", "t-group", "t-key",
+                      "t-mp", "t-passphrase", "t-pgen", "t-pubkey",
+                      "t-rand", "t-rat", "t-share"],
          ext_modules = [cat])
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..d7b97cb
--- /dev/null
@@ -0,0 +1,863 @@
+### -*- 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 != 0: 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 == 0 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, ValueError),
+                    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, OverflowError, TypeError),
+                      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))
+    if hasattr(makefn(0)[0], "hashu24"):
+      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))
+    if hasattr(makefn(0)[0], "hashbuf24"):
+      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, 0)
+    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, 0)
+    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, 0)
+    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(set(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(set(ksz.set), set([7]))
+    me.assertEqual(ksz.min, 7)
+    me.assertEqual(ksz.max, 7)
+    ksz = C.KeySZSet(7, [3, 6, 9])
+    me.assertEqual(ksz.default, 7)
+    me.assertEqual(set(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)
+    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 have working `bufsz',
+    `name', or `hashsz' attributes.  This test is mostly reused for MACs,
+    which don't have these attributes.
+    """
+    ## Check the class properties.
+    if need_bufsz:
+      me.assertEqual(type(hcls.name), str)
+      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.
+    if need_bufsz: 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 ^ len(h)*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.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(set(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(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.
+    st1 = st0.copy()
+    mask = st1.extract(len(m1))
+    st0.mix(m1)
+    st1.mix(m1)
+    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))
+
+  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, lambda: x.done(100 - x.rate//2)
+    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(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 = T.bin(""):
+                     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..096e35b
--- /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, ValueError),
+                    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.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..36a3f9f
--- /dev/null
@@ -0,0 +1,169 @@
+### -*- 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.
+    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..08c660f
--- /dev/null
@@ -0,0 +1,114 @@
+### -*- 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 [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.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 [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 [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.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.assertEqual(C.MP(5) + 3, 8)
+    me.assertEqual(3 + C.MP(5), 8)
+    me.assertRaises(TypeError, T.add, C.MP(5), C.GF(3))
+    me.assertRaises(TypeError, T.add, C.GF(3), C.MP(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), kk(3))
+    me.assertRaises(TypeError, T.add, kk(3), k(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..870297e
--- /dev/null
+++ b/t/t-ec.py
@@ -0,0 +1,252 @@
+### -*- 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(TypeError, 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(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(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.assertEqual(Q - R, 11*P)
+    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(E.os2ecp(t), (P, Z0))
+    me.assertEqual(C.ReadBuffer(t).getecptraw(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(E.os2ecp(t, C.EC_LSB), (P, Z0))
+
+    ## 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([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.parse("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..e81217e
--- /dev/null
@@ -0,0 +1,343 @@
+### -*-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.assertEqual(kf.bytag("notexist"), None)
+    me.assertEqual(kf.bytag(12345), None)
+    me.assertEqual(kf.bytype("notexist"), None)
+    me.assertRaises(TypeError, kf.bytype, 12345)
+    me.assertRaises(C.KeyError, kf.byid, 0x12345678)
+
+    ## 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, 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(kf[0x11111111].id, 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)
+
+###--------------------------------------------------------------------------
+
+def keydata_equalp(kd0, kd1):
+  if type(kd0) is not type(kd1): return False
+  elif type(kd0) is C.KeyDataBinary: return kd0.bin == kd1.bin
+  elif type(kd0) is C.KeyDataMP: return kd0.mp == kd1.mp
+  elif type(kd0) is C.KeyDataEncrypted: return kd0.ct == kd1.ct
+  elif type(kd0) is C.KeyDataECPt: return kd0.ecpt == kd1.ecpt
+  elif type(kd0) is C.KeyDataString: return kd0.str == kd1.str
+  elif type(kd0) is C.KeyDataStructured:
+    if len(kd0) != len(kd1): return False
+    for t, v0 in T.iteritems(kd0):
+      try: v1 = kd1[t]
+      except KeyError: return False
+      if not keydata_equalp(v0, v1): return False
+    return True
+  else:
+    raise SystemError("unexpected keydata type")
+
+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.assertTrue(keydata_equalp(C.KeyData.decode(kd.encode()), kd))
+    kd1, tail = C.KeyData.read(kd.write())
+    me.assertEqual(tail, "")
+    me.assertTrue(keydata_equalp(kd, kd1))
+
+  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" })
+
+###--------------------------------------------------------------------------
+### 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.check_immutable_mapping(kf, model)
+
+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..ff373ca
--- /dev/null
+++ b/t/t-mp.py
@@ -0,0 +1,561 @@
+### -*-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('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('47353320450112516611472622536175135706501', 8), big)
+
+    me.assertEqual(C.MP('0b100111011011001100000010001011'), 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(6556380541834372447694561492436749633L)')
+    me.assertEqual(hex(y), '0x4eeb684a0954ec4ceb255e3e9778d41')
+    me.assertEqual(oct(y), '047353320450112516611472622536175135706501')
+
+  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_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([(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([(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([(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.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('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('47353320450112516611472622536175135706501', 8), y)
+
+    t = C.GF(661438603)
+    me.assertEqual(C.GF('0b100111011011001100000010001011'), 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(0x4eeb684a0954ec4ceb255e3e9778d41L)')
+    me.assertEqual(hex(y), '0x4eeb684a0954ec4ceb255e3e9778d41')
+    me.assertEqual(oct(y), '047353320450112516611472622536175135706501')
+
+  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..9cc80a9
--- /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 = hex(C.rand.block(8))
+    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..dd66903
--- /dev/null
@@ -0,0 +1,228 @@
+### -*-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]:F17/P6/D][p:F60/P26/D]")
+
+###----- 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..d8d7b00
--- /dev/null
@@ -0,0 +1,165 @@
+### -*-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)
+
+  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)
+    me.check_rand(rng)
+    me.assertEqual(rng.seed, (n + 153 + 3).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..9fd15d4
--- /dev/null
@@ -0,0 +1,94 @@
+### -*-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)
+
+  def test_intrat(me):
+    me.check_rat(C.IntRat)
+
+    ## Check string conversions.
+    u = C.MP(5)/C.MP(4)
+    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.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..42bbd76
--- /dev/null
@@ -0,0 +1,275 @@
+### -*- 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.
+import itertools as I
+def bin(x): 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])
+
+Z64 = C.ByteString.zero(8)
+def detrand(seed):
+  """Return a fast deterministic random generator with the given SEED."""
+  return C.chacha8rand(C.sha256().hash(bin(seed)).done(), Z64)
+
+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)
+      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)
+    me.assertFalse(map.has_key(me._mkkey(limk)))
+    me.assertRaises(KeyError, lambda: map[me._mkkey(limk)])
+    me.assertEqual(map.get(me._mkkey(limk)), None)
+    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)
+
+    def check_views():
+      me.check_immutable_mapping(map, model)
+
+    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 --------------------------------------------------
diff --git a/util.c b/util.c
index 2e6ee25..723c819 100644 (file)
--- a/util.c
+++ b/util.c
 
 #include "catacomb-python.h"
 
+/* #undef HAVE_LONG_LONG */
+
 /*----- External values ---------------------------------------------------*/
 
 static PyObject *modname = 0;
+PyObject *home_module = 0;
 
 /*----- Conversions -------------------------------------------------------*/
 
@@ -42,12 +45,17 @@ PyObject *getulong(unsigned long w)
     return (PyLong_FromUnsignedLong(w));
 }
 
+#ifndef HAVE_LONG_LONG
 static PyObject *i32 = 0;
 static int init_i32(void)
   { if (!i32 && (i32 = PyInt_FromLong(32)) == 0) return (-1); return (0); }
+#endif
 
 PyObject *getk64(kludge64 u)
 {
+#ifdef HAVE_LONG_LONG
+  return (PyLong_FromUnsignedLongLong(GET64(unsigned PY_LONG_LONG, u)));
+#else
   PyObject *i = 0, *j = 0, *t;
   PyObject *rc = 0;
 
@@ -63,6 +71,7 @@ end:
   if (i) Py_DECREF(i);
   if (j) Py_DECREF(j);
   return (rc);
+#endif
 }
 
 PyObject *getbool(int b)
@@ -99,15 +108,32 @@ end:
   return (0);
 }
 
+#ifdef HAVE_UINT64
+#  define CONVu64(n) do {                                              \
+     kludge64 k;                                                       \
+     uint64 t;                                                         \
+     if (!convk64(o, &k)) goto end;                                    \
+     t = GET64(uint64, k);                                             \
+     if (t > MASK##n) VALERR("out of range");                          \
+     *p = t;                                                           \
+   } while (0)
+#else
+#  define CONVu64(n) assert(!"shouldn't be possible")
+#endif
+
 #define CONVU_(n)                                                      \
   int convu##n(PyObject *o, void *pp)                                  \
   {                                                                    \
     unsigned long u;                                                   \
     uint##n *p = pp;                                                   \
                                                                        \
-    if (!convulong(o, &u)) goto end;                                   \
-    if (u > MASK##n) VALERR("out of range");                           \
-    *p = u;                                                            \
+    if (MASK##n > ULONG_MAX)                                           \
+      CONVu64(n);                                                      \
+    else {                                                             \
+      if (!convulong(o, &u)) goto end;                                 \
+      if (u > MASK##n) VALERR("out of range");                         \
+      *p = u;                                                          \
+    }                                                                  \
     return (1);                                                                \
   end:                                                                 \
     return (0);                                                                \
@@ -129,11 +155,22 @@ end:
 
 int convk64(PyObject *o, void *pp)
 {
-  PyObject *i = 0, *t;
+  PyObject *i = 0;
   int rc = 0;
+#if HAVE_LONG_LONG
+  unsigned PY_LONG_LONG t;
+#else
+  PyObject *t;
   uint32 lo, hi;
+#endif
 
   if (!o) VALERR("can't delete");
+#if HAVE_LONG_LONG
+  if ((i = PyNumber_Long(o)) == 0) goto end;
+  t = PyLong_AsUnsignedLongLong(i);
+  if (t == (unsigned PY_LONG_LONG)-1 && PyErr_Occurred()) goto end;
+  ASSIGN64(*(kludge64 *)pp, t);
+#else
   if (init_i32()) goto end;
   if ((i = PyNumber_Int(o)) == 0) goto end;
   lo = PyInt_AsUnsignedLongMask(i);
@@ -144,7 +181,9 @@ int convk64(PyObject *o, void *pp)
   Py_DECREF(i); i = t;
   if (PyObject_IsTrue(i)) VALERR("out of range");
   SET64(*(kludge64 *)pp, hi, lo);
+#endif
   rc = 1;
+
 end:
   if (i) Py_DECREF(i);
   return (rc);
@@ -241,14 +280,14 @@ PyTypeObject *inittype(PyTypeObject *tyskel, PyTypeObject *meta)
 void setconstants(PyObject *mod, const struct nameval *c)
 {
   PyObject *x;
+  unsigned long u;
 
   while (c->name) {
-    if (c->value > LONG_MAX)
-      x = PyLong_FromUnsignedLong(c->value);
-    else
-      x = PyInt_FromLong(c->value);
-    PyModule_AddObject(mod, (/*unconst*/ char *)c->name, x);
-    c++;
+    u = c->value;
+    if (u <= LONG_MAX) x = PyInt_FromLong(u);
+    else if (c->f&CF_SIGNED) x = PyInt_FromLong(-1 - (long)(ULONG_MAX - u));
+    else x = PyLong_FromUnsignedLong(u);
+    PyModule_AddObject(mod, (/*unconst*/ char *)c->name, x); c++;
   }
 }
 
@@ -285,13 +324,7 @@ PyObject *mkexc(PyObject *mod, PyObject *base,
   PyObject *func = 0;
   PyObject *meth = 0;
 
-  if ((nameobj = PyString_FromFormat("%s.%s",
-                                    PyModule_GetName(mod),
-                                    name)) == 0 ||
-      (dict = PyDict_New()) == 0 ||
-      (exc = PyErr_NewException(PyString_AS_STRING(nameobj),
-                               base, dict)) == 0)
-    goto fail;
+  if ((dict = PyDict_New()) == 0) goto fail;
 
   if (mm) {
     while (mm->ml_name) {
@@ -305,6 +338,13 @@ PyObject *mkexc(PyObject *mod, PyObject *base,
     }
   }
 
+  if ((nameobj = PyString_FromFormat("%s.%s",
+                                    PyModule_GetName(mod),
+                                    name)) == 0 ||
+      (exc = PyErr_NewException(PyString_AS_STRING(nameobj),
+                               base, dict)) == 0)
+    goto fail;
+
 done:
   Py_XDECREF(nameobj);
   Py_XDECREF(dict);
@@ -318,6 +358,101 @@ fail:
   goto done;
 }
 
+void report_lost_exception_v(struct excinfo *exc,
+                            const char *why, va_list ap)
+{
+  PyObject *hookfn = 0;
+  PyObject *whyobj = 0;
+  PyObject *obj = 0;
+
+  /* Make sure we start out without a pending exception, or this will get
+   * really confusing.
+   */
+  assert(!PyErr_Occurred());
+
+  /* Format the explanation. */
+  if (why) whyobj = PyString_FromFormatV(why, ap);
+  else { whyobj = Py_None; Py_INCREF(whyobj); }
+
+  /* Find our home module's `lostexchook' function.  This won't work if
+   * there's no module, or the function isn't defined, or it's `None'.
+   */
+  if (!home_module) goto sys;
+  hookfn = PyObject_GetAttrString(home_module, "lostexchook");
+  if (hookfn == Py_None) goto sys;
+  else if (hookfn) ;
+  else if (!PyErr_ExceptionMatches(PyExc_AttributeError)) goto ouch;
+  else { PyErr_Clear(); goto sys; }
+
+  /* Call the hook function. */
+  obj = PyObject_CallFunction(hookfn, "(OOOO)",
+                             whyobj, exc->ty, exc->val, exc->tb);
+  if (!obj) goto ouch;
+  goto end;
+
+  /* Something went wrong reporting the problem. */
+ouch:
+  PySys_WriteStderr("\n!!! FAILURE REPORTING LOST EXCEPTION\n");
+  PyErr_Print();
+  /* drop through... */
+
+  /* There was no hook, so try to do something sensible using
+   * `sys.excepthook'.
+   */
+sys:
+  PySys_WriteStderr("\n!!! LOST EXCEPTION: %s\n",
+                   PyString_AS_STRING(whyobj));
+  RESTORE_EXCINFO(exc);
+  PyErr_Print();
+  /* drop through... */
+
+  /* Clean up afterwards. */
+end:
+  Py_XDECREF(hookfn);
+  Py_XDECREF(whyobj);
+  Py_XDECREF(obj);
+}
+
+void report_lost_exception(struct excinfo *exc, const char *why, ...)
+{
+  va_list ap;
+
+  va_start(ap, why);
+  report_lost_exception_v(exc, why, ap);
+  va_end(ap);
+}
+
+void stash_exception(struct excinfo *exc, const char *why, ...)
+{
+  va_list ap;
+  struct excinfo stash;
+
+  if (!exc->ty)
+    STASH_EXCINFO(exc);
+  else {
+    va_start(ap, why);
+    STASH_EXCINFO(&stash);
+    report_lost_exception_v(&stash, why, ap);
+    va_end(ap);
+  }
+}
+
+void restore_exception(struct excinfo *exc, const char *why, ...)
+{
+  va_list ap;
+  struct excinfo stash;
+
+  if (!PyErr_Occurred())
+    RESTORE_EXCINFO(exc);
+  else {
+    va_start(ap, why);
+    STASH_EXCINFO(&stash);
+    report_lost_exception_v(exc, why, ap);
+    RESTORE_EXCINFO(&stash);
+    va_end(ap);
+  }
+}
+
 /*----- Generic dictionary methods ----------------------------------------*/
 
 static PyTypeObject *itemiter_pytype, *valiter_pytype;
@@ -369,7 +504,7 @@ static PyTypeObject itemiter_pytype_skel = {
     Py_TPFLAGS_BASETYPE,
 
   /* @tp_doc@ */
-"Iterates over the items of a mapping.",
+"Iterates over the keys of a mapping.",
 
   0,                                   /* @tp_traverse@ */
   0,                                   /* @tp_clear@ */
@@ -427,7 +562,7 @@ static PyTypeObject valiter_pytype_skel = {
     Py_TPFLAGS_BASETYPE,
 
   /* @tp_doc@ */
-"Iterates over the items of a mapping.",
+"Iterates over the values of a mapping.",
 
   0,                                   /* @tp_traverse@ */
   0,                                   /* @tp_clear@ */
@@ -466,8 +601,7 @@ PySequenceMethods gmap_pysequence = {
 Py_ssize_t gmap_pysize(PyObject *me)
 {
   PyObject *i = 0, *x = 0;
-  int rc = -1;
-  int n = 0;
+  Py_ssize_t rc = -1, n = 0;
 
   if ((i = PyObject_GetIter(me)) == 0) goto done;
   while ((x = PyIter_Next(i)) != 0) { n++; Py_DECREF(x); x = 0; }
@@ -609,7 +743,7 @@ PyObject *gmapmeth_get(PyObject *me, PyObject *arg, PyObject *kw)
 {
   PyObject *k, *def = Py_None, *v;
 
-  if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO:get",
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:get",
                                   (/*unconst*/ char **)def_kwlist,
                                   &k, &def))
     return (0);
@@ -622,7 +756,7 @@ PyObject *gmapmeth_setdefault(PyObject *me, PyObject *arg, PyObject *kw)
 {
   PyObject *k, *def = Py_None, *v;
 
-  if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO:setdefault",
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:setdefault",
                                   (/*unconst*/ char **)def_kwlist,
                                   &k, &def))
     return (0);
@@ -636,16 +770,18 @@ PyObject *gmapmeth_pop(PyObject *me, PyObject *arg, PyObject *kw)
 {
   PyObject *k, *def = 0, *v;
 
-  if (!PyArg_ParseTupleAndKeywords(arg, kw, "OO:pop",
+  if (!PyArg_ParseTupleAndKeywords(arg, kw, "O|O:pop",
                                   (/*unconst*/ char **)def_kwlist,
                                   &k, &def))
     return (0);
   if ((v = PyObject_GetItem(me, k)) != 0) {
     PyObject_DelItem(me, k);
     return (v);
-  }
-  PyErr_Clear();
-  RETURN_OBJ(def);
+  } else if (def) {
+    PyErr_Clear();
+    RETURN_OBJ(def);
+  } else
+    return (0);
 }
 
 PyObject *gmapmeth_update(PyObject *me, PyObject *arg)
@@ -675,7 +811,7 @@ PyObject *gmapmeth_popitem(PyObject *me, PyObject *arg)
   PyObject *i = 0, *k = 0, *v = 0, *rc = 0;
 
   if (!PyArg_ParseTuple(arg, ":popitem") ||
-      (i = PyObject_GetIter(me)))
+      (i = PyObject_GetIter(me)) == 0)
     goto end;
   if ((k = PyIter_Next(i)) == 0) {
     if (!PyErr_Occurred()) VALERR("popitem(): mapping is empty");
@@ -697,11 +833,29 @@ PyMethodDef gmap_pymethods[] = {
 
 /*----- Initialization ----------------------------------------------------*/
 
+static PyObject *meth__set_home_module(PyObject *me, PyObject *arg)
+{
+  PyObject *mod;
+
+  if (!PyArg_ParseTuple(arg, "O!:_set_home_module", &PyModule_Type, &mod))
+    return (0);
+  Py_XDECREF(home_module); home_module = mod; Py_INCREF(home_module);
+  RETURN_NONE;
+}
+
+static const PyMethodDef methods[] = {
+#define METHNAME(func) meth_##func
+  METH (_set_home_module,      "_set_home_module(MOD)")
+#undef METHNAME
+  { 0 }
+};
+
 void util_pyinit(void)
 {
   modname = PyString_FromString("catacomb");
   INITTYPE(itemiter, root);
   INITTYPE(valiter, root);
+  addmethods(methods);
 }
 
 void util_pyinsert(PyObject *mod)