From: Mark Wooding Date: Wed, 27 Nov 2019 15:12:23 +0000 (+0000) Subject: Merge branch '1.3.x' X-Git-Url: https://git.distorted.org.uk/~mdw/catacomb-python/commitdiff_plain/740847afe208bb8f33e7d6cf642acaf4aa739f6a?hp=204416123385794c92b7767e7702c0d4fd387468 Merge branch '1.3.x' * 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. ... --- diff --git a/.gitignore b/.gitignore index 98bd1e8..a3d0fc5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ auto-version mdwsetup.py *.pyc pysetup.mk +/t/keyring.old diff --git a/MANIFEST.in b/MANIFEST.in index 36bf4c2..33ce54a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -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 diff --git a/algorithms.c b/algorithms.c index 2a3bece..888ceb0 100644 --- a/algorithms.c +++ b/algorithms.c @@ -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) diff --git a/algorithms.py b/algorithms.py index 4b65ce9..5730c45 100644 --- a/algorithms.py +++ b/algorithms.py @@ -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, ' + diff --git a/buffer.c b/buffer.c index b88232b..5b7bb64 100644 --- 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)") diff --git a/bytestring.c b/bytestring.c index a7fe5cb..82d117f 100644 --- a/bytestring.c +++ b/bytestring.c @@ -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; } diff --git a/catacomb-python.h b/catacomb-python.h index 5815343..bf3d426 100644 --- a/catacomb-python.h +++ b/catacomb-python.h @@ -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 -------------------------------------------------*/ diff --git a/catacomb.c b/catacomb.c index 572f80f..e868f0f 100644 --- a/catacomb.c +++ b/catacomb.c @@ -31,14 +31,16 @@ /*----- 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; diff --git a/catacomb/__init__.py b/catacomb/__init__.py index 8038815..65695bb 100644 --- a/catacomb/__init__.py +++ b/catacomb/__init__.py @@ -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 _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)) hashu16 = hashu16b - def hashu32l(me, n): return me.hash(_pack('L', n)) + def hashu32l(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 hashu64l(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: diff --git a/catacomb/pwsafe.py b/catacomb/pwsafe.py index cfbac7e..03ed685 100644 --- a/catacomb/pwsafe.py +++ b/catacomb/pwsafe.py @@ -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') diff --git a/debian/changelog b/debian/changelog index fad1029..66c14f7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -27,6 +27,12 @@ catacomb-python (1.3.0) experimental; urgency=medium -- Mark Wooding 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 Mon, 24 Dec 2018 15:21:08 +0000 + catacomb-python (1.2.1) experimental; urgency=low * Fix use-after-free bug in ECPt hashing causing hash instability. diff --git a/debian/control b/debian/control index 84a43a0..7b7400d 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: python Priority: extra XS-Python-Version: >= 2.6, << 2.8 Maintainer: Mark Wooding -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 diff --git a/debian/rules b/debian/rules index 4339423..04d6f4e 100755 --- a/debian/rules +++ b/debian/rules @@ -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 --- 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 --- 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 --- 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_nullev, nsteps = 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 --- 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 = ) -> STRING") - KWMETH(encode, "KD.encode(filter = ) -> BYTES") - KWMETH(copy, "KD.copy(filter = ) -> KD") + KWMETH(write, "KD.write([filter = ]) -> STRING") + KWMETH(encode, "KD.encode([filter = ]) -> BYTES") + KWMETH(copy, "KD.copy([filter = ]) -> 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 = ])") + 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 = )") - KWMETH(newkey, "KF.newkey(ID, TYPE, exptime = KEXP_FOREVER) -> KEY") + KWMETH(merge, "KF.merge(FILE, [report = ])") + 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 --- 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 --- 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 } }; diff --git a/pubkey.c b/pubkey.c index 9c43ca7..5680429 100644 --- 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 = STRING, phflag = 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 --- 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 } }; diff --git a/setup.py b/setup.py index 69bf393..8ac72ab 100755 --- 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 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 index 0000000..d7b97cb --- /dev/null +++ b/t/t-algorithms.py @@ -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 index 0000000..096e35b --- /dev/null +++ b/t/t-buffer.py @@ -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 index 0000000..36a3f9f --- /dev/null +++ b/t/t-bytes.py @@ -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 index 0000000..08c660f --- /dev/null +++ b/t/t-convert.py @@ -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 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 index 0000000..3871b45 --- /dev/null +++ b/t/t-field.py @@ -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 index 0000000..e91de94 --- /dev/null +++ b/t/t-group.py @@ -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 index 0000000..e81217e --- /dev/null +++ b/t/t-key.py @@ -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, "") + 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 index 0000000..18a3170 --- /dev/null +++ b/t/t-misc.py @@ -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 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 index 0000000..9cc80a9 --- /dev/null +++ b/t/t-passphrase.py @@ -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 index 0000000..dd66903 --- /dev/null +++ b/t/t-pgen.py @@ -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 index 0000000..5b930b0 --- /dev/null +++ b/t/t-pubkey.py @@ -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 index 0000000..d8d7b00 --- /dev/null +++ b/t/t-rand.py @@ -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 index 0000000..9fd15d4 --- /dev/null +++ b/t/t-rat.py @@ -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 index 0000000..089d2ff --- /dev/null +++ b/t/t-share.py @@ -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 index 0000000..42bbd76 --- /dev/null +++ b/t/testutils.py @@ -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 --- a/util.c +++ b/util.c @@ -28,9 +28,12 @@ #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)