/ src / highlevelcrypto.py
highlevelcrypto.py
  1  """
  2  High level cryptographic functions based on `.pyelliptic` OpenSSL bindings.
  3  
  4  .. note::
  5    Upstream pyelliptic was upgraded from SHA1 to SHA256 for signing. We must
  6    `upgrade PyBitmessage gracefully. <https://github.com/Bitmessage/PyBitmessage/issues/953>`_
  7    `More discussion. <https://github.com/yann2192/pyelliptic/issues/32>`_
  8  """
  9  
 10  import hashlib
 11  import os
 12  from binascii import hexlify
 13  
 14  try:
 15      import pyelliptic
 16      from fallback import RIPEMD160Hash
 17      from pyelliptic import OpenSSL
 18      from pyelliptic import arithmetic as a
 19  except ImportError:
 20      from pybitmessage import pyelliptic
 21      from pybitmessage.fallback import RIPEMD160Hash
 22      from pybitmessage.pyelliptic import OpenSSL
 23      from pybitmessage.pyelliptic import arithmetic as a
 24  
 25  
 26  __all__ = [
 27      'decodeWalletImportFormat', 'deterministic_keys',
 28      'double_sha512', 'calculateInventoryHash', 'encodeWalletImportFormat',
 29      'encrypt', 'makeCryptor', 'pointMult', 'privToPub', 'randomBytes',
 30      'random_keys', 'sign', 'to_ripe', 'verify']
 31  
 32  
 33  # WIF (uses arithmetic ):
 34  def decodeWalletImportFormat(WIFstring):
 35      """
 36      Convert private key from base58 that's used in the config file to
 37      8-bit binary string.
 38      """
 39      fullString = a.changebase(WIFstring, 58, 256)
 40      privkey = fullString[:-4]
 41      if fullString[-4:] != \
 42         hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]:
 43          raise ValueError('Checksum failed')
 44      elif privkey[0:1] == b'\x80':  # checksum passed
 45          return privkey[1:]
 46  
 47      raise ValueError('No hex 80 prefix')
 48  
 49  
 50  # An excellent way for us to store our keys
 51  # is in Wallet Import Format. Let us convert now.
 52  # https://en.bitcoin.it/wiki/Wallet_import_format
 53  def encodeWalletImportFormat(privKey):
 54      """
 55      Convert private key from binary 8-bit string into base58check WIF string.
 56      """
 57      privKey = b'\x80' + privKey
 58      checksum = hashlib.sha256(hashlib.sha256(privKey).digest()).digest()[0:4]
 59      return a.changebase(privKey + checksum, 256, 58)
 60  
 61  
 62  # Random
 63  
 64  def randomBytes(n):
 65      """Get n random bytes"""
 66      try:
 67          return os.urandom(n)
 68      except NotImplementedError:
 69          return OpenSSL.rand(n)
 70  
 71  
 72  # Hashes
 73  
 74  def _bm160(data):
 75      """RIPEME160(SHA512(data)) -> bytes"""
 76      return RIPEMD160Hash(hashlib.sha512(data).digest()).digest()
 77  
 78  
 79  def to_ripe(signing_key, encryption_key):
 80      """Convert two public keys to a ripe hash"""
 81      return _bm160(signing_key + encryption_key)
 82  
 83  
 84  def double_sha512(data):
 85      """Binary double SHA512 digest"""
 86      return hashlib.sha512(hashlib.sha512(data).digest()).digest()
 87  
 88  
 89  def calculateInventoryHash(data):
 90      """Calculate inventory hash from object data"""
 91      return double_sha512(data)[:32]
 92  
 93  
 94  # Keys
 95  
 96  def random_keys():
 97      """Return a pair of keys, private and public"""
 98      priv = randomBytes(32)
 99      pub = pointMult(priv)
100      return priv, pub
101  
102  
103  def deterministic_keys(passphrase, nonce):
104      """Generate keys from *passphrase* and *nonce* (encoded as varint)"""
105      priv = hashlib.sha512(passphrase + nonce).digest()[:32]
106      pub = pointMult(priv)
107      return priv, pub
108  
109  
110  def hexToPubkey(pubkey):
111      """Convert a pubkey from hex to binary"""
112      pubkey_raw = a.changebase(pubkey[2:], 16, 256, minlen=64)
113      pubkey_bin = b'\x02\xca\x00 ' + pubkey_raw[:32] + b'\x00 ' + pubkey_raw[32:]
114      return pubkey_bin
115  
116  
117  def privToPub(privkey):
118      """Converts hex private key into hex public key"""
119      private_key = a.changebase(privkey, 16, 256, minlen=32)
120      public_key = pointMult(private_key)
121      return hexlify(public_key)
122  
123  
124  def pointMult(secret):
125      """
126      Does an EC point multiplication; turns a private key into a public key.
127  
128      Evidently, this type of error can occur very rarely:
129  
130      >>> File "highlevelcrypto.py", line 54, in pointMult
131      >>>  group = OpenSSL.EC_KEY_get0_group(k)
132      >>> WindowsError: exception: access violation reading 0x0000000000000008
133      """
134      while True:
135          try:
136              k = OpenSSL.EC_KEY_new_by_curve_name(
137                  OpenSSL.get_curve('secp256k1'))
138              priv_key = OpenSSL.BN_bin2bn(secret, 32, None)
139              group = OpenSSL.EC_KEY_get0_group(k)
140              pub_key = OpenSSL.EC_POINT_new(group)
141  
142              OpenSSL.EC_POINT_mul(group, pub_key, priv_key, None, None, None)
143              OpenSSL.EC_KEY_set_private_key(k, priv_key)
144              OpenSSL.EC_KEY_set_public_key(k, pub_key)
145  
146              size = OpenSSL.i2o_ECPublicKey(k, None)
147              mb = OpenSSL.create_string_buffer(size)
148              OpenSSL.i2o_ECPublicKey(k, OpenSSL.byref(OpenSSL.pointer(mb)))
149  
150              return mb.raw
151  
152          except Exception:
153              import traceback
154              import time
155              traceback.print_exc()
156              time.sleep(0.2)
157          finally:
158              OpenSSL.EC_POINT_free(pub_key)
159              OpenSSL.BN_free(priv_key)
160              OpenSSL.EC_KEY_free(k)
161  
162  
163  # Encryption
164  
165  def makeCryptor(privkey, curve='secp256k1'):
166      """Return a private `.pyelliptic.ECC` instance"""
167      private_key = a.changebase(privkey, 16, 256, minlen=32)
168      public_key = pointMult(private_key)
169      cryptor = pyelliptic.ECC(
170          pubkey_x=public_key[1:-32], pubkey_y=public_key[-32:],
171          raw_privkey=private_key, curve=curve)
172      return cryptor
173  
174  
175  def makePubCryptor(pubkey):
176      """Return a public `.pyelliptic.ECC` instance"""
177      pubkey_bin = hexToPubkey(pubkey)
178      return pyelliptic.ECC(curve='secp256k1', pubkey=pubkey_bin)
179  
180  
181  def encrypt(msg, hexPubkey):
182      """Encrypts message with hex public key"""
183      return pyelliptic.ECC(curve='secp256k1').encrypt(
184          msg, hexToPubkey(hexPubkey))
185  
186  
187  def decrypt(msg, hexPrivkey):
188      """Decrypts message with hex private key"""
189      return makeCryptor(hexPrivkey).decrypt(msg)
190  
191  
192  def decryptFast(msg, cryptor):
193      """Decrypts message with an existing `.pyelliptic.ECC` object"""
194      return cryptor.decrypt(msg)
195  
196  
197  # Signatures
198  
199  def _choose_digest_alg(name):
200      """
201      Choose openssl digest constant by name raises ValueError if not appropriate
202      """
203      if name not in ("sha1", "sha256"):
204          raise ValueError("Unknown digest algorithm %s" % name)
205      return (
206          # SHA1, this will eventually be deprecated
207          OpenSSL.digest_ecdsa_sha1 if name == "sha1" else OpenSSL.EVP_sha256)
208  
209  
210  def sign(msg, hexPrivkey, digestAlg="sha256"):
211      """
212      Signs with hex private key using SHA1 or SHA256 depending on
213      *digestAlg* keyword.
214      """
215      return makeCryptor(hexPrivkey).sign(
216          msg, digest_alg=_choose_digest_alg(digestAlg))
217  
218  
219  def verify(msg, sig, hexPubkey, digestAlg=None):
220      """Verifies with hex public key using SHA1 or SHA256"""
221      # As mentioned above, we must upgrade gracefully to use SHA256. So
222      # let us check the signature using both SHA1 and SHA256 and if one
223      # of them passes then we will be satisfied. Eventually this can
224      # be simplified and we'll only check with SHA256.
225      if digestAlg is None:
226          # old SHA1 algorithm.
227          sigVerifyPassed = verify(msg, sig, hexPubkey, "sha1")
228          if sigVerifyPassed:
229              # The signature check passed using SHA1
230              return True
231          # The signature check using SHA1 failed. Let us try it with SHA256.
232          return verify(msg, sig, hexPubkey, "sha256")
233  
234      try:
235          return makePubCryptor(hexPubkey).verify(
236              sig, msg, digest_alg=_choose_digest_alg(digestAlg))
237      except:
238          return False