csecp256k1

Haskell FFI bindings to bitcoin-core/secp256k1 (docs.ppad.tech/csecp256k1).
git clone git://git.ppad.tech/csecp256k1.git
Log | Files | Refs | README | LICENSE

weierstrass_prover.sage (9796B)


      1 # Prover implementation for Weierstrass curves of the form
      2 # y^2 = x^3 + A * x + B, specifically with a = 0 and b = 7, with group laws
      3 # operating on affine and Jacobian coordinates, including the point at infinity
      4 # represented by a 4th variable in coordinates.
      5 
      6 load("group_prover.sage")
      7 
      8 
      9 class affinepoint:
     10   def __init__(self, x, y, infinity=0):
     11     self.x = x
     12     self.y = y
     13     self.infinity = infinity
     14   def __str__(self):
     15     return "affinepoint(x=%s,y=%s,inf=%s)" % (self.x, self.y, self.infinity)
     16 
     17 
     18 class jacobianpoint:
     19   def __init__(self, x, y, z, infinity=0):
     20     self.X = x
     21     self.Y = y
     22     self.Z = z
     23     self.Infinity = infinity
     24   def __str__(self):
     25     return "jacobianpoint(X=%s,Y=%s,Z=%s,inf=%s)" % (self.X, self.Y, self.Z, self.Infinity)
     26 
     27 
     28 def point_at_infinity():
     29   return jacobianpoint(1, 1, 1, 1)
     30 
     31 
     32 def negate(p):
     33   if p.__class__ == affinepoint:
     34     return affinepoint(p.x, -p.y)
     35   if p.__class__ == jacobianpoint:
     36     return jacobianpoint(p.X, -p.Y, p.Z)
     37   assert(False)
     38 
     39 
     40 def on_weierstrass_curve(A, B, p):
     41   """Return a set of zero-expressions for an affine point to be on the curve"""
     42   return constraints(zero={p.x^3 + A*p.x + B - p.y^2: 'on_curve'})
     43 
     44 
     45 def tangential_to_weierstrass_curve(A, B, p12, p3):
     46   """Return a set of zero-expressions for ((x12,y12),(x3,y3)) to be a line that is tangential to the curve at (x12,y12)"""
     47   return constraints(zero={
     48     (p12.y - p3.y) * (p12.y * 2) - (p12.x^2 * 3 + A) * (p12.x - p3.x): 'tangential_to_curve'
     49   })
     50 
     51 
     52 def colinear(p1, p2, p3):
     53   """Return a set of zero-expressions for ((x1,y1),(x2,y2),(x3,y3)) to be collinear"""
     54   return constraints(zero={
     55     (p1.y - p2.y) * (p1.x - p3.x) - (p1.y - p3.y) * (p1.x - p2.x): 'colinear_1',
     56     (p2.y - p3.y) * (p2.x - p1.x) - (p2.y - p1.y) * (p2.x - p3.x): 'colinear_2',
     57     (p3.y - p1.y) * (p3.x - p2.x) - (p3.y - p2.y) * (p3.x - p1.x): 'colinear_3'
     58   })
     59 
     60 
     61 def good_affine_point(p):
     62   return constraints(nonzero={p.x : 'nonzero_x', p.y : 'nonzero_y'})
     63 
     64 
     65 def good_jacobian_point(p):
     66   return constraints(nonzero={p.X : 'nonzero_X', p.Y : 'nonzero_Y', p.Z^6 : 'nonzero_Z'})
     67 
     68 
     69 def good_point(p):
     70   return constraints(nonzero={p.Z^6 : 'nonzero_X'})
     71 
     72 
     73 def finite(p, *affine_fns):
     74   con = good_point(p) + constraints(zero={p.Infinity : 'finite_point'})
     75   if p.Z != 0:
     76     return con + reduce(lambda a, b: a + b, (f(affinepoint(p.X / p.Z^2, p.Y / p.Z^3)) for f in affine_fns), con)
     77   else:
     78     return con
     79 
     80 def infinite(p):
     81   return constraints(nonzero={p.Infinity : 'infinite_point'})
     82 
     83 
     84 def law_jacobian_weierstrass_add(A, B, pa, pb, pA, pB, pC):
     85   """Check whether the passed set of coordinates is a valid Jacobian add, given assumptions"""
     86   assumeLaw = (good_affine_point(pa) +
     87                good_affine_point(pb) +
     88                good_jacobian_point(pA) +
     89                good_jacobian_point(pB) +
     90                on_weierstrass_curve(A, B, pa) +
     91                on_weierstrass_curve(A, B, pb) +
     92                finite(pA) +
     93                finite(pB) +
     94                constraints(nonzero={pa.x - pb.x : 'different_x'}))
     95   require = (finite(pC, lambda pc: on_weierstrass_curve(A, B, pc) +
     96              colinear(pa, pb, negate(pc))))
     97   return (assumeLaw, require)
     98 
     99 
    100 def law_jacobian_weierstrass_double(A, B, pa, pb, pA, pB, pC):
    101   """Check whether the passed set of coordinates is a valid Jacobian doubling, given assumptions"""
    102   assumeLaw = (good_affine_point(pa) +
    103                good_affine_point(pb) +
    104                good_jacobian_point(pA) +
    105                good_jacobian_point(pB) +
    106                on_weierstrass_curve(A, B, pa) +
    107                on_weierstrass_curve(A, B, pb) +
    108                finite(pA) +
    109                finite(pB) +
    110                constraints(zero={pa.x - pb.x : 'equal_x', pa.y - pb.y : 'equal_y'}))
    111   require = (finite(pC, lambda pc: on_weierstrass_curve(A, B, pc) +
    112              tangential_to_weierstrass_curve(A, B, pa, negate(pc))))
    113   return (assumeLaw, require)
    114 
    115 
    116 def law_jacobian_weierstrass_add_opposites(A, B, pa, pb, pA, pB, pC):
    117   assumeLaw = (good_affine_point(pa) +
    118                good_affine_point(pb) +
    119                good_jacobian_point(pA) +
    120                good_jacobian_point(pB) +
    121                on_weierstrass_curve(A, B, pa) +
    122                on_weierstrass_curve(A, B, pb) +
    123                finite(pA) +
    124                finite(pB) +
    125                constraints(zero={pa.x - pb.x : 'equal_x', pa.y + pb.y : 'opposite_y'}))
    126   require = infinite(pC)
    127   return (assumeLaw, require)
    128 
    129 
    130 def law_jacobian_weierstrass_add_infinite_a(A, B, pa, pb, pA, pB, pC):
    131   assumeLaw = (good_affine_point(pa) +
    132                good_affine_point(pb) +
    133                good_jacobian_point(pA) +
    134                good_jacobian_point(pB) +
    135                on_weierstrass_curve(A, B, pb) +
    136                infinite(pA) +
    137                finite(pB))
    138   require = finite(pC, lambda pc: constraints(zero={pc.x - pb.x : 'c.x=b.x', pc.y - pb.y : 'c.y=b.y'}))
    139   return (assumeLaw, require)
    140 
    141 
    142 def law_jacobian_weierstrass_add_infinite_b(A, B, pa, pb, pA, pB, pC):
    143   assumeLaw = (good_affine_point(pa) +
    144                good_affine_point(pb) +
    145                good_jacobian_point(pA) +
    146                good_jacobian_point(pB) +
    147                on_weierstrass_curve(A, B, pa) +
    148                infinite(pB) +
    149                finite(pA))
    150   require = finite(pC, lambda pc: constraints(zero={pc.x - pa.x : 'c.x=a.x', pc.y - pa.y : 'c.y=a.y'}))
    151   return (assumeLaw, require)
    152 
    153 
    154 def law_jacobian_weierstrass_add_infinite_ab(A, B, pa, pb, pA, pB, pC):
    155   assumeLaw = (good_affine_point(pa) +
    156                good_affine_point(pb) +
    157                good_jacobian_point(pA) +
    158                good_jacobian_point(pB) +
    159                infinite(pA) +
    160                infinite(pB))
    161   require = infinite(pC)
    162   return (assumeLaw, require)
    163 
    164 
    165 laws_jacobian_weierstrass = {
    166   'add': law_jacobian_weierstrass_add,
    167   'double': law_jacobian_weierstrass_double,
    168   'add_opposite': law_jacobian_weierstrass_add_opposites,
    169   'add_infinite_a': law_jacobian_weierstrass_add_infinite_a,
    170   'add_infinite_b': law_jacobian_weierstrass_add_infinite_b,
    171   'add_infinite_ab': law_jacobian_weierstrass_add_infinite_ab
    172 }
    173 
    174 
    175 def check_exhaustive_jacobian_weierstrass(name, A, B, branches, formula, p):
    176   """Verify an implementation of addition of Jacobian points on a Weierstrass curve, by executing and validating the result for every possible addition in a prime field"""
    177   F = Integers(p)
    178   print("Formula %s on Z%i:" % (name, p))
    179   points = []
    180   for x in range(0, p):
    181     for y in range(0, p):
    182       point = affinepoint(F(x), F(y))
    183       r, e = concrete_verify(on_weierstrass_curve(A, B, point))
    184       if r:
    185         points.append(point)
    186 
    187   ret = True
    188   for za in range(1, p):
    189     for zb in range(1, p):
    190       for pa in points:
    191         for pb in points:
    192           for ia in range(2):
    193             for ib in range(2):
    194               pA = jacobianpoint(pa.x * F(za)^2, pa.y * F(za)^3, F(za), ia)
    195               pB = jacobianpoint(pb.x * F(zb)^2, pb.y * F(zb)^3, F(zb), ib)
    196               for branch in range(0, branches):
    197                 assumeAssert, assumeBranch, pC = formula(branch, pA, pB)
    198                 pC.X = F(pC.X)
    199                 pC.Y = F(pC.Y)
    200                 pC.Z = F(pC.Z)
    201                 pC.Infinity = F(pC.Infinity)
    202                 r, e = concrete_verify(assumeAssert + assumeBranch)
    203                 if r:
    204                   match = False
    205                   for key in laws_jacobian_weierstrass:
    206                     assumeLaw, require = laws_jacobian_weierstrass[key](A, B, pa, pb, pA, pB, pC)
    207                     r, e = concrete_verify(assumeLaw)
    208                     if r:
    209                       if match:
    210                         print("  multiple branches for (%s,%s,%s,%s) + (%s,%s,%s,%s)" % (pA.X, pA.Y, pA.Z, pA.Infinity, pB.X, pB.Y, pB.Z, pB.Infinity))
    211                       else:
    212                         match = True
    213                       r, e = concrete_verify(require)
    214                       if not r:
    215                         ret = False
    216                         print("  failure in branch %i for (%s,%s,%s,%s) + (%s,%s,%s,%s) = (%s,%s,%s,%s): %s" % (branch, pA.X, pA.Y, pA.Z, pA.Infinity, pB.X, pB.Y, pB.Z, pB.Infinity, pC.X, pC.Y, pC.Z, pC.Infinity, e))
    217 
    218   print()
    219   return ret
    220 
    221 
    222 def check_symbolic_function(R, assumeAssert, assumeBranch, f, A, B, pa, pb, pA, pB, pC):
    223   assumeLaw, require = f(A, B, pa, pb, pA, pB, pC)
    224   return check_symbolic(R, assumeLaw, assumeAssert, assumeBranch, require)
    225 
    226 def check_symbolic_jacobian_weierstrass(name, A, B, branches, formula):
    227   """Verify an implementation of addition of Jacobian points on a Weierstrass curve symbolically"""
    228   R.<ax,bx,ay,by,Az,Bz,Ai,Bi> = PolynomialRing(QQ,8,order='invlex')
    229   lift = lambda x: fastfrac(R,x)
    230   ax = lift(ax)
    231   ay = lift(ay)
    232   Az = lift(Az)
    233   bx = lift(bx)
    234   by = lift(by)
    235   Bz = lift(Bz)
    236   Ai = lift(Ai)
    237   Bi = lift(Bi)
    238 
    239   pa = affinepoint(ax, ay, Ai)
    240   pb = affinepoint(bx, by, Bi)
    241   pA = jacobianpoint(ax * Az^2, ay * Az^3, Az, Ai)
    242   pB = jacobianpoint(bx * Bz^2, by * Bz^3, Bz, Bi)
    243 
    244   res = {}
    245 
    246   for key in laws_jacobian_weierstrass:
    247     res[key] = []
    248 
    249   print("Formula " + name + ":")
    250   count = 0
    251   ret = True
    252   for branch in range(branches):
    253     assumeFormula, assumeBranch, pC = formula(branch, pA, pB)
    254     assumeBranch = assumeBranch.map(lift)
    255     assumeFormula = assumeFormula.map(lift)
    256     pC.X = lift(pC.X)
    257     pC.Y = lift(pC.Y)
    258     pC.Z = lift(pC.Z)
    259     pC.Infinity = lift(pC.Infinity)
    260 
    261     for key in laws_jacobian_weierstrass:
    262       success, msg = check_symbolic_function(R, assumeFormula, assumeBranch, laws_jacobian_weierstrass[key], A, B, pa, pb, pA, pB, pC)
    263       if not success:
    264         ret = False
    265       res[key].append((msg, branch))
    266 
    267   for key in res:
    268     print("  %s:" % key)
    269     val = res[key]
    270     for x in val:
    271       if x[0] is not None:
    272         print("    branch %i: %s" % (x[1], x[0]))
    273 
    274   print()
    275   return ret