integrate PSBT support natively. WIP
This commit is contained in:
@@ -5,10 +5,10 @@ import traceback
|
||||
|
||||
from electrum import ecc
|
||||
from electrum.bitcoin import TYPE_ADDRESS, int_to_hex, var_int, is_segwit_script_type
|
||||
from electrum.bip32 import BIP32Node
|
||||
from electrum.bip32 import BIP32Node, convert_bip32_intpath_to_strpath
|
||||
from electrum.i18n import _
|
||||
from electrum.keystore import Hardware_KeyStore
|
||||
from electrum.transaction import Transaction
|
||||
from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput
|
||||
from electrum.wallet import Standard_Wallet
|
||||
from electrum.util import bfh, bh2u, versiontuple, UserFacingException
|
||||
from electrum.base_wizard import ScriptTypeNotSupported
|
||||
@@ -217,6 +217,8 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
||||
hw_type = 'ledger'
|
||||
device = 'Ledger'
|
||||
|
||||
plugin: 'LedgerPlugin'
|
||||
|
||||
def __init__(self, d):
|
||||
Hardware_KeyStore.__init__(self, d)
|
||||
# Errors and other user interaction is done through the wallet's
|
||||
@@ -231,9 +233,6 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
||||
obj['cfg'] = self.cfg
|
||||
return obj
|
||||
|
||||
def get_derivation(self):
|
||||
return self.derivation
|
||||
|
||||
def get_client(self):
|
||||
return self.plugin.get_client(self).dongleObject
|
||||
|
||||
@@ -260,13 +259,6 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
||||
self.signing = False
|
||||
return wrapper
|
||||
|
||||
def address_id_stripped(self, address):
|
||||
# Strip the leading "m/"
|
||||
change, index = self.get_address_index(address)
|
||||
derivation = self.derivation
|
||||
address_path = "%s/%d/%d"%(derivation, change, index)
|
||||
return address_path[2:]
|
||||
|
||||
def decrypt_message(self, pubkey, message, password):
|
||||
raise UserFacingException(_('Encryption and decryption are currently not supported for {}').format(self.device))
|
||||
|
||||
@@ -277,7 +269,7 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
||||
message_hash = hashlib.sha256(message).hexdigest().upper()
|
||||
# prompt for the PIN before displaying the dialog if necessary
|
||||
client = self.get_client()
|
||||
address_path = self.get_derivation()[2:] + "/%d/%d"%sequence
|
||||
address_path = self.get_derivation_prefix()[2:] + "/%d/%d"%sequence
|
||||
self.handler.show_message("Signing message ...\r\nMessage hash: "+message_hash)
|
||||
try:
|
||||
info = self.get_client().signMessagePrepare(address_path, message)
|
||||
@@ -318,16 +310,13 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
||||
|
||||
@test_pin_unlocked
|
||||
@set_and_unset_signing
|
||||
def sign_transaction(self, tx: Transaction, password):
|
||||
def sign_transaction(self, tx, password):
|
||||
if tx.is_complete():
|
||||
return
|
||||
client = self.get_client()
|
||||
inputs = []
|
||||
inputsPaths = []
|
||||
pubKeys = []
|
||||
chipInputs = []
|
||||
redeemScripts = []
|
||||
signatures = []
|
||||
changePath = ""
|
||||
output = None
|
||||
p2shTransaction = False
|
||||
@@ -336,60 +325,52 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
||||
self.get_client() # prompt for the PIN before displaying the dialog if necessary
|
||||
|
||||
# Fetch inputs of the transaction to sign
|
||||
derivations = self.get_tx_derivations(tx)
|
||||
for txin in tx.inputs():
|
||||
if txin['type'] == 'coinbase':
|
||||
if txin.is_coinbase():
|
||||
self.give_error("Coinbase not supported") # should never happen
|
||||
|
||||
if txin['type'] in ['p2sh']:
|
||||
if txin.script_type in ['p2sh']:
|
||||
p2shTransaction = True
|
||||
|
||||
if txin['type'] in ['p2wpkh-p2sh', 'p2wsh-p2sh']:
|
||||
if txin.script_type in ['p2wpkh-p2sh', 'p2wsh-p2sh']:
|
||||
if not self.get_client_electrum().supports_segwit():
|
||||
self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT)
|
||||
segwitTransaction = True
|
||||
|
||||
if txin['type'] in ['p2wpkh', 'p2wsh']:
|
||||
if txin.script_type in ['p2wpkh', 'p2wsh']:
|
||||
if not self.get_client_electrum().supports_native_segwit():
|
||||
self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT)
|
||||
segwitTransaction = True
|
||||
|
||||
pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)
|
||||
for i, x_pubkey in enumerate(x_pubkeys):
|
||||
if x_pubkey in derivations:
|
||||
signingPos = i
|
||||
s = derivations.get(x_pubkey)
|
||||
hwAddress = "%s/%d/%d" % (self.get_derivation()[2:], s[0], s[1])
|
||||
break
|
||||
else:
|
||||
self.give_error("No matching x_key for sign_transaction") # should never happen
|
||||
my_pubkey, full_path = self.find_my_pubkey_in_txinout(txin)
|
||||
if not full_path:
|
||||
self.give_error("No matching pubkey for sign_transaction") # should never happen
|
||||
full_path = convert_bip32_intpath_to_strpath(full_path)
|
||||
|
||||
redeemScript = Transaction.get_preimage_script(txin)
|
||||
txin_prev_tx = txin.get('prev_tx')
|
||||
txin_prev_tx = txin.utxo
|
||||
if txin_prev_tx is None and not Transaction.is_segwit_input(txin):
|
||||
raise UserFacingException(_('Offline signing with {} is not supported for legacy inputs.').format(self.device))
|
||||
txin_prev_tx_raw = txin_prev_tx.raw if txin_prev_tx else None
|
||||
raise UserFacingException(_('Missing previous tx for legacy input.'))
|
||||
txin_prev_tx_raw = txin_prev_tx.serialize() if txin_prev_tx else None
|
||||
inputs.append([txin_prev_tx_raw,
|
||||
txin['prevout_n'],
|
||||
txin.prevout.out_idx,
|
||||
redeemScript,
|
||||
txin['prevout_hash'],
|
||||
signingPos,
|
||||
txin.get('sequence', 0xffffffff - 1),
|
||||
txin.get('value')])
|
||||
inputsPaths.append(hwAddress)
|
||||
pubKeys.append(pubkeys)
|
||||
txin.prevout.txid.hex(),
|
||||
my_pubkey,
|
||||
txin.nsequence,
|
||||
txin.value_sats()])
|
||||
inputsPaths.append(full_path)
|
||||
|
||||
# Sanity check
|
||||
if p2shTransaction:
|
||||
for txin in tx.inputs():
|
||||
if txin['type'] != 'p2sh':
|
||||
if txin.script_type != 'p2sh':
|
||||
self.give_error("P2SH / regular input mixed in same transaction not supported") # should never happen
|
||||
|
||||
txOutput = var_int(len(tx.outputs()))
|
||||
for o in tx.outputs():
|
||||
output_type, addr, amount = o.type, o.address, o.value
|
||||
txOutput += int_to_hex(amount, 8)
|
||||
script = tx.pay_script(output_type, addr)
|
||||
txOutput += int_to_hex(o.value, 8)
|
||||
script = o.scriptpubkey.hex()
|
||||
txOutput += var_int(len(script)//2)
|
||||
txOutput += script
|
||||
txOutput = bfh(txOutput)
|
||||
@@ -403,21 +384,21 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
||||
self.give_error("Transaction with more than 2 outputs not supported")
|
||||
has_change = False
|
||||
any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
|
||||
for o in tx.outputs():
|
||||
assert o.type == TYPE_ADDRESS
|
||||
info = tx.output_info.get(o.address)
|
||||
if (info is not None) and len(tx.outputs()) > 1 \
|
||||
for txout in tx.outputs():
|
||||
assert txout.address
|
||||
if txout.is_mine and len(tx.outputs()) > 1 \
|
||||
and not has_change:
|
||||
index = info.address_index
|
||||
# prioritise hiding outputs on the 'change' branch from user
|
||||
# because no more than one change address allowed
|
||||
if info.is_change == any_output_on_change_branch:
|
||||
changePath = self.get_derivation()[2:] + "/%d/%d"%index
|
||||
if txout.is_change == any_output_on_change_branch:
|
||||
my_pubkey, changePath = self.find_my_pubkey_in_txinout(txout)
|
||||
assert changePath
|
||||
changePath = convert_bip32_intpath_to_strpath(changePath)
|
||||
has_change = True
|
||||
else:
|
||||
output = o.address
|
||||
output = txout.address
|
||||
else:
|
||||
output = o.address
|
||||
output = txout.address
|
||||
|
||||
self.handler.show_message(_("Confirm Transaction on your Ledger device..."))
|
||||
try:
|
||||
@@ -467,7 +448,10 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
||||
singleInput, redeemScripts[inputIndex], version=tx.version)
|
||||
inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime)
|
||||
inputSignature[0] = 0x30 # force for 1.4.9+
|
||||
signatures.append(inputSignature)
|
||||
my_pubkey = inputs[inputIndex][4]
|
||||
tx.add_signature_to_txin(txin_idx=inputIndex,
|
||||
signing_pubkey=my_pubkey.hex(),
|
||||
sig=inputSignature.hex())
|
||||
inputIndex = inputIndex + 1
|
||||
else:
|
||||
while inputIndex < len(inputs):
|
||||
@@ -488,7 +472,10 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
||||
# Sign input with the provided PIN
|
||||
inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime)
|
||||
inputSignature[0] = 0x30 # force for 1.4.9+
|
||||
signatures.append(inputSignature)
|
||||
my_pubkey = inputs[inputIndex][4]
|
||||
tx.add_signature_to_txin(txin_idx=inputIndex,
|
||||
signing_pubkey=my_pubkey.hex(),
|
||||
sig=inputSignature.hex())
|
||||
inputIndex = inputIndex + 1
|
||||
firstTransaction = False
|
||||
except UserWarning:
|
||||
@@ -508,16 +495,11 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
||||
finally:
|
||||
self.handler.finished()
|
||||
|
||||
for i, txin in enumerate(tx.inputs()):
|
||||
signingPos = inputs[i][4]
|
||||
tx.add_signature_to_txin(i, signingPos, bh2u(signatures[i]))
|
||||
tx.raw = tx.serialize()
|
||||
|
||||
@test_pin_unlocked
|
||||
@set_and_unset_signing
|
||||
def show_address(self, sequence, txin_type):
|
||||
client = self.get_client()
|
||||
address_path = self.get_derivation()[2:] + "/%d/%d"%sequence
|
||||
address_path = self.get_derivation_prefix()[2:] + "/%d/%d"%sequence
|
||||
self.handler.show_message(_("Showing address ..."))
|
||||
segwit = is_segwit_script_type(txin_type)
|
||||
segwitNative = txin_type == 'p2wpkh'
|
||||
|
||||
Reference in New Issue
Block a user