Skip to content

Commit

Permalink
Fix transaction sign with Ledger
Browse files Browse the repository at this point in the history
  • Loading branch information
levoncrypto committed Jul 14, 2024
2 parents d8ece9a + e3bd997 commit b44083d
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 14 deletions.
39 changes: 35 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 @@ -350,6 +352,8 @@ def hash_decode(x: str) -> bytes:

def hash160_to_b58_address(h160: bytes, addrtype: int) -> str:
s = bytes([addrtype]) + h160
if addrtype == 185:
s = bytes([1, addrtype, 187]) + h160
s = s + sha256d(s)[0:4]
res = base_encode(s, base=58)
return res
Expand All @@ -358,15 +362,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[1], _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 +386,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 +422,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 +436,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 +448,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 +476,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 +672,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
6 changes: 5 additions & 1 deletion 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 All @@ -155,7 +158,7 @@ class BitcoinTestnet(AbstractNet):
XPUB_HEADERS_INV = inv_dict(XPUB_HEADERS)
# DRKV_HEADER = 0x3a8061a0 # DRKV
# DRKP_HEADER = 0x3a805837 # DRKP
BIP44_COIN_TYPE = 136
BIP44_COIN_TYPE = 1
COIN = coins.FiroTestnet()
DIP3_ACTIVATION_HEIGHT = 5000

Expand All @@ -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
2 changes: 2 additions & 0 deletions electrum_dash/plugins/ledger/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,8 @@ def sign_transaction(self, tx, password):
v, h = b58_address_to_hash160(output)
if v == constants.net.ADDRTYPE_P2PKH or v == constants.net.ADDRTYPE_EXP2PKH:
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:
Expand Down
4 changes: 2 additions & 2 deletions electrum_dash/plugins/trezor/trezor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 22 additions & 5 deletions electrum_dash/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_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,
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[3][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
4 changes: 2 additions & 2 deletions electrum_dash/version.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import re


ELECTRUM_VERSION = '4.1.5.3' # version of the client package
APK_VERSION = '4.1.5.3' # read by buildozer.spec
ELECTRUM_VERSION = '4.1.5.4' # version of the client package
APK_VERSION = '4.1.5.4' # read by buildozer.spec

PROTOCOL_VERSION = '1.4.2' # protocol version requested

Expand Down

0 comments on commit b44083d

Please sign in to comment.