Merge pull request #9 from justjamesdev/master
Sapling - Ledger, Ledger X
This commit is contained in:
commit
cd1c9aa48b
|
@ -1,5 +1,5 @@
|
||||||
btchip-python==0.1.26 \
|
btchip-python==0.1.28 \
|
||||||
--hash=sha256:427d67c5b4f4709605c51dd91d5d44a2ad8f541693673817765271e4b3a1461e
|
--hash=sha256:da09d0d7a6180d428833795ea9a233c3b317ddfcccea8cc6f0eba59435e5dd83
|
||||||
certifi==2018.1.18 \
|
certifi==2018.1.18 \
|
||||||
--hash=sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296 \
|
--hash=sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296 \
|
||||||
--hash=sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d
|
--hash=sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d
|
||||||
|
|
|
@ -2,3 +2,5 @@ Cython>=0.27
|
||||||
trezor>=0.9.0
|
trezor>=0.9.0
|
||||||
keepkey
|
keepkey
|
||||||
btchip-python
|
btchip-python
|
||||||
|
libusb1
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,268 @@
|
||||||
|
from binascii import hexlify, unhexlify
|
||||||
|
from struct import pack, unpack
|
||||||
|
|
||||||
|
from btchip.btchip import btchip
|
||||||
|
from btchip.bitcoinTransaction import bitcoinInput, bitcoinOutput
|
||||||
|
from btchip.bitcoinVarint import readVarint, writeVarint
|
||||||
|
from btchip.btchipHelpers import parse_bip32_path, writeUint32BE
|
||||||
|
|
||||||
|
from electrum_zclassic.transaction import (OVERWINTERED_VERSION_GROUP_ID,
|
||||||
|
SAPLING_VERSION_GROUP_ID)
|
||||||
|
|
||||||
|
|
||||||
|
class zcashTransaction:
|
||||||
|
|
||||||
|
def __init__(self, data=None):
|
||||||
|
self.version = ''
|
||||||
|
self.version_group_id = ''
|
||||||
|
self.inputs = []
|
||||||
|
self.outputs = []
|
||||||
|
self.lockTime = ''
|
||||||
|
self.expiry_height = ''
|
||||||
|
self.value_balance = ''
|
||||||
|
self.overwintered = False
|
||||||
|
self.n_version = 0
|
||||||
|
if data is not None:
|
||||||
|
offset = 0
|
||||||
|
self.version = data[offset:offset + 4]
|
||||||
|
offset += 4
|
||||||
|
header = unpack('<I', self.version)[0]
|
||||||
|
if header & 0x80000000:
|
||||||
|
self.n_version = header & 0x7FFFFFFF
|
||||||
|
self.version_group_id = data[offset:offset + 4]
|
||||||
|
offset += 4
|
||||||
|
version_group_id = unpack('<I', self.version_group_id)[0]
|
||||||
|
if (self.n_version == 3
|
||||||
|
and version_group_id == OVERWINTERED_VERSION_GROUP_ID):
|
||||||
|
self.overwintered = True
|
||||||
|
elif (self.n_version == 4
|
||||||
|
and version_group_id == SAPLING_VERSION_GROUP_ID):
|
||||||
|
self.overwintered = True
|
||||||
|
else:
|
||||||
|
offset -= 4
|
||||||
|
self.version_group_id = ''
|
||||||
|
self.n_version = header
|
||||||
|
inputSize = readVarint(data, offset)
|
||||||
|
offset += inputSize['size']
|
||||||
|
numInputs = inputSize['value']
|
||||||
|
for i in range(numInputs):
|
||||||
|
tmp = { 'buffer': data, 'offset' : offset}
|
||||||
|
self.inputs.append(bitcoinInput(tmp))
|
||||||
|
offset = tmp['offset']
|
||||||
|
outputSize = readVarint(data, offset)
|
||||||
|
offset += outputSize['size']
|
||||||
|
numOutputs = outputSize['value']
|
||||||
|
for i in range(numOutputs):
|
||||||
|
tmp = { 'buffer': data, 'offset' : offset}
|
||||||
|
self.outputs.append(bitcoinOutput(tmp))
|
||||||
|
offset = tmp['offset']
|
||||||
|
self.lockTime = data[offset:offset + 4]
|
||||||
|
if self.overwintered:
|
||||||
|
offset += 4
|
||||||
|
self.expiry_height = data[offset:offset + 4]
|
||||||
|
if self.n_version >= 4:
|
||||||
|
offset += 4
|
||||||
|
self.value_balance = data[offset:offset + 8]
|
||||||
|
|
||||||
|
def serializeOutputs(self):
|
||||||
|
result = []
|
||||||
|
writeVarint(len(self.outputs), result)
|
||||||
|
for troutput in self.outputs:
|
||||||
|
result.extend(troutput.serialize())
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class btchip_zcash(btchip):
|
||||||
|
|
||||||
|
def startUntrustedTransaction(self, newTransaction, inputIndex, outputList,
|
||||||
|
redeemScript, version=0x02,
|
||||||
|
overwintered=False):
|
||||||
|
# Start building a fake transaction with the passed inputs
|
||||||
|
if newTransaction:
|
||||||
|
if overwintered:
|
||||||
|
p2 = 0x05 if version == 4 else 0x04
|
||||||
|
else:
|
||||||
|
p2 = 0x00
|
||||||
|
else:
|
||||||
|
p2 = 0x80
|
||||||
|
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_START, 0x00, p2 ]
|
||||||
|
if overwintered and version == 3:
|
||||||
|
params = bytearray([version, 0x00, 0x00, 0x80, 0x70, 0x82, 0xc4, 0x03])
|
||||||
|
elif overwintered and version == 4:
|
||||||
|
params = bytearray([version, 0x00, 0x00, 0x80, 0x85, 0x20, 0x2f, 0x89])
|
||||||
|
else:
|
||||||
|
params = bytearray([version, 0x00, 0x00, 0x00])
|
||||||
|
writeVarint(len(outputList), params)
|
||||||
|
apdu.append(len(params))
|
||||||
|
apdu.extend(params)
|
||||||
|
self.dongle.exchange(bytearray(apdu))
|
||||||
|
# Loop for each input
|
||||||
|
currentIndex = 0
|
||||||
|
for passedOutput in outputList:
|
||||||
|
if ('sequence' in passedOutput) and passedOutput['sequence']:
|
||||||
|
sequence = bytearray(unhexlify(passedOutput['sequence']))
|
||||||
|
else:
|
||||||
|
sequence = bytearray([0xFF, 0xFF, 0xFF, 0xFF]) # default sequence
|
||||||
|
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_START, 0x80, 0x00 ]
|
||||||
|
params = []
|
||||||
|
script = bytearray(redeemScript)
|
||||||
|
if overwintered:
|
||||||
|
params.append(0x02)
|
||||||
|
elif ('trustedInput' in passedOutput) and passedOutput['trustedInput']:
|
||||||
|
params.append(0x01)
|
||||||
|
else:
|
||||||
|
params.append(0x00)
|
||||||
|
if ('trustedInput' in passedOutput) and passedOutput['trustedInput']:
|
||||||
|
params.append(len(passedOutput['value']))
|
||||||
|
params.extend(passedOutput['value'])
|
||||||
|
if currentIndex != inputIndex:
|
||||||
|
script = bytearray()
|
||||||
|
writeVarint(len(script), params)
|
||||||
|
if len(script) == 0:
|
||||||
|
params.extend(sequence)
|
||||||
|
apdu.append(len(params))
|
||||||
|
apdu.extend(params)
|
||||||
|
self.dongle.exchange(bytearray(apdu))
|
||||||
|
offset = 0
|
||||||
|
while(offset < len(script)):
|
||||||
|
blockLength = 255
|
||||||
|
if ((offset + blockLength) < len(script)):
|
||||||
|
dataLength = blockLength
|
||||||
|
else:
|
||||||
|
dataLength = len(script) - offset
|
||||||
|
params = script[offset : offset + dataLength]
|
||||||
|
if ((offset + dataLength) == len(script)):
|
||||||
|
params.extend(sequence)
|
||||||
|
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_START, 0x80, 0x00, len(params) ]
|
||||||
|
apdu.extend(params)
|
||||||
|
self.dongle.exchange(bytearray(apdu))
|
||||||
|
offset += blockLength
|
||||||
|
currentIndex += 1
|
||||||
|
|
||||||
|
def finalizeInput(self, outputAddress, amount, fees, changePath, rawTx=None):
|
||||||
|
alternateEncoding = False
|
||||||
|
donglePath = parse_bip32_path(changePath)
|
||||||
|
if self.needKeyCache:
|
||||||
|
self.resolvePublicKeysInPath(changePath)
|
||||||
|
result = {}
|
||||||
|
outputs = None
|
||||||
|
if rawTx is not None:
|
||||||
|
try:
|
||||||
|
fullTx = zcashTransaction(bytearray(rawTx))
|
||||||
|
outputs = fullTx.serializeOutputs()
|
||||||
|
if len(donglePath) != 0:
|
||||||
|
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_FINALIZE_FULL, 0xFF, 0x00 ]
|
||||||
|
params = []
|
||||||
|
params.extend(donglePath)
|
||||||
|
apdu.append(len(params))
|
||||||
|
apdu.extend(params)
|
||||||
|
response = self.dongle.exchange(bytearray(apdu))
|
||||||
|
offset = 0
|
||||||
|
while (offset < len(outputs)):
|
||||||
|
blockLength = self.scriptBlockLength
|
||||||
|
if ((offset + blockLength) < len(outputs)):
|
||||||
|
dataLength = blockLength
|
||||||
|
p1 = 0x00
|
||||||
|
else:
|
||||||
|
dataLength = len(outputs) - offset
|
||||||
|
p1 = 0x80
|
||||||
|
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_FINALIZE_FULL, \
|
||||||
|
p1, 0x00, dataLength ]
|
||||||
|
apdu.extend(outputs[offset : offset + dataLength])
|
||||||
|
response = self.dongle.exchange(bytearray(apdu))
|
||||||
|
offset += dataLength
|
||||||
|
alternateEncoding = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if not alternateEncoding:
|
||||||
|
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_FINALIZE, 0x02, 0x00 ]
|
||||||
|
params = []
|
||||||
|
params.append(len(outputAddress))
|
||||||
|
params.extend(bytearray(outputAddress))
|
||||||
|
writeHexAmountBE(btc_to_satoshi(str(amount)), params)
|
||||||
|
writeHexAmountBE(btc_to_satoshi(str(fees)), params)
|
||||||
|
params.extend(donglePath)
|
||||||
|
apdu.append(len(params))
|
||||||
|
apdu.extend(params)
|
||||||
|
response = self.dongle.exchange(bytearray(apdu))
|
||||||
|
result['confirmationNeeded'] = response[1 + response[0]] != 0x00
|
||||||
|
result['confirmationType'] = response[1 + response[0]]
|
||||||
|
if result['confirmationType'] == 0x02:
|
||||||
|
result['keycardData'] = response[1 + response[0] + 1:]
|
||||||
|
if result['confirmationType'] == 0x03:
|
||||||
|
offset = 1 + response[0] + 1
|
||||||
|
keycardDataLength = response[offset]
|
||||||
|
offset = offset + 1
|
||||||
|
result['keycardData'] = response[offset : offset + keycardDataLength]
|
||||||
|
offset = offset + keycardDataLength
|
||||||
|
result['secureScreenData'] = response[offset:]
|
||||||
|
if result['confirmationType'] == 0x04:
|
||||||
|
offset = 1 + response[0] + 1
|
||||||
|
keycardDataLength = response[offset]
|
||||||
|
result['keycardData'] = response[offset + 1 : offset + 1 + keycardDataLength]
|
||||||
|
if outputs == None:
|
||||||
|
result['outputData'] = response[1 : 1 + response[0]]
|
||||||
|
else:
|
||||||
|
result['outputData'] = outputs
|
||||||
|
return result
|
||||||
|
|
||||||
|
def finalizeInputFull(self, outputData):
|
||||||
|
result = {}
|
||||||
|
offset = 0
|
||||||
|
encryptedOutputData = b""
|
||||||
|
while (offset < len(outputData)):
|
||||||
|
blockLength = self.scriptBlockLength
|
||||||
|
if ((offset + blockLength) < len(outputData)):
|
||||||
|
dataLength = blockLength
|
||||||
|
p1 = 0x00
|
||||||
|
else:
|
||||||
|
dataLength = len(outputData) - offset
|
||||||
|
p1 = 0x80
|
||||||
|
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_FINALIZE_FULL, \
|
||||||
|
p1, 0x00, dataLength ]
|
||||||
|
apdu.extend(outputData[offset : offset + dataLength])
|
||||||
|
response = self.dongle.exchange(bytearray(apdu))
|
||||||
|
encryptedOutputData = encryptedOutputData + response[1 : 1 + response[0]]
|
||||||
|
offset += dataLength
|
||||||
|
if len(response) > 1:
|
||||||
|
result['confirmationNeeded'] = response[1 + response[0]] != 0x00
|
||||||
|
result['confirmationType'] = response[1 + response[0]]
|
||||||
|
if result['confirmationType'] == 0x02:
|
||||||
|
result['keycardData'] = response[1 + response[0] + 1:] # legacy
|
||||||
|
if result['confirmationType'] == 0x03:
|
||||||
|
offset = 1 + response[0] + 1
|
||||||
|
keycardDataLength = response[offset]
|
||||||
|
offset = offset + 1
|
||||||
|
result['keycardData'] = response[offset : offset + keycardDataLength]
|
||||||
|
offset = offset + keycardDataLength
|
||||||
|
result['secureScreenData'] = response[offset:]
|
||||||
|
result['encryptedOutputData'] = encryptedOutputData
|
||||||
|
if result['confirmationType'] == 0x04:
|
||||||
|
offset = 1 + response[0] + 1
|
||||||
|
keycardDataLength = response[offset]
|
||||||
|
result['keycardData'] = response[offset + 1 : offset + 1 + keycardDataLength]
|
||||||
|
return result
|
||||||
|
|
||||||
|
def untrustedHashSign(self, path, pin="", lockTime=0, sighashType=0x01,
|
||||||
|
version=0x02, overwintered=False):
|
||||||
|
if isinstance(pin, str):
|
||||||
|
pin = pin.encode('utf-8')
|
||||||
|
donglePath = parse_bip32_path(path)
|
||||||
|
if self.needKeyCache:
|
||||||
|
self.resolvePublicKeysInPath(path)
|
||||||
|
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_SIGN, 0x00, 0x00 ]
|
||||||
|
params = []
|
||||||
|
params.extend(donglePath)
|
||||||
|
params.append(len(pin))
|
||||||
|
params.extend(bytearray(pin))
|
||||||
|
writeUint32BE(lockTime, params)
|
||||||
|
params.append(sighashType)
|
||||||
|
if overwintered:
|
||||||
|
params.extend(bytearray([0]*4))
|
||||||
|
apdu.append(len(params))
|
||||||
|
apdu.extend(params)
|
||||||
|
result = self.dongle.exchange(bytearray(apdu))
|
||||||
|
if not result:
|
||||||
|
return
|
||||||
|
result[0] = 0x30
|
||||||
|
return result
|
|
@ -28,24 +28,29 @@ try:
|
||||||
from btchip.btchipComm import HIDDongleHIDAPI, DongleWait
|
from btchip.btchipComm import HIDDongleHIDAPI, DongleWait
|
||||||
from btchip.btchip import btchip
|
from btchip.btchip import btchip
|
||||||
from btchip.btchipUtils import compress_public_key,format_transaction, get_regular_input_script, get_p2sh_input_script
|
from btchip.btchipUtils import compress_public_key,format_transaction, get_regular_input_script, get_p2sh_input_script
|
||||||
from btchip.bitcoinTransaction import bitcoinTransaction
|
|
||||||
from btchip.btchipFirmwareWizard import checkFirmware, updateFirmware
|
from btchip.btchipFirmwareWizard import checkFirmware, updateFirmware
|
||||||
from btchip.btchipException import BTChipException
|
from btchip.btchipException import BTChipException
|
||||||
|
from .btchip_zcash import btchip_zcash, zcashTransaction
|
||||||
btchip.setAlternateCoinVersions = setAlternateCoinVersions
|
btchip.setAlternateCoinVersions = setAlternateCoinVersions
|
||||||
BTCHIP = True
|
BTCHIP = True
|
||||||
BTCHIP_DEBUG = is_verbose
|
BTCHIP_DEBUG = is_verbose
|
||||||
except ImportError:
|
except ImportError as e:
|
||||||
BTCHIP = False
|
BTCHIP = False
|
||||||
|
|
||||||
MSG_NEEDS_FW_UPDATE_GENERIC = _('Firmware version too old. Please update at') + \
|
MSG_NEEDS_FW_UPDATE_GENERIC = _('Firmware version too old. Please update at') + \
|
||||||
' https://www.ledgerwallet.com'
|
' https://www.ledgerwallet.com'
|
||||||
|
MSG_NEEDS_FW_UPDATE_OVERWINTER = (_('Firmware version too old for '
|
||||||
|
'Overwinter/Sapling support. '
|
||||||
|
'Please update at') +
|
||||||
|
' https://www.ledgerwallet.com')
|
||||||
MULTI_OUTPUT_SUPPORT = '1.1.4'
|
MULTI_OUTPUT_SUPPORT = '1.1.4'
|
||||||
ALTERNATIVE_COIN_VERSION = '1.0.1'
|
ALTERNATIVE_COIN_VERSION = '1.0.1'
|
||||||
|
OVERWINTER_SUPPORT = '1.3.3'
|
||||||
|
|
||||||
|
|
||||||
class Ledger_Client():
|
class Ledger_Client():
|
||||||
def __init__(self, hidDevice):
|
def __init__(self, hidDevice):
|
||||||
self.dongleObject = btchip(hidDevice)
|
self.dongleObject = btchip_zcash(hidDevice)
|
||||||
self.preflightDone = False
|
self.preflightDone = False
|
||||||
|
|
||||||
def is_pairable(self):
|
def is_pairable(self):
|
||||||
|
@ -90,7 +95,7 @@ class Ledger_Client():
|
||||||
@test_pin_unlocked
|
@test_pin_unlocked
|
||||||
def get_xpub(self, bip32_path, xtype):
|
def get_xpub(self, bip32_path, xtype):
|
||||||
self.checkDevice()
|
self.checkDevice()
|
||||||
# bip32_path is of the form 44'/147'/0'
|
# bip32_path is of the form 44'/133'/1'
|
||||||
# S-L-O-W - we don't handle the fingerprint directly, so compute
|
# S-L-O-W - we don't handle the fingerprint directly, so compute
|
||||||
# it manually from the previous node
|
# it manually from the previous node
|
||||||
# This only happens once so it's bearable
|
# This only happens once so it's bearable
|
||||||
|
@ -139,11 +144,15 @@ class Ledger_Client():
|
||||||
def supports_multi_output(self):
|
def supports_multi_output(self):
|
||||||
return self.multiOutputSupported
|
return self.multiOutputSupported
|
||||||
|
|
||||||
|
def supports_overwinter(self):
|
||||||
|
return self.overwinterSupported
|
||||||
|
|
||||||
def perform_hw1_preflight(self):
|
def perform_hw1_preflight(self):
|
||||||
try:
|
try:
|
||||||
firmwareInfo = self.dongleObject.getFirmwareVersion()
|
firmwareInfo = self.dongleObject.getFirmwareVersion()
|
||||||
firmware = firmwareInfo['version']
|
firmware = firmwareInfo['version']
|
||||||
self.multiOutputSupported = versiontuple(firmware) >= versiontuple(MULTI_OUTPUT_SUPPORT)
|
self.multiOutputSupported = versiontuple(firmware) >= versiontuple(MULTI_OUTPUT_SUPPORT)
|
||||||
|
self.overwinterSupported = versiontuple(firmware) >= versiontuple(OVERWINTER_SUPPORT)
|
||||||
self.canAlternateCoinVersions = (versiontuple(firmware) >= versiontuple(ALTERNATIVE_COIN_VERSION)
|
self.canAlternateCoinVersions = (versiontuple(firmware) >= versiontuple(ALTERNATIVE_COIN_VERSION)
|
||||||
and firmwareInfo['specialVersion'] >= 0x20)
|
and firmwareInfo['specialVersion'] >= 0x20)
|
||||||
|
|
||||||
|
@ -190,7 +199,7 @@ class Ledger_Client():
|
||||||
self.perform_hw1_preflight()
|
self.perform_hw1_preflight()
|
||||||
except BTChipException as e:
|
except BTChipException as e:
|
||||||
if (e.sw == 0x6d00 or e.sw == 0x6700):
|
if (e.sw == 0x6d00 or e.sw == 0x6700):
|
||||||
raise Exception(_("Device not in Zclassic mode")) from e
|
raise Exception(_("Device not in ZClassic mode")) from e
|
||||||
raise e
|
raise e
|
||||||
self.preflightDone = True
|
self.preflightDone = True
|
||||||
|
|
||||||
|
@ -389,8 +398,15 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
||||||
# Get trusted inputs from the original transactions
|
# Get trusted inputs from the original transactions
|
||||||
for utxo in inputs:
|
for utxo in inputs:
|
||||||
sequence = int_to_hex(utxo[5], 4)
|
sequence = int_to_hex(utxo[5], 4)
|
||||||
if not p2shTransaction:
|
if tx.overwintered:
|
||||||
txtmp = bitcoinTransaction(bfh(utxo[0]))
|
txtmp = zcashTransaction(bfh(utxo[0]))
|
||||||
|
tmp = bfh(utxo[3])[::-1]
|
||||||
|
tmp += bfh(int_to_hex(utxo[1], 4))
|
||||||
|
tmp += txtmp.outputs[utxo[1]].amount
|
||||||
|
chipInputs.append({'value' : tmp, 'sequence' : sequence})
|
||||||
|
redeemScripts.append(bfh(utxo[2]))
|
||||||
|
elif not p2shTransaction:
|
||||||
|
txtmp = zcashTransaction(bfh(utxo[0]))
|
||||||
trustedInput = self.get_client().getTrustedInput(txtmp, utxo[1])
|
trustedInput = self.get_client().getTrustedInput(txtmp, utxo[1])
|
||||||
trustedInput['sequence'] = sequence
|
trustedInput['sequence'] = sequence
|
||||||
chipInputs.append(trustedInput)
|
chipInputs.append(trustedInput)
|
||||||
|
@ -400,12 +416,52 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
||||||
tmp += bfh(int_to_hex(utxo[1], 4))
|
tmp += bfh(int_to_hex(utxo[1], 4))
|
||||||
chipInputs.append({'value' : tmp, 'sequence' : sequence})
|
chipInputs.append({'value' : tmp, 'sequence' : sequence})
|
||||||
redeemScripts.append(bfh(utxo[2]))
|
redeemScripts.append(bfh(utxo[2]))
|
||||||
|
|
||||||
# Sign all inputs
|
# Sign all inputs
|
||||||
firstTransaction = True
|
firstTransaction = True
|
||||||
inputIndex = 0
|
inputIndex = 0
|
||||||
rawTx = tx.serialize()
|
rawTx = tx.serialize()
|
||||||
self.get_client().enableAlternate2fa(False)
|
self.get_client().enableAlternate2fa(False)
|
||||||
|
if tx.overwintered:
|
||||||
|
self.get_client().startUntrustedTransaction(True, inputIndex, chipInputs,
|
||||||
|
redeemScripts[inputIndex],
|
||||||
|
version=tx.version,
|
||||||
|
overwintered=tx.overwintered)
|
||||||
|
if changePath:
|
||||||
|
# we don't set meaningful outputAddress, amount and fees
|
||||||
|
# as we only care about the alternateEncoding==True branch
|
||||||
|
outputData = self.get_client().finalizeInput(b'', 0, 0, changePath, bfh(rawTx))
|
||||||
|
else:
|
||||||
|
outputData = self.get_client().finalizeInputFull(txOutput)
|
||||||
|
|
||||||
|
if tx.overwintered:
|
||||||
|
inputSignature = self.get_client().untrustedHashSign('',
|
||||||
|
'', lockTime=tx.locktime,
|
||||||
|
version=tx.version,
|
||||||
|
overwintered=tx.overwintered)
|
||||||
|
outputData['outputData'] = txOutput
|
||||||
|
transactionOutput = outputData['outputData']
|
||||||
|
if outputData['confirmationNeeded']:
|
||||||
|
outputData['address'] = output
|
||||||
|
self.handler.finished()
|
||||||
|
pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin
|
||||||
|
if not pin:
|
||||||
|
raise UserWarning()
|
||||||
|
if pin != 'paired':
|
||||||
|
self.handler.show_message(_("Confirmed. Signing Transaction..."))
|
||||||
|
while inputIndex < len(inputs):
|
||||||
|
singleInput = [ chipInputs[inputIndex] ]
|
||||||
|
self.get_client().startUntrustedTransaction(False, 0, singleInput,
|
||||||
|
redeemScripts[inputIndex],
|
||||||
|
version=tx.version,
|
||||||
|
overwintered=tx.overwintered)
|
||||||
|
inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex],
|
||||||
|
pin, lockTime=tx.locktime,
|
||||||
|
version=tx.version,
|
||||||
|
overwintered=tx.overwintered)
|
||||||
|
inputSignature[0] = 0x30 # force for 1.4.9+
|
||||||
|
signatures.append(inputSignature)
|
||||||
|
inputIndex = inputIndex + 1
|
||||||
|
else:
|
||||||
while inputIndex < len(inputs):
|
while inputIndex < len(inputs):
|
||||||
self.get_client().startUntrustedTransaction(firstTransaction, inputIndex,
|
self.get_client().startUntrustedTransaction(firstTransaction, inputIndex,
|
||||||
chipInputs, redeemScripts[inputIndex])
|
chipInputs, redeemScripts[inputIndex])
|
||||||
|
@ -428,7 +484,10 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
||||||
self.handler.show_message(_("Confirmed. Signing Transaction..."))
|
self.handler.show_message(_("Confirmed. Signing Transaction..."))
|
||||||
else:
|
else:
|
||||||
# Sign input with the provided PIN
|
# Sign input with the provided PIN
|
||||||
inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime)
|
inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex],
|
||||||
|
pin, lockTime=tx.locktime,
|
||||||
|
version=tx.version,
|
||||||
|
overwintered=tx.overwintered)
|
||||||
inputSignature[0] = 0x30 # force for 1.4.9+
|
inputSignature[0] = 0x30 # force for 1.4.9+
|
||||||
signatures.append(inputSignature)
|
signatures.append(inputSignature)
|
||||||
inputIndex = inputIndex + 1
|
inputIndex = inputIndex + 1
|
||||||
|
@ -483,7 +542,14 @@ class LedgerPlugin(HW_PluginBase):
|
||||||
(0x2581, 0x3b7c), # HW.1 ledger production
|
(0x2581, 0x3b7c), # HW.1 ledger production
|
||||||
(0x2581, 0x4b7c), # HW.1 ledger test
|
(0x2581, 0x4b7c), # HW.1 ledger test
|
||||||
(0x2c97, 0x0000), # Blue
|
(0x2c97, 0x0000), # Blue
|
||||||
(0x2c97, 0x0001) # Nano-S
|
(0x2c97, 0x0001), # Nano-S
|
||||||
|
(0x2c97, 0x0004), # Nano-X
|
||||||
|
(0x2c97, 0x0005), # RFU
|
||||||
|
(0x2c97, 0x0006), # RFU
|
||||||
|
(0x2c97, 0x0007), # RFU
|
||||||
|
(0x2c97, 0x0008), # RFU
|
||||||
|
(0x2c97, 0x0009), # RFU
|
||||||
|
(0x2c97, 0x000a) # RFU
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, parent, config, name):
|
def __init__(self, parent, config, name):
|
||||||
|
|
Loading…
Reference in New Issue