From 553d59fec08fd9102b21fd0dc83ba708862a6090 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Sat, 16 Nov 2019 18:53:24 +0000 Subject: [PATCH] t/: Add a test suite. It's fairly substantial, but far from complete. It's also a little strange in places because it's been sent backwards in time from the future. When building the Debian package, run the tests verbosely, so that if the test crashes I stand a chance of figuring out where. --- .gitignore | 1 + MANIFEST.in | 1 + debian/rules | 3 + setup.py | 5 + t/keyring | 4 + t/t-algorithms.py | 576 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ t/t-buffer.py | 251 ++++++++++++++++++++++++ t/t-bytes.py | 162 +++++++++++++++ t/t-convert.py | 114 +++++++++++ t/t-ec.py | 250 ++++++++++++++++++++++++ t/t-field.py | 150 ++++++++++++++ t/t-group.py | 255 ++++++++++++++++++++++++ t/t-key.py | 343 ++++++++++++++++++++++++++++++++ t/t-misc.py | 41 ++++ t/t-mp.py | 561 ++++++++++++++++++++++++++++++++++++++++++++++++++++ t/t-passphrase.py | 90 +++++++++ t/t-pgen.py | 228 +++++++++++++++++++++ t/t-pubkey.py | 192 ++++++++++++++++++ t/t-rand.py | 165 ++++++++++++++++ t/t-rat.py | 94 +++++++++ t/t-share.py | 68 +++++++ t/testutils.py | 275 ++++++++++++++++++++++++++ 22 files changed, 3829 insertions(+) create mode 100644 t/keyring create mode 100644 t/t-algorithms.py create mode 100644 t/t-buffer.py create mode 100644 t/t-bytes.py create mode 100644 t/t-convert.py create mode 100644 t/t-ec.py create mode 100644 t/t-field.py create mode 100644 t/t-group.py create mode 100644 t/t-key.py create mode 100644 t/t-misc.py create mode 100644 t/t-mp.py create mode 100644 t/t-passphrase.py create mode 100644 t/t-pgen.py create mode 100644 t/t-pubkey.py create mode 100644 t/t-rand.py create mode 100644 t/t-rat.py create mode 100644 t/t-share.py create mode 100644 t/testutils.py 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 e9531eb..c09417b 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 include pwsafe diff --git a/debian/rules b/debian/rules index f718ebd..04d6f4e 100755 --- a/debian/rules +++ b/debian/rules @@ -3,5 +3,8 @@ 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/setup.py b/setup.py index 1f45f69..80a1547 100755 --- a/setup.py +++ b/setup.py @@ -25,4 +25,9 @@ MS.setup(name = 'catacomb-python', packages = ['catacomb'], scripts = ['pwsafe'], 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..8e073f2 --- /dev/null +++ b/t/t-algorithms.py @@ -0,0 +1,576 @@ +### -*- 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) + + ## 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) + + ## 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) + + ## 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 in [(3, None), (4, 4), (5, 4), + (15, 12), (16, 16), (17, 16), + (31, 28), (32, 32), (33, 32)]: + if x == best: 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) + + ## 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 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 [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 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), C.ByteString(m) ^ C.ByteString(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..24823c6 --- /dev/null +++ b/t/t-buffer.py @@ -0,0 +1,251 @@ +### -*- 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(C.ByteString(buf), T.span(w + 2)) + me.assertEqual(C.ByteString(putfn(C.WriteBuffer(), (1 << 8*w) - 1)), + w*C.bytes("ff")) + me.assertEqual(C.ByteString(putfn(C.WriteBuffer(), C.MP(0))), + 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(C.ByteString(putfn(C.WriteBuffer().putu8(0x00), + T.span(n)).putu8(0xff)), + 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(C.ByteString(buf), 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..6755e96 --- /dev/null +++ b/t/t-bytes.py @@ -0,0 +1,162 @@ +### -*- 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(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. + 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(x + y, C.bytes("03a5fc5fac30")) + + ## Replication (asymmetric but commutative). + me.assertEqual(3*x, C.bytes("03a5fc03a5fc03a5fc")) + me.assertEqual(x*3, C.bytes("03a5fc03a5fc03a5fc")) + + ## Replication by zero (regression test). + 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..ef80b90 --- /dev/null +++ b/t/t-ec.py @@ -0,0 +1,250 @@ +### -*- 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.ByteString(C.WriteBuffer() + .putu8(0x04) + .put(P.ix.storeb(k.noctets)) + .put(P.iy.storeb(k.noctets))) + me.assertEqual(P.ec2osp(), t) + me.assertEqual(C.ByteString(C.WriteBuffer().putecptraw(P)), 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.ByteString(C.WriteBuffer() + .putu8(0x02 | ybit) + .put(P.ix.storeb(k.noctets))) + 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..203ca16 --- /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.ByteString(C.WriteBuffer().putmp(i))) + 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.ByteString(C.WriteBuffer().zero(G.noctets - 1).putu8(1))) + + ## 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 -------------------------------------------------- -- 2.11.0