Replace btchip-python with our own stripped down version of it
This commit is contained in:
parent
f7d04ff2eb
commit
681a52d3da
9
hwilib/devices/btchip/README.md
Normal file
9
hwilib/devices/btchip/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Ledger Nano S Library
|
||||
|
||||
This is a stripped down and modified version of the official [btchip-python](https://github.com/LedgerHQ/btchip-python) library.
|
||||
|
||||
This stripped down version was made at commit [fe82d7f5638169f583a445b8e200fd1c9f3ea218](https://github.com/LedgerHQ/btchip-python/tree/fe82d7f5638169f583a445b8e200fd1c9f3ea218).
|
||||
|
||||
## Changes
|
||||
|
||||
- Removed support for Ledger HW.1 and other unused things
|
||||
19
hwilib/devices/btchip/__init__.py
Normal file
19
hwilib/devices/btchip/__init__.py
Normal file
@ -0,0 +1,19 @@
|
||||
"""
|
||||
*******************************************************************************
|
||||
* BTChip Bitcoin Hardware Wallet Python API
|
||||
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
********************************************************************************
|
||||
"""
|
||||
|
||||
165
hwilib/devices/btchip/bitcoinTransaction.py
Normal file
165
hwilib/devices/btchip/bitcoinTransaction.py
Normal file
@ -0,0 +1,165 @@
|
||||
"""
|
||||
*******************************************************************************
|
||||
* BTChip Bitcoin Hardware Wallet Python API
|
||||
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
********************************************************************************
|
||||
"""
|
||||
|
||||
from .bitcoinVarint import *
|
||||
from binascii import hexlify
|
||||
|
||||
class bitcoinInput:
|
||||
|
||||
def __init__(self, bufferOffset=None):
|
||||
self.prevOut = ""
|
||||
self.script = ""
|
||||
self.sequence = ""
|
||||
if bufferOffset is not None:
|
||||
buf = bufferOffset['buffer']
|
||||
offset = bufferOffset['offset']
|
||||
self.prevOut = buf[offset:offset + 36]
|
||||
offset += 36
|
||||
scriptSize = readVarint(buf, offset)
|
||||
offset += scriptSize['size']
|
||||
self.script = buf[offset:offset + scriptSize['value']]
|
||||
offset += scriptSize['value']
|
||||
self.sequence = buf[offset:offset + 4]
|
||||
offset += 4
|
||||
bufferOffset['offset'] = offset
|
||||
|
||||
def serialize(self):
|
||||
result = []
|
||||
result.extend(self.prevOut)
|
||||
writeVarint(len(self.script), result)
|
||||
result.extend(self.script)
|
||||
result.extend(self.sequence)
|
||||
return result
|
||||
|
||||
def __str__(self):
|
||||
buf = "Prevout : " + hexlify(self.prevOut) + "\r\n"
|
||||
buf += "Script : " + hexlify(self.script) + "\r\n"
|
||||
buf += "Sequence : " + hexlify(self.sequence) + "\r\n"
|
||||
return buf
|
||||
|
||||
class bitcoinOutput:
|
||||
|
||||
def __init__(self, bufferOffset=None):
|
||||
self.amount = ""
|
||||
self.script = ""
|
||||
if bufferOffset is not None:
|
||||
buf = bufferOffset['buffer']
|
||||
offset = bufferOffset['offset']
|
||||
self.amount = buf[offset:offset + 8]
|
||||
offset += 8
|
||||
scriptSize = readVarint(buf, offset)
|
||||
offset += scriptSize['size']
|
||||
self.script = buf[offset:offset + scriptSize['value']]
|
||||
offset += scriptSize['value']
|
||||
bufferOffset['offset'] = offset
|
||||
|
||||
def serialize(self):
|
||||
result = []
|
||||
result.extend(self.amount)
|
||||
writeVarint(len(self.script), result)
|
||||
result.extend(self.script)
|
||||
return result
|
||||
|
||||
def __str__(self):
|
||||
buf = "Amount : " + hexlify(self.amount) + "\r\n"
|
||||
buf += "Script : " + hexlify(self.script) + "\r\n"
|
||||
return buf
|
||||
|
||||
|
||||
class bitcoinTransaction:
|
||||
|
||||
def __init__(self, data=None):
|
||||
self.version = ""
|
||||
self.inputs = []
|
||||
self.outputs = []
|
||||
self.lockTime = ""
|
||||
self.witness = False
|
||||
self.witnessScript = ""
|
||||
if data is not None:
|
||||
offset = 0
|
||||
self.version = data[offset:offset + 4]
|
||||
offset += 4
|
||||
if (data[offset] == 0) and (data[offset + 1] != 0):
|
||||
offset += 2
|
||||
self.witness = True
|
||||
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']
|
||||
if self.witness:
|
||||
self.witnessScript = data[offset : len(data) - 4]
|
||||
self.lockTime = data[len(data) - 4:]
|
||||
else:
|
||||
self.lockTime = data[offset:offset + 4]
|
||||
|
||||
def serialize(self, skipOutputLocktime=False, skipWitness=False):
|
||||
if skipWitness or (not self.witness):
|
||||
useWitness = False
|
||||
else:
|
||||
useWitness = True
|
||||
result = []
|
||||
result.extend(self.version)
|
||||
if useWitness:
|
||||
result.append(0x00)
|
||||
result.append(0x01)
|
||||
writeVarint(len(self.inputs), result)
|
||||
for trinput in self.inputs:
|
||||
result.extend(trinput.serialize())
|
||||
if not skipOutputLocktime:
|
||||
writeVarint(len(self.outputs), result)
|
||||
for troutput in self.outputs:
|
||||
result.extend(troutput.serialize())
|
||||
if useWitness:
|
||||
result.extend(self.witnessScript)
|
||||
result.extend(self.lockTime)
|
||||
return result
|
||||
|
||||
def serializeOutputs(self):
|
||||
result = []
|
||||
writeVarint(len(self.outputs), result)
|
||||
for troutput in self.outputs:
|
||||
result.extend(troutput.serialize())
|
||||
return result
|
||||
|
||||
def __str__(self):
|
||||
buf = "Version : " + hexlify(self.version) + "\r\n"
|
||||
index = 1
|
||||
for trinput in self.inputs:
|
||||
buf += "Input #" + str(index) + "\r\n"
|
||||
buf += str(trinput)
|
||||
index+=1
|
||||
index = 1
|
||||
for troutput in self.outputs:
|
||||
buf += "Output #" + str(index) + "\r\n"
|
||||
buf += str(troutput)
|
||||
index+=1
|
||||
buf += "Locktime : " + hexlify(self.lockTime) + "\r\n"
|
||||
if self.witness:
|
||||
buf += "Witness script : " + hexlify(self.witnessScript) + "\r\n"
|
||||
return buf
|
||||
63
hwilib/devices/btchip/bitcoinVarint.py
Normal file
63
hwilib/devices/btchip/bitcoinVarint.py
Normal file
@ -0,0 +1,63 @@
|
||||
"""
|
||||
*******************************************************************************
|
||||
* BTChip Bitcoin Hardware Wallet Python API
|
||||
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
********************************************************************************
|
||||
"""
|
||||
|
||||
from .btchipException import BTChipException
|
||||
|
||||
def readVarint(buffer, offset):
|
||||
varintSize = 0
|
||||
value = 0
|
||||
if (buffer[offset] < 0xfd):
|
||||
value = buffer[offset]
|
||||
varintSize = 1
|
||||
elif (buffer[offset] == 0xfd):
|
||||
value = (buffer[offset + 2] << 8) | (buffer[offset + 1])
|
||||
varintSize = 3
|
||||
elif (buffer[offset] == 0xfe):
|
||||
value = (buffer[offset + 4] << 24) | (buffer[offset + 3] << 16) | (buffer[offset + 2] << 8) | (buffer[offset + 1])
|
||||
varintSize = 5
|
||||
else:
|
||||
raise BTChipException("unsupported varint")
|
||||
return { "value": value, "size": varintSize }
|
||||
|
||||
def writeVarint(value, buffer):
|
||||
if (value < 0xfd):
|
||||
buffer.append(value)
|
||||
elif (value <= 0xffff):
|
||||
buffer.append(0xfd)
|
||||
buffer.append(value & 0xff)
|
||||
buffer.append((value >> 8) & 0xff)
|
||||
elif (value <= 0xffffffff):
|
||||
buffer.append(0xfe)
|
||||
buffer.append(value & 0xff)
|
||||
buffer.append((value >> 8) & 0xff)
|
||||
buffer.append((value >> 16) & 0xff)
|
||||
buffer.append((value >> 24) & 0xff)
|
||||
else:
|
||||
raise BTChipException("unsupported encoding")
|
||||
return buffer
|
||||
|
||||
def getVarintSize(value):
|
||||
if (value < 0xfd):
|
||||
return 1
|
||||
elif (value <= 0xffff):
|
||||
return 3
|
||||
elif (value <= 0xffffffff):
|
||||
return 5
|
||||
else:
|
||||
raise BTChipException("unsupported encoding")
|
||||
401
hwilib/devices/btchip/btchip.py
Normal file
401
hwilib/devices/btchip/btchip.py
Normal file
@ -0,0 +1,401 @@
|
||||
"""
|
||||
*******************************************************************************
|
||||
* BTChip Bitcoin Hardware Wallet Python API
|
||||
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
********************************************************************************
|
||||
"""
|
||||
|
||||
from .btchipComm import *
|
||||
from .bitcoinTransaction import *
|
||||
from .bitcoinVarint import *
|
||||
from .btchipException import *
|
||||
from .btchipHelpers import *
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
class btchip:
|
||||
BTCHIP_CLA = 0xe0
|
||||
BTCHIP_JC_EXT_CLA = 0xf0
|
||||
|
||||
BTCHIP_INS_SET_ALTERNATE_COIN_VERSION = 0x14
|
||||
BTCHIP_INS_SETUP = 0x20
|
||||
BTCHIP_INS_VERIFY_PIN = 0x22
|
||||
BTCHIP_INS_GET_OPERATION_MODE = 0x24
|
||||
BTCHIP_INS_SET_OPERATION_MODE = 0x26
|
||||
BTCHIP_INS_SET_KEYMAP = 0x28
|
||||
BTCHIP_INS_SET_COMM_PROTOCOL = 0x2a
|
||||
BTCHIP_INS_GET_WALLET_PUBLIC_KEY = 0x40
|
||||
BTCHIP_INS_GET_TRUSTED_INPUT = 0x42
|
||||
BTCHIP_INS_HASH_INPUT_START = 0x44
|
||||
BTCHIP_INS_HASH_INPUT_FINALIZE = 0x46
|
||||
BTCHIP_INS_HASH_SIGN = 0x48
|
||||
BTCHIP_INS_HASH_INPUT_FINALIZE_FULL = 0x4a
|
||||
BTCHIP_INS_GET_INTERNAL_CHAIN_INDEX = 0x4c
|
||||
BTCHIP_INS_SIGN_MESSAGE = 0x4e
|
||||
BTCHIP_INS_GET_TRANSACTION_LIMIT = 0xa0
|
||||
BTCHIP_INS_SET_TRANSACTION_LIMIT = 0xa2
|
||||
BTCHIP_INS_IMPORT_PRIVATE_KEY = 0xb0
|
||||
BTCHIP_INS_GET_PUBLIC_KEY = 0xb2
|
||||
BTCHIP_INS_DERIVE_BIP32_KEY = 0xb4
|
||||
BTCHIP_INS_SIGNVERIFY_IMMEDIATE = 0xb6
|
||||
BTCHIP_INS_GET_RANDOM = 0xc0
|
||||
BTCHIP_INS_GET_ATTESTATION = 0xc2
|
||||
BTCHIP_INS_GET_FIRMWARE_VERSION = 0xc4
|
||||
BTCHIP_INS_COMPOSE_MOFN_ADDRESS = 0xc6
|
||||
BTCHIP_INS_GET_POS_SEED = 0xca
|
||||
|
||||
BTCHIP_INS_EXT_GET_HALF_PUBLIC_KEY = 0x20
|
||||
BTCHIP_INS_EXT_CACHE_PUT_PUBLIC_KEY = 0x22
|
||||
BTCHIP_INS_EXT_CACHE_HAS_PUBLIC_KEY = 0x24
|
||||
BTCHIP_INS_EXT_CACHE_GET_FEATURES = 0x26
|
||||
|
||||
OPERATION_MODE_WALLET = 0x01
|
||||
OPERATION_MODE_RELAXED_WALLET = 0x02
|
||||
OPERATION_MODE_SERVER = 0x04
|
||||
OPERATION_MODE_DEVELOPER = 0x08
|
||||
|
||||
FEATURE_UNCOMPRESSED_KEYS = 0x01
|
||||
FEATURE_RFC6979 = 0x02
|
||||
FEATURE_FREE_SIGHASHTYPE = 0x04
|
||||
FEATURE_NO_2FA_P2SH = 0x08
|
||||
|
||||
QWERTY_KEYMAP = bytearray(unhexlify("000000000000000000000000760f00d4ffffffc7000000782c1e3420212224342627252e362d3738271e1f202122232425263333362e37381f0405060708090a0b0c0d0e0f101112131415161718191a1b1c1d2f3130232d350405060708090a0b0c0d0e0f101112131415161718191a1b1c1d2f313035"))
|
||||
QWERTZ_KEYMAP = bytearray(unhexlify("000000000000000000000000760f00d4ffffffc7000000782c1e3420212224342627252e362d3738271e1f202122232425263333362e37381f0405060708090a0b0c0d0e0f101112131415161718191a1b1d1c2f3130232d350405060708090a0b0c0d0e0f101112131415161718191a1b1d1c2f313035"))
|
||||
AZERTY_KEYMAP = bytearray(unhexlify("08000000010000200100007820c8ffc3feffff07000000002c38202030341e21222d352e102e3637271e1f202122232425263736362e37101f1405060708090a0b0c0d0e0f331112130415161718191d1b1c1a2f64302f2d351405060708090a0b0c0d0e0f331112130415161718191d1b1c1a2f643035"))
|
||||
|
||||
def __init__(self, dongle):
|
||||
self.dongle = dongle
|
||||
self.needKeyCache = False
|
||||
try:
|
||||
firmware = self.getFirmwareVersion()['version']
|
||||
self.multiOutputSupported = tuple(map(int, (firmware.split(".")))) >= (1, 1, 4)
|
||||
if self.multiOutputSupported:
|
||||
self.scriptBlockLength = 50
|
||||
else:
|
||||
self.scriptBlockLength = 255
|
||||
except:
|
||||
pass
|
||||
|
||||
def getWalletPublicKey(self, path, showOnScreen=False, segwit=False, segwitNative=False, cashAddr=False):
|
||||
result = {}
|
||||
donglePath = parse_bip32_path(path)
|
||||
if self.needKeyCache:
|
||||
self.resolvePublicKeysInPath(path)
|
||||
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_GET_WALLET_PUBLIC_KEY, 0x01 if showOnScreen else 0x00, 0x03 if cashAddr else 0x02 if segwitNative else 0x01 if segwit else 0x00, len(donglePath) ]
|
||||
apdu.extend(donglePath)
|
||||
response = self.dongle.exchange(bytearray(apdu))
|
||||
offset = 0
|
||||
result['publicKey'] = response[offset + 1 : offset + 1 + response[offset]]
|
||||
offset = offset + 1 + response[offset]
|
||||
result['address'] = str(response[offset + 1 : offset + 1 + response[offset]])
|
||||
offset = offset + 1 + response[offset]
|
||||
result['chainCode'] = response[offset : offset + 32]
|
||||
return result
|
||||
|
||||
def getTrustedInput(self, transaction, index):
|
||||
result = {}
|
||||
# Header
|
||||
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_GET_TRUSTED_INPUT, 0x00, 0x00 ]
|
||||
params = bytearray.fromhex("%.8x" % (index))
|
||||
params.extend(transaction.version)
|
||||
writeVarint(len(transaction.inputs), params)
|
||||
apdu.append(len(params))
|
||||
apdu.extend(params)
|
||||
self.dongle.exchange(bytearray(apdu))
|
||||
# Each input
|
||||
for trinput in transaction.inputs:
|
||||
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_GET_TRUSTED_INPUT, 0x80, 0x00 ]
|
||||
params = bytearray(trinput.prevOut)
|
||||
writeVarint(len(trinput.script), params)
|
||||
apdu.append(len(params))
|
||||
apdu.extend(params)
|
||||
self.dongle.exchange(bytearray(apdu))
|
||||
offset = 0
|
||||
while True:
|
||||
blockLength = 251
|
||||
if ((offset + blockLength) < len(trinput.script)):
|
||||
dataLength = blockLength
|
||||
else:
|
||||
dataLength = len(trinput.script) - offset
|
||||
params = bytearray(trinput.script[offset : offset + dataLength])
|
||||
if ((offset + dataLength) == len(trinput.script)):
|
||||
params.extend(trinput.sequence)
|
||||
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_GET_TRUSTED_INPUT, 0x80, 0x00, len(params) ]
|
||||
apdu.extend(params)
|
||||
self.dongle.exchange(bytearray(apdu))
|
||||
offset += dataLength
|
||||
if (offset >= len(trinput.script)):
|
||||
break
|
||||
# Number of outputs
|
||||
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_GET_TRUSTED_INPUT, 0x80, 0x00 ]
|
||||
params = []
|
||||
writeVarint(len(transaction.outputs), params)
|
||||
apdu.append(len(params))
|
||||
apdu.extend(params)
|
||||
self.dongle.exchange(bytearray(apdu))
|
||||
# Each output
|
||||
indexOutput = 0
|
||||
for troutput in transaction.outputs:
|
||||
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_GET_TRUSTED_INPUT, 0x80, 0x00 ]
|
||||
params = bytearray(troutput.amount)
|
||||
writeVarint(len(troutput.script), params)
|
||||
apdu.append(len(params))
|
||||
apdu.extend(params)
|
||||
self.dongle.exchange(bytearray(apdu))
|
||||
offset = 0
|
||||
while (offset < len(troutput.script)):
|
||||
blockLength = 255
|
||||
if ((offset + blockLength) < len(troutput.script)):
|
||||
dataLength = blockLength
|
||||
else:
|
||||
dataLength = len(troutput.script) - offset
|
||||
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_GET_TRUSTED_INPUT, 0x80, 0x00, dataLength ]
|
||||
apdu.extend(troutput.script[offset : offset + dataLength])
|
||||
self.dongle.exchange(bytearray(apdu))
|
||||
offset += dataLength
|
||||
# Locktime
|
||||
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_GET_TRUSTED_INPUT, 0x80, 0x00, len(transaction.lockTime) ]
|
||||
apdu.extend(transaction.lockTime)
|
||||
response = self.dongle.exchange(bytearray(apdu))
|
||||
result['trustedInput'] = True
|
||||
result['value'] = response
|
||||
return result
|
||||
|
||||
def startUntrustedTransaction(self, newTransaction, inputIndex, outputList, redeemScript, version=0x01, cashAddr=False):
|
||||
# Start building a fake transaction with the passed inputs
|
||||
segwit = False
|
||||
if newTransaction:
|
||||
for passedOutput in outputList:
|
||||
if ('witness' in passedOutput) and passedOutput['witness']:
|
||||
segwit = True
|
||||
break
|
||||
if newTransaction:
|
||||
if segwit:
|
||||
p2 = 0x03 if cashAddr else 0x02
|
||||
else:
|
||||
p2 = 0x00
|
||||
else:
|
||||
p2 = 0x80
|
||||
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_START, 0x00, p2 ]
|
||||
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 ('witness' in passedOutput) and passedOutput['witness']:
|
||||
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 = bitcoinTransaction(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 untrustedHashSign(self, path, pin="", lockTime=0, sighashType=0x01):
|
||||
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)
|
||||
apdu.append(len(params))
|
||||
apdu.extend(params)
|
||||
result = self.dongle.exchange(bytearray(apdu))
|
||||
result[0] = 0x30
|
||||
return result
|
||||
|
||||
def signMessagePrepareV2(self, path, message):
|
||||
donglePath = parse_bip32_path(path)
|
||||
if self.needKeyCache:
|
||||
self.resolvePublicKeysInPath(path)
|
||||
result = {}
|
||||
offset = 0
|
||||
encryptedOutputData = b""
|
||||
while (offset < len(message)):
|
||||
params = [];
|
||||
if offset == 0:
|
||||
params.extend(donglePath)
|
||||
params.append((len(message) >> 8) & 0xff)
|
||||
params.append(len(message) & 0xff)
|
||||
p2 = 0x01
|
||||
else:
|
||||
p2 = 0x80
|
||||
blockLength = 255 - len(params)
|
||||
if ((offset + blockLength) < len(message)):
|
||||
dataLength = blockLength
|
||||
else:
|
||||
dataLength = len(message) - offset
|
||||
params.extend(bytearray(message[offset : offset + dataLength]))
|
||||
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_SIGN_MESSAGE, 0x00, p2 ]
|
||||
apdu.append(len(params))
|
||||
apdu.extend(params)
|
||||
response = self.dongle.exchange(bytearray(apdu))
|
||||
encryptedOutputData = encryptedOutputData + response[1 : 1 + response[0]]
|
||||
offset += blockLength
|
||||
result['confirmationNeeded'] = response[1 + response[0]] != 0x00
|
||||
result['confirmationType'] = response[1 + response[0]]
|
||||
if result['confirmationType'] == 0x03:
|
||||
offset = 1 + response[0] + 1
|
||||
result['secureScreenData'] = response[offset:]
|
||||
result['encryptedOutputData'] = encryptedOutputData
|
||||
|
||||
return result
|
||||
|
||||
def signMessagePrepare(self, path, message):
|
||||
try:
|
||||
result = self.signMessagePrepareV2(path, message)
|
||||
except BTChipException as e:
|
||||
if (e.sw == 0x6b00): # Old firmware version, try older method
|
||||
result = self.signMessagePrepareV1(path, message)
|
||||
else:
|
||||
raise
|
||||
return result
|
||||
|
||||
def signMessageSign(self, pin=""):
|
||||
if isinstance(pin, str):
|
||||
pin = pin.encode('utf-8')
|
||||
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_SIGN_MESSAGE, 0x80, 0x00 ]
|
||||
params = []
|
||||
if pin is not None:
|
||||
params.append(len(pin))
|
||||
params.extend(bytearray(pin))
|
||||
else:
|
||||
params.append(0x00)
|
||||
apdu.append(len(params))
|
||||
apdu.extend(params)
|
||||
response = self.dongle.exchange(bytearray(apdu))
|
||||
return response
|
||||
|
||||
def getFirmwareVersion(self):
|
||||
result = {}
|
||||
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_GET_FIRMWARE_VERSION, 0x00, 0x00, 0x00 ]
|
||||
try:
|
||||
response = self.dongle.exchange(bytearray(apdu))
|
||||
except BTChipException as e:
|
||||
if (e.sw == 0x6985):
|
||||
response = [0x00, 0x00, 0x01, 0x04, 0x03 ]
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
result['compressedKeys'] = (response[0] == 0x01)
|
||||
result['version'] = "%d.%d.%d" % (response[2], response[3], response[4])
|
||||
result['specialVersion'] = response[1]
|
||||
return result
|
||||
147
hwilib/devices/btchip/btchipComm.py
Normal file
147
hwilib/devices/btchip/btchipComm.py
Normal file
@ -0,0 +1,147 @@
|
||||
"""
|
||||
*******************************************************************************
|
||||
* BTChip Bitcoin Hardware Wallet Python API
|
||||
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
********************************************************************************
|
||||
"""
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from .btchipException import *
|
||||
from .ledgerWrapper import wrapCommandAPDU, unwrapResponseAPDU
|
||||
from binascii import hexlify
|
||||
import time
|
||||
import os
|
||||
import struct
|
||||
import socket
|
||||
|
||||
try:
|
||||
import hid
|
||||
HID = True
|
||||
except ImportError:
|
||||
HID = False
|
||||
|
||||
try:
|
||||
from smartcard.Exceptions import NoCardException
|
||||
from smartcard.System import readers
|
||||
from smartcard.util import toHexString, toBytes
|
||||
SCARD = True
|
||||
except ImportError:
|
||||
SCARD = False
|
||||
|
||||
class DongleWait(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def waitFirstResponse(self, timeout):
|
||||
pass
|
||||
|
||||
class Dongle(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def exchange(self, apdu, timeout=20000):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def setWaitImpl(self, waitImpl):
|
||||
self.waitImpl = waitImpl
|
||||
|
||||
class HIDDongleHIDAPI(Dongle, DongleWait):
|
||||
|
||||
def __init__(self, device, ledger=False, debug=False):
|
||||
self.device = device
|
||||
self.ledger = ledger
|
||||
self.debug = debug
|
||||
self.waitImpl = self
|
||||
self.opened = True
|
||||
|
||||
def exchange(self, apdu, timeout=20000):
|
||||
if self.debug:
|
||||
print("=> %s" % hexlify(apdu))
|
||||
if self.ledger:
|
||||
apdu = wrapCommandAPDU(0x0101, apdu, 64)
|
||||
padSize = len(apdu) % 64
|
||||
tmp = apdu
|
||||
if padSize != 0:
|
||||
tmp.extend([0] * (64 - padSize))
|
||||
offset = 0
|
||||
while(offset != len(tmp)):
|
||||
data = tmp[offset:offset + 64]
|
||||
data = bytearray([0]) + data
|
||||
self.device.write(data)
|
||||
offset += 64
|
||||
dataLength = 0
|
||||
dataStart = 2
|
||||
result = self.waitImpl.waitFirstResponse(timeout)
|
||||
if not self.ledger:
|
||||
if result[0] == 0x61: # 61xx : data available
|
||||
self.device.set_nonblocking(False)
|
||||
dataLength = result[1]
|
||||
dataLength += 2
|
||||
if dataLength > 62:
|
||||
remaining = dataLength - 62
|
||||
while(remaining != 0):
|
||||
if remaining > 64:
|
||||
blockLength = 64
|
||||
else:
|
||||
blockLength = remaining
|
||||
result.extend(bytearray(self.device.read(65))[0:blockLength])
|
||||
remaining -= blockLength
|
||||
swOffset = dataLength
|
||||
dataLength -= 2
|
||||
self.device.set_nonblocking(True)
|
||||
else:
|
||||
swOffset = 0
|
||||
else:
|
||||
self.device.set_nonblocking(False)
|
||||
while True:
|
||||
response = unwrapResponseAPDU(0x0101, result, 64)
|
||||
if response is not None:
|
||||
result = response
|
||||
dataStart = 0
|
||||
swOffset = len(response) - 2
|
||||
dataLength = len(response) - 2
|
||||
self.device.set_nonblocking(True)
|
||||
break
|
||||
result.extend(bytearray(self.device.read(65)))
|
||||
sw = (result[swOffset] << 8) + result[swOffset + 1]
|
||||
response = result[dataStart : dataLength + dataStart]
|
||||
if self.debug:
|
||||
print("<= %s%.2x" % (hexlify(response), sw))
|
||||
if sw != 0x9000:
|
||||
raise BTChipException("Invalid status %04x" % sw, sw)
|
||||
return response
|
||||
|
||||
def waitFirstResponse(self, timeout):
|
||||
start = time.time()
|
||||
data = ""
|
||||
while len(data) == 0:
|
||||
data = self.device.read(65)
|
||||
if not len(data):
|
||||
if time.time() - start > timeout:
|
||||
raise BTChipException("Timeout")
|
||||
time.sleep(0.02)
|
||||
return bytearray(data)
|
||||
|
||||
def close(self):
|
||||
if self.opened:
|
||||
try:
|
||||
self.device.close()
|
||||
except:
|
||||
pass
|
||||
self.opened = False
|
||||
28
hwilib/devices/btchip/btchipException.py
Normal file
28
hwilib/devices/btchip/btchipException.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""
|
||||
*******************************************************************************
|
||||
* BTChip Bitcoin Hardware Wallet Python API
|
||||
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
********************************************************************************
|
||||
"""
|
||||
|
||||
class BTChipException(Exception):
|
||||
|
||||
def __init__(self, message, sw=0x6f00):
|
||||
self.message = message
|
||||
self.sw = sw
|
||||
|
||||
def __str__(self):
|
||||
buf = "Exception : " + self.message
|
||||
return buf
|
||||
86
hwilib/devices/btchip/btchipHelpers.py
Normal file
86
hwilib/devices/btchip/btchipHelpers.py
Normal file
@ -0,0 +1,86 @@
|
||||
"""
|
||||
*******************************************************************************
|
||||
* BTChip Bitcoin Hardware Wallet Python API
|
||||
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
********************************************************************************
|
||||
"""
|
||||
|
||||
import decimal
|
||||
import re
|
||||
|
||||
# from pycoin
|
||||
SATOSHI_PER_COIN = decimal.Decimal(1e8)
|
||||
COIN_PER_SATOSHI = decimal.Decimal(1)/SATOSHI_PER_COIN
|
||||
|
||||
def satoshi_to_btc(satoshi_count):
|
||||
if satoshi_count == 0:
|
||||
return decimal.Decimal(0)
|
||||
r = satoshi_count * COIN_PER_SATOSHI
|
||||
return r.normalize()
|
||||
|
||||
def btc_to_satoshi(btc):
|
||||
return int(decimal.Decimal(btc) * SATOSHI_PER_COIN)
|
||||
# /from pycoin
|
||||
|
||||
def writeUint32BE(value, buffer):
|
||||
buffer.append((value >> 24) & 0xff)
|
||||
buffer.append((value >> 16) & 0xff)
|
||||
buffer.append((value >> 8) & 0xff)
|
||||
buffer.append(value & 0xff)
|
||||
return buffer
|
||||
|
||||
def writeUint32LE(value, buffer):
|
||||
buffer.append(value & 0xff)
|
||||
buffer.append((value >> 8) & 0xff)
|
||||
buffer.append((value >> 16) & 0xff)
|
||||
buffer.append((value >> 24) & 0xff)
|
||||
return buffer
|
||||
|
||||
def writeHexAmount(value, buffer):
|
||||
buffer.append(value & 0xff)
|
||||
buffer.append((value >> 8) & 0xff)
|
||||
buffer.append((value >> 16) & 0xff)
|
||||
buffer.append((value >> 24) & 0xff)
|
||||
buffer.append((value >> 32) & 0xff)
|
||||
buffer.append((value >> 40) & 0xff)
|
||||
buffer.append((value >> 48) & 0xff)
|
||||
buffer.append((value >> 56) & 0xff)
|
||||
return buffer
|
||||
|
||||
def writeHexAmountBE(value, buffer):
|
||||
buffer.append((value >> 56) & 0xff)
|
||||
buffer.append((value >> 48) & 0xff)
|
||||
buffer.append((value >> 40) & 0xff)
|
||||
buffer.append((value >> 32) & 0xff)
|
||||
buffer.append((value >> 24) & 0xff)
|
||||
buffer.append((value >> 16) & 0xff)
|
||||
buffer.append((value >> 8) & 0xff)
|
||||
buffer.append(value & 0xff)
|
||||
return buffer
|
||||
|
||||
def parse_bip32_path(path):
|
||||
if len(path) == 0:
|
||||
return bytearray([ 0 ])
|
||||
result = []
|
||||
elements = path.split('/')
|
||||
if len(elements) > 10:
|
||||
raise BTChipException("Path too long")
|
||||
for pathElement in elements:
|
||||
element = re.split('\'|h|H', pathElement)
|
||||
if len(element) == 1:
|
||||
writeUint32BE(int(element[0]), result)
|
||||
else:
|
||||
writeUint32BE(0x80000000 | int(element[0]), result)
|
||||
return bytearray([ len(elements) ] + result)
|
||||
105
hwilib/devices/btchip/btchipUtils.py
Normal file
105
hwilib/devices/btchip/btchipUtils.py
Normal file
@ -0,0 +1,105 @@
|
||||
"""
|
||||
*******************************************************************************
|
||||
* BTChip Bitcoin Hardware Wallet Python API
|
||||
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
********************************************************************************
|
||||
"""
|
||||
|
||||
from .btchipException import *
|
||||
from .bitcoinTransaction import *
|
||||
from .btchipHelpers import *
|
||||
|
||||
def compress_public_key(publicKey):
|
||||
if publicKey[0] == 0x04:
|
||||
if (publicKey[64] & 1) != 0:
|
||||
prefix = 0x03
|
||||
else:
|
||||
prefix = 0x02
|
||||
result = [prefix]
|
||||
result.extend(publicKey[1:33])
|
||||
return bytearray(result)
|
||||
elif publicKey[0] == 0x03 or publicKey[0] == 0x02:
|
||||
return publicKey
|
||||
else:
|
||||
raise BTChipException("Invalid public key format")
|
||||
|
||||
def format_transaction(dongleOutputData, trustedInputsAndInputScripts, version=0x01, lockTime=0):
|
||||
transaction = bitcoinTransaction()
|
||||
transaction.version = []
|
||||
writeUint32LE(version, transaction.version)
|
||||
for item in trustedInputsAndInputScripts:
|
||||
newInput = bitcoinInput()
|
||||
newInput.prevOut = item[0][4:4+36]
|
||||
newInput.script = item[1]
|
||||
if len(item) > 2:
|
||||
newInput.sequence = bytearray(item[2].decode('hex'))
|
||||
else:
|
||||
newInput.sequence = bytearray([0xff, 0xff, 0xff, 0xff])
|
||||
transaction.inputs.append(newInput)
|
||||
result = transaction.serialize(True)
|
||||
result.extend(dongleOutputData)
|
||||
writeUint32LE(lockTime, result)
|
||||
return bytearray(result)
|
||||
|
||||
def get_regular_input_script(sigHashtype, publicKey):
|
||||
if len(sigHashtype) >= 0x4c:
|
||||
raise BTChipException("Invalid sigHashtype")
|
||||
if len(publicKey) >= 0x4c:
|
||||
raise BTChipException("Invalid publicKey")
|
||||
result = [ len(sigHashtype) ]
|
||||
result.extend(sigHashtype)
|
||||
result.append(len(publicKey))
|
||||
result.extend(publicKey)
|
||||
return bytearray(result)
|
||||
|
||||
def write_pushed_data_size(data, buffer):
|
||||
if (len(data) > 0xffff):
|
||||
raise BTChipException("unsupported encoding")
|
||||
if (len(data) < 0x4c):
|
||||
buffer.append(len(data))
|
||||
elif (len(data) > 255):
|
||||
buffer.append(0x4d)
|
||||
buffer.append(len(data) & 0xff)
|
||||
buffer.append((len(data) >> 8) & 0xff)
|
||||
else:
|
||||
buffer.append(0x4c)
|
||||
buffer.append(len(data))
|
||||
return buffer
|
||||
|
||||
|
||||
def get_p2sh_input_script(redeemScript, sigHashtypeList):
|
||||
result = [ 0x00 ]
|
||||
for sigHashtype in sigHashtypeList:
|
||||
write_pushed_data_size(sigHashtype, result)
|
||||
result.extend(sigHashtype)
|
||||
write_pushed_data_size(redeemScript, result)
|
||||
result.extend(redeemScript)
|
||||
return bytearray(result)
|
||||
|
||||
def get_p2pk_input_script(sigHashtype):
|
||||
if len(sigHashtype) >= 0x4c:
|
||||
raise BTChipException("Invalid sigHashtype")
|
||||
result = [ len(sigHashtype) ]
|
||||
result.extend(sigHashtype)
|
||||
return bytearray(result)
|
||||
|
||||
def get_output_script(amountScriptArray):
|
||||
result = [ len(amountScriptArray) ]
|
||||
for amountScript in amountScriptArray:
|
||||
writeHexAmount(btc_to_satoshi(str(amountScript[0])), result)
|
||||
writeVarint(len(amountScript[1]), result)
|
||||
result.extend(amountScript[1])
|
||||
return bytearray(result)
|
||||
|
||||
92
hwilib/devices/btchip/ledgerWrapper.py
Normal file
92
hwilib/devices/btchip/ledgerWrapper.py
Normal file
@ -0,0 +1,92 @@
|
||||
"""
|
||||
*******************************************************************************
|
||||
* BTChip Bitcoin Hardware Wallet Python API
|
||||
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
********************************************************************************
|
||||
"""
|
||||
|
||||
import struct
|
||||
from .btchipException import BTChipException
|
||||
|
||||
def wrapCommandAPDU(channel, command, packetSize):
|
||||
if packetSize < 3:
|
||||
raise BTChipException("Can't handle Ledger framing with less than 3 bytes for the report")
|
||||
sequenceIdx = 0
|
||||
offset = 0
|
||||
result = struct.pack(">HBHH", channel, 0x05, sequenceIdx, len(command))
|
||||
sequenceIdx = sequenceIdx + 1
|
||||
if len(command) > packetSize - 7:
|
||||
blockSize = packetSize - 7
|
||||
else:
|
||||
blockSize = len(command)
|
||||
result += command[offset : offset + blockSize]
|
||||
offset = offset + blockSize
|
||||
while offset != len(command):
|
||||
result += struct.pack(">HBH", channel, 0x05, sequenceIdx)
|
||||
sequenceIdx = sequenceIdx + 1
|
||||
if (len(command) - offset) > packetSize - 5:
|
||||
blockSize = packetSize - 5
|
||||
else:
|
||||
blockSize = len(command) - offset
|
||||
result += command[offset : offset + blockSize]
|
||||
offset = offset + blockSize
|
||||
while (len(result) % packetSize) != 0:
|
||||
result += b"\x00"
|
||||
return bytearray(result)
|
||||
|
||||
def unwrapResponseAPDU(channel, data, packetSize):
|
||||
sequenceIdx = 0
|
||||
offset = 0
|
||||
if ((data is None) or (len(data) < 7 + 5)):
|
||||
return None
|
||||
if struct.unpack(">H", data[offset : offset + 2])[0] != channel:
|
||||
raise BTChipException("Invalid channel")
|
||||
offset += 2
|
||||
if data[offset] != 0x05:
|
||||
raise BTChipException("Invalid tag")
|
||||
offset += 1
|
||||
if struct.unpack(">H", data[offset : offset + 2])[0] != sequenceIdx:
|
||||
raise BTChipException("Invalid sequence")
|
||||
offset += 2
|
||||
responseLength = struct.unpack(">H", data[offset : offset + 2])[0]
|
||||
offset += 2
|
||||
if len(data) < 7 + responseLength:
|
||||
return None
|
||||
if responseLength > packetSize - 7:
|
||||
blockSize = packetSize - 7
|
||||
else:
|
||||
blockSize = responseLength
|
||||
result = data[offset : offset + blockSize]
|
||||
offset += blockSize
|
||||
while (len(result) != responseLength):
|
||||
sequenceIdx = sequenceIdx + 1
|
||||
if (offset == len(data)):
|
||||
return None
|
||||
if struct.unpack(">H", data[offset : offset + 2])[0] != channel:
|
||||
raise BTChipException("Invalid channel")
|
||||
offset += 2
|
||||
if data[offset] != 0x05:
|
||||
raise BTChipException("Invalid tag")
|
||||
offset += 1
|
||||
if struct.unpack(">H", data[offset : offset + 2])[0] != sequenceIdx:
|
||||
raise BTChipException("Invalid sequence")
|
||||
offset += 2
|
||||
if (responseLength - len(result)) > packetSize - 5:
|
||||
blockSize = packetSize - 5
|
||||
else:
|
||||
blockSize = responseLength - len(result)
|
||||
result += data[offset : offset + blockSize]
|
||||
offset += blockSize
|
||||
return bytearray(result)
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
from ..hwwclient import HardwareWalletClient
|
||||
from ..errors import ActionCanceledError, BadArgumentError, DeviceConnectionError, DeviceFailureError, UnavailableActionError
|
||||
from btchip.btchip import *
|
||||
from btchip.btchipUtils import *
|
||||
from .btchip.btchip import *
|
||||
from .btchip.btchipUtils import *
|
||||
import base64
|
||||
import json
|
||||
import struct
|
||||
|
||||
1
setup.py
1
setup.py
@ -15,7 +15,6 @@ setuptools.setup(
|
||||
packages=setuptools.find_packages(exclude=['docs', 'test']),
|
||||
install_requires=[
|
||||
'hidapi', # HID API needed in general
|
||||
'btchip-python', # Ledger Nano S
|
||||
'pyaes',
|
||||
'ecdsa', # Needed for Ledger but their library does not install it
|
||||
'typing_extensions>=3.7',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user