/ src / pyelliptic / eccblind.py
eccblind.py
  1  """
  2  ECC blind signature functionality based on
  3  "An Efficient Blind Signature Scheme
  4  Based on the Elliptic CurveDiscrete Logarithm Problem" by Morteza Nikooghadama
  5  <mnikooghadam@sbu.ac.ir> and Ali Zakerolhosseini <a-zaker@sbu.ac.ir>,
  6  http://www.isecure-journal.com/article_39171_47f9ec605dd3918c2793565ec21fcd7a.pdf
  7  """
  8  
  9  # variable names are based on the math in the paper, so they don't conform
 10  # to PEP8
 11  
 12  import time
 13  from hashlib import sha256
 14  from struct import pack, unpack
 15  
 16  from .openssl import OpenSSL
 17  
 18  # first byte in serialisation can contain data
 19  Y_BIT = 0x01
 20  COMPRESSED_BIT = 0x02
 21  
 22  # formats
 23  BIGNUM = '!32s'
 24  EC = '!B32s'
 25  PUBKEY = '!BB33s'
 26  
 27  
 28  class Expiration(object):
 29      """Expiration of pubkey"""
 30      @staticmethod
 31      def deserialize(val):
 32          """Create an object out of int"""
 33          year = ((val & 0xF0) >> 4) + 2020
 34          month = val & 0x0F
 35          assert month < 12
 36          return Expiration(year, month)
 37  
 38      def __init__(self, year, month):
 39          assert isinstance(year, int)
 40          assert year > 2019 and year < 2036
 41          assert isinstance(month, int)
 42          assert month < 12
 43          self.year = year
 44          self.month = month
 45          self.exp = year + month / 12.0
 46  
 47      def serialize(self):
 48          """Make int out of object"""
 49          return ((self.year - 2020) << 4) + self.month
 50  
 51      def verify(self):
 52          """Check if the pubkey has expired"""
 53          now = time.gmtime()
 54          return self.exp >= now.tm_year + (now.tm_mon - 1) / 12.0
 55  
 56  
 57  class Value(object):
 58      """Value of a pubkey"""
 59      @staticmethod
 60      def deserialize(val):
 61          """Make object out of int"""
 62          return Value(val)
 63  
 64      def __init__(self, value=0xFF):
 65          assert isinstance(value, int)
 66          self.value = value
 67  
 68      def serialize(self):
 69          """Make int out of object"""
 70          return self.value & 0xFF
 71  
 72      def verify(self, value):
 73          """Verify against supplied value"""
 74          return value <= self.value
 75  
 76  
 77  class ECCBlind(object):  # pylint: disable=too-many-instance-attributes
 78      """
 79      Class for ECC blind signature functionality
 80      """
 81  
 82      # init
 83      k = None
 84      R = None
 85      F = None
 86      d = None
 87      Q = None
 88      a = None
 89      b = None
 90      c = None
 91      binv = None
 92      r = None
 93      m = None
 94      m_ = None
 95      s_ = None
 96      signature = None
 97      exp = None
 98      val = None
 99  
100      def ec_get_random(self):
101          """
102          Random integer within the EC order
103          """
104          randomnum = OpenSSL.BN_new()
105          OpenSSL.BN_rand(randomnum, OpenSSL.BN_num_bits(self.n), 0, 0)
106          return randomnum
107  
108      def ec_invert(self, a):
109          """
110          ECC inversion
111          """
112          inverse = OpenSSL.BN_mod_inverse(None, a, self.n, self.ctx)
113          return inverse
114  
115      def ec_gen_keypair(self):
116          """
117          Generate an ECC keypair
118          We're using compressed keys
119          """
120          d = self.ec_get_random()
121          Q = OpenSSL.EC_POINT_new(self.group)
122          OpenSSL.EC_POINT_mul(self.group, Q, d, None, None, None)
123          return (d, Q)
124  
125      def ec_Ftor(self, F):
126          """
127          x0 coordinate of F
128          """
129          # F = (x0, y0)
130          x0 = OpenSSL.BN_new()
131          y0 = OpenSSL.BN_new()
132          OpenSSL.EC_POINT_get_affine_coordinates(self.group, F, x0, y0, self.ctx)
133          OpenSSL.BN_free(y0)
134          return x0
135  
136      def _ec_point_serialize(self, point):
137          """Make an EC point into a string"""
138          try:
139              x = OpenSSL.BN_new()
140              y = OpenSSL.BN_new()
141              OpenSSL.EC_POINT_get_affine_coordinates(
142                  self.group, point, x, y, None)
143              y_byte = (OpenSSL.BN_is_odd(y) & Y_BIT) | COMPRESSED_BIT
144              l_ = OpenSSL.BN_num_bytes(self.n)
145              try:
146                  bx = OpenSSL.malloc(0, l_)
147                  OpenSSL.BN_bn2binpad(x, bx, l_)
148                  out = bx.raw
149              except AttributeError:
150                  # padding manually
151                  bx = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(x))
152                  OpenSSL.BN_bn2bin(x, bx)
153                  out = bx.raw.rjust(l_, b'\x00')
154              return pack(EC, y_byte, out)
155  
156          finally:
157              OpenSSL.BN_clear_free(x)
158              OpenSSL.BN_clear_free(y)
159  
160      def _ec_point_deserialize(self, data):
161          """Make a string into an EC point"""
162          y_bit, x_raw = unpack(EC, data)
163          x = OpenSSL.BN_bin2bn(x_raw, OpenSSL.BN_num_bytes(self.n), None)
164          y_bit &= Y_BIT
165          retval = OpenSSL.EC_POINT_new(self.group)
166          OpenSSL.EC_POINT_set_compressed_coordinates(self.group,
167                                                      retval,
168                                                      x,
169                                                      y_bit,
170                                                      self.ctx)
171          return retval
172  
173      def _bn_serialize(self, bn):
174          """Make a string out of BigNum"""
175          l_ = OpenSSL.BN_num_bytes(self.n)
176          try:
177              o = OpenSSL.malloc(0, l_)
178              OpenSSL.BN_bn2binpad(bn, o, l_)
179              return o.raw
180          except AttributeError:
181              o = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(bn))
182              OpenSSL.BN_bn2bin(bn, o)
183              return o.raw.rjust(l_, b'\x00')
184  
185      def _bn_deserialize(self, data):
186          """Make a BigNum out of string"""
187          x = OpenSSL.BN_bin2bn(data, OpenSSL.BN_num_bytes(self.n), None)
188          return x
189  
190      def _init_privkey(self, privkey):
191          """Initialise private key out of string/bytes"""
192          self.d = self._bn_deserialize(privkey)
193  
194      def privkey(self):
195          """Make a private key into a string"""
196          return pack(BIGNUM, self.d)
197  
198      def _init_pubkey(self, pubkey):
199          """Initialise pubkey out of string/bytes"""
200          unpacked = unpack(PUBKEY, pubkey)
201          self.expiration = Expiration.deserialize(unpacked[0])
202          self.value = Value.deserialize(unpacked[1])
203          self.Q = self._ec_point_deserialize(unpacked[2])
204  
205      def pubkey(self):
206          """Make a pubkey into a string"""
207          return pack(PUBKEY, self.expiration.serialize(),
208                      self.value.serialize(),
209                      self._ec_point_serialize(self.Q))
210  
211      def __init__(self, curve="secp256k1", pubkey=None, privkey=None,
212                   year=2025, month=11, value=0xFF):
213          self.ctx = OpenSSL.BN_CTX_new()
214  
215          # ECC group
216          self.group = OpenSSL.EC_GROUP_new_by_curve_name(
217              OpenSSL.get_curve(curve))
218  
219          # Order n
220          self.n = OpenSSL.BN_new()
221          OpenSSL.EC_GROUP_get_order(self.group, self.n, self.ctx)
222  
223          # Generator G
224          self.G = OpenSSL.EC_GROUP_get0_generator(self.group)
225  
226          # Identity O (infinity)
227          self.iO = OpenSSL.EC_POINT_new(self.group)
228          OpenSSL.EC_POINT_set_to_infinity(self.group, self.iO)
229  
230          if privkey:
231              assert pubkey
232              # load both pubkey and privkey from bytes
233              self._init_privkey(privkey)
234              self._init_pubkey(pubkey)
235          elif pubkey:
236              # load pubkey from bytes
237              self._init_pubkey(pubkey)
238          else:
239              # new keypair
240              self.d, self.Q = self.ec_gen_keypair()
241              if not year or not month:
242                  now = time.gmtime()
243                  if now.tm_mon == 12:
244                      self.expiration = Expiration(now.tm_year + 1, 1)
245                  else:
246                      self.expiration = Expiration(now.tm_year, now.tm_mon + 1)
247              else:
248                  self.expiration = Expiration(year, month)
249              self.value = Value(value)
250  
251      def __del__(self):
252          OpenSSL.BN_free(self.n)
253          OpenSSL.BN_CTX_free(self.ctx)
254  
255      def signer_init(self):
256          """
257          Init signer
258          """
259          # Signer: Random integer k
260          self.k = self.ec_get_random()
261  
262          # R = kG
263          self.R = OpenSSL.EC_POINT_new(self.group)
264          OpenSSL.EC_POINT_mul(self.group, self.R, self.k, None, None, None)
265  
266          return self._ec_point_serialize(self.R)
267  
268      def create_signing_request(self, R, msg):
269          """
270          Requester creates a new signing request
271          """
272          self.R = self._ec_point_deserialize(R)
273          msghash = sha256(msg).digest()
274  
275          # Requester: 3 random blinding factors
276          self.F = OpenSSL.EC_POINT_new(self.group)
277          OpenSSL.EC_POINT_set_to_infinity(self.group, self.F)
278          temp = OpenSSL.EC_POINT_new(self.group)
279          abinv = OpenSSL.BN_new()
280  
281          # F != O
282          while OpenSSL.EC_POINT_cmp(self.group, self.F, self.iO, self.ctx) == 0:
283              self.a = self.ec_get_random()
284              self.b = self.ec_get_random()
285              self.c = self.ec_get_random()
286  
287              # F = b^-1 * R...
288              self.binv = self.ec_invert(self.b)
289              OpenSSL.EC_POINT_mul(self.group, temp, None, self.R, self.binv,
290                                   None)
291              OpenSSL.EC_POINT_copy(self.F, temp)
292  
293              # ... + a*b^-1 * Q...
294              OpenSSL.BN_mul(abinv, self.a, self.binv, self.ctx)
295              OpenSSL.EC_POINT_mul(self.group, temp, None, self.Q, abinv, None)
296              OpenSSL.EC_POINT_add(self.group, self.F, self.F, temp, None)
297  
298              # ... + c*G
299              OpenSSL.EC_POINT_mul(self.group, temp, None, self.G, self.c, None)
300              OpenSSL.EC_POINT_add(self.group, self.F, self.F, temp, None)
301  
302          # F = (x0, y0)
303          self.r = self.ec_Ftor(self.F)
304  
305          # Requester: Blinding (m' = br(m) + a)
306          self.m = OpenSSL.BN_new()
307          OpenSSL.BN_bin2bn(msghash, len(msghash), self.m)
308  
309          self.m_ = OpenSSL.BN_new()
310          OpenSSL.BN_mod_mul(self.m_, self.b, self.r, self.n, self.ctx)
311          OpenSSL.BN_mod_mul(self.m_, self.m_, self.m, self.n, self.ctx)
312          OpenSSL.BN_mod_add(self.m_, self.m_, self.a, self.n, self.ctx)
313          return self._bn_serialize(self.m_)
314  
315      def blind_sign(self, m_):
316          """
317          Signer blind-signs the request
318          """
319          self.m_ = self._bn_deserialize(m_)
320          self.s_ = OpenSSL.BN_new()
321          OpenSSL.BN_mod_mul(self.s_, self.d, self.m_, self.n, self.ctx)
322          OpenSSL.BN_mod_add(self.s_, self.s_, self.k, self.n, self.ctx)
323          OpenSSL.BN_free(self.k)
324          return self._bn_serialize(self.s_)
325  
326      def unblind(self, s_):
327          """
328          Requester unblinds the signature
329          """
330          self.s_ = self._bn_deserialize(s_)
331          s = OpenSSL.BN_new()
332          OpenSSL.BN_mod_mul(s, self.binv, self.s_, self.n, self.ctx)
333          OpenSSL.BN_mod_add(s, s, self.c, self.n, self.ctx)
334          OpenSSL.BN_free(self.a)
335          OpenSSL.BN_free(self.b)
336          OpenSSL.BN_free(self.c)
337          self.signature = (s, self.F)
338          return self._bn_serialize(s) + self._ec_point_serialize(self.F)
339  
340      def verify(self, msg, signature, value=1):
341          """
342          Verify signature with certifier's pubkey
343          """
344  
345          # convert msg to BIGNUM
346          self.m = OpenSSL.BN_new()
347          msghash = sha256(msg).digest()
348          OpenSSL.BN_bin2bn(msghash, len(msghash), self.m)
349  
350          # init
351          s, self.F = (self._bn_deserialize(signature[0:32]),
352                       self._ec_point_deserialize(signature[32:]))
353          if self.r is None:
354              self.r = self.ec_Ftor(self.F)
355  
356          lhs = OpenSSL.EC_POINT_new(self.group)
357          rhs = OpenSSL.EC_POINT_new(self.group)
358  
359          OpenSSL.EC_POINT_mul(self.group, lhs, s, None, None, None)
360  
361          OpenSSL.EC_POINT_mul(self.group, rhs, None, self.Q, self.m, None)
362          OpenSSL.EC_POINT_mul(self.group, rhs, None, rhs, self.r, None)
363          OpenSSL.EC_POINT_add(self.group, rhs, rhs, self.F, self.ctx)
364  
365          retval = OpenSSL.EC_POINT_cmp(self.group, lhs, rhs, self.ctx)
366          if retval == -1:
367              raise RuntimeError("EC_POINT_cmp returned an error")
368          elif not self.value.verify(value):
369              return False
370          elif not self.expiration.verify():
371              return False
372          elif retval != 0:
373              return False
374          return True