math/: Support EC2OSP and OS2ECP operations, with point compression.
[catacomb] / utils / ec-compr-test.sage
diff --git a/utils/ec-compr-test.sage b/utils/ec-compr-test.sage
new file mode 100644 (file)
index 0000000..08ba525
--- /dev/null
@@ -0,0 +1,220 @@
+#! /usr/local/bin/sage
+### -*-python-*-
+
+from itertools import count, izip
+from random import randrange
+
+## Constants.
+EC_YBIT = EC_XONLY = 1
+EC_CMPR = EC_LSB = 2
+EC_EXPLY = 4
+EC_SORT = 8
+
+## Conversion primitives.
+def poly_to_int(x, p):
+  return ZZ(sum([ZZ(a)*p**i for a, i in izip(x.list(), count())]))
+def int_to_poly(n, p, t):
+  return sum([a*t**i for a, i in izip(n.digits(p), count())])
+
+def fe2ip(x):
+  return poly_to_int(x.polynomial(), x.parent().characteristic())
+def i2fep(n, k):
+  return int_to_poly(n, k.characteristic(), k.gen())
+
+def fe2osp(x):
+  k = x.parent()
+  n = fe2ip(x)
+  nb = ceil((k.cardinality() - 1).nbits()/8)
+  v = n.digits(256, padto = nb); v.reverse(); return v
+
+def os2sp(v):
+  return ''.join('%02x' % i for i in v)
+
+def fe2sp(x):
+  if x == 0 or x == 1:
+    return '%d' % x
+  if x.parent().degree() > 1:
+    return '0x%x' % fe2ip(x)
+  else:
+    n = ZZ(x + 99)
+    if n < 199: return '%d' % (n - 99)
+    else: return '0x%x' % x
+
+def os_to_hex(v):
+  return ''.join('%02x' % b for b in v)
+
+## Other utilities.
+def find_2torsion_point(E, n):
+  while is_even(n): n //= 2
+  while True:
+    Q = E.random_point()
+    R = n*Q
+    if R: break
+  while True:
+    S = 2*R
+    if not S: break
+    R = S
+  return R
+
+def pick_at_random(choices):
+  return choices[randrange(len(choices))]
+
+## Output an elliptic curve description.
+def ecdescr(E):
+  k = E.base_ring()
+  m = k.degree()
+  if m == 1:
+    fdesc = '%s: %d' % (pick_at_random(['prime', 'niceprime']),
+                        k.characteristic())
+    cdesc = '%s: %s, %s' % (pick_at_random(['prime', 'primeproj']),
+                            fe2sp(E.a4()), fe2sp(E.a6()))
+  elif k.characteristic() == 2:
+    fdesc = '%s: 0x%x' % ('binpoly', poly_to_int(k.polynomial(), 2))
+    cdesc = '%s: %s, %s' % (pick_at_random(['bin', 'binproj']),
+                            fe2sp(E.a2()), fe2sp(E.a6()))
+  else:
+    raise ValueError, 'only prime or binary fields supported'
+  return '"%s; %s"' % (fdesc, cdesc)
+
+## Output a point description.
+def ecpt(Q):
+  if Q is None: return 'FAIL'
+  elif not Q: return 'inf'
+  else: return '"%s, %s"' % (fe2sp(Q[0]), fe2sp(Q[1]))
+
+## Compress a point.
+def ptcompr_lsb(Q):
+  x, y = Q[0], Q[1]
+  if Q.curve().base_ring().characteristic() != 2:
+    return is_odd(fe2ip(y)) and EC_YBIT or 0
+  elif x == 0:
+    return 0
+  else:
+    return (y/x).polynomial()[0] == 1 and EC_YBIT or 0
+
+def ptcompr_sort(Q):
+  y = fe2ip(Q[1]); yy = fe2ip((-Q)[1])
+  return y > yy and EC_YBIT or 0
+
+## Some elliptic curves.  Chosen to have 2-torsion, so as to test edge cases
+## where Q = -Q; in particular, 2-torsion points can't have their
+## y-coordinates compressed to 1.
+p = previous_prime(2^192); k_p = GF(p)
+E_p = EllipticCurve(k_p, [-3, 6])
+n_p = 6277101735386680763835789423293484020607766684840576538738
+T_p = find_2torsion_point(E_p, n_p)
+
+k_2.<t> = GF(2^191)
+E_2 = EllipticCurve(k_2, [1, 1, 0, 0, 1])
+n_2 = 3138550867693340381917894711593325610042432240066118385966
+T_2 = find_2torsion_point(E_2, n_2)
+
+def test_ec2osp(E, Q):
+  ec = ecdescr(E)
+  pt = ecpt(Q)
+  xs, ys = fe2osp(Q[0]), fe2osp(Q[1])
+  ybit_lsb = ptcompr_lsb(Q)
+  ybit_sort = ptcompr_sort(Q)
+
+  def out(f, s):
+    print '  %s\n    %d %s\n    %s;' % (ec, f, pt, os_to_hex(s))
+
+  out(EC_XONLY, [EC_XONLY] + xs)
+  out(EC_CMPR, [EC_CMPR | ybit_lsb] + xs)
+  out(EC_CMPR | EC_SORT, [EC_CMPR | EC_SORT | ybit_sort] + xs)
+  out(EC_EXPLY, [EC_EXPLY] + xs + ys)
+  out(EC_CMPR | EC_EXPLY, [EC_CMPR | EC_EXPLY | ybit_lsb] + xs + ys)
+  out(EC_CMPR | EC_SORT | EC_EXPLY,
+      [EC_CMPR | EC_SORT | EC_EXPLY | ybit_sort] + xs + ys)
+
+print 'ec2osp {'
+for i in xrange(20): test_ec2osp(E_p, E_p.random_point())
+test_ec2osp(E_p, T_p)
+for i in xrange(20): test_ec2osp(E_2, E_2.random_point())
+test_ec2osp(E_2, T_2)
+print '}'
+
+def test_os2ecp(E, Q):
+  ec = ecdescr(E)
+  k = E.base_ring()
+  m = k.degree()
+  x, y = Q[0], Q[1]
+  xs, ys = fe2osp(Q[0]), fe2osp(Q[1])
+  ybit_lsb = ptcompr_lsb(Q)
+  ybit_sort = ptcompr_sort(Q)
+
+  def out(f, s, Q):
+    sufflen = randrange(16)
+    suff = [randrange(256) for i in xrange(sufflen)]
+    print '  %s\n    %d\n    %s\n    %s\n    %d;' % (
+      ec, f, os_to_hex(s + suff), ecpt(Q), sufflen)
+
+  ## This is the algorithm from `gfreduce_quadsolve'.
+  B = x^3 + E.a2()*x^2 + E.a4()*x + E.a6()
+  A = E.a1()*x + E.a3()
+  if A == 0:
+    yy = sqrt(B)
+  else:
+    xx = B/A^2
+    while True:
+      rho = k.random_element()
+      if rho.trace() == 1: break
+    z = sum(rho^(2^i)*sum(xx^(2^j) for j in xrange(i)) for i in xrange(m))
+    if z.polynomial()[0]: z -= 1
+    yy = A*z
+
+  out(EC_XONLY, [EC_XONLY] + xs, E(x, yy))
+  out(EC_EXPLY, [EC_EXPLY] + xs + ys, Q)
+
+  out(EC_LSB, [EC_CMPR | ybit_lsb] + xs, Q)
+  out(EC_LSB | EC_EXPLY, [EC_EXPLY | EC_CMPR | ybit_lsb] + xs + ys, Q)
+  out(EC_LSB | EC_EXPLY,
+      [EC_EXPLY | EC_CMPR | (not ybit_lsb)] + xs + ys, None)
+
+  out(EC_SORT, [EC_CMPR | EC_SORT | ybit_sort] + xs, Q)
+  out(EC_SORT | EC_EXPLY,
+      [EC_EXPLY | EC_CMPR | EC_SORT | ybit_sort] + xs + ys, Q)
+  out(EC_SORT | EC_EXPLY,
+      [EC_EXPLY | EC_CMPR | EC_SORT | (not ybit_sort)] + xs + ys, None)
+
+def test_os2ecp_2torsion(E, Q):
+  ec = ecdescr(E)
+  k = E.base_ring()
+  m = k.degree()
+  x, y = Q[0], Q[1]
+  xs, ys = fe2osp(Q[0]), fe2osp(Q[1])
+
+  def out(f, s, Q):
+    sufflen = randrange(16)
+    suff = [randrange(256) for i in xrange(sufflen)]
+    print '  %s\n    %d\n    %s\n    %s\n    %d;' % (
+      ec, f, os_to_hex(s + suff), ecpt(Q), sufflen)
+
+  out(EC_LSB, [EC_CMPR] + xs, Q)
+  out(EC_LSB, [EC_CMPR | EC_YBIT] + xs, None)
+  out(EC_SORT, [EC_SORT | EC_CMPR] + xs, Q)
+  out(EC_SORT, [EC_SORT | EC_CMPR | EC_YBIT] + xs, None)
+
+def test_os2ecp_notpoints(E):
+  ec = ecdescr(E)
+  k = E.base_ring()
+
+  def out(f, s):
+    print '  %s\n    %d\n    %s\n    FAIL\n    0;' % (ec, f, os_to_hex(s))
+
+  while True:
+    x = k.random_element()
+    if not E.is_x_coord(x): break
+  xs = fe2osp(x)
+  out(EC_XONLY, [EC_XONLY] + xs)
+  out(EC_LSB, [EC_CMPR] + xs)
+  out(EC_SORT, [EC_CMPR | EC_SORT] + xs)
+
+print 'os2ecp {'
+for i in xrange(20): test_os2ecp(E_p, E_p.random_point())
+test_os2ecp_2torsion(E_p, T_p)
+test_os2ecp_notpoints(E_p)
+for i in xrange(20): test_os2ecp(E_2, E_2.random_point())
+test_os2ecp_2torsion(E_2, T_2)
+test_os2ecp_notpoints(E_2)
+print '}'