electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

ecc.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