URI: 
       tecc.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tecc.py (21994B)
       ---
            1 # -*- coding: utf-8 -*-
            2 #
            3 # Electrum - lightweight Bitcoin client
            4 # Copyright (C) 2018 The Electrum developers
            5 #
            6 # Permission is hereby granted, free of charge, to any person
            7 # obtaining a copy of this software and associated documentation files
            8 # (the "Software"), to deal in the Software without restriction,
            9 # including without limitation the rights to use, copy, modify, merge,
           10 # publish, distribute, sublicense, and/or sell copies of the Software,
           11 # and to permit persons to whom the Software is furnished to do so,
           12 # subject to the following conditions:
           13 #
           14 # The above copyright notice and this permission notice shall be
           15 # included in all copies or substantial portions of the Software.
           16 #
           17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
           18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
           19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
           20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
           21 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
           22 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
           23 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
           24 # SOFTWARE.
           25 
           26 import base64
           27 import hashlib
           28 import functools
           29 from typing import Union, Tuple, Optional
           30 from ctypes import (
           31     byref, c_byte, c_int, c_uint, c_char_p, c_size_t, c_void_p, create_string_buffer,
           32     CFUNCTYPE, POINTER, cast
           33 )
           34 
           35 from .util import bfh, bh2u, assert_bytes, to_bytes, InvalidPassword, profiler, randrange
           36 from .crypto import (sha256d, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot)
           37 from . import constants
           38 from .logging import get_logger
           39 from .ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED
           40 
           41 _logger = get_logger(__name__)
           42 
           43 
           44 def string_to_number(b: bytes) -> int:
           45     return int.from_bytes(b, byteorder='big', signed=False)
           46 
           47 
           48 def sig_string_from_der_sig(der_sig: bytes) -> bytes:
           49     r, s = get_r_and_s_from_der_sig(der_sig)
           50     return sig_string_from_r_and_s(r, s)
           51 
           52 
           53 def der_sig_from_sig_string(sig_string: bytes) -> bytes:
           54     r, s = get_r_and_s_from_sig_string(sig_string)
           55     return der_sig_from_r_and_s(r, s)
           56 
           57 
           58 def der_sig_from_r_and_s(r: int, s: int) -> bytes:
           59     sig_string = (int.to_bytes(r, length=32, byteorder="big") +
           60                   int.to_bytes(s, length=32, byteorder="big"))
           61     sig = create_string_buffer(64)
           62     ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string)
           63     if not ret:
           64         raise Exception("Bad signature")
           65     ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
           66     der_sig = create_string_buffer(80)  # this much space should be enough
           67     der_sig_size = c_size_t(len(der_sig))
           68     ret = _libsecp256k1.secp256k1_ecdsa_signature_serialize_der(_libsecp256k1.ctx, der_sig, byref(der_sig_size), sig)
           69     if not ret:
           70         raise Exception("failed to serialize DER sig")
           71     der_sig_size = der_sig_size.value
           72     return bytes(der_sig)[:der_sig_size]
           73 
           74 
           75 def get_r_and_s_from_der_sig(der_sig: bytes) -> Tuple[int, int]:
           76     assert isinstance(der_sig, bytes)
           77     sig = create_string_buffer(64)
           78     ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_der(_libsecp256k1.ctx, sig, der_sig, len(der_sig))
           79     if not ret:
           80         raise Exception("Bad signature")
           81     ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
           82     compact_signature = create_string_buffer(64)
           83     _libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig)
           84     r = int.from_bytes(compact_signature[:32], byteorder="big")
           85     s = int.from_bytes(compact_signature[32:], byteorder="big")
           86     return r, s
           87 
           88 
           89 def get_r_and_s_from_sig_string(sig_string: bytes) -> Tuple[int, int]:
           90     if not (isinstance(sig_string, bytes) and len(sig_string) == 64):
           91         raise Exception("sig_string must be bytes, and 64 bytes exactly")
           92     sig = create_string_buffer(64)
           93     ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string)
           94     if not ret:
           95         raise Exception("Bad signature")
           96     ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
           97     compact_signature = create_string_buffer(64)
           98     _libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig)
           99     r = int.from_bytes(compact_signature[:32], byteorder="big")
          100     s = int.from_bytes(compact_signature[32:], byteorder="big")
          101     return r, s
          102 
          103 
          104 def sig_string_from_r_and_s(r: int, s: int) -> bytes:
          105     sig_string = (int.to_bytes(r, length=32, byteorder="big") +
          106                   int.to_bytes(s, length=32, byteorder="big"))
          107     sig = create_string_buffer(64)
          108     ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string)
          109     if not ret:
          110         raise Exception("Bad signature")
          111     ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
          112     compact_signature = create_string_buffer(64)
          113     _libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig)
          114     return bytes(compact_signature)
          115 
          116 
          117 def _x_and_y_from_pubkey_bytes(pubkey: bytes) -> Tuple[int, int]:
          118     assert isinstance(pubkey, bytes), f'pubkey must be bytes, not {type(pubkey)}'
          119     pubkey_ptr = create_string_buffer(64)
          120     ret = _libsecp256k1.secp256k1_ec_pubkey_parse(
          121         _libsecp256k1.ctx, pubkey_ptr, pubkey, len(pubkey))
          122     if not ret:
          123         raise InvalidECPointException('public key could not be parsed or is invalid')
          124 
          125     pubkey_serialized = create_string_buffer(65)
          126     pubkey_size = c_size_t(65)
          127     _libsecp256k1.secp256k1_ec_pubkey_serialize(
          128         _libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey_ptr, SECP256K1_EC_UNCOMPRESSED)
          129     pubkey_serialized = bytes(pubkey_serialized)
          130     assert pubkey_serialized[0] == 0x04, pubkey_serialized
          131     x = int.from_bytes(pubkey_serialized[1:33], byteorder='big', signed=False)
          132     y = int.from_bytes(pubkey_serialized[33:65], byteorder='big', signed=False)
          133     return x, y
          134 
          135 
          136 class InvalidECPointException(Exception):
          137     """e.g. not on curve, or infinity"""
          138 
          139 
          140 @functools.total_ordering
          141 class ECPubkey(object):
          142 
          143     def __init__(self, b: Optional[bytes]):
          144         if b is not None:
          145             assert isinstance(b, (bytes, bytearray)), f'pubkey must be bytes-like, not {type(b)}'
          146             if isinstance(b, bytearray):
          147                 b = bytes(b)
          148             self._x, self._y = _x_and_y_from_pubkey_bytes(b)
          149         else:
          150             self._x, self._y = None, None
          151 
          152     @classmethod
          153     def from_sig_string(cls, sig_string: bytes, recid: int, msg_hash: bytes) -> 'ECPubkey':
          154         assert_bytes(sig_string)
          155         if len(sig_string) != 64:
          156             raise Exception(f'wrong encoding used for signature? len={len(sig_string)} (should be 64)')
          157         if recid < 0 or recid > 3:
          158             raise ValueError('recid is {}, but should be 0 <= recid <= 3'.format(recid))
          159         sig65 = create_string_buffer(65)
          160         ret = _libsecp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact(
          161             _libsecp256k1.ctx, sig65, sig_string, recid)
          162         if not ret:
          163             raise Exception('failed to parse signature')
          164         pubkey = create_string_buffer(64)
          165         ret = _libsecp256k1.secp256k1_ecdsa_recover(_libsecp256k1.ctx, pubkey, sig65, msg_hash)
          166         if not ret:
          167             raise InvalidECPointException('failed to recover public key')
          168         return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey)
          169 
          170     @classmethod
          171     def from_signature65(cls, sig: bytes, msg_hash: bytes) -> Tuple['ECPubkey', bool]:
          172         if len(sig) != 65:
          173             raise Exception(f'wrong encoding used for signature? len={len(sig)} (should be 65)')
          174         nV = sig[0]
          175         if nV < 27 or nV >= 35:
          176             raise Exception("Bad encoding")
          177         if nV >= 31:
          178             compressed = True
          179             nV -= 4
          180         else:
          181             compressed = False
          182         recid = nV - 27
          183         return cls.from_sig_string(sig[1:], recid, msg_hash), compressed
          184 
          185     @classmethod
          186     def from_x_and_y(cls, x: int, y: int) -> 'ECPubkey':
          187         _bytes = (b'\x04'
          188                   + int.to_bytes(x, length=32, byteorder='big', signed=False)
          189                   + int.to_bytes(y, length=32, byteorder='big', signed=False))
          190         return ECPubkey(_bytes)
          191 
          192     def get_public_key_bytes(self, compressed=True):
          193         if self.is_at_infinity(): raise Exception('point is at infinity')
          194         x = int.to_bytes(self.x(), length=32, byteorder='big', signed=False)
          195         y = int.to_bytes(self.y(), length=32, byteorder='big', signed=False)
          196         if compressed:
          197             header = b'\x03' if self.y() & 1 else b'\x02'
          198             return header + x
          199         else:
          200             header = b'\x04'
          201             return header + x + y
          202 
          203     def get_public_key_hex(self, compressed=True):
          204         return bh2u(self.get_public_key_bytes(compressed))
          205 
          206     def point(self) -> Tuple[int, int]:
          207         return self.x(), self.y()
          208 
          209     def x(self) -> int:
          210         return self._x
          211 
          212     def y(self) -> int:
          213         return self._y
          214 
          215     def _to_libsecp256k1_pubkey_ptr(self):
          216         pubkey = create_string_buffer(64)
          217         public_pair_bytes = self.get_public_key_bytes(compressed=False)
          218         ret = _libsecp256k1.secp256k1_ec_pubkey_parse(
          219             _libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes))
          220         if not ret:
          221             raise Exception('public key could not be parsed or is invalid')
          222         return pubkey
          223 
          224     @classmethod
          225     def _from_libsecp256k1_pubkey_ptr(cls, pubkey) -> 'ECPubkey':
          226         pubkey_serialized = create_string_buffer(65)
          227         pubkey_size = c_size_t(65)
          228         _libsecp256k1.secp256k1_ec_pubkey_serialize(
          229             _libsecp256k1.ctx, pubkey_serialized, byref(pubkey_size), pubkey, SECP256K1_EC_UNCOMPRESSED)
          230         return ECPubkey(bytes(pubkey_serialized))
          231 
          232     def __repr__(self):
          233         if self.is_at_infinity():
          234             return f"<ECPubkey infinity>"
          235         return f"<ECPubkey {self.get_public_key_hex()}>"
          236 
          237     def __mul__(self, other: int):
          238         if not isinstance(other, int):
          239             raise TypeError('multiplication not defined for ECPubkey and {}'.format(type(other)))
          240 
          241         other %= CURVE_ORDER
          242         if self.is_at_infinity() or other == 0:
          243             return POINT_AT_INFINITY
          244         pubkey = self._to_libsecp256k1_pubkey_ptr()
          245 
          246         ret = _libsecp256k1.secp256k1_ec_pubkey_tweak_mul(_libsecp256k1.ctx, pubkey, other.to_bytes(32, byteorder="big"))
          247         if not ret:
          248             return POINT_AT_INFINITY
          249         return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey)
          250 
          251     def __rmul__(self, other: int):
          252         return self * other
          253 
          254     def __add__(self, other):
          255         if not isinstance(other, ECPubkey):
          256             raise TypeError('addition not defined for ECPubkey and {}'.format(type(other)))
          257         if self.is_at_infinity(): return other
          258         if other.is_at_infinity(): return self
          259 
          260         pubkey1 = self._to_libsecp256k1_pubkey_ptr()
          261         pubkey2 = other._to_libsecp256k1_pubkey_ptr()
          262         pubkey_sum = create_string_buffer(64)
          263 
          264         pubkey1 = cast(pubkey1, c_char_p)
          265         pubkey2 = cast(pubkey2, c_char_p)
          266         array_of_pubkey_ptrs = (c_char_p * 2)(pubkey1, pubkey2)
          267         ret = _libsecp256k1.secp256k1_ec_pubkey_combine(_libsecp256k1.ctx, pubkey_sum, array_of_pubkey_ptrs, 2)
          268         if not ret:
          269             return POINT_AT_INFINITY
          270         return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey_sum)
          271 
          272     def __eq__(self, other) -> bool:
          273         if not isinstance(other, ECPubkey):
          274             return False
          275         return self.point() == other.point()
          276 
          277     def __ne__(self, other):
          278         return not (self == other)
          279 
          280     def __hash__(self):
          281         return hash(self.point())
          282 
          283     def __lt__(self, other):
          284         if not isinstance(other, ECPubkey):
          285             raise TypeError('comparison not defined for ECPubkey and {}'.format(type(other)))
          286         return (self.x() or 0) < (other.x() or 0)
          287 
          288     def verify_message_for_address(self, sig65: bytes, message: bytes, algo=lambda x: sha256d(msg_magic(x))) -> None:
          289         assert_bytes(message)
          290         h = algo(message)
          291         public_key, compressed = self.from_signature65(sig65, h)
          292         # check public key
          293         if public_key != self:
          294             raise Exception("Bad signature")
          295         # check message
          296         self.verify_message_hash(sig65[1:], h)
          297 
          298     # TODO return bool instead of raising
          299     def verify_message_hash(self, sig_string: bytes, msg_hash: bytes) -> None:
          300         assert_bytes(sig_string)
          301         if len(sig_string) != 64:
          302             raise Exception(f'wrong encoding used for signature? len={len(sig_string)} (should be 64)')
          303         if not (isinstance(msg_hash, bytes) and len(msg_hash) == 32):
          304             raise Exception("msg_hash must be bytes, and 32 bytes exactly")
          305 
          306         sig = create_string_buffer(64)
          307         ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string)
          308         if not ret:
          309             raise Exception("Bad signature")
          310         ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
          311 
          312         pubkey = self._to_libsecp256k1_pubkey_ptr()
          313         if 1 != _libsecp256k1.secp256k1_ecdsa_verify(_libsecp256k1.ctx, sig, msg_hash, pubkey):
          314             raise Exception("Bad signature")
          315 
          316     def encrypt_message(self, message: bytes, magic: bytes = b'BIE1') -> bytes:
          317         """
          318         ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; hmac-sha256 is used as the mac
          319         """
          320         assert_bytes(message)
          321 
          322         ephemeral = ECPrivkey.generate_random_key()
          323         ecdh_key = (self * ephemeral.secret_scalar).get_public_key_bytes(compressed=True)
          324         key = hashlib.sha512(ecdh_key).digest()
          325         iv, key_e, key_m = key[0:16], key[16:32], key[32:]
          326         ciphertext = aes_encrypt_with_iv(key_e, iv, message)
          327         ephemeral_pubkey = ephemeral.get_public_key_bytes(compressed=True)
          328         encrypted = magic + ephemeral_pubkey + ciphertext
          329         mac = hmac_oneshot(key_m, encrypted, hashlib.sha256)
          330 
          331         return base64.b64encode(encrypted + mac)
          332 
          333     @classmethod
          334     def order(cls):
          335         return CURVE_ORDER
          336 
          337     def is_at_infinity(self):
          338         return self == POINT_AT_INFINITY
          339 
          340     @classmethod
          341     def is_pubkey_bytes(cls, b: bytes):
          342         try:
          343             ECPubkey(b)
          344             return True
          345         except:
          346             return False
          347 
          348 
          349 GENERATOR = ECPubkey(bytes.fromhex('0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'
          350                                    '483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8'))
          351 CURVE_ORDER = 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D0364141
          352 POINT_AT_INFINITY = ECPubkey(None)
          353 
          354 
          355 def msg_magic(message: bytes) -> bytes:
          356     from .bitcoin import var_int
          357     length = bfh(var_int(len(message)))
          358     return b"\x18Bitcoin Signed Message:\n" + length + message
          359 
          360 
          361 def verify_signature(pubkey: bytes, sig: bytes, h: bytes) -> bool:
          362     try:
          363         ECPubkey(pubkey).verify_message_hash(sig, h)
          364     except:
          365         return False
          366     return True
          367 
          368 def verify_message_with_address(address: str, sig65: bytes, message: bytes, *, net=None):
          369     from .bitcoin import pubkey_to_address
          370     assert_bytes(sig65, message)
          371     if net is None: net = constants.net
          372     try:
          373         h = sha256d(msg_magic(message))
          374         public_key, compressed = ECPubkey.from_signature65(sig65, h)
          375         # check public key using the address
          376         pubkey_hex = public_key.get_public_key_hex(compressed)
          377         for txin_type in ['p2pkh','p2wpkh','p2wpkh-p2sh']:
          378             addr = pubkey_to_address(txin_type, pubkey_hex, net=net)
          379             if address == addr:
          380                 break
          381         else:
          382             raise Exception("Bad signature")
          383         # check message
          384         public_key.verify_message_hash(sig65[1:], h)
          385         return True
          386     except Exception as e:
          387         _logger.info(f"Verification error: {repr(e)}")
          388         return False
          389 
          390 
          391 def is_secret_within_curve_range(secret: Union[int, bytes]) -> bool:
          392     if isinstance(secret, bytes):
          393         secret = string_to_number(secret)
          394     return 0 < secret < CURVE_ORDER
          395 
          396 
          397 class ECPrivkey(ECPubkey):
          398 
          399     def __init__(self, privkey_bytes: bytes):
          400         assert_bytes(privkey_bytes)
          401         if len(privkey_bytes) != 32:
          402             raise Exception('unexpected size for secret. should be 32 bytes, not {}'.format(len(privkey_bytes)))
          403         secret = string_to_number(privkey_bytes)
          404         if not is_secret_within_curve_range(secret):
          405             raise InvalidECPointException('Invalid secret scalar (not within curve order)')
          406         self.secret_scalar = secret
          407 
          408         pubkey = GENERATOR * secret
          409         super().__init__(pubkey.get_public_key_bytes(compressed=False))
          410 
          411     @classmethod
          412     def from_secret_scalar(cls, secret_scalar: int):
          413         secret_bytes = int.to_bytes(secret_scalar, length=32, byteorder='big', signed=False)
          414         return ECPrivkey(secret_bytes)
          415 
          416     @classmethod
          417     def from_arbitrary_size_secret(cls, privkey_bytes: bytes):
          418         """This method is only for legacy reasons. Do not introduce new code that uses it.
          419         Unlike the default constructor, this method does not require len(privkey_bytes) == 32,
          420         and the secret does not need to be within the curve order either.
          421         """
          422         return ECPrivkey(cls.normalize_secret_bytes(privkey_bytes))
          423 
          424     @classmethod
          425     def normalize_secret_bytes(cls, privkey_bytes: bytes) -> bytes:
          426         scalar = string_to_number(privkey_bytes) % CURVE_ORDER
          427         if scalar == 0:
          428             raise Exception('invalid EC private key scalar: zero')
          429         privkey_32bytes = int.to_bytes(scalar, length=32, byteorder='big', signed=False)
          430         return privkey_32bytes
          431 
          432     def __repr__(self):
          433         return f"<ECPrivkey {self.get_public_key_hex()}>"
          434 
          435     @classmethod
          436     def generate_random_key(cls):
          437         randint = randrange(CURVE_ORDER)
          438         ephemeral_exponent = int.to_bytes(randint, length=32, byteorder='big', signed=False)
          439         return ECPrivkey(ephemeral_exponent)
          440 
          441     def get_secret_bytes(self) -> bytes:
          442         return int.to_bytes(self.secret_scalar, length=32, byteorder='big', signed=False)
          443 
          444     def sign(self, msg_hash: bytes, sigencode=None) -> bytes:
          445         if not (isinstance(msg_hash, bytes) and len(msg_hash) == 32):
          446             raise Exception("msg_hash to be signed must be bytes, and 32 bytes exactly")
          447         if sigencode is None:
          448             sigencode = sig_string_from_r_and_s
          449 
          450         privkey_bytes = self.secret_scalar.to_bytes(32, byteorder="big")
          451         nonce_function = None
          452         sig = create_string_buffer(64)
          453         def sign_with_extra_entropy(extra_entropy):
          454             ret = _libsecp256k1.secp256k1_ecdsa_sign(
          455                 _libsecp256k1.ctx, sig, msg_hash, privkey_bytes,
          456                 nonce_function, extra_entropy)
          457             if not ret:
          458                 raise Exception('the nonce generation function failed, or the private key was invalid')
          459             compact_signature = create_string_buffer(64)
          460             _libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig)
          461             r = int.from_bytes(compact_signature[:32], byteorder="big")
          462             s = int.from_bytes(compact_signature[32:], byteorder="big")
          463             return r, s
          464 
          465         r, s = sign_with_extra_entropy(extra_entropy=None)
          466         counter = 0
          467         while r >= 2**255:  # grind for low R value https://github.com/bitcoin/bitcoin/pull/13666
          468             counter += 1
          469             extra_entropy = counter.to_bytes(32, byteorder="little")
          470             r, s = sign_with_extra_entropy(extra_entropy=extra_entropy)
          471 
          472         sig_string = sig_string_from_r_and_s(r, s)
          473         self.verify_message_hash(sig_string, msg_hash)
          474 
          475         sig = sigencode(r, s)
          476         return sig
          477 
          478     def sign_transaction(self, hashed_preimage: bytes) -> bytes:
          479         return self.sign(hashed_preimage, sigencode=der_sig_from_r_and_s)
          480 
          481     def sign_message(self, message: bytes, is_compressed: bool, algo=lambda x: sha256d(msg_magic(x))) -> bytes:
          482         def bruteforce_recid(sig_string):
          483             for recid in range(4):
          484                 sig65 = construct_sig65(sig_string, recid, is_compressed)
          485                 try:
          486                     self.verify_message_for_address(sig65, message, algo)
          487                     return sig65, recid
          488                 except Exception as e:
          489                     continue
          490             else:
          491                 raise Exception("error: cannot sign message. no recid fits..")
          492 
          493         message = to_bytes(message, 'utf8')
          494         msg_hash = algo(message)
          495         sig_string = self.sign(msg_hash, sigencode=sig_string_from_r_and_s)
          496         sig65, recid = bruteforce_recid(sig_string)
          497         return sig65
          498 
          499     def decrypt_message(self, encrypted: Union[str, bytes], magic: bytes=b'BIE1') -> bytes:
          500         encrypted = base64.b64decode(encrypted)  # type: bytes
          501         if len(encrypted) < 85:
          502             raise Exception('invalid ciphertext: length')
          503         magic_found = encrypted[:4]
          504         ephemeral_pubkey_bytes = encrypted[4:37]
          505         ciphertext = encrypted[37:-32]
          506         mac = encrypted[-32:]
          507         if magic_found != magic:
          508             raise Exception('invalid ciphertext: invalid magic bytes')
          509         try:
          510             ephemeral_pubkey = ECPubkey(ephemeral_pubkey_bytes)
          511         except InvalidECPointException as e:
          512             raise Exception('invalid ciphertext: invalid ephemeral pubkey') from e
          513         ecdh_key = (ephemeral_pubkey * self.secret_scalar).get_public_key_bytes(compressed=True)
          514         key = hashlib.sha512(ecdh_key).digest()
          515         iv, key_e, key_m = key[0:16], key[16:32], key[32:]
          516         if mac != hmac_oneshot(key_m, encrypted[:-32], hashlib.sha256):
          517             raise InvalidPassword()
          518         return aes_decrypt_with_iv(key_e, iv, ciphertext)
          519 
          520 
          521 def construct_sig65(sig_string: bytes, recid: int, is_compressed: bool) -> bytes:
          522     comp = 4 if is_compressed else 0
          523     return bytes([27 + recid + comp]) + sig_string