chiark / gitweb /
t/: Add a test suite.
[catacomb-python] / t / t-ec.py
diff --git a/t/t-ec.py b/t/t-ec.py
new file mode 100644 (file)
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()