Skip to content

Commit 647258a

Browse files
Exchange address support
1 parent 137e8e8 commit 647258a

File tree

4 files changed

+58
-7
lines changed

4 files changed

+58
-7
lines changed

electrum_dash/bitcoin.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ class opcodes(IntEnum):
192192

193193
OP_INVALIDOPCODE = 0xff
194194

195+
OP_EXCHANGEADDR = 0xe0
196+
195197
def hex(self) -> str:
196198
return bytes([self]).hex()
197199

@@ -358,15 +360,22 @@ def hash160_to_b58_address(h160: bytes, addrtype: int) -> str:
358360
def b58_address_to_hash160(addr: str) -> Tuple[int, bytes]:
359361
addr = to_bytes(addr, 'ascii')
360362
_bytes = DecodeBase58Check(addr)
361-
if len(_bytes) != 21:
363+
if len(_bytes) != 21 and len(_bytes) != 23:
362364
raise Exception(f'expected 21 payload bytes in base58 address. got: {len(_bytes)}')
363-
return _bytes[0], _bytes[1:21]
364-
365+
if len(_bytes) == 21
366+
return _bytes[0], _bytes[1:21]
367+
elif len(_bytes) == 23
368+
return _bytes[0], _bytes[3:23]
369+
return None
365370

366371
def hash160_to_p2pkh(h160: bytes, *, net=None) -> str:
367372
if net is None: net = constants.net
368373
return hash160_to_b58_address(h160, net.ADDRTYPE_P2PKH)
369374

375+
def hash160_to_exp2pkh(h160: bytes, *, net=None) -> str:
376+
if net is None: net = constants.net
377+
return hash160_to_b58_address(h160, net.ADDRTYPE_EXP2PKH)
378+
370379
def hash160_to_p2sh(h160: bytes, *, net=None) -> str:
371380
if net is None: net = constants.net
372381
return hash160_to_b58_address(h160, net.ADDRTYPE_P2SH)
@@ -375,10 +384,16 @@ def public_key_to_p2pkh(public_key: bytes, *, net=None) -> str:
375384
if net is None: net = constants.net
376385
return hash160_to_p2pkh(hash_160(public_key), net=net)
377386

387+
def public_key_to_exp2pkh(public_key: bytes, *, net=None) -> str:
388+
if net is None: net = constants.net
389+
return hash160_to_exp2pkh(hash_160(public_key), net=net)
390+
378391
def pubkey_to_address(txin_type: str, pubkey: str, *, net=None) -> str:
379392
if net is None: net = constants.net
380393
if txin_type == 'p2pkh':
381394
return public_key_to_p2pkh(bfh(pubkey), net=net)
395+
elif txin_type == 'exp2pkh':
396+
return public_key_to_exp2pkh(bfh(pubkey), net=net)
382397
else:
383398
raise NotImplementedError(txin_type)
384399

@@ -405,6 +420,8 @@ def address_to_script(addr: str, *, net=None) -> str:
405420
addrtype, hash_160_ = b58_address_to_hash160(addr)
406421
if addrtype == net.ADDRTYPE_P2PKH:
407422
script = pubkeyhash_to_p2pkh_script(bh2u(hash_160_))
423+
elif addrtype == net.ADDRTYPE_EXP2PKH:
424+
script = pubkeyhash_to_exp2pkh_script(bh2u(hash_160_))
408425
elif addrtype == net.ADDRTYPE_P2SH:
409426
script = construct_script([opcodes.OP_HASH160, hash_160_, opcodes.OP_EQUAL])
410427
else:
@@ -417,6 +434,7 @@ class OnchainOutputType(Enum):
417434
In case of p2sh, p2wsh and similar, no knowledge of redeem script, etc.
418435
"""
419436
P2PKH = enum.auto()
437+
EXP2PKH = enum.auto()
420438
P2SH = enum.auto()
421439

422440

@@ -428,6 +446,8 @@ def address_to_hash(addr: str, *, net=None) -> Tuple[OnchainOutputType, bytes]:
428446
addrtype, hash_160_ = b58_address_to_hash160(addr)
429447
if addrtype == net.ADDRTYPE_P2PKH:
430448
return OnchainOutputType.P2PKH, hash_160_
449+
elif addrtype == net.ADDRTYPE_EXP2PKH:
450+
return OnchainOutputType.EXP2PKH, hash_160_
431451
elif addrtype == net.ADDRTYPE_P2SH:
432452
return OnchainOutputType.P2SH, hash_160_
433453
raise BitcoinException(f"unknown address type: {addrtype}")
@@ -454,6 +474,15 @@ def pubkeyhash_to_p2pkh_script(pubkey_hash160: str) -> str:
454474
opcodes.OP_CHECKSIG
455475
])
456476

477+
def pubkeyhash_to_exp2pkh_script(pubkey_hash160: str) -> str:
478+
return construct_script([
479+
opcodes.OP_EXCHANGEADDR,
480+
opcodes.OP_DUP,
481+
opcodes.OP_HASH160,
482+
pubkey_hash160,
483+
opcodes.OP_EQUALVERIFY,
484+
opcodes.OP_CHECKSIG
485+
])
457486

458487
__b58chars = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
459488
assert len(__b58chars) == 58
@@ -641,7 +670,7 @@ def is_b58_address(addr: str, *, net=None) -> bool:
641670
addrtype, h = b58_address_to_hash160(addr)
642671
except Exception as e:
643672
return False
644-
if addrtype not in [net.ADDRTYPE_P2PKH, net.ADDRTYPE_P2SH]:
673+
if addrtype not in [net.ADDRTYPE_P2PKH, net.ADDRTYPE_P2SH, net.ADDRTYPE_EXP2PKH]:
645674
return False
646675
return True
647676

electrum_dash/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class AbstractNet:
7676
ADDRTYPE_P2SH: int
7777
GENESIS: str
7878
BIP44_COIN_TYPE: int
79+
ADDRTYPE_EXP2PKH: int
7980

8081
@classmethod
8182
def max_checkpoint(cls) -> int:
@@ -92,6 +93,7 @@ class BitcoinMainnet(AbstractNet):
9293
TESTNET = False
9394
WIF_PREFIX = 210
9495
ADDRTYPE_P2PKH = 82 # 0x52
96+
ADDRTYPE_EXP2PKH = 185 # 0xb9
9597
ADDRTYPE_P2SH = 7 # 0x07
9698
SEGWIT_HRP = "xzc"
9799
GENESIS = "4381deb85b1b2c9843c222944b616d997516dcbd6a964e1eaf0def0830695233"
@@ -129,6 +131,7 @@ class BitcoinTestnet(AbstractNet):
129131
TESTNET = True
130132
WIF_PREFIX = 185
131133
ADDRTYPE_P2PKH = 65
134+
ADDRTYPE_EXP2PKH = 185 # 0xb9
132135
ADDRTYPE_P2SH = 178
133136
SEGWIT_HRP = "txzc"
134137
GENESIS = "aa22adcc12becaf436027ffe62a8fb21b234c58c23865291e5dc52cf53f64fca"
@@ -166,6 +169,7 @@ class BitcoinRegtest(AbstractNet):
166169
TESTNET = False
167170
WIF_PREFIX = 239
168171
ADDRTYPE_P2PKH = 65
172+
ADDRTYPE_EXP2PKH = 185 # 0xb9
169173
ADDRTYPE_P2SH = 178
170174
SEGWIT_HRP = "txzc"
171175
GENESIS = "a42b98f04cc2916e8adfb5d9db8a2227c4629bc205748ed2f33180b636ee885b"

electrum_dash/plugins/ledger/ledger.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ def perform_hw1_preflight(self):
297297
self.dongleObject.verifyPin(pin)
298298
if self.canAlternateCoinVersions:
299299
self.dongleObject.setAlternateCoinVersions(constants.net.ADDRTYPE_P2PKH,
300+
constants.net.ADDRTYPE_EXP2PKH,
300301
constants.net.ADDRTYPE_P2SH)
301302
except BTChipException as e:
302303
if (e.sw == 0x6faa):
@@ -531,7 +532,7 @@ def sign_transaction(self, tx, password):
531532
output = txout.address
532533
if not self.get_client_electrum().canAlternateCoinVersions:
533534
v, h = b58_address_to_hash160(output)
534-
if v == constants.net.ADDRTYPE_P2PKH:
535+
if v == constants.net.ADDRTYPE_P2PKH or v == constants.net.ADDRTYPE_EXP2PKH:
535536
output = hash160_to_b58_address(h, 0)
536537

537538
self.handler.show_message(_("Confirm Transaction on your Ledger device..."))

electrum_dash/transaction.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
from .bip32 import BIP32Node
4545
from .util import profiler, to_bytes, bh2u, bfh, chunks, is_hex_str
4646
from .bitcoin import (TYPE_ADDRESS, TYPE_SCRIPT, hash_160,
47-
hash160_to_p2sh, hash160_to_p2pkh,
47+
hash160_to_p2sh, hash160_to_p2pkh, hash160_to_exp2pkh,
4848
var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN,
4949
int_to_hex, push_script, b58_address_to_hash160,
5050
opcodes, add_number_to_script, base_decode, base_encode,
@@ -421,6 +421,9 @@ def is_instance(cls, item):
421421
SCRIPTPUBKEY_TEMPLATE_P2PKH = [opcodes.OP_DUP, opcodes.OP_HASH160,
422422
OPPushDataGeneric(lambda x: x == 20),
423423
opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG]
424+
SCRIPTPUBKEY_TEMPLATE_EXP2PKH = [opcodes.OP_EXCHANGEADDR, opcodes.OP_DUP, opcodes.OP_HASH160,
425+
OPPushDataGeneric(lambda x: x == 20),
426+
opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG]
424427
SCRIPTPUBKEY_TEMPLATE_P2SH = [opcodes.OP_HASH160, OPPushDataGeneric(lambda x: x == 20), opcodes.OP_EQUAL]
425428

426429

@@ -454,6 +457,8 @@ def get_script_type_from_output_script(_bytes: bytes) -> Optional[str]:
454457
return None
455458
if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2PKH):
456459
return 'p2pkh'
460+
if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_EXP2PKH):
461+
return 'exp2pkh'
457462
if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2SH):
458463
return 'p2sh'
459464
return None
@@ -468,6 +473,10 @@ def get_address_from_output_script(_bytes: bytes, *, net=None) -> Optional[str]:
468473
if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2PKH):
469474
return hash160_to_p2pkh(decoded[2][1], net=net)
470475

476+
# exp2pkh
477+
if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_EXP2PKH):
478+
return hash160_to_exp2pkh(decoded[2][1], net=net)
479+
471480
# p2sh
472481
if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2SH):
473482
return hash160_to_p2sh(decoded[1][1], net=net)
@@ -675,6 +684,8 @@ def guess_txintype_from_address(cls, addr: Optional[str]) -> str:
675684
addrtype, hash_160_ = b58_address_to_hash160(addr)
676685
if addrtype == constants.net.ADDRTYPE_P2PKH:
677686
return 'p2pkh'
687+
if addrtype == constants.net.ADDRTYPE_EXP2PKH:
688+
return 'exp2pkh'
678689
elif addrtype == constants.net.ADDRTYPE_P2SH:
679690
return 'p2sh'
680691
raise Exception(f'unrecognized address: {repr(addr)}')
@@ -721,6 +732,10 @@ def get_preimage_script(cls, txin: 'PartialTxInput') -> str:
721732
pubkey = pubkeys[0]
722733
pkh = bh2u(hash_160(bfh(pubkey)))
723734
return bitcoin.pubkeyhash_to_p2pkh_script(pkh)
735+
elif txin.script_type in ['exp2pkh']:
736+
pubkey = pubkeys[0]
737+
pkh = bh2u(hash_160(bfh(pubkey)))
738+
return bitcoin.pubkeyhash_to_exp2pkh_script(pkh)
724739
elif txin.script_type == 'p2pk':
725740
pubkey = pubkeys[0]
726741
return bitcoin.public_key_to_p2pk_script(pubkey)
@@ -1242,6 +1257,8 @@ def set_script_type(self) -> None:
12421257
type = inner_type + '-' + type
12431258
if type in ('p2pkh', ):
12441259
self.script_type = type
1260+
if type in ('exp2pkh', ):
1261+
self.script_type = type
12451262
return
12461263

12471264
def is_complete(self) -> bool:
@@ -1256,7 +1273,7 @@ def is_complete(self) -> bool:
12561273
# that are related to the wallet.
12571274
# The 'fix' would be adding extra logic that matches on templates,
12581275
# and figures out the script_type from available fields.
1259-
if self.script_type in ('p2pk', 'p2pkh'):
1276+
if self.script_type in ('p2pk', 'p2pkh', "exp2pkh"):
12601277
return s >= 1
12611278
if self.script_type in ('p2sh', ):
12621279
return s >= self.num_sig

0 commit comments

Comments
 (0)