Skip to content

Commit

Permalink
Exchange address support
Browse files Browse the repository at this point in the history
  • Loading branch information
levonpetrosyan93 committed May 27, 2024
1 parent 137e8e8 commit 647258a
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 7 deletions.
37 changes: 33 additions & 4 deletions electrum_dash/bitcoin.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ class opcodes(IntEnum):

OP_INVALIDOPCODE = 0xff

OP_EXCHANGEADDR = 0xe0

def hex(self) -> str:
return bytes([self]).hex()

Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand All @@ -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:
Expand All @@ -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()


Expand All @@ -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}")
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions electrum_dash/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class AbstractNet:
ADDRTYPE_P2SH: int
GENESIS: str
BIP44_COIN_TYPE: int
ADDRTYPE_EXP2PKH: int

@classmethod
def max_checkpoint(cls) -> int:
Expand All @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion electrum_dash/plugins/ledger/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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..."))
Expand Down
21 changes: 19 additions & 2 deletions electrum_dash/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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]


Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)}')
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down

0 comments on commit 647258a

Please sign in to comment.