From 647258a00145bdb6ed72f1022a9a7c7083125529 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 27 May 2024 05:26:36 +0400 Subject: [PATCH 1/7] 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 From 9e8e3e5127aa83ec152180fece06c896e5fbfb54 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 2 Jun 2024 14:31:22 +0400 Subject: [PATCH 2/7] Syntax errors fixed --- electrum_dash/bitcoin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electrum_dash/bitcoin.py b/electrum_dash/bitcoin.py index 34a01cebc5..d70881700c 100644 --- a/electrum_dash/bitcoin.py +++ b/electrum_dash/bitcoin.py @@ -362,9 +362,9 @@ def b58_address_to_hash160(addr: str) -> Tuple[int, bytes]: _bytes = DecodeBase58Check(addr) if len(_bytes) != 21 and len(_bytes) != 23: raise Exception(f'expected 21 payload bytes in base58 address. got: {len(_bytes)}') - if len(_bytes) == 21 + if len(_bytes) == 21: return _bytes[0], _bytes[1:21] - elif len(_bytes) == 23 + elif len(_bytes) == 23: return _bytes[0], _bytes[3:23] return None From 0900ea3c723c4a13c6f726de7027b301f72a2365 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 3 Jun 2024 11:15:11 +0400 Subject: [PATCH 3/7] More bug fixed --- electrum_dash/bitcoin.py | 2 +- electrum_dash/transaction.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/electrum_dash/bitcoin.py b/electrum_dash/bitcoin.py index d70881700c..35c960de22 100644 --- a/electrum_dash/bitcoin.py +++ b/electrum_dash/bitcoin.py @@ -365,7 +365,7 @@ def b58_address_to_hash160(addr: str) -> Tuple[int, bytes]: if len(_bytes) == 21: return _bytes[0], _bytes[1:21] elif len(_bytes) == 23: - return _bytes[0], _bytes[3:23] + return _bytes[1], _bytes[3:23] return None def hash160_to_p2pkh(h160: bytes, *, net=None) -> str: diff --git a/electrum_dash/transaction.py b/electrum_dash/transaction.py index 5f9a100384..2a4de690dd 100644 --- a/electrum_dash/transaction.py +++ b/electrum_dash/transaction.py @@ -475,7 +475,7 @@ def get_address_from_output_script(_bytes: bytes, *, net=None) -> Optional[str]: # exp2pkh if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_EXP2PKH): - return hash160_to_exp2pkh(decoded[2][1], net=net) + return hash160_to_exp2pkh(decoded[3][1], net=net) # p2sh if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2SH): From a8d1255741a38e7960108b8a7c4217cb049febe4 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 9 Jun 2024 15:47:04 +0400 Subject: [PATCH 4/7] Bug fixed --- electrum_dash/bitcoin.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/electrum_dash/bitcoin.py b/electrum_dash/bitcoin.py index 35c960de22..86812ccf26 100644 --- a/electrum_dash/bitcoin.py +++ b/electrum_dash/bitcoin.py @@ -352,6 +352,11 @@ def hash_decode(x: str) -> bytes: def hash160_to_b58_address(h160: bytes, addrtype: int) -> str: s = bytes([addrtype]) + h160 + if addrtype == 185: + first_byte = 1 + third_byte = 187 + s = first_byte.to_bytes(1, 'little') + bytes([addrtype]) + third_byte.to_bytes(1, 'little') + h160 + s = s + sha256d(s)[0:4] res = base_encode(s, base=58) return res From d1205f969ec4ed3e31017be1ca9865a21a96b5ec Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 10 Jun 2024 11:40:16 +0400 Subject: [PATCH 5/7] Review comments applied --- electrum_dash/bitcoin.py | 5 +---- electrum_dash/transaction.py | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/electrum_dash/bitcoin.py b/electrum_dash/bitcoin.py index 86812ccf26..f0e641814f 100644 --- a/electrum_dash/bitcoin.py +++ b/electrum_dash/bitcoin.py @@ -353,10 +353,7 @@ def hash_decode(x: str) -> bytes: def hash160_to_b58_address(h160: bytes, addrtype: int) -> str: s = bytes([addrtype]) + h160 if addrtype == 185: - first_byte = 1 - third_byte = 187 - s = first_byte.to_bytes(1, 'little') + bytes([addrtype]) + third_byte.to_bytes(1, 'little') + h160 - + s = bytes([1, addrtype, 187]) + h160 s = s + sha256d(s)[0:4] res = base_encode(s, base=58) return res diff --git a/electrum_dash/transaction.py b/electrum_dash/transaction.py index 2a4de690dd..522bd93056 100644 --- a/electrum_dash/transaction.py +++ b/electrum_dash/transaction.py @@ -42,12 +42,12 @@ from . import ecc, bitcoin, constants, bip32 from .bip32 import BIP32Node -from .util import profiler, to_bytes, bh2u, bfh, chunks, is_hex_str +from .util import 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_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, + int_to_hex, b58_address_to_hash160, + opcodes, base_decode, base_encode, construct_script) from .crypto import sha256d from .dash_tx import (ProTxBase, read_extra_payload, serialize_extra_payload, From dbb3529f44527f279a945001e2dd5c64406dd6d3 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 1 Jul 2024 02:14:19 +0400 Subject: [PATCH 6/7] Exchange address/Trezor signing issue fixed --- electrum_dash/plugins/trezor/trezor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electrum_dash/plugins/trezor/trezor.py b/electrum_dash/plugins/trezor/trezor.py index 812febe67a..cd6a64a1e1 100644 --- a/electrum_dash/plugins/trezor/trezor.py +++ b/electrum_dash/plugins/trezor/trezor.py @@ -320,14 +320,14 @@ def get_xpub(self, device_id, derivation, xtype, wizard): return xpub def get_trezor_input_script_type(self, electrum_txin_type: str): - if electrum_txin_type in ('p2pkh',): + if electrum_txin_type in ('p2pkh', 'exp2pkh'): return InputScriptType.SPENDADDRESS if electrum_txin_type in ('p2sh',): return InputScriptType.SPENDMULTISIG raise ValueError('unexpected txin type: {}'.format(electrum_txin_type)) def get_trezor_output_script_type(self, electrum_txin_type: str): - if electrum_txin_type in ('p2pkh',): + if electrum_txin_type in ('p2pkh', 'exp2pkh'): return OutputScriptType.PAYTOADDRESS if electrum_txin_type in ('p2sh',): return OutputScriptType.PAYTOMULTISIG From 0cda598a8dd7f9a1eb036be79143004508e7bd89 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 1 Jul 2024 03:15:28 +0400 Subject: [PATCH 7/7] Exchange address/Ledger signing issue fixed --- electrum_dash/plugins/ledger/ledger.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/electrum_dash/plugins/ledger/ledger.py b/electrum_dash/plugins/ledger/ledger.py index 678cb3244f..fd5da3689e 100644 --- a/electrum_dash/plugins/ledger/ledger.py +++ b/electrum_dash/plugins/ledger/ledger.py @@ -532,8 +532,10 @@ 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 or v == constants.net.ADDRTYPE_EXP2PKH: + if v == constants.net.ADDRTYPE_P2PKH: output = hash160_to_b58_address(h, 0) + if v == constants.net.ADDRTYPE_EXP2PKH: + output = hash160_to_b58_address(h, 185) self.handler.show_message(_("Confirm Transaction on your Ledger device...")) try: