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