From 647258a00145bdb6ed72f1022a9a7c7083125529 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 27 May 2024 05:26:36 +0400 Subject: [PATCH] Exchange address support --- electrum_dash/bitcoin.py | 37 +++++++++++++++++++++++--- electrum_dash/constants.py | 4 +++ electrum_dash/plugins/ledger/ledger.py | 3 ++- electrum_dash/transaction.py | 21 +++++++++++++-- 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/electrum_dash/bitcoin.py b/electrum_dash/bitcoin.py index adf54ae66d..34a01cebc5 100644 --- a/electrum_dash/bitcoin.py +++ b/electrum_dash/bitcoin.py @@ -192,6 +192,8 @@ class opcodes(IntEnum): OP_INVALIDOPCODE = 0xff + OP_EXCHANGEADDR = 0xe0 + def hex(self) -> str: return bytes([self]).hex() @@ -358,15 +360,22 @@ def hash160_to_b58_address(h160: bytes, addrtype: int) -> str: def b58_address_to_hash160(addr: str) -> Tuple[int, bytes]: addr = to_bytes(addr, 'ascii') _bytes = DecodeBase58Check(addr) - if len(_bytes) != 21: + if len(_bytes) != 21 and len(_bytes) != 23: raise Exception(f'expected 21 payload bytes in base58 address. got: {len(_bytes)}') - return _bytes[0], _bytes[1:21] - + if len(_bytes) == 21 + return _bytes[0], _bytes[1:21] + elif len(_bytes) == 23 + return _bytes[0], _bytes[3:23] + return None def hash160_to_p2pkh(h160: bytes, *, net=None) -> str: if net is None: net = constants.net return hash160_to_b58_address(h160, net.ADDRTYPE_P2PKH) +def hash160_to_exp2pkh(h160: bytes, *, net=None) -> str: + if net is None: net = constants.net + return hash160_to_b58_address(h160, net.ADDRTYPE_EXP2PKH) + def hash160_to_p2sh(h160: bytes, *, net=None) -> str: if net is None: net = constants.net return hash160_to_b58_address(h160, net.ADDRTYPE_P2SH) @@ -375,10 +384,16 @@ def public_key_to_p2pkh(public_key: bytes, *, net=None) -> str: if net is None: net = constants.net return hash160_to_p2pkh(hash_160(public_key), net=net) +def public_key_to_exp2pkh(public_key: bytes, *, net=None) -> str: + if net is None: net = constants.net + return hash160_to_exp2pkh(hash_160(public_key), net=net) + def pubkey_to_address(txin_type: str, pubkey: str, *, net=None) -> str: if net is None: net = constants.net if txin_type == 'p2pkh': return public_key_to_p2pkh(bfh(pubkey), net=net) + elif txin_type == 'exp2pkh': + return public_key_to_exp2pkh(bfh(pubkey), net=net) else: raise NotImplementedError(txin_type) @@ -405,6 +420,8 @@ def address_to_script(addr: str, *, net=None) -> str: addrtype, hash_160_ = b58_address_to_hash160(addr) if addrtype == net.ADDRTYPE_P2PKH: script = pubkeyhash_to_p2pkh_script(bh2u(hash_160_)) + elif addrtype == net.ADDRTYPE_EXP2PKH: + script = pubkeyhash_to_exp2pkh_script(bh2u(hash_160_)) elif addrtype == net.ADDRTYPE_P2SH: script = construct_script([opcodes.OP_HASH160, hash_160_, opcodes.OP_EQUAL]) else: @@ -417,6 +434,7 @@ class OnchainOutputType(Enum): In case of p2sh, p2wsh and similar, no knowledge of redeem script, etc. """ P2PKH = enum.auto() + EXP2PKH = enum.auto() P2SH = enum.auto() @@ -428,6 +446,8 @@ def address_to_hash(addr: str, *, net=None) -> Tuple[OnchainOutputType, bytes]: addrtype, hash_160_ = b58_address_to_hash160(addr) if addrtype == net.ADDRTYPE_P2PKH: return OnchainOutputType.P2PKH, hash_160_ + elif addrtype == net.ADDRTYPE_EXP2PKH: + return OnchainOutputType.EXP2PKH, hash_160_ elif addrtype == net.ADDRTYPE_P2SH: return OnchainOutputType.P2SH, hash_160_ raise BitcoinException(f"unknown address type: {addrtype}") @@ -454,6 +474,15 @@ def pubkeyhash_to_p2pkh_script(pubkey_hash160: str) -> str: opcodes.OP_CHECKSIG ]) +def pubkeyhash_to_exp2pkh_script(pubkey_hash160: str) -> str: + return construct_script([ + opcodes.OP_EXCHANGEADDR, + opcodes.OP_DUP, + opcodes.OP_HASH160, + pubkey_hash160, + opcodes.OP_EQUALVERIFY, + opcodes.OP_CHECKSIG + ]) __b58chars = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' assert len(__b58chars) == 58 @@ -641,7 +670,7 @@ def is_b58_address(addr: str, *, net=None) -> bool: addrtype, h = b58_address_to_hash160(addr) except Exception as e: return False - if addrtype not in [net.ADDRTYPE_P2PKH, net.ADDRTYPE_P2SH]: + if addrtype not in [net.ADDRTYPE_P2PKH, net.ADDRTYPE_P2SH, net.ADDRTYPE_EXP2PKH]: return False return True diff --git a/electrum_dash/constants.py b/electrum_dash/constants.py index 3fb68b66cd..a49134cc28 100644 --- a/electrum_dash/constants.py +++ b/electrum_dash/constants.py @@ -76,6 +76,7 @@ class AbstractNet: ADDRTYPE_P2SH: int GENESIS: str BIP44_COIN_TYPE: int + ADDRTYPE_EXP2PKH: int @classmethod def max_checkpoint(cls) -> int: @@ -92,6 +93,7 @@ class BitcoinMainnet(AbstractNet): TESTNET = False WIF_PREFIX = 210 ADDRTYPE_P2PKH = 82 # 0x52 + ADDRTYPE_EXP2PKH = 185 # 0xb9 ADDRTYPE_P2SH = 7 # 0x07 SEGWIT_HRP = "xzc" GENESIS = "4381deb85b1b2c9843c222944b616d997516dcbd6a964e1eaf0def0830695233" @@ -129,6 +131,7 @@ class BitcoinTestnet(AbstractNet): TESTNET = True WIF_PREFIX = 185 ADDRTYPE_P2PKH = 65 + ADDRTYPE_EXP2PKH = 185 # 0xb9 ADDRTYPE_P2SH = 178 SEGWIT_HRP = "txzc" GENESIS = "aa22adcc12becaf436027ffe62a8fb21b234c58c23865291e5dc52cf53f64fca" @@ -166,6 +169,7 @@ class BitcoinRegtest(AbstractNet): TESTNET = False WIF_PREFIX = 239 ADDRTYPE_P2PKH = 65 + ADDRTYPE_EXP2PKH = 185 # 0xb9 ADDRTYPE_P2SH = 178 SEGWIT_HRP = "txzc" GENESIS = "a42b98f04cc2916e8adfb5d9db8a2227c4629bc205748ed2f33180b636ee885b" diff --git a/electrum_dash/plugins/ledger/ledger.py b/electrum_dash/plugins/ledger/ledger.py index b3f71ae41d..678cb3244f 100644 --- a/electrum_dash/plugins/ledger/ledger.py +++ b/electrum_dash/plugins/ledger/ledger.py @@ -297,6 +297,7 @@ def perform_hw1_preflight(self): self.dongleObject.verifyPin(pin) if self.canAlternateCoinVersions: self.dongleObject.setAlternateCoinVersions(constants.net.ADDRTYPE_P2PKH, + constants.net.ADDRTYPE_EXP2PKH, constants.net.ADDRTYPE_P2SH) except BTChipException as e: if (e.sw == 0x6faa): @@ -531,7 +532,7 @@ def sign_transaction(self, tx, password): output = txout.address if not self.get_client_electrum().canAlternateCoinVersions: v, h = b58_address_to_hash160(output) - if v == constants.net.ADDRTYPE_P2PKH: + if v == constants.net.ADDRTYPE_P2PKH or v == constants.net.ADDRTYPE_EXP2PKH: output = hash160_to_b58_address(h, 0) self.handler.show_message(_("Confirm Transaction on your Ledger device...")) diff --git a/electrum_dash/transaction.py b/electrum_dash/transaction.py index f669940438..5f9a100384 100644 --- a/electrum_dash/transaction.py +++ b/electrum_dash/transaction.py @@ -44,7 +44,7 @@ from .bip32 import BIP32Node from .util import profiler, to_bytes, bh2u, bfh, chunks, is_hex_str from .bitcoin import (TYPE_ADDRESS, TYPE_SCRIPT, hash_160, - hash160_to_p2sh, hash160_to_p2pkh, + hash160_to_p2sh, hash160_to_p2pkh, hash160_to_exp2pkh, var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN, int_to_hex, push_script, b58_address_to_hash160, opcodes, add_number_to_script, base_decode, base_encode, @@ -421,6 +421,9 @@ def is_instance(cls, item): SCRIPTPUBKEY_TEMPLATE_P2PKH = [opcodes.OP_DUP, opcodes.OP_HASH160, OPPushDataGeneric(lambda x: x == 20), opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG] +SCRIPTPUBKEY_TEMPLATE_EXP2PKH = [opcodes.OP_EXCHANGEADDR, opcodes.OP_DUP, opcodes.OP_HASH160, + OPPushDataGeneric(lambda x: x == 20), + opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG] SCRIPTPUBKEY_TEMPLATE_P2SH = [opcodes.OP_HASH160, OPPushDataGeneric(lambda x: x == 20), opcodes.OP_EQUAL] @@ -454,6 +457,8 @@ def get_script_type_from_output_script(_bytes: bytes) -> Optional[str]: return None if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2PKH): return 'p2pkh' + if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_EXP2PKH): + return 'exp2pkh' if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2SH): return 'p2sh' return None @@ -468,6 +473,10 @@ def get_address_from_output_script(_bytes: bytes, *, net=None) -> Optional[str]: if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2PKH): return hash160_to_p2pkh(decoded[2][1], net=net) + # exp2pkh + if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_EXP2PKH): + return hash160_to_exp2pkh(decoded[2][1], net=net) + # p2sh if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2SH): return hash160_to_p2sh(decoded[1][1], net=net) @@ -675,6 +684,8 @@ def guess_txintype_from_address(cls, addr: Optional[str]) -> str: addrtype, hash_160_ = b58_address_to_hash160(addr) if addrtype == constants.net.ADDRTYPE_P2PKH: return 'p2pkh' + if addrtype == constants.net.ADDRTYPE_EXP2PKH: + return 'exp2pkh' elif addrtype == constants.net.ADDRTYPE_P2SH: return 'p2sh' raise Exception(f'unrecognized address: {repr(addr)}') @@ -721,6 +732,10 @@ def get_preimage_script(cls, txin: 'PartialTxInput') -> str: pubkey = pubkeys[0] pkh = bh2u(hash_160(bfh(pubkey))) return bitcoin.pubkeyhash_to_p2pkh_script(pkh) + elif txin.script_type in ['exp2pkh']: + pubkey = pubkeys[0] + pkh = bh2u(hash_160(bfh(pubkey))) + return bitcoin.pubkeyhash_to_exp2pkh_script(pkh) elif txin.script_type == 'p2pk': pubkey = pubkeys[0] return bitcoin.public_key_to_p2pk_script(pubkey) @@ -1242,6 +1257,8 @@ def set_script_type(self) -> None: type = inner_type + '-' + type if type in ('p2pkh', ): self.script_type = type + if type in ('exp2pkh', ): + self.script_type = type return def is_complete(self) -> bool: @@ -1256,7 +1273,7 @@ def is_complete(self) -> bool: # that are related to the wallet. # The 'fix' would be adding extra logic that matches on templates, # and figures out the script_type from available fields. - if self.script_type in ('p2pk', 'p2pkh'): + if self.script_type in ('p2pk', 'p2pkh', "exp2pkh"): return s >= 1 if self.script_type in ('p2sh', ): return s >= self.num_sig