Skip to content

Commit 36f64d1

Browse files
committed
bitcoin/ecc: some more type annotations
1 parent 5376d37 commit 36f64d1

5 files changed

Lines changed: 64 additions & 60 deletions

File tree

electrum/bitcoin.py

Lines changed: 51 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
# SOFTWARE.
2525

2626
import hashlib
27-
from typing import List, Tuple, TYPE_CHECKING
27+
from typing import List, Tuple, TYPE_CHECKING, Optional, Union
2828

2929
from .util import bfh, bh2u, BitcoinException, assert_bytes, to_bytes, inv_dict
3030
from . import version
@@ -49,7 +49,7 @@
4949
TYPE_SCRIPT = 2
5050

5151

52-
def rev_hex(s):
52+
def rev_hex(s: str) -> str:
5353
return bh2u(bfh(s)[::-1])
5454

5555

@@ -162,22 +162,25 @@ def dust_threshold(network: 'Network'=None) -> int:
162162
return 182 * 3 * relayfee(network) // 1000
163163

164164

165-
hash_encode = lambda x: bh2u(x[::-1])
166-
hash_decode = lambda x: bfh(x)[::-1]
167-
hmac_sha_512 = lambda x, y: hmac_oneshot(x, y, hashlib.sha512)
165+
def hash_encode(x: bytes) -> str:
166+
return bh2u(x[::-1])
167+
168+
169+
def hash_decode(x: str) -> bytes:
170+
return bfh(x)[::-1]
168171

169172

170173
################################## electrum seeds
171174

172175

173-
def is_new_seed(x, prefix=version.SEED_PREFIX):
176+
def is_new_seed(x: str, prefix=version.SEED_PREFIX) -> bool:
174177
from . import mnemonic
175178
x = mnemonic.normalize_text(x)
176-
s = bh2u(hmac_sha_512(b"Seed version", x.encode('utf8')))
179+
s = bh2u(hmac_oneshot(b"Seed version", x.encode('utf8'), hashlib.sha512))
177180
return s.startswith(prefix)
178181

179182

180-
def is_old_seed(seed):
183+
def is_old_seed(seed: str) -> bool:
181184
from . import old_mnemonic, mnemonic
182185
seed = mnemonic.normalize_text(seed)
183186
words = seed.split()
@@ -195,7 +198,7 @@ def is_old_seed(seed):
195198
return is_hex or (uses_electrum_words and (len(words) == 12 or len(words) == 24))
196199

197200

198-
def seed_type(x):
201+
def seed_type(x: str) -> str:
199202
if is_old_seed(x):
200203
return 'old'
201204
elif is_new_seed(x):
@@ -206,56 +209,58 @@ def seed_type(x):
206209
return '2fa'
207210
return ''
208211

209-
is_seed = lambda x: bool(seed_type(x))
212+
213+
def is_seed(x: str) -> bool:
214+
return bool(seed_type(x))
210215

211216

212217
############ functions from pywallet #####################
213218

214-
def hash160_to_b58_address(h160: bytes, addrtype):
215-
s = bytes([addrtype])
216-
s += h160
217-
return base_encode(s+sha256d(s)[0:4], base=58)
219+
def hash160_to_b58_address(h160: bytes, addrtype: int) -> str:
220+
s = bytes([addrtype]) + h160
221+
s = s + sha256d(s)[0:4]
222+
return base_encode(s, base=58)
218223

219224

220-
def b58_address_to_hash160(addr):
225+
def b58_address_to_hash160(addr: str) -> Tuple[int, bytes]:
221226
addr = to_bytes(addr, 'ascii')
222227
_bytes = base_decode(addr, 25, base=58)
223228
return _bytes[0], _bytes[1:21]
224229

225230

226-
def hash160_to_p2pkh(h160, *, net=None):
231+
def hash160_to_p2pkh(h160: bytes, *, net=None) -> str:
227232
if net is None:
228233
net = constants.net
229234
return hash160_to_b58_address(h160, net.ADDRTYPE_P2PKH)
230235

231-
def hash160_to_p2sh(h160, *, net=None):
236+
def hash160_to_p2sh(h160: bytes, *, net=None) -> str:
232237
if net is None:
233238
net = constants.net
234239
return hash160_to_b58_address(h160, net.ADDRTYPE_P2SH)
235240

236241
def public_key_to_p2pkh(public_key: bytes) -> str:
237242
return hash160_to_p2pkh(hash_160(public_key))
238243

239-
def hash_to_segwit_addr(h, witver, *, net=None):
244+
def hash_to_segwit_addr(h: bytes, witver: int, *, net=None) -> str:
240245
if net is None:
241246
net = constants.net
242247
return segwit_addr.encode(net.SEGWIT_HRP, witver, h)
243248

244-
def public_key_to_p2wpkh(public_key):
249+
def public_key_to_p2wpkh(public_key: bytes) -> str:
245250
return hash_to_segwit_addr(hash_160(public_key), witver=0)
246251

247-
def script_to_p2wsh(script):
252+
def script_to_p2wsh(script: str) -> str:
248253
return hash_to_segwit_addr(sha256(bfh(script)), witver=0)
249254

250-
def p2wpkh_nested_script(pubkey):
255+
def p2wpkh_nested_script(pubkey: str) -> str:
251256
pkh = bh2u(hash_160(bfh(pubkey)))
252257
return '00' + push_script(pkh)
253258

254-
def p2wsh_nested_script(witness_script):
259+
def p2wsh_nested_script(witness_script: str) -> str:
255260
wsh = bh2u(sha256(bfh(witness_script)))
256261
return '00' + push_script(wsh)
257262

258-
def pubkey_to_address(txin_type, pubkey):
263+
def pubkey_to_address(txin_type: str, pubkey: str) -> str:
259264
if txin_type == 'p2pkh':
260265
return public_key_to_p2pkh(bfh(pubkey))
261266
elif txin_type == 'p2wpkh':
@@ -266,7 +271,7 @@ def pubkey_to_address(txin_type, pubkey):
266271
else:
267272
raise NotImplementedError(txin_type)
268273

269-
def redeem_script_to_address(txin_type, redeem_script):
274+
def redeem_script_to_address(txin_type: str, redeem_script: str) -> str:
270275
if txin_type == 'p2sh':
271276
return hash160_to_p2sh(hash_160(bfh(redeem_script)))
272277
elif txin_type == 'p2wsh':
@@ -278,7 +283,7 @@ def redeem_script_to_address(txin_type, redeem_script):
278283
raise NotImplementedError(txin_type)
279284

280285

281-
def script_to_address(script, *, net=None):
286+
def script_to_address(script: str, *, net=None) -> str:
282287
from .transaction import get_address_from_output_script
283288
t, addr = get_address_from_output_script(bfh(script), net=net)
284289
assert t == TYPE_ADDRESS
@@ -310,15 +315,15 @@ def address_to_script(addr: str, *, net=None) -> str:
310315
raise BitcoinException(f'unknown address type: {addrtype}')
311316
return script
312317

313-
def address_to_scripthash(addr):
318+
def address_to_scripthash(addr: str) -> str:
314319
script = address_to_script(addr)
315320
return script_to_scripthash(script)
316321

317-
def script_to_scripthash(script):
318-
h = sha256(bytes.fromhex(script))[0:32]
322+
def script_to_scripthash(script: str) -> str:
323+
h = sha256(bfh(script))[0:32]
319324
return bh2u(bytes(reversed(h)))
320325

321-
def public_key_to_p2pk_script(pubkey):
326+
def public_key_to_p2pk_script(pubkey: str) -> str:
322327
script = push_script(pubkey)
323328
script += 'ac' # op_checksig
324329
return script
@@ -360,7 +365,7 @@ def base_encode(v: bytes, base: int) -> str:
360365
return result.decode('ascii')
361366

362367

363-
def base_decode(v, length, base):
368+
def base_decode(v: Union[bytes, str], length: Optional[int], base: int) -> Optional[bytes]:
364369
""" decode v into a string of len bytes."""
365370
# assert_bytes(v)
366371
v = to_bytes(v, 'ascii')
@@ -398,21 +403,20 @@ class InvalidChecksum(Exception):
398403
pass
399404

400405

401-
def EncodeBase58Check(vchIn):
406+
def EncodeBase58Check(vchIn: bytes) -> str:
402407
hash = sha256d(vchIn)
403408
return base_encode(vchIn + hash[0:4], base=58)
404409

405410

406-
def DecodeBase58Check(psz):
411+
def DecodeBase58Check(psz: Union[bytes, str]) -> bytes:
407412
vchRet = base_decode(psz, None, base=58)
408-
key = vchRet[0:-4]
409-
csum = vchRet[-4:]
410-
hash = sha256d(key)
411-
cs32 = hash[0:4]
412-
if cs32 != csum:
413-
raise InvalidChecksum('expected {}, actual {}'.format(bh2u(cs32), bh2u(csum)))
413+
payload = vchRet[0:-4]
414+
csum_found = vchRet[-4:]
415+
csum_calculated = sha256d(payload)[0:4]
416+
if csum_calculated != csum_found:
417+
raise InvalidChecksum(f'calculated {bh2u(csum_calculated)}, found {bh2u(csum_found)}')
414418
else:
415-
return key
419+
return payload
416420

417421

418422
# backwards compat
@@ -484,24 +488,24 @@ def deserialize_privkey(key: str) -> Tuple[str, bytes, bool]:
484488
return txin_type, secret_bytes, compressed
485489

486490

487-
def is_compressed(sec):
491+
def is_compressed_privkey(sec: str) -> bool:
488492
return deserialize_privkey(sec)[2]
489493

490494

491-
def address_from_private_key(sec):
495+
def address_from_private_key(sec: str) -> str:
492496
txin_type, privkey, compressed = deserialize_privkey(sec)
493497
public_key = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
494498
return pubkey_to_address(txin_type, public_key)
495499

496-
def is_segwit_address(addr, *, net=None):
500+
def is_segwit_address(addr: str, *, net=None) -> bool:
497501
if net is None: net = constants.net
498502
try:
499503
witver, witprog = segwit_addr.decode(net.SEGWIT_HRP, addr)
500504
except Exception as e:
501505
return False
502506
return witprog is not None
503507

504-
def is_b58_address(addr, *, net=None):
508+
def is_b58_address(addr: str, *, net=None) -> bool:
505509
if net is None: net = constants.net
506510
try:
507511
addrtype, h = b58_address_to_hash160(addr)
@@ -511,13 +515,13 @@ def is_b58_address(addr, *, net=None):
511515
return False
512516
return addr == hash160_to_b58_address(h, addrtype)
513517

514-
def is_address(addr, *, net=None):
518+
def is_address(addr: str, *, net=None) -> bool:
515519
if net is None: net = constants.net
516520
return is_segwit_address(addr, net=net) \
517521
or is_b58_address(addr, net=net)
518522

519523

520-
def is_private_key(key):
524+
def is_private_key(key: str) -> bool:
521525
try:
522526
k = deserialize_privkey(key)
523527
return k is not False
@@ -527,7 +531,7 @@ def is_private_key(key):
527531

528532
########### end pywallet functions #######################
529533

530-
def is_minikey(text):
534+
def is_minikey(text: str) -> bool:
531535
# Minikeys are typically 22 or 30 characters, but this routine
532536
# permits any length of 20 or more provided the minikey is valid.
533537
# A valid minikey must begin with an 'S', be in base58, and when
@@ -537,5 +541,5 @@ def is_minikey(text):
537541
and all(ord(c) in __b58chars for c in text)
538542
and sha256(text + '?')[0] == 0x00)
539543

540-
def minikey_to_private_key(text):
544+
def minikey_to_private_key(text: str) -> bytes:
541545
return sha256(text)

electrum/ecc.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,31 +52,31 @@ def point_at_infinity():
5252
return ECPubkey(None)
5353

5454

55-
def sig_string_from_der_sig(der_sig, order=CURVE_ORDER):
55+
def sig_string_from_der_sig(der_sig: bytes, order=CURVE_ORDER) -> bytes:
5656
r, s = ecdsa.util.sigdecode_der(der_sig, order)
5757
return ecdsa.util.sigencode_string(r, s, order)
5858

5959

60-
def der_sig_from_sig_string(sig_string, order=CURVE_ORDER):
60+
def der_sig_from_sig_string(sig_string: bytes, order=CURVE_ORDER) -> bytes:
6161
r, s = ecdsa.util.sigdecode_string(sig_string, order)
6262
return ecdsa.util.sigencode_der_canonize(r, s, order)
6363

6464

65-
def der_sig_from_r_and_s(r, s, order=CURVE_ORDER):
65+
def der_sig_from_r_and_s(r: int, s: int, order=CURVE_ORDER) -> bytes:
6666
return ecdsa.util.sigencode_der_canonize(r, s, order)
6767

6868

69-
def get_r_and_s_from_der_sig(der_sig, order=CURVE_ORDER):
69+
def get_r_and_s_from_der_sig(der_sig: bytes, order=CURVE_ORDER) -> Tuple[int, int]:
7070
r, s = ecdsa.util.sigdecode_der(der_sig, order)
7171
return r, s
7272

7373

74-
def get_r_and_s_from_sig_string(sig_string, order=CURVE_ORDER):
74+
def get_r_and_s_from_sig_string(sig_string: bytes, order=CURVE_ORDER) -> Tuple[int, int]:
7575
r, s = ecdsa.util.sigdecode_string(sig_string, order)
7676
return r, s
7777

7878

79-
def sig_string_from_r_and_s(r, s, order=CURVE_ORDER):
79+
def sig_string_from_r_and_s(r: int, s: int, order=CURVE_ORDER) -> bytes:
8080
return ecdsa.util.sigencode_string_canonize(r, s, order)
8181

8282

@@ -410,7 +410,7 @@ def bruteforce_recid(sig_string):
410410
sig65, recid = bruteforce_recid(sig_string)
411411
return sig65
412412

413-
def decrypt_message(self, encrypted, magic=b'BIE1'):
413+
def decrypt_message(self, encrypted: Tuple[str, bytes], magic: bytes=b'BIE1') -> bytes:
414414
encrypted = base64.b64decode(encrypted)
415415
if len(encrypted) < 85:
416416
raise Exception('invalid ciphertext: length')
@@ -435,6 +435,6 @@ def decrypt_message(self, encrypted, magic=b'BIE1'):
435435
return aes_decrypt_with_iv(key_e, iv, ciphertext)
436436

437437

438-
def construct_sig65(sig_string, recid, is_compressed):
438+
def construct_sig65(sig_string: bytes, recid: int, is_compressed: bool) -> bytes:
439439
comp = 4 if is_compressed else 0
440440
return bytes([27 + recid + comp]) + sig_string

electrum/mnemonic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def is_CJK(c):
7474
return False
7575

7676

77-
def normalize_text(seed):
77+
def normalize_text(seed: str) -> str:
7878
# normalize
7979
seed = unicodedata.normalize('NFKD', seed)
8080
# lower

electrum/paymentrequest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ def sign_request_with_alias(pr, alias, alias_privkey):
336336
pr.pki_data = str(alias)
337337
message = pr.SerializeToString()
338338
ec_key = ecc.ECPrivkey(alias_privkey)
339-
compressed = bitcoin.is_compressed(alias_privkey)
339+
compressed = bitcoin.is_compressed_privkey(alias_privkey)
340340
pr.signature = ec_key.sign_message(message, compressed)
341341

342342

electrum/tests/test_bitcoin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
var_int, op_push, address_to_script,
77
deserialize_privkey, serialize_privkey, is_segwit_address,
88
is_b58_address, address_to_scripthash, is_minikey,
9-
is_compressed, seed_type, EncodeBase58Check,
9+
is_compressed_privkey, seed_type, EncodeBase58Check,
1010
script_num_to_hex, push_script, add_number_to_script, int_to_hex)
1111
from electrum.bip32 import (bip32_root, bip32_public_derivation, bip32_private_derivation,
1212
xpub_from_xprv, xpub_type, is_xprv, is_bip32_derivation,
@@ -710,10 +710,10 @@ def test_is_minikey(self):
710710
self.assertEqual(minikey, is_minikey(priv))
711711

712712
@needs_test_with_all_ecc_implementations
713-
def test_is_compressed(self):
713+
def test_is_compressed_privkey(self):
714714
for priv_details in self.priv_pub_addr:
715715
self.assertEqual(priv_details['compressed'],
716-
is_compressed(priv_details['priv']))
716+
is_compressed_privkey(priv_details['priv']))
717717

718718

719719
class Test_seeds(SequentialTestCase):

0 commit comments

Comments
 (0)